xref: /illumos-gate/usr/src/cmd/mailx/fio.c (revision 66582b606a8194f7f3ba5b3a3a6dca5b0d346361)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2014 Joyent, Inc.
25  */
26 
27 /*
28  * Copyright 1999 Sun Microsystems, Inc.  All rights reserved.
29  * Use is subject to license terms.
30  */
31 
32 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
33 /*	  All Rights Reserved  	*/
34 
35 #include "rcv.h"
36 #include <locale.h>
37 #include <wordexp.h>
38 
39 /*
40  * mailx -- a modified version of a University of California at Berkeley
41  *	mail program
42  *
43  * File I/O.
44  */
45 
46 static int getln(char *line, int max, FILE *f);
47 static int linecount(char *lp, long size);
48 
49 /*
50  * Set up the input pointers while copying the mail file into
51  * /tmp.
52  */
53 
54 void
55 setptr(register FILE *ibuf)
56 {
57 	int n, newline = 1, blankline = 1;
58 	int StartNewMsg = TRUE;
59 	int ToldUser = FALSE;
60 	long clen = -1L;
61 	int hdr = 0;
62 	int cflg = 0;			/* found Content-length in header */
63 	register char *cp;
64 	register int l;
65 	register long s;
66 	off_t offset;
67 	char linebuf[LINESIZE];
68 	int inhead, newmail, Odot;
69 	short flag;
70 
71 	if (!space) {
72 		msgCount = 0;
73 		offset = 0;
74 		space = 32;
75 		newmail = 0;
76 		message =
77 		    (struct message *)calloc(space, sizeof (struct message));
78 		if (message == NULL) {
79 			fprintf(stderr, gettext(
80 			    "calloc: insufficient memory for %d messages\n"),
81 			    space);
82 			exit(1);
83 			/* NOTREACHED */
84 		}
85 		dot = message;
86 	} else {
87 		newmail = 1;
88 		offset = fsize(otf);
89 	}
90 	s = 0L;
91 	l = 0;
92 	/*
93 	 * Set default flags.  When reading from
94 	 * a folder, assume the message has been
95 	 * previously read.
96 	 */
97 	if (edit)
98 		flag = MUSED|MREAD;
99 	else
100 		flag = MUSED|MNEW;
101 
102 	inhead = 0;
103 	while ((n = getln(linebuf, sizeof (linebuf), ibuf)) > 0) {
104 		if (!newline) {
105 			goto putout;
106 		}
107 	top:
108 		hdr = inhead && (headerp(linebuf) ||
109 		    (linebuf[0] == ' ' || linebuf[0] == '\t'));
110 		if (!hdr && cflg) {	/* nonheader, Content-length seen */
111 			if (clen > 0 && clen < n) {	/* read too much */
112 				/*
113 				 * NB: this only can happen if there is a
114 				 * small content that is NOT \n terminated
115 				 * and has no leading blank line, i.e., never.
116 				 */
117 				if (fwrite(linebuf, 1, (int)clen, otf) !=
118 					clen) {
119 					fclose(ibuf);
120 					fflush(otf);
121 				} else {
122 					l += linecount(linebuf, clen);
123 				}
124 				offset += clen;
125 				s += clen;
126 				n -= (int)clen;
127 				/* shift line to the left, copy null as well */
128 				memcpy(linebuf, linebuf+clen, n+1);
129 				cflg = 0;
130 				message[msgCount-1].m_clen = clen + 1;
131 				blankline = 1;
132 				StartNewMsg = TRUE;
133 				goto top;
134 			}
135 			/* here, clen == 0 or clen >= n */
136 			if (n == 1 && linebuf[0] == '\n') {
137 				/* leading empty line */
138 				clen++;		/* cheat */
139 				inhead = 0;
140 			}
141 			offset += clen;
142 			s += (long)clen;
143 			message[msgCount-1].m_clen = clen;
144 			for (;;) {
145 				if (fwrite(linebuf, 1, n, otf) != n) {
146 					fclose(ibuf);
147 					fflush(otf);
148 				} else {
149 					l += linecount(linebuf, n);
150 				}
151 				clen -= n;
152 				if (clen <= 0) {
153 					break;
154 				}
155 				n = clen < sizeof (linebuf) ?
156 				    (int)clen : (int)sizeof (linebuf);
157 				if ((n = fread(linebuf, 1, n, ibuf)) <= 0) {
158 					fprintf(stderr, gettext(
159 			"%s:\tYour mailfile was found to be corrupted.\n"),
160 					    progname);
161 					fprintf(stderr, gettext(
162 					    "\t(Unexpected end-of-file).\n"));
163 					fprintf(stderr, gettext(
164 					"\tMessage #%d may be truncated.\n\n"),
165 					    msgCount);
166 					offset -= clen;
167 					s -= clen;
168 					clen = 0; /* stop the loop */
169 				}
170 			}
171 			/* All done, go to top for next message */
172 			cflg = 0;
173 			blankline = 1;
174 			StartNewMsg = TRUE;
175 			continue;
176 		}
177 
178 		/* Look for a From line that starts a new message */
179 		if (blankline && linebuf[0] == 'F' && is_headline(linebuf)) {
180 			if (msgCount > 0 && !newmail) {
181 				message[msgCount-1].m_size = s;
182 				message[msgCount-1].m_lines = l;
183 				message[msgCount-1].m_flag = flag;
184 			}
185 			if (msgCount >= space) {
186 				/*
187 				 * Limit the speed at which the
188 				 * allocated space grows.
189 				 */
190 				if (space < 512)
191 					space = space*2;
192 				else
193 					space += 512;
194 				errno = 0;
195 				Odot = dot - &(message[0]);
196 				message = (struct message *)
197 				    realloc(message,
198 					space*(sizeof (struct message)));
199 				if (message == NULL) {
200 					perror("realloc failed");
201 					fprintf(stderr, gettext(
202 			"realloc: insufficient memory for %d messages\n"),
203 					    space);
204 					exit(1);
205 				}
206 				dot = &message[Odot];
207 			}
208 			message[msgCount].m_offset = offset;
209 			message[msgCount].m_text = TRUE;
210 			message[msgCount].m_clen = 0;
211 			newmail = 0;
212 			msgCount++;
213 			if (edit)
214 				flag = MUSED|MREAD;
215 			else
216 				flag = MUSED|MNEW;
217 			inhead = 1;
218 			s = 0L;
219 			l = 0;
220 			StartNewMsg = FALSE;
221 			ToldUser = FALSE;
222 			goto putout;
223 		}
224 
225 		/* if didn't get a header line, we're no longer in the header */
226 		if (!hdr)
227 			inhead = 0;
228 		if (!inhead)
229 			goto putout;
230 
231 		/*
232 		 * Look for Status: line.  Do quick check for second character,
233 		 * many headers start with "S" but few have "t" as second char.
234 		 */
235 		if ((linebuf[1] == 't' || linebuf[1] == 'T') &&
236 		    ishfield(linebuf, "status")) {
237 			cp = hcontents(linebuf);
238 			flag = MUSED|MNEW;
239 			if (strchr(cp, 'R'))
240 				flag |= MREAD;
241 			if (strchr(cp, 'O'))
242 				flag &= ~MNEW;
243 		}
244 		/*
245 		 * Look for Content-Length and Content-Type headers.  Like
246 		 * above, do a quick check for the "-", which is rare.
247 		 */
248 		if (linebuf[7] == '-') {
249 			if (ishfield(linebuf, "content-length")) {
250 				if (!cflg) {
251 					clen = atol(hcontents(linebuf));
252 					cflg = clen >= 0;
253 				}
254 			} else if (ishfield(linebuf, "content-type")) {
255 				char word[LINESIZE];
256 				char *cp2;
257 
258 				cp = hcontents(linebuf);
259 				cp2 = word;
260 				while (!isspace(*cp))
261 					*cp2++ = *cp++;
262 				*cp2 = '\0';
263 				if (icequal(word, "binary"))
264 					message[msgCount-1].m_text = FALSE;
265 			}
266 		}
267 putout:
268 		offset += n;
269 		s += (long)n;
270 		if (fwrite(linebuf, 1, n, otf) != n) {
271 			fclose(ibuf);
272 			fflush(otf);
273 		} else {
274 			l++;
275 		}
276 		if (ferror(otf)) {
277 			perror("/tmp");
278 			exit(1);
279 		}
280 		if (msgCount == 0) {
281 			fclose(ibuf);
282 			fflush(otf);
283 		}
284 		if (linebuf[n-1] == '\n') {
285 			blankline = newline && n == 1;
286 			newline = 1;
287 			if (n == 1) {
288 				/* Blank line. Skip StartNewMsg check below */
289 				continue;
290 			}
291 		} else {
292 			newline = 0;
293 		}
294 		if (StartNewMsg && !ToldUser) {
295 			fprintf(stderr, gettext(
296 			    "%s:\tYour mailfile was found to be corrupted\n"),
297 			    progname);
298 			fprintf(stderr,
299 			    gettext("\t(Content-length mismatch).\n"));
300 			fprintf(stderr, gettext(
301 			    "\tMessage #%d may be truncated,\n"), msgCount);
302 			fprintf(stderr, gettext(
303 			    "\twith another message concatenated to it.\n\n"));
304 			ToldUser = TRUE;
305 		}
306 	}
307 
308 	if (n == 0) {
309 		fflush(otf);
310 		if (fferror(otf)) {
311 			perror("/tmp");
312 			exit(1);
313 		}
314 		if (msgCount) {
315 			message[msgCount-1].m_size = s;
316 			message[msgCount-1].m_lines = l;
317 			message[msgCount-1].m_flag = flag;
318 		}
319 		fclose(ibuf);
320 	}
321 }
322 
323 /*
324  * Compute the content length of a message and set it into m_clen.
325  */
326 
327 void
328 setclen(register struct message *mp)
329 {
330 	long c;
331 	FILE *ibuf;
332 	char line[LINESIZE];
333 	int fline, nread;
334 
335 	ibuf = setinput(mp);
336 	c = mp->m_size;
337 	fline = 1;
338 	while (c > 0L) {
339 		nread = getln(line, sizeof (line), ibuf);
340 		c -= nread;
341 		/*
342 		 * First line is the From line, so no headers
343 		 * there to worry about.
344 		 */
345 		if (fline) {
346 			fline = 0;
347 			continue;
348 		}
349 		/*
350 		 * If line is blank, we've reached end of headers.
351 		 */
352 		if (line[0] == '\n')
353 			break;
354 		/*
355 		 * If this line is a continuation
356 		 * of a previous header field, keep going.
357 		 */
358 		if (isspace(line[0]))
359 			continue;
360 		/*
361 		 * If we are no longer looking at real
362 		 * header lines, we're done.
363 		 * This happens in uucp style mail where
364 		 * there are no headers at all.
365 		 */
366 		if (!headerp(line)) {
367 			c += nread;
368 			break;
369 		}
370 	}
371 	if (c == 0)
372 		c = 1;
373 	mp->m_clen = c;
374 }
375 
376 static int
377 getln(char *line, int max, FILE *f)
378 {
379 	register int c;
380 	register char *cp, *ecp;
381 
382 	cp = line;
383 	ecp = cp + max - 1;
384 	while (cp < ecp && (c = getc(f)) != EOF)
385 		if ((*cp++ = (char)c) == '\n')
386 			break;
387 	*cp = '\0';
388 	return (cp - line);
389 }
390 
391 /*
392  * Read up a line from the specified input into the line
393  * buffer.  Return the number of characters read.  Do not
394  * include the newline at the end.
395  */
396 
397 int
398 readline(FILE *ibuf, char *linebuf)
399 {
400 	register char *cp;
401 	register int c;
402 	int seennulls = 0;
403 
404 	clearerr(ibuf);
405 	c = getc(ibuf);
406 	for (cp = linebuf; c != '\n' && c != EOF; c = getc(ibuf)) {
407 		if (c == 0) {
408 			if (!seennulls) {
409 				fprintf(stderr,
410 				    gettext("mailx: NUL changed to @\n"));
411 				seennulls++;
412 			}
413 			c = '@';
414 		}
415 		if (cp - linebuf < LINESIZE-2)
416 			*cp++ = (char)c;
417 	}
418 	*cp = 0;
419 	if (c == EOF && cp == linebuf)
420 		return (0);
421 	return (cp - linebuf + 1);
422 }
423 
424 /*
425  * linecount - determine the number of lines in a printable file.
426  */
427 
428 static int
429 linecount(char *lp, long size)
430 {
431 	register char *cp, *ecp;
432 	register int count;
433 
434 	count = 0;
435 	cp = lp;
436 	ecp = cp + size;
437 	while (cp < ecp)
438 		if (*cp++ == '\n')
439 			count++;
440 	return (count);
441 }
442 
443 /*
444  * Return a file buffer all ready to read up the
445  * passed message pointer.
446  */
447 
448 FILE *
449 setinput(register struct message *mp)
450 {
451 	fflush(otf);
452 	if (fseek(itf, mp->m_offset, 0) < 0) {
453 		perror("fseek");
454 		panic("temporary file seek");
455 	}
456 	return (itf);
457 }
458 
459 
460 /*
461  * Delete a file, but only if the file is a plain file.
462  */
463 
464 int
465 removefile(char name[])
466 {
467 	struct stat statb;
468 	extern int errno;
469 
470 	if (stat(name, &statb) < 0)
471 		if (errno == ENOENT)
472 			return (0);	/* it's already gone, no error */
473 		else
474 			return (-1);
475 	if ((statb.st_mode & S_IFMT) != S_IFREG) {
476 		errno = EISDIR;
477 		return (-1);
478 	}
479 	return (unlink(name));
480 }
481 
482 /*
483  * Terminate an editing session by attempting to write out the user's
484  * file from the temporary.  Save any new stuff appended to the file.
485  */
486 int
487 edstop(
488     int noremove	/* don't allow the file to be removed, trunc instead */
489 )
490 {
491 	register int gotcha, c;
492 	register struct message *mp;
493 	FILE *obuf, *ibuf, *tbuf = 0, *readstat;
494 	struct stat statb;
495 	char tempname[STSIZ], *id;
496 	int tmpfd = -1;
497 
498 	if (readonly)
499 		return (0);
500 	holdsigs();
501 	if (Tflag != NOSTR) {
502 		if ((readstat = fopen(Tflag, "w")) == NULL)
503 			Tflag = NOSTR;
504 	}
505 	for (mp = &message[0], gotcha = 0; mp < &message[msgCount]; mp++) {
506 		if (mp->m_flag & MNEW) {
507 			mp->m_flag &= ~MNEW;
508 			mp->m_flag |= MSTATUS;
509 		}
510 		if (mp->m_flag & (MODIFY|MDELETED|MSTATUS))
511 			gotcha++;
512 		if (Tflag != NOSTR && (mp->m_flag & (MREAD|MDELETED)) != 0) {
513 			if ((id = hfield("article-id", mp, addone)) != NOSTR)
514 				fprintf(readstat, "%s\n", id);
515 		}
516 	}
517 	if (Tflag != NOSTR)
518 		fclose(readstat);
519 	if (!gotcha || Tflag != NOSTR)
520 		goto done;
521 	if ((ibuf = fopen(editfile, "r+")) == NULL) {
522 		perror(editfile);
523 		relsesigs();
524 		longjmp(srbuf, 1);
525 	}
526 	lock(ibuf, "r+", 1);
527 	if (fstat(fileno(ibuf), &statb) >= 0 && statb.st_size > mailsize) {
528 		nstrcpy(tempname, STSIZ, "/tmp/mboxXXXXXX");
529 		if ((tmpfd = mkstemp(tempname)) == -1) {
530 			perror(tempname);
531 			fclose(ibuf);
532 			relsesigs();
533 			longjmp(srbuf, 1);
534 		}
535 		if ((obuf = fdopen(tmpfd, "w")) == NULL) {
536 			perror(tempname);
537 			fclose(ibuf);
538 			removefile(tempname);
539 			relsesigs();
540 			(void) close(tmpfd);
541 			longjmp(srbuf, 1);
542 		}
543 		fseek(ibuf, mailsize, 0);
544 		while ((c = getc(ibuf)) != EOF)
545 			putc(c, obuf);
546 		fclose(obuf);
547 		if ((tbuf = fopen(tempname, "r")) == NULL) {
548 			perror(tempname);
549 			fclose(ibuf);
550 			removefile(tempname);
551 			relsesigs();
552 			longjmp(srbuf, 1);
553 		}
554 		removefile(tempname);
555 	}
556 	if ((obuf = fopen(editfile, "r+")) == NULL) {
557 		if ((obuf = fopen(editfile, "w")) == NULL) {
558 			perror(editfile);
559 			fclose(ibuf);
560 			if (tbuf)
561 				fclose(tbuf);
562 			relsesigs();
563 			longjmp(srbuf, 1);
564 		}
565 	}
566 	printf("\"%s\" ", editfile);
567 	flush();
568 	c = 0;
569 	for (mp = &message[0]; mp < &message[msgCount]; mp++) {
570 		if ((mp->m_flag & MDELETED) != 0)
571 			continue;
572 		c++;
573 		if (msend(mp, obuf, 0, fputs) < 0) {
574 			perror(editfile);
575 			fclose(ibuf);
576 			fclose(obuf);
577 			if (tbuf)
578 				fclose(tbuf);
579 			relsesigs();
580 			longjmp(srbuf, 1);
581 		}
582 	}
583 	gotcha = (c == 0 && tbuf == NULL);
584 	if (tbuf != NULL) {
585 		while ((c = getc(tbuf)) != EOF)
586 			putc(c, obuf);
587 		fclose(tbuf);
588 	}
589 	fflush(obuf);
590 	if (fferror(obuf)) {
591 		perror(editfile);
592 		fclose(ibuf);
593 		fclose(obuf);
594 		relsesigs();
595 		longjmp(srbuf, 1);
596 	}
597 	if (gotcha && !noremove && (value("keep") == NOSTR)) {
598 		removefile(editfile);
599 		printf(gettext("removed.\n"));
600 	}
601 	else
602 		printf(gettext("updated.\n"));
603 	fclose(ibuf);
604 	trunc(obuf);
605 	fclose(obuf);
606 	flush();
607 
608 done:
609 	relsesigs();
610 	return (1);
611 }
612 
613 #ifndef OLD_BSD_SIGS
614 static int sigdepth = 0;		/* depth of holdsigs() */
615 #ifdef VMUNIX
616 static int omask = 0;
617 #else
618 static	sigset_t mask, omask;
619 #endif
620 #endif
621 /*
622  * Hold signals SIGHUP - SIGQUIT.
623  */
624 void
625 holdsigs(void)
626 {
627 #ifndef OLD_BSD_SIGS
628 	if (sigdepth++ == 0) {
629 #ifdef VMUNIX
630 		omask = sigblock(sigmask(SIGHUP) |
631 		    sigmask(SIGINT)|sigmask(SIGQUIT));
632 #else
633 		sigemptyset(&mask);
634 		sigaddset(&mask, SIGHUP);
635 		sigaddset(&mask, SIGINT);
636 		sigaddset(&mask, SIGQUIT);
637 		sigprocmask(SIG_BLOCK, &mask, &omask);
638 #endif
639 	}
640 #else
641 	sighold(SIGHUP);
642 	sighold(SIGINT);
643 	sighold(SIGQUIT);
644 #endif
645 }
646 
647 /*
648  * Release signals SIGHUP - SIGQUIT
649  */
650 void
651 relsesigs(void)
652 {
653 #ifndef OLD_BSD_SIGS
654 	if (--sigdepth == 0)
655 #ifdef VMUNIX
656 		sigsetmask(omask);
657 #else
658 		sigprocmask(SIG_SETMASK, &omask, NULL);
659 #endif
660 #else
661 	sigrelse(SIGHUP);
662 	sigrelse(SIGINT);
663 	sigrelse(SIGQUIT);
664 #endif
665 }
666 
667 #if !defined(OLD_BSD_SIGS) && !defined(VMUNIX)
668 void
669 (*sigset(int sig, void (*act)(int)))(int)
670 {
671 	struct sigaction sa, osa;
672 
673 	sa.sa_handler = (void (*)())act;
674 	sigemptyset(&sa.sa_mask);
675 	sa.sa_flags = SA_RESTART;
676 	if (sigaction(sig, &sa, &osa) < 0)
677 		return ((void (*)(int))-1);
678 	return ((void (*)(int))osa.sa_handler);
679 }
680 #endif
681 
682 /*
683  * Flush the standard output.
684  */
685 
686 void
687 flush(void)
688 {
689 	fflush(stdout);
690 	fflush(stderr);
691 }
692 
693 /*
694  * Determine the size of the file possessed by
695  * the passed buffer.
696  */
697 
698 off_t
699 fsize(FILE *iob)
700 {
701 	register int f;
702 	struct stat sbuf;
703 
704 	f = fileno(iob);
705 	if (fstat(f, &sbuf) < 0)
706 		return (0);
707 	return (sbuf.st_size);
708 }
709 
710 /*
711  * Check for either a stdio recognized error, or
712  * a possibly delayed write error (in case it's
713  * an NFS file, for instance).
714  */
715 
716 int
717 fferror(FILE *iob)
718 {
719 	return (ferror(iob) || fsync(fileno(iob)) < 0);
720 }
721 
722 /*
723  * Take a file name, possibly with shell meta characters
724  * in it and expand it by using wordexp().
725  * Return the file name as a dynamic string.
726  * If the name cannot be expanded (for whatever reason)
727  * return NULL.
728  */
729 
730 char *
731 expand(char *name)
732 {
733 	char xname[BUFSIZ];
734 	char foldbuf[BUFSIZ];
735 	register char *cp;
736 	register int l;
737 	wordexp_t wrdexp_buf;
738 
739 	if (debug) fprintf(stderr, "expand(%s)=", name);
740 	cp = strchr(name, '\0') - 1;	/* pointer to last char of name */
741 	if (isspace(*cp)) {
742 		/* strip off trailing blanks */
743 		while (cp > name && isspace(*cp))
744 			cp--;
745 		l = *++cp;	/* save char */
746 		*cp = '\0';
747 		name = savestr(name);
748 		*cp = (char)l;	/* restore char */
749 	}
750 	if (name[0] == '+' && getfold(foldbuf) >= 0) {
751 		snprintf(xname, sizeof (xname), "%s/%s", foldbuf, name + 1);
752 		cp = safeexpand(savestr(xname));
753 		if (debug) fprintf(stderr, "%s\n", cp);
754 		return (cp);
755 	}
756 	if (!anyof(name, "~{[*?$`'\"\\")) {
757 		if (debug) fprintf(stderr, "%s\n", name);
758 		return (name);
759 	}
760 	if (wordexp(name, &wrdexp_buf, WRDE_NOCMD) != 0) {
761 		fprintf(stderr, gettext("Syntax error in \"%s\"\n"), name);
762 		fflush(stderr);
763 		return (NOSTR);
764 	}
765 	if (wrdexp_buf.we_wordc > 1) {
766 		fprintf(stderr, gettext("\"%s\": Ambiguous\n"), name);
767 		fflush(stderr);
768 		return (NOSTR);
769 	}
770 	if (debug) fprintf(stderr, "%s\n", wrdexp_buf.we_wordv[0]);
771 	return (savestr(wrdexp_buf.we_wordv[0]));
772 }
773 
774 /*
775  * Take a file name, possibly with shell meta characters
776  * in it and expand it by using "sh -c echo filename"
777  * Return the file name as a dynamic string.
778  * If the name cannot be expanded (for whatever reason)
779  * return the original file name.
780  */
781 
782 char *
783 safeexpand(char name[])
784 {
785 	char *t = expand(name);
786 	return (t) ? t : savestr(name);
787 }
788 
789 /*
790  * Determine the current folder directory name.
791  */
792 int
793 getfold(char *name)
794 {
795 	char *folder;
796 
797 	if ((folder = value("folder")) == NOSTR || *folder == '\0')
798 		return (-1);
799 	/*
800 	 * If name looks like a folder name, don't try
801 	 * to expand it, to prevent infinite recursion.
802 	 */
803 	if (*folder != '+' && (folder = expand(folder)) == NOSTR ||
804 	    *folder == '\0')
805 		return (-1);
806 	if (*folder == '/') {
807 		nstrcpy(name, BUFSIZ, folder);
808 	} else
809 		snprintf(name, BUFSIZ, "%s/%s", homedir, folder);
810 	return (0);
811 }
812 
813 /*
814  * A nicer version of Fdopen, which allows us to fclose
815  * without losing the open file.
816  */
817 
818 FILE *
819 Fdopen(int fildes, char *mode)
820 {
821 	register int f;
822 
823 	f = dup(fildes);
824 	if (f < 0) {
825 		perror("dup");
826 		return (NULL);
827 	}
828 	return (fdopen(f, mode));
829 }
830 
831 /*
832  * return the filename associated with "s".  This function always
833  * returns a non-null string (no error checking is done on the receiving end)
834  */
835 char *
836 Getf(register char *s)
837 {
838 	register char *cp;
839 	static char defbuf[PATHSIZE];
840 
841 	if (((cp = value(s)) != 0) && *cp) {
842 		return (safeexpand(cp));
843 	} else if (strcmp(s, "MBOX") == 0) {
844 		snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
845 			"mbox");
846 		return (defbuf);
847 	} else if (strcmp(s, "DEAD") == 0) {
848 		snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
849 			"dead.letter");
850 		return (defbuf);
851 	} else if (strcmp(s, "MAILRC") == 0) {
852 		snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"),
853 			".mailrc");
854 		return (defbuf);
855 	} else if (strcmp(s, "HOME") == 0) {
856 		/* no recursion allowed! */
857 		return (".");
858 	}
859 	return ("DEAD");	/* "cannot happen" */
860 }
861