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