SMPP: Implement support for MO SMS

Each ESME can have a number of prefix-matching routes, or it can
be a 'default route' to whcih all otherwise unknown SMS destinations
are routed.
diff --git a/openbsc/src/libmsc/gsm_04_11.c b/openbsc/src/libmsc/gsm_04_11.c
index 7ef92a8..5beae53 100644
--- a/openbsc/src/libmsc/gsm_04_11.c
+++ b/openbsc/src/libmsc/gsm_04_11.c
@@ -56,6 +56,11 @@
 #include <openbsc/chan_alloc.h>
 #include <openbsc/bsc_api.h>
 
+#ifdef BUILD_SMPP
+#include "smpp_smsc.h"
+extern int smpp_try_deliver(struct gsm_sms *sms);
+#endif
+
 #define GSM411_ALLOC_SIZE	1024
 #define GSM411_ALLOC_HEADROOM	128
 
@@ -377,13 +382,21 @@
 		LOGP(DLSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n");
 		rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
 		goto out;
+	} else if (da_len_bytes < 4) {
+		LOGP(DLSMS, LOGL_ERROR, "Destination Address < 4 bytes ?!?\n");
+		rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
+		goto out;
 	}
 	memset(address_lv, 0, sizeof(address_lv));
 	memcpy(address_lv, smsp, da_len_bytes);
 	/* mangle first byte to reflect length in bytes, not digits */
 	address_lv[0] = da_len_bytes - 1;
+
+	gsms->destination.ton = (address_lv[1] >> 4) & 7;
+	gsms->destination.npi = address_lv[1] & 0xF;
 	/* convert to real number */
-	gsm48_decode_bcd_number(gsms->dest_addr, sizeof(gsms->dest_addr), address_lv, 1);
+	gsm48_decode_bcd_number(gsms->destination.addr,
+				sizeof(gsms->destination.addr), address_lv, 1);
 	smsp += da_len_bytes;
 
 	gsms->protocol_id = *smsp++;
@@ -449,8 +462,19 @@
 	/* determine gsms->receiver based on dialled number */
 	gsms->receiver = subscr_get_by_extension(conn->bts->network, gsms->dest_addr);
 	if (!gsms->receiver) {
+#ifdef BUILD_SMPP
+		rc = smpp_try_deliver(gsms);
+		if (rc == 1) {
+			rc = 1; /* cause 1: unknown subscriber */
+			osmo_counter_inc(conn->bts->network->stats.sms.no_receiver);
+		} else if (rc < 0) {
+			rc = 21; /* cause 21: short message transfer rejected */
+			/* FIXME: handle the error somehow? */
+		}
+#else
 		rc = 1; /* cause 1: unknown subscriber */
 		osmo_counter_inc(conn->bts->network->stats.sms.no_receiver);
+#endif
 		goto out;
 	}
 
diff --git a/openbsc/src/libmsc/smpp_openbsc.c b/openbsc/src/libmsc/smpp_openbsc.c
index d8dde29..b3b8d36 100644
--- a/openbsc/src/libmsc/smpp_openbsc.c
+++ b/openbsc/src/libmsc/smpp_openbsc.c
@@ -66,7 +66,7 @@
 }
 
 /*! \brief find a TLV with given tag in list of libsmpp34 TLVs */
