xref: /original-bsd/usr.sbin/sendmail/src/main.c (revision 3c4b3416)
1 # include <stdio.h>
2 # include <signal.h>
3 # include <ctype.h>
4 # include "dlvrmail.h"
5 # ifdef LOG
6 # include <log.h>
7 # endif LOG
8 
9 static char	SccsId[] = "@(#)main.c	1.5	08/02/80";
10 
11 /*
12 **  DELIVERMAIL -- Deliver mail to a set of destinations
13 **
14 **	This is the basic mail router.  All user mail programs should
15 **	call this routine to actually deliver mail.  Delivermail in
16 **	turn calls a bunch of mail servers that do the real work of
17 **	delivering the mail.
18 **
19 **	Delivermail is driven by tables defined in config.c.  This
20 **	file will be different from system to system, but the rest
21 **	of the code will be the same.  This table could be read in,
22 **	but it seemed nicer to have it compiled in, since deliver-
23 **	mail will potentially be exercised a lot.
24 **
25 **	Usage:
26 **		/etc/delivermail [-f name] [-a] [-q] [-v] [-n] [-m] addr ...
27 **
28 **	Positional Parameters:
29 **		addr -- the address to deliver the mail to.  There
30 **			can be several.
31 **
32 **	Flags:
33 **		-f name		The mail is from "name" -- used for
34 **				the header in local mail, and to
35 **				deliver reports of failures to.
36 **		-r name		Same as -f; however, this flag is
37 **				reserved to indicate special processing
38 **				for remote mail delivery as needed
39 **				in the future.  So, network servers
40 **				should use -r.
41 **		-a		This mail should be in ARPANET std
42 **				format (not used).
43 **		-n		Don't do aliasing.  This might be used
44 **				when delivering responses, for
45 **				instance.
46 **		-d		Run in debug mode.
47 **		-em		Mail back a response if there was an
48 **				error in processing.  This should be
49 **				used when the origin of this message
50 **				is another machine.
51 **		-ew		Write back a response if the user is
52 **				still logged in, otherwise, act like
53 **				-em.
54 **		-eq		Don't print any error message (just
55 **				return exit status).
56 **		-ep		(default)  Print error messages
57 **				normally.
58 **		-ee		Send BerkNet style errors.  This
59 **				is equivalent to MailBack except
60 **				that it has gives zero return code
61 **				(unless there were errors during
62 **				returning).  This used to be
63 **				"EchoBack", but you know how the old
64 **				software bounces.
65 **		-m		In group expansion, send to the
66 **				sender also (stands for the Mail metoo
67 **				option.
68 **		-i		Do not terminate mail on a line
69 **				containing just dot.
70 **		-s		Save UNIX-like "From" lines on the
71 **				front of messages.
72 **
73 **	Return Codes:
74 **		As defined in <sysexits.h>.
75 **
76 **		These codes are actually returned from the auxiliary
77 **		mailers; it is their responsibility to make them
78 **		correct.
79 **
80 **	Compilation Flags:
81 **		BADMAIL -- the mailer used for local mail doesn't
82 **			return the standard set of exit codes.  This
83 **			causes the name to be looked up before mail
84 **			is ever sent.
85 **		LOG -- if set, everything is logged.
86 **		MESSAGEID -- if set, the Message-Id field is added
87 **			to the message header if one does not already
88 **			exist.  This can be used to delete duplicate
89 **			messages.
90 **
91 **	Compilation Instructions:
92 **		cc -c -O main.c config.c deliver.c parse.c
93 **		cc -n -s *.o -lS
94 **		chown root a.out
95 **		chmod 755 a.out
96 **		mv a.out delivermail
97 **
98 **	Deficiencies:
99 **		It ought to collect together messages that are
100 **			destined for a single host and send these
101 **			to the auxiliary mail server together.
102 **		It should take "user at host" as three separate
103 **			parameters and combine them into one address.
104 **
105 **	Author:
106 **		Eric Allman, UCB/INGRES
107 */
108 
109 
110 
111 
112 
113 char	ArpaFmt;	/* mail is expected to be in ARPANET format */
114 char	FromFlag;	/* from person is explicitly specified */
115 char	Debug;		/* run in debug mode */
116 char	MailBack;	/* mail back response on error */
117 char	BerkNet;	/* called from BerkNet */
118 char	WriteBack;	/* write back response on error */
119 char	HasXscrpt;	/* if set, the transcript file exists */
120 char	NoAlias;	/* don't do aliasing */
121 char	ForceMail;	/* mail even if already sent a copy */
122 char	MeToo;		/* send to the sender also if in a group expansion */
123 char	SaveFrom;	/* save From lines on the front of messages */
124 char	IgnrDot;	/* if set, ignore dot when collecting mail */
125 char	Error;		/* set if errors */
126 char	SuprErrs;	/* supress errors if set */
127 char	InFileName[] = "/tmp/mailtXXXXXX";
128 char	Transcript[] = "/tmp/mailxXXXXXX";
129 addrq	From;		/* the from person */
130 char	*To;		/* the target person */
131 char	MsgId[MAXNAME];	/* the message-id for this letter */
132 int	HopCount;	/* hop count */
133 int	ExitStat;	/* the exit status byte */
134 addrq	SendQ;		/* queue of people to send to */
135 addrq	AliasQ;		/* queue of people who turned out to be aliases */
136 
137 
138 
139 
140 
141 
142 main(argc, argv)
143 	int argc;
144 	char **argv;
145 {
146 	register char *p;
147 	extern char *maketemp();
148 	extern char *getname();
149 	extern int finis();
150 	extern addrq *parse();
151 	register addrq *q;
152 	extern char Version[];
153 	extern int errno;
154 	char *from;
155 	register int i;
156 	typedef int (*fnptr)();
157 
158 	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
159 		signal(SIGINT, finis);
160 	signal(SIGTERM, finis);
161 	setbuf(stdout, (char *) NULL);
162 # ifdef LOG
163 	initlog("delivermail", 0, LOG_INDEP);
164 # endif LOG
165 # ifdef DEBUG
166 # ifdef DEBUGFILE
167 	if ((i = open(DEBUGFILE, 1)) > 0)
168 	{
169 		lseek(i, 0L, 2);
170 		close(1);
171 		dup(i);
172 		close(i);
173 		Debug++;
174 	}
175 # endif DEBUGFILE
176 	if (Debug)
177 		printf("%s\n", Version);
178 # endif
179 	errno = 0;
180 	from = NULL;
181 
182 	/*
183 	** Crack argv.
184 	*/
185 
186 	while (--argc > 0 && (p = *++argv)[0] == '-')
187 	{
188 		switch (p[1])
189 		{
190 		  case 'r':	/* obsolete -f flag */
191 		  case 'f':	/* from address */
192 			p += 2;
193 			if (*p == '\0')
194 			{
195 				p = *++argv;
196 				if (--argc <= 0 || *p == '-')
197 				{
198 					syserr("No \"from\" person");
199 					argc++;
200 					argv--;
201 					break;
202 				}
203 			}
204 			if (from != NULL)
205 			{
206 				syserr("More than one \"from\" person");
207 				break;
208 			}
209 			from = p;
210 			break;
211 
212 		  case 'h':	/* hop count */
213 			p += 2;
214 			if (*p == '\0')
215 			{
216 				p = *++argv;
217 				if (--argc <= 0 || *p < '0' || *p > '9')
218 				{
219 					syserr("Bad hop count (%s)", p);
220 					argc++;
221 					argv--;
222 					break;
223 				}
224 			}
225 			HopCount = atoi(p);
226 			break;
227 
228 		  case 'e':	/* error message disposition */
229 			switch (p[2])
230 			{
231 			  case 'p':	/* print errors normally */
232 				break;	/* (default) */
233 
234 			  case 'q':	/* be silent about it */
235 				freopen("/dev/null", "w", stdout);
236 				break;
237 
238 			  case 'm':	/* mail back */
239 				MailBack++;
240 				openxscrpt();
241 				break;
242 
243 			  case 'e':	/* do berknet error processing */
244 				BerkNet++;
245 				openxscrpt();
246 				break;
247 
248 			  case 'w':	/* write back (or mail) */
249 				WriteBack++;
250 				openxscrpt();
251 				break;
252 			}
253 			break;
254 
255 # ifdef DEBUG
256 		  case 'd':	/* debug */
257 			Debug++;
258 			break;
259 # endif DEBUG
260 
261 		  case 'n':	/* don't alias */
262 			NoAlias++;
263 			break;
264 
265 		  case 'm':	/* send to me too */
266 			MeToo++;
267 			break;
268 
269 		  case 'i':	/* don't let dot stop me */
270 			IgnrDot++;
271 			break;
272 
273 		  case 'a':	/* arpanet format */
274 			ArpaFmt++;
275 			break;
276 
277 		  case 's':	/* save From lines in headers */
278 			SaveFrom++;
279 			break;
280 
281 		  default:
282 			/* at Eric Schmidt's suggestion, this will not be an error....
283 			syserr("Unknown flag %s", p);
284 			... seems that upward compatibility will be easier. */
285 			break;
286 		}
287 	}
288 
289 	if (from != NULL && ArpaFmt)
290 		syserr("-f and -a are mutually exclusive");
291 
292 	/*
293 	** Get a temp file.
294 	*/
295 
296 	p = maketemp();
297 	if (from == NULL)
298 		from = p;
299 # ifdef DEBUG
300 	if (Debug)
301 		printf("Message-Id: <%s>\n", MsgId);
302 # endif DEBUG
303 
304 	/*
305 	**  Figure out who it's coming from.
306 	**	If we are root or "network", then allow -f.  Otherwise,
307 	**	insist that we figure it out ourselves.
308 	*/
309 
310 	errno = 0;
311 	p = getname();
312 	if (p == NULL || p[0] == '\0')
313 	{
314 		syserr("Who are you? (uid=%d)", getuid());
315 		finis();
316 	}
317 	errno = 0;
318 	if (from != NULL)
319 	{
320 		if (strcmp(p, "network") != 0 && getuid() != 0 /* && strcmp(p, From) != 0 */ )
321 		{
322 			/* network sends -r regardless (why why why?) */
323 			/* syserr("%s, you cannot use the -f flag", p); */
324 			from = NULL;
325 		}
326 	}
327 	if (from == NULL || from[0] == '\0')
328 		from = p;
329 	else
330 		FromFlag++;
331 	SuprErrs = TRUE;
332 	if (parse(from, &From, 0) == NULL)
333 	{
334 		/* too many arpanet hosts generate garbage From addresses ....
335 		syserr("Bad from address `%s'", from);
336 		.... so we will just ignore this address */
337 		from = p;
338 		FromFlag = FALSE;
339 	}
340 	SuprErrs = FALSE;
341 
342 # ifdef DEBUG
343 	if (Debug)
344 		printf("From person = \"%s\"\n", From.q_paddr);
345 # endif DEBUG
346 
347 	if (argc <= 0)
348 		usrerr("Usage: /etc/delivermail [flags] addr...");
349 
350 	/*
351 	**  Process Hop count.
352 	**	The Hop count tells us how many times this message has
353 	**	been processed by delivermail.  If it exceeds some
354 	**	fairly large threshold, then we assume that we have
355 	**	an infinite forwarding loop and die.
356 	*/
357 
358 	if (++HopCount > MAXHOP)
359 		syserr("Infinite forwarding loop (%s->%s)", From.q_paddr, *argv);
360 
361 	/*
362 	** Scan argv and deliver the message to everyone.
363 	*/
364 
365 	for (; argc-- > 0; argv++)
366 	{
367 		sendto(*argv, 0);
368 	}
369 
370 	/* if we have had errors sofar, drop out now */
371 	if (Error && ExitStat == EX_OK)
372 		ExitStat = EX_USAGE;
373 	if (ExitStat != EX_OK)
374 		finis();
375 
376 	/*
377 	**  See if we have anyone to send to at all.
378 	*/
379 
380 	if (nxtinq(&SendQ) == NULL && ExitStat == EX_OK)
381 	{
382 		syserr("Noone to send to!");
383 		ExitStat = EX_USAGE;
384 		finis();
385 	}
386 
387 	/*
388 	**  Do aliasing.
389 	**	First arrange that the person who is sending the mail
390 	**	will not be expanded (unless explicitly requested).
391 	*/
392 
393 	if (!MeToo)
394 		recipient(&From, &AliasQ);
395 	To = NULL;
396 	alias();
397 	if (nxtinq(&SendQ) == NULL && ExitStat == EX_OK)
398 	{
399 /*
400 		syserr("Vacant send queue; probably aliasing loop");
401 		ExitStat = EX_SOFTWARE;
402 		finis();
403 */
404 		recipient(&From, &SendQ);
405 	}
406 
407 	/*
408 	**  Actually send everything.
409 	*/
410 
411 	for (q = &SendQ; (q = nxtinq(q)) != NULL; )
412 		deliver(q, (fnptr) NULL);
413 
414 	/*
415 	** All done.
416 	*/
417 
418 	finis();
419 }
420 /*
421 **  FINIS -- Clean up and exit.
422 **
423 **	Parameters:
424 **		none
425 **
426 **	Returns:
427 **		never
428 **
429 **	Side Effects:
430 **		exits delivermail
431 **
432 **	Called By:
433 **		main
434 **		via signal on interrupt.
435 **
436 **	Deficiencies:
437 **		It may be that it should only remove the input
438 **			file if there have been no errors.
439 */
440 
441 finis()
442 {
443 	/* mail back the transcript on errors */
444 	if (ExitStat != EX_OK)
445 		savemail();
446 
447 	if (HasXscrpt)
448 		unlink(Transcript);
449 	unlink(InFileName);
450 	exit(ExitStat);
451 }
452 /*
453 **  MAKETEMP -- Make temporary file
454 **
455 **	Creates a temporary file name and copies the standard
456 **	input to that file.  While it is doing it, it looks for
457 **	"From:" and "Sender:" fields to use as the from-person
458 **	(but only if the -a flag is specified).  It prefers to
459 **	to use the "Sender:" field -- the protocol says that
460 **	"Sender:" must come after "From:", so this works easily.
461 **	MIT seems to like to produce "Sent-By:" fields instead
462 **	of "Sender:" fields.  We used to catch this, but it turns
463 **	out that the "Sent-By:" field doesn't always correspond
464 **	to someone real, as required by the protocol.  So we limp
465 **	by.....
466 **
467 **	Parameters:
468 **		none
469 **
470 **	Returns:
471 **		Name of temp file.
472 **
473 **	Side Effects:
474 **		Temp file is created and filled.
475 **
476 **	Called By:
477 **		main
478 **
479 **	Notes:
480 **		This is broken off from main largely so that the
481 **		temp buffer can be deallocated.
482 **
483 **	Deficiencies:
484 **		It assumes that the From: field will preceed the
485 **		Sender: field.  This violates the Arpanet NIC 733
486 **		protocol, but seems reasonable in practice.  In
487 **		any case, the only problem is that error responses
488 **		may be sent to the wrong person.
489 */
490 
491 char *
492 maketemp()
493 {
494 	register FILE *tf;
495 	char buf[MAXLINE+1];
496 	static char fbuf[sizeof buf];
497 	extern char *prescan();
498 	extern char *matchhdr();
499 	register char *p;
500 	bool inheader;
501 	bool firstline;
502 
503 	/*
504 	**  Create the temp file name and create the file.
505 	*/
506 
507 	mktemp(InFileName);
508 	close(creat(InFileName, 0600));
509 	if ((tf = fopen(InFileName, "w")) == NULL)
510 	{
511 		syserr("Cannot create %s", InFileName);
512 		return (NULL);
513 	}
514 
515 	/*
516 	**  Copy stdin to temp file & do message editting.
517 	**	From person gets copied into fbuf.  At the end of
518 	**	this loop, if fbuf[0] == '\0' then there was no
519 	**	recognized from person in the message.  We also
520 	**	save the message id in MsgId.  The
521 	**	flag 'inheader' keeps track of whether we are
522 	**	in the header or in the body of the message.
523 	**	The flag 'firstline' is only true on the first
524 	**	line of a message.
525 	**	To keep certain mailers from getting confused,
526 	**	and to keep the output clean, lines that look
527 	**	like UNIX "From" lines are deleted in the header,
528 	**	and prepended with ">" in the body.
529 	*/
530 
531 	inheader = TRUE;
532 	firstline = TRUE;
533 	fbuf[0] = '\0';
534 	while (fgets(buf, sizeof buf, stdin) != NULL)
535 	{
536 		if (!IgnrDot && buf[0] == '.' && (buf[1] == '\n' || buf[1] == '\0'))
537 			break;
538 
539 		/* are we still in the header? */
540 		if ((buf[0] == '\n' || buf[0] == '\0') && inheader)
541 		{
542 			inheader = FALSE;
543 			if (MsgId[0] == '\0')
544 			{
545 				makemsgid();
546 # ifdef MESSAGEID
547 				fprintf(tf, "Message-Id: <%s>\n", MsgId);
548 # endif MESSAGEID
549 			}
550 		}
551 
552 		/* Hide UNIX-like From lines */
553 		if (buf[0] == 'F' && buf[1] == 'r' && buf[2] == 'o' &&
554 		    buf[3] == 'm' && buf[4] == ' ')
555 		{
556 			if (firstline && !SaveFrom)
557 				continue;
558 			fputs(">", tf);
559 		}
560 
561 		if (inheader && !isspace(buf[0]))
562 		{
563 			/* find out if this is really a header */
564 			for (p = buf; *p != ':' && *p != '\0' && !isspace(*p); p++)
565 				continue;
566 			while (*p != ':' && isspace(*p))
567 				p++;
568 			if (*p != ':')
569 				inheader = FALSE;
570 		}
571 
572 		if (inheader)
573 		{
574 			/* find the sender */
575 			p = matchhdr(buf, "from");
576 			if (p == NULL)
577 				p = matchhdr(buf, "sender");
578 			if (p != NULL)
579 				prescan(p, fbuf, &fbuf[sizeof fbuf - 1], '\0');
580 
581 			/* find the message id */
582 			p = matchhdr(buf, "message-id");
583 			if (p != NULL && MsgId[0] == '\0')
584 				prescan(p, MsgId, &MsgId[sizeof MsgId - 1], '\0');
585 		}
586 		fputs(buf, tf);
587 		firstline = FALSE;
588 		if (ferror(tf))
589 		{
590 			syserr("Cannot write %s", InFileName);
591 			clearerr(tf);
592 			break;
593 		}
594 	}
595 	fclose(tf);
596 	if (MsgId[0] == '\0')
597 		makemsgid();
598 	if (freopen(InFileName, "r", stdin) == NULL)
599 		syserr("Cannot reopen %s", InFileName);
600 	return (ArpaFmt && fbuf[0] != '\0' ? fbuf : NULL);
601 }
602 /*
603 **  MAKEMSGID -- Compute a message id for this process.
604 **
605 **	This routine creates a message id for a message if
606 **	it did not have one already.  If the MESSAGEID compile
607 **	flag is set, the messageid will be added to any message
608 **	that does not already have one.  Currently it is more
609 **	of an artifact, but I suggest that if you are hacking,
610 **	you leave it in -- I may want to use it someday if
611 **	duplicate messages turn out to be a problem.
612 **
613 **	Parameters:
614 **		none.
615 **
616 **	Returns:
617 **		none.
618 **
619 **	Side Effects:
620 **		Stores a message-id into MsgId.
621 **
622 **	Called By:
623 **		maketemp
624 */
625 
626 makemsgid()
627 {
628 	auto long t;
629 	extern char *MyLocName;
630 
631 	time(&t);
632 	sprintf(MsgId, "%ld.%d.%s@Berkeley", t, getpid(), MyLocName);
633 }
634 /*
635 **  OPENXSCRPT -- Open transcript file
636 **
637 **	Creates a transcript file for possible eventual mailing or
638 **	sending back.
639 **
640 **	Parameters:
641 **		none
642 **
643 **	Returns:
644 **		none
645 **
646 **	Side Effects:
647 **		Turns the standard output into a special file
648 **			somewhere.
649 **
650 **	Called By:
651 **		main
652 */
653 
654 openxscrpt()
655 {
656 	mktemp(Transcript);
657 	HasXscrpt++;
658 	if (freopen(Transcript, "w", stdout) == NULL)
659 		syserr("Can't create %s", Transcript);
660 	chmod(Transcript, 0600);
661 	setbuf(stdout, (char *) NULL);
662 }
663