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