Mercurial > noffle
view server.c @ 17:d30c5ce2eee1 noffle
[svn] *** empty log message ***
author | enz |
---|---|
date | Sat, 29 Apr 2000 13:41:06 +0100 |
parents | 43631b72021f |
children | 526a4c34ee2e |
line wrap: on
line source
/* server.c $Id: server.c 18 2000-04-15 10:09:20Z enz $ */ #include "server.h" #include <ctype.h> #include <signal.h> #include <stdarg.h> #include <sys/time.h> #include <sys/types.h> #include <time.h> #include <unistd.h> #include "client.h" #include "common.h" #include "config.h" #include "content.h" #include "database.h" #include "dynamicstring.h" #include "fetch.h" #include "fetchlist.h" #include "group.h" #include "lock.h" #include "log.h" #include "online.h" #include "outgoing.h" #include "protocol.h" #include "pseudo.h" #include "request.h" #include "util.h" struct { Bool running; int artPtr; Str grp; /* selected group, "" if none */ } serv = { FALSE, 0, "" }; typedef struct Cmd { const char *name; const char *syntax; /* Returns false, if quit cmd */ Bool (*cmdProc)( char *arg, const struct Cmd *cmd ); } Cmd; static Bool doArt( char *arg, const Cmd *cmd ); static Bool doBody( char *arg, const Cmd *cmd ); static Bool doGrp( char *arg, const Cmd *cmd ); static Bool doHead( char *arg, const Cmd *cmd ); static Bool doHelp( char *arg, const Cmd *cmd ); static Bool doIhave( char *arg, const Cmd *cmd ); static Bool doLast( char *arg, const Cmd *cmd ); static Bool doList( char *arg, const Cmd *cmd ); static Bool doListgrp( char *arg, const Cmd *cmd ); static Bool doMode( char *arg, const Cmd *cmd ); static Bool doNewgrps( char *arg, const Cmd *cmd ); static Bool doNext( char *arg, const Cmd *cmd ); static Bool doPost( char *arg, const Cmd *cmd ); static Bool doSlave( char *arg, const Cmd *cmd ); static Bool doStat( char *arg, const Cmd *cmd ); static Bool doQuit( char *arg, const Cmd *cmd ); static Bool doXhdr( char *arg, const Cmd *cmd ); static Bool doXpat( char *arg, const Cmd *cmd ); static Bool doXOver( char *arg, const Cmd *cmd ); static Bool notImplemented( char *arg, const Cmd *cmd ); static void putStat( unsigned int stat, const char *fmt, ... ); Cmd commands[] = { { "article", "ARTICLE [msg-id|n]", &doArt }, { "body", "BODY [msg-id|n]", &doBody }, { "head", "HEAD [msg-id|n]", &doHead }, { "group", "GROUP grp", &doGrp }, { "help", "HELP", &doHelp }, { "ihave", "IHAVE (ignored)", &doIhave }, { "last", "LAST", &doLast }, { "list", "LIST [ACTIVE [pat]]|ACTIVE.TIMES [pat]|" "EXTENSIONS|NEWSGROUPS [pat]|OVERVIEW.FMT", &doList }, { "listgroup", "LISTGROUP grp", &doListgrp }, { "mode", "MODE (ignored)", &doMode }, { "newgroups", "NEWGROUPS [xx]yymmdd hhmmss [GMT]", &doNewgrps }, { "newnews", "NEWNEWS (not implemented)", ¬Implemented }, { "next", "NEXT", &doNext }, { "post", "POST", &doPost }, { "quit", "QUIT", &doQuit }, { "slave", "SLAVE (ignored)", &doSlave }, { "stat", "STAT [msg-id|n]", &doStat }, { "xhdr", "XHDR over-field [m[-[n]]]", &doXhdr }, { "xpat", "XPAT over-field m[-[n]] pat", &doXpat }, { "xover", "XOVER [m[-[n]]]", &doXOver } }; /* Notice interest in reading this group. Automatically subscribe if option set in config file. */ static void noteInterest( void ) { FetchMode mode; Grp_setLastAccess( serv.grp, time( NULL ) ); if ( Cfg_autoSubscribe() && ! Online_true() ) { Fetchlist_read(); if ( ! Fetchlist_contains( serv.grp ) ) { if ( strcmp( Cfg_autoSubscribeMode(), "full" ) == 0 ) mode = FULL; else if ( strcmp( Cfg_autoSubscribeMode(), "thread" ) == 0 ) mode = THREAD; else mode = OVER; Fetchlist_add( serv.grp, mode ); Fetchlist_write(); Pseudo_autoSubscribed(); } } } static void putStat( unsigned int stat, const char *fmt, ... ) { Str s, line; va_list ap; ASSERT( stat <= 999 ); va_start( ap, fmt ); vsnprintf( s, MAXCHAR, fmt, ap ); va_end( ap ); snprintf( line, MAXCHAR, "%u %s", stat, s ); Log_dbg( "[S] %s", line ); printf( "%s\r\n", line ); } static void putTxtLn( const char *fmt, ... ) { Str line; va_list ap; va_start( ap, fmt ); vsnprintf( line, MAXCHAR, fmt, ap ); va_end( ap ); Prt_putTxtLn( line, stdout ); } static void putTxtBuf( const char *buf ) { if ( buf ) Prt_putTxtBuf( buf, stdout ); } static void putEndOfTxt( void ) { Prt_putEndOfTxt( stdout ); } static void putSyntax( const Cmd *cmd ) { putStat( STAT_SYNTAX_ERR, "Syntax error. Usage: %s", cmd->syntax ); } static Bool getLn( Str line ) { return Prt_getLn( line, stdin ); } static Bool getTxtLn( Str line, Bool *err ) { return Prt_getTxtLn( line, err, stdin ); } static Bool notImplemented( char *arg, const Cmd *cmd ) { putStat( STAT_NO_PERMISSION, "Command not implemented" ); return TRUE; } static void checkNewArts( const char *grp ) { if ( ! Online_true() || strcmp( grp, serv.grp ) == 0 || time( NULL ) - Grp_lastAccess( serv.grp ) < 1800 ) return; if ( Fetch_init( Grp_serv( grp ) ) ) { Fetch_getNewArts( grp, OVER ); Fetch_close(); } } static void postArts() { Str serv; Cfg_beginServEnum(); while ( Cfg_nextServ( serv ) ) if ( Fetch_init( serv ) ) { Fetch_postArts(); Fetch_close(); } } static void readCont( const char *name ) { Fetchlist_read(); Cont_read( name ); if ( ! Fetchlist_contains( name ) && ! Online_true() ) { Pseudo_appGeneralInfo(); Grp_setFirstLast( name, Cont_first(), Cont_last() ); } } static void changeToGrp( const char *grp ) { checkNewArts( grp ); Utl_cpyStr( serv.grp, grp ); readCont( grp ); serv.artPtr = Cont_first(); } static Bool doGrp( char *arg, const Cmd *cmd ) { int first, last, numb; if ( arg[ 0 ] == '\0' ) putSyntax( cmd ); else if ( ! Grp_exists( arg ) ) putStat( STAT_NO_SUCH_GRP, "No such group" ); else { changeToGrp( arg ); first = Cont_first(); last = Cont_last(); numb = last - first + 1; if ( first > last ) first = last = numb = 0; putStat( STAT_GRP_SELECTED, "%lu %lu %lu %s selected", numb, first, last, arg ); } return TRUE; } static Bool testGrpSelected( void ) { if ( *serv.grp == '\0' ) { putStat( STAT_NO_GRP_SELECTED, "No group selected" ); return FALSE; } return TRUE; } static void findServ( const char *msgId, Str result ) { const char *p, *pColon, *serv; Str s, grp; Utl_cpyStr( result, "(unknown)" ); if ( Db_contains( msgId ) ) { Utl_cpyStr( s, Db_xref( msgId ) ); p = strtok( s, " \t" ); if ( p ) do { pColon = strstr( p, ":" ); if ( pColon ) { Utl_cpyStrN( grp, p, pColon - p ); serv = Grp_serv( grp ); if ( Cfg_servIsPreferential( serv, result ) ) Utl_cpyStr( result, serv ); } } while ( ( p = strtok( NULL, " \t" ) ) ); } } static Bool retrieveArt( const char *msgId ) { Str serv; findServ( msgId, serv ); if ( strcmp( serv, "(unknown)" ) == 0 ) return FALSE; if ( ! Client_connect( serv ) ) return FALSE; Client_retrieveArt( msgId ); Client_disconnect(); return TRUE; } static Bool checkNumb( int numb ) { if ( ! testGrpSelected() ) return FALSE; if ( ! Cont_validNumb( numb ) ) { putStat( STAT_NO_SUCH_NUMB, "No such article" ); return FALSE; } return TRUE; } /* Parse arguments for ARTICLE, BODY, HEAD, STAT commands. Return message-ID and article number (0 if unknown). */ static Bool whichId( const char **msgId, int *numb, char *arg ) { const Over *ov; int n; if ( sscanf( arg, "%i", &n ) == 1 ) { if ( ! checkNumb( n ) ) return FALSE; serv.artPtr = n; ov = Cont_get( n ); *msgId = Ov_msgId( ov ); *numb = n; } else if ( strcmp( arg, "" ) == 0 ) { if ( ! checkNumb( serv.artPtr ) ) return FALSE; ov = Cont_get( serv.artPtr ); *msgId = Ov_msgId( ov ); *numb = serv.artPtr; } else { *msgId = arg; *numb = 0; } if ( ! Pseudo_isGeneralInfo( *msgId ) && ! Db_contains( *msgId ) ) { putStat( STAT_NO_SUCH_NUMB, "No such article" ); return FALSE; } return TRUE; } void touchArticle( const char *msgId ) { int stat = Db_stat( msgId ); stat |= DB_INTERESTING; Db_setStat( msgId, stat ); Db_updateLastAccess( msgId ); } static void touchReferences( const char *msgId ) { Str s; int len; char *p; const char *ref = Db_ref( msgId ); while ( TRUE ) { p = s; while ( *ref != '<' ) if ( *(ref++) == '\0' ) return; len = 0; while ( *ref != '>' ) { if ( *ref == '\0' || ++len >= MAXCHAR - 1 ) return; *(p++) = *(ref++); } *(p++) = '>'; *p = '\0'; if ( Db_contains( s ) ) touchArticle( s ); } } static void doBodyInDb( const char *msgId ) { int stat; Str serv; touchArticle( msgId ); touchReferences( msgId ); stat = Db_stat( msgId ); if ( Online_true() && ( stat & DB_NOT_DOWNLOADED ) ) { retrieveArt( msgId ); stat = Db_stat( msgId ); } if ( stat & DB_RETRIEVING_FAILED ) { Db_setStat( msgId, stat & ~DB_RETRIEVING_FAILED ); putTxtBuf( Db_body( msgId ) ); } else if ( stat & DB_NOT_DOWNLOADED ) { findServ( msgId, serv ); if ( Req_contains( serv, msgId ) ) putTxtBuf( Pseudo_alreadyMarkedBody() ); else if ( strcmp( serv, "(unknown)" ) != 0 && Req_add( serv, msgId ) ) putTxtBuf( Pseudo_markedBody() ); else putTxtBuf( Pseudo_markingFailedBody() ); } else putTxtBuf( Db_body( msgId ) ); } static Bool doBody( char *arg, const Cmd *cmd ) { const char *msgId; int numb; if ( ! whichId( &msgId, &numb, arg ) ) return TRUE; putStat( STAT_BODY_FOLLOWS, "%ld %s Body", numb, msgId ); if ( Pseudo_isGeneralInfo( msgId ) ) putTxtBuf( Pseudo_generalInfoBody() ); else doBodyInDb( msgId ); putEndOfTxt(); noteInterest(); return TRUE; } static void doHeadInDb( const char *msgId ) { putTxtBuf( Db_header( msgId ) ); } static Bool doHead( char *arg, const Cmd *cmd ) { const char *msgId; int numb; if ( ! whichId( &msgId, &numb, arg ) ) return TRUE; putStat( STAT_HEAD_FOLLOWS, "%ld %s Head", numb, msgId ); if ( Pseudo_isGeneralInfo( msgId ) ) putTxtBuf( Pseudo_generalInfoHead() ); else doHeadInDb( msgId ); putEndOfTxt(); return TRUE; } static void doArtInDb( const char *msgId ) { doHeadInDb( msgId ); putTxtLn( "" ); doBodyInDb( msgId ); } static Bool doArt( char *arg, const Cmd *cmd ) { const char *msgId; int numb; if ( ! whichId( &msgId, &numb, arg ) ) return TRUE; putStat( STAT_ART_FOLLOWS, "%ld %s Article", numb, msgId ); if ( Pseudo_isGeneralInfo( msgId ) ) { putTxtBuf( Pseudo_generalInfoHead() ); putTxtLn( "" ); putTxtBuf( Pseudo_generalInfoBody() ); } else doArtInDb( msgId ); putEndOfTxt(); noteInterest(); return TRUE; } static Bool doHelp( char *arg, const Cmd *cmd ) { unsigned int i; putStat( STAT_HELP_FOLLOWS, "Help" ); putTxtBuf( "\nCommands:\n\n" ); for ( i = 0; i < sizeof( commands ) / sizeof( commands[ 0 ] ); ++i ) putTxtLn( "%s", commands[ i ].syntax ); putEndOfTxt(); return TRUE; } static Bool doIhave( char *arg, const Cmd *cmd ) { putStat( STAT_ART_REJECTED, "Command not used" ); return TRUE; } static Bool doLast( char *arg, const Cmd *cmd ) { int n; if ( testGrpSelected() ) { n = serv.artPtr; if ( ! Cont_validNumb( n ) ) putStat( STAT_NO_ART_SELECTED, "No article selected" ); else { while ( ! Cont_validNumb( --n ) && n >= Cont_first() ); if ( ! Cont_validNumb( n ) ) putStat( STAT_NO_PREV_ART, "No previous article" ); else { putStat( STAT_ART_RETRIEVED, "%ld %s selected", n, Ov_msgId( Cont_get( n ) ) ); serv.artPtr = n; } } } return TRUE; } static void printGroups( const char *pat, void (*printProc)( Str, const char* ) ) { Str line; const char *g; FILE *f; sig_t lastHandler; int ret; putStat( STAT_GRPS_FOLLOW, "Groups" ); fflush( stdout ); Log_dbg( "[S FLUSH]" ); if ( Grp_exists( pat ) ) { (*printProc)( line, pat ); if ( ! Prt_putTxtLn( line, stdout ) ) Log_err( "Writing to stdout failed." ); } else { lastHandler = signal( SIGPIPE, SIG_IGN ); f = popen( "sort", "w" ); if ( f == NULL ) { Log_err( "Cannot open pipe to 'sort'" ); if ( Grp_firstGrp( &g ) ) do if ( Utl_matchPattern( g, pat ) ) { (*printProc)( line, g ); if ( ! Prt_putTxtLn( line, stdout ) ) Log_err( "Writing to stdout failed." ); } while ( Grp_nextGrp( &g ) ); } else { if ( Grp_firstGrp( &g ) ) do if ( Utl_matchPattern( g, pat ) ) { (*printProc)( line, g ); if ( ! Prt_putTxtLn( line, f ) ) { Log_err( "Writing to 'sort' pipe failed." ); break; } } while ( Grp_nextGrp( &g ) ); ret = pclose( f ); if ( ret != EXIT_SUCCESS ) Log_err( "sort command returned %i", ret ); fflush( stdout ); Log_dbg( "[S FLUSH]" ); signal( SIGPIPE, lastHandler ); } } putEndOfTxt(); } static void printActiveTimes( Str result, const char *grp ) { snprintf( result, MAXCHAR, "%s %ld", grp, Grp_created( grp ) ); } static void doListActiveTimes( const char *pat ) { printGroups( pat, &printActiveTimes ); } static void printActive( Str result, const char *grp ) { snprintf( result, MAXCHAR, "%s %i %i y", grp, Grp_last( grp ), Grp_first( grp ) ); } static void doListActive( const char *pat ) { printGroups( pat, &printActive ); } static void printNewsgrp( Str result, const char *grp ) { snprintf( result, MAXCHAR, "%s %s", grp, Grp_dsc( grp ) ); } static void doListNewsgrps( const char *pat ) { printGroups( pat, &printNewsgrp ); } static void putGrp( const char *name ) { putTxtLn( "%s %lu %lu y", name, Grp_last( name ), Grp_first( name ) ); } static void doListOverFmt( void ) { putStat( STAT_GRPS_FOLLOW, "Overview format" ); putTxtBuf( "Subject:\n" "From:\n" "Date:\n" "Message-ID:\n" "References:\n" "Bytes:\n" "Lines:\n" ); putEndOfTxt(); } static void doListExtensions( void ) { putStat( STAT_CMD_OK, "Extensions" ); putTxtBuf( " LISTGROUP\n" " XOVER\n" ); putEndOfTxt(); } static Bool doList( char *line, const Cmd *cmd ) { Str s, arg; const char *pat; if ( sscanf( line, "%s", s ) != 1 ) doListActive( "*" ); else { Utl_toLower( s ); strcpy( arg, Utl_restOfLn( line, 1 ) ); pat = Utl_stripWhiteSpace( arg ); if ( pat[ 0 ] == '\0' ) pat = "*"; if ( strcmp( "active", s ) == 0 ) doListActive( pat ); else if ( strcmp( "overview.fmt", s ) == 0 ) doListOverFmt(); else if ( strcmp( "newsgroups", s ) == 0 ) doListNewsgrps( pat ); else if ( strcmp( "active.times", s ) == 0 ) doListActiveTimes( pat ); else if ( strcmp( "extensions", s ) == 0 ) doListExtensions(); else putSyntax( cmd ); } return TRUE; } static Bool doListgrp( char *arg, const Cmd *cmd ) { const Over *ov; int first, last, i; if ( ! Grp_exists( arg ) ) putStat( STAT_NO_SUCH_GRP, "No such group" ); else { changeToGrp( arg ); first = Cont_first(); last = Cont_last(); putStat( STAT_GRP_SELECTED, "Article list" ); for ( i = first; i <= last; ++i ) if ( ( ov = Cont_get( i ) ) ) putTxtLn( "%lu", i ); putEndOfTxt(); } return TRUE; } static Bool doMode( char *arg, const Cmd *cmd ) { putStat( STAT_READY_POST_ALLOW, "Ok" ); return TRUE; } static unsigned long getTimeInSeconds( unsigned int year, unsigned int mon, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec ) { struct tm t = { 0 }; t.tm_year = year - 1900; t.tm_mon = mon - 1; t.tm_mday = day; t.tm_hour = hour; t.tm_min = min; t.tm_sec = sec; return mktime( &t ); } static Bool doNewgrps( char *arg, const Cmd *cmd ) { time_t t, now, lastUpdate; unsigned int year, mon, day, hour, min, sec, cent, len; const char *g; Str date, timeofday, file; if ( sscanf( arg, "%s %s", date, timeofday ) != 2 ) { putSyntax( cmd ); return TRUE; } len = strlen( date ); switch ( len ) { case 6: if ( sscanf( date, "%2u%2u%2u", &year, &mon, &day ) != 3 ) { putSyntax( cmd ); return TRUE; } now = time( NULL ); cent = 1900; while ( now > getTimeInSeconds( cent + 100, 1, 1, 0, 0, 0 ) ) cent += 100; year += cent; break; case 8: if ( sscanf( date, "%4u%2u%2u", &year, &mon, &day ) != 3 ) { putSyntax( cmd ); return TRUE; } break; default: putSyntax( cmd ); return TRUE; } if ( sscanf( timeofday, "%2u%2u%2u", &hour, &min, &sec ) != 3 ) { putSyntax( cmd ); return TRUE; } if ( year < 1970 || mon == 0 || mon > 12 || day == 0 || day > 31 || hour > 23 || min > 59 || sec > 60 ) { putSyntax( cmd ); return TRUE; } snprintf( file, MAXCHAR, "%s/groupinfo.lastupdate", Cfg_spoolDir() ); t = getTimeInSeconds( year, mon, day, hour, min, sec ); putStat( STAT_NEW_GRP_FOLLOW, "New groups since %s", arg ); if ( ! Utl_getStamp( &lastUpdate, file ) || t <= lastUpdate ) { if ( Grp_firstGrp( &g ) ) do if ( Grp_created( g ) > t ) putGrp( g ); while ( Grp_nextGrp( &g ) ); } putEndOfTxt(); return TRUE; } static Bool doNext( char *arg, const Cmd *cmd ) { int n; if ( testGrpSelected() ) { n = serv.artPtr; if ( ! Cont_validNumb( n ) ) putStat( STAT_NO_ART_SELECTED, "No article selected" ); else { while ( ! Cont_validNumb( ++n ) && n <= Cont_last() ); if ( ! Cont_validNumb( n ) ) putStat( STAT_NO_NEXT_ART, "No next article" ); else { putStat( STAT_ART_RETRIEVED, "%ld %s selected", n, Ov_msgId( Cont_get( n ) ) ); serv.artPtr = n; } } } return TRUE; } /* Get first group of the Newsgroups field content, which is a comma separated list of groups. */ static void getFirstGrp( char *grpResult, const char *list ) { Str t; const char *src = list; char *dest = t; while( TRUE ) { if ( *src == ',' ) *dest = ' '; else *dest = *src; if ( *src == '\0' ) break; ++src; ++dest; } *grpResult = '\0'; sscanf( t, "%s", grpResult ); } static Bool doPost( char *arg, const Cmd *cmd ) { Bool err, replyToFound, inHeader; DynStr *s; Str line, field, val, msgId, from, grp; const char* p; /* Get article and make following changes to the header: - add/replace/cut Message-ID depending on config options - add Reply-To with content of From, if missing (some providers overwrite From field) - rename X-Sender header to X-NOFFLE-X-Sender (some providers want to insert their own X-Sender) For doing this, it is not necessary to parse multiple-line headers. */ putStat( STAT_SEND_ART, "Continue (end with period)" ); fflush( stdout ); Log_dbg( "[S FLUSH]" ); s = new_DynStr( 10000 ); msgId[ 0 ] = '\0'; from[ 0 ] = '\0'; grp[ 0 ] = '\0'; replyToFound = FALSE; inHeader = TRUE; while ( getTxtLn( line, &err ) ) { if ( inHeader ) { p = Utl_stripWhiteSpace( line ); if ( *p == '\0' ) { inHeader = FALSE; if ( from[ 0 ] == '\0' ) Log_err( "Posted message has no From field" ); if ( ! Cfg_removeMsgId() ) { if ( Cfg_replaceMsgId() ) { Prt_genMsgId( msgId, from, "NOFFLE" ); Log_dbg( "Replacing Message-ID with '%s'", msgId ); } else if ( msgId[ 0 ] == '\0' ) { Prt_genMsgId( msgId, from, "NOFFLE" ); Log_inf( "Adding missing Message-ID '%s'", msgId ); } else if ( ! Prt_isValidMsgId( msgId ) ) { Log_ntc( "Replacing invalid Message-ID with '%s'", msgId ); Prt_genMsgId( msgId, from, "NOFFLE" ); } DynStr_app( s, "Message-ID: " ); DynStr_appLn( s, msgId ); } if ( ! replyToFound && from[ 0 ] != '\0' ) { Log_dbg( "Adding Reply-To field to posted message." ); DynStr_app( s, "Reply-To: " ); DynStr_appLn( s, from ); } DynStr_appLn( s, p ); } else if ( Prt_getField( field, val, p ) ) { if ( strcmp( field, "message-id" ) == 0 ) strcpy( msgId, val ); else if ( strcmp( field, "from" ) == 0 ) { strcpy( from, val ); DynStr_appLn( s, p ); } else if ( strcmp( field, "newsgroups" ) == 0 ) { getFirstGrp( grp, val ); Utl_toLower( grp ); DynStr_appLn( s, p ); } else if ( strcmp( field, "reply-to" ) == 0 ) { replyToFound = TRUE; DynStr_appLn( s, p ); } else if ( strcmp( field, "x-sender" ) == 0 ) { DynStr_app( s, "X-NOFFLE-X-Sender: " ); DynStr_appLn( s, val ); } else DynStr_appLn( s, p ); } else DynStr_appLn( s, line ); } else DynStr_appLn( s, line ); } if ( inHeader ) Log_err( "Posted message has no body" ); if ( ! err ) { if ( grp[ 0 ] == '\0' ) { Log_err( "Posted message has no Newsgroups header field" ); err = TRUE; } else if ( ! Grp_exists( grp ) ) { Log_err( "Unknown group in Newsgroups header field" ); err = TRUE; } else if ( ! Out_add( Grp_serv( grp ), msgId, s ) ) { Log_err( "Cannot add posted article to outgoing directory" ); err = TRUE; } } if ( err ) putStat( STAT_POST_FAILED, "Posting failed" ); else { putStat( STAT_POST_OK, "Message queued for posting" ); if ( Online_true() ) postArts(); } del_DynStr( s ); return TRUE; } static void parseRange( const char *s, int *first, int *last, int *numb ) { int r, i; char* p; Str t; Utl_cpyStr( t, s ); p = Utl_stripWhiteSpace( t ); r = sscanf( p, "%i-%i", first, last ); if ( r < 1 ) { *first = serv.artPtr; *last = serv.artPtr; } else if ( r == 1 ) { if ( p[ strlen( p ) - 1 ] == '-' ) *last = Cont_last(); else *last = *first; } if ( *first < Cont_first() ) *first = Cont_first(); if ( *last > Cont_last() ) *last = Cont_last(); if ( *first > Cont_last() || *last < Cont_first() ) *last = *first - 1; *numb = 0; for ( i = *first; i <= *last; ++i ) if ( Cont_validNumb( i ) ) ++(*numb); } static Bool doXhdr( char *arg, const Cmd *cmd ) { int first, last, i, n, numb; enum { SUBJ, FROM, DATE, MSG_ID, REF, BYTES, LINES } what; const char *p; const Over *ov; Str whatStr; if ( ! testGrpSelected() ) return TRUE; if ( sscanf( arg, "%s", whatStr ) != 1 ) { putSyntax( cmd ); return TRUE; } Utl_toLower( whatStr ); if ( strcmp( whatStr, "subject" ) == 0 ) what = SUBJ; else if ( strcmp( whatStr, "from" ) == 0 ) what = FROM; else if ( strcmp( whatStr, "date" ) == 0 ) what = DATE; else if ( strcmp( whatStr, "message-id" ) == 0 ) what = MSG_ID; else if ( strcmp( whatStr, "references" ) == 0 ) what = REF; else if ( strcmp( whatStr, "bytes" ) == 0 ) what = BYTES; else if ( strcmp( whatStr, "lines" ) == 0 ) what = LINES; else { putStat( STAT_HEAD_FOLLOWS, "Unknown header (empty list follows)" ); putEndOfTxt(); return TRUE; } p = Utl_restOfLn( arg, 1 ); parseRange( p, &first, &last, &numb ); if ( numb == 0 ) putStat( STAT_NO_ART_SELECTED, "No articles selected" ); else { putStat( STAT_HEAD_FOLLOWS, "%s header %lu-%lu", whatStr, first, last ) ; for ( i = first; i <= last; ++i ) if ( ( ov = Cont_get( i ) ) ) { n = Ov_numb( ov ); switch ( what ) { case SUBJ: putTxtLn( "%lu %s", n, Ov_subj( ov ) ); break; case FROM: putTxtLn( "%lu %s", n, Ov_from( ov ) ); break; case DATE: putTxtLn( "%lu %s", n, Ov_date( ov ) ); break; case MSG_ID: putTxtLn( "%lu %s", n, Ov_msgId( ov ) ); break; case REF: putTxtLn( "%lu %s", n, Ov_ref( ov ) ); break; case BYTES: putTxtLn( "%lu %d", n, Ov_bytes( ov ) ); break; case LINES: putTxtLn( "%lu %d", n, Ov_lines( ov ) ); break; default: ASSERT( FALSE ); } } putEndOfTxt(); } return TRUE; } static Bool doXpat( char *arg, const Cmd *cmd ) { int first, last, i, n; enum { SUBJ, FROM, DATE, MSG_ID, REF } what; const Over *ov; Str whatStr, pat; if ( ! testGrpSelected() ) return TRUE; if ( sscanf( arg, "%s %i-%i %s", whatStr, &first, &last, pat ) != 4 ) { if ( sscanf( arg, "%s %i- %s", whatStr, &first, pat ) == 3 ) last = Cont_last(); else if ( sscanf( arg, "%s %i %s", whatStr, &first, pat ) == 3 ) last = first; else { putSyntax( cmd ); return TRUE; } } Utl_toLower( whatStr ); if ( strcmp( whatStr, "subject" ) == 0 ) what = SUBJ; else if ( strcmp( whatStr, "from" ) == 0 ) what = FROM; else if ( strcmp( whatStr, "date" ) == 0 ) what = DATE; else if ( strcmp( whatStr, "message-id" ) == 0 ) what = MSG_ID; else if ( strcmp( whatStr, "references" ) == 0 ) what = REF; else { putStat( STAT_HEAD_FOLLOWS, "invalid header (empty list follows)" ); putEndOfTxt(); return TRUE; } putStat( STAT_HEAD_FOLLOWS, "header" ) ; for ( i = first; i <= last; ++i ) if ( ( ov = Cont_get( i ) ) ) { n = Ov_numb( ov ); switch ( what ) { case SUBJ: if ( Utl_matchPattern( Ov_subj( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_subj( ov ) ); break; case FROM: if ( Utl_matchPattern( Ov_from( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_from( ov ) ); break; case DATE: if ( Utl_matchPattern( Ov_date( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_date( ov ) ); break; case MSG_ID: if ( Utl_matchPattern( Ov_msgId( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_msgId( ov ) ); break; case REF: if ( Utl_matchPattern( Ov_ref( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_ref( ov ) ); break; default: ASSERT( FALSE ); } } putEndOfTxt(); return TRUE; } static Bool doSlave( char *arg, const Cmd *cmd ) { putStat( STAT_CMD_OK, "Ok" ); return TRUE; } static Bool doStat( char *arg, const Cmd *cmd ) { const char *msgId; int numb; if ( ! whichId( &msgId, &numb, arg ) ) return TRUE; if ( numb > 0 ) putStat( STAT_ART_RETRIEVED, "%ld %s selected", numb, msgId ); else putStat( STAT_ART_RETRIEVED, "0 %s selected", msgId ); return TRUE; } static Bool doQuit( char *arg, const Cmd *cmd ) { putStat( STAT_GOODBYE, "Goodbye" ); return FALSE; } static Bool doXOver( char *arg, const Cmd *cmd ) { int first, last, i, n; const Over *ov; if ( ! testGrpSelected() ) return TRUE; parseRange( arg, &first, &last, &n ); if ( n == 0 ) putStat( STAT_NO_ART_SELECTED, "No articles selected" ); else { putStat( STAT_OVERS_FOLLOW, "Overview %ld-%ld", first, last ); for ( i = first; i <= last; ++i ) if ( ( ov = Cont_get( i ) ) ) putTxtLn( "%lu\t%s\t%s\t%s\t%s\t%s\t%d\t%d\t", Ov_numb( ov ), Ov_subj( ov ), Ov_from( ov ), Ov_date( ov ), Ov_msgId( ov ), Ov_ref( ov ), Ov_bytes( ov ), Ov_lines( ov ) ); putEndOfTxt(); } return TRUE; } static void putFatal( const char *fmt, ... ) { va_list ap; Str s; va_start( ap, fmt ); vsnprintf( s, MAXCHAR, fmt, ap ); va_end( ap ); Log_err( s ); putStat( STAT_PROGRAM_FAULT, "%s", s ); fflush( stdout ); Log_dbg( "[S FLUSH]" ); } /* Parse line, execute command and return FALSE, if it was the quit command. */ static Bool parseAndExecute( Str line ) { unsigned int i, n; Cmd *c; Str s, arg; Bool ret; if ( sscanf( line, "%s", s ) == 1 ) { Utl_toLower( s ); strcpy( arg, Utl_restOfLn( line, 1 ) ); n = sizeof( commands ) / sizeof( commands[ 0 ] ); for ( i = 0, c = commands; i < n; ++i, ++c ) if ( strcmp( c->name, s ) == 0 ) { ret = c->cmdProc( Utl_stripWhiteSpace( arg ), c ); fflush( stdout ); Log_dbg( "[S FLUSH]" ); return ret; } } putStat( STAT_NO_SUCH_CMD, "Command not recognized" ); fflush( stdout ); Log_dbg( "[S FLUSH]" ); return TRUE; } static void putWelcome( void ) { putStat( STAT_READY_POST_ALLOW, "NNTP server NOFFLE %s", Cfg_version() ); fflush( stdout ); Log_dbg( "[S FLUSH]" ); } static Bool initServ( void ) { ASSERT( ! serv.running ); if ( ! Lock_openDatabases() ) return FALSE; serv.running = TRUE; return TRUE; } static void closeServ( void ) { ASSERT( serv.running ); serv.running = FALSE; Lock_closeDatabases(); } void Serv_run( void ) { Bool done; int r; Str line; struct timeval timeOut; fd_set readSet; putWelcome(); done = FALSE; while ( ! done ) { FD_ZERO( &readSet ); FD_SET( STDIN_FILENO, &readSet ); /* Never hold lock more than 5 seconds (empirically good value, avoids to close/open databases, if clients sends several commands, but releases the lock often enough, for allowing multiple persons to read news at the same time) */ timeOut.tv_sec = 5; timeOut.tv_usec = 0; r = select( STDIN_FILENO + 1, &readSet, NULL, NULL, &timeOut ); if ( r < 0 ) done = TRUE; else if ( r == 0 ) { if ( serv.running ) closeServ(); } else /* ( r > 0 ) */ { if ( ! serv.running ) { if ( ! initServ() ) { putFatal( "Cannot init server" ); done = TRUE; } } if ( ! getLn( line ) ) { Log_inf( "Client disconnected. Terminating." ); done = TRUE; } else if ( ! parseAndExecute( line ) ) done = TRUE; } } if ( serv.running ) closeServ(); }