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