xref: /original-bsd/usr.bin/msgs/msgs.c (revision 62734ea8)
1 #ifndef lint
2 static char sccsid[] = "@(#)msgs.c	4.4 10/29/82";
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>
31 #include <sys/param.h>
32 #include <signal.h>
33 #include <dir.h>
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 
55 typedef	char	bool;
56 
57 FILE	*newmsg;
58 char	*sep = "-";
59 char	inbuf[BUFSIZ];
60 char	fname[128];
61 char	cmdbuf[128];
62 char	subj[128];
63 char	from[128];
64 char	date[128];
65 char	*ptr;
66 char	*in;
67 bool	local;
68 bool	ruptible;
69 bool	totty;
70 bool	seenfrom;
71 bool	seensubj;
72 bool	blankline;
73 bool	printing = NO;
74 bool	mailing = NO;
75 bool	quitit = NO;
76 bool	sending = NO;
77 bool	intrpflg = NO;
78 int	uid;
79 int	msg;
80 int	prevmsg;
81 int	lct;
82 int	nlines;
83 int	Lpp = NLINES;
84 time_t	t;
85 time_t	keep;
86 struct	sgttyb	otty;
87 
88 char	*ctime();
89 char	*nxtfld();
90 int	onintr();
91 off_t	ftell();
92 FILE	*popen();
93 struct	passwd	*getpwuid();
94 
95 extern	int	errno;
96 
97 /* option initialization */
98 bool	hdrs = NO;
99 bool	qopt = NO;
100 bool	hush = NO;
101 bool	send = NO;
102 bool	locomode = NO;
103 bool	pause = NO;
104 bool	clean = NO;
105 bool	lastcmd = NO;
106 
107 main(argc, argv)
108 int 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 */
207 		struct direct *dp;
208 		struct stat stbuf;
209 		bool seenany = NO;
210 		DIR	*dirp;
211 
212 		dirp = opendir(USRMSGS);
213 		if (dirp == NULL) {
214 			perror(USRMSGS);
215 			exit(errno);
216 		}
217 
218 		firstmsg = 32767;
219 		lastmsg = 0;
220 
221 		for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)){
222 			register char *cp = dp->d_name;
223 			register int i = 0;
224 
225 			if (dp->d_ino == 0)
226 				continue;
227 			if (dp->d_namlen == 0)
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 		}
254 		closedir(dirp);
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';
454 cmnd:
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 
523 prmesg(length)
524 int 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 
557 onintr()
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 
578 linecnt(f)
579 FILE *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 
592 next(buf)
593 char *buf;
594 {
595 	int i;
596 	sscanf(buf, "%d", &i);
597 	sprintf(buf, "Goto %d", i);
598 	return(--i);
599 }
600 
601 ask(prompt)
602 char *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 
680 gfrsub(infile)
681 FILE *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 > ' ') {
702 				if (*in == ':' || *in == '@' || *in == '!')
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 
756 char *
757 nxtfld(s)
758 char *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 }
764