view noffle.c @ 8:ffb0b14fc8d9 noffle

[svn] Online mode/authentication bug
author enz
date Sat, 12 Feb 2000 11:54:42 +0000
parents 04124a4423d4
children 6deb8bb73f2a
line wrap: on
line source

/*
  noffle.c

  Main program. Implements specified actions, but running as server, which
  is done by Serv_run(), declared in server.h.
  
  Locking policy: lock access to databases while noffle is running, but
  not as server. If noffle runs as server, locking is performed while
  executing NNTP commands, but temporarily released if no new command is
  received for some seconds (to allow multiple clients connect at the same
  time).

  $Id: noffle.c 3 2000-01-04 11:35:42Z enz $
*/

#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <sys/resource.h>
#include <syslog.h>
#include <unistd.h>
#include "client.h"
#include "common.h"
#include "content.h"
#include "config.h"
#include "database.h"
#include "fetch.h"
#include "fetchlist.h"
#include "group.h"
#include "log.h"
#include "online.h"
#include "over.h"
#include "pseudo.h"
#include "util.h"
#include "server.h"
#include "request.h"
#include "lock.h"

struct Noffle
{
    Bool queryGrps;
    Bool queryDsc;
    Bool queryTimes;
    Bool interactive;
} noffle = { FALSE, FALSE, FALSE, TRUE };

static void
doArt( const char *msgId )
{
    const char *id;

    if ( strcmp( msgId, "all" ) == 0 )
    {
        if ( ! Db_first( &id ) )
            fprintf( stderr, "Database empty.\n" );
        else
            do
            {
                printf( "%s\n%s"
                        "========================================"
                        "======================================\n",
                        Db_header( id ), Db_body( id ) );
            }
            while ( Db_next( &id ) );
    }
    else
    {
        if ( ! Db_contains( msgId ) )
            fprintf( stderr, "Not in database.\n" );
        else
            printf( "%s\n%s", Db_header( msgId ), Db_body( msgId ) );
    }
}

/* List articles requested from one particular server */
static void
listRequested1( const char* serv )
{
  Str msgid;

  if ( ! Req_first( serv, msgid ) )
      return;
  do
      printf( "%s %s\n", msgid, serv );
  while ( Req_next( msgid ) );
}

/* List requested articles. List for all servers if serv = "all" or serv =
   NULL. */
void
doRequested( const char *arg )
{
    Str serv;
    
    if ( ! arg || ! strcmp( arg, "all" ) )
    {
        Cfg_beginServEnum();   
        while ( Cfg_nextServ( serv ) )
            listRequested1( serv );
    }   
    else
        listRequested1( arg );
}


static void
doDb( void )
{
    const char *msgId;

    if ( ! Db_first( &msgId ) )
        fprintf( stderr, "Database empty.\n" );
    else
        do
            printf( "%s\n", msgId );
        while ( Db_next( &msgId ) );
}

static void
doFetch( void )
{
    Str serv;

    Cfg_beginServEnum();
    while ( Cfg_nextServ( serv ) )
        if ( Fetch_init( serv ) )
        {
            Fetch_postArts();

            Fetch_getNewGrps();

            /* Get overviews of new articles and store IDs of new articles
               that are to be fetched becase of FULL or THREAD mode in the
               request database. */
            Fetch_updateGrps();         

            /* get requested articles */
            Fetch_getReq_();

            Fetch_close();
        }
}

static void
doQuery( void )
{
    Str serv;

    Cfg_beginServEnum();
    while ( Cfg_nextServ( serv ) )
        if ( Fetch_init( serv ) )
        {
            if ( noffle.queryGrps )
                Client_getGrps();
            if ( noffle.queryDsc )
                Client_getDsc();
            if ( noffle.queryTimes )
                Client_getCreationTimes();
            Fetch_close();
        }
}

