From 5346f69a543eab087380be117d625bbaf82ebd4b Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 28 Jul 2022 14:19:22 +0700 Subject: Introduce libsmpputil As part of preparation for libosmo-netif migration let's move common SMPP code into separate build-time library and use it for both smpp_mirror and OsmoMSC renaming the files if necessary. While at it we also fix id/password legth limits in smpp_mirror and drop unused fields from ESME struct. Related: OS#5568 Change-Id: I61910651bc7c188dc2fb67d96189a66a47e7e8fb --- configure.ac | 2 + include/osmocom/Makefile.am | 1 + include/osmocom/msc/Makefile.am | 1 - include/osmocom/msc/smpp.h | 4 - include/osmocom/smpp/Makefile.am | 4 + include/osmocom/smpp/smpp.h | 31 ++ include/osmocom/smpp/smpp_smsc.h | 158 ++++++ src/Makefile.am | 10 +- src/libmsc/Makefile.am | 16 - src/libmsc/gsm_04_11.c | 3 +- src/libmsc/smpp_openbsc.c | 865 ------------------------------- src/libmsc/smpp_smsc.c | 1050 -------------------------------------- src/libmsc/smpp_smsc.h | 167 ------ src/libmsc/smpp_utils.c | 166 ------ src/libmsc/smpp_vty.c | 630 ----------------------- src/libsmpputil/Makefile.am | 25 + src/libsmpputil/smpp_msc.c | 863 +++++++++++++++++++++++++++++++ src/libsmpputil/smpp_smsc.c | 1026 +++++++++++++++++++++++++++++++++++++ src/libsmpputil/smpp_utils.c | 174 +++++++ src/libsmpputil/smpp_vty.c | 629 +++++++++++++++++++++++ src/osmo-msc/Makefile.am | 10 +- src/osmo-msc/msc_main.c | 2 +- src/utils/Makefile.am | 4 + src/utils/smpp_mirror.c | 25 +- tests/db_sms/Makefile.am | 2 - tests/msc_vlr/Makefile.am | 2 - tests/smpp/Makefile.am | 4 +- tests/smpp/smpp_test.c | 4 +- tests/sms_queue/Makefile.am | 1 - tests/stubs.c | 8 + 30 files changed, 2950 insertions(+), 2937 deletions(-) delete mode 100644 include/osmocom/msc/smpp.h create mode 100644 include/osmocom/smpp/Makefile.am create mode 100644 include/osmocom/smpp/smpp.h create mode 100644 include/osmocom/smpp/smpp_smsc.h delete mode 100644 src/libmsc/smpp_openbsc.c delete mode 100644 src/libmsc/smpp_smsc.c delete mode 100644 src/libmsc/smpp_smsc.h delete mode 100644 src/libmsc/smpp_utils.c delete mode 100644 src/libmsc/smpp_vty.c create mode 100644 src/libsmpputil/Makefile.am create mode 100644 src/libsmpputil/smpp_msc.c create mode 100644 src/libsmpputil/smpp_smsc.c create mode 100644 src/libsmpputil/smpp_utils.c create mode 100644 src/libsmpputil/smpp_vty.c diff --git a/configure.ac b/configure.ac index 21056f913..41e51dad6 100644 --- a/configure.ac +++ b/configure.ac @@ -234,9 +234,11 @@ AC_OUTPUT( include/Makefile include/osmocom/Makefile include/osmocom/msc/Makefile + include/osmocom/smpp/Makefile src/Makefile src/libmsc/Makefile src/libvlr/Makefile + src/libsmpputil/Makefile src/osmo-msc/Makefile src/utils/Makefile tests/Makefile diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am index 4d8063711..b07a00459 100644 --- a/include/osmocom/Makefile.am +++ b/include/osmocom/Makefile.am @@ -1,3 +1,4 @@ SUBDIRS = \ msc \ + smpp \ $(NULL) diff --git a/include/osmocom/msc/Makefile.am b/include/osmocom/msc/Makefile.am index faf1048dc..adda44c76 100644 --- a/include/osmocom/msc/Makefile.am +++ b/include/osmocom/msc/Makefile.am @@ -45,7 +45,6 @@ noinst_HEADERS = \ sgs_vty.h \ signal.h \ silent_call.h \ - smpp.h \ sms_queue.h \ transaction.h \ vlr.h \ diff --git a/include/osmocom/msc/smpp.h b/include/osmocom/msc/smpp.h deleted file mode 100644 index bcdac8f0b..000000000 --- a/include/osmocom/msc/smpp.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -int smpp_openbsc_alloc_init(void *ctx); -int smpp_openbsc_start(struct gsm_network *net); diff --git a/include/osmocom/smpp/Makefile.am b/include/osmocom/smpp/Makefile.am new file mode 100644 index 000000000..5ad34ad0d --- /dev/null +++ b/include/osmocom/smpp/Makefile.am @@ -0,0 +1,4 @@ +noinst_HEADERS = \ + smpp.h \ + smpp_smsc.h \ + $(NULL) diff --git a/include/osmocom/smpp/smpp.h b/include/osmocom/smpp/smpp.h new file mode 100644 index 000000000..cc0e8003a --- /dev/null +++ b/include/osmocom/smpp/smpp.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +/* Length limits according to SMPP 3.4 spec including NUL-byte: */ +#define SMPP_SYS_ID_LEN 15 +#define SMPP_PASSWD_LEN 8 + +enum esme_read_state { + READ_ST_IN_LEN = 0, + READ_ST_IN_MSG = 1, +}; + +/*! \brief Ugly wrapper. libsmpp34 should do this itself! */ +#define SMPP34_UNPACK(rc, type, str, data, len) { \ + memset(str, 0, sizeof(*str)); \ + rc = smpp34_unpack(type, str, data, len); } + +#define PACK_AND_SEND(esme, ptr) pack_and_send(esme, (ptr)->command_id, ptr) + +/*! \brief initialize the libsmpp34 data structure for a response */ +#define INIT_RESP(type, resp, req) { \ + memset((resp), 0, sizeof(*(resp))); \ + (resp)->command_length = 0; \ + (resp)->command_id = type; \ + (resp)->command_status = ESME_ROK; \ + (resp)->sequence_number = (req)->sequence_number; } + +uint32_t smpp_msgb_cmdid(struct msgb *msg); +int smpp_openbsc_alloc_init(void *ctx); +int smpp_openbsc_start(struct gsm_network *net); diff --git a/include/osmocom/smpp/smpp_smsc.h b/include/osmocom/smpp/smpp_smsc.h new file mode 100644 index 000000000..fb5164faf --- /dev/null +++ b/include/osmocom/smpp/smpp_smsc.h @@ -0,0 +1,158 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MODE_7BIT 7 +#define MODE_8BIT 8 + +struct msc_a; + +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; + struct osmo_smpp_acl *acl; + int use; + + struct llist_head smpp_cmd_list; + + uint32_t own_seq_nr; + + struct osmo_wqueue wqueue; + + enum esme_read_state read_state; + uint32_t read_len; + uint32_t read_idx; + struct msgb *read_msg; + + uint8_t smpp_version; + char system_id[SMPP_SYS_ID_LEN+1]; + + uint8_t bind_flags; +}; + +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; + int osmocom_ext; + int dcs_transparent; + int alert_notifications; + 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 osmo_smpp_cmd { + struct llist_head list; + struct vlr_subscr *vsub; + uint32_t sequence_nr; + uint32_t gsm411_msg_ref; + uint8_t gsm411_trans_id; + bool is_report; + struct osmo_timer_list response_timer; +}; + +struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme, + uint32_t sequence_number); +void smpp_cmd_ack(struct osmo_smpp_cmd *cmd); +void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status); +void smpp_cmd_flush_pending(struct osmo_esme *esme); + +struct smsc { + struct osmo_fd listen_ofd; + struct llist_head esme_list; + struct llist_head acl_list; + struct llist_head route_list; + char *bind_addr; + uint16_t listen_port; + char system_id[SMPP_SYS_ID_LEN+1]; + int accept_all; + int smpp_first; + struct osmo_smpp_acl *def_route; + void *priv; +}; + +int smpp_addr_eq(const struct osmo_smpp_addr *a, + const struct osmo_smpp_addr *b); + +struct smsc *smpp_smsc_alloc_init(void *ctx); +int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port); +int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port); +int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port); +void smpp_smsc_stop(struct smsc *smsc); + +void smpp_esme_get(struct osmo_esme *esme); +void smpp_esme_put(struct osmo_esme *esme); + +int smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest, struct osmo_esme **emse); + +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); +void smpp_acl_delete(struct osmo_smpp_acl *acl); + +int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr, + uint32_t command_status, char *msg_id); + +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); + +int smpp_vty_init(void); + +int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode); + +time_t smpp_parse_time_format(const char *vp, time_t *t_now); + + +struct gsm_sms; +struct ran_conn; + +bool smpp_route_smpp_first(); +int smpp_try_deliver(struct gsm_sms *sms, struct msc_a *msc_a); diff --git a/src/Makefile.am b/src/Makefile.am index 4e7cea19b..e23ab102c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -25,8 +25,16 @@ SUBDIRS = \ libmsc \ $(NULL) +if BUILD_SMPP + +SUBDIRS += \ + libsmpputil \ + utils \ + $(NULL) + +endif + # Programs SUBDIRS += \ osmo-msc \ - utils \ $(NULL) diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am index d3b6035e2..de02a1779 100644 --- a/src/libmsc/Makefile.am +++ b/src/libmsc/Makefile.am @@ -20,9 +20,6 @@ AM_CFLAGS = \ $(LIBOSMONETIF_CFLAGS) \ $(NULL) -noinst_HEADERS = \ - $(NULL) - noinst_LIBRARIES = \ libmsc.a \ $(NULL) @@ -81,16 +78,3 @@ libmsc_a_SOURCES += \ ran_msg_iu.c \ $(NULL) endif - -if BUILD_SMPP -noinst_HEADERS += \ - smpp_smsc.h \ - $(NULL) - -libmsc_a_SOURCES += \ - smpp_smsc.c \ - smpp_openbsc.c \ - smpp_vty.c \ - smpp_utils.c \ - $(NULL) -endif diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c index 743e2722b..adc9d88c8 100644 --- a/src/libmsc/gsm_04_11.c +++ b/src/libmsc/gsm_04_11.c @@ -58,7 +58,7 @@ #include #ifdef BUILD_SMPP -#include "smpp_smsc.h" +#include #endif void *tall_gsms_ctx; @@ -1389,4 +1389,3 @@ void gsm411_sapi_n_reject(struct msc_a *msc_a) trans_free(trans); } } - diff --git a/src/libmsc/smpp_openbsc.c b/src/libmsc/smpp_openbsc.c deleted file mode 100644 index 91666a9ca..000000000 --- a/src/libmsc/smpp_openbsc.c +++ /dev/null @@ -1,865 +0,0 @@ -/* OpenBSC SMPP 3.4 interface, SMSC-side implementation */ - -/* (C) 2012-2022 by Harald Welte - * - * 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 . - */ - - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "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/libmsc/smpp_smsc.c b/src/libmsc/smpp_smsc.c deleted file mode 100644 index da6607421..000000000 --- a/src/libmsc/smpp_smsc.c +++ /dev/null @@ -1,1050 +0,0 @@ -/* SMPP 3.4 interface, SMSC-side implementation */ -/* (C) 2012 by Harald Welte - * - * 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 . - */ - - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "smpp_smsc.h" - -#include -#include - -/*! \brief Ugly wrapper. libsmpp34 should do this itself! */ -#define SMPP34_UNPACK(rc, type, str, data, len) \ - memset(str, 0, sizeof(*str)); \ - rc = smpp34_unpack(type, str, data, len) - -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 initialize the libsmpp34 data structure for a response */ -#define INIT_RESP(type, resp, req) { \ - memset((resp), 0, sizeof(*(resp))); \ - (resp)->command_length = 0; \ - (resp)->command_id = type; \ - (resp)->command_status = ESME_ROK; \ - (resp)->sequence_number = (req)->sequence_number; \ -} - -/*! \brief pack a libsmpp34 data strcutrure and send it to the ESME */ -#define PACK_AND_SEND(esme, ptr) pack_and_send(esme, (ptr)->command_id, ptr) -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 command ID from a msgb */ -static inline uint32_t smpp_msgb_cmdid(struct msgb *msg) -{ - uint8_t *tmp = msgb_data(msg) + 4; - return ntohl(*(uint32_t *)tmp); -} - -/*! \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/libmsc/smpp_smsc.h b/src/libmsc/smpp_smsc.h deleted file mode 100644 index 76772a3ee..000000000 --- a/src/libmsc/smpp_smsc.h +++ /dev/null @@ -1,167 +0,0 @@ -#ifndef _SMPP_SMSC_H -#define _SMPP_SMSC_H - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#define SMPP_SYS_ID_LEN 15 -#define SMPP_PASSWD_LEN 8 - -#define MODE_7BIT 7 -#define MODE_8BIT 8 - -struct msc_a; - -enum esme_read_state { - READ_ST_IN_LEN = 0, - READ_ST_IN_MSG = 1, -}; - -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; - struct osmo_smpp_acl *acl; - int use; - - struct llist_head smpp_cmd_list; - - uint32_t own_seq_nr; - - struct osmo_wqueue wqueue; - - enum esme_read_state read_state; - uint32_t read_len; - uint32_t read_idx; - struct msgb *read_msg; - - uint8_t smpp_version; - char system_id[SMPP_SYS_ID_LEN+1]; - - uint8_t bind_flags; -}; - -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; - int osmocom_ext; - int dcs_transparent; - int alert_notifications; - 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 osmo_smpp_cmd { - struct llist_head list; - struct vlr_subscr *vsub; - uint32_t sequence_nr; - uint32_t gsm411_msg_ref; - uint8_t gsm411_trans_id; - bool is_report; - struct osmo_timer_list response_timer; -}; - -struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme, - uint32_t sequence_number); -void smpp_cmd_ack(struct osmo_smpp_cmd *cmd); -void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status); -void smpp_cmd_flush_pending(struct osmo_esme *esme); - -struct smsc { - struct osmo_fd listen_ofd; - struct llist_head esme_list; - struct llist_head acl_list; - struct llist_head route_list; - char *bind_addr; - uint16_t listen_port; - char system_id[SMPP_SYS_ID_LEN+1]; - int accept_all; - int smpp_first; - struct osmo_smpp_acl *def_route; - void *priv; -}; - -int smpp_addr_eq(const struct osmo_smpp_addr *a, - const struct osmo_smpp_addr *b); - -struct smsc *smpp_smsc_alloc_init(void *ctx); -int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port); -int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port); -int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port); -void smpp_smsc_stop(struct smsc *smsc); - -void smpp_esme_get(struct osmo_esme *esme); -void smpp_esme_put(struct osmo_esme *esme); - -int smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest, struct osmo_esme **emse); - -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); -void smpp_acl_delete(struct osmo_smpp_acl *acl); - -int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr, - uint32_t command_status, char *msg_id); - -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); - -int smpp_vty_init(void); - -int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode); - -time_t smpp_parse_time_format(const char *vp, time_t *t_now); - - -struct gsm_sms; -struct ran_conn; - -bool smpp_route_smpp_first(); -int smpp_try_deliver(struct gsm_sms *sms, struct msc_a *msc_a); -#endif diff --git a/src/libmsc/smpp_utils.c b/src/libmsc/smpp_utils.c deleted file mode 100644 index 3c7757b9a..000000000 --- a/src/libmsc/smpp_utils.c +++ /dev/null @@ -1,166 +0,0 @@ - -/* (C) 2012-2022 by Harald Welte - * - * 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 . - */ - -#include "config.h" - -#include - -#include "smpp_smsc.h" -#include - -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/libmsc/smpp_vty.c b/src/libmsc/smpp_vty.c deleted file mode 100644 index da260a895..000000000 --- a/src/libmsc/smpp_vty.c +++ /dev/null @@ -1,630 +0,0 @@ -/* SMPP vty interface */ - -/* (C) 2012 by Harald Welte - * 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 . - * - */ - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include "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; -} 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 + * + * 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 . + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 . + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 + * + * 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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +/*! \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 + * 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 . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +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; +} diff --git a/src/osmo-msc/Makefile.am b/src/osmo-msc/Makefile.am index cdc3448ab..0380d5d1f 100644 --- a/src/osmo-msc/Makefile.am +++ b/src/osmo-msc/Makefile.am @@ -43,7 +43,6 @@ osmo_msc_LDADD = \ $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOABIS_LIBS) \ $(LIBOSMONETIF_LIBS) \ - $(LIBSMPP34_LIBS) \ $(LIBOSMORANAP_LIBS) \ $(LIBASN1C_LIBS) \ $(LIBOSMOSIGTRAN_LIBS) \ @@ -52,3 +51,12 @@ osmo_msc_LDADD = \ $(LIBSQLITE3_LIBS) \ -lsctp \ $(NULL) + +if BUILD_SMPP + +osmo_msc_LDADD += \ + $(top_builddir)/src/libsmpputil/libsmpputil.a \ + $(LIBSMPP34_LIBS) \ + $(NULL) + +endif diff --git a/src/osmo-msc/msc_main.c b/src/osmo-msc/msc_main.c index bdffb4104..4ef5948b1 100644 --- a/src/osmo-msc/msc_main.c +++ b/src/osmo-msc/msc_main.c @@ -60,7 +60,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index cb0faf69f..75a292609 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -34,12 +34,16 @@ smpp_mirror_SOURCES = \ smpp_mirror_CFLAGS = \ $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOSCCP_CFLAGS) \ + $(LIBOSMOMGCPCLIENT_CFLAGS) \ $(LIBSMPP34_CFLAGS) \ $(NULL) smpp_mirror_LDADD = \ + $(top_builddir)/src/libsmpputil/libsmpputil.a \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMONETIF_LIBS) \ $(LIBSMPP34_LIBS) \ $(NULL) endif diff --git a/src/utils/smpp_mirror.c b/src/utils/smpp_mirror.c index 72d15e39d..359389513 100644 --- a/src/utils/smpp_mirror.c +++ b/src/utils/smpp_mirror.c @@ -19,18 +19,11 @@ #include #include +#include -/* FIXME: merge with smpp_smsc.c */ -#define SMPP_SYS_ID_LEN 16 -enum esme_read_state { - READ_ST_IN_LEN = 0, - READ_ST_IN_MSG = 1, -}; /* FIXME: merge with smpp_smsc.c */ struct esme { - struct osmo_fd ofd; - uint32_t own_seq_nr; struct osmo_wqueue wqueue; @@ -45,22 +38,6 @@ struct esme { }; /* FIXME: merge with smpp_smsc.c */ -#define SMPP34_UNPACK(rc, type, str, data, len) \ - memset(str, 0, sizeof(*str)); \ - rc = smpp34_unpack(type, str, data, len) -#define INIT_RESP(type, resp, req) { \ - memset((resp), 0, sizeof(*(resp))); \ - (resp)->command_length = 0; \ - (resp)->command_id = type; \ - (resp)->command_status = ESME_ROK; \ - (resp)->sequence_number = (req)->sequence_number; \ -} -#define PACK_AND_SEND(esme, ptr) pack_and_send(esme, (ptr)->command_id, ptr) -static inline uint32_t smpp_msgb_cmdid(struct msgb *msg) -{ - uint8_t *tmp = msgb_data(msg) + 4; - return ntohl(*(uint32_t *)tmp); -} static uint32_t esme_inc_seq_nr(struct esme *esme) { esme->own_seq_nr++; diff --git a/tests/db_sms/Makefile.am b/tests/db_sms/Makefile.am index 140ccbfaf..946d6a280 100644 --- a/tests/db_sms/Makefile.am +++ b/tests/db_sms/Makefile.am @@ -14,7 +14,6 @@ AM_CFLAGS = \ $(LIBOSMOSIGTRAN_CFLAGS) \ $(LIBOSMORANAP_CFLAGS) \ $(LIBOSMONETIF_CFLAGS) \ - $(LIBSMPP34_CFLAGS) \ $(LIBOSMOMGCPCLIENT_CFLAGS) \ $(LIBOSMOGSUPCLIENT_CFLAGS) \ $(LIBSQLITE3_CFLAGS) \ @@ -37,7 +36,6 @@ db_sms_test_SOURCES = \ db_sms_test_LDADD = \ $(top_builddir)/src/libmsc/libmsc.a \ $(top_builddir)/src/libvlr/libvlr.a \ - $(LIBSMPP34_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ diff --git a/tests/msc_vlr/Makefile.am b/tests/msc_vlr/Makefile.am index 505b5c7b6..2ba718085 100644 --- a/tests/msc_vlr/Makefile.am +++ b/tests/msc_vlr/Makefile.am @@ -8,7 +8,6 @@ AM_CFLAGS = \ -ggdb3 \ $(LIBOSMOCORE_CFLAGS) \ $(LIBOSMOGSM_CFLAGS) \ - $(LIBSMPP34_CFLAGS) \ $(LIBOSMOVTY_CFLAGS) \ $(LIBOSMOABIS_CFLAGS) \ $(LIBOSMOSIGTRAN_CFLAGS) \ @@ -32,7 +31,6 @@ AM_LDFLAGS = \ LDADD = \ $(top_builddir)/src/libmsc/libmsc.a \ $(top_builddir)/src/libvlr/libvlr.a \ - $(LIBSMPP34_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ diff --git a/tests/smpp/Makefile.am b/tests/smpp/Makefile.am index 00090daae..fabfd2da3 100644 --- a/tests/smpp/Makefile.am +++ b/tests/smpp/Makefile.am @@ -11,6 +11,8 @@ AM_CFLAGS = \ $(LIBOSMOGSM_CFLAGS) \ $(LIBOSMOSCCP_CFLAGS) \ $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOSCCP_CFLAGS) \ + $(LIBOSMOMGCPCLIENT_CFLAGS) \ $(COVERAGE_CFLAGS) \ $(LIBSMPP34_CFLAGS) \ $(NULL) @@ -30,10 +32,10 @@ check_PROGRAMS = \ smpp_test_SOURCES = \ smpp_test.c \ - $(top_builddir)/src/libmsc/smpp_utils.c \ $(NULL) smpp_test_LDADD = \ + $(top_builddir)/src/libsmpputil/libsmpputil.a \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(NULL) diff --git a/tests/smpp/smpp_test.c b/tests/smpp/smpp_test.c index 4640a5f8d..9b94c63f4 100644 --- a/tests/smpp/smpp_test.c +++ b/tests/smpp/smpp_test.c @@ -22,12 +22,10 @@ #include #include - +#include #include #include -#include "smpp_smsc.h" - struct coding_test { uint8_t dcs; uint8_t coding; diff --git a/tests/sms_queue/Makefile.am b/tests/sms_queue/Makefile.am index c83a9469c..d50a8d6db 100644 --- a/tests/sms_queue/Makefile.am +++ b/tests/sms_queue/Makefile.am @@ -36,7 +36,6 @@ sms_queue_test_SOURCES = \ sms_queue_test_LDADD = \ $(top_builddir)/src/libmsc/libmsc.a \ $(top_builddir)/src/libvlr/libvlr.a \ - $(LIBSMPP34_LIBS) \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ diff --git a/tests/stubs.c b/tests/stubs.c index e6ef548cc..f29a20146 100644 --- a/tests/stubs.c +++ b/tests/stubs.c @@ -43,3 +43,11 @@ char *osmo_stream_srv_link_get_sockname(const struct osmo_stream_srv_link *link) void osmo_stream_srv_link_set_port(struct osmo_stream_srv_link *link, uint16_t port) {} void osmo_stream_srv_link_set_addr(struct osmo_stream_srv_link *link, const char *addr) {} int sctp_recvmsg(int sd, void *msg, size_t len, void *from, void *fromlen, void *info, int *msg_flags) { return 0; } +struct gsm_sms; +struct msc_a; +struct gsm_trans; +struct osmo_esme; +bool smpp_route_smpp_first() { return false; } +void smpp_esme_put(struct osmo_esme *esme) { return; } +int smpp_try_deliver(struct gsm_sms *sms, struct msc_a *msc_a) { return 0; } +int sms_route_mt_sms(struct gsm_trans *trans, struct gsm_sms *gsms) { return 0; } -- cgit v1.2.3