view src/configfile.c @ 206:eb2589384836 noffle

[svn] * TODO: Update the TODO list. * src/util.c: When updating timestamp files, write new stamp to temp file and rename, so failure with e.g. full disc doesn't leave an empty stamp file.
author bears
date Wed, 14 Nov 2001 10:56:42 +0000
parents 24d4cd032da5
children 21200ce10e68
line wrap: on
line source

/*
  configfile.c

  The following macros must be set, when compiling this file:
    CONFIGFILE
    SPOOLDIR
    VERSION

  $Id: configfile.c 316 2001-10-31 11:44:53Z bears $
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <ctype.h>
#include <limits.h>
#include <regex.h>
#include "configfile.h"
#include "filter.h"
#include "itemlist.h"
#include "log.h"
#include "util.h"
#include "portable.h"
#include "wildmat.h"

typedef struct
{
    int numGroup;
    int maxGroup;
    char **groups;
}
GroupEntry;

struct GroupEnum
{
    GroupEntry *groupEntry;
    int groupIdx;
};

typedef struct
{
    char *name;
    char *user;
    char *pass;
    GroupEntry getgroups;
    GroupEntry omitgroups;
}
ServEntry;

typedef struct
{
    char *pattern;
    int days;
}
ExpireEntry;

typedef struct
{
    char *pattern;
    char *mode;
}
AutoSubscribeModeEntry;

struct
{
    /* Compile time options */
    const char *spoolDir;
    const char *version;
    /* Options from the config file */
    int maxFetch;
    int autoUnsubscribeDays;
    int threadFollowTime;
    int connectTimeout;
    Bool autoSubscribe;
    Bool autoUnsubscribe;
    Bool infoAlways;
    Bool replaceMsgId;
    Str hostnameMsgId; 
    Bool postLocal;
    Str defaultAutoSubscribeMode;
    Str mailTo;
    int defaultExpire;
    int numServ;
    int maxServ;
    ServEntry *serv;
    int servIdx; /* for server enumeration */
    int numExpire;
    int maxExpire;
    ExpireEntry *expire;
    int numAutoSubscribeMode;
    int maxAutoSubscribeMode;
    AutoSubscribeModeEntry *autoSubscribeMode;
    Str pathHeader;
    Str fromDomain;
    Str organization;
} config =
{
    SPOOLDIR, /* spoolDir */
    VERSION,  /* version */
    300,      /* maxFetch */
    30,       /* autoUnsubscribeDays */
    7,        /* threadFollowTime */
    30,       /* connectTimeout */
    FALSE,    /* autoSubscribe */
    FALSE,    /* autoUnsubscribe */
    TRUE,     /* infoAlways */
    TRUE,     /* replaceMsgId */
    "",       /* hostnameMsgId */
    FALSE,    /* postLocal */
    "over",   /* defaultAutoSubscribeMode */
    "",       /* mailTo */
    14,       /* defaultExpire */
    0,        /* numServ */
    0,        /* maxServ */
    NULL,     /* serv */
    0,	      /* servIdx */
    0,        /* numExpire */
    0,        /* maxExpire */
    NULL,     /* expire */
    0,        /* numAutoSubscribeMode */
    0,        /* maxAutoSubscribeMode */
    NULL,     /* autoSubscribeMode */
    "",       /* pathHeader */
    "",       /* fromDomain */
    ""        /* organization */
};

const char * Cfg_spoolDir( void ) { return config.spoolDir; }
const char * Cfg_version( void ) { return config.version; }

int Cfg_maxFetch( void ) { return config.maxFetch; }
int Cfg_autoUnsubscribeDays( void ) { return config.autoUnsubscribeDays; }
int Cfg_threadFollowTime( void ) { return config.threadFollowTime; }
int Cfg_connectTimeout( void ) { return config.connectTimeout; }
Bool Cfg_autoUnsubscribe( void ) { return config.autoUnsubscribe; }
Bool Cfg_autoSubscribe( void )  { return config.autoSubscribe; }
Bool Cfg_infoAlways( void )  { return config.infoAlways; }
Bool Cfg_replaceMsgId( void ) { return config.replaceMsgId; }
const char * Cfg_hostnameMsgId( void ) { return config.hostnameMsgId; }
Bool Cfg_postLocal( void ) { return config.postLocal; }
const char * Cfg_defaultAutoSubscribeMode( void ) {
    return config.defaultAutoSubscribeMode; }
