Commit | Line | Data |
---|---|---|
dc0d77d7 CE |
1 | #include <unistd.h> |
2 | #include "droproot.h" | |
3 | #include "exit.h" | |
4 | #include "env.h" | |
5 | #include "uint32.h" | |
6 | #include "uint16.h" | |
7 | #include "ip4.h" | |
8 | #include "tai.h" | |
9 | #include "buffer.h" | |
10 | #include "timeoutread.h" | |
11 | #include "timeoutwrite.h" | |
12 | #include "open.h" | |
13 | #include "seek.h" | |
14 | #include "cdb.h" | |
15 | #include "stralloc.h" | |
16 | #include "strerr.h" | |
17 | #include "str.h" | |
18 | #include "byte.h" | |
19 | #include "case.h" | |
20 | #include "dns.h" | |
21 | #include "scan.h" | |
22 | #include "qlog.h" | |
23 | #include "response.h" | |
24 | ||
25 | extern int respond(char *,char *,char *); | |
26 | ||
27 | #define FATAL "axfrdns: fatal: " | |
28 | ||
29 | void nomem() | |
30 | { | |
31 | strerr_die2x(111,FATAL,"out of memory"); | |
32 | } | |
33 | void die_truncated() | |
34 | { | |
35 | strerr_die2x(111,FATAL,"truncated request"); | |
36 | } | |
37 | void die_netwrite() | |
38 | { | |
39 | strerr_die2sys(111,FATAL,"unable to write to network: "); | |
40 | } | |
41 | void die_netread() | |
42 | { | |
43 | strerr_die2sys(111,FATAL,"unable to read from network: "); | |
44 | } | |
45 | void die_outside() | |
46 | { | |
47 | strerr_die2x(111,FATAL,"unable to locate information in data.cdb"); | |
48 | } | |
49 | void die_cdbread() | |
50 | { | |
51 | strerr_die2sys(111,FATAL,"unable to read data.cdb: "); | |
52 | } | |
53 | void die_cdbformat() | |
54 | { | |
55 | strerr_die3x(111,FATAL,"unable to read data.cdb: ","format error"); | |
56 | } | |
57 | ||
58 | int safewrite(int fd,char *buf,unsigned int len) | |
59 | { | |
60 | int w; | |
61 | ||
62 | w = timeoutwrite(60,fd,buf,len); | |
63 | if (w <= 0) die_netwrite(); | |
64 | return w; | |
65 | } | |
66 | ||
67 | char netwritespace[1024]; | |
68 | buffer netwrite = BUFFER_INIT(safewrite,1,netwritespace,sizeof netwritespace); | |
69 | ||
70 | void print(char *buf,unsigned int len) | |
71 | { | |
72 | char tcpheader[2]; | |
73 | uint16_pack_big(tcpheader,len); | |
74 | buffer_put(&netwrite,tcpheader,2); | |
75 | buffer_put(&netwrite,buf,len); | |
76 | buffer_flush(&netwrite); | |
77 | } | |
78 | ||
79 | char *axfr; | |
80 | static char *axfrok; | |
81 | ||
82 | void axfrcheck(char *q) | |
83 | { | |
84 | int i; | |
85 | int j; | |
86 | ||
87 | if (!axfr) return; | |
88 | ||
89 | i = j = 0; | |
90 | for (;;) { | |
91 | if (!axfr[i] || (axfr[i] == '/')) { | |
92 | if (i > j) { | |
93 | if (!dns_domain_fromdot(&axfrok,axfr + j,i - j)) nomem(); | |
94 | if (dns_domain_equal(q,axfrok)) return; | |
95 | } | |
96 | j = i + 1; | |
97 | } | |
98 | if (!axfr[i]) break; | |
99 | ++i; | |
100 | } | |
101 | ||
102 | strerr_die2x(111,FATAL,"disallowed zone transfer request"); | |
103 | } | |
104 | ||
105 | static char *zone; | |
106 | unsigned int zonelen; | |
107 | char typeclass[4]; | |
108 | ||
109 | int fdcdb; | |
110 | buffer bcdb; | |
111 | char bcdbspace[1024]; | |
112 | ||
113 | void get(char *buf,unsigned int len) | |
114 | { | |
115 | int r; | |
116 | ||
117 | while (len > 0) { | |
118 | r = buffer_get(&bcdb,buf,len); | |
119 | if (r < 0) die_cdbread(); | |
120 | if (!r) die_cdbformat(); | |
121 | buf += r; | |
122 | len -= r; | |
123 | } | |
124 | } | |
125 | ||
126 | char ip[4]; | |
127 | unsigned long port; | |
128 | char clientloc[2]; | |
129 | ||
130 | struct tai now; | |
131 | char data[32767]; | |
132 | uint32 dlen; | |
133 | uint32 dpos; | |
134 | ||
135 | void copy(char *buf,unsigned int len) | |
136 | { | |
137 | dpos = dns_packet_copy(data,dlen,dpos,buf,len); | |
138 | if (!dpos) die_cdbread(); | |
139 | } | |
140 | ||
141 | void doname(stralloc *sa) | |
142 | { | |
143 | static char *d; | |
144 | dpos = dns_packet_getname(data,dlen,dpos,&d); | |
145 | if (!dpos) die_cdbread(); | |
146 | if (!stralloc_catb(sa,d,dns_domain_length(d))) nomem(); | |
147 | } | |
148 | ||
149 | int build(stralloc *sa,char *q,int flagsoa,char id[2]) | |
150 | { | |
151 | unsigned int rdatapos; | |
152 | char misc[20]; | |
153 | char type[2]; | |
154 | char recordloc[2]; | |
155 | char ttl[4]; | |
156 | char ttd[8]; | |
157 | struct tai cutoff; | |
158 | ||
159 | dpos = 0; | |
160 | copy(type,2); | |
161 | if (flagsoa) if (byte_diff(type,2,DNS_T_SOA)) return 0; | |
162 | if (!flagsoa) if (byte_equal(type,2,DNS_T_SOA)) return 0; | |
163 | ||
164 | if (!stralloc_copyb(sa,id,2)) nomem(); | |
165 | if (!stralloc_catb(sa,"\204\000\0\0\0\1\0\0\0\0",10)) nomem(); | |
166 | copy(misc,1); | |
167 | if ((misc[0] == '=' + 1) || (misc[0] == '*' + 1)) { | |
168 | --misc[0]; | |
169 | copy(recordloc,2); | |
170 | if (byte_diff(recordloc,2,clientloc)) return 0; | |
171 | } | |
172 | if (misc[0] == '*') { | |
173 | if (flagsoa) return 0; | |
174 | if (!stralloc_catb(sa,"\1*",2)) nomem(); | |
175 | } | |
176 | if (!stralloc_catb(sa,q,dns_domain_length(q))) nomem(); | |
177 | if (!stralloc_catb(sa,type,2)) nomem(); | |
178 | ||
179 | copy(ttl,4); | |
180 | copy(ttd,8); | |
181 | if (byte_diff(ttd,8,"\0\0\0\0\0\0\0\0")) { | |
182 | tai_unpack(ttd,&cutoff); | |
183 | if (byte_equal(ttl,4,"\0\0\0\0")) { | |
184 | if (tai_less(&cutoff,&now)) return 0; | |
185 | uint32_pack_big(ttl,2); | |
186 | } | |
187 | else | |
188 | if (!tai_less(&cutoff,&now)) return 0; | |
189 | } | |
190 | ||
191 | if (!stralloc_catb(sa,DNS_C_IN,2)) nomem(); | |
192 | if (!stralloc_catb(sa,ttl,4)) nomem(); | |
193 | if (!stralloc_catb(sa,"\0\0",2)) nomem(); | |
194 | rdatapos = sa->len; | |
195 | ||
196 | if (byte_equal(type,2,DNS_T_SOA)) { | |
197 | doname(sa); | |
198 | doname(sa); | |
199 | copy(misc,20); | |
200 | if (!stralloc_catb(sa,misc,20)) nomem(); | |
201 | } | |
202 | else if (byte_equal(type,2,DNS_T_NS) || byte_equal(type,2,DNS_T_PTR) || byte_equal(type,2,DNS_T_CNAME)) { | |
203 | doname(sa); | |
204 | } | |
205 | else if (byte_equal(type,2,DNS_T_MX)) { | |
206 | copy(misc,2); | |
207 | if (!stralloc_catb(sa,misc,2)) nomem(); | |
208 | doname(sa); | |
209 | } | |
210 | else | |
211 | if (!stralloc_catb(sa,data + dpos,dlen - dpos)) nomem(); | |
212 | ||
213 | if (sa->len > 65535) die_cdbformat(); | |
214 | uint16_pack_big(sa->s + rdatapos - 2,sa->len - rdatapos); | |
215 | return 1; | |
216 | } | |
217 | ||
218 | static struct cdb c; | |
219 | static char *q; | |
220 | static stralloc soa; | |
221 | static stralloc message; | |
222 | ||
223 | void doaxfr(char id[2]) | |
224 | { | |
225 | char key[512]; | |
226 | uint32 klen; | |
227 | char num[4]; | |
228 | uint32 eod; | |
229 | uint32 pos; | |
230 | int r; | |
231 | ||
232 | axfrcheck(zone); | |
233 | ||
234 | tai_now(&now); | |
235 | cdb_init(&c,fdcdb); | |
236 | ||
237 | byte_zero(clientloc,2); | |
238 | key[0] = 0; | |
239 | key[1] = '%'; | |
240 | byte_copy(key + 2,4,ip); | |
241 | r = cdb_find(&c,key,6); | |
242 | if (!r) r = cdb_find(&c,key,5); | |
243 | if (!r) r = cdb_find(&c,key,4); | |
244 | if (!r) r = cdb_find(&c,key,3); | |
245 | if (!r) r = cdb_find(&c,key,2); | |
246 | if (r == -1) die_cdbread(); | |
247 | if (r && (cdb_datalen(&c) == 2)) | |
248 | if (cdb_read(&c,clientloc,2,cdb_datapos(&c)) == -1) die_cdbread(); | |
249 | ||
250 | cdb_findstart(&c); | |
251 | for (;;) { | |
252 | r = cdb_findnext(&c,zone,zonelen); | |
253 | if (r == -1) die_cdbread(); | |
254 | if (!r) die_outside(); | |
255 | dlen = cdb_datalen(&c); | |
256 | if (dlen > sizeof data) die_cdbformat(); | |
257 | if (cdb_read(&c,data,dlen,cdb_datapos(&c)) == -1) die_cdbformat(); | |
258 | if (build(&soa,zone,1,id)) break; | |
259 | } | |
260 | ||
261 | cdb_free(&c); | |
262 | print(soa.s,soa.len); | |
263 | ||
264 | seek_begin(fdcdb); | |
265 | buffer_init(&bcdb,buffer_unixread,fdcdb,bcdbspace,sizeof bcdbspace); | |
266 | ||
267 | pos = 0; | |
268 | get(num,4); pos += 4; | |
269 | uint32_unpack(num,&eod); | |
270 | while (pos < 2048) { get(num,4); pos += 4; } | |
271 | ||
272 | while (pos < eod) { | |
273 | if (eod - pos < 8) die_cdbformat(); | |
274 | get(num,4); pos += 4; | |
275 | uint32_unpack(num,&klen); | |
276 | get(num,4); pos += 4; | |
277 | uint32_unpack(num,&dlen); | |
278 | if (eod - pos < klen) die_cdbformat(); | |
279 | pos += klen; | |
280 | if (eod - pos < dlen) die_cdbformat(); | |
281 | pos += dlen; | |
282 | ||
283 | if (klen > sizeof key) die_cdbformat(); | |
284 | get(key,klen); | |
285 | if (dlen > sizeof data) die_cdbformat(); | |
286 | get(data,dlen); | |
287 | ||
288 | if ((klen > 1) && (key[0] == 0)) continue; /* location */ | |
289 | if (klen < 1) die_cdbformat(); | |
290 | if (dns_packet_getname(key,klen,0,&q) != klen) die_cdbformat(); | |
291 | if (!dns_domain_suffix(q,zone)) continue; | |
292 | if (!build(&message,q,0,id)) continue; | |
293 | print(message.s,message.len); | |
294 | } | |
295 | ||
296 | print(soa.s,soa.len); | |
297 | } | |
298 | ||
299 | void netread(char *buf,unsigned int len) | |
300 | { | |
301 | int r; | |
302 | ||
303 | while (len > 0) { | |
304 | r = timeoutread(60,0,buf,len); | |
305 | if (r == 0) _exit(0); | |
306 | if (r < 0) die_netread(); | |
307 | buf += r; len -= r; | |
308 | } | |
309 | } | |
310 | ||
311 | char tcpheader[2]; | |
312 | char buf[512]; | |
313 | uint16 len; | |
314 | ||
315 | static char seed[128]; | |
316 | ||
317 | int main() | |
318 | { | |
319 | unsigned int pos; | |
320 | char header[12]; | |
321 | char qtype[2]; | |
322 | char qclass[2]; | |
323 | const char *x; | |
324 | ||
325 | droproot(FATAL); | |
326 | dns_random_init(seed); | |
327 | ||
328 | axfr = env_get("AXFR"); | |
329 | ||
330 | x = env_get("TCPREMOTEIP"); | |
331 | if (x && ip4_scan(x,ip)) | |
332 | ; | |
333 | else | |
334 | byte_zero(ip,4); | |
335 | ||
336 | x = env_get("TCPREMOTEPORT"); | |
337 | if (!x) x = "0"; | |
338 | scan_ulong(x,&port); | |
339 | ||
340 | for (;;) { | |
341 | netread(tcpheader,2); | |
342 | uint16_unpack_big(tcpheader,&len); | |
343 | if (len > 512) strerr_die2x(111,FATAL,"excessively large request"); | |
344 | netread(buf,len); | |
345 | ||
346 | pos = dns_packet_copy(buf,len,0,header,12); if (!pos) die_truncated(); | |
347 | if (header[2] & 254) strerr_die2x(111,FATAL,"bogus query"); | |
348 | if (header[4] || (header[5] != 1)) strerr_die2x(111,FATAL,"bogus query"); | |
349 | ||
350 | pos = dns_packet_getname(buf,len,pos,&zone); if (!pos) die_truncated(); | |
351 | zonelen = dns_domain_length(zone); | |
352 | pos = dns_packet_copy(buf,len,pos,qtype,2); if (!pos) die_truncated(); | |
353 | pos = dns_packet_copy(buf,len,pos,qclass,2); if (!pos) die_truncated(); | |
354 | ||
355 | if (byte_diff(qclass,2,DNS_C_IN) && byte_diff(qclass,2,DNS_C_ANY)) | |
356 | strerr_die2x(111,FATAL,"bogus query: bad class"); | |
357 | ||
358 | qlog(ip,port,header,zone,qtype," "); | |
359 | ||
360 | if (byte_equal(qtype,2,DNS_T_AXFR)) { | |
361 | case_lowerb(zone,zonelen); | |
362 | fdcdb = open_read("data.cdb"); | |
363 | if (fdcdb == -1) die_cdbread(); | |
364 | doaxfr(header); | |
365 | close(fdcdb); | |
366 | } | |
367 | else { | |
368 | if (!response_query(zone,qtype,qclass)) nomem(); | |
369 | response[2] |= 4; | |
370 | case_lowerb(zone,zonelen); | |
371 | response_id(header); | |
372 | response[3] &= ~128; | |
373 | if (!(header[2] & 1)) response[2] &= ~1; | |
374 | if (!respond(zone,qtype,ip)) die_outside(); | |
375 | print(response,response_len); | |
376 | } | |
377 | } | |
378 | } |