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