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