/* Expire all overviews not in database */
static void
expireContents( void )
{
    const Over *ov;
    int i;
    int cntDel, cntLeft;
    Str grp;
    Bool autoUnsubscribe;
    int autoUnsubscribeDays;
    time_t now = time( NULL ), maxAge = 0;
    const char *msgId;

    autoUnsubscribe = Cfg_autoUnsubscribe();
    autoUnsubscribeDays = Cfg_autoUnsubscribeDays();
    maxAge = Cfg_autoUnsubscribeDays() * 24 * 3600;
    if ( ! Cont_firstGrp( grp ) )
        return;
    Log_inf( "Expiring overviews not in database" );
    do
    {
        if ( ! Grp_exists( grp ) )
            Log_err( "Overview file for unknown group %s exists", grp );
        else
        {
            cntDel = cntLeft = 0;
            Cont_read( grp );
            for ( i = Cont_first(); i <= Cont_last(); ++i )
                if ( ( ov = Cont_get( i ) ) )
                {
                    msgId = Ov_msgId( ov );
                    if ( ! Db_contains( msgId ) )
                    {
                        Cont_delete( i );
                        ++cntDel;
                    }
                    else
                        ++cntLeft;
                }
            if ( autoUnsubscribe
                 && difftime( now, Grp_lastAccess( grp ) ) > maxAge )
            {
                Log_ntc( "Auto-unsubscribing from %s after %d "
                         "days without access",
                         grp, autoUnsubscribeDays );
                Pseudo_autoUnsubscribed( grp, autoUnsubscribeDays );
                Fetchlist_read();
                Fetchlist_remove( grp );
                Fetchlist_write();
            }
            Cont_write();
            Grp_setFirstLast( grp, Cont_first(), Cont_last() );
            Log_inf( "%ld overviews deleted from group %s, %ld left (%ld-%ld)",
                     cntDel, grp, cntLeft, Grp_first( grp ), Grp_last( grp ) );
        }
    }
    while ( Cont_nextGrp( grp ) );
}

static void
doExpire( unsigned int days )
{
    Db_close();
    Db_expire( days );
    if ( ! Db_open() )
        return;
    expireContents();
}

static void
doList( void )
{
    FetchMode mode;
    int i, size;
    const char *name, *modeStr = "";

    Fetchlist_read();
    size = Fetchlist_size();
    if ( size == 0 )
        fprintf( stderr, "Fetch list is empty.\n" );
    else
        for ( i = 0; i < size; ++i )
        {
            Fetchlist_element( &name, &mode, i );
            switch ( mode )
            {
            case FULL:
                modeStr = "full"; break;
            case THREAD:
                modeStr = "thread"; break;
            case OVER:
                modeStr = "over"; break;
            }
            printf( "%s %s %s\n", name, Grp_serv( name ), modeStr );
        }
}

static void
doGrps( void )
{
    const char *g;
    Str dateLastAccess, dateCreated;
    time_t lastAccess, created;

    if ( Grp_firstGrp( &g ) )
        do
        {
            lastAccess = Grp_lastAccess( g );
            created = Grp_created( g );
            ASSERT( lastAccess >= 0 );
            ASSERT( created >= 0 );
            strftime( dateLastAccess, MAXCHAR, "%Y-%m-%d %H:%M:%S",
                      localtime( &lastAccess ) );
            strftime( dateCreated, MAXCHAR, "%Y-%m-%d %H:%M:%S",
                      localtime( &created ) );
            printf( "%s\t%s\t%i\t%i\t%i\t%s\t%s\t%s\n",
                    g, Grp_serv( g ), Grp_first( g ), Grp_last( g ),
                    Grp_rmtNext( g ), dateCreated,
                    dateLastAccess, Grp_dsc( g ) );
        }
        while ( Grp_nextGrp( &g ) );
}

static Bool
doSubscribe( const char *name, FetchMode mode )
{
    if ( ! Grp_exists( name ) )
    {
        fprintf( stderr, "%s is not available at rmt server.\n", name );
        return FALSE;
    }
    Fetchlist_read();
    if ( Fetchlist_add( name, mode ) )
        printf( "Adding %s to fetch list in %s mode.\n",
                name, mode == FULL ? "full" : mode == THREAD ?
                "thread" : "overview" );
    else
        printf( "%s is already in fetch list. Mode is now: %s.\n",
                name, mode == FULL ? "full" : mode == THREAD ?
                "thread" : "overview" );
    if ( ! Fetchlist_write() )
        fprintf( stderr, "Could not save fetchlist.\n" );
    return TRUE;
}

static void
doUnsubscribe( const char *name )
{
    Fetchlist_read();
    if ( ! Fetchlist_remove( name ) )
        printf( "%s is not in fetch list.\n", name );
    else
        printf( "%s removed from fetch list.\n", name );
    if ( ! Fetchlist_write() )
        fprintf( stderr, "Could not save fetchlist.\n" );
}

