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 }