1#![allow(clippy::new_without_default)]
5
6use crate::parameters::DnsRecordType;
7use serde::Serialize;
8use std::net::{Ipv4Addr, Ipv6Addr};
9use validator::{Validate, ValidationError};
10
11fn is_ipv4(content: &str) -> Result<(), ValidationError> {
12 let result = content.parse::<Ipv4Addr>();
13
14 match result {
15 Ok(_v) => Ok(()),
16 Err(_e) => Err(ValidationError::new("IPv4")),
17 }
18}
19
20fn is_ipv6(content: &str) -> Result<(), ValidationError> {
21 let result = content.parse::<Ipv6Addr>();
22
23 match result {
24 Ok(_v) => Ok(()),
25 Err(_e) => Err(ValidationError::new("IPv6")),
26 }
27}
28
29fn is_valid_spf(content: &str) -> Result<(), ValidationError> {
30 if content.starts_with("v=spf1") {
31 return Ok(());
32 }
33 Err(ValidationError::new("SPF"))
34}
35
36#[cfg(test)]
37mod tests {
38
39 use crate::dns::*;
40 use crate::parameters::DnsRecordType;
41
42 #[test]
43 fn should_have_a_type() {
44 let ipv4_record = ARecord::new();
45 assert_eq!(ipv4_record.record_type, DnsRecordType::A)
46 }
47 #[test]
48 fn should_have_aaaa_type() {
49 let ipv6_record = AAAARecord::new();
50 assert_eq!(ipv6_record.record_type, DnsRecordType::AAAA)
51 }
52
53 #[test]
54 #[should_panic]
55 fn should_panic_for_bad_spf_content() {
56 let mut spf = SPFRecord::new();
57 spf.content = "start wrong".into();
58 spf.validate().unwrap();
59 }
60}
61#[derive(Debug, Validate, Serialize)]
65pub struct ARecord {
66 #[serde(rename(serialize = "type"))]
67 record_type: DnsRecordType,
68 #[validate(length(min = 1, max = 255))]
69 pub name: String,
70 #[validate(custom(
71 function = "is_ipv4",
72 message = "The provided content is not a valid IPv4 address"
73 ))]
74 pub content: String,
75 pub ttl: u32,
76}
77
78impl ARecord {
79 pub fn new() -> ARecord {
80 ARecord {
81 record_type: DnsRecordType::A,
82 name: "".into(),
83 content: "".into(),
84 ttl: 1,
85 }
86 }
87}
88#[derive(Debug, Validate, Serialize)]
92pub struct AAAARecord {
93 #[serde(rename(serialize = "type"))]
94 record_type: DnsRecordType,
95 #[validate(length(max = 255))]
96 pub name: String,
97 #[validate(custom(
98 function = "is_ipv6",
99 message = "The provided content is not a valid IPv6 address"
100 ))]
101 pub content: String,
102 pub ttl: u32,
103}
104
105impl AAAARecord {
106 pub fn new() -> AAAARecord {
107 AAAARecord {
108 record_type: DnsRecordType::AAAA,
109 name: "".into(),
110 content: "".into(),
111 ttl: 1,
112 }
113 }
114}
115
116#[derive(Debug, Validate, Serialize)]
120pub struct CNAMERecord {
121 #[serde(rename(serialize = "type"))]
122 pub record_type: DnsRecordType,
123 #[validate(length(max = 255))]
124 pub name: String,
125 #[validate(length(max = 255))]
126 pub content: String,
127 pub ttl: u32,
128}
129
130impl CNAMERecord {
131 pub fn new() -> CNAMERecord {
132 CNAMERecord {
133 record_type: DnsRecordType::CNAME,
134 name: "".into(),
135 content: "".into(),
136 ttl: 1,
137 }
138 }
139}
140#[derive(Debug, Validate, Serialize)]
144pub struct TXTRecord {
145 #[serde(rename(serialize = "type"))]
146 record_type: DnsRecordType,
147 #[validate(length(max = 255))]
148 pub name: String,
149 #[validate(length(max = 255))]
150 pub content: String,
151 pub ttl: u32,
152}
153
154impl TXTRecord {
155 pub fn new() -> TXTRecord {
156 TXTRecord {
157 record_type: DnsRecordType::TXT,
158 name: "".into(),
159 content: "".into(),
160 ttl: 1,
161 }
162 }
163}
164#[derive(Debug, Validate, Serialize)]
168pub struct MXRecord {
169 #[serde(rename(serialize = "type"))]
170 pub record_type: DnsRecordType,
171 #[validate(length(max = 255))]
172 pub name: String,
173 pub content: String,
174 pub ttl: u32,
175 #[validate(range(min = 0, max = 65535))]
176 pub priority: u32,
177}
178
179impl MXRecord {
180 pub fn new() -> MXRecord {
181 MXRecord {
182 record_type: DnsRecordType::MX,
183 name: "".into(),
184 content: "".into(),
185 ttl: 1,
186 priority: 0,
187 }
188 }
189}
190
191#[derive(Debug, Validate, Serialize)]
195pub struct NSRecord {
196 #[serde(rename(serialize = "type"))]
197 pub record_type: DnsRecordType,
198 #[validate(length(max = 255))]
199 pub name: String,
200 pub content: String,
201 pub ttl: u32,
202}
203
204impl NSRecord {
205 pub fn new() -> NSRecord {
206 NSRecord {
207 record_type: DnsRecordType::NS,
208 name: "".into(),
209 content: "".into(),
210 ttl: 1,
211 }
212 }
213}
214#[derive(Debug, Validate, Serialize)]
218pub struct SRVRecord {
219 #[serde(rename(serialize = "type"))]
220 pub record_type: DnsRecordType,
221 #[validate(length(max = 255))]
222 pub name: String,
223 pub content: String,
224 pub ttl: u32,
225}
226
227impl SRVRecord {
228 pub fn new() -> SRVRecord {
229 SRVRecord {
230 record_type: DnsRecordType::SRV,
231 name: "".into(),
232 content: "".into(),
233 ttl: 1,
234 }
235 }
236}
237#[derive(Debug, Validate, Serialize)]
241pub struct CAARecord {
242 #[serde(rename(serialize = "type"))]
243 pub record_type: DnsRecordType,
244 #[validate(length(max = 255))]
245 pub name: String,
246 pub ttl: u32,
247 data: CAAData,
248}
249#[derive(Debug, Validate, Serialize)]
251pub(crate) struct CAAData {
252 flags: u8,
253 tag: String,
254 value: String,
255}
256
257impl CAARecord {
258 pub fn new(ca_authority: String) -> CAARecord {
259 let data = CAAData {
260 flags: 0,
261 tag: "issue".into(),
262 value: ca_authority,
263 };
264 CAARecord {
265 record_type: DnsRecordType::CAA,
266 name: "".into(),
267 ttl: 1,
268 data,
269 }
270 }
271}
272#[derive(Debug, Validate, Serialize)]
276pub struct PTRRecord {
277 #[serde(rename(serialize = "type"))]
278 pub record_type: DnsRecordType,
279 #[validate(length(max = 255))]
280 pub name: String,
281 pub content: String,
282 pub ttl: u32,
283}
284
285impl PTRRecord {
286 pub fn new() -> PTRRecord {
287 PTRRecord {
288 record_type: DnsRecordType::PTR,
289 name: "".into(),
290 content: "".into(),
291 ttl: 1,
292 }
293 }
294}
295#[derive(Debug, Validate, Serialize)]
299pub struct SPFRecord {
300 #[serde(rename(serialize = "type"))]
301 pub record_type: DnsRecordType,
302 #[validate(length(max = 255))]
303 pub name: String,
304 #[validate(custom(
305 function = "is_valid_spf",
306 message = "The provided content should start with v=spf1"
307 ))]
308 pub content: String,
309 pub ttl: u32,
310}
311
312impl SPFRecord {
313 pub fn new() -> SPFRecord {
314 SPFRecord {
315 record_type: DnsRecordType::SPF,
316 name: "".into(),
317 content: "".into(),
318 ttl: 1,
319 }
320 }
321}