Allow configuration entirely by text file.
[adns] / 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, 1998 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 *buf) {
94 adns__diag(ads,-1,0,"warning - `sortlist' ignored fixme");
95 }
96
97 static void ccf_options(adns_state ads, const char *fn, int lno, const char *buf) {
98 if (!buf) return;
99 adns__diag(ads,-1,0,"warning - `options' ignored fixme");
100 }
101
102 static void ccf_clearnss(adns_state ads, const char *fn, int lno, const char *buf) {
103 ads->nservers= 0;
104 }
105
106 static void ccf_include(adns_state ads, const char *fn, int lno, const char *buf) {
107 if (!*buf) {
108 configparseerr(ads,fn,lno,"`include' directive with no filename");
109 return;
110 }
111 readconfig(ads,buf);
112 }
113
114 static const struct configcommandinfo {
115 const char *name;
116 void (*fn)(adns_state ads, const char *fn, int lno, const char *buf);
117 } configcommandinfos[]= {
118 { "nameserver", ccf_nameserver },
119 { "domain", ccf_search },
120 { "search", ccf_search },
121 { "sortlist", ccf_sortlist },
122 { "options", ccf_options },
123 { "clearnameservers", ccf_clearnss },
124 { "include", ccf_include },
125 { 0 }
126 };
127
128 typedef union {
129 FILE *file;
130 const char *text;
131 } getline_ctx;
132
133 static int gl_file(adns_state ads, getline_ctx *src_io, const char *filename,
134 int lno, char *buf, int buflen) {
135 FILE *file= src_io->file;
136 int c, i;
137 char *p;
138
139 p= buf;
140 buflen--;
141 i= 0;
142
143 for (;;) { /* loop over chars */
144 if (i == buflen) {
145 adns__diag(ads,-1,0,"%s:%d: line too long, ignored",filename,lno);
146 goto x_badline;
147 }
148 c= getc(file);
149 if (!c) {
150 adns__diag(ads,-1,0,"%s:%d: line contains nul, ignored",filename,lno);
151 goto x_badline;
152 } else if (c == '\n') {
153 break;
154 } else if (c == EOF) {
155 if (ferror(file)) {
156 saveerr(ads,errno);
157 adns__diag(ads,-1,0,"%s:%d: read error: %s",filename,lno,strerror(errno));
158 return -1;
159 }
160 if (!i) return -1;
161 break;
162 } else {
163 *p++= c;
164 i++;
165 }
166 }
167
168 *p++= 0;
169 return i;
170
171 x_badline:
172 saveerr(ads,EINVAL);
173 while ((c= getc(file)) != EOF && c != '\n');
174 return -2;
175 }
176
177 static int gl_text(adns_state ads, getline_ctx *src_io, const char *filename,
178 int lno, char *buf, int buflen) {
179 const char *cp= src_io->text, *nn;
180 int l;
181
182 if (!cp) return -1;
183
184 nn= strchr(cp,'\n');
185
186 l= nn ? nn-cp : strlen(cp);
187 src_io->text= nn ? nn+1 : 0;
188
189 if (l >= buflen) {
190 adns__diag(ads,-1,0,"%s:%d: line too long, ignored",filename,lno);
191 saveerr(ads,EINVAL);
192 return -2;
193 }
194
195 memcpy(buf,cp,l);
196 buf[l]= 0;
197 return l;
198 }
199
200 static void readconfiggeneric(adns_state ads, const char *filename,
201 int (*getline)(adns_state ads, getline_ctx*,
202 const char *filename, int lno,
203 char *buf, int buflen),
204 /* Returns >=0 for success, -1 for EOF or error
205 * (error will have been reported), or -2 for
206 * bad line was encountered, try again.
207 */
208 getline_ctx gl_ctx) {
209 char linebuf[2000], *p, *q;
210 int lno, l, dirl;
211 const struct configcommandinfo *ccip;
212
213 for (lno=1;
214 (l= getline(ads,&gl_ctx, filename,lno, linebuf,sizeof(linebuf))) != -1;
215 lno++) {
216 if (l == -2) continue;
217 while (l>0 && ctype_whitespace(linebuf[l-1])) l--;
218 linebuf[l]= 0;
219 p= linebuf;
220 while (ctype_whitespace(*p)) p++;
221 if (*p == '#' || !*p) continue;
222 q= p;
223 while (*q && !ctype_whitespace(*q)) q++;
224 dirl= q-p;
225 for (ccip=configcommandinfos;
226 ccip->name && !(strlen(ccip->name)==dirl && !memcmp(ccip->name,p,q-p));
227 ccip++);
228 if (!ccip->name) {
229 adns__diag(ads,-1,0,"%s:%d: unknown configuration directive `%.*s'",
230 filename,lno,q-p,p);
231 continue;
232 }
233 while (ctype_whitespace(*q)) q++;
234 ccip->fn(ads,filename,lno,q);
235 }
236 }
237
238 static const char *instrum_getenv(adns_state ads, const char *envvar) {
239 const char *value;
240
241 value= getenv(envvar);
242 if (!value) adns__debug(ads,-1,0,"environment variable %s not set",envvar);
243 else adns__debug(ads,-1,0,"environment variable %s set to `%s'",envvar,value);
244 return value;
245 }
246
247 static void readconfig(adns_state ads, const char *filename) {
248 getline_ctx gl_ctx;
249
250 gl_ctx.file= fopen(filename,"r");
251 if (!gl_ctx.file) {
252 if (errno == ENOENT) {
253 adns__debug(ads,-1,0,"configuration file `%s' does not exist",filename);
254 return;
255 }
256 saveerr(ads,errno);
257 adns__diag(ads,-1,0,"cannot open configuration file `%s': %s",
258 filename,strerror(errno));
259 return;
260 }
261
262 readconfiggeneric(ads,filename,gl_file,gl_ctx);
263
264 fclose(gl_ctx.file);
265 }
266
267 static void readconfigtext(adns_state ads, const char *text, const char *showname) {
268 getline_ctx gl_ctx;
269
270 gl_ctx.text= text;
271 readconfiggeneric(ads,showname,gl_text,gl_ctx);
272 }
273
274 static void readconfigenv(adns_state ads, const char *envvar) {
275 const char *filename;
276
277 if (ads->iflags & adns_if_noenv) {
278 adns__debug(ads,-1,0,"not checking environment variable `%s'",envvar);
279 return;
280 }
281 filename= instrum_getenv(ads,envvar);
282 if (filename) readconfig(ads,filename);
283 }
284
285
286 int adns__setnonblock(adns_state ads, int fd) {
287 int r;
288
289 r= fcntl(fd,F_GETFL,0); if (r<0) return errno;
290 r |= O_NONBLOCK;
291 r= fcntl(fd,F_SETFL,r); if (r<0) return errno;
292 return 0;
293 }
294
295 static int init_begin(adns_state *ads_r, adns_initflags flags, FILE *diagfile) {
296 adns_state ads;
297
298 ads= malloc(sizeof(*ads)); if (!ads) return errno;
299
300 ads->iflags= flags;
301 ads->diagfile= diagfile;
302 LIST_INIT(ads->timew);
303 LIST_INIT(ads->childw);
304 LIST_INIT(ads->output);
305 ads->nextid= 0x311f;
306 ads->udpsocket= ads->tcpsocket= -1;
307 adns__vbuf_init(&ads->tcpsend);
308 adns__vbuf_init(&ads->tcprecv);
309 ads->nservers= ads->tcpserver= 0;
310 ads->tcpstate= server_disconnected;
311 timerclear(&ads->tcptimeout);
312
313 *ads_r= ads;
314 return 0;
315 }
316
317 static int init_finish(adns_state ads) {
318 struct in_addr ia;
319 struct protoent *proto;
320 int r;
321
322 if (!ads->nservers) {
323 if (ads->diagfile && ads->iflags & adns_if_debug)
324 fprintf(ads->diagfile,"adns: no nameservers, using localhost\n");
325 ia.s_addr= INADDR_LOOPBACK;
326 addserver(ads,ia);
327 }
328
329 proto= getprotobyname("udp"); if (!proto) { r= ENOPROTOOPT; goto x_free; }
330 ads->udpsocket= socket(AF_INET,SOCK_DGRAM,proto->p_proto);
331 if (ads->udpsocket<0) { r= errno; goto x_free; }
332
333 r= adns__setnonblock(ads,ads->udpsocket);
334 if (r) { r= errno; goto x_closeudp; }
335
336 return 0;
337
338 x_closeudp:
339 close(ads->udpsocket);
340 x_free:
341 free(ads);
342 return r;
343 }
344
345 int adns_init(adns_state *ads_r, adns_initflags flags, FILE *diagfile) {
346 adns_state ads;
347 const char *res_options, *adns_res_options;
348 int r;
349
350 r= init_begin(&ads, flags, diagfile ? diagfile : stderr);
351 if (r) return r;
352
353 res_options= instrum_getenv(ads,"RES_OPTIONS");
354 adns_res_options= instrum_getenv(ads,"ADNS_RES_OPTIONS");
355 ccf_options(ads,"RES_OPTIONS",-1,res_options);
356 ccf_options(ads,"ADNS_RES_OPTIONS",-1,adns_res_options);
357
358 readconfig(ads,"/etc/resolv.conf");
359 readconfigenv(ads,"RES_CONF");
360 readconfigenv(ads,"ADNS_RES_CONF");
361
362 ccf_options(ads,"RES_OPTIONS",-1,res_options);
363 ccf_options(ads,"ADNS_RES_OPTIONS",-1,adns_res_options);
364
365 ccf_search(ads,"LOCALDOMAIN",-1,instrum_getenv(ads,"LOCALDOMAIN"));
366 ccf_search(ads,"ADNS_LOCALDOMAIN",-1,instrum_getenv(ads,"ADNS_LOCALDOMAIN"));
367
368 r= init_finish(ads);
369 if (r) return r;
370
371 *ads_r= ads;
372 return 0;
373 }
374
375 int adns_init_strcfg(adns_state *ads_r, adns_initflags flags,
376 FILE *diagfile, const char *configtext) {
377 adns_state ads;
378 int r;
379
380 r= init_begin(&ads, flags, diagfile); if (r) return r;
381
382 readconfigtext(ads,configtext,"<supplied configuration text>");
383 if (ads->configerrno) {
384 r= ads->configerrno;
385 free(ads);
386 return r;
387 }
388
389 r= init_finish(ads); if (r) return r;
390 *ads_r= ads;
391 return 0;
392 }
393
394 void adns_finish(adns_state ads) {
395 for (;;) {
396 if (ads->timew.head) adns_cancel(ads->timew.head);
397 else if (ads->childw.head) adns_cancel(ads->childw.head);
398 else if (ads->output.head) adns_cancel(ads->output.head);
399 else break;
400 }
401 close(ads->udpsocket);
402 if (ads->tcpsocket >= 0) close(ads->tcpsocket);
403 adns__vbuf_free(&ads->tcpsend);
404 adns__vbuf_free(&ads->tcprecv);
405 free(ads);
406 }