new file mode 100644
@@ -0,0 +1,17 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(COVERAGE_CFLAGS) \
+ $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) \
+ -I/usr/include/sofia-sip-1.12 -DNO_GSM0480_SEND_FUNC
+
+AM_LDFLAGS = $(COVERAGE_LDFLAGS)
+
+bin_PROGRAMS = ussd-proxy
+
+ussd_proxy_SOURCES = \
+ ussd_proxy.c ../libmsc/gsm_ussd_map_proto.c ../libmsc/gsm_04_80.c
+
+ussd_proxy_LDADD = \
+ -lsofia-sip-ua \
+ $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOCTRL_LIBS) $(LIBOSMOABIS_LIBS)
new file mode 100644
@@ -0,0 +1,1544 @@
+#ifdef HAVE_CONFIG_H
+//#include <config.h>
+#endif
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+#include <ctype.h>
+
+typedef struct context_s context_t;
+#define NTA_OUTGOING_MAGIC_T context_t
+#define SU_ROOT_MAGIC_T context_t
+#define NUA_MAGIC_T context_t
+
+typedef struct operation operation_t;
+#define NUA_HMAGIC_T operation_t
+
+#include <sofia-sip/nta.h>
+#include <sofia-sip/nua.h>
+#include <sofia-sip/sip_header.h>
+#include <sofia-sip/sip_tag.h>
+#include <sofia-sip/su_tag_io.h>
+#include <sofia-sip/sl_utils.h>
+#include <sofia-sip/sip_util.h>
+#include <sofia-sip/auth_client.h>
+#include <sofia-sip/tport_tag.h>
+#include <sofia-sip/url.h>
+#include <sofia-sip/su_log.h>
+
+#include <osmocom/abis/ipa.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/gsm/gsm0480.h>
+#include <osmocom/core/linuxlist.h>
+
+#include <openbsc/gprs_gsup_messages.h>
+#include <openbsc/gsm_ussd_map_proto.h>
+#include <openbsc/gsm_04_80.h>
+
+#include <iconv.h>
+
+typedef uint32_t sup_tcap_tid_t;
+
+
+typedef struct isup_connection isup_connection_t;
+
+struct isup_connection {
+ context_t *ctx;
+
+ su_socket_t isup_conn_socket;
+ su_wait_t isup_conn_event;
+ int isup_register_idx;
+
+ /* osmocom data */
+
+ struct msgb *pending_msg;
+};
+
+typedef enum ss_type {
+ TYPE_USSD,
+ TYPE_SS_OTHER
+} ss_type_t;
+
+struct ussd_session {
+ isup_connection_t *conn;
+ sup_tcap_tid_t ref;
+
+ int ms_originated;
+ char extention[32];
+
+ ss_type_t type;
+
+ uint8_t ss_code;
+ struct ss_request rigester_msg;
+};
+
+struct context_s {
+ su_home_t home[1];
+ su_root_t *root;
+
+ su_socket_t isup_acc_socket;
+ su_wait_t isup_acc_event;
+
+ nua_t *nua;
+
+ url_t *to_url;
+ url_t *self_url;
+
+ su_timer_t *timer;
+ su_duration_t max_ussd_ses_duration;
+
+ /* iconv data */
+ iconv_t* utf8_to_ucs2;
+ iconv_t* ucs2_to_utf8;
+
+ iconv_t* utf8_to_latin1;
+ iconv_t* latin1_to_utf8;
+
+ /* Array of isup connections */
+ struct isup_connection isup[1];
+
+ /* list of active operations */
+ struct llist_head operation_list;
+ unsigned operation_count;
+ unsigned operations_max;
+};
+
+
+/* Example of operation handle context information structure */
+struct operation
+{
+ struct llist_head list;
+
+ nua_handle_t *handle; /* operation handle */
+ context_t *ctx;
+ su_time_t tm_initiated;
+
+ /* protocol specific sessions */
+ struct ussd_session ussd;
+};
+
+static
+int ussd_send_data(operation_t *op, int last, const char* lang, unsigned lang_len,
+ const char* msg, unsigned msg_len);
+static
+int ussd_send_data_ss(isup_connection_t *conn,
+ uint8_t message_type,
+ const uint8_t *component,
+ uint8_t component_len,
+ uint32_t ref);
+
+static
+int ussd_send_reject(isup_connection_t *conn, uint32_t ref, uint8_t invoke_id);
+
+static const char* get_unknown_header(sip_t const *sip, const char *header)
+{
+ sip_header_t *h = (sip_header_t *)sip->sip_unknown;
+ for (; h; h = (sip_header_t *)h->sh_succ) {
+ if (strcasecmp(h->sh_unknown->un_name, header) == 0) {
+ return h->sh_unknown->un_value;
+ }
+ }
+ return NULL;
+}
+
+
+int sup_server_send(isup_connection_t *conn, struct msgb *msg)
+{
+ ssize_t sz;
+
+ if (!conn) {
+ msgb_free(msg);
+ return -ENOTCONN;
+ }
+
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_GSUP);
+ ipa_msg_push_header(msg, IPAC_PROTO_OSMO);
+
+ LOGP(DLCTRL, LOGL_ERROR,
+ "Sending wire, will send: %s\n", msgb_hexdump(msg));
+
+ // FIXME ugly hack!!!
+ // TODO place message in send queue !!!!
+ sz = send(conn->isup_conn_socket, msg->data, msg->len, 0);
+ msgb_free(msg);
+
+ return ((unsigned)sz == msg->len) ? 0 : -1;
+}
+
+static int ussd_parse_xml(const char *xml,
+ unsigned xml_len,
+ const char **lang,
+ unsigned *lang_len,
+ const char **msg,
+ unsigned *msg_len)
+{
+ /* Example of parsing XML
+ <?xml version="1.0" encoding="UTF-8"?>
+ <ussd-data>
+ <language>en</language>
+ <ussd-string>Test</ussd-string>
+ </ussd-data>
+ */
+
+ // <ussd-data> tag
+ char* ussd_data_stag = strstr(xml, "<ussd-data>");
+ if (ussd_data_stag == NULL)
+ return 0;
+
+ char* ussd_data_etag = strstr(ussd_data_stag, "</ussd-data>");
+ if (ussd_data_etag == NULL)
+ return 0;
+
+ // <language> tag
+ char* ussd_lang_stag = strstr(ussd_data_stag, "<language>");
+ if (ussd_lang_stag == NULL)
+ return 0;
+
+ char* ussd_lang_etag = strstr(ussd_lang_stag, "</language>");
+ if (ussd_lang_etag == NULL)
+ return 0;
+
+ // <language> tag
+ char* ussd_ussd_stag = strstr(ussd_data_stag, "<ussd-string>");
+ if (ussd_ussd_stag == NULL)
+ return 0;
+
+ char* ussd_ussd_etag = strstr(ussd_ussd_stag, "</ussd-string>");
+ if (ussd_ussd_etag == NULL)
+ return 0;
+
+ if (ussd_ussd_etag - xml > xml_len || ussd_lang_etag - xml > xml_len)
+ return 0;
+
+ *lang = ussd_lang_stag + strlen("<language>");
+ *lang_len = ussd_lang_etag - *lang;
+
+ *msg = ussd_ussd_stag + strlen("<ussd-string>");
+ *msg_len = ussd_ussd_etag - *msg;
+
+ return 1;
+}
+
+// Operation APIs
+static operation_t* operation_find_by_tid(context_t* ctx, sup_tcap_tid_t ref)
+{
+ operation_t* op;
+ llist_for_each_entry(op, &ctx->operation_list, list) {
+ if (op->ussd.ref == ref)
+ return op;
+ }
+ return NULL;
+}
+
+static operation_t* operation_alloc(context_t* ctx)
+{
+ operation_t* op;
+
+ if (ctx->operation_count >= ctx->operations_max) {
+ fprintf(stderr, "!!! maximum number of active session is reached: %d\n",
+ ctx->operation_count);
+ return NULL;
+ }
+
+ /* create operation context information */
+ op = su_zalloc(ctx->home, (sizeof *op));
+ if (!op) {
+ return NULL;
+ }
+
+ op->ctx = ctx;
+ op->tm_initiated = su_now();
+ INIT_LLIST_HEAD(&op->list);
+ llist_add_tail(&op->list, &ctx->operation_list);
+ ctx->operation_count++;
+
+ return op;
+}
+
+static void operation_destroy(operation_t* op)
+{
+ /* release operation handle */
+ nua_handle_destroy(op->handle);
+ op->handle = NULL;
+
+ llist_del(&op->list);
+ op->ctx->operation_count--;
+
+ if (op->ussd.type == TYPE_USSD) {
+ fprintf(stderr, "--- operation %*.s from %s destroyed (sessions: %d)\n",
+ op->ussd.rigester_msg.ussd_text_len,
+ op->ussd.rigester_msg.ussd_text,
+ op->ussd.extention,
+ op->ctx->operation_count);
+ } else {
+ fprintf(stderr, "--- operation 0x%02x from %s destroyed (sessions: %d)\n",
+ op->ussd.ss_code,
+ op->ussd.extention,
+ op->ctx->operation_count);
+ }
+
+ /* release operation context information */
+ su_free(op->ctx->home, op);
+}
+
+void proxy_r_invite(int status,
+ char const *phrase,
+ nua_t *nua,
+ nua_magic_t *magic,
+ nua_handle_t *nh,
+ nua_hmagic_t *hmagic,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+ fprintf(stderr, "*** Got reply %d for INVITE\n", status);
+ if (status == 200) {
+ nua_ack(nh, TAG_END());
+ } else if (hmagic->ussd.type == TYPE_USSD) {
+ printf("response to USSD INVITE: %03d %s\n", status, phrase);
+
+ ussd_send_reject(hmagic->ussd.conn,
+ hmagic->ussd.ref,
+ hmagic->ussd.rigester_msg.invoke_id);
+ operation_destroy(hmagic);
+ } else {
+ printf("response to SS INVITE: %03d %s\n", status, phrase);
+
+ ussd_send_data_ss(hmagic->ussd.conn,
+ GSM0480_MTYPE_RELEASE_COMPLETE,
+ NULL,
+ 0,
+ hmagic->ussd.ref);
+ operation_destroy(hmagic);
+ }
+}
+
+void proxy_i_bye(int status,
+ char const *phrase,
+ nua_t *nua,
+ nua_magic_t *magic,
+ nua_handle_t *nh,
+ nua_hmagic_t *hmagic,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+ const char* ri;
+ int rc;
+ // printf("*** call released:\n%s\n", sip->sip_payload->pl_data);
+
+ ri = get_unknown_header(sip, "Recv-Info");
+ if (ri && (strcasecmp(ri, "g.3gpp.ussd") == 0)) {
+ /* Parse XML */
+ const char *language;
+ const char *msg;
+ unsigned language_len;
+ unsigned msg_len;
+
+ if (ussd_parse_xml(sip->sip_payload->pl_data,
+ sip->sip_payload->pl_len,
+ &language, &language_len,
+ &msg, &msg_len)) {
+ printf("=== USSD (%.*s): %.*s\n",
+ language_len, language,
+ msg_len, msg);
+
+ /* Send reply back to SUP */
+ // TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ rc = ussd_send_data(hmagic, 1, language, language_len,
+ msg, msg_len);
+ if (rc == 0) {
+ // Normal shutdown
+ operation_destroy(hmagic);
+ return;
+ }
+
+ fprintf(stderr, "*** unable to send to SUP\n");
+ } else {
+ fprintf(stderr, "*** unable to parse XML\n");
+ }
+ }
+
+ fprintf(stderr, "*** response BYE with %d satus is malformed, drop session\n",
+ status);
+ ussd_send_reject(hmagic->ussd.conn,
+ hmagic->ussd.ref,
+ hmagic->ussd.rigester_msg.invoke_id);
+ operation_destroy(hmagic);
+}
+
+static
+uint8_t get_nibble(uint8_t a)
+{
+ if (a >= '0' && a <= '9')
+ return a-'0';
+ else if (a >= 'A' && a <= 'F')
+ return a-'A' + 10;
+ else if (a >= 'a' && a <= 'f')
+ return a-'a' + 10;
+
+ fprintf(stderr, "*** Incorrect nibble deteced: %02x\n", a);
+ return 0xff;
+}
+
+void proxy_i_bye_ss(int status,
+ char const *phrase,
+ nua_t *nua,
+ nua_magic_t *magic,
+ nua_handle_t *nh,
+ nua_hmagic_t *hmagic,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+ const char* pl_txt = sip->sip_payload->pl_data;
+ unsigned pl_txt_len = sip->sip_payload->pl_len;
+ uint8_t buffer[256];
+ uint8_t buflen = 0;
+ int i;
+
+ for (i = 0; i < pl_txt_len && buflen < sizeof(buffer) - 1; ) {
+ uint8_t hi_nibble = pl_txt[i++];
+ if (hi_nibble == 0xff || i == pl_txt_len)
+ break;
+ uint8_t lo_nibble = pl_txt[i++];
+ if (lo_nibble == 0xff)
+ break;
+
+ buffer[buflen++] = (get_nibble(hi_nibble) << 4) |
+ get_nibble(lo_nibble);
+ }
+
+ fprintf(stderr, "got bye_ss %d `%.*s` -> %d bytes\n", pl_txt_len, pl_txt_len, pl_txt, buflen);
+
+ if (buflen > 1) {
+ /* ASN.1 length can be 1 or 2 bytes ( >2 isn't possible anyway here ) */
+ unsigned len = (buffer[1] < 0x80) ? buffer[1] : ( (buflen > 2) ? buffer[2] : 0xff);
+ unsigned len_len = (buffer[1] < 0x80) ? 1 : buffer[1] - 0x80;
+
+ if (len + 1 + len_len != buflen) {
+ fprintf(stderr, "*** parsed %d len, but should be %d (%s)",
+ buflen, len_len + 1 + len, pl_txt);
+ }
+ }
+
+ ussd_send_data_ss(hmagic->ussd.conn,
+ GSM0480_MTYPE_RELEASE_COMPLETE,
+ buffer,
+ buflen,
+ hmagic->ussd.ref);
+ operation_destroy(hmagic);
+}
+
+void proxy_r_bye(int status,
+ char const *phrase,
+ nua_t *nua,
+ nua_magic_t *magic,
+ nua_handle_t *nh,
+ nua_hmagic_t *hmagic,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+ fprintf(stderr, "*** Got reply %d for BUY\n", status);
+ operation_destroy(hmagic);
+}
+
+void proxy_i_error(int status,
+ char const *phrase,
+ nua_t *nua,
+ nua_magic_t *magic,
+ nua_handle_t *nh,
+ nua_hmagic_t *hmagic,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+#if 0
+ if (!hmagic) {
+ return;
+ }
+
+ fprintf(stderr, "*** error in session with %d satus\n",
+ status);
+ ussd_send_reject(hmagic->ussd.conn,
+ hmagic->ussd.rigester_msg.invoke_id,
+ hmagic->ussd.rigester_msg.opcode);
+ operation_destroy(hmagic);
+#endif
+}
+
+void proxy_info(int status,
+ char const *phrase,
+ nua_t *nua,
+ nua_magic_t *magic,
+ nua_handle_t *nh,
+ nua_hmagic_t *hmagic,
+ sip_t const *sip,
+ tagi_t tags[],
+ int response)
+{
+ const char* ri;
+ int rc;
+
+ // Normal ACK is recieved
+ if (response == 1 && status == 200)
+ return;
+
+ ri = get_unknown_header(sip, "Recv-Info");
+ if (ri && (strcasecmp(ri, "g.3gpp.ussd") == 0)) {
+ /* Parse XML */
+ const char *language;
+ const char *msg;
+ unsigned language_len;
+ unsigned msg_len;
+
+ if (ussd_parse_xml(sip->sip_payload->pl_data,
+ sip->sip_payload->pl_len,
+ &language, &language_len,
+ &msg, &msg_len)) {
+ printf("%s USSD (%.*s): %.*s\n",
+ (response) ? ">>>" : "<<<",
+ language_len, language,
+ msg_len, msg);
+
+ if (hmagic == 0) {
+ printf("*** unknown session, ignoring");
+
+ // FIXME this function works only with a dialog!
+ nua_respond(nh, 481, "INFO with no session", TAG_END());
+ return;
+ }
+
+ /* Send reply back to SUP */
+ // TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ rc = ussd_send_data(hmagic, 0, language, language_len,
+ msg, msg_len);
+ if (rc == 0)
+ return;
+
+ fprintf(stderr, "*** unable to send to SUP in INFO\n");
+ } else {
+ fprintf(stderr, "*** unable to parse XML in INFO\n");
+ }
+ }
+
+ fprintf(stderr, "*** %s INFO with %d satus is malformed, drop session\n",
+ response ? "response" : "request",
+ status);
+ ussd_send_reject(hmagic->ussd.conn,
+ hmagic->ussd.ref,
+ hmagic->ussd.rigester_msg.invoke_id);
+ operation_destroy(hmagic);
+}
+
+int ussd_create_xml_latin1(context_t* ctx,
+ char *content, size_t max_len,
+ const char* inbuf_latin1, int buf_len)
+{
+ const char *language = "en";
+ char tmpbuf_utf8[2*MAX_LEN_USSD_STRING];
+ unsigned tmpbuf_utf8_len;
+
+ char* inbuf = (char*)inbuf_latin1;
+ size_t inleft = buf_len;
+ char* outbuf = tmpbuf_utf8;
+ size_t outleft = sizeof(tmpbuf_utf8);
+ size_t s;
+
+ s = iconv(ctx->latin1_to_utf8, &inbuf, &inleft, &outbuf, &outleft);
+ if (s == (size_t)-1) {
+ LOGP(DLCTRL, LOGL_ERROR, "Unable to encode latin1 into utf8\n");
+ return 0;
+ }
+
+ tmpbuf_utf8_len = outbuf - tmpbuf_utf8;
+
+ int content_len = snprintf(content, max_len,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<ussd-data>\n"
+ "<language>%s</language>\n"
+ "<ussd-string>%.*s</ussd-string>\n"
+ "</ussd-data>",
+ language,
+ tmpbuf_utf8_len, tmpbuf_utf8);
+ if (content_len > max_len) {
+ content[max_len - 1] = 0;
+ return 0;
+ }
+ return 1;
+}
+
+static int decode_to_latin1(char* outbuf, unsigned size,
+ const uint8_t* msg, unsigned msg_len, uint8_t lang)
+{
+ if (lang == 0x0f) {
+ return gsm_7bit_decode_n_ussd(outbuf, size, msg, msg_len);
+ } else {
+ LOGP(DLCTRL, LOGL_ERROR, "Unknown language: 0x%02x\n", lang);
+ return 0;
+ }
+}
+
+/* URL_RESERVED_CHARS in sofia is not strict enough as in RFC3986 */
+#define RFC3986_RESERVED_CHARS "!*'();:@&=+$,/?#[]"
+
+int ss_session_open_mo(operation_t *op,
+ isup_connection_t *conn,
+ const uint8_t* component,
+ uint8_t component_len,
+ uint32_t ref,
+ const char* extention)
+{
+ char buffer[512+1];
+ int i;
+ context_t* ctx = op->ctx;
+ sip_to_t *to = NULL;
+ sip_to_t *from = NULL;
+ url_t to_url, from_url;
+ char* to_url_str;
+ char* from_url_str;
+
+ op->ussd.ref = ref;
+ op->ussd.conn = conn;
+ op->ussd.ms_originated = 1;
+ op->ussd.type = TYPE_SS_OTHER;
+
+ strncpy(op->ussd.extention, extention, sizeof(op->ussd.extention));
+
+ for (i = 0; i < component_len; ++i) {
+ uint8_t nibble_h = component[i] >> 4;
+ uint8_t nibble_l = component[i] & 0xf;
+
+ buffer[2*i ] = (nibble_h < 10) ? '0' + nibble_h : 'a' + nibble_h - 10;
+ buffer[2*i + 1] = (nibble_l < 10) ? '0' + nibble_l : 'a' + nibble_l - 10;
+ }
+ buffer[2*i] = 0;
+
+ /* Destination address */
+ to_url = *ctx->to_url;
+ to_url.url_user = "mapss";
+ to_url_str = url_as_string(ctx->home, &to_url);
+ if (to_url_str == NULL) {
+ goto failed_create_handle;
+ }
+ to = sip_to_create(ctx->home, (url_string_t *)to_url_str);
+ su_free(ctx->home, to_url_str);
+ if (!to) {
+ goto failed_create_handle;
+ }
+
+ /* Source address */
+ from_url = *ctx->self_url;
+ from_url.url_user = extention;
+ from_url_str = url_as_string(ctx->home, &from_url);
+ if (from_url_str == NULL) {
+ goto failed_create_handle;
+ }
+ from = sip_from_create(ctx->home, (url_string_t *)from_url_str);
+ su_free(ctx->home, from_url_str);
+ if (!to) {
+ goto failed_create_handle;
+ }
+
+ /* create operation handle */
+ op->handle = nua_handle(ctx->nua,
+ op,
+ SIPTAG_TO(to),
+ SIPTAG_FROM(from),
+ NUTAG_M_USERNAME(extention),
+ TAG_END());
+
+ su_free(ctx->home, from);
+ su_free(ctx->home, to);
+ from = NULL;
+ to = NULL;
+
+ if (op->handle == NULL) {
+ goto failed_create_handle;
+ }
+
+ nua_invite(op->handle,
+ SIPTAG_CONTENT_TYPE_STR("application/map-ss-binary"),
+ SIPTAG_PAYLOAD_STR(buffer),
+ TAG_END());
+ return 0;
+
+failed_create_handle:
+ if (from != NULL)
+ su_free(ctx->home, from);
+ if (to != NULL)
+ su_free(ctx->home, to);
+
+ return -1;
+}
+
+int ussd_session_open_mo(operation_t *op,
+ isup_connection_t *conn,
+ struct ss_request* ss,
+ uint32_t ref,
+ const char* extention)
+{
+ char content[1024];
+ char decoded[MAX_LEN_USSD_STRING + 1];
+ char escaped_to[512];
+ context_t* ctx = op->ctx;
+ sip_to_t *to = NULL;
+ sip_to_t *from = NULL;
+ url_t to_url, from_url;
+ char* to_url_str;
+ char* from_url_str;
+
+ int decoded_len;
+
+ op->ussd.ref = ref;
+ op->ussd.conn = conn;
+ op->ussd.ms_originated = 1;
+ op->ussd.type = TYPE_USSD;
+ op->ussd.rigester_msg = *ss;
+ strncpy(op->ussd.extention, extention, sizeof(op->ussd.extention));
+
+ decoded_len = decode_to_latin1(decoded, MAX_LEN_USSD_STRING,
+ op->ussd.rigester_msg.ussd_text,
+ op->ussd.rigester_msg.ussd_text_len,
+ op->ussd.rigester_msg.ussd_text_language);
+ if (decoded_len <= 0) {
+ goto failed_to_parse_xml;
+ }
+ decoded[decoded_len] = 0;
+
+ if (!ussd_create_xml_latin1(ctx, content, sizeof(content),
+ decoded, decoded_len)) {
+ goto failed_to_parse_xml;
+ }
+
+
+ /* Destination address */
+ url_escape(escaped_to, decoded, RFC3986_RESERVED_CHARS);
+ to_url = *ctx->to_url;
+ to_url.url_user = escaped_to;
+ to_url_str = url_as_string(ctx->home, &to_url);
+ if (to_url_str == NULL) {
+ goto failed_create_handle;
+ }
+
+ to = sip_to_create(ctx->home, (url_string_t *)to_url_str);
+ su_free(ctx->home, to_url_str);
+ if (!to) {
+ goto failed_create_handle;
+ }
+
+ /* Source address */
+ from_url = *ctx->self_url;
+ from_url.url_user = extention;
+ from_url_str = url_as_string(ctx->home, &from_url);
+ if (from_url_str == NULL) {
+ goto failed_create_handle;
+ }
+
+ from = sip_from_create(ctx->home, (url_string_t *)from_url_str);
+ su_free(ctx->home, from_url_str);
+ if (!to) {
+ goto failed_create_handle;
+ }
+
+ /* create operation handle */
+ op->handle = nua_handle(ctx->nua,
+ op,
+ SIPTAG_TO(to),
+ SIPTAG_FROM(from),
+ NUTAG_M_USERNAME(extention),
+ TAG_END());
+
+ su_free(ctx->home, from);
+ su_free(ctx->home, to);
+ from = NULL;
+ to = NULL;
+
+ if (op->handle == NULL) {
+ goto failed_create_handle;
+ }
+
+ nua_invite(op->handle,
+ SIPTAG_UNKNOWN_STR("Recv-Info: g.3gpp.ussd"),
+ SIPTAG_CONTENT_TYPE_STR("application/vnd.3gpp.ussd+xml"),
+ SIPTAG_PAYLOAD_STR(content),
+ TAG_END());
+ return 0;
+
+failed_create_handle:
+ if (from != NULL)
+ su_free(ctx->home, from);
+ if (to != NULL)
+ su_free(ctx->home, to);
+failed_to_parse_xml:
+ fprintf(stderr, "*** open_ussd_session failed!\n");
+ return -1;
+}
+
+int ussd_session_facility(operation_t *op,
+ struct ss_request* ss,
+ const char* extention)
+{
+ char content[1024];
+ char decoded[MAX_LEN_USSD_STRING + 1];
+ int decoded_len;
+
+ decoded_len = decode_to_latin1(decoded, MAX_LEN_USSD_STRING,
+ op->ussd.rigester_msg.ussd_text,
+ op->ussd.rigester_msg.ussd_text_len,
+ op->ussd.rigester_msg.ussd_text_language);
+ if (decoded_len <= 0) {
+ return -1;
+ }
+ decoded[decoded_len] = 0;
+
+ if (!ussd_create_xml_latin1(op->ctx, content, sizeof(content),
+ decoded, decoded_len)) {
+ return -1;
+ }
+
+ nua_info(op->handle,
+ /* other tags as needed ... */
+ SIPTAG_CONTENT_TYPE_STR("application/vnd.3gpp.ussd+xml"),
+ SIPTAG_UNKNOWN_STR("Recv-Info: g.3gpp.ussd"),
+ SIPTAG_PAYLOAD_STR(content),
+ TAG_END());
+
+ return 0;
+}
+
+void context_callback(nua_event_t event,
+ int status,
+ char const *phrase,
+ nua_t *nua,
+ nua_magic_t *magic,
+ nua_handle_t *nh,
+ nua_hmagic_t *hmagic,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+ fprintf(stderr, "$$$ got event %d: status: %d (%s) : %p\n", event, status, phrase, hmagic);
+
+ switch (event) {
+ case nua_i_error:
+ proxy_i_error(status, phrase, nua, magic, nh, hmagic, sip, tags);
+ break;
+
+ case nua_i_info:
+ if (hmagic->ussd.type == TYPE_USSD)
+ proxy_info(status, phrase, nua, magic, nh, hmagic, sip, tags, 0);
+ break;
+
+ case nua_r_info:
+ if (hmagic->ussd.type == TYPE_USSD)
+ proxy_info(status, phrase, nua, magic, nh, hmagic, sip, tags, 1);
+ break;
+
+ case nua_i_bye:
+ if (hmagic->ussd.type == TYPE_USSD)
+ proxy_i_bye(status, phrase, nua, magic, nh, hmagic, sip, tags);
+ else
+ proxy_i_bye_ss(status, phrase, nua, magic, nh, hmagic, sip, tags);
+ break;
+
+ case nua_i_invite:
+ //app_i_invite(status, phrase, nua, magic, nh, hmagic, sip, tags);
+ break;
+
+ case nua_r_invite:
+ proxy_r_invite(status, phrase, nua, magic, nh, hmagic, sip, tags);
+ break;
+
+ case nua_r_bye:
+ proxy_r_bye(status, phrase, nua, magic, nh, hmagic, sip, tags);
+ break;
+
+ default:
+ /* unknown event -> print out error message */
+ if (status > 100) {
+ printf("unknown event %d: %03d %s\n",
+ event,
+ status,
+ phrase);
+ } else {
+ printf("unknown event %d\n", event);
+ }
+ tl_print(stdout, "", tags);
+ break;
+ }
+}
+
+static int rx_sup_uss_message(isup_connection_t *sup_conn, const uint8_t* data, size_t len)
+{
+ char extention[32] = {0};
+ struct ss_header ss;
+ struct ss_request ssreq;
+ uint32_t ref;
+ operation_t* op;
+ int rc;
+ context_t *ctx = sup_conn->ctx;
+ memset(&ss, 0, sizeof(ss));
+
+ if (rx_uss_message_parse(data, len, &ss, &ref, extention, sizeof(extention))) {
+ LOGP(DLCTRL, LOGL_ERROR, "Can't parse uss message\n");
+ goto err_bad_packet;
+ }
+
+ memset(&ssreq, 0, sizeof(ssreq));
+ rc = gsm0480_parse_ss_facility(data + ss.component_offset,
+ ss.component_length,
+ &ssreq);
+ if (!rc) {
+ LOGP(DLCTRL, LOGL_ERROR, "Can't parse facility message\n");
+ goto err_bad_component;
+ }
+
+ LOGP(DLCTRL, LOGL_ERROR, "Got ref=%d mtype=0x%02x invoke_id=0x%02x opcode=0x%02x ss_code=0x%02x component_type=0x%02x text=%s\n", ref,
+ ss.message_type, ssreq.invoke_id, ssreq.opcode, ssreq.ss_code, ssreq.component_type, ssreq.ussd_text);
+
+ switch (ss.message_type) {
+ case GSM0480_MTYPE_REGISTER:
+ if (ssreq.component_type != GSM0480_CTYPE_INVOKE) {
+ LOGP(DLCTRL, LOGL_ERROR, "Non-INVOKE component type in REGISTER: 0x%02x\n", ssreq.component_type);
+ goto err_send_reject;
+ }
+ if (ssreq.opcode == GSM0480_OP_CODE_PROCESS_USS_DATA ||
+ ssreq.opcode == GSM0480_OP_CODE_USS_NOTIFY ||
+ ssreq.opcode == GSM0480_OP_CODE_USS_REQUEST) {
+
+ LOGP(DLCTRL, LOGL_ERROR, "Don't know hot to handle this SS opcode: 0x%02x\n", ssreq.opcode);
+ goto err_send_reject;
+ }
+ /* Create new session */
+ op = operation_alloc(ctx);
+ if (op == NULL) {
+ LOGP(DLCTRL, LOGL_ERROR, "Unable to allocate new session\n");
+ goto err_send_reject;
+ }
+
+ if (ssreq.opcode == GSM0480_OP_CODE_PROCESS_USS_REQ) {
+ LOGP(DLCTRL, LOGL_ERROR, "New session %.*s from %s, active: %d\n",
+ ssreq.ussd_text_len,
+ ssreq.ussd_text,
+ extention,
+ ctx->operation_count);
+
+ op->ussd.ss_code = 0;
+ rc = ussd_session_open_mo(op, sup_conn, &ssreq, ref, extention);
+ if (rc < 0) {
+ operation_destroy(op);
+ goto err_send_reject;
+ }
+ } else {
+ LOGP(DLCTRL, LOGL_ERROR, "New session SS 0x%02x from %s, active: %d\n",
+ ssreq.opcode,
+ extention,
+ ctx->operation_count);
+
+ op->ussd.ss_code = ssreq.ss_code;
+ op->ussd.rigester_msg = ssreq;
+ rc = ss_session_open_mo(op,
+ sup_conn,
+ data + ss.component_offset,
+ ss.component_length,
+ ref,
+ extention);
+ if (rc < 0) {
+ operation_destroy(op);
+ goto err_send_reject;
+ }
+ }
+ break;
+
+ case GSM0480_MTYPE_FACILITY:
+ //Only MS-originated Menu session is supported, so we ignore INVOKE here
+ if (ssreq.component_type != GSM0480_CTYPE_RETURN_RESULT &&
+ ssreq.component_type != GSM0480_CTYPE_RETURN_ERROR &&
+ ssreq.component_type != GSM0480_CTYPE_REJECT) {
+ LOGP(DLCTRL, LOGL_ERROR, "Non-{RESULT/RETURN_ERROR/REJECT} component type in FACILITY: 0x%02x\n", ssreq.component_type);
+ goto err_send_reject;
+ }
+ // /////////////////////////////////////////////////
+ // TODO handle RETURN_ERROR/REJECT
+ if (ssreq.component_type != GSM0480_CTYPE_RETURN_RESULT) {
+ LOGP(DLCTRL, LOGL_ERROR, "Component type in FACILITY: 0x%02x is not implemented yet\n", ssreq.component_type);
+ goto err_send_reject;
+ }
+ if (ssreq.opcode != GSM0480_OP_CODE_USS_REQUEST) {
+ LOGP(DLCTRL, LOGL_ERROR, "Don't know hot to handle this SS opcode: 0x%02x\n", ssreq.opcode);
+ goto err_send_reject;
+ }
+ op = operation_find_by_tid(ctx, ref);
+ if (op == NULL) {
+ LOGP(DLCTRL, LOGL_ERROR, "No active session with tid=%d were found\n",
+ ssreq.invoke_id);
+ goto err_send_reject;
+ }
+
+ // TODO check result!! MO/MT error handling
+ rc = ussd_session_facility(op, &ssreq, extention);
+ if (rc < 0) {
+ operation_destroy(op);
+ goto err_send_reject;
+ }
+ break;
+
+ case GSM0480_MTYPE_RELEASE_COMPLETE:
+ op = operation_find_by_tid(ctx, ref);
+ if (op == NULL) {
+ LOGP(DLCTRL, LOGL_ERROR, "No active session with tid=%d were found for RELEASE_COMPLETE\n",
+ ssreq.invoke_id);
+ return 0;
+ }
+
+ nua_bye(op->handle, TAG_END());
+ break;
+
+ default:
+ LOGP(DLCTRL, LOGL_ERROR, "Unknown message type 0x%02x\n", ss.message_type);
+ goto err_send_reject;
+ }
+
+ return 0;
+
+err_send_reject:
+ ussd_send_reject(sup_conn, ref, ssreq.invoke_id);
+ return -1;
+
+err_bad_component:
+ return ussd_send_data_ss(sup_conn,
+ GSM0480_MTYPE_RELEASE_COMPLETE,
+ NULL,
+ 0,
+ ref);
+ return -1;
+
+err_bad_packet:
+ // Disconnect ?
+ return -1;
+}
+
+int ussd_send_reject(isup_connection_t *conn, uint32_t ref, uint8_t invoke_id)
+{
+ uint8_t buffer[2+3+3];
+
+ buffer[0] = GSM0480_CTYPE_REJECT;
+ buffer[1] = 3+3;
+
+ buffer[2] = GSM0480_COMPIDTAG_INVOKE_ID;
+ buffer[3] = 1;
+ buffer[4] = invoke_id;
+
+ buffer[5] = GSM_0480_PROBLEM_CODE_TAG_GENERAL;
+ buffer[6] = 1;
+ buffer[7] = GSM_0480_GEN_PROB_CODE_UNRECOGNISED;
+
+ return ussd_send_data_ss(conn,
+ GSM0480_MTYPE_RELEASE_COMPLETE,
+ buffer,
+ sizeof(buffer),
+ ref);
+}
+
+int ussd_send_data_ss(isup_connection_t *conn,
+ uint8_t message_type,
+ const uint8_t* component,
+ uint8_t component_len,
+ uint32_t ref)
+{
+ struct msgb *outmsg = msgb_alloc_headroom(4000, 64, __func__);
+ struct ss_header hdr;
+
+ hdr.transaction_id = 0;
+ hdr.message_type = message_type;
+ hdr.component_length = component_len;
+ hdr.component_offset = 0;
+
+ subscr_uss_message(outmsg,
+ &hdr,
+ NULL,
+ ref,
+ component);
+
+ LOGP(DLCTRL, LOGL_ERROR,
+ "Sending USS, will send: %s\n", msgb_hexdump(outmsg));
+
+ return sup_server_send(conn, outmsg);
+}
+
+int ussd_send_data(operation_t *op, int last, const char* lang, unsigned lang_len,
+ const char* msg, unsigned msg_len)
+{
+ struct msgb *buf;
+ struct ss_request ss;
+ int rc;
+ uint8_t message_type;
+
+ memset(&ss, 0, sizeof(ss));
+
+ // TODO handle language
+ if (msg == NULL) {
+ message_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+ ss.component_type = GSM0480_CTYPE_REJECT;
+ ss.opcode = op->ussd.rigester_msg.opcode;
+ } else if (last) {
+ message_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+ ss.component_type = GSM0480_CTYPE_RETURN_RESULT;
+ ss.opcode = op->ussd.rigester_msg.opcode;
+ } else {
+ message_type = GSM0480_MTYPE_FACILITY;
+ ss.component_type = (op->ussd.ms_originated) ? GSM0480_CTYPE_INVOKE
+ : GSM0480_CTYPE_RETURN_RESULT;
+ ss.opcode = GSM0480_OP_CODE_USS_REQUEST;
+ }
+
+ ss.invoke_id = op->ussd.rigester_msg.invoke_id;
+
+ if (msg) {
+ char tmpbuf[MAX_LEN_USSD_STRING + 1];
+
+ char* inbuf = (char*)msg;
+ size_t inleft = msg_len;
+ char* outbuf = (char*)tmpbuf;
+ size_t outleft = sizeof(tmpbuf);
+ size_t s;
+
+ // First of all try latin1
+ s = iconv(op->ctx->utf8_to_latin1,
+ &inbuf, &inleft,
+ &outbuf, &outleft);
+ if (s == (size_t)-1) {
+ outbuf = (char*)ss.ussd_text;
+ outleft = MAX_ASN1_LEN_USSD_STRING;
+
+ s = iconv(op->ctx->utf8_to_ucs2,
+ &inbuf, &inleft,
+ &outbuf, &outleft);
+ if (s == (size_t)-1) {
+ perror("can't convert string from utf8");
+ }
+ // UCS-2 encoding
+ ss.ussd_text_language = 0x48;
+ ss.ussd_text_len = (uint8_t*)outbuf - ss.ussd_text;
+
+ } else {
+ int outlen;
+ size_t len = (msg_len > MAX_LEN_USSD_STRING) ?
+ MAX_LEN_USSD_STRING : msg_len;
+ memcpy(tmpbuf, msg, len);
+ tmpbuf[len] = 0;
+
+ gsm_7bit_encode_n_ussd(ss.ussd_text,
+ MAX_ASN1_LEN_USSD_STRING, tmpbuf, &outlen);
+ ss.ussd_text_len = outlen;
+ ss.ussd_text_language = 0x0f;
+ }
+ } else {
+ ss.ussd_text_len = 0;
+ ss.ussd_text_language = 0x0f;
+ ss.ussd_text[0] = 0;
+ }
+
+ buf = gsm0480_compose_ussd_component(&ss);
+ if (!buf) {
+ return -1;
+ }
+ rc = ussd_send_data_ss(op->ussd.conn, message_type,
+ buf->data, msgb_length(buf), op->ussd.ref);
+ msgb_free(buf);
+
+ return rc;
+}
+
+static void timer_function(su_root_magic_t *magic,
+ su_timer_t *t,
+ su_timer_arg_t *arg)
+{
+ context_t *cli = (context_t*)arg;
+ su_time_t n = su_now();
+
+ operation_t *op, *tmp;
+ llist_for_each_entry_safe(op, tmp, &cli->operation_list, list) {
+ su_duration_t lasts = su_duration(n, op->tm_initiated);
+ if (lasts > cli->max_ussd_ses_duration) {
+ if (op->ussd.type == TYPE_USSD) {
+ fprintf(stderr, "!!! session %.*s from %s lasted %ld ms, more than thresold %ld ms, destroying\n",
+ op->ussd.rigester_msg.ussd_text_len,
+ op->ussd.rigester_msg.ussd_text,
+ op->ussd.extention,
+ lasts,
+ cli->max_ussd_ses_duration);
+
+
+ ussd_send_reject(op->ussd.conn,
+ op->ussd.ref,
+ op->ussd.rigester_msg.invoke_id);
+ } else {
+ fprintf(stderr, "!!! session 0x%02x from %s lasted %ld ms, more than thresold %ld ms, destroying\n",
+ op->ussd.ss_code,
+ op->ussd.extention,
+ lasts,
+ cli->max_ussd_ses_duration);
+
+ ussd_send_data_ss(op->ussd.conn,
+ GSM0480_MTYPE_RELEASE_COMPLETE,
+ NULL,
+ 0,
+ op->ussd.ref);
+ }
+ operation_destroy(op);
+ }
+ }
+}
+
+static int isup_handle_connection(context_t *cli, su_wait_t *w, void *p)
+{
+ int rc;
+ isup_connection_t *conn = (isup_connection_t*)p;
+
+ int events = su_wait_events(w, conn->isup_conn_socket);
+ printf("*** connection; event=0x%x\n", events);
+
+ if (events & (SU_WAIT_ERR | SU_WAIT_HUP)) {
+ printf("*** connection destroyed\n");
+ goto err;
+ } else if (events & SU_WAIT_IN) {
+ /* Incoming data */
+
+ struct ipaccess_head *iph;
+ struct msgb *msg = NULL;
+ int ret = ipa_msg_recv_buffered(conn->isup_conn_socket, &msg, &conn->pending_msg);
+ if (ret <= 0) {
+ if (ret == -EAGAIN)
+ return 0;
+ if (ret == 0)
+ LOGP(DLCTRL, LOGL_INFO, "The control connection was closed\n");
+ else
+ LOGP(DLCTRL, LOGL_ERROR, "Failed to parse ip access message: %d\n", ret);
+
+ goto err;
+ }
+
+ iph = (struct ipaccess_head *) msg->data;
+ switch (iph->proto)
+ {
+ case IPAC_PROTO_IPACCESS:
+ if (msg->l2h[0] == IPAC_MSGT_PING) {
+ printf("*** got PING\n");
+ msg->l2h[0] = IPAC_MSGT_PONG;
+ send(conn->isup_conn_socket, msg->data, ntohs(iph->len) + sizeof(struct ipaccess_head), 0);
+ msgb_free(msg);
+ conn->pending_msg = NULL;
+ return 0;
+ }
+
+ LOGP(DLCTRL, LOGL_ERROR, "Unknown IPAC_PROTO_IPACCESS msg 0x%x\n", msg->l2h[0]);
+ goto err;
+ case IPAC_PROTO_OSMO:
+ // TODO callback
+ if (msg->l2h[1] == GPRS_GSUP_MSGT_USSD_MAP) {
+ LOGP(DLCTRL, LOGL_ERROR,
+ "Receive USS: %s\n", msgb_hexdump(msg));
+
+ rc = rx_sup_uss_message(conn, &msg->l2h[1], msgb_l2len(msg) - 1);
+ if (rc < 0) {
+ /* TODO raise reject !!!!!!! */
+ /* release complete */
+ }
+
+ msgb_free(msg);
+ conn->pending_msg = NULL;
+ return 0;
+ }
+
+ /* TODO: handle gprs_gsup_decode() for other types */
+
+ LOGP(DLCTRL, LOGL_ERROR, "Unknown IPAC_PROTO_OSMO GPRS_GSUP_MSGT_* 0x%x\n", msg->l2h[1]);
+ msgb_free(msg);
+ conn->pending_msg = NULL;
+ goto err;
+ default:
+ LOGP(DLCTRL, LOGL_ERROR, "Protocol mismatch. We got 0x%x\n", iph->proto);
+ goto err;
+ }
+ }
+
+ return 0;
+
+err:
+ close(conn->isup_conn_socket);
+ conn->isup_conn_socket = INVALID_SOCKET;
+
+ su_wait_destroy(w);
+
+ msgb_free(conn->pending_msg);
+ conn->pending_msg = NULL;
+ //su_root_deregister(cli, cli->isup_register_idx);
+ return 0;
+}
+
+static int isup_handle_accept(context_t *cli, su_wait_t *w, void *p)
+{
+ su_sockaddr_t aaddr;
+ su_socket_t connection;
+ socklen_t len = sizeof(aaddr);
+ int rc;
+
+ connection = accept(cli->isup_acc_socket, &aaddr.su_sa, &len);
+ if (connection == INVALID_SOCKET) {
+ perror("can't accept isup socket");
+ return 0;
+ }
+
+ printf("*** accepted from %s:%d\n",
+ inet_ntoa(aaddr.su_sin.sin_addr),
+ ntohs(aaddr.su_sin.sin_port));
+
+ /* TODO manage isup connection list, but just now use the single connection */
+ isup_connection_t *conn = cli->isup;
+ if (conn->isup_conn_socket != INVALID_SOCKET) {
+ fprintf(stderr, "--- Can't accept, there's another connection\n");
+ su_close(connection);
+ return 0;
+ }
+
+ conn->ctx = cli;
+ conn->isup_conn_socket = connection;
+ conn->pending_msg = NULL;
+
+ su_wait_init(&conn->isup_conn_event);
+ rc = su_wait_create(&conn->isup_conn_event,
+ conn->isup_conn_socket,
+ SU_WAIT_IN | /*SU_WAIT_OUT | */ SU_WAIT_HUP | SU_WAIT_ERR);
+
+ conn->isup_register_idx = su_root_register(cli->root,
+ &conn->isup_conn_event,
+ isup_handle_connection,
+ conn,
+ 0);
+ return 0;
+}
+
+#define DIPA_USSD_PROXY 0
+
+struct log_info_cat ipa_proxy_test_cat[] = {
+ [DIPA_USSD_PROXY] = {
+ .name = "DIPA_USSD_PROXY",
+ .description = "USSD_PROXY",
+ .color = "\033[1;35m",
+ .enabled = 1,
+ .loglevel = LOGL_DEBUG,
+ },
+};
+
+const struct log_info ipa_proxy_test_log_info = {
+ .filter_fn = NULL,
+ .cat = ipa_proxy_test_cat,
+ .num_cat = ARRAY_SIZE(ipa_proxy_test_cat),
+};
+
+
+static void Usage(char* progname)
+{
+ fprintf(stderr, "Usage:\n"
+ "%s [options]\n"
+ "Options\n"
+ " -p <port> TCP port to listen incoming SUP connection\n"
+ " (default: 8184)\n"
+ " -t <url> Destination SIP URL (default: sip:127.0.0.1:5060)\n"
+ " -u <url> User agent SIP URL (default: sip:127.0.0.1:5090)\n"
+ " -x <url> Proxy SIP URL (default: <none>)\n"
+ " -T Force using TCP instead trying UDP\n"
+ " -D <secs> Maximum period of open USSD session (default: 90)\n"
+ " -o <sessions> Maximum number of concurrent USSD sessions\n"
+ " (default: 200)\n"
+ " -l <0-9> sip sofia loglevel, 0 - none; 9 - max\n"
+ , progname);
+}
+
+int main(int argc, char *argv[])
+{
+ su_home_t *home;
+ context_t context[1] = {{{SU_HOME_INIT(context)}}};
+ su_sockaddr_t listen_addr;
+ int rc;
+ int sup_port = 8184;
+ const char* to_str = "sip:127.0.0.1:5060";
+ const char* url_str = "sip:127.0.0.1:5090";
+ const char* proxy_str = NULL;
+ int force_tcp = 0;
+ int max_ussd_ses_secs = 90;
+ int max_op_limit = 200;
+ int sip_loglevel = 1;
+ int c;
+
+ while ((c = getopt (argc, argv, "x:p:t:u:D:To:l:L7?")) != -1) {
+ switch (c)
+ {
+ case 'x':
+ proxy_str = optarg;
+ break;
+ case 'p':
+ sup_port = atoi(optarg);
+ break;
+ case 't':
+ to_str = optarg;
+ break;
+ case 'u':
+ url_str = optarg;
+ break;
+ case 'T':
+ force_tcp = 1;
+ break;
+ case 'D':
+ max_ussd_ses_secs = atoi(optarg);
+ break;
+ case 'o':
+ max_op_limit = atoi(optarg);
+ break;
+ case 'l':
+ sip_loglevel = atoi(optarg);
+ break;
+ case 'L':
+ fprintf(stderr, " -L is now obsolete, ignored\n");
+ break;
+ case '7':
+ fprintf(stderr, " -7 is now obsolete, ignored\n");
+ break;
+ case '?':
+ default:
+ Usage(argv[0]);
+ return 2;
+ }
+ }
+
+ osmo_init_logging(&ipa_proxy_test_log_info);
+
+ su_init();
+ su_home_init(home = context->home);
+
+ context->root = su_root_create(context);
+
+ su_log_set_level(NULL, sip_loglevel);
+
+ /* Disable threading */
+ su_root_threading(context->root, 0);
+
+ if (!context->root) {
+ fprintf(stderr, "Unable to initialize sip-sofia context\n");
+ return 1;
+ }
+
+ context->utf8_to_latin1=iconv_open("iso8859-1", "utf-8");
+ context->latin1_to_utf8=iconv_open("utf-8", "iso8859-1");
+ context->utf8_to_ucs2=iconv_open("utf-16be", "utf-8");
+ context->ucs2_to_utf8=iconv_open("utf-8", "utf-16be");
+
+ if (context->utf8_to_ucs2 == NULL || context->ucs2_to_utf8 == NULL ||
+ context->utf8_to_latin1 == NULL || context->latin1_to_utf8 == NULL) {
+ fprintf(stderr, "Unable to initialize iconv\n");
+ return 1;
+ }
+
+ context->isup_acc_socket = su_socket(AF_INET, SOCK_STREAM, 0);
+ if (context->isup_acc_socket == INVALID_SOCKET) {
+ perror("unable to create socket\n");
+ return 1;
+ }
+ su_setblocking(context->isup_acc_socket, 0);
+ su_setreuseaddr(context->isup_acc_socket, 1);
+
+ context->isup->isup_conn_socket = INVALID_SOCKET;
+
+ memset(&listen_addr, 0, sizeof(listen_addr));
+ listen_addr.su_sin.sin_family = AF_INET;
+ listen_addr.su_sin.sin_addr.s_addr = INADDR_ANY;
+ listen_addr.su_sin.sin_port = htons(sup_port);
+
+ rc = bind(context->isup_acc_socket, &listen_addr.su_sa, sizeof(listen_addr.su_sin));
+ if (rc < 0) {
+ perror("cannot bind socket\n");
+ return 2;
+ }
+
+ rc = listen(context->isup_acc_socket, 1);
+ if (rc < 0) {
+ perror("cannot bind socket\n");
+ return 2;
+ }
+
+ su_wait_init(&context->isup_acc_event);
+ su_wait_create(&context->isup_acc_event, context->isup_acc_socket, SU_WAIT_ACCEPT);
+ su_root_register(context->root,
+ &context->isup_acc_event,
+ isup_handle_accept,
+ NULL,
+ 0);
+
+ context->to_url = url_make(home, to_str);
+ context->self_url = url_make(home, url_str);
+
+ if (context->to_url == NULL) {
+ fprintf(stderr, "Unable to parse destination URL\n");
+ return 1;
+ }
+ if (context->self_url == NULL) {
+ fprintf(stderr, "Unable to parse our (source) URL\n");
+ return 1;
+ }
+
+ context->nua = nua_create(context->root,
+ context_callback,
+ context,
+ NUTAG_URL(url_str),
+ NUTAG_ENABLEINVITE(1),
+ NUTAG_AUTOALERT(1),
+ NUTAG_SESSION_TIMER(0),
+ NUTAG_AUTOANSWER(0),
+ NUTAG_MEDIA_ENABLE(0),
+ NUTAG_ALLOW("INVITE, ACK, BYE, CANCEL, INFO"),
+ TAG_NULL());
+ if (context->nua == NULL) {
+ fprintf(stderr, "Unable to initialize sip-sofia nua\n");
+ return 1;
+ }
+
+
+ if (proxy_str) {
+ nua_set_params(context->nua,
+ NUTAG_PROXY(proxy_str),
+ TAG_NULL());
+ }
+
+ if (force_tcp) {
+ nua_set_params(context->nua,
+ NTATAG_UDP_MTU(10),
+ TAG_NULL());
+ }
+
+ INIT_LLIST_HEAD(&context->operation_list);
+ context->operation_count = 0;
+ context->operations_max = max_op_limit;
+
+ su_timer_t* tm = su_timer_create(su_root_task(context->root), 2000);
+ if (tm == NULL) {
+ fprintf(stderr, "Unable to initialize sip-sofia timer\n");
+ return 1;
+ }
+ rc = su_timer_run(tm, timer_function, context);
+ if (rc < 0) {
+ fprintf(stderr, "Unable to start sip-sofia timer\n");
+ return 1;
+ }
+ context->timer = tm;
+ context->max_ussd_ses_duration = max_ussd_ses_secs * 1000l;
+
+ su_root_run(context->root);
+ nua_destroy(context->nua);
+
+ return 0;
+}
+
From: Sergey Kostanbaev <Sergey.Kostanbaev@fairwaves.co> --- openbsc/src/ussd-proxy/Makefile.am | 17 + openbsc/src/ussd-proxy/ussd_proxy.c | 1544 +++++++++++++++++++++++++++++++++++ 2 files changed, 1561 insertions(+) create mode 100644 openbsc/src/ussd-proxy/Makefile.am create mode 100644 openbsc/src/ussd-proxy/ussd_proxy.c