1 /* mail - send/receive mail Author: Peter S. Housel */ 2 /* Version 0.2 of September 1990: added -e, -t, * options - cwr */ 3 4 /* 2003-07-18: added -s option - ASW */ 5 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <errno.h> 9 #undef EOF /* temporary hack */ 10 #include <signal.h> 11 #include <pwd.h> 12 #include <time.h> 13 #include <setjmp.h> 14 #include <string.h> 15 #include <stdlib.h> 16 #include <fcntl.h> 17 #include <unistd.h> 18 #include <sys/wait.h> 19 #include <stdio.h> 20 21 #ifdef DEBUG 22 #define D(Q) (Q) 23 #else 24 #define D(Q) 25 #endif 26 27 #define SHELL "/bin/sh" 28 29 #define DROPNAME "/var/mail/%s" 30 #define LOCKNAME "/var/mail/%s.lock" 31 #define LOCKWAIT 5 /* seconds to wait after collision */ 32 #define LOCKTRIES 4 /* maximum number of collisions */ 33 34 #define MBOX "mbox" 35 36 #define HELPFILE "/usr/lib/mail.help" 37 #define PROMPT "? " 38 #define PATHLEN 80 39 #define MAXRCPT 100 /* maximum number of recipients */ 40 #define LINELEN 512 41 42 /* #define MAILER "/usr/bin/smail" */ /* smart mailer */ 43 #define MAILERARGS /* (unused) */ 44 45 #define UNREAD 1 /* 'not read yet' status */ 46 #define DELETED 2 /* 'deleted' status */ 47 #define READ 3 /* 'has been read' status */ 48 49 struct letter { 50 struct letter *prev, *next; /* linked letter list */ 51 int status; /* letter status */ 52 off_t location; /* location within mailbox file */ 53 }; 54 55 struct letter *firstlet, *lastlet; 56 57 int usemailer = 1; /* use MAILER to deliver (if any) */ 58 int printmode = 0; /* print-and-exit mode */ 59 int quitmode = 0; /* take interrupts */ 60 int reversemode = 0; /* print mailbox in reverse order */ 61 int usedrop = 1; /* read the maildrop (no -f given) */ 62 int verbose = 0; /* pass "-v" flag on to mailer */ 63 int needupdate = 0; /* need to update mailbox */ 64 int msgstatus = 0; /* return the mail status */ 65 int distlist = 0; /* include distribution list */ 66 char mailbox[PATHLEN]; /* user's mailbox/maildrop */ 67 char tempname[PATHLEN] = "/tmp/mailXXXXXX"; /* temporary file */ 68 char *subject = NULL; 69 FILE *boxfp = NULL; /* mailbox file */ 70 jmp_buf printjump; /* for quitting out of letters */ 71 unsigned oldmask; /* saved umask() */ 72 73 extern int optind; 74 extern char *optarg; 75 76 int main(int argc, char **argv); 77 int deliver(int count, char *vec []); 78 FILE *makerewindable(void); 79 int copy(FILE *fromfp, FILE *tofp); 80 void readbox(void); 81 void printall(void); 82 void interact(void); 83 void onint(int dummy); 84 void savelet(struct letter *let, char *savefile); 85 void updatebox(void); 86 void printlet(struct letter *let, FILE *tofp); 87 void doshell(char *command); 88 void usage(void); 89 char *basename(char *name); 90 char *whoami(void); 91 void dohelp(void); 92 int filesize(char *name); 93 94 int main(argc, argv) 95 int argc; 96 char *argv[]; 97 { 98 int c; 99 100 if ('l' == (basename(argv[0]))[0]) /* 'lmail' link? */ 101 usemailer = 0; /* yes, let's deliver it */ 102 103 (void) mktemp(tempname); /* name the temp file */ 104 105 oldmask = umask(022); /* change umask for security */ 106 107 while (EOF != (c = getopt(argc, argv, "epqrf:tdvs:"))) switch (c) { 108 case 'e': ++msgstatus; break; 109 110 case 't': ++distlist; break; 111 112 case 'p': ++printmode; break; 113 114 case 'q': ++quitmode; break; 115 116 case 'r': ++reversemode; break; 117 118 case 'f': 119 setuid(getuid()); /* won't need to lock */ 120 usedrop = 0; 121 strncpy(mailbox, optarg, (size_t)(PATHLEN - 1)); 122 break; 123 124 case 'd': usemailer = 0; break; 125 126 case 'v': ++verbose; break; 127 128 case 's': subject = optarg; break; 129 130 default: 131 usage(); 132 exit(1); 133 } 134 135 if (optind < argc) { 136 if (deliver(argc - optind, argv + optind) < 0) 137 exit(1); 138 else 139 exit(0); 140 } 141 if (usedrop) sprintf(mailbox, DROPNAME, whoami()); 142 143 D(printf("mailbox=%s\n", mailbox)); 144 145 if (msgstatus) { 146 if (filesize(mailbox)) 147 exit(0); 148 else 149 exit(1); 150 } 151 152 readbox(); 153 154 if (printmode) 155 printall(); 156 else 157 interact(); 158 159 if (needupdate) updatebox(); 160 161 return(0); 162 } 163 164 int deliver(count, vec) 165 int count; 166 char *vec[]; 167 { 168 int i, j; 169 int errs = 0; /* count of errors */ 170 int dropfd; /* file descriptor for user's drop */ 171 int created = 0; /* true if we created the maildrop */ 172 FILE *mailfp; /* fp for mail */ 173 struct stat stb; /* for checking drop modes, owners */ 174 #ifdef __STDC__ 175 void (*sigint)(int), (*sighup)(int), (*sigquit)(int);/* saving signal state */ 176 #else 177 void (*sigint) (), (*sighup) (), (*sigquit) (); /* saving signal state */ 178 #endif 179 time_t now; /* for datestamping the postmark */ 180 char sender[32]; /* sender's login name */ 181 char lockname[PATHLEN]; /* maildrop lock */ 182 int locktries; /* tries when box is locked */ 183 struct passwd *pw; /* sender and recipent */ 184 int to_console; /* deliver to console if everything fails */ 185 186 if (count > MAXRCPT) { 187 fprintf(stderr, "mail: too many recipients\n"); 188 return -1; 189 } 190 #ifdef MAILER 191 if (usemailer) { 192 char *argvec[MAXRCPT + 3]; 193 char **argp; 194 195 setuid(getuid()); 196 197 argp = argvec; 198 *argp++ = "send-mail"; 199 if (verbose) *argp++ = "-v"; 200 201 for (i = 0; i < count; ++i) *argp++ = vec[i]; 202 203 *argp = NULL; 204 execv(MAILER, argvec); 205 fprintf(stderr, "mail: couldn't exec %s\n", MAILER); 206 return -1; 207 } 208 #endif /* MAILER */ 209 210 if (NULL == (pw = getpwuid(getuid()))) { 211 fprintf(stderr, "mail: unknown sender\n"); 212 return -1; 213 } 214 strcpy(sender, pw->pw_name); 215 216 /* If we need to rewind stdin and it isn't rewindable, make a copy */ 217 if (isatty(0) || (count > 1 && lseek(0, 0L, 0) == (off_t) -1)) { 218 mailfp = makerewindable(); 219 } else 220 mailfp = stdin; 221 222 /* Shut off signals during the delivery */ 223 sigint = signal(SIGINT, SIG_IGN); 224 sighup = signal(SIGHUP, SIG_IGN); 225 sigquit = signal(SIGQUIT, SIG_IGN); 226 227 for (i = 0; i < count; ++i) { 228 if (count > 1) rewind(mailfp); 229 230 D(printf("deliver to %s\n", vec[i])); 231 232 if (NULL == (pw = getpwnam(vec[i]))) { 233 fprintf(stderr, "mail: user %s not known\n", vec[i]); 234 ++errs; 235 continue; 236 } 237 sprintf(mailbox, DROPNAME, pw->pw_name); 238 sprintf(lockname, LOCKNAME, pw->pw_name); 239 240 D(printf("maildrop='%s', lock='%s'\n", mailbox, lockname)); 241 242 /* Lock the maildrop while we're messing with it. Races are 243 * possible (though not very likely) when we have to create 244 * the maildrop, but not otherwise. If the box is already 245 * locked, wait awhile and try again. */ 246 locktries = created = to_console = 0; 247 trylock: 248 if (link(mailbox, lockname) != 0) { 249 if (ENOENT == errno) { /* user doesn't have a drop yet */ 250 dropfd = creat(mailbox, 0600); 251 if (dropfd < 0 && errno == ENOENT) { 252 /* Probably missing spool dir; to console. */ 253 boxfp = fopen("/dev/console", "w"); 254 if (boxfp != NULL) { 255 to_console = 1; 256 goto nobox; 257 } 258 } 259 if (dropfd < 0) { 260 fprintf(stderr, "mail: couln't create a maildrop for user %s\n", 261 vec[i]); 262 ++errs; 263 continue; 264 } 265 ++created; 266 goto trylock; 267 } else { /* somebody else has it locked, it seems - 268 * wait */ 269 if (++locktries >= LOCKTRIES) { 270 fprintf(stderr, "mail: couldn't lock maildrop for user %s\n", 271 vec[i]); 272 ++errs; 273 continue; 274 } 275 sleep(LOCKWAIT); 276 goto trylock; 277 } 278 } 279 if (created) { 280 (void) chown(mailbox, pw->pw_uid, pw->pw_gid); 281 boxfp = fdopen(dropfd, "a"); 282 } else 283 boxfp = fopen(mailbox, "a"); 284 285 if (NULL == boxfp || stat(mailbox, &stb) < 0) { 286 fprintf(stderr, "mail: serious maildrop problems for %s\n", vec[i]); 287 unlink(lockname); 288 ++errs; 289 continue; 290 } 291 if (stb.st_uid != pw->pw_uid || (stb.st_mode & S_IFMT) != S_IFREG) { 292 fprintf(stderr, "mail: mailbox for user %s is illegal\n", vec[i]); 293 unlink(lockname); 294 ++errs; 295 continue; 296 } 297 nobox: 298 if (to_console) { 299 fprintf(boxfp, 300 "-------------\n| Mail from %s to %s\n-------------\n", 301 sender, vec[i]); 302 } else { 303 (void) time(&now); 304 fprintf(boxfp, "From %s %24.24s\n", sender, ctime(&now)); 305 } 306 307 /* Add the To: header line */ 308 fprintf(boxfp, "To: %s\n", vec[i]); 309 310 if (distlist) { 311 fprintf(boxfp, "Dist: "); 312 for (j = 0; j < count; ++j) 313 if (getpwnam(vec[j]) != NULL && j != i) 314 fprintf(boxfp, "%s ", vec[j]) ; 315 fprintf(boxfp, "\n"); 316 } 317 318 /* Add the Subject: header line */ 319 if (subject != NULL) fprintf(boxfp, "Subject: %s\n", subject); 320 321 fprintf(boxfp, "\n"); 322 323 if ((copy(mailfp, boxfp) < 0) || (fclose(boxfp) != 0)) { 324 fprintf(stderr, "mail: error delivering to user %s", vec[i]); 325 perror(" "); 326 ++errs; 327 } 328 unlink(lockname); 329 } 330 331 fclose(mailfp); 332 333 /* Put signals back the way they were */ 334 signal(SIGINT, sigint); 335 signal(SIGHUP, sighup); 336 signal(SIGQUIT, sigquit); 337 338 return(0 == errs) ? 0 : -1; 339 } 340 341 /* 'stdin' isn't rewindable. Make a temp file that is. 342 * Note that if one wanted to catch SIGINT and write a '~/dead.letter' 343 * for interactive mails, this might be the place to do it (though the 344 * case where a MAILER is being used would also need to be handled). 345 */ 346 FILE *makerewindable() 347 { 348 FILE *tempfp; /* temp file used for copy */ 349 int c; /* character being copied */ 350 int state; /* ".\n" detection state */ 351 352 if (NULL == (tempfp = fopen(tempname, "w"))) { 353 fprintf(stderr, "mail: can't create temporary file\n"); 354 return NULL; 355 } 356 357 /* Here we copy until we reach the end of the letter (end of file or 358 * a line containing only a '.'), painstakingly avoiding setting a 359 * line length limit. */ 360 state = '\n'; 361 while (EOF != (c = getc(stdin))) switch (state) { 362 case '\n': 363 if ('.' == c) 364 state = '.'; 365 else { 366 if ('\n' != c) state = '\0'; 367 putc(c, tempfp); 368 } 369 break; 370 case '.': 371 if ('\n' == c) goto done; 372 state = '\0'; 373 putc('.', tempfp); 374 putc(c, tempfp); 375 break; 376 default: 377 state = ('\n' == c) ? '\n' : '\0'; 378 putc(c, tempfp); 379 } 380 done: 381 if (ferror(tempfp) || fclose(tempfp)) { 382 fprintf(stderr, "mail: couldn't copy letter to temporary file\n"); 383 return NULL; 384 } 385 tempfp = freopen(tempname, "r", stdin); 386 unlink(tempname); /* unlink name; file lingers on in limbo */ 387 return tempfp; 388 } 389 390 int copy(fromfp, tofp) 391 FILE *fromfp, *tofp; 392 { 393 int c; /* character being copied */ 394 int state; /* ".\n" and postmark detection state */ 395 int blankline = 0; /* was most recent line completely blank? */ 396 static char postmark[] = "From "; 397 char *p, *q; 398 399 /* Here we copy until we reach the end of the letter (end of file or 400 * a line containing only a '.'). Postmarks (lines beginning with 401 * "From ") are copied with a ">" prepended. Here we also complicate 402 * things by not setting a line limit. */ 403 state = '\n'; 404 p = postmark; 405 while (EOF != (c = getc(fromfp))) { 406 switch (state) { 407 case '\n': 408 if ('.' == c) /* '.' at BOL */ 409 state = '.'; 410 else if (*p == c) { /* start of postmark */ 411 ++p; 412 state = 'P'; 413 } else { /* anything else */ 414 if ('\n' == c) 415 blankline = 1; 416 else { 417 state = '\0'; 418 blankline = 0; 419 } 420 putc(c, tofp); 421 } 422 break; 423 case '.': 424 if ('\n' == c) goto done; 425 state = '\0'; 426 putc('.', tofp); 427 putc(c, tofp); 428 break; 429 case 'P': 430 if (*p == c) { 431 if (*++p == '\0') { /* successfully reached end */ 432 p = postmark; 433 putc('>', tofp); 434 fputs(postmark, tofp); 435 state = '\0'; 436 break; 437 } 438 break; /* not there yet */ 439 } 440 state = ('\n' == c) ? '\n' : '\0'; 441 for (q = postmark; q < p; ++q) putc(*q, tofp); 442 putc(c, tofp); 443 blankline = 0; 444 p = postmark; 445 break; 446 default: 447 state = ('\n' == c) ? '\n' : '\0'; 448 putc(c, tofp); 449 } 450 } 451 if ('\n' != state) putc('\n', tofp); 452 done: 453 if (!blankline) putc('\n', tofp); 454 if (ferror(tofp)) return -1; 455 return 0; 456 } 457 458 void readbox() 459 { 460 char linebuf[512]; 461 struct letter *let; 462 off_t current; 463 464 firstlet = lastlet = NULL; 465 466 if (access(mailbox, 4) < 0 || NULL == (boxfp = fopen(mailbox, "r"))) { 467 if (usedrop && ENOENT == errno) return; 468 fprintf(stderr, "can't access mailbox "); 469 perror(mailbox); 470 exit(1); 471 } 472 current = 0L; 473 while (1) { 474 if (NULL == fgets(linebuf, sizeof linebuf, boxfp)) break; 475 476 if (!strncmp(linebuf, "From ", (size_t)5)) { 477 if (NULL == (let = (struct letter *) malloc(sizeof(struct letter)))) { 478 fprintf(stderr, "Out of memory.\n"); 479 exit(1); 480 } 481 if (NULL == lastlet) { 482 firstlet = let; 483 let->prev = NULL; 484 } else { 485 let->prev = lastlet; 486 lastlet->next = let; 487 } 488 lastlet = let; 489 let->next = NULL; 490 491 let->status = UNREAD; 492 let->location = current; 493 D(printf("letter at %ld\n", current)); 494 } 495 current += strlen(linebuf); 496 } 497 } 498 499 void printall() 500 { 501 struct letter *let; 502 503 let = reversemode ? firstlet : lastlet; 504 505 if (NULL == let) { 506 printf("No mail.\n"); 507 return; 508 } 509 while (NULL != let) { 510 printlet(let, stdout); 511 let = reversemode ? let->next : let->prev; 512 } 513 } 514 515 void interact() 516 { 517 char linebuf[512]; /* user input line */ 518 struct letter *let, *next; /* current and next letter */ 519 int interrupted = 0; /* SIGINT hit during letter print */ 520 int needprint = 1; /* need to print this letter */ 521 char *savefile; /* filename to save into */ 522 523 if (NULL == firstlet) { 524 printf("No mail.\n"); 525 return; 526 } 527 let = reversemode ? firstlet : lastlet; 528 529 while (1) { 530 next = reversemode ? let->next : let->prev; 531 if (NULL == next) next = let; 532 533 if (!quitmode) { 534 interrupted = setjmp(printjump); 535 signal(SIGINT, onint); 536 } 537 if (!interrupted && needprint) { 538 if (DELETED != let->status) let->status = READ; 539 printlet(let, stdout); 540 } 541 if (interrupted) putchar('\n'); 542 needprint = 0; 543 fputs(PROMPT, stdout); 544 fflush(stdout); 545 546 if (fgets(linebuf, sizeof linebuf, stdin) == NULL) break; 547 548 if (!quitmode) signal(SIGINT, SIG_IGN); 549 550 switch (linebuf[0]) { 551 case '\n': 552 let = next; 553 needprint = 1; 554 continue; 555 case 'd': 556 let->status = DELETED; 557 if (next != let)/* look into this */ 558 needprint = 1; 559 needupdate = 1; 560 let = next; 561 continue; 562 case 'p': 563 needprint = 1; 564 continue; 565 case '-': 566 next = reversemode ? let->prev : let->next; 567 if (NULL == next) next = let; 568 let = next; 569 needprint = 1; 570 continue; 571 case 's': 572 for (savefile = strtok(linebuf + 1, " \t\n"); 573 savefile != NULL; 574 savefile = strtok((char *) NULL, " \t\n")) { 575 savelet(let, savefile); 576 } 577 continue; 578 case '!': 579 doshell(linebuf + 1); 580 continue; 581 case '*': 582 dohelp(); 583 continue; 584 case 'q': 585 return; 586 case 'x': 587 exit(0); 588 default: 589 fprintf(stderr, "Illegal command\n"); 590 continue; 591 } 592 } 593 } 594 595 void onint(dummy) 596 int dummy; /* to satisfy ANSI compilers */ 597 { 598 longjmp(printjump, 1); 599 } 600 601 void savelet(let, savefile) 602 struct letter *let; 603 char *savefile; 604 { 605 int waitstat, pid; 606 FILE *savefp; 607 608 if ((pid = fork()) < 0) { 609 perror("mail: couldn't fork"); 610 return; 611 } else if (pid != 0) { /* parent */ 612 wait(&waitstat); 613 return; 614 } 615 616 /* Child */ 617 setgid(getgid()); 618 setuid(getuid()); 619 if ((savefp = fopen(savefile, "a")) == NULL) { 620 perror(savefile); 621 exit(0); 622 } 623 printlet(let, savefp); 624 if ((ferror(savefp) != 0) | (fclose(savefp) != 0)) { 625 fprintf(stderr, "savefile write error:"); 626 perror(savefile); 627 } 628 exit(0); 629 } 630 631 void updatebox() 632 { 633 FILE *tempfp; /* fp for tempfile */ 634 char lockname[PATHLEN]; /* maildrop lock */ 635 int locktries = 0; /* tries when box is locked */ 636 struct letter *let; /* current letter */ 637 int c; 638 639 sprintf(lockname, LOCKNAME, whoami()); 640 641 if (NULL == (tempfp = fopen(tempname, "w"))) { 642 perror("mail: can't create temporary file"); 643 return; 644 } 645 for (let = firstlet; let != NULL; let = let->next) { 646 if (let->status != DELETED) { 647 printlet(let, tempfp); 648 D(printf("printed letter at %ld\n", let->location)); 649 } 650 } 651 652 if (ferror(tempfp) || NULL == (tempfp = freopen(tempname, "r", tempfp))) { 653 perror("mail: temporary file write error"); 654 unlink(tempname); 655 return; 656 } 657 658 /* Shut off signals during the update */ 659 signal(SIGINT, SIG_IGN); 660 signal(SIGHUP, SIG_IGN); 661 signal(SIGQUIT, SIG_IGN); 662 663 if (usedrop) while (link(mailbox, lockname) != 0) { 664 if (++locktries >= LOCKTRIES) { 665 fprintf(stderr, "mail: couldn't lock maildrop for update\n"); 666 return; 667 } 668 sleep(LOCKWAIT); 669 } 670 671 if (NULL == (boxfp = freopen(mailbox, "w", boxfp))) { 672 perror("mail: couldn't reopen maildrop"); 673 fprintf(stderr, "mail may have been lost; look in %s\n", tempname); 674 if (usedrop) unlink(lockname); 675 return; 676 } 677 unlink(tempname); 678 679 while ((c = getc(tempfp)) != EOF) putc(c, boxfp); 680 681 fclose(boxfp); 682 683 if (usedrop) unlink(lockname); 684 } 685 686 void printlet(let, tofp) 687 struct letter *let; 688 FILE *tofp; 689 { 690 off_t current, limit; 691 int c; 692 693 fseek(boxfp, (current = let->location), 0); 694 limit = (NULL != let->next) ? let->next->location : -1; 695 696 while (current != limit && (c = getc(boxfp)) != EOF) { 697 putc(c, tofp); 698 ++current; 699 } 700 } 701 702 void doshell(command) 703 char *command; 704 { 705 int waitstat, pid; 706 char *shell; 707 708 if (NULL == (shell = getenv("SHELL"))) shell = SHELL; 709 710 if ((pid = fork()) < 0) { 711 perror("mail: couldn't fork"); 712 return; 713 } else if (pid != 0) { /* parent */ 714 wait(&waitstat); 715 return; 716 } 717 718 /* Child */ 719 setgid(getgid()); 720 setuid(getuid()); 721 umask(oldmask); 722 723 execl(shell, shell, "-c", command, (char *) NULL); 724 fprintf(stderr, "can't exec shell\n"); 725 exit(127); 726 } 727 728 void usage() 729 { 730 fprintf(stderr, "usage: mail [-epqr] [-f file]\n"); 731 fprintf(stderr, " mail [-dtv] [-s subject] user [...]\n"); 732 } 733 734 char *basename(name) 735 char *name; 736 { 737 char *p; 738 739 if (NULL == (p = rindex(name, '/'))) 740 return name; 741 else 742 return p + 1; 743 } 744 745 char *whoami() 746 { 747 struct passwd *pw; 748 749 if (NULL != (pw = getpwuid(getuid()))) 750 return pw->pw_name; 751 else 752 return "nobody"; 753 } 754 755 void dohelp() 756 { 757 FILE *fp; 758 char buffer[80]; 759 760 if ( (fp = fopen(HELPFILE, "r")) == NULL) 761 fprintf(stdout, "can't open helpfile %s\n", HELPFILE); 762 else 763 while (fgets(buffer, 80, fp)) 764 fputs(buffer, stdout); 765 } 766 767 int filesize(name) 768 char *name ; 769 { 770 struct stat buf; 771 772 if (stat(name, &buf) == -1) 773 buf.st_size = 0L; 774 775 return (buf.st_size ? 1 : 0); 776 } 777