4.3BSD/usr/contrib/nntp/server/newnews.c

Compare this file to the similar file:
Show the results in this format:

#ifndef lint
static char	*sccsid = "@(#)newnews.c	1.5	(Berkeley) 3/20/86";
#endif

#include "common.h"

long	gmt_to_local();

/*
 * NEWNEWS newsgroups date time ["GMT"] [<distributions>]
 *
 * Return the message-id's of any news articles past
 * a certain date and time, within the specified distributions.
 *
 */

newnews(argc, argv)
int argc;
char *argv[];
{
	char	*cp, *ngp;
	char	*key;
	char	datebuf[32];
	char	line[MAX_STRLEN];
	char	**distlist, **nglist, **histlist;
	int	distcount, ngcount, histcount;
	int	all;
	FILE	*fp;
	long	date;
	long	dtol();
	char	*ltod();

	if (argc < 4) {
		printf("%d NEWNEWS requires at least three arguments.\r\n",
			ERR_CMDSYN);
		(void) fflush(stdout);
		return;
	}

	all = streql(argv[1], "*");
	if (!all) {
		ngcount = get_nglist(&nglist, argv[1]);
		if (ngcount == 0) {
			printf("%d Bogus newsgroup specifier: %s\r\n",
				ERR_CMDSYN);
			(void) fflush(stdout);
			return;
		}
	}

	/*	    YYMMDD		    HHMMSS	*/
	if (strlen(argv[2]) != 6 || strlen(argv[3]) != 6) {
		printf("%d Date/time must be in form YYMMDD HHMMSS.\r\n",
			ERR_CMDSYN);
		(void) fflush(stdout);
		return;
	}

	(void) strcpy(datebuf, argv[2]);
	(void) strcat(datebuf, argv[3]);

	argc -= 4;
	argv += 4;

	/*
	 * Flame on.  The history file is not stored in GMT, but
	 * in local time.  So we have to convert GMT to local time
	 * if we're given GMT, otherwise we need only chop off the
	 * the seconds.  Such braindamage.
	 */

	key = datebuf;		/* Unless they specify GMT */

	if (argc > 0) {
		if (streql(*argv, "GMT")) {	/* Which we handle here */
			date = dtol(datebuf);
			if (date < 0) {
				printf("%d Invalid date specification.\r\n",
					ERR_CMDSYN);
				(void) fflush(stdout);
				return;
			}
			date = gmt_to_local(date);
			key = ltod(date);
			++argv;
			--argc;
		}
	}

	/* So, key now points to the local time, but we need to zap secs */

	key[10] = '\0';

	distcount = 0;
	if (argc > 0) {
		distcount = get_distlist(&distlist, *argv);
		if (distcount < 0) {
			printf("%d Bad distribution list: %s\r\n", ERR_CMDSYN,
				*argv);
			(void) fflush(stdout);
			return;
		}
	}

	fp = fopen(HISTORY_FILE, "r");
	if (fp == NULL) {
		syslog(LOG_ERR, "newnews: fopen %s: %m", HISTORY_FILE);
		printf("%d Cannot open history file.\r\n", ERR_FAULT);
		(void) fflush(stdout);
		return;
	}

	printf("%d New news by message id follows.\r\n", OK_NEWNEWS);

	if (seekuntil(fp, key, line, sizeof (line)) < 0) {
		printf(".\r\n");
		(void) fflush(stdout);
		(void) fclose(fp);
		return;
	}

/*
 * History file looks like:
 *
 * <1569@emory.UUCP>	01/22/86 09:19	net.micro.att/899 ucb.general/2545 
 *		     ^--tab            ^--tab		 ^--space         ^sp\0
 * Sometimes the newsgroups are missing; we try to be robust and
 * ignore such bogosity.  We tackle this by our usual parse routine,
 * and break the list of articles in the history file into an argv
 * array with one newsgroup per entry.
 */

	do {
		if ((cp = index(line, '\t')) == NULL)
			continue;

		if ((ngp = index(cp+1, '\t')) == NULL)	/* 2nd tab */
			continue;
		++ngp;			/* Points at newsgroup list */
		if (*ngp == '\n')
			continue;
		histcount = get_histlist(&histlist, ngp);
		if (histcount == 0)
			continue;

		/*
		 * For each newsgroup on this line in the history
		 * file, check it against the newsgroup names we're given.
		 * If it matches, then see if we're hacking distributions.
		 * If so, open the file and match the distribution line.
		 */

		if (!all)
			if (!ngmatch(nglist, ngcount, histlist, histcount))
				continue;

		if (distcount)
			if (!distmatch(distlist, distcount, histlist, histcount))
				continue;

		*cp = '\0';
		printf("%s\r\n", line);
	} while (fgets(line, sizeof(line), fp) != NULL);

	printf(".\r\n");
	(void) fflush(stdout);
	(void) fclose(fp);
}


