comparison client.c @ 0:04124a4423d4 noffle

[svn] Initial revision
author enz
date Tue, 04 Jan 2000 11:35:42 +0000
parents
children 6e822f4b19f9
comparison
equal deleted inserted replaced
-1:000000000000 0:04124a4423d4
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 }