comparison server.c @ 0:04124a4423d4 noffle

[svn] Initial revision
author enz
date Tue, 04 Jan 2000 11:35:42 +0000
parents
children 43631b72021f
comparison
equal deleted inserted replaced
-1:000000000000 0:04124a4423d4
1 /*
2 server.c
3
4 $Id: server.c 3 2000-01-04 11:35:42Z enz $
5 */
6
7 #include "server.h"
8 #include <ctype.h>
9 #include <signal.h>
10 #include <stdarg.h>
11 #include <sys/time.h>
12 #include <sys/types.h>
13 #include <time.h>
14 #include <unistd.h>
15 #include "client.h"
16 #include "common.h"
17 #include "config.h"
18 #include "content.h"
19 #include "database.h"
20 #include "dynamicstring.h"
21 #include "fetch.h"
22 #include "fetchlist.h"
23 #include "group.h"
24 #include "lock.h"
25 #include "log.h"
26 #include "online.h"
27 #include "outgoing.h"
28 #include "protocol.h"
29 #include "pseudo.h"
30 #include "request.h"
31 #include "util.h"
32
33 struct
34 {
35 Bool running;
36 int artPtr;
37 Str grp; /* selected group, "" if none */
38 } serv = { FALSE, 0, "" };
39
40 typedef struct Cmd
41 {
42 const char *name;
43 const char *syntax;
44 /* Returns false, if quit cmd */
45 Bool (*cmdProc)( char *arg, const struct Cmd *cmd );
46 }
47 Cmd;
48
49 static Bool doArt( char *arg, const Cmd *cmd );
50 static Bool doBody( char *arg, const Cmd *cmd );
51 static Bool doGrp( char *arg, const Cmd *cmd );
52 static Bool doHead( char *arg, const Cmd *cmd );
53 static Bool doHelp( char *arg, const Cmd *cmd );
54 static Bool doIhave( char *arg, const Cmd *cmd );
55 static Bool doLast( char *arg, const Cmd *cmd );
56 static Bool doList( char *arg, const Cmd *cmd );
57 static Bool doListgrp( char *arg, const Cmd *cmd );
58 static Bool doMode( char *arg, const Cmd *cmd );
59 static Bool doNewgrps( char *arg, const Cmd *cmd );
60 static Bool doNext( char *arg, const Cmd *cmd );
61 static Bool doPost( char *arg, const Cmd *cmd );
62 static Bool doSlave( char *arg, const Cmd *cmd );
63 static Bool doStat( char *arg, const Cmd *cmd );
64 static Bool doQuit( char *arg, const Cmd *cmd );
65 static Bool doXhdr( char *arg, const Cmd *cmd );
66 static Bool doXpat( char *arg, const Cmd *cmd );
67 static Bool doXOver( char *arg, const Cmd *cmd );
68 static Bool notImplemented( char *arg, const Cmd *cmd );
69 static void putStat( unsigned int stat, const char *fmt, ... );
70
71 Cmd commands[] =
72 {
73 { "article", "ARTICLE [msg-id|n]", &doArt },
74 { "body", "BODY [msg-id|n]", &doBody },
75 { "head", "HEAD [msg-id|n]", &doHead },
76 { "group", "GROUP grp", &doGrp },
77 { "help", "HELP", &doHelp },
78 { "ihave", "IHAVE (ignored)", &doIhave },
79 { "last", "LAST", &doLast },
80 { "list", "LIST [ACTIVE [pat]]|ACTIVE.TIMES [pat]|"
81 "EXTENSIONS|NEWSGROUPS [pat]|OVERVIEW.FMT", &doList },
82 { "listgroup", "LISTGROUP grp", &doListgrp },
83 { "mode", "MODE (ignored)", &doMode },
84 { "newgroups", "NEWGROUPS [xx]yymmdd hhmmss [GMT]", &doNewgrps },
85 { "newnews", "NEWNEWS (not implemented)", &notImplemented },
86 { "next", "NEXT", &doNext },
87 { "post", "POST", &doPost },
88 { "quit", "QUIT", &doQuit },
89 { "slave", "SLAVE (ignored)", &doSlave },
90 { "stat", "STAT [msg-id|n]", &doStat },
91 { "xhdr", "XHDR over-field [m[-[n]]]", &doXhdr },
92 { "xpat", "XPAT over-field m[-[n]] pat", &doXpat },
93 { "xover", "XOVER [m[-[n]]]", &doXOver }
94 };
95
96 /*
97 Notice interest in reading this group.
98 Automatically subscribe if option set in config file.
99 */
100 static void
101 noteInterest( void )
102 {
103 FetchMode mode;
104
105 Grp_setLastAccess( serv.grp, time( NULL ) );
106 if ( Cfg_autoSubscribe() && ! Online_true() )
107 {
108 Fetchlist_read();
109 if ( ! Fetchlist_contains( serv.grp ) )
110 {
111 if ( strcmp( Cfg_autoSubscribeMode(), "full" ) == 0 )
112 mode = FULL;
113 else if ( strcmp( Cfg_autoSubscribeMode(), "thread" ) == 0 )
114 mode = THREAD;
115 else
116 mode = OVER;
117 Fetchlist_add( serv.grp, mode );
118 Fetchlist_write();
119 Pseudo_autoSubscribed();
120 }
121 }
122 }
123
124 static void
125 putStat( unsigned int stat, const char *fmt, ... )
126 {
127 Str s, line;
128 va_list ap;
129
130 ASSERT( stat <= 999 );
131 va_start( ap, fmt );
132 vsnprintf( s, MAXCHAR, fmt, ap );
133 va_end( ap );
134 snprintf( line, MAXCHAR, "%u %s", stat, s );
135 Log_dbg( "[S] %s", line );
136 printf( "%s\r\n", line );
137 }
138
139 static void
140 putTxtLn( const char *fmt, ... )
141 {
142 Str line;
143 va_list ap;
144
145 va_start( ap, fmt );
146 vsnprintf( line, MAXCHAR, fmt, ap );
147 va_end( ap );
148 Prt_putTxtLn( line, stdout );
149 }
150
151 static void
152 putTxtBuf( const char *buf )
153 {
154 if ( buf )
155 Prt_putTxtBuf( buf, stdout );
156 }
157
158 static void
159 putEndOfTxt( void )
160 {
161 Prt_putEndOfTxt( stdout );
162 }
163
164 static void
165 putSyntax( const Cmd *cmd )
166 {
167 putStat( STAT_SYNTAX_ERR, "Syntax error. Usage: %s", cmd->syntax );
168 }
169
170 static Bool
171 getLn( Str line )
172 {
173 return Prt_getLn( line, stdin );
174 }
175
176 static Bool
177 getTxtLn( Str line, Bool *err )
178 {
179 return Prt_getTxtLn( line, err, stdin );
180 }
181
182 static Bool
183 notImplemented( char *arg, const Cmd *cmd )
184 {
185 putStat( STAT_NO_PERMISSION, "Command not implemented" );
186 return TRUE;
187 }
188
189 static void
190 checkNewArts( const char *grp )
191 {
192 if ( ! Online_true()
193 || strcmp( grp, serv.grp ) == 0
194 || time( NULL ) - Grp_lastAccess( serv.grp ) < 1800 )
195 return;
196 if ( Fetch_init( Grp_serv( grp ) ) )
197 {
198 Fetch_getNewArts( grp, OVER );
199 Fetch_close();
200 }
201 }
202
203 static void
204 postArts()
205 {
206 Str serv;
207
208 Cfg_beginServEnum();
209 while ( Cfg_nextServ( serv ) )
210 if ( Fetch_init( serv ) )
211 {
212 Fetch_postArts();
213 Fetch_close();
214 }
215 }
216
217 static void
218 readCont( const char *name )
219 {
220 Fetchlist_read();
221 Cont_read( name );
222 if ( ! Fetchlist_contains( name ) && ! Online_true() )
223 {
224 Pseudo_appGeneralInfo();
225 Grp_setFirstLast( name, Cont_first(), Cont_last() );
226 }
227 }
228
229 static void
230 changeToGrp( const char *grp )
231 {
232 checkNewArts( grp );
233 Utl_cpyStr( serv.grp, grp );
234 readCont( grp );
235 serv.artPtr = Cont_first();
236 }
237
238 static Bool
239 doGrp( char *arg, const Cmd *cmd )
240 {
241 int first, last, numb;
242
243 if ( arg[ 0 ] == '\0' )
244 putSyntax( cmd );
245 else if ( ! Grp_exists( arg ) )
246 putStat( STAT_NO_SUCH_GRP, "No such group" );
247 else
248 {
249 changeToGrp( arg );
250 first = Cont_first();
251 last = Cont_last();
252 numb = last - first + 1;
253 if ( first > last )
254 first = last = numb = 0;
255 putStat( STAT_GRP_SELECTED, "%lu %lu %lu %s selected",
256 numb, first, last, arg );
257 }
258 return TRUE;
259 }
260
261 static Bool
262 testGrpSelected( void )
263 {
264 if ( *serv.grp == '\0' )
265 {
266 putStat( STAT_NO_GRP_SELECTED, "No group selected" );
267 return FALSE;
268 }
269 return TRUE;
270 }
271
272 static void
273 findServ( const char *msgId, Str result )
274 {
275 const char *p, *pColon, *serv;
276 Str s, grp;
277
278 Utl_cpyStr( result, "(unknown)" );
279 if ( Db_contains( msgId ) )
280 {
281 Utl_cpyStr( s, Db_xref( msgId ) );
282 p = strtok( s, " \t" );
283 if ( p )
284 do
285 {
286 pColon = strstr( p, ":" );
287 if ( pColon )
288 {
289 Utl_cpyStrN( grp, p, pColon - p );
290 serv = Grp_serv( grp );
291 if ( Cfg_servIsPreferential( serv, result ) )
292 Utl_cpyStr( result, serv );
293 }
294 }
295 while ( ( p = strtok( NULL, " \t" ) ) );
296 }
297 }
298
299 static Bool
300 retrieveArt( const char *msgId )
301 {
302 Str serv;
303
304 findServ( msgId, serv );
305 if ( strcmp( serv, "(unknown)" ) == 0 )
306 return FALSE;
307 if ( ! Client_connect( serv ) )
308 return FALSE;
309 Client_retrieveArt( msgId );
310 Client_disconnect();
311 return TRUE;
312 }
313
314 static Bool
315 checkNumb( int numb )
316 {
317 if ( ! testGrpSelected() )
318 return FALSE;
319 if ( ! Cont_validNumb( numb ) )
320 {
321 putStat( STAT_NO_SUCH_NUMB, "No such article" );
322 return FALSE;
323 }
324 return TRUE;
325 }
326
327 /*
328 Parse arguments for ARTICLE, BODY, HEAD, STAT commands.
329 Return message-ID and article number (0 if unknown).
330 */
331 static Bool
332 whichId( const char **msgId, int *numb, char *arg )
333 {
334 const Over *ov;
335 int n;
336
337 if ( sscanf( arg, "%i", &n ) == 1 )
338 {
339 if ( ! checkNumb( n ) )
340 return FALSE;
341 serv.artPtr = n;
342 ov = Cont_get( n );
343 *msgId = Ov_msgId( ov );
344 *numb = n;
345 }
346 else if ( strcmp( arg, "" ) == 0 )
347 {
348 if ( ! checkNumb( serv.artPtr ) )
349 return FALSE;
350 ov = Cont_get( serv.artPtr );
351 *msgId = Ov_msgId( ov );
352 *numb = serv.artPtr;
353 }
354 else
355 {
356 *msgId = arg;
357 *numb = 0;
358 }
359 if ( ! Pseudo_isGeneralInfo( *msgId ) && ! Db_contains( *msgId ) )
360 {
361 putStat( STAT_NO_SUCH_NUMB, "No such article" );
362 return FALSE;
363 }
364 return TRUE;
365 }
366
367 void
368 touchArticle( const char *msgId )
369 {
370 int stat = Db_stat( msgId );
371 stat |= DB_INTERESTING;
372 Db_setStat( msgId, stat );
373 Db_updateLastAccess( msgId );
374 }
375
376 static void
377 touchReferences( const char *msgId )
378 {
379 Str s;
380 int len;
381 char *p;
382 const char *ref = Db_ref( msgId );
383
384 while ( TRUE )
385 {
386 p = s;
387 while ( *ref != '<' )
388 if ( *(ref++) == '\0' )
389 return;
390 len = 0;
391 while ( *ref != '>' )
392 {
393 if ( *ref == '\0' || ++len >= MAXCHAR - 1 )
394 return;
395 *(p++) = *(ref++);
396 }
397 *(p++) = '>';
398 *p = '\0';
399 if ( Db_contains( s ) )
400 touchArticle( s );
401 }
402 }
403
404 static void
405 doBodyInDb( const char *msgId )
406 {
407 int stat;
408 Str serv;
409
410 touchArticle( msgId );
411 touchReferences( msgId );
412 stat = Db_stat( msgId );
413 if ( Online_true() && ( stat & DB_NOT_DOWNLOADED ) )
414 {
415 retrieveArt( msgId );
416 stat = Db_stat( msgId );
417 }
418 if ( stat & DB_RETRIEVING_FAILED )
419 {
420 Db_setStat( msgId, stat & ~DB_RETRIEVING_FAILED );
421 putTxtBuf( Db_body( msgId ) );
422 }
423 else if ( stat & DB_NOT_DOWNLOADED )
424 {
425 findServ( msgId, serv );
426 if ( Req_contains( serv, msgId ) )
427 putTxtBuf( Pseudo_alreadyMarkedBody() );
428 else if ( strcmp( serv, "(unknown)" ) != 0 && Req_add( serv, msgId ) )
429 putTxtBuf( Pseudo_markedBody() );
430 else
431 putTxtBuf( Pseudo_markingFailedBody() );
432 }
433 else
434 putTxtBuf( Db_body( msgId ) );
435 }
436
437 static Bool
438 doBody( char *arg, const Cmd *cmd )
439 {
440 const char *msgId;
441 int numb;
442
443 if ( ! whichId( &msgId, &numb, arg ) )
444 return TRUE;
445 putStat( STAT_BODY_FOLLOWS, "%ld %s Body", numb, msgId );
446 if ( Pseudo_isGeneralInfo( msgId ) )
447 putTxtBuf( Pseudo_generalInfoBody() );
448 else
449 doBodyInDb( msgId );
450 putEndOfTxt();
451 noteInterest();
452 return TRUE;
453 }
454
455 static void
456 doHeadInDb( const char *msgId )
457 {
458 putTxtBuf( Db_header( msgId ) );
459 }
460
461 static Bool
462 doHead( char *arg, const Cmd *cmd )
463 {
464 const char *msgId;
465 int numb;
466
467 if ( ! whichId( &msgId, &numb, arg ) )
468 return TRUE;
469 putStat( STAT_HEAD_FOLLOWS, "%ld %s Head", numb, msgId );
470 if ( Pseudo_isGeneralInfo( msgId ) )
471 putTxtBuf( Pseudo_generalInfoHead() );
472 else
473 doHeadInDb( msgId );
474 putEndOfTxt();
475 return TRUE;
476 }
477
478 static void
479 doArtInDb( const char *msgId )
480 {
481 doHeadInDb( msgId );
482 putTxtLn( "" );
483 doBodyInDb( msgId );
484 }
485
486 static Bool
487 doArt( char *arg, const Cmd *cmd )
488 {
489 const char *msgId;
490 int numb;
491
492 if ( ! whichId( &msgId, &numb, arg ) )
493 return TRUE;
494 putStat( STAT_ART_FOLLOWS, "%ld %s Article", numb, msgId );
495 if ( Pseudo_isGeneralInfo( msgId ) )
496 {
497 putTxtBuf( Pseudo_generalInfoHead() );
498 putTxtLn( "" );
499 putTxtBuf( Pseudo_generalInfoBody() );
500 }
501 else
502 doArtInDb( msgId );
503 putEndOfTxt();
504 noteInterest();
505 return TRUE;
506 }
507
508 static Bool
509 doHelp( char *arg, const Cmd *cmd )
510 {
511 unsigned int i;
512
513 putStat( STAT_HELP_FOLLOWS, "Help" );
514 putTxtBuf( "\nCommands:\n\n" );
515 for ( i = 0; i < sizeof( commands ) / sizeof( commands[ 0 ] ); ++i )
516 putTxtLn( "%s", commands[ i ].syntax );
517 putEndOfTxt();
518 return TRUE;
519 }
520
521 static Bool
522 doIhave( char *arg, const Cmd *cmd )
523 {
524 putStat( STAT_ART_REJECTED, "Command not used" );
525 return TRUE;
526 }
527
528 static Bool
529 doLast( char *arg, const Cmd *cmd )
530 {
531 int n;
532
533 if ( testGrpSelected() )
534 {
535 n = serv.artPtr;
536 if ( ! Cont_validNumb( n ) )
537 putStat( STAT_NO_ART_SELECTED, "No article selected" );
538 else
539 {
540 while ( ! Cont_validNumb( --n ) && n >= Cont_first() );
541 if ( ! Cont_validNumb( n ) )
542 putStat( STAT_NO_PREV_ART, "No previous article" );
543 else
544 {
545 putStat( STAT_ART_RETRIEVED, "%ld %s selected",
546 n, Ov_msgId( Cont_get( n ) ) );
547 serv.artPtr = n;
548 }
549 }
550 }
551 return TRUE;
552 }
553
554 static void
555 printGroups( const char *pat, void (*printProc)( Str, const char* ) )
556 {
557 Str line;
558 const char *g;
559 FILE *f;
560 sig_t lastHandler;
561 int ret;
562
563 putStat( STAT_GRPS_FOLLOW, "Groups" );
564 fflush( stdout );
565 Log_dbg( "[S FLUSH]" );
566 if ( Grp_exists( pat ) )
567 {
568 (*printProc)( line, pat );
569 if ( ! Prt_putTxtLn( line, stdout ) )
570 Log_err( "Writing to stdout failed." );
571 }
572 else
573 {
574 lastHandler = signal( SIGPIPE, SIG_IGN );
575 f = popen( "sort", "w" );
576 if ( f == NULL )
577 {
578 Log_err( "Cannot open pipe to 'sort'" );
579 if ( Grp_firstGrp( &g ) )
580 do
581 if ( Utl_matchPattern( g, pat ) )
582 {
583 (*printProc)( line, g );
584 if ( ! Prt_putTxtLn( line, stdout ) )
585 Log_err( "Writing to stdout failed." );
586 }
587 while ( Grp_nextGrp( &g ) );
588 }
589 else
590 {
591 if ( Grp_firstGrp( &g ) )
592 do
593 if ( Utl_matchPattern( g, pat ) )
594 {
595 (*printProc)( line, g );
596 if ( ! Prt_putTxtLn( line, f ) )
597 {
598 Log_err( "Writing to 'sort' pipe failed." );
599 break;
600 }
601 }
602 while ( Grp_nextGrp( &g ) );
603 ret = pclose( f );
604 if ( ret != EXIT_SUCCESS )
605 Log_err( "sort command returned %i", ret );
606 fflush( stdout );
607 Log_dbg( "[S FLUSH]" );
608 signal( SIGPIPE, lastHandler );
609 }
610 }
611 putEndOfTxt();
612 }
613
614 static void
615 printActiveTimes( Str result, const char *grp )
616 {
617 snprintf( result, MAXCHAR, "%s %ld", grp, Grp_created( grp ) );
618 }
619
620 static void
621 doListActiveTimes( const char *pat )
622 {
623 printGroups( pat, &printActiveTimes );
624 }
625
626 static void
627 printActive( Str result, const char *grp )
628 {
629 snprintf( result, MAXCHAR, "%s %i %i y",
630 grp, Grp_last( grp ), Grp_first( grp ) );
631 }
632
633 static void
634 doListActive( const char *pat )
635 {
636 printGroups( pat, &printActive );
637 }
638
639 static void
640 printNewsgrp( Str result, const char *grp )
641 {
642 snprintf( result, MAXCHAR, "%s %s", grp, Grp_dsc( grp ) );
643 }
644
645 static void
646 doListNewsgrps( const char *pat )
647 {
648 printGroups( pat, &printNewsgrp );
649 }
650
651 static void
652 putGrp( const char *name )
653 {
654 putTxtLn( "%s %lu %lu y", name, Grp_last( name ), Grp_first( name ) );
655 }
656
657 static void
658 doListOverFmt( void )
659 {
660 putStat( STAT_GRPS_FOLLOW, "Overview format" );
661 putTxtBuf( "Subject:\n"
662 "From:\n"
663 "Date:\n"
664 "Message-ID:\n"
665 "References:\n"
666 "Bytes:\n"
667 "Lines:\n" );
668 putEndOfTxt();
669 }
670
671 static void
672 doListExtensions( void )
673 {
674 putStat( STAT_CMD_OK, "Extensions" );
675 putTxtBuf( " LISTGROUP\n"
676 " XOVER\n" );
677 putEndOfTxt();
678 }
679
680 static Bool
681 doList( char *line, const Cmd *cmd )
682 {
683 Str s, arg;
684 const char *pat;
685
686 if ( sscanf( line, "%s", s ) != 1 )
687 doListActive( "*" );
688 else
689 {
690 Utl_toLower( s );
691 strcpy( arg, Utl_restOfLn( line, 1 ) );
692 pat = Utl_stripWhiteSpace( arg );
693 if ( pat[ 0 ] == '\0' )
694 pat = "*";
695 if ( strcmp( "active", s ) == 0 )
696 doListActive( pat );
697 else if ( strcmp( "overview.fmt", s ) == 0 )
698 doListOverFmt();
699 else if ( strcmp( "newsgroups", s ) == 0 )
700 doListNewsgrps( pat );
701 else if ( strcmp( "active.times", s ) == 0 )
702 doListActiveTimes( pat );
703 else if ( strcmp( "extensions", s ) == 0 )
704 doListExtensions();
705 else
706 putSyntax( cmd );
707 }
708 return TRUE;
709 }
710
711 static Bool
712 doListgrp( char *arg, const Cmd *cmd )
713 {
714 const Over *ov;
715 int first, last, i;
716
717 if ( ! Grp_exists( arg ) )
718 putStat( STAT_NO_SUCH_GRP, "No such group" );
719 else
720 {
721 changeToGrp( arg );
722 first = Cont_first();
723 last = Cont_last();
724 putStat( STAT_GRP_SELECTED, "Article list" );
725 for ( i = first; i <= last; ++i )
726 if ( ( ov = Cont_get( i ) ) )
727 putTxtLn( "%lu", i );
728 putEndOfTxt();
729 }
730 return TRUE;
731 }
732
733 static Bool
734 doMode( char *arg, const Cmd *cmd )
735 {
736 putStat( STAT_READY_POST_ALLOW, "Ok" );
737 return TRUE;
738 }
739
740 static unsigned long
741 getTimeInSeconds( unsigned int year, unsigned int mon, unsigned int day,
742 unsigned int hour, unsigned int min, unsigned int sec )
743 {
744 struct tm t = { 0 };
745
746 t.tm_year = year - 1900;
747 t.tm_mon = mon - 1;
748 t.tm_mday = day;
749 t.tm_hour = hour;
750 t.tm_min = min;
751 t.tm_sec = sec;
752 return mktime( &t );
753 }
754
755
756 static Bool
757 doNewgrps( char *arg, const Cmd *cmd )
758 {
759 time_t t, now, lastUpdate;
760 unsigned int year, mon, day, hour, min, sec, cent, len;
761 const char *g;
762 Str date, timeofday, file;
763
764 if ( sscanf( arg, "%s %s", date, timeofday ) != 2 )
765 {
766 putSyntax( cmd );
767 return TRUE;
768 }
769 len = strlen( date );
770 switch ( len )
771 {
772 case 6:
773 if ( sscanf( date, "%2u%2u%2u", &year, &mon, &day ) != 3 )
774 {
775 putSyntax( cmd );
776 return TRUE;
777 }
778 now = time( NULL );
779 cent = 1900;
780 while ( now > getTimeInSeconds( cent + 100, 1, 1, 0, 0, 0 ) )
781 cent += 100;
782 year += cent;
783 break;
784 case 8:
785 if ( sscanf( date, "%4u%2u%2u", &year, &mon, &day ) != 3 )
786 {
787 putSyntax( cmd );
788 return TRUE;
789 }
790 break;
791 default:
792 putSyntax( cmd );
793 return TRUE;
794 }
795 if ( sscanf( timeofday, "%2u%2u%2u", &hour, &min, &sec ) != 3 )
796 {
797 putSyntax( cmd );
798 return TRUE;
799 }
800 if ( year < 1970 || mon == 0 || mon > 12 || day == 0 || day > 31
801 || hour > 23 || min > 59 || sec > 60 )
802 {
803 putSyntax( cmd );
804 return TRUE;
805 }
806 snprintf( file, MAXCHAR, "%s/groupinfo.lastupdate", Cfg_spoolDir() );
807 t = getTimeInSeconds( year, mon, day, hour, min, sec );
808 putStat( STAT_NEW_GRP_FOLLOW, "New groups since %s", arg );
809
810 if ( ! Utl_getStamp( &lastUpdate, file ) || t <= lastUpdate )
811 {
812 if ( Grp_firstGrp( &g ) )
813 do
814 if ( Grp_created( g ) > t )
815 putGrp( g );
816 while ( Grp_nextGrp( &g ) );
817 }
818 putEndOfTxt();
819 return TRUE;
820 }
821
822 static Bool
823 doNext( char *arg, const Cmd *cmd )
824 {
825 int n;
826
827 if ( testGrpSelected() )
828 {
829 n = serv.artPtr;
830 if ( ! Cont_validNumb( n ) )
831 putStat( STAT_NO_ART_SELECTED, "No article selected" );
832 else
833 {
834 while ( ! Cont_validNumb( ++n ) && n <= Cont_last() );
835 if ( ! Cont_validNumb( n ) )
836 putStat( STAT_NO_NEXT_ART, "No next article" );
837 else
838 {
839 putStat( STAT_ART_RETRIEVED, "%ld %s selected",
840 n, Ov_msgId( Cont_get( n ) ) );
841 serv.artPtr = n;
842 }
843 }
844 }
845 return TRUE;
846 }
847
848 /*
849 Get first group of the Newsgroups field content, which is
850 a comma separated list of groups.
851 */
852 static void
853 getFirstGrp( char *grpResult, const char *list )
854 {
855 Str t;
856 const char *src = list;
857 char *dest = t;
858 while( TRUE )
859 {
860 if ( *src == ',' )
861 *dest = ' ';
862 else
863 *dest = *src;
864 if ( *src == '\0' )
865 break;
866 ++src;
867 ++dest;
868 }
869 *grpResult = '\0';
870 sscanf( t, "%s", grpResult );
871 }
872
873 static Bool
874 doPost( char *arg, const Cmd *cmd )
875 {
876 Bool err, replyToFound, inHeader;
877 DynStr *s;
878 Str line, field, val, msgId, from, grp;
879 const char* p;
880
881 /*
882 Get article and make following changes to the header:
883 - add/replace/cut Message-ID depending on config options
884 - add Reply-To with content of From, if missing
885 (some providers overwrite From field)
886 - rename X-Sender header to X-NOFFLE-X-Sender
887 (some providers want to insert their own X-Sender)
888 */
889 putStat( STAT_SEND_ART, "Continue (end with period)" );
890 fflush( stdout );
891 Log_dbg( "[S FLUSH]" );
892 s = new_DynStr( 10000 );
893 msgId[ 0 ] = '\0';
894 from[ 0 ] = '\0';
895 grp[ 0 ] = '\0';
896 replyToFound = FALSE;
897 inHeader = TRUE;
898 while ( getTxtLn( line, &err ) )
899 {
900 if ( inHeader )
901 {
902 p = Utl_stripWhiteSpace( line );
903 if ( *p == '\0' )
904 {
905 inHeader = FALSE;
906 if ( from[ 0 ] == '\0' )
907 Log_err( "Posted message has no From field" );
908 if ( ! Cfg_removeMsgId() )
909 {
910 if ( Cfg_replaceMsgId() )
911 {
912 Prt_genMsgId( msgId, from, "NOFFLE" );
913 Log_dbg( "Replacing Message-ID with '%s'", msgId );
914 }
915 else if ( msgId[ 0 ] == '\0' )
916 {
917 Prt_genMsgId( msgId, from, "NOFFLE" );
918 Log_inf( "Adding missing Message-ID '%s'", msgId );
919 }
920 else if ( ! Prt_isValidMsgId( msgId ) )
921 {
922 Log_ntc( "Replacing invalid Message-ID with '%s'",
923 msgId );
924 Prt_genMsgId( msgId, from, "NOFFLE" );
925 }
926 DynStr_app( s, "Message-ID: " );
927 DynStr_appLn( s, msgId );
928 }
929 if ( ! replyToFound && from[ 0 ] != '\0' )
930 {
931 Log_dbg( "Adding Reply-To field to posted message." );
932 DynStr_app( s, "Reply-To: " );
933 DynStr_appLn( s, from );
934 }
935 DynStr_appLn( s, p );
936 }
937 else if ( Prt_getField( field, val, p ) )
938 {
939 if ( strcmp( field, "message-id" ) == 0 )
940 strcpy( msgId, val );
941 else if ( strcmp( field, "from" ) == 0 )
942 {
943 strcpy( from, val );
944 DynStr_appLn( s, p );
945 }
946 else if ( strcmp( field, "newsgroups" ) == 0 )
947 {
948 getFirstGrp( grp, val );
949 Utl_toLower( grp );
950 DynStr_appLn( s, p );
951 }
952 else if ( strcmp( field, "reply-to" ) == 0 )
953 {
954 replyToFound = TRUE;
955 DynStr_appLn( s, p );
956 }
957 else if ( strcmp( field, "x-sender" ) == 0 )
958 {
959 DynStr_app( s, "X-NOFFLE-X-Sender: " );
960 DynStr_appLn( s, val );
961 }
962 else
963 DynStr_appLn( s, p );
964 }
965 else
966 Log_err( "Ignoring invalid header line '%s'", p );
967 }
968 else
969 DynStr_appLn( s, line );
970 }
971 if ( inHeader )
972 Log_err( "Posted message has no body" );
973 if ( ! err )
974 {
975 if ( grp[ 0 ] == '\0' )
976 {
977 Log_err( "Posted message has no Newsgroups header field" );
978 err = TRUE;
979 }
980 else if ( ! Grp_exists( grp ) )
981 {
982 Log_err( "Unknown group in Newsgroups header field" );
983 err = TRUE;
984 }
985 else if ( ! Out_add( Grp_serv( grp ), msgId, s ) )
986 {
987 Log_err( "Cannot add posted article to outgoing directory" );
988 err = TRUE;
989 }
990 }
991 if ( err )
992 putStat( STAT_POST_FAILED, "Posting failed" );
993 else
994 {
995 putStat( STAT_POST_OK, "Message queued for posting" );
996 if ( Online_true() )
997 postArts();
998 }
999 del_DynStr( s );
1000 return TRUE;
1001 }
1002
1003 static void
1004 parseRange( const char *s, int *first, int *last, int *numb )
1005 {
1006 int r, i;
1007 char* p;
1008 Str t;
1009
1010 Utl_cpyStr( t, s );
1011 p = Utl_stripWhiteSpace( t );
1012 r = sscanf( p, "%i-%i", first, last );
1013 if ( r < 1 )
1014 {
1015 *first = serv.artPtr;
1016 *last = serv.artPtr;
1017 }
1018 else if ( r == 1 )
1019 {
1020 if ( p[ strlen( p ) - 1 ] == '-' )
1021 *last = Cont_last();
1022 else
1023 *last = *first;
1024 }
1025 if ( *first < Cont_first() )
1026 *first = Cont_first();
1027 if ( *last > Cont_last() )
1028 *last = Cont_last();
1029 if ( *first > Cont_last() || *last < Cont_first() )
1030 *last = *first - 1;
1031 *numb = 0;
1032 for ( i = *first; i <= *last; ++i )
1033 if ( Cont_validNumb( i ) )
1034 ++(*numb);
1035 }
1036
1037 static Bool
1038 doXhdr( char *arg, const Cmd *cmd )
1039 {
1040 int first, last, i, n, numb;
1041 enum { SUBJ, FROM, DATE, MSG_ID, REF, BYTES, LINES } what;
1042 const char *p;
1043 const Over *ov;
1044 Str whatStr;
1045
1046 if ( ! testGrpSelected() )
1047 return TRUE;
1048 if ( sscanf( arg, "%s", whatStr ) != 1 )
1049 {
1050 putSyntax( cmd );
1051 return TRUE;
1052 }
1053 Utl_toLower( whatStr );
1054 if ( strcmp( whatStr, "subject" ) == 0 )
1055 what = SUBJ;
1056 else if ( strcmp( whatStr, "from" ) == 0 )
1057 what = FROM;
1058 else if ( strcmp( whatStr, "date" ) == 0 )
1059 what = DATE;
1060 else if ( strcmp( whatStr, "message-id" ) == 0 )
1061 what = MSG_ID;
1062 else if ( strcmp( whatStr, "references" ) == 0 )
1063 what = REF;
1064 else if ( strcmp( whatStr, "bytes" ) == 0 )
1065 what = BYTES;
1066 else if ( strcmp( whatStr, "lines" ) == 0 )
1067 what = LINES;
1068 else
1069 {
1070 putStat( STAT_HEAD_FOLLOWS, "Unknown header (empty list follows)" );
1071 putEndOfTxt();
1072 return TRUE;
1073 }
1074 p = Utl_restOfLn( arg, 1 );
1075 parseRange( p, &first, &last, &numb );
1076 if ( numb == 0 )
1077 putStat( STAT_NO_ART_SELECTED, "No articles selected" );
1078 else
1079 {
1080 putStat( STAT_HEAD_FOLLOWS, "%s header %lu-%lu",
1081 whatStr, first, last ) ;
1082 for ( i = first; i <= last; ++i )
1083 if ( ( ov = Cont_get( i ) ) )
1084 {
1085 n = Ov_numb( ov );
1086 switch ( what )
1087 {
1088 case SUBJ:
1089 putTxtLn( "%lu %s", n, Ov_subj( ov ) );
1090 break;
1091 case FROM:
1092 putTxtLn( "%lu %s", n, Ov_from( ov ) );
1093 break;
1094 case DATE:
1095 putTxtLn( "%lu %s", n, Ov_date( ov ) );
1096 break;
1097 case MSG_ID:
1098 putTxtLn( "%lu %s", n, Ov_msgId( ov ) );
1099 break;
1100 case REF:
1101 putTxtLn( "%lu %s", n, Ov_ref( ov ) );
1102 break;
1103 case BYTES:
1104 putTxtLn( "%lu %d", n, Ov_bytes( ov ) );
1105 break;
1106 case LINES:
1107 putTxtLn( "%lu %d", n, Ov_lines( ov ) );
1108 break;
1109 default:
1110 ASSERT( FALSE );
1111 }
1112 }
1113 putEndOfTxt();
1114 }
1115 return TRUE;
1116 }
1117
1118 static Bool
1119 doXpat( char *arg, const Cmd *cmd )
1120 {
1121 int first, last, i, n;
1122 enum { SUBJ, FROM, DATE, MSG_ID, REF } what;
1123 const Over *ov;
1124 Str whatStr, pat;
1125
1126 if ( ! testGrpSelected() )
1127 return TRUE;
1128 if ( sscanf( arg, "%s %i-%i %s", whatStr, &first, &last, pat ) != 4 )
1129 {
1130 if ( sscanf( arg, "%s %i- %s", whatStr, &first, pat ) == 3 )
1131 last = Cont_last();
1132 else if ( sscanf( arg, "%s %i %s", whatStr, &first, pat ) == 3 )
1133 last = first;
1134 else
1135 {
1136 putSyntax( cmd );
1137 return TRUE;
1138 }
1139 }
1140 Utl_toLower( whatStr );
1141 if ( strcmp( whatStr, "subject" ) == 0 )
1142 what = SUBJ;
1143 else if ( strcmp( whatStr, "from" ) == 0 )
1144 what = FROM;
1145 else if ( strcmp( whatStr, "date" ) == 0 )
1146 what = DATE;
1147 else if ( strcmp( whatStr, "message-id" ) == 0 )
1148 what = MSG_ID;
1149 else if ( strcmp( whatStr, "references" ) == 0 )
1150 what = REF;
1151 else
1152 {
1153 putStat( STAT_HEAD_FOLLOWS, "invalid header (empty list follows)" );
1154 putEndOfTxt();
1155 return TRUE;
1156 }
1157 putStat( STAT_HEAD_FOLLOWS, "header" ) ;
1158 for ( i = first; i <= last; ++i )
1159 if ( ( ov = Cont_get( i ) ) )
1160 {
1161 n = Ov_numb( ov );
1162 switch ( what )
1163 {
1164 case SUBJ:
1165 if ( Utl_matchPattern( Ov_subj( ov ), pat ) )
1166 putTxtLn( "%lu %s", n, Ov_subj( ov ) );
1167 break;
1168 case FROM:
1169 if ( Utl_matchPattern( Ov_from( ov ), pat ) )
1170 putTxtLn( "%lu %s", n, Ov_from( ov ) );
1171 break;
1172 case DATE:
1173 if ( Utl_matchPattern( Ov_date( ov ), pat ) )
1174 putTxtLn( "%lu %s", n, Ov_date( ov ) );
1175 break;
1176 case MSG_ID:
1177 if ( Utl_matchPattern( Ov_msgId( ov ), pat ) )
1178 putTxtLn( "%lu %s", n, Ov_msgId( ov ) );
1179 break;
1180 case REF:
1181 if ( Utl_matchPattern( Ov_ref( ov ), pat ) )
1182 putTxtLn( "%lu %s", n, Ov_ref( ov ) );
1183 break;
1184 default:
1185 ASSERT( FALSE );
1186 }
1187 }
1188 putEndOfTxt();
1189 return TRUE;
1190 }
1191
1192 static Bool
1193 doSlave( char *arg, const Cmd *cmd )
1194 {
1195 putStat( STAT_CMD_OK, "Ok" );
1196 return TRUE;
1197 }
1198
1199 static Bool
1200 doStat( char *arg, const Cmd *cmd )
1201 {
1202 const char *msgId;
1203 int numb;
1204
1205 if ( ! whichId( &msgId, &numb, arg ) )
1206 return TRUE;
1207 if ( numb > 0 )
1208 putStat( STAT_ART_RETRIEVED, "%ld %s selected",
1209 numb, msgId );
1210 else
1211 putStat( STAT_ART_RETRIEVED, "0 %s selected", msgId );
1212 return TRUE;
1213 }
1214
1215 static Bool
1216 doQuit( char *arg, const Cmd *cmd )
1217 {
1218 putStat( STAT_GOODBYE, "Goodbye" );
1219 return FALSE;
1220 }
1221
1222 static Bool
1223 doXOver( char *arg, const Cmd *cmd )
1224 {
1225 int first, last, i, n;
1226 const Over *ov;
1227
1228 if ( ! testGrpSelected() )
1229 return TRUE;
1230 parseRange( arg, &first, &last, &n );
1231 if ( n == 0 )
1232 putStat( STAT_NO_ART_SELECTED, "No articles selected" );
1233 else
1234 {
1235 putStat( STAT_OVERS_FOLLOW, "Overview %ld-%ld", first, last );
1236 for ( i = first; i <= last; ++i )
1237 if ( ( ov = Cont_get( i ) ) )
1238 putTxtLn( "%lu\t%s\t%s\t%s\t%s\t%s\t%d\t%d\t",
1239 Ov_numb( ov ), Ov_subj( ov ), Ov_from( ov ),
1240 Ov_date( ov ), Ov_msgId( ov ), Ov_ref( ov ),
1241 Ov_bytes( ov ), Ov_lines( ov ) );
1242 putEndOfTxt();
1243 }
1244 return TRUE;
1245 }
1246
1247 static void
1248 putFatal( const char *fmt, ... )
1249 {
1250 va_list ap;
1251 Str s;
1252
1253 va_start( ap, fmt );
1254 vsnprintf( s, MAXCHAR, fmt, ap );
1255 va_end( ap );
1256 Log_err( s );
1257 putStat( STAT_PROGRAM_FAULT, "%s", s );
1258 fflush( stdout );
1259 Log_dbg( "[S FLUSH]" );
1260 }
1261
1262 /* Parse line, execute command and return FALSE, if it was the quit command. */
1263 static Bool
1264 parseAndExecute( Str line )
1265 {
1266 unsigned int i, n;
1267 Cmd *c;
1268 Str s, arg;
1269 Bool ret;
1270
1271 if ( sscanf( line, "%s", s ) == 1 )
1272 {
1273 Utl_toLower( s );
1274 strcpy( arg, Utl_restOfLn( line, 1 ) );
1275 n = sizeof( commands ) / sizeof( commands[ 0 ] );
1276 for ( i = 0, c = commands; i < n; ++i, ++c )
1277 if ( strcmp( c->name, s ) == 0 )
1278 {
1279 ret = c->cmdProc( Utl_stripWhiteSpace( arg ), c );
1280 fflush( stdout );
1281 Log_dbg( "[S FLUSH]" );
1282 return ret;
1283 }
1284 }
1285 putStat( STAT_NO_SUCH_CMD, "Command not recognized" );
1286 fflush( stdout );
1287 Log_dbg( "[S FLUSH]" );
1288 return TRUE;
1289 }
1290
1291 static void
1292 putWelcome( void )
1293 {
1294 putStat( STAT_READY_POST_ALLOW, "NNTP server NOFFLE %s",
1295 Cfg_version() );
1296 fflush( stdout );
1297 Log_dbg( "[S FLUSH]" );
1298 }
1299
1300 static Bool
1301 initServ( void )
1302 {
1303 ASSERT( ! serv.running );
1304 if ( ! Lock_openDatabases() )
1305 return FALSE;
1306 serv.running = TRUE;
1307 return TRUE;
1308 }
1309
1310 static void
1311 closeServ( void )
1312 {
1313 ASSERT( serv.running );
1314 serv.running = FALSE;
1315 Lock_closeDatabases();
1316 }
1317
1318 void
1319 Serv_run( void )
1320 {
1321 Bool done;
1322 int r;
1323 Str line;
1324 struct timeval timeOut;
1325 fd_set readSet;
1326
1327 putWelcome();
1328 done = FALSE;
1329 while ( ! done )
1330 {
1331 FD_ZERO( &readSet );
1332 FD_SET( STDIN_FILENO, &readSet );
1333 /* Never hold lock more than 5 seconds (empirically good value,
1334 avoids to close/open databases, if clients sends several
1335 commands, but releases the lock often enough, for allowing
1336 multiple persons to read news at the same time) */
1337 timeOut.tv_sec = 5;
1338 timeOut.tv_usec = 0;
1339 r = select( STDIN_FILENO + 1, &readSet, NULL, NULL, &timeOut );
1340 if ( r < 0 )
1341 done = TRUE;
1342 else if ( r == 0 )
1343 {
1344 if ( serv.running )
1345 closeServ();
1346 }
1347 else /* ( r > 0 ) */
1348 {
1349 if ( ! serv.running )
1350 {
1351 if ( ! initServ() )
1352 {
1353 putFatal( "Cannot init server" );
1354 done = TRUE;
1355 }
1356 }
1357 if ( ! getLn( line ) )
1358 {
1359 Log_inf( "Client disconnected. Terminating." );
1360 done = TRUE;
1361 }
1362 else if ( ! parseAndExecute( line ) )
1363 done = TRUE;
1364 }
1365 }
1366 if ( serv.running )
1367 closeServ();
1368 }