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