Opwnsmppbox patch

Aarno Syvänen <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Message-ID <[email protected]>
Hi List,

here are diffs again

to Kannel, for documentation



for gw



for gwlib



for opensmppbox, for documentation



for code



Aarno
doc.diff (application/octet-stream, 2.1 KB)
--- /Users/syvanen/gateway/doc/userguide/userguide.xml	2011-12-15 16:09:28.000000000 +0100
+++ doc/userguide/userguide.xml	2011-12-19 11:32:49.000000000 +0100
@@ -5359,6 +5359,7 @@
 field-mask = mask
 field-status = status
 field-boxc-id = boxc
+field-msg-id = id
 </programlisting>
 
 	</para>
@@ -5490,6 +5491,7 @@
 field-mask = mask
 field-status = status
 field-boxc-id = boxc
+field-msg-id = id
 </programlisting>
 
 	</para>
@@ -5533,6 +5535,7 @@
 field-mask = mask
 field-status = status
 field-boxc-id = boxc
+field-msg-id = id
 </programlisting>
 
 	</para>
@@ -5573,6 +5576,7 @@
 field-mask = mask
 field-status = status
 field-boxc-id = boxc
+field-msg-id = id
 </programlisting>
 
 	</para>
@@ -5616,6 +5620,7 @@
 field-mask = mask
 field-status = status
 field-boxc-id = boxc
+field-msg-id = id
 </programlisting>
 
     </para>
@@ -5653,6 +5658,7 @@
 field-mask = mask
 field-status = status
 field-boxc-id = boxc
+field-msg-id = id
 </programlisting>
 
 	</para>
@@ -5760,6 +5766,14 @@
 		  database table and keep it empty.
      </entry></row>
 
+  <row><entry><literal>field-msg-id (c)</literal></entry>
+     <entry>string</entry>
+     <entry valign="bottom">
+        The table field that is used to store the sms message id
+        that has passed the message for delivery. This is used by 
+        opensmppbox to route DLR and SMPP responses the original client
+     </entry></row>
+
   </tbody>
   </tgroup>
  </table>
@@ -5779,12 +5793,15 @@
 field-mask = mask
 field-status = status
 field-boxc-id = boxc
+flied-msg-id = id
 </programlisting>
 
   </para>
-  <para>Beware that all variables in this group are mandatory, so you
-  have to specify all fields to enable bearerbox to know how to store
-  and retrieve the DLR information from the external storage spaces.
+  <para>Beware that all variables in this group except field-msg-id 
+  are mandatory, so you have to specify all fields expect field-msg-id 
+  to enable bearerbox to know how to store and retrieve the DLR information 
+  from the external storage spaces. If you use opensmppbox, you must set 
+  field-msg-id to route DLRs and in the case of MOs, SMPP responses.. 
   </para>
 
 	</sect2>
gw.diff (application/octet-stream, 29.6 KB)
Common subdirectories: /Users/syvanen/gateway/gw/.svn and gw/.svn
Binary files /Users/syvanen/gateway/gw/bb_alog.o and gw/bb_alog.o differ
Binary files /Users/syvanen/gateway/gw/bb_boxc.o and gw/bb_boxc.o differ
Binary files /Users/syvanen/gateway/gw/bb_http.o and gw/bb_http.o differ
Binary files /Users/syvanen/gateway/gw/bb_smscconn.o and gw/bb_smscconn.o differ
Binary files /Users/syvanen/gateway/gw/bb_store.o and gw/bb_store.o differ
Binary files /Users/syvanen/gateway/gw/bb_store_file.o and gw/bb_store_file.o differ
Binary files /Users/syvanen/gateway/gw/bb_store_spool.o and gw/bb_store_spool.o differ
Binary files /Users/syvanen/gateway/gw/bb_udp.o and gw/bb_udp.o differ
Binary files /Users/syvanen/gateway/gw/bearerbox and gw/bearerbox differ
Binary files /Users/syvanen/gateway/gw/bearerbox.o and gw/bearerbox.o differ
diff -u -b -B -x '*conf' /Users/syvanen/gateway/gw/dlr.c gw/dlr.c
--- /Users/syvanen/gateway/gw/dlr.c	2011-12-15 16:11:04.000000000 +0100
+++ gw/dlr.c	2011-12-19 11:32:53.000000000 +0100
@@ -77,6 +77,8 @@
  *     connection a msg is passed. Now we can route the messages inside bearerbox.
  * 2009-04-29: aguerrieri at kannel dot org:
  *     added support for ms-sql and sybase via freetds.
+ * 2011-11-27: aarno at alisanus dot com:
+ *     added field-msg-id for mo and delivry report routing
  */
  
 #include <ctype.h>
@@ -95,6 +97,8 @@
 /* Our callback functions */
 static struct dlr_storage *handles = NULL;
 
+static void add_true(const Octstr *smsc, const Octstr *ts, Msg *msg);
+
 /*
  * Function to allocate a new struct dlr_entry entry
  * and intialize it to zero
@@ -131,10 +135,37 @@
     ret->url = octstr_duplicate(dlr->url);
     ret->boxc_id = octstr_duplicate(dlr->boxc_id);
     ret->mask = dlr->mask;
+    ret->id = octstr_duplicate(dlr->id);
 
     return ret;
 }
 
+void dlr_entry_dump(const struct dlr_entry *dlr)  
+{
+    if (dlr == NULL) {
+        debug("dlr.dlr", 0, "dlr entry points to NULL");
+        return;
+    }
+ 
+    if (dlr->smsc)
+        debug("dlr.dlr", 0, "dlr entry smsc id was %s", octstr_get_cstr(dlr->smsc));
+    if (dlr->timestamp)
+        debug("dlr.dlr", 0, "dlr entry timestamp was %s", octstr_get_cstr(dlr->timestamp));
+    if (dlr->source)
+        debug("dlr.dlr", 0, "dlr entry source was %s", octstr_get_cstr(dlr->source));
+    if (dlr->destination)
+        debug("dlr.dlr", 0, "dlr entry destination was %s", octstr_get_cstr(dlr->destination));
+    if (dlr->service)
+        debug("dlr.dlr", 0, "dlr entry service  was %s", octstr_get_cstr(dlr->service));
+    if (dlr->url)
+        debug("dlr.dlr", 0, "dlr entry dlr URL  was %s", octstr_get_cstr(dlr->url));
+    if (dlr->boxc_id)
+       debug("dlr.dlr", 0, "dlr entry box id  was %s",  octstr_get_cstr(dlr->boxc_id));
+    debug("dlr.dlr", 0, "dlr entry dlr mask  was %d", dlr->mask);
+    if (dlr->id)
+        debug("dlr.dlr", 0, "dlr entry msg id  was %s", octstr_get_cstr(dlr->id));
+}
+
 /*
  * Function to destroy the struct dlr_entry entry
  */
@@ -153,6 +184,7 @@
     O_DELETE(dlr->service);
     O_DELETE(dlr->url);
     O_DELETE(dlr->boxc_id);
+    O_DELETE(dlr->id);
 
 #undef O_DELETE
 
@@ -194,6 +226,8 @@
     if (!(ret->field_boxc = cfg_get(grp, octstr_imm("field-boxc-id"))))
    	    panic(0, "DLR: DB: directive 'field-boxc-id' is not specified!");
 
+    ret->field_id = cfg_get(grp, octstr_imm("field-msg-id"));
+    
     return ret;
 }
 
@@ -215,6 +249,7 @@
     O_DELETE(fields->field_mask);
     O_DELETE(fields->field_status);
     O_DELETE(fields->field_boxc);
+    O_DELETE(fields->field_id);
 
 #undef O_DELETE
 
@@ -319,53 +354,16 @@
  */
 void dlr_add(const Octstr *smsc, const Octstr *ts, Msg *msg)
 {
-    struct dlr_entry *dlr = NULL;
-
-    /* Add the foreign_id so all SMSC modules can use it.
-     * Obey also the original message in the split_parts list. */
-    if (msg->sms.foreign_id != NULL)
-        octstr_destroy(msg->sms.foreign_id);
-    msg->sms.foreign_id = octstr_duplicate(ts);
-    if (msg->sms.split_parts != NULL) {
-        struct split_parts *split = msg->sms.split_parts;
-        if (split->orig->sms.foreign_id != NULL)
-            octstr_destroy(split->orig->sms.foreign_id);
-        split->orig->sms.foreign_id = octstr_duplicate(ts);
-    }
-
-    if(octstr_len(smsc) == 0) {
-        warning(0, "DLR[%s]: Can't add a dlr without smsc-id", dlr_type());
-        return;
-    }
-
-    /* sanity check */
-    if (handles == NULL || handles->dlr_add == NULL || msg == NULL)
-        return;
-
     /* check if delivery receipt requested */
     if (!DLR_IS_ENABLED(msg->sms.dlr_mask))
         return;
 
-     /* allocate new struct dlr_entry struct */
-    dlr = dlr_entry_create();
-    gw_assert(dlr != NULL);
-
-    /* now copy all values, we are interested in */
-    dlr->smsc = (smsc ? octstr_duplicate(smsc) : octstr_create(""));
-    dlr->timestamp = (ts ? octstr_duplicate(ts) : octstr_create(""));
-    dlr->source = (msg->sms.sender ? octstr_duplicate(msg->sms.sender) : octstr_create(""));
-    dlr->destination = (msg->sms.receiver ? octstr_duplicate(msg->sms.receiver) : octstr_create(""));
-    dlr->service = (msg->sms.service ? octstr_duplicate(msg->sms.service) : octstr_create(""));
-    dlr->url = (msg->sms.dlr_url ? octstr_duplicate(msg->sms.dlr_url) : octstr_create(""));
-    dlr->boxc_id = (msg->sms.boxc_id ? octstr_duplicate(msg->sms.boxc_id) : octstr_create(""));
-    dlr->mask = msg->sms.dlr_mask;
-
-    debug("dlr.dlr", 0, "DLR[%s]: Adding DLR smsc=%s, ts=%s, src=%s, dst=%s, mask=%d, boxc=%s",
-          dlr_type(), octstr_get_cstr(dlr->smsc), octstr_get_cstr(dlr->timestamp),
-          octstr_get_cstr(dlr->source), octstr_get_cstr(dlr->destination), dlr->mask, octstr_get_cstr(dlr->boxc_id));
+    add_true(smsc, ts, msg);
+}
 	
-    /* call registered function */
-    handles->dlr_add(dlr);
+void msg_add(const Octstr *smsc, const Octstr *ts, Msg *msg)
+{
+    add_true(smsc, ts, msg);
 }
 
 /*
@@ -395,12 +393,12 @@
         if (len > MIN_DST_LEN)
             octstr_delete(dst_min, 0, len - MIN_DST_LEN);
     }
-    debug("dlr.dlr", 0, "DLR[%s]: Looking for DLR smsc=%s, ts=%s, dst=%s, type=%d",
+    debug("dlr.dlr", 0, "dlr_find: DLR[%s]: Looking for DLR smsc=%s, ts=%s, dst=%s, type=%d",
                                  dlr_type(), octstr_get_cstr(smsc), octstr_get_cstr(ts), octstr_get_cstr(dst), typ);
 
     dlr = handles->dlr_get(smsc, ts, dst_min);
     if (dlr == NULL)  {
-        warning(0, "DLR[%s]: DLR from SMSC<%s> for DST<%s> not found.",
+        warning(0, "DLR[%s]: dlr_find: DLR from SMSC<%s> for DST<%s> not found.",
                 dlr_type(), octstr_get_cstr(smsc), octstr_get_cstr(dst));         
         return NULL;
     }
@@ -430,12 +428,11 @@
          * route this msg back to originating smsbox
          */
         O_SET(msg->sms.boxc_id, dlr->boxc_id);
-
         time(&msg->sms.time);
-        debug("dlr.dlr", 0, "DLR[%s]: created DLR message for URL <%s>",
+        debug("dlr.dlr", 0, "dlr_find: DLR[%s]: created DLR message for URL <%s>",
                       dlr_type(), (msg->sms.dlr_url?octstr_get_cstr(msg->sms.dlr_url):""));
     } else {
-        debug("dlr.dlr", 0, "DLR[%s]: Ignoring DLR message because of mask type=%d dlr->mask=%d", dlr_type(), typ, dlr->mask);
+        debug("dlr.dlr", 0, "dlr_find: DLR[%s]: Ignoring DLR message because of mask type=%d dlr->mask=%d", dlr_type(), typ, dlr->mask);
         /* ok that was a status report but we where not interested in having it */
         msg = NULL;
     }