/*
 * seekuntil -- seek through the history file looking for
 * a line with date "key".  Get that line, and return.
 *
 *	Parameters:	"fp" is the active file.
 *			"key" is the date, in form YYMMDDHHMM (no SS)
 *			"line" is storage for the first line we find.
 *
 *	Returns:	-1 on error, 0 otherwise.
 *
 *	Side effects:	Seeks in history file, modifies line.
 */

seekuntil(fp, key, line, linesize)
FILE	*fp;
char	*key;
char	*line;
int	linesize;
{
	char	datetime[32];
	int	c;
	long	top, bot, mid;

	bot = 0;
	(void) fseek(fp, 0L, 2);
	top = ftell(fp);
	for(;;) {
		mid = (top+bot)/2;
		(void) fseek(fp, mid, 0);
		do {
			c = getc(fp);
			mid++;
		} while (c != EOF && c!='\n');
		if (!getword(fp, datetime, line, linesize)) {
			return (-1);
		}
		switch (compare(key, datetime)) {
		case -2:
		case -1:
		case 0:
			if (top <= mid)
				break;
			top = mid;
			continue;
		case 1:
		case 2:
			bot = mid;
			continue;
		}
		break;
	}
	(void) fseek(fp, bot, 0);
	while(ftell(fp) < top) {
		if (!getword(fp, datetime, line, linesize)) {
			return (-1);
		}
		switch(compare(key, datetime)) {
		case -2:
		case -1:
		case 0:
			break;
		case 1:
		case 2:
			continue;
		}
		break;
	}

	return (0);
}

compare(s, t)
register char *s, *t;
{
	for (; *s == *t; s++, t++)
		if (*s == 0)
			return(0);
	return (*s == 0 ? -1:
		*t == 0 ? 1:
		*s < *t ? -2:
		2);
}

getword(fp, w, line, linesize)
FILE *fp;
char *w;
char *line;
int linesize;
{
	register char *cp;

	if (fgets(line, linesize, fp) == NULL)
		return (0);
	if (cp = index(line, '\t')) {
/*
 * The following gross hack is present because the history file date
 * format is braindamaged.  They like "mm/dd/yy hh:mm", which is useless
 * for relative comparisons of dates using something like atoi() or
 * strcmp.  So, this changes their format into yymmddhhmm.  Sigh.
 *
 * 12345678901234	("x" for cp[x])
 * mm/dd/yy hh:mm 	(their lousy representation)
 * yymmddhhmm		(our good one)
 * 0123456789		("x" for w[x])
 */
		*cp = '\0';
		(void) strncpy(w, cp+1, 15);
		w[0] = cp[7];		/* Years */
		w[1] = cp[8];
		w[2] = cp[1];		/* Months */
		w[3] = cp[2];
		w[4] = cp[4];		/* Days */
		w[5] = cp[5];
		w[6] = cp[10];		/* Hours */
		w[7] = cp[11];
		w[8] = cp[13];		/* Minutes */
		w[9] = cp[14];
		w[10] = '\0';
	} else
		w[0] = '\0';
	return (1);
}

/*
 * ngmatch -- match a list of newsgroups, with possible wildcard
 * expansion (i.e., *) with a list of newsgroups.
 * Both the newsgroups we're to match against (regexps) and
 * the list of newsgroups for this line in the history file are
 * in argc/argv format.
 *
 *	Parameters:	"nglist" is the list of group specifiers to match
 *			against.
 *			"ngcount" is the number of groups in nglist.
 *			"matchlist" is the list of newsgroups to match against.
 *			"matchcount" is number of groups in matchlist.
 *
 *	Returns:	1 if the named newsgroup is in the list.
 *			0 otherwise.
 *
 *	Side effects:	Terminates \n on end of grlist.
 *
 *	Note:		This ain't the same routine as "ngmatch"
 *			in the normal news software, although it
 *			probably should be.
 */

ngmatch(nglist, ngcount, matchlist, matchcount)
char	**nglist;
int	ngcount;
char	**matchlist;
int	matchcount;
{
	int		i, j;
	int		match;
	register char	*cp;

	match = 0;

	for (i = 0; i < matchcount; ++i) {
		if (cp = index(matchlist[i], '/'))
			*cp = '\0';
		for (j = 0; j < ngcount; ++j) {
			if (nglist[j][0] == '!') {	/* Handle negation */
				if (restreql(nglist[j]+1, matchlist[i]))
					return (0);	/* Hit a matching '!' */
			} else {
				if (restreql(nglist[j], matchlist[i]))
					match = 1;
			}
		}
	}

	return (match);
}


