-
-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathlib.rs
270 lines (242 loc) · 8.76 KB
/
lib.rs
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
/*
* Copyright Stalwart Labs Ltd.
*
* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
* https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
* <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
* option. This file may not be copied, modified, or distributed
* except according to those terms.
*/
//! # mail-send
//!
//! [](https://crates.io/crates/mail-send)
//! [](https://github.com/stalwartlabs/mail-send/actions/workflows/rust.yml)
//! [](https://docs.rs/mail-send)
//! [](http://www.apache.org/licenses/LICENSE-2.0)
//!
//! _mail-send_ is a Rust library to build, sign and send e-mail messages via SMTP. It includes the following features:
//!
//! - Generates **e-mail** messages conforming to the Internet Message Format standard (_RFC 5322_).
//! - Full **MIME** support (_RFC 2045 - 2049_) with automatic selection of the most optimal encoding for each message body part.
//! - DomainKeys Identified Mail (**DKIM**) Signatures (_RFC 6376_) with ED25519-SHA256, RSA-SHA256 and RSA-SHA1 support.
//! - Simple Mail Transfer Protocol (**SMTP**; _RFC 5321_) delivery.
//! - SMTP Service Extension for Secure SMTP over **TLS** (_RFC 3207_).
//! - SMTP Service Extension for Authentication (_RFC 4954_) with automatic mechanism negotiation (from most secure to least secure):
//! - CRAM-MD5 (_RFC 2195_)
//! - DIGEST-MD5 (_RFC 2831_; obsolete but still supported)
//! - XOAUTH2 (Google proprietary)
//! - LOGIN
//! - PLAIN
//! - Full async (requires Tokio).
//!
//! ## Usage Example
//!
//! Send a message via an SMTP server that requires authentication:
//!
//! ```rust
//! // Build a simple multipart message
//! let message = MessageBuilder::new()
//! .from(("John Doe", "[email protected]"))
//! .to(vec![
//! ("Jane Doe", "[email protected]"),
//! ("James Smith", "[email protected]"),
//! ])
//! .subject("Hi!")
//! .html_body("<h1>Hello, world!</h1>")
//! .text_body("Hello world!");
//!
//! // Connect to the SMTP submissions port, upgrade to TLS and
//! // authenticate using the provided credentials.
//! SmtpClientBuilder::new("smtp.gmail.com", 587)
//! .implicit_tls(false)
//! .credentials(("john", "p4ssw0rd"))
//! .connect()
//! .await
//! .unwrap()
//! .send(message)
//! .await
//! .unwrap();
//! ```
//!
//! Sign a message with DKIM and send it via an SMTP relay server:
//!
//! ```rust
//! // Build a simple text message with a single attachment
//! let message = MessageBuilder::new()
//! .from(("John Doe", "[email protected]"))
//! .to("[email protected]")
//! .subject("Howdy!")
//! .text_body("These pretzels are making me thirsty.")
//! .attachment("image/png", "pretzels.png", [1, 2, 3, 4].as_ref());
//!
//! // Sign an e-mail message using RSA-SHA256
//! let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(TEST_KEY).unwrap();
//! let signer = DkimSigner::from_key(pk_rsa)
//! .domain("example.com")
//! .selector("default")
//! .headers(["From", "To", "Subject"])
//! .expiration(60 * 60 * 7); // Number of seconds before this signature expires (optional)
//!
//! // Connect to an SMTP relay server over TLS.
//! // Signs each message with the configured DKIM signer.
//! SmtpClientBuilder::new("smtp.gmail.com", 465)
//! .connect()
//! .await
//! .unwrap()
//! .send_signed(message, &signer)
//! .await
//! .unwrap();
//! ```
//!
//! More examples of how to build messages are available in the [`mail-builder`](https://crates.io/crates/mail-builder) crate.
//! Please note that this library does not support parsing e-mail messages as this functionality is provided separately by the [`mail-parser`](https://crates.io/crates/mail-parser) crate.
//!
//! ## Testing
//!
//! To run the testsuite:
//!
//! ```bash
//! $ cargo test --all-features
//! ```
//!
//! or, to run the testsuite with MIRI:
//!
//! ```bash
//! $ cargo +nightly miri test --all-features
//! ```
//!
//! ## License
//!
//! Licensed under either of
//!
//! * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
//! * MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
//!
//! at your option.
//!
//! ## Copyright
//!
//! Copyright (C) 2020-2022, Stalwart Labs Ltd.
//!
//! See [COPYING] for the license.
//!
//! [COPYING]: https://github.com/stalwartlabs/mail-send/blob/main/COPYING
//!
pub mod smtp;
use std::{fmt::Display, hash::Hash, time::Duration};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_rustls::TlsConnector;
#[cfg(feature = "builder")]
pub use mail_builder;
#[cfg(feature = "dkim")]
pub use mail_auth;
#[derive(Debug)]
pub enum Error {
/// I/O error
Io(std::io::Error),
/// TLS error
Tls(Box<rustls::Error>),
/// Base64 decode error
Base64(base64::DecodeError),
// SMTP authentication error.
Auth(smtp::auth::Error),
/// Failure parsing SMTP reply
UnparseableReply,
/// Unexpected SMTP reply.
UnexpectedReply(smtp_proto::Response<String>),
/// SMTP authentication failure.
AuthenticationFailed(smtp_proto::Response<String>),
/// Invalid TLS name provided.
InvalidTLSName,
/// Missing authentication credentials.
MissingCredentials,
/// Missing message sender.
MissingMailFrom,
/// Missing message recipients.
MissingRcptTo,
/// The server does no support any of the available authentication methods.
UnsupportedAuthMechanism,
/// Connection timeout.
Timeout,
/// STARTTLS not available
MissingStartTls,
/// Message is already signed
MessageDkimSigned,
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(ref err) => err.source(),
Error::Tls(ref err) => err.source(),
Error::Base64(ref err) => err.source(),
_ => None,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
/// SMTP client builder
#[derive(Clone)]
pub struct SmtpClientBuilder<T: AsRef<str> + PartialEq + Eq + Hash> {
pub timeout: Duration,
pub tls_connector: TlsConnector,
pub tls_hostname: T,
pub tls_implicit: bool,
pub credentials: Option<Credentials<T>>,
pub addr: String,
pub is_lmtp: bool,
pub say_ehlo: bool,
pub local_host: String,
}
/// SMTP client builder
pub struct SmtpClient<T: AsyncRead + AsyncWrite> {
pub stream: T,
pub timeout: Duration,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Credentials<T: AsRef<str> + PartialEq + Eq + Hash> {
Plain { username: T, secret: T },
OAuthBearer { token: T },
XOauth2 { username: T, secret: T },
}
impl Default for Credentials<String> {
fn default() -> Self {
Credentials::Plain {
username: String::new(),
secret: String::new(),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Io(e) => write!(f, "I/O error: {e}"),
Error::Tls(e) => write!(f, "TLS error: {e}"),
Error::Base64(e) => write!(f, "Base64 decode error: {e}"),
Error::Auth(e) => write!(f, "SMTP authentication error: {e}"),
Error::UnparseableReply => write!(f, "Unparseable SMTP reply"),
Error::UnexpectedReply(e) => write!(f, "Unexpected reply: {e}"),
Error::AuthenticationFailed(e) => write!(f, "Authentication failed: {e}"),
Error::InvalidTLSName => write!(f, "Invalid TLS name provided"),
Error::MissingCredentials => write!(f, "Missing authentication credentials"),
Error::MissingMailFrom => write!(f, "Missing message sender"),
Error::MissingRcptTo => write!(f, "Missing message recipients"),
Error::UnsupportedAuthMechanism => write!(
f,
"The server does no support any of the available authentication methods"
),
Error::Timeout => write!(f, "Connection timeout"),
Error::MissingStartTls => write!(f, "STARTTLS extension unavailable"),
Error::MessageDkimSigned => write!(f, "Message is already signed"),
}
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Io(err)
}
}
impl From<base64::DecodeError> for Error {
fn from(err: base64::DecodeError) -> Self {
Error::Base64(err)
}
}