static void
printUsage( void )
{
    static const char *msg =
      "Usage: noffle <option>\n"
      "Option is one of the following:\n"
      " -a | --article <msg id>|all   Show article(s) in database\n"
      " -d | --database               Show content of article database\n"
      " -e | --expire <n>             Expire articles older than <n> days\n"
      " -f | --fetch                  Get newsfeed from server/post articles\n"
      " -g | --groups                 Show all groups available at server\n"
      " -h | --help                   Show this text\n"
      " -l | --list                   List groups on fetch list\n"
      " -n | --online                 Switch to online mode\n"
      " -o | --offline                Switch to offline mode\n"
      " -q | --query groups           Get group list from server\n"
      " -q | --query desc             Get group descriptions from server\n"
      " -q | --query times            Get group creation times from server\n"
      " -r | --server                 Run as server on stdin/stdout\n"
      " -R | --requested              List articles marked for download\n"
      " -s | --subscribe-over <grp>   Add group to fetch list (overview)\n"
      " -S | --subscribe-full <grp>   Add group to fetch list (full)\n"
      " -t | --subscribe-thread <grp> Add group to fetch list (thread)\n"
      " -u | --unsubscribe <grp>      Remove group from fetch list\n"
      " -v | --version                Print version\n";
    fprintf( stderr, "%s", msg );
}

/*
  Allow core files: Change core limit and change working directory
  to spool directory, where news has write permissions.
*/
static void
enableCorefiles()
{
    struct rlimit lim;

    if ( getrlimit( RLIMIT_CORE, &lim ) != 0 )
    {
        Log_err( "Cannot get system core limit: %s", strerror( errno ) );
        return;
    }
    lim.rlim_cur = lim.rlim_max;
    if ( setrlimit( RLIMIT_CORE, &lim ) != 0 )
    {
        Log_err( "Cannot set system core limit: %s", strerror( errno ) );
        return;
    }
    Log_dbg( "Core limit set to %i", lim.rlim_max );
    if ( chdir( Cfg_spoolDir() ) != 0 )
    {
         Log_err( "Cannot change to directory '%s'", Cfg_spoolDir() );
         return;
    }
    Log_dbg( "Changed to directory '%s'", Cfg_spoolDir() );
}

static Bool
initNoffle( Bool interactive )
{
    Log_init( "noffle", interactive, LOG_NEWS );
    Cfg_read();
    Log_dbg( "NOFFLE version %s", Cfg_version() );
    noffle.interactive = interactive;
    if ( interactive )
      if ( ! Lock_openDatabases() )
        return FALSE;
    if ( ! interactive )
        enableCorefiles();
    return TRUE;
}

static void
closeNoffle( void )
{
    if ( noffle.interactive )
      Lock_closeDatabases();
}

static void
bugReport( int sig )
{
    Log_err( "Received SIGSEGV. Please submit a bug report" );
    signal( SIGSEGV, SIG_DFL );
    raise( sig );
}

static void
logSignal( int sig )
{
    const char *name;
    Bool err = TRUE;

    switch ( sig )
    {
    case SIGABRT:
        name = "SIGABRT"; break;
    case SIGFPE:
        name = "SIGFPE"; break;
    case SIGILL:
        name = "SIGILL"; break;
    case SIGINT:
        name = "SIGINT"; break;
    case SIGTERM:
        name = "SIGTERM"; break;
    case SIGPIPE:
        name = "SIGPIPE"; err = FALSE; break;
    default:
        name = "?"; break;
    }
    if ( err )
        Log_err( "Received signal %i (%s). Aborting.", sig, name );
    else
        Log_inf( "Received signal %i (%s). Aborting.", sig, name );
    signal( sig, SIG_DFL );
    raise( sig );
}

