1 /* $OpenBSD: fio.c,v 1.21 2001/12/18 16:55:06 millert 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. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #ifndef lint 38 #if 0 39 static const char sccsid[] = "@(#)fio.c 8.2 (Berkeley) 4/20/95"; 40 #else 41 static const char rcsid[] = "$OpenBSD: fio.c,v 1.21 2001/12/18 16:55:06 millert Exp $"; 42 #endif 43 #endif /* not lint */ 44 45 #include "rcv.h" 46 #include <sys/file.h> 47 #include <sys/wait.h> 48 49 #include <unistd.h> 50 #include <paths.h> 51 #include <errno.h> 52 #include "extern.h" 53 54 /* 55 * Mail -- a mail program 56 * 57 * File I/O. 58 */ 59 60 static volatile sig_atomic_t fiosignal; 61 62 /* 63 * Wrapper for read() to catch EINTR. 64 */ 65 ssize_t 66 myread(int fd, char *buf, int len) 67 { 68 ssize_t nread; 69 70 while ((nread = read(fd, buf, len)) == -1 && errno == EINTR) 71 ; 72 return(nread); 73 } 74 75 /* 76 * Set up the input pointers while copying the mail file into /tmp. 77 */ 78 void 79 setptr(FILE *ibuf, off_t offset) 80 { 81 int c, count; 82 char *cp, *cp2; 83 struct message this; 84 FILE *mestmp; 85 int maybe, inhead, omsgCount; 86 char linebuf[LINESIZE], pathbuf[PATHSIZE]; 87 88 /* Get temporary file. */ 89 (void)snprintf(pathbuf, sizeof(pathbuf), "%s/mail.XXXXXXXXXX", tmpdir); 90 if ((c = mkstemp(pathbuf)) == -1 || (mestmp = Fdopen(c, "r+")) == NULL) 91 err(1, "can't open %s", pathbuf); 92 (void)rm(pathbuf); 93 94 if (offset == 0) { 95 msgCount = 0; 96 } else { 97 /* Seek into the file to get to the new messages */ 98 (void)fseek(ibuf, offset, 0); 99 /* 100 * We need to make "offset" a pointer to the end of 101 * the temp file that has the copy of the mail file. 102 * If any messages have been edited, this will be 103 * different from the offset into the mail file. 104 */ 105 (void)fseek(otf, 0L, SEEK_END); 106 offset = ftell(otf); 107 } 108 omsgCount = msgCount; 109 maybe = 1; 110 inhead = 0; 111 this.m_flag = MUSED|MNEW; 112 this.m_size = 0; 113 this.m_lines = 0; 114 this.m_block = 0; 115 this.m_offset = 0; 116 for (;;) { 117 if (fgets(linebuf, sizeof(linebuf), ibuf) == NULL) { 118 if (append(&this, mestmp)) 119 err(1, "temporary file"); 120 makemessage(mestmp, omsgCount); 121 return; 122 } 123 count = strlen(linebuf); 124 /* 125 * Transforms lines ending in <CR><LF> to just <LF>. 126 * This allows mail to be able to read Eudora mailboxes 127 * that reside on a DOS partition. 128 */ 129 if (count >= 2 && linebuf[count-1] == '\n' && 130 linebuf[count - 2] == '\r') 131 linebuf[count - 2] = linebuf[--count]; 132 133 (void)fwrite(linebuf, sizeof(*linebuf), count, otf); 134 if (ferror(otf)) 135 err(1, "/tmp"); 136 if (count) 137 linebuf[count - 1] = '\0'; 138 if (maybe && linebuf[0] == 'F' && ishead(linebuf)) { 139 msgCount++; 140 if (append(&this, mestmp)) 141 err(1, "temporary file"); 142 this.m_flag = MUSED|MNEW; 143 this.m_size = 0; 144 this.m_lines = 0; 145 this.m_block = blockof(offset); 146 this.m_offset = offsetof(offset); 147 inhead = 1; 148 } else if (linebuf[0] == 0) { 149 inhead = 0; 150 } else if (inhead) { 151 for (cp = linebuf, cp2 = "status";; cp++) { 152 if ((c = *cp2++) == 0) { 153 while (isspace(*cp++)) 154 ; 155 if (cp[-1] != ':') 156 break; 157 while ((c = *cp++) != '\0') 158 if (c == 'R') 159 this.m_flag |= MREAD; 160 else if (c == 'O') 161 this.m_flag &= ~MNEW; 162 inhead = 0; 163 break; 164 } 165 if (*cp != c && *cp != toupper(c)) 166 break; 167 } 168 } 169 offset += count; 170 this.m_size += count; 171 this.m_lines++; 172 maybe = linebuf[0] == 0; 173 } 174 } 175 176 /* 177 * Drop the passed line onto the passed output buffer. 178 * If a write error occurs, return -1, else the count of 179 * characters written, including the newline if requested. 180 */ 181 int 182 putline(FILE *obuf, char *linebuf, int outlf) 183 { 184 int c; 185 186 c = strlen(linebuf); 187 (void)fwrite(linebuf, sizeof(*linebuf), c, obuf); 188 if (outlf) { 189 (void)putc('\n', obuf); 190 c++; 191 } 192 if (ferror(obuf)) 193 return(-1); 194 return(c); 195 } 196 197 /* 198 * Read up a line from the specified input into the line 199 * buffer. Return the number of characters read. Do not 200 * include the newline (or carriage return) at the end. 201 */ 202 int 203 readline(FILE *ibuf, char *linebuf, int linesize, int *signo) 204 { 205 struct sigaction act; 206 struct sigaction savetstp; 207 struct sigaction savettou; 208 struct sigaction savettin; 209 struct sigaction saveint; 210 struct sigaction savehup; 211 sigset_t oset; 212 int n; 213 214 /* 215 * Setup signal handlers if the caller asked us to catch signals. 216 * Note that we do not restart system calls since we need the 217 * read to be interuptible. 218 */ 219 if (signo) { 220 fiosignal = 0; 221 sigemptyset(&act.sa_mask); 222 act.sa_flags = 0; 223 act.sa_handler = fioint; 224 if (sigaction(SIGINT, NULL, &saveint) == 0 && 225 saveint.sa_handler != SIG_IGN) { 226 (void)sigaction(SIGINT, &act, &saveint); 227 (void)sigprocmask(SIG_UNBLOCK, &intset, &oset); 228 } 229 if (sigaction(SIGHUP, NULL, &savehup) == 0 && 230 savehup.sa_handler != SIG_IGN) 231 (void)sigaction(SIGHUP, &act, &savehup); 232 (void)sigaction(SIGTSTP, &act, &savetstp); 233 (void)sigaction(SIGTTOU, &act, &savettou); 234 (void)sigaction(SIGTTIN, &act, &savettin); 235 } 236 237 clearerr(ibuf); 238 if (fgets(linebuf, linesize, ibuf) == NULL) { 239 if (ferror(ibuf)) 240 clearerr(ibuf); 241 n = -1; 242 } else { 243 n = strlen(linebuf); 244 if (n > 0 && linebuf[n - 1] == '\n') 245 linebuf[--n] = '\0'; 246 if (n > 0 && linebuf[n - 1] == '\r') 247 linebuf[--n] = '\0'; 248 } 249 250 if (signo) { 251 (void)sigprocmask(SIG_SETMASK, &oset, NULL); 252 (void)sigaction(SIGINT, &saveint, NULL); 253 (void)sigaction(SIGHUP, &savehup, NULL); 254 (void)sigaction(SIGTSTP, &savetstp, NULL); 255 (void)sigaction(SIGTTOU, &savettou, NULL); 256 (void)sigaction(SIGTTIN, &savettin, NULL); 257 *signo = fiosignal; 258 } 259 260 return(n); 261 } 262 263 /* 264 * Return a file buffer all ready to read up the 265 * passed message pointer. 266 */ 267 FILE * 268 setinput(struct message *mp) 269 { 270 271 fflush(otf); 272 if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), 0) < 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\n", 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), 0); 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, 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