ftp.nice.ch/pub/next/unix/network/news/nn.6.4.16.s.tar.gz#/nn/admin.c

This is admin.c in view mode; [Download] [Up]

/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 *	nnadmin - nn administator program
 */

#include <signal.h>
#include <errno.h>
#include "config.h"
#include "db.h"
#include "proto.h"
#include "term.h"

import char
    *master_directory, *db_directory, *db_data_directory,
    *bin_directory, *news_directory, *news_lib_directory,
    *log_file, *news_active, *pager,
    group_path_name[];

import char *exec_chdir_to;

import int db_data_subdirs;

static char *pre_input;
static int verbose = 1;

extern int group_completion();

extern group_header *lookup_regexp();

static get_cmd(prompt1, prompt2)
char *prompt1, *prompt2;
{
    char c;

    if (s_hangup) {
	printf("\nnnmaster hangup\n");
	nn_exit(0);
    }

    s_keyboard = 0;

    if (pre_input) {
	if ((c = *pre_input++) == NUL)
	    nn_exit(0);
    } else {
	do {
	    if (prompt1) printf("\r%s\n", prompt1);
	    if (prompt2) printf("\r%s >>>", prompt2);
	    fl;
	    raw();
	    c = get_c();
	    unset_raw();
	    if (c == K_interrupt)
		s_keyboard++;
	    else
	    if (c == '!') {
		putchar(NL);
		if (exec_chdir_to != NULL)
		    printf("\n\rDirectory: %s", exec_chdir_to);
		run_shell((char *)NULL, 0, 0);
	    } else
		printf("%c\n\n\r", c);
	} while (c == '!');
    }

    if (islower(c))
	c = toupper(c);

    return c;
}


static long get_entry(prompt_str, min_val, max_val)
char *prompt_str;
long min_val, max_val;
{
    char buf[100];
    long val;

 loop:

    printf("%s %ld..%ld (or all): ", prompt_str, min_val, max_val);
    fl;
    gets(buf);
    if (buf[0] == 'a' || buf[0] == NUL)
	return -1L;

    val =  atol(buf);
    if (val < min_val || val > max_val) goto loop;

    return val;
}


static admin_confirm(action, must_confirm)
char *action;
{
    char buffer[100];

    if (pre_input && !must_confirm) return 1;

    sprintf(buffer, "Confirm %s  Y)es N)o", action);

    return get_cmd((char *)NULL, buffer) == 'Y';
}

static char *get_groupname()
{
    char * groupname;

    raw();
    printf("\n\n\r");
    prompt_line = Lines - 2;
    prompt("Group: ");
    fl;
    groupname = get_s(NONE, NONE, NONE, group_completion);
    unset_raw();

    putchar(NL); putchar(CR);

    if (groupname == NULL) return NULL;

    if (groupname[0]) return groupname;

    if (current_group == NULL) return NULL;
    if (current_group->group_flag & G_FOLDER) return NULL;

    return current_group->group_name;
}


static update_master()
{
    register group_header *gh;
    int ngp;

    if (who_am_i != I_AM_ADMIN) {
	printf("Can only perform (U)pdate as nnadmin\n");
	return;
    }

    ngp = master.number_of_groups;

    open_master(OPEN_READ);

    if (master.number_of_groups != ngp) {
	printf("\nNumber of groups changed from %d to %d\n",
	       ngp, master.number_of_groups);
    }

    ngp = 0;

    Loop_Groups_Header(gh)
	if (update_group(gh) == -1) ngp++;

    if (ngp) printf("There are %d blocked groups\n", ngp);
}

static find_files(gh)
group_header *gh;
{
    char command[512], name[FILENAME], *db_data_path();

    if (gh == NULL) {
	if (db_data_directory == NULL) {
	    printf("Cannot list all files (they are scattered over the news partition)\n");
	    return;
	}
	if (db_data_subdirs)
	    sprintf(command, "cd %s ; ls -l [0-9] | %s", db_data_directory, pager);
	else
	sprintf(command, "ls -l %s | %s", db_data_directory, pager);
    } else
	sprintf(command, "ls -l %s", db_data_path(name, gh, '*'));
    system(command);
}

