Skip to content

Instantly share code, notes, and snippets.

@saydulk saydulk/sc.sh
Created Apr 29, 2019

Embed
What would you like to do?
sc
/* ====================================================================
* The Kannel Software License, Version 1.0
*
* Copyright (c) 2001-2010 Kannel Group
* Copyright (c) 1998-2001 WapIT Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Kannel Group (http://www.kannel.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Kannel" and "Kannel Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact org@kannel.org.
*
* 5. Products derived from this software may not be called "Kannel",
* nor may "Kannel" appear in their name, without prior written
* permission of the Kannel Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Kannel Group. For more information on
* the Kannel Group, please see <http://www.kannel.org/>.
*
* Portions of this software are based upon software originally written at
* WapIT Ltd., Helsinki, Finland for the Kannel project.
*/
/*
* smsc_smpp.c - SMPP v3.3 and v3.4 implementation
*
* Lars Wirzenius
* Stipe Tolj <stolj@wapme.de>
* Alexander Malysh <amalysh at kannel.org>
*/
/* XXX check SMSCConn conformance */
/* XXX UDH reception */
/* XXX check UDH sending fields esm_class and data_coding from GSM specs */
/* XXX charset conversions on incoming messages (didn't work earlier,
either) */
/* XXX numbering plans and type of number: check spec */
#include "gwlib/gwlib.h"
#include "msg.h"
#include "smsc_p.h"
#include "smpp_pdu.h"
#include "smscconn_p.h"
#include "bb_smscconn_cb.h"
#include "sms.h"
#include "dlr.h"
#include "bearerbox.h"
#include "meta_data.h"
#include "load.h"
#define SMPP_DEFAULT_CHARSET "UTF-8"
/*
* Select these based on whether you want to dump SMPP PDUs as they are
* sent and received or not. Not dumping should be the default in at least
* stable releases.
*/
#define DEBUG 1
#ifndef DEBUG
#define dump_pdu(msg, id, pdu) do{}while(0)
#else
/** This version does dump. */
#define dump_pdu(msg, id, pdu) \
do { \
debug("bb.sms.smpp", 0, "SMPP[%s]: %s", \
octstr_get_cstr(id), msg); \
smpp_pdu_dump(pdu); \
} while(0)
#endif
/*
* Some defaults.
*/
#define SMPP_ENQUIRE_LINK_INTERVAL 30.0
#define SMPP_MAX_PENDING_SUBMITS 10
#define SMPP_DEFAULT_VERSION 0x34
#define SMPP_DEFAULT_PRIORITY 0
#define SMPP_THROTTLING_SLEEP_TIME 1
#define SMPP_DEFAULT_CONNECTION_TIMEOUT 10 * SMPP_ENQUIRE_LINK_INTERVAL
#define SMPP_DEFAULT_WAITACK 60
#define SMPP_DEFAULT_SHUTDOWN_TIMEOUT 30
/*
* Some defines
*/
#define SMPP_WAITACK_RECONNECT 0x00
#define SMPP_WAITACK_REQUEUE 0x01
#define SMPP_WAITACK_NEVER_EXPIRE 0x02
/***********************************************************************
* Implementation of the actual SMPP protocol: reading and writing
* PDUs in the correct order.
*/
typedef struct {
long transmitter;
long receiver;
gw_prioqueue_t *msgs_to_send;
Dict *sent_msgs;
List *received_msgs;
Counter *message_id_counter;
Octstr *host;
Octstr *system_type;
Octstr *username;
Octstr *password;
Octstr *address_range;
Octstr *my_number;
Octstr *service_type;
int source_addr_ton;
int source_addr_npi;
int dest_addr_ton;
int dest_addr_npi;
long bind_addr_ton;
long bind_addr_npi;
int transmit_port;
int receive_port;
int use_ssl;
volatile int quitting;
long enquire_link_interval;
long max_pending_submits;
int version;
int priority; /* set default priority for messages */
int validityperiod;
time_t throttling_err_time;
int smpp_msg_id_type; /* msg id in C string, hex or decimal */
int autodetect_addr;
Octstr *alt_charset;
Octstr *alt_addr_charset;
long connection_timeout;
long wait_ack;
int wait_ack_action;
Load *load;
SMSCConn *conn;
} SMPP;
struct smpp_msg {
time_t sent_time;
Msg *msg;
};
/*
* create smpp_msg struct
*/
static inline struct smpp_msg* smpp_msg_create(Msg *msg)
{
struct smpp_msg *result = gw_malloc(sizeof(struct smpp_msg));
gw_assert(result != NULL);
result->sent_time = time(NULL);
result->msg = msg;
return result;
}
/*
* destroy smpp_msg struct. If destroy_msg flag is set, then message will be freed as well
*/
static inline void smpp_msg_destroy(struct smpp_msg *msg, int destroy_msg)
{
/* sanity check */
if (msg == NULL)
return;
if (destroy_msg && msg->msg != NULL)
msg_destroy(msg->msg);
gw_free(msg);
}
static SMPP *smpp_create(SMSCConn *conn, Octstr *host, int transmit_port,
int receive_port, Octstr *system_type,
Octstr *username, Octstr *password,
Octstr *address_range,
int source_addr_ton, int source_addr_npi,
int dest_addr_ton, int dest_addr_npi,
int enquire_link_interval, int max_pending_submits,
int version, int priority, int validity,
Octstr *my_number, int smpp_msg_id_type,
int autodetect_addr, Octstr *alt_charset, Octstr *alt_addr_charset,
Octstr *service_type, long connection_timeout,
long wait_ack, int wait_ack_action)
{
SMPP *smpp;
smpp = gw_malloc(sizeof(*smpp));
smpp->transmitter = -1;
smpp->receiver = -1;
smpp->msgs_to_send = gw_prioqueue_create(sms_priority_compare);
smpp->sent_msgs = dict_create(max_pending_submits, NULL);
gw_prioqueue_add_producer(smpp->msgs_to_send);
smpp->received_msgs = gwlist_create();
smpp->message_id_counter = counter_create();
counter_increase(smpp->message_id_counter);
smpp->host = octstr_duplicate(host);
smpp->system_type = octstr_duplicate(system_type);
smpp->username = octstr_duplicate(username);
smpp->password = octstr_duplicate(password);
smpp->address_range = octstr_duplicate(address_range);
smpp->source_addr_ton = source_addr_ton;
smpp->source_addr_npi = source_addr_npi;
smpp->dest_addr_ton = dest_addr_ton;
smpp->dest_addr_npi = dest_addr_npi;
smpp->my_number = octstr_duplicate(my_number);
smpp->service_type = octstr_duplicate(service_type);
smpp->transmit_port = transmit_port;
smpp->receive_port = receive_port;
smpp->enquire_link_interval = enquire_link_interval;
smpp->max_pending_submits = max_pending_submits;
smpp->quitting = 0;
smpp->version = version;
smpp->priority = priority;
smpp->validityperiod = validity;
smpp->conn = conn;
smpp->throttling_err_time = 0;
smpp->smpp_msg_id_type = smpp_msg_id_type;
smpp->autodetect_addr = autodetect_addr;
smpp->alt_charset = octstr_duplicate(alt_charset);
smpp->alt_addr_charset = octstr_duplicate(alt_addr_charset);
smpp->connection_timeout = connection_timeout;
smpp->wait_ack = wait_ack;
smpp->wait_ack_action = wait_ack_action;
smpp->bind_addr_ton = 0;
smpp->bind_addr_npi = 0;
smpp->use_ssl = 0;
smpp->load = load_create_real(0);
load_add_interval(smpp->load, 1);
return smpp;
}
static void smpp_destroy(SMPP *smpp)
{
if (smpp != NULL) {
gw_prioqueue_destroy(smpp->msgs_to_send, msg_destroy_item);
dict_destroy(smpp->sent_msgs);
gwlist_destroy(smpp->received_msgs, msg_destroy_item);
counter_destroy(smpp->message_id_counter);
octstr_destroy(smpp->host);
octstr_destroy(smpp->username);
octstr_destroy(smpp->password);
octstr_destroy(smpp->system_type);
octstr_destroy(smpp->service_type);
octstr_destroy(smpp->address_range);
octstr_destroy(smpp->my_number);
octstr_destroy(smpp->alt_charset);
octstr_destroy(smpp->alt_addr_charset);
load_destroy(smpp->load);
gw_free(smpp);
}
}
/*
* Try to read an SMPP PDU from a Connection. Return -1 for error (caller
* should close the connection), -2 for malformed PDU , 0 for no PDU to
* ready yet, or 1 for PDU
* read and unpacked. Return a pointer to the PDU in `*pdu'. Use `*len'
* to store the length of the PDU to read (it may be possible to read the
* length, but not the rest of the PDU - we need to remember the lenght
* for the next call). `*len' should be zero at the first call.
*/
static int read_pdu(SMPP *smpp, Connection *conn, long *len, SMPP_PDU **pdu)
{
Octstr *os;
if (*len == 0) {
*len = smpp_pdu_read_len(conn);
if (*len == -1) {
error(0, "SMPP[%s]: Server sent garbage, ignored.",
octstr_get_cstr(smpp->conn->id));
return -2;
} else if (*len == 0) {
if (conn_eof(conn) || conn_error(conn))
return -1;
return 0;
}
}
os = smpp_pdu_read_data(conn, *len);
if (os == NULL) {
if (conn_eof(conn) || conn_error(conn))
return -1;
return 0;
}
*len = 0;
*pdu = smpp_pdu_unpack(smpp->conn->id, os);
if (*pdu == NULL) {
error(0, "SMPP[%s]: PDU unpacking failed.",
octstr_get_cstr(smpp->conn->id));
debug("bb.sms.smpp", 0, "SMPP[%s]: Failed PDU follows.",
octstr_get_cstr(smpp->conn->id));
octstr_dump(os, 0);
octstr_destroy(os);
return -2;
}
octstr_destroy(os);
return 1;
}
static long convert_addr_from_pdu(Octstr *id, Octstr *addr, long ton, long npi, Octstr *alt_addr_charset)
{
long reason = SMPP_ESME_ROK;
if (addr == NULL)
return reason;
switch(ton) {
case GSM_ADDR_TON_INTERNATIONAL:
/*
* Checks to perform:
* 1) assume international number has at least 7 chars
* 2) the whole source addr consist of digits, exception '+' in front
*/
if (octstr_len(addr) < 7) {
/* We consider this as a "non-hard" condition, since there "may"
* be international numbers routable that are < 7 digits. Think
* of 2 digit country code + 3 digit emergency code. */
} else if (octstr_get_char(addr, 0) == '+' &&
!octstr_check_range(addr, 1, 256, gw_isdigit)) {
} else if (octstr_get_char(addr, 0) != '+' &&
!octstr_check_range(addr, 0, 256, gw_isdigit)) {
}
/* check if we received leading '00', then remove it*/
if (octstr_search(addr, octstr_imm("00"), 0) == 0)
octstr_delete(addr, 0, 2);
/* international, insert '+' if not already here */
if (octstr_get_char(addr, 0) != '+')
octstr_insert_char(addr, 0, '+');
break;
case GSM_ADDR_TON_ALPHANUMERIC:
if (octstr_len(addr) > 11) {
/* alphanum sender, max. allowed length is 11 (according to GSM specs) */
}
if (alt_addr_charset) {
}
break;
default: /* otherwise don't touch addr, user should handle it */
break;
}
error:
return reason;
}
/*
* Convert SMPP PDU to internal Msgs structure.
* Return the Msg if all was fine and NULL otherwise, while getting
* the failing reason delivered back in *reason.
* XXX semantical check on the incoming values can be extended here.
*/
static Msg *pdu_to_msg(SMPP *smpp, SMPP_PDU *pdu, long *reason)
{
Msg *msg;
int ton, npi;
gw_assert(pdu->type == deliver_sm);
msg = msg_create(sms);
gw_assert(msg != NULL);
*reason = SMPP_ESME_ROK;
/*
* Reset source addr to have a prefixed '+' in case we have an
* intl. TON to allow backend boxes (ie. smsbox) to distinguish
* between national and international numbers.
*/
ton = pdu->u.deliver_sm.source_addr_ton;
npi = pdu->u.deliver_sm.source_addr_npi;
/* check source addr */
if ((*reason = convert_addr_from_pdu(smpp->conn->id, pdu->u.deliver_sm.source_addr, ton, npi, smpp->alt_addr_charset)) != SMPP_ESME_ROK)
goto error;
msg->sms.sender = pdu->u.deliver_sm.source_addr;
pdu->u.deliver_sm.source_addr = NULL;
/*
* Follows SMPP spec. v3.4. issue 1.2
* it's not allowed to have destination_addr NULL
*/
if (pdu->u.deliver_sm.destination_addr == NULL) {
error(0, "SMPP[%s]: Mallformed destination_addr `%s', may not be empty. "
"Discarding MO message.", octstr_get_cstr(smpp->conn->id),
octstr_get_cstr(pdu->u.deliver_sm.destination_addr));
*reason = SMPP_ESME_RINVDSTADR;
goto error;
}
/* Same reset of destination number as for source */
ton = pdu->u.deliver_sm.dest_addr_ton;
npi = pdu->u.deliver_sm.dest_addr_npi;
/* check destination addr */
if ((*reason = convert_addr_from_pdu(smpp->conn->id, pdu->u.deliver_sm.destination_addr, ton, npi, smpp->alt_addr_charset)) != SMPP_ESME_ROK)
goto error;
msg->sms.receiver = pdu->u.deliver_sm.destination_addr;
pdu->u.deliver_sm.destination_addr = NULL;
/* SMSCs use service_type for billing information */
msg->sms.binfo = pdu->u.deliver_sm.service_type;
pdu->u.deliver_sm.service_type = NULL;
if (pdu->u.deliver_sm.esm_class & ESM_CLASS_SUBMIT_RPI)
msg->sms.rpi = 1;
/*
* Check for message_payload if version > 0x33 and sm_length == 0
* Note: SMPP spec. v3.4. doesn't allow to send both: message_payload & short_message!
*/
if (smpp->version > 0x33 && pdu->u.deliver_sm.sm_length == 0 && pdu->u.deliver_sm.message_payload) {
msg->sms.msgdata = pdu->u.deliver_sm.message_payload;
pdu->u.deliver_sm.message_payload = NULL;
}
else {
msg->sms.msgdata = pdu->u.deliver_sm.short_message;
pdu->u.deliver_sm.short_message = NULL;
}
/*
* Encode udh if udhi set
* for reference see GSM03.40, section 9.2.3.24
*/
if (pdu->u.deliver_sm.esm_class & ESM_CLASS_SUBMIT_UDH_INDICATOR) {
int udhl;
udhl = octstr_get_char(msg->sms.msgdata, 0) + 1;
debug("bb.sms.smpp",0,"SMPP[%s]: UDH length read as %d",
octstr_get_cstr(smpp->conn->id), udhl);
if (udhl > octstr_len(msg->sms.msgdata)) {
error(0, "SMPP[%s]: Mallformed UDH length indicator 0x%03x while message length "
"0x%03lx. Discarding MO message.", octstr_get_cstr(smpp->conn->id),
udhl, octstr_len(msg->sms.msgdata));
*reason = SMPP_ESME_RINVESMCLASS;
goto error;
}
msg->sms.udhdata = octstr_copy(msg->sms.msgdata, 0, udhl);
octstr_delete(msg->sms.msgdata, 0, udhl);
}
dcs_to_fields(&msg, pdu->u.deliver_sm.data_coding);
/* handle default data coding */
switch (pdu->u.deliver_sm.data_coding) {
case 0x00: /* default SMSC alphabet */
/*
* try to convert from something interesting if specified so
* unless it was specified binary, ie. UDH indicator was detected
*/
if (smpp->alt_charset && msg->sms.coding != DC_8BIT) {
if (charset_convert(msg->sms.msgdata, octstr_get_cstr(smpp->alt_charset), SMPP_DEFAULT_CHARSET) != 0)
error(0, "Failed to convert msgdata from charset <%s> to <%s>, will leave as is.",
octstr_get_cstr(smpp->alt_charset), SMPP_DEFAULT_CHARSET);
msg->sms.coding = DC_7BIT;
} else { /* assume GSM 03.38 7-bit alphabet */
charset_gsm_to_utf8(msg->sms.msgdata);
msg->sms.coding = DC_7BIT;
}
break;
case 0x01: /* ASCII or IA5 - not sure if I need to do anything */
case 0x03: /* ISO-8859-1 - do nothing */
msg->sms.coding = DC_7BIT; break;
case 0x02: /* 8 bit binary - do nothing */
case 0x04: /* 8 bit binary - do nothing */
msg->sms.coding = DC_8BIT; break;
case 0x05: /* JIS - what do I do with that ? */
break;
case 0x06: /* Cyrllic - iso-8859-5, I'll convert to unicode */
if (charset_convert(msg->sms.msgdata, "ISO-8859-5", SMPP_DEFAULT_CHARSET) != 0)
error(0, "Failed to convert msgdata from cyrllic to " SMPP_DEFAULT_CHARSET ", will leave as is");
msg->sms.coding = DC_7BIT; break;
case 0x07: /* Hebrew iso-8859-8, I'll convert to unicode */
if (charset_convert(msg->sms.msgdata, "ISO-8859-8", SMPP_DEFAULT_CHARSET) != 0)
error(0, "Failed to convert msgdata from hebrew to " SMPP_DEFAULT_CHARSET ", will leave as is");
msg->sms.coding = DC_7BIT; break;
case 0x08: /* unicode UCS-2, yey */
msg->sms.coding = DC_UCS2; break;
/*
* don't much care about the others,
* you implement them if you feel like it
*/
default:
/*
* some of smsc send with dcs from GSM 03.38 , but these are reserved in smpp spec.
* So we just look decoded values from dcs_to_fields and if none there make our assumptions.
* if we have an UDH indicator, we assume DC_8BIT.
*/
if (msg->sms.coding == DC_UNDEF && pdu->u.deliver_sm.esm_class & ESM_CLASS_SUBMIT_UDH_INDICATOR)
msg->sms.coding = DC_8BIT;
else if (msg->sms.coding == DC_7BIT || msg->sms.coding == DC_UNDEF) { /* assume GSM 7Bit , reencode */
msg->sms.coding = DC_7BIT;
charset_gsm_to_utf8(msg->sms.msgdata);
}
}
msg->sms.pid = pdu->u.deliver_sm.protocol_id;
/* set priority flag */
msg->sms.priority = pdu->u.deliver_sm.priority_flag;
if (msg->sms.meta_data == NULL)
msg->sms.meta_data = octstr_create("");
meta_data_set_values(msg->sms.meta_data, pdu->u.deliver_sm.tlv, "smpp", 1);
return msg;
error:
msg_destroy(msg);
return NULL;
}
/*
* Convert SMPP PDU to internal Msgs structure.
* Return the Msg if all was fine and NULL otherwise, while getting
* the failing reason delivered back in *reason.
* XXX semantical check on the incoming values can be extended here.
*/
static Msg *data_sm_to_msg(SMPP *smpp, SMPP_PDU *pdu, long *reason)
{
Msg *msg;
int ton, npi;
gw_assert(pdu->type == data_sm);
msg = msg_create(sms);
gw_assert(msg != NULL);
*reason = SMPP_ESME_ROK;
/*
* Reset source addr to have a prefixed '+' in case we have an
* intl. TON to allow backend boxes (ie. smsbox) to distinguish
* between national and international numbers.
*/
ton = pdu->u.data_sm.source_addr_ton;
npi = pdu->u.data_sm.source_addr_npi;
/* check source addr */
if ((*reason = convert_addr_from_pdu(smpp->conn->id, pdu->u.data_sm.source_addr, ton, npi, smpp->alt_addr_charset)) != SMPP_ESME_ROK)
goto error;
msg->sms.sender = pdu->u.data_sm.source_addr;
pdu->u.data_sm.source_addr = NULL;
/*
* Follows SMPP spec. v3.4. issue 1.2
* it's not allowed to have destination_addr NULL
*/
if (pdu->u.data_sm.destination_addr == NULL) {
error(0, "SMPP[%s]: Mallformed destination_addr `%s', may not be empty. "
"Discarding MO message.", octstr_get_cstr(smpp->conn->id),
octstr_get_cstr(pdu->u.data_sm.destination_addr));
*reason = SMPP_ESME_RINVDSTADR;
goto error;
}
/* Same reset of destination number as for source */
ton = pdu->u.data_sm.dest_addr_ton;
npi = pdu->u.data_sm.dest_addr_npi;
/* check destination addr */
if ((*reason = convert_addr_from_pdu(smpp->conn->id, pdu->u.data_sm.destination_addr, ton, npi, smpp->alt_addr_charset)) != SMPP_ESME_ROK)
goto error;
msg->sms.receiver = pdu->u.data_sm.destination_addr;
pdu->u.data_sm.destination_addr = NULL;
/* SMSCs use service_type for billing information */
msg->sms.binfo = pdu->u.data_sm.service_type;
pdu->u.data_sm.service_type = NULL;
if (pdu->u.data_sm.esm_class & ESM_CLASS_SUBMIT_RPI)
msg->sms.rpi = 1;
msg->sms.msgdata = pdu->u.data_sm.message_payload;
pdu->u.data_sm.message_payload = NULL;
/*
* Encode udh if udhi set
* for reference see GSM03.40, section 9.2.3.24
*/
if (pdu->u.data_sm.esm_class & ESM_CLASS_SUBMIT_UDH_INDICATOR) {
int udhl;
udhl = octstr_get_char(msg->sms.msgdata, 0) + 1;
debug("bb.sms.smpp",0,"SMPP[%s]: UDH length read as %d",
octstr_get_cstr(smpp->conn->id), udhl);
if (udhl > octstr_len(msg->sms.msgdata)) {
error(0, "SMPP[%s]: Mallformed UDH length indicator 0x%03x while message length "
"0x%03lx. Discarding MO message.", octstr_get_cstr(smpp->conn->id),
udhl, octstr_len(msg->sms.msgdata));
*reason = SMPP_ESME_RINVESMCLASS;
goto error;
}
msg->sms.udhdata = octstr_copy(msg->sms.msgdata, 0, udhl);
octstr_delete(msg->sms.msgdata, 0, udhl);
}
dcs_to_fields(&msg, pdu->u.data_sm.data_coding);
/* handle default data coding */
switch (pdu->u.data_sm.data_coding) {
case 0x00: /* default SMSC alphabet */
/*
* try to convert from something interesting if specified so
* unless it was specified binary, ie. UDH indicator was detected
*/
if (smpp->alt_charset && msg->sms.coding != DC_8BIT) {
if (charset_convert(msg->sms.msgdata, octstr_get_cstr(smpp->alt_charset), SMPP_DEFAULT_CHARSET) != 0)
error(0, "Failed to convert msgdata from charset <%s> to <%s>, will leave as is.",
octstr_get_cstr(smpp->alt_charset), SMPP_DEFAULT_CHARSET);
msg->sms.coding = DC_7BIT;
} else { /* assume GSM 03.38 7-bit alphabet */
charset_gsm_to_utf8(msg->sms.msgdata);
msg->sms.coding = DC_7BIT;
}
break;
case 0x01: /* ASCII or IA5 - not sure if I need to do anything */
case 0x03: /* ISO-8859-1 - do nothing */
msg->sms.coding = DC_7BIT; break;
case 0x02: /* 8 bit binary - do nothing */
case 0x04: /* 8 bit binary - do nothing */
msg->sms.coding = DC_8BIT; break;
case 0x05: /* JIS - what do I do with that ? */
break;
case 0x06: /* Cyrllic - iso-8859-5, I'll convert to unicode */
if (charset_convert(msg->sms.msgdata, "ISO-8859-5", SMPP_DEFAULT_CHARSET) != 0)
error(0, "Failed to convert msgdata from cyrllic to " SMPP_DEFAULT_CHARSET ", will leave as is");
msg->sms.coding = DC_7BIT; break;
case 0x07: /* Hebrew iso-8859-8, I'll convert to unicode */
if (charset_convert(msg->sms.msgdata, "ISO-8859-8", SMPP_DEFAULT_CHARSET) != 0)
error(0, "Failed to convert msgdata from hebrew to " SMPP_DEFAULT_CHARSET ", will leave as is");
msg->sms.coding = DC_7BIT; break;
case 0x08: /* unicode UCS-2, yey */
msg->sms.coding = DC_UCS2; break;
/*
* don't much care about the others,
* you implement them if you feel like it
*/
default:
/*
* some of smsc send with dcs from GSM 03.38 , but these are reserved in smpp spec.
* So we just look decoded values from dcs_to_fields and if none there make our assumptions.
* if we have an UDH indicator, we assume DC_8BIT.
*/
if (msg->sms.coding == DC_UNDEF && pdu->u.data_sm.esm_class & ESM_CLASS_SUBMIT_UDH_INDICATOR)
msg->sms.coding = DC_8BIT;
else if (msg->sms.coding == DC_7BIT || msg->sms.coding == DC_UNDEF) { /* assume GSM 7Bit , reencode */
msg->sms.coding = DC_7BIT;
charset_gsm_to_utf8(msg->sms.msgdata);
}
}
if (msg->sms.meta_data == NULL)
msg->sms.meta_data = octstr_create("");
meta_data_set_values(msg->sms.meta_data, pdu->u.data_sm.tlv, "smpp", 1);
return msg;
error:
msg_destroy(msg);
return NULL;
}
static long smpp_status_to_smscconn_failure_reason(long status)
{
switch(status) {
case SMPP_ESME_RMSGQFUL:
case SMPP_ESME_RTHROTTLED:
case SMPP_ESME_RX_T_APPN:
case SMPP_ESME_RSYSERR:
return SMSCCONN_FAILED_TEMPORARILY;
break;
default:
return SMSCCONN_FAILED_REJECTED;
}
}
static SMPP_PDU *msg_to_pdu(SMPP *smpp, Msg *msg)
{
SMPP_PDU *pdu;
int validity;
pdu = smpp_pdu_create(submit_sm,
counter_increase(smpp->message_id_counter));
pdu->u.submit_sm.source_addr = octstr_duplicate(msg->sms.sender);
pdu->u.submit_sm.destination_addr = octstr_duplicate(msg->sms.receiver);
/* Set the service type of the outgoing message. We'll use the config
* directive as default and 'binfo' as specific parameter. */
pdu->u.submit_sm.service_type = octstr_len(msg->sms.binfo) ?
octstr_duplicate(msg->sms.binfo) : octstr_duplicate(smpp->service_type);
/* Check for manual override of source ton and npi values */
if(smpp->source_addr_ton > -1 && smpp->source_addr_npi > -1) {
pdu->u.submit_sm.source_addr_ton = smpp->source_addr_ton;
pdu->u.submit_sm.source_addr_npi = smpp->source_addr_npi;
debug("bb.sms.smpp", 0, "SMPP[%s]: Manually forced source addr ton = %d, source add npi = %d",
octstr_get_cstr(smpp->conn->id), smpp->source_addr_ton,
smpp->source_addr_npi);
} else {
/* setup default values */
pdu->u.submit_sm.source_addr_ton = GSM_ADDR_TON_NATIONAL; /* national */
pdu->u.submit_sm.source_addr_npi = GSM_ADDR_NPI_E164; /* ISDN number plan */
}
if (pdu->u.submit_sm.source_addr && smpp->autodetect_addr) {
/* lets see if its international or alphanumeric sender */
if (octstr_get_char(pdu->u.submit_sm.source_addr, 0) == '+') {
if (!octstr_check_range(pdu->u.submit_sm.source_addr, 1, 256, gw_isdigit)) {
pdu->u.submit_sm.source_addr_ton = GSM_ADDR_TON_ALPHANUMERIC; /* alphanum */
pdu->u.submit_sm.source_addr_npi = GSM_ADDR_NPI_UNKNOWN; /* short code */
if (smpp->alt_addr_charset) {
if (octstr_str_case_compare(smpp->alt_addr_charset, "gsm") == 0) {
/* @ would break PDU if converted into GSM*/
octstr_replace(pdu->u.submit_sm.source_addr, octstr_imm("@"), octstr_imm("?"));
charset_utf8_to_gsm(pdu->u.submit_sm.source_addr);
} else if (charset_convert(pdu->u.submit_sm.source_addr, SMPP_DEFAULT_CHARSET, octstr_get_cstr(smpp->alt_addr_charset)) != 0)
error(0, "Failed to convert source_addr from charset <%s> to <%s>, will send as is.",
SMPP_DEFAULT_CHARSET, octstr_get_cstr(smpp->alt_addr_charset));
}
} else {
/* numeric sender address with + in front -> international (remove the +) */
octstr_delete(pdu->u.submit_sm.source_addr, 0, 1);
pdu->u.submit_sm.source_addr_ton = GSM_ADDR_TON_INTERNATIONAL;
}
} else {
if (!octstr_check_range(pdu->u.submit_sm.source_addr,0, 256, gw_isdigit)) {
pdu->u.submit_sm.source_addr_ton = GSM_ADDR_TON_ALPHANUMERIC;
pdu->u.submit_sm.source_addr_npi = GSM_ADDR_NPI_UNKNOWN;
if (smpp->alt_addr_charset) {
if (octstr_str_case_compare(smpp->alt_addr_charset, "gsm") == 0) {
/* @ would break PDU if converted into GSM */
octstr_replace(pdu->u.submit_sm.source_addr, octstr_imm("@"), octstr_imm("?"));
charset_utf8_to_gsm(pdu->u.submit_sm.source_addr);
} else if (charset_convert(pdu->u.submit_sm.source_addr, SMPP_DEFAULT_CHARSET, octstr_get_cstr(smpp->alt_addr_charset)) != 0)
error(0, "Failed to convert source_addr from charset <%s> to <%s>, will send as is.",
SMPP_DEFAULT_CHARSET, octstr_get_cstr(smpp->alt_addr_charset));
}
}
}
}
/* Check for manual override of destination ton and npi values */
if (smpp->dest_addr_ton > -1 && smpp->dest_addr_npi > -1) {
pdu->u.submit_sm.dest_addr_ton = smpp->dest_addr_ton;
pdu->u.submit_sm.dest_addr_npi = smpp->dest_addr_npi;
debug("bb.sms.smpp", 0, "SMPP[%s]: Manually forced dest addr ton = %d, dest add npi = %d",
octstr_get_cstr(smpp->conn->id), smpp->dest_addr_ton,
smpp->dest_addr_npi);
} else {
pdu->u.submit_sm.dest_addr_ton = GSM_ADDR_TON_NATIONAL; /* national */
pdu->u.submit_sm.dest_addr_npi = GSM_ADDR_NPI_E164; /* ISDN number plan */
}
/*
* if its a international number starting with +, lets remove the
* '+' and set number type to international instead
*/
if (octstr_get_char(pdu->u.submit_sm.destination_addr,0) == '+') {
octstr_delete(pdu->u.submit_sm.destination_addr, 0,1);
pdu->u.submit_sm.dest_addr_ton = GSM_ADDR_TON_INTERNATIONAL;
}
/* check length of src/dst address */
if (octstr_len(pdu->u.submit_sm.destination_addr) > 20 ||
octstr_len(pdu->u.submit_sm.source_addr) > 20) {
smpp_pdu_destroy(pdu);
return NULL;
}
/*
* set the data coding scheme (DCS) field
* check if we have a forced value for this from the smsc-group.
* Note: if message class is set, then we _must_ force alt_dcs otherwise
* dcs has reserved values (e.g. mclass=2, dcs=0x11). We check MWI flag
* first here, because MWI and MCLASS can not be set at the same time and
* function fields_to_dcs check MWI first, so we have no need to force alt_dcs
* if MWI is set.
*/
if (msg->sms.mwi == MWI_UNDEF && msg->sms.mclass != MC_UNDEF)
pdu->u.submit_sm.data_coding = fields_to_dcs(msg, 1); /* force alt_dcs */
else
pdu->u.submit_sm.data_coding = fields_to_dcs(msg,
(msg->sms.alt_dcs != SMS_PARAM_UNDEFINED ?
msg->sms.alt_dcs : smpp->conn->alt_dcs));
/* set protocol id */
if(msg->sms.pid != SMS_PARAM_UNDEFINED)
pdu->u.submit_sm.protocol_id = msg->sms.pid;
/*
* set the esm_class field
* default is store and forward, plus udh and rpi if requested
*/
pdu->u.submit_sm.esm_class = ESM_CLASS_SUBMIT_STORE_AND_FORWARD_MODE;
if (octstr_len(msg->sms.udhdata))
pdu->u.submit_sm.esm_class = pdu->u.submit_sm.esm_class |
ESM_CLASS_SUBMIT_UDH_INDICATOR;
if (msg->sms.rpi > 0)
pdu->u.submit_sm.esm_class = pdu->u.submit_sm.esm_class |
ESM_CLASS_SUBMIT_RPI;
/*
* set data segments and length
*/
pdu->u.submit_sm.short_message = octstr_duplicate(msg->sms.msgdata);
/*
* only re-encoding if using default smsc charset that is defined via
* alt-charset in smsc group and if MT is not binary
*/
if (msg->sms.coding == DC_7BIT || (msg->sms.coding == DC_UNDEF && octstr_len(msg->sms.udhdata) == 0)) {
/*
* consider 3 cases:
* a) data_coding 0xFX: encoding should always be GSM 03.38 charset
* b) data_coding 0x00: encoding may be converted according to alt-charset
* c) data_coding 0x00: assume GSM 03.38 charset if alt-charset is not defined
*/
if ((pdu->u.submit_sm.data_coding & 0xF0) ||
(pdu->u.submit_sm.data_coding == 0 && !smpp->alt_charset)) {
charset_utf8_to_gsm(pdu->u.submit_sm.short_message);
} else if (pdu->u.submit_sm.data_coding == 0 && smpp->alt_charset) {
/*
* convert to the given alternative charset
*/
if (charset_convert(pdu->u.submit_sm.short_message, SMPP_DEFAULT_CHARSET,
octstr_get_cstr(smpp->alt_charset)) != 0)
error(0, "Failed to convert msgdata from charset <%s> to <%s>, will send as is.",
SMPP_DEFAULT_CHARSET, octstr_get_cstr(smpp->alt_charset));
}
}
/* prepend udh if present */
if (octstr_len(msg->sms.udhdata)) {
octstr_insert(pdu->u.submit_sm.short_message, msg->sms.udhdata, 0);
}
pdu->u.submit_sm.sm_length = octstr_len(pdu->u.submit_sm.short_message);
/*
* check for validity and defered settings
* were message value has higher priiority then smsc config group value
* Note: we always send in UTC and just define "Time Difference" as 00 and
* direction '+'.
*/
validity = msg->sms.validity != SMS_PARAM_UNDEFINED ? msg->sms.validity : smpp->validityperiod;
if (validity != SMS_PARAM_UNDEFINED) {
struct tm tm = gw_gmtime(time(NULL) + validity * 60);
pdu->u.submit_sm.validity_period = octstr_format("%02d%02d%02d%02d%02d%02d000+",
tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
}
if (msg->sms.deferred != SMS_PARAM_UNDEFINED) {
struct tm tm = gw_gmtime(time(NULL) + msg->sms.deferred * 60);
pdu->u.submit_sm.schedule_delivery_time = octstr_format("%02d%02d%02d%02d%02d%02d000+",
tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
}
/* ask for the delivery reports if needed */
if (DLR_IS_SUCCESS_OR_FAIL(msg->sms.dlr_mask))
pdu->u.submit_sm.registered_delivery = 1;
else if (DLR_IS_FAIL(msg->sms.dlr_mask) && !DLR_IS_SUCCESS(msg->sms.dlr_mask))
pdu->u.submit_sm.registered_delivery = 2;
if (DLR_IS_INTERMEDIATE(msg->sms.dlr_mask))
pdu->u.submit_sm.registered_delivery += 16;
/* set priority */
if (msg->sms.priority >= 0 && msg->sms.priority <= 3)
pdu->u.submit_sm.priority_flag = msg->sms.priority;
else
pdu->u.submit_sm.priority_flag = smpp->priority;
/* set more messages to send */
if (smpp->version > 0x33 && msg->sms.msg_left > 0)
pdu->u.submit_sm.more_messages_to_send = 1;
dict_destroy(pdu->u.submit_sm.tlv);
pdu->u.submit_sm.tlv = meta_data_get_values(msg->sms.meta_data, "smpp");
return pdu;
}
static int send_enquire_link(SMPP *smpp, Connection *conn, long *last_sent)
{
SMPP_PDU *pdu;
Octstr *os;
int ret;
if (difftime(date_universal_now(),*last_sent) < smpp->enquire_link_interval)
return 0;
*last_sent = date_universal_now();
pdu = smpp_pdu_create(enquire_link, counter_increase(smpp->message_id_counter));
dump_pdu("Sending enquire link:", smpp->conn->id, pdu);
os = smpp_pdu_pack(smpp->conn->id, pdu);
if (os != NULL)
ret = conn_write(conn, os); /* Write errors checked by caller. */
else
ret = -1;
octstr_destroy(os);
smpp_pdu_destroy(pdu);
return ret;
}
static int send_gnack(SMPP *smpp, Connection *conn, long reason, unsigned long seq_num)
{
SMPP_PDU *pdu;
Octstr *os;
int ret;
pdu = smpp_pdu_create(generic_nack, seq_num);
pdu->u.generic_nack.command_status = reason;
dump_pdu("Sending generic_nack:", smpp->conn->id, pdu);
os = smpp_pdu_pack(smpp->conn->id, pdu);
if (os != NULL)
ret = conn_write(conn, os);
else
ret = -1;
octstr_destroy(os);
smpp_pdu_destroy(pdu);
return ret;
}
static int send_unbind(SMPP *smpp, Connection *conn)
{
SMPP_PDU *pdu;
Octstr *os;
int ret;
pdu = smpp_pdu_create(unbind, counter_increase(smpp->message_id_counter));
dump_pdu("Sending unbind:", smpp->conn->id, pdu);
os = smpp_pdu_pack(smpp->conn->id, pdu);
if (os != NULL)
ret = conn_write(conn, os);
else
ret = -1;
octstr_destroy(os);
smpp_pdu_destroy(pdu);
return ret;
}
static int send_pdu(Connection *conn, Octstr *id, SMPP_PDU *pdu)
{
Octstr *os;
int ret;
dump_pdu("Sending PDU:", id, pdu);
os = smpp_pdu_pack(id, pdu);
if (os) {
/* Caller checks for write errors later */
ret = conn_write(conn, os);
/* it's not a error if we still have data buffered */
ret = (ret == 1) ? 0 : ret;
} else
ret = -1;
octstr_destroy(os);
return ret;
}
static int send_messages(SMPP *smpp, Connection *conn, long *pending_submits)
{
Msg *msg;
SMPP_PDU *pdu;
Octstr *os;
if (*pending_submits == -1)
return 0;
while (*pending_submits < smpp->max_pending_submits) {
/* check our throughput */
if (smpp->conn->throughput > 0 && load_get(smpp->load, 0) >= smpp->conn->throughput) {
debug("bb.sms.smpp", 0, "SMPP[%s]: throughput limit exceeded (%.02f,%.02f)",
octstr_get_cstr(smpp->conn->id), load_get(smpp->load, 0), smpp->conn->throughput);
break;
}
debug("bb.sms.smpp", 0, "SMPP[%s]: throughput (%.02f,%.02f)",
octstr_get_cstr(smpp->conn->id), load_get(smpp->load, 0), smpp->conn->throughput);
/* Get next message, quit if none to be sent */
msg = gw_prioqueue_remove(smpp->msgs_to_send);
if (msg == NULL)
break;
/* Send PDU, record it as waiting for ack from SMS center */
pdu = msg_to_pdu(smpp, msg);
if (pdu == NULL) {
bb_smscconn_send_failed(smpp->conn, msg, SMSCCONN_FAILED_MALFORMED, octstr_create("MALFORMED SMS"));
continue;
}
/* check for write errors */
if (send_pdu(conn, smpp->conn->id, pdu) == 0) {
struct smpp_msg *smpp_msg = smpp_msg_create(msg);
os = octstr_format("%ld", pdu->u.submit_sm.sequence_number);
dict_put(smpp->sent_msgs, os, smpp_msg);
smpp_pdu_destroy(pdu);
octstr_destroy(os);
++(*pending_submits);
load_increase(smpp->load);
}
else { /* write error occurs */
smpp_pdu_destroy(pdu);
bb_smscconn_send_failed(smpp->conn, msg, SMSCCONN_FAILED_TEMPORARILY, NULL);
return -1;
}
}
return 0;
}
/*
* Open transmission connection to SMS center. Return NULL for error,
* open Connection for OK. Caller must set smpp->conn->status correctly
* before calling this.
*/
static Connection *open_transmitter(SMPP *smpp)
{
SMPP_PDU *bind;
Connection *conn;
#ifdef HAVE_LIBSSL
if (smpp->use_ssl)
conn = conn_open_ssl(smpp->host, smpp->transmit_port, NULL, smpp->conn->our_host);
else
#endif
conn = conn_open_tcp(smpp->host, smpp->transmit_port, smpp->conn->our_host);
if (conn == NULL) {
error(0, "SMPP[%s]: Couldn't connect to server.",
octstr_get_cstr(smpp->conn->id));
return NULL;
}
bind = smpp_pdu_create(bind_transmitter,
counter_increase(smpp->message_id_counter));
bind->u.bind_transmitter.system_id = octstr_duplicate(smpp->username);
bind->u.bind_transmitter.password = octstr_duplicate(smpp->password);
if (smpp->system_type == NULL)
bind->u.bind_transmitter.system_type = octstr_create("VMA");
else
bind->u.bind_transmitter.system_type =
octstr_duplicate(smpp->system_type);
bind->u.bind_transmitter.interface_version = smpp->version;
bind->u.bind_transmitter.address_range =
octstr_duplicate(smpp->address_range);
bind->u.bind_transmitter.addr_ton = smpp->bind_addr_ton;
bind->u.bind_transmitter.addr_npi = smpp->bind_addr_npi;
if (send_pdu(conn, smpp->conn->id, bind) == -1) {
error(0, "SMPP[%s]: Couldn't send bind_transmitter to server.",
octstr_get_cstr(smpp->conn->id));
conn_destroy(conn);
conn = NULL;
}
smpp_pdu_destroy(bind);
return conn;
}
/*
* Open transceiver connection to SMS center. Return NULL for error,
* open Connection for OK. Caller must set smpp->conn->status correctly
* before calling this.
*/
static Connection *open_transceiver(SMPP *smpp)
{
SMPP_PDU *bind;
Connection *conn;
#ifdef HAVE_LIBSSL
if (smpp->use_ssl)
conn = conn_open_ssl(smpp->host, smpp->transmit_port, NULL, smpp->conn->our_host);
else
#endif
conn = conn_open_tcp(smpp->host, smpp->transmit_port, smpp->conn->our_host);
if (conn == NULL) {
error(0, "SMPP[%s]: Couldn't connect to server.",
octstr_get_cstr(smpp->conn->id));
return NULL;
}
bind = smpp_pdu_create(bind_transceiver,
counter_increase(smpp->message_id_counter));
bind->u.bind_transceiver.system_id = octstr_duplicate(smpp->username);
bind->u.bind_transceiver.password = octstr_duplicate(smpp->password);
if (smpp->system_type == NULL)
bind->u.bind_transceiver.system_type = octstr_create("VMA");
else
bind->u.bind_transceiver.system_type = octstr_duplicate(smpp->system_type);
bind->u.bind_transceiver.interface_version = smpp->version;
bind->u.bind_transceiver.address_range = octstr_duplicate(smpp->address_range);
bind->u.bind_transceiver.addr_ton = smpp->bind_addr_ton;
bind->u.bind_transceiver.addr_npi = smpp->bind_addr_npi;
if (send_pdu(conn, smpp->conn->id, bind) == -1) {
error(0, "SMPP[%s]: Couldn't send bind_transceiver to server.",
octstr_get_cstr(smpp->conn->id));
conn_destroy(conn);
conn = NULL;
}
smpp_pdu_destroy(bind);
return conn;
}
/*
* Open reception connection to SMS center. Return NULL for error,
* open Connection for OK. Caller must set smpp->conn->status correctly
* before calling this.
*/
static Connection *open_receiver(SMPP *smpp)
{
SMPP_PDU *bind;
Connection *conn;
#ifdef HAVE_LIBSSL
if (smpp->use_ssl)
conn = conn_open_ssl(smpp->host, smpp->receive_port, NULL, smpp->conn->our_host);
else
#endif
conn = conn_open_tcp(smpp->host, smpp->receive_port, smpp->conn->our_host);
if (conn == NULL) {
error(0, "SMPP[%s]: Couldn't connect to server.",
octstr_get_cstr(smpp->conn->id));
return NULL;
}
bind = smpp_pdu_create(bind_receiver,
counter_increase(smpp->message_id_counter));
bind->u.bind_receiver.system_id = octstr_duplicate(smpp->username);
bind->u.bind_receiver.password = octstr_duplicate(smpp->password);
if (smpp->system_type == NULL)
bind->u.bind_receiver.system_type = octstr_create("VMA");
else
bind->u.bind_receiver.system_type =
octstr_duplicate(smpp->system_type);
bind->u.bind_receiver.interface_version = smpp->version;
bind->u.bind_receiver.address_range =
octstr_duplicate(smpp->address_range);
bind->u.bind_receiver.addr_ton = smpp->bind_addr_ton;
bind->u.bind_receiver.addr_npi = smpp->bind_addr_npi;
if (send_pdu(conn, smpp->conn->id, bind) == -1) {
error(0, "SMPP[%s]: Couldn't send bind_receiver to server.",
octstr_get_cstr(smpp->conn->id));
conn_destroy(conn);
conn = NULL;
}
smpp_pdu_destroy(bind);
return conn;
}
static Msg *handle_dlr(SMPP *smpp, Octstr *destination_addr, Octstr *short_message, Octstr *message_payload, Octstr *receipted_message_id, long message_state, Octstr *network_error_code)
{
Msg *dlrmsg = NULL;
Octstr *respstr = NULL, *msgid = NULL, *err = NULL, *tmp;
int dlrstat = -1;
/* first check for SMPP v3.4 and above */
if (smpp->version > 0x33 && receipted_message_id) {
msgid = octstr_duplicate(receipted_message_id);
switch(message_state) {
case 1: /* ENROUTE */
case 6: /* ACCEPTED */
dlrstat = DLR_BUFFERED;
break;
case 2: /* DELIVERED */
dlrstat = DLR_SUCCESS;
break;
case 3: /* EXPIRED */
case 4: /* DELETED */
case 5: /* UNDELIVERABLE */
case 7: /* UNKNOWN */
case 8: /* REJECTED */
dlrstat = DLR_FAIL;
break;
case -1: /* message state is not present, partial SMPP v3.4 */
debug("bb.sms.smpp", 0, "SMPP[%s]: Partial SMPP v3.4, receipted_message_id present but not message_state.",
octstr_get_cstr(smpp->conn->id));
dlrstat = -1;
break;
default:
warning(0, "SMPP[%s]: Got DLR with unknown 'message_state' (%ld).",
octstr_get_cstr(smpp->conn->id), message_state);
dlrstat = DLR_FAIL;
break;
}
}
if (network_error_code != NULL)
err = octstr_duplicate(network_error_code);
/* check for SMPP v.3.4. and message_payload */
if (smpp->version > 0x33 && octstr_len(short_message) == 0)
respstr = message_payload;
else
respstr = short_message;
if (msgid == NULL || err == NULL || dlrstat == -1) {
/* parse the respstr if it exists */
if (respstr) {
long curr = 0, vpos = 0;
Octstr *stat = NULL;
char id_cstr[65], stat_cstr[16], sub_d_cstr[15], done_d_cstr[15];
char err_cstr[4];
int sub, dlrvrd, ret;
/* get server message id */
/* first try sscanf way if thus failed then old way */
ret = sscanf(octstr_get_cstr(respstr),
"id:%64[^s] sub:%d dlvrd:%d submit date:%14[0-9] done "
"date:%14[0-9] stat:%15[^t^e] err:%3[^t]",
id_cstr, &sub, &dlrvrd, sub_d_cstr, done_d_cstr,
stat_cstr, err_cstr);
if (ret == 7) {
/* only if not already here */
if (msgid == NULL) {
msgid = octstr_create(id_cstr);
octstr_strip_blanks(msgid);
}
stat = octstr_create(stat_cstr);
octstr_strip_blanks(stat);
err = octstr_create(err_cstr);
octstr_strip_blanks(err);
} else {
debug("bb.sms.smpp", 0, "SMPP[%s]: Couldnot parse DLR string sscanf way,"
"fallback to old way. Please report!", octstr_get_cstr(smpp->conn->id));
/* only if not already here */
if (msgid == NULL) {
if ((curr = octstr_search(respstr, octstr_imm("id:"), 0)) != -1) {
vpos = octstr_search_char(respstr, ' ', curr);
if ((vpos-curr >0) && (vpos != -1))
msgid = octstr_copy(respstr, curr+3, vpos-curr-3);
} else {
msgid = NULL;
}
}
/* get err & status code */
if ((curr = octstr_search(respstr, octstr_imm("stat:"), 0)) != -1) {
vpos = octstr_search_char(respstr, ' ', curr);
if ((vpos-curr >0) && (vpos != -1))
stat = octstr_copy(respstr, curr+5, vpos-curr-5);
} else {
stat = NULL;
}
if ((curr = octstr_search(respstr, octstr_imm("err:"), 0)) != -1) {
vpos = octstr_search_char(respstr, ' ', curr);
if ((vpos-curr >0) && (vpos != -1))
err = octstr_copy(respstr, curr+4, vpos-curr-4);
} else {
err = NULL;
}
}
/*
* we get the following status:
* DELIVRD, ACCEPTD, EXPIRED, DELETED, UNDELIV, UNKNOWN, REJECTD
*
* Note: some buggy SMSC's send us immediately delivery notifications although
* we doesn't requested these.
*/
if (dlrstat == -1) {
if (stat != NULL && octstr_compare(stat, octstr_imm("DELIVRD")) == 0)
dlrstat = DLR_SUCCESS;
else if (stat != NULL && (octstr_compare(stat, octstr_imm("ACCEPTD")) == 0 ||
octstr_compare(stat, octstr_imm("ACKED")) == 0 ||
octstr_compare(stat, octstr_imm("BUFFRED")) == 0 ||
octstr_compare(stat, octstr_imm("BUFFERD")) == 0 ||
octstr_compare(stat, octstr_imm("ENROUTE")) == 0))
dlrstat = DLR_BUFFERED;
else
dlrstat = DLR_FAIL;
}
octstr_destroy(stat);
}
}
if (msgid != NULL && dlrstat != -1) {
/*
* Obey which SMPP msg_id type this SMSC is using, where we
* have the following semantics for the variable smpp_msg_id:
*
* bit 1: type for submit_sm_resp, bit 2: type for deliver_sm
*
* if bit is set value is hex otherwise dec
*
* 0x00 deliver_sm dec, submit_sm_resp dec
* 0x01 deliver_sm dec, submit_sm_resp hex
* 0x02 deliver_sm hex, submit_sm_resp dec
* 0x03 deliver_sm hex, submit_sm_resp hex
*
* Default behaviour is SMPP spec compliant, which means
* msg_ids should be C strings and hence non modified.
*/
if (smpp->smpp_msg_id_type == -1) {
/* the default, C string */
tmp = octstr_duplicate(msgid);
} else {
if ((smpp->smpp_msg_id_type & 0x02) ||
(!octstr_check_range(msgid, 0, octstr_len(msgid), gw_isdigit))) {
tmp = octstr_format("%llu", strtoll(octstr_get_cstr(msgid), NULL, 16));
} else {
tmp = octstr_format("%llu", strtoll(octstr_get_cstr(msgid), NULL, 10));
}
}
dlrmsg = dlr_find(smpp->conn->id,
tmp, /* smsc message id */
destination_addr, /* destination */
dlrstat, 0);
octstr_destroy(msgid);
} else
tmp = octstr_create("");
if (dlrmsg != NULL) {
/*
* we found the delivery report in our storage, so recode the
* message structure.
* The DLR trigger URL is indicated by msg->sms.dlr_url.
* Add the DLR error code to meta-data.
*/
dlrmsg->sms.msgdata = octstr_duplicate(respstr);
dlrmsg->sms.sms_type = report_mo;
dlrmsg->sms.account = octstr_duplicate(smpp->username);
if (err != NULL) {
if (dlrmsg->sms.meta_data == NULL) {
dlrmsg->sms.meta_data = octstr_create("");
}
meta_data_set_value(dlrmsg->sms.meta_data, "smpp", octstr_imm("dlr_err"), err, 1);
}
} else {
error(0,"SMPP[%s]: got DLR but could not find message or was not interested "
"in it id<%s> dst<%s>, type<%d>",
octstr_get_cstr(smpp->conn->id), octstr_get_cstr(tmp),
octstr_get_cstr(destination_addr), dlrstat);
}
octstr_destroy(tmp);
octstr_destroy(err);
return dlrmsg;
}
static long smscconn_failure_reason_to_smpp_status(long reason)
{
switch (reason) {
case SMSCCONN_FAILED_REJECTED:
return SMPP_ESME_RX_R_APPN;
case SMSCCONN_SUCCESS:
return SMPP_ESME_ROK;
case SMSCCONN_FAILED_QFULL:
case SMSCCONN_FAILED_TEMPORARILY:
return SMPP_ESME_RX_T_APPN;
}
return SMPP_ESME_RX_T_APPN;
}
static int handle_pdu(SMPP *smpp, Connection *conn, SMPP_PDU *pdu,
long *pending_submits)
{
SMPP_PDU *resp = NULL;
Octstr *os;
Msg *msg = NULL, *dlrmsg=NULL;
struct smpp_msg *smpp_msg = NULL;
long reason, cmd_stat;
int ret = 0;
switch (pdu->type) {
case data_sm:
resp = smpp_pdu_create(data_sm_resp, pdu->u.data_sm.sequence_number);
/*
* If SMSCConn stopped then send temp. error code
*/
mutex_lock(smpp->conn->flow_mutex);
if (smpp->conn->is_stopped) {
mutex_unlock(smpp->conn->flow_mutex);
resp->u.data_sm_resp.command_status = SMPP_ESME_RX_T_APPN;
break;
}
mutex_unlock(smpp->conn->flow_mutex);
/* got a deliver ack (DLR)?
* NOTE: following SMPP v3.4. spec. we are interested
* only on bits 2-5 (some SMSC's send 0x44, and it's
* spec. conforme)
*/
if (pdu->u.data_sm.esm_class & (0x04|0x08|0x20)) {
debug("bb.sms.smpp",0,"SMPP[%s] handle_pdu, got DLR",
octstr_get_cstr(smpp->conn->id));
dlrmsg = handle_dlr(smpp, pdu->u.data_sm.source_addr, NULL, pdu->u.data_sm.message_payload,
pdu->u.data_sm.receipted_message_id, pdu->u.data_sm.message_state, pdu->u.data_sm.network_error_code);
if (dlrmsg != NULL) {
if (dlrmsg->sms.meta_data == NULL)
dlrmsg->sms.meta_data = octstr_create("");
meta_data_set_values(dlrmsg->sms.meta_data, pdu->u.data_sm.tlv, "smpp", 0);
/* passing DLR to upper layer */
reason = bb_smscconn_receive(smpp->conn, dlrmsg);
} else {
/* no DLR will be passed, but we write an access-log entry */
reason = SMSCCONN_SUCCESS;
msg = data_sm_to_msg(smpp, pdu, &reason);
bb_alog_sms(smpp->conn, msg, "FAILED DLR SMS");
msg_destroy(msg);
}
resp->u.data_sm_resp.command_status = smscconn_failure_reason_to_smpp_status(reason);
} else { /* MO message */
msg = data_sm_to_msg(smpp, pdu, &reason);
if (msg == NULL || reason != SMPP_ESME_ROK) {
resp->u.data_sm_resp.command_status = reason;
break;
}
/* Replace MO destination number with my-number */
if (octstr_len(smpp->my_number)) {
octstr_destroy(msg->sms.receiver);
msg->sms.receiver = octstr_duplicate(smpp->my_number);
}
time(&msg->sms.time);
msg->sms.smsc_id = octstr_duplicate(smpp->conn->id);
reason = bb_smscconn_receive(smpp->conn, msg);
resp->u.data_sm_resp.command_status = smscconn_failure_reason_to_smpp_status(reason);
}
break;
case deliver_sm:
/*
* If SMSCConn stopped then send temp. error code
*/
mutex_lock(smpp->conn->flow_mutex);
if (smpp->conn->is_stopped) {
mutex_unlock(smpp->conn->flow_mutex);
resp = smpp_pdu_create(deliver_sm_resp,
pdu->u.deliver_sm.sequence_number);
resp->u.deliver_sm_resp.command_status = SMPP_ESME_RX_T_APPN;
break;
}
mutex_unlock(smpp->conn->flow_mutex);
/* got a deliver ack (DLR)?
* NOTE: following SMPP v3.4. spec. we are interested
* only on bits 2-5 (some SMSC's send 0x44, and it's
* spec. conforme)
*/
if (pdu->u.deliver_sm.esm_class & (0x04|0x08|0x20)) {
debug("bb.sms.smpp",0,"SMPP[%s] handle_pdu, got DLR",
octstr_get_cstr(smpp->conn->id));
dlrmsg = handle_dlr(smpp, pdu->u.deliver_sm.source_addr, pdu->u.deliver_sm.short_message, pdu->u.deliver_sm.message_payload,
pdu->u.deliver_sm.receipted_message_id, pdu->u.deliver_sm.message_state, pdu->u.deliver_sm.network_error_code);
resp = smpp_pdu_create(deliver_sm_resp, pdu->u.deliver_sm.sequence_number);
if (dlrmsg != NULL) {
if (dlrmsg->sms.meta_data == NULL)
dlrmsg->sms.meta_data = octstr_create("");
meta_data_set_values(dlrmsg->sms.meta_data, pdu->u.deliver_sm.tlv, "smpp", 0);
reason = bb_smscconn_receive(smpp->conn, dlrmsg);
} else
reason = SMSCCONN_SUCCESS;
resp->u.deliver_sm_resp.command_status = smscconn_failure_reason_to_smpp_status(reason);
} else {/* MO-SMS */
resp = smpp_pdu_create(deliver_sm_resp,
pdu->u.deliver_sm.sequence_number);
/* ensure the smsc-id is set */
msg = pdu_to_msg(smpp, pdu, &reason);
if (msg == NULL) {
resp->u.deliver_sm_resp.command_status = reason;
break;
}
/* Replace MO destination number with my-number */
if (octstr_len(smpp->my_number)) {
octstr_destroy(msg->sms.receiver);
msg->sms.receiver = octstr_duplicate(smpp->my_number);
}
time(&msg->sms.time);
msg->sms.smsc_id = octstr_duplicate(smpp->conn->id);
msg->sms.account = octstr_duplicate(smpp->username);
reason = bb_smscconn_receive(smpp->conn, msg);
resp->u.deliver_sm_resp.command_status = smscconn_failure_reason_to_smpp_status(reason);
}
break;
case enquire_link:
resp = smpp_pdu_create(enquire_link_resp,
pdu->u.enquire_link.sequence_number);
break;
case enquire_link_resp:
if (pdu->u.enquire_link_resp.command_status != 0) {
error(0, "SMPP[%s]: SMSC got error to enquire_link, code 0x%08lx (%s).",
octstr_get_cstr(smpp->conn->id),
pdu->u.enquire_link_resp.command_status,
smpp_error_to_string(pdu->u.enquire_link_resp.command_status));
}
break;
case submit_sm_resp:
os = octstr_format("%ld", pdu->u.submit_sm_resp.sequence_number);
smpp_msg = dict_remove(smpp->sent_msgs, os);
octstr_destroy(os);
if (smpp_msg == NULL) {
warning(0, "SMPP[%s]: SMSC sent submit_sm_resp "
"with wrong sequence number 0x%08lx",
octstr_get_cstr(smpp->conn->id),
pdu->u.submit_sm_resp.sequence_number);
break;
}
msg = smpp_msg->msg;
smpp_msg_destroy(smpp_msg, 0);
if (pdu->u.submit_sm_resp.command_status != 0) {
error(0, "SMPP[%s]: SMSC returned error code 0x%08lx (%s) "
"in response to submit_sm.",
octstr_get_cstr(smpp->conn->id),
pdu->u.submit_sm_resp.command_status,
smpp_error_to_string(pdu->u.submit_sm_resp.command_status));
reason = smpp_status_to_smscconn_failure_reason(
pdu->u.submit_sm_resp.command_status);
/*
* check to see if we got a "throttling error", in which case we'll just
* sleep for a while
*/
if (pdu->u.submit_sm_resp.command_status == SMPP_ESME_RTHROTTLED)
time(&(smpp->throttling_err_time));
else
smpp->throttling_err_time = 0;
bb_smscconn_send_failed(smpp->conn, msg, reason, octstr_format("0x%08lx/%s", pdu->u.submit_sm_resp.command_status,
smpp_error_to_string(pdu->u.submit_sm_resp.command_status)));
--(*pending_submits);
} else {
Octstr *tmp;
/* check if msg_id is C string, decimal or hex for this SMSC */
if (smpp->smpp_msg_id_type == -1) {
/* the default, C string */
tmp = octstr_duplicate(pdu->u.submit_sm_resp.message_id);
} else {
if ((smpp->smpp_msg_id_type & 0x01) ||
(!octstr_check_range(pdu->u.submit_sm_resp.message_id, 0,
octstr_len(pdu->u.submit_sm_resp.message_id), gw_isdigit))) {
tmp = octstr_format("%llu", strtoll( /* hex */
octstr_get_cstr(pdu->u.submit_sm_resp.message_id), NULL, 16));
} else {
tmp = octstr_format("%llu", strtoll( /* decimal */
octstr_get_cstr(pdu->u.submit_sm_resp.message_id), NULL, 10));
}
}
/* SMSC ACK.. now we have the message id. */
if (DLR_IS_ENABLED_DEVICE(msg->sms.dlr_mask))
dlr_add(smpp->conn->id, tmp, msg);
octstr_destroy(tmp);
bb_smscconn_sent(smpp->conn, msg, NULL);
--(*pending_submits);
} /* end if for SMSC ACK */
break;
case bind_transmitter_resp:
if (pdu->u.bind_transmitter_resp.command_status != 0 &&
pdu->u.bind_transmitter_resp.command_status != SMPP_ESME_RALYNBD) {
error(0, "SMPP[%s]: SMSC rejected login to transmit, code 0x%08lx (%s).",
octstr_get_cstr(smpp->conn->id),
pdu->u.bind_transmitter_resp.command_status,
smpp_error_to_string(pdu->u.bind_transmitter_resp.command_status));
mutex_lock(smpp->conn->flow_mutex);
smpp->conn->status = SMSCCONN_DISCONNECTED;
mutex_unlock(smpp->conn->flow_mutex);
if (pdu->u.bind_transmitter_resp.command_status == SMPP_ESME_RINVSYSID ||
pdu->u.bind_transmitter_resp.command_status == SMPP_ESME_RINVPASWD ||
pdu->u.bind_transmitter_resp.command_status == SMPP_ESME_RINVSYSTYP) {
smpp->quitting = 1;
}
} else {
*pending_submits = 0;
mutex_lock(smpp->conn->flow_mutex);
smpp->conn->status = SMSCCONN_ACTIVE;
time(&smpp->conn->connect_time);
mutex_unlock(smpp->conn->flow_mutex);
bb_smscconn_connected(smpp->conn);
}
break;
case bind_transceiver_resp:
if (pdu->u.bind_transceiver_resp.command_status != 0 &&
pdu->u.bind_transceiver_resp.command_status != SMPP_ESME_RALYNBD) {
error(0, "SMPP[%s]: SMSC rejected login to transmit, code 0x%08lx (%s).",
octstr_get_cstr(smpp->conn->id),
pdu->u.bind_transceiver_resp.command_status,
smpp_error_to_string(pdu->u.bind_transceiver_resp.command_status));
mutex_lock(smpp->conn->flow_mutex);
smpp->conn->status = SMSCCONN_DISCONNECTED;
mutex_unlock(smpp->conn->flow_mutex);
if (pdu->u.bind_transceiver_resp.command_status == SMPP_ESME_RINVSYSID ||
pdu->u.bind_transceiver_resp.command_status == SMPP_ESME_RINVPASWD ||
pdu->u.bind_transceiver_resp.command_status == SMPP_ESME_RINVSYSTYP) {
smpp->quitting = 1;
}
} else {
*pending_submits = 0;
mutex_lock(smpp->conn->flow_mutex);
smpp->conn->status = SMSCCONN_ACTIVE;
time(&smpp->conn->connect_time);
mutex_unlock(smpp->conn->flow_mutex);
bb_smscconn_connected(smpp->conn);
}
break;
case bind_receiver_resp:
if (pdu->u.bind_receiver_resp.command_status != 0 &&
pdu->u.bind_receiver_resp.command_status != SMPP_ESME_RALYNBD) {
error(0, "SMPP[%s]: SMSC rejected login to receive, code 0x%08lx (%s).",
octstr_get_cstr(smpp->conn->id),
pdu->u.bind_receiver_resp.command_status,
smpp_error_to_string(pdu->u.bind_receiver_resp.command_status));
mutex_lock(smpp->conn->flow_mutex);
smpp->conn->status = SMSCCONN_DISCONNECTED;
mutex_unlock(smpp->conn->flow_mutex);
if (pdu->u.bind_receiver_resp.command_status == SMPP_ESME_RINVSYSID ||
pdu->u.bind_receiver_resp.command_status == SMPP_ESME_RINVPASWD ||
pdu->u.bind_receiver_resp.command_status == SMPP_ESME_RINVSYSTYP) {
smpp->quitting = 1;
}
} else {
/* set only receive status if no transmitt is bind */
mutex_lock(smpp->conn->flow_mutex);
if (smpp->conn->status != SMSCCONN_ACTIVE) {
smpp->conn->status = SMSCCONN_ACTIVE_RECV;
time(&smpp->conn->connect_time);
}
mutex_unlock(smpp->conn->flow_mutex);
}
break;
case unbind:
resp = smpp_pdu_create(unbind_resp, pdu->u.unbind.sequence_number);
mutex_lock(smpp->conn->flow_mutex);
smpp->conn->status = SMSCCONN_DISCONNECTED;
mutex_unlock(smpp->conn->flow_mutex);
*pending_submits = -1;
break;
case unbind_resp:
mutex_lock(smpp->conn->flow_mutex);
smpp->conn->status = SMSCCONN_DISCONNECTED;
mutex_unlock(smpp->conn->flow_mutex);
break;
case generic_nack:
cmd_stat = pdu->u.generic_nack.command_status;
os = octstr_format("%ld", pdu->u.generic_nack.sequence_number);
smpp_msg = dict_remove(smpp->sent_msgs, os);
octstr_destroy(os);
if (smpp_msg == NULL) {
error(0, "SMPP[%s]: SMSC rejected last command, code 0x%08lx (%s).",
octstr_get_cstr(smpp->conn->id),
cmd_stat,
smpp_error_to_string(cmd_stat));
} else {
msg = smpp_msg->msg;
smpp_msg_destroy(smpp_msg, 0);
error(0, "SMPP[%s]: SMSC returned error code 0x%08lx (%s) in response to submit_sm.",
octstr_get_cstr(smpp->conn->id),
cmd_stat,
smpp_error_to_string(cmd_stat));
/*
* check to see if we got a "throttling error", in which case we'll just
* sleep for a while
*/
if (cmd_stat == SMPP_ESME_RTHROTTLED)
time(&(smpp->throttling_err_time));
else
smpp->throttling_err_time = 0;
reason = smpp_status_to_smscconn_failure_reason(cmd_stat);
bb_smscconn_send_failed(smpp->conn, msg, reason,
octstr_format("0x%08lx/%s", cmd_stat, smpp_error_to_string(cmd_stat)));
--(*pending_submits);
}
break;
default:
error(0, "SMPP[%s]: Unknown PDU type 0x%08lx, ignored.",
octstr_get_cstr(smpp->conn->id), pdu->type);
/*
* We received an unknown PDU type, therefore we will respond
* with a generic_nack PDU, see SMPP v3.4 spec, section 3.3.
*/
ret = send_gnack(smpp, conn, SMPP_ESME_RINVCMDID, pdu->u.generic_nack.sequence_number);
break;
}
if (resp != NULL) {
ret = send_pdu(conn, smpp->conn->id, resp) != -1 ? 0 : -1;
smpp_pdu_destroy(resp);
}
return ret;
}
struct io_arg {
SMPP *smpp;
int transmitter;
};
static struct io_arg *io_arg_create(SMPP *smpp, int transmitter)
{
struct io_arg *io_arg;
io_arg = gw_malloc(sizeof(*io_arg));
io_arg->smpp = smpp;
io_arg->transmitter = transmitter;
return io_arg;
}
/*
* sent queue cleanup.
* @return 1 if io_thread should reconnect; 0 if not
*/
static int do_queue_cleanup(SMPP *smpp, long *pending_submits)
{
List *keys;
Octstr *key;
struct smpp_msg *smpp_msg;
time_t now = time(NULL);
if (*pending_submits <= 0)
return 0;
/* check if action set to wait ack for ever */
if (smpp->wait_ack_action == SMPP_WAITACK_NEVER_EXPIRE)
return 0;
keys = dict_keys(smpp->sent_msgs);
if (keys == NULL)
return 0;
while ((key = gwlist_extract_first(keys)) != NULL) {
smpp_msg = dict_get(smpp->sent_msgs, key);
if (smpp_msg != NULL && difftime(now, smpp_msg->sent_time) > smpp->wait_ack) {
switch(smpp->wait_ack_action) {
case SMPP_WAITACK_RECONNECT: /* reconnect */
/* found at least one not acked msg */
warning(0, "SMPP[%s]: Not ACKED message found, reconnecting.",
octstr_get_cstr(smpp->conn->id));
octstr_destroy(key);
gwlist_destroy(keys, octstr_destroy_item);
return 1; /* io_thread will reconnect */
case SMPP_WAITACK_REQUEUE: /* requeue */
smpp_msg = dict_remove(smpp->sent_msgs, key);
if (smpp_msg != NULL) {
warning(0, "SMPP[%s]: Not ACKED message found, will retransmit."
" SENT<%ld>sec. ago, SEQ<%s>, DST<%s>",
octstr_get_cstr(smpp->conn->id),
(long)difftime(now, smpp_msg->sent_time) ,
octstr_get_cstr(key),
octstr_get_cstr(smpp_msg->msg->sms.receiver));
bb_smscconn_send_failed(smpp->conn, smpp_msg->msg, SMSCCONN_FAILED_TEMPORARILY,NULL);
smpp_msg_destroy(smpp_msg, 0);
(*pending_submits)--;
}
break;
default:
error(0, "SMPP[%s] Unknown clenup action defined 0x%02x.",
octstr_get_cstr(smpp->conn->id), smpp->wait_ack_action);
octstr_destroy(key);
gwlist_destroy(keys, octstr_destroy_item);
return 0;
}
}
octstr_destroy(key);
}
gwlist_destroy(keys, octstr_destroy_item);
return 0;
}
/*
* This is the main function for the background thread for doing I/O on
* one SMPP connection (the one for transmitting or receiving messages).
* It makes the initial connection to the SMPP server and re-connects
* if there are I/O errors or other errors that require it.
*/
static void io_thread(void *arg)
{
SMPP *smpp;
struct io_arg *io_arg;
int transmitter;
Connection *conn;
int ret;
long pending_submits;
long len;
SMPP_PDU *pdu;
double timeout;
time_t last_cleanup, last_enquire_sent, last_response, now;
io_arg = arg;
smpp = io_arg->smpp;
transmitter = io_arg->transmitter;
gw_free(io_arg);
/* Make sure we log into our own log-file if defined */
log_thread_to(smpp->conn->log_idx);
#define IS_ACTIVE (smpp->conn->status == SMSCCONN_ACTIVE || smpp->conn->status == SMSCCONN_ACTIVE_RECV)
conn = NULL;
while (!smpp->quitting) {
if (transmitter == 1)
conn = open_transmitter(smpp);
else if (transmitter == 2)
conn = open_transceiver(smpp);
else
conn = open_receiver(smpp);
pending_submits = -1;
len = 0;
last_response = last_cleanup = last_enquire_sent = time(NULL);
while(conn != NULL) {
ret = read_pdu(smpp, conn, &len, &pdu);
if (ret == -1) { /* connection broken */
error(0, "SMPP[%s]: I/O error or other error. Re-connecting.",
octstr_get_cstr(smpp->conn->id));
break;
} else if (ret == -2) {
/* wrong pdu length , send gnack */
len = 0;
if (send_gnack(smpp, conn, SMPP_ESME_RINVCMDLEN, 0) == -1) {
error(0, "SMPP[%s]: I/O error or other error. Re-connecting.",
octstr_get_cstr(smpp->conn->id));
break;
}
} else if (ret == 1) { /* data available */
/* Deal with the PDU we just got */
dump_pdu("Got PDU:", smpp->conn->id, pdu);
ret = handle_pdu(smpp, conn, pdu, &pending_submits);
smpp_pdu_destroy(pdu);
if (ret == -1) {
error(0, "SMPP[%s]: I/O error or other error. Re-connecting.",
octstr_get_cstr(smpp->conn->id));
break;
}
/*
* check if we are still connected
* Note: Function handle_pdu will set status to SMSCCONN_DISCONNECTED
* when unbind was received.
*/
if (smpp->conn->status == SMSCCONN_DISCONNECTED)
break;
/*
* If we are not bounded then no PDU may coming from SMSC.
* It's just a workaround for buggy SMSC's whoes send enquire_link's
* although link is not bounded. Means: we doesn't notice these and if link
* keep to be not bounden we are reconnect after defined timeout elapsed.
*/
if (IS_ACTIVE) {
/*
* Store last response time.
*/
time(&last_response);
}
} else { /* no data available */
/* check last enquire_resp, if difftime > as idle_timeout
* mark connection as broken.
* We have some SMSC connections where connection seems to be OK, but
* in reallity is broken, because no responses received.
*/
if (smpp->connection_timeout > 0 &&
difftime(time(NULL), last_response) > smpp->connection_timeout) {
/* connection seems to be broken */
warning(0, "Got no responses within %ld sec., reconnecting...",
(long) difftime(time(NULL), last_response));
break;
}
time(&now);
timeout = last_enquire_sent + smpp->enquire_link_interval - now;
if (!IS_ACTIVE && timeout <= 0)
timeout = smpp->enquire_link_interval;
if (transmitter && gw_prioqueue_len(smpp->msgs_to_send) > 0 &&
smpp->throttling_err_time > 0 && pending_submits < smpp->max_pending_submits) {
time_t tr_timeout = smpp->throttling_err_time + SMPP_THROTTLING_SLEEP_TIME - now;
timeout = timeout > tr_timeout ? tr_timeout : timeout;
} else if (transmitter && gw_prioqueue_len(smpp->msgs_to_send) > 0 && smpp->conn->throughput > 0 &&
smpp->max_pending_submits > pending_submits) {
double t = 1.0 / smpp->conn->throughput;
timeout = t < timeout ? t : timeout;
}
/* sleep a while */
if (timeout > 0 && conn_wait(conn, timeout) == -1)
break;
}
/* send enquire link, only if connection is active */
if (IS_ACTIVE && send_enquire_link(smpp, conn, &last_enquire_sent) == -1)
break;
/* cleanup sent queue */
if (transmitter && difftime(time(NULL), last_cleanup) > smpp->wait_ack) {
if (do_queue_cleanup(smpp, &pending_submits))
break; /* reconnect */
time(&last_cleanup);
}
/* make sure we send */
if (transmitter && difftime(time(NULL), smpp->throttling_err_time) > SMPP_THROTTLING_SLEEP_TIME) {
smpp->throttling_err_time = 0;
if (send_messages(smpp, conn, &pending_submits) == -1)
break;
}
/* unbind
* Read so long as unbind_resp received or timeout passed. Otherwise we have
* double delivered messages.
*/
if (smpp->quitting) {
if (!IS_ACTIVE || send_unbind(smpp, conn) == -1)
break;
time(&last_response);
while(conn_wait(conn, 1.00) != -1 && IS_ACTIVE &&
difftime(time(NULL), last_response) < SMPP_DEFAULT_SHUTDOWN_TIMEOUT) {
if (read_pdu(smpp, conn, &len, &pdu) == 1) {
dump_pdu("Got PDU:", smpp->conn->id, pdu);
handle_pdu(smpp, conn, pdu, &pending_submits);
smpp_pdu_destroy(pdu);
}
}
debug("bb.sms.smpp", 0, "SMPP[%s]: %s: break and shutting down",
octstr_get_cstr(smpp->conn->id), __PRETTY_FUNCTION__);
break;
}
}
if (conn != NULL) {
conn_destroy(conn);
conn = NULL;
}
/* set reconnecting status first so that core don't put msgs into our queue */
if (!smpp->quitting) {
error(0, "SMPP[%s]: Couldn't connect to SMS center (retrying in %ld seconds).",
octstr_get_cstr(smpp->conn->id), smpp->conn->reconnect_delay);
mutex_lock(smpp->conn->flow_mutex);
smpp->conn->status = SMSCCONN_RECONNECTING;
mutex_unlock(smpp->conn->flow_mutex);
gwthread_sleep(smpp->conn->reconnect_delay);
}
/*
* put all queued messages back into global queue,so if
* we have another link running than messages will be delivered
* quickly
*/
if (transmitter) {
Msg *msg;
struct smpp_msg *smpp_msg;
List *noresp;
Octstr *key;
long reason = (smpp->quitting?SMSCCONN_FAILED_SHUTDOWN:SMSCCONN_FAILED_TEMPORARILY);
while((msg = gw_prioqueue_remove(smpp->msgs_to_send)) != NULL)
bb_smscconn_send_failed(smpp->conn, msg, reason, NULL);
noresp = dict_keys(smpp->sent_msgs);
while((key = gwlist_extract_first(noresp)) != NULL) {
smpp_msg = dict_remove(smpp->sent_msgs, key);
if (smpp_msg != NULL) {
bb_smscconn_send_failed(smpp->conn, smpp_msg->msg, reason, NULL);
smpp_msg_destroy(smpp_msg, 0);
}
octstr_destroy(key);
}
gwlist_destroy(noresp, NULL);
}
}
#undef IS_ACTIVE
/*
* Shutdown sequence as follow:
* 1) if this is TX session so join receiver and free SMPP
* 2) if RX session available but no TX session so nothing to join then free SMPP
*/
if (transmitter && smpp->receiver != -1) {
gwthread_wakeup(smpp->receiver);
gwthread_join(smpp->receiver);
}
if (transmitter || smpp->transmitter == -1) {
debug("bb.smpp", 0, "SMSCConn %s shut down.",
octstr_get_cstr(smpp->conn->name));
mutex_lock(smpp->conn->flow_mutex);
smpp->conn->status = SMSCCONN_DEAD;
smpp->conn->data = NULL;
mutex_unlock(smpp->conn->flow_mutex);
smpp_destroy(smpp);
bb_smscconn_killed();
}
}
/***********************************************************************
* Functions called by smscconn.c via the SMSCConn function pointers.
*/
static long queued_cb(SMSCConn *conn)
{
SMPP *smpp;
smpp = conn->data;
conn->load = (smpp ? (conn->status != SMSCCONN_DEAD ?
gw_prioqueue_len(smpp->msgs_to_send) : 0) : 0);
return conn->load;
}
static int send_msg_cb(SMSCConn *conn, Msg *msg)
{
SMPP *smpp;
smpp = conn->data;
gw_prioqueue_produce(smpp->msgs_to_send, msg_duplicate(msg));
gwthread_wakeup(smpp->transmitter);
return 0;
}
static int shutdown_cb(SMSCConn *conn, int finish_sending)
{
SMPP *smpp;
if (conn == NULL)
return -1;
debug("bb.smpp", 0, "Shutting down SMSCConn %s (%s)",
octstr_get_cstr(conn->name),
finish_sending ? "slow" : "instant");
mutex_lock(conn->flow_mutex);
conn->why_killed = SMSCCONN_KILLED_SHUTDOWN;
smpp = conn->data;
if (smpp == NULL) {
mutex_unlock(conn->flow_mutex);
return 0;
}
smpp->quitting = 1;
if (smpp->transmitter != -1)
gwthread_wakeup(smpp->transmitter);
if (smpp->receiver != -1)
gwthread_wakeup(smpp->receiver);
mutex_unlock(conn->flow_mutex);
return 0;
}
/***********************************************************************
* Public interface. This version is suitable for the Kannel bearerbox
* SMSCConn interface.
*/
int smsc_smpp_create(SMSCConn *conn, CfgGroup *grp)
{
Octstr *host;
long port;
long receive_port;
Octstr *username;
Octstr *password;
Octstr *system_id;
Octstr *system_type;
Octstr *address_range;
long source_addr_ton;
long source_addr_npi;
long dest_addr_ton;
long dest_addr_npi;
Octstr *my_number;
Octstr *service_type;
SMPP *smpp;
int ok;
int transceiver_mode;
Octstr *smsc_id;
long enquire_link_interval;
long max_pending_submits;
long version;
long priority;
long validity;
long smpp_msg_id_type;
int autodetect_addr;
Octstr *alt_charset;
Octstr *alt_addr_charset;
long connection_timeout, wait_ack, wait_ack_action;
my_number = alt_addr_charset = alt_charset = NULL;
transceiver_mode = 0;
autodetect_addr = 1;
host = cfg_get(grp, octstr_imm("host"));
if (cfg_get_integer(&port, grp, octstr_imm("port")) == -1)
port = 0;
if (cfg_get_integer(&receive_port, grp, octstr_imm("receive-port")) == -1)
receive_port = 0;
cfg_get_bool(&transceiver_mode, grp, octstr_imm("transceiver-mode"));
username = cfg_get(grp, octstr_imm("smsc-username"));
password = cfg_get(grp, octstr_imm("smsc-password"));
system_type = cfg_get(grp, octstr_imm("system-type"));
address_range = cfg_get(grp, octstr_imm("address-range"));
my_number = cfg_get(grp, octstr_imm("my-number"));
service_type = cfg_get(grp, octstr_imm("service-type"));
system_id = cfg_get(grp, octstr_imm("system-id"));
if (system_id != NULL) {
warning(0, "SMPP: obsolete system-id variable is set, "
"use smsc-username instead.");
if (username == NULL) {
warning(0, "SMPP: smsc-username not set, using system-id instead");
username = system_id;
} else
octstr_destroy(system_id);
}
/*
* check if timing values have been configured, otherwise
* use the predefined default values.
*/
if (cfg_get_integer(&enquire_link_interval, grp,
octstr_imm("enquire-link-interval")) == -1)
enquire_link_interval = SMPP_ENQUIRE_LINK_INTERVAL;
if (cfg_get_integer(&max_pending_submits, grp,
octstr_imm("max-pending-submits")) == -1)
max_pending_submits = SMPP_MAX_PENDING_SUBMITS;
/* Check that config is OK */
ok = 1;
if (host == NULL) {
error(0,"SMPP: Configuration file doesn't specify host");
ok = 0;
}
if (username == NULL) {
error(0, "SMPP: Configuration file doesn't specify username.");
ok = 0;
}
if (password == NULL) {
error(0, "SMPP: Configuration file doesn't specify password.");
ok = 0;
}
if (system_type == NULL) {
error(0, "SMPP: Configuration file doesn't specify system-type.");
ok = 0;
}
if (octstr_len(service_type) > 6) {
error(0, "SMPP: Service type must be 6 characters or less.");
ok = 0;
}
if (transceiver_mode && receive_port != 0) {
warning(0, "SMPP: receive-port for transceiver mode defined, ignoring.");
receive_port = 0;
}
if (!ok)
return -1;
/* if the ton and npi values are forced, set them, else set them to -1 */
if (cfg_get_integer(&source_addr_ton, grp,
octstr_imm("source-addr-ton")) == -1)
source_addr_ton = -1;
if (cfg_get_integer(&source_addr_npi, grp,
octstr_imm("source-addr-npi")) == -1)
source_addr_npi = -1;
if (cfg_get_integer(&dest_addr_ton, grp,
octstr_imm("dest-addr-ton")) == -1)
dest_addr_ton = -1;
if (cfg_get_integer(&dest_addr_npi, grp,
octstr_imm("dest-addr-npi")) == -1)
dest_addr_npi = -1;
/* if source addr autodetection should be used set this to 1 */
if (cfg_get_bool(&autodetect_addr, grp, octstr_imm("source-addr-autodetect")) == -1)
autodetect_addr = 1; /* default is autodetect if no option defined */
/* check for any specified interface version */
if (cfg_get_integer(&version, grp, octstr_imm("interface-version")) == -1)
version = SMPP_DEFAULT_VERSION;
else
/* convert decimal to BCD */
version = ((version / 10) << 4) + (version % 10);
/* check for any specified priority value in range [0-5] */
if (cfg_get_integer(&priority, grp, octstr_imm("priority")) == -1)
priority = SMPP_DEFAULT_PRIORITY;
else if (priority < 0 || priority > 3)
panic(0, "SMPP: Invalid value for priority directive in configuraton (allowed range 0-3).");
/* check for message validity period */
if (cfg_get_integer(&validity, grp, octstr_imm("validityperiod")) == -1)
validity = -1;
else if (validity < 0)
panic(0, "SMPP: Invalid value for validity period (allowed value >= 0).");
/* set the msg_id type variable for this SMSC */
if (cfg_get_integer(&smpp_msg_id_type, grp, octstr_imm("msg-id-type")) == -1) {
/*
* defaults to C string "as-is" style
*/
smpp_msg_id_type = -1;
} else {
if (smpp_msg_id_type < 0 || smpp_msg_id_type > 3)
panic(0,"SMPP: Invalid value for msg-id-type directive in configuraton");
}
/* check for an alternative charset */
alt_charset = cfg_get(grp, octstr_imm("alt-charset"));
alt_addr_charset = cfg_get(grp, octstr_imm("alt-addr-charset"));
/* check for connection timeout */
if (cfg_get_integer(&connection_timeout, grp, octstr_imm("connection-timeout")) == -1)
connection_timeout = SMPP_DEFAULT_CONNECTION_TIMEOUT;
/* check if wait-ack timeout set */
if (cfg_get_integer(&wait_ack, grp, octstr_imm("wait-ack")) == -1)
wait_ack = SMPP_DEFAULT_WAITACK;
if (cfg_get_integer(&wait_ack_action, grp, octstr_imm("wait-ack-expire")) == -1)
wait_ack_action = SMPP_WAITACK_REQUEUE;
if (wait_ack_action > 0x03 || wait_ack_action < 0)
panic(0, "SMPP: Invalid wait-ack-expire directive in configuration.");
smpp = smpp_create(conn, host, port, receive_port, system_type,
username, password, address_range,
source_addr_ton, source_addr_npi, dest_addr_ton,
dest_addr_npi, enquire_link_interval,
max_pending_submits, version, priority, validity, my_number,
smpp_msg_id_type, autodetect_addr, alt_charset, alt_addr_charset,
service_type, connection_timeout, wait_ack, wait_ack_action);
cfg_get_integer(&smpp->bind_addr_ton, grp, octstr_imm("bind-addr-ton"));
cfg_get_integer(&smpp->bind_addr_npi, grp, octstr_imm("bind-addr-npi"));
cfg_get_bool(&smpp->use_ssl, grp, octstr_imm("use-ssl"));
#ifndef HAVE_LIBSSL
if (smpp->use_ssl)
panic(0, "SMPP: Can not use 'use-ssl' without SSL support compiled in.");
#endif
conn->data = smpp;
conn->name = octstr_format("SMPP:%S:%d/%d:%S:%S",
host, port,
(!receive_port && transceiver_mode ? port : receive_port),
username, system_type);
smsc_id = cfg_get(grp, octstr_imm("smsc-id"));
if (smsc_id == NULL) {
conn->id = octstr_duplicate(conn->name);
}
octstr_destroy(host);
octstr_destroy(username);
octstr_destroy(password);
octstr_destroy(system_type);
octstr_destroy(address_range);
octstr_destroy(my_number);
octstr_destroy(smsc_id);
octstr_destroy(alt_charset);
octstr_destroy(alt_addr_charset);
octstr_destroy(service_type);
conn->status = SMSCCONN_CONNECTING;
/*
* I/O threads are only started if the corresponding ports
* have been configured with positive numbers. Use 0 to
* disable the creation of the corresponding thread.
*/
if (port != 0)
smpp->transmitter = gwthread_create(io_thread, io_arg_create(smpp,
(transceiver_mode ? 2 : 1)));
if (receive_port != 0)
smpp->receiver = gwthread_create(io_thread, io_arg_create(smpp, 0));
if ((port != 0 && smpp->transmitter == -1) ||
(receive_port != 0 && smpp->receiver == -1)) {
error(0, "SMPP[%s]: Couldn't start I/O threads.",
octstr_get_cstr(smpp->conn->id));
smpp->quitting = 1;
if (smpp->transmitter != -1) {
gwthread_wakeup(smpp->transmitter);
gwthread_join(smpp->transmitter);
}
if (smpp->receiver != -1) {
gwthread_wakeup(smpp->receiver);
gwthread_join(smpp->receiver);
}
smpp_destroy(conn->data);
conn->data = NULL;
return -1;
}
conn->shutdown = shutdown_cb;
conn->queued = queued_cb;
conn->send_msg = send_msg_cb;
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.