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