1 |
|
-module(mongoose_ldap_worker). |
2 |
|
|
3 |
|
-behaviour(gen_server). |
4 |
|
|
5 |
|
%% gen_server callbacks |
6 |
|
-export([init/1, |
7 |
|
handle_call/3, |
8 |
|
handle_cast/2, |
9 |
|
handle_info/2, |
10 |
|
terminate/2, |
11 |
|
code_change/3]). |
12 |
|
|
13 |
|
-include("mongoose.hrl"). |
14 |
|
-include("eldap.hrl"). |
15 |
|
|
16 |
|
-type state() :: #{handle := none | eldap:handle(), |
17 |
|
servers := [string()], |
18 |
|
encrypt := none | tls, |
19 |
|
tls_options := list(), |
20 |
|
port := pos_integer(), |
21 |
|
root_dn := binary(), |
22 |
|
password := binary(), |
23 |
|
connect_interval := pos_integer()}. |
24 |
|
-type request() :: {function(), Args :: [any()]}. |
25 |
|
|
26 |
|
%% gen_server callbacks |
27 |
|
|
28 |
|
-spec init(gen_mod:module_opts()) -> {ok, state()}. |
29 |
|
init(Options) -> |
30 |
820 |
State = initial_state(Options), |
31 |
820 |
self() ! connect, |
32 |
820 |
{ok, State}. |
33 |
|
|
34 |
|
-spec handle_call(request(), {pid(), any()}, state()) -> {reply, any(), state()}. |
35 |
|
handle_call(Request, _From, State) -> |
36 |
16585 |
{Result, NewState} = call_eldap(Request, State), |
37 |
16585 |
{reply, Result, NewState}. |
38 |
|
|
39 |
|
-spec handle_cast(any(), state()) -> {noreply, state()}. |
40 |
|
handle_cast(Cast, State) -> |
41 |
:-( |
?UNEXPECTED_CAST(Cast), |
42 |
:-( |
{noreply, State}. |
43 |
|
|
44 |
|
-spec handle_info(any(), state()) -> {noreply, state()}. |
45 |
|
handle_info(connect, State) -> |
46 |
820 |
{noreply, connect(State)}; |
47 |
|
handle_info(Info, State) -> |
48 |
:-( |
?UNEXPECTED_INFO(Info), |
49 |
:-( |
{noreply, State}. |
50 |
|
|
51 |
|
-spec terminate(any(), state()) -> ok. |
52 |
:-( |
terminate(_Reason, #{handle := none}) -> ok; |
53 |
:-( |
terminate(_Reason, #{handle := Handle}) -> eldap:close(Handle). |
54 |
|
|
55 |
|
-spec code_change(any(), state(), any()) -> {ok, state()}. |
56 |
|
code_change(_OldVsn, State, _Extra) -> |
57 |
:-( |
{ok, State}. |
58 |
|
|
59 |
|
%% internal functions |
60 |
|
|
61 |
|
initial_state(Opts = #{servers := Servers, encrypt := Encrypt, rootdn := RootDN, password := Password, |
62 |
|
connect_interval := ConnectInterval}) -> |
63 |
820 |
TLSOptions = maps:get(tls_options, Opts, []), |
64 |
820 |
DefaultPort = case Encrypt of |
65 |
820 |
tls -> ?LDAPS_PORT; |
66 |
:-( |
starttls -> ?LDAP_PORT; |
67 |
:-( |
_ -> ?LDAP_PORT |
68 |
|
end, |
69 |
820 |
Port = maps:get(port, Opts, DefaultPort), |
70 |
820 |
#{handle => none, |
71 |
|
servers => Servers, |
72 |
|
encrypt => Encrypt, |
73 |
|
tls_options => TLSOptions, |
74 |
|
port => Port, |
75 |
|
root_dn => RootDN, |
76 |
|
password => Password, |
77 |
|
connect_interval => ConnectInterval}. |
78 |
|
|
79 |
|
call_eldap(Request, State) -> |
80 |
16585 |
case do_call_eldap(Request, State) of |
81 |
|
{error, Reason} when Reason =:= ldap_closed; |
82 |
|
Reason =:= {gen_tcp_error, closed} -> |
83 |
:-( |
?LOG_INFO(#{what => ldap_request_failed, reason => Reason, |
84 |
:-( |
text => <<"LDAP request failed: connection closed, reconnecting...">>}), |
85 |
:-( |
eldap:close(maps:get(handle, State)), |
86 |
:-( |
NewState = connect(State#{handle := none}), |
87 |
:-( |
retry_call_eldap(Request, NewState); |
88 |
|
Result -> |
89 |
16585 |
{Result, State} |
90 |
|
end. |
91 |
|
|
92 |
|
connect(State = #{handle := none, |
93 |
|
servers := Servers, |
94 |
|
encrypt := Encrypt, |
95 |
|
tls_options := TLSOptions, |
96 |
|
port := Port, |
97 |
|
root_dn := RootDN, |
98 |
|
password := Password, |
99 |
|
connect_interval := ConnectInterval}) -> |
100 |
820 |
AnonAuth = RootDN =:= <<>> andalso Password =:= <<>>, |
101 |
820 |
SSLConfig = case Encrypt of |
102 |
820 |
tls -> [{ssl, true}, {sslopts, TLSOptions}]; |
103 |
:-( |
none -> [{ssl, false}] |
104 |
|
end, |
105 |
820 |
case eldap:open(Servers, [{port, Port}, {anon_auth, AnonAuth}] ++ SSLConfig) of |
106 |
|
{ok, Handle} -> |
107 |
820 |
case eldap:simple_bind(Handle, RootDN, Password) of |
108 |
|
ok -> |
109 |
820 |
?LOG_INFO(#{what => ldap_connected, |
110 |
820 |
text => <<"Connected to LDAP server">>}), |
111 |
820 |
State#{handle := Handle}; |
112 |
|
Error -> |
113 |
:-( |
?LOG_ERROR(#{what => ldap_auth_failed, reason => Error, |
114 |
|
text => <<"LDAP bind returns an error">>, |
115 |
:-( |
ldap_servers => Servers, port => Port}), |
116 |
:-( |
eldap:close(Handle), |
117 |
:-( |
erlang:send_after(ConnectInterval, self(), connect), |
118 |
:-( |
State |
119 |
|
end; |
120 |
|
Error -> |
121 |
:-( |
?LOG_ERROR(#{what => ldap_connect_failed, |
122 |
|
text => <<"LDAP open returns an error">>, |
123 |
:-( |
ldap_servers => Servers, port => Port, reason => Error}), |
124 |
:-( |
erlang:send_after(ConnectInterval, self(), connect), |
125 |
:-( |
State |
126 |
|
end. |
127 |
|
|
128 |
|
retry_call_eldap(Request, State) -> |
129 |
:-( |
Result = do_call_eldap(Request, State), |
130 |
:-( |
{Result, State}. |
131 |
|
|
132 |
:-( |
do_call_eldap(_Request, #{handle := none}) -> {error, not_connected}; |
133 |
16585 |
do_call_eldap({F, Args}, #{handle := Handle}) -> apply(eldap, F, [Handle | Args]). |