Licensing: Add copyright for Mark Wooding
[adns] / client / adnsresfilter.c
CommitLineData
592aa664 1/*
2 * adnsresfilter.c
3 * - filter which does resolving, not part of the library
4 */
5/*
ae8cc977 6 * This file is part of adns, which is
26e1c3d6 7 * Copyright (C) 1997-2000,2003,2006,2014 Ian Jackson
17cd4f48 8 * Copyright (C) 2014 Mark Wooding
ae8cc977 9 * Copyright (C) 1999-2000,2003,2006 Tony Finch
10 * Copyright (C) 1991 Massachusetts Institute of Technology
11 * (See the file INSTALL for full details.)
592aa664 12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
7f8bbe29 15 * the Free Software Foundation; either version 3, or (at your option)
592aa664 16 * any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
8c09a4c6 24 * along with this program; if not, write to the Free Software Foundation.
592aa664 25 */
26
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <errno.h>
31#include <search.h>
32#include <assert.h>
33#include <ctype.h>
34
bbede9a3 35#include <sys/types.h>
36#include <unistd.h>
0a4fd9ed 37#include <fcntl.h>
2953d358 38
592aa664 39#include "config.h"
5a0be244 40#include "adns.h"
2953d358 41#include "dlist.h"
42#include "tvarith.h"
aa3ffb57 43#include "client.h"
2953d358 44
98e3fa81 45#ifdef ADNS_REGRESS_TEST
46# include "hredirect.h"
47#endif
48
2953d358 49struct outqueuenode {
50 struct outqueuenode *next, *back;
e22eb8c7 51 char *buffer, *textp;
2953d358 52 int textlen;
53 struct timeval printbefore;
54 struct treething *addr;
55};
56
fd60df66 57static int bracket, forever, address;
51cc4d90 58static unsigned long timeout= 1000;
2953d358 59static adns_rrtype rrt= adns_r_ptr;
98e3fa81 60static adns_initflags initflags= 0;
61static const char *config_text;
2953d358 62
63static int outblocked, inputeof;
64static struct { struct outqueuenode *head, *tail; } outqueue;
65static int peroutqueuenode, outqueuelen;
66
67static struct sockaddr_in sa;
68static adns_state ads;
69
70static char addrtextbuf[14];
71static int cbyte, inbyte, inbuf;
72static unsigned char bytes[4];
73static struct timeval printbefore;
74
75struct treething {
76 unsigned char bytes[4];
77 adns_query qu;
78 adns_answer *ans;
79};
80
81static struct treething *newthing;
82static void *treeroot;
83
84static int nonblock(int fd, int isnonblock) {
85 int r;
86
87 r= fcntl(fd,F_GETFL);
88 if (r==-1) return -1;
89 r= fcntl(fd,F_SETFL, isnonblock ? r|O_NONBLOCK : r&~O_NONBLOCK);
90 if (r==-1) return -1;
91 return 0;
92}
93
aa3ffb57 94void quitnow(int exitstatus) {
2953d358 95 nonblock(0,0);
96 nonblock(1,0);
97 exit(exitstatus);
98}
592aa664 99
100static void sysfail(const char *what) NONRETURNING;
101static void sysfail(const char *what) {
102 fprintf(stderr,"adnsresfilter: system call failed: %s: %s\n",what,strerror(errno));
aa3ffb57 103 quitnow(2);
2953d358 104}
105
106static void *xmalloc(size_t sz) {
107 void *r;
108 r= malloc(sz); if (r) return r;
109 sysfail("malloc");
592aa664 110}
111
112static void outputerr(void) NONRETURNING;
113static void outputerr(void) { sysfail("write to stdout"); }
114
115static void usage(void) {
116 if (printf("usage: adnsresfilter [<options ...>]\n"
aa3ffb57 117 " adnsresfilter -h|--help | --version\n"
70da58ac 118 "options: -t<milliseconds>|--timeout <milliseconds>\n"
119 " -w|--wait (always wait for queries to time out or fail)\n"
120 " -b|--brackets (require [...] around IP addresses)\n"
121 " -a|--address (always include [address] in output)\n"
122 " -u|--unchecked (do not forward map for checking)\n"
98e3fa81 123 " --config <text> (use this instead of resolv.conf)\n"
124 " --debug (turn on adns resolver debugging)\n"
70da58ac 125 "Timeout is the maximum amount to delay any particular bit of output for.\n"
98e3fa81 126 "Lookups will go on in the background. Default timeout = 1000 (ms).\n")
592aa664 127 == EOF) outputerr();
67a57aae 128 if (fflush(stdout)) sysfail("flush stdout");
592aa664 129}
130
131static void usageerr(const char *why) NONRETURNING;
132static void usageerr(const char *why) {
133 fprintf(stderr,"adnsresfilter: bad usage: %s\n",why);
134 usage();
aa3ffb57 135 quitnow(1);
592aa664 136}
137
138static void adnsfail(const char *what, int e) NONRETURNING;
139static void adnsfail(const char *what, int e) {
140 fprintf(stderr,"adnsresfilter: adns call failed: %s: %s\n",what,strerror(e));
aa3ffb57 141 quitnow(2);
592aa664 142}
143
41559f53 144static void settimeout(const char *arg) {
145 char *ep;
146 timeout= strtoul(arg,&ep,0);
147 if (*ep) usageerr("invalid timeout");
592aa664 148}
149
2953d358 150static void parseargs(const char *const *argv) {
592aa664 151 const char *arg;
2953d358 152 int c;
592aa664 153
154 while ((arg= *++argv)) {
155 if (arg[0] != '-') usageerr("no non-option arguments are allowed");
156 if (arg[1] == '-') {
98e3fa81 157 if (!strcmp(arg,"--timeout")) {
158 if (!(arg= *++argv)) usageerr("--timeout needs a value");
159 settimeout(arg);
160 forever= 0;
592aa664 161 } else if (!strcmp(arg,"--wait")) {
162 forever= 1;
98e3fa81 163 } else if (!strcmp(arg,"--brackets")) {
164 bracket= 1;
fd60df66 165 } else if (!strcmp(arg,"--address")) {
166 address= 1;
98e3fa81 167 } else if (!strcmp(arg,"--unchecked")) {
168 rrt= adns_r_ptr_raw;
169 } else if (!strcmp(arg,"--config")) {
170 if (!(arg= *++argv)) usageerr("--config needs a value");
171 config_text= arg;
172 } else if (!strcmp(arg,"--debug")) {
173 initflags |= adns_if_debug;
592aa664 174 } else if (!strcmp(arg,"--help")) {
aa3ffb57 175 usage(); quitnow(0);
176 } else if (!strcmp(arg,"--version")) {
177 VERSION_PRINT_QUIT("adnsresfilter"); quitnow(0);
592aa664 178 } else {
179 usageerr("unknown long option");
180 }
181 } else {
182 while ((c= *++arg)) {
183 switch (c) {
98e3fa81 184 case 't':
185 if (*++arg) settimeout(arg);
186 else if ((arg= *++argv)) settimeout(arg);
187 else usageerr("-t needs a value");
188 forever= 0;
189 arg= "\0";
592aa664 190 break;
191 case 'w':
192 forever= 1;
193 break;
98e3fa81 194 case 'b':
195 bracket= 1;
196 break;
fd60df66 197 case 'a':
198 address= 1;
199 break;
98e3fa81 200 case 'u':
201 rrt= adns_r_ptr_raw;
202 break;
592aa664 203 case 'h':
41559f53 204 usage();
aa3ffb57 205 quitnow(0);
592aa664 206 default:
207 usageerr("unknown short option");
208 }
209 }
210 }
211 }
2953d358 212}
592aa664 213
2953d358 214static void queueoutchar(int c) {
215 struct outqueuenode *entry;
216
217 entry= outqueue.tail;
e22eb8c7 218 if (!entry || entry->addr ||
219 entry->textlen >= peroutqueuenode - (entry->textp - entry->buffer)) {
41559f53 220 peroutqueuenode= !peroutqueuenode || !entry || entry->addr ? 128 :
221 peroutqueuenode >= 1024 ? 4096 : peroutqueuenode<<2;
2953d358 222 entry= xmalloc(sizeof(*entry));
2953d358 223 entry->buffer= xmalloc(peroutqueuenode);
224 entry->textp= entry->buffer;
225 entry->textlen= 0;
226 entry->addr= 0;
227 LIST_LINK_TAIL(outqueue,entry);
228 outqueuelen++;
229 }
41559f53 230 entry->textp[entry->textlen++]= c;
2953d358 231}
592aa664 232
2953d358 233static void queueoutstr(const char *str, int len) {
41559f53 234 while (len-- > 0) queueoutchar(*str++);
2953d358 235}
592aa664 236
2953d358 237static void writestdout(struct outqueuenode *entry) {
238 int r;
239
240 while (entry->textlen) {
241 r= write(1, entry->textp, entry->textlen);
242 if (r < 0) {
243 if (errno == EINTR) continue;
244 if (errno == EAGAIN) { outblocked= 1; break; }
245 sysfail("write stdout");
246 }
41559f53 247 assert(r <= entry->textlen);
2953d358 248 entry->textp += r;
249 entry->textlen -= r;
250 }
251 if (!entry->textlen) {
252 LIST_UNLINK(outqueue,entry);
253 free(entry->buffer);
254 free(entry);
255 outqueuelen--;
256 }
257}
258
259static void replacetextwithname(struct outqueuenode *entry) {
fd60df66 260 char *name, *newbuf;
261 int namelen, newlen;
262
263 name= entry->addr->ans->rrs.str[0];
264 namelen= strlen(name);
265 if (!address) {
266 free(entry->buffer);
267 entry->buffer= 0;
268 entry->textp= name;
269 entry->textlen= namelen;
270 } else {
271 newlen= entry->textlen + namelen + (bracket ? 0 : 2);
272 newbuf= xmalloc(newlen + 1);
273 sprintf(newbuf, bracket ? "%s%.*s" : "%s[%.*s]", name, entry->textlen, entry->textp);
274 free(entry->buffer);
275 entry->buffer= entry->textp= newbuf;
276 entry->textlen= newlen;
277 }
2953d358 278}
279
280static void checkadnsqueries(void) {
281 adns_query qu;
282 adns_answer *ans;
283 void *context;
284 struct treething *foundthing;
285 int r;
286
287 for (;;) {
288 qu= 0; context= 0; ans= 0;
289 r= adns_check(ads,&qu,&ans,&context);
290 if (r == ESRCH || r == EAGAIN) break;
291 assert(!r);
292 foundthing= context;
293 foundthing->ans= ans;
294 foundthing->qu= 0;
295 }
296}
297
298static void restartbuf(void) {
299 if (inbuf>0) queueoutstr(addrtextbuf,inbuf);
592aa664 300 inbuf= 0;
2953d358 301}
302
41559f53 303static int comparer(const void *a, const void *b) {
304 return memcmp(a,b,4);
305}
306
2953d358 307static void procaddr(void) {
308 struct treething *foundthing;
309 void **searchfound;
310 struct outqueuenode *entry;
311 int r;
312
313 if (!newthing) {
314 newthing= xmalloc(sizeof(struct treething));
315 newthing->qu= 0;
316 newthing->ans= 0;
317 }
318
319 memcpy(newthing->bytes,bytes,4);
320 searchfound= tsearch(newthing,&treeroot,comparer);
321 if (!searchfound) sysfail("tsearch");
322 foundthing= *searchfound;
323
324 if (foundthing == newthing) {
41559f53 325 newthing= 0;
2953d358 326 memcpy(&sa.sin_addr,bytes,4);
327 r= adns_submit_reverse(ads, (const struct sockaddr*)&sa,
328 rrt,0,foundthing,&foundthing->qu);
329 if (r) adnsfail("submit",r);
330 }
331 entry= xmalloc(sizeof(*entry));
332 entry->buffer= xmalloc(inbuf);
333 entry->textp= entry->buffer;
334 memcpy(entry->textp,addrtextbuf,inbuf);
335 entry->textlen= inbuf;
336 entry->addr= foundthing;
41559f53 337 entry->printbefore= printbefore;
2953d358 338 LIST_LINK_TAIL(outqueue,entry);
339 outqueuelen++;
340 inbuf= 0;
341 cbyte= -1;
342}
343
344static void startaddr(void) {
345 bytes[cbyte=0]= 0;
346 inbyte= 0;
347}
348
349static void readstdin(void) {
350 char readbuf[512], *p;
351 int r, c, nbyte;
352
353 while ((r= read(0,readbuf,sizeof(readbuf))) <= 0) {
354 if (r == 0) { inputeof= 1; return; }
355 if (r == EAGAIN) return;
356 if (r != EINTR) sysfail("read stdin");
357 }
358 for (p=readbuf; r>0; r--,p++) {
41559f53 359 c= *p;
592aa664 360 if (cbyte==-1 && bracket && c=='[') {
2953d358 361 addrtextbuf[inbuf++]= c;
592aa664 362 startaddr();
363 } else if (cbyte==-1 && !bracket && !isalnum(c)) {
2953d358 364 queueoutchar(c);
592aa664 365 startaddr();
366 } else if (cbyte>=0 && inbyte<3 && c>='0' && c<='9' &&
367 (nbyte= bytes[cbyte]*10 + (c-'0')) <= 255) {
368 bytes[cbyte]= nbyte;
2953d358 369 addrtextbuf[inbuf++]= c;
592aa664 370 inbyte++;
371 } else if (cbyte>=0 && cbyte<3 && inbyte>0 && c=='.') {
372 bytes[++cbyte]= 0;
2953d358 373 addrtextbuf[inbuf++]= c;
592aa664 374 inbyte= 0;
375 } else if (cbyte==3 && inbyte>0 && bracket && c==']') {
2953d358 376 addrtextbuf[inbuf++]= c;
592aa664 377 procaddr();
378 } else if (cbyte==3 && inbyte>0 && !bracket && !isalnum(c)) {
379 procaddr();
2953d358 380 queueoutchar(c);
592aa664 381 startaddr();
382 } else {
383 restartbuf();
2953d358 384 queueoutchar(c);
592aa664 385 cbyte= -1;
386 if (!bracket && !isalnum(c)) startaddr();
387 }
388 }
2953d358 389}
390
391static void startup(void) {
392 int r;
393
2953d358 394 if (nonblock(0,1)) sysfail("set stdin to nonblocking mode");
395 if (nonblock(1,1)) sysfail("set stdout to nonblocking mode");
396 memset(&sa,0,sizeof(sa));
397 sa.sin_family= AF_INET;
98e3fa81 398 if (config_text) {
399 r= adns_init_strcfg(&ads,initflags,stderr,config_text);
400 } else {
401 r= adns_init(&ads,initflags,0);
402 }
403 if (r) adnsfail("init",r);
2953d358 404 cbyte= -1;
405 inbyte= -1;
406 inbuf= 0;
407 if (!bracket) startaddr();
408}
409
410int main(int argc, const char *const *argv) {
411 int r, maxfd;
412 fd_set readfds, writefds, exceptfds;
413 struct outqueuenode *entry;
414 struct timeval *tv, tvbuf, now;
415
416 parseargs(argv);
417 startup();
418
419 while (!inputeof || outqueue.head) {
420 maxfd= 2;
421 tv= 0;
422 FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds);
423 if ((entry= outqueue.head) && !outblocked) {
424 if (!entry->addr) {
425 writestdout(entry);
426 continue;
41559f53 427 }
428 if (entry->addr->ans) {
2953d358 429 if (entry->addr->ans->nrrs)
430 replacetextwithname(entry);
431 entry->addr= 0;
432 continue;
433 }
434 r= gettimeofday(&now,0); if (r) sysfail("gettimeofday");
435 if (forever) {
436 tv= 0;
437 } else if (!timercmp(&now,&entry->printbefore,<)) {
438 entry->addr= 0;
439 continue;
440 } else {
41559f53 441 tvbuf.tv_sec= entry->printbefore.tv_sec - now.tv_sec - 1;
442 tvbuf.tv_usec= entry->printbefore.tv_usec - now.tv_usec + 1000000;
2953d358 443 tvbuf.tv_sec += tvbuf.tv_usec / 1000000;
444 tvbuf.tv_usec %= 1000000;
445 tv= &tvbuf;
446 }
447 adns_beforeselect(ads,&maxfd,&readfds,&writefds,&exceptfds,
448 &tv,&tvbuf,&now);
449 }
450 if (outblocked) FD_SET(1,&writefds);
41559f53 451 if (!inputeof && outqueuelen<1024) FD_SET(0,&readfds);
2953d358 452
453 r= select(maxfd,&readfds,&writefds,&exceptfds,tv);
454 if (r < 0) { if (r == EINTR) continue; else sysfail("select"); }
455
456 r= gettimeofday(&now,0); if (r) sysfail("gettimeofday");
457 adns_afterselect(ads,maxfd,&readfds,&writefds,&exceptfds,&now);
458 checkadnsqueries();
459
460 if (FD_ISSET(0,&readfds)) {
461 if (!forever) {
462 printbefore= now;
463 timevaladd(&printbefore,timeout);
464 }
465 readstdin();
466 } else if (FD_ISSET(1,&writefds)) {
467 outblocked= 0;
468 }
469 }
2953d358 470 if (nonblock(0,0)) sysfail("un-nonblock stdin");
471 if (nonblock(1,0)) sysfail("un-nonblock stdout");
67a57aae 472 adns_finish(ads);
592aa664 473 exit(0);
474}