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