view src/protocol.c @ 494:372f8b55506e noffle

[svn] Apply patch from Jan De Luyck. Add new option 'add-messageid-if-missing', which optionally postpones adding a message ID to the upstream server. If this is done, post-locally must be off. This is to deal with an upstream server troubling Jan. It usually (but not always) rejects posts with a Noffle message ID. I have changed Jan's original option of 'add-message-id-if-missing' for consistency with 'replace-messageid' and added the manual page entry. See SourceForge feature request 1513395.
author bears
date Wed, 12 Jul 2006 20:26:41 +0100
parents 4426f4dc6e8b
children
line wrap: on
line source

/*
  protocol.c

  $Id: protocol.c 579 2003-06-25 09:40:02Z bears $
*/

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

#include <stdio.h> 
#include <ctype.h> 
#include <pwd.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include "common.h"
#include "configfile.h"
#include "dynamicstring.h"
#include "log.h"
#include "over.h"
#include "util.h"
#include "portable.h"
#include "protocol.h"

static void
readAlarm( int sig )
{
    UNUSED( sig );
    
    return;
}

/*
 * Read and return the next line from f.
 *
 * The line is considered terminated by '\n' or '\r\n'. If present,
 * these are removed before the line is returned to the caller. NNTP
 * says text should be terminated by '\r\n', but just '\n' is
 * not unknown in the wild.
 *
 * Should the line be longer than can fit into the fixed MAXCHAR
 * buffer of a Str, then return the first MAXCHAR - 2 characters (so
 * the rest of the system can deal with that line plus a '\n') and
 * push back the last character, leaving it to be read as the first
 * character of a subsequent new 'next' line. This is a Bad Thing -
 * news transports shouldn't bugger around with even pathalogical
 * articles - but we'll have to live with it for now until someone
 * gets round to FIXME.
 *
 * Lines that are terminated by EOF are returned as normal. Only if
 * EOF occurs before any characters are read will this routine return
 * FALSE.
 */
Bool
Prt_getLn( Str line, FILE *f, int timeoutSeconds )
{
    size_t len;
    char *ret;
    SignalHandler oldHandler = NULL;

    if ( timeoutSeconds >= 0 )
    {
        oldHandler = Utl_installSignalHandler( SIGALRM, readAlarm );
        if ( oldHandler == SIG_ERR )
        {
            Log_err( "Prt_getLn: signal failed." );
            return FALSE;
        }
        if ( alarm( timeoutSeconds ) != 0 )
            Log_err( "Prt_getLn: Alarm was already set." );
    }
    
    ret = fgets( line, MAXCHAR, f );
    if ( timeoutSeconds >= 0 )
    {
        alarm( 0 );
        Utl_installSignalHandler( SIGALRM, oldHandler );
    }
    if ( ret == NULL )
        return FALSE;
    len = strlen( line );
    if ( len > 0 && line[ len - 1 ] == '\n' )
    {
	line[ len - 1 ] = '\0';
	if ( len > 1 && line[ len - 2 ] == '\r' )
	    line[ len - 2 ] = '\0';
    }
    else if ( len >= MAXCHAR - 1 )
    {
	ungetc( line[ len - 1 ], f );
	line[ len - 1 ] = '\0';
	Log_err( "Prt_getLn: Input line too long, splitting." );
    }    
    
    Log_dbg( LOG_DBG_PROTOCOL, "[R] %s", line );
    return TRUE;
}

Bool
Prt_getTxtLn( Str line, Bool *err, FILE *f, int timeoutSeconds )
{
    Str buf;

    if ( ! Prt_getLn( buf, f, timeoutSeconds ) )
    {
        Log_err( "Cannot get text line" );
        *err = TRUE;
        return FALSE;
    }
    *err = FALSE;
    if ( buf[ 0 ] == '.' )
    {
        if ( buf[ 1 ] == 0 )
            return FALSE;
        else
            Utl_cpyStr( line, buf + 1 );
    }
    else
        Utl_cpyStr( line, buf );
    return TRUE;
}

Bool
Prt_putTxtLn( const char* line, FILE *f )
{
    if ( line[ 0 ] == '.' )
    {
        Log_dbg( LOG_DBG_PROTOCOL, "[S] .%s", line );
        return ( fprintf( f, ".%s\r\n", line ) == (int)strlen( line ) + 3 );
    }
    else
    {
        Log_dbg( LOG_DBG_PROTOCOL, "[S] %s", line );
        return ( fprintf( f, "%s\r\n", line ) == (int)strlen( line ) + 2 );
    }
}

