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