const char * Cfg_mailTo( void ) { return config.mailTo; }
int Cfg_defaultExpire( void ) { return config.defaultExpire; }
const char * Cfg_pathHeader( void ) { return config.pathHeader; }
const char * Cfg_fromDomain( void ) { return config.fromDomain; }
const char * Cfg_organization( void ) { return config.organization; }

void
Cfg_beginServEnum( void )
{
    config.servIdx = 0;
}

Bool
Cfg_nextServ( Str name )
{
    if ( config.servIdx >= config.numServ )
        return FALSE;
    strcpy( name, config.serv[ config.servIdx ].name );
    ++config.servIdx;
    return TRUE;
}

static Bool
searchServ( const char *name, int *idx )
{
    int i;

    for ( i = 0; i < config.numServ; ++i )
        if ( strcmp( name, config.serv[ i ].name ) == 0 )
        {
            *idx = i;
            return TRUE;
        }
    return FALSE;
}

Bool
Cfg_servListContains( const char *name )
{
    int idx;

    return searchServ( name, &idx );
}

Bool
Cfg_servIsPreferential( const char *name1, const char *name2 )
{
    Bool exists1, exists2;
    int idx1, idx2;

    exists1 = searchServ( name1, &idx1 );
    exists2 = searchServ( name2, &idx2 );
    if ( exists1 && exists2 )
        return ( idx1 < idx2 );
    if ( exists1 && ! exists2 )
        return TRUE;
    /* ( ! exists1 && exists2 ) || ( ! exists1 && ! exists2 ) */
    return FALSE;
}

void
Cfg_authInfo( const char *name, Str user, Str pass )
{
    int idx;

    if ( searchServ( name, &idx ) )
    {
        strcpy( user, config.serv[ idx ].user );
        strcpy( pass, config.serv[ idx ].pass );
    }
    else
    {
        user[ 0 ] = '\0';
        pass[ 0 ] = '\0';
    }
}

int
Cfg_expire( const char *grp )
{
    int i, res;

    for ( i = 0; i < config.numExpire; i++ )
	if ( Wld_match( grp, config.expire[ i ].pattern ) )
	{
	    res = config.expire[ i ].days;
	    Log_dbg( LOG_DBG_CONFIG,
		     "Custom expire period %d for group %s",
		     res, grp );
	    return res;
	}

    return Cfg_defaultExpire();
}

const char *
Cfg_autoSubscribeMode( const char *grp )
{
    int i;
    const char *res;

    for ( i = 0; i < config.numAutoSubscribeMode; i++ )
	if ( Wld_match( grp, config.autoSubscribeMode[ i ].pattern ) )
	{
	    res = config.autoSubscribeMode[ i ].mode;
	    Log_dbg( LOG_DBG_CONFIG,
		     "Custom auto subscribe mode %s for group %s",
		     res, grp );
	    return res;
	}

    return Cfg_defaultAutoSubscribeMode();
}

GroupEnum *
new_GetGrEn( const char *name )
{
    GroupEnum *res;
    int servIdx;

    res = (GroupEnum *) malloc( sizeof( GroupEnum ) );
    if ( res == NULL )
    {
	Log_err( "Malloc of GroupEnum failed." );
	exit( EXIT_FAILURE );
    }
    if ( ! searchServ( name, &servIdx ) )
	res->groupEntry = NULL;
    else
	res->groupEntry = &config.serv[ servIdx ].getgroups;
    GrEn_first( res );
    return res;
}

