[net.sources] 2.10.2 header.c

pag@hao.UUCP (Peter Gross) (10/23/84)

/*
 * header.c - header functions plus some other goodies
 *
 * $Log:	header.c,v $
 * Revision 2.29  84/10/23  00:07:58  pag
 * Bug fix:  getfield() made incorrect assumption about the length
 * of header fields, always copying BUFLEN characters.  Some header
 * fields are less than this (SUBDATE, RECDATE, EXPDATE, LINES), and
 * some are longer (PATH and CONTROL).  All kinds of nasty results
 * can result from this problem, including the "path truncation"
 * bug.  The fix is to add an argument to getfield indicating what
 * type of header field is being copied.
 * 
 */

#ifndef lint
static char	*RcsId = "$Header: header.c,v 2.29 84/10/23 00:07:58 pag Exp $";
#endif !lint

#include <stdio.h>
#include <sys/types.h>
#include "defs.h"
#include "header.h"

extern char	bfr[], *FULLSYSNAME, *index();
extern char	*strcpy(), *arpadate(), *ctime();
extern time_t defexp;

char *hfgets();

char *news_version = NEWS_VERSION;


/*
 * Read header from file fp into *hp.  If wholething is FALSE,
 * it's an incremental read, otherwise start from scratch.
 * Return (FILE *) if header okay, else NULL.
 */
FILE *
hread(hp, fp, wholething)
register struct hbuf *hp;
FILE *fp;
int wholething;
{
	register int	len;
	register int	i;

	if (wholething) {
		for(i=0;i<NUNREC;i++)
			if (hp->unrec[i] != NULL)
				free(hp->unrec[i]);
			else
				break;
		bclear(hp, sizeof (*hp));
	}


	/* Check that it's a B news style header. */
	if (((hfgets(bfr, PATHLEN, fp) != NULL &&
	    *bfr >= 'A' && *bfr <= 'Z') && index(bfr, ':')))
		if (frmread(fp, hp))
			goto strip;

	/* It's not.  Try A news (begins with PROTO). */
	if (*bfr != PROTO)
		return(NULL);

	/* Read in an A news format article. */
	strncpy(hp->oident, &(bfr[1]), NAMELEN);	/* file name */
	if (!nstrip(hp->oident))
		return(NULL);
	/* Newsgroup List */
	if (hfgets(hp->nbuf, BUFLEN, fp) == NULL || !nstrip(hp->nbuf))
		return(NULL);
	ngcat(hp->nbuf);
	/* source path */
	if (hfgets(hp->path, PATHLEN, fp) == NULL || !nstrip(hp->path))
		return(NULL);
	/* date */
	if (hfgets(hp->subdate, DATELEN, fp) == NULL || !nstrip(hp->subdate))
		return(NULL);
	/* title */
	if (hfgets(hp->title, BUFLEN, fp) == NULL || !nstrip(hp->title))
		return(NULL);

strip:	/* strip off sys! from front of path. */
	strcpy(bfr, FULLSYSNAME);
	if (strncmp(bfr, hp->path, (len = strlen(bfr))) == 0
	&& index(NETCHRS, hp->path[len]))
		strcpy(hp->path, &(hp->path[len+1]));
	lcase(hp->nbuf);

	/* Intuit the From: line if only a path was given. */
	if (wholething && hp->from[0] == '\0')
		intuitfrom(hp);

	/* Get old and new style message ID's. */
	if (wholething)
		fixid(hp);

	return(fp);
}


/*
 * Get header info from mail-format file.
 * Return non-zero on success.
 */
#include <ctype.h>
#define FROM 		1
#define NEWSGROUP 	2
#define TITLE 		3
#define SUBMIT		4
#define RECEIVE		5
#define EXPIRE		6
#define ARTICLEID	7
#define MESSAGEID	8
#define REPLYTO		9
#define FOLLOWID	10
#define CONTROL		11
#define SENDER		12
#define FOLLOWTO	13
#define PATH		14
#define POSTVERSION	15
#define RELAYVERSION	16
#define DISTRIBUTION	17
#define ORGANIZATION	18
#define NUMLINES	19
#define KEYWORDS	20
#define APPROVED	21
#define NFID		22
#define NFFROM		23
#define OTHER		99

char *malloc();

