]>
Commit | Line | Data |
---|---|---|
66004f22 HL |
1 | //! Email related utilities. |
2 | ||
bbc94222 WB |
3 | use std::io::Write; |
4 | use std::process::{Command, Stdio}; | |
66004f22 | 5 | |
336dab01 WB |
6 | use anyhow::{bail, Error}; |
7 | ||
66004f22 HL |
8 | /// Sends multi-part mail with text and/or html to a list of recipients |
9 | /// | |
10 | /// ``sendmail`` is used for sending the mail. | |
bbc94222 | 11 | pub fn sendmail( |
48d049d4 | 12 | mailto: &[&str], |
bbc94222 WB |
13 | subject: &str, |
14 | text: Option<&str>, | |
15 | html: Option<&str>, | |
16 | mailfrom: Option<&str>, | |
17 | author: Option<&str>, | |
18 | ) -> Result<(), Error> { | |
66004f22 HL |
19 | if mailto.is_empty() { |
20 | bail!("At least one recipient has to be specified!") | |
21 | } | |
66004f22 | 22 | let mailfrom = mailfrom.unwrap_or("root"); |
66004f22 HL |
23 | let recipients = mailto.join(","); |
24 | let author = author.unwrap_or("Proxmox Backup Server"); | |
25 | ||
336dab01 | 26 | let now = proxmox_time::epoch_i64(); |
66004f22 HL |
27 | |
28 | let mut sendmail_process = match Command::new("/usr/sbin/sendmail") | |
29 | .arg("-B") | |
30 | .arg("8BITMIME") | |
31 | .arg("-f") | |
32 | .arg(mailfrom) | |
33 | .arg("--") | |
a7f40023 | 34 | .args(mailto) |
66004f22 | 35 | .stdin(Stdio::piped()) |
bbc94222 WB |
36 | .spawn() |
37 | { | |
66004f22 | 38 | Err(err) => bail!("could not spawn sendmail process: {}", err), |
bbc94222 | 39 | Ok(process) => process, |
66004f22 HL |
40 | }; |
41 | let mut is_multipart = false; | |
42 | if let (Some(_), Some(_)) = (text, html) { | |
43 | is_multipart = true; | |
44 | } | |
45 | ||
46 | let mut body = String::new(); | |
557cce7a | 47 | let boundary = format!("----_=_NextPart_001_{}", now); |
66004f22 HL |
48 | if is_multipart { |
49 | body.push_str("Content-Type: multipart/alternative;\n"); | |
50 | body.push_str(&format!("\tboundary=\"{}\"\n", boundary)); | |
51 | body.push_str("MIME-Version: 1.0\n"); | |
52 | } else if !subject.is_ascii() { | |
53 | body.push_str("MIME-Version: 1.0\n"); | |
54 | } | |
55 | if !subject.is_ascii() { | |
bbc94222 WB |
56 | body.push_str(&format!( |
57 | "Subject: =?utf-8?B?{}?=\n", | |
58 | base64::encode(subject) | |
59 | )); | |
66004f22 HL |
60 | } else { |
61 | body.push_str(&format!("Subject: {}\n", subject)); | |
62 | } | |
63 | body.push_str(&format!("From: {} <{}>\n", author, mailfrom)); | |
64 | body.push_str(&format!("To: {}\n", &recipients)); | |
336dab01 WB |
65 | let localtime = proxmox_time::localtime(now)?; |
66 | let rfc2822_date = proxmox_time::strftime("%a, %d %b %Y %T %z", &localtime)?; | |
317d1a78 | 67 | body.push_str(&format!("Date: {}\n", rfc2822_date)); |
66004f22 HL |
68 | if is_multipart { |
69 | body.push('\n'); | |
70 | body.push_str("This is a multi-part message in MIME format.\n"); | |
71 | body.push_str(&format!("\n--{}\n", boundary)); | |
72 | } | |
73 | if let Some(text) = text { | |
74 | body.push_str("Content-Type: text/plain;\n"); | |
75 | body.push_str("\tcharset=\"UTF-8\"\n"); | |
76 | body.push_str("Content-Transfer-Encoding: 8bit\n"); | |
77 | body.push('\n'); | |
78 | body.push_str(text); | |
79 | if is_multipart { | |
80 | body.push_str(&format!("\n--{}\n", boundary)); | |
81 | } | |
82 | } | |
83 | if let Some(html) = html { | |
84 | body.push_str("Content-Type: text/html;\n"); | |
85 | body.push_str("\tcharset=\"UTF-8\"\n"); | |
86 | body.push_str("Content-Transfer-Encoding: 8bit\n"); | |
87 | body.push('\n'); | |
88 | body.push_str(html); | |
89 | if is_multipart { | |
90 | body.push_str(&format!("\n--{}--", boundary)); | |
91 | } | |
92 | } | |
93 | ||
bbc94222 WB |
94 | if let Err(err) = sendmail_process |
95 | .stdin | |
96 | .take() | |
97 | .unwrap() | |
98 | .write_all(body.as_bytes()) | |
99 | { | |
66004f22 HL |
100 | bail!("couldn't write to sendmail stdin: {}", err) |
101 | }; | |
102 | ||
103 | // wait() closes stdin of the child | |
104 | if let Err(err) = sendmail_process.wait() { | |
105 | bail!("sendmail did not exit successfully: {}", err) | |
106 | } | |
107 | ||
108 | Ok(()) | |
109 | } | |
110 | ||
111 | #[cfg(test)] | |
112 | mod test { | |
ec3965fd | 113 | use crate::email::sendmail; |
66004f22 | 114 | |
66004f22 | 115 | #[test] |
02acce2d | 116 | fn email_without_recipients() { |
66004f22 | 117 | let result = sendmail( |
48d049d4 | 118 | &[], |
66004f22 HL |
119 | "Subject2", |
120 | None, | |
121 | Some("<b>HTML</b>"), | |
122 | None, | |
bbc94222 WB |
123 | Some("test1"), |
124 | ); | |
66004f22 HL |
125 | assert!(result.is_err()); |
126 | } | |
bbc94222 | 127 | } |