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