Mercurial > noffle
view src/configfile.c @ 185:fed1334d766b noffle
[svn] * src/client.c: Change variable only used on constant to 'const'.
* src/filter.c: Add a couple of 'return's after ASSERT() to remove
compiler warnings about functions needing returns.
* NEWS,TODO,configure,configure.in,noffle.conf.example,docs/NOTES,
docs/noffle.conf.5,src/client.c,src/configfile.c,src/content.c,
src/control.c,src/database.c,src/fetch.c,src/fetchlist.c,src/filter.c,
src/group.c,src/lock.c,src/log.c,src/log.h,src/noffle.c,src/outgoing.c,
src/post.c,src/protocol.c,src/request.c,src/server.c,src/util.c:
Debug logging is always compiled and selected via noffle.conf. All debug
logs are classified as all, none, config, control, expire, fetch,
filter, newsbase, noffle, post, protocol, requests and server.
author | bears |
---|---|
date | Sun, 05 Aug 2001 09:24:22 +0100 |
parents | 1c7303c71f66 |
children | 47569cf4ad4a |
line wrap: on
line source
/* configfile.c The following macros must be set, when compiling this file: CONFIGFILE SPOOLDIR VERSION $Id: configfile.c 300 2001-08-05 08:24:22Z 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( 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 ) ); 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; 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( "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 ); } }