view sip-in/invite.c @ 86:f332ccc240f1

sip-in: preparations toward TMGW connect-through
author Mychaela Falconia <falcon@freecalypso.org>
date Tue, 20 Sep 2022 23:14:50 -0800
parents fe39404092d9
children ce3b1db7d1d7
line wrap: on
line source

/*
 * Here we implement our handling of SIP INVITE method.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include "../include/gsm48_const.h"
#include "../libsip/parse.h"
#include "../libsip/uas_basic.h"
#include "../libsip/grok_from.h"
#include "../libsip/req_supp.h"
#include "../libsip/sdp.h"
#include "../libsip/out_msg.h"
#include "call.h"

extern struct in_addr sip_bind_ip;
extern unsigned sip_bind_port;
extern int cfg_use_100rel;
extern struct call *call_list;

extern struct call *find_call_by_sip_id();
extern char *get_single_header();

fill_invite_resp_from_call(msg, call)
	struct sip_msg_out *msg;
	struct call *call;
{
	char cseq_str[32];
	int rc;

	rc = out_msg_add_header(msg, "From", call->invite_from);
	if (rc < 0)
		return rc;
	rc = out_msg_add_header(msg, "To", call->invite_to);
	if (rc < 0)
		return rc;
	rc = out_msg_add_header(msg, "Call-ID", call->sip_call_id);
	if (rc < 0)
		return rc;
	sprintf(cseq_str, "%u INVITE", call->invite_cseq);
	rc = out_msg_add_header(msg, "CSeq", cseq_str);
	if (rc < 0)
		return rc;
	return out_msg_add_header(msg, "Via", call->invite_via);
}

fill_invite_200_resp(msg, call)
	struct sip_msg_out *msg;
	struct call *call;
{
	char contact_str[80];
	struct sdp_gen sdp;
	int rc;

	start_response_out_msg(msg, "200 CONNECT");
	rc = fill_invite_resp_from_call(msg, call);
	if (rc < 0)
		return rc;
	sprintf(contact_str, "<sip:%s:%u;transport=udp>",
		inet_ntoa(sip_bind_ip), sip_bind_port);
	rc = out_msg_add_header(msg, "Contact", contact_str);
	if (rc < 0)
		return rc;
	rc = out_msg_add_header(msg, "Content-Type", "application/sdp");
	if (rc < 0)
		return rc;
	bzero(&sdp, sizeof sdp);
	sdp.conn_ip = call->pstn_rtp_local.sin_addr;
	sdp.conn_port = ntohs(call->pstn_rtp_local.sin_port);
	sdp.codec_mask = call->use_pcma ? SDP_CODEC_MASK_PCMA
					: SDP_CODEC_MASK_PCMU;
	sdp.session_id = (sdp.conn_port << 16) | call->sdp_addend;
	sdp.owner_ip = sip_bind_ip;
	return out_msg_finish_sdp(msg, &sdp);
}

static void
invite_new_call(req, ess, sin)
	struct sip_pkt_rx *req;
	struct uas_parse_hdrs *ess;
	struct sockaddr_in *sin;
{
	static unsigned cycle_tag_num, cycle_sdp_addend;
	char uri_user[13], *called_nanp;
	struct sip_msg_out resp;
	struct grok_from gfrom;
	struct supported_ext supp_ext;
	char *hval, *unsup_ext;
	int ext_100rel_req, ext_100rel_sup, use_100rel, use_pcma;
	struct sdp_parse sdp_parse;
	struct sdp_gen sdp_gen;
	struct call *call;
	char *dp;
	unsigned req_uri_len, to_hdr_len, copylen;
	int rc;

	/* extract called number from Request-URI */
	rc = user_from_sip_uri(req->req_uri, uri_user, 12);
	if (rc < 0) {
not_nanp:	start_response_out_msg(&resp,
			"416 Request-URI is not a NANP number");
error_resp:	rc = add_resp_basic_headers(&resp, ess, req->req_method);
		if (rc < 0) {
error_resp_toolong:	syslog(LOG_ERR,
				"INVITE early error response length exceeded");
			return;
		}
		out_msg_finish(&resp);
		sip_tx_packet(&resp, sin);
		return;
	}
	if (uri_user[0] == '+') {
		if (grok_number_string(uri_user+1, 0) != 11 ||
		    uri_user[1] != '1')
			goto not_nanp;
		called_nanp = uri_user + 2;
	} else switch (grok_number_string(uri_user)) {
	case 10:
		called_nanp = uri_user;
		break;
	case 11:
		if (uri_user[0] != '1')
			goto not_nanp;
		called_nanp = uri_user + 1;
		break;
	default:
		goto not_nanp;
	}
	if (!is_nanp_valid_prefix(called_nanp))
		goto not_nanp;
	/* it is valid NANP - but is it one of ours? */
	refresh_number_db();
	if (!is_nanp_locally_owned(called_nanp)) {
		start_response_out_msg(&resp,
			"404 Called number does not belong here");
		goto error_resp;
	}
	/* parse and validate From header */
	rc = grok_from_header(ess->from, &gfrom);
	if (rc < 0) {
		start_response_out_msg(&resp, "400 Malformed From header");
		goto error_resp;
	}
	/* validate To header for the purpose of tag addition */
	req_uri_len = strlen(req->req_uri);
	to_hdr_len = strlen(ess->to);
	if (to_hdr_len == req_uri_len) {
		if (strcasecmp(ess->to, req->req_uri)) {
bad_to_header:		start_response_out_msg(&resp, "400 Bad To header");
			goto error_resp;
		}
	} else if (to_hdr_len == req_uri_len + 2) {
		if (ess->to[0] != '<')
			goto bad_to_header;
		if (strncasecmp(ess->to+1, req->req_uri, req_uri_len))
			goto bad_to_header;
		if (ess->to[req_uri_len+1] != '>')
			goto bad_to_header;
	} else
		goto bad_to_header;
	/* check 100rel and catch any unsupported requirements */
	supp_ext.name = "100rel";
	supp_ext.req_flag = &ext_100rel_req;
	supp_ext.sup_flag = &ext_100rel_sup;
	ext_100rel_req = ext_100rel_sup = 0;
	rc = parse_require_supported(req, &supp_ext, 1, &unsup_ext);
	if (rc < 0) {
		start_response_out_msg(&resp, "420 Extension not supported");
		rc = out_msg_add_header(&resp, "Unsupported", unsup_ext);
		if (rc < 0)
			goto error_resp_toolong;
		goto error_resp;
	}
	if (ext_100rel_req)
		use_100rel = 1;
	else if (ext_100rel_sup)
		use_100rel = cfg_use_100rel;
	else
		use_100rel = 0;
	/* did the caller send an SDP message body? */
	if (!req->msg_body_len) {
		start_response_out_msg(&resp, "415 Missing SDP body");
error_415:	rc = out_msg_add_header(&resp, "Accept", "application/sdp");
		if (rc < 0)
			goto error_resp_toolong;
		goto error_resp;
	}
	hval = get_single_header(req, "Content-Type", "c", (int *) 0);
	if (!hval) {
		start_response_out_msg(&resp,
			"415 Missing Content-Type header");
		goto error_415;
	}
	if (strcasecmp(hval, "application/sdp")) {
		start_response_out_msg(&resp, "415 Unsupported Content-Type");
		goto error_415;
	}
	rc = parse_incoming_sdp(req->msg_body, req->msg_body_len, &sdp_parse);
	if (rc < 0) {
		start_response_out_msg(&resp, "488 Malformed SDP body");
		goto error_resp;
	}
	switch (sdp_parse.codec_mask) {
	case SDP_CODEC_MASK_PCMU:
	case SDP_CODEC_MASK_BOTH:
		use_pcma = 0;
		break;
	case SDP_CODEC_MASK_PCMA:
	case SDP_CODEC_MASK_BOTH | SDP_CODEC_MASK_PCMA_PREF:
		use_pcma = 1;
		break;
	default:
		start_response_out_msg(&resp,
			"488 Unsupported codec selection");
		rc = add_resp_basic_headers(&resp, ess, req->req_method);
		if (rc < 0)
			goto error_resp_toolong;
		rc = out_msg_add_header(&resp, "Content-Type",
					"application/sdp");
		if (rc < 0)
			goto error_resp_toolong;
		bzero(&sdp_gen, sizeof sdp_gen);
		sdp_gen.owner_ip = sip_bind_ip;
		sdp_gen.conn_ip = sip_bind_ip;
		sdp_gen.codec_mask = SDP_CODEC_MASK_BOTH;
		rc = out_msg_finish_sdp(&resp, &sdp_gen);
		if (rc < 0)
			goto error_resp_toolong;
		sip_tx_packet(&resp, sin);
		return;
	}
	/* SIP INVITE validation done - check if GSM service is up */
	rc = connect_gsm_mtcall();
	if (rc < 0) {
		start_response_out_msg(&resp, "480 GSM service is offline");
		goto error_resp;
	}
	/* stateful processing begins */
	call = malloc(sizeof(struct call) + strlen(ess->call_id) +
		      strlen(ess->from) + req_uri_len + strlen(ess->via) + 19);
	if (!call) {
		syslog(LOG_CRIT, "failed malloc for incoming call!");
		start_response_out_msg(&resp,
			"503 Gateway resource allocation failure");
		goto error_resp;
	}
	cycle_tag_num++;
	if (cycle_tag_num >= 1000000)
		cycle_tag_num = 0;
	cycle_sdp_addend++;
	if (cycle_sdp_addend >= 0x10000)
		cycle_sdp_addend = 0;
	bzero(call, sizeof(struct call));
	dp = (char *)(call + 1);
	copylen = strlen(ess->call_id) + 1;
	call->sip_call_id = dp;
	bcopy(ess->call_id, dp, copylen);
	dp += copylen;
	copylen = strlen(ess->from) + 1;
	call->invite_from = dp;
	bcopy(ess->from, dp, copylen);
	dp += copylen;
	call->invite_to = dp;
	*dp++ = '<';
	bcopy(req->req_uri, dp, req_uri_len);
	dp += req_uri_len;
	*dp++ = '>';
	sprintf(dp, ";tag=in%06u", cycle_tag_num);
	dp += 14;
	copylen = strlen(ess->via) + 1;
	call->invite_via = dp;
	bcopy(ess->via, dp, copylen);
	call->invite_cseq = ess->cseq_num;
	bcopy(sin, &call->udp_sin, sizeof(struct sockaddr_in));
	bcopy(called_nanp, call->called_nanp, 11);
	call->from_uri = call->invite_from + (gfrom.uri - ess->from);
	call->from_uri_len = gfrom.uri_len;
	call->from_user = call->invite_from + (gfrom.user - ess->from);
	call->from_user_len = gfrom.user_len;
	call->use_100rel = use_100rel;
	call->pstn_rtp_remote.sin_family = AF_INET;
	call->pstn_rtp_remote.sin_addr = sdp_parse.ip_addr;
	call->pstn_rtp_remote.sin_port = htons(sdp_parse.audio_port);
	call->use_pcma = use_pcma;
	call->sdp_addend = cycle_sdp_addend;
	/* generate 100 response */
	start_response_out_msg(&resp, "100 Proceeding");
	rc = fill_invite_resp_from_call(&resp, call);
	if (rc < 0) {
		syslog(LOG_ERR, "INVITE 100 response length exceeded");
		free(call);
		return;
	}
	out_msg_finish(&resp);
	sip_tx_packet(&resp, sin);
	/* add to call list */
	call->next = call_list;
	call_list = call;
	/* send CRCX to TMGW */
	tmgw_send_crcx(call);
	call->overall_state = OVERALL_STATE_CRCX;
	call->sip_state = SIP_STATE_INVITE_PROC;
}

