view src/configfile.c @ 331:ff7a2dc6023e noffle

[svn] * src/client.c,src/group.c: Reunite forbidden group comment with code, and revise forbidden rules to allow single component names except for some specific ones.
author bears
date Wed, 26 Feb 2003 11:30:41 +0000
parents f35a7d45efd5
children b0ee77fa24d4
line wrap: on
line source

/*
  configfile.c

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

  $Id: configfile.c 422 2003-01-11 09:30:05Z bears $
*/

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

#include <ctype.h>
#include <limits.h>
#include <sys/types.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;
    Bool clientAuth;
    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;
    Str noffleUser;
    Str noffleGroup;
} config =
{
    SPOOLDIR, /* spoolDir */
    VERSION,  /* version */
    300,      /* maxFetch */
    30,       /* autoUnsubscribeDays */
    7,        /* threadFollowTime */
    30,       /* connectTimeout */
    FALSE,    /* autoSubscribe */
    FALSE,    /* autoUnsubscribe */
    TRUE,     /* infoAlways */
    FALSE,    /* replaceMsgId */
    "",       /* hostnameMsgId */
    FALSE,    /* postLocal */
    FALSE,    /* clientAuth */
    "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 */
    "news",   /* user Noffle runs as */
    "news"    /* group Noffle runs as */
};

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; }
Bool Cfg_needClientAuth( void ) { return config.clientAuth; }
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; }
const char * Cfg_noffleUser( void ) { return config.noffleUser; }
const char * Cfg_noffleGroup( void ) { return config.noffleGroup; }

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

