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