/* $Id: sofia.c,v 1.21 2012/09/01 03:07:37 khorben Exp $ */ /* Copyright (c) 2011-2012 Pierre Pronchery */ /* This file is part of DeforaOS Desktop Phone */ /* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include /* Sofia */ /* private */ /* types */ typedef enum _SofiaHandleType { SOFIA_HANDLE_TYPE_REGISTRATION = 0, SOFIA_HANDLE_TYPE_CALL, SOFIA_HANDLE_TYPE_MESSAGE } SofiaHandleType; typedef struct _SofiaHandle { SofiaHandleType type; nua_handle_t * handle; } SofiaHandle; typedef struct _ModemPlugin { ModemPluginHelper * helper; su_home_t home[1]; su_root_t * root; guint source; nua_t * nua; SofiaHandle * handles; size_t handles_cnt; } Sofia; /* variables */ static ModemConfig _sofia_config[] = { { "username", "Username", MCT_STRING }, { "fullname", "Full name", MCT_STRING }, { NULL, "Network:", MCT_SUBSECTION }, { "bind", "Bind address", MCT_STRING }, { NULL, "Registrar:", MCT_SUBSECTION }, { "registrar_hostname", "Hostname", MCT_STRING }, { "registrar_username", "Username", MCT_STRING }, { "registrar_password", "Password", MCT_PASSWORD }, { NULL, "Proxy:", MCT_SUBSECTION }, { "proxy_hostname", "Hostname", MCT_STRING }, { NULL, NULL, MCT_NONE }, }; /* prototypes */ /* plug-in */ static ModemPlugin * _sofia_init(ModemPluginHelper * helper); static void _sofia_destroy(ModemPlugin * modem); static int _sofia_start(ModemPlugin * modem, unsigned int retry); static int _sofia_stop(ModemPlugin * modem); static int _sofia_request(ModemPlugin * modem, ModemRequest * request); /* useful */ static nua_handle_t * _sofia_handle_add(Sofia * sofia, SofiaHandleType type, sip_to_t * to); static nua_handle_t * _sofia_handle_lookup(Sofia * sofia, SofiaHandleType type); static int _sofia_handle_remove(Sofia * sofia, nua_handle_t * handle); /* callbacks */ static void _sofia_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[]); /* public */ /* variables */ ModemPluginDefinition plugin = { "Sofia", NULL, _sofia_config, _sofia_init, _sofia_destroy, _sofia_start, _sofia_stop, _sofia_request, NULL }; /* private */ /* functions */ /* sofia_init */ static ModemPlugin * _sofia_init(ModemPluginHelper * helper) { Sofia * sofia; GSource * gsource; if((sofia = object_new(sizeof(*sofia))) == NULL) return NULL; memset(sofia, 0, sizeof(*sofia)); sofia->helper = helper; su_init(); su_home_init(sofia->home); if((sofia->root = su_glib_root_create(NULL)) == NULL) { _sofia_destroy(sofia); return NULL; } gsource = su_glib_root_gsource(sofia->root); sofia->source = g_source_attach(gsource, g_main_context_default()); sofia->handles = NULL; sofia->handles_cnt = 0; return sofia; } /* sofia_destroy */ static void _sofia_destroy(ModemPlugin * modem) { Sofia * sofia = modem; _sofia_stop(modem); if(sofia->source != 0) g_source_remove(sofia->source); sofia->source = 0; su_root_destroy(sofia->root); su_home_deinit(sofia->home); su_deinit(); object_delete(sofia); } /* sofia_start */ static int _sofia_start(ModemPlugin * modem, unsigned int retry) { Sofia * sofia = modem; ModemPluginHelper * helper = sofia->helper; url_string_t us; char const * p; char const * q; nua_handle_t * handle; ModemEvent mevent; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif if(sofia->nua != NULL) /* already started */ return 0; /* bind address */ if((p = helper->config_get(helper->modem, "bind")) != NULL && strlen(p) > 0) snprintf(us.us_str, sizeof(us.us_str), "%s%s", "sip:", p); else p = NULL; /* initialization */ if((sofia->nua = nua_create(sofia->root, _sofia_callback, modem, TAG_IF(p, NUTAG_URL(us.us_str)), SOATAG_AF(SOA_AF_IP4_IP6), TAG_END())) == NULL) return -1; /* username */ if((p = helper->config_get(helper->modem, "username")) != NULL && strlen(p) > 0) nua_set_params(sofia->nua, NUTAG_M_USERNAME(p), TAG_END()); /* fullname */ if((p = helper->config_get(helper->modem, "fullname")) != NULL && strlen(p) > 0) nua_set_params(sofia->nua, NUTAG_M_DISPLAY(p), TAG_END()); /* proxy */ if((p = helper->config_get(helper->modem, "proxy_hostname")) != NULL && strlen(p) > 0) { snprintf(us.us_str, sizeof(us.us_str), "%s%s", "sip:", p); nua_set_params(sofia->nua, NUTAG_PROXY(us.us_str), TAG_END()); } /* registration */ if((p = helper->config_get(helper->modem, "registrar_username")) != NULL && strlen(p) > 0 && (q = helper->config_get(helper->modem, "registrar_hostname")) != NULL && strlen(q) > 0) { if((handle = _sofia_handle_add(sofia, SOFIA_HANDLE_TYPE_REGISTRATION, NULL)) == NULL) return -helper->error(helper->modem, "Cannot create registration handle", 1); snprintf(us.us_str, sizeof(us.us_str), "%s%s", "sip:", q); nua_set_params(sofia->nua, NUTAG_REGISTRAR(us.us_str), TAG_END()); snprintf(us.us_str, sizeof(us.us_str), "%s%s@%s", "sip:", p, q); nua_register(handle, SIPTAG_FROM_STR(us.us_str), TAG_END()); } else { /* report that we are not registering */ memset(&mevent, 0, sizeof(mevent)); mevent.type = MODEM_EVENT_TYPE_REGISTRATION; mevent.registration.mode = MODEM_REGISTRATION_MODE_DISABLED; mevent.registration.status = MODEM_REGISTRATION_STATUS_NOT_SEARCHING; helper->event(helper->modem, &mevent); } /* set (and verify) parameters */ nua_set_params(sofia->nua, NUTAG_ENABLEMESSAGE(1), NUTAG_ENABLEINVITE(1), NUTAG_AUTOALERT(1), NUTAG_AUTOANSWER(0), TAG_END()); nua_get_params(sofia->nua, TAG_ANY(), TAG_END()); return 0; } /* sofia_stop */ static void _stop_handle(SofiaHandle * handle); static int _sofia_stop(ModemPlugin * modem) { Sofia * sofia = modem; size_t i; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif for(i = 0; i < sofia->handles_cnt; i++) _stop_handle(&sofia->handles[i]); free(sofia->handles); sofia->handles = NULL; sofia->handles_cnt = 0; if(sofia->nua != NULL) { nua_shutdown(sofia->nua); su_root_run(sofia->root); nua_destroy(sofia->nua); } sofia->nua = NULL; return 0; } static void _stop_handle(SofiaHandle * handle) { if(handle->handle == NULL) return; nua_handle_destroy(handle->handle); } /* sofia_request */ static int _request_call(ModemPlugin * modem, ModemRequest * request); static int _request_dtmf_send(ModemPlugin * modem, ModemRequest * request); static int _request_message_send(ModemPlugin * modem, ModemRequest * request); static int _sofia_request(ModemPlugin * modem, ModemRequest * request) { switch(request->type) { case MODEM_REQUEST_CALL: return _request_call(modem, request); case MODEM_REQUEST_DTMF_SEND: return _request_dtmf_send(modem, request); case MODEM_REQUEST_MESSAGE_SEND: return _request_message_send(modem, request); #ifndef DEBUG default: break; #endif } return 0; } static int _request_call(ModemPlugin * modem, ModemRequest * request) { Sofia * sofia = modem; ModemPluginHelper * helper = sofia->helper; nua_handle_t * handle; url_string_t us; sip_to_t * to; snprintf(us.us_str, sizeof(us.us_str), "%s%s", "sip:", request->call.number); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, us.us_str); #endif if((to = sip_to_make(sofia->home, us.us_str)) == NULL) return -helper->error(helper->modem, "Could not initiate the call", 1); if((handle = _sofia_handle_add(sofia, SOFIA_HANDLE_TYPE_CALL, to)) == NULL) return -helper->error(helper->modem, "Could not initiate the call", 1); to->a_display = request->call.number; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() nua_invite(\"%s\")\n", __func__, us.us_str); #endif nua_invite(handle, SOATAG_USER_SDP_STR(NULL), SOATAG_RTP_SORT(SOA_RTP_SORT_REMOTE), SOATAG_RTP_SELECT(SOA_RTP_SELECT_ALL), TAG_END()); return 0; } static int _request_dtmf_send(ModemPlugin * modem, ModemRequest * request) { Sofia * sofia = modem; ModemPluginHelper * helper = sofia->helper; nua_handle_t * handle; char buf[] = "Signal=X"; #ifdef DEBUG fprintf(stderr, "DEBUG: %s()\n", __func__); #endif /* XXX lookup the first active call */ if((handle = _sofia_handle_lookup(sofia, SOFIA_HANDLE_TYPE_CALL)) == NULL) return -helper->error(helper->modem, "Could not send DTMF", 1); buf[sizeof(buf) - 2] = request->dtmf_send.dtmf; nua_info(handle, SIPTAG_CONTENT_TYPE_STR("application/dtmf-info"), SIPTAG_PAYLOAD_STR(buf), TAG_END()); return 0; } static int _request_message_send(ModemPlugin * modem, ModemRequest * request) { Sofia * sofia = modem; ModemPluginHelper * helper = sofia->helper; url_string_t us; sip_to_t * to; nua_handle_t * handle; snprintf(us.us_str, sizeof(us.us_str), "%s%s", "sip:", request->message_send.number); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, us.us_str); #endif if((to = sip_to_make(sofia->home, us.us_str)) == NULL) return -helper->error(helper->modem, "Could not send message", 1); if((handle = _sofia_handle_add(sofia, SOFIA_HANDLE_TYPE_MESSAGE, to)) == NULL) return -helper->error(helper->modem, "Could not send message", 1); nua_message(handle, SIPTAG_CONTENT_TYPE_STR("text/plain"), SIPTAG_PAYLOAD_STR(request->message_send.content), TAG_END()); return 0; } /* useful */ /* sofia_handle_add */ static nua_handle_t * _sofia_handle_add(Sofia * sofia, SofiaHandleType type, sip_to_t * to) { size_t i; SofiaHandle * p; for(i = 0; i < sofia->handles_cnt; i++) if(sofia->handles[i].handle == NULL) break; if(i == sofia->handles_cnt) { if((p = realloc(sofia->handles, sizeof(*p) * (i + 1))) == NULL) return NULL; sofia->handles = p; sofia->handles_cnt++; } sofia->handles[i].type = type; sofia->handles[i].handle = nua_handle(sofia->nua, sofia, TAG_IF(to, NUTAG_URL(to->a_url)), TAG_IF(to, SIPTAG_TO(to)), TAG_END()); return sofia->handles[i].handle; } /* sofia_handle_lookup */ static nua_handle_t * _sofia_handle_lookup(Sofia * sofia, SofiaHandleType type) { size_t i; for(i = 0; i < sofia->handles_cnt; i++) if(sofia->handles[i].type == type) return sofia->handles[i].handle; return NULL; } /* sofia_handle_remove */ static int _sofia_handle_remove(Sofia * sofia, nua_handle_t * handle) { size_t i; for(i = 0; i < sofia->handles_cnt; i++) if(sofia->handles[i].handle == handle) { /* FIXME also free memory */ nua_handle_destroy(sofia->handles[i].handle); sofia->handles[i].handle = NULL; return 0; } return -1; } /* callbacks */ /* sofia_callback */ static void _callback_i_info(ModemPlugin * modem, int status, sip_t const * sip); static void _callback_i_message(ModemPlugin * modem, int status, sip_t const * sip); static void _callback_r_invite(ModemPlugin * modem, int status, char const * phrase, nua_handle_t * handle); static void _callback_r_message(ModemPlugin * modem, int status, char const * phrase); static void _callback_r_register(ModemPlugin * modem, int status, nua_handle_t * nh, sip_t const * sip, tagi_t tags[]); static void _sofia_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[]) { ModemPlugin * modem = magic; Sofia * sofia = modem; ModemPluginHelper * helper = modem->helper; ModemEvent mevent; #ifdef DEBUG fprintf(stderr, "DEBUG: %s(%u)\n", __func__, event); #endif switch(event) { case nua_i_error: /* FIXME report error */ fprintf(stderr, "i_error %03d %s\n", status, phrase); break; case nua_i_invite: /* FIXME report event: incoming call */ fprintf(stderr, "i_invite %03d %s\n", status, phrase); break; case nua_i_cancel: /* FIXME report event: incoming call was cancelled */ fprintf(stderr, "i_invite %03d %s\n", status, phrase); break; case nua_i_info: _callback_i_info(modem, status, sip); break; case nua_i_message: _callback_i_message(modem, status, sip); break; case nua_i_notify: /* FIXME report event */ fprintf(stderr, "i_notify %03d %s\n", status, phrase); break; case nua_i_outbound: /* FIXME what to do? */ fprintf(stderr, "i_outbound %03d %s\n", status, phrase); break; case nua_i_state: /* FIXME report event, particularly if 180 Ringing! */ fprintf(stderr, "i_state %03d %s\n", status, phrase); break; case nua_i_terminated: memset(&mevent, 0, sizeof(mevent)); mevent.type = MODEM_EVENT_TYPE_CALL; /* FIXME also remember the other fields */ mevent.call.status = MODEM_CALL_STATUS_NONE; helper->event(helper->modem, &mevent); break; case nua_r_get_params: if(status == 200) break; /* FIXME what to do? */ fprintf(stderr, "r_get_params %03d %s\n", status, phrase); break; case nua_r_info: if(status == 200) break; helper->error(helper->modem, "Could not send DTMF", 1); break; case nua_r_invite: _callback_r_invite(modem, status, phrase, nh); break; case nua_r_message: _callback_r_message(modem, status, phrase); break; case nua_r_register: _callback_r_register(modem, status, nh, sip, tags); break; case nua_r_set_params: if(status == 200) break; /* FIXME implement */ fprintf(stderr, "r_set_params %03d %s\n", status, phrase); break; case nua_r_shutdown: /* exit the background loop when ready */ if(status == 200) su_root_break(sofia->root); break; default: #ifdef DEBUG fprintf(stderr, "DEBUG: %s() %s%d%s: %03d \"%s\"\n", __func__, "event ", event, " not handled: ", status, phrase); #endif break; } } static void _callback_i_info(ModemPlugin * modem, int status, sip_t const * sip) { Sofia * sofia = modem; ModemPluginHelper * helper = sofia->helper; ModemEvent mevent; sip_from_t const * from; sip_to_t const * to; if(status != 200) /* FIXME report whatever that is */ return; /* a notification arrived */ if(sip == NULL || (from = sip->sip_from) == NULL || (to = sip->sip_to) == NULL) /* FIXME report whatever that is */ return; memset(&mevent, 0, sizeof(mevent)); mevent.type = MODEM_EVENT_TYPE_NOTIFICATION; /* FIXME we may want to include more information */ mevent.notification.content = sip->sip_payload->pl_data; helper->event(helper->modem, &mevent); } static void _callback_i_message(ModemPlugin * modem, int status, sip_t const * sip) { Sofia * sofia = modem; ModemPluginHelper * helper = sofia->helper; ModemEvent mevent; sip_from_t const * from; sip_to_t const * to; char buf[256]; if(status != 200) /* FIXME report whatever that is */ return; /* a message arrived */ if(sip == NULL || (from = sip->sip_from) == NULL || (to = sip->sip_to) == NULL) /* FIXME report whatever that is */ return; memset(&mevent, 0, sizeof(mevent)); mevent.type = MODEM_EVENT_TYPE_MESSAGE; mevent.message.date = time(NULL); /* XXX automatically import as a new contact? (from->a_display) */ snprintf(buf, sizeof(buf), URL_FORMAT_STRING, URL_PRINT_ARGS(from->a_url)); mevent.message.number = buf; mevent.message.folder = MODEM_MESSAGE_FOLDER_INBOX; mevent.message.status = MODEM_MESSAGE_STATUS_NEW; mevent.message.encoding = MODEM_MESSAGE_ENCODING_ASCII; mevent.message.length = sip->sip_payload->pl_len; mevent.message.content = sip->sip_payload->pl_data; helper->event(helper->modem, &mevent); } static void _callback_r_invite(ModemPlugin * modem, int status, char const * phrase, nua_handle_t * handle) { Sofia * sofia = modem; ModemPluginHelper * helper = sofia->helper; ModemEvent mevent; #ifdef DEBUG fprintf(stderr, "%s() %03d %s\n", __func__, status, phrase); #endif memset(&mevent, 0, sizeof(mevent)); mevent.type = MODEM_EVENT_TYPE_CALL; mevent.call.call_type = MODEM_CALL_TYPE_VOICE; mevent.call.direction = MODEM_CALL_DIRECTION_OUTGOING; if(status == 200) { mevent.call.status = MODEM_CALL_STATUS_RINGING; nua_ack(handle, TAG_END()); } else { mevent.call.status = MODEM_CALL_STATUS_NONE; /* FIXME report error */ fprintf(stderr, "r_invite %03d %s\n", status, phrase); _sofia_handle_remove(sofia, handle); } helper->event(helper->modem, &mevent); } static void _callback_r_message(ModemPlugin * modem, int status, char const * phrase) { Sofia * sofia = modem; ModemPluginHelper * helper = sofia->helper; ModemEvent mevent; #ifdef DEBUG fprintf(stderr, "%s() %03d %s\n", __func__, status, phrase); #endif memset(&mevent, 0, sizeof(mevent)); mevent.type = MODEM_EVENT_TYPE_MESSAGE_SENT; if(status == 200) /* the message could be sent */ helper->event(helper->modem, &mevent); else { /* an error occurred */ mevent.message_sent.error = phrase; helper->event(helper->modem, &mevent); } } static void _callback_r_register(ModemPlugin * modem, int status, nua_handle_t * nh, sip_t const * sip, tagi_t tags[]) { Sofia * sofia = modem; ModemPluginHelper * helper = sofia->helper; ModemEvent mevent; sip_www_authenticate_t const * wa; char const * hostname; char const * username; char const * password; char const * scheme; char const * realm; char * authstring; #ifdef DEBUG fprintf(stderr, "DEBUG: %s() %03d %s\n", __func__, status, phrase); #endif memset(&mevent, 0, sizeof(mevent)); mevent.type = MODEM_EVENT_TYPE_REGISTRATION; mevent.registration.mode = MODEM_REGISTRATION_MODE_AUTOMATIC; mevent.registration.status = MODEM_REGISTRATION_STATUS_UNKNOWN; if(status == 200) mevent.registration.status = MODEM_REGISTRATION_STATUS_REGISTERED; else if(status == 401 || status == 405) { mevent.registration.status = MODEM_REGISTRATION_STATUS_SEARCHING; wa = (sip != NULL) ? sip->sip_www_authenticate : NULL; tl_gets(tags, SIPTAG_WWW_AUTHENTICATE_REF(wa), TAG_END()); hostname = helper->config_get(helper->modem, "registrar_hostname"); username = helper->config_get(helper->modem, "registrar_username"); password = helper->config_get(helper->modem, "registrar_password"); if(wa != NULL && username != NULL && password != NULL) { scheme = wa->au_scheme; realm = msg_params_find(wa->au_params, "realm="); authstring = su_sprintf(sofia->home, "%s:%s:%s@%s:%s", scheme, realm, username, hostname, password); #ifdef DEBUG fprintf(stderr, "DEBUG: %s() authstring=\"%s\"\n", __func__, authstring); #endif nua_authenticate(nh, NUTAG_AUTH(authstring), TAG_END()); su_free(sofia->home, authstring); } } else if(status == 403) mevent.registration.status = MODEM_REGISTRATION_STATUS_DENIED; else if(status >= 400 && status <= 499) mevent.registration.status = MODEM_REGISTRATION_STATUS_NOT_SEARCHING; helper->event(helper->modem, &mevent); }