diff src/post.c @ 88:1fcdced0246e noffle

[svn] Move posting code to post.c, add command line posting
author bears
date Thu, 18 May 2000 13:17:23 +0100
parents 7250be163ec4
children eb522db0d032
line wrap: on
line diff
--- 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 <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"
@@ -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;
 }