aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README38
l---------README.md1
-rwxr-xr-xconfigure52
-rw-r--r--configure.ac15
-rw-r--r--doc/guide.tex109
-rw-r--r--doc/introduction.tex1
-rw-r--r--rebar.config.script12
-rw-r--r--src/ejabberd_auth_odbc.erl1
-rw-r--r--src/ejabberd_c2s.erl6
-rw-r--r--src/ejabberd_ctl.erl1
-rw-r--r--src/ejabberd_listener.erl36
-rw-r--r--src/ejabberd_odbc.erl9
-rw-r--r--src/ejabberd_odbc_sup.erl6
-rw-r--r--src/ejabberd_s2s.erl154
-rw-r--r--src/ejabberd_s2s_in.erl185
-rw-r--r--src/ejabberd_s2s_out.erl106
-rw-r--r--src/ejabberd_sm.erl17
-rw-r--r--src/ejabberd_xmlrpc.erl6
-rw-r--r--src/mod_register_web.erl18
-rw-r--r--src/mod_sip.erl404
-rw-r--r--src/mod_sip_proxy.erl152
-rw-r--r--src/mod_sip_registrar.erl196
-rw-r--r--vars.config.in1
23 files changed, 1261 insertions, 265 deletions
diff --git a/README b/README
index d6b95bcda..502089552 100644
--- a/README
+++ b/README
@@ -1,11 +1,15 @@
ejabberd - High-Performance Enterprise Instant Messaging Server
+---------------------------------------------------------------
Quickstart guide
+================
0. Requirements
+---------------
To compile ejabberd you need:
+
- GNU Make
- GCC
- Libexpat 1.95 or higher
@@ -18,35 +22,41 @@ To compile ejabberd you need:
- GNU Iconv 1.8 or higher, for the IRC Transport
(mod_irc). Optional. Not needed on systems with GNU Libc.
- ImageMagick's Convert program. Optional. For CAPTCHA challenges.
- - exmpp 0.9.6 or higher. Optional. For import/export XEP-0227 files.
1. Compile and install on *nix systems
+--------------------------------------
To compile ejabberd execute the commands:
- ./configure
- make
+
+ ./configure
+ make
To install ejabberd, run this command with system administrator rights
(root user):
- sudo make install
+ sudo make install
These commands will:
- - Install the configuration files in /etc/ejabberd/
- - Install ejabberd binary, header and runtime files in /lib/ejabberd/
- - Install the administration script: /sbin/ejabberdctl
- - Install ejabberd documentation in /share/doc/ejabberd/
- - Create a spool directory: /var/lib/ejabberd/
- - Create a directory for log files: /var/log/ejabberd/
+
+ - Install the configuration files in `/etc/ejabberd/`
+ - Install ejabberd binary, header and runtime files in `/lib/ejabberd/`
+ - Install the administration script: `/sbin/ejabberdctl`
+ - Install ejabberd documentation in `/share/doc/ejabberd/`
+ - Create a spool directory: `/var/lib/ejabberd/`
+ - Create a directory for log files: `/var/log/ejabberd/`
2. Start ejabberd
+-----------------
-You can use the ejabberdctl command line administration script to
+You can use the `ejabberdctl` command line administration script to
start and stop ejabberd. For example:
- ejabberdctl start
+ ejabberdctl start
+
+
+For detailed information please refer to the [ejabberd Installation and
+Operation Guide][1].
-For detailed information please refer to the
-ejabberd Installation and Operation Guide
+[1]: http://www.process-one.net/docs/ejabberd/guide_en.html
diff --git a/README.md b/README.md
new file mode 120000
index 000000000..100b93820
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+README \ No newline at end of file
diff --git a/configure b/configure
index 4114b2e69..e26d6e894 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# 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>.
#
@@ -552,8 +552,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='ejabberd'
PACKAGE_TARNAME='ejabberd'
-PACKAGE_VERSION='community'
-PACKAGE_STRING='ejabberd community'
+PACKAGE_VERSION='community 13.12-100-gec6c58a'
+PACKAGE_STRING='ejabberd community 13.12-100-gec6c58a'
PACKAGE_BUGREPORT='ejabberd@process-one.net'
PACKAGE_URL=''
@@ -561,6 +561,7 @@ ac_default_prefix=/
ac_subst_vars='LTLIBOBJS
LIBOBJS
tools
+sip
lager
http
debug
@@ -672,6 +673,7 @@ enable_iconv
enable_debug
enable_http
enable_lager
+enable_sip
enable_user
'
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.
# This message is too long to be a string in the A/UX 3.1 sh.
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]...
@@ -1283,7 +1285,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of ejabberd community:";;
+ short | recursive ) echo "Configuration of ejabberd community 13.12-100-gec6c58a:";;
esac
cat <<\_ACEOF
@@ -1309,8 +1311,8 @@ Optional Features:
--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)
+ --enable-lager --enable-sip --enable-tools (useful
+ for Dialyzer checks, default: no)
--enable-tools build development tools (default: no)
--enable-nif replace some functions with C equivalents. Requires
Erlang R13B04 or higher (default: no)
@@ -1327,6 +1329,7 @@ Optional Features:
--enable-http build external HTTP libraries ('ibrowse' and
'lhttpc', default: no)
--enable-lager enable lager support (default: yes)
+ --enable-sip enable SIP support (default: no)
--enable-user[[[=USER]]]
allow this system user to start ejabberd (default:
no)
@@ -1407,7 +1410,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-ejabberd configure community
+ejabberd configure community 13.12-100-gec6c58a
generated by GNU Autoconf 2.67
Copyright (C) 2010 Free Software Foundation, Inc.
@@ -1466,7 +1469,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
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
$ $0 $@
@@ -2480,7 +2483,7 @@ if test "${enable_erlang_version_check+set}" = set; then :
enableval=$enable_erlang_version_check;
fi
- case "$enable_erlang_version_check" in
+case "$enable_erlang_version_check" in
yes|'')
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Erlang/OTP version" >&5
$as_echo_n "checking Erlang/OTP version... " >&6; }
@@ -2526,6 +2529,10 @@ parse(Version) ->
less_or_equal([], []) ->
true;
+less_or_equal([], _Any) ->
+ true;
+less_or_equal(_Any, []) ->
+ false;
less_or_equal([Left| Rl], [Right| Rr]) ->
case {Left < Right, Left == Right} of
{true, _} ->
@@ -2608,6 +2615,10 @@ parse(Version) ->
less_or_equal([], []) ->
true;
+less_or_equal([], _Any) ->
+ true;
+less_or_equal(_Any, []) ->
+ false;
less_or_equal([Left| Rl], [Right| Rr]) ->
case {Left < Right, Left == Right} of
{true, _} ->
@@ -2858,8 +2869,8 @@ fi
# Check whether --enable-all was given.
if test "${enable_all+set}" = set; then :
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 ;;
- 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 ;;
+ 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 sip=false tools=false ;;
*) as_fn_error $? "bad value ${enableval} for --enable-all" "$LINENO" 5 ;;
esac
fi
@@ -3021,6 +3032,18 @@ else
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"
@@ -3854,6 +3877,7 @@ fi
+
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
# tests run on this system so they can be shared between configure
@@ -4396,7 +4420,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
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
CONFIG_FILES = $CONFIG_FILES
@@ -4449,7 +4473,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-ejabberd config.status community
+ejabberd config.status community 13.12-100-gec6c58a
configured by $0, generated by GNU Autoconf 2.67,
with options \\"\$ac_cs_config\\"
diff --git a/configure.ac b/configure.ac
index 853d91ecb..6cb0580f9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -106,10 +106,10 @@ AC_ARG_ENABLE(mssql,
esac],[db_type=generic])
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
- 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 ;;
- 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 ;;
+ 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 sip=false tools=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
esac],[])
@@ -217,6 +217,14 @@ AC_ARG_ENABLE(lager,
*) AC_MSG_ERROR(bad value ${enableval} for --enable-lager) ;;
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
vars.config
src/ejabberd.app.src])
@@ -277,6 +285,7 @@ AC_SUBST(iconv)
AC_SUBST(debug)
AC_SUBST(http)
AC_SUBST(lager)
+AC_SUBST(sip)
AC_SUBST(tools)
AC_OUTPUT
diff --git a/doc/guide.tex b/doc/guide.tex
index 21e66a084..488ab6a3d 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -93,6 +93,7 @@
\newcommand{\modsharedroster}{\module{mod\_shared\_roster}}
\newcommand{\modsharedrosterldap}{\module{mod\_shared\_roster\_ldap}}
\newcommand{\modsic}{\module{mod\_sic}}
+\newcommand{\modsip}{\module{mod\_sip}}
\newcommand{\modstats}{\module{mod\_stats}}
\newcommand{\modtime}{\module{mod\_time}}
\newcommand{\modvcard}{\module{mod\_vcard}}
@@ -396,6 +397,9 @@ Some options that you may be interested in modifying:
\titem{--enable-zlib}
Enable Stream Compression (XEP-0138) using zlib.
+ \titem{--enable-sip}
+ Enable SIP support (see section \ref{sip}).
+
\titem{--enable-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}).\\
Options: \texttt{access}, \texttt{hosts}, \texttt{max\_fsm\_queue},
\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}}
Handles STUN Binding requests as defined in
\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
(\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.
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.
\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}
\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{modsharedrosterldap}{\modsharedrosterldap{}} & LDAP Shared roster management & \modroster{} \\
\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{modtime}{\modtime{}} & Entity Time (\xepref{0202}) & \\
\hline \ahrefloc{modvcard}{\modvcard{}} & vcard-temp (\xepref{0054}) & \\
@@ -4618,6 +4682,49 @@ Options:
\iqdiscitem{\ns{urn:xmpp:sic:0}}
\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{}}
\ind{modules!\modstats{}}\ind{protocols!XEP-0039: Statistics Gathering}\ind{statistics}
diff --git a/doc/introduction.tex b/doc/introduction.tex
index 163312b38..fee27048c 100644
--- a/doc/introduction.tex
+++ b/doc/introduction.tex
@@ -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 Support for web clients: \txepref{0025}{HTTP Polling} and \txepref{0206}{HTTP Binding (BOSH)} services.
\item IRC transport.
+\item SIP support.
\item Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
\end{itemize}
\end{itemize}
diff --git a/rebar.config.script b/rebar.config.script
index 7a5e332c4..dc68d1d30 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -43,7 +43,13 @@ HiPE = case lists:keysearch(hipe, 1, Cfg) of
end,
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(
fun({tools, true}, Acc) ->
@@ -97,6 +103,8 @@ CfgDeps = lists:flatmap(
({http, true}) ->
[{ibrowse, ".*", {git, "git://github.com/cmullaparthi/ibrowse"}},
{lhttpc, ".*", {git, "git://github.com/esl/lhttpc"}}];
+ ({sip, true}) ->
+ [{esip, ".*", {git, "git://github.com/processone/p1_sip"}}];
({lager, true}) ->
[{lager, ".*", {git, "git://github.com/basho/lager"}}];
({lager, false}) ->
@@ -112,6 +120,8 @@ CfgPostHooks = lists:flatmap(
[ConfigureCmd("p1_zlib", "")];
({iconv, true}) ->
[ConfigureCmd("p1_iconv", "")];
+ ({sip, true}) ->
+ [ConfigureCmd("esip", "")];
(_) ->
[]
end, Cfg),
diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl
index 57cef930a..27e89aa2b 100644
--- a/src/ejabberd_auth_odbc.erl
+++ b/src/ejabberd_auth_odbc.erl
@@ -212,6 +212,7 @@ is_user_exists(User, Server) ->
true; %% Account exists
{selected, [<<"password">>], []} ->
false; %% Account does not exist
+ {error, unknownhost} -> false;
{error, Error} -> {error, Error}
catch
_:B -> {error, B}
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 2cdf205cc..f2e16e15b 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -107,7 +107,7 @@
auth_module = unknown,
ip,
aux_fields = [],
- lang}).
+ lang = <<"">>}).
%-define(DBGFSM, true).
@@ -1228,6 +1228,10 @@ handle_info(replaced, StateName, StateData) ->
Lang = StateData#state.lang,
Xmlelement = ?SERRT_CONFLICT(Lang, <<"Replaced by new connection">>),
handle_info({kick, replaced, Xmlelement}, StateName, StateData);
+handle_info(disconnect, StateName, StateData) ->
+ Lang = StateData#state.lang,
+ Xmlelement = ?SERRT_POLICY_VIOLATION(Lang, <<"has been kicked">>),
+ handle_info({kick, kicked_by_admin, Xmlelement}, StateName, StateData);
handle_info({kick, Reason, Xmlelement}, _StateName, StateData) ->
send_element(StateData, Xmlelement),
send_trailer(StateData),
diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl
index d8d067640..eda1c4970 100644
--- a/src/ejabberd_ctl.erl
+++ b/src/ejabberd_ctl.erl
@@ -418,6 +418,7 @@ is_supported_args(Args) ->
fun({_Name, Format}) ->
(Format == integer)
or (Format == string)
+ or (Format == binary)
end,
Args).
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index 2051afdb2..71f744077 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -151,6 +151,19 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
{ok, Socket} ->
%% Inform my parent that this port was opened succesfully
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);
{error, Reason} ->
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),
%% Inform my parent that this port was opened succesfully
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
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.
%% So, it's normal (and harmless) that in most cases this call returns: {error, {already_started, pid()}}
maybe_start_stun(Module),
+ maybe_start_sip(Module),
start_module_sup(Port, Module),
start_listener_sup(Port, Module, Opts).
@@ -463,6 +490,11 @@ maybe_start_stun(ejabberd_stun) ->
maybe_start_stun(_) ->
ok.
+maybe_start_sip(esip_socket) ->
+ ejabberd:start_app(esip);
+maybe_start_sip(_) ->
+ ok.
+
%%%
%%% Check options
%%%
@@ -642,7 +674,11 @@ prepare_ip(IP) when is_binary(IP) ->
prepare_mod(ejabberd_stun) ->
prepare_mod(stun);
+prepare_mod(ejabberd_sip) ->
+ prepare_mod(sip);
prepare_mod(stun) ->
stun;
+prepare_mod(sip) ->
+ esip_socket;
prepare_mod(Mod) when is_atom(Mod) ->
Mod.
diff --git a/src/ejabberd_odbc.erl b/src/ejabberd_odbc.erl
index 6a7d9de6d..df9a4c398 100644
--- a/src/ejabberd_odbc.erl
+++ b/src/ejabberd_odbc.erl
@@ -140,9 +140,12 @@ sql_bloc(Host, F) -> sql_call(Host, {sql_bloc, F}).
sql_call(Host, Msg) ->
case get(?STATE_KEY) of
undefined ->
- (?GEN_FSM):sync_send_event(ejabberd_odbc_sup:get_random_pid(Host),
- {sql_cmd, Msg, now()},
- ?TRANSACTION_TIMEOUT);
+ case ejabberd_odbc_sup:get_random_pid(Host) of
+ none -> {error, unknownhost};
+ Pid ->
+ (?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg, now()},
+ ?TRANSACTION_TIMEOUT)
+ end;
_State -> nested_op(Msg)
end.
diff --git a/src/ejabberd_odbc_sup.erl b/src/ejabberd_odbc_sup.erl
index bfad5428e..d05fd139e 100644
--- a/src/ejabberd_odbc_sup.erl
+++ b/src/ejabberd_odbc_sup.erl
@@ -82,8 +82,10 @@ get_pids(Host) ->
[R#sql_pool.pid || R <- Rs].
get_random_pid(Host) ->
- Pids = get_pids(Host),
- lists:nth(erlang:phash(now(), length(Pids)), Pids).
+ case get_pids(Host) of
+ [] -> none;
+ Pids -> lists:nth(erlang:phash(now(), length(Pids)), Pids)
+ end.
add_pid(Host, Pid) ->
F = fun () ->
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index 057c60a98..eb9894350 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -37,7 +37,8 @@
incoming_s2s_number/0, outgoing_s2s_number/0,
clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0,
- external_host_overloaded/1, is_temporarly_blocked/1]).
+ external_host_overloaded/1, is_temporarly_blocked/1,
+ check_peer_certificate/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
@@ -53,6 +54,14 @@
-include("ejabberd_commands.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+-define(PKIXEXPLICIT, 'OTP-PUB-KEY').
+
+-define(PKIXIMPLICIT, 'OTP-PUB-KEY').
+
+-include("XmppAddr.hrl").
+
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
@@ -207,6 +216,31 @@ try_register(FromTo) ->
dirty_get_connections() ->
mnesia:dirty_all_keys(s2s).
+check_peer_certificate(SockMod, Sock, Peer) ->
+ case SockMod:get_peer_certificate(Sock) of
+ {ok, Cert} ->
+ case SockMod:get_verify_result(Sock) of
+ 0 ->
+ case idna:domain_utf8_to_ascii(Peer) of
+ false ->
+ {error, <<"Cannot decode remote server name">>};
+ AsciiPeer ->
+ case
+ lists:any(fun(D) -> match_domain(AsciiPeer, D) end,
+ get_cert_domains(Cert)) of
+ true ->
+ {ok, <<"Verification successful">>};
+ false ->
+ {error, <<"Certificate host name mismatch">>}
+ end
+ end;
+ VerifyRes ->
+ {error, p1_tls:get_cert_verify_string(VerifyRes, Cert)}
+ end;
+ error ->
+ {error, <<"Cannot get peer certificate">>}
+ end.
+
%%====================================================================
%% gen_server callbacks
%%====================================================================
@@ -619,3 +653,121 @@ get_s2s_state(S2sPid) ->
{badrpc, _} -> [{status, error}]
end,
[{s2s_pid, S2sPid} | Infos].
+
+get_cert_domains(Cert) ->
+ {rdnSequence, Subject} =
+ (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject,
+ Extensions =
+ (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.extensions,
+ lists:flatmap(fun (#'AttributeTypeAndValue'{type =
+ ?'id-at-commonName',
+ value = Val}) ->
+ case 'OTP-PUB-KEY':decode('X520CommonName', Val) of
+ {ok, {_, D1}} ->
+ D = if is_binary(D1) -> D1;
+ is_list(D1) -> list_to_binary(D1);
+ true -> error
+ end,
+ if D /= error ->
+ case jlib:string_to_jid(D) of
+ #jid{luser = <<"">>, lserver = LD,
+ lresource = <<"">>} ->
+ [LD];
+ _ -> []
+ end;
+ true -> []
+ end;
+ _ -> []
+ end;
+ (_) -> []
+ end,
+ lists:flatten(Subject))
+ ++
+ lists:flatmap(fun (#'Extension'{extnID =
+ ?'id-ce-subjectAltName',
+ extnValue = Val}) ->
+ BVal = if is_list(Val) -> list_to_binary(Val);
+ true -> Val
+ end,
+ case 'OTP-PUB-KEY':decode('SubjectAltName', BVal)
+ of
+ {ok, SANs} ->
+ lists:flatmap(fun ({otherName,
+ #'AnotherName'{'type-id' =
+ ?'id-on-xmppAddr',
+ value =
+ XmppAddr}}) ->
+ case
+ 'XmppAddr':decode('XmppAddr',
+ XmppAddr)
+ of
+ {ok, D}
+ when
+ is_binary(D) ->
+ case
+ jlib:string_to_jid((D))
+ of
+ #jid{luser =
+ <<"">>,
+ lserver =
+ LD,
+ lresource =
+ <<"">>} ->
+ case
+ idna:domain_utf8_to_ascii(LD)
+ of
+ false ->
+ [];
+ PCLD ->
+ [PCLD]
+ end;
+ _ -> []
+ end;
+ _ -> []
+ end;
+ ({dNSName, D})
+ when is_list(D) ->
+ case
+ jlib:string_to_jid(list_to_binary(D))
+ of
+ #jid{luser = <<"">>,
+ lserver = LD,
+ lresource =
+ <<"">>} ->
+ [LD];
+ _ -> []
+ end;
+ (_) -> []
+ end,
+ SANs);
+ _ -> []
+ end;
+ (_) -> []
+ end,
+ Extensions).
+
+match_domain(Domain, Domain) -> true;
+match_domain(Domain, Pattern) ->
+ DLabels = str:tokens(Domain, <<".">>),
+ PLabels = str:tokens(Pattern, <<".">>),
+ match_labels(DLabels, PLabels).
+
+match_labels([], []) -> true;
+match_labels([], [_ | _]) -> false;
+match_labels([_ | _], []) -> false;
+match_labels([DL | DLabels], [PL | PLabels]) ->
+ case lists:all(fun (C) ->
+ $a =< C andalso C =< $z orelse
+ $0 =< C andalso C =< $9 orelse
+ C == $- orelse C == $*
+ end,
+ binary_to_list(PL))
+ of
+ true ->
+ Regexp = ejabberd_regexp:sh_to_awk(PL),
+ case ejabberd_regexp:run(DL, Regexp) of
+ match -> match_labels(DLabels, PLabels);
+ nomatch -> false
+ end;
+ false -> false
+ end.
diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl
index 3eb0b71cc..4fde814fe 100644
--- a/src/ejabberd_s2s_in.erl
+++ b/src/ejabberd_s2s_in.erl
@@ -30,8 +30,7 @@
-behaviour(p1_fsm).
%% External exports
--export([start/2, start_link/2, match_domain/2,
- socket_type/0]).
+-export([start/2, start_link/2, socket_type/0]).
%% gen_fsm callbacks
-export([init/1, wait_for_stream/2,
@@ -44,14 +43,6 @@
-include("jlib.hrl").
--include_lib("public_key/include/public_key.hrl").
-
--define(PKIXEXPLICIT, 'OTP-PUB-KEY').
-
--define(PKIXIMPLICIT, 'OTP-PUB-KEY').
-
--include("XmppAddr.hrl").
-
-define(DICT, dict).
-record(state,
@@ -227,45 +218,11 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
Auth = if StateData#state.tls_enabled ->
case jlib:nameprep(xml:get_attr_s(<<"from">>, Attrs)) of
From when From /= <<"">>, From /= error ->
- case
- (StateData#state.sockmod):get_peer_certificate(StateData#state.socket)
- of
- {ok, Cert} ->
- case
- (StateData#state.sockmod):get_verify_result(StateData#state.socket)
- of
- 0 ->
- case
- idna:domain_utf8_to_ascii(From)
- of
- false ->
- {error, From,
- <<"Cannot decode 'from' attribute">>};
- PCAuthDomain ->
- case
- lists:any(fun (D) ->
- match_domain(PCAuthDomain,
- D)
- end,
- get_cert_domains(Cert))
- of
- true ->
- {ok, From,
- <<"Success">>};
- false ->
- {error, From,
- <<"Certificate host name mismatch">>}
- end
- end;
- CertVerifyRes ->
- {error, From,
- p1_tls:get_cert_verify_string(CertVerifyRes,
- Cert)}
- end;
- error ->
- {error, From,
- <<"Cannot get peer certificate">>}
- end;
+ {Result, Message} =
+ ejabberd_s2s:check_peer_certificate(StateData#state.sockmod,
+ StateData#state.socket,
+ From),
+ {Result, From, Message};
_ ->
{error, <<"(unknown)">>,
<<"Got no valid 'from' attribute">>}
@@ -294,15 +251,9 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
[StateData#state.server, RemoteServer, CertError]),
send_text(StateData,
- xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
- CertError))),
- {atomic, Pid} =
- ejabberd_s2s:find_connection(jlib:make_jid(<<"">>,
- Server, <<"">>),
- jlib:make_jid(<<"">>,
- RemoteServer,
- <<"">>)),
- ejabberd_s2s_out:stop_connection(Pid),
+ <<(xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
+ CertError)))/binary,
+ (?STREAM_TRAILER)/binary>>),
{stop, normal, StateData};
{VerifyResult, RemoteServer, Msg} ->
{SASL, NewStateData} = case VerifyResult of
@@ -746,124 +697,6 @@ is_key_packet(#xmlel{name = Name, attrs = Attrs,
xml:get_attr_s(<<"id">>, Attrs), xml:get_cdata(Els)};
is_key_packet(_) -> false.
-get_cert_domains(Cert) ->
- {rdnSequence, Subject} =
- (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject,
- Extensions =
- (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.extensions,
- lists:flatmap(fun (#'AttributeTypeAndValue'{type =
- ?'id-at-commonName',
- value = Val}) ->
- case 'OTP-PUB-KEY':decode('X520CommonName', Val) of
- {ok, {_, D1}} ->
- D = if is_binary(D1) -> D1;
- is_list(D1) -> list_to_binary(D1);
- true -> error
- end,
- if D /= error ->
- case jlib:string_to_jid(D) of
- #jid{luser = <<"">>, lserver = LD,
- lresource = <<"">>} ->
- [LD];
- _ -> []
- end;
- true -> []
- end;
- _ -> []
- end;
- (_) -> []
- end,
- lists:flatten(Subject))
- ++
- lists:flatmap(fun (#'Extension'{extnID =
- ?'id-ce-subjectAltName',
- extnValue = Val}) ->
- BVal = if is_list(Val) -> list_to_binary(Val);
- true -> Val
- end,
- case 'OTP-PUB-KEY':decode('SubjectAltName', BVal)
- of
- {ok, SANs} ->
- lists:flatmap(fun ({otherName,
- #'AnotherName'{'type-id' =
- ?'id-on-xmppAddr',
- value =
- XmppAddr}}) ->
- case
- 'XmppAddr':decode('XmppAddr',
- XmppAddr)
- of
- {ok, D}
- when
- is_binary(D) ->
- case
- jlib:string_to_jid((D))
- of
- #jid{luser =
- <<"">>,
- lserver =
- LD,
- lresource =
- <<"">>} ->
- case
- idna:domain_utf8_to_ascii(LD)
- of
- false ->
- [];
- PCLD ->
- [PCLD]
- end;
- _ -> []
- end;
- _ -> []
- end;
- ({dNSName, D})
- when is_list(D) ->
- case
- jlib:string_to_jid(list_to_binary(D))
- of
- #jid{luser = <<"">>,
- lserver = LD,
- lresource =
- <<"">>} ->
- [LD];
- _ -> []
- end;
- (_) -> []
- end,
- SANs);
- _ -> []
- end;
- (_) -> []
- end,
- Extensions).
-
-match_domain(Domain, Domain) -> true;
-match_domain(Domain, Pattern) ->
- DLabels = str:tokens(Domain, <<".">>),
- PLabels = str:tokens(Pattern, <<".">>),
- match_labels(DLabels, PLabels).
-
-match_labels([], []) -> true;
-match_labels([], [_ | _]) -> false;
-match_labels([_ | _], []) -> false;
-match_labels([DL | DLabels], [PL | PLabels]) ->
- case lists:all(fun (C) ->
- $a =< C andalso C =< $z orelse
- $0 =< C andalso C =< $9 orelse
- C == $- orelse C == $*
- end,
- binary_to_list(PL))
- of
- true ->
- Regexp = ejabberd_regexp:sh_to_awk(PL),
- case ejabberd_regexp:run(DL, Regexp) of
- match -> match_labels(DLabels, PLabels);
- nomatch -> false
- end;
- false -> false
- end.
-
fsm_limit_opts(Opts) ->
case lists:keysearch(max_fsm_queue, 1, Opts) of
{value, {_, N}} when is_integer(N) -> [{max_queue, N}];
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index a0a83631d..052729314 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -69,6 +69,7 @@
use_v10 = true :: boolean(),
tls = false :: boolean(),
tls_required = false :: boolean(),
+ tls_certverify = false :: boolean(),
tls_enabled = false :: boolean(),
tls_options = [connect] :: list(),
authenticated = false :: boolean(),
@@ -160,28 +161,27 @@ stop_connection(Pid) -> p1_fsm:send_event(Pid, closed).
init([From, Server, Type]) ->
process_flag(trap_exit, true),
?DEBUG("started: ~p", [{From, Server, Type}]),
- {TLS, TLSRequired} = case
- ejabberd_config:get_option(
- s2s_use_starttls,
- fun(true) -> true;
- (false) -> false;
- (optional) -> optional;
- (required) -> required;
- (required_trusted) -> required_trusted
- end)
- of
- UseTls
- when (UseTls == undefined) or
- (UseTls == false) ->
- {false, false};
- UseTls
- when (UseTls == true) or (UseTls == optional) ->
- {true, false};
- UseTls
- when (UseTls == required) or
- (UseTls == required_trusted) ->
- {true, true}
- end,
+ {TLS, TLSRequired, TLSCertverify} =
+ case ejabberd_config:get_option(
+ s2s_use_starttls,
+ fun(true) -> true;
+ (false) -> false;
+ (optional) -> optional;
+ (required) -> required;
+ (required_trusted) -> required_trusted
+ end)
+ of
+ UseTls
+ when (UseTls == undefined) or (UseTls == false) ->
+ {false, false, false};
+ UseTls
+ when (UseTls == true) or (UseTls == optional) ->
+ {true, false, false};
+ required ->
+ {true, true, false};
+ required_trusted ->
+ {true, true, true}
+ end,
UseV10 = TLS,
TLSOpts1 = case
ejabberd_config:get_option(
@@ -223,9 +223,9 @@ init([From, Server, Type]) ->
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
{ok, open_socket,
#state{use_v10 = UseV10, tls = TLS,
- tls_required = TLSRequired, tls_options = TLSOpts,
- queue = queue:new(), myname = From, server = Server,
- new = New, verify = Verify, timer = Timer}}.
+ tls_required = TLSRequired, tls_certverify = TLSCertverify,
+ tls_options = TLSOpts, queue = queue:new(), myname = From,
+ server = Server, new = New, verify = Verify, timer = Timer}}.
%%----------------------------------------------------------------------
%% Func: StateName/2
@@ -345,35 +345,57 @@ open_socket2(Type, Addr, Port) ->
wait_for_stream({xmlstreamstart, _Name, Attrs},
StateData) ->
+ {CertCheckRes, CertCheckMsg, NewStateData} =
+ if StateData#state.tls_certverify, StateData#state.tls_enabled ->
+ {Res, Msg} =
+ ejabberd_s2s:check_peer_certificate(ejabberd_socket,
+ StateData#state.socket,
+ StateData#state.server),
+ ?DEBUG("Certificate verification result for ~s: ~s",
+ [StateData#state.server, Msg]),
+ {Res, Msg, StateData#state{tls_certverify = false}};
+ true ->
+ {no_verify, <<"Not verified">>, StateData}
+ end,
case {xml:get_attr_s(<<"xmlns">>, Attrs),
xml:get_attr_s(<<"xmlns:db">>, Attrs),
xml:get_attr_s(<<"version">>, Attrs) == <<"1.0">>}
of
+ _ when CertCheckRes == error ->
+ send_text(NewStateData,
+ <<(xml:element_to_binary(?SERRT_POLICY_VIOLATION(<<"en">>,
+ CertCheckMsg)))/binary,
+ (?STREAM_TRAILER)/binary>>),
+ ?INFO_MSG("Closing s2s connection: ~s -> ~s (~s)",
+ [NewStateData#state.myname,
+ NewStateData#state.server,
+ CertCheckMsg]),
+ {stop, normal, NewStateData};
{<<"jabber:server">>, <<"jabber:server:dialback">>,
false} ->
- send_db_request(StateData);
+ send_db_request(NewStateData);
{<<"jabber:server">>, <<"jabber:server:dialback">>,
true}
- when StateData#state.use_v10 ->
- {next_state, wait_for_features, StateData, ?FSMTIMEOUT};
+ when NewStateData#state.use_v10 ->
+ {next_state, wait_for_features, NewStateData, ?FSMTIMEOUT};
%% Clause added to handle Tigase's workaround for an old ejabberd bug:
{<<"jabber:server">>, <<"jabber:server:dialback">>,
true}
- when not StateData#state.use_v10 ->
- send_db_request(StateData);
+ when not NewStateData#state.use_v10 ->
+ send_db_request(NewStateData);
{<<"jabber:server">>, <<"">>, true}
- when StateData#state.use_v10 ->
+ when NewStateData#state.use_v10 ->
{next_state, wait_for_features,
- StateData#state{db_enabled = false}, ?FSMTIMEOUT};
+ NewStateData#state{db_enabled = false}, ?FSMTIMEOUT};
{NSProvided, DB, _} ->
- send_text(StateData, ?INVALID_NAMESPACE_ERR),
+ send_text(NewStateData, ?INVALID_NAMESPACE_ERR),
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid "
"namespace).~nNamespace provided: ~p~nNamespac"
"e expected: \"jabber:server\"~nxmlns:db "
"provided: ~p~nAll attributes: ~p",
- [StateData#state.myname, StateData#state.server,
+ [NewStateData#state.myname, NewStateData#state.server,
NSProvided, DB, Attrs]),
- {stop, normal, StateData}
+ {stop, normal, NewStateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_text(StateData,
@@ -570,15 +592,19 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
if not SASLEXT and not StartTLS and
StateData#state.authenticated ->
send_queue(StateData, StateData#state.queue),
- ?INFO_MSG("Connection established: ~s -> ~s",
- [StateData#state.myname, StateData#state.server]),
+ ?INFO_MSG("Connection established: ~s -> ~s with "
+ "SASL EXTERNAL and TLS=~p",
+ [StateData#state.myname, StateData#state.server,
+ StateData#state.tls_enabled]),
ejabberd_hooks:run(s2s_connect_hook,
[StateData#state.myname,
StateData#state.server]),
{next_state, stream_established,
StateData#state{queue = queue:new()}};
SASLEXT and StateData#state.try_auth and
- (StateData#state.new /= false) ->
+ (StateData#state.new /= false) and
+ (StateData#state.tls_enabled or
+ not StateData#state.tls_required) ->
send_element(StateData,
#xmlel{name = <<"auth">>,
attrs =
@@ -736,8 +762,8 @@ wait_for_starttls_proceed({xmlstreamelement, El},
tls_options = TLSOpts},
send_text(NewStateData,
io_lib:format(?STREAM_HEADER,
- [StateData#state.myname,
- StateData#state.server,
+ [NewStateData#state.myname,
+ NewStateData#state.server,
<<" version='1.0'">>])),
{next_state, wait_for_stream, NewStateData,
?FSMTIMEOUT};
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index b1673d4b9..58debf0c1 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -52,6 +52,7 @@
connected_users/0,
connected_users_number/0,
user_resources/2,
+ disconnect_user/2,
get_session_pid/3,
get_user_info/3,
get_user_ip/3,
@@ -801,7 +802,13 @@ commands() ->
desc = "List user's connected resources",
module = ?MODULE, function = user_resources,
args = [{user, binary}, {host, binary}],
- result = {resources, {list, {resource, string}}}}].
+ result = {resources, {list, {resource, string}}}},
+ #ejabberd_commands{name = disconnect_user,
+ tags = [session],
+ desc = "Disconnect user's active sessions",
+ module = ?MODULE, function = disconnect_user,
+ args = [{user, binary}, {host, binary}],
+ result = {num_resources, integer}}].
-spec connected_users() -> [binary()].
@@ -818,6 +825,14 @@ user_resources(User, Server) ->
Resources = get_user_resources(User, Server),
lists:sort(Resources).
+disconnect_user(User, Server) ->
+ Resources = get_user_resources(User, Server),
+ lists:foreach(
+ fun(Resource) ->
+ PID = get_session_pid(User, Server, Resource),
+ PID ! disconnect
+ end, Resources),
+ length(Resources).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Update Mnesia tables
diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl
index 76610af8e..ff89d2858 100644
--- a/src/ejabberd_xmlrpc.erl
+++ b/src/ejabberd_xmlrpc.erl
@@ -428,7 +428,11 @@ format_arg({array, Elements}, {list, ElementsDef})
format_arg(Arg, integer) when is_integer(Arg) -> Arg;
format_arg(Arg, binary) when is_list(Arg) -> list_to_binary(Arg);
format_arg(Arg, binary) when is_binary(Arg) -> Arg;
-format_arg(Arg, string) when is_binary(Arg) -> Arg.
+format_arg(Arg, string) when is_list(Arg) -> list_to_binary(Arg);
+format_arg(Arg, string) when is_binary(Arg) -> Arg;
+format_arg(Arg, Format) ->
+ ?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
+ throw({error_formatting_argument, Arg, Format}).
%% -----------------------------
%% Result
diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl
index 0da53b26d..ee3ff0069 100644
--- a/src/mod_register_web.erl
+++ b/src/mod_register_web.erl
@@ -140,7 +140,10 @@ process([<<"change_password">>],
list_to_binary([?T(<<"There was an error changing the password: ">>),
?T(get_error_text(Error))]),
{404, [], ErrorText}
- end.
+ end;
+
+process(Path, _Request) ->
+ {404, [], "Not Found"}.
%%%----------------------------------------------------------------------
%%% CSS
@@ -487,12 +490,13 @@ register_account(Username, Host, Password) ->
Access = gen_mod:get_module_opt(Host, mod_register, access,
fun(A) when is_atom(A) -> A end,
all),
- JID = jlib:make_jid(Username, Host, <<"">>),
- Match = acl:match_rule(Host, Access, JID),
- case {JID, Match} of
- {error, _} -> {error, invalid_jid};
- {_, deny} -> {error, not_allowed};
- {_, allow} -> register_account2(Username, Host, Password)
+ case jlib:make_jid(Username, Host, <<"">>) of
+ error -> {error, invalid_jid};
+ JID ->
+ case acl:match_rule(Host, Access, JID) of
+ deny -> {error, not_allowed};
+ allow -> register_account2(Username, Host, Password)
+ end
end.
register_account2(Username, Host, Password) ->
diff --git a/src/mod_sip.erl b/src/mod_sip.erl
new file mode 100644
index 000000000..cca91a33d
--- /dev/null
+++ b/src/mod_sip.erl
@@ -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, []).
diff --git a/src/mod_sip_proxy.erl b/src/mod_sip_proxy.erl
new file mode 100644
index 000000000..aa749ccf7
--- /dev/null
+++ b/src/mod_sip_proxy.erl
@@ -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.
diff --git a/src/mod_sip_registrar.erl b/src/mod_sip_registrar.erl
new file mode 100644
index 000000000..d8f485fef
--- /dev/null
+++ b/src/mod_sip_registrar.erl
@@ -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.
diff --git a/vars.config.in b/vars.config.in
index 31c356fc9..037af96bc 100644
--- a/vars.config.in
+++ b/vars.config.in
@@ -28,6 +28,7 @@
{json, @json@}.
{http, @http@}.
{lager, @lager@}.
+{sip, @sip@}.
{iconv, @iconv@}.
%% Version