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