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