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