view src/content.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 5eece4dfd945
children 9b79433f0976
line wrap: on
line source

/*
  content.c

  $Id: content.c 413 2002-12-27 21:48:25Z bears $
*/

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

#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "common.h"
#include "configfile.h"
#include "content.h"
#include "group.h"
#include "log.h"
#include "over.h"
#include "pseudo.h"
#include "util.h"
#include "portable.h"

struct
{
    DIR *dir;           /* Directory for browsing through all
                           groups */
    int vecFirst;	/* First article number in vector */
    int first;		/* First live article number */
    int last;		/* Last article number */
    int size;           /* Number of overviews. */
    int max;            /* Size of elem. */
    Over **elem;        /* Ptr to array with ptrs to overviews.
                           NULL entries for non-existing article numbers
                           in group. */
    Str name;
    Str file;
    Bool dirty;		/* Needs writing? */
} cont = { NULL, 1, 1, 0, 0, 0, NULL, "", "", FALSE };

void
Cont_app( Over *ov )
{
    if ( cont.max < cont.size + 1 )
    {
        if ( ! ( cont.elem = realloc( cont.elem,
                                      ( cont.max + 500 )
                                      * sizeof( cont.elem[ 0 ] ) ) ) )
            Log_fatal( "Could not realloc overview list" );
        cont.max += 500;
    }
    ASSERT( cont.vecFirst > 0 );
    if ( ov )
        Ov_setNumb( ov, cont.vecFirst + cont.size );
    cont.elem[ cont.size++ ] = ov;
    cont.last = cont.vecFirst + cont.size - 1;
    cont.dirty = TRUE;
}

Bool
Cont_validNumb( int n )
{
    return ( n != 0 && n >= cont.first && n <= cont.last
             && cont.elem[ n - cont.vecFirst ] );
}

void
Cont_delete( int n )
{
    Over **ov;

    if ( ! Cont_validNumb( n ) )
        return;
    ov = &cont.elem[ n - cont.vecFirst ];
    free( *ov );
    *ov = NULL;
    cont.dirty = TRUE;
}

/* Remove all overviews from content. */
static void
clearCont( void )
{
    int i;

    for ( i = 0; i < cont.size; ++i )
        del_Over( cont.elem[ i ] );
    cont.size = 0;
}

static void
setupEmpty( const char *name )
{
    cont.last = Grp_last( name );
    cont.first = cont.vecFirst = cont.last + 1;
    ASSERT( cont.first > 0 );
}

/* Extend content list to size "cnt" and append NULL entries. */
static void
extendCont( int cnt )
{
    int i, n;
    
    if ( cont.size < cnt )
    {
        n = cnt - cont.size;
        for ( i = 0; i < n; ++i )
            Cont_app( NULL );
    }
}

/* Discard all cached overviews, and read in the overviews of a new group
   from its overviews file. */
void
Cont_read( const char *name )
{
    FILE *f;
    Over *ov;
    int numb;
    Str line;

    /* Delete old overviews and make room for new ones. */
    cont.vecFirst = 0;
    cont.first = 0;
    cont.last = 0;
    Utl_cpyStr( cont.name, name );
    clearCont();

    /* read overviews from overview file and store them in the overviews
       list */
    snprintf( cont.file, MAXCHAR, "%s/overview/%s", Cfg_spoolDir(), name ); 
    f = fopen( cont.file, "r" );
    if ( ! f )
    {
        Log_dbg( LOG_DBG_NEWSBASE, "No group overview file: %s", cont.file );
	setupEmpty( name );
        return;
    }
    Log_dbg( LOG_DBG_NEWSBASE, "Reading %s", cont.file );
    while ( fgets( line, MAXCHAR, f ) )
    {
        if ( ! ( ov = Ov_read( line ) ) )
        {
            Log_err( "Overview corrupted in %s: %s", name, line );
            continue;
        }
        numb = Ov_numb( ov );
        if ( numb < cont.first )
        {
            Log_err( "Wrong ordering in %s: %s", name, line );
            continue;
        }
        if ( cont.first == 0 )
            cont.first = cont.vecFirst = numb;
        cont.last = numb;
        extendCont( numb - cont.first + 1 );
        cont.elem[ numb - cont.first ] = ov;
    }
    fclose( f );


    if ( cont.first == 0 )
	setupEmpty( name );		/* Corrupt overview file recovery */
    else
    {
	int grpLast;

	/*
	  Check for end article(s) being cancelled. Need to ensure we
	  don't re-use and article number.
	 */
	grpLast = Grp_last( name );
	if ( cont.last < grpLast )
	    extendCont( grpLast - cont.first + 1 );
    }
}

