/* $Cambridge: hermes/src/prayer/accountd/config.c,v 1.3 2004/10/01 12:42:27 dpc22 Exp $ */
/************************************************
* Prayer - a Webmail Interface *
************************************************/
/* Copyright (c) University of Cambridge 2000 - 2002 */
/* See the file NOTICE for conditions of use and distribution. */
#include "accountd.h"
/* config_create() *********************************************************
*
* Create a new config structure using own pool
**************************************************************************/
struct config *config_create(void)
{
struct pool *pool = pool_create(0);
struct config *config = pool_alloc(pool, sizeof(struct config));
/* General options */
config->pool = pool;
config->accountd_port = 0L;
config->log_debug = NIL;
config->child_timeout = 10 * 60;
config->authtype = AUTHTYPE_UNKNOWN;
/* Location of filter files */
config->msforward_name = ".MSforward";
config->forward_name = ".forward";
config->aliases_name = "vacation.aliases";
config->filter_restricted = NIL;
/* Password changing */
config->pwd_cmdline = NIL;
config->pwd_script = NIL;
config->pwd_pty = T;
/* Fullname checking */
config->checkfull_cmdline = NIL;
config->checkfull_script = NIL;
config->checkfull_pty = T;
/* Fullname changing */
config->full_cmdline = NIL;
config->full_script = NIL;
config->full_pty = T;
/* Quota check */
config->quota_cmdline = NIL;
config->quota_hermes = NIL;
/* SSL configuration */
config->use_ssl = NIL;
config->ssl_cert_file = NIL;
config->ssl_privatekey_file = NIL;
config->ssl_dh_file = NIL;
config->ssl_rsakey_lifespan = (10 * 60); /* 10 mins */
config->ssl_rsakey_freshen = (10 * 60); /* 10 mins */
config->ssl_session_timeout = (24 * 60 * 60); /* 60 mins */
config->ssl_session_dir = NIL;
config->egd_socket = NIL;
return (config);
}
/* config_free() *********************************************************
*
* Free config structure
************************************************************************/
void config_free(struct config *config)
{
pool_free(config->pool);
}
/* config_paniclog() *******************************************************
*
* Special interation of paniclog() to print errrors consistently when
* frontend or session starting up with introducing dependancies on code
* which is specific to the frontend or session processes.
**************************************************************************/
static void config_paniclog(struct config *config, char *fmt, ...)
{
unsigned long len;
va_list ap;
va_start(ap, fmt);
len = log_entry_size(fmt, ap);
va_end(ap);
va_start(ap, fmt);
log_panic_ap(len, fmt, ap);
va_end(ap);
}
/* ====================================================================== */
/* conf used only to calculate offsets into live config structure */
static struct config conf;
#define OFFSET(x) ((char*)(&conf.x) - (char *)&conf)
/* Config options. Searched by binary chop => must be sorted correctly
* However config_init() checks that order is correct, bails out otherwise */
static struct {
char *name;
enum { config_bool, config_number, config_time, config_string,
config_path, config_perm, config_authtype, config_unknown
} type;
int offset;
} config_options[] = {
{
"accountd_port", config_number, OFFSET(accountd_port)}, {
"aliases_name", config_string, OFFSET(aliases_name)}, {
"authtype", config_authtype, OFFSET(authtype)}, {
"checkfull_cmdline", config_string, OFFSET(checkfull_cmdline)}, {
"checkfull_pty", config_bool, OFFSET(checkfull_pty)}, {
"checkfull_script", config_string, OFFSET(checkfull_script)}, {
"child_timeout", config_time, OFFSET(child_timeout)}, {
"egd_socket", config_path, OFFSET(egd_socket)}, {
"filter_restricted", config_bool, OFFSET(filter_restricted)}, {
"forward_name", config_string, OFFSET(forward_name)}, {
"full_cmdline", config_string, OFFSET(full_cmdline)}, {
"full_pty", config_bool, OFFSET(full_pty)}, {
"full_script", config_string, OFFSET(full_script)}, {
"msforward_name", config_string, OFFSET(msforward_name)}, {
"pwd_cmdline", config_string, OFFSET(pwd_cmdline)}, {
"pwd_pty", config_bool, OFFSET(pwd_pty)}, {
"pwd_script", config_string, OFFSET(pwd_script)}, {
"quota_cmdline", config_string, OFFSET(quota_cmdline)}, {
"quota_hermes", config_bool, OFFSET(quota_hermes)}, {
"ssl_cert_file", config_path, OFFSET(ssl_cert_file)}, {
"ssl_dh_file", config_path, OFFSET(ssl_dh_file)}, {
"ssl_privatekey_file", config_path, OFFSET(ssl_privatekey_file)}, {
"ssl_rsakey_freshen", config_time, OFFSET(ssl_rsakey_freshen)}, {
"ssl_rsakey_lifespan", config_time, OFFSET(ssl_rsakey_lifespan)}, {
"use_ssl", config_bool, OFFSET(use_ssl)}, {
NIL, config_unknown, 0}
};
static unsigned long options_size = 0L;
/* ====================================================================== */
/* config_init() *********************************************************
*
* Initialise config engine. Counts number of options in the "options"
* array, and makes sure that they are properly sorted for binary chop
* searches.
************************************************************************/
static BOOL config_init(void)
{
unsigned long offset;
offset = 0;
while (config_options[offset].name) {
if (config_options[offset + 1].name) {
if (strcmp(config_options[offset].name,
config_options[offset + 1].name) > 0) {
config_paniclog(NIL,
"config.c: options array sorted incorrectly at %s",
config_options[offset].name);
return (NIL);
}
}
offset++;
}
options_size = offset;
return (T);
}
/* ====================================================================== */
/* config_find_key() *****************************************************
*
* Find offset to "options" structure which corresponds to key
* key: Key to lookup
*
* Returns: Array offset, (-1) on error.
************************************************************************/
static int config_find_option(char *key)
{
int first = 0;
int last = options_size;
int middle = 0;
int rc;
/* Binary chop */
while (first < last) {
middle = (first + last) / 2;
rc = strcmp(config_options[middle].name, key);
if (rc == 0)
return (middle);
else if (rc < 0)
first = middle + 1;
else
last = middle;
}
/* Check whether last interaction of loop found match */
if (!strcmp(config_options[middle].name, key))
return (middle);
return (-1); /* Not found */
}
/* ====================================================================== */
/* Various static utility routines for parsing elements of config file */
static BOOL config_parse_bool(BOOL * result, char *s)
{
if (!strcasecmp(s, "TRUE") || !strcasecmp(s, "T") || !strcmp(s, "1")) {
*result = T;
return (T);
}
if (!strcasecmp(s, "FALSE") || !strcasecmp(s, "NIL")
|| !strcmp(s, "0")) {
*result = NIL;
return (T);
}
return (NIL);
}
static BOOL config_parse_number(unsigned long *result, char *text)
{
char *s;
for (s = text; *s; s++)
if (!Uisdigit(*s))
return (NIL);
*result = atoi(text);
return (T);
}
static BOOL config_parse_perm(unsigned long *result, char *s)
{
if (s[0] != '0')
return (NIL);
if ((s[1] < '0') || (s[1] > '7'))
return (NIL);
if ((s[2] < '0') || (s[2] > '7'))
return (NIL);
if ((s[3] < '0') || (s[3] > '7'))
return (NIL);
*result =
(((s[1] - '0') * (8 * 8)) + ((s[2] - '0') * (8)) + (s[3] - '0'));
return (T);
}
static BOOL config_parse_authtype(AUTHTYPE * result, char *s)
{
*result = AUTHTYPE_UNKNOWN;
if (!strcasecmp(s, "pam"))
*result = AUTHTYPE_PAM;
else if (!strcasecmp(s, "pwd"))
*result = AUTHTYPE_PWD;
else if (!strcasecmp(s, "pwd_md5"))
*result = AUTHTYPE_PWD_MD5;
else if (!strcasecmp(s, "shadow"))
*result = AUTHTYPE_SHADOW;
else if (!strcasecmp(s, "shadow_md5"))
*result = AUTHTYPE_SHADOW_MD5;
return ((*result != AUTHTYPE_UNKNOWN) ? T : NIL);
}
static BOOL config_parse_time(unsigned long *result, char *text)
{
char *s;
unsigned long multiple;
if (!(text && text[0]))
return (NIL);
for (s = text; s[1]; s++)
if (!Uisdigit(*s))
return (NIL);
if (Uisdigit(*s))
multiple = 1;
else {
switch (Utoupper(*s)) {
case 'S':
multiple = 1;
break;
case 'M':
multiple = 60;
break;
case 'H':
multiple = 60 * 60;
break;
case 'D':
multiple = 24 * 60 * 60;
break;
default:
return (NIL);
}
*s = '\0';
}
*result = (multiple * atoi(text));
return (T);
}
static BOOL
config_parse_string(char **result, char *text, struct pool *pool)
{
char *s;
text = string_trim_whitespace(text);
if (*text == '"') {
text++;
for (s = text; *s; s++) {
if (*s == '"') {
*s = '\0';
*result = pool_strdup(pool, text);
return (T);
}
}
return (NIL);
}
*result = pool_strdup(pool, text);
return (T);
}
/* ====================================================================== */
/* config_parse_single() *************************************************
*
* Parse a single (key, value) pair extracted from configuration file
* or passed as command line option.
* config:
* key: Text for key
* value: Text for value
* lineno: Line number in configuration file (for error messages)
* 0 if this option passed in on the command line.
************************************************************************/
static BOOL
config_parse_single(struct config *config, char *key, char *value,
unsigned long lineno)
{
struct pool *pool = config->pool;
int i;
char *ptr;
if ((i = config_find_option(key)) < 0) {
if (lineno > 0L)
config_paniclog(config,
("Error parsing configuration file at line %lu: "
"Unknown option \"%s\""), lineno, key);
else
config_paniclog(config, "Unknown option \"%s\"", key);
return (NIL);
}
ptr = ((char *) config) + config_options[i].offset;
switch (config_options[i].type) {
case config_bool:
if (!config_parse_bool((BOOL *) ptr, value)) {
if (lineno > 0L)
config_paniclog(config,
("Error parsing configuration file at line %lu: "
"Option %s takes boolean argument"),
lineno, key);
else
config_paniclog(config,
"option \"%s\" takes boolean argument",
key);
return (NIL);
}
break;
case config_number:
if (!config_parse_number((unsigned long *) ptr, value)) {
if (lineno > 0L)
config_paniclog(config,
("Error parsing configuration file at line %lu: "
"Option %s takes a numeric argument"),
lineno, key);
else
config_paniclog(config,
"option \"%s\" takes a numeric argument",
key);
return (NIL);
}
break;
case config_perm:
if (!config_parse_perm((unsigned long *) ptr, value)) {
if (lineno > 0L)
config_paniclog(config,
("Error parsing configuration file at line %lu: "
"Option %s takes a permission argument"),
lineno, key);
else
config_paniclog(config,
"option \"%s\" takes a permission argument",
key);
return (NIL);
}
break;
case config_time:
if (!config_parse_time((unsigned long *) ptr, value)) {
if (lineno > 0L)
config_paniclog(config,
("Error parsing configuration file at line %lu: "
"Option %s takes a time argument"),
lineno, key);
else
config_paniclog(config,
"option \"%s\" takes a time argument",
key);
return (NIL);
}
break;
case config_string:
case config_path:
if (!config_parse_string((char **) ptr, value, pool)) {
if (lineno > 0L)
config_paniclog(config,
("Error parsing configuration file at line %lu: "
"Option %s takes string argument"),
lineno, key);
else
config_paniclog(config,
"option \"%s\" takes string argument",
key);
return (NIL);
}
break;
case config_authtype:
if (!config_parse_authtype((AUTHTYPE *) ptr, value)) {
if (lineno > 0L)
config_paniclog(config,
("Error parsing configuration file at line %lu: "
"Option %s takes a authtype argument"),
lineno, key);
else
config_paniclog(config,
"option \"%s\" takes a authtype argument",
key);
return (NIL);
}
break;
default:
return (NIL);
}
return (T);
}
/* ====================================================================== */
/* config_skip_whitespace() **********************************************
*
* Skip over whitespace in string (clone of string_skip_whitespace?)
* sp: Ptr to string. Updated to reflect location of next token
*
* Returns: Location of the next token.
************************************************************************/
static char *config_skip_whitespace(char **sp)
{
char *s = *sp;
if (!s)
return (NIL);
/* Skip leading whitespace */
while ((*s == ' ') || (*s == '\t'))
s++;
*sp = s;
return ((*s) ? s : NIL);
}
/* config_get_key() *******************************************************
*
* Get key token from config line (token terminated by whitespace or '='
* character).
* sp: Ptr to string. Updated to reflect location of next token
*
* Returns: Location of the next token.
*************************************************************************/
static char *config_get_key(char **sp)
{
char *s = *sp, *result;
if (!s)
return (NIL);
while ((*s == ' ') || (*s == '\t'))
s++;
/* Record position of this token */
result = s;
/* Find next whitespace character, '=', or end of string */
while ((*s) && (*s != ' ') && (*s != '\t') && (*s != '='))
s++;
/* Tie off the string unless \0 already reached, or '=' separator */
if (*s && (*s != '=')) {
*s++ = '\0';
while ((*s == ' ') || (*s == '\t'))
s++;
}
if (*s != '=')
return (NIL);
*s++ = '\0';
while ((*s == ' ') || (*s == '\t'))
s++;
/* Record position of first non-whitespace character for next caller */
*sp = s;
return (result);
}
/* ====================================================================== */
/* config_parse_option() *************************************************
*
* Parse a single option passed in from the command line
* config:
* option: "key=value" or "<special> <args>" to parse
*
* Returns: T if option parsed successfully
* NIL otherwise (message will be sent to paniclog).
************************************************************************/
BOOL config_parse_option(struct config * config, char *option)
{
char *key, *value;
option = string_trim_whitespace(option);
if (option[0] == '\0')
return (T);
if ((key = config_get_key(&option)) == NIL) {
config_paniclog(config, "Option badly formatted: \"%s\"", option);
return (NIL);
}
if ((value = config_skip_whitespace(&option)) == NIL) {
config_paniclog(config,
"Option badly formatted: \"%s %s\"", key, option);
return (NIL);
}
return (config_parse_single(config, key, value, 0L));
}
/* ====================================================================== */
/* config_get_line() ****************************************************
*
* Get a linear whitespace line (e.g: folded RFC822 or HTTP header line)
* sp: Ptr to current string location
* (updated to point to following line)
* squeeze: Remove superfluous spaces from result.
*
* Returns: Ptr to this line
***********************************************************************/
static char *config_get_line(char **sp, BOOL squeeze)
{
char *s, *result;
s = *sp;
if (!(s && s[0]))
return (NIL);
/* CR, LF or CRLF before data proper starts? */
if ((s[0] == '\015') || (s[0] == '\012')) {
result = s;
if ((s[0] == '\015') && (s[1] == '\012')) { /* CRLF */
*s = '\0';
s += 2;
} else
*s++ = '\0'; /* CR or LF */
*sp = s;
return (result);
}
result = s; /* Record position of non-LWS */
while (*s) {
if ((*s == '\015') || (*s == '\012')) { /* CR, LF or CRLF */
char *t = s;
s += ((s[0] == '\015') && (s[1] == '\012')) ? 2 : 1;
if ((*s != ' ') && (*s != '\t')) {
*t = '\0';
break;
}
} else
s++;
}
*sp = s; /* Set up for next caller */
return (result);
}
/* config_count_line() ***************************************************
*
* Count number of lines in string
************************************************************************/
static unsigned long config_count_line(char *s)
{
unsigned long lines = 1;
while (*s) {
if ((*s == '\015') || (*s == '\012')) {
s += ((s[0] == '\015') && (s[1] == '\012')) ? 2 : 1;
lines++;
} else
s++;
}
return (lines);
}
/* config_compress_line() ************************************************
*
* Remove continuation sequences from LWS line
************************************************************************/
static BOOL config_compress_line(char *s)
{
char *t = s;
while (*s) {
/* Check for correctly folded line */
if ((s[0] == '\\') && ((s[1] == '\015') || (s[1] == '\012'))) {
s += ((s[1] == '\015') && (s[2] == '\012')) ? 3 : 2;
while (string_isspace(*s))
s++;
continue;
}
/* Check for unexpected line break */
if ((s[0] == '\015') || (s[0] == '\012'))
return (NIL);
if (s > t)
*t++ = *s++;
else
t++, s++;
}
*t = '\0';
return (T);
}
/* ====================================================================== */
/* config_parse_file() ***************************************************
*
* Parse configuration file
* config:
* filename: Name of configuration file (main() routine will pass
* compile time default or user specified version).
*
* Returns: T if option parsed successfully
* NIL otherwise (message will be sent to paniclog).
************************************************************************/
BOOL config_parse_file(struct config * config, char *filename)
{
char *buffer, *text;
char *line, *key, *value;
unsigned long next_lineno = 1;
unsigned long cur_lineno = 1;
BOOL okay;
int fd;
struct stat sbuf;
unsigned long size;
if ((fd = open(filename, O_RDONLY, 0)) < 0) {
config_paniclog(config,
"Couldn't open configuration file: \"%s\"",
filename);
return (NIL);
}
if ((fstat(fd, &sbuf)) < 0) {
config_paniclog(config,
"Couldn't fstat configuration file: \"%s\"",
filename);
return (NIL);
}
if ((text = buffer = malloc((size = sbuf.st_size) + 1)) == NIL) {
config_paniclog(config, "Out of memory");
return (NIL);
}
if (read(fd, buffer, size) < size) {
free(buffer);
config_paniclog(config,
"Couldn't read in configuration file: \"%s\"",
filename);
return (NIL);
}
buffer[size] = '\0';
/* Prep options array if we need to */
if ((options_size == 0L) && !config_init()) {
free(buffer);
return (NIL);
}
okay = T;
while ((line = config_get_line(&text, NIL))) {
cur_lineno = next_lineno;
next_lineno += config_count_line(line);
if (!config_compress_line(line)) {
config_paniclog(config,
"Invalid folded line starting at line %d: \"%s\"",
cur_lineno, line);
okay = NIL;
continue;
}
if (line[0] == '#')
continue;
line = string_trim_whitespace(line);
if (line[0] == '\0')
continue;
if ((key = config_get_key(&line)) == NIL) {
config_paniclog(config,
"Line %lu of config file badly formatted: \"%s\"",
cur_lineno, line);
okay = NIL;
continue;
}
if ((value = config_skip_whitespace(&line)) == NIL) {
config_paniclog(config,
"Line %lu of config file badly formatted: \"%s %s\"",
cur_lineno, key, line);
okay = NIL;
continue;
}
if (!config_parse_single(config, key, value, cur_lineno))
okay = NIL;
}
free(buffer);
return (T);
}
/* ====================================================================== */
/* Couple of support macros to help test the integrity of the configuration
* that we have just parsed */
#define TEST_STRING(x, y) \
{ \
if (!(x && x[0])) { \
config_paniclog(config, "config: "y" not defined"); \
return(NIL); \
} \
}
#define TEST_NUMBER(x, y) \
{ \
if (x == 0) { \
config_paniclog(config, "config: "y" not defined"); \
return(NIL); \
} \
}
/* config_check() *********************************************************
*
* Check that a parsed configuration is sane and self-consistent.
* config:
*
* Returns: T if config okay, NIL otherwise
*************************************************************************/
BOOL config_check(struct config * config)
{
TEST_STRING(config->msforward_name, "msforward_name");
TEST_STRING(config->forward_name, "forward_name");
TEST_STRING(config->forward_name, "forward_name");
TEST_STRING(config->pwd_cmdline, "pwd_cmdline");
TEST_STRING(config->full_cmdline, "full_cmdline");
TEST_STRING(config->quota_cmdline, "quota_cmdline");
if (config->authtype == AUTHTYPE_UNKNOWN) {
config_paniclog(config, "authtype not defined");
return (NIL);
}
return (T);
}
syntax highlighted by Code2HTML, v. 0.9.1