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