Mercurial > noffle
changeset 26:526a4c34ee2e noffle
[svn] Applied patch from Jim Hague: support for local groups / new command
line options --create and --cancel.
author | enz |
---|---|
date | Sat, 29 Apr 2000 15:45:56 +0100 |
parents | ab6cf19be6d3 |
children | 2b79c0df1e69 |
files | CHANGELOG.html Makefile README.html control.c control.h database.c database.h group.c group.h itemlist.c itemlist.h noffle.1 noffle.c outgoing.c outgoing.h post.c post.h pseudo.c server.c util.c util.h |
diffstat | 21 files changed, 1007 insertions(+), 151 deletions(-) [+] |
line wrap: on
line diff
--- 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 @@ <hr> +<h2>Current developer version</h2> + +<ul> +<li> +Applied patch from Jim Hague: support for local groups / new command +line options --create and --cancel. +</ul> + <h2>Version 1.0pre5</h2> <ul>
--- 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))
--- 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 @@ <html> - <head> <title>NOFFLE</title> +<META NAME="description" CONTENT="NOFFLE is a news server for Linux that is optimized for low speed dialup connections to the Internet and few users. It can be used to add offline reading capability to news readers."> +<META NAME="keywords" CONTENT="noffle, news server, news reader, offline, modem, dialup, linux"> </head> <body bgcolor=white> @@ -15,7 +16,24 @@ <hr> <p> -<h2>Features</h2> +<ul> +<li> +<a href="#features">Features</a> +<li> +<a href="#compatibility">Compatibility with News Clients</a> +<li> +<a href="#getting">Getting NOFFLE</a> +<li> +<a href="#documentation">Documentation</a> +<li> +<a href="#links">Links</a> +</ul> + +<p> +<hr> +<p> + +<h2><a name="features">Features</a></h2> NOFFLE is a <a href=http://search.yahoo.com/bin/search?p=usenet>Usenet</a> @@ -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. -<p> -It allows reading news offline with many news reader programs, -even if they do not support offline reading by themselves. -<p> NOFFLE is written for the -<a href="http://www.linux.org/">Linux</a> -operating system and freely available under GPL -(see <a href="http://www.fsf.org/copyleft/gpl.html"> -http://www.fsf.org/copyleft/gpl.html</a>). +<a href=http://www.linux.org/>Linux </a> +operating system and freely available under +<a href=http://www.fsf.org/copyleft/gpl.html>GPL</a>. <p> While Online: <ul> @@ -45,107 +58,131 @@ <li> Allows reading news offline with many news clients, even if they do not support offline reading by themselves. -<p> +</li> <li> -Groups can be retrieved in different modes: +Groups can be retrieved in overview, full or thread mode. <ul> <li> 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. +</li> <li> -In full mode, the complete articles are fetched. +In full mode, complete articles are fetched at once. +</li> <li> -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). +</li> </ul> -<p> +</li> <li> The news feed is invoked automatically next online time by calling NOFFLE in the ip-up script. -<p> +</li> <li> 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. -</i> +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. +</li> +<li> +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. +</li> </ul> -<h2>Compatibility with News Clients</h2> +<h2><a name="compatibility">Compatibility with News Clients</a></h2> 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. <p> -Subscribing to groups in overview mode or thread mode puts some -requirements on the news reader program. See -<a href="NOTES.html">NOTES.txt</a> for compatibility notes. +Subscribing to groups in overview mode or thread mode requires the +following from the news reader program: +<p> +<ul> +<li> +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. +<p> +<li> +The reader should rarely open article bodies automatically, +because it will mark them unwantedly for download. +</ul> + <p> -<h2>Getting NOFFLE</h2> +<h2><a name="getting">Getting NOFFLE</a></h2> -NOFFLE can be downloaded from the NOFFLE homepage at -<a href="http://home.t-online.de/home/markus.enzenberger/noffle.html"> -http://home.t-online.de/home/markus.enzenberger/noffle.html</a>: +NOFFLE can be downloaded from this web site. <blockquote> -<tt> -<a href="http://home.t-online.de/home/markus.enzenberger/noffle-0.19.tar.gz"> -noffle-0.19.tar.gz</a> -</tt> -(current release, 25 Apr 1999) +<tt><a href=./noffle-1.0pre5.tar.gz>noffle-1.0pre5.tar.gz</a></tt> +(current release, 18 Apr 2000) <br> -<tt> -<a href=http://home.t-online.de/home/markus.enzenberger/noffle-0.17.tar.gz> -noffle-0.17.tar.gz</a> -</tt> -(last release, 25 Jan 1999) +<tt><a href=./noffle-1.0pre4.tar.gz>noffle-1.0pre4.tar.gz</a></tt> +(current release, 13 Nov 1999) </blockquote> -Read -<a href="http://home.t-online.de/home/markus.enzenberger/noffle_INSTALL.html"> -INSTALL.txt</a> -from the package for compiling and installing NOFFLE on your system. -<p> -RPM packages have been created by Mario Moder -(<a href="mailto:moderm@gmx.net">moderm@gmx.net</a>). They are available +RPM packages have been created by +<a href="mailto:moderm@gmx.net">Mario Moder</a>. They are available at <blockquote> -<a href="ftp://ftp.fbam.de/pub/linux/">ftp://ftp.fbam.de/pub/linux/</a> +<tt><a href="ftp://ftp.fbam.de/pub/linux/">ftp://ftp.fbam.de/pub/linux/</a></tt> </blockquote> <p> + +<img src="img_new.gif" width="32" height="16" alt="[NEW]"> +I moved Noffle to +<a href="http://sourceforge.net/">SourceForge</a> +recently. You can download files from the +<a href="http://sourceforge.net/project/?group_id=1044">Noffle project page</a>. +There is also a mailing list and a discussion forum. + +<h2><a name="documentation">Documentation</a></h2> + +Read the files README and INSTALL from the package +for compiling and installing NOFFLE on your system. +<p> +Some German documentation +(<img src="img_german.gif" width="16" height="14" alt="[Deutsch]"> +<a href="http://home.t-online.de/home/klaus.moedinger/noffle_install_de.html">"NOFFLE Installation"</a>) +is provided by +<a href="mailto:klaus.moedinger@t-online.de">Klaus Mödinger</a>. +<p> The current version is still beta. Please send bug reports, -comments and patches to me -(<a href="mailto:markus.enzenberger@t-online.de">markus.enzenberger@t-online.de</a>). +comments and patches to +<a href="mailto:markus.enzenberger@t-online.de"> +markus.enzenberger@t-online.de</a>. <p> -If you want to receive announcements about NOFFLE, I will put you on -my announcement list. Just send me a mail. -<h2>Links</h2> +<h2><a name="links">Links</a></h2> <ul> <li> -NNTP information -<br> -(<a href="http://www.tin.org/docs.html">http://www.tin.org/docs.html</a>) +<a href="http://www.tin.org/docs.html">NNTP information</a> <li> -Leafnode - news server similar to NOFFLE -<br> -(<a href="http://wpxx02.toxi.uni-wuerzburg.de/~krasel/leafnode.html">http://wpxx02.toxi.uni-wuerzburg.de/~krasel/leafnode.html</a>) +<a href="http://www.privat.kkf.net/~mark.bulmahn/ncontr.html">ncontr</a> +- graphical front-end for NOFFLE by +<a href="mailto:mbu@privat.kkf.net">Mark Bulmahn</a> <li> -SLRN - powerful news reader for Windows and Unix -<br> -(<a href="http://space.mit.edu/~davis/slrn.html">http://space.mit.edu/~davis/slrn.html</a>) +<a href="http://www.leafnode.org/"> +Leafnode</a> - news server similar to NOFFLE <li> -GNUS - Emacs news reader -<br> -(<a href="http://www.gnus.org/">http://www.gnus.org/</a> +<a href="http://space.mit.edu/~davis/slrn.html">SLRN</a> +- powerful news reader for Windows and Unix +<li> +<a href="http://www.gedanken.demon.co.uk/wwwoffle/index.html">WWWOFFLE</a> +- http proxy </ul> + <p> <hr> <small><i> -Last modified 5/99, +Last modified 4/2000, <a href="mailto:markus.enzenberger@t-online.de">Markus Enzenberger</a> </i></small>
--- /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 <stdio.h> +#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; +}
--- /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
--- 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
--- 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 );
--- 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 ) )
--- 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
--- /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 <ctype.h> +#include <string.h> +#include <stdlib.h> +#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; +}
--- /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 <sys/types.h> + +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
--- 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 <message id>|all .br .B noffle +\-c | \-\-cancel <message id> +.br +.B noffle +\-C | \-\-create <local newsgroup name> +.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 <message id> +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 <local newsgroup name> +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.
--- 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 <ctype.h> #include <errno.h> #include <getopt.h> #include <signal.h> @@ -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 <option>\n" "Option is one of the following:\n" " -a | --article <msg id>|all Show article(s) in database\n" + " -c | --cancel <msg id> Remove article from database\n" + " -C | --create <grp> Create a local group\n" " -d | --database Show content of article database\n" " -e | --expire <n> Expire articles older than <n> days\n" " -f | --fetch Get newsfeed from server/post articles\n" @@ -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;
--- 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; +} + + + + +
--- 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
--- /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 <string.h> +#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; +} +
--- /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
--- 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"
--- 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; }
--- 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;
--- 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 );