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