chiark / gitweb /
adns_pollfds and struct pollfd support - on branch, because it's difficult
[adns.git] / src / setup.c
1 /*
2  * setup.c
3  * - configuration file parsing
4  * - management of global state
5  */
6 /*
7  *  This file is part of adns, which is Copyright (C) 1997-1999 Ian Jackson
8  *  
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2, or (at your option)
12  *  any later version.
13  *  
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *  
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software Foundation,
21  *  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
22  */
23
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29
30 #include <netdb.h>
31 #include <arpa/inet.h>
32
33 #include "internal.h"
34
35 static void readconfig(adns_state ads, const char *filename);
36
37 static void addserver(adns_state ads, struct in_addr addr) {
38   int i;
39   struct server *ss;
40   
41   for (i=0; i<ads->nservers; i++) {
42     if (ads->servers[i].addr.s_addr == addr.s_addr) {
43       adns__debug(ads,-1,0,"duplicate nameserver %s ignored",inet_ntoa(addr));
44       return;
45     }
46   }
47   
48   if (ads->nservers>=MAXSERVERS) {
49     adns__diag(ads,-1,0,"too many nameservers, ignoring %s",inet_ntoa(addr));
50     return;
51   }
52
53   ss= ads->servers+ads->nservers;
54   ss->addr= addr;
55   ads->nservers++;
56 }
57
58 static void saveerr(adns_state ads, int en) {
59   if (!ads->configerrno) ads->configerrno= en;
60 }
61
62 static void configparseerr(adns_state ads, const char *fn, int lno,
63                            const char *fmt, ...) {
64   va_list al;
65
66   saveerr(ads,EINVAL);
67   if (!ads->diagfile || (ads->iflags & adns_if_noerrprint)) return;
68
69   if (lno==-1) fprintf(ads->diagfile,"adns: %s: ",fn);
70   else fprintf(ads->diagfile,"adns: %s:%d: ",fn,lno);
71   va_start(al,fmt);
72   vfprintf(ads->diagfile,fmt,al);
73   va_end(al);
74   fputc('\n',ads->diagfile);
75 }
76
77 static void ccf_nameserver(adns_state ads, const char *fn, int lno, const char *buf) {
78   struct in_addr ia;
79   
80   if (!inet_aton(buf,&ia)) {
81     configparseerr(ads,fn,lno,"invalid nameserver address `%s'",buf);
82     return;
83   }
84   adns__debug(ads,-1,0,"using nameserver %s",inet_ntoa(ia));
85   addserver(ads,ia);
86 }
87
88 static void ccf_search(adns_state ads, const char *fn, int lno, const char *buf) {
89   if (!buf) return;
90   adns__diag(ads,-1,0,"warning - `search' ignored fixme");
91 }
92
93 static void ccf_sortlist(adns_state ads, const char *fn, int lno, const char *bufp) {
94   const char *p, *q;
95   char tbuf[200], *slash, *ep;
96   struct in_addr base, mask;
97   int l;
98   unsigned long initial, baselocal;
99
100   ads->nsortlist= 0;
101   if (!bufp) return;
102
103   for (;;) {
104     while (ctype_whitespace(*bufp)) bufp++;
105     if (!*bufp) return;
106
107     q= bufp;
108     while (*q && !ctype_whitespace(*q)) q++;
109
110     p= bufp;
111     l= q-p;
112     bufp= q;
113
114     if (ads->nsortlist >= MAXSORTLIST) {
115       adns__diag(ads,-1,0,"too many sortlist entries, ignoring %.*s onwards",l,p);
116       return;
117     }
118
119     if (l >= sizeof(tbuf)) {
120       configparseerr(ads,fn,lno,"sortlist entry `%.*s' too long",l,p);
121       continue;
122     }
123     
124     memcpy(tbuf,p,l);
125     slash= strchr(tbuf,'/');
126     if (slash) *slash++= 0;
127     
128     if (!inet_aton(tbuf,&base)) {
129       configparseerr(ads,fn,lno,"invalid address `%s' in sortlist",tbuf);
130       continue;
131     }
132
133     if (slash) {
134       if (strchr(slash,'.')) {
135         if (!inet_aton(slash,&mask)) {
136           configparseerr(ads,fn,lno,"invalid mask `%s' in sortlist",slash);
137           continue;
138         }
139         if (base.s_addr & ~mask.s_addr) {
140           configparseerr(ads,fn,lno,
141                          "mask `%s' in sortlist overlaps address `%s'",slash,tbuf);
142           continue;
143         }
144       } else {
145         initial= strtoul(slash,&ep,10);
146         if (*ep || initial>32) {
147           configparseerr(ads,fn,lno,"mask length `%s' invalid",slash);
148           continue;
149         }
150         mask.s_addr= htonl((0x0ffffffffUL) << (32-initial));
151       }
152     } else {
153       baselocal= ntohl(base.s_addr);
154       if (!baselocal & 0x080000000UL) /* class A */
155         mask.s_addr= htonl(0x0ff000000UL);
156       else if ((baselocal & 0x0c0000000UL) == 0x080000000UL)
157         mask.s_addr= htonl(0x0ffff0000UL); /* class B */
158       else if ((baselocal & 0x0f0000000UL) == 0x0e0000000UL)
159         mask.s_addr= htonl(0x0ff000000UL); /* class C */
160       else {
161         configparseerr(ads,fn,lno,
162                        "network address `%s' in sortlist is not in classed ranges,"
163                        " must specify mask explicitly", tbuf);
164         continue;
165       }
166     }
167
168     ads->sortlist[ads->nsortlist].base= base;
169     ads->sortlist[ads->nsortlist].mask= mask;
170     ads->nsortlist++;
171   }
172 }
173
174 static void ccf_options(adns_state ads, const char *fn, int lno, const char *buf) {
175   if (!buf) return;
176   adns__diag(ads,-1,0,"warning - `options' ignored fixme");
177 }
178
179 static void ccf_clearnss(adns_state ads, const char *fn, int lno, const char *buf) {
180   ads->nservers= 0;
181 }
182
183 static void ccf_include(adns_state ads, const char *fn, int lno, const char *buf) {
184   if (!*buf) {
185     configparseerr(ads,fn,lno,"`include' directive with no filename");
186     return;
187   }
188   readconfig(ads,buf);
189 }
190
191 static const struct configcommandinfo {
192   const char *name;
193   void (*fn)(adns_state ads, const char *fn, int lno, const char *buf);
194 } configcommandinfos[]= {
195   { "nameserver",        ccf_nameserver  },
196   { "domain",            ccf_search      },
197   { "search",            ccf_search      },
198   { "sortlist",          ccf_sortlist    },
199   { "options",           ccf_options     },
200   { "clearnameservers",  ccf_clearnss    },
201   { "include",           ccf_include     },
202   {  0                                   }
203 };
204
205 typedef union {
206   FILE *file;
207   const char *text;
208 } getline_ctx;
209
210 static int gl_file(adns_state ads, getline_ctx *src_io, const char *filename,
211                    int lno, char *buf, int buflen) {
212   FILE *file= src_io->file;
213   int c, i;
214   char *p;
215
216   p= buf;
217   buflen--;
218   i= 0;
219     
220   for (;;) { /* loop over chars */
221     if (i == buflen) {
222       adns__diag(ads,-1,0,"%s:%d: line too long, ignored",filename,lno);
223       goto x_badline;
224     }
225     c= getc(file);
226     if (!c) {
227       adns__diag(ads,-1,0,"%s:%d: line contains nul, ignored",filename,lno);
228       goto x_badline;
229     } else if (c == '\n') {
230       break;
231     } else if (c == EOF) {
232       if (ferror(file)) {
233         saveerr(ads,errno);
234         adns__diag(ads,-1,0,"%s:%d: read error: %s",filename,lno,strerror(errno));
235         return -1;
236       }
237       if (!i) return -1;
238       break;
239     } else {
240       *p++= c;
241       i++;
242     }
243   }
244
245   *p++= 0;
246   return i;
247
248  x_badline:
249   saveerr(ads,EINVAL);
250   while ((c= getc(file)) != EOF && c != '\n');
251   return -2;
252 }
253
254 static int gl_text(adns_state ads, getline_ctx *src_io, const char *filename,
255                    int lno, char *buf, int buflen) {
256   const char *cp= src_io->text;
257   int l;
258
259   if (!cp || !*cp) return -1;
260
261   if (*cp == ';' || *cp == '\n') cp++;
262   l= strcspn(cp,";\n");
263   src_io->text = cp+l;
264
265   if (l >= buflen) {
266     adns__diag(ads,-1,0,"%s:%d: line too long, ignored",filename,lno);
267     saveerr(ads,EINVAL);
268     return -2;
269   }
270     
271   memcpy(buf,cp,l);
272   buf[l]= 0;
273   return l;
274 }
275
276 static void readconfiggeneric(adns_state ads, const char *filename,
277                               int (*getline)(adns_state ads, getline_ctx*,
278                                              const char *filename, int lno,
279                                              char *buf, int buflen),
280                               /* Returns >=0 for success, -1 for EOF or error
281                                * (error will have been reported), or -2 for
282                                * bad line was encountered, try again.
283                                */
284                               getline_ctx gl_ctx) {
285   char linebuf[2000], *p, *q;
286   int lno, l, dirl;
287   const struct configcommandinfo *ccip;
288
289   for (lno=1;
290        (l= getline(ads,&gl_ctx, filename,lno, linebuf,sizeof(linebuf))) != -1;
291        lno++) {
292     if (l == -2) continue;
293     while (l>0 && ctype_whitespace(linebuf[l-1])) l--;
294     linebuf[l]= 0;
295     p= linebuf;
296     while (ctype_whitespace(*p)) p++;
297     if (*p == '#' || !*p) continue;
298     q= p;
299     while (*q && !ctype_whitespace(*q)) q++;
300     dirl= q-p;
301     for (ccip=configcommandinfos;
302          ccip->name && !(strlen(ccip->name)==dirl && !memcmp(ccip->name,p,q-p));
303          ccip++);
304     if (!ccip->name) {
305       adns__diag(ads,-1,0,"%s:%d: unknown configuration directive `%.*s'",
306                  filename,lno,q-p,p);
307       continue;
308     }
309     while (ctype_whitespace(*q)) q++;
310     ccip->fn(ads,filename,lno,q);
311   }
312 }
313
314 static const char *instrum_getenv(adns_state ads, const char *envvar) {
315   const char *value;
316
317   value= getenv(envvar);
318   if (!value) adns__debug(ads,-1,0,"environment variable %s not set",envvar);
319   else adns__debug(ads,-1,0,"environment variable %s set to `%s'",envvar,value);
320   return value;
321 }
322
323 static void readconfig(adns_state ads, const char *filename) {
324   getline_ctx gl_ctx;
325   
326   gl_ctx.file= fopen(filename,"r");
327   if (!gl_ctx.file) {
328     if (errno == ENOENT) {
329       adns__debug(ads,-1,0,"configuration file `%s' does not exist",filename);
330       return;
331     }
332     saveerr(ads,errno);
333     adns__diag(ads,-1,0,"cannot open configuration file `%s': %s",
334                filename,strerror(errno));
335     return;
336   }
337
338   readconfiggeneric(ads,filename,gl_file,gl_ctx);
339   
340   fclose(gl_ctx.file);
341 }
342
343 static void readconfigtext(adns_state ads, const char *text, const char *showname) {
344   getline_ctx gl_ctx;
345   
346   gl_ctx.text= text;
347   readconfiggeneric(ads,showname,gl_text,gl_ctx);
348 }
349   
350 static void readconfigenv(adns_state ads, const char *envvar) {
351   const char *filename;
352
353   if (ads->iflags & adns_if_noenv) {
354     adns__debug(ads,-1,0,"not checking environment variable `%s'",envvar);
355     return;
356   }
357   filename= instrum_getenv(ads,envvar);
358   if (filename) readconfig(ads,filename);
359 }
360
361 static void readconfigenvtext(adns_state ads, const char *envvar) {
362   const char *textdata;
363
364   if (ads->iflags & adns_if_noenv) {
365     adns__debug(ads,-1,0,"not checking environment variable `%s'",envvar);
366     return;
367   }
368   textdata= instrum_getenv(ads,envvar);
369   if (textdata) readconfigtext(ads,textdata,envvar);
370 }
371
372
373 int adns__setnonblock(adns_state ads, int fd) {
374   int r;
375   
376   r= fcntl(fd,F_GETFL,0); if (r<0) return errno;
377   r |= O_NONBLOCK;
378   r= fcntl(fd,F_SETFL,r); if (r<0) return errno;
379   return 0;
380 }
381
382 static int init_begin(adns_state *ads_r, adns_initflags flags, FILE *diagfile) {
383   adns_state ads;
384   
385   ads= malloc(sizeof(*ads)); if (!ads) return errno;
386
387   ads->iflags= flags;
388   ads->diagfile= diagfile;
389   LIST_INIT(ads->timew);
390   LIST_INIT(ads->childw);
391   LIST_INIT(ads->output);
392   ads->nextid= 0x311f;
393   ads->udpsocket= ads->tcpsocket= -1;
394   adns__vbuf_init(&ads->tcpsend);
395   adns__vbuf_init(&ads->tcprecv);
396   ads->nservers= ads->nsortlist= ads->tcpserver= 0;
397   ads->tcpstate= server_disconnected;
398   timerclear(&ads->tcptimeout);
399
400   *ads_r= ads;
401   return 0;
402 }
403
404 static int init_finish(adns_state ads) {
405   struct in_addr ia;
406   struct protoent *proto;
407   int r;
408   
409   if (!ads->nservers) {
410     if (ads->diagfile && ads->iflags & adns_if_debug)
411       fprintf(ads->diagfile,"adns: no nameservers, using localhost\n");
412     ia.s_addr= htonl(INADDR_LOOPBACK);
413     addserver(ads,ia);
414   }
415
416   proto= getprotobyname("udp"); if (!proto) { r= ENOPROTOOPT; goto x_free; }
417   ads->udpsocket= socket(AF_INET,SOCK_DGRAM,proto->p_proto);
418   if (ads->udpsocket<0) { r= errno; goto x_free; }
419
420   r= adns__setnonblock(ads,ads->udpsocket);
421   if (r) { r= errno; goto x_closeudp; }
422   
423   return 0;
424
425  x_closeudp:
426   close(ads->udpsocket);
427  x_free:
428   free(ads);
429   return r;
430 }
431
432 int adns_init(adns_state *ads_r, adns_initflags flags, FILE *diagfile) {
433   adns_state ads;
434   const char *res_options, *adns_res_options;
435   int r;
436   
437   r= init_begin(&ads, flags, diagfile ? diagfile : stderr);
438   if (r) return r;
439   
440   res_options= instrum_getenv(ads,"RES_OPTIONS");
441   adns_res_options= instrum_getenv(ads,"ADNS_RES_OPTIONS");
442   ccf_options(ads,"RES_OPTIONS",-1,res_options);
443   ccf_options(ads,"ADNS_RES_OPTIONS",-1,adns_res_options);
444
445   readconfig(ads,"/etc/resolv.conf");
446   readconfigenv(ads,"RES_CONF");
447   readconfigenv(ads,"ADNS_RES_CONF");
448
449   readconfigenvtext(ads,"RES_CONF_TEXT");
450   readconfigenvtext(ads,"ADNS_RES_CONF_TEXT");
451
452   ccf_options(ads,"RES_OPTIONS",-1,res_options);
453   ccf_options(ads,"ADNS_RES_OPTIONS",-1,adns_res_options);
454
455   ccf_search(ads,"LOCALDOMAIN",-1,instrum_getenv(ads,"LOCALDOMAIN"));
456   ccf_search(ads,"ADNS_LOCALDOMAIN",-1,instrum_getenv(ads,"ADNS_LOCALDOMAIN"));
457
458   r= init_finish(ads);
459   if (r) return r;
460
461   *ads_r= ads;
462   return 0;
463 }
464
465 int adns_init_strcfg(adns_state *ads_r, adns_initflags flags,
466                      FILE *diagfile, const char *configtext) {
467   adns_state ads;
468   int r;
469
470   r= init_begin(&ads, flags, diagfile);  if (r) return r;
471
472   readconfigtext(ads,configtext,"<supplied configuration text>");
473   if (ads->configerrno) {
474     r= ads->configerrno;
475     free(ads);
476     return r;
477   }
478
479   r= init_finish(ads);  if (r) return r;
480   *ads_r= ads;
481   return 0;
482 }
483
484 void adns_finish(adns_state ads) {
485   for (;;) {
486     if (ads->timew.head) adns_cancel(ads->timew.head);
487     else if (ads->childw.head) adns_cancel(ads->childw.head);
488     else if (ads->output.head) adns_cancel(ads->output.head);
489     else break;
490   }
491   close(ads->udpsocket);
492   if (ads->tcpsocket >= 0) close(ads->tcpsocket);
493   adns__vbuf_free(&ads->tcpsend);
494   adns__vbuf_free(&ads->tcprecv);
495   free(ads);
496 }