view src/fetch.c @ 288:c02c4eb95f95 noffle

[svn] * src/configfile.h,src/configfile.c,docs/noffle.conf.5: Add noffle-user and noffle-group configs. * src/configfile.c,src/fetch.c,src/fetchlist.c,src/protocol.c, src/server.c: Replace strcpy() with Utl_cpyStr() where appropriate. See Debian bug 168128. * src/control.c,src/configfile.c,src/noffle.c: Replace [s]scanf("%s") with [s]scanf(MAXCHAR_FMT). * src/noffle.c: Log warning if noffle.conf is world readable. * src/noffle.c: Restrict most options to news admins; i.e. those who are root or news on running Noffle. * Makefile.in,acconfig.h,aclocal.m4,config.h.in,configure,configure.in, docs/Makefile.in,docs/noffle.conf.5,packages/Makefile.in, packages/redhat/Makefile.in,src/Makefile.am,src/Makefile.in, src/authenticate.c,src/authenticate.h,src/noffle.c,src/server.c: Add basic authentication using either Noffle-specific user file or authenticating via PAM (service 'noffle'). PAM authentication needs to run as root, so a Noffle server that needs PAM must be started by root. Helpful (?) error messages will be logged if not. Noffle will switch ruid and euid to 'news' (or whatever is configured) ASAP. * src/noffle.c: Add uid checking.
author bears
date Fri, 10 Jan 2003 23:25:45 +0000
parents 39d9c19ffba4
children d9035d08fe51
line wrap: on
line source

/*
  fetch.c

  $Id: fetch.c 415 2003-01-10 22:40:18Z bears $
*/

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

#include <stdio.h>
#include <errno.h>

#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif

#include <signal.h>
#include "client.h"
#include "configfile.h"
#include "content.h"
#include "dynamicstring.h"
#include "fetch.h"
#include "fetchlist.h"
#include "filter.h"
#include "request.h"
#include "group.h"
#include "lock.h"
#include "log.h"
#include "outgoing.h"
#include "protocol.h"
#include "pseudo.h"
#include "util.h"
#include "portable.h"

#define	MAX_ARTICLE_CMDS_QUEUED		20

static struct Fetch
{
    Bool ready;
    Str serv;
} fetch = { FALSE, "" };

static Bool
connectToServ( const char *name )
{
    Log_inf( "Fetch from '%s'", name );
    if ( ! Client_connect( name ) )
    {
        Log_err( "Could not connect to %s", name );
        return FALSE;
    }
    return TRUE;
}

Bool
Fetch_getNewGrps( void )
{
    time_t t;
    Str file;

    ASSERT( fetch.ready );
    snprintf( file, MAXCHAR, "%s/lastupdate.%s",
	      Cfg_spoolDir(), fetch.serv );
    if ( ! Utl_getStamp( &t, file ) )
    {
        Log_err( "Cannot read %s. Please run noffle --query groups", file );
        return FALSE;
    }
    Log_inf( "Updating groupinfo" );

    /*
     * If NEWGROUPS fails, it isn't necessarily fatal. You can do
     * a periodic noffle --query groups to refresh your group list.
     * So only return failure here if the status indicates the link
     * itself failed.
     *
     * In particular, older versions of NNTPcache have a Y2K bug that
     * stops NEWGROUPS working.
     */
    return ( ! IS_FATAL( Client_getNewgrps( &t ) ) );
}

