osmo-cc-sip-endpoint/src/sip/sip.c

1906 lines
55 KiB
C
Raw Normal View History

2020-09-12 11:10:46 +00:00
/* SIP handling
*
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include "../libdebug/debug.h"
#include <sofia-sip/sip_status.h>
#include <sofia-sip/su_log.h>
#include <sofia-sip/sdp.h>
#include <sofia-sip/sip_header.h>
#include <sofia-sip/stun_tag.h>
#include <sofia-sip/su_md5.h>
#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);
}