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