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.8 (Berkeley) 02/04/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 <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 "pathnames.h" 59 60 #define CMODE 0666 /* bounds file creation mode */ 61 #define NO 0 62 #define YES 1 63 #define SUPERUSER 0 /* superuser uid */ 64 #define DAEMON 1 /* daemon uid */ 65 #define NLINES 24 /* default number of lines/crt screen */ 66 #define NDAYS 21 /* default keep time for messages */ 67 #define DAYS *24*60*60 /* seconds/day */ 68 #define MSGSRC ".msgsrc" /* user's rc file */ 69 #define BOUNDS "bounds" /* message bounds file */ 70 #define NEXT "Next message? [yq]" 71 #define MORE "More? [ynq]" 72 #define NOMORE "(No more) [q] ?" 73 74 typedef char bool; 75 76 FILE *msgsrc; 77 FILE *newmsg; 78 char *sep = "-"; 79 char inbuf[BUFSIZ]; 80 char fname[128]; 81 char cmdbuf[128]; 82 char subj[128]; 83 char from[128]; 84 char date[128]; 85 char *ptr; 86 char *in; 87 bool local; 88 bool ruptible; 89 bool totty; 90 bool seenfrom; 91 bool seensubj; 92 bool blankline; 93 bool printing = NO; 94 bool mailing = NO; 95 bool quitit = NO; 96 bool sending = NO; 97 bool intrpflg = NO; 98 int uid; 99 int msg; 100 int prevmsg; 101 int lct; 102 int nlines; 103 int Lpp = 0; 104 time_t t; 105 time_t keep; 106 struct sgttyb otty; 107 108 char *mktemp(); 109 char *nxtfld(); 110 void onintr(); 111 void onsusp(); 112 113 /* option initialization */ 114 bool hdrs = NO; 115 bool qopt = NO; 116 bool hush = NO; 117 bool send_msg = NO; 118 bool locomode = NO; 119 bool use_pager = NO; 120 bool clean = NO; 121 bool lastcmd = NO; 122 jmp_buf tstpbuf; 123 124 main(argc, argv) 125 int argc; char *argv[]; 126 { 127 bool newrc, already; 128 int rcfirst = 0; /* first message to print (from .rc) */ 129 int rcback = 0; /* amount to back off of rcfirst */ 130 int firstmsg, nextmsg, lastmsg = 0; 131 int blast = 0; 132 FILE *bounds; 133 134 #ifdef UNBUFFERED 135 setbuf(stdout, NULL); 136 #endif 137 138 gtty(fileno(stdout), &otty); 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, "a"); 376 if (msgsrc == NULL) { 377 perror(fname); 378 exit(errno); 379 } 380 if (rcfirst) { 381 if (rcfirst > lastmsg+1) { 382 printf("Warning: the last message is number %d.\n", 383 lastmsg); 384 rcfirst = nextmsg; 385 } 386 if (rcfirst > firstmsg) 387 firstmsg = rcfirst; /* don't set below first msg */ 388 } 389 if (newrc) { 390 nextmsg = firstmsg; 391 fseek(msgsrc, 0L, 0); 392 fprintf(msgsrc, "%d\n", nextmsg); 393 fflush(msgsrc); 394 } 395 396 #ifdef V7 397 if (totty) { 398 struct winsize win; 399 if (ioctl(fileno(stdout), TIOCGWINSZ, &win) != -1) 400 Lpp = win.ws_row; 401 if (Lpp <= 0) { 402 if (tgetent(inbuf, getenv("TERM")) <= 0 403 || (Lpp = tgetnum("li")) <= 0) { 404 Lpp = NLINES; 405 } 406 } 407 } 408 #endif 409 Lpp -= 6; /* for headers, etc. */ 410 411 already = NO; 412 prevmsg = firstmsg; 413 printing = YES; 414 if (ruptible) 415 signal(SIGINT, onintr); 416 417 /* 418 * Main program loop 419 */ 420 for (msg = firstmsg; msg <= lastmsg; msg++) { 421 422 sprintf(fname, "%s/%d", _PATH_MSGS, msg); 423 newmsg = fopen(fname, "r"); 424 if (newmsg == NULL) 425 continue; 426 427 gfrsub(newmsg); /* get From and Subject fields */ 428 if (locomode && !local) { 429 fclose(newmsg); 430 continue; 431 } 432 433 if (qopt) { /* This has to be located here */ 434 printf("There are new messages.\n"); 435 exit(0); 436 } 437 438 if (already && !hdrs) 439 putchar('\n'); 440 441 /* 442 * Print header 443 */ 444 if (totty) 445 signal(SIGTSTP, onsusp); 446 (void) setjmp(tstpbuf); 447 already = YES; 448 nlines = 2; 449 if (seenfrom) { 450 printf("Message %d:\nFrom %s %s", msg, from, date); 451 nlines++; 452 } 453 if (seensubj) { 454 printf("Subject: %s", subj); 455 nlines++; 456 } 457 else { 458 if (seenfrom) { 459 putchar('\n'); 460 nlines++; 461 } 462 while (nlines < 6 463 && fgets(inbuf, sizeof inbuf, newmsg) 464 && inbuf[0] != '\n') { 465 fputs(inbuf, stdout); 466 nlines++; 467 } 468 } 469 470 lct = linecnt(newmsg); 471 if (lct) 472 printf("(%d%slines) ", lct, seensubj? " " : " more "); 473 474 if (hdrs) { 475 printf("\n-----\n"); 476 fclose(newmsg); 477 continue; 478 } 479 480 /* 481 * Ask user for command 482 */ 483 if (totty) 484 ask(lct? MORE : (msg==lastmsg? NOMORE : NEXT)); 485 else 486 inbuf[0] = 'y'; 487 if (totty) 488 signal(SIGTSTP, SIG_DFL); 489 cmnd: 490 in = inbuf; 491 switch (*in) { 492 case 'x': 493 case 'X': 494 exit(0); 495 496 case 'q': 497 case 'Q': 498 quitit = YES; 499 printf("--Postponed--\n"); 500 exit(0); 501 /* intentional fall-thru */ 502 case 'n': 503 case 'N': 504 if (msg >= nextmsg) sep = "Flushed"; 505 prevmsg = msg; 506 break; 507 508 case 'p': 509 case 'P': 510 use_pager = (*in++ == 'p'); 511 /* intentional fallthru */ 512 case '\n': 513 case 'y': 514 default: 515 if (*in == '-') { 516 msg = prevmsg-1; 517 sep = "replay"; 518 break; 519 } 520 if (isdigit(*in)) { 521 msg = next(in); 522 sep = in; 523 break; 524 } 525 526 prmesg(nlines + lct + (seensubj? 1 : 0)); 527 prevmsg = msg; 528 529 } 530 531 printf("--%s--\n", sep); 532 sep = "-"; 533 if (msg >= nextmsg) { 534 nextmsg = msg + 1; 535 fseek(msgsrc, 0L, 0); 536 fprintf(msgsrc, "%d\n", nextmsg); 537 fflush(msgsrc); 538 } 539 if (newmsg) 540 fclose(newmsg); 541 if (quitit) 542 break; 543 } 544 545 /* 546 * Make sure .rc file gets updated 547 */ 548 if (--msg >= nextmsg) { 549 nextmsg = msg + 1; 550 fseek(msgsrc, 0L, 0); 551 fprintf(msgsrc, "%d\n", nextmsg); 552 fflush(msgsrc); 553 } 554 if (already && !quitit && lastcmd && totty) { 555 /* 556 * save or reply to last message? 557 */ 558 msg = prevmsg; 559 ask(NOMORE); 560 if (inbuf[0] == '-' || isdigit(inbuf[0])) 561 goto cmnd; 562 } 563 if (!(already || hush || qopt)) 564 printf("No new messages.\n"); 565 exit(0); 566 } 567 568 prmesg(length) 569 int length; 570 { 571 FILE *outf; 572 573 if (use_pager && length > Lpp) { 574 signal(SIGPIPE, SIG_IGN); 575 signal(SIGQUIT, SIG_IGN); 576 sprintf(cmdbuf, _PATH_PAGER, Lpp); 577 outf = popen(cmdbuf, "w"); 578 if (!outf) 579 outf = stdout; 580 else 581 setbuf(outf, (char *)NULL); 582 } 583 else 584 outf = stdout; 585 586 if (seensubj) 587 putc('\n', outf); 588 589 while (fgets(inbuf, sizeof inbuf, newmsg)) { 590 fputs(inbuf, outf); 591 if (ferror(outf)) { 592 clearerr(outf); 593 break; 594 } 595 } 596 597 if (outf != stdout) { 598 pclose(outf); 599 signal(SIGPIPE, SIG_DFL); 600 signal(SIGQUIT, SIG_DFL); 601 } 602 else { 603 fflush(stdout); 604 } 605 606 /* trick to force wait on output */ 607 stty(fileno(stdout), &otty); 608 } 609 610 void 611 onintr() 612 { 613 signal(SIGINT, onintr); 614 if (mailing) 615 unlink(fname); 616 if (sending) { 617 unlink(fname); 618 puts("--Killed--"); 619 exit(1); 620 } 621 if (printing) { 622 putchar('\n'); 623 if (hdrs) 624 exit(0); 625 sep = "Interrupt"; 626 if (newmsg) 627 fseek(newmsg, 0L, 2); 628 intrpflg = YES; 629 } 630 } 631 632 /* 633 * We have just gotten a susp. Suspend and prepare to resume. 634 */ 635 void 636 onsusp() 637 { 638 639 signal(SIGTSTP, SIG_DFL); 640 sigsetmask(0); 641 kill(0, SIGTSTP); 642 signal(SIGTSTP, onsusp); 643 if (!mailing) 644 longjmp(tstpbuf, 0); 645 } 646 647 linecnt(f) 648 FILE *f; 649 { 650 off_t oldpos = ftell(f); 651 int l = 0; 652 char lbuf[BUFSIZ]; 653 654 while (fgets(lbuf, sizeof lbuf, f)) 655 l++; 656 clearerr(f); 657 fseek(f, oldpos, 0); 658 return (l); 659 } 660 661 next(buf) 662 char *buf; 663 { 664 int i; 665 sscanf(buf, "%d", &i); 666 sprintf(buf, "Goto %d", i); 667 return(--i); 668 } 669 670 ask(prompt) 671 char *prompt; 672 { 673 char inch; 674 int n, cmsg; 675 off_t oldpos; 676 FILE *cpfrom, *cpto; 677 678 printf("%s ", prompt); 679 fflush(stdout); 680 intrpflg = NO; 681 (void) fgets(inbuf, sizeof inbuf, stdin); 682 if ((n = strlen(inbuf)) > 0 && inbuf[n - 1] == '\n') 683 inbuf[n - 1] = '\0'; 684 if (intrpflg) 685 inbuf[0] = 'x'; 686 687 /* 688 * Handle 'mail' and 'save' here. 689 */ 690 if ((inch = inbuf[0]) == 's' || inch == 'm') { 691 if (inbuf[1] == '-') 692 cmsg = prevmsg; 693 else if (isdigit(inbuf[1])) 694 cmsg = atoi(&inbuf[1]); 695 else 696 cmsg = msg; 697 sprintf(fname, "%s/%d", _PATH_MSGS, cmsg); 698 699 oldpos = ftell(newmsg); 700 701 cpfrom = fopen(fname, "r"); 702 if (!cpfrom) { 703 printf("Message %d not found\n", cmsg); 704 ask (prompt); 705 return; 706 } 707 708 if (inch == 's') { 709 in = nxtfld(inbuf); 710 if (*in) { 711 for (n=0; in[n] > ' '; n++) { /* sizeof fname? */ 712 fname[n] = in[n]; 713 } 714 fname[n] = NULL; 715 } 716 else 717 strcpy(fname, "Messages"); 718 } 719 else { 720 strcpy(fname, _PATH_TMP); 721 mktemp(fname); 722 sprintf(cmdbuf, _PATH_MAIL, fname); 723 mailing = YES; 724 } 725 cpto = fopen(fname, "a"); 726 if (!cpto) { 727 perror(fname); 728 mailing = NO; 729 fseek(newmsg, oldpos, 0); 730 ask(prompt); 731 return; 732 } 733 734 while (n = fread(inbuf, 1, sizeof inbuf, cpfrom)) 735 fwrite(inbuf, 1, n, cpto); 736 737 fclose(cpfrom); 738 fclose(cpto); 739 fseek(newmsg, oldpos, 0); /* reposition current message */ 740 if (inch == 's') 741 printf("Message %d saved in \"%s\"\n", cmsg, fname); 742 else { 743 system(cmdbuf); 744 unlink(fname); 745 mailing = NO; 746 } 747 ask(prompt); 748 } 749 } 750 751 gfrsub(infile) 752 FILE *infile; 753 { 754 off_t frompos; 755 756 seensubj = seenfrom = NO; 757 local = YES; 758 subj[0] = from[0] = date[0] = NULL; 759 760 /* 761 * Is this a normal message? 762 */ 763 if (fgets(inbuf, sizeof inbuf, infile)) { 764 if (strncmp(inbuf, "From", 4)==0) { 765 /* 766 * expected form starts with From 767 */ 768 seenfrom = YES; 769 frompos = ftell(infile); 770 ptr = from; 771 in = nxtfld(inbuf); 772 if (*in) while (*in && *in > ' ') { 773 if (*in == ':' || *in == '@' || *in == '!') 774 local = NO; 775 *ptr++ = *in++; 776 /* what about sizeof from ? */ 777 } 778 *ptr = NULL; 779 if (*(in = nxtfld(in))) 780 strncpy(date, in, sizeof date); 781 else { 782 date[0] = '\n'; 783 date[1] = NULL; 784 } 785 } 786 else { 787 /* 788 * not the expected form 789 */ 790 fseek(infile, 0L, 0); 791 return; 792 } 793 } 794 else 795 /* 796 * empty file ? 797 */ 798 return; 799 800 /* 801 * look for Subject line until EOF or a blank line 802 */ 803 while (fgets(inbuf, sizeof inbuf, infile) 804 && !(blankline = (inbuf[0] == '\n'))) { 805 /* 806 * extract Subject line 807 */ 808 if (!seensubj && strncmp(inbuf, "Subj", 4)==0) { 809 seensubj = YES; 810 frompos = ftell(infile); 811 strncpy(subj, nxtfld(inbuf), sizeof subj); 812 } 813 } 814 if (!blankline) 815 /* 816 * ran into EOF 817 */ 818 fseek(infile, frompos, 0); 819 820 if (!seensubj) 821 /* 822 * for possible use with Mail 823 */ 824 strncpy(subj, "(No Subject)\n", sizeof subj); 825 } 826 827 char * 828 nxtfld(s) 829 char *s; 830 { 831 if (*s) while (*s && *s > ' ') s++; /* skip over this field */ 832 if (*s) while (*s && *s <= ' ') s++; /* find start of next field */ 833 return (s); 834 } 835