frmread(fp, hp)
register FILE *fp;
register struct hbuf *hp;
{
	int	unreccnt = 0;
	register int	i;
	long	curpos;
	int hdrlineno = 0;

	i = type(bfr);
	do {
		curpos = ftell(fp);
		hdrlineno++;
		switch (i) {
		case PATH:
			getfield(hp->path, i);
			break;
		case FROM:
			getfield(hp->from, i);
			break;
		case NEWSGROUP:
			getfield(hp->nbuf, i);
			break;
		case TITLE:
			getfield(hp->title, i);
			break;
		case SUBMIT:
			getfield(hp->subdate, i);
			break;
		case RECEIVE:
			getfield(hp->recdate, i);
			break;
		case EXPIRE:
			getfield(hp->expdate, i);
			break;
		case ARTICLEID:
			getfield(hp->oident, i);
			break;
		case MESSAGEID:
			getfield(hp->ident, i);
			break;
		case REPLYTO:
			getfield(hp->replyto, i);
			break;
		case FOLLOWID:
			getfield(hp->followid, i);
			break;
		case SENDER:
			getfield(hp->sender, i);
			break;
		case FOLLOWTO:
			getfield(hp->followto, i);
			break;
		case CONTROL:
			getfield(hp->ctlmsg, i);
			break;
		case POSTVERSION:
			getfield(hp->postversion, i);
			break;
		case DISTRIBUTION:
			getfield(hp->distribution, i);
			break;
		case ORGANIZATION:
			getfield(hp->organization, i);
			break;
		case NUMLINES:
			getfield(hp->numlines, i);
			hp->intnumlines = atoi(hp->numlines);
			break;
		case KEYWORDS:
			getfield(hp->keywords, i);
			break;
		case APPROVED:
			getfield(hp->approved, i);
			break;
		case NFID:
			getfield(hp->nf_id, i);
			break;
		case NFFROM:
			getfield(hp->nf_from, i);
			break;
		case RELAYVERSION:
			/*
			 * Only believe a relay version if it's the first
			 * line, otherwise it probably got passed through
			 * by some old neighbor.
			 */
			if (hdrlineno == 1) {
				getfield(hp->relayversion, i);
			}
			break;
		case OTHER:
			if (unreccnt < NUNREC) {
				if ((hp->unrec[unreccnt] = malloc((unsigned)(strlen(bfr) + 1))) != NULL ) {
					strcpy(hp->unrec[unreccnt], bfr);
					unreccnt++;
				} else
					xerror("frmread: out of memory");
			}
			break;
		}
	} while ((i = type(hfgets(bfr, LBUFLEN, fp))) > 0);

	if (*bfr != '\n')
		fseek(fp, curpos, 0);
	if ((hp->from[0] || hp->path[0]) && hp->subdate[0] && (hp->ident[0] || hp->oident[0]))
		return TRUE;
	return FALSE;
}

/*
 * There was no From: line in the message (because it was generated by
 * an old news program).  Guess what it should have been and create it.
 */
intuitfrom(hp)
register struct hbuf *hp;
{
	char *tp;
	char *user, *host, *fullname;
	char *tailpath(), *rindex();
	char *at, *dot;

	tp = tailpath(hp);
	user = rindex(tp, '!');
	if (user == NULL)
		user = tp;
	else
		*user++ = '\0';

	/* Check for an existing Internet address on the end. */
	at = index(user, '@');
	if (at) {
		dot = index(at, '.');
		if (dot) {
			strcpy(hp->from, user);
			return;
		}
		/* @ signs are illegal except for the biggie, so */
		*at = '%';
	}

	if (tp[0] == '.')
		host = index(tp, '!') + 1;
	else if (user == tp)
		host = FULLSYSNAME;
	else
		host = tp;

	tp = index(host, '@');
	if (tp != NULL)
		*tp = 0;
	sprintf(hp->from, "%s@%s%s", user, host, MYDOMAIN);

	fullname = index(hp->path, '(');
	if (fullname != NULL) {
		fullname--;
		strcat(hp->from, fullname);
		*fullname = 0;
	}
}

/*
 * If the message has only one of ident/oident, guess what
 * the other one should be and fill them both in.
 */
fixid(hp)
register struct hbuf *hp;
{
	char lbuf[100];
	register char *p;
#ifdef OLD
	register char *q;
#endif OLD

	if (hp->ident[0] == '\0' && hp->oident[0] != '\0') {
		strcpy(lbuf, hp->oident);
		p = index(lbuf, '.');
		if (p == NULL) {
			strcpy(hp->ident, hp->oident);
			return;
		}
		*p++ = '\0';
		/*
		 * It may seem strange that we hardwire ".UUCP" in
		 * here instead of MYDOMAIN.  However, we are trying
		 * to guess what the domain was on the posting system,
		 * not the local system.  Since we don't really know
		 * what the posting system does, we just go with the
		 * majority - almost everyone will be a .UUCP if they
		 * didn't fill in their Message-ID.
		 */
		sprintf(hp->ident, "<%s@%s%s>", p, lbuf, ".UUCP");
	}

#ifdef OLD
	if (hp->oident[0] == '\0' && hp->ident[0] != '\0') {
		strcpy(lbuf, hp->ident);
		p = index(lbuf, '@');
		if (p == 0) {
			strcpy(hp->oident, hp->ident);
			return;
		}
		*p++ = '\0';
		q = index(p, '.');
		if (q == NULL)
			q = index(p, '>');
		if (q)
			*q++ = '\0';
		p[SNLN] = '\0';
		sprintf(hp->oident, "%s.%s", p, lbuf+1);
	}
#endif
}

