11558Srgrimes/*
21558Srgrimes * Copyright (c) 1980, 1993
31558Srgrimes *	The Regents of the University of California.  All rights reserved.
41558Srgrimes *
51558Srgrimes * Redistribution and use in source and binary forms, with or without
61558Srgrimes * modification, are permitted provided that the following conditions
71558Srgrimes * are met:
81558Srgrimes * 1. Redistributions of source code must retain the above copyright
91558Srgrimes *    notice, this list of conditions and the following disclaimer.
101558Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111558Srgrimes *    notice, this list of conditions and the following disclaimer in the
121558Srgrimes *    documentation and/or other materials provided with the distribution.
131558Srgrimes * 4. Neither the name of the University nor the names of its contributors
141558Srgrimes *    may be used to endorse or promote products derived from this software
151558Srgrimes *    without specific prior written permission.
161558Srgrimes *
171558Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181558Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191558Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201558Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211558Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221558Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231558Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241558Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251558Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261558Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271558Srgrimes * SUCH DAMAGE.
281558Srgrimes */
291558Srgrimes
30114589Sobrien#ifndef lint
311558Srgrimes#if 0
3238040Scharnierstatic char sccsid[] = "@(#)fio.c	8.2 (Berkeley) 4/20/95";
331558Srgrimes#endif
341558Srgrimes#endif /* not lint */
351558Srgrimes#include <sys/cdefs.h>
361558Srgrimes__FBSDID("$FreeBSD$");
371558Srgrimes
381558Srgrimes#include "rcv.h"
39114589Sobrien#include <sys/file.h>
4038040Scharnier#include <sys/wait.h>
41114589Sobrien
42114589Sobrien#include <unistd.h>
431558Srgrimes#include <paths.h>
441558Srgrimes#include <errno.h>
45102231Strhodes#include "extern.h"
461558Srgrimes
471558Srgrimes/*
4842873Sluoqi * Mail -- a mail program
4996478Sphk *
501558Srgrimes * File I/O.
511558Srgrimes */
5298542Smckusick
5398542Smckusickextern int wait_status;
541558Srgrimes
55207141Sjeff/*
561558Srgrimes * Set up the input pointers while copying the mail file into /tmp.
57110174Sgordon */
581558Srgrimesvoid
591558Srgrimessetptr(FILE *ibuf, off_t offset)
601558Srgrimes{
61109597Sjmallett	int c, count;
6238040Scharnier	char *cp, *cp2;
631558Srgrimes	struct message this;
641558Srgrimes	FILE *mestmp;
65207141Sjeff	int maybe, inhead;
6658047Ssheldonh	char linebuf[LINESIZE], pathbuf[PATHSIZE];
671558Srgrimes	int omsgCount;
681558Srgrimes
691558Srgrimes	/* Get temporary file. */
701558Srgrimes	(void)snprintf(pathbuf, sizeof(pathbuf), "%s/mail.XXXXXXXXXX", tmpdir);
711558Srgrimes	if ((c = mkstemp(pathbuf)) == -1 || (mestmp = Fdopen(c, "r+")) == NULL)
72109597Sjmallett		err(1, "can't open %s", pathbuf);
73109597Sjmallett	(void)rm(pathbuf);
741558Srgrimes
7592883Simp	if (offset == 0) {
7692883Simp		 msgCount = 0;
77207141Sjeff	} else {
78207141Sjeff		/* Seek into the file to get to the new messages */
79207141Sjeff		(void)fseeko(ibuf, offset, SEEK_SET);
801558Srgrimes		/*
811558Srgrimes		 * We need to make "offset" a pointer to the end of
82109597Sjmallett		 * the temp file that has the copy of the mail file.
831558Srgrimes		 * If any messages have been edited, this will be
84207141Sjeff		 * different from the offset into the mail file.
85109963Sjmallett		 */
8679750Sdd		(void)fseeko(otf, (off_t)0, SEEK_END);
87127455Sbde		offset = ftello(otf);
88207141Sjeff	}
89207141Sjeff	omsgCount = msgCount;
90207141Sjeff	maybe = 1;
91127441Sbde	inhead = 0;
9279750Sdd	this.m_flag = MUSED|MNEW;
9342873Sluoqi	this.m_size = 0;
9442873Sluoqi	this.m_lines = 0;
951558Srgrimes	this.m_block = 0;
96127441Sbde	this.m_offset = 0;
97127441Sbde	for (;;) {
98207141Sjeff		if (fgets(linebuf, sizeof(linebuf), ibuf) == NULL) {
99207141Sjeff			if (append(&this, mestmp))
100207141Sjeff				errx(1, "temporary file");
101207141Sjeff			makemessage(mestmp, omsgCount);
102127455Sbde			return;
103127441Sbde		}
104207141Sjeff		count = strlen(linebuf);
105127441Sbde		/*
106127441Sbde		 * Transforms lines ending in <CR><LF> to just <LF>.
107127441Sbde		 * This allows mail to be able to read Eudora mailboxes.
108127441Sbde		 */
109127441Sbde		if (count >= 2 && linebuf[count - 1] == '\n' &&
110127441Sbde		    linebuf[count - 2] == '\r') {
111127441Sbde			count--;
112127441Sbde			linebuf[count - 1] = '\n';
113127441Sbde		}
114200796Strasz
115127441Sbde		(void)fwrite(linebuf, sizeof(*linebuf), count, otf);
116127441Sbde		if (ferror(otf))
117127441Sbde			errx(1, "/tmp");
118127441Sbde		if (count)
119127441Sbde			linebuf[count - 1] = '\0';
120127441Sbde		if (maybe && linebuf[0] == 'F' && ishead(linebuf)) {
121127441Sbde			msgCount++;
122127441Sbde			if (append(&this, mestmp))
123127441Sbde				errx(1, "temporary file");
124127441Sbde			this.m_flag = MUSED|MNEW;
125127441Sbde			this.m_size = 0;
126127441Sbde			this.m_lines = 0;
127127441Sbde			this.m_block = blockof(offset);
128127441Sbde			this.m_offset = boffsetof(offset);
129127441Sbde			inhead = 1;
130127441Sbde		} else if (linebuf[0] == 0) {
131127441Sbde			inhead = 0;
132127441Sbde		} else if (inhead) {
133127441Sbde			for (cp = linebuf, cp2 = "status";; cp++) {
134127441Sbde				if ((c = *cp2++) == '\0') {
135127441Sbde					while (isspace((unsigned char)*cp++))
136127441Sbde						;
137127441Sbde					if (cp[-1] != ':')
138127441Sbde						break;
139127441Sbde					while ((c = *cp++) != '\0')
140127441Sbde						if (c == 'R')
141127441Sbde							this.m_flag |= MREAD;
142127441Sbde						else if (c == 'O')
143127441Sbde							this.m_flag &= ~MNEW;
144207141Sjeff					inhead = 0;
145207141Sjeff					break;
146207141Sjeff				}
147207141Sjeff				if (*cp != c && *cp != toupper((unsigned char)c))
148207141Sjeff					break;
149207141Sjeff			}
150207141Sjeff		}
151207141Sjeff		offset += count;
152207141Sjeff		this.m_size += count;
153207141Sjeff		this.m_lines++;
154207141Sjeff		maybe = linebuf[0] == 0;
155207141Sjeff	}
156163842Spjd}
157163842Spjd
158163842Spjd/*
159163842Spjd * Drop the passed line onto the passed output buffer.
160163842Spjd * If a write error occurs, return -1, else the count of
161163842Spjd * characters written, including the newline if requested.
162163842Spjd */
163163842Spjdint
164163842Spjdputline(FILE *obuf, char *linebuf, int outlf)
165163842Spjd{
166163842Spjd	int c;
167163842Spjd
168163842Spjd	c = strlen(linebuf);
169127441Sbde	(void)fwrite(linebuf, sizeof(*linebuf), c, obuf);
170127441Sbde	if (outlf) {
171127441Sbde		fprintf(obuf, "\n");
172127441Sbde		c++;
173127441Sbde	}
174127441Sbde	if (ferror(obuf))
175127441Sbde		return (-1);
176127441Sbde	return (c);
177127441Sbde}
178127441Sbde
179127441Sbde/*
180127441Sbde * Read up a line from the specified input into the line
181127441Sbde * buffer.  Return the number of characters read.  Do not
182127441Sbde * include the newline (or carriage return) at the end.
183127441Sbde */
184127441Sbdeint
185127441Sbdereadline(FILE *ibuf, char *linebuf, int linesize)
186127441Sbde{
187127441Sbde	int n;
188127441Sbde
189127441Sbde	clearerr(ibuf);
190127441Sbde	if (fgets(linebuf, linesize, ibuf) == NULL)
191127441Sbde		return (-1);
192127441Sbde	n = strlen(linebuf);
193127441Sbde	if (n > 0 && linebuf[n - 1] == '\n')
194127441Sbde		linebuf[--n] = '\0';
195127441Sbde	if (n > 0 && linebuf[n - 1] == '\r')
196127441Sbde		linebuf[--n] = '\0';
197127441Sbde	return (n);
198127441Sbde}
199127441Sbde
200127441Sbde/*
201127441Sbde * Return a file buffer all ready to read up the
202127441Sbde * passed message pointer.
203127441Sbde */
204127441SbdeFILE *
205127441Sbdesetinput(struct message *mp)
206127441Sbde{
207127441Sbde
208200796Strasz	(void)fflush(otf);
209200796Strasz	if (fseeko(itf,
210200796Strasz		   positionof(mp->m_block, mp->m_offset), SEEK_SET) < 0)
211200796Strasz		err(1, "fseeko");
212200796Strasz	return (itf);
213200796Strasz}
214200796Strasz
215200796Strasz/*
216200796Strasz * Take the data out of the passed ghost file and toss it into
217200796Strasz * a dynamically allocated message structure.
218200796Strasz */
219200796Straszvoid
220127441Sbdemakemessage(FILE *f, int omsgCount)
221127441Sbde{
222127441Sbde	size_t size;
223127441Sbde	struct message *nmessage;
224127455Sbde
225127455Sbde	size = (msgCount + 1) * sizeof(struct message);
226127441Sbde	nmessage = (struct message *)realloc(message, size);
227127441Sbde	if (nmessage == NULL)
228127441Sbde		errx(1, "Insufficient memory for %d messages\n",
229127441Sbde		    msgCount);
230127441Sbde	if (omsgCount == 0 || message == NULL)
231127441Sbde		dot = nmessage;
232127441Sbde	else
233127441Sbde		dot = nmessage + (dot - message);
234127441Sbde	message = nmessage;
235127455Sbde	size -= (omsgCount + 1) * sizeof(struct message);
236127441Sbde	(void)fflush(f);
237127455Sbde	(void)lseek(fileno(f), (off_t)sizeof(*message), 0);
238127441Sbde	if (read(fileno(f), (char *)&message[omsgCount], size) != size)
239127441Sbde		errx(1, "Message temporary file corrupted");
240127441Sbde	message[msgCount].m_size = 0;
241127441Sbde	message[msgCount].m_lines = 0;
242127441Sbde	(void)Fclose(f);
243127441Sbde}
244127441Sbde
245127441Sbde/*
246127441Sbde * Append the passed message descriptor onto the temp file.
247127441Sbde * If the write fails, return 1, else 0
248127441Sbde */
249127441Sbdeint
250127441Sbdeappend(struct message *mp, FILE *f)
251127441Sbde{
252127441Sbde	return (fwrite((char *)mp, sizeof(*mp), 1, f) != 1);
253127441Sbde}
254127441Sbde
255127441Sbde/*
256127441Sbde * Delete a file, but only if the file is a plain file.
257127441Sbde */
258127441Sbdeint
259127441Sbderm(char *name)
260127441Sbde{
261207141Sjeff	struct stat sb;
262207141Sjeff
263207141Sjeff	if (stat(name, &sb) < 0)
264207141Sjeff		return (-1);
265207141Sjeff	if (!S_ISREG(sb.st_mode)) {
266207141Sjeff		errno = EISDIR;
267207141Sjeff		return (-1);
268207141Sjeff	}
269207141Sjeff	return (unlink(name));
270207141Sjeff}
271127441Sbde
272127441Sbdestatic int sigdepth;		/* depth of holdsigs() */
273105120Srwatsonstatic sigset_t nset, oset;
27469314Scharnier/*
27569314Scharnier * Hold signals SIGHUP, SIGINT, and SIGQUIT.
27669314Scharnier */
277127441Sbdevoid
27869314Scharnierholdsigs(void)
279109963Sjmallett{
280109963Sjmallett
281109963Sjmallett	if (sigdepth++ == 0) {
282109963Sjmallett		(void)sigemptyset(&nset);
283207421Sjeff		(void)sigaddset(&nset, SIGHUP);
284207421Sjeff		(void)sigaddset(&nset, SIGINT);
285207421Sjeff		(void)sigaddset(&nset, SIGQUIT);
28669829Scharnier		(void)sigprocmask(SIG_BLOCK, &nset, &oset);
28769829Scharnier	}
28869829Scharnier}
28969829Scharnier
29069829Scharnier/*
29169829Scharnier * Release signals SIGHUP, SIGINT, and SIGQUIT.
29269829Scharnier */
293110174Sgordonvoid
294110174Sgordonrelsesigs(void)
295110174Sgordon{
296110174Sgordon
297105120Srwatson	if (--sigdepth == 0)
298200796Strasz		(void)sigprocmask(SIG_SETMASK, &oset, NULL);
299105120Srwatson}
300105120Srwatson
301105120Srwatson/*
302200796Strasz * Determine the size of the file possessed by
303200796Strasz * the passed buffer.
304200796Strasz */
305105120Srwatsonoff_t
306105120Srwatsonfsize(FILE *iob)
307105120Srwatson{
308105120Srwatson	struct stat sbuf;
309105120Srwatson
310105120Srwatson	if (fstat(fileno(iob), &sbuf) < 0)
311105120Srwatson		return (0);
312105120Srwatson	return (sbuf.st_size);
313105120Srwatson}
314105120Srwatson
315105120Srwatson/*
316105206Srwatson * Evaluate the string given as a new mailbox name.
317105120Srwatson * Supported meta characters:
318105120Srwatson *	%	for my system mail box
319105120Srwatson *	%user	for user's system mail box
32069829Scharnier *	#	for previous file
32169829Scharnier *	&	invoker's mbox file
322127455Sbde *	+file	file in folder directory
32369829Scharnier *	any shell meta character
32469829Scharnier * Return the file name as a dynamic string.
32569829Scharnier */
326127455Sbdechar *
32769829Scharnierexpand(char *name)
32869829Scharnier{
32969829Scharnier	char xname[PATHSIZE];
33075377Smckusick	char cmdbuf[PATHSIZE];		/* also used for file names */
33175377Smckusick	int pid, l;
332203769Smckusick	char *cp, *sh;
33375377Smckusick	int pivec[2];
33475377Smckusick	struct stat sbuf;
33575377Smckusick
33675377Smckusick	/*
33775377Smckusick	 * The order of evaluation is "%" and "#" expand into constants.
33875377Smckusick	 * "&" can expand into "+".  "+" can expand into shell meta characters.
33975377Smckusick	 * Shell meta characters expand into constants.
34075377Smckusick	 * This way, we make no recursive expansion.
341207141Sjeff	 */
342207141Sjeff	switch (*name) {
343207141Sjeff	case '%':
344207141Sjeff		findmail(name[1] ? name + 1 : myname, xname, sizeof(xname));
345207141Sjeff		return (savestr(xname));
346207141Sjeff	case '#':
347207141Sjeff		if (name[1] != 0)
348207141Sjeff			break;
349207141Sjeff		if (prevfile[0] == 0) {
350207141Sjeff			printf("No previous file\n");
351207141Sjeff			return (NULL);
352207141Sjeff		}
353207141Sjeff		return (savestr(prevfile));
354207141Sjeff	case '&':
355207141Sjeff		if (name[1] == 0 && (name = value("MBOX")) == NULL)
356207141Sjeff			name = "~/mbox";
357207141Sjeff		/* fall through */
358207141Sjeff	}
359207141Sjeff	if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) {
360207141Sjeff		(void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1);
361207141Sjeff		name = savestr(xname);
362207141Sjeff	}
363207141Sjeff	/* catch the most common shell meta character */
364207141Sjeff	if (name[0] == '~' && homedir != NULL &&
365207141Sjeff	    (name[1] == '/' || name[1] == '\0')) {
366207141Sjeff		(void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1);
367207141Sjeff		name = savestr(xname);
368163842Spjd	}
369163842Spjd	if (!strpbrk(name, "~{[*?$`'\"\\"))
370163842Spjd		return (name);
371163842Spjd	if (pipe(pivec) < 0) {
372163842Spjd		warn("pipe");
373163842Spjd		return (name);
374163842Spjd	}
375163842Spjd	(void)snprintf(cmdbuf, sizeof(cmdbuf), "echo %s", name);
376163842Spjd	if ((sh = value("SHELL")) == NULL)
377163842Spjd		sh = _PATH_CSHELL;
378163842Spjd	pid = start_command(sh, 0, -1, pivec[1], "-c", cmdbuf, NULL);
379163842Spjd	if (pid < 0) {
380163842Spjd		(void)close(pivec[0]);
381163842Spjd		(void)close(pivec[1]);
382163842Spjd		return (NULL);
383163842Spjd	}
384163842Spjd	(void)close(pivec[1]);
385163842Spjd	l = read(pivec[0], xname, BUFSIZ);
386163842Spjd	(void)close(pivec[0]);
387163842Spjd	if (wait_child(pid) < 0 && WIFSIGNALED(wait_status) &&
388105120Srwatson	    WTERMSIG(wait_status) != SIGPIPE) {
389105120Srwatson		fprintf(stderr, "\"%s\": Expansion failed.\n", name);
390105120Srwatson		return (NULL);
391105120Srwatson	}
392105120Srwatson	if (l < 0) {
393105120Srwatson		warn("read");
394105120Srwatson		return (NULL);
395105120Srwatson	}
396105120Srwatson	if (l == 0) {
397105120Srwatson		fprintf(stderr, "\"%s\": No match.\n", name);
398105120Srwatson		return (NULL);
399105120Srwatson	}
400105120Srwatson	if (l == BUFSIZ) {
401105120Srwatson		fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name);
402105120Srwatson		return (NULL);
403105120Srwatson	}
404105206Srwatson	xname[l] = '\0';
405105120Srwatson	for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--)
406105120Srwatson		;
407105120Srwatson	cp[1] = '\0';
40869829Scharnier	if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) {
40969829Scharnier		fprintf(stderr, "\"%s\": Ambiguous.\n", name);
410127455Sbde		return (NULL);
41169829Scharnier	}
41269829Scharnier	return (savestr(xname));
41369829Scharnier}
41469829Scharnier
41569829Scharnier/*
41669829Scharnier * Determine the current folder directory name.
41769829Scharnier */
41869829Scharnierint
41969829Scharniergetfold(char *name, int namelen)
42069829Scharnier{
42169829Scharnier	char *folder;
422200796Strasz	int copylen;
423200796Strasz
424200796Strasz	if ((folder = value("folder")) == NULL)
425200796Strasz		return (-1);
426200796Strasz	if (*folder == '/')
427200796Strasz		copylen = strlcpy(name, folder, namelen);
428200796Strasz	else
429200796Strasz		copylen = snprintf(name, namelen, "%s/%s",
430200796Strasz		    homedir ? homedir : ".", folder);
431200796Strasz	return (copylen < 0 || copylen >= namelen ? (-1) : (0));
432200796Strasz}
433200796Strasz
434200796Strasz/*
435200796Strasz * Return the name of the dead.letter file.
436200796Strasz */
437200796Straszchar *
438200796Straszgetdeadletter(void)
439200796Strasz{
440200796Strasz	char *cp;
441200796Strasz
442200796Strasz	if ((cp = value("DEAD")) == NULL || (cp = expand(cp)) == NULL)
443200796Strasz		cp = expand("~/dead.letter");
444200796Strasz	else if (*cp != '/') {
44569829Scharnier		char buf[PATHSIZE];
44669829Scharnier
44769829Scharnier		(void)snprintf(buf, sizeof(buf), "~/%s", cp);
448127455Sbde		cp = expand(buf);
44969829Scharnier	}
450127455Sbde	return (cp);
45175498Smckusick}
45275498Smckusick