-struct tlv_t *find_tlv(struct tlv_t *head, uint16_t tag)
+static struct tlv_t *find_tlv(struct tlv_t *head, uint16_t tag)
 {
 	struct tlv_t *t;
 
@@ -217,7 +217,6 @@
 static int smpp_sms_cb(unsigned int subsys, unsigned int signal,
 			void *handler_data, void *signal_data)
 {
-	struct gsm_network *network = handler_data;
 	struct sms_signal_data *sig_sms = signal_data;
 	struct gsm_sms *sms = sig_sms->sms;
 	int rc = 0;
@@ -288,8 +287,98 @@
 	return 0;
 }
 
+static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms)
+{
+	struct deliver_sm_t deliver;
+	uint8_t dcs;
+
+	memset(&deliver, 0, sizeof(deliver));
+	deliver.command_length	= 0;
+	deliver.command_id	= DELIVER_SM;
+	deliver.command_status	= ESME_ROK;
+
+	strcpy((char *)deliver.service_type, "CMT");
+	if (esme->acl && esme->acl->deliver_src_imsi) {
+		deliver.source_addr_ton	= TON_Subscriber_Number;
+		deliver.source_addr_npi = NPI_Land_Mobile_E212;
+		snprintf((char *)deliver.source_addr,
+			sizeof(deliver.source_addr), "%s",
+			sms->sender->imsi);
+	} else {
+		deliver.source_addr_ton = TON_Network_Specific;
+		deliver.source_addr_npi = NPI_ISDN_E163_E164;
+		snprintf((char *)deliver.source_addr,
+			 sizeof(deliver.source_addr), "%s",
+			 sms->sender->extension);
+	}
+
+	deliver.dest_addr_ton	= sms->destination.ton;
+	deliver.dest_addr_npi	= sms->destination.npi;
+	memcpy(deliver.destination_addr, sms->destination.addr,
+		sizeof(deliver.destination_addr));
+
+	deliver.esm_class	= 1;	/* datagram mode */
+	if (sms->ud_hdr_ind)
+		deliver.esm_class |= 0x40;
+	if (sms->reply_path_req)
+		deliver.esm_class |= 0x80;
+
+	deliver.protocol_id 	= sms->protocol_id;
+	deliver.priority_flag	= 0;
+	deliver.registered_delivery = 0;
+
+	dcs = sms->data_coding_scheme;
+	if (dcs == GSM338_DCS_1111_7BIT ||
+	   ((dcs & 0xE0000000) == 0 && (dcs & 0xC) == 0)) {
+		uint8_t *src = sms->user_data;
+		uint8_t *dst = deliver.short_message;
+		uint8_t src_byte_len = sms->user_data_len;
+
+		/* SMPP has this strange notion of putting 7bit SMS in
+		 * an octet-aligned mode */
+		deliver.data_coding = 0x01;
+		if (sms->ud_hdr_ind) {
+			uint8_t udh_len = sms->user_data[0];
+			src += udh_len + 1;
+			dst += udh_len + 1;
+			src_byte_len -= udh_len + 1;
+			memcpy(dst, sms->user_data, udh_len + 1);
+			deliver.sm_length = udh_len + 1;
+		}
+		deliver.sm_length += gsm_7bit_decode((char *)dst, src, src_byte_len);
+	} else if (dcs == GSM338_DCS_1111_8BIT_DATA ||
+		   ((dcs & 0xE0000000) == 0 && (dcs & 0xC) == 4)) {
+		deliver.data_coding = 0x02;
+		deliver.sm_length = sms->user_data_len;
+		memcpy(deliver.short_message, sms->user_data, deliver.sm_length);
+	} else if ((dcs & 0xE0000000) == 0 && (dcs & 0xC) == 8) {
+		deliver.data_coding = 0x08;	/* UCS-2 */
+		deliver.sm_length = sms->user_data_len;
+		memcpy(deliver.short_message, sms->user_data, deliver.sm_length);
+	}
+
+	return smpp_tx_deliver(esme, &deliver);
+}
+
 static struct smsc *g_smsc;
 
+int smpp_try_deliver(struct gsm_sms *sms)
+{
+	struct osmo_esme *esme;
+	struct osmo_smpp_addr dst;
+
+	memset(&dst, 0, sizeof(dst));
+	dst.ton = sms->destination.ton;
+	dst.npi = sms->destination.npi;
+	memcpy(dst.addr, sms->destination.addr, sizeof(dst.addr));
+
+	esme = smpp_route(g_smsc, &dst);
+	if (!esme)
+		return 1; /* unknown subscriber */
+
+	return deliver_to_esme(esme, sms);
+}
+
 struct smsc *smsc_from_vty(struct vty *v)
 {
 	/* FIXME: this is ugly */
diff --git a/openbsc/src/libmsc/smpp_smsc.c b/openbsc/src/libmsc/smpp_smsc.c
index 493d079..f9e517b 100644
--- a/openbsc/src/libmsc/smpp_smsc.c
+++ b/openbsc/src/libmsc/smpp_smsc.c
@@ -51,6 +51,71 @@
 	ESME_BIND_TX = 0x02,
 };
 
