1 /*
   2  * Copyright (c) 1998-2007, 2009 Sendmail, Inc. and its suppliers.
   3  *      All rights reserved.
   4  * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
   5  * Copyright (c) 1988, 1993
   6  *      The Regents of the University of California.  All rights reserved.
   7  *
   8  * By using this file, you agree to the terms and conditions set
   9  * forth in the LICENSE file which can be found at the top level of
  10  * the sendmail distribution.
  11  *
  12  */
  13 
  14 #include <sendmail.h>
  15 
  16 SM_RCSID("@(#)$Id: util.c,v 8.416 2009/12/18 17:05:26 ca Exp $")
  17 
  18 #include <sm/sendmail.h>
  19 #include <sysexits.h>
  20 #include <sm/xtrap.h>
  21 
  22 /*
  23 **  NEWSTR -- Create a copy of a C string
  24 **
  25 **      Parameters:
  26 **              s -- the string to copy.
  27 **
  28 **      Returns:
  29 **              pointer to newly allocated string.
  30 */
  31 
  32 char *
  33 newstr(s)
  34         const char *s;
  35 {
  36         size_t l;
  37         char *n;
  38 
  39         l = strlen(s);
  40         SM_ASSERT(l + 1 > l);
  41         n = xalloc(l + 1);
  42         sm_strlcpy(n, s, l + 1);
  43         return n;
  44 }
  45 
  46 /*
  47 **  ADDQUOTES -- Adds quotes & quote bits to a string.
  48 **
  49 **      Runs through a string and adds backslashes and quote bits.
  50 **
  51 **      Parameters:
  52 **              s -- the string to modify.
  53 **              rpool -- resource pool from which to allocate result
  54 **
  55 **      Returns:
  56 **              pointer to quoted string.
  57 */
  58 
  59 char *
  60 addquotes(s, rpool)
  61         char *s;
  62         SM_RPOOL_T *rpool;
  63 {
  64         int len = 0;
  65         char c;
  66         char *p = s, *q, *r;
  67 
  68         if (s == NULL)
  69                 return NULL;
  70 
  71         /* Find length of quoted string */
  72         while ((c = *p++) != '\0')
  73         {
  74                 len++;
  75                 if (c == '\\' || c == '"')
  76                         len++;
  77         }
  78 
  79         q = r = sm_rpool_malloc_x(rpool, len + 3);
  80         p = s;
  81 
  82         /* add leading quote */
  83         *q++ = '"';
  84         while ((c = *p++) != '\0')
  85         {
  86                 /* quote \ or " */
  87                 if (c == '\\' || c == '"')
  88                         *q++ = '\\';
  89                 *q++ = c;
  90         }
  91         *q++ = '"';
  92         *q = '\0';
  93         return r;
  94 }
  95 
  96 /*
  97 **  STRIPBACKSLASH -- Strip all leading backslashes from a string, provided
  98 **      the following character is alpha-numerical.
  99 **
 100 **      This is done in place.
 101 **
 102 **      Parameters:
 103 **              s -- the string to strip.
 104 **
 105 **      Returns:
 106 **              none.
 107 */
 108 
 109 void
 110 stripbackslash(s)
 111         char *s;
 112 {
 113         char *p, *q, c;
 114 
 115         if (s == NULL || *s == '\0')
 116                 return;
 117         p = q = s;
 118         while (*p == '\\' && (p[1] == '\\' || (isascii(p[1]) && isalnum(p[1]))))
 119                 p++;
 120         do
 121         {
 122                 c = *q++ = *p++;
 123         } while (c != '\0');
 124 }
 125 
 126 /*
 127 **  RFC822_STRING -- Checks string for proper RFC822 string quoting.
 128 **
 129 **      Runs through a string and verifies RFC822 special characters
 130 **      are only found inside comments, quoted strings, or backslash
 131 **      escaped.  Also verified balanced quotes and parenthesis.
 132 **
 133 **      Parameters:
 134 **              s -- the string to modify.
 135 **
 136 **      Returns:
 137 **              true iff the string is RFC822 compliant, false otherwise.
 138 */
 139 
 140 bool
 141 rfc822_string(s)
 142         char *s;
 143 {
 144         bool quoted = false;
 145         int commentlev = 0;
 146         char *c = s;
 147 
 148         if (s == NULL)
 149                 return false;
 150 
 151         while (*c != '\0')
 152         {
 153                 /* escaped character */
 154                 if (*c == '\\')
 155                 {
 156                         c++;
 157                         if (*c == '\0')
 158                                 return false;
 159                 }
 160                 else if (commentlev == 0 && *c == '"')
 161                         quoted = !quoted;
 162                 else if (!quoted)
 163                 {
 164                         if (*c == ')')
 165                         {
 166                                 /* unbalanced ')' */
 167                                 if (commentlev == 0)
 168                                         return false;
 169                                 else
 170                                         commentlev--;
 171                         }
 172                         else if (*c == '(')
 173                                 commentlev++;
 174                         else if (commentlev == 0 &&
 175                                  strchr(MustQuoteChars, *c) != NULL)
 176                                 return false;
 177                 }
 178                 c++;
 179         }
 180 
 181         /* unbalanced '"' or '(' */
 182         return !quoted && commentlev == 0;
 183 }
 184 
 185 /*
 186 **  SHORTEN_RFC822_STRING -- Truncate and rebalance an RFC822 string
 187 **
 188 **      Arbitrarily shorten (in place) an RFC822 string and rebalance
 189 **      comments and quotes.
 190 **
 191 **      Parameters:
 192 **              string -- the string to shorten
 193 **              length -- the maximum size, 0 if no maximum
 194 **
 195 **      Returns:
 196 **              true if string is changed, false otherwise
 197 **
 198 **      Side Effects:
 199 **              Changes string in place, possibly resulting
 200 **              in a shorter string.
 201 */
 202 
 203 bool
 204 shorten_rfc822_string(string, length)
 205         char *string;
 206         size_t length;
 207 {
 208         bool backslash = false;
 209         bool modified = false;
 210         bool quoted = false;
 211         size_t slen;
 212         int parencount = 0;
 213         char *ptr = string;
 214 
 215         /*
 216         **  If have to rebalance an already short enough string,
 217         **  need to do it within allocated space.
 218         */
 219 
 220         slen = strlen(string);
 221         if (length == 0 || slen < length)
 222                 length = slen;
 223 
 224         while (*ptr != '\0')
 225         {
 226                 if (backslash)
 227                 {
 228                         backslash = false;
 229                         goto increment;
 230                 }
 231 
 232                 if (*ptr == '\\')
 233                         backslash = true;
 234                 else if (*ptr == '(')
 235                 {
 236                         if (!quoted)
 237                                 parencount++;
 238                 }
 239                 else if (*ptr == ')')
 240                 {
 241                         if (--parencount < 0)
 242                                 parencount = 0;
 243                 }
 244 
 245                 /* Inside a comment, quotes don't matter */
 246                 if (parencount <= 0 && *ptr == '"')
 247                         quoted = !quoted;
 248 
 249 increment:
 250                 /* Check for sufficient space for next character */
 251                 if (length - (ptr - string) <= (size_t) ((backslash ? 1 : 0) +
 252                                                 parencount +
 253                                                 (quoted ? 1 : 0)))
 254                 {
 255                         /* Not enough, backtrack */
 256                         if (*ptr == '\\')
 257                                 backslash = false;
 258                         else if (*ptr == '(' && !quoted)
 259                                 parencount--;
 260                         else if (*ptr == '"' && parencount == 0)
 261                                 quoted = false;
 262                         break;
 263                 }
 264                 ptr++;
 265         }
 266 
 267         /* Rebalance */
 268         while (parencount-- > 0)
 269         {
 270                 if (*ptr != ')')
 271                 {
 272                         modified = true;
 273                         *ptr = ')';
 274                 }
 275                 ptr++;
 276         }
 277         if (quoted)
 278         {
 279                 if (*ptr != '"')
 280                 {
 281                         modified = true;
 282                         *ptr = '"';
 283                 }
 284                 ptr++;
 285         }
 286         if (*ptr != '\0')
 287         {
 288                 modified = true;
 289                 *ptr = '\0';
 290         }
 291         return modified;
 292 }
 293 
 294 /*
 295 **  FIND_CHARACTER -- find an unquoted character in an RFC822 string
 296 **
 297 **      Find an unquoted, non-commented character in an RFC822
 298 **      string and return a pointer to its location in the
 299 **      string.
 300 **
 301 **      Parameters:
 302 **              string -- the string to search
 303 **              character -- the character to find
 304 **
 305 **      Returns:
 306 **              pointer to the character, or
 307 **              a pointer to the end of the line if character is not found
 308 */
 309 
 310 char *
 311 find_character(string, character)
 312         char *string;
 313         int character;
 314 {
 315         bool backslash = false;
 316         bool quoted = false;
 317         int parencount = 0;
 318 
 319         while (string != NULL && *string != '\0')
 320         {
 321                 if (backslash)
 322                 {
 323                         backslash = false;
 324                         if (!quoted && character == '\\' && *string == '\\')
 325                                 break;
 326                         string++;
 327                         continue;
 328                 }
 329                 switch (*string)
 330                 {
 331                   case '\\':
 332                         backslash = true;
 333                         break;
 334 
 335                   case '(':
 336                         if (!quoted)
 337                                 parencount++;
 338                         break;
 339 
 340                   case ')':
 341                         if (--parencount < 0)
 342                                 parencount = 0;
 343                         break;
 344                 }
 345 
 346                 /* Inside a comment, nothing matters */
 347                 if (parencount > 0)
 348                 {
 349                         string++;
 350                         continue;
 351                 }
 352 
 353                 if (*string == '"')
 354                         quoted = !quoted;
 355                 else if (*string == character && !quoted)
 356                         break;
 357                 string++;
 358         }
 359 
 360         /* Return pointer to the character */
 361         return string;
 362 }
 363 
 364 /*
 365 **  CHECK_BODYTYPE -- check bodytype parameter
 366 **
 367 **      Parameters:
 368 **              bodytype -- bodytype parameter
 369 **
 370 **      Returns:
 371 **              BODYTYPE_* according to parameter
 372 **
 373 */
 374 
 375 int
 376 check_bodytype(bodytype)
 377         char *bodytype;
 378 {
 379         /* check body type for legality */
 380         if (bodytype == NULL)
 381                 return BODYTYPE_NONE;
 382         if (sm_strcasecmp(bodytype, "7BIT") == 0)
 383                 return BODYTYPE_7BIT;
 384         if (sm_strcasecmp(bodytype, "8BITMIME") == 0)
 385                 return BODYTYPE_8BITMIME;
 386         return BODYTYPE_ILLEGAL;
 387 }
 388 
 389 /*
 390 **  TRUNCATE_AT_DELIM -- truncate string at a delimiter and append "..."
 391 **
 392 **      Parameters:
 393 **              str -- string to truncate
 394 **              len -- maximum length (including '\0') (0 for unlimited)
 395 **              delim -- delimiter character
 396 **
 397 **      Returns:
 398 **              None.
 399 */
 400 
 401 void
 402 truncate_at_delim(str, len, delim)
 403         char *str;
 404         size_t len;
 405         int delim;
 406 {
 407         char *p;
 408 
 409         if (str == NULL || len == 0 || strlen(str) < len)
 410                 return;
 411 
 412         *(str + len - 1) = '\0';
 413         while ((p = strrchr(str, delim)) != NULL)
 414         {
 415                 *p = '\0';
 416                 if (p - str + 4 < len)
 417                 {
 418                         *p++ = (char) delim;
 419                         *p = '\0';
 420                         (void) sm_strlcat(str, "...", len);
 421                         return;
 422                 }
 423         }
 424 
 425         /* Couldn't find a place to append "..." */
 426         if (len > 3)
 427                 (void) sm_strlcpy(str, "...", len);
 428         else
 429                 str[0] = '\0';
 430 }
 431 
 432 /*
 433 **  XALLOC -- Allocate memory, raise an exception on error
 434 **
 435 **      Parameters:
 436 **              sz -- size of area to allocate.
 437 **
 438 **      Returns:
 439 **              pointer to data region.
 440 **
 441 **      Exceptions:
 442 **              SmHeapOutOfMemory (F:sm.heap) -- cannot allocate memory
 443 **
 444 **      Side Effects:
 445 **              Memory is allocated.
 446 */
 447 
 448 char *
 449 #if SM_HEAP_CHECK
 450 xalloc_tagged(sz, file, line)
 451         register int sz;
 452         char *file;
 453         int line;
 454 #else /* SM_HEAP_CHECK */
 455 xalloc(sz)
 456         register int sz;
 457 #endif /* SM_HEAP_CHECK */
 458 {
 459         register char *p;
 460 
 461         SM_REQUIRE(sz >= 0);
 462 
 463         /* some systems can't handle size zero mallocs */
 464         if (sz <= 0)
 465                 sz = 1;
 466 
 467         /* scaffolding for testing error handling code */
 468         sm_xtrap_raise_x(&SmHeapOutOfMemory);
 469 
 470         p = sm_malloc_tagged((unsigned) sz, file, line, sm_heap_group());
 471         if (p == NULL)
 472         {
 473                 sm_exc_raise_x(&SmHeapOutOfMemory);
 474         }
 475         return p;
 476 }
 477 
 478 /*
 479 **  COPYPLIST -- copy list of pointers.
 480 **
 481 **      This routine is the equivalent of strdup for lists of
 482 **      pointers.
 483 **
 484 **      Parameters:
 485 **              list -- list of pointers to copy.
 486 **                      Must be NULL terminated.
 487 **              copycont -- if true, copy the contents of the vector
 488 **                      (which must be a string) also.
 489 **              rpool -- resource pool from which to allocate storage,
 490 **                      or NULL
 491 **
 492 **      Returns:
 493 **              a copy of 'list'.
 494 */
 495 
 496 char **
 497 copyplist(list, copycont, rpool)
 498         char **list;
 499         bool copycont;
 500         SM_RPOOL_T *rpool;
 501 {
 502         register char **vp;
 503         register char **newvp;
 504 
 505         for (vp = list; *vp != NULL; vp++)
 506                 continue;
 507 
 508         vp++;
 509 
 510         newvp = (char **) sm_rpool_malloc_x(rpool, (vp - list) * sizeof(*vp));
 511         memmove((char *) newvp, (char *) list, (int) (vp - list) * sizeof(*vp));
 512 
 513         if (copycont)
 514         {
 515                 for (vp = newvp; *vp != NULL; vp++)
 516                         *vp = sm_rpool_strdup_x(rpool, *vp);
 517         }
 518 
 519         return newvp;
 520 }
 521 
 522 /*
 523 **  COPYQUEUE -- copy address queue.
 524 **
 525 **      This routine is the equivalent of strdup for address queues;
 526 **      addresses marked as QS_IS_DEAD() aren't copied
 527 **
 528 **      Parameters:
 529 **              addr -- list of address structures to copy.
 530 **              rpool -- resource pool from which to allocate storage
 531 **
 532 **      Returns:
 533 **              a copy of 'addr'.
 534 */
 535 
 536 ADDRESS *
 537 copyqueue(addr, rpool)
 538         ADDRESS *addr;
 539         SM_RPOOL_T *rpool;
 540 {
 541         register ADDRESS *newaddr;
 542         ADDRESS *ret;
 543         register ADDRESS **tail = &ret;
 544 
 545         while (addr != NULL)
 546         {
 547                 if (!QS_IS_DEAD(addr->q_state))
 548                 {
 549                         newaddr = (ADDRESS *) sm_rpool_malloc_x(rpool,
 550                                                         sizeof(*newaddr));
 551                         STRUCTCOPY(*addr, *newaddr);
 552                         *tail = newaddr;
 553                         tail = &newaddr->q_next;
 554                 }
 555                 addr = addr->q_next;
 556         }
 557         *tail = NULL;
 558 
 559         return ret;
 560 }
 561 
 562 /*
 563 **  LOG_SENDMAIL_PID -- record sendmail pid and command line.
 564 **
 565 **      Parameters:
 566 **              e -- the current envelope.
 567 **
 568 **      Returns:
 569 **              none.
 570 **
 571 **      Side Effects:
 572 **              writes pidfile, logs command line.
 573 **              keeps file open and locked to prevent overwrite of active file
 574 */
 575 
 576 static SM_FILE_T        *Pidf = NULL;
 577 
 578 void
 579 log_sendmail_pid(e)
 580         ENVELOPE *e;
 581 {
 582         long sff;
 583         char pidpath[MAXPATHLEN];
 584         extern char *CommandLineArgs;
 585 
 586         /* write the pid to the log file for posterity */
 587         sff = SFF_NOLINK|SFF_ROOTOK|SFF_REGONLY|SFF_CREAT|SFF_NBLOCK;
 588         if (TrustedUid != 0 && RealUid == TrustedUid)
 589                 sff |= SFF_OPENASROOT;
 590         expand(PidFile, pidpath, sizeof(pidpath), e);
 591         Pidf = safefopen(pidpath, O_WRONLY|O_TRUNC, FileMode, sff);
 592         if (Pidf == NULL)
 593         {
 594                 if (errno == EWOULDBLOCK)
 595                         sm_syslog(LOG_ERR, NOQID,
 596                                   "unable to write pid to %s: file in use by another process",
 597                                   pidpath);
 598                 else
 599                         sm_syslog(LOG_ERR, NOQID,
 600                                   "unable to write pid to %s: %s",
 601                                   pidpath, sm_errstring(errno));
 602         }
 603         else
 604         {
 605                 PidFilePid = getpid();
 606 
 607                 /* write the process id on line 1 */
 608                 (void) sm_io_fprintf(Pidf, SM_TIME_DEFAULT, "%ld\n",
 609                                      (long) PidFilePid);
 610 
 611                 /* line 2 contains all command line flags */
 612                 (void) sm_io_fprintf(Pidf, SM_TIME_DEFAULT, "%s\n",
 613                                      CommandLineArgs);
 614 
 615                 /* flush */
 616                 (void) sm_io_flush(Pidf, SM_TIME_DEFAULT);
 617 
 618                 /*
 619                 **  Leave pid file open until process ends
 620                 **  so it's not overwritten by another
 621                 **  process.
 622                 */
 623         }
 624         if (LogLevel > 9)
 625                 sm_syslog(LOG_INFO, NOQID, "started as: %s", CommandLineArgs);
 626 }
 627 
 628 /*
 629 **  CLOSE_SENDMAIL_PID -- close sendmail pid file
 630 **
 631 **      Parameters:
 632 **              none.
 633 **
 634 **      Returns:
 635 **              none.
 636 */
 637 
 638 void
 639 close_sendmail_pid()
 640 {
 641         if (Pidf == NULL)
 642                 return;
 643 
 644         (void) sm_io_close(Pidf, SM_TIME_DEFAULT);
 645         Pidf = NULL;
 646 }
 647 
 648 /*
 649 **  SET_DELIVERY_MODE -- set and record the delivery mode
 650 **
 651 **      Parameters:
 652 **              mode -- delivery mode
 653 **              e -- the current envelope.
 654 **
 655 **      Returns:
 656 **              none.
 657 **
 658 **      Side Effects:
 659 **              sets {deliveryMode} macro
 660 */
 661 
 662 void
 663 set_delivery_mode(mode, e)
 664         int mode;
 665         ENVELOPE *e;
 666 {
 667         char buf[2];
 668 
 669         e->e_sendmode = (char) mode;
 670         buf[0] = (char) mode;
 671         buf[1] = '\0';
 672         macdefine(&e->e_macro, A_TEMP, macid("{deliveryMode}"), buf);
 673 }
 674 
 675 /*
 676 **  SET_OP_MODE -- set and record the op mode
 677 **
 678 **      Parameters:
 679 **              mode -- op mode
 680 **              e -- the current envelope.
 681 **
 682 **      Returns:
 683 **              none.
 684 **
 685 **      Side Effects:
 686 **              sets {opMode} macro
 687 */
 688 
 689 void
 690 set_op_mode(mode)
 691         int mode;
 692 {
 693         char buf[2];
 694         extern ENVELOPE BlankEnvelope;
 695 
 696         OpMode = (char) mode;
 697         buf[0] = (char) mode;
 698         buf[1] = '\0';
 699         macdefine(&BlankEnvelope.e_macro, A_TEMP, MID_OPMODE, buf);
 700 }
 701 
 702 /*
 703 **  PRINTAV -- print argument vector.
 704 **
 705 **      Parameters:
 706 **              fp -- output file pointer.
 707 **              av -- argument vector.
 708 **
 709 **      Returns:
 710 **              none.
 711 **
 712 **      Side Effects:
 713 **              prints av.
 714 */
 715 
 716 void
 717 printav(fp, av)
 718         SM_FILE_T *fp;
 719         char **av;
 720 {
 721         while (*av != NULL)
 722         {
 723                 if (tTd(0, 44))
 724                         sm_dprintf("\n\t%08lx=", (unsigned long) *av);
 725                 else
 726                         (void) sm_io_putc(fp, SM_TIME_DEFAULT, ' ');
 727                 if (tTd(0, 99))
 728                         sm_dprintf("%s", str2prt(*av++));
 729                 else
 730                         xputs(fp, *av++);
 731         }
 732         (void) sm_io_putc(fp, SM_TIME_DEFAULT, '\n');
 733 }
 734 
 735 /*
 736 **  XPUTS -- put string doing control escapes.
 737 **
 738 **      Parameters:
 739 **              fp -- output file pointer.
 740 **              s -- string to put.
 741 **
 742 **      Returns:
 743 **              none.
 744 **
 745 **      Side Effects:
 746 **              output to stdout
 747 */
 748 
 749 void
 750 xputs(fp, s)
 751         SM_FILE_T *fp;
 752         const char *s;
 753 {
 754         int c;
 755         struct metamac *mp;
 756         bool shiftout = false;
 757         extern struct metamac MetaMacros[];
 758         static SM_DEBUG_T DebugANSI = SM_DEBUG_INITIALIZER("ANSI",
 759                 "@(#)$Debug: ANSI - enable reverse video in debug output $");
 760 
 761         /*
 762         **  TermEscape is set here, rather than in main(),
 763         **  because ANSI mode can be turned on or off at any time
 764         **  if we are in -bt rule testing mode.
 765         */
 766 
 767         if (sm_debug_unknown(&DebugANSI))
 768         {
 769                 if (sm_debug_active(&DebugANSI, 1))
 770                 {
 771                         TermEscape.te_rv_on = "\033[7m";
 772                         TermEscape.te_normal = "\033[0m";
 773                 }
 774                 else
 775                 {
 776                         TermEscape.te_rv_on = "";
 777                         TermEscape.te_normal = "";
 778                 }
 779         }
 780 
 781         if (s == NULL)
 782         {
 783                 (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s<null>%s",
 784                                      TermEscape.te_rv_on, TermEscape.te_normal);
 785                 return;
 786         }
 787         while ((c = (*s++ & 0377)) != '\0')
 788         {
 789                 if (shiftout)
 790                 {
 791                         (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
 792                                              TermEscape.te_normal);
 793                         shiftout = false;
 794                 }
 795                 if (!isascii(c) && !tTd(84, 1))
 796                 {
 797                         if (c == MATCHREPL)
 798                         {
 799                                 (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
 800                                                      "%s$",
 801                                                      TermEscape.te_rv_on);
 802                                 shiftout = true;
 803                                 if (*s == '\0')
 804                                         continue;
 805                                 c = *s++ & 0377;
 806                                 goto printchar;
 807                         }
 808                         if (c == MACROEXPAND || c == MACRODEXPAND)
 809                         {
 810                                 (void) sm_io_fprintf(fp, SM_TIME_DEFAULT,
 811                                                      "%s$",
 812                                                      TermEscape.te_rv_on);
 813                                 if (c == MACRODEXPAND)
 814                                         (void) sm_io_putc(fp,
 815                                                           SM_TIME_DEFAULT, '&');
 816                                 shiftout = true;
 817                                 if (*s == '\0')
 818                                         continue;
 819                                 if (strchr("=~&?", *s) != NULL)
 820                                         (void) sm_io_putc(fp,
 821                                                           SM_TIME_DEFAULT,
 822                                                           *s++);
 823                                 if (bitset(0200, *s))
 824                                         (void) sm_io_fprintf(fp,
 825                                                              SM_TIME_DEFAULT,
 826                                                              "{%s}",
 827                                                              macname(bitidx(*s++)));
 828                                 else
 829                                         (void) sm_io_fprintf(fp,
 830                                                              SM_TIME_DEFAULT,
 831                                                              "%c",
 832                                                              *s++);
 833                                 continue;
 834                         }
 835                         for (mp = MetaMacros; mp->metaname != '\0'; mp++)
 836                         {
 837                                 if (bitidx(mp->metaval) == c)
 838                                 {
 839                                         (void) sm_io_fprintf(fp,
 840                                                              SM_TIME_DEFAULT,
 841                                                              "%s$%c",
 842                                                              TermEscape.te_rv_on,
 843                                                              mp->metaname);
 844                                         shiftout = true;
 845                                         break;
 846                                 }
 847                         }
 848                         if (c == MATCHCLASS || c == MATCHNCLASS)
 849                         {
 850                                 if (bitset(0200, *s))
 851                                         (void) sm_io_fprintf(fp,
 852                                                              SM_TIME_DEFAULT,
 853                                                              "{%s}",
 854                                                              macname(bitidx(*s++)));
 855                                 else if (*s != '\0')
 856                                         (void) sm_io_fprintf(fp,
 857                                                              SM_TIME_DEFAULT,
 858                                                              "%c",
 859                                                              *s++);
 860                         }
 861                         if (mp->metaname != '\0')
 862                                 continue;
 863 
 864                         /* unrecognized meta character */
 865                         (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%sM-",
 866                                              TermEscape.te_rv_on);
 867                         shiftout = true;
 868                         c &= 0177;
 869                 }
 870   printchar:
 871                 if (isascii(c) && isprint(c))
 872                 {
 873                         (void) sm_io_putc(fp, SM_TIME_DEFAULT, c);
 874                         continue;
 875                 }
 876 
 877                 /* wasn't a meta-macro -- find another way to print it */
 878                 switch (c)
 879                 {
 880                   case '\n':
 881                         c = 'n';
 882                         break;
 883 
 884                   case '\r':
 885                         c = 'r';
 886                         break;
 887 
 888                   case '\t':
 889                         c = 't';
 890                         break;
 891                 }
 892                 if (!shiftout)
 893                 {
 894                         (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
 895                                              TermEscape.te_rv_on);
 896                         shiftout = true;
 897                 }
 898                 if (isascii(c) && isprint(c))
 899                 {
 900                         (void) sm_io_putc(fp, SM_TIME_DEFAULT, '\\');
 901                         (void) sm_io_putc(fp, SM_TIME_DEFAULT, c);
 902                 }
 903                 else if (tTd(84, 2))
 904                         (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, " %o ", c);
 905                 else if (tTd(84, 1))
 906                         (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, " %#x ", c);
 907                 else if (!isascii(c) && !tTd(84, 1))
 908                 {
 909                         (void) sm_io_putc(fp, SM_TIME_DEFAULT, '^');
 910                         (void) sm_io_putc(fp, SM_TIME_DEFAULT, c ^ 0100);
 911                 }
 912         }
 913         if (shiftout)
 914                 (void) sm_io_fprintf(fp, SM_TIME_DEFAULT, "%s",
 915                                      TermEscape.te_normal);
 916         (void) sm_io_flush(fp, SM_TIME_DEFAULT);
 917 }
 918 
 919 /*
 920 **  MAKELOWER -- Translate a line into lower case
 921 **
 922 **      Parameters:
 923 **              p -- the string to translate.  If NULL, return is
 924 **                      immediate.
 925 **
 926 **      Returns:
 927 **              none.
 928 **
 929 **      Side Effects:
 930 **              String pointed to by p is translated to lower case.
 931 */
 932 
 933 void
 934 makelower(p)
 935         register char *p;
 936 {
 937         register char c;
 938 
 939         if (p == NULL)
 940                 return;
 941         for (; (c = *p) != '\0'; p++)
 942                 if (isascii(c) && isupper(c))
 943                         *p = tolower(c);
 944 }
 945 
 946 /*
 947 **  FIXCRLF -- fix <CR><LF> in line.
 948 **
 949 **      Looks for the <CR><LF> combination and turns it into the
 950 **      UNIX canonical <NL> character.  It only takes one line,
 951 **      i.e., it is assumed that the first <NL> found is the end
 952 **      of the line.
 953 **
 954 **      Parameters:
 955 **              line -- the line to fix.
 956 **              stripnl -- if true, strip the newline also.
 957 **
 958 **      Returns:
 959 **              none.
 960 **
 961 **      Side Effects:
 962 **              line is changed in place.
 963 */
 964 
 965 void
 966 fixcrlf(line, stripnl)
 967         char *line;
 968         bool stripnl;
 969 {
 970         register char *p;
 971 
 972         p = strchr(line, '\n');
 973         if (p == NULL)
 974                 return;
 975         if (p > line && p[-1] == '\r')
 976                 p--;
 977         if (!stripnl)
 978                 *p++ = '\n';
 979         *p = '\0';
 980 }
 981 
 982 /*
 983 **  PUTLINE -- put a line like fputs obeying SMTP conventions
 984 **
 985 **      This routine always guarantees outputing a newline (or CRLF,
 986 **      as appropriate) at the end of the string.
 987 **
 988 **      Parameters:
 989 **              l -- line to put.
 990 **              mci -- the mailer connection information.
 991 **
 992 **      Returns:
 993 **              true iff line was written successfully
 994 **
 995 **      Side Effects:
 996 **              output of l to mci->mci_out.
 997 */
 998 
 999 bool