static master_status()
{
    int cur_group, nblocked, nignored;
    long articles, disk_use;
    register group_header *gh;

    printf("\nMaster:\n");
    printf("   initialized:   %s\n", date_time(master.db_created));
    printf("   last_scan:     %s\n", date_time(master.last_scan));
    printf("   last_size:     %ld\n", (long)master.last_size);
    printf("   no of groups:  %d\n", master.number_of_groups);

    articles = disk_use = 0;
    nblocked = nignored = 0;

    Loop_Groups_Header(gh) {

#define DISK_BLOCKS(bytes) (((bytes) + 1023) / 1024)

	disk_use += DISK_BLOCKS(gh->index_write_offset);
	disk_use += DISK_BLOCKS(gh->data_write_offset);

	articles += gh->last_db_article - gh->first_db_article + 1;

	if (gh->master_flag & M_BLOCKED) nblocked++;
	if (gh->master_flag & M_IGNORE_GROUP) nignored++;
    }

    printf("\n   Articles:   %ld\n", articles);
    printf(  "   Disk usage: %ld k\n\n", disk_use);

    if (nblocked) printf("Blocked groups: %3d\n", nblocked);
    if (nignored) printf("Ignored groups: %3d\n", nignored);
}

static dump_g_flag(gh)
register group_header *gh;
{
    printf("Flags: ");
    if (gh->master_flag & M_BLOCKED) 	printf(" BLOCKED");
    if (gh->master_flag & M_EXPIRE) 	printf(" EXPIRE");
    if (gh->master_flag & M_MODERATED) 	printf(" MODERATED");
    if (gh->master_flag & M_CONTROL) 	printf(" CONTROL");
    if (gh->master_flag & M_NO_DIRECTORY) printf(" NO_DIRECTORY");
    if (gh->master_flag & M_ALWAYS_DIGEST) printf(" ALWAYS_DIGEST");
    if (gh->master_flag & M_NEVER_DIGEST) printf(" NEVER_DIGEST");
    if (gh->master_flag & M_INCLUDE_OLD) printf(" INCL_OLD");
    if (gh->master_flag & M_AUTO_RECOLLECT) printf(" RECOLLECT");
    if (gh->master_flag & M_AUTO_ARCHIVE) printf(" ARCHIVE");
    if (gh->master_flag & M_IGNORE_GROUP) printf(" IGNORE");
    if (gh->master_flag & M_VALID) printf(" VALID");
    printf("\n");
}

static dump_m_entry(gh)
register group_header *gh;
{
    update_group(gh);

    printf("\n%s\t%d\n", gh->group_name, gh->group_num);
    printf("first/last art: %06ld %06d\n",
	   gh->first_db_article, gh->last_db_article);
    printf("   active info: %06ld %06d\n",
	   gh->first_a_article, gh->last_a_article);
    printf("Offsets: index->%ld, data->%ld\n",
	   gh->index_write_offset,
	   gh->data_write_offset);
    if (gh->master_flag & M_AUTO_ARCHIVE)
	printf("Archive file: %s\n", gh->archive_file);
    if (gh->master_flag)
	dump_g_flag(gh);
}

#define valerr( xxx , tp) { \
    if (verbose) { printf xxx ; fl; } \
    err_type = tp; \
    goto err; \
}