+const struct value_string smpp_status_strs[] = {
+	{ ESME_ROK,		"No Error" },
+	{ ESME_RINVMSGLEN,	"Message Length is invalid" },
+	{ ESME_RINVCMDLEN,	"Command Length is invalid" },
+	{ ESME_RINVCMDID,	"Invalid Command ID" },
+	{ ESME_RINVBNDSTS,	"Incorrect BIND Status for given command" },
+	{ ESME_RALYBND,		"ESME Already in Bound State" },
+	{ ESME_RINVPRTFLG,	"Invalid Priority Flag" },
+	{ ESME_RINVREGDLVFLG,	"Invalid Registered Delivery Flag" },
+	{ ESME_RSYSERR,		"System Error" },
+	{ ESME_RINVSRCADR,	"Invalid Source Address" },
+	{ ESME_RINVDSTADR,	"Invalid Destination Address" },
+	{ ESME_RINVMSGID,	"Message ID is invalid" },
+	{ ESME_RBINDFAIL,	"Bind failed" },
+	{ ESME_RINVPASWD,	"Invalid Password" },
+	{ ESME_RINVSYSID,	"Invalid System ID" },
+	{ ESME_RCANCELFAIL,	"Cancel SM Failed" },
+	{ ESME_RREPLACEFAIL,	"Replace SM Failed" },
+	{ ESME_RMSGQFUL,	"Message Queue Full" },
+	{ ESME_RINVSERTYP,	"Invalid Service Type" },
+	{ ESME_RINVNUMDESTS,	"Invalid number of destinations" },
+	{ ESME_RINVDLNAME,	"Invalid Distribution List name" },
+	{ ESME_RINVDESTFLAG,	"Destination flag is invalid" },
+	{ ESME_RINVSUBREP,	"Invalid submit with replace request" },
+	{ ESME_RINVESMCLASS,	"Invalid esm_class field data" },
+	{ ESME_RCNTSUBDL,	"Cannot Submit to Distribution List" },
+	{ ESME_RSUBMITFAIL,	"submit_sm or submit_multi failed" },
+	{ ESME_RINVSRCTON,	"Invalid Source address TON" },
+	{ ESME_RINVSRCNPI,	"Invalid Sourec address NPI" },
+	{ ESME_RINVDSTTON,	"Invalid Destination address TON" },
+	{ ESME_RINVDSTNPI,	"Invalid Desetination address NPI" },
+	{ ESME_RINVSYSTYP,	"Invalid system_type field" },
+	{ ESME_RINVREPFLAG,	"Invalid replace_if_present field" },
+	{ ESME_RINVNUMMSGS,	"Invalid number of messages" },
+	{ ESME_RTHROTTLED,	"Throttling error (ESME has exceeded message limits)" },
+	{ ESME_RINVSCHED,	"Invalid Scheduled Delivery Time" },
+	{ ESME_RINVEXPIRY,	"Invalid message validity period (Expiry time)" },
+	{ ESME_RINVDFTMSGID,	"Predefined Message Invalid or Not Found" },
+	{ ESME_RX_T_APPN,	"ESME Receiver Temporary App Error Code" },
+	{ ESME_RX_P_APPN,	"ESME Receiver Permanent App Error Code" },
+	{ ESME_RX_R_APPN,	"ESME Receiver Reject Message Error Code" },
+	{ ESME_RQUERYFAIL,	"query_sm request failed" },
+	{ ESME_RINVOPTPARSTREAM,"Error in the optional part of the PDU Body" },
+	{ ESME_ROPTPARNOTALLWD,	"Optional Parameter not allowed" },
+	{ ESME_RINVPARLEN,	"Invalid Parameter Length" },
+	{ ESME_RMISSINGOPTPARAM,"Expected Optional Parameter missing" },
+	{ ESME_RINVOPTPARAMVAL,	"Invalid Optional Parameter Value" },
+	{ ESME_RDELIVERYFAILURE,"Delivery Failure (used for data_sm_resp)" },
+	{ ESME_RUNKNOWNERR,	"Unknown Error" },
+	{ 0, NULL }
+};
+
+/*! \brief compare if two SMPP addresses are equal */
+int smpp_addr_eq(const struct osmo_smpp_addr *a,
+		 const struct osmo_smpp_addr *b)
+{
+	if (a->ton == b->ton &&
+	    a->npi == b->npi &&
+	    !strcmp(a->addr, b->addr))
+		return 1;
+
+	return 0;
+}
+
+
 struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc,
 					    const char *sys_id)
 {
@@ -80,28 +145,91 @@
 
 	acl->smsc = smsc;
 	strcpy(acl->system_id, sys_id);
+	INIT_LLIST_HEAD(&acl->route_list);
 
-	llist_add(&acl->list, &smsc->acl_list);
+	llist_add_tail(&acl->list, &smsc->acl_list);
 
 	return acl;
 }
 
 void smpp_acl_delete(struct osmo_smpp_acl *acl)
 {
-	struct osmo_esme *esme, *e2;
-	struct smsc *smsc = acl->smsc;
+	struct osmo_smpp_route *r, *r2;
 
 	llist_del(&acl->list);
 
-	llist_for_each_entry_safe(esme, e2, &smsc->esme_list, list) {
-		if (!strcmp(acl->system_id, esme->system_id)) {
-			/* FIXME: drop connection */
-		}
+	/* kill any active ESMEs */
+	if (acl->esme) {
+		struct osmo_esme *esme = acl->esme;
+		osmo_fd_unregister(&esme->wqueue.bfd);
+		close(esme->wqueue.bfd.fd);
+		esme->wqueue.bfd.fd = -1;
+		esme->acl = NULL;
+		smpp_esme_put(esme);
+	}
+
+	/* delete all routes for this ACL */
+	llist_for_each_entry_safe(r, r2, &acl->route_list, list) {
+		llist_del(&r->list);
+		llist_del(&r->global_list);
+		talloc_free(r);
 	}
 
 	talloc_free(acl);
 }
 
+static struct osmo_smpp_route *route_alloc(struct osmo_smpp_acl *acl)
+{
+	struct osmo_smpp_route *r;
+
+	r = talloc_zero(acl, struct osmo_smpp_route);
+	if (!r)
+		return NULL;
+
+	llist_add_tail(&r->list, &acl->route_list);
+	llist_add_tail(&r->global_list, &acl->smsc->route_list);
+
+	return r;
+}
+
+int smpp_route_pfx_add(struct osmo_smpp_acl *acl,
+			const struct osmo_smpp_addr *pfx)
+{
+	struct osmo_smpp_route *r;
+
+	llist_for_each_entry(r, &acl->route_list, list) {
+		if (r->type == SMPP_ROUTE_PREFIX &&
+		    smpp_addr_eq(&r->u.prefix, pfx))
+			return -EEXIST;
+	}
+
+	r = route_alloc(acl);
+	if (!r)
+		return -ENOMEM;
+	r->type = SMPP_ROUTE_PREFIX;
+	r->acl = acl;
+	memcpy(&r->u.prefix, pfx, sizeof(r->u.prefix));
+
+	return 0;
+}
+
+int smpp_route_pfx_del(struct osmo_smpp_acl *acl,
+		       const struct osmo_smpp_addr *pfx)
+{
+	struct osmo_smpp_route *r, *r2;
+
+	llist_for_each_entry_safe(r, r2, &acl->route_list, list) {
+		if (r->type == SMPP_ROUTE_PREFIX &&
+		    smpp_addr_eq(&r->u.prefix, pfx)) {
+			llist_del(&r->list);
+			talloc_free(r);
+			return 0;
+		}
+	}
+
+	return -ENODEV;
+}
+
 
 /*! \brief increaes the use/reference count */
 void smpp_esme_get(struct osmo_esme *esme)
@@ -120,6 +248,15 @@
 	talloc_free(esme);
 }
 
