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