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