@@ -444,7 +441,7 @@
  
     /* check for end status and if so remove from storage */
     if ((typ & DLR_BUFFERED) && ((dlr->mask & DLR_SUCCESS) || (dlr->mask & DLR_FAIL))) {
-        debug("dlr.dlr", 0, "DLR[%s]: DLR not destroyed, still waiting for other delivery report", dlr_type());
+        debug("dlr.dlr", 0, "dlr_find: DLR[%s]: DLR not destroyed, still waiting for other delivery report", dlr_type());
         /* update dlr entry status if function defined */
         if (handles != NULL && handles->dlr_update != NULL){
             handles->dlr_update(smsc, ts, dst_min, typ);
@@ -454,7 +451,7 @@
             /* it's not good for internal storage, but better for all others */
             handles->dlr_remove(smsc, ts, dst_min);
         } else {
-            warning(0, "DLR[%s]: Storage don't have remove operation defined", dlr_type());
+            warning(0, "dlr_find: DLR[%s]: Storage don't have remove operation defined", dlr_type());
         }
     }
 
@@ -465,6 +462,77 @@
     return msg;
 }
     
+/*
+ * Return Msg* if dlr entry found in DB, otherwise NULL. If use_smpp_id is set, only 8
+ * first characters are used for matching. NOTE: msg is not removed. This funsction is 
+ * used to fetch data from dlr table.
+ */
+Msg *dlr_find_by_id(const Octstr *id, int use_smpp_id)
+{
+    Msg *msg = NULL;
+    struct dlr_entry *dlr = NULL;
+    Octstr *used_id;
+
+    if (use_smpp_id)
+        used_id = octstr_copy(id, 0, 8);
+    else
+        used_id = octstr_duplicate(id);
+
+    /* check if we have handler registered */
+    if (handles == NULL || handles->dlr_peek_with_smppid  == NULL ||
+           handles->dlr_peek == NULL) {
+        octstr_destroy(used_id);
+        return NULL;
+    }
+
+    debug("dlr.dlr", 0, "dlr_find_by_id: DLR[%s]:Looking for DLR id=%s (using %s)", dlr_type(), octstr_get_cstr(used_id),
+          use_smpp_id ? "first 8 characters" : "the whole UUID");
+
+    if (use_smpp_id)
+        dlr = handles->dlr_peek_with_smppid(id);
+    else
+        dlr = handles->dlr_peek(id);
+    if (dlr == NULL)  {
+        warning(0, "dlr_find_by_id: DLR for ID <%s>not found.", octstr_get_cstr(id));
+        return NULL;
+    }
+
+#define O_SET(x, val) if (octstr_len(val) > 0) { x = val; val = NULL; }
+
+    msg = msg_create(sms);
+    msg->sms.sms_type = report_mo;
+    O_SET(msg->sms.service, dlr->service);
+    O_SET(msg->sms.smsc_id, dlr->smsc);
+    O_SET(msg->sms.receiver, dlr->destination);
+    O_SET(msg->sms.sender, dlr->source);
+    /* if dlr_url was present, recode it here again */
+    O_SET(msg->sms.dlr_url, dlr->url);
+    /*
+     * If id is available, use it to restore box id for smppbox
+     * usage
+     */
+    O_SET(msg->sms.foreign_id, dlr->id);
+    /*
+     * insert original message to the data segment
+     * later in the smsc module
+     */
+    msg->sms.msgdata = NULL;
+    /*
+     * If a boxc_id is available, then instruct smppbox to
+     * route this msg back to originating client
+     */
+    O_SET(msg->sms.boxc_id, dlr->boxc_id);
+    time(&msg->sms.time);
+    debug("dlr.dlr", 0, "dlr_find_by_id: created DLR message for ID <%s>",
+         (msg->sms.foreign_id ? octstr_get_cstr(msg->sms.foreign_id) : ""));
+
+#undef O_SET
+
+    /* destroy struct dlr_entry */
+    dlr_entry_destroy(dlr);
+    return msg;
+}
+    
 void dlr_flush(void)
 {
     info(0, "Flushing all %ld queued DLR messages in %s storage", dlr_messages(), 
@@ -474,6 +542,11 @@
         handles->dlr_flush();
 }
 
+void dlr_remove_by_id(const Octstr *id)
+{
+   if (handles != NULL && handles->dlr_remove_by_id != NULL)
+       handles->dlr_remove_by_id(id);
+}
 
 Msg* create_dlr_from_msg(const Octstr *smsc, const Msg *msg, const Octstr *reply, long stat)
 {
@@ -507,3 +580,57 @@
 
     return dlrmsg;
 }
+
+static void add_true(const Octstr *smsc, const Octstr *ts, Msg *msg)
+{
+    struct dlr_entry *dlr = NULL;
+
+    /* Add the foreign_id so all SMSC modules can use it.
+     * Obey also the original message in the split_parts list. */
+    if (msg->sms.foreign_id != NULL)
+        octstr_destroy(msg->sms.foreign_id);
+    msg->sms.foreign_id = octstr_duplicate(ts);
+    if (msg->sms.split_parts != NULL) {
+        struct split_parts *split = msg->sms.split_parts;
+        if (split->orig->sms.foreign_id != NULL)
+            octstr_destroy(split->orig->sms.foreign_id);
+        split->orig->sms.foreign_id = octstr_duplicate(ts);
+    }
+
+    if(octstr_len(smsc) == 0) {
+        warning(0, "DLR[%s]: Can't add a dlr without smsc-id", dlr_type());
+        return;
+    }
+
+    /* sanity check */
+    if (handles == NULL || handles->dlr_add == NULL || msg == NULL)
+        return;
+
+     /* allocate new struct dlr_entry struct */
+    dlr = dlr_entry_create();
+    gw_assert(dlr != NULL);
+
+    /* now copy all values, we are interested in */
+    dlr->smsc = (smsc ? octstr_duplicate(smsc) : octstr_create(""));
+    dlr->timestamp = (ts ? octstr_duplicate(ts) : octstr_create(""));
+    dlr->source = (msg->sms.sender ? octstr_duplicate(msg->sms.sender) : octstr_create(""));
+    dlr->destination = (msg->sms.receiver ? octstr_duplicate(msg->sms.receiver) : octstr_create(""));
+    dlr->service = (msg->sms.service ? octstr_duplicate(msg->sms.service) : octstr_create(""));
+    dlr->url = (msg->sms.dlr_url ? octstr_duplicate(msg->sms.dlr_url) : octstr_create(""));
+    dlr->boxc_id = (msg->sms.boxc_id ? octstr_duplicate(msg->sms.boxc_id) : octstr_create(""));
+    dlr->mask = msg->sms.dlr_mask;
+
+    char uuidbuf[100];
+    Octstr *id;
+    uuid_unparse(msg->sms.id, uuidbuf);
+    id = octstr_create_from_data(uuidbuf, 36);
+    dlr->id = octstr_duplicate(id);
+
+    debug("dlr.dlr", 0, "DLR[%s]: Adding DLR smsc=%s, ts=%s, src=%s, dst=%s, mask=%d, boxc=%s, id=%s",
+          dlr_type(), octstr_get_cstr(dlr->smsc), octstr_get_cstr(dlr->timestamp),
+          octstr_get_cstr(dlr->source), octstr_get_cstr(dlr->destination), dlr->mask, octstr_get_cstr(dlr->boxc_id),
+          dlr->id ? octstr_get_cstr(dlr->id) : "nil");
+
+    /* call registered function */
+    handles->dlr_add(dlr);
+}
diff -u -b -B -x '*conf' /Users/syvanen/gateway/gw/dlr.h gw/dlr.h
--- /Users/syvanen/gateway/gw/dlr.h	2011-12-15 16:11:04.000000000 +0100
+++ gw/dlr.h	2011-12-19 11:32:53.000000000 +0100
@@ -95,16 +95,28 @@
 void dlr_shutdown(void);
 
 /* 
- * Add a new entry to the list
+ * Add a new entry to the list, if delivery report is asked
  */
 void dlr_add(const Octstr *smsc, const Octstr *ts, Msg *msg);
 
 /* 
+ * Add in all cases
+ */
+void msg_add(const Octstr *smsc, const Octstr *ts, Msg *msg);
+
+/* 
  * Find an entry in the list. If there is one a message is returned and 
  * the entry is removed from the list otherwhise the message returned is NULL 
  */
 Msg* dlr_find(const Octstr *smsc, const Octstr *ts, const Octstr *dst, int type, int use_dst);
 
+/*
+ * Find an entry in the list, using smpp smsc id as key. Message is not removed from
+ * the list. If use_smpp_id is set, only first 8 characters are used for matching.
+ * Return msg, if there are one eith id on the list, otherwise NULL.
+ */
+Msg* dlr_find_by_id(const Octstr *id, int use_smpp_id);
+
 /* return the number of DLR messages in the current waiting queue */
 long dlr_messages(void);
 
Binary files /Users/syvanen/gateway/gw/dlr.o and gw/dlr.o differ
diff -u -b -B -x '*conf' /Users/syvanen/gateway/gw/dlr_mem.c gw/dlr_mem.c
--- /Users/syvanen/gateway/gw/dlr_mem.c	2011-12-15 16:11:04.000000000 +0100
+++ gw/dlr_mem.c	2011-12-19 11:32:53.000000000 +0100
@@ -120,7 +120,7 @@
 }
 
 /*
- * Private compare function
+ * Private compare functions
  * Return 0 if entry match and 1 if not.
  */
 static int dlr_mem_entry_match(struct dlr_entry *dlr, const Octstr *smsc, const Octstr *ts, const Octstr *dst)
@@ -142,6 +142,23 @@
     return 1;
 }
 
+static int dlr_mem_entry_match_one(struct dlr_entry *dlr, const Octstr *id)
+{
+    if (octstr_compare(dlr->id, id) == 0)
+        return 0;
+
+    return 1;
+}
+
+static int dlr_mem_entry_match_first_eigth(struct dlr_entry *dlr, const Octstr *id)
+{
+    if (octstr_ncompare(dlr->id, id, 8) == 0) {
+        return 0;
+    }
+        
+    return 1;
+}
+
 /*
  * Find matching entry and return copy of it, otherwise NULL
  */
@@ -167,6 +184,45 @@
     return ret;
 }
 
+static struct dlr_entry *dlr_mem_peek_true(const Octstr *id, int usesmppid)
+{
+    long i;
+    long len;
+    struct dlr_entry *dlr = NULL, *ret = NULL;
+
+    gw_rwlock_rdlock(&rwlock);
+    len = gwlist_len(dlr_waiting_list);
+    for (i=0; i < len; i++) {
+        dlr = gwlist_get(dlr_waiting_list, i);
+
+        if (!usesmppid) {
+            if (dlr_mem_entry_match_one(dlr, id) == 0) {
+                ret = dlr_entry_duplicate(dlr);
+                break;
+            }
+        } else {
+            if (dlr_mem_entry_match_first_eigth(dlr, id) == 0) {
+                ret = dlr_entry_duplicate(dlr);
+                break;
+            }
+        }
+    }
+    gw_rwlock_unlock(&rwlock);
+
+    /* we couldnt find a matching entry */
+    return ret;
+}
+
+static struct dlr_entry *dlr_mem_peek(const Octstr *id)
+{
+    dlr_mem_peek_true(id, 0);
+} 
+ 
+static struct dlr_entry *dlr_mem_peek_with_smppid(const Octstr *id)
+{ 
+    dlr_mem_peek_true(id, 1);
+}
+
 /*
  * Remove matching entry
  */
@@ -190,11 +246,34 @@
     gw_rwlock_unlock(&rwlock);
 }
 
