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