Bool
Cont_write( void )
{
    Bool anythingWritten;
    int i;
    FILE *f;
    const Over *ov, *ov_next;
    Str tmpfname;
    Bool writeErr;
    int first;

    /* If nowt has changed, do nowt. */
    if ( ! cont.dirty )
	return TRUE;
    
    /* Save the overview to temporary file in same dir. */
    /* old tmpfnames will be expired at noffle.c:expireContents() */
    snprintf( tmpfname, MAXCHAR, "%s/overview/.#%d.%s",
	      Cfg_spoolDir(), (int) getpid(), cont.name ); 
    if ( ! ( f = fopen( tmpfname, "w" ) ) )
    {
        Log_err( "Could not open %s for writing", tmpfname );
        return FALSE;
    }
    Log_dbg( LOG_DBG_NEWSBASE, "Writing %s (%lu)", tmpfname, cont.size );
    anythingWritten = FALSE;
    first = -1;
    writeErr = FALSE;
    
    for ( i = 0; i < cont.size; ++i )
    {
	ov = cont.elem[ i ];
        if ( ov )
        {
	    if ( i + 1 < cont.size )
		ov_next = cont.elem[ i + 1 ];
	    else
		ov_next = NULL;
	
	    /*
	      Preserve gen info if it is followed immediately by an
	      article with the next number. In practice, this means
	      that when in auto-subscribed mode, the gen info will
	      remain until the 'group now subscribed' message is
	      expired.
	     */
            if ( ! Pseudo_isGeneralInfo( Ov_msgId( ov ) )
		 || ( ov_next != NULL &&
		      Ov_numb( ov_next ) - Ov_numb( ov ) == 1 ) )
            {
                anythingWritten = TRUE;
                if ( ! Ov_write( ov, f ) )
                {
                    Log_err( "Writing of overview line to %s failed: %s",
			     tmpfname, strerror( errno ) );
		    writeErr = TRUE;
                    break;
                }
                else
		{
		    if  ( first < 0 )
			first = cont.vecFirst + i;
		}
            }
        }
    }
    if ( fclose( f ) != 0 )
    {
	Log_err( "Close of content file %s failed: %s",
		 tmpfname, strerror( errno ) );
	writeErr = TRUE;
    }

    if ( writeErr )
    {
        /* Write error - leave everything as at present */
        return FALSE;
    }
    
    /*
      If empty, remove the overview file and set first to one
      beyond last to flag said emptiness.
     */
    if ( ! anythingWritten )
    {
	if ( unlink( tmpfname ) < 0 )
	    Log_err( "Unlink of %s failed: %s", tmpfname, strerror( errno ) );
	if ( unlink( cont.file ) < 0 )
        {
	    Log_err( "Unlink of %s failed: %s", cont.file, strerror( errno ) );
            return FALSE;
        }
	else
	{
	    cont.dirty = FALSE;
	    cont.first = cont.last + 1;
	}
    }
    else
    {
	if ( rename( tmpfname, cont.file ) < 0 )
        {
	    Log_err( "Rename of content file %s to %s failed: %s",
		     tmpfname, cont.file, strerror( errno ) );
            return FALSE;
        }
	else
        {
            ASSERT( first != -1 );
	    cont.dirty = FALSE;
            cont.first = first;
        }
    }

    return TRUE;
}

const Over *
Cont_get( int numb )
{
    if ( ! Cont_validNumb( numb ) )
        return NULL;
    return cont.elem[ numb - cont.vecFirst ];
}

int
Cont_first( void ) { return cont.first; }

int
Cont_last( void ) { return cont.last; }

int
Cont_find( const char *msgId )
{
    int i;
    const Over *ov;
    
    for ( i = 0; i < cont.size; i++ )
    {
        if ( ( ov = cont.elem[ i ] )
	     && strcmp( Ov_msgId( ov ), msgId ) ==  0 )
	    return i + cont.vecFirst;
    }

    return -1;
}

const char *
Cont_grp( void ) { return cont.name; }

Bool
Cont_nextGrp( Str result )
{
    struct dirent *d;
    
    ASSERT( cont.dir );
    if ( ! ( d = readdir( cont.dir ) ) )
    {
	closedir( cont.dir );
        cont.dir = NULL;
        return FALSE;
    }
    if ( ! d->d_name )
        return FALSE;
    if ( d->d_name[0] == '.' )
    {
        Str tmpfname;

	/*
	 * If it is '.' or '..', skip.
	 * If it starts '.#', treat as a temporary file that didn't
	 * get deleted for some reason and flag an error and delete it.
	 */
	switch( d->d_name[1] )
	{
	case '\0':
	case '.':
	    break;

	case '#':
            snprintf( tmpfname, MAXCHAR, "%s/overview/%s",
              Cfg_spoolDir(), d->d_name ); 
	    Log_err( "Bad temporary file %s - deleting.",
		      tmpfname );
	    if ( unlink( tmpfname ) < 0 )
		Log_err( "Unlink of %s failed: %s",
			 tmpfname, strerror(errno) );
	    break;

	default:
	    Log_err( "Unknown file %s in %s/overview - please delete",
		     d->d_name, Cfg_spoolDir() );
	    break;
	}
	return Cont_nextGrp( result );
    }
    Utl_cpyStr( result, d->d_name );
    result[ MAXCHAR - 1 ] = '\0';
    return TRUE;
}

Bool
Cont_firstGrp( Str result )
{
    Str name;

    snprintf( name, MAXCHAR, "%s/overview", Cfg_spoolDir() );
    if ( ! ( cont.dir = opendir( name ) ) )
    {
        Log_err( "Cannot open %s", name );
        return FALSE;
    }
    return Cont_nextGrp( result );
}

Bool
Cont_exists( const char *grp )
{
    Str fname;

    /* Do we have a content/overview file for this group? */
    snprintf( fname, MAXCHAR, "%s/overview/%s", Cfg_spoolDir(), grp );
    return ( access( fname, R_OK ) == 0 );    
}