xref: /original-bsd/bin/rmail/rmail.c (revision 4092c5cc)
1 /*
2  * Copyright (c) 1988, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char copyright[] =
10 "@(#) Copyright (c) 1988, 1993\n\
11 	The Regents of the University of California.  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)rmail.c	8.1 (Berkeley) 05/31/93";
16 #endif /* not lint */
17 
18 /*
19  * RMAIL -- UUCP mail server.
20  *
21  * This program reads the >From ... remote from ... lines that UUCP is so
22  * fond of and turns them into something reasonable.  It then execs sendmail
23  * with various options built from these lines.
24  *
25  * The expected syntax is:
26  *
27  *	 <user> := [-a-z0-9]+
28  *	 <date> := ctime format
29  *	 <site> := [-a-z0-9!]+
30  * <blank line> := "^\n$"
31  *	 <from> := "From" <space> <user> <space> <date>
32  *		  [<space> "remote from" <space> <site>]
33  *    <forward> := ">" <from>
34  *	    msg := <from> <forward>* <blank-line> <body>
35  *
36  * The output of rmail(8) compresses the <forward> lines into a single
37  * from path.
38  *
39  * The err(3) routine is included here deliberately to make this code
40  * a bit more portable.
41  */
42 #include <sys/param.h>
43 #include <sys/stat.h>
44 #include <sys/wait.h>
45 
46 #include <ctype.h>
47 #include <fcntl.h>
48 #include <paths.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <sysexits.h>
53 #include <unistd.h>
54 
55 void err __P((int, const char *, ...));
56 void usage __P((void));
57 
58 int
59 main(argc, argv)
60 	int argc;
61 	char *argv[];
62 {
63 	extern char *optarg;
64 	extern int errno, optind;
65 	FILE *fp;
66 	struct stat sb;
67 	size_t fplen, fptlen, len;
68 	off_t offset;
69 	int ch, debug, i, pdes[2], pid, status;
70 	char *addrp, *domain, *p, *t;
71 	char *from_path, *from_sys, *from_user;
72 	char *args[100], buf[2048], lbuf[2048];
73 
74 	debug = 0;
75 	domain = "UUCP";		/* Default "domain". */
76 	while ((ch = getopt(argc, argv, "D:T")) != EOF)
77 		switch (ch) {
78 		case 'T':
79 			debug = 1;
80 			break;
81 		case 'D':
82 			domain = optarg;
83 			break;
84 		case '?':
85 		default:
86 			usage();
87 		}
88 	argc -= optind;
89 	argv += optind;
90 
91 	if (argc < 1)
92 		usage();
93 
94 	from_path = from_sys = from_user = NULL;
95 	for (offset = 0;;) {
96 
97 		/* Get and nul-terminate the line. */
98 		if (fgets(lbuf, sizeof(lbuf), stdin) == NULL)
99 			exit (EX_DATAERR);
100 		if ((p = strchr(lbuf, '\n')) == NULL)
101 			err(EX_DATAERR, "line too long");
102 		*p = '\0';
103 
104 		/* Parse lines until reach a non-"From" line. */
105 		if (!strncmp(lbuf, "From ", 5))
106 			addrp = lbuf + 5;
107 		else if (!strncmp(lbuf, ">From ", 6))
108 			addrp = lbuf + 6;
109 		else if (offset == 0)
110 			err(EX_DATAERR,
111 			    "missing or empty From line: %s", lbuf);
112 		else {
113 			*p = '\n';
114 			break;
115 		}
116 
117 		if (*addrp == '\0')
118 			err(EX_DATAERR, "corrupted From line: %s", lbuf);
119 
120 		/* Use the "remote from" if it exists. */
121 		for (p = addrp; (p = strchr(p + 1, 'r')) != NULL;)
122 			if (!strncmp(p, "remote from ", 12)) {
123 				for (t = p += 12; *t && !isspace(*t); ++t);
124 				*t = '\0';
125 				if (debug)
126 					(void)fprintf(stderr,
127 					    "remote from: %s\n", p);
128 				break;
129 			}
130 
131 		/* Else use the string up to the last bang. */
132 		if (p == NULL)
133 			if (*addrp == '!')
134 				err(EX_DATAERR,
135 				    "bang starts address: %s", addrp);
136 			else if ((t = strrchr(addrp, '!')) != NULL) {
137 				*t = '\0';
138 				p = addrp;
139 				addrp = t + 1;
140 				if (*addrp == '\0')
141 					err(EX_DATAERR,
142 					    "corrupted From line: %s", lbuf);
143 				if (debug)
144 					(void)fprintf(stderr, "bang: %s\n", p);
145 			}
146 
147 		/* 'p' now points to any system string from this line. */
148 		if (p != NULL) {
149 			/* Nul terminate it as necessary. */
150 			for (t = p; *t && !isspace(*t); ++t);
151 			*t = '\0';
152 
153 			/* If the first system, copy to the from_sys string. */
154 			if (from_sys == NULL) {
155 				if ((from_sys = strdup(p)) == NULL)
156 					err(EX_TEMPFAIL, NULL);
157 				if (debug)
158 					(void)fprintf(stderr,
159 					    "from_sys: %s\n", from_sys);
160 			}
161 
162 			/* Concatenate to the path string. */
163 			len = t - p;
164 			if (from_path == NULL) {
165 				fplen = 0;
166 				if ((from_path = malloc(fptlen = 256)) == NULL)
167 					err(EX_TEMPFAIL, NULL);
168 			}
169 			if (fplen + len + 2 > fptlen) {
170 				fptlen += MAX(fplen + len + 2, 256);
171 				if ((from_path =
172 				    realloc(from_path, fptlen)) == NULL)
173 					err(EX_TEMPFAIL, NULL);
174 			}
175 			memmove(from_path + fplen, p, len);
176 			fplen += len;
177 			from_path[fplen++] = '!';
178 			from_path[fplen] = '\0';
179 		}
180 
181 		/* Save off from user's address; the last one wins. */
182 		for (p = addrp; *p && !isspace(*p); ++p);
183 		*p = '\0';
184 		if (from_user != NULL)
185 			free(from_user);
186 		if ((from_user = strdup(addrp)) == NULL)
187 			err(EX_TEMPFAIL, NULL);
188 
189 		if (debug) {
190 			if (from_path != NULL)
191 				(void)fprintf(stderr,
192 				    "from_path: %s\n", from_path);
193 			(void)fprintf(stderr, "from_user: %s\n", from_user);
194 		}
195 
196 		if (offset != -1)
197 			offset = (off_t)ftell(stdin);
198 	}
199 
200 	i = 0;
201 	args[i++] = _PATH_SENDMAIL;	/* Build sendmail's argument list. */
202 	args[i++] = "-oee";		/* No errors, just status. */
203 	args[i++] = "-odq";		/* Queue it, don't try to deliver. */
204 	args[i++] = "-oi";		/* Ignore '.' on a line by itself. */
205 
206 	if (from_sys != NULL) {		/* Set sender's host name. */
207 		if (strchr(from_sys, '.') == NULL)
208 			(void)snprintf(buf, sizeof(buf),
209 			    "-oMs%s.%s", from_sys, domain);
210 		else
211 			(void)snprintf(buf, sizeof(buf), "-oMs%s", from_sys);
212 		if ((args[i++] = strdup(buf)) == NULL)
213 			 err(EX_TEMPFAIL, NULL);
214 	}
215 					/* Set protocol used. */
216 	(void)snprintf(buf, sizeof(buf), "-oMr%s", domain);
217 	if ((args[i++] = strdup(buf)) == NULL)
218 		err(EX_TEMPFAIL, NULL);
219 
220 					/* Set name of ``from'' person. */
221 	(void)snprintf(buf, sizeof(buf), "-f%s%s",
222 	    from_path ? from_path : "", from_user);
223 	if ((args[i++] = strdup(buf)) == NULL)
224 		err(EX_TEMPFAIL, NULL);
225 
226 	/*
227 	 * Don't copy arguments beginning with - as they will be
228 	 * passed to sendmail and could be interpreted as flags.
229 	 */
230 	do {
231 		if (*argv && **argv == '-')
232 			err(EX_USAGE, "dash precedes argument: %s", *argv);
233 	} while ((args[i++] = *argv++) != NULL);
234 
235 	if (debug) {
236 		(void)fprintf(stderr, "Sendmail arguments:\n");
237 		for (i = 0; args[i]; i++)
238 			(void)fprintf(stderr, "\t%s\n", args[i]);
239 	}
240 
241 	/*
242 	 * If called with a regular file as standard input, seek to the right
243 	 * position in the file and just exec sendmail.  Could probably skip
244 	 * skip the stat, but it's not unreasonable to believe that a failed
245 	 * seek will cause future reads to fail.
246 	 */
247 	if (!fstat(STDIN_FILENO, &sb) && S_ISREG(sb.st_mode)) {
248 		if (lseek(STDIN_FILENO, offset, SEEK_SET) != offset)
249 			err(EX_TEMPFAIL, "stdin seek");
250 		execv(_PATH_SENDMAIL, args);
251 		err(EX_OSERR, "%s", _PATH_SENDMAIL);
252 	}
253 
254 	if (pipe(pdes) < 0)
255 		err(EX_OSERR, NULL);
256 
257 	switch (pid = vfork()) {
258 	case -1:				/* Err. */
259 		err(EX_OSERR, NULL);
260 	case 0:					/* Child. */
261 		if (pdes[0] != STDIN_FILENO) {
262 			(void)dup2(pdes[0], STDIN_FILENO);
263 			(void)close(pdes[0]);
264 		}
265 		(void)close(pdes[1]);
266 		execv(_PATH_SENDMAIL, args);
267 		_exit(127);
268 		/* NOTREACHED */
269 	}
270 
271 	if ((fp = fdopen(pdes[1], "w")) == NULL)
272 		err(EX_OSERR, NULL);
273 	(void)close(pdes[0]);
274 
275 	/* Copy the file down the pipe. */
276 	do {
277 		(void)fprintf(fp, "%s", lbuf);
278 	} while (fgets(lbuf, sizeof(lbuf), stdin) != NULL);
279 
280 	if (ferror(stdin))
281 		err(EX_TEMPFAIL, "stdin: %s", strerror(errno));
282 
283 	if (fclose(fp))
284 		err(EX_OSERR, NULL);
285 
286 	if ((waitpid(pid, &status, 0)) == -1)
287 		err(EX_OSERR, "%s", _PATH_SENDMAIL);
288 
289 	if (!WIFEXITED(status))
290 		err(EX_OSERR,
291 		    "%s: did not terminate normally", _PATH_SENDMAIL);
292 
293 	if (WEXITSTATUS(status))
294 		err(status, "%s: terminated with %d (non-zero) status",
295 		    _PATH_SENDMAIL, WEXITSTATUS(status));
296 	exit(EX_OK);
297 }
298 
299 void
300 usage()
301 {
302 	(void)fprintf(stderr, "usage: rmail [-T] [-D domain] user ...\n");
303 	exit(EX_USAGE);
304 }
305 
306 #ifdef __STDC__
307 #include <stdarg.h>
308 #else
309 #include <varargs.h>
310 #endif
311 
312 void
313 #ifdef __STDC__
314 err(int eval, const char *fmt, ...)
315 #else
316 err(eval, fmt, va_alist)
317 	int eval;
318 	const char *fmt;
319 	va_dcl
320 #endif
321 {
322 	va_list ap;
323 #if __STDC__
324 	va_start(ap, fmt);
325 #else
326 	va_start(ap);
327 #endif
328 	(void)fprintf(stderr, "rmail: ");
329 	(void)vfprintf(stderr, fmt, ap);
330 	va_end(ap);
331 	(void)fprintf(stderr, "\n");
332 	exit(eval);
333 }
334