GroupEnum *
new_OmitGrEn( const char *name )
{
    GroupEnum *res;
    int servIdx;

    res = (GroupEnum *) malloc( sizeof( GroupEnum ) );
    if ( res == NULL )
    {
	Log_err( "Malloc of GroupEnum failed." );
	exit( EXIT_FAILURE );
    }
    if ( ! searchServ( name, &servIdx ) )
	res->groupEntry = NULL;
    else
	res->groupEntry = &config.serv[ servIdx ].omitgroups;
    GrEn_first( res );
    return res;
}

void
del_GrEn( GroupEnum *ge )
{
    free(ge);
}

void
GrEn_first( GroupEnum *ge )
{
    ge->groupIdx = 0;
}

const char *
GrEn_next( GroupEnum *ge )
{
    if ( ge->groupEntry == NULL ||
	 ge->groupIdx >= ge->groupEntry->numGroup )
	return NULL;
    return ge->groupEntry->groups[ ge->groupIdx++ ];
}

static void
logSyntaxErr( const char *line )
{
    Log_err( "Syntax error in config file: %s", line );
}

static void
getBool( Bool *variable, const char *line )
{
    Str value, name, lowerLn;

    strcpy( lowerLn, line );
    Utl_toLower( lowerLn );
    if ( sscanf( lowerLn, "%s %s", name, value ) != 2 )
    {
        logSyntaxErr( line );
        return;
    }
    
    if ( strcmp( value, "yes" ) == 0 )
        *variable = TRUE;
    else if ( strcmp( value, "no" ) == 0 )
        *variable = FALSE;
    else
        Log_err( "Error in config file %s must be yes or no", name );
}

static void
getInt( int *variable, int min, int max, const char *line )
{
    int value;
    Str name;

    if ( sscanf( line, "%s %d", name, &value ) != 2 )
    {
        logSyntaxErr( line );
        return;
    }
    if ( value < min || value > max )
    {
        Log_err( "Range error in config file %s [%d,%d]", name, min, max );
        return;
    }
    *variable = value;
}

static void
getStr( char *variable, const char *line )
{
    Str dummy;

    if ( sscanf( line, "%s %s", dummy, variable ) != 2 )
    {
        logSyntaxErr( line );
        return;
    }
}

static void
getText( Str variable, const char *line )
{
    const char *l;
    
    /* Skip command */
    l = Utl_restOfLn( line, 1 );
    Utl_cpyStr( variable, l );
}

static void
getServ( const char *line )
{
    Str dummy, name, user, pass;
    int r, len;
    ServEntry entry;

    memset( &entry, 0, sizeof( entry ) );
    user[ 0 ] = pass[ 0 ] = '\0';
    r = sscanf( line, "%s %s %s %s",
                dummy, name, user, pass );
    if ( r < 2 )
    {
        logSyntaxErr( line );
        return;
    }
    len = strlen( name );
    /* To make server name more definit, it is made lowercase and
       port is removed, if it is the default port */
    if ( len > 4 && strcmp( name + len - 4, ":119" ) == 0 )
        name[ len - 4 ] = '\0';
    Utl_toLower( name );

    Utl_allocAndCpy( &entry.name, name );
    Utl_allocAndCpy( &entry.user, user );
    Utl_allocAndCpy( &entry.pass, pass );

    if ( config.maxServ < config.numServ + 1 )
    {
        if ( ! ( config.serv = realloc( config.serv,
                                        ( config.maxServ + 5 )
                                        * sizeof( ServEntry ) ) ) )
        {
            Log_err( "Could not realloc server list" );
            exit( EXIT_FAILURE );
        }
        config.maxServ += 5;
    }
    config.serv[ config.numServ++ ] = entry;
}