+static uint32_t esme_inc_seq_nr(struct osmo_esme *esme)
+{
+	esme->own_seq_nr++;
+	if (esme->own_seq_nr > 0x7fffffff)
+		esme->own_seq_nr = 1;
+
+	return esme->own_seq_nr;
+}
+
 /*! \brief decrease the use/reference count, free if it is 0 */
 void smpp_esme_put(struct osmo_esme *esme)
 {
@@ -140,6 +277,62 @@
 	return NULL;
 }
 
+/*! \brief try to find a SMPP route (ESME) for given destination */
+struct osmo_esme *
+smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest)
+{
+	struct osmo_smpp_route *r;
+	struct osmo_smpp_acl *acl = NULL;
+
+	DEBUGP(DSMPP, "Looking up route for (%u/%u/%s)\n",
+		dest->ton, dest->npi, dest->addr);
+
+	/* search for a specific route */
+	llist_for_each_entry(r, &smsc->route_list, global_list) {
+		switch (r->type) {
+		case SMPP_ROUTE_PREFIX:
+			DEBUGP(DSMPP, "Checking prefix route (%u/%u/%s)->%s\n",
+				r->u.prefix.ton, r->u.prefix.npi, r->u.prefix.addr,
+				r->acl->system_id);
+			if (r->u.prefix.ton == dest->ton &&
+			    r->u.prefix.npi == dest->npi &&
+			    !strncmp(r->u.prefix.addr, dest->addr,
+				     strlen(r->u.prefix.addr))) {
+				DEBUGP(DSMPP, "Found prefix route ACL\n");
+				acl = r->acl;
+			}
+			break;
+		default:
+			break;
+		}
+
+		if (acl)
+			break;
+	}
+
+	if (!acl) {
+		/* check for default route */
+		if (smsc->def_route) {
+			DEBUGP(DSMPP, "Using existing default route\n");
+			acl = smsc->def_route;
+		}
+	}
+
+	if (acl && acl->esme) {
+		struct osmo_esme *esme;
+		DEBUGP(DSMPP, "ACL even has ESME, we can route to it!\n");
+		esme = acl->esme;
+		if (esme->bind_flags & ESME_BIND_RX)
+			return esme;
+		else
+			LOGP(DSMPP, LOGL_NOTICE, "[%s] is matching route, "
+			     "but not bound for Rx, discarding MO SMS\n",
+				     esme->system_id);
+	}
+
+	return NULL;
+}
+
 
 /*! \brief initialize the libsmpp34 data structure for a response */
 #define INIT_RESP(type, resp, req) 		{ \
@@ -223,12 +416,49 @@
 	return 0;
 }
 
+static int _process_bind(struct osmo_esme *esme, uint8_t if_version,
+			 uint32_t bind_flags, const char *sys_id,
+			 const char *passwd)
+{
+	struct osmo_smpp_acl *acl;
+
+	if (if_version != SMPP_VERSION)
+		return ESME_RSYSERR;
+
+	if (esme->bind_flags)
+		return ESME_RALYBND;
+
+	esme->smpp_version = if_version;
+	snprintf(esme->system_id, sizeof(esme->system_id), "%s", sys_id);
+
+	acl = smpp_acl_by_system_id(esme->smsc, esme->system_id);
+	if (!esme->smsc->accept_all) {
+		if (!acl) {
+			/* This system is unknown */
+			return ESME_RINVSYSID;
+		} else {
+			if (strlen(acl->passwd) &&
+			    strcmp(acl->passwd, passwd)) {
+				return ESME_RINVPASWD;
+			}
+		}
+	}
+	if (acl) {
+		esme->acl = acl;
+		acl->esme = esme;
+	}
+
+	esme->bind_flags = bind_flags;
+
+	return ESME_ROK;
+}
+
+
 /*! \brief handle an incoming SMPP BIND RECEIVER */
 static int smpp_handle_bind_rx(struct osmo_esme *esme, struct msgb *msg)
 {
 	struct bind_receiver_t bind;
 	struct bind_receiver_resp_t bind_r;
-	struct osmo_smpp_acl *acl;
 	int rc;
 
 	SMPP34_UNPACK(rc, BIND_RECEIVER, &bind, msgb_data(msg),
@@ -244,41 +474,11 @@
 	LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Rx from (Version %02x)\n",
 		bind.system_id, bind.interface_version);
 
-	if (bind.interface_version != SMPP_VERSION) {
-		bind_r.command_status = ESME_RSYSERR;
-		goto err;
-	}
+	rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX,
+			   (const char *)bind.system_id, (const char *)bind.password);
+	bind_r.command_status = rc;
 
