2016-12-11 13:03:37 +01:00
|
|
|
%%%-------------------------------------------------------------------
|
|
|
|
%%% Created : 11 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
|
|
|
%%%
|
|
|
|
%%%
|
2019-01-08 22:53:27 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
|
2016-12-11 13:03:37 +01:00
|
|
|
%%%
|
|
|
|
%%% 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
|
2018-01-23 08:54:52 +01:00
|
|
|
-export([start/2, stop/1, reload/3, depends/2, mod_options/1]).
|
2016-12-11 13:03:37 +01:00
|
|
|
%% hooks
|
|
|
|
-export([c2s_unauthenticated_packet/2, c2s_stream_features/2]).
|
|
|
|
|
|
|
|
-include("xmpp.hrl").
|
2019-06-22 16:08:45 +02:00
|
|
|
-include("translate.hrl").
|
2016-12-11 13:03:37 +01:00
|
|
|
|
2017-02-16 12:18:36 +01:00
|
|
|
-type c2s_state() :: ejabberd_c2s:state().
|
|
|
|
|
2016-12-11 13:03:37 +01:00
|
|
|
%%%===================================================================
|
|
|
|
%%% 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).
|
|
|
|
|
2017-02-22 17:46:47 +01:00
|
|
|
reload(_Host, _NewOpts, _OldOpts) ->
|
|
|
|
ok.
|
|
|
|
|
2016-12-11 13:03:37 +01:00
|
|
|
depends(_Host, _Opts) ->
|
|
|
|
[].
|
|
|
|
|
2018-01-23 08:54:52 +01:00
|
|
|
mod_options(_) ->
|
2016-12-11 13:03:37 +01:00
|
|
|
[].
|
|
|
|
|
2017-02-16 12:18:36 +01:00
|
|
|
-spec c2s_unauthenticated_packet(c2s_state(), iq()) ->
|
|
|
|
c2s_state() | {stop, c2s_state()}.
|
2016-12-28 07:47:11 +01:00
|
|
|
c2s_unauthenticated_packet(State, #iq{type = T, sub_els = [_]} = IQ)
|
2016-12-11 13:03:37 +01:00
|
|
|
when T == get; T == set ->
|
2017-12-11 07:46:26 +01:00
|
|
|
try xmpp:try_subtag(IQ, #legacy_auth{}) of
|
2016-12-11 13:03:37 +01:00
|
|
|
#legacy_auth{} = Auth ->
|
|
|
|
{stop, authenticate(State, xmpp:set_els(IQ, [Auth]))};
|
|
|
|
false ->
|
2016-12-28 07:47:11 +01:00
|
|
|
State
|
2017-12-11 07:46:26 +01:00
|
|
|
catch _:{xmpp_codec, Why} ->
|
|
|
|
Txt = xmpp:io_format_error(Why),
|
|
|
|
Lang = maps:get(lang, State),
|
2017-12-11 08:00:16 +01:00
|
|
|
Err = xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)),
|
2017-12-11 07:46:26 +01:00
|
|
|
{stop, ejabberd_c2s:send(State, Err)}
|
2016-12-11 13:03:37 +01:00
|
|
|
end;
|
2016-12-28 07:47:11 +01:00
|
|
|
c2s_unauthenticated_packet(State, _) ->
|
|
|
|
State.
|
2016-12-11 13:03:37 +01:00
|
|
|
|
2017-02-16 12:18:36 +01:00
|
|
|
-spec c2s_stream_features([xmpp_element()], binary()) -> [xmpp_element()].
|
2016-12-11 13:03:37 +01:00
|
|
|
c2s_stream_features(Acc, LServer) ->
|
|
|
|
case gen_mod:is_loaded(LServer, ?MODULE) of
|
|
|
|
true ->
|
|
|
|
[#legacy_auth_feature{}|Acc];
|
|
|
|
false ->
|
|
|
|
Acc
|
|
|
|
end.
|
|
|
|
|
|
|
|
%%%===================================================================
|
|
|
|
%%% Internal functions
|
|
|
|
%%%===================================================================
|
2017-02-16 12:18:36 +01:00
|
|
|
-spec authenticate(c2s_state(), iq()) -> c2s_state().
|
2016-12-11 13:03:37 +01:00
|
|
|
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 == <<"">> ->
|
2019-06-22 16:08:45 +02:00
|
|
|
Txt = ?T("Both the username and the resource are required"),
|
2016-12-11 13:03:37 +01:00
|
|
|
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,
|
2017-03-14 00:31:51 +01:00
|
|
|
DGen = fun (PW) -> str:sha(<<StreamID/binary, PW/binary>>) end,
|
2016-12-11 13:03:37 +01:00
|
|
|
JID = jid:make(U, Server, R),
|
|
|
|
case JID /= error andalso
|
2019-06-14 11:33:26 +02:00
|
|
|
acl:match_rule(JID#jid.lserver, Access,
|
|
|
|
#{usr => jid:split(JID), ip => IP}) == allow of
|
2016-12-11 13:03:37 +01:00
|
|
|
true ->
|
|
|
|
case ejabberd_auth:check_password_with_authmodule(
|
|
|
|
U, U, JID#jid.lserver, P, D, DGen) of
|
|
|
|
{true, AuthModule} ->
|
2019-05-15 19:55:17 +02:00
|
|
|
State1 = State#{sasl_mech => <<"legacy">>},
|
|
|
|
State2 = ejabberd_c2s:handle_auth_success(
|
|
|
|
U, <<"legacy">>, AuthModule, State1),
|
|
|
|
State3 = State2#{user := U},
|
|
|
|
open_session(State3, IQ, R);
|
2016-12-11 13:03:37 +01:00
|
|
|
_ ->
|
|
|
|
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 ->
|
2019-06-22 16:08:45 +02:00
|
|
|
Txt = ?T("Access denied by service policy"),
|
2016-12-11 13:03:37 +01:00
|
|
|
Err = xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)),
|
|
|
|
process_auth_failure(State, U, Err, 'forbidden')
|
|
|
|
end.
|
|
|
|
|
2017-02-16 12:18:36 +01:00
|
|
|
-spec open_session(c2s_state(), iq(), binary()) -> c2s_state().
|
2016-12-11 13:03:37 +01:00
|
|
|
open_session(State, IQ, R) ->
|
|
|
|
case ejabberd_c2s:bind(R, State) of
|
|
|
|
{ok, State1} ->
|
|
|
|
Res = xmpp:make_iq_result(IQ),
|
2016-12-29 22:00:36 +01:00
|
|
|
ejabberd_c2s:send(State1, Res);
|
2016-12-11 13:03:37 +01:00
|
|
|
{error, Err, State1} ->
|
|
|
|
Res = xmpp:make_error(IQ, Err),
|
|
|
|
ejabberd_c2s:send(State1, Res)
|
|
|
|
end.
|
|
|
|
|
2017-02-18 07:36:27 +01:00
|
|
|
-spec process_auth_failure(c2s_state(), binary(), iq(), atom()) -> c2s_state().
|
2016-12-11 13:03:37 +01:00
|
|
|
process_auth_failure(State, User, StanzaErr, Reason) ->
|
2016-12-28 07:47:11 +01:00
|
|
|
State1 = ejabberd_c2s:send(State, StanzaErr),
|
2019-05-15 19:55:17 +02:00
|
|
|
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">>.
|