static validate_group(gh)
group_header *gh;
{
    FILE *data, *ix;
    off_t data_offset, next_offset;
    cross_post_number cross_post;
    article_number cur_article;
    int n, err_type;

    data = ix = NULL;

    if (gh->master_flag & M_IGNORE_GROUP) return 1;

    if (gh->first_db_article == (gh->last_db_article + 1)
	&& gh->index_write_offset == 0)
	return 1;

    if (verbose) { printf("\r%s: ", gh->group_name); clrline(); }

    /* init_group returns ok for gh == current_group before check on NO_DIR */
    if ((gh->master_flag & M_NO_DIRECTORY) || init_group(gh) <= 0) {
	if (verbose) printf("NO DIRECTORY (ok)");
	return 1; /* no directory/ignored */
    }

    update_group(gh);

    if (gh->master_flag & M_BLOCKED) {
	if (verbose) printf("BLOCKED (ok)\n");
	return 1;
    }

    /*
     *	Check for major inconcistencies.
     *	Sometimes, news expire will reset article numbers
     *	to start from 0 again
     */

    if (gh->last_db_article == 0) {
	return 1;
    }

#ifdef RENUMBER_DANGER
    if (gh->first_a_article > (gh->last_db_article + 1) ||
	gh->last_db_article  > gh->last_a_article ||
	gh->first_db_article > gh->first_a_article) {

	if (verbose)
	    printf("RENUMBERING OF ARTICLES (active=%ld..%ld master=%ld..%ld)",
		   gh->first_a_article, gh->last_a_article,
		   gh->first_db_article, gh->last_db_article);
	err_type = 99;
	goto err;
    }
#endif

    ix = open_data_file(gh, 'x', OPEN_READ);
    if (ix == NULL) valerr(("NO INDEX FILE"), 1);

    data = open_data_file(gh, 'd', OPEN_READ);
    if (data == NULL) valerr(("NO DATA FILE"), 2);

    cur_article = gh->first_db_article - 1;

    while (cur_article <= gh->last_db_article) {
	if (s_hangup || s_keyboard) goto out;

	data_offset = ftell(data);

	switch (db_read_art(data)) {
	 case 0:
	    if (data_offset == gh->data_write_offset) goto out;
	    valerr(("No header for article # %ld", (long)cur_article+1), 3);
	 case 1:
	    break;
	 case -1:
	    valerr(("END OF FILE on DATA FILE"), 4);
	}

	if (db_hdr.dh_number <= 0 || cur_article > db_hdr.dh_number)
	    valerr(("OUT OF SEQUENCE: %ld after %ld", (long)db_hdr.dh_number, (long)cur_article), 11);

	if (cur_article < db_hdr.dh_number) {
	    if (db_data.dh_type == DH_SUB_DIGEST)
		valerr(("NO DIGEST HEADER: %ld", (long)db_hdr.dh_number), 5);

	    do {
		cur_article++;
		if (!db_read_offset(ix, &next_offset))
		    valerr(("NO INDEX FOR ARTICLE %ld", (long)cur_article), 6);

		if (data_offset != next_offset)
		    valerr(("OFFSET ERROR: %ld: %ld != %ld", (long)cur_article, (long)data_offset, (long)next_offset), 7);
	    } while (cur_article < db_hdr.dh_number);
	}

	for (n = 0; n < db_hdr.dh_cross_postings; n++) {
	    cross_post = NETW_CROSS_INT(db_data.dh_cross[n]);
	    if (cross_post < master.number_of_groups) continue;
	    valerr(("CROSS POST RANGE ERROR: %ld (article # %ld)", (long)cross_post, (long)cur_article), 8);
	}
    }

 out:
    if (!s_keyboard && !s_hangup) {
	data_offset = ftell(data);
	if (data_offset != gh->data_write_offset)
	    valerr(("DATA OFFSET %ld != %ld", (long)gh->data_write_offset, (long)data_offset), 9);

	while (++cur_article <= gh->last_db_article) {
	    if (!db_read_offset(ix, &next_offset))
		valerr(("NO INDEX FOR ARTICLE %ld", (long)cur_article), 12);
	    if (data_offset != next_offset)
		valerr(("OFFSET ERROR: %ld: %ld != %ld", (long)cur_article, (long)data_offset, (long)next_offset), 13);
	}

	data_offset = ftell(ix);
	if (data_offset != gh->index_write_offset)
	    valerr(("INDEX OFFSET %ld != %ld", (long)gh->index_write_offset, (long)data_offset), 10);
    }

    fclose(data);
    fclose(ix);
    if (verbose) printf("OK");
    return 1;

 err:
    if (data != NULL) fclose(data);
    if (ix != NULL) fclose(ix);
    log_entry('V', "%s: database error %d", gh->group_name, err_type);
    if (verbose) {
	putchar(NL);
	dump_m_entry(gh);
    }

    if (!pre_input) {
	ding();

	for (;;) {
	    switch (get_cmd((char *)NULL,
"\nRepair group   Y)es N)o E)nter Q)uit")) {
	     case 'Y':
		break;
	     case 'N':
		return 0;
	     case 'Q':
		s_keyboard++;
		return 0;
	     case 'E':
		group_admin(gh);
		continue;
	     default:
		continue;
	    }
	    break;
	}
    }

    send_master(SM_RECOLLECT, gh, SP, 0L);
    return 0;
}

