home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / nn.tar / nn-6.5.1 / admin.c < prev    next >
C/C++ Source or Header  |  1996-08-11  |  24KB  |  1,090 lines

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