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