Mercurial > noffle
view src/noffle.c @ 500:614a3177b15c noffle tip
Add mail-from option.
Some modern mail systems will try and ensure the sender email is a legitimate
address. Which will fail if there isn't such an address.
author | Jim Hague <jim.hague@acm.org> |
---|---|
date | Wed, 14 Aug 2013 12:04:39 +0100 |
parents | a04c52f87b6e |
children |
line wrap: on
line source
/* noffle.c Main program. Implements specified actions, but running as server, which is done by Server_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 629 2004-10-13 23:26:48Z bears $ */ #if HAVE_CONFIG_H #include <config.h> #endif #include <ctype.h> #include <errno.h> #include <signal.h> #include <string.h> #include <sys/time.h> #include <sys/resource.h> #include <syslog.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include "common.h" #include "authenticate.h" #include "client.h" #include "content.h" #include "control.h" #include "configfile.h" #include "database.h" #include "expire.h" #include "fetch.h" #include "fetchlist.h" #include "filter.h" #include "group.h" #include "itemlist.h" #include "log.h" #include "online.h" #include "outgoing.h" #include "over.h" #include "post.h" #include "pseudo.h" #include "util.h" #include "server.h" #include "request.h" #include "lock.h" #include "portable.h" #include "wildmat.h" struct Noffle { Bool queryGrps; Bool queryDsc; Bool queryTimes; Bool lockAtStartup; char *serverPattern; } noffle = { FALSE, FALSE, FALSE, TRUE, NULL }; static Bool doArt( const char *msgId ) { const char *id; Bool res = FALSE; if ( strcmp( msgId, "all" ) == 0 ) { if ( ! Db_first( &id ) ) fprintf( stderr, "Database empty.\n" ); else do { /* * Output articles in rnews format. The number after * the rnews header is the length of the article in bytes, * counting line endings as '\n'. */ const char *header = Db_header( id ); const char *body = Db_body( id ); printf( "#! rnews %d\n%s\n%s\n", strlen(header) + strlen(body) + 2, header, body); res = TRUE; } 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 ) ); res = TRUE; } } return res; } static void doCancel( const char *msgId ) { switch( Ctrl_cancel( msgId ) ) { case CANCEL_NO_SUCH_MSG: printf( "No such message '%s'.\n", msgId ); break; case CANCEL_OK: printf( "Message '%s' cancelled.\n", msgId ); break; case CANCEL_NEEDS_MSG: printf( "Message '%s' cancelled in local database only.\n", msgId ); break; } } /* 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. */ static 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; if ( ! Lock_getFetchLock( LOCK_NOWAIT ) ) { Log_err( "Another 'noffle --fetch' is in progress" ); return; } Log_inf( "Fetching news..." ); Cfg_beginServEnum(); while ( Cfg_nextServ( serv ) ) if ( ! noffle.serverPattern || Wld_match( serv, noffle.serverPattern ) ) if ( Fetch_init( serv ) ) { Bool connOK; connOK = Fetch_postArts(); Flt_init( serv ); /* get filter data before processGrps() calls Utl_stamp(). */ connOK = connOK && 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. */ connOK = connOK && Fetch_updateGrps(); /* get requested articles */ connOK = connOK && Fetch_getReq_(); Fetch_close(); } Log_inf( "Fetching news finished." ); Lock_releaseFetchLock(); } /* List articles queued for posting to one particular server */ static void listOutgoing1( const char* serv ) { Str msgid; if ( ! Out_first( serv, msgid, NULL ) ) return; do printf( "%s %s\n", msgid, serv ); while ( Out_next( msgid, NULL ) ); } /* List articles queued for posting to a particular server. */ static void doOutgoing( const char *arg ) { Str serv; Cfg_beginServEnum(); while ( Cfg_nextServ( serv ) ) if ( ! arg || Wld_match( serv, arg ) ) listOutgoing1( serv ); } static Bool doPost( FILE *f, unsigned flags ) { Str line; DynStr *s; Bool res; s = new_DynStr( 10000 ); while ( fgets( line, MAXCHAR, f ) != NULL ) DynStr_app( s, line ); res = TRUE; if ( ! Post_open( DynStr_str( s ), flags ) ) { fprintf( stderr, "Post failed: Malformed article.\n" ); res = FALSE; } else if ( ! Post_post() ) { fprintf( stderr, "Post failed: Can't post to group.\n" ); res = FALSE; } Post_close(); return res; } static void doQuery( void ) { Str serv; Cfg_beginServEnum(); while ( Cfg_nextServ( serv ) ) if ( ! noffle.serverPattern || Wld_match( serv, noffle.serverPattern ) ) if ( Fetch_init( serv ) ) { int status = STAT_OK; if ( noffle.queryGrps ) status = Client_getGrps(); if ( status == STAT_OK && noffle.queryDsc ) Client_getDsc(); Fetch_close(); } } static void doExpire( void ) { Exp_expire(); } static Bool doRebuild( void ) { Bool res = TRUE; if ( ! Db_rebuild() ) { res = FALSE; fprintf( stderr, "Rebuild failed.\n" ); } return res; } static Bool doCreateLocalGroup( const char * name ) { Str grp; Bool res = FALSE; Utl_cpyStr( grp, name ); Utl_toLower( grp ); name = Utl_stripWhiteSpace( grp ); if ( Grp_exists( name ) ) { fprintf( stderr, "'%s' already exists.\n", name ); return FALSE; } if ( ! Grp_isValidName( name ) ) fprintf( stderr, "'%s' invalid group name.\n", name ); else if ( Grp_isForbiddenName( name ) ) fprintf( stderr, "'%s' forbidden group name.\n", name ); else { Log_inf( "Creating new local group '%s'", name ); Grp_create( name ); Grp_setLocal( name ); printf( "New local group '%s' created.\n", name ); res = TRUE; snprintf( grp, MAXCHAR, "%s/groupinfo.lastupdate", Cfg_spoolDir() ); Utl_stamp( grp ); } return res; } static Bool doDeleteLocalGroup( const char * name ) { Str grp; Utl_cpyStr( grp, name ); Utl_toLower( grp ); name = Utl_stripWhiteSpace( grp ); if ( ! Grp_exists( name ) ) { fprintf( stderr, "'%s' does not exist.\n", name ); return FALSE; } if ( ! Grp_isValidName( name ) ) { fprintf( stderr, "'%s' invalid group name. Skipping deletion of overviews.\n", name ); Log_inf( "Deleting invalid group '%s' without deleting overviews.", name ); Grp_delete( name ); printf( "Group '%s' deleted.\n", name ); } else { int i; Log_inf( "Deleting group '%s'", name ); /* Delete all articles that are only in the group or are crossposted only to groups that do not exist on this server. */ Cont_read( name ); for ( i = Cont_first(); i <= Cont_last(); i++ ) { const Over *over; Bool toDelete; Str msgId; if ( ! Cont_validNumb( i ) ) continue; over = Cont_get( i ); toDelete = TRUE; if ( over != NULL ) { ItemList *xrefs; const char *xref; int localXrefs = 0; Utl_cpyStr( msgId, Ov_msgId( over ) ); xrefs = new_Itl( Db_xref( msgId ), " " ); for ( xref = Itl_first( xrefs ); xref != NULL; xref = Itl_next( xrefs) ) { Str xgrp; int no; if ( sscanf( xref, "%"MAXCHAR_STR"[^:]:%d", xgrp, &no ) != 2 ) { /* Malformed xref - leave article just in case */ Log_err( "Malformed Xref: entry in %s: %s", msgId, xref); toDelete = FALSE; break; } if ( Cont_exists( xgrp ) ) ++localXrefs; } if ( localXrefs > 1 ) toDelete = FALSE; del_Itl( xrefs ); } Cont_delete( i ); if ( toDelete ) Db_delete( msgId ); } if ( Cont_write() ) { Grp_delete( name ); printf( "Group '%s' deleted.\n", name ); } } return TRUE; } 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_server( name ), modeStr ); } } /* A modify command. argc/argv start AFTER '-m'. */ static Bool doModify( const char *cmd, int argc, char **argv ) { const char *grp; if ( argc < 2 ) { fprintf( stderr, "Insufficient arguments to -m\n" ); return FALSE; } else if ( strcmp( cmd, "desc" ) != 0 && strcmp( cmd, "post" ) != 0 ) { fprintf( stderr, "Unknown argument -m %s\n", cmd ); return FALSE; } grp = argv[ 0 ]; argv++; argc--; if ( strcmp( cmd, "desc" ) == 0 ) { Str desc; Utl_cpyStr( desc, *( argv++ ) ); while ( --argc > 0 ) { Utl_catStr( desc, " " ); Utl_catStr( desc, *( argv++ ) ); } Grp_setDsc( grp, desc ); } else { char c; if ( ! Grp_local( grp ) ) { fprintf( stderr, "%s is not a local group\n", grp ); return FALSE; } c = **argv; if ( c == 'y' || c == 'm' || c == 'n' ) Grp_setPostAllow( grp, c ); else { fprintf( stderr, "Access must be 'y', 'n' or 'm'\n" ); return FALSE; } } return TRUE; } 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%c\t%s\t%s\t%s\n", g, Grp_server( g ), Grp_first( g ), Grp_last( g ), Grp_rmtNext( g ), Grp_postAllow( 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 remote servers.\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" ); Grp_setLastAccess( name ); 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() ) Grp_setRmtNext( name, GRP_RMT_NEXT_NOT_SUBSCRIBED ); else fprintf( stderr, "Could not save fetchlist.\n" ); } static void doUninteresting( const char *msgId ) { unsigned status; Str server; if ( ! Db_contains( msgId ) ) fprintf( stderr, "Not in database.\n" ); else { /* Remove INTERESTING status */ status = Db_status( msgId ); status &= ~DB_INTERESTING; Db_setStatus( msgId, status ); /* And remove from requested articles queue if server is not local */ if ( ! Db_findServer( msgId, server ) ) Log_err( "No server for message %s", msgId ); else if ( strcmp( server, GRP_LOCAL_SERVER_NAME ) != 0 ) Req_remove( server, msgId ); } } 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" " -B | --rebuild Rebuild article database\n" " -c | --cancel <msg id> Remove article from database\n" " -C | --create <grp> Create a local group\n" " -d | --database Show content of article database\n" " -D | --delete <grp> Delete a group\n" " -e | --expire Expire articles\n" " -f | --fetch [server] 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" " -m | --modify desc <grp> <desc> Modify a group description\n" " -m | --modify post <grp> (y|m|n) Modify posting status of a local group\n" " -n | --online Switch to online mode\n" " -o | --offline Switch to offline mode\n" " -O | --outgoing [server] List articles queued for posting\n" " -p | --post Post article on stdin\n" " -q | --query groups [server] Get group list from server\n" " -q | --query desc [server] Get group descriptions from server\n" " -r | --server [auth] 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" " -U | --uninteresting <msg id> Article must be read again for download\n" " -v | --version Print version\n"; fprintf( stderr, "%s", msg ); } /* Check we are 'root' or the noffle user (usually 'news') */ static Bool checkCurrentUser( void ) { if ( ! Auth_checkPrivs() ) return FALSE; /* * If we're noffle.lockAtStartup, we need to drop privs now. * Otherwise we're a server, and privs get dropped after authentication. */ if ( noffle.lockAtStartup ) if ( ! Auth_dropPrivs() ) return FALSE; return TRUE; } /* Check file ownership and permissions. This assumes we have cd'd to the spool directory. */ static Bool checkFileOwnership( void ) { Str confFile; struct stat statBuf; Utl_cpyStr( confFile, CONFIGFILE ); if ( stat( confFile, &statBuf ) == 0 ) { if ( ( statBuf.st_mode & S_IROTH ) != 0 ) Log_inf( "Security warning: %s is globally readable.", confFile ); } return TRUE; } /* Allow core files: Change core limit and change working directory to spool directory, where news has write permissions. */ static void enableCorefiles( void ) { 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( LOG_DBG_NOFFLE, "Core limit set to %i", lim.rlim_max ); } static Bool initNoffle( void ) { Log_init( "noffle", noffle.lockAtStartup, LOG_NEWS ); Cfg_read(); Log_dbg( LOG_DBG_NOFFLE, "NOFFLE version %s", Cfg_version() ); if ( ! checkCurrentUser() ) return FALSE; /* cd to the spool directory */ if ( chdir( Cfg_spoolDir() ) != 0 ) { Log_err( "Cannot change to directory '%s'", Cfg_spoolDir() ); return FALSE; } Log_dbg( LOG_DBG_NOFFLE, "Changed to directory '%s'", Cfg_spoolDir() ); if ( ! checkFileOwnership() ) return FALSE; if ( noffle.lockAtStartup ) if ( ! Lock_openDatabases() ) return FALSE; enableCorefiles(); return TRUE; } static void closeNoffle( void ) { if ( Lock_gotLock() ) Lock_closeDatabases(); Lock_syncDatabases(); } static RETSIGTYPE bugReport( int sig ) { Log_err( "Received SIGSEGV. Please submit a bug report" ); signal( SIGSEGV, SIG_DFL ); /* Attempt to save database state before passing on sig. */ Lock_syncDatabases(); raise( sig ); } static RETSIGTYPE 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 ); /* Attempt to save database state before passing on sig. */ Lock_syncDatabases(); raise( sig ); } static void printInewsUsage( void ) { static const char *msg = "Usage: inews [-D] [-O] [-S] [input]\n" " -D Debug - send article to stdout and don't post\n" " -O Don't add Organization header\n" " -S Don't add .signature\n" " input File containing message, standard input if none specified.\n" "For compatability, -h, -A, -V, -W are ignored and -N is\n" "equivalent to -D.\n"; fprintf( stderr, "%s", msg ); } static int doInews( int argc, char **argv ) { int result; unsigned flags; FILE *f; UNUSED( argc ); noffle.lockAtStartup = TRUE; /* Process options */ flags = POST_ADD_ORG | POST_ADD_SIG | POST_ADD_FROM; for ( ; argv[0] != NULL && argv[0][0] == '-' ; argv++ ) { if ( argv[0][2] != '\0' ) { printInewsUsage(); return EXIT_FAILURE; } switch( argv[0][1] ) { case 'h': case 'A': case 'V': case 'W': break; case 'N': case 'D': flags |= POST_DEBUG; break; case 'O': flags &= ~POST_ADD_ORG; break; case 'S': flags &= ~POST_ADD_SIG; break; default: printInewsUsage(); return EXIT_FAILURE; } } if ( argv[0] == NULL ) f = stdin; else { f = fopen( argv[0], "r" ); if ( f == NULL ) { fprintf( stderr, "Can't access %s (%s).", argv[0], strerror( errno ) ); return EXIT_FAILURE; } } if ( ! initNoffle() ) return EXIT_FAILURE; result = EXIT_SUCCESS; if ( ! doPost( f, flags ) ) result = EXIT_FAILURE; if ( f != stdin ) fclose( f ); return result; } static int getArgLetter(const char *arg) { int res; struct option { const char *longOpt; const char *opt; } options[] = { { "--article", "-a" }, { "--cancel", "-c" }, { "--create", "-C" }, { "--database", "-d" }, { "--delete", "-D" }, { "--expire", "-e" }, { "--fetch", "-f" }, { "--groups", "-g" }, { "--help", "-h" }, { "--list", "-l" }, { "--modify", "-m" }, { "--offline", "-o" }, { "--online", "-n" }, { "--outgoing", "-O" }, { "--post", "-p" }, { "--query", "-q" }, { "--rebuild", "-B" }, { "--server", "-r" }, { "--requested", "-R" }, { "--subscribe-over", "-s" }, { "--subscribe-full", "-S" }, { "--subscribe-thread", "-t" }, { "--uninteresting", "-U" }, { "--unsubscribe", "-u" }, { "--version", "-v" }, { NULL, NULL } }; struct option *opt; res = -1; for ( opt = options; opt->opt != NULL; opt++ ) if ( strcmp( arg, opt->longOpt ) == 0 || strcmp( arg, opt->opt ) == 0 ) { res = opt->opt[1]; break; } return res; } /* Options available to ordinary users, as opposed to administrators. */ static const char* USER_OPTIONS = "adghlpRv"; int main ( int argc, char **argv ) { int c, result; const char *cmdname, *p; /* Attempt to ensure databases are properly closed when we exit */ atexit( closeNoffle ); signal( SIGSEGV, bugReport ); signal( SIGABRT, logSignal ); signal( SIGFPE, logSignal ); signal( SIGILL, logSignal ); signal( SIGINT, logSignal ); signal( SIGTERM, logSignal ); signal( SIGPIPE, logSignal ); /* Set umask, just in case. */ umask(022); /* Find last component of command name. */ cmdname = argv[0]; p = strrchr( cmdname, '/' ); if ( p != NULL ) cmdname = ++p; argv++; argc--; /* Were we invoked as inews? */ if ( strcmp( cmdname, "inews" ) == 0 ) return doInews( argc, argv ); c = 'h'; if ( *argv != NULL ) { c = getArgLetter( *argv ); argv++; argc--; } /* If they asked for help, give it before there's a possibility we could fail on init. */ if ( c == 'h' || c == -1 ) { printUsage(); return EXIT_SUCCESS; } noffle.lockAtStartup = ! ( c == 'r' ); if ( ! initNoffle() ) return EXIT_FAILURE; if ( ! Auth_admin() && strchr( USER_OPTIONS, c ) == NULL ) { fprintf( stderr, "You must be a news administrator to do that.\n" ); return EXIT_FAILURE; } result = EXIT_SUCCESS; switch ( c ) { case 'a': if ( *argv == NULL ) { fprintf( stderr, "Option -a needs argument.\n" ); result = EXIT_FAILURE; } else if ( ! doArt( *argv ) ) result = EXIT_FAILURE; break; case 'B': if ( ! doRebuild() ) result = EXIT_FAILURE; break; case 'c': if ( *argv == NULL ) { fprintf( stderr, "Option -c needs argument.\n" ); result = EXIT_FAILURE; } else doCancel( *argv ); break; case 'C': if ( *argv == NULL ) { fprintf( stderr, "Option -C needs argument.\n" ); result = EXIT_FAILURE; } else if ( ! doCreateLocalGroup( *argv ) ) result = EXIT_FAILURE; break; case 'd': doDb(); break; case 'D': if ( *argv == NULL ) { fprintf( stderr, "Option -D needs argument.\n" ); result = EXIT_FAILURE; } else if ( ! doDeleteLocalGroup( *argv ) ) result = EXIT_FAILURE; break; case 'e': doExpire(); break; case 'f': noffle.serverPattern = *argv; doFetch(); break; case 'g': doGrps(); break; case 'l': doList(); break; case 'm': if ( *argv == NULL ) { fprintf( stderr, "Option -m needs argument.\n" ); result = EXIT_FAILURE; } else if ( ! doModify( argv[0], --argc, &argv[1] ) ) result = EXIT_FAILURE; break; case 'n': if ( Online_true() ) fprintf( stderr, "NOFFLE is already online\n" ); else Online_set( TRUE ); break; case 'O': doOutgoing( *argv ); break; case 'o': if ( ! Online_true() ) fprintf( stderr, "NOFFLE is already offline\n" ); else Online_set( FALSE ); break; case 'p': if ( ! doPost( stdin, 0 ) ) result = EXIT_FAILURE; break; case 'q': if ( *argv == NULL ) { fprintf( stderr, "Option -q needs argument.\n" ); result = EXIT_FAILURE; } else { if ( strcmp( *argv, "groups" ) == 0 ) noffle.queryGrps = TRUE; else if ( strcmp( *argv, "desc" ) == 0 ) noffle.queryDsc = TRUE; else { fprintf( stderr, "Unknown argument -q %s\n", *argv ); result = EXIT_FAILURE; break; } argv++; argc--; noffle.serverPattern = *argv; doQuery(); } break; case 'r': if ( *argv != NULL ) { if ( strcmp( *argv, "auth" ) != 0 ) { fprintf( stderr, "Unknown argument -r %s\n", *argv ); result = EXIT_FAILURE; break; } Cfg_setClientAuth( TRUE ); p = ", authentication enabled from command line"; } else p = ""; Log_inf( "Starting as server%s", p ); Server_run(); break; case 'R': doRequested( *argv ); break; case 's': if ( *argv == NULL ) { fprintf( stderr, "Option -s needs argument.\n" ); result = EXIT_FAILURE; } else if ( ! doSubscribe( *argv, OVER ) ) result = EXIT_FAILURE; break; case 'S': if ( *argv == NULL ) { fprintf( stderr, "Option -S needs argument.\n" ); result = EXIT_FAILURE; } else if ( ! doSubscribe( *argv, FULL ) ) result = EXIT_FAILURE; break; case 't': if ( *argv == NULL ) { fprintf( stderr, "Option -t needs argument.\n" ); result = EXIT_FAILURE; } else if ( ! doSubscribe( *argv, THREAD ) ) result = EXIT_FAILURE; break; case 'u': if ( *argv == NULL ) { fprintf( stderr, "Option -u needs argument.\n" ); result = EXIT_FAILURE; } else doUnsubscribe( *argv ); break; case 'U': if ( *argv == NULL ) { fprintf( stderr, "Option -U needs argument.\n" ); result = EXIT_FAILURE; } else doUninteresting( *argv ); 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 */ } return result; }