/* Configuration file handling. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" #include "conffile.h" /*************************************************************************/ /* Perform an action for all directives in an array; the action is given * by ACTION_*, defined below. */ #define ACTION_COPYNEW 0 /* Copy `new' parameters to config variables */ #define ACTION_RESTORESAVED 1 /* Restore saved values of config variables */ static void do_all_directives(int action, ConfigDirective *directives) { int n, i; for (n = 0; directives[n].name; n++) { ConfigDirective *d = &directives[n]; for (i = 0; i < CONFIG_MAXPARAMS && d->params[i].type != CD_NONE; i++){ CDValue val; /* Select the appropriate value to copy */ if (action == ACTION_COPYNEW) val = d->params[i].new; else val = d->params[i].prev; /* In any case, we'll be rewriting the config variable, so free * the previous value if it was one we allocated */ if (d->params[i].flags & CF_ALLOCED) { free(*(void **)d->params[i].ptr); d->params[i].flags &= ~CF_ALLOCED; } /* Don't do anything if we're copying new values and this * directive wasn't seen, or if we're restoring saved values * and this directive hasn't had its values saved (except for * function parameters) */ if (action == ACTION_COPYNEW && !d->was_seen) continue; if (action == ACTION_RESTORESAVED && d->params[i].type != CD_FUNC && !(d->params[i].flags & CF_SAVED)) continue; /* Copy new value to configuration variable */ switch (d->params[i].type) { case CD_SET: if (action == ACTION_COPYNEW) *(int *)d->params[i].ptr = (int)val.intval; break; case CD_TIME: *(time_t *)d->params[i].ptr = val.timeval; break; case CD_STRING: *(char **)d->params[i].ptr = val.ptrval; break; case CD_INT: case CD_POSINT: case CD_PORT: case CD_TIMEMSEC: *(int32 *)d->params[i].ptr = val.intval; break; case CD_FUNC: { int (*func)(const char *, int, char *) = (int (*)(const char *,int,char *))(d->params[i].ptr); if (action == ACTION_COPYNEW) func(NULL, CDFUNC_SET, NULL); else func(NULL, CDFUNC_DECONFIG, NULL); break; } /* case CD_FUNC */ default: log("conffile: do_all_directives BUG: don't know how to " " copy type %d (%s/%d)", d->params[i].type, d->name, i); break; } /* switch */ /* Fix up flags */ if (action == ACTION_COPYNEW) { if (d->params[i].flags & CF_ALLOCED_NEW) { d->params[i].flags |= CF_ALLOCED; /* The value is still allocated, but it's now stored in * the configuration variable, so we don't want to free * it when clearing `new' */ d->params[i].flags &= ~CF_ALLOCED_NEW; } } else { d->params[i].flags &= ~CF_SAVED; } } /* for each parameter */ } /* for each directive */ } /*************************************************************************/ /* Print an error message to the log (and the console, if open). */ void config_error(const char *filename, int linenum, const char *message, ...) { char buf[4096]; va_list args; va_start(args, message); vsnprintf(buf, sizeof(buf), message, args); va_end(args); if (linenum) log("%s:%d: %s", filename, linenum, buf); else log("%s: %s", filename, buf); if (!nofork && isatty(2)) { if (linenum) fprintf(stderr, "%s:%d: %s\n", filename, linenum, buf); else fprintf(stderr, "%s: %s\n", filename, buf); } } /*************************************************************************/ /* Parse a configuration line. Return 1 on success; otherwise, print (and * log, if applicable) appropriate error message and return 0. Destroys * the buffer by side effect. */ static int parse_config_line(const char *filename, int linenum, char *buf, ConfigDirective *directives) { char *s, *t, *directive; int i, n, optind; long longval; unsigned long ulongval; int retval = 1; int ac = 0; char *av[CONFIG_MAXPARAMS]; directive = strtok(buf, " \t\r\n"); s = strtok(NULL, ""); if (s) { while (isspace(*s)) s++; while (*s) { if (ac >= CONFIG_MAXPARAMS) { config_error(filename, linenum, "Warning: too many parameters (%d max)", CONFIG_MAXPARAMS); break; } t = s; if (*s == '"') { t++; s++; while (*s && *s != '"') { if (*s == '\\' && s[1] != 0) strmove(s, s+1); s++; } if (!*s) config_error(filename, linenum, "Warning: unterminated double-quoted string"); else *s++ = 0; } else { s += strcspn(s, " \t\r\n"); if (*s) *s++ = 0; } av[ac++] = t; while (isspace(*s)) s++; } } if (!directive) return 1; for (n = 0; directives[n].name; n++) { ConfigDirective *d = &directives[n]; if (stricmp(directive, d->name) != 0) continue; d->was_seen = 1; optind = 0; for (i = 0; i < CONFIG_MAXPARAMS && d->params[i].type != CD_NONE; i++){ if (d->params[i].type == CD_SET) { if (!(d->params[i].flags & CF_SAVED)) { d->params[i].prev.intval = *(int *)d->params[i].ptr; d->params[i].flags |= CF_SAVED; } d->params[i].new.intval = 1; d->params[i].flags |= CF_WASSET; continue; } if (d->params[i].type == CD_DEPRECATED) { void (*func)(void); /* For clarity */ config_error(filename, linenum, "Deprecated directive `%s' used", d->name); func = (void (*)(void))(d->params[i].ptr); if (func) func(); d->params[i].flags |= CF_WASSET; continue; } if (optind >= ac) { if (!(d->params[i].flags & CF_OPTIONAL)) { config_error(filename, linenum, "Not enough parameters for `%s'", d->name); retval = 0; } break; } switch (d->params[i].type) { case CD_INT: if (!(d->params[i].flags & CF_SAVED)) { d->params[i].prev.intval = *(int32 *)d->params[i].ptr; d->params[i].flags |= CF_SAVED; } longval = strtol(av[optind++], &s, 0); if (*s) { config_error(filename, linenum, "%s: Expected an integer for parameter %d", d->name, optind); retval = 0; break; } #if SIZEOF_LONG > 4 if (longval < -0x80000000L || longval > 0x7FFFFFFFL) { config_error(filename, linenum, "%s: Value out of range for parameter %d", d->name, optind); retval = 0; break; } #endif d->params[i].new.intval = (int32)longval; break; case CD_POSINT: if (!(d->params[i].flags & CF_SAVED)) { d->params[i].prev.intval = *(int32 *)d->params[i].ptr; d->params[i].flags |= CF_SAVED; } ulongval = strtoul(av[optind++], &s, 0); if (*s || ulongval <= 0) { config_error(filename, linenum, "%s: Expected a positive integer for" " parameter %d", d->name, optind); retval = 0; break; } #if SIZEOF_LONG > 4 if (ulongval > 0xFFFFFFFFL) { config_error(filename, linenum, "%s: Value out of range for parameter %d", d->name, optind); retval = 0; break; } #endif d->params[i].new.intval = (int32)ulongval; break; case CD_PORT: if (!(d->params[i].flags & CF_SAVED)) { d->params[i].prev.intval = *(int32 *)d->params[i].ptr; d->params[i].flags |= CF_SAVED; } longval = strtol(av[optind++], &s, 0); if (*s) { config_error(filename, linenum, "%s: Expected a port number for parameter %d", d->name, optind); retval = 0; break; } if (longval < 1 || longval > 65535) { config_error(filename, linenum, "Port numbers must be in the range 1..65535"); retval = 0; break; } d->params[i].new.intval = (int32)longval; break; case CD_STRING: if (!(d->params[i].flags & CF_SAVED)) { d->params[i].prev.ptrval = *(char **)d->params[i].ptr; d->params[i].flags |= CF_SAVED; } d->params[i].new.ptrval = strdup(av[optind++]); if (!d->params[i].new.ptrval) { config_error(filename, linenum, "%s: Out of memory", d->name); return 0; } d->params[i].flags |= CF_ALLOCED_NEW; break; case CD_TIME: if (!(d->params[i].flags & CF_SAVED)) { d->params[i].prev.timeval = *(time_t *)d->params[i].ptr; d->params[i].flags |= CF_SAVED; } d->params[i].new.timeval = dotime(av[optind++]); if (d->params[i].new.timeval < 0) { config_error(filename, linenum, "%s: Expected a time value for parameter %d", d->name, optind); retval = 0; break; } break; case CD_TIMEMSEC: if (!(d->params[i].flags & CF_SAVED)) { d->params[i].prev.intval = *(int32 *)d->params[i].ptr; d->params[i].flags |= CF_SAVED; } longval = strtol(av[optind++], &s, 10); if (longval < 0) { config_error(filename, linenum, "%s: Expected a positive value for" " parameter %d", d->name, optind); retval = 0; break; } else if (longval > 1000000) { config_error(filename, linenum, "%s: Value too large (maximum 1000000)", d->name); } longval *= 1000; if (*s == '.') { int decimal = 0; int count = 0; s++; while (count < 3 && isdigit(*s)) { decimal = decimal*10 + (*s++ - '0'); count++; } while (count++ < 3) decimal *= 10; longval += decimal; while (isdigit(*s)) s++; } if (*s) { config_error(filename, linenum, "%s: Expected a decimal number for" " parameter %d", d->name, optind); retval = 0; break; } d->params[i].new.intval = (int32)longval; break; case CD_FUNC: { int (*func)(const char *, int, char *) = (int (*)(const char *, int, char *))(d->params[i].ptr); if (!func(filename, linenum, av[optind++])) retval = 0; continue; } default: config_error(filename, linenum, "%s: Unknown type %d for" " param %d", d->name, d->params[i].type, i+1); return 0; /* don't bother continuing--something's bizarre */ } /* switch (d->params[i].type) */ d->params[i].flags |= CF_WASSET; } /* for all parameters */ break; /* because we found a match */ } /* for all directives in array */ if (!directives[n].name) { config_error(filename, linenum, "Unknown directive `%s'", directive); return 1; /* don't cause abort */ } return retval; } /* parse_config_line() */ /*************************************************************************/ /* Read in configuration options, and return nonzero for success, zero for * failure. Performs the actions needed by configure(...,CONFIGURE_READ). */ static int read_config_file(const char *modulename, ConfigDirective *directives) { FILE *f; const char *filename = modulename==NULL ? IRCSERVICES_CONF : MODULES_CONF; char *current_module = NULL; /* Current module in modules.conf */ int retval = 1; /* Return value */ int linenum = 0; char buf[4096], *s; int i, n; /* Clear `was_set' flag and `new' value for all directives */ for (n = 0; directives[n].name != NULL; n++) { directives[n].was_seen = 0; for (i = 0; i < CONFIG_MAXPARAMS; i++) { if (directives[n].params[i].flags & CF_ALLOCED_NEW) free(directives[n].params[i].new.ptrval); directives[n].params[i].flags &= ~(CF_WASSET | CF_ALLOCED_NEW); memset(&directives[n].params[i].new, 0, sizeof(directives[n].params[i].new)); if (directives[n].params[i].type == CD_FUNC) { int (*func)(const char *, int, char *) = (int (*)(const char *, int, char *)) (directives[n].params[i].ptr); func(NULL, CDFUNC_INIT, NULL); } } } /* Read in configuration file */ f = fopen(filename, "r"); if (!f) { log_perror("Can't open %s", filename); if (!nofork && isatty(2)) fprintf(stderr, "Can't open %s: %s\n", filename, strerror(errno)); return 0; } while (fgets(buf, sizeof(buf), f)) { /* Check for pathologically long files */ if (linenum+1 < linenum) { config_error(filename, linenum, "File too long"); retval = 0; break; } linenum++; /* Check for pathologically long lines */ if (strlen(buf) == sizeof(buf)-1 && buf[sizeof(buf)-1] != '\n') { /* Report the maximum size as sizeof(buf)-3 to allow \r\n as * well as \n to fit */ config_error(filename, linenum, "Line too long (%d bytes maximum)", sizeof(buf)-3); /* Skip everything else until an EOL (or EOF) is seen */ while (fgets(buf, sizeof(buf), f) && buf[strlen(buf)-1] != '\n') /*nothing*/; retval = 0; } /* Strip out comments (but don't touch # inside of quotes) */ s = buf; while (*s) { if (*s == '"') { if (!(s = strchr(s+1, '"'))) break; } else if (*s == '#') { *s = 0; break; } s++; } if (modulename) { /* Handle Module/EndModule lines specially, and don't parse * lines belonging to other modules */ if (current_module) { /* Inside a Module/EndModule pair: discard lines belonging * to other modules, and handle EndModule directives. If * we reach EndModule for the module we're supposed to be * processing, exit the loop to avoid unneeded processing. */ char tmpbuf[CONFIG_LINEMAX]; /* leave `buf' alone */ if (strlen(buf) >= sizeof(tmpbuf)) { fatal("BUG: strlen(buf) >= sizeof(tmpbuf) in configure()" " (file %s line %d)", filename, linenum); } strcpy(tmpbuf, buf); /* safe: length checked above */ s = strtok(tmpbuf, " \t\r\n"); if (s && stricmp(s, "EndModule") == 0) { int strcmp_result = strcmp(current_module, modulename); free(current_module); if (strcmp_result == 0) break; /* stop processing file, we're finished */ else current_module = NULL; continue; } else if (strcmp(current_module, modulename) != 0) { continue; } } else { /* !current_module */ /* Outside a Module/EndModule pair: handle Module * directives, and report errors for anything else */ s = strtok(buf, " \t\r\n"); if (!s) continue; if (stricmp(s, "Module") != 0) { config_error(MODULES_CONF, linenum, "Expected `Module' directive"); retval = 0; } else { current_module = strtok(NULL, " \t\r\n"); if (!current_module) { config_error(filename, linenum, "Module name missing"); retval = 0; } current_module = strdup(current_module); if (!current_module) { config_error(filename, linenum, "Out of memory"); retval = 0; break; } } continue; } /* if (current_module) */ } /* if (modulename) */ if (!parse_config_line(filename, linenum, buf, directives)) retval = 0; } fclose(f); /* Make sure all required directives were seen */ for (n = 0; directives[n].name != NULL; n++) { if (!directives[n].was_seen && (directives[n].params[0].flags & CF_DIRREQ) ) { config_error(filename, linenum, "Required directive `%s' missing", directives[n].name); retval = 0; } } return retval; } /*************************************************************************/ /*************************************************************************/ /* Set configuration options for the given module (if `modulename' is NULL, * set core configuration options). Returns nonzero on success, 0 on error * (an error message is logged, and printed to the terminal if applicable, * in this case). Returns successfully without doing anything if * `directives' is NULL. * * `action' is a bitmask of CONFIGURE_* values (services.h), specifying * what this function should do, as follows: * - CONFIGURE_READ: read new values from the configuration file * - CONFIGURE_SET: copy new values to configuration variables * If both CONFIGURE_READ and CONFIGURE_SET are specified, new values are * copied to the configuration variables only if all values are read in * successfully (i.e. if a configure(...,CONFIGURE_READ) call would have * returned success). CONFIGURE_SET alone will never fail. */ int configure(const char *modulename, ConfigDirective *directives, int action) { /* If no directives were given, return success */ if (!directives) return 1; if (action & CONFIGURE_READ) { if (!read_config_file(modulename, directives)) return 0; } if (action & CONFIGURE_SET) do_all_directives(ACTION_COPYNEW, directives); return 1; } /*************************************************************************/ /* Deconfigure given directive array (free any allocated storage and * restore original values). A no-op if `directives' is NULL. */ void deconfigure(ConfigDirective *directives) { if (directives) do_all_directives(ACTION_RESTORESAVED, directives); } /*************************************************************************/