# HG changeset patch # User bears # Date 958652243 -3600 # Node ID 1fcdced0246efcbd808c187c2bf431ff7c822ebf # Parent bf8c97460fd787e9cadbd2e99f6dc4088e2633b7 [svn] Move posting code to post.c, add command line posting diff -r bf8c97460fd7 -r 1fcdced0246e docs/noffle.1 --- a/docs/noffle.1 Thu May 18 13:11:05 2000 +0100 +++ b/docs/noffle.1 Thu May 18 13:17:23 2000 +0100 @@ -1,5 +1,5 @@ .TH noffle 1 -.\" $Id: noffle.1 51 2000-05-05 23:49:38Z uh1763 $ +.\" $Id: noffle.1 100 2000-05-18 12:17:23Z bears $ .SH NAME noffle \- Usenet package optimized for dialup connections. @@ -48,6 +48,9 @@ \-o | \-\-offline .br .B noffle +\-p | \-\-post all|local +.br +.B noffle \-q | \-\-query groups|desc|times .br .B noffle @@ -219,6 +222,16 @@ .BR pppd (8). .TP +.B \-p, \-\-post all|local +Read a news article from standard input and post it. +.B all +permits posting to any group, local and external, but will +observe the group posting permission flags. +.B local +will only post to local groups, but ignores the group posting +permission flag. + +.TP .B \-q, \-\-query groups|desc|times Query information about all groups from the remote server and merge it to the diff -r bf8c97460fd7 -r 1fcdced0246e docs/noffle.conf.5 --- a/docs/noffle.conf.5 Thu May 18 13:11:05 2000 +0100 +++ b/docs/noffle.conf.5 Thu May 18 13:17:23 2000 +0100 @@ -1,5 +1,5 @@ .TH noffle.conf 5 -.\" $Id: noffle.conf.5 96 2000-05-17 10:51:22Z enz $ +.\" $Id: noffle.conf.5 100 2000-05-18 12:17:23Z bears $ .SH NAME noffle.conf \- Configuration file for NOFFLE news server @@ -38,6 +38,38 @@ after making changes to the server entries. .TP +.B getgroups [, ...] +Only retrieve from the most recently specified server newsgroups that match +the specified patterns. The patterns can contain \fIwildcards\fP, and +there can be multiple +.B getgroups +lines. For further details on +.B getgroups +processing in tandem with \fBomitgroups\fP, see the section on +.B omitgroups +following. +.br +Default: All groups + +.TP +.B omitgroups [, ...] +Don't retrieve from the most recently specified server newsgroups that match +the specified patterns. The patterns can contain \fIwildcards\fP, and +there can be multiple +.B omitgroups +lines. When processing a new newsgroup name, it is checked first to see +if it appears on the +.B getgroups +list (if any). If not, the group is rejected. +Then the group is checked to see if it appears on the +.B omitgroups +list. If it does not, the group is accepted as a group +.B NOFFLE +will list and collect as required. +.br +Default: No groups + +.TP .B max-fetch Never get more than articles. If there are more, the oldest ones are discarded. @@ -108,9 +140,14 @@ Default: yes .TP -.B remove-messageid yes|no -This option is deprecated and ignored. -It is only there for compatibility with old config-files. +.B post-locally yes|no +Place articles posted to external servers in the local aticle database +immediately. Some servers may rewrite Message-IDs, which will cause +duplicate postings of this option is enabled. Also, if for some reason +the post to the remote server fails, the article still exist in the local +database, which may be a source of some confusion. +.br +Default: no .TP .B replace-messageid yes|no diff -r bf8c97460fd7 -r 1fcdced0246e src/client.c --- a/src/client.c Thu May 18 13:11:05 2000 +0100 +++ b/src/client.c Thu May 18 13:17:23 2000 +0100 @@ -1,7 +1,7 @@ /* client.c - $Id: client.c 80 2000-05-13 15:36:35Z bears $ + $Id: client.c 100 2000-05-18 12:17:23Z bears $ */ #if HAVE_CONFIG_H @@ -359,6 +359,47 @@ } static Bool +isGetGroup( const char *name ) +{ + GroupEnum *ge; + Bool emptyList; + const char *pattern; + + emptyList = TRUE; + ge = new_GetGrEn( client.serv ); + while ( ( pattern = GrEn_next( ge ) ) != NULL ) + { + emptyList = FALSE; + if ( Wld_match( name, pattern ) ) + { + del_GrEn( ge ); + return TRUE; + } + } + + del_GrEn( ge ); + return emptyList; +} + +static Bool +isOmitGroup( const char *name ) +{ + GroupEnum *ge; + const char *pattern; + + ge = new_OmitGrEn( client.serv ); + while ( ( pattern = GrEn_next( ge ) ) != NULL ) + if ( Wld_match( name, pattern ) ) + { + del_GrEn( ge ); + return TRUE; + } + + del_GrEn( ge ); + return FALSE; +} + +static Bool isForbiddenGroupName( const char *name ) { size_t i; @@ -378,7 +419,7 @@ } static void -processGrps( void ) +processGrps( Bool noServerPattern ) { char postAllow; Bool err; @@ -398,6 +439,10 @@ Log_inf( "Group %s forbidden", grp ); continue; } + if ( noServerPattern && ! isGetGroup( grp ) ) + continue; + if ( isOmitGroup( grp ) ) + continue; if ( ! Grp_exists( grp ) ) { Log_inf( "Registering new group '%s'", grp ); @@ -445,32 +490,94 @@ client.in = client.out = NULL; } -Bool -Client_getGrps( void ) +static Bool +doGetGrps( const char *pattern, Bool *noServerPattern ) { - if ( ! putCmd( "LIST ACTIVE" ) ) + Str cmd; + int stat; + + Utl_cpyStr( cmd, "LIST ACTIVE" ); + if ( pattern[ 0 ] != '\0' ) + { + Utl_catStr( cmd, " " ); + Utl_catStr( cmd, pattern ); + } + + *noServerPattern = FALSE; + if ( ! putCmd( cmd ) ) return FALSE; - if ( getStat() != STAT_GRPS_FOLLOW ) + stat = getStat(); + if ( pattern[ 0 ] != '\0' && stat != STAT_GRPS_FOLLOW ) { - Log_err( "LIST ACTIVE command failed: %s", client.lastStat ); - return FALSE; + *noServerPattern = TRUE; + if ( ! putCmd( "LIST ACTIVE" ) ) + return FALSE; + stat = getStat(); + } + if ( stat != STAT_GRPS_FOLLOW ) + { + Log_err( "%s failed: %s", cmd, client.lastStat ); + return FALSE; } - processGrps(); + processGrps( *noServerPattern ); return TRUE; } Bool -Client_getDsc( void ) +Client_getGrps( void ) +{ + GroupEnum *ge; + const char *pattern; + Bool doneOne, noServerPattern, res; + + Log_inf( "Getting groups" ); + + doneOne = FALSE; + res = TRUE; + ge = new_GetGrEn( client.serv ); + while ( res && ( pattern = GrEn_next( ge ) ) != NULL ) + { + res = doGetGrps( pattern, &noServerPattern ); + doneOne = TRUE; + if ( noServerPattern ) + break; + } + + if ( ! doneOne ) + res = doGetGrps( "", &noServerPattern ); + + del_GrEn( ge ); + return res; +} + +static Bool +doGetDsc( const char *pattern, Bool *noServerPattern ) { Bool err; - Str name, line, dsc; + Str name, line, dsc, cmd; + int stat; + + Utl_cpyStr( cmd, "LIST NEWSGROUPS" ); + if ( pattern[ 0 ] != '\0' ) + { + Utl_catStr( cmd, " " ); + Utl_catStr( cmd, pattern ); + } - Log_inf( "Querying group descriptions" ); - if ( ! putCmd( "LIST NEWSGROUPS" ) ) + *noServerPattern = FALSE; + if ( ! putCmd( cmd ) ) return FALSE; - if ( getStat() != STAT_GRPS_FOLLOW ) + stat = getStat(); + if ( pattern[ 0 ] != '\0' && stat != STAT_GRPS_FOLLOW ) { - Log_err( "LIST NEWSGROUPS failed: %s", client.lastStat ); + *noServerPattern = TRUE; + if ( !putCmd( "LIST NEWSGROUPS" ) ) + return FALSE; + stat = getStat(); + } + if ( stat != STAT_GRPS_FOLLOW ) + { + Log_err( "%s failed: %s", cmd, client.lastStat ); return FALSE; } while ( getTxtLn( line, &err ) && ! err ) @@ -480,6 +587,8 @@ Log_err( "Unknown reply to LIST NEWSGROUPS: %s", line ); continue; } + if ( *noServerPattern && ! isGetGroup( name ) ) + continue; strcpy( dsc, Utl_restOfLn( line, 1 ) ); if ( Grp_exists( name ) ) { @@ -491,18 +600,61 @@ } Bool -Client_getCreationTimes( void ) +Client_getDsc( void ) +{ + GroupEnum *ge; + const char *pattern; + Bool doneOne, noServerPattern, res; + + Log_inf( "Querying group descriptions" ); + + doneOne = FALSE; + res = TRUE; + ge = new_GetGrEn( client.serv ); + while ( res && ( pattern = GrEn_next( ge ) ) != NULL ) + { + res = doGetDsc( pattern, &noServerPattern ); + doneOne = TRUE; + if ( noServerPattern ) + break; + } + + if ( ! doneOne ) + res = doGetDsc( "", &noServerPattern ); + + del_GrEn( ge ); + return res; +} + +static Bool +doGetCreationTimes( const char *pattern, Bool *noServerPattern ) { Bool err; - Str name, line; + Str name, line, cmd; time_t t; + int stat; + + Utl_cpyStr( cmd, "LIST ACTIVE.TIMES" ); + if ( pattern[ 0 ] != '\0' ) + { + Utl_catStr( cmd, " " ); + Utl_catStr( cmd, pattern ); + } - Log_inf( "Querying group creation times" ); - if ( ! putCmd( "LIST ACTIVE.TIMES" ) ) + *noServerPattern = FALSE; + if ( ! putCmd( cmd ) ) return FALSE; - if ( getStat() != STAT_GRPS_FOLLOW ) + stat = getStat(); + if ( pattern[ 0 ] != '\0' && stat != STAT_GRPS_FOLLOW ) { - Log_err( "LIST ACTIVE.TIMES failes: %s", client.lastStat ); + *noServerPattern= TRUE; + if ( ! putCmd( "LIST ACTIVE.TIMES" ) ) + return FALSE; + stat = getStat(); + } + if ( stat != STAT_GRPS_FOLLOW ) + { + Log_err( "%s failed: %s", cmd, client.lastStat ); return FALSE; } while ( getTxtLn( line, &err ) && ! err ) @@ -512,6 +664,8 @@ Log_err( "Unknown reply to LIST ACTIVE.TIMES: %s", line ); continue; } + if ( *noServerPattern && ! isGetGroup( name ) ) + continue; if ( Grp_exists( name ) ) { Log_inf( "Creation time of %s: %ld", name, t ); @@ -522,6 +676,33 @@ } Bool +Client_getCreationTimes( void ) +{ + GroupEnum *ge; + const char *pattern; + Bool doneOne, noServerPattern, res; + + Log_inf( "Querying group creation times" ); + + doneOne = FALSE; + res = TRUE; + ge = new_GetGrEn( client.serv ); + while ( res && ( pattern = GrEn_next( ge ) ) != NULL ) + { + res = doGetCreationTimes( pattern, &noServerPattern ); + doneOne = TRUE; + if ( noServerPattern ) + break; + } + + if ( ! doneOne ) + res = doGetCreationTimes( "", &noServerPattern ); + + del_GrEn( ge ); + return res; +} + +Bool Client_getNewgrps( const time_t *lastTime ) { Str s; @@ -535,14 +716,14 @@ (directly using %y in fmt string causes a Y2K compiler warning) */ p = s + 2; - if ( ! putCmd( "NEWGROUPS %s", p ) ) + if ( ! putCmd( "NEWGROUPS %s GMT", p ) ) return FALSE; if ( getStat() != STAT_NEW_GRP_FOLLOW ) { Log_err( "NEWGROUPS command failed: %s", client.lastStat ); return FALSE; } - processGrps(); + processGrps( TRUE ); return TRUE; } @@ -745,6 +926,8 @@ if ( ! parseOvLn( line, &rmtNumb, subj, from, date, msgId, ref, &bytes, &lines ) ) Log_err( "Bad overview line: %s", line ); + else if ( Cont_find( msgId ) >= 0 ) + Log_inf( "Already have '%s'", msgId ); else { ov = new_Over( subj, from, date, msgId, ref, bytes, lines ); diff -r bf8c97460fd7 -r 1fcdced0246e src/configfile.c --- a/src/configfile.c Thu May 18 13:11:05 2000 +0100 +++ b/src/configfile.c Thu May 18 13:17:23 2000 +0100 @@ -6,7 +6,7 @@ SPOOLDIR VERSION - $Id: configfile.c 88 2000-05-14 16:15:08Z bears $ + $Id: configfile.c 100 2000-05-18 12:17:23Z bears $ */ #if HAVE_CONFIG_H @@ -15,22 +15,40 @@ #include "configfile.h" +#include #include +#include "itemlist.h" #include "log.h" #include "util.h" #include "portable.h" typedef struct { - Str name; - Str user; - Str pass; + int numGroup; + int maxGroup; + char **groups; +} +GroupEntry; + +struct GroupEnum +{ + GroupEntry *groupEntry; + int groupIdx; +}; + +typedef struct +{ + char *name; + char *user; + char *pass; + GroupEntry getgroups; + GroupEntry omitgroups; } ServEntry; typedef struct { - Str pattern; + char *pattern; int days; } ExpireEntry; @@ -48,8 +66,8 @@ Bool autoSubscribe; Bool autoUnsubscribe; Bool infoAlways; - Bool removeMsgId; Bool replaceMsgId; + Bool postLocal; Str autoSubscribeMode; Str mailTo; int defaultExpire; @@ -72,8 +90,8 @@ FALSE, /* autoSubscribe */ FALSE, /* autoUnsubscribe */ TRUE, /* infoAlways */ - FALSE, /* removeMsgId */ TRUE, /* replaceMsgId */ + FALSE, /* postLocal */ "over", /* autoSubscribeMode */ "", /* mailTo */ 14, /* defaultExpire */ @@ -97,8 +115,8 @@ Bool Cfg_autoUnsubscribe( void ) { return config.autoUnsubscribe; } Bool Cfg_autoSubscribe( void ) { return config.autoSubscribe; } Bool Cfg_infoAlways( void ) { return config.infoAlways; } -Bool Cfg_removeMsgId( void ) { return config.removeMsgId; } Bool Cfg_replaceMsgId( void ) { return config.replaceMsgId; } +Bool Cfg_postLocal( void ) { return config.postLocal; } const char * Cfg_autoSubscribeMode( void ) { return config.autoSubscribeMode; } const char * Cfg_mailTo( void ) { return config.mailTo; } @@ -190,6 +208,67 @@ return config.expire[ config.expireIdx++ ].days; } +GroupEnum * +new_GetGrEn( const char *name ) +{ + GroupEnum *res; + int servIdx; + + res = (GroupEnum *) malloc( sizeof( GroupEnum ) ); + if ( res == NULL ) + { + Log_err( "Malloc of GroupEnum failed." ); + exit( EXIT_FAILURE ); + } + if ( ! searchServ( name, &servIdx ) ) + res->groupEntry = NULL; + else + res->groupEntry = &config.serv[ servIdx ].getgroups; + GrEn_first( res ); + return res; +} + +GroupEnum * +new_OmitGrEn( const char *name ) +{ + GroupEnum *res; + int servIdx; + + res = (GroupEnum *) malloc( sizeof( GroupEnum ) ); + if ( res == NULL ) + { + Log_err( "Malloc of GroupEnum failed." ); + exit( EXIT_FAILURE ); + } + if ( ! searchServ( name, &servIdx ) ) + res->groupEntry = NULL; + else + res->groupEntry = &config.serv[ servIdx ].omitgroups; + GrEn_first( res ); + return res; +} + +void +del_GrEn( GroupEnum *ge ) +{ + free(ge); +} + +void +GrEn_first( GroupEnum *ge ) +{ + ge->groupIdx = 0; +} + +const char * +GrEn_next( GroupEnum *ge ) +{ + if ( ge->groupEntry == NULL || + ge->groupIdx >= ge->groupEntry->numGroup ) + return NULL; + return ge->groupEntry->groups[ ge->groupIdx++ ]; +} + static void logSyntaxErr( const char *line ) { @@ -251,25 +330,28 @@ static void getServ( const char *line ) { - Str dummy; + Str dummy, name, user, pass; int r, len; ServEntry entry; - entry.user[ 0 ] = '\0'; - entry.pass[ 0 ] = '\0'; + memset( &entry, 0, sizeof( entry ) ); r = sscanf( line, "%s %s %s %s", - dummy, entry.name, entry.user, entry.pass ); + dummy, name, user, pass ); if ( r < 2 ) { logSyntaxErr( line ); return; } - len = strlen( entry.name ); + len = strlen( name ); /* To make server name more definit, it is made lowercase and port is removed, if it is the default port */ - if ( len > 4 && strcmp( entry.name + len - 4, ":119" ) == 0 ) - entry.name[ len - 4 ] = '\0'; - Utl_toLower( entry.name ); + if ( len > 4 && strcmp( name + len - 4, ":119" ) == 0 ) + name[ len - 4 ] = '\0'; + Utl_toLower( name ); + + Utl_allocAndCpy( &entry.name, name ); + Utl_allocAndCpy( &entry.user, user ); + Utl_allocAndCpy( &entry.pass, pass ); if ( config.maxServ < config.numServ + 1 ) { @@ -288,7 +370,7 @@ static void getExpire( const char *line ) { - Str dummy; + Str dummy, pattern; ExpireEntry entry; int days; @@ -296,7 +378,7 @@ The line is either "expire " or "expire ". The former updates the overall default. */ - if ( sscanf( line, "%s %s %d", dummy, entry.pattern, &days ) != 3 ) + if ( sscanf( line, "%s %s %d", dummy, pattern, &days ) != 3 ) { logSyntaxErr( line ); return; @@ -310,7 +392,8 @@ return; } - Utl_toLower( entry.pattern ); + Utl_toLower( pattern ); + Utl_allocAndCpy( &entry.pattern, pattern ); entry.days = days; if ( config.maxExpire < config.numExpire + 1 ) @@ -328,6 +411,58 @@ } } +static void +getGroups( char *line, Bool isGet ) +{ + const char *name; + ItemList *patterns; + const char *pattern; + + if ( config.numServ == 0 ) + { + Log_err( "No current server in %s", line ); + return; + } + + name = line; + /* Skip over name and terminate it */ + while ( line[ 0 ] != '\0' && ! isspace( line[ 0 ] ) ) + line++; + if ( line[ 0 ] == '\0' ) + { + logSyntaxErr( name ); + return; + } + line[ 0 ] = '\0'; + line++; + + patterns = new_Itl( line, " ," ); + for( pattern = Itl_first( patterns ); + pattern != NULL; + pattern = Itl_next( patterns ) ) + { + GroupEntry *g; + + if ( isGet ) + g = &config.serv[ config.numServ - 1 ].getgroups; + else + g = &config.serv[ config.numServ - 1 ].omitgroups; + if ( g->maxGroup < g->numGroup + 1 ) + { + if ( ! ( g->groups = realloc( g->groups, + ( g->maxGroup + 5 ) + * sizeof( char * ) ) ) ) + { + Log_err( "Could not realloc group list" ); + exit( EXIT_FAILURE ); + } + g->maxGroup += 5; + } + Utl_allocAndCpy( &g->groups[ g->numGroup++ ], pattern ); + } + del_Itl( patterns) ; +} + void Cfg_read( void ) { @@ -367,10 +502,10 @@ getBool( &config.autoUnsubscribe, p ); else if ( strcmp( "info-always-unread", name ) == 0 ) getBool( &config.infoAlways, p ); - else if ( strcmp( "remove-messageid", name ) == 0 ) - getBool( &config.removeMsgId, p ); else if ( strcmp( "replace-messageid", name ) == 0 ) getBool( &config.replaceMsgId, p ); + else if ( strcmp( "post-locally", name ) == 0 ) + getBool( &config.postLocal, p ); else if ( strcmp( "auto-subscribe-mode", name ) == 0 ) { getStr( s, p ); @@ -394,6 +529,10 @@ getStr( config.mailTo, p ); else if ( strcmp( "expire", name ) == 0 ) getExpire( p ); + else if ( strcmp( "getgroups", name ) == 0 ) + getGroups( p, TRUE ); + else if ( strcmp( "omitgroups", name ) == 0 ) + getGroups( p, FALSE ); else Log_err( "Unknown config option: %s", name ); } diff -r bf8c97460fd7 -r 1fcdced0246e src/configfile.h --- a/src/configfile.h Thu May 18 13:11:05 2000 +0100 +++ b/src/configfile.h Thu May 18 13:17:23 2000 +0100 @@ -3,7 +3,7 @@ Common declarations and handling of the configuration file. - $Id: configfile.h 96 2000-05-17 10:51:22Z enz $ + $Id: configfile.h 100 2000-05-18 12:17:23Z bears $ */ #ifndef CONFIGFILE_H @@ -15,6 +15,9 @@ #include "common.h" +struct GroupEnum; +typedef struct GroupEnum GroupEnum; + const char * Cfg_spoolDir( void ); const char * Cfg_version( void ); @@ -26,10 +29,8 @@ Bool Cfg_autoSubscribe( void ); Bool Cfg_infoAlways( void ); -/* Ignored. Should be removed in development version. */ -Bool Cfg_removeMsgId( void ); - Bool Cfg_replaceMsgId( void ); +Bool Cfg_postLocal( void ); const char * Cfg_autoSubscribeMode( void ); /* Can be: full, thread, over */ const char * Cfg_mailTo( void ); @@ -58,4 +59,24 @@ void Cfg_read( void ); +/* Get a new iterator for get group patterns for the given server */ +GroupEnum * +new_GetGrEn( const char *name ); + +/* Get a new iterator for omit group patterns for the given server */ +GroupEnum * +new_OmitGrEn( const char *name ); + +/* Free up a group enumerator */ +void +del_GrEn( GroupEnum * ge ); + +/* Rewind to first group */ +void +GrEn_first( GroupEnum * ge ); + +/* Get next group pattern or NULL if no more */ +const char * +GrEn_next( GroupEnum *ge ); + #endif diff -r bf8c97460fd7 -r 1fcdced0246e src/noffle.c --- a/src/noffle.c Thu May 18 13:11:05 2000 +0100 +++ b/src/noffle.c Thu May 18 13:17:23 2000 +0100 @@ -10,7 +10,7 @@ received for some seconds (to allow multiple clients connect at the same time). - $Id: noffle.c 67 2000-05-12 17:19:38Z enz $ + $Id: noffle.c 100 2000-05-18 12:17:23Z bears $ */ #if HAVE_CONFIG_H @@ -38,6 +38,7 @@ #include "online.h" #include "outgoing.h" #include "over.h" +#include "post.h" #include "pseudo.h" #include "util.h" #include "server.h" @@ -172,6 +173,33 @@ } } +static Bool +doPost( Bool localOnly ) +{ + Str line; + DynStr *s; + Bool res; + + + s = new_DynStr( 10000 ); + while ( fgets( line, MAXCHAR, stdin ) != NULL ) + DynStr_app( s, line ); + + res = TRUE; + if ( ! Post_open( DynStr_str( s ) ) ) + { + fprintf( stderr, "Post failed: Malformed article.\n" ); + res = FALSE; + } + else if ( ! Post_post( localOnly ) ) + { + fprintf( stderr, "Post failed: Can't post to group.\n" ); + res = FALSE; + } + Post_close(); + return res; +} + static void doQuery( void ) { @@ -498,6 +526,8 @@ " -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" + " -p | --post all Post article on stdin to all groups\n" + " -p | --post local Post article on stdin to local groups\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" @@ -619,6 +649,7 @@ { "modify", required_argument, NULL, 'm' }, { "offline", no_argument, NULL, 'o' }, { "online", no_argument, NULL, 'n' }, + { "post", required_argument, NULL, 'p' }, { "query", required_argument, NULL, 'q' }, { "server", no_argument, NULL, 'r' }, { "requested", no_argument, NULL, 'R' }, @@ -637,7 +668,7 @@ signal( SIGINT, logSignal ); signal( SIGTERM, logSignal ); signal( SIGPIPE, logSignal ); - c = getopt_long( argc, argv, "a:c:C:dD:efghlm:onq:rRs:S:t:u:v", + c = getopt_long( argc, argv, "a:c:C:dD:efghlm:onp:q:rRs:S:t:u:v", longOptions, NULL ); if ( ! initNoffle( c != 'r' ) ) return EXIT_FAILURE; @@ -724,6 +755,28 @@ else Online_set( FALSE ); break; + case 'p': + if ( ! optarg ) + { + fprintf( stderr, "Option -p needs argument.\n" ); + result = EXIT_FAILURE; + } + else + { + Bool local; + + local = FALSE; + if ( strcmp( optarg, "local" ) == 0 ) + local = TRUE; + else if ( strcmp( optarg, "all" ) != 0 ) + { + fprintf( stderr, "Unknown argument -p %s\n", optarg ); + result = EXIT_FAILURE; + } + if ( ! doPost( local ) ) + result = EXIT_FAILURE; + } + break; case 'q': if ( ! optarg ) { diff -r bf8c97460fd7 -r 1fcdced0246e src/post.c --- a/src/post.c Thu May 18 13:11:05 2000 +0100 +++ b/src/post.c Thu May 18 13:17:23 2000 +0100 @@ -1,7 +1,7 @@ /* post.c - $Id: post.c 70 2000-05-12 17:35:00Z enz $ + $Id: post.c 100 2000-05-18 12:17:23Z bears $ */ #if HAVE_CONFIG_H @@ -12,10 +12,14 @@ #include "post.h" #include #include "common.h" +#include "configfile.h" #include "content.h" +#include "control.h" #include "database.h" #include "group.h" +#include "itemlist.h" #include "log.h" +#include "outgoing.h" #include "over.h" #include "protocol.h" #include "util.h" @@ -34,80 +38,24 @@ struct Article { - const char * text; - Bool posted; + DynStr *text; /* Processed article text */ + ItemList *newsgroups; /* Newsgroups for dispatch */ + ItemList *control; /* Control message? NULL if not */ + Bool posted; /* Has it been put in the article database? */ + const char *server; /* Server for external post */ struct OverInfo over; }; -static struct Article article = { NULL, FALSE, { "", "", "", "", "", 0, 0 } }; - -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; -} - +static struct Article article = { NULL, NULL, NULL, FALSE, NULL, + { "", "", "", "", "", 0, 0 } }; /* Add the article to a group. */ -Bool -Post_add ( const char * grp ) +static Bool +addToGroup( const char * grp ) { Over * over; const char *msgId; - + over = new_Over( article.over.subject, article.over.from, article.over.date, @@ -126,7 +74,7 @@ { Log_inf( "Added '%s' to database.", msgId ); if ( ! Db_prepareEntry( over, Cont_grp(), Cont_last() ) - || ! Db_storeArt ( msgId, article.text ) ) + || ! Db_storeArt ( msgId, DynStr_str( article.text ) ) ) return FALSE; article.posted = TRUE; } @@ -145,12 +93,347 @@ Grp_setFirstLast( Cont_grp(), Cont_first(), Cont_last() ); return TRUE; } + +static Bool +checkPostableNewsgroup( Bool localOnly ) +{ + const char *grp; + Bool knownGrp = FALSE; + Bool postAllowedGrp = FALSE; + Bool local; + + /* + Check at least one group is known. Look for external group and + set server. + */ + article.server = NULL; + for( grp = Itl_first( article.newsgroups ); + grp != NULL; + grp = Itl_next( article.newsgroups ) ) + { + if ( Grp_exists( grp ) ) + { + local = Grp_local( grp ); + if ( localOnly && ! local ) + continue; + knownGrp = TRUE; + switch( Grp_postAllow( grp ) ) + { + case 'n': + if ( localOnly ) + postAllowedGrp = TRUE; + break; + case 'y': + postAllowedGrp = TRUE; + break; + default: + if ( localOnly ) + postAllowedGrp = TRUE; + else + /* Can't post to moderated local groups. */ + postAllowedGrp = ! local; + break; + } + if ( postAllowedGrp && ! local && article.server == NULL ) + article.server = Grp_server( grp ); + if ( postAllowedGrp && article.server != NULL ) + break; + } + } + + if ( ! knownGrp ) + { + Log_err( "No known group in Newsgroups header field" ); + return FALSE; + } + else if ( ! postAllowedGrp ) + { + Log_err( "No group permits posting" ); + return FALSE; + } + + return TRUE; +} + +/* Get article text, check for validity & build overview. */ +static Bool +getArticleText( const char *p ) +{ + DynStr * s; + Str line, field, value; + Bool replyToFound; + + s = new_DynStr( 10000 ); + article.text = s; + + memset( &article.over, 0, sizeof( article.over ) ); + replyToFound = FALSE; + + /* Grab header lines first, getting overview info as we go. */ + while ( ( p = Utl_getHeaderLn( line, p ) ) != NULL + && line[ 0 ] != '\0' + && Prt_getField( field, value, line ) ) + { + /* Look for headers we need to stash. */ + if ( strcmp( field, "subject" ) == 0 ) + { + Utl_cpyStr( article.over.subject, value ); + DynStr_appLn( s, line ); + } + else if ( strcmp ( field, "from" ) == 0 ) + { + Utl_cpyStr( article.over.from, value ); + DynStr_appLn( s, line ); + } + else if ( strcmp ( field, "date" ) == 0 ) + { + Utl_cpyStr( article.over.date, value ); + DynStr_appLn( s, line ); + } + else if ( strcmp ( field, "references" ) == 0 ) + { + Utl_cpyStr( article.over.ref, value ); + DynStr_appLn( s, line ); + } + else if ( strcmp ( field, "message-id" ) == 0 ) + Utl_cpyStr( article.over.msgId, value ); + else if ( strcmp ( field, "newsgroups" ) == 0 ) + { + article.newsgroups = new_Itl( value, " ,\n\t" ); + DynStr_appLn( s, line ); + } + else if ( strcmp ( field, "control" ) == 0 ) + { + article.control = new_Itl( value, " " ); + DynStr_appLn( s, line ); + } + else if ( strcmp ( field, "reply-to" ) == 0 ) + { + replyToFound = TRUE; + DynStr_appLn( s, line ); + } + else if ( strcmp ( field, "x-sender" ) == 0 ) + { + DynStr_app( s, "X-NOFFLE-X-Sender: " ); + DynStr_appLn( s, value ); + } + else if ( strcmp ( field, "xref" ) == 0 ) + Log_inf( "Xref header in post ignored" ); + else + DynStr_appLn( s, line ); + } + + /* Now sort header-related issues */ + if ( article.over.from[ 0 ] == '\0' ) + { + Log_err( "Posted message has no From field" ); + return FALSE; + } + if ( article.over.subject[ 0 ] == '\0' ) + { + Log_err( "Posted message has no Subject field" ); + return FALSE; + } + if ( article.newsgroups == NULL || Itl_count( article.newsgroups) == 0 ) + { + Log_err( "Posted message has no valid Newsgroups field" ); + return FALSE; + } + if ( Cfg_replaceMsgId() ) + { + Prt_genMsgId( article.over.msgId, article.over.from, "NOFFLE" ); + Log_dbg( "Replacing Message-ID with '%s'", article.over.msgId ); + } + else if ( article.over.msgId[ 0 ] == '\0' ) + { + Prt_genMsgId( article.over.msgId, article.over.from, "NOFFLE" ); + Log_inf( "Adding missing Message-ID '%s'", article.over.msgId ); + } + else if ( ! Prt_isValidMsgId( article.over.msgId ) ) + { + Log_ntc( "Replacing invalid Message-ID '%s'", article.over.msgId ); + Prt_genMsgId( article.over.msgId, article.over.from, "NOFFLE" ); + } + DynStr_app( s, "Message-ID: " ); + DynStr_appLn( s, article.over.msgId ); + if ( ! replyToFound ) + { + Log_dbg( "Adding Reply-To field to posted message." ); + DynStr_app( s, "Reply-To: " ); + DynStr_appLn( s, article.over.from ); + } + if ( article.over.date[ 0 ] == '\0' ) + { + time_t t; + + time( &t ); + Utl_rfc822Date( t, article.over.date ); + DynStr_app( s, "Date: " ); + DynStr_appLn( s, article.over.date ); + } + if ( p == NULL || p[ 0 ] == '\0' ) + { + Log_err( "Posted message has no body" ); + return FALSE; + } + + /* Add the empty line separating header and body */ + DynStr_appLn( s, "" ); + + /* Now pop on the rest of the body and count the lines & bytes */ + DynStr_app( s, p ); + for ( p++, article.over.lines = 0; *p != '\0'; p++ ) + if ( *p == '\n' ) + article.over.lines++; + article.over.bytes = DynStr_len( s ); + + return TRUE; +} + +/* Add article to outgoing if needs be */ +static Bool +postExternal( void ) +{ + if ( article.server == NULL ) + return TRUE; + + if ( ! Out_add( article.server, article.over.msgId, article.text ) ) + { + Log_err( "Cannot add posted article to outgoing directory" ); + return FALSE; + } + + 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 ); +} + +/* + 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( void ) +{ + const char *grp; + const char *op; + + op = Itl_first( article.control ); + if ( op == NULL ) + { + Log_err( "Malformed control line." ); + return TRUE; + } + else if ( strcasecmp( op, "cancel" ) == 0 ) + { + if ( ! controlCancel( Itl_next( article.control ) ) ) + return TRUE; /* Handled entirely locally */ + } + else + { + /* Log 'can't do' for internal groups. */ + for( grp = Itl_first( article.newsgroups ); + grp != NULL; + grp = Itl_next( article.newsgroups ) ) + { + if ( Grp_exists( grp ) && Grp_local( grp ) ) + Log_inf( "Ignoring control '%s' for '%s'.", op, grp ); + } + } + + return postExternal(); +} + +static Bool +postArticle( Bool localOnly ) +{ + const char *grp; + Bool err; + Bool postLocal; + Bool local; + + err = FALSE; + postLocal = Cfg_postLocal(); + + /* Run round & post locally */ + for( grp = Itl_first( article.newsgroups ); + grp != NULL; + grp = Itl_next( article.newsgroups ) ) + { + local = Grp_local( grp ); + if ( localOnly && ! local ) + continue; + if ( ( local || postLocal ) + && ( Grp_postAllow( grp ) == 'y' || localOnly ) ) + err = addToGroup( grp ) && err; + } + + if ( localOnly ) + return err; + else + return postExternal() && err; +} + +/* Register an article for posting. */ +Bool +Post_open( const char * text ) +{ + if ( article.text != NULL ) + { + Log_err( "Busy article in Post_open." ); + return FALSE; + } + + if ( ! getArticleText( text ) ) + return FALSE; + + if ( Db_contains( article.over.msgId ) ) + { + Log_err( "Duplicate article %s.", article.over.msgId ); + return FALSE; + } + + return TRUE; +} + +/* Process the posting */ +Bool +Post_post( Bool localOnly ) +{ + if ( ! checkPostableNewsgroup( localOnly ) ) + return FALSE; + + return ( article.control == NULL ) + ? ! postArticle( localOnly ) + : ! handleControl(); +} /* Done with article - tidy up. */ void Post_close( void ) { - article.text = NULL; + if ( article.text != NULL ) + { + del_DynStr( article.text ); + article.text = NULL; + } + if ( article.newsgroups != NULL ) + { + del_Itl( article.newsgroups ); + article.newsgroups = NULL; + } + if ( article.control != NULL ) + { + del_Itl( article.control ); + article.control = NULL; + } article.posted = FALSE; + article.server = NULL; } diff -r bf8c97460fd7 -r 1fcdced0246e src/post.h --- a/src/post.h Thu May 18 13:11:05 2000 +0100 +++ b/src/post.h Thu May 18 13:17:23 2000 +0100 @@ -1,11 +1,11 @@ /* 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). + Take the text of a single article, parse it and add/amend headers as + necessary, and add to the local database and/or queue for external + posting as appropriate. - $Id: post.h 51 2000-05-05 23:49:38Z uh1763 $ + $Id: post.h 100 2000-05-18 12:17:23Z bears $ */ #ifndef POST_H @@ -21,9 +21,12 @@ Bool Post_open( const char * text ); -/* Add the article to a group. */ +/* + Post the article. If localOnly, article is only posted to local + groups, and is posted regardless of group posting status. + */ Bool -Post_add ( const char * grp ); +Post_post( Bool localOnly ); /* Done with article - tidy up. */ void diff -r bf8c97460fd7 -r 1fcdced0246e src/server.c --- a/src/server.c Thu May 18 13:11:05 2000 +0100 +++ b/src/server.c Thu May 18 13:17:23 2000 +0100 @@ -1,7 +1,7 @@ /* server.c - $Id: server.c 96 2000-05-17 10:51:22Z enz $ + $Id: server.c 100 2000-05-18 12:17:23Z bears $ */ #if HAVE_CONFIG_H @@ -31,7 +31,6 @@ #include "common.h" #include "configfile.h" #include "content.h" -#include "control.h" #include "database.h" #include "dynamicstring.h" #include "fetch.h" @@ -41,7 +40,6 @@ #include "lock.h" #include "log.h" #include "online.h" -#include "outgoing.h" #include "over.h" #include "post.h" #include "protocol.h" @@ -959,316 +957,35 @@ 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 ); -} - -/* - 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 ) -{ - 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_server( 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 ( Grp_exists( grp ) && Grp_local( grp ) ) - Log_inf( "Ignoring control '%s' for '%s'.", op, grp ); - } - - 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_server( 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, dateFound, inHeader; DynStr *s; - Str line, field, val, msgId, from; - const char* p; - ItemList * newsgroups, *control; + Str line; + Bool err; UNUSED(arg); UNUSED(cmd); - /* - Get article and make following changes to the header: - - add/replace/cut Message-ID depending on config options - - add Reply-To with content of From, if missing - (some providers overwrite From field) - - rename X-Sender header to X-NOFFLE-X-Sender - (some providers want to insert their own X-Sender) - - For doing this, it is not necessary to parse multiple-line - headers. - */ putStat( STAT_SEND_ART, "Continue (end with period)" ); fflush( stdout ); Log_dbg( "[S FLUSH]" ); s = new_DynStr( 10000 ); - msgId[ 0 ] = '\0'; - from[ 0 ] = '\0'; - newsgroups = control = NULL; - replyToFound = dateFound = FALSE; - inHeader = TRUE; - while ( getTxtLn( line, &err ) ) - { - if ( inHeader ) - { - p = Utl_stripWhiteSpace( line ); - if ( *p == '\0' ) - { - inHeader = FALSE; - if ( from[ 0 ] == '\0' ) - Log_err( "Posted message has no From field" ); - if ( Cfg_replaceMsgId() ) - { - Prt_genMsgId( msgId, from, "NOFFLE" ); - Log_dbg( "Replacing Message-ID with '%s'", msgId ); - } - else if ( msgId[ 0 ] == '\0' ) - { - Prt_genMsgId( msgId, from, "NOFFLE" ); - - Log_inf( "Adding missing Message-ID '%s'", msgId ); - } - else if ( ! Prt_isValidMsgId( msgId ) ) - { - Log_ntc( "Replacing invalid Message-ID with '%s'", - msgId ); - Prt_genMsgId( msgId, from, "NOFFLE" ); - } - DynStr_app( s, "Message-ID: " ); - DynStr_appLn( s, msgId ); - if ( ! replyToFound && from[ 0 ] != '\0' ) - { - Log_dbg( "Adding Reply-To field to posted message." ); - DynStr_app( s, "Reply-To: " ); - DynStr_appLn( s, from ); - } - if ( ! dateFound ) - { - time_t t; + err = FALSE; + while ( ! err && getTxtLn( line, &err ) ) + DynStr_appLn( s, line ); - time( &t ); - Utl_rfc822Date( t, val ); - DynStr_app( s, "Date: " ); - DynStr_appLn( s, val ); - } - DynStr_appLn( s, p ); - } - else if ( Prt_getField( field, val, p ) ) - { - if ( strcmp( field, "message-id" ) == 0 ) - strcpy( msgId, val ); - else if ( strcmp( field, "from" ) == 0 ) - { - strcpy( from, val ); - DynStr_appLn( s, p ); - } - else if ( strcmp( field, "newsgroups" ) == 0 ) - { - 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 ) - { - 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: " ); - DynStr_appLn( s, val ); - } - else - DynStr_appLn( s, p ); - } - else - DynStr_appLn( s, line ); - } - else - DynStr_appLn( s, line ); - } - if ( inHeader ) - Log_err( "Posted message has no body" ); - if ( ! err ) - { - if ( newsgroups == NULL || Itl_count( newsgroups ) == 0 ) - { - Log_err( "Posted message has no valid Newsgroups header field" ); - err = TRUE; - } - else - { - const char *grp; - Bool knownGrp = FALSE; - Bool postAllowedGrp = 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; - 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; - } - } - - if ( ! knownGrp ) - { - - 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 ) - ? postArticle( newsgroups, msgId, s ) - : handleControl( control, newsgroups, msgId, s ); - } - } - } - if ( err ) - putStat( STAT_POST_FAILED, "Posting failed" ); - else + if ( ! err + && Post_open( DynStr_str( s ) ) + && Post_post( FALSE ) ) { putStat( STAT_POST_OK, "Message posted" ); if ( Online_true() ) postArts(); } - del_Itl( newsgroups ); - del_Itl( control ); + else + putStat( STAT_POST_FAILED, "Posting failed" ); + Post_close(); del_DynStr( s ); return TRUE; }