1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 23 /* All Rights Reserved */ 24 25 /* 26 * Copyright (c) 1999 by Sun Microsystems, Inc. 27 * All rights reserved. 28 */ 29 30 #ident "%Z%%M% %I% %E% SMI" /* from SVr4.0 1.13.2.2 */ 31 32 #include "rcv.h" 33 #include <locale.h> 34 #include <wordexp.h> 35 36 /* 37 * mailx -- a modified version of a University of California at Berkeley 38 * mail program 39 * 40 * File I/O. 41 */ 42 43 static int getln(char *line, int max, FILE *f); 44 static int linecount(char *lp, long size); 45 46 /* 47 * Set up the input pointers while copying the mail file into 48 * /tmp. 49 */ 50 51 void 52 setptr(register FILE *ibuf) 53 { 54 int n, newline = 1, blankline = 1; 55 int StartNewMsg = TRUE; 56 int ToldUser = FALSE; 57 long clen = -1L; 58 int hdr = 0; 59 int cflg = 0; /* found Content-length in header */ 60 register char *cp; 61 register int l; 62 register long s; 63 off_t offset; 64 char linebuf[LINESIZE]; 65 int inhead, newmail, Odot; 66 short flag; 67 68 if (!space) { 69 msgCount = 0; 70 offset = 0; 71 space = 32; 72 newmail = 0; 73 message = 74 (struct message *)calloc(space, sizeof (struct message)); 75 if (message == NULL) { 76 fprintf(stderr, gettext( 77 "calloc: insufficient memory for %d messages\n"), 78 space); 79 exit(1); 80 /* NOTREACHED */ 81 } 82 dot = message; 83 } else { 84 newmail = 1; 85 offset = fsize(otf); 86 } 87 s = 0L; 88 l = 0; 89 /* 90 * Set default flags. When reading from 91 * a folder, assume the message has been 92 * previously read. 93 */ 94 if (edit) 95 flag = MUSED|MREAD; 96 else 97 flag = MUSED|MNEW; 98 99 inhead = 0; 100 while ((n = getln(linebuf, sizeof (linebuf), ibuf)) > 0) { 101 if (!newline) { 102 goto putout; 103 } 104 top: 105 hdr = inhead && (headerp(linebuf) || 106 (linebuf[0] == ' ' || linebuf[0] == '\t')); 107 if (!hdr && cflg) { /* nonheader, Content-length seen */ 108 if (clen > 0 && clen < n) { /* read too much */ 109 /* 110 * NB: this only can happen if there is a 111 * small content that is NOT \n terminated 112 * and has no leading blank line, i.e., never. 113 */ 114 if (fwrite(linebuf, 1, (int)clen, otf) != 115 clen) { 116 fclose(ibuf); 117 fflush(otf); 118 } else { 119 l += linecount(linebuf, clen); 120 } 121 offset += clen; 122 s += clen; 123 n -= (int)clen; 124 /* shift line to the left, copy null as well */ 125 memcpy(linebuf, linebuf+clen, n+1); 126 cflg = 0; 127 message[msgCount-1].m_clen = clen + 1; 128 blankline = 1; 129 StartNewMsg = TRUE; 130 goto top; 131 } 132 /* here, clen == 0 or clen >= n */ 133 if (n == 1 && linebuf[0] == '\n') { 134 /* leading empty line */ 135 clen++; /* cheat */ 136 inhead = 0; 137 } 138 offset += clen; 139 s += (long)clen; 140 message[msgCount-1].m_clen = clen; 141 for (;;) { 142 if (fwrite(linebuf, 1, n, otf) != n) { 143 fclose(ibuf); 144 fflush(otf); 145 } else { 146 l += linecount(linebuf, n); 147 } 148 clen -= n; 149 if (clen <= 0) { 150 break; 151 } 152 n = clen < sizeof (linebuf) ? 153 (int)clen : (int)sizeof (linebuf); 154 if ((n = fread(linebuf, 1, n, ibuf)) <= 0) { 155 fprintf(stderr, gettext( 156 "%s:\tYour mailfile was found to be corrupted.\n"), 157 progname); 158 fprintf(stderr, gettext( 159 "\t(Unexpected end-of-file).\n")); 160 fprintf(stderr, gettext( 161 "\tMessage #%d may be truncated.\n\n"), 162 msgCount); 163 offset -= clen; 164 s -= clen; 165 clen = 0; /* stop the loop */ 166 } 167 } 168 /* All done, go to top for next message */ 169 cflg = 0; 170 blankline = 1; 171 StartNewMsg = TRUE; 172 continue; 173 } 174 175 /* Look for a From line that starts a new message */ 176 if (blankline && linebuf[0] == 'F' && ishead(linebuf)) { 177 if (msgCount > 0 && !newmail) { 178 message[msgCount-1].m_size = s; 179 message[msgCount-1].m_lines = l; 180 message[msgCount-1].m_flag = flag; 181 } 182 if (msgCount >= space) { 183 /* 184 * Limit the speed at which the 185 * allocated space grows. 186 */ 187 if (space < 512) 188 space = space*2; 189 else 190 space += 512; 191 errno = 0; 192 Odot = dot - &(message[0]); 193 message = (struct message *) 194 realloc(message, 195 space*(sizeof (struct message))); 196 if (message == NULL) { 197 perror("realloc failed"); 198 fprintf(stderr, gettext( 199 "realloc: insufficient memory for %d messages\n"), 200 space); 201 exit(1); 202 } 203 dot = &message[Odot]; 204 } 205 message[msgCount].m_offset = offset; 206 message[msgCount].m_text = TRUE; 207 message[msgCount].m_clen = 0; 208 newmail = 0; 209 msgCount++; 210 if (edit) 211 flag = MUSED|MREAD; 212 else 213 flag = MUSED|MNEW; 214 inhead = 1; 215 s = 0L; 216 l = 0; 217 StartNewMsg = FALSE; 218 ToldUser = FALSE; 219 goto putout; 220 } 221 222 /* if didn't get a header line, we're no longer in the header */ 223 if (!hdr) 224 inhead = 0; 225 if (!inhead) 226 goto putout; 227 228 /* 229 * Look for Status: line. Do quick check for second character, 230 * many headers start with "S" but few have "t" as second char. 231 */ 232 if ((linebuf[1] == 't' || linebuf[1] == 'T') && 233 ishfield(linebuf, "status")) { 234 cp = hcontents(linebuf); 235 flag = MUSED|MNEW; 236 if (strchr(cp, 'R')) 237 flag |= MREAD; 238 if (strchr(cp, 'O')) 239 flag &= ~MNEW; 240 } 241 /* 242 * Look for Content-Length and Content-Type headers. Like 243 * above, do a quick check for the "-", which is rare. 244 */ 245 if (linebuf[7] == '-') { 246 if (ishfield(linebuf, "content-length")) { 247 if (!cflg) { 248 clen = atol(hcontents(linebuf)); 249 cflg = clen >= 0; 250 } 251 } else if (ishfield(linebuf, "content-type")) { 252 char word[LINESIZE]; 253 char *cp2; 254 255 cp = hcontents(linebuf); 256 cp2 = word; 257 while (!isspace(*cp)) 258 *cp2++ = *cp++; 259 *cp2 = '\0'; 260 if (icequal(word, "binary")) 261 message[msgCount-1].m_text = FALSE; 262 } 263 } 264 putout: 265 offset += n; 266 s += (long)n; 267 if (fwrite(linebuf, 1, n, otf) != n) { 268 fclose(ibuf); 269 fflush(otf); 270 } else { 271 l++; 272 } 273 if (ferror(otf)) { 274 perror("/tmp"); 275 exit(1); 276 } 277 if (msgCount == 0) { 278 fclose(ibuf); 279 fflush(otf); 280 } 281 if (linebuf[n-1] == '\n') { 282 blankline = newline && n == 1; 283 newline = 1; 284 if (n == 1) { 285 /* Blank line. Skip StartNewMsg check below */ 286 continue; 287 } 288 } else { 289 newline = 0; 290 } 291 if (StartNewMsg && !ToldUser) { 292 fprintf(stderr, gettext( 293 "%s:\tYour mailfile was found to be corrupted\n"), 294 progname); 295 fprintf(stderr, 296 gettext("\t(Content-length mismatch).\n")); 297 fprintf(stderr, gettext( 298 "\tMessage #%d may be truncated,\n"), msgCount); 299 fprintf(stderr, gettext( 300 "\twith another message concatenated to it.\n\n")); 301 ToldUser = TRUE; 302 } 303 } 304 305 if (n == 0) { 306 fflush(otf); 307 if (fferror(otf)) { 308 perror("/tmp"); 309 exit(1); 310 } 311 if (msgCount) { 312 message[msgCount-1].m_size = s; 313 message[msgCount-1].m_lines = l; 314 message[msgCount-1].m_flag = flag; 315 } 316 fclose(ibuf); 317 } 318 } 319 320 /* 321 * Compute the content length of a message and set it into m_clen. 322 */ 323 324 void 325 setclen(register struct message *mp) 326 { 327 long c; 328 FILE *ibuf; 329 char line[LINESIZE]; 330 int fline, nread; 331 332 ibuf = setinput(mp); 333 c = mp->m_size; 334 fline = 1; 335 while (c > 0L) { 336 nread = getln(line, sizeof (line), ibuf); 337 c -= nread; 338 /* 339 * First line is the From line, so no headers 340 * there to worry about. 341 */ 342 if (fline) { 343 fline = 0; 344 continue; 345 } 346 /* 347 * If line is blank, we've reached end of headers. 348 */ 349 if (line[0] == '\n') 350 break; 351 /* 352 * If this line is a continuation 353 * of a previous header field, keep going. 354 */ 355 if (isspace(line[0])) 356 continue; 357 /* 358 * If we are no longer looking at real 359 * header lines, we're done. 360 * This happens in uucp style mail where 361 * there are no headers at all. 362 */ 363 if (!headerp(line)) { 364 c += nread; 365 break; 366 } 367 } 368 if (c == 0) 369 c = 1; 370 mp->m_clen = c; 371 } 372 373 static int 374 getln(char *line, int max, FILE *f) 375 { 376 register int c; 377 register char *cp, *ecp; 378 379 cp = line; 380 ecp = cp + max - 1; 381 while (cp < ecp && (c = getc(f)) != EOF) 382 if ((*cp++ = (char)c) == '\n') 383 break; 384 *cp = '\0'; 385 return (cp - line); 386 } 387 388 /* 389 * Read up a line from the specified input into the line 390 * buffer. Return the number of characters read. Do not 391 * include the newline at the end. 392 */ 393 394 readline(FILE *ibuf, char *linebuf) 395 { 396 register char *cp; 397 register int c; 398 int seennulls = 0; 399 400 clearerr(ibuf); 401 c = getc(ibuf); 402 for (cp = linebuf; c != '\n' && c != EOF; c = getc(ibuf)) { 403 if (c == 0) { 404 if (!seennulls) { 405 fprintf(stderr, 406 gettext("mailx: NUL changed to @\n")); 407 seennulls++; 408 } 409 c = '@'; 410 } 411 if (cp - linebuf < LINESIZE-2) 412 *cp++ = (char)c; 413 } 414 *cp = 0; 415 if (c == EOF && cp == linebuf) 416 return (0); 417 return (cp - linebuf + 1); 418 } 419 420 /* 421 * linecount - determine the number of lines in a printable file. 422 */ 423 424 static int 425 linecount(char *lp, long size) 426 { 427 register char *cp, *ecp; 428 register int count; 429 430 count = 0; 431 cp = lp; 432 ecp = cp + size; 433 while (cp < ecp) 434 if (*cp++ == '\n') 435 count++; 436 return (count); 437 } 438 439 /* 440 * Return a file buffer all ready to read up the 441 * passed message pointer. 442 */ 443 444 FILE * 445 setinput(register struct message *mp) 446 { 447 fflush(otf); 448 if (fseek(itf, mp->m_offset, 0) < 0) { 449 perror("fseek"); 450 panic("temporary file seek"); 451 } 452 return (itf); 453 } 454 455 456 /* 457 * Delete a file, but only if the file is a plain file. 458 */ 459 460 int 461 removefile(char name[]) 462 { 463 struct stat statb; 464 extern int errno; 465 466 if (stat(name, &statb) < 0) 467 if (errno == ENOENT) 468 return (0); /* it's already gone, no error */ 469 else 470 return (-1); 471 if ((statb.st_mode & S_IFMT) != S_IFREG) { 472 errno = EISDIR; 473 return (-1); 474 } 475 return (unlink(name)); 476 } 477 478 /* 479 * Terminate an editing session by attempting to write out the user's 480 * file from the temporary. Save any new stuff appended to the file. 481 */ 482 int 483 edstop( 484 int noremove /* don't allow the file to be removed, trunc instead */ 485 ) 486 { 487 register int gotcha, c; 488 register struct message *mp; 489 FILE *obuf, *ibuf, *tbuf = 0, *readstat; 490 struct stat statb; 491 char tempname[STSIZ], *id; 492 int tmpfd = -1; 493 494 if (readonly) 495 return (0); 496 holdsigs(); 497 if (Tflag != NOSTR) { 498 if ((readstat = fopen(Tflag, "w")) == NULL) 499 Tflag = NOSTR; 500 } 501 for (mp = &message[0], gotcha = 0; mp < &message[msgCount]; mp++) { 502 if (mp->m_flag & MNEW) { 503 mp->m_flag &= ~MNEW; 504 mp->m_flag |= MSTATUS; 505 } 506 if (mp->m_flag & (MODIFY|MDELETED|MSTATUS)) 507 gotcha++; 508 if (Tflag != NOSTR && (mp->m_flag & (MREAD|MDELETED)) != 0) { 509 if ((id = hfield("article-id", mp, addone)) != NOSTR) 510 fprintf(readstat, "%s\n", id); 511 } 512 } 513 if (Tflag != NOSTR) 514 fclose(readstat); 515 if (!gotcha || Tflag != NOSTR) 516 goto done; 517 if ((ibuf = fopen(editfile, "r+")) == NULL) { 518 perror(editfile); 519 relsesigs(); 520 longjmp(srbuf, 1); 521 } 522 lock(ibuf, "r+", 1); 523 if (fstat(fileno(ibuf), &statb) >= 0 && statb.st_size > mailsize) { 524 nstrcpy(tempname, STSIZ, "/tmp/mboxXXXXXX"); 525 if ((tmpfd = mkstemp(tempname)) == -1) { 526 perror(tempname); 527 fclose(ibuf); 528 relsesigs(); 529 longjmp(srbuf, 1); 530 } 531 if ((obuf = fdopen(tmpfd, "w")) == NULL) { 532 perror(tempname); 533 fclose(ibuf); 534 removefile(tempname); 535 relsesigs(); 536 (void) close(tmpfd); 537 longjmp(srbuf, 1); 538 } 539 fseek(ibuf, mailsize, 0); 540 while ((c = getc(ibuf)) != EOF) 541 putc(c, obuf); 542 fclose(obuf); 543 if ((tbuf = fopen(tempname, "r")) == NULL) { 544 perror(tempname); 545 fclose(ibuf); 546 removefile(tempname); 547 relsesigs(); 548 longjmp(srbuf, 1); 549 } 550 removefile(tempname); 551 } 552 if ((obuf = fopen(editfile, "r+")) == NULL) { 553 if ((obuf = fopen(editfile, "w")) == NULL) { 554 perror(editfile); 555 fclose(ibuf); 556 if (tbuf) 557 fclose(tbuf); 558 relsesigs(); 559 longjmp(srbuf, 1); 560 } 561 } 562 printf("\"%s\" ", editfile); 563 flush(); 564 c = 0; 565 for (mp = &message[0]; mp < &message[msgCount]; mp++) { 566 if ((mp->m_flag & MDELETED) != 0) 567 continue; 568 c++; 569 if (msend(mp, obuf, 0, fputs) < 0) { 570 perror(editfile); 571 fclose(ibuf); 572 fclose(obuf); 573 if (tbuf) 574 fclose(tbuf); 575 relsesigs(); 576 longjmp(srbuf, 1); 577 } 578 } 579 gotcha = (c == 0 && tbuf == NULL); 580 if (tbuf != NULL) { 581 while ((c = getc(tbuf)) != EOF) 582 putc(c, obuf); 583 fclose(tbuf); 584 } 585 fflush(obuf); 586 if (fferror(obuf)) { 587 perror(editfile); 588 fclose(ibuf); 589 fclose(obuf); 590 relsesigs(); 591 longjmp(srbuf, 1); 592 } 593 if (gotcha && !noremove && (value("keep") == NOSTR)) { 594 removefile(editfile); 595 printf(gettext("removed.\n")); 596 } 597 else 598 printf(gettext("updated.\n")); 599 fclose(ibuf); 600 trunc(obuf); 601 fclose(obuf); 602 flush(); 603 604 done: 605 relsesigs(); 606 return (1); 607 } 608 609 #ifndef OLD_BSD_SIGS 610 static int sigdepth = 0; /* depth of holdsigs() */ 611 #ifdef VMUNIX 612 static int omask = 0; 613 #else 614 static sigset_t mask, omask; 615 #endif 616 #endif 617 /* 618 * Hold signals SIGHUP - SIGQUIT. 619 */ 620 void 621 holdsigs(void) 622 { 623 #ifndef OLD_BSD_SIGS 624 if (sigdepth++ == 0) { 625 #ifdef VMUNIX 626 omask = sigblock(sigmask(SIGHUP) | 627 sigmask(SIGINT)|sigmask(SIGQUIT)); 628 #else 629 sigemptyset(&mask); 630 sigaddset(&mask, SIGHUP); 631 sigaddset(&mask, SIGINT); 632 sigaddset(&mask, SIGQUIT); 633 sigprocmask(SIG_BLOCK, &mask, &omask); 634 #endif 635 } 636 #else 637 sighold(SIGHUP); 638 sighold(SIGINT); 639 sighold(SIGQUIT); 640 #endif 641 } 642 643 /* 644 * Release signals SIGHUP - SIGQUIT 645 */ 646 void 647 relsesigs(void) 648 { 649 #ifndef OLD_BSD_SIGS 650 if (--sigdepth == 0) 651 #ifdef VMUNIX 652 sigsetmask(omask); 653 #else 654 sigprocmask(SIG_SETMASK, &omask, NULL); 655 #endif 656 #else 657 sigrelse(SIGHUP); 658 sigrelse(SIGINT); 659 sigrelse(SIGQUIT); 660 #endif 661 } 662 663 #if !defined(OLD_BSD_SIGS) && !defined(VMUNIX) 664 void 665 (*sigset(int sig, void (*act)(int)))(int) 666 { 667 struct sigaction sa, osa; 668 669 sa.sa_handler = (void (*)())act; 670 sigemptyset(&sa.sa_mask); 671 sa.sa_flags = SA_RESTART; 672 if (sigaction(sig, &sa, &osa) < 0) 673 return ((void (*)(int))-1); 674 return ((void (*)(int))osa.sa_handler); 675 } 676 #endif 677 678 /* 679 * Flush the standard output. 680 */ 681 682 void 683 flush(void) 684 { 685 fflush(stdout); 686 fflush(stderr); 687 } 688 689 /* 690 * Determine the size of the file possessed by 691 * the passed buffer. 692 */ 693 694 off_t 695 fsize(FILE *iob) 696 { 697 register int f; 698 struct stat sbuf; 699 700 f = fileno(iob); 701 if (fstat(f, &sbuf) < 0) 702 return (0); 703 return (sbuf.st_size); 704 } 705 706 /* 707 * Check for either a stdio recognized error, or 708 * a possibly delayed write error (in case it's 709 * an NFS file, for instance). 710 */ 711 712 int 713 fferror(FILE *iob) 714 { 715 return (ferror(iob) || fsync(fileno(iob)) < 0); 716 } 717 718 /* 719 * Take a file name, possibly with shell meta characters 720 * in it and expand it by using wordexp(). 721 * Return the file name as a dynamic string. 722 * If the name cannot be expanded (for whatever reason) 723 * return NULL. 724 */ 725 726 char * 727 expand(char *name) 728 { 729 char xname[BUFSIZ]; 730 char foldbuf[BUFSIZ]; 731 register char *cp; 732 register int l; 733 wordexp_t wrdexp_buf; 734 735 if (debug) fprintf(stderr, "expand(%s)=", name); 736 cp = strchr(name, '\0') - 1; /* pointer to last char of name */ 737 if (isspace(*cp)) { 738 /* strip off trailing blanks */ 739 while (cp > name && isspace(*cp)) 740 cp--; 741 l = *++cp; /* save char */ 742 *cp = '\0'; 743 name = savestr(name); 744 *cp = (char)l; /* restore char */ 745 } 746 if (name[0] == '+' && getfold(foldbuf) >= 0) { 747 snprintf(xname, sizeof (xname), "%s/%s", foldbuf, name + 1); 748 cp = safeexpand(savestr(xname)); 749 if (debug) fprintf(stderr, "%s\n", cp); 750 return (cp); 751 } 752 if (!anyof(name, "~{[*?$`'\"\\")) { 753 if (debug) fprintf(stderr, "%s\n", name); 754 return (name); 755 } 756 if (wordexp(name, &wrdexp_buf, 0) != 0) { 757 fprintf(stderr, gettext("Syntax error in \"%s\"\n"), name); 758 fflush(stderr); 759 return (NOSTR); 760 } 761 if (wrdexp_buf.we_wordc > 1) { 762 fprintf(stderr, gettext("\"%s\": Ambiguous\n"), name); 763 fflush(stderr); 764 return (NOSTR); 765 } 766 if (debug) fprintf(stderr, "%s\n", wrdexp_buf.we_wordv[0]); 767 return (savestr(wrdexp_buf.we_wordv[0])); 768 } 769 770 /* 771 * Take a file name, possibly with shell meta characters 772 * in it and expand it by using "sh -c echo filename" 773 * Return the file name as a dynamic string. 774 * If the name cannot be expanded (for whatever reason) 775 * return the original file name. 776 */ 777 778 char * 779 safeexpand(char name[]) 780 { 781 char *t = expand(name); 782 return (t) ? t : savestr(name); 783 } 784 785 /* 786 * Determine the current folder directory name. 787 */ 788 getfold(char *name) 789 { 790 char *folder; 791 792 if ((folder = value("folder")) == NOSTR || *folder == '\0') 793 return (-1); 794 /* 795 * If name looks like a folder name, don't try 796 * to expand it, to prevent infinite recursion. 797 */ 798 if (*folder != '+' && (folder = expand(folder)) == NOSTR || 799 *folder == '\0') 800 return (-1); 801 if (*folder == '/') { 802 nstrcpy(name, BUFSIZ, folder); 803 } else 804 snprintf(name, BUFSIZ, "%s/%s", homedir, folder); 805 return (0); 806 } 807 808 /* 809 * A nicer version of Fdopen, which allows us to fclose 810 * without losing the open file. 811 */ 812 813 FILE * 814 Fdopen(int fildes, char *mode) 815 { 816 register int f; 817 818 f = dup(fildes); 819 if (f < 0) { 820 perror("dup"); 821 return (NULL); 822 } 823 return (fdopen(f, mode)); 824 } 825 826 /* 827 * return the filename associated with "s". This function always 828 * returns a non-null string (no error checking is done on the receiving end) 829 */ 830 char * 831 Getf(register char *s) 832 { 833 register char *cp; 834 static char defbuf[PATHSIZE]; 835 836 if (((cp = value(s)) != 0) && *cp) { 837 return (safeexpand(cp)); 838 } else if (strcmp(s, "MBOX") == 0) { 839 snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"), 840 "mbox"); 841 return (defbuf); 842 } else if (strcmp(s, "DEAD") == 0) { 843 snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"), 844 "dead.letter"); 845 return (defbuf); 846 } else if (strcmp(s, "MAILRC") == 0) { 847 snprintf(defbuf, sizeof (defbuf), "%s/%s", Getf("HOME"), 848 ".mailrc"); 849 return (defbuf); 850 } else if (strcmp(s, "HOME") == 0) { 851 /* no recursion allowed! */ 852 return ("."); 853 } 854 return ("DEAD"); /* "cannot happen" */ 855 } 856