mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-24 16:23:40 +01:00
SIP support
Conflicts: configure configure.ac doc/guide.tex
This commit is contained in:
parent
ec6c58a21c
commit
02e0649d18
50
configure
vendored
50
configure
vendored
@ -1,6 +1,6 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
# Guess values for system-dependent variables and create Makefiles.
|
# Guess values for system-dependent variables and create Makefiles.
|
||||||
# Generated by GNU Autoconf 2.67 for ejabberd community.
|
# Generated by GNU Autoconf 2.67 for ejabberd community 13.12-100-gec6c58a.
|
||||||
#
|
#
|
||||||
# Report bugs to <ejabberd@process-one.net>.
|
# Report bugs to <ejabberd@process-one.net>.
|
||||||
#
|
#
|
||||||
@ -552,8 +552,8 @@ MAKEFLAGS=
|
|||||||
# Identity of this package.
|
# Identity of this package.
|
||||||
PACKAGE_NAME='ejabberd'
|
PACKAGE_NAME='ejabberd'
|
||||||
PACKAGE_TARNAME='ejabberd'
|
PACKAGE_TARNAME='ejabberd'
|
||||||
PACKAGE_VERSION='community'
|
PACKAGE_VERSION='community 13.12-100-gec6c58a'
|
||||||
PACKAGE_STRING='ejabberd community'
|
PACKAGE_STRING='ejabberd community 13.12-100-gec6c58a'
|
||||||
PACKAGE_BUGREPORT='ejabberd@process-one.net'
|
PACKAGE_BUGREPORT='ejabberd@process-one.net'
|
||||||
PACKAGE_URL=''
|
PACKAGE_URL=''
|
||||||
|
|
||||||
@ -561,6 +561,7 @@ ac_default_prefix=/
|
|||||||
ac_subst_vars='LTLIBOBJS
|
ac_subst_vars='LTLIBOBJS
|
||||||
LIBOBJS
|
LIBOBJS
|
||||||
tools
|
tools
|
||||||
|
sip
|
||||||
lager
|
lager
|
||||||
http
|
http
|
||||||
debug
|
debug
|
||||||
@ -672,6 +673,7 @@ enable_iconv
|
|||||||
enable_debug
|
enable_debug
|
||||||
enable_http
|
enable_http
|
||||||
enable_lager
|
enable_lager
|
||||||
|
enable_sip
|
||||||
enable_user
|
enable_user
|
||||||
'
|
'
|
||||||
ac_precious_vars='build_alias
|
ac_precious_vars='build_alias
|
||||||
@ -1222,7 +1224,7 @@ if test "$ac_init_help" = "long"; then
|
|||||||
# Omit some internal or obsolete options to make the list less imposing.
|
# Omit some internal or obsolete options to make the list less imposing.
|
||||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||||
cat <<_ACEOF
|
cat <<_ACEOF
|
||||||
\`configure' configures ejabberd community to adapt to many kinds of systems.
|
\`configure' configures ejabberd community 13.12-100-gec6c58a to adapt to many kinds of systems.
|
||||||
|
|
||||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||||
|
|
||||||
@ -1283,7 +1285,7 @@ fi
|
|||||||
|
|
||||||
if test -n "$ac_init_help"; then
|
if test -n "$ac_init_help"; then
|
||||||
case $ac_init_help in
|
case $ac_init_help in
|
||||||
short | recursive ) echo "Configuration of ejabberd community:";;
|
short | recursive ) echo "Configuration of ejabberd community 13.12-100-gec6c58a:";;
|
||||||
esac
|
esac
|
||||||
cat <<\_ACEOF
|
cat <<\_ACEOF
|
||||||
|
|
||||||
@ -1309,8 +1311,8 @@ Optional Features:
|
|||||||
--enable-pgsql --enable-pam --enable-zlib
|
--enable-pgsql --enable-pam --enable-zlib
|
||||||
--enable-stun --enable-riak --enable-json
|
--enable-stun --enable-riak --enable-json
|
||||||
--enable-iconv --enable-debug --enable-http
|
--enable-iconv --enable-debug --enable-http
|
||||||
--enable-lager --enable-tools (useful for Dialyzer
|
--enable-lager --enable-sip --enable-tools (useful
|
||||||
checks, default: no)
|
for Dialyzer checks, default: no)
|
||||||
--enable-tools build development tools (default: no)
|
--enable-tools build development tools (default: no)
|
||||||
--enable-nif replace some functions with C equivalents. Requires
|
--enable-nif replace some functions with C equivalents. Requires
|
||||||
Erlang R13B04 or higher (default: no)
|
Erlang R13B04 or higher (default: no)
|
||||||
@ -1327,6 +1329,7 @@ Optional Features:
|
|||||||
--enable-http build external HTTP libraries ('ibrowse' and
|
--enable-http build external HTTP libraries ('ibrowse' and
|
||||||
'lhttpc', default: no)
|
'lhttpc', default: no)
|
||||||
--enable-lager enable lager support (default: yes)
|
--enable-lager enable lager support (default: yes)
|
||||||
|
--enable-sip enable SIP support (default: no)
|
||||||
--enable-user[[[=USER]]]
|
--enable-user[[[=USER]]]
|
||||||
allow this system user to start ejabberd (default:
|
allow this system user to start ejabberd (default:
|
||||||
no)
|
no)
|
||||||
@ -1407,7 +1410,7 @@ fi
|
|||||||
test -n "$ac_init_help" && exit $ac_status
|
test -n "$ac_init_help" && exit $ac_status
|
||||||
if $ac_init_version; then
|
if $ac_init_version; then
|
||||||
cat <<\_ACEOF
|
cat <<\_ACEOF
|
||||||
ejabberd configure community
|
ejabberd configure community 13.12-100-gec6c58a
|
||||||
generated by GNU Autoconf 2.67
|
generated by GNU Autoconf 2.67
|
||||||
|
|
||||||
Copyright (C) 2010 Free Software Foundation, Inc.
|
Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
@ -1466,7 +1469,7 @@ cat >config.log <<_ACEOF
|
|||||||
This file contains any messages produced by compilers while
|
This file contains any messages produced by compilers while
|
||||||
running configure, to aid debugging if configure makes a mistake.
|
running configure, to aid debugging if configure makes a mistake.
|
||||||
|
|
||||||
It was created by ejabberd $as_me community, which was
|
It was created by ejabberd $as_me community 13.12-100-gec6c58a, which was
|
||||||
generated by GNU Autoconf 2.67. Invocation command line was
|
generated by GNU Autoconf 2.67. Invocation command line was
|
||||||
|
|
||||||
$ $0 $@
|
$ $0 $@
|
||||||
@ -2526,6 +2529,10 @@ parse(Version) ->
|
|||||||
|
|
||||||
less_or_equal([], []) ->
|
less_or_equal([], []) ->
|
||||||
true;
|
true;
|
||||||
|
less_or_equal([], _Any) ->
|
||||||
|
true;
|
||||||
|
less_or_equal(_Any, []) ->
|
||||||
|
false;
|
||||||
less_or_equal([Left| Rl], [Right| Rr]) ->
|
less_or_equal([Left| Rl], [Right| Rr]) ->
|
||||||
case {Left < Right, Left == Right} of
|
case {Left < Right, Left == Right} of
|
||||||
{true, _} ->
|
{true, _} ->
|
||||||
@ -2608,6 +2615,10 @@ parse(Version) ->
|
|||||||
|
|
||||||
less_or_equal([], []) ->
|
less_or_equal([], []) ->
|
||||||
true;
|
true;
|
||||||
|
less_or_equal([], _Any) ->
|
||||||
|
true;
|
||||||
|
less_or_equal(_Any, []) ->
|
||||||
|
false;
|
||||||
less_or_equal([Left| Rl], [Right| Rr]) ->
|
less_or_equal([Left| Rl], [Right| Rr]) ->
|
||||||
case {Left < Right, Left == Right} of
|
case {Left < Right, Left == Right} of
|
||||||
{true, _} ->
|
{true, _} ->
|
||||||
@ -2858,8 +2869,8 @@ fi
|
|||||||
# Check whether --enable-all was given.
|
# Check whether --enable-all was given.
|
||||||
if test "${enable_all+set}" = set; then :
|
if test "${enable_all+set}" = set; then :
|
||||||
enableval=$enable_all; case "${enableval}" in
|
enableval=$enable_all; case "${enableval}" in
|
||||||
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true stun=true riak=true json=true iconv=true debug=true http=true lager=true tools=true ;;
|
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true stun=true riak=true json=true iconv=true debug=true http=true lager=true sip=true tools=true ;;
|
||||||
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false stun=false riak=false json=false iconv=false debug=false http=false lager=false tools=false ;;
|
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false stun=false riak=false json=false iconv=false debug=false http=false lager=false sip=false tools=false ;;
|
||||||
*) as_fn_error $? "bad value ${enableval} for --enable-all" "$LINENO" 5 ;;
|
*) as_fn_error $? "bad value ${enableval} for --enable-all" "$LINENO" 5 ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
@ -3021,6 +3032,18 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Check whether --enable-sip was given.
|
||||||
|
if test "${enable_sip+set}" = set; then :
|
||||||
|
enableval=$enable_sip; case "${enableval}" in
|
||||||
|
yes) sip=true ;;
|
||||||
|
no) sip=false ;;
|
||||||
|
*) as_fn_error $? "bad value ${enableval} for --enable-sip" "$LINENO" 5 ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
if test "x$sip" = "x"; then sip=false; fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
ac_config_files="$ac_config_files Makefile vars.config src/ejabberd.app.src"
|
ac_config_files="$ac_config_files Makefile vars.config src/ejabberd.app.src"
|
||||||
|
|
||||||
|
|
||||||
@ -3853,6 +3876,7 @@ fi
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cat >confcache <<\_ACEOF
|
cat >confcache <<\_ACEOF
|
||||||
# This file is a shell script that caches the results of configure
|
# This file is a shell script that caches the results of configure
|
||||||
@ -4396,7 +4420,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
|||||||
# report actual input values of CONFIG_FILES etc. instead of their
|
# report actual input values of CONFIG_FILES etc. instead of their
|
||||||
# values after options handling.
|
# values after options handling.
|
||||||
ac_log="
|
ac_log="
|
||||||
This file was extended by ejabberd $as_me community, which was
|
This file was extended by ejabberd $as_me community 13.12-100-gec6c58a, which was
|
||||||
generated by GNU Autoconf 2.67. Invocation command line was
|
generated by GNU Autoconf 2.67. Invocation command line was
|
||||||
|
|
||||||
CONFIG_FILES = $CONFIG_FILES
|
CONFIG_FILES = $CONFIG_FILES
|
||||||
@ -4449,7 +4473,7 @@ _ACEOF
|
|||||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||||
ac_cs_version="\\
|
ac_cs_version="\\
|
||||||
ejabberd config.status community
|
ejabberd config.status community 13.12-100-gec6c58a
|
||||||
configured by $0, generated by GNU Autoconf 2.67,
|
configured by $0, generated by GNU Autoconf 2.67,
|
||||||
with options \\"\$ac_cs_config\\"
|
with options \\"\$ac_cs_config\\"
|
||||||
|
|
||||||
|
15
configure.ac
15
configure.ac
@ -106,10 +106,10 @@ AC_ARG_ENABLE(mssql,
|
|||||||
esac],[db_type=generic])
|
esac],[db_type=generic])
|
||||||
|
|
||||||
AC_ARG_ENABLE(all,
|
AC_ARG_ENABLE(all,
|
||||||
[AC_HELP_STRING([--enable-all], [same as --enable-nif --enable-odbc --enable-mysql --enable-pgsql --enable-pam --enable-zlib --enable-stun --enable-riak --enable-json --enable-iconv --enable-debug --enable-http --enable-lager --enable-tools (useful for Dialyzer checks, default: no)])],
|
[AC_HELP_STRING([--enable-all], [same as --enable-nif --enable-odbc --enable-mysql --enable-pgsql --enable-pam --enable-zlib --enable-stun --enable-riak --enable-json --enable-iconv --enable-debug --enable-http --enable-lager --enable-sip --enable-tools (useful for Dialyzer checks, default: no)])],
|
||||||
[case "${enableval}" in
|
[case "${enableval}" in
|
||||||
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true stun=true riak=true json=true iconv=true debug=true http=true lager=true tools=true ;;
|
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true stun=true riak=true json=true iconv=true debug=true http=true lager=true sip=true tools=true ;;
|
||||||
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false stun=false riak=false json=false iconv=false debug=false http=false lager=false tools=false ;;
|
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false stun=false riak=false json=false iconv=false debug=false http=false lager=false sip=false tools=false ;;
|
||||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
|
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
|
||||||
esac],[])
|
esac],[])
|
||||||
|
|
||||||
@ -217,6 +217,14 @@ AC_ARG_ENABLE(lager,
|
|||||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-lager) ;;
|
*) AC_MSG_ERROR(bad value ${enableval} for --enable-lager) ;;
|
||||||
esac],[if test "x$lager" = "x"; then lager=true; fi])
|
esac],[if test "x$lager" = "x"; then lager=true; fi])
|
||||||
|
|
||||||
|
AC_ARG_ENABLE(sip,
|
||||||
|
[AC_HELP_STRING([--enable-sip], [enable SIP support (default: no)])],
|
||||||
|
[case "${enableval}" in
|
||||||
|
yes) sip=true ;;
|
||||||
|
no) sip=false ;;
|
||||||
|
*) AC_MSG_ERROR(bad value ${enableval} for --enable-sip) ;;
|
||||||
|
esac],[if test "x$sip" = "x"; then sip=false; fi])
|
||||||
|
|
||||||
AC_CONFIG_FILES([Makefile
|
AC_CONFIG_FILES([Makefile
|
||||||
vars.config
|
vars.config
|
||||||
src/ejabberd.app.src])
|
src/ejabberd.app.src])
|
||||||
@ -277,6 +285,7 @@ AC_SUBST(iconv)
|
|||||||
AC_SUBST(debug)
|
AC_SUBST(debug)
|
||||||
AC_SUBST(http)
|
AC_SUBST(http)
|
||||||
AC_SUBST(lager)
|
AC_SUBST(lager)
|
||||||
|
AC_SUBST(sip)
|
||||||
AC_SUBST(tools)
|
AC_SUBST(tools)
|
||||||
|
|
||||||
AC_OUTPUT
|
AC_OUTPUT
|
||||||
|
109
doc/guide.tex
109
doc/guide.tex
@ -93,6 +93,7 @@
|
|||||||
\newcommand{\modsharedroster}{\module{mod\_shared\_roster}}
|
\newcommand{\modsharedroster}{\module{mod\_shared\_roster}}
|
||||||
\newcommand{\modsharedrosterldap}{\module{mod\_shared\_roster\_ldap}}
|
\newcommand{\modsharedrosterldap}{\module{mod\_shared\_roster\_ldap}}
|
||||||
\newcommand{\modsic}{\module{mod\_sic}}
|
\newcommand{\modsic}{\module{mod\_sic}}
|
||||||
|
\newcommand{\modsip}{\module{mod\_sip}}
|
||||||
\newcommand{\modstats}{\module{mod\_stats}}
|
\newcommand{\modstats}{\module{mod\_stats}}
|
||||||
\newcommand{\modtime}{\module{mod\_time}}
|
\newcommand{\modtime}{\module{mod\_time}}
|
||||||
\newcommand{\modvcard}{\module{mod\_vcard}}
|
\newcommand{\modvcard}{\module{mod\_vcard}}
|
||||||
@ -396,6 +397,9 @@ Some options that you may be interested in modifying:
|
|||||||
\titem{--enable-zlib}
|
\titem{--enable-zlib}
|
||||||
Enable Stream Compression (XEP-0138) using zlib.
|
Enable Stream Compression (XEP-0138) using zlib.
|
||||||
|
|
||||||
|
\titem{--enable-sip}
|
||||||
|
Enable SIP support (see section \ref{sip}).
|
||||||
|
|
||||||
\titem{--enable-stun}
|
\titem{--enable-stun}
|
||||||
Enable STUN support (see section \ref{stun}).
|
Enable STUN support (see section \ref{stun}).
|
||||||
|
|
||||||
@ -883,6 +887,10 @@ The available modules, their purpose and the options allowed by each one are:
|
|||||||
(as defined in the Jabber Component Protocol (\xepref{0114}).\\
|
(as defined in the Jabber Component Protocol (\xepref{0114}).\\
|
||||||
Options: \texttt{access}, \texttt{hosts}, \texttt{max\_fsm\_queue},
|
Options: \texttt{access}, \texttt{hosts}, \texttt{max\_fsm\_queue},
|
||||||
\texttt{service\_check\_from}, \texttt{shaper\_rule}
|
\texttt{service\_check\_from}, \texttt{shaper\_rule}
|
||||||
|
\titem{\texttt{ejabberd\_sip}}
|
||||||
|
Handles SIP requests as defined in
|
||||||
|
\footahref{http://tools.ietf.org/html/rfc3261}{RFC 3261}.\\
|
||||||
|
Options: \texttt{certfile}, \texttt{tls}
|
||||||
\titem{\texttt{ejabberd\_stun}}
|
\titem{\texttt{ejabberd\_stun}}
|
||||||
Handles STUN Binding requests as defined in
|
Handles STUN Binding requests as defined in
|
||||||
\footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389}.\\
|
\footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389}.\\
|
||||||
@ -1961,7 +1969,7 @@ listen:
|
|||||||
|
|
||||||
\ejabberd{} is able to act as a stand-alone STUN server
|
\ejabberd{} is able to act as a stand-alone STUN server
|
||||||
(\footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389}). Currently only Binding usage
|
(\footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389}). Currently only Binding usage
|
||||||
is supported. In that role \ejabberd{} helps clients with Jingle ICE (\xepref{0176}) support to discover their external addresses and ports.
|
is supported. In that role \ejabberd{} helps clients with ICE (\footahref{http://tools.ietf.org/html/rfc5245}{RFC 5245}) or Jingle ICE (\xepref{0176}) support to discover their external addresses and ports.
|
||||||
|
|
||||||
You should configure \term{ejabberd\_stun} listening module as described in \ref{listened} section.
|
You should configure \term{ejabberd\_stun} listening module as described in \ref{listened} section.
|
||||||
If \option{certfile} option is defined, \ejabberd{} multiplexes TCP and
|
If \option{certfile} option is defined, \ejabberd{} multiplexes TCP and
|
||||||
@ -2001,6 +2009,61 @@ _stun._tcp IN SRV 0 0 3478 stun.example.com.
|
|||||||
_stuns._tcp IN SRV 0 0 5349 stun.example.com.
|
_stuns._tcp IN SRV 0 0 5349 stun.example.com.
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
|
||||||
|
\makesubsection{sip}{SIP}
|
||||||
|
\ind{options!sip}\ind{sip}
|
||||||
|
|
||||||
|
\ejabberd{} has built-in SIP support. In order to activate it you need to add
|
||||||
|
listeners for it, configure DNS properly and enable \modsip{} for
|
||||||
|
the desired virtual host.
|
||||||
|
|
||||||
|
To add a listener you should configure \term{ejabberd\_sip} listening module as
|
||||||
|
described in \ref{listened} section. If option \option{tls} is specified, option
|
||||||
|
\option{certfile} must be specified as well, otherwise incoming TLS connections would fail.
|
||||||
|
|
||||||
|
Example configuration with standard ports
|
||||||
|
(as per \footahref{http://tools.ietf.org/html/rfc3261}{RFC 3261}):
|
||||||
|
\begin{verbatim}
|
||||||
|
listen:
|
||||||
|
...
|
||||||
|
-
|
||||||
|
port: 5060
|
||||||
|
transport: udp
|
||||||
|
module: ejabberd_sip
|
||||||
|
-
|
||||||
|
port: 5060
|
||||||
|
module: ejabberd_sip
|
||||||
|
-
|
||||||
|
port: 5061
|
||||||
|
module: ejabberd_sip
|
||||||
|
tls: true
|
||||||
|
certfile: "/etc/ejabberd/server.pem"
|
||||||
|
...
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
Note that there is no StartTLS support in SIP and \footahref{http://en.wikipedia.org/wiki/Server\_Name\_Indication}{SNI} support is somewhat tricky, so for TLS you have to configure
|
||||||
|
different virtual hosts on different ports if you have different certificate files for them.
|
||||||
|
|
||||||
|
Next you need to configure DNS SIP records for your virtual domains.
|
||||||
|
Refer to \footahref{http://tools.ietf.org/html/rfc3263}{RFC 3263} for the detailed explanation.
|
||||||
|
Simply put, you should add NAPTR and SRV records for your domains.
|
||||||
|
Skip NAPTR configuration if your DNS provider doesn't support this type of records.
|
||||||
|
It's not fatal, however, highly recommended.
|
||||||
|
|
||||||
|
Example configuration of NAPTR records:
|
||||||
|
\begin{verbatim}
|
||||||
|
example.com IN NAPTR 10 0 "s" "SIPS+D2T" "" _sips._tcp.example.com.
|
||||||
|
example.com IN NAPTR 20 0 "s" "SIP+D2T" "" _sip._tcp.example.com.
|
||||||
|
example.com IN NAPTR 30 0 "s" "SIP+D2U" "" _sip._udp.example.com.
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
Example configuration of SRV records with standard ports
|
||||||
|
(as per \footahref{http://tools.ietf.org/html/rfc3261}{RFC 3261}):
|
||||||
|
\begin{verbatim}
|
||||||
|
_sip._udp IN SRV 0 0 5060 sip.example.com.
|
||||||
|
_sip._tcp IN SRV 0 0 5060 sip.example.com.
|
||||||
|
_sips._tcp IN SRV 0 0 5061 sip.example.com.
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
\makesubsection{includeconfigfile}{Include Additional Configuration Files}
|
\makesubsection{includeconfigfile}{Include Additional Configuration Files}
|
||||||
\ind{options!includeconfigfile}\ind{includeconfigfile}
|
\ind{options!includeconfigfile}\ind{includeconfigfile}
|
||||||
|
|
||||||
@ -2578,6 +2641,7 @@ The following table lists all modules included in \ejabberd{}.
|
|||||||
\hline \ahrefloc{modsharedroster}{\modsharedroster{}} & Shared roster management & \modroster{} \\
|
\hline \ahrefloc{modsharedroster}{\modsharedroster{}} & Shared roster management & \modroster{} \\
|
||||||
\hline \ahrefloc{modsharedrosterldap}{\modsharedrosterldap{}} & LDAP Shared roster management & \modroster{} \\
|
\hline \ahrefloc{modsharedrosterldap}{\modsharedrosterldap{}} & LDAP Shared roster management & \modroster{} \\
|
||||||
\hline \ahrefloc{modsic}{\modsic{}} & Server IP Check (\xepref{0279}) & \\
|
\hline \ahrefloc{modsic}{\modsic{}} & Server IP Check (\xepref{0279}) & \\
|
||||||
|
\hline \ahrefloc{modsip}{\modsip{}} & SIP Registrar/Proxy (\footahref{http://tools.ietf.org/html/rfc3261}{RFC 3261}) & \term{ejabberd\_sip} \\
|
||||||
\hline \ahrefloc{modstats}{\modstats{}} & Statistics Gathering (\xepref{0039}) & \\
|
\hline \ahrefloc{modstats}{\modstats{}} & Statistics Gathering (\xepref{0039}) & \\
|
||||||
\hline \ahrefloc{modtime}{\modtime{}} & Entity Time (\xepref{0202}) & \\
|
\hline \ahrefloc{modtime}{\modtime{}} & Entity Time (\xepref{0202}) & \\
|
||||||
\hline \ahrefloc{modvcard}{\modvcard{}} & vcard-temp (\xepref{0054}) & \\
|
\hline \ahrefloc{modvcard}{\modvcard{}} & vcard-temp (\xepref{0054}) & \\
|
||||||
@ -4618,6 +4682,49 @@ Options:
|
|||||||
\iqdiscitem{\ns{urn:xmpp:sic:0}}
|
\iqdiscitem{\ns{urn:xmpp:sic:0}}
|
||||||
\end{description}
|
\end{description}
|
||||||
|
|
||||||
|
\makesubsection{modsip}{\modsip{}}
|
||||||
|
\ind{modules!\modsip{}}
|
||||||
|
This module adds SIP proxy/registrar support for the corresponding virtual host.
|
||||||
|
Note that it is not enough to just load this module only. You should also configure
|
||||||
|
listeners and DNS records properly. See section \ref{sip} for the full explanation.
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
\begin{verbatim}
|
||||||
|
modules:
|
||||||
|
...
|
||||||
|
mod_sip: {}
|
||||||
|
...
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
Options:
|
||||||
|
\begin{description}
|
||||||
|
\titem{via: [\{type: Type, host: Host, port: Port\}]}\ind{options!via}With
|
||||||
|
this option for every \term{Type} you can specify \term{Host} and \term{Port}
|
||||||
|
to set in \term{Via} header of outgoing SIP messages, where \term{Type} can be
|
||||||
|
\term{udp}, \term{tcp} or \term{tls}. \term{Host} is a string and \term{Port} is
|
||||||
|
a non negative integer. This is useful if you're running your server in a non-standard
|
||||||
|
network topology. Example configuration:
|
||||||
|
\begin{verbatim}
|
||||||
|
modules:
|
||||||
|
...
|
||||||
|
mod_sip:
|
||||||
|
via:
|
||||||
|
-
|
||||||
|
type: tls
|
||||||
|
host: "sip-tls.example.com"
|
||||||
|
port: 5061
|
||||||
|
-
|
||||||
|
type: tcp
|
||||||
|
host: "sip-tcp.example.com"
|
||||||
|
port: 5060
|
||||||
|
-
|
||||||
|
type: udp
|
||||||
|
host: "sip-udp.example.com"
|
||||||
|
port: 5060
|
||||||
|
...
|
||||||
|
\end{verbatim}
|
||||||
|
\end{description}
|
||||||
|
|
||||||
\makesubsection{modstats}{\modstats{}}
|
\makesubsection{modstats}{\modstats{}}
|
||||||
\ind{modules!\modstats{}}\ind{protocols!XEP-0039: Statistics Gathering}\ind{statistics}
|
\ind{modules!\modstats{}}\ind{protocols!XEP-0039: Statistics Gathering}\ind{statistics}
|
||||||
|
|
||||||
|
@ -128,6 +128,7 @@ Moreover, \ejabberd{} comes with a wide range of other state-of-the-art features
|
|||||||
\item \txepref{0060}{Publish-Subscribe} component with support for \txepref{0163}{Personal Eventing via Pubsub}.
|
\item \txepref{0060}{Publish-Subscribe} component with support for \txepref{0163}{Personal Eventing via Pubsub}.
|
||||||
\item Support for web clients: \txepref{0025}{HTTP Polling} and \txepref{0206}{HTTP Binding (BOSH)} services.
|
\item Support for web clients: \txepref{0025}{HTTP Polling} and \txepref{0206}{HTTP Binding (BOSH)} services.
|
||||||
\item IRC transport.
|
\item IRC transport.
|
||||||
|
\item SIP support.
|
||||||
\item Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
\item Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
@ -43,7 +43,13 @@ HiPE = case lists:keysearch(hipe, 1, Cfg) of
|
|||||||
end,
|
end,
|
||||||
|
|
||||||
Includes = [{i, "include"},
|
Includes = [{i, "include"},
|
||||||
{i, filename:join(["deps", "p1_xml", "include"])}],
|
{i, filename:join(["deps", "p1_xml", "include"])}|
|
||||||
|
lists:flatmap(
|
||||||
|
fun({sip, true}) ->
|
||||||
|
[{i, filename:join(["deps", "esip", "include"])}];
|
||||||
|
(_) ->
|
||||||
|
[]
|
||||||
|
end, Cfg)],
|
||||||
|
|
||||||
SrcDirs = lists:foldl(
|
SrcDirs = lists:foldl(
|
||||||
fun({tools, true}, Acc) ->
|
fun({tools, true}, Acc) ->
|
||||||
@ -97,6 +103,8 @@ CfgDeps = lists:flatmap(
|
|||||||
({http, true}) ->
|
({http, true}) ->
|
||||||
[{ibrowse, ".*", {git, "git://github.com/cmullaparthi/ibrowse"}},
|
[{ibrowse, ".*", {git, "git://github.com/cmullaparthi/ibrowse"}},
|
||||||
{lhttpc, ".*", {git, "git://github.com/esl/lhttpc"}}];
|
{lhttpc, ".*", {git, "git://github.com/esl/lhttpc"}}];
|
||||||
|
({sip, true}) ->
|
||||||
|
[{esip, ".*", {git, "git://github.com/processone/p1_sip"}}];
|
||||||
({lager, true}) ->
|
({lager, true}) ->
|
||||||
[{lager, ".*", {git, "git://github.com/basho/lager"}}];
|
[{lager, ".*", {git, "git://github.com/basho/lager"}}];
|
||||||
({lager, false}) ->
|
({lager, false}) ->
|
||||||
@ -112,6 +120,8 @@ CfgPostHooks = lists:flatmap(
|
|||||||
[ConfigureCmd("p1_zlib", "")];
|
[ConfigureCmd("p1_zlib", "")];
|
||||||
({iconv, true}) ->
|
({iconv, true}) ->
|
||||||
[ConfigureCmd("p1_iconv", "")];
|
[ConfigureCmd("p1_iconv", "")];
|
||||||
|
({sip, true}) ->
|
||||||
|
[ConfigureCmd("esip", "")];
|
||||||
(_) ->
|
(_) ->
|
||||||
[]
|
[]
|
||||||
end, Cfg),
|
end, Cfg),
|
||||||
|
@ -151,6 +151,19 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
|||||||
{ok, Socket} ->
|
{ok, Socket} ->
|
||||||
%% Inform my parent that this port was opened succesfully
|
%% Inform my parent that this port was opened succesfully
|
||||||
proc_lib:init_ack({ok, self()}),
|
proc_lib:init_ack({ok, self()}),
|
||||||
|
case erlang:function_exported(Module, udp_init, 2) of
|
||||||
|
true ->
|
||||||
|
case catch Module:udp_init(Socket, Opts) of
|
||||||
|
{'EXIT', _} = Err ->
|
||||||
|
?ERROR_MSG("failed to process callback function "
|
||||||
|
"~p:~s(~p, ~p): ~p",
|
||||||
|
[Module, udp_init, Socket, Opts, Err]);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
udp_recv(Socket, Module, Opts);
|
udp_recv(Socket, Module, Opts);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS)
|
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS)
|
||||||
@ -160,6 +173,19 @@ init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
|||||||
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
||||||
%% Inform my parent that this port was opened succesfully
|
%% Inform my parent that this port was opened succesfully
|
||||||
proc_lib:init_ack({ok, self()}),
|
proc_lib:init_ack({ok, self()}),
|
||||||
|
case erlang:function_exported(Module, tcp_init, 2) of
|
||||||
|
true ->
|
||||||
|
case catch Module:tcp_init(ListenSocket, Opts) of
|
||||||
|
{'EXIT', _} = Err ->
|
||||||
|
?ERROR_MSG("failed to process callback function "
|
||||||
|
"~p:~s(~p, ~p): ~p",
|
||||||
|
[Module, tcp_init, ListenSocket, Opts, Err]);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
%% And now start accepting connection attempts
|
%% And now start accepting connection attempts
|
||||||
accept(ListenSocket, Module, Opts).
|
accept(ListenSocket, Module, Opts).
|
||||||
|
|
||||||
@ -342,6 +368,7 @@ start_listener2(Port, Module, Opts) ->
|
|||||||
%% But it doesn't hurt to attempt to start it for any listener.
|
%% But it doesn't hurt to attempt to start it for any listener.
|
||||||
%% So, it's normal (and harmless) that in most cases this call returns: {error, {already_started, pid()}}
|
%% So, it's normal (and harmless) that in most cases this call returns: {error, {already_started, pid()}}
|
||||||
maybe_start_stun(Module),
|
maybe_start_stun(Module),
|
||||||
|
maybe_start_sip(Module),
|
||||||
start_module_sup(Port, Module),
|
start_module_sup(Port, Module),
|
||||||
start_listener_sup(Port, Module, Opts).
|
start_listener_sup(Port, Module, Opts).
|
||||||
|
|
||||||
@ -463,6 +490,11 @@ maybe_start_stun(ejabberd_stun) ->
|
|||||||
maybe_start_stun(_) ->
|
maybe_start_stun(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
maybe_start_sip(esip_socket) ->
|
||||||
|
ejabberd:start_app(esip);
|
||||||
|
maybe_start_sip(_) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
%%%
|
%%%
|
||||||
%%% Check options
|
%%% Check options
|
||||||
%%%
|
%%%
|
||||||
@ -642,7 +674,11 @@ prepare_ip(IP) when is_binary(IP) ->
|
|||||||
|
|
||||||
prepare_mod(ejabberd_stun) ->
|
prepare_mod(ejabberd_stun) ->
|
||||||
prepare_mod(stun);
|
prepare_mod(stun);
|
||||||
|
prepare_mod(ejabberd_sip) ->
|
||||||
|
prepare_mod(sip);
|
||||||
prepare_mod(stun) ->
|
prepare_mod(stun) ->
|
||||||
stun;
|
stun;
|
||||||
|
prepare_mod(sip) ->
|
||||||
|
esip_socket;
|
||||||
prepare_mod(Mod) when is_atom(Mod) ->
|
prepare_mod(Mod) when is_atom(Mod) ->
|
||||||
Mod.
|
Mod.
|
||||||
|
404
src/mod_sip.erl
Normal file
404
src/mod_sip.erl
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%% @copyright (C) 2014, Evgeny Khramtsov
|
||||||
|
%%% @doc
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%% Created : 21 Apr 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
-module(mod_sip).
|
||||||
|
|
||||||
|
-behaviour(gen_mod).
|
||||||
|
-behaviour(esip).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start/2, stop/1, prepare_request/1, make_response/2,
|
||||||
|
add_certfile/2, add_via/3]).
|
||||||
|
|
||||||
|
%% esip_callbacks
|
||||||
|
-export([data_in/2, data_out/2, message_in/2, message_out/2,
|
||||||
|
request/2, request/3, response/2, locate/1]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("logger.hrl").
|
||||||
|
-include("esip.hrl").
|
||||||
|
|
||||||
|
-record(sip_session, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||||
|
socket = #sip_socket{},
|
||||||
|
timestamp = now() :: erlang:timestamp(),
|
||||||
|
tref = make_ref() :: reference(),
|
||||||
|
expires = 0 :: non_neg_integer()}).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
start(_Host, _Opts) ->
|
||||||
|
ejabberd:start_app(esip),
|
||||||
|
esip:set_config_value(max_server_transactions, 10000),
|
||||||
|
esip:set_config_value(max_client_transactions, 10000),
|
||||||
|
esip:set_config_value(software, <<"ejabberd ", (?VERSION)/binary>>),
|
||||||
|
esip:set_config_value(module, ?MODULE),
|
||||||
|
Spec = {mod_sip_registrar, {mod_sip_registrar, start_link, []},
|
||||||
|
transient, 2000, worker, [mod_sip_registrar]},
|
||||||
|
TmpSupSpec = {mod_sip_proxy_sup,
|
||||||
|
{ejabberd_tmp_sup, start_link,
|
||||||
|
[mod_sip_proxy_sup, mod_sip_proxy]},
|
||||||
|
permanent, infinity, supervisor, [ejabberd_tmp_sup]},
|
||||||
|
supervisor:start_child(ejabberd_sup, Spec),
|
||||||
|
supervisor:start_child(ejabberd_sup, TmpSupSpec),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
stop(_Host) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
data_in(Data, #sip_socket{type = Transport,
|
||||||
|
addr = {MyIP, MyPort},
|
||||||
|
peer = {PeerIP, PeerPort}}) ->
|
||||||
|
?DEBUG(
|
||||||
|
"SIP [~p/in] ~s:~p -> ~s:~p:~n~s",
|
||||||
|
[Transport, inet_parse:ntoa(PeerIP), PeerPort,
|
||||||
|
inet_parse:ntoa(MyIP), MyPort, Data]).
|
||||||
|
|
||||||
|
data_out(Data, #sip_socket{type = Transport,
|
||||||
|
addr = {MyIP, MyPort},
|
||||||
|
peer = {PeerIP, PeerPort}}) ->
|
||||||
|
?DEBUG(
|
||||||
|
"SIP [~p/out] ~s:~p -> ~s:~p:~n~s",
|
||||||
|
[Transport, inet_parse:ntoa(MyIP), MyPort,
|
||||||
|
inet_parse:ntoa(PeerIP), PeerPort, Data]).
|
||||||
|
|
||||||
|
message_in(#sip{type = request, method = M} = Req, SIPSock)
|
||||||
|
when M /= <<"ACK">>, M /= <<"CANCEL">> ->
|
||||||
|
case action(Req, SIPSock) of
|
||||||
|
{relay, _LServer, _Opts} ->
|
||||||
|
ok;
|
||||||
|
Action ->
|
||||||
|
request(Req, SIPSock, undefined, Action)
|
||||||
|
end;
|
||||||
|
message_in(_, _) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
message_out(_, _) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
response(Resp, SIPSock) ->
|
||||||
|
case action(Resp, SIPSock) of
|
||||||
|
{relay, LServer, Opts} ->
|
||||||
|
case esip:split_hdrs('via', Resp#sip.hdrs) of
|
||||||
|
{[_], _} ->
|
||||||
|
ok;
|
||||||
|
{[_MyVia|Vias], TailHdrs} ->
|
||||||
|
%% TODO: check if MyVia is really my Via
|
||||||
|
NewResp = Resp#sip{hdrs = [{'via', Vias}|TailHdrs]},
|
||||||
|
case proplists:get_value(socket, Opts) of
|
||||||
|
undefined ->
|
||||||
|
case esip:connect(NewResp,
|
||||||
|
add_certfile(LServer, Opts)) of
|
||||||
|
{ok, SIPSockOut} ->
|
||||||
|
esip:send(SIPSockOut, NewResp);
|
||||||
|
{error, _} ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
SIPSockOut ->
|
||||||
|
esip:send(SIPSockOut, NewResp)
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
request(#sip{method = <<"ACK">>} = Req, SIPSock) ->
|
||||||
|
case action(Req, SIPSock) of
|
||||||
|
{relay, LServer, Opts} ->
|
||||||
|
Req1 = prepare_request(Req),
|
||||||
|
case esip:connect(Req1, add_certfile(LServer, Opts)) of
|
||||||
|
{ok, SIPSockOut} ->
|
||||||
|
Req2 = add_via(SIPSockOut, LServer, Req1),
|
||||||
|
esip:send(SIPSockOut, Req2);
|
||||||
|
{error, _} = Err ->
|
||||||
|
Err
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
pass
|
||||||
|
end;
|
||||||
|
request(#sip{method = <<"CANCEL">>} = Req, SIPSock) ->
|
||||||
|
case action(Req, SIPSock) of
|
||||||
|
loop ->
|
||||||
|
make_response(Req, #sip{status = 483, type = response});
|
||||||
|
{unsupported, Require} ->
|
||||||
|
make_response(Req, #sip{status = 420,
|
||||||
|
type = response,
|
||||||
|
hdrs = [{'unsupported',
|
||||||
|
Require}]});
|
||||||
|
{relay, LServer, Opts} ->
|
||||||
|
Req1 = prepare_request(Req),
|
||||||
|
case esip:connect(Req1, add_certfile(LServer, Opts)) of
|
||||||
|
{ok, SIPSockOut} ->
|
||||||
|
Req2 = add_via(SIPSockOut, LServer, Req1),
|
||||||
|
esip:send(SIPSockOut, Req2);
|
||||||
|
{error, _} = Err ->
|
||||||
|
Err
|
||||||
|
end,
|
||||||
|
pass;
|
||||||
|
_ ->
|
||||||
|
pass
|
||||||
|
end.
|
||||||
|
|
||||||
|
request(Req, SIPSock, TrID) ->
|
||||||
|
request(Req, SIPSock, TrID, action(Req, SIPSock)).
|
||||||
|
|
||||||
|
request(Req, SIPSock, TrID, Action) ->
|
||||||
|
case Action of
|
||||||
|
to_me ->
|
||||||
|
process(Req, SIPSock);
|
||||||
|
register ->
|
||||||
|
mod_sip_registrar:request(Req, SIPSock);
|
||||||
|
loop ->
|
||||||
|
make_response(Req, #sip{status = 483, type = response});
|
||||||
|
{unsupported, Require} ->
|
||||||
|
make_response(Req, #sip{status = 420,
|
||||||
|
type = response,
|
||||||
|
hdrs = [{'unsupported',
|
||||||
|
Require}]});
|
||||||
|
{relay, LServer, Opts} ->
|
||||||
|
case mod_sip_proxy:start(LServer, Opts) of
|
||||||
|
{ok, Pid} ->
|
||||||
|
mod_sip_proxy:route(Req, SIPSock, TrID, Pid),
|
||||||
|
{mod_sip_proxy, route, [Pid]};
|
||||||
|
Err ->
|
||||||
|
?INFO_MSG("failed to proxy request ~p: ~p", [Req, Err]),
|
||||||
|
Err
|
||||||
|
end;
|
||||||
|
{proxy_auth, Host} ->
|
||||||
|
make_response(
|
||||||
|
Req,
|
||||||
|
#sip{status = 407,
|
||||||
|
type = response,
|
||||||
|
hdrs = [{'proxy-authenticate',
|
||||||
|
make_auth_hdr(Host)}]});
|
||||||
|
{auth, Host} ->
|
||||||
|
make_response(
|
||||||
|
Req,
|
||||||
|
#sip{status = 401,
|
||||||
|
type = response,
|
||||||
|
hdrs = [{'www-authenticate',
|
||||||
|
make_auth_hdr(Host)}]});
|
||||||
|
deny ->
|
||||||
|
make_response(Req, #sip{status = 403,
|
||||||
|
type = response});
|
||||||
|
not_found ->
|
||||||
|
make_response(Req, #sip{status = 480,
|
||||||
|
type = response})
|
||||||
|
end.
|
||||||
|
|
||||||
|
locate(_SIPMsg) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
find(#uri{user = User, host = Host}) ->
|
||||||
|
LUser = jlib:nodeprep(User),
|
||||||
|
LServer = jlib:nameprep(Host),
|
||||||
|
case mod_sip_registrar:find_session(
|
||||||
|
LUser, LServer) of
|
||||||
|
{ok, #sip_session{socket = Sock}} ->
|
||||||
|
{relay, LServer, [{socket, Sock}]};
|
||||||
|
error ->
|
||||||
|
not_found
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
||||||
|
action(#sip{type = response, hdrs = Hdrs}, _SIPSock) ->
|
||||||
|
{_, ToURI, _} = esip:get_hdr('to', Hdrs),
|
||||||
|
{_, FromURI, _} = esip:get_hdr('from', Hdrs),
|
||||||
|
case at_my_host(FromURI) of
|
||||||
|
true ->
|
||||||
|
case at_my_host(ToURI) of
|
||||||
|
true ->
|
||||||
|
find(ToURI);
|
||||||
|
false ->
|
||||||
|
LServer = jlib:nameprep(FromURI#uri.host),
|
||||||
|
{relay, LServer, []}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
case at_my_host(ToURI) of
|
||||||
|
true ->
|
||||||
|
find(ToURI);
|
||||||
|
false ->
|
||||||
|
pass
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
action(#sip{method = <<"REGISTER">>, type = request, hdrs = Hdrs,
|
||||||
|
uri = #uri{user = <<"">>} = URI} = Req, SIPSock) ->
|
||||||
|
case at_my_host(URI) of
|
||||||
|
true ->
|
||||||
|
case esip:get_hdrs('require', Hdrs) of
|
||||||
|
[_|_] = Require ->
|
||||||
|
{unsupported, Require};
|
||||||
|
_ ->
|
||||||
|
{_, ToURI, _} = esip:get_hdr('to', Hdrs),
|
||||||
|
case at_my_host(ToURI) of
|
||||||
|
true ->
|
||||||
|
case check_auth(Req, 'authorization', SIPSock) of
|
||||||
|
true ->
|
||||||
|
register;
|
||||||
|
false ->
|
||||||
|
{auth, ToURI#uri.host}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
deny
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
deny
|
||||||
|
end;
|
||||||
|
action(#sip{method = Method, hdrs = Hdrs, type = request} = Req, SIPSock) ->
|
||||||
|
case esip:get_hdr('max-forwards', Hdrs) of
|
||||||
|
0 when Method == <<"OPTIONS">> ->
|
||||||
|
to_me;
|
||||||
|
0 ->
|
||||||
|
loop;
|
||||||
|
_ ->
|
||||||
|
case esip:get_hdrs('proxy-require', Hdrs) of
|
||||||
|
[_|_] = Require ->
|
||||||
|
{unsupported, Require};
|
||||||
|
_ ->
|
||||||
|
{_, ToURI, _} = esip:get_hdr('to', Hdrs),
|
||||||
|
{_, FromURI, _} = esip:get_hdr('from', Hdrs),
|
||||||
|
case at_my_host(FromURI) of
|
||||||
|
true ->
|
||||||
|
case check_auth(Req, 'proxy-authorization', SIPSock) of
|
||||||
|
true ->
|
||||||
|
case at_my_host(ToURI) of
|
||||||
|
true ->
|
||||||
|
find(ToURI);
|
||||||
|
false ->
|
||||||
|
LServer = jlib:nameprep(FromURI#uri.host),
|
||||||
|
{relay, LServer, []}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
{proxy_auth, FromURI#uri.host}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
case at_my_host(ToURI) of
|
||||||
|
true ->
|
||||||
|
find(ToURI);
|
||||||
|
false ->
|
||||||
|
deny
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
check_auth(#sip{method = <<"CANCEL">>}, _, _SIPSock) ->
|
||||||
|
true;
|
||||||
|
check_auth(#sip{method = Method, hdrs = Hdrs, body = Body}, AuthHdr, _SIPSock) ->
|
||||||
|
|
||||||
|
Issuer = case AuthHdr of
|
||||||
|
'authorization' ->
|
||||||
|
to;
|
||||||
|
'proxy-authorization' ->
|
||||||
|
from
|
||||||
|
end,
|
||||||
|
{_, #uri{user = User, host = Host}, _} = esip:get_hdr(Issuer, Hdrs),
|
||||||
|
LUser = jlib:nodeprep(User),
|
||||||
|
LServer = jlib:nameprep(Host),
|
||||||
|
case lists:filter(
|
||||||
|
fun({_, Params}) ->
|
||||||
|
Username = esip:get_param(<<"username">>, Params),
|
||||||
|
Realm = esip:get_param(<<"realm">>, Params),
|
||||||
|
(LUser == esip:unquote(Username))
|
||||||
|
and (LServer == esip:unquote(Realm))
|
||||||
|
end, esip:get_hdrs(AuthHdr, Hdrs)) of
|
||||||
|
[Auth|_] ->
|
||||||
|
case ejabberd_auth:get_password_s(LUser, LServer) of
|
||||||
|
<<"">> ->
|
||||||
|
false;
|
||||||
|
Password ->
|
||||||
|
esip:check_auth(Auth, Method, Body, Password)
|
||||||
|
end;
|
||||||
|
[] ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
allow() ->
|
||||||
|
[<<"OPTIONS">>, <<"REGISTER">>].
|
||||||
|
|
||||||
|
process(#sip{method = <<"OPTIONS">>} = Req, _) ->
|
||||||
|
make_response(Req, #sip{type = response, status = 200,
|
||||||
|
hdrs = [{'allow', allow()}]});
|
||||||
|
process(#sip{method = <<"REGISTER">>} = Req, _) ->
|
||||||
|
make_response(Req, #sip{type = response, status = 400});
|
||||||
|
process(Req, _) ->
|
||||||
|
make_response(Req, #sip{type = response, status = 405,
|
||||||
|
hdrs = [{'allow', allow()}]}).
|
||||||
|
|
||||||
|
prepare_request(#sip{hdrs = Hdrs1} = Req) ->
|
||||||
|
MF = esip:get_hdr('max-forwards', Hdrs1),
|
||||||
|
Hdrs2 = esip:set_hdr('max-forwards', MF-1, Hdrs1),
|
||||||
|
Hdrs3 = lists:filter(
|
||||||
|
fun({'proxy-authorization', {_, Params}}) ->
|
||||||
|
Realm = esip:unquote(esip:get_param(<<"realm">>, Params)),
|
||||||
|
not is_my_host(jlib:nameprep(Realm));
|
||||||
|
(_) ->
|
||||||
|
true
|
||||||
|
end, Hdrs2),
|
||||||
|
Req#sip{hdrs = Hdrs3}.
|
||||||
|
|
||||||
|
make_auth_hdr(LServer) ->
|
||||||
|
Realm = jlib:nameprep(LServer),
|
||||||
|
{<<"Digest">>, [{<<"realm">>, esip:quote(Realm)},
|
||||||
|
{<<"qop">>, esip:quote(<<"auth">>)},
|
||||||
|
{<<"nonce">>, esip:quote(esip:make_hexstr(20))}]}.
|
||||||
|
|
||||||
|
make_response(Req, Resp) ->
|
||||||
|
esip:make_response(Req, Resp, esip:make_tag()).
|
||||||
|
|
||||||
|
at_my_host(#uri{host = Host}) ->
|
||||||
|
is_my_host(jlib:nameprep(Host)).
|
||||||
|
|
||||||
|
is_my_host(LServer) ->
|
||||||
|
gen_mod:is_loaded(LServer, ?MODULE).
|
||||||
|
|
||||||
|
add_certfile(LServer, Opts) ->
|
||||||
|
case ejabberd_config:get_option({domain_certfile, LServer},
|
||||||
|
fun iolist_to_binary/1) of
|
||||||
|
CertFile when is_binary(CertFile), CertFile /= <<"">> ->
|
||||||
|
[{certfile, CertFile}|Opts];
|
||||||
|
_ ->
|
||||||
|
Opts
|
||||||
|
end.
|
||||||
|
|
||||||
|
add_via(#sip_socket{type = Transport}, LServer, #sip{hdrs = Hdrs} = Req) ->
|
||||||
|
ConfiguredVias = get_configured_vias(LServer),
|
||||||
|
{ViaHost, ViaPort} = proplists:get_value(
|
||||||
|
Transport, ConfiguredVias, {LServer, undefined}),
|
||||||
|
ViaTransport = case Transport of
|
||||||
|
tls -> <<"TLS">>;
|
||||||
|
tcp -> <<"TCP">>;
|
||||||
|
udp -> <<"UDP">>
|
||||||
|
end,
|
||||||
|
Via = #via{transport = ViaTransport,
|
||||||
|
host = ViaHost,
|
||||||
|
port = ViaPort,
|
||||||
|
params = [{<<"branch">>, esip:make_branch()},
|
||||||
|
{<<"rport">>, <<"">>}]},
|
||||||
|
Req#sip{hdrs = [{'via', [Via]}|Hdrs]}.
|
||||||
|
|
||||||
|
get_configured_vias(LServer) ->
|
||||||
|
gen_mod:get_module_opt(
|
||||||
|
LServer, ?MODULE, via,
|
||||||
|
fun(L) ->
|
||||||
|
lists:map(
|
||||||
|
fun(Opts) ->
|
||||||
|
Type = proplists:get_value(type, Opts),
|
||||||
|
Host = proplists:get_value(host, Opts),
|
||||||
|
Port = proplists:get_value(port, Opts),
|
||||||
|
true = (Type == tcp) or (Type == tls) or (Type == udp),
|
||||||
|
true = is_binary(Host) and (Host /= <<"">>),
|
||||||
|
true = (is_integer(Port)
|
||||||
|
and (Port > 0) and (Port < 65536))
|
||||||
|
or (Port == undefined),
|
||||||
|
{Type, {Host, Port}}
|
||||||
|
end, L)
|
||||||
|
end, []).
|
152
src/mod_sip_proxy.erl
Normal file
152
src/mod_sip_proxy.erl
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%% @copyright (C) 2014, Evgeny Khramtsov
|
||||||
|
%%% @doc
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%% Created : 21 Apr 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
-module(mod_sip_proxy).
|
||||||
|
|
||||||
|
-define(GEN_FSM, p1_fsm).
|
||||||
|
-behaviour(?GEN_FSM).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start/2, start_link/2, route/4, route/5]).
|
||||||
|
|
||||||
|
%% gen_fsm callbacks
|
||||||
|
-export([init/1, wait_for_request/2, wait_for_response/2,
|
||||||
|
handle_event/3, handle_sync_event/4,
|
||||||
|
handle_info/3, terminate/3, code_change/4]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("logger.hrl").
|
||||||
|
-include("esip.hrl").
|
||||||
|
|
||||||
|
-define(MAX_REDIRECTS, 5).
|
||||||
|
|
||||||
|
-record(state, {host = <<"">> :: binary(),
|
||||||
|
opts = [] :: [{certfile, binary()}],
|
||||||
|
orig_trid,
|
||||||
|
orig_req :: #sip{},
|
||||||
|
client_trid}).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
start(LServer, Opts) ->
|
||||||
|
supervisor:start_child(mod_sip_proxy_sup, [LServer, Opts]).
|
||||||
|
|
||||||
|
start_link(LServer, Opts) ->
|
||||||
|
?GEN_FSM:start_link(?MODULE, [LServer, Opts], []).
|
||||||
|
|
||||||
|
route(Resp, Req, _SIPSock, TrID, Pid) ->
|
||||||
|
?GEN_FSM:send_event(Pid, {Resp, Req, TrID}).
|
||||||
|
|
||||||
|
route(SIPMsg, _SIPSock, TrID, Pid) ->
|
||||||
|
?GEN_FSM:send_event(Pid, {SIPMsg, TrID}),
|
||||||
|
wait.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% gen_fsm callbacks
|
||||||
|
%%%===================================================================
|
||||||
|
init([Host, Opts]) ->
|
||||||
|
{ok, wait_for_request, #state{opts = Opts, host = Host}}.
|
||||||
|
|
||||||
|
wait_for_request({#sip{type = request} = Req, TrID}, State) ->
|
||||||
|
Opts = mod_sip:add_certfile(State#state.host, State#state.opts),
|
||||||
|
Req1 = mod_sip:prepare_request(Req),
|
||||||
|
case connect(Req1, Opts) of
|
||||||
|
{ok, SIPSocket} ->
|
||||||
|
Req2 = mod_sip:add_via(SIPSocket, State#state.host, Req1),
|
||||||
|
case esip:request(SIPSocket, Req2, {?MODULE, route, [self()]}) of
|
||||||
|
{ok, ClientTrID} ->
|
||||||
|
{next_state, wait_for_response,
|
||||||
|
State#state{orig_trid = TrID,
|
||||||
|
orig_req = Req,
|
||||||
|
client_trid = ClientTrID}};
|
||||||
|
Err ->
|
||||||
|
{Status, Reason} = esip:error_status(Err),
|
||||||
|
esip:reply(TrID, mod_sip:make_response(
|
||||||
|
Req, #sip{type = response,
|
||||||
|
status = Status,
|
||||||
|
reason = Reason})),
|
||||||
|
{stop, normal, State}
|
||||||
|
end;
|
||||||
|
Err ->
|
||||||
|
{Status, Reason} = esip:error_status(Err),
|
||||||
|
esip:reply(TrID, mod_sip:make_response(
|
||||||
|
Req, #sip{type = response,
|
||||||
|
status = Status,
|
||||||
|
reason = Reason})),
|
||||||
|
{stop, normal, State}
|
||||||
|
end;
|
||||||
|
wait_for_request(_Event, State) ->
|
||||||
|
{next_state, wait_for_request, State}.
|
||||||
|
|
||||||
|
wait_for_response({#sip{method = <<"CANCEL">>, type = request}, _TrID}, State) ->
|
||||||
|
esip:cancel(State#state.client_trid),
|
||||||
|
{next_state, wait_for_response, State};
|
||||||
|
wait_for_response({Resp, _TrID}, State) ->
|
||||||
|
case Resp of
|
||||||
|
{error, _} ->
|
||||||
|
Req = State#state.orig_req,
|
||||||
|
{Status, Reason} = esip:error_status(Resp),
|
||||||
|
case Status of
|
||||||
|
408 when Req#sip.method /= <<"INVITE">> ->
|
||||||
|
%% Absorb useless 408. See RFC4320
|
||||||
|
esip:stop_transaction(State#state.orig_trid);
|
||||||
|
_ ->
|
||||||
|
ErrResp = mod_sip:make_response(
|
||||||
|
Req,
|
||||||
|
#sip{type = response,
|
||||||
|
status = Status,
|
||||||
|
reason = Reason}),
|
||||||
|
esip:reply(State#state.orig_trid, ErrResp)
|
||||||
|
end,
|
||||||
|
{stop, normal, State};
|
||||||
|
#sip{status = 100} ->
|
||||||
|
{next_state, wait_for_response, State};
|
||||||
|
#sip{status = Status} ->
|
||||||
|
case esip:split_hdrs('via', Resp#sip.hdrs) of
|
||||||
|
{[_], _} ->
|
||||||
|
{stop, normal, State};
|
||||||
|
{[_|Vias], NewHdrs} ->
|
||||||
|
esip:reply(State#state.orig_trid,
|
||||||
|
Resp#sip{hdrs = [{'via', Vias}|NewHdrs]}),
|
||||||
|
if Status < 200 ->
|
||||||
|
{next_state, wait_for_response, State};
|
||||||
|
true ->
|
||||||
|
{stop, normal, State}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
wait_for_response(_Event, State) ->
|
||||||
|
{next_state, wait_for_response, State}.
|
||||||
|
|
||||||
|
handle_event(_Event, StateName, State) ->
|
||||||
|
{next_state, StateName, State}.
|
||||||
|
|
||||||
|
handle_sync_event(_Event, _From, StateName, State) ->
|
||||||
|
Reply = ok,
|
||||||
|
{reply, Reply, StateName, State}.
|
||||||
|
|
||||||
|
handle_info(_Info, StateName, State) ->
|
||||||
|
{next_state, StateName, State}.
|
||||||
|
|
||||||
|
terminate(_Reason, _StateName, _State) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
code_change(_OldVsn, StateName, State, _Extra) ->
|
||||||
|
{ok, StateName, State}.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
||||||
|
connect(Req, Opts) ->
|
||||||
|
case proplists:get_value(socket, Opts) of
|
||||||
|
undefined ->
|
||||||
|
esip:connect(Req, Opts);
|
||||||
|
#sip_socket{} = SIPSock ->
|
||||||
|
{ok, SIPSock}
|
||||||
|
end.
|
196
src/mod_sip_registrar.erl
Normal file
196
src/mod_sip_registrar.erl
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%% @copyright (C) 2014, Evgeny Khramtsov
|
||||||
|
%%% @doc
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%% Created : 23 Apr 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
-module(mod_sip_registrar).
|
||||||
|
|
||||||
|
-define(GEN_SERVER, p1_server).
|
||||||
|
-behaviour(?GEN_SERVER).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start_link/0, request/2, find_session/2]).
|
||||||
|
|
||||||
|
%% gen_server callbacks
|
||||||
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("logger.hrl").
|
||||||
|
-include("esip.hrl").
|
||||||
|
|
||||||
|
-record(sip_session, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||||
|
socket = #sip_socket{},
|
||||||
|
timestamp = now() :: erlang:timestamp(),
|
||||||
|
tref = make_ref() :: reference(),
|
||||||
|
expires = 0 :: non_neg_integer()}).
|
||||||
|
|
||||||
|
-record(state, {}).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
start_link() ->
|
||||||
|
?GEN_SERVER:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
request(#sip{hdrs = Hdrs} = Req, SIPSock) ->
|
||||||
|
{_, #uri{user = U, host = S}, _} = esip:get_hdr('to', Hdrs),
|
||||||
|
LUser = jlib:nodeprep(U),
|
||||||
|
LServer = jlib:nameprep(S),
|
||||||
|
{PeerIP, _} = SIPSock#sip_socket.peer,
|
||||||
|
US = {LUser, LServer},
|
||||||
|
Expires = esip:get_hdr('expires', Hdrs, 0),
|
||||||
|
case esip:get_hdrs('contact', Hdrs) of
|
||||||
|
[<<"*">>] when Expires == 0 ->
|
||||||
|
?INFO_MSG("unregister SIP session for user ~s@~s from ~s",
|
||||||
|
[LUser, LServer, inet_parse:ntoa(PeerIP)]),
|
||||||
|
unregister_session(US),
|
||||||
|
mod_sip:make_response(Req, #sip{type = response, status = 200});
|
||||||
|
[{_, _URI, _Params}|_] = Contacts ->
|
||||||
|
ContactsWithExpires =
|
||||||
|
lists:map(
|
||||||
|
fun({Name, URI, Params}) ->
|
||||||
|
Exp = case to_integer(
|
||||||
|
esip:get_param(
|
||||||
|
<<"expires">>, Params),
|
||||||
|
0, (1 bsl 32)-1) of
|
||||||
|
{ok, E} -> E;
|
||||||
|
_ -> Expires
|
||||||
|
end,
|
||||||
|
NewParams = esip:set_param(
|
||||||
|
<<"expires">>,
|
||||||
|
erlang:integer_to_binary(Exp),
|
||||||
|
Params),
|
||||||
|
{Exp, {Name, URI, NewParams}}
|
||||||
|
end, Contacts),
|
||||||
|
[{Expires1, _}|_] = lists:keysort(1, ContactsWithExpires),
|
||||||
|
MinExpires = min_expires(),
|
||||||
|
if Expires1 >= MinExpires ->
|
||||||
|
?INFO_MSG("register SIP session for user ~s@~s from ~s",
|
||||||
|
[LUser, LServer, inet_parse:ntoa(PeerIP)]),
|
||||||
|
register_session(US, SIPSock, Expires1),
|
||||||
|
mod_sip:make_response(
|
||||||
|
Req,
|
||||||
|
#sip{type = response,
|
||||||
|
status = 200,
|
||||||
|
hdrs = [{'contact',
|
||||||
|
[C || {_, C} <- ContactsWithExpires]}]});
|
||||||
|
Expires1 > 0, Expires1 < MinExpires ->
|
||||||
|
mod_sip:make_response(
|
||||||
|
Req, #sip{type = response,
|
||||||
|
status = 423,
|
||||||
|
hdrs = [{'min-expires', MinExpires}]});
|
||||||
|
true ->
|
||||||
|
?INFO_MSG("unregister SIP session for user ~s@~s from ~s",
|
||||||
|
[LUser, LServer, inet_parse:ntoa(PeerIP)]),
|
||||||
|
unregister_session(US),
|
||||||
|
mod_sip:make_response(
|
||||||
|
Req,
|
||||||
|
#sip{type = response, status = 200,
|
||||||
|
hdrs = [{'contact',
|
||||||
|
[C || {_, C} <- ContactsWithExpires]}]})
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
mod_sip:make_response(Req, #sip{type = response, status = 400})
|
||||||
|
end.
|
||||||
|
|
||||||
|
find_session(U, S) ->
|
||||||
|
case mnesia:dirty_read(sip_session, {U, S}) of
|
||||||
|
[Session] ->
|
||||||
|
{ok, Session};
|
||||||
|
[] ->
|
||||||
|
error
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% gen_server callbacks
|
||||||
|
%%%===================================================================
|
||||||
|
init([]) ->
|
||||||
|
mnesia:create_table(sip_session,
|
||||||
|
[{ram_copies, [node()]},
|
||||||
|
{attributes, record_info(fields, sip_session)}]),
|
||||||
|
mnesia:add_table_copy(sip_session, node(), ram_copies),
|
||||||
|
{ok, #state{}}.
|
||||||
|
|
||||||
|
handle_call({write, Session}, _From, State) ->
|
||||||
|
Res = write_session(Session),
|
||||||
|
{reply, Res, State};
|
||||||
|
handle_call({delete, US}, _From, State) ->
|
||||||
|
Res = delete_session(US),
|
||||||
|
{reply, Res, State};
|
||||||
|
handle_call(_Request, _From, State) ->
|
||||||
|
Reply = ok,
|
||||||
|
{reply, Reply, State}.
|
||||||
|
|
||||||
|
handle_cast(_Msg, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
handle_info({write, Session}, State) ->
|
||||||
|
write_session(Session),
|
||||||
|
{noreply, State};
|
||||||
|
handle_info({delete, US}, State) ->
|
||||||
|
delete_session(US),
|
||||||
|
{noreply, State};
|
||||||
|
handle_info({timeout, TRef, US}, State) ->
|
||||||
|
case mnesia:dirty_read(sip_session, US) of
|
||||||
|
[#sip_session{tref = TRef}] ->
|
||||||
|
mnesia:dirty_delete(sip_session, US);
|
||||||
|
[] ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
{noreply, State};
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
?ERROR_MSG("got unexpected info: ~p", [_Info]),
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
terminate(_Reason, _State) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
||||||
|
register_session(US, SIPSocket, Expires) ->
|
||||||
|
Session = #sip_session{us = US,
|
||||||
|
socket = SIPSocket,
|
||||||
|
timestamp = now(),
|
||||||
|
expires = Expires},
|
||||||
|
gen_server:call(?MODULE, {write, Session}).
|
||||||
|
|
||||||
|
unregister_session(US) ->
|
||||||
|
gen_server:call(?MODULE, {delete, US}).
|
||||||
|
|
||||||
|
write_session(#sip_session{us = US, expires = Expires} = Session) ->
|
||||||
|
case mnesia:dirty_read(sip_session, US) of
|
||||||
|
[#sip_session{tref = TRef}] ->
|
||||||
|
erlang:cancel_timer(TRef);
|
||||||
|
[] ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
NewTRef = erlang:start_timer(Expires * 1000, self(), US),
|
||||||
|
mnesia:dirty_write(Session#sip_session{tref = NewTRef}).
|
||||||
|
|
||||||
|
delete_session(US) ->
|
||||||
|
case mnesia:dirty_read(sip_session, US) of
|
||||||
|
[#sip_session{tref = TRef}] ->
|
||||||
|
erlang:cancel_timer(TRef),
|
||||||
|
mnesia:dirty_delete(sip_session, US);
|
||||||
|
[] ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
min_expires() ->
|
||||||
|
60.
|
||||||
|
|
||||||
|
to_integer(Bin, Min, Max) ->
|
||||||
|
case catch list_to_integer(binary_to_list(Bin)) of
|
||||||
|
N when N >= Min, N =< Max ->
|
||||||
|
{ok, N};
|
||||||
|
_ ->
|
||||||
|
error
|
||||||
|
end.
|
@ -28,6 +28,7 @@
|
|||||||
{json, @json@}.
|
{json, @json@}.
|
||||||
{http, @http@}.
|
{http, @http@}.
|
||||||
{lager, @lager@}.
|
{lager, @lager@}.
|
||||||
|
{sip, @sip@}.
|
||||||
{iconv, @iconv@}.
|
{iconv, @iconv@}.
|
||||||
|
|
||||||
%% Version
|
%% Version
|
||||||
|
Loading…
Reference in New Issue
Block a user