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