1/* $NetBSD: support.c,v 1.27 2023/09/08 20:46:45 shm 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 35static char sccsid[] = "@(#)aux.c 8.1 (Berkeley) 6/6/93"; 36#else 37__RCSID("$NetBSD: support.c,v 1.27 2023/09/08 20:46:45 shm 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 */ 59PUBLIC char * 60savestr(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 */ 73static char * 74save2str(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 */ 93PUBLIC int 94sasprintf(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 108struct set_m_flag_args_s { 109 int and_bits; 110 int xor_bits; 111}; 112static int 113set_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 122PUBLIC struct message * 123set_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 */ 139PUBLIC void 140touch(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 */ 152PUBLIC int 153isdir(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 */ 165PUBLIC int 166argcount(char **argv) 167{ 168 char **ap; 169 170 for (ap = argv; *ap++ != NULL; /*EMPTY*/) 171 continue; 172 return (int)(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 NULL. 178 */ 179static char* 180ishfield(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 NULL; 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 */ 204static int 205gethfield(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 = readline(f, linebuf, LINESIZE, 0)) <= 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 = readline(f, line2, LINESIZE, 0)) < 0) 238 break; 239 rem--; 240 cp2 = skip_WSP(line2); 241 c -= (int)(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 */ 258PUBLIC char * 259hfield(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 = (int)(mp->m_lines - 1)) < 0) 275 return NULL; 276 if (readline(ibuf, linebuf, LINESIZE, 0) < 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) 285 headerfield = mime_decode_hfield(linebuf2, 286 sizeof(linebuf2), linebuf, headerfield); 287 oldhfield = save2str(headerfield, oldhfield); 288 } 289#else 290 if ((headerfield = ishfield(linebuf, colon, field)) != NULL) 291 oldhfield = save2str(headerfield, oldhfield); 292#endif 293 } 294 return oldhfield; 295} 296 297/* 298 * Copy a string, lowercasing it as we go. 299 */ 300PUBLIC void 301istrcpy(char *dest, const char *src) 302{ 303 304 do { 305 *dest++ = tolower((unsigned char)*src); 306 } while (*src++ != 0); 307} 308 309/* 310 * The following code deals with input stacking to do source 311 * commands. All but the current file pointer are saved on 312 * the stack. 313 */ 314 315static int ssp; /* Top of file stack */ 316static struct sstack { 317 FILE *s_file; /* File we were in. */ 318 int s_cond; /* Saved state of conditionals */ 319#ifdef NEW_CONDITIONAL 320 struct cond_stack_s *s_cond_stack; /* Saved conditional stack */ 321#endif 322 int s_loading; /* Loading .mailrc, etc. */ 323} sstack[NOFILE]; 324 325/* 326 * Pushdown current input file and switch to a new one. 327 * Set the global flag "sourcing" so that others will realize 328 * that they are no longer reading from a tty (in all probability). 329 */ 330PUBLIC int 331source(void *v) 332{ 333 char **arglist = v; 334 FILE *fi; 335 const char *cp; 336 337 if ((cp = expand(*arglist)) == NULL) 338 return 1; 339 if ((fi = Fopen(cp, "ref")) == NULL) { 340 warn("%s", cp); 341 return 1; 342 } 343 if (ssp >= NOFILE - 1) { 344 (void)printf("Too much \"sourcing\" going on.\n"); 345 (void)Fclose(fi); 346 return 1; 347 } 348 sstack[ssp].s_file = input; 349 sstack[ssp].s_cond = cond; 350#ifdef NEW_CONDITIONAL 351 sstack[ssp].s_cond_stack = cond_stack; 352#endif 353 sstack[ssp].s_loading = loading; 354 ssp++; 355 loading = 0; 356 cond = CNONE; 357 input = fi; 358 sourcing++; 359 return 0; 360} 361 362/* 363 * Pop the current input back to the previous level. 364 * Update the "sourcing" flag as appropriate. 365 */ 366PUBLIC int 367unstack(void) 368{ 369 if (ssp <= 0) { 370 (void)printf("\"Source\" stack over-pop.\n"); 371 sourcing = 0; 372 return 1; 373 } 374 (void)Fclose(input); 375 if (cond != CNONE || cond_stack != NULL) 376 (void)printf("Unmatched \"if\"\n"); 377 ssp--; 378 input = sstack[ssp].s_file; 379 cond = sstack[ssp].s_cond; 380#ifdef NEW_CONDITIONAL 381 cond_stack = sstack[ssp].s_cond_stack; 382#endif 383 loading = sstack[ssp].s_loading; 384 if (ssp == 0) 385 sourcing = loading; 386 return 0; 387} 388 389/* 390 * Touch the indicated file. 391 * This is nifty for the shell. 392 */ 393PUBLIC void 394alter(char *name) 395{ 396 struct stat sb; 397 struct timeval tv[2]; 398 399 if (stat(name, &sb)) 400 return; 401 (void)gettimeofday(&tv[0], NULL); 402 tv[0].tv_sec++; 403 TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec); 404 (void)utimes(name, tv); 405} 406 407/* 408 * Examine the passed line buffer and 409 * return true if it is all blanks and tabs. 410 */ 411PUBLIC int 412blankline(char linebuf[]) 413{ 414 char *cp; 415 416 for (cp = linebuf; *cp; cp++) 417 if (!is_WSP(*cp)) 418 return 0; 419 return 1; 420} 421 422/* 423 * Start of a "comment". 424 * Ignore it. 425 */ 426static char * 427skip_comment(char *cp) 428{ 429 int nesting = 1; 430 431 for (/*EMPTY*/; nesting > 0 && *cp; cp++) { 432 switch (*cp) { 433 case '\\': 434 if (cp[1]) 435 cp++; 436 break; 437 case '(': 438 nesting++; 439 break; 440 case ')': 441 nesting--; 442 break; 443 } 444 } 445 return cp; 446} 447 448/* 449 * Skin an arpa net address according to the RFC 822 interpretation 450 * of "host-phrase." 451 */ 452PUBLIC char * 453skin(char *name) 454{ 455 int c; 456 char *cp, *cp2; 457 char *bufend; 458 int gotlt, lastsp; 459 char *nbuf, *ret; 460 461 if (name == NULL) 462 return NULL; 463 if (strchr(name, '(') == NULL && strchr(name, '<') == NULL 464 && strchr(name, ' ') == NULL) 465 return name; 466 467 nbuf = emalloc(strlen(name) + 1); 468 gotlt = 0; 469 lastsp = 0; 470 bufend = nbuf; 471 for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; /*EMPTY*/) { 472 switch (c) { 473 case '(': 474 cp = skip_comment(cp); 475 lastsp = 0; 476 break; 477 478 case '"': 479 /* 480 * Start of a "quoted-string". 481 * Copy it in its entirety. 482 */ 483 while ((c = *cp) != '\0') { 484 cp++; 485 if (c == '"') 486 break; 487 if (c != '\\') 488 *cp2++ = c; 489 else if ((c = *cp) != '\0') { 490 *cp2++ = c; 491 cp++; 492 } 493 } 494 lastsp = 0; 495 break; 496 497 case ' ': 498 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ') 499 cp += 3, *cp2++ = '@'; 500 else 501 if (cp[0] == '@' && cp[1] == ' ') 502 cp += 2, *cp2++ = '@'; 503 else 504 lastsp = 1; 505 break; 506 507 case '<': 508 cp2 = bufend; 509 gotlt++; 510 lastsp = 0; 511 break; 512 513 case '>': 514 if (gotlt) { 515 gotlt = 0; 516 while ((c = *cp) && c != ',') { 517 cp++; 518 if (c == '(') 519 cp = skip_comment(cp); 520 else if (c == '"') 521 while ((c = *cp) != '\0') { 522 cp++; 523 if (c == '"') 524 break; 525 if (c == '\\' && *cp) 526 cp++; 527 } 528 } 529 lastsp = 0; 530 break; 531 } 532 /* FALLTHROUGH */ 533 534 default: 535 if (lastsp) { 536 lastsp = 0; 537 *cp2++ = ' '; 538 } 539 *cp2++ = c; 540 if (c == ',' && *cp == ' ' && !gotlt) { 541 *cp2++ = ' '; 542 for (/*EMPTY*/; *cp == ' '; cp++) 543 continue; 544 lastsp = 0; 545 bufend = cp2; 546 } 547 } 548 } 549 *cp2 = 0; 550 551 ret = savestr(nbuf); 552 free(nbuf); 553 554 return ret; 555} 556 557/* 558 * Fetch the sender's name from the passed message. 559 * Reptype can be 560 * 0 -- get sender's name for display purposes 561 * 1 -- get sender's name for reply 562 * 2 -- get sender's name for Reply 563 */ 564static char * 565name1(struct message *mp, int reptype) 566{ 567 char namebuf[LINESIZE]; 568 char linebuf[LINESIZE]; 569 char *cp, *cp2; 570 FILE *ibuf; 571 int firstrun = 1; 572 573 if ((cp = hfield("from", mp)) != NULL) 574 return cp; 575 if (reptype == 0 && (cp = hfield("sender", mp)) != NULL) 576 return cp; 577 ibuf = setinput(mp); 578 namebuf[0] = '\0'; 579 if (readline(ibuf, linebuf, LINESIZE, 0) < 0) 580 return savestr(namebuf); 581 newname: 582 for (cp = linebuf; *cp && *cp != ' '; cp++) 583 continue; 584 cp = skip_WSP(cp); 585 for (cp2 = &namebuf[strlen(namebuf)]; 586 *cp && !is_WSP(*cp) && cp2 < namebuf + LINESIZE - 1; 587 /*EMPTY*/) 588 *cp2++ = *cp++; 589 *cp2 = '\0'; 590 if (readline(ibuf, linebuf, LINESIZE, 0) < 0) 591 return savestr(namebuf); 592 if ((cp = strchr(linebuf, 'F')) == NULL) 593 return savestr(namebuf); 594 if (strncmp(cp, "From", 4) != 0) 595 return savestr(namebuf); 596 while ((cp = strchr(cp, 'r')) != NULL) { 597 if (strncmp(cp, "remote", 6) == 0) { 598 if ((cp = strchr(cp, 'f')) == NULL) 599 break; 600 if (strncmp(cp, "from", 4) != 0) 601 break; 602 if ((cp = strchr(cp, ' ')) == NULL) 603 break; 604 cp++; 605 if (firstrun) { 606 cp2 = namebuf; 607 firstrun = 0; 608 } else 609 cp2 = strrchr(namebuf, '!') + 1; 610 while (*cp && cp2 < namebuf + LINESIZE - 1) 611 *cp2++ = *cp++; 612 if (cp2 < namebuf + LINESIZE - 1) 613 *cp2++ = '!'; 614 *cp2 = '\0'; 615 if (cp2 < namebuf + LINESIZE - 1) 616 goto newname; 617 else 618 break; 619 } 620 cp++; 621 } 622 return savestr(namebuf); 623} 624 625/* 626 * Count the occurrences of c in str 627 */ 628static int 629charcount(char *str, int c) 630{ 631 char *cp; 632 int i; 633 634 for (i = 0, cp = str; *cp; cp++) 635 if (*cp == c) 636 i++; 637 return i; 638} 639 640/* 641 * Get sender's name from this message. If the message has 642 * a bunch of arpanet stuff in it, we may have to skin the name 643 * before returning it. 644 */ 645PUBLIC char * 646nameof(struct message *mp, int reptype) 647{ 648 char *cp, *cp2; 649 650 cp = skin(name1(mp, reptype)); 651 if (reptype != 0 || charcount(cp, '!') < 2) 652 return cp; 653 cp2 = strrchr(cp, '!'); 654 cp2--; 655 while (cp2 > cp && *cp2 != '!') 656 cp2--; 657 if (*cp2 == '!') 658 return cp2 + 1; 659 return cp; 660} 661 662/* 663 * Copy s1 to s2, return pointer to null in s2. 664 */ 665PUBLIC char * 666copy(char *s1, char *s2) 667{ 668 669 while ((*s2++ = *s1++) != '\0') 670 continue; 671 return s2 - 1; 672} 673 674/* 675 * The core routine to check the ignore table for a field. 676 * Note: realfield should be lower-cased! 677 */ 678PUBLIC int 679member(char *realfield, struct ignoretab *table) 680{ 681 struct ignore *igp; 682 683 for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link) 684 if (*igp->i_field == *realfield && 685 equal(igp->i_field, realfield)) 686 return 1; 687 return 0; 688} 689 690/* 691 * See if the given header field is supposed to be ignored. 692 */ 693PUBLIC int 694isign(const char *field, struct ignoretab ignoretabs[2]) 695{ 696 char realfld[LINESIZE]; 697 698 if (ignoretabs == ignoreall) 699 return 1; 700 /* 701 * Lower-case the string, so that "Status" and "status" 702 * will hash to the same place. 703 */ 704 istrcpy(realfld, field); 705 if (ignoretabs[1].i_count > 0) 706 return !member(realfld, ignoretabs + 1); 707 else 708 return member(realfld, ignoretabs); 709} 710 711PUBLIC void 712add_ignore(const char *name, struct ignoretab *tab) 713{ 714 char field[LINESIZE]; 715 int h; 716 struct ignore *igp; 717 718 istrcpy(field, name); 719 if (member(field, tab)) 720 return; 721 h = hash(field); 722 igp = ecalloc(1, sizeof(struct ignore)); 723 igp->i_field = ecalloc(strlen(field) + 1, sizeof(char)); 724 (void)strcpy(igp->i_field, field); 725 igp->i_link = tab->i_head[h]; 726 tab->i_head[h] = igp; 727 tab->i_count++; 728} 729 730/* 731 * Write a file to stdout, skipping comment lines. 732 */ 733PUBLIC void 734cathelp(const char *fname) 735{ 736 char *line; 737 FILE *f; 738 size_t len; 739 740 if ((f = Fopen(fname, "ref")) == NULL) { 741 warn("%s", fname); 742 return; 743 } 744 while ((line = fgetln(f, &len)) != NULL) { 745 if (*line == '#') 746 continue; 747 if (fwrite(line, 1, len, stdout) != len) 748 break; 749 } 750 (void)Fclose(f); 751 return; 752} 753