-	if (esme->bind_flags) {
-		bind_r.command_status = ESME_RALYBND;
-		goto err;
-	}
-
-	esme->smpp_version = bind.interface_version;
-	snprintf(esme->system_id, sizeof(esme->system_id), "%s",
-		 bind.system_id);
-
-	acl = smpp_acl_by_system_id(esme->smsc, esme->system_id);
-	if (!esme->smsc->accept_all) {
-		if (!acl) {
-			/* This system is unknown */
-			bind_r.command_status = ESME_RINVSYSID;
-			goto err;
-		} else {
-			if (strlen(acl->passwd) &&
-			    strcmp(acl->passwd, (char *)bind.password)) {
-				bind_r.command_status = ESME_RINVPASWD;
-				goto err;
-			}
-			esme->acl = acl;
-			if (acl->default_route)
-				esme->smsc->def_route = esme;
-		}
-	}
-
-	esme->bind_flags = ESME_BIND_RX;
-err:
-	return 0;
+	return PACK_AND_SEND(esme, &bind_r);
 }
 
 /*! \brief handle an incoming SMPP BIND TRANSMITTER */
@@ -286,7 +486,6 @@
 {
 	struct bind_transmitter_t bind;
 	struct bind_transmitter_resp_t bind_r;
-	struct osmo_smpp_acl *acl;
 	struct tlv_t tlv;
 	int rc;
 
@@ -303,36 +502,9 @@
 	LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Tx (Version %02x)\n",
 		bind.system_id, bind.interface_version);
 
-	if (bind.interface_version != SMPP_VERSION) {
-		bind_r.command_status = ESME_RSYSERR;
-		goto err;
-	}
-
-	if (esme->bind_flags) {
-		bind_r.command_status = ESME_RALYBND;
-		goto err;
-	}
-
-	esme->smpp_version = bind.interface_version;
-	snprintf(esme->system_id, sizeof(esme->system_id), "%s", bind.system_id);
-
-	acl = smpp_acl_by_system_id(esme->smsc, esme->system_id);
-	if (!esme->smsc->accept_all) {
-		if (!acl) {
-			/* This system is unknown */
-			bind_r.command_status = ESME_RINVSYSID;
-			goto err;
-		} else {
-			if (strlen(acl->passwd) &&
-			    strcmp(acl->passwd, (char *)bind.password)) {
-				bind_r.command_status = ESME_RINVPASWD;
-				goto err;
-			}
-			esme->acl = acl;
-		}
-	}
-
-	esme->bind_flags = ESME_BIND_TX;
+	rc = _process_bind(esme, bind.interface_version, ESME_BIND_TX,
+			   (const char *)bind.system_id, (const char *)bind.password);
+	bind_r.command_status = rc;
 
 	/* build response */
 	snprintf((char *)bind_r.system_id, sizeof(bind_r.system_id), "%s",
@@ -344,7 +516,6 @@
 	tlv.value.val16 = esme->smpp_version;
 	build_tlv(&bind_r.tlv, &tlv);
 
-err:
 	return PACK_AND_SEND(esme, &bind_r);
 }
 
@@ -353,7 +524,6 @@
 {
 	struct bind_transceiver_t bind;
 	struct bind_transceiver_resp_t bind_r;
-	struct osmo_smpp_acl *acl;
 	int rc;
 
 	SMPP34_UNPACK(rc, BIND_TRANSCEIVER, &bind, msgb_data(msg),
@@ -369,41 +539,11 @@
 	LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Trx (Version %02x)\n",
 		bind.system_id, bind.interface_version);
 
-	if (bind.interface_version != SMPP_VERSION) {
-		bind_r.command_status = ESME_RSYSERR;
-		goto err;
-	}
+	rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX|ESME_BIND_TX,
+			   (const char *)bind.system_id, (const char *)bind.password);
+	bind_r.command_status = rc;
 
-	if (esme->bind_flags) {
-		bind_r.command_status = ESME_RALYBND;
-		goto err;
-	}
-
-	esme->smpp_version = bind.interface_version;
-	snprintf(esme->system_id, sizeof(esme->system_id), "%s", bind.system_id);
-
-	acl = smpp_acl_by_system_id(esme->smsc, esme->system_id);
-	if (!esme->smsc->accept_all) {
-		if (!acl) {
-			/* This system is unknown */
-			bind_r.command_status = ESME_RINVSYSID;
-			goto err;
-		} else {
-			if (strlen(acl->passwd) &&
-			    strcmp(acl->passwd, (char *)bind.password)) {
-				bind_r.command_status = ESME_RINVPASWD;
-				goto err;
-			}
-			esme->acl = acl;
-			if (acl->default_route)
-				esme->smsc->def_route = esme;
-		}
-	}
-
-	esme->bind_flags |= ESME_BIND_TX | ESME_BIND_RX;
-
-err:
-	return 0;
+	return PACK_AND_SEND(esme, &bind_r);
 }
 
 /*! \brief handle an incoming SMPP UNBIND */