static void
getExpire( const char *line )
{
    Str dummy, pattern;
    ExpireEntry entry;
    int days;

    if ( sscanf( line, "%s %s %d", dummy, pattern, &days ) != 3 )
    {
	logSyntaxErr( line );
	return;
    }
    else
    {
	if ( days < 0 )
	{
	    Log_err( "Expire days error in '%s': must be integer > 0",
		     line, days );
	    return;
	}

	Utl_toLower( pattern );
	Utl_allocAndCpy( &entry.pattern, pattern );
	entry.days = days;

	if ( config.maxExpire < config.numExpire + 1 )
	{
	    if ( ! ( config.expire = realloc( config.expire,
					      ( config.maxExpire + 5 )
					      * sizeof( ExpireEntry ) ) ) )
	    {
		Log_err( "Could not realloc expire list" );
		exit( EXIT_FAILURE );
	    }
	    config.maxExpire += 5;
	}
	config.expire[ config.numExpire++ ] = entry;
    }
}

static void
getGroups( char *line, Bool isGet )
{
    const char *name;
    ItemList *patterns;
    const char *pattern;

    if ( config.numServ == 0 )
    {
	Log_err( "No current server in %s", line );
	return;
    }
    
    name = line;
    /* Skip over name and terminate it */
    while ( line[ 0 ] != '\0' && ! isspace( line[ 0 ] ) )
	line++;
    if ( line[ 0 ] == '\0' )
    {
	logSyntaxErr( name );
	return;
    }
    line[ 0 ] = '\0';
    line++;
    
    patterns = new_Itl( line, " ," );
    for( pattern = Itl_first( patterns );
	 pattern != NULL;
	 pattern = Itl_next( patterns ) )
    {
	GroupEntry *g;

	if ( isGet )
	    g = &config.serv[ config.numServ - 1 ].getgroups;
	else
	    g = &config.serv[ config.numServ - 1 ].omitgroups;
	if ( g->maxGroup < g->numGroup + 1 )
	{
	    if ( ! ( g->groups = realloc( g->groups,
					  ( g->maxGroup + 5 )
					  * sizeof( char * ) ) ) )
	    {
		Log_err( "Could not realloc group list" );
		exit( EXIT_FAILURE );
	    }
	    g->maxGroup += 5;
	}
	Utl_allocAndCpy( &g->groups[ g->numGroup++ ], pattern );
    }
    del_Itl( patterns) ;
}

static void
getDebugMask( char *line )
{
    const char *name;
    ItemList *maskNames;
    const char *maskName;
    unsigned mask;

    name = line;
    /* Skip over name and terminate it */
    while ( line[ 0 ] != '\0' && ! isspace( line[ 0 ] ) )
	line++;
    if ( line[ 0 ] == '\0' )
    {
	logSyntaxErr( name );
	return;
    }
    line[ 0 ] = '\0';
    line++;

    mask = LOG_DBG_NONE;
    maskNames = new_Itl( line, " ," );
    for( maskName = Itl_first( maskNames );
	 maskName != NULL;
	 maskName = Itl_next( maskNames ) )
    {
	if ( strcmp( maskName, "all" ) == 0 )
	    mask = LOG_DBG_ALL;
	else if ( strcmp( maskName, "none" ) == 0 )
	    mask = LOG_DBG_NONE;
	else if ( strcmp( maskName, "config" ) == 0 )
	    mask |= LOG_DBG_CONFIG;
	else if ( strcmp( maskName, "control" ) == 0 )
	    mask |= LOG_DBG_CONTROL;
	else if ( strcmp( maskName, "expire" ) == 0 )
	    mask |= LOG_DBG_EXPIRE;
	else if ( strcmp( maskName, "fetch" ) == 0 )
	    mask |= LOG_DBG_FETCH;
	else if ( strcmp( maskName, "filter" ) == 0 )
	    mask |= LOG_DBG_FILTER;
	else if ( strcmp( maskName, "newsbase" ) == 0 )
	    mask |= LOG_DBG_NEWSBASE;
	else if ( strcmp( maskName, "noffle" ) == 0 )
	    mask |= LOG_DBG_NOFFLE;
	else if ( strcmp( maskName, "post" ) == 0 )
	    mask |= LOG_DBG_POST;
	else if ( strcmp( maskName, "protocol" ) == 0 )
	    mask |= LOG_DBG_PROTOCOL;
	else if ( strcmp( maskName, "requests" ) == 0 )
	    mask |= LOG_DBG_REQUESTS;
	else if ( strcmp( maskName, "server" ) == 0 )
	    mask |= LOG_DBG_SERVER;
	else
	    logSyntaxErr( line );
    }
    del_Itl( maskNames) ;
    Log_setDbgMask( mask );
}