1000 putline(l, mci)
1001         register char *l;
1002         register MCI *mci;
1003 {
1004         return putxline(l, strlen(l), mci, PXLF_MAPFROM);
1005 }
1006 
1007 /*
1008 **  PUTXLINE -- putline with flags bits.
1009 **
1010 **      This routine always guarantees outputing a newline (or CRLF,
1011 **      as appropriate) at the end of the string.
1012 **
1013 **      Parameters:
1014 **              l -- line to put.
1015 **              len -- the length of the line.
1016 **              mci -- the mailer connection information.
1017 **              pxflags -- flag bits:
1018 **                  PXLF_MAPFROM -- map From_ to >From_.
1019 **                  PXLF_STRIP8BIT -- strip 8th bit.
1020 **                  PXLF_HEADER -- map bare newline in header to newline space.
1021 **                  PXLF_NOADDEOL -- don't add an EOL if one wasn't present.
1022 **                  PXLF_STRIPMQUOTE -- strip METAQUOTE bytes.
1023 **
1024 **      Returns:
1025 **              true iff line was written successfully
1026 **
1027 **      Side Effects:
1028 **              output of l to mci->mci_out.
1029 */
1030 
1031 
1032 #define PUTX(limit)                                                     \
1033         do                                                              \
1034         {                                                               \
1035                 quotenext = false;                                      \
1036                 while (l < limit)                                    \
1037                 {                                                       \
1038                         unsigned char c = (unsigned char) *l++;         \
1039                                                                         \
1040                         if (bitset(PXLF_STRIPMQUOTE, pxflags) &&        \
1041                             !quotenext && c == METAQUOTE)               \
1042                         {                                               \
1043                                 quotenext = true;                       \
1044                                 continue;                               \
1045                         }                                               \
1046                         quotenext = false;                              \
1047                         if (strip8bit)                                  \
1048                                 c &= 0177;                          \
1049                         if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,        \
1050                                        c) == SM_IO_EOF)                 \
1051                         {                                               \
1052                                 dead = true;                            \
1053                                 break;                                  \
1054                         }                                               \
1055                         if (TrafficLogFile != NULL)                     \
1056                                 (void) sm_io_putc(TrafficLogFile,       \
1057                                                   SM_TIME_DEFAULT,      \
1058                                                   c);                   \
1059                 }                                                       \
1060         } while (0)
1061 
1062 bool
1063 putxline(l, len, mci, pxflags)
1064         register char *l;
1065         size_t len;
1066         register MCI *mci;
1067         int pxflags;
1068 {
1069         register char *p, *end;
1070         int slop;
1071         bool dead, quotenext, strip8bit;
1072 
1073         /* strip out 0200 bits -- these can look like TELNET protocol */
1074         strip8bit = bitset(MCIF_7BIT, mci->mci_flags) ||
1075                     bitset(PXLF_STRIP8BIT, pxflags);
1076         dead = false;
1077         slop = 0;
1078 
1079         end = l + len;
1080         do
1081         {
1082                 bool noeol = false;
1083 
1084                 /* find the end of the line */
1085                 p = memchr(l, '\n', end - l);
1086                 if (p == NULL)
1087                 {
1088                         p = end;
1089                         noeol = true;
1090                 }
1091 
1092                 if (TrafficLogFile != NULL)
1093                         (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1094                                              "%05d >>> ", (int) CurrentPid);
1095 
1096                 /* check for line overflow */
1097                 while (mci->mci_mailer->m_linelimit > 0 &&
1098                        (p - l + slop) > mci->mci_mailer->m_linelimit)
1099                 {
1100                         register char *q = &l[mci->mci_mailer->m_linelimit - slop - 1];
1101 
1102                         if (l[0] == '.' && slop == 0 &&
1103                             bitnset(M_XDOT, mci->mci_mailer->m_flags))
1104                         {
1105                                 if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1106                                                '.') == SM_IO_EOF)
1107                                         dead = true;
1108                                 if (TrafficLogFile != NULL)
1109                                         (void) sm_io_putc(TrafficLogFile,
1110                                                           SM_TIME_DEFAULT, '.');
1111                         }
1112                         else if (l[0] == 'F' && slop == 0 &&
1113                                  bitset(PXLF_MAPFROM, pxflags) &&
1114                                  strncmp(l, "From ", 5) == 0 &&
1115                                  bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
1116                         {
1117                                 if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1118                                                '>') == SM_IO_EOF)
1119                                         dead = true;
1120                                 if (TrafficLogFile != NULL)
1121                                         (void) sm_io_putc(TrafficLogFile,
1122                                                           SM_TIME_DEFAULT,
1123                                                           '>');
1124                         }
1125                         if (dead)
1126                                 break;
1127 
1128                         PUTX(q);
1129                         if (dead)
1130                                 break;
1131 
1132                         if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1133                                         '!') == SM_IO_EOF ||
1134                             sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
1135                                         mci->mci_mailer->m_eol) == SM_IO_EOF ||
1136                             sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1137                                         ' ') == SM_IO_EOF)
1138                         {
1139                                 dead = true;
1140                                 break;
1141                         }
1142                         if (TrafficLogFile != NULL)
1143                         {
1144                                 (void) sm_io_fprintf(TrafficLogFile,
1145                                                      SM_TIME_DEFAULT,
1146                                                      "!\n%05d >>>  ",
1147                                                      (int) CurrentPid);
1148                         }
1149                         slop = 1;
1150                 }
1151 
1152                 if (dead)
1153                         break;
1154 
1155                 /* output last part */
1156                 if (l[0] == '.' && slop == 0 &&
1157                     bitnset(M_XDOT, mci->mci_mailer->m_flags) &&
1158                     !bitset(MCIF_INLONGLINE, mci->mci_flags))
1159                 {
1160                         if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '.') ==
1161                             SM_IO_EOF)
1162                         {
1163                                 dead = true;
1164                                 break;
1165                         }
1166                         if (TrafficLogFile != NULL)
1167                                 (void) sm_io_putc(TrafficLogFile,
1168                                                   SM_TIME_DEFAULT, '.');
1169                 }
1170                 else if (l[0] == 'F' && slop == 0 &&
1171                          bitset(PXLF_MAPFROM, pxflags) &&
1172                          strncmp(l, "From ", 5) == 0 &&
1173                          bitnset(M_ESCFROM, mci->mci_mailer->m_flags) &&
1174                          !bitset(MCIF_INLONGLINE, mci->mci_flags))
1175                 {
1176                         if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '>') ==
1177                             SM_IO_EOF)
1178                         {
1179                                 dead = true;
1180                                 break;
1181                         }
1182                         if (TrafficLogFile != NULL)
1183                                 (void) sm_io_putc(TrafficLogFile,
1184                                                   SM_TIME_DEFAULT, '>');
1185                 }
1186                 PUTX(p);
1187                 if (dead)
1188                         break;
1189 
1190                 if (TrafficLogFile != NULL)
1191                         (void) sm_io_putc(TrafficLogFile, SM_TIME_DEFAULT,
1192                                           '\n');
1193                 if ((!bitset(PXLF_NOADDEOL, pxflags) || !noeol))
1194                 {
1195                         mci->mci_flags &= ~MCIF_INLONGLINE;
1196                         if (sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
1197                                         mci->mci_mailer->m_eol) == SM_IO_EOF)
1198                         {
1199                                 dead = true;
1200                                 break;
1201                         }
1202                 }
1203                 else
1204                         mci->mci_flags |= MCIF_INLONGLINE;
1205 
1206                 if (l < end && *l == '\n')
1207                 {
1208                         if (*++l != ' ' && *l != '\t' && *l != '\0' &&
1209                             bitset(PXLF_HEADER, pxflags))
1210                         {
1211                                 if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1212                                                ' ') == SM_IO_EOF)
1213                                 {
1214                                         dead = true;
1215                                         break;
1216                                 }
1217 
1218                                 if (TrafficLogFile != NULL)
1219                                         (void) sm_io_putc(TrafficLogFile,
1220                                                           SM_TIME_DEFAULT, ' ');
1221                         }
1222                 }
1223 
1224         } while (l < end);
1225         return !dead;
1226 }
1227 
1228 /*
1229 **  XUNLINK -- unlink a file, doing logging as appropriate.
1230 **
1231 **      Parameters:
1232 **              f -- name of file to unlink.
1233 **
1234 **      Returns:
1235 **              return value of unlink()
1236 **
1237 **      Side Effects:
1238 **              f is unlinked.
1239 */
1240 
1241 int
1242 xunlink(f)
1243         char *f;
1244 {
1245         register int i;
1246         int save_errno;
1247 
1248         if (LogLevel > 98)
1249                 sm_syslog(LOG_DEBUG, CurEnv->e_id, "unlink %s", f);
1250 
1251         i = unlink(f);
1252         save_errno = errno;
1253         if (i < 0 && LogLevel > 97)
1254                 sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s: unlink-fail %d",
1255                           f, errno);
1256         if (i >= 0)
1257                 SYNC_DIR(f, false);
1258         errno = save_errno;
1259         return i;
1260 }
1261 
1262 /*
1263 **  SFGETS -- "safe" fgets -- times out and ignores random interrupts.
1264 **
1265 **      Parameters:
1266 **              buf -- place to put the input line.
1267 **              siz -- size of buf.
1268 **              fp -- file to read from.
1269 **              timeout -- the timeout before error occurs.
1270 **              during -- what we are trying to read (for error messages).
1271 **
1272 **      Returns:
1273 **              NULL on error (including timeout).  This may also leave
1274 **                      buf containing a null string.
1275 **              buf otherwise.
1276 */
1277 
1278 
1279 char *
1280 sfgets(buf, siz, fp, timeout, during)
1281         char *buf;
1282         int siz;
1283         SM_FILE_T *fp;
1284         time_t timeout;
1285         char *during;
1286 {
1287         register char *p;
1288         int save_errno;
1289         int io_timeout;
1290 
1291         SM_REQUIRE(siz > 0);
1292         SM_REQUIRE(buf != NULL);
1293 
1294         if (fp == NULL)
1295         {
1296                 buf[0] = '\0';
1297                 errno = EBADF;
1298                 return NULL;
1299         }
1300 
1301         /* try to read */
1302         p = NULL;
1303         errno = 0;
1304 
1305         /* convert the timeout to sm_io notation */
1306         io_timeout = (timeout <= 0) ? SM_TIME_DEFAULT : timeout * 1000;
1307         while (!sm_io_eof(fp) && !sm_io_error(fp))
1308         {
1309                 errno = 0;
1310                 p = sm_io_fgets(fp, io_timeout, buf, siz);
1311                 if (p == NULL && errno == EAGAIN)
1312                 {
1313                         /* The sm_io_fgets() call timedout */
1314                         if (LogLevel > 1)
1315                                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
1316                                           "timeout waiting for input from %.100s during %s",
1317                                           CURHOSTNAME,
1318                                           during);
1319                         buf[0] = '\0';
1320 #if XDEBUG
1321                         checkfd012(during);
1322 #endif /* XDEBUG */
1323                         if (TrafficLogFile != NULL)
1324                                 (void) sm_io_fprintf(TrafficLogFile,
1325                                                      SM_TIME_DEFAULT,
1326                                                      "%05d <<< [TIMEOUT]\n",
1327                                                      (int) CurrentPid);
1328                         errno = ETIMEDOUT;
1329                         return NULL;
1330                 }
1331                 if (p != NULL || errno != EINTR)
1332                         break;
1333                 (void) sm_io_clearerr(fp);
1334         }
1335         save_errno = errno;
1336 
1337         /* clean up the books and exit */
1338         LineNumber++;
1339         if (p == NULL)
1340         {
1341                 buf[0] = '\0';
1342                 if (TrafficLogFile != NULL)
1343                         (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1344                                              "%05d <<< [EOF]\n",
1345                                              (int) CurrentPid);
1346                 errno = save_errno;
1347                 return NULL;
1348         }
1349         if (TrafficLogFile != NULL)
1350                 (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1351                                      "%05d <<< %s", (int) CurrentPid, buf);
1352         if (SevenBitInput)
1353         {
1354                 for (p = buf; *p != '\0'; p++)
1355                         *p &= ~0200;
1356         }
1357         else if (!HasEightBits)
1358         {
1359                 for (p = buf; *p != '\0'; p++)
1360                 {
1361                         if (bitset(0200, *p))
1362                         {
1363                                 HasEightBits = true;
1364                                 break;
1365                         }
1366                 }
1367         }
1368         return buf;
1369 }
1370 
1371 /*
1372 **  FGETFOLDED -- like fgets, but knows about folded lines.
1373 **
1374 **      Parameters:
1375 **              buf -- place to put result.
1376 **              np -- pointer to bytes available; will be updated with
1377 **                      the actual buffer size (not number of bytes filled)
1378 **                      on return.
1379 **              f -- file to read from.
1380 **
1381 **      Returns:
1382 **              input line(s) on success, NULL on error or SM_IO_EOF.
1383 **              This will normally be buf -- unless the line is too
1384 **                      long, when it will be sm_malloc_x()ed.
1385 **
1386 **      Side Effects:
1387 **              buf gets lines from f, with continuation lines (lines
1388 **              with leading white space) appended.  CRLF's are mapped
1389 **              into single newlines.  Any trailing NL is stripped.
1390 */
1391 
1392 char *
1393 fgetfolded(buf, np, f)
1394         char *buf;
1395         int *np;
1396         SM_FILE_T *f;
1397 {
1398         register char *p = buf;
1399         char *bp = buf;
1400         register int i;
1401         int n;
1402 
1403         SM_REQUIRE(np != NULL);
1404         n = *np;
1405         SM_REQUIRE(n > 0);
1406         SM_REQUIRE(buf != NULL);
1407         if (f == NULL)
1408         {
1409                 buf[0] = '\0';
1410                 errno = EBADF;
1411                 return NULL;
1412         }
1413 
1414         n--;
1415         while ((i = sm_io_getc(f, SM_TIME_DEFAULT)) != SM_IO_EOF)
1416         {
1417                 if (i == '\r')
1418                 {
1419                         i = sm_io_getc(f, SM_TIME_DEFAULT);
1420                         if (i != '\n')
1421                         {
1422                                 if (i != SM_IO_EOF)
1423                                         (void) sm_io_ungetc(f, SM_TIME_DEFAULT,
1424                                                             i);
1425                                 i = '\r';
1426                         }
1427                 }
1428                 if (--n <= 0)
1429                 {
1430                         /* allocate new space */
1431                         char *nbp;
1432                         int nn;
1433 
1434                         nn = (p - bp);
1435                         if (nn < MEMCHUNKSIZE)
1436                                 nn *= 2;
1437                         else
1438                                 nn += MEMCHUNKSIZE;
1439                         nbp = sm_malloc_x(nn);
1440                         memmove(nbp, bp, p - bp);
1441                         p = &nbp[p - bp];
1442                         if (bp != buf)
1443                                 sm_free(bp);
1444                         bp = nbp;
1445                         n = nn - (p - bp);
1446                         *np = nn;
1447                 }
1448                 *p++ = i;
1449                 if (i == '\n')
1450                 {
1451                         LineNumber++;
1452                         i = sm_io_getc(f, SM_TIME_DEFAULT);
1453                         if (i != SM_IO_EOF)
1454                                 (void) sm_io_ungetc(f, SM_TIME_DEFAULT, i);
1455                         if (i != ' ' && i != '\t')
1456                                 break;
1457                 }
1458         }
1459         if (p == bp)
1460                 return NULL;
1461         if (p[-1] == '\n')
1462                 p--;
1463         *p = '\0';
1464         return bp;
1465 }
1466 
1467 /*
1468 **  CURTIME -- return current time.
1469 **
1470 **      Parameters:
1471 **              none.
1472 **
1473 **      Returns:
1474 **              the current time.
1475 */
1476 
1477 time_t
1478 curtime()
1479 {
1480         auto time_t t;
1481 
1482         (void) time(&t);
1483         return t;
1484 }
1485 
1486 /*
1487 **  ATOBOOL -- convert a string representation to boolean.
1488 **
1489 **      Defaults to false
1490 **
1491 **      Parameters:
1492 **              s -- string to convert.  Takes "tTyY", empty, and NULL as true,
1493 **                      others as false.
1494 **
1495 **      Returns:
1496 **              A boolean representation of the string.
1497 */
1498 
1499 bool
1500 atobool(s)
1501         register char *s;
1502 {
1503         if (s == NULL || *s == '\0' || strchr("tTyY", *s) != NULL)
1504                 return true;
1505         return false;
1506 }
1507 
1508 /*
1509 **  ATOOCT -- convert a string representation to octal.
1510 **
1511 **      Parameters:
1512 **              s -- string to convert.
1513 **
1514 **      Returns:
1515 **              An integer representing the string interpreted as an
1516 **              octal number.
1517 */
1518 
1519 int
1520 atooct(s)
1521         register char *s;
1522 {
1523         register int i = 0;
1524 
1525         while (*s >= '0' && *s <= '7')
1526                 i = (i << 3) | (*s++ - '0');
1527         return i;
1528 }
1529 
1530 /*
1531 **  BITINTERSECT -- tell if two bitmaps intersect
1532 **
1533 **      Parameters:
1534 **              a, b -- the bitmaps in question
1535 **
1536 **      Returns:
1537 **              true if they have a non-null intersection
1538 **              false otherwise
1539 */
1540 
1541 bool
1542 bitintersect(a, b)
1543         BITMAP256 a;
1544         BITMAP256 b;
1545 {
1546         int i;
1547 
1548         for (i = BITMAPBYTES / sizeof(int); --i >= 0; )
1549         {
1550                 if ((a[i] & b[i]) != 0)
1551                         return true;
1552         }
1553         return false;
1554 }
1555 
1556 /*
1557 **  BITZEROP -- tell if a bitmap is all zero
1558 **
1559 **      Parameters:
1560 **              map -- the bit map to check
1561 **
1562 **      Returns:
1563 **              true if map is all zero.
1564 **              false if there are any bits set in map.
1565 */
1566 
1567 bool
1568 bitzerop(map)
1569         BITMAP256 map;
1570 {
1571         int i;
1572 
1573         for (i = BITMAPBYTES / sizeof(int); --i >= 0; )
1574         {
1575                 if (map[i] != 0)
1576                         return false;
1577         }
1578         return true;
1579 }
1580 
1581 /*
1582 **  STRCONTAINEDIN -- tell if one string is contained in another
1583 **
1584 **      Parameters:
1585 **              icase -- ignore case?
1586 **              a -- possible substring.
1587 **              b -- possible superstring.
1588 **
1589 **      Returns:
1590 **              true if a is contained in b (case insensitive).
1591 **              false otherwise.
1592 */
1593 
1594 bool
1595 strcontainedin(icase, a, b)
1596         bool icase;
1597         register char *a;
1598         register char *b;
1599 {
1600         int la;
1601         int lb;
1602         int c;
1603 
1604         la = strlen(a);
1605         lb = strlen(b);
1606         c = *a;
1607         if (icase && isascii(c) && isupper(c))
1608                 c = tolower(c);
1609         for (; lb-- >= la; b++)
1610         {
1611                 if (icase)
1612                 {
1613                         if (*b != c &&
1614                             isascii(*b) && isupper(*b) && tolower(*b) != c)
1615                                 continue;
1616                         if (sm_strncasecmp(a, b, la) == 0)
1617                                 return true;
1618                 }
1619                 else
1620                 {
1621                         if (*b != c)
1622                                 continue;
1623                         if (strncmp(a, b, la) == 0)
1624                                 return true;
1625                 }
1626         }
1627         return false;
1628 }
1629 
1630 /*
1631 **  CHECKFD012 -- check low numbered file descriptors
1632 **
1633 **      File descriptors 0, 1, and 2 should be open at all times.
1634 **      This routine verifies that, and fixes it if not true.
1635 **
1636 **      Parameters:
1637 **              where -- a tag printed if the assertion failed
1638 **
1639 **      Returns:
1640 **              none
1641 */
1642 
1643 void
1644 checkfd012(where)
1645         char *where;
1646 {
1647 #if XDEBUG
1648         register int i;
1649 
1650         for (i = 0; i < 3; i++)
1651                 fill_fd(i, where);
1652 #endif /* XDEBUG */
1653 }
1654 
1655 /*
1656 **  CHECKFDOPEN -- make sure file descriptor is open -- for extended debugging
1657 **
1658 **      Parameters:
1659 **              fd -- file descriptor to check.
1660 **              where -- tag to print on failure.
1661 **
1662 **      Returns:
1663 **              none.
1664 */
1665 
1666 void
1667 checkfdopen(fd, where)
1668         int fd;
1669         char *where;
1670 {
1671 #if XDEBUG
1672         struct stat st;
1673 
1674         if (fstat(fd, &st) < 0 && errno == EBADF)
1675         {
1676                 syserr("checkfdopen(%d): %s not open as expected!", fd, where);
1677                 printopenfds(true);
1678         }
1679 #endif /* XDEBUG */
1680 }
1681 
1682 /*
1683 **  CHECKFDS -- check for new or missing file descriptors
1684 **
1685 **      Parameters:
1686 **              where -- tag for printing.  If null, take a base line.
1687 **
1688 **      Returns:
1689 **              none
1690 **
1691 **      Side Effects:
1692 **              If where is set, shows changes since the last call.
1693 */
1694 
1695 void
1696 checkfds(where)
1697         char *where;
1698 {
1699         int maxfd;
1700         register int fd;
1701         bool printhdr = true;
1702         int save_errno = errno;
1703         static BITMAP256 baseline;
1704         extern int DtableSize;
1705 
1706         if (DtableSize > BITMAPBITS)
1707                 maxfd = BITMAPBITS;
1708         else
1709                 maxfd = DtableSize;
1710         if (where == NULL)
1711                 clrbitmap(baseline);
1712 
1713         for (fd = 0; fd < maxfd; fd++)
1714         {
1715                 struct stat stbuf;
1716 
1717                 if (fstat(fd, &stbuf) < 0 && errno != EOPNOTSUPP)
1718                 {
1719                         if (!bitnset(fd, baseline))
1720                                 continue;
1721                         clrbitn(fd, baseline);
1722                 }
1723                 else if (!bitnset(fd, baseline))
1724                         setbitn(fd, baseline);
1725                 else
1726                         continue;
1727 
1728                 /* file state has changed */
1729                 if (where == NULL)
1730                         continue;
1731                 if (printhdr)
1732                 {
1733                         sm_syslog(LOG_DEBUG, CurEnv->e_id,
1734                                   "%s: changed fds:",
1735                                   where);
1736                         printhdr = false;
1737                 }
1738                 dumpfd(fd, true, true);
1739         }
1740         errno = save_errno;
1741 }
1742 
1743 /*
1744 **  PRINTOPENFDS -- print the open file descriptors (for debugging)
1745 **
1746 **      Parameters:
1747 **              logit -- if set, send output to syslog; otherwise
1748 **                      print for debugging.
1749 **
1750 **      Returns:
1751 **              none.
1752 */
1753 
1754 #if NETINET || NETINET6
1755 # include <arpa/inet.h>
1756 #endif /* NETINET || NETINET6 */
1757 
1758 void
1759 printopenfds(logit)
1760         bool logit;
1761 {
1762         register int fd;
1763         extern int DtableSize;
1764 
1765         for (fd = 0; fd < DtableSize; fd++)
1766                 dumpfd(fd, false, logit);
1767 }
1768 
1769 /*
1770 **  DUMPFD -- dump a file descriptor
1771 **
1772 **      Parameters:
1773 **              fd -- the file descriptor to dump.
1774 **              printclosed -- if set, print a notification even if
1775 **                      it is closed; otherwise print nothing.
1776 **              logit -- if set, use sm_syslog instead of sm_dprintf()
1777 **
1778 **      Returns:
1779 **              none.
1780 */
1781 
1782 void
1783 dumpfd(fd, printclosed, logit)
1784         int fd;
1785         bool printclosed;
1786         bool logit;
1787 {
1788         register char *p;
1789         char *hp;
1790 #ifdef S_IFSOCK
1791         SOCKADDR sa;
1792 #endif /* S_IFSOCK */
1793         auto SOCKADDR_LEN_T slen;
1794         int i;
1795 #if STAT64 > 0
1796         struct stat64 st;
1797 #else /* STAT64 > 0 */
1798         struct stat st;
1799 #endif /* STAT64 > 0 */
1800         char buf[200];
1801 
1802         p = buf;
1803         (void) sm_snprintf(p, SPACELEFT(buf, p), "%3d: ", fd);
1804         p += strlen(p);
1805 
1806         if (
1807 #if STAT64 > 0
1808             fstat64(fd, &st)
1809 #else /* STAT64 > 0 */
1810             fstat(fd, &st)
1811 #endif /* STAT64 > 0 */
1812             < 0)
1813         {
1814                 if (errno != EBADF)
1815                 {
1816                         (void) sm_snprintf(p, SPACELEFT(buf, p),
1817                                 "CANNOT STAT (%s)",
1818                                 sm_errstring(errno));
1819                         goto printit;
1820                 }
1821                 else if (printclosed)
1822                 {
1823                         (void) sm_snprintf(p, SPACELEFT(buf, p), "CLOSED");
1824                         goto printit;
1825                 }
1826                 return;
1827         }
1828 
1829         i = fcntl(fd, F_GETFL, 0);
1830         if (i != -1)
1831         {
1832                 (void) sm_snprintf(p, SPACELEFT(buf, p), "fl=0x%x, ", i);
1833                 p += strlen(p);
1834         }
1835 
1836         (void) sm_snprintf(p, SPACELEFT(buf, p), "mode=%o: ",
1837                         (int) st.st_mode);
1838         p += strlen(p);
1839         switch (st.st_mode & S_IFMT)
1840         {
1841 #ifdef S_IFSOCK
1842           case S_IFSOCK:
1843                 (void) sm_snprintf(p, SPACELEFT(buf, p), "SOCK ");
1844                 p += strlen(p);
1845                 memset(&sa, '\0', sizeof(sa));
1846                 slen = sizeof(sa);
1847                 if (getsockname(fd, &sa.sa, &slen) < 0)
1848                         (void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
1849                                  sm_errstring(errno));
1850                 else
1851                 {
1852                         hp = hostnamebyanyaddr(&sa);
1853                         if (hp == NULL)
1854                         {
1855                                 /* EMPTY */
1856                                 /* do nothing */
1857                         }
1858 # if NETINET
1859                         else if (sa.sa.sa_family == AF_INET)
1860                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1861                                         "%s/%d", hp, ntohs(sa.sin.sin_port));
1862 # endif /* NETINET */
1863 # if NETINET6
1864                         else if (sa.sa.sa_family == AF_INET6)
1865                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1866                                         "%s/%d", hp, ntohs(sa.sin6.sin6_port));
1867 # endif /* NETINET6 */
1868                         else
1869                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1870                                         "%s", hp);
1871                 }
1872                 p += strlen(p);
1873                 (void) sm_snprintf(p, SPACELEFT(buf, p), "->");
1874                 p += strlen(p);
1875                 slen = sizeof(sa);
1876                 if (getpeername(fd, &sa.sa, &slen) < 0)
1877                         (void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
1878                                         sm_errstring(errno));
1879                 else
1880                 {
1881                         hp = hostnamebyanyaddr(&sa);
1882                         if (hp == NULL)
1883                         {
1884                                 /* EMPTY */
1885                                 /* do nothing */
1886                         }
1887 # if NETINET
1888                         else if (sa.sa.sa_family == AF_INET)
1889                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1890                                         "%s/%d", hp, ntohs(sa.sin.sin_port));
1891 # endif /* NETINET */
1892 # if NETINET6
1893                         else if (sa.sa.sa_family == AF_INET6)
1894                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1895                                         "%s/%d", hp, ntohs(sa.sin6.sin6_port));
1896 # endif /* NETINET6 */
1897                         else
1898                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1899                                         "%s", hp);
1900                 }
1901                 break;
1902 #endif /* S_IFSOCK */
1903 
1904           case S_IFCHR:
1905                 (void) sm_snprintf(p, SPACELEFT(buf, p), "CHR: ");
1906                 p += strlen(p);
1907                 goto defprint;
1908 
1909 #ifdef S_IFBLK
1910           case S_IFBLK:
1911                 (void) sm_snprintf(p, SPACELEFT(buf, p), "BLK: ");
1912                 p += strlen(p);
1913                 goto defprint;
1914 #endif /* S_IFBLK */
1915 
1916 #if defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK)
1917           case S_IFIFO:
1918                 (void) sm_snprintf(p, SPACELEFT(buf, p), "FIFO: ");
1919                 p += strlen(p);
1920                 goto defprint;
1921 #endif /* defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK) */
1922 
1923 #ifdef S_IFDIR
1924           case S_IFDIR:
1925                 (void) sm_snprintf(p, SPACELEFT(buf, p), "DIR: ");
1926                 p += strlen(p);
1927                 goto defprint;
1928 #endif /* S_IFDIR */
1929 
1930 #ifdef S_IFLNK
1931           case S_IFLNK:
1932                 (void) sm_snprintf(p, SPACELEFT(buf, p), "LNK: ");
1933                 p += strlen(p);
1934                 goto defprint;
1935 #endif /* S_IFLNK */
1936 
1937           default:
1938 defprint:
1939                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1940                          "dev=%d/%d, ino=%llu, nlink=%d, u/gid=%d/%d, ",
1941                          major(st.st_dev), minor(st.st_dev),
1942                          (ULONGLONG_T) st.st_ino,
1943                          (int) st.st_nlink, (int) st.st_uid,
1944                          (int) st.st_gid);
1945                 p += strlen(p);
1946                 (void) sm_snprintf(p, SPACELEFT(buf, p), "size=%llu",
1947                          (ULONGLONG_T) st.st_size);
1948                 break;
1949         }
1950 
1951 printit:
1952         if (logit)
1953                 sm_syslog(LOG_DEBUG, CurEnv ? CurEnv->e_id : NULL,
1954                           "%.800s", buf);
1955         else
1956                 sm_dprintf("%s\n", buf);
1957 }
1958 
1959 /*
1960 **  SHORTEN_HOSTNAME -- strip local domain information off of hostname.
1961 **
1962 **      Parameters:
1963 **              host -- the host to shorten (stripped in place).
1964 **
1965 **      Returns:
1966 **              place where string was truncated, NULL if not truncated.
1967 */
1968 
1969 char *
1970 shorten_hostname(host)
1971         char host[];
1972 {
1973         register char *p;
1974         char *mydom;
1975         int i;
1976         bool canon = false;
1977 
1978         /* strip off final dot */
1979         i = strlen(host);
1980         p = &host[(i == 0) ? 0 : i - 1];
1981         if (*p == '.')
1982         {
1983                 *p = '\0';
1984                 canon = true;
1985         }
1986 
1987         /* see if there is any domain at all -- if not, we are done */
1988         p = strchr(host, '.');
1989         if (p == NULL)
1990                 return NULL;
1991 
1992         /* yes, we have a domain -- see if it looks like us */
1993         mydom = macvalue('m', CurEnv);
1994         if (mydom == NULL)
1995                 mydom = "";
1996         i = strlen(++p);
1997         if ((canon ? sm_strcasecmp(p, mydom)
1998                    : sm_strncasecmp(p, mydom, i)) == 0 &&
1999                         (mydom[i] == '.' || mydom[i] == '\0'))
2000         {
2001                 *--p = '\0';
2002                 return p;
2003         }
2004         return NULL;
2005 }
2006 
2007 /*
2008 **  PROG_OPEN -- open a program for reading
2009 **
2010 **      Parameters:
2011 **              argv -- the argument list.
2012 **              pfd -- pointer to a place to store the file descriptor.
2013 **              e -- the current envelope.
2014 **
2015 **      Returns:
2016 **              pid of the process -- -1 if it failed.
2017 */
2018 
2019 pid_t
2020 prog_open(argv, pfd, e)
2021         char **argv;
2022         int *pfd;
2023         ENVELOPE *e;
2024 {
2025         pid_t pid;
2026         int save_errno;
2027         int sff;
2028         int ret;
2029         int fdv[2];
2030         char *p, *q;
2031         char buf[MAXPATHLEN];
2032         extern int DtableSize;
2033 
2034         if (pipe(fdv) < 0)
2035         {
2036                 syserr("%s: cannot create pipe for stdout", argv[0]);
2037                 return -1;
2038         }
2039         pid = fork();
2040         if (pid < 0)
2041         {
2042                 syserr("%s: cannot fork", argv[0]);
2043                 (void) close(fdv[0]);
2044                 (void) close(fdv[1]);
2045                 return -1;
2046         }
2047         if (pid > 0)
2048         {
2049                 /* parent */
2050                 (void) close(fdv[1]);
2051                 *pfd = fdv[0];
2052                 return pid;
2053         }
2054 
2055         /* Reset global flags */
2056         RestartRequest = NULL;
2057         RestartWorkGroup = false;
2058         ShutdownRequest = NULL;
2059         PendingSignal = 0;
2060         CurrentPid = getpid();
2061 
2062         /*
2063         **  Initialize exception stack and default exception
2064         **  handler for child process.
2065         */
2066 
2067         sm_exc_newthread(fatal_error);
2068 
2069         /* child -- close stdin */
2070         (void) close(0);
2071 
2072         /* stdout goes back to parent */
2073         (void) close(fdv[0]);
2074         if (dup2(fdv[1], 1) < 0)
2075         {
2076                 syserr("%s: cannot dup2 for stdout", argv[0]);
2077                 _exit(EX_OSERR);
2078         }
2079         (void) close(fdv[1]);
2080 
2081         /* stderr goes to transcript if available */
2082         if (e->e_xfp != NULL)
2083         {
2084                 int xfd;
2085 
2086                 xfd = sm_io_getinfo(e->e_xfp, SM_IO_WHAT_FD, NULL);
2087                 if (xfd >= 0 && dup2(xfd, 2) < 0)
2088                 {
2089                         syserr("%s: cannot dup2 for stderr", argv[0]);
2090                         _exit(EX_OSERR);
2091                 }
2092         }
2093 
2094         /* this process has no right to the queue file */
2095         if (e->e_lockfp != NULL)
2096         {
2097                 int fd;
2098 
2099                 fd = sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL);
2100                 if (fd >= 0)
2101                         (void) close(fd);
2102                 else
2103                         syserr("%s: lockfp does not have a fd", argv[0]);
2104         }
2105 
2106         /* chroot to the program mailer directory, if defined */
2107         if (ProgMailer != NULL && ProgMailer->m_rootdir != NULL)
2108         {
2109                 expand(ProgMailer->m_rootdir, buf, sizeof(buf), e);
2110                 if (chroot(buf) < 0)
2111                 {
2112                         syserr("prog_open: cannot chroot(%s)", buf);
2113                         exit(EX_TEMPFAIL);
2114                 }
2115                 if (chdir("/") < 0)
2116                 {
2117                         syserr("prog_open: cannot chdir(/)");
2118                         exit(EX_TEMPFAIL);
2119                 }
2120         }
2121 
2122         /* run as default user */
2123         endpwent();
2124         sm_mbdb_terminate();
2125 #if _FFR_MEMSTAT
2126         (void) sm_memstat_close();
2127 #endif /* _FFR_MEMSTAT */
2128         if (setgid(DefGid) < 0 && geteuid() == 0)
2129         {
2130                 syserr("prog_open: setgid(%ld) failed", (long) DefGid);
2131                 exit(EX_TEMPFAIL);
2132         }
2133         if (setuid(DefUid) < 0 && geteuid() == 0)
2134         {
2135                 syserr("prog_open: setuid(%ld) failed", (long) DefUid);
2136                 exit(EX_TEMPFAIL);
2137         }
2138 
2139         /* run in some directory */
2140         if (ProgMailer != NULL)
2141                 p = ProgMailer->m_execdir;
2142         else
2143                 p = NULL;
2144         for (; p != NULL; p = q)
2145         {
2146                 q = strchr(p, ':');
2147                 if (q != NULL)
2148                         *q = '\0';
2149                 expand(p, buf, sizeof(buf), e);
2150                 if (q != NULL)
2151                         *q++ = ':';
2152                 if (buf[0] != '\0' && chdir(buf) >= 0)
2153                         break;
2154         }
2155         if (p == NULL)
2156         {
2157                 /* backup directories */
2158                 if (chdir("/tmp") < 0)
2159                         (void) chdir("/");
2160         }
2161 
2162         /* Check safety of program to be run */
2163         sff = SFF_ROOTOK|SFF_EXECOK;
2164         if (!bitnset(DBS_RUNWRITABLEPROGRAM, DontBlameSendmail))
2165                 sff |= SFF_NOGWFILES|SFF_NOWWFILES;
2166         if (bitnset(DBS_RUNPROGRAMINUNSAFEDIRPATH, DontBlameSendmail))
2167                 sff |= SFF_NOPATHCHECK;
2168         else
2169                 sff |= SFF_SAFEDIRPATH;
2170         ret = safefile(argv[0], DefUid, DefGid, DefUser, sff, 0, NULL);
2171         if (ret != 0)
2172                 sm_syslog(LOG_INFO, e->e_id,
2173                           "Warning: prog_open: program %s unsafe: %s",
2174                           argv[0], sm_errstring(ret));
2175 
2176         /* arrange for all the files to be closed */
2177         sm_close_on_exec(STDERR_FILENO + 1, DtableSize);
2178 
2179         /* now exec the process */
2180         (void) execve(argv[0], (ARGV_T) argv, (ARGV_T) UserEnviron);
2181 
2182         /* woops!  failed */
2183         save_errno = errno;
2184         syserr("%s: cannot exec", argv[0]);
2185         if (transienterror(save_errno))
2186                 _exit(EX_OSERR);
2187         _exit(EX_CONFIG);
2188         return -1;      /* avoid compiler warning on IRIX */
2189 }
2190 
2191 /*
2192 **  GET_COLUMN -- look up a Column in a line buffer
2193 **
2194 **      Parameters:
2195 **              line -- the raw text line to search.
2196 **              col -- the column number to fetch.
2197 **              delim -- the delimiter between columns.  If null,
2198 **                      use white space.
2199 **              buf -- the output buffer.
2200 **              buflen -- the length of buf.
2201 **
2202 **      Returns:
2203 **              buf if successful.
2204 **              NULL otherwise.
2205 */
2206 
2207 char *
2208 get_column(line, col, delim, buf, buflen)
2209         char line[];
2210         int col;
2211         int delim;
2212         char buf[];
2213         int buflen;
2214 {
2215         char *p;
2216         char *begin, *end;
2217         int i;
2218         char delimbuf[4];
2219 
2220         if ((char) delim == '\0')
2221                 (void) sm_strlcpy(delimbuf, "\n\t ", sizeof(delimbuf));
2222         else
2223         {
2224                 delimbuf[0] = (char) delim;
2225                 delimbuf[1] = '\0';
2226         }
2227 
2228         p = line;
2229         if (*p == '\0')
2230                 return NULL;                    /* line empty */
2231         if (*p == (char) delim && col == 0)
2232                 return NULL;                    /* first column empty */
2233 
2234         begin = line;
2235 
2236         if (col == 0 && (char) delim == '\0')
2237         {
2238                 while (*begin != '\0' && isascii(*begin) && isspace(*begin))
2239                         begin++;
2240         }
2241 
2242         for (i = 0; i < col; i++)
2243         {
2244                 if ((begin = strpbrk(begin, delimbuf)) == NULL)
2245                         return NULL;            /* no such column */
2246                 begin++;
2247                 if ((char) delim == '\0')
2248                 {
2249                         while (*begin != '\0' && isascii(*begin) && isspace(*begin))
2250                                 begin++;
2251                 }
2252         }
2253 
2254         end = strpbrk(begin, delimbuf);
2255         if (end == NULL)
2256                 i = strlen(begin);
2257         else
2258                 i = end - begin;
2259         if (i >= buflen)
2260                 i = buflen - 1;
2261         (void) sm_strlcpy(buf, begin, i + 1);
2262         return buf;
2263 }
2264 
2265 /*
2266 **  CLEANSTRCPY -- copy string keeping out bogus characters
2267 **
2268 **      Parameters:
2269 **              t -- "to" string.
2270 **              f -- "from" string.
2271 **              l -- length of space available in "to" string.
2272 **
2273 **      Returns:
2274 **              none.
2275 */
2276 
2277 void
2278 cleanstrcpy(t, f, l)
2279         register char *t;
2280         register char *f;
2281         int l;
2282 {
2283         /* check for newlines and log if necessary */
2284         (void) denlstring(f, true, true);
2285 
2286         if (l <= 0)
2287                 syserr("!cleanstrcpy: length == 0");
2288 
2289         l--;
2290         while (l > 0 && *f != '\0')
2291         {
2292                 if (isascii(*f) &&
2293                     (isalnum(*f) || strchr("!#$%&'*+-./^_`{|}~", *f) != NULL))
2294                 {
2295                         l--;
2296                         *t++ = *f;
2297                 }
2298                 f++;
2299         }
2300         *t = '\0';
2301 }
2302 
2303 /*
2304 **  DENLSTRING -- convert newlines in a string to spaces
2305 **
2306 **      Parameters:
2307 **              s -- the input string
2308 **              strict -- if set, don't permit continuation lines.
2309 **              logattacks -- if set, log attempted attacks.
2310 **
2311 **      Returns:
2312 **              A pointer to a version of the string with newlines
2313 **              mapped to spaces.  This should be copied.
2314 */
2315 
2316 char *
2317 denlstring(s, strict, logattacks)
2318         char *s;
2319         bool strict;
2320         bool logattacks;
2321 {
2322         register char *p;
2323         int l;
2324         static char *bp = NULL;
2325         static int bl = 0;
2326 
2327         p = s;
2328         while ((p = strchr(p, '\n')) != NULL)
2329                 if (strict || (*++p != ' ' && *p != '\t'))
2330                         break;
2331         if (p == NULL)
2332                 return s;
2333 
2334         l = strlen(s) + 1;
2335         if (bl < l)
2336         {
2337                 /* allocate more space */
2338                 char *nbp = sm_pmalloc_x(l);
2339 
2340                 if (bp != NULL)
2341                         sm_free(bp);
2342                 bp = nbp;
2343                 bl = l;
2344         }
2345         (void) sm_strlcpy(bp, s, l);
2346         for (p = bp; (p = strchr(p, '\n')) != NULL; )
2347                 *p++ = ' ';
2348 
2349         if (logattacks)
2350         {
2351                 sm_syslog(LOG_NOTICE, CurEnv ? CurEnv->e_id : NULL,
2352                           "POSSIBLE ATTACK from %.100s: newline in string \"%s\"",
2353                           RealHostName == NULL ? "[UNKNOWN]" : RealHostName,
2354                           shortenstring(bp, MAXSHORTSTR));
2355         }
2356 
2357         return bp;
2358 }
2359 
2360 /*
2361 **  STRREPLNONPRT -- replace "unprintable" characters in a string with subst
2362 **
2363 **      Parameters:
2364 **              s -- string to manipulate (in place)
2365 **              subst -- character to use as replacement
2366 **
2367 **      Returns:
2368 **              true iff string did not contain "unprintable" characters
2369 */
2370 
2371 bool
2372 strreplnonprt(s, c)
2373         char *s;
2374         int c;
2375 {
2376         bool ok;
2377 
2378         ok = true;
2379         if (s == NULL)
2380                 return ok;
2381         while (*s != '\0')
2382         {
2383                 if (!(isascii(*s) && isprint(*s)))
2384                 {
2385                         *s = c;
2386                         ok = false;
2387                 }
2388                 ++s;
2389         }
2390         return ok;
2391 }
2392 
2393 /*
2394 **  PATH_IS_DIR -- check to see if file exists and is a directory.
2395 **
2396 **      There are some additional checks for security violations in
2397 **      here.  This routine is intended to be used for the host status
2398 **      support.
2399 **
2400 **      Parameters:
2401 **              pathname -- pathname to check for directory-ness.
2402 **              createflag -- if set, create directory if needed.
2403 **
2404 **      Returns:
2405 **              true -- if the indicated pathname is a directory
2406 **              false -- otherwise
2407 */
2408 
2409 bool
2410 path_is_dir(pathname, createflag)
2411         char *pathname;
2412         bool createflag;
2413 {
2414         struct stat statbuf;
2415 
2416 #if HASLSTAT
2417         if (lstat(pathname, &statbuf) < 0)
2418 #else /* HASLSTAT */
2419         if (stat(pathname, &statbuf) < 0)
2420 #endif /* HASLSTAT */
2421         {
2422                 if (errno != ENOENT || !createflag)
2423                         return false;
2424                 if (mkdir(pathname, 0755) < 0)
2425                         return false;
2426                 return true;
2427         }
2428         if (!S_ISDIR(statbuf.st_mode))
2429         {
2430                 errno = ENOTDIR;
2431                 return false;
2432         }
2433 
2434         /* security: don't allow writable directories */
2435         if (bitset(S_IWGRP|S_IWOTH, statbuf.st_mode))
2436         {
2437                 errno = EACCES;
2438                 return false;
2439         }
2440         return true;
2441 }
2442 
2443 /*
2444 **  PROC_LIST_ADD -- add process id to list of our children
2445 **
2446 **      Parameters:
2447 **              pid -- pid to add to list.
2448 **              task -- task of pid.
2449 **              type -- type of process.
2450 **              count -- number of processes.
2451 **              other -- other information for this type.
2452 **
2453 **      Returns:
2454 **              none
2455 **
2456 **      Side Effects:
2457 **              May increase CurChildren. May grow ProcList.
2458 */
2459 
2460 typedef struct procs    PROCS_T;
2461 
2462 struct procs
2463 {
2464         pid_t           proc_pid;
2465         char            *proc_task;
2466         int             proc_type;
2467         int             proc_count;
2468         int             proc_other;
2469         SOCKADDR        proc_hostaddr;
2470 };
2471 
2472 static PROCS_T  *volatile ProcListVec = NULL;
2473 static int      ProcListSize = 0;
2474 
2475 void
2476 proc_list_add(pid, task, type, count, other, hostaddr)
2477         pid_t pid;
2478         char *task;
2479         int type;
2480         int count;
2481         int other;
2482         SOCKADDR *hostaddr;
2483 {
2484         int i;
2485 
2486         for (i = 0; i < ProcListSize; i++)
2487         {
2488                 if (ProcListVec[i].proc_pid == NO_PID)
2489                         break;
2490         }
2491         if (i >= ProcListSize)
2492         {
2493                 /* probe the existing vector to avoid growing infinitely */
2494                 proc_list_probe();
2495 
2496                 /* now scan again */
2497                 for (i = 0; i < ProcListSize; i++)
2498                 {
2499                         if (ProcListVec[i].proc_pid == NO_PID)
2500                                 break;
2501                 }
2502         }
2503         if (i >= ProcListSize)
2504         {
2505                 /* grow process list */
2506                 int chldwasblocked;
2507                 PROCS_T *npv;
2508 
2509                 SM_ASSERT(ProcListSize < INT_MAX - PROC_LIST_SEG);
2510                 npv = (PROCS_T *) sm_pmalloc_x((sizeof(*npv)) *
2511                                                (ProcListSize + PROC_LIST_SEG));
2512 
2513                 /* Block SIGCHLD so reapchild() doesn't mess with us */
2514                 chldwasblocked = sm_blocksignal(SIGCHLD);
2515                 if (ProcListSize > 0)
2516                 {
2517                         memmove(npv, ProcListVec,
2518                                 ProcListSize * sizeof(PROCS_T));
2519                         sm_free(ProcListVec);
2520                 }
2521 
2522                 /* XXX just use memset() to initialize this part? */
2523                 for (i = ProcListSize; i < ProcListSize + PROC_LIST_SEG; i++)
2524                 {
2525                         npv[i].proc_pid = NO_PID;
2526                         npv[i].proc_task = NULL;
2527                         npv[i].proc_type = PROC_NONE;
2528                 }
2529                 i = ProcListSize;
2530                 ProcListSize += PROC_LIST_SEG;
2531                 ProcListVec = npv;
2532                 if (chldwasblocked == 0)
2533                         (void) sm_releasesignal(SIGCHLD);
2534         }
2535         ProcListVec[i].proc_pid = pid;
2536         PSTRSET(ProcListVec[i].proc_task, task);
2537         ProcListVec[i].proc_type = type;
2538         ProcListVec[i].proc_count = count;
2539         ProcListVec[i].proc_other = other;
2540         if (hostaddr != NULL)
2541                 ProcListVec[i].proc_hostaddr = *hostaddr;
2542         else
2543                 memset(&ProcListVec[i].proc_hostaddr, 0,
2544                         sizeof(ProcListVec[i].proc_hostaddr));
2545 
2546         /* if process adding itself, it's not a child */
2547         if (pid != CurrentPid)
2548         {
2549                 SM_ASSERT(CurChildren < INT_MAX);
2550                 CurChildren++;
2551         }
2552 }
2553 
2554 /*
2555 **  PROC_LIST_SET -- set pid task in process list
2556 **
2557 **      Parameters:
2558 **              pid -- pid to set
2559 **              task -- task of pid
2560 **
2561 **      Returns:
2562 **              none.
2563 */
2564 
2565 void
2566 proc_list_set(pid, task)
2567         pid_t pid;
2568         char *task;
2569 {
2570         int i;
2571 
2572         for (i = 0; i < ProcListSize; i++)
2573         {
2574                 if (ProcListVec[i].proc_pid == pid)
2575                 {
2576                         PSTRSET(ProcListVec[i].proc_task, task);
2577                         break;
2578                 }
2579         }
2580 }
2581 
2582 /*
2583 **  PROC_LIST_DROP -- drop pid from process list
2584 **
2585 **      Parameters:
2586 **              pid -- pid to drop
2587 **              st -- process status
2588 **              other -- storage for proc_other (return).
2589 **
2590 **      Returns:
2591 **              none.
2592 **
2593 **      Side Effects:
2594 **              May decrease CurChildren, CurRunners, or
2595 **              set RestartRequest or ShutdownRequest.
2596 **
2597 **      NOTE:   THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
2598 **              ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
2599 **              DOING.
2600 */
2601 
2602 void
2603 proc_list_drop(pid, st, other)
2604         pid_t pid;
2605         int st;
2606         int *other;
2607 {
2608         int i;
2609         int type = PROC_NONE;
2610 
2611         for (i = 0; i < ProcListSize; i++)
2612         {
2613                 if (ProcListVec[i].proc_pid == pid)
2614                 {
2615                         ProcListVec[i].proc_pid = NO_PID;
2616                         type = ProcListVec[i].proc_type;
2617                         if (other != NULL)
2618                                 *other = ProcListVec[i].proc_other;
2619                         if (CurChildren > 0)
2620                                 CurChildren--;
2621                         break;
2622                 }
2623         }
2624 
2625 
2626         if (type == PROC_CONTROL && WIFEXITED(st))
2627         {
2628                 /* if so, see if we need to restart or shutdown */
2629                 if (WEXITSTATUS(st) == EX_RESTART)
2630                         RestartRequest = "control socket";
2631                 else if (WEXITSTATUS(st) == EX_SHUTDOWN)
2632                         ShutdownRequest = "control socket";
2633         }
2634         else if (type == PROC_QUEUE_CHILD && !WIFSTOPPED(st) &&
2635                  ProcListVec[i].proc_other > -1)
2636         {
2637                 /* restart this persistent runner */
2638                 mark_work_group_restart(ProcListVec[i].proc_other, st);
2639         }
2640         else if (type == PROC_QUEUE)
2641                 CurRunners -= ProcListVec[i].proc_count;
2642 }
2643 
2644 /*
2645 **  PROC_LIST_CLEAR -- clear the process list
2646 **
2647 **      Parameters:
2648 **              none.
2649 **
2650 **      Returns:
2651 **              none.
2652 **
2653 **      Side Effects:
2654 **              Sets CurChildren to zero.
2655 */
2656 
2657 void
2658 proc_list_clear()
2659 {
2660         int i;
2661 
2662         /* start from 1 since 0 is the daemon itself */
2663         for (i = 1; i < ProcListSize; i++)
2664                 ProcListVec[i].proc_pid = NO_PID;
2665         CurChildren = 0;
2666 }
2667 
2668 /*
2669 **  PROC_LIST_PROBE -- probe processes in the list to see if they still exist
2670 **
2671 **      Parameters:
2672 **              none
2673 **
2674 **      Returns:
2675 **              none
2676 **
2677 **      Side Effects:
2678 **              May decrease CurChildren.
2679 */
2680 
2681 void
2682 proc_list_probe()
2683 {
2684         int i, children;
2685         int chldwasblocked;
2686         pid_t pid;
2687 
2688         children = 0;
2689         chldwasblocked = sm_blocksignal(SIGCHLD);
2690 
2691         /* start from 1 since 0 is the daemon itself */
2692         for (i = 1; i < ProcListSize; i++)
2693         {
2694                 pid = ProcListVec[i].proc_pid;
2695                 if (pid == NO_PID || pid == CurrentPid)
2696                         continue;
2697                 if (kill(pid, 0) < 0)
2698                 {
2699                         if (LogLevel > 3)
2700                                 sm_syslog(LOG_DEBUG, CurEnv->e_id,
2701                                           "proc_list_probe: lost pid %d",
2702                                           (int) ProcListVec[i].proc_pid);
2703                         ProcListVec[i].proc_pid = NO_PID;
2704                         SM_FREE_CLR(ProcListVec[i].proc_task);
2705                         CurChildren--;
2706                 }
2707                 else
2708                 {
2709                         ++children;
2710                 }
2711         }
2712         if (CurChildren < 0)
2713                 CurChildren = 0;
2714         if (chldwasblocked == 0)
2715                 (void) sm_releasesignal(SIGCHLD);
2716         if (LogLevel > 10 && children != CurChildren && CurrentPid == DaemonPid)
2717         {
2718                 sm_syslog(LOG_ERR, NOQID,
2719                           "proc_list_probe: found %d children, expected %d",
2720                           children, CurChildren);
2721         }
2722 }
2723 
2724 /*
2725 **  PROC_LIST_DISPLAY -- display the process list
2726 **
2727 **      Parameters:
2728 **              out -- output file pointer
2729 **              prefix -- string to output in front of each line.
2730 **
2731 **      Returns:
2732 **              none.
2733 */
2734 
2735 void
2736 proc_list_display(out, prefix)
2737         SM_FILE_T *out;
2738         char *prefix;
2739 {
2740         int i;
2741 
2742         for (i = 0; i < ProcListSize; i++)
2743         {
2744                 if (ProcListVec[i].proc_pid == NO_PID)
2745                         continue;
2746 
2747                 (void) sm_io_fprintf(out, SM_TIME_DEFAULT, "%s%d %s%s\n",
2748                                      prefix,
2749                                      (int) ProcListVec[i].proc_pid,
2750                                      ProcListVec[i].proc_task != NULL ?
2751                                      ProcListVec[i].proc_task : "(unknown)",
2752                                      (OpMode == MD_SMTP ||
2753                                       OpMode == MD_DAEMON ||
2754                                       OpMode == MD_ARPAFTP) ? "\r" : "");
2755         }
2756 }
2757 
2758 /*
2759 **  PROC_LIST_SIGNAL -- send a signal to a type of process in the list
2760 **
2761 **      Parameters:
2762 **              type -- type of process to signal
2763 **              signal -- the type of signal to send
2764 **
2765 **      Results:
2766 **              none.
2767 **
2768 **      NOTE:   THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
2769 **              ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
2770 **              DOING.
2771 */
2772 
2773 void
2774 proc_list_signal(type, signal)
2775         int type;
2776         int signal;
2777 {
2778         int chldwasblocked;
2779         int alrmwasblocked;
2780         int i;
2781         pid_t mypid = getpid();
2782 
2783         /* block these signals so that we may signal cleanly */
2784         chldwasblocked = sm_blocksignal(SIGCHLD);
2785         alrmwasblocked = sm_blocksignal(SIGALRM);
2786 
2787         /* Find all processes of type and send signal */
2788         for (i = 0; i < ProcListSize; i++)
2789         {
2790                 if (ProcListVec[i].proc_pid == NO_PID ||
2791                     ProcListVec[i].proc_pid == mypid)
2792                         continue;
2793                 if (ProcListVec[i].proc_type != type)
2794                         continue;
2795                 (void) kill(ProcListVec[i].proc_pid, signal);
2796         }
2797 
2798         /* restore the signals */
2799         if (alrmwasblocked == 0)
2800                 (void) sm_releasesignal(SIGALRM);
2801         if (chldwasblocked == 0)
2802                 (void) sm_releasesignal(SIGCHLD);
2803 }
2804 
2805 /*
2806 **  COUNT_OPEN_CONNECTIONS
2807 **
2808 **      Parameters:
2809 **              hostaddr - ClientAddress
2810 **
2811 **      Returns:
2812 **              the number of open connections for this client
2813 **
2814 */
2815 
2816 int
2817 count_open_connections(hostaddr)
2818         SOCKADDR *hostaddr;
2819 {
2820         int i, n;
2821 
2822         if (hostaddr == NULL)
2823                 return 0;
2824 
2825         /*
2826         **  This code gets called before proc_list_add() gets called,
2827         **  so we (the daemon child for this connection) have not yet
2828         **  counted ourselves.  Hence initialize the counter to 1
2829         **  instead of 0 to compensate.
2830         */
2831 
2832         n = 1;
2833         for (i = 0; i < ProcListSize; i++)
2834         {
2835                 if (ProcListVec[i].proc_pid == NO_PID)
2836                         continue;
2837                 if (hostaddr->sa.sa_family !=
2838                     ProcListVec[i].proc_hostaddr.sa.sa_family)
2839                         continue;
2840 #if NETINET
2841                 if (hostaddr->sa.sa_family == AF_INET &&
2842                     (hostaddr->sin.sin_addr.s_addr ==
2843                      ProcListVec[i].proc_hostaddr.sin.sin_addr.s_addr))
2844                         n++;
2845 #endif /* NETINET */
2846 #if NETINET6
2847                 if (hostaddr->sa.sa_family == AF_INET6 &&
2848                     IN6_ARE_ADDR_EQUAL(&(hostaddr->sin6.sin6_addr),
2849                                        &(ProcListVec[i].proc_hostaddr.sin6.sin6_addr)))
2850                         n++;
2851 #endif /* NETINET6 */
2852         }
2853         return n;
2854 }