@@ -431,8 +571,6 @@
 	}
 
 	esme->bind_flags = 0;
-	if (esme->smsc->def_route == esme)
-		esme->smsc->def_route = NULL;
 err:
 	return PACK_AND_SEND(esme, &unbind_r);
 }
@@ -495,7 +633,7 @@
 	alert.command_length	= 0;
 	alert.command_id	= ALERT_NOTIFICATION;
 	alert.command_status	= ESME_ROK;
-	alert.sequence_number	= esme->own_seq_nr++;
+	alert.sequence_number	= esme_inc_seq_nr(esme);
 	alert.source_addr_ton 	= ton;
 	alert.source_addr_npi	= npi;
 	snprintf((char *)alert.source_addr, sizeof(alert.source_addr), "%s", addr);
@@ -513,6 +651,36 @@
 	return PACK_AND_SEND(esme, &alert);
 }
 
+/* \brief send a DELIVER-SM message to given ESME */
+int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver)
+{
+	deliver->sequence_number = esme_inc_seq_nr(esme);
+
+	return PACK_AND_SEND(esme, deliver);
+}
+
+/*! \brief handle an incoming SMPP DELIVER-SM RESPONSE */
+static int smpp_handle_deliver_resp(struct osmo_esme *esme, struct msgb *msg)
+{
+	struct deliver_sm_resp_t deliver_r;
+	int rc;
+
+	memset(&deliver_r, 0, sizeof(deliver_r));
+	SMPP34_UNPACK(rc, DELIVER_SM_RESP, &deliver_r, msgb_data(msg),
+			   msgb_length(msg));
+	if (rc < 0) {
+		LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n",
+			esme->system_id, smpp34_strerror);
+		return rc;
+	}
+
+	LOGP(DSMPP, LOGL_INFO, "[%s] Rx DELIVER-SM RESP (%s)\n",
+		esme->system_id, get_value_string(smpp_status_strs,
+						  deliver_r.command_status));
+
+	return 0;
+}
+
 /*! \brief handle an incoming SMPP SUBMIT-SM */
 static int smpp_handle_submit(struct osmo_esme *esme, struct msgb *msg)
 {
@@ -580,6 +748,9 @@
 	case SUBMIT_SM:
 		rc = smpp_handle_submit(esme, msg);
 		break;
+	case DELIVER_SM_RESP:
+		rc = smpp_handle_deliver_resp(esme, msg);
+		break;
 	case DELIVER_SM:
 		break;
 	case DATA_SM:
@@ -698,6 +869,7 @@
 
 	smpp_esme_get(esme);
 	esme->own_seq_nr = rand();
+	esme_inc_seq_nr(esme);
 	esme->smsc = smsc;
 	osmo_wqueue_init(&esme->wqueue, 10);
 	esme->wqueue.bfd.fd = fd;
@@ -746,6 +918,7 @@
 	if (smsc->listen_ofd.fd <= 0) {
 		INIT_LLIST_HEAD(&smsc->esme_list);
 		INIT_LLIST_HEAD(&smsc->acl_list);
+		INIT_LLIST_HEAD(&smsc->route_list);
 		smsc->listen_ofd.data = smsc;
 		smsc->listen_ofd.cb = smsc_fd_cb;
 	} else {
diff --git a/openbsc/src/libmsc/smpp_smsc.h b/openbsc/src/libmsc/smpp_smsc.h
index 4b5bd85..be72a0c 100644
--- a/openbsc/src/libmsc/smpp_smsc.h
+++ b/openbsc/src/libmsc/smpp_smsc.h
@@ -22,6 +22,12 @@
 
 struct osmo_smpp_acl;
 
+struct osmo_smpp_addr {
+	uint8_t ton;
+	uint8_t npi;
+	char addr[21+1];
+};
+
 struct osmo_esme {
 	struct llist_head list;
 	struct smsc *smsc;
@@ -48,29 +54,54 @@
 struct osmo_smpp_acl {
 	struct llist_head list;
 	struct smsc *smsc;
+	struct osmo_esme *esme;
 	char *description;
 	char system_id[SMPP_SYS_ID_LEN+1];
 	char passwd[SMPP_PASSWD_LEN+1];
 	int default_route;
+	int deliver_src_imsi;
+	struct llist_head route_list;
 };
 
+enum osmo_smpp_rtype {
+	SMPP_ROUTE_NONE,
+	SMPP_ROUTE_PREFIX,
+};
+
+struct osmo_smpp_route {
+	struct llist_head list;	/*!< in acl.route_list */
+	struct llist_head global_list; /*!< in smsc->route_list */
+	struct osmo_smpp_acl *acl;
+	enum osmo_smpp_rtype type;
+	union {
+		struct osmo_smpp_addr prefix;
+	} u;
+};
+
+
 struct smsc {
 	struct osmo_fd listen_ofd;
 	struct llist_head esme_list;
 	struct llist_head acl_list;
+	struct llist_head route_list;
 	uint16_t listen_port;
 	char system_id[SMPP_SYS_ID_LEN+1];
 	int accept_all;
-	struct osmo_esme *def_route;
+	struct osmo_smpp_acl *def_route;
 	void *priv;
 };
 
+int smpp_addr_eq(const struct osmo_smpp_addr *a,
+		 const struct osmo_smpp_addr *b);
 
 int smpp_smsc_init(struct smsc *smsc, uint16_t port);
 
 void smpp_esme_get(struct osmo_esme *esme);
 void smpp_esme_put(struct osmo_esme *esme);
 
+struct osmo_esme *
+smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest);
+
 struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id);
 struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc,
 					    const char *sys_id);
