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