aboutsummaryrefslogtreecommitdiffstats
path: root/src/libsmpputil
diff options
context:
space:
mode:
Diffstat (limited to 'src/libsmpputil')
-rw-r--r--src/libsmpputil/Makefile.am25
-rw-r--r--src/libsmpputil/smpp_msc.c863
-rw-r--r--src/libsmpputil/smpp_smsc.c1026
-rw-r--r--src/libsmpputil/smpp_utils.c174
-rw-r--r--src/libsmpputil/smpp_vty.c629
5 files changed, 2717 insertions, 0 deletions
diff --git a/src/libsmpputil/Makefile.am b/src/libsmpputil/Makefile.am
new file mode 100644
index 000000000..b180ddf6f
--- /dev/null
+++ b/src/libsmpputil/Makefile.am
@@ -0,0 +1,25 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS= \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOSCCP_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(LIBOSMOMGCPCLIENT_CFLAGS) \
+ $(LIBOSMOGSUPCLIENT_CFLAGS) \
+ $(LIBSMPP34_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+noinst_HEADERS = \
+ $(NULL)
+
+noinst_LIBRARIES = libsmpputil.a
+
+libsmpputil_a_SOURCES = \
+ smpp_utils.c \
+ smpp_vty.c \
+ smpp_msc.c \
+ smpp_smsc.c \
+ $(NULL)
diff --git a/src/libsmpputil/smpp_msc.c b/src/libsmpputil/smpp_msc.c
new file mode 100644
index 000000000..090905a13
--- /dev/null
+++ b/src/libsmpputil/smpp_msc.c
@@ -0,0 +1,863 @@
+/* OpenBSC SMPP 3.4 interface, SMSC-side implementation */
+
+/* (C) 2012-2022 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <time.h>
+
+#include <smpp34.h>
+#include <smpp34_structs.h>
+#include <smpp34_params.h>
+#include <smpp34_heap.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+#include <osmocom/gsm/protocol/smpp34_osmocom.h>
+
+#include <osmocom/msc/gsm_subscriber.h>
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/db.h>
+#include <osmocom/msc/gsm_04_11.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/msc/signal.h>
+#include <osmocom/msc/transaction.h>
+#include <osmocom/msc/gsm_subscriber.h>
+#include <osmocom/msc/vlr.h>
+#include <osmocom/msc/msc_a.h>
+#include <osmocom/smpp/smpp_smsc.h>
+
+#define VSUB_USE_SMPP "SMPP"
+#define VSUB_USE_SMPP_CMD "SMPP-cmd"
+
+/* talloc integration for libsmpp34 */
+
+static struct smsc *g_smsc;
+
+static void *smpp34_talloc_malloc(size_t sz)
+{
+ return talloc_size(g_smsc, sz);
+}
+
+static void *smpp34_talloc_realloc(void *ptr, size_t sz)
+{
+ return talloc_realloc_size(g_smsc, ptr, sz);
+}
+
+static void smpp34_talloc_free(void *ptr)
+{
+ talloc_free(ptr);
+}
+
+static const struct smpp34_memory_functions smpp34_talloc = {
+ .malloc_fun = smpp34_talloc_malloc,
+ .realloc_fun = smpp34_talloc_realloc,
+ .free_fun = smpp34_talloc_free,
+};
+
+/*! \brief find vlr_subscr for a given SMPP NPI/TON/Address */
+static struct vlr_subscr *subscr_by_dst(struct gsm_network *net,
+ uint8_t npi, uint8_t ton,
+ const char *addr)
+{
+ struct vlr_subscr *vsub = NULL;
+
+ switch (npi) {
+ case NPI_Land_Mobile_E212:
+ vsub = vlr_subscr_find_by_imsi(net->vlr, addr, VSUB_USE_SMPP);
+ break;
+ case NPI_ISDN_E163_E164:
+ case NPI_Private:
+ vsub = vlr_subscr_find_by_msisdn(net->vlr, addr, VSUB_USE_SMPP);
+ break;
+ default:
+ LOGP(DSMPP, LOGL_NOTICE, "Unsupported NPI: %u\n", npi);
+ break;
+ }
+
+ log_set_context(LOG_CTX_VLR_SUBSCR, vsub);
+ return vsub;
+}
+
+static int smpp34_submit_tlv_msg_payload(const struct tlv_t *t,
+ const struct submit_sm_t *submit,
+ const uint8_t **sms_msg,
+ unsigned int *sms_msg_len)
+{
+ if (submit->sm_length) {
+ LOGP(DLSMS, LOGL_ERROR,
+ "SMPP cannot have payload in TLV _and_ in the header\n");
+ return -1;
+ }
+ *sms_msg = t->value.octet;
+ *sms_msg_len = t->length;
+
+ return 0;
+}
+
+/*! \brief convert from submit_sm_t to gsm_sms */
+static int submit_to_sms(struct gsm_sms **psms, struct gsm_network *net,
+ const struct submit_sm_t *submit)
+{
+ time_t t_now = time(NULL);
+ time_t t_validity_absolute;
+ const uint8_t *sms_msg = NULL;
+ unsigned int sms_msg_len = 0;
+ struct vlr_subscr *dest;
+ uint16_t msg_ref = 0;
+ struct gsm_sms *sms;
+ struct tlv_t *t;
+ int mode;
+ int can_store_sms = ((submit->esm_class & SMPP34_MSG_MODE_MASK) != 2); /* != forward mode */
+
+ dest = subscr_by_dst(net, submit->dest_addr_npi,
+ submit->dest_addr_ton,
+ (const char *)submit->destination_addr);
+ if (!dest && !can_store_sms) {
+ LOGP(DLSMS, LOGL_NOTICE, "SMPP SUBMIT-SM for unknown subscriber: "
+ "%s (NPI=%u)\n", submit->destination_addr,
+ submit->dest_addr_npi);
+ return ESME_RINVDSTADR;
+ }
+
+ smpp34_tlv_for_each(t, submit->tlv) {
+ switch (t->tag) {
+ case TLVID_message_payload:
+ if (smpp34_submit_tlv_msg_payload(t, submit, &sms_msg,
+ &sms_msg_len) < 0) {
+ if (dest)
+ vlr_subscr_put(dest, VSUB_USE_SMPP);
+ return ESME_ROPTPARNOTALLWD;
+ }
+ break;
+ case TLVID_user_message_reference:
+ msg_ref = t->value.val16;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!sms_msg) {
+ if (submit->sm_length > 0 && submit->sm_length < 255) {
+ sms_msg = submit->short_message;
+ sms_msg_len = submit->sm_length;
+ } else {
+ LOGP(DLSMS, LOGL_ERROR,
+ "SMPP neither message payload nor valid sm_length.\n");
+ if (dest)
+ vlr_subscr_put(dest, VSUB_USE_SMPP);
+ return ESME_RINVPARLEN;
+ }
+ }
+
+ sms = sms_alloc();
+ sms->source = SMS_SOURCE_SMPP;
+ sms->smpp.sequence_nr = submit->sequence_number;
+ sms->status_rep_req = submit->registered_delivery;
+ sms->msg_ref = msg_ref;
+
+ /* fill in the destination address */
+ sms->receiver = dest;
+ if (dest) {
+ /* Replace use count from above subscr_by_dst (VSUB_USE_SMPP) by the sms->receiver use count
+ * (VSUB_USE_SMS_RECEIVER) */
+ vlr_subscr_get(sms->receiver, VSUB_USE_SMS_RECEIVER);
+ vlr_subscr_put(dest, VSUB_USE_SMPP);
+ }
+ sms->dst.ton = submit->dest_addr_ton;
+ sms->dst.npi = submit->dest_addr_npi;
+ if (dest)
+ OSMO_STRLCPY_ARRAY(sms->dst.addr, dest->msisdn);
+ else
+ OSMO_STRLCPY_ARRAY(sms->dst.addr,
+ (const char *)submit->destination_addr);
+
+ /* fill in the source address */
+ sms->src.ton = submit->source_addr_ton;
+ sms->src.npi = submit->source_addr_npi;
+ OSMO_STRLCPY_ARRAY(sms->src.addr, (char *)submit->source_addr);
+
+ if (submit->esm_class == SMPP34_DELIVERY_ACK)
+ sms->is_report = true;
+
+ if (submit->esm_class & SMPP34_UDHI_IND)
+ sms->ud_hdr_ind = 1;
+
+ if (submit->esm_class & SMPP34_REPLY_PATH) {
+ sms->reply_path_req = 1;
+#warning Implement reply path
+ }
+
+ if (submit->data_coding == 0x00 || /* SMSC default */
+ submit->data_coding == 0x01) { /* GSM default alphabet */
+ sms->data_coding_scheme = GSM338_DCS_1111_7BIT;
+ mode = MODE_7BIT;
+ } else if ((submit->data_coding & 0xFC) == 0xF0) { /* 03.38 DCS default */
+ /* pass DCS 1:1 through from SMPP to GSM */
+ sms->data_coding_scheme = submit->data_coding;
+ mode = MODE_7BIT;
+ } else if (submit->data_coding == 0x02 ||
+ submit->data_coding == 0x04) {
+ /* 8-bit binary */
+ sms->data_coding_scheme = GSM338_DCS_1111_8BIT_DATA;
+ mode = MODE_8BIT;
+ } else if ((submit->data_coding & 0xFC) == 0xF4) { /* 03.38 DCS 8bit */
+ /* pass DCS 1:1 through from SMPP to GSM */
+ sms->data_coding_scheme = submit->data_coding;
+ mode = MODE_8BIT;
+ } else if (submit->data_coding == 0x08) {
+ /* UCS-2 */
+ sms->data_coding_scheme = (2 << 2);
+ mode = MODE_8BIT;
+ } else {
+ sms_free(sms);
+ LOGP(DLSMS, LOGL_ERROR, "SMPP Unknown Data Coding 0x%02x\n",
+ submit->data_coding);
+ return ESME_RUNKNOWNERR;
+ }
+
+ if (mode == MODE_7BIT) {
+ uint8_t ud_len = 0, padbits = 0;
+ sms->data_coding_scheme = GSM338_DCS_1111_7BIT;
+ if (sms->ud_hdr_ind) {
+ ud_len = *sms_msg + 1;
+ printf("copying %u bytes user data...\n", ud_len);
+ memcpy(sms->user_data, sms_msg,
+ OSMO_MIN(ud_len, sizeof(sms->user_data)));
+ sms_msg += ud_len;
+ sms_msg_len -= ud_len;
+ padbits = 7 - (ud_len % 7);
+ }
+ gsm_septet_pack(sms->user_data+ud_len, sms_msg, sms_msg_len, padbits);
+ sms->user_data_len = (ud_len*8 + padbits)/7 + sms_msg_len;/* SEPTETS */
+ /* FIXME: sms->text */
+ } else {
+ memcpy(sms->user_data, sms_msg, sms_msg_len);
+ sms->user_data_len = sms_msg_len;
+ }
+
+ t_validity_absolute = smpp_parse_time_format((const char *) submit->validity_period, &t_now);
+ if (!t_validity_absolute)
+ sms->validity_minutes = net->sms_queue_cfg->default_validity_mins;
+ else
+ sms->validity_minutes = (t_validity_absolute - t_now) / 60;
+
+ if (sms->validity_minutes < net->sms_queue_cfg->minimum_validity_mins) {
+ LOGP(DLSMS, LOGL_INFO, "SMS to %s: Overriding ESME-provided validity period (%lu) "
+ "with minimum SMSC validity period (%u) minutes\n", submit->destination_addr,
+ sms->validity_minutes, net->sms_queue_cfg->minimum_validity_mins);
+ sms->validity_minutes = net->sms_queue_cfg->minimum_validity_mins;
+ }
+
+ *psms = sms;
+ return ESME_ROK;
+}
+
+/*! \brief handle incoming libsmpp34 ssubmit_sm_t from remote ESME */
+int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit,
+ struct submit_sm_resp_t *submit_r)
+{
+ struct gsm_sms *sms;
+ struct gsm_network *net = esme->smsc->priv;
+ struct sms_signal_data sig;
+ int rc = -1;
+
+ rc = submit_to_sms(&sms, net, submit);
+ if (rc != ESME_ROK) {
+ submit_r->command_status = rc;
+ return 0;
+ }
+ smpp_esme_get(esme);
+ sms->smpp.esme = esme;
+ sms->protocol_id = submit->protocol_id;
+
+ switch (submit->esm_class & SMPP34_MSG_MODE_MASK) {
+ case 0: /* default */
+ case 1: /* datagram */
+ case 3: /* store-and-forward */
+ rc = db_sms_store(sms);
+ sms_free(sms);
+ sms = NULL;
+ if (rc < 0) {
+ LOGP(DLSMS, LOGL_ERROR, "SMPP SUBMIT-SM: Unable to "
+ "store SMS in database\n");
+ submit_r->command_status = ESME_RSYSERR;
+ return 0;
+ }
+ strcpy((char *)submit_r->message_id, "msg_id_not_implemented");
+ LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Stored in DB\n");
+
+ memset(&sig, 0, sizeof(sig));
+ osmo_signal_dispatch(SS_SMS, S_SMS_SUBMITTED, &sig);
+ rc = 0;
+ break;
+ case 2: /* forward (i.e. transaction) mode */
+ LOGP(DLSMS, LOGL_DEBUG, "SMPP SUBMIT-SM: Forwarding in "
+ "real time (Transaction/Forward mode)\n");
+ sms->smpp.transaction_mode = 1;
+ gsm411_send_sms(net, sms->receiver, sms);
+ rc = 1; /* don't send any response yet */
+ break;
+ }
+ return rc;
+}
+
+static void alert_all_esme(struct smsc *smsc, struct vlr_subscr *vsub,
+ uint8_t smpp_avail_status)
+{
+ struct osmo_esme *esme;
+
+ llist_for_each_entry(esme, &smsc->esme_list, list) {
+ /* we currently send an alert notification to each ESME that is
+ * connected, and do not require a (non-existent) delivery
+ * pending flag to be set before. */
+ if (!esme->bind_flags) {
+ LOGP(DSMPP, LOGL_DEBUG,
+ "ESME is not (yet) bound, skipping alert\n");
+ continue;
+ }
+ if (esme->acl && !esme->acl->alert_notifications) {
+ LOGP(DSMPP, LOGL_DEBUG,
+ "[%s] is not set to receive Alert Notifications\n",
+ esme->system_id);
+ continue;
+ }
+ if (esme->acl && esme->acl->deliver_src_imsi) {
+ smpp_tx_alert(esme, TON_Subscriber_Number,
+ NPI_Land_Mobile_E212,
+ vsub->imsi, smpp_avail_status);
+ } else {
+ smpp_tx_alert(esme, TON_Network_Specific,
+ NPI_ISDN_E163_E164,
+ vsub->msisdn, smpp_avail_status);
+ }
+ }
+}
+
+
+/*! \brief signal handler for status of attempted SMS deliveries */
+static int smpp_sms_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct sms_signal_data *sig_sms = signal_data;
+ struct gsm_sms *sms = sig_sms->sms;
+ struct smsc *smsc = handler_data;
+ int rc = 0;
+
+ if (!sms)
+ return 0;
+
+ if (sms->source != SMS_SOURCE_SMPP)
+ return 0;
+
+ switch (signal) {
+ case S_SMS_MEM_EXCEEDED:
+ /* fall-through: There is no ESME_Rxxx result code to
+ * indicate a MEMORY EXCEEDED in transaction mode back
+ * to the ESME */
+ case S_SMS_UNKNOWN_ERROR:
+ if (sms->smpp.transaction_mode) {
+ /* Send back the SUBMIT-SM response with appropriate error */
+ LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Error\n");
+ rc = smpp_tx_submit_r(sms->smpp.esme,
+ sms->smpp.sequence_nr,
+ ESME_RDELIVERYFAILURE,
+ sms->smpp.msg_id);
+ }
+ break;
+ case S_SMS_DELIVERED:
+ /* SMS layer tells us the delivery has been completed */
+ if (sms->smpp.transaction_mode) {
+ /* Send back the SUBMIT-SM response */
+ LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Success\n");
+ rc = smpp_tx_submit_r(sms->smpp.esme,
+ sms->smpp.sequence_nr,
+ ESME_ROK, sms->smpp.msg_id);
+ }
+ break;
+ case S_SMS_SMMA:
+ if (!sig_sms->trans || !sig_sms->trans->vsub) {
+ /* SMMA without a subscriber? strange... */
+ LOGP(DLSMS, LOGL_NOTICE, "SMMA without subscriber?\n");
+ break;
+ }
+
+ /* There's no real 1:1 match for SMMA in SMPP. However,
+ * an ALERT NOTIFICATION seems to be the most logical
+ * choice */
+ alert_all_esme(smsc, sig_sms->trans->vsub, 0);
+ break;
+ }
+
+ return rc;
+}
+
+/*! \brief signal handler for subscriber related signals */
+static int smpp_subscr_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct vlr_subscr *vsub = signal_data;
+ struct smsc *smsc = handler_data;
+ uint8_t smpp_avail_status;
+
+ /* determine the smpp_avail_status depending on attach/detach */
+ switch (signal) {
+ case S_SUBSCR_ATTACHED:
+ smpp_avail_status = 0;
+ break;
+ case S_SUBSCR_DETACHED:
+ smpp_avail_status = 2;
+ break;
+ default:
+ return 0;
+ }
+
+ alert_all_esme(smsc, vsub, smpp_avail_status);
+
+ return 0;
+}
+
+/* GSM 03.38 6.2.1 Character expanding (no decode!) */
+static int gsm_7bit_expand(char *text, const uint8_t *user_data, uint8_t septet_l, uint8_t ud_hdr_ind)
+{
+ int i = 0;
+ int shift = 0;
+ uint8_t c;
+
+ /* skip the user data header */
+ if (ud_hdr_ind) {
+ /* get user data header length + 1 (for the 'user data header length'-field) */
+ shift = ((user_data[0] + 1) * 8) / 7;
+ if ((((user_data[0] + 1) * 8) % 7) != 0)
+ shift++;
+ septet_l = septet_l - shift;
+ }
+
+ for (i = 0; i < septet_l; i++) {
+ c =
+ ((user_data[((i + shift) * 7 + 7) >> 3] <<
+ (7 - (((i + shift) * 7 + 7) & 7))) |
+ (user_data[((i + shift) * 7) >> 3] >>
+ (((i + shift) * 7) & 7))) & 0x7f;
+
+ *(text++) = c;
+ }
+
+ *text = '\0';
+
+ return i;
+}
+
+
+/* FIXME: libsmpp34 helpers, they should be part of libsmpp34! */
+void append_tlv(tlv_t **req_tlv, uint16_t tag,
+ const uint8_t *data, uint16_t len)
+{
+ tlv_t tlv;
+
+ memset(&tlv, 0, sizeof(tlv));
+ tlv.tag = tag;
+ tlv.length = len;
+ memcpy(tlv.value.octet, data, tlv.length);
+ build_tlv(req_tlv, &tlv);
+}
+void append_tlv_u8(tlv_t **req_tlv, uint16_t tag, uint8_t val)
+{
+ tlv_t tlv;
+
+ memset(&tlv, 0, sizeof(tlv));
+ tlv.tag = tag;
+ tlv.length = 1;
+ tlv.value.val08 = val;
+ build_tlv(req_tlv, &tlv);
+}
+void append_tlv_u16(tlv_t **req_tlv, uint16_t tag, uint16_t val)
+{
+ tlv_t tlv;
+
+ memset(&tlv, 0, sizeof(tlv));
+ tlv.tag = tag;
+ tlv.length = 2;
+ tlv.value.val16 = val;
+ build_tlv(req_tlv, &tlv);
+}
+
+#if BEFORE_MSCSPLIT
+/* We currently have no lchan information. Re-add after A-interface, see OS#2390. */
+/* Append the Osmocom vendor-specific additional TLVs to a SMPP msg */
+static void append_osmo_tlvs(tlv_t **req_tlv, const struct gsm_lchan *lchan)
+{
+ int idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+ lchan->meas_rep_idx, 1);
+ const struct gsm_meas_rep *mr = &lchan->meas_rep[idx];
+ const struct gsm_meas_rep_unidir *ul_meas = &mr->ul;
+ const struct gsm_meas_rep_unidir *dl_meas = &mr->dl;
+
+ /* Osmocom vendor-specific SMPP34 extensions */
+ append_tlv_u16(req_tlv, TLVID_osmo_arfcn, lchan->ts->trx->arfcn);
+ if (mr->flags & MEAS_REP_F_MS_L1) {
+ uint8_t ms_dbm;
+ append_tlv_u8(req_tlv, TLVID_osmo_ta, mr->ms_l1.ta);
+ ms_dbm = ms_pwr_dbm(lchan->ts->trx->bts->band, mr->ms_l1.pwr);
+ append_tlv_u8(req_tlv, TLVID_osmo_ms_l1_txpwr, ms_dbm);
+ } else if (mr->flags & MEAS_REP_F_MS_TO) /* Save Timing Offset field = MS Timing Offset + 63 */
+ append_tlv_u8(req_tlv, TLVID_osmo_ta, mr->ms_timing_offset + 63);
+
+ append_tlv_u16(req_tlv, TLVID_osmo_rxlev_ul,
+ rxlev2dbm(ul_meas->full.rx_lev));
+ append_tlv_u8(req_tlv, TLVID_osmo_rxqual_ul, ul_meas->full.rx_qual);
+
+ if (mr->flags & MEAS_REP_F_DL_VALID) {
+ append_tlv_u16(req_tlv, TLVID_osmo_rxlev_dl,
+ rxlev2dbm(dl_meas->full.rx_lev));
+ append_tlv_u8(req_tlv, TLVID_osmo_rxqual_dl,
+ dl_meas->full.rx_qual);
+ }
+
+ if (lchan->conn && lchan->conn->vsub) {
+ struct vlr_subscr *vsub = lchan->conn->vsub;
+ size_t imei_len = strlen(vsub->imei);
+ if (imei_len)
+ append_tlv(req_tlv, TLVID_osmo_imei,
+ (uint8_t *)vsub->imei, imei_len+1);
+ }
+}
+#endif
+
+struct {
+ uint32_t smpp_status_code;
+ uint8_t gsm411_cause;
+} smpp_to_gsm411_err_array[] = {
+
+ /* Seems like most phones don't care about the failure cause,
+ * although some will display a different notification for
+ * GSM411_RP_CAUSE_MO_NUM_UNASSIGNED
+ * Some provoke a display of "Try again later"
+ * while others a more definitive "Message sending failed"
+ */
+
+ { ESME_RSYSERR, GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER },
+ { ESME_RINVDSTADR, GSM411_RP_CAUSE_MO_NUM_UNASSIGNED },
+ { ESME_RMSGQFUL, GSM411_RP_CAUSE_MO_CONGESTION },
+ { ESME_RINVSRCADR, GSM411_RP_CAUSE_MO_SMS_REJECTED },
+ { ESME_RINVMSGID, GSM411_RP_CAUSE_INV_TRANS_REF }
+};
+
+static int smpp_to_gsm411_err(uint32_t smpp_status_code, int *gsm411_cause)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(smpp_to_gsm411_err_array); i++) {
+ if (smpp_to_gsm411_err_array[i].smpp_status_code != smpp_status_code)
+ continue;
+ *gsm411_cause = smpp_to_gsm411_err_array[i].gsm411_cause;
+ return 0;
+ }
+ return -1;
+}
+
+static void smpp_cmd_free(struct osmo_smpp_cmd *cmd)
+{
+ osmo_timer_del(&cmd->response_timer);
+ llist_del(&cmd->list);
+ vlr_subscr_put(cmd->vsub, VSUB_USE_SMPP_CMD);
+ talloc_free(cmd);
+}
+
+void smpp_cmd_flush_pending(struct osmo_esme *esme)
+{
+ struct osmo_smpp_cmd *cmd, *next;
+
+ llist_for_each_entry_safe(cmd, next, &esme->smpp_cmd_list, list)
+ smpp_cmd_free(cmd);
+}
+
+void smpp_cmd_ack(struct osmo_smpp_cmd *cmd)
+{
+ struct msc_a *msc_a;
+ struct gsm_trans *trans;
+
+ if (cmd->is_report)
+ goto out;
+
+ msc_a = msc_a_for_vsub(cmd->vsub, true);
+ if (!msc_a) {
+ LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber %s\n", vlr_subscr_name(cmd->vsub));
+ goto out;
+ }
+
+ trans = trans_find_by_id(msc_a, TRANS_SMS, cmd->gsm411_trans_id);
+ if (!trans) {
+ LOG_MSC_A_CAT(msc_a, DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n", cmd->gsm411_trans_id);
+ goto out;
+ }
+
+ gsm411_send_rp_ack(trans, cmd->gsm411_msg_ref);
+out:
+ smpp_cmd_free(cmd);
+}
+
+void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status)
+{
+ struct msc_a *msc_a;
+ struct gsm_trans *trans;
+ int gsm411_cause;
+
+ if (cmd->is_report)
+ goto out;
+
+ msc_a = msc_a_for_vsub(cmd->vsub, true);
+ if (!msc_a) {
+ LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber %s\n", vlr_subscr_name(cmd->vsub));
+ goto out;
+ }
+
+ trans = trans_find_by_id(msc_a, TRANS_SMS, cmd->gsm411_trans_id);
+ if (!trans) {
+ LOG_MSC_A_CAT(msc_a, DSMPP, LOGL_ERROR, "GSM transaction %u is gone\n",
+ cmd->gsm411_trans_id);
+ goto out;
+ }
+
+ if (smpp_to_gsm411_err(status, &gsm411_cause) < 0)
+ gsm411_cause = GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+
+ gsm411_send_rp_error(trans, cmd->gsm411_msg_ref, gsm411_cause);
+out:
+ smpp_cmd_free(cmd);
+}
+
+static void smpp_deliver_sm_cb(void *data)
+{
+ smpp_cmd_err(data, ESME_RSYSERR);
+}
+
+static int smpp_cmd_enqueue(struct osmo_esme *esme,
+ struct vlr_subscr *vsub, struct gsm_sms *sms,
+ uint32_t sequence_number)
+{
+ struct osmo_smpp_cmd *cmd;
+
+ cmd = talloc_zero(esme, struct osmo_smpp_cmd);
+ if (!cmd)
+ return -1;
+
+ cmd->sequence_nr = sequence_number;
+ cmd->is_report = sms->is_report;
+ cmd->gsm411_msg_ref = sms->gsm411.msg_ref;
+ cmd->gsm411_trans_id = sms->gsm411.transaction_id;
+ vlr_subscr_get(vsub, VSUB_USE_SMPP_CMD);
+ cmd->vsub = vsub;
+
+ /* FIXME: No predefined value for this response_timer as specified by
+ * SMPP 3.4 specs, section 7.2. Make this configurable? Don't forget
+ * lchan keeps busy until we get a reply to this SMPP command. Too high
+ * value may exhaust resources.
+ */
+ osmo_timer_setup(&cmd->response_timer, smpp_deliver_sm_cb, cmd);
+ osmo_timer_schedule(&cmd->response_timer, 5, 0);
+ llist_add_tail(&cmd->list, &esme->smpp_cmd_list);
+
+ return 0;
+}
+
+struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme,
+ uint32_t sequence_nr)
+{
+ struct osmo_smpp_cmd *cmd;
+
+ llist_for_each_entry(cmd, &esme->smpp_cmd_list, list) {
+ if (cmd->sequence_nr == sequence_nr)
+ return cmd;
+ }
+ return NULL;
+}
+
+static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms,
+ struct msc_a *msc_a)
+{
+ struct deliver_sm_t deliver;
+ int mode, ret;
+ uint8_t dcs;
+ struct vlr_subscr *vsub = msc_a_vsub(msc_a);
+
+ 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",
+ vsub->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",
+ vsub->msisdn);
+ }
+
+ deliver.dest_addr_ton = sms->dst.ton;
+ deliver.dest_addr_npi = sms->dst.npi;
+ memcpy(deliver.destination_addr, sms->dst.addr,
+ sizeof(deliver.destination_addr));
+
+ if (sms->is_report)
+ deliver.esm_class = SMPP34_DELIVERY_RECEIPT;
+ else
+ deliver.esm_class = SMPP34_DATAGRAM_MODE;
+
+ if (sms->ud_hdr_ind)
+ deliver.esm_class |= SMPP34_UDHI_IND;
+ if (sms->reply_path_req)
+ deliver.esm_class |= SMPP34_REPLY_PATH;
+
+ deliver.protocol_id = sms->protocol_id;
+ deliver.priority_flag = 0;
+ if (sms->status_rep_req)
+ deliver.registered_delivery = SMPP34_DELIVERY_RECEIPT_ON;
+
+ /* Figure out SMPP DCS from TP-DCS */
+ dcs = sms->data_coding_scheme;
+ if (smpp_determine_scheme(dcs, &deliver.data_coding, &mode) == -1)
+ return -1;
+
+ /* Transparently pass on DCS via SMPP if requested */
+ if (esme->acl && esme->acl->dcs_transparent)
+ deliver.data_coding = dcs;
+
+ if (mode == MODE_7BIT) {
+ uint8_t *dst = deliver.short_message;
+
+ /* SMPP has this strange notion of putting 7bit SMS in
+ * an octet-aligned mode */
+ if (sms->ud_hdr_ind) {
+ /* length (bytes) of UDH inside UD */
+ uint8_t udh_len = sms->user_data[0] + 1;
+
+ /* copy over the UDH */
+ memcpy(dst, sms->user_data, udh_len);
+ dst += udh_len;
+ deliver.sm_length = udh_len;
+ }
+ /* add decoded text */
+ deliver.sm_length += gsm_7bit_expand((char *)dst, sms->user_data, sms->user_data_len, sms->ud_hdr_ind);
+ } else {
+ deliver.sm_length = sms->user_data_len;
+ memcpy(deliver.short_message, sms->user_data, deliver.sm_length);
+ }
+
+#if BEFORE_MSCSPLIT
+ /* We currently have no lchan information. Re-add after A-interface, see OS#2390. */
+ if (esme->acl && esme->acl->osmocom_ext && conn->lchan)
+ append_osmo_tlvs(&deliver.tlv, conn->lchan);
+#endif
+
+ append_tlv_u16(&deliver.tlv, TLVID_user_message_reference,
+ sms->msg_ref);
+
+ ret = smpp_tx_deliver(esme, &deliver);
+ if (ret < 0)
+ return ret;
+
+ OSMO_ASSERT(!sms->smpp.esme);
+ smpp_esme_get(esme);
+ sms->smpp.esme = esme;
+
+ return smpp_cmd_enqueue(esme, vsub, sms,
+ deliver.sequence_number);
+}
+
+static struct smsc *g_smsc;
+
+bool smpp_route_smpp_first()
+{
+ return (bool)(g_smsc->smpp_first);
+}
+
+int smpp_try_deliver(struct gsm_sms *sms, struct msc_a *msc_a)
+{
+ struct osmo_esme *esme;
+ struct osmo_smpp_addr dst;
+ int rc;
+
+ memset(&dst, 0, sizeof(dst));
+ dst.ton = sms->dst.ton;
+ dst.npi = sms->dst.npi;
+ memcpy(dst.addr, sms->dst.addr, sizeof(dst.addr));
+
+ rc = smpp_route(g_smsc, &dst, &esme);
+ if (!rc)
+ rc = deliver_to_esme(esme, sms, msc_a);
+
+ return rc;
+}
+
+struct smsc *smsc_from_vty(struct vty *v)
+{
+ /* FIXME: this is ugly */
+ return g_smsc;
+}
+
+/*! \brief Allocate the OpenBSC SMPP interface struct and init VTY. */
+int smpp_openbsc_alloc_init(void *ctx)
+{
+ g_smsc = smpp_smsc_alloc_init(ctx);
+ if (!g_smsc) {
+ LOGP(DSMPP, LOGL_FATAL, "Cannot allocate smsc struct\n");
+ return -1;
+ }
+ smpp34_set_memory_functions(&smpp34_talloc);
+ return smpp_vty_init();
+}
+
+/*! \brief Launch the OpenBSC SMPP interface with the parameters set from VTY.
+ */
+int smpp_openbsc_start(struct gsm_network *net)
+{
+ int rc;
+ g_smsc->priv = net;
+
+ /* If a VTY configuration has taken place, the values have been stored
+ * in the smsc struct. Otherwise, use the defaults (NULL -> any, 0 ->
+ * default SMPP port, see smpp_smsc_bind()). */
+ rc = smpp_smsc_start(g_smsc, g_smsc->bind_addr, g_smsc->listen_port);
+ if (rc < 0)
+ return rc;
+
+ rc = osmo_signal_register_handler(SS_SMS, smpp_sms_cb, g_smsc);
+ if (rc < 0)
+ return rc;
+ rc = osmo_signal_register_handler(SS_SUBSCR, smpp_subscr_cb, g_smsc);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
diff --git a/src/libsmpputil/smpp_smsc.c b/src/libsmpputil/smpp_smsc.c
new file mode 100644
index 000000000..b03f7bea3
--- /dev/null
+++ b/src/libsmpputil/smpp_smsc.c
@@ -0,0 +1,1026 @@
+/* SMPP 3.4 interface, SMSC-side implementation */
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <smpp34.h>
+#include <smpp34_structs.h>
+#include <smpp34_params.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#include <osmocom/msc/debug.h>
+#include <osmocom/msc/gsm_data.h>
+#include <osmocom/smpp/smpp_smsc.h>
+
+enum emse_bind {
+ ESME_BIND_RX = 0x01,
+ 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)
+{
+ struct osmo_smpp_acl *acl;
+
+ llist_for_each_entry(acl, &smsc->acl_list, list) {
+ if (!strcmp(acl->system_id, sys_id))
+ return acl;
+ }
+
+ return NULL;
+}
+
+struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id)
+{
+ struct osmo_smpp_acl *acl;
+
+ if (strlen(sys_id) > SMPP_SYS_ID_LEN)
+ return NULL;
+
+ if (smpp_acl_by_system_id(smsc, sys_id))
+ return NULL;
+
+ acl = talloc_zero(smsc, struct osmo_smpp_acl);
+ if (!acl)
+ return NULL;
+
+ acl->smsc = smsc;
+ strcpy(acl->system_id, sys_id);
+ acl->alert_notifications = 1;
+ INIT_LLIST_HEAD(&acl->route_list);
+
+ llist_add_tail(&acl->list, &smsc->acl_list);
+
+ return acl;
+}
+
+void smpp_acl_delete(struct osmo_smpp_acl *acl)
+{
+ struct osmo_smpp_route *r, *r2;
+
+ llist_del(&acl->list);
+
+ /* 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)
+{
+ esme->use++;
+}
+
+static void esme_destroy(struct osmo_esme *esme)
+{
+ osmo_wqueue_clear(&esme->wqueue);
+ if (esme->wqueue.bfd.fd >= 0) {
+ osmo_fd_unregister(&esme->wqueue.bfd);
+ close(esme->wqueue.bfd.fd);
+ }
+ smpp_cmd_flush_pending(esme);
+ llist_del(&esme->list);
+ if (esme->acl)
+ esme->acl->esme = NULL;
+ 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)
+{
+ esme->use--;
+ if (esme->use <= 0)
+ esme_destroy(esme);
+}
+
+/*! \brief try to find a SMPP route (ESME) for given destination */
+int smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest, struct osmo_esme **pesme)
+{
+ 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) {
+ *pesme = esme;
+ return 0;
+ } else
+ LOGP(DSMPP, LOGL_NOTICE, "[%s] is matching route, "
+ "but not bound for Rx, discarding MO SMS\n",
+ esme->system_id);
+ }
+
+ *pesme = NULL;
+ if (acl)
+ return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+ else
+ return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
+}
+
+/*! \brief pack a libsmpp34 data strcutrure and send it to the ESME */
+static int pack_and_send(struct osmo_esme *esme, uint32_t type, void *ptr)
+{
+ struct msgb *msg;
+ int rc, rlen;
+
+ /* the socket was closed. Avoid allocating + enqueueing msgb, see
+ * https://osmocom.org/issues/3278 */
+ if (esme->wqueue.bfd.fd == -1)
+ return -EIO;
+
+ msg = msgb_alloc(4096, "SMPP_Tx");
+ if (!msg)
+ return -ENOMEM;
+
+ rc = smpp34_pack(type, msg->tail, msgb_tailroom(msg), &rlen, ptr);
+ if (rc != 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Error during smpp34_pack(): %s\n",
+ esme->system_id, smpp34_strerror);
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ msgb_put(msg, rlen);
+
+ if (osmo_wqueue_enqueue(&esme->wqueue, msg) != 0) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Write queue full. Dropping message\n",
+ esme->system_id);
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/*! \brief transmit a generic NACK to a remote ESME */
+static int smpp_tx_gen_nack(struct osmo_esme *esme, uint32_t seq, uint32_t status)
+{
+ struct generic_nack_t nack;
+ char buf[SMALL_BUFF];
+
+ nack.command_length = 0;
+ nack.command_id = GENERIC_NACK;
+ nack.sequence_number = seq;
+ nack.command_status = status;
+
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Tx GENERIC NACK: %s\n",
+ esme->system_id, str_command_status(status, buf));
+
+ return PACK_AND_SEND(esme, &nack);
+}
+
+/*! \brief retrieve SMPP sequence number from a msgb */
+static inline uint32_t smpp_msgb_seq(struct msgb *msg)
+{
+ uint8_t *tmp = msgb_data(msg);
+ return ntohl(*(uint32_t *)tmp);
+}
+
+/*! \brief handle an incoming SMPP generic NACK */
+static int smpp_handle_gen_nack(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct generic_nack_t nack;
+ char buf[SMALL_BUFF];
+ int rc;
+
+ SMPP34_UNPACK(rc, GENERIC_NACK, &nack, 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_ERROR, "[%s] Rx GENERIC NACK: %s\n",
+ esme->system_id, str_command_status(nack.command_status, buf));
+
+ 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;
+ int rc;
+
+ SMPP34_UNPACK(rc, BIND_RECEIVER, &bind, 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;
+ }
+
+ INIT_RESP(BIND_TRANSMITTER_RESP, &bind_r, &bind);
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Rx from (Version %02x)\n",
+ bind.system_id, bind.interface_version);
+
+ rc = _process_bind(esme, bind.interface_version, ESME_BIND_RX,
+ (const char *)bind.system_id, (const char *)bind.password);
+ bind_r.command_status = rc;
+
+ return PACK_AND_SEND(esme, &bind_r);
+}
+
+/*! \brief handle an incoming SMPP BIND TRANSMITTER */
+static int smpp_handle_bind_tx(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct bind_transmitter_t bind;
+ struct bind_transmitter_resp_t bind_r;
+ struct tlv_t tlv;
+ int rc;
+
+ SMPP34_UNPACK(rc, BIND_TRANSMITTER, &bind, 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;
+ }
+
+ INIT_RESP(BIND_TRANSMITTER_RESP, &bind_r, &bind);
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Tx (Version %02x)\n",
+ bind.system_id, bind.interface_version);
+
+ 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 */
+ osmo_strlcpy((char*)bind_r.system_id, esme->smsc->system_id, sizeof(bind_r.system_id));
+
+ /* add interface version TLV */
+ tlv.tag = TLVID_sc_interface_version;
+ tlv.length = sizeof(uint8_t);
+ tlv.value.val16 = esme->smpp_version;
+ build_tlv(&bind_r.tlv, &tlv);
+
+ rc = PACK_AND_SEND(esme, &bind_r);
+ destroy_tlv(bind_r.tlv);
+ return rc;
+}
+
+/*! \brief handle an incoming SMPP BIND TRANSCEIVER */
+static int smpp_handle_bind_trx(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct bind_transceiver_t bind;
+ struct bind_transceiver_resp_t bind_r;
+ int rc;
+
+ SMPP34_UNPACK(rc, BIND_TRANSCEIVER, &bind, 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;
+ }
+
+ INIT_RESP(BIND_TRANSCEIVER_RESP, &bind_r, &bind);
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx BIND Trx (Version %02x)\n",
+ bind.system_id, bind.interface_version);
+
+ 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;
+
+ return PACK_AND_SEND(esme, &bind_r);
+}
+
+/*! \brief handle an incoming SMPP UNBIND */
+static int smpp_handle_unbind(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct unbind_t unbind;
+ struct unbind_resp_t unbind_r;
+ int rc;
+
+ SMPP34_UNPACK(rc, UNBIND, &unbind, 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;
+ }
+
+ INIT_RESP(UNBIND_RESP, &unbind_r, &unbind);
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx UNBIND\n", esme->system_id);
+
+ if (esme->bind_flags == 0) {
+ unbind_r.command_status = ESME_RINVBNDSTS;
+ goto err;
+ }
+
+ esme->bind_flags = 0;
+err:
+ return PACK_AND_SEND(esme, &unbind_r);
+}
+
+/*! \brief handle an incoming SMPP ENQUIRE LINK */
+static int smpp_handle_enq_link(struct osmo_esme *esme, struct msgb *msg)
+{
+ struct enquire_link_t enq;
+ struct enquire_link_resp_t enq_r;
+ int rc;
+
+ SMPP34_UNPACK(rc, ENQUIRE_LINK, &enq, 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_DEBUG, "[%s] Rx Enquire Link\n", esme->system_id);
+
+ INIT_RESP(ENQUIRE_LINK_RESP, &enq_r, &enq);
+
+ LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx Enquire Link Response\n", esme->system_id);
+
+ return PACK_AND_SEND(esme, &enq_r);
+}
+
+/*! \brief send a SUBMIT-SM RESPONSE to a remote ESME */
+int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr,
+ uint32_t command_status, char *msg_id)
+{
+ struct submit_sm_resp_t submit_r;
+
+ memset(&submit_r, 0, sizeof(submit_r));
+ submit_r.command_length = 0;
+ submit_r.command_id = SUBMIT_SM_RESP;
+ submit_r.command_status = command_status;
+ submit_r.sequence_number= sequence_nr;
+ snprintf((char *) submit_r.message_id, sizeof(submit_r.message_id), "%s", msg_id);
+
+ return PACK_AND_SEND(esme, &submit_r);
+}
+
+static const struct value_string smpp_avail_strs[] = {
+ { 0, "Available" },
+ { 1, "Denied" },
+ { 2, "Unavailable" },
+ { 0, NULL }
+};
+
+/*! \brief send an ALERT_NOTIFICATION to a remote ESME */
+int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi,
+ const char *addr, uint8_t avail_status)
+{
+ struct alert_notification_t alert;
+ struct tlv_t tlv;
+ int rc;
+
+ memset(&alert, 0, sizeof(alert));
+ alert.command_length = 0;
+ alert.command_id = ALERT_NOTIFICATION;
+ alert.command_status = ESME_ROK;
+ 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);
+
+ tlv.tag = TLVID_ms_availability_status;
+ tlv.length = sizeof(uint8_t);
+ tlv.value.val08 = avail_status;
+ build_tlv(&alert.tlv, &tlv);
+
+ LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx ALERT_NOTIFICATION (%s/%u/%u): %s\n",
+ esme->system_id, alert.source_addr, alert.source_addr_ton,
+ alert.source_addr_npi,
+ get_value_string(smpp_avail_strs, avail_status));
+
+ rc = PACK_AND_SEND(esme, &alert);
+ destroy_tlv(alert.tlv);
+ return rc;
+}
+
+/* \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);
+
+ LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx DELIVER-SM (from %s)\n",
+ esme->system_id, deliver->source_addr);
+
+ 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;
+ struct osmo_smpp_cmd *cmd;
+ 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;
+ }
+
+ cmd = smpp_cmd_find_by_seqnum(esme, deliver_r.sequence_number);
+ if (!cmd) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Rx DELIVER-SM RESP !? (%s)\n",
+ esme->system_id, get_value_string(smpp_status_strs,
+ deliver_r.command_status));
+ return -1;
+ }
+
+ if (deliver_r.command_status == ESME_ROK)
+ smpp_cmd_ack(cmd);
+ else
+ smpp_cmd_err(cmd, deliver_r.command_status);
+
+ 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)
+{
+ struct submit_sm_t submit;
+ struct submit_sm_resp_t submit_r;
+ int rc;
+
+ memset(&submit, 0, sizeof(submit));
+ SMPP34_UNPACK(rc, SUBMIT_SM, &submit, 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;
+ }
+
+ INIT_RESP(SUBMIT_SM_RESP, &submit_r, &submit);
+
+ if (!(esme->bind_flags & ESME_BIND_TX)) {
+ submit_r.command_status = ESME_RINVBNDSTS;
+ return PACK_AND_SEND(esme, &submit_r);
+ }
+
+ LOGP(DSMPP, LOGL_INFO, "[%s] Rx SUBMIT-SM (%s/%u/%u)\n",
+ esme->system_id, submit.destination_addr,
+ submit.dest_addr_ton, submit.dest_addr_npi);
+
+ INIT_RESP(SUBMIT_SM_RESP, &submit_r, &submit);
+
+ rc = handle_smpp_submit(esme, &submit, &submit_r);
+ if (rc == 0)
+ return PACK_AND_SEND(esme, &submit_r);
+
+ return rc;
+}
+
+/*! \brief one complete SMPP PDU from the ESME has been received */
+static int smpp_pdu_rx(struct osmo_esme *esme, struct msgb *msg __uses)
+{
+ uint32_t cmd_id = smpp_msgb_cmdid(msg);
+ int rc = 0;
+
+ LOGP(DSMPP, LOGL_DEBUG, "[%s] smpp_pdu_rx(%s)\n", esme->system_id,
+ msgb_hexdump(msg));
+
+ switch (cmd_id) {
+ case GENERIC_NACK:
+ rc = smpp_handle_gen_nack(esme, msg);
+ break;
+ case BIND_RECEIVER:
+ rc = smpp_handle_bind_rx(esme, msg);
+ break;
+ case BIND_TRANSMITTER:
+ rc = smpp_handle_bind_tx(esme, msg);
+ break;
+ case BIND_TRANSCEIVER:
+ rc = smpp_handle_bind_trx(esme, msg);
+ break;
+ case UNBIND:
+ rc = smpp_handle_unbind(esme, msg);
+ break;
+ case ENQUIRE_LINK:
+ rc = smpp_handle_enq_link(esme, msg);
+ break;
+ 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:
+ break;
+ case CANCEL_SM:
+ case QUERY_SM:
+ case REPLACE_SM:
+ case SUBMIT_MULTI:
+ LOGP(DSMPP, LOGL_NOTICE, "[%s] Unimplemented PDU Command "
+ "0x%08x\n", esme->system_id, cmd_id);
+ break;
+ default:
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Unknown PDU Command 0x%08x\n",
+ esme->system_id, cmd_id);
+ rc = smpp_tx_gen_nack(esme, smpp_msgb_seq(msg), ESME_RINVCMDID);
+ break;
+ }
+
+ return rc;
+}
+
+/* This macro should be called after a call to read() in the read_cb of an
+ * osmo_fd to properly check for errors.
+ * rc is the return value of read, err_label is the label to jump to in case of
+ * an error. The code there should handle closing the connection.
+ * FIXME: This code should go in libosmocore utils.h so it can be used by other
+ * projects as well.
+ * */
+#define OSMO_FD_CHECK_READ(rc, err_label) \
+ if (rc < 0) { \
+ /* EINTR is a non-fatal error, just try again */ \
+ if (errno == EINTR) \
+ return 0; \
+ goto err_label; \
+ } else if (rc == 0) { \
+ goto err_label; \
+ }
+
+/* !\brief call-back when per-ESME TCP socket has some data to be read */
+static int esme_link_read_cb(struct osmo_fd *ofd)
+{
+ struct osmo_esme *esme = ofd->data;
+ uint32_t len;
+ uint8_t *lenptr = (uint8_t *) &len;
+ uint8_t *cur;
+ struct msgb *msg;
+ ssize_t rdlen, rc;
+
+ switch (esme->read_state) {
+ case READ_ST_IN_LEN:
+ rdlen = sizeof(uint32_t) - esme->read_idx;
+ rc = read(ofd->fd, lenptr + esme->read_idx, rdlen);
+ if (rc < 0)
+ LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %zd (%s)\n",
+ esme->system_id, rc, strerror(errno));
+ OSMO_FD_CHECK_READ(rc, dead_socket);
+
+ esme->read_idx += rc;
+
+ if (esme->read_idx >= sizeof(uint32_t)) {
+ esme->read_len = ntohl(len);
+ if (esme->read_len < 8 || esme->read_len > UINT16_MAX) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] length invalid %u\n",
+ esme->system_id, esme->read_len);
+ goto dead_socket;
+ }
+
+ msg = msgb_alloc(esme->read_len, "SMPP Rx");
+ if (!msg)
+ return -ENOMEM;
+ esme->read_msg = msg;
+ cur = msgb_put(msg, sizeof(uint32_t));
+ memcpy(cur, lenptr, sizeof(uint32_t));
+ esme->read_state = READ_ST_IN_MSG;
+ esme->read_idx = sizeof(uint32_t);
+ }
+ break;
+ case READ_ST_IN_MSG:
+ msg = esme->read_msg;
+ rdlen = esme->read_len - esme->read_idx;
+ rc = read(ofd->fd, msg->tail, OSMO_MIN(rdlen, msgb_tailroom(msg)));
+ if (rc < 0)
+ LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %zd (%s)\n",
+ esme->system_id, rc, strerror(errno));
+ OSMO_FD_CHECK_READ(rc, dead_socket);
+
+ esme->read_idx += rc;
+ msgb_put(msg, rc);
+
+ if (esme->read_idx >= esme->read_len) {
+ rc = smpp_pdu_rx(esme, esme->read_msg);
+ msgb_free(esme->read_msg);
+ esme->read_msg = NULL;
+ esme->read_idx = 0;
+ esme->read_len = 0;
+ esme->read_state = READ_ST_IN_LEN;
+ }
+ break;
+ }
+
+ return 0;
+dead_socket:
+ msgb_free(esme->read_msg);
+ osmo_fd_unregister(&esme->wqueue.bfd);
+ close(esme->wqueue.bfd.fd);
+ esme->wqueue.bfd.fd = -1;
+ if (esme->acl)
+ esme->acl->esme = NULL;
+ smpp_esme_put(esme);
+
+ return -EBADF;
+}
+
+/* call-back of write queue once it wishes to write a message to the socket */
+static int esme_link_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ struct osmo_esme *esme = ofd->data;
+ int rc;
+
+ rc = write(ofd->fd, msgb_data(msg), msgb_length(msg));
+ if (rc == 0) {
+ osmo_fd_unregister(&esme->wqueue.bfd);
+ close(esme->wqueue.bfd.fd);
+ esme->wqueue.bfd.fd = -1;
+ if (esme->acl)
+ esme->acl->esme = NULL;
+ smpp_esme_put(esme);
+ } else if (rc < msgb_length(msg)) {
+ LOGP(DSMPP, LOGL_ERROR, "[%s] Short write\n", esme->system_id);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* callback for already-accepted new TCP socket */
+static int link_accept_cb(struct smsc *smsc, int fd,
+ struct sockaddr_storage *s, socklen_t s_len)
+{
+ struct osmo_esme *esme = talloc_zero(smsc, struct osmo_esme);
+ if (!esme) {
+ close(fd);
+ return -ENOMEM;
+ }
+
+ INIT_LLIST_HEAD(&esme->smpp_cmd_list);
+ smpp_esme_get(esme);
+ esme->own_seq_nr = rand();
+ esme_inc_seq_nr(esme);
+ esme->smsc = smsc;
+ osmo_wqueue_init(&esme->wqueue, 10);
+ osmo_fd_setup(&esme->wqueue.bfd, fd, OSMO_FD_READ, osmo_wqueue_bfd_cb, esme, 0);
+
+ if (osmo_fd_register(&esme->wqueue.bfd) != 0) {
+ close(fd);
+ talloc_free(esme);
+ return -EIO;
+ }
+
+ esme->wqueue.read_cb = esme_link_read_cb;
+ esme->wqueue.write_cb = esme_link_write_cb;
+
+ llist_add_tail(&esme->list, &smsc->esme_list);
+
+ return 0;
+}
+
+/* callback of listening TCP socket */
+static int smsc_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ int rc;
+ struct sockaddr_storage sa;
+ socklen_t sa_len = sizeof(sa);
+
+ rc = accept(ofd->fd, (struct sockaddr *)&sa, &sa_len);
+ if (rc < 0) {
+ LOGP(DSMPP, LOGL_ERROR, "Accept returns %d (%s)\n",
+ rc, strerror(errno));
+ return rc;
+ }
+ return link_accept_cb(ofd->data, rc, &sa, sa_len);
+}
+
+/*! \brief allocate and initialize an smsc struct from talloc context ctx. */
+struct smsc *smpp_smsc_alloc_init(void *ctx)
+{
+ struct smsc *smsc = talloc_zero(ctx, struct smsc);
+
+ 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;
+
+ return smsc;
+}
+
+/*! \brief Set the SMPP address and port without binding. */
+int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port)
+{
+ smsc->listen_port = port;
+
+ /* Avoid use-after-free if bind_addr == smsc->bind_addr */
+ if (smsc->bind_addr == bind_addr)
+ return 0;
+ osmo_talloc_replace_string(smsc, &smsc->bind_addr, bind_addr);
+
+ return 0;
+}
+
+/*! \brief Bind to given address and port and accept connections.
+ * \param[in] bind_addr Local IP address, may be NULL for any.
+ * \param[in] port TCP port number, may be 0 for default SMPP (2775).
+ */
+int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port)
+{
+ int rc;
+
+ /* default port for SMPP */
+ if (!port)
+ port = 2775;
+
+ LOGP(DSMPP, LOGL_NOTICE, "SMPP at %s %d\n",
+ bind_addr? bind_addr : "0.0.0.0", port);
+
+ rc = osmo_sock_init_ofd(&smsc->listen_ofd, AF_UNSPEC, SOCK_STREAM,
+ IPPROTO_TCP, bind_addr, port,
+ OSMO_SOCK_F_BIND);
+ if (rc < 0)
+ return rc;
+
+ /* store new address and port */
+ rc = smpp_smsc_conf(smsc, bind_addr, port);
+ if (rc)
+ smpp_smsc_stop(smsc);
+ return rc;
+}
+
+/*! \brief Change a running connection to a different address/port, and upon
+ * error switch back to the running configuration. */
+int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port)
+{
+ int rc;
+
+ smpp_smsc_stop(smsc);
+
+ rc = smpp_smsc_start(smsc, bind_addr, port);
+ if (rc)
+ /* if there is an error, try to re-bind to the old port */
+ return smpp_smsc_start(smsc, smsc->bind_addr, smsc->listen_port);
+ return 0;
+}
+
+/*! /brief Close SMPP connection. */
+void smpp_smsc_stop(struct smsc *smsc)
+{
+ if (smsc->listen_ofd.fd > 0) {
+ close(smsc->listen_ofd.fd);
+ smsc->listen_ofd.fd = 0;
+ osmo_fd_unregister(&smsc->listen_ofd);
+ }
+}
diff --git a/src/libsmpputil/smpp_utils.c b/src/libsmpputil/smpp_utils.c
new file mode 100644
index 000000000..bada97217
--- /dev/null
+++ b/src/libsmpputil/smpp_utils.c
@@ -0,0 +1,174 @@
+
+/* (C) 2012-2022 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <time.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/netif/stream.h>
+#include <osmocom/smpp/smpp_smsc.h>
+
+/*! \brief retrieve SMPP command ID from a msgb */
+uint32_t smpp_msgb_cmdid(struct msgb *msg)
+{
+ uint8_t *tmp = msgb_data(msg) + 4;
+ return ntohl(*(uint32_t *)tmp);
+}
+
+int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode)
+{
+ if ((dcs & 0xF0) == 0xF0) {
+ if (dcs & 0x04) {
+ /* bit 2 == 1: 8bit data */
+ *data_coding = 0x02;
+ *mode = MODE_8BIT;
+ } else {
+ /* bit 2 == 0: default alphabet */
+ *data_coding = 0x01;
+ *mode = MODE_7BIT;
+ }
+ } else if ((dcs & 0xE0) == 0) {
+ switch (dcs & 0xC) {
+ case 0:
+ *data_coding = 0x01;
+ *mode = MODE_7BIT;
+ break;
+ case 4:
+ *data_coding = 0x02;
+ *mode = MODE_8BIT;
+ break;
+ case 8:
+ *data_coding = 0x08; /* UCS-2 */
+ *mode = MODE_8BIT;
+ break;
+ default:
+ goto unknown_mo;
+ }
+ } else {
+unknown_mo:
+ LOGP(DLSMS, LOGL_ERROR, "SMPP MO Unknown Data Coding 0x%02x\n", dcs);
+ return -1;
+ }
+
+ return 0;
+
+}
+
+/* convert a 'struct tm' holding relative time to an absolute one by adding it to t_now */
+static void relative2absolute(struct tm *tm, time_t t_now)
+{
+ struct tm tm_now;
+
+ localtime_r(&t_now, &tm_now);
+
+ tm->tm_year += tm_now.tm_year;
+ tm->tm_mon += tm_now.tm_mon;
+ tm->tm_mday += tm_now.tm_mday;
+ tm->tm_hour += tm_now.tm_hour;
+ tm->tm_min += tm_now.tm_min;
+ tm->tm_sec += tm_now.tm_sec;
+}
+
+#ifndef HAVE_TIMEGM
+/* for systems without a timegm() function, provide a reimplementation */
+static time_t timegm(struct tm *tm)
+{
+ const char *orig_tz = getenv("TZ");
+ time_t ret;
+
+ setenv("TZ", "UTC", 1);
+
+ ret = mktime(tm);
+
+ if (orig_tz)
+ setenv("TZ", orig_tz, 1);
+ else
+ unsetenv("TZ");
+
+ return ret;
+}
+#endif
+
+
+/*! Parse a SMPP time format as defined in SMPP v3.4 7.1.1.
+ * \param[in] vp string containing the time as encoded in SMPP v3.4
+ * \param[in] t_now pointer to a time value for 'now'. Can be NULL, then we call time() ourselves.
+ * \returns time_t value in seconds since the epoch of the absolute decoded time */
+time_t smpp_parse_time_format(const char *vp, time_t *t_now)
+{
+ unsigned int year, month, day, hour, minute, second, tenth, gmt_off_quarter;
+ char plus_minus_relative;
+ int gmt_off_minutes;
+ struct tm tm;
+ time_t ret;
+ int rc;
+
+ memset(&tm, 0, sizeof(tm));
+
+ if (vp[0] == '\0')
+ return 0;
+
+ /* YYMMDDhhmmsstnnp (where p can be -, + or R) */
+ rc = sscanf(vp, "%2u%2u%2u%2u%2u%2u%1u%2u%c", &year, &month, &day, &hour, &minute,
+ &second, &tenth, &gmt_off_quarter, &plus_minus_relative);
+ if (rc != 9)
+ return (time_t) -1;
+
+ tm.tm_year = year;
+ /* month handling differs between absolute/relative below... */
+ tm.tm_mday = day;
+ tm.tm_hour = hour;
+ tm.tm_min = minute;
+ tm.tm_sec = second;
+ tm.tm_isdst = 0;
+
+ switch (plus_minus_relative) {
+ case '+': /* time is in quarter hours advanced compared to UTC */
+ if (year < 70)
+ tm.tm_year += 100;
+ tm.tm_mon = month - 1;
+ gmt_off_minutes = 15 * gmt_off_quarter;
+ tm.tm_min -= gmt_off_minutes;
+ ret = timegm(&tm);
+ break;
+ case '-': /* time is in quarter hours retared compared to UTC */
+ if (year < 70)
+ tm.tm_year += 100;
+ tm.tm_mon = month - 1;
+ gmt_off_minutes = 15 * gmt_off_quarter;
+ tm.tm_min += gmt_off_minutes;
+ ret = timegm(&tm);
+ break;
+ case 'R':
+ /* relative time */
+ tm.tm_mon = month;
+ if (t_now)
+ relative2absolute(&tm, *t_now);
+ else
+ relative2absolute(&tm, time(NULL));
+ /* here we do want local time, as we're passing local time in above! */
+ ret = mktime(&tm);
+ break;
+ default:
+ return (time_t) -1;
+ }
+
+ return ret;
+}
diff --git a/src/libsmpputil/smpp_vty.c b/src/libsmpputil/smpp_vty.c
new file mode 100644
index 000000000..40514d490
--- /dev/null
+++ b/src/libsmpputil/smpp_vty.c
@@ -0,0 +1,629 @@
+/* SMPP vty interface */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/msc/vty.h>
+#include <osmocom/smpp/smpp_smsc.h>
+
+struct smsc *smsc_from_vty(struct vty *v);
+
+static struct cmd_node smpp_node = {
+ SMPP_NODE,
+ "%s(config-smpp)# ",
+ 1,
+};
+
+static struct cmd_node esme_node = {
+ SMPP_ESME_NODE,
+ "%s(config-smpp-esme)# ",
+ 1,
+};
+
+DEFUN(cfg_smpp, cfg_smpp_cmd,
+ "smpp", "Configure SMPP SMS Interface")
+{
+ vty->node = SMPP_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smpp_first, cfg_smpp_first_cmd,
+ "smpp-first",
+ "Try SMPP routes before the subscriber DB\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ smsc->smpp_first = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_smpp_first, cfg_no_smpp_first_cmd,
+ "no smpp-first",
+ NO_STR "Try SMPP before routes before the subscriber DB\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ smsc->smpp_first = 0;
+ return CMD_SUCCESS;
+}
+
+static int smpp_local_tcp(struct vty *vty,
+ const char *bind_addr, uint16_t port)
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ int is_running = smsc->listen_ofd.fd > 0;
+ int same_bind_addr;
+ int rc;
+
+ /* If it is not up yet, don't rebind, just set values. */
+ if (!is_running) {
+ rc = smpp_smsc_conf(smsc, bind_addr, port);
+ if (rc < 0) {
+ vty_out(vty, "%% Cannot configure new address:port%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+ }
+
+ rc = smpp_smsc_restart(smsc, bind_addr, port);
+ if (rc < 0) {
+ vty_out(vty, "%% Cannot bind to new port %s:%u nor to"
+ " old port %s:%u%s",
+ bind_addr? bind_addr : "0.0.0.0",
+ port,
+ smsc->bind_addr? smsc->bind_addr : "0.0.0.0",
+ smsc->listen_port,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ same_bind_addr = (bind_addr == smsc->bind_addr)
+ || (bind_addr && smsc->bind_addr
+ && (strcmp(bind_addr, smsc->bind_addr) == 0));
+
+ if (!same_bind_addr || port != smsc->listen_port) {
+ vty_out(vty, "%% Cannot bind to new port %s:%u, staying on"
+ " old port %s:%u%s",
+ bind_addr? bind_addr : "0.0.0.0",
+ port,
+ smsc->bind_addr? smsc->bind_addr : "0.0.0.0",
+ smsc->listen_port,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smpp_port, cfg_smpp_port_cmd,
+ "local-tcp-port <1-65535>",
+ "Set the local TCP port on which we listen for SMPP\n"
+ "TCP port number")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ uint16_t port = atoi(argv[0]);
+ return smpp_local_tcp(vty, smsc->bind_addr, port);
+}
+
+DEFUN(cfg_smpp_addr_port, cfg_smpp_addr_port_cmd,
+ "local-tcp-ip A.B.C.D <1-65535>",
+ "Set the local IP address and TCP port on which we listen for SMPP\n"
+ "Local IP address\n"
+ "TCP port number")
+{
+ const char *bind_addr = argv[0];
+ uint16_t port = atoi(argv[1]);
+ return smpp_local_tcp(vty, bind_addr, port);
+}
+
+DEFUN(cfg_smpp_sys_id, cfg_smpp_sys_id_cmd,
+ "system-id ID",
+ "Set the System ID of this SMSC\n"
+ "Alphanumeric SMSC System ID\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+
+ if (strlen(argv[0])+1 > sizeof(smsc->system_id))
+ return CMD_WARNING;
+
+ strcpy(smsc->system_id, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smpp_policy, cfg_smpp_policy_cmd,
+ "policy (accept-all|closed)",
+ "Set the authentication policy of this SMSC\n"
+ "Accept all SMPP connections independent of system ID / password\n"
+ "Accept only SMPP connections from ESMEs explicitly configured")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+
+ if (!strcmp(argv[0], "accept-all"))
+ smsc->accept_all = 1;
+ else
+ smsc->accept_all = 0;
+
+ return CMD_SUCCESS;
+}
+
+
+static int config_write_smpp(struct vty *vty)
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+
+ vty_out(vty, "smpp%s", VTY_NEWLINE);
+ if (smsc->bind_addr)
+ vty_out(vty, " local-tcp-ip %s %u%s", smsc->bind_addr,
+ smsc->listen_port, VTY_NEWLINE);
+ else
+ vty_out(vty, " local-tcp-port %u%s", smsc->listen_port,
+ VTY_NEWLINE);
+ if (strlen(smsc->system_id) > 0)
+ vty_out(vty, " system-id %s%s", smsc->system_id, VTY_NEWLINE);
+ vty_out(vty, " policy %s%s",
+ smsc->accept_all ? "accept-all" : "closed", VTY_NEWLINE);
+ vty_out(vty, " %ssmpp-first%s",
+ smsc->smpp_first ? "" : "no ", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme, cfg_esme_cmd,
+ "esme NAME",
+ "Configure a particular ESME\n"
+ "Alphanumeric System ID of the ESME to be configured\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ struct osmo_smpp_acl *acl;
+ const char *id = argv[0];
+
+ if (strlen(id) > 16) {
+ vty_out(vty, "%% System ID cannot be more than 16 "
+ "characters long%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ acl = smpp_acl_by_system_id(smsc, id);
+ if (!acl) {
+ acl = smpp_acl_alloc(smsc, id);
+ if (!acl)
+ return CMD_WARNING;
+ }
+
+ vty->index = acl;
+ vty->index_sub = &acl->description;
+ vty->node = SMPP_ESME_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_esme, cfg_no_esme_cmd,
+ "no esme NAME",
+ NO_STR "Remove ESME configuration\n"
+ "Alphanumeric System ID of the ESME to be removed\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ struct osmo_smpp_acl *acl;
+ const char *id = argv[0];
+
+ acl = smpp_acl_by_system_id(smsc, id);
+ if (!acl) {
+ vty_out(vty, "%% ESME with system id '%s' unknown%s",
+ id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* FIXME: close the connection, free data structure, etc. */
+
+ smpp_acl_delete(acl);
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_esme_passwd, cfg_esme_passwd_cmd,
+ "password PASSWORD",
+ "Set the password for this ESME\n"
+ "Alphanumeric password string\n")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ if (strlen(argv[0])+1 > sizeof(acl->passwd))
+ return CMD_WARNING;
+
+ strcpy(acl->passwd, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_passwd, cfg_esme_no_passwd_cmd,
+ "no password",
+ NO_STR "Remove the password for this ESME\n")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ memset(acl->passwd, 0, sizeof(acl->passwd));
+
+ return CMD_SUCCESS;
+}
+
+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 SMPP_ROUTE_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 "Unknown type-of-number\n" \
+ "International type-of-number\n" \
+ "National type-of-number\n" \
+ "Network specific type-of-number\n" \
+ "Subscriber type-of-number\n" \
+ "Alphanumeric type-of-number\n" \
+ "Abbreviated type-of-number\n"
+#define NPI_STR "Unknown numbering plan\n" \
+ "ISDN (E.164) numbering plan\n" \
+ "X.121 numbering plan\n" \
+ "F.69 numbering plan\n" \
+ "E.212 numbering plan\n" \
+ "National numbering plan\n" \
+ "Private numbering plan\n" \
+ "ERMES numbering plan\n" \
+ "IP numbering plan\n" \
+ "WAP numbeing plan\n"
+
+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;
+
+ /* 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")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->default_route = 1;
+
+ if (!acl->smsc->def_route)
+ acl->smsc->def_route = acl;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_esme_defaultroute, cfg_esme_no_defaultroute_cmd,
+ "no default-route", NO_STR
+ "Remove this ESME as default-route for all SMS to unknown destinations")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->default_route = 0;
+
+ /* remove currently active default route, if it was created by
+ * this ACL */
+ if (acl->smsc->def_route && acl->smsc->def_route == acl)
+ acl->smsc->def_route = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_del_src_imsi, cfg_esme_del_src_imsi_cmd,
+ "deliver-src-imsi",
+ "Enable the use of IMSI as source address in DELIVER")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->deliver_src_imsi = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_del_src_imsi, cfg_esme_no_del_src_imsi_cmd,
+ "no deliver-src-imsi", NO_STR
+ "Disable the use of IMSI as source address in DELIVER")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->deliver_src_imsi = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_osmo_ext, cfg_esme_osmo_ext_cmd,
+ "osmocom-extensions",
+ "Enable the use of Osmocom SMPP Extensions for this ESME")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->osmocom_ext = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_osmo_ext, cfg_esme_no_osmo_ext_cmd,
+ "no osmocom-extensions", NO_STR
+ "Disable the use of Osmocom SMPP Extensions for this ESME")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->osmocom_ext = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_dcs_transp, cfg_esme_dcs_transp_cmd,
+ "dcs-transparent",
+ "Enable the transparent pass-through of TP-DCS to SMPP DataCoding")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->dcs_transparent = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_dcs_transp, cfg_esme_no_dcs_transp_cmd,
+ "no dcs-transparent", NO_STR
+ "Disable the transparent pass-through of TP-DCS to SMPP DataCoding")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->dcs_transparent = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_alert_notif, cfg_esme_alert_notif_cmd,
+ "alert-notifications",
+ "Enable sending of SMPP Alert Notifications for this ESME")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->alert_notifications = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_esme_no_alert_notif, cfg_esme_no_alert_notif_cmd,
+ "no alert-notifications", NO_STR
+ "Disable sending of SMPP Alert Notifications for this ESME")
+{
+ struct osmo_smpp_acl *acl = vty->index;
+
+ acl->alert_notifications = 0;
+
+ return CMD_SUCCESS;
+}
+
+
+static void dump_one_esme(struct vty *vty, struct osmo_esme *esme)
+{
+ vty_out(vty, "ESME System ID: %s, Password: %s, SMPP Version %02x%s",
+ esme->system_id, esme->acl ? esme->acl->passwd : "",
+ esme->smpp_version, VTY_NEWLINE);
+ vty_out(vty, " Connection %s%s", osmo_sock_get_name(tall_vty_ctx, esme->wqueue.bfd.fd), 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,
+ "show smpp esme",
+ SHOW_STR "SMPP Interface\n" "SMPP External SMS Entity\n")
+{
+ struct smsc *smsc = smsc_from_vty(vty);
+ struct osmo_esme *esme;
+
+ llist_for_each_entry(esme, &smsc->esme_list, list)
+ dump_one_esme(vty, esme);
+
+ 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);
+ if (acl->osmocom_ext)
+ vty_out(vty, " osmocom-extensions%s", VTY_NEWLINE);
+ if (acl->dcs_transparent)
+ vty_out(vty, " dcs-transparent%s", VTY_NEWLINE);
+ if (!acl->alert_notifications)
+ vty_out(vty, " no alert-notifications%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)
+{
+ struct smsc *smsc = smsc_from_vty(v);
+ struct osmo_smpp_acl *acl;
+
+ llist_for_each_entry(acl, &smsc->acl_list, list)
+ config_write_esme_single(v, acl);
+
+ return CMD_SUCCESS;
+}
+
+int smpp_vty_init(void)
+{
+ install_node(&smpp_node, config_write_smpp);
+ install_element(CONFIG_NODE, &cfg_smpp_cmd);
+
+ install_element(SMPP_NODE, &cfg_smpp_first_cmd);
+ install_element(SMPP_NODE, &cfg_no_smpp_first_cmd);
+ install_element(SMPP_NODE, &cfg_smpp_port_cmd);
+ install_element(SMPP_NODE, &cfg_smpp_addr_port_cmd);
+ install_element(SMPP_NODE, &cfg_smpp_sys_id_cmd);
+ install_element(SMPP_NODE, &cfg_smpp_policy_cmd);
+ install_element(SMPP_NODE, &cfg_esme_cmd);
+ install_element(SMPP_NODE, &cfg_no_esme_cmd);
+
+ install_node(&esme_node, config_write_esme);
+ 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_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, &cfg_esme_del_src_imsi_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_no_del_src_imsi_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_osmo_ext_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_no_osmo_ext_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_dcs_transp_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_no_dcs_transp_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_alert_notif_cmd);
+ install_element(SMPP_ESME_NODE, &cfg_esme_no_alert_notif_cmd);
+
+ install_element_ve(&show_esme_cmd);
+
+ return 0;
+}