/*
 * Get the given field of a header (char * parm) from bfr, but only
 * if there's something actually there (after the colon).  Don't
 * bother if we already have an entry for this field.
 */
getfield(hpfield, type)
char	*hpfield;
register int type;
{
	register char	*ptr;
	register short	hcount;

	if (hpfield[0])
		return;
	for (ptr = index(bfr, ':'); isspace(*++ptr); )
		;
	if (*ptr != '\0') {
		switch(type){
		case PATH:
		case CONTROL:
			hcount = PATHLEN;
			break;
		case SUBMIT:
		case RECEIVE:
		case EXPIRE:
			hcount = DATELEN;
			break;
		case FROM:
		case NEWSGROUP:
		case TITLE:
		case ARTICLEID:
		case MESSAGEID:
		case REPLYTO:
		case FOLLOWID:
		case SENDER:
		case FOLLOWTO:
		case POSTVERSION:
		case DISTRIBUTION:
		case ORGANIZATION:
		case KEYWORDS:
		case APPROVED:
		case NFID:
		case NFFROM:
		case RELAYVERSION:
			hcount = BUFLEN;
			break;
		case NUMLINES:
			hcount = 8;
			break;
		default:
			hcount = BUFLEN;
		}

		strncpy(hpfield, ptr, hcount - 1);
		nstrip(hpfield);
	}
	return;
}


#define its(type) (prefix(ptr, type))
type(ptr)
register char	*ptr;
{
	register char	*colon, *space;

	if (ptr == NULL)
		return FALSE;
	if (!isalpha(*ptr) && strncmp(ptr, "From ", 5))
		return FALSE;
	colon = index(ptr, ':');
	space = index(ptr, ' ');
	if (!colon || space && space < colon)
		return FALSE;
	if (its("From: "))
		if (index(ptr, '@') && !index(ptr, '!'))
			return FROM;
		else
			return PATH;
	if (its("Path: "))
		return PATH;
	if (its("Newsgroups: "))
		return NEWSGROUP;
	if (its("Subject: ") || its("Title: "))
		return TITLE;
	if (its("Posted: ") || its("Date: "))
		return SUBMIT;
	if (its("Date-Received: ") || its("Received: "))
		return RECEIVE;
	if (its("Expires: "))
		return EXPIRE;
	if (its("Article-I.D.: "))
		return ARTICLEID;
	if (its("Message-ID: "))
		return MESSAGEID;
	if (its("Reply-To: "))
		return REPLYTO;
	if (its("References: "))
		return FOLLOWID;
	if (its("Control: "))
		return CONTROL;
	if (its("Sender: "))
		return SENDER;
	if (its("Followup-To: "))
		return FOLLOWTO;
	if (its("Posting-Version: "))
		return POSTVERSION;
	if (its("Relay-Version: "))
		return RELAYVERSION;
	if (its("Distribution: "))
		return DISTRIBUTION;
	if (its("Organization: "))
		return ORGANIZATION;
	if (its("Lines: "))
		return NUMLINES;
	if (its("Keywords: "))
		return KEYWORDS;
	if (its("Approved: "))
		return APPROVED;
	if (its("Nf-ID: "))
		return NFID;
	if (its("Nf-From: "))
		return NFFROM;
	return OTHER;
}

/*
 * Generate the current version of the news software.
 */
char *
genversion()
{
	static char retbuf[32];
	char rb[100];
	register char *t;

	strcpy(rb, news_version);
	while (t = index(rb, '\t'))
		*t = ' ';
	/* This is B news, so we say "B", the version, and the date. */
	sprintf(retbuf, "version %s; site %s%s", rb, FULLSYSNAME, MYDOMAIN);
	return retbuf;
}

/*
 * Write header at 'hp' on stream 'fp' in B format.  This goes out to
 * some other system.
 */
hwrite(hp, fp)
register struct hbuf *hp;
register FILE *fp;
{
	ihwrite(hp, fp, 0);
}


/*
 * Same as above, except include receival date for local usage and
 * an extra \n for looks.
 */
lhwrite(hp, fp)
register struct hbuf *hp;
register FILE *fp;
{
	ihwrite(hp, fp, 1);
}


/*
 * Write header at 'hp' on stream 'fp' in B+ format.  Include received date
 * if wr is 1.  Leave off sysname if wr is 2.
 */
