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