Compare commits

..

86 Commits

Author SHA1 Message Date
Christophe Romain d576902bd4 Prepare mix for 17.09 2017-09-28 15:21:11 +02:00
Christophe Romain a3da27e917 Fix race introduced by ffdaff374 on ejabberd_mnesia init 2017-09-28 15:14:11 +02:00
Evgeniy Khramtsov 3f015c829c Make sure Riak gets compiled on OTP20 2017-09-28 12:25:06 +03:00
Evgeniy Khramtsov 2732c8f6fc Fix function clause introduced by c17ec50e3a 2017-09-28 12:24:24 +03:00
Evgeniy Khramtsov 3192687334 Don't forget to route presence-unavailable
When a user has several resources joined to a room using the same
nick attempts to leave the room from a single resource, route
presence-unavailable back to this (and only this) resource.

Fixes #2007
2017-09-28 11:58:36 +03:00
Christophe Romain 126653e01b Pubsub: implment '6.5.7 Requesting the Most Recent Items' 2017-09-27 20:54:50 +02:00
Christophe Romain ce7acafe37 Remove useless condition 2017-09-27 20:40:07 +02:00
Christophe Romain 216a0c97b9 PubSub: add RSM support for mnesia backend 2017-09-27 20:39:54 +02:00
Christophe Romain 3c8308bb8d PubSub: enforce controls on publish and delete 2017-09-27 17:37:38 +02:00
Holger Weiss 1c6aa5e84e mysql.sql: Use multi-column index on username/ID 2017-09-27 15:30:23 +02:00
Christophe Romain 609a1d07cf harden ejabberdctl (#1977) 2017-09-27 15:04:57 +02:00
Christophe Romain 8c026582ab Fix xref error 2017-09-27 12:34:40 +02:00
Christophe Romain 81df1ae3af Unbind unused variable 2017-09-27 12:13:45 +02:00
Christophe Romain 22435ca562 Remove calls to deprecated functions 2017-09-27 11:43:59 +02:00
Paweł Chmielowski f464189819 Update dependences 2017-09-27 11:39:16 +02:00
Christophe Romain 89e504c55f PubSub: fix get_items behaviour with mnesia 2017-09-27 11:18:12 +02:00
Christophe Romain c1d3d1318e Merge branch 'master' of github.com:processone/ejabberd 2017-09-27 11:12:09 +02:00
Christophe Romain d120e0ad91 PubSub: add correct order when requesting all items 2017-09-27 11:12:01 +02:00
Evgeniy Khramtsov c17ec50e3a Add support for XEP-0368: SRV records for XMPP over TLS
Currently this is only supported for outgoing s2s connections.
For such connections ejabberd is now able to resolve SRV records
of type "_xmpps-server._tcp". Also, SNI and ALPN fields are set
during TLS handshake. No additional configuration is required.
2017-09-27 12:03:05 +03:00
Badlop 368ba3fc55 When running "make translations", use the new tools/prepare-tr.sh 2017-09-27 10:57:11 +02:00
Badlop cd098c5adc Remove remaining files of old contrib/ dir 2017-09-27 10:57:07 +02:00
Badlop 221e58fff0 Simplify prepare-tr.sh to work with new extract-tr.sh 2017-09-27 10:57:03 +02:00
Badlop 9b043ae276 Move prepare-translations.sh from contrib to tools/prepare-tr.sh 2017-09-27 10:56:58 +02:00
Evgeniy Khramtsov abc09054e5 Remove forgotten export_all 2017-09-27 11:56:22 +03:00
Christophe Romain 07a193d4dc PubSub: fix RSM support (#1994)(#2Â014) 2017-09-27 10:51:37 +02:00
Christophe Romain cf09ed2df2 PubSub: fix node_options, default options only apply on first plugin 2017-09-27 09:29:04 +02:00
Holger Weiss 0cc1ae0a6a mod_http_upload: Don't ignore 'custom_headers'
Don't ignore the 'custom_headers' option if the domain part of the
'put_url' doesn't match the XMPP domain.

Closes #1482.
2017-09-26 21:40:56 +02:00
Evgeniy Khramtsov e1efd29156 Improve presence-error processing
When a presence-error is received from a participant in a MUC, kick
only this particular participant's full JID, leaving other resources
untouched. This will prevent from erroneous kicking all user's resources
in the presence of "multi-session nicks".
2017-09-26 19:01:54 +03:00
Paweł Chmielowski 05feab35c4 Call earlier deps configure scripts durring compilation 2017-09-26 17:32:37 +02:00
Badlop 2198fbce97 Fix command set_last that always returned code 1, error (#2010) 2017-09-25 18:43:24 +02:00
Christophe Romain b8ab80d1f3 Sync containers from rroemhild and add instructions in README (#1655) 2017-09-25 17:48:57 +02:00
Evgeniy Khramtsov 30e5c9bd3e Add mod_avatar to the example configuration 2017-09-25 16:23:02 +03:00
Evgeniy Khramtsov 251756de00 Catch all p1_fsm errors
Fixes #2012
2017-09-25 13:01:00 +03:00
Evgeniy Khramtsov 3e987d3bae Use eimp instead of ImageMagick calls for thumbnails creation 2017-09-25 12:41:12 +03:00
Evgeniy Khramtsov 138cc25355 Pre-install libgd and libwebp 2017-09-25 11:19:49 +03:00
Evgeniy Khramtsov a15f186253 Remove OTP20 from Travis tests for now 2017-09-25 11:17:46 +03:00
Holger Weiss 59ec3d36f4 mod_mam: Simplify check for anon MUC JID filtering
Refuse filtering anon MUC MAM queries by JID even if it's the client's
own JID.  Clients probably won't perform such queries in practice, so
the additional complexity is unnecessary.
2017-09-24 15:04:09 +02:00
Evgeniy Khramtsov d7250111ce Reuse some translation strings 2017-09-24 14:32:37 +03:00
Evgeniy Khramtsov adfb924808 Replace translate:mark/1 with ?T() macro 2017-09-24 12:42:35 +03:00
Holger Weiss 48f2adde98 mod_mam: Refuse filtering anon MUC queries by JID
Return an empty result set if a non-moderator attempts to filter by JID
while querying the archive of an anonymous MUC room.
2017-09-24 02:05:50 +02:00
Evgeniy Khramtsov c378ea403e Add script to extract translation strings 2017-09-24 00:08:01 +03:00
Holger Weiss 692ccd2e20 mod_push_mnesia: Fix typo in error message 2017-09-22 23:36:51 +02:00
Badlop 5882c9b456 Add some recommended TLSOPTS as comments in the default config (#2004) 2017-09-21 16:28:17 +02:00
Evgeniy Khramtsov db41643bea Also replace vcard-x-update in direct presences 2017-09-18 14:17:34 +03:00
Christophe Romain cb076924cc Merge pull request #1996 from nosnilmot/ejabberdctl-quote-peer
Quote $PEER in ping command to avoid hostnames containing "-" being interpreted as arithmetic
2017-09-18 13:09:16 +02:00
Christophe Romain cce4056040 Fix iexdebug and iexlive commands (#1981) 2017-09-18 13:01:10 +02:00
Christophe Romain 7ad525b542 PubSub: broadcast updated configuration (#1945) 2017-09-18 12:48:20 +02:00
Evgeniy Khramtsov 5bf64381cb Add 'width' and 'height' to avatar info 2017-09-18 09:33:57 +03:00
Evgeniy Khramtsov f435d0a103 Add GIF support 2017-09-17 17:34:31 +03:00
Evgeniy Khramtsov e4d21c1941 Introduce mod_avatar
The purpose of the module is to cope with legacy and modern
XMPP clients posting avatars. It automatically converts vCard based
avatars (XEP-0153) to PEP based avatars (XEP-0084) and vice versa.
Also, the module supports convertation between avatar image formats on
the fly: this is controlled by `convert` option. For example, to
convert all avatars into PNG format, configure the module as:

mod_avatar:
  convert:
    default: png

In order to convert only `webp` format to `jpeg`, set the following:

mod_avatar:
  convert:
    webp: jpeg

Note: the module depends on mod_vcard, mod_vcard_xupdate and mod_pubsub.
Also, ejabberd should be built with --enable-graphics option.
2017-09-17 10:26:48 +03:00
Christophe Romain 5414cbe821 Add riakc dependency version 2017-09-13 09:49:43 +02:00
Holger Weiss e6b1521b29 mod_push_keepalive: Remove unused 'db_type' option 2017-09-11 23:48:25 +02:00
Stu Tomlinson 2a7d9d93c8 Quote $PEER in ping command to avoid hostnames containing "-" being interpreted as arithmetic 2017-09-11 15:00:00 +01:00
Evgeniy Khramtsov dfd2045523 Introduce option 'allow_transports'
This is a boolean option. If set to `true` and some server's JID
is in user's roster, then messages from any user of this server are
accepted even if no subscription present.

The option is enabled by default.
2017-09-08 23:10:01 +03:00
Badlop 9e35af54e0 The redis_reconnect_timeout was no longer used (#1983) 2017-09-07 20:25:20 +02:00
Badlop d87151aee6 Simple optimization in get_room_occupants_number command (#1964) 2017-09-07 18:32:26 +02:00
Evgeniy Khramtsov 4ecd8a0780 Really delete cache on set_data()
Fixes #1991
2017-09-07 19:27:48 +03:00
Evgeniy Khramtsov 803c31f760 Always accept messages from local (sub)domains 2017-09-07 13:53:42 +03:00
Evgeniy Khramtsov 978c92f5e1 Fix a typo and improve logging message 2017-09-07 13:41:51 +03:00
Badlop 32397aa0c3 Replace example config of drop_chat_states with queue_* in mod_client_state (#1985) 2017-09-06 13:48:33 +02:00
Evgeniy Khramtsov 1a58a201f8 Block messages from strangers before mod_mam/mod_offline processing
Fixes #1713
2017-09-02 22:54:46 +03:00
Evgeniy Khramtsov 2acbf4625b Deprecate s2s_use_starttls: required_trusted
The functionality can be now obtained by using
`s2s_use_starttls: required` with `mod_s2s_dialback`
being unloaded.
2017-09-02 11:45:06 +03:00
Evgeniy Khramtsov 7566d254e4 Introduce 'redirect_url' option for mod_register
The option enables registration redirection as described in
https://xmpp.org/extensions/xep-0077.html#redirect

Fixes #1976
2017-09-01 12:14:01 +03:00
Badlop 68dee8cbb3 Fix deletion of multiple offline messages using WebAdmin (#1962) 2017-08-25 12:44:53 +02:00
Evgeniy Khramtsov f7e8d287d5 Fix a typo 2017-08-25 12:47:33 +03:00
Evgeniy Khramtsov 30bca124f4 Add forgotten caching options to the validator (thanks to Jan Pinkas) 2017-08-24 21:40:54 +03:00
Paweł Chmielowski d7891a5562 Fix 'make install' to work with new output from rebar list-deps 2017-08-24 11:43:47 +02:00
Badlop 1d396b4716 Rewrite muc_register_nick and muc_unregister_nick to be DB independent (#1954)
Notice: The arguments expected have changed, instead of
  muc_register_nick Tim tim@example.org muc.example.org
  muc_unregister_nick Tim
it expects now:
  muc_register_nick Tim tim@example.org example.org
  muc_unregister_nick tim@example.org example.org
2017-08-24 10:40:54 +02:00
Evgeniy Khramtsov 7f3fceb432 Recompile rebar script with OTP17.5 2017-08-23 11:45:10 +03:00
Evgeniy Khramtsov 81581f7794 Use forked repo of Riak Erlang client 2017-08-23 10:35:39 +03:00
Evgeniy Khramtsov 47175adc74 Explicitly match against <domain/resource>
Fixes #1958
2017-08-23 09:00:13 +03:00
Evgeniy Khramtsov 350827f8f4 Ignore some options from rebar.config
Namely, the following options are ignored in dependency packets:
require_erts_vsn, require_otp_vsn and require_min_otp_vsn
2017-08-22 15:26:35 +03:00
Badlop 05c2995c7a When creating room, set option to ensure config is set in muc_room table (#1954) 2017-08-21 15:58:10 +02:00
Badlop 2fa6e2fd90 Fix crash when destroying room without providing reason (#1954) 2017-08-21 15:57:48 +02:00
Holger Weiss ba9a79c89c Apply cosmetic changes to previous commit 2017-08-18 16:50:08 +02:00
Holger Weiss 13ad754ecc Suppress push notifications for online clients
When a client enabled push notifications during the current session,
notifications should be suppressed as long as the client is online.
Suppressing the notification didn't work for the case where the
notification was triggered by MAM, but this is now fixed.
2017-08-18 16:44:32 +02:00
Badlop 0b02d42836 Fix mod_multicast start and reading of configured limits (#1949) 2017-08-18 14:09:49 +02:00
Evgeniy Khramtsov ee0a8d2966 Preserve correct order of deserialized XML elements
Fixes #1939
2017-08-18 10:20:27 +03:00
Evgeniy Khramtsov 793ca45dda Add OTP 20.0 to Travis testsing platforms 2017-08-17 22:14:54 +03:00
Evgeniy Khramtsov 6e20e9bcf9 Get rid of deprecated crypto functions 2017-08-17 19:32:15 +03:00
Holger Weiss b8d2a72333 mod_stream_mgmt: Delete 'c2s_init' hook
Delete the 'c2s_init' hook when the last 'mod_stream_mgmt' instance is
stopped.
2017-08-17 18:25:06 +02:00
Holger Weiss 0760c7273c mod_stream_mgmt: Remove outdated TODO comment
The CSI queue is now flushed from mod_client_state.
2017-08-17 18:19:04 +02:00
Evgeniy Khramtsov 9bd099013f Don't attempt to access(2) a certificate file
Fixes #1375
2017-08-17 14:33:41 +03:00
Christophe Romain 68fb12153e Revert "Temporary remove recent last_item refactor"
This reverts commit 1820b4f63b.
2017-08-14 15:25:45 +02:00
Holger Weiss e19d1e9571 Merge remote-tracking branch 'processone/pr/1938'
* processone/pr/1938:
  Let 'domain_certfile' take higher precedence
2017-08-14 14:43:03 +02:00
Holger Weiss 64150cc7c5 Let 'domain_certfile' take higher precedence
If a 'domain_certfile' is specified, use that instead of the
's2s_certfile' (or 'c2s_certfile').
2017-08-13 20:31:03 +02:00
80 changed files with 2377 additions and 1701 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ before_install:
- pip install --user coveralls-merge
install:
- sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev
- sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev libgd-dev libwebp-dev
before_script:
# Ulimit: See Travis-CI issue report: https://github.com/travis-ci/travis-ci/issues/3328
+12 -5
View File
@@ -9,7 +9,7 @@ ENV EJABBERD_BRANCH=17.08 \
EJABBERD_HOME=/opt/ejabberd \
EJABBERD_DEBUG_MODE=false \
HOME=$EJABBERD_HOME \
PATH=$EJABBERD_HOME/bin:/usr/sbin:/usr/bin:/sbin:/bin \
PATH=$EJABBERD_HOME/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/sbin \
DEBIAN_FRONTEND=noninteractive \
XMPP_DOMAIN=localhost \
# Set default locale for the environment
@@ -38,6 +38,7 @@ RUN set -x \
erlang-src erlang-dev \
' \
&& requiredAptPackages=' \
wget \
locales \
ldnsutils \
python2.7 \
@@ -47,7 +48,7 @@ RUN set -x \
erlang-base erlang-snmp erlang-ssl erlang-ssh erlang-webtool \
erlang-tools erlang-xmerl erlang-corba erlang-diameter erlang-eldap \
erlang-eunit erlang-ic erlang-odbc erlang-os-mon \
erlang-parsetools erlang-percept erlang-typer erlang-inets \
erlang-parsetools erlang-percept erlang-typer \
python-mysqldb \
imagemagick \
' \
@@ -68,7 +69,6 @@ RUN set -x \
&& chmod +x ./autogen.sh \
&& ./autogen.sh \
&& ./configure --enable-user=$EJABBERD_USER \
--prefix=/ \
--enable-all \
--disable-tools \
--disable-pam \
@@ -82,12 +82,19 @@ RUN set -x \
&& mkdir $EJABBERD_HOME/module_source \
&& cd $EJABBERD_HOME \
&& rm -rf /tmp/ejabberd \
&& rm -rf /etc/ejabberd \
&& ln -sf $EJABBERD_HOME/conf /etc/ejabberd \
&& rm -rf /usr/local/etc/ejabberd \
&& ln -sf $EJABBERD_HOME/conf /usr/local/etc/ejabberd \
&& chown -R $EJABBERD_USER: $EJABBERD_HOME \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get purge -y --auto-remove $buildDeps
RUN wget -P /usr/local/share/ca-certificates/cacert.org http://www.cacert.org/certs/root.crt http://www.cacert.org/certs/class3.crt; \
update-ca-certificates
# Create logging directories
RUN mkdir -p /var/log/ejabberd
RUN touch /var/log/ejabberd/crash.log /var/log/ejabberd/error.log /var/log/ejabberd/erlang.log
# Wrapper for setting config on disk from environment
# allows setting things like XMPP domain at runtime
ADD ./docker/run.sh /sbin/run
+2 -2
View File
@@ -102,7 +102,7 @@ xref: all
translations:
contrib/extract_translations/prepare-translation.sh -updateall
tools/prepare-tr.sh
edoc:
$(ERL) -noinput +B -eval \
@@ -123,7 +123,7 @@ FILES_WILDCARD=$(call FILTER_DIRS,$(foreach w,$(1),$(wildcard $(w))))
ifeq ($(MAKECMDGOALS),copy-files-sub)
DEPS:=$(sort $(shell $(REBAR) list-deps|$(SED) -e '/^=/d;s/ .*//'))
DEPS:=$(sort $(shell $(REBAR) -q list-deps|$(SED) -e '/[a-z0-9_-]+\s/d;s/ .*//'))
DEPS_FILES=$(call FILES_WILDCARD,$(foreach DEP,$(DEPS),deps/$(DEP)/ebin/*.beam deps/$(DEP)/ebin/*.app deps/$(DEP)/priv/* deps/$(DEP)/priv/lib/* deps/$(DEP)/priv/bin/* deps/$(DEP)/include/*.hrl deps/$(DEP)/COPY* deps/$(DEP)/LICENSE* deps/$(DEP)/lib/*/ebin/*.beam deps/$(DEP)/lib/*/ebin/*.app))
DEPS_FILES_FILTERED=$(filter-out %/epam deps/elixir/ebin/elixir.app,$(DEPS_FILES))
+1 -1
View File
@@ -84,7 +84,7 @@ defmodule Ejabberd.ConfigFile do
module :mod_client_state do
@opts [
drop_chat_states: true,
queue_chat_states: true,
queue_presence: false]
end
+1 -1
View File
@@ -560,7 +560,7 @@ modules:
mod_caps: {}
mod_carboncopy: {}
mod_client_state:
drop_chat_states: true
queue_chat_states: true
queue_presence: false
mod_configure: {} # requires mod_adhoc
mod_disco: {}
+9
View File
@@ -236,6 +236,14 @@ AC_ARG_ENABLE(sip,
*) AC_MSG_ERROR(bad value ${enableval} for --enable-sip) ;;
esac],[if test "x$sip" = "x"; then sip=false; fi])
AC_ARG_ENABLE(graphics,
[AC_HELP_STRING([--enable-graphics], [enable support for graphic images manipulation (default: yes)])],
[case "${enableval}" in
yes) graphics=true ;;
no) graphics=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-graphics) ;;
esac],[if test "x$graphics" = "x"; then graphics=true; fi])
AC_CONFIG_FILES([Makefile
vars.config
src/ejabberd.app.src])
@@ -280,6 +288,7 @@ AC_SUBST(iconv)
AC_SUBST(stun)
AC_SUBST(sip)
AC_SUBST(debug)
AC_SUBST(graphics)
AC_SUBST(tools)
AC_SUBST(latest_deps)
AC_SUBST(system_deps)
-21
View File
@@ -1,21 +0,0 @@
extract_translations - auxiliary tool that extracts lines to be translated
from ejabberd source tree.
Building:
erlc extract_translations.erl
Invoking 1:
erl -noinput -s extract_translations -extra dirname message_file
where dirname is the directory "src" in ejabberd's source tree root,
message_file is a file with translated messages (src/msgs/*.msg).
Result is a list of messages from source files which aren't contained in
message file.
Invoking 2:
erl -noinput -s extract_translations -extra -unused dirname message_file
Result is a list of messages from message file which aren't in source
files anymore.
@@ -1,307 +0,0 @@
%%%----------------------------------------------------------------------
%%% File : extract_translations.erl
%%% Author : Sergei Golovan <sgolovan@nes.ru>
%%% Purpose : Auxiliary tool for interface/messages translators
%%% Created : 23 Apr 2005 by Sergei Golovan <sgolovan@nes.ru>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(extract_translations).
-author('sgolovan@nes.ru').
-export([start/0]).
-define(STATUS_SUCCESS, 0).
-define(STATUS_ERROR, 1).
-define(STATUS_USAGE, 2).
-include_lib("kernel/include/file.hrl").
start() ->
ets:new(translations, [named_table, public]),
ets:new(translations_obsolete, [named_table, public]),
ets:new(files, [named_table, public]),
ets:new(vars, [named_table, public]),
case init:get_plain_arguments() of
["-srcmsg2po", Dir, File] ->
print_po_header(File),
Status = process(Dir, File, srcmsg2po),
halt(Status);
["-unused", Dir, File] ->
Status = process(Dir, File, unused),
halt(Status);
[Dir, File] ->
Status = process(Dir, File, used),
halt(Status);
_ ->
print_usage(),
halt(?STATUS_USAGE)
end.
process(Dir, File, Used) ->
case load_file(File) of
{error, Reason} ->
io:format("~s: ~s~n", [File, file:format_error(Reason)]),
?STATUS_ERROR;
_ ->
FileList = find_src_files(Dir),
lists:foreach(
fun(F) ->
parse_file(Dir, F, Used)
end, FileList),
case Used of
unused ->
ets:foldl(fun({Key, _}, _) ->
io:format("~p~n", [Key])
end, ok, translations);
srcmsg2po ->
ets:foldl(fun({Key, Trans}, _) ->
print_translation_obsolete(Key, Trans)
end, ok, translations_obsolete);
_ ->
ok
end,
?STATUS_SUCCESS
end.
parse_file(Dir, File, Used) ->
ets:delete_all_objects(vars),
case epp:parse_file(File, [Dir, filename:dirname(File) | code:get_path()], []) of
{ok, Forms} ->
lists:foreach(
fun(F) ->
parse_form(Dir, File, F, Used)
end, Forms);
_ ->
ok
end.
parse_form(Dir, File, Form, Used) ->
case Form of
%%{undefined, Something} ->
%% io:format("Undefined: ~p~n", [Something]);
{call,
_,
{remote, _, {atom, _, translate}, {atom, _, translate}},
[_, {string, Line, Str}]
} ->
process_string(Dir, File, Line, Str, Used);
{call,
_,
{remote, _, {atom, _, translate}, {atom, _, translate}},
[_,
{bin,_,
[{bin_element,_,
{string,Line,Str},
default,default}]}]
} ->
process_string(Dir, File, Line, Str, Used);
{call,
_,
{remote, _, {atom, _, translate}, {atom, _, translate}},
[_, {var, _, Name}]
} ->
case ets:lookup(vars, Name) of
[{_Name, Value, Line}] ->
process_string(Dir, File, Line, Value, Used);
_ ->
ok
end;
{match,
_,
{var, _, Name},
{string, Line, Value}
} ->
ets:insert(vars, {Name, Value, Line});
{match,
_,
{var, _, Name},
{bin,Line,[{bin_element,_,{string,_,Value},_,_}]}
} ->
ets:insert(vars, {Name, Value, Line});
L when is_list(L) ->
lists:foreach(
fun(F) ->
parse_form(Dir, File, F, Used)
end, L);
T when is_tuple(T) ->
lists:foreach(
fun(F) ->
parse_form(Dir, File, F, Used)
end, tuple_to_list(T));
_ ->
ok
end.
process_string(_Dir, _File, _Line, "", _Used) ->
ok;
process_string(_Dir, File, Line, Str, Used) ->
case {ets:lookup(translations, Str), Used} of
{[{_Key, _Trans}], unused} ->
ets:delete(translations, Str);
{[{_Key, _Trans}], used} ->
ok;
{[{_Key, Trans}], srcmsg2po} ->
ets:delete(translations_obsolete, Str),
print_translation(File, Line, Str, Trans);
{_, used} ->
case ets:lookup(files, File) of
[{_}] ->
ok;
_ ->
io:format("~n% ~s~n", [File]),
ets:insert(files, {File})
end,
case Str of
[] -> ok;
_ -> io:format("{~p, \"\"}.~n", [Str])
end,
ets:insert(translations, {Str, ""});
{_, srcmsg2po} ->
case ets:lookup(files, File) of
[{_}] ->
ok;
_ ->
ets:insert(files, {File})
end,
ets:insert(translations, {Str, ""}),
print_translation(File, Line, Str, "");
_ ->
ok
end.
load_file(File) ->
case file:consult(File) of
{ok, Terms} ->
lists:foreach(
fun({Orig, Trans}) ->
case Trans of
"" ->
ok;
_ ->
ets:insert(translations, {Orig, Trans}),
ets:insert(translations_obsolete, {Orig, Trans})
end
end, Terms);
Err ->
Err
end.
find_src_files(Dir) ->
case file:list_dir(Dir) of
{ok, FileList} ->
recurse_filelist(
lists:map(
fun(F) ->
filename:join(Dir, F)
end, FileList));
_ ->
[]
end.
recurse_filelist(FileList) ->
recurse_filelist(FileList, []).
recurse_filelist([], Acc) ->
lists:reverse(Acc);
recurse_filelist([H | T], Acc) ->
case file:read_file_info(H) of
{ok, #file_info{type = directory}} ->
recurse_filelist(T, lists:reverse(find_src_files(H)) ++ Acc);
{ok, #file_info{type = regular}} ->
case string:substr(H, string:len(H) - 3) of
".erl" ->
recurse_filelist(T, [H | Acc]);
".hrl" ->
recurse_filelist(T, [H | Acc]);
_ ->
recurse_filelist(T, Acc)
end;
_ ->
recurse_filelist(T, Acc)
end.
print_usage() ->
io:format(
"Usage: extract_translations [-unused] dir file~n"
"~n"
"Example:~n"
" extract_translations . ./msgs/ru.msg~n"
).
%%%
%%% Gettext
%%%
print_po_header(File) ->
MsgProps = get_msg_header_props(File),
{Language, [LastT | AddT]} = prepare_props(MsgProps),
print_po_header(Language, LastT, AddT).
get_msg_header_props(File) ->
{ok, F} = file:open(File, [read]),
Lines = get_msg_header_props(F, []),
file:close(F),
Lines.
get_msg_header_props(F, Lines) ->
String = io:get_line(F, ""),
case io_lib:fread("% ", String) of
{ok, [], RemString} ->
case io_lib:fread("~s", RemString) of
{ok, [Key], Value} when Value /= "\n" ->
%% The first character in Value is a blankspace:
%% And the last characters are 'slash n'
ValueClean = string:substr(Value, 2, string:len(Value)-2),
get_msg_header_props(F, Lines ++ [{Key, ValueClean}]);
_ ->
get_msg_header_props(F, Lines)
end;
_ ->
Lines
end.
prepare_props(MsgProps) ->
Language = proplists:get_value("Language:", MsgProps),
Authors = proplists:get_all_values("Author:", MsgProps),
{Language, Authors}.
print_po_header(Language, LastTranslator, AdditionalTranslatorsList) ->
AdditionalTranslatorsString = build_additional_translators(AdditionalTranslatorsList),
HeaderString =
"msgid \"\"\n"
"msgstr \"\"\n"
++ "\"X-Language: " ++ Language ++ "\\n\"\n"
"\"Last-Translator: " ++ LastTranslator ++ "\\n\"\n"
++ AdditionalTranslatorsString ++
"\"MIME-Version: 1.0\\n\"\n"
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
"\"Content-Transfer-Encoding: 8bit\\n\"\n",
io:format("~s~n", [HeaderString]).
build_additional_translators(List) ->
lists:foldl(
fun(T, Str) ->
Str ++ "\"X-Additional-Translator: " ++ T ++ "\\n\"\n"
end,
"",
List).
print_translation(File, Line, Str, StrT) ->
StrQ = ejabberd_regexp:greplace(list_to_binary(Str), <<"\\\"">>, <<"\\\\\"">>),
StrTQ = ejabberd_regexp:greplace(list_to_binary(StrT), <<"\\\"">>, <<"\\\\\"">>),
io:format("#: ~s:~p~nmsgid \"~s\"~nmsgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
print_translation_obsolete(Str, StrT) ->
File = "unknown.erl",
Line = 1,
StrQ = ejabberd_regexp:greplace(Str, "\\\"", "\\\\\""),
StrTQ = ejabberd_regexp:greplace(StrT, "\\\"", "\\\\\""),
io:format("#: ~s:~p~n#~~ msgid \"~s\"~n#~~ msgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
@@ -1,366 +0,0 @@
#!/bin/bash
# Frontend for ejabberd's extract_translations.erl
# by Badlop
# How to create template files for a new language:
# NEWLANG=zh
# cp msgs/ejabberd.pot msgs/$NEWLANG.po
# echo \{\"\",\"\"\}. > msgs/$NEWLANG.msg
# ../../extract_translations/prepare-translation.sh -updateall
prepare_dirs ()
{
# Where is Erlang binary
ERL=`which erl`
EJA_SRC_DIR=$EJA_DIR/src/
EJA_MSGS_DIR=$EJA_DIR/priv/msgs/
EXTRACT_DIR=$EJA_DIR/contrib/extract_translations/
EXTRACT_ERL=$EXTRACT_DIR/extract_translations.erl
EXTRACT_BEAM=$EXTRACT_DIR/extract_translations.beam
SRC_DIR=$RUN_DIR/src
EBIN_DIR=$RUN_DIR/ebin
MSGS_DIR=$EJA_DIR/priv/msgs
if !([[ -n $EJA_DIR ]])
then
echo "ejabberd dir does not exist: $EJA_DIR"
fi
if !([[ -x $EXTRACT_BEAM ]])
then
sh -c "cd $EXTRACT_DIR; $ERL -compile $EXTRACT_ERL"
fi
}
extract_lang ()
{
MSGS_FILE=$1
MSGS_FILE2=$MSGS_FILE.translate
MSGS_PATH=$MSGS_DIR/$MSGS_FILE
MSGS_PATH2=$MSGS_DIR/$MSGS_FILE2
echo -n "Extracting language strings for '$MSGS_FILE':"
echo -n " new..."
cd $SRC_DIR
$ERL -pa $EXTRACT_DIR -noinput -noshell -s extract_translations -s init stop -extra . $MSGS_PATH >$MSGS_PATH.new
sed -e 's/^% \.\//% /g;' $MSGS_PATH.new > $MSGS_PATH.new2
mv $MSGS_PATH.new2 $MSGS_PATH.new
echo -n " old..."
$ERL -pa $EXTRACT_DIR -noinput -noshell -s extract_translations -s init stop -extra -unused . $MSGS_PATH >$MSGS_PATH.unused
find_unused_full $MSGS_FILE $MSGS_FILE.unused
echo "" >$MSGS_PATH2
echo " ***** Translation file for ejabberd ***** " >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo " *** New strings: Can you please translate them? *** " >>$MSGS_PATH2
cat $MSGS_PATH.new >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo " *** Unused strings: They will be removed automatically *** " >>$MSGS_PATH2
cat $MSGS_PATH.unused.full >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo " *** Already translated strings: you can also modify any of them if you want *** " >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
cat $MSGS_PATH.old_cleaned >>$MSGS_PATH2
echo " ok"
rm $MSGS_PATH.new
rm $MSGS_PATH.old_cleaned
rm $MSGS_PATH.unused.full
}
extract_lang_all ()
{
cd $MSGS_DIR
for i in $( ls *.msg ) ; do
extract_lang $i;
done
echo -e "File\tMissing\tLanguage\t\tLast translator"
echo -e "----\t-------\t--------\t\t---------------"
cd $MSGS_DIR
for i in $( ls *.msg ) ; do
MISSING=`cat $i.translate | grep "\", \"\"}." | wc -l`
LANGUAGE=`grep "X-Language:" $i.translate | sed 's/% Language: //g'`
LASTAUTH=`grep "Author:" $i.translate | head -n 1 | sed 's/% Author: //g'`
echo -e "$i\t$MISSING\t$LANGUAGE\t$LASTAUTH"
done
cd $MSGS_DIR
REVISION=`git describe --always`
zip $HOME/ejabberd-langs-$REVISION.zip *.translate;
rm *.translate
}
find_unused_full ()
{
DATFILE=$1
DATFILEI=$1.old_cleaned
DELFILE=$2
cd msgs
DATFILE1=$DATFILE.t1
DATFILE2=$DATFILE.t2
DELFILE1=$DELFILE.t1
DELFILE2=$DELFILE.t2
DELFILEF=$DATFILE.unused.full
echo "" >$DELFILEF
grep -v "\\\\" $DELFILE >$DELFILE2
echo ENDFILEMARK >>$DELFILE2
cp $DATFILE $DATFILEI
cp $DATFILE $DATFILE2
cp $DELFILE2 $DELFILE1
STRING=`head -1 $DELFILE1`
until [[ $STRING == ENDFILEMARK ]]; do
cp $DELFILE2 $DELFILE1
cp $DATFILE2 $DATFILE1
STRING=`head -1 $DELFILE1`
cat $DATFILE1 | grep "$STRING" >>$DELFILEF
cat $DATFILE1 | grep -v "$STRING" >$DATFILE2
cat $DELFILE1 | grep -v "$STRING" >$DELFILE2
done
mv $DATFILE2 $DATFILEI
rm -f $MSGS_PATH.t1
rm $MSGS_PATH.unused
rm -f $MSGS_PATH.unused.t1
rm $MSGS_PATH.unused.t2
cd ..
}
extract_lang_srcmsg2po ()
{
LANG=$1
LANG_CODE=$LANG.$PROJECT
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
PO_PATH=$MSGS_DIR/$LANG_CODE.po
echo $MSGS_PATH
cd $SRC_DIR
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
sed -e 's/ \[\]$/ \"\"/g;' $PO_PATH.1 > $PO_PATH.2
msguniq --sort-by-file $PO_PATH.2 --output-file=$PO_PATH
rm $PO_PATH.*
}
extract_lang_src2pot ()
{
LANG_CODE=$PROJECT
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
POT_PATH=$MSGS_DIR/$LANG_CODE.pot
echo -n "" >$MSGS_PATH
echo "% Language: Language Name" >>$MSGS_PATH
echo "% Author: Translator name and contact method" >>$MSGS_PATH
echo "" >>$MSGS_PATH
cd $SRC_DIR
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
sed -e 's/ \[\]$/ \"\"/g;' $POT_PATH.1 > $POT_PATH.2
#msguniq --sort-by-file $POT_PATH.2 $EJA_MSGS_DIR --output-file=$POT_PATH
msguniq --sort-by-file $POT_PATH.2 --output-file=$POT_PATH
rm $POT_PATH.*
rm $MSGS_PATH
# If the project is a specific module, not the main ejabberd
if [[ $PROJECT != ejabberd ]] ; then
# Remove from project.pot the strings that are already present in the general ejabberd
EJABBERD_MSG_FILE=$EJA_MSGS_DIR/es.po # This is just some file with translated strings
POT_PATH_TEMP=$POT_PATH.temp
msgattrib --set-obsolete --only-file=$EJABBERD_MSG_FILE -o $POT_PATH_TEMP $POT_PATH
mv $POT_PATH_TEMP $POT_PATH
fi
}
extract_lang_popot2po ()
{
LANG_CODE=$1
PO_PATH=$MSGS_DIR/$LANG_CODE.po
POT_PATH=$MSGS_DIR/$PROJECT.pot
msgmerge $PO_PATH $POT_PATH >$PO_PATH.translate 2>/dev/null
mv $PO_PATH.translate $PO_PATH
}
extract_lang_po2msg ()
{
LANG_CODE=$1
PO_PATH=$LANG_CODE.po
MS_PATH=$PO_PATH.ms
MSGID_PATH=$PO_PATH.msgid
MSGSTR_PATH=$PO_PATH.msgstr
MSGS_PATH=$LANG_CODE.msg
cd $MSGS_DIR
# Check PO has correct ~
# Let's convert to C format so we can use msgfmt
PO_TEMP=$LANG_CODE.po.temp
cat $PO_PATH | sed 's/%/perc/g' | sed 's/~/%/g' | sed 's/#:.*/#, c-format/g' >$PO_TEMP
msgfmt $PO_TEMP --check-format
result=$?
rm $PO_TEMP
if [ $result -ne 0 ] ; then
exit 1
fi
msgattrib $PO_PATH --translated --no-fuzzy --no-obsolete --no-location --no-wrap | grep "^msg" | tail --lines=+3 >$MS_PATH
grep "^msgid" $PO_PATH.ms | sed 's/^msgid //g' >$MSGID_PATH
grep "^msgstr" $PO_PATH.ms | sed 's/^msgstr //g' >$MSGSTR_PATH
echo "%% -*- coding: latin-1 -*-" >$MSGS_PATH
paste $MSGID_PATH $MSGSTR_PATH --delimiter=, | awk '{print "{" $0 "}."}' | sort -g >>$MSGS_PATH
rm $MS_PATH
rm $MSGID_PATH
rm $MSGSTR_PATH
}
extract_lang_updateall ()
{
echo "Generating POT"
extract_lang_src2pot
cd $MSGS_DIR
echo ""
echo -e "File Missing Language Last translator"
echo -e "---- ------- -------- ---------------"
for i in $( ls *.msg ) ; do
LANG_CODE=${i%.msg}
echo -n $LANG_CODE | awk '{printf "%-6s", $1 }'
# Convert old MSG file to PO
PO=$LANG_CODE.po
[ -f $PO ] || extract_lang_srcmsg2po $LANG_CODE
extract_lang_popot2po $LANG_CODE
extract_lang_po2msg $LANG_CODE
MISSING=`msgfmt --statistics $PO 2>&1 | awk '{printf "%5s", $4 }'`
echo -n " $MISSING"
LANGUAGE=`grep "X-Language:" $PO | sed 's/\"X-Language: //g' | sed 's/\\\\n\"//g' | awk '{printf "%-12s", $1}'`
echo -n " $LANGUAGE"
LASTAUTH=`grep "Last-Translator" $PO | sed 's/\"Last-Translator: //g' | sed 's/\\\\n\"//g'`
echo " $LASTAUTH"
done
echo ""
rm messages.mo
cd ..
}
translation_instructions ()
{
echo ""
echo " A new file has been created for you, with the current, the new and the deprecated strings:"
echo " $MSGS_PATH2"
echo ""
echo " At the end of that file you will find the strings you must update:"
echo " - Untranslated strings are like this: {"March", ""}."
echo " To translate the string, add the text inside the commas. Example:"
echo " {"March", "Marzo"}."
echo " - Old strings that are not used: "Woowoa""
echo " Search the entire file for those strings and remove them"
echo ""
echo " Once you have translated all the strings and removed all the old ones,"
echo " rename the file to overwrite the previous one:"
echo " $MSGS_PATH"
}
EJA_DIR=`pwd`
RUN_DIR=`pwd`
PROJECT=ejabberd
while [ $# -ne 0 ] ; do
PARAM=$1
shift
case $PARAM in
--) break ;;
-project)
PROJECT=$1
shift
;;
-ejadir)
EJA_DIR=$1
shift
;;
-rundir)
RUN_DIR=$1
shift
;;
-lang)
LANGU=$1
prepare_dirs
extract_lang $LANGU
shift
;;
-langall)
prepare_dirs
extract_lang_all
;;
-srcmsg2po)
LANG_CODE=$1
prepare_dirs
extract_lang_srcmsg2po $LANG_CODE
shift
;;
-popot2po)
LANG_CODE=$1
prepare_dirs
extract_lang_popot2po $LANG_CODE
shift
;;
-src2pot)
prepare_dirs
extract_lang_src2pot
;;
-po2msg)
LANG_CODE=$1
prepare_dirs
extract_lang_po2msg $LANG_CODE
shift
;;
-updateall)
prepare_dirs
extract_lang_updateall
;;
*)
echo "Options:"
echo " -langall"
echo " -lang LANGUAGE_FILE"
echo " -srcmsg2po LANGUAGE Construct .msg file using source code to PO file"
echo " -src2pot Generate template POT file from source code"
echo " -popot2po LANGUAGE Update PO file with template POT file"
echo " -po2msg LANGUAGE Export PO file to MSG file"
echo " -updateall Generate POT and update all PO"
echo ""
echo "Example:"
echo " ./prepare-translation.sh -lang es.msg"
exit 0
;;
esac
done
+83 -41
View File
@@ -1,21 +1,23 @@
ejabberd container
- [Introduction](#introduction)
- [Version](#version)
- [Quick Start](#quick-start)
- [Usage](#usage)
- [Persistence](#persistence)
- [SSL Certificates](#ssl-certificates)
- [Base Image](#base-image)
- [Ejabberd Configuration](#ejabberd-configuration)
- [Cluster Example](#cluster-example)
- [Runtime Configuration](#runtime-configuration)
- [Served Hostnames](#served-hostnames)
- [Authentication](#authentication)
- [Admins](#admins)
- [Users](#users)
- [SSL](#ssl)
- [Erlang](#erlang)
- [Modules](#modules)
- [Logging](#logging)
- [Mount Configurations](#mount-configurations)
- [Erlang Configuration](#erlang-configuration)
- [Maintenance](#maintenance)
- [Register Users](#register-users)
- [Creating Backups](#creating-backups)
@@ -28,11 +30,29 @@ ejabberd container
# Introduction
Dockerfile to build an [ejabberd](https://www.ejabberd.im/) container image.
This [ejabberd][] docker container is based on the work done by [rroemhild][]. See more [in this blogpost][].
This container includes the necessary files to build your own containerized ejabberd,
but *IT IS NOT* used to generate official images on the docker [hub][].
This container is not maintained by [ProcessOne][].
Docker Tag Names are based on ejabberd versions in git [tags][]. The image tag `:latest` is based on the master branch.
[ProcessOne][] provides and maintain official containers on the docker [hub][], which targets developers for now and will becomes production ready in a near future.
These [new containers] allow to build and run ejabberd in a simple and lightweight environment.
[tags]: https://github.com/rroemhild/ejabberd/tags
[ejabberd]: https://www.ejabberd.im/
[rroemhild]: https://github.com/rroemhild/docker-ejabberd/
[in this blogpost]: https://blog.process-one.net/ejabberd-16-12/
[hub]: https://hub.docker.com/r/ejabberd/ecs/
[new containers]: https://github.com/processone/docker-ejabberd/
[ProcessOne]: https://www.process-one.net/
## Version
Current Version: `17.08`
Docker Tag Names are based on ejabberd versions in git [branches][] and [tags][]. The image tag `:latest` is based on the master branch.
[tags]: https://github.com/rroemhild/docker-ejabberd/tags
[branches]: https://github.com/rroemhild/docker-ejabberd/branches
# Quick Start
@@ -46,13 +66,20 @@ docker run -d \
-p 5280:5280 \
-h 'xmpp.example.de' \
-e "XMPP_DOMAIN=example.de" \
-e "ERLANG_NODE=nodename" \
-e "ERLANG_NODE=ejabberd" \
-e "EJABBERD_ADMINS=admin@example.de admin2@example.de" \
-e "EJABBERD_USERS=admin@example.de:password1234 admin2@example.de" \
-e "TZ=Europe/Berlin" \
rroemhild/ejabberd
```
or with the [docker-compose](examples/docker-compose/docker-compose.yml) example
```bash
wget https://raw.githubusercontent.com/rroemhild/docker-ejabberd/master/examples/docker-compose/docker-compose.yml
docker-compose up
```
# Usage
## Persistence
@@ -100,7 +127,11 @@ ADD ./example.com.pem /opt/ejabberd/ssl/example.com.pem
If you need root privileges switch to `USER root` and go back to `USER ejabberd` when you're done.
# Ejabberd Configuration
## Cluster Example
The [docker-compose-cluster](examples/docker-compose-cluster) example demonstrates how to extend this container image to setup a multi-master cluster.
# Runtime Configuration
You can additionally provide extra runtime configuration in a downstream image by replacing the config template `ejabberd.yml.tpl` with one based on this image's template and include extra interpolation of environment variables. The template is parsed by Jinja2 with the runtime environment (equivalent to Python's `os.environ` available as `env`).
@@ -138,6 +169,26 @@ EJABBERD_EXTAUTH_CACHE=600
```
**EJABBERD_EXTAUTH_INSTANCES** must be an integer with a minimum value of 1. **EJABBERD_EXTAUTH_CACHE** can be set to "false" or an integer value representing cache time in seconds. Note that caching should not be enabled if internal auth is also enabled.
### Password format
The variable `EJABBERD_AUTH_PASSWORD_FORMAT` controls in which format user passwords are
stored. Possible values are `plain` and `scram`. The default is to store
[SCRAM](https://en.wikipedia.org/wiki/Salted_Challenge_Response_Authentication_Mechanism)bled
passwords, meaning that it is impossible to obtain the original plain password from the
stored information.
NOTE: SCRAM does not work with SIP/TURN foreign authentication methods. In this case, you
may have to disable the option. More details can be found here:
https://docs.ejabberd.im/admin/configuration/#internal
If using SCRAM with an SQL database that has plaintext passwords stored, use the command
```
ejabberdctl convert_to_scram example.org
```
to convert all your existing plaintext passwords to scrambled format.
### MySQL Authentication
Set `EJABBERD_AUTH_METHOD=external` and `EJABBERD_EXTAUTH_PROGRAM=/opt/ejabberd/scripts/lib/auth_mysql.py` to enable MySQL authentication. Use the following environment variables to configure the database connection and the layout of the database. Password changing, registration, and unregistration are optional features and are enabled only if the respective queries are provided.
@@ -215,28 +266,38 @@ EJABBERD_USERS=admin@example.ninja:password1234 user1@test.com user1@xyz.io
```
## SSL
- **EJABBERD_SKIP_MAKE_SSLCERT**: Skip generating ssl certificates. Default: false
- **EJABBERD_SSLCERT_HOST**: SSL Certificate for the hostname.
- **EJABBERD_SSLCERT_EXAMPLE_COM**: SSL Certificates for XMPP domains.
- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Default: `true`.
- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Default: `true`.
- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Default: `true`.
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Default: `false`.
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Default: `true`.
- **EJABBERD_CIPHERS**: Cipher suite. Default: `HIGH:!aNULL:!3DES`.
- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Default: `false`.
- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Defaults
to `true`.
- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Defaults to `true`.
- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Defaults to `true`.
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Defaults to `false`.
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Defaults to `true`.
- **EJABBERD_CIPHERS**: Cipher suite. Defaults to `HIGH:!aNULL:!3DES`.
- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Defaults to `false`.
- **EJABBERD_SKIP_MAKE_DHPARAM**: Skip generating DH params. Default: false
## Erlang
- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `ejabberd` lets erlang add the hostname. Defaults to `ejabberd@localhost`.
- **ERLANG_COOKIE**: Set erlang cookie. Defaults to auto-generated cookie.
- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd.
## Modules
- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Default: `false`.
- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Default: `false`.
- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Default: `true`.
- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Default: `true`.
- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Default: `true`.
- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Defaults to `false`.
- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Defaults to `false`.
- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Defaults to `true`.
- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Defaults to `true`.
- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Defaults to `true`.
- **EJABBERD_SOURCE_MODULES**: List of modules, which will be installed from sources localized in ${EJABBERD_HOME}/module_source.
- **EJABBERD_CONTRIB_MODULES**: List of modules, which will be installed from contrib repository.
- **EJABBERD_RESTART_AFTER_MODULE_INSTALL**: If any modules were installed, restart the server, if the option is enabled.
- **EJABBERD_CUSTOM_AUTH_MODULE_OVERRIDE**: If a custom module was defined for handling auth, we need to override the pre-defined auth methods in the config.
## Logging
Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Default: `4` (Info).
Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Defaults to `4` (Info).
```
loglevel: Verbosity of log files generated by ejabberd.
@@ -276,25 +337,6 @@ Example configuration files can be downloaded from the ejabberd [github](https:/
When these files exist in ```/opt/ejabberd/conf```, the run script will ignore the configuration templates.
## Erlang Configuration
With the following environment variables you can configure options that are passed by ejabberdctl to the erlang runtime system when starting ejabberd.
- **POLL**: Set to `false` to disable Kernel polling. Default: `true`.
- **SMP**: SMP support `enable`, `auto`, `disable`. Default: `auto`.
- **ERL_MAX_PORTS**: Maximum number of simultaneously open Erlang ports. Default: `32000`.
- **FIREWALL_WINDOW**: Range of allowed ports to pass through a firewall. Default: `not defined`.
- **INET_DIST_INTERFACE**: IP address where this Erlang node listens other nodes. Default: `0.0.0.0`.
- **ERL_EPMD_ADDRESS**: IP addresses where epmd listens for connections. Default: `0.0.0.0`.
- **ERL_PROCESSES**: Maximum number of Erlang processes. Default: `250000`.
- **ERL_MAX_ETS_TABLES**: Maximum number of Erlang processes. Default: `1400`.
- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd. Default: `-noshell`
- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `nodename` lets erlang add the hostname. Default: `ejabberd@localhost`.
- **EJABBERD_CONFIG_PATH**: ejabberd configuration file. Default: `/opt/ejabberd/conf/ejabberd.yml`.
- **CONTRIB_MODULES_PATH**: contributed ejabberd modules path. Default: `/opt/ejabberd/modules`.
- **CONTRIB_MODULES_CONF_DIR**: configuration directory for contributed modules. Default: `/opt/ejabberd/modules/conf`.
- **ERLANG_COOKIE**: Set erlang cookie. Default is to auto-generated cookie.
# Maintenance
The `ejabberdctl` command is in the search path and can be run by:
+5 -1
View File
@@ -123,8 +123,10 @@ auth_method:
- {{ auth_method }}
{%- endfor %}
auth_password_format: {{ env.get('EJABBERD_AUTH_PASSWORD_FORMAT', 'scram') }}
{%- if 'anonymous' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
anonymous_protocol: login_anon
anonymous_protocol: both
allow_multiple_connections: true
{%- endif %}
@@ -347,6 +349,8 @@ modules:
- "flat"
- "hometree"
- "pep" # pep requires mod_caps
mod_push: {}
mod_push_keepalive: {}
mod_register:
##
## Protect In-Band account registrations with CAPTCHA.
@@ -0,0 +1,3 @@
FROM rroemhild/ejabberd
ENV EJABBERD_HOME /opt/ejabberd
COPY ./scripts $EJABBERD_HOME/scripts
@@ -0,0 +1,23 @@
# Ejabberd cluster with docker compose
This example uses [dnsdocker](https://github.com/tonistiigi/dnsdock) to discover other nodes and setup a multi-master cluster.
Build the ejabberd cluster image:
```bash
git clone https://github.com/rroemhild/docker-ejabberd.git
cd docker-ejabberd/examples/docker-compose-cluster
docker-compose build
```
Start dnsdocker and the first ejabberd node:
```bash
docker-compose up -d
```
Wait until the first ejabberd node is up and running `docker-compose logs ejabberd`, then add some ejabberd nodes to the cluster:
```bash
docker-compose scale ejabberd=4
```
@@ -0,0 +1,25 @@
dnsdock:
image: tonistiigi/dnsdock
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 172.17.42.1:53:53/udp
ejabberd:
build: .
ports:
- 5222
- 5269
- 5280
environment:
- XMPP_DOMAIN=example.com
- ERLANG_NODE=ejabberd
- EJABBERD_ADMINS=admin@example.com
- EJABBERD_USERS=admin@example.com:test321 user@example.com
- ERLANG_COOKIE=testCluster
- SKIP_MODULES_UPDATE=true
- EJABBERD_CLUSTER=true
- USE_DNS=true
dns: 172.17.42.1
domainname: dockercomposecluster_ejabberd.docker
tty: true
@@ -0,0 +1,37 @@
# overwrite get_nodename to discover hostname from DNS
get_nodename() {
local hostname=${HOSTNAME}
# get hostname from dns
if ( is_true ${USE_DNS} ); then
# wait for dns registration
sleep 1
nodename=$(discover_dns_hostname ${HOSTIP})
is_set ${nodename} \
&& hostname=${nodename}
fi
echo $hostname
return 0
}
# discover hostname from dns with a reverse lookup
discover_dns_hostname() {
local hostip=$1
# try to get the hostname from dns
local dnsname=$(drill -x ${hostip} \
| grep PTR \
| awk '{print $5}' \
| grep -E "^[a-zA-Z0-9]+([-._]?[a-zA-Z0-9]+)*.[a-zA-Z]+\.$" \
| cut -d '.' -f1 \
| tail -1)
is_set ${dnsname} \
&& echo ${dnsname}
return 0
}
@@ -0,0 +1,28 @@
#!/bin/bash
set -e
source "${EJABBERD_HOME}/scripts/lib/base_config.sh"
source "${EJABBERD_HOME}/scripts/lib/config.sh"
source "${EJABBERD_HOME}/scripts/lib/base_functions.sh"
source "${EJABBERD_HOME}/scripts/lib/functions.sh"
get_cluster_node_from_dns() {
local cluster_host=$(drill ${DOMAINNAME} \
| grep ${DOMAINNAME} \
| grep -v ${HOSTIP} \
| awk '{print $5}' \
| grep -v "^$" \
| head -1)
echo $(discover_dns_hostname ${cluster_host})
}
file_exist ${FIRST_START_DONE_FILE} \
&& exit 0
join_cluster $(get_cluster_node_from_dns)
exit 0
+1
View File
@@ -0,0 +1 @@
# simple docker-compose example
@@ -0,0 +1,11 @@
ejabberd:
image: rroemhild/ejabberd
ports:
- 5222:5222
- 5269:5269
- 5280:5280
environment:
- ERLANG_NODE=ejabberd
- XMPP_DOMAIN=example.com xyz.io
- EJABBERD_ADMINS=admin@example.com
- EJABBERD_USERS=admin@example.com:password4321 user1@xyz.io
+2 -2
View File
@@ -3,7 +3,7 @@ readonly HOSTNAME=$(hostname -f)
readonly DOMAINNAME=$(hostname -d)
readonly ERLANGCOOKIEFILE="${EJABBERD_HOME}/.erlang.cookie"
readonly EJABBERDCTL="/sbin/ejabberdctl"
readonly EJABBERDCTL="/usr/local/sbin/ejabberdctl"
readonly CONFIGFILE="${EJABBERD_HOME}/conf/ejabberd.yml"
readonly CONFIGTEMPLATE="${EJABBERD_HOME}/conf/ejabberd.yml.tpl"
readonly CTLCONFIGFILE="${EJABBERD_HOME}/conf/ejabberdctl.cfg"
@@ -11,7 +11,7 @@ readonly CTLCONFIGTEMPLATE="${EJABBERD_HOME}/conf/ejabberdctl.cfg.tpl"
readonly SSLCERTDIR="${EJABBERD_HOME}/ssl"
readonly SSLCERTHOST="${SSLCERTDIR}/host.pem"
readonly SSLDHPARAM="${SSLCERTDIR}/dh.pem"
readonly LOGDIR="/var/log/ejabberd"
readonly LOGDIR="/usr/local/var/log/ejabberd"
readonly FIRST_START_DONE_FILE="/${EJABBERD_HOME}/first-start-done"
readonly CLUSTER_NODE_FILE="/${EJABBERD_HOME}/cluster-done"
+8 -1
View File
@@ -111,8 +111,10 @@ hosts:
## 'CERTFILE': "/path/to/xmpp.pem"
## 'CIPHERS': "ECDH:DH:!3DES:!aNULL:!eNULL:!MEDIUM@STRENGTH"
## 'TLSOPTS':
## - "no_sslv2"
## - "no_sslv3"
## - "no_tlsv1"
## - "no_tlsv1_1"
## - "cipher_server_preference"
## - "no_compression"
## 'DHFILE': "/path/to/dhparams.pem" # generated with: openssl dhparam -out dhparams.pem 2048
@@ -231,7 +233,7 @@ listen:
##
## s2s_use_starttls: Enable STARTTLS for S2S connections.
## Allowed values are: false optional required required_trusted
## Allowed values are: false, optional or required
## You must specify a certificate file.
##
## s2s_use_starttls: required
@@ -766,6 +768,11 @@ modules:
mod_time: {}
mod_vcard:
search: false
mod_vcard_xupdate: {}
## Convert all avatars posted by Android clients from WebP to JPEG
mod_avatar:
convert:
webp: jpeg
mod_version: {}
mod_stream_mgmt: {}
## Non-SASL Authentication (XEP-0078) is now disabled by default
+50 -51
View File
@@ -7,6 +7,7 @@ ERL_MAX_PORTS=32000
ERL_PROCESSES=250000
ERL_MAX_ETS_TABLES=1400
FIREWALL_WINDOW=""
INET_DIST_INTERFACE=""
ERLANG_NODE=ejabberd@localhost
# define default environment variables
@@ -16,7 +17,7 @@ EPMD="{{epmd}}"
INSTALLUSER="{{installuser}}"
# check the proper system user is used
case `id -un` in
case $(id -un) in
"$INSTALLUSER")
EXEC_CMD="as_current_user"
;;
@@ -40,7 +41,7 @@ esac
# parse command line parameters
for arg; do
case $1 in
case $arg in
-n|--node) ERLANG_NODE_ARG=$2; shift;;
-s|--spool) SPOOL_DIR=$2; shift;;
-l|--logs) LOGS_DIR=$2; shift;;
@@ -55,25 +56,25 @@ for arg; do
done
# define ejabberd variables if not already defined from the command line
: ${ETC_DIR:={{sysconfdir}}/ejabberd}
: ${LOGS_DIR:={{localstatedir}}/log/ejabberd}
: ${SPOOL_DIR:={{localstatedir}}/lib/ejabberd}
: ${EJABBERD_CONFIG_PATH:="$ETC_DIR"/ejabberd.yml}
: ${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR"/ejabberdctl.cfg}
: "${ETC_DIR:="{{sysconfdir}}/ejabberd"}"
: "${LOGS_DIR:="{{localstatedir}}/log/ejabberd"}"
: "${SPOOL_DIR:="{{localstatedir}}/lib/ejabberd"}"
: "${EJABBERD_CONFIG_PATH:="$ETC_DIR/ejabberd.yml"}"
: "${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR/ejabberdctl.cfg"}"
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
[ "$ERLANG_NODE_ARG" != "" ] && ERLANG_NODE=$ERLANG_NODE_ARG
[ -n "$ERLANG_NODE_ARG" ] && ERLANG_NODE="$ERLANG_NODE_ARG"
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s"
: ${EJABBERD_DOC_PATH:={{docdir}}}
: ${EJABBERD_LOG_PATH:="$LOGS_DIR"/ejabberd.log}
: "${EJABBERD_DOC_PATH:="{{docdir}}"}"
: "${EJABBERD_LOG_PATH:="$LOGS_DIR/ejabberd.log"}"
# define erl parameters
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
if [ "$FIREWALL_WINDOW" != "" ] ; then
if [ -n "$FIREWALL_WINDOW" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
fi
if [ "$INET_DIST_INTERFACE" != "" ] ; then
if [ -n "$INET_DIST_INTERFACE" ] ; then
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
if [ "$INET_DIST_INTERFACE2" != "" ] ; then
if [ -n "$INET_DIST_INTERFACE2" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
fi
fi
@@ -115,12 +116,12 @@ exec_cmd()
exec_erl()
{
NODE=$1; shift
exec_cmd "$ERL" ${S:--}name $NODE $ERLANG_OPTS "$@"
exec_cmd "$ERL" ${S:--}name "$NODE" $ERLANG_OPTS "$@"
}
exec_iex()
{
NODE=$1; shift
exec_cmd "$IEX" ${S:---}name $NODE --erl "$ERLANG_OPTS" "$@"
exec_cmd "$IEX" -${S:--}name "$NODE" --erl "$ERLANG_OPTS" "$@"
}
# usage
@@ -143,7 +144,7 @@ debugwarning()
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press return to continue"
read foo
read -r
echo ""
fi
}
@@ -166,7 +167,7 @@ livewarning()
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press return to continue"
read foo
read -r
echo ""
fi
}
@@ -196,12 +197,12 @@ help()
uid()
{
uuid=$(uuidgen 2>/dev/null)
[ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$(cat /proc/sys/kernel/random/uuid)
[ -z "$uuid" ] && uuid=$(printf "%X" $RANDOM$(date +%M%S)$$)
[ -z "$uuid" ] && [ -f /proc/sys/kernel/random/uuid ] && uuid=$(cat /proc/sys/kernel/random/uuid)
[ -z "$uuid" ] && uuid=$(printf "%X" "${RANDOM:-$$}$(date +%M%S)")
uuid=${uuid%%-*}
[ $# -eq 0 ] && echo ${uuid}-${ERLANG_NODE}
[ $# -eq 1 ] && echo ${uuid}-${1}-${ERLANG_NODE}
[ $# -eq 2 ] && echo ${uuid}-${1}@${2}
[ $# -eq 0 ] && echo "${uuid}-${ERLANG_NODE}"
[ $# -eq 1 ] && echo "${uuid}-${1}-${ERLANG_NODE}"
[ $# -eq 2 ] && echo "${uuid}-${1}@${2}"
}
# stop epmd if there is no other running node
@@ -215,19 +216,17 @@ stop_epmd()
check_start()
{
"$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
ps ux | grep -v grep | grep -q " $ERLANG_NODE " && {
pgrep -f "$ERLANG_NODE" >/dev/null && {
echo "ERROR: The ejabberd node '$ERLANG_NODE' is already running."
exit 4
} || {
ps ux | grep -v grep | grep -q beam && {
echo "ERROR: The ejabberd node '$ERLANG_NODE' is registered,"
echo " but no related beam process has been found."
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
exit 5
} || {
"$EPMD" -kill >/dev/null
}
}
pgrep beam >/dev/null && {
echo "ERROR: The ejabberd node '$ERLANG_NODE' is registered,"
echo " but no related beam process has been found."
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
exit 5
}
"$EPMD" -kill >/dev/null
}
}
@@ -236,17 +235,17 @@ wait_status()
{
# args: status try delay
# return: 0 OK, 1 KO
timeout=$2
timeout="$2"
status=4
while [ $status -ne $1 ] ; do
sleep $3
timeout=`expr $timeout - 1`
while [ "$status" -ne "$1" ] ; do
sleep "$3"
timeout=$((timeout - 1))
if [ $timeout -eq 0 ] ; then
status=$1
status="$1"
else
exec_erl $(uid ctl) -hidden -noinput -s ejabberd_ctl \
-extra $ERLANG_NODE $NO_TIMEOUT status > /dev/null
status=$?
exec_erl "$(uid ctl)" -hidden -noinput -s ejabberd_ctl \
-extra "$ERLANG_NODE" $NO_TIMEOUT status > /dev/null
status="$?"
fi
done
[ $timeout -gt 0 ]
@@ -263,38 +262,38 @@ cd "$SPOOL_DIR" || {
case $1 in
start)
check_start
exec_erl $ERLANG_NODE $EJABBERD_OPTS -noinput -detached
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -noinput -detached
;;
foreground)
check_start
exec_erl $ERLANG_NODE $EJABBERD_OPTS -noinput
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -noinput
;;
live)
livewarning
check_start
exec_erl $ERLANG_NODE $EJABBERD_OPTS
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS
;;
debug)
debugwarning
exec_erl $(uid debug) -hidden -remsh $ERLANG_NODE
exec_erl "$(uid debug)" -hidden -remsh "$ERLANG_NODE"
;;
etop)
exec_erl $(uid top) -hidden -node $ERLANG_NODE -s etop \
exec_erl "$(uid top)" -hidden -node "$ERLANG_NODE" -s etop \
-s erlang halt -output text
;;
iexdebug)
debugwarning
exec_iex $(uid debug) --remsh "$ERLANG_NODE"
exec_iex "$(uid debug)" --remsh "$ERLANG_NODE"
;;
iexlive)
livewarning
exec_iex $ERLANG_NODE --erl "$EJABBERD_OPTS" --app ejabberd
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS" --app ejabberd
;;
ping)
PEER=${2:-$ERLANG_NODE}
[ "$PEER" = "${PEER%.*}" ] && PS="-s"
exec_cmd "$ERL" ${PS:--}name $(uid ping $(hostname $PS)) $ERLANG_OPTS \
-noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"$PEER"')])' \
exec_cmd "$ERL" ${PS:--}name "$(uid ping "$(hostname $PS)")" $ERLANG_OPTS \
-noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"'$PEER'"')])' \
-s erlang halt -output text
;;
started)
@@ -304,8 +303,8 @@ case $1 in
wait_status 3 30 2 && stop_epmd # wait 30x2s before timeout
;;
*)
exec_erl $(uid ctl) -hidden -noinput -s ejabberd_ctl \
-extra $ERLANG_NODE $NO_TIMEOUT "$@"
exec_erl "$(uid ctl)" -hidden -noinput -s ejabberd_ctl \
-extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
result=$?
case $result in
2|3) help;;
+1
View File
@@ -0,0 +1 @@
-define(T(S), <<S>>).
+1 -1
View File
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "17.8.0",
version: "17.9.0",
description: description(),
elixir: "~> 1.4",
elixirc_paths: ["lib"],
+32
View File
@@ -0,0 +1,32 @@
-module(override_opts).
-export([preprocess/2]).
override_opts(override, Config, Opts) ->
lists:foldl(fun({Opt, Value}, Conf) ->
rebar_config:set(Conf, Opt, Value)
end, Config, Opts);
override_opts(add, Config, Opts) ->
lists:foldl(fun({Opt, Value}, Conf) ->
V = rebar_config:get_local(Conf, Opt, []),
rebar_config:set(Conf, Opt, [Value | V])
end, Config, Opts).
preprocess(Config, _Dirs) ->
Overrides = rebar_config:get_local(Config, overrides, []),
TopOverrides = case rebar_config:get_xconf(Config, top_overrides, []) of
[] -> Overrides;
Val -> Val
end,
Config2 = rebar_config:set_xconf(Config, top_overrides, TopOverrides),
Config3 = case rebar_app_utils:load_app_file(Config2, _Dirs) of
{ok, C, AppName, _AppData} ->
lists:foldl(fun({Type, AppName2, Opts}, Conf1) when
AppName2 == AppName ->
override_opts(Type, Conf1, Opts);
(_, Conf2) ->
Conf2
end, C, TopOverrides);
_ ->
Config2
end,
{ok, Config3, []}.
Vendored
BIN
View File
Binary file not shown.
+20 -16
View File
@@ -20,30 +20,31 @@
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager",
{tag, {if_version_above, "17", "3.4.2", "3.2.1"}}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.9"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.10"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.15"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.14"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.10"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.11"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.16"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.10"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.24"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.15"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.11"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.2"}}},
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.14"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.15"}}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.15"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.16"}}}},
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
{tag, "1.0.3"}}}},
{tag, "1.0.4"}}}},
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
{tag, "1.1.3"}}}},
{tag, "1.1.4"}}}},
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
{tag, "1.1.5"}}}},
{if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam",
{tag, "1.0.3"}}}},
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
{tag, "1.0.2"}}}},
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
{tag, "2.4.1"}}}},
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/processone/riak-erlang-client.git",
{tag, {if_version_above, "19", "develop", "2.5.3"}}}}},
{if_var_true, graphics, {eimp, ".*", {git, "https://github.com/processone/eimp.git", {tag, "1.0.1"}}}},
%% Elixir support, needed to run tests
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
{tag, {if_version_above, "17", "v1.4.4", "v1.1.1"}}}}},
@@ -51,7 +52,7 @@
{if_not_rebar3, {if_var_true, elixir, {rebar_elixir_plugin, ".*",
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}}},
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
{tag, "1.0.5"}}}},
{tag, "1.0.6"}}}},
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
{tag, "0.8.4"}}}},
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
@@ -74,6 +75,7 @@
p1_oauth2,
epam,
ezlib,
eimp,
iconv]}}.
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl", "src/mod_push.erl"]}.
@@ -87,11 +89,13 @@
{if_var_true, debug, debug_info},
{if_var_true, sip, {d, 'SIP'}},
{if_var_true, stun, {d, 'STUN'}},
{if_var_true, graphics, {d, 'GRAPHICS'}},
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
{if_var_match, db_type, mssql, {d, 'mssql'}},
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
{if_have_fun, {crypto, strong_rand_bytes, 1}, {d, 'STRONG_RAND_BYTES'}},
{if_have_fun, {rand, uniform, 1}, {d, 'RAND_UNIFORM'}},
{if_have_fun, {gb_sets, iterator_from, 2}, {d, 'GB_SETS_ITERATOR_FROM'}},
{if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
{if_var_true, hipe, native},
@@ -103,7 +107,7 @@
{if_rebar3, {plugins, [rebar3_hex, {provider_asn1, "0.2.0"}]}}.
{if_not_rebar3, {plugins, [
deps_erl_opts, override_deps_versions,
deps_erl_opts, override_deps_versions, override_opts,
{if_var_true, elixir, rebar_elixir_compiler},
{if_var_true, elixir, rebar_exunit}
]}}.
@@ -153,12 +157,12 @@
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
{if_var_true, pam, {"epam", []}},
{if_var_true, zlib, {"ezlib", []}},
{if_var_true, graphics, {"eimp", []}},
{if_var_true, iconv, {"iconv", []}}]}.
{port_env, [{"CFLAGS", "-g -O2 -Wall"}]}.
{port_specs, [{"priv/lib/jid.so", ["c_src/jid.c"]}]}.
%% Local Variables:
%% mode: erlang
%% End:
+6 -6
View File
@@ -255,9 +255,9 @@ CtParams = fun(CompileOpts) ->
GenDepConfigureLine =
fun(DepPath, Flags) ->
["sh -c 'if test ! -f ",DepPath,"config.status -o ",
"config.status -nt ",DepPath,"config.status; ",
"then (cd ", DepPath, " && ",
["sh -c 'if test ! -f config.status -o ",
"../../config.status -nt config.status; ",
"then (",
"CFLAGS=\"", CFlags,"\" ",
"CPPFLAGS=\"", CPPFlags, "\" "
"LDFLAGS=\"", LDFlags, "\"",
@@ -269,8 +269,8 @@ GenDepsConfigure =
fun(Hooks) ->
lists:map(fun({Pkg, Flags}) ->
DepPath = ResolveDepPath("deps/" ++ Pkg ++ "/"),
{'compile',
lists:flatten(GenDepConfigureLine(DepPath, Flags))}
{add, list_to_atom(Pkg), [{pre_hooks, {'compile',
lists:flatten(GenDepConfigureLine(DepPath, Flags))}}]}
end, Hooks)
end,
@@ -326,7 +326,7 @@ Rules = [
AppendList([{coveralls, ".*", {git, "https://github.com/markusn/coveralls-erl.git", "master"}}]), []},
{[post_hooks], [cover_enabled], os:getenv("TRAVIS") == "true",
AppendList2(TravisPostHooks), [], false},
{[pre_hooks], [post_hook_configure], true,
{[overrides], [post_hook_configure], true,
AppendList2(GenDepsConfigure), [], []},
{[ct_extra_params], [eunit_compile_opts], true,
AppendStr2(CtParams), "", []},
+1 -1
View File
@@ -102,7 +102,7 @@ CREATE TABLE archive (
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE FULLTEXT INDEX i_text ON archive(txt);
CREATE INDEX i_username USING BTREE ON archive(username);
CREATE INDEX i_username_timestamp USING BTREE ON archive(username,timestamp);
CREATE INDEX i_timestamp USING BTREE ON archive(timestamp);
CREATE INDEX i_peer USING BTREE ON archive(peer);
CREATE INDEX i_bare_peer USING BTREE ON archive(bare_peer);
+10 -1
View File
@@ -146,7 +146,8 @@ start_apps() ->
ejabberd:start_app(fast_yaml),
ejabberd:start_app(fast_tls),
ejabberd:start_app(xmpp),
ejabberd:start_app(cache_tab).
ejabberd:start_app(cache_tab),
start_eimp().
setup_if_elixir_conf_used() ->
case ejabberd_config:is_using_elixir_config() of
@@ -170,3 +171,11 @@ start_elixir_application() ->
_ ->
ok
end.
-ifdef(GRAPHICS).
start_eimp() ->
ejabberd:start_app(eimp).
-else.
start_eimp() ->
ok.
-endif.
+4 -4
View File
@@ -303,9 +303,9 @@ tls_options(#{lserver := LServer, tls_options := DefaultOpts,
{true, CertFile} when CertFile /= undefined -> DefaultOpts;
{_, _} ->
case ejabberd_config:get_option(
{c2s_certfile, LServer},
{domain_certfile, LServer},
ejabberd_config:get_option(
{domain_certfile, LServer})) of
{c2s_certfile, LServer})) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
@@ -411,7 +411,7 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang,
ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]),
?INFO_MSG("(~s) Forbidden c2s session for ~s",
[SockMod:pp(Socket), jid:encode(JID)]),
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
{error, xmpp:err_not_allowed(Txt, Lang), State}
end
end.
@@ -658,7 +658,7 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
MyBareJID = jid:remove_resource(JID),
case acl:match_rule(LServer, Access, MyBareJID) of
deny ->
ErrText = <<"Denied by ACL">>,
ErrText = <<"Access denied by service policy">>,
Err = xmpp:err_forbidden(ErrText, Lang),
send_error(State, Pres, Err);
allow ->
+3 -2
View File
@@ -28,7 +28,7 @@
-export([start/0, load_file/1, reload_file/0, read_file/1,
get_option/1, get_option/2, add_option/2, has_option/1,
get_vh_by_auth_method/1, is_file_readable/1,
get_vh_by_auth_method/1,
get_version/0, get_myhosts/0, get_mylang/0, get_lang/1,
get_ejabberd_config_path/0, is_using_elixir_config/0,
prepare_opt_val/4, transform_options/1, collect_options/1,
@@ -46,11 +46,12 @@
get_global_option/2, get_local_option/2,
get_global_option/3, get_local_option/3,
get_option/3]).
-export([is_file_readable/1]).
-deprecated([{add_global_option, 2}, {add_local_option, 2},
{get_global_option, 2}, {get_local_option, 2},
{get_global_option, 3}, {get_local_option, 3},
{get_option, 3}]).
{get_option, 3}, {is_file_readable, 1}]).
-include("ejabberd.hrl").
-include("logger.hrl").
+2
View File
@@ -68,6 +68,8 @@ init([]) ->
_ -> ok
end,
ejabberd:start_app(mnesia, permanent),
?DEBUG("Waiting for Mnesia tables synchronization...", []),
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity),
Schema = read_schema_file(),
{ok, #state{schema = Schema}};
false ->
+1 -1
View File
@@ -436,7 +436,7 @@ process(_Handlers,
?INPUT(<<"hidden">>, <<"scope">>, Scope),
?INPUT(<<"hidden">>, <<"state">>, State),
?BR,
?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?CT(<<": ">>)]),
?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?C(<<": ">>)]),
?XAE(<<"select">>, [{<<"name">>, <<"ttl">>}],
[
?XAC(<<"option">>, [{<<"value">>, <<"3600">>}],<<"1 Hour">>),
+10 -4
View File
@@ -199,9 +199,9 @@ dirty_get_connections() ->
-spec tls_options(binary(), [proplists:property()]) -> [proplists:property()].
tls_options(LServer, DefaultOpts) ->
TLSOpts1 = case ejabberd_config:get_option(
{s2s_certfile, LServer},
{domain_certfile, LServer},
ejabberd_config:get_option(
{domain_certfile, LServer})) of
{s2s_certfile, LServer})) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
@@ -369,7 +369,7 @@ do_route(Packet) ->
<<"Server connections to local "
"subdomains are forbidden">>, Lang);
forbidden ->
xmpp:err_forbidden(<<"Denied by ACL">>, Lang);
xmpp:err_forbidden(<<"Access denied by service policy">>, Lang);
internal_server_error ->
xmpp:err_internal_server_error()
end,
@@ -726,7 +726,13 @@ opt_type(s2s_use_starttls) ->
(false) -> false;
(optional) -> optional;
(required) -> required;
(required_trusted) -> required_trusted
(required_trusted) ->
?WARNING_MSG("The value 'required_trusted' of option "
"'s2s_use_starttls' is deprected and will be "
"unsupported in future releases. Instead, "
"set it to 'required' and make sure "
"mod_s2s_dialback is *NOT* loaded", []),
required_trusted
end;
opt_type(s2s_zlib) ->
fun(B) when is_boolean(B) -> B end;
+1 -1
View File
@@ -374,7 +374,7 @@ mk_bounce_error(_Lang, _State) ->
-spec get_delay() -> non_neg_integer().
get_delay() ->
MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300),
crypto:rand_uniform(1, MaxDelay).
randoms:uniform(MaxDelay).
-spec set_idle_timeout(state()) -> state().
set_idle_timeout(#{on_route := send, server := LServer} = State) ->
+1 -1
View File
@@ -199,7 +199,7 @@ handle_info({route, Packet}, #{access := Access} = State) ->
xmpp_stream_in:send(State, Packet);
deny ->
Lang = xmpp:get_lang(Packet),
Err = xmpp:err_not_allowed(<<"Denied by ACL">>, Lang),
Err = xmpp:err_not_allowed(<<"Access denied by service policy">>, Lang),
ejabberd_router:route_error(Packet, Err),
State
end;
+2 -1
View File
@@ -317,7 +317,8 @@ get_session_sids(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
online(get_sessions(Mod, LUser, LServer)).
Sessions = online(get_sessions(Mod, LUser, LServer)),
[SID || #session{sid = SID} <- Sessions].
-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
+12 -16
View File
@@ -33,7 +33,7 @@
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
compile_exprs/2, join_atoms/2, try_read_file/1]).
compile_exprs/2, join_atoms/2, try_read_file/1, have_eimp/0]).
%% Deprecated functions
-export([decode_base64/1, encode_base64/1]).
@@ -204,25 +204,21 @@ join_atoms(Atoms, Sep) ->
%% in configuration validators only.
-spec try_read_file(file:filename_all()) -> binary().
try_read_file(Path) ->
Res = case file:read_file_info(Path) of
{ok, #file_info{type = Type, access = Access}} ->
case {Type, Access} of
{regular, read} -> ok;
{regular, read_write} -> ok;
{regular, _} -> {error, file:format_error(eaccess)};
_ -> {error, "not a regular file"}
end;
{error, Why} ->
{error, file:format_error(Why)}
end,
case Res of
ok ->
case file:open(Path, [read]) of
{ok, Fd} ->
file:close(Fd),
iolist_to_binary(Path);
{error, Reason} ->
?ERROR_MSG("Failed to read ~s: ~s", [Path, Reason]),
{error, Why} ->
?ERROR_MSG("Failed to read ~s: ~s", [Path, file:format_error(Why)]),
erlang:error(badarg)
end.
-ifdef(GRAPHICS).
have_eimp() -> true.
-else.
have_eimp() -> false.
-endif.
%%%===================================================================
%%% Internal functions
%%%===================================================================
+9 -3
View File
@@ -44,7 +44,7 @@
kick_session/4, status_num/2, status_num/1,
status_list/2, status_list/1, connected_users_info/0,
connected_users_vhost/1, set_presence/7,
get_presence/2, user_sessions_info/2, get_last/2,
get_presence/2, user_sessions_info/2, get_last/2, set_last/4,
% Accounts
set_password/3, check_password_hash/4, delete_old_users/1,
@@ -602,9 +602,9 @@ get_commands_spec() ->
]}}},
#ejabberd_commands{name = set_last, tags = [last],
desc = "Set last activity information",
longdesc = "Timestamp is the seconds since"
longdesc = "Timestamp is the seconds since "
"1970-01-01 00:00:00 UTC, for example: date +%s",
module = mod_last, function = store_last_info,
module = ?MODULE, function = set_last,
args = [{user, binary}, {host, binary}, {timestamp, integer}, {status, binary}],
args_example = [<<"user1">>,<<"myserver.com">>, 1500045311, <<"GoSleeping">>],
args_desc = ["User name", "Server name", "Number of seconds since epoch", "Status message"],
@@ -1437,6 +1437,12 @@ get_last(User, Server) ->
end,
{xmpp_util:encode_timestamp(Now), Status}.
set_last(User, Server, Timestamp, Status) ->
case mod_last:store_last_info(User, Server, Timestamp, Status) of
{ok, _} -> ok;
Error -> Error
end.
%%%
%%% Private Storage
%%%
+14 -6
View File
@@ -237,7 +237,7 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
deny ->
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow ->
{result, Feats}
end).
@@ -252,7 +252,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) -
case {acl:match_rule(LServer, Access1, From),
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
{error, xmpp:err_forbidden(Txt, Lang)};
_ ->
{result, []}
@@ -303,7 +303,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
-define(ITEMS_RESULT(Allow, Items, Lang),
case Allow of
deny ->
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow ->
{result, Items}
end).
@@ -417,7 +417,7 @@ commands_result(Allow, From, To, Request) ->
case Allow of
deny ->
Lang = Request#adhoc_command.lang,
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow ->
announce_commands(From, To, Request)
end.
@@ -843,7 +843,7 @@ add_store_hint(El) ->
-spec route_forbidden_error(stanza()) -> ok.
route_forbidden_error(Packet) ->
Lang = xmpp:get_lang(Packet),
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang),
ejabberd_router:route_error(Packet, Err).
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
@@ -913,4 +913,12 @@ import(LServer, {sql, _}, DBType, Tab, List) ->
mod_opt_type(access) -> fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) -> [access, db_type].
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
fun (I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end;
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[access, db_type, cache_life_time, cache_size,
use_cache, cache_missed].
+450
View File
@@ -0,0 +1,450 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 13 Sep 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 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.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%-------------------------------------------------------------------
-module(mod_avatar).
-behaviour(gen_mod).
%% gen_mod API
-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1]).
%% Hooks
-export([pubsub_publish_item/6, vcard_iq_convert/1, vcard_iq_publish/1]).
-include("xmpp.hrl").
-include("logger.hrl").
-include("pubsub.hrl").
-type convert_rules() :: {default | eimp:img_type(), eimp:img_type()}.
%%%===================================================================
%%% API
%%%===================================================================
start(Host, _Opts) ->
case misc:have_eimp() of
true ->
ejabberd_hooks:add(pubsub_publish_item, Host, ?MODULE,
pubsub_publish_item, 50),
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE,
vcard_iq_convert, 30),
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE,
vcard_iq_publish, 100);
false ->
?CRITICAL_MSG("ejabberd is built without "
"graphics support: reconfigure it with "
"--enable-graphics or disable '~s'",
[?MODULE]),
{error, graphics_not_compiled}
end.
stop(Host) ->
ejabberd_hooks:delete(pubsub_publish_item, Host, ?MODULE,
pubsub_publish_item, 50),
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_convert, 30),
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_publish, 100).
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[{mod_vcard, hard}, {mod_vcard_xupdate, hard}, {mod_pubsub, hard}].
%%%===================================================================
%%% Hooks
%%%===================================================================
pubsub_publish_item(LServer, ?NS_AVATAR_METADATA,
#jid{luser = LUser, lserver = LServer} = From,
#jid{luser = LUser, lserver = LServer} = Host,
ItemId, [Payload|_]) ->
try xmpp:decode(Payload) of
#avatar_meta{info = []} ->
delete_vcard_avatar(From);
#avatar_meta{info = Info} ->
Rules = get_converting_rules(LServer),
case get_meta_info(Info, Rules) of
#avatar_info{type = MimeType, id = ID, url = <<"">>} = I ->
case get_avatar_data(Host, ID) of
{ok, Data} ->
Meta = #avatar_meta{info = [I]},
Photo = #vcard_photo{type = MimeType,
binval = Data},
set_vcard_avatar(From, Photo,
#{avatar_meta => {ID, Meta}});
{error, _} ->
ok
end;
#avatar_info{type = MimeType, url = URL} ->
Photo = #vcard_photo{type = MimeType,
extval = URL},
set_vcard_avatar(From, Photo, #{})
end;
_ ->
?WARNING_MSG("invalid avatar metadata of ~s@~s published "
"with item id ~s",
[LUser, LServer, ItemId])
catch _:{xmpp_codec, Why} ->
?WARNING_MSG("failed to decode avatar metadata of ~s@~s: ~s",
[LUser, LServer, xmpp:format_error(Why)])
end;
pubsub_publish_item(_, _, _, _, _, _) ->
ok.
-spec vcard_iq_convert(iq()) -> iq() | {stop, stanza_error()}.
vcard_iq_convert(#iq{from = From, lang = Lang, sub_els = [VCard]} = IQ) ->
#jid{luser = LUser, lserver = LServer} = From,
case convert_avatar(LUser, LServer, VCard) of
{ok, MimeType, Data} ->
VCard1 = VCard#vcard_temp{
photo = #vcard_photo{type = MimeType,
binval = Data}},
IQ#iq{sub_els = [VCard1]};
pass ->
IQ;
{error, Reason} ->
stop_with_error(Lang, Reason)
end;
vcard_iq_convert(Acc) ->
Acc.
-spec vcard_iq_publish(iq()) -> iq() | {stop, stanza_error()}.
vcard_iq_publish(#iq{sub_els = [#vcard_temp{photo = undefined}]} = IQ) ->
publish_avatar(IQ, #avatar_meta{}, <<>>, <<>>, <<>>);
vcard_iq_publish(#iq{sub_els = [#vcard_temp{
photo = #vcard_photo{
type = MimeType,
binval = Data}}]} = IQ)
when is_binary(Data), Data /= <<>> ->
SHA1 = str:sha(Data),
M = get_avatar_meta(IQ),
case M of
{ok, SHA1, _} ->
IQ;
{ok, _ItemID, #avatar_meta{info = Info} = Meta} ->
case lists:keyfind(SHA1, #avatar_info.id, Info) of
#avatar_info{} ->
IQ;
false ->
Info1 = lists:filter(
fun(#avatar_info{url = URL}) -> URL /= <<"">> end,
Info),
Meta1 = Meta#avatar_meta{info = Info1},
publish_avatar(IQ, Meta1, MimeType, Data, SHA1)
end;
{error, _} ->
publish_avatar(IQ, #avatar_meta{}, MimeType, Data, SHA1)
end;
vcard_iq_publish(Acc) ->
Acc.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec get_meta_info([avatar_info()], convert_rules()) -> avatar_info().
get_meta_info(Info, Rules) ->
case lists:foldl(
fun(_, #avatar_info{} = Acc) ->
Acc;
(#avatar_info{url = URL}, Acc) when URL /= <<"">> ->
Acc;
(#avatar_info{} = I, _) when Rules == [] ->
I;
(#avatar_info{type = MimeType} = I, Acc) ->
T = decode_mime_type(MimeType),
case lists:keymember(T, 2, Rules) of
true ->
I;
false ->
case convert_to_type(T, Rules) of
undefined ->
Acc;
_ ->
[I|Acc]
end
end
end, [], Info) of
#avatar_info{} = I -> I;
[] -> hd(Info);
Is -> hd(lists:reverse(Is))
end.
-spec get_avatar_data(jid(), binary()) -> {ok, binary()} |
{error,
notfound | invalid_data | internal_error}.
get_avatar_data(JID, ItemID) ->
{LUser, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
case mod_pubsub:get_item(LBJID, ?NS_AVATAR_DATA, ItemID) of
#pubsub_item{payload = [Payload|_]} ->
try xmpp:decode(Payload) of
#avatar_data{data = Data} ->
{ok, Data};
_ ->
?WARNING_MSG("invalid avatar data detected "
"for ~s@~s with item id ~s",
[LUser, LServer, ItemID]),
{error, invalid_data}
catch _:{xmpp_codec, Why} ->
?WARNING_MSG("failed to decode avatar data for "
"~s@~s with item id ~s: ~s",
[LUser, LServer, ItemID,
xmpp:format_error(Why)]),
{error, invalid_data}
end;
{error, #stanza_error{reason = 'item-not-found'}} ->
{error, notfound};
{error, Reason} ->
?WARNING_MSG("failed to get item for ~s@~s at node ~s "
"with item id ~s: ~p",
[LUser, LServer, ?NS_AVATAR_METADATA, ItemID, Reason]),
{error, internal_error}
end.
-spec get_avatar_meta(iq()) -> {ok, binary(), avatar_meta()} |
{error,
notfound | invalid_metadata | internal_error}.
get_avatar_meta(#iq{meta = #{avatar_meta := {ItemID, Meta}}}) ->
{ok, ItemID, Meta};
get_avatar_meta(#iq{from = JID}) ->
{LUser, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
case mod_pubsub:get_items(LBJID, ?NS_AVATAR_METADATA) of
[#pubsub_item{itemid = {ItemID, _}, payload = [Payload|_]}|_] ->
try xmpp:decode(Payload) of
#avatar_meta{} = Meta ->
{ok, ItemID, Meta};
_ ->
?WARNING_MSG("invalid metadata payload detected "
"for ~s@~s with item id ~s",
[LUser, LServer, ItemID]),
{error, invalid_metadata}
catch _:{xmpp_codec, Why} ->
?WARNING_MSG("failed to decode metadata for "
"~s@~s with item id ~s: ~s",
[LUser, LServer, ItemID,
xmpp:format_error(Why)]),
{error, invalid_metadata}
end;
{error, #stanza_error{reason = 'item-not-found'}} ->
{error, notfound};
{error, Reason} ->
?WARNING_MSG("failed to get items for ~s@~s at node ~s: ~p",
[LUser, LServer, ?NS_AVATAR_METADATA, Reason]),
{error, internal_error}
end.
-spec publish_avatar(iq(), avatar_meta(), binary(), binary(), binary()) ->
iq() | {stop, stanza_error()}.
publish_avatar(#iq{from = JID} = IQ, Meta, <<>>, <<>>, <<>>) ->
{_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
case mod_pubsub:publish_item(
LBJID, LServer, ?NS_AVATAR_METADATA,
JID, <<>>, [xmpp:encode(Meta)]) of
{result, _} ->
IQ;
{error, StanzaErr} ->
{stop, StanzaErr}
end;
publish_avatar(#iq{from = JID} = IQ, Meta, MimeType, Data, ItemID) ->
#avatar_meta{info = Info} = Meta,
{_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
Payload = xmpp:encode(#avatar_data{data = Data}),
case mod_pubsub:publish_item(
LBJID, LServer, ?NS_AVATAR_DATA,
JID, ItemID, [Payload]) of
{result, _} ->
{W, H} = case eimp:identify(Data) of
{ok, ImgInfo} ->
{proplists:get_value(width, ImgInfo),
proplists:get_value(height, ImgInfo)};
_ ->
{undefined, undefined}
end,
I = #avatar_info{id = ItemID,
width = W,
height = H,
type = MimeType,
bytes = size(Data)},
Meta1 = Meta#avatar_meta{info = [I|Info]},
case mod_pubsub:publish_item(
LBJID, LServer, ?NS_AVATAR_METADATA,
JID, ItemID, [xmpp:encode(Meta1)]) of
{result, _} ->
IQ;
{error, StanzaErr} ->
?ERROR_MSG("Failed to publish avatar metadata for ~s: ~p",
[jid:encode(JID), StanzaErr]),
{stop, StanzaErr}
end;
{error, StanzaErr} ->
?ERROR_MSG("Failed to publish avatar data for ~s: ~p",
[jid:encode(JID), StanzaErr]),
{stop, StanzaErr}
end.
-spec convert_avatar(binary(), binary(), vcard_temp()) ->
{ok, binary(), binary()} |
{error, eimp:error_reason() | base64_error} |
pass.
convert_avatar(LUser, LServer, VCard) ->
case get_converting_rules(LServer) of
[] ->
pass;
Rules ->
case VCard#vcard_temp.photo of
#vcard_photo{binval = Data} when is_binary(Data) ->
convert_avatar(LUser, LServer, Data, Rules);
_ ->
pass
end
end.
-spec convert_avatar(binary(), binary(), binary(), convert_rules()) ->
{ok, eimp:img_type(), binary()} |
{error, eimp:error_reason()} |
pass.
convert_avatar(LUser, LServer, Data, Rules) ->
Type = get_type(Data),
NewType = convert_to_type(Type, Rules),
if NewType == undefined orelse Type == NewType ->
pass;
true ->
?DEBUG("Converting avatar of ~s@~s: ~s -> ~s",
[LUser, LServer, Type, NewType]),
case eimp:convert(Data, NewType) of
{ok, NewData} ->
{ok, encode_mime_type(NewType), NewData};
{error, Reason} = Err ->
?ERROR_MSG("Failed to convert avatar of "
"~s@~s (~s -> ~s): ~s",
[LUser, LServer, Type, NewType,
eimp:format_error(Reason)]),
Err
end
end.
-spec set_vcard_avatar(jid(), vcard_photo() | undefined, map()) -> ok.
set_vcard_avatar(JID, VCardPhoto, Meta) ->
case get_vcard(JID) of
{ok, #vcard_temp{photo = VCardPhoto}} ->
ok;
{ok, VCard} ->
VCard1 = VCard#vcard_temp{photo = VCardPhoto},
IQ = #iq{from = JID, to = JID, id = randoms:get_string(),
type = set, sub_els = [VCard1], meta = Meta},
LServer = JID#jid.lserver,
ejabberd_hooks:run_fold(vcard_iq_set, LServer, IQ, []),
ok;
{error, _} ->
ok
end.
-spec delete_vcard_avatar(jid()) -> ok.
delete_vcard_avatar(JID) ->
set_vcard_avatar(JID, undefined, #{}).
-spec get_vcard(jid()) -> {ok, vcard_temp()} | {error, invalid_vcard}.
get_vcard(#jid{luser = LUser, lserver = LServer}) ->
VCardEl = case mod_vcard:get_vcard(LUser, LServer) of
[El] -> El;
_ -> #vcard_temp{}
end,
try xmpp:decode(VCardEl, ?NS_VCARD, []) of
#vcard_temp{} = VCard ->
{ok, VCard};
_ ->
?ERROR_MSG("invalid vCard of ~s@~s in the database",
[LUser, LServer]),
{error, invalid_vcard}
catch _:{xmpp_codec, Why} ->
?ERROR_MSG("failed to decode vCard of ~s@~s: ~s",
[LUser, LServer, xmpp:format_error(Why)]),
{error, invalid_vcard}
end.
-spec stop_with_error(binary(), eimp:error_reason()) ->
{stop, stanza_error()}.
stop_with_error(Lang, Reason) ->
Txt = eimp:format_error(Reason),
{stop, xmpp:err_internal_server_error(Txt, Lang)}.
-spec get_converting_rules(binary()) -> convert_rules().
get_converting_rules(LServer) ->
gen_mod:get_module_opt(LServer, ?MODULE, convert, []).
-spec get_type(binary()) -> eimp:img_type() | unknown.
get_type(Data) ->
eimp:get_type(Data).
-spec convert_to_type(eimp:img_type() | unknown, convert_rules()) ->
eimp:img_type() | undefined.
convert_to_type(unknown, _Rules) ->
undefined;
convert_to_type(Type, Rules) ->
case proplists:get_value(Type, Rules) of
undefined ->
proplists:get_value(default, Rules);
T ->
T
end.
-spec decode_mime_type(binary()) -> eimp:img_type() | unknown.
decode_mime_type(MimeType) ->
case str:to_lower(MimeType) of
<<"image/jpeg">> -> jpeg;
<<"image/png">> -> png;
<<"image/webp">> -> webp;
<<"image/gif">> -> gif;
_ -> unknown
end.
-spec encode_mime_type(eimp:img_type()) -> binary().
encode_mime_type(Type) ->
<<"image/", (atom_to_binary(Type, latin1))/binary>>.
mod_opt_type({convert, png}) ->
fun(jpeg) -> jpeg;
(webp) -> webp;
(gif) -> gif
end;
mod_opt_type({convert, webp}) ->
fun(jpeg) -> jpeg;
(png) -> png;
(gif) -> gif
end;
mod_opt_type({convert, jpeg}) ->
fun(png) -> png;
(webp) -> webp;
(gif) -> gif
end;
mod_opt_type({convert, gif}) ->
fun(png) -> png;
(jpeg) -> jpeg;
(webp) -> webp
end;
mod_opt_type({convert, default}) ->
fun(png) -> png;
(webp) -> webp;
(jpeg) -> jpeg;
(gif) -> gif
end;
mod_opt_type(_) ->
[{convert, default},
{convert, webp},
{convert, png},
{convert, gif},
{convert, jpeg}].
+85 -38
View File
@@ -32,7 +32,7 @@
-export([start/2, stop/1, reload/3,
depends/2, mod_opt_type/1]).
-export([filter_packet/1]).
-export([filter_packet/1, filter_offline_msg/1]).
-include("xmpp.hrl").
-include("ejabberd.hrl").
@@ -43,54 +43,29 @@
start(Host, _Opts) ->
ejabberd_hooks:add(user_receive_packet, Host,
?MODULE, filter_packet, 25),
ok.
ejabberd_hooks:add(offline_message_hook, Host,
?MODULE, filter_offline_msg, 25).
stop(Host) ->
ejabberd_hooks:delete(user_receive_packet, Host,
?MODULE, filter_packet, 25),
ok.
ejabberd_hooks:delete(offline_message_hook, Host,
?MODULE, filter_offline_msg, 25).
reload(_Host, _NewOpts, _OldOpts) ->
ok.
filter_packet({#message{} = Msg, State} = Acc) ->
From = xmpp:get_from(Msg),
filter_packet({#message{from = From} = Msg, State} = Acc) ->
LFrom = jid:tolower(From),
LBFrom = jid:remove_resource(LFrom),
#{pres_a := PresA, jid := JID, lserver := LServer} = State,
AllowLocalUsers =
gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users, true),
case (Msg#message.body == [] andalso
Msg#message.subject == [])
orelse (AllowLocalUsers andalso
ejabberd_router:is_my_route(From#jid.lserver))
orelse (?SETS):is_element(LFrom, PresA)
orelse (?SETS):is_element(LBFrom, PresA)
#{pres_a := PresA} = State,
case (?SETS):is_element(LFrom, PresA)
orelse (?SETS):is_element(LBFrom, PresA)
orelse sets_bare_member(LBFrom, PresA) of
false ->
{Sub, _} = ejabberd_hooks:run_fold(
roster_get_jid_info, LServer,
{none, []}, [JID#jid.luser, LServer, From]),
case Sub of
none ->
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
if
Log ->
?INFO_MSG("Drop packet: ~s",
[fxml:element_to_binary(
xmpp:encode(Msg, ?NS_CLIENT))]);
true ->
ok
end,
if
Drop ->
{stop, {drop, State}};
true ->
Acc
end;
_ ->
Acc
case check_message(Msg) of
allow -> Acc;
deny -> {stop, {drop, State}}
end;
true ->
Acc
@@ -98,6 +73,76 @@ filter_packet({#message{} = Msg, State} = Acc) ->
filter_packet(Acc) ->
Acc.
filter_offline_msg({_Action, #message{} = Msg} = Acc) ->
case check_message(Msg) of
allow -> Acc;
deny -> {stop, {drop, Msg}}
end.
check_message(#message{from = From, to = To} = Msg) ->
LServer = To#jid.lserver,
AllowLocalUsers =
gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users, true),
case (Msg#message.body == [] andalso
Msg#message.subject == [])
orelse ((AllowLocalUsers orelse From#jid.luser == <<"">>) andalso
ejabberd_router:is_my_host(From#jid.lserver)) of
false ->
case check_subscription(From, To) of
none ->
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
if
Log ->
?INFO_MSG("~s message from stranger ~s to ~s",
[if Drop -> "Dropping";
true -> "Allow"
end,
jid:encode(From), jid:encode(To)]);
true ->
ok
end,
if
Drop ->
deny;
true ->
allow
end;
some ->
allow
end;
true ->
allow
end.
-spec check_subscription(jid(), jid()) -> none | some.
check_subscription(From, To) ->
{LocalUser, LocalServer, _} = jid:tolower(To),
{RemoteUser, RemoteServer, _} = jid:tolower(From),
case ejabberd_hooks:run_fold(
roster_get_jid_info, LocalServer,
{none, []}, [LocalUser, LocalServer, From]) of
{none, _} when RemoteUser == <<"">> ->
none;
{none, _} ->
case gen_mod:get_module_opt(LocalServer, ?MODULE,
allow_transports, true) of
true ->
%% Check if the contact's server is in the roster
case ejabberd_hooks:run_fold(
roster_get_jid_info, LocalServer,
{none, []},
[LocalUser, LocalServer, jid:make(RemoteServer)]) of
{none, _} -> none;
_ -> some
end;
false ->
none
end;
_ ->
some
end.
sets_bare_member({U, S, <<"">>} = LBJID, Set) ->
case ?SETS:next(sets_iterator_from(LBJID, Set)) of
{{U, S, _}, _} -> true;
@@ -133,4 +178,6 @@ mod_opt_type(log) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(allow_local_users) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) -> [drop, log, allow_local_users].
mod_opt_type(allow_transports) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) -> [drop, log, allow_local_users, allow_transports].
+5 -5
View File
@@ -192,7 +192,7 @@ get_local_identity(Acc, _From, _To, Node, Lang) ->
-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
deny -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow -> {result, Feats}
end).
@@ -310,7 +310,7 @@ get_sm_items(Acc, From,
Items ++ Nodes ++ get_user_resources(User, Server)};
{allow, <<"config">>} -> {result, []};
{_, <<"config">>} ->
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
_ -> Acc
end
end.
@@ -432,7 +432,7 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To,
_ ->
LNode = tokenize(Node),
Allow = acl:match_rule(LServer, configure, From),
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang),
case LNode of
[<<"config">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
@@ -765,7 +765,7 @@ get_stopped_nodes(_Lang) ->
-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
Request, Lang),
case acl:match_rule(LServerOrGlobal, configure, From) of
deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
deny -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow -> adhoc_local_commands(From, To, Request)
end).
@@ -1737,7 +1737,7 @@ adhoc_sm_commands(_Acc, From,
action = Action, xdata = XData} = Request) ->
case acl:match_rule(LServer, configure, From) of
deny ->
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow ->
ActionIsExecute = Action == execute orelse Action == complete,
if Action == cancel ->
+119 -91
View File
@@ -107,10 +107,11 @@
get_url :: binary(),
service_url :: binary() | undefined,
thumbnail :: boolean(),
custom_headers :: [{binary(), binary()}],
slots = #{} :: map()}).
-record(media_info,
{type :: binary(),
{type :: atom(),
height :: integer(),
width :: integer()}).
@@ -226,6 +227,7 @@ init([ServerHost, Opts]) ->
GetURL = gen_mod:get_opt(get_url, Opts, PutURL),
ServiceURL = gen_mod:get_opt(service_url, Opts),
Thumbnail = gen_mod:get_opt(thumbnail, Opts, true),
CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []),
DocRoot1 = expand_home(str:strip(DocRoot, right, $/)),
DocRoot2 = expand_host(DocRoot1, ServerHost),
case DirMode of
@@ -236,12 +238,14 @@ init([ServerHost, Opts]) ->
end,
case Thumbnail of
true ->
case string:str(os:cmd("identify"), "Magick") of
0 ->
?ERROR_MSG("Cannot find 'identify' command, please install "
"ImageMagick or disable thumbnail creation", []);
_ ->
ok
case misc:have_eimp() of
false ->
?ERROR_MSG("ejabberd is built without graphics support, "
"please rebuild it with --enable-graphics or "
"set 'thumbnail: false' for module '~s' in "
"ejabberd.yml", [?MODULE]);
_ ->
ok
end;
false ->
ok
@@ -258,7 +262,8 @@ init([ServerHost, Opts]) ->
docroot = DocRoot2,
put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
get_url = expand_host(str:strip(GetURL, right, $/), ServerHost),
service_url = ServiceURL}}.
service_url = ServiceURL,
custom_headers = CustomHeaders}}.
-spec handle_call(_, {pid(), _}, state())
-> {reply, {ok, pos_integer(), binary(),
@@ -266,25 +271,30 @@ init([ServerHost, Opts]) ->
pos_integer() | undefined}, state()} |
{reply, {error, atom()}, state()} | {noreply, state()}.
handle_call({use_slot, Slot, Size}, _From, #state{file_mode = FileMode,
dir_mode = DirMode,
get_url = GetPrefix,
thumbnail = Thumbnail,
docroot = DocRoot} = State) ->
handle_call({use_slot, Slot, Size}, _From,
#state{file_mode = FileMode,
dir_mode = DirMode,
get_url = GetPrefix,
thumbnail = Thumbnail,
custom_headers = CustomHeaders,
docroot = DocRoot} = State) ->
case get_slot(Slot, State) of
{ok, {Size, Timer}} ->
timer:cancel(Timer),
NewState = del_slot(Slot, State),
Path = str:join([DocRoot | Slot], <<$/>>),
{reply, {ok, Path, FileMode, DirMode, GetPrefix, Thumbnail},
{reply,
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders},
NewState};
{ok, {_WrongSize, _Timer}} ->
{reply, {error, size_mismatch}, State};
error ->
{reply, {error, invalid_slot}, State}
end;
handle_call(get_docroot, _From, #state{docroot = DocRoot} = State) ->
{reply, {ok, DocRoot}, State};
handle_call(get_conf, _From,
#state{docroot = DocRoot,
custom_headers = CustomHeaders} = State) ->
{reply, {ok, DocRoot, CustomHeaders}, State};
handle_call(Request, From, State) ->
?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
{noreply, State}.
@@ -353,44 +363,44 @@ process(LocalPath, #request{method = Method, host = Host, ip = IP})
Method == 'HEAD' ->
?DEBUG("Rejecting ~s request from ~s for ~s: Too few path components",
[Method, ?ADDR_TO_STR(IP), Host]),
http_response(Host, 404);
http_response(404);
process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
data = Data} = Request) ->
{Proc, Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, {use_slot, Slot, byte_size(Data)}) of
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail} ->
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders} ->
?DEBUG("Storing file from ~s for ~s: ~s",
[?ADDR_TO_STR(IP), Host, Path]),
case store_file(Path, Data, FileMode, DirMode,
GetPrefix, Slot, Thumbnail) of
ok ->
http_response(Host, 201);
http_response(201, CustomHeaders);
{ok, Headers, OutData} ->
http_response(Host, 201, Headers, OutData);
http_response(201, Headers ++ CustomHeaders, OutData);
{error, Error} ->
?ERROR_MSG("Cannot store file ~s from ~s for ~s: ~p",
[Path, ?ADDR_TO_STR(IP), Host, ?FORMAT(Error)]),
http_response(Host, 500)
http_response(500)
end;
{error, size_mismatch} ->
?INFO_MSG("Rejecting file from ~s for ~s: Unexpected size (~B)",
[?ADDR_TO_STR(IP), Host, byte_size(Data)]),
http_response(Host, 413);
http_response(413);
{error, invalid_slot} ->
?INFO_MSG("Rejecting file from ~s for ~s: Invalid slot",
[?ADDR_TO_STR(IP), Host]),
http_response(Host, 403);
http_response(403);
Error ->
?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p",
[?ADDR_TO_STR(IP), Host, Error]),
http_response(Host, 500)
http_response(500)
end;
process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
when Method == 'GET';
Method == 'HEAD' ->
{Proc, [_UserDir, _RandDir, FileName] = Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, get_docroot) of
{ok, DocRoot} ->
case catch gen_server:call(Proc, get_conf) of
{ok, DocRoot, CustomHeaders} ->
Path = str:join([DocRoot | Slot], <<$/>>),
case file:read_file(Path) of
{ok, Data} ->
@@ -405,37 +415,47 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
$", FileName/binary, $">>}]
end,
Headers2 = [{<<"Content-Type">>, ContentType} | Headers1],
http_response(Host, 200, Headers2, Data);
Headers3 = Headers2 ++ CustomHeaders,
http_response(200, Headers3, Data);
{error, eacces} ->
?INFO_MSG("Cannot serve ~s to ~s: Permission denied",
[Path, ?ADDR_TO_STR(IP)]),
http_response(Host, 403);
http_response(403);
{error, enoent} ->
?INFO_MSG("Cannot serve ~s to ~s: No such file",
[Path, ?ADDR_TO_STR(IP)]),
http_response(Host, 404);
http_response(404);
{error, eisdir} ->
?INFO_MSG("Cannot serve ~s to ~s: Is a directory",
[Path, ?ADDR_TO_STR(IP)]),
http_response(Host, 404);
http_response(404);
{error, Error} ->
?INFO_MSG("Cannot serve ~s to ~s: ~s",
[Path, ?ADDR_TO_STR(IP), ?FORMAT(Error)]),
http_response(Host, 500)
http_response(500)
end;
Error ->
?ERROR_MSG("Cannot handle ~s request from ~s for ~s: ~p",
[Method, ?ADDR_TO_STR(IP), Host, Error]),
http_response(Host, 500)
http_response(500)
end;
process(_LocalPath, #request{method = 'OPTIONS', host = Host, ip = IP}) ->
process(_LocalPath, #request{method = 'OPTIONS', host = Host,
ip = IP} = Request) ->
?DEBUG("Responding to OPTIONS request from ~s for ~s",
[?ADDR_TO_STR(IP), Host]),
http_response(Host, 200);
{Proc, _Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, get_conf) of
{ok, _DocRoot, CustomHeaders} ->
http_response(200, CustomHeaders);
Error ->
?ERROR_MSG("Cannot handle OPTIONS request from ~s for ~s: ~p",
[?ADDR_TO_STR(IP), Host, Error]),
http_response(500)
end;
process(_LocalPath, #request{method = Method, host = Host, ip = IP}) ->
?DEBUG("Rejecting ~s request from ~s for ~s",
[Method, ?ADDR_TO_STR(IP), Host]),
http_response(Host, 405, [{<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>}]).
http_response(405, [{<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>}]).
%%--------------------------------------------------------------------
%% Exported utility functions.
@@ -522,7 +542,7 @@ process_slot_request(#iq{lang = Lang, from = From} = IQ,
deny ->
?DEBUG("Denying HTTP upload slot request from ~s",
[jid:encode(From)]),
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
end.
@@ -658,7 +678,7 @@ make_rand_string(S, N) -> make_rand_string([make_rand_char() | S], N - 1).
-spec make_rand_char() -> char().
make_rand_char() ->
map_int_to_char(crypto:rand_uniform(0, 62)).
map_int_to_char(randoms:uniform(0, 61)).
-spec map_int_to_char(0..61) -> char().
@@ -726,15 +746,15 @@ parse_http_request(#request{host = Host, path = Path}) ->
store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) ->
case do_store_file(Path, Data, FileMode, DirMode) of
ok when Thumbnail ->
case identify(Path) of
case identify(Path, Data) of
{ok, MediaInfo} ->
case convert(Path, MediaInfo) of
{ok, OutPath} ->
case convert(Path, Data, MediaInfo) of
{ok, OutPath, OutMediaInfo} ->
[UserDir, RandDir | _] = Slot,
FileName = filename:basename(OutPath),
URL = str:join([GetPrefix, UserDir,
RandDir, FileName], <<$/>>),
ThumbEl = thumb_el(OutPath, URL),
ThumbEl = thumb_el(OutMediaInfo, URL),
{ok,
[{<<"Content-Type">>,
<<"text/xml; charset=utf-8">>}],
@@ -790,30 +810,29 @@ guess_content_type(FileName) ->
?DEFAULT_CONTENT_TYPE,
?CONTENT_TYPES).
-spec http_response(binary(), 100..599)
-spec http_response(100..599)
-> {pos_integer(), [{binary(), binary()}], binary()}.
http_response(Host, Code) ->
http_response(Host, Code, []).
http_response(Code) ->
http_response(Code, []).
-spec http_response(binary(), 100..599, [{binary(), binary()}])
-spec http_response(100..599, [{binary(), binary()}])
-> {pos_integer(), [{binary(), binary()}], binary()}.
http_response(Host, Code, ExtraHeaders) ->
http_response(Code, ExtraHeaders) ->
Message = <<(code_to_message(Code))/binary, $\n>>,
http_response(Host, Code, ExtraHeaders, Message).
http_response(Code, ExtraHeaders, Message).
-spec http_response(binary(), 100..599, [{binary(), binary()}], binary())
-spec http_response(100..599, [{binary(), binary()}], binary())
-> {pos_integer(), [{binary(), binary()}], binary()}.
http_response(Host, Code, ExtraHeaders, Body) ->
CustomHeaders = gen_mod:get_module_opt(Host, ?MODULE, custom_headers, []),
http_response(Code, ExtraHeaders, Body) ->
Headers = case proplists:is_defined(<<"Content-Type">>, ExtraHeaders) of
true ->
ExtraHeaders;
false ->
[{<<"Content-Type">>, <<"text/plain">>} | ExtraHeaders]
end ++ CustomHeaders,
end,
{Code, Headers, Body}.
-spec code_to_message(100..599) -> binary().
@@ -830,59 +849,68 @@ code_to_message(_Code) -> <<"">>.
%% Image manipulation stuff.
%%--------------------------------------------------------------------
-spec identify(binary()) -> {ok, media_info()} | pass.
-spec identify(binary(), binary()) -> {ok, media_info()} | pass.
identify(Path) ->
Cmd = io_lib:format("identify -format 'ok %m %h %w' ~s", [Path]),
Res = string:strip(os:cmd(Cmd), right, $\n),
case string:tokens(Res, " ") of
["ok", T, H, W] ->
{ok, #media_info{type = list_to_binary(string:to_lower(T)),
height = list_to_integer(H),
width = list_to_integer(W)}};
_ ->
?DEBUG("Cannot identify type of ~s: ~s", [Path, Res]),
identify(Path, Data) ->
case misc:have_eimp() of
true ->
case eimp:identify(Data) of
{ok, Info} ->
{ok, #media_info{
type = proplists:get_value(type, Info),
width = proplists:get_value(width, Info),
height = proplists:get_value(height, Info)}};
{error, Why} ->
?DEBUG("Cannot identify type of ~s: ~s",
[Path, eimp:format_error(Why)]),
pass
end;
false ->
pass
end.
-spec convert(binary(), media_info()) -> {ok, binary()} | pass.
-spec convert(binary(), binary(), media_info()) -> {ok, binary(), media_info()} | pass.
convert(Path, #media_info{type = T, width = W, height = H}) ->
convert(Path, Data, #media_info{type = T, width = W, height = H} = Info) ->
if W * H >= 25000000 ->
?DEBUG("The image ~s is more than 25 Mpix", [Path]),
pass;
W =< 300, H =< 300 ->
{ok, Path};
T == <<"gif">>; T == <<"jpeg">>; T == <<"png">>; T == <<"webp">> ->
Dir = filename:dirname(Path),
FileName = <<(randoms:get_string())/binary, $., T/binary>>,
OutPath = filename:join(Dir, FileName),
Cmd = io_lib:format("convert -resize 300 ~s ~s", [Path, OutPath]),
case os:cmd(Cmd) of
"" ->
{ok, OutPath};
Err ->
?ERROR_MSG("Failed to convert ~s to ~s: ~s",
[Path, OutPath, string:strip(Err, right, $\n)]),
pass
end;
{ok, Path, Info};
true ->
?DEBUG("Won't call 'convert' for unknown type ~s", [T]),
pass
Dir = filename:dirname(Path),
Ext = atom_to_binary(T, latin1),
FileName = <<(randoms:get_string())/binary, $., Ext/binary>>,
OutPath = filename:join(Dir, FileName),
{W1, H1} = if W > H -> {300, round(H*300/W)};
H > W -> {round(W*300/H), 300};
true -> {300, 300}
end,
OutInfo = #media_info{type = T, width = W1, height = H1},
case eimp:convert(Data, T, [{scale, {W1, H1}}]) of
{ok, OutData} ->
case file:write_file(OutPath, OutData) of
ok ->
{ok, OutPath, OutInfo};
{error, Why} ->
?ERROR_MSG("Failed to write to ~s: ~s",
[OutPath, file:format_error(Why)]),
pass
end;
{error, Why} ->
?ERROR_MSG("Failed to convert ~s to ~s: ~s",
[Path, OutPath, eimp:format_error(Why)]),
pass
end
end.
-spec thumb_el(binary(), binary()) -> xmlel().
-spec thumb_el(media_info(), binary()) -> xmlel().
thumb_el(Path, URI) ->
ContentType = guess_content_type(Path),
xmpp:encode(
case identify(Path) of
{ok, #media_info{height = H, width = W}} ->
#thumbnail{'media-type' = ContentType, uri = URI,
height = H, width = W};
pass ->
#thumbnail{uri = URI, 'media-type' = ContentType}
end).
thumb_el(#media_info{type = T, height = H, width = W}, URI) ->
MimeType = <<"image/", (atom_to_binary(T, latin1))/binary>>,
Thumb = #thumbnail{'media-type' = MimeType, uri = URI,
height = H, width = W},
xmpp:encode(Thumb).
%%--------------------------------------------------------------------
%% Remove user.
+1 -1
View File
@@ -262,7 +262,7 @@ do_route(Host, ServerHost, Access, Packet) ->
end;
deny ->
Lang = xmpp:get_lang(Packet),
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang),
ejabberd_router:route_error(Packet, Err)
end.
+1 -1
View File
@@ -133,7 +133,7 @@ authenticate(#{stream_id := StreamID, server := Server,
Err = xmpp:make_error(IQ, xmpp:err_jid_malformed()),
process_auth_failure(State, U, Err, 'jid-malformed');
false ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
Err = xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)),
process_auth_failure(State, U, Err, 'forbidden')
end.
+14 -2
View File
@@ -863,8 +863,13 @@ select(_LServer, JidRequestor, JidArchive, Query, RSM,
{Msgs, true, L}
end;
select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType).
case might_expose_jid(Query, MsgType) of
true ->
{[], true, 0};
false ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType)
end.
msg_to_el(#archive_msg{timestamp = TS, packet = El, nick = Nick,
peer = Peer, id = ID},
@@ -988,6 +993,13 @@ match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> ->
match_rsm(_Now, _) ->
true.
might_expose_jid(Query,
{groupchat, Role, #state{config = #config{anonymous = true}}})
when Role /= moderator ->
proplists:is_defined(with, Query);
might_expose_jid(_Query, _MsgType) ->
false.
get_jids(undefined) ->
[];
get_jids(Js) ->
+4 -3
View File
@@ -61,6 +61,7 @@
count_online_rooms/1,
register_online_user/4,
unregister_online_user/4,
iq_set_register_info/5,
count_online_rooms_by_user/3,
get_online_rooms_by_user/3,
can_use_nick/4]).
@@ -680,7 +681,7 @@ iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM
-spec get_room_disco_item({binary(), binary(), pid()},
term()) -> {ok, disco_item()} |
{error, timeout | notfound}.
{error, timeout | notfound}.
get_room_disco_item({Name, Host, Pid}, Query) ->
RoomJID = jid:make(Name, Host),
try p1_fsm:sync_send_all_state_event(Pid, Query, 100) of
@@ -688,9 +689,9 @@ get_room_disco_item({Name, Host, Pid}, Query) ->
{ok, #disco_item{jid = RoomJID, name = Desc}};
false ->
{error, notfound}
catch _:{timeout, _} ->
catch _:{timeout, {p1_fsm, _, _}} ->
{error, timeout};
_:{noproc, _} ->
_:{_, {p1_fsm, _, _}} ->
{error, notfound}
end.
+25 -33
View File
@@ -29,7 +29,7 @@
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, depends/2, muc_online_rooms/1,
muc_register_nick/3, muc_unregister_nick/1,
muc_register_nick/3, muc_unregister_nick/2,
create_room_with_opts/4, create_room/3, destroy_room/2,
create_rooms_file/1, destroy_rooms_file/1,
rooms_unused_list/2, rooms_unused_destroy/2,
@@ -91,16 +91,18 @@ get_commands_spec() ->
args = [{host, binary}],
result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = muc_register_nick, tags = [muc],
desc = "Register a nick in the MUC service",
desc = "Register a nick to a User JID in the MUC service of a server",
module = ?MODULE, function = muc_register_nick,
args_desc = ["Nick", "User JID", "MUC service"],
args_example = [<<"Tim">>, <<"tim@example.org">>, <<"muc.example.org">>],
args = [{nick, binary}, {jid, binary}, {domain, binary}],
args_desc = ["Nick", "User JID", "Server Host"],
args_example = [<<"Tim">>, <<"tim@example.org">>, <<"example.org">>],
args = [{nick, binary}, {jid, binary}, {serverhost, binary}],
result = {res, rescode}},
#ejabberd_commands{name = muc_unregister_nick, tags = [muc],
desc = "Unregister the nick in the MUC service",
desc = "Unregister the nick registered by that account in the MUC service",
module = ?MODULE, function = muc_unregister_nick,
args = [{nick, binary}],
args_desc = ["User JID", "MUC service"],
args_example = [<<"tim@example.org">>, <<"example.org">>],
args = [{jid, binary}, {serverhost, binary}],
result = {res, rescode}},
#ejabberd_commands{name = create_room, tags = [muc_room],
@@ -305,31 +307,14 @@ muc_online_rooms(ServerHost) ->
|| {Name, _, _} <- mod_muc:get_online_rooms(Host)]
end, Hosts).
muc_register_nick(Nick, JIDBinary, Domain) ->
try jid:decode(JIDBinary) of
JID ->
F = fun (MHost, MNick) ->
mnesia:write(#muc_registered{us_host=MHost, nick=MNick})
end,
case mnesia:transaction(F, [{{JID#jid.luser, JID#jid.lserver},
Domain}, Nick]) of
{atomic, ok} -> ok;
{aborted, _Error} -> error
end
catch _:{bad_jid, _} -> throw({error, "Malformed JID"})
end.
muc_register_nick(Nick, FromBinary, ServerHost) ->
Host = find_host(ServerHost),
From = jid:decode(FromBinary),
Lang = <<"en">>,
mod_muc:iq_set_register_info(ServerHost, Host, From, Nick, Lang).
muc_unregister_nick(Nick) ->
F2 = fun(N) ->
[{_,Key,_}|_] = mnesia:index_read(muc_registered, N, 3),
mnesia:delete({muc_registered, Key})
end,
case mnesia:transaction(F2, [Nick], 1) of
{atomic, ok} ->
ok;
{aborted, _Error} ->
error
end.
muc_unregister_nick(FromBinary, ServerHost) ->
muc_register_nick(<<"">>, FromBinary, ServerHost).
get_user_rooms(LUser, LServer) ->
lists:flatmap(
@@ -536,7 +521,8 @@ prepare_room_info(Room_info) ->
%% ok | error
%% @doc Create a room immediately with the default options.
create_room(Name1, Host1, ServerHost) ->
create_room_with_opts(Name1, Host1, ServerHost, []).
create_room_with_opts(Name1, Host1, ServerHost, []),
change_room_option(Name1, Host1, <<"persistent">>, <<"true">>).
create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) ->
true = (error /= (Name = jid:nodeprep(Name1))),
@@ -816,7 +802,13 @@ get_room_occupants(Pid) ->
dict:to_list(S#state.users)).
get_room_occupants_number(Room, Host) ->
length(get_room_occupants(Room, Host)).
case get_room_pid(Room, Host) of
room_not_found ->
throw({error, room_not_found});
Pid ->
S = get_room_state(Pid),
dict:size(S#state.users)
end.
%%----------------------------
%% Send Direct Invitation
+45 -46
View File
@@ -974,10 +974,9 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
Os2 = lists:sort(Os1),
Options2 = Title ++ Os2,
lists:foldl(fun ({Opt, Val}, R) ->
case get_roomconfig_text(Opt) of
case get_roomconfig_text(Opt, Lang) of
undefined -> R;
OptT ->
OptText = (?T(OptT)),
OptText ->
R2 = case Val of
false ->
<<"<div class=\"rcod\">",
@@ -1025,49 +1024,49 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
end,
<<"">>, Options2).
get_roomconfig_text(title) -> <<"Room title">>;
get_roomconfig_text(persistent) ->
<<"Make room persistent">>;
get_roomconfig_text(public) ->
<<"Make room public searchable">>;
get_roomconfig_text(public_list) ->
<<"Make participants list public">>;
get_roomconfig_text(password_protected) ->
<<"Make room password protected">>;
get_roomconfig_text(password) -> <<"Password">>;
get_roomconfig_text(anonymous) ->
<<"This room is not anonymous">>;
get_roomconfig_text(members_only) ->
<<"Make room members-only">>;
get_roomconfig_text(moderated) ->
<<"Make room moderated">>;
get_roomconfig_text(members_by_default) ->
<<"Default users as participants">>;
get_roomconfig_text(allow_change_subj) ->
<<"Allow users to change the subject">>;
get_roomconfig_text(allow_private_messages) ->
<<"Allow users to send private messages">>;
get_roomconfig_text(allow_private_messages_from_visitors) ->
<<"Allow visitors to send private messages to">>;
get_roomconfig_text(allow_query_users) ->
<<"Allow users to query other users">>;
get_roomconfig_text(allow_user_invites) ->
<<"Allow users to send invites">>;
get_roomconfig_text(logging) -> <<"Enable logging">>;
get_roomconfig_text(allow_visitor_nickchange) ->
<<"Allow visitors to change nickname">>;
get_roomconfig_text(allow_visitor_status) ->
<<"Allow visitors to send status text in "
"presence updates">>;
get_roomconfig_text(captcha_protected) ->
<<"Make room captcha protected">>;
get_roomconfig_text(description) ->
<<"Room description">>;
%% get_roomconfig_text(subject) -> "Subject";
%% get_roomconfig_text(subject_author) -> "Subject author";
get_roomconfig_text(max_users) ->
<<"Maximum Number of Occupants">>;
get_roomconfig_text(_) -> undefined.
get_roomconfig_text(title, Lang) -> ?T(<<"Room title">>);
get_roomconfig_text(persistent, Lang) ->
?T(<<"Make room persistent">>);
get_roomconfig_text(public, Lang) ->
?T(<<"Make room public searchable">>);
get_roomconfig_text(public_list, Lang) ->
?T(<<"Make participants list public">>);
get_roomconfig_text(password_protected, Lang) ->
?T(<<"Make room password protected">>);
get_roomconfig_text(password, Lang) -> ?T(<<"Password">>);
get_roomconfig_text(anonymous, Lang) ->
?T(<<"This room is not anonymous">>);
get_roomconfig_text(members_only, Lang) ->
?T(<<"Make room members-only">>);
get_roomconfig_text(moderated, Lang) ->
?T(<<"Make room moderated">>);
get_roomconfig_text(members_by_default, Lang) ->
?T(<<"Default users as participants">>);
get_roomconfig_text(allow_change_subj, Lang) ->
?T(<<"Allow users to change the subject">>);
get_roomconfig_text(allow_private_messages, Lang) ->
?T(<<"Allow users to send private messages">>);
get_roomconfig_text(allow_private_messages_from_visitors, Lang) ->
?T(<<"Allow visitors to send private messages to">>);
get_roomconfig_text(allow_query_users, Lang) ->
?T(<<"Allow users to query other users">>);
get_roomconfig_text(allow_user_invites, Lang) ->
?T(<<"Allow users to send invites">>);
get_roomconfig_text(logging, Lang) -> ?T(<<"Enable logging">>);
get_roomconfig_text(allow_visitor_nickchange, Lang) ->
?T(<<"Allow visitors to change nickname">>);
get_roomconfig_text(allow_visitor_status, Lang) ->
?T(<<"Allow visitors to send status text in "
"presence updates">>);
get_roomconfig_text(captcha_protected, Lang) ->
?T(<<"Make room CAPTCHA protected">>);
get_roomconfig_text(description, Lang) ->
?T(<<"Room description">>);
%% get_roomconfig_text(subject, Lang) -> "Subject";
%% get_roomconfig_text(subject_author, Lang) -> "Subject author";
get_roomconfig_text(max_users, Lang) ->
?T(<<"Maximum Number of Occupants">>);
get_roomconfig_text(_, _) -> undefined.
%% Users = [{JID, Nick, Role}]
roomoccupants_to_string(Users, _FileFormat) ->
+19 -6
View File
@@ -492,7 +492,7 @@ handle_event({destroy, Reason}, _StateName,
handle_event(destroy, StateName, StateData) ->
?INFO_MSG("Destroyed MUC room ~s",
[jid:encode(StateData#state.jid)]),
handle_event({destroy, undefined}, StateName, StateData);
handle_event({destroy, <<"">>}, StateName, StateData);
handle_event({set_affiliations, Affiliations},
StateName, StateData) ->
{next_state, StateName,
@@ -1028,8 +1028,16 @@ do_process_presence(Nick, #presence{from = From, type = unavailable} = Packet,
end,
NewState = add_user_presence_un(From, NewPacket, StateData),
case (?DICT):find(Nick, StateData#state.nicks) of
{ok, [_, _ | _]} -> ok;
_ -> send_new_presence(From, NewState, StateData)
{ok, [_, _ | _]} ->
Aff = get_affiliation(From, StateData),
Item = #muc_item{affiliation = Aff, role = none, jid = From},
Pres = xmpp:set_subtag(
Packet, #muc_user{items = [Item],
status_codes = [110]}),
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
From, Pres, ?NS_MUCSUB_NODES_PRESENCE, StateData);
_ ->
send_new_presence(From, NewState, StateData)
end,
Reason = xmpp:get_text(NewPacket#presence.status),
remove_online_user(From, NewState, Reason);
@@ -1243,7 +1251,12 @@ expulse_participant(Packet, From, StateData, Reason1) ->
#presence{type = unavailable,
status = xmpp:mk_text(Reason2)},
StateData),
send_new_presence(From, NewState, StateData),
LJID = jid:tolower(From),
{ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users),
case (?DICT):find(Nick, StateData#state.nicks) of
{ok, [_, _ | _]} -> ok;
_ -> send_new_presence(From, NewState, StateData)
end,
remove_online_user(From, NewState).
-spec set_affiliation(jid(), affiliation(), state()) -> state().
@@ -2697,8 +2710,8 @@ find_changed_items(UJID, UAffiliation, URole,
Nick /= <<"">> ->
case find_jids_by_nick(Nick, StateData) of
[] ->
ErrText = str:format(<<"Nickname ~s does not exist in the room">>,
[Nick]),
ErrText = {<<"Nickname ~s does not exist in the room">>,
[Nick]},
throw({error, xmpp:err_not_acceptable(ErrText, Lang)});
JIDList ->
JIDList
+8 -6
View File
@@ -998,8 +998,10 @@ build_service_limit_record(LimitOpts) ->
build_limit_record(LimitOptsR, remote)}.
get_from_limitopts(LimitOpts, SenderT) ->
{SenderT, Result} = lists:keyfind(SenderT, 1, LimitOpts),
Result.
case lists:keyfind(SenderT, 1, LimitOpts) of
false -> [];
{SenderT, Result} -> Result
end.
build_remote_limit_record(LimitOpts, SenderT) ->
build_limit_record(LimitOpts, SenderT).
@@ -1120,10 +1122,10 @@ mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type({limits, Type}) when (Type == local) or (Type == remote) ->
fun(L) ->
lists:map(
fun ({message, infinite}) -> infinite;
({presence, infinite}) -> infinite;
({message, I}) when is_integer(I) -> I;
({presence, I}) when is_integer(I) -> I
fun ({message, infinite} = O) -> O;
({presence, infinite} = O) -> O;
({message, I} = O) when is_integer(I) -> O;
({presence, I} = O) when is_integer(I) -> O
end, L)
end;
mod_opt_type(_) -> [access, host, {limits, local}, {limits, remote}].
+15 -12
View File
@@ -706,22 +706,25 @@ user_queue_parse_query(LUser, LServer, Query) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case lists:keysearch(<<"delete">>, 1, Query) of
{value, _} ->
case lists:keyfind(<<"selected">>, 1, Query) of
{_, Seq} ->
case catch binary_to_integer(Seq) of
I when is_integer(I), I>=0 ->
Mod:remove_message(LUser, LServer, I),
ok;
_ ->
nothing
end;
false ->
nothing
end;
user_queue_parse_query(LUser, LServer, Query, Mod);
_ ->
nothing
end.
user_queue_parse_query(LUser, LServer, Query, Mod) ->
case lists:keytake(<<"selected">>, 1, Query) of
{value, {_, Seq}, Query2} ->
case catch binary_to_integer(Seq) of
I when is_integer(I), I>=0 ->
Mod:remove_message(LUser, LServer, I);
_ ->
nothing
end,
user_queue_parse_query(LUser, LServer, Query2, Mod);
false ->
nothing
end.
us_to_list({User, Server}) ->
jid:encode({User, Server, <<"">>}).
+32 -20
View File
@@ -638,25 +638,29 @@ is_ptype_match(Item, PType) ->
ljid(), none | both | from | to, [binary()]) -> boolean().
is_type_match(none, _Value, _JID, _Subscription, _Groups) ->
true;
is_type_match(Type, Value, JID, Subscription, Groups) ->
case Type of
jid ->
case Value of
{<<"">>, Server, <<"">>} ->
case JID of
{_, Server, _} -> true;
_ -> false
end;
{User, Server, <<"">>} ->
case JID of
{User, Server, _} -> true;
_ -> false
end;
_ -> Value == JID
end;
subscription -> Value == Subscription;
group -> lists:member(Value, Groups)
end.
is_type_match(jid, Value, JID, _Subscription, _Groups) ->
case Value of
{<<"">>, Server, <<"">>} ->
case JID of
{_, Server, _} -> true;
_ -> false
end;
{User, Server, <<"">>} ->
case JID of
{User, Server, _} -> true;
_ -> false
end;
{<<"">>, Server, Resource} ->
case JID of
{_, Server, Resource} -> true;
_ -> false
end;
_ -> Value == JID
end;
is_type_match(subscription, Value, _JID, Subscription, _Groups) ->
Value == Subscription;
is_type_match(group, Group, _JID, _Subscription, Groups) ->
lists:member(Group, Groups).
-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
@@ -842,4 +846,12 @@ depends(_Host, _Opts) ->
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [db_type, iqdisc].
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
fun (I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end;
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[db_type, iqdisc, cache_life_time, cache_size,
use_cache, cache_missed].
+1 -1
View File
@@ -133,7 +133,7 @@ set_data(LUser, LServer, Data) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:set_data(LUser, LServer, Data) of
ok ->
delete_cache(Mod, LServer, LServer, Data);
delete_cache(Mod, LUser, LServer, Data);
{error, _} = Err ->
Err
end.
+2 -2
View File
@@ -183,7 +183,7 @@ process_bytestreams(#iq{type = get, from = JID, to = To, lang = Lang} = IQ) ->
StreamHost = get_streamhost(Host, ServerHost),
xmpp:make_iq_result(IQ, #bytestreams{hosts = [StreamHost]});
deny ->
xmpp:make_error(IQ, xmpp:err_forbidden(<<"Denied by ACL">>, Lang))
xmpp:make_error(IQ, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang))
end;
process_bytestreams(#iq{type = set, lang = Lang,
sub_els = [#bytestreams{sid = SID}]} = IQ)
@@ -232,7 +232,7 @@ process_bytestreams(#iq{type = set, lang = Lang, from = InitiatorJID, to = To,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end;
deny ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
end.
+209 -268
View File
@@ -52,7 +52,7 @@
%% exports for hooks
-export([presence_probe/3, caps_add/3, caps_update/3,
in_subscription/6, out_subscription/4,
on_user_offline/3, remove_user/2,
on_user_online/1, on_user_offline/2, remove_user/2,
disco_local_identity/5, disco_local_features/5,
disco_local_items/5, disco_sm_identity/5,
disco_sm_features/5, disco_sm_items/5,
@@ -89,11 +89,7 @@
%% API and gen_server callbacks
-export([start/2, stop/1, init/1,
handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, depends/2, export/1]).
-export([send_loop/1, mod_opt_type/1]).
-define(LOOPNAME, ejabberd_mod_pubsub_loop).
terminate/2, code_change/3, depends/2, export/1, mod_opt_type/1]).
%%====================================================================
%% API
@@ -263,10 +259,9 @@ init([ServerHost, Opts]) ->
end,
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
DefaultModule = plugin(Host, hd(Plugins)),
BaseOptions = DefaultModule:options(),
DefaultNodeCfg = filter_node_options(
DefaultNodeCfg = merge_config(
gen_mod:get_opt(default_node_config, Opts, []),
BaseOptions),
DefaultModule:options()),
lists:foreach(
fun(H) ->
T = gen_mod:get_module_proc(H, config),
@@ -300,7 +295,9 @@ init([ServerHost, Opts]) ->
?MODULE, process_commands, IQDisc),
Plugins
end, Hosts),
ejabberd_hooks:add(sm_remove_connection_hook, ServerHost,
ejabberd_hooks:add(c2s_session_opened, ServerHost,
?MODULE, on_user_online, 75),
ejabberd_hooks:add(c2s_terminated, ServerHost,
?MODULE, on_user_offline, 75),
ejabberd_hooks:add(disco_local_identity, ServerHost,
?MODULE, disco_local_identity, 75),
@@ -337,34 +334,16 @@ init([ServerHost, Opts]) ->
false ->
ok
end,
{_, State} = init_send_loop(ServerHost, Hosts),
{ok, State}.
init_send_loop(ServerHost, Hosts) ->
NodeTree = config(ServerHost, nodetree),
Plugins = config(ServerHost, plugins),
LastItemCache = config(ServerHost, last_item_cache),
MaxItemsNode = config(ServerHost, max_items_node),
PepMapping = config(ServerHost, pep_mapping),
PepOffline = config(ServerHost, ignore_pep_from_offline),
Access = config(ServerHost, access),
DBType = gen_mod:db_type(ServerHost, ?MODULE),
State = #state{hosts = Hosts, server_host = ServerHost,
access = Access, pep_mapping = PepMapping,
ignore_pep_from_offline = PepOffline,
last_item_cache = LastItemCache,
max_items_node = MaxItemsNode, nodetree = NodeTree,
plugins = Plugins, db_type = DBType},
Proc = gen_mod:get_module_proc(ServerHost, ?LOOPNAME),
Pid = case whereis(Proc) of
undefined ->
SendLoop = spawn(?MODULE, send_loop, [State]),
register(Proc, SendLoop),
SendLoop;
Loop ->
Loop
end,
{Pid, State}.
{ok, #state{hosts = Hosts, server_host = ServerHost,
access = Access, pep_mapping = PepMapping,
ignore_pep_from_offline = PepOffline,
last_item_cache = LastItemCache,
max_items_node = MaxItemsNode, nodetree = NodeTree,
plugins = Plugins, db_type = DBType}}.
depends(ServerHost, Opts) ->
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
@@ -414,94 +393,6 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
TreePlugin:terminate(Host, ServerHost),
ok.
get_subscribed(User, Server) ->
Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
lists:filtermap(
fun(#roster{jid = LJID, subscription = Sub})
when Sub == both orelse Sub == from ->
{true, LJID};
(_) ->
false
end, Items).
send_loop(State) ->
receive
{presence, JID, _Pid} ->
ServerHost = State#state.server_host,
Host = host(State#state.server_host),
DBType = State#state.db_type,
LJID = jid:tolower(JID),
BJID = jid:remove_resource(LJID),
lists:foreach(
fun(PType) ->
Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID),
lists:foreach(
fun({NodeRec, _, _, SubJID}) ->
{_, Node} = NodeRec#pubsub_node.nodeid,
Nidx = NodeRec#pubsub_node.id,
Options = NodeRec#pubsub_node.options,
[send_items(Host, Node, Nidx, PType, Options, SubJID, last)
|| NodeRec#pubsub_node.type == PType]
end,
lists:usort(Subs))
end,
State#state.plugins),
if not State#state.ignore_pep_from_offline ->
{User, Server, Resource} = LJID,
Contacts = get_subscribed(User, Server),
lists:foreach(
fun({U, S, R}) when S == ServerHost ->
case user_resources(U, S) of
[] -> %% offline
PeerJID = jid:make(U, S, R),
self() ! {presence, User, Server, [Resource], PeerJID};
_ -> %% online
%% this is already handled by presence probe
ok
end;
(_) ->
%% we can not do anything in any cases
ok
end, Contacts);
true ->
ok
end,
send_loop(State);
{presence, User, Server, Resources, JID} ->
spawn(fun() ->
Host = host(State#state.server_host),
Owner = jid:remove_resource(jid:tolower(JID)),
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
case match_option(Options, send_last_published_item, on_sub_and_presence) of
true ->
lists:foreach(fun(Resource) ->
LJID = {User, Server, Resource},
Subscribed = case get_option(Options, access_model) of
open -> true;
presence -> true;
whitelist -> false; % subscribers are added manually
authorize -> false; % likewise
roster ->
Grps = get_option(Options, roster_groups_allowed, []),
{OU, OS, _} = Owner,
element(2, get_roster_info(OU, OS, LJID, Grps))
end,
if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, LJID, last);
true -> ok
end
end,
Resources);
_ ->
ok
end
end,
tree_action(Host, get_nodes, [Owner, JID]))
end),
send_loop(State);
stop ->
ok
end.
%% -------
%% disco hooks handling functions
%%
@@ -660,12 +551,12 @@ disco_items(Host, Node, From) ->
end.
%% -------
%% presence hooks handling functions
%% presence and session hooks handling functions
%%
-spec caps_add(jid(), jid(), [binary()]) -> ok.
caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features)
when Host =/= S ->
caps_add(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features)
when S1 =/= S2 ->
%% When a remote contact goes online while the local user is offline, the
%% remote contact won't receive last items from the local user even if
%% ignore_pep_from_offline is set to false. To work around this issue a bit,
@@ -675,30 +566,36 @@ caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID
%% contact becomes available; the former is also executed when the local
%% user goes online (because that triggers the contact to send a presence
%% packet with CAPS).
presence(Host, {presence, U, S, [R], JID});
send_last_pep(To, From);
caps_add(_From, _To, _Feature) ->
ok.
-spec caps_update(jid(), jid(), [binary()]) -> ok.
caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) ->
presence(Host, {presence, U, S, [R], JID}).
caps_update(From, To, _Features) ->
send_last_pep(To, From).
-spec presence_probe(jid(), jid(), pid()) -> ok.
presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) ->
%% ignore presence_probe from my other ressources
%% to not get duplicated last items
ok;
presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, Pid) ->
presence(S, {presence, From, Pid}),
presence(S, {presence, From#jid.luser, S, [From#jid.lresource], To});
presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, _Pid) ->
send_last_pep(To, From);
presence_probe(_From, _To, _Pid) ->
%% ignore presence_probe from remote contacts,
%% those are handled via caps_add
%% ignore presence_probe from remote contacts, those are handled via caps_add
ok.
presence(ServerHost, Presence) ->
gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! Presence,
ok.
-spec on_user_online(ejabberd_c2s:state()) -> ejabberd_c2s:state().
on_user_online(C2SState) ->
JID = maps:get(jid, C2SState),
send_last_items(JID),
C2SState.
-spec on_user_offline(ejabberd_c2s:state(), atom()) -> ejabberd_c2s:state().
on_user_offline(#{jid := JID} = C2SState, _Reason) ->
purge_offline(jid:tolower(JID)),
C2SState;
on_user_offline(C2SState, _Reason) ->
C2SState.
%% -------
%% subscription hooks handling functions
@@ -707,14 +604,8 @@ presence(ServerHost, Presence) ->
-spec out_subscription(
binary(), binary(), jid(),
subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
out_subscription(User, Server, JID, subscribed) ->
Owner = jid:make(User, Server),
{PUser, PServer, PResource} = jid:tolower(JID),
PResources = case PResource of
<<>> -> user_resources(PUser, PServer);
_ -> [PResource]
end,
presence(Server, {presence, PUser, PServer, PResources, Owner}),
out_subscription(User, Server, To, subscribed) ->
send_last_pep(jid:make(User, Server), To),
true;
out_subscription(_, _, _, _) ->
true.
@@ -883,7 +774,9 @@ terminate(_Reason,
false ->
ok
end,
ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost,
ejabberd_hooks:delete(c2s_session_opened, ServerHost,
?MODULE, on_user_online, 75),
ejabberd_hooks:delete(c2s_terminated, ServerHost,
?MODULE, on_user_offline, 75),
ejabberd_hooks:delete(disco_local_identity, ServerHost,
?MODULE, disco_local_identity, 75),
@@ -901,12 +794,6 @@ terminate(_Reason,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(c2s_handle_info, ServerHost,
?MODULE, c2s_handle_info, 50),
case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
undefined ->
?ERROR_MSG("~s process is dead, pubsub was broken", [?LOOPNAME]);
Pid ->
Pid ! stop
end,
lists:foreach(
fun(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
@@ -1797,7 +1684,7 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
Nidx = TNode#pubsub_node.id,
Type = TNode#pubsub_node.type,
Options = TNode#pubsub_node.options,
send_items(Host, Node, Nidx, Type, Options, Subscriber, last),
send_items(Host, Node, Nidx, Type, Options, Subscriber, 1),
ServerHost = serverhost(Host),
ejabberd_hooks:run(pubsub_subscribe_node, ServerHost,
[ServerHost, Host, Node, Subscriber, SubId]),
@@ -1908,8 +1795,6 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
broadcast -> Payload;
PluginPayload -> PluginPayload
end,
ejabberd_hooks:run(pubsub_publish_item, ServerHost,
[ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
set_cached_item(Host, Nidx, ItemId, Publisher, BrPayload),
case get_option(Options, deliver_notifications) of
true ->
@@ -1918,6 +1803,8 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
false ->
ok
end,
ejabberd_hooks:run(pubsub_publish_item, ServerHost,
[ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
case Result of
default -> {result, Reply};
_ -> {result, Result}
@@ -2069,20 +1956,14 @@ purge_node(Host, Node, Owner) ->
%% @doc <p>Return the items of a given node.</p>
%% <p>The number of items to return is limited by MaxItems.</p>
%% <p>The permission are not checked in this function.</p>
%% @todo We probably need to check that the user doing the query has the right
%% to read the items.
-spec get_items(host(), binary(), jid(), binary(),
binary(), [binary()], undefined | rsm_set()) ->
{result, pubsub()} | {error, stanza_error()}.
get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
MaxItems = if SMaxItems == undefined ->
case get_max_items_node(Host) of
undefined -> ?MAXITEMS;
Max -> Max
end;
true ->
SMaxItems
end,
get_items(Host, Node, From, SubId, MaxItems, ItemIds, undefined)
when MaxItems =/= undefined ->
get_items(Host, Node, From, SubId, MaxItems, ItemIds,
#rsm_set{max = MaxItems, before = <<>>});
get_items(Host, Node, From, SubId, _MaxItems, ItemIds, RSM) ->
Action =
fun(#pubsub_node{options = Options, type = Type,
id = Nidx, owners = O}) ->
@@ -2101,8 +1982,14 @@ get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
Owners = node_owners_call(Host, Type, Nidx, O),
{PS, RG} = get_presence_and_roster_permissions(
Host, From, Owners, AccessModel, AllowedGroups),
node_call(Host, Type, get_items,
[Nidx, From, AccessModel, PS, RG, SubId, RSM])
case ItemIds of
[ItemId] ->
node_call(Host, Type, get_item,
[Nidx, ItemId, From, AccessModel, PS, RG, undefined]);
_ ->
node_call(Host, Type, get_items,
[Nidx, From, AccessModel, PS, RG, SubId, RSM])
end
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -2118,8 +2005,12 @@ get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
end,
{result,
#pubsub{items = #ps_items{node = Node,
items = itemsEls(lists:sublist(SendItems, MaxItems))},
items = itemsEls(SendItems)},
rsm = RsmOut}};
{result, {_, Item}} ->
{result,
#pubsub{items = #ps_items{node = Node,
items = itemsEls([Item])}}};
Error ->
Error
end.
@@ -2153,43 +2044,23 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) ->
{PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]).
get_last_items(Host, Type, Nidx, LJID, Count) ->
get_last_items(Host, Type, Nidx, LJID, 1) ->
case get_cached_item(Host, Nidx) of
undefined ->
case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of
{result, Items} -> Items;
_ -> []
_ -> []
end;
LastItem ->
[LastItem]
end.
%% @doc <p>Resend the items of a node to the user.</p>
%% @todo use cache-last-item feature
send_items(Host, Node, Nidx, Type, Options, LJID, last) ->
case get_last_items(Host, Type, Nidx, LJID, 1) of
[LastItem] ->
Stanza = items_event_stanza(Node, Options, [LastItem]),
dispatch_items(Host, LJID, Node, Stanza);
_ ->
ok
end;
send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 ->
Stanza = items_event_stanza(Node, Options, get_last_items(Host, Type, Nidx, Number, LJID)),
dispatch_items(Host, LJID, Node, Stanza);
send_items(Host, Node, _Nidx, _Type, Options, LJID, _) ->
Stanza = items_event_stanza(Node, Options, []),
dispatch_items(Host, LJID, Node, Stanza).
dispatch_items({FromU, FromS, FromR}, To, Node, Stanza) ->
SenderResource = user_resource(FromU, FromS, FromR),
ejabberd_sm:route(jid:make(FromU, FromS, SenderResource),
{send_filtered, {pep_message, <<((Node))/binary, "+notify">>},
jid:make(FromU, FromS), jid:make(To),
Stanza});
dispatch_items(From, To, _Node, Stanza) ->
ejabberd_router:route(
xmpp:set_from_to(Stanza, service_jid(From), jid:make(To))).
get_last_items(Host, Type, Nidx, LJID, Count) when Count > 1 ->
case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
{result, Items} -> Items;
_ -> []
end;
get_last_items(_Host, _Type, _Nidx, _LJID, _Count) ->
[].
%% @doc <p>Return the list of affiliations as an XMPP response.</p>
-spec get_affiliations(host(), binary(), jid(), [binary()]) ->
@@ -2536,14 +2407,15 @@ get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) ->
{result, Subs} = node_action(Host, PType,
get_entity_subscriptions_for_send_last,
[Host, JID]),
[{Node, Sub, SubId, SubJID}
[{Node, SubId, SubJID}
|| {Node, Sub, SubId, SubJID} <- Subs,
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID)];
% sql version already filter result by on_sub_and_presence
get_subscriptions_for_send_last(Host, PType, _, JID, LJID, BJID) ->
{result, Subs} = node_action(Host, PType,
get_entity_subscriptions,
[Host, JID]),
[{Node, Sub, SubId, SubJID}
[{Node, SubId, SubJID}
|| {Node, Sub, SubId, SubJID} <- Subs,
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID),
match_option(Node, send_last_published_item, on_sub_and_presence)].
@@ -2932,8 +2804,9 @@ get_options_for_subs(Host, Nidx, Subs, true) ->
broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
NotificationType = get_option(NodeOptions, notification_type, headline),
BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull
From = service_jid(Host),
Stanza = add_message_type(BaseStanza, NotificationType),
Stanza = add_message_type(
xmpp:set_from(BaseStanza, service_jid(Host)),
NotificationType),
%% Handles explicit subscriptions
SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth),
lists:foreach(fun ({LJID, _NodeName, SubIDs}) ->
@@ -2956,7 +2829,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType
end,
lists:foreach(fun(To) ->
ejabberd_router:route(
xmpp:set_from_to(StanzaToSend, From, jid:make(To)))
xmpp:set_to(StanzaToSend, jid:make(To)))
end, LJIDs)
end, SubIDsByJID).
@@ -2965,55 +2838,142 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO
%% Handles implicit presence subscriptions
SenderResource = user_resource(LUser, LServer, LResource),
NotificationType = get_option(NodeOptions, notification_type, headline),
Stanza = add_message_type(BaseStanza, NotificationType),
Stanza = add_message_type(
xmpp:set_from(BaseStanza, jid:make(LUser, LServer)),
NotificationType),
%% set the from address on the notification to the bare JID of the account owner
%% Also, add "replyto" if entity has presence subscription to the account owner
%% See XEP-0163 1.1 section 4.3.1
ejabberd_sm:route(jid:make(LUser, LServer, SenderResource),
{pep_message, <<((Node))/binary, "+notify">>,
jid:make(LUser, LServer),
add_extended_headers(
Stanza, extended_headers([Publisher]))});
broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM).
-spec c2s_handle_info(ejabberd_c2s:state(), term()) -> ejabberd_c2s:state().
c2s_handle_info(#{server := Server} = C2SState,
{pep_message, Feature, From, Packet}) ->
LServer = jid:nameprep(Server),
lists:foreach(
fun({USR, Caps}) ->
Features = mod_caps:get_features(LServer, Caps),
case lists:member(Feature, Features) of
true ->
To = jid:make(USR),
NewPacket = xmpp:set_from_to(Packet, From, To),
ejabberd_router:route(NewPacket);
false ->
ok
end
end, mod_caps:list_features(C2SState)),
c2s_handle_info(#{lserver := LServer} = C2SState,
{pep_message, Feature, Packet}) ->
[maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet)
|| {USR, Caps} <- mod_caps:list_features(C2SState)],
{stop, C2SState};
c2s_handle_info(#{server := Server} = C2SState,
{send_filtered, {pep_message, Feature}, From, To, Packet}) ->
LServer = jid:nameprep(Server),
case mod_caps:get_user_caps(To, C2SState) of
{ok, Caps} ->
Features = mod_caps:get_features(LServer, Caps),
case lists:member(Feature, Features) of
true ->
NewPacket = xmpp:set_from_to(Packet, From, To),
ejabberd_router:route(NewPacket);
false ->
ok
end;
error ->
ok
c2s_handle_info(#{lserver := LServer} = C2SState,
{pep_message, Feature, Packet, USR}) ->
case mod_caps:get_user_caps(USR, C2SState) of
{ok, Caps} -> maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet);
error -> ok
end,
{stop, C2SState};
c2s_handle_info(C2SState, _) ->
C2SState.
send_items(Host, Node, Nidx, Type, Options, LJID, Number) ->
send_items(Host, Node, Nidx, Type, Options, Host, LJID, LJID, Number).
send_items(Host, Node, Nidx, Type, Options, Publisher, SubLJID, ToLJID, Number) ->
case get_last_items(Host, Type, Nidx, SubLJID, Number) of
[] ->
ok;
Items ->
Stanza = items_event_stanza(Node, Options, Items),
send_stanza(Publisher, ToLJID, Node, Stanza)
end.
send_stanza({LUser, LServer, _} = Publisher, USR, Node, BaseStanza) ->
Stanza = xmpp:set_from(BaseStanza, jid:make(LUser, LServer)),
USRs = case USR of
{PUser, PServer, <<>>} ->
[{PUser, PServer, PRessource}
|| PRessource <- user_resources(PUser, PServer)];
_ ->
[USR]
end,
[ejabberd_sm:route(jid:make(Publisher),
{pep_message, <<((Node))/binary, "+notify">>,
add_extended_headers(
Stanza, extended_headers([Publisher])),
To}) || To <- USRs];
send_stanza(Host, USR, _Node, Stanza) ->
ejabberd_router:route(
xmpp:set_from_to(Stanza, service_jid(Host), jid:make(USR))).
maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) ->
Features = mod_caps:get_features(LServer, Caps),
case lists:member(Feature, Features) of
true ->
ejabberd_router:route(xmpp:set_to(Packet, jid:make(USR)));
false ->
ok
end.
send_last_items(JID) ->
ServerHost = JID#jid.lserver,
Host = host(ServerHost),
DBType = config(ServerHost, db_type),
LJID = jid:tolower(JID),
BJID = jid:remove_resource(LJID),
lists:foreach(
fun(PType) ->
Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID),
lists:foreach(
fun({#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx,
options = Options}, _, SubJID})
when Type == PType->
send_items(Host, Node, Nidx, PType, Options, Host, SubJID, LJID, 1);
(_) ->
ok
end,
lists:usort(Subs))
end, config(ServerHost, plugins)).
% pep_from_offline hack can not work anymore, as sender c2s does not
% exists when sender is offline, so we can't get match receiver caps
% does it make sens to send PEP from an offline contact anyway ?
% case config(ServerHost, ignore_pep_from_offline) of
% false ->
% Roster = ejabberd_hooks:run_fold(roster_get, ServerHost, [],
% [{JID#jid.luser, ServerHost}]),
% lists:foreach(
% fun(#roster{jid = {U, S, R}, subscription = Sub})
% when Sub == both orelse Sub == from,
% S == ServerHost ->
% case user_resources(U, S) of
% [] -> send_last_pep(jid:make(U, S, R), JID);
% _ -> ok %% this is already handled by presence probe
% end;
% (_) ->
% ok %% we can not do anything in any cases
% end, Roster);
% true ->
% ok
% end.
send_last_pep(From, To) ->
ServerHost = From#jid.lserver,
Host = host(ServerHost),
Publisher = jid:tolower(From),
Owner = jid:remove_resource(Publisher),
lists:foreach(
fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
case match_option(Options, send_last_published_item, on_sub_and_presence) of
true ->
LJID = jid:tolower(To),
Subscribed = case get_option(Options, access_model) of
open -> true;
presence -> true;
whitelist -> false; % subscribers are added manually
authorize -> false; % likewise
roster ->
Grps = get_option(Options, roster_groups_allowed, []),
{OU, OS, _} = Owner,
element(2, get_roster_info(OU, OS, LJID, Grps))
end,
if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, Publisher, LJID, LJID, 1);
true -> ok
end;
_ ->
ok
end
end,
tree_action(Host, get_nodes, [Owner, From])).
subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
NodesToDeliver = fun (Depth, Node, Subs, Acc) ->
NodeName = case Node#pubsub_node.nodeid of
@@ -3136,10 +3096,10 @@ get_option(Options, Var, Def) ->
-spec node_options(host(), binary()) -> [{atom(), any()}].
node_options(Host, Type) ->
case config(Host, default_node_config) of
undefined -> node_plugin_options(Host, Type);
[] -> node_plugin_options(Host, Type);
Config -> Config
DefaultOpts = node_plugin_options(Host, Type),
case config(Host, plugins) of
[Type|_] -> config(Host, default_node_config, DefaultOpts);
_ -> DefaultOpts
end.
-spec node_plugin_options(host(), binary()) -> [{atom(), any()}].
@@ -3153,13 +3113,6 @@ node_plugin_options(Host, Type) ->
Result
end.
-spec filter_node_options([{atom(), any()}], [{atom(), any()}]) -> [{atom(), any()}].
filter_node_options(Options, BaseOptions) ->
lists:foldl(fun({Key, Val}, Acc) ->
DefaultValue = proplists:get_value(Key, Options, Val),
[{Key, DefaultValue}|Acc]
end, [], BaseOptions).
-spec node_owners_action(host(), binary(), nodeIdx(), [ljid()]) -> [ljid()].
node_owners_action(Host, Type, Nidx, []) ->
case node_action(Host, Type, get_node_affiliations, [Nidx]) of
@@ -3186,9 +3139,6 @@ node_owners_call(_Host, _Type, _Nidx, Owners) ->
%% @doc <p>Return the maximum number of items for a given node.</p>
%% <p>Unlimited means that there is no limit in the number of items that can
%% be stored.</p>
%% @todo In practice, the current data structure means that we cannot manage
%% millions of items on a given node. This should be addressed in a new
%% version.
-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer().
max_items(Host, Options) ->
case get_option(Options, persist_items) of
@@ -3247,8 +3197,8 @@ set_configure(Host, Node, From, Config, Lang) ->
case tree_call(Host,
set_node,
[N#pubsub_node{options = NewOpts}]) of
{result, Nidx} -> {result, ok};
ok -> {result, ok};
{result, Nidx} -> {result, NewOpts};
ok -> {result, NewOpts};
Err -> Err
end;
_ ->
@@ -3257,10 +3207,9 @@ set_configure(Host, Node, From, Config, Lang) ->
end
end,
case transaction(Host, Node, Action, transaction) of
{result, {TNode, ok}} ->
{result, {TNode, Options}} ->
Nidx = TNode#pubsub_node.id,
Type = TNode#pubsub_node.type,
Options = TNode#pubsub_node.options,
broadcast_config_notification(Host, Node, Nidx, Type, Options, Lang),
{result, undefined};
Other ->
@@ -3268,11 +3217,11 @@ set_configure(Host, Node, From, Config, Lang) ->
end.
-spec merge_config([proplists:property()], [proplists:property()]) -> [proplists:property()].
merge_config(Config1, Config2) ->
merge_config(CustomConfig, DefaultConfig) ->
lists:foldl(
fun({Opt, Val}, Acc) ->
lists:keystore(Opt, 1, Acc, {Opt, Val})
end, Config2, Config1).
end, DefaultConfig, CustomConfig).
-spec decode_node_config(undefined | xdata(), binary(), binary()) ->
pubsub_node_config:result() |
@@ -3792,14 +3741,6 @@ subid_shim(SubIds) ->
extended_headers(Jids) ->
[#address{type = replyto, jid = Jid} || Jid <- Jids].
-spec on_user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
on_user_offline(_, JID, _) ->
{User, Server, Resource} = jid:tolower(JID),
case user_resources(User, Server) of
[] -> purge_offline({User, Server, Resource});
_ -> ok
end.
-spec purge_offline(ljid()) -> ok.
purge_offline(LJID) ->
Host = host(element(2, LJID)),
@@ -3840,7 +3781,7 @@ purge_offline(LJID) ->
end
end, lists:usort(lists:flatten(Affs)));
{Error, _} ->
?DEBUG("on_user_offline ~p", [Error])
?ERROR_MSG("can not purge offline: ~p", [Error])
end.
-spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}.
@@ -3852,13 +3793,13 @@ purge_offline(Host, LJID, Node) ->
{result, {[], _}} ->
ok;
{result, {Items, _}} ->
{User, Server, _} = LJID,
{User, Server, Resource} = LJID,
PublishModel = get_option(Options, publish_model),
ForceNotify = get_option(Options, notify_retract),
{_, NodeId} = Node#pubsub_node.nodeid,
lists:foreach(fun
(#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, _}}})
when (U == User) and (S == Server) ->
(#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, R}}})
when (U == User) and (S == Server) and (R == Resource) ->
case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of
{result, {_, broadcast}} ->
broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify),
+1 -1
View File
@@ -547,7 +547,7 @@ delete_sessions(LUser, LServer, LookupFun, Mod) ->
drop_online_sessions(LUser, LServer, Clients) ->
SessIDs = ejabberd_sm:get_session_sids(LUser, LServer),
[Client || {TS, _, _, _} = Client <- Clients,
not lists:keyfind(TS, 1, SessIDs)].
lists:keyfind(TS, 1, SessIDs) == false].
%%--------------------------------------------------------------------
%% Caching.
+1 -1
View File
@@ -91,7 +91,7 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[resume_timeout, wake_on_start, wake_on_timeout, db_type, cache_life_time,
[resume_timeout, wake_on_start, wake_on_timeout, cache_life_time,
cache_size, use_cache, cache_missed, iqdisc].
%%--------------------------------------------------------------------
+1 -1
View File
@@ -162,7 +162,7 @@ delete_session(LUser, LServer, TS) ->
{atomic, ok} ->
ok;
{aborted, E} ->
?ERROR_MSG("Cannot delete push seesion of ~s@~s: ~p",
?ERROR_MSG("Cannot delete push session of ~s@~s: ~p",
[LUser, LServer, E]),
error
end.
+15 -6
View File
@@ -127,7 +127,7 @@ process_iq(#iq{from = From, to = To} = IQ, Source) ->
process_iq(#iq{type = set, lang = Lang,
sub_els = [#register{remove = true}]} = IQ,
_Source, _IsCaptchaEnabled, _AllowRemove = false) ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang));
process_iq(#iq{type = set, lang = Lang, to = To, from = From,
sub_els = [#register{remove = true,
@@ -210,7 +210,14 @@ process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ,
Instr = translate:translate(
Lang, <<"Choose a username and password to register "
"with this server">>),
if IsCaptchaEnabled and not IsRegistered ->
URL = gen_mod:get_module_opt(Server, ?MODULE, redirect_url, <<"">>),
if (URL /= <<"">>) and not IsRegistered ->
Txt = translate:translate(Lang, <<"To register, visit ~s">>),
Desc = str:format(Txt, [URL]),
xmpp:make_iq_result(
IQ, #register{instructions = Desc,
sub_els = [#oob_x{url = URL}]});
IsCaptchaEnabled and not IsRegistered ->
TopInstr = translate:translate(
Lang, <<"You need a client that supports x:data "
"and CAPTCHA to register">>),
@@ -263,7 +270,7 @@ try_register_or_set_password(User, Server, Password,
xmpp:make_error(IQ, Error)
end;
deny ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
end;
_ ->
@@ -315,8 +322,8 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
case {acl:match_rule(Server, Access, JID),
check_ip_access(SourceRaw, IPAccess)}
of
{deny, _} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{_, deny} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{deny, _} -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
{_, deny} -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
{allow, allow} ->
Source = may_remove_resource(SourceRaw),
case check_timeout(Source) of
@@ -614,9 +621,11 @@ mod_opt_type({welcome_message, subject}) ->
fun iolist_to_binary/1;
mod_opt_type({welcome_message, body}) ->
fun iolist_to_binary/1;
mod_opt_type(redirect_url) ->
fun iolist_to_binary/1;
mod_opt_type(_) ->
[access, access_from, access_remove, captcha_protected, ip_access,
iqdisc, password_strength, registration_watchers,
iqdisc, password_strength, registration_watchers, redirect_url,
{welcome_message, subject}, {welcome_message, body}].
-spec opt_type(registration_timeout) -> fun((timeout()) -> timeout());
+1 -1
View File
@@ -184,7 +184,7 @@ process_local_iq(#iq{type = set, from = From, lang = Lang,
Access = gen_mod:get_module_opt(Server, ?MODULE, access, all),
case acl:match_rule(Server, Access, From) of
deny ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
allow ->
process_iq_set(IQ)
+1 -1
View File
@@ -320,7 +320,7 @@ check_from_to(From, To) ->
-spec mk_error(term()) -> stanza_error().
mk_error(forbidden) ->
xmpp:err_forbidden(<<"Denied by ACL">>, ?MYLANG);
xmpp:err_forbidden(<<"Access denied by service policy">>, ?MYLANG);
mk_error(host_unknown) ->
xmpp:err_not_allowed(<<"Host unknown">>, ?MYLANG);
mk_error({codec_error, Why}) ->
+6 -3
View File
@@ -71,7 +71,12 @@ start(Host, _Opts) ->
ejabberd_hooks:add(c2s_terminated, Host, ?MODULE, c2s_terminated, 50).
stop(Host) ->
%% TODO: do something with global 'c2s_init' hook
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
true ->
ok;
false ->
ejabberd_hooks:delete(c2s_init, ?MODULE, c2s_stream_init, 50)
end,
ejabberd_hooks:delete(c2s_stream_started, Host, ?MODULE,
c2s_stream_started, 50),
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE,
@@ -405,8 +410,6 @@ handle_resume(#{user := User, lserver := LServer, sockmod := SockMod,
previd = AttrId}),
State3 = resend_unacked_stanzas(State2),
State4 = send(State3, #sm_r{xmlns = AttrXmlns}),
%% TODO: move this to mod_client_state
%% csi_flush_queue(State4),
State5 = ejabberd_hooks:run_fold(c2s_session_resumed, LServer, State4, []),
?INFO_MSG("(~s) Resumed session for ~s",
[SockMod:pp(Socket), jid:encode(JID)]),
+29 -19
View File
@@ -38,7 +38,7 @@
remove_user/2, export/1, import_info/0, import/5, import_start/2,
depends/2, process_search/1, process_vcard/1, get_vcard/2,
disco_items/5, disco_features/5, disco_identity/5,
decode_iq_subel/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
vcard_iq_set/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@@ -95,6 +95,7 @@ init([Host, Opts]) ->
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50),
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts, false),
if Search ->
@@ -152,6 +153,7 @@ terminate(_Reason, #state{hosts = MyHosts, server_host = Host}) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50),
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:stop(Host),
lists:foreach(
@@ -191,14 +193,6 @@ get_sm_features(Acc, _From, _To, Node, _Lang) ->
_ -> Acc
end.
-spec decode_iq_subel(xmpp_element() | xmlel()) -> xmpp_element() | xmlel().
%% Tell gen_iq_handler not to decode vcard elements
decode_iq_subel(El) ->
case xmpp:get_ns(El) of
?NS_VCARD -> xmpp:encode(El);
_ -> xmpp:decode(El)
end.
-spec process_local_iq(iq()) -> iq().
process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
@@ -212,13 +206,15 @@ process_local_iq(#iq{type = get, lang = Lang} = IQ) ->
bday = <<"2002-11-16">>}).
-spec process_sm_iq(iq()) -> iq().
process_sm_iq(#iq{type = set, lang = Lang, from = From,
sub_els = [SubEl]} = IQ) ->
#jid{user = User, lserver = LServer} = From,
process_sm_iq(#iq{type = set, lang = Lang, from = From} = IQ) ->
#jid{lserver = LServer} = From,
case lists:member(LServer, ?MYHOSTS) of
true ->
set_vcard(User, LServer, SubEl),
xmpp:make_iq_result(IQ);
case ejabberd_hooks:run_fold(vcard_iq_set, LServer, IQ, []) of
drop -> ignore;
#stanza_error{} = Err -> xmpp:make_error(IQ, Err);
_ -> xmpp:make_iq_result(IQ)
end;
false ->
Txt = <<"The query is only allowed from local users">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
@@ -380,19 +376,33 @@ make_vcard_search(User, LUser, LServer, VCARD) ->
orgunit = OrgUnit,
lorgunit = LOrgUnit}.
-spec set_vcard(binary(), binary(), xmlel()) -> {error, badarg} | ok.
-spec vcard_iq_set(iq()) -> iq() | {stop, stanza_error()}.
vcard_iq_set(#iq{from = From, lang = Lang, sub_els = [VCard]} = IQ) ->
#jid{user = User, lserver = LServer} = From,
case set_vcard(User, LServer, VCard) of
{error, badarg} ->
%% Should not be here?
Txt = <<"Nodeprep has failed">>,
{stop, xmpp:err_internal_server_error(Txt, Lang)};
ok ->
IQ
end;
vcard_iq_set(Acc) ->
Acc.
-spec set_vcard(binary(), binary(), xmlel() | vcard_temp()) -> {error, badarg} | ok.
set_vcard(User, LServer, VCARD) ->
case jid:nodeprep(User) of
error ->
{error, badarg};
LUser ->
VCardSearch = make_vcard_search(User, LUser, LServer, VCARD),
VCardEl = xmpp:encode(VCARD),
VCardSearch = make_vcard_search(User, LUser, LServer, VCardEl),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_vcard(LUser, LServer, VCARD, VCardSearch),
Mod:set_vcard(LUser, LServer, VCardEl, VCardSearch),
ets_cache:delete(?VCARD_CACHE, {LUser, LServer},
cache_nodes(Mod, LServer)),
ejabberd_hooks:run(vcard_set, LServer,
[LUser, LServer, VCARD])
ok
end.
-spec string2lower(binary()) -> binary().
+24 -23
View File
@@ -42,6 +42,7 @@
-include("logger.hrl").
-include("eldap.hrl").
-include("xmpp.hrl").
-include("translate.hrl").
-define(PROCNAME, ejabberd_mod_vcard_ldap).
@@ -324,31 +325,31 @@ default_vcard_map() ->
{<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}].
default_search_fields() ->
[{<<"User">>, <<"%u">>},
{<<"Full Name">>, <<"displayName">>},
{<<"Given Name">>, <<"givenName">>},
{<<"Middle Name">>, <<"initials">>},
{<<"Family Name">>, <<"sn">>},
{<<"Nickname">>, <<"%u">>},
{<<"Birthday">>, <<"birthDay">>},
{<<"Country">>, <<"c">>},
{<<"City">>, <<"l">>},
{<<"Email">>, <<"mail">>},
{<<"Organization Name">>, <<"o">>},
{<<"Organization Unit">>, <<"ou">>}].
[{?T("User"), <<"%u">>},
{?T("Full Name"), <<"displayName">>},
{?T("Given Name"), <<"givenName">>},
{?T("Middle Name"), <<"initials">>},
{?T("Family Name"), <<"sn">>},
{?T("Nickname"), <<"%u">>},
{?T("Birthday"), <<"birthDay">>},
{?T("Country"), <<"c">>},
{?T("City"), <<"l">>},
{?T("Email"), <<"mail">>},
{?T("Organization Name"), <<"o">>},
{?T("Organization Unit"), <<"ou">>}].
default_search_reported() ->
[{<<"Full Name">>, <<"FN">>},
{<<"Given Name">>, <<"FIRST">>},
{<<"Middle Name">>, <<"MIDDLE">>},
{<<"Family Name">>, <<"LAST">>},
{<<"Nickname">>, <<"NICK">>},
{<<"Birthday">>, <<"BDAY">>},
{<<"Country">>, <<"CTRY">>},
{<<"City">>, <<"LOCALITY">>},
{<<"Email">>, <<"EMAIL">>},
{<<"Organization Name">>, <<"ORGNAME">>},
{<<"Organization Unit">>, <<"ORGUNIT">>}].
[{?T("Full Name"), <<"FN">>},
{?T("Given Name"), <<"FIRST">>},
{?T("Middle Name"), <<"MIDDLE">>},
{?T("Family Name"), <<"LAST">>},
{?T("Nickname"), <<"NICK">>},
{?T("Birthday"), <<"BDAY">>},
{?T("Country"), <<"CTRY">>},
{?T("City"), <<"LOCALITY">>},
{?T("Email"), <<"EMAIL">>},
{?T("Organization Name"), <<"ORGNAME">>},
{?T("Organization Unit"), <<"ORGUNIT">>}].
parse_options(Host, Opts) ->
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
+25 -24
View File
@@ -36,6 +36,7 @@
-include("xmpp.hrl").
-include("mod_vcard.hrl").
-include("logger.hrl").
-include("translate.hrl").
%%%===================================================================
%%% API
@@ -95,32 +96,32 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
end.
search_fields(_LServer) ->
[{<<"User">>, <<"user">>},
{<<"Full Name">>, <<"fn">>},
{<<"Name">>, <<"first">>},
{<<"Middle Name">>, <<"middle">>},
{<<"Family Name">>, <<"last">>},
{<<"Nickname">>, <<"nick">>},
{<<"Birthday">>, <<"bday">>},
{<<"Country">>, <<"ctry">>},
{<<"City">>, <<"locality">>},
{<<"Email">>, <<"email">>},
{<<"Organization Name">>, <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}].
[{?T("User"), <<"user">>},
{?T("Full Name"), <<"fn">>},
{?T("Name"), <<"first">>},
{?T("Middle Name"), <<"middle">>},
{?T("Family Name"), <<"last">>},
{?T("Nickname"), <<"nick">>},
{?T("Birthday"), <<"bday">>},
{?T("Country"), <<"ctry">>},
{?T("City"), <<"locality">>},
{?T("Email"), <<"email">>},
{?T("Organization Name"), <<"orgname">>},
{?T("Organization Unit"), <<"orgunit">>}].
search_reported(_LServer) ->
[{<<"Jabber ID">>, <<"jid">>},
{<<"Full Name">>, <<"fn">>},
{<<"Name">>, <<"first">>},
{<<"Middle Name">>, <<"middle">>},
{<<"Family Name">>, <<"last">>},
{<<"Nickname">>, <<"nick">>},
{<<"Birthday">>, <<"bday">>},
{<<"Country">>, <<"ctry">>},
{<<"City">>, <<"locality">>},
{<<"Email">>, <<"email">>},
{<<"Organization Name">>, <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}].
[{?T("Jabber ID"), <<"jid">>},
{?T("Full Name"), <<"fn">>},
{?T("Name"), <<"first">>},
{?T("Middle Name"), <<"middle">>},
{?T("Family Name"), <<"last">>},
{?T("Nickname"), <<"nick">>},
{?T("Birthday"), <<"bday">>},
{?T("Country"), <<"ctry">>},
{?T("City"), <<"locality">>},
{?T("Email"), <<"email">>},
{?T("Organization Name"), <<"orgname">>},
{?T("Organization Unit"), <<"orgunit">>}].
remove_user(LUser, LServer) ->
US = {LUser, LServer},
+25 -24
View File
@@ -37,6 +37,7 @@
-include("mod_vcard.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-include("translate.hrl").
%%%===================================================================
%%% API
@@ -150,32 +151,32 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
end.
search_fields(_LServer) ->
[{<<"User">>, <<"user">>},
{<<"Full Name">>, <<"fn">>},
{<<"Name">>, <<"first">>},
{<<"Middle Name">>, <<"middle">>},
{<<"Family Name">>, <<"last">>},
{<<"Nickname">>, <<"nick">>},
{<<"Birthday">>, <<"bday">>},
{<<"Country">>, <<"ctry">>},
{<<"City">>, <<"locality">>},
{<<"Email">>, <<"email">>},
{<<"Organization Name">>, <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}].
[{?T("User"), <<"user">>},
{?T("Full Name"), <<"fn">>},
{?T("Name"), <<"first">>},
{?T("Middle Name"), <<"middle">>},
{?T("Family Name"), <<"last">>},
{?T("Nickname"), <<"nick">>},
{?T("Birthday"), <<"bday">>},
{?T("Country"), <<"ctry">>},
{?T("City"), <<"locality">>},
{?T("Email"), <<"email">>},
{?T("Organization Name"), <<"orgname">>},
{?T("Organization Unit"), <<"orgunit">>}].
search_reported(_LServer) ->
[{<<"Jabber ID">>, <<"jid">>},
{<<"Full Name">>, <<"fn">>},
{<<"Name">>, <<"first">>},
{<<"Middle Name">>, <<"middle">>},
{<<"Family Name">>, <<"last">>},
{<<"Nickname">>, <<"nick">>},
{<<"Birthday">>, <<"bday">>},
{<<"Country">>, <<"ctry">>},
{<<"City">>, <<"locality">>},
{<<"Email">>, <<"email">>},
{<<"Organization Name">>, <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}].
[{?T("Jabber ID"), <<"jid">>},
{?T("Full Name"), <<"fn">>},
{?T("Name"), <<"first">>},
{?T("Middle Name"), <<"middle">>},
{?T("Family Name"), <<"last">>},
{?T("Nickname"), <<"nick">>},
{?T("Birthday"), <<"bday">>},
{?T("Country"), <<"ctry">>},
{?T("City"), <<"locality">>},
{?T("Email"), <<"email">>},
{?T("Organization Name"), <<"orgname">>},
{?T("Organization Unit"), <<"orgunit">>}].
remove_user(LUser, LServer) ->
ejabberd_sql:sql_transaction(
+50 -24
View File
@@ -30,8 +30,8 @@
%% gen_mod callbacks
-export([start/2, stop/1, reload/3]).
-export([update_presence/1, vcard_set/3, remove_user/2,
mod_opt_type/1, depends/2]).
-export([update_presence/1, vcard_set/1, remove_user/2,
user_send_packet/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -47,15 +47,19 @@ start(Host, Opts) ->
init_cache(Host, Opts),
ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE,
update_presence, 100),
ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set,
100),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
user_send_packet, 50),
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE, vcard_set,
90),
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50).
stop(Host) ->
ejabberd_hooks:delete(c2s_self_presence, Host,
?MODULE, update_presence, 100),
ejabberd_hooks:delete(vcard_set, Host, ?MODULE,
vcard_set, 100),
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
user_send_packet, 50),
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE,
vcard_set, 90),
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50).
reload(Host, NewOpts, _OldOpts) ->
@@ -71,16 +75,33 @@ depends(_Host, _Opts) ->
-> {presence(), ejabberd_c2s:state()}.
update_presence({#presence{type = available} = Pres,
#{jid := #jid{luser = LUser, lserver = LServer}} = State}) ->
Hash = get_xupdate(LUser, LServer),
Pres1 = xmpp:set_subtag(Pres, #vcard_xupdate{hash = Hash}),
Pres1 = case get_xupdate(LUser, LServer) of
undefined -> xmpp:remove_subtag(Pres, #vcard_xupdate{});
XUpdate -> xmpp:set_subtag(Pres, XUpdate)
end,
{Pres1, State};
update_presence(Acc) ->
Acc.
-spec vcard_set(binary(), binary(), xmlel()) -> ok.
vcard_set(LUser, LServer, _VCARD) ->
-spec user_send_packet({presence(), ejabberd_c2s:state()})
-> {presence(), ejabberd_c2s:state()}.
user_send_packet({#presence{type = available,
to = #jid{luser = U, lserver = S,
lresource = <<"">>}},
#{jid := #jid{luser = U, lserver = S}}} = Acc) ->
%% This is processed by update_presence/2 explicitly, we don't
%% want to call this multiple times for performance reasons
Acc;
user_send_packet(Acc) ->
update_presence(Acc).
-spec vcard_set(iq()) -> iq().
vcard_set(#iq{from = #jid{luser = LUser, lserver = LServer}} = IQ) ->
ets_cache:delete(?VCARD_XUPDATE_CACHE, {LUser, LServer}),
ejabberd_sm:force_update_presence({LUser, LServer}).
ejabberd_sm:force_update_presence({LUser, LServer}),
IQ;
vcard_set(Acc) ->
Acc.
-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
@@ -91,7 +112,7 @@ remove_user(User, Server) ->
%%====================================================================
%% Storage
%%====================================================================
-spec get_xupdate(binary(), binary()) -> binary() | undefined.
-spec get_xupdate(binary(), binary()) -> vcard_xupdate() | undefined.
get_xupdate(LUser, LServer) ->
Result = case use_cache(LServer) of
true ->
@@ -102,11 +123,12 @@ get_xupdate(LUser, LServer) ->
db_get_xupdate(LUser, LServer)
end,
case Result of
{ok, Hash} -> Hash;
error -> undefined
{ok, external} -> undefined;
{ok, Hash} -> #vcard_xupdate{hash = Hash};
error -> #vcard_xupdate{}
end.
-spec db_get_xupdate(binary(), binary()) -> {ok, binary()} | error.
-spec db_get_xupdate(binary(), binary()) -> {ok, binary() | external} | error.
db_get_xupdate(LUser, LServer) ->
case mod_vcard:get_vcard(LUser, LServer) of
[VCard] ->
@@ -147,17 +169,21 @@ use_cache(Host) ->
Host, ?MODULE, use_cache,
ejabberd_config:use_cache(Host)).
-spec compute_hash(xmlel()) -> binary().
-spec compute_hash(xmlel()) -> binary() | external.
compute_hash(VCard) ->
case fxml:get_path_s(VCard,
[{elem, <<"PHOTO">>},
{elem, <<"BINVAL">>},
cdata]) of
<<>> ->
case fxml:get_subtag(VCard, <<"PHOTO">>) of
false ->
<<>>;
BinVal ->
try str:sha(base64:decode(BinVal))
catch _:badarg -> <<>>
Photo ->
try xmpp:decode(Photo, ?NS_VCARD, []) of
#vcard_photo{binval = <<_, _/binary>> = BinVal} ->
str:sha(BinVal);
#vcard_photo{extval = <<_, _/binary>>} ->
external;
_ ->
<<>>
catch _:{xmpp_codec, _} ->
<<>>
end
end.
+90 -22
View File
@@ -376,23 +376,26 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
true ->
if MaxItems > 0 ->
Now = p1_time_compat:timestamp(),
PubId = {Now, SubKey},
Item = case get_item(Nidx, ItemId) of
{result, OldItem} ->
OldItem#pubsub_item{modification = PubId,
payload = Payload};
case get_item(Nidx, ItemId) of
{result, #pubsub_item{creation = {_, GenKey}} = OldItem} ->
set_item(OldItem#pubsub_item{
modification = {Now, SubKey},
payload = Payload}),
{result, {default, broadcast, []}};
{result, _} ->
{error, xmpp:err_forbidden()};
_ ->
#pubsub_item{itemid = {ItemId, Nidx},
nodeidx = Nidx,
creation = {Now, GenKey},
modification = PubId,
payload = Payload}
end,
Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
{result, {NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
set_item(Item),
set_state(GenState#pubsub_state{items = NI}),
{result, {default, broadcast, OI}};
Items = [ItemId | GenState#pubsub_state.items],
{result, {NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
set_state(GenState#pubsub_state{items = NI}),
set_item(#pubsub_item{
itemid = {ItemId, Nidx},
nodeidx = Nidx,
creation = {Now, GenKey},
modification = {Now, SubKey},
payload = Payload}),
{result, {default, broadcast, OI}}
end;
true ->
{result, {default, broadcast, []}}
end
@@ -725,9 +728,53 @@ del_state(#pubsub_state{stateid = {Key, Nidx}, items = Items}) ->
%% mod_pubsub module.</p>
%% <p>PubSub plugins can store the items where they wants (for example in a
%% relational database), or they can even decide not to persist any items.</p>
get_items(Nidx, _From, _RSM) ->
Items = mnesia:index_read(pubsub_item, Nidx, #pubsub_item.nodeidx),
{result, {lists:reverse(lists:keysort(#pubsub_item.modification, Items)), undefined}}.
get_items(Nidx, _From, undefined) ->
RItems = lists:keysort(#pubsub_item.creation,
mnesia:index_read(pubsub_item, Nidx, #pubsub_item.nodeidx)),
Count = length(RItems),
if Count =< ?MAXITEMS ->
{result, {RItems, undefined}};
true ->
ItemsPage = lists:sublist(RItems, ?MAXITEMS),
Rsm = rsm_page(Count, 0, 0, ItemsPage),
{result, {ItemsPage, Rsm}}
end;
get_items(Nidx, _From, #rsm_set{max = Max, index = IncIndex,
'after' = After, before = Before}) ->
RItems = lists:keysort(#pubsub_item.creation,
mnesia:index_read(pubsub_item, Nidx, #pubsub_item.nodeidx)),
Count = length(RItems),
Limit = case Max of
undefined -> ?MAXITEMS;
_ -> Max
end,
{Offset, ItemsPage} =
case {IncIndex, Before, After} of
{I, undefined, undefined} ->
SubList = lists:nthtail(I, RItems),
{I, lists:sublist(SubList, Limit)};
{_, <<>>, undefined} ->
%% 2.5 Requesting the Last Page in a Result Set
SubList = lists:reverse(RItems),
{0, lists:sublist(SubList, Limit)};
{_, Stamp, undefined} ->
BeforeNow = encode_stamp(Stamp),
SubList = lists:dropwhile(
fun(#pubsub_item{creation = {Now, _}}) ->
Now >= BeforeNow
end, lists:reverse(RItems)),
{0, lists:sublist(SubList, Limit)};
{_, undefined, Stamp} ->
AfterNow = encode_stamp(Stamp),
SubList = lists:dropwhile(
fun(#pubsub_item{creation = {Now, _}}) ->
Now =< AfterNow
end, RItems),
{0, lists:sublist(SubList, Limit)}
end,
Rsm = rsm_page(Count, IncIndex, Offset, ItemsPage),
{result, {ItemsPage, Rsm}}.
get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM) ->
SubKey = jid:tolower(JID),
@@ -765,9 +812,10 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM
get_items(Nidx, JID, RSM)
end.
get_last_items(Nidx, From, Count) when Count > 0 ->
{result, {Items, _}} = get_items(Nidx, From, undefined),
{result, lists:sublist(Items, Count)};
get_last_items(Nidx, _From, Count) when Count > 0 ->
Items = mnesia:index_read(pubsub_item, Nidx, #pubsub_item.nodeidx),
LastItems = lists:reverse(lists:keysort(#pubsub_item.modification, Items)),
{result, lists:sublist(LastItems, Count)};
get_last_items(_Nidx, _From, _Count) ->
{result, []}.
@@ -876,6 +924,26 @@ first_in_list(Pred, [H | T]) ->
_ -> first_in_list(Pred, T)
end.
rsm_page(Count, Index, Offset, Items) ->
FirstItem = hd(Items),
LastItem = lists:last(Items),
First = decode_stamp(element(1, FirstItem#pubsub_item.creation)),
Last = decode_stamp(element(1, LastItem#pubsub_item.creation)),
#rsm_set{count = Count, index = Index,
first = #rsm_first{index = Offset, data = First},
last = Last}.
encode_stamp(Stamp) ->
case catch xmpp_util:decode_timestamp(Stamp) of
{MS,S,US} -> {MS,S,US};
_ -> Stamp
end.
decode_stamp(Stamp) ->
case catch xmpp_util:encode_timestamp(Stamp) of
TimeStamp when is_binary(TimeStamp) -> TimeStamp;
_ -> Stamp
end.
transform({pubsub_state, {Id, Nidx}, Is, A, Ss}) ->
{pubsub_state, {Id, Nidx}, Nidx, Is, A, Ss};
transform({pubsub_item, {Id, Nidx}, C, M, P}) ->
+141 -115
View File
@@ -243,20 +243,31 @@ publish_item(Nidx, Publisher, PublishModel, MaxItems, ItemId, Payload,
if not ((PublishModel == open) or
(PublishModel == publishers) and
((Affiliation == owner)
or (Affiliation == publisher)
or (Affiliation == publish_only))
or (Affiliation == publisher)
or (Affiliation == publish_only))
or (Subscribed == true)) ->
{error, xmpp:err_forbidden()};
true ->
if MaxItems > 0 ->
PubId = {p1_time_compat:timestamp(), SubKey},
set_item(#pubsub_item{itemid = {ItemId, Nidx},
creation = {p1_time_compat:timestamp(), GenKey},
modification = PubId,
payload = Payload}),
Items = [ItemId | itemids(Nidx, GenKey) -- [ItemId]],
{result, {_, OI}} = remove_extra_items(Nidx, MaxItems, Items),
{result, {default, broadcast, OI}};
Now = p1_time_compat:timestamp(),
case get_item(Nidx, ItemId) of
{result, #pubsub_item{creation = {_, GenKey}} = OldItem} ->
set_item(OldItem#pubsub_item{
modification = {Now, SubKey},
payload = Payload}),
{result, {default, broadcast, []}};
{result, _} ->
{error, xmpp:err_forbidden()};
_ ->
Items = [ItemId | itemids(Nidx, GenKey)],
{result, {_NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
set_item(#pubsub_item{
itemid = {ItemId, Nidx},
creation = {Now, GenKey},
modification = {Now, SubKey},
payload = Payload}),
{result, {default, broadcast, OI}}
end;
true ->
{result, {default, broadcast, []}}
end
@@ -284,9 +295,23 @@ delete_item(Nidx, Publisher, PublishModel, ItemId) ->
if not Allowed ->
{error, xmpp:err_forbidden()};
true ->
case del_item(Nidx, ItemId) of
{updated, 1} -> {result, {default, broadcast}};
_ -> {error, xmpp:err_item_not_found()}
Items = itemids(Nidx, GenKey),
case lists:member(ItemId, Items) of
true ->
case del_item(Nidx, ItemId) of
{updated, 1} -> {result, {default, broadcast}};
_ -> {error, xmpp:err_item_not_found()}
end;
false ->
case Affiliation of
owner ->
case del_item(Nidx, ItemId) of
{updated, 1} -> {result, {default, broadcast}};
_ -> {error, xmpp:err_item_not_found()}
end;
_ ->
{error, xmpp:err_forbidden()}
end
end
end.
@@ -647,95 +672,61 @@ del_state(Nidx, JID) ->
" where jid=%(J)s and nodeid=%(Nidx)d")),
ok.
get_items(Nidx, From, undefined) ->
MaxItems = case ejabberd_sql:sql_query_t(
?SQL("select @(val)s from pubsub_node_option "
"where nodeid=%(Nidx)d and name='max_items'")) of
{selected, [{Value}]} ->
misc:expr_to_term(Value);
_ ->
?MAXITEMS
end,
get_items(Nidx, From, #rsm_set{max = MaxItems});
get_items(Nidx, _From, undefined) ->
SNidx = misc:i2l(Nidx),
case ejabberd_sql:sql_query_t(
[<<"select itemid, publisher, creation, modification, payload",
" from pubsub_item where nodeid='", SNidx/binary, "'",
" order by creation asc">>]) of
{selected, _, AllItems} ->
Count = length(AllItems),
if Count =< ?MAXITEMS ->
{result, {[raw_to_item(Nidx, RItem) || RItem <- AllItems], undefined}};
true ->
RItems = lists:sublist(AllItems, ?MAXITEMS),
Rsm = rsm_page(Count, 0, 0, RItems),
{result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], Rsm}}
end;
_ ->
{result, {[], undefined}}
end;
get_items(Nidx, _From, #rsm_set{max = Max, index = IncIndex,
'after' = After, before = Before}) ->
{Way, Order} = if After == <<>> -> {<<"is not">>, <<"desc">>};
After /= undefined -> {<<"<">>, <<"desc">>};
Before == <<>> -> {<<"is not">>, <<"asc">>};
Before /= undefined -> {<<">">>, <<"asc">>};
true -> {<<"is not">>, <<"desc">>}
end,
SNidx = misc:i2l(Nidx),
I = if After /= undefined -> After;
Before /= undefined -> Before;
true -> undefined
end,
[AttrName, Id] =
case I of
undefined when IncIndex =/= undefined ->
case ejabberd_sql:sql_query_t(
[<<"select creation from pubsub_item pi "
"where exists ( select count(*) as count1 "
"from pubsub_item where nodeid='">>, SNidx,
<<"' and creation > pi.creation having count1 = ">>,
integer_to_binary(IncIndex), <<" );">>]) of
{selected, [_], [[O]]} ->
[<<"creation">>, <<"'", O/binary, "'">>];
_ ->
[<<"creation">>, <<"null">>]
end;
undefined ->
[<<"creation">>, <<"null">>];
<<>> ->
[<<"creation">>, <<"null">>];
I ->
[A, B] = str:tokens(ejabberd_sql:escape(I), <<"@">>),
[A, <<"'", B/binary, "'">>]
end,
Count = case ejabberd_sql:sql_query_t(
[<<"select count(*) from pubsub_item where nodeid='">>,
SNidx, <<"';">>]) of
{selected, [_], [[C]]} -> binary_to_integer(C);
Count = case catch ejabberd_sql:sql_query_t(
?SQL("select @(count(itemid))d from pubsub_item"
" where nodeid=%(Nidx)d")) of
{selected, [{C}]} -> C;
_ -> 0
end,
Offset = case {IncIndex, Before, After} of
{I, undefined, undefined} when is_integer(I) -> I;
_ -> 0
end,
Limit = case Max of
undefined -> ?MAXITEMS;
_ -> Max
end,
Filters = rsm_filters(misc:i2l(Nidx), Before, After),
Query = fun(mssql, _) ->
ejabberd_sql:sql_query_t(
[<<"select top ">>, integer_to_binary(Max),
<<" itemid, publisher, creation, modification, payload "
"from pubsub_item where nodeid='">>, SNidx,
<<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
AttrName, <<" ">>, Order, <<";">>]);
[<<"select top ", (integer_to_binary(Limit))/binary,
" itemid, publisher, creation, modification, payload",
" from pubsub_item", Filters/binary>>]);
%OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
(_, _) ->
ejabberd_sql:sql_query_t(
[<<"select itemid, publisher, creation, modification, payload "
"from pubsub_item where nodeid='">>, SNidx,
<<"' and ">>, AttrName, <<" ">>, Way, <<" ">>, Id, <<" order by ">>,
AttrName, <<" ">>, Order, <<" limit ">>,
integer_to_binary(Max), <<" ;">>])
[<<"select itemid, publisher, creation, modification, payload",
" from pubsub_item", Filters/binary,
" limit ", (integer_to_binary(Limit))/binary,
" offset ", (integer_to_binary(Offset))/binary>>])
end,
case ejabberd_sql:sql_query_t(Query) of
{selected, _, []} ->
{result, {[], #rsm_set{count = Count}}};
{selected, [<<"itemid">>, <<"publisher">>, <<"creation">>,
<<"modification">>, <<"payload">>], RItems} ->
case RItems of
[[_, _, _, F, _]|_] ->
Index = case catch ejabberd_sql:sql_query_t(
[<<"select count(*) from pubsub_item "
"where nodeid='">>, SNidx, <<"' and ">>,
AttrName, <<" > '">>, F, <<"';">>]) of
{selected, [_], [[In]]} -> binary_to_integer(In);
_ -> 0
end,
[_, _, _, L, _] = lists:last(RItems),
RsmOut = #rsm_set{count = Count,
index = Index,
first = #rsm_first{
index = Index,
data = <<"creation@", F/binary>>},
last = <<"creation@", L/binary>>},
{result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], RsmOut}};
[] ->
{result, {[], #rsm_set{count = Count}}}
end;
Rsm = rsm_page(Count, IncIndex, Offset, RItems),
{result, {[raw_to_item(Nidx, RItem) || RItem <- RItems], Rsm}};
_ ->
{result, {[], undefined}}
end.
@@ -773,24 +764,24 @@ get_items(Nidx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId, RSM
get_items(Nidx, JID, RSM)
end.
get_last_items(Nidx, _From, Count) ->
Limit = misc:i2l(Count),
get_last_items(Nidx, _From, Limit) ->
SNidx = misc:i2l(Nidx),
Query = fun(mssql, _) ->
ejabberd_sql:sql_query_t(
[<<"select top ">>, Limit,
<<" itemid, publisher, creation, modification, payload "
"from pubsub_item where nodeid='">>, SNidx,
<<"' order by modification desc ;">>]);
[<<"select top ", (integer_to_binary(Limit))/binary,
" itemid, publisher, creation, modification, payload",
" from pubsub_item where nodeid='", SNidx/binary,
"' order by modification desc">>]);
(_, _) ->
ejabberd_sql:sql_query_t(
[<<"select itemid, publisher, creation, modification, payload "
"from pubsub_item where nodeid='">>, SNidx,
<<"' order by modification desc limit ">>, Limit, <<";">>])
[<<"select itemid, publisher, creation, modification, payload",
" from pubsub_item where nodeid='", SNidx/binary,
"' order by modification desc ",
" limit ", (integer_to_binary(Limit))/binary>>])
end,
case catch ejabberd_sql:sql_query_t(Query) of
{selected,
[<<"itemid">>, <<"publisher">>, <<"creation">>, <<"modification">>, <<"payload">>], RItems} ->
{selected, [<<"itemid">>, <<"publisher">>, <<"creation">>,
<<"modification">>, <<"payload">>], RItems} ->
{result, [raw_to_item(Nidx, RItem) || RItem <- RItems]};
_ ->
{result, []}
@@ -798,9 +789,9 @@ get_last_items(Nidx, _From, Count) ->
get_item(Nidx, ItemId) ->
case catch ejabberd_sql:sql_query_t(
?SQL("select @(itemid)s, @(publisher)s, @(creation)s,"
" @(modification)s, @(payload)s from pubsub_item"
" where nodeid=%(Nidx)d and itemid=%(ItemId)s"))
?SQL("select @(itemid)s, @(publisher)s, @(creation)s,"
" @(modification)s, @(payload)s from pubsub_item"
" where nodeid=%(Nidx)d and itemid=%(ItemId)s"))
of
{selected, [RItem]} ->
{result, raw_to_item(Nidx, RItem)};
@@ -850,11 +841,8 @@ set_item(Item) ->
P = encode_jid(JID),
Payload = Item#pubsub_item.payload,
XML = str:join([fxml:element_to_binary(X) || X<-Payload], <<>>),
S = fun ({T1, T2, T3}) ->
str:join([misc:i2l(T1, 6), misc:i2l(T2, 6), misc:i2l(T3, 6)], <<":">>)
end,
SM = S(M),
SC = S(C),
SM = encode_now(M),
SC = encode_now(C),
?SQL_UPSERT_T(
"pubsub_item",
["!nodeid=%(Nidx)d",
@@ -1029,15 +1017,53 @@ raw_to_item(Nidx, [ItemId, SJID, Creation, Modification, XML]) ->
raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML});
raw_to_item(Nidx, {ItemId, SJID, Creation, Modification, XML}) ->
JID = decode_jid(SJID),
ToTime = fun (Str) ->
[T1, T2, T3] = str:tokens(Str, <<":">>),
{misc:l2i(T1), misc:l2i(T2), misc:l2i(T3)}
end,
Payload = case fxml_stream:parse_element(XML) of
{error, _Reason} -> [];
El -> [El]
end,
#pubsub_item{itemid = {ItemId, Nidx},
creation = {ToTime(Creation), jid:remove_resource(JID)},
modification = {ToTime(Modification), JID},
creation = {decode_now(Creation), jid:remove_resource(JID)},
modification = {decode_now(Modification), JID},
payload = Payload}.
rsm_filters(SNidx, undefined, undefined) ->
<<" where nodeid='", SNidx/binary, "'",
" order by creation asc">>;
rsm_filters(SNidx, undefined, After) ->
<<" where nodeid='", SNidx/binary, "'",
" and creation>'", (encode_stamp(After))/binary, "'",
" order by creation asc">>;
rsm_filters(SNidx, <<>>, undefined) ->
%% 2.5 Requesting the Last Page in a Result Set
<<" where nodeid='", SNidx/binary, "'",
" order by creation desc">>;
rsm_filters(SNidx, Before, undefined) ->
<<" where nodeid='", SNidx/binary, "'",
" and creation<'", (encode_stamp(Before))/binary, "'",
" order by creation desc">>.
rsm_page(Count, Index, Offset, Items) ->
First = decode_stamp(lists:nth(3, hd(Items))),
Last = decode_stamp(lists:nth(3, lists:last(Items))),
#rsm_set{count = Count, index = Index,
first = #rsm_first{index = Offset, data = First},
last = Last}.
encode_stamp(Stamp) ->
case catch xmpp_util:decode_timestamp(Stamp) of
{MS,S,US} -> encode_now({MS,S,US});
_ -> Stamp
end.
decode_stamp(Stamp) ->
case catch xmpp_util:encode_timestamp(decode_now(Stamp)) of
TimeStamp when is_binary(TimeStamp) -> TimeStamp;
_ -> Stamp
end.
encode_now({T1, T2, T3}) ->
<<(misc:i2l(T1, 6))/binary, ":",
(misc:i2l(T2, 6))/binary, ":",
(misc:i2l(T3, 6))/binary>>.
decode_now(NowStr) ->
[MS, S, US] = binary:split(NowStr, <<":">>, [global]),
{binary_to_integer(MS), binary_to_integer(S), binary_to_integer(US)}.
+2 -2
View File
@@ -518,5 +518,5 @@ deserialize([{_, S}|T], #xmlel{children = Els} = El, Acc) when is_binary(S) ->
deserialize(T, El#xmlel{children = [{xmlcdata, S}|Els]}, Acc);
deserialize([{_, L}|T], #xmlel{children = Els} = El, Acc) when is_list(L) ->
deserialize(T, El#xmlel{children = deserialize(L) ++ Els}, Acc);
deserialize([], El, Acc) ->
[El|Acc].
deserialize([], #xmlel{children = Els} = El, Acc) ->
[El#xmlel{children = lists:reverse(Els)}|Acc].
+15
View File
@@ -32,6 +32,20 @@
-define(THRESHOLD, 16#10000000000000000).
-ifdef(RAND_UNIFORM).
get_string() ->
R = rand:uniform(?THRESHOLD),
integer_to_binary(R).
uniform() ->
rand:uniform().
uniform(N) ->
rand:uniform(N).
uniform(N, M) ->
rand:uniform(M-N+1) + N-1.
-else.
get_string() ->
R = crypto:rand_uniform(0, ?THRESHOLD),
integer_to_binary(R).
@@ -44,6 +58,7 @@ uniform(N) ->
uniform(N, M) ->
crypto:rand_uniform(N, M+1).
-endif.
-ifdef(STRONG_RAND_BYTES).
bytes(N) ->
+1 -1
View File
@@ -671,7 +671,7 @@ process_stream_established(#{stream_state := StateName} = State)
when StateName == disconnected; StateName == established ->
State;
process_stream_established(#{mod := Mod} = State) ->
State1 = State#{stream_authenticated := true,
State1 = State#{stream_authenticated => true,
stream_state => established,
stream_timeout => infinity},
try Mod:handle_stream_established(State1)
+68 -29
View File
@@ -51,13 +51,15 @@
-type host_port() :: {inet:hostname(), inet:port_number()}.
-type ip_port() :: {inet:ip_address(), inet:port_number()}.
-type network_error() :: {error, inet:posix() | inet_res:res_error()}.
-type tls_error_reason() :: inet:posix() | atom() | binary().
-type socket_error_reason() :: inet:posix() | atom().
-type stop_reason() :: {idna, bad_string} |
{dns, inet:posix() | inet_res:res_error()} |
{stream, reset | {in | out, stream_error()}} |
{tls, inet:posix() | atom() | binary()} |
{tls, tls_error_reason()} |
{pkix, binary()} |
{auth, atom() | binary() | string()} |
{socket, inet:posix() | atom()} |
{socket, socket_error_reason()} |
internal_failure.
-export_type([state/0, stop_reason/0]).
-callback init(list()) -> {ok, state()} | {error, term()} | ignore.
@@ -276,19 +278,20 @@ handle_cast(connect, #{remote_server := RemoteServer,
process_stream_end({idna, bad_string}, State);
ASCIIName ->
case resolve(binary_to_list(ASCIIName), State) of
{ok, AddrPorts} ->
case connect(AddrPorts, State) of
{{ok, AddrPorts}, Encrypted} ->
case connect(AddrPorts, State, Encrypted) of
{ok, Socket, AddrPort} ->
SocketMonitor = SockMod:monitor(Socket),
State1 = State#{ip => AddrPort,
socket => Socket,
stream_encrypted => Encrypted,
socket_monitor => SocketMonitor},
State2 = State1#{stream_state => wait_for_stream},
send_header(State2);
{error, Why} ->
process_stream_end({socket, Why}, State)
{error, {Class, Why}} ->
process_stream_end({Class, Why}, State)
end;
{error, Why} ->
{{error, Why}, _} ->
process_stream_end({dns, Why}, State)
end
end);
@@ -578,11 +581,8 @@ process_sasl_mechanisms(Mechs, #{user := User, server := Server} = State) ->
end.
-spec process_starttls(state()) -> state().
process_starttls(#{sockmod := SockMod, socket := Socket, mod := Mod} = State) ->
TLSOpts = try Mod:tls_options(State)
catch _:undef -> []
end,
case SockMod:starttls(Socket, [connect|TLSOpts]) of
process_starttls(#{socket := Socket} = State) ->
case starttls(Socket, State) of
{ok, TLSSocket} ->
State1 = State#{socket => TLSSocket,
stream_id => new_id(),
@@ -770,6 +770,19 @@ close_socket(State) ->
State#{stream_timeout => infinity,
stream_state => disconnected}.
-spec starttls(term(), state()) -> {ok, term()} | {error, tls_error_reason()}.
starttls(Socket, #{sockmod := SockMod, mod := Mod,
xmlns := NS, remote_server := RemoteServer} = State) ->
TLSOpts = try Mod:tls_options(State)
catch _:undef -> []
end,
SNI = idna_to_ascii(RemoteServer),
ALPN = case NS of
?NS_SERVER -> <<"xmpp-server">>;
?NS_CLIENT -> <<"xmpp-client">>
end,
SockMod:starttls(Socket, [connect, {sni, SNI}, {alpn, [ALPN]}|TLSOpts]).
-spec select_lang(binary(), binary()) -> binary().
select_lang(Lang, <<"">>) -> Lang;
select_lang(_, Lang) -> Lang.
@@ -841,47 +854,61 @@ idna_to_ascii(Host) ->
{error, _} -> ejabberd_idna:domain_utf8_to_ascii(Host)
end.
-spec resolve(string(), state()) -> {ok, [ip_port()]} | network_error().
-spec resolve(string(), state()) -> {{ok, [ip_port()]} | network_error(), boolean()}.
resolve(Host, State) ->
case srv_lookup(Host, State) of
{error, _Reason} ->
{{error, _Reason}, _} ->
DefaultPort = get_default_port(State),
a_lookup([{Host, DefaultPort}], State);
{ok, HostPorts} ->
a_lookup(HostPorts, State)
{a_lookup([{Host, DefaultPort}], State), false};
{{ok, HostPorts}, TLS} ->
{a_lookup(HostPorts, State), TLS}
end.
-spec srv_lookup(string(), state()) -> {ok, [host_port()]} | network_error().
-spec srv_lookup(string(), state()) -> {{ok, [host_port()]} | network_error(), boolean()}.
srv_lookup(_Host, #{xmlns := ?NS_COMPONENT}) ->
%% Do not attempt to lookup SRV for component connections
{error, nxdomain};
{{error, nxdomain}, false};
srv_lookup(Host, State) ->
%% Only perform SRV lookups for FQDN names
case string:chr(Host, $.) of
0 ->
{error, nxdomain};
{{error, nxdomain}, false};
_ ->
case inet:parse_address(Host) of
{ok, _} ->
{error, nxdomain};
{{error, nxdomain}, false};
{error, _} ->
Timeout = get_dns_timeout(State),
Retries = get_dns_retries(State),
srv_lookup(Host, Timeout, Retries)
case is_starttls_available(State) of
true ->
case srv_lookup("_xmpps-server._tcp." ++ Host,
Timeout, Retries) of
{error, _} ->
{srv_lookup("_xmpp-server._tcp." ++ Host,
Timeout, Retries),
false};
{ok, Res} ->
{{ok, Res}, true}
end;
false ->
{srv_lookup("_xmpp-server._tcp." ++ Host,
Timeout, Retries),
false}
end
end
end.
-spec srv_lookup(string(), timeout(), integer()) ->
{ok, [host_port()]} | network_error().
srv_lookup(_Host, _Timeout, Retries) when Retries < 1 ->
srv_lookup(_SRVName, _Timeout, Retries) when Retries < 1 ->
{error, timeout};
srv_lookup(Host, Timeout, Retries) ->
SRVName = "_xmpp-server._tcp." ++ Host,
srv_lookup(SRVName, Timeout, Retries) ->
case inet_res:getbyname(SRVName, srv, Timeout) of
{ok, HostEntry} ->
host_entry_to_host_ports(HostEntry);
{error, timeout} ->
srv_lookup(Host, Timeout, Retries - 1);
srv_lookup(SRVName, Timeout, Retries - 1);
{error, _} = Err ->
Err
end.
@@ -971,10 +998,22 @@ host_entry_to_addr_ports(#hostent{h_addr_list = AddrList}, Port) ->
_ -> {ok, AddrPorts}
end.
-spec connect([ip_port()], state()) -> {ok, term(), ip_port()} | network_error().
connect(AddrPorts, #{sockmod := SockMod} = State) ->
-spec connect([ip_port()], state(), boolean()) -> {ok, term(), ip_port()} |
{error, {socket, socket_error_reason()}} |
{error, {tls, tls_error_reason()}}.
connect(AddrPorts, #{sockmod := SockMod} = State, TLS) ->
Timeout = get_connect_timeout(State),
connect(AddrPorts, SockMod, Timeout, {error, nxdomain}).
case connect(AddrPorts, SockMod, Timeout, {error, nxdomain}) of
{ok, Socket, AddrPort} when TLS ->
case starttls(Socket, State) of
{ok, TLSSocket} -> {ok, TLSSocket, AddrPort};
{error, Why} -> {error, {tls, Why}}
end;
{ok, _Socket, _AddrPort} = OK ->
OK;
{error, Why} ->
{error, {socket, Why}}
end.
-spec connect([ip_port()], module(), timeout(), network_error()) ->
{ok, term(), ip_port()} | network_error().
+11 -3
View File
@@ -258,7 +258,8 @@ master_slave_cases() ->
{privacy_master_slave, [sequence],
[master_slave_test(deny_bare_jid),
master_slave_test(deny_full_jid),
master_slave_test(deny_server_jid),
master_slave_test(deny_server_bare_jid),
master_slave_test(deny_server_full_jid),
master_slave_test(deny_group),
master_slave_test(deny_sub_both),
master_slave_test(deny_sub_from),
@@ -285,11 +286,18 @@ deny_full_jid_master(Config) ->
deny_full_jid_slave(Config) ->
deny_slave(Config).
deny_server_jid_master(Config) ->
deny_server_bare_jid_master(Config) ->
{_, Server, _} = jid:tolower(?config(peer, Config)),
deny_master(Config, {jid, Server}).
deny_server_jid_slave(Config) ->
deny_server_bare_jid_slave(Config) ->
deny_slave(Config).
deny_server_full_jid_master(Config) ->
{_, Server, Resource} = jid:tolower(?config(peer, Config)),
deny_master(Config, {jid, jid:encode({<<"">>, Server, Resource})}).
deny_server_full_jid_slave(Config) ->
deny_slave(Config).
deny_group_master(Config) ->
+2 -2
View File
@@ -134,8 +134,8 @@ mam_slave(Config) ->
self_presence(Config, available),
ct:comment("Receiving message from offline storage"),
recv_test_message(Config),
ct:comment("Re-enabling push notifications"),
ok = enable_push(Config),
%% Don't re-enable push notifications, otherwise the notification would be
%% suppressed while the slave is online.
ct:comment("Enabling MAM"),
ok = enable_mam(Config),
ct:comment("Letting the master know that we're ready"),
+300
View File
@@ -0,0 +1,300 @@
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -pa ebin
main([Dir]) ->
Txts =
filelib:fold_files(
Dir, ".+\.beam\$", false,
fun(BeamFile, Res) ->
Mod = mod(BeamFile),
ErlFile = filename:join("src", Mod ++ ".erl"),
case get_forms(BeamFile, ErlFile) of
{ok, BeamForms, ErlForms} ->
process_forms(BeamForms, Mod, application) ++
process_forms(ErlForms, Mod, macro) ++ Res;
_Err ->
Res
end
end, []),
Dict = lists:foldl(
fun({B, Meta}, Acc) ->
dict:update(
binary_to_list(B),
fun(OldMeta) ->
lists:usort([Meta|OldMeta])
end,
[Meta], Acc)
end, dict:new(), Txts),
generate_pot(Dict).
process_forms(Forms, Mod, Type) ->
Tree = erl_syntax:form_list(Forms),
erl_syntax_lib:fold_subtrees(
fun(Form, Acc) ->
case erl_syntax:type(Form) of
function ->
case map(Form, Mod, Type) of
[] ->
Acc;
Vars ->
Vars ++ Acc
end;
_ ->
Acc
end
end, [], Tree).
map(Tree, Mod, Type) ->
Vars = erl_syntax_lib:fold(
fun(Form, Acc) ->
case erl_syntax:type(Form) of
Type when Type == application ->
analyze_app(Form, Mod) ++ Acc;
Type when Type == macro ->
analyze_macro(Form, Mod) ++ Acc;
_ ->
Acc
end
end, [], Tree),
Bins = lists:flatmap(
fun({Var, Pos}) when is_atom(Var) ->
Res = erl_syntax_lib:fold(
fun(Form, Acc) ->
case process_match_expr(
Form, Var, Mod) of
{ok, Binary, NewPos} ->
[{Binary, NewPos}|Acc];
error ->
Acc
end
end, [], Tree),
case Res of
[] ->
log("~s:~p: unresolved variable: ~s~n",
[Mod, Pos, Var]);
_ ->
ok
end,
Res;
({Var, Pos}) when is_binary(Var) ->
[{Var, Pos}]
end, lists:usort(Vars)),
[{B, {Mod, Pos}} || {B, Pos} <- Bins, B /= <<"">>].
process_match_expr(Form, Var, Mod) ->
case erl_syntax:type(Form) of
match_expr ->
Pattern = erl_syntax:match_expr_pattern(Form),
Body = erl_syntax:match_expr_body(Form),
{V, Expr} =
case {erl_syntax:type(Pattern), erl_syntax:type(Body)} of
{variable, _} ->
{erl_syntax:variable_name(Pattern), Body};
{_, variable} ->
{erl_syntax:variable_name(Body), Pattern};
_ ->
{'', none}
end,
Text = maybe_extract_tuple(Expr),
if V == Var ->
Pos = erl_syntax:get_pos(Text),
try {ok, erl_syntax:concrete(Text), Pos}
catch _:_ ->
case catch erl_syntax_lib:analyze_application(Text) of
{_M, {Fn, 1}} when Fn == format_error;
Fn == io_format_error ->
error;
_ ->
log("~s:~p: not a binary: ~s~n",
[Mod, Pos, erl_prettypr:format(Text)]),
{ok, <<>>, Pos}
end
end;
true ->
error
end;
_ ->
error
end.
maybe_extract_tuple(none) ->
none;
maybe_extract_tuple(Form) ->
try
tuple = erl_syntax:type(Form),
[Text, _] = erl_syntax:tuple_elements(Form),
Text
catch _:{badmatch, _} ->
Form
end.
analyze_app(Form, Mod) ->
try
{M, {F, A}} = erl_syntax_lib:analyze_application(Form),
Args = erl_syntax:application_arguments(Form),
Txt = case {M, atom_to_list(F), A, Args} of
{xmpp, "err_" ++ _, 2, [T|_]} -> T;
{xmpp, "serr_" ++ _, 2, [T|_]} -> T;
{xmpp, "mk_text", 2, [T|_]} -> T;
{translate, "translate", 2, [_,T|_]} -> T
end,
Pos = erl_syntax:get_pos(Txt),
case erl_syntax:type(Txt) of
binary ->
try [{erl_syntax:concrete(Txt), Pos}]
catch _:_ ->
Pos = erl_syntax:get_pos(Txt),
log("~s:~p: not a binary: ~s~n",
[Mod, Pos, erl_prettypr:format(Txt)]),
[]
end;
variable ->
[{erl_syntax:variable_name(Txt), Pos}];
application ->
Vars = sets:to_list(erl_syntax_lib:variables(Txt)),
case Vars of
[Var] ->
[{Var, Pos}];
[_|_] ->
log("Too many variables: ~p~n", [Vars]),
[];
[] ->
[]
end;
_ ->
[]
end
catch _:{badmatch, _} ->
[];
_:{case_clause, _} ->
[]
end.
analyze_macro(Form, Mod) ->
try
Name = erl_syntax:macro_name(Form),
variable = erl_syntax:type(Name),
'T' = erl_syntax:variable_name(Name),
[Txt] = erl_syntax:macro_arguments(Form),
string = erl_syntax:type(Txt),
Pos = erl_syntax:get_pos(Txt),
try [{list_to_binary(erl_syntax:string_value(Txt)), Pos}]
catch _:_ ->
log("~s:~p: not a binary: ~s~n",
[Mod, Pos, erl_prettypr:format(Txt)]),
[]
end
catch _:{badmatch, _} ->
[]
end.
generate_pot(Dict) ->
io:format("~s~n~n", [pot_header()]),
lists:foreach(
fun({Msg, Location}) ->
S1 = format_location(Location),
S2 = format_msg(Msg),
io:format("~smsgstr \"\"~n~n", [S1 ++ S2])
end, lists:keysort(1, dict:to_list(Dict))).
format_location([A, B, C|T]) ->
format_location_list([A,B,C]) ++ format_location(T);
format_location([A, B|T]) ->
format_location_list([A,B]) ++ format_location(T);
format_location([A|T]) ->
format_location_list([A]) ++ format_location(T);
format_location([]) ->
"".
format_location_list(L) ->
"#: " ++ string:join(
lists:map(
fun({File, Pos}) ->
io_lib:format("~s:~B", [File, Pos])
end, L),
" ") ++ io_lib:nl().
format_msg(Bin) ->
io_lib:format("msgid \"~s\"~n", [escape(Bin)]).
escape(Bin) ->
lists:map(
fun($") -> "\\\"";
(C) -> C
end, binary_to_list(iolist_to_binary(Bin))).
pot_header() ->
string:join(
["msgid \"\"",
"msgstr \"\"",
"\"Project-Id-Version: 15.11.127\\n\"",
"\"X-Language: Language Name\\n\"",
"\"Last-Translator: Translator name and contact method\\n\"",
"\"MIME-Version: 1.0\\n\"",
"\"Content-Type: text/plain; charset=UTF-8\\n\"",
"\"Content-Transfer-Encoding: 8bit\\n\""],
io_lib:nl()).
mod(Path) ->
filename:rootname(filename:basename(Path)).
log(Format, Args) ->
io:format(standard_error, Format, Args).
get_forms(BeamFile, ErlFile) ->
try
{ok, BeamForms} = get_beam_forms(BeamFile),
{ok, ErlForms} = get_erl_forms(ErlFile),
{ok, BeamForms, ErlForms}
catch _:{badmatch, error} ->
error
end.
get_beam_forms(File) ->
case beam_lib:chunks(File, [abstract_code]) of
{ok, {_, List}} ->
case lists:keyfind(abstract_code, 1, List) of
{abstract_code, {raw_abstract_v1, Abstr}} ->
{ok, Abstr};
_Err ->
log("failed to get abstract code from ~s~n", [File]),
error
end;
Err ->
log("failed to read chunks from ~s: ~p~n", [File, Err]),
error
end.
get_erl_forms(Path) ->
case file:open(Path, [read]) of
{ok, Fd} ->
parse(Path, Fd, 1, []);
{error, Why} ->
log("failed to read ~s: ~s~n", [Path, file:format_error(Why)]),
error
end.
parse(Path, Fd, Line, Acc) ->
{ok, Pos} = file:position(Fd, cur),
case epp_dodger:parse_form(Fd, Line) of
{ok, Form, NewLine} ->
{ok, NewPos} = file:position(Fd, cur),
{ok, RawForm} = file:pread(Fd, Pos, NewPos - Pos),
file:position(Fd, {bof, NewPos}),
AnnForm = erl_syntax:set_ann(Form, RawForm),
parse(Path, Fd, NewLine, [AnnForm|Acc]);
{eof, _} ->
{ok, NewPos} = file:position(Fd, cur),
if NewPos > Pos ->
{ok, RawForm} = file:pread(Fd, Pos, NewPos - Pos),
Form = erl_syntax:text(""),
AnnForm = erl_syntax:set_ann(Form, RawForm),
{ok, lists:reverse([AnnForm|Acc])};
true ->
{ok, lists:reverse(Acc)}
end;
Err ->
log("failed to parse ~s: ~p~n", [Path, Err]),
error
end.
+100
View File
@@ -0,0 +1,100 @@
#!/bin/bash
# Frontend for ejabberd's extract-tr.sh
# How to create template files for a new language:
# NEWLANG=zh
# cp priv/msgs/ejabberd.pot priv/msgs/$NEWLANG.po
# echo \{\"\",\"\"\}. > priv/msgs/$NEWLANG.msg
# make translations
extract_lang_src2pot ()
{
./tools/extract-tr.sh ebin/ > priv/msgs/ejabberd.pot
}
extract_lang_popot2po ()
{
LANG_CODE=$1
PO_PATH=$MSGS_DIR/$LANG_CODE.po
POT_PATH=$MSGS_DIR/$PROJECT.pot
msgmerge $PO_PATH $POT_PATH >$PO_PATH.translate 2>/dev/null
mv $PO_PATH.translate $PO_PATH
}
extract_lang_po2msg ()
{
LANG_CODE=$1
PO_PATH=$LANG_CODE.po
MS_PATH=$PO_PATH.ms
MSGID_PATH=$PO_PATH.msgid
MSGSTR_PATH=$PO_PATH.msgstr
MSGS_PATH=$LANG_CODE.msg
cd $MSGS_DIR
# Check PO has correct ~
# Let's convert to C format so we can use msgfmt
PO_TEMP=$LANG_CODE.po.temp
cat $PO_PATH | sed 's/%/perc/g' | sed 's/~/%/g' | sed 's/#:.*/#, c-format/g' >$PO_TEMP
msgfmt $PO_TEMP --check-format
result=$?
rm $PO_TEMP
if [ $result -ne 0 ] ; then
exit 1
fi
msgattrib $PO_PATH --translated --no-fuzzy --no-obsolete --no-location --no-wrap | grep "^msg" | tail --lines=+3 >$MS_PATH
grep "^msgid" $PO_PATH.ms | sed 's/^msgid //g' >$MSGID_PATH
grep "^msgstr" $PO_PATH.ms | sed 's/^msgstr //g' >$MSGSTR_PATH
echo "%% -*- coding: latin-1 -*-" >$MSGS_PATH
paste $MSGID_PATH $MSGSTR_PATH --delimiter=, | awk '{print "{" $0 "}."}' | sort -g >>$MSGS_PATH
rm $MS_PATH
rm $MSGID_PATH
rm $MSGSTR_PATH
}
extract_lang_updateall ()
{
echo ""
echo "Generating POT..."
extract_lang_src2pot
cd $MSGS_DIR
echo ""
echo -e "File Missing (fuzzy) Language Last translator"
echo -e "---- ------- ------- -------- ---------------"
for i in $( ls *.msg ) ; do
LANG_CODE=${i%.msg}
echo -n $LANG_CODE | awk '{printf "%-6s", $1 }'
PO=$LANG_CODE.po
extract_lang_popot2po $LANG_CODE
extract_lang_po2msg $LANG_CODE
MISSING=`msgfmt --statistics $PO 2>&1 | awk '{printf "%5s", $4+$7 }'`
echo -n " $MISSING"
FUZZY=`msgfmt --statistics $PO 2>&1 | awk '{printf "%7s", $4 }'`
echo -n " $FUZZY"
LANGUAGE=`grep "X-Language:" $PO | sed 's/\"X-Language: //g' | sed 's/\\\\n\"//g' | awk '{printf "%-12s", $1}'`
echo -n " $LANGUAGE"
LASTAUTH=`grep "Last-Translator" $PO | sed 's/\"Last-Translator: //g' | sed 's/\\\\n\"//g'`
echo " $LASTAUTH"
done
echo ""
rm messages.mo
cd ..
}
EJA_DIR=`pwd`
PROJECT=ejabberd
MSGS_DIR=$EJA_DIR/priv/msgs
extract_lang_updateall
+1
View File
@@ -42,6 +42,7 @@
{iconv, @iconv@}.
{stun, @stun@}.
{sip, @sip@}.
{graphics, @graphics@}.
%% Version
{vsn, "@PACKAGE_VERSION@"}.