Mercurial > noffle
view src/client.c @ 58:b4e6f7f96135 noffle
[svn] Add some intermediate variables for easier debugging in needsMark().
It seems that thread mode is sometimes not working.
Changed some variable types and used some casts to avoid compiler
warnings about signedness. In general, int should be used for parameters
for allowing a signedness assertion in the function.
author | enz |
---|---|
date | Fri, 12 May 2000 17:52:07 +0100 |
parents | 84e32c84666b |
children | adf0af5152f7 |
line wrap: on
line source
/* client.c $Id: client.c 64 2000-05-12 16:52:07Z enz $ */ #if HAVE_CONFIG_H #include <config.h> #endif #include "client.h" #include <stdio.h> #include <arpa/inet.h> #include <ctype.h> #include <netdb.h> #include <netinet/in.h> #include <signal.h> #include <stdarg.h> #include <sys/socket.h> #include <unistd.h> #include "configfile.h" #include "content.h" #include "dynamicstring.h" #include "group.h" #include "log.h" #include "over.h" #include "protocol.h" #include "pseudo.h" #include "request.h" #include "util.h" #include "wildmat.h" #include "portable.h" /* Some newsgroups names are reserved for server-specific or server pseudo groups. We don't want to fetch them. For example, INN keeps all its control messages in a 'control' hierarchy, and used the "to." heirarchy for dark and mysterious purposes I think are to do with newsfeeds. The recommended restrictions are documented in C.Lindsay, "News Article Format", <draft-ietf-usefor-article-03.txt>. */ struct ForbiddenGroupName { const char *pattern; Bool match; } forbiddenGroupNames[] = { { "*.*", FALSE }, /* Single component */ { "control.*", TRUE }, /* control.* groups */ { "to.*", TRUE }, /* control.* groups */ { "*.all", TRUE }, /* 'all' as a component */ { "*.all.*", TRUE }, { "all.*", TRUE }, { "*.ctl", TRUE }, /* 'ctl' as a component */ { "*.ctl.*", TRUE }, { "ctl.*", TRUE } }; struct { FILE* in; /* Receiving socket from server */ FILE* out; /* Sending socket to server */ Str lastCmd; /* Last command line */ Str lastStat; /* Response from server to last command */ Str grp; /* Selected group */ int rmtFirst; /* First article of current group at server */ int rmtLast; /* Last article of current group at server */ Bool auth; /* Authetication already done? */ Str serv; /* Remote server name */ } client = { NULL, NULL, "", "", "", 1, 0, FALSE, "" }; static void logBreakDown( void ) { Log_err( "Connection to remote server lost " "(article numbers could be inconsistent)" ); } static Bool getLn( Str line ) { Bool r; r = Prt_getLn( line, client.in ); if ( ! r ) logBreakDown(); return r; } static Bool getTxtLn( Str line, Bool *err ) { Bool r; r = Prt_getTxtLn( line, err, client.in ); if ( *err ) logBreakDown(); return r; } static void putTxtBuf( const char *buf ) { Prt_putTxtBuf( buf, client.out ); fflush( client.out ); Log_dbg( "[S FLUSH]" ); } static void putEndOfTxt( void ) { Prt_putEndOfTxt( client.out ); fflush( client.out ); Log_dbg( "[S FLUSH]" ); } static Bool putCmd( const char *fmt, ... ) { Bool err; unsigned int n; Str line; va_list ap; va_start( ap, fmt ); vsnprintf( line, MAXCHAR, fmt, ap ); va_end( ap ); strcpy( client.lastCmd, line ); Log_dbg( "[S] %s", line ); n = fprintf( client.out, "%s\r\n", line ); fflush( client.out ); Log_dbg( "[S FLUSH]" ); err = ( n != strlen( line ) + 2 ); if ( err ) logBreakDown();; return ! err; } static Bool putCmdNoFlush( const char *fmt, ... ) { Bool err; unsigned int n; Str line; va_list ap; va_start( ap, fmt ); vsnprintf( line, MAXCHAR, fmt, ap ); va_end( ap ); strcpy( client.lastCmd, line ); Log_dbg( "[S] %s", line ); n = fprintf( client.out, "%s\r\n", line ); err = ( n != strlen( line ) + 2 ); if ( err ) logBreakDown();; return ! err; } static int getStat( void ); static Bool performAuth( void ) { int stat; Str user, pass; Cfg_authInfo( client.serv, user, pass ); if ( strcmp( user, "" ) == 0 ) { Log_err( "No username for authentication set" ); return FALSE; } putCmd( "AUTHINFO USER %s", user ); stat = getStat(); if ( stat == STAT_AUTH_ACCEPTED ) return TRUE; else if ( stat != STAT_MORE_AUTH_REQUIRED ) { Log_err( "Username rejected. Server stat: %s", client.lastStat ); return FALSE; } if ( strcmp( pass, "" ) == 0 ) { Log_err( "No password for authentication set" ); return FALSE; } putCmd( "AUTHINFO PASS %s", pass ); stat = getStat(); if ( stat != STAT_AUTH_ACCEPTED ) { Log_err( "Password rejected. Server status: %s", client.lastStat ); return FALSE; } return TRUE; } static int getStat( void ) { int result; Str lastCmd; if ( ! getLn( client.lastStat ) ) result = STAT_PROGRAM_FAULT; else if ( sscanf( client.lastStat, "%d", &result ) != 1 ) { Log_err( "Invalid server status: %s", client.lastStat ); result = STAT_PROGRAM_FAULT; } if ( result == STAT_AUTH_REQUIRED && ! client.auth ) { client.auth = TRUE; strcpy( lastCmd, client.lastCmd ); if ( performAuth() ) { putCmd( lastCmd ); return getStat(); } } return result; } static void connectAlarm( int sig ) { return; } static sig_t installSignalHandler( int sig, sig_t handler ) { struct sigaction act, oldAct; act.sa_handler = handler; sigemptyset( &act.sa_mask ); act.sa_flags = 0; if ( sig == SIGALRM ) act.sa_flags |= SA_INTERRUPT; else act.sa_flags |= SA_RESTART; if ( sigaction( sig, &act, &oldAct ) < 0 ) return SIG_ERR; return oldAct.sa_handler; } static Bool connectWithTimeout( int sock, const struct sockaddr *servAddr, socklen_t addrLen ) { sig_t oldHandler; int r, to; oldHandler = installSignalHandler( SIGALRM, connectAlarm ); if ( oldHandler == SIG_ERR ) { Log_err( "client.c:connectWithTimeout: signal failed." ); return FALSE; } to = Cfg_connectTimeout(); if ( alarm( to ) != 0 ) Log_err( "client.c:connectWithTimeout: Alarm was already set." ); r = connect( sock, servAddr, addrLen ); alarm( 0 ); installSignalHandler( SIGALRM, oldHandler ); return ( r >= 0 ); } Bool Client_connect( const char *serv ) { unsigned short int port; int sock, i; unsigned int stat; struct hostent *hp; char *pStart, *pColon; Str host, s; struct sockaddr_in sIn; client.auth = FALSE; Utl_cpyStr( s, serv ); pStart = Utl_stripWhiteSpace( s ); pColon = strstr( pStart, ":" ); if ( pColon == NULL ) { strcpy( host, pStart ); port = 119; } else { *pColon = '\0'; strcpy( host, pStart ); if ( sscanf( pColon + 1, "%hi", &port ) != 1 ) { Log_err( "Syntax error in server name: '%s'", serv ); return FALSE;; } if ( port <= 0 || port > 65535 ) { Log_err( "Invalid port number %hi. Must be in [1, 65535]", port ); return FALSE;; } } memset( (void *)&sIn, 0, sizeof( sIn ) ); hp = gethostbyname( host ); if ( hp ) { for ( i = 0; (hp->h_addr_list)[ i ]; ++i ) { sIn.sin_family = hp->h_addrtype; sIn.sin_port = htons( port ); sIn.sin_addr = *( (struct in_addr *)hp->h_addr_list[ i ] ); sock = socket( AF_INET, SOCK_STREAM, 0 ); if ( sock < 0 ) break; if ( ! connectWithTimeout( sock, (struct sockaddr *)&sIn, sizeof( sIn ) ) ) { close( sock ); break; } if ( ! ( client.out = fdopen( sock, "w" ) ) || ! ( client.in = fdopen( dup( sock ), "r" ) ) ) { if ( client.out != NULL ) fclose( client.out ); close( sock ); break; } stat = getStat(); if ( stat == STAT_READY_POST_ALLOW || stat == STAT_READY_NO_POST_ALLOW ) { /* INN needs a MODE READER before it will permit POST. */ putCmd( "MODE READER" ); stat = getStat(); } switch( stat ) { case STAT_READY_POST_ALLOW: case STAT_READY_NO_POST_ALLOW: Log_inf( "Connected to %s:%d", inet_ntoa( sIn.sin_addr ), port ); Utl_cpyStr( client.serv, serv ); return TRUE; default: Log_err( "Bad server stat %d", stat ); } shutdown( fileno( client.out ), 0 ); fclose( client.in ); fclose( client.out ); close( sock ); } } return FALSE; } static Bool isForbiddenGroupName( const char *name ) { size_t i; for ( i = 0; i < sizeof( forbiddenGroupNames ) / sizeof( struct ForbiddenGroupName ); i++ ) { /* Negate result of Wld_match to ensure it is 1 or 0. */ if ( forbiddenGroupNames[i].match != ( ! Wld_match( name, forbiddenGroupNames[i].pattern ) ) ) return TRUE; } return FALSE; } static void processGrps( void ) { char postAllow; Bool err; int first, last; Str grp, line, file; while ( getTxtLn( line, &err ) && ! err ) { if ( sscanf( line, "%s %d %d %c", grp, &last, &first, &postAllow ) != 4 ) { Log_err( "Unknown reply to LIST or NEWGROUPS: %s", line ); continue; } if ( isForbiddenGroupName( grp ) ) { Log_inf( "Group %s forbidden", grp ); continue; } if ( ! Grp_exists( grp ) ) { Log_inf( "Registering new group '%s'", grp ); Grp_create( grp ); /* Start local numbering with remote first number to avoid new numbering at the readers if noffle is re-installed */ if ( first != 0 ) Grp_setFirstLast( grp, first, first - 1 ); else Grp_setFirstLast( grp, 1, 0 ); Grp_setRmtNext( grp, first ); Grp_setServ( grp, client.serv ); Grp_setPostAllow( grp, postAllow ); } else { if ( Cfg_servIsPreferential( client.serv, Grp_serv( grp ) ) ) { Log_inf( "Changing server for '%s': '%s'->'%s'", grp, Grp_serv( grp ), client.serv ); Grp_setServ( grp, client.serv ); Grp_setRmtNext( grp, first ); Grp_setPostAllow( grp, postAllow ); } else Log_dbg( "Group %s is already fetched from %s", grp, Grp_serv( grp ) ); } } if ( ! err ) { snprintf( file, MAXCHAR, "%s/groupinfo.lastupdate", Cfg_spoolDir() ); Utl_stamp( file ); } } void Client_disconnect( void ) { if ( putCmd( "QUIT" ) ) getStat(); fclose( client.in ); fclose( client.out ); client.in = client.out = NULL; } Bool Client_getGrps( void ) { if ( ! putCmd( "LIST ACTIVE" ) ) return FALSE; if ( getStat() != STAT_GRPS_FOLLOW ) { Log_err( "LIST ACTIVE command failed: %s", client.lastStat ); return FALSE; } processGrps(); return TRUE; } Bool Client_getDsc( void ) { Bool err; Str name, line, dsc; Log_inf( "Querying group descriptions" ); if ( ! putCmd( "LIST NEWSGROUPS" ) ) return FALSE; if ( getStat() != STAT_GRPS_FOLLOW ) { Log_err( "LIST NEWSGROUPS failed: %s", client.lastStat ); return FALSE; } while ( getTxtLn( line, &err ) && ! err ) { if ( sscanf( line, "%s", name ) != 1 ) { Log_err( "Unknown reply to LIST NEWSGROUPS: %s", line ); continue; } strcpy( dsc, Utl_restOfLn( line, 1 ) ); if ( Grp_exists( name ) ) { Log_dbg( "Description of %s: %s", name, dsc ); Grp_setDsc( name, dsc ); } } return TRUE; } Bool Client_getCreationTimes( void ) { Bool err; Str name, line; time_t t; Log_inf( "Querying group creation times" ); if ( ! putCmd( "LIST ACTIVE.TIMES" ) ) return FALSE; if ( getStat() != STAT_GRPS_FOLLOW ) { Log_err( "LIST ACTIVE.TIMES failes: %s", client.lastStat ); return FALSE; } while ( getTxtLn( line, &err ) && ! err ) { if ( sscanf( line, "%s %ld", name, &t ) != 2 ) { Log_err( "Unknown reply to LIST ACTIVE.TIMES: %s", line ); continue; } if ( Grp_exists( name ) ) { Log_inf( "Creation time of %s: %ld", name, t ); Grp_setCreated( name, t ); } } return TRUE; } Bool Client_getNewgrps( const time_t *lastTime ) { Str s; const char *p; ASSERT( *lastTime > 0 ); strftime( s, MAXCHAR, "%Y%m%d %H%M00", gmtime( lastTime ) ); /* Do not use century for working with old server software until 2000. According to newest IETF draft, this is still valid after 2000. (directly using %y in fmt string causes a Y2K compiler warning) */ p = s + 2; if ( ! putCmd( "NEWGROUPS %s", p ) ) return FALSE; if ( getStat() != STAT_NEW_GRP_FOLLOW ) { Log_err( "NEWGROUPS command failed: %s", client.lastStat ); return FALSE; } processGrps(); return TRUE; } static const char * readField( Str result, const char *p ) { size_t len; char *r; if ( ! p ) return NULL; r = result; *r = '\0'; len = 0; while ( *p != '\t' && *p != '\n' ) { if ( ! *p ) return p; *(r++) = *(p++); ++len; if ( len >= MAXCHAR - 1 ) { *r = '\0'; Log_err( "Field in overview too long: %s", r ); return ++p; } } *r = '\0'; return ++p; } static Bool parseOvLn( Str line, int *numb, Str subj, Str from, Str date, Str msgId, Str ref, size_t *bytes, size_t *lines ) { const char *p; Str t; p = readField( t, line ); if ( sscanf( t, "%d", numb ) != 1 ) return FALSE; p = readField( subj, p ); p = readField( from, p ); p = readField( date, p ); p = readField( msgId, p ); p = readField( ref, p ); p = readField( t, p ); *bytes = 0; *lines = 0; if ( sscanf( t, "%d", bytes ) != 1 ) return TRUE; p = readField( t, p ); if ( sscanf( t, "%d", lines ) != 1 ) return TRUE; return TRUE; } static const char* nextXref( const char *pXref, Str grp, int *numb ) { Str s; const char *pColon, *src; char *dst; src = pXref; while ( *src && isspace( *src ) ) ++src; dst = s; while ( *src && ! isspace( *src ) ) *(dst++) = *(src++); *dst = '\0'; if ( strlen( s ) == 0 ) return NULL; pColon = strstr( s, ":" ); if ( ! pColon || sscanf( pColon + 1, "%d", numb ) != 1 ) { Log_err( "Corrupt Xref at position '%s'", pXref ); return NULL; } Utl_cpyStrN( grp, s, pColon - s ); Log_dbg( "client.c: nextXref: grp '%s' numb %lu", grp, numb ); return src; } static Bool needsMark( const char *ref ) { Bool done = FALSE, interesting; char *p; Str msgId; int stat, len; time_t lastAccess, nowTime; double threadFollowTime, limit, age; nowTime = time( NULL ); threadFollowTime = (double)Cfg_threadFollowTime(); limit = threadFollowTime * 24. * 3600.; while ( ! done ) { p = msgId; while ( *ref != '<' ) if ( *(ref++) == '\0' ) return FALSE; len = 0; while ( *ref != '>' ) { if ( *ref == '\0' || ++len >= MAXCHAR - 1 ) return FALSE; *(p++) = *(ref++); } *(p++) = '>'; *p = '\0'; if ( Db_contains( msgId ) ) { stat = Db_stat( msgId ); lastAccess = Db_lastAccess( msgId ); interesting = ( stat & DB_INTERESTING ); age = difftime( nowTime, lastAccess ); if ( interesting && age <= limit ) return TRUE; } } return FALSE; } static void prepareEntry( Over *ov ) { Str g, t; const char *msgId, *p, *xref; int n; msgId = Ov_msgId( ov ); if ( Pseudo_isGeneralInfo( msgId ) ) Log_dbg( "Skipping general info '%s'", msgId ); else if ( Db_contains( msgId ) ) { xref = Db_xref( msgId ); Log_dbg( "Entry '%s' already in db with Xref '%s'", msgId, xref ); p = nextXref( xref, g, &n ); if ( p == NULL ) Log_err( "Overview with no group in Xref '%s'", msgId ); else { /* TODO: This code block seems unnessesary. Can we remove it? */ if ( Cfg_servIsPreferential( client.serv, Grp_serv( g ) ) ) { Log_dbg( "Changing first server for '%s' from '%s' to '%s'", msgId, Grp_serv( g ), client.serv ); snprintf( t, MAXCHAR, "%s:%d %s", client.grp, Ov_numb( ov ), xref ); Db_setXref( msgId, t ); } else { Log_dbg( "Adding '%s' to Xref of '%s'", g, msgId ); snprintf( t, MAXCHAR, "%s %s:%d", xref, client.grp, Ov_numb( ov ) ); Db_setXref( msgId, t ); } } } else { Log_dbg( "Preparing '%s' in database", msgId ); Db_prepareEntry( ov, client.grp, Ov_numb( ov ) ); } } Bool Client_getOver( int rmtFirst, int rmtLast, FetchMode mode ) { Bool err; size_t bytes, lines; int rmtNumb, oldLast, cntMarked; Over *ov; Str line, subj, from, date, msgId, ref; ASSERT( strcmp( client.grp, "" ) != 0 ); if ( ! putCmd( "XOVER %lu-%lu", rmtFirst, rmtLast ) ) return FALSE; if ( getStat() != STAT_OVERS_FOLLOW ) { Log_err( "XOVER command failed: %s", client.lastStat ); return FALSE; } Log_dbg( "Requesting overview for remote %lu-%lu", rmtFirst, rmtLast ); oldLast = Cont_last(); cntMarked = 0; while ( getTxtLn( line, &err ) && ! err ) { if ( ! parseOvLn( line, &rmtNumb, subj, from, date, msgId, ref, &bytes, &lines ) ) Log_err( "Bad overview line: %s", line ); else { ov = new_Over( subj, from, date, msgId, ref, bytes, lines ); Cont_app( ov ); prepareEntry( ov ); if ( mode == FULL || ( mode == THREAD && needsMark( ref ) ) ) { Req_add( client.serv, msgId ); ++cntMarked; } } Grp_setRmtNext( client.grp, rmtNumb + 1 ); } if ( oldLast != Cont_last() ) Log_inf( "Added %s %lu-%lu", client.grp, oldLast + 1, Cont_last() ); Log_inf( "%u articles marked for download in %s", cntMarked, client.grp ); return err; } static void retrievingFailed( const char* msgId, const char *reason ) { int stat; Log_err( "Retrieving of %s failed: %s", msgId, reason ); stat = Db_stat( msgId ); Pseudo_retrievingFailed( msgId, reason ); Db_setStat( msgId, stat | DB_RETRIEVING_FAILED ); } static Bool retrieveAndStoreArt( const char *msgId ) { Bool err; DynStr *s = NULL; Str line; Log_inf( "Retrieving %s", msgId ); s = new_DynStr( 5000 ); while ( getTxtLn( line, &err ) && ! err ) DynStr_appLn( s, line ); if ( ! err ) Db_storeArt( msgId, DynStr_str( s ) ); else retrievingFailed( msgId, "Connection broke down" ); del_DynStr( s ); return ! err; } void Client_retrieveArt( const char *msgId ) { if ( ! Db_contains( msgId ) ) { Log_err( "Article '%s' not prepared in database. Skipping.", msgId ); return; } if ( ! ( Db_stat( msgId ) & DB_NOT_DOWNLOADED ) ) { Log_inf( "Article '%s' already retrieved. Skipping.", msgId ); return; } if ( ! putCmd( "ARTICLE %s", msgId ) ) retrievingFailed( msgId, "Connection broke down" ); else if ( getStat() != STAT_ART_FOLLOWS ) retrievingFailed( msgId, client.lastStat ); else retrieveAndStoreArt( msgId ); } void Client_retrieveArtList( const char *list ) { Str msgId; DynStr *s; const char *p; Log_inf( "Retrieving article list" ); s = new_DynStr( (int)strlen( list ) ); p = list; while ( ( p = Utl_getLn( msgId, p ) ) ) if ( ! Db_contains( msgId ) ) Log_err( "Skipping retrieving of %s (not prepared in database)", msgId ); else if ( ! ( Db_stat( msgId ) & DB_NOT_DOWNLOADED ) ) Log_inf( "Skipping %s (already retrieved)", msgId ); else if ( ! putCmdNoFlush( "ARTICLE %s", msgId ) ) { retrievingFailed( msgId, "Connection broke down" ); del_DynStr( s ); return; } else DynStr_appLn( s, msgId ); fflush( client.out ); Log_dbg( "[S FLUSH]" ); p = DynStr_str( s ); while ( ( p = Utl_getLn( msgId, p ) ) ) { if ( getStat() != STAT_ART_FOLLOWS ) retrievingFailed( msgId, client.lastStat ); else if ( ! retrieveAndStoreArt( msgId ) ) break; } del_DynStr( s ); } Bool Client_changeToGrp( const char* name ) { unsigned int stat; int estimatedNumb, first, last; if ( ! Grp_exists( name ) ) return FALSE; if ( ! putCmd( "GROUP %s", name ) ) return FALSE; if ( getStat() != STAT_GRP_SELECTED ) return FALSE; if ( sscanf( client.lastStat, "%u %d %d %d", &stat, &estimatedNumb, &first, &last ) != 4 ) { Log_err( "Bad server response to GROUP: %s", client.lastStat ); return FALSE; } Utl_cpyStr( client.grp, name ); client.rmtFirst = first; client.rmtLast = last; return TRUE; } void Client_rmtFirstLast( int *first, int *last ) { *first = client.rmtFirst; *last = client.rmtLast; } Bool Client_postArt( const char *msgId, const char *artTxt, Str errStr ) { if ( ! putCmd( "POST" ) ) return FALSE; if ( getStat() != STAT_SEND_ART ) { Log_err( "Posting of %s not allowed: %s", msgId, client.lastStat ); strcpy( errStr, client.lastStat ); return FALSE; } putTxtBuf( artTxt ); putEndOfTxt(); if ( getStat() != STAT_POST_OK ) { Log_err( "Posting of %s failed: %s", msgId, client.lastStat ); strcpy( errStr, client.lastStat ); return FALSE; } Log_inf( "Posted %s (Status: %s)", msgId, client.lastStat ); return TRUE; }