@@ -82,7 +113,13 @@
 int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi,
 		  const char *addr, uint8_t avail_status);
 
+int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver);
+
 int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit,
 			struct submit_sm_resp_t *submit_r);
 
+int smpp_route_pfx_add(struct osmo_smpp_acl *acl,
+		       const struct osmo_smpp_addr *pfx);
+int smpp_route_pfx_del(struct osmo_smpp_acl *acl,
+		       const struct osmo_smpp_addr *pfx);
 #endif
diff --git a/openbsc/src/libmsc/smpp_vty.c b/openbsc/src/libmsc/smpp_vty.c
index ad496da..c6bb74d 100644
--- a/openbsc/src/libmsc/smpp_vty.c
+++ b/openbsc/src/libmsc/smpp_vty.c
@@ -18,7 +18,9 @@
  *
  */
 
+#include <ctype.h>
 #include <string.h>
+#include <errno.h>
 #include <netdb.h>
 #include <sys/socket.h>
 
@@ -195,18 +197,117 @@
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_esme_route, cfg_esme_route_cmd,
-	"route DESTINATION",
-	"Configure a route for MO-SMS to be sent to this ESME\n"
-	"Destination phone number")
+static int osmo_is_digits(const char *str)
+{
+	int i;
+	for (i = 0; i < strlen(str); i++) {
+		if (!isdigit(str[i]))
+			return 0;
+	}
+	return 1;
+}
+
+static const struct value_string route_errstr[] = {
+	{ -EEXIST,	"Route already exists" },
+	{ -ENODEV,	"Route does not exist" },
+	{ -ENOMEM,	"No memory" },
+	{ -EINVAL,	"Invalid" },
+	{ 0, NULL }
+};
+
+static const struct value_string smpp_ton_str_short[] = {
+	{ TON_Unknown,		"unknown" },
+	{ TON_International,	"international" },
+	{ TON_National,		"national" },
+	{ TON_Network_Specific,	"network" },
+	{ TON_Subscriber_Number,"subscriber" },
+	{ TON_Alphanumeric,	"alpha" },
+	{ TON_Abbreviated,	"abbrev" },
+	{ 0, NULL }
+};
+
+static const struct value_string smpp_npi_str_short[] = {
+	{ NPI_Unknown,		"unknown" },
+	{ NPI_ISDN_E163_E164,	"isdn" },
+	{ NPI_Data_X121,	"x121" },
+	{ NPI_Telex_F69,	"f69" },
+	{ NPI_Land_Mobile_E212,	"e212" },
+	{ NPI_National,		"national" },
+	{ NPI_Private,		"private" },
+	{ NPI_ERMES,		"ermes" },
+	{ NPI_Internet_IP,	"ip" },
+	{ NPI_WAP_Client_Id,	"wap" },
+	{ 0, NULL }
+};
+
+
+#define SMPP_ROUTE_STR "Configure a route for MO-SMS to be sent to this ESME\n"
+#define SMPP_ROUTE_P_STR "Prefix-match route\n"
+#define SMPP_PREFIX_STR "Destination number prefix\n"
+
+#define TON_CMD "(unknown|international|national|network|subscriber|alpha|abbrev)"
+#define NPI_CMD "(unknown|isdn|x121|f69|e212|national|private|ermes|ip|wap)"
+#define TON_STR "FIXME"
+#define NPI_STR "FIXME"
+
+DEFUN(cfg_esme_route_pfx, cfg_esme_route_pfx_cmd,
+	"route prefix " TON_CMD " " NPI_CMD " PREFIX",
+	SMPP_ROUTE_P_STR TON_STR NPI_STR SMPP_PREFIX_STR)
 {
 	struct osmo_smpp_acl *acl = vty->index;
+	struct osmo_smpp_addr pfx;
+	int rc;
 
-	/* FIXME: check if DESTINATION is all-digits */
+	/* check if DESTINATION is all-digits */
+	if (!osmo_is_digits(argv[2])) {
+		vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	pfx.ton = get_string_value(smpp_ton_str_short, argv[0]);
+	pfx.npi = get_string_value(smpp_npi_str_short, argv[1]);
+	snprintf(pfx.addr, sizeof(pfx.addr), "%s", argv[2]);
+
+	rc = smpp_route_pfx_add(acl, &pfx);
+	if (rc < 0) {
+		vty_out(vty, "%% error adding prefix route: %s%s",
+			get_value_string(route_errstr, rc), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
 
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_esme_no_route_pfx, cfg_esme_no_route_pfx_cmd,
+	"no route prefix " TON_CMD " " NPI_CMD " PREFIX",
+	NO_STR SMPP_ROUTE_P_STR TON_STR NPI_STR SMPP_PREFIX_STR)
+{
+	struct osmo_smpp_acl *acl = vty->index;
+	struct osmo_smpp_addr pfx;
+	int rc;
+
+	/* check if DESTINATION is all-digits */
+	if (!osmo_is_digits(argv[2])) {
+		vty_out(vty, "%% PREFIX has to be numeric%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	pfx.ton = get_string_value(smpp_ton_str_short, argv[0]);
+	pfx.npi = get_string_value(smpp_npi_str_short, argv[1]);
+	snprintf(pfx.addr, sizeof(pfx.addr), "%s", argv[2]);
+
+	rc = smpp_route_pfx_del(acl, &pfx);
+	if (rc < 0) {
+		vty_out(vty, "%% error removing prefix route: %s%s",
+			get_value_string(route_errstr, rc), VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	return CMD_SUCCESS;
+
+}
+
+
 DEFUN(cfg_esme_defaultroute, cfg_esme_defaultroute_cmd,
 	"default-route",
 	"Set this ESME as default-route for all SMS to unknown destinations")
@@ -215,6 +316,9 @@
 
 	acl->default_route = 1;
 
+	if (!acl->smsc->def_route)
+		acl->smsc->def_route = acl;
+
 	return CMD_SUCCESS;
 }
 
@@ -228,7 +332,7 @@
 
 	/* remove currently active default route, if it was created by
 	 * this ACL */
-	if (acl->smsc->def_route && acl->smsc->def_route->acl == acl)
+	if (acl->smsc->def_route && acl->smsc->def_route == acl)
 		acl->smsc->def_route = NULL;
 
 	return CMD_SUCCESS;
@@ -244,8 +348,11 @@
 		    host, sizeof(host), serv, sizeof(serv), NI_NUMERICSERV);
 
 	vty_out(vty, "ESME System ID: %s, Password: %s, SMPP Version %02x%s",
-		esme->system_id, esme->acl->passwd, esme->smpp_version, VTY_NEWLINE);
+		esme->system_id, esme->acl ? esme->acl->passwd : "",
+		esme->smpp_version, VTY_NEWLINE);
 	vty_out(vty, "  Connected from: %s:%s%s", host, serv, VTY_NEWLINE);
+	if (esme->smsc->def_route == esme->acl)
+		vty_out(vty, "  Is current default route%s", VTY_NEWLINE);
 }
 
 DEFUN(show_esme, show_esme_cmd,
@@ -261,13 +368,35 @@
 	return CMD_SUCCESS;
 }
 
+static void write_esme_route_single(struct vty *vty, struct osmo_smpp_route *r)
+{
+	switch (r->type) {
+	case SMPP_ROUTE_PREFIX:
+		vty_out(vty, "   route prefix %s ",
+			get_value_string(smpp_ton_str_short, r->u.prefix.ton));
+		vty_out(vty, "%s %s%s",
+			get_value_string(smpp_npi_str_short, r->u.prefix.npi),
+			r->u.prefix.addr, VTY_NEWLINE);
+		break;
+	case SMPP_ROUTE_NONE:
+		break;
+	}
+}
+
 static void config_write_esme_single(struct vty *vty, struct osmo_smpp_acl *acl)
 {
+	struct osmo_smpp_route *r;
+
 	vty_out(vty, " esme %s%s", acl->system_id, VTY_NEWLINE);
 	if (strlen(acl->passwd))
 		vty_out(vty, "  password %s%s", acl->passwd, VTY_NEWLINE);
 	if (acl->default_route)
 		vty_out(vty, "  default-route%s", VTY_NEWLINE);
+	if (acl->deliver_src_imsi)
+		vty_out(vty, "  deliver-src-imsi%s", VTY_NEWLINE);
+
+	llist_for_each_entry(r, &acl->route_list, list)
+		write_esme_route_single(vty, r);
 }
 
 static int config_write_esme(struct vty *v)
@@ -297,7 +426,8 @@
 	install_default(SMPP_ESME_NODE);
 	install_element(SMPP_ESME_NODE, &cfg_esme_passwd_cmd);
 	install_element(SMPP_ESME_NODE, &cfg_esme_no_passwd_cmd);
-	install_element(SMPP_ESME_NODE, &cfg_esme_route_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_route_pfx_cmd);
+	install_element(SMPP_ESME_NODE, &cfg_esme_no_route_pfx_cmd);
 	install_element(SMPP_ESME_NODE, &cfg_esme_defaultroute_cmd);
 	install_element(SMPP_ESME_NODE, &cfg_esme_no_defaultroute_cmd);
 	install_element(SMPP_ESME_NODE, &ournode_exit_cmd);