xref: /freebsd/contrib/sendmail/rmail/rmail.c (revision 4cf49a43559ed9fdad601bdcccd2c55963008675)
1 /*
2  * Copyright (c) 1998 Sendmail, Inc.  All rights reserved.
3  * Copyright (c) 1988, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * By using this file, you agree to the terms and conditions set
7  * forth in the LICENSE file which can be found at the top level of
8  * the sendmail distribution.
9  *
10  */
11 
12 #ifndef lint
13 static char copyright[] =
14 "@(#) Copyright (c) 1988, 1993\n\
15 	The Regents of the University of California.  All rights reserved.\n";
16 #endif /* not lint */
17 
18 #ifndef lint
19 static char sccsid[] = "@(#)rmail.c	8.18 (Berkeley) 10/23/1998";
20 #endif /* not lint */
21 
22 /*
23  * RMAIL -- UUCP mail server.
24  *
25  * This program reads the >From ... remote from ... lines that UUCP is so
26  * fond of and turns them into something reasonable.  It then execs sendmail
27  * with various options built from these lines.
28  *
29  * The expected syntax is:
30  *
31  *	 <user> := [-a-z0-9]+
32  *	 <date> := ctime format
33  *	 <site> := [-a-z0-9!]+
34  * <blank line> := "^\n$"
35  *	 <from> := "From" <space> <user> <space> <date>
36  *		  [<space> "remote from" <space> <site>]
37  *    <forward> := ">" <from>
38  *	    msg := <from> <forward>* <blank-line> <body>
39  *
40  * The output of rmail(8) compresses the <forward> lines into a single
41  * from path.
42  *
43  * The err(3) routine is included here deliberately to make this code
44  * a bit more portable.
45  */
46 #include <sys/param.h>
47 #include <sys/stat.h>
48 #include <sys/wait.h>
49 
50 #include <ctype.h>
51 #include <fcntl.h>
52 #ifdef BSD4_4
53 # define FORK vfork
54 # include <paths.h>
55 #else
56 # define FORK fork
57 # ifndef _PATH_SENDMAIL
58 #  define _PATH_SENDMAIL "/usr/lib/sendmail"
59 # endif
60 #endif
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <unistd.h>
65 #ifdef EX_OK
66 # undef EX_OK		/* unistd.h may have another use for this */
67 #endif
68 #include <sysexits.h>
69 
70 #ifndef MAX
71 # define MAX(a, b)	((a) < (b) ? (b) : (a))
72 #endif
73 
74 #ifndef __P
75 # ifdef __STDC__
76 #  define __P(protos)	protos
77 # else
78 #  define __P(protos)	()
79 #  define const
80 # endif
81 #endif
82 
83 #if defined(BSD4_4) || defined(linux) || SOLARIS >= 20600 || (SOLARIS < 10000 && SOLARIS >= 206)
84 # define HASSNPRINTF	1
85 #endif
86 
87 #if defined(IRIX64) || defined(IRIX5) || defined(IRIX6) || \
88     defined(BSD4_4) || defined(__osf__) || defined(__GNU_LIBRARY__)
89 # ifndef HASSTRERROR
90 #  define HASSTRERROR	1	/* has strerror(3) */
91 # endif
92 #endif
93 
94 #if !HASSTRERROR
95 extern char	*strerror __P((int));
96 #endif
97 
98 #if defined(sun) && !defined(BSD) && !defined(SOLARIS) && !defined(__svr4__) && !defined(__SVR4)
99 #  define memmove(d, s, l)	(bcopy((s), (d), (l)))
100 #endif
101 
102 #if !HASSNPRINTF
103 extern int	snprintf __P((char *, size_t, const char *, ...));
104 #endif /* !HASSNPRINTF */
105 
106 u_char	tTdvect[100];
107 
108 void err __P((int, const char *, ...));
109 void usage __P((void));
110 char *xalloc __P((int));
111 
112 #define newstr(s)	strcpy(xalloc(strlen(s) + 1), s)
113 
114 char *
115 xalloc(sz)
116 	register int sz;
117 {
118 	register char *p;
119 
120 	/* some systems can't handle size zero mallocs */
121 	if (sz <= 0)
122 		sz = 1;
123 
124 	p = malloc((unsigned) sz);
125 	if (p == NULL)
126 		err(EX_TEMPFAIL, "out of memory");
127 	return (p);
128 }
129 
130 int
131 main(argc, argv)
132 	int argc;
133 	char *argv[];
134 {
135 	extern char *optarg;
136 	extern int errno, optind;
137 	FILE *fp;
138 	struct stat sb;
139 	size_t fplen = 0, fptlen = 0, len;
140 	off_t offset;
141 	int ch, debug, i, pdes[2], pid, status;
142 	char *addrp = NULL, *domain, *p, *t;
143 	char *from_path, *from_sys, *from_user;
144 	char *args[100], buf[2048], lbuf[2048];
145 
146 	debug = 0;
147 	domain = "UUCP";		/* Default "domain". */
148 	while ((ch = getopt(argc, argv, "D:T")) != -1)
149 		switch (ch) {
150 		case 'T':
151 			debug = 1;
152 			break;
153 		case 'D':
154 			domain = optarg;
155 			break;
156 		case '?':
157 		default:
158 			usage();
159 		}
160 	argc -= optind;
161 	argv += optind;
162 
163 	if (argc < 1)
164 		usage();
165 
166 	fplen = fptlen = 0;
167 	addrp = "";
168 	from_path = from_sys = from_user = NULL;
169 	for (offset = 0;;) {
170 
171 		/* Get and nul-terminate the line. */
172 		if (fgets(lbuf, sizeof(lbuf), stdin) == NULL)
173 			exit (EX_DATAERR);
174 		if ((p = strchr(lbuf, '\n')) == NULL)
175 			err(EX_DATAERR, "line too long");
176 		*p = '\0';
177 
178 		/* Parse lines until reach a non-"From" line. */
179 		if (!strncmp(lbuf, "From ", 5))
180 			addrp = lbuf + 5;
181 		else if (!strncmp(lbuf, ">From ", 6))
182 			addrp = lbuf + 6;
183 		else if (offset == 0)
184 			err(EX_DATAERR,
185 			    "missing or empty From line: %s", lbuf);
186 		else {
187 			*p = '\n';
188 			break;
189 		}
190 
191 		if (*addrp == '\0')
192 			err(EX_DATAERR, "corrupted From line: %s", lbuf);
193 
194 		/* Use the "remote from" if it exists. */
195 		for (p = addrp; (p = strchr(p + 1, 'r')) != NULL;)
196 			if (!strncmp(p, "remote from ", 12)) {
197 				for (t = p += 12;
198 				     *t && !(isascii(*t) && isspace(*t)); ++t);
199 				*t = '\0';
200 				if (debug)
201 					(void)fprintf(stderr,
202 					    "remote from: %s\n", p);
203 				break;
204 			}
205 
206 		/* Else use the string up to the last bang. */
207 		if (p == NULL) {
208 			if (*addrp == '!')
209 				err(EX_DATAERR,
210 				    "bang starts address: %s", addrp);
211 			else if ((t = strrchr(addrp, '!')) != NULL) {
212 				*t = '\0';
213 				p = addrp;
214 				addrp = t + 1;
215 				if (*addrp == '\0')
216 					err(EX_DATAERR,
217 					    "corrupted From line: %s", lbuf);
218 				if (debug)
219 					(void)fprintf(stderr, "bang: %s\n", p);
220 			}
221 		}
222 		/* 'p' now points to any system string from this line. */
223 		if (p != NULL) {
224 			/* Nul terminate it as necessary. */
225 			for (t = p; *t && !(isascii(*t) && isspace(*t)); ++t);
226 			*t = '\0';
227 
228 			/* If the first system, copy to the from_sys string. */
229 			if (from_sys == NULL) {
230 				from_sys = newstr(p);
231 				if (debug)
232 					(void)fprintf(stderr,
233 					    "from_sys: %s\n", from_sys);
234 			}
235 
236 			/* Concatenate to the path string. */
237 			len = t - p;
238 			if (from_path == NULL) {
239 				fplen = 0;
240 				if ((from_path = malloc(fptlen = 256)) == NULL)
241 					err(EX_TEMPFAIL, NULL);
242 			}
243 			if (fplen + len + 2 > fptlen) {
244 				fptlen += MAX(fplen + len + 2, 256);
245 				if ((from_path =
246 				    realloc(from_path, fptlen)) == NULL)
247 					err(EX_TEMPFAIL, NULL);
248 			}
249 			memmove(from_path + fplen, p, len);
250 			fplen += len;
251 			from_path[fplen++] = '!';
252 			from_path[fplen] = '\0';
253 		}
254 
255 		/* Save off from user's address; the last one wins. */
256 		for (p = addrp; *p && !(isascii(*p) && isspace(*p)); ++p);
257 		*p = '\0';
258 		if (*addrp == '\0')
259 			addrp = "<>";
260 		if (from_user != NULL)
261 			free(from_user);
262 		from_user = newstr(addrp);
263 
264 		if (debug) {
265 			if (from_path != NULL)
266 				(void)fprintf(stderr,
267 				    "from_path: %s\n", from_path);
268 			(void)fprintf(stderr, "from_user: %s\n", from_user);
269 		}
270 
271 		if (offset != -1)
272 			offset = (off_t)ftell(stdin);
273 	}
274 
275 	i = 0;
276 	args[i++] = _PATH_SENDMAIL;	/* Build sendmail's argument list. */
277 	args[i++] = "-oee";		/* No errors, just status. */
278 #ifdef QUEUE_ONLY
279 	args[i++] = "-odq";		/* Queue it, don't try to deliver. */
280 #else
281 	args[i++] = "-odi";		/* Deliver in foreground. */
282 #endif
283 	args[i++] = "-oi";		/* Ignore '.' on a line by itself. */
284 
285 	/* set from system and protocol used */
286 	if (from_sys == NULL)
287 		(void)snprintf(buf, sizeof(buf), "-p%s", domain);
288 	else if (strchr(from_sys, '.') == NULL)
289 		(void)snprintf(buf, sizeof(buf), "-p%s:%s.%s",
290 			domain, from_sys, domain);
291 	else
292 		(void)snprintf(buf, sizeof(buf), "-p%s:%s", domain, from_sys);
293 	args[i++] = newstr(buf);
294 
295 					/* Set name of ``from'' person. */
296 	(void)snprintf(buf, sizeof(buf), "-f%s%s",
297 	    from_path ? from_path : "", from_user);
298 	args[i++] = newstr(buf);
299 
300 	/*
301 	 * Don't copy arguments beginning with - as they will be
302 	 * passed to sendmail and could be interpreted as flags.
303 	 * To prevent confusion of sendmail wrap < and > around
304 	 * the address (helps to pass addrs like @gw1,@gw2:aa@bb)
305 	 */
306 	while (*argv) {
307 		if (**argv == '-')
308 			err(EX_USAGE, "dash precedes argument: %s", *argv);
309 		if (strchr(*argv, ',') == NULL || strchr(*argv, '<') != NULL)
310 			args[i++] = *argv;
311 		else {
312 			if ((args[i] = malloc(strlen(*argv) + 3)) == NULL)
313 				err(EX_TEMPFAIL, "Cannot malloc");
314 			sprintf (args [i++], "<%s>", *argv);
315 		}
316 		argv++;
317 	}
318 	args[i] = 0;
319 
320 	if (debug) {
321 		(void)fprintf(stderr, "Sendmail arguments:\n");
322 		for (i = 0; args[i]; i++)
323 			(void)fprintf(stderr, "\t%s\n", args[i]);
324 	}
325 
326 	/*
327 	 * If called with a regular file as standard input, seek to the right
328 	 * position in the file and just exec sendmail.  Could probably skip
329 	 * skip the stat, but it's not unreasonable to believe that a failed
330 	 * seek will cause future reads to fail.
331 	 */
332 	if (!fstat(STDIN_FILENO, &sb) && S_ISREG(sb.st_mode)) {
333 		if (lseek(STDIN_FILENO, offset, SEEK_SET) != offset)
334 			err(EX_TEMPFAIL, "stdin seek");
335 		execv(_PATH_SENDMAIL, args);
336 		err(EX_OSERR, "%s", _PATH_SENDMAIL);
337 	}
338 
339 	if (pipe(pdes) < 0)
340 		err(EX_OSERR, NULL);
341 
342 	switch (pid = FORK()) {
343 	case -1:				/* Err. */
344 		err(EX_OSERR, NULL);
345 	case 0:					/* Child. */
346 		if (pdes[0] != STDIN_FILENO) {
347 			(void)dup2(pdes[0], STDIN_FILENO);
348 			(void)close(pdes[0]);
349 		}
350 		(void)close(pdes[1]);
351 		execv(_PATH_SENDMAIL, args);
352 		_exit(127);
353 		/* NOTREACHED */
354 	}
355 
356 	if ((fp = fdopen(pdes[1], "w")) == NULL)
357 		err(EX_OSERR, NULL);
358 	(void)close(pdes[0]);
359 
360 	/* Copy the file down the pipe. */
361 	do {
362 		(void)fprintf(fp, "%s", lbuf);
363 	} while (fgets(lbuf, sizeof(lbuf), stdin) != NULL);
364 
365 	if (ferror(stdin))
366 		err(EX_TEMPFAIL, "stdin: %s", strerror(errno));
367 
368 	if (fclose(fp))
369 		err(EX_OSERR, NULL);
370 
371 	if ((waitpid(pid, &status, 0)) == -1)
372 		err(EX_OSERR, "%s", _PATH_SENDMAIL);
373 
374 	if (!WIFEXITED(status))
375 		err(EX_OSERR,
376 		    "%s: did not terminate normally", _PATH_SENDMAIL);
377 
378 	if (WEXITSTATUS(status))
379 		err(status, "%s: terminated with %d (non-zero) status",
380 		    _PATH_SENDMAIL, WEXITSTATUS(status));
381 	exit(EX_OK);
382 }
383 
384 void
385 usage()
386 {
387 	(void)fprintf(stderr, "usage: rmail [-T] [-D domain] user ...\n");
388 	exit(EX_USAGE);
389 }
390 
391 #ifdef __STDC__
392 #include <stdarg.h>
393 #else
394 #include <varargs.h>
395 #endif
396 
397 void
398 #ifdef __STDC__
399 err(int eval, const char *fmt, ...)
400 #else
401 err(eval, fmt, va_alist)
402 	int eval;
403 	const char *fmt;
404 	va_dcl
405 #endif
406 {
407 	va_list ap;
408 #if __STDC__
409 	va_start(ap, fmt);
410 #else
411 	va_start(ap);
412 #endif
413 	(void)fprintf(stderr, "rmail: ");
414 	(void)vfprintf(stderr, fmt, ap);
415 	va_end(ap);
416 	(void)fprintf(stderr, "\n");
417 	exit(eval);
418 }
419 
420 #if !HASSTRERROR
421 
422 char *
423 strerror(eno)
424 	int eno;
425 {
426 	extern int sys_nerr;
427 	extern char *sys_errlist[];
428 	static char ebuf[60];
429 
430 	if (eno >= 0 && eno < sys_nerr)
431 		return sys_errlist[eno];
432 	(void) sprintf(ebuf, "Error %d", eno);
433 	return ebuf;
434 }
435 
436 #endif /* !HASSTRERROR */
437