+static void dlr_mem_remove_by_id(const Octstr *id)
+{
+    long i;
+    long len;
+    struct dlr_entry *dlr = NULL;
+
+    gw_rwlock_wrlock(&rwlock);
+    len = gwlist_len(dlr_waiting_list);
+    for (i=0; i < len; i++) {
+        dlr = gwlist_get(dlr_waiting_list, i);
+
+        if (dlr_mem_entry_match_one(dlr, id) == 0) {
+            gwlist_delete(dlr_waiting_list, i, 1);
+            dlr_entry_destroy(dlr);
+            break;
+        }
+    }
+    gw_rwlock_unlock(&rwlock);
+}
+
 static struct dlr_storage  handles = {
     .type = "internal",
     .dlr_add = dlr_mem_add,
     .dlr_get = dlr_mem_get,
+    .dlr_peek = dlr_mem_peek,
+    .dlr_peek_with_smppid = dlr_mem_peek_with_smppid,
     .dlr_remove = dlr_mem_remove,
+    .dlr_remove_by_id = dlr_mem_remove_by_id,
     .dlr_shutdown = dlr_mem_shutdown,
     .dlr_messages = dlr_mem_messages,
     .dlr_flush = dlr_mem_flush
Binary files /Users/syvanen/gateway/gw/dlr_mem.o and gw/dlr_mem.o differ
Binary files /Users/syvanen/gateway/gw/dlr_mssql.o and gw/dlr_mssql.o differ
diff -u -b -B -x '*conf' /Users/syvanen/gateway/gw/dlr_mysql.c gw/dlr_mysql.c
--- /Users/syvanen/gateway/gw/dlr_mysql.c	2011-12-15 16:11:04.000000000 +0100
+++ gw/dlr_mysql.c	2011-12-19 11:32:53.000000000 +0100
@@ -63,6 +63,7 @@
  * Andreas Fink <[email protected]>, 18.08.2001
  * Stipe Tolj <[email protected]>, 22.03.2002
  * Alexander Malysh <[email protected]> 2003
+ * Aarno Syvanen <aarno at alisanus dot com> 2011
 */
 
 #include "gwlib/gwlib.h"
@@ -105,12 +106,21 @@
         return;
     }
 
+    if (fields->field_id == NULL || octstr_len(fields->field_id) == 0) 
     sql = octstr_format("INSERT INTO `%S` (`%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S`) VALUES "
                         "(?, ?, ?, ?, ?, ?, ?, ?, 0)",
                         fields->table, fields->field_smsc, fields->field_ts,
                         fields->field_src, fields->field_dst, fields->field_serv,
                         fields->field_url, fields->field_mask, fields->field_boxc,
                         fields->field_status);
+    else
+        sql = octstr_format("INSERT INTO `%S` (`%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S`) VALUES "
+                            "(?, ?, ?, ?, ?, ?, ?, ?, ?, 0)",
+                            fields->table, fields->field_smsc, fields->field_ts,
+                            fields->field_src, fields->field_dst, fields->field_serv,
+                            fields->field_url, fields->field_mask, fields->field_boxc,
+                            fields->field_id, fields->field_status);
+
     os_mask = octstr_format("%d", entry->mask);
     gwlist_append(binds, entry->smsc);
     gwlist_append(binds, entry->timestamp);
@@ -121,6 +131,9 @@
     gwlist_append(binds, os_mask);
     gwlist_append(binds, entry->boxc_id);
 
+    if (fields->field_id && octstr_len(fields->field_id) > 0)
+        gwlist_append(binds, entry->id);
+
 #if defined(DLR_TRACE)
     debug("dlr.mysql", 0, "sql: %s", octstr_get_cstr(sql));
 #endif
@@ -202,6 +215,86 @@
     return res;
 }
 
+/* If use_smpp_id is on, only first eigth characters used for matching */
+static struct dlr_entry* dlr_mysql_peek_true(const Octstr *id, int use_smpp_id)
+{
+    Octstr *sql, *like, *smpp_id;
+    DBPoolConn *pconn;
+    List *result = NULL, *row;
+    struct dlr_entry *res = NULL;
+    List *binds = gwlist_create();
+
+    pconn = dbpool_conn_consume(pool);
+    if (pconn == NULL) /* should not happens, but sure is sure */
+        return NULL;
+
+    if (use_smpp_id) {
+        smpp_id = octstr_copy(id, 0, 8);
+        like = octstr_format("LIKE '%s%%'", octstr_get_cstr(smpp_id));
+        octstr_destroy(smpp_id);
+        sql = octstr_format("SELECT `%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S` FROM `%S` WHERE `%S`  %S LIMIT 1",
+                            fields->field_mask, fields->field_serv,
+                            fields->field_url, fields->field_src,
+                            fields->field_dst, fields->field_boxc,
+                            fields->field_id, fields->field_smsc, 
+                            fields->table, fields->field_id, like);
+    } else
+        sql = octstr_format("SELECT `%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S`, `%S` FROM `%S` WHERE `%S`=?  LIMIT 1",
+                            fields->field_mask, fields->field_serv,
+                            fields->field_url, fields->field_src,
+                            fields->field_dst, fields->field_boxc,
+                            fields->field_id, fields->field_smsc,
+                            fields->table, fields->field_id);
+
+    gwlist_append(binds, (Octstr *)id);
+    
+#if defined(DLR_TRACE)
+    debug("dlr.mysql", 0, "sql: %s", octstr_get_cstr(sql));
+#endif
+
+    if (dbpool_conn_select(pconn, sql, binds, &result) != 0) {
+        octstr_destroy(sql);
+        octstr_destroy(like);
+        dbpool_conn_produce(pconn);
+        return NULL;
+    }
+    octstr_destroy(sql);
+
+    gwlist_destroy(binds, NULL);
+    dbpool_conn_produce(pconn);
+
+#define LO2CSTR(r, i) octstr_get_cstr(gwlist_get(r, i))
+
+    if (gwlist_len(result) > 0) {
+        row = gwlist_extract_first(result);
+        res = dlr_entry_create();
+        gw_assert(res != NULL);
+        res->mask = atoi(LO2CSTR(row,0));
+        res->service = octstr_create(LO2CSTR(row, 1));
+        res->url = octstr_create(LO2CSTR(row,2));
+        res->source = octstr_create(LO2CSTR(row, 3));
+        res->destination = octstr_create(LO2CSTR(row, 4));
+        res->boxc_id = octstr_create(LO2CSTR(row, 5));
+        res->id = octstr_create(LO2CSTR(row, 6));
+        res->smsc = octstr_create(LO2CSTR(row, 7));
+    }
+    gwlist_destroy(result, NULL);
+
+#undef LO2CSTR
+
+    return res;
+}
+
+static struct dlr_entry* dlr_mysql_peek_with_smppid(const Octstr *id)
+{ 
+    dlr_mysql_peek_true(id, 1);
+}
+  
+static struct dlr_entry* dlr_mysql_peek(const Octstr *id)
+{
+    dlr_mysql_peek_true(id, 0);
+}
+
 static void dlr_mysql_remove(const Octstr *smsc, const Octstr *ts, const Octstr *dst)
 {
     Octstr *sql, *like;
@@ -209,7 +302,7 @@
     List *binds = gwlist_create();
     int res;
 
-    debug("dlr.mysql", 0, "removing DLR from database");
+    debug("dlr.mysql", 0, "dlr_mysql_remove: removing DLR from database");
 
     pconn = dbpool_conn_consume(pool);
     /* just for sure */
@@ -235,9 +328,9 @@
 #endif
 
     if ((res = dbpool_conn_update(pconn, sql, binds)) == -1)
-        error(0, "DLR: MYSQL: Error while removing dlr entry for DST<%s>", octstr_get_cstr(dst));
+        error(0, "DLR: MYSQL: dlr_mysql_remove: Error while removing dlr entry for DST<%s>", octstr_get_cstr(dst));
     else if (!res)
-        warning(0, "DLR: MYSQL: No dlr deleted for DST<%s>", octstr_get_cstr(dst));
+        warning(0, "DLR: MYSQL: dl_mysql_remove: No dlr deleted for DST<%s>", octstr_get_cstr(dst));
 
     dbpool_conn_produce(pconn);
     gwlist_destroy(binds, NULL);
@@ -245,6 +338,39 @@
     octstr_destroy(like);
 }
 
+static void dlr_mysql_remove_by_id(const Octstr *id)
+{
+    Octstr *sql;
+    DBPoolConn *pconn;
+    List *binds = gwlist_create();
+    int res;
+
+    debug("dlr.mysql", 0, "dlr_mysql_remove_by_id: removing DLR from database");
+
+    pconn = dbpool_conn_consume(pool);
+    /* just for sure */
+    if (pconn == NULL)
+        return;
+
+    sql = octstr_format("DELETE FROM `%S` WHERE `%S`=? LIMIT 1",
+                        fields->table, fields->field_id);
+
+    gwlist_append(binds, (Octstr *)id);
+
+#if defined(DLR_TRACE)
+    debug("dlr.mysql", 0, "sql: %s", octstr_get_cstr(sql));
+#endif
+
+    if ((res = dbpool_conn_update(pconn, sql, binds)) == -1)
+        error(0, "DLR: MYSQL: dlr_mysql_remove_by_id: Error while removing dlr entry for ID<%s>", octstr_get_cstr(id));
+    else if (!res)
+        warning(0, "DLR: MYSQL: dlr_nysql_remove_by_id: No dlr deleted for ID<%s>", octstr_get_cstr(id));
+
+    dbpool_conn_produce(pconn);
+    gwlist_destroy(binds, NULL);
+    octstr_destroy(sql);
+}
+
 static void dlr_mysql_update(const Octstr *smsc, const Octstr *ts, const Octstr *dst, int status)
 {
     Octstr *sql, *os_status, *like;
@@ -353,8 +479,11 @@
     .type = "mysql",
     .dlr_add = dlr_mysql_add,
     .dlr_get = dlr_mysql_get,
+    .dlr_peek_with_smppid = dlr_mysql_peek_with_smppid,
+    .dlr_peek = dlr_mysql_peek,
     .dlr_update = dlr_mysql_update,
     .dlr_remove = dlr_mysql_remove,
+    .dlr_remove_by_id = dlr_mysql_remove_by_id,
     .dlr_shutdown = dlr_mysql_shutdown,
     .dlr_messages = dlr_mysql_messages,
     .dlr_flush = dlr_mysql_flush
@@ -397,6 +526,9 @@
     octstr_replace(fields->field_status, octstr_imm("`"), octstr_imm("``"));
     octstr_replace(fields->field_boxc, octstr_imm("`"), octstr_imm("``"));
 
+    if (fields->field_id && octstr_len(fields->field_id) > 0)
+        octstr_replace(fields->field_id, octstr_imm("`"), octstr_imm("``"));
+
     /*
      * now grap the required information from the 'mysql-connection' group
      * with the mysql-id we just obtained
@@ -466,6 +598,7 @@
  * Return NULL , so we point dlr-core that we were
  * not compiled in.
  */
+    
 struct dlr_storage *dlr_init_mysql(Cfg* cfg)
 {
     return NULL;
Binary files /Users/syvanen/gateway/gw/dlr_mysql.o and gw/dlr_mysql.o differ
Binary files /Users/syvanen/gateway/gw/dlr_oracle.o and gw/dlr_oracle.o differ
diff -u -b -B -x '*conf' /Users/syvanen/gateway/gw/dlr_p.h gw/dlr_p.h
--- /Users/syvanen/gateway/gw/dlr_p.h	2011-12-15 16:11:04.000000000 +0100
+++ gw/dlr_p.h	2011-12-19 11:32:53.000000000 +0100
@@ -63,6 +63,7 @@
  * Andreas Fink <[email protected]>, 18.08.2001
  * Stipe Tolj <[email protected]>, 22.03.2002
  * Alexander Malysh <[email protected]>
+ * Aarno Syvanen <[email protected]> 27.10.2011
 */
 
 #ifndef	DLR_P_H
@@ -84,6 +85,7 @@
    Octstr *url;
    Octstr *boxc_id;
    int mask;
+   Octstr *id;
 };
 
 /*
@@ -120,9 +122,17 @@
      */
     struct dlr_entry* (*dlr_get) (const Octstr *smsc, const Octstr *ts, const Octstr *dst);
     /*
-     * Remove matching dlr entry from storage
+     * Find and return struct dlr_entry. dlr_peek  will use msg id as key, 
+     * *smppid will match first 8 charaxters. If entry not found return NULL.
+     * NOTE: Caller will detroy struct dlr_entry
+     */
+    struct dlr_entry* (*dlr_peek) (const Octstr *id);
+    struct dlr_entry* (*dlr_peek_with_smppid) (const Octstr *id);
+    /*
+     * Remove matching dlr entry from storage 0
      */
     void (*dlr_remove) (const Octstr *smsc, const Octstr *ts, const Octstr *dst);
+    void (*dlr_remove_by_id)(const Octstr *id);
     /*
      * Update dlr entry status field if any.
      */
@@ -156,6 +166,7 @@
     Octstr *field_mask;
     Octstr *field_status;
     Octstr *field_boxc;
+    Octstr *field_id;
 };
 
 struct dlr_db_fields *dlr_db_fields_create(CfgGroup *grp);