static dump_group(gh)
group_header *gh;
{
    FILE *data, *ix;
    off_t offset;
    cross_post_number cross_post;
    article_number first_article;
    int n;

    if (init_group(gh) <= 0) {
	printf("cannot access group %s\n", gh->group_name);
	return 0;
    }

    update_group(gh);

    if (gh->index_write_offset == 0) {
	printf("group %s is empty\n", gh->group_name);
	return 1;
    }
    
    first_article = get_entry("First article",
			      (long)gh->first_db_article,
			      (long)gh->last_db_article);

    if (first_article < 0) first_article = gh->first_db_article;
    if (first_article <= 0) first_article = 1;

    ix = open_data_file(gh, 'x', OPEN_READ);
    data = open_data_file(gh, 'd', OPEN_READ);
    if (ix == NULL || data == NULL) goto err;

    fseek(ix, get_index_offset(gh, first_article), 0);
    if (!db_read_offset(ix, &offset)) goto err;
    fseek(data, offset, 0);

    clrdisp();
    pg_init(1, 1);

    for (;;) {
	if (s_hangup || s_keyboard) break;

	if (pg_scroll(6)) {
	    s_keyboard = 1;
	    break;
	}

	offset = ftell(data);

	switch (db_read_art(data)) {
	 case 0:
	    goto out;
	 case 1:
	    break;
	 case -1:
	    goto err;
	}

	printf("\noffset = %ld, article # = %ld",
	       (long)offset, (long)(db_hdr.dh_number));

	switch (db_data.dh_type) {
	 case DH_DIGEST_HEADER:
	    printf(" (digest header)\n");
	    break;
	 case DH_SUB_DIGEST:
	    printf(" (digest sub-article)\n");
	    break;
	 case DH_NORMAL:
	    putchar(NL);
	    break;
	}

	if (db_hdr.dh_cross_postings) {
	    printf("xpost(%d):", db_hdr.dh_cross_postings);

	    for (n = 0; n < db_hdr.dh_cross_postings; n++) {
		cross_post = NETW_CROSS_INT(db_data.dh_cross[n]);
		printf(" %d", cross_post);
	    }
	    putchar(NL);
	}

	printf("ts=%lu hp=%ld, fp=+%d, lp=%ld, ref=%d%s, lines=%d\n",
	       (long unsigned)db_hdr.dh_date, (long)db_hdr.dh_hpos,
	       (int)db_hdr.dh_fpos, (long)db_hdr.dh_lpos,
	       db_hdr.dh_replies & 0x7f,
	       (db_hdr.dh_replies & 0x80) ? "+Re" : "",
	       db_hdr.dh_lines);

	if (db_hdr.dh_sender_length)
	    printf("Sender(%d): %s\n", db_hdr.dh_sender_length, db_data.dh_sender);
	else
	    printf("No sender\n");

	if (db_hdr.dh_subject_length)
	    printf("Subj(%d): %s\n", db_hdr.dh_subject_length, db_data.dh_subject);
	else
	    printf("No subject\n");
    }

 out:
    if (!s_keyboard && !s_hangup && ftell(data) != gh->data_write_offset)
	goto err;

    fclose(data);
    fclose(ix);
    return 1;

 err:
    if (data != NULL) fclose(data);
    if (ix != NULL) fclose(ix);
    if (gh->master_flag & M_IGNORE_GROUP) return 0;
    printf("\n*** DATABASE INCONSISTENCY DETECTED - run V)alidate\n");
    return 0;
}

static kill_master(term)
int term;
{
    switch (proto_lock(I_AM_MASTER, term ? PL_TERMINATE : PL_WAKEUP_SOFT)) {
     case 1:
	if (verbose)
	    printf("sent %s signal to master\n", term ? "termination" : "wakeup");
	break;
     case 0:
	printf("cannot signal master\n");
	break;
     case -1:
	if (verbose) printf("master is not running\n");
	break;
    }
}