static Bool
isValidAutoSubscribeMode( const char *mode )
{
    return strcmp( mode, "full" ) == 0
	|| strcmp( mode, "thread" ) == 0
	|| strcmp( mode, "over" ) == 0
	|| strcmp( mode, "off" ) == 0;
}

static void
getAutoSubscribeMode( const char *line )
{
    Str dummy, pattern, mode;
    AutoSubscribeModeEntry entry;
    int items;

    items = sscanf( line, "%s %s %s", dummy, pattern, mode );
    if ( items == 2 )
    {
	/* Backwards compat. default-auto-subscribe-mode */
	Utl_cpyStr( mode, pattern );
	Utl_toLower( mode );
	if ( ! isValidAutoSubscribeMode( mode ) )
	{
	    logSyntaxErr( line );
	    return;
	}
	strcpy( config.defaultAutoSubscribeMode, mode );
	return;
    }
    else if ( items != 3 )
    {
	logSyntaxErr( line );
	return;
    }

    Utl_toLower( mode );
    if ( ! isValidAutoSubscribeMode( mode ) )
    {
	logSyntaxErr( line );
	return;
    }

    Utl_toLower( pattern );
    Utl_allocAndCpy( &entry.pattern, pattern );
    Utl_allocAndCpy( &entry.mode, mode );

    if ( config.maxAutoSubscribeMode < config.numAutoSubscribeMode + 1 )
    {
	if ( ! ( config.autoSubscribeMode =
		 realloc( config.autoSubscribeMode,
			  ( config.maxAutoSubscribeMode + 5 )
			  * sizeof( AutoSubscribeModeEntry ) ) ) )
	{
	    Log_err( "Could not realloc auto subscribe mode list" );
	    exit( EXIT_FAILURE );
	}
	config.maxAutoSubscribeMode += 5;
    }
    config.autoSubscribeMode[ config.numAutoSubscribeMode++ ] = entry;
}

static const char *
getToken( const char *line, Str value )
{
    Bool isQuoted;
    char quoteChar;
    Bool seenEscape;
    char *maxVal;
    
    while ( *line != '\0' && isspace( *line ) )
	line++;
    if ( *line == '\0' )
	return NULL;

    maxVal = &value[ MAXCHAR ];
    isQuoted = ( *line == '\'' || *line == '"' );
    if ( isQuoted )
    {
	quoteChar = *line;
	line++;

	seenEscape = FALSE;
	while ( *line != '\0'
		&& ( *line != quoteChar || seenEscape )
		&& value < maxVal )
	{
	    if ( seenEscape )
	    {
		*value++ = *line;
		seenEscape = FALSE;
	    }
	    else
	    {
		if ( *line == '\\' )
		    seenEscape = TRUE;
		else
		    *value++ = *line;
	    }
	    line++;
	}

	if ( *line == quoteChar )
	    line++;
    }
    else
    {
	while ( *line != '\0' && ! isspace( *line ) && value < maxVal )
	    *value++ = *line++;
    }
    *value = '\0';
    return line;
}

