view src/configfile.c @ 131:3598fc9581c1 noffle

[svn] Added filtering
author bears
date Wed, 09 Aug 2000 22:30:58 +0100
parents 8897b7e3b108
children 7dfbb1c20a81
line wrap: on
line source

/*
  configfile.c

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

  $Id: configfile.c 189 2000-08-09 21:19:17Z bears $
*/

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

#include "configfile.h"

#include <ctype.h>
#include <limits.h>
#include <regex.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;
    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;
} config =
{
    SPOOLDIR, /* spoolDir */
    VERSION,  /* version */
    300,      /* maxFetch */
    30,       /* autoUnsubscribeDays */
    7,        /* threadFollowTime */
    30,       /* connectTimeout */
    FALSE,    /* autoSubscribe */
    FALSE,    /* autoUnsubscribe */
    TRUE,     /* infoAlways */
    TRUE,     /* replaceMsgId */
    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 */
};

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; }
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; }

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( "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( "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
getServ( const char *line )
{
    Str dummy, name, user, pass;
    int r, len;
    ServEntry entry;

    memset( &entry, 0, sizeof( entry ) );
    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 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;
	    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;

	    rule.data.amount = strtoul( value, &endVal, 0 );
	    if ( *endVal != '\0' && ! isspace( *endVal ) )
		goto synErr;
	}

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

    Log_dbg( "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( "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( "getgroups", name ) == 0 )
            getGroups( p, TRUE );
        else if ( strcmp( "omitgroups", name ) == 0 )
            getGroups( p, FALSE );
	/* The following need line because they may have uppercase data */
        else if ( strcmp( "server", name ) == 0 )
            getServ( line );
        else if ( strcmp( "path-header", name ) == 0 )
            getStr( config.pathHeader, p );
	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 );
    }
}