aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ejabberd_app.erl2
-rw-r--r--src/ejabberd_auth.erl44
-rw-r--r--src/ejabberd_auth_sql.erl28
-rw-r--r--src/ejabberd_c2s.erl14
-rw-r--r--src/ejabberd_config.erl8
-rw-r--r--src/ejabberd_http.erl24
-rw-r--r--src/ejabberd_listener.erl58
-rw-r--r--src/ejabberd_sm.erl58
-rw-r--r--src/ejabberd_sql.erl17
-rw-r--r--src/ejabberd_sql_pt.erl27
-rw-r--r--src/mod_carboncopy.erl33
-rw-r--r--src/mod_http_api.erl21
-rw-r--r--src/mod_offline_sql.erl13
-rw-r--r--src/mod_private.erl58
-rw-r--r--src/mod_pubsub.erl8
-rw-r--r--src/proxy_protocol.erl184
16 files changed, 523 insertions, 74 deletions
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index 9bffbae35..90c36a593 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -152,10 +152,10 @@ start_apps() ->
crypto:start(),
ejabberd:start_app(sasl),
ejabberd:start_app(ssl),
- ejabberd:start_app(pkix),
ejabberd:start_app(p1_utils),
ejabberd:start_app(fast_yaml),
ejabberd:start_app(fast_tls),
+ ejabberd:start_app(pkix),
ejabberd:start_app(xmpp),
ejabberd:start_app(cache_tab),
ejabberd:start_app(eimp).
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index 5659ee389..bc0211548 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -41,7 +41,8 @@
get_password_s/2, get_password_with_authmodule/2,
user_exists/2, user_exists_in_other_modules/3,
remove_user/2, remove_user/3, plain_password_required/1,
- store_type/1, entropy/1, backend_type/1, password_format/1]).
+ store_type/1, entropy/1, backend_type/1, password_format/1,
+ which_users_exists/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
@@ -411,6 +412,47 @@ user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) ->
maybe
end.
+-spec which_users_exists(list({binary(), binary()})) -> list({binary(), binary()}).
+which_users_exists(USPairs) ->
+ ByServer = lists:foldl(
+ fun({User, Server}, Dict) ->
+ LServer = jid:nameprep(Server),
+ LUser = jid:nodeprep(User),
+ case gb_trees:lookup(LServer, Dict) of
+ none ->
+ gb_trees:insert(LServer, gb_sets:singleton(LUser), Dict);
+ {value, Set} ->
+ gb_trees:update(LServer, gb_sets:add(LUser, Set), Dict)
+ end
+ end, gb_trees:empty(), USPairs),
+ Set = lists:foldl(
+ fun({LServer, UsersSet}, Results) ->
+ UsersList = gb_sets:to_list(UsersSet),
+ lists:foldl(
+ fun(M, Results2) ->
+ try M:which_users_exists(LServer, UsersList) of
+ {error, _} ->
+ Results2;
+ Res ->
+ gb_sets:union(
+ gb_sets:from_list([{U, LServer} || U <- Res]),
+ Results2)
+ catch
+ _:undef ->
+ lists:foldl(
+ fun(U, R2) ->
+ case user_exists(U, LServer) of
+ true ->
+ gb_sets:add({U, LServer}, R2);
+ _ ->
+ R2
+ end
+ end, Results2, UsersList)
+ end
+ end, Results, auth_modules(LServer))
+ end, gb_sets:empty(), gb_trees:to_list(ByServer)),
+ gb_sets:to_list(Set).
+
-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
case validate_credentials(User, Server) of
diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl
index 4b774642a..cd9c02b91 100644
--- a/src/ejabberd_auth_sql.erl
+++ b/src/ejabberd_auth_sql.erl
@@ -35,7 +35,7 @@
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, count_users/2, get_password/2,
remove_user/2, store_type/1, plain_password_required/1,
- convert_to_scram/1, opt_type/1, export/1]).
+ convert_to_scram/1, opt_type/1, export/1, which_users_exists/2]).
-include("scram.hrl").
-include("logger.hrl").
@@ -247,6 +247,32 @@ users_number(LServer, [{prefix, Prefix}])
users_number(LServer, []) ->
users_number(LServer).
+which_users_exists(LServer, LUsers) when length(LUsers) =< 100 ->
+ try ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(username)s from users where username in %(LUsers)ls")) of
+ {selected, Matching} ->
+ [U || {U} <- Matching];
+ {error, _} = E ->
+ E
+ catch _:B ->
+ {error, B}
+ end;
+which_users_exists(LServer, LUsers) ->
+ {First, Rest} = lists:split(100, LUsers),
+ case which_users_exists(LServer, First) of
+ {error, _} = E ->
+ E;
+ V ->
+ case which_users_exists(LServer, Rest) of
+ {error, _} = E2 ->
+ E2;
+ V2 ->
+ V ++ V2
+ end
+ end.
+
+
convert_to_scram(Server) ->
LServer = jid:nameprep(Server),
if
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index c65e71bdf..ba5b04af8 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -708,13 +708,11 @@ process_presence_out(#{lserver := LServer, jid := JID,
end.
-spec process_self_presence(state(), presence()) -> state().
-process_self_presence(#{ip := IP, conn := Conn, lserver := LServer,
- auth_module := AuthMod, sid := SID,
+process_self_presence(#{lserver := LServer, sid := SID,
user := U, server := S, resource := R} = State,
#presence{type = unavailable} = Pres) ->
Status = xmpp:get_text(Pres#presence.status),
- Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthMod}],
- ejabberd_sm:unset_presence(SID, U, S, R, Status, Info),
+ ejabberd_sm:unset_presence(SID, U, S, R, Status),
{Pres1, State1} = ejabberd_hooks:run_fold(
c2s_self_presence, LServer, {Pres, State}, []),
State2 = broadcast_presence_unavailable(State1, Pres1),
@@ -732,13 +730,11 @@ process_self_presence(#{lserver := LServer} = State,
process_self_presence(State, _Pres) ->
State.
--spec update_priority(state(), presence()) -> ok.
-update_priority(#{ip := IP, conn := Conn, auth_module := AuthMod,
- sid := SID, user := U, server := S, resource := R},
+-spec update_priority(state(), presence()) -> ok | {error, notfound}.
+update_priority(#{sid := SID, user := U, server := S, resource := R},
Pres) ->
Priority = get_priority_from_presence(Pres),
- Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthMod}],
- ejabberd_sm:set_presence(SID, U, S, R, Priority, Pres, Info).
+ ejabberd_sm:set_presence(SID, U, S, R, Priority, Pres).
-spec broadcast_presence_unavailable(state(), presence()) -> state().
broadcast_presence_unavailable(#{jid := JID, pres_a := PresA} = State, Pres) ->
diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl
index 48fdcefbc..90bbed179 100644
--- a/src/ejabberd_config.erl
+++ b/src/ejabberd_config.erl
@@ -500,7 +500,8 @@ get_config_option_key(Name, Val) ->
maps_to_lists(IMap) ->
maps:fold(fun(Name, Map, Res) when Name == host_config orelse Name == append_host_config ->
- [{Name, [{Host, maps_to_lists(SMap)} || {Host,SMap} <- maps:values(Map)]} | Res];
+ [{Name, [{jid:nameprep(Host), maps_to_lists(SMap)} ||
+ {Host,SMap} <- maps:values(Map)]} | Res];
(Name, Map, Res) when is_map(Map) ->
[{Name, maps:values(Map)} | Res];
(Name, Val, Res) ->
@@ -513,8 +514,9 @@ merge_configs(Terms, ResMap) ->
New = lists:foldl(fun(SVal, OMap) ->
NVal = if Name == host_config orelse Name == append_host_config ->
{Host, Opts} = SVal,
- {_, SubMap} = maps:get(Host, OMap, {Host, #{}}),
- {Host, merge_configs(Opts, SubMap)};
+ HostNP = jid:nameprep(Host),
+ {_, SubMap} = maps:get(HostNP, OMap, {HostNP, #{}}),
+ {HostNP, merge_configs(Opts, SubMap)};
true ->
SVal
end,
diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl
index 727b57f8f..769577371 100644
--- a/src/ejabberd_http.erl
+++ b/src/ejabberd_http.erl
@@ -69,7 +69,8 @@
default_host,
custom_headers,
trail = <<>>,
- addr_re
+ addr_re,
+ sock_peer_name = none
}).
-define(XHTML_DOCTYPE,
@@ -143,6 +144,7 @@ init({SockMod, Socket}, Opts) ->
true -> [{[], ejabberd_xmlrpc}];
false -> []
end,
+ SockPeer = proplists:get_value(sock_peer_name, Opts, none),
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
Admin ++ Bind ++ XMLRPC,
@@ -159,6 +161,7 @@ init({SockMod, Socket}, Opts) ->
custom_headers = CustomHeaders,
options = Opts,
request_handlers = RequestHandlers,
+ sock_peer_name = SockPeer,
addr_re = RE},
try receive_headers(State) of
V -> V
@@ -463,6 +466,7 @@ process_request(#state{request_method = Method,
request_version = Version,
sockmod = SockMod,
socket = Socket,
+ sock_peer_name = SockPeer,
options = Options,
request_host = Host,
request_port = Port,
@@ -481,13 +485,17 @@ process_request(#state{request_method = Method,
{State2, false} ->
{State2, make_bad_request(State)};
{State2, {LPath, LQuery, Data}} ->
- PeerName =
- case SockMod of
- gen_tcp ->
- inet:peername(Socket);
- _ ->
- SockMod:peername(Socket)
- end,
+ PeerName = case SockPeer of
+ none ->
+ case SockMod of
+ gen_tcp ->
+ inet:peername(Socket);
+ _ ->
+ SockMod:peername(Socket)
+ end;
+ {_, Peer} ->
+ {ok, Peer}
+ end,
IPHere = case PeerName of
{ok, V} -> V;
{error, _} = E -> throw(E)
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index 3a1448c0b..e8742413b 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -204,26 +204,49 @@ accept(ListenSocket, Module, Opts, Sup, Interval) ->
NewInterval = check_rate_limit(Interval),
case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
- case {inet:sockname(Socket), inet:peername(Socket)} of
- {{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} ->
- Receiver = case start_connection(Module, Socket, Opts, Sup) of
- {ok, RecvPid} ->
- RecvPid;
- _ ->
- gen_tcp:close(Socket),
- none
- end,
- ?INFO_MSG("(~p) Accepted connection ~s:~p -> ~s:~p",
- [Receiver,
- ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
- PPort, inet_parse:ntoa(Addr), Port]);
+ case proplists:get_value(use_proxy_protocol, Opts, false) of
+ true ->
+ case proxy_protocol:decode(gen_tcp, Socket, 10000) of
+ {error, Err} ->
+ ?ERROR_MSG("(~w) Proxy protocol parsing failed: ~s",
+ [ListenSocket, inet:format_error(Err)]),
+ gen_tcp:close(Socket);
+ {{Addr, Port}, {PAddr, PPort}} = SP ->
+ Opts2 = [{sock_peer_name, SP} | Opts],
+ Receiver = case start_connection(Module, Socket, Opts2, Sup) of
+ {ok, RecvPid} ->
+ RecvPid;
+ _ ->
+ gen_tcp:close(Socket),
+ none
+ end,
+ ?INFO_MSG("(~p) Accepted proxied connection ~s:~p -> ~s:~p",
+ [Receiver,
+ ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
+ PPort, inet_parse:ntoa(Addr), Port])
+ end;
_ ->
- gen_tcp:close(Socket)
+ case {inet:sockname(Socket), inet:peername(Socket)} of
+ {{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} ->
+ Receiver = case start_connection(Module, Socket, Opts, Sup) of
+ {ok, RecvPid} ->
+ RecvPid;
+ _ ->
+ gen_tcp:close(Socket),
+ none
+ end,
+ ?INFO_MSG("(~p) Accepted connection ~s:~p -> ~s:~p",
+ [Receiver,
+ ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
+ PPort, inet_parse:ntoa(Addr), Port]);
+ _ ->
+ gen_tcp:close(Socket)
+ end
end,
accept(ListenSocket, Module, Opts, Sup, NewInterval);
{error, Reason} ->
?ERROR_MSG("(~w) Failed TCP accept: ~s",
- [ListenSocket, inet:format_error(Reason)]),
+ [ListenSocket, inet:format_error(Reason)]),
accept(ListenSocket, Module, Opts, Sup, NewInterval)
end.
@@ -665,7 +688,9 @@ listen_opt_type(max_fsm_queue) ->
listen_opt_type(shaper) ->
fun acl:shaper_rules_validator/1;
listen_opt_type(access) ->
- fun acl:access_rules_validator/1.
+ fun acl:access_rules_validator/1;
+listen_opt_type(use_proxy_protocol) ->
+ fun(B) when is_boolean(B) -> B end.
listen_options() ->
[module, port,
@@ -675,6 +700,7 @@ listen_options() ->
{inet6, false},
{accept_interval, 0},
{backlog, 5},
+ {use_proxy_protocol, false},
{supervisor, true}].
opt_type(listen) -> fun validate_cfg/1;
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 119a70939..4643f46b7 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -48,8 +48,8 @@
disconnect_removed_user/2,
get_user_resources/2,
get_user_present_resources/2,
- set_presence/7,
- unset_presence/6,
+ set_presence/6,
+ unset_presence/5,
close_session_unset_presence/5,
dirty_get_sessions_list/0,
dirty_get_my_sessions_list/0,
@@ -316,26 +316,48 @@ del_user_info(User, Server, Resource, Key) ->
end.
-spec set_presence(sid(), binary(), binary(), binary(),
- prio(), presence(), info()) -> ok.
+ prio(), presence()) -> ok | {error, notfound}.
-set_presence(SID, User, Server, Resource, Priority,
- Presence, Info) ->
- set_session(SID, User, Server, Resource, Priority,
- Info),
- ejabberd_hooks:run(set_presence_hook,
- jid:nameprep(Server),
- [User, Server, Resource, Presence]).
+set_presence(SID, User, Server, Resource, Priority, Presence) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
+ case get_sessions(Mod, LUser, LServer, LResource) of
+ [] -> {error, notfound};
+ Ss ->
+ case lists:keyfind(SID, 1, Ss) of
+ #session{info = Info} ->
+ set_session(SID, User, Server, Resource, Priority, Info),
+ ejabberd_hooks:run(set_presence_hook,
+ LServer,
+ [User, Server, Resource, Presence]);
+ false ->
+ {error, notfound}
+ end
+ end.
-spec unset_presence(sid(), binary(), binary(),
- binary(), binary(), info()) -> ok.
+ binary(), binary()) -> ok | {error, notfound}.
-unset_presence(SID, User, Server, Resource, Status,
- Info) ->
- set_session(SID, User, Server, Resource, undefined,
- Info),
- ejabberd_hooks:run(unset_presence_hook,
- jid:nameprep(Server),
- [User, Server, Resource, Status]).
+unset_presence(SID, User, Server, Resource, Status) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ LResource = jid:resourceprep(Resource),
+ Mod = get_sm_backend(LServer),
+ case get_sessions(Mod, LUser, LServer, LResource) of
+ [] -> {error, notfound};
+ Ss ->
+ case lists:keyfind(SID, 1, Ss) of
+ #session{info = Info} ->
+ set_session(SID, User, Server, Resource, undefined, Info),
+ ejabberd_hooks:run(unset_presence_hook,
+ LServer,
+ [User, Server, Resource, Status]);
+ false ->
+ {error, notfound}
+ end
+ end.
-spec close_session_unset_presence(sid(), binary(), binary(),
binary(), binary()) -> ok.
diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl
index 3d3741548..9e088f211 100644
--- a/src/ejabberd_sql.erl
+++ b/src/ejabberd_sql.erl
@@ -37,12 +37,12 @@
sql_query_t/1,
sql_transaction/2,
sql_bloc/2,
- abort/1,
- restart/1,
- use_new_schema/0,
- sql_query_to_iolist/1,
+ abort/1,
+ restart/1,
+ use_new_schema/0,
+ sql_query_to_iolist/1,
escape/1,
- standard_escape/1,
+ standard_escape/1,
escape_like/1,
escape_like_arg/1,
escape_like_arg_circumflex/1,
@@ -55,7 +55,8 @@
freetds_config/0,
odbcinst_config/0,
init_mssql/1,
- keep_alive/2]).
+ keep_alive/2,
+ to_list/2]).
%% gen_fsm callbacks
-export([init/1, handle_event/3, handle_sync_event/4,
@@ -258,6 +259,10 @@ to_bool(true) -> true;
to_bool(1) -> true;
to_bool(_) -> false.
+to_list(EscapeFun, Val) ->
+ Escaped = lists:join(<<",">>, lists:map(EscapeFun, Val)),
+ [<<"(">>, Escaped, <<")">>].
+
encode_term(Term) ->
escape(list_to_binary(
erl_prettypr:format(erl_syntax:abstract(Term),
diff --git a/src/ejabberd_sql_pt.erl b/src/ejabberd_sql_pt.erl
index eb7905bf0..1f6134d07 100644
--- a/src/ejabberd_sql_pt.erl
+++ b/src/ejabberd_sql_pt.erl
@@ -306,6 +306,20 @@ parse1([$%, $( | S], Acc, State) ->
false ->
append_string("0=0", State3)
end;
+ {list, InternalType} ->
+ Convert = erl_syntax:application(
+ erl_syntax:atom(ejabberd_sql),
+ erl_syntax:atom(to_list),
+ [erl_syntax:record_access(
+ erl_syntax:variable(?ESCAPE_VAR),
+ erl_syntax:atom(?ESCAPE_RECORD),
+ erl_syntax:atom(InternalType)),
+ erl_syntax:variable(Name)]),
+ State2#state{'query' = [{var, Var} | State2#state.'query'],
+ args = [Convert | State2#state.args],
+ params = [Var | State2#state.params],
+ param_pos = State2#state.param_pos + 1,
+ used_vars = [Name | State2#state.used_vars]};
_ ->
Convert =
erl_syntax:application(
@@ -335,6 +349,19 @@ parse_name(S, IsArg, State) ->
parse_name([], _Acc, _Depth, _IsArg, State) ->
throw({error, State#state.loc,
"expected ')', found end of string"});
+parse_name([$), $l, T | S], Acc, 0, true, State) ->
+ Type = case T of
+ $d -> {list, integer};
+ $s -> {list, string};
+ $b -> {list, boolean};
+ _ ->
+ throw({error, State#state.loc,
+ ["unknown type specifier 'l", T, "'"]})
+ end,
+ {lists:reverse(Acc), Type, S, State};
+parse_name([$), $l, T | _], _Acc, 0, false, State) ->
+ throw({error, State#state.loc,
+ ["list type 'l", T, "' is not allowed for outputs"]});
parse_name([$), T | S], Acc, 0, IsArg, State) ->
Type =
case T of
diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl
index 4c05f84c3..e1f82e872 100644
--- a/src/mod_carboncopy.erl
+++ b/src/mod_carboncopy.erl
@@ -38,6 +38,7 @@
iq_handler/1, disco_features/5,
is_carbon_copy/1, mod_opt_type/1, depends/2,
mod_options/1]).
+-export([c2s_copy_session/2, c2s_session_opened/1, c2s_session_resumed/1]).
%% For debugging purposes
-export([list/2]).
@@ -45,6 +46,7 @@
-include("xmpp.hrl").
-type direction() :: sent | received.
+-type c2s_state() :: ejabberd_c2s:state().
-spec is_carbon_copy(stanza()) -> boolean().
is_carbon_copy(#message{meta = #{carbon_copy := true}}) ->
@@ -57,6 +59,9 @@ start(Host, _Opts) ->
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89),
ejabberd_hooks:add(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
+ ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE, c2s_copy_session, 50),
+ ejabberd_hooks:add(c2s_session_resumed, Host, ?MODULE, c2s_session_resumed, 50),
+ ejabberd_hooks:add(c2s_session_opened, Host, ?MODULE, c2s_session_opened, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler).
stop(Host) ->
@@ -64,7 +69,10 @@ stop(Host) ->
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
ejabberd_hooks:delete(user_send_packet,Host, ?MODULE, user_send_packet, 89),
- ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89).
+ ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
+ ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE, c2s_copy_session, 50),
+ ejabberd_hooks:delete(c2s_session_resumed, Host, ?MODULE, c2s_session_resumed, 50),
+ ejabberd_hooks:delete(c2s_session_opened, Host, ?MODULE, c2s_session_opened, 50).
reload(_Host, _NewOpts, _OldOpts) ->
ok.
@@ -123,6 +131,29 @@ user_receive_packet({Packet, #{jid := JID} = C2SState}) ->
Pkt -> {Pkt, C2SState}
end.
+-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
+c2s_copy_session(State, #{user := U, server := S, resource := R}) ->
+ case ejabberd_sm:get_user_info(U, S, R) of
+ offline -> State;
+ Info ->
+ case lists:keyfind(carboncopy, 1, Info) of
+ {_, CC} -> State#{carboncopy => CC};
+ false -> State
+ end
+ end.
+
+-spec c2s_session_resumed(c2s_state()) -> c2s_state().
+c2s_session_resumed(#{user := U, server := S, resource := R,
+ carboncopy := CC} = State) ->
+ ejabberd_sm:set_user_info(U, S, R, carboncopy, CC),
+ maps:remove(carboncopy, State);
+c2s_session_resumed(State) ->
+ State.
+
+-spec c2s_session_opened(c2s_state()) -> c2s_state().
+c2s_session_opened(State) ->
+ maps:remove(carboncopy, State).
+
% Modified from original version:
% - registered to the user_send_packet hook, to be called only once even for multicast
% - do not support "private" message mode, and do not modify the original packet in any way
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index 4d4a40f79..3fb0d5981 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -325,15 +325,20 @@ handle2(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) ->
format_command_result(Call, Auth, Res, Version)
end.
-get_elem_delete(A, L) ->
+get_elem_delete(A, L, F) ->
case proplists:get_all_values(A, L) of
[Value] -> {Value, proplists:delete(A, L)};
[_, _ | _] ->
%% Crash reporting the error
exit({duplicated_attribute, A, L});
[] ->
- %% Report the error and then force a crash
- exit({attribute_not_found, A, L})
+ case F of
+ {list, _} ->
+ {[], L};
+ _ ->
+ %% Report the error and then force a crash
+ exit({attribute_not_found, A, L})
+ end
end.
format_args(Args, ArgsFormat) ->
@@ -342,7 +347,7 @@ format_args(Args, ArgsFormat) ->
{Args1, Res}) ->
{ArgValue, Args2} =
get_elem_delete(ArgName,
- Args1),
+ Args1, ArgFormat),
Formatted = format_arg(ArgValue,
ArgFormat),
{Args2, Res ++ [Formatted]}
@@ -471,6 +476,9 @@ format_result(Code, {Name, restuple}) ->
format_result(Els, {Name, {list, {_, {tuple, [{_, atom}, _]}} = Fmt}}) ->
{misc:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
+format_result(Els, {Name, {list, {_, {tuple, [{name, string}, {value, _}]}} = Fmt}}) ->
+ {misc:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
+
format_result(Els, {Name, {list, Def}}) ->
{misc:atom_to_binary(Name), [element(2, format_result(El, Def)) || El <- Els]};
@@ -479,6 +487,11 @@ format_result(Tuple, {_Name, {tuple, [{_, atom}, ValFmt]}}) ->
{_, Val2} = format_result(Val, ValFmt),
{misc:atom_to_binary(Name2), Val2};
+format_result(Tuple, {_Name, {tuple, [{name, string}, {value, _} = ValFmt]}}) ->
+ {Name2, Val} = Tuple,
+ {_, Val2} = format_result(Val, ValFmt),
+ {iolist_to_binary(Name2), Val2};
+
format_result(Tuple, {Name, {tuple, Def}}) ->
Els = lists:zip(tuple_to_list(Tuple), Def),
{misc:atom_to_binary(Name), {[format_result(El, ElDef) || {El, ElDef} <- Els]}};
diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl
index c114b1dce..f2cc682d6 100644
--- a/src/mod_offline_sql.erl
+++ b/src/mod_offline_sql.erl
@@ -90,8 +90,17 @@ remove_expired_messages(_LServer) ->
remove_old_messages(Days, LServer) ->
case ejabberd_sql:sql_query(
LServer,
- ?SQL("DELETE FROM spool"
- " WHERE created_at < NOW() - INTERVAL %(Days)d DAY")) of
+ fun(pgsql, _) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("DELETE FROM spool"
+ " WHERE created_at <"
+ " NOW() - INTERVAL '%(Days)d DAY'"));
+ (_, _) ->
+ ejabberd_sql:sql_query_t(
+ ?SQL("DELETE FROM spool"
+ " WHERE created_at < NOW() - INTERVAL %(Days)d DAY"))
+ end)
+ of
{updated, N} ->
?INFO_MSG("~p message(s) deleted from offline spool", [N]);
_Error ->
diff --git a/src/mod_private.erl b/src/mod_private.erl
index db82e1295..b32fff98e 100644
--- a/src/mod_private.erl
+++ b/src/mod_private.erl
@@ -37,9 +37,12 @@
import/5, import_start/2, mod_opt_type/1, set_data/2,
mod_options/1, depends/2, get_sm_features/5, pubsub_publish_item/6]).
+-export([get_commands_spec/0, bookmarks_to_pep/2]).
+
-include("logger.hrl").
-include("xmpp.hrl").
-include("mod_private.hrl").
+-include("ejabberd_commands.hrl").
-define(PRIVATE_CACHE, private_cache).
@@ -61,13 +64,20 @@ start(Host, Opts) ->
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:add(pubsub_publish_item, Host, ?MODULE, pubsub_publish_item, 50),
- gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE, ?MODULE, process_sm_iq).
+ gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE, ?MODULE, process_sm_iq),
+ ejabberd_commands:register_commands(get_commands_spec()).
stop(Host) ->
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(pubsub_publish_item, Host, ?MODULE, pubsub_publish_item, 50),
- gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE).
+ gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE),
+ case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
+ false ->
+ ejabberd_commands:unregister_commands(get_commands_spec());
+ true ->
+ ok
+ end.
reload(Host, NewOpts, OldOpts) ->
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
@@ -265,6 +275,50 @@ pubsub_publish_item(_, _, _, _, _, _) ->
ok.
%%%===================================================================
+%%% Commands
+%%%===================================================================
+-spec get_commands_spec() -> [ejabberd_commands()].
+get_commands_spec() ->
+ [#ejabberd_commands{name = bookmarks_to_pep, tags = [private],
+ desc = "Export private XML storage bookmarks to PEP",
+ module = ?MODULE, function = bookmarks_to_pep,
+ args = [{user, binary}, {server, binary}],
+ args_desc = ["Username", "Server"],
+ args_example = [<<"bob">>, <<"example.com">>],
+ result = {res, restuple},
+ result_desc = "Result tuple",
+ result_example = {ok, <<"Bookmarks exported">>}}].
+
+-spec bookmarks_to_pep(binary(), binary())
+ -> {ok, binary()} | {error, binary()}.
+bookmarks_to_pep(User, Server) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Res = case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:lookup(
+ ?PRIVATE_CACHE, {LUser, LServer, ?NS_STORAGE_BOOKMARKS},
+ fun() ->
+ Mod:get_data(LUser, LServer, ?NS_STORAGE_BOOKMARKS)
+ end);
+ false ->
+ Mod:get_data(LUser, LServer, ?NS_STORAGE_BOOKMARKS)
+ end,
+ case Res of
+ {ok, El} ->
+ Data = [{?NS_STORAGE_BOOKMARKS, El}],
+ case publish_data(jid:make(User, Server), Data) of
+ ok ->
+ {ok, <<"Bookmarks exported to PEP node">>};
+ {error, Err} ->
+ {error, xmpp:format_stanza_error(Err)}
+ end;
+ _ ->
+ {error, <<"Cannot retrieve bookmarks from private XML storage">>}
+ end.
+
+%%%===================================================================
%%% Cache
%%%===================================================================
-spec delete_cache(module(), binary(), binary(), [{binary(), xmlel()}]) -> ok.
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 3367c192e..72edea9ce 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -2990,6 +2990,7 @@ send_last_pep(From, To) ->
Host = host(ServerHost),
Publisher = jid:tolower(From),
Owner = jid:remove_resource(Publisher),
+ RecipientIsOwner = jid:remove_resource(jid:tolower(To)) == Owner,
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
@@ -2998,8 +2999,11 @@ send_last_pep(From, To) ->
Subscribed = case get_option(Options, access_model) of
open -> true;
presence -> true;
- whitelist -> false; % subscribers are added manually
- authorize -> false; % likewise
+ %% TODO: Fix the 'whitelist'/'authorize'
+ %% cases. Currently, only node owners
+ %% receive last PEP notifications.
+ whitelist -> RecipientIsOwner;
+ authorize -> RecipientIsOwner;
roster ->
Grps = get_option(Options, roster_groups_allowed, []),
{OU, OS, _} = Owner,
diff --git a/src/proxy_protocol.erl b/src/proxy_protocol.erl
new file mode 100644
index 000000000..2103a4004
--- /dev/null
+++ b/src/proxy_protocol.erl
@@ -0,0 +1,184 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_http.erl
+%%% Author : Paweł Chmielowski <pawel@process-one.net>
+%%% Purpose :
+%%% Created : 27 Nov 2018 by Paweł Chmielowski <pawel@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2018 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(proxy_protocol).
+-author("pawel@process-one.net").
+
+%% API
+-export([decode/3]).
+
+decode(SockMod, Socket, Timeout) ->
+ V = SockMod:recv(Socket, 6, Timeout),
+ case V of
+ {ok, <<"PROXY ">>} ->
+ decode_v1(SockMod, Socket, Timeout);
+ {ok, <<16#0d, 16#0a, 16#0d, 16#0a, 16#00, 16#0d>>} ->
+ decode_v2(SockMod, Socket, Timeout);
+ _ ->
+ {error, eproto}
+ end.
+
+decode_v1(SockMod, Socket, Timeout) ->
+ case read_until_rn(SockMod, Socket, <<>>, false, Timeout) of
+ {error, _} = Err ->
+ Err;
+ Val ->
+ case binary:split(Val, <<" ">>, [global]) of
+ [<<"TCP4">>, SAddr, DAddr, SPort, DPort] ->
+ try {inet_parse:ipv4strict_address(binary_to_list(SAddr)),
+ inet_parse:ipv4strict_address(binary_to_list(DAddr)),
+ binary_to_integer(SPort),
+ binary_to_integer(DPort)}
+ of
+ {{ok, DA}, {ok, SA}, DP, SP} ->
+ {{SA, SP}, {DA, DP}};
+ _ ->
+ {error, eproto}
+ catch
+ error:badarg ->
+ {error, eproto}
+ end;
+ [<<"TCP6">>, SAddr, DAddr, SPort, DPort] ->
+ try {inet_parse:ipv6strict_address(binary_to_list(SAddr)),
+ inet_parse:ipv6strict_address(binary_to_list(DAddr)),
+ binary_to_integer(SPort),
+ binary_to_integer(DPort)}
+ of
+ {{ok, DA}, {ok, SA}, DP, SP} ->
+ {{SA, SP}, {DA, DP}};
+ _ ->
+ {error, eproto}
+ catch
+ error:badarg ->
+ {error, eproto}
+ end;
+ [<<"UNKNOWN">> | _] ->
+ {undefined, undefined}
+ end
+ end.
+
+decode_v2(SockMod, Socket, Timeout) ->
+ case SockMod:recv(Socket, 10, Timeout) of
+ {error, _} = Err ->
+ Err;
+ {ok, <<16#0a, 16#51, 16#55, 16#49, 16#54, 16#0a,
+ 2:4, Command:4, Transport:8, AddrLen:16/big-unsigned-integer>>} ->
+ case SockMod:recv(Socket, AddrLen, Timeout) of
+ {error, _} = Err ->
+ Err;
+ {ok, Data} ->
+ case Command of
+ 0 ->
+ case {inet:sockname(Socket), inet:peername(Socket)} of
+ {{ok, SA}, {ok, DA}} ->
+ {SA, DA};
+ {{error, _} = E, _} ->
+ E;
+ {_, {error, _} = E} ->
+ E
+ end;
+ 1 ->
+ case Transport of
+ % UNSPEC or UNIX
+ V when V == 0; V == 16#31; V == 16#32 ->
+ {{unknown, unknown}, {unknown, unknown}};
+ % IPV4 over TCP or UDP
+ V when V == 16#11; V == 16#12 ->
+ case Data of
+ <<D1:8, D2:8, D3:8, D4:8,
+ S1:8, S2:8, S3:8, S4:8,
+ DP:16/big-unsigned-integer,
+ SP:16/big-unsigned-integer,
+ _/binary>> ->
+ {{{S1, S2, S3, S4}, SP},
+ {{D1, D2, D3, D4}, DP}};
+ _ ->
+ {error, eproto}
+ end;
+ % IPV6 over TCP or UDP
+ V when V == 16#21; V == 16#22 ->
+ case Data of
+ <<D1:16/big-unsigned-integer,
+ D2:16/big-unsigned-integer,
+ D3:16/big-unsigned-integer,
+ D4:16/big-unsigned-integer,
+ D5:16/big-unsigned-integer,
+ D6:16/big-unsigned-integer,
+ D7:16/big-unsigned-integer,
+ D8:16/big-unsigned-integer,
+ S1:16/big-unsigned-integer,
+ S2:16/big-unsigned-integer,
+ S3:16/big-unsigned-integer,
+ S4:16/big-unsigned-integer,
+ S5:16/big-unsigned-integer,
+ S6:16/big-unsigned-integer,
+ S7:16/big-unsigned-integer,
+ S8:16/big-unsigned-integer,
+ DP:16/big-unsigned-integer,
+ SP:16/big-unsigned-integer,
+ _/binary>> ->
+ {{{S1, S2, S3, S4, S5, S6, S7, S8}, SP},
+ {{D1, D2, D3, D4, D5, D6, D7, D8}, DP}};
+ _ ->
+ {error, eproto}
+ end
+ end;
+ _ ->
+ {error, eproto}
+ end
+ end;
+ <<16#0a, 16#51, 16#55, 16#49, 16#54, 16#0a, _/binary>> ->
+ {error, eproto};
+ _ ->
+ {error, eproto}
+ end.
+
+read_until_rn(_SockMod, _Socket, Data, _, _) when size(Data) > 107 ->
+ {error, eproto};
+read_until_rn(SockMod, Socket, Data, true, Timeout) ->
+ case SockMod:recv(Socket, 1, Timeout) of
+ {ok, <<"\n">>} ->
+ Data;
+ {ok, <<"\r">>} ->
+ read_until_rn(SockMod, Socket, <<Data/binary, "\r">>,
+ true, Timeout);
+ {ok, Other} ->
+ read_until_rn(SockMod, Socket, <<Data/binary, "\r", Other/binary>>,
+ false, Timeout);
+ {error, _} = Err ->
+ Err
+ end;
+read_until_rn(SockMod, Socket, Data, false, Timeout) ->
+ case SockMod:recv(Socket, 2, Timeout) of
+ {ok, <<"\r\n">>} ->
+ Data;
+ {ok, <<Byte:8, "\r">>} ->
+ read_until_rn(SockMod, Socket, <<Data/binary, Byte:8>>,
+ true, Timeout);
+ {ok, Other} ->
+ read_until_rn(SockMod, Socket, <<Data/binary, Other/binary>>,
+ false, Timeout);
+ {error, _} = Err ->
+ Err
+ end.