├── .gitignore ├── .github └── workflows │ ├── cargo.yml │ └── semgrep.yml ├── src ├── errors.rs ├── result.rs ├── dns.rs ├── parser.rs ├── lib.rs └── policy.rs ├── Cargo.toml ├── LICENSE ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /.github/workflows/cargo.yml: -------------------------------------------------------------------------------- 1 | name: Cargo 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: cargo build 18 | - name: Check formatting 19 | run: cargo fmt --check 20 | - name: Run Clippy 21 | run: cargo clippy -- -D warnings 22 | - name: Run tests 23 | run: cargo test 24 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | quick_error! { 2 | #[derive(Debug, PartialEq)] 3 | /// DMARC errors 4 | pub enum DMARCError { 5 | PolicyParseError(err: String) { 6 | display("failed to parse policy: {}", err) 7 | } 8 | MissingRequiredTag(tag: &'static str) { 9 | display("missing required tag: {}", tag) 10 | } 11 | IncompatibleVersion(value: String) { 12 | display("incompatible version: {}", value) 13 | } 14 | UnknownInternalError(err: String) { 15 | display("internal error: {}", err) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dmarc" 3 | version = "0.1.8" 4 | edition = "2021" 5 | authors = ["Sven Sauleau "] 6 | description = "DMARC (RFC7489) implementation" 7 | repository = "https://github.com/cloudflare/dmarc" 8 | documentation = "https://docs.rs/dmarc" 9 | categories = ["email"] 10 | keywords = ["email", "dmarc", "authentification"] 11 | readme = "README.md" 12 | license = "MIT" 13 | 14 | [dependencies] 15 | cfdkim = "0.3.0" 16 | trust-dns-resolver = "0.23" 17 | quick-error = "2.0.1" 18 | futures = "0.3.18" 19 | rand = "0.8.4" 20 | slog = "2.7.0" 21 | addr = "0.15.2" 22 | 23 | [dev-dependencies] 24 | tokio = { version = "1.20", features = ["macros"] } 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: {} 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - main 7 | - master 8 | schedule: 9 | - cron: '0 0 * * *' 10 | name: Semgrep config 11 | jobs: 12 | semgrep: 13 | name: semgrep/ci 14 | runs-on: ubuntu-latest 15 | env: 16 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 17 | SEMGREP_URL: https://cloudflare.semgrep.dev 18 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 20 | container: 21 | image: semgrep/semgrep 22 | steps: 23 | - uses: actions/checkout@v4 24 | - run: semgrep ci 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Cloudflare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dmarc 2 | 3 | > DMARC ([RFC7489]) implementation 4 | 5 | ## Features 6 | 7 | ### Load the policy for a domain 8 | 9 | ```rust 10 | let policy: Option = dmarc::load_policy(&logger, &from_domain).await?; 11 | ``` 12 | 13 | The `load_policy` arguments are the following: 14 | - `logger`: [slog]::Logger 15 | - `from_domain`: &str ([RFC5322].From's domain) 16 | 17 | ### Apply a policy 18 | 19 | ```rust 20 | let dkim_result: cfdkim::DKIMResult = ...; 21 | let spf_result: SPFResult = ...; 22 | 23 | let ctx = dmarc::PolicyContext { 24 | from_domain: &from_domain, 25 | logger: &logger, 26 | dkim_result, 27 | spf_result, 28 | }; 29 | 30 | let res: DMARCResult = policy.apply(&ctx); 31 | println!("dmarc={}", res.to_str()); 32 | ``` 33 | 34 | `dkim_result` is the result of verifying DKIM using the [cfdkim] crate. In the future it should be a trait. 35 | 36 | `spf_result` is the result of verifying SPF. 37 | 38 | ### Sending feedback report 39 | 40 | Not planned yet. 41 | 42 | [RFC7489]: https://datatracker.ietf.org/doc/html/rfc7489 43 | [slog]: https://crates.io/crates/slog 44 | [RFC5322]: https://datatracker.ietf.org/doc/html/rfc5322 45 | [cfdkim]: https://crates.io/crates/cfdkim 46 | -------------------------------------------------------------------------------- /src/result.rs: -------------------------------------------------------------------------------- 1 | use crate::policy; 2 | 3 | #[derive(PartialEq)] 4 | enum Value { 5 | None, 6 | Neutral, 7 | Pass, 8 | Fail, 9 | } 10 | 11 | /// Result of applying a DMARC policy 12 | pub struct DMARCResult { 13 | value: Value, 14 | policy: Option, 15 | } 16 | 17 | impl DMARCResult { 18 | /// Get the result as string (neutral, fail or pass) 19 | pub fn to_str(&self) -> &'static str { 20 | match self.value { 21 | Value::None => "none", 22 | Value::Neutral => "neutral", 23 | Value::Pass => "pass", 24 | Value::Fail => "fail", 25 | } 26 | } 27 | 28 | /// Constructs a neutral result 29 | pub fn neutral(policy: policy::Policy) -> Self { 30 | Self { 31 | value: Value::Neutral, 32 | policy: Some(policy), 33 | } 34 | } 35 | 36 | /// Constructs a pass result 37 | pub fn pass(policy: policy::Policy) -> Self { 38 | Self { 39 | value: Value::Pass, 40 | policy: Some(policy), 41 | } 42 | } 43 | 44 | /// Constructs a fail result 45 | pub fn fail(policy: policy::Policy) -> Self { 46 | Self { 47 | value: Value::Fail, 48 | policy: Some(policy), 49 | } 50 | } 51 | 52 | /// Constructs a none result 53 | pub fn none() -> Self { 54 | Self { 55 | value: Value::None, 56 | policy: None, 57 | } 58 | } 59 | 60 | /// Checks if the email is supposed to be reject based on the DMARC policy and 61 | /// its result 62 | pub fn should_reject(&self) -> bool { 63 | if let Some(policy) = &self.policy { 64 | self.value == Value::Fail && policy.action == policy::ReceiverAction::Reject 65 | } else { 66 | false 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/dns.rs: -------------------------------------------------------------------------------- 1 | /// Module to abstract DNS operations 2 | use crate::DMARCError; 3 | use futures::future::BoxFuture; 4 | use std::sync::Arc; 5 | use trust_dns_resolver::error::ResolveErrorKind; 6 | use trust_dns_resolver::TokioAsyncResolver; 7 | 8 | /// A trait for entities that perform DNS resolution. 9 | pub trait Lookup: Sync + Send { 10 | fn lookup_txt<'a>(&'a self, name: &'a str) -> BoxFuture<'a, Result, DMARCError>>; 11 | } 12 | 13 | // Technically we should be able to implemement Lookup for TokioAsyncResolver 14 | // directly but it's failing for some reason. 15 | struct TokioAsyncResolverWrapper { 16 | inner: TokioAsyncResolver, 17 | } 18 | impl Lookup for TokioAsyncResolverWrapper { 19 | fn lookup_txt<'a>(&'a self, name: &'a str) -> BoxFuture<'a, Result, DMARCError>> { 20 | Box::pin(async move { 21 | let res = self.inner.txt_lookup(name).await; 22 | match res { 23 | Ok(res) => { 24 | let records: Vec = res 25 | .into_iter() 26 | .map(|txt| { 27 | txt.iter() 28 | .map(|data| String::from_utf8_lossy(data)) 29 | .collect() 30 | }) 31 | .collect(); 32 | Ok(records) 33 | } 34 | Err(err) => match err.kind() { 35 | ResolveErrorKind::NoRecordsFound { .. } => Ok(vec![]), 36 | _ => Err(DMARCError::UnknownInternalError(format!( 37 | "failed to query DNS: {}", 38 | err 39 | ))), 40 | }, 41 | } 42 | }) 43 | } 44 | } 45 | 46 | pub fn from_tokio_resolver(resolver: TokioAsyncResolver) -> Arc { 47 | Arc::new(TokioAsyncResolverWrapper { inner: resolver }) 48 | } 49 | 50 | // https://datatracker.ietf.org/doc/html/rfc7489#section-3.2 51 | pub(crate) fn get_root_domain_name(domain: &str) -> Option { 52 | if let Ok(domain) = addr::parse_domain_name(domain) { 53 | domain.root().map(|d| d.to_owned()) 54 | } else { 55 | None 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::policy::{Alignement, ReceiverAction}; 2 | use crate::DMARCError; 3 | 4 | pub use cfdkim::Tag; 5 | 6 | pub(crate) fn parse(input: &str) -> Result, DMARCError> { 7 | // DMARC records follow the extensible "tag-value" syntax for DNS-based key 8 | // records defined in DKIM. 9 | let (_, tags) = cfdkim::parse_tag_list(input) 10 | .map_err(|err| DMARCError::PolicyParseError(err.to_string()))?; 11 | 12 | Ok(tags) 13 | } 14 | 15 | pub(crate) fn parse_alignement_mode(input: &str) -> Alignement { 16 | match input { 17 | "r" => Alignement::Relaxed, 18 | "s" => Alignement::Strict, 19 | _ => Alignement::default(), 20 | } 21 | } 22 | 23 | pub(crate) fn parse_receiver_action(input: &str) -> Result { 24 | match input { 25 | "none" => Ok(ReceiverAction::None), 26 | "quarantine" => Ok(ReceiverAction::Quarantine), 27 | "reject" => Ok(ReceiverAction::Reject), 28 | v => Err(DMARCError::PolicyParseError(format!( 29 | "invalid receiver policy (p): {}", 30 | v 31 | ))), 32 | } 33 | } 34 | 35 | pub(crate) fn parse_percentage(input: &str) -> usize { 36 | let default = 100; 37 | 38 | if let Ok(value) = input.parse::() { 39 | if value > 100 { 40 | default 41 | } else { 42 | value 43 | } 44 | } else { 45 | default 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::*; 52 | 53 | #[test] 54 | fn test_parse() { 55 | assert_eq!( 56 | parse("v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com").unwrap(), 57 | vec![ 58 | Tag { 59 | name: "v".to_string(), 60 | value: "DMARC1".to_string(), 61 | raw_value: "DMARC1".to_string() 62 | }, 63 | Tag { 64 | name: "p".to_string(), 65 | value: "none".to_string(), 66 | raw_value: "none".to_string() 67 | }, 68 | Tag { 69 | name: "rua".to_string(), 70 | value: "mailto:dmarc@yourdomain.com".to_string(), 71 | raw_value: "mailto:dmarc@yourdomain.com".to_string() 72 | } 73 | ] 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Implementation of https://datatracker.ietf.org/doc/html/rfc7489 2 | use slog::warn; 3 | use std::collections::HashMap; 4 | use std::sync::Arc; 5 | use trust_dns_resolver::TokioAsyncResolver; 6 | 7 | #[macro_use] 8 | extern crate quick_error; 9 | 10 | pub mod dns; 11 | mod errors; 12 | mod parser; 13 | mod policy; 14 | mod result; 15 | 16 | pub use errors::DMARCError; 17 | pub use policy::{Policy, ReceiverAction}; 18 | pub use result::DMARCResult; 19 | 20 | const DNS_SUBDOMAIN: &str = "_dmarc"; 21 | 22 | /// Since the SPF crate we are using (visaspf) doesn't expose a result struct 23 | /// with the domain that it used, we'll use our own. 24 | pub struct SPFResult { 25 | pub domain_used: String, 26 | pub value: String, 27 | } 28 | 29 | /// Context needed to run a DMARC policy 30 | pub struct PolicyContext<'a> { 31 | /// Result of the DKIM verification 32 | pub dkim_result: cfdkim::DKIMResult, 33 | /// Result of the SPF verification 34 | pub spf_result: SPFResult, 35 | /// RFC5322.From's domain 36 | pub from_domain: &'a str, 37 | /// Logger for debugging 38 | pub logger: &'a slog::Logger, 39 | } 40 | 41 | /// Load the DMARC policy for the domain 42 | pub async fn load_policy<'a>( 43 | logger: &'a slog::Logger, 44 | from_domain: &'a str, 45 | ) -> Result, DMARCError> { 46 | let resolver = TokioAsyncResolver::tokio_from_system_conf().map_err(|err| { 47 | DMARCError::UnknownInternalError(format!("failed to create DNS resolver: {}", err)) 48 | })?; 49 | let resolver = dns::from_tokio_resolver(resolver); 50 | 51 | load_policy_with_resolver(resolver, logger, from_domain).await 52 | } 53 | 54 | // https://datatracker.ietf.org/doc/html/rfc7489#section-6.6.3 55 | pub async fn load_policy_with_resolver<'a>( 56 | resolver: Arc, 57 | logger: &'a slog::Logger, 58 | from_domain: &'a str, 59 | ) -> Result, DMARCError> { 60 | macro_rules! load { 61 | ($name:expr, $is_root:expr) => { 62 | for record in resolver.lookup_txt(&$name).await? { 63 | if record.starts_with("v=") { 64 | match parse_policy(&record, $is_root) { 65 | Ok(policy) => return Ok(Some(policy)), 66 | Err(err) => warn!(logger, "DMARC policy parse error: {}", err), 67 | } 68 | } 69 | } 70 | }; 71 | } 72 | 73 | // Search DMARC policy at the current domain 74 | load!(format!("{}.{}", DNS_SUBDOMAIN, from_domain), false); 75 | 76 | // No policy was found, if the domain was a subdomain try at the root domain 77 | if let Some(root) = dns::get_root_domain_name(from_domain) { 78 | load!(format!("{}.{}", DNS_SUBDOMAIN, root), true); 79 | } 80 | 81 | // Finally, if no policy was found return nothing 82 | Ok(None) 83 | } 84 | 85 | /// Parse a DMARC policy 86 | /// 87 | /// If the policy wasn't found at the current domain but was found at the root 88 | /// domain `is_root` must be true. And if the `sp` tag was provided it will be 89 | /// set to the policy's action, otherwise the `p` tag will be used. 90 | /// 91 | /// For non-root domains, the policy's action is set to the `p` tag. 92 | fn parse_policy(record: &str, is_root: bool) -> Result { 93 | let tags = parser::parse(record)?; 94 | 95 | let mut tags_map = HashMap::new(); 96 | for tag in &tags { 97 | tags_map.insert(tag.name.clone(), tag.value.clone()); 98 | } 99 | 100 | // Check version 101 | { 102 | let v = tags_map 103 | .get("v") 104 | .ok_or(DMARCError::MissingRequiredTag("v"))?; 105 | if v != "DMARC1" { 106 | return Err(DMARCError::IncompatibleVersion(v.to_owned())); 107 | } 108 | } 109 | 110 | let action = if is_root { 111 | let p = tags_map 112 | .get("p") 113 | .ok_or(DMARCError::MissingRequiredTag("p"))?; 114 | 115 | if let Some(sp) = tags_map.get("sp") { 116 | sp 117 | } else { 118 | p 119 | } 120 | } else { 121 | tags_map 122 | .get("p") 123 | .ok_or(DMARCError::MissingRequiredTag("p"))? 124 | }; 125 | 126 | let action = parser::parse_receiver_action(action)?; 127 | 128 | let mut policy = policy::Policy::new(action); 129 | 130 | if let Some(v) = tags_map.get("adkim") { 131 | policy.adkim = parser::parse_alignement_mode(v); 132 | } 133 | if let Some(v) = tags_map.get("aspf") { 134 | policy.aspf = parser::parse_alignement_mode(v); 135 | } 136 | if let Some(v) = tags_map.get("pct") { 137 | policy.pct = parser::parse_percentage(v); 138 | } 139 | 140 | Ok(policy) 141 | } 142 | 143 | #[cfg(test)] 144 | mod tests { 145 | use super::*; 146 | use futures::future::BoxFuture; 147 | use policy::{Alignement, Policy, ReceiverAction}; 148 | use std::collections::HashMap; 149 | 150 | #[test] 151 | fn test_parse_policy() { 152 | assert_eq!( 153 | parse_policy( 154 | "v=DMARC1;p=none;sp=quarantine;pct=67;rua=mailto:dmarcreports@example.com;", 155 | false 156 | ) 157 | .unwrap(), 158 | Policy { 159 | adkim: Alignement::Relaxed, 160 | aspf: Alignement::Relaxed, 161 | pct: 67, 162 | action: ReceiverAction::None 163 | } 164 | ); 165 | } 166 | 167 | #[test] 168 | fn test_parse_policy_invalid_version() { 169 | assert_eq!( 170 | parse_policy("v=DMARC6", false).unwrap_err(), 171 | DMARCError::IncompatibleVersion("DMARC6".to_owned()) 172 | ); 173 | } 174 | 175 | #[test] 176 | fn test_parse_policy_require_tags() { 177 | assert_eq!( 178 | parse_policy("p=none;", false).unwrap_err(), 179 | DMARCError::MissingRequiredTag("v") 180 | ); 181 | assert_eq!( 182 | parse_policy("v=DMARC1;", false).unwrap_err(), 183 | DMARCError::MissingRequiredTag("p") 184 | ); 185 | } 186 | 187 | #[test] 188 | fn test_parse_policy_invalid_pct() { 189 | let policy = parse_policy("v=DMARC1;p=none;pct=77777;", false).unwrap(); 190 | assert_eq!(policy.pct, 100); 191 | } 192 | 193 | #[test] 194 | fn test_parse_policy_invalid_alignement_mode() { 195 | let policy = parse_policy("v=DMARC1;p=none;adkim=hein", false).unwrap(); 196 | assert_eq!(policy.adkim, Alignement::Relaxed); 197 | } 198 | 199 | #[test] 200 | fn test_parse_policy_action_inherit_from_root() { 201 | let policy = parse_policy("v=DMARC1;p=none;sp=reject", true).unwrap(); 202 | assert_eq!(policy.action, ReceiverAction::Reject); 203 | } 204 | 205 | macro_rules! map { 206 | { $($key:expr => $value:expr),+ } => { 207 | { 208 | let mut m = ::std::collections::HashMap::new(); 209 | $( 210 | m.insert($key, $value); 211 | )+ 212 | m 213 | } 214 | }; 215 | } 216 | 217 | fn test_resolver(db: HashMap<&'static str, &'static str>) -> Arc { 218 | struct TestResolver { 219 | db: HashMap<&'static str, &'static str>, 220 | } 221 | impl dns::Lookup for TestResolver { 222 | fn lookup_txt<'a>( 223 | &'a self, 224 | name: &'a str, 225 | ) -> BoxFuture<'a, Result, DMARCError>> { 226 | let res = if let Some(value) = self.db.get(name) { 227 | vec![value.to_string()] 228 | } else { 229 | vec![] 230 | }; 231 | Box::pin(async move { Ok(res) }) 232 | } 233 | } 234 | Arc::new(TestResolver { db }) 235 | } 236 | 237 | #[tokio::test] 238 | async fn test_load_policy() { 239 | let resolver = test_resolver(map! { 240 | "_dmarc.example.com" => "v=DMARC1; p=none; pct=13;", 241 | "_dmarc.sub.example.com" => "v=DMARC1; p=none; pct=26;" 242 | }); 243 | let logger = slog::Logger::root(slog::Discard, slog::o!()); 244 | 245 | let policy = load_policy_with_resolver(Arc::clone(&resolver), &logger, "example.com") 246 | .await 247 | .unwrap() 248 | .unwrap(); 249 | assert_eq!(policy.pct, 13); 250 | 251 | let policy = load_policy_with_resolver(Arc::clone(&resolver), &logger, "sub.example.com") 252 | .await 253 | .unwrap() 254 | .unwrap(); 255 | assert_eq!(policy.pct, 26); 256 | } 257 | 258 | #[tokio::test] 259 | async fn test_load_policy_subdomain_no_policy() { 260 | let resolver = test_resolver(map! { 261 | "_dmarc.example.com" => "v=DMARC1; p=none; pct=13;" 262 | }); 263 | let logger = slog::Logger::root(slog::Discard, slog::o!()); 264 | 265 | let policy = load_policy_with_resolver(Arc::clone(&resolver), &logger, "sub.example.com") 266 | .await 267 | .unwrap() 268 | .unwrap(); 269 | assert_eq!(policy.pct, 13); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/policy.rs: -------------------------------------------------------------------------------- 1 | use rand::distributions::Bernoulli; 2 | use rand::distributions::Distribution; 3 | use slog::debug; 4 | use std::default::Default; 5 | 6 | use crate::{dns, DMARCResult, PolicyContext}; 7 | 8 | #[derive(Debug, PartialEq, Clone)] 9 | pub enum Alignement { 10 | Relaxed, 11 | Strict, 12 | } 13 | // Since deriving `Default` on enums is experimental we'll need to implement 14 | // it ourselves for the time being 15 | impl Default for Alignement { 16 | fn default() -> Self { 17 | Self::Relaxed 18 | } 19 | } 20 | 21 | #[derive(Debug, PartialEq, Clone)] 22 | pub enum ReceiverAction { 23 | None, 24 | Quarantine, 25 | Reject, 26 | } 27 | impl ReceiverAction { 28 | pub fn to_str(&self) -> &'static str { 29 | match self { 30 | Self::None => "none", 31 | Self::Quarantine => "quarantine", 32 | Self::Reject => "reject", 33 | } 34 | } 35 | } 36 | 37 | #[derive(Debug, PartialEq, Clone)] 38 | /// DMARC policy 39 | pub struct Policy { 40 | /// DKIM Identifier Alignment mode 41 | pub adkim: Alignement, 42 | /// SPF Identifier Alignment mode 43 | pub aspf: Alignement, 44 | /// Requested Mail Receiver policy (includes subdomain) 45 | pub action: ReceiverAction, 46 | /// Percentage of messages to which the DMARC policy is to be applied 47 | pub pct: usize, 48 | } 49 | 50 | impl Policy { 51 | /// Creates a Policy with default as specified in 52 | /// https://datatracker.ietf.org/doc/html/rfc7489#section-6.3 53 | pub fn new(action: ReceiverAction) -> Self { 54 | Policy { 55 | adkim: Alignement::Relaxed, 56 | aspf: Alignement::Relaxed, 57 | pct: 100, 58 | action, 59 | } 60 | } 61 | 62 | /// Based on the `pct` tag, determine if the DMARC policy should be applied 63 | pub fn should_apply(&self) -> bool { 64 | let d = match Bernoulli::new(self.pct as f64 / 100.0) { 65 | Ok(d) => d, 66 | Err(_) => { 67 | // an invalid probability throws an error, it's unlikely to happen 68 | // given that we validate the value before. 69 | // Return true like rcpt = 100. 70 | return true; 71 | } 72 | }; 73 | d.sample(&mut rand::thread_rng()) 74 | } 75 | 76 | // https://datatracker.ietf.org/doc/html/rfc7489#section-3.1 77 | pub fn check_spf_alignment(&self, from_domain: &str, spf_domain: &str) -> bool { 78 | match self.aspf { 79 | Alignement::Relaxed => { 80 | let root_from = dns::get_root_domain_name(from_domain); 81 | let root_used_domain = dns::get_root_domain_name(spf_domain); 82 | 83 | if root_from == root_used_domain { 84 | return true; 85 | } 86 | } 87 | Alignement::Strict => { 88 | if from_domain == spf_domain { 89 | return true; 90 | } 91 | } 92 | } 93 | false 94 | } 95 | 96 | pub fn check_dkim_alignment( 97 | &self, 98 | from_domain: &str, 99 | dkim_result: &cfdkim::DKIMResult, 100 | ) -> bool { 101 | match self.adkim { 102 | Alignement::Relaxed => { 103 | let root_from = dns::get_root_domain_name(from_domain); 104 | let root_used_domain = dns::get_root_domain_name(&dkim_result.domain_used()); 105 | 106 | if root_from == root_used_domain { 107 | return true; 108 | } 109 | } 110 | Alignement::Strict => { 111 | if from_domain == dkim_result.domain_used() { 112 | return true; 113 | } 114 | } 115 | } 116 | false 117 | } 118 | 119 | /// Apply a DMARC policy as specified in 120 | /// https://datatracker.ietf.org/doc/html/rfc7489#section-6.6 121 | /// 122 | /// The context provides the information (steps 1, 3 and 4 from 123 | /// https://datatracker.ietf.org/doc/html/rfc7489#section-6.6.2) 124 | /// 125 | /// Checks authentication mechanisms result 126 | /// https://datatracker.ietf.org/doc/html/rfc7489#section-4.2 127 | pub fn apply(&self, ctx: &PolicyContext) -> DMARCResult { 128 | if !self.should_apply() { 129 | debug!(ctx.logger, "should not apply DMARC policy"); 130 | return DMARCResult::neutral(self.clone()); 131 | } 132 | 133 | // comparison should be done in a case-insensitive manner 134 | // as per https://datatracker.ietf.org/doc/html/rfc7489#section-3.1 135 | let from_domain = ctx.from_domain.to_lowercase(); 136 | let spf_domain = ctx.spf_result.domain_used.to_lowercase(); 137 | 138 | // If DKIM is aligned, check its result. If pass, DMARC passes 139 | if self.check_dkim_alignment(&from_domain, &ctx.dkim_result) { 140 | let res = ctx.dkim_result.summary(); 141 | if res == "pass" { 142 | return DMARCResult::pass(self.clone()); 143 | } 144 | 145 | debug!(ctx.logger, "dkim aligned but result {}", res); 146 | } 147 | 148 | // If PSF is aligned, check its result. If pass, DMARC passes 149 | if self.check_spf_alignment(&from_domain, &spf_domain) { 150 | let res = &ctx.spf_result.value; 151 | if res == "pass" { 152 | return DMARCResult::pass(self.clone()); 153 | } 154 | 155 | debug!(ctx.logger, "spf aligned but result {}", res); 156 | } 157 | 158 | // No authentication mechanisms were aligned and passes, DMARC fails 159 | DMARCResult::fail(self.clone()) 160 | } 161 | } 162 | 163 | #[cfg(test)] 164 | mod tests { 165 | use cfdkim::canonicalization::Type; 166 | 167 | use super::*; 168 | use crate::SPFResult; 169 | 170 | #[test] 171 | fn test_should_apply() { 172 | let mut policy = Policy::new(ReceiverAction::Reject); 173 | 174 | policy.pct = 0; 175 | assert!(!policy.should_apply()); 176 | 177 | policy.pct = 100; 178 | assert!(policy.should_apply()); 179 | } 180 | 181 | #[test] 182 | fn test_apply() { 183 | let policy = Policy::new(ReceiverAction::Reject); 184 | let from_domain = "a.com"; 185 | let logger = slog::Logger::root(slog::Discard, slog::o!()); 186 | 187 | // SPF & DKIM pass 188 | { 189 | let ctx = PolicyContext { 190 | from_domain, 191 | logger: &logger, 192 | dkim_result: cfdkim::DKIMResult::pass( 193 | "a.com".to_owned(), 194 | Type::Simple, 195 | Type::Simple, 196 | ), 197 | spf_result: SPFResult { 198 | domain_used: "a.com".to_string(), 199 | value: "pass".to_string(), 200 | }, 201 | }; 202 | assert_eq!(policy.apply(&ctx).to_str(), "pass"); 203 | } 204 | 205 | // SPF & DKIM pass but not aligned 206 | { 207 | let ctx = PolicyContext { 208 | from_domain, 209 | logger: &logger, 210 | dkim_result: cfdkim::DKIMResult::pass( 211 | "b.com".to_owned(), 212 | Type::Simple, 213 | Type::Simple, 214 | ), 215 | spf_result: SPFResult { 216 | domain_used: "b.com".to_string(), 217 | value: "pass".to_string(), 218 | }, 219 | }; 220 | assert_eq!(policy.apply(&ctx).to_str(), "fail"); 221 | } 222 | 223 | // SPF pass 224 | { 225 | let ctx = PolicyContext { 226 | from_domain, 227 | logger: &logger, 228 | dkim_result: cfdkim::DKIMResult::neutral("a.com".to_owned()), 229 | spf_result: SPFResult { 230 | domain_used: "a.com".to_string(), 231 | value: "pass".to_string(), 232 | }, 233 | }; 234 | assert_eq!(policy.apply(&ctx).to_str(), "pass"); 235 | } 236 | 237 | // DKIM pass 238 | { 239 | let ctx = PolicyContext { 240 | from_domain, 241 | logger: &logger, 242 | dkim_result: cfdkim::DKIMResult::pass( 243 | "a.com".to_owned(), 244 | Type::Simple, 245 | Type::Simple, 246 | ), 247 | spf_result: SPFResult { 248 | domain_used: "a.com".to_string(), 249 | value: "fail".to_string(), 250 | }, 251 | }; 252 | assert_eq!(policy.apply(&ctx).to_str(), "pass"); 253 | } 254 | 255 | // non pass 256 | { 257 | let ctx = PolicyContext { 258 | from_domain, 259 | logger: &logger, 260 | dkim_result: cfdkim::DKIMResult::neutral("a.com".to_owned()), 261 | spf_result: SPFResult { 262 | domain_used: "a.com".to_string(), 263 | value: "fail".to_string(), 264 | }, 265 | }; 266 | assert_eq!(policy.apply(&ctx).to_str(), "fail"); 267 | } 268 | } 269 | 270 | #[test] 271 | fn test_check_alignement_spf_strict() { 272 | let mut policy = Policy::new(ReceiverAction::Reject); 273 | policy.aspf = Alignement::Strict; 274 | 275 | let from_domain = "a.com"; 276 | 277 | let spf_result = SPFResult { 278 | domain_used: "notfy.a.com".to_string(), 279 | value: "-".to_string(), 280 | }; 281 | assert!(!policy.check_spf_alignment(from_domain, &spf_result.domain_used)); 282 | 283 | let spf_result = SPFResult { 284 | domain_used: "a.com".to_string(), 285 | value: "-".to_string(), 286 | }; 287 | assert!(policy.check_spf_alignment(from_domain, &spf_result.domain_used)); 288 | 289 | let spf_result = SPFResult { 290 | domain_used: "cc.com".to_string(), 291 | value: "-".to_string(), 292 | }; 293 | assert!(!policy.check_spf_alignment(from_domain, &spf_result.domain_used)); 294 | } 295 | 296 | #[test] 297 | fn test_check_alignement_spf_relaxed() { 298 | let mut policy = Policy::new(ReceiverAction::Reject); 299 | policy.aspf = Alignement::Relaxed; 300 | 301 | let from_domain = "a.com"; 302 | 303 | let spf_result = SPFResult { 304 | domain_used: "notfy.a.com".to_string(), 305 | value: "-".to_string(), 306 | }; 307 | assert!(policy.check_spf_alignment(from_domain, &spf_result.domain_used)); 308 | 309 | let spf_result = SPFResult { 310 | domain_used: "cc.com".to_string(), 311 | value: "-".to_string(), 312 | }; 313 | assert!(!policy.check_spf_alignment(from_domain, &spf_result.domain_used)); 314 | } 315 | 316 | #[test] 317 | fn test_check_alignement_dkim_strict() { 318 | let mut policy = Policy::new(ReceiverAction::Reject); 319 | policy.adkim = Alignement::Strict; 320 | 321 | let from_domain = "a.com"; 322 | 323 | let dkim_result = cfdkim::DKIMResult::neutral("notify.a.com".to_owned()); 324 | assert!(!policy.check_dkim_alignment(from_domain, &dkim_result)); 325 | 326 | let dkim_result = cfdkim::DKIMResult::neutral("a.com".to_owned()); 327 | assert!(policy.check_dkim_alignment(from_domain, &dkim_result)); 328 | 329 | let dkim_result = cfdkim::DKIMResult::neutral("cc.com".to_owned()); 330 | assert!(!policy.check_dkim_alignment(from_domain, &dkim_result)); 331 | } 332 | 333 | #[test] 334 | fn test_check_alignement_dkim_relaxed() { 335 | let mut policy = Policy::new(ReceiverAction::Reject); 336 | policy.adkim = Alignement::Relaxed; 337 | 338 | let from_domain = "a.com"; 339 | 340 | let dkim_result = cfdkim::DKIMResult::neutral("a.com".to_owned()); 341 | assert!(policy.check_dkim_alignment(from_domain, &dkim_result)); 342 | 343 | let dkim_result = cfdkim::DKIMResult::neutral("notify.a.com".to_owned()); 344 | assert!(policy.check_dkim_alignment(from_domain, &dkim_result)); 345 | 346 | let dkim_result = cfdkim::DKIMResult::neutral("cc.com".to_owned()); 347 | assert!(!policy.check_dkim_alignment(from_domain, &dkim_result)); 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr" 7 | version = "0.15.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a93b8a41dbe230ad5087cc721f8d41611de654542180586b315d9f4cf6b72bef" 10 | dependencies = [ 11 | "psl", 12 | "psl-types", 13 | ] 14 | 15 | [[package]] 16 | name = "android_system_properties" 17 | version = "0.1.5" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 20 | dependencies = [ 21 | "libc", 22 | ] 23 | 24 | [[package]] 25 | name = "async-trait" 26 | version = "0.1.67" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" 29 | dependencies = [ 30 | "proc-macro2", 31 | "quote", 32 | "syn 2.0.8", 33 | ] 34 | 35 | [[package]] 36 | name = "autocfg" 37 | version = "1.1.0" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 40 | 41 | [[package]] 42 | name = "base64" 43 | version = "0.13.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 46 | 47 | [[package]] 48 | name = "base64" 49 | version = "0.21.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" 52 | 53 | [[package]] 54 | name = "base64ct" 55 | version = "1.6.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 58 | 59 | [[package]] 60 | name = "bitflags" 61 | version = "1.3.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 64 | 65 | [[package]] 66 | name = "block-buffer" 67 | version = "0.10.4" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 70 | dependencies = [ 71 | "generic-array", 72 | ] 73 | 74 | [[package]] 75 | name = "bumpalo" 76 | version = "3.12.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 79 | 80 | [[package]] 81 | name = "byteorder" 82 | version = "1.4.3" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 85 | 86 | [[package]] 87 | name = "bytes" 88 | version = "1.4.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 91 | 92 | [[package]] 93 | name = "cc" 94 | version = "1.0.79" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 97 | 98 | [[package]] 99 | name = "cfdkim" 100 | version = "0.3.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "9ed384723f61ef3b55f81a5148fc1a8c0917472f17817c6171ba8e0c8a4c094e" 103 | dependencies = [ 104 | "base64 0.21.0", 105 | "chrono", 106 | "ed25519-dalek", 107 | "futures", 108 | "indexmap", 109 | "mailparse", 110 | "nom", 111 | "quick-error 2.0.1", 112 | "rsa", 113 | "sha-1", 114 | "sha2", 115 | "slog", 116 | "trust-dns-resolver", 117 | ] 118 | 119 | [[package]] 120 | name = "cfg-if" 121 | version = "1.0.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 124 | 125 | [[package]] 126 | name = "charset" 127 | version = "0.1.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "18e9079d1a12a2cc2bffb5db039c43661836ead4082120d5844f02555aca2d46" 130 | dependencies = [ 131 | "base64 0.13.1", 132 | "encoding_rs", 133 | ] 134 | 135 | [[package]] 136 | name = "chrono" 137 | version = "0.4.24" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" 140 | dependencies = [ 141 | "iana-time-zone", 142 | "num-integer", 143 | "num-traits", 144 | "winapi", 145 | ] 146 | 147 | [[package]] 148 | name = "codespan-reporting" 149 | version = "0.11.1" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 152 | dependencies = [ 153 | "termcolor", 154 | "unicode-width", 155 | ] 156 | 157 | [[package]] 158 | name = "const-oid" 159 | version = "0.9.2" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" 162 | 163 | [[package]] 164 | name = "core-foundation-sys" 165 | version = "0.8.3" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 168 | 169 | [[package]] 170 | name = "cpufeatures" 171 | version = "0.2.9" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" 174 | dependencies = [ 175 | "libc", 176 | ] 177 | 178 | [[package]] 179 | name = "crypto-common" 180 | version = "0.1.6" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 183 | dependencies = [ 184 | "generic-array", 185 | "typenum", 186 | ] 187 | 188 | [[package]] 189 | name = "curve25519-dalek" 190 | version = "4.1.1" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" 193 | dependencies = [ 194 | "cfg-if", 195 | "cpufeatures", 196 | "curve25519-dalek-derive", 197 | "digest", 198 | "fiat-crypto", 199 | "platforms", 200 | "rustc_version", 201 | "subtle", 202 | "zeroize", 203 | ] 204 | 205 | [[package]] 206 | name = "curve25519-dalek-derive" 207 | version = "0.1.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" 210 | dependencies = [ 211 | "proc-macro2", 212 | "quote", 213 | "syn 2.0.8", 214 | ] 215 | 216 | [[package]] 217 | name = "cxx" 218 | version = "1.0.93" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038" 221 | dependencies = [ 222 | "cc", 223 | "cxxbridge-flags", 224 | "cxxbridge-macro", 225 | "link-cplusplus", 226 | ] 227 | 228 | [[package]] 229 | name = "cxx-build" 230 | version = "1.0.93" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca" 233 | dependencies = [ 234 | "cc", 235 | "codespan-reporting", 236 | "once_cell", 237 | "proc-macro2", 238 | "quote", 239 | "scratch", 240 | "syn 2.0.8", 241 | ] 242 | 243 | [[package]] 244 | name = "cxxbridge-flags" 245 | version = "1.0.93" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31" 248 | 249 | [[package]] 250 | name = "cxxbridge-macro" 251 | version = "1.0.93" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" 254 | dependencies = [ 255 | "proc-macro2", 256 | "quote", 257 | "syn 2.0.8", 258 | ] 259 | 260 | [[package]] 261 | name = "data-encoding" 262 | version = "2.3.3" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" 265 | 266 | [[package]] 267 | name = "der" 268 | version = "0.7.8" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" 271 | dependencies = [ 272 | "const-oid", 273 | "pem-rfc7468", 274 | "zeroize", 275 | ] 276 | 277 | [[package]] 278 | name = "digest" 279 | version = "0.10.6" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 282 | dependencies = [ 283 | "block-buffer", 284 | "const-oid", 285 | "crypto-common", 286 | ] 287 | 288 | [[package]] 289 | name = "dmarc" 290 | version = "0.1.8" 291 | dependencies = [ 292 | "addr", 293 | "cfdkim", 294 | "futures", 295 | "quick-error 2.0.1", 296 | "rand", 297 | "slog", 298 | "tokio", 299 | "trust-dns-resolver", 300 | ] 301 | 302 | [[package]] 303 | name = "ed25519" 304 | version = "2.2.2" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" 307 | dependencies = [ 308 | "pkcs8", 309 | "signature", 310 | ] 311 | 312 | [[package]] 313 | name = "ed25519-dalek" 314 | version = "2.0.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" 317 | dependencies = [ 318 | "curve25519-dalek", 319 | "ed25519", 320 | "serde", 321 | "sha2", 322 | "zeroize", 323 | ] 324 | 325 | [[package]] 326 | name = "encoding_rs" 327 | version = "0.8.32" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" 330 | dependencies = [ 331 | "cfg-if", 332 | ] 333 | 334 | [[package]] 335 | name = "enum-as-inner" 336 | version = "0.6.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" 339 | dependencies = [ 340 | "heck", 341 | "proc-macro2", 342 | "quote", 343 | "syn 2.0.8", 344 | ] 345 | 346 | [[package]] 347 | name = "fiat-crypto" 348 | version = "0.2.1" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" 351 | 352 | [[package]] 353 | name = "form_urlencoded" 354 | version = "1.2.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 357 | dependencies = [ 358 | "percent-encoding", 359 | ] 360 | 361 | [[package]] 362 | name = "futures" 363 | version = "0.3.27" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" 366 | dependencies = [ 367 | "futures-channel", 368 | "futures-core", 369 | "futures-executor", 370 | "futures-io", 371 | "futures-sink", 372 | "futures-task", 373 | "futures-util", 374 | ] 375 | 376 | [[package]] 377 | name = "futures-channel" 378 | version = "0.3.27" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" 381 | dependencies = [ 382 | "futures-core", 383 | "futures-sink", 384 | ] 385 | 386 | [[package]] 387 | name = "futures-core" 388 | version = "0.3.27" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" 391 | 392 | [[package]] 393 | name = "futures-executor" 394 | version = "0.3.27" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" 397 | dependencies = [ 398 | "futures-core", 399 | "futures-task", 400 | "futures-util", 401 | ] 402 | 403 | [[package]] 404 | name = "futures-io" 405 | version = "0.3.27" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" 408 | 409 | [[package]] 410 | name = "futures-macro" 411 | version = "0.3.27" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" 414 | dependencies = [ 415 | "proc-macro2", 416 | "quote", 417 | "syn 1.0.109", 418 | ] 419 | 420 | [[package]] 421 | name = "futures-sink" 422 | version = "0.3.27" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" 425 | 426 | [[package]] 427 | name = "futures-task" 428 | version = "0.3.27" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" 431 | 432 | [[package]] 433 | name = "futures-util" 434 | version = "0.3.27" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" 437 | dependencies = [ 438 | "futures-channel", 439 | "futures-core", 440 | "futures-io", 441 | "futures-macro", 442 | "futures-sink", 443 | "futures-task", 444 | "memchr", 445 | "pin-project-lite", 446 | "pin-utils", 447 | "slab", 448 | ] 449 | 450 | [[package]] 451 | name = "generic-array" 452 | version = "0.14.6" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 455 | dependencies = [ 456 | "typenum", 457 | "version_check", 458 | ] 459 | 460 | [[package]] 461 | name = "getrandom" 462 | version = "0.2.8" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 465 | dependencies = [ 466 | "cfg-if", 467 | "libc", 468 | "wasi", 469 | ] 470 | 471 | [[package]] 472 | name = "hashbrown" 473 | version = "0.12.3" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 476 | 477 | [[package]] 478 | name = "heck" 479 | version = "0.4.1" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 482 | 483 | [[package]] 484 | name = "hermit-abi" 485 | version = "0.2.6" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 488 | dependencies = [ 489 | "libc", 490 | ] 491 | 492 | [[package]] 493 | name = "hostname" 494 | version = "0.3.1" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" 497 | dependencies = [ 498 | "libc", 499 | "match_cfg", 500 | "winapi", 501 | ] 502 | 503 | [[package]] 504 | name = "iana-time-zone" 505 | version = "0.1.54" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" 508 | dependencies = [ 509 | "android_system_properties", 510 | "core-foundation-sys", 511 | "iana-time-zone-haiku", 512 | "js-sys", 513 | "wasm-bindgen", 514 | "windows", 515 | ] 516 | 517 | [[package]] 518 | name = "iana-time-zone-haiku" 519 | version = "0.1.1" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 522 | dependencies = [ 523 | "cxx", 524 | "cxx-build", 525 | ] 526 | 527 | [[package]] 528 | name = "idna" 529 | version = "0.4.0" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 532 | dependencies = [ 533 | "unicode-bidi", 534 | "unicode-normalization", 535 | ] 536 | 537 | [[package]] 538 | name = "indexmap" 539 | version = "1.9.2" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 542 | dependencies = [ 543 | "autocfg", 544 | "hashbrown", 545 | ] 546 | 547 | [[package]] 548 | name = "ipconfig" 549 | version = "0.3.1" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "bd302af1b90f2463a98fa5ad469fc212c8e3175a41c3068601bfa2727591c5be" 552 | dependencies = [ 553 | "socket2", 554 | "widestring", 555 | "winapi", 556 | "winreg", 557 | ] 558 | 559 | [[package]] 560 | name = "ipnet" 561 | version = "2.7.1" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" 564 | 565 | [[package]] 566 | name = "js-sys" 567 | version = "0.3.61" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" 570 | dependencies = [ 571 | "wasm-bindgen", 572 | ] 573 | 574 | [[package]] 575 | name = "lazy_static" 576 | version = "1.4.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 579 | dependencies = [ 580 | "spin", 581 | ] 582 | 583 | [[package]] 584 | name = "libc" 585 | version = "0.2.140" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" 588 | 589 | [[package]] 590 | name = "libm" 591 | version = "0.2.6" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" 594 | 595 | [[package]] 596 | name = "link-cplusplus" 597 | version = "1.0.8" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" 600 | dependencies = [ 601 | "cc", 602 | ] 603 | 604 | [[package]] 605 | name = "linked-hash-map" 606 | version = "0.5.6" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 609 | 610 | [[package]] 611 | name = "lock_api" 612 | version = "0.4.9" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 615 | dependencies = [ 616 | "autocfg", 617 | "scopeguard", 618 | ] 619 | 620 | [[package]] 621 | name = "log" 622 | version = "0.4.17" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 625 | dependencies = [ 626 | "cfg-if", 627 | ] 628 | 629 | [[package]] 630 | name = "lru-cache" 631 | version = "0.1.2" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 634 | dependencies = [ 635 | "linked-hash-map", 636 | ] 637 | 638 | [[package]] 639 | name = "mailparse" 640 | version = "0.14.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "6b56570f5f8c0047260d1c8b5b331f62eb9c660b9dd4071a8c46f8c7d3f280aa" 643 | dependencies = [ 644 | "charset", 645 | "data-encoding", 646 | "quoted_printable", 647 | ] 648 | 649 | [[package]] 650 | name = "match_cfg" 651 | version = "0.1.0" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" 654 | 655 | [[package]] 656 | name = "memchr" 657 | version = "2.5.0" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 660 | 661 | [[package]] 662 | name = "minimal-lexical" 663 | version = "0.2.1" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 666 | 667 | [[package]] 668 | name = "mio" 669 | version = "0.8.6" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" 672 | dependencies = [ 673 | "libc", 674 | "log", 675 | "wasi", 676 | "windows-sys", 677 | ] 678 | 679 | [[package]] 680 | name = "nom" 681 | version = "7.1.3" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 684 | dependencies = [ 685 | "memchr", 686 | "minimal-lexical", 687 | ] 688 | 689 | [[package]] 690 | name = "num-bigint-dig" 691 | version = "0.8.2" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" 694 | dependencies = [ 695 | "byteorder", 696 | "lazy_static", 697 | "libm", 698 | "num-integer", 699 | "num-iter", 700 | "num-traits", 701 | "rand", 702 | "smallvec", 703 | "zeroize", 704 | ] 705 | 706 | [[package]] 707 | name = "num-integer" 708 | version = "0.1.45" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 711 | dependencies = [ 712 | "autocfg", 713 | "num-traits", 714 | ] 715 | 716 | [[package]] 717 | name = "num-iter" 718 | version = "0.1.43" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" 721 | dependencies = [ 722 | "autocfg", 723 | "num-integer", 724 | "num-traits", 725 | ] 726 | 727 | [[package]] 728 | name = "num-traits" 729 | version = "0.2.15" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 732 | dependencies = [ 733 | "autocfg", 734 | "libm", 735 | ] 736 | 737 | [[package]] 738 | name = "num_cpus" 739 | version = "1.15.0" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 742 | dependencies = [ 743 | "hermit-abi", 744 | "libc", 745 | ] 746 | 747 | [[package]] 748 | name = "once_cell" 749 | version = "1.18.0" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 752 | 753 | [[package]] 754 | name = "parking_lot" 755 | version = "0.12.1" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 758 | dependencies = [ 759 | "lock_api", 760 | "parking_lot_core", 761 | ] 762 | 763 | [[package]] 764 | name = "parking_lot_core" 765 | version = "0.9.7" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 768 | dependencies = [ 769 | "cfg-if", 770 | "libc", 771 | "redox_syscall", 772 | "smallvec", 773 | "windows-sys", 774 | ] 775 | 776 | [[package]] 777 | name = "pem-rfc7468" 778 | version = "0.7.0" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 781 | dependencies = [ 782 | "base64ct", 783 | ] 784 | 785 | [[package]] 786 | name = "percent-encoding" 787 | version = "2.3.0" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 790 | 791 | [[package]] 792 | name = "pin-project-lite" 793 | version = "0.2.9" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 796 | 797 | [[package]] 798 | name = "pin-utils" 799 | version = "0.1.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 802 | 803 | [[package]] 804 | name = "pkcs1" 805 | version = "0.7.5" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 808 | dependencies = [ 809 | "der", 810 | "pkcs8", 811 | "spki", 812 | ] 813 | 814 | [[package]] 815 | name = "pkcs8" 816 | version = "0.10.2" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 819 | dependencies = [ 820 | "der", 821 | "spki", 822 | ] 823 | 824 | [[package]] 825 | name = "platforms" 826 | version = "3.1.2" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" 829 | 830 | [[package]] 831 | name = "ppv-lite86" 832 | version = "0.2.17" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 835 | 836 | [[package]] 837 | name = "proc-macro2" 838 | version = "1.0.53" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" 841 | dependencies = [ 842 | "unicode-ident", 843 | ] 844 | 845 | [[package]] 846 | name = "psl" 847 | version = "2.1.3" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "2ac31add97d7c393248c273c9f862bcfd396e870db1ff5dec63e0e707db82ae4" 850 | dependencies = [ 851 | "psl-types", 852 | ] 853 | 854 | [[package]] 855 | name = "psl-types" 856 | version = "2.0.11" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" 859 | 860 | [[package]] 861 | name = "quick-error" 862 | version = "1.2.3" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 865 | 866 | [[package]] 867 | name = "quick-error" 868 | version = "2.0.1" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" 871 | 872 | [[package]] 873 | name = "quote" 874 | version = "1.0.26" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 877 | dependencies = [ 878 | "proc-macro2", 879 | ] 880 | 881 | [[package]] 882 | name = "quoted_printable" 883 | version = "0.4.7" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "a24039f627d8285853cc90dcddf8c1ebfaa91f834566948872b225b9a28ed1b6" 886 | 887 | [[package]] 888 | name = "rand" 889 | version = "0.8.5" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 892 | dependencies = [ 893 | "libc", 894 | "rand_chacha", 895 | "rand_core", 896 | ] 897 | 898 | [[package]] 899 | name = "rand_chacha" 900 | version = "0.3.1" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 903 | dependencies = [ 904 | "ppv-lite86", 905 | "rand_core", 906 | ] 907 | 908 | [[package]] 909 | name = "rand_core" 910 | version = "0.6.4" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 913 | dependencies = [ 914 | "getrandom", 915 | ] 916 | 917 | [[package]] 918 | name = "redox_syscall" 919 | version = "0.2.16" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 922 | dependencies = [ 923 | "bitflags", 924 | ] 925 | 926 | [[package]] 927 | name = "resolv-conf" 928 | version = "0.7.0" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" 931 | dependencies = [ 932 | "hostname", 933 | "quick-error 1.2.3", 934 | ] 935 | 936 | [[package]] 937 | name = "rsa" 938 | version = "0.9.2" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" 941 | dependencies = [ 942 | "byteorder", 943 | "const-oid", 944 | "digest", 945 | "num-bigint-dig", 946 | "num-integer", 947 | "num-iter", 948 | "num-traits", 949 | "pkcs1", 950 | "pkcs8", 951 | "rand_core", 952 | "signature", 953 | "spki", 954 | "subtle", 955 | "zeroize", 956 | ] 957 | 958 | [[package]] 959 | name = "rustc_version" 960 | version = "0.4.0" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 963 | dependencies = [ 964 | "semver", 965 | ] 966 | 967 | [[package]] 968 | name = "scopeguard" 969 | version = "1.1.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 972 | 973 | [[package]] 974 | name = "scratch" 975 | version = "1.0.5" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" 978 | 979 | [[package]] 980 | name = "semver" 981 | version = "1.0.18" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" 984 | 985 | [[package]] 986 | name = "serde" 987 | version = "1.0.158" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" 990 | 991 | [[package]] 992 | name = "sha-1" 993 | version = "0.10.1" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" 996 | dependencies = [ 997 | "cfg-if", 998 | "cpufeatures", 999 | "digest", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "sha2" 1004 | version = "0.10.6" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 1007 | dependencies = [ 1008 | "cfg-if", 1009 | "cpufeatures", 1010 | "digest", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "signature" 1015 | version = "2.0.0" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" 1018 | dependencies = [ 1019 | "digest", 1020 | "rand_core", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "slab" 1025 | version = "0.4.8" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 1028 | dependencies = [ 1029 | "autocfg", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "slog" 1034 | version = "2.7.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" 1037 | 1038 | [[package]] 1039 | name = "smallvec" 1040 | version = "1.10.0" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 1043 | 1044 | [[package]] 1045 | name = "socket2" 1046 | version = "0.4.9" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 1049 | dependencies = [ 1050 | "libc", 1051 | "winapi", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "spin" 1056 | version = "0.5.2" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1059 | 1060 | [[package]] 1061 | name = "spki" 1062 | version = "0.7.2" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" 1065 | dependencies = [ 1066 | "base64ct", 1067 | "der", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "subtle" 1072 | version = "2.5.0" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 1075 | 1076 | [[package]] 1077 | name = "syn" 1078 | version = "1.0.109" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1081 | dependencies = [ 1082 | "proc-macro2", 1083 | "quote", 1084 | "unicode-ident", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "syn" 1089 | version = "2.0.8" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" 1092 | dependencies = [ 1093 | "proc-macro2", 1094 | "quote", 1095 | "unicode-ident", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "termcolor" 1100 | version = "1.2.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 1103 | dependencies = [ 1104 | "winapi-util", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "thiserror" 1109 | version = "1.0.40" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 1112 | dependencies = [ 1113 | "thiserror-impl", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "thiserror-impl" 1118 | version = "1.0.40" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 1121 | dependencies = [ 1122 | "proc-macro2", 1123 | "quote", 1124 | "syn 2.0.8", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "tinyvec" 1129 | version = "1.6.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1132 | dependencies = [ 1133 | "tinyvec_macros", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "tinyvec_macros" 1138 | version = "0.1.1" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1141 | 1142 | [[package]] 1143 | name = "tokio" 1144 | version = "1.26.0" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" 1147 | dependencies = [ 1148 | "autocfg", 1149 | "bytes", 1150 | "libc", 1151 | "memchr", 1152 | "mio", 1153 | "num_cpus", 1154 | "pin-project-lite", 1155 | "socket2", 1156 | "tokio-macros", 1157 | "windows-sys", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "tokio-macros" 1162 | version = "1.8.2" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" 1165 | dependencies = [ 1166 | "proc-macro2", 1167 | "quote", 1168 | "syn 1.0.109", 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "tracing" 1173 | version = "0.1.37" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1176 | dependencies = [ 1177 | "cfg-if", 1178 | "pin-project-lite", 1179 | "tracing-attributes", 1180 | "tracing-core", 1181 | ] 1182 | 1183 | [[package]] 1184 | name = "tracing-attributes" 1185 | version = "0.1.23" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 1188 | dependencies = [ 1189 | "proc-macro2", 1190 | "quote", 1191 | "syn 1.0.109", 1192 | ] 1193 | 1194 | [[package]] 1195 | name = "tracing-core" 1196 | version = "0.1.30" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 1199 | dependencies = [ 1200 | "once_cell", 1201 | ] 1202 | 1203 | [[package]] 1204 | name = "trust-dns-proto" 1205 | version = "0.23.0" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "0dc775440033cb114085f6f2437682b194fa7546466024b1037e82a48a052a69" 1208 | dependencies = [ 1209 | "async-trait", 1210 | "cfg-if", 1211 | "data-encoding", 1212 | "enum-as-inner", 1213 | "futures-channel", 1214 | "futures-io", 1215 | "futures-util", 1216 | "idna", 1217 | "ipnet", 1218 | "once_cell", 1219 | "rand", 1220 | "smallvec", 1221 | "thiserror", 1222 | "tinyvec", 1223 | "tokio", 1224 | "tracing", 1225 | "url", 1226 | ] 1227 | 1228 | [[package]] 1229 | name = "trust-dns-resolver" 1230 | version = "0.23.0" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "2dff7aed33ef3e8bf2c9966fccdfed93f93d46f432282ea875cd66faabc6ef2f" 1233 | dependencies = [ 1234 | "cfg-if", 1235 | "futures-util", 1236 | "ipconfig", 1237 | "lru-cache", 1238 | "once_cell", 1239 | "parking_lot", 1240 | "rand", 1241 | "resolv-conf", 1242 | "smallvec", 1243 | "thiserror", 1244 | "tokio", 1245 | "tracing", 1246 | "trust-dns-proto", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "typenum" 1251 | version = "1.16.0" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1254 | 1255 | [[package]] 1256 | name = "unicode-bidi" 1257 | version = "0.3.13" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 1260 | 1261 | [[package]] 1262 | name = "unicode-ident" 1263 | version = "1.0.8" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 1266 | 1267 | [[package]] 1268 | name = "unicode-normalization" 1269 | version = "0.1.22" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1272 | dependencies = [ 1273 | "tinyvec", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "unicode-width" 1278 | version = "0.1.10" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 1281 | 1282 | [[package]] 1283 | name = "url" 1284 | version = "2.4.1" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" 1287 | dependencies = [ 1288 | "form_urlencoded", 1289 | "idna", 1290 | "percent-encoding", 1291 | ] 1292 | 1293 | [[package]] 1294 | name = "version_check" 1295 | version = "0.9.4" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1298 | 1299 | [[package]] 1300 | name = "wasi" 1301 | version = "0.11.0+wasi-snapshot-preview1" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1304 | 1305 | [[package]] 1306 | name = "wasm-bindgen" 1307 | version = "0.2.84" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" 1310 | dependencies = [ 1311 | "cfg-if", 1312 | "wasm-bindgen-macro", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "wasm-bindgen-backend" 1317 | version = "0.2.84" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" 1320 | dependencies = [ 1321 | "bumpalo", 1322 | "log", 1323 | "once_cell", 1324 | "proc-macro2", 1325 | "quote", 1326 | "syn 1.0.109", 1327 | "wasm-bindgen-shared", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "wasm-bindgen-macro" 1332 | version = "0.2.84" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" 1335 | dependencies = [ 1336 | "quote", 1337 | "wasm-bindgen-macro-support", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "wasm-bindgen-macro-support" 1342 | version = "0.2.84" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" 1345 | dependencies = [ 1346 | "proc-macro2", 1347 | "quote", 1348 | "syn 1.0.109", 1349 | "wasm-bindgen-backend", 1350 | "wasm-bindgen-shared", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "wasm-bindgen-shared" 1355 | version = "0.2.84" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" 1358 | 1359 | [[package]] 1360 | name = "widestring" 1361 | version = "0.5.1" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" 1364 | 1365 | [[package]] 1366 | name = "winapi" 1367 | version = "0.3.9" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1370 | dependencies = [ 1371 | "winapi-i686-pc-windows-gnu", 1372 | "winapi-x86_64-pc-windows-gnu", 1373 | ] 1374 | 1375 | [[package]] 1376 | name = "winapi-i686-pc-windows-gnu" 1377 | version = "0.4.0" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1380 | 1381 | [[package]] 1382 | name = "winapi-util" 1383 | version = "0.1.5" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1386 | dependencies = [ 1387 | "winapi", 1388 | ] 1389 | 1390 | [[package]] 1391 | name = "winapi-x86_64-pc-windows-gnu" 1392 | version = "0.4.0" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1395 | 1396 | [[package]] 1397 | name = "windows" 1398 | version = "0.46.0" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" 1401 | dependencies = [ 1402 | "windows-targets", 1403 | ] 1404 | 1405 | [[package]] 1406 | name = "windows-sys" 1407 | version = "0.45.0" 1408 | source = "registry+https://github.com/rust-lang/crates.io-index" 1409 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1410 | dependencies = [ 1411 | "windows-targets", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "windows-targets" 1416 | version = "0.42.2" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1419 | dependencies = [ 1420 | "windows_aarch64_gnullvm", 1421 | "windows_aarch64_msvc", 1422 | "windows_i686_gnu", 1423 | "windows_i686_msvc", 1424 | "windows_x86_64_gnu", 1425 | "windows_x86_64_gnullvm", 1426 | "windows_x86_64_msvc", 1427 | ] 1428 | 1429 | [[package]] 1430 | name = "windows_aarch64_gnullvm" 1431 | version = "0.42.2" 1432 | source = "registry+https://github.com/rust-lang/crates.io-index" 1433 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1434 | 1435 | [[package]] 1436 | name = "windows_aarch64_msvc" 1437 | version = "0.42.2" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1440 | 1441 | [[package]] 1442 | name = "windows_i686_gnu" 1443 | version = "0.42.2" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1446 | 1447 | [[package]] 1448 | name = "windows_i686_msvc" 1449 | version = "0.42.2" 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" 1451 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1452 | 1453 | [[package]] 1454 | name = "windows_x86_64_gnu" 1455 | version = "0.42.2" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1458 | 1459 | [[package]] 1460 | name = "windows_x86_64_gnullvm" 1461 | version = "0.42.2" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1464 | 1465 | [[package]] 1466 | name = "windows_x86_64_msvc" 1467 | version = "0.42.2" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1470 | 1471 | [[package]] 1472 | name = "winreg" 1473 | version = "0.10.1" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 1476 | dependencies = [ 1477 | "winapi", 1478 | ] 1479 | 1480 | [[package]] 1481 | name = "zeroize" 1482 | version = "1.5.7" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" 1485 | --------------------------------------------------------------------------------