int main ( int argc, char **argv )
{
    int c, result;
    struct option longOptions[] =
    {
        { "article",          required_argument, NULL, 'a' },
        { "database",         no_argument,       NULL, 'd' },
        { "expire",           required_argument, NULL, 'e' },
        { "fetch",            no_argument,       NULL, 'f' },
        { "groups",           no_argument,       NULL, 'g' },
        { "help",             no_argument,       NULL, 'h' },
        { "list",             no_argument,       NULL, 'l' },
        { "offline",          no_argument,       NULL, 'o' },
        { "online",           no_argument,       NULL, 'n' },
        { "query",            required_argument, NULL, 'q' },
        { "server",           no_argument,       NULL, 'r' },
        { "requested",        no_argument,       NULL, 'R' },
        { "subscribe-over",   required_argument, NULL, 's' },
        { "subscribe-full",   required_argument, NULL, 'S' },
        { "subscribe-thread", required_argument, NULL, 't' },
        { "unsubscribe",      required_argument, NULL, 'u' },
        { "version",          no_argument,       NULL, 'v' },
        { NULL, 0, NULL, 0 }
    };
    
    signal( SIGSEGV, bugReport );
    signal( SIGABRT, logSignal );
    signal( SIGFPE, logSignal );
    signal( SIGILL, logSignal );
    signal( SIGINT, logSignal );
    signal( SIGTERM, logSignal );
    signal( SIGPIPE, logSignal );
    c = getopt_long( argc, argv, "a:de:fghlonq:rRs:S:t:u:v",
                     longOptions, NULL );
    if ( ! initNoffle( c != 'r' ) )
        return EXIT_FAILURE;
    result = EXIT_SUCCESS;
    switch ( c )
    {
    case 0:
        /* Options that set a flag. */
        break;
    case 'a':
        if ( ! optarg )
        {
            fprintf( stderr, "Option -a needs argument.\n" );
            result = EXIT_FAILURE;
        }
        else
            doArt( optarg );
        break;
    case 'd':
        doDb();
        break;
    case 'e':
        {
            unsigned int days;

            if ( ! optarg || sscanf( optarg, "%u", &days ) != 1 )
            {
                fprintf( stderr, "Bad argument: -e %s\n", optarg );
                result = EXIT_FAILURE;
            }
            else
                doExpire( days );
        }
        break;
    case 'f':
        doFetch();
        break;
    case 'g':
        doGrps();
        break;
    case -1:
    case 'h':
        printUsage();
        break;
    case 'l':
        doList();
        break;
    case 'n':
        if ( Online_true() )
            fprintf( stderr, "NOFFLE is already online\n" );
        else
            Online_set( TRUE );
        break;
    case 'o':
        if ( ! Online_true() )
            fprintf( stderr, "NOFFLE is already offline\n" );
        else
            Online_set( FALSE );
        break;
    case 'q':
        if ( ! optarg )
        {
            fprintf( stderr, "Option -q needs argument.\n" );
            result = EXIT_FAILURE;
        }
        else
        {
            if ( strcmp( optarg, "groups" ) == 0 )
                noffle.queryGrps = TRUE;
            else if ( strcmp( optarg, "desc" ) == 0 )
                noffle.queryDsc = TRUE;
            else if ( strcmp( optarg, "times" ) == 0 )
                noffle.queryTimes = TRUE;
            else
            {
                fprintf( stderr, "Unknown argument -q %s\n", optarg );
                result = EXIT_FAILURE;
            }
            doQuery();
        }
        break;
    case 'r':
        Log_inf( "Starting as server" );
        Serv_run();
        break;
    case 'R':
        doRequested( optarg );
        break;
    case 's':
        if ( ! optarg )
        {
            fprintf( stderr, "Option -s needs argument.\n" );
            result = EXIT_FAILURE;
        }
        else
            result = doSubscribe( optarg, OVER );
        break;
    case 'S':
        if ( ! optarg )
        {
            fprintf( stderr, "Option -S needs argument.\n" );
            result = EXIT_FAILURE;
        }
        else
            doSubscribe( optarg, FULL );
        break;
    case 't':
        if ( ! optarg )
        {
            fprintf( stderr, "Option -t needs argument.\n" );
            result = EXIT_FAILURE;
        }
        else
            result = doSubscribe( optarg, THREAD );
        break;
    case 'u':
        if ( ! optarg )
        {
            fprintf( stderr, "Option -u needs argument.\n" );
            result = EXIT_FAILURE;
        }
        else
            doUnsubscribe( optarg );
        break;
    case '?':
        /* Error message already printed by getopt_long */
        result = EXIT_FAILURE;
        break;
    case 'v':
        printf( "NNTP server NOFFLE, version %s.\n", Cfg_version() );
        break;
    default:
        abort(); /* Never reached */
    }
    closeNoffle();
    return result;
}