xref: /netbsd-src/usr.bin/mail/fio.c (revision 9c1da17e908379b8a470f1117a6395bd6a0ca559)
1 /*	$NetBSD: fio.c,v 1.25 2005/07/19 23:07:10 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1980, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)fio.c	8.2 (Berkeley) 4/20/95";
36 #else
37 __RCSID("$NetBSD: fio.c,v 1.25 2005/07/19 23:07:10 christos Exp $");
38 #endif
39 #endif /* not lint */
40 
41 #include "rcv.h"
42 #include "extern.h"
43 
44 /*
45  * Mail -- a mail program
46  *
47  * File I/O.
48  */
49 
50 /*
51  * Set up the input pointers while copying the mail file into /tmp.
52  */
53 void
54 setptr(FILE *ibuf, off_t offset)
55 {
56 	int c;
57 	size_t len;
58 	char *cp;
59 	const char *cp2;
60 	struct message this;
61 	FILE *mestmp;
62 	int maybe, inhead;
63 	char linebuf[LINESIZE];
64 	int omsgCount;
65 
66 	/* Get temporary file. */
67 	(void)snprintf(linebuf, LINESIZE, "%s/mail.XXXXXX", tmpdir);
68 	if ((c = mkstemp(linebuf)) == -1 ||
69 	    (mestmp = Fdopen(c, "r+")) == NULL) {
70 		(void)fprintf(stderr, "mail: can't open %s\n", linebuf);
71 		exit(1);
72 	}
73 	(void)unlink(linebuf);
74 
75 	if (offset == 0) {
76 		 msgCount = 0;
77 	} else {
78 		/* Seek into the file to get to the new messages */
79 		(void)fseeko(ibuf, offset, 0);
80 		/*
81 		 * We need to make "offset" a pointer to the end of
82 		 * the temp file that has the copy of the mail file.
83 		 * If any messages have been edited, this will be
84 		 * different from the offset into the mail file.
85 		 */
86 		(void)fseek(otf, 0L, 2);
87 		offset = ftell(otf);
88 	}
89 	omsgCount = msgCount;
90 	maybe = 1;
91 	inhead = 0;
92 	this.m_flag = MUSED|MNEW;
93 	this.m_size = 0;
94 	this.m_lines = 0;
95 	this.m_blines = 0;
96 	this.m_block = 0;
97 	this.m_offset = 0;
98 	for (;;) {
99 		if (fgets(linebuf, LINESIZE, ibuf) == NULL) {
100 			if (append(&this, mestmp)) {
101 				warn("temporary file");
102 				exit(1);
103 			}
104 			makemessage(mestmp, omsgCount);
105 			return;
106 		}
107 		len = strlen(linebuf);
108 		/*
109 		 * Transforms lines ending in <CR><LF> to just <LF>.
110 		 * This allows mail to be able to read Eudora mailboxes
111 		 * that reside on a DOS partition.
112 		 */
113 		if (len >= 2 && linebuf[len-1] == '\n' &&
114 		    linebuf[len-2] == '\r') {
115 			linebuf[len-2] = '\n';
116 			len--;
117 		}
118 		(void)fwrite(linebuf, sizeof *linebuf, len, otf);
119 		if (ferror(otf)) {
120 			warn("/tmp");
121 			exit(1);
122 		}
123 		if(len)
124 			linebuf[len - 1] = 0;
125 		if (maybe && linebuf[0] == 'F' && ishead(linebuf)) {
126 			msgCount++;
127 			if (append(&this, mestmp)) {
128 				warn("temporary file");
129 				exit(1);
130 			}
131 			this.m_flag = MUSED|MNEW;
132 			this.m_size = 0;
133 			this.m_lines = 0;
134 			this.m_blines = 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 = *cp2++) == 0) {
143 					while (isspace((unsigned char)*cp++))
144 						;
145 					if (cp[-1] != ':')
146 						break;
147 					while ((c = *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 += len;
160 		this.m_size += len;
161 		this.m_lines++;
162 		if (!inhead) {
163 			int lines_plus_wraps = 1;
164 			size_t linelen = strlen(linebuf);
165 
166 			if (linelen > screenwidth) {
167 				lines_plus_wraps = linelen / screenwidth;
168 				if (linelen % screenwidth != 0)
169 					++lines_plus_wraps;
170 			}
171 			this.m_blines += lines_plus_wraps;
172 		}
173 		maybe = linebuf[0] == 0;
174 	}
175 }
176 
177 /*
178  * Drop the passed line onto the passed output buffer.
179  * If a write error occurs, return -1, else the count of
180  * characters written, including the newline if requested.
181  */
182 int
183 putline(FILE *obuf, const char *linebuf, int outlf)
184 {
185 	size_t c;
186 
187 	c = strlen(linebuf);
188 	(void)fwrite(linebuf, sizeof *linebuf, c, obuf);
189 	if (outlf) {
190 		(void)putc('\n', obuf);
191 		c++;
192 	}
193 	if (ferror(obuf))
194 		return (-1);
195 	return (c);
196 }
197 
198 /*
199  * Read up a line from the specified input into the line
200  * buffer.  Return the number of characters read.  Do not
201  * include the newline at the end.
202  */
203 int
204 readline(FILE *ibuf, char *linebuf, int linesize)
205 {
206 	int n;
207 
208 	clearerr(ibuf);
209 	if (fgets(linebuf, linesize, ibuf) == NULL)
210 		return -1;
211 	n = strlen(linebuf);
212 	if (n > 0 && linebuf[n - 1] == '\n')
213 		linebuf[--n] = '\0';
214 	return n;
215 }
216 
217 /*
218  * Return a file buffer all ready to read up the
219  * passed message pointer.
220  */
221 FILE *
222 setinput(const struct message *mp)
223 {
224 
225 	(void)fflush(otf);
226 	if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), 0) < 0)
227 		err(1, "fseek");
228 	return (itf);
229 }
230 
231 /*
232  * Take the data out of the passed ghost file and toss it into
233  * a dynamically allocated message structure.
234  */
235 void
236 makemessage(FILE *f, int omsgCount)
237 {
238 	size_t size = (msgCount + 1) * sizeof (struct message);
239 	struct message *nmessage = realloc(message, size);
240 
241 	if (nmessage == NULL)
242 		err(1, "Insufficient memory for %d messages", msgCount);
243 	if (omsgCount == 0 || message == NULL)
244 		dot = nmessage;
245 	else
246 		dot = nmessage + (dot - message);
247 	message = nmessage;
248 	size -= (omsgCount + 1) * sizeof (struct message);
249 	(void)fflush(f);
250 	(void)lseek(fileno(f), (off_t)sizeof *message, 0);
251 	if (read(fileno(f), &message[omsgCount], size) != size)
252 		errx(1, "Message temporary file corrupted");
253 	message[msgCount].m_size = 0;
254 	message[msgCount].m_lines = 0;
255 	message[msgCount].m_blines = 0;
256 	(void)Fclose(f);
257 }
258 
259 /*
260  * Append the passed message descriptor onto the temp file.
261  * If the write fails, return 1, else 0
262  */
263 int
264 append(struct message *mp, FILE *f)
265 {
266 	return fwrite(mp, sizeof *mp, 1, f) != 1;
267 }
268 
269 /*
270  * Delete a file, but only if the file is a plain file.
271  */
272 int
273 rm(char *name)
274 {
275 	struct stat sb;
276 
277 	if (stat(name, &sb) < 0)
278 		return(-1);
279 	if (!S_ISREG(sb.st_mode)) {
280 		errno = EISDIR;
281 		return(-1);
282 	}
283 	return(unlink(name));
284 }
285 
286 static int sigdepth;		/* depth of holdsigs() */
287 static sigset_t nset, oset;
288 /*
289  * Hold signals SIGHUP, SIGINT, and SIGQUIT.
290  */
291 void
292 holdsigs(void)
293 {
294 
295 	if (sigdepth++ == 0) {
296 		(void)sigemptyset(&nset);
297 		(void)sigaddset(&nset, SIGHUP);
298 		(void)sigaddset(&nset, SIGINT);
299 		(void)sigaddset(&nset, SIGQUIT);
300 		(void)sigprocmask(SIG_BLOCK, &nset, &oset);
301 	}
302 }
303 
304 /*
305  * Release signals SIGHUP, SIGINT, and SIGQUIT.
306  */
307 void
308 relsesigs(void)
309 {
310 
311 	if (--sigdepth == 0)
312 		(void)sigprocmask(SIG_SETMASK, &oset, NULL);
313 }
314 
315 /*
316  * Determine the size of the file possessed by
317  * the passed buffer.
318  */
319 off_t
320 fsize(FILE *iob)
321 {
322 	struct stat sbuf;
323 
324 	if (fstat(fileno(iob), &sbuf) < 0)
325 		return 0;
326 	return sbuf.st_size;
327 }
328 
329 /*
330  * Evaluate the string given as a new mailbox name.
331  * Supported meta characters:
332  *	%	for my system mail box
333  *	%user	for user's system mail box
334  *	#	for previous file
335  *	&	invoker's mbox file
336  *	+file	file in folder directory
337  *	any shell meta character
338  * Return the file name as a dynamic string.
339  */
340 const char *
341 expand(const char *name)
342 {
343 	char xname[PATHSIZE];
344 	char cmdbuf[PATHSIZE];		/* also used for file names */
345 	int pid, l;
346 	char *cp;
347 	const char *shellcmd;
348 	int pivec[2];
349 	struct stat sbuf;
350 
351 	/*
352 	 * The order of evaluation is "%" and "#" expand into constants.
353 	 * "&" can expand into "+".  "+" can expand into shell meta characters.
354 	 * Shell meta characters expand into constants.
355 	 * This way, we make no recursive expansion.
356 	 */
357 	switch (*name) {
358 	case '%':
359 		findmail(name[1] ? name + 1 : myname, xname);
360 		return savestr(xname);
361 	case '#':
362 		if (name[1] != 0)
363 			break;
364 		if (prevfile[0] == 0) {
365 			(void)printf("No previous file\n");
366 			return NULL;
367 		}
368 		return savestr(prevfile);
369 	case '&':
370 		if (name[1] == 0 && (name = value("MBOX")) == NULL)
371 			name = "~/mbox";
372 		/* fall through */
373 	}
374 	if (name[0] == '+' && getfold(cmdbuf) >= 0) {
375 		(void)snprintf(xname, PATHSIZE, "%s/%s", cmdbuf, name + 1);
376 		name = savestr(xname);
377 	}
378 	/* catch the most common shell meta character */
379 	if (name[0] == '~' && (name[1] == '/' || name[1] == '\0')) {
380 		(void)snprintf(xname, PATHSIZE, "%s%s", homedir, name + 1);
381 		name = savestr(xname);
382 	}
383 	if (strpbrk(name, "~{[*?$`'\"\\") == NULL)
384 		return name;
385 	if (pipe(pivec) < 0) {
386 		warn("pipe");
387 		return name;
388 	}
389 	(void)snprintf(cmdbuf, PATHSIZE, "echo %s", name);
390 	if ((shellcmd = value("SHELL")) == NULL)
391 		shellcmd = _PATH_CSHELL;
392 	pid = start_command(shellcmd, 0, -1, pivec[1], "-c", cmdbuf, NULL);
393 	if (pid < 0) {
394 		(void)close(pivec[0]);
395 		(void)close(pivec[1]);
396 		return NULL;
397 	}
398 	(void)close(pivec[1]);
399 	l = read(pivec[0], xname, PATHSIZE);
400 	(void)close(pivec[0]);
401 	if (wait_child(pid) < 0 && WTERMSIG(wait_status) != SIGPIPE) {
402 		(void)fprintf(stderr, "\"%s\": Expansion failed.\n", name);
403 		return NULL;
404 	}
405 	if (l < 0) {
406 		warn("read");
407 		return NULL;
408 	}
409 	if (l == 0) {
410 		(void)fprintf(stderr, "\"%s\": No match.\n", name);
411 		return NULL;
412 	}
413 	if (l == PATHSIZE) {
414 		(void)fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name);
415 		return NULL;
416 	}
417 	xname[l] = '\0';
418 	for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--)
419 		;
420 	cp[1] = '\0';
421 	if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) {
422 		(void)fprintf(stderr, "\"%s\": Ambiguous.\n", name);
423 		return NULL;
424 	}
425 	return savestr(xname);
426 }
427 
428 /*
429  * Determine the current folder directory name.
430  */
431 int
432 getfold(char *name)
433 {
434 	char *folder;
435 
436 	if ((folder = value("folder")) == NULL)
437 		return (-1);
438 	if (*folder == '/') {
439 		(void)strncpy(name, folder, PATHSIZE - 1);
440 		name[PATHSIZE - 1] = '\0' ;
441 	}
442 	else
443 		(void)snprintf(name, PATHSIZE, "%s/%s", homedir, folder);
444 	return (0);
445 }
446 
447 /*
448  * Return the name of the dead.letter file.
449  */
450 const char *
451 getdeadletter(void)
452 {
453 	const char *cp;
454 
455 	if ((cp = value("DEAD")) == NULL || (cp = expand(cp)) == NULL)
456 		cp = expand("~/dead.letter");
457 	else if (*cp != '/') {
458 		char buf[PATHSIZE];
459 
460 		(void)snprintf(buf, PATHSIZE, "~/%s", cp);
461 		cp = expand(buf);
462 	}
463 	return cp;
464 }
465