Add rmail from 4.4BSD-Lite Release 2
[bobcat.git] / usr / src / bin / rmail / rmail.c
1 /*
2  * Copyright (c) 1988, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #ifndef lint
31 static char copyright[] =
32 "@(#) Copyright (c) 1988, 1993\n\
33         The Regents of the University of California.  All rights reserved.\n";
34 #endif /* not lint */
35
36 #ifndef lint
37 static char sccsid[] = "@(#)rmail.c     8.3 (Berkeley) 5/15/95";
38 #endif /* not lint */
39
40 /*
41  * RMAIL -- UUCP mail server.
42  *
43  * This program reads the >From ... remote from ... lines that UUCP is so
44  * fond of and turns them into something reasonable.  It then execs sendmail
45  * with various options built from these lines. 
46  *
47  * The expected syntax is:
48  *
49  *       <user> := [-a-z0-9]+
50  *       <date> := ctime format
51  *       <site> := [-a-z0-9!]+
52  * <blank line> := "^\n$"
53  *       <from> := "From" <space> <user> <space> <date>
54  *                [<space> "remote from" <space> <site>]
55  *    <forward> := ">" <from>
56  *          msg := <from> <forward>* <blank-line> <body>
57  *
58  * The output of rmail(8) compresses the <forward> lines into a single
59  * from path.
60  *
61  * The err(3) routine is included here deliberately to make this code
62  * a bit more portable.
63  */
64 #include <sys/param.h>
65 #include <sys/stat.h>
66 #include <sys/wait.h>
67
68 #include <ctype.h>
69 #include <fcntl.h>
70 #include <paths.h>
71 #include <stdio.h>
72 #include <stdlib.h>
73 #include <string.h>
74 #include <sysexits.h>
75 #include <unistd.h>
76
77 #ifndef MAX
78 # define MAX(a, b)      ((a) < (b) ? (b) : (a))
79 #endif
80
81 void err __P((int, const char *, ...));
82 void usage __P((void));
83
84 int
85 main(argc, argv)
86         int argc;
87         char *argv[];
88 {
89         extern char *optarg;
90         extern int errno, optind;
91         FILE *fp;
92         struct stat sb;
93         size_t fplen, fptlen, len;
94         off_t offset;
95         int ch, debug, i, pdes[2], pid, status;
96         char *addrp, *domain, *p, *t;
97         char *from_path, *from_sys, *from_user;
98         char *args[100], buf[2048], lbuf[2048];
99
100         debug = 0;
101         domain = "UUCP";                /* Default "domain". */
102         while ((ch = getopt(argc, argv, "D:T")) != EOF)
103                 switch (ch) {
104                 case 'T':
105                         debug = 1;
106                         break;
107                 case 'D':
108                         domain = optarg;
109                         break;
110                 case '?':
111                 default:
112                         usage();
113                 }
114         argc -= optind;
115         argv += optind;
116
117         if (argc < 1)
118                 usage();
119
120         from_path = from_sys = from_user = NULL;
121         for (offset = 0;;) {
122
123                 /* Get and nul-terminate the line. */
124                 if (fgets(lbuf, sizeof(lbuf), stdin) == NULL)
125                         exit (EX_DATAERR);
126                 if ((p = strchr(lbuf, '\n')) == NULL)
127                         err(EX_DATAERR, "line too long");
128                 *p = '\0';
129
130                 /* Parse lines until reach a non-"From" line. */
131                 if (!strncmp(lbuf, "From ", 5))
132                         addrp = lbuf + 5;
133                 else if (!strncmp(lbuf, ">From ", 6))
134                         addrp = lbuf + 6;
135                 else if (offset == 0)
136                         err(EX_DATAERR,
137                             "missing or empty From line: %s", lbuf);
138                 else {
139                         *p = '\n';
140                         break;
141                 }
142
143                 if (*addrp == '\0')
144                         err(EX_DATAERR, "corrupted From line: %s", lbuf);
145
146                 /* Use the "remote from" if it exists. */
147                 for (p = addrp; (p = strchr(p + 1, 'r')) != NULL;)
148                         if (!strncmp(p, "remote from ", 12)) {
149                                 for (t = p += 12; *t && !isspace(*t); ++t);
150                                 *t = '\0';
151                                 if (debug)
152                                         (void)fprintf(stderr,
153                                             "remote from: %s\n", p);
154                                 break;
155                         }
156
157                 /* Else use the string up to the last bang. */
158                 if (p == NULL)
159                         if (*addrp == '!')
160                                 err(EX_DATAERR,
161                                     "bang starts address: %s", addrp);
162                         else if ((t = strrchr(addrp, '!')) != NULL) {
163                                 *t = '\0';
164                                 p = addrp;
165                                 addrp = t + 1;
166                                 if (*addrp == '\0')
167                                         err(EX_DATAERR,
168                                             "corrupted From line: %s", lbuf);
169                                 if (debug)
170                                         (void)fprintf(stderr, "bang: %s\n", p);
171                         }
172
173                 /* 'p' now points to any system string from this line. */
174                 if (p != NULL) {
175                         /* Nul terminate it as necessary. */
176                         for (t = p; *t && !isspace(*t); ++t);
177                         *t = '\0';
178
179                         /* If the first system, copy to the from_sys string. */
180                         if (from_sys == NULL) {
181                                 if ((from_sys = strdup(p)) == NULL)
182                                         err(EX_TEMPFAIL, NULL);
183                                 if (debug)
184                                         (void)fprintf(stderr,
185                                             "from_sys: %s\n", from_sys);
186                         }
187
188                         /* Concatenate to the path string. */
189                         len = t - p;
190                         if (from_path == NULL) {
191                                 fplen = 0;
192                                 if ((from_path = malloc(fptlen = 256)) == NULL)
193                                         err(EX_TEMPFAIL, NULL);
194                         }
195                         if (fplen + len + 2 > fptlen) {
196                                 fptlen += MAX(fplen + len + 2, 256);
197                                 if ((from_path =
198                                     realloc(from_path, fptlen)) == NULL)
199                                         err(EX_TEMPFAIL, NULL);
200                         }
201                         memmove(from_path + fplen, p, len);
202                         fplen += len;
203                         from_path[fplen++] = '!';
204                         from_path[fplen] = '\0';
205                 }
206
207                 /* Save off from user's address; the last one wins. */
208                 for (p = addrp; *p && !isspace(*p); ++p);
209                 *p = '\0';
210                 if (*addrp == '\0')
211                         addrp = "<>";
212                 if (from_user != NULL)
213                         free(from_user);
214                 if ((from_user = strdup(addrp)) == NULL)
215                         err(EX_TEMPFAIL, NULL);
216
217                 if (debug) {
218                         if (from_path != NULL)
219                                 (void)fprintf(stderr,
220                                     "from_path: %s\n", from_path);
221                         (void)fprintf(stderr, "from_user: %s\n", from_user);
222                 }
223
224                 if (offset != -1)
225                         offset = (off_t)ftell(stdin);
226         }
227
228         i = 0;
229         args[i++] = _PATH_SENDMAIL;     /* Build sendmail's argument list. */
230         args[i++] = "-oee";             /* No errors, just status. */
231         args[i++] = "-odq";             /* Queue it, don't try to deliver. */
232         args[i++] = "-oi";              /* Ignore '.' on a line by itself. */
233
234         /* set from system and protocol used */
235         if (from_sys == NULL)
236                 (void)snprintf(buf, sizeof(buf), "-p%s", domain);
237         else if (strchr(from_sys, '.') == NULL)
238                 (void)snprintf(buf, sizeof(buf), "-p%s:%s.%s",
239                         domain, from_sys, domain);
240         else
241                 (void)snprintf(buf, sizeof(buf), "-p%s:%s", domain, from_sys);
242         if ((args[i++] = strdup(buf)) == NULL)
243                 err(EX_TEMPFAIL, NULL);
244
245                                         /* Set name of ``from'' person. */
246         (void)snprintf(buf, sizeof(buf), "-f%s%s",
247             from_path ? from_path : "", from_user);
248         if ((args[i++] = strdup(buf)) == NULL)
249                 err(EX_TEMPFAIL, NULL);
250
251         /*
252          * Don't copy arguments beginning with - as they will be
253          * passed to sendmail and could be interpreted as flags.
254          * To prevent confusion of sendmail wrap < and > around
255          * the address (helps to pass addrs like @gw1,@gw2:aa@bb)
256          */
257         while (*argv) {
258                 if (**argv == '-')
259                         err(EX_USAGE, "dash precedes argument: %s", *argv);
260                 if (strchr(*argv, ',') == NULL || strchr(*argv, '<') != NULL)
261                         args[i++] = *argv;
262                 else {
263                         if ((args[i] = malloc(strlen(*argv) + 3)) == NULL)
264                                 err(EX_TEMPFAIL, "Cannot malloc");
265                         sprintf (args [i++], "<%s>", *argv);
266                 }
267                 argv++;
268         } 
269         args[i] = 0;
270
271         if (debug) {
272                 (void)fprintf(stderr, "Sendmail arguments:\n");
273                 for (i = 0; args[i]; i++)
274                         (void)fprintf(stderr, "\t%s\n", args[i]);
275         }
276
277         /*
278          * If called with a regular file as standard input, seek to the right
279          * position in the file and just exec sendmail.  Could probably skip
280          * skip the stat, but it's not unreasonable to believe that a failed
281          * seek will cause future reads to fail.
282          */
283         if (!fstat(STDIN_FILENO, &sb) && S_ISREG(sb.st_mode)) {
284                 if (lseek(STDIN_FILENO, offset, SEEK_SET) != offset)
285                         err(EX_TEMPFAIL, "stdin seek");
286                 execv(_PATH_SENDMAIL, args);
287                 err(EX_OSERR, "%s", _PATH_SENDMAIL);
288         }
289
290         if (pipe(pdes) < 0)
291                 err(EX_OSERR, NULL);
292
293         switch (pid = vfork()) {
294         case -1:                                /* Err. */
295                 err(EX_OSERR, NULL);
296         case 0:                                 /* Child. */
297                 if (pdes[0] != STDIN_FILENO) {
298                         (void)dup2(pdes[0], STDIN_FILENO);
299                         (void)close(pdes[0]);
300                 }
301                 (void)close(pdes[1]);
302                 execv(_PATH_SENDMAIL, args);
303                 _exit(127);
304                 /* NOTREACHED */
305         }
306
307         if ((fp = fdopen(pdes[1], "w")) == NULL)
308                 err(EX_OSERR, NULL);
309         (void)close(pdes[0]);
310
311         /* Copy the file down the pipe. */
312         do {
313                 (void)fprintf(fp, "%s", lbuf);
314         } while (fgets(lbuf, sizeof(lbuf), stdin) != NULL);
315
316         if (ferror(stdin))
317                 err(EX_TEMPFAIL, "stdin: %s", strerror(errno));
318
319         if (fclose(fp))
320                 err(EX_OSERR, NULL);
321
322         if ((waitpid(pid, &status, 0)) == -1)
323                 err(EX_OSERR, "%s", _PATH_SENDMAIL);
324
325         if (!WIFEXITED(status))
326                 err(EX_OSERR,
327                     "%s: did not terminate normally", _PATH_SENDMAIL);
328
329         if (WEXITSTATUS(status))
330                 err(status, "%s: terminated with %d (non-zero) status",
331                     _PATH_SENDMAIL, WEXITSTATUS(status));
332         exit(EX_OK);
333 }
334
335 void
336 usage()
337 {
338         (void)fprintf(stderr, "usage: rmail [-T] [-D domain] user ...\n");
339         exit(EX_USAGE);
340 }
341
342 #ifdef __STDC__
343 #include <stdarg.h>
344 #else
345 #include <varargs.h>
346 #endif
347
348 void
349 #ifdef __STDC__
350 err(int eval, const char *fmt, ...)
351 #else
352 err(eval, fmt, va_alist)
353         int eval;
354         const char *fmt;
355         va_dcl
356 #endif
357 {
358         va_list ap;
359 #if __STDC__
360         va_start(ap, fmt);
361 #else
362         va_start(ap);
363 #endif
364         (void)fprintf(stderr, "rmail: ");
365         (void)vfprintf(stderr, fmt, ap);
366         va_end(ap);
367         (void)fprintf(stderr, "\n");
368         exit(eval);
369 }