Mercurial > noffle
view src/post.c @ 191:28488e0e3630 noffle
[svn] * src/group.h,src/group.c,src/noffle.c,src/server.c: Grp_setLastAccess is
only ever called with last param as time(NULL), so remove it and call
time() inside the implementation of Grp_setLastAccess.
* src/client.c,src/group.h,src/group.c,src/noffle.c,src/post.c: Groups are
automatically unsubscribed when the last access to the group is older
than a particular threshold. However, for very low traffic groups, the
last access may exceed the threshold simply because there has been no new
article posted. In this case, rather than unsubscribe, update the group
last access time. This means that groups are now only unsubscribed if
the last access exceeds the threshold AND articles have arrived in the
group since. Add Grp_setLastPostTime() to track the last time an article
arrived in the group.
author | bears |
---|---|
date | Sat, 20 Oct 2001 14:23:46 +0100 |
parents | fed1334d766b |
children | 24d4cd032da5 |
line wrap: on
line source
/* post.c $Id: post.c 310 2001-10-20 13:23:46Z bears $ */ #if HAVE_CONFIG_H #include <config.h> #endif #include <errno.h> #include <pwd.h> #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include "post.h" #include <string.h> #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" #include "portable.h" #define BEGIN_SIG "-- " #define SIG_FILE "/.signature" struct OverInfo { Str subject; Str from; Str date; Str msgId; Str ref; size_t bytes; size_t lines; }; struct Article { DynStr *text; /* Processed article text */ ItemList *newsgroups; /* Newsgroups for dispatch */ ItemList *control; /* Control message? NULL if not */ Bool approved; /* Has Approved: header? */ Bool posted; /* Has it been put in the article database? */ int flags; /* Posting flags */ struct OverInfo over; }; static struct Article article = { NULL, NULL, NULL, FALSE, FALSE, 0, { "", "", "", "", "", 0, 0 } }; /* Add the article to a group. */ static Bool addToGroup( 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( LOG_DBG_POST, "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, DynStr_str( article.text ) ) ) return FALSE; article.posted = TRUE; } else { Str t; const char *xref; xref = Db_xref( msgId ); Log_dbg( LOG_DBG_POST, "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() ); Grp_setLastPostTime( Cont_grp() ); return TRUE; } static Bool checkPostableNewsgroup( void ) { const char * grp; Bool knownGrp = FALSE; Bool postAllowedGrp = TRUE; Bool local; /* * Check all known groups are writeable, and there is * at least one known group. */ for( grp = Itl_first( article.newsgroups ); postAllowedGrp && grp != NULL; grp = Itl_next( article.newsgroups ) ) { if ( Grp_exists( grp ) ) { local = Grp_local( grp ); knownGrp = TRUE; switch( Grp_postAllow( grp ) ) { case 'n': postAllowedGrp = FALSE; break; case 'y': break; case 'm': /* * Can post to moderated groups if *either* * 1. Group is local and article approved, or * 2. Group is external */ postAllowedGrp = ! local || article.approved; break; default: /* * Unknown mode for local groups. Forward * to server for external groups; presumably the * server knows what to do. */ postAllowedGrp = ! local; break; } } } if ( ! knownGrp ) { Log_err( "No known group in Newsgroups header field" ); return FALSE; } else if ( ! postAllowedGrp ) { Log_err( "A group does not permit 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, pathFound, orgFound; time_t t; int sigLines; s = new_DynStr( 10000 ); article.text = s; memset( &article.over, 0, sizeof( article.over ) ); replyToFound = pathFound = orgFound = 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 ); 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, "approved" ) == 0 ) { article.approved = TRUE; DynStr_appLn( s, line ); } else if ( strcmp ( field, "path" ) == 0 ) { pathFound = TRUE; DynStr_appLn( s, line ); } else if ( strcmp ( field, "organization" ) == 0 ) { orgFound = 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' ) { if ( article.flags & POST_ADD_FROM ) { Log_dbg( LOG_DBG_POST, "Adding From field to posted message." ); DynStr_app( s, "From: " ); if ( ! Prt_genFromHdr( article.over.from ) ) { Log_err( "Can't generate From field" ); return FALSE; } DynStr_appLn( s, article.over.from ); } else { 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; } /* Ensure correctly formatted date */ t = Utl_parseNewsDate( article.over.date ); if ( t == (time_t) -1 ) { time( &t ); Utl_newsDate( t, article.over.date ); } DynStr_app( s, "Date: " ); DynStr_appLn( s, article.over.date ); /* Ensure Message ID is present and valid */ if ( Cfg_replaceMsgId() ) { Prt_genMsgId( article.over.msgId, article.over.from, "NOFFLE" ); Log_dbg( LOG_DBG_POST, "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 ); } DynStr_app( s, "Message-ID: " ); DynStr_appLn( s, article.over.msgId ); /* Ensure Path header */ if ( ! pathFound ) { Str path; Log_dbg( LOG_DBG_POST, "Adding Path field to posted message." ); DynStr_app( s, "Path: " ); Utl_cpyStr( path, Cfg_pathHeader() ); if ( path[ 0 ] == '\0' ) Prt_genPathHdr( path, article.over.from ); DynStr_appLn( s, path ); } /* Ensure Reply-To header */ if ( ! replyToFound ) { Log_dbg( LOG_DBG_POST, "Adding Reply-To field to posted message." ); DynStr_app( s, "Reply-To: " ); DynStr_appLn( s, article.over.from ); } /* Ensure Organization header if required */ if ( ( ! orgFound ) && ( article.flags & POST_ADD_ORG ) ) { Str org; Utl_cpyStr( org, Cfg_organization() ); if ( org[ 0 ] != '\0' ) { Log_dbg( LOG_DBG_POST, "Adding Organization field to posted message." ); DynStr_app( s, "Organization: " ); DynStr_appLn( s, org ); } } /* OK, header ready to roll. Something to accompany it? */ 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 */ DynStr_app( s, p ); /* Add a signature if requested to do so and if one found. */ sigLines = 0; if ( article.flags & POST_ADD_SIG ) { Str sigfile; struct passwd *pwd; FILE *f; /* Generate sig file path */ pwd = getpwuid( getuid() ); Utl_cpyStr( sigfile, pwd->pw_dir ); Utl_catStr( sigfile, SIG_FILE ); f = fopen( sigfile, "r" ); if ( f == NULL ) { /* If err is ENOENT, file doesn't exist. This is OK. */ if ( errno != ENOENT ) { Log_err( "Can't access .signature file (%s), " "article not posted.", strerror( errno ) ); return FALSE; } } else { /* OK, try to add it. */ Str sline; Log_dbg( LOG_DBG_POST, "Adding .signature to posted message." ); DynStr_appLn( s, BEGIN_SIG ); sigLines++; while ( Prt_getLn( sline, f, 0 ) ) { DynStr_appLn( s, sline ); sigLines++; } if ( ferror( f ) ) { Log_err( "Error reading .signature file (%s), " "article not posted.", strerror( errno ) ); fclose( f ); return FALSE; } fclose( f ); } } /* * Count the lines & bytes. This counts the original number of * lines in the supplied body, so add in the number of signature * lines added, including the separator. */ for ( p++, article.over.lines = sigLines; *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 ) { const char * grp; Str serversSeen; Bool err; /* * For each external group, send to that group's server if it has * not seen the post already. */ serversSeen[ 0 ] = '\0'; err = FALSE; for ( grp = Itl_first( article.newsgroups ); grp != NULL; grp = Itl_next( article.newsgroups ) ) { if ( Grp_exists( grp ) && ! Grp_local( grp ) ) { const char * servName = Grp_server( grp ); if ( strstr( serversSeen, servName ) != NULL ) continue; if ( ! Out_add( servName, article.over.msgId, article.text ) ) { Log_err( "Cannot add posted article to outgoing directory" ); err = TRUE; } Utl_catStr( serversSeen, " " ); Utl_catStr( serversSeen, servName ); } } return err; } /* 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( void ) { const char *grp; Bool err; Bool local; Bool postLocal; err = FALSE; postLocal = Cfg_postLocal(); /* * Run round first doing all local groups. * Remember, we've already checked it is OK to post to them all. */ for( grp = Itl_first( article.newsgroups ); grp != NULL; grp = Itl_next( article.newsgroups ) ) { local = Grp_local( grp ); /* * Only post locally to external group if that group's post * status is 'y'. Otherwise retrieve from upstream server - * for example, we don't want to immediately post locally articles * destined for the moderator of a moderated group. */ if ( ! local && ( ! postLocal || Grp_postAllow( grp ) != 'y' ) ) continue; err = addToGroup( grp ) && err; } return postExternal() && err; } /* Register an article for posting. */ Bool Post_open( const char * text, unsigned flags ) { if ( article.text != NULL ) { Log_err( "Busy article in Post_open." ); return FALSE; } article.flags = flags; if ( ! getArticleText( text ) ) return FALSE; if ( Db_contains( article.over.msgId ) ) { Post_close(); Log_err( "Duplicate article %s.", article.over.msgId ); return FALSE; } return TRUE; } /* Process the posting */ Bool Post_post( void ) { if ( article.flags & POST_DEBUG ) { fputs( DynStr_str( article.text ), stdout ); return TRUE; } if ( ! checkPostableNewsgroup() ) return FALSE; return ( article.control == NULL ) ? ! postArticle() : ! handleControl(); } /* Done with article - tidy up. */ void Post_close( void ) { 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.approved = FALSE; article.posted = FALSE; }