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