Source file tls_miou_unix.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
let src = Logs.Src.create "tls-miou"
module Log = (val Logs.src_log src : Logs.LOG)
external reraise : exn -> 'a = "%reraise"
let ( $ ) f x = f x
exception Tls_alert of Tls.Packet.alert_type
exception Tls_failure of Tls.Engine.failure
exception Closed_by_peer
let () =
Printexc.register_printer @@ function
| Closed_by_peer -> Some "Connection closed by peer"
| Tls_alert alert -> Some (Tls.Packet.alert_type_to_string alert)
| Tls_failure failure -> Some (Tls.Engine.string_of_failure failure)
| _ -> None
type state =
[ `Active of Tls.Engine.state
| `Read_closed of Tls.Engine.state
| `Write_closed of Tls.Engine.state
| `Closed
| `Error of exn ]
type t = {
role : [ `Server | `Client ];
fd : Miou_unix.file_descr;
mutable state : state;
mutable linger : string option;
read_buffer_size : int;
buf : bytes;
mutable rd_closed : bool;
}
let file_descr { fd; _ } = fd
let half_close state mode =
match (state, mode) with
| `Active tls, `read -> `Read_closed tls
| `Active tls, `write -> `Write_closed tls
| `Active _, `read_write -> `Closed
| `Read_closed tls, `read -> `Read_closed tls
| `Read_closed _, (`write | `read_write) -> `Closed
| `Write_closed tls, `write -> `Write_closed tls
| `Write_closed _, (`read | `read_write) -> `Closed
| ((`Closed | `Error _) as e), (`read | `write | `read_write) -> e
let inject_state tls = function
| `Active _ -> `Active tls
| `Read_closed _ -> `Read_closed tls
| `Write_closed _ -> `Write_closed tls
| (`Closed | `Error _) as e -> e
let tls_alert a = Tls_alert a
let tls_fail f = Tls_failure f
let inhibit fn v = try fn v with _ -> ()
let write flow str =
Log.debug (fun m -> m "try to write %d byte(s)" (String.length str));
try Miou_unix.write flow.fd str with
| Unix.Unix_error ((Unix.EPIPE | Unix.ECONNRESET), _, _) ->
flow.state <- half_close flow.state `write;
raise Closed_by_peer
| Unix.Unix_error (_, _, _) as exn ->
flow.state <- `Error exn;
reraise exn
let handle flow tls str =
match Tls.Engine.handle_tls tls str with
| Ok (state, eof, `Response resp, `Data data) ->
Log.debug (fun m -> m "We handled %d byte(s)" (String.length str));
let state = inject_state state flow.state in
let state = Option.(value ~default:state (map (fun `Eof -> half_close state `read) eof)) in
flow.state <- state;
let to_close = flow.state = `Closed in
Option.iter (inhibit $ write flow) resp;
if to_close || flow.state = `Closed then Miou_unix.close flow.fd;
data
| Error (fail, `Response resp) ->
let exn = match fail with
| `Alert a -> tls_alert a | f -> tls_fail f in
flow.state <- `Error exn;
let _ = inhibit (write flow) resp in
raise exn
let read flow =
match Miou_unix.read flow.fd flow.buf ~off:0 ~len:(Bytes.length flow.buf) with
| 0 -> Ok String.empty
| len -> Ok (Bytes.sub_string flow.buf 0 len)
| exception Unix.Unix_error (Unix.ECONNRESET, _, _) -> Ok String.empty
| exception exn -> Error exn
let not_errored = function `Error _ -> false | _ -> true
let garbage flow = match flow.linger with
| Some "" | None -> false
| _ -> true
let read_react flow =
match flow.state with
| `Error exn -> raise exn
| `Read_closed _ | `Closed when garbage flow ->
let mbuf = flow.linger in
flow.linger <- None;
mbuf
| `Read_closed _ | `Closed ->
raise End_of_file
| `Active _ | `Write_closed _ ->
Log.debug (fun m -> m "read something from the TLS session");
match read flow with
| Error exn ->
if not_errored flow.state then flow.state <- `Error exn;
raise exn
| Ok "" ->
raise End_of_file
| Ok str ->
Log.debug (fun m -> m "got %d byte(s)" (String.length str));
match flow.state with
| `Active tls | `Read_closed tls | `Write_closed tls -> handle flow tls str
| `Closed -> raise End_of_file
| `Error exn -> raise exn
[@@ocamlformat "disable"]
let rec read_in flow ?(off= 0) ?len buf =
let len = Option.value ~default:(Bytes.length buf - off) len in
let write_in res =
let rlen = String.length res in
let mlen = min len rlen in
Bytes.blit_string res 0 buf off mlen;
let linger = if mlen < rlen
then Some (String.sub res mlen (rlen - mlen))
else None in
flow.linger <- linger; mlen
in
match flow.linger with
| Some res -> write_in res
| None -> (
match read_react flow with
| None -> read_in ~off ~len flow buf
| Some res -> write_in res)
let writev flow bufs =
match flow.state with
| `Closed | `Write_closed _ -> raise Closed_by_peer
| `Error exn -> reraise exn
| `Active tls | `Read_closed tls -> (
match Tls.Engine.send_application_data tls bufs with
| Some (tls, answer) ->
flow.state <- inject_state tls flow.state;
write flow answer
| None -> assert false)
let rec drain_handshake flow =
let push_linger flow mcs =
match (mcs, flow.linger) with
| None, _ -> ()
| scs, None -> flow.linger <- scs
| Some cs, Some l -> flow.linger <- Some (l ^ cs)
in
match flow.state with
| `Active tls when not (Tls.Engine.handshake_in_progress tls) -> flow
| (`Read_closed _ | `Closed) when garbage flow -> flow
| _ ->
Log.debug (fun m -> m "start to read something from the TLS session");
let mcs = read_react flow in
push_linger flow mcs;
drain_handshake flow
let close flow =
match flow.state with
| `Active tls | `Read_closed tls ->
let tls, str = Tls.Engine.send_close_notify tls in
flow.rd_closed <- true;
flow.state <- inject_state tls flow.state;
flow.state <- `Closed;
inhibit (write flow) str;
Miou_unix.close flow.fd
| `Write_closed _ ->
flow.rd_closed <- true;
flow.state <- `Closed;
Miou_unix.close flow.fd
| `Closed -> flow.rd_closed <- true
| `Error _ ->
flow.rd_closed <- true;
Miou_unix.close flow.fd
let closed_by_user flow = function
| `read | `read_write -> flow.rd_closed <- true
| `write -> ()
let shutdown flow mode =
closed_by_user flow mode;
match (flow.state, mode) with
| `Active tls, `read ->
Log.debug (fun m -> m "shutdown `read");
flow.state <- inject_state tls (half_close flow.state mode)
| (`Active tls | `Read_closed tls), (`write | `read_write) ->
let tls, str = Tls.Engine.send_close_notify tls in
flow.state <- inject_state tls (half_close flow.state mode);
let to_close = flow.state = `Closed in
inhibit (write flow) str;
if to_close || flow.state = `Closed then Miou_unix.close flow.fd
| `Write_closed tls, (`read | `read_write) ->
flow.state <- inject_state tls (half_close flow.state mode);
if flow.state = `Closed then Miou_unix.close flow.fd
| `Error _, _ -> Miou_unix.close flow.fd
| `Read_closed _, `read -> ()
| `Write_closed _, `write -> ()
| `Closed, _ -> ()
let client_of_fd conf ?(read_buffer_size = 0x1000) ?host fd =
let conf' =
match host with None -> conf | Some host -> Tls.Config.peer conf host
in
let tls, init = Tls.Engine.client conf' in
let tls_flow =
{
role = `Client;
fd;
state = `Active tls;
linger = None;
read_buffer_size;
buf = Bytes.make read_buffer_size '\000';
rd_closed = false;
}
in
write tls_flow init;
drain_handshake tls_flow
let server_of_fd conf ?(read_buffer_size = 0x1000) fd =
let tls = Tls.Engine.server conf in
let tls_flow =
{
role = `Server;
fd;
state = `Active tls;
linger = None;
read_buffer_size;
buf = Bytes.make read_buffer_size '\000';
rd_closed = false;
}
in
drain_handshake tls_flow
let write flow ?(off = 0) ?len str =
let len = Option.value ~default:(String.length str - off) len in
if off < 0 || len < 0 || off > String.length str - len
then invalid_arg "Tls_miou.write";
if len > 0 then writev flow [ String.sub str off len ]
let read t ?(off= 0) ?len buf =
let len = Option.value ~default:(Bytes.length buf - off) len in
if off < 0 || len < 0 || off > Bytes.length buf - len
then invalid_arg "Tls_miou.read";
if t.rd_closed then 0
else try read_in t ~off ~len buf with End_of_file -> 0
let rec really_read_go t off len buf =
let len' = read t buf ~off ~len in
if len' == 0 then raise End_of_file
else if len - len' > 0
then really_read_go t (off + len') (len - len') buf
let really_read t ?(off= 0) ?len buf =
let len = Option.value ~default:(Bytes.length buf - off) len in
if off < 0 || len < 0 || off > Bytes.length buf - len
then invalid_arg "Tls_miou.really_read";
if len > 0 then really_read_go t off len buf
let resolve host service =
let tcp = Unix.getprotobyname "tcp" in
match Unix.getaddrinfo host service [ AI_PROTOCOL tcp.p_proto ] with
| [] -> Fmt.invalid_arg "No address for %s:%s" host service
| ai :: _ -> ai.ai_addr
let connect authenticator (v, port) =
let conf =
match Tls.Config.client ~authenticator () with
| Ok config -> config
| Error `Msg msg -> Fmt.invalid_arg "Configuration failure: %s" msg
in
let addr = resolve v (string_of_int port) in
let fd =
match addr with
| Unix.ADDR_UNIX _ -> invalid_arg "Tls_miou.connect: Invalid UNIX socket"
| Unix.ADDR_INET (inet_addr, _) ->
if Unix.is_inet6_addr inet_addr then Miou_unix.tcpv6 ()
else Miou_unix.tcpv4 ()
in
let host = Result.to_option Domain_name.(Result.bind (of_string v) host) in
match Miou_unix.connect fd addr with
| () -> client_of_fd conf ?host fd
| exception exn ->
Miou_unix.close fd;
raise exn
let epoch flow = match flow.state with
| `Active tls | `Read_closed tls | `Write_closed tls ->
( match Tls.Engine.epoch tls with
| Error () -> assert false
| Ok data -> Some data )
| _ -> None