home *** CD-ROM | disk | FTP | other *** search
Wrap
/** pexpire.c **/ /** This program is designed to set the expire dates of newsgroups according to whether they are read locally or not. The idea is that it is easy to go into users .newsrc files and compile an overall list of who reads what groups, then 1-day-expire those groups that no-one is reading. This hinges on the availability of other local machines to serve as archives for various groups, as well as the understanding of the local users that subscribing to a new newsgroup will more than likely net you almost *no* new articles -- but will allow the news to flow in normally and then gradually build up to a more reasonable level. (C) Copyright 1988 Dave Taylor *************************************************************************** ** Permission is granted for unlimited modification, use, and dist- ** ** ribution, except that this software may not be sold for profit ** ** directly nor as part of any software package. This software is made ** ** available with no warranty of any kind, express or implied. ** *************************************************************************** **/ #include <stdio.h> #include <pwd.h> #include "pexpire.h" #define ROOT_UID 0 /* who's root? */ #define MAX_GROUPS 1024 /* should be sufficient */ #define SLEN 128 #define COLON ':' #ifndef TRUE # define TRUE 1 # define FALSE 0 #endif /** some easy to read and use macro functions **/ #define whitespace(c) (c == ' ' || c == '\t') #define matches(re,pat) (regex(re, pat) != NULL) #define plural(n) (n == 1 ? "" : "s") /** and data structures/global variables for the program **/ struct group_rec { char *name; int is_read; #ifndef CNEWS int read_expire; int unread_expire; #endif }; char *login_shells[] = { "/bin/sh", "/bin/csh", "/bin/ksh", "/bin/rsh", "" }; struct group_rec groups[MAX_GROUPS]; int group_count = 0, /** total number of groups **/ verbose = FALSE, /** lots of output? **/ #ifndef CNEWS comma_separated = TRUE, /** output list format **/ groups_per_cmd, /** .. and more too **/ min_history_expire, /** for the expire() cmd **/ max_history_expire, /** " " **/ default_history_expire, /** " " **/ output_one_per_line = FALSE, /** final output format **/ #endif include_root = FALSE; /** include root .newsrc? **/ #ifndef CNEWS char *prog_name, /** program name for errors **/ expire_cmd[SLEN]; /** expire cmd we'll use **/ #else char *prog_name; /** program name for errors **/ double read_retention_period, read_purge_period, read_expirey_period, unread_retention_period, unread_purge_period, unread_expirey_period; #endif /** forward definitions and other stuff to keep LINT a happy clam **/ char *regcmp(), *regex(), *strcpy(), *strcat(), *strchr(), *malloc(); void exit(), perror(), qsort(); /** The algorithm that we'll be using here is: 1. Read in the active file to get a list of all newsgroups available 2. Go through the ``EXPIRE_DEFAULTS'' file (bnews version) to set initial expiration dates (typically by high level groups -- it's left rooted). This file typically has two sets of numbers, the first being for groups that are being actively read, the second being for those that are not. For example: soc.* 24 1 would set any soc.* group to a 24 day expire if read, and a 1 day expire if not. 3. Then, for each user of the system: if they have a .newsrc, tag as 'read' any group that the user subscribes to 4. Sort the newsgroups by expiration date (bnews version), then output a shell script suitable for automatic execution (bnews version) or output an expire list file for expire(1) (cnews version) ... **/ main(argc, argv) char *argv[]; { extern char *optarg; int c; /** first off let's grab the program name for error messages **/ prog_name = *argv; /** initialize some values that can be changed by the user **/ #ifndef CNEWS groups_per_cmd = DEFAULT_GROUPS_PER_LINE; max_history_expire = DEFAULT_MAX_HISTORY_EXPIRE; min_history_expire = DEFAULT_MIN_HISTORY_EXPIRE; default_history_expire = DEFAULT_HISTORY_EXPIRE; (void) strcpy(expire_cmd, EXPIRE); /** now process the starting arguments **/ while ((c = getopt(argc, argv, "a:ch:H:e:g:rov")) != EOF) { #else read_retention_period = READ_RETENTION_DEFAULT; read_purge_period = READ_PURGE_DEFAULT; read_expirey_period = READ_EXPIREY_DEFAULT; unread_retention_period = UNREAD_RETENTION_DEFAULT; unread_purge_period = UNREAD_PURGE_DEFAULT; unread_expirey_period = UNREAD_EXPIREY_DEFAULT; while ((c = getopt(argc, argv, "r:e:p:R:E:P:sv")) != EOF) { #endif switch (c) { #ifndef CNEWS case 'a' : default_history_expire = atoi(optarg); break; case 'c' : comma_separated = TRUE; break; case 'e' : (void) strcpy(expire_cmd, optarg); break; case 'g' : groups_per_cmd = atoi(optarg); break; case 'h' : min_history_expire = atoi(optarg); break; case 'H' : max_history_expire = atoi(optarg); break; case 'r' : include_root = TRUE; break; case 'o' : output_one_per_line = TRUE; break; case 'v' : verbose = TRUE; break; default : (void) fprintf(stderr, "\nUsage: %s [-a n] [-c] [-e cmd] [-g] [-h n] [-H n] [-r] [-o] [-v]\n", prog_name); (void) fprintf(stderr,"\nWhere:\n"); (void) fprintf(stderr, " -a n \tset default history expire value to 'n' (see the man page)\n"); (void) fprintf(stderr, " \t(the current default is set to %d day%s)\n", DEFAULT_HISTORY_EXPIRE, plural(DEFAULT_HISTORY_EXPIRE)); (void) fprintf(stderr, " -c \tmake the groupname list comma separated, rather than space\n"); (void) fprintf(stderr, " -e cmd\tuses 'cmd' for output rather than the default expire program\n"); (void) fprintf(stderr, " \t(current default set to \"%s\")\n", EXPIRE); (void) fprintf(stderr, " -g n \tforces `n' or less groups output per command (default = %d)\n", DEFAULT_GROUPS_PER_LINE); (void) fprintf(stderr, " -h n \tset the default minimum history expire value to 'n'\n"); (void) fprintf(stderr, " \t(the current default is set to %d day%s)\n", DEFAULT_MIN_HISTORY_EXPIRE, plural(DEFAULT_MIN_HISTORY_EXPIRE)); (void) fprintf(stderr, " -H n \tset the default maximum history expire value to 'n'\n"); (void) fprintf(stderr, " \t(the current default is set to %d day%s)\n", DEFAULT_MAX_HISTORY_EXPIRE, plural(DEFAULT_MAX_HISTORY_EXPIRE)); (void) fprintf(stderr, " -r \ttakes UID 0 account .newsrc files into account\n"); (void) fprintf(stderr, " -o \tforces one-group-per-line output format\n"); (void) fprintf(stderr, " -v \tturns on verbose output mode\n\n"); exit(0); #else case 's' : include_root = TRUE; break; case 'v' : verbose = TRUE; break; case 'r' : read_retention_period = atof(optarg); sscanf(optarg,"%lg",&read_retention_period); break; case 'p' : read_purge_period = atof(optarg); sscanf(optarg,"%lg",&read_purge_period); break; case 'e' : read_expirey_period = atof(optarg); sscanf(optarg,"%lg",&read_expirey_period); break; case 'R' : sscanf(optarg,"%lg",&unread_retention_period); break; case 'P' : sscanf(optarg,"%lg",&unread_purge_period); break; case 'E' : sscanf(optarg,"%lg",&unread_expirey_period); break; default : (void) fprintf(stderr, "\nUsage: %s [-r n] [-e n] [-p n] [-R n] [-E n] [-P n] [-s] [-v]\n", prog_name); (void) fprintf(stderr,"\nWhere:\n"); (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); (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); (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); (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); (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); (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); (void) fprintf(stderr, "\t-s\ttakes UID 0 account .newsrc files into account\n"); (void) fprintf(stderr, "\t-v\tturns on verbose output mode\n\n"); exit(0); #endif } } /** next let's read in the netnews active file **/ read_active_file(); /** read in the EXPIRE_DEFAULTS file and set default expires **/ #ifndef CNEWS set_default_expiration_dates(); #endif /** check each user for a .newsrc and mark groups subscribed **/ check_each_user(); /** whip through a quick resort by expiration time **/ #ifndef CNEWS sort_groups_by_expiration(); #endif /** and finally output the script that we can execute **/ output_script(); /** and we're done **/ return(0); } read_active_file() { /** this routine reads in the active file, sorts it, and returns. It is assumed that it always works - if something fails it will exit from here.. **/ int compare(); FILE *fd; char buffer[SLEN]; register int i; if ((fd = fopen(ACTIVE_FILE, "r")) == NULL) { (void) fprintf(stderr,"%s: cannot open active file '%s':\n", prog_name, ACTIVE_FILE); perror("fopen"); exit(1); } while (fgets(buffer, SLEN, fd) != NULL) { /** get just the first word ... **/ for (i=0; ! whitespace(buffer[i]); i++) ; buffer[i] = '\0'; if ((groups[group_count].name = malloc((unsigned)(i+1))) == NULL) { (void) fprintf(stderr,"%s: couldn't malloc memory for group '%s'\n", prog_name, buffer); perror("malloc"); exit(1); } /** now load up the new record and increment our counter **/ (void) strcpy(groups[group_count].name, buffer); groups[group_count].is_read = FALSE; #ifndef CNEWS groups[group_count].read_expire = DEFAULT_READ_EXPIRE; groups[group_count].unread_expire = DEFAULT_UNREAD_EXPIRE; #endif group_count++; /** and on to the next one... **/ } (void) fclose(fd); qsort(groups, (unsigned) group_count, sizeof (struct group_rec), compare); if (verbose) (void) printf("Read %d group%s out of the active file.\n", group_count, plural(group_count)); } #ifndef CNEWS set_default_expiration_dates() { /** this routine is responsible for reading in the default expire file and setting the default expiration dates on all of the groups in memory. If there is no file or it is impossible to get to, then the defaults indicated in this program will be used for all groups. The format of the file is quite simple: <regular expression> < tab > <+expire> <tab> <-expire> where +expire is the expiration time if people are reading the group, and -expire is if they're not. The regular expression format is that of regex(3c), so you can have structures such as "^comp.*" and so on. **/ FILE *fd; char buffer[SLEN], *regular_expression; int read_expire, unread_expire; register int i, count = 0; if ((fd = fopen(EXPIRE_DEFAULTS, "r")) == NULL) { (void) fprintf(stderr,"%s: Couldn't read file '%s'\n", prog_name, EXPIRE_DEFAULTS); (void) fprintf(stderr, "(Using default expirations: read = %d, unread = %d)\n", DEFAULT_READ_EXPIRE, DEFAULT_UNREAD_EXPIRE); perror("fopen"); (void) fprintf(stderr,"---\n"); /** now spin through setting all expire dates accordingly **/ for (i=0; i < group_count; i++) { groups[i].read_expire = DEFAULT_READ_EXPIRE; groups[i].unread_expire = DEFAULT_UNREAD_EXPIRE; } return; } /** if we've gotten here we've got the file open and ready to work with... **/ while (fgets(buffer, SLEN, fd) != NULL) { if (buffer[0] == '#' || strlen(buffer) < 3) continue; (void) sscanf(buffer, "%*s %d %d", &read_expire, &unread_expire); count++; for (i=0;! whitespace(buffer[i]); i++) ; buffer[i] = '\0'; /** now apply this pattern to all groups we've got, setting the expire date as makes sense... **/ regular_expression = regcmp(buffer, (char *) 0); for (i=0 ; i < group_count; i++) if (matches(regular_expression, groups[i].name)) { groups[i].unread_expire = unread_expire; groups[i].read_expire = read_expire; } } (void) fclose(fd); if (verbose) (void) printf("Checked against %d pattern%s in default-expire file.\n", count, plural(count)); } #endif check_each_user() { /** this routine goes through the /etc/passwd file to find all the users on the machine. For each entry found, it will ascertain if they have a login shell then look for a .newsrc file. If they have one, it will extract all the groups that they currently read, marking each in memory as being read .. **/ FILE *fd; struct passwd *getpwent(), *pass; char newsrc[SLEN], buffer[SLEN], user_list[SLEN]; register int i; /** initialize **/ user_list[0] = '\0'; /** and step through the password file .. **/ while ((pass = getpwent()) != NULL) { /*if (has_login_shell(pass->pw_shell)) { */ if (pass->pw_uid == ROOT_UID && ! include_root) continue; (void) sprintf(newsrc, "%s/%s", pass->pw_dir, NEWSRC); if ((fd = fopen(newsrc, "r")) == NULL) continue; if (verbose) (void) printf("Checking against %s for user \"%s\"\n", NEWSRC, pass->pw_name); else { if (user_list[0] != '\0') (void) strcat(user_list, " "); (void) strcat(user_list, pass->pw_name); } while (fgets(buffer, SLEN, fd) != NULL) if (strchr(buffer, COLON) != (char *) NULL) { for (i=0;buffer[i] != COLON; i++); buffer[i] = '\0'; mark_as_read(buffer); } (void) fclose(fd); /*}*/ } if (verbose && strlen(user_list) > 0) (void) fprintf(stderr, "Checked against \"%s\" for the following users:\n\t%s\n", NEWSRC, user_list); } #ifndef CNEWS sort_groups_by_expiration() { /** We now resort the list according to the expiration date of the group... **/ int compare_expirations(); qsort(groups, (unsigned) group_count, sizeof (struct group_rec), compare_expirations); } #endif output_script() { /** Now that we've gotten the groups sorted by their expiration date, we can output a script that is suitable for input to the real netnews expire() program... **/ register int i; #ifndef CNEWS int current_expire_time = 0, expire, groups_on_line = 0, on_line = 0, in_expiration = 0; /** set the current expiration time, then: for each group that has the same date output the group name when we hit a new date output the new format line **/ for (i=0; i < group_count; i++) { /** set the expiration time based on if the group is currently being read or not... **/ expire = groups[i].is_read ? groups[i].read_expire : groups[i].unread_expire; if (output_one_per_line) { if (expire > max_history_expire) (void) printf("%s -e %d -E %d -n %s\n", expire_cmd, expire, expire, groups[i].name); else if (expire < min_history_expire) (void) printf("%s -e %d -E %d -n %s\n", expire_cmd, expire, default_history_expire, groups[i].name); else (void) printf("%s -e %d -n %s\n", expire_cmd, expire, groups[i].name); } else { if ( expire != current_expire_time || in_expiration > groups_per_cmd) { if (expire > max_history_expire) (void) printf("\n%s -e %d -E %d -n ", expire_cmd, expire, expire); else if (expire < min_history_expire) (void) printf("\n%s -e %d -E %d -n ", expire_cmd, expire, default_history_expire); else (void) printf("\n%s -e %d -n ", expire_cmd, expire); groups_on_line = 0; current_expire_time = expire; in_expiration = 0; } in_expiration++; on_line += strlen(groups[i].name) + 1; if (on_line > 66) { (void) printf("%c \\\n\t", groups_on_line > 0? ',':' '); on_line = 8 + strlen(groups[i].name); groups_on_line = 0; } if (groups_on_line) (void) printf("%c%s", comma_separated? ',' : ' ', groups[i].name); else (void) printf("%s", groups[i].name); groups_on_line++; } } (void) printf("\n"); #else for (i=0; i < group_count; i++) { /** set the expiration time based on if the group is currently being read or not... **/ if (groups[i].is_read) (void) printf("%s\tx\t%g-%g-%g\t-\n",groups[i].name, read_retention_period, read_expirey_period, read_purge_period); else (void) printf("%s\tx\t%g-%g-%g\t-\n",groups[i].name, unread_retention_period, unread_expirey_period, unread_purge_period); } #endif } int compare(a,b) struct group_rec *a, *b; { /** strcmp() routine for our data structure, rather than the simple expedient of just using strcmp directly. See the invocation of qsort() above **/ return( strcmp(a->name, b->name) ); } #ifndef CNEWS int compare_expirations(a, b) struct group_rec *a, *b; { /** strcmp() routine for data for second sort -- this one is a sort by the expiration date of the groups. To do this we want to look at the is_read flag and from that decide which of the two expiration dates we want to be looking at. **/ return ( (b->is_read ? b->read_expire : b->unread_expire) - (a->is_read ? a->read_expire : a->unread_expire) ); } #endif int has_login_shell(shell_name) char *shell_name; { /** returns TRUE iff the shell given is contained in the list of possible login shells compiled with. **/ register int i; for (i=0; login_shells[i][0] != '\0'; i++) if (strcmp(login_shells[i], shell_name) == 0) return(TRUE); return(FALSE); } mark_as_read(name) char *name; { /** Mark the group specified as being read -- it's extracted from a users .newsrc file. **/ int index; if ((index = find_group(name)) == -1) (void) fprintf(stderr, "** Couldn't find group '%s' in internal tables?? **\n", name); else groups[index].is_read = TRUE; } int find_group(name) char *name; { /** A binary search of the list to find the group - returns the index into the 'groups' array of the group, or '-1' if not in the list. **/ register int first = 0, last, middle, difference; last = group_count-1; while (first <= last) { middle = ((first+last) / 2); difference = strcmp(name, groups[middle].name); if (difference < 0) last = middle - 1; else if (difference == 0) return(middle); else first = middle + 1; } return(-1); }