0
|
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)", ¬Implemented },
|
|
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 }
|