ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/vendor/ircservices-5.1.24/conffile.c
Revision: 3389
Committed: Fri Apr 25 14:12:15 2014 UTC (9 years, 10 months ago) by michael
Content type: text/x-csrc
File size: 25173 byte(s)
Log Message:
- Imported ircservices-5.1.24

File Contents

# Content
1 /* Configuration file handling.
2 *
3 * IRC Services is copyright (c) 1996-2009 Andrew Church.
4 * E-mail: <achurch@achurch.org>
5 * Parts written by Andrew Kempe and others.
6 * This program is free but copyrighted software; see the file GPL.txt for
7 * details.
8 */
9
10 #include "services.h"
11 #include "conffile.h"
12
13 /*************************************************************************/
14
15 static void do_all_directives(int action, ConfigDirective *directives);
16
17 static int read_config_file(const char *modulename,
18 ConfigDirective *directives);
19
20 static int do_read_config_file(const char *modulename,
21 ConfigDirective *directives, FILE *f,
22 const char *filename, int recursion_level);
23
24 static int parse_config_line(const char *filename, int linenum, char *buf,
25 ConfigDirective *directives);
26
27
28 /* Actions for do_all_directives(): */
29
30 #define ACTION_COPYNEW 0 /* Copy `new' parameters to config variables */
31 #define ACTION_RESTORESAVED 1 /* Restore saved values of config variables */
32
33 /*************************************************************************/
34 /*************************************************************************/
35
36 /* Set configuration options for the given module (if `modulename' is NULL,
37 * set core configuration options). Returns nonzero on success, 0 on error
38 * (an error message is logged, and printed to the terminal if applicable,
39 * in this case). Returns successfully without doing anything if
40 * `directives' is NULL.
41 *
42 * `action' is a bitmask of CONFIGURE_* values (services.h), specifying
43 * what this function should do, as follows:
44 * - CONFIGURE_READ: read new values from the configuration file
45 * - CONFIGURE_SET: copy new values to configuration variables
46 * If both CONFIGURE_READ and CONFIGURE_SET are specified, new values are
47 * copied to the configuration variables only if all values are read in
48 * successfully (i.e. if a configure(...,CONFIGURE_READ) call would have
49 * returned success). CONFIGURE_SET alone will never fail.
50 */
51
52 int configure(const char *modulename, ConfigDirective *directives,
53 int action)
54 {
55 /* If no directives were given, return success */
56 if (!directives)
57 return 1;
58
59 if (action & CONFIGURE_READ) {
60 if (!read_config_file(modulename, directives))
61 return 0;
62 }
63
64 if (action & CONFIGURE_SET)
65 do_all_directives(ACTION_COPYNEW, directives);
66
67 return 1;
68 }
69
70 /*************************************************************************/
71
72 /* Deconfigure given directive array (free any allocated storage and
73 * restore original values). A no-op if `directives' is NULL.
74 */
75
76 void deconfigure(ConfigDirective *directives)
77 {
78 if (directives)
79 do_all_directives(ACTION_RESTORESAVED, directives);
80 }
81
82 /*************************************************************************/
83
84 /* Print a warning or error message to the log (and the console, if open). */
85
86 void config_error(const char *filename, int linenum, const char *message, ...)
87 {
88 char buf[4096];
89 va_list args;
90
91 va_start(args, message);
92 vsnprintf(buf, sizeof(buf), message, args);
93 va_end(args);
94 if (linenum)
95 log("%s:%d: %s", filename, linenum, buf);
96 else
97 log("%s: %s", filename, buf);
98 if (!nofork && isatty(2)) {
99 if (linenum)
100 fprintf(stderr, "%s:%d: %s\n", filename, linenum, buf);
101 else
102 fprintf(stderr, "%s: %s\n", filename, buf);
103 }
104 }
105
106 /*************************************************************************/
107 /*************************************************************************/
108
109 /* Perform an action for all directives in an array; the action is given
110 * by ACTION_*, defined above.
111 */
112
113 static void do_all_directives(int action, ConfigDirective *directives)
114 {
115 int n, i;
116
117 for (n = 0; directives[n].name; n++) {
118 ConfigDirective *d = &directives[n];
119 for (i = 0; i < CONFIG_MAXPARAMS && d->params[i].type != CD_NONE; i++){
120 CDValue val;
121
122 /* Select the appropriate value to copy */
123 if (action == ACTION_COPYNEW)
124 val = d->params[i].new;
125 else
126 val = d->params[i].prev;
127
128 /* In any case, we'll be rewriting the config variable, so free
129 * the previous value if it was one we allocated */
130 if (d->params[i].flags & CF_ALLOCED) {
131 free(*(void **)d->params[i].ptr);
132 d->params[i].flags &= ~CF_ALLOCED;
133 }
134
135 /* Don't do anything if we're copying new values and this
136 * directive/parameter wasn't seen, or if we're restoring saved
137 * values and this parameter hasn't had its value saved (except
138 * for function parameters) */
139 if (action == ACTION_COPYNEW
140 && (!d->was_seen || !(d->params[i].flags & CF_WASSET)))
141 continue;
142 if (action == ACTION_RESTORESAVED
143 && d->params[i].type != CD_FUNC
144 && !(d->params[i].flags & CF_SAVED))
145 continue;
146
147 /* Copy new value to configuration variable */
148 switch (d->params[i].type) {
149 case CD_SET:
150 if (action == ACTION_COPYNEW)
151 *(int *)d->params[i].ptr = (int)val.intval;
152 break;
153 case CD_TIME:
154 *(time_t *)d->params[i].ptr = val.timeval;
155 break;
156 case CD_STRING:
157 *(char **)d->params[i].ptr = val.ptrval;
158 break;
159 case CD_INT:
160 case CD_POSINT:
161 case CD_PORT:
162 case CD_TIMEMSEC:
163 *(int32 *)d->params[i].ptr = val.intval;
164 break;
165 case CD_FUNC: {
166 int (*func)(const char *, int, char *)
167 = (int (*)(const char *,int,char *))(d->params[i].ptr);
168 if (action == ACTION_COPYNEW)
169 func(NULL, CDFUNC_SET, NULL);
170 else
171 func(NULL, CDFUNC_DECONFIG, NULL);
172 break;
173 } /* case CD_FUNC */
174 case CD_DEPRECATED:
175 /* Nothing to do */
176 break;
177 default:
178 log("conffile: do_all_directives BUG: don't know how to "
179 " copy type %d (%s/%d)", d->params[i].type, d->name, i);
180 break;
181 } /* switch */
182
183 /* Fix up flags */
184 if (action == ACTION_COPYNEW) {
185 if (d->params[i].flags & CF_ALLOCED_NEW) {
186 d->params[i].flags |= CF_ALLOCED;
187 /* The value is still allocated, but it's now stored in
188 * the configuration variable, so we don't want to free
189 * it when clearing `new' */
190 d->params[i].flags &= ~CF_ALLOCED_NEW;
191 }
192 } else {
193 d->params[i].flags &= ~CF_SAVED;
194 }
195 } /* for each parameter */
196 } /* for each directive */
197 }
198
199 /*************************************************************************/
200
201 /* Read in configuration options, and return nonzero for success, zero for
202 * failure. Performs the actions needed by configure(...,CONFIGURE_READ).
203 */
204
205 static int read_config_file(const char *modulename,
206 ConfigDirective *directives)
207 {
208 const char *filename;
209 FILE *f;
210 int retval, i, n;
211
212 /* Open default file */
213 filename = modulename==NULL ? IRCSERVICES_CONF : MODULES_CONF;
214 f = fopen(filename, "r");
215 if (!f) {
216 log_perror("Unable to open %s", filename);
217 if (!nofork && isatty(2))
218 fprintf(stderr, "Unable to open %s: %s\n", filename,
219 strerror(errno));
220 return 0;
221 }
222
223 /* Clear `was_set' flag and `new' value for all directives */
224 for (n = 0; directives[n].name != NULL; n++) {
225 directives[n].was_seen = 0;
226 for (i = 0; i < CONFIG_MAXPARAMS; i++) {
227 if (directives[n].params[i].flags & CF_ALLOCED_NEW)
228 free(directives[n].params[i].new.ptrval);
229 directives[n].params[i].flags &= ~(CF_WASSET | CF_ALLOCED_NEW);
230 memset(&directives[n].params[i].new, 0,
231 sizeof(directives[n].params[i].new));
232 if (directives[n].params[i].type == CD_FUNC) {
233 int (*func)(const char *, int, char *)
234 = (int (*)(const char *, int, char *))
235 (directives[n].params[i].ptr);
236 func(NULL, CDFUNC_INIT, NULL);
237 }
238 }
239 }
240
241 /* Actually do the work */
242 retval = do_read_config_file(modulename, directives, f, filename, 0);
243
244 fclose(f);
245
246 /* Make sure all required directives were seen */
247 for (n = 0; directives[n].name != NULL; n++) {
248 if (!directives[n].was_seen
249 && (directives[n].params[0].flags & CF_DIRREQ)
250 ) {
251 config_error(filename, 0, "Required directive `%s' missing",
252 directives[n].name);
253 retval = 0;
254 }
255 }
256
257 return retval;
258 }
259
260 /*************************************************************************/
261
262 /* Read in a single configuration file, recursively processing IncludeFile
263 * directives.
264 */
265
266 static int do_read_config_file(const char *modulename,
267 ConfigDirective *directives, FILE *f,
268 const char *filename, int recursion_level)
269 {
270 char *current_module = NULL; /* Current module in modules.conf */
271 int retval = 1; /* Return value */
272 int linenum = 0;
273 char buf[4096], tmpbuf[4096], *s;
274
275 while (fgets(buf, sizeof(buf), f)) {
276 /* Check for pathologically long files */
277 if (linenum+1 < linenum) {
278 config_error(filename, linenum, "File too long");
279 retval = 0;
280 break;
281 }
282 linenum++;
283 /* Check for pathologically long lines */
284 if (strlen(buf) == sizeof(buf)-1 && buf[sizeof(buf)-1] != '\n') {
285 /* Report the maximum size as sizeof(buf)-3 to allow \r\n as
286 * well as \n to fit */
287 config_error(filename, linenum, "Line too long (%d bytes maximum)",
288 sizeof(buf)-3);
289 /* Skip everything else until an EOL (or EOF) is seen */
290 while (fgets(buf, sizeof(buf), f) && buf[strlen(buf)-1] != '\n')
291 /*nothing*/;
292 retval = 0;
293 }
294 /* Strip out comments (but don't touch # inside of quotes) */
295 s = buf;
296 while (*s) {
297 if (*s == '"') {
298 if (!(s = strchr(s+1, '"')))
299 break;
300 } else if (*s == '#') {
301 *s = 0;
302 break;
303 }
304 s++;
305 }
306 /* Check for IncludeFile directives (use tmpbuf to avoid damaging
307 * the original copy of the line) */
308 strbcpy(tmpbuf, buf);
309 s = strtok(tmpbuf, " \t\r\n");
310 if (s && stricmp(s, "IncludeFile") == 0) {
311 FILE *f2;
312 /* Check recursion level for infinite loops */
313 if (recursion_level > 100) {
314 config_error(filename, linenum,
315 "IncludeFile recursion depth limit exceeded");
316 retval = 0;
317 continue;
318 }
319 /* Find the filename */
320 s = strtok_remaining();
321 if (s && *s == '"') {
322 char *t = strchr(s+1, '"');
323 if (!t) {
324 config_error(filename, linenum,
325 "Missing closing double quote");
326 retval = 0;
327 continue;
328 }
329 *t = 0;
330 s++;
331 } else {
332 s = strtok(s, " \t\r\n");
333 }
334 if (!s || !*s) {
335 config_error(filename, linenum,
336 "Missing filename for IncludeFile");
337 retval = 0;
338 continue;
339 }
340 /* Valid filename string; try to open it */
341 f2 = fopen(s, "r");
342 if (!f2) {
343 config_error(filename, linenum,
344 "Unable to open %s: %s", s, strerror(errno));
345 retval = 0;
346 continue;
347 }
348 if (!do_read_config_file(modulename, directives, f2, s,
349 recursion_level+1))
350 retval = 0;
351 fclose(f2);
352 continue;
353 }
354 /* Handle Module/EndModule lines specially, and don't parse lines
355 * belonging to other modules */
356 if (modulename) {
357 if (current_module) {
358 /* Inside a Module/EndModule pair: discard lines belonging
359 * to other modules, and handle EndModule directives. If
360 * we reach EndModule for the module we're supposed to be
361 * processing, exit the loop to avoid unneeded processing. */
362 strbcpy(tmpbuf, buf);
363 s = strtok(tmpbuf, " \t\r\n");
364 if (s && stricmp(s, "EndModule") == 0) {
365 int strcmp_result = strcmp(current_module, modulename);
366 free(current_module);
367 if (strcmp_result == 0)
368 break; /* stop processing file, we're finished */
369 else
370 current_module = NULL;
371 continue;
372 } else if (strcmp(current_module, modulename) != 0) {
373 continue;
374 }
375 } else { /* !current_module */
376 /* Outside a Module/EndModule pair: handle Module
377 * directives, and report errors for anything else */
378 s = strtok(buf, " \t\r\n");
379 if (!s)
380 continue;
381 if (stricmp(s, "Module") != 0) {
382 config_error(filename, linenum,
383 "Expected `Module' directive");
384 retval = 0;
385 } else {
386 current_module = strtok(NULL, " \t\r\n");
387 if (!current_module) {
388 config_error(filename, linenum, "Module name missing");
389 retval = 0;
390 }
391 current_module = strdup(current_module);
392 if (!current_module) {
393 config_error(filename, linenum, "Out of memory");
394 retval = 0;
395 break;
396 }
397 }
398 continue;
399 } /* if (current_module) */
400 } /* if (modulename) */
401 /* Valid line--parse it */
402 if (!parse_config_line(filename, linenum, buf, directives))
403 retval = 0;
404 }
405
406 return retval;
407 }
408
409 /*************************************************************************/
410
411 /* Parse a configuration line. Return 1 on success; otherwise, print (and
412 * log, if applicable) appropriate error message and return 0. Destroys
413 * the buffer by side effect.
414 */
415
416 static int parse_config_line(const char *filename, int linenum, char *buf,
417 ConfigDirective *directives)
418 {
419 char *s, *t, *directive;
420 int i, n, optind;
421 long longval;
422 unsigned long ulongval;
423 int retval = 1;
424 int ac = 0;
425 char *av[CONFIG_MAXPARAMS];
426
427 directive = strtok(buf, " \t\r\n");
428 s = strtok(NULL, "");
429 if (s) {
430 while (isspace(*s))
431 s++;
432 while (*s) {
433 if (ac >= CONFIG_MAXPARAMS) {
434 config_error(filename, linenum,
435 "Warning: too many parameters (%d max)",
436 CONFIG_MAXPARAMS);
437 break;
438 }
439 t = s;
440 if (*s == '"') {
441 t++;
442 s++;
443 while (*s && *s != '"') {
444 if (*s == '\\' && s[1] != 0)
445 strmove(s, s+1);
446 s++;
447 }
448 if (!*s)
449 config_error(filename, linenum,
450 "Warning: unterminated double-quoted string");
451 else
452 *s++ = 0;
453 } else {
454 s += strcspn(s, " \t\r\n");
455 if (*s)
456 *s++ = 0;
457 }
458 av[ac++] = t;
459 while (isspace(*s))
460 s++;
461 }
462 }
463
464 if (!directive)
465 return 1;
466
467 for (n = 0; directives[n].name; n++) {
468 ConfigDirective *d = &directives[n];
469 if (stricmp(directive, d->name) != 0)
470 continue;
471 d->was_seen = 1;
472 optind = 0;
473 for (i = 0; i < CONFIG_MAXPARAMS && d->params[i].type != CD_NONE; i++){
474 if (d->params[i].type == CD_SET) {
475 if (!(d->params[i].flags & CF_SAVED)) {
476 d->params[i].prev.intval = *(int *)d->params[i].ptr;
477 d->params[i].flags |= CF_SAVED;
478 }
479 d->params[i].new.intval = 1;
480 d->params[i].flags |= CF_WASSET;
481 continue;
482 }
483 if (d->params[i].type == CD_DEPRECATED) {
484 config_error(filename, linenum,
485 "Deprecated directive `%s' used", d->name);
486 d->params[i].flags |= CF_WASSET;
487 continue;
488 }
489 if (optind >= ac) {
490 if (!(d->params[i].flags & CF_OPTIONAL)) {
491 config_error(filename, linenum,
492 "Not enough parameters for `%s'", d->name);
493 retval = 0;
494 }
495 break;
496 }
497 switch (d->params[i].type) {
498 case CD_INT:
499 if (!(d->params[i].flags & CF_SAVED)) {
500 d->params[i].prev.intval = *(int32 *)d->params[i].ptr;
501 d->params[i].flags |= CF_SAVED;
502 }
503 longval = strtol(av[optind++], &s, 0);
504 if (*s) {
505 config_error(filename, linenum,
506 "%s: Expected an integer for parameter %d",
507 d->name, optind);
508 retval = 0;
509 break;
510 }
511 #if SIZEOF_LONG > 4
512 if (longval < -0x80000000L || longval > 0x7FFFFFFFL) {
513 config_error(filename, linenum,
514 "%s: Value out of range for parameter %d",
515 d->name, optind);
516 retval = 0;
517 break;
518 }
519 #endif
520 d->params[i].new.intval = (int32)longval;
521 break;
522 case CD_POSINT:
523 if (!(d->params[i].flags & CF_SAVED)) {
524 d->params[i].prev.intval = *(int32 *)d->params[i].ptr;
525 d->params[i].flags |= CF_SAVED;
526 }
527 ulongval = strtoul(av[optind++], &s, 0);
528 if (*s || ulongval <= 0) {
529 config_error(filename, linenum,
530 "%s: Expected a positive integer for"
531 " parameter %d", d->name, optind);
532 retval = 0;
533 break;
534 }
535 #if SIZEOF_LONG > 4
536 if (ulongval > 0xFFFFFFFFL) {
537 config_error(filename, linenum,
538 "%s: Value out of range for parameter %d",
539 d->name, optind);
540 retval = 0;
541 break;
542 }
543 #endif
544 d->params[i].new.intval = (int32)ulongval;
545 break;
546 case CD_PORT:
547 if (!(d->params[i].flags & CF_SAVED)) {
548 d->params[i].prev.intval = *(int32 *)d->params[i].ptr;
549 d->params[i].flags |= CF_SAVED;
550 }
551 longval = strtol(av[optind++], &s, 0);
552 if (*s) {
553 config_error(filename, linenum,
554 "%s: Expected a port number for parameter %d",
555 d->name, optind);
556 retval = 0;
557 break;
558 }
559 if (longval < 1 || longval > 65535) {
560 config_error(filename, linenum,
561 "Port numbers must be in the range 1..65535");
562 retval = 0;
563 break;
564 }
565 d->params[i].new.intval = (int32)longval;
566 break;
567 case CD_STRING:
568 if (!(d->params[i].flags & CF_SAVED)) {
569 d->params[i].prev.ptrval = *(char **)d->params[i].ptr;
570 d->params[i].flags |= CF_SAVED;
571 }
572 d->params[i].new.ptrval = strdup(av[optind++]);
573 if (!d->params[i].new.ptrval) {
574 config_error(filename, linenum, "%s: Out of memory",
575 d->name);
576 return 0;
577 }
578 d->params[i].flags |= CF_ALLOCED_NEW;
579 break;
580 case CD_TIME:
581 if (!(d->params[i].flags & CF_SAVED)) {
582 d->params[i].prev.timeval = *(time_t *)d->params[i].ptr;
583 d->params[i].flags |= CF_SAVED;
584 }
585 d->params[i].new.timeval = dotime(av[optind++]);
586 if (d->params[i].new.timeval < 0) {
587 config_error(filename, linenum,
588 "%s: Expected a time value for parameter %d",
589 d->name, optind);
590 retval = 0;
591 break;
592 }
593 break;
594 case CD_TIMEMSEC:
595 if (!(d->params[i].flags & CF_SAVED)) {
596 d->params[i].prev.intval = *(int32 *)d->params[i].ptr;
597 d->params[i].flags |= CF_SAVED;
598 }
599 longval = strtol(av[optind++], &s, 10);
600 if (longval < 0) {
601 config_error(filename, linenum,
602 "%s: Expected a positive value for"
603 " parameter %d", d->name, optind);
604 retval = 0;
605 break;
606 } else if (longval > 1000000) {
607 config_error(filename, linenum,
608 "%s: Value too large (maximum 1000000)",
609 d->name);
610 }
611 longval *= 1000;
612 if (*s == '.') {
613 int decimal = 0;
614 int count = 0;
615 s++;
616 while (count < 3 && isdigit(*s)) {
617 decimal = decimal*10 + (*s++ - '0');
618 count++;
619 }
620 while (count++ < 3)
621 decimal *= 10;
622 longval += decimal;
623 while (isdigit(*s))
624 s++;
625 }
626 if (*s) {
627 config_error(filename, linenum,
628 "%s: Expected a decimal number for"
629 " parameter %d", d->name, optind);
630 retval = 0;
631 break;
632 }
633 d->params[i].new.intval = (int32)longval;
634 break;
635 case CD_FUNC: {
636 int (*func)(const char *, int, char *)
637 = (int (*)(const char *, int, char *))(d->params[i].ptr);
638 if (!func(filename, linenum, av[optind++]))
639 retval = 0;
640 break;
641 }
642 default:
643 config_error(filename, linenum, "%s: Unknown type %d for"
644 " param %d", d->name, d->params[i].type, i+1);
645 return 0; /* don't bother continuing--something's bizarre */
646 } /* switch (d->params[i].type) */
647 d->params[i].flags |= CF_WASSET;
648 } /* for all parameters */
649 break; /* because we found a match */
650 } /* for all directives in array */
651
652 if (!directives[n].name) {
653 config_error(filename, linenum, "Unknown directive `%s'", directive);
654 return 1; /* don't cause abort */
655 }
656
657 return retval;
658 } /* parse_config_line() */
659
660 /*************************************************************************/
661
662 /*
663 * Local variables:
664 * c-file-style: "stroustrup"
665 * c-file-offsets: ((case-label . *) (statement-case-intro . *))
666 * indent-tabs-mode: nil
667 * End:
668 *
669 * vim: expandtab shiftwidth=4:
670 */