Index: gwlib/cfg.def
===================================================================
--- gwlib/cfg.def	(revision 5124)
+++ gwlib/cfg.def	(working copy)
@@ -650,7 +650,14 @@
     OCTSTR(field-boxc-id)
 )
 
+SINGLE_GROUP(store-db,
+    OCTSTR(id)
+    OCTSTR(table)
+    OCTSTR(field-uuid)
+    OCTSTR(field-message)
+)
 
+
 SINGLE_GROUP(radius-acct,
     OCTSTR(our-host)
     OCTSTR(our-port)
Index: test/test_store_dump.c
===================================================================
--- test/test_store_dump.c	(revision 5124)
+++ test/test_store_dump.c	(working copy)
@@ -93,7 +93,7 @@
     type = octstr_create("file");
     
     /* init store subsystem */
-    store_init(type, octstr_imm(argv[cf_index]), -1, msg_pack, msg_unpack_wrapper);
+    store_init(type, octstr_imm(argv[cf_index]), -1, msg_pack, msg_unpack_wrapper, NULL);
 
     /* pass every entry in the store to callback print_msg() */
     store_load(print_msg);
Index: gw/bearerbox.c
===================================================================
--- gw/bearerbox.c	(revision 5124)
+++ gw/bearerbox.c	(working copy)
@@ -457,7 +457,7 @@
         log = cfg_get(grp, octstr_imm("store-location"));
         val = cfg_get(grp, octstr_imm("store-type"));
     }
-    if (store_init(val, log, store_dump_freq, msg_pack, msg_unpack_wrapper) == -1)
+    if (store_init(val, log, store_dump_freq, msg_pack, msg_unpack_wrapper, cfg) == -1)
         panic(0, "Could not start with store init failed.");
     octstr_destroy(val);
     octstr_destroy(log);
