Merge from vendor branch LIBARCHIVE:
[dragonfly.git] / contrib / sendmail-8.14 / sendmail / util.c
1 /*
2  * Copyright (c) 1998-2006 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.410 2006/12/18 18:36:44 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 (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 (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                 {
1159                         if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '.') ==
1160                             SM_IO_EOF)
1161                         {
1162                                 dead = true;
1163                                 break;
1164                         }
1165                         if (TrafficLogFile != NULL)
1166                                 (void) sm_io_putc(TrafficLogFile,
1167                                                   SM_TIME_DEFAULT, '.');
1168                 }
1169                 else if (l[0] == 'F' && slop == 0 &&
1170                          bitset(PXLF_MAPFROM, pxflags) &&
1171                          strncmp(l, "From ", 5) == 0 &&
1172                          bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
1173                 {
1174                         if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '>') ==
1175                             SM_IO_EOF)
1176                         {
1177                                 dead = true;
1178                                 break;
1179                         }
1180                         if (TrafficLogFile != NULL)
1181                                 (void) sm_io_putc(TrafficLogFile,
1182                                                   SM_TIME_DEFAULT, '>');
1183                 }
1184                 PUTX(p);
1185                 if (dead)
1186                         break;
1187
1188                 if (TrafficLogFile != NULL)
1189                         (void) sm_io_putc(TrafficLogFile, SM_TIME_DEFAULT,
1190                                           '\n');
1191                 if ((!bitset(PXLF_NOADDEOL, pxflags) || !noeol) &&
1192                     sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
1193                                 mci->mci_mailer->m_eol) == SM_IO_EOF)
1194                 {
1195                         dead = true;
1196                         break;
1197                 }
1198                 if (l < end && *l == '\n')
1199                 {
1200                         if (*++l != ' ' && *l != '\t' && *l != '\0' &&
1201                             bitset(PXLF_HEADER, pxflags))
1202                         {
1203                                 if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
1204                                                ' ') == SM_IO_EOF)
1205                                 {
1206                                         dead = true;
1207                                         break;
1208                                 }
1209
1210                                 if (TrafficLogFile != NULL)
1211                                         (void) sm_io_putc(TrafficLogFile,
1212                                                           SM_TIME_DEFAULT, ' ');
1213                         }
1214                 }
1215
1216         } while (l < end);
1217         return !dead;
1218 }
1219
1220 /*
1221 **  XUNLINK -- unlink a file, doing logging as appropriate.
1222 **
1223 **      Parameters:
1224 **              f -- name of file to unlink.
1225 **
1226 **      Returns:
1227 **              return value of unlink()
1228 **
1229 **      Side Effects:
1230 **              f is unlinked.
1231 */
1232
1233 int
1234 xunlink(f)
1235         char *f;
1236 {
1237         register int i;
1238         int save_errno;
1239
1240         if (LogLevel > 98)
1241                 sm_syslog(LOG_DEBUG, CurEnv->e_id, "unlink %s", f);
1242
1243         i = unlink(f);
1244         save_errno = errno;
1245         if (i < 0 && LogLevel > 97)
1246                 sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s: unlink-fail %d",
1247                           f, errno);
1248         if (i >= 0)
1249                 SYNC_DIR(f, false);
1250         errno = save_errno;
1251         return i;
1252 }
1253
1254 /*
1255 **  SFGETS -- "safe" fgets -- times out and ignores random interrupts.
1256 **
1257 **      Parameters:
1258 **              buf -- place to put the input line.
1259 **              siz -- size of buf.
1260 **              fp -- file to read from.
1261 **              timeout -- the timeout before error occurs.
1262 **              during -- what we are trying to read (for error messages).
1263 **
1264 **      Returns:
1265 **              NULL on error (including timeout).  This may also leave
1266 **                      buf containing a null string.
1267 **              buf otherwise.
1268 */
1269
1270
1271 char *
1272 sfgets(buf, siz, fp, timeout, during)
1273         char *buf;
1274         int siz;
1275         SM_FILE_T *fp;
1276         time_t timeout;
1277         char *during;
1278 {
1279         register char *p;
1280         int save_errno;
1281         int io_timeout;
1282
1283         SM_REQUIRE(siz > 0);
1284         SM_REQUIRE(buf != NULL);
1285
1286         if (fp == NULL)
1287         {
1288                 buf[0] = '\0';
1289                 errno = EBADF;
1290                 return NULL;
1291         }
1292
1293         /* try to read */
1294         p = NULL;
1295         errno = 0;
1296
1297         /* convert the timeout to sm_io notation */
1298         io_timeout = (timeout <= 0) ? SM_TIME_DEFAULT : timeout * 1000;
1299         while (!sm_io_eof(fp) && !sm_io_error(fp))
1300         {
1301                 errno = 0;
1302                 p = sm_io_fgets(fp, io_timeout, buf, siz);
1303                 if (p == NULL && errno == EAGAIN)
1304                 {
1305                         /* The sm_io_fgets() call timedout */
1306                         if (LogLevel > 1)
1307                                 sm_syslog(LOG_NOTICE, CurEnv->e_id,
1308                                           "timeout waiting for input from %.100s during %s",
1309                                           CURHOSTNAME,
1310                                           during);
1311                         buf[0] = '\0';
1312 #if XDEBUG
1313                         checkfd012(during);
1314 #endif /* XDEBUG */
1315                         if (TrafficLogFile != NULL)
1316                                 (void) sm_io_fprintf(TrafficLogFile,
1317                                                      SM_TIME_DEFAULT,
1318                                                      "%05d <<< [TIMEOUT]\n",
1319                                                      (int) CurrentPid);
1320                         errno = ETIMEDOUT;
1321                         return NULL;
1322                 }
1323                 if (p != NULL || errno != EINTR)
1324                         break;
1325                 (void) sm_io_clearerr(fp);
1326         }
1327         save_errno = errno;
1328
1329         /* clean up the books and exit */
1330         LineNumber++;
1331         if (p == NULL)
1332         {
1333                 buf[0] = '\0';
1334                 if (TrafficLogFile != NULL)
1335                         (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1336                                              "%05d <<< [EOF]\n",
1337                                              (int) CurrentPid);
1338                 errno = save_errno;
1339                 return NULL;
1340         }
1341         if (TrafficLogFile != NULL)
1342                 (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1343                                      "%05d <<< %s", (int) CurrentPid, buf);
1344         if (SevenBitInput)
1345         {
1346                 for (p = buf; *p != '\0'; p++)
1347                         *p &= ~0200;
1348         }
1349         else if (!HasEightBits)
1350         {
1351                 for (p = buf; *p != '\0'; p++)
1352                 {
1353                         if (bitset(0200, *p))
1354                         {
1355                                 HasEightBits = true;
1356                                 break;
1357                         }
1358                 }
1359         }
1360         return buf;
1361 }
1362
1363 /*
1364 **  FGETFOLDED -- like fgets, but knows about folded lines.
1365 **
1366 **      Parameters:
1367 **              buf -- place to put result.
1368 **              np -- pointer to bytes available; will be updated with
1369 **                      the actual buffer size (not number of bytes filled)
1370 **                      on return.
1371 **              f -- file to read from.
1372 **
1373 **      Returns:
1374 **              input line(s) on success, NULL on error or SM_IO_EOF.
1375 **              This will normally be buf -- unless the line is too
1376 **                      long, when it will be sm_malloc_x()ed.
1377 **
1378 **      Side Effects:
1379 **              buf gets lines from f, with continuation lines (lines
1380 **              with leading white space) appended.  CRLF's are mapped
1381 **              into single newlines.  Any trailing NL is stripped.
1382 */
1383
1384 char *
1385 fgetfolded(buf, np, f)
1386         char *buf;
1387         int *np;
1388         SM_FILE_T *f;
1389 {
1390         register char *p = buf;
1391         char *bp = buf;
1392         register int i;
1393         int n;
1394
1395         SM_REQUIRE(np != NULL);
1396         n = *np;
1397         SM_REQUIRE(n > 0);
1398         SM_REQUIRE(buf != NULL);
1399         if (f == NULL)
1400         {
1401                 buf[0] = '\0';
1402                 errno = EBADF;
1403                 return NULL;
1404         }
1405
1406         n--;
1407         while ((i = sm_io_getc(f, SM_TIME_DEFAULT)) != SM_IO_EOF)
1408         {
1409                 if (i == '\r')
1410                 {
1411                         i = sm_io_getc(f, SM_TIME_DEFAULT);
1412                         if (i != '\n')
1413                         {
1414                                 if (i != SM_IO_EOF)
1415                                         (void) sm_io_ungetc(f, SM_TIME_DEFAULT,
1416                                                             i);
1417                                 i = '\r';
1418                         }
1419                 }
1420                 if (--n <= 0)
1421                 {
1422                         /* allocate new space */
1423                         char *nbp;
1424                         int nn;
1425
1426                         nn = (p - bp);
1427                         if (nn < MEMCHUNKSIZE)
1428                                 nn *= 2;
1429                         else
1430                                 nn += MEMCHUNKSIZE;
1431                         nbp = sm_malloc_x(nn);
1432                         memmove(nbp, bp, p - bp);
1433                         p = &nbp[p - bp];
1434                         if (bp != buf)
1435                                 sm_free(bp);
1436                         bp = nbp;
1437                         n = nn - (p - bp);
1438                         *np = nn;
1439                 }
1440                 *p++ = i;
1441                 if (i == '\n')
1442                 {
1443                         LineNumber++;
1444                         i = sm_io_getc(f, SM_TIME_DEFAULT);
1445                         if (i != SM_IO_EOF)
1446                                 (void) sm_io_ungetc(f, SM_TIME_DEFAULT, i);
1447                         if (i != ' ' && i != '\t')
1448                                 break;
1449                 }
1450         }
1451         if (p == bp)
1452                 return NULL;
1453         if (p[-1] == '\n')
1454                 p--;
1455         *p = '\0';
1456         return bp;
1457 }
1458
1459 /*
1460 **  CURTIME -- return current time.
1461 **
1462 **      Parameters:
1463 **              none.
1464 **
1465 **      Returns:
1466 **              the current time.
1467 */
1468
1469 time_t
1470 curtime()
1471 {
1472         auto time_t t;
1473
1474         (void) time(&t);
1475         return t;
1476 }
1477
1478 /*
1479 **  ATOBOOL -- convert a string representation to boolean.
1480 **
1481 **      Defaults to false
1482 **
1483 **      Parameters:
1484 **              s -- string to convert.  Takes "tTyY", empty, and NULL as true,
1485 **                      others as false.
1486 **
1487 **      Returns:
1488 **              A boolean representation of the string.
1489 */
1490
1491 bool
1492 atobool(s)
1493         register char *s;
1494 {
1495         if (s == NULL || *s == '\0' || strchr("tTyY", *s) != NULL)
1496                 return true;
1497         return false;
1498 }
1499
1500 /*
1501 **  ATOOCT -- convert a string representation to octal.
1502 **
1503 **      Parameters:
1504 **              s -- string to convert.
1505 **
1506 **      Returns:
1507 **              An integer representing the string interpreted as an
1508 **              octal number.
1509 */
1510
1511 int
1512 atooct(s)
1513         register char *s;
1514 {
1515         register int i = 0;
1516
1517         while (*s >= '0' && *s <= '7')
1518                 i = (i << 3) | (*s++ - '0');
1519         return i;
1520 }
1521
1522 /*
1523 **  BITINTERSECT -- tell if two bitmaps intersect
1524 **
1525 **      Parameters:
1526 **              a, b -- the bitmaps in question
1527 **
1528 **      Returns:
1529 **              true if they have a non-null intersection
1530 **              false otherwise
1531 */
1532
1533 bool
1534 bitintersect(a, b)
1535         BITMAP256 a;
1536         BITMAP256 b;
1537 {
1538         int i;
1539
1540         for (i = BITMAPBYTES / sizeof(int); --i >= 0; )
1541         {
1542                 if ((a[i] & b[i]) != 0)
1543                         return true;
1544         }
1545         return false;
1546 }
1547
1548 /*
1549 **  BITZEROP -- tell if a bitmap is all zero
1550 **
1551 **      Parameters:
1552 **              map -- the bit map to check
1553 **
1554 **      Returns:
1555 **              true if map is all zero.
1556 **              false if there are any bits set in map.
1557 */
1558
1559 bool
1560 bitzerop(map)
1561         BITMAP256 map;
1562 {
1563         int i;
1564
1565         for (i = BITMAPBYTES / sizeof(int); --i >= 0; )
1566         {
1567                 if (map[i] != 0)
1568                         return false;
1569         }
1570         return true;
1571 }
1572
1573 /*
1574 **  STRCONTAINEDIN -- tell if one string is contained in another
1575 **
1576 **      Parameters:
1577 **              icase -- ignore case?
1578 **              a -- possible substring.
1579 **              b -- possible superstring.
1580 **
1581 **      Returns:
1582 **              true if a is contained in b (case insensitive).
1583 **              false otherwise.
1584 */
1585
1586 bool
1587 strcontainedin(icase, a, b)
1588         bool icase;
1589         register char *a;
1590         register char *b;
1591 {
1592         int la;
1593         int lb;
1594         int c;
1595
1596         la = strlen(a);
1597         lb = strlen(b);
1598         c = *a;
1599         if (icase && isascii(c) && isupper(c))
1600                 c = tolower(c);
1601         for (; lb-- >= la; b++)
1602         {
1603                 if (icase)
1604                 {
1605                         if (*b != c &&
1606                             isascii(*b) && isupper(*b) && tolower(*b) != c)
1607                                 continue;
1608                         if (sm_strncasecmp(a, b, la) == 0)
1609                                 return true;
1610                 }
1611                 else
1612                 {
1613                         if (*b != c)
1614                                 continue;
1615                         if (strncmp(a, b, la) == 0)
1616                                 return true;
1617                 }
1618         }
1619         return false;
1620 }
1621
1622 /*
1623 **  CHECKFD012 -- check low numbered file descriptors
1624 **
1625 **      File descriptors 0, 1, and 2 should be open at all times.
1626 **      This routine verifies that, and fixes it if not true.
1627 **
1628 **      Parameters:
1629 **              where -- a tag printed if the assertion failed
1630 **
1631 **      Returns:
1632 **              none
1633 */
1634
1635 void
1636 checkfd012(where)
1637         char *where;
1638 {
1639 #if XDEBUG
1640         register int i;
1641
1642         for (i = 0; i < 3; i++)
1643                 fill_fd(i, where);
1644 #endif /* XDEBUG */
1645 }
1646
1647 /*
1648 **  CHECKFDOPEN -- make sure file descriptor is open -- for extended debugging
1649 **
1650 **      Parameters:
1651 **              fd -- file descriptor to check.
1652 **              where -- tag to print on failure.
1653 **
1654 **      Returns:
1655 **              none.
1656 */
1657
1658 void
1659 checkfdopen(fd, where)
1660         int fd;
1661         char *where;
1662 {
1663 #if XDEBUG
1664         struct stat st;
1665
1666         if (fstat(fd, &st) < 0 && errno == EBADF)
1667         {
1668                 syserr("checkfdopen(%d): %s not open as expected!", fd, where);
1669                 printopenfds(true);
1670         }
1671 #endif /* XDEBUG */
1672 }
1673
1674 /*
1675 **  CHECKFDS -- check for new or missing file descriptors
1676 **
1677 **      Parameters:
1678 **              where -- tag for printing.  If null, take a base line.
1679 **
1680 **      Returns:
1681 **              none
1682 **
1683 **      Side Effects:
1684 **              If where is set, shows changes since the last call.
1685 */
1686
1687 void
1688 checkfds(where)
1689         char *where;
1690 {
1691         int maxfd;
1692         register int fd;
1693         bool printhdr = true;
1694         int save_errno = errno;
1695         static BITMAP256 baseline;
1696         extern int DtableSize;
1697
1698         if (DtableSize > BITMAPBITS)
1699                 maxfd = BITMAPBITS;
1700         else
1701                 maxfd = DtableSize;
1702         if (where == NULL)
1703                 clrbitmap(baseline);
1704
1705         for (fd = 0; fd < maxfd; fd++)
1706         {
1707                 struct stat stbuf;
1708
1709                 if (fstat(fd, &stbuf) < 0 && errno != EOPNOTSUPP)
1710                 {
1711                         if (!bitnset(fd, baseline))
1712                                 continue;
1713                         clrbitn(fd, baseline);
1714                 }
1715                 else if (!bitnset(fd, baseline))
1716                         setbitn(fd, baseline);
1717                 else
1718                         continue;
1719
1720                 /* file state has changed */
1721                 if (where == NULL)
1722                         continue;
1723                 if (printhdr)
1724                 {
1725                         sm_syslog(LOG_DEBUG, CurEnv->e_id,
1726                                   "%s: changed fds:",
1727                                   where);
1728                         printhdr = false;
1729                 }
1730                 dumpfd(fd, true, true);
1731         }
1732         errno = save_errno;
1733 }
1734
1735 /*
1736 **  PRINTOPENFDS -- print the open file descriptors (for debugging)
1737 **
1738 **      Parameters:
1739 **              logit -- if set, send output to syslog; otherwise
1740 **                      print for debugging.
1741 **
1742 **      Returns:
1743 **              none.
1744 */
1745
1746 #if NETINET || NETINET6
1747 # include <arpa/inet.h>
1748 #endif /* NETINET || NETINET6 */
1749
1750 void
1751 printopenfds(logit)
1752         bool logit;
1753 {
1754         register int fd;
1755         extern int DtableSize;
1756
1757         for (fd = 0; fd < DtableSize; fd++)
1758                 dumpfd(fd, false, logit);
1759 }
1760
1761 /*
1762 **  DUMPFD -- dump a file descriptor
1763 **
1764 **      Parameters:
1765 **              fd -- the file descriptor to dump.
1766 **              printclosed -- if set, print a notification even if
1767 **                      it is closed; otherwise print nothing.
1768 **              logit -- if set, use sm_syslog instead of sm_dprintf()
1769 **
1770 **      Returns:
1771 **              none.
1772 */
1773
1774 void
1775 dumpfd(fd, printclosed, logit)
1776         int fd;
1777         bool printclosed;
1778         bool logit;
1779 {
1780         register char *p;
1781         char *hp;
1782 #ifdef S_IFSOCK
1783         SOCKADDR sa;
1784 #endif /* S_IFSOCK */
1785         auto SOCKADDR_LEN_T slen;
1786         int i;
1787 #if STAT64 > 0
1788         struct stat64 st;
1789 #else /* STAT64 > 0 */
1790         struct stat st;
1791 #endif /* STAT64 > 0 */
1792         char buf[200];
1793
1794         p = buf;
1795         (void) sm_snprintf(p, SPACELEFT(buf, p), "%3d: ", fd);
1796         p += strlen(p);
1797
1798         if (
1799 #if STAT64 > 0
1800             fstat64(fd, &st)
1801 #else /* STAT64 > 0 */
1802             fstat(fd, &st)
1803 #endif /* STAT64 > 0 */
1804             < 0)
1805         {
1806                 if (errno != EBADF)
1807                 {
1808                         (void) sm_snprintf(p, SPACELEFT(buf, p),
1809                                 "CANNOT STAT (%s)",
1810                                 sm_errstring(errno));
1811                         goto printit;
1812                 }
1813                 else if (printclosed)
1814                 {
1815                         (void) sm_snprintf(p, SPACELEFT(buf, p), "CLOSED");
1816                         goto printit;
1817                 }
1818                 return;
1819         }
1820
1821         i = fcntl(fd, F_GETFL, 0);
1822         if (i != -1)
1823         {
1824                 (void) sm_snprintf(p, SPACELEFT(buf, p), "fl=0x%x, ", i);
1825                 p += strlen(p);
1826         }
1827
1828         (void) sm_snprintf(p, SPACELEFT(buf, p), "mode=%o: ",
1829                         (int) st.st_mode);
1830         p += strlen(p);
1831         switch (st.st_mode & S_IFMT)
1832         {
1833 #ifdef S_IFSOCK
1834           case S_IFSOCK:
1835                 (void) sm_snprintf(p, SPACELEFT(buf, p), "SOCK ");
1836                 p += strlen(p);
1837                 memset(&sa, '\0', sizeof(sa));
1838                 slen = sizeof(sa);
1839                 if (getsockname(fd, &sa.sa, &slen) < 0)
1840                         (void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
1841                                  sm_errstring(errno));
1842                 else
1843                 {
1844                         hp = hostnamebyanyaddr(&sa);
1845                         if (hp == NULL)
1846                         {
1847                                 /* EMPTY */
1848                                 /* do nothing */
1849                         }
1850 # if NETINET
1851                         else if (sa.sa.sa_family == AF_INET)
1852                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1853                                         "%s/%d", hp, ntohs(sa.sin.sin_port));
1854 # endif /* NETINET */
1855 # if NETINET6
1856                         else if (sa.sa.sa_family == AF_INET6)
1857                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1858                                         "%s/%d", hp, ntohs(sa.sin6.sin6_port));
1859 # endif /* NETINET6 */
1860                         else
1861                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1862                                         "%s", hp);
1863                 }
1864                 p += strlen(p);
1865                 (void) sm_snprintf(p, SPACELEFT(buf, p), "->");
1866                 p += strlen(p);
1867                 slen = sizeof(sa);
1868                 if (getpeername(fd, &sa.sa, &slen) < 0)
1869                         (void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
1870                                         sm_errstring(errno));
1871                 else
1872                 {
1873                         hp = hostnamebyanyaddr(&sa);
1874                         if (hp == NULL)
1875                         {
1876                                 /* EMPTY */
1877                                 /* do nothing */
1878                         }
1879 # if NETINET
1880                         else if (sa.sa.sa_family == AF_INET)
1881                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1882                                         "%s/%d", hp, ntohs(sa.sin.sin_port));
1883 # endif /* NETINET */
1884 # if NETINET6
1885                         else if (sa.sa.sa_family == AF_INET6)
1886                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1887                                         "%s/%d", hp, ntohs(sa.sin6.sin6_port));
1888 # endif /* NETINET6 */
1889                         else
1890                                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1891                                         "%s", hp);
1892                 }
1893                 break;
1894 #endif /* S_IFSOCK */
1895
1896           case S_IFCHR:
1897                 (void) sm_snprintf(p, SPACELEFT(buf, p), "CHR: ");
1898                 p += strlen(p);
1899                 goto defprint;
1900
1901 #ifdef S_IFBLK
1902           case S_IFBLK:
1903                 (void) sm_snprintf(p, SPACELEFT(buf, p), "BLK: ");
1904                 p += strlen(p);
1905                 goto defprint;
1906 #endif /* S_IFBLK */
1907
1908 #if defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK)
1909           case S_IFIFO:
1910                 (void) sm_snprintf(p, SPACELEFT(buf, p), "FIFO: ");
1911                 p += strlen(p);
1912                 goto defprint;
1913 #endif /* defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK) */
1914
1915 #ifdef S_IFDIR
1916           case S_IFDIR:
1917                 (void) sm_snprintf(p, SPACELEFT(buf, p), "DIR: ");
1918                 p += strlen(p);
1919                 goto defprint;
1920 #endif /* S_IFDIR */
1921
1922 #ifdef S_IFLNK
1923           case S_IFLNK:
1924                 (void) sm_snprintf(p, SPACELEFT(buf, p), "LNK: ");
1925                 p += strlen(p);
1926                 goto defprint;
1927 #endif /* S_IFLNK */
1928
1929           default:
1930 defprint:
1931                 (void) sm_snprintf(p, SPACELEFT(buf, p),
1932                          "dev=%d/%d, ino=%llu, nlink=%d, u/gid=%d/%d, ",
1933                          major(st.st_dev), minor(st.st_dev),
1934                          (ULONGLONG_T) st.st_ino,
1935                          (int) st.st_nlink, (int) st.st_uid,
1936                          (int) st.st_gid);
1937                 p += strlen(p);
1938                 (void) sm_snprintf(p, SPACELEFT(buf, p), "size=%llu",
1939                          (ULONGLONG_T) st.st_size);
1940                 break;
1941         }
1942
1943 printit:
1944         if (logit)
1945                 sm_syslog(LOG_DEBUG, CurEnv ? CurEnv->e_id : NULL,
1946                           "%.800s", buf);
1947         else
1948                 sm_dprintf("%s\n", buf);
1949 }
1950
1951 /*
1952 **  SHORTEN_HOSTNAME -- strip local domain information off of hostname.
1953 **
1954 **      Parameters:
1955 **              host -- the host to shorten (stripped in place).
1956 **
1957 **      Returns:
1958 **              place where string was truncated, NULL if not truncated.
1959 */
1960
1961 char *
1962 shorten_hostname(host)
1963         char host[];
1964 {
1965         register char *p;
1966         char *mydom;
1967         int i;
1968         bool canon = false;
1969
1970         /* strip off final dot */
1971         i = strlen(host);
1972         p = &host[(i == 0) ? 0 : i - 1];
1973         if (*p == '.')
1974         {
1975                 *p = '\0';
1976                 canon = true;
1977         }
1978
1979         /* see if there is any domain at all -- if not, we are done */
1980         p = strchr(host, '.');
1981         if (p == NULL)
1982                 return NULL;
1983
1984         /* yes, we have a domain -- see if it looks like us */
1985         mydom = macvalue('m', CurEnv);
1986         if (mydom == NULL)
1987                 mydom = "";
1988         i = strlen(++p);
1989         if ((canon ? sm_strcasecmp(p, mydom)
1990                    : sm_strncasecmp(p, mydom, i)) == 0 &&
1991                         (mydom[i] == '.' || mydom[i] == '\0'))
1992         {
1993                 *--p = '\0';
1994                 return p;
1995         }
1996         return NULL;
1997 }
1998
1999 /*
2000 **  PROG_OPEN -- open a program for reading
2001 **
2002 **      Parameters:
2003 **              argv -- the argument list.
2004 **              pfd -- pointer to a place to store the file descriptor.
2005 **              e -- the current envelope.
2006 **
2007 **      Returns:
2008 **              pid of the process -- -1 if it failed.
2009 */
2010
2011 pid_t
2012 prog_open(argv, pfd, e)
2013         char **argv;
2014         int *pfd;
2015         ENVELOPE *e;
2016 {
2017         pid_t pid;
2018         int save_errno;
2019         int sff;
2020         int ret;
2021         int fdv[2];
2022         char *p, *q;
2023         char buf[MAXPATHLEN];
2024         extern int DtableSize;
2025
2026         if (pipe(fdv) < 0)
2027         {
2028                 syserr("%s: cannot create pipe for stdout", argv[0]);
2029                 return -1;
2030         }
2031         pid = fork();
2032         if (pid < 0)
2033         {
2034                 syserr("%s: cannot fork", argv[0]);
2035                 (void) close(fdv[0]);
2036                 (void) close(fdv[1]);
2037                 return -1;
2038         }
2039         if (pid > 0)
2040         {
2041                 /* parent */
2042                 (void) close(fdv[1]);
2043                 *pfd = fdv[0];
2044                 return pid;
2045         }
2046
2047         /* Reset global flags */
2048         RestartRequest = NULL;
2049         RestartWorkGroup = false;
2050         ShutdownRequest = NULL;
2051         PendingSignal = 0;
2052         CurrentPid = getpid();
2053
2054         /*
2055         **  Initialize exception stack and default exception
2056         **  handler for child process.
2057         */
2058
2059         sm_exc_newthread(fatal_error);
2060
2061         /* child -- close stdin */
2062         (void) close(0);
2063
2064         /* stdout goes back to parent */
2065         (void) close(fdv[0]);
2066         if (dup2(fdv[1], 1) < 0)
2067         {
2068                 syserr("%s: cannot dup2 for stdout", argv[0]);
2069                 _exit(EX_OSERR);
2070         }
2071         (void) close(fdv[1]);
2072
2073         /* stderr goes to transcript if available */
2074         if (e->e_xfp != NULL)
2075         {
2076                 int xfd;
2077
2078                 xfd = sm_io_getinfo(e->e_xfp, SM_IO_WHAT_FD, NULL);
2079                 if (xfd >= 0 && dup2(xfd, 2) < 0)
2080                 {
2081                         syserr("%s: cannot dup2 for stderr", argv[0]);
2082                         _exit(EX_OSERR);
2083                 }
2084         }
2085
2086         /* this process has no right to the queue file */
2087         if (e->e_lockfp != NULL)
2088         {
2089                 int fd;
2090
2091                 fd = sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL);
2092                 if (fd >= 0)
2093                         (void) close(fd);
2094                 else
2095                         syserr("%s: lockfp does not have a fd", argv[0]);
2096         }
2097
2098         /* chroot to the program mailer directory, if defined */
2099         if (ProgMailer != NULL && ProgMailer->m_rootdir != NULL)
2100         {
2101                 expand(ProgMailer->m_rootdir, buf, sizeof(buf), e);
2102                 if (chroot(buf) < 0)
2103                 {
2104                         syserr("prog_open: cannot chroot(%s)", buf);
2105                         exit(EX_TEMPFAIL);
2106                 }
2107                 if (chdir("/") < 0)
2108                 {
2109                         syserr("prog_open: cannot chdir(/)");
2110                         exit(EX_TEMPFAIL);
2111                 }
2112         }
2113
2114         /* run as default user */
2115         endpwent();
2116         sm_mbdb_terminate();
2117 #if _FFR_MEMSTAT
2118         (void) sm_memstat_close();
2119 #endif /* _FFR_MEMSTAT */
2120         if (setgid(DefGid) < 0 && geteuid() == 0)
2121         {
2122                 syserr("prog_open: setgid(%ld) failed", (long) DefGid);
2123                 exit(EX_TEMPFAIL);
2124         }
2125         if (setuid(DefUid) < 0 && geteuid() == 0)
2126         {
2127                 syserr("prog_open: setuid(%ld) failed", (long) DefUid);
2128                 exit(EX_TEMPFAIL);
2129         }
2130
2131         /* run in some directory */
2132         if (ProgMailer != NULL)
2133                 p = ProgMailer->m_execdir;
2134         else
2135                 p = NULL;
2136         for (; p != NULL; p = q)
2137         {
2138                 q = strchr(p, ':');
2139                 if (q != NULL)
2140                         *q = '\0';
2141                 expand(p, buf, sizeof(buf), e);
2142                 if (q != NULL)
2143                         *q++ = ':';
2144                 if (buf[0] != '\0' && chdir(buf) >= 0)
2145                         break;
2146         }
2147         if (p == NULL)
2148         {
2149                 /* backup directories */
2150                 if (chdir("/tmp") < 0)
2151                         (void) chdir("/");
2152         }
2153
2154         /* Check safety of program to be run */
2155         sff = SFF_ROOTOK|SFF_EXECOK;
2156         if (!bitnset(DBS_RUNWRITABLEPROGRAM, DontBlameSendmail))
2157                 sff |= SFF_NOGWFILES|SFF_NOWWFILES;
2158         if (bitnset(DBS_RUNPROGRAMINUNSAFEDIRPATH, DontBlameSendmail))
2159                 sff |= SFF_NOPATHCHECK;
2160         else
2161                 sff |= SFF_SAFEDIRPATH;
2162         ret = safefile(argv[0], DefUid, DefGid, DefUser, sff, 0, NULL);
2163         if (ret != 0)
2164                 sm_syslog(LOG_INFO, e->e_id,
2165                           "Warning: prog_open: program %s unsafe: %s",
2166                           argv[0], sm_errstring(ret));
2167
2168         /* arrange for all the files to be closed */
2169         sm_close_on_exec(STDERR_FILENO + 1, DtableSize);
2170
2171         /* now exec the process */
2172         (void) execve(argv[0], (ARGV_T) argv, (ARGV_T) UserEnviron);
2173
2174         /* woops!  failed */
2175         save_errno = errno;
2176         syserr("%s: cannot exec", argv[0]);
2177         if (transienterror(save_errno))
2178                 _exit(EX_OSERR);
2179         _exit(EX_CONFIG);
2180         return -1;      /* avoid compiler warning on IRIX */
2181 }
2182
2183 /*
2184 **  GET_COLUMN -- look up a Column in a line buffer
2185 **
2186 **      Parameters:
2187 **              line -- the raw text line to search.
2188 **              col -- the column number to fetch.
2189 **              delim -- the delimiter between columns.  If null,
2190 **                      use white space.
2191 **              buf -- the output buffer.
2192 **              buflen -- the length of buf.
2193 **
2194 **      Returns:
2195 **              buf if successful.
2196 **              NULL otherwise.
2197 */
2198
2199 char *
2200 get_column(line, col, delim, buf, buflen)
2201         char line[];
2202         int col;
2203         int delim;
2204         char buf[];
2205         int buflen;
2206 {
2207         char *p;
2208         char *begin, *end;
2209         int i;
2210         char delimbuf[4];
2211
2212         if ((char) delim == '\0')
2213                 (void) sm_strlcpy(delimbuf, "\n\t ", sizeof(delimbuf));
2214         else
2215         {
2216                 delimbuf[0] = (char) delim;
2217                 delimbuf[1] = '\0';
2218         }
2219
2220         p = line;
2221         if (*p == '\0')
2222                 return NULL;                    /* line empty */
2223         if (*p == (char) delim && col == 0)
2224                 return NULL;                    /* first column empty */
2225
2226         begin = line;
2227
2228         if (col == 0 && (char) delim == '\0')
2229         {
2230                 while (*begin != '\0' && isascii(*begin) && isspace(*begin))
2231                         begin++;
2232         }
2233
2234         for (i = 0; i < col; i++)
2235         {
2236                 if ((begin = strpbrk(begin, delimbuf)) == NULL)
2237                         return NULL;            /* no such column */
2238                 begin++;
2239                 if ((char) delim == '\0')
2240                 {
2241                         while (*begin != '\0' && isascii(*begin) && isspace(*begin))
2242                                 begin++;
2243                 }
2244         }
2245
2246         end = strpbrk(begin, delimbuf);
2247         if (end == NULL)
2248                 i = strlen(begin);
2249         else
2250                 i = end - begin;
2251         if (i >= buflen)
2252                 i = buflen - 1;
2253         (void) sm_strlcpy(buf, begin, i + 1);
2254         return buf;
2255 }
2256
2257 /*
2258 **  CLEANSTRCPY -- copy string keeping out bogus characters
2259 **
2260 **      Parameters:
2261 **              t -- "to" string.
2262 **              f -- "from" string.
2263 **              l -- length of space available in "to" string.
2264 **
2265 **      Returns:
2266 **              none.
2267 */
2268
2269 void
2270 cleanstrcpy(t, f, l)
2271         register char *t;
2272         register char *f;
2273         int l;
2274 {
2275         /* check for newlines and log if necessary */
2276         (void) denlstring(f, true, true);
2277
2278         if (l <= 0)
2279                 syserr("!cleanstrcpy: length == 0");
2280
2281         l--;
2282         while (l > 0 && *f != '\0')
2283         {
2284                 if (isascii(*f) &&
2285                     (isalnum(*f) || strchr("!#$%&'*+-./^_`{|}~", *f) != NULL))
2286                 {
2287                         l--;
2288                         *t++ = *f;
2289                 }
2290                 f++;
2291         }
2292         *t = '\0';
2293 }
2294
2295 /*
2296 **  DENLSTRING -- convert newlines in a string to spaces
2297 **
2298 **      Parameters:
2299 **              s -- the input string
2300 **              strict -- if set, don't permit continuation lines.
2301 **              logattacks -- if set, log attempted attacks.
2302 **
2303 **      Returns:
2304 **              A pointer to a version of the string with newlines
2305 **              mapped to spaces.  This should be copied.
2306 */
2307
2308 char *
2309 denlstring(s, strict, logattacks)
2310         char *s;
2311         bool strict;
2312         bool logattacks;
2313 {
2314         register char *p;
2315         int l;
2316         static char *bp = NULL;
2317         static int bl = 0;
2318
2319         p = s;
2320         while ((p = strchr(p, '\n')) != NULL)
2321                 if (strict || (*++p != ' ' && *p != '\t'))
2322                         break;
2323         if (p == NULL)
2324                 return s;
2325
2326         l = strlen(s) + 1;
2327         if (bl < l)
2328         {
2329                 /* allocate more space */
2330                 char *nbp = sm_pmalloc_x(l);
2331
2332                 if (bp != NULL)
2333                         sm_free(bp);
2334                 bp = nbp;
2335                 bl = l;
2336         }
2337         (void) sm_strlcpy(bp, s, l);
2338         for (p = bp; (p = strchr(p, '\n')) != NULL; )
2339                 *p++ = ' ';
2340
2341         if (logattacks)
2342         {
2343                 sm_syslog(LOG_NOTICE, CurEnv ? CurEnv->e_id : NULL,
2344                           "POSSIBLE ATTACK from %.100s: newline in string \"%s\"",
2345                           RealHostName == NULL ? "[UNKNOWN]" : RealHostName,
2346                           shortenstring(bp, MAXSHORTSTR));
2347         }
2348
2349         return bp;
2350 }
2351
2352 /*
2353 **  STRREPLNONPRT -- replace "unprintable" characters in a string with subst
2354 **
2355 **      Parameters:
2356 **              s -- string to manipulate (in place)
2357 **              subst -- character to use as replacement
2358 **
2359 **      Returns:
2360 **              true iff string did not contain "unprintable" characters
2361 */
2362
2363 bool
2364 strreplnonprt(s, c)
2365         char *s;
2366         int c;
2367 {
2368         bool ok;
2369
2370         ok = true;
2371         if (s == NULL)
2372                 return ok;
2373         while (*s != '\0')
2374         {
2375                 if (!(isascii(*s) && isprint(*s)))
2376                 {
2377                         *s = c;
2378                         ok = false;
2379                 }
2380                 ++s;
2381         }
2382         return ok;
2383 }
2384
2385 /*
2386 **  PATH_IS_DIR -- check to see if file exists and is a directory.
2387 **
2388 **      There are some additional checks for security violations in
2389 **      here.  This routine is intended to be used for the host status
2390 **      support.
2391 **
2392 **      Parameters:
2393 **              pathname -- pathname to check for directory-ness.
2394 **              createflag -- if set, create directory if needed.
2395 **
2396 **      Returns:
2397 **              true -- if the indicated pathname is a directory
2398 **              false -- otherwise
2399 */
2400
2401 bool
2402 path_is_dir(pathname, createflag)
2403         char *pathname;
2404         bool createflag;
2405 {
2406         struct stat statbuf;
2407
2408 #if HASLSTAT
2409         if (lstat(pathname, &statbuf) < 0)
2410 #else /* HASLSTAT */
2411         if (stat(pathname, &statbuf) < 0)
2412 #endif /* HASLSTAT */
2413         {
2414                 if (errno != ENOENT || !createflag)
2415                         return false;
2416                 if (mkdir(pathname, 0755) < 0)
2417                         return false;
2418                 return true;
2419         }
2420         if (!S_ISDIR(statbuf.st_mode))
2421         {
2422                 errno = ENOTDIR;
2423                 return false;
2424         }
2425
2426         /* security: don't allow writable directories */
2427         if (bitset(S_IWGRP|S_IWOTH, statbuf.st_mode))
2428         {
2429                 errno = EACCES;
2430                 return false;
2431         }
2432         return true;
2433 }
2434
2435 /*
2436 **  PROC_LIST_ADD -- add process id to list of our children
2437 **
2438 **      Parameters:
2439 **              pid -- pid to add to list.
2440 **              task -- task of pid.
2441 **              type -- type of process.
2442 **              count -- number of processes.
2443 **              other -- other information for this type.
2444 **
2445 **      Returns:
2446 **              none
2447 **
2448 **      Side Effects:
2449 **              May increase CurChildren. May grow ProcList.
2450 */
2451
2452 typedef struct procs    PROCS_T;
2453
2454 struct procs
2455 {
2456         pid_t           proc_pid;
2457         char            *proc_task;
2458         int             proc_type;
2459         int             proc_count;
2460         int             proc_other;
2461         SOCKADDR        proc_hostaddr;
2462 };
2463
2464 static PROCS_T  *volatile ProcListVec = NULL;
2465 static int      ProcListSize = 0;
2466
2467 void
2468 proc_list_add(pid, task, type, count, other, hostaddr)
2469         pid_t pid;
2470         char *task;
2471         int type;
2472         int count;
2473         int other;
2474         SOCKADDR *hostaddr;
2475 {
2476         int i;
2477
2478         for (i = 0; i < ProcListSize; i++)
2479         {
2480                 if (ProcListVec[i].proc_pid == NO_PID)
2481                         break;
2482         }
2483         if (i >= ProcListSize)
2484         {
2485                 /* probe the existing vector to avoid growing infinitely */
2486                 proc_list_probe();
2487
2488                 /* now scan again */
2489                 for (i = 0; i < ProcListSize; i++)
2490                 {
2491                         if (ProcListVec[i].proc_pid == NO_PID)
2492                                 break;
2493                 }
2494         }
2495         if (i >= ProcListSize)
2496         {
2497                 /* grow process list */
2498                 int chldwasblocked;
2499                 PROCS_T *npv;
2500
2501                 SM_ASSERT(ProcListSize < INT_MAX - PROC_LIST_SEG);
2502                 npv = (PROCS_T *) sm_pmalloc_x((sizeof(*npv)) *
2503                                                (ProcListSize + PROC_LIST_SEG));
2504
2505                 /* Block SIGCHLD so reapchild() doesn't mess with us */
2506                 chldwasblocked = sm_blocksignal(SIGCHLD);
2507                 if (ProcListSize > 0)
2508                 {
2509                         memmove(npv, ProcListVec,
2510                                 ProcListSize * sizeof(PROCS_T));
2511                         sm_free(ProcListVec);
2512                 }
2513
2514                 /* XXX just use memset() to initialize this part? */
2515                 for (i = ProcListSize; i < ProcListSize + PROC_LIST_SEG; i++)
2516                 {
2517                         npv[i].proc_pid = NO_PID;
2518                         npv[i].proc_task = NULL;
2519                         npv[i].proc_type = PROC_NONE;
2520                 }
2521                 i = ProcListSize;
2522                 ProcListSize += PROC_LIST_SEG;
2523                 ProcListVec = npv;
2524                 if (chldwasblocked == 0)
2525                         (void) sm_releasesignal(SIGCHLD);
2526         }
2527         ProcListVec[i].proc_pid = pid;
2528         PSTRSET(ProcListVec[i].proc_task, task);
2529         ProcListVec[i].proc_type = type;
2530         ProcListVec[i].proc_count = count;
2531         ProcListVec[i].proc_other = other;
2532         if (hostaddr != NULL)
2533                 ProcListVec[i].proc_hostaddr = *hostaddr;
2534         else
2535                 memset(&ProcListVec[i].proc_hostaddr, 0,
2536                         sizeof(ProcListVec[i].proc_hostaddr));
2537
2538         /* if process adding itself, it's not a child */
2539         if (pid != CurrentPid)
2540         {
2541                 SM_ASSERT(CurChildren < INT_MAX);
2542                 CurChildren++;
2543         }
2544 }
2545
2546 /*
2547 **  PROC_LIST_SET -- set pid task in process list
2548 **
2549 **      Parameters:
2550 **              pid -- pid to set
2551 **              task -- task of pid
2552 **
2553 **      Returns:
2554 **              none.
2555 */
2556
2557 void
2558 proc_list_set(pid, task)
2559         pid_t pid;
2560         char *task;
2561 {
2562         int i;
2563
2564         for (i = 0; i < ProcListSize; i++)
2565         {
2566                 if (ProcListVec[i].proc_pid == pid)
2567                 {
2568                         PSTRSET(ProcListVec[i].proc_task, task);
2569                         break;
2570                 }
2571         }
2572 }
2573
2574 /*
2575 **  PROC_LIST_DROP -- drop pid from process list
2576 **
2577 **      Parameters:
2578 **              pid -- pid to drop
2579 **              st -- process status
2580 **              other -- storage for proc_other (return).
2581 **
2582 **      Returns:
2583 **              none.
2584 **
2585 **      Side Effects:
2586 **              May decrease CurChildren, CurRunners, or
2587 **              set RestartRequest or ShutdownRequest.
2588 **
2589 **      NOTE:   THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
2590 **              ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
2591 **              DOING.
2592 */
2593
2594 void
2595 proc_list_drop(pid, st, other)
2596         pid_t pid;
2597         int st;
2598         int *other;
2599 {
2600         int i;
2601         int type = PROC_NONE;
2602
2603         for (i = 0; i < ProcListSize; i++)
2604         {
2605                 if (ProcListVec[i].proc_pid == pid)
2606                 {
2607                         ProcListVec[i].proc_pid = NO_PID;
2608                         type = ProcListVec[i].proc_type;
2609                         if (other != NULL)
2610                                 *other = ProcListVec[i].proc_other;
2611                         if (CurChildren > 0)
2612                                 CurChildren--;
2613                         break;
2614                 }
2615         }
2616
2617
2618         if (type == PROC_CONTROL && WIFEXITED(st))
2619         {
2620                 /* if so, see if we need to restart or shutdown */
2621                 if (WEXITSTATUS(st) == EX_RESTART)
2622                         RestartRequest = "control socket";
2623                 else if (WEXITSTATUS(st) == EX_SHUTDOWN)
2624                         ShutdownRequest = "control socket";
2625         }
2626         else if (type == PROC_QUEUE_CHILD && !WIFSTOPPED(st) &&
2627                  ProcListVec[i].proc_other > -1)
2628         {
2629                 /* restart this persistent runner */
2630                 mark_work_group_restart(ProcListVec[i].proc_other, st);
2631         }
2632         else if (type == PROC_QUEUE)
2633                 CurRunners -= ProcListVec[i].proc_count;
2634 }
2635
2636 /*
2637 **  PROC_LIST_CLEAR -- clear the process list
2638 **
2639 **      Parameters:
2640 **              none.
2641 **
2642 **      Returns:
2643 **              none.
2644 **
2645 **      Side Effects:
2646 **              Sets CurChildren to zero.
2647 */
2648
2649 void
2650 proc_list_clear()
2651 {
2652         int i;
2653
2654         /* start from 1 since 0 is the daemon itself */
2655         for (i = 1; i < ProcListSize; i++)
2656                 ProcListVec[i].proc_pid = NO_PID;
2657         CurChildren = 0;
2658 }
2659
2660 /*
2661 **  PROC_LIST_PROBE -- probe processes in the list to see if they still exist
2662 **
2663 **      Parameters:
2664 **              none
2665 **
2666 **      Returns:
2667 **              none
2668 **
2669 **      Side Effects:
2670 **              May decrease CurChildren.
2671 */
2672
2673 void
2674 proc_list_probe()
2675 {
2676         int i, children;
2677         int chldwasblocked;
2678         pid_t pid;
2679
2680         children = 0;
2681         chldwasblocked = sm_blocksignal(SIGCHLD);
2682
2683         /* start from 1 since 0 is the daemon itself */
2684         for (i = 1; i < ProcListSize; i++)
2685         {
2686                 pid = ProcListVec[i].proc_pid;
2687                 if (pid == NO_PID || pid == CurrentPid)
2688                         continue;
2689                 if (kill(pid, 0) < 0)
2690                 {
2691                         if (LogLevel > 3)
2692                                 sm_syslog(LOG_DEBUG, CurEnv->e_id,
2693                                           "proc_list_probe: lost pid %d",
2694                                           (int) ProcListVec[i].proc_pid);
2695                         ProcListVec[i].proc_pid = NO_PID;
2696                         SM_FREE_CLR(ProcListVec[i].proc_task);
2697                         CurChildren--;
2698                 }
2699                 else
2700                 {
2701                         ++children;
2702                 }
2703         }
2704         if (CurChildren < 0)
2705                 CurChildren = 0;
2706         if (chldwasblocked == 0)
2707                 (void) sm_releasesignal(SIGCHLD);
2708         if (LogLevel > 10 && children != CurChildren && CurrentPid == DaemonPid)
2709         {
2710                 sm_syslog(LOG_ERR, NOQID,
2711                           "proc_list_probe: found %d children, expected %d",
2712                           children, CurChildren);
2713         }
2714 }
2715
2716 /*
2717 **  PROC_LIST_DISPLAY -- display the process list
2718 **
2719 **      Parameters:
2720 **              out -- output file pointer
2721 **              prefix -- string to output in front of each line.
2722 **
2723 **      Returns:
2724 **              none.
2725 */
2726
2727 void
2728 proc_list_display(out, prefix)
2729         SM_FILE_T *out;
2730         char *prefix;
2731 {
2732         int i;
2733
2734         for (i = 0; i < ProcListSize; i++)
2735         {
2736                 if (ProcListVec[i].proc_pid == NO_PID)
2737                         continue;
2738
2739                 (void) sm_io_fprintf(out, SM_TIME_DEFAULT, "%s%d %s%s\n",
2740                                      prefix,
2741                                      (int) ProcListVec[i].proc_pid,
2742                                      ProcListVec[i].proc_task != NULL ?
2743                                      ProcListVec[i].proc_task : "(unknown)",
2744                                      (OpMode == MD_SMTP ||
2745                                       OpMode == MD_DAEMON ||
2746                                       OpMode == MD_ARPAFTP) ? "\r" : "");
2747         }
2748 }
2749
2750 /*
2751 **  PROC_LIST_SIGNAL -- send a signal to a type of process in the list
2752 **
2753 **      Parameters:
2754 **              type -- type of process to signal
2755 **              signal -- the type of signal to send
2756 **
2757 **      Results:
2758 **              none.
2759 **
2760 **      NOTE:   THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
2761 **              ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
2762 **              DOING.
2763 */
2764
2765 void
2766 proc_list_signal(type, signal)
2767         int type;
2768         int signal;
2769 {
2770         int chldwasblocked;
2771         int alrmwasblocked;
2772         int i;
2773         pid_t mypid = getpid();
2774
2775         /* block these signals so that we may signal cleanly */
2776         chldwasblocked = sm_blocksignal(SIGCHLD);
2777         alrmwasblocked = sm_blocksignal(SIGALRM);
2778
2779         /* Find all processes of type and send signal */
2780         for (i = 0; i < ProcListSize; i++)
2781         {
2782                 if (ProcListVec[i].proc_pid == NO_PID ||
2783                     ProcListVec[i].proc_pid == mypid)
2784                         continue;
2785                 if (ProcListVec[i].proc_type != type)
2786                         continue;
2787                 (void) kill(ProcListVec[i].proc_pid, signal);
2788         }
2789
2790         /* restore the signals */
2791         if (alrmwasblocked == 0)
2792                 (void) sm_releasesignal(SIGALRM);
2793         if (chldwasblocked == 0)
2794                 (void) sm_releasesignal(SIGCHLD);
2795 }
2796
2797 /*
2798 **  COUNT_OPEN_CONNECTIONS
2799 **
2800 **      Parameters:
2801 **              hostaddr - ClientAddress
2802 **
2803 **      Returns:
2804 **              the number of open connections for this client
2805 **
2806 */
2807
2808 int
2809 count_open_connections(hostaddr)
2810         SOCKADDR *hostaddr;
2811 {
2812         int i, n;
2813
2814         if (hostaddr == NULL)
2815                 return 0;
2816         n = 0;
2817         for (i = 0; i < ProcListSize; i++)
2818         {
2819                 if (ProcListVec[i].proc_pid == NO_PID)
2820                         continue;
2821                 if (hostaddr->sa.sa_family !=
2822                     ProcListVec[i].proc_hostaddr.sa.sa_family)
2823                         continue;
2824 #if NETINET
2825                 if (hostaddr->sa.sa_family == AF_INET &&
2826                     (hostaddr->sin.sin_addr.s_addr ==
2827                      ProcListVec[i].proc_hostaddr.sin.sin_addr.s_addr))
2828                         n++;
2829 #endif /* NETINET */
2830 #if NETINET6
2831                 if (hostaddr->sa.sa_family == AF_INET6 &&
2832                     IN6_ARE_ADDR_EQUAL(&(hostaddr->sin6.sin6_addr),
2833                                        &(ProcListVec[i].proc_hostaddr.sin6.sin6_addr)))
2834                         n++;
2835 #endif /* NETINET6 */
2836         }
2837         return n;
2838 }