# 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 @@
+
+
+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 \n"
"Option is one of the following:\n"
- " -a | --article |all Show article(s) in database\n"
- " -c | --cancel Remove article from database\n"
- " -C | --create Create a local group\n"
- " -d | --database Show content of article database\n"
- " -e | --expire Expire articles older than 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 Add group to fetch list (overview)\n"
- " -S | --subscribe-full Add group to fetch list (full)\n"
- " -t | --subscribe-thread Add group to fetch list (thread)\n"
- " -u | --unsubscribe Remove group from fetch list\n"
- " -v | --version Print version\n";
+ " -a | --article |all Show article(s) in database\n"
+ " -c | --cancel Remove article from database\n"
+ " -C | --create Create a local group\n"
+ " -d | --database Show content of article database\n"
+ " -D | --delete 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 Modify a group description\n"
+ " -m | --modify post (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 Add group to fetch list (overview)\n"
+ " -S | --subscribe-full Add group to fetch list (full)\n"
+ " -t | --subscribe-thread Add group to fetch list (thread)\n"
+ " -u | --unsubscribe 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" );
diff -r 792eb10e936d -r 8e972daaeab9 noffle.conf.5
--- 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 [:] [ ]
+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
+Never get more than articles. If there are more, the oldest ones
+are discarded.
+.br
+Default: 300
+
+.TP
+.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:
+
+.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
+Number of days used for auto-unsubscribe option.
+.br
+Default: 30
+
+.TP
+.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
+
+.TP
+.B connect-timeout
+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.
+ 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
+The default expiry period, in days. An expiry period of 0 means "never".
+.br
+Default: 14
+
+.TP
+.B expire
+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
+.br
+Volker Wysk
+.br
+Jim Hague
+.br
+1998-2000.
+
+
diff -r 792eb10e936d -r 8e972daaeab9 noffle.conf.example
--- 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
diff -r 792eb10e936d -r 8e972daaeab9 pseudo.c
--- 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 );
diff -r 792eb10e936d -r 8e972daaeab9 server.c
--- 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:
diff -r 792eb10e936d -r 8e972daaeab9 util.c
--- 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
#include
-#include
#include
#include
#include
@@ -15,6 +14,7 @@
#include
#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 );
-}
diff -r 792eb10e936d -r 8e972daaeab9 util.h
--- 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 . */
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
diff -r 792eb10e936d -r 8e972daaeab9 wildmat.c
--- /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 .
+** April, 1991: Replaced mutually-recursive calls with in-line code
+** for the star character.
+**
+** Special thanks to Lars Mathiesen 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
+#include
+#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) */
diff -r 792eb10e936d -r 8e972daaeab9 wildmat.h
--- /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