home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Unix System Administration Handbook 1997 October
/
usah_oct97.iso
/
news
/
cnews.tar
/
relay
/
history.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-03-13
|
9KB
|
376 lines
/*
* history file bashing
*
* B 2.10.3+ rnews puts out a leading space before received
* time if the article contains an Expires: header; tough.
* C news does this right instead of compatibly.
*
* The second history field is really two: time-received and Expires: value,
* separated by a tilde. This is an attempt at partial compatibility with
* B news, in that C expire can cope with B news history files.
*
* There is no point to storing seek offsets in network byte order in the
* dbm file, since dbm files are machine-dependent and so can't be shared
* by dissimilar machines anyway.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* for memcpy */
#include <errno.h>
#include "fixerrno.h"
#include <sys/types.h>
#include "libc.h"
#include "news.h"
#include "config.h"
#include "dbz.h"
#include "fgetmfs.h"
#include "headers.h"
#include "relay.h"
#include "history.h"
#include "msgs.h"
#define HISTNAME "history" /* name of the history file in $NEWSCTL */
#define FIELDSEP '\t'
#define SUBFIELDSEP '~'
/* give 0 & 2 pretty, SVIDish names */
#ifndef SEEK_SET
#define SEEK_SET 0
#define SEEK_END 2
#endif
/* private data */
static FILE *fp = NULL;
static char *filename; /* absolute name of the ascii history file */
static boolean writable;
/* libdbm imports */
extern int dbminit(), store();
extern datum fetch();
/* other imports */
extern void prefuse();
STATIC void
histname()
{
if (filename == NULL)
filename = strsave(ctlfile(HISTNAME));
}
/*
* open the history files: ascii first, then dbm.
* Try a+ mode first, then r mode, as dbm(3) does nowadays,
* so that this routine can be used by any user to read history files.
*/
STATIC boolean
openhist()
{
histname();
if (fp == NULL) {
if ((fp = fopenclex(filename, "a+")) != NULL)
writable = YES;
else if ((fp = fopenwclex(filename, "r")) != NULL)
writable = NO;
/* else fp==NULL and fopenwclex just complained */
errno = 0;
if (fp != NULL && dbminit(filename) < 0) {
/*
* no luck. dbm's dbminit will have just honked (on
* stdout, alas) but dbz's won't have, so bitch.
*/
persistent(NOART, 'f',
"database files for `%s' incomprehensible or unavailable",
filename);
(void) nfclose(fp); /* close ascii file */
fp = NULL; /* and mark it closed */
}
}
return fp != NULL;
}
/*
* Turn \n & FIELDSEP into ' ' in s.
*/
STATIC void
sanitise(s)
register char *s;
{
for (; *s != '\0'; ++s)
if (*s == FIELDSEP || *s == '\n')
*s = ' ';
}
/*
* Turn SUBFIELDSEP into ' ' in s.
*/
STATIC void
subsanitise(s)
register char *s;
{
stranslit(s, SUBFIELDSEP, ' ');
}
STATIC datum
getposhist(msgid) /* return seek offset of history entry */
char *msgid;
{
register char *clnmsgid;
datum msgidkey, keypos;
msgidkey.dptr = NULL;
msgidkey.dsize = 0;
if (!openhist())
return msgidkey;
clnmsgid = strsave(msgid);
sanitise(clnmsgid);
msgidkey.dptr = clnmsgid;
msgidkey.dsize = strlen(clnmsgid) + SIZENUL;
keypos = dbzfetch(msgidkey); /* offset into ascii file */
free(clnmsgid);
return keypos;
}
boolean
alreadyseen(msgid) /* return true if found in the data base */
char *msgid;
{
datum posdatum;
posdatum = getposhist(msgid);
return posdatum.dptr != NULL;
}
char * /* NULL if no history entry; else malloced */
gethistory(msgid) /* return existing history entry, if any */
char *msgid;
{
long pos = 0;
datum posdatum;
posdatum = getposhist(msgid);
if (posdatum.dptr != NULL && posdatum.dsize == sizeof pos) {
static char *histent = NULL;
(void) memcpy((char *)&pos, posdatum.dptr, sizeof pos); /* align */
nnfree(&histent);
if (fseek(fp, pos, SEEK_SET) != -1 &&
(histent = fgetms(fp)) != NULL)
return histent; /* could note move from EOF */
}
return NULL;
}
/*
* Return a pointer to the "files" field of a history entry.
* Side-effect: trims \n from the history entry.
*/
char *
findfiles(histent)
char *histent;
{
register char *tabp;
trim(histent);
/* find start of 2nd field (arrival~expiry) */
tabp = strchr(histent, FIELDSEP);
if (tabp == NULL)
return NULL; /* mangled entry */
/* find start of 3rd field (files list) */
else if ((tabp = strchr(tabp + 1, FIELDSEP)) == NULL)
return NULL; /* cancelled or expired art. */
else
return tabp + 1;
}
/*
* Internal interface to generate a history file entry,
* assuming all sanity checking has been done already.
* Record the (msgid, position) pair in the data base.
*
* The fflush is crash-proofing.
*/
STATIC void
mkhistent(art, msgid, now, expiry)
register struct article *art;
char *msgid, *expiry;
time_t now;
{
long pos;
datum msgidkey, posdatum;
pos = ftell(fp); /* get seek ptr for dbm; could keep track instead */
if (fprintf(fp, "%s%c%ld%c%s%c", msgid, FIELDSEP,
(long)now, SUBFIELDSEP, expiry, SUBFIELDSEP) == EOF)
fulldisk(art, filename);
if (art->a_charswritten > 0 &&
fprintf(fp, "%ld", (long)art->a_charswritten) == EOF)
fulldisk(art, filename);
/* don't write 3rd field for cancelled but unseen articles */
if (art->a_files != NULL && art->a_files[0] != '\0')
if (fprintf(fp, "%c%s", FIELDSEP, art->a_files) == EOF)
fulldisk(art, filename);
(void) putc('\n', fp);
if (fflush(fp) == EOF)
fulldisk(art, filename);
msgidkey.dptr = msgid;
msgidkey.dsize = strlen(msgid) + SIZENUL;
posdatum.dptr = (char *)&pos;
posdatum.dsize = sizeof pos;
if (dbzstore(msgidkey, posdatum) < 0)
fulldisk(art, filename);
}
/*
* Generate a history entry from art.
* The history entry will have tabs and newlines deleted from the
* interior of fields, to keep the file format sane.
* Optionally print the start of an "accepted" log file line (no \n)
* (transmit() prints site names).
*/
void
history(art, startlog)
register struct article *art;
boolean startlog;
{
register char *msgid, *expiry;
time_t now;
if (!msgidok(art)) /* complains in log if unhappy */
return; /* refuse to corrupt history */
msgid = strsave(nullify(art->h.h_msgid));
sanitise(msgid); /* RFC 1036 forbids whitespace in msg-ids */
expiry = strsave(nullify(art->h.h_expiry));
sanitise(expiry);
subsanitise(expiry);
if (startlog) {
timestamp(stdout, &now);
if (printf(" %s + %s", sendersite(nullify(art->h.h_path)),
msgid) == EOF)
fulldisk(art, "stdout");
} else
now = time(&now);
if (!openhist())
persistent(art, '\0', "can't open history", "");
else if (!writable)
persistent(art, 'f', "no write permission on `%s'", filename);
else if (fseek(fp, 0L, SEEK_END) == -1)
/* could avoid fseek if still at EOF */
persistent(art, 'f', "can't seek to end of `%s'", filename);
else
mkhistent(art, msgid, now, expiry);
free(msgid);
free(expiry);
}
void
decline(art) /* mark art as undesirable */
struct article *art;
{
transient(art, '\0', "article is a turkey", "");
if (!opts.okrefusal)
art->a_status |= ST_DROPPED;
}
char *
ismsgidbad(msgid) /* if bad, return error */
register char *msgid;
{
if (msgid == NULL || msgid[0] == '\0')
return "missing Message-ID";
else if (strchr(msgid, '@') == NULL)
return "no @ in Message-ID";
else if (strchr(msgid, ' ') != NULL || strchr(msgid, '\t') != NULL)
return "whitespace in Message-ID";
else if (msgid[0] != '<' || msgid[strlen(msgid)-1] != '>')
return "Message-ID not bracketed by <>";
else
return NULL;
}
int
msgidok(art) /* if bad, complain in log */
register struct article *art;
{
register char *err = ismsgidbad(art->h.h_msgid);
if (err == NULL)
return YES;
else {
prefuse(art);
(void) fputs(err, stdout);
decline(art);
return NO;
}
}
/*
* Generate a fake history file entry, given a message-id, an Expires:
* value, and a "file" list ("net.foo/123").
*/
statust
fakehist(fkmsgid, fkexpiry, fkfiles)
char *fkmsgid, *fkexpiry, *fkfiles;
{
struct article art;
artinit(&art);
art.h.h_msgid = fkmsgid;
art.h.h_expiry = fkexpiry;
art.a_files = fkfiles;
history(&art, STARTLOG);
return art.a_status;
}
/* Append "group/artnumstr" to the file list in *art. */
void
histupdfiles(art, group, artnumstr)
register struct article *art;
register char *group;
register char *artnumstr;
{
unsigned addlen = strlen(group) + STRLEN(SFNDELIM) +
strlen(artnumstr) + SIZENUL;
if (art->a_files == NULL) {
art->a_files = nemalloc(addlen);
art->a_files[0] = '\0';
} else {
art->a_files = realloc(art->a_files, (unsigned)
strlen(art->a_files) + STRLEN(" ") + addlen);
if (art->a_files == NULL)
errunlock("can't grow a_files", "");
(void) strcat(art->a_files, " ");
}
(void) strcat(art->a_files, group); /* normal case */
(void) strcat(art->a_files, SFNDELIM);
(void) strcat(art->a_files, artnumstr);
}
statust
closehist()
{
register statust status = ST_OKAY;
if (fp != NULL) {
/* dbmclose is only needed by dbz, to flush statistics to disk */
if (dbmclose() < 0) {
persistent(NOART, 'f', "error closing dbm history file",
"");
status |= ST_DROPPED;
}
if (nfclose(fp) == EOF) {
persistent(NOART, 'f', "error closing history file",
"");
status |= ST_DROPPED;
}
fp = NULL; /* mark file closed */
}
return status;
}