home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / unix_c / mail / digest.c < prev    next >
Encoding:
C/C++ Source or Header  |  1989-03-21  |  18.2 KB  |  806 lines

  1. /*
  2.  * digest - create digests of mail messages
  3.  *
  4.  * This program uses the file "digest.info" to figure out what issue
  5.  * of the digest it is making.  The format of this file is:
  6.  *
  7.  *    Name of the List        # leave out the word "digest"
  8.  *    Host                # the host where the digest lives
  9.  *    From                # who sends the digest out
  10.  *    To                # who the list is sent to
  11.  *    Volume                # Volume XX : Issue XXX
  12.  *    Date                # Day, dd Mon yy hh:mm:ss ZZZ
  13.  *
  14.  * As an example:
  15.  *
  16.  *    Foobar
  17.  *    intrepid.ecn.purdue.edu
  18.  *    Dave Curry (The Moderator) <Foobar-Digest@intrepid.ecn.purdue.edu>
  19.  *    Foobar-List@intrepid.ecn.purdue.edu
  20.  *    Volume 1 : Issue 0
  21.  *    Mon,  4 Jan 88 20:15:33 EST
  22.  *
  23.  * Make sure the "From" line includes a legitimate RFC 822 mail address.
  24.  * Make sure the issue number starts at zero; it gets incremented BEFORE
  25.  * generating each digest.  Volume numbers must be incremented by hand.
  26.  * The "digest.info" file gets modified by the program after generation
  27.  * of each digest.
  28.  *
  29.  * The contents of the file "digest.head", if it exists, will be placed
  30.  * between the list of today's topics and the top of the digest.  This
  31.  * can be used to put information about where to FTP archives from, etc.
  32.  *
  33.  * The file "digest.input" should contain a set of mail messages in the
  34.  * format of a UNIX mailbox.  These messages will be read into memory,
  35.  * and a list of "Today's Topics" generated from the subject lines.  The
  36.  * messages will then be sorted so that all the messages on the same topic
  37.  * come out together in the digest.  Any message whost first word in the
  38.  * subject line is "Administrivia" will be guaranteed to come out first
  39.  * in the digest.
  40.  *
  41.  * The digest will be left in the file "digest.output".  You can send it
  42.  * using the command "/usr/lib/sendmail -t < digest.output".
  43.  *
  44.  * I suggest creating the following mail aliases in /usr/lib/aliases:
  45.  *
  46.  *    1. Foobar-Digest:/path/to/the/digest.input/file
  47.  *        This file must be world-writable for sendmail to modify it.
  48.  *        This is the address to publish for people to send digest
  49.  *        submissions to.
  50.  *    2. Foobar-Digest-Request:yourlogin
  51.  *        This is the address for people to use to ask to be added
  52.  *        or deleted from the list.
  53.  *    3. Foobar-List: :include:/path/to/list/of/recipients
  54.  *        This is the list of people who receive the digest.  It should
  55.  *        be a list of addresses of the format:
  56.  *
  57.  *            name, name, name, name,
  58.  *                name, name, name
  59.  *
  60.  *        Continuation lines should start with whitespace.
  61.  *
  62.  * There is one problem with the sorting of messages by subject line to get
  63.  * all the same topic together.  The code handles elimination of "Re:"
  64.  * strings, but if someone changes the subject on you, then things get ugly.
  65.  * This shouldn't happen too often, though.
  66.  *
  67.  * Special thanks to Jon Solomon who sent me his TELECOM digest generating
  68.  * program.  I swiped a lot of ideas from it in writing this one.
  69.  *
  70.  * David A. Curry
  71.  * davy@intrepid.ecn.purdue.edu
  72.  */
  73. #include <sys/types.h>
  74. #include <sys/timeb.h>
  75. #include <sys/time.h>
  76. #include <ctype.h>
  77. #include <stdio.h>
  78.  
  79. #define HEAD1        27        /* Field width of first third    */
  80. #define HEAD2        20        /* Field width of second third    */
  81. #define HEAD3        21        /* Field width of last third    */
  82. #define DATELEN        14        /* Amount of date to put in hdr    */
  83. #define LINELEN        70        /* Length of an average line    */
  84. #define MAXMSGS        64        /* Maximum number of msgs/digest*/
  85. #define LINESIZE    256        /* Maximum line size        */
  86. #define LISTINFO    "digest.info"    /* Information file name    */
  87. #define LISTHEAD    "digest.head"    /* Header for top of digest    */
  88. #define LISTINPUT    "digest.input"    /* Input file name        */
  89. #define LISTOUTPUT    "digest.output"    /* Output file name        */
  90.  
  91. /*
  92.  * Message structure.  We read through the input file and fill one of
  93.  * these in for each message.  The To, Cc, From, Date, and Subject
  94.  * point to the fields of the same names from the message.  The
  95.  * "sortstring" is a copy of the subject string with all whitespace
  96.  * deleted and all letters in lower case.  The messageaddr is the
  97.  * seek position in the file where the message body starts, and
  98.  * messagelength is how long the message is.
  99.  */
  100. struct message {
  101.     char *To;
  102.     char *Cc;
  103.     char *From;
  104.     char *Date;
  105.     char *Subject;
  106.     char *sortstring;
  107.     long messageaddr;
  108.     long messagelength;
  109. } messages[MAXMSGS];
  110.  
  111. /*
  112.  * List structure.  Contains the information from the LISTINFO file.
  113.  */
  114. struct listinfo {
  115.     char *Title;
  116.     char *Host;
  117.     char *From;
  118.     char *To;
  119.     char *Volline;
  120.     char *Dateline;
  121. } listinfo;
  122.  
  123. FILE *input;
  124. FILE *output;
  125.  
  126. int issue_number;            /* The number of this issue    */
  127. int nmessages = 0;            /* Number of messages        */
  128. int digestsize = 0;            /* Size of digest in bytes    */
  129.  
  130. char *index(), *malloc(), *safter(), *nospace(), *getline();
  131.  
  132. main()
  133. {
  134.     /*
  135.      * Read the list information file and update the
  136.      * issue number and date strings.
  137.      */
  138.     get_list_info();
  139.     inc_volume_and_date();
  140.  
  141.     printf("Assembling %s Digest %s (%.*s)\n", listinfo.Title, listinfo.Volline, DATELEN, listinfo.Dateline);
  142.     printf("Scanning and sorting messages for topic lines.\n");
  143.  
  144.     /*
  145.      * Scan the message file for subject strings and
  146.      * sort the messages to get all the messages for
  147.      * each topic next to each other.
  148.      */
  149.     scan_messages();
  150.     sort_messages();
  151.  
  152.     printf("Writing %s Digest to \"%s\"\n", listinfo.Title, LISTOUTPUT);
  153.  
  154.     /*
  155.      * Print the digest header, put the messages
  156.      * in the digest.
  157.      */
  158.     do_digest_header();
  159.     read_messages();
  160.  
  161.     printf("The digest is %d characters long in %d messages.\n", digestsize, nmessages);
  162.  
  163.     /*
  164.      * Put out the new list information.
  165.      */
  166.     put_list_info();
  167. }
  168.  
  169. /*
  170.  * get_list_info - reads in the LISTINFO file.
  171.  */
  172. get_list_info()
  173. {
  174.     FILE *fp;
  175.     int incomplete;
  176.  
  177.     if ((fp = fopen(LISTINFO, "r")) == NULL) {
  178.         printf("digest: cannot open \"%s\" for reading.\n", LISTINFO);
  179.         exit(1);
  180.     }
  181.  
  182.     incomplete = 0;
  183.  
  184.     if ((listinfo.Title = getline(fp)) == NULL)
  185.         incomplete++;
  186.     if ((listinfo.Host = getline(fp)) == NULL)
  187.         incomplete++;
  188.     if ((listinfo.From = getline(fp)) == NULL)
  189.         incomplete++;
  190.     if ((listinfo.To = getline(fp)) == NULL)
  191.         incomplete++;
  192.     if ((listinfo.Volline = getline(fp)) == NULL)
  193.         incomplete++;
  194.     if ((listinfo.Dateline = getline(fp)) == NULL)
  195.         incomplete++;
  196.  
  197.     fclose(fp);
  198.  
  199.     /*
  200.      * Error-check.  Not too sophisicated, but then you're
  201.      * supposed to know what you're doing anyway.
  202.      */
  203.     if (incomplete) {
  204.         printf("digest: incomplete or badly formatted \"%s\" file.\n", LISTINFO);
  205.         printf("Proper format:\n");
  206.         printf("\tTitle\n\tHost\n\tFrom\n\tTo\n\tVolline\n\tDateline\n");
  207.         exit(1);
  208.     }
  209. }
  210.  
  211. /*
  212.  * inc_volume_and_date - update the volume/issue string and get a new date.
  213.  */
  214. inc_volume_and_date()
  215. {
  216.     char *msgdate();
  217.     register char *volline, *colon;
  218.  
  219.     if ((volline = malloc(strlen(listinfo.Volline)+1)) == NULL) {
  220.         printf("digest: out of memory.\n");
  221.         exit(1);
  222.     }
  223.  
  224.     /*
  225.      * Volume numbers get changed by hand.
  226.      */
  227.     issue_number = atoi(safter(listinfo.Volline, " Issue ")) + 1;
  228.  
  229.     if ((colon = index(listinfo.Volline, ':')) != NULL)
  230.         *colon = NULL;
  231.  
  232.     sprintf(volline, "%s: Issue %3d", listinfo.Volline, issue_number);
  233.     strcpy(listinfo.Volline, volline);
  234.  
  235.     /*
  236.      * Get a new date.
  237.      */
  238.     listinfo.Dateline = msgdate();
  239.  
  240.     free(volline);
  241. }
  242.  
  243. /*
  244.  * msgdate - produce a new date string.  Format is
  245.  *
  246.  *        Day, dd Mon yy hh:mm:ss tzn
  247.  */
  248. char *msgdate()
  249. {
  250.     char *timezone();
  251.     struct timeb tbuf;
  252.     register struct tm *t;
  253.     struct tm *localtime();
  254.     static char datebuf[64];
  255.     char *days = "SunMonTueWedThuFriSat";
  256.     char *months = "JanFebMarAprMayJunJulAugSepOctNovDec";
  257.  
  258.     ftime(&tbuf);
  259.     t = localtime(&(tbuf.time));
  260.  
  261.     sprintf(datebuf, "%3.3s, %2d %3.3s %02d %02d:%02d:%02d %3.3s",
  262.             &days[3 * t->tm_wday], t->tm_mday,
  263.             &months[3 * t->tm_mon],    t->tm_year, t->tm_hour,
  264.             t->tm_min, t->tm_sec, timezone(tbuf.timezone, t->tm_isdst));
  265.  
  266.     return(datebuf);
  267. }
  268.  
  269. /*
  270.  * getline - read a line into a dynamically allocated buffer.
  271.  */
  272. char *getline(fp)
  273. FILE *fp;
  274. {
  275.     register int c;
  276.     register char *str, *str_begin;
  277.  
  278.     if ((str = malloc(LINESIZE)) == NULL) {
  279.         printf("digest: out of memory.\n");
  280.         exit(1);
  281.     }
  282.  
  283.     str_begin = str;
  284.  
  285.     while (((str - str_begin) < (LINESIZE - 1)) &&
  286.            ((c = getc(fp)) != '\n') && (c != EOF))
  287.         *str++ = c;
  288.     *str++ = NULL;
  289.  
  290.     if (c == EOF)
  291.         return(NULL);
  292.  
  293.     return(str_begin);
  294. }
  295.  
  296. /*
  297.  * scan_messages - scans through LISTINPUT reading in header fields
  298.  *           and marking the beginning and ending of messages.
  299.  *
  300.  * NOTE: some of the code here depends on the UNIX mail header format.
  301.  *       This format simply guarantees that the first line of a message's
  302.  *     header will be "From blah-blah-blah".  Note there is no colon
  303.  *     (`:') on the "From", the real "From:" line is farther down in
  304.  *     the headers.
  305.  */
  306. scan_messages()
  307. {
  308.     register long n;
  309.     register char *s;
  310.  
  311.     if ((input = fopen(LISTINPUT, "r")) == NULL) {
  312.         printf("digest: cannot open \"%s\" for reading.\n", LISTINPUT);
  313.         exit(1);
  314.     }
  315.  
  316.     /*
  317.      * We break out of this from inside.
  318.      */
  319.     for (;;) {
  320.         if (nmessages >= MAXMSGS) {
  321.             printf("digest: too many messages.\n");
  322.             exit(1);
  323.         }
  324.  
  325.         /*
  326.          * Find the start of the next message.
  327.          */
  328.         do {
  329.             /*
  330.              * If we hit EOF, mark the length of the
  331.              * previous message and go back.
  332.              */
  333.             if ((s = getline(input)) == NULL) {
  334.                 n = ftell(input);
  335.                 n = n - messages[nmessages - 1].messageaddr;
  336.                 messages[nmessages - 1].messagelength = n;
  337.                 return;
  338.             }
  339.         } while (strncmp(s, "From ", 5) != 0);
  340.  
  341.         /*
  342.          * If we have found another message, mark the
  343.          * length of the previous message.
  344.          */
  345.         if (nmessages) {
  346.             n = ftell(input);
  347.             n = n - (strlen(s) + 1);
  348.             n = n - messages[nmessages - 1].messageaddr;
  349.             messages[nmessages - 1].messagelength = n;
  350.         }
  351.  
  352.         /*
  353.          * Read in the headers.
  354.          */
  355.         for (;;) {
  356.             /*
  357.              * We shouldn't hit EOF here, we should
  358.              * at least finish the headers first.
  359.              */
  360.             if ((s = getline(input)) == NULL) {
  361.                 printf("digest: \"%s\": unexpected EOF.\n", LISTINPUT);
  362.                 exit(1);
  363.             }
  364.  
  365.             /*
  366.              * Blank line terminates headers.
  367.              */
  368.             if (*s == NULL)
  369.                 break;
  370.  
  371.             /*
  372.              * Save certain headers.  We strip the
  373.              * header name and leading whitespace.
  374.              */
  375.             if (strncmp(s, "To:", 3) == 0) {
  376.                 messages[nmessages].To = nospace(safter(s, "To:"));
  377.             }
  378.             else if (strncmp(s, "Cc:", 3) == 0) {
  379.                 messages[nmessages].Cc = nospace(safter(s, "Cc:"));
  380.             }
  381.             else if (strncmp(s, "From:", 5) == 0) {
  382.                 messages[nmessages].From = nospace(safter(s, "From:"));
  383.             }
  384.             else if (strncmp(s, "Date:", 5) == 0) {
  385.                 messages[nmessages].Date = nospace(safter(s, "Date:"));
  386.             }
  387.             else if (strncmp(s, "Subject:", 8) == 0) {
  388.                 s = nospace(safter(s, "Subject:"));
  389.  
  390.                 /*
  391.                  * We don't need the "Re:" stuff.
  392.                  */
  393.                 if ((strncmp(s, "re:", 3) == 0) || (strncmp(s, "Re:", 3) == 0) ||
  394.                     (strncmp(s, "RE:", 3) == 0) || (strncmp(s, "rE:", 3) == 0))
  395.                         s += 3;
  396.  
  397.                 messages[nmessages].Subject = nospace(s);
  398.             }
  399.             else {
  400.                 /*
  401.                  * If we aren't saving this line,
  402.                  * give the memory back.
  403.                  */
  404.                 free(s);
  405.             }
  406.         }
  407.  
  408.         /*
  409.          * The message starts after the header.
  410.          */
  411.         messages[nmessages].messageaddr = ftell(input);
  412.         nmessages++;
  413.     }
  414. }
  415.  
  416. /*
  417.  * sort_messages - convert each message's subject line to a string
  418.  *           all in lower case with no whitespace.  Then sort
  419.  *           the messages on this string.  This will group
  420.  *           all the messages on the same subject together.
  421.  */
  422. sort_messages()
  423. {
  424.     register int i;
  425.     extern int comp();
  426.     register char *s, *t;
  427.  
  428.     for (i=0; i < nmessages; i++) {
  429.         /*
  430.          * Skip messages with no subject.
  431.          */
  432.         if (messages[i].Subject == NULL)
  433.             continue;
  434.  
  435.         s = messages[i].Subject;
  436.  
  437.         if ((t = malloc(strlen(s)+1)) == NULL) {
  438.             printf("digest: out of memory.\n");
  439.             exit(1);
  440.         }
  441.  
  442.         messages[i].sortstring = t;
  443.  
  444.         /*
  445.          * Zap leading whitespace.
  446.          */
  447.         s = nospace(s);
  448.  
  449.         /*
  450.          * Copy the subject string into sortstring
  451.          * converting upper case to lower case and
  452.          * ignoring whitespace.
  453.          */
  454.         while (*s) {
  455.             if ((*s == ' ') || (*s == '\t')) {
  456.                 s++;
  457.                 continue;
  458.             }
  459.  
  460.             if (isupper(*s))
  461.                 *t++ = tolower(*s);
  462.             else
  463.                 *t++ = *s;
  464.  
  465.             s++;
  466.         }
  467.  
  468.         *t = NULL;
  469.     }
  470.  
  471.     /*
  472.      * Sort 'em.
  473.      */
  474.     qsort(messages, nmessages, sizeof(struct message), comp);
  475. }
  476.  
  477. /*
  478.  * comp - comparison routine for qsort.  Meassges with no subject go
  479.  *      at the end of the digest, messages with "administrivia" as
  480.  *      the subject go to the top of the digest.
  481.  */
  482. comp(m1, m2)
  483. register struct message *m1, *m2;
  484. {
  485.     int admin1, admin2;
  486.  
  487.     if (m1->sortstring == NULL) {
  488.         if (m2->sortstring == NULL)
  489.             return(0);
  490.         return(1);        /* no subject messages to end */
  491.     }
  492.  
  493.     if (m2->sortstring == NULL)
  494.         return(-1);        /* no subject messages to end */
  495.  
  496.     admin1 = strncmp(m1->sortstring, "administrivia", 13);
  497.     admin2 = strncmp(m2->sortstring, "administrivia", 13);
  498.  
  499.     if (admin1 == 0) {
  500.         if (admin2 == 0)
  501.             return(0);
  502.         return(-1);        /* administrivia to beginning */
  503.     }
  504.  
  505.     if (admin2 == 0)
  506.         return(1);        /* administrivia to beginning */
  507.  
  508.     return(strcmp(m1->sortstring, m2->sortstring));
  509. }
  510.  
  511. /*
  512.  * do_digest_header - prints the digest header and mailer headers.
  513.  */
  514. do_digest_header()
  515. {
  516.     FILE *fp;
  517.     char *laststr;
  518.     char buf[BUFSIZ];
  519.     char tmp[LINESIZE];
  520.     extern int comp2();
  521.     register int i, j, length;
  522.  
  523.     if ((output = fopen(LISTOUTPUT, "w")) == NULL) {
  524.         printf("digest: cannot create \"%s\"\n", LISTOUTPUT);
  525.         exit(1);
  526.     }
  527.  
  528.     digestsize = 0;
  529.  
  530.     /*
  531.      * Mailer headers.
  532.      */
  533.     sprintf(buf, "Date: %s\n", listinfo.Dateline);
  534.     digestsize += strlen(buf);
  535.     fputs(buf, output);
  536.  
  537.     sprintf(buf, "From: %s\n", listinfo.From);
  538.     digestsize += strlen(buf);
  539.     fputs(buf, output);
  540.  
  541.     sprintf(buf, "Reply-To: %s@%s\n", listinfo.Title, listinfo.Host);
  542.     digestsize += strlen(buf);
  543.     fputs(buf, output);
  544.  
  545.     sprintf(buf, "Subject: %s Digest V1 #%d\n", listinfo.Title, issue_number);
  546.     digestsize += strlen(buf);
  547.     fputs(buf, output);
  548.  
  549.     sprintf(buf, "To: %s\n", listinfo.To);
  550.     digestsize += strlen(buf);
  551.     fputs(buf, output);
  552.  
  553.     /*
  554.      * The digest header.
  555.      */
  556.     sprintf(tmp, "%s Digest", listinfo.Title);
  557.     sprintf(buf, "\n\n%-*.*s %-*.*s %-*.*s\n\n",
  558.                 HEAD1, HEAD1, tmp,
  559.                 HEAD2, DATELEN, listinfo.Dateline,
  560.                 HEAD3, HEAD3, listinfo.Volline);
  561.     digestsize += strlen(buf);
  562.     fputs(buf, output);
  563.  
  564.     sprintf(buf, "Today's Topics:\n");
  565.     digestsize += strlen(buf);
  566.     fputs(buf, output);
  567.  
  568.     /*
  569.      * Do today's topics lines.
  570.      */
  571.     laststr = "";
  572.     for (i=0; i < nmessages; i++) {
  573.         /*
  574.          * No topic.
  575.          */
  576.         if (messages[i].Subject == NULL)
  577.             continue;
  578.  
  579.         laststr = messages[i].sortstring;
  580.  
  581.         /*
  582.          * Count the number of messages with this topic.
  583.          */
  584.         j = 1;
  585.         while (((i + j) < nmessages) && (strcmp(laststr, messages[i+j].sortstring) == 0))
  586.             j++;
  587.  
  588.         /*
  589.          * Print the topic centered on the line.
  590.          */
  591.         if (j > 1) {
  592.             sprintf(tmp, "%s (%d msgs)", messages[i].Subject, j);
  593.             length = (LINELEN / 2) + (strlen(tmp) / 2);
  594.             sprintf(buf, "%*s\n", length, tmp);
  595.  
  596.             /*
  597.              * Sort messages with same topic into their
  598.              * original arrival order.
  599.              */
  600.             qsort(&messages[i], j, sizeof(struct message), comp2);
  601.             i += (j - 1);
  602.         }
  603.         else {
  604.             length = (LINELEN / 2) + (strlen(messages[i].Subject) / 2);
  605.             sprintf(buf, "%*s\n", length, messages[i].Subject);
  606.         }
  607.  
  608.         digestsize += strlen(buf);
  609.         fputs(buf, output);
  610.     }
  611.  
  612.     /*
  613.      * Read the LISTHEAD file, if there is one.
  614.      */
  615.     if ((fp = fopen(LISTHEAD, "r")) != NULL) {
  616.         fputc('\n', output);
  617.         digestsize++;
  618.  
  619.         while (fgets(buf, BUFSIZ, fp) != NULL) {
  620.             digestsize += strlen(buf);
  621.             fputs(buf, output);
  622.         }
  623.  
  624.         fclose(fp);
  625.     }
  626.  
  627.     /*
  628.      * Print a line of dashes.
  629.      */
  630.     for (i=0; i < LINELEN; i++) {
  631.         putc('-', output);
  632.         digestsize++;
  633.     }
  634.  
  635.     fputs("\n\n", output);
  636.     digestsize += 2;
  637. }
  638.  
  639. /*
  640.  * comp2 - comparison routine for second qsort.  This one simply compares
  641.  *       messages addresses in the input file, so that we can sort the
  642.  *       messages with the same topic back into the order they arrived.
  643.  */
  644. comp2(m1, m2)
  645. register struct message *m1, *m2;
  646. {
  647.     return(m1->messageaddr - m2->messageaddr);
  648. }
  649.  
  650. /*
  651.  * read_messages - reads in the message texts and puts them in the
  652.  *           digest with their headers.
  653.  */
  654. read_messages()
  655. {
  656.     char buf[BUFSIZ];
  657.     register char *s, *t;
  658.     register int i, length;
  659.  
  660.     for (i=0; i < nmessages; i++) {
  661.         /*
  662.          * Just in case.
  663.          */
  664.         clearerr(input);
  665.  
  666.         /*
  667.          * Put the message's headers back in.
  668.          */
  669.         sprintf(buf, "Date: %s\n", messages[i].Date);
  670.         digestsize += strlen(buf);
  671.         fputs(buf, output);
  672.  
  673.         sprintf(buf, "From: %s\n", messages[i].From);
  674.         digestsize += strlen(buf);
  675.         fputs(buf, output);
  676.  
  677.         if (messages[i].Subject != NULL) {
  678.             sprintf(buf, "Subject: %s\n", messages[i].Subject);
  679.             digestsize += strlen(buf);
  680.             fputs(buf, output);
  681.         }
  682.  
  683.         if (messages[i].To != NULL) {
  684.             sprintf(buf, "To: %s\n\n", messages[i].To);
  685.             digestsize += strlen(buf);
  686.             fputs(buf, output);
  687.         }
  688.  
  689.         /*
  690.          * Read the message into memory.  This is
  691.          * so we can zap extra blank lines.
  692.          */
  693.         fseek(input, messages[i].messageaddr, 0);
  694.         length = messages[i].messagelength;
  695.  
  696.         if ((s = malloc(length+1)) == NULL) {
  697.             printf("digest: out of memory.\n");
  698.             exit(1);
  699.         }
  700.  
  701.         fread(s, 1, length, input);
  702.  
  703.         /*
  704.          * Zap trailing newlines.
  705.          */
  706.         t = s + length;
  707.         while (*--t == '\n')
  708.             length--;
  709.         *++t = NULL;
  710.         
  711.         /*
  712.          * Zap leading newlines.
  713.          */
  714.         t = s;
  715.         while (*t++ == '\n')
  716.             length--;
  717.         t--;
  718.  
  719.         /*
  720.          * Write the message.
  721.          */
  722.         digestsize += length;
  723.         fwrite(t, 1, length, output);
  724.  
  725.         sprintf(buf, "\n\n------------------------------\n\n");
  726.         digestsize += strlen(buf);
  727.         fputs(buf, output);
  728.         free(s);
  729.     }
  730.  
  731.     /*
  732.      * All done.
  733.      */
  734.     sprintf(buf, "End of %s Digest\n******************************\n", listinfo.Title);
  735.     digestsize += strlen(buf);
  736.     fputs(buf, output);
  737.     fclose(output);
  738.     fclose(input);
  739. }
  740.  
  741. /*
  742.  * put_list_info - rewrite the LISTINFO file with the new data.
  743.  */
  744. put_list_info()
  745. {
  746.     FILE *fp;
  747.     char tmp[LINESIZE];
  748.  
  749.     sprintf(tmp, "%s.old", LISTINFO);
  750.  
  751.     if (rename(LISTINFO, tmp) < 0) {
  752.         printf("digest: cannot move old \"%s\" file, today's data lost.\n", LISTINFO);
  753.         return;
  754.     }
  755.  
  756.     if ((fp = fopen(LISTINFO, "w")) == NULL) {
  757.         printf("digest: cannot create \"%s\", today's data lost.\n", LISTINFO);
  758.         return;
  759.     }
  760.  
  761.     fprintf(fp, "%s\n", listinfo.Title);
  762.     fprintf(fp, "%s\n", listinfo.Host);
  763.     fprintf(fp, "%s\n", listinfo.From);
  764.     fprintf(fp, "%s\n", listinfo.To);
  765.     fprintf(fp, "%s\n", listinfo.Volline);
  766.     fprintf(fp, "%s\n", listinfo.Dateline);
  767.  
  768.     fclose(fp);
  769.     unlink(tmp);
  770. }
  771.  
  772. /*
  773.  * safter - return a pointer to the position in str which follows pat.
  774.  */
  775. char *safter(str, pat)
  776. register char *str, *pat;
  777. {
  778.     register int len;
  779.  
  780.     len = strlen(pat);
  781.  
  782.     while (*str) {
  783.         if (strncmp(str, pat, len) == 0) {
  784.             str += len;
  785.             return(str);
  786.         }
  787.  
  788.         str++;
  789.     }
  790.  
  791.     return(NULL);
  792. }
  793.  
  794. /*
  795.  * nospace - advance s over leading whitespace, return new value.
  796.  */
  797. char *nospace(s)
  798. register char *s;
  799. {
  800.     while ((*s != NULL) && ((*s == ' ') || (*s == '\t')))
  801.         s++;
  802.  
  803.     return(s);
  804. }
  805.  
  806.