diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ejabberd_app.erl | 2 | ||||
-rw-r--r-- | src/ejabberd_auth.erl | 44 | ||||
-rw-r--r-- | src/ejabberd_auth_sql.erl | 28 | ||||
-rw-r--r-- | src/ejabberd_c2s.erl | 14 | ||||
-rw-r--r-- | src/ejabberd_config.erl | 8 | ||||
-rw-r--r-- | src/ejabberd_http.erl | 24 | ||||
-rw-r--r-- | src/ejabberd_listener.erl | 58 | ||||
-rw-r--r-- | src/ejabberd_sm.erl | 58 | ||||
-rw-r--r-- | src/ejabberd_sql.erl | 17 | ||||
-rw-r--r-- | src/ejabberd_sql_pt.erl | 27 | ||||
-rw-r--r-- | src/mod_carboncopy.erl | 33 | ||||
-rw-r--r-- | src/mod_http_api.erl | 21 | ||||
-rw-r--r-- | src/mod_offline_sql.erl | 13 | ||||
-rw-r--r-- | src/mod_private.erl | 58 | ||||
-rw-r--r-- | src/mod_pubsub.erl | 8 | ||||
-rw-r--r-- | src/proxy_protocol.erl | 184 |
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. |