1 /*
2  *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
3  *      Copyright (c) 1996-2005 Michael T Pins.  All rights reserved.
4  *
5  *	nn database daemon (nnmaster)
6  *
7  *	maintains the article header database.
8  */
9 
10 #include <sys/types.h>
11 #include <unistd.h>
12 #include <stdlib.h>
13 #include <signal.h>
14 #include <errno.h>
15 #include <string.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #include "config.h"
19 #include "global.h"
20 #include "master.h"
21 #include "active.h"
22 #include "collect.h"
23 #include "db.h"
24 #include "digest.h"
25 #include "execute.h"
26 #include "expire.h"
27 #include "nntp.h"
28 #include "options.h"
29 #include "proto.h"
30 
31 /* master.c */
32 
33 static void     clean_group_internal(register group_header * gh);
34 static void     group_restriction(register group_header * gh);
35 static void     set_group_restrictions(char **restr, int n);
36 static void     visit_active_file(void);
37 static void     build_master(void);
38 static void     set_lock_message(void);
39 static void     do_reread_groups(void);
40 static int      receive_admin(void);
41 
42 extern char    *bin_directory;
43 extern char     proto_host[];
44 
45 /*
46  * nnmaster options:
47  *
48  *	-e [N]	expire a group if more than N articles are gone
49  *	-r N	repeat every N minutes
50  *	-h N	don't update if active file is less than N *seconds* old
51  *
52  *	-f	foreground execution (use with -r)
53  *	-y N	retry N times on error
54  *
55  *	-E [N]	expire mode (see expire.c) [N==1 if omitted]
56  *	-F	Run ONLY expire ONCE and exit.
57  *	-R N	auto-recollect mode (see expire.c)
58  *
59  *	-C	check consistency of database on start-up
60  *	-b	include 'bad' articles (disables -B)
61  *	-B	remove 'bad' articles (just unlink the files)
62  *	-O N	Consider articles older than N days as bad articles.
63  *
64  *	-I [N]	initialize [ limit to N articles in each group ]
65  *	-G	reread groups file.
66  *	-X	clean ignored groups.
67  *
68  *	-l"MSG"	lock database with message MSG
69  *	-l	unlock database
70  *	-i	ignore lock (run collection on locked database)
71  *	-k	kill the running master and take over.
72  *
73  *	-Q	quiet: don't write fatal errors to /dev/console (if no syslog).
74  *	-t	trace collection of each group
75  *	-v	print version and exit
76  *	-u	update even if active is not modified
77  *	-w	send wakeup to real master
78  *	-Ltypes	exclude 'types' entries from the log
79  *	-D [N]	debug, N = +(1 => verbose, 2 => nntp trace)
80  *
81  *	[master group]...	Collect these groups only.
82  */
83 
84 
85 extern int      dont_write_console, expire_method, expire_level,
86                 mail_errors_mode, recollect_method, reread_groups_file,
87                 ignore_bad_articles, remove_bad_articles, retry_on_error;
88 
89 #ifdef NNTP
90 extern int      nntp_local_server, nntp_debug;
91 #endif
92 
93 extern          time_t max_article_age;
94 
95 extern char    *log_entry_filter;
96 
97 int             trace = 0, debug_mode = 0, Debug = 0, no_update = 0;
98 
99 #ifdef NNTP
100 int             silent = 1;
101 #endif
102 
103 int             Name_Length = 16;
104 
105 static int      check_on_startup = 0, clean_ignored = 0, expire_once = 0,
106                 foreground = 0, ignore_lock = 0, initialize = -1,
107                 kill_running = 0, max_age_days = 0, prt_vers = 0,
108                 unconditional = 0, Unconditional = 0, wakeup_master = 0;
109 
110 static unsigned hold_updates = 0, repeat_delay = 0;
111 
112 
113 static char    *lock_message = NULL;
114 
115 
116 static
Option_Description(master_options)117 Option_Description(master_options)
118 {
119 
120     'b', Bool_Option(ignore_bad_articles),
121 	'B', Bool_Option(remove_bad_articles),
122 	'C', Bool_Option(check_on_startup),
123 	'D', Int_Option_Optional(debug_mode, 1),
124 	'e', Int_Option_Optional(expire_level, 1),
125 	'E', Int_Option_Optional(expire_method, 1),
126 	'f', Bool_Option(foreground),
127 	'F', Bool_Option(expire_once),
128 	'G', Bool_Option(reread_groups_file),
129 	'h', Int_Option_Optional(hold_updates, 60),
130 
131 #ifdef NNTP
132 	'H', Bool_Option(nntp_local_server),
133 #endif
134 
135 	'i', Bool_Option(ignore_lock),
136 	'I', Int_Option_Optional(initialize, 0),
137 	'k', Bool_Option(kill_running),
138 	'l', String_Option_Optional(lock_message, ""),
139 	'L', String_Option(log_entry_filter),
140 	'M', Int_Option(mail_errors_mode),
141 	'O', Int_Option(max_age_days),
142 	'Q', Bool_Option(dont_write_console),
143 	'r', Int_Option_Optional(repeat_delay, 10),
144 	'R', Int_Option(recollect_method),
145 	't', Bool_Option(trace),
146 	'u', Bool_Option(unconditional),
147 	'U', Bool_Option(Unconditional),
148 	'v', Bool_Option(prt_vers),
149 	'w', Bool_Option(wakeup_master),
150 	'X', Bool_Option(clean_ignored),
151 	'y', Int_Option(retry_on_error),
152 	'\0',
153 };
154 
155 extern char    *master_directory, *db_directory, *news_active;
156 
157 static int      unlock_on_exit = 0;
158 
159 /*
160  * nn_exit() --- called whenever a program exits.
161  */
162 
163 void
nn_exit(int n)164 nn_exit(int n)
165 {
166 
167 #ifdef NNTP
168     if (use_nntp)
169 	nntp_cleanup();
170 #endif				/* NNTP */
171 
172     close_master();
173 
174     if (unlock_on_exit)
175 	proto_lock(I_AM_MASTER, PL_CLEAR);
176 
177     if (n)
178 	log_entry('E', "Abnormal termination, exit=%d", n);
179     else if (unlock_on_exit)
180 	log_entry('M', "Master terminated%s", s_hangup ? " (hangup)" : "");
181 
182     exit(n);
183 }
184 
185 
186 static void
clean_group_internal(register group_header * gh)187 clean_group_internal(register group_header * gh)
188 {				/* no write */
189     gh->first_db_article = 0;
190     gh->last_db_article = 0;
191 
192     gh->data_write_offset = 0;
193     gh->index_write_offset = 0;
194 
195     if (init_group(gh)) {
196 	(void) open_data_file(gh, 'd', -1);
197 	(void) open_data_file(gh, 'x', -1);
198     }
199     gh->master_flag &= ~(M_EXPIRE | M_BLOCKED);
200     if ((gh->master_flag & M_IGNORE_GROUP) == 0)
201 	gh->master_flag |= M_BLOCKED;
202 
203 }
204 
205 void
clean_group(group_header * gh)206 clean_group(group_header * gh)
207 {				/* does write */
208     if (trace)
209 	log_entry('T', "CLEAN %s", gh->group_name);
210     if (debug_mode)
211 	printf("CLEAN %s\n", gh->group_name);
212 
213     clean_group_internal(gh);
214 
215     db_write_group(gh);
216 }
217 
218 static char   **restrictions = NULL;
219 static int     *restr_len, *restr_excl;
220 
221 static void
group_restriction(register group_header * gh)222 group_restriction(register group_header * gh)
223 {
224     register char **rp;
225     register int   *lp, *xp;
226 
227     if (restrictions == NULL)
228 	return;
229 
230     for (rp = restrictions, lp = restr_len, xp = restr_excl; *lp > 0; rp++, lp++, xp++)
231 	if (strncmp(gh->group_name, *rp, *lp) == 0) {
232 	    if (*xp)
233 		break;
234 	    return;
235 	}
236     if (*lp == 0)
237 	return;
238 
239     gh->master_flag |= M_IGNORE_G;
240 }
241 
242 static void
set_group_restrictions(char ** restr,int n)243 set_group_restrictions(char **restr, int n)
244 {
245     register group_header *gh;
246     register int    i;
247 
248     restrictions = restr;
249     restr_len = newobj(int, n + 1);
250     restr_excl = newobj(int, n);
251 
252     for (i = 0; i < n; i++) {
253 	if (restrictions[i][0] == '!') {
254 	    restr_excl[i] = 1;
255 	    restrictions[i]++;
256 	} else
257 	    restr_excl[i] = 0;
258 	restr_len[i] = strlen(restrictions[i]);
259     }
260 
261     restr_len[n] = -1;
262 
263     Loop_Groups_Header(gh) {
264 	if (gh->master_flag & M_IGNORE_GROUP)
265 	    continue;
266 	group_restriction(gh);
267 	if (clean_ignored && (gh->master_flag & M_IGNORE_G)) {
268 	    log_entry('X', "Group %s ignored", gh->group_name);
269 	    clean_group(gh);
270 	}
271     }
272 }
273 
274 /*
275  * add new group to master file
276  */
277 
278 group_header   *
add_new_group(char * name)279 add_new_group(char *name)
280 {
281     register group_header *gh;
282 
283     if (master.free_groups <= 0)
284 	db_expand_master();
285 
286     master.free_groups--;
287 
288 #ifdef MALLOC_64K_LIMITATION
289     gh = newobj(group_header, 1);
290     active_groups[master.number_of_groups] = gh;
291 #else
292     gh = &active_groups[master.number_of_groups];
293 #endif
294 
295     gh->group_name_length = strlen(name);
296     gh->group_name = copy_str(name);
297 
298     gh->group_num = master.number_of_groups++;
299     gh->creation_time = cur_time();
300 
301     db_rewrite_groups(1, (group_header *) NULL);
302 
303     group_restriction(gh);	/* done after append to avoid setting ! */
304     clean_group(gh);
305 
306     db_write_master();
307 
308     sort_groups();
309 
310     log_entry('C', "new group: %s (%d)", gh->group_name, gh->group_num);
311 
312     return gh;
313 }
314 
315 
316 static void
visit_active_file(void)317 visit_active_file(void)
318 {
319     FILE           *act;
320     FILE           *nntp_act = NULL;
321 
322 #ifdef NNTP
323     if (!use_nntp)		/* copy 'active' to DB/ACTIVE */
324 	nntp_act = open_file(relative(db_directory, "ACTIVE"), OPEN_CREATE | MUST_EXIST);
325 #endif
326 
327     act = open_file(news_active, OPEN_READ | MUST_EXIST);
328 
329     read_active_file(act, nntp_act);
330 
331     master.last_size = ftell(act);
332 
333     fclose(act);
334 
335 #ifdef NNTP
336     if (nntp_act != NULL)
337 	fclose(nntp_act);
338 #endif
339 }
340 
341 
342 /*
343  *	Build initial master file.
344  */
345 
346 static void
build_master(void)347 build_master(void)
348 {
349     char            command[512];
350     char            groupname[512];
351     group_header   *groups, *next_g, *gh;
352     group_header   *dupg;
353     FILE           *src;
354     int             lcount, use_group_file, found_nn_group = 0;
355 
356     printf("Confirm initialization by typing 'OK': ");
357     fl;
358     fgets(command, sizeof(command), stdin);
359     if (strcmp(command, "OK")) {
360 	printf("No initialization\n");
361 	nn_exit(0);
362     }
363     if (chdir(master_directory) < 0)	/* so we can use open_file (?) */
364 	sys_error("master");
365 
366 #ifdef NNTP
367     if (use_nntp && nntp_get_active() < 0)
368 	sys_error("Can't get active file");
369 #endif
370 
371     /* check active file for duplicates */
372 
373     sprintf(command, "awk 'NF>0{print $1}' %s | sort | uniq -d", news_active);
374 
375     src = popen(command, "r");
376     if (src == NULL)
377 	sys_error("popen(%s) failed", command);
378 
379     for (lcount = 0; fgets(groupname, 512, src); lcount++) {
380 	if (lcount == 0)
381 	    printf("\n%s contains duplicate entries for the following groups:",
382 		   news_active);
383 
384 	fputs(groupname, stdout);
385     }
386 
387     pclose(src);
388 
389     if (lcount > 0) {
390 	printf("Do you want to repair this file before continuing ? (y)");
391 	fgets(command, sizeof(command), stdin);
392 	if (s_hangup ||
393 	    command[0] == NUL || command[0] == 'y' || command[0] == 'Y') {
394 	    printf("Initialization stopped.\n");
395 	    printf("Redo ./inst INIT after repairing active file\n");
396 	    nn_exit(0);
397 	}
398     }
399     /* if a "GROUPS" file exist offer to use that, else */
400     /* read group names from active file */
401 
402     use_group_file = 0;
403 
404     if (open_groups(OPEN_READ)) {
405 	printf("\nA GROUPS file already exist -- reuse it? (y)");
406 	fl;
407 	fgets(command, sizeof(command), stdin);
408 	if (command[0] == NUL || command[0] == 'y' || command[0] == 'Y') {
409 	    use_group_file = 1;
410 	} else
411 	    close_groups();
412 	if (s_hangup)
413 	    return;
414     }
415     printf("\nBuilding %s/MASTER file\n", db_directory);
416     fl;
417 
418     if (!use_group_file) {
419 	sprintf(command, "awk 'NF>0{print $1}' %s | sort -u", news_active);
420 
421 	src = popen(command, "r");
422 	if (src == NULL)
423 	    sys_error("popen(%s) failed", command);
424     }
425     open_master(OPEN_CREATE);
426 
427     master.db_magic = NNDB_MAGIC;
428     master.last_scan = 0;
429     master.number_of_groups = 0;
430     strcpy(master.db_lock, "Initializing database");
431 
432     db_write_master();
433 
434     groups = next_g = newobj(group_header, 1);
435     next_g->next_group = NULL;
436 
437     for (;;) {
438 	if (s_hangup)
439 	    goto intr;
440 	gh = newobj(group_header, 1);
441 
442 	gh->master_flag = 0;
443 
444 	if (use_group_file) {
445 	    gh->group_name_length = 0;
446 	    if (db_parse_group(gh, 0) <= 0)
447 		break;
448 	} else {
449 	    if (fgets(groupname, 512, src) == NULL)
450 		break;
451 
452 	    gh->group_name_length = strlen(groupname) - 1;	/* strip NL */
453 	    groupname[gh->group_name_length] = NUL;
454 	    gh->creation_time = 0;
455 	    gh->group_name = copy_str(groupname);
456 	    gh->archive_file = NULL;
457 	}
458 
459 	for (dupg = groups->next_group; dupg != NULL; dupg = dupg->next_group)
460 	    if (strcmp(dupg->group_name, gh->group_name) == 0)
461 		break;
462 	if (dupg != NULL) {
463 	    printf("Ignored duplicate of group %s\n", dupg->group_name);
464 	    continue;
465 	}
466 	gh->group_num = master.number_of_groups++;
467 
468 	if (trace || debug_mode)
469 	    printf("%4ld '%s' (%d)\n", gh->group_num, gh->group_name,
470 		   gh->group_name_length);
471 
472 	next_g->next_group = gh;
473 	next_g = gh;
474 	gh->next_group = NULL;
475 
476 	/* moderation flag will be set by first visit_active_file call */
477 
478 	/* might have the INN control.newgrp, control.cancel, etc */
479 	if (strncmp(gh->group_name, "control", 7) == 0)
480 	    gh->master_flag |= M_CONTROL;
481 
482 	if (strcmp(gh->group_name, "news.software.nn") == 0)
483 	    found_nn_group++;
484 
485 	gh->master_flag &= ~M_MUST_CLEAN;
486 	clean_group_internal(gh);
487 	gh->master_flag |= M_VALID;	/* better than the reverse */
488 	db_write_group(gh);
489     }
490 
491     if (use_group_file)
492 	close_groups();
493     else
494 	pclose(src);
495 
496     printf("%s %s/GROUPS file\n",
497 	   use_group_file ? "Updating" : "Building", db_directory);
498 
499     db_rewrite_groups(use_group_file, groups);
500 
501     if (initialize > 0) {
502 	printf("Setting articles per group limit to %d...\n", initialize);
503 	db_write_master();
504 	open_master(OPEN_READ);
505 	open_master(OPEN_UPDATE);
506 	visit_active_file();
507 	Loop_Groups_Header(gh) {
508 	    gh->first_db_article = gh->last_a_article - initialize + 1;
509 	    if (gh->first_db_article <= gh->first_a_article)
510 		continue;
511 	    gh->last_db_article = gh->first_db_article - 1;
512 	    if (gh->last_db_article < 0)
513 		gh->last_db_article = 0;
514 	    db_write_group(gh);
515 	}
516     }
517     master.db_lock[0] = NUL;
518     master.db_created = cur_time();
519 
520     db_write_master();
521 
522     close_master();
523 
524     printf("Done\n");
525     fl;
526 
527     log_entry('M', "Master data base initialized");
528 
529     if (!found_nn_group)
530 	printf("\nNotice: nn's own news group `news.software.nn' was not found\n");
531 
532     return;
533 
534 intr:
535     printf("\nINTERRUPT\n\nDatabase NOT completed\n");
536     log_entry('M', "Master data base initialization not completed (INTERRUPTED)");
537 }
538 
539 static void
set_lock_message(void)540 set_lock_message(void)
541 {
542     if (lock_message[0] || master.db_lock[0]) {
543 	open_master(OPEN_UPDATE);
544 	strncpy(master.db_lock, lock_message, DB_LOCK_MESSAGE);
545 	master.db_lock[DB_LOCK_MESSAGE - 1] = NUL;
546 	db_write_master();
547 	printf("DATABASE %sLOCKED\n", lock_message[0] ? "" : "UN");
548     }
549 }
550 
551 static void
do_reread_groups(void)552 do_reread_groups(void)
553 {
554     register group_header *gh;
555 
556     open_master(OPEN_UPDATE);
557     Loop_Groups_Header(gh)
558 	if (gh->master_flag & M_MUST_CLEAN) {
559 	gh->master_flag &= ~M_MUST_CLEAN;
560 	clean_group(gh);
561     } else
562 	db_write_group(gh);
563     master.last_scan--;		/* force update */
564     db_write_master();
565     close_master();
566     log_entry('M', "Reread GROUPS file");
567 }
568 
569 #ifdef HAVE_HARD_SLEEP
570 /*
571  * Hard sleeper...  The normal sleep is not terminated by SIGALRM or
572  * other signals which nnadmin may send to wake it up.
573  */
574 
575 static int      got_alarm;
576 
577 static          sig_type
catch_alarm(void)578 catch_alarm(void)
579 {
580     signal(SIGALRM, SIG_IGN);
581     got_alarm = 1;
582 }
583 
584 static int
take_a_nap(int t)585 take_a_nap(int t)
586 {
587     got_alarm = 0;
588     signal(SIGALRM, catch_alarm);
589     alarm(repeat_delay);
590     /* the timeout is at least 1 minute, so we ignore racing condition here */
591     pause();
592     if (!got_alarm) {
593 	signal(SIGALRM, SIG_IGN);
594 	alarm(0);
595     }
596 }
597 
598 #else
599 #define take_a_nap(t) sleep(t)
600 #endif
601 
602 int
main(int argc,char ** argv)603 main(int argc, char **argv)
604 {
605     register group_header *gh;
606     time_t          age_active;
607     int             group_selection;
608     int             temp;
609     int             skip_pass;
610     long            pass_no;
611 
612     umask(002);			/* avoid paranoia */
613 
614     who_am_i = I_AM_MASTER;
615 
616     init_global();
617 
618     group_selection =
619 	parse_options(argc, argv, (char *) NULL, master_options, (char *) NULL);
620 
621     if (debug_mode) {
622 
623 #ifdef NNTP
624 	nntp_debug = debug_mode & 2;
625 #endif
626 
627 	debug_mode = debug_mode & 1;
628     }
629     if (debug_mode) {
630 	signal(SIGINT, catch_hangup);
631     }
632     if (wakeup_master) {
633 	if (proto_lock(I_AM_MASTER, PL_WAKEUP) < 0)
634 	    printf("master is not running\n");
635 	exit(0);
636     }
637     if (prt_vers) {
638 	printf("nnmaster release %s\n", version_id);
639 	exit(0);
640     }
641     if (kill_running) {
642 	if (proto_lock(I_AM_MASTER, PL_TERMINATE) < 0)
643 	    temp = 0;
644 	else {
645 	    int             mpid;
646 
647 	    if (proto_host[0]) {
648 		printf("Can't kill master on another host (%s)\n", proto_host);
649 		log_entry('R', "Attempt to kill master on host %s", proto_host);
650 		exit(1);
651 	    }
652 	    for (temp = 10; --temp >= 0; sleep(3)) {
653 		sleep(3);
654 		mpid = proto_lock(I_AM_MASTER, PL_CHECK);
655 		if (mpid < 0)
656 		    break;
657 		sleep(1);
658 		kill(mpid, SIGTERM);	/* SIGHUP lost??? */
659 	    }
660 	}
661 
662 	if (temp < 0) {
663 	    printf("The running master will not die....!\n");
664 	    log_entry('E', "Could not kill running master");
665 	    exit(1);
666 	}
667 	if (repeat_delay == 0 && !foreground &&
668 	    !reread_groups_file && lock_message == NULL &&
669 	    group_selection == 0 && initialize < 0)
670 	    exit(0);
671     }
672     if (!file_exist(db_directory, "drwx")) {
673 	fprintf(stderr, "%s invoked with wrong user privileges\n", argv[0]);
674 	exit(9);
675     }
676     if (proto_lock(I_AM_MASTER, PL_SET) != 0) {
677 	if (proto_host[0])
678 	    printf("The master is running on another host (%s)\n", proto_host);
679 	else
680 	    printf("The master is already running\n");
681 	exit(0);
682     }
683     unlock_on_exit = 1;
684 
685 #ifdef NNTP
686     nntp_check();
687 #endif
688 
689     if (initialize >= 0) {
690 	build_master();
691 	nn_exit(0);
692     }
693     open_master(OPEN_READ);
694 
695     if (lock_message != NULL) {
696 	set_lock_message();
697 	if (repeat_delay == 0 && !foreground &&
698 	    !reread_groups_file && group_selection == 0)
699 	    nn_exit(0);
700     }
701     if (!ignore_lock && master.db_lock[0]) {
702 	printf("Database locked (unlock with -l or ignore with -i)\n");
703 	nn_exit(88);
704     }
705     if (reread_groups_file) {
706 	do_reread_groups();
707 	nn_exit(0);
708     }
709     if (!debug_mode) {
710 	close(0);
711 	close(1);
712 	close(2);
713 	if (open("/dev/null", 2) == 0)
714 	    dup(0), dup(0);
715     }
716     if (repeat_delay && !debug_mode && !foreground) {
717 	while ((temp = fork()) < 0)
718 	    sleep(1);
719 	if (temp)
720 	    exit(0);		/* not nn_exit() !!! */
721 
722 	process_id = getpid();	/* init_global saved parent's pid */
723 
724 	proto_lock(I_AM_MASTER, PL_TRANSFER);
725 
726 #ifdef DETACH_TERMINAL
727 	DETACH_TERMINAL
728 #endif
729     }
730     log_entry('M', "Master started -r%d -e%d %s-E%d",
731 	      repeat_delay, expire_level,
732 	      expire_once ? "-F " : "", expire_method);
733 
734     if (check_on_startup) {
735 	char            cmd[FILENAME];
736 	sprintf(cmd, "%s/nnadmin Z", bin_directory);
737 	system(cmd);
738 	log_entry('M', "Database validation completed");
739     }
740     repeat_delay *= 60;
741 
742     init_digest_parsing();
743 
744     open_master(OPEN_UPDATE);
745 
746     if (group_selection)
747 	set_group_restrictions(argv + 1, group_selection);
748 
749     if (max_age_days && !use_nntp)	/* we have to stat spool files */
750 	max_article_age = cur_time() - (time_t) max_age_days *24 * 60 * 60;
751     else
752 	max_article_age = 0;
753 
754     if (expire_once) {
755 	if (group_selection)	/* mark selected groups for expire */
756 	    Loop_Groups_Header(gh) {
757 	    if (gh->master_flag & M_IGNORE_GROUP)
758 		continue;
759 	    gh->master_flag |= M_EXPIRE;
760 	    }
761 	unconditional = 1;
762     }
763     for (pass_no = 0; !s_hangup; pass_no++) {
764 	if (pass_no > 0) {
765 	    take_a_nap(repeat_delay);
766 	    if (s_hangup)
767 		break;
768 	}
769 
770 #ifdef NNTP
771 	if (use_nntp && nntp_get_active() < 0) {
772 	    nntp_close_server();
773 	    current_group = NULL;	/* for init_group */
774 	    log_entry('N', "Can't access active file --- %s",
775 		      repeat_delay ? "sleeping" : "terminating");
776 	    if (repeat_delay == 0)
777 		nn_exit(1);
778 	    continue;
779 	}
780 #endif
781 
782 	temp = 2;
783 	while ((age_active = file_exist(news_active, "fr")) == (time_t) 0) {
784 	    if (use_nntp)
785 		break;
786 	    if (--temp < 0)
787 		sys_error("Cannot access active file");
788 	    sleep(5);		/* maybe a temporary glitch ? */
789 	}
790 
791 	skip_pass = 0;
792 
793 	if (Unconditional)
794 	    unconditional = 1;
795 
796 	if (unconditional) {
797 	    unconditional = 0;
798 	} else if (age_active <= master.last_scan ||
799 		 (hold_updates && (cur_time() - age_active) < hold_updates))
800 	    skip_pass = 1;
801 
802 	if (receive_admin())
803 	    skip_pass = 0;
804 
805 	if (skip_pass) {
806 	    if (repeat_delay == 0)
807 		break;
808 	    if (s_hangup)
809 		break;
810 
811 #ifdef NNTP
812 	    if (use_nntp)
813 		nntp_cleanup();
814 #endif
815 
816 	    if (debug_mode) {
817 		printf("NONE (*** SLEEP ***)\n");
818 	    }
819 	    if (trace)
820 		log_entry('T', "none");
821 	    continue;
822 	}
823 	visit_active_file();
824 
825 	if (do_expire())
826 	    if (!expire_once && do_collect()) {
827 		if (Unconditional)
828 		    master.last_scan = cur_time();
829 		else
830 		    master.last_scan = age_active;
831 	    }
832 	db_write_master();
833 
834 	if (expire_once || s_hangup)
835 	    break;
836 	if (repeat_delay == 0)
837 	    break;
838 
839 #ifdef NNTP
840 	if (use_nntp)
841 	    nntp_cleanup();
842 #endif
843     }
844 
845     nn_exit(0);
846     /* NOTREACHED */
847     return 0;
848 }
849 
850 
851 
852 /*
853  * receive commands from administrator
854  */
855 
856 static int
receive_admin(void)857 receive_admin(void)
858 {
859     FILE           *gate;
860     char            buffer[128], *bp;
861     char            command, opt, *user_date;
862     int32           arg;
863     int             must_collect;
864     register group_header *gh;
865 
866     gate = open_gate_file(OPEN_READ);
867     if (gate == NULL)
868 	return 0;
869 
870     sleep(2);			/* give administrator time to flush buffers */
871 
872     must_collect = 0;
873 
874     while (fgets(buffer, 128, gate)) {
875 	bp = buffer;
876 
877 	command = *bp++;
878 	if (*bp++ != ';')
879 	    continue;
880 
881 	arg = atol(bp);
882 	if (arg >= master.number_of_groups)
883 	    continue;
884 	gh = (arg >= 0) ? ACTIVE_GROUP(arg) : NULL;
885 	if ((bp = strchr(bp, ';')) == NULL)
886 	    continue;
887 	bp++;
888 
889 	opt = *bp++;
890 	if (*bp++ != ';')
891 	    continue;
892 
893 	arg = atol(bp);
894 	if ((bp = strchr(bp, ';')) == NULL)
895 	    continue;
896 
897 	user_date = ++bp;
898 	if ((bp = strchr(bp, ';')) == NULL)
899 	    continue;
900 	*bp++ = NUL;
901 	if (*bp != NL)
902 	    continue;
903 
904 	log_entry('A', "RECV %c %s %c %ld (%s)",
905 	command, gh == NULL ? "(all)" : gh->group_name, opt, arg, user_date);
906 
907 	switch (command) {
908 
909 	    case SM_SET_OPTION:
910 		switch (opt) {
911 		    case 'r':
912 			repeat_delay = arg;
913 			continue;
914 		    case 'e':
915 			expire_level = arg;
916 			continue;
917 		    case 't':
918 			trace = (arg < 0) ? !trace : arg;
919 			continue;
920 		}
921 		continue;	/* XXX: Is this the right thing to do? */
922 
923 
924 	    case SM_EXPIRE:
925 		if (gh) {
926 		    gh->master_flag |= M_EXPIRE | M_BLOCKED;
927 		    db_write_group(gh);
928 		    break;
929 		}
930 		Loop_Groups_Header(gh) {
931 		    if (gh->master_flag & M_IGNORE_GROUP)
932 			continue;
933 		    if (gh->index_write_offset == 0)
934 			continue;
935 		    if (gh->master_flag & M_EXPIRE)
936 			continue;
937 		    gh->master_flag |= M_EXPIRE;
938 		    db_write_group(gh);
939 		}
940 		break;
941 
942 	    case SM_SET_FLAG:
943 		if (opt == 's')
944 		    gh->master_flag |= (flag_type) arg;
945 		else
946 		    gh->master_flag &= ~(flag_type) arg;
947 		db_write_group(gh);
948 		continue;
949 
950 	    case SM_RECOLLECT:	/* recollect */
951 		if (gh) {
952 		    if ((gh->master_flag & M_IGNORE_GROUP) == 0)
953 			clean_group(gh);
954 		} else
955 		    Loop_Groups_Header(gh)
956 		    if ((gh->master_flag & M_IGNORE_GROUP) == 0)
957 		    clean_group(gh);
958 		break;
959 
960 	    case SM_SCAN_ONCE:	/* unconditional pass */
961 		unconditional++;
962 		break;
963 
964 	    default:
965 		continue;
966 	}
967 	must_collect = 1;
968     }
969 
970     fclose(gate);
971 
972     return must_collect;
973 }
974 
975 
976 void
write_error(void)977 write_error(void)
978 {
979 
980     /*
981      * should wait for problems to clear out rather than die...
982      */
983     sys_error("DISK WRITE ERROR");
984 }
985 
986 /*
987  * dummy routines - should never be called by master
988  */
989 
990 char            delayed_msg[100] = "";
991 
992 FILE           *
open_purpose_file(void)993 open_purpose_file(void)
994 {
995     return NULL;
996 }
997 
998 int
set_variable(char * variable,int on,char * val_string)999 set_variable(char *variable, int on, char *val_string)
1000 {
1001     return 0;
1002 }
1003 
1004 char           *
alloc_str(int len)1005 alloc_str(int len)
1006 {
1007     return 0;
1008 }
1009 
1010 void
nn_exitmsg(int n,char * fmt,...)1011 nn_exitmsg(int n, char *fmt,...)
1012 {
1013 }
1014 
1015 void
msg(char * fmt,...)1016 msg(char *fmt,...)
1017 {
1018 }
1019 
1020 void
init_term(int full)1021 init_term(int full)
1022 {
1023 }
1024 
1025 char           *
full_name(void)1026 full_name(void)
1027 {
1028     return 0;
1029 }
1030 
1031 void
clrdisp(void)1032 clrdisp(void)
1033 {
1034 }
1035 
1036 void
prompt(char * fmt,...)1037 prompt(char *fmt,...)
1038 {
1039 }
1040 
1041 void
user_delay(int ticks)1042 user_delay(int ticks)
1043 {
1044     return;
1045 }
1046 
1047 #ifdef HAVE_JOBCONTROL
1048 int
suspend_nn(void)1049 suspend_nn(void)
1050 {
1051     return 0;
1052 }
1053 
1054 #endif
1055