home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / misc / volume9 / cn_pexpire / pexpire.c < prev    next >
C/C++ Source or Header  |  1990-01-04  |  21KB  |  679 lines

  1. /**                pexpire.c            **/
  2.  
  3. /** This program is designed to set the expire dates of newsgroups according
  4.     to whether they are read locally or not.  The idea is that it is easy
  5.     to go into users .newsrc files and compile an overall list of who reads
  6.     what groups, then 1-day-expire those groups that no-one is reading.
  7.  
  8.     This hinges on the availability of other local machines to serve as
  9.     archives for various groups, as well as the understanding of the 
  10.     local users that subscribing to a new newsgroup will more than likely
  11.     net you almost *no* new articles -- but will allow the news to flow
  12.     in normally and then gradually build up to a more reasonable level.
  13.  
  14.     (C) Copyright 1988 Dave Taylor
  15.  
  16.     ***************************************************************************
  17.     **  Permission is granted for unlimited modification, use, and dist-     **
  18.     **  ribution, except that this software may not be sold for profit       **
  19.     **  directly nor as part of any software package.  This software is made **
  20.     **  available with no warranty of any kind, express or implied.          **
  21.     ***************************************************************************
  22.  
  23. **/
  24.  
  25. #include <stdio.h>
  26. #include <pwd.h>
  27.  
  28. #include "pexpire.h"
  29.  
  30. #define ROOT_UID    0                 /* who's root? */
  31.  
  32. #define MAX_GROUPS    1024            /* should be sufficient */
  33.  
  34. #define SLEN        128
  35.  
  36. #define COLON        ':'
  37.  
  38. #ifndef TRUE
  39. # define TRUE        1
  40. # define FALSE        0
  41. #endif
  42.  
  43. /** some easy to read and use macro functions **/
  44.  
  45. #define whitespace(c)    (c == ' ' || c == '\t')
  46. #define matches(re,pat)    (regex(re, pat) != NULL)
  47.  
  48. #define plural(n)    (n == 1 ? "" : "s")
  49.  
  50. /** and data structures/global variables for the program **/
  51.  
  52. struct group_rec {
  53.     char     *name;
  54.     int      is_read;
  55. #ifndef CNEWS
  56.     int      read_expire;
  57.     int      unread_expire;
  58. #endif
  59.        };
  60.  
  61. char *login_shells[] = { "/bin/sh", "/bin/csh", "/bin/ksh", "/bin/rsh", "" };
  62.  
  63. struct group_rec groups[MAX_GROUPS];
  64.  
  65. int group_count = 0,                /** total number of groups  **/
  66.     verbose = FALSE,                /** lots of output?         **/
  67. #ifndef CNEWS
  68.     comma_separated = TRUE,         /** output list format        **/
  69.     groups_per_cmd,                 /**  .. and more too        **/
  70.  
  71.     min_history_expire,             /** for the expire() cmd    **/
  72.     max_history_expire,             /**     "   "               **/
  73.     default_history_expire,         /**     "   "            **/
  74.  
  75.     output_one_per_line = FALSE,    /** final output format     **/
  76. #endif
  77.     include_root = FALSE;           /** include root .newsrc?   **/
  78.  
  79. #ifndef CNEWS
  80. char *prog_name,                    /** program name for errors **/
  81.      expire_cmd[SLEN];              /** expire cmd we'll use    **/
  82. #else
  83. char *prog_name;                    /** program name for errors **/
  84. double    read_retention_period,
  85.     read_purge_period,
  86.     read_expirey_period,
  87.     unread_retention_period,
  88.     unread_purge_period,
  89.     unread_expirey_period;
  90. #endif
  91.  
  92. /** forward definitions and other stuff to keep LINT a happy clam   **/
  93.  
  94. char *regcmp(), *regex(), *strcpy(), *strcat(), *strchr(), *malloc();
  95. void    exit(), perror(), qsort();
  96.  
  97. /** The algorithm that we'll be using here is:
  98.  
  99.      1. Read in the active file to get a list of all newsgroups available
  100.  
  101.      2. Go through the ``EXPIRE_DEFAULTS'' file (bnews version) to set initial
  102.         expiration dates (typically by high level groups -- it's left rooted).
  103.         This file typically has two sets of numbers, the first being for groups
  104.         that are being actively read, the second being for those that are
  105.         not.  For example:
  106.  
  107.              soc.*    24    1
  108.  
  109.         would set any soc.* group to a 24 day expire if read, and a 1 day
  110.         expire if not.
  111.  
  112.      3. Then, for each user of the system:
  113.  
  114.             if they have a .newsrc,
  115.             tag as 'read' any group that the user subscribes to
  116.      
  117.      4. Sort the newsgroups by expiration date (bnews version), then output a
  118.         shell script suitable for automatic execution (bnews version) or output
  119.         an expire list file for expire(1) (cnews version) ...
  120.  
  121. **/
  122.  
  123. main(argc, argv)
  124. char *argv[];
  125. {
  126.     extern char *optarg;
  127.     int c;
  128.  
  129.     /** first off let's grab the program name for error messages **/
  130.  
  131.     prog_name = *argv;
  132.  
  133.     /** initialize some values that can be changed by the user **/
  134. #ifndef CNEWS
  135.     groups_per_cmd = DEFAULT_GROUPS_PER_LINE;
  136.  
  137.     max_history_expire = DEFAULT_MAX_HISTORY_EXPIRE;
  138.     min_history_expire = DEFAULT_MIN_HISTORY_EXPIRE;
  139.  
  140.     default_history_expire = DEFAULT_HISTORY_EXPIRE;
  141.  
  142.     (void) strcpy(expire_cmd, EXPIRE);
  143.  
  144.     /** now process the starting arguments **/
  145.  
  146.     while ((c = getopt(argc, argv, "a:ch:H:e:g:rov")) != EOF) {
  147. #else
  148.     read_retention_period = READ_RETENTION_DEFAULT;
  149.     read_purge_period = READ_PURGE_DEFAULT;
  150.     read_expirey_period = READ_EXPIREY_DEFAULT;
  151.     unread_retention_period = UNREAD_RETENTION_DEFAULT;
  152.     unread_purge_period = UNREAD_PURGE_DEFAULT;
  153.     unread_expirey_period = UNREAD_EXPIREY_DEFAULT;
  154.     while ((c = getopt(argc, argv, "r:e:p:R:E:P:sv")) != EOF) {
  155. #endif
  156.       switch (c) {
  157.  
  158. #ifndef CNEWS
  159.         case 'a' : default_history_expire = atoi(optarg);
  160.                break;
  161.  
  162.         case 'c' : comma_separated = TRUE;
  163.                break;
  164.  
  165.         case 'e' : (void) strcpy(expire_cmd, optarg);    
  166.                break;
  167.  
  168.         case 'g' : groups_per_cmd = atoi(optarg);
  169.                break;
  170.  
  171.         case 'h' : min_history_expire = atoi(optarg);
  172.                break;
  173.  
  174.         case 'H' : max_history_expire = atoi(optarg);
  175.                break;
  176.  
  177.         case 'r' : include_root = TRUE;
  178.                break;
  179.  
  180.         case 'o' : output_one_per_line = TRUE;
  181.                break;
  182.  
  183.         case 'v' : verbose = TRUE;
  184.                break;
  185.  
  186.         default  : (void) fprintf(stderr,
  187.    "\nUsage: %s [-a n] [-c] [-e cmd] [-g] [-h n] [-H n] [-r] [-o] [-v]\n", 
  188.                   prog_name);
  189.                (void) fprintf(stderr,"\nWhere:\n");
  190.                (void) fprintf(stderr,
  191.    "   -a n  \tset default history expire value to 'n' (see the man page)\n");
  192.                (void) fprintf(stderr,
  193.    "         \t(the current default is set to %d day%s)\n", 
  194.                       DEFAULT_HISTORY_EXPIRE, 
  195.                   plural(DEFAULT_HISTORY_EXPIRE));
  196.                (void) fprintf(stderr,
  197.    "   -c    \tmake the groupname list comma separated, rather than space\n");
  198.                (void) fprintf(stderr,
  199.    "   -e cmd\tuses 'cmd' for output rather than the default expire program\n");
  200.                (void) fprintf(stderr,
  201.    "         \t(current default set to \"%s\")\n", EXPIRE);
  202.                (void) fprintf(stderr,
  203.    "   -g n  \tforces `n' or less groups output per command (default = %d)\n",
  204.                   DEFAULT_GROUPS_PER_LINE);
  205.                (void) fprintf(stderr,
  206.    "   -h n  \tset the default minimum history expire value to 'n'\n");
  207.                (void) fprintf(stderr,
  208.    "         \t(the current default is set to %d day%s)\n", 
  209.                       DEFAULT_MIN_HISTORY_EXPIRE, 
  210.                   plural(DEFAULT_MIN_HISTORY_EXPIRE));
  211.                (void) fprintf(stderr,
  212.    "   -H n  \tset the default maximum history expire value to 'n'\n");
  213.                (void) fprintf(stderr,
  214.    "         \t(the current default is set to %d day%s)\n", 
  215.                       DEFAULT_MAX_HISTORY_EXPIRE, 
  216.                   plural(DEFAULT_MAX_HISTORY_EXPIRE));
  217.                (void) fprintf(stderr,
  218.    "   -r    \ttakes UID 0 account .newsrc files into account\n");
  219.                (void) fprintf(stderr,
  220.    "   -o    \tforces one-group-per-line output format\n");
  221.                (void) fprintf(stderr,
  222.    "   -v    \tturns on verbose output mode\n\n");
  223.                exit(0);
  224. #else
  225.         case 's' : include_root = TRUE;
  226.                break;
  227.  
  228.         case 'v' : verbose = TRUE;
  229.                break;
  230.  
  231.         case 'r' : read_retention_period = atof(optarg);
  232.                sscanf(optarg,"%lg",&read_retention_period);
  233.                break;
  234.  
  235.         case 'p' : read_purge_period = atof(optarg);
  236.                sscanf(optarg,"%lg",&read_purge_period);
  237.                break;
  238.  
  239.         case 'e' : read_expirey_period = atof(optarg);
  240.                sscanf(optarg,"%lg",&read_expirey_period);
  241.                break;
  242.  
  243.         case 'R' :
  244.                sscanf(optarg,"%lg",&unread_retention_period);
  245.                break;
  246.  
  247.         case 'P' :
  248.                sscanf(optarg,"%lg",&unread_purge_period);
  249.                break;
  250.  
  251.         case 'E' :
  252.                sscanf(optarg,"%lg",&unread_expirey_period);
  253.                break;
  254.  
  255.         default  : (void) fprintf(stderr, "\nUsage: %s [-r n] [-e n] [-p n] [-R n] [-E n] [-P n] [-s] [-v]\n", prog_name);
  256.                (void) fprintf(stderr,"\nWhere:\n");
  257.                (void) fprintf(stderr, "\t-r n\tset retention period for read news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", READ_RETENTION_DEFAULT);
  258.                (void) fprintf(stderr, "\t-e n\tset expirey period for read news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", READ_EXPIREY_DEFAULT);
  259.                (void) fprintf(stderr, "\t-p n\tset purge period for read news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", READ_PURGE_DEFAULT);
  260.                (void) fprintf(stderr, "\t-R n\tset retention period for unread news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", UNREAD_RETENTION_DEFAULT);
  261.                (void) fprintf(stderr, "\t-E n\tset expirey period for unread news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", UNREAD_EXPIREY_DEFAULT);
  262.                (void) fprintf(stderr, "\t-P n\tset purge period for unread news groups to 'n',\n\t\twhere 'n' may be a decimal fraction (default = %g).\n", UNREAD_PURGE_DEFAULT);
  263.                (void) fprintf(stderr, "\t-s\ttakes UID 0 account .newsrc files into account\n");
  264.                (void) fprintf(stderr, "\t-v\tturns on verbose output mode\n\n");
  265.                exit(0);
  266. #endif
  267.       }
  268.     }
  269.     
  270.     /** next let's read in the netnews active file **/
  271.  
  272.     read_active_file();
  273.  
  274.     /** read in the EXPIRE_DEFAULTS file and set default expires **/
  275. #ifndef CNEWS
  276.     set_default_expiration_dates();
  277. #endif
  278.     /** check each user for a .newsrc and mark groups subscribed **/
  279.  
  280.     check_each_user();
  281.  
  282.     /** whip through a quick resort by expiration time **/
  283. #ifndef CNEWS
  284.     sort_groups_by_expiration();
  285. #endif
  286.     /** and finally output the script that we can execute **/
  287.  
  288.     output_script();
  289.  
  290.     /** and we're done **/
  291.  
  292.     return(0);
  293. }
  294.  
  295. read_active_file()
  296. {
  297.     /** this routine reads in the active file, sorts it, and 
  298.         returns.  It is assumed that it always works - if something
  299.         fails it will exit from here..
  300.     **/
  301.  
  302.     int  compare();
  303.  
  304.     FILE *fd;
  305.     char buffer[SLEN];
  306.     register int  i;
  307.     
  308.     if ((fd = fopen(ACTIVE_FILE, "r")) == NULL) {
  309.       (void) fprintf(stderr,"%s: cannot open active file '%s':\n",
  310.           prog_name, ACTIVE_FILE);
  311.       perror("fopen");
  312.       exit(1);
  313.     }
  314.  
  315.     while (fgets(buffer, SLEN, fd) != NULL) {
  316.  
  317.       /** get just the first word ... **/ 
  318.  
  319.       for (i=0; ! whitespace(buffer[i]); i++) ;
  320.       buffer[i] = '\0';
  321.  
  322.       if ((groups[group_count].name = malloc((unsigned)(i+1))) == NULL) {
  323.          (void) fprintf(stderr,"%s: couldn't malloc memory for group '%s'\n",
  324.             prog_name, buffer);
  325.         perror("malloc");
  326.         exit(1);
  327.       }
  328.  
  329.       /** now load up the new record and increment our counter **/
  330.  
  331.       (void) strcpy(groups[group_count].name, buffer);
  332.       groups[group_count].is_read = FALSE;
  333. #ifndef CNEWS
  334.       groups[group_count].read_expire = DEFAULT_READ_EXPIRE;
  335.       groups[group_count].unread_expire = DEFAULT_UNREAD_EXPIRE;
  336. #endif
  337.  
  338.       group_count++;
  339.  
  340.       /** and on to the next one... **/
  341.     }
  342.     
  343.     (void) fclose(fd);
  344.  
  345.     qsort(groups, (unsigned) group_count, 
  346.           sizeof (struct group_rec), compare);
  347.  
  348.     if (verbose)
  349.       (void) printf("Read %d group%s out of the active file.\n",
  350.           group_count, plural(group_count)); 
  351. }
  352.  
  353. #ifndef CNEWS
  354. set_default_expiration_dates()
  355. {
  356.     /** this routine is responsible for reading in the default
  357.         expire file and setting the default expiration dates on
  358.         all of the groups in memory.   If there is no file or it
  359.         is impossible to get to, then the defaults indicated in
  360.         this program will be used for all groups.
  361.  
  362.         The format of the file is quite simple:
  363.  
  364.             <regular expression> < tab > <+expire> <tab> <-expire>
  365.  
  366.         where +expire is the expiration time if people are reading
  367.         the group, and -expire is if they're not.  The regular
  368.         expression format is that of regex(3c), so you can have
  369.         structures such as "^comp.*" and so on.
  370.     **/
  371.  
  372.     FILE *fd;
  373.     char  buffer[SLEN], *regular_expression;
  374.     int   read_expire, unread_expire;
  375.     register int i, count = 0;
  376.  
  377.     if ((fd = fopen(EXPIRE_DEFAULTS, "r")) == NULL) {
  378.       (void) fprintf(stderr,"%s: Couldn't read file '%s'\n", 
  379.           prog_name, EXPIRE_DEFAULTS);
  380.       (void) fprintf(stderr,
  381.           "(Using default expirations: read = %d, unread = %d)\n",
  382.           DEFAULT_READ_EXPIRE, DEFAULT_UNREAD_EXPIRE);
  383.       perror("fopen");
  384.       (void) fprintf(stderr,"---\n");
  385.  
  386.       /** now spin through setting all expire dates accordingly **/
  387.  
  388.       for (i=0; i < group_count; i++) {
  389.         groups[i].read_expire = DEFAULT_READ_EXPIRE;
  390.         groups[i].unread_expire = DEFAULT_UNREAD_EXPIRE;
  391.       }
  392.  
  393.       return;
  394.     }
  395.  
  396.     /** if we've gotten here we've got the file open and ready
  397.         to work with... **/
  398.  
  399.     while (fgets(buffer, SLEN, fd) != NULL) {
  400.  
  401.        if (buffer[0] == '#' || strlen(buffer) < 3) continue;
  402.  
  403.       (void) sscanf(buffer, "%*s %d %d", &read_expire, &unread_expire);
  404.  
  405.       count++;
  406.  
  407.       for (i=0;! whitespace(buffer[i]); i++) ; 
  408.       buffer[i] = '\0';
  409.  
  410.       /** now apply this pattern to all groups we've got, setting
  411.           the expire date as makes sense... **/
  412.  
  413.       regular_expression = regcmp(buffer, (char *) 0);
  414.  
  415.       for (i=0 ; i < group_count; i++)
  416.         if (matches(regular_expression, groups[i].name)) {
  417.           groups[i].unread_expire = unread_expire;
  418.           groups[i].read_expire = read_expire;
  419.         }
  420.     }
  421.  
  422.     (void) fclose(fd);
  423.  
  424.     if (verbose)
  425.      (void) printf("Checked against %d pattern%s in default-expire file.\n",
  426.          count, plural(count)); 
  427. }
  428. #endif
  429.  
  430. check_each_user()
  431. {
  432.     /** this routine goes through the /etc/passwd file to
  433.         find all the users on the machine.  For each entry
  434.         found, it will ascertain if they have a login shell
  435.         then look for a .newsrc file.  If they have one, it
  436.         will extract all the groups that they currently read,
  437.         marking each in memory as being read ..
  438.     **/
  439.  
  440.     FILE   *fd;
  441.     struct passwd    *getpwent(), *pass;
  442.     char   newsrc[SLEN], buffer[SLEN], user_list[SLEN];
  443.     register int i;
  444.  
  445.     /** initialize **/
  446.  
  447.     user_list[0] = '\0';
  448.  
  449.     /** and step through the password file .. **/
  450.  
  451.     while ((pass = getpwent()) != NULL) {
  452.       /*if (has_login_shell(pass->pw_shell)) { */
  453.  
  454.         if (pass->pw_uid == ROOT_UID && ! include_root)
  455.           continue;
  456.  
  457.         (void) sprintf(newsrc, "%s/%s", pass->pw_dir, NEWSRC);
  458.         
  459.         if ((fd = fopen(newsrc, "r")) == NULL) continue;
  460.  
  461.         if (verbose)
  462.           (void) printf("Checking against %s for user \"%s\"\n", 
  463.              NEWSRC, pass->pw_name);
  464.         else {
  465.           if (user_list[0] != '\0') (void) strcat(user_list, " ");
  466.           (void) strcat(user_list, pass->pw_name);
  467.         }
  468.  
  469.         while (fgets(buffer, SLEN, fd) != NULL)
  470.           if (strchr(buffer, COLON) != (char *) NULL) {
  471.             for (i=0;buffer[i] != COLON; i++);
  472.         buffer[i] = '\0';
  473.             mark_as_read(buffer);
  474.           } 
  475.  
  476.         (void) fclose(fd);
  477.       /*}*/
  478.     }
  479.  
  480.     if (verbose && strlen(user_list) > 0) 
  481.       (void) fprintf(stderr, 
  482.           "Checked against \"%s\" for the following users:\n\t%s\n",
  483.           NEWSRC, user_list);
  484. }
  485.  
  486. #ifndef CNEWS
  487. sort_groups_by_expiration()
  488. {
  489.     /** We now resort the list according to the expiration date of
  490.         the group... 
  491.     **/
  492.  
  493.     int compare_expirations();
  494.  
  495.     qsort(groups, (unsigned) group_count, sizeof (struct group_rec), 
  496.           compare_expirations);
  497. }
  498. #endif
  499.  
  500. output_script()
  501. {
  502.     /** Now that we've gotten the groups sorted by their
  503.         expiration date, we can output a script that is suitable 
  504.         for input to the real netnews expire() program...
  505.     **/
  506.     
  507.     register int i; 
  508. #ifndef CNEWS
  509.     int current_expire_time = 0, expire, 
  510.         groups_on_line = 0, on_line = 0, in_expiration = 0;
  511.  
  512.     /** set the current expiration time, then:
  513.           for each group that has the same date
  514.           output the group name
  515.         when we hit a new date output the new format line
  516.     **/
  517.  
  518.     for (i=0; i < group_count; i++) {
  519.  
  520.       /** set the expiration time based on if the group is 
  521.           currently being read or not... 
  522.       **/
  523.  
  524.       expire = groups[i].is_read ? groups[i].read_expire : 
  525.              groups[i].unread_expire;
  526.  
  527.       if (output_one_per_line) {
  528.         if (expire > max_history_expire)
  529.            (void) printf("%s -e %d -E %d -n %s\n", 
  530.               expire_cmd, expire, expire, groups[i].name);
  531.          else if (expire < min_history_expire)
  532.            (void) printf("%s -e %d -E %d -n %s\n", 
  533.               expire_cmd, expire, default_history_expire, 
  534.               groups[i].name);
  535.         else
  536.            (void) printf("%s -e %d -n %s\n", 
  537.               expire_cmd, expire, groups[i].name);
  538.       }
  539.       else {
  540.         if ( expire != current_expire_time || 
  541.          in_expiration > groups_per_cmd) {
  542.           if (expire > max_history_expire)
  543.             (void) printf("\n%s -e %d -E %d -n ", 
  544.                   expire_cmd, expire, expire);
  545.            else if (expire < min_history_expire)
  546.             (void) printf("\n%s -e %d -E %d -n ", 
  547.                   expire_cmd, expire, default_history_expire);
  548.           else
  549.             (void) printf("\n%s -e %d -n ", expire_cmd, expire);
  550.           groups_on_line = 0;
  551.           current_expire_time = expire;
  552.           in_expiration = 0;
  553.         }
  554.  
  555.         in_expiration++;
  556.  
  557.         on_line += strlen(groups[i].name) + 1;
  558.         
  559.         if (on_line > 66) {
  560.           (void) printf("%c \\\n\t", groups_on_line > 0? ',':' ');
  561.           on_line = 8 + strlen(groups[i].name);
  562.           groups_on_line = 0;
  563.         }
  564.  
  565.         if (groups_on_line)
  566.           (void) printf("%c%s", comma_separated? ',' : ' ', groups[i].name);
  567.         else
  568.           (void) printf("%s", groups[i].name);
  569.  
  570.         groups_on_line++;
  571.       }
  572.     }
  573.     (void) printf("\n");
  574. #else
  575.     for (i=0; i < group_count; i++) {
  576.  
  577.       /** set the expiration time based on if the group is 
  578.           currently being read or not... 
  579.       **/
  580.  
  581.       if (groups[i].is_read)
  582.            (void) printf("%s\tx\t%g-%g-%g\t-\n",groups[i].name, read_retention_period, read_expirey_period, read_purge_period);
  583.       else
  584.            (void) printf("%s\tx\t%g-%g-%g\t-\n",groups[i].name, unread_retention_period, unread_expirey_period, unread_purge_period);
  585.     }
  586. #endif
  587. }
  588.  
  589. int
  590. compare(a,b)
  591. struct group_rec *a, *b;
  592. {
  593.     /** strcmp() routine for our data structure, rather than the
  594.         simple expedient of just using strcmp directly.  See the
  595.         invocation of qsort() above
  596.     **/
  597.  
  598.     return( strcmp(a->name, b->name) );
  599. }
  600.  
  601. #ifndef CNEWS
  602. int 
  603. compare_expirations(a, b)
  604. struct group_rec *a, *b;
  605. {
  606.     /** strcmp() routine for data for second sort -- this one
  607.         is a sort by the expiration date of the groups.  To
  608.         do this we want to look at the is_read flag and from
  609.         that decide which of the two expiration dates we want to be 
  610.         looking at.
  611.     **/
  612.  
  613.     return ( (b->is_read ? b->read_expire : b->unread_expire) - 
  614.          (a->is_read ? a->read_expire : a->unread_expire) );
  615. }
  616. #endif
  617.  
  618. int
  619. has_login_shell(shell_name)
  620. char *shell_name;
  621. {
  622.     /** returns TRUE iff the shell given is contained in the
  623.         list of possible login shells compiled with.
  624.     **/
  625.  
  626.     register int i;
  627.  
  628.     for (i=0; login_shells[i][0] != '\0'; i++)
  629.       if (strcmp(login_shells[i], shell_name) == 0) return(TRUE);
  630.     
  631.     return(FALSE);
  632. }
  633.       
  634. mark_as_read(name)
  635. char *name;
  636. {
  637.     /** Mark the group specified as being read -- it's extracted
  638.         from a users .newsrc file.
  639.     **/
  640.  
  641.     int index;
  642.  
  643.     if ((index = find_group(name)) == -1)
  644.       (void) fprintf(stderr, 
  645.           "** Couldn't find group '%s' in internal tables?? **\n",
  646.           name);
  647.     else
  648.       groups[index].is_read = TRUE;
  649. }
  650.  
  651. int
  652. find_group(name)
  653. char *name;
  654. {
  655.     /** A binary search of the list to find the group - returns the
  656.         index into the 'groups' array of the group, or '-1' if not
  657.         in the list.
  658.      **/
  659.  
  660.     register int first = 0, last, middle, difference;
  661.  
  662.     last = group_count-1;
  663.  
  664.     while (first <= last) {
  665.           middle = ((first+last) / 2);
  666.  
  667.           difference = strcmp(name, groups[middle].name);
  668.  
  669.           if (difference < 0)
  670.             last = middle - 1;
  671.           else if (difference == 0)
  672.             return(middle);
  673.           else
  674.             first = middle + 1;
  675.         }
  676.  
  677.         return(-1);
  678. }
  679.