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