# HG changeset patch # User enz # Date 957511395 -3600 # Node ID 8e972daaeab95ddebc9aad405acb0c7fcf00b893 # Parent 792eb10e936df54eff27be699bc20ad0798c34bb [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 diff -r 792eb10e936d -r 8e972daaeab9 CHANGELOG.html --- 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 @@
-

Current developer version

+

Version 1.opre6pre

diff -r 792eb10e936d -r 8e972daaeab9 Makefile --- 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) diff -r 792eb10e936d -r 8e972daaeab9 README.html --- 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 @@ Documentation
  • Links +
  • +Acknowledgements

    @@ -179,6 +181,12 @@ +

    Acknowledgements

    + +The wildmat newsgroup pattern matching software used by NOFFLE +was developed by Rich Salz, and is as distributed with INN +v2.2. +


    diff -r 792eb10e936d -r 8e972daaeab9 client.c --- 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", . +*/ + +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 ); diff -r 792eb10e936d -r 8e972daaeab9 config.c --- 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 " or "expire ". + 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 ); } diff -r 792eb10e936d -r 8e972daaeab9 config.h --- 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 diff -r 792eb10e936d -r 8e972daaeab9 content.c --- 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 @@ -11,6 +11,7 @@ #include #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 diff -r 792eb10e936d -r 8e972daaeab9 database.c --- 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 #include #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 diff -r 792eb10e936d -r 8e972daaeab9 database.h --- 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 */ +/* + 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 diff -r 792eb10e936d -r 8e972daaeab9 group.c --- 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 ) ) diff -r 792eb10e936d -r 8e972daaeab9 group.h --- 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. */ diff -r 792eb10e936d -r 8e972daaeab9 itemlist.c --- 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 ); diff -r 792eb10e936d -r 8e972daaeab9 noffle.1 --- 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 +\-D | \-\-delete +.br +.B noffle +\-e | \-\-expire .br .B noffle \-f | \-\-fetch @@ -33,6 +36,12 @@ \-l | \-\-list .br .B noffle +\-m | \-\-modify desc +.br +.B noffle +\-m | \-\-modify post (y|n) +.br +.B noffle \-n | \-\-online .br .B noffle @@ -104,8 +113,8 @@ .TP .B \-a, \-\-article |all Write article 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 -Delete all articles older than days from the database. +.B \-D, \-\-delete +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 - + .TP .B \-h, \-\-help @@ -167,6 +187,16 @@ Format: full|thread|over .TP +.B \-m | \-\-modify desc +Modify the description of the named newsgroup. + +.TP +.B \-m | \-\-modify post +Modify the posting permission on a local newsgroup. 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 -Configuration file. Comment lines begin with -.I #. -Definition lines may contain: -.br -.B server [:] [ ] -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 -Never get more than articles. If there are more, the oldest ones -are discarded. -.br -Default: 300 -.br -.B mail-to
    -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: -.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 -Number of days used for auto-unsubscribe option. -.br -Default: 30 -.br -.B thread-follow-time -Automatically mark articles for download in thread mode, if they -are referencing an article that has been opened by a reader within the last - days. -.br -Default: 7 -.br -.B connect-timeout -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. - 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 /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, diff -r 792eb10e936d -r 8e972daaeab9 noffle.c --- 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 @@ -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