Mercurial > noffle
changeset 38:8e972daaeab9 noffle
[svn] Applied patch from Jim Hague:
- Forget cached group info when group database closed.
- Added list of 'forbidden' newsgroup specs.
- Fixed problem with article numbering if the overview file empties.
- Changed %i to %d in sscanfs (%i interprets leading zeros as octal numbers)
- New groups now always start numbering at article 1.
- Record newsgroup posting status. Enforce it at posting time.
Added --modify
- Added group deletion.
- Added wildmat code taken from INN
author | enz |
---|---|
date | Fri, 05 May 2000 08:23:15 +0100 |
parents | 792eb10e936d |
children | 2c9ea1ffcc88 |
files | CHANGELOG.html INSTALL.html Makefile README.html client.c config.c config.h content.c database.c database.h group.c group.h itemlist.c noffle.1 noffle.c noffle.conf.5 noffle.conf.example pseudo.c server.c util.c util.h wildmat.c wildmat.h |
diffstat | 23 files changed, 1154 insertions(+), 279 deletions(-) [+] |
line wrap: on
line diff
--- a/CHANGELOG.html Thu May 04 09:16:09 2000 +0100 +++ b/CHANGELOG.html Fri May 05 08:23:15 2000 +0100 @@ -12,12 +12,59 @@ <hr> -<h2>Current developer version</h2> +<h2>Version 1.opre6pre</h2> <ul> <li> -Noffle now sends a "MODE READER" command after connecting to the remote -server. INN needs this before it will permit POST. +Forget cached group info when group database closed. +<li> +Added list of 'forbidden' newsgroup specs., as defined in draft IETF +Newsgroup Format (C.Lindsey), tracked to replace RFC1036. This defines +newsgroup names that should only be used for server-local groups and +server pseudo-groups (e.g. INN's to.*, cancel, cancel.*, junk). These +are now intercepted when querying server groups and ignored. Group names +omitted are any single component names, any 'control.*', 'to' or +'to.*',and any with a component 'all' or 'ctl'. +Note these restrictions do not apply to local group names. +<li> +Fixed problem with article numbering if the overview file empties, +e.g. due to all articles in a very low volume group expiring. This +would cause article numbers to be set back to 1 when a new article +arrives. +<li> +Changed %i to %d in sscanfs everywhere. INN often (as it is entitled to +do) has leading zeros on numbers. %i interprets these as octal +numbers. Also changed %i to %d in printfs, for no good reason. +<li> +New groups now always start numbering at article 1. Previously article +numbering would start with the first held remote article number, in an +attempt to avoid newsreaders noticing if noffle is deleted and +reinstalled. Given Noffle may well not collect the first held article +anyway - it only will if the default number of articles to retrieve on +a first connect is big enough - and the fact that Noffle's pseudo +articles make it impossible to keep local article numbers in lock-step +with the server, there is the chance this scheme would just cause +readers to miss new articles. +<li> +Record newsgroup posting status. Enforce it at posting time. +Added --modify to change newsgroup descriptions for all groups and +posting status for local groups. +<li> +Added group deletion. +<li> +Added message cancellation - from command line or by control message. +Note command line only cancels locally - it can't be used to cancel a +message that has already gone offsite. A control messages cancels +locally if possible; it is only propaged offsite if the target is in a +non-local group and has itself already gone offsite. +<li> +Added wildmat code taken from INN - ensure Noffle wildcarding is +exactly to spec. +<li> +Added group-specific expire times. +<li> +Noffle now sends a "MODE READER" command after connecting to the +remote server. INN needs this before it will permit POST. <li> Applied patch from Jim Hague: support for local groups / new command line options --create and --cancel.
--- a/INSTALL.html Thu May 04 09:16:09 2000 +0100 +++ b/INSTALL.html Fri May 05 08:23:15 2000 +0100 @@ -131,10 +131,11 @@ Add a line for running noffle to the crontab of news (by running 'crontab -u news -e' as root): <pre> - 0 19 * * 1 /usr/local/bin/noffle --expire 14 + 0 19 * * 1 /usr/local/bin/noffle --expire </pre> (if you want to run 'noffle' on Monday (1st day of week) at -19.00 and delete all articles not accessed within the last 14 days). +19.00 and delete all articles not accessed recently. The default +expiry period is 14 days, but this can be changed in /etc/noffle.conf. <p> </ul>
--- a/Makefile Thu May 04 09:16:09 2000 +0100 +++ b/Makefile Fri May 05 08:23:15 2000 +0100 @@ -2,7 +2,7 @@ # # Makefile for Noffle news server # -# $Id: Makefile 37 2000-04-30 14:22:12Z enz $ +# $Id: Makefile 44 2000-05-05 07:23:15Z enz $ # ############################################################################### @@ -23,12 +23,12 @@ FILESH = client.h common.h config.h content.h control.h database.h \ dynamicstring.h fetch.h fetchlist.h group.h itemlist.h lock.h log.h \ online.h outgoing.h over.h post.h protocol.h pseudo.h request.h \ - server.h util.h + server.h util.h wildmat.h FILESC = fetch.c client.c config.c content.c control.c database.c \ dynamicstring.c fetchlist.c group.c itemlist.c lock.c log.c noffle.c \ online.c outgoing.c over.c post.c protocol.c pseudo.c request.c \ - server.c util.c + server.c util.c wildmat.c OBJS = $(patsubst %.c,%.o,$(FILESC)) @@ -65,17 +65,17 @@ install -o news -g news -d $(RPM_BUILD_ROOT)$(SPOOLDIR)/outgoing install -o news -g news -d $(RPM_BUILD_ROOT)$(SPOOLDIR)/overview install -o 0 -g 0 -d $(RPM_BUILD_ROOT)$(DOCDIR) - install -m 0644 -o 0 -g 0 README.txt $(RPM_BUILD_ROOT)$(DOCDIR) - install -m 0644 -o 0 -g 0 NOTES.txt $(RPM_BUILD_ROOT)$(DOCDIR) - install -m 0644 -o 0 -g 0 INSTALL.txt $(RPM_BUILD_ROOT)$(DOCDIR) - install -m 0644 -o 0 -g 0 CHANGELOG.txt $(RPM_BUILD_ROOT)$(DOCDIR) - install -m 0644 -o 0 -g 0 FAQ.txt $(RPM_BUILD_ROOT)$(DOCDIR) - install -m 0644 -o 0 -g 0 COPYING.txt $(RPM_BUILD_ROOT)$(DOCDIR) + install -m 0644 -o 0 -g 0 README.html $(RPM_BUILD_ROOT)$(DOCDIR) + install -m 0644 -o 0 -g 0 NOTES.html $(RPM_BUILD_ROOT)$(DOCDIR) + install -m 0644 -o 0 -g 0 INSTALL.html $(RPM_BUILD_ROOT)$(DOCDIR) + install -m 0644 -o 0 -g 0 CHANGELOG.html $(RPM_BUILD_ROOT)$(DOCDIR) + install -m 0644 -o 0 -g 0 FAQ.html $(RPM_BUILD_ROOT)$(DOCDIR) + install -m 0644 -o 0 -g 0 COPYING.html $(RPM_BUILD_ROOT)$(DOCDIR) install -m 0644 -o 0 -g 0 noffle.conf.example \ $(RPM_BUILD_ROOT)$(DOCDIR) chown -R news.news $(RPM_BUILD_ROOT)$(SPOOLDIR) @echo - @echo Read INSTALL.txt for further instructions. + @echo Read INSTALL.html for further instructions. tags: ctags -e $(FILESC) $(FILESH)
--- a/README.html Thu May 04 09:16:09 2000 +0100 +++ b/README.html Fri May 05 08:23:15 2000 +0100 @@ -27,6 +27,8 @@ <a href="#documentation">Documentation</a> <li> <a href="#links">Links</a> +<li> +<a href="#acknowledgements">Acknowledgements</a> </ul> <p> @@ -179,6 +181,12 @@ </ul> +<h2><a name="acknowledgements">Acknowledgements</a></h2> + +The <i>wildmat</i> newsgroup pattern matching software used by NOFFLE +was developed by Rich Salz, and is as distributed with INN +v2.2. + <p> <hr> <small><i>
--- a/client.c Thu May 04 09:16:09 2000 +0100 +++ b/client.c Fri May 05 08:23:15 2000 +0100 @@ -1,7 +1,7 @@ /* client.c - $Id: client.c 38 2000-04-30 19:07:54Z enz $ + $Id: client.c 44 2000-05-05 07:23:15Z enz $ */ #include "client.h" @@ -24,6 +24,33 @@ #include "pseudo.h" #include "request.h" #include "util.h" +#include "wildmat.h" + +/* + Some newsgroups names are reserved for server-specific or server + pseudo groups. We don't want to fetch them. For example, INN + keeps all its control messages in a 'control' hierarchy, and + used the "to." heirarchy for dark and mysterious purposes I think + are to do with newsfeeds. The recommended restrictions are documented + in C.Lindsay, "News Article Format", <draft-ietf-usefor-article-03.txt>. +*/ + +struct ForbiddenGroupName +{ + const char *pattern; + Bool match; +} forbiddenGroupNames[] = +{ + { "*.*", FALSE }, /* Single component */ + { "control.*", TRUE }, /* control.* groups */ + { "to.*", TRUE }, /* control.* groups */ + { "*.all", TRUE }, /* 'all' as a component */ + { "*.all.*", TRUE }, + { "all.*", TRUE }, + { "*.ctl", TRUE }, /* 'ctl' as a component */ + { "*.ctl.*", TRUE }, + { "ctl.*", TRUE } +}; struct { @@ -290,6 +317,8 @@ if ( ! ( client.out = fdopen( sock, "w" ) ) || ! ( client.in = fdopen( dup( sock ), "r" ) ) ) { + if ( client.out != NULL ) + fclose( client.out ); close( sock ); break; } @@ -312,11 +341,33 @@ Log_err( "Bad server stat %d", stat ); } shutdown( fileno( client.out ), 0 ); + fclose( client.in ); + fclose( client.out ); + close( sock ); } } return FALSE; } +static Bool +isForbiddenGroupName( const char *name ) +{ + int i; + + for ( i = 0; + i < sizeof( forbiddenGroupNames ) / + sizeof( struct ForbiddenGroupName ); + i++ ) + { + /* Negate result of Wld_match to ensure it is 1 or 0. */ + if ( forbiddenGroupNames[i].match != + ( ! Wld_match( name, forbiddenGroupNames[i].pattern ) ) ) + return TRUE; + } + + return FALSE; +} + static void processGrps( void ) { @@ -327,24 +378,24 @@ while ( getTxtLn( line, &err ) && ! err ) { - if ( sscanf( line, "%s %i %i %c", + if ( sscanf( line, "%s %d %d %c", grp, &last, &first, &postAllow ) != 4 ) { Log_err( "Unknown reply to LIST or NEWGROUPS: %s", line ); continue; } + if ( isForbiddenGroupName( grp ) ) + { + Log_inf( "Group %s forbidden", grp ); + continue; + } if ( ! Grp_exists( grp ) ) { Log_inf( "Registering new group '%s'", grp ); Grp_create( grp ); - /* Start local numbering with remote first number to avoid - new numbering at the readers if noffle is re-installed */ - if ( first != 0 ) - Grp_setFirstLast( grp, first, first - 1 ); - else - Grp_setFirstLast( grp, 1, 0 ); Grp_setRmtNext( grp, first ); Grp_setServ( grp, client.serv ); + Grp_setPostAllow( grp, postAllow ); } else { @@ -354,6 +405,7 @@ grp, Grp_serv( grp ), client.serv ); Grp_setServ( grp, client.serv ); Grp_setRmtNext( grp, first ); + Grp_setPostAllow( grp, postAllow ); } else Log_dbg( "Group %s is already fetched from %s", @@ -515,7 +567,7 @@ Str t; p = readField( t, line ); - if ( sscanf( t, "%i", numb ) != 1 ) + if ( sscanf( t, "%d", numb ) != 1 ) return FALSE; p = readField( subj, p ); p = readField( from, p ); @@ -550,7 +602,7 @@ if ( strlen( s ) == 0 ) return NULL; pColon = strstr( s, ":" ); - if ( ! pColon || sscanf( pColon + 1, "%i", numb ) != 1 ) + if ( ! pColon || sscanf( pColon + 1, "%d", numb ) != 1 ) { Log_err( "Corrupt Xref at position '%s'", pXref ); return NULL; @@ -623,14 +675,14 @@ { Log_dbg( "Changing first server for '%s' from '%s' to '%s'", msgId, Grp_serv( g ), client.serv ); - snprintf( t, MAXCHAR, "%s:%i %s", + snprintf( t, MAXCHAR, "%s:%d %s", client.grp, Ov_numb( ov ), xref ); Db_setXref( msgId, t ); } else { Log_dbg( "Adding '%s' to Xref of '%s'", g, msgId ); - snprintf( t, MAXCHAR, "%s %s:%i", + snprintf( t, MAXCHAR, "%s %s:%d", xref, client.grp, Ov_numb( ov ) ); Db_setXref( msgId, t ); } @@ -787,7 +839,7 @@ return FALSE; if ( getStat() != STAT_GRP_SELECTED ) return FALSE; - if ( sscanf( client.lastStat, "%u %i %i %i", + if ( sscanf( client.lastStat, "%u %d %d %d", &stat, &estimatedNumb, &first, &last ) != 4 ) { Log_err( "Bad server response to GROUP: %s", client.lastStat );
--- a/config.c Thu May 04 09:16:09 2000 +0100 +++ b/config.c Fri May 05 08:23:15 2000 +0100 @@ -6,7 +6,7 @@ SPOOLDIR VERSION - $Id: config.c 7 2000-01-06 09:30:49Z enz $ + $Id: config.c 44 2000-05-05 07:23:15Z enz $ */ #include "config.h" @@ -23,6 +23,13 @@ } ServEntry; +typedef struct +{ + Str pattern; + int days; +} +ExpireEntry; + struct { /* Compile time options */ @@ -39,10 +46,15 @@ Bool replaceMsgId; Str autoSubscribeMode; Str mailTo; + int defaultExpire; int numServ; int maxServ; ServEntry *serv; int servIdx; /* for server enumeration */ + int numExpire; + int maxExpire; + ExpireEntry *expire; + int expireIdx; } config = { SPOOLDIR, /* spoolDir */ @@ -57,10 +69,15 @@ TRUE, /* replaceMsgId */ "over", /* autoSubscribeMode */ "", /* mailTo */ + 14, /* defaultExpire */ 0, /* numServ */ 0, /* maxServ */ NULL, /* serv */ - 0 /* servIdx */ + 0, /* servIdx */ + 0, /* numExpire */ + 0, /* maxExpire */ + NULL, /* expire */ + 0 /* expireIdx */ }; const char * Cfg_spoolDir( void ) { return config.spoolDir; } @@ -77,6 +94,7 @@ const char * Cfg_autoSubscribeMode( void ) { return config.autoSubscribeMode; } const char * Cfg_mailTo( void ) { return config.mailTo; } +int Cfg_expire( void ) { return config.defaultExpire; } void Cfg_beginServEnum( void ) @@ -149,6 +167,21 @@ } } +void +Cfg_beginExpireEnum( void ) +{ + config.expireIdx = 0; +} + +int +Cfg_nextExpire( Str pattern ) +{ + if ( config.expireIdx >= config.numExpire ) + return -1; + strcpy( pattern, config.expire[ config.expireIdx ].pattern ); + return config.expire[ config.expireIdx++ ].days; +} + static void logSyntaxErr( const char *line ) { @@ -244,6 +277,49 @@ config.serv[ config.numServ++ ] = entry; } +static void +getExpire( const char *line ) +{ + Str dummy; + ExpireEntry entry; + int days; + + /* + The line is either "expire <num>" or "expire <pat> <num>". + The former updates the overall default. + */ + if ( sscanf( line, "%s %s %d", dummy, entry.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( entry.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 exipre list" ); + exit( EXIT_FAILURE ); + } + config.maxExpire += 5; + } + config.expire[ config.numExpire++ ] = entry; + } +} + void Cfg_read( void ) { @@ -259,10 +335,11 @@ } while ( fgets( line, MAXCHAR, f ) ) { - Utl_cpyStr( lowerLine, line ); + p = Utl_stripWhiteSpace( line ); + Utl_stripComment( p ); + Utl_cpyStr( lowerLine, p ); Utl_toLower( lowerLine ); - p = Utl_stripWhiteSpace( lowerLine ); - if ( *p == '#' || *p == '\0' ) + if ( *p == '\0' ) continue; if ( sscanf( p, "%s", name ) != 1 ) Log_err( "Syntax error in %s: %s", file, line ); @@ -274,6 +351,8 @@ 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 ) @@ -303,6 +382,8 @@ getServ( line ); else if ( strcmp( "mail-to", name ) == 0 ) getStr( config.mailTo, p ); + else if ( strcmp( "expire", name ) == 0 ) + getExpire( p ); else Log_err( "Unknown config option: %s", name ); }
--- a/config.h Thu May 04 09:16:09 2000 +0100 +++ b/config.h Fri May 05 08:23:15 2000 +0100 @@ -3,7 +3,7 @@ Common declarations and handling of the configuration file. - $Id: config.h 3 2000-01-04 11:35:42Z enz $ + $Id: config.h 44 2000-05-05 07:23:15Z enz $ */ #ifndef CONFIG_H @@ -38,6 +38,16 @@ Bool Cfg_servIsPreferential( const char *name1, const char *name2 ); void Cfg_authInfo( const char *name, Str user, Str pass ); +/* Begin iteration through expire entries. */ +void Cfg_beginExpireEnum( void ); + +/* Put next expire pattern in "pattern" and return its days count. + Return -1 if no more expire patterns. */ +int Cfg_nextExpire( Str pattern ); + +/* Return default expire days. */ +int Cfg_expire( void ); + void Cfg_read( void ); #endif
--- a/content.c Thu May 04 09:16:09 2000 +0100 +++ b/content.c Fri May 05 08:23:15 2000 +0100 @@ -1,7 +1,7 @@ /* content.c - $Id: content.c 6 2000-01-04 13:49:50Z enz $ + $Id: content.c 44 2000-05-05 07:23:15Z enz $ */ #include <dirent.h> @@ -11,6 +11,7 @@ #include <unistd.h> #include "common.h" #include "config.h" +#include "group.h" #include "log.h" #include "over.h" #include "pseudo.h" @@ -20,8 +21,9 @@ { DIR *dir; /* Directory for browsing through all groups */ - int first; - int last; + int vecFirst; /* First article number in vector */ + int first; /* First live article number */ + int last; /* Last article number */ unsigned int size; /* Number of overviews. */ unsigned int max; /* Size of elem. */ Over **elem; /* Ptr to array with ptrs to overviews. @@ -29,7 +31,7 @@ in group. */ Str name; Str file; -} cont = { NULL, 1, 0, 0, 0, NULL, "", "" }; +} cont = { NULL, 1, 1, 0, 0, 0, NULL, "", "" }; void Cont_app( Over *ov ) @@ -45,19 +47,18 @@ } cont.max += 500; } - if ( cont.first == 0 ) - cont.first = 1; + ASSERT( cont.vecFirst > 0 ); if ( ov ) - Ov_setNumb( ov, cont.first + cont.size ); + Ov_setNumb( ov, cont.vecFirst + cont.size ); cont.elem[ cont.size++ ] = ov; - cont.last = cont.first + cont.size - 1; + cont.last = cont.vecFirst + cont.size - 1; } Bool Cont_validNumb( int n ) { return ( n != 0 && n >= cont.first && n <= cont.last - && cont.elem[ n - cont.first ] ); + && cont.elem[ n - cont.vecFirst ] ); } void @@ -67,7 +68,7 @@ if ( ! Cont_validNumb( n ) ) return; - ov = &cont.elem[ n - cont.first ]; + ov = &cont.elem[ n - cont.vecFirst ]; free( *ov ); *ov = NULL; } @@ -83,6 +84,14 @@ cont.size = 0; } +static void +setupEmpty( const char *name ) +{ + cont.last = Grp_last( name ); + cont.first = cont.vecFirst = cont.last + 1; + ASSERT( cont.first > 0 ); +} + /* Extend content list to size "cnt" and append NULL entries. */ static void extendCont( int cnt ) @@ -108,6 +117,7 @@ Str line; /* Delete old overviews and make room for new ones. */ + cont.vecFirst = 0; cont.first = 0; cont.last = 0; Utl_cpyStr( cont.name, name ); @@ -120,6 +130,7 @@ if ( ! f ) { Log_dbg( "No group overview file: %s", cont.file ); + setupEmpty( name ); return; } Log_dbg( "Reading %s", cont.file ); @@ -137,25 +148,31 @@ continue; } if ( cont.first == 0 ) - cont.first = numb; + cont.first = cont.vecFirst = numb; cont.last = numb; extendCont( numb - cont.first + 1 ); cont.elem[ numb - cont.first ] = ov; } fclose( f ); + + if ( cont.first == 0 ) + setupEmpty( name ); /* Corrupt overview file recovery */ } void Cont_write( void ) { Bool anythingWritten; - int i, first; + int i; FILE *f; const Over *ov; - first = cont.first; - while ( ! Cont_validNumb( first ) && first <= cont.last ) - ++first; + + /* Move the first article no. to the first active article */ + while ( ! Cont_validNumb( cont.first ) && cont.first <= cont.last ) + ++cont.first; + + /* Save the overview */ if ( ! ( f = fopen( cont.file, "w" ) ) ) { Log_err( "Could not open %s for writing", cont.file ); @@ -180,8 +197,16 @@ } } fclose( f ); + + /* + If empty, remove the overview file and set set first to one + beyond last to flag said emptiness. + */ if ( ! anythingWritten ) - unlink( cont.file ); + { + unlink( cont.file ); + cont.first = cont.last + 1; + } } const Over * @@ -189,7 +214,7 @@ { if ( ! Cont_validNumb( numb ) ) return NULL; - return cont.elem[ numb - cont.first ]; + return cont.elem[ numb - cont.vecFirst ]; } int
--- a/database.c Thu May 04 09:16:09 2000 +0100 +++ b/database.c Fri May 05 08:23:15 2000 +0100 @@ -1,7 +1,7 @@ /* database.c - $Id: database.c 39 2000-05-01 09:22:42Z enz $ + $Id: database.c 44 2000-05-05 07:23:15Z enz $ Uses GNU gdbm library. Using Berkeley db (included in libc6) was cumbersome. It is based on Berkeley db 1.85, which has severe bugs @@ -18,9 +18,11 @@ #include <sys/types.h> #include <sys/stat.h> #include "config.h" +#include "itemlist.h" #include "log.h" #include "protocol.h" #include "util.h" +#include "wildmat.h" static struct Db { @@ -561,11 +563,49 @@ return ( cursor.dptr != NULL ); } -Bool -Db_expire( unsigned int days ) +static int +calcExpireDays( const char *msgId ) { - double limit; - int cntDel, cntLeft, flags; + const char *xref; + ItemList *refs; + const char *ref; + int res; + + xref = Db_xref( msgId ); + if ( xref[ 0 ] == '\0' ) + return -1; + + res = -1; + refs = new_Itl( xref, " :" ); + for ( ref = Itl_first( refs ); ref != NULL; ref = Itl_next( refs ) ) + { + Str pattern; + int days; + + Cfg_beginExpireEnum(); + while ( ( days = Cfg_nextExpire( pattern ) ) != -1 ) + if ( Wld_match( ref, pattern ) + && ( ( days > res && res != 0 ) || + days == 0 ) ) + { + res = days; + Log_dbg ( "Custom expiry %d for %s in group %s", + days, msgId, ref ); + break; + } + + Itl_next( refs ); /* Throw away group number */ + } + + if ( res == -1 ) + res = Cfg_expire(); + return res; +} + +Bool +Db_expire( void ) +{ + int cntDel, cntLeft, flags, expDays; time_t nowTime, lastAccess; const char *msgId; Str name, tmpName; @@ -583,22 +623,35 @@ Db_close(); return FALSE; } - Log_inf( "Expiring articles that have not been accessed for %u days", - days ); - limit = days * 24. * 3600.; + Log_inf( "Expiring articles" ); cntDel = 0; cntLeft = 0; nowTime = time( NULL ); if ( Db_first( &msgId ) ) do { + expDays = calcExpireDays( msgId ); lastAccess = Db_lastAccess( msgId ); - if ( lastAccess == -1 ) + if ( expDays == -1 ) + Log_err( "Internal error: Failed expiry calculation on %s", + msgId ); + else if ( lastAccess == -1 ) Log_err( "Internal error: Getting lastAccess of %s failed", msgId ); - else if ( difftime( nowTime, lastAccess ) > limit ) + else if ( expDays > 0 + && difftime( nowTime, lastAccess ) > + ( (double) expDays * 24 * 3600 ) ) { - Log_dbg( "Expiring %s", msgId ); +#ifdef DEBUG + Str last, now; + + Utl_cpyStr( last, ctime( &lastAccess ) ); + last[ strlen( last ) - 1 ] = '\0'; + Utl_cpyStr( now, ctime( &nowTime ) ); + last[ strlen( now ) - 1 ] = '\0'; + Log_dbg( "Expiring %s: last access %s, time now %s", + msgId, last, now ); +#endif ++cntDel; } else
--- a/database.h Thu May 04 09:16:09 2000 +0100 +++ b/database.h Fri May 05 08:23:15 2000 +0100 @@ -3,7 +3,7 @@ Article database. - $Id: database.h 39 2000-05-01 09:22:42Z enz $ + $Id: database.h 44 2000-05-05 07:23:15Z enz $ */ #ifndef DB_H @@ -89,8 +89,11 @@ Bool Db_next( const char** msgId ); -/* Expire all articles that have not been accessed for <days> */ +/* + Expire all articles that have not been accessed for a number of + days determined by their group membership and noffle configuration. + */ Bool -Db_expire( unsigned int days ); +Db_expire( void ); #endif
--- a/group.c Thu May 04 09:16:09 2000 +0100 +++ b/group.c Fri May 05 08:23:15 2000 +0100 @@ -7,7 +7,7 @@ loadGrp() and saveGrp(). This is done transparently. Access to the groups database is done by group name, by the functions defined in group.h. - $Id: group.c 33 2000-04-29 14:49:13Z enz $ + $Id: group.c 44 2000-05-05 07:23:15Z enz $ */ #include "group.h" @@ -22,22 +22,31 @@ /* currently only used within grp */ typedef struct { - int first; /* number of first article within group */ - int last; /* number of last article within group */ - int rmtNext; - time_t created; - time_t lastAccess; + int first; /* number of first article within group */ + int last; /* number of last article within group */ + int rmtNext; + time_t created; + time_t lastAccess; } Entry; struct { - Str name; /* name of the group */ - Entry entry; /* more information about this group */ - Str serv; /* server the group resides on */ - Str dsc; /* description of the group */ - GDBM_FILE dbf; + Str name; /* name of the group */ + Entry entry; /* more information about this group */ + Str serv; /* server the group resides on */ + Str dsc; /* description of the group */ + char postAllow; /* Posting status */ + GDBM_FILE dbf; +} grp = { "(no grp)", { 0, 0, 0, 0, 0 }, "", "", ' ', NULL }; -} grp = { "(no grp)", { 0, 0, 0, 0, 0 }, "", "", NULL }; +/* + Note: postAllow should really go in Entry. But changing Entry would + make backwards group file format capability tricky, so it goes + where it is, and we test the length of the retrieved record to + determine if it exists. + + Someday if we really change the record format this should be tidied up. + */ static const char * errMsg( void ) @@ -72,6 +81,7 @@ Log_dbg( "Closing groupinfo" ); gdbm_close( grp.dbf ); grp.dbf = NULL; + Utl_cpyStr( grp.name, "" ); } /* Load group info from gdbm-database into global struct grp */ @@ -94,6 +104,11 @@ Utl_cpyStr( grp.serv, p ); p += strlen( p ) + 1; Utl_cpyStr( grp.dsc, p ); + p += strlen( p) + 1; + if ( p - val.dptr < val.dsize ) + grp.postAllow = p[ 0 ]; + else + grp.postAllow = 'y'; Utl_cpyStr( grp.name, name ); free( val.dptr ); return TRUE; @@ -111,13 +126,15 @@ ASSERT( grp.dbf ); lenServ = strlen( grp.serv ); lenDsc = strlen( grp.dsc ); - bufLen = sizeof( grp.entry ) + lenServ + lenDsc + 2; + bufLen = sizeof( grp.entry ) + lenServ + lenDsc + 2 + sizeof( char ); buf = malloc( bufLen ); memcpy( buf, (void *)&grp.entry, sizeof( grp.entry ) ); p = (char *)buf + sizeof( grp.entry ); strcpy( p, grp.serv ); p += lenServ + 1; strcpy( p, grp.dsc ); + p += lenDsc + 1; + p[ 0 ] = grp.postAllow; key.dptr = (void *)grp.name; key.dsize = strlen( grp.name ) + 1; val.dptr = buf; @@ -152,14 +169,26 @@ Utl_cpyStr( grp.name, name ); Utl_cpyStr( grp.serv, "(unknown)" ); grp.dsc[ 0 ] = '\0'; - grp.entry.first = 0; + grp.entry.first = 1; grp.entry.last = 0; grp.entry.rmtNext = 0; grp.entry.created = 0; grp.entry.lastAccess = 0; + grp.postAllow = 'y'; saveGrp(); } +void +Grp_delete( const char *name ) +{ + datum key; + + ASSERT( grp.dbf ); + key.dptr = (void*)name; + key.dsize = strlen( name ) + 1; + gdbm_delete( grp.dbf, key ); +} + const char * Grp_dsc( const char *name ) { @@ -175,7 +204,7 @@ if ( ! loadGrp( name ) ) return "[unknown grp]"; - if ( Grp_local( name ) || Cfg_servListContains( grp.serv ) ) + if ( Cfg_servListContains( grp.serv ) ) Utl_cpyStr( serv, grp.serv ); else snprintf( serv, MAXCHAR, "[%s]", grp.serv ); @@ -222,6 +251,15 @@ return grp.entry.created; } +char +Grp_postAllow( const char *name ) +{ + if ( ! loadGrp( name ) ) + return 0; + return grp.postAllow; +} + + /* Replace group's description (only if value != ""). */ void Grp_setDsc( const char *name, const char *value ) @@ -280,6 +318,16 @@ } void +Grp_setPostAllow( const char *name, char postAllow ) +{ + if ( loadGrp( name ) ) + { + grp.postAllow = postAllow; + saveGrp(); + } +} + +void Grp_setFirstLast( const char *name, int first, int last ) { if ( loadGrp( name ) )
--- a/group.h Thu May 04 09:16:09 2000 +0100 +++ b/group.h Fri May 05 08:23:15 2000 +0100 @@ -3,7 +3,7 @@ Groups database - $Id: group.h 32 2000-04-29 14:45:56Z enz $ + $Id: group.h 44 2000-05-05 07:23:15Z enz $ */ #ifndef GRP_H @@ -32,6 +32,10 @@ void Grp_create( const char *name ); +/* delete a group and its articles from the database. */ +void +Grp_delete( const char *name ); + /* Get group description */ const char * Grp_dsc( const char *name ); @@ -65,6 +69,9 @@ time_t Grp_created( const char *name ); +char +Grp_postAllow( const char *name ); + /* Replace group's description (only if value != ""). */ void Grp_setDsc( const char *name, const char *value ); @@ -87,6 +94,9 @@ void Grp_setFirstLast( const char *name, int first, int last ); +void +Grp_setPostAllow( const char *name, char postAllow ); + /* Begin iterating trough the names of all groups. Store name of first group (or NULL if there aren't any) in name. Returns whether there are any groups. */
--- a/itemlist.c Thu May 04 09:16:09 2000 +0100 +++ b/itemlist.c Fri May 05 08:23:15 2000 +0100 @@ -1,7 +1,7 @@ /* itemlist.c - $Id: itemlist.c 32 2000-04-29 14:45:56Z enz $ + $Id: itemlist.c 44 2000-05-05 07:23:15Z enz $ */ #include "itemlist.h" @@ -34,7 +34,7 @@ exit( EXIT_FAILURE ); } - res->list = (char *) malloc( strlen(list) + 2 ); + res->list = (char *) malloc ( strlen(list) + 2 ); if ( res->list == NULL ) { Log_err( "Malloc of ItemList.list failed." ); @@ -90,7 +90,7 @@ /* Get first item. */ const char * -Itl_first( ItemList *self ) +Itl_first( ItemList *self) { self->next = self->list; return Itl_next( self );
--- a/noffle.1 Thu May 04 09:16:09 2000 +0100 +++ b/noffle.1 Fri May 05 08:23:15 2000 +0100 @@ -1,5 +1,5 @@ .TH noffle 1 -.\" $Id: noffle.1 32 2000-04-29 14:45:56Z enz $ +.\" $Id: noffle.1 44 2000-05-05 07:23:15Z enz $ .SH NAME noffle \- Usenet package optimized for dialup connections. @@ -18,7 +18,10 @@ \-d | \-\-database .br .B noffle -\-e | \-\-expire <days> +\-D | \-\-delete <newsgroup name> +.br +.B noffle +\-e | \-\-expire .br .B noffle \-f | \-\-fetch @@ -33,6 +36,12 @@ \-l | \-\-list .br .B noffle +\-m | \-\-modify desc <newsgroup name> <group description> +.br +.B noffle +\-m | \-\-modify post <local newsgroup name> (y|n) +.br +.B noffle \-n | \-\-online .br .B noffle @@ -104,8 +113,8 @@ .TP .B \-a, \-\-article <message id>|all Write article <message id> to standard output. Message Id must contain -the leading '<' and trailing '>' (quote the argument with single quotes to -avoid shell interpretation of characters like '<' and '>' and '$'). +the leading '<' and trailing '>' (quote the argument to avoid shell +interpretation of '<' and '>'). .br If "all" is given as message Id, all articles are shown. @@ -127,10 +136,21 @@ Write the complete content of the article database to standard output. .TP -.B \-e, \-\-expire <days> -Delete all articles older than <days> days from the database. +.B \-D, \-\-delete <newsgroup name> +Delete the newsgroup with the given name. All articles that only +belong to the group are deleted as well. + +.TP +.B \-e, \-\-expire +Delete all articles that have not been accessed recently from the +database. Should be run regularily from .BR crond (8). +.TP +The default expire period is 14 days. This can be changed and +custom expiry periods set for individual newsgroups or sets of +newsgroups in +.B /etc/noffle.conf. .TP .B \-f, \-\-fetch @@ -154,7 +174,7 @@ .br Format (fields separated by tabs): .br -<name> <server> <first> <last> <remote next> <created> <last access> <desc> +<name> <server> <first> <last> <remote next> <post allowed> <created> <last access> <desc> .TP .B \-h, \-\-help @@ -167,6 +187,16 @@ Format: <groupname> <server> full|thread|over .TP +.B \-m | \-\-modify desc <newsgroup name> <group description> +Modify the description of the named newsgroup. + +.TP +.B \-m | \-\-modify post <local newsgroup name> <permission> +Modify the posting permission on a local newsgroup. <permission> must +be either 'y' (yes, posting allowed) or 'n' (no, posting not allowed). +Attempts to post to a newsgroup with posting disabled will be rejected. + +.TP .B \-n, \-\-online Put .B NOFFLE @@ -249,93 +279,17 @@ .SH FILES -There exists a spool directory (default -.I /var/spool/noffle), -and a config file (default -.I /etc/noffle.conf). - -.PP +.B NOFFLE +takes its configuration from a configuration file, by default +.I /etc/noffle.conf. +For a description of this file, see +.BR noffle.conf (5). +. -.TP -.B <config file> -Configuration file. Comment lines begin with -.I #. -Definition lines may contain: -.br -.B server <hostname>[:<port>] [<user> <pass>] -Name of the remote server. If no port is given, port 119 is used. -Username and password for servers that need authentication -(Original AUTHINFO). The password may not contain white-spaces. -If there are multiple server entries in the config file, all of them are -used for getting groups. In this case the first server should be -the one of your main provider. Note that you must always run -"noffle --query groups" after making changes to the server entries. -.br -.B max-fetch <n> -Never get more than <n> articles. If there are more, the oldest ones -are discarded. -.br -Default: 300 -.br -.B mail-to <address> -Receiver of failed postings. If empty then failed postings are returned -to the sender (taking the address from the article's Sender, X-Sender or -From field, in this order). -.br -Default: <empty string> -.br -.B auto-unsubscribe yes|no -Automatically remove groups from fetch list if they have not been -accessed for a number of days. -.br -Default: no -.br -.B auto-unsubscribe-days <n> -Number of days used for auto-unsubscribe option. -.br -Default: 30 -.br -.B thread-follow-time <n> -Automatically mark articles for download in thread mode, if they -are referencing an article that has been opened by a reader within the last -<n> days. -.br -Default: 7 -.br -.B connect-timeout <n> -Timeout for connecting to remote server in seconds. -.br -Default: 30 -.br -.B auto-subscribe yes|no -Automatically put groups on fetch list if someone reads them. -<mode> can be full, over, thread (depending on the fetch mode) or -off (do not subscribe automatically). Condition for putting a group -on the list is that an article is opened. For this reason there is -always a pseudo article visible in groups that are not on the fetch list. -.br -Default: no -.br -.B auto-subscribe-mode full|thread|over -Mode for auto-subscribe option. -.br -Default: over -.br -.B remove-messageid yes|no -Remove Message-ID from posted articles. Some remote servers can generate -Message-IDs. -.br -Default: no -.br -.B replace-messageid yes|no -Replace Message-ID of posted articles by a Message-ID generated by -NOFFLE. Some news readers generate Message-IDs that are not accepted by -some servers. For generating Message-IDs, the domain name of your system should -be a valid domain name. If you are in a local domain, set it to your -provider's domain name. -.br -Default: yes -.br +.B NOFFLE +keeps all its data files in a spool directory. +.I /var/spool/noffle +is the default location. .TP .B <spool dir>/fetchlist @@ -373,9 +327,10 @@ .SH SEE ALSO -.BR crond (8) +.BR noffle.conf (5), +.BR crond (8), .BR inetd (8), -.BR pppd (8), +.BR pppd (8) .br .B RFC 977, .B RFC 1036,
--- a/noffle.c Thu May 04 09:16:09 2000 +0100 +++ b/noffle.c Fri May 05 08:23:15 2000 +0100 @@ -10,7 +10,7 @@ received for some seconds (to allow multiple clients connect at the same time). - $Id: noffle.c 40 2000-05-01 09:23:31Z enz $ + $Id: noffle.c 44 2000-05-05 07:23:15Z enz $ */ #include <ctype.h> @@ -224,7 +224,7 @@ else ++cntLeft; } - if ( !Grp_local( grp ) + if ( ! Grp_local( grp ) && autoUnsubscribe && difftime( now, Grp_lastAccess( grp ) ) > maxAge ) { @@ -246,10 +246,10 @@ } static void -doExpire( unsigned int days ) +doExpire( void ) { Db_close(); - Db_expire( days ); + Db_expire(); if ( ! Db_open() ) return; expireContents(); @@ -258,15 +258,70 @@ static void doCreateLocalGroup( const char * name ) { + Str grp; + + Utl_cpyStr( grp, name ); + Utl_toLower( grp ); + name = Utl_stripWhiteSpace( grp ); + if ( Grp_exists( name ) ) fprintf( stderr, "'%s' already exists.\n", name ); else { Log_inf( "Creating new local group '%s'", name ); Grp_create( name ); - Grp_setFirstLast( name, 1, 0 ); Grp_setLocal( name ); - printf( "New local group '%s' created.\n", name ); + printf( "New local group '%s' created.\n", name ); + } +} + +static void +doDeleteLocalGroup( const char * name ) +{ + Str grp; + + Utl_cpyStr( grp, name ); + Utl_toLower( grp ); + name = Utl_stripWhiteSpace( grp ); + + if ( ! Grp_exists( name ) ) + fprintf( stderr, "'%s' does not exist.\n", name ); + else + { + int i; + + Log_inf( "Deleting group '%s'", name ); + + /* + Delete all articles that are only in the group. Check the + article Xref for more than one group. + */ + Cont_read( name ); + for ( i = Cont_first(); i <= Cont_last(); i++ ) + { + const Over *over; + Bool toDelete; + Str msgId; + + over = Cont_get( i ); + toDelete = TRUE; + if ( over != NULL ) + { + ItemList * xref; + + Utl_cpyStr( msgId, Ov_msgId( over ) ); + xref = new_Itl( Db_xref( msgId ), " " ); + if ( Itl_count( xref ) > 1 ) + toDelete = FALSE; + del_Itl( xref ); + } + Cont_delete( i ); + if ( toDelete ) + Db_delete( msgId ); + } + Cont_write(); + Grp_delete( name ); + printf( "Group '%s' deleted.\n", name ); } } @@ -298,6 +353,65 @@ } } +/* A modify command. argc/argv start AFTER '-m'. */ +static Bool +doModify( const char *cmd, int argc, char **argv ) +{ + const char *grp; + + if ( argc < 2 ) + { + fprintf( stderr, "Insufficient arguments to -m\n" ); + return FALSE; + + } + else if ( strcmp( cmd, "desc" ) != 0 + && strcmp( cmd, "post" ) != 0 ) + { + fprintf( stderr, "Unknown argument -m %s\n", optarg ); + return FALSE; + } + + grp = argv[ 0 ]; + argv++; + argc--; + + if ( strcmp( cmd, "desc" ) == 0 ) + { + Str desc; + + Utl_cpyStr( desc, *( argv++ ) ); + while ( --argc > 0 ) + { + Utl_catStr( desc, " " ); + Utl_catStr( desc, *( argv++ ) ); + } + + Grp_setDsc( grp, desc ); + } + else + { + char c; + + if ( ! Grp_local( grp ) ) + { + fprintf( stderr, "%s is not a local group\n", grp ); + return FALSE; + } + + c = **argv; + if ( c == 'y' || c == 'm' || c == 'n' ) + Grp_setPostAllow( grp, c ); + else + { + fprintf( stderr, "Access must be 'y', 'n' or 'm'" ); + return FALSE; + } + } + + return TRUE; +} + static void doGrps( void ) { @@ -316,9 +430,9 @@ localtime( &lastAccess ) ); strftime( dateCreated, MAXCHAR, "%Y-%m-%d %H:%M:%S", localtime( &created ) ); - printf( "%s\t%s\t%i\t%i\t%i\t%s\t%s\t%s\n", + printf( "%s\t%s\t%i\t%i\t%i\t%c\t%s\t%s\t%s\n", g, Grp_serv( g ), Grp_first( g ), Grp_last( g ), - Grp_rmtNext( g ), dateCreated, + Grp_rmtNext( g ), Grp_postAllow( g ), dateCreated, dateLastAccess, Grp_dsc( g ) ); } while ( Grp_nextGrp( &g ) ); @@ -364,27 +478,30 @@ static const char *msg = "Usage: noffle <option>\n" "Option is one of the following:\n" - " -a | --article <msg id>|all Show article(s) in database\n" - " -c | --cancel <msg id> Remove article from database\n" - " -C | --create <grp> Create a local group\n" - " -d | --database Show content of article database\n" - " -e | --expire <n> Expire articles older than <n> days\n" - " -f | --fetch Get newsfeed from server/post articles\n" - " -g | --groups Show all groups available at server\n" - " -h | --help Show this text\n" - " -l | --list List groups on fetch list\n" - " -n | --online Switch to online mode\n" - " -o | --offline Switch to offline mode\n" - " -q | --query groups Get group list from server\n" - " -q | --query desc Get group descriptions from server\n" - " -q | --query times Get group creation times from server\n" - " -r | --server Run as server on stdin/stdout\n" - " -R | --requested List articles marked for download\n" - " -s | --subscribe-over <grp> Add group to fetch list (overview)\n" - " -S | --subscribe-full <grp> Add group to fetch list (full)\n" - " -t | --subscribe-thread <grp> Add group to fetch list (thread)\n" - " -u | --unsubscribe <grp> Remove group from fetch list\n" - " -v | --version Print version\n"; + " -a | --article <msg id>|all Show article(s) in database\n" + " -c | --cancel <msg id> Remove article from database\n" + " -C | --create <grp> Create a local group\n" + " -d | --database Show content of article database\n" + " -D | --delete <grp> Delete a group\n" + " -e | --expire Expire articles\n" + " -f | --fetch Get newsfeed from server/post articles\n" + " -g | --groups Show all groups available at server\n" + " -h | --help Show this text\n" + " -l | --list List groups on fetch list\n" + " -m | --modify desc <grp> <desc> Modify a group description\n" + " -m | --modify post <grp> (y|n) Modify posting status of a local group\n" + " -n | --online Switch to online mode\n" + " -o | --offline Switch to offline mode\n" + " -q | --query groups Get group list from server\n" + " -q | --query desc Get group descriptions from server\n" + " -q | --query times Get group creation times from server\n" + " -r | --server Run as server on stdin/stdout\n" + " -R | --requested List articles marked for download\n" + " -s | --subscribe-over <grp> Add group to fetch list (overview)\n" + " -S | --subscribe-full <grp> Add group to fetch list (full)\n" + " -t | --subscribe-thread <grp> Add group to fetch list (thread)\n" + " -u | --unsubscribe <grp> Remove group from fetch list\n" + " -v | --version Print version\n"; fprintf( stderr, "%s", msg ); } @@ -442,7 +559,7 @@ static void bugReport( int sig ) { - Log_err( "Received SIGSEGV. Please submit a bug report." ); + Log_err( "Received SIGSEGV. Please submit a bug report" ); signal( SIGSEGV, SIG_DFL ); raise( sig ); } @@ -487,11 +604,13 @@ { "cancel", required_argument, NULL, 'c' }, { "create", required_argument, NULL, 'C' }, { "database", no_argument, NULL, 'd' }, - { "expire", required_argument, NULL, 'e' }, + { "delete", required_argument, NULL, 'D' }, + { "expire", no_argument, NULL, 'e' }, { "fetch", no_argument, NULL, 'f' }, { "groups", no_argument, NULL, 'g' }, { "help", no_argument, NULL, 'h' }, { "list", no_argument, NULL, 'l' }, + { "modify", required_argument, NULL, 'm' }, { "offline", no_argument, NULL, 'o' }, { "online", no_argument, NULL, 'n' }, { "query", required_argument, NULL, 'q' }, @@ -512,7 +631,7 @@ signal( SIGINT, logSignal ); signal( SIGTERM, logSignal ); signal( SIGPIPE, logSignal ); - c = getopt_long( argc, argv, "a:c:C:de:fghlonq:rRs:S:t:u:v", + c = getopt_long( argc, argv, "a:c:C:dD:efghlm:onq:rRs:S:t:u:v", longOptions, NULL ); if ( ! initNoffle( c != 'r' ) ) return EXIT_FAILURE; @@ -552,19 +671,18 @@ case 'd': doDb(); break; - case 'e': + case 'D': + if ( ! optarg ) { - unsigned int days; - - if ( ! optarg || sscanf( optarg, "%u", &days ) != 1 ) - { - fprintf( stderr, "Bad argument: -e %s\n", optarg ); - result = EXIT_FAILURE; - } - else - doExpire( days ); + fprintf( stderr, "Option -D needs argument.\n" ); + result = EXIT_FAILURE; } + else + doDeleteLocalGroup( optarg ); break; + case 'e': + doExpire(); + break; case 'f': doFetch(); break; @@ -578,6 +696,16 @@ case 'l': doList(); break; + case 'm': + if ( ! optarg ) + { + fprintf( stderr, "Option -m needs argument.\n" ); + result = EXIT_FAILURE; + } + else + if ( ! doModify( optarg, argc - optind, &argv[ optind ] ) ) + result = EXIT_FAILURE; + break; case 'n': if ( Online_true() ) fprintf( stderr, "NOFFLE is already online\n" );
--- a/noffle.conf.5 Thu May 04 09:16:09 2000 +0100 +++ b/noffle.conf.5 Fri May 05 08:23:15 2000 +0100 @@ -1,14 +1,200 @@ - .TH noffle.conf 5 -.\" $Id: noffle.conf.5 3 2000-01-04 11:35:42Z enz $ +.\" $Id: noffle.conf.5 44 2000-05-05 07:23:15Z enz $ .SH NAME noffle.conf \- Configuration file for NOFFLE news server .SH DESCRIPTION -noffle.conf is the configuration file of the NOFFLE -news server. +The +.B NOFFLE +news server - see +.BR noffle (1) +- takes its configuration from a configuration file. +By default this file is \fI/etc/noffle.conf\fP. + +.PP +.B noffle.conf +is a normal text file containing +.B NOFFLE +settings, one per line. + +.PP +Leading whitespace on a line is ignored, as is any comment +text. Comment text begins with a '#' character and continues to the +end of the line. Blank lines are permitted. + +.SH SETTINGS + +.TP +.B server <hostname>[:<port>] [<user> <pass>] +Name of the remote server. If no port given, port 119 is used. +Username and password for servers that need authentication +(Original AUTHINFO). The password may not contain white-spaces. +If there are multiple server entries in the config file, all of them are +used for getting groups. In this case the first server should be +the one of your main provider. Note that you must always +run 'noffle --query groups' +after making changes to the server entries. + +.TP +.B max-fetch <n> +Never get more than <n> articles. If there are more, the oldest ones +are discarded. +.br +Default: 300 + +.TP +.B mail-to <address> +Receiver of failed postings. If empty then failed postings are returned +to the sender (taking the address from the article's Sender, X-Sender or +From field, in this order). +.br +Default: <empty string> + +.TP +.B auto-unsubscribe yes|no +Automatically remove groups from fetch list if they have not been +accessed for a number days. +.br +Default: no + +.TP +.B auto-unsubscribe-days <n> +Number of days used for auto-unsubscribe option. +.br +Default: 30 + +.TP +.B thread-follow-time <n> +Automatically mark articles for download in thread mode, if they +are referencing an article that has been opened by a reader within the last +<n> days. +.br +Default: 7 + +.TP +.B connect-timeout <n> +Timeout for connecting to remote server in seconds. +.br +Default: 30 + +.TP +.B auto-subscribe yes|no +Automatically put groups on fetch list if someone reads them. +<mode> can be full, over, thread (depending on the fetch mode) or +off (do not subscribe automatically). Condition for putting a group +on the list is that an article is opened. For this reason there is +always a pseudo article visible in groups that are not on the fetch list. +.br +Default: no + +.TP +.B auto-subscribe-mode full|thread|over +Mode for auto-subscribe option. +.br +Default: over + +.TP +.B remove-messageid yes|no +Remove Message-ID from posted articles. Some remote servers can generate +Message-IDs. +.br +Default: no -See -.BR noffle (3) -for information about NOFFLE and the format of the configuration file. +.TP +.B replace-messageid yes|no +Replace Message-ID of posted articles by a Message-ID generated by +NOFFLE. Some news readers generate Message-IDs that are not accepted by +some servers. For generating Message-IDs, the domain name of your system should +be a valid domain name. If you are in a local domain, set it to your +provider's domain name. +.br +Default: yes + +.TP +.B default-expire <n> +The default expiry period, in days. An expiry period of 0 means "never". +.br +Default: 14 + +.TP +.B expire <group pattern> <n> +The expiry period for a newsgroup or set of newsgroups, in days. The +expiry pattern can contain \fIwildcards\fP, and there can be multiple +.B expire +lines. When checking the expiry period for a group, the expiry +patterns are checked in the order in which they appear in +.I /etc/noffle.conf +until the first match occurs. If no pattern matches the group name, the +.B default expiry period +is used. An expiry period of 0 means "never". +.br +Default: no + +.SH "GROUP NAME WILDCARDS" + +.B NOFFLE +uses a wildcard format that closely matches filename-style wildcards. +\fIalt.binaries.*\fP, for example, matches all newsgroups under the +.I alt.binaries +hierarchy. A full description of the fomat (known as +.B wildmat +patterns) is as follows. + +.TP +.BI \e x +Turns off the special meaning of +.I x +and matches it directly; this is used mostly before a question mark or +asterisk, and is not special inside square brackets. +.TP +.B ? +Matches any single character. +.TP +.B * +Matches any sequence of zero or more characters. +.TP +.BI [ x...y ] +Matches any single character specified by the set +.IR x...y . +A minus sign may be used to indicate a range of characters. +That is, +.I [0\-5abc] +is a shorthand for +.IR [012345abc] . +More than one range may appear inside a character set; +.I [0-9a-zA-Z._] +matches almost all of the legal characters for a host name. +The close bracket, +.IR ] , +may be used if it is the first character in the set. +The minus sign, +.IR \- , +may be used if it is either the first or last character in the set. +.TP +.BI [^ x...y ] +This matches any character +.I not +in the set +.IR x...y , +which is interpreted as described above. +For example, +.I [^]\-] +matches any character other than a close bracket or minus sign. + + +.SH SEE ALSO + +.BR noffle (1) + +.SH AUTHORS + +Markus Enzenberger <markus.enzenberger@t-online.de> +.br +Volker Wysk <volker.wysk@student.uni-tuebingen.de> +.br +Jim Hague <jim.hague@acm.org> +.br +1998-2000. + +
--- a/noffle.conf.example Thu May 04 09:16:09 2000 +0100 +++ b/noffle.conf.example Fri May 05 08:23:15 2000 +0100 @@ -52,3 +52,18 @@ remove-messageid no replace-messageid yes + +# Set the default expire period in days +default-expire 14 + +# Expire all alt.* groups after 2 days, except for alt.oxford.* +# expire after 4 days and alt.oxford.talk never expire. +#expire alt.oxford.talk 0 +#expire alt.oxford.* 4 +#expire alt.* 2 + +# Appearing here, this is equivalent to 'default-expire 20' above. If it +# appeared before the other expire lines, all groups would be +# expired at 20 days, as it would be the first custom match +# for every group. +#expire * 20
--- a/pseudo.c Thu May 04 09:16:09 2000 +0100 +++ b/pseudo.c Fri May 05 08:23:15 2000 +0100 @@ -1,7 +1,7 @@ /* pseudo.c - $Id: pseudo.c 32 2000-04-29 14:45:56Z enz $ + $Id: pseudo.c 44 2000-05-05 07:23:15Z enz $ */ #include "pseudo.h" @@ -14,6 +14,7 @@ #include "group.h" #include "log.h" #include "protocol.h" +#include "util.h" Over * genOv( const char *rawSubj, const char *rawBody, const char *suffix ) @@ -24,7 +25,7 @@ snprintf( subj, MAXCHAR, "[ %s ]", rawSubj ); time( &t ); - strftime( date, MAXCHAR, "%d %b %Y %H:%M:%S %Z", localtime( &t ) ); + Utl_rfc822Date( t, date ); Prt_genMsgId( msgId, "", suffix ); bytes = lines = 0; while ( *rawBody ) @@ -245,8 +246,7 @@ "consistent Probably the remote news server\n" "was changed or has reset its article counter\n" "for this group. As a consequence there could\n" - "be some articles be duplicated in this group\n" - "\n" ); + "be some articles be duplicated in this group\n" ); snprintf( s, MAXCHAR, "Group: %s", grp ); DynStr_appLn( info, s ); snprintf( s, MAXCHAR, "Remote first article number: %i", first ); @@ -275,8 +275,7 @@ "deleted them.\n" "If this group is on the fetch list, then\n" "contact your newsmaster to ensure that\n" - "\"noffle\" is fetching news more frequently.\n" - "\n" ); + "\"noffle\" is fetching news more frequently.\n" ); snprintf( s, MAXCHAR, "Group: %s", grp ); DynStr_appLn( info, s ); snprintf( s, MAXCHAR, "Remote next article number: %i", next ); @@ -303,8 +302,7 @@ "some time.\n" "Re-subscribing is done either automatically\n" "by NOFFLE (if configured) or by manually\n" - "running the 'noffle --subscribe' command\n" - "\n" ); + "running the 'noffle --subscribe' command\n" ); snprintf( s, MAXCHAR, "Group: %s", grp ); DynStr_appLn( info, s ); snprintf( s, MAXCHAR, "Days without access: %i", days ); @@ -325,8 +323,7 @@ DynStr_app( info, "NOFFLE has now automatically subscribed to\n" "this group. It will fetch articles next time\n" - "it is online.\n" - "\n" ); + "it is online.\n" ); genPseudo( "Auto subscribed", DynStr_str( info ) ); } del_DynStr( info );
--- a/server.c Thu May 04 09:16:09 2000 +0100 +++ b/server.c Fri May 05 08:23:15 2000 +0100 @@ -1,7 +1,7 @@ /* server.c - $Id: server.c 43 2000-05-04 08:16:09Z enz $ + $Id: server.c 44 2000-05-05 07:23:15Z enz $ */ #include "server.h" @@ -32,6 +32,7 @@ #include "pseudo.h" #include "request.h" #include "util.h" +#include "wildmat.h" struct { @@ -254,9 +255,11 @@ changeToGrp( arg ); first = Cont_first(); last = Cont_last(); - numb = last - first + 1; - if ( first > last ) + if ( ( first == 0 && last == 0 ) + || first > last ) first = last = numb = 0; + else + numb = last - first + 1; putStat( STAT_GRP_SELECTED, "%lu %lu %lu %s selected", numb, first, last, arg ); } @@ -339,7 +342,7 @@ const Over *ov; int n; - if ( sscanf( arg, "%i", &n ) == 1 ) + if ( sscanf( arg, "%d", &n ) == 1 ) { if ( ! checkNumb( n ) ) return FALSE; @@ -585,7 +588,7 @@ Log_err( "Cannot open pipe to 'sort'" ); if ( Grp_firstGrp( &g ) ) do - if ( Utl_matchPattern( g, pat ) ) + if ( Wld_match( g, pat ) ) { (*printProc)( line, g ); if ( ! Prt_putTxtLn( line, stdout ) ) @@ -597,7 +600,7 @@ { if ( Grp_firstGrp( &g ) ) do - if ( Utl_matchPattern( g, pat ) ) + if ( Wld_match( g, pat ) ) { (*printProc)( line, g ); if ( ! Prt_putTxtLn( line, f ) ) @@ -609,7 +612,7 @@ while ( Grp_nextGrp( &g ) ); ret = pclose( f ); if ( ret != EXIT_SUCCESS ) - Log_err( "sort command returned %i", ret ); + Log_err( "sort command returned %d", ret ); fflush( stdout ); Log_dbg( "[S FLUSH]" ); signal( SIGPIPE, lastHandler ); @@ -633,8 +636,8 @@ static void printActive( Str result, const char *grp ) { - snprintf( result, MAXCHAR, "%s %i %i y", - grp, Grp_last( grp ), Grp_first( grp ) ); + snprintf( result, MAXCHAR, "%s %d %d %c", + grp, Grp_last( grp ), Grp_first( grp ), Grp_postAllow( grp ) ); } static void @@ -1043,8 +1046,7 @@ time_t t; time( &t ); - strftime( val, MAXCHAR, "%d %b %Y %H:%M:%S %Z", - localtime( &t ) ); + Utl_rfc822Date( t, val ); DynStr_app( s, "Date: " ); DynStr_appLn( s, val ); } @@ -1061,13 +1063,13 @@ } else if ( strcmp( field, "newsgroups" ) == 0 ) { - Utl_toLower( val ); - newsgroups = new_Itl ( val, " ," ); + Utl_toLower( val ); + newsgroups = new_Itl ( val, " ," ); DynStr_appLn( s, p ); } else if ( strcmp( field, "control" ) == 0 ) { - control = new_Itl ( val, " " ); + control = new_Itl ( val, " " ); DynStr_appLn( s, p ); } else if ( strcmp( field, "reply-to" ) == 0 ) @@ -1107,6 +1109,7 @@ { const char *grp; Bool knownGrp = FALSE; + Bool postAllowedGrp = FALSE; /* Check at least one group is known. */ for( grp = Itl_first( newsgroups ); @@ -1116,7 +1119,19 @@ if ( Grp_exists( grp ) ) { knownGrp = TRUE; - break; + switch( Grp_postAllow( grp ) ) + { + case 'n': + break; + case 'm': + /* Can't post to moderated local groups. */ + postAllowedGrp = ! Grp_local( grp ); + break; + default: + postAllowedGrp = TRUE; + } + if ( postAllowedGrp ) + break; } } @@ -1126,6 +1141,12 @@ Log_err( "No known group in Newsgroups header field" ); err = TRUE; } + else if ( ! postAllowedGrp ) + { + + Log_err( "No group permits posting" ); + err = TRUE; + } else { err = ( control == NULL ) @@ -1157,7 +1178,7 @@ Utl_cpyStr( t, s ); p = Utl_stripWhiteSpace( t ); - r = sscanf( p, "%i-%i", first, last ); + r = sscanf( p, "%d-%d", first, last ); if ( r < 1 ) { *first = serv.artPtr; @@ -1273,11 +1294,11 @@ if ( ! testGrpSelected() ) return TRUE; - if ( sscanf( arg, "%s %i-%i %s", whatStr, &first, &last, pat ) != 4 ) + if ( sscanf( arg, "%s %d-%d %s", whatStr, &first, &last, pat ) != 4 ) { - if ( sscanf( arg, "%s %i- %s", whatStr, &first, pat ) == 3 ) + if ( sscanf( arg, "%s %d- %s", whatStr, &first, pat ) == 3 ) last = Cont_last(); - else if ( sscanf( arg, "%s %i %s", whatStr, &first, pat ) == 3 ) + else if ( sscanf( arg, "%s %d %s", whatStr, &first, pat ) == 3 ) last = first; else { @@ -1310,23 +1331,23 @@ switch ( what ) { case SUBJ: - if ( Utl_matchPattern( Ov_subj( ov ), pat ) ) + if ( Wld_match( Ov_subj( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_subj( ov ) ); break; case FROM: - if ( Utl_matchPattern( Ov_from( ov ), pat ) ) + if ( Wld_match( Ov_from( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_from( ov ) ); break; case DATE: - if ( Utl_matchPattern( Ov_date( ov ), pat ) ) + if ( Wld_match( Ov_date( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_date( ov ) ); break; case MSG_ID: - if ( Utl_matchPattern( Ov_msgId( ov ), pat ) ) + if ( Wld_match( Ov_msgId( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_msgId( ov ) ); break; case REF: - if ( Utl_matchPattern( Ov_ref( ov ), pat ) ) + if ( Wld_match( Ov_ref( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_ref( ov ) ); break; default:
--- a/util.c Thu May 04 09:16:09 2000 +0100 +++ b/util.c Fri May 05 08:23:15 2000 +0100 @@ -1,13 +1,12 @@ /* util.c - $Id: util.c 32 2000-04-29 14:45:56Z enz $ + $Id: util.c 44 2000-05-05 07:23:15Z enz $ */ #include "util.h" #include <errno.h> #include <ctype.h> -#include <fnmatch.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> @@ -15,6 +14,7 @@ #include <unistd.h> #include "config.h" #include "log.h" +#include "wildmat.h" static const char * nextWhiteSpace( const char *p ) @@ -162,6 +162,17 @@ } void +Utl_stripComment( char *line ) +{ + for ( ; *line != '\0'; line++ ) + if ( *line =='#' ) + { + *line = '\0'; + break; + } +} + +void Utl_cpyStr( Str dst, const char *src ) { dst[ 0 ] = '\0'; @@ -226,6 +237,12 @@ } void +Utl_rfc822Date( time_t t, Str res ) +{ + strftime( res, MAXCHAR,"%a, %d %b %Y %H:%M:%S %z", localtime( &t ) ); +} + +void Utl_allocAndCpy( char **dst, const char *src ) { int len = strlen( src ); @@ -236,11 +253,3 @@ } memcpy( *dst, src, len + 1 ); } - -Bool -Utl_matchPattern( const char *text, const char *pattern ) -{ - if ( pattern[ 0 ] == '*' && pattern[ 1 ] == '\0' ) - return TRUE; - return ( fnmatch( pattern, text, 0 ) == 0 ); -}
--- a/util.h Thu May 04 09:16:09 2000 +0100 +++ b/util.h Fri May 05 08:23:15 2000 +0100 @@ -3,7 +3,7 @@ Miscellaneous helper functions. - $Id: util.h 32 2000-04-29 14:45:56Z enz $ + $Id: util.h 44 2000-05-05 07:23:15Z enz $ */ #ifndef UTL_H @@ -49,6 +49,10 @@ char * Utl_stripWhiteSpace( char *line ); +/* Strip comment from a line. Comments start with '#'. */ +void +Utl_stripComment( char *line ); + /* Write timestamp into <file>. */ void Utl_stamp( Str file ); @@ -57,6 +61,10 @@ Bool Utl_getStamp( time_t *result, Str file ); +/* Put RFC822-compliant date string into res. */ +void +Utl_rfc822Date( time_t t, Str res ); + void Utl_cpyStr( Str dst, const char *src ); @@ -73,10 +81,4 @@ void Utl_allocAndCpy( char **dst, const char *src ); -/* - Do shell-style pattern matching for ?, \, [], and * characters. -*/ -Bool -Utl_matchPattern( const char *text, const char *pattern ); - #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wildmat.c Fri May 05 08:23:15 2000 +0100 @@ -0,0 +1,206 @@ +/* + wildmat.c + + Taken from the INN 2.2 distribution and slightly altered to fit the + Noffle environment. Changes are: + o Rename wildmat() to Wld_match(). + o Adjust includes. + + $Id: wildmat.c 44 2000-05-05 07:23:15Z enz $ + + The entire INN distribution is covered by the following copyright + notice. As this file originated in the INN distribution is it + subject to the conditions of this notice. + + Copyright 1991 Rich Salz. + All rights reserved. + $Revision: 44 $ + + Redistribution and use in any form are permitted provided that the + following restrictions are are met: + 1. Source distributions must retain this entire copyright notice + and comment. + 2. Binary distributions must include the acknowledgement ``This + product includes software developed by Rich Salz'' in the + documentation or other materials provided with the + distribution. This must not be represented as an endorsement + or promotion without specific prior written permission. + 3. The origin of this software must not be misrepresented, either + by explicit claim or by omission. Credits must appear in the + source and documentation. + 4. Altered versions must be plainly marked as such in the source + and documentation and must not be misrepresented as being the + original software. + THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + +*/ + +/* $Revision: 44 $ +** +** Do shell-style pattern matching for ?, \, [], and * characters. +** Might not be robust in face of malformed patterns; e.g., "foo[a-" +** could cause a segmentation violation. It is 8bit clean. +** +** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. +** Rich $alz is now <rsalz@osf.org>. +** April, 1991: Replaced mutually-recursive calls with in-line code +** for the star character. +** +** Special thanks to Lars Mathiesen <thorinn@diku.dk> for the ABORT code. +** This can greatly speed up failing wildcard patterns. For example: +** pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-* +** text 1: -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1 +** text 2: -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1 +** Text 1 matches with 51 calls, while text 2 fails with 54 calls. Without +** the ABORT code, it takes 22310 calls to fail. Ugh. The following +** explanation is from Lars: +** The precondition that must be fulfilled is that DoMatch will consume +** at least one character in text. This is true if *p is neither '*' nor +** '\0'.) The last return has ABORT instead of FALSE to avoid quadratic +** behaviour in cases like pattern "*a*b*c*d" with text "abcxxxxx". With +** FALSE, each star-loop has to run to the end of the text; with ABORT +** only the last one does. +** +** Once the control of one instance of DoMatch enters the star-loop, that +** instance will return either TRUE or ABORT, and any calling instance +** will therefore return immediately after (without calling recursively +** again). In effect, only one star-loop is ever active. It would be +** possible to modify the code to maintain this context explicitly, +** eliminating all recursive calls at the cost of some complication and +** loss of clarity (and the ABORT stuff seems to be unclear enough by +** itself). I think it would be unwise to try to get this into a +** released version unless you have a good test data base to try it out +** on. +*/ +#include <stdio.h> +#include <sys/types.h> +#include "common.h" +#include "log.h" + +#define ABORT (-1) + +/* What character marks an inverted character class? */ +#define NEGATE_CLASS '^' + /* Is "*" a common pattern? */ +#define OPTIMIZE_JUST_STAR + /* Do tar(1) matching rules, which ignore a trailing slash? */ +#undef MATCH_TAR_PATTERN + + +/* +** Match text and p, return TRUE, FALSE, or ABORT. +*/ +static int DoMatch(const char *text, const char *p) +{ + int last; + int matched; + int reverse; + + for ( ; *p; text++, p++) { + if (*text == '\0' && *p != '*') + return ABORT; + switch (*p) { + case '\\': + /* Literal match with following character. */ + p++; + /* FALLTHROUGH */ + default: + if (*text != *p) + return FALSE; + continue; + case '?': + /* Match anything. */ + continue; + case '*': + while (*++p == '*') + /* Consecutive stars act just like one. */ + continue; + if (*p == '\0') + /* Trailing star matches everything. */ + return TRUE; + while (*text) + if ((matched = DoMatch(text++, p)) != FALSE) + return matched; + return ABORT; + case '[': + reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE; + if (reverse) + /* Inverted character class. */ + p++; + matched = FALSE; + if (p[1] == ']' || p[1] == '-') + if (*++p == *text) + matched = TRUE; + for (last = *p; *++p && *p != ']'; last = *p) + /* This next line requires a good C compiler. */ + if (*p == '-' && p[1] != ']' + ? *text <= *++p && *text >= last : *text == *p) + matched = TRUE; + if (matched == reverse) + return FALSE; + continue; + } + } + +#ifdef MATCH_TAR_PATTERN + if (*text == '/') + return TRUE; +#endif /* MATCH_TAR_ATTERN */ + return *text == '\0'; +} + + +/* +** User-level routine. Returns TRUE or FALSE. +*/ +Bool +Wld_match(const char *text, const char *pattern) +{ +#ifdef OPTIMIZE_JUST_STAR + if (pattern[0] == '*' && pattern[1] == '\0') + return TRUE; +#endif /* OPTIMIZE_JUST_STAR */ + return DoMatch(text, pattern) == TRUE; +} + + + +#if defined(WILDMAT_TEST) + +/* Yes, we use gets not fgets. Sue me. */ +extern char *gets(); + + +int +main() +{ + char p[80]; + char text[80]; + + printf("Wildmat tester. Enter pattern, then strings to test.\n"); + printf("A blank line gets prompts for a new pattern; a blank pattern\n"); + printf("exits the program.\n"); + + for ( ; ; ) { + printf("\nEnter pattern: "); + (void)fflush(stdout); + if (gets(p) == NULL || p[0] == '\0') + break; + for ( ; ; ) { + printf("Enter text: "); + (void)fflush(stdout); + if (gets(text) == NULL) + exit(0); + if (text[0] == '\0') + /* Blank line; go back and get a new pattern. */ + break; + printf(" %s\n", Wld_match(text, p) ? "YES" : "NO"); + } + } + + exit(0); + /* NOTREACHED */ +} +#endif /* defined(TEST) */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wildmat.h Fri May 05 08:23:15 2000 +0100 @@ -0,0 +1,18 @@ +/* + wildmat.h + + Noffle header file for wildmat. + + $Id: wildmat.h 44 2000-05-05 07:23:15Z enz $ + */ + +#ifndef WILDMAT_H +#define WILDMAT_H + +/* + See if test is matched by pattern p. Return TRUE if so. + */ +Bool +Wld_match(const char *text, const char *pattern); + +#endif