/* Databases open on entry, closed on exit. */
static int
fetchNewArts( const char *name, FetchMode mode )
{
    int next, first, last, refetch, stat;

    stat = Client_changeToGrp( name );
    if ( stat != STAT_OK )
    {
        Log_err( "Could not change to group %s", name );
	if ( Lock_gotLock() )
	    Lock_closeDatabases();
        return stat;
    }
    Client_rmtFirstLast( &first, &last );
    Cont_read( name );
    next = Grp_rmtNext( name );
    if ( next == GRP_RMT_NEXT_NOT_SUBSCRIBED )
	next = first;
    if ( next == last + 1 )
    {
        Log_inf( "No new articles in %s", name );
        if ( Cont_write() )
            Grp_setFirstLast( name, Cont_first(), Cont_last() );
	Lock_closeDatabases();
        return STAT_OK;
    }
    if ( first == 0 && last == 0 )
    {
        Log_inf( "No articles in %s", name );
        if ( Cont_write() )
            Grp_setFirstLast( name, Cont_first(), Cont_last() );
	Lock_closeDatabases();
        return STAT_OK;
    }
    if ( next > last + 1 )
    {
    	refetch = last - Cfg_maxFetch() + 1;
    	if ( refetch < 0 ) refetch = 1;
        Log_err( "Article number inconsistent (%s rmt=%lu-%lu, next=%lu). "
		 "Refetching from %lu",
                 name, first, last, next, refetch );
        Pseudo_cntInconsistent( name, first, last, next, refetch );
        first = refetch;
    }
    else if ( next < first )
    {
        Log_inf( "Missing articles (%s first=%lu next=%lu)",
                 name, first, next );
        Pseudo_missArts( name, first, next );

	/*
	 * If we are missing articles but there are none to fetch,
	 * we must ensure we don't repeatedly generate missing
	 * article warning on every fetch until there is something
	 * to fetch. To guard against this, update the group remote
	 * next now.
	 */
	Grp_setRmtNext( name, first );
	next = first;
    }
    else
        first = next;
    if ( last - first > Cfg_maxFetch() )
    {
        Log_ntc( "Cutting number of overviews to %lu", Cfg_maxFetch() );
        first = last - Cfg_maxFetch() + 1;
    }
    Log_inf( "Getting remote overviews %lu-%lu for group %s",
             first, last, name );
    Lock_closeDatabases();
    return Client_getOver( name, first, last, mode );
}

Bool
Fetch_getNewArts( const char *name, FetchMode mode )
{
    if ( ! Lock_openDatabases() )
    {
        Log_err( "Could not open message base" );
        return FALSE;
    }
    Flt_init( fetch.serv ); /* Get filter data. Sorry, can't do it in Client_getOver().
                       * This is the lowest procedure not in the
                       * noffle.c:doFetch() tree. */
 
    return fetchNewArts( name, mode );
}

Bool
Fetch_updateGrps( void )
{
    FetchMode mode;
    int i, size;
    const char *name;

    ASSERT( fetch.ready );
    if ( ! Lock_openDatabases() )
    {
        Log_err( "Could not open message base" );
        return FALSE;
    }
    Fetchlist_read();
    size = Fetchlist_size();
    for ( i = 0; i < size; ++i )
    {
        Fetchlist_element( &name, &mode, i );
        if ( strcmp( Grp_server( name ), fetch.serv ) == 0 )
	{
            if ( IS_FATAL( fetchNewArts( name, mode ) ) )
		return FALSE;
	    if ( ! Lock_openDatabases() )
	    {
		Log_err( "Could not open message base" );
		return FALSE;
	    }
	}
    }
    Lock_closeDatabases();
    return TRUE;
}

static int
fetchMessageList( const char *list, int *artcnt, int artmax )
{
    const char *p;
    Str msgId;
    int stat;

    ASSERT( Lock_gotLock() );
    stat = Client_retrieveArtList( list, artcnt, artmax );
    if ( IS_FATAL( stat ) )
	return stat;
    p = list;
    while ( ( p = Utl_getLn( msgId, p ) ) )
        Req_remove( fetch.serv, msgId );
    return STAT_OK;
}

Bool
Fetch_getReq_( void )
{
    Str msgId;
    DynStr *list;
    DynStr *fetchList;
    const char *p;
    int count = 0, artcnt = 0, artmax = 0;
    Bool res;
    int stat;

    ASSERT( fetch.ready );
    Log_dbg( LOG_DBG_FETCH, "Retrieving articles marked for download" );
    list = new_DynStr( 10000 );
    fetchList = new_DynStr( 1000 );
    if ( list == NULL || fetchList == NULL )
    {
	if ( list != NULL )
	    del_DynStr( list );
        Log_err( "Out of memory in Fetch_get_Req_");
	return FALSE;
    }

    /*
     * Get all waiting message IDs for this server. We copy into a master
     * list as the requests file will be closed and re-opened during the
     * fetch and the position therein will be lost.
     */
    if ( ! Lock_openDatabases() )
    {
        Log_err( "Could not open message base" );
        return FALSE;
    }

    if ( Req_first( fetch.serv, msgId ) )
    {
        do
        {
            DynStr_appLn( list, msgId );
	    artmax++;
        }
        while ( Req_next( msgId ) );
	Log_inf( "%d TOTAL messages to download", artmax);
    }

    /* Retrieve in groups of up to size MAX_ARTICLE_CMDS_QUEUED. */
    p = DynStr_str( list );
    res = TRUE;
    while ( res && ( p = Utl_getLn( msgId, p ) ) != NULL )
    {
	DynStr_appLn( fetchList, msgId );
	if ( ++count % MAX_ARTICLE_CMDS_QUEUED == 0 )
	{
	    stat = fetchMessageList( DynStr_str( fetchList ), &artcnt,
				     artmax );
	    res = ! IS_FATAL( stat );
	    DynStr_clear( fetchList );
	}
    }
    stat = fetchMessageList( DynStr_str( fetchList ), &artcnt, artmax );
    res = res && ! IS_FATAL( stat );

    del_DynStr( fetchList );
    del_DynStr( list );
    Lock_closeDatabases();
    return res;
}

