home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / cnews.tar / relay / history.c < prev    next >
C/C++ Source or Header  |  1993-03-13  |  9KB  |  376 lines

  1. /*
  2.  * history file bashing
  3.  *
  4.  * B 2.10.3+ rnews puts out a leading space before received
  5.  * time if the article contains an Expires: header; tough.
  6.  * C news does this right instead of compatibly.
  7.  *
  8.  * The second history field is really two: time-received and Expires: value,
  9.  * separated by a tilde.  This is an attempt at partial compatibility with
  10.  * B news, in that C expire can cope with B news history files.
  11.  *
  12.  * There is no point to storing seek offsets in network byte order in the
  13.  * dbm file, since dbm files are machine-dependent and so can't be shared
  14.  * by dissimilar machines anyway.
  15.  */
  16.  
  17. #include <stdio.h>
  18. #include <stdlib.h>
  19. #include <string.h>        /* for memcpy */
  20. #include <errno.h>
  21. #include "fixerrno.h"
  22. #include <sys/types.h>
  23. #include "libc.h"
  24. #include "news.h"
  25. #include "config.h"
  26. #include "dbz.h"
  27. #include "fgetmfs.h"
  28. #include "headers.h"
  29. #include "relay.h"
  30. #include "history.h"
  31. #include "msgs.h"
  32.  
  33. #define HISTNAME "history"    /* name of the history file in $NEWSCTL */
  34. #define FIELDSEP '\t'
  35. #define SUBFIELDSEP '~'
  36.  
  37. /* give 0 & 2 pretty, SVIDish names */
  38. #ifndef SEEK_SET
  39. #define SEEK_SET 0
  40. #define SEEK_END 2
  41. #endif
  42.  
  43. /* private data */
  44. static FILE *fp = NULL;
  45. static char *filename;        /* absolute name of the ascii history file */
  46. static boolean writable;
  47.  
  48. /* libdbm imports */
  49. extern int dbminit(), store();
  50. extern datum fetch();
  51.  
  52. /* other imports */
  53. extern void prefuse();
  54.  
  55. STATIC void
  56. histname()
  57. {
  58.     if (filename == NULL)
  59.         filename = strsave(ctlfile(HISTNAME));
  60. }
  61.  
  62. /*
  63.  * open the history files: ascii first, then dbm.
  64.  * Try a+ mode first, then r mode, as dbm(3) does nowadays,
  65.  * so that this routine can be used by any user to read history files.
  66.  */
  67. STATIC boolean
  68. openhist()
  69. {
  70.     histname();
  71.     if (fp == NULL) {
  72.         if ((fp = fopenclex(filename, "a+")) != NULL)
  73.             writable = YES;
  74.         else if ((fp = fopenwclex(filename, "r")) != NULL)
  75.             writable = NO;
  76.         /* else fp==NULL and fopenwclex just complained */
  77.  
  78.         errno = 0;
  79.         if (fp != NULL && dbminit(filename) < 0) {
  80.             /*
  81.              * no luck.  dbm's dbminit will have just honked (on
  82.              * stdout, alas) but dbz's won't have, so bitch.
  83.              */
  84.             persistent(NOART, 'f',
  85.         "database files for `%s' incomprehensible or unavailable",
  86.                 filename);
  87.             (void) nfclose(fp);    /* close ascii file */
  88.             fp = NULL;        /* and mark it closed */
  89.         }
  90.     }
  91.     return fp != NULL;
  92. }
  93.  
  94. /*
  95.  * Turn \n & FIELDSEP into ' ' in s.
  96.  */
  97. STATIC void
  98. sanitise(s)
  99. register char *s;
  100. {
  101.     for (; *s != '\0'; ++s)
  102.         if (*s == FIELDSEP || *s == '\n')
  103.             *s = ' ';
  104. }
  105.  
  106. /*
  107.  * Turn SUBFIELDSEP into ' ' in s.
  108.  */
  109. STATIC void
  110. subsanitise(s)
  111. register char *s;
  112. {
  113.     stranslit(s, SUBFIELDSEP, ' ');
  114. }
  115.  
  116. STATIC datum
  117. getposhist(msgid)        /* return seek offset of history entry */
  118. char *msgid;
  119. {
  120.     register char *clnmsgid;
  121.     datum msgidkey, keypos;
  122.  
  123.     msgidkey.dptr = NULL;
  124.     msgidkey.dsize = 0;
  125.     if (!openhist())
  126.         return msgidkey;
  127.     clnmsgid = strsave(msgid);
  128.     sanitise(clnmsgid);
  129.     msgidkey.dptr = clnmsgid;
  130.     msgidkey.dsize = strlen(clnmsgid) + SIZENUL;
  131.     keypos = dbzfetch(msgidkey);        /* offset into ascii file */
  132.     free(clnmsgid);
  133.     return keypos;
  134. }
  135.  
  136. boolean
  137. alreadyseen(msgid)        /* return true if found in the data base */
  138. char *msgid;
  139. {
  140.     datum posdatum;
  141.  
  142.     posdatum = getposhist(msgid);
  143.     return posdatum.dptr != NULL;
  144. }
  145.  
  146. char *                /* NULL if no history entry; else malloced */
  147. gethistory(msgid)        /* return existing history entry, if any */
  148. char *msgid;
  149. {
  150.     long pos = 0;
  151.     datum posdatum;
  152.  
  153.     posdatum = getposhist(msgid);
  154.     if (posdatum.dptr != NULL && posdatum.dsize == sizeof pos) {
  155.         static char *histent = NULL;
  156.  
  157.         (void) memcpy((char *)&pos, posdatum.dptr, sizeof pos); /* align */
  158.         nnfree(&histent);
  159.         if (fseek(fp, pos, SEEK_SET) != -1 &&
  160.             (histent = fgetms(fp)) != NULL)
  161.             return histent;        /* could note move from EOF */
  162.     }
  163.     return NULL;
  164. }
  165.  
  166. /*
  167.  * Return a pointer to the "files" field of a history entry.
  168.  * Side-effect: trims \n from the history entry.
  169.  */
  170. char *
  171. findfiles(histent)
  172. char *histent;
  173. {
  174.     register char *tabp;
  175.  
  176.     trim(histent);
  177.     /* find start of 2nd field (arrival~expiry) */
  178.     tabp = strchr(histent, FIELDSEP);
  179.     if (tabp == NULL)
  180.         return NULL;                /* mangled entry */
  181.     /* find start of 3rd field (files list) */
  182.     else if ((tabp = strchr(tabp + 1, FIELDSEP)) == NULL)
  183.         return NULL;            /* cancelled or expired art. */
  184.     else
  185.         return tabp + 1;
  186. }
  187.  
  188. /*
  189.  * Internal interface to generate a history file entry,
  190.  * assuming all sanity checking has been done already.
  191.  * Record the (msgid, position) pair in the data base.
  192.  *
  193.  * The fflush is crash-proofing.
  194.  */
  195. STATIC void
  196. mkhistent(art, msgid, now, expiry)
  197. register struct article *art;
  198. char *msgid, *expiry;
  199. time_t now;
  200. {
  201.     long pos;
  202.     datum msgidkey, posdatum;
  203.  
  204.     pos = ftell(fp);  /* get seek ptr for dbm; could keep track instead */
  205.  
  206.     if (fprintf(fp, "%s%c%ld%c%s%c", msgid, FIELDSEP,
  207.         (long)now, SUBFIELDSEP, expiry, SUBFIELDSEP) == EOF)
  208.         fulldisk(art, filename);
  209.     if (art->a_charswritten > 0 &&
  210.         fprintf(fp, "%ld", (long)art->a_charswritten) == EOF)
  211.         fulldisk(art, filename);
  212.     /* don't write 3rd field for cancelled but unseen articles */
  213.     if (art->a_files != NULL && art->a_files[0] != '\0')
  214.         if (fprintf(fp, "%c%s", FIELDSEP, art->a_files) == EOF)
  215.             fulldisk(art, filename);
  216.     (void) putc('\n', fp);
  217.     if (fflush(fp) == EOF)
  218.         fulldisk(art, filename);
  219.  
  220.     msgidkey.dptr = msgid;
  221.     msgidkey.dsize = strlen(msgid) + SIZENUL;
  222.     posdatum.dptr = (char *)&pos;
  223.     posdatum.dsize = sizeof pos;
  224.     if (dbzstore(msgidkey, posdatum) < 0)
  225.         fulldisk(art, filename);
  226. }
  227.  
  228. /*
  229.  * Generate a history entry from art.
  230.  * The history entry will have tabs and newlines deleted from the
  231.  * interior of fields, to keep the file format sane.
  232.  * Optionally print the start of an "accepted" log file line (no \n)
  233.  * (transmit() prints site names).
  234.  */
  235. void
  236. history(art, startlog)
  237. register struct article *art;
  238. boolean startlog;
  239. {
  240.     register char *msgid, *expiry;
  241.     time_t now;
  242.  
  243.     if (!msgidok(art))        /* complains in log if unhappy */
  244.         return;            /* refuse to corrupt history */
  245.     msgid = strsave(nullify(art->h.h_msgid));
  246.     sanitise(msgid);    /* RFC 1036 forbids whitespace in msg-ids */
  247.     expiry = strsave(nullify(art->h.h_expiry));
  248.     sanitise(expiry);
  249.     subsanitise(expiry);
  250.  
  251.     if (startlog) {
  252.         timestamp(stdout, &now);
  253.         if (printf(" %s + %s", sendersite(nullify(art->h.h_path)),
  254.             msgid) == EOF)
  255.             fulldisk(art, "stdout");
  256.     } else
  257.         now = time(&now);
  258.     if (!openhist())
  259.         persistent(art, '\0', "can't open history", "");
  260.     else if (!writable)
  261.         persistent(art, 'f', "no write permission on `%s'", filename);
  262.     else if (fseek(fp, 0L, SEEK_END) == -1)
  263.         /* could avoid fseek if still at EOF */
  264.         persistent(art, 'f', "can't seek to end of `%s'", filename);
  265.     else
  266.         mkhistent(art, msgid, now, expiry);
  267.     free(msgid);
  268.     free(expiry);
  269. }
  270.  
  271. void
  272. decline(art)                    /* mark art as undesirable */
  273. struct article *art;
  274. {
  275.     transient(art, '\0', "article is a turkey", "");
  276.     if (!opts.okrefusal)
  277.         art->a_status |= ST_DROPPED;
  278. }
  279.  
  280. char *
  281. ismsgidbad(msgid)                /* if bad, return error */
  282. register char *msgid;
  283. {
  284.     if (msgid == NULL || msgid[0] == '\0')
  285.         return "missing Message-ID";
  286.     else if (strchr(msgid, '@') == NULL)
  287.         return "no @ in Message-ID";
  288.     else if (strchr(msgid, ' ') != NULL || strchr(msgid, '\t') != NULL)
  289.         return "whitespace in Message-ID";
  290.     else if (msgid[0] != '<' || msgid[strlen(msgid)-1] != '>')
  291.         return "Message-ID not bracketed by <>";
  292.     else
  293.         return NULL;
  294. }
  295.  
  296. int
  297. msgidok(art)                    /* if bad, complain in log */
  298. register struct article *art;
  299. {
  300.     register char *err = ismsgidbad(art->h.h_msgid);
  301.  
  302.     if (err == NULL)
  303.         return YES;
  304.     else {
  305.         prefuse(art);
  306.         (void) fputs(err, stdout);
  307.         decline(art);
  308.         return NO;
  309.     }
  310. }
  311.  
  312. /*
  313.  * Generate a fake history file entry, given a message-id, an Expires:
  314.  * value, and a "file" list ("net.foo/123").
  315.  */
  316. statust
  317. fakehist(fkmsgid, fkexpiry, fkfiles)
  318. char *fkmsgid, *fkexpiry, *fkfiles;
  319. {
  320.     struct article art;
  321.  
  322.     artinit(&art);
  323.     art.h.h_msgid = fkmsgid;
  324.     art.h.h_expiry = fkexpiry;
  325.     art.a_files = fkfiles;
  326.     history(&art, STARTLOG);
  327.     return art.a_status;
  328. }
  329.  
  330. /* Append "group/artnumstr" to the file list in *art. */
  331. void
  332. histupdfiles(art, group, artnumstr)
  333. register struct article *art;
  334. register char *group;
  335. register char *artnumstr;
  336. {
  337.     unsigned addlen = strlen(group) + STRLEN(SFNDELIM) +
  338.         strlen(artnumstr) + SIZENUL;
  339.  
  340.     if (art->a_files == NULL) {
  341.         art->a_files = nemalloc(addlen);
  342.         art->a_files[0] = '\0';
  343.     } else {
  344.         art->a_files = realloc(art->a_files, (unsigned)
  345.             strlen(art->a_files) + STRLEN(" ") + addlen);
  346.         if (art->a_files == NULL)
  347.             errunlock("can't grow a_files", "");
  348.         (void) strcat(art->a_files, " ");
  349.     }
  350.     (void) strcat(art->a_files, group);    /* normal case */
  351.     (void) strcat(art->a_files, SFNDELIM);
  352.     (void) strcat(art->a_files, artnumstr);
  353. }
  354.  
  355. statust
  356. closehist()
  357. {
  358.     register statust status = ST_OKAY;
  359.  
  360.     if (fp != NULL) {
  361.         /* dbmclose is only needed by dbz, to flush statistics to disk */
  362.         if (dbmclose() < 0) {
  363.             persistent(NOART, 'f', "error closing dbm history file",
  364.                 "");
  365.             status |= ST_DROPPED;
  366.         }
  367.         if (nfclose(fp) == EOF) {
  368.             persistent(NOART, 'f', "error closing history file",
  369.                 "");
  370.             status |= ST_DROPPED;
  371.         }
  372.         fp = NULL;        /* mark file closed */
  373.     }
  374.     return status;
  375. }
  376.