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