Binary files /Users/syvanen/gateway/gw/dlr_pgsql.o and gw/dlr_pgsql.o differ
Binary files /Users/syvanen/gateway/gw/dlr_sdb.o and gw/dlr_sdb.o differ
Binary files /Users/syvanen/gateway/gw/dlr_sqlite3.o and gw/dlr_sqlite3.o differ
Binary files /Users/syvanen/gateway/gw/heartbeat.o and gw/heartbeat.o differ
Binary files /Users/syvanen/gateway/gw/html.o and gw/html.o differ
Binary files /Users/syvanen/gateway/gw/load.o and gw/load.o differ
Binary files /Users/syvanen/gateway/gw/meta_data.o and gw/meta_data.o differ
Binary files /Users/syvanen/gateway/gw/mime_decompiler.o and gw/mime_decompiler.o differ
Binary files /Users/syvanen/gateway/gw/msg.o and gw/msg.o differ
Binary files /Users/syvanen/gateway/gw/numhash.o and gw/numhash.o differ
Binary files /Users/syvanen/gateway/gw/ota_compiler.o and gw/ota_compiler.o differ
Binary files /Users/syvanen/gateway/gw/ota_prov.o and gw/ota_prov.o differ
Binary files /Users/syvanen/gateway/gw/shared.o and gw/shared.o differ
Binary files /Users/syvanen/gateway/gw/sms.o and gw/sms.o differ
Binary files /Users/syvanen/gateway/gw/smsbox and gw/smsbox differ
Binary files /Users/syvanen/gateway/gw/smsbox.o and gw/smsbox.o differ
Common subdirectories: /Users/syvanen/gateway/gw/smsc and gw/smsc
Binary files /Users/syvanen/gateway/gw/smscconn.o and gw/smscconn.o differ
Binary files /Users/syvanen/gateway/gw/urltrans.o and gw/urltrans.o differ
Binary files /Users/syvanen/gateway/gw/wap-appl.o and gw/wap-appl.o differ
Binary files /Users/syvanen/gateway/gw/wap-error.o and gw/wap-error.o differ
Binary files /Users/syvanen/gateway/gw/wap-maps.o and gw/wap-maps.o differ
Binary files /Users/syvanen/gateway/gw/wap_push_ota.o and gw/wap_push_ota.o differ
Binary files /Users/syvanen/gateway/gw/wap_push_pap_compiler.o and gw/wap_push_pap_compiler.o differ
Binary files /Users/syvanen/gateway/gw/wap_push_pap_mime.o and gw/wap_push_pap_mime.o differ
Binary files /Users/syvanen/gateway/gw/wap_push_ppg.o and gw/wap_push_ppg.o differ
Binary files /Users/syvanen/gateway/gw/wap_push_ppg_pushuser.o and gw/wap_push_ppg_pushuser.o differ
Binary files /Users/syvanen/gateway/gw/wap_push_si_compiler.o and gw/wap_push_si_compiler.o differ
Binary files /Users/syvanen/gateway/gw/wap_push_sl_compiler.o and gw/wap_push_sl_compiler.o differ
Binary files /Users/syvanen/gateway/gw/wapbox and gw/wapbox differ
Binary files /Users/syvanen/gateway/gw/wapbox.o and gw/wapbox.o differ
Binary files /Users/syvanen/gateway/gw/wml_compiler.o and gw/wml_compiler.o differ
Binary files /Users/syvanen/gateway/gw/xml_shared.o and gw/xml_shared.o differ
gwlib.diff (application/octet-stream, 5.2 KB)
Common subdirectories: /Users/syvanen/gateway/gwlib/.svn and gwlib/.svn
Binary files /Users/syvanen/gateway/gwlib/accesslog.o and gwlib/accesslog.o differ
diff -u -b -B -x 'gw_uuid_types.*' /Users/syvanen/gateway/gwlib/cfg.def gwlib/cfg.def
--- /Users/syvanen/gateway/gwlib/cfg.def	2011-12-15 16:08:39.000000000 +0100
+++ gwlib/cfg.def	2011-12-19 11:32:57.000000000 +0100
@@ -622,6 +622,7 @@
     OCTSTR(field-mask)
     OCTSTR(field-status)
     OCTSTR(field-boxc-id)
+    OCTSTR(field-msg-id)
 )
 
 
Binary files /Users/syvanen/gateway/gwlib/cfg.o and gwlib/cfg.o differ
Binary files /Users/syvanen/gateway/gwlib/charset.o and gwlib/charset.o differ
diff -u -b -B -x 'gw_uuid_types.*' /Users/syvanen/gateway/gwlib/conn.c gwlib/conn.c
--- /Users/syvanen/gateway/gwlib/conn.c	2011-12-15 16:08:39.000000000 +0100
+++ gwlib/conn.c	2011-12-19 11:32:57.000000000 +0100
@@ -1474,3 +1474,24 @@
     else
 	return conn->fd;
 }
+
+void conn_dump_salient(Connection *conn)
+{
+    debug("gwlib.conn", 0, "connection dump starts");
+    debug("gwlib.conn", 0, "is connection claimed %d", conn->claimed);
+#ifndef NO_GWASSERT
+    debug("gwlib.conn", 0, "claiming thread was %ld", conn->claiming_thread);
+#endif
+
+    debug("gwlib.conn", 0, "conn is using socket %d", conn->fd);
+    debug("gwlib.conn", 0, "was conn connected ?", conn->connected);
+    debug("gwlib.conn", 0, "conn outbuf was %s", octstr_get_cstr(conn->outbuf));
+    debug("gwlib.conn", 0, "conn output pointer was at %ld", conn->outbufpos);
+    debug("gwlib.conn", 0, "conn buffers until %ld bytes", conn->output_buffering);
+    debug("gwlib.conn", 0, "conn inbuf was %s", octstr_get_cstr(conn->inbuf));
+    debug("gwlib.conn", 0, "conn intput pointer was at %ld", conn->inbufpos);
+
+    debug("gwlib.conn", 0, "conn eof flag was %d", conn->read_eof);
+    debug("gwlib.conn", 0, "conn error flag was %d", conn->io_error);
+    debug("gwlib.conn", 0, "connection dump ends");
+}
diff -u -b -B -x 'gw_uuid_types.*' /Users/syvanen/gateway/gwlib/conn.h gwlib/conn.h
--- /Users/syvanen/gateway/gwlib/conn.h	2011-12-15 16:08:39.000000000 +0100
+++ gwlib/conn.h	2011-12-19 11:32:57.000000000 +0100
@@ -346,3 +346,5 @@
 #endif /* HAVE_LIBSSL */
 
 int conn_get_id(Connection *conn);
+
+void conn_dump_salient(Connection *conn);
Binary files /Users/syvanen/gateway/gwlib/conn.o and gwlib/conn.o differ
Binary files /Users/syvanen/gateway/gwlib/counter.o and gwlib/counter.o differ
Binary files /Users/syvanen/gateway/gwlib/date.o and gwlib/date.o differ
Binary files /Users/syvanen/gateway/gwlib/dbpool.o and gwlib/dbpool.o differ
Binary files /Users/syvanen/gateway/gwlib/dbpool_mssql.o and gwlib/dbpool_mssql.o differ
Binary files /Users/syvanen/gateway/gwlib/dbpool_mysql.o and gwlib/dbpool_mysql.o differ
Binary files /Users/syvanen/gateway/gwlib/dbpool_oracle.o and gwlib/dbpool_oracle.o differ
Binary files /Users/syvanen/gateway/gwlib/dbpool_pgsql.o and gwlib/dbpool_pgsql.o differ
Binary files /Users/syvanen/gateway/gwlib/dbpool_sdb.o and gwlib/dbpool_sdb.o differ
Binary files /Users/syvanen/gateway/gwlib/dbpool_sqlite.o and gwlib/dbpool_sqlite.o differ
Binary files /Users/syvanen/gateway/gwlib/dbpool_sqlite3.o and gwlib/dbpool_sqlite3.o differ
Binary files /Users/syvanen/gateway/gwlib/dict.o and gwlib/dict.o differ
Binary files /Users/syvanen/gateway/gwlib/fdset.o and gwlib/fdset.o differ
Binary files /Users/syvanen/gateway/gwlib/gw-prioqueue.o and gwlib/gw-prioqueue.o differ
Binary files /Users/syvanen/gateway/gwlib/gw-rwlock.o and gwlib/gw-rwlock.o differ
Binary files /Users/syvanen/gateway/gwlib/gw_uuid.o and gwlib/gw_uuid.o differ
Binary files /Users/syvanen/gateway/gwlib/gwlib.o and gwlib/gwlib.o differ
Binary files /Users/syvanen/gateway/gwlib/gwmem-check.o and gwlib/gwmem-check.o differ
Binary files /Users/syvanen/gateway/gwlib/gwmem-native.o and gwlib/gwmem-native.o differ
Binary files /Users/syvanen/gateway/gwlib/gwpoll.o and gwlib/gwpoll.o differ
Binary files /Users/syvanen/gateway/gwlib/gwthread-pthread.o and gwlib/gwthread-pthread.o differ
Binary files /Users/syvanen/gateway/gwlib/http.o and gwlib/http.o differ
Binary files /Users/syvanen/gateway/gwlib/list.o and gwlib/list.o differ
Binary files /Users/syvanen/gateway/gwlib/log.o and gwlib/log.o differ
Binary files /Users/syvanen/gateway/gwlib/md5.o and gwlib/md5.o differ
Binary files /Users/syvanen/gateway/gwlib/mime.o and gwlib/mime.o differ
Binary files /Users/syvanen/gateway/gwlib/octstr.o and gwlib/octstr.o differ
Binary files /Users/syvanen/gateway/gwlib/parse.o and gwlib/parse.o differ
Binary files /Users/syvanen/gateway/gwlib/pcre.o and gwlib/pcre.o differ
Binary files /Users/syvanen/gateway/gwlib/pki.o and gwlib/pki.o differ
Binary files /Users/syvanen/gateway/gwlib/protected.o and gwlib/protected.o differ
Binary files /Users/syvanen/gateway/gwlib/regex.o and gwlib/regex.o differ
Binary files /Users/syvanen/gateway/gwlib/semaphore.o and gwlib/semaphore.o differ
Binary files /Users/syvanen/gateway/gwlib/socket.o and gwlib/socket.o differ
Binary files /Users/syvanen/gateway/gwlib/ssl.o and gwlib/ssl.o differ
Binary files /Users/syvanen/gateway/gwlib/thread.o and gwlib/thread.o differ
Binary files /Users/syvanen/gateway/gwlib/utils.o and gwlib/utils.o differ
Binary files /Users/syvanen/gateway/gwlib/xmlrpc.o and gwlib/xmlrpc.o differ
doc.diff (application/octet-stream, 3.8 KB)
Common subdirectories: /Users/syvanen/gateway/addons/opensmppbox/doc/.svn and doc/.svn
Only in doc: SMPPBox.ps
Only in doc: userguide.html
Only in doc: userguide.pdf
Only in doc: userguide.ps
Only in doc: userguide.rtf
diff -u -b -B -x 'Makefile*' /Users/syvanen/gateway/addons/opensmppbox/doc/userguide.xml doc/userguide.xml
--- /Users/syvanen/gateway/addons/opensmppbox/doc/userguide.xml	2011-12-15 16:11:55.000000000 +0100
+++ doc/userguide.xml	2011-12-19 11:32:41.000000000 +0100
@@ -693,7 +693,22 @@
                 <literal>string</literal>
               </entry>
               <entry valign="bottom">
-                Optional opensmppbox instance identifier. This is used for logging identification.
+                Optional opensmppbox instance identifier. This is used for logging identification,
+                and as opensmppbox box id, if usesmppboxid is set
+              </entry>
+            </row>
+            <row>
+            <entry>
+              <literal> use-smppboxid (o) </literal>
+            </entry>
+            <entry>
+              <literal> Boolean </literal>
+            </entry>
+            <entry valign = "bottom">
+                If this flag is set, openamppbox will make only one connection to bearwrbox, and
+                use opensmppbox-id as its box id. If this flag is not set, every client will 
+                have a separate connection instead. Not that if you use this flag, you must set
+                use-systemid-as-smsboxid flag, too.
               </entry>
             </row>
             <row>
@@ -864,6 +879,58 @@
       receive a valid pdu in this number of seconds.
 		(Defaults to 300).
      </entry></row>
