diff options
Diffstat (limited to 'src/mod_legacy_auth.erl')
-rw-r--r-- | src/mod_legacy_auth.erl | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/mod_legacy_auth.erl b/src/mod_legacy_auth.erl new file mode 100644 index 000000000..a48ef8de5 --- /dev/null +++ b/src/mod_legacy_auth.erl @@ -0,0 +1,171 @@ +%%%------------------------------------------------------------------- +%%% Created : 11 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net> +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2019 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_legacy_auth). +-behaviour(gen_mod). + +-protocol({xep, 78, '2.5'}). + +%% gen_mod API +-export([start/2, stop/1, reload/3, depends/2, mod_options/1]). +%% hooks +-export([c2s_unauthenticated_packet/2, c2s_stream_features/2]). + +-include("xmpp.hrl"). +-include("translate.hrl"). + +-type c2s_state() :: ejabberd_c2s:state(). + +%%%=================================================================== +%%% API +%%%=================================================================== +start(Host, _Opts) -> + ejabberd_hooks:add(c2s_unauthenticated_packet, Host, ?MODULE, + c2s_unauthenticated_packet, 50), + ejabberd_hooks:add(c2s_pre_auth_features, Host, ?MODULE, + c2s_stream_features, 50). + +stop(Host) -> + ejabberd_hooks:delete(c2s_unauthenticated_packet, Host, ?MODULE, + c2s_unauthenticated_packet, 50), + ejabberd_hooks:delete(c2s_pre_auth_features, Host, ?MODULE, + c2s_stream_features, 50). + +reload(_Host, _NewOpts, _OldOpts) -> + ok. + +depends(_Host, _Opts) -> + []. + +mod_options(_) -> + []. + +-spec c2s_unauthenticated_packet(c2s_state(), iq()) -> + c2s_state() | {stop, c2s_state()}. +c2s_unauthenticated_packet(State, #iq{type = T, sub_els = [_]} = IQ) + when T == get; T == set -> + try xmpp:try_subtag(IQ, #legacy_auth{}) of + #legacy_auth{} = Auth -> + {stop, authenticate(State, xmpp:set_els(IQ, [Auth]))}; + false -> + State + catch _:{xmpp_codec, Why} -> + Txt = xmpp:io_format_error(Why), + Lang = maps:get(lang, State), + Err = xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)), + {stop, ejabberd_c2s:send(State, Err)} + end; +c2s_unauthenticated_packet(State, _) -> + State. + +-spec c2s_stream_features([xmpp_element()], binary()) -> [xmpp_element()]. +c2s_stream_features(Acc, LServer) -> + case gen_mod:is_loaded(LServer, ?MODULE) of + true -> + [#legacy_auth_feature{}|Acc]; + false -> + Acc + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +-spec authenticate(c2s_state(), iq()) -> c2s_state(). +authenticate(#{server := Server} = State, + #iq{type = get, sub_els = [#legacy_auth{}]} = IQ) -> + LServer = jid:nameprep(Server), + Auth = #legacy_auth{username = <<>>, password = <<>>, resource = <<>>}, + Res = case ejabberd_auth:plain_password_required(LServer) of + false -> + xmpp:make_iq_result(IQ, Auth#legacy_auth{digest = <<>>}); + true -> + xmpp:make_iq_result(IQ, Auth) + end, + ejabberd_c2s:send(State, Res); +authenticate(State, + #iq{type = set, lang = Lang, + sub_els = [#legacy_auth{username = U, + resource = R}]} = IQ) + when U == undefined; R == undefined; U == <<"">>; R == <<"">> -> + Txt = ?T("Both the username and the resource are required"), + Err = xmpp:make_error(IQ, xmpp:err_not_acceptable(Txt, Lang)), + ejabberd_c2s:send(State, Err); +authenticate(#{stream_id := StreamID, server := Server, + access := Access, ip := IP} = State, + #iq{type = set, lang = Lang, + sub_els = [#legacy_auth{username = U, + password = P0, + digest = D0, + resource = R}]} = IQ) -> + P = if is_binary(P0) -> P0; true -> <<>> end, + D = if is_binary(D0) -> D0; true -> <<>> end, + DGen = fun (PW) -> str:sha(<<StreamID/binary, PW/binary>>) end, + JID = jid:make(U, Server, R), + case JID /= error andalso + acl:match_rule(JID#jid.lserver, Access, + #{usr => jid:split(JID), ip => IP}) == allow of + true -> + case ejabberd_auth:check_password_with_authmodule( + U, U, JID#jid.lserver, P, D, DGen) of + {true, AuthModule} -> + State1 = State#{sasl_mech => <<"legacy">>}, + State2 = ejabberd_c2s:handle_auth_success( + U, <<"legacy">>, AuthModule, State1), + State3 = State2#{user := U}, + open_session(State3, IQ, R); + _ -> + Err = xmpp:make_error(IQ, xmpp:err_not_authorized()), + process_auth_failure(State, U, Err, 'not-authorized') + end; + false when JID == error -> + Err = xmpp:make_error(IQ, xmpp:err_jid_malformed()), + process_auth_failure(State, U, Err, 'jid-malformed'); + false -> + Txt = ?T("Access denied by service policy"), + Err = xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)), + process_auth_failure(State, U, Err, 'forbidden') + end. + +-spec open_session(c2s_state(), iq(), binary()) -> c2s_state(). +open_session(State, IQ, R) -> + case ejabberd_c2s:bind(R, State) of + {ok, State1} -> + Res = xmpp:make_iq_result(IQ), + ejabberd_c2s:send(State1, Res); + {error, Err, State1} -> + Res = xmpp:make_error(IQ, Err), + ejabberd_c2s:send(State1, Res) + end. + +-spec process_auth_failure(c2s_state(), binary(), iq(), atom()) -> c2s_state(). +process_auth_failure(State, User, StanzaErr, Reason) -> + State1 = ejabberd_c2s:send(State, StanzaErr), + State2 = State1#{sasl_mech => <<"legacy">>}, + Text = format_reason(Reason), + ejabberd_c2s:handle_auth_failure(User, <<"legacy">>, Text, State2). + +-spec format_reason(atom()) -> binary(). +format_reason('not-authorized') -> + <<"Invalid username or password">>; +format_reason('forbidden') -> + <<"Access denied by service policy">>; +format_reason('jid-malformed') -> + <<"Malformed XMPP address">>. |