add ID keywords
[unix-history] / usr / src / usr.sbin / sendmail / src / main.c
CommitLineData
b3cbe40f
EA
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
916b3375
EA
9static char SccsId[] = "@(#)main.c 1.4 %G%";
10
b3cbe40f
EA
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.
3b661a43
EA
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.
b3cbe40f
EA
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
145char ArpaFmt; /* mail is expected to be in ARPANET format */
146char FromFlag; /* from person is explicitly specified */
147char Debug; /* run in debug mode */
148char MailBack; /* mail back response on error */
3b661a43 149char BerkNet; /* called from BerkNet */
b3cbe40f
EA
150char WriteBack; /* write back response on error */
151char HasXscrpt; /* if set, the transcript file exists */
152char NoAlias; /* don't do aliasing */
153char ForceMail; /* mail even if already sent a copy */
154char MeToo; /* send to the sender also if in a group expansion */
155char SaveFrom; /* save From lines on the front of messages */
156char IgnrDot; /* if set, ignore dot when collecting mail */
157char Error; /* set if errors */
158char SuprErrs; /* supress errors if set */
159char InFileName[] = "/tmp/mailtXXXXXX";
160char Transcript[] = "/tmp/mailxXXXXXX";
161addrq From; /* the from person */
162char *To; /* the target person */
163char MsgId[MAXNAME]; /* the message-id for this letter */
164int HopCount; /* hop count */
165int ExitStat; /* the exit status byte */
166addrq SendQ; /* queue of people to send to */
167addrq AliasQ; /* queue of people who turned out to be aliases */
168
169
170
171
172
173
174main(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
3b661a43
EA
275 case 'e': /* do berknet error processing */
276 BerkNet++;
b3cbe40f
EA
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
b3cbe40f
EA
393 /*
394 ** Scan argv and deliver the message to everyone.
395 */
396
397 for (; argc-- > 0; argv++)
398 {
399 sendto(*argv, 0);
400 }
401
a4b004a6
EA
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
b3cbe40f
EA
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\f/*
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
488finis()
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\f/*
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
558char *
559maketemp()
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\f/*
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
701makemsgid()
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\f/*
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
739openxscrpt()
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}