1 /* $NetBSD: support.c,v 1.20 2007/10/27 15:14:51 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[] = "@(#)aux.c 8.1 (Berkeley) 6/6/93"; 36 #else 37 __RCSID("$NetBSD: support.c,v 1.20 2007/10/27 15:14:51 christos Exp $"); 38 #endif 39 #endif /* not lint */ 40 41 #include <stdarg.h> 42 #include <util.h> 43 44 #include "rcv.h" 45 #include "extern.h" 46 #include "mime.h" 47 #include "thread.h" 48 49 /* 50 * Mail -- a mail program 51 * 52 * Auxiliary functions. 53 */ 54 55 56 /* 57 * Return a pointer to a dynamic copy of the argument. 58 */ 59 PUBLIC char * 60 savestr(const char *str) 61 { 62 char *new; 63 size_t size = strlen(str) + 1; 64 65 if ((new = salloc(size)) != NULL) 66 (void)memmove(new, str, size); 67 return new; 68 } 69 70 /* 71 * Make a copy of new argument incorporating old one. 72 */ 73 static char * 74 save2str(char *str, char *old) 75 { 76 char *new; 77 size_t newsize = strlen(str) + 1; 78 size_t oldsize = old ? strlen(old) + 1 : 0; 79 80 if ((new = salloc(newsize + oldsize)) != NULL) { 81 if (oldsize) { 82 (void)memmove(new, old, oldsize); 83 new[oldsize - 1] = ' '; 84 } 85 (void)memmove(new + oldsize, str, newsize); 86 } 87 return new; 88 } 89 90 /* 91 * Like asprintf(), but with result salloc-ed rather than malloc-ed. 92 */ 93 PUBLIC int 94 sasprintf(char **ret, const char *format, ...) 95 { 96 int rval; 97 char *p; 98 va_list args; 99 100 va_start(args, format); 101 rval = evasprintf(&p, format, args); 102 *ret = savestr(p); 103 free(p); 104 va_end(args); 105 return rval; 106 } 107 108 struct set_m_flag_args_s { 109 int and_bits; 110 int xor_bits; 111 }; 112 static int 113 set_m_flag_core(struct message *mp, void *v) 114 { 115 struct set_m_flag_args_s *args; 116 args = v; 117 mp->m_flag &= args->and_bits; 118 mp->m_flag ^= args->xor_bits; 119 return 0; 120 } 121 122 PUBLIC struct message * 123 set_m_flag(int msgnum, int and_bits, int xor_bits) 124 { 125 struct message *mp; 126 struct set_m_flag_args_s args; 127 mp = get_message(msgnum); 128 args.and_bits = and_bits; 129 args.xor_bits = xor_bits; 130 (void)thread_recursion(mp, set_m_flag_core, &args); 131 return mp; 132 } 133 134 /* 135 * Touch the named message by setting its MTOUCH flag. 136 * Touched messages have the effect of not being sent 137 * back to the system mailbox on exit. 138 */ 139 PUBLIC void 140 touch(struct message *mp) 141 { 142 143 mp->m_flag |= MTOUCH; 144 if ((mp->m_flag & MREAD) == 0) 145 mp->m_flag |= MREAD|MSTATUS; 146 } 147 148 /* 149 * Test to see if the passed file name is a directory. 150 * Return true if it is. 151 */ 152 PUBLIC int 153 isdir(const char name[]) 154 { 155 struct stat sbuf; 156 157 if (stat(name, &sbuf) < 0) 158 return 0; 159 return S_ISDIR(sbuf.st_mode); 160 } 161 162 /* 163 * Count the number of arguments in the given string raw list. 164 */ 165 PUBLIC int 166 argcount(char **argv) 167 { 168 char **ap; 169 170 for (ap = argv; *ap++ != NULL; /*EMPTY*/) 171 continue; 172 return ap - argv - 1; 173 } 174 175 /* 176 * Check whether the passed line is a header line of 177 * the desired breed. Return the field body, or 0. 178 */ 179 static char* 180 ishfield(const char linebuf[], char *colon, const char field[]) 181 { 182 char *cp = colon; 183 184 *cp = 0; 185 if (strcasecmp(linebuf, field) != 0) { 186 *cp = ':'; 187 return 0; 188 } 189 *cp = ':'; 190 for (cp++; is_WSP(*cp); cp++) 191 continue; 192 return cp; 193 } 194 195 /* 196 * Return the next header field found in the given message. 197 * Return >= 0 if something found, < 0 elsewise. 198 * "colon" is set to point to the colon in the header. 199 * Must deal with \ continuations & other such fraud. 200 * 201 * WARNING - do not call this outside hfield() or decoding will not 202 * get done. 203 */ 204 static int 205 gethfield(FILE *f, char linebuf[], int rem, char **colon) 206 { 207 char line2[LINESIZE]; 208 char *cp, *cp2; 209 int c; 210 211 for (;;) { 212 if (--rem < 0) 213 return -1; 214 if ((c = mail_readline(f, linebuf, LINESIZE)) <= 0) 215 return -1; 216 for (cp = linebuf; 217 isprint((unsigned char)*cp) && *cp != ' ' && *cp != ':'; 218 cp++) 219 continue; 220 if (*cp != ':' || cp == linebuf) 221 continue; 222 /* 223 * I guess we got a headline. 224 * Handle wraparounding 225 */ 226 *colon = cp; 227 cp = linebuf + c; 228 for (;;) { 229 while (--cp >= linebuf && is_WSP(*cp)) 230 continue; 231 cp++; 232 if (rem <= 0) 233 break; 234 (void)ungetc(c = getc(f), f); 235 if (!is_WSP(c)) 236 break; 237 if ((c = mail_readline(f, line2, LINESIZE)) < 0) 238 break; 239 rem--; 240 cp2 = skip_WSP(line2); 241 c -= cp2 - line2; 242 if (cp + c >= linebuf + LINESIZE - 2) 243 break; 244 *cp++ = ' '; 245 (void)memmove(cp, cp2, (size_t)c); 246 cp += c; 247 } 248 *cp = 0; 249 return rem; 250 } 251 /* NOTREACHED */ 252 } 253 254 /* 255 * Return the desired header line from the passed message 256 * pointer (or NULL if the desired header field is not available). 257 */ 258 PUBLIC char * 259 hfield(const char field[], const struct message *mp) 260 { 261 FILE *ibuf; 262 char linebuf[LINESIZE]; 263 int lc; 264 char *headerfield; 265 char *colon, *oldhfield = NULL; 266 #ifdef MIME_SUPPORT 267 int decode; 268 269 decode = value(ENAME_MIME_DECODE_MSG) && 270 value(ENAME_MIME_DECODE_HDR); 271 #endif 272 273 ibuf = setinput(mp); 274 if ((lc = mp->m_lines - 1) < 0) 275 return NULL; 276 if (mail_readline(ibuf, linebuf, LINESIZE) < 0) 277 return NULL; 278 while (lc > 0) { 279 if ((lc = gethfield(ibuf, linebuf, lc, &colon)) < 0) 280 return oldhfield; 281 #ifdef MIME_SUPPORT 282 if ((headerfield = ishfield(linebuf, colon, field)) != NULL) { 283 char linebuf2[LINESIZE]; 284 if (decode && colon) 285 headerfield = mime_decode_hfield(linebuf2, sizeof(linebuf2), headerfield); 286 oldhfield = save2str(headerfield, oldhfield); 287 } 288 #else 289 if ((headerfield = ishfield(linebuf, colon, field)) != NULL) 290 oldhfield = save2str(headerfield, oldhfield); 291 #endif 292 } 293 return oldhfield; 294 } 295 296 /* 297 * Copy a string, lowercasing it as we go. 298 */ 299 PUBLIC void 300 istrcpy(char *dest, const char *src) 301 { 302 303 do { 304 *dest++ = tolower((unsigned char)*src); 305 } while (*src++ != 0); 306 } 307 308 /* 309 * The following code deals with input stacking to do source 310 * commands. All but the current file pointer are saved on 311 * the stack. 312 */ 313 314 static int ssp; /* Top of file stack */ 315 static struct sstack { 316 FILE *s_file; /* File we were in. */ 317 int s_cond; /* Saved state of conditionals */ 318 #ifdef NEW_CONDITIONAL 319 struct cond_stack_s *s_cond_stack; /* Saved conditional stack */ 320 #endif 321 int s_loading; /* Loading .mailrc, etc. */ 322 } sstack[NOFILE]; 323 324 /* 325 * Pushdown current input file and switch to a new one. 326 * Set the global flag "sourcing" so that others will realize 327 * that they are no longer reading from a tty (in all probability). 328 */ 329 PUBLIC int 330 source(void *v) 331 { 332 char **arglist = v; 333 FILE *fi; 334 const char *cp; 335 336 if ((cp = expand(*arglist)) == NULL) 337 return 1; 338 if ((fi = Fopen(cp, "r")) == NULL) { 339 warn("%s", cp); 340 return 1; 341 } 342 if (ssp >= NOFILE - 1) { 343 (void)printf("Too much \"sourcing\" going on.\n"); 344 (void)Fclose(fi); 345 return 1; 346 } 347 sstack[ssp].s_file = input; 348 sstack[ssp].s_cond = cond; 349 #ifdef NEW_CONDITIONAL 350 sstack[ssp].s_cond_stack = cond_stack; 351 #endif 352 sstack[ssp].s_loading = loading; 353 ssp++; 354 loading = 0; 355 cond = CNONE; 356 input = fi; 357 sourcing++; 358 return 0; 359 } 360 361 /* 362 * Pop the current input back to the previous level. 363 * Update the "sourcing" flag as appropriate. 364 */ 365 PUBLIC int 366 unstack(void) 367 { 368 if (ssp <= 0) { 369 (void)printf("\"Source\" stack over-pop.\n"); 370 sourcing = 0; 371 return 1; 372 } 373 (void)Fclose(input); 374 if (cond != CNONE || cond_stack != NULL) 375 (void)printf("Unmatched \"if\"\n"); 376 ssp--; 377 input = sstack[ssp].s_file; 378 cond = sstack[ssp].s_cond; 379 #ifdef NEW_CONDITIONAL 380 cond_stack = sstack[ssp].s_cond_stack; 381 #endif 382 loading = sstack[ssp].s_loading; 383 if (ssp == 0) 384 sourcing = loading; 385 return 0; 386 } 387 388 /* 389 * Touch the indicated file. 390 * This is nifty for the shell. 391 */ 392 PUBLIC void 393 alter(char *name) 394 { 395 struct stat sb; 396 struct timeval tv[2]; 397 398 if (stat(name, &sb)) 399 return; 400 (void)gettimeofday(&tv[0], NULL); 401 tv[0].tv_sec++; 402 TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec); 403 (void)utimes(name, tv); 404 } 405 406 /* 407 * Examine the passed line buffer and 408 * return true if it is all blanks and tabs. 409 */ 410 PUBLIC int 411 blankline(char linebuf[]) 412 { 413 char *cp; 414 415 for (cp = linebuf; *cp; cp++) 416 if (!is_WSP(*cp)) 417 return 0; 418 return 1; 419 } 420 421 /* 422 * Start of a "comment". 423 * Ignore it. 424 */ 425 static char * 426 skip_comment(char *cp) 427 { 428 int nesting = 1; 429 430 for (/*EMPTY*/; nesting > 0 && *cp; cp++) { 431 switch (*cp) { 432 case '\\': 433 if (cp[1]) 434 cp++; 435 break; 436 case '(': 437 nesting++; 438 break; 439 case ')': 440 nesting--; 441 break; 442 } 443 } 444 return cp; 445 } 446 447 /* 448 * Skin an arpa net address according to the RFC 822 interpretation 449 * of "host-phrase." 450 */ 451 PUBLIC char * 452 skin(char *name) 453 { 454 int c; 455 char *cp, *cp2; 456 char *bufend; 457 int gotlt, lastsp; 458 char nbuf[LINESIZE]; 459 460 if (name == NULL) 461 return NULL; 462 if (strchr(name, '(') == NULL && strchr(name, '<') == NULL 463 && strchr(name, ' ') == NULL) 464 return name; 465 gotlt = 0; 466 lastsp = 0; 467 bufend = nbuf; 468 for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; /*EMPTY*/) { 469 switch (c) { 470 case '(': 471 cp = skip_comment(cp); 472 lastsp = 0; 473 break; 474 475 case '"': 476 /* 477 * Start of a "quoted-string". 478 * Copy it in its entirety. 479 */ 480 while ((c = *cp) != '\0') { 481 cp++; 482 if (c == '"') 483 break; 484 if (c != '\\') 485 *cp2++ = c; 486 else if ((c = *cp) != '\0') { 487 *cp2++ = c; 488 cp++; 489 } 490 } 491 lastsp = 0; 492 break; 493 494 case ' ': 495 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ') 496 cp += 3, *cp2++ = '@'; 497 else 498 if (cp[0] == '@' && cp[1] == ' ') 499 cp += 2, *cp2++ = '@'; 500 else 501 lastsp = 1; 502 break; 503 504 case '<': 505 cp2 = bufend; 506 gotlt++; 507 lastsp = 0; 508 break; 509 510 case '>': 511 if (gotlt) { 512 gotlt = 0; 513 while ((c = *cp) && c != ',') { 514 cp++; 515 if (c == '(') 516 cp = skip_comment(cp); 517 else if (c == '"') 518 while ((c = *cp) != '\0') { 519 cp++; 520 if (c == '"') 521 break; 522 if (c == '\\' && *cp) 523 cp++; 524 } 525 } 526 lastsp = 0; 527 break; 528 } 529 /* FALLTHROUGH */ 530 531 default: 532 if (lastsp) { 533 lastsp = 0; 534 *cp2++ = ' '; 535 } 536 *cp2++ = c; 537 if (c == ',' && !gotlt) { 538 *cp2++ = ' '; 539 for (/*EMPTY*/; *cp == ' '; cp++) 540 continue; 541 lastsp = 0; 542 bufend = cp2; 543 } 544 } 545 } 546 *cp2 = 0; 547 548 return savestr(nbuf); 549 } 550 551 /* 552 * Fetch the sender's name from the passed message. 553 * Reptype can be 554 * 0 -- get sender's name for display purposes 555 * 1 -- get sender's name for reply 556 * 2 -- get sender's name for Reply 557 */ 558 static char * 559 name1(struct message *mp, int reptype) 560 { 561 char namebuf[LINESIZE]; 562 char linebuf[LINESIZE]; 563 char *cp, *cp2; 564 FILE *ibuf; 565 int firstrun = 1; 566 567 if ((cp = hfield("from", mp)) != NULL) 568 return cp; 569 if (reptype == 0 && (cp = hfield("sender", mp)) != NULL) 570 return cp; 571 ibuf = setinput(mp); 572 namebuf[0] = '\0'; 573 if (mail_readline(ibuf, linebuf, LINESIZE) < 0) 574 return savestr(namebuf); 575 newname: 576 for (cp = linebuf; *cp && *cp != ' '; cp++) 577 continue; 578 cp = skip_WSP(cp); 579 for (cp2 = &namebuf[strlen(namebuf)]; 580 *cp && !is_WSP(*cp) && cp2 < namebuf + LINESIZE - 1; 581 /*EMPTY*/) 582 *cp2++ = *cp++; 583 *cp2 = '\0'; 584 if (mail_readline(ibuf, linebuf, LINESIZE) < 0) 585 return savestr(namebuf); 586 if ((cp = strchr(linebuf, 'F')) == NULL) 587 return savestr(namebuf); 588 if (strncmp(cp, "From", 4) != 0) 589 return savestr(namebuf); 590 while ((cp = strchr(cp, 'r')) != NULL) { 591 if (strncmp(cp, "remote", 6) == 0) { 592 if ((cp = strchr(cp, 'f')) == NULL) 593 break; 594 if (strncmp(cp, "from", 4) != 0) 595 break; 596 if ((cp = strchr(cp, ' ')) == NULL) 597 break; 598 cp++; 599 if (firstrun) { 600 cp2 = namebuf; 601 firstrun = 0; 602 } else 603 cp2 = strrchr(namebuf, '!') + 1; 604 while (*cp && cp2 < namebuf + LINESIZE - 1) 605 *cp2++ = *cp++; 606 if (cp2 < namebuf + LINESIZE - 1) 607 *cp2++ = '!'; 608 *cp2 = '\0'; 609 if (cp2 < namebuf + LINESIZE - 1) 610 goto newname; 611 else 612 break; 613 } 614 cp++; 615 } 616 return savestr(namebuf); 617 } 618 619 /* 620 * Count the occurrences of c in str 621 */ 622 static int 623 charcount(char *str, int c) 624 { 625 char *cp; 626 int i; 627 628 for (i = 0, cp = str; *cp; cp++) 629 if (*cp == c) 630 i++; 631 return i; 632 } 633 634 /* 635 * Get sender's name from this message. If the message has 636 * a bunch of arpanet stuff in it, we may have to skin the name 637 * before returning it. 638 */ 639 PUBLIC char * 640 nameof(struct message *mp, int reptype) 641 { 642 char *cp, *cp2; 643 644 cp = skin(name1(mp, reptype)); 645 if (reptype != 0 || charcount(cp, '!') < 2) 646 return cp; 647 cp2 = strrchr(cp, '!'); 648 cp2--; 649 while (cp2 > cp && *cp2 != '!') 650 cp2--; 651 if (*cp2 == '!') 652 return cp2 + 1; 653 return cp; 654 } 655 656 /* 657 * Copy s1 to s2, return pointer to null in s2. 658 */ 659 PUBLIC char * 660 copy(char *s1, char *s2) 661 { 662 663 while ((*s2++ = *s1++) != '\0') 664 continue; 665 return s2 - 1; 666 } 667 668 /* 669 * The core routine to check the ignore table for a field. 670 * Note: realfield should be lower-cased! 671 */ 672 PUBLIC int 673 member(char *realfield, struct ignoretab *table) 674 { 675 struct ignore *igp; 676 677 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link) 678 if (*igp->i_field == *realfield && 679 equal(igp->i_field, realfield)) 680 return 1; 681 return 0; 682 } 683 684 /* 685 * See if the given header field is supposed to be ignored. 686 */ 687 PUBLIC int 688 isign(const char *field, struct ignoretab ignoretabs[2]) 689 { 690 char realfld[LINESIZE]; 691 692 if (ignoretabs == ignoreall) 693 return 1; 694 /* 695 * Lower-case the string, so that "Status" and "status" 696 * will hash to the same place. 697 */ 698 istrcpy(realfld, field); 699 if (ignoretabs[1].i_count > 0) 700 return !member(realfld, ignoretabs + 1); 701 else 702 return member(realfld, ignoretabs); 703 } 704 705 PUBLIC void 706 add_ignore(const char *name, struct ignoretab *tab) 707 { 708 char field[LINESIZE]; 709 int h; 710 struct ignore *igp; 711 712 istrcpy(field, name); 713 if (member(field, tab)) 714 return; 715 h = hash(field); 716 igp = ecalloc(1, sizeof(struct ignore)); 717 igp->i_field = ecalloc(strlen(field) + 1, sizeof(char)); 718 (void)strcpy(igp->i_field, field); 719 igp->i_link = tab->i_head[h]; 720 tab->i_head[h] = igp; 721 tab->i_count++; 722 } 723 724 /* 725 * Write a file to stdout, skipping comment lines. 726 */ 727 PUBLIC void 728 cathelp(const char *fname) 729 { 730 char *line; 731 FILE *f; 732 size_t len; 733 734 if ((f = Fopen(fname, "r")) == NULL) { 735 warn(fname); 736 return; 737 } 738 while ((line = fgetln(f, &len)) != NULL) { 739 if (*line == '#') 740 continue; 741 if (fwrite(line, 1, len, stdout) != len) 742 break; 743 } 744 (void)Fclose(f); 745 return; 746 } 747