/* SIP handling * * (C) 2020 by Andreas Eversberg * All Rights Reserved * * 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, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU 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 "../libdebug/debug.h" #include #include #include #include #include #include #include "sip.h" #ifndef SOFIA_SIP_GCC_4_8_PATCH_APLLIED #warning ******************************************************** #warning Please apply the sofia-sip-gcc-4.8.patch to Sofia lib ! #warning Or compile Sofia lib with "./configure CFLAGS=-O0" ! #warning ******************************************************** #endif #undef NUTAG_AUTO100 static void invite_option_timeout(struct timer *timer); call_t *call_create(sip_endpoint_t *sip_ep) { call_t *call, **call_p; call = calloc(1, sizeof(*call)); if (!call) { PDEBUG(DSIP, DEBUG_ERROR, "No memory!\n"); abort(); } call_p = &sip_ep->call_list; while (*call_p) call_p = &((*call_p)->next); *call_p = call; call->sip_ep = sip_ep; timer_init(&call->invite_option_timer, invite_option_timeout, call); PDEBUG(DSIP, DEBUG_DEBUG, "Created new call\n"); return call; } void call_destroy(call_t *call) { call_t **call_p; /* detach */ call_p = &call->sip_ep->call_list; while (*call_p) { if (*call_p == call) break; call_p = &((*call_p)->next); } *call_p = call->next; free(call->sdp_request); free(call->sdp_response); timer_exit(&call->invite_option_timer); free(call); PDEBUG(DSIP, DEBUG_DEBUG, "destroyed call instance\n"); } static const char *state_names[] = { "IDLE", "OUT-INVITE", "IN-INVITE", "CONNECT", "OUT-RELEASE", }; static void new_state(call_t *call, enum sip_state state) { if (call->state == state) return; PDEBUG(DSIP, DEBUG_DEBUG, "Changing state %s -> %s\n", state_names[call->state], state_names[state]); call->state = state; } /* replace all internet contact lines ("c=IN ") by given type and address */ static const char *sdp_replace_contact(const char *input, const char *address) { static char sdp[65000]; char check[6], *type; int i = 0, o = 0; switch (osmo_cc_address_type(address)) { case osmo_cc_session_addrtype_ipv4: type = "IP4"; break; case osmo_cc_session_addrtype_ipv6: type = "IP6"; break; default: PDEBUG(DSIP, DEBUG_ERROR, "Public IP '%s' is not IPv4 nor IPv6, please correct config!\n", address); return input; } while (*input) { if (o == sizeof(sdp) - 1) break; /* start over when reading CR/LF */ if (*input < 32) { i = 0; sdp[o++] = *input++; continue; } /* read string to check */ check[i++] = *input; sdp[o++] = *input++; if (i < 5) continue; check[i] = '\0'; i = 0; // not required, but just to be safe! /* if string does not match, copy rest of the line */ if (!!strcmp(check, "c=IN ")) { while (*input >= 32) { if (o == sizeof(sdp) - 1) break; sdp[o++] = *input++; } continue; } /* replace address */ while (*input >= 32) input++; if (sizeof(sdp) - o <= strlen(type) + 1 + strlen(address)) break; strcpy(sdp + o, type); o += strlen(type); sdp[o++] = ' '; strcpy(sdp + o, address); o += strlen(address); } sdp[o] = '\0'; return sdp; } /* authenticate (remote) */ static int authenticate(sip_endpoint_t *sip_ep, nua_handle_t *nh, sip_t const *sip) { sip_www_authenticate_t const *authenticate = NULL; char const *realm = NULL; char const *scheme = NULL; int i; char *cur; char authentication[256] = ""; PDEBUG(DSIP, DEBUG_DEBUG, "challenge order received\n"); if (!sip_ep->authenticate_remote) { PDEBUG(DSIP, DEBUG_NOTICE, "No remote authentication enabled, cannot authenticate us towards remote peer.\n"); return -1; } if (!sip_ep->auth_user[0]) { PDEBUG(DSIP, DEBUG_NOTICE, "No credentials available\n"); return -1; } if (sip->sip_www_authenticate) { authenticate = sip->sip_www_authenticate; } else if (sip->sip_proxy_authenticate) { authenticate = sip->sip_proxy_authenticate; } else { PDEBUG(DSIP, DEBUG_NOTICE, "No authentication header found\n"); return -1; } scheme = (char const *) authenticate->au_scheme; if (authenticate->au_params) { for (i = 0; (cur = (char *) authenticate->au_params[i]); i++) { if ((realm = strstr(cur, "realm="))) { realm += 6; break; } } } if (!scheme || !realm) { PDEBUG(DSIP, DEBUG_NOTICE, "No scheme or no realm in authentication header found\n"); return -1; } snprintf(authentication, sizeof(authentication) - 1, "%s:%s:%s:%s", scheme, realm, sip_ep->auth_user, sip_ep->auth_password); authentication[sizeof(authentication) - 1] = '\0'; PDEBUG(DSIP, DEBUG_DEBUG, "auth: '%s'\n", authentication); nua_authenticate(nh, /*SIPTAG_EXPIRES_STR("3600"),*/ NUTAG_AUTH(authentication), TAG_END()); return 0; } /* some simple nonce generator */ static void generate_nonce(char *result) { sprintf(result, "%08x", (uint32_t)random()); result += 8; sprintf(result, "%08x", (uint32_t)random()); result += 8; sprintf(result, "%08x", (uint32_t)random()); result += 8; sprintf(result, "%08x", (uint32_t)random()); } /* check authorization (local) */ static int check_authorization(sip_authorization_t const *authorization, const char *regstr, const char *check_user, const char *check_pass, const char *check_realm, const char *check_nonce, const char **auth_text) { int ret = 500; *auth_text = "Internal Server Error"; char *username = NULL; char *realm = NULL; char *nonce = NULL; char *uri = NULL; char *qop = NULL; char *cnonce = NULL; char *nc = NULL; char *response = NULL; int indexnum; const char *cur; char temp[256], first_digest[2 * SU_MD5_DIGEST_SIZE + 1], second_digest[2 * SU_MD5_DIGEST_SIZE + 1], third_digest[2 * SU_MD5_DIGEST_SIZE + 1]; su_md5_t md5_ctx; if (!check_nonce || !check_nonce[0] || !authorization || !authorization->au_params) { if (!strcmp(regstr, "REGISTER")) { *auth_text = "Unauthorized"; ret = 401; } else { *auth_text = "Proxy Authentication Required"; ret = 407; } goto end; } /* parse header (stolen from freeswitch) */ for (indexnum = 0; (cur = authorization->au_params[indexnum]); indexnum++) { char *var, *val, *p, *work; var = val = work = NULL; if ((work = strdup(cur))) { var = work; if ((val = strchr(var, '='))) { *val++ = '\0'; while (*val == '"') { *val++ = '\0'; } if ((p = strchr(val, '"'))) { *p = '\0'; } PDEBUG(DSIP, DEBUG_DEBUG, "Found in Auth header: %s = %s\n", var, val); if (!strcasecmp(var, "username")) { username = strdup(val); } else if (!strcasecmp(var, "realm")) { realm = strdup(val); } else if (!strcasecmp(var, "nonce")) { nonce = strdup(val); } else if (!strcasecmp(var, "uri")) { uri = strdup(val); } else if (!strcasecmp(var, "qop")) { qop = strdup(val); } else if (!strcasecmp(var, "cnonce")) { cnonce = strdup(val); } else if (!strcasecmp(var, "response")) { response = strdup(val); } else if (!strcasecmp(var, "nc")) { nc = strdup(val); } } free(work); } } if (!username || !realm || !nonce || ! uri || !response) { *auth_text = "Authorization header incomplete"; ret = 400; goto end; } if (!!strcmp(username, check_user)) { *auth_text = "Authorization Username Missmatch"; ret = 403; goto end; } if (!!strcmp(realm, check_realm)) { *auth_text = "Authorization Realm Missmatch"; ret = 403; goto end; } if (!!strcmp(nonce, check_nonce)) { *auth_text = "Authorization Nonce Missmatch"; ret = 403; goto end; } /* perform hash */ snprintf(temp, sizeof(temp) - 1, "%s:%s:%s", check_user, realm, check_pass); temp[sizeof(temp) - 1] = '\0'; PDEBUG(DSIP, DEBUG_DEBUG, "First hash: %s\n", temp); su_md5_init(&md5_ctx); su_md5_strupdate(&md5_ctx, temp); su_md5_hexdigest(&md5_ctx, first_digest); su_md5_deinit(&md5_ctx); snprintf(temp, sizeof(temp) - 1, "%s:%s", regstr, uri); temp[sizeof(temp) - 1] = '\0'; PDEBUG(DSIP, DEBUG_DEBUG, "Second hash: %s\n", temp); su_md5_init(&md5_ctx); su_md5_strupdate(&md5_ctx, temp); su_md5_hexdigest(&md5_ctx, second_digest); su_md5_deinit(&md5_ctx); if (nc && cnonce && qop) snprintf(temp, sizeof(temp) - 1, "%s:%s:%s:%s:%s:%s", first_digest, nonce, nc, cnonce, qop, second_digest); else snprintf(temp, sizeof(temp) - 1, "%s:%s:%s", first_digest, nonce, second_digest); temp[sizeof(temp) - 1] = '\0'; PDEBUG(DSIP, DEBUG_DEBUG, "Third hash: %s\n", temp); su_md5_init(&md5_ctx); su_md5_strupdate(&md5_ctx, temp); su_md5_hexdigest(&md5_ctx, third_digest); su_md5_deinit(&md5_ctx); if (!!strcmp(response, third_digest)) { *auth_text = "Authorization Failed"; ret = 403; goto end; } *auth_text = "Authorization Success"; ret = 200; end: free(username); free(realm); free(nonce); free(uri); free(qop); free(cnonce); free(nc); free(response); return ret; } static void release_and_destroy(call_t *call, uint8_t cc_isdn_cause, uint16_t cc_sip_cause, uint8_t isdn_cause, uint16_t sip_cause, const char *sip_cause_text) { char isdn_cause_str[256] = "", sip_cause_str[256] = ""; osmo_cc_msg_t *msg; if (isdn_cause) { sprintf(isdn_cause_str, "Q.850;cause=%d;text=\"%s\"", isdn_cause, ""); } if (sip_cause) { sprintf(sip_cause_str, "SIP;cause=%d;text=\"%s\"", sip_cause, ""); } if (cc_isdn_cause || cc_sip_cause) { /* create osmo-cc message */ if (call->state == SIP_STATE_OUT_RELEASE) msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_CNF); else if (call->state == SIP_STATE_IDLE) msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND); else msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); /* cause */ osmo_cc_add_ie_cause(msg, OSMO_CC_LOCATION_BEYOND_INTERWORKING, cc_isdn_cause, cc_sip_cause, 0); /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); } if (call->nua_handle && (isdn_cause || sip_cause)) { if (call->state == SIP_STATE_IN_INVITE) { PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", sip_cause, sip_cause_text, call->cc_callref); nua_respond(call->nua_handle, (sip_cause < 300) ? 486 : sip_cause, sip_cause_text, // if no usable sip_cause, use 486 (Busy Here) TAG_IF(isdn_cause_str[0], SIPTAG_REASON_STR(isdn_cause_str)), TAG_END()); } else if (call->state == SIP_STATE_OUT_INVITE) { PDEBUG(DSIP, DEBUG_INFO, "Sending CANCEL (callref %d)\n", call->cc_callref); nua_cancel(call->nua_handle, TAG_IF(sip_cause_str[0], SIPTAG_REASON_STR(sip_cause_str)), TAG_IF(isdn_cause_str[0], SIPTAG_REASON_STR(isdn_cause_str)), TAG_END()); return; } else { PDEBUG(DSIP, DEBUG_INFO, "Sending BYE (callref %d)\n", call->cc_callref); nua_bye(call->nua_handle, TAG_IF(sip_cause_str[0], SIPTAG_REASON_STR(sip_cause_str)), TAG_IF(isdn_cause_str[0], SIPTAG_REASON_STR(isdn_cause_str)), TAG_END()); return; } } if (call->nua_handle) { PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", call->nua_handle); nua_handle_destroy(call->nua_handle); call->nua_handle = NULL; } /* call terminated */ new_state(call, SIP_STATE_IDLE); call_destroy(call); } /* * messages from from CC */ static void setup_req(call_t *call, osmo_cc_msg_t *msg) { char from[256] = ""; char asserted_id[256] = "", asserted_msg[512] = ""; char to[256] = ""; char contact[256 + 10] = ""; sip_cseq_t *cseq = NULL; uint8_t type, plan, present, screen; char callerid[256], dialing[256]; const char *sdp = sdp; int rc; if (!call->sip_ep->remote_peer[0]) { PDEBUG(DSIP, DEBUG_NOTICE, "No remote peer set or no peer has registered to us.\n"); release_and_destroy(call, OSMO_CC_ISDN_CAUSE_DEST_OOO, 0, 0, 0, ""); return; } PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE (callref %d)\n", call->cc_callref); call->nua_handle = nua_handle(call->sip_ep->nua, NULL, TAG_END()); if (!call->nua_handle) { PDEBUG(DSIP, DEBUG_ERROR, "Failed to create handle\n"); release_and_destroy(call, OSMO_CC_ISDN_CAUSE_TEMP_FAILURE, 0, 0, 0, ""); return; } PDEBUG(DSIP, DEBUG_DEBUG, " -> new nua_handle %p\n", call->nua_handle); /* caller information */ rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, callerid, sizeof(callerid)); if (rc < 0) callerid[0] = '\0'; if (callerid[0] || call->sip_ep->local_user) { sprintf(from, "sip:%s@%s", (call->sip_ep->local_user) ? : callerid, call->sip_ep->local_peer); if (call->sip_ep->public_ip[0]) sprintf(contact, "sip:%s@%s", (call->sip_ep->local_user) ? : callerid, call->sip_ep->public_ip); } else { sprintf(from, "sip:%s", call->sip_ep->local_peer); if (call->sip_ep->public_ip[0]) sprintf(contact, "sip:%s", call->sip_ep->public_ip); } PDEBUG(DSIP, DEBUG_DEBUG, " -> From = %s\n", from); /* dialing information */ rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, dialing, sizeof(dialing)); if (rc < 0) dialing[0] = '\0'; if (dialing[0] || call->sip_ep->remote_user) { sprintf(to, "sip:%s@%s", (call->sip_ep->remote_user) ? : dialing, call->sip_ep->remote_peer); } else sprintf(to, "sip:%s", call->sip_ep->remote_peer); PDEBUG(DSIP, DEBUG_DEBUG, " -> To = %s\n", to); /* asserted id */ if (call->sip_ep->asserted_id) { sprintf(asserted_id, "sip:%s@%s", call->sip_ep->asserted_id, call->sip_ep->local_peer); sprintf(asserted_msg, "P-Asserted-Identity: <%s>", asserted_id); PDEBUG(DSIP, DEBUG_DEBUG, " -> Asserted ID = %s\n", asserted_id); } /* public (or stun) ip */ if (call->sip_ep->public_ip[0]) { const char *p; // contact is set above /* append port of local peer */ p = osmo_cc_port_of_address(call->sip_ep->local_peer); if (p) strcat(contact, p); PDEBUG(DSIP, DEBUG_DEBUG, " -> Contact = %s\n", contact); } /* SDP */ char sdp_buffer[65536]; rc = osmo_cc_get_ie_sdp(msg, 0, sdp_buffer, sizeof(sdp_buffer)); if (rc >= 0) { sdp = sdp_buffer; if (call->sip_ep->public_ip[0]) { sdp = sdp_replace_contact(sdp, call->sip_ep->public_ip); PDEBUG(DSIP, DEBUG_DEBUG, " -> Modify Contact line(s) of SDP:\n"); } free(call->sdp_request); call->sdp_request = strdup(sdp); osmo_cc_debug_sdp(sdp); } else sdp = NULL; // cseq = sip_cseq_create(sip_home, 123, SIP_METHOD_INVITE); nua_invite(call->nua_handle, TAG_IF(from[0], SIPTAG_FROM_STR(from)), TAG_IF(to[0], SIPTAG_TO_STR(to)), TAG_IF(asserted_msg[0], SIPTAG_HEADER_STR(asserted_msg)), TAG_IF(contact[0], SIPTAG_CONTACT_STR(contact)), TAG_IF(cseq, SIPTAG_CSEQ(cseq)), NUTAG_MEDIA_ENABLE(0), TAG_IF(sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")), TAG_IF(sdp, SIPTAG_PAYLOAD_STR(sdp)), TAG_END()); new_state(call, SIP_STATE_OUT_INVITE); /* create osmo-cc message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_PROC_IND); /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); } static void send_progress_sdp(call_t *call, const char *sdp) { if (call->sdp_sent) return; PDEBUG(DSIP, DEBUG_DEBUG, " -> Reply with 183 'Session Progress'\n"); if (call->sip_ep->public_ip[0]) { sdp = sdp_replace_contact(sdp, call->sip_ep->public_ip); PDEBUG(DSIP, DEBUG_DEBUG, " -> Modify Contact line(s) of SDP:\n"); } free(call->sdp_response); call->sdp_response = strdup(sdp); osmo_cc_debug_sdp(sdp); PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", SIP_183_SESSION_PROGRESS, call->cc_callref); nua_respond(call->nua_handle, SIP_183_SESSION_PROGRESS, NUTAG_MEDIA_ENABLE(0), TAG_IF(sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")), TAG_IF(sdp, SIPTAG_PAYLOAD_STR(sdp)), TAG_END()); call->sdp_sent = 1; } static void setup_ack_req(call_t *call, osmo_cc_msg_t *msg) { char sdp[65536]; int rc; rc = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp)); if (rc >= 0) send_progress_sdp(call, sdp); } static void proc_req(call_t *call, osmo_cc_msg_t *msg) { char sdp[65536]; int rc; rc = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp)); if (rc >= 0) send_progress_sdp(call, sdp); } static void alert_req(call_t *call, osmo_cc_msg_t *msg) { char sdp[65536]; int rc; rc = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp)); if (rc >= 0) send_progress_sdp(call, sdp); if (call->sip_ep->send_no_ringing_after_progress) { PDEBUG(DSIP, DEBUG_DEBUG, "Sending no 180 'Ringing' after 183 'Session Progress' with SDP.\n"); return; } PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", SIP_180_RINGING, call->cc_callref); nua_respond(call->nua_handle, SIP_180_RINGING, TAG_END()); } static void setup_rsp(call_t *call, osmo_cc_msg_t *msg) { char sdp_buffer[65536]; const char *sdp; int rc; rc = osmo_cc_get_ie_sdp(msg, 0, sdp_buffer, sizeof(sdp_buffer)); if (rc >= 0) { sdp = sdp_buffer; if (call->sip_ep->public_ip[0]) { sdp = sdp_replace_contact(sdp, call->sip_ep->public_ip); PDEBUG(DSIP, DEBUG_DEBUG, " -> Modify Contact line(s) of SDP:\n"); } free(call->sdp_response); call->sdp_response = strdup(sdp); osmo_cc_debug_sdp(sdp); } else sdp = NULL; PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref); nua_respond(call->nua_handle, SIP_200_OK, NUTAG_MEDIA_ENABLE(0), TAG_IF(sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")), TAG_IF(sdp, SIPTAG_PAYLOAD_STR(sdp)), TAG_END()); new_state(call, SIP_STATE_CONNECT); /* create osmo-cc message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_COMP_IND); /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); } static void progress_req(call_t *call, osmo_cc_msg_t *msg) { char sdp[65536]; int rc; rc = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp)); if (rc >= 0) send_progress_sdp(call, sdp); } static void info_req(call_t *call, osmo_cc_msg_t *msg) { uint8_t duration_ms, pause_ms, dtmf_mode; char digits[256]; char dtmf_str[256]; int rc; rc = osmo_cc_get_ie_dtmf(msg, 0, &duration_ms, &pause_ms, &dtmf_mode, digits, sizeof(digits)); if (rc >= 0 && digits[0]) { /* prepare DTMF info payload */ sprintf(dtmf_str, "Signal=%c\r\nDuration=%d\r\n", digits[0], duration_ms); PDEBUG(DSIP, DEBUG_INFO, "Sending INFO (callref %d)\n", call->cc_callref); PDEBUG(DSIP, DEBUG_DEBUG, " -> DTMF digit %c\n", digits[0]); /* start invite to handle DTMF */ nua_info(call->nua_handle, NUTAG_MEDIA_ENABLE(0), SIPTAG_CONTENT_TYPE_STR("application/dtmf-relay"), SIPTAG_PAYLOAD_STR(dtmf_str), TAG_END()); } } static void notify_req(void) { PDEBUG(DSIP, DEBUG_NOTICE, "CC-NOTIFY-REQUEST not supported!\n"); } static void rej_req(call_t *call, osmo_cc_msg_t *msg) { uint8_t location, isdn_cause, socket_cause; uint16_t sip_cause; int rc; rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); if (rc < 0) { location = OSMO_CC_LOCATION_BEYOND_INTERWORKING; isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; sip_cause = 486; } release_and_destroy(call, 0, 0, isdn_cause, sip_cause, ""); } static void disc_req(call_t *call, osmo_cc_msg_t *msg) { uint8_t location, isdn_cause, socket_cause; uint16_t sip_cause; int rc; rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); if (rc < 0) { location = OSMO_CC_LOCATION_BEYOND_INTERWORKING; isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; sip_cause = 486; } /* create osmo-cc message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); /* cause */ osmo_cc_add_ie_cause(msg, location, isdn_cause, sip_cause, 0); /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); release_and_destroy(call, 0, 0, isdn_cause, sip_cause, ""); } static void rel_req(call_t *call, osmo_cc_msg_t *msg) { uint8_t location, isdn_cause, socket_cause; uint16_t sip_cause; int rc; rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); if (rc < 0) { location = OSMO_CC_LOCATION_BEYOND_INTERWORKING; isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; sip_cause = 486; } new_state(call, SIP_STATE_OUT_RELEASE); release_and_destroy(call, 0, 0, isdn_cause, sip_cause, ""); } void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg) { sip_endpoint_t *sip_ep = ep->priv; call_t *call; /* hunt for callref */ call = sip_ep->call_list; while (call) { if (call->cc_callref == callref) break; call = call->next; } /* process SETUP */ if (!call) { if (msg->type != OSMO_CC_MSG_SETUP_REQ) { PDEBUG(DSIP, DEBUG_ERROR, "received message without call instance, please fix!\n"); return; } /* creating call instance, transparent until setup with hdlc */ call = call_create(sip_ep); if (!call) { PDEBUG(DSIP, DEBUG_ERROR, "Cannot create calll instance.\n"); abort(); } /* link with cc */ call->cc_callref = callref; } switch (msg->type) { case OSMO_CC_MSG_SETUP_REQ: /* dial-out command received from epoint */ setup_req(call, msg); break; case OSMO_CC_MSG_SETUP_ACK_REQ: /* more information is needed */ if (call->state != SIP_STATE_IN_INVITE) break; setup_ack_req(call, msg); break; case OSMO_CC_MSG_PROC_REQ: /* call of endpoint is proceeding */ if (call->state != SIP_STATE_IN_INVITE) break; proc_req(call, msg); break; case OSMO_CC_MSG_ALERT_REQ: /* call of endpoint is ringing */ if (call->state != SIP_STATE_IN_INVITE) break; alert_req(call, msg); break; case OSMO_CC_MSG_SETUP_RSP: /* call of endpoint is connected */ if (call->state != SIP_STATE_IN_INVITE) break; setup_rsp(call, msg); break; case OSMO_CC_MSG_SETUP_COMP_REQ: /* call of endpoint is connected */ break; case OSMO_CC_MSG_PROGRESS_REQ: /* progress */ if (call->state != SIP_STATE_IN_INVITE) break; progress_req(call, msg); break; case OSMO_CC_MSG_INFO_REQ: /* overlap dialing */ if (call->state != SIP_STATE_CONNECT) break; info_req(call, msg); break; case OSMO_CC_MSG_NOTIFY_REQ: /* display and notifications */ if (call->state != SIP_STATE_CONNECT) break; notify_req(); break; case OSMO_CC_MSG_REJ_REQ: /* call has been rejected */ rej_req(call, msg); break; case OSMO_CC_MSG_DISC_REQ: /* call has been disconnected */ disc_req(call, msg); break; case OSMO_CC_MSG_REL_REQ: /* release sip call port */ rel_req(call, msg); break; default: PDEBUG(DSIP, DEBUG_ERROR, "received an unsupported CC message: %d\n", msg->type); } osmo_cc_free_msg(msg); } /* * messages from SIP stack */ static void ep_i_register(sip_endpoint_t *sip_ep, int status, nua_t *nua, nua_handle_t *nh, sip_t const *sip) { PDEBUG(DSIP, DEBUG_INFO, "Received REGISTER (registration)\n"); #define NUTAG_WITH_THIS_MSG(msg) nutag_with, tag_ptr_v(msg) nua_saved_event_t saved[1]; nua_save_event(nua, saved); nua_event_data_t const *data = nua_event_data(saved); sip_authorization_t const *authorization; char contact[255] = ""; const char *auth_text = NULL; char auth_str[256] = ""; if (sip->sip_contact->m_url->url_host) { // FIXME: unstable/might not work with IPv6 strcpy(contact, sip->sip_contact->m_url->url_host); if (sip->sip_contact->m_url->url_port && sip->sip_contact->m_url->url_port[0]) { strcat(contact, ":"); strcat(contact, sip->sip_contact->m_url->url_port); } } if (!sip_ep->local_register) { PDEBUG(DSIP, DEBUG_DEBUG, "forbidden, because we don't accept registration"); PDEBUG(DSIP, DEBUG_INFO, "Sending REGISTER response: %d %s (registration)\n", SIP_403_FORBIDDEN); nua_respond(nh, SIP_403_FORBIDDEN, NUTAG_WITH_THIS_MSG(data->e_msg), TAG_END()); PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p (register)\n", nh); nua_handle_destroy(nh); sip_ep->register_handle = NULL; return; } PDEBUG(DSIP, DEBUG_DEBUG, " -> contact %s\n", contact); if (sip_ep->authenticate_local && sip_ep->auth_realm[0]) { authorization = sip->sip_authorization; status = check_authorization(authorization, "REGISTER", sip_ep->auth_user, sip_ep->auth_password, sip_ep->auth_realm, sip_ep->register_nonce, &auth_text); if (status == 401) { if (!sip_ep->register_nonce[0]) generate_nonce(sip_ep->register_nonce); sprintf(auth_str, "Digest realm=\"%s\", nonce=\"%s\", algorithm=MD5, qop=\"auth\"", sip_ep->auth_realm, sip_ep->register_nonce); } } else { status = 200; auth_text = "Authentication not required"; } PDEBUG(DSIP, DEBUG_DEBUG, " -> Authentication: %d %s\n", status, auth_text); if (status == 200) { strncpy(sip_ep->remote_contact, contact, sizeof(sip_ep->remote_contact) - 1); sip_ep->remote_peer = sip_ep->remote_contact; sip_ep->register_nonce[0] = '\0'; } PDEBUG(DSIP, DEBUG_INFO, "Sending REGISTER response: %d %s (registration)\n", status, auth_text); nua_respond(nh, status, auth_text, SIPTAG_CONTACT(sip->sip_contact), NUTAG_WITH_THIS_MSG(data->e_msg), TAG_IF(auth_str[0], SIPTAG_WWW_AUTHENTICATE_STR(auth_str)), TAG_END()); PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p (register)\n", nh); nua_handle_destroy(nh); sip_ep->register_handle = NULL; } static void ep_r_register(sip_endpoint_t *sip_ep, int status, char const *phrase, nua_handle_t *nh, sip_t const *sip) { int rc; PDEBUG(DSIP, DEBUG_INFO, "Received REGISTER response: %d %s (registration)\n", status, phrase); switch (status) { case 200: status_200: /* if not registered, become registered and start register interval timer */ if (sip_ep->register_state != REGISTER_STATE_REGISTERED) { if (sip_ep->register_interval) timer_start(&sip_ep->register_retry_timer, sip_ep->register_interval); sip_ep->register_state = REGISTER_STATE_REGISTERED; } #if 0 //register options does not work /* start option timer */ if (sip_ep->options_interval) { PDEBUG(DSIP, DEBUG_DEBUG, "Register ok, scheduling option timer with %d seconds\n", sip_ep->options_interval); timer_start(&sip_ep->register_option_timer, sip_ep->options_interval); } #endif break; case 401: case 407: PDEBUG(DSIP, DEBUG_DEBUG, "Register challenge received\n"); rc = authenticate(sip_ep, nh, sip); if (rc < 0) goto status_400; break; default: if (status >= 200 && status <= 299) goto status_200; if (status < 400) break; status_400: PDEBUG(DSIP, DEBUG_DEBUG, "Register failed, starting register timer\n"); sip_ep->register_state = REGISTER_STATE_FAILED; PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p (register)\n", nh); nua_handle_destroy(nh); sip_ep->register_handle = NULL; /* stop option timer */ timer_stop(&sip_ep->register_option_timer); /* if failed, start register interval timer with REGISTER_RETRY_TIMER */ timer_start(&sip_ep->register_retry_timer, REGISTER_RETRY_TIMER); } } static void ep_i_options(nua_t *nua, nua_handle_t *nh) { PDEBUG(DSIP, DEBUG_INFO, "Received OPTIONS (registration)\n"); #define NUTAG_WITH_THIS_MSG(msg) nutag_with, tag_ptr_v(msg) nua_saved_event_t saved[1]; nua_save_event(nua, saved); nua_event_data_t const *data = nua_event_data(saved); PDEBUG(DSIP, DEBUG_INFO, "Sending OPTIONS response: %d %s (registration)\n", SIP_200_OK); nua_respond(nh, SIP_200_OK, NUTAG_WITH_THIS_MSG(data->e_msg), TAG_END()); } static void ep_r_options(sip_endpoint_t __attribute__((unused)) *sip_ep, int status, char const *phrase, nua_handle_t __attribute__((unused)) *nh) { PDEBUG(DSIP, DEBUG_INFO, "Received OPTIONS response: %d %s (registration)\n", status, phrase); #if 0 //register options does not work if (status >= 200 && status <= 299) { PDEBUG(DSIP, DEBUG_DEBUG, "options ok, scheduling option timer with %d seconds\n", sip_ep->options_interval); /* restart option timer */ timer_start(&sip_ep->register_option_timer, sip_ep->options_interval); return; } PDEBUG(DSIP, DEBUG_DEBUG, "Register options failed, starting register timer\n"); sip_ep->register_state = REGISTER_STATE_FAILED; PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p (register)\n", nh); nua_handle_destroy(nh); sip_ep->register_handle = NULL; /* if failed, start register interval timer with REGISTER_RETRY_TIMER */ timer_start(&sip_ep->register_retry_timer, REGISTER_RETRY_TIMER); #endif } static void call_i_invite(call_t *call, nua_handle_t *nh, sip_t const *sip) { const char *from = "", *to = "", *from_name = "", *to_name = ""; sip_authorization_t const *authorization; const char *auth_text = NULL; char auth_str[256] = ""; osmo_cc_msg_t *msg; int status; if (sip->sip_from) { if (sip->sip_from->a_url && sip->sip_from->a_url->url_user) from = sip->sip_from->a_url->url_user; if (sip->sip_from->a_display) from_name = sip->sip_from->a_display; } if (sip->sip_to) { if (sip->sip_to->a_url && sip->sip_to->a_url->url_user) to = sip->sip_to->a_url->url_user; if (sip->sip_to->a_display) to_name = sip->sip_to->a_display; } if (call->state != SIP_STATE_IDLE) PDEBUG(DSIP, DEBUG_INFO, "Received RE-INVITE (callref %d)\n", call->cc_callref); else PDEBUG(DSIP, DEBUG_INFO, "Received INVITE (callref %d)\n", call->cc_callref); PDEBUG(DSIP, DEBUG_DEBUG, " -> From = %s '%s'\n", from, from_name); PDEBUG(DSIP, DEBUG_DEBUG, " -> To = %s '%s'\n", to, to_name); if (call->sip_ep->authenticate_local && call->sip_ep->auth_realm[0] && call->state == SIP_STATE_IDLE) { /* only authenticate remote, if we don't have a re-invite */ authorization = sip->sip_proxy_authorization; status = check_authorization(authorization, "INVITE", call->sip_ep->auth_user, call->sip_ep->auth_password, call->sip_ep->auth_realm, call->sip_ep->invite_nonce, &auth_text); if (status == 407) { if (!call->sip_ep->invite_nonce[0]) generate_nonce(call->sip_ep->invite_nonce); sprintf(auth_str, "Digest realm=\"%s\", nonce=\"%s\", algorithm=MD5, qop=\"auth\"", call->sip_ep->auth_realm, call->sip_ep->invite_nonce); } } else { status = 200; auth_text = "Authentication not required"; } if (status != 200) { PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", status, auth_text, call->cc_callref); nua_respond(nh, status, auth_text, SIPTAG_CONTACT(sip->sip_contact), TAG_IF(auth_str[0], SIPTAG_PROXY_AUTHENTICATE_STR(auth_str)), TAG_END()); nua_handle_destroy(nh); call_destroy(call); return; } call->sip_ep->invite_nonce[0] = '\0'; if (sip->sip_payload && sip->sip_payload->pl_data && sip->sip_payload->pl_len) { free(call->sdp_request); call->sdp_request = malloc(sip->sip_payload->pl_len + 1); memcpy(call->sdp_request, sip->sip_payload->pl_data, sip->sip_payload->pl_len); call->sdp_request[sip->sip_payload->pl_len] = '\0'; } else { PDEBUG(DSIP, DEBUG_DEBUG, " -> No SDP in message\n"); new_state(call, SIP_STATE_IN_INVITE); release_and_destroy(call, 0, 400, 0, SIP_400_BAD_REQUEST); return; } /* handle re-invite */ if (call->state != SIP_STATE_IDLE) { char *sdp = call->sdp_response; PDEBUG(DSIP, DEBUG_INFO, "Sending RE-INVITE response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref); nua_respond(call->nua_handle, SIP_200_OK, NUTAG_MEDIA_ENABLE(0), TAG_IF(sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")), TAG_IF(sdp, SIPTAG_PAYLOAD_STR(sdp)), TAG_END()); return; } /* apply handle */ PDEBUG(DSIP, DEBUG_DEBUG, "new nua_handle %p\n", nh); call->nua_handle = nh; /* create osmo-cc message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND); /* newtwork + interface */ osmo_cc_add_ie_calling_network(msg, OSMO_CC_NETWORK_SIP_NONE, ""); osmo_cc_add_ie_calling_interface(msg, call->sip_ep->name); if (call->sdp_request) { /* send SDP answer */ osmo_cc_add_ie_sdp(msg, call->sdp_request); } /* caller information */ if (!from[0]) { osmo_cc_add_ie_calling(msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_NOT_AVAIL, OSMO_CC_SCREEN_NETWORK, ""); } else { osmo_cc_add_ie_calling(msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, OSMO_CC_PRESENT_ALLOWED, OSMO_CC_SCREEN_NETWORK, from); if (from_name[0]) osmo_cc_add_ie_calling_name(msg, from_name); } /* dialing information */ if (to[0]) { osmo_cc_add_ie_called(msg, OSMO_CC_TYPE_UNKNOWN, OSMO_CC_PLAN_TELEPHONY, to); if (to_name[0]) osmo_cc_add_ie_called_name(msg, to_name); } /* complete */ osmo_cc_add_ie_complete(msg); #ifdef NUTAG_AUTO100 /* send trying (proceeding) */ PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s (callref %d)\n", SIP_100_TRYING, call->cc_callref); nua_respond(nh, SIP_100_TRYING, TAG_END()); #endif /* create endpoint */ osmo_cc_call_t *cc_call = osmo_cc_call_new(&call->sip_ep->cc_ep); call->cc_callref = cc_call->callref; new_state(call, SIP_STATE_IN_INVITE); /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); /* start option timer */ if (call->sip_ep->options_interval) { PDEBUG(DSIP, DEBUG_DEBUG, "Invite received, scheduling option timer with %d seconds\n", call->sip_ep->options_interval); timer_start(&call->invite_option_timer, call->sip_ep->options_interval); } } static void call_r_invite(call_t *call, int status, char const *phrase, nua_handle_t *nh, sip_t const *sip) { osmo_cc_msg_t *msg; int rc; if (call->state == SIP_STATE_CONNECT) PDEBUG(DSIP, DEBUG_INFO, "Received RE-INVITE response: %d %s (callref %d)\n", status, phrase, call->cc_callref); else PDEBUG(DSIP, DEBUG_INFO, "Received INVITE response: %d %s (callref %d)\n", status, phrase, call->cc_callref); if (status == 401 || status == 407) { PDEBUG(DSIP, DEBUG_DEBUG, "Invite challenge received\n"); rc = authenticate(call->sip_ep, nh, sip); if (rc < 0) { release_and_destroy(call, 0, status, 0, 0, ""); } return; } /* connect audio */ if (status == 183 || (status >= 200 && status <= 299)) { if (sip->sip_payload && sip->sip_payload->pl_data && sip->sip_payload->pl_len) { free(call->sdp_response); call->sdp_response = malloc(sip->sip_payload->pl_len + 1); memcpy(call->sdp_response, sip->sip_payload->pl_data, sip->sip_payload->pl_len); call->sdp_response[sip->sip_payload->pl_len] = '\0'; osmo_cc_debug_sdp(call->sdp_response); } else if (status >= 200 && status <= 299) { PDEBUG(DSIP, DEBUG_DEBUG, " -> No SDP in message\n"); release_and_destroy(call, 0, 400, 0, SIP_400_BAD_REQUEST); return; } } switch (status) { case 180: if (call->alerting_sent) return; /* create osmo-cc message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND); /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); return; case 183: /* create osmo-cc message */ if (call->sip_ep->receive_no_ringing_after_progress) { msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND); call->alerting_sent = 1; } else msg = osmo_cc_new_msg(OSMO_CC_MSG_PROGRESS_IND); if (call->sdp_response) { /* progress indicator */ osmo_cc_add_ie_progress(msg, OSMO_CC_CODING_ITU_T, OSMO_CC_LOCATION_BEYOND_INTERWORKING, OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE); /* send SDP answer */ osmo_cc_add_ie_sdp(msg, call->sdp_response); } /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); return; case 200: status_200: nua_ack(nh, TAG_END()); /* on reinvite we are done */ if (call->state == SIP_STATE_CONNECT) return; /* start option timer */ if (call->sip_ep->options_interval) { PDEBUG(DSIP, DEBUG_DEBUG, "Invite response, scheduling option timer with %d seconds\n", call->sip_ep->options_interval); timer_start(&call->invite_option_timer, call->sip_ep->options_interval); } /* create osmo-cc message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_CNF); if (call->sdp_response) { /* send SDP answer */ osmo_cc_add_ie_sdp(msg, call->sdp_response); } new_state(call, SIP_STATE_CONNECT); /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); return; default: if (status >= 200 && status <= 299) goto status_200; if (status < 100 || status > 199) break; PDEBUG(DSIP, DEBUG_DEBUG, "skipping 1xx message\n"); return; } release_and_destroy(call, 0, status, 0, 0, ""); } static void call_i_options(call_t *call, nua_handle_t *nh) { PDEBUG(DSIP, DEBUG_INFO, "Received OPTIONS (callref %d)\n", call->cc_callref); PDEBUG(DSIP, DEBUG_INFO, "Sending OPTIONS response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref); nua_respond(nh, SIP_200_OK, TAG_END()); } static void call_r_options(call_t *call, int status, char const *phrase) { PDEBUG(DSIP, DEBUG_INFO, "Received OPTIONS response: %d %s (callref %d)\n", status, phrase, call->cc_callref); if (status >= 200 && status <= 299) { PDEBUG(DSIP, DEBUG_DEBUG, "options ok, scheduling option timer with %d seconds\n", call->sip_ep->options_interval); /* restart option timer */ timer_start(&call->invite_option_timer, call->sip_ep->options_interval); return; } } // code stolen from freeswitch.... static char RFC2833_CHARS[] = "0123456789*#ABCDF"; static char switch_rfc2833_to_char(int event) { if (event > -1 && event < (int32_t) sizeof(RFC2833_CHARS)) { return RFC2833_CHARS[event]; } return '\0'; } static void call_i_info(call_t *call, nua_t *nua, nua_handle_t *nh, sip_t const *sip) { char digit = '\0'; osmo_cc_msg_t *msg; #define NUTAG_WITH_THIS_MSG(msg) nutag_with, tag_ptr_v(msg) nua_saved_event_t saved[1]; nua_save_event(nua, saved); nua_event_data_t const *data = nua_event_data(saved); PDEBUG(DSIP, DEBUG_INFO, "Received INFO (callref %d)\n", call->cc_callref); // code stolen from freeswitch.... if (sip && sip->sip_content_type && sip->sip_content_type->c_type && sip->sip_content_type->c_subtype && sip->sip_payload && sip->sip_payload->pl_data) { if (!strncasecmp(sip->sip_content_type->c_type, "application", 11) && !strcasecmp(sip->sip_content_type->c_subtype, "dtmf-relay")) { const char *signal_ptr; if ((signal_ptr = strstr(sip->sip_payload->pl_data, "Signal="))) { int tmp; /* move signal_ptr where we need it (right past Signal=) */ signal_ptr = signal_ptr + 7; /* handle broken devices with spaces after the = (cough) VegaStream (cough) */ while (*signal_ptr && *signal_ptr == ' ') signal_ptr++; if (*signal_ptr && (*signal_ptr == '*' || *signal_ptr == '#' || *signal_ptr == 'A' || *signal_ptr == 'B' || *signal_ptr == 'C' || *signal_ptr == 'D')) { digit = *signal_ptr; } else { tmp = atoi(signal_ptr); digit = switch_rfc2833_to_char(tmp); } } } else if (!strncasecmp(sip->sip_content_type->c_type, "application", 11) && !strcasecmp(sip->sip_content_type->c_subtype, "dtmf")) { int tmp = atoi(sip->sip_payload->pl_data); digit = switch_rfc2833_to_char(tmp); } } PDEBUG(DSIP, DEBUG_INFO, "Sending INFO response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref); nua_respond(nh, SIP_200_OK, NUTAG_WITH_THIS_MSG(data->e_msg), TAG_END()); if (digit) { char digits[2] = { digit, '\0' }; PDEBUG(DSIP, DEBUG_DEBUG, " -> DTMF digit: %c\n", digit); /* create osmo-cc message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_INFO_IND); /* dtmf */ osmo_cc_add_ie_dtmf(msg, 160, 160, OSMO_CC_DTMF_MODE_DIGITS, digits); /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); } } static void call_i_bye(call_t *call, nua_handle_t *nh, sip_t const *sip) { sip_reason_t *reason; uint8_t isdn_cause = 0; uint16_t sip_cause = 0; osmo_cc_msg_t *msg; PDEBUG(DSIP, DEBUG_INFO, "Received BYE (callref %d)\n", call->cc_callref); for (reason = sip->sip_reason; reason; reason = reason->re_next) { if (!reason->re_protocol) continue; if (!isdn_cause && !strcasecmp(sip->sip_reason->re_protocol, "Q.850") && sip->sip_reason->re_cause) { isdn_cause = atoi(sip->sip_reason->re_cause); PDEBUG(DSIP, DEBUG_INFO, " -> ISDN cause: %d\n", isdn_cause); } if (!sip_cause && !strcasecmp(sip->sip_reason->re_protocol, "SIP") && sip->sip_reason->re_cause) { sip_cause = atoi(sip->sip_reason->re_cause); PDEBUG(DSIP, DEBUG_INFO, " -> SIP cause: %d\n", sip_cause); } } PDEBUG(DSIP, DEBUG_INFO, "Sending BYE response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref); nua_respond(nh, SIP_200_OK, TAG_END()); /* create osmo-cc message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); /* cause */ osmo_cc_add_ie_cause(msg, OSMO_CC_LOCATION_BEYOND_INTERWORKING, isdn_cause, sip_cause, 0); /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh); nua_handle_destroy(nh); call->nua_handle = NULL; call_destroy(call); } static void call_r_bye(call_t *call, int status, char const *phrase, nua_handle_t *nh) { PDEBUG(DSIP, DEBUG_INFO, "Received BYE response: %d %s (callref %d)\n", status, phrase, call->cc_callref); PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh); nua_handle_destroy(nh); call->nua_handle = NULL; call_destroy(call); } static void call_i_cancel(call_t *call, nua_handle_t *nh) { osmo_cc_msg_t *msg; PDEBUG(DSIP, DEBUG_INFO, "Received CANCEL (callref %d)\n", call->cc_callref); PDEBUG(DSIP, DEBUG_INFO, "Sending CANCEL response: %d %s (callref %d)\n", SIP_200_OK, call->cc_callref); nua_respond(nh, SIP_200_OK, TAG_END()); /* create osmo-cc message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); /* send message to osmo-cc */ osmo_cc_ll_msg(&call->sip_ep->cc_ep, call->cc_callref, msg); PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh); nua_handle_destroy(nh); call->nua_handle = NULL; call_destroy(call); } static void call_r_cancel(call_t *call, int status, char const *phrase, nua_handle_t *nh) { PDEBUG(DSIP, DEBUG_INFO, "Received CANCEL response: %d %s (callref %d)\n", status, phrase, call->cc_callref); PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh); nua_handle_destroy(nh); call->nua_handle = NULL; call_destroy(call); } static void call_i_state(call_t *call, int status, char const *phrase) { PDEBUG(DSIP, DEBUG_DEBUG, "State change received: %d %s (callref %d)\n", status, phrase, call->cc_callref); } /* messages from SIP stack */ static void sip_message(nua_event_t event, int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t __attribute__((unused)) *hmagic, sip_t const *sip, tagi_t __attribute__((unused)) tags[]) { sip_endpoint_t *sip_ep = (sip_endpoint_t *)magic; call_t *call; PDEBUG(DSIP, DEBUG_DEBUG, "Event %d from SIP stack received (handle=%p)\n", event, nh); if (!nh) return; call = sip_ep->call_list; while (call) { if (call->nua_handle == nh) break; call = call->next; } /* new handle */ switch (event) { case nua_i_register: if (!call && !sip_ep->register_handle) { PDEBUG(DSIP, DEBUG_DEBUG, "new nua_handle %p (register)\n", nh); sip_ep->register_handle = nh; } if (!call) { ep_i_register(sip_ep, status, nua, nh, sip); return; } break; case nua_r_register: if (!call) { ep_r_register(sip_ep, status, phrase, nh, sip); return; } break; case nua_i_options: if (!call) { ep_i_options(nua, nh); if (sip_ep->register_handle != nh) { PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh); nua_handle_destroy(nh); } return; } break; case nua_r_options: if (!call) { ep_r_options(sip_ep, status, phrase, nh); return; } break; case nua_i_invite: if (!call) { PDEBUG(DSIP, DEBUG_DEBUG, "New call instance\n"); /* create call instance */ call = call_create(sip_ep); if (!call) { PDEBUG(DSIP, DEBUG_ERROR, "Cannot create call instance.\n"); abort(); } } break; case nua_i_outbound: PDEBUG(DSIP, DEBUG_DEBUG, "Outbound status\n"); break; default: ; } if (!call) { /* terminate call, if it does not exist */ if (nh != sip_ep->register_handle) { PDEBUG(DSIP, DEBUG_ERROR, "no SIP Port found for handle %p\n", nh); PDEBUG(DSIP, DEBUG_INFO, "Sending INVITE response: %d %s\n", SIP_500_INTERNAL_SERVER_ERROR); nua_respond(nh, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", nh); nua_handle_destroy(nh); } return; } switch (event) { case nua_r_set_params: PDEBUG(DSIP, DEBUG_DEBUG, "setparam response\n"); break; case nua_i_error: PDEBUG(DSIP, DEBUG_DEBUG, "error received\n"); break; case nua_i_invite: call_i_invite(call, nh, sip); break; case nua_r_invite: call_r_invite(call, status, phrase, nh, sip); break; case nua_i_ack: PDEBUG(DSIP, DEBUG_DEBUG, "ack received\n"); break; case nua_i_active: PDEBUG(DSIP, DEBUG_DEBUG, "active received\n"); break; case nua_i_options: call_i_options(call, nh); break; case nua_r_options: call_r_options(call, status, phrase); break; case nua_i_info: call_i_info(call, nua, nh, sip); break; case nua_i_bye: call_i_bye(call, nh, sip); break; case nua_r_bye: call_r_bye(call, status, phrase, nh); break; case nua_i_cancel: call_i_cancel(call, nh); break; case nua_r_cancel: call_r_cancel(call, status, phrase, nh); break; case nua_i_state: call_i_state(call, status, phrase); break; case nua_i_terminated: PDEBUG(DSIP, DEBUG_DEBUG, "terminated received\n"); break; default: PDEBUG(DSIP, DEBUG_DEBUG, "Event %d not handled\n", event); } } static void stun_bind_cb(stun_discovery_magic_t *magic, stun_handle_t __attribute__((unused)) *sh, stun_discovery_t *sd, stun_action_t __attribute__((unused)) action, stun_state_t event) { sip_endpoint_t *sip_ep = (sip_endpoint_t *)magic; su_sockaddr_t sa; socklen_t addrlen; PDEBUG(DSIP, DEBUG_DEBUG, "Event %d from STUN stack received\n", event); switch (event) { case stun_discovery_done: addrlen = sizeof(sa); memset(&sa, 0, addrlen); if (stun_discovery_get_address(sd, &sa, &addrlen) < 0) goto failed; su_inet_ntop(sa.su_family, SU_ADDR(&sa), sip_ep->public_ip, sizeof(sip_ep->public_ip)); sip_ep->stun_state = STUN_STATE_RESOLVED; /* start timer for next stun request with sip_ep->stun_interval */ timer_start(&sip_ep->stun_retry_timer, sip_ep->stun_interval); PDEBUG(DSIP, DEBUG_INFO, "STUN resolved!\n"); PDEBUG(DSIP, DEBUG_DEBUG, " -> Public IP = %s\n", sip_ep->public_ip); break; default: failed: PDEBUG(DSIP, DEBUG_INFO, "STUN Resolving failed!\n"); sip_ep->stun_state = STUN_STATE_FAILED; /* start timer for next stun request (after failing) with STUN_RETRY_TIMER */ timer_start(&sip_ep->stun_retry_timer, STUN_RETRY_TIMER); } } static void invite_option_timeout(struct timer *timer) { call_t *call = timer->priv; PDEBUG(DSIP, DEBUG_DEBUG, "invite options timer fired\n"); PDEBUG(DSIP, DEBUG_INFO, "Sending OPTIONS (callref %d)\n", call->cc_callref); nua_options(call->nua_handle, TAG_END()); } static void stun_retry_timeout(struct timer *timer) { sip_endpoint_t *sip_ep = timer->priv; PDEBUG(DSIP, DEBUG_DEBUG, "timeout, restart stun lookup\n"); sip_ep->stun_state = STUN_STATE_UNRESOLVED; } static void register_retry_timeout(struct timer *timer) { sip_endpoint_t *sip_ep = timer->priv; PDEBUG(DSIP, DEBUG_DEBUG, "timeout, restart register\n"); /* if we have a handle, destroy it and becom unregistered, so registration is * triggered next */ if (sip_ep->register_handle) { /* stop option timer */ timer_stop(&sip_ep->register_option_timer); PDEBUG(DSIP, DEBUG_DEBUG, "destroying nua_handle %p\n", sip_ep->register_handle); nua_handle_destroy(sip_ep->register_handle); sip_ep->register_handle = NULL; } sip_ep->register_state = REGISTER_STATE_UNREGISTERED; } static void register_option_timeout(struct timer *timer) { sip_endpoint_t *sip_ep = timer->priv; PDEBUG(DSIP, DEBUG_DEBUG, "register options timer fired\n"); PDEBUG(DSIP, DEBUG_INFO, "Sending OPTIONS (registration)\n"); nua_options(sip_ep->register_handle, TAG_END()); } sip_endpoint_t *sip_endpoint_create(int send_no_ringing_after_progress, int receive_no_ringing_after_progress, const char *name, const char *local_user, const char *local_peer, const char *remote_user, const char *remote_peer, const char *asserted_id, int local_register, int remote_register, const char *register_user, const char *register_peer, int authenticate_local, int authenticate_remote, const char *auth_user, const char *auth_password, const char *auth_realm, const char *public_ip, const char *stun_server, int register_interval, int options_interval, int stun_interval, int expires) { sip_endpoint_t *sip_ep; char local[256]; const char *p; sip_ep = calloc(1, sizeof(*sip_ep)); if (!sip_ep) { PDEBUG(DSIP, DEBUG_ERROR, "No mem!\n"); return NULL; } sip_ep->send_no_ringing_after_progress = send_no_ringing_after_progress; sip_ep->receive_no_ringing_after_progress = receive_no_ringing_after_progress; sip_ep->name = name; sip_ep->local_user = local_user; sip_ep->local_peer = local_peer; sip_ep->remote_user = remote_user; sip_ep->remote_peer = remote_peer; sip_ep->asserted_id = asserted_id; sip_ep->local_register = local_register; sip_ep->remote_register = remote_register; sip_ep->register_user = register_user; sip_ep->register_peer = register_peer; sip_ep->authenticate_local = authenticate_local; sip_ep->authenticate_remote = authenticate_remote; sip_ep->auth_user = auth_user; sip_ep->auth_password = auth_password; sip_ep->auth_realm = auth_realm; if (public_ip) strncpy(sip_ep->public_ip, public_ip, sizeof(sip_ep->public_ip) - 1); sip_ep->stun_server = stun_server; sip_ep->register_interval = register_interval; sip_ep->options_interval = options_interval; sip_ep->stun_interval = stun_interval; /* create timers */ timer_init(&sip_ep->stun_retry_timer, stun_retry_timeout, sip_ep); timer_init(&sip_ep->register_retry_timer, register_retry_timeout, sip_ep); timer_init(&sip_ep->register_option_timer, register_option_timeout, sip_ep); /* init root object */ sip_ep->su_root = su_root_create(sip_ep); if (!sip_ep->su_root) { PDEBUG(DSIP, DEBUG_ERROR, "Failed to create SIP root\n"); goto error; } sprintf(local, "sip:%s",sip_ep->local_peer); p = osmo_cc_port_of_address(sip_ep->local_peer); if (!p) strcat(local, ":5060"); sip_ep->nua = nua_create(sip_ep->su_root, sip_message, sip_ep, NUTAG_URL(local), TAG_END()); if (!sip_ep->nua) { PDEBUG(DSIP, DEBUG_ERROR, "Failed to create SIP stack object\n"); goto error; } nua_set_params(sip_ep->nua, SIPTAG_ALLOW_STR("REGISTER,INVITE,ACK,BYE,CANCEL,OPTIONS,NOTIFY,INFO"), NUTAG_APPL_METHOD("REGISTER"), NUTAG_APPL_METHOD("INVITE"), NUTAG_APPL_METHOD("ACK"), /* We want to reply to BYE, so no tag!!! */ NUTAG_APPL_METHOD("CANCEL"), NUTAG_APPL_METHOD("OPTIONS"), NUTAG_APPL_METHOD("NOTIFY"), NUTAG_APPL_METHOD("INFO"), NUTAG_AUTOACK(0), #ifdef NUTAG_AUTO100 NUTAG_AUTO100(0), #endif NUTAG_AUTOALERT(0), NUTAG_AUTOANSWER(0), TAG_IF(expires, NUTAG_SESSION_TIMER(expires)), // wozu das? NUTAG_ALLOW("INFO"), TAG_NULL()); if (sip_ep->remote_register) sip_ep->register_state = REGISTER_STATE_UNREGISTERED; if (sip_ep->stun_server) { sip_ep->stun_handle = stun_handle_init(sip_ep->su_root, STUNTAG_SERVER(sip_ep->stun_server), TAG_NULL()); if (!sip_ep->stun_handle) { PDEBUG(DSIP, DEBUG_ERROR, "Failed to create STUN handle\n"); goto error; } sip_ep->stun_socket = su_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sip_ep->stun_socket < 0) { PDEBUG(DSIP, DEBUG_ERROR, "Failed to create STUN socket\n"); goto error; } sip_ep->stun_state = STUN_STATE_UNRESOLVED; } PDEBUG(DSIP, DEBUG_DEBUG, "SIP endpoint created\n"); return sip_ep; error: sip_endpoint_destroy(sip_ep); return NULL; } void sip_endpoint_destroy(sip_endpoint_t *sip_ep) { if (!sip_ep) return; timer_exit(&sip_ep->stun_retry_timer); timer_exit(&sip_ep->register_retry_timer); timer_exit(&sip_ep->register_option_timer); if (sip_ep->stun_socket) su_close(sip_ep->stun_socket); if (sip_ep->stun_handle) stun_handle_destroy(sip_ep->stun_handle); if (sip_ep->register_handle) nua_handle_destroy(sip_ep->register_handle); if (sip_ep->su_root) su_root_destroy(sip_ep->su_root); if (sip_ep->nua) nua_destroy(sip_ep->nua); free(sip_ep); PDEBUG(DSIP, DEBUG_DEBUG, "SIP endpoint destroyed\n"); } extern su_log_t su_log_default[]; extern su_log_t nua_log[]; static su_home_t sip_home[1]; /* global init */ int sip_init(int debug_level) { /* init SOFIA lib */ su_init(); su_home_init(sip_home); if (debug_level) { su_log_set_level(su_log_default, debug_level); su_log_set_level(nua_log, debug_level); //su_log_set_level(soa_log, debug_level); } PDEBUG(DSIP, DEBUG_DEBUG, "SIP globals initialized\n"); return 0; } /* global exit */ void sip_exit(void) { su_home_deinit(sip_home); su_deinit(); PDEBUG(DSIP, DEBUG_DEBUG, "SIP globals de-initialized\n"); } /* trigger stun resolver */ static void sip_handle_stun(sip_endpoint_t *sip_ep) { int rc; switch (sip_ep->stun_state) { case STUN_STATE_UNRESOLVED: PDEBUG(DSIP, DEBUG_INFO, "STUN resolving for public IP\n"); rc = stun_bind(sip_ep->stun_handle, stun_bind_cb, (stun_discovery_magic_t *)sip_ep, STUNTAG_SOCKET(sip_ep->stun_socket), STUNTAG_REGISTER_EVENTS(1), TAG_NULL()); if (rc < 0) { PDEBUG(DSIP, DEBUG_ERROR, "Failed to call stun_bind()\n"); sip_ep->stun_state = STUN_STATE_FAILED; break; } PDEBUG(DSIP, DEBUG_DEBUG, " -> Server = %s\n", sip_ep->stun_server); sip_ep->stun_state = STUN_STATE_RESOLVING; break; default: ; } } /* trigger registration to remote */ static void sip_handle_register(sip_endpoint_t *sip_ep) { char from[256] = ""; char to[256] = ""; char contact[256+10] = ""; char expires[256] = ""; switch (sip_ep->register_state) { case REGISTER_STATE_UNREGISTERED: /* wait for resoved stun */ if (sip_ep->stun_handle && sip_ep->stun_state != STUN_STATE_RESOLVED) return; PDEBUG(DSIP, DEBUG_INFO, "Sending REGISTER\n"); sip_ep->register_handle = nua_handle(sip_ep->nua, NULL, TAG_END()); if (!sip_ep->register_handle) { PDEBUG(DSIP, DEBUG_ERROR, "Failed to create handle\n"); sip_ep->register_state = REGISTER_STATE_FAILED; break; } PDEBUG(DSIP, DEBUG_DEBUG, "new nua_handle %p (register)\n", sip_ep->register_handle); sprintf(from, "sip:%s@%s", sip_ep->register_user, sip_ep->register_peer); PDEBUG(DSIP, DEBUG_DEBUG, " -> From = %s\n", from); sprintf(to, "sip:%s@%s", sip_ep->register_user, sip_ep->register_peer); PDEBUG(DSIP, DEBUG_DEBUG, " -> To = %s\n", to); if (sip_ep->public_ip[0]) { const char *p; sprintf(contact, "sip:%s@%s", sip_ep->register_user, sip_ep->public_ip); /* append port of local peer */ p = osmo_cc_port_of_address(sip_ep->local_peer); if (p) strcat(contact, p); PDEBUG(DSIP, DEBUG_DEBUG, " -> Contact = %s\n", contact); } if (sip_ep->register_interval) { sprintf(expires, "%d", sip_ep->register_interval + 60); PDEBUG(DSIP, DEBUG_DEBUG, " -> Expires = %s\n", expires); } nua_register(sip_ep->register_handle, TAG_IF(from[0], SIPTAG_FROM_STR(from)), TAG_IF(to[0], SIPTAG_TO_STR(to)), TAG_IF(contact[0], SIPTAG_CONTACT_STR(contact)), TAG_IF(expires[0], SIPTAG_EXPIRES_STR(expires)), NUTAG_OUTBOUND("no-validate"), // do not validate outbound // NUTAG_OUTBOUND("no-options-keepalive"), NUTAG_KEEPALIVE(0), // prevent sending options, but this causes outbound to validate TAG_END()); sip_ep->register_state = REGISTER_STATE_REGISTERING; break; default: ; } } void sip_handle(sip_endpoint_t *sip_ep) { /* sofia */ su_root_step(sip_ep->su_root, 0); /* stun */ sip_handle_stun(sip_ep); /* register */ sip_handle_register(sip_ep); }