Skip to content

Commit 267d24a

Browse files
authored
Merge pull request #78 from mirage/best-tls
Try to do our best to use TLS in any situations, also remove any raise from our codebase.
2 parents 6d66ea5 + ad30552 commit 267d24a

12 files changed

+450
-300
lines changed

sendmail-lwt.opam

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ depends: [
2020
"sendmail" {= version}
2121
"domain-name"
2222
"ipaddr"
23+
"ca-certs"
2324
"lwt"
2425
"tls" {>= "0.13.0"}
2526
"tls-lwt" {>= "0.16.0"}

sendmail-miou-unix.opam

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
opam-version: "2.0"
2+
license: "MIT"
3+
authors: [ "Gwenaëlle Lecat" "Romain Calascibetta <[email protected]>" ]
4+
maintainer: [ "Gwenaëlle Lecat" "Romain Calascibetta <[email protected]>" ]
5+
homepage: "https://github.com/mirage/colombe"
6+
bug-reports: "https://github.com/mirage/colombe/issues"
7+
dev-repo: "git+https://github.com/mirage/colombe.git"
8+
doc: "https://mirage.github.io/colombe/"
9+
synopsis: "Implementation of the sendmail command over LWT"
10+
description: """A library to be able to send an email with LWT and TLS."""
11+
12+
build: [
13+
[ "dune" "build" "-p" name "-j" jobs ]
14+
[ "dune" "runtest" "-p" name "-j" jobs ] {with-test}
15+
]
16+
17+
depends: [
18+
"ocaml" {>= "5.1.0"}
19+
"dune" {>= "2.0"}
20+
"sendmail" {= version}
21+
"domain-name"
22+
"happy-eyeballs-miou-unix"
23+
"tls-miou-unix" {>= "1.0.3"}
24+
"x509"
25+
"alcotest" {with-test}
26+
]

sendmail-mirage.opam

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ depends: [
2121
"domain-name"
2222
"happy-eyeballs-mirage"
2323
"mirage-flow"
24+
"ca-certs-nss"
2425
"lwt"
2526
"tls" {>= "0.13.0"}
2627
"tls-mirage" {>= "0.16.0"}

sendmail/dune

+10-3
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,18 @@
1717
(public_name sendmail-lwt)
1818
(modules sendmail_lwt)
1919
(libraries sendmail sendmail.starttls domain-name lwt lwt.unix tls tls-lwt
20-
ipaddr.unix))
20+
ipaddr.unix ca-certs))
21+
22+
(library
23+
(name sendmail_miou_unix)
24+
(public_name sendmail-miou-unix)
25+
(modules sendmail_miou_unix)
26+
(libraries sendmail sendmail.starttls domain-name miou.unix tls-miou-unix
27+
ca-certs happy-eyeballs-miou-unix))
2128

2229
(library
2330
(name sendmail_mirage)
2431
(public_name sendmail-mirage)
2532
(modules sendmail_mirage)
26-
(libraries sendmail sendmail.starttls domain-name lwt tls tls-mirage
27-
happy-eyeballs-mirage))
33+
(libraries sendmail sendmail.starttls domain-name ca-certs-nss lwt tls
34+
tls-mirage happy-eyeballs-mirage))

sendmail/sendmail_lwt.ml

+78-81
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ open Lwt.Infix
22
open Colombe
33

