1use milter::{
5 on_abort, on_body, on_close, on_data, on_eoh, on_eom, on_header, on_mail, on_negotiate,
6 on_unknown, Actions, Context, Error, Milter, ProtocolOpts, Status,
7};
8use once_cell::sync::Lazy;
9use std::{
10 env,
11 fs::{self, File, OpenOptions},
12 io::Write,
13 path::PathBuf,
14 process,
15 sync::Mutex,
16 time::SystemTime,
17};
18
19struct Message {
20 path: PathBuf,
21 file: File,
22}
23
24static MTA_CAPS: Lazy<Mutex<ProtocolOpts>> = Lazy::new(|| Mutex::new(ProtocolOpts::empty()));
25
26fn set_mta_caps(protocol_opts: ProtocolOpts) {
27 *MTA_CAPS.lock().unwrap() = protocol_opts;
28}
29
30fn mta_caps() -> ProtocolOpts {
31 *MTA_CAPS.lock().unwrap()
32}
33
34#[on_negotiate(negotiate_callback)]
35fn handle_negotiate(
36 _: Context<Message>,
37 _: Actions,
38 protocol_opts: ProtocolOpts,
39) -> (Status, Actions, ProtocolOpts) {
40 set_mta_caps(protocol_opts);
41
42 let actions = Actions::ADD_HEADER;
43
44 let mut protocol_opts =
45 ProtocolOpts::NO_CONNECT | ProtocolOpts::NO_HELO | ProtocolOpts::NO_RCPT;
46 if mta_caps().contains(ProtocolOpts::NOREPLY_HEADER) {
47 protocol_opts |= ProtocolOpts::NOREPLY_HEADER;
48 }
49
50 (Status::Continue, actions, protocol_opts)
51}
52
53#[on_mail(mail_callback)]
54fn handle_mail(mut ctx: Context<Message>, _: Vec<&str>) -> milter::Result<Status> {
55 let epoch_secs = SystemTime::now()
56 .duration_since(SystemTime::UNIX_EPOCH)
57 .expect("system time out of range")
58 .as_secs();
59 let path = PathBuf::from(format!("/tmp/msg.{}", epoch_secs));
60
61 let file = match OpenOptions::new()
62 .write(true)
63 .truncate(true)
64 .create(true)
65 .open(&path)
66 {
67 Ok(file) => file,
68 Err(_) => return Ok(Status::Tempfail),
69 };
70
71 ctx.data.replace(Message { path, file })?;
72
73 Ok(Status::Continue)
74}
75
76#[on_data(data_callback)]
77fn handle_data(_: Context<Message>) -> Status {
78 Status::Continue
79}
80
81#[on_header(header_callback)]
82fn handle_header(mut ctx: Context<Message>, name: &str, value: &str) -> milter::Result<Status> {
83 let msg = ctx.data.borrow_mut().unwrap();
84
85 write!(&mut msg.file, "{}: {}\r\n", name, value).map_err(|e| Error::Custom(e.into()))?;
86
87 Ok(if mta_caps().contains(ProtocolOpts::NOREPLY_HEADER) {
88 Status::Noreply
89 } else {
90 Status::Continue
91 })
92}
93
94#[on_eoh(eoh_callback)]
95fn handle_eoh(mut ctx: Context<Message>) -> milter::Result<Status> {
96 let msg = ctx.data.borrow_mut().unwrap();
97
98 write!(&mut msg.file, "\r\n").map_err(|e| Error::Custom(e.into()))?;
99
100 Ok(Status::Continue)
101}
102
103#[on_body(body_callback)]
104fn handle_body(mut ctx: Context<Message>, content: &[u8]) -> milter::Result<Status> {
105 let msg = ctx.data.borrow_mut().unwrap();
106
107 if msg.file.write_all(content).is_err() {
108 cleanup(ctx)?;
109 return Ok(Status::Tempfail);
110 }
111
112 Ok(Status::Continue)
113}
114
115#[on_eom(eom_callback)]
116fn handle_eom(mut ctx: Context<Message>) -> milter::Result<Status> {
117 let msg = ctx.data.take()?.unwrap();
118
119 let path = msg.path.to_str().expect("invalid characters in path");
120
121 ctx.api.add_header("X-Archived", path)?;
122
123 Ok(Status::Continue)
124}
125
126#[on_abort(abort_callback)]
127fn handle_abort(ctx: Context<Message>) -> milter::Result<Status> {
128 cleanup(ctx)?;
129
130 Ok(Status::Continue)
131}
132
133fn cleanup(mut ctx: Context<Message>) -> milter::Result<()> {
134 if let Some(msg) = ctx.data.take()? {
135 fs::remove_file(msg.path).map_err(|e| Error::Custom(e.into()))?;
136 }
137 Ok(())
138}
139
140#[on_close(close_callback)]
141fn handle_close(_: Context<Message>) -> Status {
142 Status::Accept
143}
144
145#[on_unknown(unknown_callback)]
146fn handle_unknown(_: Context<Message>, _: &str) -> Status {
147 Status::Continue
148}
149
150fn main() {
151 let args = env::args().collect::<Vec<_>>();
152
153 if args.len() != 3 || args[1] != "-p" {
154 eprintln!("usage: {} -p <socket>", args[0]);
155 process::exit(1);
156 }
157
158 if let Err(e) = Milter::new(&args[2])
159 .name("SampleFilter")
160 .on_negotiate(negotiate_callback)
161 .on_mail(mail_callback)
162 .on_data(data_callback)
163 .on_header(header_callback)
164 .on_eoh(eoh_callback)
165 .on_body(body_callback)
166 .on_eom(eom_callback)
167 .on_abort(abort_callback)
168 .on_close(close_callback)
169 .on_unknown(unknown_callback)
170 .actions(Actions::ADD_HEADER)
171 .run()
172 {
173 eprintln!("milter execution failed: {}", e);
174 process::exit(1);
175 }
176}