# HG changeset patch
# User enz
# Date 957019556 -3600
# Node ID 526a4c34ee2e7a32c3411a2de3553abd393c9ce1
# Parent ab6cf19be6d3b9ebe3ccde4ab579875bae96f883
[svn] Applied patch from Jim Hague: support for local groups / new command
line options --create and --cancel.
diff -r ab6cf19be6d3 -r 526a4c34ee2e CHANGELOG.html
--- a/CHANGELOG.html Sat Apr 29 14:37:59 2000 +0100
+++ b/CHANGELOG.html Sat Apr 29 15:45:56 2000 +0100
@@ -12,6 +12,14 @@
diff -r ab6cf19be6d3 -r 526a4c34ee2e Makefile
--- a/Makefile Sat Apr 29 14:37:59 2000 +0100
+++ b/Makefile Sat Apr 29 15:45:56 2000 +0100
@@ -2,7 +2,7 @@
#
# Makefile for Noffle news server
#
-# $Id: Makefile 20 2000-04-18 06:11:50Z enz $
+# $Id: Makefile 32 2000-04-29 14:45:56Z enz $
#
###############################################################################
@@ -18,15 +18,17 @@
CFLAGS = -Wall -O -g
#CFLAGS = -Wall -g -DDEBUG
-VERSION = 1.0pre5
+VERSION = 1.0pre5develop
-FILESH = client.h common.h config.h content.h database.h dynamicstring.h \
- fetch.h fetchlist.h group.h lock.h log.h online.h outgoing.h over.h \
- protocol.h pseudo.h request.h server.h util.h
+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
-FILESC = fetch.c client.c config.c content.c database.c dynamicstring.c \
- fetchlist.c group.c lock.c log.c noffle.c online.c outgoing.c over.c \
- protocol.c pseudo.c request.c server.c util.c
+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
OBJS = $(patsubst %.c,%.o,$(FILESC))
diff -r ab6cf19be6d3 -r 526a4c34ee2e README.html
--- a/README.html Sat Apr 29 14:37:59 2000 +0100
+++ b/README.html Sat Apr 29 15:45:56 2000 +0100
@@ -1,7 +1,8 @@
-
NOFFLE
+
+
@@ -15,7 +16,24 @@
-
Features
+
+
+
+
+
+
+
NOFFLE is a
Usenet
@@ -23,15 +41,10 @@
to the Internet.
It acts as a server to news clients running on the
local host, but gets its news feed by acting as a client to a remote server.
-
-It allows reading news offline with many news reader programs,
-even if they do not support offline reading by themselves.
-
NOFFLE is written for the
-Linux
-operating system and freely available under GPL
-(see
-http://www.fsf.org/copyleft/gpl.html ).
+Linux
+operating system and freely available under
+GPL .
While Online:
@@ -45,107 +58,131 @@
Allows reading news offline with many news clients,
even if they do not support offline reading by themselves.
-
+
-Groups can be retrieved in different modes:
+Groups can be retrieved in overview, full or thread mode.
In overview mode, opened articles that have not been completely downloaded
-yet are marked for download. NOFFLE generates a pseudo article telling
+yet are marked for download. NOFFLE generates a pseudo article body telling
the human about this.
+
-In full mode, the complete articles are fetched.
+In full mode, complete articles are fetched at once.
+
-Thread mode is like overview mode, but automatically downloads all
-article bodies that are in the same thread as articles that already have
-been read.
+Thread mode is like overview mode, but opening an article marks the
+whole thread for download (all later articles for some time
+that are referencing the original article).
+
-
+
The news feed is invoked automatically next online time by calling
NOFFLE in the ip-up script.
-
+
Groups can be put on the fetch list via the 'noffle'
-command or automatically when someone tries to read them. Groups can be
-removed from the fetch list manually or automatically, when nobody accesses
-them for some time.
-
+command or
+automatically when someone tries to read them. Groups can be automatically
+removed from the fetch list, when nobody accesses them for some time.
+
+
+NOFFLE also offers limited support for local groups. Articles posted
+in local groups appear in the news database for those groups immediately,
+and are expired in the same way as other articles.
+
-Compatibility with News Clients
+
Subscribing to groups in full mode should work with any news reader.
Caching of articles is unnecessary, since NOFFLE already caches them
and should be switched off.
-Subscribing to groups in overview mode or thread mode puts some
-requirements on the news reader program. See
-NOTES.txt for compatibility notes.
+Subscribing to groups in overview mode or thread mode requires the
+following from the news reader program:
+
+
+
+It must not cache articles at all (or allow to switch the cache off),
+because the article bodies change from the pseudo body
+"marked for download" to the real body.
+
+
+The reader should rarely open article bodies automatically,
+because it will mark them unwantedly for download.
+
+
-
Getting NOFFLE
+
-NOFFLE can be downloaded from the NOFFLE homepage at
-
-http://home.t-online.de/home/markus.enzenberger/noffle.html :
+NOFFLE can be downloaded from this web site.
-
-
-noffle-0.19.tar.gz
-
-(current release, 25 Apr 1999)
+noffle-1.0pre5.tar.gz
+(current release, 18 Apr 2000)
-
-
-noffle-0.17.tar.gz
-
-(last release, 25 Jan 1999)
+noffle-1.0pre4.tar.gz
+(current release, 13 Nov 1999)
-Read
-
-INSTALL.txt
-from the package for compiling and installing NOFFLE on your system.
-
-RPM packages have been created by Mario Moder
-(moderm@gmx.net ). They are available
+RPM packages have been created by
+Mario Moder . They are available
at
-ftp://ftp.fbam.de/pub/linux/
+ftp://ftp.fbam.de/pub/linux/
+
+
+I moved Noffle to
+SourceForge
+recently. You can download files from the
+Noffle project page .
+There is also a mailing list and a discussion forum.
+
+
+
+Read the files README and INSTALL from the package
+for compiling and installing NOFFLE on your system.
+
+Some German documentation
+(
+"NOFFLE Installation" )
+is provided by
+Klaus Mödinger .
+
The current version is still beta. Please send bug reports,
-comments and patches to me
-(markus.enzenberger@t-online.de ).
+comments and patches to
+
+markus.enzenberger@t-online.de .
-If you want to receive announcements about NOFFLE, I will put you on
-my announcement list. Just send me a mail.
-
Links
+
+
-Last modified 5/99,
+Last modified 4/2000,
Markus Enzenberger
diff -r ab6cf19be6d3 -r 526a4c34ee2e control.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/control.c Sat Apr 29 15:45:56 2000 +0100
@@ -0,0 +1,74 @@
+/*
+ control.c
+
+ $Id: control.c 32 2000-04-29 14:45:56Z enz $
+*/
+
+#include "control.h"
+#include
+#include "common.h"
+#include "content.h"
+#include "database.h"
+#include "group.h"
+#include "itemlist.h"
+#include "log.h"
+#include "outgoing.h"
+
+int
+Ctrl_cancel( const char *msgId )
+{
+ ItemList *refs;
+ const char *ref;
+ Str server;
+ Bool seen = FALSE;
+ int res = CANCEL_OK;
+
+ /* See if in outgoing and zap if so. */
+ if ( Out_find( msgId, server ) )
+ {
+ Out_remove( server, msgId );
+ Log_inf( "'%s' cancelled from outgoing queue for '%s'.\n",
+ msgId, server );
+ seen = TRUE;
+ }
+
+ if ( ! Db_contains( msgId ) )
+ {
+ Log_inf( "Cancel: '%s' not in database.", msgId );
+ return seen ? CANCEL_OK : CANCEL_NO_SUCH_MSG;
+ }
+
+ /*
+ Retrieve the Xrefs, remove from each group and then
+ remove from the database.
+ */
+ refs = new_Itl( Db_xref( msgId ), " " );
+ for( ref = Itl_first( refs ); ref != NULL; ref = Itl_next( refs ) )
+ {
+ Str grp;
+ int no;
+
+ if ( sscanf( ref, "%s:%d", grp, &no ) != 2 )
+ break;
+
+ if ( Grp_exists( grp ) )
+ {
+ Cont_read( grp );
+ Cont_delete( no );
+ Cont_write();
+
+ if ( ! Grp_local( grp ) && ! seen )
+ res = CANCEL_NEEDS_MSG;
+
+ Log_dbg( "Removed '%s' from group '%s'.", msgId, grp );
+ }
+ else
+ {
+ Log_inf( "Group '%s' in Xref for '%s' not found.", grp, msgId );
+ }
+ }
+ del_Itl( refs );
+ Db_delete( msgId );
+ Log_inf( "Message '%s' cancelled.", msgId );
+ return res;
+}
diff -r ab6cf19be6d3 -r 526a4c34ee2e control.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/control.h Sat Apr 29 15:45:56 2000 +0100
@@ -0,0 +1,25 @@
+/*
+ control.h
+
+ Control actions needed by server and command line.
+
+ $Id: control.h 32 2000-04-29 14:45:56Z enz $
+*/
+
+#ifndef CONTROL_H
+#define CONTROL_H
+
+#define CANCEL_OK 0
+#define CANCEL_NO_SUCH_MSG 1
+#define CANCEL_NEEDS_MSG 2
+
+/*
+ Cancel a message. Return CANCEL_OK if completely cancelled,
+ CANCEL_NO_SUCH_MSG if no message with that ID exists, and
+ CANCEL_NEEDS_MSG if a 'cancel' message should be propagated upstream
+ to cancel the message elsewhere.
+ */
+int
+Ctrl_cancel( const char *msgId );
+
+#endif
diff -r ab6cf19be6d3 -r 526a4c34ee2e database.c
--- a/database.c Sat Apr 29 14:37:59 2000 +0100
+++ b/database.c Sat Apr 29 15:45:56 2000 +0100
@@ -1,7 +1,7 @@
/*
database.c
- $Id: database.c 3 2000-01-04 11:35:42Z enz $
+ $Id: database.c 32 2000-04-29 14:45:56Z 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
@@ -251,7 +251,7 @@
}
if ( ! ( db.stat & DB_NOT_DOWNLOADED ) )
{
- Log_err( "Trying to store alrady retrieved article '%s'", msgId );
+ Log_err( "Trying to store already retrieved article '%s'", msgId );
return FALSE;
}
db.stat &= ~DB_NOT_DOWNLOADED;
@@ -264,24 +264,12 @@
startPos = artTxt;
while ( TRUE )
{
- artTxt = Utl_getLn( lineEx, artTxt );
+ artTxt = Utl_getHeaderLn( lineEx, artTxt );
if ( lineEx[ 0 ] == '\0' )
{
DynStr_appLn( db.txt, lineEx );
break;
}
- /* Get other lines if field is split over multiple lines */
- while ( ( artTxt = Utl_getLn( line, artTxt ) ) )
- if ( isspace( line[ 0 ] ) )
- {
- strncat( lineEx, "\n", MAXCHAR );
- strncat( lineEx, line, MAXCHAR );
- }
- else
- {
- artTxt = Utl_ungetLn( startPos, artTxt );
- break;
- }
/* Remove fields already in overview and handle x-noffle
headers correctly in case of cascading NOFFLEs */
if ( Prt_getField( field, value, lineEx ) )
@@ -514,6 +502,19 @@
return gdbm_exists( db.dbf, key );
}
+void
+Db_delete( const char *msgId )
+{
+ datum key;
+
+ ASSERT( db.dbf );
+ if ( strcmp( msgId, db.msgId ) == 0 )
+ db.msgId[ 0 ] = '\0';
+ key.dptr = (void*)msgId;
+ key.dsize = strlen( msgId ) + 1;
+ gdbm_delete( db.dbf, key );
+}
+
static datum cursor = { NULL, 0 };
Bool
diff -r ab6cf19be6d3 -r 526a4c34ee2e database.h
--- a/database.h Sat Apr 29 14:37:59 2000 +0100
+++ b/database.h Sat Apr 29 15:45:56 2000 +0100
@@ -3,7 +3,7 @@
Article database.
- $Id: database.h 3 2000-01-04 11:35:42Z enz $
+ $Id: database.h 32 2000-04-29 14:45:56Z enz $
*/
#ifndef DB_H
@@ -66,6 +66,10 @@
Bool
Db_contains( const char *msgId );
+/* Delete entry from database */
+void
+Db_delete( const char *msgId );
+
Bool
Db_first( const char** msgId );
diff -r ab6cf19be6d3 -r 526a4c34ee2e group.c
--- a/group.c Sat Apr 29 14:37:59 2000 +0100
+++ b/group.c Sat Apr 29 15:45:56 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 3 2000-01-04 11:35:42Z enz $
+ $Id: group.c 32 2000-04-29 14:45:56Z enz $
*/
#include "group.h"
@@ -138,6 +138,14 @@
return gdbm_exists( grp.dbf, key );
}
+Bool
+Grp_local( const char *name )
+{
+ if ( ! loadGrp( name ) )
+ return 0;
+ return ( strcmp( grp.serv, "(local)" ) == 0 );
+}
+
void
Grp_create( const char *name )
{
@@ -226,6 +234,12 @@
}
void
+Grp_setLocal( const char *name )
+{
+ Grp_setServ( name, "(local)" );
+}
+
+void
Grp_setServ( const char *name, const char *value )
{
if ( loadGrp( name ) )
diff -r ab6cf19be6d3 -r 526a4c34ee2e group.h
--- a/group.h Sat Apr 29 14:37:59 2000 +0100
+++ b/group.h Sat Apr 29 15:45:56 2000 +0100
@@ -3,7 +3,7 @@
Groups database
- $Id: group.h 3 2000-01-04 11:35:42Z enz $
+ $Id: group.h 32 2000-04-29 14:45:56Z enz $
*/
#ifndef GRP_H
@@ -24,6 +24,10 @@
Bool
Grp_exists( const char *name );
+/* is it a local group? */
+Bool
+Grp_local( const char *name );
+
/* create new group and save it in database */
void
Grp_create( const char *name );
@@ -66,6 +70,9 @@
Grp_setDsc( const char *name, const char *value );
void
+Grp_setLocal( const char *name );
+
+void
Grp_setServ( const char *name, const char *value );
void
diff -r ab6cf19be6d3 -r 526a4c34ee2e itemlist.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/itemlist.c Sat Apr 29 15:45:56 2000 +0100
@@ -0,0 +1,123 @@
+/*
+ itemlist.c
+
+ $Id: itemlist.c 32 2000-04-29 14:45:56Z enz $
+*/
+
+#include "itemlist.h"
+#include
+#include
+#include
+#include "common.h"
+#include "log.h"
+
+struct ItemList
+{
+ char *list;
+ char *separators;
+ char *next;
+ size_t count;
+};
+
+/* Make a new item list. */
+ItemList *
+new_Itl( const char *list, const char *separators )
+{
+ ItemList * res;
+ char *p;
+ Bool inItem;
+
+ res = (ItemList *) malloc( sizeof( ItemList ) );
+ if ( res == NULL )
+ {
+ Log_err( "Malloc of ItemList failed." );
+ exit( EXIT_FAILURE );
+ }
+
+ res->list = (char *) malloc( strlen(list) + 2 );
+ if ( res->list == NULL )
+ {
+ Log_err( "Malloc of ItemList.list failed." );
+ exit( EXIT_FAILURE );
+ }
+ strcpy( res->list, list );
+
+ if ( ( res->separators = strdup( separators ) ) == NULL )
+ {
+ Log_err( "Malloc of ItemList.separators failed." );
+ exit( EXIT_FAILURE );
+ }
+
+ res->count = 0;
+ res->next = res->list;
+
+ /* Separate items into strings and have final zero-length string. */
+ for( p = res->list, inItem = FALSE; *p != '\0'; p++ )
+ {
+ Bool isSep = ( strchr( separators, p[ 0 ] ) != NULL );
+
+ if ( inItem )
+ {
+ if ( isSep )
+ {
+ p[ 0 ] = '\0';
+ inItem = FALSE;
+ res->count++;
+ }
+ }
+ else
+ {
+ if ( ! isSep )
+ inItem = TRUE;
+ }
+ }
+ if ( inItem )
+ res->count++;
+ p[ 1 ] = '\0';
+ return res;
+}
+
+/* Delete an item list. */
+void
+del_Itl( ItemList *self )
+{
+ if ( self == NULL )
+ return;
+ free( self->list );
+ free( self->separators );
+ free( self );
+}
+
+/* Get first item. */
+const char *
+Itl_first( ItemList *self )
+{
+ self->next = self->list;
+ return Itl_next( self );
+}
+
+/* Get next item or NULL. */
+const char *
+Itl_next( ItemList *self )
+{
+ const char *res = self->next;
+
+ if ( res[ 0 ] == '\0' )
+ return NULL;
+
+ while ( strchr( self->separators, res[ 0 ] ) != NULL )
+ res++;
+
+ if ( res[ 0 ] == '\0' && res[ 1 ] == '\0' )
+ return NULL;
+
+ self->next += strlen( res ) + 1;
+ return res;
+}
+
+/* Get count of items in list. */
+size_t
+Itl_count( const ItemList *self )
+{
+ return self->count;
+}
diff -r ab6cf19be6d3 -r 526a4c34ee2e itemlist.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/itemlist.h Sat Apr 29 15:45:56 2000 +0100
@@ -0,0 +1,38 @@
+/*
+ itemlist.h
+
+ Copy a string wiht a list of separated items (as found in several
+ header lines) and provide a convenient way of accessing the
+ individual items.
+
+ $Id: itemlist.h 32 2000-04-29 14:45:56Z enz $ */
+
+#ifndef ITEMLIST_H
+#define ITEMLIST_H
+
+#include
+
+struct ItemList;
+typedef struct ItemList ItemList;
+
+/* Make a new item list. */
+ItemList *
+new_Itl( const char *list, const char *separators );
+
+/* Delete an item list. */
+void
+del_Itl( ItemList *self );
+
+/* Get first item. */
+const char *
+Itl_first( ItemList *self);
+
+/* Get next item or NULL. */
+const char *
+Itl_next( ItemList *self );
+
+/* Get count of items in list. */
+size_t
+Itl_count( const ItemList *self );
+
+#endif
diff -r ab6cf19be6d3 -r 526a4c34ee2e noffle.1
--- a/noffle.1 Sat Apr 29 14:37:59 2000 +0100
+++ b/noffle.1 Sat Apr 29 15:45:56 2000 +0100
@@ -1,6 +1,5 @@
-
.TH noffle 1
-.\" $Id: noffle.1 29 2000-04-29 12:59:10Z enz $
+.\" $Id: noffle.1 32 2000-04-29 14:45:56Z enz $
.SH NAME
noffle \- Usenet package optimized for dialup connections.
@@ -10,6 +9,12 @@
\-a | \-\-article |all
.br
.B noffle
+\-c | \-\-cancel
+.br
+.B noffle
+\-C | \-\-create
+.br
+.B noffle
\-d | \-\-database
.br
.B noffle
@@ -80,6 +85,20 @@
but download articles full if an article of the same thread already has
been downloaded.
+.PP
+.B NOFFLE
+also offers limited support for local news groups. Articles
+posted to these appear in full in the database for the local group(s)
+immediately. They are expired in the usual way.
+.PP
+If an article is cross-posted to a local group and a remote group, it
+appears in the local group immediately and in the remote group after
+the next fetch from the remove server.
+.PP
+Note that
+.B NOFFLE
+cannot exchange the contents of local groups with other news servers.
+
.SH OPTIONS
.TP
@@ -91,6 +110,19 @@
If "all" is given as message Id, all articles are shown.
.TP
+.B \-c, \-\-cancel
+Cancel the article from the database and remove it from the queue of
+outbound messages if it has not already been sent. Message Id must
+contain the leading '<' and trailing '>' (quote the argument to avoid
+shell interpretation of '<' and '>').
+
+.TP
+.B \-C, \-\-create
+Create a new local newsgroup with the given name. The name should
+conform to the usual newsgroup naming rules to avoid confusing
+newsreaders.
+
+.TP
.B \-d, \-\-database
Write the complete content of the article database to standard output.
diff -r ab6cf19be6d3 -r 526a4c34ee2e noffle.c
--- a/noffle.c Sat Apr 29 14:37:59 2000 +0100
+++ b/noffle.c Sat Apr 29 15:45:56 2000 +0100
@@ -10,9 +10,10 @@
received for some seconds (to allow multiple clients connect at the same
time).
- $Id: noffle.c 15 2000-04-11 06:36:57Z enz $
+ $Id: noffle.c 32 2000-04-29 14:45:56Z enz $
*/
+#include
#include
#include
#include
@@ -22,13 +23,16 @@
#include "client.h"
#include "common.h"
#include "content.h"
+#include "control.h"
#include "config.h"
#include "database.h"
#include "fetch.h"
#include "fetchlist.h"
#include "group.h"
+#include "itemlist.h"
#include "log.h"
#include "online.h"
+#include "outgoing.h"
#include "over.h"
#include "pseudo.h"
#include "util.h"
@@ -72,6 +76,25 @@
}
}
+static void
+doCancel( const char *msgId )
+{
+ switch( Ctrl_cancel( msgId ) )
+ {
+ case CANCEL_NO_SUCH_MSG:
+ printf( "No such message '%s'.\n", msgId );
+ break;
+
+ case CANCEL_OK:
+ printf( "Message '%s' cancelled.\n", msgId );
+ break;
+
+ case CANCEL_NEEDS_MSG:
+ printf( "Message '%s' cancelled in local database only.\n", msgId );
+ break;
+ }
+}
+
/* List articles requested from one particular server */
static void
listRequested1( const char* serv )
@@ -199,7 +222,8 @@
else
++cntLeft;
}
- if ( autoUnsubscribe
+ if ( !Grp_local( grp )
+ && autoUnsubscribe
&& difftime( now, Grp_lastAccess( grp ) ) > maxAge )
{
Log_ntc( "Auto-unsubscribing from %s after %d "
@@ -230,6 +254,21 @@
}
static void
+doCreateLocalGroup( const char * name )
+{
+ 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 );
+ }
+}
+
+static void
doList( void )
{
FetchMode mode;
@@ -324,6 +363,8 @@
"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"
@@ -399,7 +440,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 );
}
@@ -441,6 +482,8 @@
struct option longOptions[] =
{
{ "article", required_argument, NULL, 'a' },
+ { "cancel", required_argument, NULL, 'c' },
+ { "create", required_argument, NULL, 'C' },
{ "database", no_argument, NULL, 'd' },
{ "expire", required_argument, NULL, 'e' },
{ "fetch", no_argument, NULL, 'f' },
@@ -467,7 +510,7 @@
signal( SIGINT, logSignal );
signal( SIGTERM, logSignal );
signal( SIGPIPE, logSignal );
- c = getopt_long( argc, argv, "a:de:fghlonq:rRs:S:t:u:v",
+ c = getopt_long( argc, argv, "a:c:C:de:fghlonq:rRs:S:t:u:v",
longOptions, NULL );
if ( ! initNoffle( c != 'r' ) )
return EXIT_FAILURE;
@@ -486,6 +529,24 @@
else
doArt( optarg );
break;
+ case 'c':
+ if ( ! optarg )
+ {
+ fprintf( stderr, "Option -c needs argument.\n" );
+ result = EXIT_FAILURE;
+ }
+ else
+ doCancel( optarg );
+ break;
+ case 'C':
+ if ( ! optarg )
+ {
+ fprintf( stderr, "Option -C needs argument.\n" );
+ result = EXIT_FAILURE;
+ }
+ else
+ doCreateLocalGroup( optarg );
+ break;
case 'd':
doDb();
break;
diff -r ab6cf19be6d3 -r 526a4c34ee2e outgoing.c
--- a/outgoing.c Sat Apr 29 14:37:59 2000 +0100
+++ b/outgoing.c Sat Apr 29 15:45:56 2000 +0100
@@ -1,7 +1,7 @@
/*
outgoing.c
- $Id: outgoing.c 3 2000-01-04 11:35:42Z enz $
+ $Id: outgoing.c 32 2000-04-29 14:45:56Z enz $
*/
#include "outgoing.h"
@@ -43,7 +43,7 @@
}
Bool
-Out_add( const char *serv, const Str msgId, const DynStr *artTxt )
+Out_add( const char *serv, const char *msgId, const DynStr *artTxt )
{
Str file;
FILE *f;
@@ -111,7 +111,7 @@
}
void
-Out_remove( const char *serv, Str msgId )
+Out_remove( const char *serv, const char *msgId )
{
Str file;
@@ -119,3 +119,45 @@
if ( unlink( file ) != 0 )
Log_err( "Cannot remove %s", file );
}
+
+Bool
+Out_find( const char *msgId, Str server )
+{
+ Str servdir;
+ DIR *d;
+ struct dirent *entry;
+ Bool res;
+
+
+ snprintf( servdir, MAXCHAR, "%s/outgoing", Cfg_spoolDir() );
+ if ( ! ( d = opendir( servdir ) ) )
+ {
+ Log_dbg( "Cannot open %s", servdir );
+ return FALSE;
+ }
+
+ readdir( d ); /* '.' */
+ readdir( d ); /* '..' */
+
+ res = FALSE;
+ while ( ! res && ( entry = readdir( d ) ) != NULL )
+ {
+ Str file;
+ struct stat s;
+
+ fileOutgoing( file, entry->d_name, msgId );
+ if ( stat( file, &s ) == 0 )
+ {
+ res = TRUE;
+ Utl_cpyStr( server, entry->d_name );
+ }
+ }
+
+ closedir( d );
+ return res;
+}
+
+
+
+
+
diff -r ab6cf19be6d3 -r 526a4c34ee2e outgoing.h
--- a/outgoing.h Sat Apr 29 14:37:59 2000 +0100
+++ b/outgoing.h Sat Apr 29 15:45:56 2000 +0100
@@ -3,7 +3,7 @@
Collection of posted articles.
- $Id: outgoing.h 3 2000-01-04 11:35:42Z enz $
+ $Id: outgoing.h 32 2000-04-29 14:45:56Z enz $
*/
#ifndef OUT_H
@@ -13,7 +13,7 @@
#include "dynamicstring.h"
Bool
-Out_add( const char *serv, const Str msgId, const DynStr *artTxt );
+Out_add( const char *serv, const char *msgId, const DynStr *artTxt );
/* Start enumeration. Return TRUE on success. */
Bool
@@ -25,6 +25,10 @@
/* Delete article from outgoing collection */
void
-Out_remove( const char *serv, Str msgId );
+Out_remove( const char *serv, const char *msgId );
+
+/* Find server for outgoing message. */
+Bool
+Out_find( const char *msgId, Str server );
#endif
diff -r ab6cf19be6d3 -r 526a4c34ee2e post.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/post.c Sat Apr 29 15:45:56 2000 +0100
@@ -0,0 +1,150 @@
+/*
+ post.c
+
+ $Id: post.c 32 2000-04-29 14:45:56Z enz $
+*/
+
+#include "post.h"
+#include
+#include "common.h"
+#include "content.h"
+#include "database.h"
+#include "group.h"
+#include "log.h"
+#include "over.h"
+#include "protocol.h"
+#include "util.h"
+
+struct OverInfo
+{
+ Str subject;
+ Str from;
+ Str date;
+ Str msgId;
+ Str ref;
+ size_t bytes;
+ size_t lines;
+};
+
+struct Article
+{
+ const char * text;
+ Bool posted;
+ struct OverInfo over;
+};
+
+static struct Article article = { NULL, FALSE };
+
+static void
+getOverInfo( struct OverInfo * o )
+{
+ const char *p = article.text;
+ Str line, field, value;
+
+ o->bytes = strlen( p );
+
+ while( p != NULL )
+ {
+ p = Utl_getHeaderLn( line, p );
+ if ( line[ 0 ] == '\0' )
+ break;
+
+ /* Look for headers we need to stash. */
+ if ( Prt_getField( field, value, line ) )
+ {
+ if ( strcmp( field, "subject" ) == 0 )
+ Utl_cpyStr( o->subject, value );
+ else if ( strcmp ( field, "from" ) == 0 )
+ Utl_cpyStr( o->from, value );
+ else if ( strcmp ( field, "date" ) == 0 )
+ Utl_cpyStr( o->date, value );
+ else if ( strcmp ( field, "references" ) == 0 )
+ Utl_cpyStr( o->ref, value );
+ else if ( strcmp ( field, "message-id" ) == 0 )
+ Utl_cpyStr( o->msgId, value );
+ }
+ }
+
+ /* Move to start of body and count lines. */
+ for ( p++, o->lines = 0; *p != '\0'; p++ )
+ if ( *p == '\n' )
+ o->lines++;
+}
+
+/* Register an article for posting. */
+Bool
+Post_open( const char * text )
+{
+ if ( article.text != NULL )
+ {
+ Log_err( "Busy article in Post_open." );
+ return FALSE;
+ }
+
+ memset( &article.over, 0, sizeof( article.over ) );
+ article.text = text;
+ getOverInfo( &article.over );
+
+ if ( Db_contains( article.over.msgId ) )
+ {
+ Log_err( "Duplicate article %s.", article.over.msgId );
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/* Add the article to a group. */
+Bool
+Post_add ( const char * grp )
+{
+ Over * over;
+ const char *msgId;
+
+ over = new_Over( article.over.subject,
+ article.over.from,
+ article.over.date,
+ article.over.msgId,
+ article.over.ref,
+ article.over.bytes,
+ article.over.lines );
+
+ msgId = article.over.msgId;
+
+ Cont_read( grp );
+ Cont_app( over );
+ Log_dbg( "Added message '%s' to group '%s'.", msgId, grp );
+
+ if ( !article.posted )
+ {
+ Log_inf( "Added '%s' to database.", msgId );
+ if ( ! Db_prepareEntry( over, Cont_grp(), Cont_last() )
+ || ! Db_storeArt ( msgId, article.text ) )
+ return FALSE;
+ article.posted = TRUE;
+ }
+ else
+ {
+ Str t;
+ const char *xref;
+
+ xref = Db_xref( msgId );
+ Log_dbg( "Adding '%s' to Xref of '%s'", grp, msgId );
+ snprintf( t, MAXCHAR, "%s %s:%i", xref, grp, Ov_numb( over ) );
+ Db_setXref( msgId, t );
+ }
+
+ Cont_write();
+ Grp_setFirstLast( Cont_grp(), Cont_first(), Cont_last() );
+ return TRUE;
+}
+
+/* Done with article - tidy up. */
+void
+Post_close( void )
+{
+ article.text = NULL;
+ article.posted = FALSE;
+}
+
diff -r ab6cf19be6d3 -r 526a4c34ee2e post.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/post.h Sat Apr 29 15:45:56 2000 +0100
@@ -0,0 +1,28 @@
+/*
+ post.h
+
+ Take a single article received in its entirety without an overview
+ (i.e. received via at the server via a POST), and add it to the database
+ and (possibly multiple) group(s).
+
+ $Id: post.h 32 2000-04-29 14:45:56Z enz $
+*/
+
+#ifndef POST_H
+#define POST_H
+
+#include "common.h"
+
+/* Register an article for posting. */
+Bool
+Post_open( const char * text );
+
+/* Add the article to a group. */
+Bool
+Post_add ( const char * grp );
+
+/* Done with article - tidy up. */
+void
+Post_close( void );
+
+#endif
diff -r ab6cf19be6d3 -r 526a4c34ee2e pseudo.c
--- a/pseudo.c Sat Apr 29 14:37:59 2000 +0100
+++ b/pseudo.c Sat Apr 29 15:45:56 2000 +0100
@@ -1,7 +1,7 @@
/*
pseudo.c
- $Id: pseudo.c 3 2000-01-04 11:35:42Z enz $
+ $Id: pseudo.c 32 2000-04-29 14:45:56Z enz $
*/
#include "pseudo.h"
@@ -108,7 +108,7 @@
"\t[ optimized for low speed dial-up Internet connections. ]\n"
"\n"
"\t[ This group is presently not on the fetch list. You can ]\n"
- "\t[ put groups on the fetxh list by running the \"noffle\" ]\n"
+ "\t[ put groups on the fetch list by running the \"noffle\" ]\n"
"\t[ command on the computer where this server is running. ]\n"
"\n"
"\t[ If you have more questions about NOFFLE please talk ]\n"
diff -r ab6cf19be6d3 -r 526a4c34ee2e server.c
--- a/server.c Sat Apr 29 14:37:59 2000 +0100
+++ b/server.c Sat Apr 29 15:45:56 2000 +0100
@@ -1,7 +1,7 @@
/*
server.c
- $Id: server.c 18 2000-04-15 10:09:20Z enz $
+ $Id: server.c 32 2000-04-29 14:45:56Z enz $
*/
#include "server.h"
@@ -16,15 +16,18 @@
#include "common.h"
#include "config.h"
#include "content.h"
+#include "control.h"
#include "database.h"
#include "dynamicstring.h"
#include "fetch.h"
#include "fetchlist.h"
#include "group.h"
+#include "itemlist.h"
#include "lock.h"
#include "log.h"
#include "online.h"
#include "outgoing.h"
+#include "post.h"
#include "protocol.h"
#include "pseudo.h"
#include "request.h"
@@ -103,7 +106,7 @@
FetchMode mode;
Grp_setLastAccess( serv.grp, time( NULL ) );
- if ( Cfg_autoSubscribe() && ! Online_true() )
+ if ( ! Grp_local ( serv.grp ) && Cfg_autoSubscribe() && ! Online_true() )
{
Fetchlist_read();
if ( ! Fetchlist_contains( serv.grp ) )
@@ -219,7 +222,9 @@
{
Fetchlist_read();
Cont_read( name );
- if ( ! Fetchlist_contains( name ) && ! Online_true() )
+ if ( ! Grp_local ( name )
+ && ! Fetchlist_contains( name )
+ && ! Online_true() )
{
Pseudo_appGeneralInfo();
Grp_setFirstLast( name, Cont_first(), Cont_last() );
@@ -302,7 +307,7 @@
Str serv;
findServ( msgId, serv );
- if ( strcmp( serv, "(unknown)" ) == 0 )
+ if ( strcmp( serv, "(unknown)" ) == 0 || strcmp( serv, "(local)" ) == 0 )
return FALSE;
if ( ! Client_connect( serv ) )
return FALSE;
@@ -425,7 +430,9 @@
findServ( msgId, serv );
if ( Req_contains( serv, msgId ) )
putTxtBuf( Pseudo_alreadyMarkedBody() );
- else if ( strcmp( serv, "(unknown)" ) != 0 && Req_add( serv, msgId ) )
+ else if ( strcmp( serv, "(unknown)" ) != 0 &&
+ strcmp( serv, "(local)" ) != 0 &&
+ Req_add( serv, msgId ) )
putTxtBuf( Pseudo_markedBody() );
else
putTxtBuf( Pseudo_markingFailedBody() );
@@ -845,38 +852,133 @@
return TRUE;
}
+/* Cancel and return TRUE if need to send cancel message on to server. */
+static Bool
+controlCancel( const char *cancelId )
+{
+ return ( Ctrl_cancel( cancelId ) == CANCEL_NEEDS_MSG );
+}
+
/*
- Get first group of the Newsgroups field content, which is
- a comma separated list of groups.
-*/
-static void
-getFirstGrp( char *grpResult, const char *list )
+ It's a control message. Currently we only know about 'cancel'
+ messages; others are passed on for outside groups, and logged
+ as ignored for local groups.
+ */
+static Bool
+handleControl( ItemList *control, ItemList *newsgroups,
+ const char *msgId, const DynStr *art )
{
- Str t;
- const char *src = list;
- char *dest = t;
- while( TRUE )
+ const char *grp;
+ const char *op;
+ Bool err = FALSE;
+ Bool localDone = FALSE;
+
+ op = Itl_first( control );
+ if ( op == NULL )
+ {
+ Log_err( "Malformed control line." );
+ return TRUE;
+ }
+ else if ( strcasecmp( op, "cancel" ) == 0 )
+ {
+ if ( controlCancel( Itl_next( control ) ) )
+ localDone = TRUE;
+ else
+ return err;
+ }
+
+ /* Pass on for outside groups. */
+ for( grp = Itl_first( newsgroups );
+ grp != NULL;
+ grp = Itl_next( newsgroups ) )
+ {
+ if ( Grp_exists( grp ) && ! Grp_local( grp ) )
+ {
+ if ( ! Out_add( Grp_serv( grp ), msgId, art ) )
+ {
+ Log_err( "Cannot add posted article to outgoing directory" );
+ err = TRUE;
+ }
+ break;
+ }
+ }
+
+ if ( localDone )
+ return err;
+
+ /* Log 'can't do' for internal groups. */
+ for( grp = Itl_first( newsgroups );
+ grp != NULL;
+ grp = Itl_next( newsgroups ) )
{
- if ( *src == ',' )
- *dest = ' ';
- else
- *dest = *src;
- if ( *src == '\0' )
- break;
- ++src;
- ++dest;
+ if ( Grp_exists( grp ) && Grp_local( grp ) )
+ Log_inf( "Ignoring control '%s' for '%s'.", op, grp );
}
- *grpResult = '\0';
- sscanf( t, "%s", grpResult );
+
+ return err;
+}
+
+static Bool
+postArticle( ItemList *newsgroups, const char *msgId, const DynStr *art )
+{
+ const char *grp;
+ Bool err;
+ Bool oneLocal;
+
+ err = oneLocal = FALSE;
+
+ /* Run round first doing all local groups. */
+ for( grp = Itl_first( newsgroups );
+ grp != NULL;
+ grp = Itl_next( newsgroups ) )
+ {
+ if ( Grp_local( grp ) )
+ {
+ if ( ! oneLocal )
+ {
+ if ( ! Post_open( DynStr_str( art ) ) )
+ {
+ err = TRUE;
+ break;
+ }
+ else
+ oneLocal = TRUE;
+ }
+
+ if ( ! Post_add( grp ) )
+ err = TRUE;
+ }
+ }
+ if ( oneLocal )
+ Post_close();
+
+ /* Now look for a valid external group. */
+ for( grp = Itl_first( newsgroups );
+ grp != NULL;
+ grp = Itl_next( newsgroups ) )
+ {
+ if ( Grp_exists( grp ) && ! Grp_local( grp ) )
+ {
+ if ( ! Out_add( Grp_serv( grp ), msgId, art ) )
+ {
+ Log_err( "Cannot add posted article to outgoing directory" );
+ err = TRUE;
+ }
+ break;
+ }
+ }
+
+ return err;
}
static Bool
doPost( char *arg, const Cmd *cmd )
{
- Bool err, replyToFound, inHeader;
+ Bool err, replyToFound, dateFound, inHeader;
DynStr *s;
- Str line, field, val, msgId, from, grp;
+ Str line, field, val, msgId, from;
const char* p;
+ ItemList * newsgroups, *control;
/*
Get article and make following changes to the header:
@@ -895,8 +997,8 @@
s = new_DynStr( 10000 );
msgId[ 0 ] = '\0';
from[ 0 ] = '\0';
- grp[ 0 ] = '\0';
- replyToFound = FALSE;
+ newsgroups = control = NULL;
+ replyToFound = dateFound = FALSE;
inHeader = TRUE;
while ( getTxtLn( line, &err ) )
{
@@ -918,6 +1020,7 @@
else if ( msgId[ 0 ] == '\0' )
{
Prt_genMsgId( msgId, from, "NOFFLE" );
+
Log_inf( "Adding missing Message-ID '%s'", msgId );
}
else if ( ! Prt_isValidMsgId( msgId ) )
@@ -935,6 +1038,16 @@
DynStr_app( s, "Reply-To: " );
DynStr_appLn( s, from );
}
+ if ( ! dateFound )
+ {
+ time_t t;
+
+ time( &t );
+ strftime( val, MAXCHAR, "%d %b %Y %H:%M:%S %Z",
+ localtime( &t ) );
+ DynStr_app( s, "Date: " );
+ DynStr_appLn( s, val );
+ }
DynStr_appLn( s, p );
}
else if ( Prt_getField( field, val, p ) )
@@ -948,8 +1061,13 @@
}
else if ( strcmp( field, "newsgroups" ) == 0 )
{
- getFirstGrp( grp, val );
- Utl_toLower( grp );
+ Utl_toLower( val );
+ newsgroups = new_Itl ( val, " ," );
+ DynStr_appLn( s, p );
+ }
+ else if ( strcmp( field, "control" ) == 0 )
+ {
+ control = new_Itl ( val, " " );
DynStr_appLn( s, p );
}
else if ( strcmp( field, "reply-to" ) == 0 )
@@ -957,6 +1075,11 @@
replyToFound = TRUE;
DynStr_appLn( s, p );
}
+ else if ( strcmp( field, "date" ) == 0 )
+ {
+ dateFound = TRUE;
+ DynStr_appLn( s, p );
+ }
else if ( strcmp( field, "x-sender" ) == 0 )
{
DynStr_app( s, "X-NOFFLE-X-Sender: " );
@@ -975,21 +1098,41 @@
Log_err( "Posted message has no body" );
if ( ! err )
{
- if ( grp[ 0 ] == '\0' )
+ if ( newsgroups == NULL || Itl_count( newsgroups ) == 0 )
{
- Log_err( "Posted message has no Newsgroups header field" );
+ Log_err( "Posted message has no valid Newsgroups header field" );
err = TRUE;
}
- else if ( ! Grp_exists( grp ) )
- {
- Log_err( "Unknown group in Newsgroups header field" );
- err = TRUE;
- }
- else if ( ! Out_add( Grp_serv( grp ), msgId, s ) )
- {
- Log_err( "Cannot add posted article to outgoing directory" );
- err = TRUE;
- }
+ else
+ {
+ const char *grp;
+ Bool knownGrp = FALSE;
+
+ /* Check at least one group is known. */
+ for( grp = Itl_first( newsgroups );
+ grp != NULL;
+ grp = Itl_next( newsgroups ) )
+ {
+ if ( Grp_exists( grp ) )
+ {
+ knownGrp = TRUE;
+ break;
+ }
+ }
+
+ if ( ! knownGrp )
+ {
+
+ Log_err( "No known group in Newsgroups header field" );
+ err = TRUE;
+ }
+ else
+ {
+ err = ( control == NULL )
+ ? postArticle( newsgroups, msgId, s )
+ : handleControl( control, newsgroups, msgId, s );
+ }
+ }
}
if ( err )
putStat( STAT_POST_FAILED, "Posting failed" );
@@ -999,6 +1142,8 @@
if ( Online_true() )
postArts();
}
+ del_Itl( newsgroups );
+ del_Itl( control );
del_DynStr( s );
return TRUE;
}
diff -r ab6cf19be6d3 -r 526a4c34ee2e util.c
--- a/util.c Sat Apr 29 14:37:59 2000 +0100
+++ b/util.c Sat Apr 29 15:45:56 2000 +0100
@@ -1,7 +1,7 @@
/*
util.c
- $Id: util.c 3 2000-01-04 11:35:42Z enz $
+ $Id: util.c 32 2000-04-29 14:45:56Z enz $
*/
#include "util.h"
@@ -100,6 +100,38 @@
}
}
+const char *
+Utl_getHeaderLn( Str result, const char *p )
+{
+ const char * res = Utl_getLn( result, p );
+
+ /* Look for followon line if this isn't a blank line. */
+ if ( res != NULL && !isspace( result[ 0 ] ) )
+ while ( res != NULL && res[ 0 ] != '\n' && isspace( res[ 0 ] ) )
+ {
+ Str nextLine;
+ const char *here;
+ char *next;
+
+ here = res;
+ res = Utl_getLn( nextLine, res );
+ next = Utl_stripWhiteSpace( nextLine );
+
+ if ( next[ 0 ] != '\0' )
+ {
+ Utl_catStr( result, " " );
+ Utl_catStr( result, next );
+ }
+ else
+ {
+ res = here;
+ break;
+ }
+ }
+
+ return res;
+}
+
void
Utl_toLower( Str line )
{
@@ -139,11 +171,27 @@
void
Utl_cpyStrN( Str dst, const char *src, size_t n )
{
+ if ( n > MAXCHAR )
+ n = MAXCHAR;
dst[ 0 ] = '\0';
strncat( dst, src, n );
}
void
+Utl_catStr( Str dst, const char *src )
+{
+ strncat( dst, src, MAXCHAR - strlen( dst ) );
+}
+
+void
+Utl_catStrN( Str dst, const char *src, size_t n )
+{
+ if ( n > MAXCHAR - strlen( dst ) )
+ n = MAXCHAR - strlen( dst );
+ strncat( dst, src, n );
+}
+
+void
Utl_stamp( Str file )
{
FILE *f;
diff -r ab6cf19be6d3 -r 526a4c34ee2e util.h
--- a/util.h Sat Apr 29 14:37:59 2000 +0100
+++ b/util.h Sat Apr 29 15:45:56 2000 +0100
@@ -3,7 +3,7 @@
Miscellaneous helper functions.
- $Id: util.h 3 2000-01-04 11:35:42Z enz $
+ $Id: util.h 32 2000-04-29 14:45:56Z enz $
*/
#ifndef UTL_H
@@ -36,6 +36,13 @@
Utl_ungetLn( const char *str, const char *p );
/*
+ Read a header line from string. Reads continuation lines if
+ necessary. Return NULL if pos == NULL or no more line to read
+*/
+const char *
+Utl_getHeaderLn( Str result, const char *p );
+
+/*
Strip white spaces from left and right side.
Return pointer to new start. This is within line.
*/
@@ -56,6 +63,12 @@
void
Utl_cpyStrN( Str dst, const char *src, size_t n );
+void
+Utl_catStr( Str dst, const char *src );
+
+void
+Utl_catStrN( Str dst, const char *src, size_t n );
+
/* String allocation and copying. */
void
Utl_allocAndCpy( char **dst, const char *src );