1use serde_json::Value;
8
9use crate::core::dns::{names::relative_to_zone, records::RecordData, responses::ZoneRecord};
10
11pub fn sshfp_algorithm_to_str(n: u64) -> &'static str {
14 match n {
15 1 => "RSA",
16 2 => "DSA",
17 3 => "ECDSA",
18 4 => "Ed25519",
19 6 => "Ed448",
20 _ => "RSA",
21 }
22}
23
24pub fn sshfp_algorithm_to_num(alg: &crate::core::dns::records::SshfpAlgorithm) -> u8 {
25 use crate::core::dns::records::SshfpAlgorithm::*;
26 match alg {
27 Rsa => 1,
28 Dsa => 2,
29 Ecdsa => 3,
30 Ed25519 => 4,
31 Ed448 => 6,
32 }
33}
34
35pub fn sshfp_fp_type_to_str(n: u64) -> &'static str {
36 match n {
37 1 => "SHA1",
38 2 => "SHA256",
39 _ => "SHA256",
40 }
41}
42
43pub fn sshfp_fp_type_to_num(ft: &crate::core::dns::records::SshfpFingerprintType) -> u8 {
44 use crate::core::dns::records::SshfpFingerprintType::*;
45 match ft {
46 Sha1 => 1,
47 Sha256 => 2,
48 }
49}
50
51pub fn tlsa_cert_usage_to_num(cu: &crate::core::dns::records::TlsaCertUsage) -> u8 {
54 use crate::core::dns::records::TlsaCertUsage::*;
55 match cu {
56 PkixTa => 0,
57 PkixEe => 1,
58 DaneTa => 2,
59 DaneEe => 3,
60 }
61}
62
63pub fn tlsa_cert_usage_to_str(n: u64) -> &'static str {
64 match n {
65 0 => "PKIX-TA",
66 1 => "PKIX-EE",
67 2 => "DANE-TA",
68 3 => "DANE-EE",
69 _ => "DANE-EE",
70 }
71}
72
73pub fn tlsa_selector_to_num(s: &crate::core::dns::records::TlsaSelector) -> u8 {
74 use crate::core::dns::records::TlsaSelector::*;
75 match s {
76 Cert => 0,
77 Spki => 1,
78 }
79}
80
81pub fn tlsa_selector_to_str(n: u64) -> &'static str {
82 match n {
83 0 => "Cert",
84 1 => "SPKI",
85 _ => "Cert",
86 }
87}
88
89pub fn tlsa_matching_type_to_num(mt: &crate::core::dns::records::TlsaMatchingType) -> u8 {
90 use crate::core::dns::records::TlsaMatchingType::*;
91 match mt {
92 Full => 0,
93 Sha2_256 => 1,
94 Sha2_512 => 2,
95 }
96}
97
98pub fn tlsa_matching_type_to_str(n: u64) -> &'static str {
99 match n {
100 0 => "Full",
101 1 => "SHA2-256",
102 2 => "SHA2-512",
103 _ => "Full",
104 }
105}
106
107pub fn ds_algorithm_to_num(alg: &crate::core::dns::records::DsAlgorithm) -> u8 {
110 use crate::core::dns::records::DsAlgorithm::*;
111 match alg {
112 Rsamd5 => 1,
113 Dsa => 3,
114 Rsasha1 => 5,
115 DsaNsec3Sha1 => 6,
116 Rsasha1Nsec3Sha1 => 7,
117 Rsasha256 => 8,
118 Rsasha512 => 10,
119 EccGost => 12,
120 Ecdsap256sha256 => 13,
121 Ecdsap384sha384 => 14,
122 Ed25519 => 15,
123 Ed448 => 16,
124 }
125}
126
127pub fn ds_algorithm_to_str(n: u64) -> &'static str {
128 match n {
129 1 => "RSAMD5",
130 3 => "DSA",
131 5 => "RSASHA1",
132 6 => "DSA-NSEC3-SHA1",
133 7 => "RSASHA1-NSEC3-SHA1",
134 8 => "RSASHA256",
135 10 => "RSASHA512",
136 12 => "ECC-GOST",
137 13 => "ECDSAP256SHA256",
138 14 => "ECDSAP384SHA384",
139 15 => "ED25519",
140 16 => "ED448",
141 _ => "RSASHA256",
142 }
143}
144
145pub fn ds_digest_type_to_num(dt: &crate::core::dns::records::DigestType) -> u8 {
146 use crate::core::dns::records::DigestType::*;
147 match dt {
148 Sha1 => 1,
149 Sha256 => 2,
150 GostR341194 => 3,
151 Sha384 => 4,
152 }
153}
154
155pub fn ds_digest_type_to_str(n: u64) -> &'static str {
156 match n {
157 1 => "SHA1",
158 2 => "SHA256",
159 3 => "GOST-R-34-11-94",
160 4 => "SHA384",
161 _ => "SHA256",
162 }
163}
164
165pub fn normalize_rdata(record_type: &str, content: &str, cf_record: &Value) -> Value {
168 match record_type {
169 "A" | "AAAA" => serde_json::json!({ "ipAddress": content }),
170 "CNAME" => serde_json::json!({ "cname": content }),
171 "DNAME" => serde_json::json!({ "dname": content }),
172 "MX" => {
173 let priority = cf_record
174 .get("priority")
175 .and_then(|p| p.as_u64())
176 .unwrap_or(10);
177 serde_json::json!({ "preference": priority, "exchange": content })
178 }
179 "TXT" => serde_json::json!({ "text": content, "splitText": false }),
180 "NS" => serde_json::json!({ "nameServer": content, "glue": null }),
181 "PTR" => serde_json::json!({ "ptrName": content }),
182 "SRV" => {
183 if let Some(data) = cf_record.get("data") {
184 let priority = data.get("priority").and_then(|p| p.as_u64()).unwrap_or(0);
185 let weight = data.get("weight").and_then(|w| w.as_u64()).unwrap_or(0);
186 let port = data.get("port").and_then(|p| p.as_u64()).unwrap_or(0);
187 let target = data.get("target").and_then(|t| t.as_str()).unwrap_or("");
188 serde_json::json!({
189 "priority": priority,
190 "weight": weight,
191 "port": port,
192 "target": target,
193 })
194 } else {
195 serde_json::json!({ "value": content })
196 }
197 }
198 "CAA" => {
199 if let Some(data) = cf_record.get("data") {
200 let flags = data.get("flags").and_then(|f| f.as_u64()).unwrap_or(0);
201 let tag = data.get("tag").and_then(|t| t.as_str()).unwrap_or("");
202 let value = data.get("value").and_then(|v| v.as_str()).unwrap_or("");
203 serde_json::json!({ "flags": flags, "tag": tag, "value": value })
204 } else {
205 serde_json::json!({ "value": content })
206 }
207 }
208 "SSHFP" => {
209 if let Some(data) = cf_record.get("data") {
210 let alg = data.get("algorithm").and_then(|a| a.as_u64()).unwrap_or(1);
211 let fp_type = data.get("type").and_then(|t| t.as_u64()).unwrap_or(2);
212 let fingerprint = data
213 .get("fingerprint")
214 .and_then(|f| f.as_str())
215 .unwrap_or("");
216 serde_json::json!({
217 "sshfpAlgorithm": sshfp_algorithm_to_str(alg),
218 "sshfpFingerprintType": sshfp_fp_type_to_str(fp_type),
219 "sshfpFingerprint": fingerprint,
220 })
221 } else {
222 serde_json::json!({ "value": content })
223 }
224 }
225 "TLSA" => {
226 if let Some(data) = cf_record.get("data") {
227 let usage = data.get("usage").and_then(|u| u.as_u64()).unwrap_or(3);
228 let selector = data.get("selector").and_then(|s| s.as_u64()).unwrap_or(1);
229 let matching_type = data
230 .get("matching_type")
231 .and_then(|m| m.as_u64())
232 .unwrap_or(1);
233 let certificate = data
234 .get("certificate")
235 .and_then(|c| c.as_str())
236 .unwrap_or("");
237 serde_json::json!({
238 "tlsaCertificateUsage": tlsa_cert_usage_to_str(usage),
239 "tlsaSelector": tlsa_selector_to_str(selector),
240 "tlsaMatchingType": tlsa_matching_type_to_str(matching_type),
241 "tlsaCertificateAssociationData": certificate,
242 })
243 } else {
244 serde_json::json!({ "value": content })
245 }
246 }
247 "DS" => {
248 if let Some(data) = cf_record.get("data") {
249 let key_tag = data.get("key_tag").and_then(|k| k.as_u64()).unwrap_or(0);
250 let algorithm = data.get("algorithm").and_then(|a| a.as_u64()).unwrap_or(13);
251 let digest_type = data
252 .get("digest_type")
253 .and_then(|d| d.as_u64())
254 .unwrap_or(2);
255 let digest = data.get("digest").and_then(|d| d.as_str()).unwrap_or("");
256 serde_json::json!({
257 "keyTag": key_tag,
258 "algorithm": ds_algorithm_to_str(algorithm),
259 "digestType": ds_digest_type_to_str(digest_type),
260 "digest": digest,
261 })
262 } else {
263 serde_json::json!({ "value": content })
264 }
265 }
266 "HTTPS" | "SVCB" => {
267 if let Some(data) = cf_record.get("data") {
268 let priority = data.get("priority").and_then(|p| p.as_u64()).unwrap_or(1);
269 let target = data.get("target").and_then(|t| t.as_str()).unwrap_or(".");
270 let params = data.get("value").and_then(|v| v.as_str());
271 serde_json::json!({
272 "svcPriority": priority,
273 "svcTargetName": target,
274 "svcParams": params,
275 "autoIpv4Hint": false,
276 "autoIpv6Hint": false,
277 })
278 } else {
279 serde_json::json!({ "value": content })
280 }
281 }
282 "NAPTR" => {
283 if let Some(data) = cf_record.get("data") {
284 let order = data.get("order").and_then(|o| o.as_u64()).unwrap_or(100);
285 let preference = data
286 .get("preference")
287 .and_then(|p| p.as_u64())
288 .unwrap_or(10);
289 let flags = data.get("flags").and_then(|f| f.as_str()).unwrap_or("");
290 let services = data.get("service").and_then(|s| s.as_str()).unwrap_or("");
291 let regexp = data.get("regexp").and_then(|r| r.as_str()).unwrap_or("");
292 let replacement = data
293 .get("replacement")
294 .and_then(|r| r.as_str())
295 .unwrap_or(".");
296 serde_json::json!({
297 "naptrOrder": order,
298 "naptrPreference": preference,
299 "naptrFlags": flags,
300 "naptrServices": services,
301 "naptrRegexp": regexp,
302 "naptrReplacement": replacement,
303 })
304 } else {
305 serde_json::json!({ "value": content })
306 }
307 }
308 "URI" => {
309 if let Some(data) = cf_record.get("data") {
310 let priority = data.get("priority").and_then(|p| p.as_u64()).unwrap_or(10);
311 let weight = data.get("weight").and_then(|w| w.as_u64()).unwrap_or(1);
312 let uri = data.get("content").and_then(|c| c.as_str()).unwrap_or("");
313 serde_json::json!({
314 "uriPriority": priority,
315 "uriWeight": weight,
316 "uri": uri,
317 })
318 } else {
319 serde_json::json!({ "value": content })
320 }
321 }
322 _ => serde_json::json!({ "value": content }),
323 }
324}
325
326pub fn cloudflare_record_to_zone_record(cf: &Value, zone_name: &str) -> ZoneRecord {
329 let record_type = cf
330 .get("type")
331 .and_then(|t| t.as_str())
332 .unwrap_or("UNKNOWN")
333 .to_uppercase();
334 let cf_name = cf.get("name").and_then(|n| n.as_str()).unwrap_or("");
335 let name = relative_to_zone(cf_name, zone_name);
336 let ttl = cf.get("ttl").and_then(|t| t.as_u64()).unwrap_or(0) as u32;
337 let content = cf.get("content").and_then(|c| c.as_str()).unwrap_or("");
338 let proxied = cf.get("proxied").and_then(|p| p.as_bool()).unwrap_or(false);
339 let comment = cf
340 .get("comment")
341 .and_then(|c| c.as_str())
342 .unwrap_or("")
343 .to_string();
344 let cf_id = cf
345 .get("id")
346 .and_then(|i| i.as_str())
347 .unwrap_or("")
348 .to_string();
349
350 let mut data = normalize_rdata(&record_type, content, cf);
351 if let Some(obj) = data.as_object_mut() {
352 obj.insert("proxied".into(), Value::Bool(proxied));
353 if !cf_id.is_empty() {
354 obj.insert("id".into(), Value::String(cf_id));
355 }
356 }
357
358 ZoneRecord {
359 name,
360 record_type,
361 ttl,
362 disabled: false,
363 comments: comment,
364 expiry_ttl: 0,
365 data,
366 parsed: None,
367 }
368}
369
370pub fn record_data_to_cloudflare_body(name: &str, ttl: u32, record: &RecordData) -> Value {
371 let record_type = record.type_name();
372 match record {
373 RecordData::A { ip } => serde_json::json!({
374 "name": name, "type": record_type,
375 "content": ip.to_string(), "ttl": ttl, "proxied": false,
376 }),
377 RecordData::Aaaa { ip } => serde_json::json!({
378 "name": name, "type": record_type,
379 "content": ip.to_string(), "ttl": ttl, "proxied": false,
380 }),
381 RecordData::Cname { target } => serde_json::json!({
382 "name": name, "type": record_type,
383 "content": target, "ttl": ttl, "proxied": false,
384 }),
385 RecordData::Mx {
386 preference,
387 exchange,
388 } => serde_json::json!({
389 "name": name, "type": record_type,
390 "content": exchange, "priority": preference, "ttl": ttl,
391 }),
392 RecordData::Txt { text, .. } => serde_json::json!({
393 "name": name, "type": record_type,
394 "content": text, "ttl": ttl,
395 }),
396 RecordData::Ns { nameserver, .. } => serde_json::json!({
397 "name": name, "type": record_type,
398 "content": nameserver, "ttl": ttl,
399 }),
400 RecordData::Ptr { name: ptr_name } => serde_json::json!({
401 "name": name, "type": record_type,
402 "content": ptr_name, "ttl": ttl,
403 }),
404 RecordData::Srv {
405 priority,
406 weight,
407 port,
408 target,
409 } => serde_json::json!({
410 "name": name, "type": record_type,
411 "data": { "priority": priority, "weight": weight, "port": port, "target": target },
412 "ttl": ttl,
413 }),
414 RecordData::Caa { flags, tag, value } => serde_json::json!({
415 "name": name, "type": record_type,
416 "data": { "flags": flags, "tag": tag, "value": value },
417 "ttl": ttl,
418 }),
419 RecordData::Dname { dname } => serde_json::json!({
420 "name": name, "type": record_type,
421 "content": dname, "ttl": ttl,
422 }),
423 RecordData::Sshfp {
424 algorithm,
425 fingerprint_type,
426 fingerprint,
427 } => serde_json::json!({
428 "name": name, "type": record_type,
429 "data": {
430 "algorithm": sshfp_algorithm_to_num(algorithm),
431 "type": sshfp_fp_type_to_num(fingerprint_type),
432 "fingerprint": fingerprint,
433 },
434 "ttl": ttl,
435 }),
436 RecordData::Tlsa {
437 cert_usage,
438 selector,
439 matching_type,
440 cert_association_data,
441 } => serde_json::json!({
442 "name": name, "type": record_type,
443 "data": {
444 "usage": tlsa_cert_usage_to_num(cert_usage),
445 "selector": tlsa_selector_to_num(selector),
446 "matching_type": tlsa_matching_type_to_num(matching_type),
447 "certificate": cert_association_data,
448 },
449 "ttl": ttl,
450 }),
451 RecordData::Ds {
452 key_tag,
453 algorithm,
454 digest_type,
455 digest,
456 } => serde_json::json!({
457 "name": name, "type": record_type,
458 "data": {
459 "key_tag": key_tag,
460 "algorithm": ds_algorithm_to_num(algorithm),
461 "digest_type": ds_digest_type_to_num(digest_type),
462 "digest": digest,
463 },
464 "ttl": ttl,
465 }),
466 RecordData::Https {
467 svc_priority,
468 svc_target_name,
469 svc_params,
470 ..
471 }
472 | RecordData::Svcb {
473 svc_priority,
474 svc_target_name,
475 svc_params,
476 ..
477 } => serde_json::json!({
478 "name": name, "type": record_type,
479 "data": {
480 "priority": svc_priority,
481 "target": svc_target_name,
482 "value": svc_params,
483 },
484 "ttl": ttl,
485 }),
486 RecordData::Naptr {
487 order,
488 preference,
489 flags,
490 services,
491 regexp,
492 replacement,
493 } => serde_json::json!({
494 "name": name, "type": record_type,
495 "data": {
496 "order": order,
497 "preference": preference,
498 "flags": flags,
499 "service": services,
500 "regexp": regexp,
501 "replacement": replacement,
502 },
503 "ttl": ttl,
504 }),
505 RecordData::Uri {
506 priority,
507 weight,
508 uri,
509 } => serde_json::json!({
510 "name": name, "type": record_type,
511 "data": { "priority": priority, "weight": weight, "content": uri },
512 "ttl": ttl,
513 }),
514 _ => {
515 let params = record.to_api_params();
516 let content = params
517 .iter()
518 .find(|(k, _)| *k != "type")
519 .map(|(_, v)| v.clone())
520 .unwrap_or_default();
521 serde_json::json!({
522 "name": name, "type": record_type,
523 "content": content, "ttl": ttl,
524 })
525 }
526 }
527}