Mercurial > noffle
view src/configfile.c @ 222:bf290632d29e noffle
[svn] * src/post.c: Always replace message ID in posted message if existing
message ID fails Prt_isValidMsgId.
* noffle.conf.example,docs/noffle.conf.5,src/configfile.c: Change
replace-messageid default from 'yes' to 'no'. These days bad message
IDs from newsreaders shouldn't happen (or should be more easily fixable
from the newsreader config), and replacing message IDs causes havoc if
you're gatewaying mailing lists or similar.
author | bears |
---|---|
date | Sun, 09 Dec 2001 11:32:31 +0000 |
parents | 21200ce10e68 |
children | 0340b9c17edc |
line wrap: on
line source
/* configfile.c The following macros must be set, when compiling this file: CONFIGFILE SPOOLDIR VERSION $Id: configfile.c 341 2001-12-09 11:32:31Z 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 */ FALSE, /* 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, "post-status" ) == 0 ) rule.type = RULE_POST_STATUS; 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 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 { 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 ); } }