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