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