From be5028d85586093643892d176df3915f29a95385 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 30 Jan 2025 15:50:37 -0300 Subject: [PATCH] refactor(zone.rs): Change fn from_master_file and tests for the same fn. It compiles. --- src/zones.rs | 251 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 162 insertions(+), 89 deletions(-) diff --git a/src/zones.rs b/src/zones.rs index 10a66530..f7fc7f11 100644 --- a/src/zones.rs +++ b/src/zones.rs @@ -5,13 +5,14 @@ use crate::message::rdata::a_rdata::ARdata; use crate::message::rdata::cname_rdata::CnameRdata; use crate::message::rdata::soa_rdata::SoaRdata; use crate::message::rdata::aaaa_rdata::AAAARdata; -use crate::message::rdata::Rdata; +use crate::message::rdata::{self, Rdata}; use crate::message::rdata::txt_rdata::TxtRdata; use crate::message::rdata::mx_rdata::MxRdata; use crate::domain_name::DomainName; use crate::message::rdata::ptr_rdata::PtrRdata; use crate::message::resource_record::ResourceRecord; use crate::message::rclass::Rclass; +use crate::message::rdata::ns_rdata::NsRdata; /* The following entries are defined: [] @@ -160,13 +161,18 @@ impl DnsZone { // Variables for the zone let mut name = String::new(); - let mut ttl = 3600; // Default value - let mut soa: Option = None; + let mut ttl = 3600; // Default value of ttl general + let mut soa: SoaRdata = SoaRdata::new(); let mut ns_records = Vec::new(); let mut resource_records = Vec::new(); + //let class = "IN"; // Default value of class general, for the moment only IN is supported - // Variable to check multiples zones - let mut origin_count = 0; + // Variables to work with the file + let mut last_name = String::new(); + let mut last_ttl = 3600; + let mut class = Rclass::IN; + let mut first_line = true; + //let mut count_ns = 0; // Read the file line by line for line in reader.lines() { @@ -177,17 +183,8 @@ impl DnsZone { if line.is_empty() || line.starts_with(';') { continue; } - // Process directives if line.starts_with("$ORIGIN") { - origin_count += 1; - if origin_count > 1 { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Multiple $ORIGIN directives found", - )); - } - name = line.split_whitespace().nth(1).unwrap_or("").to_string(); continue; } @@ -195,86 +192,162 @@ impl DnsZone { ttl = line.split_whitespace().nth(1).unwrap_or("3600").parse().unwrap_or(3600); continue; } - + // $INCLUDE directive is not supported + // Process records - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() < 4 { + let mut parts: Vec<&str> = line.split_whitespace().collect(); + + // Remove comments from the line + if let Some(index) = parts.iter().position(|&x| x == ";") { + parts.truncate(index); + } + + // Assume that the first line is the SOA record + if first_line { + parts.retain(|x| x != &"(" && x != &")"); + let record_name = parts[0]; // The first part is the name of the record + let record_name_string = record_name.to_string(); // Convert to String + name = record_name_string; // Save the name of the zone + last_name = name.clone(); // Save the last name for the next iteration + + // Set the SOA record + soa.set_mname(DomainName::new_from_str(parts[3])); + soa.set_rname(DomainName::new_from_str(parts[4])); + soa.set_serial(parts[5].parse().unwrap_or(0)); + soa.set_refresh(parts[6].parse().unwrap_or(3600)); + soa.set_retry(parts.get(7).unwrap_or(&"1800").parse().unwrap_or(1800)); + soa.set_expire(parts.get(8).unwrap_or(&"1209600").parse().unwrap_or(1209600)); + soa.set_minimum(parts.get(9).unwrap_or(&"3600").parse().unwrap_or(3600)); + ttl = parts[9].parse().unwrap_or(3600); // Save the TTL for the next iteration + // Change the flag to false + first_line = false; continue; - //return Err(io::Error::new( - // io::ErrorKind::InvalidData, - // "Registro de recurso incompleto", - //)); } - let record_name = parts[0]; - - let record_ttl_or_type = parts[1]; - - let rr_type_index = if record_ttl_or_type.parse::().is_ok() { - ttl = record_ttl_or_type.parse().unwrap_or(3600); - 2 - } else { - 1 - }; - - let record_type = parts[rr_type_index]; - let record_class = parts[rr_type_index+1]; - match record_type { - "SOA" => { - if parts.len() >= 7 { - // Crear un SoaRdata vacĂ­o y completarlo con setters - let mut soa_data = SoaRdata::new(); - soa_data.set_mname(DomainName::new_from_str(parts[rr_type_index+2])); - soa_data.set_rname(DomainName::new_from_str(parts[rr_type_index+3])); - soa_data.set_serial(parts[rr_type_index+4].parse().unwrap_or(0)); - soa_data.set_refresh(parts[rr_type_index+5].parse().unwrap_or(3600)); - soa_data.set_retry(parts.get(rr_type_index+6).unwrap_or(&"1800").parse().unwrap_or(1800)); - soa_data.set_expire(parts.get(rr_type_index+7).unwrap_or(&"1209600").parse().unwrap_or(1209600)); - soa_data.set_minimum(parts.get(rr_type_index+8).unwrap_or(&"3600").parse().unwrap_or(3600)); - soa = Some(soa_data); + // Check if the line has at least 4 parts + if parts.len() >= 4 { + let record_name = parts[0]; // The first part is the name of the record + let record_name_string = record_name.to_string(); // Convert to String + last_name = record_name_string; // Save the last name for the next iteration + + let record_ttl_or_type = parts[1]; // The second part is the TTL or the record type + + let mut record_ttl = ttl; // Default value of ttl for the record + + let rr_type_index = if record_ttl_or_type.parse::().is_ok() { // Check if the second part is a TTL + record_ttl = record_ttl_or_type.parse().unwrap_or(3600); // If it is a TTL, save it + 2 // The index of the record type is 3 + } else { // If it is not a TTL, it is the record type + 1 // The index of the record type is 2 + }; + + last_ttl = record_ttl; // Save the last ttl for the next iteration + + let record_type = parts[rr_type_index]; // The third part is the record type + match record_type { + "NS" => { // If the record type is NS + ns_records.push(parts[rr_type_index+1].to_string()); // Save the NS record } + "A" => { + let resource_record = ARdata::rr_from_master_file(parts[rr_type_index+1].split_whitespace(), ttl, "IN", record_name.to_string()); + resource_records.push(resource_record); + } + "AAAA" | "CNAME" | "MX" | "TXT" | "PTR" => { + let rdata = match record_type { + "AAAA" => { + let ip_addr: std::net::IpAddr = parts[rr_type_index+1].parse().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid IP address"))?; + Rdata::AAAA(AAAARdata::new_from_addr(ip_addr)) + }, + "CNAME" => { + let cname = DomainName::new_from_str(parts[rr_type_index+1]); + let mut cname_rdata = CnameRdata::new(); + cname_rdata.set_cname(cname); + let rdata = Rdata::CNAME(cname_rdata); + rdata}, // CNAME + "MX" => Rdata::MX(MxRdata::new()), + "TXT" => Rdata::TXT(TxtRdata::new(vec![parts[rr_type_index+1].to_string()])), + "PTR" => Rdata::PTR(PtrRdata::new()), + _ => continue, + }; + + let mut resource_record = ResourceRecord::new(rdata); + + resource_record.set_name(DomainName::new_from_str(record_name)); + resource_record.set_ttl(last_ttl); + + resource_records.push(resource_record); + } + _ => { + continue; + } // Here is where ZONEMD and other unknow types should be entered. } - "NS" => { - ns_records.push(parts[rr_type_index+1].to_string()); - } - "A" => { - let resource_record = ARdata::rr_from_master_file(parts[rr_type_index+1].split_whitespace(), ttl, record_class, record_name.to_string()); - resource_records.push(resource_record); - } - "AAAA" | "CNAME" | "MX" | "TXT" | "PTR" => { - let rdata = match record_type { - "AAAA" => { - let ip_addr: std::net::IpAddr = parts[rr_type_index+1].parse().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid IP address"))?; - Rdata::AAAA(AAAARdata::new_from_addr(ip_addr)) - }, - "CNAME" => Rdata::CNAME(CnameRdata::new()), // CNAME - "MX" => Rdata::MX(MxRdata::new()), - "TXT" => Rdata::TXT(TxtRdata::new(vec![parts[rr_type_index+1].to_string()])), - "PTR" => Rdata::PTR(PtrRdata::new()), - _ => continue, - }; - - let mut resource_record = ResourceRecord::new(rdata); - - resource_record.set_name(DomainName::new_from_str(record_name)); - resource_record.set_ttl(ttl); - - resource_records.push(resource_record); + } + else if parts.len() < 4 { + //print!("{:?}", parts); + + let record_ttl_or_type = parts[0]; + + let mut record_ttl = ttl; // Default value of ttl for the record + + let rr_type_index = if record_ttl_or_type.parse::().is_ok() { // Check if the second part is a TTL + record_ttl = record_ttl_or_type.parse().unwrap_or(3600); // If it is a TTL, save it + 1 // The index of the record type is 2 + } else { // If it is not a TTL, it is the record type + 0 // The index of the record type is 1 + }; + + + let record_type = parts[rr_type_index]; + let record_data = parts[rr_type_index+1]; + + match record_type { + "NS" => { + ns_records.push(record_data.to_string()); + } + "A" => { + let resource_record = ARdata::rr_from_master_file(record_data.split_whitespace(), ttl, "IN", last_name.to_string()); + resource_records.push(resource_record); + } + "AAAA" | "CNAME" | "MX" | "TXT" | "PTR" => { + let rdata = match record_type { + "AAAA" => { + let ip_addr: std::net::IpAddr = record_data.parse().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid IP address"))?; + Rdata::AAAA(AAAARdata::new_from_addr(ip_addr)) + }, + "CNAME" => { + let cname = DomainName::new_from_str(record_data); + let mut cname_rdata = CnameRdata::new(); + cname_rdata.set_cname(cname); + let rdata = Rdata::CNAME(cname_rdata); + rdata}, // CNAME + "MX" => Rdata::MX(MxRdata::new()), + "TXT" => Rdata::TXT(TxtRdata::new(vec![record_data.to_string()])), + "PTR" => Rdata::PTR(PtrRdata::new()), + _ => continue, + }; + + let mut resource_record = ResourceRecord::new(rdata); + + resource_record.set_name(DomainName::new_from_str(last_name.as_str())); + resource_record.set_ttl(record_ttl); + + resource_records.push(resource_record); + } + _ => { + continue; + } } - _ => { - continue; - /*return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("Registro de recurso desconocido: {}", record_type), - ));*/ - } // Here is where ZONEMD and other unknow types should be entered. } + /*else if parts.len() == 1 { // It is the case of the information of the SOA record + continue;; + } */ } - // Validate and construct the zone + print!("{:?}", resource_records); + // Validate and construct the zone Ok(DnsZone { name, ttl, - soa: soa.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "SOA record missing"))?, + soa, ns_records, resource_records, }) @@ -399,8 +472,8 @@ mod dns_zone_tests { let dns_zone = DnsZone::from_master_file(masterfile_path).unwrap(); // Validate main properties of the zone - assert_eq!(dns_zone.name, "EDU."); - assert_eq!(dns_zone.ttl, 86400); // Default TTL in the file + assert_eq!(dns_zone.name, "EDU."); // The example does not have a line with $ORIGIN + assert_eq!(dns_zone.ttl, 3600); // Default TTL in the file is 3600, the example does not have a line with $TTL assert_eq!(dns_zone.soa.get_mname().get_name(), "SRI-NIC.ARPA."); assert_eq!(dns_zone.soa.get_rname().get_name(), "HOSTMASTER.SRI-NIC.ARPA."); assert_eq!(dns_zone.soa.get_serial(), 870729); @@ -410,14 +483,14 @@ mod dns_zone_tests { assert_eq!(dns_zone.soa.get_minimum(), 86400); // Validate name server records - assert_eq!(dns_zone.get_ns_records().len(), 2); + assert_eq!(dns_zone.get_ns_records().len(), 13); assert!(dns_zone.get_ns_records().contains(&"SRI-NIC.ARPA.".to_string())); assert!(dns_zone.get_ns_records().contains(&"C.ISI.EDU.".to_string())); // Validate resource records - assert_eq!(dns_zone.get_resource_records().len(), 14); // Count A, NS, etc. records + assert_eq!(dns_zone.get_resource_records().len(), 11); // Count A, NS, etc. records assert!(dns_zone.get_resource_records().iter().any(|rr| rr.get_name().get_name() == "ICS.UCI" && matches!(rr.get_rdata(), Rdata::A(_)))); - assert!(dns_zone.get_resource_records().iter().any(|rr| rr.get_name().get_name() == "YALE.EDU." && matches!(rr.get_rdata(), Rdata::NS(_)))); + //assert!(dns_zone.get_resource_records().iter().any(|rr| rr.get_name().get_name() == "YALE.EDU." && matches!(rr.get_rdata(), Rdata::NS(_)))); } #[test] @@ -443,13 +516,13 @@ mod dns_zone_tests { assert_eq!(dns_zone.soa.get_minimum(), 86400); // Validate name server records - assert_eq!(dns_zone.ns_records.len(), 3); + assert_eq!(dns_zone.ns_records.len(), 7); assert!(dns_zone.get_ns_records().contains(&"A.ISI.EDU.".to_string())); assert!(dns_zone.get_ns_records().contains(&"C.ISI.EDU.".to_string())); assert!(dns_zone.get_ns_records().contains(&"SRI-NIC.ARPA.".to_string())); // Validate resource records - assert_eq!(dns_zone.get_resource_records().len(), 14); // Count A, MX, HINFO, etc. records + assert_eq!(dns_zone.get_resource_records().len(), 15); // Count A, MX, HINFO, etc. records assert!(dns_zone.get_resource_records().iter().any(|rr| rr.get_name().get_name() == "MIL." && matches!(rr.get_rdata(), Rdata::NS(_)))); assert!(dns_zone.get_resource_records().iter().any(|rr| rr.get_name().get_name() == "A.ISI.EDU" && matches!(rr.get_rdata(), Rdata::A(_)))); }