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#if 0
36static char sccsid[] = "@(#)fio.c	8.2 (Berkeley) 4/20/95";
37#endif
38static const char rcsid[] =
39  "$FreeBSD: src/usr.bin/mail/fio.c,v 1.12 2002/06/30 05:25:06 obrien Exp $";
40#endif /* not lint */
41
42#include <sys/cdefs.h>
43
44#include "rcv.h"
45#include <sys/file.h>
46#include <sys/wait.h>
47
48#include <unistd.h>
49#include <paths.h>
50#include <errno.h>
51#include "extern.h"
52
53/*
54 * Mail -- a mail program
55 *
56 * File I/O.
57 */
58
59extern int wait_status;
60
61/*
62 * Set up the input pointers while copying the mail file into /tmp.
63 */
64void
65setptr(ibuf, offset)
66	FILE *ibuf;
67	off_t offset;
68{
69	int c, count;
70	char *cp, *cp2;
71	struct message this;
72	FILE *mestmp;
73	int maybe, inhead;
74	char linebuf[LINESIZE], pathbuf[PATHSIZE];
75	int omsgCount;
76
77	/* Get temporary file. */
78	(void)snprintf(pathbuf, sizeof(pathbuf), "%s/mail.XXXXXXXXXX", tmpdir);
79	if ((c = mkstemp(pathbuf)) == -1 || (mestmp = Fdopen(c, "r+")) == NULL)
80		err(1, "can't open %s", pathbuf);
81	(void)rm(pathbuf);
82
83	if (offset == 0) {
84		 msgCount = 0;
85	} else {
86		/* Seek into the file to get to the new messages */
87		(void)fseeko(ibuf, offset, SEEK_SET);
88		/*
89		 * We need to make "offset" a pointer to the end of
90		 * the temp file that has the copy of the mail file.
91		 * If any messages have been edited, this will be
92		 * different from the offset into the mail file.
93		 */
94		(void)fseeko(otf, (off_t)0, SEEK_END);
95		offset = ftello(otf);
96	}
97	omsgCount = msgCount;
98	maybe = 1;
99	inhead = 0;
100	this.m_flag = MUSED|MNEW;
101	this.m_size = 0;
102	this.m_lines = 0;
103	this.m_block = 0;
104	this.m_offset = 0;
105	for (;;) {
106		if (fgets(linebuf, sizeof(linebuf), ibuf) == NULL) {
107			if (append(&this, mestmp))
108				errx(1, "temporary file");
109			makemessage(mestmp, omsgCount);
110			return;
111		}
112		count = strlen(linebuf);
113		/*
114		 * Transforms lines ending in <CR><LF> to just <LF>.
115		 * This allows mail to be able to read Eudora mailboxes.
116		 */
117		if (count >= 2 && linebuf[count - 1] == '\n' &&
118		    linebuf[count - 2] == '\r') {
119			count--;
120			linebuf[count - 1] = '\n';
121		}
122
123		(void)fwrite(linebuf, sizeof(*linebuf), count, otf);
124		if (ferror(otf))
125			errx(1, "/tmp");
126		if (count)
127			linebuf[count - 1] = '\0';
128		if (maybe && linebuf[0] == 'F' && ishead(linebuf)) {
129			msgCount++;
130			if (append(&this, mestmp))
131				errx(1, "temporary file");
132			this.m_flag = MUSED|MNEW;
133			this.m_size = 0;
134			this.m_lines = 0;
135			this.m_block = blockof(offset);
136			this.m_offset = boffsetof(offset);
137			inhead = 1;
138		} else if (linebuf[0] == 0) {
139			inhead = 0;
140		} else if (inhead) {
141			for (cp = linebuf, cp2 = "status";; cp++) {
142				if ((c = *cp2++) == '\0') {
143					while (isspace((unsigned char)*cp++))
144						;
145					if (cp[-1] != ':')
146						break;
147					while ((c = *cp++) != '\0')
148						if (c == 'R')
149							this.m_flag |= MREAD;
150						else if (c == 'O')
151							this.m_flag &= ~MNEW;
152					inhead = 0;
153					break;
154				}
155				if (*cp != c && *cp != toupper((unsigned char)c))
156					break;
157			}
158		}
159		offset += count;
160		this.m_size += count;
161		this.m_lines++;
162		maybe = linebuf[0] == 0;
163	}
164}
165
166/*
167 * Drop the passed line onto the passed output buffer.
168 * If a write error occurs, return -1, else the count of
169 * characters written, including the newline if requested.
170 */
171int
172putline(obuf, linebuf, outlf)
173	FILE *obuf;
174	char *linebuf;
175	int outlf;
176{
177	int c;
178
179	c = strlen(linebuf);
180	(void)fwrite(linebuf, sizeof(*linebuf), c, obuf);
181	if (outlf) {
182		fprintf(obuf, "\n");
183		c++;
184	}
185	if (ferror(obuf))
186		return (-1);
187	return (c);
188}
189
190/*
191 * Read up a line from the specified input into the line
192 * buffer.  Return the number of characters read.  Do not
193 * include the newline (or carriage return) at the end.
194 */
195int
196readline(ibuf, linebuf, linesize)
197	FILE *ibuf;
198	char *linebuf;
199	int linesize;
200{
201	int n;
202
203	clearerr(ibuf);
204	if (fgets(linebuf, linesize, ibuf) == NULL)
205		return (-1);
206	n = strlen(linebuf);
207	if (n > 0 && linebuf[n - 1] == '\n')
208		linebuf[--n] = '\0';
209	if (n > 0 && linebuf[n - 1] == '\r')
210		linebuf[--n] = '\0';
211	return (n);
212}
213
214/*
215 * Return a file buffer all ready to read up the
216 * passed message pointer.
217 */
218FILE *
219setinput(mp)
220	struct message *mp;
221{
222
223	(void)fflush(otf);
224	if (fseeko(itf,
225		   positionof(mp->m_block, mp->m_offset), SEEK_SET) < 0)
226		err(1, "fseeko");
227	return (itf);
228}
229
230/*
231 * Take the data out of the passed ghost file and toss it into
232 * a dynamically allocated message structure.
233 */
234void
235makemessage(f, omsgCount)
236	FILE *f;
237	int omsgCount;
238{
239	size_t size;
240	struct message *nmessage;
241
242	size = (msgCount + 1) * sizeof(struct message);
243	nmessage = (struct message *)realloc(message, size);
244	if (nmessage == NULL)
245		errx(1, "Insufficient memory for %d messages\n",
246		    msgCount);
247	if (omsgCount == 0 || message == NULL)
248		dot = nmessage;
249	else
250		dot = nmessage + (dot - message);
251	message = nmessage;
252	size -= (omsgCount + 1) * sizeof(struct message);
253	(void)fflush(f);
254	(void)lseek(fileno(f), (off_t)sizeof(*message), 0);
255	if (read(fileno(f), (char *)&message[omsgCount], size) != size)
256		errx(1, "Message temporary file corrupted");
257	message[msgCount].m_size = 0;
258	message[msgCount].m_lines = 0;
259	(void)Fclose(f);
260}
261
262/*
263 * Append the passed message descriptor onto the temp file.
264 * If the write fails, return 1, else 0
265 */
266int
267append(mp, f)
268	struct message *mp;
269	FILE *f;
270{
271	return (fwrite((char *)mp, sizeof(*mp), 1, f) != 1);
272}
273
274/*
275 * Delete a file, but only if the file is a plain file.
276 */
277int
278rm(name)
279	char *name;
280{
281	struct stat sb;
282
283	if (stat(name, &sb) < 0)
284		return (-1);
285	if (!S_ISREG(sb.st_mode)) {
286		errno = EISDIR;
287		return (-1);
288	}
289	return (unlink(name));
290}
291
292static int sigdepth;		/* depth of holdsigs() */
293static sigset_t nset, oset;
294/*
295 * Hold signals SIGHUP, SIGINT, and SIGQUIT.
296 */
297void
298holdsigs()
299{
300
301	if (sigdepth++ == 0) {
302		(void)sigemptyset(&nset);
303		(void)sigaddset(&nset, SIGHUP);
304		(void)sigaddset(&nset, SIGINT);
305		(void)sigaddset(&nset, SIGQUIT);
306		(void)sigprocmask(SIG_BLOCK, &nset, &oset);
307	}
308}
309
310/*
311 * Release signals SIGHUP, SIGINT, and SIGQUIT.
312 */
313void
314relsesigs()
315{
316
317	if (--sigdepth == 0)
318		(void)sigprocmask(SIG_SETMASK, &oset, NULL);
319}
320
321/*
322 * Determine the size of the file possessed by
323 * the passed buffer.
324 */
325off_t
326fsize(iob)
327	FILE *iob;
328{
329	struct stat sbuf;
330
331	if (fstat(fileno(iob), &sbuf) < 0)
332		return (0);
333	return (sbuf.st_size);
334}
335
336/*
337 * Evaluate the string given as a new mailbox name.
338 * Supported meta characters:
339 *	%	for my system mail box
340 *	%user	for user's system mail box
341 *	#	for previous file
342 *	&	invoker's mbox file
343 *	+file	file in folder directory
344 *	any shell meta character
345 * Return the file name as a dynamic string.
346 */
347char *
348expand(name)
349	char *name;
350{
351	char xname[PATHSIZE];
352	char cmdbuf[PATHSIZE];		/* also used for file names */
353	int pid, l;
354	char *cp, *sh;
355	int pivec[2];
356	struct stat sbuf;
357
358	/*
359	 * The order of evaluation is "%" and "#" expand into constants.
360	 * "&" can expand into "+".  "+" can expand into shell meta characters.
361	 * Shell meta characters expand into constants.
362	 * This way, we make no recursive expansion.
363	 */
364	switch (*name) {
365	case '%':
366		findmail(name[1] ? name + 1 : myname, xname, sizeof(xname));
367		return (savestr(xname));
368	case '#':
369		if (name[1] != 0)
370			break;
371		if (prevfile[0] == 0) {
372			printf("No previous file\n");
373			return (NULL);
374		}
375		return (savestr(prevfile));
376	case '&':
377		if (name[1] == 0 && (name = value("MBOX")) == NULL)
378			name = "~/mbox";
379		/* fall through */
380	}
381	if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) {
382		(void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1);
383		name = savestr(xname);
384	}
385	/* catch the most common shell meta character */
386	if (name[0] == '~' && homedir != NULL &&
387	    (name[1] == '/' || name[1] == '\0')) {
388		(void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1);
389		name = savestr(xname);
390	}
391	if (!strpbrk(name, "~{[*?$`'\"\\"))
392		return (name);
393	if (pipe(pivec) < 0) {
394		warn("pipe");
395		return (name);
396	}
397	(void)snprintf(cmdbuf, sizeof(cmdbuf), "echo %s", name);
398	if ((sh = value("SHELL")) == NULL)
399		sh = _PATH_BSHELL;
400	pid = start_command(sh, 0, -1, pivec[1], "-c", cmdbuf, NULL);
401	if (pid < 0) {
402		(void)close(pivec[0]);
403		(void)close(pivec[1]);
404		return (NULL);
405	}
406	(void)close(pivec[1]);
407	l = read(pivec[0], xname, BUFSIZ);
408	(void)close(pivec[0]);
409	if (wait_child(pid) < 0 && WIFSIGNALED(wait_status) &&
410	    WTERMSIG(wait_status) != SIGPIPE) {
411		fprintf(stderr, "\"%s\": Expansion failed.\n", name);
412		return (NULL);
413	}
414	if (l < 0) {
415		warn("read");
416		return (NULL);
417	}
418	if (l == 0) {
419		fprintf(stderr, "\"%s\": No match.\n", name);
420		return (NULL);
421	}
422	if (l == BUFSIZ) {
423		fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name);
424		return (NULL);
425	}
426	xname[l] = '\0';
427	for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--)
428		;
429	cp[1] = '\0';
430	if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) {
431		fprintf(stderr, "\"%s\": Ambiguous.\n", name);
432		return (NULL);
433	}
434	return (savestr(xname));
435}
436
437/*
438 * Determine the current folder directory name.
439 */
440int
441getfold(name, namelen)
442	char *name;
443	int namelen;
444{
445	char *folder;
446	int copylen;
447
448	if ((folder = value("folder")) == NULL)
449		return (-1);
450	if (*folder == '/')
451		copylen = strlcpy(name, folder, namelen);
452	else
453		copylen = snprintf(name, namelen, "%s/%s",
454		    homedir ? homedir : ".", folder);
455	return (copylen < 0 || copylen >= namelen ? (-1) : (0));
456}
457
458/*
459 * Return the name of the dead.letter file.
460 */
461char *
462getdeadletter()
463{
464	char *cp;
465
466	if ((cp = value("DEAD")) == NULL || (cp = expand(cp)) == NULL)
467		cp = expand("~/dead.letter");
468	else if (*cp != '/') {
469		char buf[PATHSIZE];
470
471		(void)snprintf(buf, sizeof(buf), "~/%s", cp);
472		cp = expand(buf);
473	}
474	return (cp);
475}
476