view src/configfile.c @ 150:1c7303c71f66 noffle

[svn] * src/protocol.c: Fix bug in Prt_getLn if we should read a line starting with '\0' - according to the leafnode mailing list, this has been seen in the wild. * docs/inews.1,docs/noffle.1,docs/noffle.conf.5, packages/redhat/noffle.spec,src/configfile.h,src/configfile.c, src/noffle.c,src/post.h,src/post.c: Removed use of getopt_long, and added inews mode - the Noffle executable behaves as inews is invoked as inews. This includes adding From: and Organization: headers if necessary - add configs to override defaults for the From: domain and specify the organization. For all my fellow trn-heads out there, and users of any other ageing newsreader that expects inews. Updated RPM spec to create inews link to noffle on install.
author bears
date Thu, 26 Oct 2000 22:21:13 +0100
parents 7dfbb1c20a81
children fed1334d766b
line wrap: on
line source

/*
  configfile.c

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

  $Id: configfile.c 227 2000-10-26 21:21:13Z 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;
    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 */
    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; }
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( "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
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 ) );
    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;
	    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( "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 );
        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 );
    }
}