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