static master_run_check()
{
    extern char proto_host[];
    int running = proto_lock(I_AM_MASTER, PL_CHECK) >= 0;
    
    if (!verbose && pre_input != NULL)
	exit(running ? 0 : 1);

    if (running)
	printf("Master is running%s%s\n",
	       proto_host[0] ? " on host " : "", proto_host);
    else
	printf("Master is NOT running");
}

static master_admin()
{
    register char c;
    int cur_group;
    long value;
    register group_header *gh;

    exec_chdir_to = db_directory;

    for (;;) {
	switch (c = get_cmd(
"\nC)heck D)ump F)iles G)roup K)ill O)ptions S)tat T)race",
"MASTER")) {

	 case 'C':
	    master_run_check();
	    break;

	 case 'G':
	    cur_group = (int)get_entry("Group number",
				  0L, (long)(master.number_of_groups - 1));
	    if (cur_group >= 0)
		dump_m_entry(ACTIVE_GROUP(cur_group));
	    break;

	 case 'D':
	    do {
		c = get_cmd(
"\nA)ll B)locked E)mpty H)oles I)gnored N)on-empty V)alid in(W)alid",
"DUMP");
		pg_init(1, 1);

		Loop_Groups_Header(gh) {
		    if (s_keyboard || s_hangup) break;
#define NODUMP(cond) if (cond) continue; break
		    switch (c) {
		     case 'N':
			NODUMP(gh->index_write_offset == 0);
		     case 'E':
			NODUMP(gh->index_write_offset != 0);
		     case 'H':
			NODUMP(gh->first_a_article >= gh->first_db_article);
		     case 'B':
			NODUMP((gh->master_flag & M_BLOCKED) == 0);
		     case 'I':
			NODUMP((gh->master_flag & M_IGNORE_GROUP) == 0);
		     case 'V':
			NODUMP((gh->master_flag & M_VALID) == 0);
		     case 'W':
			NODUMP(gh->master_flag & M_VALID);
		     case 'A':
			break;
		     default:
			s_keyboard = 1; continue;
		    }

		    if (pg_scroll(6)) break;
		    dump_m_entry(gh);
		}
	    } while (!s_keyboard && !s_hangup);
	    break;

	 case 'F':
	    find_files((group_header *)NULL);
	    break;

	 case 'O':
	    c = get_cmd("r)epeat_delay  e)xpire_level", "OPTION");
	    if (c != 'R' && c != 'E') break;
	    value = get_entry("Option value", 1L, 10000L);
	    if (value < 0) break;
	    send_master(SM_SET_OPTION, (group_header *)NULL, tolower(c), value);
	    break;

	 case 'S':
	    master_status();
	    break;

	 case 'T':
	    send_master(SM_SET_OPTION, (group_header *)NULL, 't', -1L);
	    break;

	 case 'K':
	    if (admin_confirm("Stop nn Master", 0))
		kill_master(1);
	    break;

	 default:
	    exec_chdir_to = NULL;
	    return;
	}
    }
}


static log_admin()
{
    char command[FILENAME + 100], c;

    if (pre_input && *pre_input == NUL) {
	c = SP;
	goto log_tail;
    }

 loop:

    c = get_cmd(
"\n1-9)tail A)dm C)ollect E)rr N)ntp G)roup R)eport e(X)pire .)all @)clean",
"LOG");

    if (c < SP || c == 'Q') return;

    if (c == '@') {
	if (admin_confirm("Truncation", 0)) {
	    sprintf(command, "%s.old", log_file);
	    unlink(command);
	    if (link(log_file, command) < 0) goto tr_failed;
	    if (unlink(log_file) < 0) {
		unlink(command);
		goto tr_failed;
	    }
	    log_entry('A', "Log Truncated");
	    chmod(log_file, 0666);
	}
	return;

     tr_failed:
	printf("Truncation failed -- check permissions etc.\n");
	goto loop;
    }

    if (c == 'G') {
	char *groupname;

	if ((groupname = get_groupname()) == NULL) goto loop;
	sprintf(command, "fgrep '%s' %s | %s",
		groupname, log_file, pager);
	system(command);

	goto loop;
    }

 log_tail:
    if (c == '$' || c == SP || isdigit(c)) {
	int n;

	n = isdigit(c) ? 10 * (c - '0') : 10;
	sprintf(command, "tail -%d %s", n, log_file);
	system(command);
	goto loop;
    }

    if (c == '*') {
	c = '.';
    }

    sprintf(command, "grep '^%c:' %s | %s", c, log_file, pager);
    system(command);

    goto loop;
}


