diff options
-rw-r--r-- | README | 38 | ||||
l--------- | README.md | 1 | ||||
-rwxr-xr-x | configure | 52 | ||||
-rw-r--r-- | configure.ac | 15 | ||||
-rw-r--r-- | doc/guide.tex | 109 | ||||
-rw-r--r-- | doc/introduction.tex | 1 | ||||
-rw-r--r-- | rebar.config.script | 12 | ||||
-rw-r--r-- | src/ejabberd_auth_odbc.erl | 1 | ||||
-rw-r--r-- | src/ejabberd_c2s.erl | 6 | ||||
-rw-r--r-- | src/ejabberd_ctl.erl | 1 | ||||
-rw-r--r-- | src/ejabberd_listener.erl | 36 | ||||
-rw-r--r-- | src/ejabberd_odbc.erl | 9 | ||||
-rw-r--r-- | src/ejabberd_odbc_sup.erl | 6 | ||||
-rw-r--r-- | src/ejabberd_s2s.erl | 154 | ||||
-rw-r--r-- | src/ejabberd_s2s_in.erl | 185 | ||||
-rw-r--r-- | src/ejabberd_s2s_out.erl | 106 | ||||
-rw-r--r-- | src/ejabberd_sm.erl | 17 | ||||
-rw-r--r-- | src/ejabberd_xmlrpc.erl | 6 | ||||
-rw-r--r-- | src/mod_register_web.erl | 18 | ||||
-rw-r--r-- | src/mod_sip.erl | 404 | ||||
-rw-r--r-- | src/mod_sip_proxy.erl | 152 | ||||
-rw-r--r-- | src/mod_sip_registrar.erl | 196 | ||||
-rw-r--r-- | vars.config.in | 1 |
23 files changed, 1261 insertions, 265 deletions
@@ -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 @@ -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 |