1 /*
2  * fileart - file an article, given its temporary file name and its headers
3  *
4  * It may be desirable to, some day, prevent cross-postings across
5  * "universes", where a universe might be "alt" or "comp,news".
6  *
7  * There are three classes of newsgroup for the purposes of filing:
8  * "wanted" (in the active file and missing the "x" flag);
9  * "not wanted" ("x"ed in active, or not in active and not matched by sys
10  *	file's subscription list for this machine), so ignore it; or
11  * "don't know it" (not in active and matched by subscription list,
12  *	so file the article in junk once, iff there are no good groups).
13  * junk *must* be in the active file or it's an error (ST_DROPPED),
14  * but junk may have an "x" flag to prevent filing.
15  *
16  * Use the active file 'x' flag to snuff groups quietly, even when your
17  * subscription list permits them, without filing in junk.
18  *
19  * Constraints.
20  *
21  * Article filing is more subtle than it looks at first, and there are quite
22  * a few constraints on it.  The problems are primarily with cross-posted
23  * articles.  We prefer to make real Unix links where possible, so that
24  * each newsgroup mentioned costs only a directory entry and each article
25  * an i-node.  Where this isn't feasible (the news spool is split across
26  * file systems or the operating system doesn't support links), we try to
27  * make symbolic links.  Where that isn't feasible (the underlying
28  * operating system doesn't support symbolic links), we make copies; this
29  * is costly in disk space and bandwidth, but provides a worst-case
30  * fall-back strategy.
31  *
32  * To complicate the situation, we may have to create a temporary link for
33  * an article with a message header too large to fit in memory, so that we
34  * can dump what we have in memory to disk and thence copy the rest of the
35  * article there.  And if we must generate an Xref: header (either because
36  * the article is cross-posted or because we were asked on the command line
37  * to do so), we clearly must emit it before we emit any of the message body,
38  * so the names of the links must all be known for certain upon reaching
39  * the end of the message header, which means we must at least have created
40  * all the links by then, even if they aren't all populated.  In fact, on
41  * systems where we must make copies, we will have to go back and make a
42  * second pass to populate one link per filesystem.
43  *
44  * It is tempting to mandate that message headers shall be small, but they
45  * continue to grow over time and gatewayed RFC 822 mail messages may contain
46  * arbitrary numbers of large Received: headers, for example.
47  */
48 
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <ctype.h>
52 #include <string.h>
53 #include <errno.h>
54 #include "fixerrno.h"
55 #include <sys/types.h>
56 #include <sys/stat.h>
57 #include <unistd.h>
58 
59 #include "libc.h"
60 #include "news.h"
61 #include "config.h"
62 #include "headers.h"
63 #include "relay.h"
64 #include "active.h"
65 #include "history.h"
66 #include "ngmatch.h"
67 #include "system.h"
68 #include "mkdirs.h"
69 #include "rerror.h"
70 
71 #define XREFDELIM ':'
72 
73 /*
74  * If a_unlink is true, there is a temporary link.
75  *
76  * If tmplink is false, use a_tmpf to hold the name of the first permanent link,
77  * and art->a_artf as its stdio stream.  !tmplink means "Newsgroups:" was seen
78  * in time, and is normally true.
79  * If tmplink is true, just make links to a_tmpf, which is already open as
80  * art->a_artf.  This is only the case if we had to dump the headers early.
81  */
82 #define tmplink(art)	(art)->a_unlink
83 
84 /* imports */
85 extern void prefuse(register struct article *art);
86 
87 
88 /* privates */
89 struct link {
90 	char	*l_grp;			/* group name, not directory */
91 	const char 	*l_num;			/* NULL or article number */
92 	char	l_type;			/* 's', 'l' or '\0' */
93 	boolean	l_fillme;		/* still needs work? */
94 	dev_t	l_dev;			/* -1 if not known */
95 };
96 static struct link *links;		/* array of links */
97 static struct link *link1;		/* first permanent link */
98 static struct link *linklim;		/* just past last link in use */
99 static struct link templink;
100 static struct link *tlp;
101 static long artnum;			/* asgnartnum sets artnum */
102 static int goodngs;			/* asgnartnum reads goodngs */
103 static int junkgroups;			/* count "junked" groups */
104 static boolean debug = NO;
105 static boolean slinknoted = NO;		/* have we noted use of symlinks? */
106 extern char *slinkfile;			/* in relaynews.c */
107 
108 static char dbglink[] = "linking `%s' to `%s'... ";
109 static char dbgsymlink[] = "symlinking `%s' to `%s'... ";
110 static char dbgempty[] = "couldn't link or symlink; making empty `%s'... ";
111 static char dbgopen[] = "opening `%s'... ";
112 static char logxcljunk[] = "no known groups in `%s' and %s group is excluded in active\n";
113 static char lognojunk[] = "no known groups in `%s' and no %s group\n";
114 static char logxcl[] = "all groups `%s' excluded in active\n";
115 static char dbgcopy[] = "couldn't link or symlink; copying `%s' to `%s'... ";
116 
117 /* forward */
118 STATIC int lnkcmp();
119 
120 void
filedebug(state)121 filedebug(state)		/* set debugging state */
122 boolean state;
123 {
124 	debug = state;
125 }
126 
127 /* Append ng/artnumstr to art's list of files, and bump goodngs. */
128 STATIC void
gotgoodng(art,lp)129 gotgoodng(art, lp)
130 struct article *art;
131 struct link *lp;
132 {
133 	++goodngs;
134 	histupdfiles(art, lp->l_grp, lp->l_num);
135 }
136 
137 STATIC void
filllink(lp,grp,num,type,fillme,dev)138 filllink(lp, grp, num, type, fillme, dev)
139 register struct link *lp;
140 const char *grp, *num;
141 char type;
142 boolean fillme;
143 dev_t dev;
144 {
145 	lp->l_grp = strdup(grp);
146 	lp->l_num = num;
147 	lp->l_type = type;
148 	lp->l_fillme = fillme;
149 	lp->l_dev = dev;
150 }
151 
152 STATIC char *					/* malloced */
linkname(lp)153 linkname(lp)
154 register struct link *lp;
155 {
156 	register char *name;
157 
158 	if (lp->l_grp == NULL || lp->l_grp[0] == '\0')
159 		name = strsave(lp->l_num);	/* mostly for SPOOLTMP */
160 	else {
161 		name = str3save(lp->l_grp, SFNDELIM, lp->l_num);
162 		mkfilenm(name);
163 	}
164 	return name;
165 }
166 
167 STATIC int
trylink(olp,artname,lp)168 trylink(olp, artname, lp)
169 char *artname;
170 register struct link *olp, *lp;
171 {
172 	register int worked = NO;
173 
174 	if (newsconf.nc_link) {			/* e.g. Unix */
175 		char *oname = linkname(olp);
176 
177 		if (debug)
178 			(void) fprintf(stderr, dbglink, oname, artname);
179 		worked = link(oname, artname) == 0;
180 		if (worked && lp != NULL) {
181 			lp->l_type = 'l';
182 			lp->l_dev = olp->l_dev;
183 		}
184 		free(oname);
185 	}
186 	return worked;
187 }
188 
189 STATIC int
trysymlink(olp,artname,lp)190 trysymlink(olp, artname, lp)
191 char *artname;
192 register struct link *olp, *lp;
193 {
194 	register int worked = NO;
195 
196 	if (newsconf.nc_symlink) {			/* e.g. 4.2, Eunice */
197 		char *oname = linkname(olp);
198 
199 		if (debug)
200 			(void) fprintf(stderr, dbgsymlink, fullartfile(oname),
201 				artname);
202 		worked = symlink(fullartfile(oname), artname) == 0;
203 		if (worked && lp != NULL)
204 			lp->l_type = 's';
205 		if (worked && !slinknoted && slinkfile != NULL) {
206 			register FILE *f = fopen(slinkfile, "w");
207 
208 			if (f != NULL) {
209 				fprintf(f, "%ld\n", (long)time((time_t *)NULL));
210 				fclose(f);
211 			}
212 			slinknoted = 1;
213 		}
214 		free(oname);
215 	}
216 	return worked;
217 }
218 
219 /*
220  * we have to create the links before we have seen the entire article,
221  * so just make empty links for now; later on, we will copy into them.
222  */
223 STATIC int
trycopy(artname,lp)224 trycopy(artname, lp)
225 char *artname;
226 register struct link *lp;
227 {
228 	register FILE *out;
229 	register int worked = NO;
230 	struct stat statb;
231 
232 	if (debug)
233 		(void) fprintf(stderr, dbgempty, artname);
234 	out = fopenexcl(artname);
235 	worked = out != NULL;
236 	if (worked) {
237 		lp->l_fillme = YES;		/* revisit me later */
238 		lp->l_type = 'l';
239 		if (fstat(fileno(out), &statb) >= 0)
240 			lp->l_dev = statb.st_dev;
241 		(void) fclose(out);
242 	}
243 	return worked;
244 }
245 
246 /*
247  * If we get here, things look pretty bleak.
248  * Links to the first link have failed.
249  * We either have no link facilities at all (e.g. Plan 9),
250  * or we're trying to link across file systems and can't (e.g. V7).
251  * Making copies wastes space, so try to make a link
252  * to any other link, if possible, first.
253  * If all else fails, make a copy; with luck a later link can be made to it.
254  */
255 STATIC int
tryanything(olp,artname,lp)256 tryanything(olp, artname, lp)
257 char *artname;
258 register struct link *olp, *lp;
259 {
260 	register int worked = NO;
261 	register struct link *plp;
262 	char *destdir = strsave(artname);
263 	char *slp = strrchr(destdir, FNDELIM);
264 	struct stat statb;
265 
266 	/* have we got a link on this device already? */
267 	if (slp != NULL)
268 		*slp = '\0';		/* form directory name */
269 	if (stat(destdir, &statb) >= 0)
270 		lp->l_dev = statb.st_dev;
271 	free(destdir);
272 	for (plp = link1; plp < lp; plp++)
273 		if (plp->l_dev == lp->l_dev && plp->l_dev != (dev_t)-1)
274 			break;
275 	if (plp < lp) {			/* yes, we do */
276 		if (plp->l_type == 'l') {
277 			worked = trylink(plp, artname, lp);
278 			if (!worked)
279 				worked = trysymlink(link1, artname, lp);
280 			/* else make a copy */
281 		} else if (plp->l_type == 's' && olp->l_type != 's')
282 			worked = trysymlink(link1, artname, lp);
283 	}
284 	if (!worked)
285 		worked = trycopy(artname, lp);
286 	return worked;
287 }
288 
289 void
filetmp(art)290 filetmp(art)				/* make temporary link */
291 register struct article *art;
292 {
293 	if (art->a_artf == NULL) {
294 		nnfree(&art->a_tmpf);
295 		art->a_tmpf = strsave(SPOOLTMP);
296 		(void) mktemp(art->a_tmpf);
297 		art->a_unlink = YES;
298 		art->a_artf = fopenwclex(art->a_tmpf, "w+");
299 		if (art->a_artf == NULL)
300 			persistent(art, '\0', "", "");	/* can't open article */
301 	}
302 }
303 
304 STATIC boolean
fileopen(art,lp,artname)305 fileopen(art, lp, artname)		/* create first permanent link */
306 register struct article *art;
307 register struct link *lp;
308 char *artname;
309 {
310 	register boolean worked = NO;
311 	struct stat statb;
312 
313 	if (debug)
314 		(void) fprintf(stderr, dbgopen, artname);
315 	nnfree(&art->a_tmpf);
316 	art->a_tmpf = strsave(artname);
317 	art->a_artf = fopenexcl(art->a_tmpf);
318 	worked = art->a_artf != NULL;
319 	if (worked && lp != NULL) {
320 		lp->l_type = 'l';
321 		if (fstat(fileno(art->a_artf), &statb) >= 0)
322 			lp->l_dev = statb.st_dev;
323 	}
324 	return worked;
325 }
326 
327 /*
328  * Try to make a link of "olp" to artname.
329  * If "olp" is NULL, record and open artname iff no
330  * link yet exists to that name (by any file, to avoid overwriting
331  * existing articles, e.g. due to an out-of-date active file).
332  * Result goes in "lp".
333  */
334 STATIC boolean
openorlink(artname,art,olp,lp)335 openorlink(artname, art, olp, lp)
336 register char *artname;
337 register struct article *art;
338 struct link *olp, *lp;
339 {
340 	register boolean worked = NO;	/* open or link worked? */
341 
342 	errno = 0;			/* paranoia */
343 	if (olp == NULL)
344 		worked = fileopen(art, lp, artname);
345 	else {				/* temp. or perm. link(s) exist */
346 		/* try links to the first link first */
347 		worked = trylink(olp, artname, lp);
348 		if (!worked && errno != ENOENT && olp->l_type != 's')
349 			worked = trysymlink(link1, artname, lp);
350 		if (!worked && errno != ENOENT)	/* e.g. v7 over fs's, Plan 9 */
351 			worked = tryanything(olp, artname, lp);
352 	}
353 	if (debug) {
354 		if (worked)
355 			(void) fprintf(stderr, "success.\n");
356 		else
357 			warning("failed.", "");
358 	}
359 	return worked;
360 }
361 
362 /*
363  * Try to link art to artname.
364  * If the attempt fails, maybe some intermediate directories are missing,
365  * so create any missing directories and try again.  If the second attempt
366  * also fails, look at errno; if it is EEXIST, artname already exists
367  * (presumably because the active file is out of date, or the existing
368  * file is a directory such as net/micro/432), so indicate that higher
369  * levels should keep trying, otherwise we are unable to create the
370  * necessary directories, so complain and set bad status in art.
371  *
372  * Returns YES iff there is no point in trying to file this article again,
373  * usually because it has been successfully filed, but sometimes because
374  * the necessary directories cannot be made.
375  */
376 STATIC boolean
mkonelink(art,olp,lp,lnkstatp,artname)377 mkonelink(art, olp, lp, lnkstatp, artname)
378 register struct article *art;
379 struct link *olp, *lp;
380 statust *lnkstatp;
381 register char *artname;
382 {
383 	if (openorlink(artname, art, olp, lp))
384 		return YES;
385 	else if (errno == ENOENT) {
386 		(void) mkdirs(artname, getuid(), getgid());
387 		if (openorlink(artname, art, olp, lp))
388 			return YES;
389 		else if (errno != EEXIST) {
390 			persistent(NOART, 'f', "can't link to `%s'", artname);
391 			*lnkstatp |= ST_DROPPED; /* really can't make a link */
392 			return YES;		/* hopeless - give up */
393 		} else
394 			return NO;
395 	} else
396 		return NO;
397 }
398 
399 /*
400  * Construct a link name (slashng/artnum) for this article,
401  * and try to link "olp" to it, with results in "lp".
402  *
403  * We changed directory to spooldir in main(), so the generated name
404  * is relative to spooldir, therefore artname can be used as is.
405  *
406  * Return value is the same as mkonelink's.
407  */
408 STATIC boolean
tryartnum(art,olp,lp,lnkstatp)409 tryartnum(art, olp, lp, lnkstatp)
410 struct article *art;
411 register struct link *olp, *lp;
412 statust *lnkstatp;
413 {
414 	register char *artname;		/* article file name */
415 	register boolean ret;
416 	char artnumstr[30];
417 
418 	(void) sprintf(artnumstr, "%ld", artnum);
419 	nnfree(&lp->l_num);		/* in case we are trying again */
420 	lp->l_num = strsave(artnumstr);
421 	artname = linkname(lp);
422 	ret = mkonelink(art, olp, lp, lnkstatp, artname);
423 	free(artname);
424 	return ret;
425 }
426 
427 /*
428  * Assign a permanent name and article number to the existing link "olp",
429  * in newsgroup lp->l_grp & store the ascii form of the article
430  * number into lp->l_num, returning the article number in "artnum".
431  * If lp->l_num is non-null initially, it's the ascii article number
432  * to file under.
433  *
434  * If tmplink is false and goodngs is zero, set inname to artname,
435  * fopen artname and store the result in art->a_artf.
436  */
437 STATIC statust
asgnartnum(art,olp,lp)438 asgnartnum(art, olp, lp)
439 register struct article *art;
440 register struct link *olp, *lp;
441 {
442 	statust lnkstat = ST_OKAY;
443 
444 	/* field active 'x' flag: don't file this group, quietly */
445 	if (unwanted(lp->l_grp)) {
446 		artnum = -1;
447 		logaudit(art, 'a', "group `%s' is 'x'ed", lp->l_grp);
448 		return ST_REFUSED;
449 	}
450 	if (lp->l_num != NULL) {		/* number supplied? */
451 		artnum = atol(lp->l_num);	/* believe number */
452 		if (!tryartnum(art, olp, lp, &lnkstat)) {
453 			char *s1;
454 			char number[30];
455 
456 			(void) sprintf(number, "%ld", artnum);
457 			s1 = str3save("article #", number,
458 				      " in group `%s' supplied but occupied!");
459 			errno = 0;
460 			transient(NOART, 'f', s1, lp->l_grp);
461 			free(s1);
462 			lnkstat |= ST_DROPPED;
463 		}
464 	} else
465 		while ((artnum = nxtartnum(lp->l_grp)) >= 1 &&
466 		    !tryartnum(art, olp, lp, &lnkstat))
467 				;
468 	return lnkstat;
469 }
470 
471 /*
472  * File once in "junk" iff no ngs were filed due to absence from
473  * active, but some were permitted by sys.  This will make one junk
474  * link, no matter how many bad groups, and only if all are bad
475  * (e.g. rec.drugs,talk.chew-the-fat).
476  */
477 STATIC void
mkjunklink(art)478 mkjunklink(art)
479 register struct article *art;
480 {
481 	register statust lnkstat = ST_OKAY;
482 	struct link jlink;
483 	register struct link *jlp = &jlink;
484 
485 	if (goodngs != 0)
486 		return;		/* shouldn't be here, with valid groups */
487 
488 	if (junkgroups > 0) {
489 		/* All groups were "junked"; try to file this article in junk */
490 		filllink(jlp, JUNK, (char *)NULL, '\0', NO, (dev_t)-1);
491 		lnkstat = asgnartnum(art, tlp, jlp);
492 		art->a_status |= lnkstat;
493 		if (artnum >= 1 && lnkstat == ST_OKAY) {
494 			gotgoodng(art, jlp);
495 			logaudit(art, '\0', "article junked", "");
496 			art->a_status |= ST_JUNKED;
497 		} else
498 		/* couldn't file article in junk.  why? */
499 		if (lnkstat&ST_REFUSED) {	/* junk is 'x'ed */
500 			prefuse(art);
501 			(void) printf(logxcljunk, art->h.h_ngs, JUNK);
502 		} else {			/* junk is missing? */
503 			persistent(art, 'f',
504 		"can't file in %s group; is it absent from active?", JUNK);
505 			art->a_status |= ST_REFUSED;
506 			prefuse(art);
507 			(void) printf(lognojunk, art->h.h_ngs, JUNK);
508 		}
509 	} else {
510 		/*
511 		 * Groups were permitted by subscription list, but all
512 		 * were 'x'ed in active, or otherwise refused.
513 		 */
514 		if (opts.histreject)
515 			history(art, NOLOG);
516 		prefuse(art);
517 		(void) printf(logxcl, art->h.h_ngs);
518 		transient(art, '\0', "article rejected due to groups", "");
519 	}
520 }
521 
522 static char *
xrefngs(art)523 xrefngs(art)		/* hack up Xref: value and return ng:num pairs */
524 register struct article *art;
525 {
526 	register char *ngs = NULL, *site, *groups;
527 
528 	errno = 0;
529 	if (art->h.h_xref == NULL)
530 		transient(art, 'b', "no Xref: in believe-Xref mode", "");
531 	else {
532 		for (site = groups = skipsp(art->h.h_xref);
533 		     *groups != '\0' && isascii(*groups) && !isspace(*groups);
534 		     groups++)
535 			;			/* skip over site name */
536 		if (*groups != '\0')
537 			*groups++ = '\0';	/* terminate site name */
538 		groups = skipsp(groups);
539 		if (!STREQ(site, opts.blvsite))
540 			transient(art, 'b',
541 	"article received from wrong site `%s' in believe-Xref mode", site);
542 		else
543 			ngs = groups;		/* site is cool; rest is ngs */
544 	}
545 	return ngs;
546 }
547 
548 static char *
histngs(art)549 histngs(art)
550 register struct article *art;
551 {
552 	register char *ngs = NULL, *site, *groups = NULL, *histent = NULL;
553 	static char *lasthist;
554 
555 	if (lasthist != NULL)
556 		free(lasthist);
557 	lasthist = histent = gethistory(art->h.h_msgid);
558 	errno = 0;
559 	if (histent == NULL)
560 		transient(art, 'b', "no history entry in duplicate-feed mode",
561 			"");
562 	else if ((groups = findfiles(histent)) == NULL)
563 		transient(art, 'b',
564 			"expired history entry in duplicate-feed mode", "");
565 	else {
566 		site = strsvto(art->h.h_path, '!');
567 		if (!STREQ(site, opts.dupsite))
568 			transient(art, 'b',
569 	"article received from wrong site `%s' in duplicate-feed mode", site);
570 		else {
571 			ngs = groups;
572 			/* convert group/art list to group:art list */
573 			stranslit(ngs, FNDELIM, XREFDELIM);
574 		}
575 		free(site);
576 	}
577 	return ngs;
578 }
579 
580 /*
581  * extract list of newsgroups (and possibly article numbers) from article
582  * headers and history file, as options indicate.  If article numbers are
583  * being dictated by incoming Xref: or old history entry, the article numbers
584  * will be attached to the end of the group names by a colon as in Xref:
585  * (e.g. comp.lang.c:25780,general:12).
586  */
587 static char *
extngs(art)588 extngs(art)
589 register struct article *art;
590 {
591 	register char *ngs = NULL, *groups;
592 
593 	errno = 0;
594 	if (opts.blvxref)
595 		ngs = xrefngs(art);
596 	else if (opts.dupsokay)
597 		ngs = histngs(art);
598 	else {
599 #ifndef NO_PLUS_CONTROL
600 		if (art->h.h_ctlcmd) {
601 		        /* File control articles in "control.<type>"
602 			   if that exists, in "control" otherwise. */
603 		        groups = str3save(CONTROL, ".", art->h.h_ctlcmd);
604 			stranslit(groups, ' ', '\0');    /* kill args */
605 			stranslit(groups, '\t', '\0');
606 			if (!actlook(groups)) {
607 			      groups = strdup(CONTROL);
608 			}
609 		} else
610 		        groups = art->h.h_ngs;
611 #else
612 		groups = art->h.h_ctlcmd != NULL? CONTROL: art->h.h_ngs; /* NCMP */
613 #endif
614 		if (strchr(groups, XREFDELIM) != NULL)
615 			transient(art, 'b',
616 				"colon not permitted in Newsgroups: list", "");
617 		else
618 			ngs = groups;
619 	}
620 	if (ngs == NULL)
621 		transient(art, '\0', "no groups in headers", "");
622 	else {
623 		/* convert any whitespace to commas for fileart */
624 		stranslit(ngs, ' ', NGSEP);
625 		stranslit(ngs, '\t', NGSEP);		/* probably overkill */
626 	}
627 	return ngs;
628 }
629 
630 STATIC boolean
linkonce(art,olp,lp)631 linkonce(art, olp, lp)
632 register struct article *art;
633 register struct link *olp, *lp;
634 {
635 	register statust lst = asgnartnum(art, olp, lp);
636 	register boolean ret;
637 
638 	ret = artnum >= 1 && lst == ST_OKAY;
639 	if (ret)
640 		gotgoodng(art, lp);
641 	else if (!(lst&ST_REFUSED) && ngpatmat(oursys()->sy_trngs, lp->l_grp))
642 	    	++junkgroups;
643 	art->a_status |= lst&~ST_REFUSED;
644 	return ret;
645 }
646 
647 STATIC struct link *				/* malloced */
parsengs(ngs)648 parsengs(ngs)					/* parse ngs into links array */
649 register char *ngs;
650 {
651 	register char *ng, *comma, *numb;
652 	register struct link *lp;
653 
654 	links = lp = (struct link *)
655 		nemalloc((unsigned)(charcount(ngs, NGSEP)+1) * sizeof *links);
656 	for (; ngs != NULL; ngs = comma) {
657 		ngs = skipsp(ngs);		/* allow for user stupidity */
658 		STRCHR(ngs, NGSEP, comma);
659 		if (comma != NULL)
660 			*comma = '\0';		/* will be restored below */
661 		STRCHR(ngs, XREFDELIM, numb);	/* in case of opts.blvxref */
662 		if (numb != NULL)		/* number supplied? */
663 			*numb++ = '\0';		/* cleave number from group */
664 
665 		ng = realngname(ngs);
666 		if (ng == NULL)
667 			ng = strsave(ngs);
668 		if (ng[0] != '\0')		/* ignore null groups */
669 			filllink(lp++, ng, (numb == NULL? NULL: strsave(numb)),
670 				'\0', NO, (dev_t)-1);
671 		else
672 			free(ng);
673 		if (numb != NULL)		/* number supplied? */
674 			*--numb = XREFDELIM;	/* restore lost byte */
675 		if (comma != NULL)
676 			*comma++ = NGSEP;	/* step past comma */
677 	}
678 	linklim = lp;
679 	return links;
680 }
681 
682 /*
683  * Store in spooldir.  Link temp file to spooldir/ng/article-number
684  * for each ng.  Control messages go in CONTROL.
685  *
686  * The plan is: for each newsgroup, map the group name to its local
687  * equivalent (due to = active flag, etc.) for filing, try to file the
688  * article, if (no such group in active or link failed, and the group
689  * wasn't 'x'ed in active, but our subscription list permits this group),
690  * then set flag to file it under "junk" later, if the article number was
691  * assigned and the link succeeded, then update art->a_files list for history,
692  * and finally clear ST_REFUSED in the article's status, since only this
693  * single group was refused (by asgnartnum).
694  */
695 STATIC void
mklinks(art)696 mklinks(art)
697 register struct article *art;
698 {
699 	register struct link *lp, *olp;
700 	register char *ngs;
701 	struct stat statb;
702 
703 	if (art->a_filed)
704 		return;				/* don't file twice */
705 	art->a_filed = YES;			/* make a note */
706 	if (art->a_status&ST_REFUSED)
707 		canthappen(art, 'i',
708 			"mklinks called with ST_REFUSED set (can't happen)", "");
709 	artnum = goodngs = junkgroups = 0;
710 	ngs = extngs(art);
711 	if (ngs == NULL) {
712 		link1 = links = NULL;
713 		return;
714 	}
715 	link1 = links = parsengs(ngs);
716 
717 	/* sort links and strip duplicates (due to =grp or user stupidity) */
718 	qsort((char *)links, (size_t)(linklim - links), sizeof *links, lnkcmp);
719 	olp = links;
720 	for (lp = links + 1; lp < linklim; lp++)
721 		if (STREQ(olp->l_grp, lp->l_grp))
722 			lp->l_grp[0] = '\0';	/* mark the link as a dup */
723 		else
724 			olp = lp;		/* starting a new group */
725 
726 	/* make first permanent link without using symlinks. */
727 	if (tmplink(art)) {  /* we've already got one; belatedly register it */
728 		tlp = &templink;
729 		/* 's' is a white lie: it prevents symlinks to this link */
730 		filllink(tlp, "", art->a_tmpf, 's', NO, (art->a_artf != NULL &&
731 			fstat(fileno(art->a_artf), &statb) >= 0?
732 			statb.st_dev: (dev_t)-1));
733 	} else
734 		tlp = NULL;
735 	for (lp = links; lp < linklim; lp++)
736 		if (lp->l_grp[0] != '\0')	/* not a duplicate link? */
737 	 		if (linkonce(art, tlp, lp)) {
738 				link1 = lp++;
739 				break;	/* kept trying until the sucker took */
740 			}
741 
742 	/* create all links after 1st that took; copies to be filled in later */
743 	for (; lp < linklim; lp++)
744 		if (lp->l_grp[0] != '\0')	/* not a duplicate link? */
745 			(void) linkonce(art, link1, lp);
746 }
747 
748 STATIC int
lnkcmp(a1,a2)749 lnkcmp(a1, a2)
750 char *a1, *a2;
751 {
752 	return strcmp(((struct link *)a1)->l_grp, ((struct link *)a2)->l_grp);
753 }
754 
755 /*
756  * File in the spool directory the article in art & fill in art->a_files.
757  * Generate Xref: header if needed (successfully cross-posted).
758  * (N.B.: Thus must be called before emitting any article body!)
759  */
760 void
fileart(art)761 fileart(art)
762 register struct article *art;
763 {
764 	mklinks(art);
765 	if (goodngs == 0)
766 		mkjunklink(art);
767 	/* -g or article crossposted, and article is open? */
768 	if ((opts.genxref && goodngs > 0 || goodngs > 1) && art->a_artf != NULL)
769 		emitxref(art);
770 }
771 
772 /*
773  * we have to create the links before we have seen the entire article,
774  * so just make empty links for now; later on, we will copy into them.
775  */
776 STATIC int
fill(oname,in,artname,lp)777 fill(oname, in, artname, lp)
778 char *oname, *artname;
779 FILE *in;
780 register struct link *lp;
781 {
782 	register FILE *out;
783 	register int worked = NO;
784 	struct stat statb;
785 
786 	if (debug)
787 		(void) fprintf(stderr, dbgcopy, oname, artname);
788 	out = fopen(artname, "w");
789 	worked = out != NULL;
790 	if (worked) {
791 		register int cnt;
792 		char buf[8192];
793 
794 		rewind(in);
795 		while ((cnt = fread(buf, 1, sizeof buf, in)) > 0)
796 			(void) fwrite(buf, 1, cnt, out);
797 		lp->l_type = 'l';
798 		if (fstat(fileno(out), &statb) >= 0)
799 			lp->l_dev = statb.st_dev;
800 		lp->l_fillme = NO;
801 		worked = fclose(out) != EOF;
802 	}
803 	return worked;
804 }
805 
806 void
mkcopies(art)807 mkcopies(art)			/* if copies must be made, fill them in here */
808 register struct article *art;
809 {
810 	register struct link *lp;
811 
812 	if (art->a_status&ST_REFUSED)
813 		canthappen(art, 'i',
814 			"mkcopies called with ST_REFUSED set (can't happen)", "");
815 	if (links == NULL)	/* fileart failed? */
816 		return;
817 
818 	/* fill in any empty links */
819 	for (lp = link1; lp < linklim; lp++) {
820 		if (lp->l_fillme) {
821 			char *artname = linkname(lp);
822 
823 			if (!fill(art->a_tmpf, art->a_artf, artname, lp))
824 				persistent(art, '\0',
825 					"can't fill an empty link", "");
826 			free(artname);
827 		}
828 		nnfree(&lp->l_grp);
829 		nnfree(&lp->l_num);
830 	}
831 	free((char *)links);
832 }
833