44
let ( <.> ) f g x = f (g x)
5+
let ( >>? ) = Lwt_result.bind
6+
let error_msgf fmt = Fmt.kstr (fun msg -> Error (`Msg msg)) fmt
57

68
module Lwt_scheduler = Sigs.Make (Lwt)
79

@@ -46,55 +48,81 @@ let open_sendmail_with_starttls_error = function
4648
| Error (#Sendmail_with_starttls.error as err) -> Error err
4749

4850
let open_error = function Ok _ as v -> v | Error (#error as err) -> Error err
49-
let failwith_error_msg = function Ok v -> v | Error (`Msg msg) -> failwith msg
50-
let failf fmt = Fmt.kstr (fun err -> Lwt.fail (Failure err)) fmt
51+
let authenticator = Lazy.from_fun Ca_certs.authenticator
52+
53+
let tls_config user's_tls_config user's_authenticator =
54+
match user's_tls_config with
55+
| Some cfg -> Ok cfg
56+
| None ->
57+
let ( let* ) = Result.bind in
58+
let* authenticator =
59+
match (Lazy.force authenticator, user's_authenticator) with
60+
| Ok authenticator, None -> Ok authenticator
61+
| _, Some authenticator -> Ok authenticator
62+
| (Error _ as err), None -> err in
63+
Tls.Config.client ~authenticator ()
5164

5265
let resolve host ?port service =
5366
Lwt_unix.getprotobyname "tcp" >>= fun tcp ->
5467
Lwt_unix.getaddrinfo host service Unix.[ AI_PROTOCOL tcp.Unix.p_proto ]
5568
>>= fun result ->
5669
match (result, port) with
5770
| [], None ->
58-
failf
59-
"Service %S is not recognized by your system or the host %s is \
60-
unreachable"
61-
service host
71+
Lwt.return
72+
(error_msgf
73+
"Service %S is not recognized by your system or the host %s is \
74+
unreachable"
75+
service host)
6276
| [], Some port -> (
6377
Lwt_unix.gethostbyname host >>= function
64-
| { Unix.h_addr_list = [||]; _ } -> failf "Host %s unreachable" host
78+
| { Unix.h_addr_list = [||]; _ } ->
79+
Lwt.return (error_msgf "Host %s unreachable" host)
6580
| { Unix.h_addr_list; _ } ->
66-
Lwt.return (Unix.ADDR_INET (h_addr_list.(0), port)))
81+
Lwt.return_ok (Unix.ADDR_INET (h_addr_list.(0), port)))
6782
| ai :: _, _ ->
6883
match (port, ai.ai_addr) with
6984
| Some port, Unix.ADDR_INET (inet_addr, _) ->
70-
Lwt.return (Unix.ADDR_INET (inet_addr, port))
71-
| _ -> Lwt.return ai.ai_addr
85+
Lwt.return_ok (Unix.ADDR_INET (inet_addr, port))
86+
| _ -> Lwt.return_ok ai.ai_addr
87+
88+
let pp_addr ppf = function
89+
| Unix.ADDR_INET (inet_addr, port) ->
90+
Fmt.pf ppf "%s:%d" (Unix.string_of_inet_addr inet_addr) port
91+
| Unix.ADDR_UNIX str -> Fmt.pf ppf "<%s>" str
7292

73-
let submit ?encoder ?decoder ?queue ~destination ?port ~domain ?authenticator
74-
?authentication sender recipients mail =
93+
let connect socket addr =
94+
Lwt.pick
95+
[
96+
Lwt_unix.sleep 5.0 >|= Fun.const `Timeout;
97+
Lwt_unix.connect socket addr >|= Fun.const `Connected;
98+
]
99+
>>= function
100+
| `Timeout -> Lwt.return (error_msgf "Connection to %a timeout" pp_addr addr)
101+
| `Connected -> Lwt.return_ok ()
102+
103+
let submit ?encoder ?decoder ?queue ~destination ?port ~domain
104+
?cfg:user's_tls_config ?authenticator:user's_authenticator ?authentication
105+
sender recipients mail =
75106
let mail () = Lwt_scheduler.inj (mail ()) in
107+
Lwt.return (tls_config user's_tls_config user's_authenticator)
108+
>>? fun tls_cfg ->
76109
let protocol =
77-
match (port, authenticator) with
78-
| Some 587, Some authenticator ->
79-
`With_starttls
80-
(failwith_error_msg (Tls.Config.client ~authenticator ()))
81-
| (Some 587 | None), None -> `Clear
82-
| (Some _ | None), Some authenticator ->
83-
`With_tls (failwith_error_msg (Tls.Config.client ~authenticator ()))
84-
| Some _, None -> `Clear in
85-
match (protocol, authentication) with
86-
| `With_starttls tls, _ ->
110+
match port with
111+
| Some 587 -> `With_starttls tls_cfg
112+
| Some _ | None -> `With_tls tls_cfg in
113+
match protocol with
114+
| `With_starttls tls ->
87115
(match (destination, port) with
88116
| `Ipaddr ipaddr, Some port ->
89-
Lwt.return (Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, port))
117+
Lwt.return_ok (Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, port))
90118
| `Ipaddr ipaddr, None ->
91-
Lwt.return (Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, 587))
119+
Lwt.return_ok (Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, 587))
92120
| `Domain_name domain_name, port ->
93121
resolve (Domain_name.to_string domain_name) ?port "submission")
94-
>>= fun addr ->
122+
>>? fun addr ->
95123
let socket =
96124
Lwt_unix.socket (Unix.domain_of_sockaddr addr) Unix.SOCK_STREAM 0 in
97-
Lwt_unix.connect socket addr >>= fun () ->
125+
connect socket addr >>? fun () ->
98126
let ic = Lwt_io.of_fd ~mode:Lwt_io.Input socket in
99127
let oc = Lwt_io.of_fd ~mode:Lwt_io.Output socket in
100128
let ctx =
@@ -105,50 +133,31 @@ let submit ?encoder ?decoder ?queue ~destination ?port ~domain ?authenticator
105133
|> Lwt_scheduler.prj
106134
>|= open_sendmail_with_starttls_error
107135
>|= open_error
108-
| `Clear, Some _ -> Lwt.return_error `Encryption_required
109-
| `Clear, None ->
110-
(match (destination, port) with
111-
| `Ipaddr ipaddr, Some port ->
112-
Lwt.return (Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, port))
113-
| `Ipaddr ipaddr, None ->
114-
Lwt.return (Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, 587))
115-
| `Domain_name domain_name, port ->
116-
resolve (Domain_name.to_string domain_name) ?port "submission")
117-
>>= fun addr ->
118-
let socket =
119-
Lwt_unix.socket (Unix.domain_of_sockaddr addr) Unix.SOCK_STREAM 0 in
120-
Lwt_unix.connect socket addr >>= fun () ->
121-
let ic = Lwt_io.of_fd ~mode:Lwt_io.Input socket in
122-
let oc = Lwt_io.of_fd ~mode:Lwt_io.Output socket in
123-
let ctx = Colombe.State.Context.make ?encoder ?decoder () in
124-
Sendmail.sendmail lwt rdwr { ic; oc } ctx ~domain ?authentication sender
125-
recipients mail
126-
|> Lwt_scheduler.prj
127-
>|= open_sendmail_error
128-
>|= (function Error err -> Error (err :> error) | Ok value -> Ok value)
129-
>|= open_error
130-
| `With_tls tls, _ ->
136+
| `With_tls tls ->
131137
(match (destination, port) with
132138
| `Ipaddr ipaddr, Some port ->
133139
let addr = Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, port) in
134140
let socket =
135141
Lwt_unix.socket (Unix.domain_of_sockaddr addr) Unix.SOCK_STREAM 0
136142
in
137-
Lwt_unix.connect socket addr >>= fun () ->
143+
connect socket addr >>? fun () ->
138144
Tls_lwt.Unix.client_of_fd tls socket
139145
>|= Tls_lwt.of_t ~close:(fun () -> Lwt_unix.close socket)
146+
>|= Result.ok
140147
| `Ipaddr ipaddr, None ->
141148
let addr = Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, 465) in
142149
let socket =
143150
Lwt_unix.socket (Unix.domain_of_sockaddr addr) Unix.SOCK_STREAM 0
144151
in
145-
Lwt_unix.connect socket addr >>= fun () ->
152+
connect socket addr >>? fun () ->
146153
Tls_lwt.Unix.client_of_fd tls socket
147154
>|= Tls_lwt.of_t ~close:(fun () -> Lwt_unix.close socket)
155+
>|= Result.ok
148156
| `Domain_name domain_name, port ->
149157
let port = Option.value ~default:465 port in
150-
Tls_lwt.connect_ext tls (Domain_name.to_string domain_name, port))
151-
>>= fun (ic, oc) ->
158+
Tls_lwt.connect_ext tls (Domain_name.to_string domain_name, port)
159+
>|= Result.ok)
160+
>>? fun (ic, oc) ->
152161
let ctx = Colombe.State.Context.make ?encoder ?decoder () in
153162
Sendmail.sendmail lwt rdwr { ic; oc } ctx ~domain ?authentication sender
154163
recipients mail
@@ -157,41 +166,29 @@ let submit ?encoder ?decoder ?queue ~destination ?port ~domain ?authenticator
157166
>|= (function Error err -> Error (err :> error) | Ok value -> Ok value)
158167
>|= open_error
159168

