Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2f2f64161 | |||
| 2c09d7c8a7 | |||
| 389a99b2db | |||
| 07c8bf5064 | |||
| 7fd91a4b12 | |||
| ae4356265f | |||
| a0396620f2 | |||
| 7e73ed88f7 | |||
| 150a5392e5 | |||
| 8cbbe4a3eb | |||
| 4b82cb9953 | |||
| 63b2d21b13 | |||
| 1b8876bf55 | |||
| b82eeeeec9 | |||
| dd26398a02 | |||
| 11292c809f | |||
| a62fb69e20 | |||
| e3483ef9e1 | |||
| 0fe3de6b30 | |||
| 91a74e3e27 | |||
| f68dfacbbf | |||
| 0b9754884e | |||
| cc6dcd161c | |||
| 7a77186fbe | |||
| 06bb10a032 | |||
| 3e2f9dc6b0 | |||
| 807a1fe164 | |||
| 8ce22b790d | |||
| f75d78d3f5 | |||
| ca6463ed78 | |||
| d58148fa8d | |||
| a2ead99c83 | |||
| 33f09c7a78 | |||
| 2d43b035d7 | |||
| 986f4d1a7f | |||
| 8482641b4e | |||
| 8fc5d86704 | |||
| 47994806f0 | |||
| 0d7a5476c0 | |||
| 4e72dd6751 | |||
| 48819d163a | |||
| 8621a8f006 | |||
| b66e4fbdc4 | |||
| b5623d6bee | |||
| e89f0f6461 | |||
| 2b24e97936 | |||
| a302af7770 | |||
| 0aca3a4585 | |||
| c262c08513 | |||
| 0266207e9d | |||
| 60600c341e | |||
| 02c59422cf | |||
| 97810b4cc3 | |||
| 1a9f0ab084 | |||
| fa7fe73a0e | |||
| 49e17922a3 | |||
| 09c450062a | |||
| ac3cd2ebaa | |||
| 2636da0d98 | |||
| 91744733c1 | |||
| 295ea0633e | |||
| bc2ea224eb | |||
| 9f4d12bd0a | |||
| 653e23c0e7 | |||
| c7e2128dab | |||
| 8a9743ab3b | |||
| 597934637c | |||
| 2ea397e476 | |||
| 9094169440 | |||
| 3631301304 | |||
| 42a9e4f4cf | |||
| aab70fc066 | |||
| 167f02ab72 | |||
| 1b0c02cb2e | |||
| c05edabe58 |
+5
-4
@@ -77,7 +77,7 @@ deps/.got:
|
||||
rm -rf deps/.built
|
||||
$(REBAR) get-deps && :> deps/.got
|
||||
|
||||
deps/.built:
|
||||
deps/.built: deps/.got
|
||||
$(REBAR) compile && :> deps/.built
|
||||
|
||||
src: deps/.built
|
||||
@@ -109,9 +109,9 @@ install: all
|
||||
#
|
||||
# Configuration files
|
||||
$(INSTALL) -d -m 750 $(G_USER) $(ETCDIR)
|
||||
[ -f $(ETCDIR)/ejabberd.cfg ] \
|
||||
&& $(INSTALL) -b -m 640 $(G_USER) ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg-new \
|
||||
|| $(INSTALL) -b -m 640 $(G_USER) ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg
|
||||
[ -f $(ETCDIR)/ejabberd.yml ] \
|
||||
&& $(INSTALL) -b -m 640 $(G_USER) ejabberd.yml.example $(ETCDIR)/ejabberd.yml-new \
|
||||
|| $(INSTALL) -b -m 640 $(G_USER) ejabberd.yml.example $(ETCDIR)/ejabberd.yml
|
||||
$(SED) -e "s*{{rootdir}}*@prefix@*" \
|
||||
-e "s*{{installuser}}*@INSTALLUSER@*" \
|
||||
-e "s*{{libdir}}*@libdir@*" \
|
||||
@@ -215,6 +215,7 @@ uninstall-all: uninstall-binary
|
||||
clean:
|
||||
rm -rf deps/.got
|
||||
rm -rf deps/.built
|
||||
rm -rf test/*.beam
|
||||
$(REBAR) clean
|
||||
|
||||
clean-rel:
|
||||
|
||||
@@ -9,6 +9,7 @@ To compile ejabberd you need:
|
||||
- GNU Make
|
||||
- GCC
|
||||
- Libexpat 1.95 or higher
|
||||
- Libyaml 1.4 or higher
|
||||
- Erlang/OTP R15B or higher.
|
||||
- OpenSSL 0.9.8 or higher, for STARTTLS, SASL and SSL encryption.
|
||||
- Zlib 1.2.3 or higher, for Stream Compression support
|
||||
|
||||
+14
-14
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ(2.53)
|
||||
AC_INIT(ejabberd, community, [ejabberd@process-one.net], [ejabberd])
|
||||
AC_INIT(ejabberd, community m4_esyscmd([git describe --tags | tr -d '\n']), [ejabberd@process-one.net], [ejabberd])
|
||||
REQUIRE_ERLANG_MIN="5.9.1 (Erlang/OTP R15B01)"
|
||||
REQUIRE_ERLANG_MAX="9.0.0 (No Max)"
|
||||
|
||||
@@ -35,7 +35,7 @@ AC_ERLANG_NEED_ERLC
|
||||
AC_ARG_ENABLE(erlang-version-check,
|
||||
[AC_HELP_STRING([--enable-erlang-version-check],
|
||||
[Check Erlang/OTP version @<:@default=yes@:>@])])
|
||||
case "$enable_erlang_version_check" in
|
||||
case "$enable_erlang_version_check" in
|
||||
yes|'')
|
||||
ERLANG_VERSION_CHECK([$REQUIRE_ERLANG_MIN],[$REQUIRE_ERLANG_MAX])
|
||||
;;
|
||||
@@ -105,21 +105,21 @@ AC_ARG_ENABLE(mssql,
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --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)])],
|
||||
[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 ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
|
||||
esac],[])
|
||||
|
||||
AC_ARG_ENABLE(tools,
|
||||
[AC_HELP_STRING([--enable-tools], [build development tools (currently the ejabberd profiler only, default: no)])],
|
||||
[AC_HELP_STRING([--enable-tools], [build development tools (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) tools=true ;;
|
||||
no) tools=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-tools) ;;
|
||||
esac],[tools=false])
|
||||
|
||||
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-json --enable-iconv --enable-debug --enable-http (useful for Dialyzer checks, default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true stun=true json=true iconv=true debug=true http=true ;;
|
||||
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false stun=false json=false iconv=false debug=false http=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
|
||||
esac],[])
|
||||
esac],[if test "x$tools" = "x"; then tools=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(nif,
|
||||
[AC_HELP_STRING([--enable-nif], [replace some functions with C equivalents. Requires Erlang R13B04 or higher (default: no)])],
|
||||
@@ -210,12 +210,12 @@ AC_ARG_ENABLE(http,
|
||||
esac],[if test "x$http" = "x"; then http=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(lager,
|
||||
[AC_HELP_STRING([--enable-lager], [enable lager support (default: no)])],
|
||||
[AC_HELP_STRING([--enable-lager], [enable lager support (default: yes)])],
|
||||
[case "${enableval}" in
|
||||
yes) lager=true ;;
|
||||
no) lager=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-lager) ;;
|
||||
esac],[if test "x$lager" = "x"; then lager=false; fi])
|
||||
esac],[if test "x$lager" = "x"; then lager=true; fi])
|
||||
|
||||
AC_CONFIG_FILES([Makefile
|
||||
vars.config
|
||||
|
||||
+2
-2
@@ -13,12 +13,12 @@ all: release pdf html
|
||||
release:
|
||||
@echo "Notes for the releaser:"
|
||||
@echo "* Do not forget to add a link to the release notes in guide.tex"
|
||||
@echo "* Do not forget to update the version number in src/ejabberd.app!"
|
||||
@echo "* Do not forget to update the version number in ebin/ejabberd.app!"
|
||||
@echo "* Do not forget to update the features in introduction.tex (including \new{} and \improved{} tags)."
|
||||
@echo "Press any key to continue"
|
||||
##@read foo
|
||||
@echo "% ejabberd version (automatically generated)." > version.tex
|
||||
@echo "\newcommand{\version}{"`sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../src/ejabberd.app`"}" >> version.tex
|
||||
@echo "\newcommand{\version}{"`sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../ebin/ejabberd.app`"}" >> version.tex
|
||||
@echo -n "% Contributed modules (automatically generated)." > contributed_modules.tex
|
||||
@echo -e "$(CONTRIBUTED_MODULES)" >> contributed_modules.tex
|
||||
|
||||
|
||||
+1555
-1418
File diff suppressed because it is too large
Load Diff
+1327
-1378
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,2 +1,2 @@
|
||||
% ejabberd version (automatically generated).
|
||||
\newcommand{\version}{13.03-beta2}
|
||||
\newcommand{\version}{13.10}
|
||||
|
||||
@@ -1,609 +0,0 @@
|
||||
%%%
|
||||
%%% ejabberd configuration file
|
||||
%%%
|
||||
%%%'
|
||||
|
||||
%%% The parameters used in this configuration file are explained in more detail
|
||||
%%% in the ejabberd Installation and Operation Guide.
|
||||
%%% Please consult the Guide in case of doubts, it is included with
|
||||
%%% your copy of ejabberd, and is also available online at
|
||||
%%% http://www.process-one.net/en/ejabberd/docs/
|
||||
|
||||
%%% This configuration file contains Erlang terms.
|
||||
%%% In case you want to understand the syntax, here are the concepts:
|
||||
%%%
|
||||
%%% - The character to comment a line is %
|
||||
%%%
|
||||
%%% - Each term ends in a dot, for example:
|
||||
%%% override_global.
|
||||
%%%
|
||||
%%% - A tuple has a fixed definition, its elements are
|
||||
%%% enclosed in {}, and separated with commas:
|
||||
%%% {loglevel, 4}.
|
||||
%%%
|
||||
%%% - A list can have as many elements as you want,
|
||||
%%% and is enclosed in [], for example:
|
||||
%%% [http_poll, web_admin, tls]
|
||||
%%%
|
||||
%%% - A keyword of ejabberd is a word in lowercase.
|
||||
%%% Strings are enclosed in "" and can contain spaces, dots, ...
|
||||
%%% {language, "en"}.
|
||||
%%% {ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%%
|
||||
%%% - This term includes a tuple, a keyword, a list, and two strings:
|
||||
%%% {hosts, ["jabber.example.net", "im.example.com"]}.
|
||||
%%%
|
||||
|
||||
|
||||
%%%. =======================
|
||||
%%%' OVERRIDE STORED OPTIONS
|
||||
|
||||
%%
|
||||
%% Override the old values stored in the database.
|
||||
%%
|
||||
|
||||
%%
|
||||
%% Override global options (shared by all ejabberd nodes in a cluster).
|
||||
%%
|
||||
%%override_global.
|
||||
|
||||
%%
|
||||
%% Override local options (specific for this particular ejabberd node).
|
||||
%%
|
||||
%%override_local.
|
||||
|
||||
%%
|
||||
%% Remove the Access Control Lists before new ones are added.
|
||||
%%
|
||||
%%override_acls.
|
||||
|
||||
|
||||
%%%. =========
|
||||
%%%' DEBUGGING
|
||||
|
||||
%%
|
||||
%% loglevel: Verbosity of log files generated by ejabberd.
|
||||
%% 0: No ejabberd log at all (not recommended)
|
||||
%% 1: Critical
|
||||
%% 2: Error
|
||||
%% 3: Warning
|
||||
%% 4: Info
|
||||
%% 5: Debug
|
||||
%%
|
||||
{loglevel, 4}.
|
||||
|
||||
%%
|
||||
%% watchdog_admins: Only useful for developers: if an ejabberd process
|
||||
%% consumes a lot of memory, send live notifications to these XMPP
|
||||
%% accounts.
|
||||
%%
|
||||
%%{watchdog_admins, ["bob@example.com"]}.
|
||||
|
||||
|
||||
%%%. ================
|
||||
%%%' SERVED HOSTNAMES
|
||||
|
||||
%%
|
||||
%% hosts: Domains served by ejabberd.
|
||||
%% You can define one or several, for example:
|
||||
%% {hosts, ["example.net", "example.com", "example.org"]}.
|
||||
%%
|
||||
{hosts, ["localhost"]}.
|
||||
|
||||
%%
|
||||
%% route_subdomains: Delegate subdomains to other XMPP servers.
|
||||
%% For example, if this ejabberd serves example.org and you want
|
||||
%% to allow communication with an XMPP server called im.example.org.
|
||||
%%
|
||||
%%{route_subdomains, s2s}.
|
||||
|
||||
|
||||
%%%. ===============
|
||||
%%%' LISTENING PORTS
|
||||
|
||||
%%
|
||||
%% listen: The ports ejabberd will listen on, which service each is handled
|
||||
%% by and what options to start it with.
|
||||
%%
|
||||
{listen,
|
||||
[
|
||||
|
||||
{5222, ejabberd_c2s, [
|
||||
|
||||
%%
|
||||
%% If TLS is compiled in and you installed a SSL
|
||||
%% certificate, specify the full path to the
|
||||
%% file and uncomment this line:
|
||||
%%
|
||||
%%{certfile, "/path/to/ssl.pem"}, starttls,
|
||||
|
||||
{access, c2s},
|
||||
{shaper, c2s_shaper},
|
||||
{max_stanza_size, 65536}
|
||||
]},
|
||||
|
||||
%%
|
||||
%% To enable the old SSL connection method on port 5223:
|
||||
%%
|
||||
%%{5223, ejabberd_c2s, [
|
||||
%% {access, c2s},
|
||||
%% {shaper, c2s_shaper},
|
||||
%% {certfile, "/path/to/ssl.pem"}, tls,
|
||||
%% {max_stanza_size, 65536}
|
||||
%% ]},
|
||||
|
||||
{5269, ejabberd_s2s_in, [
|
||||
{shaper, s2s_shaper},
|
||||
{max_stanza_size, 131072}
|
||||
]},
|
||||
|
||||
%%
|
||||
%% ejabberd_service: Interact with external components (transports, ...)
|
||||
%%
|
||||
%%{8888, ejabberd_service, [
|
||||
%% {access, all},
|
||||
%% {shaper_rule, fast},
|
||||
%% {hosts, ["icq.example.org", "sms.example.org"],
|
||||
%% [{password, "secret"}]
|
||||
%% }
|
||||
%% ]},
|
||||
|
||||
%%
|
||||
%% ejabberd_stun: Handles STUN Binding requests
|
||||
%%
|
||||
%%{{3478, udp}, ejabberd_stun, []},
|
||||
|
||||
{5280, ejabberd_http, [
|
||||
%%{request_handlers,
|
||||
%% [
|
||||
%% {["pub", "archive"], mod_http_fileserver}
|
||||
%% ]},
|
||||
captcha,
|
||||
http_bind,
|
||||
http_poll,
|
||||
%%register,
|
||||
web_admin
|
||||
]}
|
||||
|
||||
]}.
|
||||
|
||||
%%
|
||||
%% s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
|
||||
%% Allowed values are: false optional required required_trusted
|
||||
%% You must specify a certificate file.
|
||||
%%
|
||||
%%{s2s_use_starttls, optional}.
|
||||
|
||||
%%
|
||||
%% s2s_certfile: Specify a certificate file.
|
||||
%%
|
||||
%%{s2s_certfile, "/path/to/ssl.pem"}.
|
||||
|
||||
%%
|
||||
%% domain_certfile: Specify a different certificate for each served hostname.
|
||||
%%
|
||||
%%{domain_certfile, "example.org", "/path/to/example_org.pem"}.
|
||||
%%{domain_certfile, "example.com", "/path/to/example_com.pem"}.
|
||||
|
||||
%%
|
||||
%% S2S whitelist or blacklist
|
||||
%%
|
||||
%% Default s2s policy for undefined hosts.
|
||||
%%
|
||||
%%{s2s_default_policy, allow}.
|
||||
|
||||
%%
|
||||
%% Allow or deny communication with specific servers.
|
||||
%%
|
||||
%%{{s2s_host, "goodhost.org"}, allow}.
|
||||
%%{{s2s_host, "badhost.org"}, deny}.
|
||||
|
||||
%%
|
||||
%% Outgoing S2S options
|
||||
%%
|
||||
%% Preferred address families (which to try first) and connect timeout
|
||||
%% in milliseconds.
|
||||
%%
|
||||
%%{outgoing_s2s_options, [ipv4, ipv6], 10000}.
|
||||
|
||||
|
||||
%%%. ==============
|
||||
%%%' AUTHENTICATION
|
||||
|
||||
%%
|
||||
%% auth_method: Method used to authenticate the users.
|
||||
%% The default method is the internal.
|
||||
%% If you want to use a different method,
|
||||
%% comment this line and enable the correct ones.
|
||||
%%
|
||||
{auth_method, internal}.
|
||||
%%
|
||||
%% Store the plain passwords or hashed for SCRAM:
|
||||
%%{auth_password_format, plain}.
|
||||
%%{auth_password_format, scram}.
|
||||
%%
|
||||
%% Define the FQDN if ejabberd doesn't detect it:
|
||||
%%{fqdn, "server3.example.com"}.
|
||||
|
||||
%%
|
||||
%% Authentication using external script
|
||||
%% Make sure the script is executable by ejabberd.
|
||||
%%
|
||||
%%{auth_method, external}.
|
||||
%%{extauth_program, "/path/to/authentication/script"}.
|
||||
|
||||
%%
|
||||
%% Authentication using ODBC
|
||||
%% Remember to setup a database in the next section.
|
||||
%%
|
||||
%%{auth_method, odbc}.
|
||||
|
||||
%%
|
||||
%% Authentication using PAM
|
||||
%%
|
||||
%%{auth_method, pam}.
|
||||
%%{pam_service, "pamservicename"}.
|
||||
|
||||
%%
|
||||
%% Authentication using LDAP
|
||||
%%
|
||||
%%{auth_method, ldap}.
|
||||
%%
|
||||
%% List of LDAP servers:
|
||||
%%{ldap_servers, ["localhost"]}.
|
||||
%%
|
||||
%% Encryption of connection to LDAP servers:
|
||||
%%{ldap_encrypt, none}.
|
||||
%%{ldap_encrypt, tls}.
|
||||
%%
|
||||
%% Port to connect to on LDAP servers:
|
||||
%%{ldap_port, 389}.
|
||||
%%{ldap_port, 636}.
|
||||
%%
|
||||
%% LDAP manager:
|
||||
%%{ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%
|
||||
%% Password of LDAP manager:
|
||||
%%{ldap_password, "******"}.
|
||||
%%
|
||||
%% Search base of LDAP directory:
|
||||
%%{ldap_base, "dc=example,dc=com"}.
|
||||
%%
|
||||
%% LDAP attribute that holds user ID:
|
||||
%%{ldap_uids, [{"mail", "%u@mail.example.org"}]}.
|
||||
%%
|
||||
%% LDAP filter:
|
||||
%%{ldap_filter, "(objectClass=shadowAccount)"}.
|
||||
|
||||
%%
|
||||
%% Anonymous login support:
|
||||
%% auth_method: anonymous
|
||||
%% anonymous_protocol: sasl_anon | login_anon | both
|
||||
%% allow_multiple_connections: true | false
|
||||
%%
|
||||
%%{host_config, "public.example.org", [{auth_method, anonymous},
|
||||
%% {allow_multiple_connections, false},
|
||||
%% {anonymous_protocol, sasl_anon}]}.
|
||||
%%
|
||||
%% To use both anonymous and internal authentication:
|
||||
%%
|
||||
%%{host_config, "public.example.org", [{auth_method, [internal, anonymous]}]}.
|
||||
|
||||
|
||||
%%%. ==============
|
||||
%%%' DATABASE SETUP
|
||||
|
||||
%% ejabberd by default uses the internal Mnesia database,
|
||||
%% so you do not necessarily need this section.
|
||||
%% This section provides configuration examples in case
|
||||
%% you want to use other database backends.
|
||||
%% Please consult the ejabberd Guide for details on database creation.
|
||||
|
||||
%%
|
||||
%% MySQL server:
|
||||
%%
|
||||
%%{odbc_server, {mysql, "server", "database", "username", "password"}}.
|
||||
%%
|
||||
%% If you want to specify the port:
|
||||
%%{odbc_server, {mysql, "server", 1234, "database", "username", "password"}}.
|
||||
|
||||
%%
|
||||
%% PostgreSQL server:
|
||||
%%
|
||||
%%{odbc_server, {pgsql, "server", "database", "username", "password"}}.
|
||||
%%
|
||||
%% If you want to specify the port:
|
||||
%%{odbc_server, {pgsql, "server", 1234, "database", "username", "password"}}.
|
||||
%%
|
||||
%% If you use PostgreSQL, have a large database, and need a
|
||||
%% faster but inexact replacement for "select count(*) from users"
|
||||
%%
|
||||
%%{pgsql_users_number_estimate, true}.
|
||||
|
||||
%%
|
||||
%% ODBC compatible or MSSQL server:
|
||||
%%
|
||||
%%{odbc_server, "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"}.
|
||||
|
||||
%%
|
||||
%% Number of connections to open to the database for each virtual host
|
||||
%%
|
||||
%%{odbc_pool_size, 10}.
|
||||
|
||||
%%
|
||||
%% Interval to make a dummy SQL request to keep the connections to the
|
||||
%% database alive. Specify in seconds: for example 28800 means 8 hours
|
||||
%%
|
||||
%%{odbc_keepalive_interval, undefined}.
|
||||
|
||||
|
||||
%%%. ===============
|
||||
%%%' TRAFFIC SHAPERS
|
||||
|
||||
%%
|
||||
%% The "normal" shaper limits traffic speed to 1000 B/s
|
||||
%%
|
||||
{shaper, normal, {maxrate, 1000}}.
|
||||
|
||||
%%
|
||||
%% The "fast" shaper limits traffic speed to 50000 B/s
|
||||
%%
|
||||
{shaper, fast, {maxrate, 50000}}.
|
||||
|
||||
%%
|
||||
%% This option specifies the maximum number of elements in the queue
|
||||
%% of the FSM. Refer to the documentation for details.
|
||||
%%
|
||||
{max_fsm_queue, 1000}.
|
||||
|
||||
|
||||
%%%. ====================
|
||||
%%%' ACCESS CONTROL LISTS
|
||||
|
||||
%%
|
||||
%% The 'admin' ACL grants administrative privileges to XMPP accounts.
|
||||
%% You can put here as many accounts as you want.
|
||||
%%
|
||||
%%{acl, admin, {user, "aleksey", "localhost"}}.
|
||||
%%{acl, admin, {user, "ermine", "example.org"}}.
|
||||
|
||||
%%
|
||||
%% Blocked users
|
||||
%%
|
||||
%%{acl, blocked, {user, "baduser", "example.org"}}.
|
||||
%%{acl, blocked, {user, "test"}}.
|
||||
|
||||
%%
|
||||
%% Local users: don't modify this line.
|
||||
%%
|
||||
{acl, local, {user_regexp, ""}}.
|
||||
|
||||
%%
|
||||
%% More examples of ACLs
|
||||
%%
|
||||
%%{acl, jabberorg, {server, "jabber.org"}}.
|
||||
%%{acl, aleksey, {user, "aleksey", "jabber.ru"}}.
|
||||
%%{acl, test, {user_regexp, "^test"}}.
|
||||
%%{acl, test, {user_glob, "test*"}}.
|
||||
|
||||
%%
|
||||
%% Define specific ACLs in a virtual host.
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [
|
||||
%% {acl, admin, {user, "bob-local", "localhost"}}
|
||||
%% ]
|
||||
%%}.
|
||||
|
||||
|
||||
%%%. ============
|
||||
%%%' ACCESS RULES
|
||||
|
||||
%% Maximum number of simultaneous sessions allowed for a single user:
|
||||
{access, max_user_sessions, [{10, all}]}.
|
||||
|
||||
%% Maximum number of offline messages that users can have:
|
||||
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
|
||||
|
||||
%% This rule allows access only for local users:
|
||||
{access, local, [{allow, local}]}.
|
||||
|
||||
%% Only non-blocked users can use c2s connections:
|
||||
{access, c2s, [{deny, blocked},
|
||||
{allow, all}]}.
|
||||
|
||||
%% For C2S connections, all users except admins use the "normal" shaper
|
||||
{access, c2s_shaper, [{none, admin},
|
||||
{normal, all}]}.
|
||||
|
||||
%% All S2S connections use the "fast" shaper
|
||||
{access, s2s_shaper, [{fast, all}]}.
|
||||
|
||||
%% Only admins can send announcement messages:
|
||||
{access, announce, [{allow, admin}]}.
|
||||
|
||||
%% Only admins can use the configuration interface:
|
||||
{access, configure, [{allow, admin}]}.
|
||||
|
||||
%% Admins of this server are also admins of the MUC service:
|
||||
{access, muc_admin, [{allow, admin}]}.
|
||||
|
||||
%% Only accounts of the local ejabberd server can create rooms:
|
||||
{access, muc_create, [{allow, local}]}.
|
||||
|
||||
%% All users are allowed to use the MUC service:
|
||||
{access, muc, [{allow, all}]}.
|
||||
|
||||
%% Only accounts on the local ejabberd server can create Pubsub nodes:
|
||||
{access, pubsub_createnode, [{allow, local}]}.
|
||||
|
||||
%% In-band registration allows registration of any possible username.
|
||||
%% To disable in-band registration, replace 'allow' with 'deny'.
|
||||
{access, register, [{allow, all}]}.
|
||||
|
||||
%% By default the frequency of account registrations from the same IP
|
||||
%% is limited to 1 account every 10 minutes. To disable, specify: infinity
|
||||
%%{registration_timeout, 600}.
|
||||
|
||||
%%
|
||||
%% Define specific Access Rules in a virtual host.
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [
|
||||
%% {access, c2s, [{allow, admin}, {deny, all}]},
|
||||
%% {access, register, [{deny, all}]}
|
||||
%% ]
|
||||
%%}.
|
||||
|
||||
|
||||
%%%. ================
|
||||
%%%' DEFAULT LANGUAGE
|
||||
|
||||
%%
|
||||
%% language: Default language used for server messages.
|
||||
%%
|
||||
{language, "en"}.
|
||||
|
||||
%%
|
||||
%% Set a different default language in a virtual host.
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [{language, "ru"}]
|
||||
%%}.
|
||||
|
||||
|
||||
%%%. =======
|
||||
%%%' CAPTCHA
|
||||
|
||||
%%
|
||||
%% Full path to a script that generates the image.
|
||||
%%
|
||||
%%{captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}.
|
||||
|
||||
%%
|
||||
%% Host for the URL and port where ejabberd listens for CAPTCHA requests.
|
||||
%%
|
||||
%%{captcha_host, "example.org:5280"}.
|
||||
|
||||
%%
|
||||
%% Limit CAPTCHA calls per minute for JID/IP to avoid DoS.
|
||||
%%
|
||||
%%{captcha_limit, 5}.
|
||||
|
||||
%%%. =======
|
||||
%%%' MODULES
|
||||
|
||||
%%
|
||||
%% Modules enabled in all ejabberd virtual hosts.
|
||||
%%
|
||||
{modules,
|
||||
[
|
||||
{mod_adhoc, []},
|
||||
{mod_announce, [{access, announce}]}, % recommends mod_adhoc
|
||||
{mod_blocking,[]}, % requires mod_privacy
|
||||
{mod_caps, []},
|
||||
{mod_configure,[]}, % requires mod_adhoc
|
||||
{mod_disco, []},
|
||||
%%{mod_echo, [{host, "echo.localhost"}]},
|
||||
{mod_irc, []},
|
||||
{mod_http_bind, []},
|
||||
%%{mod_http_fileserver, [
|
||||
%% {docroot, "/var/www"},
|
||||
%% {accesslog, "/var/log/ejabberd/access.log"}
|
||||
%% ]},
|
||||
{mod_last, []},
|
||||
{mod_muc, [
|
||||
%%{host, "conference.@HOST@"},
|
||||
{access, muc},
|
||||
{access_create, muc_create},
|
||||
{access_persistent, muc_create},
|
||||
{access_admin, muc_admin}
|
||||
]},
|
||||
%%{mod_muc_log,[]},
|
||||
{mod_offline, [{access_max_user_messages, max_user_offline_messages}]},
|
||||
{mod_ping, []},
|
||||
%%{mod_pres_counter,[{count, 5}, {interval, 60}]},
|
||||
{mod_privacy, []},
|
||||
{mod_private, []},
|
||||
%%{mod_proxy65,[]},
|
||||
{mod_pubsub, [
|
||||
{access_createnode, pubsub_createnode},
|
||||
{ignore_pep_from_offline, true}, % reduces resource comsumption, but XEP incompliant
|
||||
%%{ignore_pep_from_offline, false}, % XEP compliant, but increases resource comsumption
|
||||
{last_item_cache, false},
|
||||
{plugins, ["flat", "hometree", "pep"]} % pep requires mod_caps
|
||||
]},
|
||||
{mod_register, [
|
||||
%%
|
||||
%% Protect In-Band account registrations with CAPTCHA.
|
||||
%%
|
||||
%%{captcha_protected, true},
|
||||
|
||||
%%
|
||||
%% Set the minimum informational entropy for passwords.
|
||||
%%
|
||||
%%{password_strength, 32},
|
||||
|
||||
%%
|
||||
%% After successful registration, the user receives
|
||||
%% a message with this subject and body.
|
||||
%%
|
||||
{welcome_message, {"Welcome!",
|
||||
"Hi.\nWelcome to this XMPP server."}},
|
||||
|
||||
%%
|
||||
%% When a user registers, send a notification to
|
||||
%% these XMPP accounts.
|
||||
%%
|
||||
%%{registration_watchers, ["admin1@example.org"]},
|
||||
|
||||
%%
|
||||
%% Only clients in the server machine can register accounts
|
||||
%%
|
||||
{ip_access, [{allow, "127.0.0.0/8"},
|
||||
{deny, "0.0.0.0/0"}]},
|
||||
|
||||
%%
|
||||
%% Local c2s or remote s2s users cannot register accounts
|
||||
%%
|
||||
%%{access_from, deny},
|
||||
|
||||
{access, register}
|
||||
]},
|
||||
%%{mod_register_web, [
|
||||
%%
|
||||
%% When a user registers, send a notification to
|
||||
%% these XMPP accounts.
|
||||
%%
|
||||
%%{registration_watchers, ["admin1@example.org"]}
|
||||
%% ]},
|
||||
{mod_roster, []},
|
||||
%%{mod_service_log,[]},
|
||||
{mod_shared_roster,[]},
|
||||
{mod_stats, []},
|
||||
{mod_time, []},
|
||||
{mod_vcard, []},
|
||||
{mod_version, []}
|
||||
]}.
|
||||
|
||||
%%
|
||||
%% Enable modules with custom options in a specific virtual host
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [{{add, modules},
|
||||
%% [
|
||||
%% {mod_echo, [{host, "mirror.localhost"}]}
|
||||
%% ]
|
||||
%% }
|
||||
%% ]}.
|
||||
|
||||
|
||||
%%%.
|
||||
%%%'
|
||||
|
||||
%%% $Id$
|
||||
|
||||
%%% Local Variables:
|
||||
%%% mode: erlang
|
||||
%%% End:
|
||||
%%% vim: set filetype=erlang tabstop=8 foldmarker=%%%',%%%. foldmethod=marker:
|
||||
@@ -0,0 +1,612 @@
|
||||
###
|
||||
### ejabberd configuration file
|
||||
###
|
||||
###
|
||||
|
||||
### The parameters used in this configuration file are explained in more detail
|
||||
### in the ejabberd Installation and Operation Guide.
|
||||
### Please consult the Guide in case of doubts, it is included with
|
||||
### your copy of ejabberd, and is also available online at
|
||||
### http://www.process-one.net/en/ejabberd/docs/
|
||||
|
||||
### The configuration file is written in YAML.
|
||||
### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
|
||||
### However, ejabberd treats different literals as different types:
|
||||
###
|
||||
### - unquoted or single-quoted strings. They are called "atoms".
|
||||
### Example: dog, 'Jupiter', '3.14159', YELLOW
|
||||
###
|
||||
### - numeric literals. Example: 3, -45.0, .0
|
||||
###
|
||||
### - quoted or folded strings.
|
||||
### Examples of quoted string: "Lizzard", "orange".
|
||||
### Example of folded string:
|
||||
### > Art thou not Romeo,
|
||||
### and a Montague?
|
||||
|
||||
### =========
|
||||
### DEBUGGING
|
||||
|
||||
##
|
||||
## loglevel: Verbosity of log files generated by ejabberd.
|
||||
## 0: No ejabberd log at all (not recommended)
|
||||
## 1: Critical
|
||||
## 2: Error
|
||||
## 3: Warning
|
||||
## 4: Info
|
||||
## 5: Debug
|
||||
##
|
||||
loglevel: 4
|
||||
|
||||
##
|
||||
## watchdog_admins: Only useful for developers: if an ejabberd process
|
||||
## consumes a lot of memory, send live notifications to these XMPP
|
||||
## accounts.
|
||||
##
|
||||
## watchdog_admins:
|
||||
## - "bob@example.com"
|
||||
|
||||
|
||||
### ================
|
||||
### SERVED HOSTNAMES
|
||||
|
||||
##
|
||||
## hosts: Domains served by ejabberd.
|
||||
## You can define one or several, for example:
|
||||
## hosts:
|
||||
## - "example.net"
|
||||
## - "example.com"
|
||||
## - "example.org"
|
||||
##
|
||||
hosts:
|
||||
- "localhost"
|
||||
|
||||
##
|
||||
## route_subdomains: Delegate subdomains to other XMPP servers.
|
||||
## For example, if this ejabberd serves example.org and you want
|
||||
## to allow communication with an XMPP server called im.example.org.
|
||||
##
|
||||
## route_subdomains: s2s
|
||||
|
||||
### ===============
|
||||
### LISTENING PORTS
|
||||
|
||||
##
|
||||
## listen: The ports ejabberd will listen on, which service each is handled
|
||||
## by and what options to start it with.
|
||||
##
|
||||
listen:
|
||||
-
|
||||
port: 5222
|
||||
module: ejabberd_c2s
|
||||
##
|
||||
## If TLS is compiled in and you installed a SSL
|
||||
## certificate, specify the full path to the
|
||||
## file and uncomment this line:
|
||||
##
|
||||
## certfile: "/path/to/ssl.pem"
|
||||
## starttls: true
|
||||
max_stanza_size: 65536
|
||||
shaper: c2s_shaper
|
||||
access: c2s
|
||||
-
|
||||
port: 5269
|
||||
module: ejabberd_s2s_in
|
||||
##
|
||||
## ejabberd_service: Interact with external components (transports, ...)
|
||||
##
|
||||
## -
|
||||
## port: 8888
|
||||
## module: ejabberd_service
|
||||
## access: all
|
||||
## shaper_rule: fast
|
||||
## ip: "127.0.0.1"
|
||||
## hosts:
|
||||
## "icq.example.org":
|
||||
## password: "secret"
|
||||
## "sms.example.org":
|
||||
## password: "secret"
|
||||
|
||||
##
|
||||
## ejabberd_stun: Handles STUN Binding requests
|
||||
##
|
||||
## -
|
||||
## port: 3478
|
||||
## transport: udp
|
||||
## module: ejabberd_stun
|
||||
|
||||
##
|
||||
## To handle XML-RPC requests that provide admin credentials:
|
||||
##
|
||||
## -
|
||||
## port: 4560
|
||||
## module: ejabberd_xmlrpc
|
||||
-
|
||||
port: 5280
|
||||
module: ejabberd_http
|
||||
## request_handlers:
|
||||
## "/pub/archive": mod_http_fileserver
|
||||
web_admin: true
|
||||
http_poll: true
|
||||
http_bind: true
|
||||
## register: true
|
||||
captcha: true
|
||||
|
||||
##
|
||||
## s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
|
||||
## Allowed values are: false optional required required_trusted
|
||||
## You must specify a certificate file.
|
||||
##
|
||||
## s2s_use_starttls: optional
|
||||
|
||||
##
|
||||
## s2s_certfile: Specify a certificate file.
|
||||
##
|
||||
## s2s_certfile: "/path/to/ssl.pem"
|
||||
|
||||
##
|
||||
## domain_certfile: Specify a different certificate for each served hostname.
|
||||
##
|
||||
## host_config:
|
||||
## "example.org":
|
||||
## domain_certfile: "/path/to/example_org.pem"
|
||||
## "example.com":
|
||||
## domain_certfile: "/path/to/example_com.pem"
|
||||
|
||||
##
|
||||
## S2S whitelist or blacklist
|
||||
##
|
||||
## Default s2s policy for undefined hosts.
|
||||
##
|
||||
## s2s_policy: s2s_access
|
||||
|
||||
##
|
||||
## Outgoing S2S options
|
||||
##
|
||||
## Preferred address families (which to try first) and connect timeout
|
||||
## in milliseconds.
|
||||
##
|
||||
## outgoing_s2s_families:
|
||||
## - ipv4
|
||||
## - ipv6
|
||||
## outgoing_s2s_timeout: 10000
|
||||
|
||||
### ==============
|
||||
### AUTHENTICATION
|
||||
|
||||
##
|
||||
## auth_method: Method used to authenticate the users.
|
||||
## The default method is the internal.
|
||||
## If you want to use a different method,
|
||||
## comment this line and enable the correct ones.
|
||||
##
|
||||
auth_method: internal
|
||||
|
||||
##
|
||||
## Store the plain passwords or hashed for SCRAM:
|
||||
## auth_password_format: plain
|
||||
## auth_password_format: scram
|
||||
##
|
||||
## Define the FQDN if ejabberd doesn't detect it:
|
||||
## fqdn: "server3.example.com"
|
||||
|
||||
##
|
||||
## Authentication using external script
|
||||
## Make sure the script is executable by ejabberd.
|
||||
##
|
||||
## auth_method: external
|
||||
## extauth_program: "/path/to/authentication/script"
|
||||
|
||||
##
|
||||
## Authentication using ODBC
|
||||
## Remember to setup a database in the next section.
|
||||
##
|
||||
## auth_method: odbc
|
||||
|
||||
##
|
||||
## Authentication using PAM
|
||||
##
|
||||
## auth_method: pam
|
||||
## pam_service: "pamservicename"
|
||||
|
||||
##
|
||||
## Authentication using LDAP
|
||||
##
|
||||
## auth_method: ldap
|
||||
##
|
||||
## List of LDAP servers:
|
||||
## ldap_servers:
|
||||
## - "localhost"
|
||||
##
|
||||
## Encryption of connection to LDAP servers:
|
||||
## ldap_encrypt: none
|
||||
## ldap_encrypt: tls
|
||||
##
|
||||
## Port to connect to on LDAP servers:
|
||||
## ldap_port: 389
|
||||
## ldap_port: 636
|
||||
##
|
||||
## LDAP manager:
|
||||
## ldap_rootdn: "dc=example,dc=com"
|
||||
##
|
||||
## Password of LDAP manager:
|
||||
## ldap_password: "******"
|
||||
##
|
||||
## Search base of LDAP directory:
|
||||
## ldap_base: "dc=example,dc=com"
|
||||
##
|
||||
## LDAP attribute that holds user ID:
|
||||
## ldap_uids:
|
||||
## - "mail": "%u@mail.example.org"
|
||||
##
|
||||
## LDAP filter:
|
||||
## ldap_filter: "(objectClass=shadowAccount)"
|
||||
|
||||
##
|
||||
## Anonymous login support:
|
||||
## auth_method: anonymous
|
||||
## anonymous_protocol: sasl_anon | login_anon | both
|
||||
## allow_multiple_connections: true | false
|
||||
##
|
||||
## host_config:
|
||||
## "public.example.org":
|
||||
## auth_method: anonymous
|
||||
## allow_multiple_connections: false
|
||||
## anonymous_protocol: sasl_anon
|
||||
##
|
||||
## To use both anonymous and internal authentication:
|
||||
##
|
||||
## host_config:
|
||||
## "public.example.org":
|
||||
## auth_method:
|
||||
## - internal
|
||||
## - anonymous
|
||||
|
||||
### ==============
|
||||
### DATABASE SETUP
|
||||
|
||||
## ejabberd by default uses the internal Mnesia database,
|
||||
## so you do not necessarily need this section.
|
||||
## This section provides configuration examples in case
|
||||
## you want to use other database backends.
|
||||
## Please consult the ejabberd Guide for details on database creation.
|
||||
|
||||
##
|
||||
## MySQL server:
|
||||
##
|
||||
## odbc_type: mysql
|
||||
## odbc_server: "server"
|
||||
## odbc_database: "database"
|
||||
## odbc_username: "username"
|
||||
## odbc_password: "password"
|
||||
##
|
||||
## If you want to specify the port:
|
||||
## odbc_port: 1234
|
||||
|
||||
##
|
||||
## PostgreSQL server:
|
||||
##
|
||||
## odbc_type: pgsql
|
||||
## odbc_server: "server"
|
||||
## odbc_database: "database"
|
||||
## odbc_username: "username"
|
||||
## odbc_password: "password"
|
||||
##
|
||||
## If you want to specify the port:
|
||||
## odbc_port: 1234
|
||||
##
|
||||
## If you use PostgreSQL, have a large database, and need a
|
||||
## faster but inexact replacement for "select count(*) from users"
|
||||
##
|
||||
## pgsql_users_number_estimate: true
|
||||
|
||||
##
|
||||
## ODBC compatible or MSSQL server:
|
||||
##
|
||||
## odbc_type: odbc
|
||||
## odbc_server: "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"
|
||||
|
||||
##
|
||||
## Number of connections to open to the database for each virtual host
|
||||
##
|
||||
## odbc_pool_size: 10
|
||||
|
||||
##
|
||||
## Interval to make a dummy SQL request to keep the connections to the
|
||||
## database alive. Specify in seconds: for example 28800 means 8 hours
|
||||
##
|
||||
## odbc_keepalive_interval: undefined
|
||||
|
||||
### ===============
|
||||
### TRAFFIC SHAPERS
|
||||
|
||||
shaper:
|
||||
##
|
||||
## The "normal" shaper limits traffic speed to 1000 B/s
|
||||
##
|
||||
normal: 1000
|
||||
|
||||
##
|
||||
## The "fast" shaper limits traffic speed to 50000 B/s
|
||||
##
|
||||
fast: 50000
|
||||
|
||||
##
|
||||
## This option specifies the maximum number of elements in the queue
|
||||
## of the FSM. Refer to the documentation for details.
|
||||
##
|
||||
max_fsm_queue: 1000
|
||||
|
||||
###. ====================
|
||||
###' ACCESS CONTROL LISTS
|
||||
acl:
|
||||
##
|
||||
## The 'admin' ACL grants administrative privileges to XMPP accounts.
|
||||
## You can put here as many accounts as you want.
|
||||
##
|
||||
## admin:
|
||||
## user:
|
||||
## - "aleksey": "localhost"
|
||||
## - "ermine": "example.org"
|
||||
##
|
||||
## Blocked users
|
||||
##
|
||||
## blocked:
|
||||
## user:
|
||||
## - "baduser": "example.org"
|
||||
## - "test"
|
||||
|
||||
## Local users: don't modify this.
|
||||
##
|
||||
local:
|
||||
user_regexp: ""
|
||||
|
||||
##
|
||||
## More examples of ACLs
|
||||
##
|
||||
## jabberorg:
|
||||
## server:
|
||||
## - "jabber.org"
|
||||
## aleksey:
|
||||
## user:
|
||||
## - "aleksey": "jabber.ru"
|
||||
## test:
|
||||
## user_regexp: "^test"
|
||||
## user_glob: "test*"
|
||||
|
||||
##
|
||||
## Loopback network
|
||||
##
|
||||
loopback:
|
||||
ip:
|
||||
- "127.0.0.0/8"
|
||||
|
||||
##
|
||||
## Bad XMPP servers
|
||||
##
|
||||
## bad_servers:
|
||||
## server:
|
||||
## - "xmpp.zombie.org"
|
||||
## - "xmpp.spam.com"
|
||||
|
||||
##
|
||||
## Define specific ACLs in a virtual host.
|
||||
##
|
||||
## host_config:
|
||||
## "localhost":
|
||||
## acl:
|
||||
## admin:
|
||||
## user:
|
||||
## - "bob-local": "localhost"
|
||||
|
||||
### ============
|
||||
### ACCESS RULES
|
||||
access:
|
||||
## Maximum number of simultaneous sessions allowed for a single user:
|
||||
max_user_sessions:
|
||||
all: 10
|
||||
## Maximum number of offline messages that users can have:
|
||||
max_user_offline_messages:
|
||||
admin: 5000
|
||||
all: 100
|
||||
## This rule allows access only for local users:
|
||||
local:
|
||||
local: allow
|
||||
## Only non-blocked users can use c2s connections:
|
||||
c2s:
|
||||
blocked: deny
|
||||
all: allow
|
||||
## For C2S connections, all users except admins use the "normal" shaper
|
||||
c2s_shaper:
|
||||
admin: none
|
||||
all: normal
|
||||
## All S2S connections use the "fast" shaper
|
||||
s2s_shaper:
|
||||
all: fast
|
||||
## Only admins can send announcement messages:
|
||||
announce:
|
||||
admin: allow
|
||||
## Only admins can use the configuration interface:
|
||||
configure:
|
||||
admin: allow
|
||||
## Admins of this server are also admins of the MUC service:
|
||||
muc_admin:
|
||||
admin: allow
|
||||
## Only accounts of the local ejabberd server can create rooms:
|
||||
muc_create:
|
||||
local: allow
|
||||
## All users are allowed to use the MUC service:
|
||||
muc:
|
||||
all: allow
|
||||
## Only accounts on the local ejabberd server can create Pubsub nodes:
|
||||
pubsub_createnode:
|
||||
local: allow
|
||||
## In-band registration allows registration of any possible username.
|
||||
## To disable in-band registration, replace 'allow' with 'deny'.
|
||||
register:
|
||||
all: allow
|
||||
## Only allow to register from localhost
|
||||
trusted_network:
|
||||
loopback: allow
|
||||
## Do not establish S2S connections with bad servers
|
||||
## s2s_access:
|
||||
## bad_servers: deny
|
||||
## all: allow
|
||||
|
||||
## By default the frequency of account registrations from the same IP
|
||||
## is limited to 1 account every 10 minutes. To disable, specify: infinity
|
||||
## registration_timeout: 600
|
||||
|
||||
##
|
||||
## Define specific Access Rules in a virtual host.
|
||||
##
|
||||
## host_config:
|
||||
## "localhost":
|
||||
## access:
|
||||
## c2s:
|
||||
## admin: allow
|
||||
## all: deny
|
||||
## register:
|
||||
## all: deny
|
||||
|
||||
### ================
|
||||
### DEFAULT LANGUAGE
|
||||
|
||||
##
|
||||
## language: Default language used for server messages.
|
||||
##
|
||||
language: "en"
|
||||
|
||||
##
|
||||
## Set a different default language in a virtual host.
|
||||
##
|
||||
## host_config:
|
||||
## "localhost":
|
||||
## language: "ru"
|
||||
|
||||
### =======
|
||||
### CAPTCHA
|
||||
|
||||
##
|
||||
## Full path to a script that generates the image.
|
||||
##
|
||||
## captcha_cmd: "/lib/ejabberd/priv/bin/captcha.sh"
|
||||
|
||||
##
|
||||
## Host for the URL and port where ejabberd listens for CAPTCHA requests.
|
||||
##
|
||||
## captcha_host: "example.org:5280"
|
||||
|
||||
##
|
||||
## Limit CAPTCHA calls per minute for JID/IP to avoid DoS.
|
||||
##
|
||||
## captcha_limit: 5
|
||||
|
||||
### =======
|
||||
### MODULES
|
||||
|
||||
##
|
||||
## Modules enabled in all ejabberd virtual hosts.
|
||||
##
|
||||
modules:
|
||||
mod_adhoc: {}
|
||||
mod_announce: # recommends mod_adhoc
|
||||
access: announce
|
||||
mod_blocking: {} # requires mod_privacy
|
||||
mod_caps: {}
|
||||
mod_configure: {} # requires mod_adhoc
|
||||
mod_disco: {}
|
||||
## mod_echo: {}
|
||||
mod_irc: {}
|
||||
mod_http_bind: {}
|
||||
## mod_http_fileserver:
|
||||
## docroot: "/var/www"
|
||||
## accesslog: "/var/log/ejabberd/access.log"
|
||||
mod_last: {}
|
||||
mod_muc:
|
||||
## host: "conference.@HOST@"
|
||||
access: muc
|
||||
access_create: muc_create
|
||||
access_persistent: muc_create
|
||||
access_admin: muc_admin
|
||||
## mod_muc_log: {}
|
||||
mod_offline:
|
||||
access_max_user_messages: max_user_offline_messages
|
||||
mod_ping: {}
|
||||
## mod_pres_counter:
|
||||
## count: 5
|
||||
## interval: 60
|
||||
mod_privacy: {}
|
||||
mod_private: {}
|
||||
## mod_proxy65: {}
|
||||
mod_pubsub:
|
||||
access_createnode: pubsub_createnode
|
||||
## reduces resource comsumption, but XEP incompliant
|
||||
ignore_pep_from_offline: true
|
||||
## XEP compliant, but increases resource comsumption
|
||||
## ignore_pep_from_offline: false
|
||||
last_item_cache: false
|
||||
plugins:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep" # pep requires mod_caps
|
||||
mod_register:
|
||||
##
|
||||
## Protect In-Band account registrations with CAPTCHA.
|
||||
##
|
||||
## captcha_protected: true
|
||||
|
||||
##
|
||||
## Set the minimum informational entropy for passwords.
|
||||
##
|
||||
## password_strength: 32
|
||||
|
||||
##
|
||||
## After successful registration, the user receives
|
||||
## a message with this subject and body.
|
||||
##
|
||||
welcome_message:
|
||||
subject: "Welcome!"
|
||||
body: |-
|
||||
Hi.
|
||||
Welcome to this XMPP server.
|
||||
|
||||
##
|
||||
## When a user registers, send a notification to
|
||||
## these XMPP accounts.
|
||||
##
|
||||
## registration_watchers:
|
||||
## - "admin1@example.org"
|
||||
|
||||
##
|
||||
## Only clients in the server machine can register accounts
|
||||
##
|
||||
ip_access: trusted_network
|
||||
|
||||
##
|
||||
## Local c2s or remote s2s users cannot register accounts
|
||||
##
|
||||
## access_from: deny
|
||||
|
||||
access: register
|
||||
mod_roster: {}
|
||||
mod_shared_roster: {}
|
||||
mod_stats: {}
|
||||
mod_time: {}
|
||||
mod_vcard: {}
|
||||
mod_version: {}
|
||||
|
||||
##
|
||||
## Enable modules with custom options in a specific virtual host
|
||||
##
|
||||
## host_config:
|
||||
## "localhost":
|
||||
## add:
|
||||
## modules:
|
||||
## mod_echo:
|
||||
## host: "mirror.localhost"
|
||||
|
||||
### Local Variables:
|
||||
### mode: yaml
|
||||
### End:
|
||||
### vim: set filetype=yaml tabstop=8
|
||||
@@ -65,7 +65,7 @@ if [ -f "$EJABBERDCTL_CONFIG_PATH" ] ; then
|
||||
. "$EJABBERDCTL_CONFIG_PATH"
|
||||
fi
|
||||
if [ "$EJABBERD_CONFIG_PATH" = "" ] ; then
|
||||
EJABBERD_CONFIG_PATH=$ETCDIR/ejabberd.cfg
|
||||
EJABBERD_CONFIG_PATH=$ETCDIR/ejabberd.yml
|
||||
fi
|
||||
if [ "$LOGS_DIR" = "" ] ; then
|
||||
LOGS_DIR={{localstatedir}}/log/ejabberd
|
||||
|
||||
@@ -19,15 +19,12 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-record(config, {key :: any(), value :: any()}).
|
||||
|
||||
-record(local_config, {key :: any(), value :: any()}).
|
||||
|
||||
-type config() :: #config{}.
|
||||
-type local_config() :: #local_config{}.
|
||||
|
||||
-record(state,
|
||||
{opts = [] :: [acl:acl() | config() | local_config()],
|
||||
{opts = [] :: [acl:acl() | local_config()],
|
||||
hosts = [] :: [binary()],
|
||||
override_local = false :: boolean(),
|
||||
override_global = false :: boolean(),
|
||||
|
||||
+5
-5
@@ -41,17 +41,17 @@
|
||||
-else.
|
||||
|
||||
-define(DEBUG(Format, Args),
|
||||
ejabberd_logger:debug_msg(?MODULE, ?LINE, Format, Args)).
|
||||
p1_logger:debug_msg(?MODULE, ?LINE, Format, Args)).
|
||||
|
||||
-define(INFO_MSG(Format, Args),
|
||||
ejabberd_logger:info_msg(?MODULE, ?LINE, Format, Args)).
|
||||
p1_logger:info_msg(?MODULE, ?LINE, Format, Args)).
|
||||
|
||||
-define(WARNING_MSG(Format, Args),
|
||||
ejabberd_logger:warning_msg(?MODULE, ?LINE, Format, Args)).
|
||||
p1_logger:warning_msg(?MODULE, ?LINE, Format, Args)).
|
||||
|
||||
-define(ERROR_MSG(Format, Args),
|
||||
ejabberd_logger:error_msg(?MODULE, ?LINE, Format, Args)).
|
||||
p1_logger:error_msg(?MODULE, ?LINE, Format, Args)).
|
||||
|
||||
-define(CRITICAL_MSG(Format, Args),
|
||||
ejabberd_logger:critical_msg(?MODULE, ?LINE, Format, Args)).
|
||||
p1_logger:critical_msg(?MODULE, ?LINE, Format, Args)).
|
||||
-endif.
|
||||
|
||||
@@ -50,6 +50,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, _} ->
|
||||
|
||||
+7
-4
@@ -30,9 +30,9 @@ Macros = lists:flatmap(
|
||||
|
||||
DebugInfo = case lists:keysearch(debug, 1, Cfg) of
|
||||
{value, {debug, true}} ->
|
||||
[debug_info];
|
||||
[];
|
||||
_ ->
|
||||
[]
|
||||
[no_debug_info]
|
||||
end,
|
||||
|
||||
HiPE = case lists:keysearch(hipe, 1, Cfg) of
|
||||
@@ -52,11 +52,11 @@ SrcDirs = lists:foldl(
|
||||
Acc
|
||||
end, [], Cfg),
|
||||
|
||||
Deps = [{p1_logger, ".*", {git, "git://github.com/processone/p1_logger"}},
|
||||
{p1_cache_tab, ".*", {git, "git://github.com/processone/cache_tab"}},
|
||||
Deps = [{p1_cache_tab, ".*", {git, "git://github.com/processone/cache_tab"}},
|
||||
{p1_tls, ".*", {git, "git://github.com/processone/tls"}},
|
||||
{p1_stringprep, ".*", {git, "git://github.com/processone/stringprep"}},
|
||||
{p1_xml, ".*", {git, "git://github.com/processone/xml"}},
|
||||
{p1_yaml, ".*", {git, "git://github.com/processone/p1_yaml"}},
|
||||
{xmlrpc, ".*", {git, "git://github.com/rds13/xmlrpc"}}],
|
||||
|
||||
ConfigureCmd = fun(Pkg, Flags) ->
|
||||
@@ -98,6 +98,8 @@ CfgDeps = lists:flatmap(
|
||||
{lhttpc, ".*", {git, "git://github.com/esl/lhttpc"}}];
|
||||
({lager, true}) ->
|
||||
[{lager, ".*", {git, "git://github.com/basho/lager"}}];
|
||||
({lager, false}) ->
|
||||
[{p1_logger, ".*", {git, "git://github.com/processone/p1_logger"}}];
|
||||
(_) ->
|
||||
[]
|
||||
end, Cfg),
|
||||
@@ -118,6 +120,7 @@ CfgPostHooks = lists:flatmap(
|
||||
Config = [{erl_opts, Includes ++ Macros ++ HiPE ++ DebugInfo ++
|
||||
[{src_dirs, [asn1, src | SrcDirs]}]},
|
||||
{sub_dirs, ["rel"]},
|
||||
{keep_build_info, true},
|
||||
{ct_extra_params, "-include "
|
||||
++ filename:join([Cwd, "tools"]) ++ " "
|
||||
++ filename:join([Cwd, "deps", "p1_xml", "include"])},
|
||||
|
||||
@@ -28,7 +28,7 @@ ConfiguredOTPApps = lists:flatmap(
|
||||
|
||||
OTPApps = RequiredOTPApps ++ ConfiguredOTPApps,
|
||||
|
||||
DepRequiredApps = [p1_logger, p1_cache_tab, p1_tls, p1_stringprep, p1_xml, xmlrpc],
|
||||
DepRequiredApps = [p1_cache_tab, p1_tls, p1_stringprep, p1_xml, p1_yaml, xmlrpc],
|
||||
|
||||
DepConfiguredApps = lists:flatmap(
|
||||
fun({mysql, true}) -> [p1_mysql];
|
||||
@@ -39,7 +39,8 @@ DepConfiguredApps = lists:flatmap(
|
||||
({json, true}) -> [jiffy];
|
||||
({iconv, true}) -> [p1_iconv];
|
||||
({http, true}) -> [ibrowse, lhttpc];
|
||||
({lager, true}) -> [lager];
|
||||
({lager, true}) -> [lager, goldrush];
|
||||
({lager, false}) -> [p1_logger];
|
||||
(_) -> []
|
||||
end, Vars),
|
||||
|
||||
@@ -88,7 +89,7 @@ Overlay = [
|
||||
{template, "files/erl", "\{\{erts_vsn\}\}/bin/erl"},
|
||||
{template, "../ejabberdctl.template", "bin/ejabberdctl"},
|
||||
{copy, "../ejabberdctl.cfg.example", "etc/ejabberd/ejabberdctl.cfg"},
|
||||
{copy, "../ejabberd.cfg.example", "etc/ejabberd/ejabberd.cfg"},
|
||||
{copy, "../ejabberd.yml.example", "etc/ejabberd/ejabberd.yml"},
|
||||
{copy, "../inetrc", "etc/ejabberd/inetrc"},
|
||||
{copy, "files/install_upgrade.escript", "bin/install_upgrade.escript"}
|
||||
],
|
||||
|
||||
+345
-154
@@ -29,34 +29,38 @@
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, to_record/3, add/3, add_list/3,
|
||||
match_rule/3, match_acl/3]).
|
||||
add_local/3, add_list_local/3, load_from_config/0,
|
||||
match_rule/3, match_acl/3, transform_options/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(acl, {aclname, aclspec}).
|
||||
-record(access, {name :: access_name(),
|
||||
rules = [] :: [access_rule()]}).
|
||||
|
||||
-type regexp() :: binary().
|
||||
-type glob() :: binary().
|
||||
-type access_name() :: atom().
|
||||
-type access_rule() :: {atom(), any()}.
|
||||
-type host() :: binary().
|
||||
-type aclname() :: {atom(), binary() | global}.
|
||||
-type aclspec() :: all | none |
|
||||
{user, binary()} |
|
||||
{user, binary(), binary()} |
|
||||
{user, {binary(), host()} | binary()} |
|
||||
{server, binary()} |
|
||||
{resource, binary()} |
|
||||
{user_regexp, regexp()} |
|
||||
{shared_group, binary()} |
|
||||
{shared_group, binary(), binary()} |
|
||||
{user_regexp, regexp(), binary()} |
|
||||
{user_regexp, {regexp(), host()} | regexp()} |
|
||||
{shared_group, {binary(), host()} | binary()} |
|
||||
{user_regexp, {regexp(), host()} | regexp()} |
|
||||
{server_regexp, regexp()} |
|
||||
{resource_regexp, regexp()} |
|
||||
{node_regexp, regexp(), regexp()} |
|
||||
{user_glob, glob()} |
|
||||
{user_glob, glob(), binary()} |
|
||||
{node_regexp, {regexp(), regexp()}} |
|
||||
{user_glob, {glob(), host()} | glob()} |
|
||||
{server_glob, glob()} |
|
||||
{resource_glob, glob()} |
|
||||
{node_glob, glob(), glob()}.
|
||||
{ip, {inet:ip_address(), integer()}} |
|
||||
{node_glob, {glob(), glob()}}.
|
||||
|
||||
-type acl() :: #acl{aclname :: aclname(),
|
||||
aclspec :: aclspec()}.
|
||||
@@ -64,11 +68,23 @@
|
||||
-export_type([acl/0]).
|
||||
|
||||
start() ->
|
||||
case catch mnesia:table_info(acl, storage_type) of
|
||||
disc_copies ->
|
||||
mnesia:delete_table(acl);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
mnesia:create_table(acl,
|
||||
[{disc_copies, [node()]}, {type, bag},
|
||||
[{ram_copies, [node()]}, {type, bag},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, acl)}]),
|
||||
mnesia:create_table(access,
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, access)}]),
|
||||
mnesia:add_table_copy(acl, node(), ram_copies),
|
||||
update_table(),
|
||||
mnesia:add_table_copy(access, node(), ram_copies),
|
||||
load_from_config(),
|
||||
ok.
|
||||
|
||||
-spec to_record(binary(), atom(), aclspec()) -> acl().
|
||||
@@ -77,18 +93,49 @@ to_record(Host, ACLName, ACLSpec) ->
|
||||
#acl{aclname = {ACLName, Host},
|
||||
aclspec = normalize_spec(ACLSpec)}.
|
||||
|
||||
-spec add(binary(), aclname(), aclspec()) -> {atomic, ok} | {aborted, any()}.
|
||||
-spec add(binary(), aclname(), aclspec()) -> ok | {error, any()}.
|
||||
|
||||
add(Host, ACLName, ACLSpec) ->
|
||||
{ResL, BadNodes} = rpc:multicall(mnesia:system_info(running_db_nodes),
|
||||
?MODULE, add_local,
|
||||
[Host, ACLName, ACLSpec]),
|
||||
case lists:keyfind(aborted, 1, ResL) of
|
||||
false when BadNodes == [] ->
|
||||
ok;
|
||||
false ->
|
||||
{error, {failed_nodes, BadNodes}};
|
||||
Err ->
|
||||
{error, Err}
|
||||
end.
|
||||
|
||||
add_local(Host, ACLName, ACLSpec) ->
|
||||
F = fun () ->
|
||||
mnesia:write(#acl{aclname = {ACLName, Host},
|
||||
aclspec = normalize_spec(ACLSpec)})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec add_list(binary(), [acl()], boolean()) -> false | ok.
|
||||
-spec add_list(binary(), [acl()], boolean()) -> ok | {error, any()}.
|
||||
|
||||
add_list(Host, ACLs, Clear) ->
|
||||
{ResL, BadNodes} = rpc:multicall(mnesia:system_info(running_db_nodes),
|
||||
?MODULE, add_list_local,
|
||||
[Host, ACLs, Clear]),
|
||||
case lists:keyfind(aborted, 1, ResL) of
|
||||
false when BadNodes == [] ->
|
||||
ok;
|
||||
false ->
|
||||
{error, {failed_nodes, BadNodes}};
|
||||
Err ->
|
||||
{error, Err}
|
||||
end.
|
||||
|
||||
add_list_local(Host, ACLs, Clear) ->
|
||||
F = fun () ->
|
||||
if Clear ->
|
||||
Ks = mnesia:select(acl,
|
||||
@@ -112,135 +159,198 @@ add_list(Host, ACLs, Clear) ->
|
||||
end,
|
||||
ACLs)
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, _} -> ok;
|
||||
_ -> false
|
||||
mnesia:transaction(F).
|
||||
|
||||
-spec add_access(binary() | global,
|
||||
access_name(), [access_rule()]) -> ok | {error, any()}.
|
||||
|
||||
add_access(Host, Access, Rules) ->
|
||||
case mnesia:transaction(
|
||||
fun() ->
|
||||
mnesia:write(
|
||||
#access{name = {Access, Host},
|
||||
rules = Rules})
|
||||
end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
Err ->
|
||||
{error, Err}
|
||||
end.
|
||||
|
||||
normalize(A) -> jlib:nodeprep(iolist_to_binary(A)).
|
||||
-spec load_from_config() -> ok.
|
||||
|
||||
normalize_spec({A, B}) -> {A, normalize(B)};
|
||||
normalize_spec({A, B, C}) ->
|
||||
{A, normalize(B), normalize(C)};
|
||||
normalize_spec(all) -> all;
|
||||
normalize_spec(none) -> none.
|
||||
load_from_config() ->
|
||||
Hosts = [global|?MYHOSTS],
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ACLs = ejabberd_config:get_option(
|
||||
{acl, Host}, fun(V) -> V end, []),
|
||||
AccessRules = ejabberd_config:get_option(
|
||||
{access, Host}, fun(V) -> V end, []),
|
||||
lists:foreach(
|
||||
fun({ACLName, SpecList}) ->
|
||||
lists:foreach(
|
||||
fun({ACLType, ACLSpecs}) when is_list(ACLSpecs) ->
|
||||
lists:foreach(
|
||||
fun(ACLSpec) ->
|
||||
add(Host, ACLName,
|
||||
{ACLType, ACLSpec})
|
||||
end, lists:flatten(ACLSpecs));
|
||||
({ACLType, ACLSpecs}) ->
|
||||
add(Host, ACLName, {ACLType, ACLSpecs})
|
||||
end, lists:flatten(SpecList))
|
||||
end, ACLs),
|
||||
lists:foreach(
|
||||
fun({Access, Rules}) ->
|
||||
add_access(Host, Access, Rules)
|
||||
end, AccessRules)
|
||||
end, Hosts).
|
||||
|
||||
-spec match_rule(global | binary(), atom(), jid() | ljid()) -> any().
|
||||
b(S) ->
|
||||
iolist_to_binary(S).
|
||||
|
||||
match_rule(global, Rule, JID) ->
|
||||
case Rule of
|
||||
all -> allow;
|
||||
none -> deny;
|
||||
_ ->
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, global}, fun(V) -> V end)
|
||||
of
|
||||
undefined -> deny;
|
||||
GACLs -> match_acls(GACLs, JID, global)
|
||||
end
|
||||
end;
|
||||
match_rule(Host, Rule, JID) ->
|
||||
case Rule of
|
||||
all -> allow;
|
||||
none -> deny;
|
||||
_ ->
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, global}, fun(V) -> V end)
|
||||
of
|
||||
undefined ->
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, Host}, fun(V) -> V end)
|
||||
of
|
||||
undefined -> deny;
|
||||
ACLs -> match_acls(ACLs, JID, Host)
|
||||
end;
|
||||
GACLs ->
|
||||
case ejabberd_config:get_global_option(
|
||||
{access, Rule, Host}, fun(V) -> V end)
|
||||
of
|
||||
undefined -> match_acls(GACLs, JID, Host);
|
||||
ACLs ->
|
||||
case lists:reverse(GACLs) of
|
||||
[{allow, all} | Rest] ->
|
||||
match_acls(lists:reverse(Rest) ++
|
||||
ACLs ++ [{allow, all}],
|
||||
JID, Host);
|
||||
_ -> match_acls(GACLs ++ ACLs, JID, Host)
|
||||
end
|
||||
end
|
||||
end
|
||||
nodeprep(S) ->
|
||||
jlib:nodeprep(b(S)).
|
||||
|
||||
nameprep(S) ->
|
||||
jlib:nameprep(b(S)).
|
||||
|
||||
resourceprep(S) ->
|
||||
jlib:resourceprep(b(S)).
|
||||
|
||||
normalize_spec(Spec) ->
|
||||
case Spec of
|
||||
all -> all;
|
||||
none -> none;
|
||||
{user, {U, S}} -> {user, {nodeprep(U), nameprep(S)}};
|
||||
{user, U} -> {user, nodeprep(U)};
|
||||
{shared_group, {G, H}} -> {shared_group, {b(G), nameprep(H)}};
|
||||
{shared_group, G} -> {shared_group, b(G)};
|
||||
{user_regexp, {UR, S}} -> {user_regexp, {b(UR), nameprep(S)}};
|
||||
{user_regexp, UR} -> {user_regexp, b(UR)};
|
||||
{node_regexp, {UR, SR}} -> {node_regexp, {b(UR), b(SR)}};
|
||||
{user_glob, {UR, S}} -> {user_glob, {b(UR), nameprep(S)}};
|
||||
{user_glob, UR} -> {user_glob, b(UR)};
|
||||
{node_glob, {UR, SR}} -> {node_glob, {b(UR), b(SR)}};
|
||||
{server, S} -> {server, nameprep(S)};
|
||||
{resource, R} -> {resource, resourceprep(R)};
|
||||
{server_regexp, SR} -> {server_regexp, b(SR)};
|
||||
{server_glob, S} -> {server_glob, b(S)};
|
||||
{resource_glob, R} -> {resource_glob, b(R)};
|
||||
{ip, S} ->
|
||||
case parse_ip_netmask(b(S)) of
|
||||
{ok, Net, Mask} ->
|
||||
{ip, {Net, Mask}};
|
||||
error ->
|
||||
?INFO_MSG("Invalid network address: ~p", [S]),
|
||||
none
|
||||
end
|
||||
end.
|
||||
|
||||
-spec match_rule(global | binary(), access_name(),
|
||||
jid() | ljid() | inet:ip_address()) -> any().
|
||||
|
||||
match_rule(_Host, all, _JID) ->
|
||||
allow;
|
||||
match_rule(_Host, none, _JID) ->
|
||||
deny;
|
||||
match_rule(Host, Access, JID) ->
|
||||
GAccess = ets:lookup(access, {Access, global}),
|
||||
LAccess = if Host /= global ->
|
||||
ets:lookup(access, {Access, Host});
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
case GAccess ++ LAccess of
|
||||
[] ->
|
||||
?WARNING_MSG("Attempt to match against unspecified "
|
||||
"access rule '~s' (scope: ~s)",
|
||||
[Access, Host]),
|
||||
deny;
|
||||
AccessList ->
|
||||
Rules = lists:flatmap(
|
||||
fun(#access{rules = Rs}) ->
|
||||
Rs
|
||||
end, AccessList),
|
||||
match_acls(Rules, JID, Host)
|
||||
end.
|
||||
|
||||
match_acls([], _, _Host) -> deny;
|
||||
match_acls([{Access, ACL} | ACLs], JID, Host) ->
|
||||
match_acls([{ACL, Access} | ACLs], JID, Host) ->
|
||||
case match_acl(ACL, JID, Host) of
|
||||
true -> Access;
|
||||
_ -> match_acls(ACLs, JID, Host)
|
||||
end.
|
||||
|
||||
-spec match_acl(atom(), jid() | ljid(), binary()) -> boolean().
|
||||
-spec match_acl(atom(),
|
||||
jid() | ljid() | inet:ip_address(),
|
||||
binary()) -> boolean().
|
||||
|
||||
match_acl(all, _JID, _Host) ->
|
||||
true;
|
||||
match_acl(none, _JID, _Host) ->
|
||||
false;
|
||||
match_acl(ACL, IP, Host) when tuple_size(IP) == 4;
|
||||
tuple_size(IP) == 8 ->
|
||||
lists:any(
|
||||
fun(#acl{aclspec = {ip, {Net, Mask}}}) ->
|
||||
is_ip_match(IP, Net, Mask);
|
||||
(_) ->
|
||||
false
|
||||
end,
|
||||
ets:lookup(acl, {ACL, Host}) ++
|
||||
ets:lookup(acl, {ACL, global}));
|
||||
match_acl(ACL, JID, Host) ->
|
||||
case ACL of
|
||||
all -> true;
|
||||
none -> false;
|
||||
_ ->
|
||||
{User, Server, Resource} = jlib:jid_tolower(JID),
|
||||
lists:any(fun (#acl{aclspec = Spec}) ->
|
||||
case Spec of
|
||||
all -> true;
|
||||
{user, U} ->
|
||||
U == User andalso
|
||||
(Host == Server orelse
|
||||
Host == global andalso
|
||||
lists:member(Server, ?MYHOSTS));
|
||||
{user, U, S} -> U == User andalso S == Server;
|
||||
{server, S} -> S == Server;
|
||||
{resource, R} -> R == Resource;
|
||||
{user_regexp, UR} ->
|
||||
(Host == Server orelse
|
||||
Host == global andalso
|
||||
lists:member(Server, ?MYHOSTS))
|
||||
andalso is_regexp_match(User, UR);
|
||||
{shared_group, G} ->
|
||||
Mod = loaded_shared_roster_module(Host),
|
||||
Mod:is_user_in_group({User, Server}, G, Host);
|
||||
{shared_group, G, H} ->
|
||||
Mod = loaded_shared_roster_module(H),
|
||||
Mod:is_user_in_group({User, Server}, G, H);
|
||||
{user_regexp, UR, S} ->
|
||||
S == Server andalso is_regexp_match(User, UR);
|
||||
{server_regexp, SR} ->
|
||||
is_regexp_match(Server, SR);
|
||||
{resource_regexp, RR} ->
|
||||
is_regexp_match(Resource, RR);
|
||||
{node_regexp, UR, SR} ->
|
||||
is_regexp_match(Server, SR) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{user_glob, UR} ->
|
||||
(Host == Server orelse
|
||||
Host == global andalso
|
||||
lists:member(Server, ?MYHOSTS))
|
||||
andalso is_glob_match(User, UR);
|
||||
{user_glob, UR, S} ->
|
||||
S == Server andalso is_glob_match(User, UR);
|
||||
{server_glob, SR} -> is_glob_match(Server, SR);
|
||||
{resource_glob, RR} ->
|
||||
is_glob_match(Resource, RR);
|
||||
{node_glob, UR, SR} ->
|
||||
is_glob_match(Server, SR) andalso
|
||||
is_glob_match(User, UR);
|
||||
WrongSpec ->
|
||||
?ERROR_MSG("Wrong ACL expression: ~p~nCheck your "
|
||||
"config file and reload it with the override_a"
|
||||
"cls option enabled",
|
||||
[WrongSpec]),
|
||||
false
|
||||
end
|
||||
end,
|
||||
ets:lookup(acl, {ACL, global}) ++
|
||||
ets:lookup(acl, {ACL, Host}))
|
||||
end.
|
||||
{User, Server, Resource} = jlib:jid_tolower(JID),
|
||||
lists:any(
|
||||
fun(#acl{aclspec = Spec}) ->
|
||||
case Spec of
|
||||
all -> true;
|
||||
{user, {U, S}} -> U == User andalso S == Server;
|
||||
{user, U} ->
|
||||
U == User andalso
|
||||
lists:member(Server, ?MYHOSTS);
|
||||
{server, S} -> S == Server;
|
||||
{resource, R} -> R == Resource;
|
||||
{shared_group, {G, H}} ->
|
||||
Mod = loaded_shared_roster_module(H),
|
||||
Mod:is_user_in_group({User, Server}, G, H);
|
||||
{shared_group, G} ->
|
||||
Mod = loaded_shared_roster_module(Host),
|
||||
Mod:is_user_in_group({User, Server}, G, Host);
|
||||
{user_regexp, {UR, S}} ->
|
||||
S == Server andalso is_regexp_match(User, UR);
|
||||
{user_regexp, UR} ->
|
||||
lists:member(Server, ?MYHOSTS)
|
||||
andalso is_regexp_match(User, UR);
|
||||
{server_regexp, SR} ->
|
||||
is_regexp_match(Server, SR);
|
||||
{resource_regexp, RR} ->
|
||||
is_regexp_match(Resource, RR);
|
||||
{node_regexp, {UR, SR}} ->
|
||||
is_regexp_match(Server, SR) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{user_glob, {UR, S}} ->
|
||||
S == Server andalso is_glob_match(User, UR);
|
||||
{user_glob, UR} ->
|
||||
lists:member(Server, ?MYHOSTS)
|
||||
andalso is_glob_match(User, UR);
|
||||
{server_glob, SR} -> is_glob_match(Server, SR);
|
||||
{resource_glob, RR} ->
|
||||
is_glob_match(Resource, RR);
|
||||
{node_glob, {UR, SR}} ->
|
||||
is_glob_match(Server, SR) andalso
|
||||
is_glob_match(User, UR);
|
||||
WrongSpec ->
|
||||
?ERROR_MSG("Wrong ACL expression: ~p~nCheck your "
|
||||
"config file and reload it with the override_a"
|
||||
"cls option enabled",
|
||||
[WrongSpec]),
|
||||
false
|
||||
end
|
||||
end,
|
||||
ets:lookup(acl, {ACL, Host}) ++
|
||||
ets:lookup(acl, {ACL, global})).
|
||||
|
||||
is_regexp_match(String, RegExp) ->
|
||||
case ejabberd_regexp:run(String, RegExp) of
|
||||
@@ -256,34 +366,115 @@ is_glob_match(String, Glob) ->
|
||||
is_regexp_match(String,
|
||||
ejabberd_regexp:sh_to_awk(Glob)).
|
||||
|
||||
is_ip_match({_, _, _, _} = IP, {_, _, _, _} = Net, Mask) ->
|
||||
IPInt = ip_to_integer(IP),
|
||||
NetInt = ip_to_integer(Net),
|
||||
M = bnot (1 bsl (32 - Mask) - 1),
|
||||
IPInt band M =:= NetInt band M;
|
||||
is_ip_match({_, _, _, _, _, _, _, _} = IP,
|
||||
{_, _, _, _, _, _, _, _} = Net, Mask) ->
|
||||
IPInt = ip_to_integer(IP),
|
||||
NetInt = ip_to_integer(Net),
|
||||
M = bnot (1 bsl (128 - Mask) - 1),
|
||||
IPInt band M =:= NetInt band M;
|
||||
is_ip_match(_, _, _) ->
|
||||
false.
|
||||
|
||||
ip_to_integer({IP1, IP2, IP3, IP4}) ->
|
||||
IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4;
|
||||
ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7,
|
||||
IP8}) ->
|
||||
IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16
|
||||
bor IP5
|
||||
bsl 16
|
||||
bor IP6
|
||||
bsl 16
|
||||
bor IP7
|
||||
bsl 16
|
||||
bor IP8.
|
||||
|
||||
loaded_shared_roster_module(Host) ->
|
||||
case gen_mod:is_loaded(Host, mod_shared_roster_ldap) of
|
||||
true -> mod_shared_roster_ldap;
|
||||
false -> mod_shared_roster
|
||||
end.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, acl),
|
||||
case mnesia:table_info(acl, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
acl, Fields, bag,
|
||||
fun(#acl{aclspec = Spec}) when is_tuple(Spec) ->
|
||||
element(2, Spec);
|
||||
(_) ->
|
||||
'$next'
|
||||
end,
|
||||
fun(#acl{aclname = {ACLName, Host},
|
||||
aclspec = Spec} = R) ->
|
||||
NewHost = if Host == global ->
|
||||
Host;
|
||||
true ->
|
||||
iolist_to_binary(Host)
|
||||
end,
|
||||
R#acl{aclname = {ACLName, NewHost},
|
||||
aclspec = normalize_spec(Spec)}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating acl table", []),
|
||||
mnesia:transform_table(acl, ignore, Fields)
|
||||
parse_ip_netmask(S) ->
|
||||
case str:tokens(S, <<"/">>) of
|
||||
[IPStr] ->
|
||||
case inet_parse:address(binary_to_list(IPStr)) of
|
||||
{ok, {_, _, _, _} = IP} -> {ok, IP, 32};
|
||||
{ok, {_, _, _, _, _, _, _, _} = IP} -> {ok, IP, 128};
|
||||
_ -> error
|
||||
end;
|
||||
[IPStr, MaskStr] ->
|
||||
case catch jlib:binary_to_integer(MaskStr) of
|
||||
Mask when is_integer(Mask), Mask >= 0 ->
|
||||
case inet_parse:address(binary_to_list(IPStr)) of
|
||||
{ok, {_, _, _, _} = IP} when Mask =< 32 ->
|
||||
{ok, IP, Mask};
|
||||
{ok, {_, _, _, _, _, _, _, _} = IP} when Mask =< 128 ->
|
||||
{ok, IP, Mask};
|
||||
_ -> error
|
||||
end;
|
||||
_ -> error
|
||||
end;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
transform_options(Opts) ->
|
||||
Opts1 = lists:foldl(fun transform_options/2, [], Opts),
|
||||
{ACLOpts, Opts2} = lists:mapfoldl(
|
||||
fun({acl, Os}, Acc) ->
|
||||
{Os, Acc};
|
||||
(O, Acc) ->
|
||||
{[], [O|Acc]}
|
||||
end, [], Opts1),
|
||||
{AccessOpts, Opts3} = lists:mapfoldl(
|
||||
fun({access, Os}, Acc) ->
|
||||
{Os, Acc};
|
||||
(O, Acc) ->
|
||||
{[], [O|Acc]}
|
||||
end, [], Opts2),
|
||||
ACLOpts1 = ejabberd_config:collect_options(lists:flatten(ACLOpts)),
|
||||
AccessOpts1 = case ejabberd_config:collect_options(
|
||||
lists:flatten(AccessOpts)) of
|
||||
[] -> [];
|
||||
L1 -> [{access, L1}]
|
||||
end,
|
||||
ACLOpts2 = case lists:map(
|
||||
fun({ACLName, Os}) ->
|
||||
{ACLName, ejabberd_config:collect_options(Os)}
|
||||
end, ACLOpts1) of
|
||||
[] -> [];
|
||||
L2 -> [{acl, L2}]
|
||||
end,
|
||||
ACLOpts2 ++ AccessOpts1 ++ Opts3.
|
||||
|
||||
transform_options({acl, Name, Type}, Opts) ->
|
||||
T = case Type of
|
||||
all -> all;
|
||||
none -> none;
|
||||
{user, U} -> {user, [U]};
|
||||
{user, U, S} -> {user, [[{U, S}]]};
|
||||
{shared_group, G} -> {shared_group, [G]};
|
||||
{shared_group, G, H} -> {shared_group, [[{G, H}]]};
|
||||
{user_regexp, UR} -> {user_regexp, [UR]};
|
||||
{user_regexp, UR, S} -> {user_regexp, [[{UR, S}]]};
|
||||
{node_regexp, UR, SR} -> {node_regexp, [[{UR, SR}]]};
|
||||
{user_glob, UR} -> {user_glob, [UR]};
|
||||
{user_glob, UR, S} -> {user_glob, [[{UR, S}]]};
|
||||
{node_glob, UR, SR} -> {node_glob, [[{UR, SR}]]};
|
||||
{server, S} -> {server, [S]};
|
||||
{resource, R} -> {resource, [R]};
|
||||
{server_regexp, SR} -> {server_regexp, [SR]};
|
||||
{server_glob, S} -> {server_glob, [S]};
|
||||
{ip, S} -> {ip, [S]};
|
||||
{resource_glob, R} -> {resource_glob, [R]}
|
||||
end,
|
||||
[{acl, [{Name, [T]}]}|Opts];
|
||||
transform_options({access, Name, Rules}, Opts) ->
|
||||
NewRules = [{ACL, Action} || {Action, ACL} <- Rules],
|
||||
[{access, [{Name, NewRules}]}|Opts];
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
@@ -204,11 +204,11 @@ get_local_fqdn() ->
|
||||
Str when is_binary(Str) -> Str;
|
||||
_ ->
|
||||
<<"unknown-fqdn, please configure fqdn "
|
||||
"option in ejabberd.cfg!">>
|
||||
"option in ejabberd.yml!">>
|
||||
end.
|
||||
|
||||
get_local_fqdn2() ->
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
fqdn, fun iolist_to_binary/1) of
|
||||
ConfiguredFqdn when is_binary(ConfiguredFqdn) ->
|
||||
ConfiguredFqdn;
|
||||
|
||||
+81
-15
@@ -27,8 +27,8 @@
|
||||
-module(ejabberd).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, stop/0, start_app/1,
|
||||
get_pid_file/0]).
|
||||
-export([start/0, stop/0, start_app/1, start_app/2,
|
||||
get_pid_file/0, check_app/1]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
@@ -51,27 +51,93 @@ get_pid_file() ->
|
||||
Path
|
||||
end.
|
||||
|
||||
start_app(App) when not is_list(App) ->
|
||||
start_app([App]);
|
||||
start_app([App|Apps]) ->
|
||||
start_app(App) ->
|
||||
start_app(App, temporary).
|
||||
|
||||
start_app(App, Type) ->
|
||||
StartFlag = not is_loaded(),
|
||||
start_app(App, Type, StartFlag).
|
||||
|
||||
check_app(App) ->
|
||||
StartFlag = not is_loaded(),
|
||||
spawn(fun() -> check_app_modules(App, StartFlag) end),
|
||||
ok.
|
||||
|
||||
is_loaded() ->
|
||||
Apps = application:which_applications(),
|
||||
lists:keymember(ejabberd, 1, Apps).
|
||||
|
||||
start_app(App, Type, StartFlag) when not is_list(App) ->
|
||||
start_app([App], Type, StartFlag);
|
||||
start_app([App|Apps], Type, StartFlag) ->
|
||||
case application:start(App) of
|
||||
ok ->
|
||||
start_app(Apps);
|
||||
spawn(fun() -> check_app_modules(App, StartFlag) end),
|
||||
start_app(Apps, Type, StartFlag);
|
||||
{error, {already_started, _}} ->
|
||||
start_app(Apps);
|
||||
start_app(Apps, Type, StartFlag);
|
||||
{error, {not_started, DepApp}} ->
|
||||
case lists:member(DepApp, [App|Apps]) of
|
||||
true ->
|
||||
?CRITICAL_MSG("failed to start application '~p': "
|
||||
"circular dependency on '~p' detected",
|
||||
[App, DepApp]),
|
||||
erlang:error(application_start_failed);
|
||||
Reason = io_lib:format(
|
||||
"failed to start application '~p': "
|
||||
"circular dependency on '~p' detected",
|
||||
[App, DepApp]),
|
||||
exit_or_halt(Reason, StartFlag);
|
||||
false ->
|
||||
start_app([DepApp,App|Apps])
|
||||
start_app([DepApp,App|Apps], Type, StartFlag)
|
||||
end;
|
||||
Err ->
|
||||
?CRITICAL_MSG("failed to start application '~p': ~p", [App, Err]),
|
||||
erlang:error(application_start_failed)
|
||||
Reason = io_lib:format("failed to start application '~p': ~p",
|
||||
[App, Err]),
|
||||
exit_or_halt(Reason, StartFlag)
|
||||
end;
|
||||
start_app([]) ->
|
||||
start_app([], _Type, _StartFlag) ->
|
||||
ok.
|
||||
|
||||
check_app_modules(App, StartFlag) ->
|
||||
{A, B, C} = now(),
|
||||
random:seed(A, B, C),
|
||||
sleep(5000),
|
||||
case application:get_key(App, modules) of
|
||||
{ok, Mods} ->
|
||||
lists:foreach(
|
||||
fun(Mod) ->
|
||||
case code:which(Mod) of
|
||||
non_existing ->
|
||||
File = get_module_file(App, Mod),
|
||||
Reason = io_lib:format(
|
||||
"couldn't find module ~s "
|
||||
"needed for application '~p'",
|
||||
[File, App]),
|
||||
exit_or_halt(Reason, StartFlag);
|
||||
_ ->
|
||||
sleep(10)
|
||||
end
|
||||
end, Mods);
|
||||
_ ->
|
||||
%% No modules? This is strange
|
||||
ok
|
||||
end.
|
||||
|
||||
exit_or_halt(Reason, StartFlag) ->
|
||||
?CRITICAL_MSG(Reason, []),
|
||||
if StartFlag ->
|
||||
%% Wait for the critical message is written in the console/log
|
||||
timer:sleep(1000),
|
||||
halt(string:substr(lists:flatten(Reason), 1, 199));
|
||||
true ->
|
||||
erlang:error(application_start_failed)
|
||||
end.
|
||||
|
||||
sleep(N) ->
|
||||
timer:sleep(random:uniform(N)).
|
||||
|
||||
get_module_file(App, Mod) ->
|
||||
BaseName = atom_to_list(Mod),
|
||||
case code:lib_dir(App, ebin) of
|
||||
{error, _} ->
|
||||
BaseName;
|
||||
Dir ->
|
||||
filename:join([Dir, BaseName ++ ".beam"])
|
||||
end.
|
||||
|
||||
@@ -164,6 +164,12 @@ commands() ->
|
||||
module = ejd2odbc, function = export,
|
||||
args = [{host, string}, {file, string}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = convert_to_yaml, tags = [config],
|
||||
desc = "Convert the input file from Erlang to YAML format",
|
||||
module = ejabberd_config, function = convert_to_yaml,
|
||||
args = [{in, string}, {out, string}],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = delete_expired_messages, tags = [purge],
|
||||
desc = "Delete expired offline messages from database",
|
||||
module = ?MODULE, function = delete_expired_messages,
|
||||
|
||||
+17
-46
@@ -29,7 +29,7 @@
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-export([start_modules/0,start/2, get_log_path/0, prep_stop/1, stop/1, init/0]).
|
||||
-export([start_modules/0,start/2, prep_stop/1, stop/1, init/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -39,21 +39,22 @@
|
||||
%%%
|
||||
|
||||
start(normal, _Args) ->
|
||||
maybe_start_lager(),
|
||||
ejabberd_logger:set(4),
|
||||
ejabberd_logger:start(),
|
||||
write_pid_file(),
|
||||
start_apps(),
|
||||
ejabberd:check_app(ejabberd),
|
||||
randoms:start(),
|
||||
db_init(),
|
||||
start(),
|
||||
translate:start(),
|
||||
acl:start(),
|
||||
ejabberd_ctl:init(),
|
||||
ejabberd_commands:init(),
|
||||
ejabberd_admin:start(),
|
||||
gen_mod:start(),
|
||||
ejabberd_config:start(),
|
||||
ejabberd_check:config(),
|
||||
set_loglevel_from_config(),
|
||||
acl:start(),
|
||||
shaper:start(),
|
||||
connect_nodes(),
|
||||
Sup = ejabberd_sup:start_link(),
|
||||
ejabberd_rdbms:start(),
|
||||
@@ -98,10 +99,6 @@ start() ->
|
||||
|
||||
init() ->
|
||||
register(ejabberd, self()),
|
||||
%erlang:system_flag(fullsweep_after, 0),
|
||||
%error_logger:logfile({open, ?LOG_PATH}),
|
||||
LogPath = get_log_path(),
|
||||
ejabberd_logger:set_logfile(LogPath),
|
||||
loop().
|
||||
|
||||
loop() ->
|
||||
@@ -117,14 +114,14 @@ db_init() ->
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
application:start(mnesia, permanent),
|
||||
ejabberd:start_app(mnesia, permanent),
|
||||
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
|
||||
|
||||
%% Start all the modules in all the hosts
|
||||
start_modules() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
Modules = ejabberd_config:get_local_option(
|
||||
Modules = ejabberd_config:get_option(
|
||||
{modules, Host},
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
@@ -142,7 +139,7 @@ start_modules() ->
|
||||
stop_modules() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
Modules = ejabberd_config:get_local_option(
|
||||
Modules = ejabberd_config:get_option(
|
||||
{modules, Host},
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
@@ -157,7 +154,7 @@ stop_modules() ->
|
||||
end, ?MYHOSTS).
|
||||
|
||||
connect_nodes() ->
|
||||
Nodes = ejabberd_config:get_local_option(
|
||||
Nodes = ejabberd_config:get_option(
|
||||
cluster_nodes,
|
||||
fun(Ns) ->
|
||||
true = lists:all(fun is_atom/1, Ns),
|
||||
@@ -167,26 +164,6 @@ connect_nodes() ->
|
||||
net_kernel:connect_node(Node)
|
||||
end, Nodes).
|
||||
|
||||
%% @spec () -> string()
|
||||
%% @doc Returns the full path to the ejabberd log file.
|
||||
%% It first checks for application configuration parameter 'log_path'.
|
||||
%% If not defined it checks the environment variable EJABBERD_LOG_PATH.
|
||||
%% And if that one is neither defined, returns the default value:
|
||||
%% "ejabberd.log" in current directory.
|
||||
get_log_path() ->
|
||||
case application:get_env(log_path) of
|
||||
{ok, Path} ->
|
||||
Path;
|
||||
undefined ->
|
||||
case os:getenv("EJABBERD_LOG_PATH") of
|
||||
false ->
|
||||
?LOG_PATH;
|
||||
Path ->
|
||||
Path
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
%% If ejabberd is running on some Windows machine, get nameservers and add to Erlang
|
||||
maybe_add_nameservers() ->
|
||||
case os:type() of
|
||||
@@ -237,23 +214,17 @@ delete_pid_file() ->
|
||||
file:delete(PidFilename)
|
||||
end.
|
||||
|
||||
|
||||
-ifdef(LAGER).
|
||||
|
||||
maybe_start_lager() ->
|
||||
lager:start().
|
||||
|
||||
-else.
|
||||
|
||||
maybe_start_lager() ->
|
||||
ok.
|
||||
|
||||
-endif.
|
||||
|
||||
set_loglevel_from_config() ->
|
||||
Level = ejabberd_config:get_option(
|
||||
loglevel,
|
||||
fun(P) when P>=0, P=<5 -> P end,
|
||||
4),
|
||||
ejabberd_logger:set(Level).
|
||||
|
||||
start_apps() ->
|
||||
ejabberd:start_app(sasl),
|
||||
ejabberd:start_app(ssl),
|
||||
ejabberd:start_app(p1_yaml),
|
||||
ejabberd:start_app(p1_tls),
|
||||
ejabberd:start_app(p1_xml),
|
||||
ejabberd:start_app(p1_stringprep),
|
||||
|
||||
+11
-3
@@ -35,8 +35,8 @@
|
||||
check_password/5, check_password_with_authmodule/3,
|
||||
check_password_with_authmodule/5, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2, export/1,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users/2, export/1, import/1,
|
||||
get_vh_registered_users_number/1, import/3,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, get_password_with_authmodule/2,
|
||||
is_user_exists/2, is_user_exists_in_other_modules/3,
|
||||
@@ -423,7 +423,7 @@ auth_modules() ->
|
||||
%% Return the list of authenticated modules for a given host
|
||||
auth_modules(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Methods = ejabberd_config:get_local_option(
|
||||
Methods = ejabberd_config:get_option(
|
||||
{auth_method, LServer},
|
||||
fun(V) when is_list(V) ->
|
||||
true = lists:all(fun is_atom/1, V),
|
||||
@@ -437,3 +437,11 @@ auth_modules(Server) ->
|
||||
|
||||
export(Server) ->
|
||||
ejabberd_auth_internal:export(Server).
|
||||
|
||||
import(Server) ->
|
||||
ejabberd_auth_internal:import(Server).
|
||||
|
||||
import(Server, mnesia, Passwd) ->
|
||||
ejabberd_auth_internal:import(Server, mnesia, Passwd);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
@@ -104,7 +104,7 @@ is_login_anonymous_enabled(Host) ->
|
||||
%% Return the anonymous protocol to use: sasl_anon|login_anon|both
|
||||
%% defaults to login_anon
|
||||
anonymous_protocol(Host) ->
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
{anonymous_protocol, Host},
|
||||
fun(sasl_anon) -> sasl_anon;
|
||||
(login_anon) -> login_anon;
|
||||
@@ -115,7 +115,7 @@ anonymous_protocol(Host) ->
|
||||
%% Return true if multiple connections have been allowed in the config file
|
||||
%% defaults to false
|
||||
allow_multiple_connections(Host) ->
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
{allow_multiple_connections, Host},
|
||||
fun(V) when is_boolean(V) -> V end,
|
||||
false).
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
Cmd = ejabberd_config:get_local_option(
|
||||
Cmd = ejabberd_config:get_option(
|
||||
{extauth_program, Host},
|
||||
fun(V) ->
|
||||
binary_to_list(iolist_to_binary(V))
|
||||
@@ -171,7 +171,7 @@ remove_user(User, Server, Password) ->
|
||||
|
||||
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
|
||||
get_cache_option(Host) ->
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
{extauth_cache, Host},
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> false;
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, export/1,
|
||||
plain_password_required/0]).
|
||||
remove_user/3, store_type/0, export/1, import/1,
|
||||
import/3, plain_password_required/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -406,7 +406,7 @@ is_scrammed() ->
|
||||
|
||||
is_option_scram() ->
|
||||
scram ==
|
||||
ejabberd_config:get_local_option({auth_password_format, ?MYNAME},
|
||||
ejabberd_config:get_option({auth_password_format, ?MYNAME},
|
||||
fun(V) -> V end).
|
||||
|
||||
maybe_alert_password_scrammed_without_option() ->
|
||||
@@ -474,3 +474,14 @@ export(_Server) ->
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select username, password from users;">>,
|
||||
fun([LUser, Password]) ->
|
||||
#passwd{us = {LUser, LServer}, password = Password}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #passwd{} = P) ->
|
||||
mnesia:dirty_write(P);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
@@ -369,8 +369,10 @@ parse_options(Host) ->
|
||||
{iolist_to_binary(U),
|
||||
iolist_to_binary(P)};
|
||||
({U}) ->
|
||||
{iolist_to_binary(U)};
|
||||
(U) ->
|
||||
{iolist_to_binary(U)}
|
||||
end, Us)
|
||||
end, lists:flatten(Us))
|
||||
end, [{<<"uid">>, <<"%u">>}]),
|
||||
UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
|
||||
SubFilter = eldap_utils:generate_subfilter(UIDs),
|
||||
|
||||
@@ -107,13 +107,13 @@ store_type() -> external.
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
get_pam_service(Host) ->
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
{pam_service, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>).
|
||||
|
||||
get_pam_userinfotype(Host) ->
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
{pam_userinfotype, Host},
|
||||
fun(username) -> username;
|
||||
(jid) -> jid
|
||||
|
||||
+17
-10
@@ -47,7 +47,8 @@
|
||||
del_aux_field/2,
|
||||
get_subscription/2,
|
||||
broadcast/4,
|
||||
get_subscribed/1]).
|
||||
get_subscribed/1,
|
||||
transform_listen_option/2]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1,
|
||||
@@ -233,18 +234,21 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
{value, {_, XS}} -> XS;
|
||||
_ -> false
|
||||
end,
|
||||
Zlib = lists:member(zlib, Opts),
|
||||
StartTLS = lists:member(starttls, Opts),
|
||||
StartTLSRequired = lists:member(starttls_required,
|
||||
Opts),
|
||||
TLSEnabled = lists:member(tls, Opts),
|
||||
Zlib = proplists:get_bool(zlib, Opts),
|
||||
StartTLS = proplists:get_bool(starttls, Opts),
|
||||
StartTLSRequired = proplists:get_bool(starttls_required, Opts),
|
||||
TLSEnabled = proplists:get_bool(tls, Opts),
|
||||
TLS = StartTLS orelse
|
||||
StartTLSRequired orelse TLSEnabled,
|
||||
TLSOpts1 = lists:filter(fun ({certfile, _}) -> true;
|
||||
(_) -> false
|
||||
end,
|
||||
Opts),
|
||||
TLSOpts = [verify_none | TLSOpts1],
|
||||
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
|
||||
false -> [compression_none | TLSOpts1];
|
||||
true -> TLSOpts1
|
||||
end,
|
||||
TLSOpts = [verify_none | TLSOpts2],
|
||||
IP = peerip(SockMod, Socket),
|
||||
%% Check if IP is blacklisted:
|
||||
case is_ip_blacklisted(IP) of
|
||||
@@ -678,7 +682,7 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
when TLS == true, TLSEnabled == false,
|
||||
SockMod == gen_tcp ->
|
||||
TLSOpts = case
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, StateData#state.server},
|
||||
fun iolist_to_binary/1)
|
||||
of
|
||||
@@ -872,7 +876,7 @@ resource_conflict_action(U, S, R) ->
|
||||
R)
|
||||
of
|
||||
true ->
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
{resource_conflict, S},
|
||||
fun(setresource) -> setresource;
|
||||
(closeold) -> closeold;
|
||||
@@ -2275,7 +2279,7 @@ fsm_limit_opts(Opts) ->
|
||||
case lists:keysearch(max_fsm_queue, 1, Opts) of
|
||||
{value, {_, N}} when is_integer(N) -> [{max_queue, N}];
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
max_fsm_queue,
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> [];
|
||||
@@ -2373,3 +2377,6 @@ pack_string(String, Pack) ->
|
||||
{value, PackedString} -> {PackedString, Pack};
|
||||
none -> {String, gb_trees:insert(String, String, Pack)}
|
||||
end.
|
||||
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
%% Get first c2s configuration limitations to apply it to other c2s
|
||||
%% connectors.
|
||||
get_c2s_limits() ->
|
||||
case ejabberd_config:get_local_option(listen, fun(V) -> V end) of
|
||||
case ejabberd_config:get_option(listen, fun(V) -> V end) of
|
||||
undefined -> [];
|
||||
C2SFirstListen ->
|
||||
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
|
||||
|
||||
@@ -504,7 +504,7 @@ do_create_image(Key) ->
|
||||
end.
|
||||
|
||||
get_prog_name() ->
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
captcha_cmd,
|
||||
fun(FileName) ->
|
||||
F = iolist_to_binary(FileName),
|
||||
@@ -521,7 +521,7 @@ get_prog_name() ->
|
||||
end.
|
||||
|
||||
get_url(Str) ->
|
||||
CaptchaHost = ejabberd_config:get_local_option(
|
||||
CaptchaHost = ejabberd_config:get_option(
|
||||
captcha_host,
|
||||
fun iolist_to_binary/1,
|
||||
<<"">>),
|
||||
@@ -549,7 +549,7 @@ get_transfer_protocol(PortString) ->
|
||||
get_captcha_transfer_protocol(PortListeners).
|
||||
|
||||
get_port_listeners(PortNumber) ->
|
||||
AllListeners = ejabberd_config:get_local_option(listen, fun(V) -> V end),
|
||||
AllListeners = ejabberd_config:get_option(listen, fun(V) -> V end),
|
||||
lists:filter(fun ({{Port, _Ip, _Netp}, _Module1,
|
||||
_Opts1})
|
||||
when Port == PortNumber ->
|
||||
@@ -579,7 +579,7 @@ get_captcha_transfer_protocol([_ | Listeners]) ->
|
||||
|
||||
is_limited(undefined) -> false;
|
||||
is_limited(Limiter) ->
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
captcha_limit,
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> false;
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_check.erl
|
||||
%%% Author : Mickael Remond <mremond@process-one.net>
|
||||
%%% Purpose : Check ejabberd configuration and
|
||||
%%% Created : 27 Feb 2008 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_check).
|
||||
|
||||
-export([libs/0, config/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
|
||||
%% TODO:
|
||||
%% We want to implement library checking at launch time to issue
|
||||
%% human readable user messages.
|
||||
libs() ->
|
||||
ok.
|
||||
|
||||
%% @doc Consistency check on ejabberd configuration
|
||||
config() ->
|
||||
check_database_modules().
|
||||
|
||||
check_database_modules() ->
|
||||
[check_database_module(M)||M<-get_db_used()].
|
||||
|
||||
check_database_module(odbc) ->
|
||||
check_modules(odbc, [odbc, odbc_app, odbc_sup, ejabberd_odbc, ejabberd_odbc_sup, odbc_queries]);
|
||||
check_database_module(mysql) ->
|
||||
check_modules(mysql, [mysql, mysql_auth, mysql_conn, mysql_recv]);
|
||||
check_database_module(pgsql) ->
|
||||
check_modules(pgsql, [pgsql, pgsql_proto, pgsql_tcp, pgsql_util]).
|
||||
|
||||
%% @doc Issue a critical error and throw an exit if needing module is
|
||||
%% missing.
|
||||
check_modules(DB, Modules) ->
|
||||
case get_missing_modules(Modules) of
|
||||
[] ->
|
||||
ok;
|
||||
MissingModules when is_list(MissingModules) ->
|
||||
?CRITICAL_MSG("ejabberd is configured to use '~p', but the following Erlang modules are not installed: '~p'", [DB, MissingModules]),
|
||||
exit(database_module_missing)
|
||||
end.
|
||||
|
||||
|
||||
%% @doc Return the list of undefined modules
|
||||
get_missing_modules(Modules) ->
|
||||
lists:filter(fun(Module) ->
|
||||
case catch Module:module_info() of
|
||||
{'EXIT', {undef, _}} ->
|
||||
true;
|
||||
_ -> false
|
||||
end
|
||||
end, Modules).
|
||||
|
||||
%% @doc Return the list of databases used
|
||||
get_db_used() ->
|
||||
%% Retrieve domains with a database configured:
|
||||
Domains =
|
||||
ets:match(local_config, #local_config{key={odbc_server, '$1'},
|
||||
value='$2'}),
|
||||
%% Check that odbc is the auth method used for those domains:
|
||||
%% and return the database name
|
||||
DBs = lists:foldr(
|
||||
fun([Domain, DB], Acc) ->
|
||||
case check_odbc_option(
|
||||
ejabberd_config:get_local_option(
|
||||
{auth_method, Domain}, fun(V) -> V end)) of
|
||||
true -> [get_db_type(DB)|Acc];
|
||||
_ -> Acc
|
||||
end
|
||||
end,
|
||||
[], Domains),
|
||||
lists:usort(DBs).
|
||||
|
||||
%% @doc Depending in the DB definition, return which type of DB this is.
|
||||
%% Note that MSSQL is detected as ODBC.
|
||||
%% @spec (DB) -> mysql | pgsql | odbc
|
||||
get_db_type(DB) when is_tuple(DB) ->
|
||||
element(1, DB);
|
||||
get_db_type(DB) when is_list(DB) ->
|
||||
odbc.
|
||||
|
||||
%% @doc Return true if odbc option is used
|
||||
check_odbc_option(odbc) ->
|
||||
true;
|
||||
check_odbc_option(AuthMethods) when is_list(AuthMethods) ->
|
||||
lists:member(odbc, AuthMethods);
|
||||
check_odbc_option(_) ->
|
||||
false.
|
||||
+382
-252
@@ -27,15 +27,16 @@
|
||||
-module(ejabberd_config).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, load_file/1,
|
||||
-export([start/0, load_file/1, read_file/1,
|
||||
add_global_option/2, add_local_option/2,
|
||||
get_global_option/2, get_local_option/2,
|
||||
get_global_option/3, get_local_option/3]).
|
||||
-export([get_vh_by_auth_method/1]).
|
||||
-export([is_file_readable/1]).
|
||||
-export([get_version/0, get_myhosts/0, get_mylang/0]).
|
||||
-export([prepare_opt_val/4]).
|
||||
-export([convert_table_to_binary/5]).
|
||||
get_global_option/3, get_local_option/3,
|
||||
get_option/2, get_option/3, add_option/2,
|
||||
get_vh_by_auth_method/1, is_file_readable/1,
|
||||
get_version/0, get_myhosts/0, get_mylang/0,
|
||||
prepare_opt_val/4, convert_table_to_binary/5,
|
||||
transform_options/1, collect_options/1,
|
||||
convert_to_yaml/1, convert_to_yaml/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -52,25 +53,29 @@
|
||||
|
||||
|
||||
start() ->
|
||||
mnesia:create_table(config,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, config)}]),
|
||||
mnesia:add_table_copy(config, node(), ram_copies),
|
||||
case catch mnesia:table_info(local_config, storage_type) of
|
||||
disc_copies ->
|
||||
mnesia:delete_table(local_config);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
mnesia:create_table(local_config,
|
||||
[{disc_copies, [node()]},
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, local_config)}]),
|
||||
mnesia:add_table_copy(local_config, node(), ram_copies),
|
||||
Config = get_ejabberd_config_path(),
|
||||
load_file(Config),
|
||||
State = read_file(Config),
|
||||
%% This start time is used by mod_last:
|
||||
add_local_option(node_start, now()),
|
||||
ok.
|
||||
{MegaSecs, Secs, _} = now(),
|
||||
UnixTime = MegaSecs*1000000 + Secs,
|
||||
State1 = set_option({node_start, global}, UnixTime, State),
|
||||
set_opts(State1).
|
||||
|
||||
%% @doc Get the filename of the ejabberd configuration file.
|
||||
%% The filename can be specified with: erl -config "/path/to/ejabberd.cfg".
|
||||
%% The filename can be specified with: erl -config "/path/to/ejabberd.yml".
|
||||
%% It can also be specified with the environtment variable EJABBERD_CONFIG_PATH.
|
||||
%% If not specified, the default value 'ejabberd.cfg' is assumed.
|
||||
%% If not specified, the default value 'ejabberd.yml' is assumed.
|
||||
%% @spec () -> string()
|
||||
get_ejabberd_config_path() ->
|
||||
case application:get_env(config) of
|
||||
@@ -84,16 +89,59 @@ get_ejabberd_config_path() ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% @doc Load the ejabberd configuration file.
|
||||
%% @doc Read the ejabberd configuration file.
|
||||
%% It also includes additional configuration files and replaces macros.
|
||||
%% This function will crash if finds some error in the configuration file.
|
||||
%% @spec (File::string()) -> ok
|
||||
load_file(File) ->
|
||||
Terms = get_plain_terms_file(File),
|
||||
%% @spec (File::string()) -> #state{}.
|
||||
read_file(File) ->
|
||||
read_file(File, [{replace_macros, true},
|
||||
{include_files, true}]).
|
||||
|
||||
read_file(File, Opts) ->
|
||||
Terms1 = get_plain_terms_file(File, Opts),
|
||||
Terms_macros = case proplists:get_bool(replace_macros, Opts) of
|
||||
true -> replace_macros(Terms1);
|
||||
false -> Terms1
|
||||
end,
|
||||
Terms = transform_terms(Terms_macros),
|
||||
State = lists:foldl(fun search_hosts/2, #state{}, Terms),
|
||||
Terms_macros = replace_macros(Terms),
|
||||
Res = lists:foldl(fun process_term/2, State, Terms_macros),
|
||||
set_opts(Res).
|
||||
{Head, Tail} = lists:partition(
|
||||
fun({host_config, _}) -> false;
|
||||
({append_host_config, _}) -> false;
|
||||
(_) -> true
|
||||
end, Terms),
|
||||
State1 = lists:foldl(fun process_term/2, State, Head ++ Tail),
|
||||
State1#state{opts = compact(State1#state.opts)}.
|
||||
|
||||
-spec load_file(string()) -> ok.
|
||||
|
||||
load_file(File) ->
|
||||
State = read_file(File),
|
||||
set_opts(State).
|
||||
|
||||
-spec convert_to_yaml(file:filename()) -> ok | {error, any()}.
|
||||
|
||||
convert_to_yaml(File) ->
|
||||
convert_to_yaml(File, stdout).
|
||||
|
||||
-spec convert_to_yaml(file:filename(),
|
||||
stdout | file:filename()) -> ok | {error, any()}.
|
||||
|
||||
convert_to_yaml(File, Output) ->
|
||||
State = read_file(File, [{include_files, false}]),
|
||||
Opts = [{K, V} || #local_config{key = K, value = V} <- State#state.opts],
|
||||
{GOpts, HOpts} = split_by_hosts(Opts),
|
||||
NewOpts = GOpts ++ lists:map(
|
||||
fun({Host, Opts1}) ->
|
||||
{host_config, [{Host, Opts1}]}
|
||||
end, HOpts),
|
||||
Data = p1_yaml:encode(lists:reverse(NewOpts)),
|
||||
case Output of
|
||||
stdout ->
|
||||
io:format("~s~n", [Data]);
|
||||
FileName ->
|
||||
file:write_file(FileName, Data)
|
||||
end.
|
||||
|
||||
%% @doc Read an ejabberd configuration file and return the terms.
|
||||
%% Input is an absolute or relative path to an ejabberd config file.
|
||||
@@ -102,22 +150,47 @@ load_file(File) ->
|
||||
%% and the terms in those files were included.
|
||||
%% @spec(string()) -> [term()]
|
||||
%% @spec(iolist()) -> [term()]
|
||||
get_plain_terms_file(File) when is_binary(File) ->
|
||||
get_plain_terms_file(binary_to_list(File));
|
||||
get_plain_terms_file(File1) ->
|
||||
get_plain_terms_file(File) ->
|
||||
get_plain_terms_file(File, [{include_files, true}]).
|
||||
|
||||
get_plain_terms_file(File, Opts) when is_binary(File) ->
|
||||
get_plain_terms_file(binary_to_list(File), Opts);
|
||||
get_plain_terms_file(File1, Opts) ->
|
||||
File = get_absolute_path(File1),
|
||||
case file:consult(File) of
|
||||
case consult(File) of
|
||||
{ok, Terms} ->
|
||||
BinTerms = strings_to_binary(Terms),
|
||||
include_config_files(BinTerms);
|
||||
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
ExitText = describe_config_problem(File, Reason, LineNumber),
|
||||
?ERROR_MSG(ExitText, []),
|
||||
exit_or_halt(ExitText);
|
||||
case proplists:get_bool(include_files, Opts) of
|
||||
true ->
|
||||
include_config_files(BinTerms);
|
||||
false ->
|
||||
BinTerms
|
||||
end;
|
||||
{error, Reason} ->
|
||||
ExitText = describe_config_problem(File, Reason),
|
||||
?ERROR_MSG(ExitText, []),
|
||||
exit_or_halt(ExitText)
|
||||
?ERROR_MSG(Reason, []),
|
||||
exit_or_halt(Reason)
|
||||
end.
|
||||
|
||||
consult(File) ->
|
||||
case filename:extension(File) of
|
||||
".yml" ->
|
||||
case p1_yaml:decode_from_file(File, [plain_as_atom]) of
|
||||
{ok, []} ->
|
||||
{ok, []};
|
||||
{ok, [Document|_]} ->
|
||||
{ok, Document};
|
||||
{error, Err} ->
|
||||
{error, p1_yaml:format_error(Err)}
|
||||
end;
|
||||
_ ->
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
{ok, Terms};
|
||||
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
{error, describe_config_problem(File, Reason, LineNumber)};
|
||||
{error, Reason} ->
|
||||
{error, describe_config_problem(File, Reason)}
|
||||
end
|
||||
end.
|
||||
|
||||
%% @doc Convert configuration filename to absolute path.
|
||||
@@ -161,7 +234,7 @@ search_hosts(Term, State) ->
|
||||
|
||||
add_hosts_to_option(Hosts, State) ->
|
||||
PrepHosts = normalize_hosts(Hosts),
|
||||
add_option(hosts, PrepHosts, State#state{hosts = PrepHosts}).
|
||||
set_option({hosts, global}, PrepHosts, State#state{hosts = PrepHosts}).
|
||||
|
||||
normalize_hosts(Hosts) ->
|
||||
normalize_hosts(Hosts,[]).
|
||||
@@ -235,21 +308,37 @@ exit_or_halt(ExitText) ->
|
||||
%% @doc Include additional configuration files in the list of terms.
|
||||
%% @spec ([term()]) -> [term()]
|
||||
include_config_files(Terms) ->
|
||||
include_config_files(Terms, []).
|
||||
{FileOpts, Terms1} =
|
||||
lists:mapfoldl(
|
||||
fun({include_config_file, _} = T, Ts) ->
|
||||
{[transform_include_option(T)], Ts};
|
||||
({include_config_file, _, _} = T, Ts) ->
|
||||
{[transform_include_option(T)], Ts};
|
||||
(T, Ts) ->
|
||||
{[], [T|Ts]}
|
||||
end, [], Terms),
|
||||
Terms2 = lists:flatmap(
|
||||
fun({File, Opts}) ->
|
||||
include_config_file(File, Opts)
|
||||
end, lists:flatten(FileOpts)),
|
||||
Terms1 ++ Terms2.
|
||||
|
||||
include_config_files([], Res) ->
|
||||
Res;
|
||||
include_config_files([{include_config_file, Filename} | Terms], Res) ->
|
||||
include_config_files([{include_config_file, Filename, []} | Terms], Res);
|
||||
include_config_files([{include_config_file, Filename, Options} | Terms], Res) ->
|
||||
transform_include_option({include_config_file, File}) when is_list(File) ->
|
||||
case is_string(File) of
|
||||
true -> {File, []};
|
||||
false -> File
|
||||
end;
|
||||
transform_include_option({include_config_file, Filename}) ->
|
||||
{Filename, []};
|
||||
transform_include_option({include_config_file, Filename, Options}) ->
|
||||
{Filename, Options}.
|
||||
|
||||
include_config_file(Filename, Options) ->
|
||||
Included_terms = get_plain_terms_file(Filename),
|
||||
Disallow = proplists:get_value(disallow, Options, []),
|
||||
Included_terms2 = delete_disallowed(Disallow, Included_terms),
|
||||
Allow_only = proplists:get_value(allow_only, Options, all),
|
||||
Included_terms3 = keep_only_allowed(Allow_only, Included_terms2),
|
||||
include_config_files(Terms, Res ++ Included_terms3);
|
||||
include_config_files([Term | Terms], Res) ->
|
||||
include_config_files(Terms, Res ++ [Term]).
|
||||
keep_only_allowed(Allow_only, Included_terms2).
|
||||
|
||||
%% @doc Filter from the list of terms the disallowed.
|
||||
%% Returns a sublist of Terms without the ones which first element is
|
||||
@@ -311,12 +400,19 @@ split_terms_macros(Terms) ->
|
||||
fun(Term, {TOs, Ms}) ->
|
||||
case Term of
|
||||
{define_macro, Key, Value} ->
|
||||
case is_atom(Key) and is_all_uppercase(Key) of
|
||||
case is_correct_macro({Key, Value}) of
|
||||
true ->
|
||||
{TOs, Ms++[{Key, Value}]};
|
||||
false ->
|
||||
exit({macro_not_properly_defined, Term})
|
||||
end;
|
||||
{define_macro, KeyVals} ->
|
||||
case lists:all(fun is_correct_macro/1, KeyVals) of
|
||||
true ->
|
||||
{TOs, Ms ++ KeyVals};
|
||||
false ->
|
||||
exit({macros_not_properly_defined, Term})
|
||||
end;
|
||||
Term ->
|
||||
{TOs ++ [Term], Ms}
|
||||
end
|
||||
@@ -324,6 +420,11 @@ split_terms_macros(Terms) ->
|
||||
{[], []},
|
||||
Terms).
|
||||
|
||||
is_correct_macro({Key, _Val}) ->
|
||||
is_atom(Key) and is_all_uppercase(Key);
|
||||
is_correct_macro(_) ->
|
||||
false.
|
||||
|
||||
%% @doc Recursively replace in Terms macro usages with the defined value.
|
||||
%% @spec (Terms, Macros) -> Terms
|
||||
%% Terms = [term()]
|
||||
@@ -331,7 +432,9 @@ split_terms_macros(Terms) ->
|
||||
replace([], _) ->
|
||||
[];
|
||||
replace([Term|Terms], Macros) ->
|
||||
[replace_term(Term, Macros) | replace(Terms, Macros)].
|
||||
[replace_term(Term, Macros) | replace(Terms, Macros)];
|
||||
replace(Term, Macros) ->
|
||||
replace_term(Term, Macros).
|
||||
|
||||
replace_term(Key, Macros) when is_atom(Key) ->
|
||||
case is_all_uppercase(Key) of
|
||||
@@ -365,197 +468,65 @@ is_all_uppercase(Atom) ->
|
||||
|
||||
process_term(Term, State) ->
|
||||
case Term of
|
||||
override_global ->
|
||||
State#state{override_global = true};
|
||||
override_local ->
|
||||
State#state{override_local = true};
|
||||
override_acls ->
|
||||
State#state{override_acls = true};
|
||||
{acl, _ACLName, _ACLData} ->
|
||||
process_host_term(Term, global, State);
|
||||
{access, _RuleName, _Rules} ->
|
||||
process_host_term(Term, global, State);
|
||||
{shaper, _Name, _Data} ->
|
||||
%%lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end,
|
||||
%% State, State#state.hosts);
|
||||
process_host_term(Term, global, State);
|
||||
{host, _Host} ->
|
||||
State;
|
||||
{hosts, _Hosts} ->
|
||||
State;
|
||||
{fqdn, HostFQDN} ->
|
||||
?DEBUG("FQDN set to: ~p", [HostFQDN]),
|
||||
add_option(fqdn, HostFQDN, State);
|
||||
{host_config, Host, Terms} ->
|
||||
lists:foldl(fun(T, S) -> process_host_term(T, Host, S) end,
|
||||
State, Terms);
|
||||
{listen, Listeners} ->
|
||||
Listeners2 =
|
||||
lists:map(
|
||||
fun({PortIP, Module, Opts}) ->
|
||||
{Port, IPT, _, _, Proto, OptsClean} =
|
||||
ejabberd_listener:parse_listener_portip(PortIP, Opts),
|
||||
{{Port, IPT, Proto}, Module, OptsClean}
|
||||
end,
|
||||
Listeners),
|
||||
add_option(listen, Listeners2, State);
|
||||
{language, Val} ->
|
||||
add_option(language, Val, State);
|
||||
{outgoing_s2s_port, Port} ->
|
||||
add_option(outgoing_s2s_port, Port, State);
|
||||
{outgoing_s2s_options, Methods, Timeout} ->
|
||||
add_option(outgoing_s2s_options, {Methods, Timeout}, State);
|
||||
{s2s_dns_options, PropList} ->
|
||||
add_option(s2s_dns_options, PropList, State);
|
||||
{s2s_use_starttls, Port} ->
|
||||
add_option(s2s_use_starttls, Port, State);
|
||||
{s2s_certfile, CertFile} ->
|
||||
CertFileS = binary_to_list(CertFile),
|
||||
case ejabberd_config:is_file_readable(CertFileS) of
|
||||
true -> add_option(s2s_certfile, CertFile, State);
|
||||
false ->
|
||||
ErrorText = "There is a problem in the configuration: "
|
||||
"the specified file is not readable: ",
|
||||
throw({error, ErrorText ++ CertFileS})
|
||||
end;
|
||||
{domain_certfile, Domain, CertFile} ->
|
||||
CertFileS = binary_to_list(CertFile),
|
||||
case ejabberd_config:is_file_readable(CertFileS) of
|
||||
true -> add_option({domain_certfile, Domain}, CertFile, State);
|
||||
false ->
|
||||
ErrorText = "There is a problem in the configuration: "
|
||||
"the specified file is not readable: ",
|
||||
throw({error, ErrorText ++ CertFileS})
|
||||
end;
|
||||
{node_type, NodeType} ->
|
||||
add_option(node_type, NodeType, State);
|
||||
{cluster_nodes, Nodes} ->
|
||||
add_option(cluster_nodes, Nodes, State);
|
||||
{domain_balancing, Domain, Balancing} ->
|
||||
add_option({domain_balancing, Domain}, Balancing, State);
|
||||
{domain_balancing_component_number, Domain, N} ->
|
||||
add_option({domain_balancing_component_number, Domain}, N, State);
|
||||
{watchdog_admins, Admins} ->
|
||||
add_option(watchdog_admins, Admins, State);
|
||||
{watchdog_large_heap, LH} ->
|
||||
add_option(watchdog_large_heap, LH, State);
|
||||
{registration_timeout, Timeout} ->
|
||||
add_option(registration_timeout, Timeout, State);
|
||||
{captcha_cmd, Cmd} ->
|
||||
add_option(captcha_cmd, Cmd, State);
|
||||
{captcha_host, Host} ->
|
||||
add_option(captcha_host, Host, State);
|
||||
{captcha_limit, Limit} ->
|
||||
add_option(captcha_limit, Limit, State);
|
||||
{ejabberdctl_access_commands, ACs} ->
|
||||
add_option(ejabberdctl_access_commands, ACs, State);
|
||||
{loglevel, Loglevel} ->
|
||||
ejabberd_logger:set(Loglevel),
|
||||
State;
|
||||
{max_fsm_queue, N} ->
|
||||
add_option(max_fsm_queue, N, State);
|
||||
{_Opt, _Val} ->
|
||||
lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end,
|
||||
State, State#state.hosts)
|
||||
end.
|
||||
|
||||
process_host_term(Term, Host, State) ->
|
||||
case Term of
|
||||
{acl, ACLName, ACLData} ->
|
||||
State#state{opts =
|
||||
[acl:to_record(Host, ACLName, ACLData) | State#state.opts]};
|
||||
{access, RuleName, Rules} ->
|
||||
State#state{opts = [#config{key = {access, RuleName, Host},
|
||||
value = Rules} |
|
||||
State#state.opts]};
|
||||
{shaper, Name, Data} ->
|
||||
State#state{opts = [#config{key = {shaper, Name, Host},
|
||||
value = Data} |
|
||||
State#state.opts]};
|
||||
{host, Host} ->
|
||||
State;
|
||||
{hosts, _Hosts} ->
|
||||
State;
|
||||
{odbc_server, ODBC_server} ->
|
||||
add_option({odbc_server, Host}, ODBC_server, State);
|
||||
{modules, Modules} ->
|
||||
add_option({modules, Host}, replace_modules(Modules), State);
|
||||
{Opt, Val} ->
|
||||
add_option({Opt, Host}, Val, State)
|
||||
end.
|
||||
|
||||
add_option(Opt, Val, State) ->
|
||||
Table = case Opt of
|
||||
hosts ->
|
||||
config;
|
||||
language ->
|
||||
config;
|
||||
_ ->
|
||||
local_config
|
||||
end,
|
||||
case Table of
|
||||
config ->
|
||||
State#state{opts = [#config{key = Opt, value = Val} |
|
||||
State#state.opts]};
|
||||
local_config ->
|
||||
case Opt of
|
||||
{{add, OptName}, Host} ->
|
||||
State#state{opts = compact({OptName, Host}, Val,
|
||||
State#state.opts, [])};
|
||||
_ ->
|
||||
State#state{opts = [#local_config{key = Opt, value = Val} |
|
||||
State#state.opts]}
|
||||
end
|
||||
end.
|
||||
|
||||
compact({OptName, Host} = Opt, Val, [], Os) ->
|
||||
?WARNING_MSG("The option '~p' is defined for the host ~p using host_config "
|
||||
"before the global '~p' option. This host_config option may get overwritten.", [OptName, Host, OptName]),
|
||||
[#local_config{key = Opt, value = Val}] ++ Os;
|
||||
%% Traverse the list of the options already parsed
|
||||
compact(Opt, Val, [O | Os1], Os2) ->
|
||||
case catch O#local_config.key of
|
||||
%% If the key of a local_config matches the Opt that wants to be added
|
||||
Opt ->
|
||||
%% Then prepend the new value to the list of old values
|
||||
Os2 ++ [#local_config{key = Opt,
|
||||
value = Val++O#local_config.value}
|
||||
] ++ Os1;
|
||||
{host_config, HostTerms} ->
|
||||
lists:foldl(
|
||||
fun({Host, Terms}, AccState) ->
|
||||
lists:foldl(fun(T, S) ->
|
||||
process_host_term(T, Host, S, set)
|
||||
end, AccState, Terms)
|
||||
end, State, HostTerms);
|
||||
{append_host_config, HostTerms} ->
|
||||
lists:foldl(
|
||||
fun({Host, Terms}, AccState) ->
|
||||
lists:foldl(fun(T, S) ->
|
||||
process_host_term(T, Host, S, append)
|
||||
end, AccState, Terms)
|
||||
end, State, HostTerms);
|
||||
_ ->
|
||||
compact(Opt, Val, Os1, Os2++[O])
|
||||
process_host_term(Term, global, State, set)
|
||||
end.
|
||||
|
||||
process_host_term(Term, Host, State, Action) ->
|
||||
case Term of
|
||||
{modules, Modules} when Action == set ->
|
||||
set_option({modules, Host}, replace_modules(Modules), State);
|
||||
{modules, Modules} when Action == append ->
|
||||
append_option({modules, Host}, replace_modules(Modules), State);
|
||||
{host, _} ->
|
||||
State;
|
||||
{hosts, _} ->
|
||||
State;
|
||||
{Opt, Val} when Action == set ->
|
||||
set_option({Opt, Host}, Val, State);
|
||||
{Opt, Val} when Action == append ->
|
||||
append_option({Opt, Host}, Val, State);
|
||||
Opt ->
|
||||
?WARNING_MSG("Ignore invalid (outdated?) option ~p", [Opt]),
|
||||
State
|
||||
end.
|
||||
|
||||
set_option(Opt, Val, State) ->
|
||||
State#state{opts = [#local_config{key = Opt, value = Val} |
|
||||
State#state.opts]}.
|
||||
|
||||
append_option({Opt, Host}, Val, State) ->
|
||||
GlobalVals = lists:flatmap(
|
||||
fun(#local_config{key = {O, global}, value = V})
|
||||
when O == Opt ->
|
||||
if is_list(V) -> V;
|
||||
true -> [V]
|
||||
end;
|
||||
(_) ->
|
||||
[]
|
||||
end, State#state.opts),
|
||||
NewVal = if is_list(Val) -> Val ++ GlobalVals;
|
||||
true -> [Val|GlobalVals]
|
||||
end,
|
||||
set_option({Opt, Host}, NewVal, State).
|
||||
|
||||
set_opts(State) ->
|
||||
Opts = lists:reverse(State#state.opts),
|
||||
Opts = State#state.opts,
|
||||
F = fun() ->
|
||||
if
|
||||
State#state.override_global ->
|
||||
Ksg = mnesia:all_keys(config),
|
||||
lists:foreach(fun(K) ->
|
||||
mnesia:delete({config, K})
|
||||
end, Ksg);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if
|
||||
State#state.override_local ->
|
||||
Ksl = mnesia:all_keys(local_config),
|
||||
lists:foreach(fun(K) ->
|
||||
mnesia:delete({local_config, K})
|
||||
end, Ksl);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if
|
||||
State#state.override_acls ->
|
||||
Ksa = mnesia:all_keys(acl),
|
||||
lists:foreach(fun(K) ->
|
||||
mnesia:delete({acl, K})
|
||||
end, Ksa);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(fun(R) ->
|
||||
mnesia:write(R)
|
||||
end, Opts)
|
||||
@@ -576,12 +547,14 @@ set_opts(State) ->
|
||||
end.
|
||||
|
||||
add_global_option(Opt, Val) ->
|
||||
mnesia:transaction(fun() ->
|
||||
mnesia:write(#config{key = Opt,
|
||||
value = Val})
|
||||
end).
|
||||
add_option(Opt, Val).
|
||||
|
||||
add_local_option(Opt, Val) ->
|
||||
add_option(Opt, Val).
|
||||
|
||||
add_option(Opt, Val) when is_atom(Opt) ->
|
||||
add_option({Opt, global}, Val);
|
||||
add_option(Opt, Val) ->
|
||||
mnesia:transaction(fun() ->
|
||||
mnesia:write(#local_config{key = Opt,
|
||||
value = Val})
|
||||
@@ -615,31 +588,49 @@ prepare_opt_val(Opt, Val, F, Default) ->
|
||||
-spec get_global_option(any(), check_fun()) -> any().
|
||||
|
||||
get_global_option(Opt, F) ->
|
||||
get_global_option(Opt, F, undefined).
|
||||
get_option(Opt, F, undefined).
|
||||
|
||||
-spec get_global_option(any(), check_fun(), any()) -> any().
|
||||
|
||||
get_global_option(Opt, F, Default) ->
|
||||
case ets:lookup(config, Opt) of
|
||||
[#config{value = Val}] ->
|
||||
prepare_opt_val(Opt, Val, F, Default);
|
||||
_ ->
|
||||
Default
|
||||
end.
|
||||
get_option(Opt, F, Default).
|
||||
|
||||
-spec get_local_option(any(), check_fun()) -> any().
|
||||
|
||||
get_local_option(Opt, F) ->
|
||||
get_local_option(Opt, F, undefined).
|
||||
get_option(Opt, F, undefined).
|
||||
|
||||
-spec get_local_option(any(), check_fun(), any()) -> any().
|
||||
|
||||
get_local_option(Opt, F, Default) ->
|
||||
get_option(Opt, F, Default).
|
||||
|
||||
-spec get_option(any(), check_fun()) -> any().
|
||||
|
||||
get_option(Opt, F) ->
|
||||
get_option(Opt, F, undefined).
|
||||
|
||||
-spec get_option(any(), check_fun(), any()) -> any().
|
||||
|
||||
get_option(Opt, F, Default) when is_atom(Opt) ->
|
||||
get_option({Opt, global}, F, Default);
|
||||
get_option(Opt, F, Default) ->
|
||||
case Opt of
|
||||
{O, global} when is_atom(O) -> ok;
|
||||
{O, H} when is_atom(O), is_binary(H) -> ok;
|
||||
_ -> ?WARNING_MSG("Option ~p has invalid (outdated?) format. "
|
||||
"This is likely a bug", [Opt])
|
||||
end,
|
||||
case ets:lookup(local_config, Opt) of
|
||||
[#local_config{value = Val}] ->
|
||||
prepare_opt_val(Opt, Val, F, Default);
|
||||
_ ->
|
||||
Default
|
||||
_ ->
|
||||
case Opt of
|
||||
{Key, Host} when Host /= global ->
|
||||
get_option({Key, global}, F, Default);
|
||||
_ ->
|
||||
Default
|
||||
end
|
||||
end.
|
||||
|
||||
-spec get_vh_by_auth_method(atom()) -> [binary()].
|
||||
@@ -669,12 +660,12 @@ get_version() ->
|
||||
-spec get_myhosts() -> [binary()].
|
||||
|
||||
get_myhosts() ->
|
||||
ejabberd_config:get_global_option(hosts, fun(V) -> V end).
|
||||
get_option(hosts, fun(V) -> V end).
|
||||
|
||||
-spec get_mylang() -> binary().
|
||||
|
||||
get_mylang() ->
|
||||
ejabberd_config:get_global_option(
|
||||
get_option(
|
||||
language,
|
||||
fun iolist_to_binary/1,
|
||||
<<"en">>).
|
||||
@@ -702,14 +693,14 @@ replace_modules(Modules) ->
|
||||
emit_deprecation_warning(Module, NewModule, DBType),
|
||||
NewOpts = [{db_type, DBType} |
|
||||
lists:keydelete(db_type, 1, Opts)],
|
||||
{NewModule, NewOpts};
|
||||
{NewModule, transform_module_options(Module, NewOpts)};
|
||||
NewModule ->
|
||||
if Module /= NewModule ->
|
||||
emit_deprecation_warning(Module, NewModule);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{NewModule, Opts}
|
||||
{NewModule, transform_module_options(Module, Opts)}
|
||||
end
|
||||
end, Modules).
|
||||
|
||||
@@ -722,6 +713,9 @@ strings_to_binary(L) when is_list(L) ->
|
||||
false ->
|
||||
strings_to_binary1(L)
|
||||
end;
|
||||
strings_to_binary({A, B, C, D}) when
|
||||
is_integer(A), is_integer(B), is_integer(C), is_integer(D) ->
|
||||
{A, B, C ,D};
|
||||
strings_to_binary(T) when is_tuple(T) ->
|
||||
list_to_tuple(strings_to_binary1(tuple_to_list(T)));
|
||||
strings_to_binary(X) ->
|
||||
@@ -762,6 +756,142 @@ format_term(S) when is_list(S), S /= [] ->
|
||||
format_term(T) ->
|
||||
io_lib:format("~p", [binary_to_strings(T)]).
|
||||
|
||||
transform_terms(Terms) ->
|
||||
%% We could check all ejabberd beams, but this
|
||||
%% slows down start-up procedure :(
|
||||
Mods = [mod_register,
|
||||
mod_last,
|
||||
ejabberd_s2s,
|
||||
ejabberd_listener,
|
||||
ejabberd_odbc_sup,
|
||||
shaper,
|
||||
ejabberd_s2s_out,
|
||||
acl,
|
||||
ejabberd_config],
|
||||
collect_options(transform_terms(Mods, Terms)).
|
||||
|
||||
transform_terms([Mod|Mods], Terms) ->
|
||||
case catch Mod:transform_options(Terms) of
|
||||
{'EXIT', _} = Err ->
|
||||
?ERROR_MSG("Failed to transform terms by ~p: ~p", [Mod, Err]),
|
||||
transform_terms(Mods, Terms);
|
||||
NewTerms ->
|
||||
transform_terms(Mods, NewTerms)
|
||||
end;
|
||||
transform_terms([], NewTerms) ->
|
||||
NewTerms.
|
||||
|
||||
transform_module_options(Module, Opts) ->
|
||||
Opts1 = gen_iq_handler:transform_module_options(Opts),
|
||||
try
|
||||
Module:transform_module_options(Opts1)
|
||||
catch error:undef ->
|
||||
Opts1
|
||||
end.
|
||||
|
||||
compact(Cfg) ->
|
||||
Opts = [{K, V} || #local_config{key = K, value = V} <- Cfg],
|
||||
{GOpts, HOpts} = split_by_hosts(Opts),
|
||||
[#local_config{key = {O, global}, value = V} || {O, V} <- GOpts] ++
|
||||
lists:flatmap(
|
||||
fun({Host, OptVal}) ->
|
||||
case lists:member(OptVal, GOpts) of
|
||||
true ->
|
||||
[];
|
||||
false ->
|
||||
[#local_config{key = {Opt, Host}, value = Val}
|
||||
|| {Opt, Val} <- OptVal]
|
||||
end
|
||||
end, lists:flatten(HOpts)).
|
||||
|
||||
split_by_hosts(Opts) ->
|
||||
Opts1 = orddict:to_list(
|
||||
lists:foldl(
|
||||
fun({{Opt, Host}, Val}, D) ->
|
||||
orddict:append(Host, {Opt, Val}, D)
|
||||
end, orddict:new(), Opts)),
|
||||
case lists:keytake(global, 1, Opts1) of
|
||||
{value, {global, GlobalOpts}, HostOpts} ->
|
||||
{GlobalOpts, HostOpts};
|
||||
_ ->
|
||||
{[], Opts1}
|
||||
end.
|
||||
|
||||
collect_options(Opts) ->
|
||||
{D, InvalidOpts} =
|
||||
lists:foldl(
|
||||
fun({K, V}, {D, Os}) when is_list(V) ->
|
||||
{orddict:append_list(K, V, D), Os};
|
||||
({K, V}, {D, Os}) ->
|
||||
{orddict:store(K, V, D), Os};
|
||||
(Opt, {D, Os}) ->
|
||||
{D, [Opt|Os]}
|
||||
end, {orddict:new(), []}, Opts),
|
||||
InvalidOpts ++ orddict:to_list(D).
|
||||
|
||||
transform_options(Opts) ->
|
||||
Opts1 = lists:foldl(fun transform_options/2, [], Opts),
|
||||
{HOpts, Opts2} = lists:mapfoldl(
|
||||
fun({host_config, O}, Os) ->
|
||||
{[O], Os};
|
||||
(O, Os) ->
|
||||
{[], [O|Os]}
|
||||
end, [], Opts1),
|
||||
{AHOpts, Opts3} = lists:mapfoldl(
|
||||
fun({append_host_config, O}, Os) ->
|
||||
{[O], Os};
|
||||
(O, Os) ->
|
||||
{[], [O|Os]}
|
||||
end, [], Opts2),
|
||||
HOpts1 = case collect_options(lists:flatten(HOpts)) of
|
||||
[] ->
|
||||
[];
|
||||
HOs ->
|
||||
[{host_config,
|
||||
[{H, transform_terms(O)} || {H, O} <- HOs]}]
|
||||
end,
|
||||
AHOpts1 = case collect_options(lists:flatten(AHOpts)) of
|
||||
[] ->
|
||||
[];
|
||||
AHOs ->
|
||||
[{append_host_config,
|
||||
[{H, transform_terms(O)} || {H, O} <- AHOs]}]
|
||||
end,
|
||||
HOpts1 ++ AHOpts1 ++ Opts3.
|
||||
|
||||
transform_options({domain_certfile, Domain, CertFile}, Opts) ->
|
||||
?WARNING_MSG("Option 'domain_certfile' now should be defined "
|
||||
"per virtual host or globally. The old format is "
|
||||
"still supported but it is better to fix your config", []),
|
||||
[{host_config, [{Domain, [{domain_certfile, CertFile}]}]}|Opts];
|
||||
transform_options(Opt, Opts) when Opt == override_global;
|
||||
Opt == override_local;
|
||||
Opt == override_acls ->
|
||||
?WARNING_MSG("Ignoring '~s' option which has no effect anymore", [Opt]),
|
||||
Opts;
|
||||
transform_options({host_config, Host, HOpts}, Opts) ->
|
||||
{AddOpts, HOpts1} =
|
||||
lists:mapfoldl(
|
||||
fun({{add, Opt}, Val}, Os) ->
|
||||
?WARNING_MSG("Option 'add' is deprecated. "
|
||||
"The option is still supported "
|
||||
"but it is better to fix your config: "
|
||||
"use 'append_host_config' instead.", []),
|
||||
{[{Opt, Val}], Os};
|
||||
(O, Os) ->
|
||||
{[], [O|Os]}
|
||||
end, [], HOpts),
|
||||
[{append_host_config, [{Host, lists:flatten(AddOpts)}]},
|
||||
{host_config, [{Host, HOpts1}]}|Opts];
|
||||
transform_options({define_macro, Macro, Val}, Opts) ->
|
||||
[{define_macro, [{Macro, Val}]}|Opts];
|
||||
transform_options({include_config_file, _} = Opt, Opts) ->
|
||||
[{include_config_file, [transform_include_option(Opt)]} | Opts];
|
||||
transform_options({include_config_file, _, _} = Opt, Opts) ->
|
||||
[{include_config_file, [transform_include_option(Opt)]} | Opts];
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-spec convert_table_to_binary(atom(), [atom()], atom(),
|
||||
fun(), fun()) -> ok.
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ process(["status"]) ->
|
||||
[node(), InternalStatus, ProvidedStatus]),
|
||||
case lists:keysearch(ejabberd, 1, application:which_applications()) of
|
||||
false ->
|
||||
EjabberdLogPath = ejabberd_app:get_log_path(),
|
||||
EjabberdLogPath = ejabberd_logger:get_log_path(),
|
||||
?PRINT("ejabberd is not running in that node~n"
|
||||
"Check for error messages: ~s~n"
|
||||
"or other files in that directory.~n", [EjabberdLogPath]),
|
||||
@@ -237,7 +237,7 @@ process2(Args, Auth, AccessCommands) ->
|
||||
end.
|
||||
|
||||
get_accesscommands() ->
|
||||
ejabberd_config:get_local_option(ejabberdctl_access_commands,
|
||||
ejabberd_config:get_option(ejabberdctl_access_commands,
|
||||
fun(V) when is_list(V) -> V end, []).
|
||||
|
||||
%%-----------------------------
|
||||
|
||||
@@ -280,7 +280,7 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
check_starttls(SockMod, Socket, Receiver, Opts) ->
|
||||
TLSEnabled = lists:member(tls, Opts),
|
||||
TLSEnabled = proplists:get_bool(tls, Opts),
|
||||
TLSOpts = lists:filter(fun({certfile, _}) -> true;
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
@@ -292,4 +292,3 @@ check_starttls(SockMod, Socket, Receiver, Opts) ->
|
||||
true ->
|
||||
{SockMod, Socket}
|
||||
end.
|
||||
|
||||
|
||||
+45
-17
@@ -30,7 +30,8 @@
|
||||
|
||||
%% External exports
|
||||
-export([start/2, start_link/2, become_controller/1,
|
||||
socket_type/0, receive_headers/1, url_encode/1]).
|
||||
socket_type/0, receive_headers/1, url_encode/1,
|
||||
transform_listen_option/2]).
|
||||
|
||||
%% Callbacks
|
||||
-export([init/2]).
|
||||
@@ -91,12 +92,16 @@ start_link(SockData, Opts) ->
|
||||
[SockData, Opts])}.
|
||||
|
||||
init({SockMod, Socket}, Opts) ->
|
||||
TLSEnabled = lists:member(tls, Opts),
|
||||
TLSEnabled = proplists:get_bool(tls, Opts),
|
||||
TLSOpts1 = lists:filter(fun ({certfile, _}) -> true;
|
||||
(_) -> false
|
||||
end,
|
||||
Opts),
|
||||
TLSOpts = [verify_none | TLSOpts1],
|
||||
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
|
||||
false -> [compression_none | TLSOpts1];
|
||||
true -> TLSOpts1
|
||||
end,
|
||||
TLSOpts = [verify_none | TLSOpts2],
|
||||
{SockMod1, Socket1} = if TLSEnabled ->
|
||||
inet:setopts(Socket, [{recbuf, 8192}]),
|
||||
{ok, TLSSocket} = p1_tls:tcp_to_tls(Socket,
|
||||
@@ -109,32 +114,33 @@ init({SockMod, Socket}, Opts) ->
|
||||
inet:setopts(Socket1, [{packet, http_bin}, {recbuf, 8192}]);
|
||||
_ -> ok
|
||||
end,
|
||||
Captcha = case lists:member(captcha, Opts) of
|
||||
Captcha = case proplists:get_bool(captcha, Opts) of
|
||||
true -> [{[<<"captcha">>], ejabberd_captcha}];
|
||||
false -> []
|
||||
end,
|
||||
Register = case lists:member(register, Opts) of
|
||||
Register = case proplists:get_bool(register, Opts) of
|
||||
true -> [{[<<"register">>], mod_register_web}];
|
||||
false -> []
|
||||
end,
|
||||
Admin = case lists:member(web_admin, Opts) of
|
||||
Admin = case proplists:get_bool(web_admin, Opts) of
|
||||
true -> [{[<<"admin">>], ejabberd_web_admin}];
|
||||
false -> []
|
||||
end,
|
||||
Bind = case lists:member(http_bind, Opts) of
|
||||
Bind = case proplists:get_bool(http_bind, Opts) of
|
||||
true -> [{[<<"http-bind">>], mod_http_bind}];
|
||||
false -> []
|
||||
end,
|
||||
Poll = case lists:member(http_poll, Opts) of
|
||||
Poll = case proplists:get_bool(http_poll, Opts) of
|
||||
true -> [{[<<"http-poll">>], ejabberd_http_poll}];
|
||||
false -> []
|
||||
end,
|
||||
DefinedHandlers = case lists:keysearch(request_handlers,
|
||||
1, Opts)
|
||||
of
|
||||
{value, {request_handlers, H}} -> H;
|
||||
false -> []
|
||||
end,
|
||||
DefinedHandlers = gen_mod:get_opt(
|
||||
request_handlers, Opts,
|
||||
fun(Hs) ->
|
||||
[{str:tokens(
|
||||
iolist_to_binary(Path), <<"/">>),
|
||||
Mod} || {Path, Mod} <- Hs]
|
||||
end, []),
|
||||
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
||||
Admin ++ Bind ++ Poll,
|
||||
?DEBUG("S: ~p~n", [RequestHandlers]),
|
||||
@@ -195,8 +201,8 @@ parse_headers(#state{request_method = Method,
|
||||
trail = Data} =
|
||||
State) ->
|
||||
PktType = case Method of
|
||||
undefined -> http;
|
||||
_ -> httph
|
||||
undefined -> http_bin;
|
||||
_ -> httph_bin
|
||||
end,
|
||||
case erlang:decode_packet(PktType, Data, []) of
|
||||
{ok, Pkt, Rest} ->
|
||||
@@ -480,7 +486,7 @@ analyze_ip_xff(IP, [], _Host) -> IP;
|
||||
analyze_ip_xff({IPLast, Port}, XFF, Host) ->
|
||||
[ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++
|
||||
[jlib:ip_to_list(IPLast)],
|
||||
TrustedProxies = ejabberd_config:get_local_option(
|
||||
TrustedProxies = ejabberd_config:get_option(
|
||||
{trusted_proxies, Host},
|
||||
fun(TPs) ->
|
||||
[iolist_to_binary(TP) || TP <- TPs]
|
||||
@@ -830,3 +836,25 @@ normalize_path([_Parent, <<"..">>|Path], Norm) ->
|
||||
normalize_path(Path, Norm);
|
||||
normalize_path([Part | Path], Norm) ->
|
||||
normalize_path(Path, [Part|Norm]).
|
||||
|
||||
transform_listen_option(captcha, Opts) ->
|
||||
[{captcha, true}|Opts];
|
||||
transform_listen_option(register, Opts) ->
|
||||
[{register, true}|Opts];
|
||||
transform_listen_option(web_admin, Opts) ->
|
||||
[{web_admin, true}|Opts];
|
||||
transform_listen_option(http_bind, Opts) ->
|
||||
[{http_bind, true}|Opts];
|
||||
transform_listen_option(http_poll, Opts) ->
|
||||
[{http_poll, true}|Opts];
|
||||
transform_listen_option({request_handlers, Hs}, Opts) ->
|
||||
Hs1 = lists:map(
|
||||
fun({PList, Mod}) when is_list(PList) ->
|
||||
Path = iolist_to_binary([[$/, P] || P <- PList]),
|
||||
{Path, Mod};
|
||||
(Opt) ->
|
||||
Opt
|
||||
end, Hs),
|
||||
[{request_handlers, Hs1} | Opts];
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
@@ -205,7 +205,7 @@ get_human_html_xmlel() ->
|
||||
init([ID, Key, IP]) ->
|
||||
?INFO_MSG("started: ~p", [{ID, Key, IP}]),
|
||||
Opts = ejabberd_c2s_config:get_c2s_limits(),
|
||||
HTTPPollTimeout = ejabberd_config:get_local_option(
|
||||
HTTPPollTimeout = ejabberd_config:get_option(
|
||||
{http_poll_timeout, ?MYNAME},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
?HTTP_POLL_TIMEOUT) * 1000,
|
||||
|
||||
+77
-22
@@ -36,7 +36,8 @@
|
||||
parse_listener_portip/2,
|
||||
add_listener/3,
|
||||
delete_listener/2,
|
||||
validate_cfg/1
|
||||
transform_options/1,
|
||||
validate_cfg/1
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -55,7 +56,7 @@ init(_) ->
|
||||
{ok, {{one_for_one, 10, 1}, []}}.
|
||||
|
||||
bind_tcp_ports() ->
|
||||
case ejabberd_config:get_local_option(listen, fun validate_cfg/1) of
|
||||
case ejabberd_config:get_option(listen, fun validate_cfg/1) of
|
||||
undefined ->
|
||||
ignore;
|
||||
Ls ->
|
||||
@@ -88,7 +89,7 @@ bind_tcp_port(PortIP, Module, RawOpts) ->
|
||||
end.
|
||||
|
||||
start_listeners() ->
|
||||
case ejabberd_config:get_local_option(listen, fun validate_cfg/1) of
|
||||
case ejabberd_config:get_option(listen, fun validate_cfg/1) of
|
||||
undefined ->
|
||||
ignore;
|
||||
Ls ->
|
||||
@@ -267,7 +268,7 @@ strip_ip_option(Opts) ->
|
||||
Opts),
|
||||
case IPL of
|
||||
%% Only the first ip option is considered
|
||||
[{ip, T1} | _] when is_tuple(T1) ->
|
||||
[{ip, T1} | _] ->
|
||||
{T1, OptsNoIP};
|
||||
[] ->
|
||||
{no_ip_option, OptsNoIP}
|
||||
@@ -364,7 +365,7 @@ start_listener_sup(Port, Module, Opts) ->
|
||||
supervisor:start_child(ejabberd_listeners, ChildSpec).
|
||||
|
||||
stop_listeners() ->
|
||||
Ports = ejabberd_config:get_local_option(listen, fun validate_cfg/1),
|
||||
Ports = ejabberd_config:get_option(listen, fun validate_cfg/1),
|
||||
lists:foreach(
|
||||
fun({PortIpNetp, Module, _Opts}) ->
|
||||
delete_listener(PortIpNetp, Module)
|
||||
@@ -397,7 +398,7 @@ add_listener(PortIP, Module, Opts) ->
|
||||
PortIP1 = {Port, IPT, Proto},
|
||||
case start_listener(PortIP1, Module, Opts) of
|
||||
{ok, _Pid} ->
|
||||
Ports = case ejabberd_config:get_local_option(
|
||||
Ports = case ejabberd_config:get_option(
|
||||
listen, fun validate_cfg/1) of
|
||||
undefined ->
|
||||
[];
|
||||
@@ -406,7 +407,8 @@ add_listener(PortIP, Module, Opts) ->
|
||||
end,
|
||||
Ports1 = lists:keydelete(PortIP1, 1, Ports),
|
||||
Ports2 = [{PortIP1, Module, Opts} | Ports1],
|
||||
ejabberd_config:add_local_option(listen, Ports2),
|
||||
Ports3 = lists:map(fun transform_option/1, Ports2),
|
||||
ejabberd_config:add_option(listen, Ports3),
|
||||
ok;
|
||||
{error, {already_started, _Pid}} ->
|
||||
{error, {already_started, PortIP}};
|
||||
@@ -428,7 +430,7 @@ delete_listener(PortIP, Module) ->
|
||||
delete_listener(PortIP, Module, Opts) ->
|
||||
{Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts),
|
||||
PortIP1 = {Port, IPT, Proto},
|
||||
Ports = case ejabberd_config:get_local_option(
|
||||
Ports = case ejabberd_config:get_option(
|
||||
listen, fun validate_cfg/1) of
|
||||
undefined ->
|
||||
[];
|
||||
@@ -436,7 +438,8 @@ delete_listener(PortIP, Module, Opts) ->
|
||||
Ls
|
||||
end,
|
||||
Ports1 = lists:keydelete(PortIP1, 1, Ports),
|
||||
ejabberd_config:add_local_option(listen, Ports1),
|
||||
Ports2 = lists:map(fun transform_option/1, Ports1),
|
||||
ejabberd_config:add_option(listen, Ports2),
|
||||
stop_listener(PortIP1, Module).
|
||||
|
||||
|
||||
@@ -541,6 +544,55 @@ format_error(Reason) ->
|
||||
-define(IS_PORT(P), (is_integer(P) and (P > 0) and (P =< 65535))).
|
||||
-define(IS_TRANSPORT(T), ((T == tcp) or (T == udp))).
|
||||
|
||||
transform_option({{Port, IP, Transport}, Mod, Opts}) ->
|
||||
IPStr = if is_tuple(IP) ->
|
||||
list_to_binary(inet_parse:ntoa(IP));
|
||||
true ->
|
||||
IP
|
||||
end,
|
||||
Opts1 = lists:map(
|
||||
fun({ip, IPT}) when is_tuple(IPT) ->
|
||||
{ip, list_to_binary(inet_parse:ntoa(IP))};
|
||||
(tls) -> {tls, true};
|
||||
(ssl) -> {tls, true};
|
||||
(zlib) -> {zlib, true};
|
||||
(starttls) -> {starttls, true};
|
||||
(starttls_required) -> {starttls_required, true};
|
||||
(Opt) -> Opt
|
||||
end, Opts),
|
||||
Opts2 = lists:foldl(
|
||||
fun(Opt, Acc) ->
|
||||
try
|
||||
Mod:transform_listen_option(Opt, Acc)
|
||||
catch error:undef ->
|
||||
Acc
|
||||
end
|
||||
end, [], Opts1),
|
||||
TransportOpt = if Transport == tcp -> [];
|
||||
true -> [{transport, Transport}]
|
||||
end,
|
||||
IPOpt = if IPStr == <<"0.0.0.0">> -> [];
|
||||
true -> [{ip, IPStr}]
|
||||
end,
|
||||
IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2];
|
||||
transform_option({{Port, Transport}, Mod, Opts})
|
||||
when ?IS_TRANSPORT(Transport) ->
|
||||
transform_option({{Port, {0,0,0,0}, Transport}, Mod, Opts});
|
||||
transform_option({{Port, IP}, Mod, Opts}) ->
|
||||
transform_option({{Port, IP, tcp}, Mod, Opts});
|
||||
transform_option({Port, Mod, Opts}) ->
|
||||
transform_option({{Port, {0,0,0,0}, tcp}, Mod, Opts});
|
||||
transform_option(Opt) ->
|
||||
Opt.
|
||||
|
||||
transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
|
||||
transform_options({listen, LOpts}, Opts) ->
|
||||
[{listen, lists:map(fun transform_option/1, LOpts)} | Opts];
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-type transport() :: udp | tcp.
|
||||
-type port_ip_transport() :: inet:port_number() |
|
||||
{inet:port_number(), transport()} |
|
||||
@@ -551,18 +603,21 @@ format_error(Reason) ->
|
||||
|
||||
validate_cfg(L) ->
|
||||
lists:map(
|
||||
fun({PortIPTransport, Mod1, Opts}) when is_atom(Mod1), is_list(Opts) ->
|
||||
Mod = prepare_mod(Mod1),
|
||||
case PortIPTransport of
|
||||
Port when ?IS_PORT(Port) ->
|
||||
{Port, Mod, Opts};
|
||||
{Port, Trans} when ?IS_PORT(Port) and ?IS_TRANSPORT(Trans) ->
|
||||
{{Port, Trans}, Mod, Opts};
|
||||
{Port, IP} when ?IS_PORT(Port) ->
|
||||
{{Port, prepare_ip(IP)}, Mod, Opts};
|
||||
{Port, IP, Trans} when ?IS_PORT(Port) and ?IS_TRANSPORT(Trans) ->
|
||||
{{Port, prepare_ip(IP), Trans}, Mod, Opts}
|
||||
end
|
||||
fun(LOpts) ->
|
||||
lists:foldl(
|
||||
fun({port, Port}, {{_, IP, T}, Mod, Opts}) ->
|
||||
true = ?IS_PORT(Port),
|
||||
{{Port, IP, T}, Mod, Opts};
|
||||
({ip, IP}, {{Port, _, T}, Mod, Opts}) ->
|
||||
{{Port, prepare_ip(IP), T}, Mod, Opts};
|
||||
({transport, T}, {{Port, IP, _}, Mod, Opts}) ->
|
||||
true = ?IS_TRANSPORT(T),
|
||||
{{Port, IP, T}, Mod, Opts};
|
||||
({module, Mod}, {Port, _, Opts}) ->
|
||||
{Port, prepare_mod(Mod), Opts};
|
||||
(Opt, {Port, Mod, Opts}) ->
|
||||
{Port, Mod, [Opt|Opts]}
|
||||
end, {{5222, {0,0,0,0}, tcp}, ejabberd_c2s, []}, LOpts)
|
||||
end, L).
|
||||
|
||||
prepare_ip({A, B, C, D} = IP)
|
||||
@@ -583,5 +638,5 @@ prepare_mod(ejabberd_stun) ->
|
||||
prepare_mod(stun) ->
|
||||
ejabberd:start_app(p1_stun),
|
||||
stun;
|
||||
prepare_mod(Mod) ->
|
||||
prepare_mod(Mod) when is_atom(Mod) ->
|
||||
Mod.
|
||||
|
||||
+122
-28
@@ -27,31 +27,118 @@
|
||||
-module(ejabberd_logger).
|
||||
|
||||
%% API
|
||||
-export([start/0, set_logfile/1, reopen_log/0, get/0, set/1,
|
||||
debug_msg/4, info_msg/4, warning_msg/4, error_msg/4,
|
||||
critical_msg/4]).
|
||||
-export([start/0, reopen_log/0, get/0, set/1, get_log_path/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-type loglevel() :: 0 | 1 | 2 | 3 | 4 | 5.
|
||||
|
||||
-spec start() -> ok.
|
||||
-spec get_log_path() -> string().
|
||||
-spec reopen_log() -> ok.
|
||||
-spec get() -> {loglevel(), atom(), string()}.
|
||||
-spec set(loglevel() | {loglevel(), list()}) -> {module, module()}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
%% @doc Returns the full path to the ejabberd log file.
|
||||
%% It first checks for application configuration parameter 'log_path'.
|
||||
%% If not defined it checks the environment variable EJABBERD_LOG_PATH.
|
||||
%% And if that one is neither defined, returns the default value:
|
||||
%% "ejabberd.log" in current directory.
|
||||
get_log_path() ->
|
||||
case application:get_env(ejabberd, log_path) of
|
||||
{ok, Path} ->
|
||||
Path;
|
||||
undefined ->
|
||||
case os:getenv("EJABBERD_LOG_PATH") of
|
||||
false ->
|
||||
?LOG_PATH;
|
||||
Path ->
|
||||
Path
|
||||
end
|
||||
end.
|
||||
|
||||
-ifdef(LAGER).
|
||||
|
||||
start() ->
|
||||
application:load(lager),
|
||||
ConsoleLog = get_log_path(),
|
||||
Dir = filename:dirname(ConsoleLog),
|
||||
ErrorLog = filename:join([Dir, "error.log"]),
|
||||
CrashLog = filename:join([Dir, "crash.log"]),
|
||||
application:set_env(
|
||||
lager, handlers,
|
||||
[{lager_console_backend, info},
|
||||
{lager_file_backend, [{file, ConsoleLog}, {level, info}, {count, 1}]},
|
||||
{lager_file_backend, [{file, ErrorLog}, {level, error}, {count, 1}]}]),
|
||||
application:set_env(lager, crash_log, CrashLog),
|
||||
ejabberd:start_app(lager),
|
||||
ok.
|
||||
|
||||
set_logfile(FileName) ->
|
||||
error_logger:add_report_handler(p1_logger_h, FileName).
|
||||
reopen_log() ->
|
||||
lists:foreach(
|
||||
fun({lager_file_backend, File}) ->
|
||||
whereis(lager_event) ! {rotate, File};
|
||||
(_) ->
|
||||
ok
|
||||
end, gen_event:which_handlers(lager_event)),
|
||||
reopen_sasl_log().
|
||||
|
||||
get() ->
|
||||
case lager:get_loglevel(lager_console_backend) of
|
||||
none -> {0, no_log, "No log"};
|
||||
emergency -> {1, critical, "Critical"};
|
||||
alert -> {1, critical, "Critical"};
|
||||
critical -> {1, critical, "Critical"};
|
||||
error -> {2, error, "Error"};
|
||||
warning -> {3, warning, "Warning"};
|
||||
notice -> {3, warning, "Warning"};
|
||||
info -> {4, info, "Info"};
|
||||
debug -> {5, debug, "Debug"}
|
||||
end.
|
||||
|
||||
set(LogLevel) when is_integer(LogLevel) ->
|
||||
LagerLogLevel = case LogLevel of
|
||||
0 -> none;
|
||||
1 -> critical;
|
||||
2 -> error;
|
||||
3 -> warning;
|
||||
4 -> info;
|
||||
5 -> debug
|
||||
end,
|
||||
case lager:get_loglevel(lager_console_backend) of
|
||||
LagerLogLevel ->
|
||||
ok;
|
||||
_ ->
|
||||
ConsoleLog = get_log_path(),
|
||||
lists:foreach(
|
||||
fun({lager_file_backend, File} = H) when File == ConsoleLog ->
|
||||
lager:set_loglevel(H, LagerLogLevel);
|
||||
(lager_console_backend = H) ->
|
||||
lager:set_loglevel(H, LagerLogLevel);
|
||||
(_) ->
|
||||
ok
|
||||
end, gen_event:which_handlers(lager_event))
|
||||
end,
|
||||
{module, lager};
|
||||
set({_LogLevel, _}) ->
|
||||
error_logger:error_msg("custom loglevels are not supported for 'lager'"),
|
||||
{module, lager}.
|
||||
|
||||
-else.
|
||||
|
||||
start() ->
|
||||
set(4),
|
||||
LogPath = get_log_path(),
|
||||
error_logger:add_report_handler(p1_logger_h, LogPath),
|
||||
ok.
|
||||
|
||||
reopen_log() ->
|
||||
%% TODO: Use the Reopen log API for logger_h ?
|
||||
p1_logger_h:reopen_log(),
|
||||
case application:get_env(sasl,sasl_error_logger) of
|
||||
{ok, {file, SASLfile}} ->
|
||||
error_logger:delete_report_handler(sasl_report_file_h),
|
||||
p1_logger_h:rotate_log(SASLfile),
|
||||
error_logger:add_report_handler(sasl_report_file_h,
|
||||
{SASLfile, get_sasl_error_logger_type()});
|
||||
_ -> false
|
||||
end,
|
||||
ok.
|
||||
reopen_sasl_log().
|
||||
|
||||
get() ->
|
||||
p1_loglevel:get().
|
||||
@@ -59,24 +146,31 @@ get() ->
|
||||
set(LogLevel) ->
|
||||
p1_loglevel:set(LogLevel).
|
||||
|
||||
debug_msg(Mod, Line, Format, Args) ->
|
||||
p1_logger:debug_msg(Mod, Line, Format, Args).
|
||||
|
||||
info_msg(Mod, Line, Format, Args) ->
|
||||
p1_logger:info_msg(Mod, Line, Format, Args).
|
||||
|
||||
warning_msg(Mod, Line, Format, Args) ->
|
||||
p1_logger:warning_msg(Mod, Line, Format, Args).
|
||||
|
||||
error_msg(Mod, Line, Format, Args) ->
|
||||
p1_logger:error_msg(Mod, Line, Format, Args).
|
||||
|
||||
critical_msg(Mod, Line, Format, Args) ->
|
||||
p1_logger:critical_msg(Mod, Line, Format, Args).
|
||||
-endif.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
reopen_sasl_log() ->
|
||||
case application:get_env(sasl,sasl_error_logger) of
|
||||
{ok, {file, SASLfile}} ->
|
||||
error_logger:delete_report_handler(sasl_report_file_h),
|
||||
rotate_sasl_log(SASLfile),
|
||||
error_logger:add_report_handler(sasl_report_file_h,
|
||||
{SASLfile, get_sasl_error_logger_type()});
|
||||
_ -> false
|
||||
end,
|
||||
ok.
|
||||
|
||||
rotate_sasl_log(Filename) ->
|
||||
case file:read_file_info(Filename) of
|
||||
{ok, _FileInfo} ->
|
||||
file:rename(Filename, [Filename, ".0"]),
|
||||
ok;
|
||||
{error, _Reason} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% Function copied from Erlang/OTP lib/sasl/src/sasl.erl which doesn't export it
|
||||
get_sasl_error_logger_type () ->
|
||||
case application:get_env (sasl, errlog_type) of
|
||||
|
||||
@@ -84,7 +84,12 @@ get_closest_node(Name) ->
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
{FE, BE} =
|
||||
case ejabberd_config:get_local_option(node_type, fun(N) -> N end) of
|
||||
case ejabberd_config:get_option(
|
||||
node_type,
|
||||
fun(frontend) -> frontend;
|
||||
(backend) -> backend;
|
||||
(generic) -> generic
|
||||
end, generic) of
|
||||
frontend ->
|
||||
{true, false};
|
||||
backend ->
|
||||
|
||||
+46
-43
@@ -71,12 +71,12 @@
|
||||
|
||||
-define(TOP_LEVEL_TXN, 0).
|
||||
|
||||
-define(MAX_TRANSACTION_RESTARTS, 10).
|
||||
|
||||
-define(PGSQL_PORT, 5432).
|
||||
|
||||
-define(MYSQL_PORT, 3306).
|
||||
|
||||
-define(MAX_TRANSACTION_RESTARTS, 10).
|
||||
|
||||
-define(TRANSACTION_TIMEOUT, 60000).
|
||||
|
||||
-define(KEEPALIVE_TIMEOUT, 60000).
|
||||
@@ -201,8 +201,8 @@ decode_term(Bin) ->
|
||||
%%% Callback functions from gen_fsm
|
||||
%%%----------------------------------------------------------------------
|
||||
init([Host, StartInterval]) ->
|
||||
case ejabberd_config:get_local_option(
|
||||
{odbc_keepalive_interval, Host},
|
||||
case ejabberd_config:get_option(
|
||||
{keepalive_interval, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end) of
|
||||
undefined ->
|
||||
ok;
|
||||
@@ -325,7 +325,7 @@ handle_info(Info, StateName, State) ->
|
||||
terminate(_Reason, _StateName, State) ->
|
||||
ejabberd_odbc_sup:remove_pid(State#state.host, self()),
|
||||
case State#state.db_type of
|
||||
mysql -> catch mysql_conn:stop(State#state.db_ref);
|
||||
mysql -> catch p1_mysql_conn:stop(State#state.db_ref);
|
||||
_ -> ok
|
||||
end,
|
||||
ok.
|
||||
@@ -447,9 +447,9 @@ sql_query_internal(Query) ->
|
||||
mysql ->
|
||||
?DEBUG("MySQL, Send query~n~p~n", [Query]),
|
||||
%%squery to be able to specify result_type = binary
|
||||
%%[Query] because mysql_conn expect query to be a list (elements can be binaries, or iolist)
|
||||
%%[Query] because p1_mysql_conn expect query to be a list (elements can be binaries, or iolist)
|
||||
%% but doesn't accept just a binary
|
||||
R = mysql_to_odbc(mysql_conn:squery(State#state.db_ref,
|
||||
R = mysql_to_odbc(p1_mysql_conn:squery(State#state.db_ref,
|
||||
[Query], self(),
|
||||
[{timeout, (?TRANSACTION_TIMEOUT) - 1000},
|
||||
{result_type, binary}])),
|
||||
@@ -515,6 +515,9 @@ pgsql_to_odbc({ok, PGSQLResult}) ->
|
||||
pgsql_item_to_odbc({<<"SELECT", _/binary>>, Rows,
|
||||
Recs}) ->
|
||||
{selected, [element(1, Row) || Row <- Rows], Recs};
|
||||
pgsql_item_to_odbc({<<"FETCH", _/binary>>, Rows,
|
||||
Recs}) ->
|
||||
{selected, [element(1, Row) || Row <- Rows], Recs};
|
||||
pgsql_item_to_odbc(<<"INSERT ", OIDN/binary>>) ->
|
||||
[_OID, N] = str:tokens(OIDN, <<" ">>),
|
||||
{updated, jlib:binary_to_integer(N)};
|
||||
@@ -530,12 +533,12 @@ pgsql_item_to_odbc(_) -> {updated, undefined}.
|
||||
%% part of init/1
|
||||
%% Open a database connection to MySQL
|
||||
mysql_connect(Server, Port, DB, Username, Password) ->
|
||||
case mysql_conn:start(binary_to_list(Server), Port,
|
||||
case p1_mysql_conn:start(binary_to_list(Server), Port,
|
||||
binary_to_list(Username), binary_to_list(Password),
|
||||
binary_to_list(DB), fun log/3)
|
||||
of
|
||||
{ok, Ref} ->
|
||||
mysql_conn:fetch(Ref, [<<"set names 'utf8';">>],
|
||||
p1_mysql_conn:fetch(Ref, [<<"set names 'utf8';">>],
|
||||
self()),
|
||||
{ok, Ref};
|
||||
Err -> Err
|
||||
@@ -543,15 +546,15 @@ mysql_connect(Server, Port, DB, Username, Password) ->
|
||||
|
||||
%% Convert MySQL query result to Erlang ODBC result formalism
|
||||
mysql_to_odbc({updated, MySQLRes}) ->
|
||||
{updated, mysql:get_result_affected_rows(MySQLRes)};
|
||||
{updated, p1_mysql:get_result_affected_rows(MySQLRes)};
|
||||
mysql_to_odbc({data, MySQLRes}) ->
|
||||
mysql_item_to_odbc(mysql:get_result_field_info(MySQLRes),
|
||||
mysql:get_result_rows(MySQLRes));
|
||||
mysql_item_to_odbc(p1_mysql:get_result_field_info(MySQLRes),
|
||||
p1_mysql:get_result_rows(MySQLRes));
|
||||
mysql_to_odbc({error, MySQLRes})
|
||||
when is_binary(MySQLRes) ->
|
||||
{error, MySQLRes};
|
||||
mysql_to_odbc({error, MySQLRes}) ->
|
||||
{error, mysql:get_result_reason(MySQLRes)}.
|
||||
{error, p1_mysql:get_result_reason(MySQLRes)}.
|
||||
|
||||
%% When tabular data is returned, convert it to the ODBC formalism
|
||||
mysql_item_to_odbc(Columns, Recs) ->
|
||||
@@ -570,39 +573,39 @@ log(Level, Format, Args) ->
|
||||
end.
|
||||
|
||||
db_opts(Host) ->
|
||||
case ejabberd_config:get_local_option(
|
||||
{odbc_server, Host},
|
||||
fun({Type, Server, DB, User, Pass}) ->
|
||||
{Type,
|
||||
iolist_to_binary(Server),
|
||||
case Type of
|
||||
mysql -> ?MYSQL_PORT;
|
||||
pgsql -> ?PGSQL_PORT
|
||||
end,
|
||||
iolist_to_binary(DB),
|
||||
iolist_to_binary(User),
|
||||
iolist_to_binary(Pass)};
|
||||
({Type, Server, Port, DB, User, Pass})
|
||||
when ((Type == mysql) or (Type == pgsql))
|
||||
and (is_integer(Port) and ((Port > 0)
|
||||
and (Port < 65536))) ->
|
||||
{Type,
|
||||
iolist_to_binary(Server),
|
||||
Port,
|
||||
iolist_to_binary(DB),
|
||||
iolist_to_binary(User),
|
||||
iolist_to_binary(Pass)};
|
||||
(S) ->
|
||||
iolist_to_binary(S)
|
||||
end, <<"localhost">>) of
|
||||
{Type, Server, Port, DB, User, Pass} ->
|
||||
[Type, Server, Port, DB, User, Pass];
|
||||
SQLServer ->
|
||||
[odbc, SQLServer]
|
||||
Type = ejabberd_config:get_option({odbc_type, Host},
|
||||
fun(mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(odbc) -> odbc
|
||||
end, odbc),
|
||||
Server = ejabberd_config:get_option({odbc_server, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"localhost">>),
|
||||
case Type of
|
||||
odbc ->
|
||||
[odbc, Server];
|
||||
_ ->
|
||||
Port = ejabberd_config:get_option(
|
||||
{port, Host},
|
||||
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
|
||||
case Type of
|
||||
mysql -> ?MYSQL_PORT;
|
||||
pgsql -> ?PGSQL_PORT
|
||||
end),
|
||||
DB = ejabberd_config:get_option({odbc_database, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>),
|
||||
User = ejabberd_config:get_option({odbc_username, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>),
|
||||
Pass = ejabberd_config:get_option({odbc_password, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"">>),
|
||||
[Type, Server, Port, DB, User, Pass]
|
||||
end.
|
||||
|
||||
max_fsm_queue() ->
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
max_fsm_queue,
|
||||
fun(N) when is_integer(N), N > 0 -> N end).
|
||||
|
||||
|
||||
@@ -30,11 +30,15 @@
|
||||
|
||||
%% API
|
||||
-export([start_link/1, init/1, add_pid/2, remove_pid/2,
|
||||
get_pids/1, get_random_pid/1]).
|
||||
get_pids/1, get_random_pid/1, transform_options/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-define(PGSQL_PORT, 5432).
|
||||
|
||||
-define(MYSQL_PORT, 3306).
|
||||
|
||||
-define(DEFAULT_POOL_SIZE, 10).
|
||||
|
||||
-define(DEFAULT_ODBC_START_INTERVAL, 30).
|
||||
@@ -56,11 +60,11 @@ start_link(Host) ->
|
||||
?MODULE, [Host]).
|
||||
|
||||
init([Host]) ->
|
||||
PoolSize = ejabberd_config:get_local_option(
|
||||
PoolSize = ejabberd_config:get_option(
|
||||
{odbc_pool_size, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
?DEFAULT_POOL_SIZE),
|
||||
StartInterval = ejabberd_config:get_local_option(
|
||||
StartInterval = ejabberd_config:get_option(
|
||||
{odbc_start_interval, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
?DEFAULT_ODBC_START_INTERVAL),
|
||||
@@ -93,3 +97,20 @@ remove_pid(Host, Pid) ->
|
||||
mnesia:delete_object(#sql_pool{host = Host, pid = Pid})
|
||||
end,
|
||||
mnesia:ets(F).
|
||||
|
||||
transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
|
||||
transform_options({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) ->
|
||||
[{odbc_type, Type},
|
||||
{odbc_server, Server},
|
||||
{odbc_port, Port},
|
||||
{odbc_database, DB},
|
||||
{odbc_username, User},
|
||||
{odbc_password, Pass}|Opts];
|
||||
transform_options({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) ->
|
||||
transform_options({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts);
|
||||
transform_options({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) ->
|
||||
transform_options({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts);
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
+17
-7
@@ -34,7 +34,8 @@
|
||||
-include("logger.hrl").
|
||||
|
||||
start() ->
|
||||
case lists:any(fun needs_odbc/1, ?MYHOSTS) of
|
||||
case lists:any(fun(H) -> needs_odbc(H) /= false end,
|
||||
?MYHOSTS) of
|
||||
true ->
|
||||
start_hosts();
|
||||
false ->
|
||||
@@ -45,14 +46,15 @@ start() ->
|
||||
start_hosts() ->
|
||||
lists:foreach(fun (Host) ->
|
||||
case needs_odbc(Host) of
|
||||
true -> start_odbc(Host);
|
||||
{true, App} -> start_odbc(Host, App);
|
||||
false -> ok
|
||||
end
|
||||
end,
|
||||
?MYHOSTS).
|
||||
|
||||
%% Start the ODBC module on the given host
|
||||
start_odbc(Host) ->
|
||||
start_odbc(Host, App) ->
|
||||
ejabberd:start_app(App),
|
||||
Supervisor_name = gen_mod:get_module_proc(Host,
|
||||
ejabberd_odbc_sup),
|
||||
ChildSpec = {Supervisor_name,
|
||||
@@ -64,11 +66,19 @@ start_odbc(Host) ->
|
||||
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying."
|
||||
"..~n",
|
||||
[Supervisor_name, _Error]),
|
||||
start_odbc(Host)
|
||||
start_odbc(Host, App)
|
||||
end.
|
||||
|
||||
%% Returns true if we have configured odbc_server for the given host
|
||||
%% Returns {true, App} if we have configured odbc for the given host
|
||||
needs_odbc(Host) ->
|
||||
LHost = jlib:nameprep(Host),
|
||||
ejabberd_config:get_local_option(
|
||||
{odbc_server, LHost}, fun(_) -> true end, false).
|
||||
case ejabberd_config:get_option({odbc_type, LHost},
|
||||
fun(mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(odbc) -> odbc
|
||||
end, undefined) of
|
||||
mysql -> {true, p1_mysql};
|
||||
pgsql -> {true, p1_pgsql};
|
||||
odbc -> {true, odbc};
|
||||
undefined -> false
|
||||
end.
|
||||
|
||||
@@ -387,14 +387,10 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
|
||||
end.
|
||||
|
||||
get_component_number(LDomain) ->
|
||||
case
|
||||
ejabberd_config:get_local_option({domain_balancing_component_number,
|
||||
LDomain}, fun(D) -> D end)
|
||||
of
|
||||
N when is_integer(N), N > 1 -> N;
|
||||
_ -> undefined
|
||||
end.
|
||||
|
||||
ejabberd_config:get_option(
|
||||
{domain_balancing_component_number, LDomain},
|
||||
fun(N) when is_integer(N), N > 1 -> N end,
|
||||
undefined).
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(route, attributes) of
|
||||
|
||||
+42
-26
@@ -45,7 +45,7 @@
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
%% ejabberd API
|
||||
-export([get_info_s2s_connections/1]).
|
||||
-export([get_info_s2s_connections/1, transform_options/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -461,12 +461,12 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
|
||||
%% --------------------------------------------------------------------
|
||||
is_service(From, To) ->
|
||||
LFromDomain = From#jid.lserver,
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
{route_subdomains, LFromDomain},
|
||||
fun(s2s) -> s2s end) of
|
||||
fun(s2s) -> s2s; (local) -> local end, local) of
|
||||
s2s -> % bypass RFC 3920 10.3
|
||||
false;
|
||||
undefined ->
|
||||
local ->
|
||||
Hosts = (?MYHOSTS),
|
||||
P = fun (ParentDomain) ->
|
||||
lists:member(ParentDomain, Hosts)
|
||||
@@ -548,34 +548,50 @@ allow_host2(MyServer, S2SHost) ->
|
||||
end.
|
||||
|
||||
allow_host1(MyHost, S2SHost) ->
|
||||
case ejabberd_config:get_local_option(
|
||||
{{s2s_host, S2SHost}, MyHost},
|
||||
fun(deny) -> deny; (allow) -> allow end)
|
||||
of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
undefined ->
|
||||
case ejabberd_config:get_local_option(
|
||||
{s2s_default_policy, MyHost},
|
||||
fun(deny) -> deny; (allow) -> allow end)
|
||||
of
|
||||
deny -> false;
|
||||
_ ->
|
||||
case ejabberd_hooks:run_fold(s2s_allow_host, MyHost,
|
||||
allow, [MyHost, S2SHost])
|
||||
of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ -> true
|
||||
end
|
||||
end
|
||||
Rule = ejabberd_config:get_option(
|
||||
s2s_access,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
all),
|
||||
JID = jlib:make_jid(<<"">>, S2SHost, <<"">>),
|
||||
case acl:match_rule(MyHost, Rule, JID) of
|
||||
deny -> false;
|
||||
allow ->
|
||||
case ejabberd_hooks:run_fold(s2s_allow_host, MyHost,
|
||||
allow, [MyHost, S2SHost]) of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ -> true
|
||||
end
|
||||
end.
|
||||
|
||||
transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
|
||||
transform_options({{s2s_host, Host}, Action}, Opts) ->
|
||||
?WARNING_MSG("Option 's2s_host' is deprecated. "
|
||||
"The option is still supported but it is better to "
|
||||
"fix your config: use access rules instead.", []),
|
||||
ACLName = jlib:binary_to_atom(
|
||||
iolist_to_binary(["s2s_access_", Host])),
|
||||
[{acl, ACLName, {server, Host}},
|
||||
{access, s2s, [{Action, ACLName}]},
|
||||
{s2s_access, s2s} |
|
||||
Opts];
|
||||
transform_options({s2s_default_policy, Action}, Opts) ->
|
||||
?WARNING_MSG("Option 's2s_default_policy' is deprecated. "
|
||||
"The option is still supported but it is better to "
|
||||
"fix your config: "
|
||||
"use 's2s_access' with an access rule.", []),
|
||||
[{access, s2s, [{Action, all}]},
|
||||
{s2s_access, s2s} |
|
||||
Opts];
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
%% Get information about S2S connections of the specified type.
|
||||
%% @spec (Type) -> [Info]
|
||||
%% where Type = in | out
|
||||
%% Info = [{InfoName::atom(), InfoValue::any()}]
|
||||
|
||||
get_info_s2s_connections(Type) ->
|
||||
ChildType = case Type of
|
||||
in -> ejabberd_s2s_in_sup;
|
||||
|
||||
+20
-6
@@ -149,7 +149,7 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
_ -> none
|
||||
end,
|
||||
{StartTLS, TLSRequired, TLSCertverify} =
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
s2s_use_starttls,
|
||||
fun(false) -> false;
|
||||
(true) -> true;
|
||||
@@ -171,12 +171,16 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
required_trusted ->
|
||||
{true, true, true}
|
||||
end,
|
||||
TLSOpts = case ejabberd_config:get_local_option(
|
||||
TLSOpts1 = case ejabberd_config:get_option(
|
||||
s2s_certfile,
|
||||
fun iolist_to_binary/1) of
|
||||
undefined -> [];
|
||||
CertFile -> [{certfile, CertFile}]
|
||||
end,
|
||||
TLSOpts = case proplists:get_bool(tls_compression, Opts) of
|
||||
false -> [compression_none | TLSOpts1];
|
||||
true -> TLSOpts1
|
||||
end,
|
||||
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
|
||||
{ok, wait_for_stream,
|
||||
#state{socket = Socket, sockmod = SockMod,
|
||||
@@ -289,7 +293,9 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
|
||||
[Server])}),
|
||||
{next_state, stream_established, StateData};
|
||||
{<<"jabber:server">>, <<"jabber:server:dialback">>,
|
||||
_Server, _} ->
|
||||
_Server, _} when
|
||||
(StateData#state.tls_required and StateData#state.tls_enabled)
|
||||
or (not StateData#state.tls_required) ->
|
||||
send_text(StateData, ?STREAM_HEADER(<<"">>)),
|
||||
{next_state, stream_established, StateData};
|
||||
_ ->
|
||||
@@ -319,8 +325,8 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
SockMod == gen_tcp ->
|
||||
?DEBUG("starttls", []),
|
||||
Socket = StateData#state.socket,
|
||||
TLSOpts = case
|
||||
ejabberd_config:get_local_option(
|
||||
TLSOpts1 = case
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, StateData#state.server},
|
||||
fun iolist_to_binary/1) of
|
||||
undefined -> StateData#state.tls_options;
|
||||
@@ -328,6 +334,14 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
[{certfile, CertFile} | lists:keydelete(certfile, 1,
|
||||
StateData#state.tls_options)]
|
||||
end,
|
||||
TLSOpts = case ejabberd_config:get_option(
|
||||
{s2s_tls_compression, StateData#state.server},
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, true) of
|
||||
true -> lists:delete(compression_none, TLSOpts1);
|
||||
false -> [compression_none | TLSOpts1]
|
||||
end,
|
||||
TLSSocket = (StateData#state.sockmod):starttls(Socket,
|
||||
TLSOpts,
|
||||
xml:element_to_binary(#xmlel{name
|
||||
@@ -831,7 +845,7 @@ fsm_limit_opts(Opts) ->
|
||||
case lists:keysearch(max_fsm_queue, 1, Opts) of
|
||||
{value, {_, N}} when is_integer(N) -> [{max_queue, N}];
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
max_fsm_queue,
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> [];
|
||||
|
||||
+62
-25
@@ -35,7 +35,8 @@
|
||||
start_link/3,
|
||||
start_connection/1,
|
||||
terminate_if_waiting_delay/2,
|
||||
stop_connection/1]).
|
||||
stop_connection/1,
|
||||
transform_options/1]).
|
||||
|
||||
%% p1_fsm callbacks (same as gen_fsm)
|
||||
-export([init/1,
|
||||
@@ -161,7 +162,7 @@ init([From, Server, Type]) ->
|
||||
process_flag(trap_exit, true),
|
||||
?DEBUG("started: ~p", [{From, Server, Type}]),
|
||||
{TLS, TLSRequired} = case
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
s2s_use_starttls,
|
||||
fun(true) -> true;
|
||||
(false) -> false;
|
||||
@@ -183,13 +184,21 @@ init([From, Server, Type]) ->
|
||||
{true, true}
|
||||
end,
|
||||
UseV10 = TLS,
|
||||
TLSOpts = case
|
||||
ejabberd_config:get_local_option(
|
||||
TLSOpts1 = case
|
||||
ejabberd_config:get_option(
|
||||
s2s_certfile, fun iolist_to_binary/1)
|
||||
of
|
||||
undefined -> [connect];
|
||||
CertFile -> [{certfile, CertFile}, connect]
|
||||
end,
|
||||
TLSOpts = case ejabberd_config:get_option(
|
||||
{s2s_tls_compression, From},
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, true) of
|
||||
false -> [compression_none | TLSOpts1];
|
||||
true -> TLSOpts1
|
||||
end,
|
||||
{New, Verify} = case Type of
|
||||
{new, Key} -> {Key, false};
|
||||
{verify, Pid, Key, SID} ->
|
||||
@@ -694,7 +703,7 @@ wait_for_starttls_proceed({xmlstreamelement, El},
|
||||
[{StateData#state.myname, StateData#state.server}]),
|
||||
Socket = StateData#state.socket,
|
||||
TLSOpts = case
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, StateData#state.myname},
|
||||
fun iolist_to_binary/1)
|
||||
of
|
||||
@@ -1134,16 +1143,15 @@ get_addr_port(Server) ->
|
||||
end.
|
||||
|
||||
srv_lookup(Server) ->
|
||||
Options = case
|
||||
ejabberd_config:get_local_option(
|
||||
s2s_dns_options, fun(L) when is_list(L) -> L end)
|
||||
of
|
||||
undefined -> [];
|
||||
L -> L
|
||||
end,
|
||||
TimeoutMs = timer:seconds(proplists:get_value(timeout,
|
||||
Options, 10)),
|
||||
Retries = proplists:get_value(retries, Options, 2),
|
||||
TimeoutMs = timer:seconds(
|
||||
ejabberd_config:get_option(
|
||||
s2s_dns_timeout,
|
||||
fun(I) when is_integer(I), I>=0 -> I end,
|
||||
10)),
|
||||
Retries = ejabberd_config:get_option(
|
||||
s2s_dns_retries,
|
||||
fun(I) when is_integer(I), I>=0 -> I end,
|
||||
2),
|
||||
srv_lookup(binary_to_list(Server), TimeoutMs, Retries).
|
||||
|
||||
%% XXX - this behaviour is suboptimal in the case that the domain
|
||||
@@ -1203,15 +1211,15 @@ get_addrs(Host, Family) ->
|
||||
end.
|
||||
|
||||
outgoing_s2s_port() ->
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
outgoing_s2s_port,
|
||||
fun(I) when is_integer(I), I > 0, I =< 65536 -> I end,
|
||||
5269).
|
||||
|
||||
outgoing_s2s_families() ->
|
||||
ejabberd_config:get_local_option(
|
||||
outgoing_s2s_options,
|
||||
fun({Families, _}) ->
|
||||
ejabberd_config:get_option(
|
||||
outgoing_s2s_families,
|
||||
fun(Families) ->
|
||||
true = lists:all(
|
||||
fun(ipv4) -> true;
|
||||
(ipv6) -> true
|
||||
@@ -1220,14 +1228,43 @@ outgoing_s2s_families() ->
|
||||
end, [ipv4, ipv6]).
|
||||
|
||||
outgoing_s2s_timeout() ->
|
||||
ejabberd_config:get_local_option(
|
||||
outgoing_s2s_options,
|
||||
fun({_, TimeOut}) when is_integer(TimeOut), TimeOut > 0 ->
|
||||
ejabberd_config:get_option(
|
||||
outgoing_s2s_timeout,
|
||||
fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
|
||||
TimeOut;
|
||||
({_, infinity}) ->
|
||||
(infinity) ->
|
||||
infinity
|
||||
end, 10000).
|
||||
|
||||
transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
|
||||
transform_options({outgoing_s2s_options, Families, Timeout}, Opts) ->
|
||||
?WARNING_MSG("Option 'outgoing_s2s_options' is deprecated. "
|
||||
"The option is still supported "
|
||||
"but it is better to fix your config: "
|
||||
"use 'outgoing_s2s_timeout' and "
|
||||
"'outgoing_s2s_families' instead.", []),
|
||||
[{outgoing_s2s_families, Families},
|
||||
{outgoing_s2s_timeout, Timeout}
|
||||
| Opts];
|
||||
transform_options({s2s_dns_options, S2SDNSOpts}, AllOpts) ->
|
||||
?WARNING_MSG("Option 's2s_dns_options' is deprecated. "
|
||||
"The option is still supported "
|
||||
"but it is better to fix your config: "
|
||||
"use 's2s_dns_timeout' and "
|
||||
"'s2s_dns_retries' instead", []),
|
||||
lists:foldr(
|
||||
fun({timeout, T}, AccOpts) ->
|
||||
[{s2s_dns_timeout, T}|AccOpts];
|
||||
({retries, R}, AccOpts) ->
|
||||
[{s2s_dns_retries, R}|AccOpts];
|
||||
(_, AccOpts) ->
|
||||
AccOpts
|
||||
end, AllOpts, S2SDNSOpts);
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
%% Human readable S2S logging: Log only new outgoing connections as INFO
|
||||
%% Do not log dialback
|
||||
log_s2s_out(false, _, _, _) -> ok;
|
||||
@@ -1270,7 +1307,7 @@ wait_before_reconnect(StateData) ->
|
||||
queue = queue:new()}}.
|
||||
|
||||
get_max_retry_delay() ->
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
s2s_max_retry_delay,
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> ?MAX_RETRY_DELAY;
|
||||
@@ -1287,7 +1324,7 @@ terminate_if_waiting_delay(From, To) ->
|
||||
Pids).
|
||||
|
||||
fsm_limit_opts() ->
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
max_fsm_queue,
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> [];
|
||||
|
||||
+32
-25
@@ -34,7 +34,7 @@
|
||||
|
||||
%% External exports
|
||||
-export([start/2, start_link/2, send_text/2,
|
||||
send_element/2, socket_type/0]).
|
||||
send_element/2, socket_type/0, transform_listen_option/2]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1, wait_for_stream/2,
|
||||
@@ -124,29 +124,18 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
{value, {_, A}} -> A;
|
||||
_ -> all
|
||||
end,
|
||||
{Hosts, Password} = case lists:keysearch(hosts, 1, Opts)
|
||||
of
|
||||
{value, {_, Hs, HOpts}} ->
|
||||
case lists:keysearch(password, 1, HOpts) of
|
||||
{value, {_, P}} -> {Hs, P};
|
||||
_ ->
|
||||
% TODO: generate error
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
case lists:keysearch(host, 1, Opts) of
|
||||
{value, {_, H, HOpts}} ->
|
||||
case lists:keysearch(password, 1, HOpts) of
|
||||
{value, {_, P}} -> {[H], P};
|
||||
_ ->
|
||||
% TODO: generate error
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
% TODO: generate error
|
||||
false
|
||||
end
|
||||
end,
|
||||
%% This should be improved probably
|
||||
{Hosts, HostOpts} = case lists:keyfind(hosts, 1, Opts) of
|
||||
{_, HOpts} ->
|
||||
{[H || {H, _} <- HOpts],
|
||||
lists:flatten(
|
||||
[O || {_, O} <- HOpts])};
|
||||
_ ->
|
||||
{[], []}
|
||||
end,
|
||||
Password = gen_mod:get_opt(password, HostOpts,
|
||||
fun iolist_to_binary/1,
|
||||
p1_sha:sha(crypto:rand_bytes(20))),
|
||||
Shaper = case lists:keysearch(shaper_rule, 1, Opts) of
|
||||
{value, {_, S}} -> S;
|
||||
_ -> none
|
||||
@@ -384,12 +373,30 @@ send_element(StateData, El) ->
|
||||
|
||||
new_id() -> randoms:get_string().
|
||||
|
||||
transform_listen_option({hosts, Hosts, O}, Opts) ->
|
||||
case lists:keyfind(hosts, 1, Opts) of
|
||||
{_, PrevHostOpts} ->
|
||||
NewHostOpts =
|
||||
lists:foldl(
|
||||
fun(H, Acc) ->
|
||||
dict:append_list(H, O, Acc)
|
||||
end, dict:from_list(PrevHostOpts), Hosts),
|
||||
[{hosts, dict:to_list(NewHostOpts)}|
|
||||
lists:keydelete(hosts, 1, Opts)];
|
||||
_ ->
|
||||
[{hosts, [{H, O} || H <- Hosts]}|Opts]
|
||||
end;
|
||||
transform_listen_option({host, Host, Os}, Opts) ->
|
||||
transform_listen_option({hosts, [Host], Os}, Opts);
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
fsm_limit_opts(Opts) ->
|
||||
case lists:keysearch(max_fsm_queue, 1, Opts) of
|
||||
{value, {_, N}} when is_integer(N) ->
|
||||
[{max_queue, N}];
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
max_fsm_queue,
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
undefined -> [];
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
LH = ejabberd_config:get_local_option(
|
||||
LH = ejabberd_config:get_option(
|
||||
watchdog_large_heap,
|
||||
fun(I) when is_integer(I), I > 0 -> I end,
|
||||
1000000),
|
||||
@@ -200,7 +200,7 @@ send_message(From, To, Body) ->
|
||||
[{xmlcdata, Body}]}]}).
|
||||
|
||||
get_admin_jids() ->
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
watchdog_admins,
|
||||
fun(JIDs) ->
|
||||
[jlib:jid_tolower(
|
||||
|
||||
+13
-13
@@ -724,8 +724,8 @@ process_admin(Host,
|
||||
auth = {_, _Auth, AJID}, q = Query, lang = Lang}) ->
|
||||
SetAccess = fun (Rs) ->
|
||||
mnesia:transaction(fun () ->
|
||||
Os = mnesia:select(config,
|
||||
[{{config,
|
||||
Os = mnesia:select(local_config,
|
||||
[{{local_config,
|
||||
{access,
|
||||
'$1',
|
||||
Host},
|
||||
@@ -739,7 +739,7 @@ process_admin(Host,
|
||||
lists:foreach(fun ({access,
|
||||
Name,
|
||||
Rules}) ->
|
||||
mnesia:write({config,
|
||||
mnesia:write({local_config,
|
||||
{access,
|
||||
Name,
|
||||
Host},
|
||||
@@ -764,8 +764,8 @@ process_admin(Host,
|
||||
end;
|
||||
_ -> nothing
|
||||
end,
|
||||
Access = ets:select(config,
|
||||
[{{config, {access, '$1', Host}, '$2'}, [],
|
||||
Access = ets:select(local_config,
|
||||
[{{local_config, {access, '$1', Host}, '$2'}, [],
|
||||
[{{access, '$1', '$2'}}]}]),
|
||||
{NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80),
|
||||
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
|
||||
@@ -798,8 +798,8 @@ process_admin(Host,
|
||||
end;
|
||||
_ -> nothing
|
||||
end,
|
||||
AccessRules = ets:select(config,
|
||||
[{{config, {access, '$1', Host}, '$2'}, [],
|
||||
AccessRules = ets:select(local_config,
|
||||
[{{local_config, {access, '$1', Host}, '$2'}, [],
|
||||
[{{access, '$1', '$2'}}]}]),
|
||||
make_xhtml((?H1GL((?T(<<"Access Rules">>)),
|
||||
<<"AccessRights">>, <<"Access Rights">>))
|
||||
@@ -827,14 +827,14 @@ process_admin(Host,
|
||||
{value, {_, String}} ->
|
||||
case parse_access_rule(String) of
|
||||
{ok, Rs} ->
|
||||
ejabberd_config:add_global_option({access, Name, Host},
|
||||
ejabberd_config:add_option({access, Name, Host},
|
||||
Rs),
|
||||
ok;
|
||||
_ -> error
|
||||
end;
|
||||
_ -> nothing
|
||||
end,
|
||||
Rules = case ejabberd_config:get_global_option(
|
||||
Rules = case ejabberd_config:get_option(
|
||||
{access, Name, Host}, fun(V) -> V end)
|
||||
of
|
||||
undefined -> [];
|
||||
@@ -1181,8 +1181,8 @@ access_rules_to_xhtml(AccessRules, Lang) ->
|
||||
<<"Add New">>)])])]))]).
|
||||
|
||||
access_parse_query(Host, Query) ->
|
||||
AccessRules = ets:select(config,
|
||||
[{{config, {access, '$1', Host}, '$2'}, [],
|
||||
AccessRules = ets:select(local_config,
|
||||
[{{local_config, {access, '$1', Host}, '$2'}, [],
|
||||
[{{access, '$1', '$2'}}]}]),
|
||||
case lists:keysearch(<<"addnew">>, 1, Query) of
|
||||
{value, _} ->
|
||||
@@ -1198,7 +1198,7 @@ access_parse_addnew(_AccessRules, Host, Query) ->
|
||||
case lists:keysearch(<<"namenew">>, 1, Query) of
|
||||
{value, {_, String}} when String /= <<"">> ->
|
||||
Name = jlib:binary_to_atom(String),
|
||||
ejabberd_config:add_global_option({access, Name, Host},
|
||||
ejabberd_config:add_option({access, Name, Host},
|
||||
[]),
|
||||
ok
|
||||
end.
|
||||
@@ -1210,7 +1210,7 @@ access_parse_delete(AccessRules, Host, Query) ->
|
||||
case lists:member({<<"selected">>, ID}, Query) of
|
||||
true ->
|
||||
mnesia:transaction(fun () ->
|
||||
mnesia:delete({config,
|
||||
mnesia:delete({local_config,
|
||||
{access,
|
||||
Name,
|
||||
Host}})
|
||||
|
||||
+160
-17
@@ -28,10 +28,14 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([export/2, export/3]).
|
||||
-include("logger.hrl").
|
||||
|
||||
-export([export/2, export/3, import_file/2, import/2, import/3]).
|
||||
|
||||
-define(MAX_RECORDS_PER_TRANSACTION, 100).
|
||||
|
||||
-record(dump, {fd, cont = start}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -42,21 +46,24 @@
|
||||
%%% - Output can be either odbc to export to the configured relational
|
||||
%%% database or "Filename" to export to text file.
|
||||
|
||||
modules() ->
|
||||
[ejabberd_auth,
|
||||
mod_announce,
|
||||
mod_caps,
|
||||
mod_irc,
|
||||
mod_last,
|
||||
mod_muc,
|
||||
mod_offline,
|
||||
mod_privacy,
|
||||
mod_private,
|
||||
mod_roster,
|
||||
mod_shared_roster,
|
||||
mod_vcard,
|
||||
mod_vcard_xupdate].
|
||||
|
||||
export(Server, Output) ->
|
||||
LServer = jlib:nameprep(iolist_to_binary(Server)),
|
||||
Modules = [ejabberd_auth,
|
||||
mod_announce,
|
||||
mod_caps,
|
||||
mod_irc,
|
||||
mod_last,
|
||||
mod_muc,
|
||||
mod_offline,
|
||||
mod_privacy,
|
||||
mod_private,
|
||||
mod_roster,
|
||||
mod_shared_roster,
|
||||
mod_vcard,
|
||||
mod_vcard_xupdate],
|
||||
Modules = modules(),
|
||||
IO = prepare_output(Output),
|
||||
lists:foreach(
|
||||
fun(Module) ->
|
||||
@@ -73,6 +80,50 @@ export(Server, Output, Module) ->
|
||||
end, Module:export(Server)),
|
||||
close_output(Output, IO).
|
||||
|
||||
import_file(Server, FileName) when is_binary(FileName) ->
|
||||
import(Server, binary_to_list(FileName));
|
||||
import_file(Server, FileName) ->
|
||||
case disk_log:open([{name, make_ref()},
|
||||
{file, FileName},
|
||||
{mode, read_only}]) of
|
||||
{ok, Fd} ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Mods = [{Mod, gen_mod:db_type(LServer, Mod)}
|
||||
|| Mod <- modules(), gen_mod:is_loaded(LServer, Mod)],
|
||||
AuthMods = case lists:member(ejabberd_auth_internal,
|
||||
ejabberd_auth:auth_modules(LServer)) of
|
||||
true ->
|
||||
[{ejabberd_auth, mnesia}];
|
||||
false ->
|
||||
[]
|
||||
end,
|
||||
import_dump(LServer, AuthMods ++ Mods, #dump{fd = Fd});
|
||||
Err ->
|
||||
exit(Err)
|
||||
end.
|
||||
|
||||
import(Server, Output) ->
|
||||
import(Server, Output, [{fast, true}]).
|
||||
|
||||
import(Server, Output, Opts) ->
|
||||
LServer = jlib:nameprep(iolist_to_binary(Server)),
|
||||
Modules = modules(),
|
||||
IO = prepare_output(Output, disk_log),
|
||||
lists:foreach(
|
||||
fun(Module) ->
|
||||
import(LServer, IO, Opts, Module)
|
||||
end, Modules),
|
||||
close_output(Output, IO).
|
||||
|
||||
import(Server, Output, Opts, Module) ->
|
||||
LServer = jlib:nameprep(iolist_to_binary(Server)),
|
||||
IO = prepare_output(Output, disk_log),
|
||||
lists:foreach(
|
||||
fun({SelectQuery, ConvertFun}) ->
|
||||
import(LServer, SelectQuery, IO, ConvertFun, Opts)
|
||||
end, Module:import(Server)),
|
||||
close_output(Output, IO).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -109,18 +160,110 @@ output(_LServer, Table, Fd, SQLs) ->
|
||||
file:write(Fd, ["-- \n-- Mnesia table: ", atom_to_list(Table),
|
||||
"\n--\n", SQLs]).
|
||||
|
||||
prepare_output(FileName) when is_list(FileName); is_binary(FileName) ->
|
||||
import(LServer, SelectQuery, IO, ConvertFun, Opts) ->
|
||||
F = case proplists:get_bool(fast, Opts) of
|
||||
true ->
|
||||
fun() ->
|
||||
case ejabberd_odbc:sql_query_t(SelectQuery) of
|
||||
{selected, _, Rows} ->
|
||||
lists:foldl(fun process_sql_row/2,
|
||||
{IO, ConvertFun, undefined}, Rows);
|
||||
Err ->
|
||||
erlang:error(Err)
|
||||
end
|
||||
end;
|
||||
false ->
|
||||
fun() ->
|
||||
ejabberd_odbc:sql_query_t(
|
||||
[iolist_to_binary(
|
||||
[<<"declare c cursor for ">>, SelectQuery])]),
|
||||
fetch(IO, ConvertFun, undefined)
|
||||
end
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(LServer, F).
|
||||
|
||||
fetch(IO, ConvertFun, PrevRow) ->
|
||||
case ejabberd_odbc:sql_query_t([<<"fetch c;">>]) of
|
||||
{selected, _, [Row]} ->
|
||||
process_sql_row(Row, {IO, ConvertFun, PrevRow}),
|
||||
fetch(IO, ConvertFun, Row);
|
||||
{selected, _, []} ->
|
||||
ok;
|
||||
Err ->
|
||||
erlang:error(Err)
|
||||
end.
|
||||
|
||||
process_sql_row(Row, {IO, ConvertFun, PrevRow}) when Row == PrevRow ->
|
||||
%% Avoid calling ConvertFun with the same input
|
||||
{IO, ConvertFun, Row};
|
||||
process_sql_row(Row, {IO, ConvertFun, _PrevRow}) ->
|
||||
case catch ConvertFun(Row) of
|
||||
{'EXIT', _} = Err ->
|
||||
?ERROR_MSG("failed to convert ~p: ~p", [Row, Err]);
|
||||
Term ->
|
||||
ok = disk_log:log(IO#dump.fd, Term)
|
||||
end,
|
||||
{IO, ConvertFun, Row}.
|
||||
|
||||
import_dump(LServer, Mods, #dump{fd = Fd, cont = Cont}) ->
|
||||
case disk_log:chunk(Fd, Cont) of
|
||||
{NewCont, Terms} ->
|
||||
import_terms(LServer, Mods, Terms),
|
||||
import_dump(LServer, Mods, #dump{fd = Fd, cont = NewCont});
|
||||
eof ->
|
||||
ok;
|
||||
Err ->
|
||||
exit(Err)
|
||||
end.
|
||||
|
||||
import_terms(LServer, Mods, [Term|Terms]) ->
|
||||
import_term(LServer, Mods, Term),
|
||||
import_terms(LServer, Mods, Terms);
|
||||
import_terms(_LServer, _Mods, []) ->
|
||||
ok.
|
||||
|
||||
import_term(LServer, [{Mod, DBType}|Mods], Term) ->
|
||||
case catch Mod:import(LServer, DBType, Term) of
|
||||
pass -> import_term(LServer, Mods, Term);
|
||||
ok -> ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to import ~p for module ~p: ~p",
|
||||
[Term, Mod, Err])
|
||||
end;
|
||||
import_term(_LServer, [], _Term) ->
|
||||
ok.
|
||||
|
||||
prepare_output(FileName) ->
|
||||
prepare_output(FileName, normal).
|
||||
|
||||
prepare_output(FileName, Type) when is_binary(FileName) ->
|
||||
prepare_output(binary_to_list(FileName), Type);
|
||||
prepare_output(FileName, normal) when is_list(FileName) ->
|
||||
case file:open(FileName, [write, raw]) of
|
||||
{ok, Fd} ->
|
||||
Fd;
|
||||
Err ->
|
||||
exit(Err)
|
||||
end;
|
||||
prepare_output(Output) ->
|
||||
prepare_output(FileName, disk_log) when is_list(FileName) ->
|
||||
case disk_log:open([{name, make_ref()},
|
||||
{repair, truncate},
|
||||
{file, FileName}]) of
|
||||
{ok, Fd} ->
|
||||
#dump{fd = Fd};
|
||||
Err ->
|
||||
exit(Err)
|
||||
end;
|
||||
prepare_output(Output, _Type) ->
|
||||
Output.
|
||||
|
||||
close_output(FileName, Fd) when FileName /= Fd ->
|
||||
file:close(Fd),
|
||||
case Fd of
|
||||
#dump{} ->
|
||||
disk_log:close(Fd#dump.fd);
|
||||
_ ->
|
||||
file:close(Fd)
|
||||
end,
|
||||
ok;
|
||||
close_output(_, _) ->
|
||||
ok.
|
||||
|
||||
+1
-1
@@ -182,7 +182,7 @@ get_opt({Key, Host}, Opts, F) ->
|
||||
get_opt({Key, Host}, Opts, F, Default) ->
|
||||
case gen_mod:get_opt(Key, Opts, F, undefined) of
|
||||
undefined ->
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
{Key, Host}, F, Default);
|
||||
Val ->
|
||||
Val
|
||||
|
||||
+8
-14
@@ -70,28 +70,28 @@ get_process_name(Host, Integer) ->
|
||||
eauth).
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
call_port(Server, ["auth", User, Server, Password]).
|
||||
call_port(Server, [<<"auth">>, User, Server, Password]).
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
call_port(Server, ["isuser", User, Server]).
|
||||
call_port(Server, [<<"isuser">>, User, Server]).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
call_port(Server, ["setpass", User, Server, Password]).
|
||||
call_port(Server, [<<"setpass">>, User, Server, Password]).
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
case call_port(Server,
|
||||
["tryregister", User, Server, Password])
|
||||
[<<"tryregister">>, User, Server, Password])
|
||||
of
|
||||
true -> {atomic, ok};
|
||||
false -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
call_port(Server, ["removeuser", User, Server]).
|
||||
call_port(Server, [<<"removeuser">>, User, Server]).
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
call_port(Server,
|
||||
["removeuser3", User, Server, Password]).
|
||||
[<<"removeuser3">>, User, Server, Password]).
|
||||
|
||||
call_port(Server, Msg) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
@@ -106,7 +106,7 @@ random_instance(MaxNum) ->
|
||||
random:uniform(MaxNum) - 1.
|
||||
|
||||
get_instances(Server) ->
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
{extauth_instances, Server},
|
||||
fun(V) when is_integer(V), V > 0 ->
|
||||
V
|
||||
@@ -154,13 +154,7 @@ flush_buffer_and_forward_messages(Pid) ->
|
||||
after 0 -> true
|
||||
end.
|
||||
|
||||
join(List, Sep) ->
|
||||
lists:foldl(fun (A, "") -> A;
|
||||
(A, Acc) -> Acc ++ Sep ++ A
|
||||
end,
|
||||
"", List).
|
||||
|
||||
encode(L) -> join(L, ":").
|
||||
encode(L) -> str:join(L, <<":">>).
|
||||
|
||||
decode([0, 0]) -> false;
|
||||
decode([0, 1]) -> true.
|
||||
|
||||
+23
-2
@@ -33,7 +33,7 @@
|
||||
%% API
|
||||
-export([start_link/3, add_iq_handler/6,
|
||||
remove_iq_handler/3, stop_iq_handler/3, handle/7,
|
||||
process_iq/6]).
|
||||
process_iq/6, check_type/1, transform_module_options/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
@@ -45,6 +45,10 @@
|
||||
|
||||
-record(state, {host, module, function}).
|
||||
|
||||
-type component() :: ejabberd_sm | ejabberd_local.
|
||||
-type type() :: no_queue | one_queue | pos_integer() | parallel.
|
||||
-type opts() :: no_queue | {one_queue, pid()} | {queues, [pid()]} | parallel.
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@@ -67,7 +71,7 @@ add_iq_handler(Component, Host, NS, Module, Function,
|
||||
[Host, Module, Function]),
|
||||
Component:register_iq_handler(Host, NS, Module,
|
||||
Function, {one_queue, Pid});
|
||||
{queues, N} ->
|
||||
N when is_integer(N) ->
|
||||
Pids = lists:map(fun (_) ->
|
||||
{ok, Pid} =
|
||||
supervisor:start_child(ejabberd_iq_sup,
|
||||
@@ -122,6 +126,23 @@ process_iq(_Host, Module, Function, From, To, IQ) ->
|
||||
end
|
||||
end.
|
||||
|
||||
-spec check_type(type()) -> type().
|
||||
|
||||
check_type(no_queue) -> no_queue;
|
||||
check_type(one_queue) -> one_queue;
|
||||
check_type(N) when is_integer(N), N>0 -> N;
|
||||
check_type(parallel) -> parallel.
|
||||
|
||||
-spec transform_module_options([{atom(), any()}]) -> [{atom(), any()}].
|
||||
|
||||
transform_module_options(Opts) ->
|
||||
lists:map(
|
||||
fun({iqdisc, {queues, N}}) ->
|
||||
{iqdisc, N};
|
||||
(Opt) ->
|
||||
Opt
|
||||
end, Opts).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
+1
-22
@@ -64,13 +64,11 @@ start() ->
|
||||
-spec start_module(binary(), atom(), opts()) -> any().
|
||||
|
||||
start_module(Host, Module, Opts) ->
|
||||
set_module_opts_mnesia(Host, Module, Opts),
|
||||
ets:insert(ejabberd_modules,
|
||||
#ejabberd_module{module_host = {Module, Host},
|
||||
opts = Opts}),
|
||||
try Module:start(Host, Opts) catch
|
||||
Class:Reason ->
|
||||
del_module_mnesia(Host, Module),
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
ErrorText =
|
||||
io_lib:format("Problem starting the module ~p for host "
|
||||
@@ -101,7 +99,7 @@ is_app_running(AppName) ->
|
||||
stop_module(Host, Module) ->
|
||||
case stop_module_keep_config(Host, Module) of
|
||||
error -> error;
|
||||
ok -> del_module_mnesia(Host, Module)
|
||||
ok -> ok
|
||||
end.
|
||||
|
||||
%% @doc Stop the module in a host, but keep its configuration.
|
||||
@@ -232,25 +230,6 @@ loaded_modules_with_opts(Host) ->
|
||||
opts = '$2'},
|
||||
[], [{{'$1', '$2'}}]}]).
|
||||
|
||||
set_module_opts_mnesia(Host, Module, Opts) ->
|
||||
Modules = ejabberd_config:get_local_option(
|
||||
{modules, Host},
|
||||
fun(Ls) when is_list(Ls) -> Ls end,
|
||||
[]),
|
||||
Modules1 = lists:keydelete(Module, 1, Modules),
|
||||
Modules2 = [{Module, Opts} | Modules1],
|
||||
ejabberd_config:add_local_option({modules, Host},
|
||||
Modules2).
|
||||
|
||||
del_module_mnesia(Host, Module) ->
|
||||
Modules = ejabberd_config:get_local_option(
|
||||
{modules, Host},
|
||||
fun(Ls) when is_list(Ls) -> Ls end,
|
||||
[]),
|
||||
Modules1 = lists:keydelete(Module, 1, Modules),
|
||||
ejabberd_config:add_local_option({modules, Host},
|
||||
Modules1).
|
||||
|
||||
-spec get_hosts(opts(), binary()) -> [binary()].
|
||||
|
||||
get_hosts(Opts, Prefix) ->
|
||||
|
||||
+23
-4
@@ -28,18 +28,21 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%%-compile(export_all).
|
||||
-export([domain_utf8_to_ascii/1,
|
||||
domain_ucs2_to_ascii/1,
|
||||
utf8_to_ucs2/1]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-endif.
|
||||
|
||||
-spec domain_utf8_to_ascii(binary()) -> false | binary().
|
||||
|
||||
domain_utf8_to_ascii(Domain) ->
|
||||
domain_ucs2_to_ascii(utf8_to_ucs2(Domain)).
|
||||
|
||||
utf8_to_ucs2(S) ->
|
||||
list_to_binary(utf8_to_ucs2(binary_to_list(S), "")).
|
||||
utf8_to_ucs2(binary_to_list(S), "").
|
||||
|
||||
utf8_to_ucs2([], R) -> lists:reverse(R);
|
||||
utf8_to_ucs2([C | S], R) when C < 128 ->
|
||||
@@ -51,10 +54,10 @@ utf8_to_ucs2([C1, C2, C3 | S], R) when C1 < 240 ->
|
||||
[C1 band 15 bsl 12 bor (C2 band 63 bsl 6) bor C3 band 63
|
||||
| R]).
|
||||
|
||||
-spec domain_ucs2_to_ascii(binary()) -> false | binary().
|
||||
-spec domain_ucs2_to_ascii(list()) -> false | binary().
|
||||
|
||||
domain_ucs2_to_ascii(Domain) ->
|
||||
case catch domain_ucs2_to_ascii1(binary_to_list(Domain)) of
|
||||
case catch domain_ucs2_to_ascii1(Domain) of
|
||||
{'EXIT', _Reason} -> false;
|
||||
Res -> iolist_to_binary(Res)
|
||||
end.
|
||||
@@ -204,3 +207,19 @@ codepoint(C) ->
|
||||
if (0 =< C) and (C =< 25) -> C + 97;
|
||||
(26 =< C) and (C =< 35) -> C + 22
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Unit tests
|
||||
%%%===================================================================
|
||||
-ifdef(TEST).
|
||||
|
||||
acsii_test() ->
|
||||
?assertEqual(<<"test.org">>, domain_utf8_to_ascii(<<"test.org">>)).
|
||||
|
||||
utf8_test() ->
|
||||
?assertEqual(
|
||||
<<"xn--d1acufc.xn--p1ai">>,
|
||||
domain_utf8_to_ascii(
|
||||
<<208,180,208,190,208,188,208,181,208,189,46,209,128,209,132>>)).
|
||||
|
||||
-endif.
|
||||
|
||||
+19
-5
@@ -48,9 +48,10 @@
|
||||
now_to_local_string/1, datetime_string_to_timestamp/1,
|
||||
decode_base64/1, encode_base64/1, ip_to_list/1,
|
||||
rsm_encode/1, rsm_encode/2, rsm_decode/1,
|
||||
binary_to_integer/1, binary_to_integer/2,
|
||||
integer_to_binary/1, integer_to_binary/2,
|
||||
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1]).
|
||||
binary_to_integer/1, binary_to_integer/2,
|
||||
integer_to_binary/1, integer_to_binary/2,
|
||||
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
|
||||
l2i/1, i2l/1, i2l/2]).
|
||||
|
||||
%% TODO: Remove once XEP-0091 is Obsolete
|
||||
%% TODO: Remove once XEP-0091 is Obsolete
|
||||
@@ -599,8 +600,6 @@ rsm_encode_count(Count, Arr) ->
|
||||
children = [{xmlcdata, i2l(Count)}]}
|
||||
| Arr].
|
||||
|
||||
i2l(I) when is_integer(I) -> integer_to_binary(I).
|
||||
|
||||
-type tz() :: {binary(), {integer(), integer()}} | {integer(), integer()} | utc.
|
||||
|
||||
%% Timezone = utc | {Sign::string(), {Hours, Minutes}} | {Hours, Minutes}
|
||||
@@ -880,3 +879,18 @@ tuple_to_binary(T) ->
|
||||
|
||||
atom_to_binary(A) ->
|
||||
erlang:atom_to_binary(A, utf8).
|
||||
|
||||
|
||||
l2i(I) when is_integer(I) -> I;
|
||||
l2i(L) when is_binary(L) -> binary_to_integer(L).
|
||||
|
||||
i2l(I) when is_integer(I) -> integer_to_binary(I);
|
||||
i2l(L) when is_binary(L) -> L.
|
||||
|
||||
i2l(I, N) when is_integer(I) -> i2l(i2l(I), N);
|
||||
i2l(L, N) when is_binary(L) ->
|
||||
case str:len(L) of
|
||||
N -> L;
|
||||
C when C > N -> L;
|
||||
_ -> i2l(<<$0, L/binary>>, N)
|
||||
end.
|
||||
|
||||
+21
-1
@@ -35,7 +35,9 @@
|
||||
-export([start/2,
|
||||
init/0,
|
||||
stop/1,
|
||||
export/1,
|
||||
export/1,
|
||||
import/1,
|
||||
import/3,
|
||||
announce/3,
|
||||
send_motd/1,
|
||||
disco_identity/5,
|
||||
@@ -1072,3 +1074,21 @@ export(_Server) ->
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select xml from motd where username='';">>,
|
||||
fun([XML]) ->
|
||||
El = xml_stream:parse_element(XML),
|
||||
#motd{server = LServer, packet = El}
|
||||
end},
|
||||
{<<"select username from motd where xml='';">>,
|
||||
fun([LUser]) ->
|
||||
#motd_users{us = {LUser, LServer}}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #motd{} = Motd) ->
|
||||
mnesia:dirty_write(Motd);
|
||||
import(_LServer, mnesia, #motd_users{} = Users) ->
|
||||
mnesia:dirty_write(Users);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
%%% Author : Eric Cestari <ecestari@process-one.net>
|
||||
%%% Purpose : Message Carbons XEP-0280 0.8
|
||||
%%% Created : 5 May 2008 by Mickael Remond <mremond@process-one.net>
|
||||
%%% Usage : Add the following line in modules section of ejabberd.cfg:
|
||||
%%% Usage : Add the following line in modules section of ejabberd.yml:
|
||||
%%% {mod_carboncopy, []}
|
||||
%%%
|
||||
%%%
|
||||
|
||||
@@ -1365,8 +1365,8 @@ get_form(Host, [<<"config">>, <<"access">>], Lang) ->
|
||||
[{xmlcdata, S}]}
|
||||
end,
|
||||
str:tokens(iolist_to_binary(io_lib:format("~p.",
|
||||
[ets:select(config,
|
||||
[{{config,
|
||||
[ets:select(local_config,
|
||||
[{{local_config,
|
||||
{access,
|
||||
'$1',
|
||||
'$2'},
|
||||
@@ -1770,8 +1770,8 @@ set_form(_From, Host, [<<"config">>, <<"access">>],
|
||||
_Lang, XData) ->
|
||||
SetAccess = fun (Rs) ->
|
||||
mnesia:transaction(fun () ->
|
||||
Os = mnesia:select(config,
|
||||
[{{config,
|
||||
Os = mnesia:select(local_config,
|
||||
[{{local_config,
|
||||
{access,
|
||||
'$1',
|
||||
'$2'},
|
||||
@@ -1787,7 +1787,7 @@ set_form(_From, Host, [<<"config">>, <<"access">>],
|
||||
lists:foreach(fun ({access,
|
||||
Name,
|
||||
Rules}) ->
|
||||
mnesia:write({config,
|
||||
mnesia:write({local_config,
|
||||
{access,
|
||||
Name,
|
||||
Host},
|
||||
|
||||
@@ -129,7 +129,7 @@ process_get(#xmlel{name = <<"info">>}) ->
|
||||
children = []}};
|
||||
process_get(#xmlel{name = <<"welcome-message">>,
|
||||
attrs = Attrs}) ->
|
||||
{Subj, Body} = ejabberd_config:get_local_option(
|
||||
{Subj, Body} = ejabberd_config:get_option(
|
||||
welcome_message,
|
||||
fun({Subj, Body}) ->
|
||||
{iolist_to_binary(Subj),
|
||||
@@ -145,7 +145,7 @@ process_get(#xmlel{name = <<"welcome-message">>,
|
||||
children = [{xmlcdata, Body}]}]}};
|
||||
process_get(#xmlel{name = <<"registration-watchers">>,
|
||||
attrs = Attrs}) ->
|
||||
SubEls = ejabberd_config:get_local_option(
|
||||
SubEls = ejabberd_config:get_option(
|
||||
registration_watchers,
|
||||
fun(JIDs) when is_list(JIDs) ->
|
||||
lists:map(
|
||||
@@ -167,8 +167,8 @@ process_get(#xmlel{name = <<"acls">>, attrs = Attrs}) ->
|
||||
process_get(#xmlel{name = <<"access">>,
|
||||
attrs = Attrs}) ->
|
||||
Str = iolist_to_binary(io_lib:format("~p.",
|
||||
[ets:select(config,
|
||||
[{{config, {access, '$1'},
|
||||
[ets:select(local_config,
|
||||
[{{local_config, {access, '$1'},
|
||||
'$2'},
|
||||
[],
|
||||
[{{access, '$1',
|
||||
|
||||
+29
-4
@@ -36,7 +36,8 @@
|
||||
process_sm_iq_items/3, process_sm_iq_info/3,
|
||||
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
|
||||
get_info/5, register_feature/2, unregister_feature/2,
|
||||
register_extra_domain/2, unregister_extra_domain/2]).
|
||||
register_extra_domain/2, unregister_extra_domain/2,
|
||||
transform_module_options/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -440,6 +441,22 @@ get_user_resources(User, Server) ->
|
||||
end,
|
||||
lists:sort(Rs)).
|
||||
|
||||
transform_module_options(Opts) ->
|
||||
lists:map(
|
||||
fun({server_info, Infos}) ->
|
||||
NewInfos = lists:map(
|
||||
fun({Modules, Name, URLs}) ->
|
||||
[[{modules, Modules},
|
||||
{name, Name},
|
||||
{urls, URLs}]];
|
||||
(Opt) ->
|
||||
Opt
|
||||
end, Infos),
|
||||
{server_info, NewInfos};
|
||||
(Opt) ->
|
||||
Opt
|
||||
end, Opts).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%%% Support for: XEP-0157 Contact Addresses for XMPP Services
|
||||
@@ -465,9 +482,17 @@ get_info(_A, Host, Mod, Node, _Lang) when Node == <<>> ->
|
||||
get_info(Acc, _, _, _Node, _) -> Acc.
|
||||
|
||||
get_fields_xml(Host, Module) ->
|
||||
Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info,
|
||||
fun(L) when is_list(L) -> L end,
|
||||
[]),
|
||||
Fields = gen_mod:get_module_opt(
|
||||
Host, ?MODULE, server_info,
|
||||
fun(L) ->
|
||||
lists:map(
|
||||
fun(Opts) ->
|
||||
Mods = proplists:get_value(modules, Opts, all),
|
||||
Name = proplists:get_value(names, Opts, <<>>),
|
||||
URLs = proplists:get_value(urls, Opts, []),
|
||||
{Mods, Name, URLs}
|
||||
end, lists:flatmap(L))
|
||||
end, []),
|
||||
Fields_good = lists:filter(fun ({Modules, _, _}) ->
|
||||
case Modules of
|
||||
all -> true;
|
||||
|
||||
+16
-2
@@ -33,8 +33,8 @@
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% API
|
||||
-export([start_link/2, start/2, stop/1, export/1,
|
||||
closed_connection/3, get_connection_params/3]).
|
||||
-export([start_link/2, start/2, stop/1, export/1, import/1,
|
||||
import/3, closed_connection/3, get_connection_params/3]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
@@ -1288,3 +1288,17 @@ export(_Server) ->
|
||||
[]
|
||||
end
|
||||
end}].
|
||||
|
||||
import(_LServer) ->
|
||||
[{<<"select jid, host, data from irc_custom;">>,
|
||||
fun([SJID, IRCHost, SData]) ->
|
||||
#jid{luser = U, lserver = S} = jlib:string_to_jid(SJID),
|
||||
Data = ejabberd_odbc:decode_term(SData),
|
||||
#irc_custom{us_host = {{U, S}, IRCHost},
|
||||
data = Data}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #irc_custom{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+31
-11
@@ -31,8 +31,9 @@
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_local_iq/3, export/1,
|
||||
process_sm_iq/3, on_presence_update/4,
|
||||
store_last_info/4, get_last_info/2, remove_user/2]).
|
||||
process_sm_iq/3, on_presence_update/4, import/1, import/3,
|
||||
store_last_info/4, get_last_info/2, remove_user/2,
|
||||
transform_options/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -101,18 +102,13 @@ process_local_iq(_From, _To,
|
||||
%% @doc Get the uptime of the ejabberd node, expressed in seconds.
|
||||
%% When ejabberd is starting, ejabberd_config:start/0 stores the datetime.
|
||||
get_node_uptime() ->
|
||||
case ejabberd_config:get_local_option(
|
||||
case ejabberd_config:get_option(
|
||||
node_start,
|
||||
fun({MegaSecs, Secs, MicroSecs} = Now)
|
||||
when is_integer(MegaSecs), MegaSecs >= 0,
|
||||
is_integer(Secs), Secs >= 0,
|
||||
is_integer(MicroSecs), MicroSecs >= 0 ->
|
||||
Now
|
||||
end) of
|
||||
fun(S) when is_integer(S), S >= 0 -> S end) of
|
||||
undefined ->
|
||||
trunc(element(1, erlang:statistics(wall_clock)) / 1000);
|
||||
StartNow ->
|
||||
now_to_seconds(now()) - now_to_seconds(StartNow)
|
||||
Now ->
|
||||
now_to_seconds(now()) - Now
|
||||
end.
|
||||
|
||||
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
|
||||
@@ -305,3 +301,27 @@ export(_Server) ->
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select username, seconds, state from last">>,
|
||||
fun([LUser, TimeStamp, State]) ->
|
||||
#last_activity{us = {LUser, LServer},
|
||||
timestamp = jlib:binary_to_integer(
|
||||
TimeStamp),
|
||||
status = State}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #last_activity{} = LA) ->
|
||||
mnesia:dirty_write(LA);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
|
||||
transform_options({node_start, {_, _, _} = Now}, Opts) ->
|
||||
?WARNING_MSG("Old 'node_start' format detected. This is still supported "
|
||||
"but it is better to fix your config.", []),
|
||||
[{node_start, now_to_seconds(Now)}|Opts];
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
@@ -44,6 +44,9 @@
|
||||
shutdown_rooms/1,
|
||||
process_iq_disco_items/4,
|
||||
broadcast_service_message/2,
|
||||
export/1,
|
||||
import/1,
|
||||
import/3,
|
||||
can_use_nick/4]).
|
||||
|
||||
%% gen_server callbacks
|
||||
@@ -1141,3 +1144,64 @@ update_muc_registered_table(_Host) ->
|
||||
?INFO_MSG("Recreating muc_registered table", []),
|
||||
mnesia:transform_table(muc_registered, ignore, Fields)
|
||||
end.
|
||||
|
||||
export(_Server) ->
|
||||
[{muc_room,
|
||||
fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
|
||||
case str:suffix(Host, RoomHost) of
|
||||
true ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
SRoomHost = ejabberd_odbc:escape(RoomHost),
|
||||
SOpts = ejabberd_odbc:encode_term(Opts),
|
||||
[[<<"delete from muc_room where name='">>, SName,
|
||||
<<"' and host='">>, SRoomHost, <<"';">>],
|
||||
[<<"insert into muc_room(name, host, opts) ",
|
||||
"values (">>,
|
||||
<<"'">>, SName, <<"', '">>, SRoomHost,
|
||||
<<"', '">>, SOpts, <<"');">>]];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
end},
|
||||
{muc_registered,
|
||||
fun(Host, #muc_registered{us_host = {{U, S}, RoomHost},
|
||||
nick = Nick}) ->
|
||||
case str:suffix(Host, RoomHost) of
|
||||
true ->
|
||||
SJID = ejabberd_odbc:escape(
|
||||
jlib:jid_to_string(
|
||||
jlib:make_jid(U, S, <<"">>))),
|
||||
SNick = ejabberd_odbc:escape(Nick),
|
||||
SRoomHost = ejabberd_odbc:escape(RoomHost),
|
||||
[[<<"delete from muc_registered where jid='">>,
|
||||
SJID, <<"' and host='">>, SRoomHost, <<"';">>],
|
||||
[<<"insert into muc_registered(jid, host, "
|
||||
"nick) values ('">>,
|
||||
SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
|
||||
<<"');">>]];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
end}].
|
||||
|
||||
import(_LServer) ->
|
||||
[{<<"select name, host, opts from muc_room;">>,
|
||||
fun([Name, RoomHost, SOpts]) ->
|
||||
Opts = opts_to_binary(ejabberd_odbc:decode_term(SOpts)),
|
||||
#muc_room{name_host = {Name, RoomHost},
|
||||
opts = Opts}
|
||||
end},
|
||||
{<<"select jid, host, nick from muc_registered;">>,
|
||||
fun([J, RoomHost, Nick]) ->
|
||||
#jid{user = U, server = S} =
|
||||
jlib:string_to_jid(J),
|
||||
#muc_registered{us_host = {{U, S}, RoomHost},
|
||||
nick = Nick}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #muc_room{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_LServer, mnesia, #muc_registered{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+11
-3
@@ -33,7 +33,7 @@
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% API
|
||||
-export([start_link/2, start/2, stop/1,
|
||||
-export([start_link/2, start/2, stop/1, transform_module_options/1,
|
||||
check_access_log/2, add_to_log/5]).
|
||||
|
||||
%% gen_server callbacks
|
||||
@@ -111,6 +111,14 @@ check_access_log(Host, From) ->
|
||||
Res -> Res
|
||||
end.
|
||||
|
||||
transform_module_options(Opts) ->
|
||||
lists:map(
|
||||
fun({top_link, {S1, S2}}) ->
|
||||
{top_link, [{S1, S2}]};
|
||||
(Opt) ->
|
||||
Opt
|
||||
end, Opts).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
@@ -152,14 +160,14 @@ init([Host, Opts]) ->
|
||||
(universal) -> universal
|
||||
end, local),
|
||||
Top_link = gen_mod:get_opt(top_link, Opts,
|
||||
fun({S1, S2}) ->
|
||||
fun([{S1, S2}]) ->
|
||||
{iolist_to_binary(S1),
|
||||
iolist_to_binary(S2)}
|
||||
end, {<<"/">>, <<"Home">>}),
|
||||
NoFollow = gen_mod:get_opt(spam_prevention, Opts,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
true),
|
||||
Lang = ejabberd_config:get_local_option(
|
||||
Lang = ejabberd_config:get_option(
|
||||
{language, Host},
|
||||
fun iolist_to_binary/1,
|
||||
?MYLANG),
|
||||
|
||||
+19
-3
@@ -943,9 +943,14 @@ process_groupchat_message(From,
|
||||
Packet)
|
||||
end,
|
||||
?DICT:to_list(StateData#state.users)),
|
||||
NewStateData2 = add_message_to_history(FromNick, From,
|
||||
NewStateData2 = case has_body_or_subject(Packet) of
|
||||
true ->
|
||||
add_message_to_history(FromNick, From,
|
||||
Packet,
|
||||
NewStateData1),
|
||||
NewStateData1);
|
||||
false ->
|
||||
NewStateData1
|
||||
end,
|
||||
{next_state, normal_state, NewStateData2};
|
||||
_ ->
|
||||
Err = case
|
||||
@@ -2500,7 +2505,8 @@ process_iq_admin(From, get, Lang, SubEl, StateData) ->
|
||||
{'EXIT', _} -> {error, ?ERR_BAD_REQUEST};
|
||||
SAffiliation ->
|
||||
if (FAffiliation == owner) or
|
||||
(FAffiliation == admin) ->
|
||||
(FAffiliation == admin) or
|
||||
((FAffiliation == member) and (SAffiliation == member)) ->
|
||||
Items = items_with_affiliation(SAffiliation,
|
||||
StateData),
|
||||
{result, Items, StateData};
|
||||
@@ -4462,3 +4468,13 @@ tab_count_user(JID) ->
|
||||
|
||||
element_size(El) ->
|
||||
byte_size(xml:element_to_binary(El)).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%% Detect messange stanzas that don't have meaninful content
|
||||
|
||||
has_body_or_subject(Packet) ->
|
||||
[] /= lists:dropwhile(fun
|
||||
(#xmlel{name = <<"body">>}) -> false;
|
||||
(#xmlel{name = <<"subject">>}) -> false;
|
||||
(_) -> true
|
||||
end, Packet#xmlel.children).
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
remove_expired_messages/1,
|
||||
remove_old_messages/2,
|
||||
remove_user/2,
|
||||
import/1,
|
||||
import/3,
|
||||
export/1,
|
||||
get_queue_length/2,
|
||||
get_offline_els/2,
|
||||
webadmin_page/3,
|
||||
@@ -873,3 +876,64 @@ count_offline_messages(LUser, LServer) ->
|
||||
_ ->
|
||||
0
|
||||
end.
|
||||
|
||||
export(_Server) ->
|
||||
[{offline_msg,
|
||||
fun(Host, #offline_msg{us = {LUser, LServer},
|
||||
timestamp = TimeStamp, from = From, to = To,
|
||||
packet = Packet})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
#xmlel{name = Name, attrs = Attrs, children = Els} =
|
||||
Packet,
|
||||
Attrs2 =
|
||||
jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
NewPacket = #xmlel{name = Name, attrs = Attrs2,
|
||||
children =
|
||||
Els ++
|
||||
[jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(TimeStamp),
|
||||
utc,
|
||||
jlib:make_jid(<<"">>,
|
||||
LServer,
|
||||
<<"">>),
|
||||
<<"Offline Storage">>),
|
||||
jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(TimeStamp))]},
|
||||
XML =
|
||||
ejabberd_odbc:escape(xml:element_to_binary(NewPacket)),
|
||||
[[<<"delete from spool where username='">>, Username, <<"';">>],
|
||||
[<<"insert into spool(username, xml) values ('">>,
|
||||
Username, <<"', '">>, XML, <<"');">>]];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select username, xml from spool;">>,
|
||||
fun([LUser, XML]) ->
|
||||
El = #xmlel{} = xml_stream:parse_element(XML),
|
||||
From = #jid{} = jlib:string_to_jid(
|
||||
xml:get_attr_s(<<"from">>, El#xmlel.attrs)),
|
||||
To = #jid{} = jlib:string_to_jid(
|
||||
xml:get_attr_s(<<"to">>, El#xmlel.attrs)),
|
||||
Stamp = xml:get_path_s(El, [{elem, <<"delay">>},
|
||||
{attr, <<"stamp">>}]),
|
||||
TS = case jlib:datetime_string_to_timestamp(Stamp) of
|
||||
{_, _, _} = Now ->
|
||||
Now;
|
||||
undefined ->
|
||||
now()
|
||||
end,
|
||||
Expire = find_x_expire(TS, El#xmlel.children),
|
||||
#offline_msg{us = {LUser, LServer},
|
||||
from = From, to = To,
|
||||
timestamp = TS, expire = Expire}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #offline_msg{} = Msg) ->
|
||||
mnesia:dirty_write(Msg);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+41
-2
@@ -30,11 +30,11 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_iq/3, export/1,
|
||||
-export([start/2, stop/1, process_iq/3, export/1, import/1,
|
||||
process_iq_set/4, process_iq_get/5, get_user_list/3,
|
||||
check_packet/6, remove_user/2, item_to_raw/1,
|
||||
raw_to_item/1, is_list_needdb/1, updated_list/3,
|
||||
item_to_xml/1, get_user_lists/2]).
|
||||
item_to_xml/1, get_user_lists/2, import/3]).
|
||||
|
||||
%% For mod_blocking
|
||||
-export([sql_add_privacy_list/2,
|
||||
@@ -965,6 +965,11 @@ sql_get_privacy_list_data(LUser, LServer, Name) ->
|
||||
odbc_queries:get_privacy_list_data(LServer, Username,
|
||||
SName).
|
||||
|
||||
sql_get_privacy_list_data_t(LUser, Name) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
odbc_queries:get_privacy_list_data_t(Username, SName).
|
||||
|
||||
sql_get_privacy_list_data_by_id(ID, LServer) ->
|
||||
odbc_queries:get_privacy_list_data_by_id(LServer, ID).
|
||||
|
||||
@@ -1098,3 +1103,37 @@ get_id() ->
|
||||
ID = get(id),
|
||||
put(id, ID + 1),
|
||||
ID + 1.
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select username from privacy_list;">>,
|
||||
fun([LUser]) ->
|
||||
Default = case sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, [<<"name">>], []} ->
|
||||
none;
|
||||
{selected, [<<"name">>], [[DefName]]} ->
|
||||
DefName;
|
||||
_ ->
|
||||
none
|
||||
end,
|
||||
{selected, [<<"name">>], Names} =
|
||||
sql_get_privacy_list_names_t(LUser),
|
||||
Lists = lists:flatmap(
|
||||
fun([Name]) ->
|
||||
case sql_get_privacy_list_data_t(LUser, Name) of
|
||||
{selected, _, RItems} ->
|
||||
[{Name,
|
||||
lists:map(fun raw_to_item/1,
|
||||
RItems)}];
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end, Names),
|
||||
#privacy{default = Default,
|
||||
us = {LUser, LServer},
|
||||
lists = Lists}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #privacy{} = P) ->
|
||||
mnesia:dirty_write(P);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+15
-2
@@ -30,8 +30,8 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_sm_iq/3,
|
||||
remove_user/2, get_data/2, export/1]).
|
||||
-export([start/2, stop/1, process_sm_iq/3, import/3,
|
||||
remove_user/2, get_data/2, export/1, import/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -277,3 +277,16 @@ export(_Server) ->
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select username, namespace, data from private_storage;">>,
|
||||
fun([LUser, XMLNS, XML]) ->
|
||||
El = #xmlel{} = xml_stream:parse_element(XML),
|
||||
#private_storage{usns = {LUser, LServer, XMLNS},
|
||||
xml = El}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #private_storage{} = PS) ->
|
||||
mnesia:dirty_write(PS);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+4
-1
@@ -33,7 +33,7 @@
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% gen_mod callbacks.
|
||||
-export([start/2, stop/1]).
|
||||
-export([start/2, stop/1, transform_module_options/1]).
|
||||
|
||||
%% supervisor callbacks.
|
||||
-export([init/1]).
|
||||
@@ -64,6 +64,9 @@ start_link(Host, Opts) ->
|
||||
supervisor:start_link({local, Proc}, ?MODULE,
|
||||
[Host, Opts]).
|
||||
|
||||
transform_module_options(Opts) ->
|
||||
mod_proxy65_service:transform_module_options(Opts).
|
||||
|
||||
init([Host, Opts]) ->
|
||||
Service = {mod_proxy65_service,
|
||||
{mod_proxy65_service, start_link, [Host, Opts]},
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
handle_cast/2, terminate/2, code_change/3]).
|
||||
|
||||
%% API.
|
||||
-export([start_link/2, add_listener/2,
|
||||
-export([start_link/2, add_listener/2, transform_module_options/1,
|
||||
delete_listener/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -261,16 +261,15 @@ parse_options(ServerHost, Opts) ->
|
||||
Name = gen_mod:get_opt(name, Opts, fun iolist_to_binary/1,
|
||||
<<"SOCKS5 Bytestreams">>),
|
||||
IP = gen_mod:get_opt(ip, Opts,
|
||||
fun(Addr) ->
|
||||
jlib:ip_to_list(Addr),
|
||||
fun(S) ->
|
||||
{ok, Addr} = inet_parse:address(
|
||||
binary_to_list(
|
||||
iolist_to_binary(S))),
|
||||
Addr
|
||||
end, get_my_ip()),
|
||||
HostName = gen_mod:get_opt(hostname, Opts,
|
||||
fun(Addr) when is_tuple(Addr) ->
|
||||
jlib:ip_to_list(Addr);
|
||||
(S) ->
|
||||
iolist_to_binary(S)
|
||||
end, jlib:ip_to_list(IP)),
|
||||
fun iolist_to_binary/1,
|
||||
jlib:ip_to_list(IP)),
|
||||
StreamAddr = [{<<"jid">>, MyHost},
|
||||
{<<"host">>, HostName},
|
||||
{<<"port">>, jlib:integer_to_binary(Port)}],
|
||||
@@ -278,6 +277,16 @@ parse_options(ServerHost, Opts) ->
|
||||
name = Name, port = Port, ip = IP,
|
||||
stream_addr = StreamAddr, acl = ACL}.
|
||||
|
||||
transform_module_options(Opts) ->
|
||||
lists:map(
|
||||
fun({ip, IP}) when is_tuple(IP) ->
|
||||
{ip, jlib:ip_to_list(IP)};
|
||||
({hostname, IP}) when is_tuple(IP) ->
|
||||
{hostname, jlib:ip_to_list(IP)};
|
||||
(Opt) ->
|
||||
Opt
|
||||
end, Opts).
|
||||
|
||||
get_my_ip() ->
|
||||
{ok, MyHostName} = inet:gethostname(),
|
||||
case inet:getaddr(MyHostName, inet) of
|
||||
|
||||
@@ -279,10 +279,14 @@ select_auth_method(anonymous, AuthMethods) ->
|
||||
|
||||
%% Obviously, we must use shaper with maximum rate.
|
||||
find_maxrate(Shaper, JID1, JID2, Host) ->
|
||||
MaxRate1 = shaper:new(acl:match_rule(Host, Shaper,
|
||||
JID1)),
|
||||
MaxRate2 = shaper:new(acl:match_rule(Host, Shaper,
|
||||
JID2)),
|
||||
MaxRate1 = case acl:match_rule(Host, Shaper, JID1) of
|
||||
deny -> none;
|
||||
R1 -> shaper:new(R1)
|
||||
end,
|
||||
MaxRate2 = case acl:match_rule(Host, Shaper, JID2) of
|
||||
deny -> none;
|
||||
R2 -> shaper:new(R2)
|
||||
end,
|
||||
if MaxRate1 == none; MaxRate2 == none -> none;
|
||||
true -> lists:max([MaxRate1, MaxRate2])
|
||||
end.
|
||||
|
||||
+24
-16
@@ -1302,7 +1302,8 @@ remove_user(User, Server) ->
|
||||
[NodeId,
|
||||
Entity,
|
||||
JID,
|
||||
all])
|
||||
all]);
|
||||
(_) -> ok
|
||||
end,
|
||||
Subscriptions),
|
||||
{result, Affiliations} =
|
||||
@@ -3682,26 +3683,33 @@ get_options_helper(JID, Lang, Node, NodeID, SubID, Type) ->
|
||||
{error,
|
||||
extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)};
|
||||
{_, _} ->
|
||||
read_sub(Subscriber, Node, NodeID, SubID, Lang)
|
||||
ValidSubId = lists:member(SubID, SubIDs),
|
||||
if ValidSubId ->
|
||||
read_sub(Subscriber, Node, NodeID, SubID, Lang);
|
||||
true ->
|
||||
{error,
|
||||
extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}
|
||||
end
|
||||
end.
|
||||
|
||||
read_sub(Subscriber, Node, NodeID, SubID, Lang) ->
|
||||
case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of
|
||||
Children = case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of
|
||||
{error, notfound} ->
|
||||
{error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)};
|
||||
[];
|
||||
{result, #pubsub_subscription{options = Options}} ->
|
||||
{result, XdataEl} = pubsub_subscription:get_options_xform(Lang, Options),
|
||||
OptionsEl = #xmlel{name = <<"options">>,
|
||||
attrs =
|
||||
[{<<"jid">>, jlib:jid_to_string(Subscriber)},
|
||||
{<<"subid">>, SubID}
|
||||
| nodeAttr(Node)],
|
||||
children = [XdataEl]},
|
||||
PubsubEl = #xmlel{name = <<"pubsub">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
|
||||
children = [OptionsEl]},
|
||||
{result, PubsubEl}
|
||||
end.
|
||||
[XdataEl]
|
||||
end,
|
||||
OptionsEl = #xmlel{name = <<"options">>,
|
||||
attrs =
|
||||
[{<<"jid">>, jlib:jid_to_string(Subscriber)},
|
||||
{<<"subid">>, SubID}
|
||||
| nodeAttr(Node)],
|
||||
children = Children},
|
||||
PubsubEl = #xmlel{name = <<"pubsub">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
|
||||
children = [OptionsEl]},
|
||||
{result, PubsubEl}.
|
||||
|
||||
set_options(Host, Node, JID, SubID, Configuration) ->
|
||||
Action = fun (#pubsub_node{type = Type, id = NodeID}) ->
|
||||
@@ -4436,7 +4444,7 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, NodeId, Type, Nod
|
||||
[] ->
|
||||
case user_resources(LUser, LServer) of
|
||||
[Resource|_] -> Resource;
|
||||
_ -> ""
|
||||
_ -> <<>>
|
||||
end;
|
||||
_ ->
|
||||
LResource
|
||||
|
||||
+25
-17
@@ -443,7 +443,7 @@ send_loop(State) ->
|
||||
LJID = jlib:jid_tolower(JID),
|
||||
BJID = jlib:jid_remove_resource(LJID),
|
||||
lists:foreach(fun (PType) ->
|
||||
{result, Subscriptions} = case catch node_action(Host,
|
||||
Subscriptions = case catch node_action(Host,
|
||||
PType,
|
||||
get_entity_subscriptions_for_send_last,
|
||||
[Host, JID]) of
|
||||
@@ -952,7 +952,8 @@ remove_user(User, Server) ->
|
||||
[NodeId,
|
||||
Entity,
|
||||
JID,
|
||||
all])
|
||||
all]);
|
||||
(_) -> ok
|
||||
end,
|
||||
Subscriptions),
|
||||
{result, Affiliations} =
|
||||
@@ -3301,26 +3302,33 @@ get_options_helper(JID, Lang, Node, NodeID, SubID, Type) ->
|
||||
{error,
|
||||
extended_error(?ERR_NOT_ACCEPTABLE, <<"subid-required">>)};
|
||||
{_, _} ->
|
||||
read_sub(Subscriber, Node, NodeID, SubID, Lang)
|
||||
ValidSubId = lists:member(SubID, SubIDs),
|
||||
if ValidSubId ->
|
||||
read_sub(Subscriber, Node, NodeID, SubID, Lang);
|
||||
true ->
|
||||
{error,
|
||||
extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)}
|
||||
end
|
||||
end.
|
||||
|
||||
read_sub(Subscriber, Node, NodeID, SubID, Lang) ->
|
||||
case pubsub_subscription_odbc:get_subscription(Subscriber, NodeID, SubID) of
|
||||
Children = case pubsub_subscription_odbc:get_subscription(Subscriber, NodeID, SubID) of
|
||||
{error, notfound} ->
|
||||
{error, extended_error(?ERR_NOT_ACCEPTABLE, <<"invalid-subid">>)};
|
||||
[];
|
||||
{result, #pubsub_subscription{options = Options}} ->
|
||||
{result, XdataEl} = pubsub_subscription_odbc:get_options_xform(Lang, Options),
|
||||
OptionsEl = #xmlel{name = <<"options">>,
|
||||
attrs =
|
||||
[{<<"jid">>, jlib:jid_to_string(Subscriber)},
|
||||
{<<"subid">>, SubID}
|
||||
| nodeAttr(Node)],
|
||||
children = [XdataEl]},
|
||||
PubsubEl = #xmlel{name = <<"pubsub">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
|
||||
children = [OptionsEl]},
|
||||
{result, PubsubEl}
|
||||
end.
|
||||
[XdataEl]
|
||||
end,
|
||||
OptionsEl = #xmlel{name = <<"options">>,
|
||||
attrs =
|
||||
[{<<"jid">>, jlib:jid_to_string(Subscriber)},
|
||||
{<<"subid">>, SubID}
|
||||
| nodeAttr(Node)],
|
||||
children = Children},
|
||||
PubsubEl = #xmlel{name = <<"pubsub">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PUBSUB}],
|
||||
children = [OptionsEl]},
|
||||
{result, PubsubEl}.
|
||||
|
||||
set_options(Host, Node, JID, SubID, Configuration) ->
|
||||
Action = fun (#pubsub_node{type = Type, id = NodeID}) ->
|
||||
@@ -4055,7 +4063,7 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, NodeId, Type, Nod
|
||||
[] ->
|
||||
case user_resources(LUser, LServer) of
|
||||
[Resource|_] -> Resource;
|
||||
_ -> ""
|
||||
_ -> <<>>
|
||||
end;
|
||||
_ ->
|
||||
LResource
|
||||
|
||||
+65
-72
@@ -32,7 +32,8 @@
|
||||
|
||||
-export([start/2, stop/1, stream_feature_register/2,
|
||||
unauthenticated_iq_register/4, try_register/5,
|
||||
process_iq/3, send_registration_notifications/3]).
|
||||
process_iq/3, send_registration_notifications/3,
|
||||
transform_options/1, transform_module_options/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -418,7 +419,11 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
|
||||
send_welcome_message(JID) ->
|
||||
Host = JID#jid.lserver,
|
||||
case gen_mod:get_module_opt(Host, ?MODULE, welcome_message,
|
||||
fun({S, B}) ->
|
||||
fun(Opts) ->
|
||||
S = proplists:get_value(
|
||||
subject, Opts, <<>>),
|
||||
B = proplists:get_value(
|
||||
body, Opts, <<>>),
|
||||
{iolist_to_binary(S),
|
||||
iolist_to_binary(B)}
|
||||
end, {<<"">>, <<"">>})
|
||||
@@ -483,7 +488,7 @@ check_from(JID, Server) ->
|
||||
|
||||
check_timeout(undefined) -> true;
|
||||
check_timeout(Source) ->
|
||||
Timeout = ejabberd_config:get_local_option(
|
||||
Timeout = ejabberd_config:get_option(
|
||||
registration_timeout,
|
||||
fun(TO) when is_integer(TO), TO > 0 ->
|
||||
TO;
|
||||
@@ -537,7 +542,7 @@ clean_treap(Treap, CleanPriority) ->
|
||||
|
||||
remove_timeout(undefined) -> true;
|
||||
remove_timeout(Source) ->
|
||||
Timeout = ejabberd_config:get_local_option(
|
||||
Timeout = ejabberd_config:get_option(
|
||||
registration_timeout,
|
||||
fun(TO) when is_integer(TO), TO > 0 ->
|
||||
TO;
|
||||
@@ -604,6 +609,54 @@ is_strong_password(Server, Password) ->
|
||||
ejabberd_auth:entropy(Password) >= Entropy
|
||||
end.
|
||||
|
||||
transform_options(Opts) ->
|
||||
Opts1 = transform_ip_access(Opts),
|
||||
transform_module_options(Opts1).
|
||||
|
||||
transform_ip_access(Opts) ->
|
||||
try
|
||||
{value, {modules, ModOpts}, Opts1} = lists:keytake(modules, 1, Opts),
|
||||
{value, {?MODULE, RegOpts}, ModOpts1} = lists:keytake(?MODULE, 1, ModOpts),
|
||||
{value, {ip_access, L}, RegOpts1} = lists:keytake(ip_access, 1, RegOpts),
|
||||
true = is_list(L),
|
||||
?WARNING_MSG("Old 'ip_access' format detected. "
|
||||
"The old format is still supported "
|
||||
"but it is better to fix your config: "
|
||||
"use access rules instead.", []),
|
||||
ACLs = lists:flatmap(
|
||||
fun({Action, S}) ->
|
||||
ACLName = jlib:binary_to_atom(
|
||||
iolist_to_binary(
|
||||
["ip_", S])),
|
||||
[{Action, ACLName},
|
||||
{acl, ACLName, {ip, S}}]
|
||||
end, L),
|
||||
Access = {access, mod_register_networks,
|
||||
[{Action, ACLName} || {Action, ACLName} <- ACLs]},
|
||||
[ACL || {acl, _, _} = ACL <- ACLs] ++
|
||||
[Access,
|
||||
{modules,
|
||||
[{mod_register,
|
||||
[{ip_access, mod_register_networks}|RegOpts1]}
|
||||
| ModOpts1]}|Opts1]
|
||||
catch error:{badmatch, false} ->
|
||||
Opts
|
||||
end.
|
||||
|
||||
transform_module_options(Opts) ->
|
||||
lists:flatmap(
|
||||
fun({welcome_message, {Subj, Body}}) ->
|
||||
?WARNING_MSG("Old 'welcome_message' format detected. "
|
||||
"The old format is still supported "
|
||||
"but it is better to fix your config: "
|
||||
"change it to {welcome_message, "
|
||||
"[{subject, Subject}, {body, Body}]}",
|
||||
[]),
|
||||
[{welcome_message, [{subject, Subj}, {body, Body}]}];
|
||||
(Opt) ->
|
||||
[Opt]
|
||||
end, Opts).
|
||||
|
||||
%%%
|
||||
%%% ip_access management
|
||||
%%%
|
||||
@@ -614,75 +667,15 @@ may_remove_resource(From) -> From.
|
||||
|
||||
get_ip_access(Host) ->
|
||||
gen_mod:get_module_opt(Host, ?MODULE, ip_access,
|
||||
fun(IPAccess) ->
|
||||
lists:flatmap(
|
||||
fun({Access, S}) ->
|
||||
{ok, IP, Mask} =
|
||||
parse_ip_netmask(
|
||||
iolist_to_binary(S)),
|
||||
[{Access, IP, Mask}]
|
||||
end, IPAccess)
|
||||
end, []).
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
all).
|
||||
|
||||
parse_ip_netmask(S) ->
|
||||
case str:tokens(S, <<"/">>) of
|
||||
[IPStr] ->
|
||||
case inet_parse:address(binary_to_list(IPStr)) of
|
||||
{ok, {_, _, _, _} = IP} -> {ok, IP, 32};
|
||||
{ok, {_, _, _, _, _, _, _, _} = IP} -> {ok, IP, 128};
|
||||
_ -> error
|
||||
end;
|
||||
[IPStr, MaskStr] ->
|
||||
case catch jlib:binary_to_integer(MaskStr) of
|
||||
Mask when is_integer(Mask), Mask >= 0 ->
|
||||
case inet_parse:address(binary_to_list(IPStr)) of
|
||||
{ok, {_, _, _, _} = IP} when Mask =< 32 ->
|
||||
{ok, IP, Mask};
|
||||
{ok, {_, _, _, _, _, _, _, _} = IP} when Mask =< 128 ->
|
||||
{ok, IP, Mask};
|
||||
_ -> error
|
||||
end;
|
||||
_ -> error
|
||||
end;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
check_ip_access(_Source, []) -> allow;
|
||||
check_ip_access({User, Server, Resource}, IPAccess) ->
|
||||
case ejabberd_sm:get_user_ip(User, Server, Resource) of
|
||||
{IPAddress, _PortNumber} ->
|
||||
check_ip_access(IPAddress, IPAccess);
|
||||
_ -> true
|
||||
{IPAddress, _PortNumber} ->
|
||||
check_ip_access(IPAddress, IPAccess);
|
||||
_ ->
|
||||
deny
|
||||
end;
|
||||
check_ip_access({_, _, _, _} = IP,
|
||||
[{Access, {_, _, _, _} = Net, Mask} | IPAccess]) ->
|
||||
IPInt = ip_to_integer(IP),
|
||||
NetInt = ip_to_integer(Net),
|
||||
M = bnot (1 bsl (32 - Mask) - 1),
|
||||
if IPInt band M =:= NetInt band M -> Access;
|
||||
true -> check_ip_access(IP, IPAccess)
|
||||
end;
|
||||
check_ip_access({_, _, _, _, _, _, _, _} = IP,
|
||||
[{Access, {_, _, _, _, _, _, _, _} = Net, Mask}
|
||||
| IPAccess]) ->
|
||||
IPInt = ip_to_integer(IP),
|
||||
NetInt = ip_to_integer(Net),
|
||||
M = bnot (1 bsl (128 - Mask) - 1),
|
||||
if IPInt band M =:= NetInt band M -> Access;
|
||||
true -> check_ip_access(IP, IPAccess)
|
||||
end;
|
||||
check_ip_access(IP, [_ | IPAccess]) ->
|
||||
check_ip_access(IP, IPAccess).
|
||||
|
||||
ip_to_integer({IP1, IP2, IP3, IP4}) ->
|
||||
IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4;
|
||||
ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7,
|
||||
IP8}) ->
|
||||
IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16
|
||||
bor IP5
|
||||
bsl 16
|
||||
bor IP6
|
||||
bsl 16
|
||||
bor IP7
|
||||
bsl 16
|
||||
bor IP8.
|
||||
check_ip_access(IPAddress, IPAccess) ->
|
||||
acl:match_rule(global, IPAccess, IPAddress).
|
||||
|
||||
+29
-3
@@ -39,8 +39,8 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, process_iq/3, export/1,
|
||||
process_local_iq/3, get_user_roster/2,
|
||||
-export([start/2, stop/1, process_iq/3, export/1, import/1,
|
||||
process_local_iq/3, get_user_roster/2, import/3,
|
||||
get_subscription_lists/3, get_roster/2,
|
||||
get_in_pending_subscriptions/3, in_subscription/6,
|
||||
out_subscription/4, set_items/3, remove_user/2,
|
||||
@@ -196,7 +196,7 @@ read_roster_version(LUser, LServer, mnesia) ->
|
||||
[#roster_version{version = V}] -> V;
|
||||
[] -> error
|
||||
end;
|
||||
read_roster_version(LServer, LUser, odbc) ->
|
||||
read_roster_version(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case odbc_queries:get_roster_version(LServer, Username)
|
||||
of
|
||||
@@ -1569,3 +1569,29 @@ export(_Server) ->
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select username, jid, nick, subscription, "
|
||||
"ask, askmessage, server, subscribe, type from rosterusers;">>,
|
||||
fun([LUser, JID|_] = Row) ->
|
||||
Item = raw_to_record(LServer, Row),
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(JID),
|
||||
{selected, _, Rows} =
|
||||
ejabberd_odbc:sql_query_t(
|
||||
[<<"select grp from rostergroups where username='">>,
|
||||
Username, <<"' and jid='">>, SJID, <<"'">>]),
|
||||
Groups = [Grp || [Grp] <- Rows],
|
||||
Item#roster{groups = Groups}
|
||||
end},
|
||||
{<<"select username, version from roster_version;">>,
|
||||
fun([LUser, Ver]) ->
|
||||
#roster_version{us = {LUser, LServer}, version = Ver}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #roster{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_LServer, mnesia, #roster_version{} = RV) ->
|
||||
mnesia:dirty_write(RV);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, item_to_xml/1, export/1,
|
||||
-export([start/2, stop/1, item_to_xml/1, export/1, import/1,
|
||||
webadmin_menu/3, webadmin_page/3, get_user_roster/2,
|
||||
get_subscription_lists/3, get_jid_info/4,
|
||||
get_subscription_lists/3, get_jid_info/4, import/3,
|
||||
process_item/2, in_subscription/6, out_subscription/4,
|
||||
user_available/1, unset_presence/4, register_user/2,
|
||||
remove_user/2, list_groups/1, create_group/2,
|
||||
@@ -1334,3 +1334,22 @@ export(_Server) ->
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select name, opts from sr_group;">>,
|
||||
fun([Group, SOpts]) ->
|
||||
#sr_group{group_host = {Group, LServer},
|
||||
opts = ejabberd_odbc:decode_term(SOpts)}
|
||||
end},
|
||||
{<<"select jid, grp from sr_user;">>,
|
||||
fun([SJID, Group]) ->
|
||||
#jid{luser = U, lserver = S} = jlib:string_to_jid(SJID),
|
||||
#sr_user{us = {U, S}, group_host = {Group, LServer}}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #sr_group{} = G) ->
|
||||
mnesia:dirty_write(G);
|
||||
import(_LServer, mnesia, #sr_user{} = U) ->
|
||||
mnesia:dirty_write(U);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+37
-1
@@ -32,7 +32,7 @@
|
||||
|
||||
-export([start/2, init/3, stop/1, get_sm_features/5,
|
||||
process_local_iq/3, process_sm_iq/3, reindex_vcards/0,
|
||||
remove_user/2, export/1]).
|
||||
remove_user/2, export/1, import/1, import/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -1006,3 +1006,39 @@ export(_Server) ->
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select username, vcard from vcard;">>,
|
||||
fun([LUser, SVCard]) ->
|
||||
#xmlel{} = VCARD = xml_stream:parse_element(SVCard),
|
||||
#vcard{us = {LUser, LServer}, vcard = VCARD}
|
||||
end},
|
||||
{<<"select username, lusername, fn, lfn, family, lfamily, "
|
||||
"given, lgiven, middle, lmiddle, nickname, lnickname, "
|
||||
"bday, lbday, ctry, lctry, locality, llocality, email, "
|
||||
"lemail, orgname, lorgname, orgunit, lorgunit from vcard_search;">>,
|
||||
fun([User, LUser, FN, LFN,
|
||||
Family, LFamily, Given, LGiven,
|
||||
Middle, LMiddle, Nickname, LNickname,
|
||||
BDay, LBDay, CTRY, LCTRY, Locality, LLocality,
|
||||
EMail, LEMail, OrgName, LOrgName, OrgUnit, LOrgUnit]) ->
|
||||
#vcard_search{us = {LUser, LServer},
|
||||
user = {User, LServer}, luser = LUser,
|
||||
fn = FN, lfn = LFN, family = Family,
|
||||
lfamily = LFamily, given = Given,
|
||||
lgiven = LGiven, middle = Middle,
|
||||
lmiddle = LMiddle, nickname = Nickname,
|
||||
lnickname = LNickname, bday = BDay,
|
||||
lbday = LBDay, ctry = CTRY, lctry = LCTRY,
|
||||
locality = Locality, llocality = LLocality,
|
||||
email = EMail, lemail = LEMail,
|
||||
orgname = OrgName, lorgname = LOrgName,
|
||||
orgunit = OrgUnit, lorgunit = LOrgUnit}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #vcard{} = VCard) ->
|
||||
mnesia:dirty_write(VCard);
|
||||
import(_LServer, mnesia, #vcard_search{} = S) ->
|
||||
mnesia:dirty_write(S);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+16
-2
@@ -38,7 +38,7 @@
|
||||
|
||||
-export([start/2, start_link/2, stop/1,
|
||||
get_sm_features/5, process_local_iq/3, process_sm_iq/3,
|
||||
remove_user/1, route/4]).
|
||||
remove_user/1, route/4, transform_module_options/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -767,7 +767,7 @@ parse_options(Host, Opts) ->
|
||||
VCardMap = gen_mod:get_opt(ldap_vcard_map, Opts,
|
||||
fun(Ls) ->
|
||||
lists:map(
|
||||
fun({S, P, L}) ->
|
||||
fun({S, [{P, L}]}) ->
|
||||
{iolist_to_binary(S),
|
||||
iolist_to_binary(P),
|
||||
[iolist_to_binary(E)
|
||||
@@ -823,6 +823,20 @@ parse_options(Host, Opts) ->
|
||||
search_reported_attrs = SearchReportedAttrs,
|
||||
matches = Matches}.
|
||||
|
||||
transform_module_options(Opts) ->
|
||||
lists:map(
|
||||
fun({ldap_vcard_map, Map}) ->
|
||||
NewMap = lists:map(
|
||||
fun({Field, Pattern, Attrs}) ->
|
||||
{Field, [{Pattern, Attrs}]};
|
||||
(Opt) ->
|
||||
Opt
|
||||
end, Map),
|
||||
{ldap_vcard_map, NewMap};
|
||||
(Opt) ->
|
||||
Opt
|
||||
end, Opts).
|
||||
|
||||
check_filter(F) ->
|
||||
NewF = iolist_to_binary(F),
|
||||
{ok, _} = eldap_filter:parse(NewF),
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
%% hooks
|
||||
-export([update_presence/3, vcard_set/3, export/1]).
|
||||
-export([update_presence/3, vcard_set/3, export/1, import/1, import/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -203,3 +203,14 @@ export(_Server) ->
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer) ->
|
||||
[{<<"select username, hash from vcard_xupdate;">>,
|
||||
fun([LUser, Hash]) ->
|
||||
#vcard_xupdate{us = {LUser, LServer}, hash = Hash}
|
||||
end}].
|
||||
|
||||
import(_LServer, mnesia, #vcard_xupdate{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+11
-33
@@ -383,7 +383,7 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
|
||||
{Affiliation, Subscriptions} =
|
||||
select_affiliation_subscriptions(NodeId, SubKey),
|
||||
SubIdExists = case SubId of
|
||||
[] -> false;
|
||||
<<>> -> false;
|
||||
List when is_binary(List) -> true;
|
||||
_ -> false
|
||||
end,
|
||||
@@ -788,15 +788,8 @@ get_entity_subscriptions(Host, Owner) ->
|
||||
case decode_subscriptions(S) of
|
||||
[] -> [{Node, none, Jid} | Acc];
|
||||
Subs ->
|
||||
lists:foldl(fun ({Sub, SubId},
|
||||
Acc2) ->
|
||||
[{Node, Sub,
|
||||
SubId, Jid}
|
||||
| Acc2];
|
||||
(Sub, Acc2) ->
|
||||
[{Node, Sub,
|
||||
Jid}
|
||||
| Acc2]
|
||||
lists:foldl(fun ({Sub, SubId}, Acc2) ->
|
||||
[{Node, Sub, SubId, Jid} | Acc2]
|
||||
end,
|
||||
Acc, Subs)
|
||||
end
|
||||
@@ -1253,7 +1246,7 @@ get_items(NodeId, From, none) ->
|
||||
get_items(NodeId, _From,
|
||||
#rsm_in{max = M, direction = Direction, id = I,
|
||||
index = IncIndex}) ->
|
||||
Max = (?PUBSUB):escape(i2l(M)),
|
||||
Max = (?PUBSUB):escape(jlib:i2l(M)),
|
||||
{Way, Order} = case Direction of
|
||||
aft -> {<<"<">>, <<"desc">>};
|
||||
before when I == <<>> -> {<<"is not">>, <<"asc">>};
|
||||
@@ -1271,7 +1264,7 @@ get_items(NodeId, _From,
|
||||
NodeId,
|
||||
<<"' and modification > pi.modification "
|
||||
"having count1 = ">>,
|
||||
(?PUBSUB):escape(i2l(IncIndex)),
|
||||
(?PUBSUB):escape(jlib:i2l(IncIndex)),
|
||||
<<" );">>])
|
||||
of
|
||||
{selected, [_], [[O]]} ->
|
||||
@@ -1281,7 +1274,7 @@ get_items(NodeId, _From,
|
||||
undefined -> [<<"modification">>, <<"null">>];
|
||||
<<>> -> [<<"modification">>, <<"null">>];
|
||||
I ->
|
||||
[A, B] = str:tokens((?PUBSUB):escape(i2l(I)),
|
||||
[A, B] = str:tokens((?PUBSUB):escape(jlib:i2l(I)),
|
||||
<<"@">>),
|
||||
[A, <<"'", B/binary, "'">>]
|
||||
end,
|
||||
@@ -1300,7 +1293,7 @@ get_items(NodeId, _From,
|
||||
NodeId, <<"' and ">>, AttrName, <<" ">>,
|
||||
Way, <<" ">>, Id, <<" order by ">>,
|
||||
AttrName, <<" ">>, Order, <<" limit ">>,
|
||||
i2l(Max), <<" ;">>])
|
||||
jlib:i2l(Max), <<" ;">>])
|
||||
of
|
||||
{selected,
|
||||
[<<"itemid">>, <<"publisher">>, <<"creation">>,
|
||||
@@ -1322,7 +1315,7 @@ get_items(NodeId, _From,
|
||||
[_, _, _, L, _] = lists:last(RItems),
|
||||
RsmOut = #rsm_out{count = Count, index = Index,
|
||||
first = <<"modification@", F/binary>>,
|
||||
last = <<"modification@", (i2l(L))/binary>>},
|
||||
last = <<"modification@", (jlib:i2l(L))/binary>>},
|
||||
{result, {[raw_to_item(NodeId, RItem) || RItem <- RItems], RsmOut}};
|
||||
0 -> {result, {[], #rsm_out{count = Count}}}
|
||||
end;
|
||||
@@ -1377,7 +1370,7 @@ get_last_items(NodeId, _From, Count) ->
|
||||
"where nodeid='">>,
|
||||
NodeId,
|
||||
<<"' order by modification desc limit ">>,
|
||||
i2l(Count), <<";">>])
|
||||
jlib:i2l(Count), <<";">>])
|
||||
of
|
||||
{selected,
|
||||
[<<"itemid">>, <<"publisher">>, <<"creation">>,
|
||||
@@ -1459,7 +1452,7 @@ set_item(Item) ->
|
||||
Payload = Item#pubsub_item.payload,
|
||||
XML = (?PUBSUB):escape(str:join([xml:element_to_binary(X) || X<-Payload], <<>>)),
|
||||
S = fun ({T1, T2, T3}) ->
|
||||
str:join([i2l(T1, 6), i2l(T2, 6), i2l(T3, 6)], <<":">>)
|
||||
str:join([jlib:i2l(T1, 6), jlib:i2l(T2, 6), jlib:i2l(T3, 6)], <<":">>)
|
||||
end,
|
||||
case catch
|
||||
ejabberd_odbc:sql_query_t([<<"update pubsub_item set publisher='">>,
|
||||
@@ -1666,7 +1659,7 @@ raw_to_item(NodeId,
|
||||
JID = decode_jid(SJID),
|
||||
ToTime = fun (Str) ->
|
||||
[T1, T2, T3] = str:tokens(Str, <<":">>),
|
||||
{l2i(T1), l2i(T2), l2i(T3)}
|
||||
{jlib:l2i(T1), jlib:l2i(T2), jlib:l2i(T3)}
|
||||
end,
|
||||
Payload = case xml_stream:parse_element(XML) of
|
||||
{error, _Reason} -> [];
|
||||
@@ -1676,18 +1669,3 @@ raw_to_item(NodeId,
|
||||
creation = {ToTime(Creation), JID},
|
||||
modification = {ToTime(Modification), JID},
|
||||
payload = Payload}.
|
||||
|
||||
l2i(I) when is_integer(I) -> I;
|
||||
l2i(L) when is_binary(L) -> jlib:binary_to_integer(L).
|
||||
|
||||
i2l(I) when is_integer(I) ->
|
||||
iolist_to_binary(integer_to_list(I));
|
||||
i2l(L) when is_binary(L) -> L.
|
||||
|
||||
i2l(I, N) when is_integer(I) -> i2l(i2l(I), N);
|
||||
i2l(L, N) when is_binary(L) ->
|
||||
case str:len(L) of
|
||||
N -> L;
|
||||
C when C > N -> L;
|
||||
_ -> i2l(<<$0, L/binary>>, N)
|
||||
end.
|
||||
|
||||
+7
-11
@@ -144,12 +144,10 @@ create_node(NodeIdx, Owner) ->
|
||||
).
|
||||
|
||||
delete_node(Removed) ->
|
||||
{result, {_, _, Removed}} = node_hometree:delete_node(Removed),
|
||||
{result, {[], Removed}}.
|
||||
% case node_hometree:delete_node(Removed) of
|
||||
% {result, {_, _, Removed}} -> {result, {[], Removed}};
|
||||
% Error -> Error
|
||||
% end.
|
||||
case node_hometree:delete_node(Removed) of
|
||||
{result, {_, _, Result}} -> {result, {[], Result}};
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
-spec(subscribe_node/8 ::
|
||||
(
|
||||
@@ -492,13 +490,11 @@ path_to_node(Path) -> node_flat:path_to_node(Path).
|
||||
%% Check that the mod_caps module is enabled in that Jabber Host
|
||||
%% If not, show a warning message in the ejabberd log file.
|
||||
complain_if_modcaps_disabled(ServerHost) ->
|
||||
Modules = ejabberd_config:get_local_option({modules, ServerHost}, fun(Ms) when is_list(Ms) -> Ms end),
|
||||
ModCaps = [mod_caps_enabled || {mod_caps, _Opts} <- Modules],
|
||||
case ModCaps of
|
||||
[] ->
|
||||
case gen_mod:is_loaded(ServerHost, mod_caps) of
|
||||
false ->
|
||||
?WARNING_MSG("The PEP plugin is enabled in mod_pubsub "
|
||||
"of host ~p. This plugin requires mod_caps "
|
||||
"to be enabled, but it isn't.",
|
||||
[ServerHost]);
|
||||
_ -> ok
|
||||
true -> ok
|
||||
end.
|
||||
|
||||
@@ -156,7 +156,7 @@ create_node(NodeIdx, Owner) ->
|
||||
).
|
||||
delete_node(Removed) ->
|
||||
case node_hometree_odbc:delete_node(Removed) of
|
||||
{result, {_, _, Removed}} -> {result, {[], Removed}};
|
||||
{result, {_, _, Result}} -> {result, {[], Result}};
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
@@ -433,7 +433,7 @@ path_to_node(Path) -> node_flat_odbc:path_to_node(Path).
|
||||
%% Check that the mod_caps module is enabled in that Jabber Host
|
||||
%% If not, show a warning message in the ejabberd log file.
|
||||
complain_if_modcaps_disabled(ServerHost) ->
|
||||
Modules = ejabberd_config:get_local_option({modules,
|
||||
Modules = ejabberd_config:get_option({modules,
|
||||
ServerHost},
|
||||
fun(Ms) when is_list(Ms) -> Ms end),
|
||||
ModCaps = [mod_caps_enabled
|
||||
|
||||
@@ -362,9 +362,13 @@ raw_to_node(Host, [Node, Parent, Type, NodeIdx]) ->
|
||||
StdOpts, DbOpts);
|
||||
_ -> []
|
||||
end,
|
||||
Parents = case Parent of
|
||||
<<>> -> [];
|
||||
_ -> [Parent]
|
||||
end,
|
||||
#pubsub_node{nodeid =
|
||||
{Host, Node},
|
||||
parents = [Parent],
|
||||
parents = Parents,
|
||||
id = NodeIdx, type = Type, options = Options}.
|
||||
|
||||
% @spec (NodeRecord) -> ok | {error, Reason}
|
||||
|
||||
+17
-6
@@ -45,7 +45,7 @@
|
||||
get_default_privacy_list_t/1, get_privacy_list_names/2,
|
||||
get_privacy_list_names_t/1, get_privacy_list_id/3,
|
||||
get_privacy_list_id_t/2, get_privacy_list_data/3,
|
||||
get_privacy_list_data_by_id/2,
|
||||
get_privacy_list_data_by_id/2, get_privacy_list_data_t/2,
|
||||
get_privacy_list_data_by_id_t/1,
|
||||
set_default_privacy_list/2,
|
||||
unset_default_privacy_list/2,
|
||||
@@ -219,13 +219,15 @@ list_users(LServer,
|
||||
[Prefix, Limit, Offset]))]).
|
||||
|
||||
users_number(LServer) ->
|
||||
case element(1,
|
||||
ejabberd_config:get_local_option(
|
||||
{odbc_server, LServer}, fun(V) -> V end))
|
||||
of
|
||||
Type = ejabberd_config:get_option({odbc_type, LServer},
|
||||
fun(pgsql) -> pgsql;
|
||||
(mysql) -> mysql;
|
||||
(odbc) -> odbc
|
||||
end, odbc),
|
||||
case Type of
|
||||
pgsql ->
|
||||
case
|
||||
ejabberd_config:get_local_option(
|
||||
ejabberd_config:get_option(
|
||||
{pgsql_users_number_estimate, LServer},
|
||||
fun(V) when is_boolean(V) -> V end,
|
||||
false)
|
||||
@@ -519,6 +521,15 @@ get_privacy_list_data(LServer, Username, SName) ->
|
||||
Username, <<"' and name='">>, SName,
|
||||
<<"') order by ord;">>]).
|
||||
|
||||
get_privacy_list_data_t(Username, SName) ->
|
||||
ejabberd_odbc:sql_query_t([<<"select t, value, action, ord, match_all, "
|
||||
"match_iq, match_message, match_presence_in, "
|
||||
"match_presence_out from privacy_list_data "
|
||||
"where id = (select id from privacy_list "
|
||||
"where username='">>,
|
||||
Username, <<"' and name='">>, SName,
|
||||
<<"') order by ord;">>]).
|
||||
|
||||
get_privacy_list_data_by_id(LServer, ID) ->
|
||||
ejabberd_odbc:sql_query(LServer,
|
||||
[<<"select t, value, action, ord, match_all, "
|
||||
|
||||
+1
-1
@@ -792,7 +792,7 @@ get_msg(Msg) -> Msg.
|
||||
%% Status information
|
||||
%%-----------------------------------------------------------------
|
||||
format_status(Opt, StatusData) ->
|
||||
[PDict, SysState, Parent, Debug, [Name, StateName, StateData, Mod, _Time]] =
|
||||
[PDict, SysState, Parent, Debug, [Name, StateName, StateData, Mod, _Time, _Limits, _Queue, _QueueLen]] =
|
||||
StatusData,
|
||||
NameTag = if is_pid(Name) ->
|
||||
pid_to_list(Name);
|
||||
|
||||
+60
-15
@@ -28,7 +28,8 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([new/1, new1/1, update/2]).
|
||||
-export([start/0, new/1, new1/1, update/2,
|
||||
transform_options/1, load_from_config/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -37,32 +38,66 @@
|
||||
lastrate = 0.0 :: float(),
|
||||
lasttime = 0 :: integer()}).
|
||||
|
||||
-type maxrate() :: none | #maxrate{}.
|
||||
-record(shaper, {name :: {atom(), global},
|
||||
maxrate :: integer()}).
|
||||
|
||||
-type shaper() :: maxrate() | {maxrate(), integer()}.
|
||||
-type shaper() :: none | #maxrate{}.
|
||||
|
||||
-export_type([shaper/0]).
|
||||
|
||||
-spec new(atom()) -> maxrate().
|
||||
-spec start() -> ok.
|
||||
|
||||
start() ->
|
||||
mnesia:create_table(shaper,
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, shaper)}]),
|
||||
mnesia:add_table_copy(shaper, node(), ram_copies),
|
||||
load_from_config(),
|
||||
ok.
|
||||
|
||||
-spec load_from_config() -> ok | {error, any()}.
|
||||
|
||||
load_from_config() ->
|
||||
Shapers = ejabberd_config:get_option(
|
||||
shaper, fun(V) -> V end, []),
|
||||
case mnesia:transaction(
|
||||
fun() ->
|
||||
lists:foreach(
|
||||
fun({Name, MaxRate}) ->
|
||||
mnesia:write(#shaper{name = {Name, global},
|
||||
maxrate = MaxRate})
|
||||
end, Shapers)
|
||||
end) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
Err ->
|
||||
{error, Err}
|
||||
end.
|
||||
|
||||
-spec new(atom()) -> shaper().
|
||||
|
||||
new(none) ->
|
||||
none;
|
||||
new(Name) ->
|
||||
Data = ejabberd_config:get_global_option(
|
||||
{shaper, Name, global},
|
||||
fun({maxrate, R}) when is_integer(R), R>0 ->
|
||||
{maxrate, R};
|
||||
(none) ->
|
||||
none
|
||||
end, none),
|
||||
new1(Data).
|
||||
MaxRate = case ets:lookup(shaper, {Name, global}) of
|
||||
[#shaper{maxrate = R}] ->
|
||||
R;
|
||||
[] ->
|
||||
?WARNING_MSG("Attempt to initialize an "
|
||||
"unspecified shaper '~s'", [Name]),
|
||||
none
|
||||
end,
|
||||
new1(MaxRate).
|
||||
|
||||
-spec new1(none | {maxrate, integer()}) -> maxrate().
|
||||
-spec new1(none | integer()) -> shaper().
|
||||
|
||||
new1(none) -> none;
|
||||
new1({maxrate, MaxRate}) ->
|
||||
new1(MaxRate) ->
|
||||
#maxrate{maxrate = MaxRate, lastrate = 0.0,
|
||||
lasttime = now_to_usec(now())}.
|
||||
|
||||
-spec update(maxrate(), integer()) -> {maxrate(), integer()}.
|
||||
-spec update(shaper(), integer()) -> {shaper(), integer()}.
|
||||
|
||||
update(none, _Size) -> {none, 0};
|
||||
update(#maxrate{} = State, Size) ->
|
||||
@@ -84,5 +119,15 @@ update(#maxrate{} = State, Size) ->
|
||||
lasttime = NextNow},
|
||||
Pause}.
|
||||
|
||||
transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
|
||||
transform_options({shaper, Name, {maxrate, N}}, Opts) ->
|
||||
[{shaper, [{Name, N}]}|Opts];
|
||||
transform_options({shaper, Name, none}, Opts) ->
|
||||
[{shaper, [{Name, none}]}|Opts];
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
now_to_usec({MSec, Sec, USec}) ->
|
||||
(MSec * 1000000 + Sec) * 1000000 + USec.
|
||||
|
||||
+138
-481
@@ -10,107 +10,38 @@
|
||||
|
||||
-compile(export_all).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include("xml.hrl").
|
||||
-include("ns.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("mod_proxy65.hrl").
|
||||
-include("xmpp_codec.hrl").
|
||||
-import(suite, [init_config/1, connect/1, disconnect/1,
|
||||
recv/0, send/2, send_recv/2, my_jid/1, server_jid/1,
|
||||
pubsub_jid/1, proxy_jid/1, muc_jid/1,
|
||||
muc_room_jid/1, get_features/2, re_register/1,
|
||||
is_feature_advertised/2, subscribe_to_events/1,
|
||||
is_feature_advertised/3, set_opt/3, auth_SASL/2,
|
||||
wait_for_master/1, wait_for_slave/1,
|
||||
make_iq_result/1, start_event_relay/0,
|
||||
stop_event_relay/1, put_event/2, get_event/1,
|
||||
bind/1, auth/1, open_session/1, zlib/1, starttls/1]).
|
||||
|
||||
-define(STREAM_HEADER,
|
||||
<<"<?xml version='1.0'?><stream:stream "
|
||||
"xmlns:stream='http://etherx.jabber.org/stream"
|
||||
"s' xmlns='jabber:client' to='~s' version='1.0"
|
||||
"'>">>).
|
||||
|
||||
-define(STREAM_TRAILER, <<"</stream:stream>">>).
|
||||
|
||||
-define(PUBSUB(Node), <<(?NS_PUBSUB)/binary, "#", Node>>).
|
||||
|
||||
-define(recv2(P1, P2),
|
||||
(fun() ->
|
||||
case {R1 = recv(), R2 = recv()} of
|
||||
{P1, P2} -> {R1, R2};
|
||||
{P2, P1} -> {R2, R1}
|
||||
end
|
||||
end)()).
|
||||
|
||||
-define(recv3(P1, P2, P3),
|
||||
(fun() ->
|
||||
case R3 = recv() of
|
||||
P1 -> insert(R3, 1, ?recv2(P2, P3));
|
||||
P2 -> insert(R3, 2, ?recv2(P1, P3));
|
||||
P3 -> insert(R3, 3, ?recv2(P1, P2))
|
||||
end
|
||||
end)()).
|
||||
|
||||
-define(recv4(P1, P2, P3, P4),
|
||||
(fun() ->
|
||||
case R4 = recv() of
|
||||
P1 -> insert(R4, 1, ?recv3(P2, P3, P4));
|
||||
P2 -> insert(R4, 2, ?recv3(P1, P3, P4));
|
||||
P3 -> insert(R4, 3, ?recv3(P1, P2, P4));
|
||||
P4 -> insert(R4, 4, ?recv3(P1, P2, P3))
|
||||
end
|
||||
end)()).
|
||||
|
||||
-define(recv5(P1, P2, P3, P4, P5),
|
||||
(fun() ->
|
||||
case R5 = recv() of
|
||||
P1 -> insert(R5, 1, ?recv4(P2, P3, P4, P5));
|
||||
P2 -> insert(R5, 2, ?recv4(P1, P3, P4, P5));
|
||||
P3 -> insert(R5, 3, ?recv4(P1, P2, P4, P5));
|
||||
P4 -> insert(R5, 4, ?recv4(P1, P2, P3, P5));
|
||||
P5 -> insert(R5, 5, ?recv4(P1, P2, P3, P4))
|
||||
end
|
||||
end)()).
|
||||
|
||||
-define(COMMON_VHOST, <<"localhost">>).
|
||||
-define(MNESIA_VHOST, <<"mnesia.localhost">>).
|
||||
-define(MYSQL_VHOST, <<"mysql.localhost">>).
|
||||
-define(PGSQL_VHOST, <<"pgsql.localhost">>).
|
||||
-define(LDAP_VHOST, <<"ldap.localhost">>).
|
||||
-include("suite.hrl").
|
||||
|
||||
suite() ->
|
||||
[{timetrap, {seconds,10}}].
|
||||
[{timetrap, {seconds,20}}].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
DataDir = proplists:get_value(data_dir, Config),
|
||||
PrivDir = proplists:get_value(priv_dir, Config),
|
||||
[_, _|Tail] = lists:reverse(filename:split(DataDir)),
|
||||
BaseDir = filename:join(lists:reverse(Tail)),
|
||||
ConfigPath = filename:join([DataDir, "ejabberd.cfg"]),
|
||||
LogPath = filename:join([PrivDir, "ejabberd.log"]),
|
||||
SASLPath = filename:join([PrivDir, "sasl.log"]),
|
||||
MnesiaDir = filename:join([PrivDir, "mnesia"]),
|
||||
CertFile = filename:join([DataDir, "cert.pem"]),
|
||||
LDIFFile = filename:join([DataDir, "ejabberd.ldif"]),
|
||||
NewConfig = init_config(Config),
|
||||
DataDir = proplists:get_value(data_dir, NewConfig),
|
||||
{ok, CWD} = file:get_cwd(),
|
||||
{ok, _} = file:copy(CertFile, filename:join([CWD, "cert.pem"])),
|
||||
application:set_env(ejabberd, config, ConfigPath),
|
||||
application:set_env(ejabberd, log_path, LogPath),
|
||||
application:set_env(sasl, sasl_error_logger, {file, SASLPath}),
|
||||
application:set_env(mnesia, dir, MnesiaDir),
|
||||
ExtAuthScript = filename:join([DataDir, "extauth.py"]),
|
||||
LDIFFile = filename:join([DataDir, "ejabberd.ldif"]),
|
||||
{ok, _} = file:copy(ExtAuthScript, filename:join([CWD, "extauth.py"])),
|
||||
{ok, _} = ldap_srv:start(LDIFFile),
|
||||
ok = application:start(ejabberd),
|
||||
[{server_port, 5222},
|
||||
{server_host, "localhost"},
|
||||
{server, ?COMMON_VHOST},
|
||||
{user, <<"test_single">>},
|
||||
{certfile, CertFile},
|
||||
{base_dir, BaseDir},
|
||||
{ldif_file, LDIFFile},
|
||||
{resource, <<"resource">>},
|
||||
{password, <<"password">>}
|
||||
|Config].
|
||||
NewConfig.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
ok.
|
||||
|
||||
init_per_group(no_db, Config) ->
|
||||
User = ?config(user, Config),
|
||||
Server = ?config(server, Config),
|
||||
Password = ?config(password, Config),
|
||||
{atomic, ok} = ejabberd_auth:try_register(User, Server, Password),
|
||||
re_register(Config),
|
||||
Config;
|
||||
init_per_group(mnesia, Config) ->
|
||||
mod_muc:shutdown_rooms(?MNESIA_VHOST),
|
||||
@@ -134,8 +65,9 @@ init_per_group(pgsql, Config) ->
|
||||
{skip, {pgsql_not_available, Err}}
|
||||
end;
|
||||
init_per_group(ldap, Config) ->
|
||||
{ok, _} = ldap_srv:start(?config(ldif_file, Config)),
|
||||
set_opt(server, ?LDAP_VHOST, Config);
|
||||
init_per_group(extauth, Config) ->
|
||||
set_opt(server, ?EXTAUTH_VHOST, Config);
|
||||
init_per_group(_GroupName, Config) ->
|
||||
Pid = start_event_relay(),
|
||||
set_opt(event_relay, Pid, Config).
|
||||
@@ -150,6 +82,8 @@ end_per_group(no_db, _Config) ->
|
||||
ok;
|
||||
end_per_group(ldap, _Config) ->
|
||||
ok;
|
||||
end_per_group(extauth, _Config) ->
|
||||
ok;
|
||||
end_per_group(_GroupName, Config) ->
|
||||
stop_event_relay(Config),
|
||||
ok.
|
||||
@@ -227,6 +161,7 @@ db_tests() ->
|
||||
presence_broadcast,
|
||||
last,
|
||||
roster_get,
|
||||
roster_ver,
|
||||
private,
|
||||
privacy,
|
||||
blocking,
|
||||
@@ -248,8 +183,14 @@ ldap_tests() ->
|
||||
[test_auth,
|
||||
vcard_get]}].
|
||||
|
||||
extauth_tests() ->
|
||||
[{extauth_tests, [sequence],
|
||||
[test_auth,
|
||||
test_unregister]}].
|
||||
|
||||
groups() ->
|
||||
[{ldap, [sequence], ldap_tests()},
|
||||
{extauth, [sequence], extauth_tests()},
|
||||
{no_db, [sequence], no_db_tests()},
|
||||
{mnesia, [sequence], db_tests()},
|
||||
{mysql, [sequence], db_tests()},
|
||||
@@ -261,6 +202,7 @@ all() ->
|
||||
{group, mnesia},
|
||||
{group, mysql},
|
||||
{group, pgsql},
|
||||
{group, extauth},
|
||||
stop_ejabberd].
|
||||
|
||||
stop_ejabberd(Config) ->
|
||||
@@ -272,44 +214,6 @@ stop_ejabberd(Config) ->
|
||||
test_connect(Config) ->
|
||||
disconnect(connect(Config)).
|
||||
|
||||
connect(Config) ->
|
||||
{ok, Sock} = ejabberd_socket:connect(
|
||||
?config(server_host, Config),
|
||||
?config(server_port, Config),
|
||||
[binary, {packet, 0}, {active, false}]),
|
||||
init_stream(set_opt(socket, Sock, Config)).
|
||||
|
||||
init_stream(Config) ->
|
||||
ok = send_text(Config, io_lib:format(?STREAM_HEADER,
|
||||
[?config(server, Config)])),
|
||||
{xmlstreamstart, <<"stream:stream">>, Attrs} = recv(),
|
||||
<<"jabber:client">> = xml:get_attr_s(<<"xmlns">>, Attrs),
|
||||
<<"1.0">> = xml:get_attr_s(<<"version">>, Attrs),
|
||||
#stream_features{sub_els = Fs} = recv(),
|
||||
Mechs = lists:flatmap(
|
||||
fun(#sasl_mechanisms{list = Ms}) ->
|
||||
Ms;
|
||||
(_) ->
|
||||
[]
|
||||
end, Fs),
|
||||
lists:foldl(
|
||||
fun(#feature_register{}, Acc) ->
|
||||
set_opt(register, true, Acc);
|
||||
(#starttls{}, Acc) ->
|
||||
set_opt(starttls, true, Acc);
|
||||
(#compression{methods = Ms}, Acc) ->
|
||||
set_opt(compression, Ms, Acc);
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, set_opt(mechs, Mechs, Config), Fs).
|
||||
|
||||
disconnect(Config) ->
|
||||
Socket = ?config(socket, Config),
|
||||
ok = ejabberd_socket:send(Socket, ?STREAM_TRAILER),
|
||||
{xmlstreamend, <<"stream:stream">>} = recv(),
|
||||
ejabberd_socket:close(Socket),
|
||||
Config.
|
||||
|
||||
test_starttls(Config) ->
|
||||
case ?config(starttls, Config) of
|
||||
true ->
|
||||
@@ -318,15 +222,6 @@ test_starttls(Config) ->
|
||||
{skipped, 'starttls_not_available'}
|
||||
end.
|
||||
|
||||
starttls(Config) ->
|
||||
send(Config, #starttls{}),
|
||||
#starttls_proceed{} = recv(),
|
||||
TLSSocket = ejabberd_socket:starttls(
|
||||
?config(socket, Config),
|
||||
[{certfile, ?config(certfile, Config)},
|
||||
connect]),
|
||||
init_stream(set_opt(socket, TLSSocket, Config)).
|
||||
|
||||
test_zlib(Config) ->
|
||||
case ?config(compression, Config) of
|
||||
[_|_] = Ms ->
|
||||
@@ -340,12 +235,6 @@ test_zlib(Config) ->
|
||||
{skipped, 'compression_not_available'}
|
||||
end.
|
||||
|
||||
zlib(Config) ->
|
||||
send(Config, #compress{methods = [<<"zlib">>]}),
|
||||
#compressed{} = recv(),
|
||||
ZlibSocket = ejabberd_socket:compress(?config(socket, Config)),
|
||||
init_stream(set_opt(socket, ZlibSocket, Config)).
|
||||
|
||||
test_register(Config) ->
|
||||
case ?config(register, Config) of
|
||||
true ->
|
||||
@@ -386,45 +275,63 @@ try_unregister(Config) ->
|
||||
#stream_error{reason = conflict} = recv(),
|
||||
Config.
|
||||
|
||||
auth_md5(Config) ->
|
||||
Mechs = ?config(mechs, Config),
|
||||
case lists:member(<<"DIGEST-MD5">>, Mechs) of
|
||||
true ->
|
||||
disconnect(auth_SASL(<<"DIGEST-MD5">>, Config));
|
||||
false ->
|
||||
disconnect(Config),
|
||||
{skipped, 'DIGEST-MD5_not_available'}
|
||||
end.
|
||||
|
||||
auth_plain(Config) ->
|
||||
Mechs = ?config(mechs, Config),
|
||||
case lists:member(<<"PLAIN">>, Mechs) of
|
||||
true ->
|
||||
disconnect(auth_SASL(<<"PLAIN">>, Config));
|
||||
false ->
|
||||
disconnect(Config),
|
||||
{skipped, 'PLAIN_not_available'}
|
||||
end.
|
||||
|
||||
test_auth(Config) ->
|
||||
disconnect(auth(Config)).
|
||||
|
||||
auth(Config) ->
|
||||
Mechs = ?config(mechs, Config),
|
||||
HaveMD5 = lists:member(<<"DIGEST-MD5">>, Mechs),
|
||||
HavePLAIN = lists:member(<<"PLAIN">>, Mechs),
|
||||
if HavePLAIN ->
|
||||
auth_SASL(<<"PLAIN">>, Config);
|
||||
HaveMD5 ->
|
||||
auth_SASL(<<"DIGEST-MD5">>, Config);
|
||||
true ->
|
||||
ct:fail(no_sasl_mechanisms_available)
|
||||
end.
|
||||
|
||||
test_bind(Config) ->
|
||||
disconnect(bind(Config)).
|
||||
|
||||
bind(Config) ->
|
||||
#iq{type = result, sub_els = [#bind{}]} =
|
||||
send_recv(
|
||||
Config,
|
||||
#iq{type = set,
|
||||
sub_els = [#bind{resource = ?config(resource, Config)}]}),
|
||||
Config.
|
||||
|
||||
test_open_session(Config) ->
|
||||
disconnect(open_session(Config)).
|
||||
|
||||
open_session(Config) ->
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(Config, #iq{type = set, sub_els = [#session{}]}),
|
||||
Config.
|
||||
|
||||
roster_get(Config) ->
|
||||
#iq{type = result, sub_els = [#roster{items = []}]} =
|
||||
send_recv(Config, #iq{type = get, sub_els = [#roster{}]}),
|
||||
disconnect(Config).
|
||||
|
||||
roster_ver(Config) ->
|
||||
%% Get initial "ver"
|
||||
#iq{type = result, sub_els = [#roster{ver = Ver1, items = []}]} =
|
||||
send_recv(Config, #iq{type = get,
|
||||
sub_els = [#roster{ver = <<"">>}]}),
|
||||
%% Should receive empty IQ-result
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(Config, #iq{type = get,
|
||||
sub_els = [#roster{ver = Ver1}]}),
|
||||
%% Attempting to subscribe to server's JID
|
||||
send(Config, #presence{type = subscribe, to = server_jid(Config)}),
|
||||
%% Receive a single roster push with the new "ver"
|
||||
#iq{type = set, sub_els = [#roster{ver = Ver2}]} = recv(),
|
||||
%% Requesting roster with the previous "ver". Should receive Ver2 again
|
||||
#iq{type = result, sub_els = [#roster{ver = Ver2}]} =
|
||||
send_recv(Config, #iq{type = get,
|
||||
sub_els = [#roster{ver = Ver1}]}),
|
||||
%% Now requesting roster with the newest "ver". Should receive empty IQ.
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(Config, #iq{type = get,
|
||||
sub_els = [#roster{ver = Ver2}]}),
|
||||
disconnect(Config).
|
||||
|
||||
presence(Config) ->
|
||||
send(Config, #presence{}),
|
||||
JID = my_jid(Config),
|
||||
@@ -486,19 +393,22 @@ private(Config) ->
|
||||
<<"some.conference.org">>,
|
||||
<<>>)},
|
||||
Storage = #bookmark_storage{conference = [Conference]},
|
||||
StorageXMLOut = xmpp_codec:encode(Storage),
|
||||
#iq{type = error} =
|
||||
send_recv(Config, #iq{type = get, sub_els = [#private{}],
|
||||
to = server_jid(Config)}),
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(
|
||||
Config, #iq{type = set,
|
||||
sub_els = [#private{sub_els = [Storage]}]}),
|
||||
sub_els = [#private{xml_els = [StorageXMLOut]}]}),
|
||||
#iq{type = result,
|
||||
sub_els = [#private{sub_els = [Storage]}]} =
|
||||
sub_els = [#private{xml_els = [StorageXMLIn]}]} =
|
||||
send_recv(
|
||||
Config,
|
||||
#iq{type = get,
|
||||
sub_els = [#private{sub_els = [#bookmark_storage{}]}]}),
|
||||
sub_els = [#private{xml_els = [xmpp_codec:encode(
|
||||
#bookmark_storage{})]}]}),
|
||||
Storage = xmpp_codec:decode(StorageXMLIn),
|
||||
disconnect(Config).
|
||||
|
||||
last(Config) ->
|
||||
@@ -653,26 +563,33 @@ stats(Config) ->
|
||||
disconnect(Config).
|
||||
|
||||
pubsub(Config) ->
|
||||
true = is_feature_advertised(Config, ?NS_PUBSUB),
|
||||
Features = get_features(Config, pubsub_jid(Config)),
|
||||
true = lists:member(?NS_PUBSUB, Features),
|
||||
%% Publish <presence/> element within node "presence"
|
||||
ItemID = randoms:get_string(),
|
||||
Node = <<"presence">>,
|
||||
Item = #pubsub_item{id = ItemID, sub_els = [#presence{}]},
|
||||
Item = #pubsub_item{id = ItemID,
|
||||
xml_els = [xmpp_codec:encode(#presence{})]},
|
||||
#iq{type = result,
|
||||
sub_els = [#pubsub{publish = {<<"presence">>,
|
||||
[#pubsub_item{id = ItemID}]}}]} =
|
||||
sub_els = [#pubsub{publish = #pubsub_publish{
|
||||
node = Node,
|
||||
items = [#pubsub_item{id = ItemID}]}}]} =
|
||||
send_recv(Config,
|
||||
#iq{type = set, to = pubsub_jid(Config),
|
||||
sub_els = [#pubsub{publish = {Node, [Item]}}]}),
|
||||
sub_els = [#pubsub{publish = #pubsub_publish{
|
||||
node = Node,
|
||||
items = [Item]}}]}),
|
||||
%% Subscribe to node "presence"
|
||||
I = send(Config,
|
||||
I1 = send(Config,
|
||||
#iq{type = set, to = pubsub_jid(Config),
|
||||
sub_els = [#pubsub{subscribe = {Node, my_jid(Config)}}]}),
|
||||
sub_els = [#pubsub{subscribe = #pubsub_subscribe{
|
||||
node = Node,
|
||||
jid = my_jid(Config)}}]}),
|
||||
?recv2(
|
||||
#message{sub_els = [#pubsub_event{}, #delay{}]},
|
||||
#iq{type = result, id = I}),
|
||||
#iq{type = result, id = I1}),
|
||||
%% Get subscriptions
|
||||
true = is_feature_advertised(Config, ?PUBSUB("retrieve-subscriptions")),
|
||||
true = lists:member(?PUBSUB("retrieve-subscriptions"), Features),
|
||||
#iq{type = result,
|
||||
sub_els =
|
||||
[#pubsub{subscriptions =
|
||||
@@ -680,35 +597,53 @@ pubsub(Config) ->
|
||||
send_recv(Config, #iq{type = get, to = pubsub_jid(Config),
|
||||
sub_els = [#pubsub{subscriptions = {none, []}}]}),
|
||||
%% Get affiliations
|
||||
true = is_feature_advertised(Config, ?PUBSUB("retrieve-affiliations")),
|
||||
true = lists:member(?PUBSUB("retrieve-affiliations"), Features),
|
||||
#iq{type = result,
|
||||
sub_els = [#pubsub{
|
||||
affiliations =
|
||||
[#pubsub_affiliation{node = Node, type = owner}]}]} =
|
||||
send_recv(Config, #iq{type = get, to = pubsub_jid(Config),
|
||||
sub_els = [#pubsub{affiliations = []}]}),
|
||||
%% Get subscription options
|
||||
true = lists:member(?PUBSUB("subscription-options"), Features),
|
||||
#iq{type = result, sub_els = [#pubsub{options = #pubsub_options{
|
||||
node = Node}}]} =
|
||||
send_recv(Config,
|
||||
#iq{type = get, to = pubsub_jid(Config),
|
||||
sub_els = [#pubsub{options = #pubsub_options{
|
||||
node = Node,
|
||||
jid = my_jid(Config)}}]}),
|
||||
%% Fetching published items from node "presence"
|
||||
#iq{type = result,
|
||||
sub_els = [#pubsub{items = #pubsub_items{
|
||||
node = Node,
|
||||
items = [Item]}}]} =
|
||||
send_recv(Config,
|
||||
#iq{type = get, to = pubsub_jid(Config),
|
||||
sub_els = [#pubsub{items = #pubsub_items{node = Node}}]}),
|
||||
%% Deleting the item from the node
|
||||
true = lists:member(?PUBSUB("delete-items"), Features),
|
||||
I2 = send(Config,
|
||||
#iq{type = set, to = pubsub_jid(Config),
|
||||
sub_els = [#pubsub{retract = #pubsub_retract{
|
||||
node = Node,
|
||||
items = [#pubsub_item{id = ItemID}]}}]}),
|
||||
?recv2(
|
||||
#iq{type = result, id = I2, sub_els = []},
|
||||
#message{sub_els = [#pubsub_event{
|
||||
items = [#pubsub_event_items{
|
||||
node = Node,
|
||||
retract = [ItemID]}]},
|
||||
#shim{headers = [{<<"Collection">>, Node}]}]}),
|
||||
%% Unsubscribe from node "presence"
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(Config,
|
||||
#iq{type = set, to = pubsub_jid(Config),
|
||||
sub_els = [#pubsub{unsubscribe = #pubsub_unsubscribe{
|
||||
node = Node,
|
||||
jid = my_jid(Config)}}]}),
|
||||
disconnect(Config).
|
||||
|
||||
auth_md5(Config) ->
|
||||
Mechs = ?config(mechs, Config),
|
||||
case lists:member(<<"DIGEST-MD5">>, Mechs) of
|
||||
true ->
|
||||
disconnect(auth_SASL(<<"DIGEST-MD5">>, Config));
|
||||
false ->
|
||||
disconnect(Config),
|
||||
{skipped, 'DIGEST-MD5_not_available'}
|
||||
end.
|
||||
|
||||
auth_plain(Config) ->
|
||||
Mechs = ?config(mechs, Config),
|
||||
case lists:member(<<"PLAIN">>, Mechs) of
|
||||
true ->
|
||||
disconnect(auth_SASL(<<"PLAIN">>, Config));
|
||||
false ->
|
||||
disconnect(Config),
|
||||
{skipped, 'PLAIN_not_available'}
|
||||
end.
|
||||
|
||||
roster_subscribe_master(Config) ->
|
||||
send(Config, #presence{}),
|
||||
#presence{} = recv(),
|
||||
@@ -975,217 +910,13 @@ offline_slave(Config) ->
|
||||
#message{from = Peer,
|
||||
body = [#text{data = <<"body">>}],
|
||||
subject = [#text{data = <<"subject">>}]}),
|
||||
lists:foreach(
|
||||
fun(#legacy_delay{}) -> ok;
|
||||
(#delay{}) -> ok
|
||||
end, SubEls),
|
||||
true = lists:keymember(delay, 1, SubEls),
|
||||
true = lists:keymember(legacy_delay, 1, SubEls),
|
||||
disconnect(Config).
|
||||
|
||||
auth_SASL(Mech, Config) ->
|
||||
{Response, SASL} = sasl_new(Mech,
|
||||
?config(user, Config),
|
||||
?config(server, Config),
|
||||
?config(password, Config)),
|
||||
send(Config, #sasl_auth{mechanism = Mech, text = Response}),
|
||||
wait_auth_SASL_result(set_opt(sasl, SASL, Config)).
|
||||
|
||||
wait_auth_SASL_result(Config) ->
|
||||
case recv() of
|
||||
#sasl_success{} ->
|
||||
ejabberd_socket:reset_stream(?config(socket, Config)),
|
||||
send_text(Config,
|
||||
io_lib:format(?STREAM_HEADER,
|
||||
[?config(server, Config)])),
|
||||
{xmlstreamstart, <<"stream:stream">>, Attrs} = recv(),
|
||||
<<"jabber:client">> = xml:get_attr_s(<<"xmlns">>, Attrs),
|
||||
<<"1.0">> = xml:get_attr_s(<<"version">>, Attrs),
|
||||
#stream_features{} = recv(),
|
||||
Config;
|
||||
#sasl_challenge{text = ClientIn} ->
|
||||
{Response, SASL} = (?config(sasl, Config))(ClientIn),
|
||||
send(Config, #sasl_response{text = Response}),
|
||||
wait_auth_SASL_result(set_opt(sasl, SASL, Config));
|
||||
#sasl_failure{} ->
|
||||
ct:fail(sasl_auth_failed)
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Aux functions
|
||||
%%%===================================================================
|
||||
re_register(Config) ->
|
||||
User = ?config(user, Config),
|
||||
Server = ?config(server, Config),
|
||||
Pass = ?config(password, Config),
|
||||
{atomic, ok} = ejabberd_auth:try_register(User, Server, Pass),
|
||||
ok.
|
||||
|
||||
recv() ->
|
||||
receive
|
||||
{'$gen_event', {xmlstreamelement, El}} ->
|
||||
Pkt = xmpp_codec:decode(fix_ns(El)),
|
||||
ct:pal("recv: ~p ->~n~s", [El, xmpp_codec:pp(Pkt)]),
|
||||
Pkt;
|
||||
{'$gen_event', Event} ->
|
||||
Event
|
||||
end.
|
||||
|
||||
fix_ns(#xmlel{name = Tag, attrs = Attrs} = El)
|
||||
when Tag == <<"stream:features">>; Tag == <<"stream:error">> ->
|
||||
NewAttrs = [{<<"xmlns">>, <<"http://etherx.jabber.org/streams">>}
|
||||
|lists:keydelete(<<"xmlns">>, 1, Attrs)],
|
||||
El#xmlel{attrs = NewAttrs};
|
||||
fix_ns(#xmlel{name = Tag, attrs = Attrs} = El)
|
||||
when Tag == <<"message">>; Tag == <<"iq">>; Tag == <<"presence">> ->
|
||||
NewAttrs = [{<<"xmlns">>, <<"jabber:client">>}
|
||||
|lists:keydelete(<<"xmlns">>, 1, Attrs)],
|
||||
El#xmlel{attrs = NewAttrs};
|
||||
fix_ns(El) ->
|
||||
El.
|
||||
|
||||
send_text(Config, Text) ->
|
||||
ejabberd_socket:send(?config(socket, Config), Text).
|
||||
|
||||
send(State, Pkt) ->
|
||||
{NewID, NewPkt} = case Pkt of
|
||||
#message{id = I} ->
|
||||
ID = id(I),
|
||||
{ID, Pkt#message{id = ID}};
|
||||
#presence{id = I} ->
|
||||
ID = id(I),
|
||||
{ID, Pkt#presence{id = ID}};
|
||||
#iq{id = I} ->
|
||||
ID = id(I),
|
||||
{ID, Pkt#iq{id = ID}};
|
||||
_ ->
|
||||
{undefined, Pkt}
|
||||
end,
|
||||
El = xmpp_codec:encode(NewPkt),
|
||||
ct:pal("sent: ~p <-~n~s", [El, xmpp_codec:pp(NewPkt)]),
|
||||
ok = send_text(State, xml:element_to_binary(El)),
|
||||
NewID.
|
||||
|
||||
send_recv(State, IQ) ->
|
||||
ID = send(State, IQ),
|
||||
#iq{id = ID} = recv().
|
||||
|
||||
sasl_new(<<"PLAIN">>, User, Server, Password) ->
|
||||
{<<User/binary, $@, Server/binary, 0, User/binary, 0, Password/binary>>,
|
||||
fun (_) -> {error, <<"Invalid SASL challenge">>} end};
|
||||
sasl_new(<<"DIGEST-MD5">>, User, Server, Password) ->
|
||||
{<<"">>,
|
||||
fun (ServerIn) ->
|
||||
case cyrsasl_digest:parse(ServerIn) of
|
||||
bad -> {error, <<"Invalid SASL challenge">>};
|
||||
KeyVals ->
|
||||
Nonce = xml:get_attr_s(<<"nonce">>, KeyVals),
|
||||
CNonce = id(),
|
||||
DigestURI = <<"xmpp/", Server/binary>>,
|
||||
Realm = Server,
|
||||
NC = <<"00000001">>,
|
||||
QOP = <<"auth">>,
|
||||
AuthzId = <<"">>,
|
||||
MyResponse = response(User, Password, Nonce, AuthzId,
|
||||
Realm, CNonce, DigestURI, NC, QOP,
|
||||
<<"AUTHENTICATE">>),
|
||||
ServerResponse = response(User, Password, Nonce,
|
||||
AuthzId, Realm, CNonce, DigestURI,
|
||||
NC, QOP, <<"">>),
|
||||
Resp = <<"username=\"", User/binary, "\",realm=\"",
|
||||
Realm/binary, "\",nonce=\"", Nonce/binary,
|
||||
"\",cnonce=\"", CNonce/binary, "\",nc=", NC/binary,
|
||||
",qop=", QOP/binary, ",digest-uri=\"",
|
||||
DigestURI/binary, "\",response=\"",
|
||||
MyResponse/binary, "\"">>,
|
||||
{Resp,
|
||||
fun (ServerIn2) ->
|
||||
case cyrsasl_digest:parse(ServerIn2) of
|
||||
bad -> {error, <<"Invalid SASL challenge">>};
|
||||
KeyVals2 ->
|
||||
RspAuth = xml:get_attr_s(<<"rspauth">>,
|
||||
KeyVals2),
|
||||
if RspAuth == ServerResponse ->
|
||||
{<<"">>,
|
||||
fun (_) ->
|
||||
{error,
|
||||
<<"Invalid SASL challenge">>}
|
||||
end};
|
||||
true ->
|
||||
{error, <<"Invalid SASL challenge">>}
|
||||
end
|
||||
end
|
||||
end}
|
||||
end
|
||||
end}.
|
||||
|
||||
hex(S) ->
|
||||
p1_sha:to_hexlist(S).
|
||||
|
||||
response(User, Passwd, Nonce, AuthzId, Realm, CNonce,
|
||||
DigestURI, NC, QOP, A2Prefix) ->
|
||||
A1 = case AuthzId of
|
||||
<<"">> ->
|
||||
<<((crypto:md5(<<User/binary, ":", Realm/binary, ":",
|
||||
Passwd/binary>>)))/binary,
|
||||
":", Nonce/binary, ":", CNonce/binary>>;
|
||||
_ ->
|
||||
<<((crypto:md5(<<User/binary, ":", Realm/binary, ":",
|
||||
Passwd/binary>>)))/binary,
|
||||
":", Nonce/binary, ":", CNonce/binary, ":",
|
||||
AuthzId/binary>>
|
||||
end,
|
||||
A2 = case QOP of
|
||||
<<"auth">> ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary>>;
|
||||
_ ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary,
|
||||
":00000000000000000000000000000000">>
|
||||
end,
|
||||
T = <<(hex((crypto:md5(A1))))/binary, ":", Nonce/binary,
|
||||
":", NC/binary, ":", CNonce/binary, ":", QOP/binary,
|
||||
":", (hex((crypto:md5(A2))))/binary>>,
|
||||
hex((crypto:md5(T))).
|
||||
|
||||
my_jid(Config) ->
|
||||
jlib:make_jid(?config(user, Config),
|
||||
?config(server, Config),
|
||||
?config(resource, Config)).
|
||||
|
||||
server_jid(Config) ->
|
||||
jlib:make_jid(<<>>, ?config(server, Config), <<>>).
|
||||
|
||||
pubsub_jid(Config) ->
|
||||
Server = ?config(server, Config),
|
||||
jlib:make_jid(<<>>, <<"pubsub.", Server/binary>>, <<>>).
|
||||
|
||||
proxy_jid(Config) ->
|
||||
Server = ?config(server, Config),
|
||||
jlib:make_jid(<<>>, <<"proxy.", Server/binary>>, <<>>).
|
||||
|
||||
muc_jid(Config) ->
|
||||
Server = ?config(server, Config),
|
||||
jlib:make_jid(<<>>, <<"conference.", Server/binary>>, <<>>).
|
||||
|
||||
muc_room_jid(Config) ->
|
||||
Server = ?config(server, Config),
|
||||
jlib:make_jid(<<"test">>, <<"conference.", Server/binary>>, <<>>).
|
||||
|
||||
id() ->
|
||||
id(undefined).
|
||||
|
||||
id(undefined) ->
|
||||
randoms:get_string();
|
||||
id(ID) ->
|
||||
ID.
|
||||
|
||||
is_feature_advertised(Config, Feature) ->
|
||||
is_feature_advertised(Config, Feature, server_jid(Config)).
|
||||
|
||||
is_feature_advertised(Config, Feature, To) ->
|
||||
ID = send(Config, #iq{type = get, sub_els = [#disco_info{}], to = To}),
|
||||
#iq{type = result, id = ID,
|
||||
sub_els = [#disco_info{feature = Features}]} = recv(),
|
||||
lists:member(Feature, Features).
|
||||
|
||||
bookmark_conference() ->
|
||||
#bookmark_conference{name = <<"Some name">>,
|
||||
autojoin = true,
|
||||
@@ -1194,20 +925,6 @@ bookmark_conference() ->
|
||||
<<"some.conference.org">>,
|
||||
<<>>)}.
|
||||
|
||||
set_opt(Opt, Val, Config) ->
|
||||
[{Opt, Val}|lists:keydelete(Opt, 1, Config)].
|
||||
|
||||
wait_for_master(Config) ->
|
||||
put_event(Config, slave_ready),
|
||||
master_ready = get_event(Config).
|
||||
|
||||
wait_for_slave(Config) ->
|
||||
put_event(Config, master_ready),
|
||||
slave_ready = get_event(Config).
|
||||
|
||||
make_iq_result(#iq{from = From} = IQ) ->
|
||||
IQ#iq{type = result, to = From, from = undefined, sub_els = []}.
|
||||
|
||||
socks5_connect(#streamhost{host = Host, port = Port},
|
||||
{SID, JID1, JID2}) ->
|
||||
Hash = p1_sha:sha([SID, jlib:jid_to_string(JID1), jlib:jid_to_string(JID2)]),
|
||||
@@ -1231,66 +948,6 @@ socks5_send(Sock, Data) ->
|
||||
socks5_recv(Sock, Data) ->
|
||||
{ok, Data} = gen_tcp:recv(Sock, size(Data)).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Clients puts and gets events via this relay.
|
||||
%%%===================================================================
|
||||
start_event_relay() ->
|
||||
spawn(fun event_relay/0).
|
||||
|
||||
stop_event_relay(Config) ->
|
||||
Pid = ?config(event_relay, Config),
|
||||
exit(Pid, normal).
|
||||
|
||||
event_relay() ->
|
||||
event_relay([], []).
|
||||
|
||||
event_relay(Events, Subscribers) ->
|
||||
receive
|
||||
{subscribe, From} ->
|
||||
From ! {ok, self()},
|
||||
lists:foreach(
|
||||
fun(Event) -> From ! {event, Event, self()}
|
||||
end, Events),
|
||||
event_relay(Events, [From|Subscribers]);
|
||||
{put, Event, From} ->
|
||||
From ! {ok, self()},
|
||||
lists:foreach(
|
||||
fun(Pid) when Pid /= From ->
|
||||
Pid ! {event, Event, self()};
|
||||
(_) ->
|
||||
ok
|
||||
end, Subscribers),
|
||||
event_relay([Event|Events], Subscribers)
|
||||
end.
|
||||
|
||||
subscribe_to_events(Config) ->
|
||||
Relay = ?config(event_relay, Config),
|
||||
Relay ! {subscribe, self()},
|
||||
receive
|
||||
{ok, Relay} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
put_event(Config, Event) ->
|
||||
Relay = ?config(event_relay, Config),
|
||||
Relay ! {put, Event, self()},
|
||||
receive
|
||||
{ok, Relay} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
get_event(Config) ->
|
||||
Relay = ?config(event_relay, Config),
|
||||
receive
|
||||
{event, Event, Relay} ->
|
||||
Event
|
||||
end.
|
||||
|
||||
insert(Val, N, Tuple) ->
|
||||
L = tuple_to_list(Tuple),
|
||||
{H, T} = lists:split(N-1, L),
|
||||
list_to_tuple(H ++ [Val|T]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% SQL stuff
|
||||
%%%===================================================================
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"mnesia.localhost",
|
||||
"mysql.localhost",
|
||||
"pgsql.localhost",
|
||||
"extauth.localhost",
|
||||
"ldap.localhost"]}.
|
||||
{define_macro, 'CERTFILE', "cert.pem"}.
|
||||
{listen,
|
||||
@@ -59,6 +60,9 @@
|
||||
{mod_version, []}
|
||||
]}.
|
||||
{host_config, "localhost", [{auth_method, internal}]}.
|
||||
{host_config, "extauth.localhost",
|
||||
[{auth_method, external},
|
||||
{extauth_program, "python extauth.py"}]}.
|
||||
{host_config, "mnesia.localhost",
|
||||
[{auth_method, internal},
|
||||
{{add, modules}, [{mod_announce, [{db_type, internal}]},
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
host_config:
|
||||
"pgsql.localhost":
|
||||
odbc_username: "ejabberd_test"
|
||||
odbc_type: pgsql
|
||||
odbc_server: "localhost"
|
||||
odbc_port: 5432
|
||||
odbc_pool_size: 1
|
||||
odbc_password: "ejabberd_test"
|
||||
odbc_database: "ejabberd_test"
|
||||
auth_method: odbc
|
||||
modules:
|
||||
mod_announce:
|
||||
db_type: odbc
|
||||
mod_blocking:
|
||||
db_type: odbc
|
||||
mod_caps:
|
||||
db_type: odbc
|
||||
mod_last:
|
||||
db_type: odbc
|
||||
mod_muc:
|
||||
db_type: odbc
|
||||
mod_offline:
|
||||
db_type: odbc
|
||||
mod_privacy:
|
||||
db_type: odbc
|
||||
mod_private:
|
||||
db_type: odbc
|
||||
mod_pubsub_odbc:
|
||||
access_createnode: pubsub_createnode
|
||||
ignore_pep_from_offline: true
|
||||
last_item_cache: false
|
||||
plugins:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep"
|
||||
mod_roster:
|
||||
versioning: true
|
||||
store_current_id: true
|
||||
db_type: odbc
|
||||
mod_vcard:
|
||||
db_type: odbc
|
||||
mod_adhoc: []
|
||||
mod_configure: []
|
||||
mod_disco: []
|
||||
mod_ping: []
|
||||
mod_proxy65: []
|
||||
mod_register:
|
||||
welcome_message:
|
||||
subject: "Welcome!"
|
||||
body: "Hi.
|
||||
Welcome to this XMPP server."
|
||||
mod_stats: []
|
||||
mod_time: []
|
||||
mod_version: []
|
||||
"mysql.localhost":
|
||||
odbc_username: "ejabberd_test"
|
||||
odbc_type: mysql
|
||||
odbc_server: "localhost"
|
||||
odbc_port: 3306
|
||||
odbc_pool_size: 1
|
||||
odbc_password: "ejabberd_test"
|
||||
odbc_database: "ejabberd_test"
|
||||
auth_method: odbc
|
||||
modules:
|
||||
mod_announce:
|
||||
db_type: odbc
|
||||
mod_blocking:
|
||||
db_type: odbc
|
||||
mod_caps:
|
||||
db_type: odbc
|
||||
mod_last:
|
||||
db_type: odbc
|
||||
mod_muc:
|
||||
db_type: odbc
|
||||
mod_offline:
|
||||
db_type: odbc
|
||||
mod_privacy:
|
||||
db_type: odbc
|
||||
mod_private:
|
||||
db_type: odbc
|
||||
mod_pubsub_odbc:
|
||||
access_createnode: pubsub_createnode
|
||||
ignore_pep_from_offline: true
|
||||
last_item_cache: false
|
||||
plugins:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep"
|
||||
mod_roster:
|
||||
versioning: true
|
||||
store_current_id: true
|
||||
db_type: odbc
|
||||
mod_vcard:
|
||||
db_type: odbc
|
||||
mod_adhoc: []
|
||||
mod_configure: []
|
||||
mod_disco: []
|
||||
mod_ping: []
|
||||
mod_proxy65: []
|
||||
mod_register:
|
||||
welcome_message:
|
||||
subject: "Welcome!"
|
||||
body: "Hi.
|
||||
Welcome to this XMPP server."
|
||||
mod_stats: []
|
||||
mod_time: []
|
||||
mod_version: []
|
||||
"mnesia.localhost":
|
||||
auth_method: internal
|
||||
modules:
|
||||
mod_announce:
|
||||
db_type: internal
|
||||
mod_blocking:
|
||||
db_type: internal
|
||||
mod_caps:
|
||||
db_type: internal
|
||||
mod_last:
|
||||
db_type: internal
|
||||
mod_muc:
|
||||
db_type: internal
|
||||
mod_offline:
|
||||
db_type: internal
|
||||
mod_privacy:
|
||||
db_type: internal
|
||||
mod_private:
|
||||
db_type: internal
|
||||
mod_pubsub:
|
||||
access_createnode: pubsub_createnode
|
||||
ignore_pep_from_offline: true
|
||||
last_item_cache: false
|
||||
plugins:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep"
|
||||
mod_roster:
|
||||
versioning: true
|
||||
store_current_id: true
|
||||
db_type: internal
|
||||
mod_vcard:
|
||||
db_type: internal
|
||||
mod_adhoc: []
|
||||
mod_configure: []
|
||||
mod_disco: []
|
||||
mod_ping: []
|
||||
mod_proxy65: []
|
||||
mod_register:
|
||||
welcome_message:
|
||||
subject: "Welcome!"
|
||||
body: "Hi.
|
||||
Welcome to this XMPP server."
|
||||
mod_stats: []
|
||||
mod_time: []
|
||||
mod_version: []
|
||||
"localhost":
|
||||
auth_method: internal
|
||||
"ldap.localhost":
|
||||
ldap_servers:
|
||||
- "localhost"
|
||||
ldap_rootdn: "cn=admin,dc=localhost"
|
||||
ldap_port: 1389
|
||||
ldap_password: "password"
|
||||
ldap_base: "ou=users,dc=localhost"
|
||||
auth_method: ldap
|
||||
modules:
|
||||
mod_vcard_ldap: []
|
||||
mod_adhoc: []
|
||||
mod_configure: []
|
||||
mod_disco: []
|
||||
mod_ping: []
|
||||
mod_proxy65: []
|
||||
mod_register:
|
||||
welcome_message:
|
||||
subject: "Welcome!"
|
||||
body: "Hi.
|
||||
Welcome to this XMPP server."
|
||||
mod_stats: []
|
||||
mod_time: []
|
||||
mod_version: []
|
||||
"extauth.localhost":
|
||||
extauth_program: "python extauth.py"
|
||||
auth_method: external
|
||||
hosts:
|
||||
- "localhost"
|
||||
- "mnesia.localhost"
|
||||
- "mysql.localhost"
|
||||
- "pgsql.localhost"
|
||||
- "extauth.localhost"
|
||||
- "ldap.localhost"
|
||||
access:
|
||||
announce:
|
||||
admin: allow
|
||||
c2s:
|
||||
blocked: deny
|
||||
all: allow
|
||||
c2s_shaper:
|
||||
admin: none
|
||||
all: normal
|
||||
configure:
|
||||
admin: allow
|
||||
local:
|
||||
local: allow
|
||||
max_user_offline_messages:
|
||||
admin: 5000
|
||||
all: 100
|
||||
max_user_sessions:
|
||||
all: 10
|
||||
muc:
|
||||
all: allow
|
||||
muc_admin:
|
||||
admin: allow
|
||||
muc_create:
|
||||
local: allow
|
||||
pubsub_createnode:
|
||||
local: allow
|
||||
register:
|
||||
all: allow
|
||||
s2s_shaper:
|
||||
all: fast
|
||||
acl:
|
||||
local:
|
||||
user_regexp: ""
|
||||
define_macro:
|
||||
CERTFILE: "cert.pem"
|
||||
language: "en"
|
||||
listen:
|
||||
-
|
||||
port: 5222
|
||||
module: ejabberd_c2s
|
||||
max_stanza_size: 65536
|
||||
certfile: CERTFILE
|
||||
zlib: true
|
||||
starttls: true
|
||||
shaper: c2s_shaper
|
||||
access: c2s
|
||||
-
|
||||
port: 5269
|
||||
module: ejabberd_s2s_in
|
||||
-
|
||||
port: 5280
|
||||
module: ejabberd_http
|
||||
captcha: true
|
||||
loglevel: 4
|
||||
max_fsm_queue: 1000
|
||||
modules:
|
||||
mod_adhoc: []
|
||||
mod_configure: []
|
||||
mod_disco: []
|
||||
mod_ping: []
|
||||
mod_proxy65: []
|
||||
mod_register:
|
||||
welcome_message:
|
||||
subject: "Welcome!"
|
||||
body: "Hi.
|
||||
Welcome to this XMPP server."
|
||||
mod_stats: []
|
||||
mod_time: []
|
||||
mod_version: []
|
||||
registration_timeout: infinity
|
||||
shaper:
|
||||
fast: 50000
|
||||
normal: 1000
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
import sys
|
||||
import struct
|
||||
|
||||
def read():
|
||||
(pkt_size,) = struct.unpack('>H', sys.stdin.read(2))
|
||||
pkt = sys.stdin.read(pkt_size).split(':')
|
||||
cmd = pkt[0]
|
||||
args_num = len(pkt) - 1
|
||||
if cmd == 'auth' and args_num == 3:
|
||||
write(True)
|
||||
elif cmd == 'isuser' and args_num == 2:
|
||||
write(True)
|
||||
elif cmd == 'setpass' and args_num == 3:
|
||||
write(True)
|
||||
elif cmd == 'tryregister' and args_num == 3:
|
||||
write(True)
|
||||
elif cmd == 'removeuser' and args_num == 2:
|
||||
write(True)
|
||||
elif cmd == 'removeuser3' and args_num == 3:
|
||||
write(True)
|
||||
else:
|
||||
write(False)
|
||||
read()
|
||||
|
||||
def write(result):
|
||||
if result:
|
||||
sys.stdout.write('\x00\x02\x00\x01')
|
||||
else:
|
||||
sys.stdout.write('\x00\x02\x00\x00')
|
||||
sys.stdout.flush()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
read()
|
||||
except struct.error:
|
||||
pass
|
||||
+8
-6
@@ -22,9 +22,11 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include("ELDAPv3.hrl").
|
||||
|
||||
-define(INFO_MSG(Fmt, Args), error_logger:info_msg(Fmt, Args)).
|
||||
-define(ERROR_MSG(Fmt, Args), error_logger:error_msg(Fmt, Args)).
|
||||
|
||||
-define(TCP_SEND_TIMEOUT, 32000).
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
@@ -75,8 +77,8 @@ handle_cast(_Msg, State) ->
|
||||
|
||||
handle_info({'DOWN', MRef, _Type, _Object, Info},
|
||||
#state{listener = MRef} = State) ->
|
||||
?CRITICAL_MSG("listener died with reason ~p, terminating",
|
||||
[Info]),
|
||||
?ERROR_MSG("listener died with reason ~p, terminating",
|
||||
[Info]),
|
||||
{stop, normal, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
@@ -111,7 +113,7 @@ process(Socket, Tree) ->
|
||||
fun(ReplyOp) ->
|
||||
Reply = #'LDAPMessage'{messageID = Id,
|
||||
protocolOp = ReplyOp},
|
||||
?DEBUG("sent:~n~p", [Reply]),
|
||||
%%?DEBUG("sent:~n~p", [Reply]),
|
||||
{ok, Bytes} = asn1rt:encode(
|
||||
'ELDAPv3', 'LDAPMessage', Reply),
|
||||
gen_tcp:send(Socket, Bytes)
|
||||
@@ -125,8 +127,8 @@ process(Socket, Tree) ->
|
||||
Err
|
||||
end.
|
||||
|
||||
process_msg(#'LDAPMessage'{protocolOp = Op} = Msg, TopTree) ->
|
||||
?DEBUG("got:~n~p", [Msg]),
|
||||
process_msg(#'LDAPMessage'{protocolOp = Op} = _Msg, TopTree) ->
|
||||
%%?DEBUG("got:~n~p", [Msg]),
|
||||
case Op of
|
||||
{bindRequest,
|
||||
#'BindRequest'{name = DN}} ->
|
||||
|
||||
+392
@@ -0,0 +1,392 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeniy Khramtsov <>
|
||||
%%% @copyright (C) 2013, Evgeniy Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 27 Jun 2013 by Evgeniy Khramtsov <>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(suite).
|
||||
|
||||
%% API
|
||||
-compile(export_all).
|
||||
|
||||
-include("suite.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init_config(Config) ->
|
||||
DataDir = proplists:get_value(data_dir, Config),
|
||||
PrivDir = proplists:get_value(priv_dir, Config),
|
||||
[_, _|Tail] = lists:reverse(filename:split(DataDir)),
|
||||
BaseDir = filename:join(lists:reverse(Tail)),
|
||||
ConfigPath = filename:join([DataDir, "ejabberd.yml"]),
|
||||
LogPath = filename:join([PrivDir, "ejabberd.log"]),
|
||||
SASLPath = filename:join([PrivDir, "sasl.log"]),
|
||||
MnesiaDir = filename:join([PrivDir, "mnesia"]),
|
||||
CertFile = filename:join([DataDir, "cert.pem"]),
|
||||
{ok, CWD} = file:get_cwd(),
|
||||
{ok, _} = file:copy(CertFile, filename:join([CWD, "cert.pem"])),
|
||||
ok = application:load(sasl),
|
||||
ok = application:load(mnesia),
|
||||
ok = application:load(ejabberd),
|
||||
application:set_env(ejabberd, config, ConfigPath),
|
||||
application:set_env(ejabberd, log_path, LogPath),
|
||||
application:set_env(sasl, sasl_error_logger, {file, SASLPath}),
|
||||
application:set_env(mnesia, dir, MnesiaDir),
|
||||
[{server_port, 5222},
|
||||
{server_host, "localhost"},
|
||||
{server, ?COMMON_VHOST},
|
||||
{user, <<"test_single">>},
|
||||
{certfile, CertFile},
|
||||
{base_dir, BaseDir},
|
||||
{resource, <<"resource">>},
|
||||
{password, <<"password">>}
|
||||
|Config].
|
||||
|
||||
connect(Config) ->
|
||||
{ok, Sock} = ejabberd_socket:connect(
|
||||
?config(server_host, Config),
|
||||
?config(server_port, Config),
|
||||
[binary, {packet, 0}, {active, false}]),
|
||||
init_stream(set_opt(socket, Sock, Config)).
|
||||
|
||||
init_stream(Config) ->
|
||||
ok = send_text(Config, io_lib:format(?STREAM_HEADER,
|
||||
[?config(server, Config)])),
|
||||
{xmlstreamstart, <<"stream:stream">>, Attrs} = recv(),
|
||||
<<"jabber:client">> = xml:get_attr_s(<<"xmlns">>, Attrs),
|
||||
<<"1.0">> = xml:get_attr_s(<<"version">>, Attrs),
|
||||
#stream_features{sub_els = Fs} = recv(),
|
||||
Mechs = lists:flatmap(
|
||||
fun(#sasl_mechanisms{list = Ms}) ->
|
||||
Ms;
|
||||
(_) ->
|
||||
[]
|
||||
end, Fs),
|
||||
lists:foldl(
|
||||
fun(#feature_register{}, Acc) ->
|
||||
set_opt(register, true, Acc);
|
||||
(#starttls{}, Acc) ->
|
||||
set_opt(starttls, true, Acc);
|
||||
(#compression{methods = Ms}, Acc) ->
|
||||
set_opt(compression, Ms, Acc);
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, set_opt(mechs, Mechs, Config), Fs).
|
||||
|
||||
disconnect(Config) ->
|
||||
Socket = ?config(socket, Config),
|
||||
ok = ejabberd_socket:send(Socket, ?STREAM_TRAILER),
|
||||
{xmlstreamend, <<"stream:stream">>} = recv(),
|
||||
ejabberd_socket:close(Socket),
|
||||
Config.
|
||||
|
||||
starttls(Config) ->
|
||||
send(Config, #starttls{}),
|
||||
#starttls_proceed{} = recv(),
|
||||
TLSSocket = ejabberd_socket:starttls(
|
||||
?config(socket, Config),
|
||||
[{certfile, ?config(certfile, Config)},
|
||||
connect]),
|
||||
init_stream(set_opt(socket, TLSSocket, Config)).
|
||||
|
||||
zlib(Config) ->
|
||||
send(Config, #compress{methods = [<<"zlib">>]}),
|
||||
#compressed{} = recv(),
|
||||
ZlibSocket = ejabberd_socket:compress(?config(socket, Config)),
|
||||
init_stream(set_opt(socket, ZlibSocket, Config)).
|
||||
|
||||
auth(Config) ->
|
||||
Mechs = ?config(mechs, Config),
|
||||
HaveMD5 = lists:member(<<"DIGEST-MD5">>, Mechs),
|
||||
HavePLAIN = lists:member(<<"PLAIN">>, Mechs),
|
||||
if HavePLAIN ->
|
||||
auth_SASL(<<"PLAIN">>, Config);
|
||||
HaveMD5 ->
|
||||
auth_SASL(<<"DIGEST-MD5">>, Config);
|
||||
true ->
|
||||
ct:fail(no_sasl_mechanisms_available)
|
||||
end.
|
||||
|
||||
bind(Config) ->
|
||||
#iq{type = result, sub_els = [#bind{}]} =
|
||||
send_recv(
|
||||
Config,
|
||||
#iq{type = set,
|
||||
sub_els = [#bind{resource = ?config(resource, Config)}]}),
|
||||
Config.
|
||||
|
||||
open_session(Config) ->
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(Config, #iq{type = set, sub_els = [#session{}]}),
|
||||
Config.
|
||||
|
||||
auth_SASL(Mech, Config) ->
|
||||
{Response, SASL} = sasl_new(Mech,
|
||||
?config(user, Config),
|
||||
?config(server, Config),
|
||||
?config(password, Config)),
|
||||
send(Config, #sasl_auth{mechanism = Mech, text = Response}),
|
||||
wait_auth_SASL_result(set_opt(sasl, SASL, Config)).
|
||||
|
||||
wait_auth_SASL_result(Config) ->
|
||||
case recv() of
|
||||
#sasl_success{} ->
|
||||
ejabberd_socket:reset_stream(?config(socket, Config)),
|
||||
send_text(Config,
|
||||
io_lib:format(?STREAM_HEADER,
|
||||
[?config(server, Config)])),
|
||||
{xmlstreamstart, <<"stream:stream">>, Attrs} = recv(),
|
||||
<<"jabber:client">> = xml:get_attr_s(<<"xmlns">>, Attrs),
|
||||
<<"1.0">> = xml:get_attr_s(<<"version">>, Attrs),
|
||||
#stream_features{} = recv(),
|
||||
Config;
|
||||
#sasl_challenge{text = ClientIn} ->
|
||||
{Response, SASL} = (?config(sasl, Config))(ClientIn),
|
||||
send(Config, #sasl_response{text = Response}),
|
||||
wait_auth_SASL_result(set_opt(sasl, SASL, Config));
|
||||
#sasl_failure{} ->
|
||||
ct:fail(sasl_auth_failed)
|
||||
end.
|
||||
|
||||
re_register(Config) ->
|
||||
User = ?config(user, Config),
|
||||
Server = ?config(server, Config),
|
||||
Pass = ?config(password, Config),
|
||||
{atomic, ok} = ejabberd_auth:try_register(User, Server, Pass),
|
||||
ok.
|
||||
|
||||
recv() ->
|
||||
receive
|
||||
{'$gen_event', {xmlstreamelement, El}} ->
|
||||
Pkt = xmpp_codec:decode(fix_ns(El)),
|
||||
ct:pal("recv: ~p ->~n~s", [El, xmpp_codec:pp(Pkt)]),
|
||||
Pkt;
|
||||
{'$gen_event', Event} ->
|
||||
Event
|
||||
end.
|
||||
|
||||
fix_ns(#xmlel{name = Tag, attrs = Attrs} = El)
|
||||
when Tag == <<"stream:features">>; Tag == <<"stream:error">> ->
|
||||
NewAttrs = [{<<"xmlns">>, <<"http://etherx.jabber.org/streams">>}
|
||||
|lists:keydelete(<<"xmlns">>, 1, Attrs)],
|
||||
El#xmlel{attrs = NewAttrs};
|
||||
fix_ns(#xmlel{name = Tag, attrs = Attrs} = El)
|
||||
when Tag == <<"message">>; Tag == <<"iq">>; Tag == <<"presence">> ->
|
||||
NewAttrs = [{<<"xmlns">>, <<"jabber:client">>}
|
||||
|lists:keydelete(<<"xmlns">>, 1, Attrs)],
|
||||
El#xmlel{attrs = NewAttrs};
|
||||
fix_ns(El) ->
|
||||
El.
|
||||
|
||||
send_text(Config, Text) ->
|
||||
ejabberd_socket:send(?config(socket, Config), Text).
|
||||
|
||||
send(State, Pkt) ->
|
||||
{NewID, NewPkt} = case Pkt of
|
||||
#message{id = I} ->
|
||||
ID = id(I),
|
||||
{ID, Pkt#message{id = ID}};
|
||||
#presence{id = I} ->
|
||||
ID = id(I),
|
||||
{ID, Pkt#presence{id = ID}};
|
||||
#iq{id = I} ->
|
||||
ID = id(I),
|
||||
{ID, Pkt#iq{id = ID}};
|
||||
_ ->
|
||||
{undefined, Pkt}
|
||||
end,
|
||||
El = xmpp_codec:encode(NewPkt),
|
||||
ct:pal("sent: ~p <-~n~s", [El, xmpp_codec:pp(NewPkt)]),
|
||||
ok = send_text(State, xml:element_to_binary(El)),
|
||||
NewID.
|
||||
|
||||
send_recv(State, IQ) ->
|
||||
ID = send(State, IQ),
|
||||
#iq{id = ID} = recv().
|
||||
|
||||
sasl_new(<<"PLAIN">>, User, Server, Password) ->
|
||||
{<<User/binary, $@, Server/binary, 0, User/binary, 0, Password/binary>>,
|
||||
fun (_) -> {error, <<"Invalid SASL challenge">>} end};
|
||||
sasl_new(<<"DIGEST-MD5">>, User, Server, Password) ->
|
||||
{<<"">>,
|
||||
fun (ServerIn) ->
|
||||
case cyrsasl_digest:parse(ServerIn) of
|
||||
bad -> {error, <<"Invalid SASL challenge">>};
|
||||
KeyVals ->
|
||||
Nonce = xml:get_attr_s(<<"nonce">>, KeyVals),
|
||||
CNonce = id(),
|
||||
Realm = proplists:get_value(<<"realm">>, KeyVals, Server),
|
||||
DigestURI = <<"xmpp/", Realm/binary>>,
|
||||
NC = <<"00000001">>,
|
||||
QOP = <<"auth">>,
|
||||
AuthzId = <<"">>,
|
||||
MyResponse = response(User, Password, Nonce, AuthzId,
|
||||
Realm, CNonce, DigestURI, NC, QOP,
|
||||
<<"AUTHENTICATE">>),
|
||||
Resp = <<"username=\"", User/binary, "\",realm=\"",
|
||||
Realm/binary, "\",nonce=\"", Nonce/binary,
|
||||
"\",cnonce=\"", CNonce/binary, "\",nc=", NC/binary,
|
||||
",qop=", QOP/binary, ",digest-uri=\"",
|
||||
DigestURI/binary, "\",response=\"",
|
||||
MyResponse/binary, "\"">>,
|
||||
{Resp,
|
||||
fun (ServerIn2) ->
|
||||
case cyrsasl_digest:parse(ServerIn2) of
|
||||
bad -> {error, <<"Invalid SASL challenge">>};
|
||||
_KeyVals2 ->
|
||||
{<<"">>,
|
||||
fun (_) ->
|
||||
{error,
|
||||
<<"Invalid SASL challenge">>}
|
||||
end}
|
||||
end
|
||||
end}
|
||||
end
|
||||
end}.
|
||||
|
||||
hex(S) ->
|
||||
p1_sha:to_hexlist(S).
|
||||
|
||||
response(User, Passwd, Nonce, AuthzId, Realm, CNonce,
|
||||
DigestURI, NC, QOP, A2Prefix) ->
|
||||
A1 = case AuthzId of
|
||||
<<"">> ->
|
||||
<<((crypto:md5(<<User/binary, ":", Realm/binary, ":",
|
||||
Passwd/binary>>)))/binary,
|
||||
":", Nonce/binary, ":", CNonce/binary>>;
|
||||
_ ->
|
||||
<<((crypto:md5(<<User/binary, ":", Realm/binary, ":",
|
||||
Passwd/binary>>)))/binary,
|
||||
":", Nonce/binary, ":", CNonce/binary, ":",
|
||||
AuthzId/binary>>
|
||||
end,
|
||||
A2 = case QOP of
|
||||
<<"auth">> ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary>>;
|
||||
_ ->
|
||||
<<A2Prefix/binary, ":", DigestURI/binary,
|
||||
":00000000000000000000000000000000">>
|
||||
end,
|
||||
T = <<(hex((crypto:md5(A1))))/binary, ":", Nonce/binary,
|
||||
":", NC/binary, ":", CNonce/binary, ":", QOP/binary,
|
||||
":", (hex((crypto:md5(A2))))/binary>>,
|
||||
hex((crypto:md5(T))).
|
||||
|
||||
my_jid(Config) ->
|
||||
jlib:make_jid(?config(user, Config),
|
||||
?config(server, Config),
|
||||
?config(resource, Config)).
|
||||
|
||||
server_jid(Config) ->
|
||||
jlib:make_jid(<<>>, ?config(server, Config), <<>>).
|
||||
|
||||
pubsub_jid(Config) ->
|
||||
Server = ?config(server, Config),
|
||||
jlib:make_jid(<<>>, <<"pubsub.", Server/binary>>, <<>>).
|
||||
|
||||
proxy_jid(Config) ->
|
||||
Server = ?config(server, Config),
|
||||
jlib:make_jid(<<>>, <<"proxy.", Server/binary>>, <<>>).
|
||||
|
||||
muc_jid(Config) ->
|
||||
Server = ?config(server, Config),
|
||||
jlib:make_jid(<<>>, <<"conference.", Server/binary>>, <<>>).
|
||||
|
||||
muc_room_jid(Config) ->
|
||||
Server = ?config(server, Config),
|
||||
jlib:make_jid(<<"test">>, <<"conference.", Server/binary>>, <<>>).
|
||||
|
||||
id() ->
|
||||
id(undefined).
|
||||
|
||||
id(undefined) ->
|
||||
randoms:get_string();
|
||||
id(ID) ->
|
||||
ID.
|
||||
|
||||
get_features(Config) ->
|
||||
get_features(Config, server_jid(Config)).
|
||||
|
||||
get_features(Config, To) ->
|
||||
#iq{type = result, sub_els = [#disco_info{features = Features}]} =
|
||||
send_recv(Config, #iq{type = get, sub_els = [#disco_info{}], to = To}),
|
||||
Features.
|
||||
|
||||
is_feature_advertised(Config, Feature) ->
|
||||
is_feature_advertised(Config, Feature, server_jid(Config)).
|
||||
|
||||
is_feature_advertised(Config, Feature, To) ->
|
||||
Features = get_features(Config, To),
|
||||
lists:member(Feature, Features).
|
||||
|
||||
set_opt(Opt, Val, Config) ->
|
||||
[{Opt, Val}|lists:keydelete(Opt, 1, Config)].
|
||||
|
||||
wait_for_master(Config) ->
|
||||
put_event(Config, slave_ready),
|
||||
master_ready = get_event(Config).
|
||||
|
||||
wait_for_slave(Config) ->
|
||||
put_event(Config, master_ready),
|
||||
slave_ready = get_event(Config).
|
||||
|
||||
make_iq_result(#iq{from = From} = IQ) ->
|
||||
IQ#iq{type = result, to = From, from = undefined, sub_els = []}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Clients puts and gets events via this relay.
|
||||
%%%===================================================================
|
||||
start_event_relay() ->
|
||||
spawn(fun event_relay/0).
|
||||
|
||||
stop_event_relay(Config) ->
|
||||
Pid = ?config(event_relay, Config),
|
||||
exit(Pid, normal).
|
||||
|
||||
event_relay() ->
|
||||
event_relay([], []).
|
||||
|
||||
event_relay(Events, Subscribers) ->
|
||||
receive
|
||||
{subscribe, From} ->
|
||||
From ! {ok, self()},
|
||||
lists:foreach(
|
||||
fun(Event) -> From ! {event, Event, self()}
|
||||
end, Events),
|
||||
event_relay(Events, [From|Subscribers]);
|
||||
{put, Event, From} ->
|
||||
From ! {ok, self()},
|
||||
lists:foreach(
|
||||
fun(Pid) when Pid /= From ->
|
||||
Pid ! {event, Event, self()};
|
||||
(_) ->
|
||||
ok
|
||||
end, Subscribers),
|
||||
event_relay([Event|Events], Subscribers)
|
||||
end.
|
||||
|
||||
subscribe_to_events(Config) ->
|
||||
Relay = ?config(event_relay, Config),
|
||||
Relay ! {subscribe, self()},
|
||||
receive
|
||||
{ok, Relay} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
put_event(Config, Event) ->
|
||||
Relay = ?config(event_relay, Config),
|
||||
Relay ! {put, Event, self()},
|
||||
receive
|
||||
{ok, Relay} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
get_event(Config) ->
|
||||
Relay = ?config(event_relay, Config),
|
||||
receive
|
||||
{event, Event, Relay} ->
|
||||
Event
|
||||
end.
|
||||
@@ -0,0 +1,66 @@
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include("xml.hrl").
|
||||
-include("ns.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("mod_proxy65.hrl").
|
||||
-include("xmpp_codec.hrl").
|
||||
|
||||
-define(STREAM_HEADER,
|
||||
<<"<?xml version='1.0'?><stream:stream "
|
||||
"xmlns:stream='http://etherx.jabber.org/stream"
|
||||
"s' xmlns='jabber:client' to='~s' version='1.0"
|
||||
"'>">>).
|
||||
|
||||
-define(STREAM_TRAILER, <<"</stream:stream>">>).
|
||||
|
||||
-define(PUBSUB(Node), <<(?NS_PUBSUB)/binary, "#", Node>>).
|
||||
|
||||
-define(recv2(P1, P2),
|
||||
(fun() ->
|
||||
case {R1 = recv(), R2 = recv()} of
|
||||
{P1, P2} -> {R1, R2};
|
||||
{P2, P1} -> {R2, R1}
|
||||
end
|
||||
end)()).
|
||||
|
||||
-define(recv3(P1, P2, P3),
|
||||
(fun() ->
|
||||
case R3 = recv() of
|
||||
P1 -> insert(R3, 1, ?recv2(P2, P3));
|
||||
P2 -> insert(R3, 2, ?recv2(P1, P3));
|
||||
P3 -> insert(R3, 3, ?recv2(P1, P2))
|
||||
end
|
||||
end)()).
|
||||
|
||||
-define(recv4(P1, P2, P3, P4),
|
||||
(fun() ->
|
||||
case R4 = recv() of
|
||||
P1 -> insert(R4, 1, ?recv3(P2, P3, P4));
|
||||
P2 -> insert(R4, 2, ?recv3(P1, P3, P4));
|
||||
P3 -> insert(R4, 3, ?recv3(P1, P2, P4));
|
||||
P4 -> insert(R4, 4, ?recv3(P1, P2, P3))
|
||||
end
|
||||
end)()).
|
||||
|
||||
-define(recv5(P1, P2, P3, P4, P5),
|
||||
(fun() ->
|
||||
case R5 = recv() of
|
||||
P1 -> insert(R5, 1, ?recv4(P2, P3, P4, P5));
|
||||
P2 -> insert(R5, 2, ?recv4(P1, P3, P4, P5));
|
||||
P3 -> insert(R5, 3, ?recv4(P1, P2, P4, P5));
|
||||
P4 -> insert(R5, 4, ?recv4(P1, P2, P3, P5));
|
||||
P5 -> insert(R5, 5, ?recv4(P1, P2, P3, P4))
|
||||
end
|
||||
end)()).
|
||||
|
||||
-define(COMMON_VHOST, <<"localhost">>).
|
||||
-define(MNESIA_VHOST, <<"mnesia.localhost">>).
|
||||
-define(MYSQL_VHOST, <<"mysql.localhost">>).
|
||||
-define(PGSQL_VHOST, <<"pgsql.localhost">>).
|
||||
-define(LDAP_VHOST, <<"ldap.localhost">>).
|
||||
-define(EXTAUTH_VHOST, <<"extauth.localhost">>).
|
||||
|
||||
insert(Val, N, Tuple) ->
|
||||
L = tuple_to_list(Tuple),
|
||||
{H, T} = lists:split(N-1, L),
|
||||
list_to_tuple(H ++ [Val|T]).
|
||||
+1966
-881
File diff suppressed because it is too large
Load Diff
+58
-32
@@ -9,6 +9,10 @@
|
||||
host :: binary(),
|
||||
port = 1080 :: non_neg_integer()}).
|
||||
|
||||
-record(pubsub_unsubscribe, {node :: binary(),
|
||||
jid :: any(),
|
||||
subid :: binary()}).
|
||||
|
||||
-record(ping, {}).
|
||||
|
||||
-record(delay, {stamp :: any(),
|
||||
@@ -34,12 +38,15 @@
|
||||
|
||||
-record(gone, {uri :: binary()}).
|
||||
|
||||
-record(private, {sub_els = [] :: [any()]}).
|
||||
-record(private, {xml_els = [] :: [any()]}).
|
||||
|
||||
-record(p1_ack, {}).
|
||||
|
||||
-record(pubsub_item, {id :: binary(),
|
||||
sub_els = [] :: [any()]}).
|
||||
xml_els = [] :: [any()]}).
|
||||
|
||||
-record(pubsub_publish, {node :: binary(),
|
||||
items = [] :: [#pubsub_item{}]}).
|
||||
|
||||
-record(roster_item, {jid :: any(),
|
||||
name :: binary(),
|
||||
@@ -60,7 +67,7 @@
|
||||
-record(stat, {name :: binary(),
|
||||
units :: binary(),
|
||||
value :: binary(),
|
||||
error = [] :: [{integer(),binary()}]}).
|
||||
error = [] :: [{integer(),'undefined' | binary()}]}).
|
||||
|
||||
-record('see-other-host', {host :: binary()}).
|
||||
|
||||
@@ -81,6 +88,9 @@
|
||||
|
||||
-record(sasl_response, {text :: any()}).
|
||||
|
||||
-record(pubsub_subscribe, {node :: binary(),
|
||||
jid :: any()}).
|
||||
|
||||
-record(sasl_auth, {mechanism :: binary(),
|
||||
text :: any()}).
|
||||
|
||||
@@ -116,6 +126,8 @@
|
||||
subid :: binary(),
|
||||
type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}).
|
||||
|
||||
-record(shim, {headers = [] :: [{binary(),'undefined' | binary()}]}).
|
||||
|
||||
-record(caps, {hash :: binary(),
|
||||
node :: binary(),
|
||||
ver :: any()}).
|
||||
@@ -127,11 +139,6 @@
|
||||
|
||||
-record(stats, {stat = [] :: [#stat{}]}).
|
||||
|
||||
-record(pubsub, {subscriptions :: {binary(),[#pubsub_subscription{}]},
|
||||
affiliations :: [#pubsub_affiliation{}],
|
||||
publish :: {binary(),[#pubsub_item{}]},
|
||||
subscribe :: {binary(),_}}).
|
||||
|
||||
-record(pubsub_items, {node :: binary(),
|
||||
max_items :: non_neg_integer(),
|
||||
subid :: binary(),
|
||||
@@ -150,6 +157,10 @@
|
||||
x400 = false :: boolean(),
|
||||
userid :: binary()}).
|
||||
|
||||
-record(pubsub_retract, {node :: binary(),
|
||||
notify = false :: any(),
|
||||
items = [] :: [#pubsub_item{}]}).
|
||||
|
||||
-record(text, {lang :: binary(),
|
||||
data :: binary()}).
|
||||
|
||||
@@ -171,9 +182,9 @@
|
||||
values = [] :: [binary()],
|
||||
options = [] :: [binary()]}).
|
||||
|
||||
-record(version, {version_name :: binary(),
|
||||
version_ver :: binary(),
|
||||
version_os :: binary()}).
|
||||
-record(version, {name :: binary(),
|
||||
ver :: binary(),
|
||||
os :: binary()}).
|
||||
|
||||
-record(muc_invite, {reason :: binary(),
|
||||
from :: any(),
|
||||
@@ -233,6 +244,7 @@
|
||||
|
||||
-record(identity, {category :: binary(),
|
||||
type :: binary(),
|
||||
lang :: binary(),
|
||||
name :: binary()}).
|
||||
|
||||
-record(bookmark_conference, {name :: binary(),
|
||||
@@ -244,23 +256,23 @@
|
||||
-record(register, {registered = false :: boolean(),
|
||||
remove = false :: boolean(),
|
||||
instructions :: binary(),
|
||||
username :: binary(),
|
||||
nick :: binary(),
|
||||
password :: binary(),
|
||||
name :: binary(),
|
||||
first :: binary(),
|
||||
last :: binary(),
|
||||
email :: binary(),
|
||||
address :: binary(),
|
||||
city :: binary(),
|
||||
state :: binary(),
|
||||
zip :: binary(),
|
||||
phone :: binary(),
|
||||
url :: binary(),
|
||||
date :: binary(),
|
||||
misc :: binary(),
|
||||
text :: binary(),
|
||||
key :: binary()}).
|
||||
username :: 'none' | binary(),
|
||||
nick :: 'none' | binary(),
|
||||
password :: 'none' | binary(),
|
||||
name :: 'none' | binary(),
|
||||
first :: 'none' | binary(),
|
||||
last :: 'none' | binary(),
|
||||
email :: 'none' | binary(),
|
||||
address :: 'none' | binary(),
|
||||
city :: 'none' | binary(),
|
||||
state :: 'none' | binary(),
|
||||
zip :: 'none' | binary(),
|
||||
phone :: 'none' | binary(),
|
||||
url :: 'none' | binary(),
|
||||
date :: 'none' | binary(),
|
||||
misc :: 'none' | binary(),
|
||||
text :: 'none' | binary(),
|
||||
key :: 'none' | binary()}).
|
||||
|
||||
-record(bookmark_url, {name :: binary(),
|
||||
url :: binary()}).
|
||||
@@ -310,9 +322,23 @@
|
||||
-record(muc_owner, {destroy :: #muc_owner_destroy{},
|
||||
config :: #xdata{}}).
|
||||
|
||||
-record(pubsub_options, {node :: binary(),
|
||||
jid :: any(),
|
||||
subid :: binary(),
|
||||
xdata :: #xdata{}}).
|
||||
|
||||
-record(pubsub, {subscriptions :: {'none' | binary(),[#pubsub_subscription{}]},
|
||||
affiliations :: [#pubsub_affiliation{}],
|
||||
publish :: #pubsub_publish{},
|
||||
subscribe :: #pubsub_subscribe{},
|
||||
unsubscribe :: #pubsub_unsubscribe{},
|
||||
options :: #pubsub_options{},
|
||||
items :: #pubsub_items{},
|
||||
retract :: #pubsub_retract{}}).
|
||||
|
||||
-record(disco_info, {node :: binary(),
|
||||
identity = [] :: [#identity{}],
|
||||
feature = [] :: [binary()],
|
||||
identities = [] :: [#identity{}],
|
||||
features = [] :: [binary()],
|
||||
xdata = [] :: [#xdata{}]}).
|
||||
|
||||
-record(sasl_mechanisms, {list = [] :: [binary()]}).
|
||||
@@ -362,8 +388,8 @@
|
||||
items = [] :: [#privacy_item{}]}).
|
||||
|
||||
-record(privacy, {lists = [] :: [#privacy_list{}],
|
||||
default :: binary(),
|
||||
active :: binary()}).
|
||||
default :: 'none' | binary(),
|
||||
active :: 'none' | binary()}).
|
||||
|
||||
-record(stream_error, {reason :: atom() | #'see-other-host'{},
|
||||
text :: #text{}}).
|
||||
|
||||
+1937
-1874
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user