static void
returnArticleToSender( const char *sender, const char *reason,
                       const char *article )
{
    int ret;
    Str cmd;
    FILE *f;
    SignalHandler lastHandler;

    Log_err( "Return article to '%s' by mail", sender );
    snprintf( cmd, MAXCHAR, "%s -t -oi", SENDMAILPROG);
    lastHandler = signal( SIGPIPE, SIG_IGN );
    f = popen( cmd, "w" );
    if ( f == NULL )
        Log_err( "Invocation of '%s' failed (%s)", cmd, strerror( errno ) );
    else
    {
        fprintf( f,
                 "To: %s\n"
                 "Subject: [ NOFFLE: Posting failed ]\n"
                 "\n"
                 "\t[ NOFFLE: POSTING OF ARTICLE FAILED ]\n"
                 "\n"
                 "\t[ The posting of your article failed. ]\n"
                 "\t[ Reason of failure at remote server: ]\n"
                 "\n"
                 "\t[ %s ]\n"
                 "\n"
                 "\t[ Full article text has been appended. ]\n"
                 "\n"
                 "%s"
                 ".\n",
                 sender,
                 reason, article );
        ret = pclose( f );
        if ( ret != EXIT_SUCCESS )
            Log_err( "'%s' exit value %d", cmd, ret );
        signal( SIGPIPE, lastHandler );
    }
}

Bool
Fetch_postArts( void )
{
    DynStr *s;
    Str msgId, errStr, sender;
    const char *txt;
    Bool res;

    res = TRUE;
    s = new_DynStr( 10000 );
    if ( Out_first( fetch.serv, msgId, s ) )
    {
        Log_inf( "Posting articles" );
        do
        {
            txt = DynStr_str( s );
	    if ( Client_postArt( msgId, txt, errStr ) != STAT_OK )
	    {
		res = FALSE;
		break;
	    }
	    
	    /*
	     * OK, no server communication SNAFU during post. Now, do we
	     * get an error response? If so, try to return article to sender.
	     */
	    Out_remove( fetch.serv, msgId );
	    if ( errStr[0] != '\0' )
	    {
		Utl_cpyStr( sender, Cfg_mailTo() );
		if ( strcmp( sender, "" ) == 0
		     && ! Prt_searchHeader( txt, "REPLY-TO", sender )
		     && ! Prt_searchHeader( txt, "SENDER", sender )
		     && ! Prt_searchHeader( txt, "X-NOFFLE-X-SENDER",
					    sender ) /* see server.c */
		     && ! Prt_searchHeader( txt, "FROM", sender ) )
		    Log_err( "Article %s has no From/Sender/X-Sender field",
			     msgId );
		else
		    returnArticleToSender( sender, errStr, txt );
	    }
        }
        while ( Out_next( msgId, s ) );
    }
    del_DynStr( s );
    return res;
}

Bool
Fetch_init( const char *serv )
{
    Lock_closeDatabases();
    if ( ! connectToServ( serv ) )
    {
        Lock_openDatabases();
        return FALSE;
    }
    Utl_cpyStr( fetch.serv, serv );
    fetch.ready = TRUE;
    return TRUE;
}

void
Fetch_close()
{
    Client_disconnect();
    fetch.ready = FALSE;
    Log_inf( "Fetch from '%s' finished", fetch.serv );
    Lock_openDatabases();
}