static void
getFilter( const char *line )
{
    Str ruleBuf, value;
    const char *l;
    char *p, *ruleName;
    Filter *f;
    FilterRule rule;
    Bool seenAction;

    f = new_Filter();
    
    /* Skip "filter" */
    l = Utl_restOfLn( line, 1 );
    seenAction = FALSE;
    
    for(;;)
    {
	while ( *l != '\0' && isspace( *l ) )
	    l++;

	if ( *l == '\0' )
	    break;
	
	/* Get the rule title */
	p = ruleBuf;
	while ( *l != '\0' && *l != '=' && *l != '<' && *l != '>' )
	    *p++ = *l++;
	*p = '\0';
	ruleName = Utl_stripWhiteSpace( ruleBuf );
	Utl_toLower( ruleName );

	if ( *ruleName == '\0' )
	    goto synErr;

	/* Do we know this rule? */
	if ( strcmp( ruleName, "group" ) == 0 )
	    rule.type = RULE_NEWSGROUP;
	else if ( strcmp( ruleName, "subject" ) == 0 )
	    rule.type = RULE_SUBJECT;
	else if ( strcmp( ruleName, "from" ) == 0 )
	    rule.type = RULE_FROM;
	else if ( strcmp( ruleName, "msgid" ) == 0 )
	    rule.type = RULE_MSGID;
	else if ( strcmp( ruleName, "bytes" ) == 0 )
	    rule.type = RULE_BYTES_LT;
	else if ( strcmp( ruleName, "lines" ) == 0 )
	    rule.type = RULE_LINES_LT;
	else if ( strcmp( ruleName, "refs" ) == 0 )
	    rule.type = RULE_NOREFS_LT;
	else if ( strcmp( ruleName, "xposts" ) == 0 )
	    rule.type = RULE_XPOSTS_LT;
	else if ( strcmp( ruleName, "action" ) != 0 )
	    goto synErr;

	if ( rule.type == RULE_BYTES_LT ||
	     rule.type == RULE_LINES_LT ||
	     rule.type == RULE_NOREFS_LT ||
	     rule.type == RULE_XPOSTS_LT )
	{
	    if ( *l == '=' )
		rule.type += 1;
	    else if ( *l == '>' )
		rule.type += 2;
	    else if ( *l != '<' )
		goto synErr;
	}
	else if ( *l != '=' )
	    goto synErr;

	/* Skip past '=' (or '>' or '<') */
	l++;
	
	/* OK, we now have a valid rule. What value? */
	l = getToken( l, value );
	if ( l == NULL )
	    goto synErr;

	if ( strcmp( ruleName, "action" ) == 0 )
	{
	    if ( seenAction )
		goto synErr;
	    
	    Utl_toLower( value );
	    if ( strcmp( value, "full" ) == 0 )
		f->action = FILTER_FULL;
	    else if ( strcmp( value, "over" ) == 0 )
		f->action = FILTER_XOVER;
	    else if ( strcmp( value, "thread" ) == 0 )
		f->action = FILTER_THREAD;
	    else if ( strcmp( value, "discard" ) == 0 )
		f->action = FILTER_DISCARD;
	    else if ( strcmp( value, "default" ) == 0 )
		f->action = FILTER_DEFAULT;
	    seenAction = TRUE;
	}
	else if ( rule.type == RULE_NEWSGROUP )
	    Utl_allocAndCpy( &rule.data.grp, value );
	else if ( rule.type >= RULE_SUBJECT && rule.type <= RULE_MSGID )
	{
	    if ( regcomp( &rule.data.regex, value, REG_EXTENDED ) != 0 )
		goto synErr;
	}
	else
	{
	    char * endVal;
	    int suffix;

	    rule.data.amount = strtoul( value, &endVal, 0 );
	    suffix = tolower( *endVal );
	    if ( suffix == 'k' || suffix == 'm' )
	    {
		rule.data.amount *= 1024;
		if ( suffix == 'm' )
		    rule.data.amount *= 1024;
		endVal++;
	    }
	    if ( *endVal != '\0' && ! isspace( *endVal ) )
		goto synErr;
	}

	if ( strcmp( ruleName, "action" ) != 0 )
	{
	    Log_dbg( LOG_DBG_CONFIG,
		     "Adding rule type %d value %s",
		     rule.type, value );
	    Flt_addRule( f, rule );
	}
    }

    Log_dbg( LOG_DBG_CONFIG, "Adding filter, action %d", f->action );
    Flt_addFilter( f );
    return;
    
synErr:
    logSyntaxErr( line );
    return;
}

