1mod auth_results;
27mod callbacks;
28mod config;
29mod format;
30mod resolver;
31mod session;
32mod sign;
33mod util;
34mod verify;
35
36pub use crate::{
37 config::{
38 model::{
39 LogDestination, LogLevel, ParseLogDestinationError, ParseLogLevelError,
40 ParseSocketError, ParseSyslogFacilityError, Socket, SyslogFacility,
41 },
42 CliOptions,
43 },
44 resolver::LookupFuture,
45};
46
47use crate::{
48 config::{LogConfig, SessionConfig},
49 resolver::MockLookupTxt,
50};
51use indymilter::IntoListener;
52use log::{error, info, LevelFilter, Log, Metadata, Record, SetLoggerError};
53use std::{
54 error::Error,
55 future::Future,
56 io::{self, stderr, ErrorKind, Write},
57 sync::{Arc, RwLock},
58};
59use tokio::sync::mpsc;
60
61pub const MILTER_NAME: &str = "DKIM Milter";
63
64pub const VERSION: &str = env!("CARGO_PKG_VERSION");
66
67pub struct StubConfig {
69 opts: CliOptions,
70 log_config: LogConfig,
71 config_file_content: String,
72}
73
74impl StubConfig {
75 pub async fn read(opts: CliOptions) -> Result<Self, Box<dyn Error + 'static>> {
76 let (log_config, config_file_content) = match LogConfig::read(&opts).await {
77 Ok(config) => config,
78 Err(e) => return Err(Box::new(e)),
79 };
80
81 Ok(Self {
82 opts,
83 log_config,
84 config_file_content,
85 })
86 }
87
88 pub fn install_static_logger(&self) -> Result<(), Box<dyn Error + 'static>> {
89 configure_logging(&self.log_config)?;
90 Ok(())
91 }
92
93 pub async fn read_fully(self) -> Result<Config, Box<dyn Error + 'static>> {
94 let StubConfig { opts, log_config, config_file_content } = self;
95 Config::read_fully(opts, log_config, &config_file_content, None).await
96 }
97
98 pub async fn read_fully_with_lookup(
99 self,
100 lookup: impl Fn(&str) -> LookupFuture + Send + Sync + 'static,
101 ) -> Result<Config, Box<dyn Error + 'static>> {
102 let StubConfig { opts, log_config, config_file_content } = self;
103 let lookup = Arc::new(lookup);
104 Config::read_fully(opts, log_config, &config_file_content, Some(lookup)).await
105 }
106}
107
108pub struct Config {
109 cli_opts: CliOptions,
110 config: config::Config,
111 mock_resolver: Option<MockLookupTxt>,
112}
113
114impl Config {
117 pub async fn read(opts: CliOptions) -> Result<Self, Box<dyn Error + 'static>> {
118 Self::read_internal(opts, None).await
119 }
120
121 pub async fn read_with_lookup(
122 opts: CliOptions,
123 lookup: impl Fn(&str) -> LookupFuture + Send + Sync + 'static,
124 ) -> Result<Self, Box<dyn Error + 'static>> {
125 let lookup = Arc::new(lookup);
126 Self::read_internal(opts, Some(lookup)).await
127 }
128
129 async fn read_internal(
130 opts: CliOptions,
131 mock_resolver: Option<Arc<dyn Fn(&str) -> LookupFuture + Send + Sync>>,
132 ) -> Result<Self, Box<dyn Error + 'static>> {
133 let config = StubConfig::read(opts).await?;
134
135 config.install_static_logger()?;
136
137 let StubConfig { opts, log_config, config_file_content } = config;
140
141 Self::read_fully(opts, log_config, &config_file_content, mock_resolver).await
142 }
143
144 async fn read_fully(
145 opts: CliOptions,
146 log_config: LogConfig,
147 config_file_content: &str,
148 mock_resolver: Option<Arc<dyn Fn(&str) -> LookupFuture + Send + Sync>>,
149 ) -> Result<Self, Box<dyn Error + 'static>> {
150 let config = match config::Config::read_with_log_config(
151 &opts,
152 log_config,
153 config_file_content,
154 )
155 .await
156 {
157 Ok(config) => config,
158 Err(e) => {
159 return Err(Box::new(e));
160 }
161 };
162
163 let mock_resolver = mock_resolver.map(MockLookupTxt::new);
164
165 Ok(Self {
166 cli_opts: opts,
167 config,
168 mock_resolver,
169 })
170 }
171
172 pub fn socket(&self) -> &Socket {
173 &self.config.socket
174 }
175}
176
177fn configure_logging(config: &LogConfig) -> Result<(), Box<dyn Error + 'static>> {
178 let level = match config.log_level {
179 LogLevel::Error => LevelFilter::Error,
180 LogLevel::Warn => LevelFilter::Warn,
181 LogLevel::Info => LevelFilter::Info,
182 LogLevel::Debug => LevelFilter::Debug,
183 };
184
185 match config.log_destination {
186 LogDestination::Syslog => {
187 syslog::init_unix(config.syslog_facility.into(), level).map_err(|e| {
188 io::Error::new(
189 ErrorKind::Other,
190 format!("could not initialize syslog: {e}"),
191 )
192 })?;
193 }
194 LogDestination::Stderr => {
195 StderrLog::init(level).map_err(|e| {
196 io::Error::new(
197 ErrorKind::Other,
198 format!("could not initialize stderr log: {e}"),
199 )
200 })?;
201 }
202 }
203
204 Ok(())
205}
206
207pub async fn run(
208 listener: impl IntoListener,
209 config: Config,
210 reload: mpsc::Receiver<()>,
211 shutdown: impl Future,
212) -> io::Result<()> {
213 let Config { cli_opts, config, mock_resolver } = config;
214
215 let session_config = match mock_resolver {
216 Some(resolver) => SessionConfig::with_mock_resolver(config, resolver),
217 None => SessionConfig::new(config),
218 };
219 let session_config = Arc::new(RwLock::new(Arc::new(session_config)));
220
221 spawn_reload_task(session_config.clone(), cli_opts, reload);
222
223 let callbacks = callbacks::make_callbacks(session_config);
224 let config = Default::default();
225
226 info!("{MILTER_NAME} {VERSION} starting");
227
228 let result = indymilter::run(listener, callbacks, config, shutdown).await;
229
230 match &result {
231 Ok(()) => info!("{MILTER_NAME} {VERSION} shut down"),
232 Err(e) => error!("{MILTER_NAME} {VERSION} terminated with error: {e}"),
233 }
234
235 result
236}
237
238fn spawn_reload_task(
239 session_config: Arc<RwLock<Arc<SessionConfig>>>,
240 opts: CliOptions,
241 mut reload: mpsc::Receiver<()>,
242) {
243 tokio::spawn(async move {
244 while let Some(()) = reload.recv().await {
245 config::reload(&session_config, &opts).await;
246 }
247 });
248}
249
250struct StderrLog {
252 level: LevelFilter,
253}
254
255impl StderrLog {
256 fn init<L: Into<LevelFilter>>(level: L) -> Result<(), SetLoggerError> {
257 let level = level.into();
258 log::set_boxed_logger(Box::new(Self { level }))
259 .map(|_| log::set_max_level(level))
260 }
261}
262
263impl Log for StderrLog {
264 fn enabled(&self, metadata: &Metadata) -> bool {
265 metadata.level() <= self.level
266 }
267
268 fn log(&self, record: &Record) {
269 if self.enabled(record.metadata()) {
270 let _ = writeln!(stderr(), "{}", record.args());
271 }
272 }
273
274 fn flush(&self) {}
275}