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