void
Cfg_read( void )
{
    char *p;
    FILE *f;
    Str file, line, lowerLine, name, s;

    snprintf( file, MAXCHAR, CONFIGFILE );
    if ( ! ( f = fopen( file, "r" ) ) )
    {
        Log_err( "Cannot read %s", file );
        return;
    }
    while ( fgets( line, MAXCHAR, f ) )
    {
        p = Utl_stripWhiteSpace( line );
	Utl_stripComment( p );
        Utl_cpyStr( lowerLine, p );
        Utl_toLower( lowerLine );
	p = lowerLine;
        if ( *p == '\0' )
            continue;
        if ( sscanf( p, "%s", name ) != 1 )
            Log_err( "Syntax error in %s: %s", file, line );
        else if ( strcmp( "max-fetch", name ) == 0 )
            getInt( &config.maxFetch, 0, INT_MAX, p );
        else if ( strcmp( "auto-unsubscribe-days", name ) == 0 )
            getInt( &config.autoUnsubscribe, -1, INT_MAX, p );
        else if ( strcmp( "thread-follow-time", name ) == 0 )
            getInt( &config.threadFollowTime, 0, INT_MAX, p );
        else if ( strcmp( "connect-timeout", name ) == 0 )
            getInt( &config.connectTimeout, 0, INT_MAX, p );
        else if ( strcmp( "default-expire", name ) == 0 )
            getInt( &config.defaultExpire, 0, INT_MAX, p );
        else if ( strcmp( "auto-subscribe", name ) == 0 )
            getBool( &config.autoSubscribe, p );
        else if ( strcmp( "auto-unsubscribe", name ) == 0 )
            getBool( &config.autoUnsubscribe, p );
        else if ( strcmp( "info-always-unread", name ) == 0 )
            getBool( &config.infoAlways, p );
        else if ( strcmp( "replace-messageid", name ) == 0 )
            getBool( &config.replaceMsgId, p );
        else if ( strcmp( "hostname", name ) == 0 ) 
        /* use line, do not change to lowercase */
            getStr( config.hostnameMsgId, line );
        else if ( strcmp( "post-locally", name ) == 0 )
            getBool( &config.postLocal, p );
        else if ( strcmp( "default-auto-subscribe-mode", name ) == 0 )
        {
            getStr( s, p );
	    if ( ! isValidAutoSubscribeMode( s ) )
	    {
		logSyntaxErr( line );
		return;
	    }
            else
                strcpy( config.defaultAutoSubscribeMode, s );
        }
        else if ( strcmp( "mail-to", name ) == 0 )
            getStr( config.mailTo, p );
        else if ( strcmp( "expire", name ) == 0 )
            getExpire( p );
        else if ( strcmp( "auto-subscribe-mode", name ) == 0 )
            getAutoSubscribeMode( p );
        else if ( strcmp( "log-debug", name ) == 0 )
            getDebugMask( p );
        else if ( strcmp( "getgroups", name ) == 0 )
            getGroups( p, TRUE );
        else if ( strcmp( "omitgroups", name ) == 0 )
            getGroups( p, FALSE );
        else if ( strcmp( "path-header", name ) == 0 )
            getStr( config.pathHeader, p );
        else if ( strcmp( "from-domain", name ) == 0 )
            getStr( config.fromDomain, p );
	/* The following need line because they may have uppercase data */
        else if ( strcmp( "organization", name ) == 0 )
            getText( config.organization, line );
        else if ( strcmp( "server", name ) == 0 )
            getServ( line );
	else if ( strcmp( "filter", name ) == 0 )
	    getFilter( line );
        else
            Log_err( "Unknown config option: %s", name );
    }
    fclose( f );
    if ( ! config.numServ )
    {
        Log_err( "Config file contains no server" );
        exit( EXIT_FAILURE );
    }
}