Bool
Cfg_nextServ( Str name )
{
    if ( config.servIdx >= config.numServ )
        return FALSE;
    Utl_cpyStr( 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 ) )
    {
        Utl_cpyStr( user, config.serv[ idx ].user );
        Utl_cpyStr( 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_fatal( "Malloc of GroupEnum failed." );
    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_fatal( "Malloc of GroupEnum failed." );
    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;

    Utl_cpyStr( lowerLn, line );
    Utl_toLower( lowerLn );
    if ( sscanf( lowerLn, MAXCHAR_FMT " " MAXCHAR_FMT, 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, MAXCHAR_FMT " %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, MAXCHAR_FMT " " MAXCHAR_FMT, 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,
		MAXCHAR_FMT " " MAXCHAR_FMT " " MAXCHAR_FMT " " MAXCHAR_FMT,
                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_fatal( "Could not realloc server list" );
        config.maxServ += 5;
    }
    config.serv[ config.numServ++ ] = entry;
}

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

    if ( sscanf( line, MAXCHAR_FMT " " MAXCHAR_FMT " %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_fatal( "Could not realloc expire list" );
	    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_fatal( "Could not realloc group list" );
	    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 if ( strcmp( maskName, "auth" ) == 0 )
	    mask |= LOG_DBG_AUTH;
	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, MAXCHAR_FMT " " MAXCHAR_FMT " " MAXCHAR_FMT,
		    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;
	}
	Utl_cpyStr( 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_fatal( "Could not realloc auto subscribe mode list" );
	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;
}

/* very simple date parser.
 * examples:
 *      now+
 */
static Bool
get_simpledate( time_t *timeoffsetp, FilterRuleDateEnumType *vartimep, const char *val)
{
    float timef;

    if ( ! strncasecmp( val, "invalid", 7 ) )
    {
        *vartimep = INVALID;
        return TRUE;
    }
    else if ( ! strncasecmp( val, "now", 3 ) ) 
    {
        val += 3;
        *vartimep = NOW;
    }
    else if ( ! strncasecmp( val, "lastupdate", 10 ) )
    {
        val += 10;
        *vartimep = LASTUPDATE;
    }
    else
    {
        *vartimep = FIXED;
        *timeoffsetp = Utl_parseNewsDate( val );
        if ( *timeoffsetp == (time_t) -1 )
            return FALSE;
        else
            return TRUE;
    }
    /* NOW, LASTUPDATE +/- number of days. */
    timef = atof( val ) * 86400.0 ; /* 24 * 60 * 60 == 86400 */

    /* let's assume more than 10 years of timeoffset are a mistake. */
    if ( timef > 31536000.0 || timef < -31536000.0 )
        return FALSE; 
    *timeoffsetp = (time_t) timef;
    /* Todo: check if any garbage follows. */
    return TRUE;
}

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, "reference" ) == 0 )
	    rule.type = RULE_REFERENCE;
	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_EQ;
	else if ( strcmp( ruleName, "lines" ) == 0 )
	    rule.type = RULE_LINES_EQ;
	else if ( strcmp( ruleName, "refs" ) == 0 )
	    rule.type = RULE_NOREFS_EQ;
	else if ( strcmp( ruleName, "xposts" ) == 0 )
	    rule.type = RULE_XPOSTS_EQ;
	else if ( strcmp( ruleName, "post-status" ) == 0 )
	    rule.type = RULE_POST_STATUS;
	else if ( strcmp( ruleName, "date" ) == 0 )
	    rule.type = RULE_DATE_EQ;
        /* date<lastupdate-12 equals older=lastupdate-12
         * date>now+1.5       equals newer=now+1.5
         * date=now           equals older=now+1 AND newer=now-1
         * Stupid people like Mirko keep making mistakes
         * if they're forced using date< or date>.
         */
	else if ( strcmp( ruleName, "older" ) == 0 ) 
	    rule.type = RULE_DATE_LT;
	else if ( strcmp( ruleName, "newer" ) == 0 )
	    rule.type = RULE_DATE_GT;

	else if ( strcmp( ruleName, "action" ) != 0 )
	    goto synErr;

	if ( rule.type == RULE_BYTES_EQ ||
	     rule.type == RULE_LINES_EQ ||
	     rule.type == RULE_NOREFS_EQ ||
	     rule.type == RULE_XPOSTS_EQ || 
	     rule.type == RULE_DATE_EQ )
	{
	    if ( *l == '<' )
		rule.type--;
	    else if ( *l == '>' )
		rule.type++;
	    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 if (rule.type == RULE_POST_STATUS )
        {
	    if ( ( strcmp( value, "yes" ) == 0 ) || \
	         ( strcmp( value, "no" ) == 0 ) || \
	         ( strncmp( value, "mod", 3 ) == 0 ) )
				/* no need to type out "moderated" */
                rule.data.postAllow = value[0]; /* 'y','n' or 'm' */
	    else
		goto synErr;
        }
        else if ( rule.type == RULE_DATE_LT || 
                  rule.type == RULE_DATE_EQ ||
                  rule.type == RULE_DATE_GT )
        {
             if ( !get_simpledate( &rule.data.reftime.timeoffset, &rule.data.reftime.vartime, value ) )
                goto synErr;
             if ( rule.type != RULE_DATE_EQ && 
                  rule.data.reftime.vartime == INVALID )
                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;

    Utl_cpyStr( file, 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, MAXCHAR_FMT, 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 );
#if USE_AUTH
	/*
	 * Don't recognise this unless we have some sort of auth
	 * built in. A small sanity check on the config.
	 */
        else if ( strcmp( "authenticate-client", name ) == 0 )
            getBool( &config.clientAuth, p );
#endif	
        else if ( strcmp( "default-auto-subscribe-mode", name ) == 0 )
        {
            getStr( s, p );
	    if ( ! isValidAutoSubscribeMode( s ) )
	    {
		logSyntaxErr( line );
		return;
	    }
            else
                Utl_cpyStr( 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( "noffle-user", name ) == 0 )
            getText( config.noffleUser, line );
        else if ( strcmp( "noffle-group", name ) == 0 )
            getText( config.noffleUser, 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_fatal( "Config file contains no server" );
}