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