1use std::sync::{
4 Arc,
5 atomic::{AtomicU64, Ordering},
6};
7
8use pharos_corefile::{Cursor, Error as CorefileError};
9use pharos_proto::{Class, MessageBuilder, NameRef, Opcode, Section, Type};
10use pharos_server::{Context, Error, Next, Outcome, Plugin, Protocol, metrics::Registry};
11
12const TTL: u32 = 604_800;
13const LOCALHOST: &[u8] = b"\x09localhost\0";
14const SOA: &[u8] =
16 b"\x09localhost\0\x04root\x09localhost\0\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\x09\x3a\x80";
17
18#[derive(Debug)]
19pub struct Local {
20 prefix: bool,
21 counter: Option<Arc<AtomicU64>>,
22}
23
24impl Local {
25 pub(crate) fn parse(cursor: &mut Cursor<'_>) -> Result<Self, CorefileError> {
26 cursor.next();
27 if !cursor.remaining_args().is_empty() {
28 return Err(cursor.arg_err());
29 }
30 let mut prefix = true;
31 while cursor.next_block() {
32 if cursor.val() != "localhost_prefix" {
33 return Err(cursor.error("unknown local property"));
34 }
35 let args = cursor.remaining_args();
36 prefix = match args.as_slice() {
37 [value] if value == "on" => true,
38 [value] if value == "off" => false,
39 _ => return Err(cursor.arg_err()),
40 };
41 }
42 Ok(Self {
43 prefix,
44 counter: None,
45 })
46 }
47
48 pub(crate) fn set_metrics(&mut self, registry: &Registry) {
49 self.counter = Some(registry.local());
50 }
51}
52
53impl Plugin for Local {
54 async fn serve<N: Next + Send>(&self, cx: &mut Context<'_>, next: N) -> Outcome {
55 let Some(question) = cx.query.questions().next() else {
56 return next.run(cx).await;
57 };
58 let mut labels = [&[][..]; 127];
59 let mut count = 0;
60 for label in question.name.labels() {
61 labels[count] = label;
62 count += 1;
63 }
64 let eq = |i: usize, value: &[u8]| labels[i].eq_ignore_ascii_case(value);
66 let special =
68 count > 1 && (eq(count - 1, b"localhost") || (self.prefix && eq(0, b"localhost")));
69 let zone_start = if count > 0 && eq(count - 1, b"localhost") {
71 Some(count - 1)
72 } else if count >= 3
73 && eq(count - 2, b"in-addr")
74 && eq(count - 1, b"arpa")
75 && [b"0".as_slice(), b"127", b"255"]
76 .iter()
77 .any(|z| eq(count - 3, z))
78 {
79 Some(count - 3)
80 } else {
81 None
82 };
83 if !special && zone_start.is_none() {
84 return next.run(cx).await;
85 }
86 if special {
87 if pharos_server::logging::debug_enabled() {
88 pharos_server::logging::log_debug(
89 "local",
90 format_args!(
91 "Intercepting localhost query for \"{}\" {}, from {}",
92 question.name,
93 question.qtype,
94 cx.client.ip()
95 ),
96 );
97 }
98 if let Some(counter) = &self.counter {
99 counter.fetch_add(1, Ordering::Relaxed);
100 }
101 }
102 let apex = zone_start == Some(0);
103 let localhost_apex = apex && eq(0, b"localhost");
104 let reverse_ptr = count == 6
105 && eq(0, b"1")
106 && eq(1, b"0")
107 && eq(2, b"0")
108 && eq(3, b"127")
109 && eq(4, b"in-addr")
110 && eq(5, b"arpa");
111 let answer_type = if special || localhost_apex {
113 match question.qtype {
114 Type::A | Type::AAAA => Some(question.qtype),
115 Type::NS | Type::SOA if !special => Some(question.qtype),
116 _ => None,
117 }
118 } else if apex && matches!(question.qtype, Type::NS | Type::SOA) {
119 Some(question.qtype)
120 } else if reverse_ptr && question.qtype == Type::PTR {
121 Some(Type::PTR)
122 } else {
123 None
124 };
125 let nxdomain = !special && !apex && !reverse_ptr;
127 let mut origin = [0u8; 255];
129 let mut len = 0;
130 if !special {
131 for label in &labels[zone_start.unwrap()..count] {
132 origin[len] = label.len() as u8;
133 len += 1;
134 origin[len..len + label.len()].copy_from_slice(label);
135 len += label.len();
136 }
137 }
138 let owner = if special || apex {
139 question.name
140 } else {
141 NameRef::parse(&origin[..=len], 0).expect("valid Domain")
142 };
143 let result = (|| {
144 let max = cx.max_response_size.max(512);
145 if cx.response.capacity() < max {
146 if cx.protocol == Protocol::Tcp {
147 cx.response.reserve(max.saturating_sub(cx.response.len()));
148 } else {
149 return Err(Error::ResponseBufferTooSmall);
150 }
151 }
152 let mut flags = 0x8000 | (cx.query.flags() & 0x7800) | if nxdomain { 3 } else { 0 };
154 if cx.query.opcode() == Opcode::QUERY {
155 flags |= cx.query.flags() & 0x0110;
156 }
157 let mut builder = MessageBuilder::new(cx.response, max, cx.query.id(), flags)
158 .map_err(Error::Build)?;
159 builder
160 .question(question.name, question.qtype, question.qclass)
161 .map_err(Error::Build)?;
162 let rtype = answer_type.unwrap_or(Type::SOA);
163 let rdata: &[u8] = match rtype {
164 Type::A => &[127, 0, 0, 1],
165 Type::AAAA => &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
166 Type::NS | Type::PTR => LOCALHOST,
167 _ => SOA,
168 };
169 builder
170 .record(
171 if answer_type.is_some() {
172 Section::Answer
173 } else {
174 Section::Authority
175 },
176 if answer_type.is_some() {
177 question.name
178 } else {
179 owner
180 },
181 rtype,
182 Class::IN,
183 TTL,
184 rdata,
185 )
186 .map_err(Error::Build)?;
187 let _ = builder.finish();
188 Ok(())
189 })();
190 match result {
191 Ok(()) => Outcome::Response,
192 Err(error) => Outcome::Error(error),
193 }
194 }
195}