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
|
use chrono::Utc;
use std::io::{Error, ErrorKind};
use std::process::{Output, Stdio};
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
use crate::model::config::Config;
static FROM_NAME: &str = "Budget";
static FROM_ADDRESS: &str = "budget@guyonvarch.me";
#[derive(Clone)]
pub struct Recipient {
pub name: String,
pub address: String,
}
pub async fn send(
config: &Config,
recipients: Vec<Recipient>,
subject: String,
message: String,
) -> bool {
let headers = format_headers(recipients.clone(), subject);
log::info!(
"Sending mail{}\n{}",
if config.mock_mails { " (MOCK)" } else { "" },
headers.clone()
);
if config.mock_mails {
true
} else {
let recipient_addresses = recipients
.clone()
.into_iter()
.map(|r| r.address)
.collect::<Vec<String>>();
// https://github.com/NixOS/nixpkgs/issues/90248
let mut command = Command::new("/run/wrappers/bin/sendmail");
command.kill_on_drop(true);
command.arg("-f").arg(FROM_ADDRESS);
command.arg("--").args(recipient_addresses);
command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let message = format!("{}\n\n{}", headers, message);
match spawn(command, &message.into_bytes()).await {
Ok(output) => {
if output.status.success() {
log::info!("Mail sent");
true
} else {
match String::from_utf8(output.stderr) {
Ok(error) => log::error!("Error sending email: {}", error),
_ => log::error!("Error sending email"),
};
false
}
}
Err(err) => {
log::error!("Error spawning command: {:?}", err);
false
}
}
}
}
fn format_headers(recipients: Vec<Recipient>, subject: String) -> String {
let recipients = recipients
.into_iter()
.map(|r| format_address(r.name, r.address))
.collect::<Vec<String>>()
.join(", ");
format!(
"Date: {}\nFrom: {}\nTo: {}\nSubject: {}",
Utc::now().to_rfc2822(),
format_address(FROM_NAME.to_string(), FROM_ADDRESS.to_string()),
recipients,
subject,
)
}
fn format_address(name: String, address: String) -> String {
format!("{} <{}>", name, address)
}
async fn spawn(mut command: Command, stdin: &[u8]) -> Result<Output, Error> {
let mut process = command.spawn()?;
process
.stdin
.as_mut()
.ok_or(Error::new(ErrorKind::Other, "Getting mutable stdin"))?
.write_all(stdin)
.await?;
process.wait_with_output().await
}
|