/* Configuration file handling.
*
* IRC Services is copyright (c) 1996-2007 Andrew Church.
* E-mail: <achurch@achurch.org>
* 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);
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1