1 /*- 2 * Copyright (c) 1980, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 static char copyright[] = 10 "@(#) Copyright (c) 1980, 1993\n\ 11 The Regents of the University of California. All rights reserved.\n"; 12 #endif /* not lint */ 13 14 #ifndef lint 15 static char sccsid[] = "@(#)msgs.c 8.2 (Berkeley) 04/28/95"; 16 #endif /* not lint */ 17 18 /* 19 * msgs - a user bulletin board program 20 * 21 * usage: 22 * msgs [fhlopq] [[-]number] to read messages 23 * msgs -s to place messages 24 * msgs -c [-days] to clean up the bulletin board 25 * 26 * prompt commands are: 27 * y print message 28 * n flush message, go to next message 29 * q flush message, quit 30 * p print message, turn on 'pipe thru more' mode 31 * P print message, turn off 'pipe thru more' mode 32 * - reprint last message 33 * s[-][<num>] [<filename>] save message 34 * m[-][<num>] mail with message in temp mbox 35 * x exit without flushing this message 36 * <num> print message number <num> 37 */ 38 39 #define V7 /* will look for TERM in the environment */ 40 #define OBJECT /* will object to messages without Subjects */ 41 /* #define REJECT /* will reject messages without Subjects 42 (OBJECT must be defined also) */ 43 /* #define UNBUFFERED /* use unbuffered output */ 44 45 #include <sys/param.h> 46 #include <sys/dir.h> 47 #include <sys/ioctl.h> 48 #include <sys/stat.h> 49 #include <ctype.h> 50 #include <errno.h> 51 #include <pwd.h> 52 #include <setjmp.h> 53 #include <signal.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <termios.h> 58 #include <time.h> 59 #include <unistd.h> 60 #include "pathnames.h" 61 62 #define CMODE 0666 /* bounds file creation mode */ 63 #define NO 0 64 #define YES 1 65 #define SUPERUSER 0 /* superuser uid */ 66 #define DAEMON 1 /* daemon uid */ 67 #define NLINES 24 /* default number of lines/crt screen */ 68 #define NDAYS 21 /* default keep time for messages */ 69 #define DAYS *24*60*60 /* seconds/day */ 70 #define MSGSRC ".msgsrc" /* user's rc file */ 71 #define BOUNDS "bounds" /* message bounds file */ 72 #define NEXT "Next message? [yq]" 73 #define MORE "More? [ynq]" 74 #define NOMORE "(No more) [q] ?" 75 76 typedef char bool; 77 78 FILE *msgsrc; 79 FILE *newmsg; 80 char *sep = "-"; 81 char inbuf[BUFSIZ]; 82 char fname[128]; 83 char cmdbuf[128]; 84 char subj[128]; 85 char from[128]; 86 char date[128]; 87 char *ptr; 88 char *in; 89 bool local; 90 bool ruptible; 91 bool totty; 92 bool seenfrom; 93 bool seensubj; 94 bool blankline; 95 bool printing = NO; 96 bool mailing = NO; 97 bool quitit = NO; 98 bool sending = NO; 99 bool intrpflg = NO; 100 int uid; 101 int msg; 102 int prevmsg; 103 int lct; 104 int nlines; 105 int Lpp = 0; 106 time_t t; 107 time_t keep; 108 109 char *mktemp(); 110 char *nxtfld(); 111 void onintr(); 112 void onsusp(); 113 114 /* option initialization */ 115 bool hdrs = NO; 116 bool qopt = NO; 117 bool hush = NO; 118 bool send_msg = NO; 119 bool locomode = NO; 120 bool use_pager = NO; 121 bool clean = NO; 122 bool lastcmd = NO; 123 jmp_buf tstpbuf; 124 125 main(argc, argv) 126 int argc; char *argv[]; 127 { 128 bool newrc, already; 129 int rcfirst = 0; /* first message to print (from .rc) */ 130 int rcback = 0; /* amount to back off of rcfirst */ 131 int firstmsg, nextmsg, lastmsg = 0; 132 int blast = 0; 133 FILE *bounds; 134 135 #ifdef UNBUFFERED 136 setbuf(stdout, NULL); 137 #endif 138 139 time(&t); 140 setuid(uid = getuid()); 141 ruptible = (signal(SIGINT, SIG_IGN) == SIG_DFL); 142 if (ruptible) 143 signal(SIGINT, SIG_DFL); 144 145 argc--, argv++; 146 while (argc > 0) { 147 if (isdigit(argv[0][0])) { /* starting message # */ 148 rcfirst = atoi(argv[0]); 149 } 150 else if (isdigit(argv[0][1])) { /* backward offset */ 151 rcback = atoi( &( argv[0][1] ) ); 152 } 153 else { 154 ptr = *argv; 155 while (*ptr) switch (*ptr++) { 156 157 case '-': 158 break; 159 160 case 'c': 161 if (uid != SUPERUSER && uid != DAEMON) { 162 fprintf(stderr, "Sorry\n"); 163 exit(1); 164 } 165 clean = YES; 166 break; 167 168 case 'f': /* silently */ 169 hush = YES; 170 break; 171 172 case 'h': /* headers only */ 173 hdrs = YES; 174 break; 175 176 case 'l': /* local msgs only */ 177 locomode = YES; 178 break; 179 180 case 'o': /* option to save last message */ 181 lastcmd = YES; 182 break; 183 184 case 'p': /* pipe thru 'more' during long msgs */ 185 use_pager = YES; 186 break; 187 188 case 'q': /* query only */ 189 qopt = YES; 190 break; 191 192 case 's': /* sending TO msgs */ 193 send_msg = YES; 194 break; 195 196 default: 197 fprintf(stderr, 198 "usage: msgs [fhlopq] [[-]number]\n"); 199 exit(1); 200 } 201 } 202 argc--, argv++; 203 } 204 205 /* 206 * determine current message bounds 207 */ 208 sprintf(fname, "%s/%s", _PATH_MSGS, BOUNDS); 209 bounds = fopen(fname, "r"); 210 211 if (bounds != NULL) { 212 fscanf(bounds, "%d %d\n", &firstmsg, &lastmsg); 213 fclose(bounds); 214 blast = lastmsg; /* save upper bound */ 215 } 216 217 if (clean) 218 keep = t - (rcback? rcback : NDAYS) DAYS; 219 220 if (clean || bounds == NULL) { /* relocate message bounds */ 221 struct direct *dp; 222 struct stat stbuf; 223 bool seenany = NO; 224 DIR *dirp; 225 226 dirp = opendir(_PATH_MSGS); 227 if (dirp == NULL) { 228 perror(_PATH_MSGS); 229 exit(errno); 230 } 231 232 firstmsg = 32767; 233 lastmsg = 0; 234 235 for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)){ 236 register char *cp = dp->d_name; 237 register int i = 0; 238 239 if (dp->d_ino == 0) 240 continue; 241 if (dp->d_namlen == 0) 242 continue; 243 244 if (clean) 245 sprintf(inbuf, "%s/%s", _PATH_MSGS, cp); 246 247 while (isdigit(*cp)) 248 i = i * 10 + *cp++ - '0'; 249 if (*cp) 250 continue; /* not a message! */ 251 252 if (clean) { 253 if (stat(inbuf, &stbuf) != 0) 254 continue; 255 if (stbuf.st_mtime < keep 256 && stbuf.st_mode&S_IWRITE) { 257 unlink(inbuf); 258 continue; 259 } 260 } 261 262 if (i > lastmsg) 263 lastmsg = i; 264 if (i < firstmsg) 265 firstmsg = i; 266 seenany = YES; 267 } 268 closedir(dirp); 269 270 if (!seenany) { 271 if (blast != 0) /* never lower the upper bound! */ 272 lastmsg = blast; 273 firstmsg = lastmsg + 1; 274 } 275 else if (blast > lastmsg) 276 lastmsg = blast; 277 278 if (!send_msg) { 279 bounds = fopen(fname, "w"); 280 if (bounds == NULL) { 281 perror(fname); 282 exit(errno); 283 } 284 chmod(fname, CMODE); 285 fprintf(bounds, "%d %d\n", firstmsg, lastmsg); 286 fclose(bounds); 287 } 288 } 289 290 if (send_msg) { 291 /* 292 * Send mode - place msgs in _PATH_MSGS 293 */ 294 bounds = fopen(fname, "w"); 295 if (bounds == NULL) { 296 perror(fname); 297 exit(errno); 298 } 299 300 nextmsg = lastmsg + 1; 301 sprintf(fname, "%s/%d", _PATH_MSGS, nextmsg); 302 newmsg = fopen(fname, "w"); 303 if (newmsg == NULL) { 304 perror(fname); 305 exit(errno); 306 } 307 chmod(fname, 0644); 308 309 fprintf(bounds, "%d %d\n", firstmsg, nextmsg); 310 fclose(bounds); 311 312 sending = YES; 313 if (ruptible) 314 signal(SIGINT, onintr); 315 316 if (isatty(fileno(stdin))) { 317 ptr = getpwuid(uid)->pw_name; 318 printf("Message %d:\nFrom %s %sSubject: ", 319 nextmsg, ptr, ctime(&t)); 320 fflush(stdout); 321 fgets(inbuf, sizeof inbuf, stdin); 322 putchar('\n'); 323 fflush(stdout); 324 fprintf(newmsg, "From %s %sSubject: %s\n", 325 ptr, ctime(&t), inbuf); 326 blankline = seensubj = YES; 327 } 328 else 329 blankline = seensubj = NO; 330 for (;;) { 331 fgets(inbuf, sizeof inbuf, stdin); 332 if (feof(stdin) || ferror(stdin)) 333 break; 334 blankline = (blankline || (inbuf[0] == '\n')); 335 seensubj = (seensubj || (!blankline && (strncmp(inbuf, "Subj", 4) == 0))); 336 fputs(inbuf, newmsg); 337 } 338 #ifdef OBJECT 339 if (!seensubj) { 340 printf("NOTICE: Messages should have a Subject field!\n"); 341 #ifdef REJECT 342 unlink(fname); 343 #endif 344 exit(1); 345 } 346 #endif 347 exit(ferror(stdin)); 348 } 349 if (clean) 350 exit(0); 351 352 /* 353 * prepare to display messages 354 */ 355 totty = (isatty(fileno(stdout)) != 0); 356 use_pager = use_pager && totty; 357 358 sprintf(fname, "%s/%s", getenv("HOME"), MSGSRC); 359 msgsrc = fopen(fname, "r"); 360 if (msgsrc) { 361 newrc = NO; 362 fscanf(msgsrc, "%d\n", &nextmsg); 363 fclose(msgsrc); 364 if (nextmsg > lastmsg+1) { 365 printf("Warning: bounds have been reset (%d, %d)\n", 366 firstmsg, lastmsg); 367 truncate(fname, (off_t)0); 368 newrc = YES; 369 } 370 else if (!rcfirst) 371 rcfirst = nextmsg - rcback; 372 } 373 else 374 newrc = YES; 375 msgsrc = fopen(fname, "r+"); 376 if (msgsrc == NULL) 377 msgsrc = fopen(fname, "w"); 378 if (msgsrc == NULL) { 379 perror(fname); 380 exit(errno); 381 } 382 if (rcfirst) { 383 if (rcfirst > lastmsg+1) { 384 printf("Warning: the last message is number %d.\n", 385 lastmsg); 386 rcfirst = nextmsg; 387 } 388 if (rcfirst > firstmsg) 389 firstmsg = rcfirst; /* don't set below first msg */ 390 } 391 if (newrc) { 392 nextmsg = firstmsg; 393 fseek(msgsrc, 0L, 0); 394 fprintf(msgsrc, "%d\n", nextmsg); 395 fflush(msgsrc); 396 } 397 398 #ifdef V7 399 if (totty) { 400 struct winsize win; 401 if (ioctl(fileno(stdout), TIOCGWINSZ, &win) != -1) 402 Lpp = win.ws_row; 403 if (Lpp <= 0) { 404 if (tgetent(inbuf, getenv("TERM")) <= 0 405 || (Lpp = tgetnum("li")) <= 0) { 406 Lpp = NLINES; 407 } 408 } 409 } 410 #endif 411 Lpp -= 6; /* for headers, etc. */ 412 413 already = NO; 414 prevmsg = firstmsg; 415 printing = YES; 416 if (ruptible) 417 signal(SIGINT, onintr); 418 419 /* 420 * Main program loop 421 */ 422 for (msg = firstmsg; msg <= lastmsg; msg++) { 423 424 sprintf(fname, "%s/%d", _PATH_MSGS, msg); 425 newmsg = fopen(fname, "r"); 426 if (newmsg == NULL) 427 continue; 428 429 gfrsub(newmsg); /* get From and Subject fields */ 430 if (locomode && !local) { 431 fclose(newmsg); 432 continue; 433 } 434 435 if (qopt) { /* This has to be located here */ 436 printf("There are new messages.\n"); 437 exit(0); 438 } 439 440 if (already && !hdrs) 441 putchar('\n'); 442 443 /* 444 * Print header 445 */ 446 if (totty) 447 signal(SIGTSTP, onsusp); 448 (void) setjmp(tstpbuf); 449 already = YES; 450 nlines = 2; 451 if (seenfrom) { 452 printf("Message %d:\nFrom %s %s", msg, from, date); 453 nlines++; 454 } 455 if (seensubj) { 456 printf("Subject: %s", subj); 457 nlines++; 458 } 459 else { 460 if (seenfrom) { 461 putchar('\n'); 462 nlines++; 463 } 464 while (nlines < 6 465 && fgets(inbuf, sizeof inbuf, newmsg) 466 && inbuf[0] != '\n') { 467 fputs(inbuf, stdout); 468 nlines++; 469 } 470 } 471 472 lct = linecnt(newmsg); 473 if (lct) 474 printf("(%d%slines) ", lct, seensubj? " " : " more "); 475 476 if (hdrs) { 477 printf("\n-----\n"); 478 fclose(newmsg); 479 continue; 480 } 481 482 /* 483 * Ask user for command 484 */ 485 if (totty) 486 ask(lct? MORE : (msg==lastmsg? NOMORE : NEXT)); 487 else 488 inbuf[0] = 'y'; 489 if (totty) 490 signal(SIGTSTP, SIG_DFL); 491 cmnd: 492 in = inbuf; 493 switch (*in) { 494 case 'x': 495 case 'X': 496 exit(0); 497 498 case 'q': 499 case 'Q': 500 quitit = YES; 501 printf("--Postponed--\n"); 502 exit(0); 503 /* intentional fall-thru */ 504 case 'n': 505 case 'N': 506 if (msg >= nextmsg) sep = "Flushed"; 507 prevmsg = msg; 508 break; 509 510 case 'p': 511 case 'P': 512 use_pager = (*in++ == 'p'); 513 /* intentional fallthru */ 514 case '\n': 515 case 'y': 516 default: 517 if (*in == '-') { 518 msg = prevmsg-1; 519 sep = "replay"; 520 break; 521 } 522 if (isdigit(*in)) { 523 msg = next(in); 524 sep = in; 525 break; 526 } 527 528 prmesg(nlines + lct + (seensubj? 1 : 0)); 529 prevmsg = msg; 530 531 } 532 533 printf("--%s--\n", sep); 534 sep = "-"; 535 if (msg >= nextmsg) { 536 nextmsg = msg + 1; 537 fseek(msgsrc, 0L, 0); 538 fprintf(msgsrc, "%d\n", nextmsg); 539 fflush(msgsrc); 540 } 541 if (newmsg) 542 fclose(newmsg); 543 if (quitit) 544 break; 545 } 546 547 /* 548 * Make sure .rc file gets updated 549 */ 550 if (--msg >= nextmsg) { 551 nextmsg = msg + 1; 552 fseek(msgsrc, 0L, 0); 553 fprintf(msgsrc, "%d\n", nextmsg); 554 fflush(msgsrc); 555 } 556 if (already && !quitit && lastcmd && totty) { 557 /* 558 * save or reply to last message? 559 */ 560 msg = prevmsg; 561 ask(NOMORE); 562 if (inbuf[0] == '-' || isdigit(inbuf[0])) 563 goto cmnd; 564 } 565 if (!(already || hush || qopt)) 566 printf("No new messages.\n"); 567 exit(0); 568 } 569 570 prmesg(length) 571 int length; 572 { 573 FILE *outf; 574 575 if (use_pager && length > Lpp) { 576 signal(SIGPIPE, SIG_IGN); 577 signal(SIGQUIT, SIG_IGN); 578 sprintf(cmdbuf, _PATH_PAGER, Lpp); 579 outf = popen(cmdbuf, "w"); 580 if (!outf) 581 outf = stdout; 582 else 583 setbuf(outf, (char *)NULL); 584 } 585 else 586 outf = stdout; 587 588 if (seensubj) 589 putc('\n', outf); 590 591 while (fgets(inbuf, sizeof inbuf, newmsg)) { 592 fputs(inbuf, outf); 593 if (ferror(outf)) { 594 clearerr(outf); 595 break; 596 } 597 } 598 599 if (outf != stdout) { 600 pclose(outf); 601 signal(SIGPIPE, SIG_DFL); 602 signal(SIGQUIT, SIG_DFL); 603 } 604 else { 605 fflush(stdout); 606 } 607 608 /* trick to force wait on output */ 609 tcdrain(fileno(stdout)); 610 } 611 612 void 613 onintr() 614 { 615 signal(SIGINT, onintr); 616 if (mailing) 617 unlink(fname); 618 if (sending) { 619 unlink(fname); 620 puts("--Killed--"); 621 exit(1); 622 } 623 if (printing) { 624 putchar('\n'); 625 if (hdrs) 626 exit(0); 627 sep = "Interrupt"; 628 if (newmsg) 629 fseek(newmsg, 0L, 2); 630 intrpflg = YES; 631 } 632 } 633 634 /* 635 * We have just gotten a susp. Suspend and prepare to resume. 636 */ 637 void 638 onsusp() 639 { 640 641 signal(SIGTSTP, SIG_DFL); 642 sigsetmask(0); 643 kill(0, SIGTSTP); 644 signal(SIGTSTP, onsusp); 645 if (!mailing) 646 longjmp(tstpbuf, 0); 647 } 648 649 linecnt(f) 650 FILE *f; 651 { 652 off_t oldpos = ftell(f); 653 int l = 0; 654 char lbuf[BUFSIZ]; 655 656 while (fgets(lbuf, sizeof lbuf, f)) 657 l++; 658 clearerr(f); 659 fseek(f, oldpos, 0); 660 return (l); 661 } 662 663 next(buf) 664 char *buf; 665 { 666 int i; 667 sscanf(buf, "%d", &i); 668 sprintf(buf, "Goto %d", i); 669 return(--i); 670 } 671 672 ask(prompt) 673 char *prompt; 674 { 675 char inch; 676 int n, cmsg; 677 off_t oldpos; 678 FILE *cpfrom, *cpto; 679 680 printf("%s ", prompt); 681 fflush(stdout); 682 intrpflg = NO; 683 (void) fgets(inbuf, sizeof inbuf, stdin); 684 if ((n = strlen(inbuf)) > 0 && inbuf[n - 1] == '\n') 685 inbuf[n - 1] = '\0'; 686 if (intrpflg) 687 inbuf[0] = 'x'; 688 689 /* 690 * Handle 'mail' and 'save' here. 691 */ 692 if ((inch = inbuf[0]) == 's' || inch == 'm') { 693 if (inbuf[1] == '-') 694 cmsg = prevmsg; 695 else if (isdigit(inbuf[1])) 696 cmsg = atoi(&inbuf[1]); 697 else 698 cmsg = msg; 699 sprintf(fname, "%s/%d", _PATH_MSGS, cmsg); 700 701 oldpos = ftell(newmsg); 702 703 cpfrom = fopen(fname, "r"); 704 if (!cpfrom) { 705 printf("Message %d not found\n", cmsg); 706 ask (prompt); 707 return; 708 } 709 710 if (inch == 's') { 711 in = nxtfld(inbuf); 712 if (*in) { 713 for (n=0; in[n] > ' '; n++) { /* sizeof fname? */ 714 fname[n] = in[n]; 715 } 716 fname[n] = NULL; 717 } 718 else 719 strcpy(fname, "Messages"); 720 } 721 else { 722 strcpy(fname, _PATH_TMP); 723 mktemp(fname); 724 sprintf(cmdbuf, _PATH_MAIL, fname); 725 mailing = YES; 726 } 727 cpto = fopen(fname, "a"); 728 if (!cpto) { 729 perror(fname); 730 mailing = NO; 731 fseek(newmsg, oldpos, 0); 732 ask(prompt); 733 return; 734 } 735 736 while (n = fread(inbuf, 1, sizeof inbuf, cpfrom)) 737 fwrite(inbuf, 1, n, cpto); 738 739 fclose(cpfrom); 740 fclose(cpto); 741 fseek(newmsg, oldpos, 0); /* reposition current message */ 742 if (inch == 's') 743 printf("Message %d saved in \"%s\"\n", cmsg, fname); 744 else { 745 system(cmdbuf); 746 unlink(fname); 747 mailing = NO; 748 } 749 ask(prompt); 750 } 751 } 752 753 gfrsub(infile) 754 FILE *infile; 755 { 756 off_t frompos; 757 758 seensubj = seenfrom = NO; 759 local = YES; 760 subj[0] = from[0] = date[0] = NULL; 761 762 /* 763 * Is this a normal message? 764 */ 765 if (fgets(inbuf, sizeof inbuf, infile)) { 766 if (strncmp(inbuf, "From", 4)==0) { 767 /* 768 * expected form starts with From 769 */ 770 seenfrom = YES; 771 frompos = ftell(infile); 772 ptr = from; 773 in = nxtfld(inbuf); 774 if (*in) while (*in && *in > ' ') { 775 if (*in == ':' || *in == '@' || *in == '!') 776 local = NO; 777 *ptr++ = *in++; 778 /* what about sizeof from ? */ 779 } 780 *ptr = NULL; 781 if (*(in = nxtfld(in))) 782 strncpy(date, in, sizeof date); 783 else { 784 date[0] = '\n'; 785 date[1] = NULL; 786 } 787 } 788 else { 789 /* 790 * not the expected form 791 */ 792 fseek(infile, 0L, 0); 793 return; 794 } 795 } 796 else 797 /* 798 * empty file ? 799 */ 800 return; 801 802 /* 803 * look for Subject line until EOF or a blank line 804 */ 805 while (fgets(inbuf, sizeof inbuf, infile) 806 && !(blankline = (inbuf[0] == '\n'))) { 807 /* 808 * extract Subject line 809 */ 810 if (!seensubj && strncmp(inbuf, "Subj", 4)==0) { 811 seensubj = YES; 812 frompos = ftell(infile); 813 strncpy(subj, nxtfld(inbuf), sizeof subj); 814 } 815 } 816 if (!blankline) 817 /* 818 * ran into EOF 819 */ 820 fseek(infile, frompos, 0); 821 822 if (!seensubj) 823 /* 824 * for possible use with Mail 825 */ 826 strncpy(subj, "(No Subject)\n", sizeof subj); 827 } 828 829 char * 830 nxtfld(s) 831 char *s; 832 { 833 if (*s) while (*s && *s > ' ') s++; /* skip over this field */ 834 if (*s) while (*s && *s <= ' ') s++; /* find start of next field */ 835 return (s); 836 } 837