1 #ifndef lint
2 static char	sccsid[] = "@(#)$Id: newnews.c,v 1.27 1994/11/01 06:08:21 sob Exp sob $";
3 #endif
4 
5 #include "common.h"
6 #include "date.h"
7 
8 #ifdef LOG
9 int	nn_told = 0;
10 int	nn_took = 0;
11 #endif
12 
13 
14 /*
15  * NEWNEWS newsgroups date time ["GMT"] [<distributions>]
16  *
17  * Return the message-id's of any news articles past
18  * a certain date and time, within the specified distributions.
19  *
20  * Made RFC977/Y2K compliant via changes in dtol() - see time.c - and
21  * slightly simplified the comparison between incoming date and history
22  * file.  Fixed(?) the local time offset.  D. Glover 29/11/99
23  *
24  */
25 
26 void
newnews(argc,argv)27 newnews(argc, argv)
28 	register int	argc;
29 	char		*argv[];
30 {
31 	register char	*cp, *ngp;
32 	char		*key;
33 	char		datebuf[32];
34 	char		line[MAXBUFLEN];
35 	char		**distlist, **histlist;
36 	static char	**nglist;
37 	int		distcount, ngcount, histcount;
38 	int		all;
39 	FILE		*fp;
40 	long		date;
41 	long		dtol();
42 	char		*ltod();
43 #ifdef USGHIST
44 	FILE		*tmplst;
45 	int		i;
46 	char		*tmpfile;
47 #endif
48 
49 	if (argc < 4) {
50 		printf("%d Usage: NEWNEWS newsgroups yymmdd hhmmss [\"GMT\"] [<distributions>].\r\n",
51 			ERR_CMDSYN);
52 		(void) fflush(stdout);
53 		return;
54 	}
55 
56 	if (!canread) {
57 		printf("%d You do not have permission to read.  sorry.\r\n",
58 			ERR_ACCESS);
59 		(void) fflush(stdout);
60 		return;
61 	}
62 
63 #ifdef LOG
64 	sprintf(line, "%s newnews %s %s %s %s %s",
65 		hostname,
66 		argv[1],
67 		argv[2],
68 		argv[3],
69 		(argc >= 5 && *argv[4] == 'G') ? "GMT" : "local",
70 		(argc >= 5 && *argv[argc-1] == '<') ? argv[argc-1] : "none");
71 	syslog(LOG_INFO, line);
72 #endif
73 
74 	all = (argv[1][0] == '*' && argv[1][1] == '\0');
75 	if (!all) {
76 		ngcount = get_nglist(&nglist, argv[1]);
77 		if (ngcount == 0) {
78 			printf("%d Bogus newsgroup specifier: %s\r\n",
79 				ERR_CMDSYN, argv[1]);
80 			(void) fflush(stdout);
81 			return;
82 		}
83 	}
84 
85 	/*	    YYMMDD		    HHMMSS	*/
86 	if (strlen(argv[2]) != 6 || strlen(argv[3]) != 6) {
87 		printf("%d Date/time must be in form YYMMDD HHMMSS.\r\n",
88 			ERR_CMDSYN);
89 		(void) fflush(stdout);
90 		return;
91 	}
92 
93 	(void) strcpy(datebuf, argv[2]);
94 	(void) strcat(datebuf, argv[3]);
95 
96 	argc -= 4;
97 	argv += 4;
98 
99 /* Originally the "key" for searching was obtained here and the localtime
100  * offset wasn't applied when doing the actual comparison... DG 29/11/99
101  */
102 	date = dtol(datebuf); /* local time here */
103 	if (date < 0) {
104 		printf("%d Invalid date specification.\r\n",ERR_CMDSYN);
105 		(void) fflush(stdout);
106 		return;
107 	}
108 
109 /* REMOVED apparently unwanted call to gmt_to_local()
110  * and tidied up handling of GMT/distributions DG 29/11/99
111  */
112 	if (argc > 0 && !strcasecmp(*argv, "GMT")) {
113 			++argv;
114 			--argc;
115 	}
116 	else {
117 	/* now we convert from local to GMT since this is what history */
118 	/* file in News 2.11 expects */
119 			date = local_to_gmt(date);
120 	}
121 	strcpy(datebuf, ltod(date));
122 	key = datebuf;
123 	distcount = 0;
124 	if (argc > 0) {
125 		distcount = get_distlist(&distlist, *argv);
126 		if (distcount < 0) {
127 			printf("%d Bad distribution list: %s\r\n", ERR_CMDSYN,
128 				*argv);
129 			(void) fflush(stdout);
130 			return;
131 		}
132 	}
133 
134 #ifdef USGHIST
135     if ((tmpfile = mktemp("/tmp/listXXXXXX")) == NULL ||
136 		(tmplst = fopen(tmpfile, "w+")) == NULL) {
137 	printf("%d Cannot process history file.\r\n", ERR_FAULT);
138 	(void) fflush(stdout);
139 	return;
140     }
141 
142     for (i = 0; i < 9; i++) {
143 		sprintf(historyfile, "%s.d/%d", HISTORY_FILE, i);
144 #endif
145 
146 	fp = fopen(historyfile, "r");
147 	if (fp == NULL) {
148 #ifdef SYSLOG
149 		syslog(LOG_ERR, "newnews: fopen %s: %m", historyfile);
150 #endif
151 #ifndef USGHIST
152 		printf("%d Cannot open history file.\r\n", ERR_FAULT);
153 		(void) fflush(stdout);
154 		return;
155 #else
156 		continue;
157 #endif
158 	}
159 
160 #ifndef USGHIST
161 	printf("%d New news by message id follows\r\n", OK_NEWNEWS);
162 #endif
163 
164 	if (seekuntil(fp, key, line, sizeof (line)) < 0) {
165 #ifndef USGHIST
166 		printf(".\r\n");
167 		(void) fflush(stdout);
168 #endif
169 		(void) fclose(fp);
170 #ifndef USGHIST
171 		return;
172 #else
173 		continue;
174 #endif
175 	}
176 
177 /*
178  * History file looks like:
179  *
180  * <1569@emory.UUCP>	01/22/86 09:19	net.micro.att/899 ucb.general/2545
181  *		     ^--tab            ^--tab		 ^--space         ^sp\0
182  * Sometimes the newsgroups are missing; we try to be robust and
183  * ignore such bogosity.  We tackle this by our usual parse routine,
184  * and break the list of articles in the history file into an argv
185  * array with one newsgroup per entry.
186  */
187 
188 	do {
189 		if ((cp = index(line, '\t')) == NULL)
190 			continue;
191 
192 		if ((ngp = index(cp+1, '\t')) == NULL)	/* 2nd tab */
193 			continue;
194 		++ngp;			/* Points at newsgroup list */
195 		if (*ngp == '\n')
196 			continue;
197 		histcount = get_histlist(&histlist, ngp);
198 		if (histcount == 0)
199 			continue;
200 
201 		/*
202 		 * For each newsgroup on this line in the history
203 		 * file, check it against the newsgroup names we're given.
204 		 * If it matches, then see if we're hacking distributions.
205 		 * If so, open the file and match the distribution line.
206 		 */
207 
208 		if (!all)
209 			if (!ngmatch(restreql, 0, nglist, ngcount,
210 			    histlist, histcount))
211 				continue;
212 
213 		if (distcount)
214 			if (!distmatch(distlist, distcount, histlist, histcount))
215 				continue;
216 
217 		*cp = '\0';
218 #ifdef USGHIST
219 		fputs(line, tmplst);
220 		fputc('\n', tmplst);
221 #else
222 		putline(line);
223 #endif
224 #ifdef LOG
225 		nn_told++;
226 #endif
227 	} while (fgets(line, sizeof(line), fp) != NULL);
228 
229 #ifndef USGHIST
230 	putline(".");
231 	(void) fflush(stdout);
232 #endif
233 	(void) fclose(fp);
234 #ifdef USGHIST
235     }
236     printf("%d New news by message id follows\r\n", OK_NEWNEWS);
237     rewind(tmplst);
238     while (fgets(line, sizeof(line), tmplst) != NULL)
239             if (line[0] == '<') putline(line);
240     putline(".");
241     (void) fflush(stdout);
242     (void) fclose(tmplst);
243     (void) unlink(tmpfile);
244 #endif
245 }
246 
247 
248 /*
249  * seekuntil -- seek through the history file looking for
250  * a line with date later than "akey".  Get that line, and return.
251  *
252  *	Parameters:	"fp" is the active file.
253  *			"akey" is the date, in form YYYYMMDDHHMMSS
254  *			"line" is storage for the first line we find.
255  *
256  *	Returns:	-1 on error, 0 otherwise.
257  *
258  *	Side effects:	Seeks in history file, modifies line.
259  */
260 
261 int
seekuntil(fp,akey,line,linesize)262 seekuntil(fp, akey, line, linesize)
263 	FILE		*fp;
264 	char		*akey;
265 	char		*line;
266 	int		linesize;
267 {
268 	char		datetime[32];
269 	register int	c;
270 	register long	top, bot, mid;
271 
272 	bot = 0;
273 	(void) fseek(fp, 0L, 2);
274 	top = ftell(fp);
275 	for(;;) {
276 		mid = (top+bot)/2;
277 		(void) fseek(fp, mid, 0);
278 		do {
279 			c = getc(fp);
280 			mid++;
281 		} while (c != EOF && c!='\n');
282 		if (!getword(fp, datetime, line, linesize)) {
283 			return (-1);
284 		}
285 		switch (compare(akey, datetime)) {
286 		case -2:
287 		case -1:
288 		case 0:
289 			if (top <= mid)
290 				break;
291 			top = mid;
292 			continue;
293 		case 1:
294 		case 2:
295 			bot = mid;
296 			continue;
297 		}
298 		break;
299 	}
300 	(void) fseek(fp, bot, 0);
301 	while(ftell(fp) < top) {
302 		if (!getword(fp, datetime, line, linesize)) {
303 			return (-1);
304 		}
305 		switch(compare(akey, datetime)) {
306 		case -2:
307 		case -1:
308 		case 0:
309 			break;
310 		case 1:
311 		case 2:
312 			continue;
313 		}
314 		break;
315 	}
316 
317 	return (0);
318 }
319 
320 int
compare(s,t)321 compare(s, t)
322 	register char *s, *t;
323 {
324 for (; *s == *t; s++, t++)
325 		if (*s == 0)
326 			return(0);
327 	return (*s == 0 ? -1:
328 		*t == 0 ? 1:
329 		*s < *t ? -2:
330 		2);
331 }
332 
333 /*
334  * Combined B and C news version of getword.
335  */
336 int
getword(fp,w,line,linesize)337 getword(fp, w, line, linesize)
338 	FILE		*fp;
339 	register char	*w;
340 	char		*line;
341 	int		linesize;
342 {
343 	register char	*cp;
344 	long qz = 0;
345 	if (fgets(line, linesize, fp) == NULL)
346 		return (0);
347 	w[0] = '\0';				/* in case of bad format */
348 	if ((cp = index(line, '\t')) != NULL) {	/* find 2nd field */
349 		register char *endp;
350 		while (*cp == ' ' || *cp == '\t') cp++;
351 				/* skip any leading spaces or tabs */
352 		endp = index(cp, '~');		/* end of date-received */
353 						/* This will fail for B news */
354 		if (endp == NULL)
355 			endp = index(cp, '\t');	/* end of expiry */
356  						/* This will fail if article */
357 						/* has expired */
358 		if (endp == NULL) return(1);	/* nothing is returned */
359 
360 		(void) strncpy(w, cp, endp - cp);
361 		w[endp - cp] = '\0';
362 		if (index(w,'/') != NULL){  /* old B news format */
363 /*
364  * The following gross hack is present because the old history file date
365  * format is braindamaged.  They like "mm/dd/yy hh:mm", which is useless
366  * for relative comparisons of dates using something like atoi() or
367  * strcmp.  So, this changes their format into yyyymmddhhmm.  Sigh.
368  *
369  * Modified to use 4 digit years, just in case anyone is still using B news
370  * but not tested here.  DG 29/11/99
371  *
372  * 01234567890123	("x" for cp[x])
373  * mm/dd/yy hh:mm 	(their lousy representation)
374  * yymmddhhmmss		(our good one)
375  * 012345678901		("x" for w[x])
376  */
377 		    w[0] = cp[6];		/* Years */
378 		    w[1] = cp[7];
379 		    w[2] = cp[0];		/* Months */
380 		    w[3] = cp[1];
381 		    w[4] = cp[3];		/* Days */
382 		    w[5] = cp[4];
383 		    w[6] = cp[9];		/* Hours */
384 		    w[7] = cp[10];
385 		    w[8] = cp[12];		/* Minutes */
386 		    w[9] = cp[13];
387 		    w[10] = '0';		/* Seconds are faked */
388 		    w[11] = '0';
389 		    w[12] = '\0';
390 
391 /* We now have YYMMDDHHMMSS - use dtol() to get something sensible for the
392  *  century and then use ltod() below to make it compatible with the way
393  *  the search is done.  Really it would be much better to compare everything
394  *  as time_t, but this means changing seekuntil() and all the underlying stuff.
395  *  Maybe for a subsequent release if anyone is bothered?  DG 29/11/99
396  */
397 		    qz = dtol(w);
398 		}
399 
400 	    else	/* convert new format to yyyymmddhhmmss */
401 		{
402 		    qz =atol(w);
403 	    }
404         }
405 	strcpy(w,ltod(qz));
406 	return (1);
407 }
408 
409 /*
410  * distmatch -- see if a file matches a set of distributions.
411  * We have to do this by (yech!) opening the file, finding
412  * the Distribution: line, if it has one, and seeing if the
413  * things match.
414  *
415  *	Parameters:	"distlist" is the distribution list
416  *			we want.
417  *			"distcount" is the count of distributions in it.
418  *			"grouplist" is the list of groups (articles)
419  *			for this line of the history file.  Note that
420  *			this isn't quite a filename.
421  *			"groupcount" is the count of groups in it.
422  *
423  *	Returns:	1 if the article is in the given distribution.
424  *			0 otherwise.
425  */
426 
427 int
distmatch(distlist,distcount,grouplist,groupcount)428 distmatch(distlist, distcount, grouplist, groupcount)
429 	char		*distlist[];
430 	int		distcount;
431 	char		*grouplist[];
432 	int		groupcount;
433 {
434 	register char	c;
435 	register char	*cp;
436 	register FILE	*fp;
437 	register int	i, j;
438 	char		buf[MAXBUFLEN];
439 
440 	(void) strcpy(buf, spooldir);
441 	(void) strcat(buf, "/");
442 	(void) strcat(buf, grouplist[0]);
443 
444 	for (cp = buf; *cp; cp++)
445 		if (*cp == '.')
446 			*cp = '/';
447 
448 	fp = fopen(buf, "r");
449 	if (fp == NULL) {
450 #ifdef SYSLOG
451 		syslog(LOG_ERR, "distmatch: fopen %s: %m", buf);
452 #endif
453 		return (0);
454 	}
455 
456 	while (fgets(buf, sizeof (buf), fp) != NULL) {
457 		if ((c = buf[0]) == '\n')		/* End of header */
458 			break;
459 		if (c != 'd' && c != 'D')
460 			continue;
461 		cp = index(cp + 1, '\n');
462 		if (cp)
463 			*cp = '\0';
464 		cp = index(buf, ':');
465 		if (cp == NULL)
466 			continue;
467 		*cp = '\0';
468 		if (!strcasecmp(buf, "distribution")) {
469 			for (i = 0; i < distcount; ++i) {
470 				if (!strcasecmp(cp + 2, distlist[i])) {
471 					(void) fclose(fp);
472 					return (1);
473 				}
474 			}
475 			(void) fclose(fp);
476 			return (0);
477 		}
478 	}
479 
480 	(void) fclose(fp);
481 
482 	/*
483 	 * We've finished the header with no distribution field.
484 	 * So we'll assume that the distribution is the characters
485 	 * up to the first dot in the newsgroup name.
486 	 */
487 
488 	for (i = 0; i < groupcount; i++) {
489 		cp = index(grouplist[i], '.');
490 		if (cp)
491 			*cp = '\0';
492 		for (j = 0; j < distcount; j++)
493 			if (!strcasecmp(grouplist[i], distlist[j]))
494 				return (1);
495 	}
496 
497 	return (0);
498 }
499 
500 
501 /*
502  * get_histlist -- return a nicely set up array of newsgroups
503  * (actually, net.foo.bar/article_num) along with a count.
504  *
505  *	Parameters:		"array" is storage for our array,
506  *				set to point at some static data.
507  *				"list" is the history file newsgroup list.
508  *
509  *	Returns:		Number of group specs found.
510  *
511  *	Side effects:		Changes static data area.
512  *				Also puts null bytes in "list"
513  *
514  */
515 
516 int
get_histlist(array,list)517 get_histlist(array, list)
518 	char		***array;
519 	register char	*list;
520 {
521 	register int	histcount = 0;
522 	static  int	nalloc = 0;
523 	static	char	**hist_list = (char **) NULL;
524 
525 	if (nalloc == 0)
526 		hist_list = (char **) malloc(((nalloc = 10) + 1)*
527 					     sizeof(char *));
528 
529 	while (1) {
530 		for (; *list == ' ' || *list == '\t'; list++);
531 
532 		if (*list == '\0' || *list == '\n') break;
533 
534 		if (histcount >= nalloc)
535 			hist_list = (char **) realloc((char *) hist_list,
536 						      ((nalloc += 10) + 1)*
537 						      sizeof(char *));
538 
539 		if (hist_list == (char **) NULL) {
540 			fprintf(stderr, "get_histlist: Out of memory!\n");
541 			return(0);
542 		}
543 
544 		hist_list[histcount++] = list;
545 
546 		for (; *list && *list != ' ' && *list != '\t' && *list != '\n';
547 		     list++);
548 
549 		if (*list) *(list++) = '\0';
550 	}
551 
552 	hist_list[histcount] = (char *) NULL;
553 
554 	*array = hist_list;
555 	return (histcount);
556 }
557 
558 
559 /*
560  * get_nglist -- return a nicely set up array of newsgroups
561  * along with a count, when given an NNTP-spec newsgroup list
562  * in the form ng1,ng2,ng...
563  *
564  *	Parameters:		"array" is storage for our array,
565  *				set to point at some static data.
566  *				"list" is the NNTP newsgroup list.
567  *
568  *	Returns:		Number of group specs found.
569  *
570  *	Side effects:		Changes static data area.
571  */
572 
573 int
get_nglist(array,list)574 get_nglist(array, list)
575 	char		***array;
576 	char		*list;
577 {
578 	register char	*cp;
579 	register int	ngcount;
580 
581 	for (cp = list; *cp != '\0'; ++cp)
582 		if (*cp == ',')
583 			*cp = ' ';
584 
585 	ngcount = parsit(list, array);
586 
587 	return (ngcount);
588 }
589