├── images ├── pct.png ├── no_dmarc.png ├── p_policy.png └── spelling.png ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── query.rs ├── utils.rs ├── main.rs └── parse.rs └── Cargo.lock /images/pct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Accenture/dmarc_checker/HEAD/images/pct.png -------------------------------------------------------------------------------- /images/no_dmarc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Accenture/dmarc_checker/HEAD/images/no_dmarc.png -------------------------------------------------------------------------------- /images/p_policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Accenture/dmarc_checker/HEAD/images/p_policy.png -------------------------------------------------------------------------------- /images/spelling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Accenture/dmarc_checker/HEAD/images/spelling.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files 2 | .*.swp 3 | dmarc_checker_linux* 4 | dmarc_checker_win* 5 | # Folders 6 | /target 7 | /test_data 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dmarc_checker" 3 | version = "1.0.1" 4 | authors = ["Chris ", "Orson "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | trust-dns-client = "0.20.1" 11 | trust-dns-proto = "0.20.1" 12 | tokio = { version = "1", features = ["full"] } 13 | futures = "0.3.13" 14 | rand = "0.8.3" 15 | clap = "2.33.3" 16 | serde = { version = "1.0", features = ["derive"] } 17 | env_logger = "0.7.1" 18 | csv-async = { version = "1.2.0", features = ["tokio"] } 19 | 20 | [dev-dependencies] 21 | pretty_assertions = "0.7.1" 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 6point6 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 Checker 2 | DMARC Checker is a Rust powered asynchronous DMARC lookup engine. It takes a list of domains as its input and generates a CSV output of the parsed DMARC records. 3 | 4 | For individual domains, it's simpler to use the dig utility, e.g. `dig dmarc.example.org TXT +short`. 5 | 6 | However, unlike dig, DMARC Check is fast. It averages around 500 lookups per second and can parse the top 1 million domains within 30 minutes. 7 | 8 | ### Identifying Vulnerable Domains 9 | Please see the [Wiki](https://github.com/6point6/dmarc_checker/wiki) located here. 10 | 11 | * Access our [Mail Spoofer](https://github.com/6point6/mail-spoofer) tool and how-to guides on the [Mail Spoofer Wiki](https://github.com/6point6/mail-spoofer/wiki). 12 | * For help identifying vulnerable domains, check out our tool [DMARC Checker](https://github.com/6point6/dmarc_checker) and its [Wiki](https://github.com/6point6/dmarc_checker/wiki). 13 | 14 | ### Build 15 | DMARC Checker is built in Rust, meaning you can compile it using the Cargo engine on Rust supported platforms. Within the Git directory, use the following commands. 16 | 17 | `cargo build` for debug versions, and 18 | `cargo build --release` for release versions. 19 | 20 | ### Usage 21 | Provide a file of domains with the `-i' flag, and specify a file to output for the `-o' flag. The domain list needs to be a newline separated list of domains. 22 | 23 | **domain_list.txt** 24 | ``` 25 | google.com 26 | cia.gov 27 | nca.gov.uk 28 | dwp.gov.uk 29 | gmail.com 30 | ``` 31 | 32 | `./dmarc_checker -i domain_list.txt -o domain_output.csv` 33 | 34 | OR 35 | 36 | `cargo run -- -i domain_list.txt -o domain_output.csv` 37 | 38 | The tool parses batches of 50,000 domains — it prevents I/O kernel problems — and writes results to the `domain_output.csv` file. 39 | 40 | You can increase or decrease the batch size by specifying `-b`. 41 | 42 | `./dmarc_checker -i domain_list.txt -o domain_output.csv -b 100` 43 | 44 | OR 45 | 46 | `cargo run -- -i domain_list.txt -o domain_output.csv -b 100` 47 | 48 | #### Domain Examples 49 | We've tested the DMARC Checker against the following list of domains. 50 | - [UK Government](https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/842955/List_of_gov.uk_domain_names_as_at_28_Oct_2019.csv/preview) 51 | - [USA Government](https://github.com/cisagov/dotgov-data) 52 | - [Majestic Top 1 Million](https://blog.majestic.com/development/majestic-million-csv-daily) 53 | 54 | ### Notes 55 | Some DMARC records specify CNAME domains. We list these records with CNAME entries but DO NOT recursively check the CNAME tree. 56 | 57 | We felt that the effort required to write the check, especially asynchronously, wasn't worth the time as not many DMARC domains use CNAME records. 58 | 59 | We've also added helpful hints where DMARC records are empty if a domain is vulnerable to subdomain spoofing only and inconsistencies arise with the percentage specifier. 60 | -------------------------------------------------------------------------------- /src/query.rs: -------------------------------------------------------------------------------- 1 | use rand::seq::SliceRandom; 2 | use rand::thread_rng; 3 | use std::{ 4 | net::{IpAddr, Ipv4Addr, SocketAddr}, 5 | str::FromStr, 6 | }; 7 | use tokio::sync::mpsc; 8 | use trust_dns_client::client::{Client, SyncClient}; 9 | use trust_dns_client::rr::{DNSClass, Name, Record, RecordType}; 10 | use trust_dns_client::udp::UdpClientConnection; 11 | 12 | pub const DNS_SERVERS: &'static [&'static str] = &[ 13 | "8.8.8.8", 14 | "9.9.9.9", 15 | "8.8.4.4", 16 | "185.228.168.9", 17 | "185.228.169.9", 18 | "149.112.112.112", 19 | "208.67.222.222", 20 | "208.67.220.220", 21 | "87.117.196.200", 22 | "62.134.11.4", 23 | "213.171.217.147", 24 | "194.145.240.6", 25 | "213.171.217.148", 26 | "195.27.1.1", 27 | "195.182.110.132", 28 | "213.52.192.198", 29 | "154.32.105.18", 30 | "158.43.192.1", 31 | "84.8.2.11", 32 | "154.32.109.18", 33 | "154.32.107.18", 34 | "5.253.114.91", 35 | "212.118.241.33", 36 | "178.62.57.141", 37 | "188.227.240.58", 38 | "194.187.251.67", 39 | "193.111.200.191", 40 | "158.43.128.72", 41 | "158.43.240.4", 42 | "1.1.1.1", 43 | "1.0.0.1", 44 | "8.26.56.26", 45 | "8.20.247.20", 46 | "64.6.64.6", 47 | "64.6.65.6", 48 | "4.2.2.1", 49 | "4.2.2.2", 50 | "4.2.2.3", 51 | "4.2.2.4", 52 | "4.2.2.5", 53 | "192.250.35.250", 54 | "129.250.35.251", 55 | "204.117.214.10", 56 | "199.2.252.10", 57 | "76.86.19.19", 58 | "76.223.122.150", 59 | "94.140.14.14", 60 | "94.140.15.15", 61 | ]; 62 | 63 | pub async fn try_query(domain_name: String, tx: mpsc::Sender<(String, Vec)>) { 64 | // Read URIs to query 65 | let name = Name::from_utf8(format!("_dmarc.{}", domain_name)).unwrap(); 66 | 67 | loop { 68 | // Clone for request 69 | let name = name.clone(); 70 | 71 | // Open Udp connection for DNS client 72 | let conn = UdpClientConnection::with_timeout( 73 | get_dns_ip().await, 74 | std::time::Duration::from_millis(200), 75 | ) 76 | .unwrap(); 77 | 78 | // DNS client stuff 79 | let client = SyncClient::new(conn); 80 | 81 | // Spawn blocking task and check for errors 82 | match tokio::task::spawn_blocking(move || { 83 | client.query(&name, DNSClass::IN, RecordType::TXT) 84 | }) 85 | .await 86 | .unwrap() 87 | { 88 | Ok(dns_response) => { 89 | // Success to send domain name and DNS response to channel 90 | let _ = tx 91 | .send((domain_name, dns_response.answers().to_owned())) 92 | .await; 93 | return; 94 | } 95 | Err(_e) => { 96 | //eprintln!("Failed '{}'", e) 97 | } 98 | } 99 | } 100 | } 101 | 102 | async fn get_dns_ip() -> SocketAddr { 103 | // Choose random DNS server IP from DNS_SERVERS 104 | let dns_server = DNS_SERVERS.choose(&mut thread_rng()).unwrap(); 105 | 106 | // Conver to SockAddr 107 | SocketAddr::new(IpAddr::V4(Ipv4Addr::from_str(dns_server).unwrap()), 53) 108 | } 109 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg}; 2 | 3 | static DEFAULT_BATCH_SIZE: usize = 50000; 4 | 5 | pub struct Config { 6 | pub input_domain_file: String, 7 | pub output_dmarc_file: String, 8 | pub batch_size: usize, 9 | } 10 | 11 | impl Config { 12 | pub fn new() -> Self { 13 | let args = App::new("dmarc_checker") 14 | .version("0.1") 15 | .about("Checks for dmarc misconfigurations") 16 | .arg( 17 | Arg::with_name("input_domain_file") 18 | .short("i") 19 | .long("input_domain_file") 20 | .value_name("DOMAIN_NAME_LIST_CSV") 21 | .help("List of domain names to query") 22 | .required(true) 23 | .takes_value(true), 24 | ) 25 | .arg( 26 | Arg::with_name("output_dmarc_file") 27 | .short("o") 28 | .long("output_dmarc_file") 29 | .value_name("PARSED_DOMAIN_LIST_CSV") 30 | .help("output file containing results") 31 | .required(true) 32 | .takes_value(true), 33 | ) 34 | .arg( 35 | Arg::with_name("batch_size") 36 | .short("b") 37 | .long("batch_size") 38 | .value_name("BATCH_SIZE") 39 | .help("batch size of domains to process") 40 | .required(false) 41 | .takes_value(true), 42 | ) 43 | .get_matches(); 44 | 45 | // If batch size exists set it to CLI argument, otherwise set it to default 46 | let batch_size = if args.occurrences_of("batch_size") > 0 { 47 | // Attempt to extract valid u32 from string CLI value 48 | match args.value_of("batch_size").unwrap().parse::() { 49 | Ok(0) => { 50 | eprintln!("Cannot parse provided batch size 'size must be larger than 0'\r\nUsing default!"); 51 | DEFAULT_BATCH_SIZE 52 | } 53 | Ok(num) => num, 54 | Err(e) => { 55 | eprintln!("Cannot parse provided batch size '{}'\r\nUsing default!", e); 56 | DEFAULT_BATCH_SIZE 57 | } 58 | } 59 | // Default condition 60 | } else { 61 | DEFAULT_BATCH_SIZE 62 | }; 63 | 64 | eprintln!("Using batch size of {}", batch_size); 65 | 66 | Self { 67 | input_domain_file: String::from(args.value_of("input_domain_file").unwrap()), 68 | output_dmarc_file: String::from(args.value_of("output_dmarc_file").unwrap()), 69 | batch_size, 70 | } 71 | } 72 | } 73 | 74 | #[macro_export] 75 | macro_rules! fmt_err { 76 | ($($arg:tt)*) => {{ 77 | let res = std::fmt::format(format_args!($($arg)*)); 78 | 79 | format!( 80 | "[-] Error:\n\t- Cause: {}\n\t- Line: {}\n\t- File: {}\n\n", 81 | res, 82 | line!(), 83 | file!(), 84 | ) 85 | }} 86 | } 87 | 88 | macro_rules! print_err { 89 | ($($arg:tt)*) => {{ 90 | let res = fmt_err!($($arg)*); 91 | eprintln!("{}", res); 92 | }} 93 | } 94 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use csv_async::AsyncSerializer; 2 | use tokio::fs::File; 3 | use tokio::sync::mpsc; 4 | use trust_dns_client::rr::Record; 5 | 6 | mod parse; 7 | mod query; 8 | #[macro_use] 9 | mod utils; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<(), ()> { 13 | let config = utils::Config::new(); 14 | 15 | // Open asynchronous file writers and serializers 16 | let output_dmarc_file = File::create(&config.output_dmarc_file).await.map_err(|e| { 17 | print_err!( 18 | "Failed to create output file: {} - {}", 19 | config.input_domain_file, 20 | e 21 | ) 22 | })?; 23 | 24 | let mut output_dmarc_filewriter = csv_async::AsyncSerializer::from_writer(output_dmarc_file); 25 | 26 | // Read all domains for batching 27 | let domain_names = std::fs::read_to_string(&config.input_domain_file) 28 | .map_err(|e| { 29 | print_err!( 30 | "Failed to read file: {} 31 | - {}", 32 | config.input_domain_file, 33 | e 34 | ) 35 | })?; 36 | 37 | // Batch into iter chunks of batch_size 38 | for domain_chunk in domain_names 39 | .lines() 40 | .collect::>() 41 | .chunks(config.batch_size) { 42 | 43 | let (tx, rx) = mpsc::channel(query::DNS_SERVERS.len()); 44 | 45 | for domain_name in domain_chunk { 46 | 47 | // Convert domain name to string for tokio task 48 | let domain_name = domain_name.to_string(); 49 | 50 | // Clone Sender tx for task move 51 | let tx = tx.clone(); 52 | 53 | tokio::spawn(async move { query::try_query(domain_name, tx).await }); 54 | } 55 | // Close original Sender tx to prevent a dead lock 56 | drop(tx); 57 | 58 | // Write output to csv file 59 | output_dmarc_filewriter = write_dmarc_output_to_csv(output_dmarc_filewriter, rx) 60 | .await 61 | .map_err(|e| { 62 | eprintln!( 63 | "Failed to write output to file: {} - {}", 64 | config.input_domain_file, e 65 | )})?; 66 | } 67 | 68 | // Flush filewriter buffers 69 | output_dmarc_filewriter 70 | .flush() 71 | .await 72 | .map_err(|e| eprintln!("Failed to flush file {} - {}", config.input_domain_file, e))?; 73 | 74 | Ok(()) 75 | } 76 | 77 | async fn write_dmarc_output_to_csv( 78 | mut output_dmarc_filewriter: AsyncSerializer, 79 | mut rx: mpsc::Receiver<(String, Vec)>, 80 | ) -> Result, String> { 81 | // Recieve data from channel asynchronously 82 | while let Some((domain_name, dns_respoonse_answers)) = rx.recv().await { 83 | println!("Scanned '{}'", &domain_name); 84 | 85 | let string_records = parse::StringRecords::new(&dns_respoonse_answers); 86 | 87 | match string_records { 88 | Some(sr) => match sr { 89 | // Write single DMARC record 90 | parse::StringRecords::Single(s) => { 91 | let dmarc = parse::Dmarc::new(&domain_name, Some(s)); 92 | output_dmarc_filewriter 93 | .serialize::(dmarc) 94 | .await 95 | .map_err(|e| { 96 | fmt_err!( 97 | "Failed to write single record for domain name: {} - {}", 98 | domain_name, 99 | e 100 | ) 101 | })? 102 | } 103 | // Write multiple DMARC record 104 | parse::StringRecords::Multiple(vs) => { 105 | for s in vs { 106 | let dmarc = parse::Dmarc::new(&domain_name, Some(s)); 107 | output_dmarc_filewriter 108 | .serialize::(dmarc) 109 | .await 110 | .map_err(|e| { 111 | fmt_err!( 112 | "Failed to write multiple record for domain name: {} - {}", 113 | domain_name, 114 | e 115 | ) 116 | })? 117 | } 118 | } 119 | }, 120 | // No DMARC record 121 | None => { 122 | let dmarc = parse::Dmarc::new(&domain_name, None); 123 | output_dmarc_filewriter 124 | .serialize::(dmarc) 125 | .await 126 | .map_err(|e| { 127 | fmt_err!( 128 | "Failed to write empty record for domain name: {} - {}", 129 | domain_name, 130 | e 131 | ) 132 | })? 133 | } 134 | } 135 | } 136 | 137 | Ok(output_dmarc_filewriter) 138 | } 139 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use pretty_assertions::assert_eq; 3 | use serde::{Deserialize, Serialize, Serializer}; 4 | use trust_dns_client::rr::{Record, RecordType}; 5 | 6 | const DMARC1: &str = "DMARC1"; 7 | 8 | const V_TAG: &str = "v"; 9 | const P_TAG: &str = "p"; 10 | const PCT_TAG: &str = "pct"; 11 | const RUA_TAG: &str = "rua"; 12 | const RUF_TAG: &str = "ruf"; 13 | const SP_TAG: &str = "sp"; 14 | const ADKIM_TAG: &str = "adkim"; 15 | const ASPF_TAG: &str = "aspf"; 16 | 17 | const TAG_NONE: &str = "none"; 18 | const TAG_QURANTINE: &str = "quarantine"; 19 | const TAG_REJECT: &str = "reject"; 20 | const TAG_INVALID: &str = "INVALID"; 21 | 22 | const CNAME_RECORD: &str = "CNAME"; 23 | const TXT_RECORD: &str = "TXT"; 24 | const OTHER_RECORD: &str = "OTHER"; 25 | 26 | const YES: &str = "Yes"; 27 | const NO: &str = "No"; 28 | 29 | const ERR_FLAG_NOT_PRESENT: &str = "Flag not present"; 30 | const ERR_MISSING_V_OR_P_FLAG: &str = "V or P flag missing"; 31 | const ERR_FIRST_FLAG_NOT_V: &str = "First flag is not V"; 32 | const ERR_SECOND_FLAG_NOT_P: &str = "Second flag is not P"; 33 | const ERR_POLICY_IS_NONE: &str = "Policy is `none`"; 34 | 35 | const ERR_P_FLAG_MISSING_AND_SP_NOT_SET: &str = "Missing p flag and sp not set"; 36 | const ERR_P_FLAG_MISSING: &str = "P flag missing"; 37 | const ERR_IGNORE_SUBDOMAIN_DMARC_FAILS: &str = "sp=none: Ignores subdomain DMARC fails"; 38 | const ERR_PERMITS_SUBDOMAIN_SPOOFING: &str = 39 | "p=reject, sp=none: Ignores subdomain DMARC fails and permits subdomain spoofing"; 40 | 41 | #[derive(Debug, Deserialize)] 42 | pub struct DomainName(pub String); 43 | 44 | #[derive(Serialize)] 45 | pub struct ParseResult { 46 | pub domain_name: String, 47 | } 48 | 49 | #[derive(Debug)] 50 | pub enum StringRecords { 51 | Single(DmarcRecordType), 52 | Multiple(Vec), 53 | } 54 | 55 | #[derive(Debug)] 56 | pub enum DmarcRecordType { 57 | Cname(Option), 58 | Txt(Option), 59 | Other, 60 | } 61 | 62 | impl DmarcRecordType { 63 | fn new(r: &Record) -> Self { 64 | match r.rdata().to_record_type() { 65 | RecordType::CNAME => Self::Cname( 66 | r.rdata() 67 | .as_cname() 68 | .and_then(|cname| Some(cname.to_string())), 69 | ), 70 | RecordType::TXT => Self::Txt( 71 | r.rdata() 72 | .as_txt() 73 | .and_then(|txt_data| Some(txt_data.to_string())), 74 | ), 75 | _ => Self::Other, 76 | } 77 | } 78 | } 79 | 80 | impl StringRecords { 81 | pub fn new(r: &[Record]) -> Option { 82 | match r.len() { 83 | 0 => None, 84 | 1 => Some(Self::Single(DmarcRecordType::new(&r[0]))), 85 | n @ _ => { 86 | let mut dmarc_records: Vec = Vec::with_capacity(n); 87 | 88 | for i in r.iter() { 89 | dmarc_records.push(DmarcRecordType::new(i)) 90 | } 91 | 92 | Some(Self::Multiple(dmarc_records)) 93 | } 94 | } 95 | } 96 | } 97 | 98 | #[derive(Debug, PartialEq)] 99 | enum DmarcVersion { 100 | Dmarc1, 101 | Invalid(String), 102 | } 103 | 104 | impl Serialize for DmarcVersion { 105 | fn serialize(&self, serializer: S) -> Result 106 | where 107 | S: Serializer, 108 | { 109 | match *self { 110 | Self::Dmarc1 => serializer.serialize_unit_variant(DMARC1, 0, DMARC1), 111 | Self::Invalid(ref s) => { 112 | serializer.serialize_newtype_variant("Invalid", 1, "Invalid", s) 113 | } 114 | } 115 | } 116 | } 117 | 118 | impl DmarcVersion { 119 | fn to_ver(v_tag_val: &str) -> Self { 120 | match v_tag_val { 121 | DMARC1 => Self::Dmarc1, 122 | _ => Self::Invalid(v_tag_val.to_string()), 123 | } 124 | } 125 | } 126 | 127 | fn match_tag<'a>(tag: &str, dmarc_entries: &'a mut Vec) -> Option> { 128 | for (i, e) in dmarc_entries.iter_mut().enumerate() { 129 | if e.tag == tag { 130 | return Some(dmarc_entries.remove(i)); 131 | } 132 | } 133 | 134 | return None; 135 | } 136 | 137 | #[derive(Debug, PartialEq)] 138 | enum TagAction { 139 | None, 140 | Qurantine, 141 | Reject, 142 | Invalid(String), 143 | } 144 | 145 | impl Serialize for TagAction { 146 | fn serialize(&self, serializer: S) -> Result 147 | where 148 | S: Serializer, 149 | { 150 | match *self { 151 | Self::None => serializer.serialize_unit_variant("None", 0, TAG_NONE), 152 | Self::Qurantine => serializer.serialize_unit_variant("Quarantine", 1, TAG_QURANTINE), 153 | Self::Reject => serializer.serialize_unit_variant("Reject", 2, TAG_REJECT), 154 | Self::Invalid(ref s) => { 155 | serializer.serialize_newtype_variant("Invalid", 3, "Invalid", s) 156 | } 157 | } 158 | } 159 | } 160 | 161 | impl TagAction { 162 | fn to_enum(p_tag_val: &str) -> Self { 163 | match p_tag_val.to_lowercase().as_str() { 164 | TAG_NONE => Self::None, 165 | TAG_QURANTINE => Self::Qurantine, 166 | TAG_REJECT => Self::Reject, 167 | _ => Self::Invalid(p_tag_val.to_string()), 168 | } 169 | } 170 | } 171 | 172 | #[derive(Debug, PartialEq)] 173 | struct DmarcEntry<'a> { 174 | tag: &'a str, 175 | val: &'a str, 176 | } 177 | 178 | impl<'a> DmarcEntry<'a> { 179 | pub fn new(tag: &'a str, val: &'a str) -> Self { 180 | Self { tag, val } 181 | } 182 | } 183 | #[derive(Debug, Default, PartialEq, Serialize)] 184 | pub struct Dmarc { 185 | domain_name: String, 186 | returned_record: String, 187 | record_type: String, 188 | v: Option, 189 | p: Option, 190 | pct: Option, // TODO: Should change this to a u8 later 191 | rua: Option, 192 | ruf: Option, 193 | sp: Option, 194 | adkim: Option, 195 | aspf: Option, 196 | others: Option, 197 | invalid_flags: Option, 198 | config_v_p_order: Option, 199 | config_v: Option, 200 | config_p: Option, 201 | config_pct: Option, 202 | config_sp: Option, 203 | raw_data: String, 204 | } 205 | 206 | impl Dmarc { 207 | pub fn new(domain_name: &str, dmarc_record: Option) -> Self { 208 | let mut dmarc = Self::default(); 209 | dmarc.domain_name = domain_name.to_string(); 210 | 211 | let dmarc_parsed = match dmarc_record { 212 | Some(ref r) => match r { 213 | DmarcRecordType::Cname(opt_url) => { 214 | dmarc.record_type = CNAME_RECORD.to_string(); 215 | dmarc.returned_record = YES.to_string(); 216 | if let Some(url) = opt_url { 217 | dmarc.raw_data = url.to_string(); 218 | } 219 | return dmarc; 220 | } 221 | DmarcRecordType::Txt(ref opt_txt) => match opt_txt { 222 | Some(ref txt) => { 223 | dmarc.record_type = TXT_RECORD.to_string(); 224 | dmarc.returned_record = YES.to_string(); 225 | DmarcParsed::new(txt) 226 | } 227 | None => { 228 | dmarc.record_type = TXT_RECORD.to_string(); 229 | dmarc.returned_record = YES.to_string(); 230 | return dmarc; 231 | } 232 | }, 233 | DmarcRecordType::Other => { 234 | dmarc.record_type = OTHER_RECORD.to_string(); 235 | dmarc.returned_record = YES.to_string(); 236 | return dmarc; 237 | } 238 | }, 239 | None => { 240 | dmarc.returned_record = NO.to_string(); 241 | return dmarc; 242 | } 243 | }; 244 | 245 | let mut dmarc_entries = match dmarc_parsed.dmarc_entries { 246 | Some(de) => de, 247 | None => { 248 | dmarc.invalid_flags = dmarc_parsed.invalid_entries; 249 | dmarc.raw_data = dmarc_parsed.raw_txt; 250 | return dmarc; 251 | } 252 | }; 253 | 254 | let config_v_p_order = Self::check_v_and_p_order(&dmarc_entries).to_string(); 255 | 256 | dmarc.v = match_tag(V_TAG, &mut dmarc_entries) 257 | .and_then(|v_entry| Some(DmarcVersion::to_ver(v_entry.val))); 258 | 259 | dmarc.p = match_tag(P_TAG, &mut dmarc_entries) 260 | .and_then(|p_entry| Some(TagAction::to_enum(p_entry.val))); 261 | 262 | dmarc.pct = match_tag(PCT_TAG, &mut dmarc_entries) 263 | .and_then(|pct_entry| Some(pct_entry.val.to_string())); 264 | 265 | dmarc.rua = match_tag(RUA_TAG, &mut dmarc_entries) 266 | .and_then(|rua_entry| Some(rua_entry.val.to_string())); 267 | 268 | dmarc.ruf = match_tag(RUF_TAG, &mut dmarc_entries) 269 | .and_then(|ruf_entry| Some(ruf_entry.val.to_string())); 270 | 271 | dmarc.sp = match_tag(SP_TAG, &mut dmarc_entries) 272 | .and_then(|sp_entry| Some(TagAction::to_enum(sp_entry.val))); 273 | 274 | dmarc.adkim = match_tag(ADKIM_TAG, &mut dmarc_entries) 275 | .and_then(|adkim_entry| Some(adkim_entry.val.to_string())); 276 | 277 | dmarc.aspf = match_tag(ASPF_TAG, &mut dmarc_entries) 278 | .and_then(|aspf_entry| Some(aspf_entry.val.to_string())); 279 | 280 | dmarc.others = match dmarc_entries.is_empty() { 281 | true => None, 282 | false => { 283 | let mut others_tmp = String::new(); 284 | for e in dmarc_entries { 285 | others_tmp.push_str(&format!("{}={} ", e.tag, e.val)); 286 | } 287 | 288 | Some(others_tmp) 289 | } 290 | }; 291 | 292 | dmarc.invalid_flags = dmarc_parsed.invalid_entries; 293 | dmarc.config_v_p_order = Some(config_v_p_order); 294 | dmarc.config_v = Some(dmarc.check_v().to_string()); 295 | dmarc.config_p = Some(dmarc.check_p().to_string()); 296 | dmarc.config_pct = Some(dmarc.check_pct().to_string()); 297 | dmarc.config_sp = Some(dmarc.check_sp().to_string()); 298 | dmarc.raw_data = dmarc_parsed.raw_txt; 299 | 300 | dmarc 301 | } 302 | 303 | fn check_v(&self) -> DmarcFieldResult { 304 | match &self.v { 305 | Some(ver) => match ver { 306 | DmarcVersion::Dmarc1 => DmarcFieldResult::ValidConfig, 307 | DmarcVersion::Invalid(s) => DmarcFieldResult::InvalidConfig(s.clone()), 308 | }, 309 | None => DmarcFieldResult::InvalidConfig(ERR_FLAG_NOT_PRESENT.to_string()), 310 | } 311 | } 312 | 313 | fn check_p(&self) -> DmarcFieldResult { 314 | match &self.p { 315 | Some(v) => match v { 316 | TagAction::Invalid(s) => DmarcFieldResult::InvalidConfig(s.clone()), 317 | TagAction::None => DmarcFieldResult::VeryBadConfig(TAG_NONE.to_string()), 318 | _ => DmarcFieldResult::ValidConfig, 319 | }, 320 | None => DmarcFieldResult::InvalidConfig(ERR_FLAG_NOT_PRESENT.to_string()), 321 | } 322 | } 323 | 324 | fn check_pct(&self) -> DmarcFieldResult { 325 | match &self.pct { 326 | Some(pct) => match &pct.parse::() { 327 | Ok(n) => match &self.p { 328 | Some(p) => match p { 329 | TagAction::None => { 330 | DmarcFieldResult::VeryBadConfig(ERR_POLICY_IS_NONE.to_string()) 331 | } 332 | TagAction::Qurantine => { 333 | if *n < 25 { 334 | DmarcFieldResult::VeryBadConfig(pct.clone()) 335 | } else if *n < 100 { 336 | DmarcFieldResult::BadConfig(pct.clone()) 337 | } else if *n > 100 { 338 | DmarcFieldResult::InvalidConfig(pct.clone()) 339 | } else { 340 | DmarcFieldResult::ValidConfig 341 | } 342 | } 343 | TagAction::Reject => match *n > 100 { 344 | true => DmarcFieldResult::InvalidConfig(pct.clone()), 345 | false => DmarcFieldResult::ValidConfig, 346 | }, 347 | TagAction::Invalid(i) => DmarcFieldResult::InvalidConfig(i.clone()), 348 | }, 349 | None => DmarcFieldResult::InvalidConfig(ERR_P_FLAG_MISSING.to_string()), 350 | }, 351 | Err(_) => DmarcFieldResult::InvalidConfig(format!( 352 | "{} <- {}", 353 | pct, 354 | "Is not a number".to_string() 355 | )), 356 | }, 357 | None => DmarcFieldResult::ValidConfig, 358 | } 359 | } 360 | 361 | fn check_v_and_p_order(dmarc_entries: &Vec) -> DmarcFieldResult { 362 | if dmarc_entries.len() < 2 { 363 | return DmarcFieldResult::InvalidConfig(ERR_MISSING_V_OR_P_FLAG.to_string()); 364 | } 365 | 366 | let (v, p) = (&dmarc_entries[0], &dmarc_entries[1]); 367 | 368 | if v.tag != V_TAG { 369 | return DmarcFieldResult::InvalidConfig(ERR_FIRST_FLAG_NOT_V.to_string()); 370 | } 371 | 372 | if p.tag != P_TAG { 373 | return DmarcFieldResult::InvalidConfig(ERR_SECOND_FLAG_NOT_P.to_string()); 374 | } 375 | 376 | DmarcFieldResult::ValidConfig 377 | } 378 | 379 | fn check_sp(&self) -> DmarcFieldResult { 380 | let sp = match &self.sp { 381 | Some(sp) => sp, 382 | None => match &self.p { 383 | Some(p) => p, 384 | None => { 385 | return DmarcFieldResult::InvalidConfig( 386 | ERR_P_FLAG_MISSING_AND_SP_NOT_SET.to_string(), 387 | ) 388 | } 389 | }, 390 | }; 391 | 392 | match sp { 393 | TagAction::None => match &self.p { 394 | Some(p) => match p { 395 | TagAction::Reject => { 396 | DmarcFieldResult::VeryBadConfig(ERR_PERMITS_SUBDOMAIN_SPOOFING.to_string()) 397 | } 398 | _ => DmarcFieldResult::BadConfig(ERR_IGNORE_SUBDOMAIN_DMARC_FAILS.to_string()), 399 | }, 400 | None => DmarcFieldResult::InvalidConfig(ERR_P_FLAG_MISSING.to_string()), 401 | }, 402 | _ => DmarcFieldResult::ValidConfig, 403 | } 404 | } 405 | } 406 | 407 | #[derive(Debug, PartialEq)] 408 | struct DmarcParsed<'a> { 409 | dmarc_entries: Option>>, 410 | invalid_entries: Option, 411 | raw_txt: String, 412 | } 413 | 414 | impl<'a> DmarcParsed<'a> { 415 | fn new(txt: &'a String) -> Self { 416 | let raw_txt_quoted = format!("\"{}\"", txt.clone()); 417 | 418 | if txt.is_empty() { 419 | return Self { 420 | dmarc_entries: None, 421 | invalid_entries: None, 422 | raw_txt: raw_txt_quoted, 423 | }; 424 | } 425 | 426 | let mut dmarc_entries: Vec = Vec::new(); 427 | let mut invalid_entries = String::new(); 428 | let entry_iter = txt.split(';'); 429 | 430 | if entry_iter.clone().count() > 1 { 431 | for e in entry_iter { 432 | match e.find('=') { 433 | Some(idx) => dmarc_entries.push(DmarcEntry::new( 434 | &e[0..idx].trim(), 435 | &e[idx + 1..e.len()].trim(), 436 | )), 437 | None => { 438 | if !e.is_empty() { 439 | invalid_entries.push_str(e) 440 | } 441 | } 442 | } 443 | } 444 | } 445 | 446 | let invalid_entries_opt = match invalid_entries.is_empty() { 447 | true => None, 448 | false => Some(invalid_entries), 449 | }; 450 | 451 | let dmarc_entries_opt = match dmarc_entries.is_empty() { 452 | true => None, 453 | false => Some(dmarc_entries), 454 | }; 455 | 456 | Self { 457 | dmarc_entries: dmarc_entries_opt, 458 | invalid_entries: invalid_entries_opt, 459 | raw_txt: raw_txt_quoted, 460 | } 461 | } 462 | } 463 | 464 | #[derive(Debug, PartialEq)] 465 | pub enum DmarcFieldResult { 466 | ValidConfig, 467 | BadConfig(String), 468 | VeryBadConfig(String), 469 | InvalidConfig(String), 470 | Empty, 471 | } 472 | 473 | impl ToString for DmarcFieldResult { 474 | fn to_string(&self) -> String { 475 | match self { 476 | Self::ValidConfig => "Valid".to_string(), 477 | Self::BadConfig(s) => format!("Bad: {}", s), 478 | Self::VeryBadConfig(s) => format!("Very bad: {}", s), 479 | Self::InvalidConfig(s) => format!("Invalid: {}", s), 480 | Self::Empty => "Empty".to_string(), 481 | } 482 | } 483 | } 484 | 485 | #[test] 486 | fn dmarc_parsed_new() { 487 | assert_eq!( 488 | DmarcParsed::new(&"".to_string()), 489 | DmarcParsed { 490 | dmarc_entries: None, 491 | invalid_entries: None, 492 | raw_txt: "\"\"".to_string() 493 | } 494 | ); 495 | 496 | let invalid_entry = format!("{}={} {}={}", V_TAG, DMARC1, P_TAG, TAG_NONE); 497 | assert_eq!( 498 | DmarcParsed::new(&invalid_entry), 499 | DmarcParsed { 500 | dmarc_entries: None, 501 | invalid_entries: None, 502 | raw_txt: format!("\"{}\"", invalid_entry), 503 | } 504 | ); 505 | 506 | let invalid_tag = "p%fsa"; 507 | let invalid_entry = format!("{}={};{};", V_TAG, DMARC1, invalid_tag); 508 | assert_eq!( 509 | DmarcParsed::new(&invalid_entry), 510 | DmarcParsed { 511 | dmarc_entries: Some(vec![DmarcEntry::new(V_TAG, DMARC1)]), 512 | invalid_entries: Some(invalid_tag.to_string()), 513 | raw_txt: format!("\"{}\"", invalid_entry), 514 | } 515 | ); 516 | 517 | let valid_entry = format!("{}={}; {}={};", V_TAG, DMARC1, P_TAG, TAG_NONE); 518 | assert_eq!( 519 | DmarcParsed::new(&valid_entry), 520 | DmarcParsed { 521 | dmarc_entries: Some(vec![ 522 | DmarcEntry::new(V_TAG, DMARC1), 523 | DmarcEntry::new(P_TAG, TAG_NONE) 524 | ]), 525 | invalid_entries: None, 526 | raw_txt: format!("\"{}\"", valid_entry), 527 | } 528 | ); 529 | } 530 | 531 | // Tests 532 | #[test] 533 | fn dmarc_version_to_ver() { 534 | assert_eq!( 535 | DmarcVersion::Invalid("blah".to_string()), 536 | DmarcVersion::to_ver("blah") 537 | ); 538 | 539 | assert_eq!( 540 | DmarcVersion::Invalid("dmarc1".to_string()), 541 | DmarcVersion::to_ver("dmarc1") 542 | ); 543 | 544 | assert_eq!(DmarcVersion::Dmarc1, DmarcVersion::to_ver(DMARC1)); 545 | } 546 | 547 | #[test] 548 | fn dmarc_check_v() { 549 | let mut dmarc = Dmarc::default(); 550 | 551 | dmarc.v = None; 552 | assert_eq!( 553 | DmarcFieldResult::InvalidConfig(ERR_FLAG_NOT_PRESENT.to_string()), 554 | dmarc.check_v() 555 | ); 556 | 557 | let invalid_ver = "smark"; 558 | dmarc.v = Some(DmarcVersion::Invalid(invalid_ver.to_string())); 559 | assert_eq!( 560 | DmarcFieldResult::InvalidConfig(invalid_ver.to_string()), 561 | dmarc.check_v() 562 | ); 563 | 564 | dmarc.v = Some(DmarcVersion::Dmarc1); 565 | assert_eq!(DmarcFieldResult::ValidConfig, dmarc.check_v()); 566 | } 567 | 568 | #[test] 569 | fn dmarc_check_p() { 570 | let mut dmarc = Dmarc::default(); 571 | 572 | dmarc.p = None; 573 | assert_eq!( 574 | DmarcFieldResult::InvalidConfig(ERR_FLAG_NOT_PRESENT.to_string()), 575 | dmarc.check_p() 576 | ); 577 | 578 | dmarc.p = Some(TagAction::None); 579 | assert_eq!( 580 | DmarcFieldResult::VeryBadConfig(TAG_NONE.to_string()), 581 | dmarc.check_p() 582 | ); 583 | 584 | dmarc.p = Some(TagAction::Qurantine); 585 | assert_eq!(DmarcFieldResult::ValidConfig, dmarc.check_p()); 586 | 587 | dmarc.p = Some(TagAction::Reject); 588 | assert_eq!(DmarcFieldResult::ValidConfig, dmarc.check_p()); 589 | } 590 | 591 | #[test] 592 | fn dmarc_check_pct() { 593 | let mut dmarc = Dmarc::default(); 594 | 595 | dmarc.pct = None; 596 | assert_eq!(DmarcFieldResult::ValidConfig, dmarc.check_pct()); 597 | 598 | let not_number = "a".to_string(); 599 | let zero_pct = "0".to_string(); 600 | let twenty_six_pct = "26".to_string(); 601 | let one_hundred_pct = "100".to_string(); 602 | let two_hundred_pct = "200".to_string(); 603 | 604 | dmarc.pct = Some(not_number.clone()); 605 | assert_eq!( 606 | DmarcFieldResult::InvalidConfig(format!("{} <- Is not a number", not_number)), 607 | dmarc.check_pct() 608 | ); 609 | 610 | dmarc.pct = Some(zero_pct.clone()); 611 | assert_eq!( 612 | DmarcFieldResult::InvalidConfig(ERR_P_FLAG_MISSING.to_string()), 613 | dmarc.check_pct() 614 | ); 615 | 616 | dmarc.p = Some(TagAction::None); 617 | assert_eq!( 618 | DmarcFieldResult::VeryBadConfig(ERR_POLICY_IS_NONE.to_string()), 619 | dmarc.check_pct() 620 | ); 621 | 622 | dmarc.p = Some(TagAction::Qurantine); 623 | dmarc.pct = Some(zero_pct.clone()); 624 | assert_eq!( 625 | DmarcFieldResult::VeryBadConfig(zero_pct.clone()), 626 | dmarc.check_pct() 627 | ); 628 | 629 | dmarc.pct = Some(twenty_six_pct.clone()); 630 | assert_eq!( 631 | DmarcFieldResult::BadConfig(twenty_six_pct.clone()), 632 | dmarc.check_pct() 633 | ); 634 | 635 | dmarc.pct = Some(one_hundred_pct.clone()); 636 | assert_eq!(DmarcFieldResult::ValidConfig, dmarc.check_pct()); 637 | 638 | dmarc.pct = Some(two_hundred_pct.clone()); 639 | assert_eq!( 640 | DmarcFieldResult::InvalidConfig(two_hundred_pct.clone()), 641 | dmarc.check_pct() 642 | ); 643 | 644 | dmarc.p = Some(TagAction::Reject); 645 | dmarc.pct = Some(two_hundred_pct.clone()); 646 | assert_eq!( 647 | DmarcFieldResult::InvalidConfig(two_hundred_pct.clone()), 648 | dmarc.check_pct() 649 | ); 650 | 651 | dmarc.pct = Some(twenty_six_pct.clone()); 652 | assert_eq!(DmarcFieldResult::ValidConfig, dmarc.check_pct()); 653 | } 654 | 655 | #[test] 656 | fn dmarc_check_sp() { 657 | let mut dmarc = Dmarc::default(); 658 | 659 | dmarc.p = None; 660 | dmarc.sp = None; 661 | assert_eq!( 662 | DmarcFieldResult::InvalidConfig(ERR_P_FLAG_MISSING_AND_SP_NOT_SET.to_string()), 663 | dmarc.check_sp() 664 | ); 665 | 666 | dmarc.sp = Some(TagAction::None); 667 | assert_eq!( 668 | DmarcFieldResult::InvalidConfig(ERR_P_FLAG_MISSING.to_string()), 669 | dmarc.check_sp() 670 | ); 671 | 672 | dmarc.p = Some(TagAction::None); 673 | assert_eq!( 674 | DmarcFieldResult::BadConfig(ERR_IGNORE_SUBDOMAIN_DMARC_FAILS.to_string()), 675 | dmarc.check_sp() 676 | ); 677 | 678 | dmarc.p = Some(TagAction::Reject); 679 | assert_eq!( 680 | DmarcFieldResult::VeryBadConfig(ERR_PERMITS_SUBDOMAIN_SPOOFING.to_string()), 681 | dmarc.check_sp() 682 | ); 683 | 684 | dmarc.p = Some(TagAction::Qurantine); 685 | assert_eq!( 686 | DmarcFieldResult::BadConfig(ERR_IGNORE_SUBDOMAIN_DMARC_FAILS.to_string()), 687 | dmarc.check_sp() 688 | ); 689 | 690 | dmarc.sp = Some(TagAction::Reject); 691 | assert_eq!(DmarcFieldResult::ValidConfig, dmarc.check_sp()); 692 | 693 | dmarc.sp = Some(TagAction::Qurantine); 694 | assert_eq!(DmarcFieldResult::ValidConfig, dmarc.check_sp()); 695 | } 696 | 697 | #[test] 698 | fn dmarc_check_v_and_p_order() { 699 | let mut dmarc_entries: Vec = Vec::new(); 700 | 701 | assert_eq!( 702 | Dmarc::check_v_and_p_order(&dmarc_entries), 703 | DmarcFieldResult::InvalidConfig(ERR_MISSING_V_OR_P_FLAG.to_string()), 704 | ); 705 | 706 | dmarc_entries.push(DmarcEntry::new("A", DMARC1)); 707 | assert_eq!( 708 | Dmarc::check_v_and_p_order(&dmarc_entries), 709 | DmarcFieldResult::InvalidConfig(ERR_MISSING_V_OR_P_FLAG.to_string()), 710 | ); 711 | 712 | dmarc_entries.push(DmarcEntry::new("B", TAG_NONE)); 713 | assert_eq!( 714 | Dmarc::check_v_and_p_order(&dmarc_entries), 715 | DmarcFieldResult::InvalidConfig(ERR_FIRST_FLAG_NOT_V.to_string()), 716 | ); 717 | 718 | dmarc_entries[0] = DmarcEntry::new(V_TAG, DMARC1); 719 | assert_eq!( 720 | Dmarc::check_v_and_p_order(&dmarc_entries), 721 | DmarcFieldResult::InvalidConfig(ERR_SECOND_FLAG_NOT_P.to_string()), 722 | ); 723 | 724 | dmarc_entries[1] = DmarcEntry::new(P_TAG, TAG_NONE); 725 | assert_eq!( 726 | Dmarc::check_v_and_p_order(&dmarc_entries), 727 | DmarcFieldResult::ValidConfig, 728 | ); 729 | } 730 | 731 | #[test] 732 | fn tag_action_to_enum() { 733 | let invalid_tag = "Destroy"; 734 | assert_eq!( 735 | TagAction::to_enum(invalid_tag), 736 | TagAction::Invalid(invalid_tag.to_string()) 737 | ); 738 | 739 | assert_eq!(TagAction::to_enum(TAG_NONE), TagAction::None); 740 | assert_eq!(TagAction::to_enum(TAG_QURANTINE), TagAction::Qurantine); 741 | assert_eq!(TagAction::to_enum(TAG_REJECT), TagAction::Reject); 742 | } 743 | 744 | #[test] 745 | fn dmarc_new() { 746 | let test_domain = "google.com"; 747 | let valid = "Valid"; 748 | 749 | let dmarc = Dmarc::new(test_domain, None); 750 | let mut dmarc_compare = Dmarc::default(); 751 | 752 | dmarc_compare.domain_name = test_domain.to_string(); 753 | dmarc_compare.returned_record = NO.to_string(); 754 | assert_eq!(dmarc, dmarc_compare); 755 | 756 | let raw_cname = "microsoft.com".to_string(); 757 | let cname_record = DmarcRecordType::Cname(Some(raw_cname.clone())); 758 | let dmarc = Dmarc::new(test_domain, Some(cname_record)); 759 | dmarc_compare.returned_record = YES.to_string(); 760 | dmarc_compare.record_type = CNAME_RECORD.to_string(); 761 | dmarc_compare.raw_data = raw_cname.clone(); 762 | assert_eq!(dmarc, dmarc_compare); 763 | 764 | let cname_record = DmarcRecordType::Cname(None); 765 | let dmarc = Dmarc::new(test_domain, Some(cname_record)); 766 | dmarc_compare.returned_record = YES.to_string(); 767 | dmarc_compare.record_type = CNAME_RECORD.to_string(); 768 | dmarc_compare.raw_data = "".to_string(); 769 | assert_eq!(dmarc, dmarc_compare); 770 | 771 | let txt_record = DmarcRecordType::Txt(None); 772 | let dmarc = Dmarc::new(test_domain, Some(txt_record)); 773 | dmarc_compare.returned_record = YES.to_string(); 774 | dmarc_compare.record_type = TXT_RECORD.to_string(); 775 | dmarc_compare.raw_data = "".to_string(); 776 | assert_eq!(dmarc, dmarc_compare); 777 | 778 | let txt_record = DmarcRecordType::Other; 779 | let dmarc = Dmarc::new(test_domain, Some(txt_record)); 780 | dmarc_compare.returned_record = YES.to_string(); 781 | dmarc_compare.record_type = OTHER_RECORD.to_string(); 782 | dmarc_compare.raw_data = "".to_string(); 783 | assert_eq!(dmarc, dmarc_compare); 784 | 785 | let raw_txt = format!("{}={}; {}={};", V_TAG, DMARC1, P_TAG, TAG_NONE); 786 | let txt_record = DmarcRecordType::Txt(Some(raw_txt.clone())); 787 | let dmarc = Dmarc::new(test_domain, Some(txt_record)); 788 | dmarc_compare.returned_record = YES.to_string(); 789 | dmarc_compare.record_type = TXT_RECORD.to_string(); 790 | dmarc_compare.v = Some(DmarcVersion::Dmarc1); 791 | dmarc_compare.p = Some(TagAction::None); 792 | dmarc_compare.config_v_p_order = Some(valid.to_string()); 793 | dmarc_compare.config_v = Some(dmarc.check_v().to_string()); 794 | dmarc_compare.config_p = Some(dmarc.check_p().to_string()); 795 | dmarc_compare.config_pct = Some(dmarc.check_pct().to_string()); 796 | dmarc_compare.config_sp = Some(dmarc.check_sp().to_string()); 797 | dmarc_compare.raw_data = format!("\"{}\"", raw_txt); 798 | assert_eq!(dmarc, dmarc_compare); 799 | } 800 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.15" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 8 | dependencies = [ 9 | "memchr", 10 | ] 11 | 12 | [[package]] 13 | name = "ansi_term" 14 | version = "0.11.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 17 | dependencies = [ 18 | "winapi", 19 | ] 20 | 21 | [[package]] 22 | name = "ansi_term" 23 | version = "0.12.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 26 | dependencies = [ 27 | "winapi", 28 | ] 29 | 30 | [[package]] 31 | name = "async-trait" 32 | version = "0.1.48" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf" 35 | dependencies = [ 36 | "proc-macro2", 37 | "quote", 38 | "syn", 39 | ] 40 | 41 | [[package]] 42 | name = "atty" 43 | version = "0.2.14" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 46 | dependencies = [ 47 | "hermit-abi", 48 | "libc", 49 | "winapi", 50 | ] 51 | 52 | [[package]] 53 | name = "autocfg" 54 | version = "1.0.1" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 57 | 58 | [[package]] 59 | name = "bitflags" 60 | version = "1.2.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 63 | 64 | [[package]] 65 | name = "bstr" 66 | version = "0.2.15" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" 69 | dependencies = [ 70 | "lazy_static", 71 | "memchr", 72 | "regex-automata", 73 | "serde", 74 | ] 75 | 76 | [[package]] 77 | name = "byteorder" 78 | version = "1.4.3" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 81 | 82 | [[package]] 83 | name = "bytes" 84 | version = "1.0.1" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" 87 | 88 | [[package]] 89 | name = "cfg-if" 90 | version = "1.0.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 93 | 94 | [[package]] 95 | name = "chrono" 96 | version = "0.4.19" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 99 | dependencies = [ 100 | "libc", 101 | "num-integer", 102 | "num-traits", 103 | "time", 104 | "winapi", 105 | ] 106 | 107 | [[package]] 108 | name = "clap" 109 | version = "2.33.3" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 112 | dependencies = [ 113 | "ansi_term 0.11.0", 114 | "atty", 115 | "bitflags", 116 | "strsim", 117 | "textwrap", 118 | "unicode-width", 119 | "vec_map", 120 | ] 121 | 122 | [[package]] 123 | name = "csv-async" 124 | version = "1.2.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "4e2d954bf8f8a40f2f8ba6edc2f00a69c604317e4d375f76b140617b25492946" 127 | dependencies = [ 128 | "bstr", 129 | "cfg-if", 130 | "csv-core", 131 | "futures", 132 | "itoa", 133 | "ryu", 134 | "serde", 135 | "tokio", 136 | "tokio-stream", 137 | ] 138 | 139 | [[package]] 140 | name = "csv-core" 141 | version = "0.1.10" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 144 | dependencies = [ 145 | "memchr", 146 | ] 147 | 148 | [[package]] 149 | name = "ctor" 150 | version = "0.1.20" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" 153 | dependencies = [ 154 | "quote", 155 | "syn", 156 | ] 157 | 158 | [[package]] 159 | name = "data-encoding" 160 | version = "2.3.2" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" 163 | 164 | [[package]] 165 | name = "diff" 166 | version = "0.1.12" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" 169 | 170 | [[package]] 171 | name = "dmarc_checker" 172 | version = "1.0.1" 173 | dependencies = [ 174 | "clap", 175 | "csv-async", 176 | "env_logger", 177 | "futures", 178 | "pretty_assertions", 179 | "rand", 180 | "serde", 181 | "tokio", 182 | "trust-dns-client", 183 | "trust-dns-proto", 184 | ] 185 | 186 | [[package]] 187 | name = "endian-type" 188 | version = "0.1.2" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 191 | 192 | [[package]] 193 | name = "enum-as-inner" 194 | version = "0.3.3" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" 197 | dependencies = [ 198 | "heck", 199 | "proc-macro2", 200 | "quote", 201 | "syn", 202 | ] 203 | 204 | [[package]] 205 | name = "env_logger" 206 | version = "0.7.1" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 209 | dependencies = [ 210 | "atty", 211 | "humantime", 212 | "log", 213 | "regex", 214 | "termcolor", 215 | ] 216 | 217 | [[package]] 218 | name = "form_urlencoded" 219 | version = "1.0.1" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 222 | dependencies = [ 223 | "matches", 224 | "percent-encoding", 225 | ] 226 | 227 | [[package]] 228 | name = "futures" 229 | version = "0.3.13" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1" 232 | dependencies = [ 233 | "futures-channel", 234 | "futures-core", 235 | "futures-executor", 236 | "futures-io", 237 | "futures-sink", 238 | "futures-task", 239 | "futures-util", 240 | ] 241 | 242 | [[package]] 243 | name = "futures-channel" 244 | version = "0.3.13" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" 247 | dependencies = [ 248 | "futures-core", 249 | "futures-sink", 250 | ] 251 | 252 | [[package]] 253 | name = "futures-core" 254 | version = "0.3.13" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" 257 | 258 | [[package]] 259 | name = "futures-executor" 260 | version = "0.3.13" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1" 263 | dependencies = [ 264 | "futures-core", 265 | "futures-task", 266 | "futures-util", 267 | ] 268 | 269 | [[package]] 270 | name = "futures-io" 271 | version = "0.3.13" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59" 274 | 275 | [[package]] 276 | name = "futures-macro" 277 | version = "0.3.13" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7" 280 | dependencies = [ 281 | "proc-macro-hack", 282 | "proc-macro2", 283 | "quote", 284 | "syn", 285 | ] 286 | 287 | [[package]] 288 | name = "futures-sink" 289 | version = "0.3.13" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3" 292 | 293 | [[package]] 294 | name = "futures-task" 295 | version = "0.3.13" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" 298 | 299 | [[package]] 300 | name = "futures-util" 301 | version = "0.3.13" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" 304 | dependencies = [ 305 | "futures-channel", 306 | "futures-core", 307 | "futures-io", 308 | "futures-macro", 309 | "futures-sink", 310 | "futures-task", 311 | "memchr", 312 | "pin-project-lite", 313 | "pin-utils", 314 | "proc-macro-hack", 315 | "proc-macro-nested", 316 | "slab", 317 | ] 318 | 319 | [[package]] 320 | name = "getrandom" 321 | version = "0.2.2" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 324 | dependencies = [ 325 | "cfg-if", 326 | "libc", 327 | "wasi", 328 | ] 329 | 330 | [[package]] 331 | name = "heck" 332 | version = "0.3.2" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" 335 | dependencies = [ 336 | "unicode-segmentation", 337 | ] 338 | 339 | [[package]] 340 | name = "hermit-abi" 341 | version = "0.1.18" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 344 | dependencies = [ 345 | "libc", 346 | ] 347 | 348 | [[package]] 349 | name = "humantime" 350 | version = "1.3.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 353 | dependencies = [ 354 | "quick-error", 355 | ] 356 | 357 | [[package]] 358 | name = "idna" 359 | version = "0.2.2" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" 362 | dependencies = [ 363 | "matches", 364 | "unicode-bidi", 365 | "unicode-normalization", 366 | ] 367 | 368 | [[package]] 369 | name = "instant" 370 | version = "0.1.9" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" 373 | dependencies = [ 374 | "cfg-if", 375 | ] 376 | 377 | [[package]] 378 | name = "ipnet" 379 | version = "2.3.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" 382 | 383 | [[package]] 384 | name = "itoa" 385 | version = "0.4.7" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 388 | 389 | [[package]] 390 | name = "lazy_static" 391 | version = "1.4.0" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 394 | 395 | [[package]] 396 | name = "libc" 397 | version = "0.2.92" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" 400 | 401 | [[package]] 402 | name = "lock_api" 403 | version = "0.4.3" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" 406 | dependencies = [ 407 | "scopeguard", 408 | ] 409 | 410 | [[package]] 411 | name = "log" 412 | version = "0.4.14" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 415 | dependencies = [ 416 | "cfg-if", 417 | ] 418 | 419 | [[package]] 420 | name = "matches" 421 | version = "0.1.8" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 424 | 425 | [[package]] 426 | name = "memchr" 427 | version = "2.3.4" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 430 | 431 | [[package]] 432 | name = "mio" 433 | version = "0.7.11" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" 436 | dependencies = [ 437 | "libc", 438 | "log", 439 | "miow", 440 | "ntapi", 441 | "winapi", 442 | ] 443 | 444 | [[package]] 445 | name = "miow" 446 | version = "0.3.7" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 449 | dependencies = [ 450 | "winapi", 451 | ] 452 | 453 | [[package]] 454 | name = "nibble_vec" 455 | version = "0.1.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 458 | dependencies = [ 459 | "smallvec", 460 | ] 461 | 462 | [[package]] 463 | name = "ntapi" 464 | version = "0.3.6" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 467 | dependencies = [ 468 | "winapi", 469 | ] 470 | 471 | [[package]] 472 | name = "num-integer" 473 | version = "0.1.44" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 476 | dependencies = [ 477 | "autocfg", 478 | "num-traits", 479 | ] 480 | 481 | [[package]] 482 | name = "num-traits" 483 | version = "0.2.14" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 486 | dependencies = [ 487 | "autocfg", 488 | ] 489 | 490 | [[package]] 491 | name = "num_cpus" 492 | version = "1.13.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 495 | dependencies = [ 496 | "hermit-abi", 497 | "libc", 498 | ] 499 | 500 | [[package]] 501 | name = "once_cell" 502 | version = "1.7.2" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" 505 | 506 | [[package]] 507 | name = "output_vt100" 508 | version = "0.1.2" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" 511 | dependencies = [ 512 | "winapi", 513 | ] 514 | 515 | [[package]] 516 | name = "parking_lot" 517 | version = "0.11.1" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" 520 | dependencies = [ 521 | "instant", 522 | "lock_api", 523 | "parking_lot_core", 524 | ] 525 | 526 | [[package]] 527 | name = "parking_lot_core" 528 | version = "0.8.3" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" 531 | dependencies = [ 532 | "cfg-if", 533 | "instant", 534 | "libc", 535 | "redox_syscall", 536 | "smallvec", 537 | "winapi", 538 | ] 539 | 540 | [[package]] 541 | name = "percent-encoding" 542 | version = "2.1.0" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 545 | 546 | [[package]] 547 | name = "pin-project-lite" 548 | version = "0.2.6" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" 551 | 552 | [[package]] 553 | name = "pin-utils" 554 | version = "0.1.0" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 557 | 558 | [[package]] 559 | name = "ppv-lite86" 560 | version = "0.2.10" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 563 | 564 | [[package]] 565 | name = "pretty_assertions" 566 | version = "0.7.1" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "f297542c27a7df8d45de2b0e620308ab883ad232d06c14b76ac3e144bda50184" 569 | dependencies = [ 570 | "ansi_term 0.12.1", 571 | "ctor", 572 | "diff", 573 | "output_vt100", 574 | ] 575 | 576 | [[package]] 577 | name = "proc-macro-hack" 578 | version = "0.5.19" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 581 | 582 | [[package]] 583 | name = "proc-macro-nested" 584 | version = "0.1.7" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" 587 | 588 | [[package]] 589 | name = "proc-macro2" 590 | version = "1.0.26" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" 593 | dependencies = [ 594 | "unicode-xid", 595 | ] 596 | 597 | [[package]] 598 | name = "quick-error" 599 | version = "1.2.3" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 602 | 603 | [[package]] 604 | name = "quote" 605 | version = "1.0.9" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 608 | dependencies = [ 609 | "proc-macro2", 610 | ] 611 | 612 | [[package]] 613 | name = "radix_trie" 614 | version = "0.2.1" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 617 | dependencies = [ 618 | "endian-type", 619 | "nibble_vec", 620 | ] 621 | 622 | [[package]] 623 | name = "rand" 624 | version = "0.8.3" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 627 | dependencies = [ 628 | "libc", 629 | "rand_chacha", 630 | "rand_core", 631 | "rand_hc", 632 | ] 633 | 634 | [[package]] 635 | name = "rand_chacha" 636 | version = "0.3.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 639 | dependencies = [ 640 | "ppv-lite86", 641 | "rand_core", 642 | ] 643 | 644 | [[package]] 645 | name = "rand_core" 646 | version = "0.6.2" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 649 | dependencies = [ 650 | "getrandom", 651 | ] 652 | 653 | [[package]] 654 | name = "rand_hc" 655 | version = "0.3.0" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 658 | dependencies = [ 659 | "rand_core", 660 | ] 661 | 662 | [[package]] 663 | name = "redox_syscall" 664 | version = "0.2.5" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" 667 | dependencies = [ 668 | "bitflags", 669 | ] 670 | 671 | [[package]] 672 | name = "regex" 673 | version = "1.4.5" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" 676 | dependencies = [ 677 | "aho-corasick", 678 | "memchr", 679 | "regex-syntax", 680 | ] 681 | 682 | [[package]] 683 | name = "regex-automata" 684 | version = "0.1.9" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" 687 | dependencies = [ 688 | "byteorder", 689 | ] 690 | 691 | [[package]] 692 | name = "regex-syntax" 693 | version = "0.6.23" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" 696 | 697 | [[package]] 698 | name = "ryu" 699 | version = "1.0.5" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 702 | 703 | [[package]] 704 | name = "scopeguard" 705 | version = "1.1.0" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 708 | 709 | [[package]] 710 | name = "serde" 711 | version = "1.0.125" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" 714 | dependencies = [ 715 | "serde_derive", 716 | ] 717 | 718 | [[package]] 719 | name = "serde_derive" 720 | version = "1.0.125" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 723 | dependencies = [ 724 | "proc-macro2", 725 | "quote", 726 | "syn", 727 | ] 728 | 729 | [[package]] 730 | name = "signal-hook-registry" 731 | version = "1.3.0" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" 734 | dependencies = [ 735 | "libc", 736 | ] 737 | 738 | [[package]] 739 | name = "slab" 740 | version = "0.4.2" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 743 | 744 | [[package]] 745 | name = "smallvec" 746 | version = "1.6.1" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 749 | 750 | [[package]] 751 | name = "strsim" 752 | version = "0.8.0" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 755 | 756 | [[package]] 757 | name = "syn" 758 | version = "1.0.68" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" 761 | dependencies = [ 762 | "proc-macro2", 763 | "quote", 764 | "unicode-xid", 765 | ] 766 | 767 | [[package]] 768 | name = "termcolor" 769 | version = "1.1.2" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 772 | dependencies = [ 773 | "winapi-util", 774 | ] 775 | 776 | [[package]] 777 | name = "textwrap" 778 | version = "0.11.0" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 781 | dependencies = [ 782 | "unicode-width", 783 | ] 784 | 785 | [[package]] 786 | name = "thiserror" 787 | version = "1.0.24" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" 790 | dependencies = [ 791 | "thiserror-impl", 792 | ] 793 | 794 | [[package]] 795 | name = "thiserror-impl" 796 | version = "1.0.24" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" 799 | dependencies = [ 800 | "proc-macro2", 801 | "quote", 802 | "syn", 803 | ] 804 | 805 | [[package]] 806 | name = "time" 807 | version = "0.1.43" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 810 | dependencies = [ 811 | "libc", 812 | "winapi", 813 | ] 814 | 815 | [[package]] 816 | name = "tinyvec" 817 | version = "1.2.0" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" 820 | dependencies = [ 821 | "tinyvec_macros", 822 | ] 823 | 824 | [[package]] 825 | name = "tinyvec_macros" 826 | version = "0.1.0" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 829 | 830 | [[package]] 831 | name = "tokio" 832 | version = "1.4.0" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" 835 | dependencies = [ 836 | "autocfg", 837 | "bytes", 838 | "libc", 839 | "memchr", 840 | "mio", 841 | "num_cpus", 842 | "once_cell", 843 | "parking_lot", 844 | "pin-project-lite", 845 | "signal-hook-registry", 846 | "tokio-macros", 847 | "winapi", 848 | ] 849 | 850 | [[package]] 851 | name = "tokio-macros" 852 | version = "1.1.0" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" 855 | dependencies = [ 856 | "proc-macro2", 857 | "quote", 858 | "syn", 859 | ] 860 | 861 | [[package]] 862 | name = "tokio-stream" 863 | version = "0.1.5" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0" 866 | dependencies = [ 867 | "futures-core", 868 | "pin-project-lite", 869 | "tokio", 870 | ] 871 | 872 | [[package]] 873 | name = "trust-dns-client" 874 | version = "0.20.1" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "87b05ceb7459ea5c9242da2b92cc221b65b2849d4ff3c19313485ce468c62096" 877 | dependencies = [ 878 | "cfg-if", 879 | "chrono", 880 | "data-encoding", 881 | "futures-channel", 882 | "futures-util", 883 | "lazy_static", 884 | "log", 885 | "radix_trie", 886 | "rand", 887 | "thiserror", 888 | "tokio", 889 | "trust-dns-proto", 890 | ] 891 | 892 | [[package]] 893 | name = "trust-dns-proto" 894 | version = "0.20.1" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "8d57e219ba600dd96c2f6d82eb79645068e14edbc5c7e27514af40436b88150c" 897 | dependencies = [ 898 | "async-trait", 899 | "cfg-if", 900 | "data-encoding", 901 | "enum-as-inner", 902 | "futures-channel", 903 | "futures-io", 904 | "futures-util", 905 | "idna", 906 | "ipnet", 907 | "lazy_static", 908 | "log", 909 | "rand", 910 | "smallvec", 911 | "thiserror", 912 | "tinyvec", 913 | "tokio", 914 | "url", 915 | ] 916 | 917 | [[package]] 918 | name = "unicode-bidi" 919 | version = "0.3.4" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 922 | dependencies = [ 923 | "matches", 924 | ] 925 | 926 | [[package]] 927 | name = "unicode-normalization" 928 | version = "0.1.17" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" 931 | dependencies = [ 932 | "tinyvec", 933 | ] 934 | 935 | [[package]] 936 | name = "unicode-segmentation" 937 | version = "1.7.1" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 940 | 941 | [[package]] 942 | name = "unicode-width" 943 | version = "0.1.8" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 946 | 947 | [[package]] 948 | name = "unicode-xid" 949 | version = "0.2.1" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 952 | 953 | [[package]] 954 | name = "url" 955 | version = "2.2.1" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" 958 | dependencies = [ 959 | "form_urlencoded", 960 | "idna", 961 | "matches", 962 | "percent-encoding", 963 | ] 964 | 965 | [[package]] 966 | name = "vec_map" 967 | version = "0.8.2" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 970 | 971 | [[package]] 972 | name = "wasi" 973 | version = "0.10.2+wasi-snapshot-preview1" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 976 | 977 | [[package]] 978 | name = "winapi" 979 | version = "0.3.9" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 982 | dependencies = [ 983 | "winapi-i686-pc-windows-gnu", 984 | "winapi-x86_64-pc-windows-gnu", 985 | ] 986 | 987 | [[package]] 988 | name = "winapi-i686-pc-windows-gnu" 989 | version = "0.4.0" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 992 | 993 | [[package]] 994 | name = "winapi-util" 995 | version = "0.1.5" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 998 | dependencies = [ 999 | "winapi", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "winapi-x86_64-pc-windows-gnu" 1004 | version = "0.4.0" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1007 | --------------------------------------------------------------------------------