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