rmail.c revision 266692
1/*
2 * Copyright (c) 1998-2001 Proofpoint, 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 * $FreeBSD: stable/10/contrib/sendmail/rmail/rmail.c 266692 2014-05-26 15:28:28Z gshapiro $
12 *
13 */
14
15#include <sm/gen.h>
16
17SM_IDSTR(copyright,
18"@(#) Copyright (c) 1998-2001 Proofpoint, Inc. and its suppliers.\n\
19	All rights reserved.\n\
20     Copyright (c) 1988, 1993\n\
21	The Regents of the University of California.  All rights reserved.\n")
22
23SM_IDSTR(id, "@(#)$Id: rmail.c,v 8.63 2013-11-22 20:51:53 ca Exp $")
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#include <sm/io.h>
58#include <stdlib.h>
59#include <sm/string.h>
60#include <unistd.h>
61#ifdef EX_OK
62# undef EX_OK		/* unistd.h may have another use for this */
63#endif /* EX_OK */
64#include <sysexits.h>
65
66#include <sm/conf.h>
67#include <sm/errstring.h>
68#include <sendmail/pathnames.h>
69
70static void err __P((int, const char *, ...));
71static void usage __P((void));
72static char *xalloc __P((int));
73
74#define newstr(s)	strcpy(xalloc(strlen(s) + 1), s)
75
76static char *
77xalloc(sz)
78	register int sz;
79{
80	register char *p;
81
82	/* some systems can't handle size zero mallocs */
83	if (sz <= 0)
84		sz = 1;
85
86	p = malloc(sz);
87	if (p == NULL)
88		err(EX_TEMPFAIL, "out of memory");
89	return (p);
90}
91
92int
93main(argc, argv)
94	int argc;
95	char *argv[];
96{
97	int ch, debug, i, pdes[2], pid, status;
98	size_t fplen = 0, fptlen = 0, len;
99	off_t offset;
100	SM_FILE_T *fp;
101	char *addrp = NULL, *domain, *p, *t;
102	char *from_path, *from_sys, *from_user;
103	char **args, buf[2048], lbuf[2048];
104	struct stat sb;
105	extern char *optarg;
106	extern int optind;
107
108	debug = 0;
109	domain = "UUCP";		/* Default "domain". */
110	while ((ch = getopt(argc, argv, "D:T")) != -1)
111	{
112		switch (ch)
113		{
114		  case 'T':
115			debug = 1;
116			break;
117
118		  case 'D':
119			domain = optarg;
120			break;
121
122		  case '?':
123		  default:
124			usage();
125		}
126	}
127
128	argc -= optind;
129	argv += optind;
130
131	if (argc < 1)
132		usage();
133
134	from_path = from_sys = from_user = NULL;
135	for (offset = 0; ; )
136	{
137		/* Get and nul-terminate the line. */
138		if (sm_io_fgets(smioin, SM_TIME_DEFAULT, lbuf,
139				sizeof(lbuf)) < 0)
140			err(EX_DATAERR, "no data");
141		if ((p = strchr(lbuf, '\n')) == NULL)
142			err(EX_DATAERR, "line too long");
143		*p = '\0';
144
145		/* Parse lines until reach a non-"From" line. */
146		if (!strncmp(lbuf, "From ", 5))
147			addrp = lbuf + 5;
148		else if (!strncmp(lbuf, ">From ", 6))
149			addrp = lbuf + 6;
150		else if (offset == 0)
151			err(EX_DATAERR,
152			    "missing or empty From line: %s", lbuf);
153		else
154		{
155			*p = '\n';
156			break;
157		}
158
159		if (addrp == NULL || *addrp == '\0')
160			err(EX_DATAERR, "corrupted From line: %s", lbuf);
161
162		/* Use the "remote from" if it exists. */
163		for (p = addrp; (p = strchr(p + 1, 'r')) != NULL; )
164		{
165			if (!strncmp(p, "remote from ", 12))
166			{
167				for (t = p += 12; *t != '\0'; ++t)
168				{
169					if (isascii(*t) && isspace(*t))
170						break;
171				}
172				*t = '\0';
173				if (debug)
174					(void) sm_io_fprintf(smioerr,
175							     SM_TIME_DEFAULT,
176							     "remote from: %s\n",
177							     p);
178				break;
179			}
180		}
181
182		/* Else use the string up to the last bang. */
183		if (p == NULL)
184		{
185			if (*addrp == '!')
186				err(EX_DATAERR, "bang starts address: %s",
187				    addrp);
188			else if ((t = strrchr(addrp, '!')) != NULL)
189			{
190				*t = '\0';
191				p = addrp;
192				addrp = t + 1;
193				if (*addrp == '\0')
194					err(EX_DATAERR,
195					    "corrupted From line: %s", lbuf);
196				if (debug)
197					(void) sm_io_fprintf(smioerr,
198							     SM_TIME_DEFAULT,
199							     "bang: %s\n", p);
200			}
201		}
202
203		/* 'p' now points to any system string from this line. */
204		if (p != NULL)
205		{
206			/* Nul terminate it as necessary. */
207			for (t = p; *t != '\0'; ++t)
208			{
209				if (isascii(*t) && isspace(*t))
210					break;
211			}
212			*t = '\0';
213
214			/* If the first system, copy to the from_sys string. */
215			if (from_sys == NULL)
216			{
217				from_sys = newstr(p);
218				if (debug)
219					(void) sm_io_fprintf(smioerr,
220							     SM_TIME_DEFAULT,
221							     "from_sys: %s\n",
222							     from_sys);
223			}
224
225			/* Concatenate to the path string. */
226			len = t - p;
227			if (from_path == NULL)
228			{
229				fplen = 0;
230				if ((from_path = malloc(fptlen = 256)) == NULL)
231					err(EX_TEMPFAIL, "out of memory");
232			}
233			if (fplen + len + 2 > fptlen)
234			{
235				fptlen += SM_MAX(fplen + len + 2, 256);
236				if ((from_path = realloc(from_path,
237							 fptlen)) == NULL)
238					err(EX_TEMPFAIL, "out of memory");
239			}
240			memmove(from_path + fplen, p, len);
241			fplen += len;
242			from_path[fplen++] = '!';
243			from_path[fplen] = '\0';
244		}
245
246		/* Save off from user's address; the last one wins. */
247		for (p = addrp; *p != '\0'; ++p)
248		{
249			if (isascii(*p) && isspace(*p))
250				break;
251		}
252		*p = '\0';
253		if (*addrp == '\0')
254			addrp = "<>";
255		if (from_user != NULL)
256			free(from_user);
257		from_user = newstr(addrp);
258
259		if (debug)
260		{
261			if (from_path != NULL)
262				(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
263						     "from_path: %s\n",
264						     from_path);
265			(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
266					     "from_user: %s\n", from_user);
267		}
268
269		if (offset != -1)
270			offset = (off_t)sm_io_tell(smioin, SM_TIME_DEFAULT);
271	}
272
273
274	/* Allocate args (with room for sendmail args as well as recipients */
275	args = (char **)xalloc(sizeof(*args) * (10 + argc));
276
277	i = 0;
278	args[i++] = _PATH_SENDMAIL;	/* Build sendmail's argument list. */
279	args[i++] = "-G";		/* relay submission */
280	args[i++] = "-oee";		/* No errors, just status. */
281#ifdef QUEUE_ONLY
282	args[i++] = "-odq";		/* Queue it, don't try to deliver. */
283#else
284	args[i++] = "-odi";		/* Deliver in foreground. */
285#endif
286	args[i++] = "-oi";		/* Ignore '.' on a line by itself. */
287
288	/* set from system and protocol used */
289	if (from_sys == NULL)
290		sm_snprintf(buf, sizeof(buf), "-p%s", domain);
291	else if (strchr(from_sys, '.') == NULL)
292		sm_snprintf(buf, sizeof(buf), "-p%s:%s.%s",
293			domain, from_sys, domain);
294	else
295		sm_snprintf(buf, sizeof(buf), "-p%s:%s", domain, from_sys);
296	args[i++] = newstr(buf);
297
298	/* Set name of ``from'' person. */
299	sm_snprintf(buf, sizeof(buf), "-f%s%s",
300		 from_path ? from_path : "", from_user);
301	args[i++] = newstr(buf);
302
303	/*
304	**  Don't copy arguments beginning with - as they will be
305	**  passed to sendmail and could be interpreted as flags.
306	**  To prevent confusion of sendmail wrap < and > around
307	**  the address (helps to pass addrs like @gw1,@gw2:aa@bb)
308	*/
309
310	while (*argv != NULL)
311	{
312		if (**argv == '-')
313			err(EX_USAGE, "dash precedes argument: %s", *argv);
314
315		if (strchr(*argv, ',') == NULL || strchr(*argv, '<') != NULL)
316			args[i++] = *argv;
317		else
318		{
319			len = strlen(*argv) + 3;
320			if ((args[i] = malloc(len)) == NULL)
321				err(EX_TEMPFAIL, "Cannot malloc");
322			sm_snprintf(args[i++], len, "<%s>", *argv);
323		}
324		argv++;
325		argc--;
326
327		/* Paranoia check, argc used for args[] bound */
328		if (argc < 0)
329			err(EX_SOFTWARE, "Argument count mismatch");
330	}
331	args[i] = NULL;
332
333	if (debug)
334	{
335		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
336				     "Sendmail arguments:\n");
337		for (i = 0; args[i] != NULL; i++)
338			(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
339					     "\t%s\n", args[i]);
340	}
341
342	/*
343	**  If called with a regular file as standard input, seek to the right
344	**  position in the file and just exec sendmail.  Could probably skip
345	**  skip the stat, but it's not unreasonable to believe that a failed
346	**  seek will cause future reads to fail.
347	*/
348
349	if (!fstat(STDIN_FILENO, &sb) && S_ISREG(sb.st_mode))
350	{
351		if (lseek(STDIN_FILENO, offset, SEEK_SET) != offset)
352			err(EX_TEMPFAIL, "stdin seek");
353		(void) execv(_PATH_SENDMAIL, args);
354		err(EX_OSERR, "%s", _PATH_SENDMAIL);
355	}
356
357	if (pipe(pdes) < 0)
358		err(EX_OSERR, "pipe failed");
359
360	switch (pid = fork())
361	{
362	  case -1:				/* Err. */
363		err(EX_OSERR, "fork failed");
364		/* NOTREACHED */
365
366	  case 0:				/* Child. */
367		if (pdes[0] != STDIN_FILENO)
368		{
369			(void) dup2(pdes[0], STDIN_FILENO);
370			(void) close(pdes[0]);
371		}
372		(void) close(pdes[1]);
373		(void) execv(_PATH_SENDMAIL, args);
374		err(EX_UNAVAILABLE, "%s", _PATH_SENDMAIL);
375		/* NOTREACHED */
376	}
377
378	if ((fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &(pdes[1]),
379			     SM_IO_WRONLY, NULL)) == NULL)
380		err(EX_OSERR, "sm_io_open failed");
381	(void) close(pdes[0]);
382
383	/* Copy the file down the pipe. */
384	do
385	{
386		(void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s", lbuf);
387	} while (sm_io_fgets(smioin, SM_TIME_DEFAULT, lbuf,
388			     sizeof(lbuf)) >= 0);
389
390	if (sm_io_error(smioin))
391		err(EX_TEMPFAIL, "stdin: %s", sm_errstring(errno));
392
393	if (sm_io_close(fp, SM_TIME_DEFAULT))
394		err(EX_OSERR, "sm_io_close failed");
395
396	if ((waitpid(pid, &status, 0)) == -1)
397		err(EX_OSERR, "%s", _PATH_SENDMAIL);
398
399	if (!WIFEXITED(status))
400		err(EX_OSERR, "%s: did not terminate normally", _PATH_SENDMAIL);
401
402	if (WEXITSTATUS(status))
403		err(status, "%s: terminated with %d (non-zero) status",
404		    _PATH_SENDMAIL, WEXITSTATUS(status));
405	exit(EX_OK);
406	/* NOTREACHED */
407	return EX_OK;
408}
409
410static void
411usage()
412{
413	(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
414			     "usage: rmail [-T] [-D domain] user ...\n");
415	exit(EX_USAGE);
416}
417
418static void
419#ifdef __STDC__
420err(int eval, const char *fmt, ...)
421#else /* __STDC__ */
422err(eval, fmt, va_alist)
423	int eval;
424	const char *fmt;
425	va_dcl
426#endif /* __STDC__ */
427{
428	SM_VA_LOCAL_DECL
429
430	if (fmt != NULL)
431	{
432		SM_VA_START(ap, fmt);
433		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "rmail: ");
434		(void) sm_io_vfprintf(smioerr, SM_TIME_DEFAULT, fmt, ap);
435		SM_VA_END(ap);
436		(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "\n");
437	}
438	exit(eval);
439}
440