1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1980, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include "rcv.h"
33#include <sys/file.h>
34#include <sys/wait.h>
35
36#include <unistd.h>
37#include <paths.h>
38#include <errno.h>
39#include "extern.h"
40
41/*
42 * Mail -- a mail program
43 *
44 * File I/O.
45 */
46
47extern int wait_status;
48
49/*
50 * Set up the input pointers while copying the mail file into /tmp.
51 */
52void
53setptr(FILE *ibuf, off_t offset)
54{
55	int c, count;
56	char *cp, *cp2;
57	struct message this;
58	FILE *mestmp;
59	int maybe, inhead;
60	char linebuf[LINESIZE], pathbuf[PATHSIZE];
61	int omsgCount;
62
63	/* Get temporary file. */
64	(void)snprintf(pathbuf, sizeof(pathbuf), "%s/mail.XXXXXXXXXX", tmpdir);
65	if ((c = mkstemp(pathbuf)) == -1 || (mestmp = Fdopen(c, "r+")) == NULL)
66		err(1, "can't open %s", pathbuf);
67	(void)rm(pathbuf);
68
69	if (offset == 0) {
70		 msgCount = 0;
71	} else {
72		/* Seek into the file to get to the new messages */
73		(void)fseeko(ibuf, offset, SEEK_SET);
74		/*
75		 * We need to make "offset" a pointer to the end of
76		 * the temp file that has the copy of the mail file.
77		 * If any messages have been edited, this will be
78		 * different from the offset into the mail file.
79		 */
80		(void)fseeko(otf, (off_t)0, SEEK_END);
81		offset = ftello(otf);
82	}
83	omsgCount = msgCount;
84	maybe = 1;
85	inhead = 0;
86	this.m_flag = MUSED|MNEW;
87	this.m_size = 0;
88	this.m_lines = 0;
89	this.m_block = 0;
90	this.m_offset = 0;
91	for (;;) {
92		if (fgets(linebuf, sizeof(linebuf), ibuf) == NULL) {
93			if (append(&this, mestmp))
94				errx(1, "temporary file");
95			makemessage(mestmp, omsgCount);
96			return;
97		}
98		count = strlen(linebuf);
99		/*
100		 * Transforms lines ending in <CR><LF> to just <LF>.
101		 * This allows mail to be able to read Eudora mailboxes.
102		 */
103		if (count >= 2 && linebuf[count - 1] == '\n' &&
104		    linebuf[count - 2] == '\r') {
105			count--;
106			linebuf[count - 1] = '\n';
107		}
108
109		(void)fwrite(linebuf, sizeof(*linebuf), count, otf);
110		if (ferror(otf))
111			errx(1, "/tmp");
112		if (count)
113			linebuf[count - 1] = '\0';
114		if (maybe && linebuf[0] == 'F' && ishead(linebuf)) {
115			msgCount++;
116			if (append(&this, mestmp))
117				errx(1, "temporary file");
118			this.m_flag = MUSED|MNEW;
119			this.m_size = 0;
120			this.m_lines = 0;
121			this.m_block = blockof(offset);
122			this.m_offset = boffsetof(offset);
123			inhead = 1;
124		} else if (linebuf[0] == 0) {
125			inhead = 0;
126		} else if (inhead) {
127			for (cp = linebuf, cp2 = "status";; cp++) {
128				if ((c = *cp2++) == '\0') {
129					while (isspace((unsigned char)*cp++))
130						;
131					if (cp[-1] != ':')
132						break;
133					while ((c = *cp++) != '\0')
134						if (c == 'R')
135							this.m_flag |= MREAD;
136						else if (c == 'O')
137							this.m_flag &= ~MNEW;
138					inhead = 0;
139					break;
140				}
141				if (*cp != c && *cp != toupper((unsigned char)c))
142					break;
143			}
144		}
145		offset += count;
146		this.m_size += count;
147		this.m_lines++;
148		maybe = linebuf[0] == 0;
149	}
150}
151
152/*
153 * Drop the passed line onto the passed output buffer.
154 * If a write error occurs, return -1, else the count of
155 * characters written, including the newline if requested.
156 */
157int
158putline(FILE *obuf, char *linebuf, int outlf)
159{
160	int c;
161
162	c = strlen(linebuf);
163	(void)fwrite(linebuf, sizeof(*linebuf), c, obuf);
164	if (outlf) {
165		fprintf(obuf, "\n");
166		c++;
167	}
168	if (ferror(obuf))
169		return (-1);
170	return (c);
171}
172
173/*
174 * Read up a line from the specified input into the line
175 * buffer.  Return the number of characters read.  Do not
176 * include the newline (or carriage return) at the end.
177 */
178int
179readline(FILE *ibuf, char *linebuf, int linesize)
180{
181	int n;
182
183	clearerr(ibuf);
184	if (fgets(linebuf, linesize, ibuf) == NULL)
185		return (-1);
186	n = strlen(linebuf);
187	if (n > 0 && linebuf[n - 1] == '\n')
188		linebuf[--n] = '\0';
189	if (n > 0 && linebuf[n - 1] == '\r')
190		linebuf[--n] = '\0';
191	return (n);
192}
193
194/*
195 * Return a file buffer all ready to read up the
196 * passed message pointer.
197 */
198FILE *
199setinput(struct message *mp)
200{
201
202	(void)fflush(otf);
203	if (fseeko(itf,
204		   positionof(mp->m_block, mp->m_offset), SEEK_SET) < 0)
205		err(1, "fseeko");
206	return (itf);
207}
208
209/*
210 * Take the data out of the passed ghost file and toss it into
211 * a dynamically allocated message structure.
212 */
213void
214makemessage(FILE *f, int omsgCount)
215{
216	size_t size;
217	struct message *nmessage;
218
219	size = (msgCount + 1) * sizeof(struct message);
220	nmessage = (struct message *)realloc(message, size);
221	if (nmessage == NULL)
222		errx(1, "Insufficient memory for %d messages\n",
223		    msgCount);
224	if (omsgCount == 0 || message == NULL)
225		dot = nmessage;
226	else
227		dot = nmessage + (dot - message);
228	message = nmessage;
229	size -= (omsgCount + 1) * sizeof(struct message);
230	(void)fflush(f);
231	(void)lseek(fileno(f), (off_t)sizeof(*message), 0);
232	if (read(fileno(f), (void *)&message[omsgCount], size) != size)
233		errx(1, "Message temporary file corrupted");
234	message[msgCount].m_size = 0;
235	message[msgCount].m_lines = 0;
236	(void)Fclose(f);
237}
238
239/*
240 * Append the passed message descriptor onto the temp file.
241 * If the write fails, return 1, else 0
242 */
243int
244append(struct message *mp, FILE *f)
245{
246	return (fwrite((char *)mp, sizeof(*mp), 1, f) != 1);
247}
248
249/*
250 * Delete a file, but only if the file is a plain file.
251 */
252int
253rm(char *name)
254{
255	struct stat sb;
256
257	if (stat(name, &sb) < 0)
258		return (-1);
259	if (!S_ISREG(sb.st_mode)) {
260		errno = EISDIR;
261		return (-1);
262	}
263	return (unlink(name));
264}
265
266static int sigdepth;		/* depth of holdsigs() */
267static sigset_t nset, oset;
268/*
269 * Hold signals SIGHUP, SIGINT, and SIGQUIT.
270 */
271void
272holdsigs(void)
273{
274
275	if (sigdepth++ == 0) {
276		(void)sigemptyset(&nset);
277		(void)sigaddset(&nset, SIGHUP);
278		(void)sigaddset(&nset, SIGINT);
279		(void)sigaddset(&nset, SIGQUIT);
280		(void)sigprocmask(SIG_BLOCK, &nset, &oset);
281	}
282}
283
284/*
285 * Release signals SIGHUP, SIGINT, and SIGQUIT.
286 */
287void
288relsesigs(void)
289{
290
291	if (--sigdepth == 0)
292		(void)sigprocmask(SIG_SETMASK, &oset, NULL);
293}
294
295/*
296 * Determine the size of the file possessed by
297 * the passed buffer.
298 */
299off_t
300fsize(FILE *iob)
301{
302	struct stat sbuf;
303
304	if (fstat(fileno(iob), &sbuf) < 0)
305		return (0);
306	return (sbuf.st_size);
307}
308
309/*
310 * Evaluate the string given as a new mailbox name.
311 * Supported meta characters:
312 *	%	for my system mail box
313 *	%user	for user's system mail box
314 *	#	for previous file
315 *	&	invoker's mbox file
316 *	+file	file in folder directory
317 *	any shell meta character
318 * Return the file name as a dynamic string.
319 */
320char *
321expand(char *name)
322{
323	char xname[PATHSIZE];
324	char cmdbuf[PATHSIZE];		/* also used for file names */
325	int pid, l;
326	char *cp, *sh;
327	int pivec[2];
328	struct stat sbuf;
329
330	/*
331	 * The order of evaluation is "%" and "#" expand into constants.
332	 * "&" can expand into "+".  "+" can expand into shell meta characters.
333	 * Shell meta characters expand into constants.
334	 * This way, we make no recursive expansion.
335	 */
336	switch (*name) {
337	case '%':
338		findmail(name[1] ? name + 1 : myname, xname, sizeof(xname));
339		return (savestr(xname));
340	case '#':
341		if (name[1] != 0)
342			break;
343		if (prevfile[0] == 0) {
344			printf("No previous file\n");
345			return (NULL);
346		}
347		return (savestr(prevfile));
348	case '&':
349		if (name[1] == 0 && (name = value("MBOX")) == NULL)
350			name = "~/mbox";
351		/* fall through */
352	}
353	if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) {
354		(void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1);
355		name = savestr(xname);
356	}
357	/* catch the most common shell meta character */
358	if (name[0] == '~' && homedir != NULL &&
359	    (name[1] == '/' || name[1] == '\0')) {
360		(void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1);
361		name = savestr(xname);
362	}
363	if (!strpbrk(name, "~{[*?$`'\"\\"))
364		return (savestr(name));
365	if (pipe(pivec) < 0) {
366		warn("pipe");
367		return (NULL);
368	}
369	(void)snprintf(cmdbuf, sizeof(cmdbuf), "echo %s", name);
370	if ((sh = value("SHELL")) == NULL)
371		sh = _PATH_CSHELL;
372	pid = start_command(sh, 0, -1, pivec[1], "-c", cmdbuf, NULL);
373	if (pid < 0) {
374		(void)close(pivec[0]);
375		(void)close(pivec[1]);
376		return (NULL);
377	}
378	(void)close(pivec[1]);
379	l = read(pivec[0], xname, BUFSIZ);
380	(void)close(pivec[0]);
381	if (wait_child(pid) < 0 && WIFSIGNALED(wait_status) &&
382	    WTERMSIG(wait_status) != SIGPIPE) {
383		fprintf(stderr, "\"%s\": Expansion failed.\n", name);
384		return (NULL);
385	}
386	if (l < 0) {
387		warn("read");
388		return (NULL);
389	}
390	if (l == 0) {
391		fprintf(stderr, "\"%s\": No match.\n", name);
392		return (NULL);
393	}
394	if (l == BUFSIZ) {
395		fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name);
396		return (NULL);
397	}
398	xname[l] = '\0';
399	for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--)
400		;
401	cp[1] = '\0';
402	if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) {
403		fprintf(stderr, "\"%s\": Ambiguous.\n", name);
404		return (NULL);
405	}
406	return (savestr(xname));
407}
408
409/*
410 * Determine the current folder directory name.
411 */
412int
413getfold(char *name, int namelen)
414{
415	char *folder;
416	int copylen;
417
418	if ((folder = value("folder")) == NULL)
419		return (-1);
420	if (*folder == '/')
421		copylen = strlcpy(name, folder, namelen);
422	else
423		copylen = snprintf(name, namelen, "%s/%s",
424		    homedir ? homedir : ".", folder);
425	return (copylen < 0 || copylen >= namelen ? (-1) : (0));
426}
427
428/*
429 * Return the name of the dead.letter file.
430 */
431char *
432getdeadletter(void)
433{
434	char *cp;
435
436	if ((cp = value("DEAD")) == NULL || (cp = expand(cp)) == NULL)
437		cp = expand("~/dead.letter");
438	else if (*cp != '/') {
439		char buf[PATHSIZE];
440
441		(void)snprintf(buf, sizeof(buf), "~/%s", cp);
442		cp = expand(buf);
443	}
444	return (cp);
445}
446