xref: /freebsd/contrib/sendmail/rmail/rmail.c (revision 77a0943ded95b9e6438f7db70c4a28e4d93946d4)
1 /*
2  * Copyright (c) 1998-2000 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-2000 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.8 2000/09/16 22:20:25 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
103 extern int	snprintf __P((char *, size_t, const char *, ...));
104 #endif /* !HASSNPRINTF */
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++] = "-G";		/* relay submission */
322 	args[i++] = "-oee";		/* No errors, just status. */
323 #ifdef QUEUE_ONLY
324 	args[i++] = "-odq";		/* Queue it, don't try to deliver. */
325 #else
326 	args[i++] = "-odi";		/* Deliver in foreground. */
327 #endif
328 	args[i++] = "-oi";		/* Ignore '.' on a line by itself. */
329 
330 	/* set from system and protocol used */
331 	if (from_sys == NULL)
332 		snprintf(buf, sizeof(buf), "-p%s", domain);
333 	else if (strchr(from_sys, '.') == NULL)
334 		snprintf(buf, sizeof(buf), "-p%s:%s.%s",
335 			domain, from_sys, domain);
336 	else
337 		snprintf(buf, sizeof(buf), "-p%s:%s", domain, from_sys);
338 	args[i++] = newstr(buf);
339 
340 	/* Set name of ``from'' person. */
341 	snprintf(buf, sizeof(buf), "-f%s%s",
342 		 from_path ? from_path : "", from_user);
343 	args[i++] = newstr(buf);
344 
345 	/*
346 	**  Don't copy arguments beginning with - as they will be
347 	**  passed to sendmail and could be interpreted as flags.
348 	**  To prevent confusion of sendmail wrap < and > around
349 	**  the address (helps to pass addrs like @gw1,@gw2:aa@bb)
350 	*/
351 
352 	while (*argv != NULL)
353 	{
354 		if (**argv == '-')
355 			err(EX_USAGE, "dash precedes argument: %s", *argv);
356 
357 		if (strchr(*argv, ',') == NULL || strchr(*argv, '<') != NULL)
358 			args[i++] = *argv;
359 		else
360 		{
361 			len = strlen(*argv) + 3;
362 			if ((args[i] = malloc(len)) == NULL)
363 				err(EX_TEMPFAIL, "Cannot malloc");
364 			snprintf(args[i++], len, "<%s>", *argv);
365 		}
366 		argv++;
367 		argc--;
368 
369 		/* Paranoia check, argc used for args[] bound */
370 		if (argc < 0)
371 			err(EX_SOFTWARE, "Argument count mismatch");
372 	}
373 	args[i] = NULL;
374 
375 	if (debug)
376 	{
377 		fprintf(stderr, "Sendmail arguments:\n");
378 		for (i = 0; args[i] != NULL; i++)
379 			fprintf(stderr, "\t%s\n", args[i]);
380 	}
381 
382 	/*
383 	**  If called with a regular file as standard input, seek to the right
384 	**  position in the file and just exec sendmail.  Could probably skip
385 	**  skip the stat, but it's not unreasonable to believe that a failed
386 	**  seek will cause future reads to fail.
387 	*/
388 
389 	if (!fstat(STDIN_FILENO, &sb) && S_ISREG(sb.st_mode))
390 	{
391 		if (lseek(STDIN_FILENO, offset, SEEK_SET) != offset)
392 			err(EX_TEMPFAIL, "stdin seek");
393 		(void) execv(_PATH_SENDMAIL, args);
394 		err(EX_OSERR, "%s", _PATH_SENDMAIL);
395 	}
396 
397 	if (pipe(pdes) < 0)
398 		err(EX_OSERR, NULL);
399 
400 	switch (pid = FORK())
401 	{
402 	  case -1:				/* Err. */
403 		err(EX_OSERR, NULL);
404 		/* NOTREACHED */
405 
406 	  case 0:				/* Child. */
407 		if (pdes[0] != STDIN_FILENO)
408 		{
409 			(void) dup2(pdes[0], STDIN_FILENO);
410 			(void) close(pdes[0]);
411 		}
412 		(void) close(pdes[1]);
413 		(void) execv(_PATH_SENDMAIL, args);
414 		_exit(127);
415 		/* NOTREACHED */
416 	}
417 
418 	if ((fp = fdopen(pdes[1], "w")) == NULL)
419 		err(EX_OSERR, NULL);
420 	(void) close(pdes[0]);
421 
422 	/* Copy the file down the pipe. */
423 	do
424 	{
425 		(void) fprintf(fp, "%s", lbuf);
426 	} while (fgets(lbuf, sizeof(lbuf), stdin) != NULL);
427 
428 	if (ferror(stdin))
429 		err(EX_TEMPFAIL, "stdin: %s", errstring(errno));
430 
431 	if (fclose(fp))
432 		err(EX_OSERR, NULL);
433 
434 	if ((waitpid(pid, &status, 0)) == -1)
435 		err(EX_OSERR, "%s", _PATH_SENDMAIL);
436 
437 	if (!WIFEXITED(status))
438 		err(EX_OSERR, "%s: did not terminate normally", _PATH_SENDMAIL);
439 
440 	if (WEXITSTATUS(status))
441 		err(status, "%s: terminated with %d (non-zero) status",
442 		    _PATH_SENDMAIL, WEXITSTATUS(status));
443 	exit(EX_OK);
444 	/* NOTREACHED */
445 	return EX_OK;
446 }
447 
448 static void
449 usage()
450 {
451 	(void) fprintf(stderr, "usage: rmail [-T] [-D domain] user ...\n");
452 	exit(EX_USAGE);
453 }
454 
455 #ifdef __STDC__
456 # include <stdarg.h>
457 #else /* __STDC__ */
458 # include <varargs.h>
459 #endif /* __STDC__ */
460 
461 static void
462 #ifdef __STDC__
463 err(int eval, const char *fmt, ...)
464 #else /* __STDC__ */
465 err(eval, fmt, va_alist)
466 	int eval;
467 	const char *fmt;
468 	va_dcl
469 #endif /* __STDC__ */
470 {
471 	va_list ap;
472 #ifdef __STDC__
473 	va_start(ap, fmt);
474 #else /* __STDC__ */
475 	va_start(ap);
476 #endif /* __STDC__ */
477 	(void) fprintf(stderr, "rmail: ");
478 	(void) vfprintf(stderr, fmt, ap);
479 	va_end(ap);
480 	(void) fprintf(stderr, "\n");
481 	exit(eval);
482 }
483