/* * config.c * Functions for the manipulation of configuration files. * * Matej Pfajfar */ /* * Changes : * $Log$ * Revision 1.5 2002/07/15 19:05:12 montrose * bug-fix. poptReadDefaultOptions() should find and load ~/.rc files now. * * Revision 1.4 2002/07/09 19:51:41 montrose * Miscellaneous bug fixes / activated "make check" for src/or * * Revision 1.3 2002/07/03 16:31:22 montrose * Added getoptions() and made minor adjustment to poptReadDefaultOptions() * * Revision 1.2 2002/06/28 18:14:55 montrose * Added poptReadOptions() and poptReadDefaultOptions() * * Revision 1.1.1.1 2002/06/26 22:45:50 arma * initial commit: current code * * Revision 1.7 2002/04/02 14:27:11 badbytes * Final finishes. * * Revision 1.6 2002/01/27 19:23:03 mp292 * Fixed a bug in parameter checking. * * Revision 1.5 2002/01/26 18:42:15 mp292 * Reviewed according to Secure-Programs-HOWTO. * * Revision 1.4 2002/01/21 21:07:56 mp292 * Parameter checking was missing in some functions. * * Revision 1.3 2001/12/07 09:38:03 badbytes * Tested. * * Revision 1.2 2001/12/06 15:43:50 badbytes * config.c compiles. Proceeding to test it. * * Revision 1.1 2001/11/22 01:20:27 mp292 * Functions for dealing with configuration files. * * */ #include #include #include #include #include #include #include #include "config.h" #include "log.h" /* open configuration file for reading */ FILE *open_config(const unsigned char *filename) { FILE *f; if (filename) /* non-NULL filename */ { if (strspn(filename,CONFIG_LEGAL_FILENAME_CHARACTERS) == strlen(filename)) /* filename consists of legal characters only */ { f = fopen(filename, "r"); return f; } /* filename consists of legal characters only */ else /* illegal values in filename */ { return NULL; } /* illegal values in filename */ } /* non-NULL filename */ else /* NULL filename */ return NULL; } /* close configuration file */ int close_config(FILE *f) { int retval = 0; if (f) /* valid file descriptor */ { retval = fclose(f); return retval; } /* valid file descriptor */ else return -1; } /* parse the config file and obtain the required option values */ int parse_config(FILE *f, config_opt_t *option) { unsigned char keyword[CONFIG_KEYWORD_MAXLEN+1]; /* for storing the option keyword */ unsigned char *buffer = NULL; /* option value */ size_t buflen = 0; char *errtest = NULL; /* used for testing correctness of strtol() etc. */ unsigned int i_keyword = 0; /* current position within keyword */ unsigned int i_buf = 0; /* current position within buffer */ char c=0; /* input char */ unsigned int state=0; /* internal state * 0 - trying to find a keyword * 1 - reading a keyword * 2 - keyword read and recognized, looking for the option value * 3 - reading the option value * 4 - option value read * 5 - inside a comment */ int retval=0; /* return value */ int lineno=1; /* current line number */ int curopt=-1; /* current option, as an indexed in config_opt_t */ int i; if ( (f==NULL) || (option==NULL) ) /* invalid parameters */ return -1; fseek(f,0,SEEK_SET); /* make sure we start at the beginning of file */ for (;;) /* infinite loop */ { c = getc(f); if ((c == '\n') || (c == EOF)) { if (state == 1) /* reading a keyboard */ { log(LOG_ERR,"Error parsing the configuration file on line %d.", lineno); i_keyword = 0; state = 0; retval = -1; break; } /* reading a keyboard */ else if (state == 2) /* keyword read and recognized */ { log(LOG_ERR,"Error parsing option %s on line %d.",option[curopt].keyword, lineno); i_keyword = 0; state = 0; option[curopt].err=-1; retval = -1; break; } /* keyboard read and recognized */ else if (state == 3) /* reading the option value */ { buffer[i_buf++] = 0; /* add NULL character to terminate the string */ state = 4; /* conversion and copying the value into config_opt_t is done later on */ } /* reading the option value */ else if (state == 5) /* reached end of comment */ state = 0; if (c == EOF) { log(LOG_DEBUG,"parse_config() : Reached eof on line %d.",lineno); break; } else { log(LOG_DEBUG,"parse_config() : Reached eol on line %d.", lineno); lineno++; } } else if ( (state==0) && (c == '#') ) /* lines beginning with # are ignored */ { log(LOG_DEBUG,"parse_config() : Line %d begins with #.",lineno); state = 5; } else if ( (state==0) && (isspace(c)) ) /* leading whitespace is ignored */ ; else if ( (state==1) && (isspace(c)) ) /* have apparently read in all of the keyword */ { keyword[i_keyword++] = 0; curopt = -1; for (i=0;option[i].keyword != NULL;i++) /* try and identify the keyword */ { if (!strncmp(keyword,option[i].keyword,CONFIG_KEYWORD_MAXLEN)) { curopt = i; break; } } /* try and identify the keyword */ if (curopt == -1) /* can't recognise the keyword */ { log(LOG_ERR,"Error parsing the configuration file. Cannot recognize keyword %s on line %d.",keyword,lineno); retval=-1; break; } else state = 2; } else if ( (state==2) && (isspace(c)) ) /* whitespace separating keyword and value is ignored */ ; else if ( (state==3) && (isspace(c)) ) /* have apparently finished reading the option value */ { buffer[i_buf++]=0; state = 4; } else /* all other characters */ { if (state == 0) /* first character of the keyword */ { log(LOG_DEBUG, "parse_config() : %c is the start of a keyword on line %d.",c,lineno); state = 1; i_keyword = 0; keyword[i_keyword++] = c; } else if (state == 1) /* keep on reading the keyword */ { log(LOG_DEBUG,"parse_config() : %c is a character in the keyword on line %d.",c,lineno); if (i_keyword < CONFIG_KEYWORD_MAXLEN) /* check for buffer overflow */ keyword[i_keyword++] = c; else { log(LOG_ERR,"Error parsing the configuration file. Keyword on line %d exceeds %d characters.",lineno,CONFIG_KEYWORD_MAXLEN); retval=-1; break; } } else if (state == 2) /* first character of the value */ { log(LOG_DEBUG,"parse_config() : %c is the first character of the option value on line %d.",c,lineno); state = 3; i_buf=0; buflen = CONFIG_VALUE_MAXLEN+1; /* allocate memory for the value buffer */ buffer = (char *)malloc(buflen); if (!buffer) { log(LOG_ERR,"Could not allocate memory."); retval=-1; break; } else buffer[i_buf++]=c; } else if (state == 3) /* keep on reading the value */ { log(LOG_DEBUG,"parse_config() : %c is a character in the value of the keyword on line %d.",c,lineno); if (i_buf >= buflen) { log(LOG_ERR,"Length of keyword value on line %u exceeds the length limit (%u).",lineno, CONFIG_VALUE_MAXLEN); retval=-1; break; } buffer[i_buf++]=c; } else if (state == 5) ; /* character is part of a comment, skip */ else /* unexpected error */ { log(LOG_ERR,"Unexpected error while parsing the configuration file."); log(LOG_DEBUG,"parse_config() : Encountered a non-delimiter character while not in states 0,1,2 or 3!"); break; } } if (state==4) /* convert the value of the option to the appropriate type and write into OPT */ { switch(option[curopt].r_type) /* consider each type separately */ { case CONFIG_TYPE_STRING: /* resize the buffer to fit the data exactly */ buffer = (char *)realloc(buffer,i_buf); if (!buffer) { log(LOG_ERR,"Could not allocate memory."); return -1; } option[curopt].r.str = buffer; option[curopt].err = 1; break; case CONFIG_TYPE_CHAR: option[curopt].r.c = *buffer; option[curopt].err = 1; break; case CONFIG_TYPE_INT: errtest = NULL; option[curopt].r.i = (int)strtol(buffer,&errtest,0); if ((unsigned char *)errtest == buffer) { log(LOG_ERR, "Error parsing configuration file. Option %s on line %d does not seem to be of the required type.\n",option[curopt].keyword,--lineno); option[curopt].err = -1; if (buffer) free(buffer); return -1; } else option[curopt].err = 1; break; case CONFIG_TYPE_LONG: errtest = NULL; option[curopt].r.l = strtol(buffer,&errtest,0); if ((unsigned char *)errtest == buffer) { log(LOG_ERR, "Error parsing configuration file. Option %s on line %d does not seem to be of the required type.\n",option[curopt].keyword,--lineno); option[curopt].err = -1; if (buffer) free(buffer); return -1; } else option[curopt].err = 1; break; case CONFIG_TYPE_DOUBLE: errtest = NULL; option[curopt].r.d = strtod(buffer,&errtest); if ((unsigned char *)errtest == buffer) { log(LOG_ERR, "Error parsing configuration file. Option %s on line %d does not seem to be of the required type.\n",option[curopt].keyword,--lineno); option[curopt].err = -1; if (buffer) free(buffer); return -1; } else option[curopt].err = 1; break; default: /* unexpected type */ log(LOG_ERR, "Error parsing configuration file. Unrecognized option type!"); if (buffer) free(buffer); return -1; } /* clean up */ if (option[curopt].r_type != CONFIG_TYPE_STRING) { if (buffer) free(buffer); buflen=0; } state = 0; curopt = -1; i_buf=0; i_keyword=0; } } /* infinite loop */ return retval; } int poptReadOptions(poptContext optCon, const unsigned char *fname) /** poptReadOptions reads popt-style options from the specified filename. RETURN VALUE: INT_MIN = problem opening config file, else standard poptGetNextOpt() return value **/ { FILE *fp; int argc, c, n; char **argv; char line[1024]; line[0] = line[1] = '-'; /* prepend expected long name option flag */ fp = fopen(fname,"r"); if ( fp == NULL ) return INT_MIN; c = 0; while ( c >= -1 ) { if ( fscanf(fp,"%*[ \n]%n",&n) == EOF ) break; /* eat leading whitespace */ if ( fscanf(fp, "%[^\n]",&line[2]) == EOF ) break; /* read a line */ switch ( line[2] ) { case '#': /* comments begin with this */ case '[': /* section header. ignore for now. maybe do something special in future version... */ continue;/* ignore */ default: /* we got a bite, lets reel it in now */ poptParseArgvString(line,&argc,(const char ***)&argv); /* Argv-ify what we found */ poptStuffArgs(optCon,(const char **)argv); /* stuff new arguments so they can be interpreted */ free(argv); /* free storage allocated by poptParseArgvString */ c = poptGetNextOpt(optCon); /* interpret option read from config file */ } } fclose(fp); return c; } int poptReadDefaultOptions(const char *cmd, poptContext optCon) /** reads popt-style options from /etc/rc and ~/.rc RETURN VALUE: same as poptReadOptions() **/ { char fname[256]; int c; sprintf(fname,"/etc/%src",cmd); c = poptReadOptions(optCon,fname); if ( c == INT_MIN || c >= -1 ) { sprintf(fname,"%s/.%src",getenv("HOME"),cmd); c = poptReadOptions(optCon,fname); } return (c == INT_MIN) ? -1 : c; }