changeset 154:e54b0a9e322f

beginning of themwi-sip-out
author Mychaela Falconia <falcon@freecalypso.org>
date Tue, 11 Oct 2022 23:04:01 -0800
parents 99fd4ae573ae
children 2730ccb44549
files sip-out/Makefile sip-out/call.h sip-out/call_clear.c sip-out/call_list.c sip-out/call_setup.c sip-out/disconnect.c sip-out/main.c sip-out/mgw_ops.c sip-out/mgw_resp.c sip-out/mgw_sock.c sip-out/mncc_sig_in.c sip-out/mncc_sig_out.c sip-out/mncc_sock.c sip-out/readconf.c sip-out/retrans.c sip-out/shutdown.c sip-out/sip_log.c sip-out/sip_uas.c sip-out/sip_udp.c sip-out/uac_out.c
diffstat 20 files changed, 2384 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/Makefile	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,19 @@
+CC=	gcc
+CFLAGS=	-O2
+PROG=	themwi-sip-out
+OBJS=	call_clear.o call_list.o call_setup.o disconnect.o main.o mgw_ops.o \
+	mgw_resp.o mgw_sock.o mncc_sig_in.o mncc_sig_out.o mncc_sock.o \
+	readconf.o retrans.o shutdown.o sip_log.o sip_uas.o sip_udp.o uac_out.o
+LIBS=	../liboutrt/liboutrt.a ../libsip/libsip.a ../libutil/libutil.a
+INSTBIN=/usr/local/bin
+
+all:	${OBJS}
+
+${PROG}: ${OBJS} ${LIBS}
+	${CC} ${CFLAGS} -o $@ ${OBJS} ${LIBS}
+
+install:
+	install -c -o bin -g bin -m 755 ${PROG} ${INSTBIN}
+
+clean:
+	rm -f *.o ${PROG} errs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/call.h	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,90 @@
+/*
+ * In this header file we define two key structures for call state
+ * in themwi-sip-out: struct call exists for each call just like in
+ * themwi-sip-in, and struct mncc_conn exists for every connection
+ * to our outcall socket.
+ */
+
+struct mncc_conn {
+	int		fd;
+	struct mncc_conn *next;
+};
+
+#define	MAX_SIP_CALL_ID		30
+#define	MAX_SIP_FROM_HEADER	(MAX_SIP_USER_PART + 5 + 17 + 5 + 8)
+#define	MAX_SIP_TO_URI		(MAX_SIP_USER_PART + MAX_SIP_DEST_DOMAIN + 5)
+#define	MAX_SIP_TO_TAG		63
+
+struct call {
+	/* global call list */
+	struct call	*next;
+	char		sip_call_id[MAX_SIP_CALL_ID+1];
+	int		sip_call_exists;
+	/* MNCC call origin */
+	struct mncc_conn *mncc;
+	uint32_t	mncc_callref;
+	/* call routing info */
+	char		from_user[MAX_SIP_USER_PART+1];
+	char		from_header[MAX_SIP_FROM_HEADER+1];
+	char		to_uri[MAX_SIP_TO_URI+1];
+	char		to_tag[MAX_SIP_TO_TAG+1];
+	struct sockaddr_in udp_sin;
+	/* PSTN side RTP info */
+	struct sockaddr_in pstn_rtp_local;
+	struct sockaddr_in pstn_rtp_remote;
+	int		use_pcma;
+	/* GSM side RTP info */
+	struct sockaddr_storage gsm_rtp_osmo;
+	struct sockaddr_storage gsm_rtp_tmgw;
+	uint32_t	gsm_payload_type;
+	uint32_t	gsm_payload_msg_type;
+	/* state machines */
+	uint32_t	overall_state;
+	uint32_t	mncc_state;
+	uint32_t	sip_state;
+	uint32_t	mgw_state;
+	uint32_t	mgw_ep_id;
+	uint32_t	mgw_xact;
+	uint32_t	mgw_xact_id;
+	unsigned	sip_tx_count;
+	time_t		sip_clear_time;
+	int		dtmf_digit;
+	int		dtmf_pending_stop;
+};
+
+#define	OVERALL_STATE_GSM_RTP		1
+#define	OVERALL_STATE_CRCX		2
+#define	OVERALL_STATE_SIP_OUT		3
+#define	OVERALL_STATE_RINGING		4
+#define	OVERALL_STATE_CONNECTED		5
+#define	OVERALL_STATE_TEARDOWN		6
+#define	OVERALL_STATE_SIP_FINISH	7
+#define	OVERALL_STATE_GC		8
+
+#define	MNCC_STATE_MO_PROC		1
+#define	MNCC_STATE_GOT_RTP		2
+#define	MNCC_STATE_ALERTING		3
+#define	MNCC_STATE_CONNECTED		4
+#define	MNCC_STATE_DISCONNECT		5
+#define	MNCC_STATE_RELEASE		6
+
+#define	SIP_STATE_INV_SENT		1
+#define	SIP_STATE_100_RCVD		2
+#define	SIP_STATE_CONNECTED		3
+#define	SIP_STATE_CANCEL_SENT		4
+#define	SIP_STATE_BYE_SENT		5
+#define	SIP_STATE_ACCEPT_100		6
+#define	SIP_STATE_ACCEPT_200		7
+#define	SIP_STATE_ENDED			8
+
+#define	MGW_STATE_NO_EXIST		0
+#define	MGW_STATE_ALLOCATED		1
+#define	MGW_STATE_IBT_CONN		2
+#define	MGW_STATE_COMPLETE		3
+#define	MGW_STATE_HELD			4
+#define	MGW_STATE_MDCX_IBT		5
+#define	MGW_STATE_MDCX_CONN		6
+#define	MGW_STATE_MDCX_HOLD		7
+#define	MGW_STATE_MDCX_RETR		8
+#define	MGW_STATE_DTMF_OP		9
+#define	MGW_STATE_DELETING		10
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/call_clear.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,66 @@
+/*
+ * In this module we implement final clearing of calls, i.e., freeing
+ * of struct call, and all preliminary steps that need to happen
+ * toward this ultimate end state.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/out_routes.h"
+#include "call.h"
+
+extern struct call *call_list;
+extern struct timeval cur_event_time;
+
+void
+sip_mark_end_time(call, linger)
+	struct call *call;
+	unsigned linger;
+{
+	call->sip_clear_time = cur_event_time.tv_sec + linger;
+}
+
+void
+check_dead_call(call)
+	struct call *call;
+{
+	if (call->overall_state != OVERALL_STATE_TEARDOWN)
+		return;
+	if (call->mncc)
+		return;
+	if (call->mgw_state != MGW_STATE_NO_EXIST)
+		return;
+	if (call->mgw_xact)
+		return;
+	if (call->sip_call_exists)
+		call->overall_state = OVERALL_STATE_SIP_FINISH;
+	else
+		call->overall_state = OVERALL_STATE_GC;
+}
+
+void
+clear_dead_calls()
+{
+	struct call *call, **pp;
+
+	for (pp = &call_list; *pp; ) {
+		call = *pp;
+		if (call->overall_state == OVERALL_STATE_GC ||
+		    call->overall_state == OVERALL_STATE_SIP_FINISH &&
+		    call->sip_state >= SIP_STATE_ACCEPT_100 &&
+		    cur_event_time.tv_sec >= call->sip_clear_time) {
+			*pp = call->next;
+			free(call);
+			continue;
+		}
+		pp = &call->next;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/call_list.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,80 @@
+/*
+ * In this module we implement call list management for themwi-sip-out.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/out_routes.h"
+#include "call.h"
+
+struct call *call_list;
+
+struct call *
+find_call_by_sip_id(sought_id)
+	char *sought_id;
+{
+	struct call *call;
+
+	for (call = call_list; call; call = call->next) {
+		if (!call->sip_call_exists)
+			continue;
+		if (!strcmp(call->sip_call_id, sought_id))
+			return call;
+	}
+	return 0;
+}
+
+struct call *
+find_call_by_mncc_callref(mncc, callref)
+	struct mncc_conn *mncc;
+	uint32_t callref;
+{
+	struct call *call;
+
+	for (call = call_list; call; call = call->next)
+		if (call->mncc == mncc && call->mncc_callref == callref)
+			return call;
+	return 0;
+}
+
+void
+scan_call_list_for_timeouts(retrans, dead_sip_flag, dead_sip_time)
+	int *retrans, *dead_sip_flag;
+	time_t *dead_sip_time;
+{
+	struct call *call;
+	int got_dead_sip = 0;
+
+	for (call = call_list; call; call = call->next) {
+		if (!call->sip_call_exists)
+			continue;
+		switch (call->sip_state) {
+		case SIP_STATE_INV_SENT:
+		case SIP_STATE_CANCEL_SENT:
+		case SIP_STATE_BYE_SENT:
+			*retrans = 1;
+			break;
+		case SIP_STATE_ACCEPT_100:
+		case SIP_STATE_ACCEPT_200:
+		case SIP_STATE_ENDED:
+			if (call->overall_state != OVERALL_STATE_SIP_FINISH)
+				continue;
+			if (got_dead_sip) {
+				if (call->sip_clear_time < *dead_sip_time)
+					*dead_sip_time = call->sip_clear_time;
+			} else {
+				got_dead_sip = 1;
+				*dead_sip_flag = 1;
+				*dead_sip_time = call->sip_clear_time;
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/call_setup.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,171 @@
+/*
+ * In this module we implement our initial handling of MNCC_SETUP_IND.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+#include "../include/gsm48_const.h"
+#include "../include/out_routes.h"
+#include "call.h"
+
+extern struct call *find_call_by_mncc_callref();
+
+extern int block_1900_numbers;
+extern struct call *call_list;
+
+static void
+reject_mo_call(mncc, callref, cause_loc, cause_val)
+	struct mncc_conn *mncc;
+	uint32_t callref;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_REJ_REQ;
+	msg.callref = callref;
+	mncc_set_cause(&msg, cause_loc, cause_val);
+	send_mncc_to_sock(mncc, &msg, sizeof(struct gsm_mncc));
+}
+
+static void
+send_call_proceeding(call)
+	struct call *call;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_CALL_PROC_REQ;
+	msg.callref = call->mncc_callref;
+	send_mncc_to_sock(call->mncc, &msg, sizeof(struct gsm_mncc));
+}
+
+void
+handle_setup_ind(mncc, msg, msglen)
+	struct mncc_conn *mncc;
+	struct gsm_mncc *msg;
+	unsigned msglen;
+{
+	struct call *call;
+	struct sip_out_dest *dest;
+	struct special_num_route *special_rt;
+	char to_sip_user[MAX_SIP_USER_PART+1];
+	int rc;
+
+	if (msglen != sizeof(struct gsm_mncc)) {
+		syslog(LOG_CRIT,
+			"FATAL: Rx MNCC_SETUP_IND has wrong length");
+		exit(1);
+	}
+	/* check for duplicates */
+	call = find_call_by_mncc_callref(mncc, msg->callref);
+	if (call) {
+		syslog(LOG_ERR, "duplicate MNCC_SETUP_IND for callref 0x%x",
+			msg->callref);
+		/* drop it like OsmoMSC's mncc_builtin does */
+		return;
+	}
+	if (!(msg->fields & MNCC_F_CALLED) ||
+	    !(msg->fields & MNCC_F_BEARER_CAP)) {
+		syslog(LOG_ERR, "rejecting MO call 0x%x: missing mandatory IE",
+			msg->callref);
+		reject_mo_call(mncc, msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_INVAL_MAND_INF);
+		return;
+	}
+	/* route based on destination address */
+	refresh_out_routes_db();
+	if (msg->called.type == GSM48_TON_INTERNATIONAL) {
+		rc = grok_number_string(msg->called.number, 0);
+		if (rc < 2 || rc > MAX_E164_NUMBER) {
+inv_nr_format:		reject_mo_call(mncc, msg->callref,
+					GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_INV_NR_FORMAT);
+			return;
+		}
+		if (msg->called.number[0] == '0')
+			goto inv_nr_format;
+		if (msg->called.number[0] == '1') {
+			if (rc != 11)
+				goto inv_nr_format;
+			if (!is_nanp_valid_prefix(msg->called.number+1))
+				goto inv_nr_format;
+			if (msg->called.number[1] == '9' &&
+			    msg->called.number[2] == '0' &&
+			    msg->called.number[3] == '0' &&
+			    block_1900_numbers) {
+call_barred:			reject_mo_call(mncc, msg->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_OP_DET_BARRING);
+				return;
+			}
+		}
+		rc = route_e164_number(msg->called.number, &dest);
+		if (!rc) {
+no_route_to_dest:	reject_mo_call(mncc, msg->callref,
+					GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_NO_ROUTE);
+			return;
+		}
+		to_sip_user[0] = '+';
+		strcpy(to_sip_user+1, msg->called.number);
+	} else {
+		rc = route_special_number(msg->called.number, &dest,
+					  &special_rt);
+		if (!rc)
+			goto no_route_to_dest;
+		strcpy(to_sip_user, special_rt->sip_user);
+	}
+	/* validate From number */
+	if (!(msg->fields & MNCC_F_CALLING))
+		goto call_barred;
+	if (msg->calling.type != GSM48_TON_INTERNATIONAL)
+		goto call_barred;
+	if (grok_number_string(msg->calling.number, 0) != 11 ||
+	    msg->calling.number[0] != '1')
+		goto call_barred;
+	/* speech-only restriction */
+	if (msg->bearer_cap.transfer != GSM48_BCAP_ITCAP_SPEECH ||
+	    msg->bearer_cap.mode != GSM48_BCAP_TMOD_CIRCUIT) {
+		syslog(LOG_ERR, "rejecting MO call 0x%x: bad bearer cap",
+			msg->callref);
+		reject_mo_call(mncc, msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+		return;
+	}
+	/* TMGW must be up and running */
+	rc = connect_tmgw_socket();
+	if (rc < 0) {
+		reject_mo_call(mncc, msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_NETWORK_OOO);
+		return;
+	}
+	/* allocate struct call and being stateful processing */
+	call = malloc(sizeof(struct call));
+	if (!call) {
+		syslog(LOG_CRIT, "failed malloc for outbound call!");
+		reject_mo_call(mncc, msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		return;
+	}
+	bzero(call, sizeof(struct call));
+	call->mncc = mncc;
+	call->mncc_callref = msg->callref;
+	call->from_user[0] = '+';
+	strcpy(call->from_user+1, msg->calling.number);
+	sprintf(call->to_uri, "sip:%s@%s", to_sip_user, dest->domain);
+	bcopy(&dest->sin, &call->udp_sin, sizeof(struct sockaddr_in));
+	call->next = call_list;
+	call_list = call;
+	send_call_proceeding(call);
+	call->overall_state = OVERALL_STATE_GSM_RTP;
+	call->mncc_state = MNCC_STATE_MO_PROC;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/disconnect.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,88 @@
+/*
+ * In this module we implement call disconnection and clearing procedures.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+#include "../include/gsm48_const.h"
+#include "../include/out_routes.h"
+#include "call.h"
+
+extern unsigned sip_linger_timeout;
+
+void
+disconnect_mncc(call, cause_loc, cause_val)
+	struct call *call;
+{
+	struct gsm_mncc msg;
+
+	if (!call->mncc)
+		return;
+	switch (call->mncc_state) {
+	case MNCC_STATE_DISCONNECT:
+	case MNCC_STATE_RELEASE:
+		return;
+	}
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_DISC_REQ;
+	msg.callref = call->mncc_callref;
+	mncc_set_cause(&msg, cause_loc, cause_val);
+	send_mncc_to_sock(call->mncc, &msg, sizeof(struct gsm_mncc));
+	call->mncc_state = MNCC_STATE_DISCONNECT;
+}
+
+void
+disconnect_tmgw(call)
+	struct call *call;
+{
+	switch (call->mgw_state) {
+	case MGW_STATE_NO_EXIST:
+	case MGW_STATE_DELETING:
+	case MGW_STATE_MDCX_IBT:
+	case MGW_STATE_MDCX_CONN:
+	case MGW_STATE_MDCX_HOLD:
+	case MGW_STATE_MDCX_RETR:
+	case MGW_STATE_DTMF_OP:
+		return;
+	case MGW_STATE_ALLOCATED:
+	case MGW_STATE_IBT_CONN:
+	case MGW_STATE_COMPLETE:
+	case MGW_STATE_HELD:
+		tmgw_send_dlcx(call);
+		return;
+	default:
+		syslog(LOG_CRIT,
+			"FATAL: invalid MGW state 0x%x in disconnect_tmgw()",
+			call->mgw_state);
+		exit(1);
+	}
+}
+
+void
+disconnect_sip(call)
+	struct call *call;
+{
+	if (!call->sip_call_exists)
+		return;
+	switch (call->sip_state) {
+	case SIP_STATE_INV_SENT:
+		call->sip_state = SIP_STATE_ACCEPT_100;
+		sip_mark_end_time(call, sip_linger_timeout);
+		break;
+	case SIP_STATE_100_RCVD:
+		initiate_sip_cancel(call);
+		break;
+	case SIP_STATE_CONNECTED:
+		initiate_bye(call);
+		break;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/main.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,119 @@
+/*
+ * Main module for themwi-sip-out.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <signal.h>
+#include <syslog.h>
+#include <unistd.h>
+#include "../include/out_routes.h"
+#include "call.h"
+
+extern unsigned cfg_retrans_timeout;
+
+extern int mgw_socket, mgw_is_connected;
+extern int sip_socket, mncc_listener;
+extern struct mncc_conn *mncc_conn_list;
+
+static int max_fd;
+
+struct timeval cur_event_time;
+
+update_max_fd(newfd)
+{
+	if (newfd > max_fd)
+		max_fd = newfd;
+}
+
+main(argc, argv)
+	char **argv;
+{
+	fd_set fds;
+	struct mncc_conn *conn, **connp;
+	int rc, need_retrans, dead_sip_flag;
+	struct timeval timeout;
+	time_t dead_sip_time;
+
+	openlog("themwi-sip-out", 0, LOG_LOCAL5);
+	read_config_file();
+	if (read_out_routes_db() < 0) {
+		fprintf(stderr, "error reading out-routes.bin database\n");
+		exit(1);
+	}
+	if (create_outcall_socket() < 0) {
+		fprintf(stderr, "error creating outcall socket\n");
+		exit(1);
+	}
+	if (open_sip_udp_socket() < 0) {
+		fprintf(stderr, "error opening SIP UDP socket\n");
+		exit(1);
+	}
+	if (argv[1]) {
+		rc = open_sip_log_file(argv[1]);
+		if (rc < 0)
+			exit(1);	/* error msg already printed */
+	}
+	signal(SIGPIPE, SIG_IGN);
+	/* main select loop */
+	for (;;) {
+		FD_ZERO(&fds);
+		FD_SET(sip_socket, &fds);
+		FD_SET(mncc_listener, &fds);
+		if (mgw_is_connected)
+			FD_SET(mgw_socket, &fds);
+		for (connp = &mncc_conn_list; conn = *connp; ) {
+			if (conn->fd < 0) {
+				*connp = conn->next;
+				free(conn);
+				continue;
+			}
+			FD_SET(conn->fd, &fds);
+			connp = &conn->next;
+		}
+		need_retrans = dead_sip_flag = 0;
+		scan_call_list_for_timeouts(&need_retrans, &dead_sip_flag,
+					    &dead_sip_time);
+		if (need_retrans) {
+			timeout.tv_sec = cfg_retrans_timeout / 1000;
+			timeout.tv_usec = (cfg_retrans_timeout % 1000) * 1000;
+			rc = select(max_fd+1, &fds, 0, 0, &timeout);
+		} else if (dead_sip_flag) {
+			if (cur_event_time.tv_sec >= dead_sip_time)
+				timeout.tv_sec = 0;
+			else
+				timeout.tv_sec =
+					dead_sip_time - cur_event_time.tv_sec;
+			timeout.tv_usec = 0;
+			rc = select(max_fd+1, &fds, 0, 0, &timeout);
+		} else
+			rc = select(max_fd+1, &fds, 0, 0, 0);
+		if (rc < 0) {
+			if (errno == EINTR)
+				continue;
+			syslog(LOG_CRIT, "select: %m");
+			exit(1);
+		}
+		gettimeofday(&cur_event_time, 0);
+		if (rc) {
+			if (FD_ISSET(mncc_listener, &fds))
+				mncc_listener_select();
+			for (conn = mncc_conn_list; conn; conn = conn->next)
+				if (FD_ISSET(conn->fd, &fds))
+					mncc_socket_select(conn);
+			if (FD_ISSET(sip_socket, &fds))
+				sip_socket_select();
+			if (mgw_is_connected && FD_ISSET(mgw_socket, &fds))
+				mgw_socket_select();
+		} else if (need_retrans)
+			run_periodic_retrans();
+		clear_dead_calls();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/mgw_ops.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,172 @@
+/*
+ * In this module we implement all transactions from themwi-sip-out
+ * toward themwi-mgw.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/tmgw_ctrl.h"
+#include "../include/tmgw_const.h"
+#include "../include/out_routes.h"
+#include "call.h"
+
+extern struct call *call_list;
+
+struct call *
+find_call_with_mgw_xact(xact_id)
+	uint32_t xact_id;
+{
+	struct call *call;
+
+	for (call = call_list; call; call = call->next)
+		if (call->mgw_xact && call->mgw_xact_id == xact_id)
+			return call;
+	return 0;
+}
+
+uint32_t
+get_new_tmgw_xact_id()
+{
+	static uint32_t next_xact_id;
+
+	for (;;) {
+		next_xact_id++;
+		if (!find_call_with_mgw_xact(next_xact_id))
+			return next_xact_id;
+	}
+}
+
+void
+tmgw_send_crcx(call)
+	struct call *call;
+{
+	struct tmgw_ctrl_req req;
+
+	bzero(&req, sizeof req);
+	req.opcode = TMGW_CTRL_OP_CRCX;
+	req.transact_ref = get_new_tmgw_xact_id();
+	req.ep_id = TMGW_EP_TYPE_GATEWAY;
+	req.setup_mask = TMGW_CTRL_MASK_GSM_CONN;
+	bcopy(&call->gsm_rtp_osmo, &req.gsm_addr,
+		sizeof(struct sockaddr_storage));
+	req.gsm_payload_type = call->gsm_payload_type;
+	req.gsm_payload_msg_type = call->gsm_payload_msg_type;
+	send_req_to_tmgw(&req);
+	call->mgw_xact = TMGW_CTRL_OP_CRCX;
+	call->mgw_xact_id = req.transact_ref;
+}
+
+void
+tmgw_send_mdcx_connect(call, ibt)
+	struct call *call;
+{
+	struct tmgw_ctrl_req req;
+
+	bzero(&req, sizeof req);
+	req.opcode = TMGW_CTRL_OP_MDCX;
+	req.transact_ref = get_new_tmgw_xact_id();
+	req.ep_id = call->mgw_ep_id;
+	req.setup_mask = TMGW_CTRL_MASK_PSTN_CONN | TMGW_CTRL_MASK_FWD_MODE;
+	bcopy(&call->pstn_rtp_remote, &req.pstn_addr,
+		sizeof(struct sockaddr_in));
+	req.pstn_payload_type =
+		call->use_pcma ? PSTN_CODEC_PCMA : PSTN_CODEC_PCMU;
+	req.fwd_mode = ibt ? TMGW_FWD_MODE_RECVONLY : TMGW_FWD_MODE_SENDRECV;
+	send_req_to_tmgw(&req);
+	call->mgw_state = ibt ? MGW_STATE_MDCX_IBT : MGW_STATE_MDCX_CONN;
+	call->mgw_xact = TMGW_CTRL_OP_MDCX;
+	call->mgw_xact_id = req.transact_ref;
+}
+
+void
+tmgw_send_mdcx_hold(call)
+	struct call *call;
+{
+	struct tmgw_ctrl_req req;
+
+	bzero(&req, sizeof req);
+	req.opcode = TMGW_CTRL_OP_MDCX;
+	req.transact_ref = get_new_tmgw_xact_id();
+	req.ep_id = call->mgw_ep_id;
+	req.setup_mask = TMGW_CTRL_MASK_FWD_MODE;
+	req.fwd_mode = TMGW_FWD_MODE_INACTIVE;
+	send_req_to_tmgw(&req);
+	call->mgw_state = MGW_STATE_MDCX_HOLD;
+	call->mgw_xact = TMGW_CTRL_OP_MDCX;
+	call->mgw_xact_id = req.transact_ref;
+}
+
+void
+tmgw_send_mdcx_retrieve(call)
+	struct call *call;
+{
+	struct tmgw_ctrl_req req;
+
+	bzero(&req, sizeof req);
+	req.opcode = TMGW_CTRL_OP_MDCX;
+	req.transact_ref = get_new_tmgw_xact_id();
+	req.ep_id = call->mgw_ep_id;
+	req.setup_mask = TMGW_CTRL_MASK_FWD_MODE;
+	req.fwd_mode = TMGW_FWD_MODE_SENDRECV;
+	send_req_to_tmgw(&req);
+	call->mgw_state = MGW_STATE_MDCX_RETR;
+	call->mgw_xact = TMGW_CTRL_OP_MDCX;
+	call->mgw_xact_id = req.transact_ref;
+}
+
+void
+tmgw_send_dlcx(call)
+	struct call *call;
+{
+	struct tmgw_ctrl_req req;
+
+	bzero(&req, sizeof req);
+	req.opcode = TMGW_CTRL_OP_DLCX;
+	req.transact_ref = get_new_tmgw_xact_id();
+	req.ep_id = call->mgw_ep_id;
+	send_req_to_tmgw(&req);
+	call->mgw_state = MGW_STATE_DELETING;
+	call->mgw_xact = TMGW_CTRL_OP_DLCX;
+	call->mgw_xact_id = req.transact_ref;
+}
+
+void
+tmgw_send_dtmf_start(call)
+	struct call *call;
+{
+	struct tmgw_ctrl_req req;
+
+	bzero(&req, sizeof req);
+	req.opcode = TMGW_CTRL_OP_DTMF_START;
+	req.transact_ref = get_new_tmgw_xact_id();
+	req.ep_id = call->mgw_ep_id;
+	req.fwd_mode = call->dtmf_digit;
+	send_req_to_tmgw(&req);
+	call->mgw_state = MGW_STATE_DTMF_OP;
+	call->mgw_xact = TMGW_CTRL_OP_DTMF_START;
+	call->mgw_xact_id = req.transact_ref;
+}
+
+void
+tmgw_send_dtmf_stop(call)
+	struct call *call;
+{
+	struct tmgw_ctrl_req req;
+
+	bzero(&req, sizeof req);
+	req.opcode = TMGW_CTRL_OP_DTMF_STOP;
+	req.transact_ref = get_new_tmgw_xact_id();
+	req.ep_id = call->mgw_ep_id;
+	send_req_to_tmgw(&req);
+	call->mgw_state = MGW_STATE_DTMF_OP;
+	call->mgw_xact = TMGW_CTRL_OP_DTMF_STOP;
+	call->mgw_xact_id = req.transact_ref;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/mgw_resp.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,212 @@
+/*
+ * In this module we implement our handling of all responses
+ * from themwi-mgw.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.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 "../include/tmgw_ctrl.h"
+#include "../include/tmgw_const.h"
+#include "../include/out_routes.h"
+#include "call.h"
+
+extern struct call *find_call_with_mgw_xact();
+
+static void
+crcx_response(call, msg)
+	struct call *call;
+	struct tmgw_ctrl_resp *msg;
+{
+	int cause;
+
+	if (msg->res == TMGW_RESP_OK) {
+		call->mgw_state = MGW_STATE_ALLOCATED;
+		call->mgw_ep_id = msg->ep_id;
+		bcopy(&msg->gsm_addr, &call->gsm_rtp_tmgw,
+			sizeof(struct sockaddr_storage));
+		bcopy(&msg->pstn_addr, &call->pstn_rtp_local,
+			sizeof(struct sockaddr_in));
+		switch (call->overall_state) {
+		case OVERALL_STATE_CRCX:
+			send_rtp_connect(call);
+			start_sip_out(call);
+			return;
+		case OVERALL_STATE_TEARDOWN:
+			tmgw_send_dlcx(call);
+			return;
+		default:
+		bad_state:
+			syslog(LOG_CRIT,
+			"FATAL: invalid overall state 0x%x on CRCX response",
+				call->overall_state);
+			exit(1);
+		}
+	} else {
+		switch (call->overall_state) {
+		case OVERALL_STATE_CRCX:
+			call->overall_state = OVERALL_STATE_TEARDOWN;
+			switch (msg->res) {
+			case TMGW_RESP_ERR_RSRC:
+				cause = GSM48_CC_CAUSE_RESOURCE_UNAVAIL;
+				break;
+			case TMGW_RESP_ERR_NOTSUP:
+				cause = GSM48_CC_CAUSE_BEARER_CA_UNAVAIL;
+				break;
+			default:
+				cause = GSM48_CC_CAUSE_PROTO_ERR;
+			}
+			disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU, cause);
+			return;
+		case OVERALL_STATE_TEARDOWN:
+			check_dead_call(call);
+			return;
+		default:
+			goto bad_state;
+		}
+	}
+}
+
+static void
+mdcx_response(call, msg)
+	struct call *call;
+	struct tmgw_ctrl_resp *msg;
+{
+	if (call->overall_state == OVERALL_STATE_TEARDOWN) {
+		tmgw_send_dlcx(call);
+		return;
+	}
+	if (msg->res != TMGW_RESP_OK) {
+		call->overall_state = OVERALL_STATE_TEARDOWN;
+		tmgw_send_dlcx(call);
+		if (msg->res == TMGW_RESP_ERR_RSRC &&
+		    (call->mgw_state == MGW_STATE_MDCX_IBT ||
+		     call->mgw_state == MGW_STATE_MDCX_CONN))
+			disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		else
+			disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_NETWORK_OOO);
+		disconnect_sip(call);
+		return;
+	}
+	switch (call->mgw_state) {
+	case MGW_STATE_MDCX_IBT:
+		call->mgw_state = MGW_STATE_IBT_CONN;
+		mncc_signal_ibt_ring(call);
+		if (call->sip_state == SIP_STATE_CONNECTED)
+			tmgw_send_mdcx_connect(call, 0);
+		return;
+	case MGW_STATE_MDCX_CONN:
+		call->mgw_state = MGW_STATE_COMPLETE;
+		mncc_signal_answer_sup(call);
+		return;
+	case MGW_STATE_MDCX_HOLD:
+		call->mgw_state = MGW_STATE_HELD;
+		mncc_send_hold_ack(call);
+		return;
+	case MGW_STATE_MDCX_RETR:
+		call->mgw_state = MGW_STATE_COMPLETE;
+		mncc_send_retrieve_ack(call);
+		return;
+	default:
+		syslog(LOG_CRIT,
+			"FATAL: invalid MGW state 0x%x on MDCX response",
+			call->mgw_state);
+		exit(1);
+	}
+}
+
+static void
+dlcx_response(call, msg)
+	struct call *call;
+	struct tmgw_ctrl_resp *msg;
+{
+	if (msg->res != TMGW_RESP_OK) {
+		syslog(LOG_CRIT, "FATAL: TMGW DLCX failed with code 0x%x",
+			msg->res);
+		exit(1);
+	}
+	call->mgw_state = MGW_STATE_NO_EXIST;
+	check_dead_call(call);
+}
+
+static void
+dtmf_start_response(call, msg)
+	struct call *call;
+	struct tmgw_ctrl_resp *msg;
+{
+	if (call->overall_state == OVERALL_STATE_TEARDOWN) {
+		tmgw_send_dlcx(call);
+		return;
+	}
+	if (msg->res == TMGW_RESP_OK)
+		mncc_dtmf_start_ok(call);
+	else
+		mncc_dtmf_start_err(call);
+	if (call->dtmf_pending_stop)
+		tmgw_send_dtmf_stop(call);
+	else
+		call->mgw_state = MGW_STATE_COMPLETE;
+}
+
+static void
+dtmf_stop_response(call, msg)
+	struct call *call;
+	struct tmgw_ctrl_resp *msg;
+{
+	if (call->overall_state == OVERALL_STATE_TEARDOWN) {
+		tmgw_send_dlcx(call);
+		return;
+	}
+	mncc_dtmf_stop_ok(call);
+	call->mgw_state = MGW_STATE_COMPLETE;
+	call->dtmf_pending_stop = 0;
+}
+
+void
+process_tmgw_response(msg)
+	struct tmgw_ctrl_resp *msg;
+{
+	struct call *call;
+	unsigned opc;
+
+	call = find_call_with_mgw_xact(msg->transact_ref);
+	if (!call) {
+		syslog(LOG_CRIT,
+		"FATAL: response from TMGW xact 0x%x does not match any call",
+			msg->transact_ref);
+		exit(1);
+	}
+	opc = call->mgw_xact;
+	call->mgw_xact = 0;
+	switch (opc) {
+	case TMGW_CTRL_OP_CRCX:
+		crcx_response(call, msg);
+		return;
+	case TMGW_CTRL_OP_MDCX:
+		mdcx_response(call, msg);
+		return;
+	case TMGW_CTRL_OP_DLCX:
+		dlcx_response(call, msg);
+		return;
+	case TMGW_CTRL_OP_DTMF_START:
+		dtmf_start_response(call, msg);
+		return;
+	case TMGW_CTRL_OP_DTMF_STOP:
+		dtmf_stop_response(call, msg);
+		return;
+	default:
+		syslog(LOG_CRIT,
+			"FATAL: invalid opcode 0x%x in call->msg_xact", opc);
+		exit(1);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/mgw_sock.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,70 @@
+/*
+ * In this module we implement our local socket interface to themwi-mgw.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include "../include/tmgw_ctrl.h"
+
+static char tmgw_socket_pathname[] = "/var/gsm/tmgw_socket";
+
+int mgw_socket, mgw_is_connected;
+
+connect_tmgw_socket()
+{
+	struct sockaddr_un sa;
+	unsigned sa_len;
+	int rc;
+
+	if (mgw_is_connected)
+		return(0);
+	mgw_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (mgw_socket < 0) {
+		syslog(LOG_CRIT, "socket(AF_UNIX, SOCK_SEQPACKET, 0): %m");
+		return(-1);
+	}
+	fill_sockaddr_un(tmgw_socket_pathname, &sa, &sa_len);
+	rc = connect(mgw_socket, (struct sockaddr *) &sa, sa_len);
+	if (rc < 0) {
+		syslog(LOG_ERR, "connect to %s: %m", tmgw_socket_pathname);
+		close(mgw_socket);
+		return(-1);
+	}
+	update_max_fd(mgw_socket);
+	mgw_is_connected = 1;
+	return(0);
+}
+
+void
+mgw_socket_select()
+{
+	struct tmgw_ctrl_resp msg;
+	int rc;
+
+	rc = recv(mgw_socket, &msg, sizeof msg, 0);
+	if (rc <= 0) {
+		syslog(LOG_ERR, "themwi-mgw socket disconnected");
+		close(mgw_socket);
+		mgw_is_connected = 0;
+		shutdown_mgw_conn();
+		return;
+	}
+	if (rc != sizeof(struct tmgw_ctrl_resp)) {
+		syslog(LOG_CRIT,
+			"response packet from TMGW has wrong length: %d bytes",
+			rc);
+		exit(1);
+	}
+	process_tmgw_response(&msg);
+}
+
+send_req_to_tmgw(msg)
+	struct tmgw_ctrl_req *msg;
+{
+	return send(mgw_socket, msg, sizeof(struct tmgw_ctrl_req), 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/mncc_sig_in.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,214 @@
+/*
+ * In this module we implement our handling of call control messages
+ * from OsmoMSC relayed to us via themwi-mncc.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+#include "../include/gsm48_const.h"
+#include "../include/out_routes.h"
+#include "call.h"
+
+extern struct call *find_call_by_mncc_callref();
+
+static void
+handle_disconnect_ind(call, msg)
+	struct call *call;
+	struct gsm_mncc *msg;
+{
+	/* release back to MNCC */
+	msg->msg_type = MNCC_REL_REQ;
+	send_mncc_to_sock(call->mncc, msg, sizeof(struct gsm_mncc));
+	call->mncc_state = MNCC_STATE_RELEASE;
+	/* signal disconnect to SIP */
+	call->overall_state = OVERALL_STATE_TEARDOWN;
+	disconnect_sip(call);
+	disconnect_tmgw(call);
+}
+
+static void
+handle_final_release(call, msg)
+	struct call *call;
+	struct gsm_mncc *msg;
+{
+	/* MNCC call leg is gone */
+	call->mncc = 0;
+	/* signal disconnect to SIP */
+	call->overall_state = OVERALL_STATE_TEARDOWN;
+	disconnect_sip(call);
+	disconnect_tmgw(call);
+	check_dead_call(call);
+}
+
+static void
+handle_dtmf_start(call, msg)
+	struct call *call;
+	struct gsm_mncc *msg;
+{
+	if (!(msg->fields & MNCC_F_KEYPAD) ||
+	    !is_valid_dtmf_digit(msg->keypad)) {
+		msg->msg_type = MNCC_START_DTMF_REJ;
+		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_INVAL_MAND_INF);
+		send_mncc_to_sock(call->mncc, msg, sizeof(struct gsm_mncc));
+		return;
+	}
+	if (call->mncc_state != MNCC_STATE_CONNECTED ||
+	    call->mgw_state != MGW_STATE_COMPLETE) {
+		msg->msg_type = MNCC_START_DTMF_REJ;
+		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_MSGTYPE_INCOMPAT);
+		send_mncc_to_sock(call->mncc, msg, sizeof(struct gsm_mncc));
+		return;
+	}
+	call->dtmf_digit = msg->keypad;
+	tmgw_send_dtmf_start(call);
+}
+
+static void
+handle_dtmf_stop(call, msg)
+	struct call *call;
+	struct gsm_mncc *msg;
+{
+	if (call->mncc_state != MNCC_STATE_CONNECTED)
+		return;
+	if (call->mgw_state == MGW_STATE_COMPLETE)
+		tmgw_send_dtmf_stop(call);
+	else if (call->mgw_state == MGW_STATE_DTMF_OP)
+		call->dtmf_pending_stop = 1;
+	else {
+		/* dummy OK response */
+		msg->msg_type = MNCC_STOP_DTMF_RSP;
+		send_mncc_to_sock(call->mncc, msg, sizeof(struct gsm_mncc));
+	}
+}
+
+static void
+handle_call_hold(call, msg)
+	struct call *call;
+	struct gsm_mncc *msg;
+{
+	if (call->mncc_state != MNCC_STATE_CONNECTED ||
+	    call->mgw_state != MGW_STATE_COMPLETE) {
+		msg->msg_type = MNCC_HOLD_REJ;
+		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_MSGTYPE_INCOMPAT);
+		send_mncc_to_sock(call->mncc, msg, sizeof(struct gsm_mncc));
+		return;
+	}
+	tmgw_send_mdcx_hold(call);
+}
+
+static void
+handle_call_retrieve(call, msg)
+	struct call *call;
+	struct gsm_mncc *msg;
+{
+	if (call->mncc_state != MNCC_STATE_CONNECTED ||
+	    call->mgw_state != MGW_STATE_HELD) {
+		msg->msg_type = MNCC_RETRIEVE_REJ;
+		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_MSGTYPE_INCOMPAT);
+		send_mncc_to_sock(call->mncc, msg, sizeof(struct gsm_mncc));
+		return;
+	}
+	send_rtp_connect(call);
+	tmgw_send_mdcx_retrieve(call);
+}
+
+void
+handle_mncc_signal(mncc, msg, msglen)
+	struct mncc_conn *mncc;
+	struct gsm_mncc *msg;
+	unsigned msglen;
+{
+	struct call *call;
+
+	if (msglen != sizeof(struct gsm_mncc)) {
+		syslog(LOG_CRIT,
+			"FATAL: Rx MNCC message type 0x%x has wrong length",
+			msg->msg_type);
+		exit(1);
+	}
+	call = find_call_by_mncc_callref(mncc, msg->callref);
+	if (!call) {
+		syslog(LOG_CRIT,
+		"error: Rx MNCC message type 0x%x has invalid callref 0x%x",
+			msg->msg_type, msg->callref);
+		exit(1);
+	}
+	switch (msg->msg_type) {
+	case MNCC_DISC_IND:
+		handle_disconnect_ind(call, msg);
+		return;
+	case MNCC_REL_IND:
+	case MNCC_REL_CNF:
+		handle_final_release(call, msg);
+		return;
+	case MNCC_START_DTMF_IND:
+		handle_dtmf_start(call, msg);
+		return;
+	case MNCC_STOP_DTMF_IND:
+		handle_dtmf_stop(call, msg);
+		return;
+	case MNCC_MODIFY_IND:
+		msg->msg_type = MNCC_MODIFY_REJ;
+		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		send_mncc_to_sock(mncc, msg, sizeof(struct gsm_mncc));
+		return;
+	case MNCC_HOLD_IND:
+		handle_call_hold(call, msg);
+		return;
+	case MNCC_RETRIEVE_IND:
+		handle_call_retrieve(call, msg);
+		return;
+	}
+}
+
+void
+handle_rtp_create(mncc, msg, msglen)
+	struct mncc_conn *mncc;
+	struct gsm_mncc_rtp *msg;
+	unsigned msglen;
+{
+	struct call *call;
+
+	if (msglen != sizeof(struct gsm_mncc_rtp)) {
+		syslog(LOG_CRIT,
+			"FATAL: Rx MNCC message type 0x%x has wrong length",
+			msg->msg_type);
+		exit(1);
+	}
+	call = find_call_by_mncc_callref(mncc, msg->callref);
+	if (!call) {
+		syslog(LOG_CRIT,
+		"error: Rx MNCC message type 0x%x has invalid callref 0x%x",
+			msg->msg_type, msg->callref);
+		exit(1);
+	}
+	/* we need to be in the right state */
+	if (call->mncc_state != MNCC_STATE_MO_PROC) {
+		syslog(LOG_ERR,
+		"duplicate MNCC_RTP_CREATE for MO callref 0x%x, ignoring",
+			msg->callref);
+		return;
+	}
+	/* save Osmocom network RTP information */
+	bcopy(&msg->addr, &call->gsm_rtp_osmo, sizeof(struct sockaddr_storage));
+	call->gsm_payload_type = msg->payload_type;
+	call->gsm_payload_msg_type = msg->payload_msg_type;
+	/* move forward */
+	call->mncc_state = MNCC_STATE_GOT_RTP;
+	call->overall_state = OVERALL_STATE_CRCX;
+	tmgw_send_crcx(call);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/mncc_sig_out.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,145 @@
+/*
+ * In this module we implement functions that send MNCC messages
+ * to OsmoMSC via themwi-mncc, to be called by SIP and TMGW events.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+#include "../include/gsm48_const.h"
+#include "../include/out_routes.h"
+#include "call.h"
+
+void
+mncc_signal_alerting(call)
+	struct call *call;
+{
+	struct gsm_mncc msg;
+
+	if (call->mncc_state == MNCC_STATE_ALERTING)
+		return;
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_ALERT_REQ;
+	msg.callref = call->mncc_callref;
+	send_mncc_to_sock(call->mncc, &msg, sizeof(struct gsm_mncc));
+	call->mncc_state = MNCC_STATE_ALERTING;
+}
+
+void
+mncc_signal_ibt_ring(call)
+	struct call *call;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	if (call->overall_state == OVERALL_STATE_RINGING &&
+	    call->mncc_state != MNCC_STATE_ALERTING) {
+		msg.msg_type = MNCC_ALERT_REQ;
+		call->mncc_state = MNCC_STATE_ALERTING;
+	} else
+		msg.msg_type = MNCC_PROGRESS_REQ;
+	msg.callref = call->mncc_callref;
+	msg.fields |= MNCC_F_PROGRESS;
+	msg.progress.coding = GSM48_CAUSE_CODING_GSM;
+	msg.progress.location = GSM48_CAUSE_LOC_NET_BEYOND;
+	msg.progress.descr = GSM48_PROGR_IN_BAND_AVAIL;
+	send_mncc_to_sock(call->mncc, &msg, sizeof(struct gsm_mncc));
+}
+
+void
+mncc_signal_answer_sup(call)
+	struct call *call;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_SETUP_RSP;
+	msg.callref = call->mncc_callref;
+	send_mncc_to_sock(call->mncc, &msg, sizeof(struct gsm_mncc));
+	call->mncc_state = MNCC_STATE_CONNECTED;
+}
+
+void
+mncc_dtmf_start_ok(call)
+	struct call *call;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_START_DTMF_RSP;
+	msg.callref = call->mncc_callref;
+	msg.fields |= MNCC_F_KEYPAD;
+	msg.keypad = call->dtmf_digit;
+	send_mncc_to_sock(call->mncc, &msg, sizeof(struct gsm_mncc));
+}
+
+void
+mncc_dtmf_start_err(call)
+	struct call *call;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_START_DTMF_REJ;
+	msg.callref = call->mncc_callref;
+	mncc_set_cause(&msg, GSM48_CAUSE_LOC_PRN_S_LU,
+			GSM48_CC_CAUSE_PROTO_ERR);
+	send_mncc_to_sock(call->mncc, &msg, sizeof(struct gsm_mncc));
+}
+
+void
+mncc_dtmf_stop_ok(call)
+	struct call *call;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_STOP_DTMF_RSP;
+	msg.callref = call->mncc_callref;
+	send_mncc_to_sock(call->mncc, &msg, sizeof(struct gsm_mncc));
+}
+
+void
+mncc_send_hold_ack(call)
+	struct call *call;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_HOLD_CNF;
+	msg.callref = call->mncc_callref;
+	send_mncc_to_sock(call->mncc, &msg, sizeof(struct gsm_mncc));
+}
+
+void
+mncc_send_retrieve_ack(call)
+	struct call *call;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_RETRIEVE_CNF;
+	msg.callref = call->mncc_callref;
+	send_mncc_to_sock(call->mncc, &msg, sizeof(struct gsm_mncc));
+}
+
+void
+send_rtp_connect(call)
+	struct call *call;
+{
+	struct gsm_mncc_rtp rtp;
+
+	bzero(&rtp, sizeof(struct gsm_mncc_rtp));
+	rtp.msg_type = MNCC_RTP_CONNECT;
+	rtp.callref = call->mncc_callref;
+	bcopy(&call->gsm_rtp_tmgw, &rtp.addr, sizeof(struct sockaddr_storage));
+	send_mncc_to_sock(call->mncc, &rtp, sizeof(struct gsm_mncc_rtp));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/mncc_sock.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,153 @@
+/*
+ * In this module we implement the MNCC socket interface to themwi-sip-out.
+ * We have an outcall socket on which we accept connections, and each
+ * accepted connection becomes a struct mncc_conn for us.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <unistd.h>
+#include "../include/mncc.h"
+#include "../include/out_routes.h"
+#include "call.h"
+
+static char outcall_socket_pathname[] = "/var/gsm/outcall_socket";
+
+int mncc_listener;
+struct mncc_conn *mncc_conn_list;
+
+create_outcall_socket()
+{
+	struct sockaddr_un sa;
+	unsigned sa_len;
+	int rc;
+
+	mncc_listener = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (mncc_listener < 0) {
+		syslog(LOG_CRIT, "socket(AF_UNIX, SOCK_SEQPACKET, 0): %m");
+		return(-1);
+	}
+	unlink(outcall_socket_pathname);
+	fill_sockaddr_un(outcall_socket_pathname, &sa, &sa_len);
+	rc = bind(mncc_listener, (struct sockaddr *) &sa, sa_len);
+	if (rc < 0) {
+		syslog(LOG_ERR, "bind to %s: %m", outcall_socket_pathname);
+		return(-1);
+	}
+	rc = listen(mncc_listener, 3);
+	if (rc < 0) {
+		syslog(LOG_CRIT, "listen on UNIX socket: %m");
+		return(-1);
+	}
+	chmod(outcall_socket_pathname, 0775);
+	update_max_fd(mncc_listener);
+	return(0);
+}
+
+void
+mncc_listener_select()
+{
+	struct sockaddr_un sa;
+	socklen_t sa_len;
+	int fd;
+	struct mncc_conn *conn;
+
+	sa_len = sizeof sa;
+	fd = accept(mncc_listener, (struct sockaddr *) &sa, &sa_len);
+	if (fd < 0) {
+		syslog(LOG_CRIT, "accept on UNIX socket: %m");
+		exit(1);
+	}
+	conn = malloc(sizeof(struct mncc_conn));
+	if (!conn) {
+		syslog(LOG_CRIT, "malloc for outcall socket conn: %m");
+		close(fd);
+		return;
+	}
+	syslog(LOG_INFO, "new outcall socket connection");
+	conn->fd = fd;
+	conn->next = mncc_conn_list;
+	mncc_conn_list = conn;
+	update_max_fd(fd);
+}
+
+static void
+dispatch_mncc_msg(mncc, msg, msglen)
+	struct mncc_conn *mncc;
+	union mncc_msg *msg;
+	unsigned msglen;
+{
+	switch (msg->msg_type) {
+	case MNCC_SETUP_IND:
+		handle_setup_ind(mncc, msg, msglen);
+		return;
+	case MNCC_SETUP_COMPL_IND:
+	case MNCC_NOTIFY_IND:
+	case MNCC_DISC_IND:
+	case MNCC_FACILITY_IND:
+	case MNCC_START_DTMF_IND:
+	case MNCC_STOP_DTMF_IND:
+	case MNCC_MODIFY_IND:
+	case MNCC_HOLD_IND:
+	case MNCC_RETRIEVE_IND:
+	case MNCC_USERINFO_IND:
+	case MNCC_REL_IND:
+	case MNCC_REL_CNF:
+		handle_mncc_signal(mncc, msg, msglen);
+		return;
+	case MNCC_RTP_CREATE:
+		handle_rtp_create(mncc, msg, msglen);
+		return;
+	case MNCC_RTP_CONNECT:
+		syslog(LOG_ERR, "MNCC_RTP_CONNECT error from OsmoMSC");
+		return;
+	case MNCC_RTP_FREE:
+		syslog(LOG_ERR, "MNCC_RTP_FREE bogon from OsmoMSC");
+		return;
+	default:
+		syslog(LOG_CRIT,
+			"FATAL: received unexpected MNCC message type 0x%x",
+			msg->msg_type);
+		exit(1);
+	}
+}
+
+void
+mncc_socket_select(conn)
+	struct mncc_conn *conn;
+{
+	union mncc_msg msg;
+	int rc;
+
+	rc = recv(conn->fd, &msg, sizeof msg, 0);
+	if (rc <= 0) {
+		syslog(LOG_ERR, "outcall socket disconnected");
+		close(conn->fd);
+		conn->fd = -1;
+		shutdown_mncc_socket(conn);
+		return;
+	}
+	if (rc < 4) {
+		syslog(LOG_CRIT, "short read from MNCC socket: %d bytes", rc);
+		exit(1);
+	}
+	dispatch_mncc_msg(conn, &msg, rc);
+}
+
+send_mncc_to_sock(mncc, msg, msglen)
+	struct mncc_conn *mncc;
+	union mncc_msg *msg;
+	unsigned msglen;
+{
+	return send(mncc->fd, msg, msglen, 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/readconf.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,237 @@
+/*
+ * In this module we implement the reading of /var/gsm/themwi-sip-out.cfg:
+ * the main settings are bind-ip and bind-port, but we also have some
+ * optional config settings.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+struct in_addr sip_bind_ip;
+unsigned sip_bind_port;
+unsigned cfg_retrans_timeout = 500;
+unsigned cfg_retrans_count = 10;
+unsigned max_forwards = 70;
+unsigned sip_linger_timeout = 10;
+unsigned sip_linger_invite_err = 10;
+unsigned sip_linger_gotbye = 30;
+unsigned sip_linger_bye_out_ok = 5;
+unsigned sip_linger_bye_out_err = 180;
+int block_1900_numbers = 1;
+
+static char config_file_pathname[] = "/var/gsm/themwi-sip-out.cfg";
+
+struct parse_state {
+	int lineno;
+	int set_mask;
+};
+
+static void
+require_one_arg(st, kw, arg)
+	struct parse_state *st;
+	char *kw, *arg;
+{
+	char *cp;
+
+	if (*arg == '\0' || *arg == '#') {
+inv_syntax:	fprintf(stderr,
+			"%s line %d: %s setting requires one argument\n",
+			config_file_pathname, st->lineno, kw);
+		exit(1);
+	}
+	for (cp = arg; *cp && !isspace(*cp); cp++)
+		;
+	if (*cp)
+		*cp++ = '\0';
+	while (isspace(*cp))
+		cp++;
+	if (*cp != '\0' && *cp != '#')
+		goto inv_syntax;
+}
+
+static void
+handle_ip(st, kw, arg, var)
+	struct parse_state *st;
+	char *kw, *arg;
+	struct in_addr *var;
+{
+	require_one_arg(st, kw, arg);
+	var->s_addr = inet_addr(arg);
+	if (var->s_addr == INADDR_NONE) {
+		fprintf(stderr,
+			"%s line %d: invalid IP address argument \"%s\"\n",
+			config_file_pathname, st->lineno, arg);
+		exit(1);
+	}
+}
+
+static void
+handle_num(st, kw, arg, var)
+	struct parse_state *st;
+	char *kw, *arg;
+	unsigned *var;
+{
+	char *endp;
+
+	require_one_arg(st, kw, arg);
+	*var = strtoul(arg, &endp, 10);
+	if (*endp) {
+		fprintf(stderr, "%s line %d: invalid numeric argument \"%s\"\n",
+			config_file_pathname, st->lineno, arg);
+		exit(1);
+	}
+}
+
+static void
+handle_bool(st, kw, arg, var)
+	struct parse_state *st;
+	char *kw, *arg;
+	int *var;
+{
+	require_one_arg(st, kw, arg);
+	if (!strcmp(arg, "true") || !strcmp(arg, "on") || !strcmp(arg, "yes")
+	    || !strcmp(arg, "1")) {
+		*var = 1;
+		return;
+	}
+	if (!strcmp(arg, "false") || !strcmp(arg, "off") || !strcmp(arg, "no")
+	    || !strcmp(arg, "0")) {
+		*var = 0;
+		return;
+	}
+	fprintf(stderr, "%s line %d: invalid boolean argument \"%s\"\n",
+		config_file_pathname, st->lineno, arg);
+	exit(1);
+}
+
+static void
+handle_retrans_conf(st, kw, arg)
+	struct parse_state *st;
+	char *kw, *arg;
+{
+	char *cp = arg;
+
+	if (!isdigit(*cp)) {
+inv:		fprintf(stderr,
+		"%s line %d: %s setting requires two numeric arguments\n",
+			config_file_pathname, st->lineno, kw);
+		exit(1);
+	}
+	cfg_retrans_timeout = strtoul(cp, &cp, 10);
+	while (isspace(*cp))
+		cp++;
+	if (!isdigit(*cp))
+		goto inv;
+	cfg_retrans_count = strtoul(cp, &cp, 10);
+	while (isspace(*cp))
+		cp++;
+	if (*cp != '\0' && *cp != '#')
+		goto inv;
+}
+
+static void
+process_line(st, line)
+	struct parse_state *st;
+	char *line;
+{
+	char *cp, *kw;
+	void (*handler)(), *var;
+	int set_id;
+
+	if (!index(line, '\n')) {
+		fprintf(stderr, "%s line %d: too long or missing newline\n",
+			config_file_pathname, st->lineno);
+		exit(1);
+	}
+	for (cp = line; isspace(*cp); cp++)
+		;
+	if (*cp == '\0' || *cp == '#')
+		return;
+	for (kw = cp; *cp && !isspace(*cp); cp++)
+		;
+	if (*cp)
+		*cp++ = '\0';
+	if (!strcmp(kw, "bind-ip")) {
+		handler = handle_ip;
+		var = &sip_bind_ip;
+		set_id = 1;
+	} else if (!strcmp(kw, "bind-port")) {
+		handler = handle_num;
+		var = &sip_bind_port;
+		set_id = 2;
+	} else if (!strcmp(kw, "sip-udp-retrans")) {
+		handler = handle_retrans_conf;
+		var = (void *) 0;
+		set_id = 0;
+	} else if (!strcmp(kw, "sip-linger-timeout")) {
+		handler = handle_num;
+		var = &sip_linger_timeout;
+		set_id = 0;
+	} else if (!strcmp(kw, "sip-linger-invite-error")) {
+		handler = handle_num;
+		var = &sip_linger_invite_err;
+		set_id = 0;
+	} else if (!strcmp(kw, "sip-linger-got-bye")) {
+		handler = handle_num;
+		var = &sip_linger_gotbye;
+		set_id = 0;
+	} else if (!strcmp(kw, "sip-linger-bye-out-ok")) {
+		handler = handle_num;
+		var = &sip_linger_bye_out_ok;
+		set_id = 0;
+	} else if (!strcmp(kw, "sip-linger-bye-out-error")) {
+		handler = handle_num;
+		var = &sip_linger_bye_out_err;
+		set_id = 0;
+	} else if (!strcmp(kw, "max-forwards")) {
+		handler = &handle_num;
+		var = &max_forwards;
+		set_id = 0;
+	} else if (!strcmp(kw, "block-1900")) {
+		handler = handle_bool;
+		var = &block_1900_numbers;
+		set_id = 0;
+	} else {
+		fprintf(stderr, "%s line %d: non-understood keyword \"%s\"\n",
+			config_file_pathname, st->lineno, kw);
+		exit(1);
+	}
+	if (st->set_mask & set_id) {
+		fprintf(stderr, "%s line %d: duplicate %s setting\n",
+			config_file_pathname, st->lineno, kw);
+		exit(1);
+	}
+	while (isspace(*cp))
+		cp++;
+	handler(st, kw, cp, var);
+	st->set_mask |= set_id;
+}
+
+read_config_file()
+{
+	FILE *inf;
+	struct parse_state pst;
+	char linebuf[256];
+
+	inf = fopen(config_file_pathname, "r");
+	if (!inf) {
+		perror(config_file_pathname);
+		exit(1);
+	}
+	pst.set_mask = 0;
+	for (pst.lineno = 1; fgets(linebuf, sizeof linebuf, inf); pst.lineno++)
+		process_line(&pst, linebuf);
+	fclose(inf);
+	if (pst.set_mask != 3) {
+		fprintf(stderr, "error: %s did not set all required settings\n",
+			config_file_pathname);
+		exit(1);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/retrans.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,74 @@
+/*
+ * In this module we handle retransmission of our outgoing SIP UAC packets.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.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 "../include/out_routes.h"
+#include "../libsip/out_msg.h"
+#include "call.h"
+
+extern unsigned cfg_retrans_count;
+extern unsigned sip_linger_timeout;
+extern unsigned sip_linger_bye_out_err;
+extern struct call *call_list;
+
+void
+run_periodic_retrans()
+{
+	struct call *call;
+	struct sip_msg_out msg;
+
+	for (call = call_list; call; call = call->next) {
+		switch (call->sip_state) {
+		case SIP_STATE_INV_SENT:
+			if (call->sip_tx_count < cfg_retrans_count) {
+				fill_invite_req_msg(&msg, call);
+				sip_tx_packet(&msg, &call->udp_sin);
+				call->sip_tx_count++;
+			} else {
+				syslog(LOG_ERR,
+					"outbound INVITE retrans timeout");
+				call->overall_state = OVERALL_STATE_TEARDOWN;
+				disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_DEST_OOO);
+				disconnect_tmgw(call);
+				call->sip_state = SIP_STATE_ACCEPT_100;
+				sip_mark_end_time(call, sip_linger_timeout);
+			}
+			break;
+		case SIP_STATE_CANCEL_SENT:
+			if (call->sip_tx_count < cfg_retrans_count) {
+				fill_cancel_req_msg(&msg, call);
+				sip_tx_packet(&msg, &call->udp_sin);
+				call->sip_tx_count++;
+			} else {
+				syslog(LOG_ERR,
+					"outbound CANCEL retrans timeout");
+				call->sip_state = SIP_STATE_ACCEPT_200;
+				sip_mark_end_time(call, sip_linger_timeout);
+			}
+			break;
+		case SIP_STATE_BYE_SENT:
+			if (call->sip_tx_count < cfg_retrans_count) {
+				fill_bye_req_msg(&msg, call);
+				sip_tx_packet(&msg, &call->udp_sin);
+				call->sip_tx_count++;
+			} else {
+				syslog(LOG_ERR, "outbound BYE retrans timeout");
+				call->sip_state = SIP_STATE_ENDED;
+				sip_mark_end_time(call, sip_linger_bye_out_err);
+			}
+			break;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/shutdown.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,63 @@
+/*
+ * In this module we handle the scenarios of themwi-mncc and/or themwi-mgw
+ * shutting down while we are connected to them.  In both scenarios we
+ * terminate all active calls (in the case of MNCC socket closing, only
+ * those calls that came on that socket), but our themwi-sip-out process
+ * itself stays running.  This way once the other required processes restart,
+ * outbound calls will start working once again, without needing to restart
+ * themwi-sip-out.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+#include "../include/gsm48_const.h"
+#include "../include/out_routes.h"
+#include "call.h"
+
+extern struct call *call_list;
+
+void
+shutdown_mncc_socket(mncc)
+	struct mncc_conn *mncc;
+{
+	struct call *call;
+
+	for (call = call_list; call; call = call->next) {
+		if (call->mncc != mncc)
+			continue;
+		call->mncc = 0;
+		if (call->overall_state < OVERALL_STATE_TEARDOWN) {
+			call->overall_state = OVERALL_STATE_TEARDOWN;
+			disconnect_tmgw(call);
+			disconnect_sip(call);
+			check_dead_call(call);
+		}
+	}
+}
+
+void
+shutdown_mgw_conn()
+{
+	struct call *call;
+
+	for (call = call_list; call; call = call->next) {
+		call->mgw_state = MGW_STATE_NO_EXIST;
+		call->mgw_xact = 0;
+		if (call->overall_state < OVERALL_STATE_TEARDOWN) {
+			call->overall_state = OVERALL_STATE_TEARDOWN;
+			disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_NETWORK_OOO);
+			disconnect_sip(call);
+			check_dead_call(call);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/sip_log.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,68 @@
+/*
+ * In this module we implement debug logging of SIP Rx & Tx messages.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+extern struct timeval cur_event_time;
+
+static FILE *logfile;
+
+open_sip_log_file(filename)
+	char *filename;
+{
+	logfile = fopen(filename, "a");
+	if (!logfile) {
+		perror(filename);
+		return(-1);
+	}
+	return(0);
+}
+
+static void
+log_common(msg, msglen, sin, dir)
+	char *msg, *dir;
+	unsigned msglen;
+	struct sockaddr_in *sin;
+{
+	unsigned sec, ms;
+
+	sec = cur_event_time.tv_sec % 86400;
+	ms = cur_event_time.tv_usec / 1000;
+	fprintf(logfile, "Msg %s %s:%u %u bytes %02u:%02u:%02u.%03u\n", dir,
+		inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), msglen,
+		sec / 3600, (sec / 60) % 60, sec % 60, ms);
+	fwrite(msg, 1, msglen, logfile);
+	putc('\n', logfile);
+	fflush(logfile);
+}
+
+void
+log_sip_msg_rx(msg, msglen, sin)
+	char *msg;
+	unsigned msglen;
+	struct sockaddr_in *sin;
+{
+	if (!logfile)
+		return;
+	log_common(msg, msglen, sin, "from");
+}
+
+void
+log_sip_msg_tx(msg, msglen, sin)
+	char *msg;
+	unsigned msglen;
+	struct sockaddr_in *sin;
+{
+	if (!logfile)
+		return;
+	log_common(msg, msglen, sin, "to");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/sip_uas.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,62 @@
+/*
+ * Basic UAS functions for themwi-sip-out.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../libsip/parse.h"
+#include "../libsip/uas_basic.h"
+#include "../libsip/out_msg.h"
+
+static void
+unsupported_method(req, ess, sin)
+	struct sip_pkt_rx *req;
+	struct uas_parse_hdrs *ess;
+	struct sockaddr_in *sin;
+{
+	struct sip_msg_out resp;
+	int rc;
+
+	start_response_out_msg(&resp, "501 Method not supported");
+	rc = add_resp_basic_headers(&resp, ess, req->req_method);
+	if (rc < 0) {
+too_long:	syslog(LOG_ERR, "sending 501 error: response length exceeded");
+		return;
+	}
+	rc = out_msg_add_header(&resp, "Allow", "INVITE,ACK,BYE");
+	if (rc < 0)
+		goto too_long;
+	out_msg_finish(&resp);
+	sip_tx_packet(&resp, sin);
+}
+
+void
+process_sip_request(msg, sin)
+	struct sip_pkt_rx *msg;
+	struct sockaddr_in *sin;
+{
+	struct uas_parse_hdrs ess;
+	int rc;
+
+	rc = uas_get_basic_headers(msg, &ess);
+	if (rc < 0) {
+		syslog(LOG_ERR, "SIP %.16s request: bad or missing %s header",
+			msg->req_method, ess.error_field);
+		return;
+	}
+	/* dispatch by method */
+	if (!strcmp(msg->req_method, "INVITE"))
+		handle_invite_req(msg, &ess, sin);
+	else if (!strcmp(msg->req_method, "ACK"))
+		return;		/* no handling required */
+	else if (!strcmp(msg->req_method, "BYE"))
+		handle_bye_req(msg, &ess, sin);
+	else
+		unsupported_method(msg, &ess, sin);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/sip_udp.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,91 @@
+/*
+ * In this module we implement our UDP socket for SIP,
+ * and the associated lowest-level protocol handling.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <unistd.h>
+#include "../libsip/parse.h"
+#include "../libsip/out_msg.h"
+
+extern struct in_addr sip_bind_ip;
+extern unsigned sip_bind_port;
+
+int sip_socket;
+
+open_sip_udp_socket()
+{
+	struct sockaddr_in sin;
+	int rc;
+
+	sip_socket = socket(AF_INET, SOCK_DGRAM, 0);
+	if (sip_socket < 0) {
+		syslog(LOG_CRIT, "socket(AF_INET, SOCK_DGRAM, 0): %m");
+		return(-1);
+	}
+	sin.sin_family = AF_INET;
+	sin.sin_addr = sip_bind_ip;
+	sin.sin_port = htons(sip_bind_port);
+	rc = bind(sip_socket, (struct sockaddr *) &sin, sizeof sin);
+	if (rc < 0) {
+		syslog(LOG_CRIT, "bind of SIP UDP socket: %m");
+		return(-1);
+	}
+	update_max_fd(sip_socket);
+	return(0);
+}
+
+void
+sip_socket_select()
+{
+	struct sip_pkt_rx pkt;
+	struct sockaddr_in sin;
+	socklen_t addrlen;
+	int rc;
+
+	addrlen = sizeof sin;
+	rc = recvfrom(sip_socket, pkt.pkt_buffer, MAX_SIP_RX_PACKET, 0,
+			(struct sockaddr *) &sin, &addrlen);
+	if (rc <= 0)
+		return;
+	pkt.pkt_length = rc;
+	log_sip_msg_rx(pkt.pkt_buffer, pkt.pkt_length, &sin);
+	rc = parse_incoming_sip_msg(&pkt);
+	if (rc < 0) {
+		/* parse errors */
+		if (rc == -2 && pkt.parse_msgtype == SIP_MSG_TYPE_REQ)
+			syslog(LOG_ERR,
+				"SIP %.16s msg exceeds MAX_HEADER_FIELDS",
+				pkt.req_method);
+		else if (rc == -2 && pkt.parse_msgtype == SIP_MSG_TYPE_RESP)
+			syslog(LOG_ERR,
+				"SIP response msg exceeds MAX_HEADER_FIELDS");
+		/* in any case, silently discard */
+		return;
+	}
+	/* dispatch good-so-far SIP message */
+	if (pkt.parse_msgtype == SIP_MSG_TYPE_REQ)
+		process_sip_request(&pkt, &sin);
+	else if (pkt.parse_msgtype == SIP_MSG_TYPE_RESP)
+		process_sip_response(&pkt, &sin);
+}
+
+void
+sip_tx_packet(msg, sin)
+	struct sip_msg_out *msg;
+	struct sockaddr_in *sin;
+{
+	socklen_t addrlen;
+
+	addrlen = sizeof(struct sockaddr_in);
+	sendto(sip_socket, msg->buf, msg->msg_len, 0, (struct sockaddr *) sin,
+		addrlen);
+	log_sip_msg_tx(msg->buf, msg->msg_len, sin);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sip-out/uac_out.c	Tue Oct 11 23:04:01 2022 -0800
@@ -0,0 +1,190 @@
+/*
+ * In this module we implement generation of outgoing SIP messages
+ * in the UAC role.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.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 "../include/out_routes.h"
+#include "../libsip/out_msg.h"
+#include "../libsip/sdp.h"
+#include "call.h"
+
+extern struct call *find_call_by_sip_id();
+
+extern struct in_addr sip_bind_ip;
+extern unsigned sip_bind_port;
+extern unsigned max_forwards;
+extern struct timeval cur_event_time;
+
+add_req_boilerplate(msg, call, cseq, to_tag)
+	struct sip_msg_out *msg;
+	struct call *call;
+	char *cseq, *to_tag;
+{
+	char strbuf[256];
+	int rc;
+
+	sprintf(strbuf, "SIP/2.0/UDP %s:%u",
+		inet_ntoa(sip_bind_ip), sip_bind_port);
+	rc = out_msg_add_header(msg, "Via", strbuf);
+	if (rc < 0)
+		return rc;
+	rc = out_msg_add_header(msg, "From", call->from_header);
+	if (rc < 0)
+		return rc;
+	if (to_tag && to_tag[0]) {
+		sprintf(strbuf, "<%s>;tag=%s", call->to_uri, to_tag);
+		rc = out_msg_add_header(msg, "To", strbuf);
+	} else
+		rc = out_msg_add_header(msg, "To", call->to_uri);
+	if (rc < 0)
+		return rc;
+	rc = out_msg_add_header(msg, "Call-ID", call->sip_call_id);
+	if (rc < 0)
+		return rc;
+	rc = out_msg_add_header(msg, "CSeq", cseq);
+	if (rc < 0)
+		return rc;
+	sprintf(strbuf, "%u", max_forwards);
+	return out_msg_add_header(msg, "Max-Forwards", strbuf);
+}
+
+add_contact_header(msg)
+	struct sip_msg_out *msg;
+{
+	char strbuf[80];
+
+	sprintf(strbuf, "<sip:%s:%u;transport=udp>",
+		inet_ntoa(sip_bind_ip), sip_bind_port);
+	return out_msg_add_header(msg, "Contact", strbuf);
+}
+
+fill_invite_req_msg(msg, call)
+	struct sip_msg_out *msg;
+	struct call *call;
+{
+	struct sdp_gen sdp;
+	int rc;
+
+	rc = start_request_out_msg(msg, "INVITE", call->to_uri);
+	if (rc < 0)
+		return rc;
+	rc = add_req_boilerplate(msg, call, "1 INVITE", (char *) 0);
+	if (rc < 0)
+		return rc;
+	rc = add_contact_header(msg);
+	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 = SDP_CODEC_MASK_BOTH;
+	sdp.session_id = sdp.conn_port << 16;
+	sdp.owner_ip = sip_bind_ip;
+	return out_msg_finish_sdp(msg, &sdp);
+}
+
+fill_cancel_req_msg(msg, call)
+	struct sip_msg_out *msg;
+	struct call *call;
+{
+	int rc;
+
+	rc = start_request_out_msg(msg, "CANCEL", call->to_uri);
+	if (rc < 0)
+		return rc;
+	rc = add_req_boilerplate(msg, call, "1 CANCEL", (char *) 0);
+	if (rc < 0)
+		return rc;
+	out_msg_finish(msg);
+	return 0;
+}
+
+fill_bye_req_msg(msg, call)
+	struct sip_msg_out *msg;
+	struct call *call;
+{
+	int rc;
+
+	rc = start_request_out_msg(msg, "BYE", call->to_uri);
+	if (rc < 0)
+		return rc;
+	rc = add_req_boilerplate(msg, call, "2 BYE", call->to_tag);
+	if (rc < 0)
+		return rc;
+	out_msg_finish(msg);
+	return 0;
+}
+
+void
+start_sip_out(call)
+	struct call *call;
+{
+	struct sip_msg_out msg;
+	int rc;
+
+	sprintf(call->sip_call_id, "%08u_%u@%s",
+		(unsigned)(cur_event_time.tv_sec % 100000000),
+		ntohs(call->pstn_rtp_local.sin_port), inet_ntoa(sip_bind_ip));
+	if (find_call_by_sip_id(call->sip_call_id)) {
+		syslog(LOG_ERR, "generated Call-ID collision!");
+		call->overall_state = OVERALL_STATE_TEARDOWN;
+		disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SWITCH_CONG);
+		disconnect_tmgw(call);
+		return;
+	}
+	sprintf(call->from_header, "<sip:%s@%s>;tag=out%u", call->from_user,
+		inet_ntoa(sip_bind_ip), ntohs(call->pstn_rtp_local.sin_port));
+	rc = fill_invite_req_msg(&msg, call);
+	if (rc < 0) {
+		syslog(LOG_CRIT, "generated SIP INVITE req exceeds msg size!");
+		call->overall_state = OVERALL_STATE_TEARDOWN;
+		disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_NETWORK_OOO);
+		disconnect_tmgw(call);
+		return;
+	}
+	sip_tx_packet(&msg, &call->udp_sin);
+	call->overall_state = OVERALL_STATE_SIP_OUT;
+	call->sip_call_exists = 1;
+	call->sip_state = SIP_STATE_INV_SENT;
+	call->sip_tx_count = 1;
+}
+
+void
+initiate_sip_cancel(call)
+	struct call *call;
+{
+	struct sip_msg_out msg;
+
+	fill_cancel_req_msg(&msg, call);
+	sip_tx_packet(&msg, &call->udp_sin);
+	call->sip_state = SIP_STATE_CANCEL_SENT;
+	call->sip_tx_count = 1;
+}
+
+void
+initiate_bye(call)
+	struct call *call;
+{
+	struct sip_msg_out msg;
+
+	fill_bye_req_msg(&msg, call);
+	sip_tx_packet(&msg, &call->udp_sin);
+	call->sip_state = SIP_STATE_BYE_SENT;
+	call->sip_tx_count = 1;
+}