1 /* $OpenBSD: fio.c,v 1.18 2001/01/16 05:36:08 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 char sccsid[] = "@(#)fio.c 8.2 (Berkeley) 4/20/95"; 40 #else 41 static char rcsid[] = "$OpenBSD: fio.c,v 1.18 2001/01/16 05:36:08 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 /* 61 * Wrapper for read() to catch EINTR. 62 */ 63 ssize_t 64 myread(fd, buf, len) 65 int fd; 66 char *buf; 67 int len; 68 { 69 ssize_t nread; 70 71 while ((nread = read(fd, buf, len)) == -1 && errno == EINTR) 72 ; 73 return(nread); 74 } 75 76 /* 77 * Set up the input pointers while copying the mail file into /tmp. 78 */ 79 void 80 setptr(ibuf, offset) 81 FILE *ibuf; 82 off_t offset; 83 { 84 int c, count; 85 char *cp, *cp2; 86 struct message this; 87 FILE *mestmp; 88 int maybe, inhead, omsgCount; 89 char linebuf[LINESIZE], pathbuf[PATHSIZE]; 90 91 /* Get temporary file. */ 92 (void)snprintf(pathbuf, sizeof(pathbuf), "%s/mail.XXXXXXXXXX", tmpdir); 93 if ((c = mkstemp(pathbuf)) == -1 || (mestmp = Fdopen(c, "r+")) == NULL) 94 err(1, "can't open %s", pathbuf); 95 (void)rm(pathbuf); 96 97 if (offset == 0) { 98 msgCount = 0; 99 } else { 100 /* Seek into the file to get to the new messages */ 101 (void)fseek(ibuf, offset, 0); 102 /* 103 * We need to make "offset" a pointer to the end of 104 * the temp file that has the copy of the mail file. 105 * If any messages have been edited, this will be 106 * different from the offset into the mail file. 107 */ 108 (void)fseek(otf, 0L, SEEK_END); 109 offset = ftell(otf); 110 } 111 omsgCount = msgCount; 112 maybe = 1; 113 inhead = 0; 114 this.m_flag = MUSED|MNEW; 115 this.m_size = 0; 116 this.m_lines = 0; 117 this.m_block = 0; 118 this.m_offset = 0; 119 for (;;) { 120 if (fgets(linebuf, sizeof(linebuf), ibuf) == NULL) { 121 if (append(&this, mestmp)) 122 err(1, "temporary file"); 123 makemessage(mestmp, omsgCount); 124 return; 125 } 126 count = strlen(linebuf); 127 /* 128 * Transforms lines ending in <CR><LF> to just <LF>. 129 * This allows mail to be able to read Eudora mailboxes 130 * that reside on a DOS partition. 131 */ 132 if (count >= 2 && linebuf[count-1] == '\n' && 133 linebuf[count - 2] == '\r') 134 linebuf[count - 2] = linebuf[--count]; 135 136 (void)fwrite(linebuf, sizeof(*linebuf), count, otf); 137 if (ferror(otf)) 138 err(1, "/tmp"); 139 if (count) 140 linebuf[count - 1] = '\0'; 141 if (maybe && linebuf[0] == 'F' && ishead(linebuf)) { 142 msgCount++; 143 if (append(&this, mestmp)) 144 err(1, "temporary file"); 145 this.m_flag = MUSED|MNEW; 146 this.m_size = 0; 147 this.m_lines = 0; 148 this.m_block = blockof(offset); 149 this.m_offset = offsetof(offset); 150 inhead = 1; 151 } else if (linebuf[0] == 0) { 152 inhead = 0; 153 } else if (inhead) { 154 for (cp = linebuf, cp2 = "status";; cp++) { 155 if ((c = *cp2++) == 0) { 156 while (isspace(*cp++)) 157 ; 158 if (cp[-1] != ':') 159 break; 160 while ((c = *cp++) != '\0') 161 if (c == 'R') 162 this.m_flag |= MREAD; 163 else if (c == 'O') 164 this.m_flag &= ~MNEW; 165 inhead = 0; 166 break; 167 } 168 if (*cp != c && *cp != toupper(c)) 169 break; 170 } 171 } 172 offset += count; 173 this.m_size += count; 174 this.m_lines++; 175 maybe = linebuf[0] == 0; 176 } 177 } 178 179 /* 180 * Drop the passed line onto the passed output buffer. 181 * If a write error occurs, return -1, else the count of 182 * characters written, including the newline if requested. 183 */ 184 int 185 putline(obuf, linebuf, outlf) 186 FILE *obuf; 187 char *linebuf; 188 int outlf; 189 { 190 int c; 191 192 c = strlen(linebuf); 193 (void)fwrite(linebuf, sizeof(*linebuf), c, obuf); 194 if (outlf) { 195 (void)putc('\n', obuf); 196 c++; 197 } 198 if (ferror(obuf)) 199 return(-1); 200 return(c); 201 } 202 203 /* 204 * Read up a line from the specified input into the line 205 * buffer. Return the number of characters read. Do not 206 * include the newline (or carriage return) at the end. 207 */ 208 int 209 readline(ibuf, linebuf, linesize) 210 FILE *ibuf; 211 char *linebuf; 212 int linesize; 213 { 214 int n; 215 216 clearerr(ibuf); 217 if (fgets(linebuf, linesize, ibuf) == NULL) 218 return(-1); 219 220 n = strlen(linebuf); 221 if (n > 0 && linebuf[n - 1] == '\n') 222 linebuf[--n] = '\0'; 223 if (n > 0 && linebuf[n - 1] == '\r') 224 linebuf[--n] = '\0'; 225 return(n); 226 } 227 228 /* 229 * Return a file buffer all ready to read up the 230 * passed message pointer. 231 */ 232 FILE * 233 setinput(mp) 234 struct message *mp; 235 { 236 237 fflush(otf); 238 if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), 0) < 0) 239 err(1, "fseek"); 240 return(itf); 241 } 242 243 /* 244 * Take the data out of the passed ghost file and toss it into 245 * a dynamically allocated message structure. 246 */ 247 void 248 makemessage(f, omsgCount) 249 FILE *f; 250 int omsgCount; 251 { 252 size_t size = (msgCount + 1) * sizeof(struct message); 253 254 if (omsgCount) { 255 message = (struct message *)realloc(message, size); 256 if (message == 0) 257 errx(1, "Insufficient memory for %d messages\n", 258 msgCount); 259 } else { 260 if (message != 0) 261 (void)free(message); 262 if ((message = (struct message *)malloc(size)) == NULL) 263 errx(1, "Insufficient memory for %d messages", 264 msgCount); 265 dot = message; 266 } 267 size -= (omsgCount + 1) * sizeof(struct message); 268 fflush(f); 269 (void)lseek(fileno(f), (off_t)sizeof(*message), 0); 270 if (myread(fileno(f), (void *) &message[omsgCount], size) != size) 271 errx(1, "Message temporary file corrupted"); 272 message[msgCount].m_size = 0; 273 message[msgCount].m_lines = 0; 274 (void)Fclose(f); 275 } 276 277 /* 278 * Append the passed message descriptor onto the temp file. 279 * If the write fails, return 1, else 0 280 */ 281 int 282 append(mp, f) 283 struct message *mp; 284 FILE *f; 285 { 286 return(fwrite((char *) mp, sizeof(*mp), 1, f) != 1); 287 } 288 289 /* 290 * Delete or truncate a file, but only if the file is a plain file. 291 */ 292 int 293 rm(name) 294 char *name; 295 { 296 struct stat sb; 297 298 if (stat(name, &sb) < 0) 299 return(-1); 300 if (!S_ISREG(sb.st_mode)) { 301 errno = EISDIR; 302 return(-1); 303 } 304 if (unlink(name) == -1) { 305 if (errno == EPERM) 306 return(truncate(name, 0)); 307 else 308 return(-1); 309 } 310 return(0); 311 } 312 313 static int sigdepth; /* depth of holdsigs() */ 314 static sigset_t nset, oset; 315 /* 316 * Hold signals SIGHUP, SIGINT, and SIGQUIT. 317 */ 318 void 319 holdsigs() 320 { 321 322 if (sigdepth++ == 0) { 323 sigemptyset(&nset); 324 sigaddset(&nset, SIGHUP); 325 sigaddset(&nset, SIGINT); 326 sigaddset(&nset, SIGQUIT); 327 sigprocmask(SIG_BLOCK, &nset, &oset); 328 } 329 } 330 331 /* 332 * Release signals SIGHUP, SIGINT, and SIGQUIT. 333 */ 334 void 335 relsesigs() 336 { 337 338 if (--sigdepth == 0) 339 sigprocmask(SIG_SETMASK, &oset, NULL); 340 } 341 342 /* 343 * Determine the size of the file possessed by 344 * the passed buffer. 345 */ 346 off_t 347 fsize(iob) 348 FILE *iob; 349 { 350 struct stat sbuf; 351 352 if (fstat(fileno(iob), &sbuf) < 0) 353 return(0); 354 return(sbuf.st_size); 355 } 356 357 /* 358 * Evaluate the string given as a new mailbox name. 359 * Supported meta characters: 360 * % for my system mail box 361 * %user for user's system mail box 362 * # for previous file 363 * & invoker's mbox file 364 * +file file in folder directory 365 * any shell meta character 366 * Return the file name as a dynamic string. 367 */ 368 char * 369 expand(name) 370 char *name; 371 { 372 char xname[PATHSIZE]; 373 char cmdbuf[PATHSIZE]; /* also used for file names */ 374 int pid, l; 375 char *cp, *shell; 376 int pivec[2]; 377 struct stat sbuf; 378 extern int wait_status; 379 380 /* 381 * The order of evaluation is "%" and "#" expand into constants. 382 * "&" can expand into "+". "+" can expand into shell meta characters. 383 * Shell meta characters expand into constants. 384 * This way, we make no recursive expansion. 385 */ 386 switch (*name) { 387 case '%': 388 findmail(name[1] ? name + 1 : myname, xname, sizeof(xname)); 389 return(savestr(xname)); 390 case '#': 391 if (name[1] != 0) 392 break; 393 if (prevfile[0] == 0) { 394 puts("No previous file"); 395 return(NULL); 396 } 397 return(savestr(prevfile)); 398 case '&': 399 if (name[1] == 0 && (name = value("MBOX")) == NULL) 400 name = "~/mbox"; 401 /* fall through */ 402 } 403 if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) { 404 (void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1); 405 name = savestr(xname); 406 } 407 /* catch the most common shell meta character */ 408 if (name[0] == '~' && homedir && (name[1] == '/' || name[1] == '\0')) { 409 (void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1); 410 name = savestr(xname); 411 } 412 if (!anyof(name, "~{[*?$`'\"\\")) 413 return(name); 414 if (pipe(pivec) < 0) { 415 warn("pipe"); 416 return(name); 417 } 418 (void)snprintf(cmdbuf, sizeof(cmdbuf), "echo %s", name); 419 shell = value("SHELL"); 420 pid = start_command(shell, 0, -1, pivec[1], "-c", cmdbuf, NULL); 421 if (pid < 0) { 422 (void)close(pivec[0]); 423 (void)close(pivec[1]); 424 return(NULL); 425 } 426 (void)close(pivec[1]); 427 l = myread(pivec[0], xname, PATHSIZE); 428 if (l < 0) 429 warn("read"); /* report error before errno changes */ 430 (void)close(pivec[0]); 431 if (wait_child(pid) < 0 && WIFSIGNALED(wait_status) && 432 WTERMSIG(wait_status) != SIGPIPE) { 433 fprintf(stderr, "\"%s\": Expansion failed.\n", name); 434 return(NULL); 435 } 436 if (l < 0) 437 return(NULL); 438 if (l == 0) { 439 fprintf(stderr, "\"%s\": No match.\n", name); 440 return(NULL); 441 } 442 if (l == PATHSIZE) { 443 fprintf(stderr, "\"%s\": Expansion buffer overflow.\n", name); 444 return(NULL); 445 } 446 xname[l] = '\0'; 447 for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--) 448 ; 449 cp[1] = '\0'; 450 if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) { 451 fprintf(stderr, "\"%s\": Ambiguous.\n", name); 452 return(NULL); 453 } 454 return(savestr(xname)); 455 } 456 457 /* 458 * Determine the current folder directory name. 459 */ 460 int 461 getfold(name, namelen) 462 char *name; 463 int namelen; 464 { 465 char *folder; 466 467 if ((folder = value("folder")) == NULL) 468 return(-1); 469 if (*folder == '/') { 470 strncpy(name, folder, namelen-1); 471 name[namelen-1] = '\0'; 472 } else 473 (void)snprintf(name, namelen, "%s/%s", homedir ? homedir : ".", 474 folder); 475 return(0); 476 } 477 478 /* 479 * Return the name of the dead.letter file. 480 */ 481 char * 482 getdeadletter() 483 { 484 char *cp; 485 486 if ((cp = value("DEAD")) == NULL || (cp = expand(cp)) == NULL) 487 cp = expand("~/dead.letter"); 488 else if (*cp != '/') { 489 char buf[PATHSIZE]; 490 491 (void)snprintf(buf, sizeof(buf), "~/%s", cp); 492 cp = expand(buf); 493 } 494 return(cp); 495 } 496