comparison src/noffle.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 noffle.c
3
4 Main program. Implements specified actions, but running as server, which
5 is done by Serv_run(), declared in server.h.
6
7 Locking policy: lock access to databases while noffle is running, but
8 not as server. If noffle runs as server, locking is performed while
9 executing NNTP commands, but temporarily released if no new command is
10 received for some seconds (to allow multiple clients connect at the same
11 time).
12
13 $Id: noffle.c 49 2000-05-05 21:45:56Z uh1763 $
14 */
15
16 #include <ctype.h>
17 #include <errno.h>
18 #include <getopt.h>
19 #include <signal.h>
20 #include <sys/resource.h>
21 #include <syslog.h>
22 #include <unistd.h>
23 #include "client.h"
24 #include "common.h"
25 #include "content.h"
26 #include "control.h"
27 #include "configfile.h"
28 #include "database.h"
29 #include "fetch.h"
30 #include "fetchlist.h"
31 #include "group.h"
32 #include "itemlist.h"
33 #include "log.h"
34 #include "online.h"
35 #include "outgoing.h"
36 #include "over.h"
37 #include "pseudo.h"
38 #include "util.h"
39 #include "server.h"
40 #include "request.h"
41 #include "lock.h"
42
43 struct Noffle
44 {
45 Bool queryGrps;
46 Bool queryDsc;
47 Bool queryTimes;
48 Bool interactive;
49 } noffle = { FALSE, FALSE, FALSE, TRUE };
50
51 static void
52 doArt( const char *msgId )
53 {
54 const char *id;
55
56 if ( strcmp( msgId, "all" ) == 0 )
57 {
58 if ( ! Db_first( &id ) )
59 fprintf( stderr, "Database empty.\n" );
60 else
61 do
62 {
63 printf( "From %s %s\n"
64 "%s\n"
65 "%s\n",
66 Db_from( id ), Db_date( id ),
67 Db_header( id ),
68 Db_body( id ) );
69 }
70 while ( Db_next( &id ) );
71 }
72 else
73 {
74 if ( ! Db_contains( msgId ) )
75 fprintf( stderr, "Not in database.\n" );
76 else
77 printf( "%s\n%s", Db_header( msgId ), Db_body( msgId ) );
78 }
79 }
80
81 static void
82 doCancel( const char *msgId )
83 {
84 switch( Ctrl_cancel( msgId ) )
85 {
86 case CANCEL_NO_SUCH_MSG:
87 printf( "No such message '%s'.\n", msgId );
88 break;
89
90 case CANCEL_OK:
91 printf( "Message '%s' cancelled.\n", msgId );
92 break;
93
94 case CANCEL_NEEDS_MSG:
95 printf( "Message '%s' cancelled in local database only.\n", msgId );
96 break;
97 }
98 }
99
100 /* List articles requested from one particular server */
101 static void
102 listRequested1( const char* serv )
103 {
104 Str msgid;
105
106 if ( ! Req_first( serv, msgid ) )
107 return;
108 do
109 printf( "%s %s\n", msgid, serv );
110 while ( Req_next( msgid ) );
111 }
112
113 /* List requested articles. List for all servers if serv = "all" or serv =
114 NULL. */
115 void
116 doRequested( const char *arg )
117 {
118 Str serv;
119
120 if ( ! arg || ! strcmp( arg, "all" ) )
121 {
122 Cfg_beginServEnum();
123 while ( Cfg_nextServ( serv ) )
124 listRequested1( serv );
125 }
126 else
127 listRequested1( arg );
128 }
129
130
131 static void
132 doDb( void )
133 {
134 const char *msgId;
135
136 if ( ! Db_first( &msgId ) )
137 fprintf( stderr, "Database empty.\n" );
138 else
139 do
140 printf( "%s\n", msgId );
141 while ( Db_next( &msgId ) );
142 }
143
144 static void
145 doFetch( void )
146 {
147 Str serv;
148
149 Cfg_beginServEnum();
150 while ( Cfg_nextServ( serv ) )
151 if ( Fetch_init( serv ) )
152 {
153 Fetch_postArts();
154
155 Fetch_getNewGrps();
156
157 /* Get overviews of new articles and store IDs of new articles
158 that are to be fetched becase of FULL or THREAD mode in the
159 request database. */
160 Fetch_updateGrps();
161
162 /* get requested articles */
163 Fetch_getReq_();
164
165 Fetch_close();
166 }
167 }
168
169 static void
170 doQuery( void )
171 {
172 Str serv;
173
174 Cfg_beginServEnum();
175 while ( Cfg_nextServ( serv ) )
176 if ( Fetch_init( serv ) )
177 {
178 if ( noffle.queryGrps )
179 Client_getGrps();
180 if ( noffle.queryDsc )
181 Client_getDsc();
182 if ( noffle.queryTimes )
183 Client_getCreationTimes();
184 Fetch_close();
185 }
186 }
187
188 /* Expire all overviews not in database */
189 static void
190 expireContents( void )
191 {
192 const Over *ov;
193 int i;
194 int cntDel, cntLeft;
195 Str grp;
196 Bool autoUnsubscribe;
197 int autoUnsubscribeDays;
198 time_t now = time( NULL ), maxAge = 0;
199 const char *msgId;
200
201 autoUnsubscribe = Cfg_autoUnsubscribe();
202 autoUnsubscribeDays = Cfg_autoUnsubscribeDays();
203 maxAge = Cfg_autoUnsubscribeDays() * 24 * 3600;
204 if ( ! Cont_firstGrp( grp ) )
205 return;
206 Log_inf( "Expiring overviews not in database" );
207 do
208 {
209 if ( ! Grp_exists( grp ) )
210 Log_err( "Overview file for unknown group %s exists", grp );
211 else
212 {
213 cntDel = cntLeft = 0;
214 Cont_read( grp );
215 for ( i = Cont_first(); i <= Cont_last(); ++i )
216 if ( ( ov = Cont_get( i ) ) )
217 {
218 msgId = Ov_msgId( ov );
219 if ( ! Db_contains( msgId ) )
220 {
221 Cont_delete( i );
222 ++cntDel;
223 }
224 else
225 ++cntLeft;
226 }
227 if ( ! Grp_local( grp )
228 && autoUnsubscribe
229 && difftime( now, Grp_lastAccess( grp ) ) > maxAge )
230 {
231 Log_ntc( "Auto-unsubscribing from %s after %d "
232 "days without access",
233 grp, autoUnsubscribeDays );
234 Pseudo_autoUnsubscribed( grp, autoUnsubscribeDays );
235 Fetchlist_read();
236 Fetchlist_remove( grp );
237 Fetchlist_write();
238 }
239 Cont_write();
240 Grp_setFirstLast( grp, Cont_first(), Cont_last() );
241 Log_inf( "%ld overviews deleted from group %s, %ld left (%ld-%ld)",
242 cntDel, grp, cntLeft, Grp_first( grp ), Grp_last( grp ) );
243 }
244 }
245 while ( Cont_nextGrp( grp ) );
246 }
247
248 static void
249 doExpire( void )
250 {
251 Db_close();
252 Db_expire();
253 if ( ! Db_open() )
254 return;
255 expireContents();
256 }
257
258 static void
259 doCreateLocalGroup( const char * name )
260 {
261 Str grp;
262
263 Utl_cpyStr( grp, name );
264 Utl_toLower( grp );
265 name = Utl_stripWhiteSpace( grp );
266
267 if ( Grp_exists( name ) )
268 fprintf( stderr, "'%s' already exists.\n", name );
269 else
270 {
271 Log_inf( "Creating new local group '%s'", name );
272 Grp_create( name );
273 Grp_setLocal( name );
274 printf( "New local group '%s' created.\n", name );
275 }
276 }
277
278 static void
279 doDeleteLocalGroup( const char * name )
280 {
281 Str grp;
282
283 Utl_cpyStr( grp, name );
284 Utl_toLower( grp );
285 name = Utl_stripWhiteSpace( grp );
286
287 if ( ! Grp_exists( name ) )
288 fprintf( stderr, "'%s' does not exist.\n", name );
289 else
290 {
291 int i;
292
293 Log_inf( "Deleting group '%s'", name );
294
295 /*
296 Delete all articles that are only in the group. Check the
297 article Xref for more than one group.
298 */
299 Cont_read( name );
300 for ( i = Cont_first(); i <= Cont_last(); i++ )
301 {
302 const Over *over;
303 Bool toDelete;
304 Str msgId;
305
306 over = Cont_get( i );
307 toDelete = TRUE;
308 if ( over != NULL )
309 {
310 ItemList * xref;
311
312 Utl_cpyStr( msgId, Ov_msgId( over ) );
313 xref = new_Itl( Db_xref( msgId ), " " );
314 if ( Itl_count( xref ) > 1 )
315 toDelete = FALSE;
316 del_Itl( xref );
317 }
318 Cont_delete( i );
319 if ( toDelete )
320 Db_delete( msgId );
321 }
322 Cont_write();
323 Grp_delete( name );
324 printf( "Group '%s' deleted.\n", name );
325 }
326 }
327
328 static void
329 doList( void )
330 {
331 FetchMode mode;
332 int i, size;
333 const char *name, *modeStr = "";
334
335 Fetchlist_read();
336 size = Fetchlist_size();
337 if ( size == 0 )
338 fprintf( stderr, "Fetch list is empty.\n" );
339 else
340 for ( i = 0; i < size; ++i )
341 {
342 Fetchlist_element( &name, &mode, i );
343 switch ( mode )
344 {
345 case FULL:
346 modeStr = "full"; break;
347 case THREAD:
348 modeStr = "thread"; break;
349 case OVER:
350 modeStr = "over"; break;
351 }
352 printf( "%s %s %s\n", name, Grp_serv( name ), modeStr );
353 }
354 }
355
356 /* A modify command. argc/argv start AFTER '-m'. */
357 static Bool
358 doModify( const char *cmd, int argc, char **argv )
359 {
360 const char *grp;
361
362 if ( argc < 2 )
363 {
364 fprintf( stderr, "Insufficient arguments to -m\n" );
365 return FALSE;
366
367 }
368 else if ( strcmp( cmd, "desc" ) != 0
369 && strcmp( cmd, "post" ) != 0 )
370 {
371 fprintf( stderr, "Unknown argument -m %s\n", optarg );
372 return FALSE;
373 }
374
375 grp = argv[ 0 ];
376 argv++;
377 argc--;
378
379 if ( strcmp( cmd, "desc" ) == 0 )
380 {
381 Str desc;
382
383 Utl_cpyStr( desc, *( argv++ ) );
384 while ( --argc > 0 )
385 {
386 Utl_catStr( desc, " " );
387 Utl_catStr( desc, *( argv++ ) );
388 }
389
390 Grp_setDsc( grp, desc );
391 }
392 else
393 {
394 char c;
395
396 if ( ! Grp_local( grp ) )
397 {
398 fprintf( stderr, "%s is not a local group\n", grp );
399 return FALSE;
400 }
401
402 c = **argv;
403 if ( c == 'y' || c == 'm' || c == 'n' )
404 Grp_setPostAllow( grp, c );
405 else
406 {
407 fprintf( stderr, "Access must be 'y', 'n' or 'm'" );
408 return FALSE;
409 }
410 }
411
412 return TRUE;
413 }
414
415 static void
416 doGrps( void )
417 {
418 const char *g;
419 Str dateLastAccess, dateCreated;
420 time_t lastAccess, created;
421
422 if ( Grp_firstGrp( &g ) )
423 do
424 {
425 lastAccess = Grp_lastAccess( g );
426 created = Grp_created( g );
427 ASSERT( lastAccess >= 0 );
428 ASSERT( created >= 0 );
429 strftime( dateLastAccess, MAXCHAR, "%Y-%m-%d %H:%M:%S",
430 localtime( &lastAccess ) );
431 strftime( dateCreated, MAXCHAR, "%Y-%m-%d %H:%M:%S",
432 localtime( &created ) );
433 printf( "%s\t%s\t%i\t%i\t%i\t%c\t%s\t%s\t%s\n",
434 g, Grp_serv( g ), Grp_first( g ), Grp_last( g ),
435 Grp_rmtNext( g ), Grp_postAllow( g ), dateCreated,
436 dateLastAccess, Grp_dsc( g ) );
437 }
438 while ( Grp_nextGrp( &g ) );
439 }
440
441 static Bool
442 doSubscribe( const char *name, FetchMode mode )
443 {
444 if ( ! Grp_exists( name ) )
445 {
446 fprintf( stderr, "%s is not available at remote servers.\n", name );
447 return FALSE;
448 }
449 Fetchlist_read();
450 if ( Fetchlist_add( name, mode ) )
451 printf( "Adding %s to fetch list in %s mode.\n",
452 name, mode == FULL ? "full" : mode == THREAD ?
453 "thread" : "overview" );
454 else
455 printf( "%s is already in fetch list. Mode is now: %s.\n",
456 name, mode == FULL ? "full" : mode == THREAD ?
457 "thread" : "overview" );
458 if ( ! Fetchlist_write() )
459 fprintf( stderr, "Could not save fetchlist.\n" );
460 return TRUE;
461 }
462
463 static void
464 doUnsubscribe( const char *name )
465 {
466 Fetchlist_read();
467 if ( ! Fetchlist_remove( name ) )
468 printf( "%s is not in fetch list.\n", name );
469 else
470 printf( "%s removed from fetch list.\n", name );
471 if ( ! Fetchlist_write() )
472 fprintf( stderr, "Could not save fetchlist.\n" );
473 }
474
475 static void
476 printUsage( void )
477 {
478 static const char *msg =
479 "Usage: noffle <option>\n"
480 "Option is one of the following:\n"
481 " -a | --article <msg id>|all Show article(s) in database\n"
482 " -c | --cancel <msg id> Remove article from database\n"
483 " -C | --create <grp> Create a local group\n"
484 " -d | --database Show content of article database\n"
485 " -D | --delete <grp> Delete a group\n"
486 " -e | --expire Expire articles\n"
487 " -f | --fetch Get newsfeed from server/post articles\n"
488 " -g | --groups Show all groups available at server\n"
489 " -h | --help Show this text\n"
490 " -l | --list List groups on fetch list\n"
491 " -m | --modify desc <grp> <desc> Modify a group description\n"
492 " -m | --modify post <grp> (y|n) Modify posting status of a local group\n"
493 " -n | --online Switch to online mode\n"
494 " -o | --offline Switch to offline mode\n"
495 " -q | --query groups Get group list from server\n"
496 " -q | --query desc Get group descriptions from server\n"
497 " -q | --query times Get group creation times from server\n"
498 " -r | --server Run as server on stdin/stdout\n"
499 " -R | --requested List articles marked for download\n"
500 " -s | --subscribe-over <grp> Add group to fetch list (overview)\n"
501 " -S | --subscribe-full <grp> Add group to fetch list (full)\n"
502 " -t | --subscribe-thread <grp> Add group to fetch list (thread)\n"
503 " -u | --unsubscribe <grp> Remove group from fetch list\n"
504 " -v | --version Print version\n";
505 fprintf( stderr, "%s", msg );
506 }
507
508 /*
509 Allow core files: Change core limit and change working directory
510 to spool directory, where news has write permissions.
511 */
512 static void
513 enableCorefiles()
514 {
515 struct rlimit lim;
516
517 if ( getrlimit( RLIMIT_CORE, &lim ) != 0 )
518 {
519 Log_err( "Cannot get system core limit: %s", strerror( errno ) );
520 return;
521 }
522 lim.rlim_cur = lim.rlim_max;
523 if ( setrlimit( RLIMIT_CORE, &lim ) != 0 )
524 {
525 Log_err( "Cannot set system core limit: %s", strerror( errno ) );
526 return;
527 }
528 Log_dbg( "Core limit set to %i", lim.rlim_max );
529 if ( chdir( Cfg_spoolDir() ) != 0 )
530 {
531 Log_err( "Cannot change to directory '%s'", Cfg_spoolDir() );
532 return;
533 }
534 Log_dbg( "Changed to directory '%s'", Cfg_spoolDir() );
535 }
536
537 static Bool
538 initNoffle( Bool interactive )
539 {
540 Log_init( "noffle", interactive, LOG_NEWS );
541 Cfg_read();
542 Log_dbg( "NOFFLE version %s", Cfg_version() );
543 noffle.interactive = interactive;
544 if ( interactive )
545 if ( ! Lock_openDatabases() )
546 return FALSE;
547 if ( ! interactive )
548 enableCorefiles();
549 return TRUE;
550 }
551
552 static void
553 closeNoffle( void )
554 {
555 if ( noffle.interactive )
556 Lock_closeDatabases();
557 }
558
559 static void
560 bugReport( int sig )
561 {
562 Log_err( "Received SIGSEGV. Please submit a bug report" );
563 signal( SIGSEGV, SIG_DFL );
564 raise( sig );
565 }
566
567 static void
568 logSignal( int sig )
569 {
570 const char *name;
571 Bool err = TRUE;
572
573 switch ( sig )
574 {
575 case SIGABRT:
576 name = "SIGABRT"; break;
577 case SIGFPE:
578 name = "SIGFPE"; break;
579 case SIGILL:
580 name = "SIGILL"; break;
581 case SIGINT:
582 name = "SIGINT"; break;
583 case SIGTERM:
584 name = "SIGTERM"; break;
585 case SIGPIPE:
586 name = "SIGPIPE"; err = FALSE; break;
587 default:
588 name = "?"; break;
589 }
590 if ( err )
591 Log_err( "Received signal %i (%s). Aborting.", sig, name );
592 else
593 Log_inf( "Received signal %i (%s). Aborting.", sig, name );
594 signal( sig, SIG_DFL );
595 raise( sig );
596 }
597
598 int main ( int argc, char **argv )
599 {
600 int c, result;
601 struct option longOptions[] =
602 {
603 { "article", required_argument, NULL, 'a' },
604 { "cancel", required_argument, NULL, 'c' },
605 { "create", required_argument, NULL, 'C' },
606 { "database", no_argument, NULL, 'd' },
607 { "delete", required_argument, NULL, 'D' },
608 { "expire", no_argument, NULL, 'e' },
609 { "fetch", no_argument, NULL, 'f' },
610 { "groups", no_argument, NULL, 'g' },
611 { "help", no_argument, NULL, 'h' },
612 { "list", no_argument, NULL, 'l' },
613 { "modify", required_argument, NULL, 'm' },
614 { "offline", no_argument, NULL, 'o' },
615 { "online", no_argument, NULL, 'n' },
616 { "query", required_argument, NULL, 'q' },
617 { "server", no_argument, NULL, 'r' },
618 { "requested", no_argument, NULL, 'R' },
619 { "subscribe-over", required_argument, NULL, 's' },
620 { "subscribe-full", required_argument, NULL, 'S' },
621 { "subscribe-thread", required_argument, NULL, 't' },
622 { "unsubscribe", required_argument, NULL, 'u' },
623 { "version", no_argument, NULL, 'v' },
624 { NULL, 0, NULL, 0 }
625 };
626
627 signal( SIGSEGV, bugReport );
628 signal( SIGABRT, logSignal );
629 signal( SIGFPE, logSignal );
630 signal( SIGILL, logSignal );
631 signal( SIGINT, logSignal );
632 signal( SIGTERM, logSignal );
633 signal( SIGPIPE, logSignal );
634 c = getopt_long( argc, argv, "a:c:C:dD:efghlm:onq:rRs:S:t:u:v",
635 longOptions, NULL );
636 if ( ! initNoffle( c != 'r' ) )
637 return EXIT_FAILURE;
638 result = EXIT_SUCCESS;
639 switch ( c )
640 {
641 case 0:
642 /* Options that set a flag. */
643 break;
644 case 'a':
645 if ( ! optarg )
646 {
647 fprintf( stderr, "Option -a needs argument.\n" );
648 result = EXIT_FAILURE;
649 }
650 else
651 doArt( optarg );
652 break;
653 case 'c':
654 if ( ! optarg )
655 {
656 fprintf( stderr, "Option -c needs argument.\n" );
657 result = EXIT_FAILURE;
658 }
659 else
660 doCancel( optarg );
661 break;
662 case 'C':
663 if ( ! optarg )
664 {
665 fprintf( stderr, "Option -C needs argument.\n" );
666 result = EXIT_FAILURE;
667 }
668 else
669 doCreateLocalGroup( optarg );
670 break;
671 case 'd':
672 doDb();
673 break;
674 case 'D':
675 if ( ! optarg )
676 {
677 fprintf( stderr, "Option -D needs argument.\n" );
678 result = EXIT_FAILURE;
679 }
680 else
681 doDeleteLocalGroup( optarg );
682 break;
683 case 'e':
684 doExpire();
685 break;
686 case 'f':
687 doFetch();
688 break;
689 case 'g':
690 doGrps();
691 break;
692 case -1:
693 case 'h':
694 printUsage();
695 break;
696 case 'l':
697 doList();
698 break;
699 case 'm':
700 if ( ! optarg )
701 {
702 fprintf( stderr, "Option -m needs argument.\n" );
703 result = EXIT_FAILURE;
704 }
705 else
706 if ( ! doModify( optarg, argc - optind, &argv[ optind ] ) )
707 result = EXIT_FAILURE;
708 break;
709 case 'n':
710 if ( Online_true() )
711 fprintf( stderr, "NOFFLE is already online\n" );
712 else
713 Online_set( TRUE );
714 break;
715 case 'o':
716 if ( ! Online_true() )
717 fprintf( stderr, "NOFFLE is already offline\n" );
718 else
719 Online_set( FALSE );
720 break;
721 case 'q':
722 if ( ! optarg )
723 {
724 fprintf( stderr, "Option -q needs argument.\n" );
725 result = EXIT_FAILURE;
726 }
727 else
728 {
729 if ( strcmp( optarg, "groups" ) == 0 )
730 noffle.queryGrps = TRUE;
731 else if ( strcmp( optarg, "desc" ) == 0 )
732 noffle.queryDsc = TRUE;
733 else if ( strcmp( optarg, "times" ) == 0 )
734 noffle.queryTimes = TRUE;
735 else
736 {
737 fprintf( stderr, "Unknown argument -q %s\n", optarg );
738 result = EXIT_FAILURE;
739 }
740 doQuery();
741 }
742 break;
743 case 'r':
744 Log_inf( "Starting as server" );
745 Serv_run();
746 break;
747 case 'R':
748 doRequested( optarg );
749 break;
750 case 's':
751 if ( ! optarg )
752 {
753 fprintf( stderr, "Option -s needs argument.\n" );
754 result = EXIT_FAILURE;
755 }
756 else
757 result = doSubscribe( optarg, OVER );
758 break;
759 case 'S':
760 if ( ! optarg )
761 {
762 fprintf( stderr, "Option -S needs argument.\n" );
763 result = EXIT_FAILURE;
764 }
765 else
766 doSubscribe( optarg, FULL );
767 break;
768 case 't':
769 if ( ! optarg )
770 {
771 fprintf( stderr, "Option -t needs argument.\n" );
772 result = EXIT_FAILURE;
773 }
774 else
775 result = doSubscribe( optarg, THREAD );
776 break;
777 case 'u':
778 if ( ! optarg )
779 {
780 fprintf( stderr, "Option -u needs argument.\n" );
781 result = EXIT_FAILURE;
782 }
783 else
784 doUnsubscribe( optarg );
785 break;
786 case '?':
787 /* Error message already printed by getopt_long */
788 result = EXIT_FAILURE;
789 break;
790 case 'v':
791 printf( "NNTP server NOFFLE, version %s.\n", Cfg_version() );
792 break;
793 default:
794 abort(); /* Never reached */
795 }
796 closeNoffle();
797 return result;
798 }