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
main(argc,argv)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
prmesg(length)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
onintr()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
onsusp()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
linecnt(f)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
next(buf)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
ask(prompt)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
gfrsub(infile)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 *
nxtfld(s)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