chiark / gitweb /
+ * m1test script can invoke `hrecord' differently.
[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= 1000;
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 }
373
374 static void startup(void) {
375   int r;
376
377   if (nonblock(0,1)) sysfail("set stdin to nonblocking mode");
378   if (nonblock(1,1)) sysfail("set stdout to nonblocking mode");
379   memset(&sa,0,sizeof(sa));
380   sa.sin_family= AF_INET;
381   r= adns_init(&ads,0,0);  if (r) adnsfail("init",r);
382   cbyte= -1;
383   inbyte= -1;
384   inbuf= 0;
385   if (!bracket) startaddr();
386 }
387
388 int main(int argc, const char *const *argv) {
389   int r, maxfd;
390   fd_set readfds, writefds, exceptfds;
391   struct outqueuenode *entry;
392   struct timeval *tv, tvbuf, now;
393
394   parseargs(argv);
395   startup();
396
397   while (!inputeof || outqueue.head) {
398     maxfd= 2;
399     tv= 0;
400     FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds);
401     if ((entry= outqueue.head) && !outblocked) {
402       if (!entry->addr) {
403         writestdout(entry);
404         continue;
405       }
406       if (entry->addr->ans) {
407         if (entry->addr->ans->nrrs) 
408           replacetextwithname(entry);
409         entry->addr= 0;
410         continue;
411       }
412       r= gettimeofday(&now,0);  if (r) sysfail("gettimeofday");
413       if (forever) {
414         tv= 0;
415       } else if (!timercmp(&now,&entry->printbefore,<)) {
416         entry->addr= 0;
417         continue;
418       } else {
419         tvbuf.tv_sec= entry->printbefore.tv_sec - now.tv_sec - 1;
420         tvbuf.tv_usec= entry->printbefore.tv_usec - now.tv_usec + 1000000;
421         tvbuf.tv_sec += tvbuf.tv_usec / 1000000;
422         tvbuf.tv_usec %= 1000000;
423         tv= &tvbuf;
424       }
425       adns_beforeselect(ads,&maxfd,&readfds,&writefds,&exceptfds,
426                         &tv,&tvbuf,&now);
427     }
428     if (outblocked) FD_SET(1,&writefds);
429     if (!inputeof && outqueuelen<1024) FD_SET(0,&readfds);
430     
431     r= select(maxfd,&readfds,&writefds,&exceptfds,tv);
432     if (r < 0) { if (r == EINTR) continue; else sysfail("select"); }
433
434     r= gettimeofday(&now,0);  if (r) sysfail("gettimeofday");
435     adns_afterselect(ads,maxfd,&readfds,&writefds,&exceptfds,&now);
436     checkadnsqueries();
437
438     if (FD_ISSET(0,&readfds)) {
439       if (!forever) {
440         printbefore= now;
441         timevaladd(&printbefore,timeout);
442       }
443       readstdin();
444     } else if (FD_ISSET(1,&writefds)) {
445       outblocked= 0;
446     }
447   }
448   if (nonblock(0,0)) sysfail("un-nonblock stdin");
449   if (nonblock(1,0)) sysfail("un-nonblock stdout");
450   if (ferror(stdin) || fclose(stdin)) sysfail("read stdin");
451   if (fclose(stdout)) sysfail("close stdout");
452   exit(0);
453 }