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