Mercurial > noffle
diff server.c @ 0:04124a4423d4 noffle
[svn] Initial revision
author | enz |
---|---|
date | Tue, 04 Jan 2000 11:35:42 +0000 |
parents | |
children | 43631b72021f |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server.c Tue Jan 04 11:35:42 2000 +0000 @@ -0,0 +1,1368 @@ +/* + server.c + + $Id: server.c 3 2000-01-04 11:35:42Z 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) + */ + 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 + Log_err( "Ignoring invalid header line '%s'", p ); + } + 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(); +}