Index: gw/bb_store_redis.c
===================================================================
--- gw/bb_store_redis.c	(revision 0)
+++ gw/bb_store_redis.c	(revision 0)
@@ -0,0 +1,551 @@
+/* ====================================================================
+ * The Kannel Software License, Version 1.0
+ *
+ * Copyright (c) 2001-2015 Kannel Group
+ * Copyright (c) 1998-2001 WapIT Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Kannel Group (http://www.kannel.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Kannel" and "Kannel Group" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact org@kannel.org.
+ *
+ * 5. Products derived from this software may not be called "Kannel",
+ *    nor may "Kannel" appear in their name, without prior written
+ *    permission of the Kannel Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Kannel Group.  For more information on
+ * the Kannel Group, please see <http://www.kannel.org/>.
+ *
+ * Portions of this software are based upon software originally written at
+ * WapIT Ltd., Helsinki, Finland for the Kannel project.
+ */
+
+/**
+ * bb_store_redis.c - bearerbox box SMS storage/retrieval module using a redis database
+ *
+ * Author: Alejandro Guerrieri, 2015
+ */
+
+#include "gw-config.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include "gwlib/gwlib.h"
+#include "msg.h"
+#include "sms.h"
+#include "bearerbox.h"
+#include "bb_store.h"
+
+#ifdef HAVE_REDIS
+#include "gwlib/dbpool.h"
+
+static Counter *counter;
+static List *loaded;
+
+static DBPool *pool = NULL;
+
+struct store_db_fields {
+    Octstr *table;
+    Octstr *field_uuid;
+    Octstr *field_message;
+};
+
+static struct store_db_fields *fields = NULL;
+
+static int store_redis_dump()
+{
+    /* nothing to do */
+    return 0;
+}
+
+static long store_redis_messages()
+{
+    return counter ? counter_value(counter) : -1;
+}
+
+static void redis_update(const Octstr *sql)
+{
+    int	res;
+    DBPoolConn *pc;
+    List *binds = gwlist_create();
+
+#if defined(DLR_TRACE)
+     debug("store.redis", 0, "sql: %s", octstr_get_cstr(sql));
+#endif
+
+    pc = dbpool_conn_consume(pool);
+    if (pc == NULL) {
+        error(0, "Redis: Database pool got no connection! DB update failed!");
+        return;
+    }
+
+	res = dbpool_conn_update(pc, sql, binds);
+ 
+    if (res != 1) {
+        error(0, "Store-Redis: Error while updating %s",
+              octstr_get_cstr(sql));
+    }
+
+    dbpool_conn_produce(pc);
+    gwlist_destroy(binds, NULL);
+}
+
+static void store_redis_add(Octstr *id, Octstr *os)
+{
+    Octstr *sql;
+    octstr_binary_to_base64(os);
+    sql = octstr_format("HSET %s %s %s",
+                        octstr_get_cstr(fields->table),
+                        octstr_get_cstr(id), octstr_get_cstr(os));
+
+    redis_update(sql);
+    octstr_destroy(sql);
+}
+
+static void store_redis_delete(Octstr *uuid) {
+	Octstr *sql;
+	sql = octstr_format("HDEL %s %s",
+                        octstr_get_cstr(fields->table),
+                        octstr_get_cstr(uuid));
+    redis_update(sql);
+    octstr_destroy(sql);
+}
+
+struct store_db_fields *store_db_fields_create(CfgGroup *grp)
+{
+    struct store_db_fields *ret = NULL;
+
+    ret = gw_malloc(sizeof(*ret));
+    gw_assert(ret != NULL);
+    memset(ret, 0, sizeof(*ret));
+
+    if (!(ret->table = cfg_get(grp, octstr_imm("table"))))
+   	    panic(0, "Store-Redis: Directive 'table' is not specified!");
+
+    return ret;
+}
+
+void store_db_fields_destroy(struct store_db_fields *fields)
+{
+    /* sanity check */
+    if (fields == NULL)
+        return;
+
+#define O_DELETE(a)	 { if (a) octstr_destroy(a); a = NULL; }
+
+    O_DELETE(fields->table);
+    O_DELETE(fields->field_uuid);
+    O_DELETE(fields->field_message);
+
+#undef O_DELETE
+
+    gw_free(fields);
+}
+
+static int store_redis_getall(int ignore_err, void(*cb)(Octstr*, void*), void *data)
+{
+    DBPoolConn *pc;
+    Octstr *sql;
+    Octstr *os, *key;
+    List *result, *row;
+    List *binds = gwlist_create();
+
+    sql = octstr_format("HGETALL %s", octstr_get_cstr(fields->table));
+
+#if defined(DLR_TRACE)
+    debug("store.redis", 0, "sql: %s", octstr_get_cstr(sql));
+#endif
+
+    pc = dbpool_conn_consume(pool);
+    if (pc == NULL) {
+        error(0, "Store-Redis: Database pool got no connection! DB select failed!");
+        gwlist_destroy(binds, NULL);
+        dbpool_conn_produce(pc);
+        return -1;
+    }
+    if (dbpool_conn_select(pc, sql, binds, &result) != 0) {
+        error(0, "Store-Redis: Failed to fetch message %s", octstr_get_cstr(sql));
+        octstr_destroy(sql);
+        gwlist_destroy(binds, NULL);
+        dbpool_conn_produce(pc);
+        return -1;
+    } 
+
+	dbpool_conn_produce(pc);
+    octstr_destroy(sql);
+    gwlist_destroy(binds, NULL);
+
+    if (gwlist_len(result) > 0) {
+        row = gwlist_extract_first(result);
+        while (gwlist_len(row) > 1) {
+            key = gwlist_extract_first(row);
+            os = gwlist_extract_first(row);
+            debug("store.redis", 0, "Found entry %s", octstr_get_cstr(key));
+		    octstr_base64_to_binary(os);
+		    if (os == NULL) {
+		    	info(0, "Could not decode message %s", gwlist_get(row, 0));
+		    } else {
+		    	cb(os, data);
+		    }
+		    octstr_destroy(os);
+        }
+        gwlist_destroy(row, octstr_destroy_item);
+        while ((row = gwlist_extract_first(result)) != NULL)
+            gwlist_destroy(row, octstr_destroy_item);
+    } else {
+        debug("store.redis", 0, "no messages found in store");
+    }
+    gwlist_destroy(result, NULL);
+
+    return 0;
+}
+
+
+struct status {
+    const char *format;
+    Octstr *status;
+};
+
+static void status_cb(Octstr *msg_s, void *d)
+{
+    struct status *data = d;
+    struct tm tm;
+    char id[UUID_STR_LEN + 1];
+    Msg *msg;
+
+    msg = store_msg_unpack(msg_s);
+
+    if (msg == NULL)
+        return;
+    /* transform the time value */
+#if LOG_TIMESTAMP_LOCALTIME
+    tm = gw_localtime(msg->sms.time);
+#else
+    tm = gw_gmtime(msg->sms.time);
+#endif
+    if (msg->sms.udhdata)
+        octstr_binary_to_hex(msg->sms.udhdata, 1);
+    if (msg->sms.msgdata &&
+        (msg->sms.coding == DC_8BIT || msg->sms.coding == DC_UCS2 ||
+        (msg->sms.coding == DC_UNDEF && msg->sms.udhdata)))
+        octstr_binary_to_hex(msg->sms.msgdata, 1);
+
+    uuid_unparse(msg->sms.id, id);
+
+    octstr_format_append(data->status, data->format,
+        id,
+        (msg->sms.sms_type == mo ? "MO" :
+        msg->sms.sms_type == mt_push ? "MT-PUSH" :
+        msg->sms.sms_type == mt_reply ? "MT-REPLY" :
+        msg->sms.sms_type == report_mo ? "DLR-MO" :
+        msg->sms.sms_type == report_mt ? "DLR-MT" : ""),
+        tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+        tm.tm_hour, tm.tm_min, tm.tm_sec,
+        (msg->sms.sender ? octstr_get_cstr(msg->sms.sender) : ""),
+        (msg->sms.receiver ? octstr_get_cstr(msg->sms.receiver) : ""),
+        (msg->sms.smsc_id ? octstr_get_cstr(msg->sms.smsc_id) : ""),
+        (msg->sms.boxc_id ? octstr_get_cstr(msg->sms.boxc_id) : ""),
+        (msg->sms.udhdata ? octstr_get_cstr(msg->sms.udhdata) : ""),
+        (msg->sms.msgdata ? octstr_get_cstr(msg->sms.msgdata) : ""));
+
+    msg_destroy(msg);
+}
+
+static Octstr *store_redis_status(int status_type)
+{
+    Octstr *ret = octstr_create("");
+    const char *format;
+    struct status data;
+
+    /* check if we are active */
+    if (pool == NULL)
+        return ret;
+
+    /* set the type based header */
+    if (status_type == BBSTATUS_HTML) {
+        octstr_append_cstr(ret, "<table border=1>\n"
+            "<tr><td>SMS ID</td><td>Type</td><td>Time</td><td>Sender</td><td>Receiver</td>"
+            "<td>SMSC ID</td><td>BOX ID</td><td>UDH</td><td>Message</td>"
+            "</tr>\n");
+
+        format = "<tr><td>%s</td><td>%s</td>"
+                "<td>%04d-%02d-%02d %02d:%02d:%02d</td>"
+                "<td>%s</td><td>%s</td><td>%s</td>"
+                "<td>%s</td><td>%s</td><td>%s</td></tr>\n";
+    } else if (status_type == BBSTATUS_XML) {
+        format = "<message>\n\t<id>%s</id>\n\t<type>%s</type>\n\t"
+                "<time>%04d-%02d-%02d %02d:%02d:%02d</time>\n\t"
+                "<sender>%s</sender>\n\t"
+                "<receiver>%s</receiver>\n\t<smsc-id>%s</smsc-id>\n\t"
+                "<box-id>%s</box-id>\n\t"
+                "<udh-data>%s</udh-data>\n\t<msg-data>%s</msg-data>\n\t"
+                "</message>\n";
+    } else {
+        octstr_append_cstr(ret, "[SMS ID] [Type] [Time] [Sender] [Receiver] [SMSC ID] [BOX ID] [UDH] [Message]\n");
+        format = "[%s] [%s] [%04d-%02d-%02d %02d:%02d:%02d] [%s] [%s] [%s] [%s] [%s] [%s]\n";
+    }
+
+    data.format = format;
+    data.status = ret;
+    /* ignore error because files may disappear */
+    store_redis_getall(1, status_cb, &data);
+
+    /* set the type based footer */
+    if (status_type == BBSTATUS_HTML) {
+        octstr_append_cstr(ret,"</table>");
+    }
+
+    return ret;
+}
+
+static void dispatch(Octstr *msg_s, void *data)
+{
+    Msg *msg;
+    void(*receive_msg)(Msg*) = data;
+
+    if (msg_s == NULL)
+        return;
+    msg = store_msg_unpack(msg_s);
+    if (msg != NULL) {
+        receive_msg(msg);
+        counter_increase(counter);
+    } else {
+        error(0, "Could not unpack message");
+    }
+}
+
+static int store_redis_load(void(*receive_msg)(Msg*))
+{
+    int rc;
+
+    /* check if we are active */
+    if (pool == NULL)
+        return 0;
+        
+    /* sanity check */
+    if (receive_msg == NULL)
+        return -1;
+
+    rc = store_redis_getall(0, dispatch, receive_msg);
+
+    info(0, "Loaded %ld messages from store.", counter_value(counter));
+
+    /* allow using of storage */
+    gwlist_remove_producer(loaded);
+
+    return rc;
+}
+
+
+static int store_redis_save(Msg *msg)
+{
+    char id[UUID_STR_LEN + 1];
+    Octstr *id_s;
+
+    /* always set msg id and timestamp */
+    if (msg_type(msg) == sms && uuid_is_null(msg->sms.id))
+        uuid_generate(msg->sms.id);
+
+    if (msg_type(msg) == sms && msg->sms.time == MSG_PARAM_UNDEFINED)
+        time(&msg->sms.time);
+
+    if (pool == NULL)
+        return 0;
+
+    /* block here if store still not loaded */
+    gwlist_consume(loaded);
+
+    switch(msg_type(msg)) {
+        case sms:
+        {
+            Octstr *os = store_msg_pack(msg);
+
+            if (os == NULL) {
+                error(0, "Could not pack message.");
+                return -1;
+            }
+            uuid_unparse(msg->sms.id, id);
+            id_s = octstr_create(id);
+            store_redis_add(id_s, os);
+            octstr_destroy(id_s);
+            counter_increase(counter);
+            octstr_destroy(os);
+            break;
+        }
+        case ack:
+        {
+            //Octstr *filename;
+            uuid_unparse(msg->ack.id, id);
+            id_s = octstr_create(id);
+        	store_redis_delete(id_s);
+            octstr_destroy(id_s);
+            counter_decrease(counter);
+            break;
+        }
+        default:
+            return -1;
+    }
+
+    return 0;
+}
+
+static int store_redis_save_ack(Msg *msg, ack_status_t status)
+{
+    int ret;
+    Msg *nack = msg_create(ack);
+
+    nack->ack.nack = status;
+    uuid_copy(nack->ack.id, msg->sms.id);
+    nack->ack.time = msg->sms.time;
+    ret = store_redis_save(nack);
+    msg_destroy(nack);
+
+    return ret;
+}
+
+
+static void store_redis_shutdown()
+{
+    dbpool_destroy(pool);
+    store_db_fields_destroy(fields);
+        
+    counter_destroy(counter);
+    gwlist_destroy(loaded, NULL);
+}
+
+int store_redis_init(Cfg *cfg)
+{
+    CfgGroup *grp;
+    List *grplist;
+    Octstr *redis_host, *redis_pass, *redis_id;
+    long redis_port = 0, redis_database = -1, redis_idle_timeout = -1;
+    Octstr *p = NULL;
+    long pool_size;
+    DBConf *db_conf = NULL;
+
+    store_messages = store_redis_messages;
+    store_save = store_redis_save;
+    store_save_ack = store_redis_save_ack;
+    store_load = store_redis_load;
+    store_dump = store_redis_dump;
+    store_shutdown = store_redis_shutdown;
+    store_status = store_redis_status;
+
+    /*
+     * Check for all mandatory directives that specify the field names
+     * of the used Redis key
+     */
+    if (!(grp = cfg_get_single_group(cfg, octstr_imm("store-db"))))
+        panic(0, "Store-Redis: group 'store-db' is not specified!");
+
+    if (!(redis_id = cfg_get(grp, octstr_imm("id"))))
+        panic(0, "Store-Redis: directive 'id' is not specified!");
+
+    fields = store_db_fields_create(grp);
+    gw_assert(fields != NULL);
+
+    /*
+     * Now grab the required information from the 'redis-connection' group
+     * with the redis-id we just obtained.
+     *
+     * We have to loop through all available Redis connection definitions
+     * and search for the one we are looking for.
+     */
+    grplist = cfg_get_multi_group(cfg, octstr_imm("redis-connection"));
+    while (grplist && (grp = gwlist_extract_first(grplist)) != NULL) {
+        p = cfg_get(grp, octstr_imm("id"));
+        if (p != NULL && octstr_compare(p, redis_id) == 0) {
+            goto found;
+        }
+        if (p != NULL)
+            octstr_destroy(p);
+    }
+    panic(0, "Store-Redis: connection settings for id '%s' are not specified!",
+          octstr_get_cstr(redis_id));
+
+found:
+    octstr_destroy(p);
+    gwlist_destroy(grplist, NULL);
+
+    if (cfg_get_integer(&pool_size, grp, octstr_imm("max-connections")) == -1 || pool_size == 0)
+        pool_size = 1;
+
+    if (!(redis_host = cfg_get(grp, octstr_imm("host"))))
+   	    panic(0, "Store-Redis: directive 'host' is not specified!");
+    if (cfg_get_integer(&redis_port, grp, octstr_imm("port")) == -1)
+   	    panic(0, "Store-Redis: directive 'port' is not specified!");
+    redis_pass = cfg_get(grp, octstr_imm("password"));
+    cfg_get_integer(&redis_database, grp, octstr_imm("database"));
+    cfg_get_integer(&redis_idle_timeout, grp, octstr_imm("idle-timeout"));
+
+    /*
+     * Ok, ready to connect to Redis
+     */
+    db_conf = gw_malloc(sizeof(DBConf));
+    gw_assert(db_conf != NULL);
+
+    db_conf->redis = gw_malloc(sizeof(RedisConf));
+    gw_assert(db_conf->redis != NULL);
+
+    db_conf->redis->host = redis_host;
+    db_conf->redis->port = redis_port;
+    db_conf->redis->password = redis_pass;
+    db_conf->redis->database = redis_database;
+    db_conf->redis->idle_timeout = redis_idle_timeout;
+
+    pool = dbpool_create(DBPOOL_REDIS, db_conf, pool_size);
+    gw_assert(pool != NULL);
+
+    /*
+     * Panic on failure to connect. Should we just try to reconnect?
+     */
+    if (dbpool_conn_count(pool) == 0)
+        panic(0,"Store-Redis: database pool has no connections!");
+
+    octstr_destroy(redis_id);
+    loaded = gwlist_create();
+    gwlist_add_producer(loaded);
+    counter = counter_create();
+    return 0;
+}
+
+#endif
Index: gw/bb_store.c
===================================================================
--- gw/bb_store.c	(revision 5124)
+++ gw/bb_store.c	(working copy)
@@ -79,7 +79,7 @@
  
 
 int store_init(const Octstr *type, const Octstr *fname, long dump_freq,
-               void *pack_func, void *unpack_func)
+               void *pack_func, void *unpack_func, Cfg *cfg)
 {
     int ret;
     
@@ -90,6 +90,10 @@
         ret = store_file_init(fname, dump_freq);
     } else if (octstr_str_compare(type, "spool") == 0) {
         ret = store_spool_init(fname);
+#ifdef HAVE_REDIS
+    } else if (octstr_str_compare(type, "redis") == 0) {
+        ret = store_redis_init(cfg);
+#endif
     } else {
         error(0, "Unknown 'store-type' defined.");
         ret = -1;
Index: gw/bb_store.h
===================================================================
--- gw/bb_store.h	(revision 5124)
+++ gw/bb_store.h	(working copy)
@@ -99,7 +99,7 @@
 
 /* initialize system. Return -1 if fname is bad (too long). */
 int store_init(const Octstr *type, const Octstr *fname, long dump_freq,
-               void *pack_func, void *unpack_func);
+               void *pack_func, void *unpack_func, Cfg *cfg);
 
 /* init shutdown (system dies when all acks have been processed) */
 extern void (*store_shutdown)(void);
@@ -112,7 +112,9 @@
  */
 int store_spool_init(const Octstr *fname);
 int store_file_init(const Octstr *fname, long dump_freq);
+#ifdef HAVE_REDIS
+int store_redis_init(Cfg *cfg);
+#endif
 
-
 #endif /*BB_STORE_H_*/
 
