1 /*
2  * relaynews - relay Usenet news (version C)
3  * See the file COPYRIGHT for the copyright notice.
4  *
5  * Written by Geoff Collyer, 15-20 November 1985 and revised periodically
6  * since.
7  *
8  * relaynews parses article headers, rejects articles by newsgroup &
9  * message-id, files articles, updates the active & history files,
10  * transmits articles, and honours (infrequent) control messages, which do
11  * all sorts of varied and rococo things.  Most control messages are
12  * implemented by separate programs.  relaynews reads a "sys" file to
13  * control the transmission of articles but can function as a promiscuous
14  * leaf node without one.  See Internet RFC 1036 for the whole story and
15  * RFC 850 for background.
16  *
17  * relaynews must be invoked under the news user and group ids,
18  * typically from cron.  It must *not* be made setuid nor setgid.
19  *
20  * A truly radical notion: people may over-ride via environment variables
21  * the compiled-in default directories so IHCC kludges are not needed and
22  * testing is possible (and encouraged) in alternate directories.  This
23  * does cause a loss of privilege, to avoid spoofing.
24  *
25  * The disused old unbatched ihave/sendme protocol is gone because it was
26  * too wasteful; use the batched form instead (see the ihave sys flag
27  * ("I") instead).
28  */
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <ctype.h>
33 #include <string.h>
34 #include <signal.h>		/* to make locking safe */
35 #include <errno.h>
36 #include "fixerrno.h"
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 
41 #include "libc.h"
42 #include "news.h"
43 #include "config.h"
44 #include "fgetfln.h"
45 #include "active.h"
46 #include "headers.h"
47 #include "relay.h"
48 #include "history.h"
49 #include "fileart.h"
50 #include "transmit.h"
51 #include "caches.h"
52 #include "io.h"
53 
54 /* imports */
55 extern statust cpinsart(FILE *in, register const char *inname, long maxima, boolean blvmax);
56 extern statust clshdrstrm(void);
57 extern void persistent(void *art, int code, const char *fmt, const char *arg);
58 extern void morefds(void);
59 
60 /* exports */
61 const char *progname = "relaynews";
62 struct options opts;
63 struct newsconf newsconf;
64 char *slinkfile = NULL;
65 
66 /* privates */
67 static boolean uunlink = NO;
68 
69 /*
70  * SystemV getcwd simulation, courtesy peter honeyman
71  */
72 static char *
getpwd(path,size)73 getpwd(path, size)
74 register char *path;
75 int size;
76 {
77 	register FILE *fp;
78 
79 	fp = popen("PATH=/bin:/usr/bin pwd 2>/dev/null", "r");
80 	if (fp == NULL)
81 		return NULL;
82 	if (fgets(path, size, fp) == NULL)
83 		path = NULL;			/* signal failure */
84 	else
85 		trim(path);
86 	return (pclose(fp) != 0? NULL: path);
87 }
88 
89 /*
90  * if argv contains relative file name arguments, save current directory name
91  * in malloced memory, in opts.currdir.
92  * then change directory to the spool directory ($NEWSARTS).
93  */
94 void
getwdandcd(argc,argv)95 getwdandcd(argc, argv)
96 int argc;
97 char **argv;
98 {
99 	register int argind;
100 	boolean needpwd = NO;
101 	char dirtmp[MAXPATH];			/* much bigger than needed */
102 
103 	if (opts.currdir == NULL) {			/* not yet set? */
104 		for (argind = optind; argind < argc; argind++)
105 			if (argv[argind][0] != FNDELIM)
106 				needpwd = YES;
107 
108 		opts.currdir = "/???";		/* pessimism */
109 		if (needpwd && getpwd(dirtmp, sizeof dirtmp) != 0)
110 			opts.currdir = dirtmp;
111 		opts.currdir = strsave(opts.currdir); /* save a smaller copy */
112 	}
113 	cd(fullartfile((char *)NULL));		/* move to spool directory */
114 }
115 
116 /*
117  * reset various environmental things for safety: umask, alarm,
118  * environment variables (PATH, IFS), standard file descriptors,
119  * user & group ids.  May exit, so call before locking the news system.
120  */
121 /* ARGSUSED argv */
122 void
prelude(argv)123 prelude(argv)				/* setuid daemon prelude */
124 char **argv;
125 {
126 	register char *newpath;
127 
128 	(void) umask(newsumask());
129 	(void) alarm(0);		/* cancel any pending alarm */
130 	/* TODO: suppress chatter on failure here */
131 	newpath = str3save("PATH=", newspath(), "");
132 	if (newpath == NULL)
133 		exit(1);		/* no chatter until stdfdopen */
134 	if (putenv(newpath) ||
135 	    putenv(strdup("SHELL=/bin/sh")) ||
136 	    putenv(strdup("IFS= \t\n")))
137 		exit(1);		/* no chatter until stdfdopen */
138 	closeall(1);			/* closes all but std descriptors */
139 	stdfdopen();			/* ensure open standard descriptors */
140 }
141 
142 STATIC boolean
debugon(dbopt)143 debugon(dbopt)
144 register char *dbopt;
145 {
146 	statust status = YES;
147 
148 	for (; *dbopt != '\0'; dbopt++)
149 		switch (*dbopt) {
150 		case 'f':
151 			filedebug(YES);
152 			break;
153 		case 'h':
154 			hdrdebug(YES);
155 			break;
156 		case 'l':
157 			lockdebug(YES);
158 			break;
159 		case 'm':
160 			matchdebug(YES);
161 			break;
162 		case 't':
163 			transdebug(YES);
164 			break;
165 		default:
166 			status = NO;	/* unknown debugging option */
167 			(void) fprintf(stderr, "%s: bad -d %c\n",
168 				progname, *dbopt);
169 			break;
170 		}
171 	return status;
172 }
173 
174 /*
175  * parse options and set flags
176  */
177 void
procopts(argc,argv,optsp)178 procopts(argc, argv, optsp)
179 int argc;
180 char **argv;
181 register struct options *optsp;
182 {
183 	int c, errflg = 0;
184 
185 	while ((c = getopt(argc, argv, "a:b:c:d:hn:o:sux")) != EOF)
186 		switch (c) {
187 		case 'a':
188 			optsp->dupsokay = YES;
189 			optsp->dupsite = optarg;
190 			break;
191 		case 'b':
192 			optsp->blvxref = YES;
193 			optsp->blvsite = optarg;
194 			break;
195 		case 'c':
196 			if (optarg[0] != FNDELIM) {
197 				(void) fprintf(stderr,
198 					"%s: %s: must be absolute path\n",
199 					progname, optarg);
200 				exit(1);
201 			}
202 			optsp->currdir = optarg;
203 			break;
204 		case 'd':		/* -d debug-options; thanks, henry */
205 			if (!debugon(optarg))
206 				errflg++;	/* debugon has complained */
207 			break;
208 		case 'h':		/* keep no history of rejects */
209 			optsp->histreject = NO;
210 			break;
211 		case 'n':		/* note use of symlinks in this file */
212 			slinkfile = optarg;
213 			break;
214 		case 'o':
215 			/* "oldness": drop articles older than this many days */
216 			optsp->staledays = atol(optarg);
217 			break;
218 		case 's':		/* dropping input is serious (inews) */
219 			optsp->okrefusal = NO;
220 			break;
221 		case 'u':		/* unlink good batches when done */
222 			uunlink = YES;
223 			break;
224 		case 'x':
225 			optsp->genxref = NO;
226 			break;
227 		default:
228 			errflg++;
229 			break;
230 		}
231 	if (errflg) {
232 		(void) fprintf(stderr,
233 "usage: %s [-c currdir][-hsux][-d fhlmt][-o days][-b xrefsite][-a dupsite]\n",
234 			progname);
235 		exit(1);
236 	}
237 }
238 
239 /*
240  * Is line a batcher-produced line (#! rnews count)?
241  * If so, return the count through charcntp.
242  * This is slightly less convenient than sscanf, but a lot smaller.
243  */
244 boolean
batchln(line,charcntp)245 batchln(line, charcntp)
246 register char *line;
247 register long *charcntp;
248 {
249 	static char numbang[] = "#!";
250 	static char rnews[] = "rnews";
251 
252 	*charcntp = 0;
253 	if (!STREQN(line, numbang, STRLEN(numbang)))
254 		return NO;
255 	line = skipsp(line + STRLEN(numbang));
256 	if (!STREQN(line, rnews, STRLEN(rnews)))
257 		return NO;
258 	line = skipsp(line + STRLEN(rnews));
259 	if (isascii(*line) && isdigit(*line)) {
260 		*charcntp = atol(line);
261 		return YES;
262 	} else
263 		return NO;
264 }
265 
266 /*
267  * Unwind "in" and insert each article.
268  * For each article, call cpinsart to copy the article from "in" into
269  * a (temporary) file in the news spool directory and rename the temp file
270  * to the correct final name if it isn't right already.
271  *
272  * If the unbatcher gets out of sync with the input batch, the unbatcher
273  * will print and discard each input line until it gets back in sync.
274  */
275 statust
unbatch(in,inname)276 unbatch(in, inname)
277 register FILE *in;
278 const char *inname;
279 {
280 	register int c;
281 	/* register */ char *line;
282 	register statust status = ST_OKAY;
283 	int insynch = YES;
284 	long charcnt;
285 
286 	while (!(status&ST_NEEDATTN) && (c = getc(in)) != EOF) {
287 		(void) ungetc(c, in);
288 		while ((line = fgetln(in)) != NULL &&
289 		    !batchln(line, &charcnt)) {		/* returns charcnt */
290 			status |= ST_DROPPED|ST_NEEDATTN;	/* save batch */
291 			if (insynch)
292 				persistent(NOART, 'b',
293 					   "unbatcher out of synch in file %s",
294 					   inname);
295 			insynch = NO;
296 		}
297 		if (!feof(in) && charcnt > 0)	/* anything to do? */
298 			status |= cpinsart(in, inname, charcnt, YES);
299 	}
300 	if (ferror(in))
301 		errunlock("error reading `%s'", inname);
302 	return status;
303 }
304 
305 /*
306  * compute the largest number that can be stored in a long.  in theory,
307  * #define MAXLONG ((long)(~(unsigned long)0 >> 1))
308  * will do the job, but old compilers don't have "unsigned long", don't
309  * like casts in initialisers, or otherwise miscompute.
310  */
311 STATIC long
maxlong()312 maxlong()
313 {
314 	register int bits = 0;
315 	register unsigned word = 1;		/* "unsigned" avoids overflow */
316 	static long savemaxlong = 0;
317 
318 	if (savemaxlong > 0)
319 		return savemaxlong;
320 	for (bits = 0, word = 1; word != 0; word <<= 1)
321 		bits++;
322 	/* bits/sizeof word = bits per char; all bits on but the sign bit */
323 	savemaxlong = ~(1L << (bits/sizeof word * sizeof savemaxlong - 1));
324 	if (savemaxlong <= 0) {			/* sanity check */
325 		errno = 0;
326 		errunlock("maxlong is non-positive; your compiler is broken", "");
327 	}
328 	return savemaxlong;
329 }
330 
331 /*
332  * process - process input file
333  * If it starts with '#', assume it's a batch and unravel it,
334  * else it's a single article, so just inject it.
335  */
336 statust
process(in,inname)337 process(in, inname)
338 FILE *in;
339 const char *inname;
340 {
341 	register int c;
342 
343 	if ((c = getc(in)) == EOF)
344 		return ST_OKAY; 		/* normal EOF */
345 	(void) ungetc(c, in);
346 	if (c == '#')
347 		return unbatch(in, inname);
348 	else
349 		/* -SIZENUL is to avoid overflow later during +SIZENUL */
350 		return cpinsart(in, inname, maxlong() - SIZENUL, NO);
351 }
352 
353 /*
354  * process a (relative) file name.
355  * in unlink mode, try to unlink good batches, but not very hard.
356  */
357 statust
relnmprocess(name)358 relnmprocess(name)
359 char *name;
360 {
361 	register statust status = ST_OKAY;
362 	register FILE *in;
363 	register char *fullname = (name[0] != FNDELIM?
364 		str3save(opts.currdir, SFNDELIM, name): strsave(name));
365 
366 	in = fopenwclex(fullname, "r");
367 	if (in != NULL) {
368 		status |= process(in, fullname);
369 		(void) nfclose(in);
370 		if (uunlink && !(status&(ST_DROPPED|ST_SHORT)))
371 			(void) unlink(fullname);
372 	}
373 	free(fullname);
374 	return status;
375 }
376 
377 /*
378  * process files named as arguments (or implied)
379  */
380 statust
procargs(argc,argv)381 procargs(argc, argv)
382 int argc;
383 char **argv;
384 {
385 	register statust status = ST_OKAY;
386 
387 	if (optind == argc)
388 		status |= process(stdin, "(stdin)");
389 	else
390 		for (; optind < argc; optind++)
391 			status |= relnmprocess(argv[optind]);
392 	return status;
393 }
394 
395 /*
396  * main - take setuid precautions, switch to "news" ids, ignore signals,
397  * handle options, lock news system, process files & unlock news system.
398  */
399 int
main(argc,argv)400 main(argc, argv)
401 int argc;
402 char *argv[];
403 {
404 	statust status = ST_OKAY;
405 
406 	if (argc > 0)
407 		progname = argv[0];
408 	opts.okrefusal = YES;
409 	opts.genxref = YES;		/* needed for overview data */
410 	opts.histreject = YES;		/* retain history of rejects normally */
411 	opts.currdir = NULL;		/* pessimism */
412 #ifdef CSRIMALLOC
413 	{
414 		char *maldebug = getenv("MALDEBUG");
415 
416 		if (maldebug != NULL)
417 			mal_debug(atoi(maldebug));
418 		mal_leaktrace(0);	/* was 1 */
419 	}
420 #endif
421 	prelude(argv);		/* various precautions; switch to "news" */
422 
423 	/* ignore signals (for locking). relaynews runs quickly, so don't worry. */
424 	(void) signal(SIGINT, SIG_IGN);
425 	(void) signal(SIGQUIT, SIG_IGN);
426 	(void) signal(SIGHUP, SIG_IGN);
427 	(void) signal(SIGTERM, SIG_IGN);
428 	(void) signal(SIGPIPE, SIG_IGN);	/* we check write returns */
429 
430 	newsconf.nc_link = YES;		/* HACK */
431 	newsconf.nc_symlink = YES;	/* HACK */
432 	procopts(argc, argv, &opts);
433 
434 	(void) morefds();		/* ask Unix for more descriptors */
435 	newslock();			/* done here due to dbm internal cacheing */
436 
437 	getwdandcd(argc, argv);
438 	status |= procargs(argc, argv);
439 
440 	status |= synccaches();		/* being cautious: write & close caches */
441 	status |= closehist();
442 	status |= clshdrstrm();
443 	(void) fflush(stdout);		/* log file */
444 	(void) fflush(stderr);		/* errlog file */
445 
446 #ifdef notdef
447 #ifdef CSRIMALLOC
448 	mal_dumpleaktrace(fileno(stderr));
449 #endif
450 #endif
451 	newsunlock();
452 	exit((status&ST_NEEDATTN)? 2: (status != ST_OKAY? 1: 0));
453 	/* NOTREACHED */
454 }
455