Source file ldap_funserver.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
open Lber
open Ldap_types
open Ldap_protocol
open Unix
open Printf
exception Server_error of string
exception Finished
type connection_id = int
type backendInfo = {
bi_op_bind : (connection_id -> ldap_message -> ldap_message) option;
bi_op_unbind : (connection_id -> ldap_message -> unit) option;
bi_op_search : (connection_id -> ldap_message -> (unit -> ldap_message)) option;
bi_op_compare : (connection_id -> ldap_message -> ldap_message) option;
bi_op_modify : (connection_id -> ldap_message -> ldap_message) option;
bi_op_modrdn : (connection_id -> ldap_message -> ldap_message) option;
bi_op_add : (connection_id -> ldap_message -> ldap_message) option;
bi_op_delete : (connection_id -> ldap_message -> ldap_message) option;
bi_op_abandon : (connection_id -> ldap_message -> unit) option;
bi_op_extended : (connection_id -> ldap_message -> ldap_message) option;
bi_init : (unit -> unit) option;
bi_close : (unit -> unit) option;
}
type log_level =
[ `GENERAL
| `CONNECTION
| `OPERATIONS
| `ERROR
| `TRACE ]
type msgid = int
type opcnt = int
type pending_operations = (unit -> unit) list
type server_info = {
si_listening_socket: file_descr;
si_client_sockets: (file_descr, connection_id * opcnt * pending_operations * readbyte) Hashtbl.t;
si_backend: backendInfo;
si_log: (log_level -> string -> unit);
mutable si_current_connection_id: int;
}
let allocate_connection_id si =
if si.si_current_connection_id < max_int then
(si.si_current_connection_id <- si.si_current_connection_id + 1;
si.si_current_connection_id)
else
(si.si_current_connection_id <- 1;1)
let log_result conn_id op_nr si msg =
let log_search_result {result_code=err;error_message=text} =
si.si_log `OPERATIONS
(sprintf "conn=%d op=%d SEARCH RESULT tag=0 err=%d nentries=0 text=%s"
conn_id op_nr (Ldap_protocol.encode_resultcode err) text)
in
let log_normal_result {result_code=err;error_message=text} =
si.si_log `OPERATIONS
(sprintf "conn=%d op=%d RESULT tag=0 err=%d text=%s"
conn_id op_nr (Ldap_protocol.encode_resultcode err) text)
in
match msg.protocolOp with
Bind_response {bind_result=result}
| Modify_response result
| Add_response result
| Delete_response result
| Modify_dn_response result
| Compare_response result -> log_normal_result result
| Search_result_done result -> log_search_result result
| _ -> ()
let send_message si conn_id op_nr fd msg =
let e_msg = encode_ldapmessage msg in
let e_msg = Bytes.of_string e_msg in
let len = Bytes.length e_msg in
let written = ref 0 in
try
while !written < len
do
written := ((write fd e_msg
!written (len - !written)) +
!written)
done;
log_result conn_id op_nr si msg
with Unix_error (_, _, _) ->
(try close fd with _ -> ());
raise (Server_error "data cannot be written")
let keys h = Hashtbl.fold (fun k v l -> k :: l) h []
let init ?(log=(fun _ _ -> ())) ?(port=389) bi =
let s =
let s = socket PF_INET SOCK_STREAM 0 in
setsockopt s SO_REUSEADDR true;
bind s (ADDR_INET (inet_addr_any, port));
listen s 500;
s
in
(match bi.bi_init with
Some f -> f ()
| None -> ());
{si_listening_socket=s;
si_client_sockets=Hashtbl.create 10;
si_current_connection_id=0;
si_log=log;
si_backend=bi}
let shutdown si =
(match si.si_backend.bi_close with
Some f -> f ()
| None -> ());
close si.si_listening_socket;
List.iter (fun fd -> close fd) (keys si.si_client_sockets);
Hashtbl.clear si.si_client_sockets;
si.si_log `GENERAL "stopped."
let dispatch_request si conn_id op_nr rb fd =
let bi = si.si_backend in
let not_imp msg op =
{messageID=msg.messageID;
protocolOp=op;
controls=None}
in
let not_implemented = {result_code=`OTHER;
matched_dn="";
error_message="Not Implemented";
ldap_referral=None}
in
let message = decode_ldapmessage rb in
match message with
{protocolOp=Bind_request {bind_name=dn;bind_authentication=auth}} ->
si.si_log `OPERATIONS
(sprintf "conn=%d op=%d BIND dn=\"%s\" method=128" conn_id op_nr dn);
si.si_log `OPERATIONS
(sprintf "conn=%d op=%d BIND dn=\"%s\" mech=%s ssf=0" conn_id op_nr dn
(match auth with
Simple _ -> "SIMPLE"
| Sasl _ -> "SASL"));
(match bi.bi_op_bind with
Some f ->
(fun () ->
send_message si conn_id op_nr fd (f conn_id message);
raise Finished)
| None -> (fun () -> send_message si conn_id op_nr fd
(not_imp message (Bind_response
{bind_result=not_implemented;
bind_serverSaslCredentials=None}));
raise Finished))
| {protocolOp=Unbind_request} ->
si.si_log `OPERATIONS
(sprintf "conn=%d op=%d UNBIND" conn_id op_nr);
(match bi.bi_op_unbind with
Some f -> (fun () -> f conn_id message;raise Finished)
| None -> (fun () -> raise Finished))
| {protocolOp=(Search_request
{baseObject=base;
scope=scope;
derefAliases=deref;
sizeLimit=sizelimit;
timeLimit=timelimit;
typesOnly=attrsonly;
filter=filter;
s_attributes=attrs})} ->
si.si_log `OPERATIONS
(sprintf "conn=%d op=%d SRCH base=\"%s\" scope=%d deref=%d filter=\"%s\""
conn_id op_nr base
(match scope with
`BASE -> 0
| `ONELEVEL -> 1
| `SUBTREE -> 2)
(match deref with
`NEVERDEREFALIASES -> 0
| `DEREFINSEARCHING -> 1
| `DEREFFINDINGBASE -> 2
| `DEREFALWAYS -> 3)
(Ldap_filter.to_string filter));
(match attrs with
[] -> ()
| lst -> si.si_log `OPERATIONS
(sprintf "conn=%d op=%d SRCH attr=%s" conn_id op_nr
(List.fold_left
(fun s attr -> if s = "" then attr else (attr ^ " " ^ s))
"" lst)));
(match bi.bi_op_search with
Some f ->
let get_srch_result = f conn_id message in
(fun () ->
let msg = get_srch_result () in
send_message si conn_id op_nr fd msg;
match msg.protocolOp with
Search_result_done _ -> raise Finished
| _ -> ())
| None -> (fun () -> send_message si conn_id op_nr fd
(not_imp message (Search_result_done not_implemented));
raise Finished))
| {protocolOp=Modify_request {mod_dn=modify;modification=modlst}} ->
si.si_log `OPERATIONS
(sprintf "conn=%d op=%d MOD dn=\"%s\"" conn_id op_nr modify);
si.si_log `OPERATIONS
(sprintf "conn=%d op=%d MOD attr=\"%s\"" conn_id op_nr
(List.fold_left
(fun s attr ->
if s = "" then
attr.mod_value.attr_type
else
(attr.mod_value.attr_type ^ " " ^ s))
"" modlst));
(match bi.bi_op_modify with
Some f -> (fun () ->
send_message si conn_id op_nr fd (f conn_id message);
raise Finished)
| None -> (fun () -> send_message si conn_id op_nr fd
(not_imp message (Modify_response not_implemented));
raise Finished))
| {protocolOp=Add_request {sr_dn=dn}} ->
si.si_log `OPERATIONS (sprintf "conn=%d op=%d ADD dn=\"%s\"" conn_id op_nr dn);
(match bi.bi_op_add with
Some f -> (fun () ->
send_message si conn_id op_nr fd (f conn_id message);
raise Finished)
| None -> (fun () -> send_message si conn_id op_nr fd
(not_imp message (Add_response not_implemented));
raise Finished))
| {protocolOp=Delete_request dn} ->
si.si_log `OPERATIONS (sprintf "conn=%d op=%d DEL dn=\"%s\"" conn_id op_nr dn);
(match bi.bi_op_delete with
Some f -> (fun () ->
send_message si conn_id op_nr fd (f conn_id message);
raise Finished)
| None -> (fun () -> send_message si conn_id op_nr fd
(not_imp message (Delete_response not_implemented));
raise Finished))
| {protocolOp=Modify_dn_request {modn_dn=dn}} ->
si.si_log `OPERATIONS (sprintf "conn=%d op=%d MODRDN dn=\"%s\"" conn_id op_nr dn);
(match bi.bi_op_modrdn with
Some f -> (fun () ->
send_message si conn_id op_nr fd (f conn_id message);
raise Finished)
| None -> (fun () -> send_message si conn_id op_nr fd
(not_imp message (Modify_dn_response not_implemented));
raise Finished))
| {protocolOp=Compare_request {cmp_dn=dn;cmp_ava=ava}} ->
si.si_log `OPERATIONS
(sprintf "conn=%d op=%d CMP dn=\"%s\" attr=\"%s\""
conn_id op_nr dn ava.attributeDesc);
(match bi.bi_op_compare with
Some f -> (fun () ->
send_message si conn_id op_nr fd (f conn_id message);
raise Finished)
| None -> (fun () -> send_message si conn_id op_nr fd
(not_imp message (Compare_response not_implemented));
raise Finished))
| {protocolOp=Abandon_request msgid} ->
si.si_log `OPERATIONS (sprintf "conn=%d op=%d ABANDON msgid=%ld" conn_id op_nr msgid);
(match bi.bi_op_abandon with
Some f -> (fun () -> f conn_id message;raise Finished)
| None -> (fun () -> raise Finished))
| {protocolOp=Extended_request _} ->
(match bi.bi_op_extended with
Some f -> (fun () ->
send_message si conn_id op_nr fd (f conn_id message);
raise Finished)
| None -> (fun () -> send_message si conn_id op_nr fd
(not_imp message
(Extended_response
{ext_result=not_implemented;
ext_responseName=None;
ext_response=None}));
raise Finished))
| _ -> raise (Server_error "invalid operation")
let string_of_sockaddr sockaddr =
match sockaddr with
ADDR_UNIX addr -> addr
| ADDR_INET (ip, port) ->
(sprintf "%s:%d" (string_of_inet_addr ip) port)
let run si =
let pending_writes si =
Hashtbl.fold
(fun k (_, _, ops_pending, _) pending ->
match ops_pending with
[] -> pending
| _ -> k :: pending)
si.si_client_sockets []
in
let process_read reading writing excond (fd:file_descr) =
if Hashtbl.mem si.si_client_sockets fd then
let (conn_id, op_nr, pending_ops, rb) = Hashtbl.find si.si_client_sockets fd in
try
try
Hashtbl.replace
si.si_client_sockets
fd
(conn_id,
(op_nr + 1),
(dispatch_request si conn_id op_nr rb fd) :: pending_ops,
rb)
with LDAP_Decoder e | Decoding_error e ->
send_message si conn_id 0 fd
{messageID=0l;
protocolOp=Extended_response
{ext_result={result_code=`PROTOCOL_ERROR;
matched_dn="";
error_message=e;
ldap_referral=None};
ext_responseName=(Some "1.3.6.1.4.1.1466.20036");
ext_response=None};
controls=None};
raise (Readbyte_error Transport_error)
with Readbyte_error Transport_error ->
(match si.si_backend.bi_op_unbind with
Some f -> f conn_id {messageID=0l;protocolOp=Unbind_request;controls=None}
| None -> ());
Hashtbl.remove si.si_client_sockets fd;
reading := List.filter ((<>) fd) !reading;
writing := List.filter ((<>) fd) !writing;
excond := List.filter ((<>) fd) !excond;
(try close fd with _ -> ());
si.si_log `CONNECTION (sprintf "conn=%d fd=0 closed" conn_id)
else
let (newfd, sockaddr) = accept fd in
let rb = readbyte_of_fd newfd in
let connid = allocate_connection_id si in
Hashtbl.add si.si_client_sockets newfd (connid, 0, [], rb);
si.si_log `CONNECTION
(sprintf "conn=%d fd=0 ACCEPT from IP=%s (IP=%s)"
connid
(string_of_sockaddr sockaddr)
(string_of_sockaddr (getsockname fd)))
in
let process_write reading writing excond (fd: file_descr) =
if Hashtbl.mem si.si_client_sockets fd then
let (conn_id, op_nr, pending_ops, rb) = Hashtbl.find si.si_client_sockets fd in
try
match pending_ops with
[] -> ()
| hd :: tl ->
try hd () with Finished ->
Hashtbl.replace si.si_client_sockets fd (conn_id, op_nr, tl, rb)
with Server_error "data cannot be written" ->
(match si.si_backend.bi_op_unbind with
Some f -> f conn_id {messageID=0l;protocolOp=Unbind_request;controls=None}
| None -> ());
Hashtbl.remove si.si_client_sockets fd;
reading := List.filter ((<>) fd) !reading;
writing := List.filter ((<>) fd) !writing;
excond := List.filter ((<>) fd) !excond;
si.si_log `CONNECTION (sprintf "conn=%d fd=0 closed" conn_id)
else raise (Server_error "socket to write to not found")
in
si.si_log `GENERAL "starting";
while true
do
let fds = keys si.si_client_sockets in
let reading = ref []
and writing = ref []
and excond = ref [] in
let (rd, wr, ex) =
select (si.si_listening_socket :: fds)
(pending_writes si)
fds (-1.0)
in
reading := rd;writing := wr;excond := ex;
List.iter (process_read reading writing excond) !reading;
List.iter (process_write reading writing excond) !writing;
List.iter (process_read reading writing excond) !excond
done