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.5 (Berkeley) 05/06/90"; 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 <sys/dir.h> 48 #include <sys/stat.h> 49 #include <ctype.h> 50 #include <pwd.h> 51 #include <sgtty.h> 52 #include <setjmp.h> 53 #include <stdio.h> 54 #include "pathnames.h" 55 56 #define CMODE 0666 /* bounds file creation mode */ 57 #define NO 0 58 #define YES 1 59 #define SUPERUSER 0 /* superuser uid */ 60 #define DAEMON 1 /* daemon uid */ 61 #define NLINES 24 /* default number of lines/crt screen */ 62 #define NDAYS 21 /* default keep time for messages */ 63 #define DAYS *24*60*60 /* seconds/day */ 64 #define MSGSRC ".msgsrc" /* user's rc file */ 65 #define BOUNDS "bounds" /* message bounds file */ 66 #define NEXT "Next message? [yq]" 67 #define MORE "More? [ynq]" 68 #define NOMORE "(No more) [q] ?" 69 70 typedef char bool; 71 72 FILE *newmsg; 73 char *sep = "-"; 74 char inbuf[BUFSIZ]; 75 char fname[128]; 76 char cmdbuf[128]; 77 char subj[128]; 78 char from[128]; 79 char date[128]; 80 char *ptr; 81 char *in; 82 bool local; 83 bool ruptible; 84 bool totty; 85 bool seenfrom; 86 bool seensubj; 87 bool blankline; 88 bool printing = NO; 89 bool mailing = NO; 90 bool quitit = NO; 91 bool sending = NO; 92 bool intrpflg = NO; 93 bool tstpflag = 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 *nxtfld(); 106 int onintr(); 107 int onsusp(); 108 off_t ftell(); 109 FILE *popen(); 110 struct passwd *getpwuid(); 111 112 extern int errno; 113 114 /* option initialization */ 115 bool hdrs = NO; 116 bool qopt = NO; 117 bool hush = NO; 118 bool send = NO; 119 bool locomode = NO; 120 bool pause = 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, *msgsrc; 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 pause = YES; 187 break; 188 189 case 'q': /* query only */ 190 qopt = YES; 191 break; 192 193 case 's': /* sending TO msgs */ 194 send = 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) { 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) { 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 pause = pause && 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, 0); 369 newrc = YES; 370 } 371 else if (!rcfirst) 372 rcfirst = nextmsg - rcback; 373 } 374 else 375 newrc = YES; 376 msgsrc = fopen(fname, "a"); 377 if (msgsrc == NULL) { 378 perror(fname); 379 exit(errno); 380 } 381 if (rcfirst) { 382 if (rcfirst > lastmsg+1) { 383 printf("Warning: the last message is number %d.\n", 384 lastmsg); 385 rcfirst = nextmsg; 386 } 387 if (rcfirst > firstmsg) 388 firstmsg = rcfirst; /* don't set below first msg */ 389 } 390 if (newrc) { 391 nextmsg = firstmsg; 392 fseek(msgsrc, 0L, 0); 393 fprintf(msgsrc, "%d\n", nextmsg); 394 fflush(msgsrc); 395 } 396 397 #ifdef V7 398 if (totty) { 399 struct winsize win; 400 if (ioctl(fileno(stdout), TIOCGWINSZ, &win) != -1) 401 Lpp = win.ws_row; 402 if (Lpp <= 0) { 403 if (tgetent(inbuf, getenv("TERM")) <= 0 404 || (Lpp = tgetnum("li")) <= 0) { 405 Lpp = NLINES; 406 } 407 } 408 } 409 #endif 410 Lpp -= 6; /* for headers, etc. */ 411 412 already = NO; 413 prevmsg = firstmsg; 414 printing = YES; 415 if (ruptible) 416 signal(SIGINT, onintr); 417 418 /* 419 * Main program loop 420 */ 421 for (msg = firstmsg; msg <= lastmsg; msg++) { 422 423 sprintf(fname, "%s/%d", _PATH_MSGS, msg); 424 newmsg = fopen(fname, "r"); 425 if (newmsg == NULL) 426 continue; 427 428 gfrsub(newmsg); /* get From and Subject fields */ 429 if (locomode && !local) { 430 fclose(newmsg); 431 continue; 432 } 433 434 if (qopt) { /* This has to be located here */ 435 printf("There are new messages.\n"); 436 exit(0); 437 } 438 439 if (already && !hdrs) 440 putchar('\n'); 441 already = YES; 442 443 /* 444 * Print header 445 */ 446 again: 447 if (totty) 448 signal(SIGTSTP, onsusp); 449 (void) setjmp(tstpbuf); 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 pause = (*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, *inf; 574 int c; 575 576 if (pause && 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, 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 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 onsusp() 638 { 639 640 signal(SIGTSTP, SIG_DFL); 641 sigsetmask(0); 642 kill(0, SIGTSTP); 643 signal(SIGTSTP, onsusp); 644 if (!mailing) 645 longjmp(tstpbuf); 646 } 647 648 linecnt(f) 649 FILE *f; 650 { 651 off_t oldpos = ftell(f); 652 int l = 0; 653 char lbuf[BUFSIZ]; 654 655 while (fgets(lbuf, sizeof lbuf, f)) 656 l++; 657 clearerr(f); 658 fseek(f, oldpos, 0); 659 return (l); 660 } 661 662 next(buf) 663 char *buf; 664 { 665 int i; 666 sscanf(buf, "%d", &i); 667 sprintf(buf, "Goto %d", i); 668 return(--i); 669 } 670 671 ask(prompt) 672 char *prompt; 673 { 674 char inch; 675 int n, cmsg; 676 off_t oldpos; 677 FILE *cpfrom, *cpto; 678 679 printf("%s ", prompt); 680 fflush(stdout); 681 intrpflg = NO; 682 gets(inbuf); 683 if (intrpflg) 684 inbuf[0] = 'x'; 685 686 /* 687 * Handle 'mail' and 'save' here. 688 */ 689 if ((inch = inbuf[0]) == 's' || inch == 'm') { 690 if (inbuf[1] == '-') 691 cmsg = prevmsg; 692 else if (isdigit(inbuf[1])) 693 cmsg = atoi(&inbuf[1]); 694 else 695 cmsg = msg; 696 sprintf(fname, "%s/%d", _PATH_MSGS, cmsg); 697 698 oldpos = ftell(newmsg); 699 700 cpfrom = fopen(fname, "r"); 701 if (!cpfrom) { 702 printf("Message %d not found\n", cmsg); 703 ask (prompt); 704 return; 705 } 706 707 if (inch == 's') { 708 in = nxtfld(inbuf); 709 if (*in) { 710 for (n=0; in[n] > ' '; n++) { /* sizeof fname? */ 711 fname[n] = in[n]; 712 } 713 fname[n] = NULL; 714 } 715 else 716 strcpy(fname, "Messages"); 717 } 718 else { 719 strcpy(fname, _PATH_TMP); 720 mktemp(fname); 721 sprintf(cmdbuf, _PATH_MAIL, fname); 722 mailing = YES; 723 } 724 cpto = fopen(fname, "a"); 725 if (!cpto) { 726 perror(fname); 727 mailing = NO; 728 fseek(newmsg, oldpos, 0); 729 ask(prompt); 730 return; 731 } 732 733 while (n = fread(inbuf, 1, sizeof inbuf, cpfrom)) 734 fwrite(inbuf, 1, n, cpto); 735 736 fclose(cpfrom); 737 fclose(cpto); 738 fseek(newmsg, oldpos, 0); /* reposition current message */ 739 if (inch == 's') 740 printf("Message %d saved in \"%s\"\n", cmsg, fname); 741 else { 742 system(cmdbuf); 743 unlink(fname); 744 mailing = NO; 745 } 746 ask(prompt); 747 } 748 } 749 750 gfrsub(infile) 751 FILE *infile; 752 { 753 off_t frompos; 754 755 seensubj = seenfrom = NO; 756 local = YES; 757 subj[0] = from[0] = date[0] = NULL; 758 759 /* 760 * Is this a normal message? 761 */ 762 if (fgets(inbuf, sizeof inbuf, infile)) { 763 if (strncmp(inbuf, "From", 4)==0) { 764 /* 765 * expected form starts with From 766 */ 767 seenfrom = YES; 768 frompos = ftell(infile); 769 ptr = from; 770 in = nxtfld(inbuf); 771 if (*in) while (*in && *in > ' ') { 772 if (*in == ':' || *in == '@' || *in == '!') 773 local = NO; 774 *ptr++ = *in++; 775 /* what about sizeof from ? */ 776 } 777 *ptr = NULL; 778 if (*(in = nxtfld(in))) 779 strncpy(date, in, sizeof date); 780 else { 781 date[0] = '\n'; 782 date[1] = NULL; 783 } 784 } 785 else { 786 /* 787 * not the expected form 788 */ 789 fseek(infile, 0L, 0); 790 return; 791 } 792 } 793 else 794 /* 795 * empty file ? 796 */ 797 return; 798 799 /* 800 * look for Subject line until EOF or a blank line 801 */ 802 while (fgets(inbuf, sizeof inbuf, infile) 803 && !(blankline = (inbuf[0] == '\n'))) { 804 /* 805 * extract Subject line 806 */ 807 if (!seensubj && strncmp(inbuf, "Subj", 4)==0) { 808 seensubj = YES; 809 frompos = ftell(infile); 810 strncpy(subj, nxtfld(inbuf), sizeof subj); 811 } 812 } 813 if (!blankline) 814 /* 815 * ran into EOF 816 */ 817 fseek(infile, frompos, 0); 818 819 if (!seensubj) 820 /* 821 * for possible use with Mail 822 */ 823 strncpy(subj, "(No Subject)\n", sizeof subj); 824 } 825 826 char * 827 nxtfld(s) 828 char *s; 829 { 830 if (*s) while (*s && *s > ' ') s++; /* skip over this field */ 831 if (*s) while (*s && *s <= ' ') s++; /* find start of next field */ 832 return (s); 833 } 834