1 /*
2 * (c) Copyright 1990, Kim Fabricius Storm. All rights reserved.
3 * Copyright (c) 1996-2005 Michael T Pins. All rights reserved.
4 *
5 * nnadmin - nn administator program
6 */
7
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <signal.h>
11 #include <errno.h>
12 #include <string.h>
13 #include <ctype.h>
14 #include "config.h"
15 #include "global.h"
16 #include "admin.h"
17 #include "db.h"
18 #include "execute.h"
19 #include "group.h"
20 #include "nn.h"
21 #include "proto.h"
22 #include "nn_term.h"
23
24 /* admin.c */
25
26 static int get_cmd(char *prompt1, char *prompt2);
27 static long get_entry(char *prompt_str, long min_val, long max_val);
28 static int admin_confirm(char *action, int must_confirm);
29 static char *get_groupname(void);
30 static void update_master(void);
31 static void find_files(group_header * gh);
32 static void master_status(void);
33 static void dump_g_flag(register group_header * gh);
34 static void dump_m_entry(register group_header * gh);
35 static int validate_group(group_header * gh);
36 static int dump_group(group_header * gh);
37 static void kill_master(int term);
38 static void master_run_check(void);
39 static void master_admin(void);
40 static void log_admin(void);
41 static void flag_admin(group_header * gh, char *mode_str, int set_flag);
42 static void rmgroup(group_header * gh);
43 static void group_admin(register group_header * gh);
44 static void show_config(void);
45
46
47 extern char
48 *master_directory, *db_directory, *db_data_directory, *bin_directory, *news_directory,
49 *news_lib_directory, *log_file, *news_active, *pager, group_path_name[];
50
51 extern char *exec_chdir_to;
52
53 extern int db_data_subdirs;
54 extern char proto_host[];
55
56 #ifdef NNTP
57 extern char *nntp_server;
58 #endif
59
60 static char *pre_input;
61 static int verbose2 = 1;
62
63 static int
get_cmd(char * prompt1,char * prompt2)64 get_cmd(char *prompt1, char *prompt2)
65 {
66 char c;
67
68 if (s_hangup)
69 nn_exitmsg(0, "\nnnmaster hangup\n");
70
71 s_keyboard = 0;
72
73 if (pre_input) {
74 if ((c = *pre_input++) == NUL)
75 nn_exit(0);
76 } else {
77 do {
78 if (prompt1)
79 tprintf("\r%s\n", prompt1);
80 if (prompt2)
81 tprintf("\r%s >>>", prompt2);
82 fl;
83 nn_raw();
84 c = get_c();
85 unset_raw();
86 if (c == K_interrupt)
87 s_keyboard++;
88 else if (c == '!') {
89 tputc(NL);
90 if (exec_chdir_to != NULL)
91 tprintf("\n\rDirectory: %s", exec_chdir_to);
92 run_shell((char *) NULL, 0, 0);
93 } else
94 tprintf("%c\n\n\r", c);
95 } while (c == '!');
96 }
97
98 if (islower(c))
99 c = toupper(c);
100
101 return c;
102 }
103
104
105 static long
get_entry(char * prompt_str,long min_val,long max_val)106 get_entry(char *prompt_str, long min_val, long max_val)
107 {
108 char buf[100];
109 long val;
110
111 loop:
112
113 tprintf("%s %ld..%ld (or all): ", prompt_str, min_val, max_val);
114 fl;
115 fgets(buf, sizeof(buf), stdin);
116 if (buf[0] == 'a' || buf[0] == NUL)
117 return -1L;
118
119 val = atol(buf);
120 if (val < min_val || val > max_val)
121 goto loop;
122
123 return val;
124 }
125
126
127 static int
admin_confirm(char * action,int must_confirm)128 admin_confirm(char *action, int must_confirm)
129 {
130 char buffer[100];
131
132 if (pre_input && !must_confirm)
133 return 1;
134
135 sprintf(buffer, "Confirm %s Y)es N)o", action);
136
137 return get_cmd((char *) NULL, buffer) == 'Y';
138 }
139
140 static char *
get_groupname(void)141 get_groupname(void)
142 {
143 char *groupname;
144
145 nn_raw();
146 tprintf("\n\n\r");
147 prompt_line = Lines - 2;
148 prompt("Group: ");
149 fl;
150 groupname = get_s(NONE, NONE, NONE, group_completion);
151 unset_raw();
152
153 tputc(NL);
154 tputc(CR);
155
156 if (groupname == NULL)
157 return NULL;
158
159 if (groupname[0])
160 return groupname;
161
162 if (current_group == NULL)
163 return NULL;
164 if (current_group->group_flag & G_FOLDER)
165 return NULL;
166
167 return current_group->group_name;
168 }
169
170
171 static void
update_master(void)172 update_master(void)
173 {
174 register group_header *gh;
175 int ngp;
176
177 if (who_am_i != I_AM_ADMIN) {
178 tprintf("Can only perform (U)pdate as nnadmin\n");
179 return;
180 }
181 ngp = master.number_of_groups;
182
183 open_master(OPEN_READ);
184
185 if (master.number_of_groups != ngp) {
186 tprintf("\nNumber of groups changed from %d to %d\n",
187 ngp, master.number_of_groups);
188 }
189 ngp = 0;
190
191 Loop_Groups_Header(gh)
192 if (update_group(gh) == -1)
193 ngp++;
194
195 if (ngp)
196 tprintf("There are %d blocked groups\n", ngp);
197 }
198
199 static void
find_files(group_header * gh)200 find_files(group_header * gh)
201 {
202 char command[512], name[FILENAME];
203
204 if (gh == NULL) {
205 if (db_data_directory == NULL) {
206 tprintf("Cannot list all files (they are scattered over the news partition)\n");
207 return;
208 }
209 if (db_data_subdirs)
210 sprintf(command, "cd %s ; ls -l [0-9] | %s", db_data_directory, pager);
211 else
212 sprintf(command, "ls -l %s | %s", db_data_directory, pager);
213 } else
214 sprintf(command, "ls -l %s", db_data_path(name, gh, '*'));
215 system(command);
216 }
217
218 static void
master_status(void)219 master_status(void)
220 {
221 int nblocked, nignored;
222 long articles1, disk_use;
223 register group_header *gh;
224
225 tprintf("\nMaster:\n");
226 tprintf(" initialized: %s\n", date_time(master.db_created));
227 tprintf(" last_scan: %s\n", date_time(master.last_scan));
228 tprintf(" last_size: %ld\n", (long) master.last_size);
229 tprintf(" no of groups: %d\n", master.number_of_groups);
230
231 articles1 = disk_use = 0;
232 nblocked = nignored = 0;
233
234 Loop_Groups_Header(gh) {
235
236 #define DISK_BLOCKS(bytes) (((bytes) + 1023) / 1024)
237
238 disk_use += DISK_BLOCKS(gh->index_write_offset);
239 disk_use += DISK_BLOCKS(gh->data_write_offset);
240
241 articles1 += gh->last_db_article - gh->first_db_article + 1;
242
243 if (gh->master_flag & M_BLOCKED)
244 nblocked++;
245 if (gh->master_flag & M_IGNORE_GROUP)
246 nignored++;
247 }
248
249 tprintf("\n Articles: %ld\n", articles1);
250 tprintf(" Disk usage: %ld k\n\n", disk_use);
251
252 if (nblocked)
253 tprintf("Blocked groups: %3d\n", nblocked);
254 if (nignored)
255 tprintf("Ignored groups: %3d\n", nignored);
256 }
257
258 static void
dump_g_flag(register group_header * gh)259 dump_g_flag(register group_header * gh)
260 {
261 tprintf("Flags: ");
262 if (gh->master_flag & M_BLOCKED)
263 tprintf(" BLOCKED");
264 if (gh->master_flag & M_EXPIRE)
265 tprintf(" EXPIRE");
266 if (gh->master_flag & M_MODERATED)
267 tprintf(" MODERATED");
268 if (gh->master_flag & M_CONTROL)
269 tprintf(" CONTROL");
270 if (gh->master_flag & M_NO_DIRECTORY)
271 tprintf(" NO_DIRECTORY");
272 if (gh->master_flag & M_ALWAYS_DIGEST)
273 tprintf(" ALWAYS_DIGEST");
274 if (gh->master_flag & M_NEVER_DIGEST)
275 tprintf(" NEVER_DIGEST");
276 if (gh->master_flag & M_INCLUDE_OLD)
277 tprintf(" INCL_OLD");
278 if (gh->master_flag & M_AUTO_RECOLLECT)
279 tprintf(" RECOLLECT");
280 if (gh->master_flag & M_AUTO_ARCHIVE)
281 tprintf(" ARCHIVE");
282 if (gh->master_flag & M_IGNORE_GROUP)
283 tprintf(" IGNORE");
284 if (gh->master_flag & M_VALID)
285 tprintf(" VALID");
286 tprintf("\n");
287 }
288
289 static void
dump_m_entry(register group_header * gh)290 dump_m_entry(register group_header * gh)
291 {
292 update_group(gh);
293
294 tprintf("\n%s\t%d\n", gh->group_name, gh->group_num);
295 tprintf("first/last art: %06ld %06d\n",
296 gh->first_db_article, gh->last_db_article);
297 tprintf(" active info: %06ld %06d\n",
298 gh->first_a_article, gh->last_a_article);
299 tprintf("Offsets: index->%ld, data->%ld\n",
300 gh->index_write_offset,
301 gh->data_write_offset);
302 if (gh->master_flag & M_AUTO_ARCHIVE)
303 tprintf("Archive file: %s\n", gh->archive_file);
304 if (gh->master_flag)
305 dump_g_flag(gh);
306 }
307
308 #define valerr( xxx , tp) { \
309 if (verbose2) { tprintf xxx ; fl; } \
310 err_type = tp; \
311 goto err; \
312 }
313
314 static int
validate_group(group_header * gh)315 validate_group(group_header * gh)
316 {
317 FILE *data, *ix;
318 long data_offset, next_offset;
319 cross_post_number cross_post;
320 article_number cur_article;
321 int n, err_type;
322
323 data = ix = NULL;
324
325 if (gh->master_flag & M_IGNORE_GROUP)
326 return 1;
327
328 if (gh->first_db_article == (gh->last_db_article + 1)
329 && gh->index_write_offset == 0)
330 return 1;
331
332 if (verbose2) {
333 tprintf("\r%s: ", gh->group_name);
334 clrline();
335 }
336 /* init_group returns ok for gh == current_group before check on NO_DIR */
337 if ((gh->master_flag & M_NO_DIRECTORY) || init_group(gh) <= 0) {
338 if (verbose2)
339 tprintf("NO DIRECTORY for %s (ok)", gh->group_name);
340 return 1; /* no directory/ignored */
341 }
342 update_group(gh);
343
344 if (gh->master_flag & M_BLOCKED) {
345 if (verbose2)
346 tprintf("BLOCKED (ok)\n");
347 return 1;
348 }
349
350 /*
351 * Check for major inconcistencies. Sometimes, news expire will reset
352 * article numbers to start from 0 again
353 */
354
355 if (gh->last_db_article == 0) {
356 return 1;
357 }
358
359 #ifdef RENUMBER_DANGER
360 if (gh->first_a_article > (gh->last_db_article + 1) ||
361 gh->last_db_article > gh->last_a_article ||
362 gh->first_db_article > gh->first_a_article) {
363
364 if (verbose2)
365 tprintf("RENUMBERING OF ARTICLES (active=%ld..%ld master=%ld..%ld)",
366 gh->first_a_article, gh->last_a_article,
367 gh->first_db_article, gh->last_db_article);
368 err_type = 99;
369 goto err;
370 }
371 #endif
372
373 ix = open_data_file(gh, 'x', OPEN_READ);
374 if (ix == NULL)
375 valerr(("NO INDEX FILE"), 1);
376
377 data = open_data_file(gh, 'd', OPEN_READ);
378 if (data == NULL)
379 valerr(("NO DATA FILE"), 2);
380
381 cur_article = gh->first_db_article - 1;
382
383 while (cur_article <= gh->last_db_article) {
384 if (s_hangup || s_keyboard)
385 goto out;
386
387 data_offset = ftell(data);
388
389 switch (db_read_art(data)) {
390 case 0:
391 if (data_offset == gh->data_write_offset)
392 goto out;
393 valerr(("No header for article # %ld", (long) cur_article + 1), 3);
394 default:
395 break;
396 case -1:
397 valerr(("END OF FILE on DATA FILE"), 4);
398 }
399
400 if (db_hdr.dh_number <= 0 || cur_article > db_hdr.dh_number)
401 valerr(("OUT OF SEQUENCE: %ld after %ld", (long) db_hdr.dh_number, (long) cur_article), 11);
402
403 if (cur_article < db_hdr.dh_number) {
404 if (db_data.dh_type == DH_SUB_DIGEST)
405 valerr(("NO DIGEST HEADER: %ld", (long) db_hdr.dh_number), 5);
406
407 do {
408 cur_article++;
409 if (!db_read_offset(ix, &next_offset))
410 valerr(("NO INDEX FOR ARTICLE %ld", (long) cur_article), 6);
411
412 if (data_offset != next_offset)
413 valerr(("OFFSET ERROR: %ld: %ld != %ld", (long) cur_article, data_offset, next_offset), 7);
414 } while (cur_article < db_hdr.dh_number);
415 }
416 for (n = 0; n < (int) db_hdr.dh_cross_postings; n++) {
417 cross_post = NETW_CROSS_INT(db_data.dh_cross[n]);
418 if (cross_post < master.number_of_groups)
419 continue;
420 valerr(("CROSS POST RANGE ERROR: %ld (article # %ld)", (long) cross_post, (long) cur_article), 8);
421 }
422 }
423
424 out:
425 if (!s_keyboard && !s_hangup) {
426 data_offset = ftell(data);
427 if (data_offset != gh->data_write_offset)
428 valerr(("DATA OFFSET %ld != %ld", gh->data_write_offset, data_offset), 9);
429
430 while (++cur_article <= gh->last_db_article) {
431 if (!db_read_offset(ix, &next_offset))
432 valerr(("NO INDEX FOR ARTICLE %ld", (long) cur_article), 12);
433 if (data_offset != next_offset)
434 valerr(("OFFSET ERROR: %ld: %ld != %ld", (long) cur_article, data_offset, next_offset), 13);
435 }
436
437 data_offset = ftell(ix);
438 if (data_offset != gh->index_write_offset)
439 valerr(("INDEX OFFSET %ld != %ld", gh->index_write_offset, data_offset), 10);
440 }
441 fclose(data);
442 fclose(ix);
443 if (verbose2)
444 tprintf("OK");
445 return 1;
446
447 err:
448 if (data != NULL)
449 fclose(data);
450 if (ix != NULL)
451 fclose(ix);
452 log_entry('V', "%s: database error %d", gh->group_name, err_type);
453 if (verbose2) {
454 tputc(NL);
455 dump_m_entry(gh);
456 }
457 if (!pre_input) {
458 ding();
459
460 for (;;) {
461 switch (get_cmd((char *) NULL,
462 "\nRepair group Y)es N)o E)nter Q)uit")) {
463 case 'Y':
464 break;
465 case 'N':
466 return 0;
467 case 'Q':
468 s_keyboard++;
469 return 0;
470 case 'E':
471 group_admin(gh);
472 continue;
473 default:
474 continue;
475 }
476 break;
477 }
478 }
479 send_master(SM_RECOLLECT, gh, SP, 0L);
480 return 0;
481 }
482
483 static int
dump_group(group_header * gh)484 dump_group(group_header * gh)
485 {
486 FILE *data, *ix;
487 long offset;
488 cross_post_number cross_post;
489 article_number first_article;
490 int n;
491
492 if (init_group(gh) <= 0) {
493 tprintf("cannot access group %s\n", gh->group_name);
494 return 0;
495 }
496 update_group(gh);
497
498 if (gh->index_write_offset == 0) {
499 tprintf("group %s is empty\n", gh->group_name);
500 return 1;
501 }
502 first_article = get_entry("First article",
503 (long) gh->first_db_article,
504 (long) gh->last_db_article);
505
506 if (first_article < 0)
507 first_article = gh->first_db_article;
508 if (first_article <= 0)
509 first_article = 1;
510
511 ix = open_data_file(gh, 'x', OPEN_READ);
512 data = open_data_file(gh, 'd', OPEN_READ);
513 if (ix == NULL || data == NULL)
514 goto err;
515
516 fseek(ix, get_index_offset(gh, first_article), 0);
517 if (!db_read_offset(ix, &offset))
518 goto err;
519 fseek(data, offset, 0);
520
521 clrdisp();
522 pg_init(1, 1);
523
524 for (;;) {
525 if (s_hangup || s_keyboard)
526 break;
527
528 if (pg_scroll(6)) {
529 s_keyboard = 1;
530 break;
531 }
532 offset = ftell(data);
533
534 switch (db_read_art(data)) {
535 case 0:
536 goto out;
537 default:
538 break;
539 case -1:
540 goto err;
541 }
542
543 tprintf("\noffset = %ld, article # = %ld",
544 offset, (long) (db_hdr.dh_number));
545
546 switch (db_data.dh_type) {
547 case DH_DIGEST_HEADER:
548 tprintf(" (digest header)\n");
549 break;
550 case DH_SUB_DIGEST:
551 tprintf(" (digest sub-article)\n");
552 break;
553 case DH_NORMAL:
554 tputc(NL);
555 break;
556 }
557
558 if (db_hdr.dh_cross_postings) {
559 tprintf("xpost(%d):", db_hdr.dh_cross_postings);
560
561 for (n = 0; n < (int) db_hdr.dh_cross_postings; n++) {
562 cross_post = NETW_CROSS_INT(db_data.dh_cross[n]);
563 tprintf(" %d", cross_post);
564 }
565 tputc(NL);
566 }
567 tprintf("ts=%lu hp=%ld, fp=+%d, lp=%ld, ref=%d%s, lines=%d\n",
568 (long unsigned) db_hdr.dh_date, (long) db_hdr.dh_hpos,
569 (int) db_hdr.dh_fpos, (long) db_hdr.dh_lpos,
570 db_hdr.dh_replies & 0x7f,
571 (db_hdr.dh_replies & 0x80) ? "+Re" : "",
572 db_hdr.dh_lines);
573
574 if (db_hdr.dh_sender_length)
575 tprintf("Sender(%d): %s\n", db_hdr.dh_sender_length, db_data.dh_sender);
576 else
577 tprintf("No sender\n");
578
579 if (db_hdr.dh_subject_length)
580 tprintf("Subj(%d): %s\n", db_hdr.dh_subject_length, db_data.dh_subject);
581 else
582 tprintf("No subject\n");
583 }
584
585 out:
586 if (!s_keyboard && !s_hangup && ftell(data) != gh->data_write_offset)
587 goto err;
588
589 fclose(data);
590 fclose(ix);
591 return 1;
592
593 err:
594 if (data != NULL)
595 fclose(data);
596 if (ix != NULL)
597 fclose(ix);
598 if (gh->master_flag & M_IGNORE_GROUP)
599 return 0;
600 tprintf("\n*** DATABASE INCONSISTENCY DETECTED - run V)alidate\n");
601 return 0;
602 }
603
604 static void
kill_master(int term)605 kill_master(int term)
606 {
607 switch (proto_lock(I_AM_MASTER, term ? PL_TERMINATE : PL_WAKEUP_SOFT)) {
608 case 1:
609 if (verbose2)
610 tprintf("sent %s signal to master\n", term ? "termination" : "wakeup");
611 break;
612 case 0:
613 tprintf("cannot signal master\n");
614 break;
615 case -1:
616 if (verbose2)
617 tprintf("master is not running\n");
618 break;
619 }
620 }
621
622 static void
master_run_check(void)623 master_run_check(void)
624 {
625 int running = proto_lock(I_AM_MASTER, PL_CHECK) >= 0;
626
627 if (!verbose2 && pre_input != NULL)
628 exit(running ? 0 : 1);
629
630 if (running)
631 tprintf("Master is running%s%s\n",
632 proto_host[0] ? " on host " : "", proto_host);
633 else
634 tprintf("Master is NOT running");
635 }
636
637 static void
master_admin(void)638 master_admin(void)
639 {
640 register char c;
641 int cur_group;
642 long value;
643 register group_header *gh;
644
645 exec_chdir_to = db_directory;
646
647 for (;;) {
648 switch (c = get_cmd(
649 "\nC)heck D)ump F)iles G)roup K)ill O)ptions S)tat T)race",
650 "MASTER")) {
651
652 case 'C':
653 master_run_check();
654 break;
655
656 case 'G':
657 cur_group = (int) get_entry("Group number",
658 0L, (long) (master.number_of_groups - 1));
659 if (cur_group >= 0)
660 dump_m_entry(ACTIVE_GROUP(cur_group));
661 break;
662
663 case 'D':
664 do {
665 c = get_cmd(
666 "\nA)ll B)locked E)mpty H)oles I)gnored N)on-empty V)alid in(W)alid",
667 "DUMP");
668 pg_init(1, 1);
669
670 Loop_Groups_Header(gh) {
671 if (s_keyboard || s_hangup)
672 break;
673 #define NODUMP(cond) if (cond) continue; break
674 switch (c) {
675 case 'N':
676 NODUMP(gh->index_write_offset == 0);
677 case 'E':
678 NODUMP(gh->index_write_offset != 0);
679 case 'H':
680 NODUMP(gh->first_a_article >= gh->first_db_article);
681 case 'B':
682 NODUMP((gh->master_flag & M_BLOCKED) == 0);
683 case 'I':
684 NODUMP((gh->master_flag & M_IGNORE_GROUP) == 0);
685 case 'V':
686 NODUMP((gh->master_flag & M_VALID) == 0);
687 case 'W':
688 NODUMP(gh->master_flag & M_VALID);
689 case 'A':
690 break;
691 default:
692 s_keyboard = 1;
693 continue;
694 }
695
696 if (pg_scroll(6))
697 break;
698 dump_m_entry(gh);
699 }
700 } while (!s_keyboard && !s_hangup);
701 break;
702
703 case 'F':
704 find_files((group_header *) NULL);
705 break;
706
707 case 'O':
708 c = get_cmd("r)epeat_delay e)xpire_level", "OPTION");
709 if (c != 'R' && c != 'E')
710 break;
711 value = get_entry("Option value", 1L, 10000L);
712 if (value < 0)
713 break;
714 send_master(SM_SET_OPTION, (group_header *) NULL, tolower(c), value);
715 break;
716
717 case 'S':
718 master_status();
719 break;
720
721 case 'T':
722 send_master(SM_SET_OPTION, (group_header *) NULL, 't', -1L);
723 break;
724
725 case 'K':
726 if (admin_confirm("Stop nn Master", 0))
727 kill_master(1);
728 break;
729
730 default:
731 exec_chdir_to = NULL;
732 return;
733 }
734 }
735 }
736
737
738 static void
log_admin(void)739 log_admin(void)
740 {
741 char command[FILENAME + 100], c;
742
743 if (pre_input && *pre_input == NUL) {
744 c = SP;
745 goto log_tail;
746 }
747 loop:
748
749 c = get_cmd(
750 "\n1-9)tail A)dm C)ollect E)rr N)ntp G)roup R)eport e(X)pire .)all @)clean",
751 "LOG");
752
753 if (c < SP || c == 'Q')
754 return;
755
756 if (c == '@') {
757 if (admin_confirm("Truncation", 0)) {
758 sprintf(command, "%s.old", log_file);
759 unlink(command);
760 if (link(log_file, command) < 0)
761 goto tr_failed;
762 if (unlink(log_file) < 0) {
763 unlink(command);
764 goto tr_failed;
765 }
766 log_entry('A', "Log Truncated");
767 chmod(log_file, 0666);
768 }
769 return;
770
771 tr_failed:
772 tprintf("Truncation failed -- check permissions etc.\n");
773 goto loop;
774 }
775 if (c == 'G') {
776 char *groupname;
777
778 if ((groupname = get_groupname()) == NULL)
779 goto loop;
780 sprintf(command, "fgrep '%s' %s | %s",
781 groupname, log_file, pager);
782 system(command);
783
784 goto loop;
785 }
786 log_tail:
787 if (c == '$' || c == SP || isdigit(c)) {
788 int n;
789
790 n = isdigit(c) ? 10 * (c - '0') : 10;
791 sprintf(command, "tail -%d %s", n, log_file);
792 system(command);
793 goto loop;
794 }
795 if (c == '*') {
796 c = '.';
797 }
798 sprintf(command, "grep '^%c:' %s | %s", c, log_file, pager);
799 system(command);
800
801 goto loop;
802 }
803
804
805 static void
flag_admin(group_header * gh,char * mode_str,int set_flag)806 flag_admin(group_header * gh, char *mode_str, int set_flag)
807 {
808 char buffer[50];
809 int new_flag = 0;
810
811 tputc(NL);
812
813 dump_g_flag(gh);
814
815 sprintf(buffer, "%s FLAG", mode_str);
816
817 switch (get_cmd(
818 "\nA)lways_digest N)ever_digest M)oderated C)ontrol no_(D)ir",
819 buffer)) {
820
821 default:
822 return;
823
824 case 'M':
825 new_flag = M_MODERATED;
826 break;
827
828 case 'C':
829 new_flag = M_CONTROL;
830 break;
831
832 case 'D':
833 new_flag = M_NO_DIRECTORY;
834 break;
835
836 case 'A':
837 new_flag = M_ALWAYS_DIGEST;
838 break;
839
840 case 'N':
841 new_flag = M_NEVER_DIGEST;
842 break;
843 }
844
845 if (new_flag & (M_CONTROL | M_NO_DIRECTORY))
846 if (!admin_confirm("Flag Change", 0))
847 new_flag = 0;
848
849 if (new_flag) {
850 if (set_flag) {
851 if (gh->master_flag & new_flag)
852 new_flag = 0;
853 else {
854 send_master(SM_SET_FLAG, gh, 's', (long) new_flag);
855 gh->master_flag |= new_flag;
856 }
857 } else {
858 if ((gh->master_flag & new_flag) == 0)
859 new_flag = 0;
860 else {
861 send_master(SM_SET_FLAG, gh, 'c', (long) new_flag);
862 gh->master_flag &= ~new_flag;
863 }
864 }
865 }
866 if (new_flag == 0)
867 tprintf("NO CHANGE\n");
868 else
869 dump_g_flag(gh);
870 }
871
872
873 static void
rmgroup(group_header * gh)874 rmgroup(group_header * gh)
875 {
876 char command[FILENAME * 2];
877 char *rmprog;
878
879 if (user_id != 0 && !file_exist(news_active, "w")) {
880 tprintf("Not privileged to run rmgroup\n");
881 return;
882 }
883
884 #ifdef RMGROUP_PATH
885 rmprog = RMGROUP_PATH;
886 #else
887 rmprog = "rmgroup";
888 #endif
889
890 if (*rmprog != '/')
891 rmprog = relative(news_lib_directory, rmprog);
892 if (file_exist(rmprog, "x"))
893 goto rm_ok;
894
895 #ifndef RMGROUP_PATH
896 rmprog = relative(news_lib_directory, "delgroup");
897 if (file_exist(rmprog, "x"))
898 goto rm_ok;
899 #endif
900
901 tprintf("Program %s not found\n", rmprog);
902 return;
903 rm_ok:
904 sprintf(command, "%s %s", rmprog, gh->group_name);
905 system(command);
906 any_key(0);
907 gh->master_flag &= ~M_VALID;/* just for nnadmin */
908 gh->master_flag |= M_IGNORE_A;
909 }
910
911 static void
group_admin(register group_header * gh)912 group_admin(register group_header * gh)
913 {
914 char *groupname, gbuf[FILENAME], dirbuf[FILENAME];
915
916 if (gh != NULL)
917 goto have_group;
918
919 new_group:
920 if ((groupname = get_groupname()) == NULL)
921 return;
922
923 if ((gh = lookup_regexp(groupname, "Enter", 0)) == NULL)
924 goto new_group;
925
926 have_group:
927 if (!use_nntp && init_group(gh)) {
928 strcpy(dirbuf, group_path_name);
929 dirbuf[strlen(dirbuf) - 1] = NUL;
930 exec_chdir_to = dirbuf;
931 }
932 sprintf(gbuf, "GROUP %s", gh->group_name);
933
934 for (;;) {
935 switch (get_cmd(
936 "\nD)ata E)xpire F)iles G)roup H)eader R)ecollect V)alidate Z)ap",
937 gbuf)) {
938 case 'D':
939 dump_group(gh);
940 break;
941
942 case 'V':
943 verbose2 = 1;
944 validate_group(gh);
945 tputc(NL);
946 break;
947
948 case 'H':
949 dump_m_entry(gh);
950 break;
951
952 case 'F':
953 find_files(gh);
954 break;
955
956 case 'S':
957 flag_admin(gh, "Set", 1);
958 break;
959
960 case 'C':
961 flag_admin(gh, "Clear", 0);
962 break;
963
964 case 'R':
965 if (admin_confirm("Recollection of Group", 0))
966 send_master(SM_RECOLLECT, gh, SP, 0L);
967 break;
968
969 case 'Z':
970 if (admin_confirm("Remove Group (run rmgroup)", 1))
971 rmgroup(gh);
972 break;
973
974 case 'E':
975 if (admin_confirm("Expire Group", 0))
976 send_master(SM_EXPIRE, gh, SP, 0L);
977 break;
978
979 case 'G':
980 goto new_group;
981
982 default:
983 exec_chdir_to = NULL;
984 return;
985 }
986 }
987 }
988
989
990 static void
show_config(void)991 show_config(void)
992 {
993
994 tprintf("\nConfiguration:\n\n");
995 tprintf("BIN: %s\nLIB: %s\nNEWS SPOOL: %s\nNEWS LIB: %s\n",
996 bin_directory,
997 lib_directory,
998 news_directory,
999 news_lib_directory);
1000
1001 tprintf("DB: %s\nDATA: ", db_directory);
1002 if (db_data_directory != NULL) {
1003
1004 #ifdef DB_LONG_NAMES
1005 tprintf("%s/{group.name}.[dx]\n", db_data_directory);
1006 #else
1007 tprintf("%s%s/{group-number}.[dx]\n", db_data_directory,
1008 db_data_subdirs ? "/[0-9]" : "");
1009 #endif
1010 } else {
1011 tprintf("%s/{group/name/path}/.nn[dx]\n", news_directory);
1012 }
1013
1014 tprintf("ACTIVE: %s\n", news_active);
1015
1016 #ifdef NNTP
1017 if (use_nntp)
1018 tprintf("NNTP ACTIVE. server=%s\n", nntp_server);
1019 else
1020 tprintf("NNTP NOT ACTIVE\n");
1021 #endif
1022
1023 #ifdef NETWORK_DATABASE
1024 tprintf("Database is machine independent (network format).\n");
1025
1026 #ifdef NETWORK_BYTE_ORDER
1027 tprintf("Local system assumes to use network byte order\n");
1028 #endif
1029
1030 #else
1031 tprintf("Database format is machine dependent (byte order and alignment)\n");
1032 #endif
1033
1034 tprintf("Database version: %d\n", NNDB_MAGIC & 0xff);
1035
1036 #ifdef STATISTICS
1037 tprintf("Recording usage statistics\n");
1038 #else
1039 tprintf("No usage statistics are recorded\n");
1040 #endif
1041
1042 tprintf("Mail delivery program: %s\n", REC_MAIL);
1043
1044 #ifdef APPEND_SIGNATURE
1045 tprintf("Query for appending .signature ENABLED\n");
1046 #else
1047 tprintf("Query for appending .signature DISABLED\n");
1048 #endif
1049
1050 tprintf("Max pathname length is %d bytes\n", FILENAME - 1);
1051 }
1052
1053
1054 void
admin_mode(char * input_string)1055 admin_mode(char *input_string)
1056 {
1057 register group_header *gh;
1058 int was_raw = unset_raw();
1059
1060 if (input_string && *input_string) {
1061 pre_input = input_string;
1062 } else {
1063 pre_input = NULL;
1064 tputc(NL);
1065 master_run_check();
1066 }
1067
1068 for (;;) {
1069 switch (get_cmd(
1070 "\nC)onf E)xpire G)roups I)nit L)og M)aster Q)uit S)tat U)pdate V)alidate W)akeup",
1071 "ADMIN")) {
1072
1073 case 'M':
1074 master_admin();
1075 break;
1076
1077 case 'W':
1078 kill_master(0);
1079 break;
1080
1081 case 'E':
1082 if (admin_confirm("Expire All Groups", 1))
1083 send_master(SM_EXPIRE, (group_header *) NULL, SP, 0L);
1084 break;
1085
1086 case 'I':
1087 if (admin_confirm("INITIALIZATION, i.e. recollect all groups", 1))
1088 send_master(SM_RECOLLECT, (group_header *) NULL, SP, 0L);
1089 break;
1090
1091 case 'G':
1092 group_admin((group_header *) NULL);
1093 break;
1094
1095 case 'L':
1096 log_admin();
1097 break;
1098
1099 case 'S':
1100 master_status();
1101 break;
1102
1103 case 'C':
1104 show_config();
1105 break;
1106
1107 case 'U':
1108 update_master();
1109 break;
1110
1111 case 'Z':
1112 verbose2 = 0;
1113
1114 /* FALLTHROUGH */
1115 case 'V':
1116 Loop_Groups_Sorted(gh) {
1117 if (s_hangup || s_keyboard)
1118 break;
1119 validate_group(gh);
1120 }
1121 verbose2 = 1;
1122 break;
1123
1124 case '=':
1125 verbose2 = !verbose2;
1126 break;
1127
1128 case 'Q':
1129 if (was_raw)
1130 nn_raw();
1131 return;
1132 }
1133 }
1134 }
1135