Mercurial > noffle
comparison src/client.c @ 43:2842f50feb55 noffle
[svn] * client.c, client.h, common.h, config.c, config.h, content.c, content.h,
control.c, control.h, database.c, database.h, dynamicstring.c,
dynamicstring.h, fetch.c, fetch.h, fetchlist.c, fetchlist.h, group.c,
group.h, itemlist.c, itemlist.h, lock.c, lock.h, log.c, log.h, noffle.c,
online.c, online.h, outgoing.c, outgoing.h, over.c, over.h, post.c, post.h,
protocol.c, protocol.h, pseudo.c, pseudo.h, request.c, request.h, server.c,
server.h, util.c, util.h, wildmat.c, wildmat.h: Moved files to the
subdirectory src/
* Makefile.am, acconfig.h, configure.in, docs/Makefile.am, src/Makefile.am,
Makefile.in, aclocal.m4, config.h.in, configure, install-sh, missing,
mkinstalldirs, stamp-h.in, docs/Makefile.in, src/Makefile.in: Added files.
They are used by aclocal, autoheader, autoconf and automake.
* src/config.c, src/config.h: Renamed to configfile.c and configfile.h,
because configure will generate a config.h file itself.
* src/client.c, src/content.c, src/database.c, src/fetch.c, src/fetchlist.c,
src/group.c, src/lock.c, src/noffle.c, src/online.c, src/outgoing.c,
src/over.c, src/pseudo.c, src/request.c, src/server.c, src/util.c:
Changed '#include "config.h"' to '#include "configfile.h"'.
* src/client.c, src/content.c, src/database.c, src/fetch.c, src/fetchlist.c,
src/group.c, src/lock.c, src/online.c, src/outgoing.c, src/post.c,
src/protocol.c, src/request.c, src/server.c: Files now #include <config.h>.
Added missing <stdio.h>. This removes the warnings about snprintf() not
being declared.
* Makefile: Removed. This is now generated by configure.
| author | uh1763 |
|---|---|
| date | Fri, 05 May 2000 22:45:56 +0100 |
| parents | |
| children | 21d3102dbc37 |
comparison
equal
deleted
inserted
replaced
| 42:2467ff423c15 | 43:2842f50feb55 |
|---|---|
| 1 /* | |
| 2 client.c | |
| 3 | |
| 4 $Id: client.c 49 2000-05-05 21:45:56Z uh1763 $ | |
| 5 */ | |
| 6 | |
| 7 #if HAVE_CONFIG_H | |
| 8 #include <config.h> | |
| 9 #endif | |
| 10 | |
| 11 #include "client.h" | |
| 12 | |
| 13 #include <stdio.h> | |
| 14 #include <arpa/inet.h> | |
| 15 #include <ctype.h> | |
| 16 #include <netdb.h> | |
| 17 #include <netinet/in.h> | |
| 18 #include <signal.h> | |
| 19 #include <stdarg.h> | |
| 20 #include <sys/socket.h> | |
| 21 #include <unistd.h> | |
| 22 #include "configfile.h" | |
| 23 #include "content.h" | |
| 24 #include "dynamicstring.h" | |
| 25 #include "group.h" | |
| 26 #include "log.h" | |
| 27 #include "over.h" | |
| 28 #include "protocol.h" | |
| 29 #include "pseudo.h" | |
| 30 #include "request.h" | |
| 31 #include "util.h" | |
| 32 #include "wildmat.h" | |
| 33 | |
| 34 /* | |
| 35 Some newsgroups names are reserved for server-specific or server | |
| 36 pseudo groups. We don't want to fetch them. For example, INN | |
| 37 keeps all its control messages in a 'control' hierarchy, and | |
| 38 used the "to." heirarchy for dark and mysterious purposes I think | |
| 39 are to do with newsfeeds. The recommended restrictions are documented | |
| 40 in C.Lindsay, "News Article Format", <draft-ietf-usefor-article-03.txt>. | |
| 41 */ | |
| 42 | |
| 43 struct ForbiddenGroupName | |
| 44 { | |
| 45 const char *pattern; | |
| 46 Bool match; | |
| 47 } forbiddenGroupNames[] = | |
| 48 { | |
| 49 { "*.*", FALSE }, /* Single component */ | |
| 50 { "control.*", TRUE }, /* control.* groups */ | |
| 51 { "to.*", TRUE }, /* control.* groups */ | |
| 52 { "*.all", TRUE }, /* 'all' as a component */ | |
| 53 { "*.all.*", TRUE }, | |
| 54 { "all.*", TRUE }, | |
| 55 { "*.ctl", TRUE }, /* 'ctl' as a component */ | |
| 56 { "*.ctl.*", TRUE }, | |
| 57 { "ctl.*", TRUE } | |
| 58 }; | |
| 59 | |
| 60 struct | |
| 61 { | |
| 62 FILE* in; /* Receiving socket from server */ | |
| 63 FILE* out; /* Sending socket to server */ | |
| 64 Str lastCmd; /* Last command line */ | |
| 65 Str lastStat; /* Response from server to last command */ | |
| 66 Str grp; /* Selected group */ | |
| 67 int rmtFirst; /* First article of current group at server */ | |
| 68 int rmtLast; /* Last article of current group at server */ | |
| 69 Bool auth; /* Authetication already done? */ | |
| 70 Str serv; /* Remote server name */ | |
| 71 } client = { NULL, NULL, "", "", "", 1, 0, FALSE, "" }; | |
| 72 | |
| 73 static void | |
| 74 logBreakDown( void ) | |
| 75 { | |
| 76 Log_err( "Connection to remote server lost " | |
| 77 "(article numbers could be inconsistent)" ); | |
| 78 } | |
| 79 | |
| 80 static Bool | |
| 81 getLn( Str line ) | |
| 82 { | |
| 83 Bool r; | |
| 84 | |
| 85 r = Prt_getLn( line, client.in ); | |
| 86 if ( ! r ) | |
| 87 logBreakDown(); | |
| 88 return r; | |
| 89 } | |
| 90 | |
| 91 static Bool | |
| 92 getTxtLn( Str line, Bool *err ) | |
| 93 { | |
| 94 Bool r; | |
| 95 | |
| 96 r = Prt_getTxtLn( line, err, client.in ); | |
| 97 if ( *err ) | |
| 98 logBreakDown(); | |
| 99 return r; | |
| 100 } | |
| 101 | |
| 102 static void | |
| 103 putTxtBuf( const char *buf ) | |
| 104 { | |
| 105 Prt_putTxtBuf( buf, client.out ); | |
| 106 fflush( client.out ); | |
| 107 Log_dbg( "[S FLUSH]" ); | |
| 108 } | |
| 109 | |
| 110 static void | |
| 111 putEndOfTxt( void ) | |
| 112 { | |
| 113 Prt_putEndOfTxt( client.out ); | |
| 114 fflush( client.out ); | |
| 115 Log_dbg( "[S FLUSH]" ); | |
| 116 } | |
| 117 | |
| 118 static Bool | |
| 119 putCmd( const char *fmt, ... ) | |
| 120 { | |
| 121 Bool err; | |
| 122 unsigned int n; | |
| 123 Str line; | |
| 124 va_list ap; | |
| 125 | |
| 126 va_start( ap, fmt ); | |
| 127 vsnprintf( line, MAXCHAR, fmt, ap ); | |
| 128 va_end( ap ); | |
| 129 strcpy( client.lastCmd, line ); | |
| 130 Log_dbg( "[S] %s", line ); | |
| 131 n = fprintf( client.out, "%s\r\n", line ); | |
| 132 fflush( client.out ); | |
| 133 Log_dbg( "[S FLUSH]" ); | |
| 134 err = ( n != strlen( line ) + 2 ); | |
| 135 if ( err ) | |
| 136 logBreakDown();; | |
| 137 return ! err; | |
| 138 } | |
| 139 | |
| 140 static Bool | |
| 141 putCmdNoFlush( const char *fmt, ... ) | |
| 142 { | |
| 143 Bool err; | |
| 144 unsigned int n; | |
| 145 Str line; | |
| 146 va_list ap; | |
| 147 | |
| 148 va_start( ap, fmt ); | |
| 149 vsnprintf( line, MAXCHAR, fmt, ap ); | |
| 150 va_end( ap ); | |
| 151 strcpy( client.lastCmd, line ); | |
| 152 Log_dbg( "[S] %s", line ); | |
| 153 n = fprintf( client.out, "%s\r\n", line ); | |
| 154 err = ( n != strlen( line ) + 2 ); | |
| 155 if ( err ) | |
| 156 logBreakDown();; | |
| 157 return ! err; | |
| 158 } | |
| 159 | |
| 160 static int getStat( void ); | |
| 161 | |
| 162 static Bool | |
| 163 performAuth( void ) | |
| 164 { | |
| 165 int stat; | |
| 166 Str user, pass; | |
| 167 | |
| 168 Cfg_authInfo( client.serv, user, pass ); | |
| 169 if ( strcmp( user, "" ) == 0 ) | |
| 170 { | |
| 171 Log_err( "No username for authentication set" ); | |
| 172 return FALSE; | |
| 173 } | |
| 174 putCmd( "AUTHINFO USER %s", user ); | |
| 175 stat = getStat(); | |
| 176 if ( stat == STAT_AUTH_ACCEPTED ) | |
| 177 return TRUE; | |
| 178 else if ( stat != STAT_MORE_AUTH_REQUIRED ) | |
| 179 { | |
| 180 Log_err( "Username rejected. Server stat: %s", client.lastStat ); | |
| 181 return FALSE; | |
| 182 } | |
| 183 if ( strcmp( pass, "" ) == 0 ) | |
| 184 { | |
| 185 Log_err( "No password for authentication set" ); | |
| 186 return FALSE; | |
| 187 } | |
| 188 putCmd( "AUTHINFO PASS %s", pass ); | |
| 189 stat = getStat(); | |
| 190 if ( stat != STAT_AUTH_ACCEPTED ) | |
| 191 { | |
| 192 Log_err( "Password rejected. Server status: %s", client.lastStat ); | |
| 193 return FALSE; | |
| 194 } | |
| 195 return TRUE; | |
| 196 } | |
| 197 | |
| 198 static int | |
| 199 getStat( void ) | |
| 200 { | |
| 201 int result; | |
| 202 Str lastCmd; | |
| 203 | |
| 204 if ( ! getLn( client.lastStat ) ) | |
| 205 result = STAT_PROGRAM_FAULT; | |
| 206 else if ( sscanf( client.lastStat, "%d", &result ) != 1 ) | |
| 207 { | |
| 208 Log_err( "Invalid server status: %s", client.lastStat ); | |
| 209 result = STAT_PROGRAM_FAULT; | |
| 210 } | |
| 211 if ( result == STAT_AUTH_REQUIRED && ! client.auth ) | |
| 212 { | |
| 213 client.auth = TRUE; | |
| 214 strcpy( lastCmd, client.lastCmd ); | |
| 215 if ( performAuth() ) | |
| 216 { | |
| 217 putCmd( lastCmd ); | |
| 218 return getStat(); | |
| 219 } | |
| 220 } | |
| 221 return result; | |
| 222 } | |
| 223 | |
| 224 static void | |
| 225 connectAlarm( int sig ) | |
| 226 { | |
| 227 return; | |
| 228 } | |
| 229 | |
| 230 static sig_t | |
| 231 installSignalHandler( int sig, sig_t handler ) | |
| 232 { | |
| 233 struct sigaction act, oldAct; | |
| 234 | |
| 235 act.sa_handler = handler; | |
| 236 sigemptyset( &act.sa_mask ); | |
| 237 act.sa_flags = 0; | |
| 238 if ( sig == SIGALRM ) | |
| 239 act.sa_flags |= SA_INTERRUPT; | |
| 240 else | |
| 241 act.sa_flags |= SA_RESTART; | |
| 242 if ( sigaction( sig, &act, &oldAct ) < 0 ) | |
| 243 return SIG_ERR; | |
| 244 return oldAct.sa_handler; | |
| 245 } | |
| 246 | |
| 247 static Bool | |
| 248 connectWithTimeout( int sock, const struct sockaddr *servAddr, | |
| 249 socklen_t addrLen ) | |
| 250 { | |
| 251 sig_t oldHandler; | |
| 252 int r, to; | |
| 253 | |
| 254 oldHandler = installSignalHandler( SIGALRM, connectAlarm ); | |
| 255 if ( oldHandler == SIG_ERR ) | |
| 256 { | |
| 257 Log_err( "client.c:connectWithTimeout: signal failed." ); | |
| 258 return FALSE; | |
| 259 } | |
| 260 to = Cfg_connectTimeout(); | |
| 261 if ( alarm( to ) != 0 ) | |
| 262 Log_err( "client.c:connectWithTimeout: Alarm was already set." ); | |
| 263 r = connect( sock, servAddr, addrLen ); | |
| 264 alarm( 0 ); | |
| 265 installSignalHandler( SIGALRM, oldHandler ); | |
| 266 return ( r >= 0 ); | |
| 267 } | |
| 268 | |
| 269 Bool | |
| 270 Client_connect( const char *serv ) | |
| 271 { | |
| 272 unsigned short int port; | |
| 273 int sock, i; | |
| 274 unsigned int stat; | |
| 275 struct hostent *hp; | |
| 276 char *pStart, *pColon; | |
| 277 Str host, s; | |
| 278 struct sockaddr_in sIn; | |
| 279 | |
| 280 client.auth = FALSE; | |
| 281 Utl_cpyStr( s, serv ); | |
| 282 pStart = Utl_stripWhiteSpace( s ); | |
| 283 pColon = strstr( pStart, ":" ); | |
| 284 if ( pColon == NULL ) | |
| 285 { | |
| 286 strcpy( host, pStart ); | |
| 287 port = 119; | |
| 288 } | |
| 289 else | |
| 290 { | |
| 291 *pColon = '\0'; | |
| 292 strcpy( host, pStart ); | |
| 293 if ( sscanf( pColon + 1, "%hi", &port ) != 1 ) | |
| 294 { | |
| 295 Log_err( "Syntax error in server name: '%s'", serv ); | |
| 296 return FALSE;; | |
| 297 } | |
| 298 if ( port <= 0 || port > 65535 ) | |
| 299 { | |
| 300 Log_err( "Invalid port number %hi. Must be in [1, 65535]", port ); | |
| 301 return FALSE;; | |
| 302 } | |
| 303 } | |
| 304 memset( (void *)&sIn, 0, sizeof( sIn ) ); | |
| 305 hp = gethostbyname( host ); | |
| 306 if ( hp ) | |
| 307 { | |
| 308 for ( i = 0; (hp->h_addr_list)[ i ]; ++i ) | |
| 309 { | |
| 310 sIn.sin_family = hp->h_addrtype; | |
| 311 sIn.sin_port = htons( port ); | |
| 312 sIn.sin_addr = *( (struct in_addr *)hp->h_addr_list[ i ] ); | |
| 313 sock = socket( AF_INET, SOCK_STREAM, 0 ); | |
| 314 if ( sock < 0 ) | |
| 315 break; | |
| 316 if ( ! connectWithTimeout( sock, (struct sockaddr *)&sIn, | |
| 317 sizeof( sIn ) ) ) | |
| 318 { | |
| 319 close( sock ); | |
| 320 break; | |
| 321 } | |
| 322 if ( ! ( client.out = fdopen( sock, "w" ) ) | |
| 323 || ! ( client.in = fdopen( dup( sock ), "r" ) ) ) | |
| 324 { | |
| 325 if ( client.out != NULL ) | |
| 326 fclose( client.out ); | |
| 327 close( sock ); | |
| 328 break; | |
| 329 } | |
| 330 stat = getStat(); | |
| 331 if ( stat == STAT_READY_POST_ALLOW || | |
| 332 stat == STAT_READY_NO_POST_ALLOW ) | |
| 333 { | |
| 334 /* INN needs a MODE READER before it will permit POST. */ | |
| 335 putCmd( "MODE READER" ); | |
| 336 stat = getStat(); | |
| 337 } | |
| 338 switch( stat ) { | |
| 339 case STAT_READY_POST_ALLOW: | |
| 340 case STAT_READY_NO_POST_ALLOW: | |
| 341 Log_inf( "Connected to %s:%d", | |
| 342 inet_ntoa( sIn.sin_addr ), port ); | |
| 343 Utl_cpyStr( client.serv, serv ); | |
| 344 return TRUE; | |
| 345 default: | |
| 346 Log_err( "Bad server stat %d", stat ); | |
| 347 } | |
| 348 shutdown( fileno( client.out ), 0 ); | |
| 349 fclose( client.in ); | |
| 350 fclose( client.out ); | |
| 351 close( sock ); | |
| 352 } | |
| 353 } | |
| 354 return FALSE; | |
| 355 } | |
| 356 | |
| 357 static Bool | |
| 358 isForbiddenGroupName( const char *name ) | |
| 359 { | |
| 360 int i; | |
| 361 | |
| 362 for ( i = 0; | |
| 363 i < sizeof( forbiddenGroupNames ) / | |
| 364 sizeof( struct ForbiddenGroupName ); | |
| 365 i++ ) | |
| 366 { | |
| 367 /* Negate result of Wld_match to ensure it is 1 or 0. */ | |
| 368 if ( forbiddenGroupNames[i].match != | |
| 369 ( ! Wld_match( name, forbiddenGroupNames[i].pattern ) ) ) | |
| 370 return TRUE; | |
| 371 } | |
| 372 | |
| 373 return FALSE; | |
| 374 } | |
| 375 | |
| 376 static void | |
| 377 processGrps( void ) | |
| 378 { | |
| 379 char postAllow; | |
| 380 Bool err; | |
| 381 int first, last; | |
| 382 Str grp, line, file; | |
| 383 | |
| 384 while ( getTxtLn( line, &err ) && ! err ) | |
| 385 { | |
| 386 if ( sscanf( line, "%s %d %d %c", | |
| 387 grp, &last, &first, &postAllow ) != 4 ) | |
| 388 { | |
| 389 Log_err( "Unknown reply to LIST or NEWGROUPS: %s", line ); | |
| 390 continue; | |
| 391 } | |
| 392 if ( isForbiddenGroupName( grp ) ) | |
| 393 { | |
| 394 Log_inf( "Group %s forbidden", grp ); | |
| 395 continue; | |
| 396 } | |
| 397 if ( ! Grp_exists( grp ) ) | |
| 398 { | |
| 399 Log_inf( "Registering new group '%s'", grp ); | |
| 400 Grp_create( grp ); | |
| 401 Grp_setRmtNext( grp, first ); | |
| 402 Grp_setServ( grp, client.serv ); | |
| 403 Grp_setPostAllow( grp, postAllow ); | |
| 404 } | |
| 405 else | |
| 406 { | |
| 407 if ( Cfg_servIsPreferential( client.serv, Grp_serv( grp ) ) ) | |
| 408 { | |
| 409 Log_inf( "Changing server for '%s': '%s'->'%s'", | |
| 410 grp, Grp_serv( grp ), client.serv ); | |
| 411 Grp_setServ( grp, client.serv ); | |
| 412 Grp_setRmtNext( grp, first ); | |
| 413 Grp_setPostAllow( grp, postAllow ); | |
| 414 } | |
| 415 else | |
| 416 Log_dbg( "Group %s is already fetched from %s", | |
| 417 grp, Grp_serv( grp ) ); | |
| 418 | |
| 419 } | |
| 420 } | |
| 421 if ( ! err ) | |
| 422 { | |
| 423 snprintf( file, MAXCHAR, "%s/groupinfo.lastupdate", Cfg_spoolDir() ); | |
| 424 Utl_stamp( file ); | |
| 425 } | |
| 426 } | |
| 427 | |
| 428 void | |
| 429 Client_disconnect( void ) | |
| 430 { | |
| 431 if ( putCmd( "QUIT" ) ) | |
| 432 getStat(); | |
| 433 fclose( client.in ); | |
| 434 fclose( client.out ); | |
| 435 client.in = client.out = NULL; | |
| 436 } | |
| 437 | |
| 438 Bool | |
| 439 Client_getGrps( void ) | |
| 440 { | |
| 441 if ( ! putCmd( "LIST ACTIVE" ) ) | |
| 442 return FALSE; | |
| 443 if ( getStat() != STAT_GRPS_FOLLOW ) | |
| 444 { | |
| 445 Log_err( "LIST ACTIVE command failed: %s", client.lastStat ); | |
| 446 return FALSE; | |
| 447 } | |
| 448 processGrps(); | |
| 449 return TRUE; | |
| 450 } | |
| 451 | |
| 452 Bool | |
| 453 Client_getDsc( void ) | |
| 454 { | |
| 455 Bool err; | |
| 456 Str name, line, dsc; | |
| 457 | |
| 458 Log_inf( "Querying group descriptions" ); | |
| 459 if ( ! putCmd( "LIST NEWSGROUPS" ) ) | |
| 460 return FALSE; | |
| 461 if ( getStat() != STAT_GRPS_FOLLOW ) | |
| 462 { | |
| 463 Log_err( "LIST NEWSGROUPS failed: %s", client.lastStat ); | |
| 464 return FALSE; | |
| 465 } | |
| 466 while ( getTxtLn( line, &err ) && ! err ) | |
| 467 { | |
| 468 if ( sscanf( line, "%s", name ) != 1 ) | |
| 469 { | |
| 470 Log_err( "Unknown reply to LIST NEWSGROUPS: %s", line ); | |
| 471 continue; | |
| 472 } | |
| 473 strcpy( dsc, Utl_restOfLn( line, 1 ) ); | |
| 474 if ( Grp_exists( name ) ) | |
| 475 { | |
| 476 Log_dbg( "Description of %s: %s", name, dsc ); | |
| 477 Grp_setDsc( name, dsc ); | |
| 478 } | |
| 479 } | |
| 480 return TRUE; | |
| 481 } | |
| 482 | |
| 483 Bool | |
| 484 Client_getCreationTimes( void ) | |
| 485 { | |
| 486 Bool err; | |
| 487 Str name, line; | |
| 488 time_t t; | |
| 489 | |
| 490 Log_inf( "Querying group creation times" ); | |
| 491 if ( ! putCmd( "LIST ACTIVE.TIMES" ) ) | |
| 492 return FALSE; | |
| 493 if ( getStat() != STAT_GRPS_FOLLOW ) | |
| 494 { | |
| 495 Log_err( "LIST ACTIVE.TIMES failes: %s", client.lastStat ); | |
| 496 return FALSE; | |
| 497 } | |
| 498 while ( getTxtLn( line, &err ) && ! err ) | |
| 499 { | |
| 500 if ( sscanf( line, "%s %ld", name, &t ) != 2 ) | |
| 501 { | |
| 502 Log_err( "Unknown reply to LIST ACTIVE.TIMES: %s", line ); | |
| 503 continue; | |
| 504 } | |
| 505 if ( Grp_exists( name ) ) | |
| 506 { | |
| 507 Log_inf( "Creation time of %s: %ld", name, t ); | |
| 508 Grp_setCreated( name, t ); | |
| 509 } | |
| 510 } | |
| 511 return TRUE; | |
| 512 } | |
| 513 | |
| 514 Bool | |
| 515 Client_getNewgrps( const time_t *lastTime ) | |
| 516 { | |
| 517 Str s; | |
| 518 const char *p; | |
| 519 | |
| 520 ASSERT( *lastTime > 0 ); | |
| 521 strftime( s, MAXCHAR, "%Y%m%d %H%M00", gmtime( lastTime ) ); | |
| 522 /* | |
| 523 Do not use century for working with old server software until 2000. | |
| 524 According to newest IETF draft, this is still valid after 2000. | |
| 525 (directly using %y in fmt string causes a Y2K compiler warning) | |
| 526 */ | |
| 527 p = s + 2; | |
| 528 if ( ! putCmd( "NEWGROUPS %s", p ) ) | |
| 529 return FALSE; | |
| 530 if ( getStat() != STAT_NEW_GRP_FOLLOW ) | |
| 531 { | |
| 532 Log_err( "NEWGROUPS command failed: %s", client.lastStat ); | |
| 533 return FALSE; | |
| 534 } | |
| 535 processGrps(); | |
| 536 return TRUE; | |
| 537 } | |
| 538 | |
| 539 static const char * | |
| 540 readField( Str result, const char *p ) | |
| 541 { | |
| 542 size_t len; | |
| 543 char *r; | |
| 544 | |
| 545 if ( ! p ) | |
| 546 return NULL; | |
| 547 r = result; | |
| 548 *r = '\0'; | |
| 549 len = 0; | |
| 550 while ( *p != '\t' && *p != '\n' ) | |
| 551 { | |
| 552 if ( ! *p ) | |
| 553 return p; | |
| 554 *(r++) = *(p++); | |
| 555 ++len; | |
| 556 if ( len >= MAXCHAR - 1 ) | |
| 557 { | |
| 558 *r = '\0'; | |
| 559 Log_err( "Field in overview too long: %s", r ); | |
| 560 return ++p; | |
| 561 } | |
| 562 } | |
| 563 *r = '\0'; | |
| 564 return ++p; | |
| 565 } | |
| 566 | |
| 567 static Bool | |
| 568 parseOvLn( Str line, int *numb, Str subj, Str from, | |
| 569 Str date, Str msgId, Str ref, size_t *bytes, size_t *lines ) | |
| 570 { | |
| 571 const char *p; | |
| 572 Str t; | |
| 573 | |
| 574 p = readField( t, line ); | |
| 575 if ( sscanf( t, "%d", numb ) != 1 ) | |
| 576 return FALSE; | |
| 577 p = readField( subj, p ); | |
| 578 p = readField( from, p ); | |
| 579 p = readField( date, p ); | |
| 580 p = readField( msgId, p ); | |
| 581 p = readField( ref, p ); | |
| 582 p = readField( t, p ); | |
| 583 *bytes = 0; | |
| 584 *lines = 0; | |
| 585 if ( sscanf( t, "%d", bytes ) != 1 ) | |
| 586 return TRUE; | |
| 587 p = readField( t, p ); | |
| 588 if ( sscanf( t, "%d", lines ) != 1 ) | |
| 589 return TRUE; | |
| 590 return TRUE; | |
| 591 } | |
| 592 | |
| 593 static const char* | |
| 594 nextXref( const char *pXref, Str grp, int *numb ) | |
| 595 { | |
| 596 Str s; | |
| 597 const char *pColon, *src; | |
| 598 char *dst; | |
| 599 | |
| 600 src = pXref; | |
| 601 while ( *src && isspace( *src ) ) | |
| 602 ++src; | |
| 603 dst = s; | |
| 604 while ( *src && ! isspace( *src ) ) | |
| 605 *(dst++) = *(src++); | |
| 606 *dst = '\0'; | |
| 607 if ( strlen( s ) == 0 ) | |
| 608 return NULL; | |
| 609 pColon = strstr( s, ":" ); | |
| 610 if ( ! pColon || sscanf( pColon + 1, "%d", numb ) != 1 ) | |
| 611 { | |
| 612 Log_err( "Corrupt Xref at position '%s'", pXref ); | |
| 613 return NULL; | |
| 614 } | |
| 615 Utl_cpyStrN( grp, s, pColon - s ); | |
| 616 Log_dbg( "client.c: nextXref: grp '%s' numb %lu", grp, numb ); | |
| 617 return src; | |
| 618 } | |
| 619 | |
| 620 static Bool | |
| 621 needsMark( const char *ref ) | |
| 622 { | |
| 623 Bool done = FALSE; | |
| 624 char *p; | |
| 625 Str msgId; | |
| 626 int stat, len; | |
| 627 time_t lastAccess, nowTime; | |
| 628 double limit; | |
| 629 | |
| 630 nowTime = time( NULL ); | |
| 631 limit = Cfg_threadFollowTime() * 24. * 3600.; | |
| 632 while ( ! done ) | |
| 633 { | |
| 634 p = msgId; | |
| 635 while ( *ref != '<' ) | |
| 636 if ( *(ref++) == '\0' ) | |
| 637 return FALSE; | |
| 638 len = 0; | |
| 639 while ( *ref != '>' ) | |
| 640 { | |
| 641 if ( *ref == '\0' || ++len >= MAXCHAR - 1 ) | |
| 642 return FALSE; | |
| 643 *(p++) = *(ref++); | |
| 644 } | |
| 645 *(p++) = '>'; | |
| 646 *p = '\0'; | |
| 647 if ( Db_contains( msgId ) ) | |
| 648 { | |
| 649 stat = Db_stat( msgId ); | |
| 650 lastAccess = Db_lastAccess( msgId ); | |
| 651 if ( ( stat & DB_INTERESTING ) | |
| 652 && difftime( nowTime, lastAccess ) <= limit ) | |
| 653 return TRUE; | |
| 654 } | |
| 655 } | |
| 656 return FALSE; | |
| 657 } | |
| 658 | |
| 659 static void | |
| 660 prepareEntry( Over *ov ) | |
| 661 { | |
| 662 Str g, t; | |
| 663 const char *msgId, *p, *xref; | |
| 664 int n; | |
| 665 | |
| 666 msgId = Ov_msgId( ov ); | |
| 667 if ( Pseudo_isGeneralInfo( msgId ) ) | |
| 668 Log_dbg( "Skipping general info '%s'", msgId ); | |
| 669 else if ( Db_contains( msgId ) ) | |
| 670 { | |
| 671 xref = Db_xref( msgId ); | |
| 672 Log_dbg( "Entry '%s' already in db with Xref '%s'", msgId, xref ); | |
| 673 p = nextXref( xref, g, &n ); | |
| 674 if ( p == NULL ) | |
| 675 Log_err( "Overview with no group in Xref '%s'", msgId ); | |
| 676 else | |
| 677 { | |
| 678 /* TODO: This code block seems unnessesary. Can we remove it? */ | |
| 679 if ( Cfg_servIsPreferential( client.serv, Grp_serv( g ) ) ) | |
| 680 { | |
| 681 Log_dbg( "Changing first server for '%s' from '%s' to '%s'", | |
| 682 msgId, Grp_serv( g ), client.serv ); | |
| 683 snprintf( t, MAXCHAR, "%s:%d %s", | |
| 684 client.grp, Ov_numb( ov ), xref ); | |
| 685 Db_setXref( msgId, t ); | |
| 686 } | |
| 687 else | |
| 688 { | |
| 689 Log_dbg( "Adding '%s' to Xref of '%s'", g, msgId ); | |
| 690 snprintf( t, MAXCHAR, "%s %s:%d", | |
| 691 xref, client.grp, Ov_numb( ov ) ); | |
| 692 Db_setXref( msgId, t ); | |
| 693 } | |
| 694 } | |
| 695 } | |
| 696 else | |
| 697 { | |
| 698 Log_dbg( "Preparing '%s' in database", msgId ); | |
| 699 Db_prepareEntry( ov, client.grp, Ov_numb( ov ) ); | |
| 700 } | |
| 701 } | |
| 702 | |
| 703 Bool | |
| 704 Client_getOver( int rmtFirst, int rmtLast, FetchMode mode ) | |
| 705 { | |
| 706 Bool err; | |
| 707 size_t bytes, lines; | |
| 708 int rmtNumb, oldLast, cntMarked; | |
| 709 Over *ov; | |
| 710 Str line, subj, from, date, msgId, ref; | |
| 711 | |
| 712 ASSERT( strcmp( client.grp, "" ) != 0 ); | |
| 713 if ( ! putCmd( "XOVER %lu-%lu", rmtFirst, rmtLast ) ) | |
| 714 return FALSE; | |
| 715 if ( getStat() != STAT_OVERS_FOLLOW ) | |
| 716 { | |
| 717 Log_err( "XOVER command failed: %s", client.lastStat ); | |
| 718 return FALSE; | |
| 719 } | |
| 720 Log_dbg( "Requesting overview for remote %lu-%lu", rmtFirst, rmtLast ); | |
| 721 oldLast = Cont_last(); | |
| 722 cntMarked = 0; | |
| 723 while ( getTxtLn( line, &err ) && ! err ) | |
| 724 { | |
| 725 if ( ! parseOvLn( line, &rmtNumb, subj, from, date, msgId, ref, | |
| 726 &bytes, &lines ) ) | |
| 727 Log_err( "Bad overview line: %s", line ); | |
| 728 else | |
| 729 { | |
| 730 ov = new_Over( subj, from, date, msgId, ref, bytes, lines ); | |
| 731 Cont_app( ov ); | |
| 732 prepareEntry( ov ); | |
| 733 if ( mode == FULL || ( mode == THREAD && needsMark( ref ) ) ) | |
| 734 { | |
| 735 Req_add( client.serv, msgId ); | |
| 736 ++cntMarked; | |
| 737 } | |
| 738 } | |
| 739 Grp_setRmtNext( client.grp, rmtNumb + 1 ); | |
| 740 } | |
| 741 if ( oldLast != Cont_last() ) | |
| 742 Log_inf( "Added %s %lu-%lu", client.grp, oldLast + 1, Cont_last() ); | |
| 743 Log_inf( "%u articles marked for download in %s", cntMarked, client.grp ); | |
| 744 return err; | |
| 745 } | |
| 746 | |
| 747 static void | |
| 748 retrievingFailed( const char* msgId, const char *reason ) | |
| 749 { | |
| 750 int stat; | |
| 751 | |
| 752 Log_err( "Retrieving of %s failed: %s", msgId, reason ); | |
| 753 stat = Db_stat( msgId ); | |
| 754 Pseudo_retrievingFailed( msgId, reason ); | |
| 755 Db_setStat( msgId, stat | DB_RETRIEVING_FAILED ); | |
| 756 } | |
| 757 | |
| 758 static Bool | |
| 759 retrieveAndStoreArt( const char *msgId ) | |
| 760 { | |
| 761 Bool err; | |
| 762 DynStr *s = NULL; | |
| 763 Str line; | |
| 764 | |
| 765 Log_inf( "Retrieving %s", msgId ); | |
| 766 s = new_DynStr( 5000 ); | |
| 767 while ( getTxtLn( line, &err ) && ! err ) | |
| 768 DynStr_appLn( s, line ); | |
| 769 if ( ! err ) | |
| 770 Db_storeArt( msgId, DynStr_str( s ) ); | |
| 771 else | |
| 772 retrievingFailed( msgId, "Connection broke down" ); | |
| 773 del_DynStr( s ); | |
| 774 return ! err; | |
| 775 } | |
| 776 | |
| 777 void | |
| 778 Client_retrieveArt( const char *msgId ) | |
| 779 { | |
| 780 if ( ! Db_contains( msgId ) ) | |
| 781 { | |
| 782 Log_err( "Article '%s' not prepared in database. Skipping.", msgId ); | |
| 783 return; | |
| 784 } | |
| 785 if ( ! ( Db_stat( msgId ) & DB_NOT_DOWNLOADED ) ) | |
| 786 { | |
| 787 Log_inf( "Article '%s' already retrieved. Skipping.", msgId ); | |
| 788 return; | |
| 789 } | |
| 790 if ( ! putCmd( "ARTICLE %s", msgId ) ) | |
| 791 retrievingFailed( msgId, "Connection broke down" ); | |
| 792 else if ( getStat() != STAT_ART_FOLLOWS ) | |
| 793 retrievingFailed( msgId, client.lastStat ); | |
| 794 else | |
| 795 retrieveAndStoreArt( msgId ); | |
| 796 } | |
| 797 | |
| 798 void | |
| 799 Client_retrieveArtList( const char *list ) | |
| 800 { | |
| 801 Str msgId; | |
| 802 DynStr *s; | |
| 803 const char *p; | |
| 804 | |
| 805 Log_inf( "Retrieving article list" ); | |
| 806 s = new_DynStr( strlen( list ) ); | |
| 807 p = list; | |
| 808 while ( ( p = Utl_getLn( msgId, p ) ) ) | |
| 809 if ( ! Db_contains( msgId ) ) | |
| 810 Log_err( "Skipping retrieving of %s (not prepared in database)", | |
| 811 msgId ); | |
| 812 else if ( ! ( Db_stat( msgId ) & DB_NOT_DOWNLOADED ) ) | |
| 813 Log_inf( "Skipping %s (already retrieved)", msgId ); | |
| 814 else if ( ! putCmdNoFlush( "ARTICLE %s", msgId ) ) | |
| 815 { | |
| 816 retrievingFailed( msgId, "Connection broke down" ); | |
| 817 del_DynStr( s ); | |
| 818 return; | |
| 819 } | |
| 820 else | |
| 821 DynStr_appLn( s, msgId ); | |
| 822 fflush( client.out ); | |
| 823 Log_dbg( "[S FLUSH]" ); | |
| 824 p = DynStr_str( s ); | |
| 825 while ( ( p = Utl_getLn( msgId, p ) ) ) | |
| 826 { | |
| 827 if ( getStat() != STAT_ART_FOLLOWS ) | |
| 828 retrievingFailed( msgId, client.lastStat ); | |
| 829 else if ( ! retrieveAndStoreArt( msgId ) ) | |
| 830 break; | |
| 831 } | |
| 832 del_DynStr( s ); | |
| 833 } | |
| 834 | |
| 835 Bool | |
| 836 Client_changeToGrp( const char* name ) | |
| 837 { | |
| 838 unsigned int stat; | |
| 839 int estimatedNumb, first, last; | |
| 840 | |
| 841 if ( ! Grp_exists( name ) ) | |
| 842 return FALSE; | |
| 843 if ( ! putCmd( "GROUP %s", name ) ) | |
| 844 return FALSE; | |
| 845 if ( getStat() != STAT_GRP_SELECTED ) | |
| 846 return FALSE; | |
| 847 if ( sscanf( client.lastStat, "%u %d %d %d", | |
| 848 &stat, &estimatedNumb, &first, &last ) != 4 ) | |
| 849 { | |
| 850 Log_err( "Bad server response to GROUP: %s", client.lastStat ); | |
| 851 return FALSE; | |
| 852 } | |
| 853 Utl_cpyStr( client.grp, name ); | |
| 854 client.rmtFirst = first; | |
| 855 client.rmtLast = last; | |
| 856 return TRUE; | |
| 857 } | |
| 858 | |
| 859 void | |
| 860 Client_rmtFirstLast( int *first, int *last ) | |
| 861 { | |
| 862 *first = client.rmtFirst; | |
| 863 *last = client.rmtLast; | |
| 864 } | |
| 865 | |
| 866 Bool | |
| 867 Client_postArt( const char *msgId, const char *artTxt, | |
| 868 Str errStr ) | |
| 869 { | |
| 870 if ( ! putCmd( "POST" ) ) | |
| 871 return FALSE; | |
| 872 if ( getStat() != STAT_SEND_ART ) | |
| 873 { | |
| 874 Log_err( "Posting of %s not allowed: %s", msgId, client.lastStat ); | |
| 875 strcpy( errStr, client.lastStat ); | |
| 876 return FALSE; | |
| 877 } | |
| 878 putTxtBuf( artTxt ); | |
| 879 putEndOfTxt(); | |
| 880 if ( getStat() != STAT_POST_OK ) | |
| 881 { | |
| 882 Log_err( "Posting of %s failed: %s", msgId, client.lastStat ); | |
| 883 strcpy( errStr, client.lastStat ); | |
| 884 return FALSE; | |
| 885 } | |
| 886 Log_inf( "Posted %s (Status: %s)", msgId, client.lastStat ); | |
| 887 return TRUE; | |
| 888 } |
