1 | /* vi: set sw=4 ts=4: */
|
---|
2 | /*
|
---|
3 | * Mini DNS server implementation for busybox
|
---|
4 | *
|
---|
5 | * Copyright (C) 2005 Roberto A. Foglietta (me@roberto.foglietta.name)
|
---|
6 | * Copyright (C) 2005 Odd Arild Olsen (oao at fibula dot no)
|
---|
7 | * Copyright (C) 2003 Paul Sheer
|
---|
8 | *
|
---|
9 | * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
|
---|
10 | *
|
---|
11 | * Odd Arild Olsen started out with the sheerdns [1] of Paul Sheer and rewrote
|
---|
12 | * it into a shape which I believe is both easier to understand and maintain.
|
---|
13 | * I also reused the input buffer for output and removed services he did not
|
---|
14 | * need. [1] http://threading.2038bug.com/sheerdns/
|
---|
15 | *
|
---|
16 | * Some bugfix and minor changes was applied by Roberto A. Foglietta who made
|
---|
17 | * the first porting of oao' scdns to busybox also.
|
---|
18 | */
|
---|
19 |
|
---|
20 | #include <syslog.h>
|
---|
21 | #include "libbb.h"
|
---|
22 |
|
---|
23 | //#define DEBUG 1
|
---|
24 | #define DEBUG 0
|
---|
25 |
|
---|
26 | enum {
|
---|
27 | MAX_HOST_LEN = 16, // longest host name allowed is 15
|
---|
28 | IP_STRING_LEN = 18, // .xxx.xxx.xxx.xxx\0
|
---|
29 |
|
---|
30 | //must be strlen('.in-addr.arpa') larger than IP_STRING_LEN
|
---|
31 | MAX_NAME_LEN = (IP_STRING_LEN + 13),
|
---|
32 |
|
---|
33 | /* Cannot get bigger packets than 512 per RFC1035
|
---|
34 | In practice this can be set considerably smaller:
|
---|
35 | Length of response packet is header (12B) + 2*type(4B) + 2*class(4B) +
|
---|
36 | ttl(4B) + rlen(2B) + r (MAX_NAME_LEN =21B) +
|
---|
37 | 2*querystring (2 MAX_NAME_LEN= 42B), all together 90 Byte
|
---|
38 | */
|
---|
39 | MAX_PACK_LEN = 512 + 1,
|
---|
40 |
|
---|
41 | DEFAULT_TTL = 30, // increase this when not testing?
|
---|
42 |
|
---|
43 | REQ_A = 1,
|
---|
44 | REQ_PTR = 12
|
---|
45 | };
|
---|
46 |
|
---|
47 | struct dns_repl { // resource record, add 0 or 1 to accepted dns_msg in resp
|
---|
48 | uint16_t rlen;
|
---|
49 | uint8_t *r; // resource
|
---|
50 | uint16_t flags;
|
---|
51 | };
|
---|
52 |
|
---|
53 | struct dns_head { // the message from client and first part of response mag
|
---|
54 | uint16_t id;
|
---|
55 | uint16_t flags;
|
---|
56 | uint16_t nquer; // accepts 0
|
---|
57 | uint16_t nansw; // 1 in response
|
---|
58 | uint16_t nauth; // 0
|
---|
59 | uint16_t nadd; // 0
|
---|
60 | };
|
---|
61 | struct dns_prop {
|
---|
62 | uint16_t type;
|
---|
63 | uint16_t class;
|
---|
64 | };
|
---|
65 | struct dns_entry { // element of known name, ip address and reversed ip address
|
---|
66 | struct dns_entry *next;
|
---|
67 | char ip[IP_STRING_LEN]; // dotted decimal IP
|
---|
68 | char rip[IP_STRING_LEN]; // length decimal reversed IP
|
---|
69 | char name[MAX_HOST_LEN];
|
---|
70 | };
|
---|
71 |
|
---|
72 | static struct dns_entry *dnsentry;
|
---|
73 | static uint32_t ttl = DEFAULT_TTL;
|
---|
74 |
|
---|
75 | static const char *fileconf = "/etc/dnsd.conf";
|
---|
76 |
|
---|
77 | // Must match getopt32 call
|
---|
78 | #define OPT_daemon (option_mask32 & 0x10)
|
---|
79 | #define OPT_verbose (option_mask32 & 0x20)
|
---|
80 |
|
---|
81 |
|
---|
82 | /*
|
---|
83 | * Convert host name from C-string to dns length/string.
|
---|
84 | */
|
---|
85 | static void convname(char *a, uint8_t *q)
|
---|
86 | {
|
---|
87 | int i = (q[0] == '.') ? 0 : 1;
|
---|
88 | for (; i < MAX_HOST_LEN-1 && *q; i++, q++)
|
---|
89 | a[i] = tolower(*q);
|
---|
90 | a[0] = i - 1;
|
---|
91 | a[i] = 0;
|
---|
92 | }
|
---|
93 |
|
---|
94 | /*
|
---|
95 | * Insert length of substrings instead of dots
|
---|
96 | */
|
---|
97 | static void undot(uint8_t * rip)
|
---|
98 | {
|
---|
99 | int i = 0, s = 0;
|
---|
100 | while (rip[i])
|
---|
101 | i++;
|
---|
102 | for (--i; i >= 0; i--) {
|
---|
103 | if (rip[i] == '.') {
|
---|
104 | rip[i] = s;
|
---|
105 | s = 0;
|
---|
106 | } else s++;
|
---|
107 | }
|
---|
108 | }
|
---|
109 |
|
---|
110 | /*
|
---|
111 | * Read one line of hostname/IP from file
|
---|
112 | * Returns 0 for each valid entry read, -1 at EOF
|
---|
113 | * Assumes all host names are lower case only
|
---|
114 | * Hostnames with more than one label are not handled correctly.
|
---|
115 | * Presently the dot is copied into name without
|
---|
116 | * converting to a length/string substring for that label.
|
---|
117 | */
|
---|
118 | static int getfileentry(FILE * fp, struct dns_entry *s)
|
---|
119 | {
|
---|
120 | unsigned int a,b,c,d;
|
---|
121 | char *line, *r, *name;
|
---|
122 |
|
---|
123 | restart:
|
---|
124 | line = r = xmalloc_fgets(fp);
|
---|
125 | if (!r)
|
---|
126 | return -1;
|
---|
127 | while (*r == ' ' || *r == '\t') {
|
---|
128 | r++;
|
---|
129 | if (!*r || *r == '#' || *r == '\n') {
|
---|
130 | free(line);
|
---|
131 | goto restart; /* skipping empty/blank and commented lines */
|
---|
132 | }
|
---|
133 | }
|
---|
134 | name = r;
|
---|
135 | while (*r != ' ' && *r != '\t')
|
---|
136 | r++;
|
---|
137 | *r++ = '\0';
|
---|
138 | if (sscanf(r, ".%u.%u.%u.%u"+1, &a, &b, &c, &d) != 4) {
|
---|
139 | free(line);
|
---|
140 | goto restart; /* skipping wrong lines */
|
---|
141 | }
|
---|
142 |
|
---|
143 | sprintf(s->ip, ".%u.%u.%u.%u"+1, a, b, c, d);
|
---|
144 | sprintf(s->rip, ".%u.%u.%u.%u", d, c, b, a);
|
---|
145 | undot((uint8_t*)s->rip);
|
---|
146 | convname(s->name, (uint8_t*)name);
|
---|
147 |
|
---|
148 | if (OPT_verbose)
|
---|
149 | fprintf(stderr, "\tname:%s, ip:%s\n", &(s->name[1]),s->ip);
|
---|
150 |
|
---|
151 | free(line);
|
---|
152 | return 0;
|
---|
153 | }
|
---|
154 |
|
---|
155 | /*
|
---|
156 | * Read hostname/IP records from file
|
---|
157 | */
|
---|
158 | static void dnsentryinit(void)
|
---|
159 | {
|
---|
160 | FILE *fp;
|
---|
161 | struct dns_entry *m, *prev;
|
---|
162 |
|
---|
163 | prev = dnsentry = NULL;
|
---|
164 | fp = xfopen(fileconf, "r");
|
---|
165 |
|
---|
166 | while (1) {
|
---|
167 | m = xzalloc(sizeof(*m));
|
---|
168 | /*m->next = NULL;*/
|
---|
169 | if (getfileentry(fp, m))
|
---|
170 | break;
|
---|
171 |
|
---|
172 | if (prev == NULL)
|
---|
173 | dnsentry = m;
|
---|
174 | else
|
---|
175 | prev->next = m;
|
---|
176 | prev = m;
|
---|
177 | }
|
---|
178 | fclose(fp);
|
---|
179 | }
|
---|
180 |
|
---|
181 | /*
|
---|
182 | * Look query up in dns records and return answer if found
|
---|
183 | * qs is the query string, first byte the string length
|
---|
184 | */
|
---|
185 | static int table_lookup(uint16_t type, uint8_t * as, uint8_t * qs)
|
---|
186 | {
|
---|
187 | int i;
|
---|
188 | struct dns_entry *d = dnsentry;
|
---|
189 |
|
---|
190 | do {
|
---|
191 | #if DEBUG
|
---|
192 | char *p,*q;
|
---|
193 | q = (char *)&(qs[1]);
|
---|
194 | p = &(d->name[1]);
|
---|
195 | fprintf(stderr, "\n%s: %d/%d p:%s q:%s %d",
|
---|
196 | __FUNCTION__, (int)strlen(p), (int)(d->name[0]),
|
---|
197 | p, q, (int)strlen(q));
|
---|
198 | #endif
|
---|
199 | if (type == REQ_A) { /* search by host name */
|
---|
200 | for (i = 1; i <= (int)(d->name[0]); i++)
|
---|
201 | if (tolower(qs[i]) != d->name[i])
|
---|
202 | break;
|
---|
203 | if (i > (int)(d->name[0])) {
|
---|
204 | strcpy((char *)as, d->ip);
|
---|
205 | #if DEBUG
|
---|
206 | fprintf(stderr, " OK as:%s\n", as);
|
---|
207 | #endif
|
---|
208 | return 0;
|
---|
209 | }
|
---|
210 | } else if (type == REQ_PTR) { /* search by IP-address */
|
---|
211 | if (!strncmp((char*)&d->rip[1], (char*)&qs[1], strlen(d->rip)-1)) {
|
---|
212 | strcpy((char *)as, d->name);
|
---|
213 | return 0;
|
---|
214 | }
|
---|
215 | }
|
---|
216 | d = d->next;
|
---|
217 | } while (d);
|
---|
218 | return -1;
|
---|
219 | }
|
---|
220 |
|
---|
221 |
|
---|
222 | /*
|
---|
223 | * Decode message and generate answer
|
---|
224 | */
|
---|
225 | static int process_packet(uint8_t * buf)
|
---|
226 | {
|
---|
227 | struct dns_head *head;
|
---|
228 | struct dns_prop *qprop;
|
---|
229 | struct dns_repl outr;
|
---|
230 | void *next, *from, *answb;
|
---|
231 |
|
---|
232 | uint8_t answstr[MAX_NAME_LEN + 1];
|
---|
233 | int lookup_result, type, len, packet_len;
|
---|
234 | uint16_t flags;
|
---|
235 |
|
---|
236 | answstr[0] = '\0';
|
---|
237 |
|
---|
238 | head = (struct dns_head *)buf;
|
---|
239 | if (head->nquer == 0) {
|
---|
240 | bb_error_msg("no queries");
|
---|
241 | return -1;
|
---|
242 | }
|
---|
243 |
|
---|
244 | if (head->flags & 0x8000) {
|
---|
245 | bb_error_msg("ignoring response packet");
|
---|
246 | return -1;
|
---|
247 | }
|
---|
248 |
|
---|
249 | from = (void *)&head[1]; // start of query string
|
---|
250 | next = answb = from + strlen((char *)from) + 1 + sizeof(struct dns_prop); // where to append answer block
|
---|
251 |
|
---|
252 | outr.rlen = 0; // may change later
|
---|
253 | outr.r = NULL;
|
---|
254 | outr.flags = 0;
|
---|
255 |
|
---|
256 | qprop = (struct dns_prop *)(answb - 4);
|
---|
257 | type = ntohs(qprop->type);
|
---|
258 |
|
---|
259 | // only let REQ_A and REQ_PTR pass
|
---|
260 | if (!(type == REQ_A || type == REQ_PTR)) {
|
---|
261 | goto empty_packet; /* we can't handle the query type */
|
---|
262 | }
|
---|
263 |
|
---|
264 | if (ntohs(qprop->class) != 1 /* class INET */ ) {
|
---|
265 | outr.flags = 4; /* not supported */
|
---|
266 | goto empty_packet;
|
---|
267 | }
|
---|
268 | /* we only support standard queries */
|
---|
269 |
|
---|
270 | if ((ntohs(head->flags) & 0x7800) != 0)
|
---|
271 | goto empty_packet;
|
---|
272 |
|
---|
273 | // We have a standard query
|
---|
274 | bb_info_msg("%s", (char *)from);
|
---|
275 | lookup_result = table_lookup(type, answstr, (uint8_t*)from);
|
---|
276 | if (lookup_result != 0) {
|
---|
277 | outr.flags = 3 | 0x0400; //name do not exist and auth
|
---|
278 | goto empty_packet;
|
---|
279 | }
|
---|
280 | if (type == REQ_A) { // return an address
|
---|
281 | struct in_addr a;
|
---|
282 | if (!inet_aton((char*)answstr, &a)) {//dotted dec to long conv
|
---|
283 | outr.flags = 1; /* Frmt err */
|
---|
284 | goto empty_packet;
|
---|
285 | }
|
---|
286 | memcpy(answstr, &a.s_addr, 4); // save before a disappears
|
---|
287 | outr.rlen = 4; // uint32_t IP
|
---|
288 | } else
|
---|
289 | outr.rlen = strlen((char *)answstr) + 1; // a host name
|
---|
290 | outr.r = answstr; // 32 bit ip or a host name
|
---|
291 | outr.flags |= 0x0400; /* authority-bit */
|
---|
292 | // we have an answer
|
---|
293 | head->nansw = htons(1);
|
---|
294 |
|
---|
295 | // copy query block to answer block
|
---|
296 | len = answb - from;
|
---|
297 | memcpy(answb, from, len);
|
---|
298 | next += len;
|
---|
299 |
|
---|
300 | // and append answer rr
|
---|
301 | *(uint32_t *) next = htonl(ttl);
|
---|
302 | next += 4;
|
---|
303 | *(uint16_t *) next = htons(outr.rlen);
|
---|
304 | next += 2;
|
---|
305 | memcpy(next, (void *)answstr, outr.rlen);
|
---|
306 | next += outr.rlen;
|
---|
307 |
|
---|
308 | empty_packet:
|
---|
309 |
|
---|
310 | flags = ntohs(head->flags);
|
---|
311 | // clear rcode and RA, set responsebit and our new flags
|
---|
312 | flags |= (outr.flags & 0xff80) | 0x8000;
|
---|
313 | head->flags = htons(flags);
|
---|
314 | head->nauth = head->nadd = htons(0);
|
---|
315 | head->nquer = htons(1);
|
---|
316 |
|
---|
317 | packet_len = (uint8_t *)next - buf;
|
---|
318 | return packet_len;
|
---|
319 | }
|
---|
320 |
|
---|
321 | /*
|
---|
322 | * Exit on signal
|
---|
323 | */
|
---|
324 | static void interrupt(int x)
|
---|
325 | {
|
---|
326 | /* unlink("/var/run/dnsd.lock"); */
|
---|
327 | bb_error_msg("interrupt, exiting\n");
|
---|
328 | exit(2);
|
---|
329 | }
|
---|
330 |
|
---|
331 | int dnsd_main(int argc, char **argv);
|
---|
332 | int dnsd_main(int argc, char **argv)
|
---|
333 | {
|
---|
334 | const char *listen_interface = "0.0.0.0";
|
---|
335 | char *sttl, *sport;
|
---|
336 | len_and_sockaddr *lsa;
|
---|
337 | int udps;
|
---|
338 | uint16_t port = 53;
|
---|
339 | uint8_t buf[MAX_PACK_LEN];
|
---|
340 |
|
---|
341 | getopt32(argv, "i:c:t:p:dv", &listen_interface, &fileconf, &sttl, &sport);
|
---|
342 | //if (option_mask32 & 0x1) // -i
|
---|
343 | //if (option_mask32 & 0x2) // -c
|
---|
344 | if (option_mask32 & 0x4) // -t
|
---|
345 | ttl = xatou_range(sttl, 1, 0xffffffff);
|
---|
346 | if (option_mask32 & 0x8) // -p
|
---|
347 | port = xatou_range(sport, 1, 0xffff);
|
---|
348 |
|
---|
349 | if (OPT_verbose) {
|
---|
350 | bb_info_msg("listen_interface: %s", listen_interface);
|
---|
351 | bb_info_msg("ttl: %d, port: %d", ttl, port);
|
---|
352 | bb_info_msg("fileconf: %s", fileconf);
|
---|
353 | }
|
---|
354 |
|
---|
355 | if (OPT_daemon) {
|
---|
356 | bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
|
---|
357 | openlog(applet_name, LOG_PID, LOG_DAEMON);
|
---|
358 | logmode = LOGMODE_SYSLOG;
|
---|
359 | }
|
---|
360 |
|
---|
361 | dnsentryinit();
|
---|
362 |
|
---|
363 | signal(SIGINT, interrupt);
|
---|
364 | /* why? signal(SIGPIPE, SIG_IGN); */
|
---|
365 | signal(SIGHUP, SIG_IGN);
|
---|
366 | #ifdef SIGTSTP
|
---|
367 | signal(SIGTSTP, SIG_IGN);
|
---|
368 | #endif
|
---|
369 | #ifdef SIGURG
|
---|
370 | signal(SIGURG, SIG_IGN);
|
---|
371 | #endif
|
---|
372 |
|
---|
373 | lsa = xdotted2sockaddr(listen_interface, port);
|
---|
374 | udps = xsocket(lsa->sa.sa_family, SOCK_DGRAM, 0);
|
---|
375 | xbind(udps, &lsa->sa, lsa->len);
|
---|
376 | /* xlisten(udps, 50); - ?!! DGRAM sockets are never listened on I think? */
|
---|
377 | bb_info_msg("Accepting UDP packets on %s",
|
---|
378 | xmalloc_sockaddr2dotted(&lsa->sa));
|
---|
379 |
|
---|
380 | while (1) {
|
---|
381 | int r;
|
---|
382 | socklen_t fromlen = lsa->len;
|
---|
383 | // FIXME: need to get *DEST* address (to which of our addresses
|
---|
384 | // this query was directed), and reply from the same address.
|
---|
385 | // Or else we can exhibit usual UDP ugliness:
|
---|
386 | // [ip1.multihomed.ip2] <= query to ip1 <= peer
|
---|
387 | // [ip1.multihomed.ip2] => reply from ip2 => peer (confused)
|
---|
388 | r = recvfrom(udps, buf, sizeof(buf), 0, &lsa->sa, &fromlen);
|
---|
389 | if (OPT_verbose)
|
---|
390 | bb_info_msg("Got UDP packet");
|
---|
391 | if (r < 12 || r > 512) {
|
---|
392 | bb_error_msg("invalid packet size");
|
---|
393 | continue;
|
---|
394 | }
|
---|
395 | r = process_packet(buf);
|
---|
396 | if (r <= 0)
|
---|
397 | continue;
|
---|
398 | sendto(udps, buf, r, 0, &lsa->sa, fromlen);
|
---|
399 | }
|
---|
400 | return 0;
|
---|
401 | }
|
---|