160-
let sendmail ?encoder ?decoder ?queue ~destination ?port ~domain ?authenticator
161-
?authentication sender recipients mail =
169+
let sendmail ?encoder ?decoder ?queue ~destination ?port ~domain
170+
?cfg:user's_tls_config ?authenticator:user's_authenticator ?authentication
171+
sender recipients mail =
162172
(match (destination, port) with
163173
| `Ipaddr ipaddr, Some port ->
164-
Lwt.return (Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, port))
174+
Lwt.return_ok (Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, port))
165175
| `Ipaddr ipaddr, None ->
166-
Lwt.return (Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, 25))
176+
Lwt.return_ok (Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ipaddr, 25))
167177
| `Domain_name domain_name, port ->
168178
resolve (Domain_name.to_string domain_name) ?port "smtp")
169-
>>= fun addr ->
179+
>>? fun addr ->
170180
let socket =
171181
Lwt_unix.socket (Unix.domain_of_sockaddr addr) Unix.SOCK_STREAM 0 in
172-
Lwt_unix.connect socket addr >>= fun () ->
182+
connect socket addr >>? fun () ->
173183
let mail () = Lwt_scheduler.inj (mail ()) in
174184
let ic = Lwt_io.of_fd ~mode:Lwt_io.Input socket in
175185
let oc = Lwt_io.of_fd ~mode:Lwt_io.Output socket in
176-
match
177-
Option.map
178-
(fun authenticator -> Tls.Config.client ~authenticator ())
179-
authenticator
180-
with
181-
| Some (Error _ as err) -> Lwt.return err
182-
| Some (Ok tls) ->
183-
let ctx =
184-
Sendmail_with_starttls.Context_with_tls.make ?encoder ?decoder ?queue ()
185-
in
186-
Sendmail_with_starttls.sendmail lwt rdwr { ic; oc } ctx tls
187-
?authentication ~domain sender recipients mail
188-
|> Lwt_scheduler.prj
189-
>|= open_sendmail_with_starttls_error
190-
| None ->
191-
let ctx = Colombe.State.Context.make ?encoder ?decoder () in
192-
Sendmail.sendmail lwt rdwr { ic; oc } ctx ~domain ?authentication sender
193-
recipients mail
194-
|> Lwt_scheduler.prj
195-
>|= open_sendmail_error
196-
>|= (function Error err -> Error (err :> error) | Ok value -> Ok value)
197-
>|= open_error
186+
Lwt.return (tls_config user's_tls_config user's_authenticator)
187+
>>? fun tls_cfg ->
188+
let ctx =
189+
Sendmail_with_starttls.Context_with_tls.make ?encoder ?decoder ?queue ()
190+
in
191+
Sendmail_with_starttls.sendmail lwt rdwr { ic; oc } ctx tls_cfg
192+
?authentication ~domain sender recipients mail
193+
|> Lwt_scheduler.prj
194+
>|= open_sendmail_with_starttls_error

0 commit comments

Comments
 (0)