chiark / gitweb /
WIP inotify configure test
[inn-innduct.git] / frontends / rnews.c
1 /*  $Id: rnews.c 7424 2005-10-09 05:04:12Z eagle $
2 **
3 **  A front-end for InterNetNews.
4 **
5 **  Read UUCP batches and offer them up NNTP-style.  Because we may end
6 **  up sending our input down a pipe to uncompress, we have to be careful
7 **  to do unbuffered reads.
8 */
9
10 #include "config.h"
11 #include "clibrary.h"
12 #include "portable/wait.h"
13 #include <ctype.h>
14 #include <dirent.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <pwd.h>
18 #include <syslog.h>
19 #include <sys/stat.h>
20
21 #include "inn/innconf.h"
22 #include "inn/messages.h"
23 #include "inn/wire.h"
24 #include "libinn.h"
25 #include "nntp.h"
26 #include "paths.h"
27 #include "storage.h"
28
29
30 typedef struct _HEADER {
31     const char *Name;
32     int size;
33 } HEADER;
34
35
36 static bool     Verbose;
37 static const char       *InputFile = "stdin";
38 static char     *UUCPHost;
39 static char     *PathBadNews = NULL;
40 static char     *remoteServer;
41 static FILE     *FromServer;
42 static FILE     *ToServer;
43 static char     UNPACK[] = "gzip";
44 static HEADER   RequiredHeaders[] = {
45     { "Message-ID",     10 },
46 #define _messageid      0
47     { "Newsgroups",     10 },
48 #define _newsgroups     1
49     { "From",            4 },
50 #define _from           2
51     { "Date",            4 },
52 #define _date           3
53     { "Subject",         7 },
54 #define _subject        4
55     { "Path",            4 },
56 #define _path           5
57 };
58 #define IS_MESGID(hp)   ((hp) == &RequiredHeaders[_messageid])
59 #define IS_PATH(hp)     ((hp) == &RequiredHeaders[_path])
60
61 \f
62
63 /*
64 **  Open up a pipe to a process with fd tied to its stdin.  Return a
65 **  descriptor tied to its stdout or -1 on error.
66 */
67 static int
68 StartChild(int fd, const char *path, const char *argv[])
69 {
70     int         pan[2];
71     int         i;
72     pid_t       pid;
73
74     /* Create a pipe. */
75     if (pipe(pan) < 0)
76         sysdie("cannot pipe for %s", path);
77
78     /* Get a child. */
79     for (i = 0; (pid = fork()) < 0; i++) {
80         if (i == innconf->maxforks) {
81             syswarn("cannot fork %s, spooling", path);
82             return -1;
83         }
84         notice("cannot fork %s, waiting", path);
85         sleep(60);
86     }
87
88     /* Run the child, with redirection. */
89     if (pid == 0) {
90         close(pan[PIPE_READ]);
91
92         /* Stdin comes from our old input. */
93         if (fd != STDIN_FILENO) {
94             if ((i = dup2(fd, STDIN_FILENO)) != STDIN_FILENO) {
95                 syswarn("cannot dup2 %d to 0, got %d", fd, i);
96                 _exit(1);
97             }
98             close(fd);
99         }
100
101         /* Stdout goes down the pipe. */
102         if (pan[PIPE_WRITE] != STDOUT_FILENO) {
103             if ((i = dup2(pan[PIPE_WRITE], STDOUT_FILENO)) != STDOUT_FILENO) {
104                 syswarn("cannot dup2 %d to 1, got %d", pan[PIPE_WRITE], i);
105                 _exit(1);
106             }
107             close(pan[PIPE_WRITE]);
108         }
109
110         execv(path, (char * const *)argv);
111         syswarn("cannot execv %s", path);
112         _exit(1);
113     }
114
115     close(pan[PIPE_WRITE]);
116     close(fd);
117     return pan[PIPE_READ];
118 }
119
120
121 /*
122 **  Wait for the specified number of children.
123 */
124 static void
125 WaitForChildren(int n)
126 {
127     pid_t pid;
128
129     while (--n >= 0) {
130         pid = waitpid(-1, NULL, WNOHANG);
131         if (pid == (pid_t) -1 && errno != EINTR) {
132             if (errno != ECHILD)
133                 syswarn("cannot wait");
134             break;
135         }
136     }
137 }
138
139
140 \f
141
142 /*
143 **  Clean up the NNTP escapes from a line.
144 */
145 static char *REMclean(char *buff)
146 {
147     char        *p;
148
149     if ((p = strchr(buff, '\r')) != NULL)
150         *p = '\0';
151     if ((p = strchr(buff, '\n')) != NULL)
152         *p = '\0';
153
154     /* The dot-escape is only in text, not command responses. */
155     return buff;
156 }
157
158
159 /*
160 **  Write an article to the rejected directory.
161 */
162 static void
163 Reject(const char *article, size_t length UNUSED, const char *reason,
164        const char *arg)
165 {
166 #if     defined(DO_RNEWS_SAVE_BAD)
167     char *filename;
168     FILE *F;
169     int fd;
170 #endif  /* defined(DO_RNEWS_SAVE_BAD) */
171
172     notice(reason, arg);
173     if (Verbose) {
174         fprintf(stderr, "%s: ", InputFile);
175         fprintf(stderr, reason, arg);
176         fprintf(stderr, " [%.40s...]\n", article);
177     }
178
179 #if     defined(DO_RNEWS_SAVE_BAD)
180     filename = concat(PathBadNews, "/XXXXXX", (char *) 0);
181     fd = mkstemp(filename);
182     if (fd < 0) {
183         warn("cannot create temporary file");
184         return;
185     }
186     F = fdopen(fd, "w");
187     if (F == NULL) {
188         warn("cannot fdopen %s", filename);
189         return;
190     }
191     if (fwrite(article, 1, length, F) != length)
192         warn("cannot fwrite to %s", filename);
193     if (fclose(F) == EOF)
194         warn("cannot close %s", filename);
195     free(filename);
196 #endif  /* defined(DO_RNEWS_SAVE_BAD) */
197 }
198
199
200 /*
201 **  Process one article.  Return true if the article was okay; false if the
202 **  whole batch needs to be saved (such as when the server goes down or if
203 **  the file is corrupted).
204 */
205 static bool
206 Process(char *article, size_t artlen)
207 {
208     HEADER              *hp;
209     const char          *p;
210     size_t              length;
211     char                *wirefmt, *q;
212     const char          *id = NULL;
213     char                *msgid;
214     char                buff[SMBUF];
215 #if     defined(FILE_RNEWS_LOG_DUPS)
216     FILE                *F;
217 #endif  /* defined(FILE_RNEWS_LOG_DUPS) */
218 #if     !defined(DONT_RNEWS_LOG_DUPS)
219     char                path[40];
220 #endif  /* !defined(DONT_RNEWS_LOG_DUPS) */
221
222     /* Empty article? */
223     if (*article == '\0')
224         return true;
225
226     /* Convert the article to wire format. */
227     wirefmt = ToWireFmt(article, artlen, &length);
228
229     /* Make sure that all the headers are there, note the ID. */
230     for (hp = RequiredHeaders; hp < ARRAY_END(RequiredHeaders); hp++) {
231         p = wire_findheader(wirefmt, length, hp->Name);
232         if (p == NULL) {
233             free(wirefmt);
234             Reject(article, artlen, "bad_article missing %s", hp->Name);
235             return true;
236         }
237         if (IS_MESGID(hp)) {
238             id = p;
239             continue;
240         }
241 #if     !defined(DONT_RNEWS_LOG_DUPS)
242         if (IS_PATH(hp)) {
243             strlcpy(path, p, sizeof(path));
244             if ((q = strchr(path, '\r')) != NULL)
245                 *q = '\0';
246         }
247 #endif  /* !defined(DONT_RNEWS_LOG_DUPS) */
248     }
249
250     /* Send the NNTP "ihave" message. */
251     if ((p = strchr(id, '\r')) == NULL) {
252         free(wirefmt);
253         Reject(article, artlen, "bad_article unterminated %s header",
254                "Message-ID");
255         return true;
256     }
257     msgid = xstrndup(id, p - id);
258     fprintf(ToServer, "ihave %s\r\n", msgid);
259     fflush(ToServer);
260     if (UUCPHost)
261         notice("offered %s %s", msgid, UUCPHost);
262     free(msgid);
263
264     /* Get a reply, see if they want the article. */
265     if (fgets(buff, sizeof buff, FromServer) == NULL) {
266         free(wirefmt);
267         if (ferror(FromServer))
268             syswarn("cannot fgets after ihave");
269         else
270             warn("unexpected EOF from server after ihave");
271         return false;
272     }
273     REMclean(buff);
274     if (!CTYPE(isdigit, buff[0])) {
275         free(wirefmt);
276         notice("bad_reply after ihave %s", buff);
277         return false;
278     }
279     switch (atoi(buff)) {
280     default:
281         free(wirefmt);
282         notice("unknown_reply after ihave %s", buff);
283         return false;
284     case NNTP_RESENDIT_VAL:
285         free(wirefmt);
286         return false;
287     case NNTP_SENDIT_VAL:
288         break;
289     case NNTP_HAVEIT_VAL:
290 #if     defined(SYSLOG_RNEWS_LOG_DUPS)
291         *p = '\0';
292         notice("duplicate %s %s", id, path);
293 #endif  /* defined(SYSLOG_RNEWS_LOG_DUPS) */
294 #if     defined(FILE_RNEWS_LOG_DUPS)
295         if ((F = fopen(_PATH_RNEWS_DUP_LOG, "a")) != NULL) {
296             *p = '\0';
297             fprintf(F, "duplicate %s %s\n", id, path);
298             fclose(F);
299         }
300 #endif  /* defined(FILE_RNEWS_LOG_DUPS) */
301         free(wirefmt);
302         return true;
303     }
304
305     /* Send the article to the server. */
306     if (fwrite(wirefmt, length, 1, ToServer) != 1) {
307         free(wirefmt);
308         sysnotice("cant sendarticle");
309         return false;
310     }
311     free(wirefmt);
312
313     /* Flush the server buffer. */
314     if (fflush(ToServer) == EOF) {
315         syswarn("cant fflush after article");
316         return false;
317     }
318
319     /* Process server reply code. */
320     if (fgets(buff, sizeof buff, FromServer) == NULL) {
321         if (ferror(FromServer))
322             syswarn("cannot fgets after article");
323         else
324             warn("unexpected EOF from server after article");
325         return false;
326     }
327     REMclean(buff);
328     if (!CTYPE(isdigit, buff[0])) {
329         notice("bad_reply after article %s", buff);
330         return false;
331     }
332     switch (atoi(buff)) {
333     default:
334         notice("unknown_reply after article %s", buff);
335         /* FALLTHROUGH */
336     case NNTP_RESENDIT_VAL:
337         return false;
338     case NNTP_TOOKIT_VAL:
339         break;
340     case NNTP_REJECTIT_VAL:
341         Reject(article, artlen, "rejected %s", buff);
342         break;
343     }
344     return true;
345 }
346
347
348 /*
349 **  Read the rest of the input as an article.
350 */
351 static bool
352 ReadRemainder(int fd, char first, char second)
353 {
354     char        *article;
355     char        *p;
356     char        buf[BUFSIZ];
357     int         size;
358     int         used;
359     int         left;
360     int         skipnl;
361     int         i, n;
362     bool        ok;
363
364     /* Get an initial allocation, leaving space for the \0. */
365     size = BUFSIZ + 1;
366     article = xmalloc(size + 2);
367     article[0] = first;
368     article[1] = second;
369     used = second ? 2 : 1;
370     left = size - used;
371     skipnl = 0;
372
373     /* Read the input, coverting line ends as we go if necessary. */
374     while ((n = read(fd, buf, sizeof(buf))) > 0) {
375         p = article + used;
376         for (i = 0; i < n; i++) {
377             if (skipnl) {
378                 skipnl = 0;
379                 if (buf[i] == '\n') continue;
380             }
381             if (buf[i] == '\r') {
382                 buf[i] = '\n';
383                 skipnl = 1;
384             }
385             *p++ = buf[i];
386             used++;
387             left--;
388             if (left < SMBUF) {
389                 size += BUFSIZ;
390                 left += BUFSIZ;
391                 article = xrealloc(article, size);
392                 p = article + used;
393             }
394         }
395     }
396     if (n < 0)
397         sysdie("cannot read after %d bytes", used);
398
399     if (article[used - 1] != '\n')
400         article[used++] = '\n';
401     article[used] = '\0';
402
403     ok = Process(article, used);
404     free(article);
405     return ok;
406 }
407
408
409 /*
410 **  Read an article from the input stream that is artsize bytes long.
411 */
412 static bool
413 ReadBytecount(int fd, int artsize)
414 {
415     static char         *article;
416     static int          oldsize;
417     char        *p;
418     int left;
419     int i;
420
421     /* If we haven't gotten any memory before, or we didn't get enough,
422      * then get some. */
423     if (article == NULL) {
424         oldsize = artsize;
425         article = xmalloc(oldsize + 1 + 1);
426     }
427     else if (artsize > oldsize) {
428         oldsize = artsize;
429         article = xrealloc(article, oldsize + 1 + 1);
430     }
431
432     /* Read in the article. */
433     for (p = article, left = artsize; left; p += i, left -= i)
434         if ((i = read(fd, p, left)) <= 0) {
435             i = errno;
436             warn("cannot read, wanted %d got %d", artsize, artsize - left);
437 #if     0
438             /* Don't do this -- if the article gets re-processed we
439              * will end up accepting the truncated version. */
440             artsize = p - article;
441             article[artsize] = '\0';
442             Reject(article, "short read (%s?)", strerror(i));
443 #endif  /* 0 */
444             return true;
445         }
446     if (p[-1] != '\n') {
447         *p++ = '\n';
448         artsize++;
449     }
450     *p = '\0';
451
452     return Process(article, artsize);
453 }
454
455 \f
456
457 /*
458 **  Read a single text line; not unlike fgets().  Just more inefficient.
459 */
460 static bool
461 ReadLine(char *p, int size, int fd)
462 {
463     char        *save;
464
465     /* Fill the buffer, a byte at a time. */
466     for (save = p; size > 0; p++, size--) {
467         if (read(fd, p, 1) != 1) {
468             *p = '\0';
469             sysdie("cannot read first line, got %s", save);
470         }
471         if (*p == '\n') {
472             *p = '\0';
473             return true;
474         }
475     }
476     *p = '\0';
477     warn("bad_line too long %s", save);
478     return false;
479 }
480
481
482 /*
483 **  Unpack a single batch.
484 */
485 static bool
486 UnpackOne(int *fdp, size_t *countp)
487 {
488 #if     defined(DO_RNEWSPROGS)
489     char        path[(SMBUF * 2) + 1];
490     char        *p;
491 #endif  /* defined(DO_RNEWSPROGS) */
492     char        buff[SMBUF];
493     const char *cargv[4];
494     int         artsize;
495     int         i;
496     int         gzip = 0;
497     bool        HadCount;
498     bool        SawCunbatch;
499     int         len;
500
501     *countp = 0;
502     for (SawCunbatch = false, HadCount = false; ; ) {
503         /* Get the first character. */
504         if ((i = read(*fdp, &buff[0], 1)) < 0) {
505             syswarn("cannot read first character");
506             return false;
507         }
508         if (i == 0)
509             break;
510
511         if (buff[0] == 0x1f)
512             gzip = 1;
513         else if (buff[0] != '#')
514             /* Not a batch file.  If we already got one count, the batch
515              * is corrupted, else read rest of input as an article. */
516             return HadCount ? false : ReadRemainder(*fdp, buff[0], '\0');
517
518         /* Get the second character. */
519         if ((i = read(*fdp, &buff[1], 1)) < 0) {
520             syswarn("cannot read second character");
521             return false;
522         }
523         if (i == 0)
524             /* A one-byte batch? */
525             return false;
526
527         /* Check second magic character. */
528         /* gzipped ($1f$8b) or compressed ($1f$9d) */
529         if (gzip && ((buff[1] == (char)0x8b) || (buff[1] == (char)0x9d))) {
530             cargv[0] = "gzip";
531             cargv[1] = "-d";
532             cargv[2] = NULL;
533             lseek(*fdp, 0, 0); /* Back to the beginning */
534             *fdp = StartChild(*fdp, _PATH_GZIP, cargv);
535             if (*fdp < 0)
536                 return false;
537             (*countp)++;
538             SawCunbatch = true;
539             continue;
540         }
541         if (buff[1] != '!')
542             return HadCount ? false : ReadRemainder(*fdp, buff[0], buff[1]);
543
544         /* Some kind of batch -- get the command. */
545         if (!ReadLine(&buff[2], (int)(sizeof buff - 3), *fdp))
546             return false;
547
548         if (strncmp(buff, "#! rnews ", 9) == 0) {
549             artsize = atoi(&buff[9]);
550             if (artsize <= 0) {
551                 syswarn("bad_line bad count %s", buff);
552                 return false;
553             }
554             HadCount = true;
555             if (ReadBytecount(*fdp, artsize))
556                 continue;
557             return false;
558         }
559
560         if (HadCount)
561             /* Already saw a bytecount -- probably corrupted. */
562             return false;
563
564         if (strcmp(buff, "#! cunbatch") == 0) {
565             if (SawCunbatch) {
566                 syswarn("nested_cunbatch");
567                 return false;
568             }
569             cargv[0] = UNPACK;
570             cargv[1] = "-d";
571             cargv[2] = NULL;
572             *fdp = StartChild(*fdp, _PATH_GZIP, cargv);
573             if (*fdp < 0)
574                 return false;
575             (*countp)++;
576             SawCunbatch = true;
577             continue;
578         }
579
580 #if     defined(DO_RNEWSPROGS)
581         cargv[0] = UNPACK;
582         cargv[1] = NULL;
583         /* Ignore any possible leading pathnames, to avoid trouble. */
584         if ((p = strrchr(&buff[3], '/')) != NULL)
585             p++;
586         else
587             p = &buff[3];
588         if (strchr(_PATH_RNEWSPROGS, '/') == NULL) {
589             snprintf(path, sizeof(path), "%s/%s/%s", innconf->pathbin,
590                      _PATH_RNEWSPROGS, p);
591             len = strlen(innconf->pathbin) + 1 + sizeof _PATH_RNEWSPROGS;
592         } else {
593             snprintf(path, sizeof(path), "%s/%s", _PATH_RNEWSPROGS, p);
594             len = sizeof _PATH_RNEWSPROGS;
595         }
596         for (p = &path[len]; *p; p++)
597             if (ISWHITE(*p)) {
598                 *p = '\0';
599                 break;
600             }
601         *fdp = StartChild(*fdp, path, cargv);
602         if (*fdp < 0)
603             return false;
604         (*countp)++;
605         continue;
606 #else
607         warn("bad_format unknown command %s", buff);
608         return false;
609 #endif  /* defined(DO_RNEWSPROGS) */
610     }
611     return true;
612 }
613
614
615 /*
616 **  Read all articles in the spool directory and unpack them.  Print all
617 **  errors with xperror as well as syslog, since we're probably being run
618 **  interactively.
619 */
620 static void
621 Unspool(void)
622 {
623     DIR *dp;
624     struct dirent       *ep;
625     bool        ok;
626     struct stat         Sb;
627     char                hostname[10];
628     int                 fd;
629     size_t              i;
630     char                *uuhost;
631
632     message_handlers_die(2, message_log_stderr, message_log_syslog_err);
633     message_handlers_warn(2, message_log_stderr, message_log_syslog_err);
634
635     /* Go to the spool directory, get ready to scan it. */
636     if (chdir(innconf->pathincoming) < 0)
637         sysdie("cannot chdir to %s", innconf->pathincoming);
638     if ((dp = opendir(".")) == NULL)
639         sysdie("cannot open spool directory");
640
641     /* Loop over all files, and parse them. */
642     while ((ep = readdir(dp)) != NULL) {
643         InputFile = ep->d_name;
644         if (InputFile[0] == '.')
645             continue;
646         if (stat(InputFile, &Sb) < 0 && errno != ENOENT) {
647             syswarn("cannot stat %s", InputFile);
648             continue;
649         }
650
651         if (!S_ISREG(Sb.st_mode))
652             continue;
653
654         if ((fd = open(InputFile, O_RDWR)) < 0) {
655             if (errno != ENOENT)
656                 syswarn("cannot open %s", InputFile);
657             continue;
658         }
659
660         /* Make sure multiple Unspools don't stomp on eachother. */
661         if (!inn_lock_file(fd, INN_LOCK_WRITE, 0)) {
662             close(fd);
663             continue;
664         }
665
666         /* Get UUCP host from spool file, deleting the mktemp XXXXXX suffix. */
667         uuhost = UUCPHost;
668         hostname[0] = 0;
669         if ((i = strlen(InputFile)) > 6) {
670             i -= 6;
671             if (i > sizeof hostname - 1)
672                 /* Just in case someone wrote their own spooled file. */
673                 i = sizeof hostname - 1;
674             strlcpy(hostname, InputFile, i + 1);
675             UUCPHost = hostname;
676         }
677         ok = UnpackOne(&fd, &i);
678         WaitForChildren(i);
679         UUCPHost = uuhost;
680
681         /* If UnpackOne returned true, the article has been dealt with one way
682            or the other, so remove it.  Otherwise, leave it in place; either
683            we got an unknown error from the server or we got a deferral, and
684            for both we want to try later. */
685         if (ok) {
686             if (unlink(InputFile) < 0)
687                 syswarn("cannot remove %s", InputFile);
688         }
689
690         close(fd);
691     }
692     closedir(dp);
693
694     message_handlers_die(1, message_log_syslog_err);
695     message_handlers_warn(1, message_log_syslog_err);
696 }
697
698 \f
699
700 /*
701 **  Can't connect to the server, so spool our input.  There isn't much
702 **  we can do if this routine fails, unfortunately.  Perhaps try to use
703 **  an alternate filesystem?
704 */
705 static void
706 Spool(int fd, int mode)
707 {
708     int spfd;
709     int i;
710     int j;
711     char *tmpspool, *spoolfile, *p;
712     char buff[BUFSIZ];
713     int count;
714     int status;
715
716     if (mode == 'N')
717         exit(9);
718     tmpspool = concat(innconf->pathincoming, "/.",
719                 UUCPHost ? UUCPHost : "", "XXXXXX", (char *)0);
720     spfd = mkstemp(tmpspool);
721     if (spfd < 0)
722         sysdie("cannot create temporary batch file %s", tmpspool);
723     if (fchmod(spfd, BATCHFILE_MODE) < 0)
724         sysdie("cannot chmod temporary batch file %s", tmpspool);
725
726     /* Read until we there is nothing left. */
727     for (status = 0, count = 0; (i = read(fd, buff, sizeof buff)) != 0; ) {
728         /* Break out on error. */
729         if (i < 0) {
730             syswarn("cannot read after %d", count);
731             status++;
732             break;
733         }
734         /* Write out what we read. */
735         for (count += i, p = buff; i; p += j, i -= j)
736             if ((j = write(spfd, p, i)) <= 0) {
737                 syswarn("cannot write around %d", count);
738                 status++;
739                 break;
740             }
741     }
742
743     /* Close the file. */
744     if (close(spfd) < 0) {
745         syswarn("cannot close spooled article %s", tmpspool);
746         status++;
747     }
748
749     /* Move temp file into the spool area, and exit appropriately. */
750     spoolfile = concat(innconf->pathincoming, "/",
751                 UUCPHost ? UUCPHost : "", "XXXXXX", (char *)0);
752     spfd = mkstemp(spoolfile);
753     if (spfd < 0) {
754         syswarn("cannot create spool file %s", spoolfile);
755         status++;
756     } else {
757         close(spfd);
758         if (rename(tmpspool, spoolfile) < 0) {
759             syswarn("cannot rename %s to %s", tmpspool, spoolfile);
760             status++;
761         }
762     }
763     free(tmpspool);
764     free(spoolfile);
765     exit(status);
766     /* NOTREACHED */
767 }
768
769
770 /*
771 **  Try to read the password file and open a connection to a remote
772 **  NNTP server.
773 */
774 static bool OpenRemote(char *server, int port, char *buff)
775 {
776     int         i;
777
778     /* Open the remote connection. */
779     if (server)
780         i = NNTPconnect(server, port, &FromServer, &ToServer, buff);
781     else
782         i = NNTPremoteopen(port, &FromServer, &ToServer, buff);
783     if (i < 0)
784         return false;
785
786     *buff = '\0';
787     if (NNTPsendpassword(server, FromServer, ToServer) < 0) {
788         int oerrno = errno;
789         fclose(FromServer);
790         fclose(ToServer);
791         errno = oerrno;
792         return false;
793     }
794     return true;
795 }
796
797
798 /*
799 **  Can't connect to server; print message and spool if necessary.
800 */
801 static void
802 CantConnect(char *buff, int mode, int fd)
803 {
804     if (buff[0])
805         notice("rejected connection %s", REMclean(buff));
806     else
807         syswarn("cant open_remote");
808     if (mode != 'U')
809         Spool(fd, mode);
810     exit(1);
811 }
812
813
814 int main(int ac, char *av[])
815 {
816     int         fd;
817     int         i;
818     size_t      count;
819     int         mode;
820     char        buff[SMBUF];
821     int         port = NNTP_PORT;
822
823     /* First thing, set up logging and our identity. */
824     openlog("rnews", L_OPENLOG_FLAGS, LOG_INN_PROG);
825     message_program_name = "rnews";
826     message_handlers_notice(1, message_log_syslog_notice);
827     message_handlers_warn(1, message_log_syslog_err);
828     message_handlers_die(1, message_log_syslog_err);
829
830     /* The reason for the following is somewhat obscure and is done only
831        because rnews is sometimes installed setuid.
832
833        The stderr stream used by message_log_syslog_err is associated with
834        file descriptor 2, generally even if that file descriptor is closed.
835        Someone running rnews may close all of the standard file descriptors
836        before running it, in which case, later in its operations, one of the
837        article files or network connections it has open could be file
838        descriptor 2.  If an error occurs at that point, the error message may
839        be written to that file or network connection instead of to stderr,
840        with unpredictable results.
841
842        We avoid this by burning three file descriptors if the real and
843        effective user IDs don't match, or if we're running as root.  (If they
844        do match, there is no escalation of privileges and at worst the user is
845        just managing to produce a strange bug.) */
846     if (getuid() != geteuid() || geteuid() == 0) {
847         if (open("/dev/null", O_RDONLY) < 0)
848             sysdie("cannot open /dev/null");
849         if (open("/dev/null", O_RDONLY) < 0)
850             sysdie("cannot open /dev/null");
851         if (open("/dev/null", O_RDONLY) < 0)
852             sysdie("cannot open /dev/null");
853     }
854
855     /* Make sure that we switch to the news user if we're running as root,
856        since we may spool files and don't want those files owned by root.
857        Don't require that we be running as the news user, though; there are
858        other setups where rnews might be setuid news or be run by other
859        processes in the news group. */
860     if (getuid() == 0 || geteuid() == 0) {
861         struct passwd *pwd;
862
863         pwd = getpwnam(NEWSUSER);
864         if (pwd == NULL)
865             die("can't resolve %s to a UID (account doesn't exist?)",
866                 NEWSUSER);
867         setuid(pwd->pw_uid);
868     }
869
870     if (!innconf_read(NULL))
871         exit(1);
872     UUCPHost = getenv(_ENV_UUCPHOST);
873     PathBadNews = concatpath(innconf->pathincoming, _PATH_BADNEWS);
874     port = innconf->nnrpdpostport;
875
876     umask(NEWSUMASK);
877
878     /* Parse JCL. */
879     fd = STDIN_FILENO;
880     mode = '\0';
881     while ((i = getopt(ac, av, "h:P:NUvr:S:")) != EOF)
882         switch (i) {
883         default:
884             die("usage error");
885             /* NOTRTEACHED */
886         case 'h':
887             UUCPHost = *optarg ? optarg : NULL;
888             break;
889         case 'N':
890         case 'U':
891             mode = i;
892             break;
893         case 'P':
894             port = atoi(optarg);
895             break;
896         case 'v':
897             Verbose = true;
898             break;
899         case 'r':
900         case 'S':
901             remoteServer = optarg;
902             break;
903         }
904     ac -= optind;
905     av += optind;
906
907     /* Parse arguments.  At most one, the input file. */
908     switch (ac) {
909     default:
910         die("usage error");
911         /* NOTREACHED */
912     case 0:
913         break;
914     case 1:
915         if (mode == 'U')
916             die("usage error");
917         if (freopen(av[0], "r", stdin) == NULL)
918             sysdie("cannot freopen %s", av[0]);
919         fd = fileno(stdin);
920         InputFile = av[0];
921         break;
922     }
923
924     /* Open the link to the server. */
925     if (remoteServer != NULL) {
926         if (!OpenRemote(remoteServer,port,buff))
927                 CantConnect(buff,mode,fd);
928     } else if (innconf->nnrpdposthost != NULL) {
929         if (!OpenRemote(innconf->nnrpdposthost,
930             (port != NNTP_PORT) ? port : innconf->nnrpdpostport, buff))
931                 CantConnect(buff, mode, fd);
932     }
933     else {
934 #if     defined(DO_RNEWSLOCALCONNECT)
935         if (NNTPlocalopen(&FromServer, &ToServer, buff) < 0) {
936             /* If server rejected us, no point in continuing. */
937             if (buff[0])
938                 CantConnect(buff, mode, fd);
939             if (!OpenRemote((char *)NULL,
940                 (port != NNTP_PORT) ? port : innconf->port, buff))
941                         CantConnect(buff, mode, fd);
942         }
943 #else
944         if (!OpenRemote((char *)NULL, 
945             (port != NNTP_PORT) ? port : innconf->port, buff))
946                 CantConnect(buff, mode, fd);
947 #endif  /* defined(DO_RNEWSLOCALCONNECT) */
948     }
949     close_on_exec(fileno(FromServer), true);
950     close_on_exec(fileno(ToServer), true);
951
952     /* Execute the command. */
953     if (mode == 'U')
954         Unspool();
955     else {
956         if (!UnpackOne(&fd, &count)) {
957             lseek(fd, 0, 0);
958             Spool(fd, mode);
959         }
960         close(fd);
961         WaitForChildren(count);
962     }
963
964     /* Tell the server we're quitting, get his okay message. */
965     fprintf(ToServer, "quit\r\n");
966     fflush(ToServer);
967     fgets(buff, sizeof buff, FromServer);
968
969     /* Return the appropriate status. */
970     exit(0);
971     /* NOTREACHED */
972 }