view 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 source

/*
  post.c

  $Id: post.c 100 2000-05-18 12:17:23Z bears $
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.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"

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 posted;	   /*  Has it been put in the article database? */
    const char *server;	   /* Server for external post */
    struct OverInfo over;
};

static struct Article article = { NULL, NULL, NULL, FALSE, NULL,
				  { "", "", "", "", "", 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( "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( "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;
}

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 )
{
    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;
}