adns_zone/
updates.rs

1use adns_proto::{Name, Record, SoaData, Type, TypeData};
2
3use crate::Zone;
4
5pub struct ZoneUpdate {
6    /// "" for root zone, "name" for 2nd level zone
7    pub zone_name: Name,
8    pub actions: Vec<ZoneUpdateAction>,
9}
10
11pub enum ZoneUpdateAction {
12    CreateZone(Name),
13    DeleteRecords(Name, Option<Type>),
14    DeleteRecord(Name, TypeData),
15    AddRecord(Record),
16}
17
18impl ZoneUpdate {
19    pub fn apply_to(&self, root_zone: &mut Zone) {
20        let zone = if self.zone_name.as_ref().is_empty() {
21            root_zone
22        } else {
23            root_zone.zones.entry(self.zone_name.clone()).or_default()
24        };
25        for action in &self.actions {
26            action.apply_to(&self.zone_name, zone);
27        }
28    }
29}
30
31impl ZoneUpdateAction {
32    pub fn apply_to(&self, zone_name: &Name, zone: &mut Zone) {
33        match self {
34            ZoneUpdateAction::DeleteRecords(name, None) => {
35                if name == zone_name {
36                    zone.records.retain(|record| {
37                        &record.name != name
38                            || record.type_ == Type::SOA
39                            || record.type_ == Type::NS
40                    });
41                } else {
42                    zone.records.retain(|record| &record.name != name);
43                }
44            }
45            ZoneUpdateAction::DeleteRecords(name, Some(type_)) => {
46                if name == zone_name && (*type_ == Type::SOA || *type_ == Type::NS) {
47                    return;
48                }
49                zone.records
50                    .retain(|record| &record.name != name || record.type_ != *type_);
51            }
52            ZoneUpdateAction::DeleteRecord(name, data) => {
53                if name == zone_name
54                    && (data.dns_type() == Type::SOA
55                        || (data.dns_type() == Type::NS
56                            && zone.records.iter().filter(|x| x.type_ == Type::NS).count() <= 1))
57                {
58                    return;
59                }
60                zone.records.retain(|record| {
61                    &record.name != name || record.type_ != data.dns_type() || &record.data != data
62                });
63            }
64            ZoneUpdateAction::AddRecord(record) => {
65                if record.type_ == Type::CNAME {
66                    if zone.records.len() > 1
67                        || zone
68                            .records
69                            .first()
70                            .map(|x| x.type_ != Type::CNAME)
71                            .unwrap_or_default()
72                    {
73                        return;
74                    }
75                } else if zone.records.iter().any(|x| x.type_ == Type::CNAME) {
76                    return;
77                }
78                if record.type_ == Type::SOA {
79                    let Record { data: TypeData::SOA(SoaData { serial: new_serial, .. }), .. } = &record else {
80                        return;
81                    };
82                    if let Some(Record {
83                        data: TypeData::SOA(SoaData { serial, .. }),
84                        ..
85                    }) = zone
86                        .records
87                        .iter()
88                        .find(|x| x.name == record.name && x.type_ == Type::SOA)
89                    {
90                        if serial > new_serial {
91                            return;
92                        }
93                    }
94                }
95                for zone_record in zone.records.iter_mut().filter(|zone_record| {
96                    zone_record.name == record.name && zone_record.type_ == record.type_
97                }) {
98                    if record.type_ == Type::CNAME
99                        || record.type_ == Type::SOA
100                        || record.data == zone_record.data
101                    {
102                        *zone_record = record.clone();
103                        return;
104                    }
105                }
106                zone.records.push(record.clone());
107            }
108            ZoneUpdateAction::CreateZone(name) => {
109                zone.zones.insert(name.clone(), Default::default());
110            }
111        }
112    }
113}