1 /* $OpenBSD: fio.c,v 1.25 2004/05/10 15:25:51 deraadt 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.25 2004/05/10 15:25:51 deraadt 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
myread(int fd,char * buf,int len)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
setptr(FILE * ibuf,off_t offset)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, 0);
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] = linebuf[--count];
128
129 (void)fwrite(linebuf, sizeof(*linebuf), count, otf);
130 if (ferror(otf))
131 err(1, "/tmp");
132 if (count)
133 linebuf[count - 1] = '\0';
134 if (maybe && linebuf[0] == 'F' && ishead(linebuf)) {
135 msgCount++;
136 if (append(&this, mestmp))
137 err(1, "temporary file");
138 this.m_flag = MUSED|MNEW;
139 this.m_size = 0;
140 this.m_lines = 0;
141 this.m_block = blockof(offset);
142 this.m_offset = offsetof(offset);
143 inhead = 1;
144 } else if (linebuf[0] == 0) {
145 inhead = 0;
146 } else if (inhead) {
147 for (cp = linebuf, cp2 = "status";; cp++) {
148 if ((c = *cp2++) == 0) {
149 while (isspace(*cp++))
150 ;
151 if (cp[-1] != ':')
152 break;
153 while ((c = *cp++) != '\0')
154 if (c == 'R')
155 this.m_flag |= MREAD;
156 else if (c == 'O')
157 this.m_flag &= ~MNEW;
158 inhead = 0;
159 break;
160 }
161 if (*cp != c && *cp != toupper(c))
162 break;
163 }
164 }
165 offset += count;
166 this.m_size += count;
167 this.m_lines++;
168 maybe = linebuf[0] == 0;
169 }
170 }
171
172 /*
173 * Drop the passed line onto the passed output buffer.
174 * If a write error occurs, return -1, else the count of
175 * characters written, including the newline if requested.
176 */
177 int
putline(FILE * obuf,char * linebuf,int outlf)178 putline(FILE *obuf, char *linebuf, int outlf)
179 {
180 int c;
181
182 c = strlen(linebuf);
183 (void)fwrite(linebuf, sizeof(*linebuf), c, obuf);
184 if (outlf) {
185 (void)putc('\n', obuf);
186 c++;
187 }
188 if (ferror(obuf))
189 return(-1);
190 return(c);
191 }
192
193 /*
194 * Read up a line from the specified input into the line
195 * buffer. Return the number of characters read. Do not
196 * include the newline (or carriage return) at the end.
197 */
198 int
readline(FILE * ibuf,char * linebuf,int linesize,int * signo)199 readline(FILE *ibuf, char *linebuf, int linesize, int *signo)
200 {
201 struct sigaction act;
202 struct sigaction savetstp;
203 struct sigaction savettou;
204 struct sigaction savettin;
205 struct sigaction saveint;
206 struct sigaction savehup;
207 sigset_t oset;
208 int n;
209
210 /*
211 * Setup signal handlers if the caller asked us to catch signals.
212 * Note that we do not restart system calls since we need the
213 * read to be interuptible.
214 */
215 if (signo) {
216 fiosignal = 0;
217 sigemptyset(&act.sa_mask);
218 act.sa_flags = 0;
219 act.sa_handler = fioint;
220 if (sigaction(SIGINT, NULL, &saveint) == 0 &&
221 saveint.sa_handler != SIG_IGN) {
222 (void)sigaction(SIGINT, &act, &saveint);
223 (void)sigprocmask(SIG_UNBLOCK, &intset, &oset);
224 }
225 if (sigaction(SIGHUP, NULL, &savehup) == 0 &&
226 savehup.sa_handler != SIG_IGN)
227 (void)sigaction(SIGHUP, &act, &savehup);
228 (void)sigaction(SIGTSTP, &act, &savetstp);
229 (void)sigaction(SIGTTOU, &act, &savettou);
230 (void)sigaction(SIGTTIN, &act, &savettin);
231 }
232
233 clearerr(ibuf);
234 if (fgets(linebuf, linesize, ibuf) == NULL) {
235 if (ferror(ibuf))
236 clearerr(ibuf);
237 n = -1;
238 } else {
239 n = strlen(linebuf);
240 if (n > 0 && linebuf[n - 1] == '\n')
241 linebuf[--n] = '\0';
242 if (n > 0 && linebuf[n - 1] == '\r')
243 linebuf[--n] = '\0';
244 }
245
246 if (signo) {
247 (void)sigprocmask(SIG_SETMASK, &oset, NULL);
248 (void)sigaction(SIGINT, &saveint, NULL);
249 (void)sigaction(SIGHUP, &savehup, NULL);
250 (void)sigaction(SIGTSTP, &savetstp, NULL);
251 (void)sigaction(SIGTTOU, &savettou, NULL);
252 (void)sigaction(SIGTTIN, &savettin, NULL);
253 *signo = fiosignal;
254 }
255
256 return(n);
257 }
258
259 /*
260 * Return a file buffer all ready to read up the
261 * passed message pointer.
262 */
263 FILE *
setinput(struct message * mp)264 setinput(struct message *mp)
265 {
266
267 fflush(otf);
268 if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), 0) < 0)
269 err(1, "fseek");
270 return(itf);
271 }
272
273 /*
274 * Take the data out of the passed ghost file and toss it into
275 * a dynamically allocated message structure.
276 */
277 void
makemessage(FILE * f,int omsgCount)278 makemessage(FILE *f, int omsgCount)
279 {
280 size_t size;
281 struct message *nmessage;
282
283 size = (msgCount + 1) * sizeof(struct message);
284 nmessage = (struct message *)realloc(message, size);
285 if (nmessage == 0)
286 errx(1, "Insufficient memory for %d messages",
287 msgCount);
288 if (omsgCount == 0 || message == NULL)
289 dot = nmessage;
290 else
291 dot = nmessage + (dot - message);
292 message = nmessage;
293 size -= (omsgCount + 1) * sizeof(struct message);
294 fflush(f);
295 (void)lseek(fileno(f), (off_t)sizeof(*message), 0);
296 if (myread(fileno(f), (void *) &message[omsgCount], size) != size)
297 errx(1, "Message temporary file corrupted");
298 message[msgCount].m_size = 0;
299 message[msgCount].m_lines = 0;
300 (void)Fclose(f);
301 }
302
303 /*
304 * Append the passed message descriptor onto the temp file.
305 * If the write fails, return 1, else 0
306 */
307 int
append(struct message * mp,FILE * f)308 append(struct message *mp, FILE *f)
309 {
310
311 return(fwrite((char *) mp, sizeof(*mp), 1, f) != 1);
312 }
313
314 /*
315 * Delete or truncate a file, but only if the file is a plain file.
316 */
317 int
rm(char * name)318 rm(char *name)
319 {
320 struct stat sb;
321
322 if (stat(name, &sb) < 0)
323 return(-1);
324 if (!S_ISREG(sb.st_mode)) {
325 errno = EISDIR;
326 return(-1);
327 }
328 if (unlink(name) == -1) {
329 if (errno == EPERM)
330 return(truncate(name, (off_t)0));
331 else
332 return(-1);
333 }
334 return(0);
335 }
336
337 static int sigdepth; /* depth of holdsigs() */
338 static sigset_t nset, oset;
339 /*
340 * Hold signals SIGHUP, SIGINT, and SIGQUIT.
341 */
342 void
holdsigs(void)343 holdsigs(void)
344 {
345
346 if (sigdepth++ == 0) {
347 sigemptyset(&nset);
348 sigaddset(&nset, SIGHUP);
349 sigaddset(&nset, SIGINT);
350 sigaddset(&nset, SIGQUIT);
351 sigprocmask(SIG_BLOCK, &nset, &oset);
352 }
353 }
354
355 /*
356 * Release signals SIGHUP, SIGINT, and SIGQUIT.
357 */
358 void
relsesigs(void)359 relsesigs(void)
360 {
361
362 if (--sigdepth == 0)
363 sigprocmask(SIG_SETMASK, &oset, NULL);
364 }
365
366 /*
367 * Unblock and ignore a signal
368 */
369 int
ignoresig(int sig,struct sigaction * oact,sigset_t * oset)370 ignoresig(int sig, struct sigaction *oact, sigset_t *oset)
371 {
372 struct sigaction act;
373 sigset_t nset;
374 int error;
375
376 sigemptyset(&act.sa_mask);
377 act.sa_flags = SA_RESTART;
378 act.sa_handler = SIG_IGN;
379 error = sigaction(sig, &act, oact);
380
381 if (error == 0) {
382 sigemptyset(&nset);
383 sigaddset(&nset, sig);
384 (void)sigprocmask(SIG_UNBLOCK, &nset, oset);
385 } else if (oset != NULL)
386 (void)sigprocmask(SIG_BLOCK, NULL, oset);
387
388 return(error);
389 }
390
391 /*
392 * Determine the size of the file possessed by
393 * the passed buffer.
394 */
395 off_t
fsize(FILE * iob)396 fsize(FILE *iob)
397 {
398 struct stat sbuf;
399
400 if (fstat(fileno(iob), &sbuf) < 0)
401 return(0);
402 return(sbuf.st_size);
403 }
404
405 /*
406 * Evaluate the string given as a new mailbox name.
407 * Supported meta characters:
408 * % for my system mail box
409 * %user for user's system mail box
410 * # for previous file
411 * & invoker's mbox file
412 * +file file in folder directory
413 * any shell meta character
414 * Return the file name as a dynamic string.
415 */
416 char *
expand(char * name)417 expand(char *name)
418 {
419 char xname[PATHSIZE];
420 char cmdbuf[PATHSIZE]; /* also used for file names */
421 pid_t pid;
422 int l;
423 char *cp, *shell;
424 int pivec[2];
425 struct stat sbuf;
426 extern int wait_status;
427
428 /*
429 * The order of evaluation is "%" and "#" expand into constants.
430 * "&" can expand into "+". "+" can expand into shell meta characters.
431 * Shell meta characters expand into constants.
432 * This way, we make no recursive expansion.
433 */
434 switch (*name) {
435 case '%':
436 findmail(name[1] ? name + 1 : myname, xname, sizeof(xname));
437 return(savestr(xname));
438 case '#':
439 if (name[1] != 0)
440 break;
441 if (prevfile[0] == 0) {
442 puts("No previous file");
443 return(NULL);
444 }
445 return(savestr(prevfile));
446 case '&':
447 if (name[1] == 0 && (name = value("MBOX")) == NULL)
448 name = "~/mbox";
449 /* fall through */
450 }
451 if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) {
452 (void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1);
453 name = savestr(xname);
454 }
455 /* catch the most common shell meta character */
456 if (name[0] == '~' && homedir && (name[1] == '/' || name[1] == '\0')) {
457 (void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1);
458 name = savestr(xname);
459 }
460 if (strpbrk(name, "~{[*?$`'\"\\") == NULL)
461 return(name);
462 /* XXX - just use glob(3) and env expansion instead? */
463 if (pipe(pivec) < 0) {
464 warn("pipe");
465 return(name);
466 }
467 (void)snprintf(cmdbuf, sizeof(cmdbuf), "echo %s", name);
468 shell = value("SHELL");
469 pid = start_command(shell, 0, -1, pivec[1], "-c", cmdbuf, NULL);
470 if (pid < 0) {
471 (void)close(pivec[0]);
472 (void)close(pivec[1]);
473 return(NULL);
474 }
475 (void)close(pivec[1]);
476 l = myread(pivec[0], xname, PATHSIZE);
477 if (l < 0)
478 warn("read"); /* report error before errno changes */
479 (void)close(pivec[0]);
480 if (wait_child(pid) < 0 && WIFSIGNALED(wait_status) &&
481 WTERMSIG(wait_status) != SIGPIPE) {
482 fprintf(stderr, "\"%s\": Expansion failed.\n", name);
483 return(NULL);
484 }
485 if (l < 0)
486 return(NULL);
487 if (l == 0) {
488 fprintf(stderr, "\"%s\": No match.\n", name);
489 return(NULL);
490 }
491 if (l == PATHSIZE) {
492 fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name);
493 return(NULL);
494 }
495 xname[l] = '\0';
496 for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--)
497 ;
498 cp[1] = '\0';
499 if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) {
500 fprintf(stderr, "\"%s\": Ambiguous.\n", name);
501 return(NULL);
502 }
503 return(savestr(xname));
504 }
505
506 /*
507 * Determine the current folder directory name.
508 */
509 int
getfold(char * name,int namelen)510 getfold(char *name, int namelen)
511 {
512 char *folder;
513
514 if ((folder = value("folder")) == NULL)
515 return(-1);
516 if (*folder == '/')
517 strlcpy(name, folder, namelen);
518 else
519 (void)snprintf(name, namelen, "%s/%s", homedir ? homedir : ".",
520 folder);
521 return(0);
522 }
523
524 /*
525 * Return the name of the dead.letter file.
526 */
527 char *
getdeadletter(void)528 getdeadletter(void)
529 {
530 char *cp;
531
532 if ((cp = value("DEAD")) == NULL || (cp = expand(cp)) == NULL)
533 cp = expand("~/dead.letter");
534 else if (*cp != '/') {
535 char buf[PATHSIZE];
536
537 (void)snprintf(buf, sizeof(buf), "~/%s", cp);
538 cp = expand(buf);
539 }
540 return(cp);
541 }
542
543 /*
544 * Signal handler used by readline() to catch SIGINT, SIGHUP, SIGTSTP,
545 * SIGTTOU, SIGTTIN.
546 */
547 void
fioint(int s)548 fioint(int s)
549 {
550
551 fiosignal = s;
552 }
553