Bool
Prt_putEndOfTxt( FILE *f )
{
    Log_dbg( LOG_DBG_PROTOCOL, "[S] ." );
    return ( fprintf( f, ".\r\n" ) == 3 );
}

/*
  Write text buffer of lines each ending with '\n'.
  Replace '\n' by "\r\n".
*/
Bool
Prt_putTxtBuf( const char *buf, FILE *f )
{
    DynStr *line;
    const char *eol;
    Bool res = TRUE;

    line = new_DynStr(MAXCHAR);

    while ( res && *buf != '\0' )
    {
	eol = strchr( buf, '\n' );
	if ( eol != NULL )
	{
	    DynStr_appN( line, buf, eol - buf );
	    buf = eol + 1;
	    res = Prt_putTxtLn( DynStr_str( line ), f );
	    DynStr_clear( line );
	}
	else
	{
	    res = Prt_putTxtLn( buf, f );
	    break;
	}
    }

    del_DynStr( line );
    return res;
}

Bool
Prt_getField( Str resultField, Str resultValue,
	      Bool* isContinuation, const char* line )
{
    char *dst;
    const char *p;
    Str lineLower, t;

    ASSERT( isContinuation );
    
    *isContinuation = FALSE;
    Utl_cpyStr( lineLower, line );
    Utl_toLower( lineLower );
    p = Utl_stripWhiteSpace( lineLower );
    if ( p == lineLower )
    {
        dst = resultField;
        while ( ! isspace( *p ) && *p != ':' && *p != '\0' )
            *(dst++) = *(p++);
        *dst = '\0';
        while ( isspace( *p ) )
            ++p;    
        if ( *p == ':' )
        {
            ++p;
            Utl_cpyStr( t, line + ( p - lineLower ) );
            p = Utl_stripWhiteSpace( t );
            Utl_cpyStr( resultValue, p );
            return TRUE;
        } else
	    return FALSE;	/* Not a header line */
    }
    else
    {
	/*
	 * If the line starts with white space, it can be a header
	 * continuation.
	 */
	if( ! isspace( *line ) )
	    return FALSE;
	
	Utl_cpyStr( resultValue, line );
	*isContinuation = TRUE;
        return TRUE;
    }
    /* NOTREACHED */
}

Bool
Prt_searchHeader( const char *artTxt, const char *which, Str result )
{
    const char *src, *p;
    char *dst;
    Str line, whichLower, field;
    int len;
    Bool continuation;
    
    Utl_cpyStr( whichLower, which );
    Utl_toLower( whichLower );
    src = artTxt;
    while ( TRUE )
    {
        dst = line;
        len = 0;
        while ( *src != '\n' && len < MAXCHAR )
        {
            if ( *src == '\0' )
                return FALSE;
            *(dst++) = *(src++);
            ++len;
        }
        if ( *src == '\n' )
            ++src;
        *dst = '\0';
        p = Utl_stripWhiteSpace( line );
        if ( *p == '\0' )
            break;
        if ( Prt_getField( field, result, &continuation, line )
             && strcmp( field, whichLower ) == 0 )
            return TRUE;
    }
    return FALSE;
}

static void
getDomain( Str domain, const char *from )
{
    const char *addTopLevel, *p1, *p2, *p, *domainStart;
    Str myDomain;

    if ( Utl_getFQDN( myDomain ) )
    {
        p = strstr( myDomain, "." );
        if ( p != NULL )
            domainStart = p + 1;
        else
            domainStart = myDomain;
    }
    else /* Take domain of From field */
    {
        myDomain[ 0 ] = '\0';
        p1 = strstr( from, "@" );
        if ( p1 != NULL )
        {
            p2 = strstr( p1, ">" );
            if ( p2 != NULL )
                Utl_cpyStrN( myDomain, p1 + 1, p2 - p1 - 1 );
        }
        if ( myDomain[ 0 ] == '\0' )
            Utl_cpyStr( myDomain, "unknown" );
        domainStart = myDomain;
    }
    /*
      If domain contains no dot (and is probably invalid anyway),
      we add ".local", because some servers insist on domainnames with dot
      in message ID.
    */
    addTopLevel = strstr( domainStart, "." ) == NULL ? ".local" : "";
    snprintf( domain, MAXCHAR, "%s%s", myDomain, addTopLevel );    
}

