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(list()) -> {ok, state()}. |
29 |
|
init(Options) -> |
30 |
:-( |
State = initial_state(Options), |
31 |
:-( |
self() ! connect, |
32 |
:-( |
{ok, State}. |
33 |
|
|
34 |
|
-spec handle_call(request(), {pid(), any()}, state()) -> {reply, any(), state()}. |
35 |
|
handle_call(Request, _From, State) -> |
36 |
:-( |
{Result, NewState} = call_eldap(Request, State), |
37 |
:-( |
{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 |
:-( |
{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) -> |
62 |
:-( |
Servers = eldap_utils:get_mod_opt(servers, Opts, |
63 |
|
fun(L) -> |
64 |
:-( |
lists:map(fun(H) when is_list(H) -> H end, L) |
65 |
|
end, ["localhost"]), |
66 |
:-( |
Encrypt = eldap_utils:get_mod_opt(encrypt, Opts, |
67 |
:-( |
fun(tls) -> tls; |
68 |
:-( |
(none) -> none |
69 |
|
end, none), |
70 |
:-( |
TLSOptions = eldap_utils:get_mod_opt(tls_options, Opts, |
71 |
:-( |
fun(L) when is_list(L) -> L end, []), |
72 |
:-( |
Port = eldap_utils:get_mod_opt(port, Opts, |
73 |
:-( |
fun(I) when is_integer(I), I>0 -> I end, |
74 |
|
case Encrypt of |
75 |
:-( |
tls -> ?LDAPS_PORT; |
76 |
:-( |
starttls -> ?LDAP_PORT; |
77 |
:-( |
_ -> ?LDAP_PORT |
78 |
|
end), |
79 |
:-( |
RootDN = eldap_utils:get_mod_opt(rootdn, Opts, |
80 |
|
fun iolist_to_binary/1, |
81 |
|
<<"">>), |
82 |
:-( |
Password = eldap_utils:get_mod_opt(password, Opts, |
83 |
|
fun iolist_to_binary/1, |
84 |
|
<<"">>), |
85 |
:-( |
ConnectInterval = eldap_utils:get_mod_opt(connect_interval, Opts, |
86 |
:-( |
fun(I) when is_integer(I), I>0 -> I end, |
87 |
|
default_connect_interval()), |
88 |
:-( |
#{handle => none, |
89 |
|
servers => Servers, |
90 |
|
encrypt => Encrypt, |
91 |
|
tls_options => TLSOptions, |
92 |
|
port => Port, |
93 |
|
root_dn => RootDN, |
94 |
|
password => Password, |
95 |
|
connect_interval => ConnectInterval}. |
96 |
|
|
97 |
|
call_eldap(Request, State) -> |
98 |
:-( |
case do_call_eldap(Request, State) of |
99 |
|
{error, Reason} when Reason =:= ldap_closed; |
100 |
|
Reason =:= {gen_tcp_error, closed} -> |
101 |
:-( |
?LOG_INFO(#{what => ldap_request_failed, reason => Reason, |
102 |
:-( |
text => <<"LDAP request failed: connection closed, reconnecting...">>}), |
103 |
:-( |
eldap:close(maps:get(handle, State)), |
104 |
:-( |
NewState = connect(State#{handle := none}), |
105 |
:-( |
retry_call_eldap(Request, NewState); |
106 |
|
Result -> |
107 |
:-( |
{Result, State} |
108 |
|
end. |
109 |
|
|
110 |
|
connect(State = #{handle := none, |
111 |
|
servers := Servers, |
112 |
|
encrypt := Encrypt, |
113 |
|
tls_options := TLSOptions, |
114 |
|
port := Port, |
115 |
|
root_dn := RootDN, |
116 |
|
password := Password, |
117 |
|
connect_interval := ConnectInterval}) -> |
118 |
:-( |
AnonAuth = RootDN =:= <<>> andalso Password =:= <<>>, |
119 |
:-( |
SSLConfig = case Encrypt of |
120 |
:-( |
tls -> [{ssl, true}, {sslopts, TLSOptions}]; |
121 |
:-( |
none -> [{ssl, false}] |
122 |
|
end, |
123 |
:-( |
case eldap:open(Servers, [{port, Port}, {anon_auth, AnonAuth}] ++ SSLConfig) of |
124 |
|
{ok, Handle} -> |
125 |
:-( |
case eldap:simple_bind(Handle, RootDN, Password) of |
126 |
|
ok -> |
127 |
:-( |
?LOG_INFO(#{what => ldap_connected, |
128 |
:-( |
text => <<"Connected to LDAP server">>}), |
129 |
:-( |
State#{handle := Handle}; |
130 |
|
Error -> |
131 |
:-( |
?LOG_ERROR(#{what => ldap_auth_failed, reason => Error, |
132 |
|
text => <<"LDAP bind returns an error">>, |
133 |
:-( |
ldap_servers => Servers, port => Port}), |
134 |
:-( |
eldap:close(Handle), |
135 |
:-( |
erlang:send_after(ConnectInterval, self(), connect), |
136 |
:-( |
State |
137 |
|
end; |
138 |
|
Error -> |
139 |
:-( |
?LOG_ERROR(#{what => ldap_connect_failed, |
140 |
|
text => <<"LDAP open returns an error">>, |
141 |
:-( |
ldap_servers => Servers, port => Port, reason => Error}), |
142 |
:-( |
erlang:send_after(ConnectInterval, self(), connect), |
143 |
:-( |
State |
144 |
|
end. |
145 |
|
|
146 |
|
retry_call_eldap(Request, State) -> |
147 |
:-( |
Result = do_call_eldap(Request, State), |
148 |
:-( |
{Result, State}. |
149 |
|
|
150 |
:-( |
do_call_eldap(_Request, #{handle := none}) -> {error, not_connected}; |
151 |
:-( |
do_call_eldap({F, Args}, #{handle := Handle}) -> apply(eldap, F, [Handle | Args]). |
152 |
|
|
153 |
|
default_connect_interval() -> |
154 |
:-( |
10000. |