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