xref: /original-bsd/bin/rmail/rmail.c (revision 0ac4996f)
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.3 (Berkeley) 05/15/95";
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 #ifndef MAX
56 # define MAX(a, b)	((a) < (b) ? (b) : (a))
57 #endif
58 
59 void err __P((int, const char *, ...));
60 void usage __P((void));
61 
62 int
63 main(argc, argv)
64 	int argc;
65 	char *argv[];
66 {
67 	extern char *optarg;
68 	extern int errno, optind;
69 	FILE *fp;
70 	struct stat sb;
71 	size_t fplen, fptlen, len;
72 	off_t offset;
73 	int ch, debug, i, pdes[2], pid, status;
74 	char *addrp, *domain, *p, *t;
75 	char *from_path, *from_sys, *from_user;
76 	char *args[100], buf[2048], lbuf[2048];
77 
78 	debug = 0;
79 	domain = "UUCP";		/* Default "domain". */
80 	while ((ch = getopt(argc, argv, "D:T")) != EOF)
81 		switch (ch) {
82 		case 'T':
83 			debug = 1;
84 			break;
85 		case 'D':
86 			domain = optarg;
87 			break;
88 		case '?':
89 		default:
90 			usage();
91 		}
92 	argc -= optind;
93 	argv += optind;
94 
95 	if (argc < 1)
96 		usage();
97 
98 	from_path = from_sys = from_user = NULL;
99 	for (offset = 0;;) {
100 
101 		/* Get and nul-terminate the line. */
102 		if (fgets(lbuf, sizeof(lbuf), stdin) == NULL)
103 			exit (EX_DATAERR);
104 		if ((p = strchr(lbuf, '\n')) == NULL)
105 			err(EX_DATAERR, "line too long");
106 		*p = '\0';
107 
108 		/* Parse lines until reach a non-"From" line. */
109 		if (!strncmp(lbuf, "From ", 5))
110 			addrp = lbuf + 5;
111 		else if (!strncmp(lbuf, ">From ", 6))
112 			addrp = lbuf + 6;
113 		else if (offset == 0)
114 			err(EX_DATAERR,
115 			    "missing or empty From line: %s", lbuf);
116 		else {
117 			*p = '\n';
118 			break;
119 		}
120 
121 		if (*addrp == '\0')
122 			err(EX_DATAERR, "corrupted From line: %s", lbuf);
123 
124 		/* Use the "remote from" if it exists. */
125 		for (p = addrp; (p = strchr(p + 1, 'r')) != NULL;)
126 			if (!strncmp(p, "remote from ", 12)) {
127 				for (t = p += 12; *t && !isspace(*t); ++t);
128 				*t = '\0';
129 				if (debug)
130 					(void)fprintf(stderr,
131 					    "remote from: %s\n", p);
132 				break;
133 			}
134 
135 		/* Else use the string up to the last bang. */
136 		if (p == NULL)
137 			if (*addrp == '!')
138 				err(EX_DATAERR,
139 				    "bang starts address: %s", addrp);
140 			else if ((t = strrchr(addrp, '!')) != NULL) {
141 				*t = '\0';
142 				p = addrp;
143 				addrp = t + 1;
144 				if (*addrp == '\0')
145 					err(EX_DATAERR,
146 					    "corrupted From line: %s", lbuf);
147 				if (debug)
148 					(void)fprintf(stderr, "bang: %s\n", p);
149 			}
150 
151 		/* 'p' now points to any system string from this line. */
152 		if (p != NULL) {
153 			/* Nul terminate it as necessary. */
154 			for (t = p; *t && !isspace(*t); ++t);
155 			*t = '\0';
156 
157 			/* If the first system, copy to the from_sys string. */
158 			if (from_sys == NULL) {
159 				if ((from_sys = strdup(p)) == NULL)
160 					err(EX_TEMPFAIL, NULL);
161 				if (debug)
162 					(void)fprintf(stderr,
163 					    "from_sys: %s\n", from_sys);
164 			}
165 
166 			/* Concatenate to the path string. */
167 			len = t - p;
168 			if (from_path == NULL) {
169 				fplen = 0;
170 				if ((from_path = malloc(fptlen = 256)) == NULL)
171 					err(EX_TEMPFAIL, NULL);
172 			}
173 			if (fplen + len + 2 > fptlen) {
174 				fptlen += MAX(fplen + len + 2, 256);
175 				if ((from_path =
176 				    realloc(from_path, fptlen)) == NULL)
177 					err(EX_TEMPFAIL, NULL);
178 			}
179 			memmove(from_path + fplen, p, len);
180 			fplen += len;
181 			from_path[fplen++] = '!';
182 			from_path[fplen] = '\0';
183 		}
184 
185 		/* Save off from user's address; the last one wins. */
186 		for (p = addrp; *p && !isspace(*p); ++p);
187 		*p = '\0';
188 		if (*addrp == '\0')
189 			addrp = "<>";
190 		if (from_user != NULL)
191 			free(from_user);
192 		if ((from_user = strdup(addrp)) == NULL)
193 			err(EX_TEMPFAIL, NULL);
194 
195 		if (debug) {
196 			if (from_path != NULL)
197 				(void)fprintf(stderr,
198 				    "from_path: %s\n", from_path);
199 			(void)fprintf(stderr, "from_user: %s\n", from_user);
200 		}
201 
202 		if (offset != -1)
203 			offset = (off_t)ftell(stdin);
204 	}
205 
206 	i = 0;
207 	args[i++] = _PATH_SENDMAIL;	/* Build sendmail's argument list. */
208 	args[i++] = "-oee";		/* No errors, just status. */
209 	args[i++] = "-odq";		/* Queue it, don't try to deliver. */
210 	args[i++] = "-oi";		/* Ignore '.' on a line by itself. */
211 
212 	/* set from system and protocol used */
213 	if (from_sys == NULL)
214 		(void)snprintf(buf, sizeof(buf), "-p%s", domain);
215 	else if (strchr(from_sys, '.') == NULL)
216 		(void)snprintf(buf, sizeof(buf), "-p%s:%s.%s",
217 			domain, from_sys, domain);
218 	else
219 		(void)snprintf(buf, sizeof(buf), "-p%s:%s", domain, from_sys);
220 	if ((args[i++] = strdup(buf)) == NULL)
221 		err(EX_TEMPFAIL, NULL);
222 
223 					/* Set name of ``from'' person. */
224 	(void)snprintf(buf, sizeof(buf), "-f%s%s",
225 	    from_path ? from_path : "", from_user);
226 	if ((args[i++] = strdup(buf)) == NULL)
227 		err(EX_TEMPFAIL, NULL);
228 
229 	/*
230 	 * Don't copy arguments beginning with - as they will be
231 	 * passed to sendmail and could be interpreted as flags.
232 	 * To prevent confusion of sendmail wrap < and > around
233 	 * the address (helps to pass addrs like @gw1,@gw2:aa@bb)
234 	 */
235 	while (*argv) {
236 		if (**argv == '-')
237 			err(EX_USAGE, "dash precedes argument: %s", *argv);
238 		if (strchr(*argv, ',') == NULL || strchr(*argv, '<') != NULL)
239 			args[i++] = *argv;
240 		else {
241 			if ((args[i] = malloc(strlen(*argv) + 3)) == NULL)
242 				err(EX_TEMPFAIL, "Cannot malloc");
243 			sprintf (args [i++], "<%s>", *argv);
244 		}
245 		argv++;
246 	}
247 	args[i] = 0;
248 
249 	if (debug) {
250 		(void)fprintf(stderr, "Sendmail arguments:\n");
251 		for (i = 0; args[i]; i++)
252 			(void)fprintf(stderr, "\t%s\n", args[i]);
253 	}
254 
255 	/*
256 	 * If called with a regular file as standard input, seek to the right
257 	 * position in the file and just exec sendmail.  Could probably skip
258 	 * skip the stat, but it's not unreasonable to believe that a failed
259 	 * seek will cause future reads to fail.
260 	 */
261 	if (!fstat(STDIN_FILENO, &sb) && S_ISREG(sb.st_mode)) {
262 		if (lseek(STDIN_FILENO, offset, SEEK_SET) != offset)
263 			err(EX_TEMPFAIL, "stdin seek");
264 		execv(_PATH_SENDMAIL, args);
265 		err(EX_OSERR, "%s", _PATH_SENDMAIL);
266 	}
267 
268 	if (pipe(pdes) < 0)
269 		err(EX_OSERR, NULL);
270 
271 	switch (pid = vfork()) {
272 	case -1:				/* Err. */
273 		err(EX_OSERR, NULL);
274 	case 0:					/* Child. */
275 		if (pdes[0] != STDIN_FILENO) {
276 			(void)dup2(pdes[0], STDIN_FILENO);
277 			(void)close(pdes[0]);
278 		}
279 		(void)close(pdes[1]);
280 		execv(_PATH_SENDMAIL, args);
281 		_exit(127);
282 		/* NOTREACHED */
283 	}
284 
285 	if ((fp = fdopen(pdes[1], "w")) == NULL)
286 		err(EX_OSERR, NULL);
287 	(void)close(pdes[0]);
288 
289 	/* Copy the file down the pipe. */
290 	do {
291 		(void)fprintf(fp, "%s", lbuf);
292 	} while (fgets(lbuf, sizeof(lbuf), stdin) != NULL);
293 
294 	if (ferror(stdin))
295 		err(EX_TEMPFAIL, "stdin: %s", strerror(errno));
296 
297 	if (fclose(fp))
298 		err(EX_OSERR, NULL);
299 
300 	if ((waitpid(pid, &status, 0)) == -1)
301 		err(EX_OSERR, "%s", _PATH_SENDMAIL);
302 
303 	if (!WIFEXITED(status))
304 		err(EX_OSERR,
305 		    "%s: did not terminate normally", _PATH_SENDMAIL);
306 
307 	if (WEXITSTATUS(status))
308 		err(status, "%s: terminated with %d (non-zero) status",
309 		    _PATH_SENDMAIL, WEXITSTATUS(status));
310 	exit(EX_OK);
311 }
312 
313 void
314 usage()
315 {
316 	(void)fprintf(stderr, "usage: rmail [-T] [-D domain] user ...\n");
317 	exit(EX_USAGE);
318 }
319 
320 #ifdef __STDC__
321 #include <stdarg.h>
322 #else
323 #include <varargs.h>
324 #endif
325 
326 void
327 #ifdef __STDC__
328 err(int eval, const char *fmt, ...)
329 #else
330 err(eval, fmt, va_alist)
331 	int eval;
332 	const char *fmt;
333 	va_dcl
334 #endif
335 {
336 	va_list ap;
337 #if __STDC__
338 	va_start(ap, fmt);
339 #else
340 	va_start(ap);
341 #endif
342 	(void)fprintf(stderr, "rmail: ");
343 	(void)vfprintf(stderr, fmt, ap);
344 	va_end(ap);
345 	(void)fprintf(stderr, "\n");
346 	exit(eval);
347 }
348