xref: /openbsd-src/usr.bin/mail/fio.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: fio.c,v 1.31 2008/07/16 14:49:09 martynas 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.31 2008/07/16 14:49:09 martynas 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)fseeko(ibuf, offset, SEEK_SET);
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)fseeko(otf, (off_t)0, 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] = '\n';
128 			linebuf[count - 1] = '\0';
129 			count--;
130 		}
131 
132 		(void)fwrite(linebuf, sizeof(*linebuf), count, otf);
133 		if (ferror(otf))
134 			err(1, "%s", pathbuf);
135 		if (count && linebuf[count - 1] == '\n')
136 			linebuf[count - 1] = '\0';
137 		if (maybe && linebuf[0] == 'F' && ishead(linebuf)) {
138 			msgCount++;
139 			if (append(&this, mestmp))
140 				err(1, "temporary file");
141 			this.m_flag = MUSED|MNEW;
142 			this.m_size = 0;
143 			this.m_lines = 0;
144 			this.m_block = blockof(offset);
145 			this.m_offset = offsetof(offset);
146 			inhead = 1;
147 		} else if (linebuf[0] == 0) {
148 			inhead = 0;
149 		} else if (inhead) {
150 			for (cp = linebuf, cp2 = "status";; cp++) {
151 				if ((c = *cp2++) == 0) {
152 					while (isspace(*cp++))
153 						;
154 					if (cp[-1] != ':')
155 						break;
156 					while ((c = *cp++) != '\0')
157 						if (c == 'R')
158 							this.m_flag |= MREAD;
159 						else if (c == 'O')
160 							this.m_flag &= ~MNEW;
161 					inhead = 0;
162 					break;
163 				}
164 				if (*cp != c && *cp != toupper(c))
165 					break;
166 			}
167 		}
168 		offset += count;
169 		this.m_size += count;
170 		this.m_lines++;
171 		maybe = linebuf[0] == 0;
172 	}
173 }
174 
175 /*
176  * Drop the passed line onto the passed output buffer.
177  * If a write error occurs, return -1, else the count of
178  * characters written, including the newline if requested.
179  */
180 int
181 putline(FILE *obuf, char *linebuf, int outlf)
182 {
183 	int c;
184 
185 	c = strlen(linebuf);
186 	(void)fwrite(linebuf, sizeof(*linebuf), c, obuf);
187 	if (outlf) {
188 		(void)putc('\n', obuf);
189 		c++;
190 	}
191 	if (ferror(obuf))
192 		return(-1);
193 	return(c);
194 }
195 
196 /*
197  * Read up a line from the specified input into the line
198  * buffer.  Return the number of characters read.  Do not
199  * include the newline (or carriage return) at the end.
200  */
201 int
202 readline(FILE *ibuf, char *linebuf, int linesize, int *signo)
203 {
204 	struct sigaction act;
205 	struct sigaction savetstp;
206 	struct sigaction savettou;
207 	struct sigaction savettin;
208 	struct sigaction saveint;
209 	struct sigaction savehup;
210 	sigset_t oset;
211 	int n;
212 
213 	/*
214 	 * Setup signal handlers if the caller asked us to catch signals.
215 	 * Note that we do not restart system calls since we need the
216 	 * read to be interruptible.
217 	 */
218 	if (signo) {
219 		fiosignal = 0;
220 		sigemptyset(&act.sa_mask);
221 		act.sa_flags = 0;
222 		act.sa_handler = fioint;
223 		if (sigaction(SIGINT, NULL, &saveint) == 0 &&
224 		    saveint.sa_handler != SIG_IGN) {
225 			(void)sigaction(SIGINT, &act, &saveint);
226 			(void)sigprocmask(SIG_UNBLOCK, &intset, &oset);
227 		}
228 		if (sigaction(SIGHUP, NULL, &savehup) == 0 &&
229 		    savehup.sa_handler != SIG_IGN)
230 			(void)sigaction(SIGHUP, &act, &savehup);
231 		(void)sigaction(SIGTSTP, &act, &savetstp);
232 		(void)sigaction(SIGTTOU, &act, &savettou);
233 		(void)sigaction(SIGTTIN, &act, &savettin);
234 	}
235 
236 	clearerr(ibuf);
237 	if (fgets(linebuf, linesize, ibuf) == NULL) {
238 		if (ferror(ibuf))
239 			clearerr(ibuf);
240 		n = -1;
241 	} else {
242 		n = strlen(linebuf);
243 		if (n > 0 && linebuf[n - 1] == '\n')
244 			linebuf[--n] = '\0';
245 		if (n > 0 && linebuf[n - 1] == '\r')
246 			linebuf[--n] = '\0';
247 	}
248 
249 	if (signo) {
250 		(void)sigprocmask(SIG_SETMASK, &oset, NULL);
251 		(void)sigaction(SIGINT, &saveint, NULL);
252 		(void)sigaction(SIGHUP, &savehup, NULL);
253 		(void)sigaction(SIGTSTP, &savetstp, NULL);
254 		(void)sigaction(SIGTTOU, &savettou, NULL);
255 		(void)sigaction(SIGTTIN, &savettin, NULL);
256 		*signo = fiosignal;
257 	}
258 
259 	return(n);
260 }
261 
262 /*
263  * Return a file buffer all ready to read up the
264  * passed message pointer.
265  */
266 FILE *
267 setinput(struct message *mp)
268 {
269 
270 	fflush(otf);
271 	if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), SEEK_SET)
272 	    < 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",
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), SEEK_SET);
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, (off_t)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