+     </table>
+
+    <para>
+    If usesmppbox-id variable is used, you probably want to configure
+    client-route group. It instructs opensmppbox to route MOs to a 
+    specific client. If this group is missing, a random client is 
+    picked instead. If both shortcode and and client-id variable is 
+    supplied, this is interpreted as logical AND: only messages 
+    addressed to the specifed shotcode and coming from specified 
+    smsc are routed to client in question.
+    </para>
+
+    <table frame="none">
+                                <title>client-route Group Variables</title>
+        <tgroup cols="3">
+          <thead>
+            <row>
+              <entry>Variable</entry>
+              <entry>Value</entry>
+              <entry>Description</entry>
+            </row>
+          </thead>
+          <tbody>
+    <row><entry><literal>group (o) </literal></entry>
+      <entry><literal>client-route</literal></entry>
+      <entry valign="bottom">
+      Optional. Configure this if you se use-smppboxid variable 
+      and want MOs routed to a certain client
+      </entry></row> 
+
+    <row><entry><literal>client-id</literal></entry>
+      <entry><literal>string</literal></entry>
+      <entry valign="bottom">
+      System id of the client the messages should be routed. Use
+      variable use-systemid-as-smsboxid to allow routing. 
+      </entry></row>
+
+    <row><entry><literal>shortcode</literal></entry>
+      <entry><literal>list of numbers separated by semicolons</literal></entry>
+      <entry valign="bottom">
+      If set, specifies which receiver numbers for inbound messages 
+      should be routed to this client. This rule may be used to pull 
+      receiver number specific message streams to a client. 
+      </entry></row>
+
+    <row><entry><literal>smsc-id</literal></entry>
+      <entry><literal>list of strings separated by semicolons</literal></entry>
+      <entry valign="bottom">
+      If set, specifies from which smsc-ids all inbound messages should be routed 
+      to this client. This rule may be used to pull any smsc specific message 
+      stream to a client.
+      </entry></row>
           </tbody>
         </tgroup>
 </table>
box.diff (application/octet-stream, 40.5 KB)
Only in gw: .deps
Only in gw: .libs
Common subdirectories: /Users/syvanen/gateway/addons/opensmppbox/gw/.svn and gw/.svn
Only in gw: opensmppbox
diff -u -b -B -x 'Makefile*' /Users/syvanen/gateway/addons/opensmppbox/gw/opensmppbox-cfg.def gw/opensmppbox-cfg.def
--- /Users/syvanen/gateway/addons/opensmppbox/gw/opensmppbox-cfg.def	2011-12-15 16:11:57.000000000 +0100
+++ gw/opensmppbox-cfg.def	2011-12-19 11:32:42.000000000 +0100
@@ -78,6 +78,13 @@
 	OCTSTR(dest-addr-npi)
 	OCTSTR(timeout)
 	OCTSTR(use-systemid-as-smsboxid)
+        OCTSTR(use-smppboxid)
 	OCTSTR(enable-pam)
 	OCTSTR(pam-acl)
 )
+
+MULTI_GROUP(client-route,
+    OCTSTR(client-id)
+    OCTSTR(smsc-id)
+    OCTSTR(shortcode)
+) 
diff -u -b -B -x 'Makefile*' /Users/syvanen/gateway/addons/opensmppbox/gw/opensmppbox.c gw/opensmppbox.c
--- /Users/syvanen/gateway/addons/opensmppbox/gw/opensmppbox.c	2011-12-15 16:11:57.000000000 +0100
+++ gw/opensmppbox.c	2011-12-19 11:32:42.000000000 +0100
@@ -118,18 +118,24 @@
 
 static Octstr *smppbox_id;
 static Octstr *our_system_id;
-static Octstr *route_to_smsc;
+static Octstr *route_to_smsc = NULL;
 static time_t smpp_timeout;
 
 static int systemidisboxcid;
 static int enablepam;
 static Octstr *pamacl;
+static int usesmppboxid;
 
+static Dict *client_by_smsc;
+static Dict *client_by_receiver;
+static Dict *client_by_smsc_receiver;
 
 #define TIMEOUT_SECONDS 300
 
 typedef enum { SMPP_LOGIN_NOTLOGGEDIN, SMPP_LOGIN_TRANSMITTER, SMPP_LOGIN_RECEIVER, SMPP_LOGIN_TRANSCEIVER } smpp_login;
 
+enum {USE_UUID = 0, USE_SMPP_ID = 1};
+
 typedef struct _boxc {
     Connection	*smpp_connection;
     Connection	*bearerbox_connection;
@@ -150,6 +156,7 @@
     Semaphore	*pending;
     volatile sig_atomic_t alive;
     Octstr	*boxc_id; /* identifies the connected opensmppbox instance */
+    Octstr      *our_id;   /* pir own id*/
     Octstr	*sms_service;
     Octstr	*route_to_smsc;
     Dict	*msg_acks;
@@ -173,11 +180,95 @@
 
 } Boxc;
 
+typedef struct _bbboxc {
+    Connection *bearerbox_connection;
+    volatile sig_atomic_t alive;
+    Dict       *msg_acks;
+    Dict       *deliver_acks;
+    int        mo_recode;
+} BBBoxc;
+
+static BBBoxc *bbbox;
+
+
+static void box_dump_salient(Boxc *boxc)
+{
+        if (!boxc) {
+                debug("opensmppbox", 0, "client pointer points to NULL");
+                return;
+        }
+
+        debug("opensmppbox", 0, "client dump starts");
+        if (boxc->client_ip)   
+                debug("opensmppbox", 0, "client ip address was <%s>", octstr_get_cstr(boxc->client_ip));
+        if (boxc->boxc_id)
+                debug("opensmppbox", 0, "client box id (username) was <%s>", octstr_get_cstr(boxc->boxc_id));
+        if (boxc->our_id)
+                debug("opensmppbox", 0, "client id was <%s>", octstr_get_cstr(boxc->our_id));
+        if (boxc->bearerbox_connection) {
+                debug("opensmppbox", 0, "bearerboz connection was");
+                conn_dump_salient(boxc->bearerbox_connection);
+        }
+        if (boxc->smpp_connection) {
+                debug("opensmppbox", 0, "client connection was");
+                conn_dump_salient(boxc->smpp_connection);
+        }
+
+        debug("opensmppbox", 0, "client dump ends");
+}
+
+static void box_list_dump_salient(List *boxes)
+{ 
+        long i, len;
+        Boxc *box;
+  
+        i = 0;
+        len = gwlist_len(boxes);
+        while (i < len) {
+                box = gwlist_get(boxes, i);
+                box_dump_salient(box);
+                ++i;
+        }
+}
+
+void box_id_dict_dump(Dict *box_id_by_msg_id)
+{
+        long i, count;
+        List *ids;
+        Octstr *key;
+        Octstr *box_id;
+
+        gwlib_assert_init();
+
+        debug("opensmppbox", 0, "Dumping box_id dict:");
+        if (!box_id_by_msg_id) {
+                debug("opensmppbox", 0, "box id dict point NULL");
+                return;
+        }
+
+        ids = dict_keys(box_id_by_msg_id);
+        count = dict_key_count(box_id_by_msg_id);
+        for (i = 0; i < count; ++i) {
+                key = gwlist_get(ids, i);
+                box_id = dict_get(box_id_by_msg_id, key);
+                debug("opensmppbox", 0, "Msg id %ld was: ", i);
+                octstr_dump(key, 0);
+                debug("opensmppbox", 0, "Box id %ld was: ", i);
+                octstr_dump(box_id, 0);
+        }
+        debug("opensmppbox", 0, "End of dump.");
+}
+
 void smpp_pdu_destroy_item(void *pdu)
 {
 	smpp_pdu_destroy(pdu);
 }
 
+static void shutdown_connection(Connection *conn)
+{
+        conn_destroy(conn);
+}
+
 /*
  * Use PAM (Pluggable Authentication Module) to check sendsms authentication.
  */
@@ -309,16 +401,20 @@
 valid_login:
 	for (box = 0; box < gwlist_len(all_boxes); box++) {
 		thisbox = (Boxc *)gwlist_get(all_boxes, box);
-		if (octstr_compare(system_type, thisbox->boxc_id) == 0 && (thisbox->login_type == SMPP_LOGIN_TRANSCEIVER || (thisbox->login_type == login_type))) {
-			debug("bb.sms.smpp", 0, "opensmppbox[%s]: Multiple login: disconnect.",
+	     if (octstr_compare(system_type, thisbox->boxc_id) == 0 && (thisbox->login_type == SMPP_LOGIN_TRANSCEIVER 
+                        || (thisbox->login_type == login_type))) {
+		debug("opensmppbox", 0, "opensmppbox[%s]: Multiple login: disconnect.",
 				octstr_get_cstr(thisbox->boxc_id));
 			thisbox->alive = 0;
 #ifdef HAVE_SHUTDOWN_CONNECTION
+                if (!use_smppboxid)
 			shutdown_connection(thisbox->bearerbox_connection);
 			shutdown_connection(thisbox->smpp_connection);
 #endif
 		}
 	}
+        debug("opensmppbox", 0, "Accepted client with box id (username ?) <%s>",
+              octstr_get_cstr(boxc->boxc_id));
 	return 1;
 }
 
@@ -342,6 +438,12 @@
     } while(0)
 #endif
 
+static void dump_pdu_debug(char *msg, Octstr *id, SMPP_PDU *pdu)
+{
+        debug("opensmppbox", 0, "SMPP[%s]: %s", \
+              octstr_get_cstr(id), msg);          \
+        smpp_pdu_dump(pdu);
+}
 
 /*
  * Converting SMPP timestamp to minutes relative 
@@ -396,7 +498,7 @@
         localdiff = difftime(gw_mktime(&local), gw_mktime(&tm));
         valutc += localdiff;
 
-        debug("sms.smpp",0, "diff between utc and localtime (%d)", localdiff);
+        debug("opensmppbox",0, "diff between utc and localtime (%d)", localdiff);
         diff = diff*15*60;
         switch(relation) {
             case '+':
@@ -421,10 +523,10 @@
         return -1;
     }
     tm = gw_gmtime(valutc);
-    debug("sms.smpp",0,"Requested UTC timestamp: %02d-%02d-%02d %02d:%02d:%02d",
+    debug("opensmppbox",0,"Requested UTC timestamp: %02d-%02d-%02d %02d:%02d:%02d",
             tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
 
-    debug("sms.smpp", 0, "requested timestamp in min. (%ld)", (valutc - utc)/60);
+    debug("opensmppbox", 0, "requested timestamp in min. (%ld)", (valutc - utc)/60);
 
     return ceil ( difftime (valutc, utc) / 60 );
 }
@@ -440,7 +542,7 @@
 
 /* send to bearerbox */
 
-static int send_msg(Connection *conn, Boxc *boxconn, Msg *pmsg)
+static int send_msg(Connection *conn, Msg *pmsg)
 {
 	/* Caution: implicit msg_destroy */
 	write_to_bearerbox_real(conn, pmsg);
@@ -456,7 +558,7 @@
 
 	for (pos = 0; pos < gwlist_len(all_boxes); pos++) {
 		box = (Boxc *)gwlist_get(all_boxes, pos);
-		send_msg(box->bearerbox_connection, box, msg);
+		send_msg(box->bearerbox_connection, msg);
 	}
 }
 */
@@ -474,25 +576,25 @@
  * Do this even while no opensmppbox-id is given to unlock the sender thread in
  * bearerbox.
  */
-static void identify_to_bearerbox(Boxc *conn)
+static void identify_to_bearerbox(Connection *conn, Octstr *our_id)
 {
     Msg *msg;
 
     msg = msg_create(admin);
     msg->admin.command = cmd_identify;
-    msg->admin.boxc_id = octstr_duplicate(conn->boxc_id);
-    send_msg(conn->bearerbox_connection, conn, msg);
+    msg->admin.boxc_id = octstr_duplicate(our_id);
+    send_msg(conn, msg);
 }
 
 /* read from bearerbox */
 