static void
invite_existing_call(req, ess, sin, call)
	struct sip_pkt_rx *req;
	struct uas_parse_hdrs *ess;
	struct sockaddr_in *sin;
	struct call *call;
{
	struct sip_msg_out resp;
	int rc;

	if (ess->cseq_num != call->invite_cseq) {
		start_response_out_msg(&resp, "501 Re-INVITE not supported");
		rc = add_resp_basic_headers(&resp, ess, req->req_method);
		if (rc < 0) {
			syslog(LOG_ERR,
		"sending 501 Re-INVITE error: response length exceeded");
			return;
		}
		out_msg_finish(&resp);
		sip_tx_packet(&resp, sin);
		return;
	}
	/* it's a retransmission, not a re-INVITE */
	switch (call->sip_state) {
	case SIP_STATE_INVITE_PROC:
		start_response_out_msg(&resp, "100 Proceeding");
		fill_invite_resp_from_call(&resp, call);
		out_msg_finish(&resp);
		sip_tx_packet(&resp, sin);
		return;
	case SIP_STATE_RINGING:
	case SIP_STATE_RINGING_PRACK:
		start_response_out_msg(&resp, "180 Ringing");
		fill_invite_resp_from_call(&resp, call);
		out_msg_finish(&resp);
		sip_tx_packet(&resp, sin);
		return;
	case SIP_STATE_INVITE_200:
	case SIP_STATE_CONNECTED:
		fill_invite_200_resp(&resp, call);
		sip_tx_packet(&resp, sin);
		return;
	case SIP_STATE_INVITE_ERR:
		start_response_out_msg(&resp, call->invite_fail);
		fill_invite_resp_from_call(&resp, call);
		out_msg_finish(&resp);
		sip_tx_packet(&resp, sin);
		return;
	default:
		/* silently discard */
		return;
	}
}

