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