static flag_admin(gh, mode_str, set_flag)
group_header *gh;
char *mode_str;
int set_flag;
{
    char buffer[50];
    int new_flag = 0;

    putchar(NL);

    dump_g_flag(gh);

    sprintf(buffer, "%s FLAG", mode_str);

    switch (get_cmd(
"\nA)lways_digest N)ever_digest M)oderated C)ontrol no_(D)ir",
buffer)) {

     default:
	return;

     case 'M':
	new_flag = M_MODERATED;
	break;

     case 'C':
	new_flag = M_CONTROL;
	break;

     case 'D':
	new_flag = M_NO_DIRECTORY;
	break;

     case 'A':
	new_flag = M_ALWAYS_DIGEST;
	break;

     case 'N':
	new_flag = M_NEVER_DIGEST;
	break;
    }

    if (new_flag & (M_CONTROL | M_NO_DIRECTORY))
	if (!admin_confirm("Flag Change", 0))
	    new_flag = 0;

    if (new_flag) {
	if (set_flag) {
	    if (gh->master_flag & new_flag)
		new_flag = 0;
	    else {
		send_master(SM_SET_FLAG, gh, 's', (long)new_flag);
		gh->master_flag |= new_flag;
	    }
	} else {
	    if ((gh->master_flag & new_flag) == 0)
		new_flag = 0;
	    else {
		send_master(SM_SET_FLAG, gh, 'c', (long)new_flag);
		gh->master_flag &= ~new_flag;
	    }
	}
    }

    if (new_flag == 0)
	printf("NO CHANGE\n");
    else
	dump_g_flag(gh);
}


static rmgroup(gh)
group_header *gh;
{
    char command[FILENAME*2];
    char *rmprog;

    if (user_id != 0 && !file_exist(news_active, "w")) {
	printf("Not privileged to run rmgroup\n");
	return;
    }

#ifdef RMGROUP_PATH
    rmprog = RMGROUP_PATH;
#else
    rmprog = "rmgroup";
#endif
    if (*rmprog != '/') rmprog = relative(news_lib_directory, rmprog);
    if (file_exist(rmprog, "x")) goto rm_ok;
#ifndef RMGROUP_PATH
    rmprog = relative(news_lib_directory, "delgroup");
    if (file_exist(rmprog, "x")) goto rm_ok;
#endif
    printf("Program %s not found\n", rmprog);
    return;
 rm_ok:
    sprintf(command, "%s %s", rmprog, gh->group_name);
    system(command);
    any_key(0);
    gh->master_flag &= ~M_VALID;		/* just for nnadmin */
    gh->master_flag |= M_IGNORE_A;
}

static group_admin(gh)
register group_header *gh;
{
    char *groupname, gbuf[FILENAME], dirbuf[FILENAME];

    if (gh != NULL) goto have_group;

 new_group:
    if ((groupname = get_groupname()) == NULL) return;

    if ((gh = lookup_regexp(groupname, "Enter", 0)) == NULL)
	goto new_group;

 have_group:
    if (!use_nntp && init_group(gh)) {
	strcpy(dirbuf, group_path_name);
	dirbuf[strlen(dirbuf)-1] = NUL;
	exec_chdir_to = dirbuf;
    }

    sprintf(gbuf, "GROUP %s", gh->group_name);

    for (;;) {
	switch (get_cmd(
"\nD)ata E)xpire F)iles G)roup H)eader R)ecollect V)alidate Z)ap",
gbuf)) {
	 case 'D':
	    dump_group(gh);
	    break;

	 case 'V':
	    verbose = 1;
	    validate_group(gh);
	    putchar(NL);
	    break;

	 case 'H':
	    dump_m_entry(gh);
	    break;

	 case 'F':
	    find_files(gh);
	    break;

	 case 'S':
	    flag_admin(gh, "Set", 1);
	    break;

	 case 'C':
	    flag_admin(gh, "Clear", 0);
	    break;

	 case 'R':
	    if (admin_confirm("Recollection of Group", 0))
		send_master(SM_RECOLLECT, gh, SP, 0L);
	    break;

	 case 'Z':
	    if (admin_confirm("Remove Group (run rmgroup)", 1))
		rmgroup(gh);
	    break;

	 case 'E':
	    if (admin_confirm("Expire Group", 0))
		send_master(SM_EXPIRE, gh, SP, 0L);
	    break;

	 case 'G':
	    goto new_group;

	 default:
	    exec_chdir_to = NULL;
	    return;
	}
    }
}