void
handle_sip_invite(req, ess, sin)
	struct sip_pkt_rx *req;
	struct uas_parse_hdrs *ess;
	struct sockaddr_in *sin;
{
	struct call *call;

	call = find_call_by_sip_id(ess->call_id);
	if (call)
		invite_existing_call(req, ess, sin, call);
	else
		invite_new_call(req, ess, sin);
}

void
signal_invite_ringing(call)
	struct call *call;
{
	struct sip_msg_out resp;

	start_response_out_msg(&resp, "180 Ringing");
	fill_invite_resp_from_call(&resp, call);
	out_msg_finish(&resp);
	sip_tx_packet(&resp, &call->udp_sin);
	call->sip_state = SIP_STATE_RINGING;
}

void
signal_invite_200(call)
	struct call *call;
{
	struct sip_msg_out resp;
	int rc;

	rc = fill_invite_200_resp(&resp, call);
	if (rc < 0) {
		syslog(LOG_ERR, "INVITE 200 response length exceeded");
		call->sip_state = SIP_STATE_MSG_SIZE_ERR;
		call->overall_state = OVERALL_STATE_TEARDOWN;
		disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_INTERWORKING);
		disconnect_tmgw(call);
		/* TODO: transition from TEARDOWN to DEAD_SIP */
		return;
	}
	sip_tx_packet(&resp, &call->udp_sin);
	call->sip_state = SIP_STATE_INVITE_200;
	call->sip_tx_count = 1;
}

void
signal_invite_error(call)
	struct call *call;
{
	struct sip_msg_out resp;
	int rc;

	start_response_out_msg(&resp, call->invite_fail);
	rc = fill_invite_resp_from_call(&resp, call);
	if (rc < 0) {
		syslog(LOG_ERR, "INVITE late error response length exceeded");
		call->sip_state = SIP_STATE_MSG_SIZE_ERR;
		/* TODO: transition from TEARDOWN to DEAD_SIP */
		return;
	}
	out_msg_finish(&resp);
	sip_tx_packet(&resp, &call->udp_sin);
	call->sip_state = SIP_STATE_INVITE_ERR;
	call->sip_tx_count = 1;
}