melib/smtp: add BINARYMIME support to smtp client

Concerns #49

IMAP: Lemonade profile tracking issue
imap-lemonade
Manos Pitsidianakis 2022-09-03 18:17:17 +03:00
parent ed16e29de1
commit 808bdf75a1
1 changed files with 66 additions and 54 deletions

View File

@ -204,7 +204,7 @@ pub struct SmtpExtensionSupport {
//envelope exchange. //envelope exchange.
#[serde(default = "crate::conf::true_val")] #[serde(default = "crate::conf::true_val")]
prdr: bool, prdr: bool,
#[serde(default = "crate::conf::false_val")] #[serde(default = "crate::conf::true_val")]
binarymime: bool, binarymime: bool,
//Resources: //Resources:
//- http://www.postfix.org/SMTPUTF8_README.html //- http://www.postfix.org/SMTPUTF8_README.html
@ -227,7 +227,7 @@ impl Default for SmtpExtensionSupport {
chunking: true, chunking: true,
prdr: true, prdr: true,
_8bitmime: true, _8bitmime: true,
binarymime: false, binarymime: true,
smtputf8: true, smtputf8: true,
auth: true, auth: true,
dsn_notify: Some("FAILURE".into()), dsn_notify: Some("FAILURE".into()),
@ -641,7 +641,9 @@ impl SmtpConnection {
if self.server_conf.extensions.prdr { if self.server_conf.extensions.prdr {
current_command.push(b" PRDR"); current_command.push(b" PRDR");
} }
if self.server_conf.extensions._8bitmime { if self.server_conf.extensions.binarymime {
current_command.push(b" BODY=BINARYMIME");
} else if self.server_conf.extensions._8bitmime {
current_command.push(b" BODY=8BITMIME"); current_command.push(b" BODY=8BITMIME");
} }
self.send_command(&current_command).await?; self.send_command(&current_command).await?;
@ -688,71 +690,81 @@ impl SmtpConnection {
//permitted on either side of the colon following FROM in the MAIL command or TO in the //permitted on either side of the colon following FROM in the MAIL command or TO in the
//RCPT command. The syntax is exactly as given above. //RCPT command. The syntax is exactly as given above.
//The third step in the procedure is the DATA command if self.server_conf.extensions.binarymime {
//(or some alternative specified in a service extension). let mail_length = format!("{}", mail.as_bytes().len());
//DATA <CRLF> self.send_command(&[b"BDAT", mail_length.as_bytes(), b"LAST"])
self.send_command(&[b"DATA"]).await?; .await?;
//Client SMTP implementations that employ pipelining MUST check ALL statuses associated self.stream
//with each command in a group. For example, if none of the RCPT TO recipient addresses .write_all(mail.as_bytes())
//were accepted the client must then check the response to the DATA command -- the client .await
//cannot assume that the DATA command will be rejected just because none of the RCPT TO .chain_err_kind(crate::error::ErrorKind::Network)?;
//commands worked. If the DATA command was properly rejected the client SMTP can just } else {
//issue RSET, but if the DATA command was accepted the client SMTP should send a single //The third step in the procedure is the DATA command
//dot. //(or some alternative specified in a service extension).
let mut _all_error = self.server_conf.extensions.pipelining; //DATA <CRLF>
let mut _any_error = false; self.send_command(&[b"DATA"]).await?;
let mut ignore_mailfrom = true; //Client SMTP implementations that employ pipelining MUST check ALL statuses associated
for expected_reply_code in pipelining_queue { //with each command in a group. For example, if none of the RCPT TO recipient addresses
let reply = self.read_lines(&mut res, expected_reply_code).await?; //were accepted the client must then check the response to the DATA command -- the client
if !ignore_mailfrom { //cannot assume that the DATA command will be rejected just because none of the RCPT TO
_all_error &= reply.code.is_err(); //commands worked. If the DATA command was properly rejected the client SMTP can just
_any_error |= reply.code.is_err(); //issue RSET, but if the DATA command was accepted the client SMTP should send a single
//dot.
let mut _all_error = self.server_conf.extensions.pipelining;
let mut _any_error = false;
let mut ignore_mailfrom = true;
for expected_reply_code in pipelining_queue {
let reply = self.read_lines(&mut res, expected_reply_code).await?;
if !ignore_mailfrom {
_all_error &= reply.code.is_err();
_any_error |= reply.code.is_err();
}
ignore_mailfrom = false;
pipelining_results.push(reply.into());
} }
ignore_mailfrom = false;
pipelining_results.push(reply.into());
}
//If accepted, the SMTP server returns a 354 Intermediate reply and considers all //If accepted, the SMTP server returns a 354 Intermediate reply and considers all
//succeeding lines up to but not including the end of mail data indicator to be the //succeeding lines up to but not including the end of mail data indicator to be the
//message text. When the end of text is successfully received and stored, the //message text. When the end of text is successfully received and stored, the
//SMTP-receiver sends a "250 OK" reply. //SMTP-receiver sends a "250 OK" reply.
self.read_lines(&mut res, Some((ReplyCode::_354, &[]))) self.read_lines(&mut res, Some((ReplyCode::_354, &[])))
.await?; .await?;
//Before sending a line of mail text, the SMTP client checks the first character of the //Before sending a line of mail text, the SMTP client checks the first character of the
//line.If it is a period, one additional period is inserted at the beginning of the line. //line.If it is a period, one additional period is inserted at the beginning of the line.
for line in mail.lines() { for line in mail.lines() {
if line.starts_with('.') { if line.starts_with('.') {
self.stream
.write_all(b".")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
self.stream self.stream
.write_all(b".") .write_all(line.as_bytes())
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
self.stream
.write_all(b"\r\n")
.await .await
.chain_err_kind(crate::error::ErrorKind::Network)?; .chain_err_kind(crate::error::ErrorKind::Network)?;
} }
self.stream
.write_all(line.as_bytes())
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
self.stream
.write_all(b"\r\n")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
if !mail.ends_with('\n') { if !mail.ends_with('\n') {
self.stream
.write_all(b".\r\n")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
}
//The mail data are terminated by a line containing only a period, that is, the character
//sequence "<CRLF>.<CRLF>", where the first <CRLF> is actually the terminator of the
//previous line (see Section 4.5.2). This is the end of mail data indication.
self.stream self.stream
.write_all(b".\r\n") .write_all(b".\r\n")
.await .await
.chain_err_kind(crate::error::ErrorKind::Network)?; .chain_err_kind(crate::error::ErrorKind::Network)?;
} }
//The mail data are terminated by a line containing only a period, that is, the character
//sequence "<CRLF>.<CRLF>", where the first <CRLF> is actually the terminator of the
//previous line (see Section 4.5.2). This is the end of mail data indication.
self.stream
.write_all(b".\r\n")
.await
.chain_err_kind(crate::error::ErrorKind::Network)?;
//The end of mail data indicator also confirms the mail transaction and tells the SMTP //The end of mail data indicator also confirms the mail transaction and tells the SMTP
//server to now process the stored recipients and mail data. If accepted, the SMTP //server to now process the stored recipients and mail data. If accepted, the SMTP
//server returns a "250 OK" reply. //server returns a "250 OK" reply.