summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2020-09-12 13:10:46 +0200
committerAndreas Eversberg <jolly@eversberg.eu>2020-12-29 11:18:32 +0100
commitec0042ae7020ee7c4d6242db64fdf395b57a20c7 (patch)
tree2d0b325aefd7bd4b422e97267f456334501ac697
Inital GIT import
-rw-r--r--.gitignore41
-rw-r--r--Makefile.am5
-rw-r--r--configure.ac97
-rwxr-xr-xgit-version-gen151
-rw-r--r--src/Makefile.am9
-rw-r--r--src/sip/Makefile.am17
-rw-r--r--src/sip/main.c371
-rw-r--r--src/sip/sip.c1905
-rw-r--r--src/sip/sip.h115
9 files changed, 2711 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9a3e486
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+Makefile
+Makefile.in
+.deps
+.libs
+*.o
+*.lo
+*.la
+*.pc
+aclocal.m4
+acinclude.m4
+aminclude.am
+m4/*.m4
+autom4te.cache
+compile
+config.h*
+config.sub
+config.log
+config.status
+config.guess
+configure
+depcomp
+missing
+ltmain.sh
+install-sh
+stamp-h1
+libtool
+tests/
+
+.tarball-version
+.version
+.dirstamp
+
+Doxyfile
+
+.*.sw?
+
+src/libdebug/libdebug.a
+src/liboptions/liboptions.a
+src/libosmocc/libosmocc.a
+src/libtimer/libtimer.a
+src/sip/osmo-cc-sip-endpoint
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..689d568
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,5 @@
+AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS = src
+
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..4f69cd7
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,97 @@
+AC_INIT([osmo-cc-sip-endpoint],
+ m4_esyscmd([./git-version-gen .tarball-version]),
+ [jolly@eversberg.eu])
+
+dnl *This* is the root dir, even if an install-sh exists in ../ or ../../
+AC_CONFIG_AUX_DIR([.])
+
+AM_INIT_AUTOMAKE([subdir-objects dist-bzip2])
+AC_CONFIG_TESTDIR(tests)
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl include release helper
+RELMAKE='-include osmo-release.mk'
+AC_SUBST([RELMAKE])
+
+dnl checks for programs
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+LT_INIT
+AC_PROG_LIBTOOL
+
+dnl check for pkg-config (explained in detail in libosmocore/configure.ac)
+AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no)
+if test "x$PKG_CONFIG_INSTALLED" = "xno"; then
+ AC_MSG_ERROR([You need to install pkg-config])
+fi
+
+AC_CONFIG_MACRO_DIRS([m4])
+AC_CONFIG_MACRO_DIR([m4])
+
+AC_ARG_ENABLE(sanitize,
+ [AS_HELP_STRING(
+ [--enable-sanitize],
+ [Compile with address sanitizer enabled],
+ )],
+ [sanitize=$enableval], [sanitize="no"])
+if test x"$sanitize" = x"yes"
+then
+ CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
+ CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
+fi
+
+AC_ARG_ENABLE(werror,
+ [AS_HELP_STRING(
+ [--enable-werror],
+ [Turn all compiler warnings into errors, with exceptions:
+ a) deprecation (allow upstream to mark deprecation without breaking builds);
+ b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds)
+ ]
+ )],
+ [werror=$enableval], [werror="no"])
+if test x"$werror" = x"yes"
+then
+ WERROR_FLAGS="-Werror"
+ WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
+ WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
+ CFLAGS="$CFLAGS $WERROR_FLAGS"
+ CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
+fi
+
+CFLAGS="$CFLAGS -Wall"
+CPPFLAGS="$CPPFLAGS -Wall"
+
+dnl checks for header files
+AC_HEADER_STDC
+AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h syslog.h ctype.h)
+
+# The following test is taken from WebKit's webkit.m4
+saved_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -fvisibility=hidden "
+AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
+ [ AC_MSG_RESULT([yes])
+ SYMBOL_VISIBILITY="-fvisibility=hidden"],
+ AC_MSG_RESULT([no]))
+CFLAGS="$saved_CFLAGS"
+AC_SUBST(SYMBOL_VISIBILITY)
+
+dnl Generate the output
+AM_CONFIG_HEADER(config.h)
+
+PKG_CHECK_MODULES(SOFIA, sofia-sip-ua >= 1.12)
+
+AC_CHECK_LIB([m], [main])
+AC_CHECK_LIB([pthread], [main])
+
+AC_OUTPUT(
+ src/liboptions/Makefile
+ src/libdebug/Makefile
+ src/libtimer/Makefile
+ src/libosmocc/Makefile
+ src/sip/Makefile
+ src/Makefile
+ Makefile)
diff --git a/git-version-gen b/git-version-gen
new file mode 100755
index 0000000..42cf3d2
--- /dev/null
+++ b/git-version-gen
@@ -0,0 +1,151 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2010-01-28.01
+
+# Copyright (C) 2007-2010 Free Software Foundation, Inc.
+#
+# 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/>.
+
+# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+# produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+# presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+# a checked-out repository. Created with contents that were learned at
+# the last time autoconf was run, and used by git-version-gen. Must not
+# be present in either $(srcdir) or $(builddir) for git-version-gen to
+# give accurate answers during normal development with a checked out tree,
+# but must be present in a tarball when there is no version control system.
+# Therefore, it cannot be used in any dependencies. GNUmakefile has
+# hooks to force a reconfigure at distribution time to get the value
+# correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+# tarball. Usable in dependencies, particularly for files that don't
+# want to depend on config.h but do want to track version changes.
+# Delete this file prior to any autoconf run where you want to rebuild
+# files to pick up a version string change; and leave it stale to
+# minimize rebuild time after unrelated changes to configure sources.
+#
+# It is probably wise to add these two files to .gitignore, so that you
+# don't accidentally commit either generated file.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+# m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+# [bug-project@example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .tarball-version will
+# exist in distribution tarballs.
+#
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+# echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+# echo $(VERSION) > $(distdir)/.tarball-version
+
+case $# in
+ 1) ;;
+ *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
+esac
+
+tarball_version_file=$1
+nl='
+'
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+ v=`cat $tarball_version_file` || exit 1
+ case $v in
+ *$nl*) v= ;; # reject multi-line output
+ [0-9]*) ;;
+ *) v= ;;
+ esac
+ test -z "$v" \
+ && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
+fi
+
+if test -n "$v"
+then
+ : # use $v
+elif
+ v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
+ || git describe --abbrev=4 HEAD 2>/dev/null` \
+ && case $v in
+ [0-9]*) ;;
+ v[0-9]*) ;;
+ *) (exit 1) ;;
+ esac
+then
+ # Is this a new git that lists number of commits since the last
+ # tag or the previous older version that did not?
+ # Newer: v6.10-77-g0f8faeb
+ # Older: v6.10-g0f8faeb
+ case $v in
+ *-*-*) : git describe is okay three part flavor ;;
+ *-*)
+ : git describe is older two part flavor
+ # Recreate the number of commits and rewrite such that the
+ # result is the same as if we were using the newer version
+ # of git describe.
+ vtag=`echo "$v" | sed 's/-.*//'`
+ numcommits=`git rev-list "$vtag"..HEAD | wc -l`
+ v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+ ;;
+ esac
+
+ # Change the first '-' to a '.', so version-comparing tools work properly.
+ # Remove the "g" in git describe's output string, to save a byte.
+ v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
+else
+ v=UNKNOWN
+fi
+
+v=`echo "$v" |sed 's/^v//'`
+
+# Don't declare a version "dirty" merely because a time stamp has changed.
+git status > /dev/null 2>&1
+
+dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
+case "$dirty" in
+ '') ;;
+ *) # Append the suffix only if there isn't one already.
+ case $v in
+ *-dirty) ;;
+ *) v="$v-dirty" ;;
+ esac ;;
+esac
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+echo "$v" | tr -d '\012'
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..6c73336
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,9 @@
+AUTOMAKE_OPTIONS = foreign
+
+SUBDIRS = \
+ liboptions \
+ libdebug \
+ libtimer \
+ libosmocc \
+ sip
+
diff --git a/src/sip/Makefile.am b/src/sip/Makefile.am
new file mode 100644
index 0000000..ec5bc33
--- /dev/null
+++ b/src/sip/Makefile.am
@@ -0,0 +1,17 @@
+AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) $(SOFIA_CFLAGS)
+
+bin_PROGRAMS = \
+ osmo-cc-sip-endpoint
+
+osmo_cc_sip_endpoint_SOURCES = \
+ sip.c \
+ main.c
+
+osmo_cc_sip_endpoint_LDADD = \
+ $(COMMON_LA) \
+ ../libdebug/libdebug.a \
+ ../liboptions/liboptions.a \
+ ../libtimer/libtimer.a \
+ ../libosmocc/libosmocc.a \
+ $(SOFIA_LIBS)
+
diff --git a/src/sip/main.c b/src/sip/main.c
new file mode 100644
index 0000000..f0a6e4d
--- /dev/null
+++ b/src/sip/main.c
@@ -0,0 +1,371 @@
+/* osmo-cc-sip-endpoint main
+ *
+ * (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 <signal.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include "../libdebug/debug.h"
+#include "../liboptions/options.h"
+#include "sip.h"
+
+sip_endpoint_t *sip_ep = NULL;
+int num_kanal = 1;
+
+int sofia_debug = 0;
+int send_no_ringing_after_progress = 0;
+int receive_no_ringing_after_progress = 0;
+const char *name = "sip";
+const char *local_user = NULL;
+const char *local_peer = NULL;
+const char *remote_user = NULL;
+const char *remote_peer = NULL;
+const char *asserted_id = NULL;
+int local_register = 0;
+int remote_register = 0;
+const char *register_user = NULL;
+const char *register_peer = NULL;
+int local_auth = 0;
+int remote_auth = 0;
+const char *auth_user = NULL;
+const char *auth_password = NULL;
+const char *auth_realm = NULL;
+const char *public_ip = NULL;
+const char *stun_server = NULL;
+int register_interval = 600;
+int options_interval = 60;
+int stun_interval = 60;
+int expires = 0;
+#define MAX_CC_ARGS 1024
+static int cc_argc = 0;
+static const char *cc_argv[MAX_CC_ARGS];
+
+static void print_usage(const char *app)
+{
+ printf("Usage: %s --local [<user>@]<address> --remote [<user>@]<address> [<options>]\n", app);
+}
+
+static void print_help()
+{
+ /* - - */
+ printf(" -h --help\n");
+ printf(" This help\n");
+ printf(" -v --verbose <level> | <level>,<category>[,<category>[,...]] | list\n");
+ printf(" Use 'list' to get a list of all levels and categories\n");
+ printf(" Verbose level: digit of debug level (default = '%d')\n", debuglevel);
+ printf(" Verbose level+category: level digit followed by one or more categories\n");
+ printf(" -> If no category is specified, all categories are selected\n");
+ printf(" -D --sofia-debug <level>\n");
+ printf(" A level of 0 is off, a level of 9 is full debugging. (default = %d)\n", sofia_debug);
+ printf(" --send-ner\n");
+ printf(" Do not send extra ringing response (180), after progress response (183)\n");
+ printf(" was sent.\n");
+ printf(" --receive-ner\n");
+ printf(" Do not expect to receive extra ringing response (180), after progress\n");
+ printf(" response (183) was received.\n");
+ printf(" -n --name <interface name>\n");
+ printf(" Give name of this interface. It will be sent in each call towards\n");
+ printf(" remote interface. (default = '%s')\n", name);
+ printf(" -l --local [<user>@]<address>\n");
+ printf(" Give local SIP peer. It will be used for local URI.\n");
+ printf(" If user is not given, the caller ID is used as user.\n");
+ printf(" -r --remote [<user>@]<address>\n");
+ printf(" Give remote SIP peer. It will be used for remote URI.\n");
+ printf(" If user is not given, the dialed number is used as user.\n");
+ printf(" -a --asserted-id <user>\n");
+ printf(" The asserted ID is used to idenitfy the actual SIP user, if the local\n");
+ printf(" and remote user in their URI are replaced by caller ID and dialed\n");
+ printf(" number. To send caller ID, you must not specify user at local SIP peer.\n");
+ printf(" To send dialed number, you must not specify user at remote SIP peer.\n");
+ printf(" -R --register <user>@<address>\n");
+ printf(" Give user and address to register at a remote SIP registrar.\n");
+ printf(" --local-register\n");
+ printf(" Define, if the remote must register to us, so that we know the remote\n");
+ printf(" address.\n");
+ printf(" --remote-register\n");
+ printf(" Define, if we must register to a registrar.\n");
+ printf(" If '--register' was given, but not any of '--local-register' or\n");
+ printf(" '--remote-register', remote registration automatically used.\n");
+ printf(" -A --auth <user> <password> <realm>\n");
+ printf(" Define, if we must perform authentication.\n");
+ printf(" Realm can be set to anything. It is relevant for local authentication.\n");
+ printf(" --local-auth\n");
+ printf(" Define, if the remote must authenticate when calling (or registering)\n");
+ printf(" to us.\n");
+ printf(" --remote-auth\n");
+ printf(" Define, if we must authenticate when calling (or registering) towards\n");
+ printf(" remote.\n");
+ printf(" If '--auth' was given, but not any of '--local-auth' or\n");
+ printf(" '--remote-auth', remote authentication automatically used.\n");
+ printf(" -P --public-ip <ip>\n");
+ printf(" If our local IP is changed by NAT, give the actual public IP.\n");
+ printf(" -S --stun-server <address>\n");
+ printf(" Instead of dynamic public IP, a STUN server can be given.\n");
+ printf(" --register-interval <seconds> (default = %d seconds)\n", register_interval);
+ printf(" --options-interval <seconds> (default = %d seconds)\n", options_interval);
+ printf(" --stun-interval <seconds> (default = %d seconds)\n", stun_interval);
+ printf(" --expires <seconds> | 0 (default = %d seconds)\n", expires);
+ printf(" Alter intervals, if needed.\n");
+ printf(" -C --cc \"<osmo-cc arg>\" [--cc ...]\n");
+ printf(" Pass arguments to Osmo-CC endpoint. Use '-cc help' for description.\n");
+}
+
+#define OPT_SEND_NER 256
+#define OPT_RECEIVE_NER 257
+#define OPT_LOCAL_REG 258
+#define OPT_REMOTE_REG 259
+#define OPT_LOCAL_AUTH 260
+#define OPT_REMOTE_AUTH 261
+#define OPT_REG_INTER 262
+#define OPT_OPT_INTER 263
+#define OPT_STUN_INTER 264
+#define OPT_EXPIRES 265
+
+static void add_options(void)
+{
+ option_add('h', "help", 0);
+ option_add('v', "verbose", 1);
+ option_add('D', "sofia-debug", 1);
+ option_add(OPT_SEND_NER, "send-ner", 0);
+ option_add(OPT_RECEIVE_NER, "receive-ner", 0);
+ option_add('n', "name", 1);
+ option_add('l', "local", 1);
+ option_add('r', "remote", 1);
+ option_add('a', "asserted-id", 1);
+ option_add('R', "register", 1);
+ option_add(OPT_LOCAL_REG, "local-register", 0);
+ option_add(OPT_REMOTE_REG, "remote-register", 0);
+ option_add('A', "auth", 3);
+ option_add(OPT_LOCAL_AUTH, "local-auth", 0);
+ option_add(OPT_REMOTE_AUTH, "remote-auth", 0);
+ option_add('P', "public-ip", 1);
+ option_add('S', "stun-server", 1);
+ option_add(OPT_REG_INTER, "register-interval", 1);
+ option_add(OPT_OPT_INTER, "options-interval", 1);
+ option_add(OPT_STUN_INTER, "stun-interval", 1);
+ option_add(OPT_EXPIRES, "expires", 1);
+ option_add('C', "cc", 1);
+}
+
+static int handle_options(int short_option, int argi, char **argv)
+{
+ const char *p;
+ int rc;
+
+ switch (short_option) {
+ case 'h':
+ print_usage(argv[0]);
+ print_help();
+ return 0;
+ case 'v':
+ if (!strcasecmp(argv[argi], "list")) {
+ debug_list_cat();
+ return 0;
+ }
+ rc = parse_debug_opt(argv[argi]);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse debug option, please use -h for help.\n");
+ return rc;
+ }
+ break;
+ case 'D':
+ sofia_debug = atoi(argv[argi]);
+ break;
+ case OPT_SEND_NER:
+ send_no_ringing_after_progress = 1;
+ break;
+ case OPT_RECEIVE_NER:
+ receive_no_ringing_after_progress = 1;
+ break;
+ case 'n':
+ name = strdup(argv[argi]);
+ break;
+ case 'l':
+ if ((p = strchr(argv[argi], '@'))) {
+ local_user = strdup(argv[argi]);
+ *strchr(local_user, '@') = '\0';
+ local_peer = strdup(p + 1);
+ } else
+ local_peer = strdup(argv[argi]);
+ break;
+ case 'r':
+ if ((p = strchr(argv[argi], '@'))) {
+ remote_user = strdup(argv[argi]);
+ *strchr(remote_user, '@') = '\0';
+ remote_peer = strdup(p + 1);
+ } else
+ remote_peer = strdup(argv[argi]);
+ break;
+ case 'a':
+ asserted_id = strdup(argv[argi]);
+ break;
+ case 'R':
+ if ((p = strchr(argv[argi], '@'))) {
+ register_user = strdup(argv[argi]);
+ *strchr(register_user, '@') = '\0';
+ register_peer = strdup(p + 1);
+ } else {
+ fprintf(stderr, "Missing '@' sign in given registrar!\n");
+ return -EINVAL;
+ }
+ break;
+ case OPT_LOCAL_REG:
+ local_register = 1;
+ break;
+ case OPT_REMOTE_REG:
+ remote_register = 1;
+ break;
+ case 'A':
+ auth_user = strdup(argv[argi]);
+ auth_password = strdup(argv[argi + 1]);
+ auth_realm = strdup(argv[argi + 2]);
+ break;
+ case OPT_LOCAL_AUTH:
+ local_auth = 1;
+ break;
+ case OPT_REMOTE_AUTH:
+ remote_auth = 1;
+ break;
+ case 'P':
+ public_ip = strdup(argv[argi]);
+ break;
+ case 'S':
+ stun_server = strdup(argv[argi]);
+ break;
+ case OPT_REG_INTER:
+ register_interval = atoi(argv[argi]);
+ break;
+ case OPT_OPT_INTER:
+ options_interval = atoi(argv[argi]);
+ break;
+ case OPT_STUN_INTER:
+ stun_interval = atoi(argv[argi]);
+ break;
+ case OPT_EXPIRES:
+ expires = atoi(argv[argi]);
+ break;
+ case 'C':
+ if (!strcasecmp(argv[argi], "help")) {
+ osmo_cc_help();
+ return 0;
+ }
+ if (cc_argc == MAX_CC_ARGS) {
+ fprintf(stderr, "Too many osmo-cc args!\n");
+ break;
+ }
+ cc_argv[cc_argc++] = strdup(argv[argi]);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 1;
+}
+
+static int quit = 0;
+void sighandler(int sigset)
+{
+ if (sigset == SIGHUP || sigset == SIGPIPE)
+ return;
+
+ fprintf(stderr, "\nSignal %d received.\n", sigset);
+
+ quit = 1;
+}
+
+int main(int argc, char *argv[])
+{
+ int argi, rc;
+
+ cc_argv[cc_argc++] = strdup("remote auto");
+
+ /* handle options / config file */
+ add_options();
+ rc = options_config_file("~/.osmocom/sip/sip.conf", handle_options);
+ if (rc < 0)
+ return 0;
+ argi = options_command_line(argc, argv, handle_options);
+ if (argi <= 0)
+ return argi;
+
+ sip_init(sofia_debug);
+
+ /* complete remote register and authentication flag */
+ if (register_peer && !local_register && !remote_register)
+ remote_register = 1;
+ if (auth_user && !local_auth && !remote_auth)
+ remote_auth = 1;
+
+ if (!local_peer) {
+ PDEBUG(DSIP, DEBUG_ERROR, "You must specify local SIP peer!\n");
+ return -EINVAL;
+ }
+
+ if (!remote_peer) {
+ PDEBUG(DSIP, DEBUG_ERROR, "You must specify remote SIP peer!\n");
+ return -EINVAL;
+ }
+
+ if (!cc_argc || !!strncasecmp(cc_argv[0], "help", 4)) {
+ sip_ep = sip_endpoint_create(send_no_ringing_after_progress, receive_no_ringing_after_progress, name, local_user, local_peer, remote_user, remote_peer, asserted_id, local_register, remote_register, register_user, register_peer, local_auth, remote_auth, auth_user, auth_password, auth_realm, public_ip, stun_server, register_interval, options_interval, stun_interval, expires);
+ if (!sip_ep) {
+ PDEBUG(DSIP, DEBUG_ERROR, "SIP initializing failed!\n");
+ goto error;
+ }
+ }
+
+ rc = osmo_cc_new(&sip_ep->cc_ep, OSMO_CC_VERSION, name, OSMO_CC_LOCATION_BEYOND_INTERWORKING, cc_message, NULL, sip_ep, cc_argc, cc_argv);
+ if (rc)
+ goto error;
+
+ signal(SIGINT, sighandler);
+ signal(SIGHUP, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, sighandler);
+
+ while (!quit) {
+ int w;
+ process_timer();
+ sip_handle(sip_ep);
+ do {
+ w = 0;
+ w |= osmo_cc_handle();
+ } while (w);
+ usleep(1000);
+ }
+
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTSTP, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGPIPE, SIG_DFL);
+
+error:
+ if (sip_ep) {
+ osmo_cc_delete(&sip_ep->cc_ep);
+ sip_endpoint_destroy(sip_ep);
+ }
+
+ sip_exit();
+
+ return 0;
+}
+
diff --git a/src/sip/sip.c b/src/sip/sip.c
new file mode 100644
index 0000000..c9d4d46
--- /dev/null
+++ b/src/sip/sip.c
@@ -0,0 +1,1905 @@
+/* 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);
+}
+
diff --git a/src/sip/sip.h b/src/sip/sip.h
new file mode 100644
index 0000000..37b8b8f
--- /dev/null
+++ b/src/sip/sip.h
@@ -0,0 +1,115 @@
+
+#include "../libtimer/timer.h"
+#include "../libosmocc/endpoint.h"
+#include <sofia-sip/nua.h>
+#include <sofia-sip/stun.h>
+
+#define STUN_RETRY_TIMER 10
+#define REGISTER_RETRY_TIMER 10
+
+enum reg_state {
+ REGISTER_STATE_NULL = 0,
+ REGISTER_STATE_UNREGISTERED,
+ REGISTER_STATE_REGISTERING,
+ REGISTER_STATE_REGISTERED,
+ REGISTER_STATE_FAILED,
+};
+
+enum stun_state {
+ STUN_STATE_NULL = 0,
+ STUN_STATE_UNRESOLVED,
+ STUN_STATE_RESOLVING,
+ STUN_STATE_RESOLVED,
+ STUN_STATE_FAILED,
+};
+
+enum sip_state {
+ SIP_STATE_IDLE = 0,
+ SIP_STATE_OUT_INVITE, /* invite sent, waiting for replies */
+ SIP_STATE_IN_INVITE, /* invite received, sending replies */
+ SIP_STATE_CONNECT, /* active call */
+ SIP_STATE_OUT_RELEASE, /* outgoing release, sending REL_CNF */
+};
+
+struct sip_call;
+
+typedef struct sip_endpoint {
+ /* setting flags */
+ int send_no_ringing_after_progress;
+ int receive_no_ringing_after_progress;
+
+ /* endpoint */
+ osmo_cc_endpoint_t cc_ep;
+ const char *name;
+ struct sip_call *call_list;
+
+ /* SIP settings */
+ 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;
+
+ /* NAT help */
+ char public_ip[256];
+ const char *stun_server;
+
+ /* timers */
+ int register_interval;
+ int options_interval;
+ int stun_interval;
+ struct timer stun_retry_timer;
+ struct timer register_retry_timer;
+ struct timer register_option_timer;
+
+ /* SIP stack */
+ su_root_t *su_root;
+ nua_t *nua;
+ nua_handle_t *register_handle;
+
+ /* register process */
+ char remote_contact[256];
+ char register_nonce[64];
+ char invite_nonce[64];
+ enum reg_state register_state;
+ enum stun_state stun_state;
+
+ /* stun process */
+ stun_handle_t *stun_handle;
+ su_socket_t stun_socket;
+} sip_endpoint_t;
+
+typedef struct sip_call {
+ struct sip_call *next;
+
+ osmo_cc_call_t *cc_call;
+ uint32_t cc_callref;
+ sip_endpoint_t *sip_ep;
+ enum sip_state state;
+
+ nua_handle_t *nua_handle;
+
+ struct timer invite_option_timer;
+
+ char *sdp_request, *sdp_response;
+ int sdp_sent;
+ int alerting_sent;
+
+} call_t;
+
+void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg);
+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);
+void sip_endpoint_destroy(sip_endpoint_t *sip_ep);
+int sip_init(int debug_level);
+void sip_exit(void);
+void sip_handle(sip_endpoint_t *sip_ep);
+