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