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