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