1 /* $NetBSD: fio.c,v 1.44 2023/08/10 20:36:28 mrg 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.44 2023/08/10 20:36:28 mrg 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 ptrdiff_t off; 129 130 omessage = get_abs_message(1); 131 132 size = (nmsgCount + 1) * sizeof(*nmessage); 133 134 if (omsgCount == 0 || omessage == NULL) 135 off = 0; 136 else 137 off = dot - omessage; 138 nmessage = realloc(omessage, size); 139 if (nmessage == NULL) 140 err(EXIT_FAILURE, 141 "Insufficient memory for %d messages", nmsgCount); 142 dot = nmessage + off; 143 144 thread_fix_old_links(nmessage, off, omsgCount); 145 146 #ifndef THREAD_SUPPORT 147 message = nmessage; 148 #endif 149 size -= (omsgCount + 1) * sizeof(*nmessage); 150 (void)fflush(f); 151 (void)lseek(fileno(f), (off_t)sizeof(*nmessage), SEEK_SET); 152 if (read(fileno(f), &nmessage[omsgCount], size) != (ssize_t)size) 153 errx(EXIT_FAILURE, "Message temporary file corrupted"); 154 155 message_init(&nmessage[nmsgCount], (off_t)0, 0); /* append a dummy */ 156 157 thread_fix_new_links(nmessage, omsgCount, nmsgCount); 158 159 (void)Fclose(f); 160 } 161 162 /* 163 * Append the passed message descriptor onto the temp file. 164 * If the write fails, return 1, else 0 165 */ 166 static int 167 append(struct message *mp, FILE *f) 168 { 169 return fwrite(mp, sizeof(*mp), 1, f) != 1; 170 } 171 172 /* 173 * Set up the input pointers while copying the mail file into /tmp. 174 */ 175 PUBLIC void 176 setptr(FILE *ibuf, off_t offset) 177 { 178 int c; 179 size_t len; 180 char *cp; 181 const char *cp2; 182 struct message this; 183 FILE *mestmp; 184 int maybe, inhead; 185 char linebuf[LINESIZE]; 186 int omsgCount; 187 #ifdef THREAD_SUPPORT 188 int nmsgCount; 189 #else 190 # define nmsgCount msgCount 191 #endif 192 193 /* Get temporary file. */ 194 (void)snprintf(linebuf, LINESIZE, "%s/mail.XXXXXX", tmpdir); 195 if ((c = mkstemp(linebuf)) == -1 || 196 (mestmp = Fdopen(c, "ref+")) == NULL) { 197 (void)fprintf(stderr, "mail: can't open %s\n", linebuf); 198 exit(1); 199 } 200 (void)unlink(linebuf); 201 202 nmsgCount = get_abs_msgCount(); 203 if (offset == 0) { 204 nmsgCount = 0; 205 } else { 206 /* Seek into the file to get to the new messages */ 207 (void)fseeko(ibuf, offset, 0); 208 /* 209 * We need to make "offset" a pointer to the end of 210 * the temp file that has the copy of the mail file. 211 * If any messages have been edited, this will be 212 * different from the offset into the mail file. 213 */ 214 (void)fseek(otf, 0L, SEEK_END); 215 offset = ftell(otf); 216 } 217 omsgCount = nmsgCount; 218 maybe = 1; 219 inhead = 0; 220 message_init(&this, (off_t)0, MUSED|MNEW); 221 222 for (;;) { 223 if (fgets(linebuf, LINESIZE, ibuf) == NULL) { 224 if (append(&this, mestmp)) 225 err(EXIT_FAILURE, "temporary file"); 226 makemessage(mestmp, omsgCount, nmsgCount); 227 return; 228 } 229 len = strlen(linebuf); 230 /* 231 * Transforms lines ending in <CR><LF> to just <LF>. 232 * This allows mail to be able to read Eudora mailboxes 233 * that reside on a DOS partition. 234 */ 235 if (len >= 2 && linebuf[len - 1] == '\n' && 236 linebuf[len - 2] == '\r') { 237 linebuf[len - 2] = '\n'; 238 len--; 239 } 240 (void)fwrite(linebuf, sizeof(*linebuf), len, otf); 241 if (ferror(otf)) 242 err(EXIT_FAILURE, "/tmp"); 243 if (len) 244 linebuf[len - 1] = 0; 245 if (maybe && linebuf[0] == 'F' && ishead(linebuf)) { 246 nmsgCount++; 247 if (append(&this, mestmp)) 248 err(EXIT_FAILURE, "temporary file"); 249 message_init(&this, offset, MUSED|MNEW); 250 inhead = 1; 251 } else if (linebuf[0] == 0) { 252 inhead = 0; 253 } else if (inhead) { 254 for (cp = linebuf, cp2 = "status";; cp++) { 255 if ((c = *cp2++) == 0) { 256 while (isspace((unsigned char)*cp++)) 257 continue; 258 if (cp[-1] != ':') 259 break; 260 while ((c = *cp++) != '\0') 261 if (c == 'R') 262 this.m_flag |= MREAD; 263 else if (c == 'O') 264 this.m_flag &= ~MNEW; 265 inhead = 0; 266 break; 267 } 268 if (*cp != c && *cp != toupper(c)) 269 break; 270 } 271 } 272 offset += len; 273 this.m_size += len; 274 this.m_lines++; 275 if (!inhead) { 276 int lines_plus_wraps = 1; 277 int linelen = (int)strlen(linebuf); 278 279 if (screenwidth && (int)linelen > screenwidth) { 280 lines_plus_wraps = linelen / screenwidth; 281 if (linelen % screenwidth != 0) 282 ++lines_plus_wraps; 283 } 284 this.m_blines += lines_plus_wraps; 285 } 286 maybe = linebuf[0] == 0; 287 } 288 } 289 290 /* 291 * Drop the passed line onto the passed output buffer. 292 * If a write error occurs, return -1, else the count of 293 * characters written, including the newline if requested. 294 */ 295 PUBLIC int 296 putline(FILE *obuf, const char *linebuf, int outlf) 297 { 298 size_t c; 299 300 c = strlen(linebuf); 301 (void)fwrite(linebuf, sizeof(*linebuf), c, obuf); 302 if (outlf) { 303 (void)putc('\n', obuf); 304 c++; 305 } 306 if (ferror(obuf)) 307 return -1; 308 return (int)c; 309 } 310 311 /* 312 * Read up a line from the specified input into the line 313 * buffer. Return the number of characters read. Do not 314 * include the newline at the end. 315 */ 316 PUBLIC int 317 readline(FILE *ibuf, char *linebuf, int linesize, int no_restart) 318 { 319 struct sigaction osa_sigtstp; 320 struct sigaction osa_sigttin; 321 struct sigaction osa_sigttou; 322 int n; 323 324 clearerr(ibuf); 325 326 sig_check(); 327 if (no_restart) { 328 (void)sig_setflags(SIGTSTP, 0, &osa_sigtstp); 329 (void)sig_setflags(SIGTTIN, 0, &osa_sigttin); 330 (void)sig_setflags(SIGTTOU, 0, &osa_sigttou); 331 } 332 if (fgets(linebuf, linesize, ibuf) == NULL) 333 n = -1; 334 else { 335 n = (int)strlen(linebuf); 336 if (n > 0 && linebuf[n - 1] == '\n') 337 linebuf[--n] = '\0'; 338 } 339 if (no_restart) { 340 (void)sigaction(SIGTSTP, &osa_sigtstp, NULL); 341 (void)sigaction(SIGTTIN, &osa_sigttin, NULL); 342 (void)sigaction(SIGTTOU, &osa_sigttou, NULL); 343 } 344 sig_check(); 345 return n; 346 } 347 348 /* 349 * Return a file buffer all ready to read up the 350 * passed message pointer. 351 */ 352 PUBLIC FILE * 353 setinput(const struct message *mp) 354 { 355 356 (void)fflush(otf); 357 if (fseek(itf, (long)positionof(mp->m_block, mp->m_offset), SEEK_SET) < 0) 358 err(EXIT_FAILURE, "fseek"); 359 return itf; 360 } 361 362 /* 363 * Delete a file, but only if the file is a plain file. 364 */ 365 PUBLIC int 366 rm(char *name) 367 { 368 struct stat sb; 369 370 if (stat(name, &sb) < 0) 371 return -1; 372 if (!S_ISREG(sb.st_mode)) { 373 errno = EISDIR; 374 return -1; 375 } 376 return unlink(name); 377 } 378 379 /* 380 * Determine the size of the file possessed by 381 * the passed buffer. 382 */ 383 PUBLIC off_t 384 fsize(FILE *iob) 385 { 386 struct stat sbuf; 387 388 if (fstat(fileno(iob), &sbuf) < 0) 389 return 0; 390 return sbuf.st_size; 391 } 392 393 /* 394 * Determine the current folder directory name. 395 */ 396 PUBLIC int 397 getfold(char *name, size_t namesize) 398 { 399 char unres[PATHSIZE], res[PATHSIZE]; 400 char *folder; 401 402 if ((folder = value(ENAME_FOLDER)) == NULL) 403 return -1; 404 if (*folder != '/') { 405 (void)snprintf(unres, sizeof(unres), "%s/%s", homedir, folder); 406 folder = unres; 407 } 408 if (realpath(folder, res) == NULL) 409 warn("Can't canonicalize folder `%s'", folder); 410 else 411 folder = res; 412 (void)strlcpy(name, folder, namesize); 413 return 0; 414 } 415 416 /* 417 * Evaluate the string given as a new mailbox name. 418 * Supported meta characters: 419 * % for my system mail box 420 * %user for user's system mail box 421 * # for previous file 422 * & invoker's mbox file 423 * +file file in folder directory 424 * any shell meta character 425 * Return the file name as a dynamic string. 426 */ 427 PUBLIC const char * 428 expand(const char *name) 429 { 430 char xname[PATHSIZE]; 431 char cmdbuf[PATHSIZE]; 432 int e; 433 wordexp_t we; 434 sigset_t nset, oset; 435 436 /* 437 * The order of evaluation is "%" and "#" expand into constants. 438 * "&" can expand into "+". "+" can expand into shell meta characters. 439 * Shell meta characters expand into constants. 440 * This way, we make no recursive expansion. 441 */ 442 switch (*name) { 443 case '%': 444 findmail(name[1] ? name + 1 : myname, xname, sizeof(xname)); 445 return savestr(xname); 446 case '#': 447 if (name[1] != 0) 448 break; 449 if (prevfile[0] == 0) { 450 warnx("No previous file"); 451 return NULL; 452 } 453 return savestr(prevfile); 454 case '&': 455 if (name[1] == 0 && (name = value(ENAME_MBOX)) == NULL) 456 name = "~/mbox"; 457 /* fall through */ 458 } 459 if (name[0] == '+' && getfold(cmdbuf, sizeof(cmdbuf)) >= 0) { 460 (void)snprintf(xname, sizeof(xname), "%s/%s", cmdbuf, name + 1); 461 name = savestr(xname); 462 } 463 /* catch the most common shell meta character */ 464 if (name[0] == '~' && (name[1] == '/' || name[1] == '\0')) { 465 (void)snprintf(xname, sizeof(xname), "%s%s", homedir, name + 1); 466 name = savestr(xname); 467 } 468 if (strpbrk(name, "~{[*?$`'\"\\") == NULL) 469 return name; 470 471 *xname = '\0'; 472 473 sigemptyset(&nset); 474 sigaddset(&nset, SIGCHLD); 475 sigprocmask(SIG_BLOCK, &nset, &oset); 476 e = wordexp(name, &we, WRDE_NOCMD); 477 sigprocmask(SIG_SETMASK, &oset, NULL); 478 479 switch (e) { 480 case 0: /* OK */ 481 break; 482 case WRDE_NOSPACE: 483 warnx("Out of memory expanding `%s'", name); 484 return NULL; 485 case WRDE_BADVAL: 486 case WRDE_BADCHAR: 487 case WRDE_SYNTAX: 488 warnx("Syntax error expanding `%s'", name); 489 return NULL; 490 case WRDE_CMDSUB: 491 warnx("Command substitution not allowed expanding `%s'", 492 name); 493 return NULL; 494 default: 495 warnx("Unknown expansion error %d expanding `%s'", e, name); 496 return NULL; 497 } 498 499 switch (we.we_wordc) { 500 case 0: 501 warnx("No match for `%s'", name); 502 break; 503 case 1: 504 if (strlen(we.we_wordv[0]) >= PATHSIZE) 505 warnx("Expansion too long for `%s'", name); 506 strlcpy(xname, we.we_wordv[0], PATHSIZE); 507 break; 508 default: 509 warnx("Ambiguous expansion for `%s'", name); 510 break; 511 } 512 513 wordfree(&we); 514 if (!*xname) 515 return NULL; 516 else 517 return savestr(xname); 518 } 519 520 /* 521 * Return the name of the dead.letter file. 522 */ 523 PUBLIC const char * 524 getdeadletter(void) 525 { 526 const char *cp; 527 528 if ((cp = value(ENAME_DEAD)) == NULL || (cp = expand(cp)) == NULL) 529 cp = expand("~/dead.letter"); 530 else if (*cp != '/') { 531 char buf[PATHSIZE]; 532 (void)snprintf(buf, sizeof(buf), "~/%s", cp); 533 cp = expand(buf); 534 } 535 return cp; 536 } 537