ihwrite(hp, fp, wr)
register struct hbuf *hp;
register FILE *fp;
int	wr;
{
	int iu;
	time_t t;
	time_t cgtdate();

	fprintf(fp, "Relay-Version: %s\n", genversion());
	if (*hp->postversion)
		fprintf(fp, "Posting-Version: %s\n", hp->postversion);
	/*
	 * We're being tricky with Path/From because of upward compatibility
	 * issues.  The new version considers From and Path to be separate.
	 * The old one thinks they both mean "Path" but only believes the
	 * first one it sees, so will ignore the second.
	 */
	if (prefix(hp->path, FULLSYSNAME))
		fprintf(fp, "Path: %s\n", hp->path);
	else
		fprintf(fp, "Path: %s!%s\n", FULLSYSNAME, hp->path);
	if (hp->from[0])
		fprintf(fp, "From: %s\n", hp->from);

	strcpy(bfr, hp->nbuf);
	ngdel(bfr);
	fprintf(fp, "Newsgroups: %s\n", bfr);
	fprintf(fp, "Subject: %s\n", hp->title);
	fixid(hp);
	fprintf(fp, "Message-ID: %s\n", hp->ident);
	t = cgtdate(hp->subdate);
	fprintf(fp, "Date: %s\n", arpadate(&t));
#ifdef OLD
	fprintf(fp, "Article-I.D.: %s\n", hp->oident);
	fprintf(fp, "Posted: %s", ctime(&t));
#endif
	if (wr == 1)
		fprintf(fp, "Date-Received: %s\n", hp->recdate);
	if (*hp->expdate)
		fprintf(fp, "Expires: %s\n", hp->expdate);
	if (*hp->followid)
		fprintf(fp, "References: %s\n", hp->followid);
	if (*hp->ctlmsg)
		fprintf(fp, "Control: %s\n", hp->ctlmsg);
	if (*hp->sender)
		fprintf(fp, "Sender: %s\n", hp->sender);
	if (*hp->replyto)
		fprintf(fp, "Reply-To: %s\n", hp->replyto);
	if (*hp->followto)
		fprintf(fp, "Followup-To: %s\n", hp->followto);
	if (*hp->distribution)
		fprintf(fp, "Distribution: %s\n", hp->distribution);
	if (*hp->organization)
		fprintf(fp, "Organization: %s\n", hp->organization);
	if (*hp->numlines)
		fprintf(fp, "Lines: %s\n", hp->numlines);
	if (*hp->keywords)
		fprintf(fp, "Keywords: %s\n", hp->keywords);
	if (*hp->approved)
		fprintf(fp, "Approved: %s\n", hp->approved);
	if (*hp->nf_id)
		fprintf(fp, "Nf-ID: %s\n", hp->nf_id);
	if (*hp->nf_from)
		fprintf(fp, "Nf-From: %s\n", hp->nf_from);
	for (iu = 0; iu < NUNREC; iu++) {
		if (hp->unrec[iu])
			fprintf(fp, "%s", &hp->unrec[iu][0]);
	}
	putc('\n', fp);
}


/*
 * Set nc bytes, starting at cp, to zero.
 */
bclear(cp, nc)
register char	*cp;
register int	nc;
{
	while (nc--)
		*cp++ = 0;
}

/*
 * hfgets is like fgets, but deals with continuation lines.
 * It also ensures that even if a line that is too long is
 * received, the remainder of the line is thrown away
 * instead of treated like a second line.
 */
char *
hfgets(buf, len, fp)
char *buf;
int len;
FILE *fp;
{
	register int c;
	register char *cp, *tp;

	cp = fgets(buf, len, fp);
	if (cp == NULL)
		return NULL;

	tp = cp + strlen(cp);
	if (tp[-1] != '\n') {
		/* Line too long - part read didn't fit into a newline */
		while ((c = getc(fp)) != '\n' && c != EOF)
			;
	} else if (tp == (cp+1))
		return(cp);	/* Don't look for continuation of blank lines */
	else
		*--tp = '\0';	/* clobber newline */

	while ((c = getc(fp)) == ' ' || c == '\t') {	/* for each cont line */
		/* Continuation line. */
		while ((c = getc(fp)) == ' ' || c == '\t')	/* skip white space */
			;
 		if (c == '\n')	/* oops, it's really a blank line (I hope) */
 			break;
		if (tp-cp < len) {*tp++ = ' '; *tp++ = c;}
		while ((c = getc(fp)) != '\n' && c != EOF)
			if (tp-cp < len) *tp++ = c;
	}
	*tp++ = '\n';
	*tp++ = '\0';
	if (c != EOF)
		(void) ungetc(c, fp); /* push back first char of next header */
	return cp;
}