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