1 /* $NetBSD: fio.c,v 1.43 2017/11/09 20:27:50 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1980, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 #if 0 35 static char sccsid[] = "@(#)fio.c 8.2 (Berkeley) 4/20/95"; 36 #else 37 __RCSID("$NetBSD: fio.c,v 1.43 2017/11/09 20:27:50 christos Exp $"); 38 #endif 39 #endif /* not lint */ 40 41 #include "rcv.h" 42 #include "extern.h" 43 #include "thread.h" 44 #include "sig.h" 45 #include <wordexp.h> 46 47 /* 48 * Mail -- a mail program 49 * 50 * File I/O. 51 */ 52 53 #ifndef THREAD_SUPPORT 54 /************************************************************************/ 55 /* 56 * If we have threading support, these routines live in thread.c. 57 */ 58 static struct message *message; /* message structure array */ 59 static int msgCount; /* Count of messages read in */ 60 61 PUBLIC struct message * 62 next_message(struct message *mp) 63 { 64 if (mp + 1 < message || mp + 1 >= message + msgCount) 65 return NULL; 66 67 return mp + 1; 68 } 69 70 PUBLIC struct message * 71 prev_message(struct message *mp) 72 { 73 if (mp - 1 < message || mp - 1 >= message + msgCount) 74 return NULL; 75 76 return mp - 1; 77 } 78 79 PUBLIC struct message * 80 get_message(int msgnum) 81 { 82 if (msgnum < 1 || msgnum > msgCount) 83 return NULL; 84 85 return message + msgnum - 1; 86 } 87 88 PUBLIC int 89 get_msgnum(struct message *mp) 90 { 91 if (mp < message || mp >= message + msgCount) 92 return 0; 93 94 return mp - message + 1; 95 } 96 97 PUBLIC int 98 get_msgCount(void) 99 { 100 return msgCount; 101 } 102 #endif /* THREAD_SUPPORT */ 103 /************************************************************************/ 104 105 /* 106 * Initialize a message structure. 107 */ 108 static void 109 message_init(struct message *mp, off_t offset, short flags) 110 { 111 /* use memset so new fields are always zeroed */ 112 (void)memset(mp, 0, sizeof(*mp)); 113 mp->m_flag = flags; 114 mp->m_block = blockof(offset); 115 mp->m_offset = blkoffsetof(offset); 116 } 117 118 /* 119 * Take the data out of the passed ghost file and toss it into 120 * a dynamically allocated message structure. 121 */ 122 static void 123 makemessage(FILE *f, int omsgCount, int nmsgCount) 124 { 125 size_t size; 126 struct message *omessage; /* old message structure array */ 127 struct message *nmessage; 128 129 omessage = get_abs_message(1); 130 131 size = (nmsgCount + 1) * sizeof(*nmessage); 132 nmessage = realloc(omessage, size); 133 if (nmessage == NULL) 134 err(EXIT_FAILURE, 135 "Insufficient memory for %d messages", nmsgCount); 136 if (omsgCount == 0 || omessage == NULL) 137 dot = nmessage; 138 else 139 dot = nmessage + (dot - omessage); 140 141 thread_fix_old_links(nmessage, omessage, omsgCount); 142 143 #ifndef THREAD_SUPPORT 144 message = nmessage; 145 #endif 146 size -= (omsgCount + 1) * sizeof(*nmessage); 147 (void)fflush(f); 148 (void)lseek(fileno(f), (off_t)sizeof(*nmessage), SEEK_SET); 149 if (read(fileno(f), &nmessage[omsgCount], size) != (ssize_t)size) 150 errx(EXIT_FAILURE, "Message temporary file corrupted"); 151 152 message_init(&nmessage[nmsgCount], (off_t)0, 0); /* append a dummy */ 153 154 thread_fix_new_links(nmessage, omsgCount, nmsgCount); 155 156 (void)Fclose(f); 157 } 158 159 /* 160 * Append the passed message descriptor onto the temp file. 161 * If the write fails, return 1, else 0 162 */ 163 static int 164 append(struct message *mp, FILE *f) 165 { 166 return fwrite(mp, sizeof(*mp), 1, f) != 1; 167 } 168 169 /* 170 * Set up the input pointers while copying the mail file into /tmp. 171 */ 172 PUBLIC void 173 setptr(FILE *ibuf, off_t offset) 174 { 175 int c; 176 size_t len; 177 char *cp; 178 const char *cp2; 179 struct message this; 180 FILE *mestmp; 181 int maybe, inhead; 182 char linebuf[LINESIZE]; 183 int omsgCount; 184 #ifdef THREAD_SUPPORT 185 int nmsgCount; 186 #else 187 # define nmsgCount msgCount 188 #endif 189 190 /* Get temporary file. */ 191 (void)snprintf(linebuf, LINESIZE, "%s/mail.XXXXXX", tmpdir); 192 if ((c = mkstemp(linebuf)) == -1 || 193 (mestmp = Fdopen(c, "ref+")) == NULL) { 194 (void)fprintf(stderr, "mail: can't open %s\n", linebuf); 195 exit(1); 196 } 197 (void)unlink(linebuf); 198 199 nmsgCount = get_abs_msgCount(); 200 if (offset == 0) { 201 nmsgCount = 0; 202 } else { 203 /* Seek into the file to get to the new messages */ 204 (void)fseeko(ibuf, offset, 0); 205 /* 206 * We need to make "offset" a pointer to the end of 207 * the temp file that has the copy of the mail file. 208 * If any messages have been edited, this will be 209 * different from the offset into the mail file. 210 */ 211 (void)fseek(otf, 0L, SEEK_END); 212 offset = ftell(otf); 213 } 214 omsgCount = nmsgCount; 215 maybe = 1; 216 inhead = 0; 217 message_init(&this, (off_t)0, MUSED|MNEW); 218 219 for (;;) { 220 if (fgets(linebuf, LINESIZE, ibuf) == NULL) { 221 if (append(&this, mestmp)) 222 err(EXIT_FAILURE, "temporary file"); 223 makemessage(mestmp, omsgCount, nmsgCount); 224 return; 225 } 226 len = strlen(linebuf); 227 /* 228 * Transforms lines ending in <CR><LF> to just <LF>. 229 * This allows mail to be able to read Eudora mailboxes 230 * that reside on a DOS partition. 231 */ 232 if (len >= 2 && linebuf[len - 1] == '\n' && 233 linebuf[len - 2] == '\r') { 234 linebuf[len - 2] = '\n'; 235 len--; 236 } 237 (void)fwrite(linebuf, sizeof(*linebuf), len, otf); 238 if (ferror(otf)) 239 err(EXIT_FAILURE, "/tmp"); 240 if (len) 241 linebuf[len - 1] = 0; 242 if (maybe && linebuf[0] == 'F' && ishead(linebuf)) { 243 nmsgCount++; 244 if (append(&this, mestmp)) 245 err(EXIT_FAILURE, "temporary file"); 246 message_init(&this, offset, MUSED|MNEW); 247 inhead = 1; 248 } else if (linebuf[0] == 0) { 249 inhead = 0; 250 } else if (inhead) { 251 for (cp = linebuf, cp2 = "status";; cp++) { 252 if ((c = *cp2++) == 0) { 253 while (isspace((unsigned char)*cp++)) 254 continue; 255 if (cp[-1] != ':') 256 break; 257 while ((c = *cp++) != '\0') 258 if (c == 'R') 259 this.m_flag |= MREAD; 260 else if (c == 'O') 261 this.m_flag &= ~MNEW; 262 inhead = 0; 263 break; 264 } 265 if (*cp != c && *cp != toupper(c)) 266 break; 267 } 268 } 269 offset += len; 270 this.m_size += len; 271 this.m_lines++; 272 if (!inhead) { 273 int lines_plus_wraps = 1; 274 int linelen = (int)strlen(linebuf); 275 276 if (screenwidth && (int)linelen > screenwidth) { 277 lines_plus_wraps = linelen / screenwidth; 278 if (linelen % screenwidth != 0) 279 ++lines_plus_wraps; 280 } 281 this.m_blines += lines_plus_wraps; 282 } 283 maybe = linebuf[0] == 0; 284 } 285 } 286 287 /* 288 * Drop the passed line onto the passed output buffer. 289 * If a write error occurs, return -1, else the count of 290 * characters written, including the newline if requested. 291 */ 292 PUBLIC int 293 putline(FILE *obuf, const char *linebuf, int outlf) 294 { 295 size_t c; 296 297 c = strlen(linebuf); 298 (void)fwrite(linebuf, sizeof(*linebuf), c, obuf); 299 if (outlf) { 300 (void)putc('\n', obuf); 301 c++; 302 } 303 if (ferror(obuf)) 304 return -1; 305 return (int)c; 306 } 307 308 /* 309 * Read up a line from the specified input into the line 310 * buffer. Return the number of characters read. Do not 311 * include the newline at the end. 312 */ 313 PUBLIC int 314 readline(FILE *ibuf, char *linebuf, int linesize, int no_restart) 315 { 316 struct sigaction osa_sigtstp; 317 struct sigaction osa_sigttin; 318 struct sigaction osa_sigttou; 319 int n; 320 321 clearerr(ibuf); 322 323 sig_check(); 324 if (no_restart) { 325 (void)sig_setflags(SIGTSTP, 0, &osa_sigtstp); 326 (void)sig_setflags(SIGTTIN, 0, &osa_sigttin); 327 (void)sig_setflags(SIGTTOU, 0, &osa_sigttou); 328 } 329 if (fgets(linebuf, linesize, ibuf) == NULL) 330 n = -1; 331 else { 332 n = (int)strlen(linebuf); 333 if (n > 0 && linebuf[n - 1] == '\n') 334 linebuf[--n] = '\0'; 335 } 336 if (no_restart) { 337 (void)sigaction(SIGTSTP, &osa_sigtstp, NULL); 338 (void)sigaction(SIGTTIN, &osa_sigttin, NULL); 339 (void)sigaction(SIGTTOU, &osa_sigttou, NULL); 340 } 341 sig_check(); 342 return n; 343 } 344 345 /* 346 * Return a file buffer all ready to read up the 347 * passed message pointer. 348 */ 349 PUBLIC FILE * 350 setinput(const struct message *mp) 351 { 352 353 (void)fflush(otf); 354 if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), SEEK_SET) < 0) 355 err(EXIT_FAILURE, "fseek"); 356 return itf; 357 } 358 359 /* 360 * Delete a file, but only if the file is a plain file. 361 */ 362 PUBLIC int 363 rm(char *name) 364 { 365 struct stat sb; 366 367 if (stat(name, &sb) < 0) 368 return -1; 369 if (!S_ISREG(sb.st_mode)) { 370 errno = EISDIR; 371 return -1; 372 } 373 return unlink(name); 374 } 375 376 /* 377 * Determine the size of the file possessed by 378 * the passed buffer. 379 */ 380 PUBLIC off_t 381 fsize(FILE *iob) 382 { 383 struct stat sbuf; 384 385 if (fstat(fileno(iob), &sbuf) < 0) 386 return 0; 387 return sbuf.st_size; 388 } 389 390 /* 391 * Determine the current folder directory name. 392 */ 393 PUBLIC int 394 getfold(char *name, size_t namesize) 395 { 396 char unres[PATHSIZE], res[PATHSIZE]; 397 char *folder; 398 399 if ((folder = value(ENAME_FOLDER)) == NULL) 400 return -1; 401 if (*folder != '/') { 402 (void)snprintf(unres, sizeof(unres), "%s/%s", homedir, folder); 403 folder = unres; 404 } 405 if (realpath(folder, res) == NULL) 406 warn("Can't canonicalize folder `%s'", folder); 407 else 408 folder = res; 409 (void)strlcpy(name, folder, namesize); 410 return 0; 411 } 412 413 /* 414 * Evaluate the string given as a new mailbox name. 415 * Supported meta characters: 416 * % for my system mail box 417 * %user for user's system mail box 418 * # for previous file 419 * & invoker's mbox file 420 * +file file in folder directory 421 * any shell meta character 422 * Return the file name as a dynamic string. 423 */ 424 PUBLIC const char * 425 expand(const char *name) 426 { 427 char xname[PATHSIZE]; 428 char cmdbuf[PATHSIZE]; 429 int e; 430 wordexp_t we; 431 sigset_t nset, oset; 432 433 /* 434 * The order of evaluation is "%" and "#" expand into constants. 435 * "&" can expand into "+". "+" can expand into shell meta characters. 436 * Shell meta characters expand into constants. 437 * This way, we make no recursive expansion. 438 */ 439 switch (*name) { 440 case '%': 441 findmail(name[1] ? name + 1 : myname, xname, sizeof(xname)); 442 return savestr(xname); 443 case '#': 444 if (name[1] != 0) 445 break; 446 if (prevfile[0] == 0) { 447 warnx("No previous file"); 448 return NULL; 449 } 450 return savestr(prevfile); 451 case '&': 452 if (name[1] == 0 && (name = value(ENAME_MBOX)) == NULL) 453 name = "~/mbox"; 454 /* fall through */ 455 } 456 if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) { 457 (void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1); 458 name = savestr(xname); 459 } 460 /* catch the most common shell meta character */ 461 if (name[0] == '~' && (name[1] == '/' || name[1] == '\0')) { 462 (void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1); 463 name = savestr(xname); 464 } 465 if (strpbrk(name, "~{[*?$`'\"\\") == NULL) 466 return name; 467 468 *xname = '\0'; 469 470 sigemptyset(&nset); 471 sigaddset(&nset, SIGCHLD); 472 sigprocmask(SIG_BLOCK, &nset, &oset); 473 e = wordexp(name, &we, WRDE_NOCMD); 474 sigprocmask(SIG_SETMASK, &oset, NULL); 475 476 switch (e) { 477 case 0: /* OK */ 478 break; 479 case WRDE_NOSPACE: 480 warnx("Out of memory expanding `%s'", name); 481 return NULL; 482 case WRDE_BADVAL: 483 case WRDE_BADCHAR: 484 case WRDE_SYNTAX: 485 warnx("Syntax error expanding `%s'", name); 486 return NULL; 487 case WRDE_CMDSUB: 488 warnx("Command substitution not allowed expanding `%s'", 489 name); 490 return NULL; 491 default: 492 warnx("Unknown expansion error %d expanding `%s'", e, name); 493 return NULL; 494 } 495 496 switch (we.we_wordc) { 497 case 0: 498 warnx("No match for `%s'", name); 499 break; 500 case 1: 501 if (strlen(we.we_wordv[0]) >= PATHSIZE) 502 warnx("Expansion too long for `%s'", name); 503 strlcpy(xname, we.we_wordv[0], PATHSIZE); 504 break; 505 default: 506 warnx("Ambiguous expansion for `%s'", name); 507 break; 508 } 509 510 wordfree(&we); 511 if (!*xname) 512 return NULL; 513 else 514 return savestr(xname); 515 } 516 517 /* 518 * Return the name of the dead.letter file. 519 */ 520 PUBLIC const char * 521 getdeadletter(void) 522 { 523 const char *cp; 524 525 if ((cp = value(ENAME_DEAD)) == NULL || (cp = expand(cp)) == NULL) 526 cp = expand("~/dead.letter"); 527 else if (*cp != '/') { 528 char buf[PATHSIZE]; 529 (void)snprintf(buf, sizeof(buf), "~/%s", cp); 530 cp = expand(buf); 531 } 532 return cp; 533 } 534