-static Msg *read_from_box(Connection *conn, Boxc *boxconn)
+static Msg *read_from_box(Connection *conn, int alive)
 {
     Octstr *pack;
     Msg *msg;
 
     pack = NULL;
-    while (boxconn->alive) {
+    while (alive) {
 	switch (read_from_bearerbox_real(conn, &msg, 1.0)) {
 	case -1:
 	    /* connection to bearerbox lost */
@@ -626,7 +728,7 @@
 /* generate 8 character ID, taken from msgid */
 static Octstr *generate_smppid(Msg *msg)
 {
-	char uuidbuf[100];
+	char uuidbuf[UUID_STR_LEN + 1];
 	Octstr *result;
 
 	// gw_assert(msg->type == sms); // we segfault on this
@@ -673,7 +775,7 @@
     if (*pdu == NULL) {
         error(0, "opensmppbox[%s]: PDU unpacking failed.",
               octstr_get_cstr(box->boxc_id));
-        debug("bb.sms.smpp", 0, "opensmppbox[%s]: Failed PDU omitted.",
+        debug("opensmppbox", 0, "opensmppbox[%s]: Failed PDU omitted.",
               octstr_get_cstr(box->boxc_id));
         /* octstr_dump(os, 0); */
         octstr_destroy(os);
@@ -684,6 +786,35 @@
     return 1;
 }
 
+ /* To route to the original client, we restore the original
+  * box id (i.e., essentially the username). */
+static void restore_box_id(Msg *msg, Octstr *msgid)
+{ 
+      if (msg->sms.sms_type == report_mo) {
+                List *parts = octstr_split(msg->sms.dlr_url, octstr_imm(";"));
+                Octstr *msgid = gwlist_extract_first(parts);
+                Msg *dlr = dlr_find_by_id(msgid, USE_SMPP_ID);
+                Octstr *boxid = NULL;
+                if (dlr && dlr->sms.boxc_id)
+                        boxid = octstr_duplicate(dlr->sms.boxc_id);
+                   
+                if (boxid) {
+                        octstr_destroy(msg->sms.boxc_id);
+                        msg->sms.boxc_id = octstr_duplicate(boxid);
+                }
+
+                octstr_destroy(boxid);
+                msg_destroy(dlr);
+       }
+       /* MOs are routed separately, using smsc_id */
+}
+
+static void change_box_id(Msg *msg)
+{
+        octstr_destroy(msg->sms.boxc_id);
+        msg->sms.boxc_id = octstr_duplicate(smppbox_id);
+}
+
 static List *msg_to_pdu(Boxc *box, Msg *msg)
 {
     SMPP_PDU *pdu, *pdu2;
@@ -717,7 +848,7 @@
     if(box->source_addr_ton > -1 && box->source_addr_npi > -1) {
         pdu->u.deliver_sm.source_addr_ton = box->source_addr_ton;
         pdu->u.deliver_sm.source_addr_npi = box->source_addr_npi;
-        debug("bb.sms.smpp", 0, "SMPP[%s]: Manually forced source addr ton = %ld, source add npi = %ld",
+        debug("opensmppbox", 0, "SMPP[%s]: Manually forced source addr ton = %ld, source add npi = %ld",
               octstr_get_cstr(box->boxc_id), box->source_addr_ton,
               box->source_addr_npi);
     } else {
@@ -749,7 +880,7 @@
     if (box->dest_addr_ton > -1 && box->dest_addr_npi > -1) {
         pdu->u.deliver_sm.dest_addr_ton = box->dest_addr_ton;
         pdu->u.deliver_sm.dest_addr_npi = box->dest_addr_npi;
-        debug("bb.sms.smpp", 0, "SMPP[%s]: Manually forced dest addr ton = %ld, dest add npi = %ld",
+        debug("opensmppbox", 0, "SMPP[%s]: Manually forced dest addr ton = %ld, dest add npi = %ld",
               octstr_get_cstr(box->boxc_id), box->dest_addr_ton,
               box->dest_addr_npi);
     } else {
@@ -771,6 +902,7 @@
         octstr_len(pdu->u.deliver_sm.source_addr) > 20) {
         smpp_pdu_destroy(pdu);
         gwlist_destroy(pdulist, NULL);
+        warning(0, "opensmppbox: msg_to_pdu: address too long, not acceptable");
         return NULL;
     }
 
@@ -819,6 +952,7 @@
 		gwlist_destroy(pdulist, NULL);
 		octstr_destroy(msgid);
 		gwlist_destroy(parts, octstr_destroy_item);
+                warning(0, "opensmppbox: msg_to_pdu: no msg corresponding dlr, ignoring");
 		return NULL;
 	}
 	dlvrd = octstr_imm("000");
@@ -901,7 +1035,6 @@
 			if (box->version > 0x33) {
 				pdu2->u.deliver_sm.receipted_message_id = octstr_duplicate(msgid2);
 				pdu2->u.deliver_sm.message_state = dlr_state;
-				dict_destroy(pdu2->u.deliver_sm.tlv);
 				pdu2->u.deliver_sm.tlv = meta_data_get_values(msg->sms.meta_data, "smpp");
 			}
 			pdu2->u.deliver_sm.short_message = octstr_format("id:%S sub:001 dlvrd:%S submit date:%s done date:%s stat:%S err:%s text:%12s", msgid2, dlvrd, submit_date_c_str, done_date_c_str, dlr_status, err, text);
@@ -1028,7 +1161,7 @@
         parts = NULL;
     }
 
-    debug("SMPP", 0, "message length %ld, sending %ld message%s",
+    debug("opensmppbox", 0, "message length %ld, sending %ld message%s",
         octstr_len(msg->sms.msgdata), msg_count, msg_count == 1 ? "" : "s");
 
     if (parts) {
@@ -1055,7 +1188,6 @@
 	        }
 
 	        if (box->version > 0x33) {
-		    dict_destroy(pdu2->u.deliver_sm.tlv);
 	            pdu2->u.deliver_sm.tlv = meta_data_get_values(msg->sms.meta_data, "smpp");
 	        }
 
@@ -1067,7 +1199,6 @@
     }
     else {
         if (box->version > 0x33) {
-	    dict_destroy(pdu->u.deliver_sm.tlv);
             pdu->u.deliver_sm.tlv = meta_data_get_values(msg->sms.meta_data, "smpp");
         }
 
@@ -1162,7 +1292,7 @@
     if (pdu->u.submit_sm.esm_class & ESM_CLASS_SUBMIT_UDH_INDICATOR) {
         int udhl;
         udhl = octstr_get_char(msg->sms.msgdata, 0) + 1;
-        debug("bb.sms.smpp",0,"SMPP[%s]: UDH length read as %d", 
+        debug("opensmppbox",0,"SMPP[%s]: UDH length read as %d", 
               octstr_get_cstr(box->boxc_id), udhl);
         if (udhl > octstr_len(msg->sms.msgdata)) {
             error(0, "SMPP[%s]: Mallformed UDH length indicator 0x%03x while message length "
@@ -1348,7 +1477,7 @@
     if (pdu->u.data_sm.esm_class & ESM_CLASS_SUBMIT_UDH_INDICATOR) {
         int udhl;
         udhl = octstr_get_char(msg->sms.msgdata, 0) + 1;
-        debug("bb.sms.smpp",0,"SMPP[%s]: UDH length read as %d", 
+        debug("opensmppbox",0,"SMPP[%s]: UDH length read as %d", 
               octstr_get_cstr(box->boxc_id), udhl);
         if (udhl > octstr_len(msg->sms.msgdata)) {
             error(0, "SMPP[%s]: Mallformed UDH length indicator 0x%03x while message length "
@@ -1500,6 +1629,12 @@
 	int msg_to_send = 1;
 	List *parts_list = NULL;
 	char id[UUID_STR_LEN + 1];
+        Connection *bearerbox_connection;
+
+        if (usesmppboxid)
+                bearerbox_connection = bbbox->bearerbox_connection;
+        else
+                bearerbox_connection = box->bearerbox_connection;
 
 	dump_pdu("Got PDU:", box->boxc_id, pdu);
 	switch (pdu->type) {
@@ -1524,7 +1659,8 @@
 			box->login_type = SMPP_LOGIN_TRANSMITTER;
 			box->boxc_id = systemidisboxcid ? octstr_duplicate(pdu->u.bind_transmitter.system_id) : octstr_duplicate(system_type);
 			box->sms_service = octstr_duplicate(pdu->u.bind_transmitter.system_id);
-			identify_to_bearerbox(box);
+			if (!usesmppboxid)
+                                identify_to_bearerbox(box->bearerbox_connection, box->boxc_id);
 			resp = smpp_pdu_create(bind_transmitter_resp, pdu->u.bind_transmitter.sequence_number);
 			resp->u.bind_transmitter_resp.system_id = octstr_duplicate(our_system_id);
 		}
@@ -1541,7 +1677,8 @@
 			box->login_type = SMPP_LOGIN_RECEIVER;
 			box->boxc_id = systemidisboxcid ? octstr_duplicate(pdu->u.bind_transmitter.system_id) : octstr_duplicate(system_type);
 			box->sms_service = octstr_duplicate(pdu->u.bind_receiver.system_id);
-			identify_to_bearerbox(box);
+			if (!usesmppboxid)
+                                identify_to_bearerbox(box->bearerbox_connection, box->boxc_id);
 			resp = smpp_pdu_create(bind_receiver_resp, pdu->u.bind_receiver.sequence_number);
 			resp->u.bind_receiver_resp.system_id = octstr_duplicate(our_system_id);
 		}
@@ -1558,7 +1695,8 @@
 			box->login_type = SMPP_LOGIN_TRANSCEIVER;
 			box->boxc_id = systemidisboxcid ? octstr_duplicate(pdu->u.bind_transmitter.system_id) : octstr_duplicate(system_type);
 			box->sms_service = octstr_duplicate(pdu->u.bind_transceiver.system_id);
-			identify_to_bearerbox(box);
+			if (!usesmppboxid)
+                                identify_to_bearerbox(box->bearerbox_connection, box->boxc_id);
 			resp = smpp_pdu_create(bind_transceiver_resp, pdu->u.bind_transceiver.sequence_number);
 			resp->u.bind_transceiver_resp.system_id = octstr_duplicate(our_system_id);
 		}
@@ -1587,7 +1725,6 @@
 			check_multipart(box, msg, &msg_to_send, &msg2, &parts_list);
 			msg->sms.smsc_id = box->route_to_smsc ? octstr_duplicate(box->route_to_smsc) : NULL;
 			msg->sms.boxc_id = octstr_duplicate(box->boxc_id);
-			msg_dump(msg, 0);
 			resp = smpp_pdu_create(data_sm_resp, pdu->u.data_sm.sequence_number);
 			msgid = generate_smppid(msg);
 			msg->sms.dlr_url = octstr_duplicate(msgid);
@@ -1601,15 +1738,23 @@
 						msg2->sms.dlr_url = concat_msgids(msgid, parts_list);
 					}
 					dlr_add(box->boxc_id, msgid, msg2);
+					if (usesmppboxid)
+                                                change_box_id(msg2);
 					octstr_destroy(msgid);
 					octstr_destroy(msg2->sms.service);
 					msg2->sms.service = hold_service;
 				}
 				uuid_unparse(msg2->sms.id, id);
 				msgid = octstr_create(id);
+                                if (!usesmppboxid)
 				dict_put(box->msg_acks, msgid, resp);
+                                else
+                                        dict_put(bbbox->msg_acks, msgid, resp);
 				resp = NULL;
-				send_msg(box->bearerbox_connection, box, msg2);
+                                if (usesmppboxid) {
+                                        send_msg(bearerbox_connection, msg2);
+                                } else
+                                        send_msg(box->bearerbox_connection, msg2);
 				if (parts_list) {
 					/* destroy values */
 					gwlist_destroy(parts_list, msg_destroy_item);
@@ -1628,11 +1773,16 @@
 			check_multipart(box, msg, &msg_to_send, &msg2, &parts_list);
 			msg->sms.smsc_id = box->route_to_smsc ? octstr_duplicate(box->route_to_smsc) : NULL;
 			msg->sms.boxc_id = octstr_duplicate(box->boxc_id);
-			msg_dump(msg, 0);
 			resp = smpp_pdu_create(submit_sm_resp, pdu->u.submit_sm.sequence_number);
 			msgid = generate_smppid(msg);
 			msg->sms.dlr_url = octstr_duplicate(msgid);
-			resp->u.submit_sm_resp.message_id = msgid;
+			resp->u.submit_sm_resp.message_id = octstr_duplicate(msgid);
+			/* Note that we add msg to dlr tzble in all cases. 
+                         * In case of report_mo, need is obvious. In case 
+                         * of mo, it is needed to route resps. Report mo must be stored before
+                         * we gahnge box id.*/
+                        if (usesmppboxid)
+                                 msg_add(msg->sms.boxc_id, msgid, msg);
 			if (msg_to_send) {
 				if (DLR_IS_ENABLED(msg2->sms.dlr_mask)) {
 					hold_service = msg2->sms.service;
@@ -1641,17 +1791,26 @@
 					if (parts_list) {
 						msg2->sms.dlr_url = concat_msgids(msgid, parts_list);
 					}
-					dlr_add(box->boxc_id, msgid, msg2);
+                                        //dlr_add(box->boxc_id, msgid, msg2);
+                                        if (usesmppboxid)
+                                                change_box_id(msg2);
 					octstr_destroy(msgid);
 					octstr_destroy(msg2->sms.service);
 					msg2->sms.service = hold_service;
 				}
 				uuid_unparse(msg2->sms.id, id);
 				msgid = octstr_create(id);
+                                if (!usesmppboxid) {
 				dict_put(box->msg_acks, msgid, resp);
+				} else {
+                                        dict_put(bbbox->msg_acks, msgid, resp);
+                                }
 				octstr_destroy(msgid);
 				resp = NULL;
-				send_msg(box->bearerbox_connection, box, msg2);
+                                if (usesmppboxid) {
+                                        send_msg(bearerbox_connection, msg2);
+                                } else
+                                        send_msg(box->bearerbox_connection, msg2);
 				if (parts_list) {
 					/* destroy values */
 					gwlist_destroy(parts_list, msg_destroy_item);
@@ -1668,7 +1827,11 @@
 			if (pdu->u.deliver_sm_resp.command_status != 0) {
 				msg->ack.nack = ack_failed;
 			}
-			send_msg(box->bearerbox_connection, box, msg);
+                        if (usesmppboxid) {
+                                send_msg(bearerbox_connection, msg);
+                        } else {
+                                send_msg(box->bearerbox_connection, msg);
+                        }
 			dict_put(box->deliver_acks, msgid, NULL);
 		}
 		octstr_destroy(msgid);
@@ -1692,7 +1855,6 @@
 	smpp_pdu_destroy(pdu);
 	if (resp != NULL) {
 		send_pdu(conn, box->boxc_id, resp);
-		smpp_pdu_destroy(resp);
 	}
 }
 
@@ -1712,11 +1874,13 @@
     boxc->is_wap = 0;
     boxc->load = 0;
     boxc->smpp_connection = conn_wrap_fd(fd, ssl);
+    boxc->bearerbox_connection = NULL;
     boxc->id = counter_increase(boxid);
     boxc->client_ip = octstr_duplicate(ip);
     boxc->alive = 1;
     boxc->connect_time = time(NULL);
-    boxc->boxc_id = NULL;
+    boxc->boxc_id = octstr_duplicate(smppbox_id); /* Fix this one*/
+    boxc->our_id = octstr_duplicate(smppbox_id);
     boxc->routable = 0;
     boxc->smpp_pdu_counter = counter_create();
     boxc->alt_charset = NULL; /* todo: get from config */
@@ -1754,6 +1918,8 @@
 	    conn_destroy(boxc->bearerbox_connection);
     if (boxc->boxc_id)
 	    octstr_destroy(boxc->boxc_id);
+    if (boxc->our_id)
+            octstr_destroy(boxc->our_id);
     if (boxc->alt_charset)
 	    octstr_destroy(boxc->alt_charset);
     counter_destroy(boxc->smpp_pdu_counter);
@@ -1767,6 +1933,31 @@
     gw_free(boxc);
 }
 
+static BBBoxc *bbboxc_create(void)
+{
+    BBBoxc *boxc;
+
+    boxc = gw_malloc(sizeof(BBBoxc));
+    boxc->bearerbox_connection = connect_to_bearerbox_real(bearerbox_host, bearerbox_port, bearerbox_port_ssl, NULL);
+    boxc->alive = 1;
+    boxc->mo_recode = 0;
+    boxc->msg_acks = dict_create(1024, smpp_pdu_destroy_item);
+
+    return boxc;
+}
+
+static void bbboxc_destroy(BBBoxc *boxc)
+{
+    if (boxc == NULL)
+        return;
+
+    /* do not do anyhting to dict, it stpres only refrences*/
+    if (boxc->bearerbox_connection)
+        conn_destroy(boxc->bearerbox_connection);
+
+    gw_free(boxc);
+}
+
 /* ------------------------------------------------------------------
  * SMPP thingies
  * ------------------------------------------------------------------
@@ -1816,7 +2007,9 @@
     SMPP_PDU *pdu;
     long len;
 
+    error(0, "opensmppbox: smpp_to_bearerbox: thread starts");
     box->last_pdu_received = time(NULL);
+
     while (smppbox_status == SMPP_RUNNING && box->alive) {
 		len = 0;
 		switch (read_pdu(box, conn, &len, &pdu)) {
@@ -1838,16 +2031,117 @@
 		}
     }
 #ifdef HAVE_SHUTDOWN_CONNECTION
+    if (!usesmppboxid)
     shutdown_connection(box->bearerbox_connection);
 #endif
+    error(0, "opensmppbox: smpp_to_bearerbox: thread terminates");
+}
+
+/*
+ * Check do we have a specific route to pass this msg to client-id?
+ */
+static Octstr *find_configured_box_id(Msg *msg)
+{
+        Octstr *boxc_id = NULL;
+        Octstr *r, *s, *rs;
+        /*
+         * Check if we have a "client-route" for this msg.
+         * Where the shortcode route has a higher priority then the smsc-id rule.
+         * Highest priority has the combined <shortcode>:<smsc-id> route.
+         */
+         Octstr *os = octstr_format("%s:%s", octstr_get_cstr(msg->sms.receiver),
+                                    octstr_get_cstr(msg->sms.smsc_id));
+         s = (msg->sms.smsc_id ? dict_get(client_by_smsc, msg->sms.smsc_id) : NULL);
+         r = (msg->sms.receiver ? dict_get(client_by_receiver, msg->sms.receiver) : NULL);
+         rs = (os ? dict_get(client_by_smsc_receiver, os) : NULL);
+         octstr_destroy(os);
+         if (rs)
+                 boxc_id = rs;
+         else if (r)
+                 boxc_id = r;
+         else if (s)
+                 boxc_id = s;
+
+         return boxc_id;
 }
 
-/* if this login was made as a transmitter, then find the corresponding receiver connection */
-static Boxc *find_receiver_box(Boxc *box)
+static Boxc *find_box_by_id(Octstr *boxc_id)
+{
+        Boxc *thisbox;
+        long retry = 0;
+        long i = 0;
+        long cnt = gwlist_len(all_boxes);
+        while (i < cnt) {
+                thisbox = gwlist_get(all_boxes, i);
+                if (!thisbox ||  !(thisbox->boxc_id)) {
+                        ++i;
+                        continue;
+                }
+
+                /* Reject connection that is just sending*/
+                if (thisbox->login_type == SMPP_LOGIN_TRANSMITTER){
+                         ++i;
+                         continue;
+                }
+
+                /* If the client was not logged in, wait some time*/
+                if (thisbox->login_type == SMPP_LOGIN_NOTLOGGEDIN && retry < 30){
+                        ++retry;
+                        if (retry < 30)
+                                gwthread_sleep(0.1);
+                        else
+                                ++i;
+                        continue;
+                }
+
+                if (boxc_id && octstr_compare(thisbox->boxc_id, boxc_id) == 0) {
+                            return thisbox;
+                }
+                ++i;
+        }
+
+        return NULL;
+}
+
+/*
+ * This function is used to route msg acks. Ack's msg id maps ack to original
+ * message. The box id of the original message is used for routing.
+ */
+static Boxc *find_receiver_box_by_msg_id(Octstr *msgid, Msg *msg)
+{
+        Msg *sack;
+        Octstr *boxid = NULL;
+        Boxc *thisbox;
+
+        sack = dlr_find_by_id(msgid, USE_UUID);
+        if (sack)
+                boxid = sack->sms.boxc_id;
+        else
+                return NULL;
+
+        if (boxid)
+                thisbox = find_box_by_id(boxid);
+        else
+                return NULL;
+
+        return thisbox; 
+}
+
+/* if this login was made as a transmitter or receiver, then find the corresponding 
+ * receiver connection */
+static Boxc *find_receiver_box(Boxc *box, Msg *msg)
 {
 	Boxc *thisbox;
 	int cnt;
+        Octstr *boxc_id = NULL;
+        int report_mo = 0;
+        int retry = 0;
 
+        if (usesmppboxid && octstr_len(msg->sms.boxc_id) > 0) {
+                boxc_id = msg->sms.boxc_id;
+        }
+
+        if (!usesmppboxid) {
 	if (box->login_type == SMPP_LOGIN_RECEIVER || box->login_type == SMPP_LOGIN_TRANSCEIVER) {
 		return box;
 	}
@@ -1858,37 +2152,93 @@
 		}
 	}
 	return box;
+        }
+
+        else {
+                /* If we had no clients, wait a little, bearerbox may resend when they  be connecting*/
+                report_mo = 1;
+                while (gwlist_len(all_boxes) == 0 && retry < 3) {
+                        gwthread_sleep(1);
+                        ++retry;
+                }
+
+                if (gwlist_len(all_boxes) == 0 && retry == 3)
+                        return NULL;
+
+                if (msg->sms.sms_type == mo) {
+                        report_mo = 0;
+                        boxc_id = find_configured_box_id(msg);
+                }
+
+               /*
+                * Report_mos will be routed simply by they boxc-id, defined by smsbox-route. 
+                * If there is none, a random box is picked.
+                */
+                thisbox = find_box_by_id(boxc_id);
+
+                /* If no box was found and we have MO, pick a random box. In case
+                 * of DLR, this is an error. */
+                if (!thisbox && report_mo) {
+                        thisbox = gwlist_get(all_boxes, gw_rand() % gwlist_len(all_boxes));
+                        return thisbox;
+                }
+
+                return thisbox;
+        }
 }
 
 static void bearerbox_to_smpp(void *arg)
 {
     Msg *msg, *mack;
-    Boxc *box = arg;
+    Boxc *box;
+    BBBoxc *bbbox;
     SMPP_PDU *pdu;
-    List *pdulist;
+    List *pdulist = NULL;
     int dreport, errcode;
-    Boxc *receiver_box;
+    Boxc *receiver_box = NULL;
     char id[UUID_STR_LEN + 1];
     Octstr *msgid;
 
-    while (smppbox_status == SMPP_RUNNING && box->alive) {
+    error(0, "opensmppbox: bearerbox_to_smpp: thread starts");
 
-	msg = read_from_box(box->bearerbox_connection, box);
+    if (!usesmppboxid) {
+        box = arg;
+    } else {
+        bbbox = arg;
+    }
+
+    while (smppbox_status == SMPP_RUNNING && (usesmppboxid ? bbbox->alive : box->alive)) {
+
+        if (usesmppboxid)
+            msg = read_from_box(bbbox->bearerbox_connection, bbbox->alive);
+        else
+            msg = read_from_box(box->bearerbox_connection, box->alive);
         if (msg == NULL) {
-	    if ((!box->alive) || conn_eof(box->bearerbox_connection)) {
             	/* tell opensmppbox to die */
 	    	/* the client closes the connection, after that die in receiver */
+	    if (usesmppboxid)  
+                if (conn_eof(bbbox->bearerbox_connection))
+            	    bbbox->alive = 0;
+	    else 
+                if (conn_eof(box->bearerbox_connection))
 	    	box->alive = 0;
-	    }
 	    continue;
         }
+        debug("", 0, "we have msg from bearerbox");
+        msg_dump(msg, 0);
 	if (msg_type(msg) == admin) {
 	    if (msg->admin.command == cmd_shutdown) {
 		info(0, "Bearerbox told us to die");
+                if (usesmppboxid)
+		    bbbox->alive = 0;
+                else
 		box->alive = 0;
 	    } else if (msg->admin.command == cmd_restart) {
 		info(0, "Bearerbox told us to restart");
 		restart = 1;
+                if (usesmppboxid)
+		    bbbox->alive = 0;
+                else
 		box->alive = 0;
 	    }
 	}
@@ -1901,7 +2251,11 @@
 	if (msg_type(msg) == ack) {
 	    uuid_unparse(msg->ack.id, id);
 	    msgid = octstr_create(id);
+            if (!usesmppboxid)
 	    pdu = dict_get(box->msg_acks, msgid);
+            else
+                pdu = dict_get(bbbox->msg_acks, msgid);
+          
 	    errcode = SMPP_ESME_RMSGQFUL; /* in case we get ack_failed_tmp */
 	    if (pdu) {
 		switch (msg->ack.nack) {
@@ -1933,26 +2287,38 @@
 			debug("opensmppbox", 0, "Unknown ack.nack type: %ld.", msg->ack.nack);
 			break;
 		}
+                if (!usesmppboxid) {
 		send_pdu(box->smpp_connection, box->boxc_id, pdu);
 		dict_put(box->msg_acks, msgid, NULL); /* also destroys item */
+                } else {
+                    Boxc *receiver_box = find_receiver_box_by_msg_id(msgid, msg);
+                    if (receiver_box) {
+                        send_pdu(receiver_box->smpp_connection, receiver_box->boxc_id, pdu);
+                        dict_put(receiver_box->msg_acks, msgid, NULL);
+                    } 
+                    //dlr_remove_by_id(msg);
+                }
 	    }
 	    else {
 		debug("opensmppbox", 0, "Ack to unknown message: %s.", id);
 	    }
 	    octstr_destroy(msgid);
 	}
-        if (!box->alive) {
+        if (usesmppboxid ? !bbbox->alive : !box->alive) {
 		msg_destroy(msg);
 		break;
 	}
 	if (msg_type(msg) == sms) {
 		info(0, "We received an SMS message.");
-		if (msg->sms.sms_type == report_mo)
+            uuid_unparse(msg->sms.id, id);
+            msgid = octstr_create(id);
+	    if (msg->sms.sms_type == report_mo) {
 			dreport = 1;
-		else
+	    } else {
 			dreport = 0;
+            }
 		/* Recode to iso-8859-1 the MO message if possible */
-		if (box->mo_recode && msg->sms.coding == DC_UCS2) {
+	    if ((usesmppboxid ? bbbox->mo_recode : box->mo_recode) && msg->sms.coding == DC_UCS2) {
 			int converted = 0;
 			Octstr *text;
 
@@ -2006,7 +2372,10 @@
 			mack->ack.nack = ack_failed;
 			mack->ack.time = msg->sms.time;
 			uuid_copy(mack->ack.id, msg->sms.id);
-			send_msg(box->bearerbox_connection, box, mack);
+               if (usesmppboxid) {
+                   send_msg(bbbox->bearerbox_connection, mack);
+               } else
+                   send_msg(box->bearerbox_connection, mack);
 
 			msg_destroy(msg);
 			continue;
@@ -2017,8 +2386,12 @@
 		mack->ack.time = msg->sms.time;
 		uuid_copy(mack->ack.id, msg->sms.id);
 
-		msgid = NULL;
-		receiver_box = find_receiver_box(box);
+	   if (usesmppboxid) {
+               restore_box_id(msg, msgid);
+               receiver_box = find_receiver_box(NULL, msg);
+           } else
+               receiver_box = find_receiver_box(box, msg);
+           if (receiver_box != NULL)
 		pdulist = msg_to_pdu(receiver_box, msg);
 		if (pdulist != NULL) {
 			while ((pdu = gwlist_extract_first(pdulist)) != NULL) {
@@ -2027,7 +2400,7 @@
 					msgid = octstr_format("%ld", pdu->u.deliver_sm.sequence_number);
 					dict_put(receiver_box->deliver_acks, msgid, mack);
 				}
-				send_pdu(receiver_box->smpp_connection, box->boxc_id, pdu);
+		   send_pdu(receiver_box->smpp_connection, receiver_box->boxc_id, pdu);
 				smpp_pdu_destroy(pdu);
 			}
 			if (msgid)
@@ -2038,11 +2411,17 @@
 			/* Send NACK to bearerbox, otherwise message remains in store file. */
 			warning(0, "msg_to_pdu failed, sending negative ack");
 			mack->ack.nack = ack_failed;
-			send_msg(box->bearerbox_connection, box, mack);
+              if (usesmppboxid) {
+	          send_msg(bbbox->bearerbox_connection, mack);
+              } else
+                  send_msg(box->bearerbox_connection, mack);
 		}		
 	}
         msg_destroy(msg);
     }
+
+    error(0, "opensmppbox: bearerbox_to_smpp: thread terminates");
+    smppbox_status = SMPP_SHUTDOWN;
 }
 
 static void run_smppbox(void *arg)
@@ -2057,7 +2436,8 @@
 	    panic(0, "Socket accept failed");
 	    return;
     }
-    newconn->boxc_id = octstr_duplicate(smppbox_id);
+    
+    if (!usesmppboxid) {
     newconn->bearerbox_connection = connect_to_bearerbox_real(bearerbox_host, bearerbox_port, bearerbox_port_ssl, NULL /* bb_our_host */);
 	/* XXX add our_host if required */
     if (newconn->bearerbox_connection == NULL) {
@@ -2065,6 +2445,7 @@
 	    boxc_destroy(newconn);
 	    return;
     }
+    }
 
     gwlist_append(all_boxes, newconn);
 
@@ -2083,6 +2464,7 @@
     		boxc_destroy(newconn);
 	    return;
     }
+    if (!usesmppboxid)
     bearerbox_to_smpp(newconn);
     gwthread_join(sender);
     gwlist_delete_equal(all_boxes, newconn);
@@ -2163,7 +2545,7 @@
 
     switch (signum) {
         case SIGINT:
-
+        case SIGTERM:
        	    if (smppbox_status == SMPP_RUNNING) {
                 error(0, "SIGINT received, aborting program...");
 		smppbox_status = SMPP_SHUTDOWN;
@@ -2198,6 +2580,7 @@
     sigaction(SIGQUIT, &act, NULL);
     sigaction(SIGHUP, &act, NULL);
     sigaction(SIGPIPE, &act, NULL);
+    sigaction(SIGTERM, &act, NULL);
 }
 
 
@@ -2210,6 +2593,109 @@
 {
 }
 
+/*
+ * Populates the corresponding client_by_foobar dictionary hash tables
+ */
+static void init_client_routes(Cfg *cfg)
+{
+    CfgGroup *grp;
+    List *list, *items;
+    Octstr *client_id, *smsc_ids, *shortcuts;
+    int i, j;
+
+    client_id = smsc_ids = shortcuts = NULL;
+
+    list = cfg_get_multi_group(cfg, octstr_imm("client-route"));
+
+    /* loop multi-group "client-route" */
+    while (list && (grp = gwlist_extract_first(list)) != NULL) {
+        if ((client_id = cfg_get(grp, octstr_imm("client-id"))) == NULL) {
+            grp_dump(grp);
+            panic(0,"'client-route' group without valid 'client-id' directive!");
+        }
+
+        /*
+         * If smsc-id is given, then any message comming from the specified
+         * smsc-id in the list will be routed to this client.
+         * If shortcode is given, then any message with receiver number
+         * matching those will be routed to this client.
+         * If both are given, then only receiver within shortcode originating
+         * from smsc-id list will be routed to this cleint. So if both are 
+         * present then this is a logical AND operation.
+         */
+        smsc_ids = cfg_get(grp, octstr_imm("smsc-id"));
+        shortcuts = cfg_get(grp, octstr_imm("shortcode"));
+
+        /* consider now the 3 possibilities: */
+        if (smsc_ids && !shortcuts) {
+            /* smsc-id only, so all MO traffic */
+            items = octstr_split(smsc_ids, octstr_imm(";"));
+            for (i = 0; i < gwlist_len(items); i++) {
+                Octstr *item = gwlist_get(items, i);
+                octstr_strip_blanks(item);
+
+                debug("opensmppbox",0,"Adding client routing to id <%s> for smsc id <%s>",
+                      octstr_get_cstr(client_id), octstr_get_cstr(item));
+
+                if (!dict_put_once(client_by_smsc, item, octstr_duplicate(client_id)))
+                    panic(0, "Routing for smsc-id <%s> already exists!",
+                          octstr_get_cstr(item));
+            }
+            gwlist_destroy(items, octstr_destroy_item);
+            octstr_destroy(smsc_ids);
+        }
+        else if (!smsc_ids && shortcuts) {
+            /* shortcode only, so these MOs from all smscs */
+            items = octstr_split(shortcuts, octstr_imm(";"));
+            for (i = 0; i < gwlist_len(items); i++) {
+                Octstr *item = gwlist_get(items, i);
+                octstr_strip_blanks(item);
+
+                debug("opensmppbox",0,"Adding client routing to id <%s> for receiver no <%s>",
+                      octstr_get_cstr(client_id), octstr_get_cstr(item));
+
+                if (!dict_put_once(client_by_receiver, item, octstr_duplicate(client_id)))
+                    panic(0, "Routing for receiver no <%s> already exists!",
+                          octstr_get_cstr(item));
+            }
+            gwlist_destroy(items, octstr_destroy_item);
+            octstr_destroy(shortcuts);
+        }
+        else if (smsc_ids && shortcuts) {
+            /* both, so only specified MOs from specified smscs */
+            items = octstr_split(shortcuts, octstr_imm(";"));
+            for (i = 0; i < gwlist_len(items); i++) {
+                List *subitems;
+                Octstr *item = gwlist_get(items, i);
+                octstr_strip_blanks(item);
+                subitems = octstr_split(smsc_ids, octstr_imm(";"));
+                for (j = 0; j < gwlist_len(subitems); j++) {
+                    Octstr *subitem = gwlist_get(subitems, j);
+                    octstr_strip_blanks(subitem);
+
+                    debug("opensmppbox",0,"Adding client routing to id <%s> "
+                          "for receiver no <%s> and smsc id <%s>",
+                          octstr_get_cstr(client_id), octstr_get_cstr(item),
+                          octstr_get_cstr(subitem));
+
+                    /* construct the dict key '<shortcode>:<smsc-id>' */
+                    octstr_insert(subitem, item, 0);
+                    octstr_insert_char(subitem, octstr_len(item), ':');
+                    if (!dict_put_once(client_by_smsc_receiver, subitem, octstr_duplicate(client_id)))
+                        panic(0, "Routing for receiver:smsc <%s> already exists!",
+                              octstr_get_cstr(subitem));
+                }
+                gwlist_destroy(subitems, octstr_destroy_item);
+            }
+            gwlist_destroy(items, octstr_destroy_item);
+            octstr_destroy(shortcuts);
+        }
+        octstr_destroy(client_id);
+    }
+
+    gwlist_destroy(list, NULL);
+}
+
 static void init_smppbox(Cfg *cfg)
 {
 	CfgGroup *grp;
@@ -2225,6 +2711,7 @@
 	lvl = 0;
 	systemidisboxcid = 0; /* default backward compatible */
 	enablepam = 0; /* also default false */
+        usesmppboxid = 0; /* Ditto */
 
 	/* init dlr storage */
 	dlr_init(cfg);
@@ -2298,6 +2784,7 @@
 
 	cfg_get_bool(&systemidisboxcid, grp, octstr_imm("use-systemid-as-smsboxid"));
 	cfg_get_bool(&enablepam, grp, octstr_imm("enable-pam"));
+	cfg_get_bool(&usesmppboxid, grp, octstr_imm("use-smppboxid"));
 	pamacl = cfg_get(grp, octstr_imm("pam-acl"));
 	if (NULL == pamacl) {
 		pamacl = octstr_create("kannel");
@@ -2318,6 +2805,12 @@
         boxid = counter_create();
 	gw_smpp_enter(cfg);
 
+        /* the client routing specific inits */
+        client_by_smsc = dict_create(30, (void(*)(void *)) octstr_destroy);
+        client_by_receiver = dict_create(50, (void(*)(void *)) octstr_destroy);
+        client_by_smsc_receiver = dict_create(50, (void(*)(void *)) octstr_destroy);
+        init_client_routes(cfg);
+
 	smppbox_status = SMPP_RUNNING;
 }
 
@@ -2414,15 +2907,30 @@
 	octstr_destroy(version);
 
 	init_smppbox(cfg);
-
+        if (usesmppboxid) {
+                bbbox = bbboxc_create();
+	        identify_to_bearerbox(bbbox->bearerbox_connection, smppbox_id);
+                gwthread_create(bearerbox_to_smpp, bbbox);
+        }
 	smppboxc_run((void *)smppbox_port);
 
 	/* shutdown dlr storage */
 	heartbeat_stop(ALL_HEARTBEATS);
+        if (usesmppboxid) {
+                gwthread_join_every(bearerbox_to_smpp);
+                bbboxc_destroy(bbbox);
+        }
 	dlr_shutdown();
 	counter_destroy(catenated_sms_counter);
 	counter_destroy(boxid);
 
+        dict_destroy(client_by_smsc);
+        client_by_smsc = NULL;
+        dict_destroy(client_by_receiver);
+        client_by_receiver = NULL;
+        dict_destroy(client_by_smsc_receiver);
+        client_by_smsc_receiver = NULL;
+
 	if (restart_smppbox) {
 		gwthread_sleep(1.0);
 	}
Only in gw: opensmppbox.c.save
Only in gw: opensmppbox.c.save.1
Only in gw: opensmppbox.c.save.10
Only in gw: opensmppbox.c.save.11
Only in gw: opensmppbox.c.save.12
Only in gw: opensmppbox.c.save.2
Only in gw: opensmppbox.c.save.3
Only in gw: opensmppbox.c.save.4
Only in gw: opensmppbox.c.save.5
Only in gw: opensmppbox.c.save.6
Only in gw: opensmppbox.c.save.7
Only in gw: opensmppbox.c.save.8
Only in gw: opensmppbox.c.save.9
Only in gw: opensmppbox.o
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.