static show_config()
{
#ifdef NNTP
    extern char nntp_server[];
#endif

    printf("\nConfiguration:\n\n");
    printf("BIN:  %s\nLIB:  %s\nNEWS SPOOL: %s\nNEWS LIB: %s\n",
	   bin_directory,
	   lib_directory,
	   news_directory,
	   news_lib_directory);

    printf("DB:   %s\nDATA: ", db_directory);
    if (db_data_directory != NULL) {
#ifdef DB_LONG_NAMES
	printf("%s/{group.name}.[dx]\n", db_data_directory);
#else
	printf("%s%s/{group-number}.[dx]\n", db_data_directory,
	       db_data_subdirs ? "/[0-9]" : "");
#endif
    } else {
	printf("%s/{group/name/path}/.nn[dx]\n", news_directory);
    }

    printf("ACTIVE: %s\n", news_active);

#ifdef NNTP
    if (use_nntp)
	printf("NNTP ACTIVE. server=%s\n", nntp_server);
    else
	printf("NNTP NOT ACTIVE\n");
#endif

#ifdef NETWORK_DATABASE
    printf("Database is machine independent (network format).\n");
#ifdef NETWORK_BYTE_ORDER
    printf("Local system assumes to use network byte order\n");
#endif
#else
    printf("Database format is machine dependent (byte order and alignment)\n");
#endif
    printf("Database version: %d\n", NNDB_MAGIC & 0xff);

#ifdef STATISTICS
    printf("Recording usage statistics\n");
#else
    printf("No usage statistics are recorded\n");
#endif

    printf("Mail delivery program: %s\n", REC_MAIL);
#ifdef APPEND_SIGNATURE
    printf("Query for appending .signature ENABLED\n");
#else
    printf("Query for appending .signature DISABLED\n");
#endif

    printf("Max pathname length is %d bytes\n", FILENAME-1);
}


admin_mode(input_string)
char *input_string;
{
    register group_header *gh;
    int was_raw = unset_raw();

    if (input_string && *input_string) {
	pre_input = input_string;
    } else {
	pre_input = NULL;
	putchar(NL);
	master_run_check();
    }

    for (;;) {
	switch(get_cmd(
"\nC)onf E)xpire G)roups I)nit L)og M)aster Q)uit S)tat U)pdate V)alidate W)akeup",
"ADMIN")) {

	 case 'M':
	    master_admin();
	    break;

	 case 'W':
	    kill_master(0);
	    break;

	 case 'E':
	    if (admin_confirm("Expire All Groups", 1))
		send_master(SM_EXPIRE, (group_header *)NULL, SP, 0L);
	    break;

	 case 'I':
	    if (admin_confirm("INITIALIZATION, i.e. recollect all groups", 1))
		send_master(SM_RECOLLECT, (group_header *)NULL, SP, 0L);
	    break;

	 case 'G':
	    group_admin((group_header *)NULL);
	    break;

	 case 'L':
	    log_admin();
	    break;

	 case 'S':
	    master_status();
	    break;

	 case 'C':
	    show_config();
	    break;

	 case 'U':
	    update_master();
	    break;

	 case 'Z':
	    verbose = 0;
	    /* FALL THRU */

	 case 'V':
	    Loop_Groups_Sorted(gh) {
		if (s_hangup || s_keyboard) break;
		validate_group(gh);
	    }
	    verbose = 1;
	    break;

	 case '=':
	    verbose = !verbose;
	    break;

	 case 'Q':
	    if (was_raw) raw();
	    return;
	}
    }
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.