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