1 |
|
%% @doc SASL layer between C2S and MIM internal mongoose_acc. |
2 |
|
%% |
3 |
|
%% Implements RFC6120 Section 6 – but stanza agnostic, uses mongoose_acc instead |
4 |
|
%% |
5 |
|
%% This module is intended to have no side-effects, that is, it proccesses between XMPP contents |
6 |
|
%% (c2s data, xml stanzas), and MongooseIM internal mongoose_acc, |
7 |
|
%% which enables tracing by refs and timestamps. |
8 |
|
%% It does not send anything on the socket nor changes the state of the client process. |
9 |
|
%% This way, we can decouple SASL from the client process core code. |
10 |
|
%% @end |
11 |
|
-module(mongoose_c2s_sasl). |
12 |
|
|
13 |
|
-include_lib("kernel/include/logger.hrl"). |
14 |
|
|
15 |
|
-export([new/1, start/4, continue/3]). |
16 |
|
|
17 |
|
-type mechanism() :: binary(). |
18 |
|
-type maybe_username() :: undefined | jid:luser(). |
19 |
|
-type success() :: #{server_out := undefined | binary(), |
20 |
|
jid := jid:jid(), |
21 |
|
auth_module := cyrsasl:sasl_module()}. |
22 |
|
-type continue() :: #{server_out := binary()}. |
23 |
|
-type failure() :: #{server_out := binary() | {binary(), undefined | iodata()}, |
24 |
|
maybe_username := maybe_username()}. |
25 |
|
-type error() :: #{type := atom(), text := binary()}. |
26 |
|
-type result() :: {success, mongoose_acc:t(), success()} |
27 |
|
| {continue, mongoose_acc:t(), continue()} |
28 |
|
| {failure, mongoose_acc:t(), failure()} |
29 |
|
| {error, mongoose_acc:t(), error()}. |
30 |
|
-export_type([result/0, success/0, continue/0, failure/0, error/0, mechanism/0]). |
31 |
|
|
32 |
|
-spec new(mongoose_c2s:data()) -> mongoose_acc:t(). |
33 |
|
new(C2SData) -> |
34 |
7070 |
HostType = mongoose_c2s:get_host_type(C2SData), |
35 |
7070 |
LServer = mongoose_c2s:get_lserver(C2SData), |
36 |
7070 |
LOpts = mongoose_c2s:get_listener_opts(C2SData), |
37 |
7070 |
CredOpts = mongoose_credentials:make_opts(LOpts), |
38 |
7070 |
Creds = mongoose_credentials:new(LServer, HostType, CredOpts), |
39 |
7070 |
CyrSaslState = cyrsasl:server_new(<<"jabber">>, LServer, HostType, <<>>, [], Creds), |
40 |
7070 |
sasl_acc(HostType, LServer, CyrSaslState, Creds). |
41 |
|
|
42 |
|
-spec start(mongoose_c2s:data(), mongoose_acc:t(), mechanism(), binary()) -> result(). |
43 |
|
start(C2SData, SaslAcc, Mech, ClientIn) -> |
44 |
6689 |
Socket = mongoose_c2s:get_socket(C2SData), |
45 |
6689 |
LOpts = mongoose_c2s:get_listener_opts(C2SData), |
46 |
6689 |
case {mongoose_c2s_socket:is_ssl(Socket), LOpts} of |
47 |
|
{false, #{tls := #{mode := starttls_required}}} -> |
48 |
4 |
{error, SaslAcc, #{type => policy_violation, text => <<"Use of STARTTLS required">>}}; |
49 |
|
_ -> |
50 |
6685 |
AuthMech = mongoose_c2s:get_auth_mechs(C2SData), |
51 |
6685 |
SocketData = #{socket => Socket, auth_mech => AuthMech, listener_opts => LOpts}, |
52 |
6685 |
CyrSaslState = get_cyrsasl_state_from_acc(SaslAcc), |
53 |
6685 |
CyrSaslResult = cyrsasl:server_start(CyrSaslState, Mech, ClientIn, SocketData), |
54 |
6685 |
handle_sasl_step(C2SData, CyrSaslResult, SaslAcc) |
55 |
|
end. |
56 |
|
|
57 |
|
-spec continue(mongoose_c2s:data(), mongoose_acc:t(), binary()) -> result(). |
58 |
|
continue(C2SData, SaslAcc, ClientIn) -> |
59 |
48 |
CyrSaslState = get_cyrsasl_state_from_acc(SaslAcc), |
60 |
48 |
CyrSaslResult = cyrsasl:server_step(CyrSaslState, ClientIn), |
61 |
48 |
handle_sasl_step(C2SData, CyrSaslResult, SaslAcc). |
62 |
|
|
63 |
|
-spec handle_sasl_step(mongoose_c2s:data(), cyrsasl:sasl_result(), mongoose_acc:t()) -> result(). |
64 |
|
handle_sasl_step(C2SData, {ok, Creds}, SaslAcc) -> |
65 |
6612 |
handle_sasl_success(C2SData, Creds, SaslAcc); |
66 |
|
handle_sasl_step(C2SData, {continue, ServerOut, NewCyrSaslState}, SaslAcc) -> |
67 |
51 |
handle_sasl_continue(C2SData, ServerOut, NewCyrSaslState, SaslAcc); |
68 |
|
handle_sasl_step(C2SData, {error, Error, Username}, SaslAcc) -> |
69 |
21 |
handle_sasl_failure(C2SData, Error, Username, undefined, SaslAcc); |
70 |
|
handle_sasl_step(C2SData, {error, Error}, SaslAcc) -> |
71 |
49 |
MaybeUsername = maybe_get_username_from_data(C2SData), |
72 |
49 |
handle_sasl_failure(C2SData, Error, undefined, MaybeUsername, SaslAcc). |
73 |
|
|
74 |
|
-spec handle_sasl_success(mongoose_c2s:data(), mongoose_credentials:t(), mongoose_acc:t()) -> result(). |
75 |
|
handle_sasl_success(C2SData, Creds, SaslAcc) -> |
76 |
6612 |
ServerOut = mongoose_credentials:get(Creds, sasl_success_response, undefined), |
77 |
6612 |
AuthModule = mongoose_credentials:get(Creds, auth_module), |
78 |
6612 |
User = mongoose_credentials:get(Creds, username), |
79 |
6612 |
LServer = mongoose_c2s:get_lserver(C2SData), |
80 |
6612 |
Jid = jid:make_bare(User, LServer), |
81 |
6612 |
Ret = #{server_out => ServerOut, jid => Jid, auth_module => AuthModule}, |
82 |
6612 |
{success, SaslAcc, Ret}. |
83 |
|
|
84 |
|
-spec handle_sasl_continue( |
85 |
|
mongoose_c2s:data(), binary(), cyrsasl:sasl_state(), mongoose_acc:t()) -> result(). |
86 |
|
handle_sasl_continue(_C2SData, ServerOut, NewCyrSaslState, SaslAcc) -> |
87 |
51 |
SaslAcc1 = set_cyrsasl_state_in_acc(SaslAcc, NewCyrSaslState), |
88 |
51 |
{continue, SaslAcc1, #{server_out => ServerOut}}. |
89 |
|
|
90 |
|
-spec handle_sasl_failure( |
91 |
|
mongoose_c2s:data(), term(), maybe_username(), maybe_username(), mongoose_acc:t()) -> result(). |
92 |
|
handle_sasl_failure(_C2SData, Error, undefined, undefined, SaslAcc) -> |
93 |
49 |
{failure, SaslAcc, #{server_out => Error, maybe_username => undefined}}; |
94 |
|
handle_sasl_failure(_C2SData, Error, Username, _, SaslAcc) -> |
95 |
21 |
{failure, SaslAcc, #{server_out => Error, maybe_username => Username}}. |
96 |
|
|
97 |
|
sasl_acc(HostType, LServer, CyrSaslState, Creds) -> |
98 |
7070 |
Acc = mongoose_acc:new(#{host_type => HostType, lserver => LServer, location => ?LOCATION}), |
99 |
7070 |
Fields = [{creds, Creds}, {cyrsasl, CyrSaslState}], |
100 |
7070 |
mongoose_acc:set(?MODULE, Fields, Acc). |
101 |
|
|
102 |
|
-spec set_cyrsasl_state_in_acc(mongoose_acc:t(), cyrsasl:sasl_state()) -> mongoose_acc:t(). |
103 |
|
set_cyrsasl_state_in_acc(SaslAcc, NewCyrSaslState) -> |
104 |
51 |
mongoose_acc:set(?MODULE, cyrsasl, NewCyrSaslState, SaslAcc). |
105 |
|
|
106 |
|
-spec get_cyrsasl_state_from_acc(mongoose_acc:t()) -> cyrsasl:sasl_state(). |
107 |
|
get_cyrsasl_state_from_acc(SaslAcc) -> |
108 |
6733 |
mongoose_acc:get(?MODULE, cyrsasl, SaslAcc). |
109 |
|
|
110 |
|
-spec maybe_get_username_from_data(mongoose_c2s:data()) -> maybe_username(). |
111 |
|
maybe_get_username_from_data(C2SData) -> |
112 |
49 |
case mongoose_c2s:get_jid(C2SData) of |
113 |
|
undefined -> |
114 |
49 |
undefined; |
115 |
|
Jid -> |
116 |
:-( |
{U, _S} = jid:to_lus(Jid), |
117 |
:-( |
U |
118 |
|
end. |