xref: /freebsd/contrib/sendmail/rmail/rmail.c (revision 2a4a1db342263067035ce69a4017c645da63455d)
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 #include <sm/gen.h>
14 
15 SM_IDSTR(copyright,
16 "@(#) Copyright (c) 1998-2001 Sendmail, 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 1.1.1.8 2002/02/17 21:56:45 gshapiro Exp $")
22 
23 /* $FreeBSD$ */
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 
70 static void err __P((int, const char *, ...));
71 static void usage __P((void));
72 static char *xalloc __P((int));
73 
74 #define newstr(s)	strcpy(xalloc(strlen(s) + 1), s)
75 
76 static char *
77 xalloc(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 
92 int
93 main(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)) == NULL)
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)) != NULL);
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 
410 static void
411 usage()
412 {
413 	(void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
414 			     "usage: rmail [-T] [-D domain] user ...\n");
415 	exit(EX_USAGE);
416 }
417 
418 static void
419 #ifdef __STDC__
420 err(int eval, const char *fmt, ...)
421 #else /* __STDC__ */
422 err(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