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