xref: /openbsd-src/usr.bin/mail/fio.c (revision 3a3fbb3f2e2521ab7c4a56b7ff7462ebd9095ec5)
1 /*	$OpenBSD: fio.c,v 1.21 2001/12/18 16:55:06 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 const char sccsid[] = "@(#)fio.c	8.2 (Berkeley) 4/20/95";
40 #else
41 static const char rcsid[] = "$OpenBSD: fio.c,v 1.21 2001/12/18 16:55:06 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 static volatile sig_atomic_t fiosignal;
61 
62 /*
63  * Wrapper for read() to catch EINTR.
64  */
65 ssize_t
66 myread(int fd, char *buf, int len)
67 {
68 	ssize_t nread;
69 
70 	while ((nread = read(fd, buf, len)) == -1 && errno == EINTR)
71 		;
72 	return(nread);
73 }
74 
75 /*
76  * Set up the input pointers while copying the mail file into /tmp.
77  */
78 void
79 setptr(FILE *ibuf, off_t offset)
80 {
81 	int c, count;
82 	char *cp, *cp2;
83 	struct message this;
84 	FILE *mestmp;
85 	int maybe, inhead, omsgCount;
86 	char linebuf[LINESIZE], pathbuf[PATHSIZE];
87 
88 	/* Get temporary file. */
89 	(void)snprintf(pathbuf, sizeof(pathbuf), "%s/mail.XXXXXXXXXX", tmpdir);
90 	if ((c = mkstemp(pathbuf)) == -1 || (mestmp = Fdopen(c, "r+")) == NULL)
91 		err(1, "can't open %s", pathbuf);
92 	(void)rm(pathbuf);
93 
94 	if (offset == 0) {
95 		msgCount = 0;
96 	} else {
97 		/* Seek into the file to get to the new messages */
98 		(void)fseek(ibuf, offset, 0);
99 		/*
100 		 * We need to make "offset" a pointer to the end of
101 		 * the temp file that has the copy of the mail file.
102 		 * If any messages have been edited, this will be
103 		 * different from the offset into the mail file.
104 		 */
105 		(void)fseek(otf, 0L, SEEK_END);
106 		offset = ftell(otf);
107 	}
108 	omsgCount = msgCount;
109 	maybe = 1;
110 	inhead = 0;
111 	this.m_flag = MUSED|MNEW;
112 	this.m_size = 0;
113 	this.m_lines = 0;
114 	this.m_block = 0;
115 	this.m_offset = 0;
116 	for (;;) {
117 		if (fgets(linebuf, sizeof(linebuf), ibuf) == NULL) {
118 			if (append(&this, mestmp))
119 				err(1, "temporary file");
120 			makemessage(mestmp, omsgCount);
121 			return;
122 		}
123 		count = strlen(linebuf);
124 		/*
125 		 * Transforms lines ending in <CR><LF> to just <LF>.
126 		 * This allows mail to be able to read Eudora mailboxes
127 		 * that reside on a DOS partition.
128 		 */
129 		if (count >= 2 && linebuf[count-1] == '\n' &&
130 		    linebuf[count - 2] == '\r')
131 			linebuf[count - 2] = linebuf[--count];
132 
133 		(void)fwrite(linebuf, sizeof(*linebuf), count, otf);
134 		if (ferror(otf))
135 			err(1, "/tmp");
136 		if (count)
137 			linebuf[count - 1] = '\0';
138 		if (maybe && linebuf[0] == 'F' && ishead(linebuf)) {
139 			msgCount++;
140 			if (append(&this, mestmp))
141 				err(1, "temporary file");
142 			this.m_flag = MUSED|MNEW;
143 			this.m_size = 0;
144 			this.m_lines = 0;
145 			this.m_block = blockof(offset);
146 			this.m_offset = offsetof(offset);
147 			inhead = 1;
148 		} else if (linebuf[0] == 0) {
149 			inhead = 0;
150 		} else if (inhead) {
151 			for (cp = linebuf, cp2 = "status";; cp++) {
152 				if ((c = *cp2++) == 0) {
153 					while (isspace(*cp++))
154 						;
155 					if (cp[-1] != ':')
156 						break;
157 					while ((c = *cp++) != '\0')
158 						if (c == 'R')
159 							this.m_flag |= MREAD;
160 						else if (c == 'O')
161 							this.m_flag &= ~MNEW;
162 					inhead = 0;
163 					break;
164 				}
165 				if (*cp != c && *cp != toupper(c))
166 					break;
167 			}
168 		}
169 		offset += count;
170 		this.m_size += count;
171 		this.m_lines++;
172 		maybe = linebuf[0] == 0;
173 	}
174 }
175 
176 /*
177  * Drop the passed line onto the passed output buffer.
178  * If a write error occurs, return -1, else the count of
179  * characters written, including the newline if requested.
180  */
181 int
182 putline(FILE *obuf, char *linebuf, int outlf)
183 {
184 	int c;
185 
186 	c = strlen(linebuf);
187 	(void)fwrite(linebuf, sizeof(*linebuf), c, obuf);
188 	if (outlf) {
189 		(void)putc('\n', obuf);
190 		c++;
191 	}
192 	if (ferror(obuf))
193 		return(-1);
194 	return(c);
195 }
196 
197 /*
198  * Read up a line from the specified input into the line
199  * buffer.  Return the number of characters read.  Do not
200  * include the newline (or carriage return) at the end.
201  */
202 int
203 readline(FILE *ibuf, char *linebuf, int linesize, int *signo)
204 {
205 	struct sigaction act;
206 	struct sigaction savetstp;
207 	struct sigaction savettou;
208 	struct sigaction savettin;
209 	struct sigaction saveint;
210 	struct sigaction savehup;
211 	sigset_t oset;
212 	int n;
213 
214 	/*
215 	 * Setup signal handlers if the caller asked us to catch signals.
216 	 * Note that we do not restart system calls since we need the
217 	 * read to be interuptible.
218 	 */
219 	if (signo) {
220 		fiosignal = 0;
221 		sigemptyset(&act.sa_mask);
222 		act.sa_flags = 0;
223 		act.sa_handler = fioint;
224 		if (sigaction(SIGINT, NULL, &saveint) == 0 &&
225 		    saveint.sa_handler != SIG_IGN) {
226 			(void)sigaction(SIGINT, &act, &saveint);
227 			(void)sigprocmask(SIG_UNBLOCK, &intset, &oset);
228 		}
229 		if (sigaction(SIGHUP, NULL, &savehup) == 0 &&
230 		    savehup.sa_handler != SIG_IGN)
231 			(void)sigaction(SIGHUP, &act, &savehup);
232 		(void)sigaction(SIGTSTP, &act, &savetstp);
233 		(void)sigaction(SIGTTOU, &act, &savettou);
234 		(void)sigaction(SIGTTIN, &act, &savettin);
235 	}
236 
237 	clearerr(ibuf);
238 	if (fgets(linebuf, linesize, ibuf) == NULL) {
239 		if (ferror(ibuf))
240 			clearerr(ibuf);
241 		n = -1;
242 	} else {
243 		n = strlen(linebuf);
244 		if (n > 0 && linebuf[n - 1] == '\n')
245 			linebuf[--n] = '\0';
246 		if (n > 0 && linebuf[n - 1] == '\r')
247 			linebuf[--n] = '\0';
248 	}
249 
250 	if (signo) {
251 		(void)sigprocmask(SIG_SETMASK, &oset, NULL);
252 		(void)sigaction(SIGINT, &saveint, NULL);
253 		(void)sigaction(SIGHUP, &savehup, NULL);
254 		(void)sigaction(SIGTSTP, &savetstp, NULL);
255 		(void)sigaction(SIGTTOU, &savettou, NULL);
256 		(void)sigaction(SIGTTIN, &savettin, NULL);
257 		*signo = fiosignal;
258 	}
259 
260 	return(n);
261 }
262 
263 /*
264  * Return a file buffer all ready to read up the
265  * passed message pointer.
266  */
267 FILE *
268 setinput(struct message *mp)
269 {
270 
271 	fflush(otf);
272 	if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), 0) < 0)
273 		err(1, "fseek");
274 	return(itf);
275 }
276 
277 /*
278  * Take the data out of the passed ghost file and toss it into
279  * a dynamically allocated message structure.
280  */
281 void
282 makemessage(FILE *f, int omsgCount)
283 {
284 	size_t size;
285 	struct message *nmessage;
286 
287 	size = (msgCount + 1) * sizeof(struct message);
288 	nmessage = (struct message *)realloc(message, size);
289 	if (nmessage == 0)
290 		errx(1, "Insufficient memory for %d messages\n",
291 		    msgCount);
292 	if (omsgCount == 0 || message == NULL)
293 		dot = nmessage;
294 	else
295 		dot = nmessage + (dot - message);
296 	message = nmessage;
297 	size -= (omsgCount + 1) * sizeof(struct message);
298 	fflush(f);
299 	(void)lseek(fileno(f), (off_t)sizeof(*message), 0);
300 	if (myread(fileno(f), (void *) &message[omsgCount], size) != size)
301 		errx(1, "Message temporary file corrupted");
302 	message[msgCount].m_size = 0;
303 	message[msgCount].m_lines = 0;
304 	(void)Fclose(f);
305 }
306 
307 /*
308  * Append the passed message descriptor onto the temp file.
309  * If the write fails, return 1, else 0
310  */
311 int
312 append(struct message *mp, FILE *f)
313 {
314 
315 	return(fwrite((char *) mp, sizeof(*mp), 1, f) != 1);
316 }
317 
318 /*
319  * Delete or truncate a file, but only if the file is a plain file.
320  */
321 int
322 rm(char *name)
323 {
324 	struct stat sb;
325 
326 	if (stat(name, &sb) < 0)
327 		return(-1);
328 	if (!S_ISREG(sb.st_mode)) {
329 		errno = EISDIR;
330 		return(-1);
331 	}
332 	if (unlink(name) == -1) {
333 		if (errno == EPERM)
334 			return(truncate(name, 0));
335 		else
336 			return(-1);
337 	}
338 	return(0);
339 }
340 
341 static int sigdepth;		/* depth of holdsigs() */
342 static sigset_t nset, oset;
343 /*
344  * Hold signals SIGHUP, SIGINT, and SIGQUIT.
345  */
346 void
347 holdsigs(void)
348 {
349 
350 	if (sigdepth++ == 0) {
351 		sigemptyset(&nset);
352 		sigaddset(&nset, SIGHUP);
353 		sigaddset(&nset, SIGINT);
354 		sigaddset(&nset, SIGQUIT);
355 		sigprocmask(SIG_BLOCK, &nset, &oset);
356 	}
357 }
358 
359 /*
360  * Release signals SIGHUP, SIGINT, and SIGQUIT.
361  */
362 void
363 relsesigs(void)
364 {
365 
366 	if (--sigdepth == 0)
367 		sigprocmask(SIG_SETMASK, &oset, NULL);
368 }
369 
370 /*
371  * Unblock and ignore a signal
372  */
373 int
374 ignoresig(int sig, struct sigaction *oact, sigset_t *oset)
375 {
376 	struct sigaction act;
377 	sigset_t nset;
378 	int error;
379 
380 	sigemptyset(&act.sa_mask);
381 	act.sa_flags = SA_RESTART;
382 	act.sa_handler = SIG_IGN;
383 	error = sigaction(sig, &act, oact);
384 
385 	if (error == 0) {
386 		sigemptyset(&nset);
387 		sigaddset(&nset, sig);
388 		(void)sigprocmask(SIG_UNBLOCK, &nset, oset);
389 	} else if (oset != NULL)
390 		(void)sigprocmask(SIG_BLOCK, NULL, oset);
391 
392 	return(error);
393 }
394 
395 /*
396  * Determine the size of the file possessed by
397  * the passed buffer.
398  */
399 off_t
400 fsize(FILE *iob)
401 {
402 	struct stat sbuf;
403 
404 	if (fstat(fileno(iob), &sbuf) < 0)
405 		return(0);
406 	return(sbuf.st_size);
407 }
408 
409 /*
410  * Evaluate the string given as a new mailbox name.
411  * Supported meta characters:
412  *	%	for my system mail box
413  *	%user	for user's system mail box
414  *	#	for previous file
415  *	&	invoker's mbox file
416  *	+file	file in folder directory
417  *	any shell meta character
418  * Return the file name as a dynamic string.
419  */
420 char *
421 expand(char *name)
422 {
423 	char xname[PATHSIZE];
424 	char cmdbuf[PATHSIZE];		/* also used for file names */
425 	pid_t pid;
426 	int l;
427 	char *cp, *shell;
428 	int pivec[2];
429 	struct stat sbuf;
430 	extern int wait_status;
431 
432 	/*
433 	 * The order of evaluation is "%" and "#" expand into constants.
434 	 * "&" can expand into "+".  "+" can expand into shell meta characters.
435 	 * Shell meta characters expand into constants.
436 	 * This way, we make no recursive expansion.
437 	 */
438 	switch (*name) {
439 	case '%':
440 		findmail(name[1] ? name + 1 : myname, xname, sizeof(xname));
441 		return(savestr(xname));
442 	case '#':
443 		if (name[1] != 0)
444 			break;
445 		if (prevfile[0] == 0) {
446 			puts("No previous file");
447 			return(NULL);
448 		}
449 		return(savestr(prevfile));
450 	case '&':
451 		if (name[1] == 0 && (name = value("MBOX")) == NULL)
452 			name = "~/mbox";
453 		/* fall through */
454 	}
455 	if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) {
456 		(void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1);
457 		name = savestr(xname);
458 	}
459 	/* catch the most common shell meta character */
460 	if (name[0] == '~' && homedir && (name[1] == '/' || name[1] == '\0')) {
461 		(void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1);
462 		name = savestr(xname);
463 	}
464 	if (strpbrk(name, "~{[*?$`'\"\\") == NULL)
465 		return(name);
466 	/* XXX - just use glob(3) and env expansion instead? */
467 	if (pipe(pivec) < 0) {
468 		warn("pipe");
469 		return(name);
470 	}
471 	(void)snprintf(cmdbuf, sizeof(cmdbuf), "echo %s", name);
472 	shell = value("SHELL");
473 	pid = start_command(shell, 0, -1, pivec[1], "-c", cmdbuf, NULL);
474 	if (pid < 0) {
475 		(void)close(pivec[0]);
476 		(void)close(pivec[1]);
477 		return(NULL);
478 	}
479 	(void)close(pivec[1]);
480 	l = myread(pivec[0], xname, PATHSIZE);
481 	if (l < 0)
482 		warn("read"); /* report error before errno changes */
483 	(void)close(pivec[0]);
484 	if (wait_child(pid) < 0 && WIFSIGNALED(wait_status) &&
485 	    WTERMSIG(wait_status) != SIGPIPE) {
486 		fprintf(stderr, "\"%s\": Expansion failed.\n", name);
487 		return(NULL);
488 	}
489 	if (l < 0)
490 		return(NULL);
491 	if (l == 0) {
492 		fprintf(stderr, "\"%s\": No match.\n", name);
493 		return(NULL);
494 	}
495 	if (l == PATHSIZE) {
496 		fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name);
497 		return(NULL);
498 	}
499 	xname[l] = '\0';
500 	for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--)
501 		;
502 	cp[1] = '\0';
503 	if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) {
504 		fprintf(stderr, "\"%s\": Ambiguous.\n", name);
505 		return(NULL);
506 	}
507 	return(savestr(xname));
508 }
509 
510 /*
511  * Determine the current folder directory name.
512  */
513 int
514 getfold(char *name, int namelen)
515 {
516 	char *folder;
517 
518 	if ((folder = value("folder")) == NULL)
519 		return(-1);
520 	if (*folder == '/')
521 		strlcpy(name, folder, namelen);
522 	else
523 		(void)snprintf(name, namelen, "%s/%s", homedir ? homedir : ".",
524 		    folder);
525 	return(0);
526 }
527 
528 /*
529  * Return the name of the dead.letter file.
530  */
531 char *
532 getdeadletter(void)
533 {
534 	char *cp;
535 
536 	if ((cp = value("DEAD")) == NULL || (cp = expand(cp)) == NULL)
537 		cp = expand("~/dead.letter");
538 	else if (*cp != '/') {
539 		char buf[PATHSIZE];
540 
541 		(void)snprintf(buf, sizeof(buf), "~/%s", cp);
542 		cp = expand(buf);
543 	}
544 	return(cp);
545 }
546 
547 /*
548  * Signal handler used by readline() to catch SIGINT, SIGHUP, SIGTSTP,
549  * SIGTTOU, SIGTTIN.
550  */
551 void
552 fioint(int s)
553 {
554 
555 	fiosignal = s;
556 }
557