dkim_milter/config/
model.rs

1// DKIM Milter – milter for DKIM signing and verification
2// Copyright © 2022–2023 David Bürgin <dbuergin@gluet.ch>
3//
4// This program is free software: you can redistribute it and/or modify it under
5// the terms of the GNU General Public License as published by the Free Software
6// Foundation, either version 3 of the License, or (at your option) any later
7// version.
8//
9// This program is distributed in the hope that it will be useful, but WITHOUT
10// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12// details.
13//
14// You should have received a copy of the GNU General Public License along with
15// this program. If not, see <https://www.gnu.org/licenses/>.
16
17use ipnet::IpNet;
18use regex::Regex;
19use std::{
20    collections::HashSet,
21    error::Error,
22    fmt::{self, Debug, Display, Formatter},
23    net::{IpAddr, Ipv4Addr, Ipv6Addr},
24    str::FromStr,
25    sync::Arc,
26    time::Duration,
27};
28use syslog::Facility;
29use viadkim::{
30    crypto::{HashAlgorithm, SigningKey},
31    header::{FieldName, HeaderFieldError},
32    signature::{Canonicalization, CanonicalizationAlgorithm, DomainName, Selector},
33    signer,
34};
35
36// Provide FromStr impl only for types that have an ‘atomic’, ‘natural’, obvious
37// string representation.
38
39#[derive(Clone, Debug, Default)]
40pub struct ConfigOverrides {
41    pub signing_config: PartialSigningConfig,
42    pub verification_config: PartialVerificationConfig,
43}
44
45impl ConfigOverrides {
46    pub fn merge(&mut self, other: &Self) {
47        self.signing_config.merge(&other.signing_config);
48        self.verification_config.merge(&other.verification_config);
49    }
50}
51
52#[derive(Clone, Debug, PartialEq)]
53pub struct SigningConfig {
54    pub ascii_only_signatures: bool,
55    pub canonicalization: Canonicalization,
56    pub copy_headers: bool,
57    pub default_signed_headers: Vec<SignedFieldName>,  // must include From
58    pub default_unsigned_headers: Vec<SignedFieldName>,  // must not include From
59    pub expiration: Expiration,
60    pub hash_algorithm: HashAlgorithm,
61    pub limit_body_length: bool,
62    pub oversign_headers: OversignedHeaders,
63    pub request_reports: bool,
64    pub sign_headers: SignedHeaders,
65}
66
67impl SigningConfig {
68    fn check_invariants(&self) -> Result<(), Box<dyn Error>> {
69        match (&self.oversign_headers, &self.sign_headers) {
70            (
71                OversignedHeaders::Pick(oversigned_names),
72                s @ (SignedHeaders::Pick(names) | SignedHeaders::PickWithDefault(names)),
73            ) => {
74                let mut all_signed: HashSet<_> = names.iter().collect();
75                if matches!(s, SignedHeaders::PickWithDefault(_)) {
76                    all_signed.extend(self.default_signed_headers.iter());
77                }
78                for h in oversigned_names {
79                    if !all_signed.contains(h) {
80                        return Err("cannot oversign header not included for signing".into());
81                    }
82                }
83            }
84            (OversignedHeaders::Pick(oversigned_names), SignedHeaders::All) => {
85                for h in oversigned_names {
86                    if self.default_unsigned_headers.contains(h) {
87                        return Err("cannot oversign header expressly excluded from signing".into());
88                    }
89                }
90            }
91            (OversignedHeaders::Extended, SignedHeaders::All) => {
92                for h in &self.default_signed_headers {
93                    if self.default_unsigned_headers.contains(h) {
94                        return Err("cannot oversign header expressly excluded from signing".into());
95                    }
96                }
97            }
98            _ => {}
99        }
100        Ok(())
101    }
102
103    pub fn merged_with(&self, overrides: &PartialSigningConfig) -> Result<Self, Box<dyn Error>> {
104        let mut config = self.clone();
105
106        if let Some(value) = overrides.ascii_only_signatures {
107            config.ascii_only_signatures = value;
108        }
109        if let Some(value) = overrides.canonicalization {
110            config.canonicalization = value;
111        }
112        if let Some(value) = overrides.copy_headers {
113            config.copy_headers = value;
114        }
115        if let Some(value) = &overrides.default_signed_headers {
116            config.default_signed_headers = value.as_ref().clone();
117        }
118        if let Some(value) = &overrides.default_unsigned_headers {
119            config.default_unsigned_headers = value.as_ref().clone();
120        }
121        if let Some(value) = overrides.expiration {
122            config.expiration = value;
123        }
124        if let Some(value) = overrides.hash_algorithm {
125            config.hash_algorithm = value;
126        }
127        if let Some(value) = overrides.limit_body_length {
128            config.limit_body_length = value;
129        }
130        if let Some(value) = &overrides.oversign_headers {
131            config.oversign_headers = value.as_ref().clone();
132        }
133        if let Some(value) = overrides.request_reports {
134            config.request_reports = value;
135        }
136        if let Some(value) = &overrides.sign_headers {
137            config.sign_headers = value.as_ref().clone();
138        }
139
140        config.check_invariants()?;
141
142        Ok(config)
143    }
144}
145
146impl Default for SigningConfig {
147    fn default() -> Self {
148        use CanonicalizationAlgorithm::*;
149        Self {
150            ascii_only_signatures: false,
151            canonicalization: Canonicalization::from((Relaxed, Simple)),
152            copy_headers: false,
153            default_signed_headers: signer::default_signed_headers().into_iter()
154                .map(SignedFieldName)
155                .collect(),
156            default_unsigned_headers: signer::default_unsigned_headers().into_iter()
157                .map(SignedFieldName)
158                .collect(),
159            expiration: Expiration::After(Duration::from_secs(60 * 60 * 24 * 5)),  // five days
160            hash_algorithm: HashAlgorithm::Sha256,
161            limit_body_length: false,
162            oversign_headers: OversignedHeaders::Pick(Default::default()),
163            request_reports: false,
164            sign_headers: SignedHeaders::PickWithDefault(Default::default()),
165        }
166    }
167}
168
169#[derive(Clone, Debug, Default, PartialEq)]
170pub struct PartialSigningConfig {
171    pub ascii_only_signatures: Option<bool>,
172    pub canonicalization: Option<Canonicalization>,
173    pub copy_headers: Option<bool>,
174    pub default_signed_headers: Option<Arc<Vec<SignedFieldName>>>,
175    pub default_unsigned_headers: Option<Arc<Vec<SignedFieldName>>>,
176    pub expiration: Option<Expiration>,
177    pub hash_algorithm: Option<HashAlgorithm>,
178    pub limit_body_length: Option<bool>,
179    pub oversign_headers: Option<Arc<OversignedHeaders>>,
180    pub request_reports: Option<bool>,
181    pub sign_headers: Option<Arc<SignedHeaders>>,
182}
183
184impl PartialSigningConfig {
185    pub fn merged_with(&self, overrides: &Self) -> Self {
186        Self {
187            ascii_only_signatures: overrides.ascii_only_signatures.or(self.ascii_only_signatures),
188            canonicalization: overrides.canonicalization.or(self.canonicalization),
189            copy_headers: overrides.copy_headers.or(self.copy_headers),
190            default_signed_headers: overrides.default_signed_headers.as_ref()
191                .or(self.default_signed_headers.as_ref())
192                .cloned(),
193            default_unsigned_headers: overrides.default_unsigned_headers.as_ref()
194                .or(self.default_unsigned_headers.as_ref())
195                .cloned(),
196            expiration: overrides.expiration.or(self.expiration),
197            hash_algorithm: overrides.hash_algorithm.or(self.hash_algorithm),
198            limit_body_length: overrides.limit_body_length.or(self.limit_body_length),
199            oversign_headers: overrides.oversign_headers.as_ref()
200                .or(self.oversign_headers.as_ref())
201                .cloned(),
202            request_reports: overrides.request_reports.or(self.request_reports),
203            sign_headers: overrides.sign_headers.as_ref()
204                .or(self.sign_headers.as_ref())
205                .cloned(),
206        }
207    }
208
209    pub fn merge(&mut self, other: &Self) {
210        if let Some(value) = other.ascii_only_signatures {
211            self.ascii_only_signatures = Some(value);
212        }
213        if let Some(value) = other.canonicalization {
214            self.canonicalization = Some(value);
215        }
216        if let Some(value) = other.copy_headers {
217            self.copy_headers = Some(value);
218        }
219        if let Some(value) = &other.default_signed_headers {
220            self.default_signed_headers = Some(value.clone());
221        }
222        if let Some(value) = &other.default_unsigned_headers {
223            self.default_unsigned_headers = Some(value.clone());
224        }
225        if let Some(value) = other.expiration {
226            self.expiration = Some(value);
227        }
228        if let Some(value) = other.hash_algorithm {
229            self.hash_algorithm = Some(value);
230        }
231        if let Some(value) = other.limit_body_length {
232            self.limit_body_length = Some(value);
233        }
234        if let Some(value) = &other.oversign_headers {
235            self.oversign_headers = Some(value.clone());
236        }
237        if let Some(value) = other.request_reports {
238            self.request_reports = Some(value);
239        }
240        if let Some(value) = &other.sign_headers {
241            self.sign_headers = Some(value.clone());
242        }
243    }
244
245    pub fn into_signing_config(self) -> Result<SigningConfig, Box<dyn Error>> {
246        let mut config = SigningConfig::default();
247
248        if let Some(value) = self.ascii_only_signatures {
249            config.ascii_only_signatures = value;
250        }
251        if let Some(value) = self.canonicalization {
252            config.canonicalization = value;
253        }
254        if let Some(value) = self.copy_headers {
255            config.copy_headers = value;
256        }
257        if let Some(value) = self.default_signed_headers {
258            config.default_signed_headers = unwrap_arc(value);
259        }
260        if let Some(value) = self.default_unsigned_headers {
261            config.default_unsigned_headers = unwrap_arc(value);
262        }
263        if let Some(value) = self.expiration {
264            config.expiration = value;
265        }
266        if let Some(value) = self.hash_algorithm {
267            config.hash_algorithm = value;
268        }
269        if let Some(value) = self.limit_body_length {
270            config.limit_body_length = value;
271        }
272        if let Some(value) = self.oversign_headers {
273            config.oversign_headers = unwrap_arc(value);
274        }
275        if let Some(value) = self.request_reports {
276            config.request_reports = value;
277        }
278        if let Some(value) = self.sign_headers {
279            config.sign_headers = unwrap_arc(value);
280        }
281
282        config.check_invariants()?;
283
284        Ok(config)
285    }
286}
287
288#[derive(Clone, Debug, PartialEq)]
289pub struct VerificationConfig {
290    pub allow_expired: bool,
291    pub allow_sha1: bool,
292    pub allow_timestamp_in_future: bool,
293    pub forbid_unsigned_content: bool,
294    pub max_signatures_to_verify: usize,
295    pub min_rsa_key_bits: usize,
296    pub reject_failures: RejectFailures,
297    pub required_signed_headers: Vec<SignedFieldNameWithQualifier>,
298    pub time_tolerance: Duration,
299}
300
301impl VerificationConfig {
302    pub fn merged_with(&self, overrides: &PartialVerificationConfig) -> Self {
303        let mut config = self.clone();
304
305        if let Some(value) = overrides.allow_expired {
306            config.allow_expired = value;
307        }
308        if let Some(value) = overrides.allow_sha1 {
309            config.allow_sha1 = value;
310        }
311        if let Some(value) = overrides.allow_timestamp_in_future {
312            config.allow_timestamp_in_future = value;
313        }
314        if let Some(value) = overrides.forbid_unsigned_content {
315            config.forbid_unsigned_content = value;
316        }
317        if let Some(value) = overrides.max_signatures_to_verify {
318            config.max_signatures_to_verify = value;
319        }
320        if let Some(value) = overrides.min_rsa_key_bits {
321            config.min_rsa_key_bits = value;
322        }
323        if let Some(value) = &overrides.reject_failures {
324            config.reject_failures = value.as_ref().clone();
325        }
326        if let Some(value) = &overrides.required_signed_headers {
327            config.required_signed_headers = value.as_ref().clone();
328        }
329        if let Some(value) = overrides.time_tolerance {
330            config.time_tolerance = value;
331        }
332
333        config
334    }
335}
336
337impl Default for VerificationConfig {
338    fn default() -> Self {
339        Self {
340            allow_expired: false,
341            allow_sha1: false,
342            allow_timestamp_in_future: false,
343            forbid_unsigned_content: false,
344            max_signatures_to_verify: 10,
345            min_rsa_key_bits: 1024,
346            reject_failures: Default::default(),
347            required_signed_headers: vec![SignedFieldNameWithQualifier::Asterisk(
348                SignedFieldName::new("From").unwrap(),
349            )],
350            time_tolerance: Duration::from_secs(5 * 60),
351        }
352    }
353}
354
355#[derive(Clone, Debug, Default, PartialEq)]
356pub struct PartialVerificationConfig {
357    pub allow_expired: Option<bool>,
358    pub allow_sha1: Option<bool>,
359    pub allow_timestamp_in_future: Option<bool>,
360    pub forbid_unsigned_content: Option<bool>,
361    pub max_signatures_to_verify: Option<usize>,
362    pub min_rsa_key_bits: Option<usize>,
363    pub reject_failures: Option<Arc<RejectFailures>>,
364    pub required_signed_headers: Option<Arc<Vec<SignedFieldNameWithQualifier>>>,
365    pub time_tolerance: Option<Duration>,
366}
367
368impl PartialVerificationConfig {
369    pub fn merged_with(&self, overrides: &Self) -> Self {
370        Self {
371            allow_expired: overrides.allow_expired.or(self.allow_expired),
372            allow_sha1: overrides.allow_sha1.or(self.allow_sha1),
373            allow_timestamp_in_future: overrides.allow_timestamp_in_future.or(self.allow_timestamp_in_future),
374            forbid_unsigned_content: overrides.forbid_unsigned_content.or(self.forbid_unsigned_content),
375            max_signatures_to_verify: overrides.max_signatures_to_verify.or(self.max_signatures_to_verify),
376            min_rsa_key_bits: overrides.min_rsa_key_bits.or(self.min_rsa_key_bits),
377            reject_failures: overrides.reject_failures.as_ref()
378                .or(self.reject_failures.as_ref())
379                .cloned(),
380            required_signed_headers: overrides.required_signed_headers.as_ref()
381                .or(self.required_signed_headers.as_ref())
382                .cloned(),
383            time_tolerance: overrides.time_tolerance.or(self.time_tolerance),
384        }
385    }
386
387    pub fn merge(&mut self, other: &Self) {
388        if let Some(value) = other.allow_expired {
389            self.allow_expired = Some(value);
390        }
391        if let Some(value) = other.allow_sha1 {
392            self.allow_sha1 = Some(value);
393        }
394        if let Some(value) = other.allow_timestamp_in_future {
395            self.allow_timestamp_in_future = Some(value);
396        }
397        if let Some(value) = other.forbid_unsigned_content {
398            self.forbid_unsigned_content = Some(value);
399        }
400        if let Some(value) = other.max_signatures_to_verify {
401            self.max_signatures_to_verify = Some(value);
402        }
403        if let Some(value) = other.min_rsa_key_bits {
404            self.min_rsa_key_bits = Some(value);
405        }
406        if let Some(value) = &other.reject_failures {
407            self.reject_failures = Some(value.clone());
408        }
409        if let Some(value) = &other.required_signed_headers {
410            self.required_signed_headers = Some(value.clone());
411        }
412        if let Some(value) = other.time_tolerance {
413            self.time_tolerance = Some(value);
414        }
415    }
416
417    pub fn into_verification_config(self) -> VerificationConfig {
418        let mut config = VerificationConfig::default();
419
420        if let Some(value) = self.allow_expired {
421            config.allow_expired = value;
422        }
423        if let Some(value) = self.allow_sha1 {
424            config.allow_sha1 = value;
425        }
426        if let Some(value) = self.allow_timestamp_in_future {
427            config.allow_timestamp_in_future = value;
428        }
429        if let Some(value) = self.forbid_unsigned_content {
430            config.forbid_unsigned_content = value;
431        }
432        if let Some(value) = self.max_signatures_to_verify {
433            config.max_signatures_to_verify = value;
434        }
435        if let Some(value) = self.min_rsa_key_bits {
436            config.min_rsa_key_bits = value;
437        }
438        if let Some(value) = self.reject_failures {
439            config.reject_failures = unwrap_arc(value);
440        }
441        if let Some(value) = self.required_signed_headers {
442            config.required_signed_headers = unwrap_arc(value);
443        }
444        if let Some(value) = self.time_tolerance {
445            config.time_tolerance = value;
446        }
447
448        config
449    }
450}
451
452fn unwrap_arc<T: Clone>(arc: Arc<T>) -> T {
453    Arc::try_unwrap(arc).unwrap_or_else(|a| a.as_ref().clone())
454}
455
456#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
457pub struct ParseLogDestinationError;
458
459impl Error for ParseLogDestinationError {}
460
461impl Display for ParseLogDestinationError {
462    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
463        write!(f, "failed to parse log destination")
464    }
465}
466
467#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
468pub enum LogDestination {
469    #[default]
470    Syslog,
471    Stderr,
472}
473
474impl FromStr for LogDestination {
475    type Err = ParseLogDestinationError;
476
477    fn from_str(s: &str) -> Result<Self, Self::Err> {
478        match s {
479            "syslog" => Ok(Self::Syslog),
480            "stderr" => Ok(Self::Stderr),
481            _ => Err(ParseLogDestinationError),
482        }
483    }
484}
485
486#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
487pub struct ParseLogLevelError;
488
489impl Error for ParseLogLevelError {}
490
491impl Display for ParseLogLevelError {
492    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
493        write!(f, "failed to parse log level")
494    }
495}
496
497#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
498pub enum LogLevel {
499    Error,
500    Warn,
501    #[default]
502    Info,
503    Debug,
504}
505
506impl FromStr for LogLevel {
507    type Err = ParseLogLevelError;
508
509    fn from_str(s: &str) -> Result<Self, Self::Err> {
510        match s {
511            "error" => Ok(Self::Error),
512            "warn" => Ok(Self::Warn),
513            "info" => Ok(Self::Info),
514            "debug" => Ok(Self::Debug),
515            _ => Err(ParseLogLevelError),
516        }
517    }
518}
519
520/// An error indicating that a syslog facility could not be parsed.
521#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
522pub struct ParseSyslogFacilityError;
523
524impl Error for ParseSyslogFacilityError {}
525
526impl Display for ParseSyslogFacilityError {
527    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
528        write!(f, "failed to parse syslog facility")
529    }
530}
531
532/// The syslog facility.
533#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
534pub enum SyslogFacility {
535    Auth,
536    Authpriv,
537    Cron,
538    Daemon,
539    Ftp,
540    Kern,
541    Local0,
542    Local1,
543    Local2,
544    Local3,
545    Local4,
546    Local5,
547    Local6,
548    Local7,
549    Lpr,
550    #[default]
551    Mail,
552    News,
553    Syslog,
554    User,
555    Uucp,
556}
557
558impl FromStr for SyslogFacility {
559    type Err = ParseSyslogFacilityError;
560
561    fn from_str(s: &str) -> Result<Self, Self::Err> {
562        match s {
563            "auth" => Ok(Self::Auth),
564            "authpriv" => Ok(Self::Authpriv),
565            "cron" => Ok(Self::Cron),
566            "daemon" => Ok(Self::Daemon),
567            "ftp" => Ok(Self::Ftp),
568            "kern" => Ok(Self::Kern),
569            "local0" => Ok(Self::Local0),
570            "local1" => Ok(Self::Local1),
571            "local2" => Ok(Self::Local2),
572            "local3" => Ok(Self::Local3),
573            "local4" => Ok(Self::Local4),
574            "local5" => Ok(Self::Local5),
575            "local6" => Ok(Self::Local6),
576            "local7" => Ok(Self::Local7),
577            "lpr" => Ok(Self::Lpr),
578            "mail" => Ok(Self::Mail),
579            "news" => Ok(Self::News),
580            "syslog" => Ok(Self::Syslog),
581            "user" => Ok(Self::User),
582            "uucp" => Ok(Self::Uucp),
583            _ => Err(ParseSyslogFacilityError),
584        }
585    }
586}
587
588impl From<SyslogFacility> for Facility {
589    fn from(syslog_facility: SyslogFacility) -> Self {
590        match syslog_facility {
591            SyslogFacility::Auth => Self::LOG_AUTH,
592            SyslogFacility::Authpriv => Self::LOG_AUTHPRIV,
593            SyslogFacility::Cron => Self::LOG_CRON,
594            SyslogFacility::Daemon => Self::LOG_DAEMON,
595            SyslogFacility::Ftp => Self::LOG_FTP,
596            SyslogFacility::Kern => Self::LOG_KERN,
597            SyslogFacility::Local0 => Self::LOG_LOCAL0,
598            SyslogFacility::Local1 => Self::LOG_LOCAL1,
599            SyslogFacility::Local2 => Self::LOG_LOCAL2,
600            SyslogFacility::Local3 => Self::LOG_LOCAL3,
601            SyslogFacility::Local4 => Self::LOG_LOCAL4,
602            SyslogFacility::Local5 => Self::LOG_LOCAL5,
603            SyslogFacility::Local6 => Self::LOG_LOCAL6,
604            SyslogFacility::Local7 => Self::LOG_LOCAL7,
605            SyslogFacility::Lpr => Self::LOG_LPR,
606            SyslogFacility::Mail => Self::LOG_MAIL,
607            SyslogFacility::News => Self::LOG_NEWS,
608            SyslogFacility::Syslog => Self::LOG_SYSLOG,
609            SyslogFacility::User => Self::LOG_USER,
610            SyslogFacility::Uucp => Self::LOG_UUCP,
611        }
612    }
613}
614
615#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
616pub struct ParseSocketError;
617
618impl Error for ParseSocketError {}
619
620impl Display for ParseSocketError {
621    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
622        write!(f, "failed to parse socket")
623    }
624}
625
626#[derive(Clone, Debug, Eq, Hash, PartialEq)]
627pub enum Socket {
628    Inet(String),
629    Unix(String),
630}
631
632impl Display for Socket {
633    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
634        match self {
635            Self::Inet(s) => write!(f, "inet:{s}"),
636            Self::Unix(s) => write!(f, "unix:{s}"),
637        }
638    }
639}
640
641impl FromStr for Socket {
642    type Err = ParseSocketError;
643
644    fn from_str(s: &str) -> Result<Self, Self::Err> {
645        if let Some(s) = s.strip_prefix("inet:") {
646            Ok(Self::Inet(s.into()))
647        } else if let Some(s) = s.strip_prefix("unix:") {
648            Ok(Self::Unix(s.into()))
649        } else {
650            Err(ParseSocketError)
651        }
652    }
653}
654
655#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
656pub enum OpMode {
657    Sign,
658    Verify,
659    #[default]
660    Auto,
661}
662
663impl FromStr for OpMode {
664    type Err = &'static str;
665
666    fn from_str(s: &str) -> Result<Self, Self::Err> {
667        match s {
668            "sign" => Ok(Self::Sign),
669            "verify" => Ok(Self::Verify),
670            "auto" => Ok(Self::Auto),
671            _ => Err("unknown mode"),
672        }
673    }
674}
675
676#[derive(Clone, Debug, Eq, PartialEq)]
677pub struct TrustedNetworks {
678    pub trust_loopback: bool,
679    pub networks: HashSet<IpNet>,
680}
681
682impl TrustedNetworks {
683    pub fn contains(&self, addr: IpAddr) -> bool {
684        (self.trust_loopback && addr.is_loopback())
685            || self.networks.iter().any(|n| n.contains(&addr))
686    }
687
688    pub fn contains_loopback(&self) -> bool {
689        // Also do a rudimentary check if a well-known loopback address is
690        // contained in `self.networks`.
691        self.trust_loopback
692            || self.networks.iter().any(|n| {
693                n.contains(&IpAddr::from(Ipv4Addr::LOCALHOST))
694                    || n.contains(&IpAddr::from(Ipv6Addr::LOCALHOST))
695            })
696    }
697}
698
699impl Default for TrustedNetworks {
700    fn default() -> Self {
701        Self {
702            trust_loopback: true,
703            networks: Default::default(),
704        }
705    }
706}
707
708// like viadkim's FieldName but does not allow ";" in name
709#[derive(Clone, Eq, Hash, PartialEq)]
710pub struct SignedFieldName(FieldName);
711
712impl SignedFieldName {
713    pub fn new(value: impl Into<Box<str>>) -> Result<Self, HeaderFieldError> {
714        let name = FieldName::new(value)?;
715        if name.as_ref().contains(';') {
716            return Err(HeaderFieldError);
717        }
718        Ok(Self(name))
719    }
720}
721
722impl AsRef<FieldName> for SignedFieldName {
723    fn as_ref(&self) -> &FieldName {
724        &self.0
725    }
726}
727
728impl fmt::Debug for SignedFieldName {
729    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
730        write!(f, "{:?}", self.0)
731    }
732}
733
734#[derive(Clone, Debug, PartialEq)]
735pub enum SignedFieldNameWithQualifier {
736    Bare(SignedFieldName),
737    Plus(SignedFieldName),
738    Asterisk(SignedFieldName),
739}
740
741#[derive(Clone, Debug, PartialEq)]
742pub enum SignedHeaders {
743    Pick(Vec<SignedFieldName>),  // must include From
744    PickWithDefault(Vec<SignedFieldName>),  // From stripped (already in default)
745    All,
746}
747
748#[derive(Clone, Debug, PartialEq)]
749pub enum OversignedHeaders {
750    Pick(Vec<SignedFieldName>),
751    Signed,
752    Extended,
753}
754
755#[derive(Clone, Copy, Debug, PartialEq)]
756pub enum Expiration {
757    Never,
758    After(Duration),  // non-zero
759}
760
761#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
762pub enum RejectFailure {
763    Missing,
764    NoPass,
765    AuthorMismatch,
766}
767
768#[derive(Clone, Debug, Default, PartialEq)]
769pub struct RejectFailures(pub HashSet<RejectFailure>);
770
771#[derive(Clone, Debug, Default)]
772pub struct SigningSenders {
773    pub entries: Vec<SigningSender>,
774}
775
776#[derive(Clone, Debug)]
777pub struct SigningSender {
778    pub sender_expr: Regex,
779    pub domain: DomainExpr,
780    pub selector: Selector,
781    pub key: Arc<SigningKey>,
782    pub signing_config: Option<PartialSigningConfig>,
783}
784
785#[derive(Clone, Debug)]
786pub enum DomainExpr {
787    Domain(DomainName),
788    SenderDomain,
789    Identity(IdentityExpr),
790}
791
792#[derive(Clone, Debug)]
793pub struct IdentityExpr {
794    pub local_part: Option<LocalPartExpr>,
795    pub domain_part: IdentityDomainExpr,
796}
797
798#[derive(Clone, Debug)]
799pub enum LocalPartExpr {
800    LocalPart(String),
801    SenderLocalPart,
802}
803
804#[derive(Clone, Debug)]
805pub enum IdentityDomainExpr {
806    Domain(DomainName),
807    SenderDomain,
808    SplitDomain {
809        d_domain: DomainName,
810        i_domain: DomainName,
811    },
812}
813
814#[derive(Clone, Debug, Default)]
815pub struct ConnectionOverrides {
816    pub entries: Vec<NetworkOverride>,
817}
818
819#[derive(Clone, Debug)]
820pub struct NetworkOverride {
821    pub net: IpNet,
822    pub config: ConfigOverrides,
823}
824
825#[derive(Clone, Debug, Default)]
826pub struct RecipientOverrides {
827    pub entries: Vec<MailAddrOverride>,
828}
829
830#[derive(Clone, Debug)]
831pub struct MailAddrOverride {
832    pub expr: Regex,
833    pub config: ConfigOverrides,
834}
835
836#[cfg(test)]
837mod tests {
838    use super::*;
839    use ipnet::Ipv4Net;
840
841    #[test]
842    fn trusted_networks_contains_ip() {
843        let net = Ipv4Net::new([43, 5, 0, 0].into(), 16).unwrap();
844        let trusted_networks = TrustedNetworks {
845            networks: HashSet::from([net.into()]),
846            ..Default::default()
847        };
848
849        assert!(trusted_networks.contains(IpAddr::from([43, 5, 117, 8])));
850        assert!(trusted_networks.contains(IpAddr::from(Ipv6Addr::LOCALHOST)));
851    }
852
853    #[test]
854    fn trusted_networks_contains_loopback() {
855        let trusted_networks = TrustedNetworks::default();
856
857        assert!(trusted_networks.contains_loopback());
858
859        let net = Ipv4Net::new([127, 0, 0, 1].into(), 8).unwrap();
860        let trusted_networks = TrustedNetworks {
861            trust_loopback: false,
862            networks: HashSet::from([net.into()]),
863        };
864
865        assert!(trusted_networks.contains_loopback());
866    }
867}