/* See RFC 850, section 2.1.7 */
Bool
Prt_isValidMsgId( const char *msgId )
{
    Str head, domain;
    int len, headLen;
    const char *p;
    const char * specials = "\t\r\n ()@<>"; /* hmm, check "\\\'\"[]" as well? */

    len = strlen( msgId );
    if ( len > 250 )
        return FALSE; /* see draft-ietf-usefor-article-06.txt, ch 5.3 */
    p = strchr( msgId, '@' );
    if ( msgId[ 0 ] != '<' || msgId[ len - 1 ] != '>' || p == NULL )
        return FALSE;
    Utl_cpyStr( domain, p + 1 );
    domain[ strlen( domain ) - 1 ] = '\0';
    headLen = p - msgId - 1;
    Utl_cpyStrN( head, msgId + 1, headLen );
    head[ headLen ] = '\0';
    for ( p = msgId ; *p != '\0' ; p++ )
    {
        if ( ! isascii( *p ) )
            return FALSE;
    }
    if ( strpbrk( head, specials ) )
        return FALSE; 
    if ( strpbrk( domain, specials ) )
        return FALSE;
    return TRUE;
}

void
Prt_genMsgId( Str msgId, const char *from, const char *suffix )
{
    Str domain, date;
    time_t t;
    static long count = 0;
    const char *pattern;

    getDomain( domain, from );
    time( &t );
    strftime( date, MAXCHAR, "%Y%m%d%H%M%S", gmtime( &t ) );
    if ( strchr( domain, '@' ) )
        pattern = "<%s.%X.%lx.%s%s>";
    else
        pattern = "<%s.%X.%lx.%s@%s>";
    snprintf( msgId, MAXCHAR, pattern , date, getpid(), count++ ,suffix, domain );
    ASSERT( Prt_isValidMsgId( msgId ) );
}

void
Prt_genPathHdr( Str pathHdr, const char *from )
{
    getDomain( pathHdr, from );
    Utl_catStr( pathHdr, "!not-for-mail" );
}

Bool
Prt_genFromHdr( Str fromHdr )
{
    Str name, domain;
    const char *nameval;
    struct passwd *pwd;

    /* First get the domain to use. If config empty, use FQDN */
    Utl_cpyStr( domain, Cfg_fromDomain() );

    if ( strlen( domain ) == 0 )
	if ( ! Utl_getFQDN( domain ) )
	    Utl_catStr( domain, "unknown" );

    /* Now get pwd for the username */
    pwd = getpwuid( getuid() );
    if ( pwd == NULL )
	return FALSE;

    /* Now for their name - use env NAME if available */
    nameval = getenv( "NAME" );
    if ( nameval != NULL )
	Utl_cpyStr( name, nameval );
    else
    {
	char *p;
	
	/*
	  Extract from GECOS field. Following the lead of the INN inews,
	  ignore leading stuff like "23-" "stuff]-" or "stuff -" as well
	  as trailing whitespace, or anything that comes after
	  a comma or semicolon.
	 */
	nameval = pwd->pw_gecos;
	p = strchr( nameval, '-' );
	if ( p != NULL && p > nameval &&
	     ( p[-1] == ']' || p[-1] == ' ' || isdigit( p[ -1 ] ) ) )
	    nameval = p;
	p = strrchr( nameval, ',' );
	if ( p != NULL )
	    *p = '\0';
	p = strchr( nameval, ';' );
	if ( p != NULL )
	    *p = '\0';
	Utl_cpyStr( name, nameval );
    }

    /* OK, build From: contents */
/*  deprecated.
    Utl_cpyStr( fromHdr, pwd->pw_name );
    Utl_catStr( fromHdr, "@" );
    Utl_catStr( fromHdr, domain );
    Utl_catStr( fromHdr, " (" );
    Utl_catStr( fromHdr, name );
    Utl_catStr( fromHdr, ")" );
*/
    Utl_cpyStr( fromHdr, "\"" );
    Utl_catStr( fromHdr, name );
    Utl_catStr( fromHdr, "\" <" );
    Utl_catStr( fromHdr, pwd->pw_name );
    Utl_catStr( fromHdr, "@" );
    Utl_catStr( fromHdr, domain );
    Utl_catStr( fromHdr, ">" );

    return TRUE;
}