/*
 * restreql -- regular expression string equivalnce routine,
 * but not really full fledged.  Thanks and a tip of the hat to
 * Nick Lai, <lai@shadow.berkeley.edu> for this time saving device.
 *
 *	Parameters:	"w" is an asterisk-broadened regexp,
 *			"s" is a non-regexp string.
 *	Returns:	1 if match, 0 otherwise.
 *
 *	Side effects:	None.
 */

restreql(w, s)
	register char *w;
	register char *s;
{

	while (*s && *w) {
		switch (*w) {
			case '*':
				for (w++; *s; s++)
					if (restreql(w, s))
						return 1;
				break;
			default:
				if (*w != *s)
					return 0;
				w++, s++;
				break;
		}
	}
	if (*s)
		return 0;
	while (*w)
		if (*w++ != '*')
			return 0;

	return 1;
}


/*
 * distmatch -- see if a file matches a set of distributions.
 * We have to do this by (yech!) opening the file, finding
 * the Distribution: line, if it has one, and seeing if the
 * things match.
 *
 *	Parameters:	"distlist" is the distribution list
 *			we want.
 *			"distcount" is the count of distributions in it.
 *			"grouplist" is the list of groups (articles)
 *			for this line of the history file.
 *			"groupcount" is the count of groups in it.
 *			
 *	Returns:	1 if the article is in the given distribution.
 *			0 otherwise.
 */

distmatch(distlist, distcount, grouplist, groupcount)
char	*distlist[];
int	distcount;
char	*grouplist[];
int	groupcount;
{
	register char	*cp;
	register FILE	*fp;
	register int	i, j;
	char		buf[MAX_STRLEN];

	fp = fopen(grouplist[0], "r");
	if (fp == NULL) {
		syslog(LOG_ERR, "distmatch: fopen %s: %m", buf);
		return (0);
	}

	while (fgets(buf, sizeof (buf), fp) != NULL) {
		cp = index(buf, '\n');
		if (cp)
			*cp = '\0';
		if (buf[0] == '\0')		/* End of header */
			break;
		cp = index(buf, ':');
		if (cp == NULL)
			continue;
		*cp = '\0';
		if (streql(buf, "distribution")) {
			for (i = 0; i < distcount; ++i) {
				if (streql(cp + 2, distlist[i])) {
					(void) fclose(fp);
					return (1);
				}
			}
			return (0);
		}
	}

	(void) fclose(fp);

	/*
	 * We've finished the header with no distribution field.
	 * So we'll assume that the distribution is the characters
	 * up to the first dot in the newsgroup name.
	 */

	for (i = 0; i < groupcount; ++i) {
		cp = index(grouplist[i], '.');
		if (cp)
			*cp = '\0';
		for (j = 0; j < distcount; ++i)
			if (streql(grouplist[i], distlist[i]))
				return (1);
	}
		
	return (0);
}


/*
 * get_histlist -- return a nicely set up array of newsgroups
 * (actually, net.foo.bar/article_num) along with a count.
 *
 *	Parameters:		"array" is storage for our array,
 *				set to point at some static data.
 *				"list" is the history file newsgroup list.
 *
 *	Returns:		Number of group specs found.
 *
 *	Side effects:		Changes static data area.
 */

get_histlist(array, list)
char	***array;
char	*list;
{
	register int	histcount;
	register char	*cp;
	static	char	**hist_list = (char **) NULL;

	cp = index(list, '\n');
	if (cp)
		*cp-- = '\0';
	histcount = parsit(list, &hist_list);
	*array = hist_list;
	return (histcount);
}


/*
 * get_nglist -- return a nicely set up array of newsgroups
 * along with a count, when given an NNTP-spec newsgroup list
 * in the form ng1,ng2,ng...
 *
 *	Parameters:		"array" is storage for our array,
 *				set to point at some static data.
 *				"list" is the NNTP newsgroup list.
 *
 *	Returns:		Number of group specs found.
 *
 *	Side effects:		Changes static data area.
 */

get_nglist(array, list)
char	***array;
char	*list;
{
	register char	*cp;
	register int	ngcount;
	static	char	**ng_list = (char **) NULL;

	for (cp = list; *cp != '\0'; ++cp)
		if (*cp == ',')
			*cp = ' ';
	ngcount = parsit(list, &ng_list);
	*array = ng_list;
	return (ngcount);
}