blob: 8ee01f28a0af2ff8ef3b012771d848020dcc5ec1 [file] [log] [blame]
/* OsmoHLR SMS routing implementation */
/* (C) 2019 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/protocol/gsm_04_11.h>
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/hlr_sms.h>
#include <osmocom/hlr/hlr_ussd.h>
#include <osmocom/hlr/gsup_server.h>
#include <osmocom/hlr/gsup_router.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/db.h>
struct hlr_sms_route *sms_route_find(struct hlr *hlr,
enum hlr_sms_route_type type,
const char *pattern)
{
struct hlr_sms_route *rt;
llist_for_each_entry(rt, &hlr->sms_routes, list) {
if (rt->type != type)
continue;
if (!strcmp(rt->match_pattern, pattern))
return rt;
}
return NULL;
}
struct hlr_sms_route *sms_route_alloc(struct hlr *hlr,
enum hlr_sms_route_type type,
const char *pattern,
const struct hlr_euse *euse)
{
struct hlr_sms_route *rt;
if (sms_route_find(hlr, type, pattern))
return NULL;
rt = talloc(hlr, struct hlr_sms_route);
OSMO_ASSERT(rt != NULL);
rt->match_pattern = talloc_strdup(rt, pattern);
rt->type = type;
rt->euse = euse;
llist_add_tail(&rt->list, &hlr->sms_routes);
return rt;
}
void sms_route_del(struct hlr_sms_route *rt)
{
llist_del(&rt->list);
talloc_free(rt);
}
/* Common helper for preparing to be encoded GSUP message */
static void gsup_prepare_sm_error(struct osmo_gsup_message *msg,
const struct osmo_gsup_message *src_msg)
{
/* Init a mew GSUP message */
*msg = (struct osmo_gsup_message) {
.message_type = OSMO_GSUP_TO_MSGT_ERROR(src_msg->message_type),
.message_class = OSMO_GSUP_MESSAGE_CLASS_SMS,
.sm_rp_mr = src_msg->sm_rp_mr,
/* Swap optional source and destination addresses */
.destination_name_len = src_msg->source_name_len,
.destination_name = src_msg->source_name,
};
/* Fill in subscriber's IMSI */
OSMO_STRLCPY_ARRAY(msg->imsi, src_msg->imsi);
}
static int gsup_conn_enc_send(struct osmo_gsup_conn *conn,
struct osmo_gsup_message *msg)
{
struct msgb *gsup_msgb;
int rc;
gsup_msgb = msgb_alloc_headroom(512, 64, __func__);
if (!gsup_msgb) {
LOGP(DLSMS, LOGL_ERROR, "Failed to allocate a GSUP message\n");
return -ENOMEM;
}
rc = osmo_gsup_encode(gsup_msgb, msg);
if (rc) {
LOGP(DLSMS, LOGL_ERROR, "Failed to encode GSUP message '%s' (rc=%d)\n",
osmo_gsup_message_type_name(msg->message_type), rc);
return rc;
}
rc = osmo_gsup_conn_send(conn, gsup_msgb);
if (rc) {
LOGP(DLSMS, LOGL_ERROR, "Failed to send GSUP message '%s' (rc=%d)\n",
osmo_gsup_message_type_name(msg->message_type), rc);
return rc;
}
return 0;
}
/* Short Message delivery status, to be forwarded 'as-is' */
int forward_sm_res_or_err(struct osmo_gsup_conn *conn,
const struct osmo_gsup_message *gsup,
struct msgb *msg)
{
struct hlr_subscriber subscr;
char src_name_buf[32];
char dst_name_buf[32];
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr);
if (rc) {
LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' for unknown subscriber IMSI-%s\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi);
/* TODO: send some error back? */
msgb_free(msg);
return -ENODEV;
}
/* Make sure destination name is present */
if (gsup->destination_name == NULL || !gsup->destination_name_len) {
LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' (IMSI-%s) without destination name\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi);
/* TODO: send some error back? */
msgb_free(msg);
return -EINVAL;
}
/* Make sure source name is present */
if (gsup->source_name == NULL || !gsup->source_name_len) {
LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' (IMSI-%s) without source name\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi);
/* TODO: send some error back? */
msgb_free(msg);
return -EINVAL;
}
LOGP(DLSMS, LOGL_INFO, "Forward '%s' (IMSI-%s) from %s to %s\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi,
osmo_quote_str_buf2(src_name_buf, sizeof(src_name_buf),
(const char *) gsup->source_name,
gsup->source_name_len),
osmo_quote_str_buf2(dst_name_buf, sizeof(dst_name_buf),
(const char *) gsup->destination_name,
gsup->destination_name_len));
rc = osmo_gsup_addr_send(conn->server, gsup->destination_name,
gsup->destination_name_len, msg);
if (rc) {
LOGP(DLSMS, LOGL_NOTICE, "Failed to forward '%s' (IMSI-%s)\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi);
/* osmo_gsup_addr_send() free()d msg */
return rc;
}
return 0;
}
static const struct hlr_euse *find_euse(const struct hlr_subscriber *subscr,
const char *smsc_addr)
{
struct hlr_sms_route *rt;
const char *pattern;
/* Iterate over all known routes */
llist_for_each_entry(rt, &g_hlr->sms_routes, list) {
switch (rt->type) {
case HLR_SMS_RT_SMSC_ADDR:
pattern = smsc_addr;
break;
case HLR_SMS_RT_SENDER_MSISDN:
pattern = subscr->msisdn;
break;
case HLR_SMS_RT_SENDER_IMSI:
pattern = subscr->imsi;
break;
default:
/* Shall not happen, make Coverity happy */
continue;
}
if (strcmp(rt->match_pattern, pattern) == 0)
return rt->euse;
}
/* Fall-back to default route if nothing will be found */
return g_hlr->sms_euse_default;
}
static const struct osmo_gsup_conn *find_conn(struct osmo_gsup_server *srv,
const struct hlr_euse *euse)
{
char euse_addr[128];
int rc;
rc = snprintf(euse_addr, sizeof(euse_addr), "EUSE-%s", euse->name);
return gsup_route_find(srv, (uint8_t *) euse_addr, rc + 1);
}
/* Short Message from MSC/VLR towards SMSC */
int forward_mo_sms(struct osmo_gsup_conn *conn,
const struct osmo_gsup_message *gsup,
struct msgb *msg)
{
char smsc_addr[GSM23003_MSISDN_MAX_DIGITS + 1];
struct osmo_gsup_message rsp_msg;
struct hlr_subscriber subscr;
uint8_t ext, ton, npi;
uint8_t sm_rp_cause;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr);
if (rc) {
LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' for unknown subscriber IMSI-%s\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi);
gsup_prepare_sm_error(&rsp_msg, gsup);
rsp_msg.cause = GMM_CAUSE_IMSI_UNKNOWN;
goto exit_error;
}
/* Make sure SM-RP-DA (SMSC address) is present */
if (gsup->sm_rp_da == NULL || !gsup->sm_rp_da_len) {
LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' (IMSI-%s) without mandatory SM-RP-DA\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi);
gsup_prepare_sm_error(&rsp_msg, gsup);
rsp_msg.cause = GMM_CAUSE_INV_MAND_INFO;
goto exit_error;
}
if (gsup->sm_rp_da_type != OSMO_GSUP_SMS_SM_RP_ODA_SMSC_ADDR) {
LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' (IMSI-%s) with unexpected SM-RP-DA 0x%02x\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi,
gsup->sm_rp_da_type);
gsup_prepare_sm_error(&rsp_msg, gsup);
rsp_msg.cause = GMM_CAUSE_INV_MAND_INFO;
goto exit_error;
}
/* Parse ToN (Type of Number) / NPI (Numbering Plan Indicator) */
ext = (gsup->sm_rp_da[0] >> 7) ^ 0x01; /* NOTE: inversed */
ton = (gsup->sm_rp_da[0] >> 4) & 0x07;
npi = gsup->sm_rp_da[0] & 0x0f;
/* We only support International ISDN/telephone format */
if (ext || ton != 0x01 || npi != 0x01) {
LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' (IMSI-%s) with unsupported SMSC address format: "
"ToN=0x%02x, NPI=0x%02x\n, extension=%s\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi,
ton, npi, ext ? "yes" : "no");
gsup_prepare_sm_error(&rsp_msg, gsup);
rsp_msg.cause = GMM_CAUSE_SEM_INCORR_MSG;
goto exit_error;
}
/* Decode SMSC address from SM-RP-DA */
rc = osmo_bcd2str(smsc_addr, sizeof(smsc_addr), gsup->sm_rp_da + 1,
2, (gsup->sm_rp_da_len - 1) * 2, true);
if (rc < 0 || rc >= sizeof(smsc_addr)) {
LOGP(DLSMS, LOGL_NOTICE, "Failed to decode SMSC address from '%s' (IMSI-%s): rc=%d\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi, rc);
gsup_prepare_sm_error(&rsp_msg, gsup);
rsp_msg.cause = GMM_CAUSE_SEM_INCORR_MSG;
goto exit_error;
}
/* Attempt to find a EUSE */
const struct hlr_euse *euse = find_euse(&subscr, smsc_addr);
if (euse == NULL) {
LOGP(DLSMS, LOGL_NOTICE, "Failed to find a route for '%s' (IMSI-%s, MR-0x%02x)\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi, *gsup->sm_rp_mr);
gsup_prepare_sm_error(&rsp_msg, gsup);
sm_rp_cause = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
rsp_msg.sm_rp_cause = &sm_rp_cause;
goto exit_error;
}
const struct osmo_gsup_conn *euse_conn = find_conn(conn->server, euse);
if (euse_conn == NULL) {
LOGP(DLSMS, LOGL_ERROR, "EUSE '%s' is not connected!\n", euse->name);
gsup_prepare_sm_error(&rsp_msg, gsup);
sm_rp_cause = GSM411_RP_CAUSE_MO_TEMP_FAIL;
rsp_msg.sm_rp_cause = &sm_rp_cause;
goto exit_error;
}
LOGP(DLSMS, LOGL_INFO, "Forwarding '%s' (IMSI-%s, MR-0x%02x) to SMSC '%s'\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi,
*gsup->sm_rp_mr, euse->name);
/* HACK: make sure source name is present, fill in if needed */
if (gsup->source_name == NULL || !gsup->source_name_len) {
/* FIXME: distinguish between MSC/VLR and SGSN */
msgb_tlv_put(msg, OSMO_GSUP_SOURCE_NAME_IE,
strlen(subscr.vlr_number) + 1,
(uint8_t *) subscr.vlr_number);
}
/* Ensure the buffer has enough headroom to put IPA headers */
msgb_pull_to_l2(msg);
/* Finally forward the original message */
rc = osmo_gsup_conn_send((struct osmo_gsup_conn *) euse_conn, msg);
if (rc) {
LOGP(DLSMS, LOGL_ERROR, "Failed to send GSUP message '%s' (rc=%d)\n",
osmo_gsup_message_type_name(gsup->message_type), rc);
gsup_prepare_sm_error(&rsp_msg, gsup);
sm_rp_cause = GSM411_RP_CAUSE_MO_TEMP_FAIL;
rsp_msg.sm_rp_cause = &sm_rp_cause;
msg = NULL; /* free()d by osmo_gsup_conn_send() */
goto exit_error;
}
return 0;
exit_error:
gsup_conn_enc_send(conn, &rsp_msg);
if (msg != NULL)
talloc_free(msg);
return rc;
}
/* Short Message from SMSC towards MSC/VLR */
int forward_mt_sms(struct osmo_gsup_conn *conn,
const struct osmo_gsup_message *gsup,
struct msgb *msg)
{
struct osmo_gsup_message rsp_msg;
struct hlr_subscriber subscr;
uint8_t sm_rp_cause;
int rc;
rc = db_subscr_get_by_imsi(g_hlr->dbc, gsup->imsi, &subscr);
if (rc) {
LOGP(DLSMS, LOGL_NOTICE, "Rx '%s' for unknown subscriber IMSI-%s\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi);
gsup_prepare_sm_error(&rsp_msg, gsup);
rsp_msg.cause = GMM_CAUSE_IMSI_UNKNOWN;
goto exit_error;
}
#if 0
LOGP(DLSMS, LOGL_INFO, "Forwarding '%s' (IMSI-%s, MR-0x%02x) to MSC/VLR '%s'\n",
osmo_gsup_message_type_name(gsup->message_type), gsup->imsi,
*gsup->sm_rp_mr, FIXME!);
/* Finally forward the original message */
rc = osmo_gsup_conn_send(subscr_conn, msg);
if (rc) {
LOGP(DLSMS, LOGL_ERROR, "Failed to send GSUP message '%s' (rc=%d)\n",
osmo_gsup_message_type_name(gsup->message_type), rc);
gsup_prepare_sm_error(&rsp_msg, gsup);
sm_rp_cause = GSM411_RP_CAUSE_MO_TEMP_FAIL;
rsp_msg.sm_rp_cause = &sm_rp_cause;
msg = NULL; /* free()d by osmo_gsup_conn_send() */
goto exit_error;
}
#endif
return 0;
exit_error:
gsup_conn_enc_send(conn, &rsp_msg);
if (msg != NULL)
talloc_free(msg);
return rc;
}