Mercurial > noffle
view src/server.c @ 45:32ba1198c6fa noffle
[svn] * Makefile.in, configure, configure.in, docs/Makefile.in, src/Makefile.in:
Added checks for the mail and sort programs. ./configure will abort if
they're not found.
* README: Removed the paragraph about news client software, as it's the same
as the one in docs/NOTES.
* TODO: Removed 'expiring by groups' and
'move some text from noffle.1 to noffle.conf.5'.
* docs/NOTES: Changed the text about -DDEBUG to explain one should use
'./configure --enable-debug'.
* docs/noffle.1, docs/noffle.conf.5: Minor fixes. Added myself and Jim Hague
to the AUTHORS section :-)
* src/client.h, src/common.h, src/configfile.h, src/content.c, src/content.h,
src/control.c, src/control.h, src/database.h, src/dynamicstring.c,
src/dynamicstring.h, src/fetch.h, src/fetchlist.h, src/group.h,
src/itemlist.c, src/itemlist.h, src/lock.h, src/log.c, src/log.h,
src/noffle.c, src/online.h, src/outgoing.h, src/over.c, src/over.h,
src/post.h, src/protocol.h, src/pseudo.h, src/request.h, src/server.h,
src/util.c, src/util.h: Added the <config.h> include.
* src/content.c: Added missing include "content.h". Added a missing 'void'
in the declaration of clearCont().
* src/fetchlist.c: Casted fetchlist.size to (size_t) in a call to qsort(),
as qsort() expects a size_t. This removes a warning.
* src/noffle.c: Made doRequested() static. Added missing void to
enableCorefiles().
* src/log.c, src/protocol.c, src/online.c, src/pseudo.c: Added missing
includes.
* src/pseudo.c: Made genOv() and genPseudo() static.
* src/server.c: Added missing void to postArts(). Made touchArticle() static.
* src/util.c: Casted arguments of malloc() and memcpy() to size_t.
* src/dynamicstring.c, src/itemlist.c, src/over.c, src/request.c, src/util.c:
Removed casting of the result of malloc(). This is not necessary and
can hide a missing include of <stdlib.h>.
author | uh1763 |
---|---|
date | Sat, 06 May 2000 00:49:38 +0100 |
parents | 2842f50feb55 |
children | 5ecb646acf97 |
line wrap: on
line source
/* server.c $Id: server.c 51 2000-05-05 23:49:38Z uh1763 $ */ #if HAVE_CONFIG_H #include <config.h> #endif #include <stdio.h> #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 "configfile.h" #include "content.h" #include "control.h" #include "database.h" #include "dynamicstring.h" #include "fetch.h" #include "fetchlist.h" #include "group.h" #include "itemlist.h" #include "lock.h" #include "log.h" #include "online.h" #include "outgoing.h" #include "post.h" #include "protocol.h" #include "pseudo.h" #include "request.h" #include "util.h" #include "wildmat.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 ( ! Grp_local ( serv.grp ) && 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 || Grp_local( grp ) || time( NULL ) - Grp_lastAccess( serv.grp ) < 1800 ) return; if ( Fetch_init( Grp_serv( grp ) ) ) { Fetch_getNewArts( grp, OVER ); Fetch_close(); } } static void postArts( void ) { 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 ( ! Grp_local ( name ) && ! 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(); if ( ( first == 0 && last == 0 ) || first > last ) first = last = numb = 0; else numb = last - first + 1; 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 || strcmp( serv, GRP_LOCAL_SERVER_NAME ) == 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, "%d", &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; } static 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 && strcmp( serv, GRP_LOCAL_SERVER_NAME ) != 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 ( Wld_match( 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 ( Wld_match( 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 %d", 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 %d %d %c", grp, Grp_last( grp ), Grp_first( grp ), Grp_postAllow( 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; } /* Cancel and return TRUE if need to send cancel message on to server. */ static Bool controlCancel( const char *cancelId ) { return ( Ctrl_cancel( cancelId ) == CANCEL_NEEDS_MSG ); } /* It's a control message. Currently we only know about 'cancel' messages; others are passed on for outside groups, and logged as ignored for local groups. */ static Bool handleControl( ItemList *control, ItemList *newsgroups, const char *msgId, const DynStr *art ) { const char *grp; const char *op; Bool err = FALSE; Bool localDone = FALSE; op = Itl_first( control ); if ( op == NULL ) { Log_err( "Malformed control line." ); return TRUE; } else if ( strcasecmp( op, "cancel" ) == 0 ) { if ( controlCancel( Itl_next( control ) ) ) localDone = TRUE; else return err; } /* Pass on for outside groups. */ for( grp = Itl_first( newsgroups ); grp != NULL; grp = Itl_next( newsgroups ) ) { if ( Grp_exists( grp ) && ! Grp_local( grp ) ) { if ( ! Out_add( Grp_serv( grp ), msgId, art ) ) { Log_err( "Cannot add posted article to outgoing directory" ); err = TRUE; } break; } } if ( localDone ) return err; /* Log 'can't do' for internal groups. */ for( grp = Itl_first( newsgroups ); grp != NULL; grp = Itl_next( newsgroups ) ) { if ( Grp_exists( grp ) && Grp_local( grp ) ) Log_inf( "Ignoring control '%s' for '%s'.", op, grp ); } return err; } static Bool postArticle( ItemList *newsgroups, const char *msgId, const DynStr *art ) { const char *grp; Bool err; Bool oneLocal; err = oneLocal = FALSE; /* Run round first doing all local groups. */ for( grp = Itl_first( newsgroups ); grp != NULL; grp = Itl_next( newsgroups ) ) { if ( Grp_local( grp ) ) { if ( ! oneLocal ) { if ( ! Post_open( DynStr_str( art ) ) ) { err = TRUE; break; } else oneLocal = TRUE; } if ( ! Post_add( grp ) ) err = TRUE; } } if ( oneLocal ) Post_close(); /* Now look for a valid external group. */ for( grp = Itl_first( newsgroups ); grp != NULL; grp = Itl_next( newsgroups ) ) { if ( Grp_exists( grp ) && ! Grp_local( grp ) ) { if ( ! Out_add( Grp_serv( grp ), msgId, art ) ) { Log_err( "Cannot add posted article to outgoing directory" ); err = TRUE; } break; } } return err; } static Bool doPost( char *arg, const Cmd *cmd ) { Bool err, replyToFound, dateFound, inHeader; DynStr *s; Str line, field, val, msgId, from; const char* p; ItemList * newsgroups, *control; /* 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'; newsgroups = control = NULL; replyToFound = dateFound = 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 ); } if ( ! dateFound ) { time_t t; time( &t ); Utl_rfc822Date( t, val ); DynStr_app( s, "Date: " ); DynStr_appLn( s, val ); } 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 ) { Utl_toLower( val ); newsgroups = new_Itl ( val, " ," ); DynStr_appLn( s, p ); } else if ( strcmp( field, "control" ) == 0 ) { control = new_Itl ( val, " " ); DynStr_appLn( s, p ); } else if ( strcmp( field, "reply-to" ) == 0 ) { replyToFound = TRUE; DynStr_appLn( s, p ); } else if ( strcmp( field, "date" ) == 0 ) { dateFound = 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 ( newsgroups == NULL || Itl_count( newsgroups ) == 0 ) { Log_err( "Posted message has no valid Newsgroups header field" ); err = TRUE; } else { const char *grp; Bool knownGrp = FALSE; Bool postAllowedGrp = FALSE; /* Check at least one group is known. */ for( grp = Itl_first( newsgroups ); grp != NULL; grp = Itl_next( newsgroups ) ) { if ( Grp_exists( grp ) ) { knownGrp = TRUE; switch( Grp_postAllow( grp ) ) { case 'n': break; case 'm': /* Can't post to moderated local groups. */ postAllowedGrp = ! Grp_local( grp ); break; default: postAllowedGrp = TRUE; } if ( postAllowedGrp ) break; } } if ( ! knownGrp ) { Log_err( "No known group in Newsgroups header field" ); err = TRUE; } else if ( ! postAllowedGrp ) { Log_err( "No group permits posting" ); err = TRUE; } else { err = ( control == NULL ) ? postArticle( newsgroups, msgId, s ) : handleControl( control, newsgroups, msgId, s ); } } } if ( err ) putStat( STAT_POST_FAILED, "Posting failed" ); else { putStat( STAT_POST_OK, "Message posted" ); if ( Online_true() ) postArts(); } del_Itl( newsgroups ); del_Itl( control ); 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, "%d-%d", 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 %d-%d %s", whatStr, &first, &last, pat ) != 4 ) { if ( sscanf( arg, "%s %d- %s", whatStr, &first, pat ) == 3 ) last = Cont_last(); else if ( sscanf( arg, "%s %d %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 ( Wld_match( Ov_subj( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_subj( ov ) ); break; case FROM: if ( Wld_match( Ov_from( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_from( ov ) ); break; case DATE: if ( Wld_match( Ov_date( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_date( ov ) ); break; case MSG_ID: if ( Wld_match( Ov_msgId( ov ), pat ) ) putTxtLn( "%lu %s", n, Ov_msgId( ov ) ); break; case REF: if ( Wld_match( 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(); }