]>
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 | |
d20d9ec1 | 6 | use anyhow::{bail, format_err, Error}; |
336dab01 | 7 | |
66004f22 HL |
8 | /// Sends multi-part mail with text and/or html to a list of recipients |
9 | /// | |
bcdcb181 GG |
10 | /// Includes the header `Auto-Submitted: auto-generated`, so that auto-replies |
11 | /// (i.e. OOO replies) won't trigger. | |
66004f22 | 12 | /// ``sendmail`` is used for sending the mail. |
bbc94222 | 13 | pub fn sendmail( |
48d049d4 | 14 | mailto: &[&str], |
bbc94222 WB |
15 | subject: &str, |
16 | text: Option<&str>, | |
17 | html: Option<&str>, | |
18 | mailfrom: Option<&str>, | |
19 | author: Option<&str>, | |
20 | ) -> Result<(), Error> { | |
757031ef WB |
21 | use std::fmt::Write as _; |
22 | ||
66004f22 HL |
23 | if mailto.is_empty() { |
24 | bail!("At least one recipient has to be specified!") | |
25 | } | |
66004f22 | 26 | let mailfrom = mailfrom.unwrap_or("root"); |
66004f22 HL |
27 | let recipients = mailto.join(","); |
28 | let author = author.unwrap_or("Proxmox Backup Server"); | |
29 | ||
336dab01 | 30 | let now = proxmox_time::epoch_i64(); |
66004f22 HL |
31 | |
32 | let mut sendmail_process = match Command::new("/usr/sbin/sendmail") | |
33 | .arg("-B") | |
34 | .arg("8BITMIME") | |
35 | .arg("-f") | |
36 | .arg(mailfrom) | |
37 | .arg("--") | |
a7f40023 | 38 | .args(mailto) |
66004f22 | 39 | .stdin(Stdio::piped()) |
bbc94222 WB |
40 | .spawn() |
41 | { | |
66004f22 | 42 | Err(err) => bail!("could not spawn sendmail process: {}", err), |
bbc94222 | 43 | Ok(process) => process, |
66004f22 HL |
44 | }; |
45 | let mut is_multipart = false; | |
46 | if let (Some(_), Some(_)) = (text, html) { | |
47 | is_multipart = true; | |
48 | } | |
49 | ||
50 | let mut body = String::new(); | |
557cce7a | 51 | let boundary = format!("----_=_NextPart_001_{}", now); |
66004f22 HL |
52 | if is_multipart { |
53 | body.push_str("Content-Type: multipart/alternative;\n"); | |
757031ef | 54 | let _ = writeln!(body, "\tboundary=\"{}\"", boundary); |
66004f22 HL |
55 | body.push_str("MIME-Version: 1.0\n"); |
56 | } else if !subject.is_ascii() { | |
57 | body.push_str("MIME-Version: 1.0\n"); | |
58 | } | |
59 | if !subject.is_ascii() { | |
757031ef | 60 | let _ = writeln!(body, "Subject: =?utf-8?B?{}?=", base64::encode(subject)); |
66004f22 | 61 | } else { |
757031ef | 62 | let _ = writeln!(body, "Subject: {}", subject); |
66004f22 | 63 | } |
757031ef WB |
64 | let _ = writeln!(body, "From: {} <{}>", author, mailfrom); |
65 | let _ = writeln!(body, "To: {}", &recipients); | |
dc72878d | 66 | let rfc2822_date = proxmox_time::epoch_to_rfc2822(now)?; |
757031ef | 67 | let _ = writeln!(body, "Date: {}", rfc2822_date); |
5517d6f8 GG |
68 | body.push_str("Auto-Submitted: auto-generated;\n"); |
69 | ||
66004f22 HL |
70 | if is_multipart { |
71 | body.push('\n'); | |
72 | body.push_str("This is a multi-part message in MIME format.\n"); | |
757031ef | 73 | let _ = write!(body, "\n--{}\n", boundary); |
66004f22 HL |
74 | } |
75 | if let Some(text) = text { | |
76 | body.push_str("Content-Type: text/plain;\n"); | |
77 | body.push_str("\tcharset=\"UTF-8\"\n"); | |
78 | body.push_str("Content-Transfer-Encoding: 8bit\n"); | |
79 | body.push('\n'); | |
80 | body.push_str(text); | |
81 | if is_multipart { | |
757031ef | 82 | let _ = write!(body, "\n--{}\n", boundary); |
66004f22 HL |
83 | } |
84 | } | |
85 | if let Some(html) = html { | |
86 | body.push_str("Content-Type: text/html;\n"); | |
87 | body.push_str("\tcharset=\"UTF-8\"\n"); | |
88 | body.push_str("Content-Transfer-Encoding: 8bit\n"); | |
89 | body.push('\n'); | |
90 | body.push_str(html); | |
91 | if is_multipart { | |
757031ef | 92 | let _ = write!(body, "\n--{}--", boundary); |
66004f22 HL |
93 | } |
94 | } | |
95 | ||
bbc94222 WB |
96 | if let Err(err) = sendmail_process |
97 | .stdin | |
98 | .take() | |
99 | .unwrap() | |
100 | .write_all(body.as_bytes()) | |
101 | { | |
66004f22 HL |
102 | bail!("couldn't write to sendmail stdin: {}", err) |
103 | }; | |
104 | ||
105 | // wait() closes stdin of the child | |
106 | if let Err(err) = sendmail_process.wait() { | |
107 | bail!("sendmail did not exit successfully: {}", err) | |
108 | } | |
109 | ||
110 | Ok(()) | |
111 | } | |
112 | ||
d20d9ec1 LW |
113 | /// Forwards an email message to a given list of recipients. |
114 | /// | |
115 | /// ``sendmail`` is used for sending the mail, thus `message` must be | |
116 | /// compatible with that (the message is piped into stdin unmodified). | |
117 | pub fn forward( | |
118 | mailto: &[&str], | |
119 | mailfrom: &str, | |
120 | message: &[u8], | |
121 | uid: Option<u32>, | |
122 | ) -> Result<(), Error> { | |
123 | use std::os::unix::process::CommandExt; | |
124 | ||
125 | if mailto.is_empty() { | |
126 | bail!("At least one recipient has to be specified!") | |
127 | } | |
128 | ||
129 | let mut builder = Command::new("/usr/sbin/sendmail"); | |
130 | ||
131 | builder | |
132 | .args([ | |
133 | "-N", "never", // never send DSN (avoid mail loops) | |
134 | "-f", mailfrom, "--", | |
135 | ]) | |
136 | .args(mailto) | |
137 | .stdin(Stdio::piped()) | |
138 | .stdout(Stdio::null()) | |
139 | .stderr(Stdio::null()); | |
140 | ||
141 | if let Some(uid) = uid { | |
142 | builder.uid(uid); | |
143 | } | |
144 | ||
145 | let mut process = builder | |
146 | .spawn() | |
147 | .map_err(|err| format_err!("could not spawn sendmail process: {err}"))?; | |
148 | ||
149 | process | |
150 | .stdin | |
151 | .take() | |
152 | .unwrap() | |
153 | .write_all(message) | |
154 | .map_err(|err| format_err!("couldn't write to sendmail stdin: {err}"))?; | |
155 | ||
156 | process | |
157 | .wait() | |
158 | .map_err(|err| format_err!("sendmail did not exit successfully: {err}"))?; | |
159 | ||
160 | Ok(()) | |
161 | } | |
162 | ||
66004f22 HL |
163 | #[cfg(test)] |
164 | mod test { | |
ec3965fd | 165 | use crate::email::sendmail; |
66004f22 | 166 | |
66004f22 | 167 | #[test] |
02acce2d | 168 | fn email_without_recipients() { |
66004f22 | 169 | let result = sendmail( |
48d049d4 | 170 | &[], |
66004f22 HL |
171 | "Subject2", |
172 | None, | |
173 | Some("<b>HTML</b>"), | |
174 | None, | |
bbc94222 WB |
175 | Some("test1"), |
176 | ); | |
66004f22 HL |
177 | assert!(result.is_err()); |
178 | } | |
bbc94222 | 179 | } |