Ian Kirk Radius support
Stuart Levy Replacement for broken inet_ntoa() on IRIX
Stuart Lynne First code for LDAP
-Nigel Metheringham Setting up the web site and mailing list
- Managing the web site and mailing list
+Nigel Metheringham Setting up the website and mailing list
+ Managing the website and mailing list
Interface to Berkeley DB
Support for cdb
Support for maildir
# appropriate links, and then creating and running the main makefile in that
# directory.
-# Copyright (c) University of Cambridge, 1995 - 2015
+# Copyright (c) University of Cambridge, 1995 - 2018
# See the file NOTICE for conditions of use and distribution.
# IRIX make uses the shell that is in the SHELL variable, which often defaults
echo "-q" > $@
echo "-p3" >> $@
find src Local OS exim_monitor -name "*.[cshyl]" -print \
- -o -name "os.h*" -print \
+ -o -name "os.[ch]*" -print \
-o -name "*akefile*" -print \
-o -name config.h.defaults -print \
-o -name EDITME -print >> $@
- ls OS/* >> $@
FRC:
# optional, Local/* files at the front of this file, to create Makefile in the
# build directory.
#
-# Copyright (c) The Exim Maintainers 2016
+# Copyright (c) The Exim Maintainers 1995 - 2018
SHELL = $(MAKE_SHELL)
SCRIPTS = ../scripts
# are set up, and finally it goes to the main Exim target.
all: utils exim
-config: $(EDITME) checklocalmake Makefile os.c config.h version.h
+config: $(EDITME) checklocalmake Makefile os.c config.h version.h version.sh macro.c
-checklocalmake:
+checklocalmake:
@if $(SHELL) $(SCRIPTS)/newer $(EDITME)-$(OSTYPE) $(EDITME) || \
$(SHELL) $(SCRIPTS)/newer $(EDITME)-$(ARCHTYPE) $(EDITME) || \
$(SHELL) $(SCRIPTS)/newer $(EDITME)-$(OSTYPE)-$(ARCHTYPE) $(EDITME); \
# Build (link) the os.h file
+#os.h: $(SCRIPTS)/Configure-os.h \
+# $(O)/os.h-AIX $(O)/os.h-BSDI $(O)/os.h-cygwin \
+# $(O)/os.h-Darwin $(O)/os.h-DGUX $(O)/os.h-DragonFly \
+# $(O)/os.h-FreeBSD $(O)/os.h-GNU $(O)/os.h-GNUkFreeBSD \
+# $(O)/os.h-GNUkNetBSD $(O)/os.h-HI-OSF \
+# $(O)/os.h-HI-UX $(O)/os.h-HP-UX $(O)/os.h-HP-UX-9 \
+# $(O)/os.h-IRIX $(O)/os.h-IRIX6 $(O)/os.h-IRIX632 \
+# $(O)/os.h-IRIX65 $(O)/os.h-Linux $(O)/os.h-mips \
+# $(O)/os.h-NetBSD $(O)/os.h-NetBSD-a.out \
+# $(O)/os.h-OpenBSD $(O)/os.h-OpenUNIX $(O)/os.h-OSF1 \
+# $(O)/os.h-QNX $(O)/os.h-SCO $(O)/os.h-SCO_SV \
+# $(O)/os.h-SunOS4 $(O)/os.h-SunOS5 $(O)/os.h-SunOS5-hal \
+# $(O)/os.h-ULTRIX $(O)/os.h-UNIX_SV \
+# $(O)/os.h-Unixware7 $(O)/os.h-USG
+# $(SHELL) $(SCRIPTS)/Configure-os.h
+
os.h: $(SCRIPTS)/Configure-os.h \
- $(O)/os.h-AIX $(O)/os.h-BSDI $(O)/os.h-cygwin \
- $(O)/os.h-Darwin $(O)/os.h-DGUX $(O)/os.h-DragonFly \
- $(O)/os.h-FreeBSD $(O)/os.h-GNU $(O)/os.h-GNUkFreeBSD \
- $(O)/os.h-GNUkNetBSD $(O)/os.h-HI-OSF \
- $(O)/os.h-HI-UX $(O)/os.h-HP-UX $(O)/os.h-HP-UX-9 \
- $(O)/os.h-IRIX $(O)/os.h-IRIX6 $(O)/os.h-IRIX632 \
- $(O)/os.h-IRIX65 $(O)/os.h-Linux $(O)/os.h-mips \
- $(O)/os.h-NetBSD $(O)/os.h-NetBSD-a.out \
- $(O)/os.h-OpenBSD $(O)/os.h-OpenUNIX $(O)/os.h-OSF1 \
- $(O)/os.h-QNX $(O)/os.h-SCO $(O)/os.h-SCO_SV \
- $(O)/os.h-SunOS4 $(O)/os.h-SunOS5 $(O)/os.h-SunOS5-hal \
- $(O)/os.h-ULTRIX $(O)/os.h-UNIX_SV \
- $(O)/os.h-Unixware7 $(O)/os.h-USG
+ $(O)/os.h-FreeBSD \
+ $(O)/os.h-Linux \
+ $(O)/os.h-OpenBSD \
+ $(O)/os.h-SunOS5
$(SHELL) $(SCRIPTS)/Configure-os.h
# Build the os.c file
+#os.c: ../src/os.c \
+# $(SCRIPTS)/Configure-os.c \
+# $(O)/os.c-cygwin $(O)/os.c-GNU $(O)/os.c-HI-OSF \
+# $(O)/os.c-IRIX $(O)/os.c-IRIX6 $(O)/os.c-IRIX632 \
+# $(O)/os.c-IRIX65 $(O)/os.c-Linux $(O)/os.c-OSF1
+# $(SHELL) $(SCRIPTS)/Configure-os.c
+
os.c: ../src/os.c \
$(SCRIPTS)/Configure-os.c \
- $(O)/os.c-cygwin $(O)/os.c-GNU $(O)/os.c-HI-OSF \
- $(O)/os.c-IRIX $(O)/os.c-IRIX6 $(O)/os.c-IRIX632 \
- $(O)/os.c-IRIX65 $(O)/os.c-Linux $(O)/os.c-OSF1
+ $(O)/os.c-Linux
$(SHELL) $(SCRIPTS)/Configure-os.c
# Build the config.h file.
config.h: Makefile buildconfig ../src/config.h.defaults $(EDITME)
$(SHELL) $(SCRIPTS)/Configure-config.h "$(MAKE)"
+# Build the builtin-macros data struct
+
+MACRO_HSRC = macro_predef.h os.h globals.h config.h \
+ routers/accept.h routers/dnslookup.h routers/ipliteral.h \
+ routers/iplookup.h routers/manualroute.h routers/queryprogram.h \
+ routers/redirect.h
+
+OBJ_MACRO = macro_predef.o \
+ macro-globals.o macro-readconf.o macro-route.o macro-transport.o macro-drtables.o \
+ macro-tls.o \
+ macro-appendfile.o macro-autoreply.o macro-lmtp.o macro-pipe.o macro-queuefile.o \
+ macro-smtp.o macro-accept.o macro-dnslookup.o macro-ipliteral.o macro-iplookup.o \
+ macro-manualroute.o macro-queryprogram.o macro-redirect.o \
+ macro-auth-spa.o macro-cram_md5.o macro-cyrus_sasl.o macro-dovecot.o macro-gsasl_exim.o \
+ macro-heimdal_gssapi.o macro-plaintext.o macro-spa.o macro-authtls.o \
+ macro-dkim.o macro-malware.o macro-signing.o
+
+$(OBJ_MACRO): $(MACRO_HSRC)
+
+macro_predef.o : macro_predef.c
+ @echo "$(CC) -DMACRO_PREDEF macro_predef.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ macro_predef.c
+macro-globals.o : globals.c
+ @echo "$(CC) -DMACRO_PREDEF globals.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ globals.c
+macro-readconf.o : readconf.c
+ @echo "$(CC) -DMACRO_PREDEF readconf.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ readconf.c
+macro-route.o : route.c
+ @echo "$(CC) -DMACRO_PREDEF route.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ route.c
+macro-transport.o: transport.c
+ @echo "$(CC) -DMACRO_PREDEF transport.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transport.c
+macro-drtables.o : drtables.c
+ @echo "$(CC) -DMACRO_PREDEF drtables.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ drtables.c
+macro-tls.o: tls.c
+ @echo "$(CC) -DMACRO_PREDEF tls.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ tls.c
+macro-appendfile.o : transports/appendfile.c
+ @echo "$(CC) -DMACRO_PREDEF transports/appendfile.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/appendfile.c
+macro-autoreply.o : transports/autoreply.c
+ @echo "$(CC) -DMACRO_PREDEF transports/autoreply.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/autoreply.c
+macro-lmtp.o: transports/lmtp.c
+ @echo "$(CC) -DMACRO_PREDEF transports/lmtp.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/lmtp.c
+macro-pipe.o : transports/pipe.c
+ @echo "$(CC) -DMACRO_PREDEF transports/pipe.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/pipe.c
+macro-queuefile.o : transports/queuefile.c
+ @echo "$(CC) -DMACRO_PREDEF transports/queuefile.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/queuefile.c
+macro-smtp.o : transports/smtp.c
+ @echo "$(CC) -DMACRO_PREDEF transports/smtp.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ transports/smtp.c
+macro-accept.o : routers/accept.c
+ @echo "$(CC) -DMACRO_PREDEF routers/accept.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/accept.c
+macro-dnslookup.o : routers/dnslookup.c
+ @echo "$(CC) -DMACRO_PREDEF routers/dnslookup.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/dnslookup.c
+macro-ipliteral.o : routers/ipliteral.c
+ @echo "$(CC) -DMACRO_PREDEF routers/ipliteral.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/ipliteral.c
+macro-iplookup.o : routers/iplookup.c
+ @echo "$(CC) -DMACRO_PREDEF routers/iplookup.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/iplookup.c
+macro-manualroute.o : routers/manualroute.c
+ @echo "$(CC) -DMACRO_PREDEF routers/manualroute.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/manualroute.c
+macro-queryprogram.o : routers/queryprogram.c
+ @echo "$(CC) -DMACRO_PREDEF routers/queryprogram.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/queryprogram.c
+macro-redirect.o : routers/redirect.c
+ @echo "$(CC) -DMACRO_PREDEF routers/redirect.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ routers/redirect.c
+macro-auth-spa.o : auths/auth-spa.c
+ @echo "$(CC) -DMACRO_PREDEF auths/auth-spa.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/auth-spa.c
+macro-cram_md5.o : auths/cram_md5.c
+ @echo "$(CC) -DMACRO_PREDEF auths/cram_md5.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/cram_md5.c
+macro-cyrus_sasl.o : auths/cyrus_sasl.c
+ @echo "$(CC) -DMACRO_PREDEF auths/cyrus_sasl.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/cyrus_sasl.c
+macro-dovecot.o: auths/dovecot.c
+ @echo "$(CC) -DMACRO_PREDEF auths/dovecot.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/dovecot.c
+macro-gsasl_exim.o : auths/gsasl_exim.c
+ @echo "$(CC) -DMACRO_PREDEF auths/gsasl_exim.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/gsasl_exim.c
+macro-heimdal_gssapi.o: auths/heimdal_gssapi.c
+ @echo "$(CC) -DMACRO_PREDEF auths/heimdal_gssapi.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/heimdal_gssapi.c
+macro-plaintext.o : auths/plaintext.c
+ @echo "$(CC) -DMACRO_PREDEF auths/plaintext.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/plaintext.c
+macro-spa.o : auths/spa.c
+ @echo "$(CC) -DMACRO_PREDEF auths/spa.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/spa.c
+macro-authtls.o: auths/tls.c
+ @echo "$(CC) -DMACRO_PREDEF auths/tls.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/tls.c
+macro-dkim.o: dkim.c
+ @echo "$(CC) -DMACRO_PREDEF dkim.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ dkim.c
+macro-malware.o: malware.c
+ @echo "$(CC) -DMACRO_PREDEF malware.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ malware.c
+macro-signing.o: pdkim/signing.c
+ @echo "$(CC) -DMACRO_PREDEF pdkim/signing.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ pdkim/signing.c
+
+macro_predef: $(OBJ_MACRO)
+ @echo "$(LNCC) -o $@"
+ $(FE)$(LNCC) -o $@ $(LFLAGS) $(OBJ_MACRO)
+
+macro.c: macro_predef
+ ./macro_predef > macro.c
# This target is recognized specially by GNU make. It records those targets
# that do not correspond to files that are being built and which should
# Target for the exicyclog utility script
exicyclog: config ../src/exicyclog.src
@rm -f exicyclog
- @sed \
+ @. ./version.sh && sed \
-e "s?PROCESSED_FLAG?This file has been so processed.?"\
-e "/^# /p" \
-e "/^# /d" \
-e "s?MV_COMMAND?$(MV_COMMAND)?" \
-e "s?RM_COMMAND?$(RM_COMMAND)?" \
-e "s?TOUCH_COMMAND?$(TOUCH_COMMAND)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/exicyclog.src > exicyclog-t
@mv exicyclog-t exicyclog
@chmod a+x exicyclog
# Target for the exinext utility script
exinext: config ../src/exinext.src
@rm -f exinext
- @sed \
+ @. ./version.sh && sed \
-e "s?PROCESSED_FLAG?This file has been so processed.?"\
-e "/^# /p" \
-e "/^# /d" \
-e "s?CONFIGURE_FILE_USE_NODE?$(CONFIGURE_FILE_USE_NODE)?" \
-e "s?CONFIGURE_FILE?$(CONFIGURE_FILE)?" \
-e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/exinext.src > exinext-t
@mv exinext-t exinext
@chmod a+x exinext
# Target for the exiwhat utility script
exiwhat: config ../src/exiwhat.src
@rm -f exiwhat
- @sed \
+ @. ./version.sh && sed \
-e "s?PROCESSED_FLAG?This file has been so processed.?"\
-e "/^# /p" \
-e "/^# /d" \
-e "s?EXIWHAT_EGREP_ARG?$(EXIWHAT_EGREP_ARG)?" \
-e "s?EXIWHAT_MULTIKILL_CMD?$(EXIWHAT_MULTIKILL_CMD)?" \
-e "s?EXIWHAT_MULTIKILL_ARG?$(EXIWHAT_MULTIKILL_ARG)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
+ -e "s?RM_COMMAND?$(RM_COMMAND)?" \
../src/exiwhat.src > exiwhat-t
@mv exiwhat-t exiwhat
@chmod a+x exiwhat
# Target for the exim_checkaccess utility script
exim_checkaccess: config ../src/exim_checkaccess.src
@rm -f exim_checkaccess
- @sed \
+ @. ./version.sh && sed \
-e "s?PROCESSED_FLAG?This file has been so processed.?"\
-e "/^# /p" \
-e "/^# /d" \
-e "s?CONFIGURE_FILE?$(CONFIGURE_FILE)?" \
-e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
-e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/exim_checkaccess.src > exim_checkaccess-t
@mv exim_checkaccess-t exim_checkaccess
@chmod a+x exim_checkaccess
../Local/eximon.conf
@rm -f eximon
$(SHELL) $(SCRIPTS)/Configure-eximon
- @sed \
+ @. ./version.sh && sed \
-e "s?PROCESSED_FLAG?This file has been so processed.?"\
-e "/^# /p" \
-e "/^# /d" \
-e "s?BASENAME_COMMAND?$(BASENAME_COMMAND)?" \
-e "s?HOSTNAME_COMMAND?$(HOSTNAME_COMMAND)?" \
-e "s?X11_LD_LIBRARY?$(X11_LD_LIB)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/eximon.src >> eximon
@echo ">>> eximon script built"; echo ""
# Targets for utilities; these are all Perl scripts that have to get the
# location of Perl put in them. A few need other things as well.
-exigrep: Makefile ../src/exigrep.src
+exigrep: config ../src/exigrep.src
@rm -f exigrep
- @sed \
+ @. ./version.sh && sed \
-e "s?PROCESSED_FLAG?This file has been so processed.?"\
-e "/^# /p" \
-e "/^# /d" \
-e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
-e "s?ZCAT_COMMAND?$(ZCAT_COMMAND)?" \
-e "s?COMPRESS_SUFFIX?$(COMPRESS_SUFFIX)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/exigrep.src > exigrep-t
@mv exigrep-t exigrep
@chmod a+x exigrep
@echo ">>> exigrep script built"
-eximstats: Makefile ../src/eximstats.src
+eximstats: config ../src/eximstats.src
@rm -f eximstats
- @sed \
+ @. ./version.sh && sed \
-e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/eximstats.src > eximstats-t
@mv eximstats-t eximstats
@chmod a+x eximstats
@echo ">>> eximstats script built"
-exiqgrep: Makefile ../src/exiqgrep.src
+exiqgrep: config ../src/exiqgrep.src
@rm -f exiqgrep
- @sed \
+ @. ./version.sh && sed \
-e "s?PROCESSED_FLAG?This file has been so processed.?"\
-e "/^# /p" \
-e "/^# /d" \
-e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
-e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/exiqgrep.src > exiqgrep-t
@mv exiqgrep-t exiqgrep
@chmod a+x exiqgrep
@echo ">>> exiqgrep script built"
-exiqsumm: Makefile ../src/exiqsumm.src
+exiqsumm: config ../src/exiqsumm.src
@rm -f exiqsumm
- @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ @. ./version.sh && sed \
+ -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/exiqsumm.src > exiqsumm-t
@mv exiqsumm-t exiqsumm
@chmod a+x exiqsumm
@echo ">>> exiqsumm script built"
-exipick: Makefile ../src/exipick.src
+exipick: config ../src/exipick.src
@rm -f exipick
- @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ @. ./version.sh && sed \
+ -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
-e "s?SPOOL_DIRECTORY?$(SPOOL_DIRECTORY)?" \
-e "s?BIN_DIRECTORY?$(BIN_DIRECTORY)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/exipick.src > exipick-t
@mv exipick-t exipick
@chmod a+x exipick
@echo ">>> exipick script built"
-transport-filter.pl: Makefile ../src/transport-filter.src
+transport-filter.pl: config ../src/transport-filter.src
@rm -f transport-filter.pl
- @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ @. ./version.sh && sed \
+ -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/transport-filter.src > transport-filter.pl-t
@mv transport-filter.pl-t transport-filter.pl
@chmod a+x transport-filter.pl
@echo ">>> transport-filter.pl script built"
-convert4r3: Makefile ../src/convert4r3.src
+convert4r3: config ../src/convert4r3.src
@rm -f convert4r3
- @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ @. ./version.sh && sed \
+ -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/convert4r3.src > convert4r3-t
@mv convert4r3-t convert4r3
@chmod a+x convert4r3
@echo ">>> convert4r3 script built"
-convert4r4: Makefile ../src/convert4r4.src
+convert4r4: config ../src/convert4r4.src
@rm -f convert4r4
- @sed -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ @. ./version.sh && sed \
+ -e "s?PERL_COMMAND?$(PERL_COMMAND)?" \
+ -e "s?EXIM_RELEASE_VERSION?$${EXIM_RELEASE_VERSION}?" \
+ -e "s?EXIM_VARIANT_VERSION?$${EXIM_VARIANT_VERSION}?" \
../src/convert4r4.src > convert4r4-t
@mv convert4r4-t convert4r4
@chmod a+x convert4r4
# are thrown away by the linker.
OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o
-OBJ_EXPERIMENTAL = bmi_spam.o \
- dane.o \
- dcc.o \
- dmarc.o \
- imap_utf7.o \
- spf.o \
- srs.o \
- utf8.o
+OBJ_EXPERIMENTAL = arc.o \
+ bmi_spam.o \
+ dane.o \
+ dcc.o \
+ dmarc.o \
+ imap_utf7.o \
+ spf.o \
+ srs.o \
+ utf8.o
# Targets for final binaries; the main one has a build number which is
# updated each time. We don't bother with that for the auxiliaries.
OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \
- filtertest.o globals.o dkim.o hash.o \
+ filtertest.o globals.o dkim.o dkim_transport.o hash.o \
header.o host.o ip.o log.o lss.o match.o moan.o \
os.o parse.o queue.o \
rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \
route.o search.o sieve.o smtp_in.o smtp_out.o spool_in.o spool_out.o \
std-crypto.o store.o string.o tls.o tod.o transport.o tree.o verify.o \
- environment.o \
+ environment.o macro.o \
$(OBJ_LOOKUPS) \
local_scan.o $(EXIM_PERL) $(OBJ_WITH_CONTENT_SCAN) \
$(OBJ_EXPERIMENTAL)
util-store.o \
util-string.o \
util-queue.o \
- tod.o \
+ util-tod.o \
tree.o \
$(MONBIN)
# Update Exim's version information and build the version object.
-version.h::
+version.h version.sh::
@../scripts/reversion
cnumber.h: version.h
exim_dbmbuild.o: $(HDRS) exim_dbmbuild.c
@echo "$(CC) exim_dbmbuild.c"
- $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -o exim_dbmbuild.o exim_dbmbuild.c
+ $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY \
+ -o exim_dbmbuild.o exim_dbmbuild.c
# Utilities use special versions of some modules - typically with debugging
# calls cut out.
@echo "$(CC) -DCOMPILE_UTILITY queue.c"
$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-queue.o queue.c
+util-tod.o: $(HDRS) tod.c
+ @echo "$(CC) -DCOMPILE_UTILITY tod.c"
+ $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -DCOMPILE_UTILITY -o util-tod.o tod.c
+
util-os.o: $(HDRS) os.c
@echo "$(CC) -DCOMPILE_UTILITY os.c"
$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) \
daemon.o: $(HDRS) daemon.c
dbfn.o: $(HDRS) dbfn.c
debug.o: $(HDRS) debug.c
-deliver.o: $(HDRS) deliver.c
+deliver.o: $(HDRS) transports/smtp.h deliver.c
directory.o: $(HDRS) directory.c
dns.o: $(HDRS) dns.c
enq.o: $(HDRS) enq.c
tree.o: $(HDRS) tree.c
verify.o: $(HDRS) transports/smtp.h verify.c
dkim.o: $(HDRS) pdkim/pdkim.h dkim.c
+dkim_transport.o: $(HDRS) dkim_transport.c
# Dependencies for WITH_CONTENT_SCAN modules
# Dependencies for EXPERIMENTAL_* modules
+arc.o: $(HDRS) pdkim/pdkim.h arc.c
bmi_spam.o: $(HDRS) bmi_spam.c
-dane.o: $(HDRS) dane.c dane-gnu.c dane-openssl.c
+dane.o: $(HDRS) dane.c dane-openssl.c
dcc.o: $(HDRS) dcc.h dcc.c
dmarc.o: $(HDRS) pdkim/pdkim.h dmarc.h dmarc.c
imap_utf7.o: $(HDRS) imap_utf7.c
CHGRP_COMMAND=/usr/sbin/chgrp
CHMOD_COMMAND=/bin/chmod
-CFLAGS=-O2 -Wall
+CFLAGS=-O2 -Wall -Wno-parentheses -Wno-self-assign -Wno-logical-op-parentheses
LIBS=-lm
--- /dev/null
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* FreeBSD-specific code. This is concatenated onto the generic
+src/os.c file. */
+
+
+/*************
+* Sendfile *
+*************/
+
+ssize_t
+os_sendfile(int out, int in, off_t * off, size_t cnt)
+{
+off_t written;
+return sendfile(in, out, *off, cnt, NULL, &written, 0) < 0
+ ? (ssize_t) -1 : (ssize_t) written;
+}
+
+/* End of os.c-Linux */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1997 - 2016 */
+/* Copyright (c) University of Cambridge 1997 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Linux-specific code. This is concatenated onto the generic
#endif /* FIND_RUNNING_INTERFACES */
+
+/*************
+* Sendfile *
+*************/
+#include <sys/sendfile.h>
+
+ssize_t
+os_sendfile(int out, int in, off_t * off, size_t cnt)
+{
+return sendfile(out, in, off, cnt);
+}
+
/* End of os.c-Linux */
#define PAM_H_IN_PAM
#define SIOCGIFCONF_GIVES_ADDR
-/* OSX 10.2 does not have poll.h, 10.3 does emulate it badly. */
-#define NO_POLL_H
#define F_FREESP O_TRUNC
typedef struct flock flock_t;
Consider reducing MAX_LOCALHOST_NUMBER */
#ifndef _BSD_SOCKLEN_T_
-#define _BSD_SOCKLEN_T_ int32_t /* socklen_t (duh) */
+# define _BSD_SOCKLEN_T_ int32_t /* socklen_t (duh) */
#endif
/* Settings for handling IP options. There's no netinet/ip_var.h. The IP
/* default is non-const */
#define ICONV_ARG2_TYPE const char **
+/* seems arpa/nameser.h does not define this */
+#define NS_MAXMSG 65535
+
+/* There may be very many supplementary groups for the user. See notes
+in "man 2 getgroups". */
+#define _DARWIN_UNLIMITED_GETGROUPS
+#define EXIM_GROUPLIST_SIZE 64
+
+/* TCP Fast Open: Darwin uses a connectx() call
+rather than a modified sendto() */
+#define EXIM_TFO_CONNECTX
+
/* End */
/* Exim: OS-specific C header file for FreeBSD */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+
+#include <sys/types.h>
+#include <sys/param.h>
#define HAVE_BSD_GETLOADAVG
#define HAVE_SETCLASSRESOURCES
#define HAVE_SRANDOMDEV
#define HAVE_ARC4RANDOM
+/* Applications should not call arc4random_stir() explicitly after
+ * FreeBSD r227520 (approximately 1000002).
+ * Set NOT_HAVE_ARC4RANDOM_STIR if the version released is past
+ * that point. */
+#if __FreeBSD_version >= 1000002
+# define NOT_HAVE_ARC4RANDOM_STIR
+#endif
+
typedef struct flock flock_t;
/* iconv arg2 type: libiconv in Ports uses "const char* * inbuf" and was
#if __FreeBSD__ >= 10
# define LIBICONV_PLUG
#endif
-/* for more specific version constraints, include <sys/param.h> and look at
- * __FreeBSD_version */
+/* for more specific version constraints, look at __FreeBSD_version
+ * from <sys/param.h> */
+
+/* When using DKIM, setting OS_SENDFILE can increase
+performance on outgoing mail a bit. */
+
+#define OS_SENDFILE
+extern ssize_t os_sendfile(int, int, off_t *, size_t);
+
+
+/*******************/
+
+/* TCP_FASTOPEN support. There does not seems to be a
+MSG_FASTOPEN defined yet... */
+#define EXIM_TFO_PROBE
+
+#include <netinet/tcp.h> /* for TCP_FASTOPEN */
+#include <sys/socket.h> /* for MSG_FASTOPEN */
+#if defined(TCP_FASTOPEN) && !defined(MSG_FASTOPEN)
+# define MSG_FASTOPEN 0x20000000
+#endif
+
+/* for TCP state-variable values, for TFO logging */
+#include <netinet/tcp_fsm.h>
+#define TCP_SYN_RECV TCPS_SYN_RECEIVED
+
+/*******************/
/* End */
/* Exim: OS-specific C header file for Linux */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
/* Some old systems we've received bug-reports for have a <limits.h> which
does not pull in <features.h>. Best to just pull it in now and have done
with the issue. */
#include <features.h>
+#include <sys/types.h>
#define CRYPT_H
#define NO_IP_VAR_H
#define SIG_IGN_WORKS
-/* When using the DKIM, setting HAVE_LINUX_SENDFILE can increase
+/* When using DKIM, setting OS_SENDFILE can increase
performance on outgoing mail a bit. Note: With older glibc versions
this setting will conflict with the _FILE_OFFSET_BITS=64 setting
-defined as part of the Linux CFLAGS. */
+defined as part of the Linux CFLAGS. As of 2017 those are declared
+to be too old to build by default. */
-/* #define HAVE_LINUX_SENDFILE */
+#define OS_SENDFILE
+extern ssize_t os_sendfile(int, int, off_t *, size_t);
#define F_FREESP O_TRUNC
typedef struct flock flock_t;
#define OS_STRSIGNAL
#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__NetBSD_kernel__)
-#define SIOCGIFCONF_GIVES_ADDR
-#define HAVE_SYS_MOUNT_H
+# define SIOCGIFCONF_GIVES_ADDR
+# define HAVE_SYS_MOUNT_H
#endif
#if defined(__linux__)
# define EXIM_HAVE_OPENAT
#endif
+/* TCP Fast Open support */
+
#include <netinet/tcp.h> /* for TCP_FASTOPEN */
#include <sys/socket.h> /* for MSG_FASTOPEN */
#if defined(TCP_FASTOPEN) && !defined(MSG_FASTOPEN)
# define MSG_FASTOPEN 0x20000000
#endif
+#define EXIM_HAVE_TCPI_UNACKED
+#ifndef TCPI_OPT_SYN_DATA
+# define TCPI_OPT_SYN_DATA 32
+#endif
/* End */
/* Exim: OS-specific C header file for OpenBSD */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
#define HAVE_BSD_GETLOADAVG
#define HAVE_MMAP
if the version released is past that point. */
#include <sys/param.h>
#if OpenBSD >= 201405
-#define NOT_HAVE_ARC4RANDOM_STIR
+# define NOT_HAVE_ARC4RANDOM_STIR
#endif
typedef struct flock flock_t;
# define EPROTO 71
#endif
+/* We need to force this; the automatic in buildconfig.c gets %ld */
+#ifdef OFF_T_FMT
+# undef OFF_T_FMT
+# undef LONGLONG_T
+#endif
+#define OFF_T_FMT "%lld"
+#define LONGLONG_T long long int
+
+#ifdef PID_T_FMT
+# undef PID_T_FMT
+#endif
+#define PID_T_FMT "%d"
+
+#ifdef INO_T_FMT
+# undef INO_T_FMT
+#endif
+#define INO_T_FMT "%llu"
+
+#ifdef TIME_T_FMT
+# undef TIME_T_FMT
+#endif
+#define TIME_T_FMT "%lld"
+
+/* seems arpa/nameser.h does not define this.
+Space-constrained devices could use much smaller; a few k. */
+#define NS_MAXMSG 65535
+
/* End */
--- /dev/null
+Files in this directory are historical. They may have worked once but the
+project has no assurance that they still do.
+
+If you need to use one for a build for your platform, copy it up one directory
+level first. We'll reinstate it given a current version and evidence of testing.
+For the latter please look into the project regression testsuite, and please
+consider operating a buildfarm animal in the long term (it runs the testsuite).
+
+The buildfarm status page is:
+ https://buildfarm.exim.org/cgi-bin/show_status.pl
+There's a "register" link there with a link to how-to instructions. Please do
+monitor the status of your animal on an ongoing basis. The exim-users or
+exim-dev mailinglist are good places to ask for help and to discuss any regressions
+seen in test runs. There is also the #exim IRC channel on Freenode.
#define OS_UNSETENV
int
-os_unsetenv(const unsigned char * name)
+os_unsetenv(const uschar * name)
{
-unsetenv((char *)name);
+unsetenv(CS name);
return 0;
}
if ((ifam->ifam_addrs & RTA_IFA) != 0)
{
- char *cp = (char *)mask;
+ char *cp = CS mask;
struct sockaddr *sa = (struct sockaddr *)mask;
ADVANCE(cp, sa);
addr = (struct sockaddr_in *)cp;
if ((ifam->ifam_addrs & RTA_IFA) != 0)
{
- char *cp = (char *)mask;
+ char *cp = CS mask;
struct sockaddr *sa = (struct sockaddr *)mask;
ADVANCE(cp, sa);
addr = (struct sockaddr_in *)cp;
if ((ifam->ifam_addrs & RTA_IFA) != 0)
{
- char *cp = (char *)mask;
+ char *cp = CS mask;
struct sockaddr *sa = (struct sockaddr *)mask;
ADVANCE(cp, sa);
addr = (struct sockaddr_in *)cp;
if ((ifam->ifam_addrs & RTA_IFA) != 0)
{
- char *cp = (char *)mask;
+ char *cp = CS mask;
struct sockaddr *sa = (struct sockaddr *)mask;
ADVANCE(cp, sa);
addr = (struct sockaddr_in *)cp;
THE EXIM MAIL TRANSFER AGENT VERSION 4
--------------------------------------
-Copyright (c) 1995 - 2012 University of Cambridge.
+Copyright (c) 1995 - 2018 University of Cambridge.
See the file NOTICE for conditions of use and distribution.
There is a book about Exim by Philip Hazel called "The Exim SMTP Mail Server",
older book may be helpful for the background, but a lot of the detail has
changed, so it is likely to be confusing to newcomers.
-There is a web site at http://www.exim.org; this contains details of the
+There is a website at https://www.exim.org; this contains details of the
mailing list exim-users@exim.org.
A copy of the Exim FAQ should be available from the same source that you used
that might affect a running system.
+Exim version 4.92
+-----------------
+
+ * Exim used to manually follow CNAME chains, to a limited depth. In this
+ day-and-age we expect the resolver to be doing this for us, so the loop
+ is limited to one retry unless the (new) config option dns_cname_loops
+ is changed.
+
+Exim version 4.91
+-----------------
+
+ * DANE and SPF have been promoted from Experimental to Supported status, thus
+ the options to enable them in Local/Makefile have been renamed.
+ See current src/EDITME for full details, including changes in dependencies,
+ but loosely: replace EXPERIMENTAL_SPF with SUPPORT_SPF and replace
+ EXPERIMENTAL_DANE with SUPPORT_DANE.
+
+ * Ancient ClamAV stream support, long deprecated by ClamAV, has been removed;
+ if you were building with WITH_OLD_CLAMAV_STREAM enabled then your problems
+ have marginally increased.
+
+ * A number of logging changes; if relying upon the previous DKIM additional
+ log-line, explicit log_selector configuration is needed to keep it.
+
+ * Other incompatible changes in EXPERIMENTAL_* features, read NewStuff and
+ ChangeLog carefully if relying upon an experimental feature such as DMARC.
+ Note that this includes changes to SPF as it was promoted into Supported.
+
+
Exim version 4.89
-----------------
-----------------
* SPF condition results renamed "permerror" and "temperror". The old
- names are still accepted for back-compatability, for this release.
+ names are still accepted for back-compatibility, for this release.
* TLS details are now logged on rejects, subject to log selectors.
upgrading, then lock the message, replace the new-lines that should be part
of the -tls_peerdn line with the two-character sequence \n and then unlock
the message. No tool has been provided as we believe this is a rare
- occurence.
+ occurrence.
* For OpenSSL, SSLv2 is now disabled by default. (GnuTLS does not support
SSLv2). RFC 6176 prohibits SSLv2 and some informal surveys suggest no
-----------------
1. Experimental Yahoo! Domainkeys support has been dropped in this release.
-It has been superceded by a native implementation of its successor DKIM.
+It has been superseded by a native implementation of its successor DKIM.
2. Up to version 4.69, Exim came with an embedded version of the PCRE library.
As of 4.70, this is no longer the case. To compile Exim, you will need PCRE
---- EDITME.exim4-light 2017-03-04 11:15:58.309895066 +0100
-+++ EDITME.exim4-heavy 2017-03-04 11:17:12.616522005 +0100
-@@ -212,7 +212,7 @@
+--- EDITME.exim4-light 2019-04-16 15:54:51.009790678 +0000
++++ EDITME.exim4-heavy 2019-04-16 15:54:44.177917231 +0000
+@@ -217,7 +217,7 @@
# This one is very special-purpose, so is not included by default.
#------------------------------------------------------------------------------
-@@ -244,7 +244,7 @@
+@@ -249,7 +249,7 @@
SUPPORT_MAILDIR=yes
SUPPORT_MAILSTORE=yes
#------------------------------------------------------------------------------
-@@ -305,15 +305,15 @@
+@@ -310,16 +310,16 @@
LOOKUP_CDB=yes
LOOKUP_DSEARCH=yes
# LOOKUP_IBASE=yes
-# LOOKUP_MYSQL=yes
+LOOKUP_LDAP=yes
+LOOKUP_MYSQL=yes
+ # LOOKUP_MYSQL_PC=mariadb
LOOKUP_NIS=yes
# LOOKUP_NISPLUS=yes
# LOOKUP_ORACLE=yes
# LOOKUP_SQLITE_PC=sqlite3
# LOOKUP_WHOSON=yes
-@@ -334,7 +334,7 @@
+@@ -340,7 +340,7 @@
# with Solaris 7 onwards. Uncomment whichever of these you are using.
# LDAP_LIB_TYPE=OPENLDAP1
# LDAP_LIB_TYPE=NETSCAPE
# LDAP_LIB_TYPE=SOLARIS
-@@ -373,6 +373,9 @@
+@@ -385,6 +385,9 @@
# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds -lsqlite3
#------------------------------------------------------------------------------
# Compiling the Exim monitor: If you want to compile the Exim monitor, a
# program that requires an X11 display, then EXIM_MONITOR should be set to the
-@@ -381,7 +384,7 @@
+@@ -393,7 +396,7 @@
# files are defaulted in the OS/Makefile-Default file, but can be overridden in
# local OS-specific make files.
#------------------------------------------------------------------------------
-@@ -391,7 +394,7 @@
+@@ -403,7 +406,7 @@
# and the MIME ACL. Please read the documentation to learn more about these
# features.
-# WITH_CONTENT_SCAN=yes
+WITH_CONTENT_SCAN=yes
- #------------------------------------------------------------------------------
- # If you're using ClamAV and are backporting fixes to an old version, instead
-@@ -627,16 +630,16 @@
+ # If you have content scanning you may wish to only include some of the scanner
+ # interfaces. Uncomment any of these lines to remove that code.
+@@ -645,16 +648,16 @@
# configuration to make use of the mechanism(s) selected.
AUTH_CRAM_MD5=yes
# Heimdal through 1.5 required pkg-config 'heimdal-gssapi'; Heimdal 7.1
# requires multiple pkg-config files to work with Exim, so the second example
-@@ -649,7 +652,7 @@
+@@ -667,7 +670,7 @@
# Similarly for GNU SASL, unless pkg-config is used via AUTH_GSASL_PC.
# Ditto for AUTH_HEIMDAL_GSSAPI(_PC).
# AUTH_LIBS=-lgsasl
# AUTH_LIBS=-lgssapi -lheimntlm -lkrb5 -lhx509 -lcom_err -lhcrypto -lasn1 -lwind -lroken -lcrypt
-@@ -923,7 +926,7 @@
+@@ -945,7 +948,7 @@
# (version 5.004 or later) installed, set EXIM_PERL to perl.o. Using embedded
# Perl costs quite a lot of resources. Only do this if you really need it.
#------------------------------------------------------------------------------
-@@ -933,7 +936,7 @@
+@@ -955,7 +958,7 @@
# that the local_scan API is made available by the linker. You may also need
# to add -ldl to EXTRALIBS so that dlopen() is available to Exim.
#------------------------------------------------------------------------------
-@@ -943,11 +946,11 @@
+@@ -965,11 +968,11 @@
# support, which is intended for use in conjunction with the SMTP AUTH
# facilities, is included only when requested by the following setting:
#------------------------------------------------------------------------------
-@@ -961,7 +964,7 @@
+@@ -983,7 +986,7 @@
# If you may want to use inbound (server-side) proxying, using Proxy Protocol,
# uncomment the line below.
#------------------------------------------------------------------------------
-@@ -1299,7 +1302,7 @@
+@@ -1338,7 +1341,7 @@
# local part) can be increased by changing this value. It should be set to
# a multiple of 16.
---- src/EDITME 2017-02-12 14:19:37.000000000 +0000
-+++ EDITME.exim4-light 2017-02-12 14:22:15.062382937 +0000
+--- src/EDITME 2019-04-16 15:52:53.000000000 +0000
++++ EDITME.exim4-light 2019-04-16 15:54:51.009790678 +0000
@@ -98,7 +98,7 @@
# /usr/local/sbin. The installation script will try to create this directory,
# and any superior directories, if they do not exist.
-@@ -232,7 +233,7 @@
+@@ -237,7 +238,7 @@
# This one is special-purpose, and commonly not required, so it is not
# included by default.
#------------------------------------------------------------------------------
-@@ -241,8 +242,8 @@
+@@ -246,8 +247,8 @@
# MBX, is included only when requested. If you do not know what this is about,
# leave these settings commented out.
# SUPPORT_MBX=yes
-@@ -301,15 +302,15 @@
+@@ -306,16 +307,16 @@
LOOKUP_LSEARCH=yes
LOOKUP_DNSDB=yes
# LOOKUP_IBASE=yes
# LOOKUP_LDAP=yes
# LOOKUP_MYSQL=yes
+ # LOOKUP_MYSQL_PC=mariadb
-# LOOKUP_NIS=yes
+LOOKUP_NIS=yes
# LOOKUP_NISPLUS=yes
# LOOKUP_PGSQL=yes
# LOOKUP_REDIS=yes
# LOOKUP_SQLITE=yes
-@@ -577,7 +578,7 @@
+@@ -367,7 +368,7 @@
+ # Uncomment the following line to add DANE support
+ # Note: Enabling this unconditionally overrides DISABLE_DNSSEC
+ # For DANE under GnuTLS we need an additional library. See TLS_LIBS below.
+-# SUPPORT_DANE=yes
++SUPPORT_DANE=yes
+
+ #------------------------------------------------------------------------------
+ # Additional libraries and include directories may be required for some
+@@ -595,7 +596,7 @@
# CONFIGURE_OWNER setting, to specify a configuration file which is listed in
# the TRUSTED_CONFIG_LIST file, then root privileges are not dropped by Exim.
#------------------------------------------------------------------------------
-@@ -613,6 +614,9 @@
+@@ -631,6 +632,9 @@
# WHITELIST_D_MACROS=TLS:SPOOL
#------------------------------------------------------------------------------
# Exim has support for the AUTH (authentication) extension of the SMTP
# protocol, as defined by RFC 2554. If you don't know what SMTP authentication
-@@ -622,7 +626,7 @@
+@@ -640,7 +644,7 @@
# included in the Exim binary. You will then need to set up the run time
# configuration to make use of the mechanism(s) selected.
# AUTH_CYRUS_SASL=yes
# AUTH_DOVECOT=yes
# AUTH_GSASL=yes
-@@ -630,7 +634,7 @@
+@@ -648,7 +652,7 @@
# AUTH_HEIMDAL_GSSAPI=yes
# AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi
# AUTH_HEIMDAL_GSSAPI_PC=heimdal-gssapi heimdal-krb5
# AUTH_SPA=yes
# AUTH_TLS=yes
-@@ -656,7 +660,7 @@
+@@ -674,7 +678,7 @@
# one that is set in the headers_charset option. The default setting is
# defined by this setting:
# If you are going to make use of $header_xxx expansions in your configuration
# file, or if your users are going to use them in filter files, and the normal
-@@ -745,7 +749,7 @@
+@@ -763,7 +767,7 @@
# leave these settings commented out.
# This setting is required for any TLS support (either OpenSSL or GnuTLS)
# Uncomment one of these settings if you are using OpenSSL; pkg-config vs not
# USE_OPENSSL_PC=openssl
-@@ -753,9 +757,9 @@
+@@ -771,9 +775,9 @@
# Uncomment the first and either the second or the third of these if you
# are using GnuTLS. If you have pkg-config, then the second, else the third.
# If using GnuTLS older than 2.10 and using pkg-config then note that Exim's
# build process will require libgcrypt-config to exist in your $PATH. A
-@@ -847,6 +851,7 @@
+@@ -809,7 +813,7 @@
+ # TLS_LIBS=-L/opt/gnu/lib -lgnutls -ltasn1 -lgcrypt
+
+ # For DANE under GnuTLS we need an additional library.
+-# TLS_LIBS += -lgnutls-dane
++TLS_LIBS += -lgnutls-dane
+
+ # TLS_LIBS is included only on the command for linking Exim itself, not on any
+ # auxiliary programs. If the include files are not in a standard place, you can
+@@ -830,6 +834,7 @@
+ # description of the API to this function, see the Exim specification.
+
+ DLOPEN_LOCAL_SCAN=yes
++HAVE_LOCAL_SCAN=yes
+
+ # If you set DLOPEN_LOCAL_SCAN, then you need to include -rdynamic in the
+ # linker flags. Without it, the loaded .so won't be able to access any
+@@ -868,6 +873,7 @@
# to form the final file names. Some installations may want something like this:
# LOG_FILE_PATH=/var/log/exim_%slog
# which results in files with names /var/log/exim_mainlog, etc. The directory
# in which the log files are placed must exist; Exim does not try to create
-@@ -895,7 +900,7 @@
+@@ -916,7 +922,7 @@
# files. Both the name of the command and the suffix that it adds to files
# need to be defined here. See also the EXICYCLOG_MAX configuration.
COMPRESS_SUFFIX=gz
-@@ -910,7 +915,7 @@
+@@ -931,7 +937,7 @@
# ZCAT_COMMAND=zcat
#
# Or specify the full pathname:
#------------------------------------------------------------------------------
# Compiling in support for embedded Perl: If you want to be able to
-@@ -942,6 +947,7 @@
+@@ -963,6 +969,7 @@
# You probably need to add -lpam to EXTRALIBS, and in some releases of
# GNU/Linux -ldl is also needed.
#------------------------------------------------------------------------------
-@@ -950,7 +956,7 @@
+@@ -971,7 +978,7 @@
# If you may want to use outbound (client-side) proxying, using Socks5,
# uncomment the line below.
# If you may want to use inbound (server-side) proxying, using Proxy Protocol,
# uncomment the line below.
-@@ -1038,6 +1044,8 @@
+@@ -1069,6 +1076,8 @@
# CYRUS_SASLAUTHD_SOCKET=/var/state/saslauthd/mux
#------------------------------------------------------------------------------
# TCP wrappers: If you want to use tcpwrappers from within Exim, uncomment
-@@ -1343,6 +1351,7 @@
+@@ -1381,6 +1390,7 @@
# file can be specified here. Some installations may want something like this:
# PID_FILE_PATH=/var/lock/exim.pid
# If PID_FILE_PATH is not defined, Exim writes a file in its spool directory
# using the name "exim-daemon.pid".
-@@ -1376,6 +1385,7 @@
+@@ -1414,6 +1424,7 @@
# messages become "invisible" to the normal management tools.
# SUPPORT_MOVE_FROZEN_MESSAGES=yes
#------------------------------------------------------------------------------
-@@ -1414,3 +1424,6 @@
+@@ -1452,3 +1463,6 @@
# ENABLE_DISABLE_FSYNC=yes
# End of EDITME for Exim 4.
---- exim_monitor/EDITME 2017-02-12 00:58:50.000000000 +0000
-+++ EDITME.eximon 2017-02-12 14:19:40.765243359 +0000
+--- exim_monitor/EDITME 2018-03-15 20:22:06.000000000 +0000
++++ EDITME.eximon 2018-03-16 18:27:06.609171034 +0000
@@ -1,6 +1,7 @@
##################################################
# The Exim Monitor #
---- EDITME.exim4-light 2012-05-29 19:16:05.000000000 +0200
-+++ EDITME.exim4-light 2012-05-29 19:17:05.000000000 +0200
-@@ -697,13 +697,13 @@ SUPPORT_TLS=yes
+--- EDITME.exim4-light 2017-10-28 08:02:20.930695089 +0200
++++ EDITME.exim4-light 2017-10-28 08:03:25.433584564 +0200
+@@ -760,13 +760,13 @@ SUPPORT_TLS=yes
# Uncomment one of these settings if you are using OpenSSL; pkg-config vs not
# USE_OPENSSL_PC=openssl
-TLS_LIBS=-lgnutls
+# TLS_LIBS=-lgnutls
- # If you are running Exim as a server, note that just building it with TLS
- # support is not all you need to do. You also need to set up a suitable
+ # If using GnuTLS older than 2.10 and using pkg-config then note that Exim's
+ # build process will require libgcrypt-config to exist in your $PATH. A
lead you to the page.
</simpara>
</listitem>
- <listitem>
- <simpara>
- The Debian Exim 4 packages have their own
- <ulink url="http://pkg-exim4.alioth.debian.org">
- Home Page
- </ulink> which also links to a User FAQ.
- </simpara>
- </listitem>
<listitem>
<para>
The very extensive Upstream documentation is shipped
extremely flexible, allowing you to get exactly the amount of
control you need for the job at hand.
</para>
- <para>
- The <ulink url="http://pkg-exim4.alioth.debian.org"
- type="http">development web page</ulink> contains a lot of
- useful links and other information. The subversion repository
- of the Debian package is available for public read-only access
- and is linked from the development web page.
- </para>
<section> <title>Feature Sets in the daemon packages</title>
<para>
To use Exim 4, you need at least the following packages:
</section>
</section>
</section>
+ <section> <title>Notes on running SpamAssassin at SMTP time</title>
+ <para>
+ Exim can run
+ <ulink url="https://spamassassin.apache.org/">
+ SpamAssassin</ulink> while receiving a message by SMTP which
+ allows one to avoid acceptance of spam messages. The Debian
+ configuration contains some example code for running SpamAssassin,
+ but like all filtering this needs to be handled carefully.
+ </para>
+ <para>
+ SpamAssassin's default report should not be used in a add_header
+ statement since it contains empty lines. (This triggers e.g.
+ Amavis' warning "BAD HEADER SECTION, Improper folded header field
+ made up entirely of whitespace".) This is a safe, terse alternative:
+ <programlisting>
+ clear_report_template
+ report (_SCORE_ / _REQD_ requ) _TESTSSCORES(,)_ autolearn=_AUTOLEARN_
+ </programlisting>
+ </para>
+ <para>
+ Rejecting spam messages: Do not reject spam-messages received on
+ (non-spam) mailing lists, this can/will cause auto-unsubscription.
+ This also applies to messages received via forwarding services
+ (e.g. @debian.org addresses). If theses messages are rejected the
+ forwarding services will need to send a bounce address to the
+ spammer and will probably disable the forwarding if it happens all
+ the time. You will need to have some kind of whitelist to exclude
+ these hosts.
+ </para>
+ <para>
+ Security considerations: By default <command>spamd</command>
+ runs as root and changes uid/gid to the requested user to run
+ SpamAssassin. The example uses SpamAssassin default non-privileged
+ user (nobody) which prevents use of Bayesian filtering since this
+ requires persistent storage. You might want to setup a dedicated
+ user for exim spam scanning and use that one, either for a separate
+ SpamAssassin user profile or to run SpamAssassin as non-privileged
+ user.
+ </para>
+ </section>
</section>
<section> <title>Updating from Exim 3</title>
+++ /dev/null
-Packaging of upstream snapshots and GIT checkout.
-===========================
-Upstream keeps code and documentation in different CVS modules, which is
-why development snapshots do not contain any documentation (including a
-changelog). Since this does not make packagable software these parts have
-been copied over from exim-doc:
-
-mkdir doc/
-cp ~/GIT/exim-doc/doc-txt/* doc/
-cp ~/GIT/exim-doc/doc-docbook/{exim.8,spec.txt,filter.txt} doc/
-
-
-The latter files are built from git with
-
-make exim.8 spec.txt filter.txt
-
-which requires installation of xmlto xfpt docbook-xsl w3m
-plus this patch:
-
-diff --git a/doc-docbook/MyStyle-txt-html.xsl b/doc-docbook/MyStyle-txt-html.xsl
-index 284a99d..9a59c58 100644
---- a/doc-docbook/MyStyle-txt-html.xsl
-+++ b/doc-docbook/MyStyle-txt-html.xsl
-@@ -7,7 +7,7 @@ HTML output, and then imports my common stylesheet for HTML output. Then it
- adds an instruction to use "(c)" for copyright rather than the Unicode
- character. -->
-
--<xsl:import href="/usr/share/sgml/docbook/xsl-stylesheets-1.70.1/xhtml/docbook.xsl"/>
-+<xsl:import href="/usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl"/>
- <xsl:import href="MyStyle-html.xsl"/>
-
- <xsl:template name="dingbat.characters">
-exim4 (4.89-2+deb9u6~hcoop11) unstable; urgency=medium
+exim4 (4.92-8+deb10u3) buster-security; urgency=high
- * New upstream security release
+ * 78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch:
+ Fix buffer overflow in string_vformat.
- -- Clinton Ebadi <clinton@unknownlamer.org> Fri, 06 Sep 2019 14:23:08 -0400
+ -- Andreas Metzler <ametzler@debian.org> Fri, 27 Sep 2019 18:09:35 +0200
-exim4 (4.89-2+deb9u6) stretch-security; urgency=high
+exim4 (4.92-8+deb10u2) buster-security; urgency=high
- * 85_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch Fix SNI
+ * 78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch Fix SNI
related buffer overflow. CVE-2019-15846
- -- Andreas Metzler <ametzler@debian.org> Tue, 03 Sep 2019 20:01:38 +0200
+ -- Andreas Metzler <ametzler@debian.org> Tue, 03 Sep 2019 19:51:11 +0200
-exim4 (4.89-2+deb9u5) stretch-security; urgency=high
+exim4 (4.92-8+deb10u1) buster-security; urgency=high
* Fix remote command execution vulnerability related to
"${sort}"-expansion. CVE-2019-13917 OVE-20190718-0006
- -- Andreas Metzler <ametzler@debian.org> Sat, 20 Jul 2019 13:32:35 +0200
+ -- Andreas Metzler <ametzler@debian.org> Sat, 20 Jul 2019 13:35:58 +0200
-exim4 (4.89-2+deb9u4~hcoop10) unstable; urgency=medium
+exim4 (4.92-8) unstable; urgency=low
- * Rebuild on 4.89-2+deb9u4
+ * Pulled from exim-4.92+fixes branch:
+ + 75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch
+ Fix expansion of $tls_out_ocsp under hosts_request_ocsp.
+ + 75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch
+ When tls_verify_certificates was set to a directory instead of a file
+ exim/GnuTLS would still send out the list of accepted certificates,
+ This did not match documented behavior.
+ + 75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch
+ The dsn_from option was not used for DSN success messages.
+ * Pulled from upstream GIT master:
+ + 75_14-Fix-smtp-response-timeout.patch
+ Fix the timeout on smtp response to apply to the whole response instead
+ of resetting for every byte received.
+ + 75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch
+ https://bugs.exim.org/show_bug.cgi?id=2405
+ ${eval } was broken on 32bit archs.
- -- Clinton Ebadi <clinton@unknownlamer.org> Thu, 06 Jun 2019 19:35:28 -0400
+ -- Andreas Metzler <ametzler@debian.org> Sat, 08 Jun 2019 17:37:43 +0200
-exim4 (4.89-2+deb9u4) stretch-security; urgency=high
+exim4 (4.92-7) unstable; urgency=medium
- * Non-maintainer upload by the Security Team.
- * Fix remote command execution vulnerability (CVE-2019-10149)
+ * Upload to unstable.
- -- Salvatore Bonaccorso <carnil@debian.org> Tue, 28 May 2019 22:13:55 +0200
+ -- Andreas Metzler <ametzler@debian.org> Tue, 07 May 2019 19:44:23 +0200
-exim4 (4.89-2+deb9u3) stretch-security; urgency=high
+exim4 (4.92-6) experimental; urgency=medium
- * Non-maintainer upload by the Security Team.
- * Fix base64d() buffer size (CVE-2018-6789) (Closes: #890000)
+ * Revert 90_localscan_dlopen.dpatch removal to give Magnus some chance for
+ debugging sa-exim.
+ * Set HAVE_LOCAL_SCAN=yes in EDITME.
+ * Upload to experimental.
- -- Salvatore Bonaccorso <carnil@debian.org> Sat, 10 Feb 2018 09:26:05 +0100
+ -- Andreas Metzler <ametzler@debian.org> Tue, 16 Apr 2019 17:58:20 +0200
-exim4 (4.89-2+deb9u2) stretch-security; urgency=high
+exim4 (4.92-5) unstable; urgency=medium
- * Non-maintainer upload by the Security Team.
- * Avoid release of store if there have been later allocations
- (CVE-2017-16943) (Closes: #882648)
- * Chunking: do not treat the first lonely dot special (CVE-2017-16944)
- (Closes: #882671)
+ * Improved spam-scanning example with accompaning information in
+ README.Debian. Explicitly warn about adding the default SpamAssassin
+ report in a header, which Closes: #774553
+ * Drop 90_localscan_dlopen.dpatch. (It has been non-functional for a couple
+ of months.) Closes: #925982 Add a Conflicts for sa-exim, which relied on
+ the (working) version of the patch. Drop exim4-dev package. Add a NEWS
+ entry for this change.
+
+ -- Andreas Metzler <ametzler@debian.org> Sun, 07 Apr 2019 13:39:31 +0200
+
+exim4 (4.92-4) unstable; urgency=medium
+
+ * Another patch from exim-4.92+fixes branch:
+ 75_10-Harden-plaintext-authenticator.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Fri, 22 Mar 2019 07:15:20 +0100
+
+exim4 (4.92-3) unstable; urgency=medium
+
+ * Pull fixes from exim-4.92+fixes branch.
+ + 75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch
+ + 75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch
+ + 75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch
+ + 75_08-Logging-fix-initial-listening-on-log-line.patch
+ + 75_09-OpenSSL-Fix-aggregation-of-messages.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Wed, 20 Mar 2019 17:01:29 +0100
+
+exim4 (4.92-2) unstable; urgency=medium
+
+ * Upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org> Wed, 20 Feb 2019 19:23:11 +0100
+
+exim4 (4.92-1) experimental; urgency=medium
+
+ * Point watchfile to release directory again.
+ * New upstream stable release, identical to rc6 except for the version
+ string.
+ * Pull fixes from exim-4.92+fixes branch.
+ + 75_01-Fix-json-extract-operator-for-unfound-case.patch
+ + 75_02-Fix-transport-buffer-size-handling.patch
+ + 75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch
+ + 75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch
+ * Upload to experimental while waiting for rc6 to migrate.
+
+ -- Andreas Metzler <ametzler@debian.org> Sun, 17 Feb 2019 13:13:55 +0100
+
+exim4 (4.92~RC6-1) unstable; urgency=low
+
+ * New upstream snapshot rc6, includes
+ 40_01-Fix-dkim_verify_signers-option.-Bug-2366.patch.
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 09 Feb 2019 14:33:15 +0100
+
+exim4 (4.92~RC5-2) unstable; urgency=high
+
+ * In init script use start-stop-daemon directly instead of lsb-base's
+ killproc which currently fails to pass on the executable name to s-s-d
+ (921558). This broke with s-s-d 1.19.2 which (for security reasons)
+ requires further filtering arguments in addition to --pidfile when the pid
+ file is not owned by root. Closes: #921205
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 07 Feb 2019 18:42:41 +0100
+
+exim4 (4.92~RC5-1) unstable; urgency=medium
+
+ * New upstream snapshot rc5.
+ * 40_01-Fix-dkim_verify_signers-option.-Bug-2366.patch: dkim_verify_signers
+ was ignored.
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 31 Jan 2019 19:25:03 +0100
+
+exim4 (4.92~RC4-3) unstable; urgency=medium
+
+ * Refresh debian/upstream/signing-key.asc from
+ https://downloads.exim.org/Exim-Maintainers-Keyring.asc.
+ * Drop outdated pointers to alioth package homepage from README.Debian.
+ * Update exim4-config Breaks to enforce upgrade to daemon binary package
+ with DANE support. Closes: #919902
+ * [lintian] Minimize upstream/signing-key.asc.
+
+ -- Andreas Metzler <ametzler@debian.org> Sun, 20 Jan 2019 17:52:39 +0100
+
+exim4 (4.92~RC4-2) unstable; urgency=medium
+
+ * Upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 05 Jan 2019 15:35:38 +0100
+
+exim4 (4.92~RC4-1) experimental; urgency=low
+
+ * New upstream version.
+ + Drop 75_GnuTLS-repeat-lowlevel-read-and-write-operations-whi.patch.
+ + Unfuzz patches.
+
+ -- Andreas Metzler <ametzler@debian.org> Mon, 31 Dec 2018 13:13:45 +0100
+
+exim4 (4.92~RC3-1) unstable; urgency=low
+
+ * Add 75_GnuTLS-repeat-lowlevel-read-and-write-operations-whi.patch from
+ upstream GIT master, fixing outgoing TLS 1.3.
+ https://bugs.exim.org/show_bug.cgi?id=2359
+ * New upstream version.
+ * Upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org> Wed, 26 Dec 2018 16:07:52 +0100
+
+exim4 (4.92~RC2-1) experimental; urgency=low
+
+ * New upstream version.
+ + Drop 75_01-Fix-parsing-of-option-type-Kint-integer-stored-in-K-.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Tue, 18 Dec 2018 19:20:24 +0100
+
+exim4 (4.92~RC1-1) experimental; urgency=low
+
+ * Update upstream/signing-key.asc from
+ https://ftp.exim.org/pub/exim/Exim-Maintainers-Keyring.asc, adding
+ 96E4754B8F93C1B239F1A95785BCF7AC6735A680 while removing
+ 1F9C181B1E83D2099F02C95AC4F4F94804D29EBA and
+ FAA1C7F9CD077DC4304BC0C885AB833FDDC03262.
+ * New upstream release candidate:
+ + Point watchfile to test subdir.
+ + Update watchfile to handle -RC1 in addition to _RC1.
+ + Drop 75_fixes*.patch.
+ + Unfuzz 32_exim4.dpatch and 90_localscan_dlopen.dpatch
+ + Update configuration from upstream example, except for
+ tls_sni/tls_require_ciphers settings on remote_smtp_smarthost transport:
+ * Enable dns_dnssec_ok.
+ * Set dnssec_request_domains = * on dnslookup and
+ dnslookup_relay_to_domains routers.
+ * Set hosts_try_dane = */dnssec_request_domains = * on remote_smtp
+ transport unless REMOTE_SMTP_DISABLE_DANE is set.
+ * Set multi_domain on remote_smtp_smarthost transport.
+ * Post release updates:
+ + 75_01-Fix-parsing-of-option-type-Kint-integer-stored-in-K-.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 15 Dec 2018 16:24:54 +0100
+
+exim4 (4.91-9) unstable; urgency=low
+
+ * Run "wrap-and-sort --max-line-length=72 --short-indent" and add back
+ autodeleted comments.
+ * Update from exim-4_91+fixes branch:
+ + 75_fixes_26-Fix-bad-use-of-library-copying-string-over-itself.patch
+ + 75_fixes_27-Fix-cyrus-sasl-authenticator-for-authenticated_fail_.patch
+ + 75_fixes_28-Avoid-leaving-domain-live-with-bogus-info-during-ser.patch
+ + 75_fixes_29-Fix-AUTH_GSASL-build.patch
+ + 75_fixes_30-Harden-string-list-handling.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 06 Dec 2018 19:19:38 +0100
+
+exim4 (4.91-8) unstable; urgency=low
+
+ [ Andreas Metzler ]
+ * Update from exim-4_91+fixes branch:
+ + 75_fixes_18-Restore-Darwin-OS-configuration.patch
+ + 75_fixes_20-Fix-filter-noerror-command.-Bug-2318.patch
+ + 75_fixes_21-DANE-fix-TA-mode-verify-under-GnuTLS.-Bug-2311.patch
+ + 75_fixes_22-Testsuite-track-newer-GnuTLS-behaviour.patch
+ + 75_fixes_24-DANE-ignore-undersized-TLSA-records.patch
+ + 75_fixes_25-Logging-do-not-log-a-missing-proxy-address-on-delive.patch
+
+ [ Marc Haber ]
+ * Move definition of CHECK_RCPT_*_LOCALPARTS macro to acl file proper.
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 29 Sep 2018 19:08:52 +0200
+
+exim4 (4.91-7) unstable; urgency=low
+
+ * Update from exim-4_91+fixes branch:
+ + 75_fixes_16-Fix-non-EVENTS-build.patch
+ + 75_fixes_17-Fix-cutthrough-delivery-for-more-than-one-iteration-.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Sun, 26 Aug 2018 11:33:15 +0200
+
+exim4 (4.91-6) unstable; urgency=low
+
+ * Update from exim-4_91+fixes branch:
+ + 75_fixes_13-DKIM-Fix-signing-for-body-lines-starting-with-a-pair.patch
+ + 75_fixes_14-ARC-Fix-verification-to-do-AS-checks-in-reverse-orde.patch
+ + 75_fixes_15-I18N-Fix-protocol-recorded-for-a-multi-SMTPUTF8-mess.patch
+ * [lintian] Do not run mininal testsuite with DEB_BUILD_OPTIONS=nocheck.
+ (override_dh_auto_test-does-not-check-DEB_BUILD_OPTIONS)
+
+ -- Andreas Metzler <ametzler@debian.org> Fri, 20 Jul 2018 11:21:24 +0200
+
+exim4 (4.91-5) unstable; urgency=medium
+
+ * Update from exim-4_91+fixes branch:
+ + 75_fixes_10-Use-serial-number-1-for-self-generated-selfsigned-ce.patch
+ + 75_fixes_11-Fix-logging-of-cmdline-args-when-starting-in-an-unli.patch
+ + 75_fixes_12-ARC-Fix-signing-for-case-when-DKIM-signing-failed.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 09 Jun 2018 18:10:39 +0200
+
+exim4 (4.91-4) unstable; urgency=medium
+
+ * Update from exim-4_91+fixes branch:
+ + 75_fixes_06-Cutthrough-fix-race-resulting-in-duplicate-delivery..patch
+ + 75_fixes_07-tidying.patch
+ + 75_fixes_08-ARC-fix-crash-on-signing-with-missing-key-file.patch
+ + 75_fixes_09-Content-scanning-Fix-locking-on-message-spool-files..patch
+ * [lintian] Delete trailing empty lines in changelog.
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 17 May 2018 17:14:53 +0200
+
+exim4 (4.91-3) unstable; urgency=medium
+
+ * Update from exim-4_91+fixes branch:
+ + 75_fixes_01-Belated-README.UPDATING-notes-for-Exim-4.91.patch
+ + 75_fixes_02-Avoid-doing-logging-in-signal-handlers.-Bug-1007.patch
+ + 75_fixes_03-Fix-typo-in-arc.-Bug-2262.patch
+ + 75_fixes_04-Fix-OpenSSL-non-OCSP-build.patch
+ + 75_fixes_05-DKIM-enforce-limit-of-20-on-received-DKIM-Signature-.patch
+ + Move 50_localscan_dlopen.dpatch to end of patch series and rename to
+ 90_... to preserve alphanumeric patch ordering.
+ * Add log_message for local blacklists to improve log readability. (Patch by
+ Dominic Hargreaves).
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 28 Apr 2018 14:59:36 +0200
+
+exim4 (4.91-2) unstable; urgency=low
+
+ * Upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 21 Apr 2018 10:38:50 +0200
+
+exim4 (4.91-1) experimental; urgency=medium
+
+ * Point watchfile to release directory again and use downloads.exim.org
+ host.
+ * New upstream version.
+ * Tighten b-d on libgnutls28-dev to >= 3.5.7, earlier Debian packages did
+ not ship libgnutls-dane0.
+
+ -- Andreas Metzler <ametzler@debian.org> Sun, 15 Apr 2018 17:52:05 +0200
+
+exim4 (4.91~RC4-1) experimental; urgency=medium
+
+ * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org> Mon, 09 Apr 2018 19:25:18 +0200
+
+exim4 (4.91~RC3-1) experimental; urgency=medium
+
+ * New upstream version.
+ * Point vcs* to salsa.
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 05 Apr 2018 19:43:39 +0200
+
+exim4 (4.91~RC2-1) experimental; urgency=medium
+
+ * New upstream version.
+ Drop 75_01-Fix-heavy-pipeline-SMTP-command-input-corruption.-Bu.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Wed, 21 Mar 2018 19:25:44 +0100
+
+exim4 (4.91~RC1-1) experimental; urgency=medium
+
+ * Point watchfile to test subdirectory.
+ * New upstream version:
+ + Drop debian/patches/75_*.
+ + Update example.conf.md5.
+ Upstream now enables verify = header_syntax check in default config,
+ mirror this change in Debian, introduce
+ NO_CHECK_DATA_VERIFY_HEADER_SYNTAX macro to override this.
+ * Build with newly available (well, for GnuTLS) DANE support.
+ * Pull 75_01-Fix-heavy-pipeline-SMTP-command-input-corruption.-Bu.patch from
+ upstream master, fixing https://bugs.exim.org/show_bug.cgi?id=2250.
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 17 Mar 2018 17:41:51 +0100
+
+exim4 (4.90.1-5) unstable; urgency=medium
+
+ * Update from exim-4_90+fixes branch:
+ 75_15-Pipe-transport-part-two.-Bug-2257.patch
+ 75_16-Fix-spool_wireformat-final-dot-on-LMTP-transport.-Bu.patch
+ 75_17-Cutthrough-enforce-non-use-in-combination-with-DKIM-.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 31 Mar 2018 07:14:31 +0200
+
+exim4 (4.90.1-4) unstable; urgency=medium
+
+ * Update from exim-4_90+fixes branch:
+ 75_11-DMARC-add-variables-to-list-of-those-now-unused-at-t.patch
+ 75_12-Fix-heavy-pipeline-SMTP-command-input-corruption.-Bu.patch
+ 75_13-Unbreak-DMARC.patch
+ 75_14-Fix-pipe-transport-to-not-use-a-socket-only-syscall..patch
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 22 Mar 2018 07:44:05 +0100
+
+exim4 (4.90.1-3) unstable; urgency=medium
+
+ * Update from exim-4_90+fixes branch:
+ 75_07-Fix-ldap-lookups-for-zero-length-attribute-value.-Bu.patch
+ 75_08-Mark-variables-unused-before-release-of-store-in-the.patch
+ 75_09-Mark-variables-unused-before-release-of-store-in-the.patch
+ 75_10-Mark-variables-that-are-unused-before-release-of-sto.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Fri, 16 Mar 2018 18:35:01 +0100
+
+exim4 (4.90.1-2) unstable; urgency=medium
+
+ * Update from exim-4_90+fixes branch:
+ 75_01-ACL-Enforce-non-usability-of-control-utf8_downconver.patch
+ 75_02-Fix-memory-leak-during-multi-message-reception-using.patch
+ 75_03-OpenSSL-Fix-memory-leak-during-multi-message-connect.patch
+ 75_04-Fix-exim_dbmbuild-to-permit-directoryless-filenames..patch
+ 75_05-OpenSSL-revert-needless-free-of-certificate-list.-Th.patch
+ 75_06-I18N-Fix-utf8_downconvert-propagation-through-a-redi.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 10 Mar 2018 14:25:51 +0100
+
+exim4 (4.90.1-1) unstable; urgency=high
+
+ * New upstream version, fixing CVE-2018-6789. Closes: #890000
+ + Drop 75_*.patch.
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 10 Feb 2018 13:45:40 +0100
+
+exim4 (4.90-7) unstable; urgency=medium
+
+ * Update from exim-4_90+fixes branch. (exim-4.90.0.27)
+ + 75_21-DKIM-fix-buffer-overflow-in-verify.patch
+ + 75_22-Repair-Heimdal-GSSAPI-authenticator-init.patch
+ + 75_23-Repair-Heimdal-GSSAPI-authenticator-init-part-2.patch
+ * Typo fixes in old patch descriptions. (Thanks, lintian!)
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 10 Feb 2018 13:13:37 +0100
+
+exim4 (4.90-6) unstable; urgency=medium
+
+ * Update from exim-4_90+fixes branch.
+ + 75_17-Cutthrough-fix-for-port-number-defined-by-router.-Bu.patch
+ + 75_18-GnuTLS-fix-to-ignore-timeout-on-unrelated-callout-co.patch
+ Closes: #887489
+ + 75_19-Build-.git-may-be-a-file-when-this-repo-is-a-submodu.patch
+ + 75_20-Debugging-fix-potential-null-derefs-in-DSN-debug_pri.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Wed, 07 Feb 2018 19:37:03 +0100
+
+exim4 (4.90-5) unstable; urgency=low
+
+ * Add 75_16-Cutthrough-fix-multi-message-initiating-connections.patch from
+ exim-4_90+fixes branch.
+ * Improved exim4-daemon-custom documentation by Gedalya. Closes: #887971
+ * [update-exim4.conf] stop converting variables set to an empty value in
+ /etc/exim4/update-exim4.conf.conf to exim macros with a literal value of
+ "empty" in the generated configuration. Thanks, Gedalya. Closes: #887972
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 27 Jan 2018 17:00:42 +0100
+
+exim4 (4.90-4) unstable; urgency=low
+
+ * Update from exim-4_90+fixes branch.
+ 75_13-Lookups-fix-mysql-lookup-returns-for-no-data-queries.patch
+ 75_14-Fix-D-string-expansion-to-not-use-millisec.patch
+ 75_15-DKIM-DNS-records-having-no-v-tag-are-acceptable.-Bug.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 20 Jan 2018 08:00:45 +0100
+
+exim4 (4.90-3) unstable; urgency=medium
+
+ * Three more patches from exim-4_90+fixes branch:
+ 75_10-Fix-issue-with-continued-connections-when-the-DNS-sh.patch
+ 75_11-MIME-ACL-fix-SMTP-response-for-non-accept-result-of-.patch
+ 75_12-DKIM-permit-dkim_private_key-to-override-dkim_strict.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Mon, 08 Jan 2018 18:55:28 +0100
+
+exim4 (4.90-2) unstable; urgency=medium
+
+ * Update to exim-4_90+fixes branch:
+ + Replace 75_Lookups-fix-pgsql-multiple-row-single-column-return.patch.
+ + 75_01-TLS-Fix-excessive-calling-of-smtp_auth_acl-under-AUT.patch
+ + 75_02-TLS-avoid-calling-smtp_auth_acl-on-client-cert-when-.patch
+ + 75_03-Debug-fix-coding-in-dnssec-reporting.-Bug-2205.patch
+ + 75_04-DKIM-Ignore-non-DKIM-TXT-records-in-DNS-response.-Bu.patch
+ + 75_05-Fix-build-of-nisplus-lookup.patch
+ + 75_06-Fix-const-issue-in-nisplus-lookup.patch
+ + 75_08-DKIM-tighter-checking-while-parsing-signature-header.patch
+ + 75_09-Fix-crash-associated-with-dnsdb-lookup-done-from-DKI.patch
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 30 Dec 2017 15:43:52 +0100
+
+exim4 (4.90-1) unstable; urgency=low
+
+ * rc4 released as 4.90.
+ * Point watchfile to release directory again.
+ * 75_Lookups-fix-pgsql-multiple-row-single-column-return.patch from upstream
+ GIT master branch. Fix pgsql lookup for multiple result-tuples with a
+ single column. Previously only the last row was returned.
+ https://lists.exim.org/lurker/message/20171223.102237.a53dd5bd.en.html
+ * Simplify debian/rules and make it usable with dh v10 compat. The
+ fine-grained support for selecting the to be built packages (-custom with
+ or without -base) was dropped. The build process is now controlled by
+ attaching tasks to dh-override hooks instead of using file dependencies,
+ makefile-style. The latter broke with dh v10 due to upstream's
+ build-system which always has the main targets out-of-date inter alia due
+ to the compile-number feature.
+ * Use hardening=+all instead of hardening=+bindnow,+pie. (Does not change
+ buildflags ATM.)
+ * Use debhelper v10 compat.
+ * Drop override_dh_strip-arch, we have had enough toolchain and
+ source changes to prevent file conflicts.
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 28 Dec 2017 13:42:23 +0100
+
+exim4 (4.90~RC4-1) unstable; urgency=medium
+
+ * New upstream version.
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 14 Dec 2017 18:11:40 +0100
+
+exim4 (4.90~RC3-2) unstable; urgency=low
+
+ * Upload to unstable.
+ * Point homepage to https URL.
- -- Salvatore Bonaccorso <carnil@debian.org> Tue, 28 Nov 2017 22:58:00 +0100
+ -- Andreas Metzler <ametzler@debian.org> Sat, 02 Dec 2017 17:37:13 +0100
+
+exim4 (4.90~RC3-1) experimental; urgency=medium
+
+ * New upstream version.
+ + Fix a use-after-free while reading smtp input for header lines.
+ A crafted sequence of BDAT commands could result in in-use memory
+ being freed. CVE-2017-16943. Closes: #882648
+ + Fix checking for leading-dot on a line during headers reading
+ from SMTP input. Previously it was always done; now only done for
+ DATA and not BDAT commands. CVE-2017-16944 Closes: #882671
+ * Drop 78_Disable-chunking-BDAT-by-default.patch again.
+
+ -- Andreas Metzler <ametzler@debian.org> Fri, 01 Dec 2017 19:14:08 +0100
+
+exim4 (4.90~RC2-3) experimental; urgency=medium
+
+ * As a workaround for the yet-unfixed security vulnerability resurrect (and
+ adapt for 4.90) 78_Disable-chunking-BDAT-by-default.patch (dropped in
+ 4.89-4) to disable both incoming and outgoing BDAT/CHUNKING. #882648
+ https://lists.exim.org/lurker/message/20171125.034842.d1d75cac.en.html
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 25 Nov 2017 12:01:40 +0100
+
+exim4 (4.90~RC2-2) experimental; urgency=low
+
+ * B-d on lynx, instead of lynx-cur | lynx.
+
+ -- Andreas Metzler <ametzler@debian.org> Fri, 17 Nov 2017 17:03:10 +0100
+
+exim4 (4.90~RC2-1) experimental; urgency=low
+
+ * New upstream release candidate.
+ + Unfuzz patches, drop 40_reproducible_build.diff and
+ 75_fix_ftbfs_SOURCE_DATE_EPOCH.diff.
+ + Refresh debian/example.conf.md5, No changes to Debian's configuration
+ needed, upstream added a (commented) entry to change OpenSSL ciphers.
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 16 Nov 2017 19:40:35 +0100
+
+exim4 (4.90~RC1-1) experimental; urgency=low
+
+ * New upstream release candidate.
+ + Point watchfile to test subdirectory.
+ + Update 40_reproducible_build.diff
+ + Drop 75_fixes*.patch and
+ 80_Repair-manualroute-transport-name-not-last-option.patch.
+ + Unfuzz EDITME*.diff
+ + 75_fix_ftbfs_SOURCE_DATE_EPOCH.diff Fix build-error when
+ SOURCE_DATE_EPOCH is set.
+ * Drop trailing whitespace in debian/README.source, debian/changelog and
+ debian/rules. (Thanks, lintian)
+ * Drop debian/README.source and outdated parts of debian/copyright.
+
+ -- Andreas Metzler <ametzler@debian.org> Sun, 29 Oct 2017 10:52:30 +0100
+
+exim4 (4.89-13) unstable; urgency=high
+
+ * 75_fixes_21-Chunking-do-not-treat-the-first-lonely-dot-special.-.patch
+ from exim-4_89+fixes branch. Closes: #882671 CVE-2017-16944
+
+ -- Andreas Metzler <ametzler@debian.org> Wed, 29 Nov 2017 19:30:37 +0100
+
+exim4 (4.89-12) unstable; urgency=high
+
+ * Sync with exim-4_89+fixes branch:
+ + 75_fixes_19-Fix-mariadb-mysql-macro-confusion.patch
+ + 75_fixes_20-Avoid-release-of-store-if-there-have-been-later-allo.patch
+ Closes: #882648 (use-after-free, remote-code-execution) CVE-2017-16943
+ * Update EDITME* for 75_fixes_19-Fix-mariadb-mysql-macro-confusion.patch.
+
+ -- Andreas Metzler <ametzler@debian.org> Tue, 28 Nov 2017 20:04:23 +0100
+
+exim4 (4.89-11) unstable; urgency=critical
+
+ * B-d on lynx, instead of lynx-cur | lynx.
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 25 Nov 2017 13:02:43 +0100
+
+exim4 (4.89-10) unstable; urgency=critical
+
+ * As a workaround for the yet-unfixed security vulnerability resurrect
+ 78_Disable-chunking-BDAT-by-default.patch (dropped in 4.89-4) to disable
+ both incoming and outgoing BDAT/CHUNKING. #882648
+ https://lists.exim.org/lurker/message/20171125.034842.d1d75cac.en.html
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 25 Nov 2017 11:43:24 +0100
+
+exim4 (4.89-9) unstable; urgency=medium
+
+ * Upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org> Fri, 27 Oct 2017 19:23:25 +0200
+
+exim4 (4.89-8) experimental; urgency=low
+
+ * Sync with exim-4_89+fixes branch:
+ 75_fixes_17-Fix-queue_run_in_order-to-ignore-the-PID-portion-of-.patch
+ 75_fixes_18-Use-safer-routine-for-possibly-overlapping-copy.patch
+ * Point watchfile to https site.
+
+ -- Andreas Metzler <ametzler@debian.org> Mon, 23 Oct 2017 19:14:24 +0200
+
+exim4 (4.89-7) unstable; urgency=low
+
+ * In debian/rules' manually called update-mtaconflicts target use
+ grep-aptavail instead of hard-coding /var/lib/apt/lists/.
+ (Thanks, Julian Andres Klode) Closes: #874772
+ * Update debian/mtalist.
+ * Sync with exim-4_89+fixes branch:
+ 75_fixes_13-Document-CVE-assignment-for-Berkeley-DB-issue.patch
+ 75_fixes_14-DKIM-fix-signing-bug-induced-by-total-size-of-parame.patch
+ 75_fixes_15-SOCKS-fix-unitialized-pointer.patch
+ 75_fixes_16-Fix-crash-in-transport-on-second-smtp-connect-fail-f.patch.
+
+ -- Andreas Metzler <ametzler@debian.org> Wed, 27 Sep 2017 07:35:23 +0200
+
+exim4 (4.89-6) unstable; urgency=medium
+
+ * Use "runuser --command ..." instead of "su - --command ..." in
+ exim4-base.cron.daily to avoid invoking pam_systemd. Closes: #871688
+ (Thanks, Jakobus Schürz)
+ * Sync priorities with override file: exim4{,-base,-config,-daemon-light}
+ optional from standard, exim4-dev optional from extra.
+ * In debian/rules when setting up the build-tree for -custom also copy
+ EDITME.eximon to allow building based on EDITME.exim4-light with eximon
+ building *not* disabled. (Thanks, Marko von Oppen) Closes: #783813
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 09 Sep 2017 15:29:39 +0200
+
+exim4 (4.89-5) unstable; urgency=medium
+
+ * Update to exim-4_89+fixes branch:
+ 75_fixes_01-Start-exim-4_89-fixes-to-cherry-pick-some-commits-fr.patch
+ 75_fixes_02-Cleanup-prevent-repeated-use-of-p-oMr-to-avoid-mem-l.patch
+ (replaces 79_CVE-2017-1000369.patch)
+ 75_fixes_03-Fix-log-line-corruption-for-DKIM-status.patch (replaces
+ 81_Fix-log-line-corruption-for-DKIM-status.patch)
+ 75_fixes_04-Openssl-disable-session-tickets-by-default-and-sessi.patch
+ 75_fixes_05-Transport-fix-smtp-under-combo-of-mua_wrapper-and-li.patch
+ 75_fixes_07-Openssl-disable-session-tickets-by-default-and-sessi.patch
+ 75_fixes_08-Transport-fix-smtp-under-combo-of-mua_wrapper-and-li.patch
+ 75_fixes_09-Use-the-BDB-environment-so-that-a-database-config-fi.patch
+ (CVE-2017-10140)
+ 75_fixes_10-Fix-cache-cold-random-callout-verify.-Bug-2147.patch
+ 75_fixes_11-On-callout-avoid-SIZE-every-time-but-noncacheable-rc.patch
+ 75_fixes_12-Fix-build-for-earlier-version-Berkeley-DB.patch
+ * Simplify debian/rules by including buildflags.mk unconditionally which was
+ introduced in dpkg 1.16.1 released in October 2011.
+ * Use pkg-info.mk to get package-version, upstream-version and
+ SOURCE_DATE_EPOCH. For the latter fall back to current time if it is not
+ provided by pkg-info.mk.
+ * [lintian] In *daemon.postinst use which certtool instead of
+ [ -x /usr/bin/certtool ] to check for availablility of the command.
+
+ -- Andreas Metzler <ametzler@debian.org> Thu, 10 Aug 2017 10:17:05 +0200
+
+exim4 (4.89-4) unstable; urgency=low
+
+ * 80_Repair-manualroute-transport-name-not-last-option.patch from GIT
+ master: Starting with 4.85 a transport name needed to specified after
+ options in route_list. Closes: #865287
+ * Add 81_Fix-log-line-corruption-for-DKIM-status.patch from GIT master.
+ * Drop 78_Disable-chunking-BDAT-by-default.patch, enable BDAT/Chunking by
+ default.
+ * Standards-Version: 4.0.0
+ + Do not check for availability of invoke-rc.d, use it always and do not
+ fall back to invoking the init-script directly.
+ + Drop eximon menu file.
+ * Migrate to automatic debug packages. Bump b-d on debhelper since
+ --dbgsym-migration was introduced in debhelper 9.20160114.
+
+ -- Andreas Metzler <ametzler@debian.org> Sat, 15 Jul 2017 12:46:16 +0200
+
+exim4 (4.89-3) unstable; urgency=high
+
+ * Re-upload to unstable.
+
+ -- Andreas Metzler <ametzler@debian.org> Mon, 19 Jun 2017 18:51:13 +0200
exim4 (4.89-2+deb9u1) stretch-security; urgency=medium
+Drop included patches.
(-72_0001-Guard-routing-against-a-null-deref.-Bug-1639.patch,
72_0002-Spamd-add-missing-initialiser.-Rspamd-mode-was-incor.patch,
- 72_0003-DSN-fix-null-deref-when-bounce-is-due-to-conn-timeou.patch,
+ 72_0003-DSN-fix-null-deref-when-bounce-is-due-to-conn-timeou.patch,
72_0004-Content-scan-Use-ETIMEDOUT-not-ETIME-as-having-bette.patch)
* Sync Debian config with upstream default config:
+ Set prdr_enable.
86_Dovecot-robustness.diff 87_localinjected_mimeacl.diff), unfuzz patches.
* Applying upstream's default configuration updates to Debian configuration
change 30_exim4-config_examples to use tls_in_cipher/tls_out_cipher
- instead of tls_out_cipher. - exim4-config therefore Breaks
+ instead of tls_out_cipher. - exim4-config therefore Breaks
exim daemon << 4.82~rc1.
* 80_addmanuallybuiltdocs.diff: Upstream rc tarball ships empty filter.txt
and spec.txt, replace these with correct handbuilt versions.
"match_ip" & "match_local_part". Named lists can still be used. The
previous behavior made it too easy to create (remotely) vulnerable
configurations. A more detailed rationale and explanation can be found
- on
+ on
https://lists.exim.org/lurker/message/20111003.122326.fbcf32b7.en.html
+ doc/pcrepattern.txt is not shipped anymore as part of the exim tarball
(and therefore the Debian package suite.)
+ Drop exim4-config's conflicts with bash (<< 2.05). This was relevant
pre-sarge.
+ Drop exim4-daemon-* dependency on exim4-base (>> 4.71-2). This one is
- superfluous because of of the dependency on
+ superfluous because of of the dependency on
exim4-base (>= ${Upstream-Version}).
+ exim4-config breaks instead of conflicts with pre-DKIM (i.e. << 4.69.1)
exim4-daemon.
+ exim4-base breaks instead of conflicts with <<${Upstream-Version} daemon
packages.
* Add Vcs-Svn and Vcs-Browser fields to debian/control.
- * Build depend on libmysqlclient-dev | libmysqlclient15-dev instead of
+ * Build depend on libmysqlclient-dev | libmysqlclient15-dev instead of
libmysqlclient15-dev. libmysqlclient-dev is not a virtual package
anymore. Closes: #590218
* Use db_settitle unconditionally, even etch supports this. Drop unneeded
Thanks to Fabien André. Closes: #578176
* Re-work config.autogenerated header to more exactly reflect
configuration source. (mh) Closes: #593984
-
+
[ Andreas Metzler ]
* Fix getopt invocation to make update-exim4.conf.template -o work. (Thank
you Matthew W. S. Bell) Closes: #590333
exim4 (4.72-1) unstable; urgency=low
- * New upstream release. (Identical to the git snapshot previously
+ * New upstream release. (Identical to the git snapshot previously
uploaded to experimental.)
-- Andreas Metzler <ametzler@debian.org> Thu, 03 Jun 2010 17:42:52 +0200
* New upstream cvs snapshot.
+ Drop unnecessary patches: 36_pcre 37_exiwhatpsmisc.
+ Close dovecot socket after wrong password was given. Closes: #515503
- + Standalone DKIM support. Obsoletes and therefore
+ + Standalone DKIM support. Obsoletes and therefore
Closes: #486437,#459883
* Drop upstream URL from package descriptions. Closes: #471425
* [patches/00_unpack.dpatch] Drop workaround for tar 1.14, even oldstable
* [exim4 init-script]. Modify check for smtp inetd entry to use an anchored
pattern, matching "smtp" but not "smtp-foo". Closes: #516146
* exim4-daemon-light now Provides: default-mta. See #508644.
- * Ship both transport-filter.pl and ratelimit.pl in
+ * Ship both transport-filter.pl and ratelimit.pl in
/usr/share/doc/exim4-base/examples. Closes: #518836
* [lintian] Add ${misc:Depends} to all Depends.
* [lintian] Add override for dbg-package-missing-depends exim4-dbg.
exim4 (4.69-7) unstable; urgency=low
[ Andreas Metzler ]
- * Sync from ubuntu: Refer to spec.txt.gz instead of spec.txt in
+ * Sync from ubuntu: Refer to spec.txt.gz instead of spec.txt in
README.Debian.xml.
[ Debconf translations ]
in daily cron job. Thanks to Justin Pryzby. Closes: #476541
* Move docs from Apps/Net to Network/Communication
* linda R.I.P.
-
+
[ Robert Millan ]
* Process acl_local_deny_exceptions ACL before rejecting a message in SPF
check. Thanks to Miklos Szeredi. Closes: #451633
[ Andreas Metzler ]
- * Fix typos in exinext's man page (/s/eximnext/exinext/). (Thanks,
+ * Fix typos in exinext's man page (/s/eximnext/exinext/). (Thanks,
Filipus Klutiero) Closes: #471113
* exiwhat: Check at runtime whether killall is available. Fall back to a
combination of 'ps ax' and regular kill otherwise.
exim4 (4.67-5) unstable; urgency=low
* the "verderben viele Koeche den Brei?" release
-
+
[ Andreas Metzler ]
* Point to exim4_passwd(5) instead of non-existing exim_passwd(5) in AUTH
section of configuration. (Thanks Arkadiusz Dykiel, #430149)
- better documentation about differences in configuring for GnuTLS or
OpenSSL. (Closes: #241725)
- verify = header_sender now respects callout options. (Closes: #260114)
- - There is now an overall timeout for performing a callout verification.
+ - There is now an overall timeout for performing a callout verification.
(Closes: #261511)
- Less typos in filter.txt. (Closes: #230545)
- New ACL: acl_smtp_predata, useful for greylisting. (Closes: #237947)
* Sorry, this is not 4.23. Tom is on holidays and because 4.23 changes
some ACL code, exiscan needs in depth checking and not just applying the
- patch by hand.
+ patch by hand.
* exim4-config conflicts with bash (<< 2.05), because it cannot handle
aliases in functions. This does not necessarily fix dist-upgrades
from potato to sarge because debconf-config might happen before the
* remove the %s from PID_FILE_PATH
* apply debian/fix-pid.issue.patch to fix minor security issue
http://www.exim.org/pipermail/exim-users/Week-of-Mon-20021202/046978.html
- * test in init-script for working config before reloading/restarting
+ * test in init-script for working config before reloading/restarting
(Andreas Piesk)
-- Andreas Metzler <ametzler@downhill.at.eu.org> Thu, 5 Dec 2002 13:04:51 +0100
* debian/control: Short description improved (Closes: #130698)
-- Mark Baker <mark@mnb.org.uk> Mon, 4 Mar 2002 23:04:52 +0000
-
-
Section: mail
Priority: standard
Maintainer: Exim4 Maintainers <pkg-exim4-maintainers@lists.alioth.debian.org>
-Uploaders: Andreas Metzler <ametzler@debian.org>,Marc Haber <mh+debian-packages@zugschlus.de>
-Homepage: http://www.exim.org/
-Standards-Version: 3.9.8
-Vcs-Git: https://anonscm.debian.org/git/pkg-exim4/exim4.git
-Vcs-Browser: https://anonscm.debian.org/git/pkg-exim4/exim4.git
-Build-Depends: debhelper (>= 9), po-debconf, docbook-xsl, xsltproc,
- lynx-cur | lynx, docbook-xml, libpcre3-dev, libldap2-dev, libpam0g-dev,
- libident-dev, libdb5.3-dev, libxmu-dev, libxt-dev, libxext-dev, libx11-dev,
- libxaw7-dev, libpq-dev, default-libmysqlclient-dev,
- libsqlite3-dev, libperl-dev, libgnutls28-dev, libsasl2-dev
+Uploaders:
+ Andreas Metzler <ametzler@debian.org>,
+ Marc Haber <mh+debian-packages@zugschlus.de>
+Homepage: https://www.exim.org/
+Standards-Version: 4.3.0
+Vcs-Git: https://salsa.debian.org/exim-team/exim4.git
+Vcs-Browser: https://salsa.debian.org/exim-team/exim4
+Build-Depends:
+ debhelper (>= 10),
+ default-libmysqlclient-dev,
+ docbook-xml,
+ docbook-xsl,
+ libdb5.3-dev,
+ libgnutls28-dev (>= 3.5.7),
+ libident-dev,
+ libldap2-dev,
+ libpam0g-dev,
+ libpcre3-dev,
+ libperl-dev,
+ libpq-dev,
+ libsasl2-dev,
+ libsqlite3-dev,
+ libx11-dev,
+ libxaw7-dev,
+ libxext-dev,
+ libxmu-dev,
+ libxt-dev,
+ lynx,
+ po-debconf,
+ xsltproc
Package: exim4-base
Architecture: any
-Breaks: exim4-daemon-light (<<${Upstream-Version}),
- exim4-daemon-heavy (<<${Upstream-Version}),
- exim4-daemon-custom (<<${Upstream-Version})
+Priority: optional
+Breaks:
+ exim4-daemon-custom (<<${Upstream-Version}),
+ exim4-daemon-heavy (<<${Upstream-Version}),
+ exim4-daemon-light (<<${Upstream-Version})
Conflicts: exim, exim-tls
-Replaces: exim, exim-tls, exim4-daemon-light, exim4-daemon-heavy, exim4-daemon-custom
-Depends: ${shlibs:Depends}, ${misc:Depends},
+Replaces:
+ exim,
+ exim-tls,
+ exim4-daemon-custom,
+ exim4-daemon-heavy,
+ exim4-daemon-light
+Depends:
+ adduser,
cron | cron-daemon | anacron,
- exim4-config (>=4.82) | exim4-config-2, adduser, netbase, lsb-base (>= 3.0-6)
+ exim4-config (>=4.82) | exim4-config-2,
+ lsb-base (>= 3.0-6),
+ netbase,
+ ${misc:Depends},
+ ${shlibs:Depends}
# psmisc just for exiwhat.
-Recommends: psmisc, mailx
-Suggests: mail-reader, eximon4, exim4-doc-html|exim4-doc-info,
- gnutls-bin | openssl, file, spf-tools-perl, swaks
+Recommends: mailx, psmisc
+Suggests:
+ exim4-doc-html | exim4-doc-info,
+ eximon4,
+ file,
+ gnutls-bin | openssl,
+ mail-reader,
+ spf-tools-perl,
+ swaks
Description: support files for all Exim MTA (v4) packages
Exim (v4) is a mail transport agent. exim4-base provides the support
files needed by all exim4 daemon packages. You need an additional package
Package: exim4-config
Architecture: all
-Breaks: exim4-daemon-light (<< 4.87~RC5), exim4-daemon-heavy (<< 4.87~RC5)
+Priority: optional
+Breaks:
+ exim4-daemon-heavy (<< 4.91~RC1),
+ exim4-daemon-light (<< 4.91~RC1)
Provides: exim4-config-2
-Conflicts: exim, exim-tls, exim4-config, exim4-config-2, ${MTA-Conflicts}
-Depends: ${shlibs:Depends}, ${misc:Depends}, adduser
+Conflicts:
+ exim,
+ exim-tls,
+ exim4-config,
+ exim4-config-2,
+ ${MTA-Conflicts}
+Depends: adduser, ${misc:Depends}, ${shlibs:Depends}
Description: configuration for the Exim MTA (v4)
Exim (v4) is a mail transport agent. exim4-config provides the configuration
for the exim4 daemon packages. The configuration framework has been split
Package: exim4-daemon-light
Architecture: any
-Provides: mail-transport-agent, exim4-localscanapi-2.0,
+Priority: optional
+Provides:
+ exim4-localscanapi-2.0,
+ mail-transport-agent,
${dist:Provides:exim4-daemon-light}
Conflicts: mail-transport-agent
-Replaces: mail-transport-agent, exim4-base (<= 4.61-1)
-Depends: exim4-base (>= ${Upstream-Version}), ${shlibs:Depends}, ${misc:Depends}
+Replaces: exim4-base (<= 4.61-1), mail-transport-agent
+Depends:
+ exim4-base (>= ${Upstream-Version}),
+ ${misc:Depends},
+ ${shlibs:Depends}
Description: lightweight Exim MTA (v4) daemon
Exim (v4) is a mail transport agent. This package contains the exim4
daemon with only basic features enabled. It works well with the
Package: exim4
Architecture: all
-Depends: ${misc:Depends}, debconf (>= 1.4.69) | cdebconf (>= 0.39),
- exim4-base (>= ${source:Version}),
+Priority: optional
+Depends:
+ debconf (>= 1.4.69) | cdebconf (>= 0.39),
exim4-base (<< ${source:Version}.1),
- exim4-daemon-light | exim4-daemon-heavy | exim4-daemon-custom
+ exim4-base (>= ${source:Version}),
+ exim4-daemon-light | exim4-daemon-heavy | exim4-daemon-custom,
+ ${misc:Depends}
Description: metapackage to ease Exim MTA (v4) installation
Exim (v4) is a mail transport agent. exim4 is the metapackage depending
on the essential components for a basic exim4 installation.
Package: exim4-daemon-heavy
Architecture: any
Priority: optional
-Provides: mail-transport-agent, exim4-localscanapi-2.0
+Provides: exim4-localscanapi-2.0, mail-transport-agent
Conflicts: mail-transport-agent
-Replaces: mail-transport-agent, exim4-base (<= 4.61-1)
-Depends: exim4-base (>= ${Upstream-Version}), ${shlibs:Depends},
- ${misc:Depends}
+Replaces: exim4-base (<= 4.61-1), mail-transport-agent
+Depends:
+ exim4-base (>= ${Upstream-Version}),
+ ${misc:Depends},
+ ${shlibs:Depends}
Breaks: clamav-daemon (<< 0.95)
Description: Exim MTA (v4) daemon with extended features, including exiscan-acl
Exim (v4) is a mail transport agent. This package contains the exim4
#Package: exim4-daemon-custom
#Architecture: any
#Priority: optional
-#Provides: mail-transport-agent, exim4-localscanapi-2.0
+#Provides: exim4-localscanapi-2.0, mail-transport-agent
#Conflicts: mail-transport-agent
-#Replaces: mail-transport-agent, exim4-base (<= 4.61-1)
-#Depends: exim4-base (>= ${Upstream-Version}), ${shlibs:Depends}, ${misc:Depends}
+#Replaces: exim4-base (<= 4.61-1), mail-transport-agent
+#Depends:
+# exim4-base (>= ${Upstream-Version}),
+# ${misc:Depends},
+# ${shlibs:Depends}
#Description: custom Exim MTA (v4) daemon with locally set features
# Exim (v4) is a mail transport agent. This package contains a
# custom-configured exim4 daemon compiled to local needs. This package
Priority: optional
Conflicts: eximon
Replaces: eximon
-Depends: ${shlibs:Depends}, ${misc:Depends}, exim4-base (>= 4.10)
+Depends: exim4-base (>= 4.10), ${misc:Depends}, ${shlibs:Depends}
Description: monitor application for the Exim MTA (v4) (X11 interface)
Eximon is a helper program for the Exim MTA (v4). It allows
administrators to view the mail queue and logs, and perform a variety
of actions on queued messages, such as freezing, bouncing and thawing
messages.
-Package: exim4-dbg
-Architecture: any
-Priority: extra
-Section: debug
-Depends: exim4-base, exim4-config, ${misc:Depends}
-Recommends: eximon4
-Description: debugging symbols for the Exim MTA (utilities)
- Exim (v4) is a mail transport agent. This package contains
- debugging symbols for the binaries contained in the exim4
- packages. The daemon packages have their own debug package.
- .
- The Debian exim4 packages have their own web page,
- http://wiki.debian.org/PkgExim4. There is also a Debian-specific
- FAQ list. Information about the way the Debian packages are
- configured can be found in
- /usr/share/doc/exim4-base/README.Debian.gz, which additionally contains
- information about the way the Debian binary packages are built. The
- very extensive upstream documentation is shipped in
- /usr/share/doc/exim4-base/spec.txt.gz. To repeat the debconf-driven
- configuration process in a standard setup, invoke dpkg-reconfigure
- exim4-config. There is a Debian-centered mailing list,
- pkg-exim4-users@lists.alioth.debian.org. Please ask Debian-specific
- questions there, and only write to the upstream exim-users mailing
- list if you are sure that your question is not Debian-specific. You
- can find the subscription web page on
- http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users
-
-Package: exim4-daemon-light-dbg
-Architecture: any
-Priority: extra
-Section: debug
-Depends: exim4-daemon-light, ${misc:Depends}
-Description: debugging symbols for the Exim MTA "light" daemon
- Exim (v4) is a mail transport agent. This package contains
- debugging symbols for the binaries contained in the
- exim4-daemon-light package.
- .
- The Debian exim4 packages have their own web page,
- http://wiki.debian.org/PkgExim4. There is also a Debian-specific
- FAQ list. Information about the way the Debian packages are
- configured can be found in
- /usr/share/doc/exim4-base/README.Debian.gz, which additionally contains
- information about the way the Debian binary packages are built. The
- very extensive upstream documentation is shipped in
- /usr/share/doc/exim4-base/spec.txt.gz. To repeat the debconf-driven
- configuration process in a standard setup, invoke dpkg-reconfigure
- exim4-config. There is a Debian-centered mailing list,
- pkg-exim4-users@lists.alioth.debian.org. Please ask Debian-specific
- questions there, and only write to the upstream exim-users mailing
- list if you are sure that your question is not Debian-specific. You
- can find the subscription web page on
- http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users
-
-Package: exim4-daemon-heavy-dbg
-Architecture: any
-Priority: extra
-Section: debug
-Depends: exim4-daemon-heavy, ${misc:Depends}
-Description: debugging symbols for the Exim MTA "heavy" daemon
- Exim (v4) is a mail transport agent. This package contains
- debugging symbols for the binaries contained in the
- exim4-daemon-heavy package.
- .
- The Debian exim4 packages have their own web page,
- http://wiki.debian.org/PkgExim4. There is also a Debian-specific
- FAQ list. Information about the way the Debian packages are
- configured can be found in
- /usr/share/doc/exim4-base/README.Debian.gz, which additionally contains
- information about the way the Debian binary packages are built. The
- very extensive upstream documentation is shipped in
- /usr/share/doc/exim4-base/spec.txt.gz. To repeat the debconf-driven
- configuration process in a standard setup, invoke dpkg-reconfigure
- exim4-config. There is a Debian-centered mailing list,
- pkg-exim4-users@lists.alioth.debian.org. Please ask Debian-specific
- questions there, and only write to the upstream exim-users mailing
- list if you are sure that your question is not Debian-specific. You
- can find the subscription web page on
- http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users
-
-#Package: exim4-daemon-custom-dbg
-#Architecture: any
-#Priority: extra
-#Section: debug
-#Depends: exim4-daemon-custom, ${misc:Depends}
-#Description: debugging symbols for the Exim MTA (v4) packages
-# Exim (v4) is a mail transport agent. This package contains
-# debugging symbols for the binaries contained in the
-# exim4-daemon-custom package.
-# .
-# The Debian exim4 packages have their own web page,
-# http://wiki.debian.org/PkgExim4. There is also a Debian-specific
-# FAQ list. Information about the way the Debian packages are
-# configured can be found in
-# /usr/share/doc/exim4-base/README.Debian.gz, which additionally contains
-# information about the way the Debian binary packages are built. The
-# very extensive upstream documentation is shipped in
-# /usr/share/doc/exim4-base/spec.txt.gz. To repeat the debconf-driven
-# configuration process in a standard setup, invoke dpkg-reconfigure
-# exim4-config. There is a Debian-centered mailing list,
-# pkg-exim4-users@lists.alioth.debian.org. Please ask Debian-specific
-# questions there, and only write to the upstream exim-users mailing
-# list if you are sure that your question is not Debian-specific. You
-# can find the subscription web page on
-# http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users
-
Package: exim4-dev
Architecture: any
-Priority: extra
+Priority: optional
Depends: ${misc:Depends}
Description: header files for the Exim MTA (v4) packages
Exim (v4) is a mail transport agent. This package contains header
-----------------------------------------------------------------
-exim is copyright (c) 1995 - 2017 University of Cambridge.
+exim is copyright (c) 1995 - 2018 University of Cambridge.
The original licence is as follows (from the file NOTICE in the upstream
-distribution); a copy of the GNU GPL version 2 is available in
+distribution); a copy of the GNU GPL version 2 is available in
/usr/share/common-licenses/GPL-2 on Debian systems.
_________________________________________________________________________
PDKIM - a RFC4871 (DKIM) implementation
http://duncanthrax.net/pdkim/
-Copyright (C) 2009 - 2016 Tom Kistner <tom@duncanthrax.net>
-Copyright (C) 2016 - 2017 Jeremy Harris <jgh@exim.org>
+Copyright (C) 2009 Tom Kistner <tom@duncanthrax.net>
-Includes code from the PolarSSL project.
-http://polarssl.org
-Copyright (C) 2009 Paul Bakker <polarssl_maintainer@polarssl.org>
-Copyright (C) 2006-2008 Christophe Devine
-Copyright (C) 2006-2010, Brainspark B.V.
+No longer includes code from the PolarSSL project.
+Copyright (C) 2016 Jeremy Harris <jgh@exim.org>
This copy of PDKIM is included with Exim. For a standalone distribution,
visit http://duncanthrax.net/pdkim/.
-
-License: Both the parts from PolarSSL and the original code are licensed
-under GPLv2+.
-
-Please note that the parts copied from PolarSSL are only used with ancient
-(< 2.10) GnuTLS.
------------------------------------------------------------------
-
-----------------------------------------------------------------
-Generating a tarball from CVS snapshot.
-
-Upstream is keeping sourcecode and documentation (including changelog) in
-separate CVS modules: exim-src and exim-doc. However the release tarball
-contains parts from both modules.
-
-1. Use exim-src modules as base
-2. Generate a doc subdirectory containing he contents of exim-doc/doc-txt/.
-3. Take exim-doc and build the txt files You will need xfpt, xmlto, docbook-xsl
-and w3m.
-cd doc-docbook ; make spec.txt filter.txt exim.8
-Copy the three files to exim-version/doc/
-
### acl/30_exim4-config_check_rcpt
#################################
+# define macros to be used below in this file to check recipient
+# local parts for strange characters. Documentation below.
+# This blocks local parts that begin with a dot or contain a quite
+# broad range of non-alphanumeric characters.
+
+.ifndef CHECK_RCPT_LOCAL_LOCALPARTS
+CHECK_RCPT_LOCAL_LOCALPARTS = ^[.] : ^.*[@%!/|`#&?]
+.endif
+
+.ifndef CHECK_RCPT_REMOTE_LOCALPARTS
+CHECK_RCPT_REMOTE_LOCALPARTS = ^[./|] : ^.*[@%!`#&?] : ^.*/\\.\\./
+.endif
+
# This access control list is used for every RCPT command in an incoming
# SMTP message. The tests are run in order until the address is either
# accepted or denied.
# incorporated unthinkingly into a shell command line.
#
# These ACL components will block recipient addresses that are valid
- # from an RFC2822 point of view. We chose to have them blocked by
+ # from an RFC5322 point of view. We chose to have them blocked by
# default for security reasons.
#
# If you feel that your site should have less strict recipient
# default, and is applied to messages that are addressed to one of the
# local domains handled by this host.
- # The default value of CHECK_RCPT_LOCAL_LOCALPARTS is defined in
- # main/01_exim4-config_listmacrosdefs:
- # CHECK_RCPT_LOCAL_LOCALPARTS = ^[.] : ^.*[@%!/|`#&?]
- # This blocks local parts that begin with a dot or contain a quite
- # broad range of non-alphanumeric characters.
+ # The default value of CHECK_RCPT_LOCAL_LOCALPARTS is defined
+ # at the top of this file.
.ifdef CHECK_RCPT_LOCAL_LOCALPARTS
deny
domains = +local_domains
# the black list. See exim4-config_files(5) for details.
deny
message = sender envelope address $sender_address is locally blacklisted here. If you think this is wrong, get in touch with postmaster
+ log_message = sender envelope address is locally blacklisted.
!acl = acl_local_deny_exceptions
senders = ${if exists{CONFDIR/local_sender_blacklist}\
{CONFDIR/local_sender_blacklist}\
# the black list. See exim4-config_files(5) for details.
deny
message = sender IP address $sender_host_address is locally blacklisted here. If you think this is wrong, get in touch with postmaster
+ log_message = sender IP address is locally blacklisted.
!acl = acl_local_deny_exceptions
hosts = ${if exists{CONFDIR/local_host_blacklist}\
{CONFDIR/local_host_blacklist}\
condition = ${if > {$max_received_linelength}{998}}
.endif
- # Deny unless the address list headers are syntactically correct.
+ # Deny if the headers contain badly-formed addresses.
#
- # If you enable this, you might reject legitimate mail.
- .ifdef CHECK_DATA_VERIFY_HEADER_SYNTAX
+ .ifndef NO_CHECK_DATA_VERIFY_HEADER_SYNTAX
deny
- message = Message headers fail syntax check
!acl = acl_local_deny_exceptions
!verify = header_syntax
+ message = header syntax
+ log_message = header syntax ($acl_verify_message)
.endif
# Add headers to a message if it is judged to be spam. Before enabling this,
- # you must install SpamAssassin. You also need to set the spamd_address
+ # you must install SpamAssassin. You may also need to set the spamd_address
# option in the main configuration.
#
# exim4-daemon-heavy must be used for this section to work.
#
- # Please note that this is only suiteable as an example. There are
- # multiple issues with this configuration method. For example, if you go
- # this way, you'll give your spamassassin daemon write access to the
- # entire exim spool which might be a security issue in case of a
- # spamassassin exploit.
+ # Please note that this is only suiteable as an example. See
+ # /usr/share/doc/exim4-base/README.Debian.gz
#
# See the exim docs and the exim wiki for more suitable examples.
#
+ # # Remove internal headers
# warn
- # spam = Debian-exim:true
- # add_header = X-Spam_score: $spam_score\n\
- # X-Spam_score_int: $spam_score_int\n\
- # X-Spam_bar: $spam_bar\n\
- # X-Spam_report: $spam_report
+ # remove_header = X-Spam_score: X-Spam_score_int : X-Spam_bar : \
+ # X-Spam_report
+ #
+ # warn
+ # condition = ${if <{$message_size}{120k}{1}{0}}
+ # # ":true" to add headers/acl variables even if not spam
+ # spam = nobody:true
+ # add_header = X-Spam_score: $spam_score
+ # add_header = X-Spam_bar: $spam_bar
+ # # Do not enable this unless you have shorted SpamAssassin's report
+ # #add_header = X-Spam_report: $spam_report
+ #
+ # Reject spam messages (score >15.0).
+ # This breaks mailing list and forward messages.
+ # deny
+ # message = Classified as spam (score $spam_score)
+ # condition = ${if <{$message_size}{120k}{1}{0}}
+ # condition = ${if >{$spam_score_int}{150}{true}{false}}
# This hook allows you to hook in your own ACLs without having to
gecos_pattern = ^([^,:]*)
gecos_name = $1
-# define macros to be used in acl/30_exim4-config_check_rcpt to check
-# recipient local parts for strange characters.
-
-# This macro definition really should be in
-# acl/30_exim4-config_check_rcpt but cannot be there due to
-# http://www.exim.org/bugzilla/show_bug.cgi?id=101 as of exim 4.62.
-
-# These macros are documented in acl/30_exim4-config_check_rcpt,
-# can be changed here or overridden by a locally added configuration
-# file as described in README.Debian section "Using Exim Macros to control
-# the configuration".
-
-.ifndef CHECK_RCPT_LOCAL_LOCALPARTS
-CHECK_RCPT_LOCAL_LOCALPARTS = ^[.] : ^.*[@%!/|`#&?]
-.endif
-
-.ifndef CHECK_RCPT_REMOTE_LOCALPARTS
-CHECK_RCPT_REMOTE_LOCALPARTS = ^[./|] : ^.*[@%!`#&?] : ^.*/\\.\\./
-.endif
-
# always log tls_peerdn as we use TLS for outgoing connects by default
.ifndef MAIN_LOG_SELECTOR
MAIN_LOG_SELECTOR = +smtp_protocol_error +smtp_syntax_error +tls_certificate_verified +tls_peerdn
host_lookup = MAIN_HOST_LOOKUP
.endif
+# The setting below causes Exim to try to initialize the system resolver
+# library with DNSSEC support. It has no effect if your library lacks
+# DNSSEC support.
+dns_dnssec_ok = 1
# In a minimaldns setup, update-exim4.conf guesses the hostname and
# dumps it here to avoid DNS lookups being done at Exim run time.
domains = ! +local_domains : +relay_to_domains
transport = remote_smtp
same_domain_copy_routing = yes
+ dnssec_request_domains = *
no_more
# deliver mail directly to the recipient. This router is only reached
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8 : 192.168.0.0/16 :\
172.16.0.0/12 : 10.0.0.0/8 : 169.254.0.0/16 :\
255.255.255.255
+ dnssec_request_domains = *
no_more
.endif
.ifdef REMOTE_SMTP_PRIVATEKEY
tls_privatekey = REMOTE_SMTP_PRIVATEKEY
.endif
+.ifndef REMOTE_SMTP_DISABLE_DANE
+dnssec_request_domains = *
+hosts_try_dane = *
+.endif
remote_smtp_smarthost:
debug_print = "T: remote_smtp_smarthost for $local_part@$domain"
driver = smtp
+ multi_domain
.ifndef IGNORE_SMTP_LINE_LENGTH_LIMIT
message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
.endif
preprocess_macro() {
macroname="${1:-}"
shift
- contents="$(lowercase ${@:-empty} | check_ascii_pipe)"
+ contents="$(lowercase ${@} | check_ascii_pipe)"
printf "%s" ".ifndef $macroname\n$macroname=$contents\n.endif\n"
}
if [ "$MODE" = 'stop' ]; then
rm -f $SMARTHOSTFILE
- /etc/init.d/exim4 reload > /dev/null || true
+ invoke exim4 reload > /dev/null || true
exit 0
fi
if [ "$IF_EXIM4_SMARTHOST" = "none" ]; then
rm -f $SMARTHOSTFILE
- /etc/init.d/exim4 reload > /dev/null || true
+ invoke exim4 reload > /dev/null || true
exit 0
fi
echo "DCsmarthost = ${IF_EXIM4_SMARTHOST}" > $SMARTHOSTFILE
-/etc/init.d/exim4 reload > /dev/null || true
+invoke exim4 reload > /dev/null || true
/usr/sbin/exim4 -qqf
-855d721412eba13426a8781cc804157d -
+321b32be071eee7394d7884f9471e6bd -
# (see #373786 and #376165)
find $SPOOLDIR/db -maxdepth 1 -name '*.lockfile' -or -name 'log.*' \
-or -type f -printf '%f\0' | \
- su - --shell /bin/bash \
- --command "xargs -0r -n 1 /usr/sbin/exim_tidydb $SPOOLDIR > /dev/null" \
+ runuser --shell=/bin/bash \
+ --command="xargs -0r -n 1 /usr/sbin/exim_tidydb $SPOOLDIR > /dev/null" \
Debian-exim
fi
fi
-/usr/sbin
-/usr/share/man/man8
/etc/cron.daily
/etc/logrotate.d
+/usr/sbin
/usr/share/doc/exim4-base/examples
+/usr/share/man/man8
-b-exim4-daemon-light/NOTICE
b-exim4-daemon-light/ACKNOWLEDGMENTS
-b-exim4-daemon-light/doc/README
-b-exim4-daemon-light/doc/README.SIEVE
+b-exim4-daemon-light/NOTICE
b-exim4-daemon-light/README.UPDATING
-b-exim4-daemon-light/doc/dbm.discuss.txt
b-exim4-daemon-light/doc/Exim3.upgrade
b-exim4-daemon-light/doc/Exim4.upgrade
-b-exim4-daemon-light/doc/filter.txt
+b-exim4-daemon-light/doc/GnuTLS-FAQ.txt
b-exim4-daemon-light/doc/NewStuff
b-exim4-daemon-light/doc/OptionLists.txt
+b-exim4-daemon-light/doc/README
+b-exim4-daemon-light/doc/README.SIEVE
+b-exim4-daemon-light/doc/dbm.discuss.txt
+b-exim4-daemon-light/doc/filter.txt
b-exim4-daemon-light/doc/spec.txt
-b-exim4-daemon-light/doc/GnuTLS-FAQ.txt
-debian/changelog.Debian.old
debian/README.Debian.html
+debian/changelog.Debian.old
b-exim4-daemon-light/util/cramtest.pl
b-exim4-daemon-light/util/logargs.sh
b-exim4-daemon-light/util/unknownuser.sh
-debian/exim-gencert
debian/exim-adduser
+debian/exim-gencert
# we try to kill eximqr and exim SMTP listener, no matter what
# ${QUEUERUNNER} is set to, we could have switched since starting.
if [ -f "$QRPIDFILE" ]; then
- killproc -p "$QRPIDFILE" "$DAEMON"
+ start-stop-daemon --stop --retry 5 --quiet --oknodo --remove-pidfile \
+ --pidfile "$QRPIDFILE" \
+ --exec "$DAEMON"
# exim does not remove the pidfile
- if [ $? -eq 0 ] ; then rm -f "$QRPIDFILE" ; fi
+ if [ $? -eq 2 ] ; then rm -f "$QRPIDFILE" ; fi
log_progress_msg "exim4_queuerunner"
fi
if [ -f "$PIDFILE" ]; then
- killproc -p "$PIDFILE" "$DAEMON"
+ start-stop-daemon --stop --retry 5 --quiet --oknodo --remove-pidfile \
+ --pidfile "$PIDFILE" \
+ --exec "$DAEMON"
# exim does not remove the pidfile
- if [ $? -eq 0 ] ; then rm -f "$PIDFILE" ; fi
+ if [ $? -eq 2 ] ; then rm -f "$PIDFILE" ; fi
log_progress_msg "exim4_listener"
fi
}
{
case ${QUEUERUNNER} in
combined|no|ppp|queueonly)
- killproc -p "$PIDFILE" "$DAEMON" -HUP
+ start-stop-daemon --stop --signal HUP --quiet --oknodo \
+ --pidfile "$PIDFILE" \
+ --exec "$DAEMON"
log_progress_msg "exim4"
;;
separate)
- killproc -p "$PIDFILE" "$DAEMON" -HUP
+ start-stop-daemon --stop --signal HUP --quiet --oknodo \
+ --pidfile "$PIDFILE" \
+ --exec "$DAEMON"
log_progress_msg "exim4_listener"
- killproc -p "$QRPIDFILE" "$DAEMON" -HUP
+ start-stop-daemon --stop --signal HUP --quiet --oknodo \
+ --pidfile "$QRPIDFILE" \
+ --exec "$DAEMON"
log_progress_msg "exim4_queuerunner"
;;
esac
-debian/script usr/share/bug/exim4-base
-debian/gnutls-params-2048 usr/share/exim4
debian/exim4_refresh_gnutls-params usr/share/exim4
+debian/gnutls-params-2048 usr/share/exim4
+debian/script usr/share/bug/exim4-base
debian/manpages/exicyclog.8
debian/manpages/exigrep.8
debian/manpages/exim_checkaccess.8
+debian/manpages/exim_convert4r4.8
debian/manpages/exim_db.8
debian/manpages/exim_dbmbuild.8
debian/manpages/exim_lock.8
debian/manpages/exiqgrep.8
debian/manpages/exiqsumm.8
debian/manpages/exiwhat.8
-debian/manpages/exim_convert4r4.8
cat /run/exim4/exim.pid
pidof exim4
fi
- if command -v invoke-rc.d >/dev/null 2>&1; then
- invoke-rc.d exim4 stop
- else
- /etc/init.d/exim4 stop
- fi
+ invoke-rc.d exim4 stop
if [ -n "$EX4DEBUG" ]; then
netstat -tulpen
ls -al /run/exim4/
-/usr/sbin
/etc/exim4/conf.d
/etc/ppp/ip-up.d
+/usr/sbin
/usr/share/doc/exim4-config
/usr/share/man/man8
/var/lib/exim4
-debian/debconf/update-exim4.conf.template usr/sbin
debian/debconf/exim4.conf.template etc/exim4
+debian/debconf/update-exim4.conf.template usr/sbin
debian/script usr/share/bug/exim4-config
usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/etc-aliases.5.gz
usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/etc-email-addresses.5.gz
+usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_exim_crt.5.gz
+usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_exim_key.5.gz
+usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_host_local_deny_exceptions.5.gz
+usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_hubbed_hosts.5.gz
+usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_local_domain_dnsbl_whitelist.5.gz
usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_local_host_blacklist.5.gz
+usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_local_rcpt_callout.5.gz
usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_local_sender_blacklist.5.gz
-usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_host_local_deny_exceptions.5.gz
-usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_sender_local_deny_exceptions.5.gz
usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_local_sender_callout.5.gz
-usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_local_rcpt_callout.5.gz
-usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_local_domain_dnsbl_whitelist.5.gz
-usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_hubbed_hosts.5.gz
usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_passwd.5.gz
usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_passwd_client.5.gz
-usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_exim_crt.5.gz
-usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_exim_key.5.gz
+usr/share/man/man5/exim4-config_files.5.gz usr/share/man/man5/exim4_sender_local_deny_exceptions.5.gz
usr/share/man/man8/update-exim4.conf.8.gz usr/share/man/man5/update-exim4.conf.conf.5.gz
+debian/manpages/exim4-config_files.5
debian/manpages/update-exim4.conf.8
debian/manpages/update-exim4.conf.template.8
debian/manpages/update-exim4defaults.8
-debian/manpages/exim4-config_files.5
-usr/share/man/man8/exim.8.gz usr/share/man/man8/exim4.8.gz
+usr/sbin/exim4 usr/bin/mailq
+usr/sbin/exim4 usr/bin/newaliases
usr/sbin/exim4 usr/lib/exim4/exim4
usr/sbin/exim4 usr/lib/sendmail
usr/sbin/exim4 usr/sbin/exim
-usr/sbin/exim4 usr/sbin/sendmail
-usr/sbin/exim4 usr/sbin/runq
usr/sbin/exim4 usr/sbin/rmail
usr/sbin/exim4 usr/sbin/rsmtp
-usr/sbin/exim4 usr/bin/mailq
-usr/sbin/exim4 usr/bin/newaliases
-usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-daemon-custom/changelog.gz
+usr/sbin/exim4 usr/sbin/runq
+usr/sbin/exim4 usr/sbin/sendmail
usr/share/doc/exim4-base/README.Debian.gz usr/share/doc/exim4-daemon-custom/README.Debian.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/sendmail.8.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/runq.8.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/rmail.8.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/rsmtp.8.gz
+usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-daemon-custom/changelog.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/exim4.8.gz
usr/share/man/man8/exim.8.gz usr/share/man/man8/mailq.8.gz
usr/share/man/man8/exim.8.gz usr/share/man/man8/newaliases.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/rmail.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/rsmtp.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/runq.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/sendmail.8.gz
+++ /dev/null
-usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-daemon-heavy-dbg/changelog.gz
/usr/lib/exim4
+/usr/lib/exim4/local_scan
/usr/sbin
/usr/share/man/man8
-/usr/lib/exim4/local_scan
-usr/share/man/man8/exim.8.gz usr/share/man/man8/exim4.8.gz
+usr/sbin/exim4 usr/bin/mailq
+usr/sbin/exim4 usr/bin/newaliases
usr/sbin/exim4 usr/lib/exim4/exim4
usr/sbin/exim4 usr/lib/sendmail
usr/sbin/exim4 usr/sbin/exim
-usr/sbin/exim4 usr/sbin/sendmail
-usr/sbin/exim4 usr/sbin/runq
usr/sbin/exim4 usr/sbin/rmail
usr/sbin/exim4 usr/sbin/rsmtp
-usr/sbin/exim4 usr/bin/mailq
-usr/sbin/exim4 usr/bin/newaliases
-usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-daemon-heavy/changelog.gz
+usr/sbin/exim4 usr/sbin/runq
+usr/sbin/exim4 usr/sbin/sendmail
usr/share/doc/exim4-base/README.Debian.gz usr/share/doc/exim4-daemon-heavy/README.Debian.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/sendmail.8.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/runq.8.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/rmail.8.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/rsmtp.8.gz
+usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-daemon-heavy/changelog.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/exim4.8.gz
usr/share/man/man8/exim.8.gz usr/share/man/man8/mailq.8.gz
usr/share/man/man8/exim.8.gz usr/share/man/man8/newaliases.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/rmail.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/rsmtp.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/runq.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/sendmail.8.gz
+++ /dev/null
-usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-daemon-light-dbg/changelog.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/exim4.8.gz
+usr/sbin/exim4 usr/bin/mailq
+usr/sbin/exim4 usr/bin/newaliases
usr/sbin/exim4 usr/lib/exim4/exim4
usr/sbin/exim4 usr/lib/sendmail
usr/sbin/exim4 usr/sbin/exim
-usr/sbin/exim4 usr/sbin/sendmail
-usr/sbin/exim4 usr/sbin/runq
usr/sbin/exim4 usr/sbin/rmail
usr/sbin/exim4 usr/sbin/rsmtp
-usr/sbin/exim4 usr/bin/mailq
-usr/sbin/exim4 usr/bin/newaliases
-usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-daemon-light/changelog.gz
+usr/sbin/exim4 usr/sbin/runq
+usr/sbin/exim4 usr/sbin/sendmail
usr/share/doc/exim4-base/README.Debian.gz usr/share/doc/exim4-daemon-light/README.Debian.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/sendmail.8.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/runq.8.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/rmail.8.gz
-usr/share/man/man8/exim.8.gz usr/share/man/man8/rsmtp.8.gz
+usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-daemon-light/changelog.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/exim4.8.gz
usr/share/man/man8/exim.8.gz usr/share/man/man8/mailq.8.gz
usr/share/man/man8/exim.8.gz usr/share/man/man8/newaliases.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/rmail.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/rsmtp.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/runq.8.gz
+usr/share/man/man8/exim.8.gz usr/share/man/man8/sendmail.8.gz
echo "Initializing GnuTLS DH parameter file" 1>&2
tempgnutls=$(tempfile --directory /var/spool/exim4 --mode 644 --prefix "gnutp")
chown Debian-exim:Debian-exim $tempgnutls
- if [ -x /usr/bin/certtool ] && \
+ if which certtool > /dev/null 2>&1 && \
timeout --preserve-status --kill-after=15 120 \
certtool --generate-dh-params --bits 2048 > $tempgnutls ; then
mv $tempgnutls /var/spool/exim4/gnutls-params-2048
cat /run/exim4/exim.pid
pidof exim4
fi
- if command -v invoke-rc.d >/dev/null 2>&1; then
- invoke-rc.d exim4 stop
- else
- /etc/init.d/exim4 stop
- fi
+ invoke-rc.d exim4 stop
if [ -n "$EX4DEBUG" ]; then
netstat -tulpen
ls -al /run/exim4/
+++ /dev/null
-usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-dbg/changelog.gz
b-exim4-daemon-light/src/local_scan.h usr/include/exim4
-b-exim4-daemon-light/src/store.h usr/include/exim4
b-exim4-daemon-light/src/mytypes.h usr/include/exim4
+b-exim4-daemon-light/src/store.h usr/include/exim4
debian/exim4-localscan-plugin-config usr/bin
-usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-dev/changelog.gz
usr/share/doc/exim4-base/README.Debian.gz usr/share/doc/exim4-dev/README.Debian.gz
+usr/share/doc/exim4-base/changelog.gz usr/share/doc/exim4-dev/changelog.gz
-usr/sbin
usr/lib/exim4
+usr/sbin
+++ /dev/null
-?package(eximon4):needs="X11" section="Applications/System/Administration"\
- title="eximon" command="/usr/sbin/eximon"
-courier-mta, esmtp-run, hula-mta, masqmail, mta-dummy, nullmailer, postfix, sendmail-bin, smail, ssmtp, xmail, zmailer
\ No newline at end of file
+citadel-server, courier-mta, dma, esmtp-run, hula-mta, masqmail, msmtp-mta, mta-dummy, nullmailer, opensmtpd, postfix, qmail-run, sendmail-bin, smail, ssmtp, xmail, zmailer
accordingly.
Author: Marc Haber <mh+debian-packages@zugschlus.de>,
Andreas Metzler <ametzler@bebt.de>
-Last-Update: 2017-01-31
+Last-Update: 2018-12-31
Forwarded: not-needed (upstream uses the "exim" name)
--- a/doc/exim.8
PID_FILE_PATH in Local/Makefile. The file is written while Exim is still
running as root.
.sp
-@@ -175,7 +175,7 @@ of lookups, you will just get the same r
+@@ -180,7 +180,7 @@ available to admin users.
This option operates like \fB\-be\fP except that it must be followed by the name
of a file. For example:
.sp
.sp
The file is read as a message (as if receiving a locally\-submitted non\-SMTP
message) before any of the test expansions are done. Thus, message\-specific
-@@ -201,7 +201,7 @@ If you want to test a system filter file
+@@ -206,7 +206,7 @@ If you want to test a system filter file
can use both \fB\-bF\fP and \fB\-bf\fP on the same command, in order to test a system
filter and a user filter in the same run. For example:
.sp
.sp
This is helpful when the system filter adds header lines or sets filter
variables that are used by the user filter.
-@@ -253,8 +253,8 @@ This option runs a fake SMTP session as
+@@ -258,8 +258,8 @@ This option runs a fake SMTP session as
standard input and output. The IP address may include a port number at the end,
after a full stop. For example:
.sp
.sp
When an IPv6 address is given, it is converted into canonical form. In the case
of the second example above, the value of \fI$sender_host_address\fP after
-@@ -412,7 +412,7 @@ main configuration options to be written
+@@ -417,7 +417,7 @@ main configuration options to be written
of one or more specific options can be requested by giving their names as
arguments, for example:
.sp
.sp
However, any option setting that is preceded by the word "hide" in the
configuration file is not shown in full, except to an admin user. For other
-@@ -440,7 +440,7 @@ written directly into the spool director
+@@ -445,7 +445,7 @@ written directly into the spool director
.sp
If \fB\-bP\fP is followed by a name preceded by +, for example,
.sp
.sp
it searches for a matching named list of any type (domain, host, address, or
local part) and outputs what it finds.
-@@ -449,7 +449,7 @@ If one of the words \fBrouter\fP, \fBtra
+@@ -454,7 +454,7 @@ If one of the words \fBrouter\fP, \fBtra
followed by the name of an appropriate driver instance, the option settings for
that driver are output. For example:
.sp
.sp
The generic driver options are output first, followed by the driver's private
options. A list of the names of drivers of a particular type can be obtained by
-@@ -532,7 +532,7 @@ This option is for testing retry rules,
+@@ -539,7 +539,7 @@ This option is for testing retry rules,
arguments. It causes Exim to look for a retry rule that matches the values
and to write it to the standard output. For example:
.sp
Retry rule: *.comp.mus.example F,2h,15m; F,4d,30m;
.sp
The first
-@@ -545,7 +545,7 @@ rule is found that matches the host, one
+@@ -552,7 +552,7 @@ rule is found that matches the host, one
sought. Finally, an argument that is the name of a specific delivery error, as
used in setting up retry rules, can be given. For example:
.sp
Retry rule: *@haydn.comp.mus.example quota_3d F,1h,15m
.TP 10
\fB\-brw\fP
-@@ -648,7 +648,7 @@ doing such tests.
+@@ -655,7 +655,7 @@ doing such tests.
.TP 10
\fB\-bV\fP
This option causes Exim to write the current version number, compilation
+number, and compilation date of the \fIexim4\fP binary to the standard output.
It also lists the DBM library that is being used, the optional modules (such as
specific lookup types), the drivers that are included in the binary, and the
- name of the run time configuration file that is in use.
-@@ -676,7 +676,7 @@ If no arguments are given, Exim runs in
+ name of the runtime configuration file that is in use.
+@@ -683,7 +683,7 @@ If no arguments are given, Exim runs in
right angle bracket for addresses to be verified.
.sp
Unlike the \fB\-be\fP test option, you cannot arrange for Exim to use the
security issues.
.sp
Verification differs from address testing (the \fB\-bt\fP option) in that routers
-@@ -789,14 +789,14 @@ command line item. \fB\-D\fP can be used
+@@ -796,14 +796,14 @@ command line item. \fB\-D\fP can be used
string, in which case the equals sign is optional. These two commands are
synonymous:
.sp
.sp
\fB\-D\fP may be repeated up to 10 times on a command line.
Only macro names up to 22 letters long can be set.
-@@ -926,8 +926,8 @@ never provoke a bounce. An empty sender
+@@ -938,8 +938,8 @@ never provoke a bounce. An empty sender
string, or as a pair of angle brackets with nothing between them, as in these
examples of shell commands:
.sp
.sp
In addition, the use of \fB\-f\fP is not restricted when testing a filter file
with \fB\-bf\fP or when testing or verifying addresses using the \fB\-bt\fP or
-@@ -1292,12 +1292,12 @@ other circumstances, they are ignored un
+@@ -1315,12 +1315,12 @@ other circumstances, they are ignored un
The \fB\-oMa\fP option sets the sender host address. This may include a port
number at the end, after a full stop (period). For example:
.sp
.sp
The IP address is placed in the \fI$sender_host_address\fP variable, and the
port, if present, in \fI$sender_host_port\fP. If both \fB\-oMa\fP and \fB\-bh\fP
-@@ -1502,22 +1502,22 @@ If other commandline options specify an
+@@ -1526,22 +1526,22 @@ If other commandline options specify an
will specify a queue to operate on.
For example:
.sp
.sp
just one delivery process is started, for that message. This differs from
\fB\-M\fP in that retry data is respected, and it also differs from \fB\-Mc\fP in
-@@ -1533,7 +1533,7 @@ starting a queue runner process at inter
+@@ -1557,7 +1557,7 @@ starting a queue runner process at inter
single daemon process handles both functions. A common way of starting up a
combined daemon at system boot time is to use a command such as
.sp
.sp
Such a daemon listens for incoming SMTP calls, and also starts a queue runner
process every 30 minutes.
-@@ -1564,7 +1564,7 @@ regular expression; otherwise it is a li
+@@ -1588,7 +1588,7 @@ regular expression; otherwise it is a li
If you want to do periodic queue runs for messages with specific recipients,
you can combine \fB\-R\fP with \fB\-q\fP and a time value. For example:
.sp
.sp
This example does a queue run for messages with recipients in the given domain
every 25 minutes. Any additional flags that are specified with \fB\-q\fP are
-@@ -1680,6 +1680,26 @@ under most shells.
+@@ -1704,6 +1704,26 @@ under most shells.
.sp
.
.SH "SEE ALSO"
-Description: Accomodate source for installing exim as exim4.
+Description: Accommodate source for installing exim as exim4.
Author: Andreas Metzler <ametzler@debian.org>
Origin: vendor
Forwarded: not-needed
-Last-Update: 2013-09-28
+Last-Update: 2018-12-12
--- a/OS/Makefile-Linux
+++ b/OS/Makefile-Linux
# End
--- a/src/exicyclog.src
+++ b/src/exicyclog.src
-@@ -144,7 +144,7 @@ done
+@@ -149,7 +149,7 @@ done
st=' '
exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
#########################################################################
--- a/src/eximon.src
+++ b/src/eximon.src
-@@ -72,7 +72,7 @@ config=${EXIMON_EXIM_CONFIG-$config}
+@@ -79,7 +79,7 @@ config=${EXIMON_EXIM_CONFIG-$config}
st=' '
EXIM_PATH=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
LOG_FILE_PATH=`$EXIM_PATH -C $config -bP log_file_path | sed 's/.*=[ ]*//'`
--- a/src/exinext.src
+++ b/src/exinext.src
-@@ -90,7 +90,7 @@ if [ "$exim_path" = "" ]; then
+@@ -97,7 +97,7 @@ if [ "$exim_path" = "" ]; then
exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
fi
spool_directory=`$exim_path $eximmacdef -C $config -bP spool_directory | sed 's/.*=[ ]*//'`
qualify_domain=`$exim_path $eximmacdef -C $config -bP qualify_domain | sed 's/.*=[ ]*//'`
-@@ -171,7 +171,7 @@ perl - $exim_path "$eximmacdef" $argone
+@@ -181,7 +181,7 @@ perl - $exim_path "$eximmacdef" $argone
# Run exim_dumpdb to get out the retry data and pick off what we want
while (<DATA>)
--- a/src/exiqgrep.src
+++ b/src/exiqgrep.src
-@@ -21,7 +21,7 @@ use strict;
- use Getopt::Std;
+@@ -24,7 +24,7 @@ use Getopt::Std;
+ use File::Basename;
# Have this variable point to your exim binary.
-my $exim = 'BIN_DIRECTORY/exim';
my %opt;
--- a/src/exiwhat.src
+++ b/src/exiwhat.src
-@@ -88,7 +88,7 @@ fi
+@@ -98,7 +98,7 @@ fi
st=' '
exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
--- a/src/globals.c
+++ b/src/globals.c
-@@ -705,7 +705,7 @@ const uschar *event_name = NULL;
+@@ -906,7 +906,7 @@ const uschar *event_name = NULL;
+
gid_t exim_gid = EXIM_GID;
- BOOL exim_gid_set = TRUE; /* This gid is always set */
-uschar *exim_path = US BIN_DIRECTORY "/exim"
+uschar *exim_path = US BIN_DIRECTORY "/exim4"
"\0<---------------Space to patch exim_path->";
uid_t exim_uid = EXIM_UID;
- BOOL exim_uid_set = TRUE; /* This uid is always set */
+ int expand_level = 0; /* Nesting depth, indent for debug */
+++ /dev/null
-Description: Reproducible build fix.
- Use REPBUILDDATE which is pulled from debian/changelog in debian/rules
- instead of __DATE__ as compile date.
-Author: Andreas Metzler <ametzler@debian.org>
-
---- a/exim_monitor/em_version.c
-+++ b/exim_monitor/em_version.c
-@@ -10,6 +10,8 @@
- #include <string.h>
- #include <stdlib.h>
-
-+#include "../src/repbuildtime.h"
-+
- extern uschar *version_string;
- extern uschar *version_date;
-
-@@ -21,7 +23,7 @@ uschar today[20];
-
- version_string = US"2.06";
-
--Ustrcpy(today, __DATE__);
-+Ustrcpy(today, REPBUILDDATE);
- if (today[4] == ' ') i = 1;
- today[3] = today[6] = '-';
-
-@@ -31,7 +33,7 @@ Ustrncat(version_date, today+4+i, 3-i);
- Ustrncat(version_date, today, 4);
- Ustrncat(version_date, today+7, 4);
- Ustrcat(version_date, " ");
--Ustrcat(version_date, __TIME__);
-+Ustrcat(version_date, REPBUILDTIME);
- }
-
- /* End of em_version.c */
---- a/src/version.c
-+++ b/src/version.c
-@@ -11,6 +11,8 @@
-
- #include "version.h"
-
-+#include "../src/repbuildtime.h"
-+
-
- /* The header file cnumber.h contains a single line containing the
- compilation number, making it easy to have it updated automatically.
-@@ -40,7 +42,7 @@ version_cnumber_format = US"%d\0<<eximcn
- sprintf(CS version_cnumber, CS version_cnumber_format, cnumber);
- version_string = US EXIM_VERSION_STR "\0<<eximversion>>";
-
--Ustrcpy(today, __DATE__);
-+Ustrcpy(today, REPBUILDDATE);
- if (today[4] == ' ') today[4] = '0';
- today[3] = today[6] = '-';
-
-@@ -50,7 +52,7 @@ Ustrncat(version_date, today+4, 3);
- Ustrncat(version_date, today, 4);
- Ustrncat(version_date, today+7, 4);
- Ustrcat(version_date, " ");
--Ustrcat(version_date, __TIME__);
-+Ustrcat(version_date, REPBUILDTIME);
- }
-
- /* End of version.c */
Forwarded: no
Last-Update: 2013-09-28
---- exim4-4.82~rc1.orig/src/convert4r4.src
-+++ exim4-4.82~rc1/src/convert4r4.src
-@@ -652,6 +652,32 @@ return defined $main{$_[0]} && $main{$_[
+--- a/src/convert4r4.src
++++ b/src/convert4r4.src
+@@ -666,6 +666,32 @@ return defined $main{$_[0]} && $main{$_[
print STDERR "Runtime configuration file converter for Exim release 4.\n";
Description: Stop using exim's -C option in utility scripts (exiwhat
et al.) since this breaks with ALT_CONFIG_PREFIX.
-Author: Andreas Metzler <ametzler@downhill.at.eu.org>
+Author: Andreas Metzler <ametzler@bebt.de>
Forwarded: http://bugs.exim.org/show_bug.cgi?id=1045
-Last-Update: 2014-12-01
+Last-Update: 2018-12-31
--- a/src/exicyclog.src
+++ b/src/exicyclog.src
-@@ -146,10 +146,10 @@ st=' '
+@@ -151,10 +151,10 @@ st=' '
exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
# If log_file_path contains only "syslog" then no Exim log files are in use.
--- a/src/eximon.src
+++ b/src/eximon.src
-@@ -74,8 +74,8 @@ st=' '
+@@ -81,8 +81,8 @@ st=' '
EXIM_PATH=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
if test "$EXIM_PATH" = ""; then EXIM_PATH=BIN_DIRECTORY/exim4; fi
# is unable to display a log tail unless EXIMON_LOG_FILE_PATH is set to tell
--- a/src/exinext.src
+++ b/src/exinext.src
-@@ -91,8 +91,8 @@ if [ "$exim_path" = "" ]; then
+@@ -98,8 +98,8 @@ if [ "$exim_path" = "" ]; then
fi
if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
# Now do the job. Perl uses $ so frequently that we don't want to have to
# escape them all from the shell, so pass in shell variable values as
-@@ -134,7 +134,7 @@ perl - $exim_path "$eximmacdef" $argone
+@@ -144,7 +144,7 @@ perl - $exim_path "$eximmacdef" $argone
# Run Exim to get a list of hosts for the given domain; for
# each one construct the appropriate retry key.
while (<LIST>)
--- a/src/exiwhat.src
+++ b/src/exiwhat.src
-@@ -89,8 +89,8 @@ fi
+@@ -99,8 +99,8 @@ fi
st=' '
exim_path=`grep "^[$st]*exim_path" $config | sed "s/.*=[$st]*//"`
if test "$exim_path" = ""; then exim_path=BIN_DIRECTORY/exim4; fi
-#! /bin/sh /usr/share/dpatch/dpatch-run
-## 70_remove_exim-users_references.dpatch by Marc Haber <mh+debian-packages@zugschlus.de>
-##
-## All lines beginning with `## DP:' are a description of the patch.
-## DP: No description.
-Last-Update: 2014-12-01
+Description: Point Debian users to Debian specific ML.
+Author: Marc Haber <mh+debian-packages@zugschlus.de>
+Last-Update: 2018-12-31
--- a/README
+++ b/README
older book may be helpful for the background, but a lot of the detail has
changed, so it is likely to be confusing to newcomers.
--There is a web site at http://www.exim.org; this contains details of the
+-There is a website at https://www.exim.org; this contains details of the
-mailing list exim-users@exim.org.
+Information about the way Debian has built the binary packages is
+obtainable in /usr/share/doc/exim4-base/README.Debian.gz, and there
+can find the subscription web page on
+http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users
+
-+There is a web site at http://www.exim.org.
++There is a website at https://www.exim.org/.
A copy of the Exim FAQ should be available from the same source that you used
to obtain the Exim distribution. Additional formats for the documentation
=head1 AUTHOR
--There is a web site at http://www.exim.org - this contains details of the
+-There is a website at https://www.exim.org - this contains details of the
-mailing list exim-users@exim.org.
-+There is a web site at http://www.exim.org
++There is a website at https://www.exim.org/.
=head1 TO DO
--- /dev/null
+From b2734f7b45111f9b7de790c7b334a2ece47675b5 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Sat, 9 Feb 2019 16:56:59 +0000
+Subject: [PATCH 1/7] Fix json extract operator for unfound case
+
+(cherry picked from commit e73798976812e652320f096870359ef35ed069ff)
+---
+ doc/doc-docbook/spec.xfpt | 4 ++++
+ src/expand.c | 13 ++++++++-----
+ test/scripts/0000-Basic/0002 | 3 +++
+ test/stdout/0002 | 3 +++
+ 4 files changed, 18 insertions(+), 5 deletions(-)
+
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -3901,7 +3901,8 @@ return NULL;
+ /* Pull off the leading array or object element, returning
+ a copy in an allocated string. Update the list pointer.
+
+-The element may itself be an abject or array.
++The element may itself be an object or array.
++Return NULL when the list is empty.
+ */
+
+ uschar *
+@@ -3923,6 +3924,7 @@ for (item = s;
+ case '}': object_depth--; break;
+ }
+ *list = *s ? s+1 : s;
++if (item == s) return NULL;
+ item = string_copyn(item, s - item);
+ DEBUG(D_expand) debug_printf_indent(" json ele: '%s'\n", item);
+ return US item;
+@@ -5790,10 +5792,11 @@ while (*s != 0)
+ }
+ while (field_number > 0 && (item = json_nextinlist(&list)))
+ field_number--;
+- s = item;
+- lookup_value = s;
+- while (*s) s++;
+- while (--s >= lookup_value && isspace(*s)) *s = '\0';
++ if ((lookup_value = s = item))
++ {
++ while (*s) s++;
++ while (--s >= lookup_value && isspace(*s)) *s = '\0';
++ }
+ }
+ else
+ {
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -8776,6 +8776,8 @@ ${extract json{<key>}{<string1>}{<string
+ The braces, commas and colons, and the quoting of the member name are
+ required; the spaces are optional. Matching of the key against the member
+ names is done case-sensitively.
++ If a returned value is a JSON string, it retains its leading and
++ trailing quotes.
+
+ The results of matching are handled as above.
+
+@@ -8813,6 +8815,8 @@ ${extract json{<number>}}{<string1>}{<st
+
+ Field selection and result handling is as above; there is no choice of
+ field separator.
++ If a returned value is a JSON string, it retains its leading and
++ trailing quotes.
+
+ ${filter{<string>}{<condition>}}
+
--- /dev/null
+From 1cfa7822ca8928f95160df8742af11fff888ae7e Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 12 Feb 2019 16:52:51 +0000
+Subject: [PATCH 3/7] Fix transport buffer size handling Broken-by: 59932f7dcd
+
+(cherry picked from commit 05bf16f6217e93594929c8bbbbbc852caf3ed374)
+---
+ doc/ChangeLog | 7 +++++++
+ src/transport.c | 4 ++--
+ 2 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 7da07ad4..66c8a7a1 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -5,6 +5,13 @@ affect Exim's operation, with an unchanged configuration file. For new
+ options, and new features, see the NewStuff file next to this ChangeLog.
+
+
++Since version 4.92
++------------------
++
++JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible
++ buffer overrun for (non-chunking) other transports.
++
++
+ Exim version 4.92
+ -----------------
+
+diff --git a/src/transport.c b/src/transport.c
+index 8ccdd038..a069b883 100644
+--- a/src/transport.c
++++ b/src/transport.c
+@@ -1115,13 +1115,13 @@ DEBUG(D_transport)
+
+ if (!(tctx->options & topt_no_body))
+ {
+- int size = size_limit;
++ unsigned long size = size_limit > 0 ? size_limit : ULONG_MAX;
+
+ nl_check_length = abs(nl_check_length);
+ nl_partial_match = 0;
+ if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0)
+ return FALSE;
+- while ( (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
++ while ( (len = MIN(DELIVER_IN_BUFFER_SIZE, size)) > 0
+ && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
+ {
+ if (!write_chunk(tctx, deliver_in_buffer, len))
+--
+2.20.1
+
--- /dev/null
+From cb25b75af850d664fc005d24fbad0e58bf79d4c7 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 14 Feb 2019 17:14:34 +0000
+Subject: [PATCH 5/7] Fix info on using local_scan() in the default Makefile
+
+Broken-by: 9723f96673
+(cherry picked from commit 882bc1704d33aa34873e3a0f72e657b0cc2985e5)
+---
+ OS/Makefile-Default | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/OS/Makefile-Default b/OS/Makefile-Default
+index b3990fe8..41a4dbbd 100644
+--- a/OS/Makefile-Default
++++ b/OS/Makefile-Default
+@@ -232,6 +232,11 @@ RANLIB=ranlib
+ EXIM_CHMOD=@true
+
+
++# If you want to use local_scan() at all, the support code must be included
++# by uncommenting this line.
++
++# HAVE_LOCAL_SCAN=yes
++
+ # LOCAL_SCAN_SOURCE defines the file in which the function local_scan() is
+ # defined. This provides the administrator with a hook for including C code
+ # for scanning incoming mails. The path that is defined must be relative to
+@@ -239,8 +244,9 @@ EXIM_CHMOD=@true
+
+ # LOCAL_SCAN_SOURCE=Local/local_scan.c
+
+-# The default setting points to a template function that doesn't actually do
+-# any scanning, but just accepts the message.
++# A very simple example points to a template function that doesn't actually do
++# any scanning, but just accepts the message. A compilable file must be
++# included in the build even if HAVE_LOCAL_SCAN is not defined.
+
+ LOCAL_SCAN_SOURCE=src/local_scan.c
+
+--
+2.20.1
+
--- /dev/null
+From c15523829ba17cce5829e2976aa1ff928965d948 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Sat, 16 Feb 2019 12:59:23 +0000
+Subject: [PATCH 7/7] GnuTLS: Fix client detection of server reject of client
+ cert under TLS1.3
+
+(cherry picked from commit fc243e944ec00b59b75f41d07494116f925d58b4)
+---
+ doc/ChangeLog | 7 +++
+ src/deliver.c | 2 +-
+ src/smtp_out.c | 10 +++--
+ src/tls-gnu.c | 23 +++-------
+ src/transports/lmtp.c | 3 +-
+ src/transports/smtp.c | 81 +++++++++++++++++++++++++++--------
+ test/confs/2027 | 8 ++--
+ test/confs/5652 | 1 +
+ test/confs/5821 | 2 +-
+ test/log/2027 | 2 +-
+ test/runtest | 14 ++++++
+ test/scripts/2000-GnuTLS/2027 | 2 +
+ 12 files changed, 111 insertions(+), 44 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 66c8a7a1..867a1d8a 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -11,6 +11,13 @@ Since version 4.92
+ JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible
+ buffer overrun for (non-chunking) other transports.
+
++JH/07 GnuTLS: Our use of late (post-handshake) certificate verification, under
++ TLS1.3, means that a server rejecting a client certificate is not visible
++ to the client until the first read of encrypted data (typically the
++ response to EHLO). Add detection for that case and treat it as a failed
++ TLS connection attempt, so that the normal retry-in-clear can work (if
++ suitably configured).
++
+
+ Exim version 4.92
+ -----------------
+diff --git a/src/deliver.c b/src/deliver.c
+index 664d0045..e1799411 100644
+--- a/src/deliver.c
++++ b/src/deliver.c
+@@ -7433,7 +7433,7 @@ if (addr_senddsn)
+
+ tctx.u.fd = fd;
+ tctx.options = topt_add_return_path | topt_no_body;
+- /*XXX hmm, retval ignored.
++ /*XXX hmm, FALSE(fail) retval ignored.
+ Could error for any number of reasons, and they are not handled. */
+ transport_write_message(&tctx, 0);
+ fflush(f);
+diff --git a/src/smtp_out.c b/src/smtp_out.c
+index 9bd90c77..b194e804 100644
+--- a/src/smtp_out.c
++++ b/src/smtp_out.c
+@@ -688,20 +688,22 @@ Returns: TRUE if a valid, non-error response was received; else FALSE
+ /*XXX could move to smtp transport; no other users */
+
+ BOOL
+-smtp_read_response(void * sx0, uschar *buffer, int size, int okdigit,
++smtp_read_response(void * sx0, uschar * buffer, int size, int okdigit,
+ int timeout)
+ {
+ smtp_context * sx = sx0;
+-uschar *ptr = buffer;
+-int count = 0;
++uschar * ptr = buffer;
++int count = 0, rc;
+
+ errno = 0; /* Ensure errno starts out zero */
+
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->pending_BANNER || sx->pending_EHLO)
+- if (smtp_reap_early_pipe(sx, &count) != OK)
++ if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
+ {
+ DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
++ buffer[0] = '\0';
++ if (rc == DEFER) errno = ERRNO_TLSFAILURE;
+ return FALSE;
+ }
+ #endif
+diff --git a/src/tls-gnu.c b/src/tls-gnu.c
+index c404dc29..de2d70c0 100644
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -229,7 +229,7 @@ static gnutls_dh_params_t dh_server_params = NULL;
+
+ static const int ssl_session_timeout = 200;
+
+-static const char * const exim_default_gnutls_priority = "NORMAL";
++static const uschar * const exim_default_gnutls_priority = US"NORMAL";
+
+ /* Guard library core initialisation */
+
+@@ -1278,7 +1278,6 @@ int rc;
+ size_t sz;
+ const char *errpos;
+ uschar *p;
+-BOOL want_default_priorities;
+
+ if (!exim_gnutls_base_init_done)
+ {
+@@ -1387,32 +1386,24 @@ and replaces gnutls_require_kx, gnutls_require_mac & gnutls_require_protocols.
+ This was backwards incompatible, but means Exim no longer needs to track
+ all algorithms and provide string forms for them. */
+
+-want_default_priorities = TRUE;
+-
++p = NULL;
+ if (state->tls_require_ciphers && *state->tls_require_ciphers)
+ {
+ if (!expand_check_tlsvar(tls_require_ciphers, errstr))
+ return DEFER;
+ if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
+ {
+- DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n",
+- state->exp_tls_require_ciphers);
+-
+- rc = gnutls_priority_init(&state->priority_cache,
+- CS state->exp_tls_require_ciphers, &errpos);
+- want_default_priorities = FALSE;
+ p = state->exp_tls_require_ciphers;
++ DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p);
+ }
+ }
+-if (want_default_priorities)
++if (!p)
+ {
++ p = exim_default_gnutls_priority;
+ DEBUG(D_tls)
+- debug_printf("GnuTLS using default session cipher/priority \"%s\"\n",
+- exim_default_gnutls_priority);
+- rc = gnutls_priority_init(&state->priority_cache,
+- exim_default_gnutls_priority, &errpos);
+- p = US exim_default_gnutls_priority;
++ debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", p);
+ }
++rc = gnutls_priority_init(&state->priority_cache, CCS p, &errpos);
+
+ exim_gnutls_err_check(rc, string_sprintf(
+ "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
+diff --git a/src/transports/lmtp.c b/src/transports/lmtp.c
+index 240d78b2..57b346d4 100644
+--- a/src/transports/lmtp.c
++++ b/src/transports/lmtp.c
+@@ -122,7 +122,8 @@ Arguments:
+ Returns: TRUE if a "QUIT" command should be sent, else FALSE
+ */
+
+-static BOOL check_response(int *errno_value, int more_errno, uschar *buffer,
++static BOOL
++check_response(int *errno_value, int more_errno, uschar *buffer,
+ int *yield, uschar **message)
+ {
+ *yield = '4'; /* Default setting is to give a temporary error */
+diff --git a/src/transports/smtp.c b/src/transports/smtp.c
+index a351da84..bfd6018d 100644
+--- a/src/transports/smtp.c
++++ b/src/transports/smtp.c
+@@ -594,6 +594,11 @@ switch(*errno_value)
+ pl, smtp_command, s);
+ return FALSE;
+
++ case ERRNO_TLSFAILURE: /* Handle bad first read; can happen with
++ GnuTLS and TLS1.3 */
++ *message = US"bad first read from TLS conn";
++ return TRUE;
++
+ case ERRNO_FILTER_FAIL: /* Handle a failed filter process error;
+ can't send QUIT as we mustn't end the DATA. */
+ *message = string_sprintf("transport filter process failed (%d)%s",
+@@ -942,6 +947,7 @@ Arguments:
+
+ Return:
+ OK all well
++ DEFER error on first read of TLS'd conn
+ FAIL SMTP error in response
+ */
+ int
+@@ -949,6 +955,7 @@ smtp_reap_early_pipe(smtp_context * sx, int * countp)
+ {
+ BOOL pending_BANNER = sx->pending_BANNER;
+ BOOL pending_EHLO = sx->pending_EHLO;
++int rc = FAIL;
+
+ sx->pending_BANNER = FALSE; /* clear early to avoid recursion */
+ sx->pending_EHLO = FALSE;
+@@ -960,6 +967,7 @@ if (pending_BANNER)
+ if (!smtp_reap_banner(sx))
+ {
+ DEBUG(D_transport) debug_printf("bad banner\n");
++ if (tls_out.active.sock >= 0) rc = DEFER;
+ goto fail;
+ }
+ }
+@@ -974,6 +982,7 @@ if (pending_EHLO)
+ if (!smtp_reap_ehlo(sx))
+ {
+ DEBUG(D_transport) debug_printf("bad response for EHLO\n");
++ if (tls_out.active.sock >= 0) rc = DEFER;
+ goto fail;
+ }
+
+@@ -1011,7 +1020,7 @@ return OK;
+ fail:
+ invalidate_ehlo_cache_entry(sx);
+ (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
+- return FAIL;
++ return rc;
+ }
+ #endif
+
+@@ -1056,6 +1065,7 @@ Returns: 3 if at least one address had 2xx and one had 5xx
+ -2 I/O or other non-response error for RCPT
+ -3 DATA or MAIL failed - errno and buffer set
+ -4 banner or EHLO failed (early-pipelining)
++ -5 banner or EHLO failed (early-pipelining, TLS)
+ */
+
+ static int
+@@ -1064,10 +1074,11 @@ sync_responses(smtp_context * sx, int count, int pending_DATA)
+ address_item * addr = sx->sync_addr;
+ smtp_transport_options_block * ob = sx->conn_args.ob;
+ int yield = 0;
++int rc;
+
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+-if (smtp_reap_early_pipe(sx, &count) != OK)
+- return -4;
++if ((rc = smtp_reap_early_pipe(sx, &count)) != OK)
++ return rc == FAIL ? -4 : -5;
+ #endif
+
+ /* Handle the response for a MAIL command. On error, reinstate the original
+@@ -1083,6 +1094,8 @@ if (sx->pending_MAIL)
+ {
+ DEBUG(D_transport) debug_printf("bad response for MAIL\n");
+ Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */
++ if (errno == ERRNO_TLSFAILURE)
++ return -5;
+ if (errno == 0 && sx->buffer[0] != 0)
+ {
+ int save_errno = 0;
+@@ -1141,6 +1154,11 @@ while (count-- > 0)
+ }
+ }
+
++ /* Error on first TLS read */
++
++ else if (errno == ERRNO_TLSFAILURE)
++ return -5;
++
+ /* Timeout while reading the response */
+
+ else if (errno == ETIMEDOUT)
+@@ -1253,6 +1271,10 @@ if (pending_DATA != 0)
+ int code;
+ uschar *msg;
+ BOOL pass_message;
++
++ if (errno == ERRNO_TLSFAILURE) /* Error on first TLS read */
++ return -5;
++
+ if (pending_DATA > 0 || (yield & 1) != 0)
+ {
+ if (errno == 0 && sx->buffer[0] == '4')
+@@ -1802,7 +1824,9 @@ Args:
+ tc_chunk_last add LAST option to SMTP BDAT command
+ tc_reap_prev reap response to previous SMTP commands
+
+-Returns: OK or ERROR
++Returns:
++ OK or ERROR
++ DEFER TLS error on first read (EHLO-resp); errno set
+ */
+
+ static int
+@@ -1859,10 +1883,12 @@ if (flags & tc_reap_prev && prev_cmd_count > 0)
+ case 2: sx->completed_addr = TRUE; /* 5xx (only) => progress made */
+ case 0: break; /* No 2xx or 5xx, but no probs */
+
+- case -1: /* Timeout on RCPT */
++ case -5: errno = ERRNO_TLSFAILURE;
++ return DEFER;
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: /* non-2xx for pipelined banner or EHLO */
+ #endif
++ case -1: /* Timeout on RCPT */
+ default: return ERROR; /* I/O error, or any MAIL/DATA error */
+ }
+ cmd_count = 1;
+@@ -1933,6 +1959,9 @@ BOOL pass_message = FALSE;
+ uschar * message = NULL;
+ int yield = OK;
+ int rc;
++#ifdef SUPPORT_TLS
++uschar * tls_errstr;
++#endif
+
+ sx->conn_args.ob = ob;
+
+@@ -2474,27 +2503,27 @@ if ( smtp_peer_options & OPTION_TLS
+ TLS_NEGOTIATE:
+ {
+ address_item * addr;
+- uschar * errstr;
+ sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->conn_args.host,
+ sx->addrlist, sx->conn_args.tblock,
+ # ifdef SUPPORT_DANE
+ sx->dane ? &tlsa_dnsa : NULL,
+ # endif
+- &tls_out, &errstr);
++ &tls_out, &tls_errstr);
+
+ if (!sx->cctx.tls_ctx)
+ {
+ /* TLS negotiation failed; give an error. From outside, this function may
+ be called again to try in clear on a new connection, if the options permit
+ it for this host. */
+- DEBUG(D_tls) debug_printf("TLS session fail: %s\n", errstr);
++GNUTLS_CONN_FAILED:
++ DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr);
+
+ # ifdef SUPPORT_DANE
+ if (sx->dane)
+ {
+ log_write(0, LOG_MAIN,
+ "DANE attempt failed; TLS connection to %s [%s]: %s",
+- sx->conn_args.host->name, sx->conn_args.host->address, errstr);
++ sx->conn_args.host->name, sx->conn_args.host->address, tls_errstr);
+ # ifndef DISABLE_EVENT
+ (void) event_raise(sx->conn_args.tblock->event_action,
+ US"dane:fail", US"validation-failure"); /* could do with better detail */
+@@ -2503,7 +2532,7 @@ if ( smtp_peer_options & OPTION_TLS
+ # endif
+
+ errno = ERRNO_TLSFAILURE;
+- message = string_sprintf("TLS session: %s", errstr);
++ message = string_sprintf("TLS session: %s", tls_errstr);
+ sx->send_quit = FALSE;
+ goto TLS_FAILED;
+ }
+@@ -2601,7 +2630,22 @@ if (tls_out.active.sock >= 0)
+ #endif
+ {
+ if (!smtp_reap_ehlo(sx))
++#ifdef USE_GNUTLS
++ {
++ /* The GnuTLS layer in Exim only spots a server-rejection of a client
++ cert late, under TLS1.3 - which means here; the first time we try to
++ receive crypted data. Treat it as if it was a connect-time failure.
++ See also the early-pipe equivalent... which will be hard; every call
++ to sync_responses will need to check the result.
++ It would be nicer to have GnuTLS check the cert during the handshake.
++ Can it do that, with all the flexibility we need? */
++
++ tls_errstr = US"error on first read";
++ goto GNUTLS_CONN_FAILED;
++ }
++#else
+ goto RESPONSE_FAILED;
++#endif
+ smtp_peer_options = 0;
+ }
+ }
+@@ -3261,6 +3305,7 @@ for (addr = sx->first_addr, address_count = 0;
+
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: return -1; /* non-2xx for pipelined banner or EHLO */
++ case -5: return -1; /* TLS first-read error */
+ #endif
+ }
+ sx->pending_MAIL = FALSE; /* Dealt with MAIL */
+@@ -3589,11 +3634,12 @@ if ( !(sx.peer_offered & OPTION_CHUNKING)
+
+ case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
+ if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
+- case 0: break; /* No 2xx or 5xx, but no probs */
++ case 0: break; /* No 2xx or 5xx, but no probs */
+
+- case -1: goto END_OFF; /* Timeout on RCPT */
++ case -1: goto END_OFF; /* Timeout on RCPT */
+
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
++ case -5: /* TLS first-read error */
+ case -4: HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+ #endif
+@@ -3730,19 +3776,20 @@ else
+ {
+ case 3: sx.ok = TRUE; /* 2xx & 5xx => OK & progress made */
+ case 2: sx.completed_addr = TRUE; /* 5xx (only) => progress made */
+- break;
++ break;
+
+- case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
++ case 1: sx.ok = TRUE; /* 2xx (only) => OK, but if LMTP, */
+ if (!sx.lmtp) sx.completed_addr = TRUE; /* can't tell about progress yet */
+- case 0: break; /* No 2xx or 5xx, but no probs */
++ case 0: break; /* No 2xx or 5xx, but no probs */
+
+- case -1: goto END_OFF; /* Timeout on RCPT */
++ case -1: goto END_OFF; /* Timeout on RCPT */
+
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
++ case -5: /* TLS first-read error */
+ case -4: HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+ #endif
+- default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
++ default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
+ }
+ }
+
+--
+2.20.1
+
--- /dev/null
+From f634b80846cc7ffcab65c9855bcb35312f0232e8 Mon Sep 17 00:00:00 2001
+From: Jasen Betts <jasen@xnet.co.nz>
+Date: Mon, 18 Feb 2019 13:52:16 +0000
+Subject: [PATCH 1/5] Fix expansions for RFC 822 addresses having comments in
+ local-part and/or domain. Bug 2375
+
+(cherry picked from commit e2ff8e24f41caca3623228b1ec66a3f3961ecad6)
+---
+ doc/ChangeLog | 3 +++
+ src/expand.c | 19 +++++++------------
+ test/scripts/0000-Basic/0002 | 7 +++++++
+ test/stdout/0002 | 7 +++++++
+ 4 files changed, 24 insertions(+), 12 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 867a1d8a..9659da32 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -16,10 +16,13 @@ JH/07 GnuTLS: Our use of late (post-handshake) certificate verification, under
+ to the client until the first read of encrypted data (typically the
+ response to EHLO). Add detection for that case and treat it as a failed
+ TLS connection attempt, so that the normal retry-in-clear can work (if
+ suitably configured).
+
++JB/01 BZg 2375: fix expansions of 822 addresses having comments in local-part
++ and/or domain. Found and fixed by Jason Betts.
++
+
+ Exim version 4.92
+ -----------------
+
+ JH/01 Remove code calling the customisable local_scan function, unless a new
+diff --git a/src/expand.c b/src/expand.c
+index 2c290251..35ede718 100644
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -7071,20 +7071,15 @@ while (*s != 0)
+ uschar * error;
+ int start, end, domain;
+ uschar * t = parse_extract_address(sub, &error, &start, &end, &domain,
+ FALSE);
+ if (t)
+- if (c != EOP_DOMAIN)
+- {
+- if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1;
+- yield = string_catn(yield, sub+start, end-start);
+- }
+- else if (domain != 0)
+- {
+- domain += start;
+- yield = string_catn(yield, sub+domain, end-domain);
+- }
++ yield = c == EOP_DOMAIN
++ ? string_cat(yield, t + domain)
++ : c == EOP_LOCAL_PART && domain > 0
++ ? string_catn(yield, t, domain - 1 )
++ : string_cat(yield, t);
+ continue;
+ }
+
+ case EOP_ADDRESSES:
+ {
+@@ -7104,11 +7099,11 @@ while (*s != 0)
+ }
+ f.parse_allow_group = TRUE;
+
+ for (;;)
+ {
+- uschar *p = parse_find_address_end(sub, FALSE);
++ uschar * p = parse_find_address_end(sub, FALSE);
+ uschar saveend = *p;
+ *p = '\0';
+ address = parse_extract_address(sub, &error, &start, &end, &domain,
+ FALSE);
+ *p = saveend;
+@@ -7117,11 +7112,11 @@ while (*s != 0)
+ done in chunks by searching for the separator character. At the
+ start, unless we are dealing with the first address of the output
+ list, add in a space if the new address begins with the separator
+ character, or is an empty string. */
+
+- if (address != NULL)
++ if (address)
+ {
+ if (yield->ptr != save_ptr && address[0] == *outsep)
+ yield = string_catn(yield, US" ", 1);
+
+ for (;;)
+--
+2.20.1
+
--- /dev/null
+From 8dde16b89efe2138f92cbfa6c59fb31dc80ec22a Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 19 Feb 2019 14:45:27 +0000
+Subject: [PATCH 2/5] Docs: Add note on lsearch for IPv4-mapped IPv6 addresses
+
+Cherry-picked from: 52af443324, c77d3d85fe
+---
+ doc/doc-docbook/spec.xfpt | 11 ++++++++++-
+ doc/ChangeLog | 2 +-
+ 2 files changed, 11 insertions(+), 2 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -18,7 +18,7 @@ JH/07 GnuTLS: Our use of late (post-hand
+ TLS connection attempt, so that the normal retry-in-clear can work (if
+ suitably configured).
+
+-JB/01 BZg 2375: fix expansions of 822 addresses having comments in local-part
++JB/01 Bug 2375: fix expansions of 822 addresses having comments in local-part
+ and/or domain. Found and fixed by Jason Betts.
+
+
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -6302,6 +6302,10 @@ The following single-key lookup types ar
+ implicit key is the host's IP address rather than its name (see section
+ 10.12).
+
++ Warning 3: Do not use an IPv4-mapped IPv6 address for a key; use the
++ IPv4, in dotted-quad form. (Exim converts IPv4-mapped IPv6 addresses to
++ this notation before executing the lookup.)
++
+ * lsearch: The given file is a text file that is searched linearly for a line
+ beginning with the search key, terminated by a colon or white space or the
+ end of the line. The search is case-insensitive; that is, upper and lower
+@@ -8003,7 +8007,11 @@ quote keys was made available in lsearch
+ implemented iplsearch files do require colons in IPv6 keys (notated using the
+ quoting facility) so as to distinguish them from IPv4 keys. For this reason,
+ when the lookup type is iplsearch, IPv6 addresses are converted using colons
+-and not dots. In all cases, full, unabbreviated IPv6 addresses are always used.
++and not dots.
++
++In all cases except IPv4-mapped IPv6, full, unabbreviated IPv6 addresses
++are always used. The latter are converted to IPv4 addresses, in dotted-quad
++form.
+
+ Ideally, it would be nice to tidy up this anomalous situation by changing to
+ colons in all cases, given that quoting is now available for lsearch. However,
--- /dev/null
+From 09720dd9506176294154dad7152f5f40554046a4 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 14 Mar 2019 12:26:34 +0000
+Subject: [PATCH 3/5] Fix crash from SRV lookup hitting a CNAME
+
+(cherry picked from commit 14bc9cf085aff7bd5147881e5b7068769a29b026)
+---
+ doc/ChangeLog | 4 ++++
+ src/dns.c | 10 +++++++---
+ 2 files changed, 11 insertions(+), 3 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 419c1061..0f8d05b2 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -19,10 +19,14 @@ JH/07 GnuTLS: Our use of late (post-handshake) certificate verification, under
+ suitably configured).
+
+ JB/01 Bug 2375: fix expansions of 822 addresses having comments in local-part
+ and/or domain. Found and fixed by Jason Betts.
+
++JH/08 Add hardening against SRV & TLSA lookups the hit CNAMEs (a nonvalid
++ configuration). If a CNAME target was not a wellformed name pattern, a
++ crash could result.
++
+
+ Exim version 4.92
+ -----------------
+
+ JH/01 Remove code calling the customisable local_scan function, unless a new
+diff --git a/src/dns.c b/src/dns.c
+index 0f0b435d..b7978c52 100644
+--- a/src/dns.c
++++ b/src/dns.c
+@@ -714,11 +714,15 @@ regex has substrings that are used - the default uses a conditional.
+ This test is omitted for PTR records. These occur only in calls from the dnsdb
+ lookup, which constructs the names itself, so they should be OK. Besides,
+ bitstring labels don't conform to normal name syntax. (But the aren't used any
+ more.)
+
+-For SRV records, we omit the initial _smtp._tcp. components at the start. */
++For SRV records, we omit the initial _smtp._tcp. components at the start.
++The check has been seen to bite on the destination of a SRV lookup that
++initiall hit a CNAME, for which the next name had only two components.
++RFC2782 makes no mention of the possibiility of CNAMES, but the Wikipedia
++article on SRV says they are not a valid configuration. */
+
+ #ifndef STAND_ALONE /* Omit this for stand-alone tests */
+
+ if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
+ {
+@@ -730,12 +734,12 @@ if (check_dns_names_pattern[0] != 0 && type != T_PTR && type != T_TXT)
+ /* For an SRV lookup, skip over the first two components (the service and
+ protocol names, which both start with an underscore). */
+
+ if (type == T_SRV || type == T_TLSA)
+ {
+- while (*checkname++ != '.');
+- while (*checkname++ != '.');
++ while (*checkname && *checkname++ != '.') ;
++ while (*checkname && *checkname++ != '.') ;
+ }
+
+ if (pcre_exec(regex_check_dns_names, NULL, CCS checkname, Ustrlen(checkname),
+ 0, PCRE_EOPT, ovector, nelem(ovector)) < 0)
+ {
+--
+2.20.1
+
--- /dev/null
+From e5be948a65fe601024e5d4256f64efbfed3dd72e Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Mon, 18 Mar 2019 00:31:43 +0000
+Subject: [PATCH 4/5] Logging: fix initial listening-on log line
+
+(cherry picked from commit 254f38d1c5ada5e4df0bccb385dc466549620c71)
+---
+ doc/ChangeLog | 4 +++
+ src/daemon.c | 73 +++++++++++++++++++++++++++----------------
+ src/host.c | 1 +
+ src/structs.h | 1 +
+ test/confs/0282 | 2 +-
+ test/log/0282 | 2 +-
+ 6 files changed, 54 insertions(+), 29 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 0f8d05b2..3c0ffbf0 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -23,10 +23,14 @@ JB/01 Bug 2375: fix expansions of 822 addresses having comments in local-part
+
+ JH/08 Add hardening against SRV & TLSA lookups the hit CNAMEs (a nonvalid
+ configuration). If a CNAME target was not a wellformed name pattern, a
+ crash could result.
+
++JH/09 Logging: Fix initial listening-on line for multiple ports for an IP when
++ the OS reports them interleaved with other addresses.
++
++
+
+ Exim version 4.92
+ -----------------
+
+ JH/01 Remove code calling the customisable local_scan function, unless a new
+diff --git a/src/daemon.c b/src/daemon.c
+index a852192e..01da3936 100644
+--- a/src/daemon.c
++++ b/src/daemon.c
+@@ -1625,12 +1625,12 @@ if (f.inetd_wait_mode)
+ else if (f.daemon_listen)
+ {
+ int i, j;
+ int smtp_ports = 0;
+ int smtps_ports = 0;
+- ip_address_item * ipa, * i2;
+- uschar * p = big_buffer;
++ ip_address_item * ipa;
++ uschar * p;
+ uschar * qinfo = queue_interval > 0
+ ? string_sprintf("-q%s", readconf_printtime(queue_interval))
+ : US"no queue runs";
+
+ /* Build a list of listening addresses in big_buffer, but limit it to 10
+@@ -1638,73 +1638,92 @@ else if (f.daemon_listen)
+
+ It is now possible to have some ports listening for SMTPS (the old,
+ deprecated protocol that starts TLS without using STARTTLS), and others
+ listening for standard SMTP. Keep their listings separate. */
+
+- for (j = 0; j < 2; j++)
++ for (int j = 0, i; j < 2; j++)
+ {
+ for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
+ {
+ /* First time round, look for SMTP ports; second time round, look for
+- SMTPS ports. For the first one of each, insert leading text. */
++ SMTPS ports. Build IP+port strings. */
+
+ if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
+ {
+ if (j == 0)
+- {
+- if (smtp_ports++ == 0)
+- {
+- memcpy(p, "SMTP on", 8);
+- p += 7;
+- }
+- }
++ smtp_ports++;
+ else
+- if (smtps_ports++ == 0)
+- p += sprintf(CS p, "%sSMTPS on",
+- smtp_ports == 0 ? "" : " and for ");
++ smtps_ports++;
+
+ /* Now the information about the port (and sometimes interface) */
+
+ if (ipa->address[0] == ':' && ipa->address[1] == 0)
+ { /* v6 wildcard */
+ if (ipa->next && ipa->next->address[0] == 0 &&
+ ipa->next->port == ipa->port)
+ {
+- p += sprintf(CS p, " port %d (IPv6 and IPv4)", ipa->port);
+- ipa = ipa->next;
++ ipa->log = string_sprintf(" port %d (IPv6 and IPv4)", ipa->port);
++ (ipa = ipa->next)->log = NULL;
+ }
+ else if (ipa->v6_include_v4)
+- p += sprintf(CS p, " port %d (IPv6 with IPv4)", ipa->port);
++ ipa->log = string_sprintf(" port %d (IPv6 with IPv4)", ipa->port);
+ else
+- p += sprintf(CS p, " port %d (IPv6)", ipa->port);
++ ipa->log = string_sprintf(" port %d (IPv6)", ipa->port);
+ }
+ else if (ipa->address[0] == 0) /* v4 wildcard */
+- p += sprintf(CS p, " port %d (IPv4)", ipa->port);
++ ipa->log = string_sprintf(" port %d (IPv4)", ipa->port);
+ else /* check for previously-seen IP */
+ {
++ ip_address_item * i2;
+ for (i2 = addresses; i2 != ipa; i2 = i2->next)
+ if ( host_is_tls_on_connect_port(i2->port) == (j > 0)
+ && Ustrcmp(ipa->address, i2->address) == 0
+ )
+ { /* found; append port to list */
+- if (p[-1] == '}') p--;
+- while (isdigit(*--p)) ;
+- p += 1 + sprintf(CS p+1, "%s%d,%d}", *p == ',' ? "" : "{",
+- i2->port, ipa->port);
++ for (p = i2->log; *p; ) p++; /* end of existing string */
++ if (*--p == '}') *p = '\0'; /* drop EOL */
++ while (isdigit(*--p)) ; /* char before port */
++
++ i2->log = *p == ':' /* no list yet? */
++ ? string_sprintf("%.*s{%s,%d}",
++ (int)(p - i2->log + 1), i2->log, p+1, ipa->port)
++ : string_sprintf("%s,%d}", i2->log, ipa->port);
++ ipa->log = NULL;
+ break;
+ }
+ if (i2 == ipa) /* first-time IP */
+- p += sprintf(CS p, " [%s]:%d", ipa->address, ipa->port);
++ ipa->log = string_sprintf(" [%s]:%d", ipa->address, ipa->port);
+ }
+ }
+ }
++ }
+
+- if (ipa)
++ p = big_buffer;
++ for (int j = 0, i; j < 2; j++)
++ {
++ /* First time round, look for SMTP ports; second time round, look for
++ SMTPS ports. For the first one of each, insert leading text. */
++
++ if (j == 0)
+ {
+- memcpy(p, " ...", 5);
+- p += 4;
++ if (smtp_ports > 0)
++ p += sprintf(CS p, "SMTP on");
+ }
++ else
++ if (smtps_ports > 0)
++ p += sprintf(CS p, "%sSMTPS on",
++ smtp_ports == 0 ? "" : " and for ");
++
++ /* Now the information about the port (and sometimes interface) */
++
++ for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
++ if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
++ if (ipa->log)
++ p += sprintf(CS p, "%s", ipa->log);
++
++ if (ipa)
++ p += sprintf(CS p, " ...");
+ }
+
+ log_write(0, LOG_MAIN,
+ "exim %s daemon started: pid=%d, %s, listening for %s",
+ version_string, getpid(), qinfo, big_buffer);
+diff --git a/src/host.c b/src/host.c
+index 29c977fe..a3b0977b 100644
+--- a/src/host.c
++++ b/src/host.c
+@@ -757,10 +757,11 @@ while ((s = string_nextinlist(&list, &sep, NULL, 0)))
+ next = store_get(sizeof(ip_address_item));
+ next->next = NULL;
+ Ustrcpy(next->address, s);
+ next->port = port;
+ next->v6_include_v4 = FALSE;
++ next->log = NULL;
+
+ if (!yield)
+ yield = last = next;
+ else
+ {
+diff --git a/src/structs.h b/src/structs.h
+index 20db0e5f..1e63d752 100644
+--- a/src/structs.h
++++ b/src/structs.h
+@@ -442,10 +442,11 @@ hold an IPv6 address. */
+ typedef struct ip_address_item {
+ struct ip_address_item *next;
+ int port;
+ BOOL v6_include_v4; /* Used in the daemon */
+ uschar address[46];
++ uschar * log; /* portion of "listening on" log line */
+ } ip_address_item;
+
+ /* Structure for chaining together arbitrary strings. */
+
+ typedef struct string_item {
+--
+2.20.1
+
--- /dev/null
+From 332ebeaf8139b2b75f475880fc14b63c7c45c706 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 19 Mar 2019 15:33:31 +0000
+Subject: [PATCH 5/5] OpenSSL: Fix aggregation of messages.
+
+Broken-by: a5ffa9b475
+(cherry picked from commit c09dbcfb71f4b9a42cbfd8a20e0be6bfa1b12488)
+---
+ doc/ChangeLog | 5 +++
+ src/tls-openssl.c | 24 ++++++++++----
+ test/confs/2152 | 76 +++++++++++++++++++++++++++++++++++++++++++
+ test/log/2152 | 9 +++++
+ 4 files changed, 108 insertions(+), 6 deletions(-)
+ create mode 100644 test/confs/2152
+ create mode 100644 test/log/2152
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 3c0ffbf0..3d63725f 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -26,10 +26,15 @@ JH/08 Add hardening against SRV & TLSA lookups the hit CNAMEs (a nonvalid
+ crash could result.
+
+ JH/09 Logging: Fix initial listening-on line for multiple ports for an IP when
+ the OS reports them interleaved with other addresses.
+
++JH/10 OpenSSL: Fix aggregation of messages. Previously, when PIPELINING was
++ used both for input and for a verify callout, both encrypted, SMTP
++ responses being sent by the server could be lost. This resulted in
++ dropped connections and sometimes bounces generated by a peer sending
++ to this system.
+
+
+ Exim version 4.92
+ -----------------
+
+diff --git a/src/tls-openssl.c b/src/tls-openssl.c
+index 8f4cf4d8..cc0ead02 100644
+--- a/src/tls-openssl.c
++++ b/src/tls-openssl.c
+@@ -272,10 +272,11 @@ Server:
+ */
+
+ typedef struct {
+ SSL_CTX * ctx;
+ SSL * ssl;
++ gstring * corked;
+ } exim_openssl_client_tls_ctx;
+
+ static SSL_CTX *server_ctx = NULL;
+ static SSL *server_ssl = NULL;
+
+@@ -2471,10 +2472,11 @@ BOOL require_ocsp = FALSE;
+ #endif
+
+ rc = store_pool;
+ store_pool = POOL_PERM;
+ exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx));
++exim_client_ctx->corked = NULL;
+ store_pool = rc;
+
+ #ifdef SUPPORT_DANE
+ tlsp->tlsa_usage = 0;
+ #endif
+@@ -2906,22 +2908,29 @@ Used by both server-side and client-side TLS.
+
+ int
+ tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
+ {
+ int outbytes, error, left;
+-SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
+-static gstring * corked = NULL;
++SSL * ssl = ct_ctx
++ ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
++static gstring * server_corked = NULL;
++gstring ** corkedp = ct_ctx
++ ? &((exim_openssl_client_tls_ctx *)ct_ctx)->corked : &server_corked;
++gstring * corked = *corkedp;
+
+ DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
+ buff, (unsigned long)len, more ? ", more" : "");
+
+ /* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when
+ "more" is notified. This hack is only ok if small amounts are involved AND only
+ one stream does it, in one context (i.e. no store reset). Currently it is used
+-for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */
+-/*XXX + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
+-a store reset there. */
++for the responses to the received SMTP MAIL , RCPT, DATA sequence, only.
++We support callouts done by the server process by using a separate client
++context for the stashed information. */
++/* + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
++a store reset there, so use POOL_PERM. */
++/* + if CHUNKING, cmds EHLO,MAIL,RCPT(s),BDAT */
+
+ if (!ct_ctx && (more || corked))
+ {
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+ int save_pool = store_pool;
+@@ -2933,14 +2942,17 @@ if (!ct_ctx && (more || corked))
+ #ifdef EXPERIMENTAL_PIPE_CONNECT
+ store_pool = save_pool;
+ #endif
+
+ if (more)
++ {
++ *corkedp = corked;
+ return len;
++ }
+ buff = CUS corked->s;
+ len = corked->ptr;
+- corked = NULL;
++ *corkedp = NULL;
+ }
+
+ for (left = len; left > 0;)
+ {
+ DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
+diff --git a/test/confs/2152 b/test/confs/2152
+new file mode 100644
+index 00000000..f783192b
+diff --git a/test/log/2152 b/test/log/2152
+new file mode 100644
+index 00000000..720200be
+--
+2.20.1
+
--- /dev/null
+From e5b942ae007d0533fbd599c64d550f3a8355b940 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 21 Mar 2019 20:01:03 +0000
+Subject: [PATCH] Harden plaintext authenticator
+
+Cherry-picked from: f9fc942757
+---
+ doc/ChangeLog | 5 +++++
+ src/auths/plaintext.c | 6 +-----
+ 2 files changed, 6 insertions(+), 5 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 3d63725f..c34e60d1 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -32,10 +32,15 @@ JH/10 OpenSSL: Fix aggregation of messages. Previously, when PIPELINING was
+ used both for input and for a verify callout, both encrypted, SMTP
+ responses being sent by the server could be lost. This resulted in
+ dropped connections and sometimes bounces generated by a peer sending
+ to this system.
+
++JH/11 Harden plaintext authenticator against a badly misconfigured client-send
++ string. Previously it was possible to cause undefined behaviour in a
++ library routine (usually a crash). Found by "zerons".
++
++
+
+ Exim version 4.92
+ -----------------
+
+ JH/01 Remove code calling the customisable local_scan function, unless a new
+diff --git a/src/auths/plaintext.c b/src/auths/plaintext.c
+index 7a0f7885..fa05b0ad 100644
+--- a/src/auths/plaintext.c
++++ b/src/auths/plaintext.c
+@@ -221,15 +221,11 @@ while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)))
+ for (i = 0; i < len; i++)
+ if (ss[i] == '^')
+ if (ss[i+1] != '^')
+ ss[i] = 0;
+ else
+- {
+- i++;
+- len--;
+- memmove(ss + i, ss + i + 1, len - i);
+- }
++ if (--len > ++i) memmove(ss + i, ss + i + 1, len - i);
+
+ /* The first string is attached to the AUTH command; others are sent
+ unembellished. */
+
+ if (first)
+--
+2.20.1
+
--- /dev/null
+From 5e64b73ef7cdaf20b998b3345a588b462fd30bfb Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 7 May 2019 22:55:41 +0100
+Subject: [PATCH] GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp
+
+(cherry picked from commit 7a501c874f028f689c44999ab05bb0d39da46941)
+---
+ doc/ChangeLog | 3 +++
+ src/tls-gnu.c | 12 ++++++++----
+ test/log/5651 | 2 +-
+ test/log/5730 | 8 ++++----
+ 4 files changed, 16 insertions(+), 9 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -39,6 +39,9 @@ JH/11 Harden plaintext authenticator aga
+ library routine (usually a crash). Found by "zerons".
+
+
++JH/18 GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp. Previously the
++ verification result was not updated unless hosts_require_ocsp applied.
++
+
+ Exim version 4.92
+ -----------------
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -2450,7 +2450,7 @@ if (!verify_certificate(state, errstr))
+ }
+
+ #ifndef DISABLE_OCSP
+-if (require_ocsp)
++if (request_ocsp)
+ {
+ DEBUG(D_tls)
+ {
+@@ -2474,10 +2474,14 @@ if (require_ocsp)
+ {
+ tlsp->ocsp = OCSP_FAILED;
+ tls_error(US"certificate status check failed", NULL, state->host, errstr);
+- return NULL;
++ if (require_ocsp)
++ return FALSE;
++ }
++ else
++ {
++ DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
++ tlsp->ocsp = OCSP_VFIED;
+ }
+- DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
+- tlsp->ocsp = OCSP_VFIED;
+ }
+ #endif
+
--- /dev/null
+From 44893ba5249c6c6d5a0d62a1cc57ba3fbf7185b4 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Sun, 19 May 2019 12:12:36 +0100
+Subject: [PATCH 1/2] GnuTLS: fix the advertising of acceptable certs by the
+ server. Bug 2389
+
+(cherry picked from commit 12d95aa62042377fc9f603245a17a43142972447)
+---
+ doc/ChangeLog | 4 ++++
+ src/tls-gnu.c | 8 ++++++++
+ 2 files changed, 12 insertions(+)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -42,6 +42,10 @@ JH/11 Harden plaintext authenticator aga
+ JH/18 GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp. Previously the
+ verification result was not updated unless hosts_require_ocsp applied.
+
++JH/20 Bug 2389: fix server advertising of usable certificates, under GnuTLS in
++ directory-of-certs mode. Previously they were advertised despite the
++ documentation.
++
+
+ Exim version 4.92
+ -----------------
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -1133,6 +1133,14 @@ else
+ #endif
+ gnutls_certificate_set_x509_trust_file(state->x509_cred,
+ CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM);
++
++#ifdef SUPPORT_CA_DIR
++ /* Mimic the behaviour with OpenSSL of not advertising a usable-cert list
++ when using the directory-of-certs config model. */
++
++ if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
++ gnutls_certificate_send_x509_rdn_sequence(state->session, 1);
++#endif
+ }
+
+ if (cert_count < 0)
--- /dev/null
+From 454bab46ae6812e29652d10c390451c962a6f806 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 4 Jun 2019 18:13:21 +0100
+Subject: [PATCH 2/2] Use dsn_from for success-DSN messages. Bug 2404
+
+(cherry picked from commit 87abcb247b4444bab5fd0bcb212ddb26d5fd9191)
+---
+ doc/ChangeLog | 4 ++++
+ src/deliver.c | 4 ++--
+ 2 files changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/doc/ChangeLog b/doc/ChangeLog
+index 5a3e453d..1a12c014 100644
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -65,6 +65,10 @@ JH/20 Bug 2389: fix server advertising of usable certificates, under GnuTLS in
+ directory-of-certs mode. Previously they were advertised despite the
+ documentation.
+
++JH/27 Bug 2404: Use the main-section configuration option "dsn_from" for
++ success-DSN messages. Previously the From: header was always the default
++ one for these; the option was ignored.
++
+
+ Exim version 4.92
+ -----------------
+diff --git a/src/deliver.c b/src/deliver.c
+index e1799411..4720f596 100644
+--- a/src/deliver.c
++++ b/src/deliver.c
+@@ -7365,8 +7365,8 @@ if (addr_senddsn)
+ if (errors_reply_to)
+ fprintf(f, "Reply-To: %s\n", errors_reply_to);
+
++ moan_write_from(f);
+ fprintf(f, "Auto-Submitted: auto-generated\n"
+- "From: Mail Delivery System <Mailer-Daemon@%s>\n"
+ "To: %s\n"
+ "Subject: Delivery Status Notification\n"
+ "Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n"
+@@ -7377,7 +7377,7 @@ if (addr_senddsn)
+
+ "This message was created automatically by mail delivery software.\n"
+ " ----- The following addresses had successful delivery notifications -----\n",
+- qualify_domain_sender, sender_address, bound, bound);
++ sender_address, bound, bound);
+
+ for (addr_dsntmp = addr_senddsn; addr_dsntmp;
+ addr_dsntmp = addr_dsntmp->next)
+--
+2.20.1
+
--- /dev/null
+From 0a5441fcd93ae4145c07b3ed138dfe0e107174e0 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Mon, 27 May 2019 23:44:31 +0100
+Subject: [PATCH 1/2] Fix smtp response timeout
+
+---
+ doc/ChangeLog | 6 ++++++
+ src/functions.h | 4 ++--
+ src/ip.c | 16 +++++++---------
+ src/malware.c | 26 +++++++++++++-------------
+ src/routers/iplookup.c | 2 +-
+ src/smtp_out.c | 9 +++++----
+ src/spam.c | 2 +-
+ src/transports/smtp_socks.c | 6 +++---
+ src/verify.c | 2 +-
+ 9 files changed, 39 insertions(+), 34 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -50,6 +50,13 @@ JH/27 Bug 2404: Use the main-section con
+ success-DSN messages. Previously the From: header was always the default
+ one for these; the option was ignored.
+
++JH/28 Fix the timeout on smtp response to apply to the whole response.
++ Previously it was reset for every read, so a teergrubing peer sending
++ single bytes within the time limit could extend the connection for a
++ long time. Credit to Qualsys Security Advisory Team for the discovery.
++[from GIT master]
++
++
+
+ Exim version 4.92
+ -----------------
+--- a/src/functions.h
++++ b/src/functions.h
+@@ -225,7 +225,7 @@ extern uschar *expand_string_copy(const
+ extern int_eximarith_t expand_string_integer(uschar *, BOOL);
+ extern void modify_variable(uschar *, void *);
+
+-extern BOOL fd_ready(int, int);
++extern BOOL fd_ready(int, time_t);
+
+ extern int filter_interpret(uschar *, int, address_item **, uschar **);
+ extern BOOL filter_personal(string_item *, BOOL);
+@@ -271,7 +271,7 @@ extern int ip_connectedsocket(int, c
+ int, host_item *, uschar **, const blob *);
+ extern int ip_get_address_family(int);
+ extern void ip_keepalive(int, const uschar *, BOOL);
+-extern int ip_recv(client_conn_ctx *, uschar *, int, int);
++extern int ip_recv(client_conn_ctx *, uschar *, int, time_t);
+ extern int ip_socket(int, int);
+
+ extern int ip_tcpsocket(const uschar *, uschar **, int);
+--- a/src/ip.c
++++ b/src/ip.c
+@@ -566,16 +566,15 @@ if (setsockopt(sock, SOL_SOCKET, SO_KEEP
+ /*
+ Arguments:
+ fd the file descriptor
+- timeout the timeout, seconds
++ timelimit the timeout endpoint, seconds-since-epoch
+ Returns: TRUE => ready for i/o
+ FALSE => timed out, or other error
+ */
+ BOOL
+-fd_ready(int fd, int timeout)
++fd_ready(int fd, time_t timelimit)
+ {
+ fd_set select_inset;
+-time_t start_recv = time(NULL);
+-int time_left = timeout;
++int time_left = timelimit - time(NULL);
+ int rc;
+
+ if (time_left <= 0)
+@@ -609,8 +608,7 @@ do
+ DEBUG(D_transport) debug_printf("EINTR while waiting for socket data\n");
+
+ /* Watch out, 'continue' jumps to the condition, not to the loops top */
+- time_left = timeout - (time(NULL) - start_recv);
+- if (time_left > 0) continue;
++ if ((time_left = timelimit - time(NULL)) > 0) continue;
+ }
+
+ if (rc <= 0)
+@@ -634,18 +632,18 @@ Arguments:
+ cctx the connection context (socket fd, possibly TLS context)
+ buffer to read into
+ bufsize the buffer size
+- timeout the timeout
++ timelimit the timeout endpoint, seconds-since-epoch
+
+ Returns: > 0 => that much data read
+ <= 0 on error or EOF; errno set - zero for EOF
+ */
+
+ int
+-ip_recv(client_conn_ctx * cctx, uschar * buffer, int buffsize, int timeout)
++ip_recv(client_conn_ctx * cctx, uschar * buffer, int buffsize, time_t timelimit)
+ {
+ int rc;
+
+-if (!fd_ready(cctx->sock, timeout))
++if (!fd_ready(cctx->sock, timelimit))
+ return -1;
+
+ /* The socket is ready, read from it (via TLS if it's active). On EOF (i.e.
+--- a/src/malware.c
++++ b/src/malware.c
+@@ -349,13 +349,13 @@ return cre;
+ -2 on timeout or error
+ */
+ static int
+-recv_line(int fd, uschar * buffer, int bsize, int tmo)
++recv_line(int fd, uschar * buffer, int bsize, time_t tmo)
+ {
+ uschar * p = buffer;
+ ssize_t rcv;
+ BOOL ok = FALSE;
+
+-if (!fd_ready(fd, tmo-time(NULL)))
++if (!fd_ready(fd, tmo))
+ return -2;
+
+ /*XXX tmo handling assumes we always get a whole line */
+@@ -382,9 +382,9 @@ return p - buffer;
+
+ /* return TRUE iff size as requested */
+ static BOOL
+-recv_len(int sock, void * buf, int size, int tmo)
++recv_len(int sock, void * buf, int size, time_t tmo)
+ {
+-return fd_ready(sock, tmo-time(NULL))
++return fd_ready(sock, tmo)
+ ? recv(sock, buf, size, 0) == size
+ : FALSE;
+ }
+@@ -430,7 +430,7 @@ for (;;)
+ }
+
+ static inline int
+-mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo)
++mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, time_t tmo)
+ {
+ client_conn_ctx cctx = {.sock = sock};
+ int offset = 0;
+@@ -438,7 +438,7 @@ int i;
+
+ do
+ {
+- i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
++ i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo);
+ if (i <= 0)
+ {
+ (void) malware_panic_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
+@@ -497,7 +497,7 @@ switch (*line)
+
+ static int
+ mksd_scan_packed(struct scan * scanent, int sock, const uschar * scan_filename,
+- int tmo)
++ time_t tmo)
+ {
+ struct iovec iov[3];
+ const char *cmd = "MSQ\n";
+@@ -746,7 +746,7 @@ if (!malware_ok)
+ if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
+
+- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
+
+ if (bread <= 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+@@ -1064,7 +1064,7 @@ badseek: err = errno;
+ if (m_sock_send(malware_daemon_ctx.sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
+
+- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
+ if (bread > 0) av_buffer[bread]='\0';
+ if (bread < 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+@@ -1096,7 +1096,7 @@ badseek: err = errno;
+ {
+ errno = ETIMEDOUT;
+ i = av_buffer+sizeof(av_buffer)-p;
+- if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo-time(NULL))) < 0)
++ if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo)) < 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+ string_sprintf("unable to read result (%s)", strerror(errno)),
+ malware_daemon_ctx.sock);
+@@ -1401,7 +1401,7 @@ badseek: err = errno;
+
+ /* wait for result */
+ memset(av_buffer, 0, sizeof(av_buffer));
+- if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
++ if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo)) <= 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+ string_sprintf("unable to read from UNIX socket (%s)", scanner_options),
+ malware_daemon_ctx.sock);
+@@ -1737,7 +1737,7 @@ b_seek: err = errno;
+
+ /* Read the result */
+ memset(av_buffer, 0, sizeof(av_buffer));
+- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
+ (void)close(malware_daemon_ctx.sock);
+ malware_daemon_ctx.sock = -1;
+ malware_daemon_ctx.tls_ctx = NULL;
+@@ -1895,7 +1895,7 @@ b_seek: err = errno;
+ return m_panic_defer(scanent, CUS callout_address, errstr);
+
+ /* Read the result */
+- bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
++ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo);
+
+ if (bread <= 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+--- a/src/routers/iplookup.c
++++ b/src/routers/iplookup.c
+@@ -279,7 +279,7 @@ while ((hostname = string_nextinlist(&li
+ /* Read the response and close the socket. If the read fails, try the
+ next IP address. */
+
+- count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, ob->timeout);
++ count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, time(NULL) + ob->timeout);
+ (void)close(query_cctx.sock);
+ if (count <= 0)
+ {
+--- a/src/smtp_out.c
++++ b/src/smtp_out.c
+@@ -587,14 +587,14 @@ Arguments:
+ inblock the SMTP input block (contains holding buffer, socket, etc.)
+ buffer where to put the line
+ size space available for the line
+- timeout the timeout to use when reading a packet
++ timelimit deadline for reading the lime, seconds past epoch
+
+ Returns: length of a line that has been put in the buffer
+ -1 otherwise, with errno set
+ */
+
+ static int
+-read_response_line(smtp_inblock *inblock, uschar *buffer, int size, int timeout)
++read_response_line(smtp_inblock *inblock, uschar *buffer, int size, time_t timelimit)
+ {
+ uschar *p = buffer;
+ uschar *ptr = inblock->ptr;
+@@ -637,7 +637,7 @@ for (;;)
+
+ /* Need to read a new input packet. */
+
+- if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timeout)) <= 0)
++ if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timelimit)) <= 0)
+ {
+ DEBUG(D_deliver|D_transport|D_acl)
+ debug_printf_indent(errno ? " SMTP(%s)<<\n" : " SMTP(closed)<<\n",
+@@ -694,6 +694,7 @@ smtp_read_response(void * sx0, uschar *
+ smtp_context * sx = sx0;
+ uschar * ptr = buffer;
+ int count = 0, rc;
++time_t timelimit = time(NULL) + timeout;
+
+ errno = 0; /* Ensure errno starts out zero */
+
+@@ -713,7 +714,7 @@ response. */
+
+ for (;;)
+ {
+- if ((count = read_response_line(&sx->inblock, ptr, size, timeout)) < 0)
++ if ((count = read_response_line(&sx->inblock, ptr, size, timelimit)) < 0)
+ return FALSE;
+
+ HDEBUG(D_transport|D_acl|D_v)
+--- a/src/spam.c
++++ b/src/spam.c
+@@ -503,7 +503,7 @@ offset = 0;
+ while ((i = ip_recv(&spamd_cctx,
+ spamd_buffer + offset,
+ sizeof(spamd_buffer) - offset - 1,
+- sd->timeout - time(NULL) + start)) > 0)
++ sd->timeout + start)) > 0)
+ offset += i;
+ spamd_buffer[offset] = '\0'; /* guard byte */
+
+--- a/src/transports/smtp_socks.c
++++ b/src/transports/smtp_socks.c
+@@ -129,7 +129,7 @@ switch(method)
+ #ifdef TCP_QUICKACK
+ (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+ #endif
+- if (!fd_ready(fd, tmo-time(NULL)) || read(fd, s, 2) != 2)
++ if (!fd_ready(fd, tmo) || read(fd, s, 2) != 2)
+ return FAIL;
+ HDEBUG(D_transport|D_acl|D_v)
+ debug_printf_indent(" SOCKS<< %02x %02x\n", s[0], s[1]);
+@@ -320,7 +320,7 @@ HDEBUG(D_transport|D_acl|D_v) debug_prin
+ (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+ #endif
+
+-if ( !fd_ready(fd, tmo-time(NULL))
++if ( !fd_ready(fd, tmo)
+ || read(fd, buf, 2) != 2
+ )
+ goto rcv_err;
+@@ -370,7 +370,7 @@ if (send(fd, buf, size, 0) < 0)
+ /* expect conn-reply (success, local(ipver, addr, port))
+ of same length as conn-request, or non-success fail code */
+
+-if ( !fd_ready(fd, tmo-time(NULL))
++if ( !fd_ready(fd, tmo)
+ || (size = read(fd, buf, size)) < 2
+ )
+ goto rcv_err;
+--- a/src/verify.c
++++ b/src/verify.c
+@@ -2770,7 +2770,7 @@ for (;;)
+ int size = sizeof(buffer) - (p - buffer);
+
+ if (size <= 0) goto END_OFF; /* Buffer filled without seeing \n. */
+- count = ip_recv(&ident_conn_ctx, p, size, rfc1413_query_timeout);
++ count = ip_recv(&ident_conn_ctx, p, size, time(NULL) + rfc1413_query_timeout);
+ if (count <= 0) goto END_OFF; /* Read error or EOF */
+
+ /* Scan what we just read, to see if we have reached the terminating \r\n. Be
--- /dev/null
+From 26dd3aa007b3b77969610c031f59388e0953bd00 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Fri, 7 Jun 2019 11:54:10 +0100
+Subject: [PATCH 2/2] Fix detection of 32b platform at build time. Bug 2405
+
+---
+ src/buildconfig.c | 12 +++---
+ test/scripts/0000-Basic/0002 | 72 +++++++++++++++++++-----------------
+ test/stdout/0002 | 72 +++++++++++++++++++-----------------
+ 3 files changed, 83 insertions(+), 73 deletions(-)
+
+diff --git a/src/buildconfig.c b/src/buildconfig.c
+index 71cf97b1..a680b344 100644
+--- a/src/buildconfig.c
++++ b/src/buildconfig.c
+@@ -111,6 +111,7 @@ unsigned long test_ulong_t = 0L;
+ unsigned int test_uint_t = 0;
+ #endif
+ long test_long_t = 0;
++long long test_longlong_t = 0;
+ int test_int_t = 0;
+ FILE *base;
+ FILE *new;
+@@ -155,15 +156,16 @@ This assumption is known to be OK for the common operating systems. */
+
+ fprintf(new, "#ifndef OFF_T_FMT\n");
+ if (sizeof(test_off_t) > sizeof(test_long_t))
+- {
+ fprintf(new, "# define OFF_T_FMT \"%%lld\"\n");
+- fprintf(new, "# define LONGLONG_T long long int\n");
+- }
+ else
+- {
+ fprintf(new, "# define OFF_T_FMT \"%%ld\"\n");
++fprintf(new, "#endif\n\n");
++
++fprintf(new, "#ifndef LONGLONG_T\n");
++if (sizeof(test_longlong_t) > sizeof(test_long_t))
++ fprintf(new, "# define LONGLONG_T long long int\n");
++else
+ fprintf(new, "# define LONGLONG_T long int\n");
+- }
+ fprintf(new, "#endif\n\n");
+
+ /* Now do the same thing for time_t variables. If the length is greater than
+--
+2.20.1
+
--- a/src/expand.c
+++ b/src/expand.c
-@@ -2115,6 +2115,55 @@ return ret;
+@@ -2147,6 +2147,55 @@ return ret;
/*************************************************
* Read and evaluate a condition *
-@@ -2145,6 +2194,7 @@ BOOL sub2_honour_dollar = TRUE;
+@@ -2177,6 +2226,7 @@ BOOL sub2_honour_dollar = TRUE;
int i, rc, cond_type, roffset;
int_eximarith_t num[2];
struct stat statbuf;
uschar name[256];
const uschar *sub[10];
-@@ -2157,37 +2207,7 @@ for (;;)
+@@ -2189,37 +2239,7 @@ for (;;)
if (*s == '!') { testfor = !testfor; s++; } else break;
}
{
/* def: tests for a non-empty variable, or for the existence of a header. If
yield == NULL we are in a skipping state, and don't care about the answer. */
-@@ -2506,7 +2526,7 @@ switch(cond_type)
+@@ -2538,7 +2558,7 @@ switch(cond_type)
{
if (i == 0) goto COND_FAILED_CURLY_START;
expand_string_message = string_sprintf("missing 2nd string in {} "
+ "after \"%s\"", opname);
return NULL;
}
- sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
-@@ -2518,7 +2538,7 @@ switch(cond_type)
+ if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
+@@ -2553,7 +2573,7 @@ switch(cond_type)
conditions that compare numbers do not start with a letter. This just saves
checking for them individually. */
if (sub[i][0] == 0)
{
num[i] = 0;
-@@ -2832,7 +2852,7 @@ switch(cond_type)
+@@ -2867,7 +2887,7 @@ switch(cond_type)
uschar *save_iterate_item = iterate_item;
int (*compare)(const uschar *, const uschar *);
-- DEBUG(D_expand) debug_printf_indent("condition: %s\n", name);
-+ DEBUG(D_expand) debug_printf_indent("condition: %s\n", opname);
+- DEBUG(D_expand) debug_printf_indent("condition: %s item: %s\n", name, sub[0]);
++ DEBUG(D_expand) debug_printf_indent("condition: %s item: %s\n", opname, sub[0]);
tempcond = FALSE;
compare = cond_type == ECOND_INLISTI
-@@ -2871,14 +2891,14 @@ switch(cond_type)
+@@ -2909,14 +2929,14 @@ switch(cond_type)
if (*s != '{') /* }-for-text-editors */
{
expand_string_message = string_sprintf("each subcondition "
return NULL;
}
while (isspace(*s)) s++;
-@@ -2888,7 +2908,7 @@ switch(cond_type)
+@@ -2926,7 +2946,7 @@ switch(cond_type)
{
/* {-for-text-editors */
expand_string_message = string_sprintf("missing } at end of condition "
return NULL;
}
-@@ -2920,7 +2940,7 @@ switch(cond_type)
+@@ -2958,7 +2978,7 @@ switch(cond_type)
int sep = 0;
uschar *save_iterate_item = iterate_item;
while (isspace(*s)) s++;
if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
-@@ -2941,7 +2961,7 @@ switch(cond_type)
+@@ -2979,7 +2999,7 @@ switch(cond_type)
if (!(s = eval_condition(sub[1], resetok, NULL)))
{
expand_string_message = string_sprintf("%s inside \"%s\" condition",
return NULL;
}
while (isspace(*s)) s++;
-@@ -2951,7 +2971,7 @@ switch(cond_type)
+@@ -2989,7 +3009,7 @@ switch(cond_type)
{
/* {-for-text-editors */
expand_string_message = string_sprintf("missing } at end of condition "
return NULL;
}
-@@ -2963,11 +2983,11 @@ switch(cond_type)
+@@ -3001,11 +3021,11 @@ switch(cond_type)
if (!eval_condition(sub[1], resetok, &tempcond))
{
expand_string_message = string_sprintf("%s inside \"%s\" condition",
tempcond? "true":"false");
if (yield != NULL) *yield = (tempcond == testfor);
-@@ -3060,19 +3080,20 @@ switch(cond_type)
+@@ -3098,19 +3118,20 @@ switch(cond_type)
/* Unknown condition */
default:
return NULL;
/* A condition requires code that is not compiled */
-@@ -3082,7 +3103,7 @@ return NULL;
+@@ -3120,7 +3141,7 @@ return NULL;
!defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET)
COND_FAILED_NOT_COMPILED:
expand_string_message = string_sprintf("support for \"%s\" not compiled",
return NULL;
#endif
}
-@@ -3793,6 +3814,58 @@ return x;
- }
+@@ -3849,6 +3870,56 @@ return x;
+
+/************************************************/
+}
+
+
-+
-+
+ /* Return pointer to dewrapped string, with enclosing specified chars removed.
+ The given string is modified on return. Leading whitespace is skipped while
+ looking for the opening wrap character, then the rest is scanned for the trailing
+@@ -3905,7 +3976,7 @@ The element may itself be an object or a
+ Return NULL when the list is empty.
+ */
- /*************************************************
- * Expand string *
-@@ -5904,9 +5977,10 @@ while (*s != 0)
+-uschar *
++static uschar *
+ json_nextinlist(const uschar ** list)
+ {
+ unsigned array_depth = 0, object_depth = 0;
+@@ -6243,9 +6314,10 @@ while (*s != 0)
case EITEM_SORT:
{
const uschar *dstlist = NULL, *dstkeylist = NULL;
uschar * tmp;
uschar *save_iterate_item = iterate_item;
-@@ -5941,6 +6015,25 @@ while (*s != 0)
+@@ -6280,6 +6352,25 @@ while (*s != 0)
goto EXPAND_FAILED_CURLY;
}
while (isspace(*s)) s++;
if (*s++ != '{')
{
-@@ -5969,10 +6062,9 @@ while (*s != 0)
+@@ -6307,11 +6398,10 @@ while (*s != 0)
+ if (skipping) continue;
while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
- {
+- {
- uschar * dstitem;
++ {
+ uschar * srcfield, * dstitem;
- uschar * newlist = NULL;
- uschar * newkeylist = NULL;
+ gstring * newlist = NULL;
+ gstring * newkeylist = NULL;
- uschar * srcfield;
DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
-@@ -5993,25 +6085,15 @@ while (*s != 0)
+@@ -6332,25 +6422,15 @@ while (*s != 0)
while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
{
uschar * dstfield;
+HS/01 Handle trailing backslash gracefully. (CVE-2019-15846)
+
- Exim version 4.89
- -----------------
+ Since version 4.92
+ ------------------
--- a/src/string.c
+++ b/src/string.c
-@@ -220,6 +220,8 @@ interpreted in strings.
+@@ -224,6 +224,8 @@ interpreted in strings.
Arguments:
pp points a pointer to the initiating "\" in the string;
the pointer gets updated to point to the final character
Returns: the value of the character escape
*/
-@@ -232,6 +234,7 @@ const uschar *hex_digits= CUS"0123456789
+@@ -236,6 +238,7 @@ const uschar *hex_digits= CUS"0123456789
int ch;
const uschar *p = *pp;
ch = *(++p);
if (isdigit(ch) && ch != '8' && ch != '9')
{
ch -= '0';
+@@ -1210,8 +1213,8 @@ memcpy(g->s + p, s, count);
+ g->ptr = p + count;
+ return g;
+ }
+-
+-
++
++
+ gstring *
+ string_cat(gstring *string, const uschar *s)
+ {
--- /dev/null
+From 478effbfd9c3cc5a627fc671d4bf94d13670d65f Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Fri, 27 Sep 2019 12:21:49 +0100
+Subject: [PATCH] Fix buffer overflow in string_vformat. Bug 2449
+
+---
+ src/string.c | 4 ++--
+ test/scripts/0000-Basic/0214 | 11 +++++++++++
+ test/stdout/0214 | 7 +++++++
+ 3 files changed, 20 insertions(+), 2 deletions(-)
+
+diff --git a/src/string.c b/src/string.c
+index c6549bf93..3445f8a42 100644
+--- a/src/string.c
++++ b/src/string.c
+@@ -1132,7 +1132,7 @@ store_reset(g->s + (g->size = g->ptr + 1));
+ Arguments:
+ g the growable-string
+ p current end of data
+- count amount to grow by
++ count amount to grow by, offset from p
+ */
+
+ static void
+@@ -1590,7 +1590,7 @@ while (*fp)
+ }
+ else if (g->ptr >= lim - width)
+ {
+- gstring_grow(g, g->ptr, width - (lim - g->ptr));
++ gstring_grow(g, g->ptr, width);
+ lim = g->size - 1;
+ gp = CS g->s + g->ptr;
+ }
+--
+2.23.0
+
+++ /dev/null
-Description: Disable chunking (BDAT) by default.
- Change default value of main option chunking_advertise_hosts and smtp
- transport option hosts_try_chunking from "*" to empty.
-Author: Andreas Metzler <ametzler@debian.org>
-Origin: vendor
-Forwarded: not-needed
-Last-Update: 2017-01-19
-
---- a/doc/spec.txt
-+++ b/doc/spec.txt
-@@ -13215,9 +13215,9 @@ There is a slight performance penalty fo
- preceding 4.88 had these disabled by default; high-rate installations confident
- they will never run out of resources may wish to deliberately disable them.
-
--+--------------------------------------------------------------+
--|chunking_advertise_hosts|Use: main|Type: host list*|Default: *|
--+--------------------------------------------------------------+
-++------------------------------------------------------------------+
-+|chunking_advertise_hosts|Use: main|Type: host list*|Default: unset|
-++------------------------------------------------------------------+
-
- The CHUNKING extension (RFC3030) will be advertised in the EHLO message to
- these hosts. Hosts may use the BDAT command as an alternate to DATA.
-@@ -22522,9 +22522,9 @@ connects. If authentication fails, Exim
- unauthenticated. See also hosts_require_auth, and chapter 33 for details of
- authentication.
-
--+--------------------------------------------------------+
--|hosts_try_chunking|Use: smtp|Type: host list*|Default: *|
--+--------------------------------------------------------+
-++------------------------------------------------------------+
-+|hosts_try_chunking|Use: smtp|Type: host list*|Default: unset|
-++------------------------------------------------------------+
-
- This option provides a list of servers to which, provided they announce
- CHUNKING support, Exim will attempt to use BDAT commands rather than DATA. BDAT
---- a/src/globals.c
-+++ b/src/globals.c
-@@ -498,7 +498,7 @@ BOOL check_rfc2047_length = TRUE;
- int check_spool_inodes = 100;
- int check_spool_space = 10*1024; /* 10K Kbyte == 10MB */
-
--uschar *chunking_advertise_hosts = US"*";
-+uschar *chunking_advertise_hosts = NULL;
- unsigned chunking_datasize = 0;
- unsigned chunking_data_left = 0;
- BOOL chunking_offered = FALSE;
---- a/src/transports/smtp.c
-+++ b/src/transports/smtp.c
-@@ -200,7 +200,7 @@ smtp_transport_options_block smtp_transp
- NULL, /* serialize_hosts */
- NULL, /* hosts_try_auth */
- NULL, /* hosts_require_auth */
-- US"*", /* hosts_try_chunking */
-+ NULL, /* hosts_try_chunking */
- #ifdef EXPERIMENTAL_DANE
- NULL, /* hosts_try_dane */
- NULL, /* hosts_require_dane */
+++ /dev/null
-commit 65e061b76867a9ea7aeeb535341b790b90ae6c21
-Author: Heiko Schlittermann (HS12-RIPE) <hs@schlittermann.de>
-Date: Wed May 31 23:08:56 2017 +0200
-
- Cleanup (prevent repeated use of -p/-oMr to avoid mem leak)
-
-diff --git a/src/exim.c b/src/exim.c
-index 67583e5..88e1197 100644
---- a/src/exim.c
-+++ b/src/exim.c
-@@ -3106,7 +3106,14 @@ for (i = 1; i < argc; i++)
-
- /* -oMr: Received protocol */
-
-- else if (Ustrcmp(argrest, "Mr") == 0) received_protocol = argv[++i];
-+ else if (Ustrcmp(argrest, "Mr") == 0)
-+
-+ if (received_protocol)
-+ {
-+ fprintf(stderr, "received_protocol is set already\n");
-+ exit(EXIT_FAILURE);
-+ }
-+ else received_protocol = argv[++i];
-
- /* -oMs: Set sender host name */
-
-@@ -3202,7 +3209,15 @@ for (i = 1; i < argc; i++)
-
- if (*argrest != 0)
- {
-- uschar *hn = Ustrchr(argrest, ':');
-+ uschar *hn;
-+
-+ if (received_protocol)
-+ {
-+ fprintf(stderr, "received_protocol is set already\n");
-+ exit(EXIT_FAILURE);
-+ }
-+
-+ hn = Ustrchr(argrest, ':');
- if (hn == NULL)
- {
- received_protocol = argrest;
+++ /dev/null
-From: Jeremy Harris <jgh146exb@wizmail.org>
-Date: Fri, 24 Nov 2017 20:22:33 +0000
-Subject: Avoid release of store if there have been later allocations. Bug
- 2199
-Origin: https://git.exim.org/exim.git/commit/4090d62a4b25782129cc1643596dc2f6e8f63bde
-Bug: https://bugs.exim.org/show_bug.cgi?id=2199
-Bug-Debian: https://bugs.debian.org/882648
-Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2017-16943
-
----
-diff --git a/src/receive.c b/src/receive.c
-index 95cf13e1..20672dbe 100644
---- a/src/receive.c
-+++ b/src/receive.c
-@@ -1772,8 +1772,8 @@ for (;;)
- (and sometimes lunatic messages can have ones that are 100s of K long) we
- call store_release() for strings that have been copied - if the string is at
- the start of a block (and therefore the only thing in it, because we aren't
-- doing any other gets), the block gets freed. We can only do this because we
-- know there are no other calls to store_get() going on. */
-+ doing any other gets), the block gets freed. We can only do this release if
-+ there were no allocations since the once that we want to free. */
-
- if (ptr >= header_size - 4)
- {
-@@ -1782,9 +1782,10 @@ for (;;)
- header_size *= 2;
- if (!store_extend(next->text, oldsize, header_size))
- {
-+ BOOL release_ok = store_last_get[store_pool] == next->text;
- uschar *newtext = store_get(header_size);
- memcpy(newtext, next->text, ptr);
-- store_release(next->text);
-+ if (release_ok) store_release(next->text);
- next->text = newtext;
- }
- }
---
-2.15.0
-
+++ /dev/null
-From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
-Date: Mon, 27 Nov 2017 22:42:33 +0100
-Subject: Chunking: do not treat the first lonely dot special. CVE-2017-16944,
- Bug 2201
-Origin: https://git.exim.org/exim.git/commit/4804c62909a62a3ac12ec4777ebd48c541028965
-Bug: https://bugs.exim.org/show_bug.cgi?id=2201
-Bug-Debian: https://bugs.debian.org/882671
-Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2017-16944
-
-(cherry picked from commit 178ecb70987f024f0e775d87c2f8b2cf587dd542)
-
-Change log update
-
-(cherry picked from commit b488395f4d99d44a950073a64b35ec8729102782)
-
----
-diff --git a/src/receive.c b/src/receive.c
-index 20672dbe..2812ea2c 100644
---- a/src/receive.c
-+++ b/src/receive.c
-@@ -1827,7 +1827,7 @@ for (;;)
- prevent further reading), and break out of the loop, having freed the
- empty header, and set next = NULL to indicate no data line. */
-
-- if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
-+ if (ptr == 0 && ch == '.' && dot_ends)
- {
- ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
- if (ch == '\r')
-diff --git a/src/smtp_in.c b/src/smtp_in.c
-index 1b45f84d..02075404 100644
---- a/src/smtp_in.c
-+++ b/src/smtp_in.c
-@@ -4955,16 +4955,23 @@ while (done <= 0)
- DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
- (int)chunking_state, chunking_data_left);
-
-+ /* push the current receive_* function on the "stack", and
-+ replace them by bdat_getc(), which in turn will use the lwr_receive_*
-+ functions to do the dirty work. */
- lwr_receive_getc = receive_getc;
- lwr_receive_ungetc = receive_ungetc;
-+
- receive_getc = bdat_getc;
- receive_ungetc = bdat_ungetc;
-
-+ dot_ends = FALSE;
-+
- goto DATA_BDAT;
- }
-
- case DATA_CMD:
- HAD(SCH_DATA);
-+ dot_ends = TRUE;
-
- DATA_BDAT: /* Common code for DATA and BDAT */
- if (!discarded && recipients_count <= 0)
---
-2.15.0
-
+++ /dev/null
-Description: Fix base64d() buffer size (CVE-2018-6789)
- Credits for discovering this bug: Meh Chang <meh@devco.re>
-Origin: vendor
-Bug-Debian: https://bugs.debian.org/890000
-Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2018-6789
-Forwarded: not-needed
-Author: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
-Last-Update: 2018-02-10
----
-
---- a/src/base64.c
-+++ b/src/base64.c
-@@ -152,10 +152,14 @@ static uschar dec64table[] = {
- int
- b64decode(uschar *code, uschar **ptr)
- {
-+
- int x, y;
--uschar *result = store_get(3*(Ustrlen(code)/4) + 1);
-+uschar *result;
-
--*ptr = result;
-+{
-+ int l = Ustrlen(code);
-+ *ptr = result = store_get(1 + l/4 * 3 + l%4);
-+}
-
- /* Each cycle of the loop handles a quantum of 4 input bytes. For the last
- quantum this may decode to 1, 2, or 3 output bytes. */
+++ /dev/null
-From d740d2111f189760593a303124ff6b9b1f83453d Mon Sep 17 00:00:00 2001
-From: Jeremy Harris <jgh146exb@wizmail.org>
-Date: Mon, 27 May 2019 21:57:31 +0100
-Subject: [PATCH] Fix CVE-2019-10149
-
----
-diff --git a/src/deliver.c b/src/deliver.c
-index 59256ac2c..45cc0723f 100644
---- a/src/deliver.c
-+++ b/src/deliver.c
-@@ -6227,17 +6227,23 @@ if (process_recipients != RECIP_IGNORE)
- {
- uschar * save_local = deliver_localpart;
- const uschar * save_domain = deliver_domain;
-+ uschar * addr = new->address, * errmsg = NULL;
-+ int start, end, dom;
-
-- deliver_localpart = expand_string(
-- string_sprintf("${local_part:%s}", new->address));
-- deliver_domain = expand_string(
-- string_sprintf("${domain:%s}", new->address));
-+ if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
-+ log_write(0, LOG_MAIN|LOG_PANIC,
-+ "failed to parse address '%.100s': %s\n", addr, errmsg);
-+ else
-+ {
-+ deliver_localpart =
-+ string_copyn(addr+start, dom ? (dom-1) - start : end - start);
-+ deliver_domain = dom ? CUS string_copyn(addr+dom, end - dom) : CUS"";
-
-- (void) event_raise(event_action,
-- US"msg:fail:internal", new->message);
-+ event_raise(event_action, US"msg:fail:internal", new->message);
-
-- deliver_localpart = save_local;
-- deliver_domain = save_domain;
-+ deliver_localpart = save_local;
-+ deliver_domain = save_domain;
-+ }
- }
- #endif
- }
---
-2.20.1
-
-## 50_localscan_dlopen.dpatch by Marc MERLIN
-
-
-Description: Allow to use and switch between different local_scan functions
+Description: Allow one to use and switch between different local_scan functions
without recompiling exim.
http://marc.merlins.org/linux/exim/files/sa-exim-current/ Original patch from
David Woodhouse, modified first by Derrick 'dman' Hudson and then by Marc
Author: David Woodhouse, Derrick 'dman' Hudson, Marc MERLIN
Origin: other, http://marc.merlins.org/linux/exim/files/sa-exim-current/
Forwarded: no
-Last-Update: 2014-12-01
+Last-Update: 2018-12-12
--- a/src/EDITME
+++ b/src/EDITME
-@@ -785,6 +785,21 @@ HEADERS_CHARSET="ISO-8859-1"
+@@ -824,6 +824,21 @@ HEADERS_CHARSET="ISO-8859-1"
#------------------------------------------------------------------------------
# the documentation in "info" format, first fetch the Texinfo documentation
--- a/src/config.h.defaults
+++ b/src/config.h.defaults
-@@ -28,6 +28,8 @@ it's a default value. */
+@@ -32,6 +32,8 @@ Do not put spaces between # and the 'def
#define AUTH_VARS 3
#define CONFIGURE_FILE
--- a/src/globals.c
+++ b/src/globals.c
-@@ -140,6 +140,10 @@ int dsn_ret = 0;
+@@ -141,6 +141,10 @@ int dsn_ret = 0;
const pcre *regex_DSN = NULL;
uschar *dsn_advertise_hosts = NULL;
BOOL gnutls_allow_auto_pkcs11 = FALSE;
--- a/src/globals.h
+++ b/src/globals.h
-@@ -133,6 +133,9 @@ extern int dsn_ret; /
+@@ -138,6 +138,9 @@ extern int dsn_ret; /
extern const pcre *regex_DSN; /* For recognizing DSN settings */
extern uschar *dsn_advertise_hosts; /* host for which TLS is advertised */
--- a/src/local_scan.c
+++ b/src/local_scan.c
-@@ -5,60 +5,131 @@
+@@ -5,61 +5,131 @@
/* Copyright (c) University of Cambridge 1995 - 2009 */
/* See the file NOTICE for conditions of use and distribution. */
-Local/local_scan.c, and edit the copy. To use your version instead of the
-default, you must set
-
+-HAVE_LOCAL_SCAN=yes
-LOCAL_SCAN_SOURCE=Local/local_scan.c
-
-in your Local/Makefile. This makes it easy to copy your version for use with
/* End of local_scan.h */
--- a/src/readconf.c
+++ b/src/readconf.c
-@@ -313,6 +313,9 @@ static optionlist optionlist_config[] =
+@@ -199,6 +199,9 @@ static optionlist optionlist_config[] =
{ "local_from_prefix", opt_stringptr, &local_from_prefix },
{ "local_from_suffix", opt_stringptr, &local_from_suffix },
{ "local_interfaces", opt_stringptr, &local_interfaces },
+#ifdef DLOPEN_LOCAL_SCAN
+ { "local_scan_path", opt_stringptr, &local_scan_path },
+#endif
+ #ifdef HAVE_LOCAL_SCAN
{ "local_scan_timeout", opt_time, &local_scan_timeout },
- { "local_sender_retain", opt_bool, &local_sender_retain },
- { "localhost_number", opt_stringptr, &host_number_string },
+ #endif
-31_eximmanpage.dpatch
-32_exim4.dpatch
-33_eximon.binary.dpatch
-34_eximstatsmanpage.dpatch
-35_install.dpatch
-40_reproducible_build.diff
-50_localscan_dlopen.dpatch
-50-relax-appendfile-chown-openafs.patch
-60_convert4r4.dpatch
-67_unnecessaryCopt.diff
-70_remove_exim-users_references.dpatch
-78_Disable-chunking-BDAT-by-default.patch
-79_CVE-2017-1000369.patch
-80_Avoid-release-of-store-if-there-have-been-later-allo.patch
-81_Chunking-do-not-treat-the-first-lonely-dot-special.-.patch
-82_Fix-base64d-buffer-size-CVE-2018-6789.patch
-83_qsa-2019-exim4.patch
-84_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch
-85_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch
+31_eximmanpage.dpatch
+32_exim4.dpatch
+33_eximon.binary.dpatch
+34_eximstatsmanpage.dpatch
+35_install.dpatch
+50-relax-appendfile-chown-openafs.patch
+60_convert4r4.dpatch
+67_unnecessaryCopt.diff
+70_remove_exim-users_references.dpatch
+75_01-Fix-json-extract-operator-for-unfound-case.patch
+75_02-Fix-transport-buffer-size-handling.patch
+75_03-Fix-info-on-using-local_scan-in-the-default-Makefile.patch
+75_04-GnuTLS-Fix-client-detection-of-server-reject-of-clie.patch
+75_05-Fix-expansions-for-RFC-822-addresses-having-comments.patch
+75_06-Docs-Add-note-on-lsearch-for-IPv4-mapped-IPv6-addres.patch
+75_07-Fix-crash-from-SRV-lookup-hitting-a-CNAME.patch
+75_08-Logging-fix-initial-listening-on-log-line.patch
+75_09-OpenSSL-Fix-aggregation-of-messages.patch
+75_10-Harden-plaintext-authenticator.patch
+75_11-GnuTLS-fix-tls_out_ocsp-under-hosts_request_ocsp.patch
+75_12-GnuTLS-fix-the-advertising-of-acceptable-certs-by-th.patch
+75_13-Use-dsn_from-for-success-DSN-messages.-Bug-2404.patch
+75_14-Fix-smtp-response-timeout.patch
+75_15-Fix-detection-of-32b-platform-at-build-time.-Bug-240.patch
+77_Avoid-re-expansion-in-sort-CVE-2019-13917-OVE-201907.patch
+78_01-string.c-do-not-interpret-before-0-CVE-2019-15846.patch
+78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch
+90_localscan_dlopen.dpatch
# debian/rules for exim4
# This file is public domain software, originally written by Joey Hess.
#
-# Uncomment this to turn on verbose mode.
+# Uncomment this to turn on verbose mode.
# export DH_VERBOSE=1
buildname := $(shell scripts/os-type)-$(shell scripts/arch-type)
DEBIAN := $(shell pwd)/debian
-ifeq ($(wildcard /usr/share/dpkg/buildflags.mk),)
-CFLAGS := -g
-ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
-CFLAGS += -O0
-else
-CFLAGS += -O2
-endif
-else
-export DEB_BUILD_MAINT_OPTIONS := hardening=+bindnow,+pie
-DPKG_EXPORT_BUILDFLAGS := 1
+export DEB_BUILD_MAINT_OPTIONS := hardening=+all
include /usr/share/dpkg/buildflags.mk
-endif
+include /usr/share/dpkg/pkg-info.mk
+# SOURCE_DATE_EPOCH is exported by pkg-info.mk since dpkg 1.18.8/July 2016
+# fall back to current date otherwise.
+SOURCE_DATE_EPOCH ?= $(shell date '+%s')
+
# The build system ignores CPPFLAGS, append them to CFLAGS
-CFLAGS := $(CFLAGS) $(shell getconf LFS_CFLAGS) -D_LARGEFILE_SOURCE -fno-strict-aliasing -Wall $(CPPFLAGS)
+CFLAGS += $(shell getconf LFS_CFLAGS) -D_LARGEFILE_SOURCE \
+ -fno-strict-aliasing -Wall $(CPPFLAGS)
export CFLAGS
# LFLAGS is used where GNU would use LDFLAGS
-export LFLAGS += $(LDFLAGS)
+export LFLAGS = $(LDFLAGS)
LC_ALL=C
export LC_ALL
-# Which packages should we build?
-ifndef buildbasepackages
-buildbasepackages=yes
-endif
-
-ifndef extradaemonpackages
-extradaemonpackages=exim4-daemon-heavy
-endif
# If you want to build a daemon with a configuration tailored to YOUR special
-# needs, uncomment the two custom packages in debian/control
-# call "fakeroot debian/rules unpack-configs", copy EDITME.exim4-light
-# to EDITME.exim4-custom and modify it. Please note that you _need_ to
-# modify EDITME.exim4-custom or your build will fail due to #386188.
+# needs, uncomment the exim4-daemon-custom package in debian/control,
+# call "debian/rules unpack-configs", copy EDITME.exim4-light to
+# EDITME.exim4-custom and modify it, then call "debian/rules pack-configs".
+#
+# Afterwards EITHER uncomment the customdaemon definition below, or set it
+# to the desired value via the environment.
+# e.g run:
+# env customdaemon=exim4-daemon-custom dpkg-buildpackage -uc -us
#
# If you want to create multiple custom packages with different names, use
# the script debian/create-custom-package [suffix].
-#
-# Afterwards EITHER change the definition of extradaemonpackages above OR
-# simply set extradaemonpackages to the desired value via the environment.
-
-# If you want your changes to survive a debian/rules clean, call
-# "fakeroot debian/rules pack-configs" after customizing EDITME.exim4-custom
-
-# If you remove exim4-daemon-light from basedaemonpackages to prevent
-# exim4-daemon-light from being built, you need to modify the build
-# process to pull the helper binaries from the daemon package that you
-# actually build. If you simply remove exim4-daemon-light here, you will
-# end up with exim4-base sans binaries, which is most probably not what
-# you intend to have.
-#
-# combined[ai]dbgpackage has a list of packages whose debug information
-# goes into the combined debug package exim4-dbg, separated as arch
-# independent and arch dependent list.
-# extraadbgpackage has a list of packages whose debug information
-# goes into one debug package foo-dbg per package. This is currently
-# only implemented and needed for arch dependent packages.
-
-ifeq ($(buildbasepackages),yes)
-basedaemonpackages=exim4-daemon-light
-combinedadbgpackage=exim4-base eximon4
-exim4dbg=exim4-dbg
-dhstripparm=--dbg-package=$(exim4dbg)
-exim4dev=exim4-dev
-extraadbgpackage=$(basedaemonpackages) $(extradaemonpackages)
-else
-basedaemonpackages=
-combinedadbgpackage=
-exim4dbg=
-dhstripparm=
-exim4dev=
-extraadbgpackage=$(extradaemonpackages)
-endif
+
+# customdaemon = exim4-daemon-custom
+daemons = exim4-daemon-light exim4-daemon-heavy $(customdaemon)
# If you want to build with OpenSSL instead of GnuTLS, uncomment this
# OPENSSL:=1
# Please note that building exim4-daemon-heavy with OpenSSL is a GPL
# violation.
-
-# list of all arch dependent packages to be built
-buildpackages=$(combinedadbgpackage) $(extraadbgpackage) $(addsuffix -dbg,$(extraadbgpackage)) $(exim4dbg) $(exim4dev)
-# generate -pexim4-base -peximon4 ... commandline for debhelper
-dhbuildpackages=$(addprefix -p,$(buildpackages))
-dhcombinedadbgpackage=$(addprefix -p,$(combinedadbgpackage))
-
-# exim4-daemon-heavy --> b-exim4-daemon-heavy/build-Linux-x86_64/exim
-daemonbinaries=$(addprefix b-,$(addsuffix /build-$(buildname)/exim,$(extradaemonpackages)))
-debiandaemonbinaries=$(addprefix $(DEBIAN)/,$(addsuffix /usr/sbin/exim4,$(extradaemonpackages)))
-BDIRS=$(addprefix b-,$(extradaemonpackages) $(basedaemonpackages))
-
-
-# get upstream-version from debian/changelog, i.e. anything until the first -
-DEBVERSION := $(shell dpkg-parsechangelog | sed -n '/^Version: /s/^Version: //p')
-UPSTREAMVERSION := $(shell echo $(DEBVERSION) | sed -n 's/\(.\+\)-[^-]\+/\1/p')
-DEBTIME := $(shell dpkg-parsechangelog --show-field Date)
-REPBUILDDATE := \
- $(shell env LC_ALL=C TZ=UTC date --date="$(DEBTIME)" '+%b %e %Y')
-REPBUILDTIME := \
- $(shell env LC_ALL=C TZ=UTC date --date="$(DEBTIME)" '+%H:%M:%S')
-
PROVIDE_DEFAULT_MTA := $(shell if dpkg-vendor --is Ubuntu || \
dpkg-vendor --derives-from Ubuntu ; then : ; else \
echo "default-mta" ; fi)
unexport TZ
-# set up build directory b-exim4-daemon-heavy/
-$(addsuffix /Makefile,$(BDIRS)): %/Makefile:
- mkdir $*
- find . -mindepth 1 -maxdepth 1 \
- -name debian -prune -o \
- -name 'b-*' -o -print0 | \
- xargs --no-run-if-empty --null \
- cp -a --target-directory=$*
- printf '#define REPBUILDDATE "$(REPBUILDDATE)"\n' \
- > $*/src/repbuildtime.h && \
- printf '#define REPBUILDTIME "$(REPBUILDTIME)"\n' \
- >> $*/src/repbuildtime.h
-
-
unpack-configs: unpack-configs-stamp
+
unpack-configs-stamp: src/EDITME exim_monitor/EDITME
patch -o EDITME.eximon exim_monitor/EDITME \
$(DEBIAN)/EDITME.eximon.diff
-diff -u exim_monitor/EDITME EDITME.eximon \
> $(DEBIAN)/EDITME.eximon.diff
-# only called manually by maintainer before upload.
-update-mtaconflicts:
- which grep-available > /dev/null && \
- grep-available --show-field=Package --field=Provides \
- mail-transport-agent --no-field-names \
- /var/lib/apt/lists/*Packages | grep -v exim | sort -u | \
- tr '\n' ',' | sed -e 's/,/, /g;s/, $$//' > $(DEBIAN)/mtalist
-
-# Generate README.Debian as text/html ...
-debian/README.Debian.html: debian/README.Debian.xml
- xsltproc --nonet --stringparam section.autolabel 1 \
- -o $@ \
- /usr/share/xml/docbook/stylesheet/nwalsh/html/docbook.xsl \
- $<
-# ... and text/plain
-debian/README.Debian: debian/README.Debian.html
- chmod 755 $(DEBIAN)/lynx-dump-postprocess
- lynx -force_html -dump $< | $(DEBIAN)/lynx-dump-postprocess > $@.tmp
- mv $@.tmp $@
-
-configure: configure-stamp
-
-configure-stamp: $(addsuffix /Makefile,$(BDIRS)) unpack-configs-stamp
- dh_testdir
- # Add here commands to configure the package.
- touch $@
-
-# Build binaries for the base package, the eximon4 package, and the
-# exim4-daemon-light package.
-b-exim4-daemon-light/build-$(buildname)/exim: b-exim4-daemon-light/Makefile configure-stamp
- @echo build $(<D)
- dh_testdir
-
- rm -rf $(@D)
- mkdir -p $(<D)/Local
- cp EDITME.exim4-light $(<D)/Local/Makefile
- cp EDITME.eximon $(<D)/Local/eximon.conf
- cd $(<D) && $(MAKE) FULLECHO=''
-
-b-exim4-daemon-heavy/build-$(buildname)/exim: b-exim4-daemon-heavy/Makefile configure-stamp
- @echo build $(<D)
- dh_testdir
-
- rm -rf $(@D)
- mkdir -p $(<D)/Local
- cp EDITME.exim4-heavy $(<D)/Local/Makefile
- cd $(<D) && $(MAKE) FULLECHO=''
-
-b-exim4-daemon-custom/build-$(buildname)/exim: b-exim4-daemon-custom/Makefile configure-stamp
- @echo build $(<D)
- dh_testdir
-
- rm -rf $(@D)
- mkdir -p $(<D)/Local
- cp EDITME.exim4-custom $(<D)/Local/Makefile
- cd $(<D) && $(MAKE) FULLECHO=''
-
-build-indep: build-indep-stamp
-build-indep-stamp: debian/README.Debian
- dh_testdir
+bdir-stamp: unpack-configs-stamp
+ for i in $(daemons) ; do \
+ mkdir b-$$i && \
+ find . -mindepth 1 -maxdepth 1 \
+ -name debian -prune -o \
+ -name 'b-*' -o -print0 | \
+ xargs --no-run-if-empty --null \
+ cp -a --target-directory=b-$$i ; \
+ done
touch $@
-build-arch: build-arch-stamp test-stamp
-
-ifeq ($(buildbasepackages),yes)
-build-arch-stamp: b-exim4-daemon-light/build-$(buildname)/exim $(daemonbinaries)
-else
-build-arch-stamp: $(daemonbinaries)
-endif
- dh_testdir
+override_dh_auto_configure: unpack-configs-stamp bdir-stamp
+ for i in $(daemons) ; do \
+ mkdir -p b-$$i/Local && \
+ cp EDITME.`echo $$i | sed -e s/exim4-daemon/exim4/` \
+ b-$$i/Local/Makefile && \
+ cp EDITME.eximon b-$$i/Local/eximon.conf ;\
+ done
+
+override_dh_auto_build:
+ for i in $(daemons) ; do \
+ echo building $$i; \
+ cd $(CURDIR)/b-$$i && \
+ $(MAKE) FULLECHO='' ; \
+ done
# Which version of Berkeley DB are we building against?
printf '#include <db.h>\ninstdbversionis DB_VERSION_MAJOR DB_VERSION_MINOR\n' | \
cpp -P | grep instdbversionis |\
# Store Berkeley DB version in postinst script.
sed -i -f $(DEBIAN)/berkeleydb.sed \
$(DEBIAN)/exim4-base.postinst
- touch build-arch-stamp
-
-test-stamp: build-arch-stamp
- # it is not possible to run exim unless the compile-time specified
- # user exists.
- if id -u Debian-exim ; then \
- echo Debian-exim user found, running minimal testsuite ; \
- chmod +x debian/minimaltest ; \
- rm -rf $(CURDIR)/test ; \
- for i in b-exim4-daemon-light/build-$(buildname)/exim \
- $(daemonbinaries) ;\
- do mkdir $(CURDIR)/test && \
- debian/minimaltest $(CURDIR)/test $$i || \
- { echo testsuite error ; exit 1 ; } ; \
- rm -rf $(CURDIR)/test ; \
- done \
- fi
- touch $@
-
-build: build-arch build-indep
-
-clean: cleanfiles
-
-cleanfiles:
- dh_testdir
- dh_testroot
-
- debconf-updatepo
-
- rm -f build-stamp configure-stamp installbase-stamp test-stamp
-
- # Add here commands to clean up after the build process.
- [ ! -f Makefile ] || $(MAKE) distclean
- -rm -rf build-* doc/tmp test/
- -rm -f EDITME.* unpack-configs-stamp
- -rm -f $(DEBIAN)/debconf/exim4.conf.template $(DEBIAN)/files \
- $(DEBIAN)/README.Debian $(DEBIAN)/README.Debian.html \
- $(DEBIAN)/berkeleydb.sed
-
- #these are identical for all daemon-* and therefore symlinked
- @cd $(DEBIAN) && find . -maxdepth 1 \
- -regex '^\./exim4-daemon-.*\.\(postinst\|prerm\)$$' \
- -and -not -name 'exim4-daemon-light.*' -print0 \
- | xargs -0r rm -v
-
- #pwd
- chmod 755 $(DEBIAN)/exim-gencert \
- $(DEBIAN)/lynx-dump-postprocess $(DEBIAN)/script \
- $(DEBIAN)/exim-adduser $(DEBIAN)/exim4_refresh_gnutls-params
- dh_clean
- rm -rf $(BDIRS)
-
-installbase-stamp: b-exim4-daemon-light/build-$(buildname)/exim debian/README.Debian debian/README.Debian.html
- dh_testdir
- dh_testroot
- dh_prep
- dh_installdirs
+ # symlink identical maintainerscripts
+ for i in `echo $(daemons) | sed -e s/exim4-daemon-light//` ; do \
+ ln -sfv exim4-daemon-light.prerm \
+ "$(DEBIAN)/$$i.prerm" ; \
+ ln -sfv exim4-daemon-light.postinst \
+ "$(DEBIAN)/$$i.postinst" ; \
+ done
+override_dh_auto_install-arch: debian/README.Debian
cd b-exim4-daemon-light && \
$(MAKE) install FULLECHO='' \
INSTALL_ARG=-no_symlink \
b-exim4-daemon-light/build-$(buildname)/transport-filter.pl \
b-exim4-daemon-light/util/ratelimit.pl \
$(DEBIAN)/exim4-base/usr/share/doc/exim4-base/examples
- mv $(DEBIAN)/exim4-base/usr/sbin/exim \
- $(DEBIAN)/exim4-daemon-light/usr/sbin/exim4
- # fix permissions of /usr/sbin/exim4 if running with restrictive umask,
- # dh_fixperms sanitizes anything else
- chmod 4755 $(DEBIAN)/exim4-daemon-light/usr/sbin/exim4
+ rm $(DEBIAN)/exim4-base/usr/sbin/exim
mv $(DEBIAN)/exim4-base/usr/sbin/eximon \
$(DEBIAN)/eximon4/usr/sbin
mv $(DEBIAN)/exim4-base/usr/sbin/eximon.bin \
pod2man --center=EXIM4 --section=8 \
$(DEBIAN)/syslog2eximlog \
$(DEBIAN)/exim4-base/usr/share/man/man8/syslog2eximlog.8
+ for i in b-exim4-daemon-*/build-$(buildname)/exim ; do \
+ install -m4755 -oroot -groot $$i \
+ $(DEBIAN)/`echo $$i | sed -e 's/^b-//' -e 's_/.*__'`/usr/sbin/exim4 ; \
+ done
+
+override_dh_auto_install-indep: debian/README.Debian
# if you change anything here, you will have to change
# config-custom/debian/rules as well
sed -e \
- "s/^UPEX4C_version=\"\"/UPEX4C_version=\"$(DEBVERSION)\"/" \
+ "s/^UPEX4C_version=\"\"/UPEX4C_version=\"$(DEB_VERSION)\"/" \
< $(DEBIAN)/debconf/update-exim4.conf \
> $(DEBIAN)/exim4-config/usr/sbin/update-exim4.conf
chmod 755 $(DEBIAN)/exim4-config/usr/sbin/update-exim4.conf
chmod 755 $(DEBIAN)/debconf/update-exim4.conf.template
env CONFDIR=$(DEBIAN)/debconf \
$(DEBIAN)/debconf/update-exim4.conf.template --nobackup --run
- touch $@
+# only called manually by maintainer before upload.
+update-mtaconflicts:
+ which grep-aptavail > /dev/null && \
+ grep-aptavail --show-field=Package --field=Provides \
+ mail-transport-agent --no-field-names \
+ | grep -v exim | sort -u | \
+ tr '\n' ',' | sed -e 's/,/, /g;s/, $$//' > $(DEBIAN)/mtalist
-# This dependency expands to
-# debian/exim4-daemon-heavy/usr/sbin/exim4: b-exim4-daemon-heavy/build-Linux-x86_64/exim
-$(debiandaemonbinaries): $(DEBIAN)/%/usr/sbin/exim4: b-%/build-$(buildname)/exim
- dh_testdir
- dh_testroot
- dh_installdirs
- install -m4755 -oroot -groot $< $@
-
+# Generate README.Debian as text/html ...
+debian/README.Debian.html: debian/README.Debian.xml
+ xsltproc --nonet --stringparam section.autolabel 1 \
+ -o $@ \
+ /usr/share/xml/docbook/stylesheet/nwalsh/html/docbook.xsl \
+ $<
+# ... and text/plain
+debian/README.Debian: debian/README.Debian.html
+ chmod 755 $(DEBIAN)/lynx-dump-postprocess
+ lynx -force_html -dump $< | $(DEBIAN)/lynx-dump-postprocess > $@.tmp
+ mv $@.tmp $@
-ifeq ($(buildbasepackages),yes)
-install=installbase-stamp $(debiandaemonbinaries)
-else
-install=$(debiandaemonbinaries)
+override_dh_auto_test:
+ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
+ # it is not possible to run exim unless the compile-time specified
+ # user exists.
+ if id -u Debian-exim ; then \
+ echo Debian-exim user found, running minimal testsuite ; \
+ chmod +x debian/minimaltest ; \
+ rm -rf $(CURDIR)/test ; \
+ for i in b-exim4-daemon*/build-$(buildname)/exim ;\
+ do mkdir $(CURDIR)/test && \
+ debian/minimaltest $(CURDIR)/test $$i || \
+ { echo testsuite error ; exit 1 ; } ; \
+ rm -rf $(CURDIR)/test ; \
+ done \
+ fi
endif
+override_dh_auto_clean:
+ debconf-updatepo
+
+ -rm -rf build-* doc/tmp test/ b-exim*
+ -rm -f EDITME.* unpack-configs-stamp bdir-stamp
+ -rm -f $(DEBIAN)/debconf/exim4.conf.template $(DEBIAN)/files \
+ $(DEBIAN)/README.Debian $(DEBIAN)/README.Debian.html \
+ $(DEBIAN)/berkeleydb.sed
+
+ #these are identical for all daemon-* and therefore symlinked
+ @cd $(DEBIAN) && find . -maxdepth 1 \
+ -regex '^\./exim4-daemon-.*\.\(postinst\|prerm\)$$' \
+ -and -not -name 'exim4-daemon-light.*' -delete
+ #pwd
+ chmod 755 $(DEBIAN)/exim-gencert \
+ $(DEBIAN)/lynx-dump-postprocess $(DEBIAN)/script \
+ $(DEBIAN)/exim-adduser $(DEBIAN)/exim4_refresh_gnutls-params
+
override_dh_installchangelogs:
dh_installchangelogs -pexim4-base doc/ChangeLog
dh_installchangelogs --no-package=exim4-base \
override_dh_installppp:
dh_installppp --name=exim4
-override_dh_strip-arch:
- dh_strip $(dhcombinedadbgpackage) $(dhstripparm)
- for pkg in $(extraadbgpackage); do \
- dh_strip -p$$pkg --dbg-package=$${pkg}-dbg; \
- done
-
override_dh_fixperms:
dh_fixperms -X/etc/exim4/passwd.client -Xusr/sbin/exim4
override_dh_gencontrol:
dh_gencontrol -- \
- -VUpstream-Version=$(UPSTREAMVERSION) \
+ -VUpstream-Version=$(DEB_VERSION_EPOCH_UPSTREAM) \
-VMTA-Conflicts="$(shell cat $(DEBIAN)/mtalist)" \
-Vdist:Provides:exim4-daemon-light="$(PROVIDE_DEFAULT_MTA)"
rm -rf debian/exim4/usr/share/doc/exim4
dh_link
-override_dh_auto_install:
- # disabled
-
-# Build architecture-independent files here.
-# this is just exim4-config and exim4.
-binary-indep: build $(install)
-ifeq ($(buildbasepackages),yes)
- dh binary-indep
-endif
-
-# Build architecture-dependent files here.
-binary-arch: build $(install)
- # symlink identical maintainerscripts
- @for i in $(extradaemonpackages) ; do \
- ln -sfv exim4-daemon-light.prerm \
- "$(DEBIAN)/$$i.prerm" ; \
- ln -sfv exim4-daemon-light.postinst \
- "$(DEBIAN)/$$i.postinst" ; \
- done
- dh binary-arch
+%:
+ dh $@ --no-parallel
-binary: binary-arch binary-indep
-.PHONY: build clean binary-indep binary-arch binary install
+.PHONY: pack-configs unpack-configs update-mtaconflicts
------BEGIN PGP PUBLIC KEY BLOCK-----
-
-mQGiBEIV3d4RBADiY+ImtiuxCxe4ImIWZd6IetWIZaAjxLQliWrRHK7CdA6ANYAA
-OWwk6uMucPSjP2RUYXehDdVAb2i5AG3kGb/SNZ08x2eaeAtALAvRw3SxPW5/Ch4g
-bNB8VBCyyZlPsmS1epbaOags+1oD41FopdvfIQrtoD4I0d/ndG64wkDh2wCgiXdE
-QZzYknZgf4HA9DZHhizNnx0EAMBDVTpIq7xaYlK4dot4xNcWNJg4UX27a62lEKvV
-sDf1tH1qB4ujZy1ht83oXURpNk7uDf718kwaLGoSwW6qOx9iI46XoOtoxSH+6J8A
-oKtBNhCl03x10E8MK1fANe9WLdxARxgZxnPo9QOSTNO4PYR1yvrq0ThTKXvMweYT
-OJlIBADdTquCiM9fgoU3sBsnlmSMpFn27By0Yz4QjR8cLD0F1bZKmWPRAHDdwArS
-pOmKNv4tOaNp8WuuLEEJbPEcc6QdPEOH3lVQ/QZHdemYerwMN25i3MYeWAPRg4Sl
-dZ648IPWdHA/QYfp5JhlT/9UwwKPvIDTPg10FI5ecPYxcXUT2LQuTmlnZWwgTWV0
-aGVyaW5naGFtIChFeGltIGtleSkgPG5pZ2VsQGV4aW0ub3JnPohkBBMRCgAkAhsD
-BgsJCAcDAgMVAgMDFgIBAh4BAheABQJWzue5BQkVsOPbAAoJEIWrgz/dwDJiGmoA
-oIfRyEwpzL4v6JB4BzK3TqfH6mVRAJ90M8AfnhzW3KG7l3KYxscnVZdOlbkCDQRC
-Fd3nEAgAgeLGF7rot+0cc0hwGFK7h1aGP6r2p+o1arsR/zJystk99UBWqjmKzu+3
-6ve+H4J28Al4B7Sm75bvnKignppp0ZGP/WXlkGsk6Tt30c7tkK+1izrCFGlxf5j0
-LKrH/cCyZp7tgqRN0ewDoqK6OmEBmSqMgarSTatyYuZy5OKof8EcJEt6nTydPdts
-VgRziX71B1pd0t/bdWwLnuQ9gkSJNiwPGBrV53x9uh43ZcpqLl17yfXh/FaUcdlZ
-N1GPtXYMr208Hv8fGpPEQVr92OJAblrlGck+aWIoYgX3tqCZDqCYtxcBaXCyRZzu
-7usKJukY1Z6t0qF1U7aWTjeVVeWXhwADBQf/RYK2jTNLnhtCVWqWhFVd0/NTbXIs
-QDeZuZXp8xHB+YjxmcbrSTvKrkRqfCvPR5r5SBOwBtq+LHElwp1OcIt2xYIEmuS1
-Jod8+h+ohl9p11XtTp3Rd8selh7AHccFz6BYK1SsHO5ZdrFwlZf+oVxLrQzibFqZ
-Ob69T4HUp5Vh5Z9XO+YsVa5a3K1/pfpOJYMP3VgdsBlX/gUxkz9stfNUOIR5caQK
-UHfOaCQaQ02fAsmnThQkAmqACTapvqZV9wSHxgvUUbPcw2h3rty14u13J+cJDrE0
-+x1tCDSsPLbq62A1d9GJor8s6GpyYXq1ArZJgBpdq74qOKU5jc1gvMmE8YhOBBgR
-AgAPBQJCFd3nAhsMBQkJZgGAAAoJEIWrgz/dwDJiqxIAnAm3NzfRaBtl5XpnCA6n
-W2MNAwIgAJds5g802u5CKZDLGE90hHNXgF2kuQENBE1Aj9kBCADfrgx9xrDHoYSU
-3aU8zST2GEoMZypO1fBi3AiInsKakMsVibZpEI8MVM24lZw9jxGfsX70Xr+mYiTI
-ZY9GJROG6fHFLKgUYFxYeUA1GtNNilFvBGlXJAYduyKYZMdEVVtUX4b6QpQqmTeY
-sgNCznb1HuVpj4Vl6CiirjWhnZ/WhR3L20AMK6422lCw9jZuAK5RbSRJwkgI55rl
-zZGpGbBmBIHSCccMB/jg2LRYsVs//D9Qrxtkt8W8fIHCj66L6eNw1gcndpEkyytZ
-bifE3khwlRWn/Llpw8NiQiJKUE01TWQusEvd5EHFThE/9bYpUGdMiR0UmpSLkEq3
-zurCcUK1ABEBAAGJAW4EGBECAA8FAk1Aj9kCGwIFCRKtsIABKQkQhauDP93AMmLA
-XSAEGQECAAYFAk1Aj9kACgkQA8m6p6iaqTb0Dwf/QiTT/Aj4XdoSVGR4yeXFpQNR
-l99dOtUwsP7wtSSeV5jQgEMpRwh8ib702retoWbHQva0FsDxotEatHKvdtkkCUqF
-D33jZ+aKkadcXjqnSepXY0m7sG605QN5hE1dXBhPPy5hUfXuAphSq+ma4Q4Vz+Zm
-al3etKXL2xIgAIkSX+srng3j09JfOaYdEDXOU5sNEMuDqcqPC/yt0giGFPDBd7xZ
-JQER08MyfDoFmwiVGi1Trbzjdnp1Y0q9UF2NpWUMB0q9/CaodwjU7SB4OU9FYst9
-uImVDwI3XqL45ULUCZGhUnuHz15ePb1W5cUUu55M0iuCrjhHqt0e8/c7BrdFuwee
-AJ41rUXzNNSj3w/o9T0O7mWd0rh+HQCfSNjhzVUditAzFdNneXLgs9KddFq5AQ0E
-TUCP7gEIALzLEYpmJLCDALPKv07Yd4bhyX/st+7Hz3Uj1BjIW/+pCEFf8e+ihZg/
-caWuSL695DddreiIhJlQiso8HsjehDccU51kep4vvTKu2p3zTSSZvIgsTTPAeyqa
-L12UCAm4SlkjhEH86Yf7Qyic5cZhkGBCtN/1RVxoEoonRGOJg2jkrvok3Dz1DQ5W
-UyS5gRASDnF58EW4HSMiRek2XgN/MEY9GLkXsoaSFWU9X3rW3Mgd4EMpTf+id2eS
-Ffp820Ati+1VB6Hte8JOWRhTopSB6FZfpZ322N2iCAX0TkZesfSwfZSTZ/Xc+29B
-3JHDrVbFmCLhJfzv6MqQ04VQZ1VWzUEAEQEAAYhPBBgRAgAPBQJNQI/uAhsMBQkS
-rbCAAAoJEIWrgz/dwDJiNIgAoIdWmf17rL5Zmf/EoPtmYngbadnaAJ45YtXrEDCV
-4fuUhLK6EdvHsGGtl5kBogRRHjTKEQQA7Nj/xLjtdH+34XBWzVRupKAEA27d5Ikn
-AVtyPK/4aiGZ2mQHPX7qaVOOHHFHVfj+38ENwZG2do87x5oJgaAf/WAqQRp0m81r
-7YZ3DGWZxeDuCYESwZxEkJ9SfOwmQ66NrHuXjjabOoQEoxtQdxcyaGDBWbvpDaXS
-4fG1oKyx1T8AoOGl+25xKVwA5GKU/DLqbBOoyOi7A/914vhUW1bd8TcKk5owI7/q
-FoSIjk1/lxxDFX600giri1FrENN+ERg0jaIBFFnkJF4dx6G5xIuEAHLJ0Y2BdXCF
-mJPJw7ZzgtTmWSKW0kDhbRx+Ozvpwa1spxyjgQAg3B1fVUBkGlV6+bDZOHmMDK8b
-7RoRdW44+ygbE+WHS5/oiQQAiZtFY14WcSi4bqhpTDK5YFZh2lyhQ2snYfOiQWB/
-gLLfKDTDJ6pVygtayPKlx4jXuapyNE62QhU5zgCKr9DpsM7v7UnPfTgPYse5HqUW
-IPOiOE+ga0TpZT4egqzW6mPGRYQ/ZjViL+JGMa2ATvrSoR1BJCd8BFmmplDs2it2
-Nme0LlRvZGQgTHlvbnMgKEV4aW0gTWFpbnRhaW5lcikgPHRseW9uc0BleGltLm9y
-Zz6IZgQTEQIAJgUCUR40ygIbAwUJCWYBgAYLCQgHAwIEFQIIAwQWAgMBAh4BAheA
-AAoJEMT0+UgE0p66MDwAnRW1VWjfUD5yGhedcxiHsEg1A8vnAJ9NxfoOwPP50sWT
-f2vycK0mGECYcrkCDQRRHjTREAgAlhjQZt1+uSQ3puq7p9o/AqRrVsZxxbi/C0cS
-eAvr/iN4tkKk/4esSMevwLIMPw0ByuwCDdZusdLAI6TdDe3nwDBQVRbMlmmQM1fx
-1wsJHbiEO+WDENULU0SxqU7lwq3YCqL7oKVtZsJ0MkmEAbZlWuzBE1RzNTgdoMSB
-GmSeDu5f5q1a+BMH1gcZWQkW7Y1e1kgHDgnz6vh+cBulWCwEzrwGaEvmJJ+w2HPE
-cD9q4IvTjXxZbli7WHrSctqCdgF433iWOa+NjUCfl98z4D7KjKMqvXKqD88NYbqG
-wrvupQZMOeNjybWMnkouAXHJdA8fiTy5hV9P7nat1OMq6h+YRwAEDQf9Gl43A+H4
-xJJ34RrCp9il8/Ef7VHEn9ZnaoMNuwCjYU9OaTHAjd7V5N23ZF15+XMvO0Szx/to
-qQ14ev385VgBD/FWGy1r+UBK1/gA3pArQhpd4mtzRsjg8e2yl0D5v3v4K1EjEtDn
-37IBwAmWjwbMU12SP0NM+KQXtO0WCQF+ggRhD8hhUPV20ejYqnismX5b7LYX+8NB
-OCleryW4pz4ZQT6MTolyjeojyCyaHE9G554ECKX+fKG/WMQmjjwjngkrPk0s3HN/
-uU8UvQv+uucP62iHcPRKwIk6jrlR7KODR00IzSXaRNYtJoDC8oFS0xyhrG1vMiGv
-OQTBfKpgyxoIBIhPBBgRAgAPBQJRHjTRAhsMBQkJZgGAAAoJEMT0+UgE0p66lx4A
-n2JHiU9h4ElPNbDSfqjQoshYKIb3AJ9RjvMg0AdlIPi6k2PWTTBAKsoB+JkCDQRU
-rvZBARAA4jmen1cqxMnj2SIOPBV5igqnsSljlCADmC8MlW1OzozaxFJo/GMMfZjE
-AAiST3IFIzk8YBotDfUwSaVpRQ8QFz0XT6+BrDwKvMId7lZ3AuaqWkXT4+uv52Yr
-PVN87kbn52MLoUEtxgWxa1dvNmg8+wzsBVI63Oep3yo9eot95SIHeqDQj+4Rzd2Z
-Ejh/m3AHcoZl+Y71b9zsaherqvBgB6QpBNaYhEXXAFZGXzynX+6WxNKQ9gRxnsKD
-ZkbnJvBOyOLz+fsVI/lbGnSXycQ/hVw3xg30bXHuOkYhIe1SRz78YAaAlBp76o3P
-+M9oJA9SxP8j6XWj3vlBtbLRNl1eUXl1ED8S95jGVzmou0I08HGJRmOGAmEYQjDJ
-JB8UR6RHn4m0yCQZZgocXCGERgSRNmPMUOaIskMnBqoCfqEifGS1ATqZgYuEik9M
-o8wfHCAeMOGsjr6ew1NGPfjvzUQGRUPRgvuE5c3m6WcsDJkgTH7YW9P8T4QeboeV
-Y7xpwkAp9Sd/eoQvpXGXjEAkC5dJhaHXKbtxrxlLHaV7cTp17+Vajuf4s3zzXhjQ
-rh9ojiNyEVBDetsowzN+UxgWybGeFtXeeqUmUgLpoV8iOjaqKI/n24+dl+JY51tH
-8cR8DG93N9xYL/CDersmvxgIZEVDrpvc3/YMhCWVHDZ0ZqmnQrsAEQEAAbQzSGVp
-a28gU2NobGl0dGVybWFubiAoRHJlc2RlbikgPGhzQHNjaGxpdHRlcm1hbm4uZGU+
-iQJABBMBCgAqAhsBAh4BAheAAhkBBQsJCAcDBRUKCQgLBRYCAwEABQJYVnXoBQkF
-vsCnAAoJECYQG2L2k3bOhKkP/2zWhq0BlT7AAAuefaZPl9b52uT7PbY4owcMWXJz
-i7FTLWFo6KJOCBH9UTX0TXmf9S3AMMfoewblU6zOy+H1Q/ZVdzth5iJaXSbTgLlZ
-7yc7k3P+qUdBGdCHwpUJmBScdGaKCkbdcOPIxTi02sPFTBJx45ogr3/n0S8PFNOY
-Vv0fl4Nnr2bOpoSKSka08lk4HJKsMMA/BRfaSffez1QYdRJKhKTkljlJjA682Fuf
-NBaZIQ8GHUjyyOIUwQUit2yAGChbBCh9wq5Z//xzBwdqGx64QLHHF+wCg2r9Ba3D
-QMNllPidfPBPUPQ+xGXmHz0R0FzlaTYnFYKqpJSX8j/5IhaijZRxvtJljXa0fOg3
-D1A7ZuagCpNcXWVM66FeOx2hYMlBNn/eLejBc244ydlI5lqyocGRL3qjHufp6JVi
-uwJpNMnWyLvxqrwgC6mcCDx7jJL7eI7rdAFLwfoTYnBb0zNPStf9pWngLmxsD9G0
-U3nJnVzhsZfa9s7F13wxkfZYio/HkKW1IGZHTkJzWswXx0Ba2UK9oLDCy8dByesA
-5KmtA09dk0M/GuMFcb+ZZ3x3USa36Cw7vbJYcmDw6O4XgNf1aja5cdltENLsVKIW
-I1VguZGfIwkLC3iXN2PzO3yOW5GXQ1wPHTc/SPMuBeT6UAqPBjO6vRQRFf4ghslG
-//T2tDVIZWlrbyBTY2hsaXR0ZXJtYW5uIChIUzEyLVJJUEUpIDxoc0BzY2hsaXR0
-ZXJtYW5uLmRlPokCPwQTAQoAKQIbAQcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheA
-BQJYVnXuBQkFvsCnAAoJECYQG2L2k3bO3YYP/0vbSNKAD68r2EN8//yRGgH1xyUe
-uRARgxJnhw7tBsO3k1YIkIEG7vKzLcRhi3vcM12ttY9R425Kl1c5ug6f4jt22bnO
-ONrJ++0Or6hRucJ3L5IHRK0b2niPqvXBbg9PMp/9p0jKCHqme7mdD6jBOHBAQIZe
-MuGLyzNKx6Dk52DZeLYRznoloYtUEurckrysL1/C9Qsah3JKlURSihVFibnIF1Wa
-GfphxKsgLDDi8FUyNWrt99MhxYwwlAbBNQ99ifX3ZLFR9Q2B2ntL4Vfvom9QBYWG
-5e3rzlfQtw4pGWpFZFDSi0LdP8FfM9wKhtnbHVEav9Te7syYgMBDx5q6irqwTh58
-gKLicWkD22rtVGYPv+En54thAq6MXMQuzJ3s4MW/5GTZcbtsBBAj4OtHvtyKzI08
-/TlS09bk9mlaI8PYGUU8JKZj39alL7bI7hZVn5HkGMn1Z/lojdW8Is35uKmMZnF+
-im0vonw1n52OTv+4nOpBcidckeDr0PsiAScJBnaJNVF6v+jL5hrUxs4hD4UgTgSL
-obUzHi1g4/UP/eC1cEZH7aC2FiG2jTUqo84qTZ9Cik07fmUf95jCfsWFvijzVCPB
-oIg4W5SDfkccvoermqS2KE9b9DXdZDiaWTLO3U98nwkO6ps24lbX6mjJ+QjsSokA
-msGdN5BhOaltRYBZ0dHq0egBEAABAQAAAAAAAAAAAAAAAP/Y/+AAEEpGSUYAAQEB
-AEgASAAA/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBAUKBwcGCAwKDAwLCgsLDQ4S
-EA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFBAUJBQUJFA0LDRQU
-FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU
-/8IAEQgAYQBQAwERAAIRAQMRAf/EABwAAAIDAQEBAQAAAAAAAAAAAAQFAwYHCAIB
-AP/EABsBAAIDAQEBAAAAAAAAAAAAAAIDAQQFAAYH/9oADAMBAAIQAxAAAAG9fPO/
-GubSEtBlGa96w7AivEFcmWBP87YnPpNAfdeJLIrVB5bw3TDYD3w4bu2LTZXYacWO
-gVnc64Pf8Ec9x669KlsTzp6t1iYG/tSFU0r7naLuwuQl12uih5tA9jqX6uq2jvAz
-pGF6McSNYyGJiilQ83NYWhp/p61ZWTds7d5z0VM584dN0L7eKFnVdTHT5L9NlFkL
-Jva35/0DrrVIVyK3lLNTL2PPsXHG0OOvT44MFAwd0xttYpwTK1Q38ZNWPTkM3fF0
-OIvQZknQrAnUwPMrh54UPTFkg+osm3wFrVWJQNEvXACUD12FzzDoYjPXeS7gnWSR
-0nGJtlalBxLL5HGT12juo6HcKX+skxbGBn/EvUTEoWDLWe08e6Foxw5fK4kD5o5m
-sxQloUADLA+1dXbvUH//xAAmEAACAgICAgICAgMAAAAAAAACAwEEAAUREgYTFCEi
-MyM1FTEy/9oACAEBAAEFAh54gp4AvUZvJ8dYrKN0sFQz7ztyx4H0EEdgn+AVsX2K
-5xi+MsfZSAxnq9LIzmSBbDrKZvr20zWWNhGTtuhVrgymkEXBTUUEP16HjdozXaov
-Sqyv7Wdy7mp019t/4ZaUE1EkNSPWK3fUv4zdhDteu4LYesShtWHx48tCNjsdwe12
-IVFoxOzQbWEa8m1j7PtqjS7NtA34pW18MuyE6hX+QsDaHjY0G2XFto1jW2l2AM5s
-hS9iWprwqCszeskE962zirt1Vu5q2ddQPrfOlzY1Qa7cTYzSMVegV+qKZBraztqE
-4yxFsNXuRva2xHOTswprhrbFx4EkvFtlM7guCwbISTYggUz0un4y2PsEWBYbJV7n
-5fuqeLdGb0W85DjwH/U/nIjETZniFxAQMxirnTNeInc7CEBH0AdmNqlVeEfTmd2/
-Z53xGePqk7HP8g5o6/v2vkyYXduNJeTONiKtYOWEOePK4R2EWDni/wDeeTf2lz98
-f9bP9dfA/wBaX9LM/8QAJBEAAgIBBAEFAQEAAAAAAAAAAAECESEDEBIxBBMgIkFR
-YZH/2gAIAQMBAT8BSxkqhfFnPNGIrByOxd2cnJUL9stfZJR7TLbwjH2Pb+oqTF/d
-mv0wjPaOV9kYOZSXRQ/hh7TXHZtbR8SXpx1ZfZGCiqJad9Honkaa4EbWSXyjndIe
-Uor62STOBrriNcskouqW1EVy1IxIZPTjfIbjWTng8jWt0iMs0KHE620MT5M0v0q+
-xxU0eRqLRVEu7NFf6Kx5KONM0pYJan6S8hRXZqOWq7EQbjIvOCtmKbHJsorbojK1
-XsYt62hWPavatn0IXuW0tl7frb//xAAoEQACAQQCAQMEAwEAAAAAAAAAAQIDERIx
-ECEEEyJBMlFhgQUUIDP/2gAIAQIBAT8BpSxlhq5DFv03olRg+oK37MZItdWaMLqy
-KVTF+74HVb7HJkYRlG6J+NWqRTUbiozo1FCcdkqcaZOLqO8dHtTJSVzrQrJWIuKj
-7iNWUFaJSpQpRxpxsjBS+on4cZEv4+pD/mz+nSppZO7I04LSJUKc9oq+P6c7LR38
-opoSb0KDH+CfkpSdNbRlkyLMir7omSnsjSim5JlhuxFfY+XL7iaL20ZmSkmiFKVN
-JD/BcZf06U6n6LnbVhNofZHxHVpvuwlOldNGbkrF78eXb08I6MS6RdrR41F15dkX
-ZWPM3f4HjcXSMhTyjYls2QoTqOyKWFBYkvuVkpwdy3Xei/C2OCexRSLsTPgqfSyV
-7/ji/CXZLm5LRPLvlbESf+HolzFEnx8cyHwti0S3w9LmQz//xAA0EAABAwMBBgMH
-AgcAAAAAAAABAAIRAxIhMRMiQVFhcQQQMjNCUnKRobEjgRRDc4PC0fH/2gAIAQEA
-Bj8C1VvWUZG8RhmpQZZ6viUTp7sLdAlundSLbQbiOql2GgnXK9mY9Wn3QqNvtHqJ
-z9EHkWNd7xVxfAHpRxCE7x+KFy6KMCcYVwJiMoAjXM81YSZ4f9Vt4z6uqB2NXkKj
-dP3TA6kwt4mlWbP0KqsqU3B7W3Y94KnX2l1MiRjVNe+6HZDV7Nv0UFlvVuFsHP3T
-o5fq6nqsZ5ZwrfD0Kj/lHBUm1m7Jky5u0Ex2TfFube2qbalB+lv+097GWUb5ZQ4A
-LOvm951p7yve1k94V7XXcZHBbO51Dw4/l0d2e5VgY2nSbmOyaGez9DJ/KnLg3QLZ
-k2nqVLTLVJT2YBdjKaTdS4YCDKTczo1EXFsc0/ZH1cUQ/dB3TH3+35UGAOSa9jJc
-NHoUaxEwtrScOyLA8Nd1WyfLGTndmED6qg4kJjKb3Y1eOATWTLCcTwXhWM3aIJbn
-jPFbd+eYTphpbp1VV7ocXHIKLGkbaoIps5K5zpfqZWTFenHHgjNS+dJQm7avy6Gz
-+yItdBzK2o3Xg7w6qnvfrgb0c1Lt1HfuJ91uq/iK+vM8Ft2Cze+kqjve0ZaUZkSt
-LD1XMoEenQjmENk2uK54Mfa09UTUe6uebiY/ZQHW/LhC+Z+JVGE3GJXhqjn2U272
-vRNHCOC1u7rGDy4KVf75bHZAKSroXrjpqgwzZxtQtGQPJoGpMKx2qzlFABQcqeCc
-/sPutbR5eFZzeqQbrZJ+qY1pha5KpPHrqE/QeXRMd8dQKXCR5UOzvwv7bfId14T+
-n/kfPwnzD8o91//EACUQAQACAgEEAgIDAQAAAAAAAAEAESExQVFhcYGhsZHBENHw
-4f/aAAgBAQABPyE9Z0rcCdB2bi2gtW4HmMjIVWHuYcUCU5eYwoKFP2hBbZQu19sF
-WDFwPZOZgcDQquaH+Yxs5203VdHuFqNXouUu6EZ+Z1hTDzTyxnM2aDHT1EbUVngs
-BSjk5PuFkJ5DWp0M5q09Mths3DHf6UTfwkPHrMbbXRvQOEmjDiBef7oFSfCo7F8n
-SLaqvShWNQC6XDUeXtNSX2i4ae6lqNVvt8EfJrA7V0fcxG1jFyP/ACV9cKBqOiUZ
-C2wXUN1ApsETZqunJIbCqcbC1m/dd4h6PcoosA95waBemcygowzxV8n1CAldAx+G
-OkInmlb5kcXOqMULtdvdZzsfDvL+5eXLDgwcETK42ClB5gsmWhGyDjvAo+hw1rIV
-9xwyiycHKe4fkg6SxlMs8Vz6mlZG7809IE71q2QhtMSziU80qOcS/wCBscTUFAae
-5nJNf12vZAGlQczs+e8aURa6ew/GYQRAvyXqXlqPNGre4DAcqcwxTNC4oUqEbJcl
-biDq9rKPMLLuULL4ivJldr7qiB6YFVL8gr8TptMS6eyMT0lxwSMNwp9qE2vLaYjv
-XMHtP6jsaTjgGggcJu1d8kFcDC9Vv9TEDAt1EJs+D+J9kefEfIF+uQ4kBP2GO5Ag
-Tyx8JjS81gCjw0bY3cFQ+JV5iroWcvcomXk7O8cl2dMyCY6tvK8RFTCsu+0teB/b
-O9OZeCphaTSkz8OWFmWqmDiurNXKDVUxz5gmEbUh+U18t/mMXgnTEypbiqsw6syz
-AGjrHFnPuRKNbJhRgUl/lgr4M/qH0ZfZhCE1tqWF5RdHUDxi+X6m59xaDSY6z8JC
-aFK2E1n+p1z/AGeZ9H8d8/DV/m13h//aAAwDAQACAAMAAAAQHHAkhuHnIM0XSVXk
-2hYx4dFqqkUj87TBa9n+afigH7l5i9yqnkoqIre5SNPbUU7e8//EACARAQACAgMA
-AwEBAAAAAAAAAAEAESExEEFRYXGB8PH/2gAIAQMBAT8QJY8fHUttqO9u11G1pn57
-i0lUztYjpmDC1RtJ/vvzGVqhZWAwTm+S5AAjvGuag4qpoYZK6xBsVVKvHc9FxssZ
-sNUdOxiNrVDNURCUkbmtMAKzN6jP9+Ro3K2JlQluitTtDF/XnsqCG7gTuE0ySzYz
-EEDMv2NsuS+6IktRiSqxBqcMtqULuW7FV5GEJTD3KR9t+iFGI4DdTIKIxu558TCD
-Q/sMDuUCEb2bj6lkow7UclYgA7llruXDRg3PeBUHccjqV17gVDq4UPuZFMIV7U6H
-mWIXeZpANE2LcB5GDCQbXBEcJEoiomU1KiGY3H93LkuOYY8umbnDpRNQezUMqcum
-IHPGsORthO4bQ1P/xAAnEQEAAwACAQMDBAMAAAAAAAABABEhMUFhUYGxEHGRocHh
-8CDR8f/aAAgBAgEBPxAGWhXj2uOieu61TMeZb0hy/Dc4lXFTKOT+PxEWfb489ylj
-hXxEJxbivXj++s0HnzzAbWfEqE5y9e3x94q5OgUteh4ilDGel+rLo7eeePHvAuvb
-fz79TB7ZCbx8fDXEVlJXiLjXOZV9/wB6ll8lS3giEAPzH0tr+9wAQTrd8k1EHO5s
-BzPacX+2fEwD1X5/3KVlH7SzUr9fecGj0uD16QLMNehi7WZ7GkFi9bELz+1n/ZyJ
-OuiIqp0Yi15foXLB+sOdyK0snlFN7GGXfVXn7QKdQlYrbIFfmqfdgTmLY8RtouM0
-Tm4eP5ii1nv/ABFhc/M1yV5yWddv3fWJvSX6qDVUUuD94YDgjwlsbXiWYuu1ikmq
-qXS5Iwp+Jq2ARzt6lANr8zKjIZdC/wATNpBjVTGJoAgthUUy5XyRbRFIC/NINcQg
-1lyZkqEuuoMiVVLHE2XOJmTJeoQ2XCGNXKgbKFY5QR3mJQfX6Gv0X2o/4lfpP3nc
-OPo7T//EACUQAQACAgMAAgICAwEAAAAAAAERIQAxQVFhcZGBoRCx0fDxwf/aAAgB
-AQABPxCUBCWAk8m945oGlsQalN+ZGw2ECd9zQQNzsxJ8GXOmKEjFkyxgQcQ+ZQEQ
-mLyRUgMBGQ/Dn2MR5mTXYGgZibmeIMVlINayjJgqHH6y6LMN0kG5tGGYwsioXfaV
-yxhhBW4OFS/b8YuvJGyFPBN4CBa0SKESkjAu8aYmuwMtOwz/AJwXCcxJ7Adx9Y7K
-AGg3C8G2zcc4rIY2hYH5fqNzlwVTAoA2l+O0MkAZCKJIUiSYoVvBBCEBrgiwsKSR
-zlDgqv2M9tJw5VZocibdDM6JVvKs9CCIpCXTNZ2Y04tyRLZunfVYPlAK2zwXHfxg
-9FANFHUsucm6Lgjin4cTkQ+W1rpAJJ3gaMdwQ1KFcoyNlZ7cMTOjbVXxjZuKMbQn
-R2rK5BiSzwTKntV65yVaC1QEKRAQCNFRluTDWhmuSWkQ0hkgJEp2v+MCcPT3ixFe
-hj5gnJwAPiGY8weAkvaIdiRp5EZrxTqkYigJE6U+cCnlwd5kDwjARSogJarKFCsf
-jCWJRVIKg0LfgA4x+WIHpD9ivK41E0QCzpOMC2OmSs1iJxz/AMwoOFCWttD81ktc
-ZIkOARg5XPZjp5lYkI2JgV6kGItFG19eXHN3YbWUh2nZ7hD5WqQU1sSOeMM0RBHg
-E86x8bDCjOWpv2cnq4dB8v8AOCFKDbbqsZ4yFlzz4dTxvWb7UBTTcLl8ljgTKiRi
-2RwmHdMVgr3HejpRnhMPWE8lASSO5wG1MRdMelBXBBh8yYFRGx5iNe+YgikAEiRZ
-0jWDKpGjgBPMWQ3pGTXGhzKusGAB5oeNER5lONEQOShhBE/IY96ZaMNTFp/WUBix
-85oB5tcYKAEYSW4eZDKB2cgJV0xJ0icGD2ROsJD8WJ/OPY7JpXsnXxORpkBfIJo+
-0e5CKdoDhLtCNEr+cC0aAzIAQVNY4lH63ojxD+cnNBQlGe+OoxrKsBE+J0n0MFwD
-cGD5D+sv6PTba95HhBwyPgtCSKOtgYkbDJMkS0upbPX6ylD0FfZf7wO7B0mrmZwT
-YyVY2COq/vGeuYqZxJqGFc4ERwS5iE+LwNUiIiD4xTQrSDlTZg2kqyz78aNUdB9L
-3iQgNwcBo+8OaC3vJwOaFA+ZwGgwILT1VE+uVpWRTr0SFXihhAmpW2N/qM72dvOR
-3ok+Qf8AuHSQRRJzA8cRsxtVv85SwUvgcYiANqODFJ5dSVhk3wA/AwDaSYf+MON/
-JWK0kneU2jzC8arwrxZtC5JIfQ/eSsO8/onzHUJkK79xsC+Coko5lcNjYWVitI6O
-XFemlMcCcPuGgEBLvizNH8FNf+152Z/Sf2fxc/f/AIf990z99/bn/9mJAj8EEwEK
-ACkCGwEHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgAUCWFZ19gUJBb7ApwAKCRAm
-EBti9pN2znUgD/0X0ppBbZ8LZBYkyCtCtIqK+CzQTI6sWU6NDAjHrHoDsmy2RcQS
-K0Ihb4g3w0VWhU/xbwDsOyHCEj5KnhfLQTUAD/8LIKUha0lJFnpT0/WDUV9EBRMT
-xJYENuE+Cn6VhjJLrsXNTawcifU3RFUOnxYDHI/0UwEJ52b+9l1D4c+HxkJZGjqQ
-DSQh8skqos2Lrhm4m41B7/dY2BfpzA/ZVUpMtWOwLHumBjtu2n97h6Jhx6duTSif
-+qghW9ViLAK0u86ZXyQKnhZSbTpeHdfU5tJUpCVb3hFNqzaS0HSfRTxeanQ09zyV
-92eoRuOVqfcj2/uYq6PerLgoPPhmP90PpSg8WVHSo/nsqV7+oteFkEvPxU2Pq21k
-B4iqD0TNann6h9qu40ZkrwX/oe1y7DVRBmBhcRHYiClmQQHO19OvD/gGt5KHKXZR
-jEvMD1EhW2d8sDlr3tvOiplim+k2EdjMBa/edhmtoRVV0NAuStlgiWNuzehFay9g
-7AjA2qurNoGvLlr/016hDy8KcP+0Uhg7bdhuELzU4RDqGRPGD49cH5QFYn4FaGre
-LrYksk/zNt8Hj1nko9seOMX36gSXqA+dyl/095Mtl+8E3rwhWtQbx4AzWlhFmQ1m
-f1sKZxdPbIa2MuSmzWBnctUCIus/4i8AOi4w4J/gAQ6txiAVaytzMUxf8bQ6SGVp
-a28gU2NobGl0dGVybWFubiAoRXhpbSBNVEEgTWFpbnRhaW5lcikgPGhlaWtvQGV4
-aW0ub3JnPokCPQQTAQoAJwIbAQULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAUCWFZ1
-/AUJBb7ApwAKCRAmEBti9pN2zo33D/4xSI5qfxOJMVdwmcK03uWQoaAkda4n5/AV
-yZb2lEfwR+CfuwmXFKwTc4ogZhE06lkDoW6bfQbsbA81Vnmjzn6YUIR0/7Te5YDQ
-l30MeBR7dD5yXArOw1yNT+/jDU9BM2wisJyAzdGuYUm9AEH2EDn8iRehSKYIhDwK
-eqhSWGr0Epl6qQLB2nTQb3yCB6dXxYKVOr1OFcZI7sOn2yc9LxbHdajWXcf+xvWP
-khvnGdsx2ZDjCKUvEa9JmKkF9WszqIdHl0oNceJSa5qf1PXKL2EcNGd6KMx5Pjwu
-LKBxQtWx3SD6tGs33jHBh99keQ06zZwpS4DsrQWR3g/ks8YvjIY2DJJEtMbka0dk
-cnZGbUl114/UYFEsmLK6r5/TB5WTAL4ucl/chrr5+CZ3yZChhv6+1HUyEtIDQ5CL
-zjtheVb6PTzWbSYZTaqXv9Rkq9611LpUeb+61PaHDKw00hFur+e4ITKM0ouaMQBo
-XKLhTYt4HsiRKuoTjiaTlMm2yLPQDpc+Fcmnrq1YaNIAq1qVzapRb7pL06ZwJm28
-6sixlfrC4K/p4TZ5H91uorI+8zaIiKH1knbg1y1iW1J1JgJ+4qkG23TFYPeFevsU
-dY5KitWUEIGZUYbvi7IfP4FKUfobT2Ed/4nWvm67lDUXT1dU+KkII2Zp3fnYTBKa
-dWmOwHP62LkBDQRUrwUqAQgAoloa9GF0nWdO/3DrH4XvOdcupSk6oFoZMQdoQfx8
-7NoxjR4epy1iZtYrZNgexs6S7a3lOyaAmH0zSBw8iJ5CydKpY7pVFd2lFbUvS2qe
-Hz/XVVOnCXcDShHfYULBpt9geuJc9NGmoSlF8Jjp0h3HxrSTDneatYlrwJaxMCmz
-4AfC2QIwmt8FfX7WvNm5qqEc/7qDLgAVhbFBNPLRUpyhLn2JfMaXM0aPFaPvqwSw
-0reLIpe+L4TXdv68jRq8FPjBzcXBgsW9uV3qJnncE3yHVVv5pIF3ls8V24jl7k+W
-wf0vdbHFomPbFRWosabwlG00O23X1TCdqytDNal7iHCzPQARAQABiQIfBBgBAgAJ
-BQJUrwUqAhsMAAoJECYQG2L2k3bOL/4QAJHiGmiO+h7e7G9AUMmZUmiLdcZ0QJhz
-webKbsebI5qGF6x4sqsT5FuVEFs4HYEaXCP/Mk92xBpt/5/9h1uKqrxToiIsL7EY
-dDtTQM9dlLACPTinbz/JRXG13aH19IAQcpc2mVwKNSR4qPnPLUJmBIrGdUGNh7dm
-zmnTrziM8U35DcnEf6Dj1GzIK3wfj+p4DFp0YWXr5dNGmxU63e/RJXOA6fZet4ZU
-ON6BhooEEGiZHxQ3sL43VLEKUaGbOkFBHq4+I1zec7VM++SkW+7zjNWvspvk3Tab
-tPDAf4OEtl84jHQpC863AzehOcXT+60THTC+K/1/u7C2B3yPUO1gIArHFkBrWIu6
-ePUj5YsqxXDhM3u3EYG4vqUB3b3zbg+1vLx8w+j0/Y0b6UX5GkbfYAVi27SGxg7o
-FaLR+ceFuzybw1xhUVWp795gHf6pX0XZOFRoBUlsSGczCJK+BhJzDm6swEtbSBcT
-eZsfnH1GmBM4X0+730tGs0Z6Va/+rn7KgST+JzztiO6/D3uBeUVC/wOHuMNcI3AP
-e0lSZp3iX57nxedd24TioFyOhXGjExl5Rb7PtntGT2cFrn4hZcxUMaobKZDsGVi9
-pGaT/LvWPauIzY06f+kS/iCdDUHQhrtzEj+vuZF4xY2YYKxbTpicC76LrdW0iVBF
-CS4Bdra3PzODuQENBFSvBtkBCACz7w7u9QK+K1Sbtr5wree+76DNF79X8a3+I9hL
-w+mRJXV3CIn666fzaqI666nQFeUXK5C6x/utoGfqPn9Ki3nXOg5NibHRcwC6yRi1
-vxoFLhsPYZGtHUuReToGpBqRxa6VtwKbiojRIr7EXS+JAwhrEsEpYIO0CymXHFmb
-2p4EPWQB16ukWOO3MRn/Z1ucuF+9LJCwWVEGI0oKyEFQ9QNFRCnqP9gSjU8q0HVZ
-XQWUr7+hNmfkK8ODVnnNW1EHpEZAO2AfBObngSjfT9ETzNzLTsWsgvhDx33o79SZ
-Iim47U6JYjTsfavRjEkXhaJNkTKGC/1RXAjBI3NaISmQFjeBABEBAAGJAkcEKAEK
-ADEFAlTSdNYqHQFrZXkgZGVzdHJveWVkLCByZXBsYWNlZCB3aXRoIG5ldyB2ZXJz
-aW9uAAoJECYQG2L2k3bO58wQAKYDrOJAhpamwad8AcgA98Ary2AWPMLeSKqiV7uv
-3c0JN19owZcsSR5lmknaXH5fCAVaJg4x2RlO1iFGwRBekS2gX781er/evNktWBvA
-EHX9dZjbuc/78k6Pl9XpbBCljbGtClLi/gM7k/tgGEwyqr+Pg+dXBFhGbgknumjh
-0XJ+cc+1Hiq/pgzx+/m1blQPACxruh2Dmt9QE/SfkvxseGNcCVVppWM2JvZAQI6B
-YVGUiKDOcO1bgdaISzp47/2ShJJ2RNQzKMQ2pAPjtTUbTfq3VxkJCi3pzkkoKVkZ
-hgduh/tKA6RMqPYCXuRimB1QEixfRWwBGlAPbgCmXtaFR8FcFWtSMFs2w2zibxe0
-cWRLAAUfqkMEUPJA8aUZzsBaM0o4Qlz7+ZX6Vp8/av4nfjfgZVQyrwmedGcgCj3X
-uYTdiGLLhjYA7XyH8uiKyVjCXRc2j8GcTtKfa0DTFMvdMwPtt39IEv9Fs4m2xlIq
-hg9rIUydgIv1+iiJOUF5iqoF8tMUko2moqEoCe3cc8+w8BsTncjKiN6nbng77vIk
-zRO101YJN6Kw1bPvGeFu8MapXNq3/fKM1CGJBx7G/dI545CHsc7Cd4YWX5LF7+6Q
-Fc2jTAceFG81OEoYD6O1YHXDcwEcTQYrLO3iPSHBLW7qAeCkhVH7BmHjXyQYuyZH
-sH0fiQM+BBgBAgAJBQJUrwbZAhsCASkJECYQG2L2k3bOwF0gBBkBAgAGBQJUrwbZ
-AAoJEJG05d4bZCmnqnoH/24cH0moIvRY+KPhEkSEn/9BTTd0ugm6wxNi2MyS9bWS
-wGaUkk31OG6I4unGauca7qMbbhHqn0G+ibWT4IHyU7En8ROyXbLXs4ySzk9Tja48
-g3qaFWeqTZVpMzhqewM8R3cZxvucYPxriDFdZjWHmdi/qCTd+s8RPCOQ8fW04VH/
-U/Eeoon9soQE+8s/MeA9fyyrBMI/AXIiiEHP3dpAiWLJsMKZoHSmAvIonolan8BW
-4NRH4SqO7jvoj05Ac8snkHVTO/BxHanZ0kEUsytABs0L4XEI30w5ctC+XAVyTFoR
-UjPp9UY8lGRIN2E8cn51klNAaQIrNje71Db6PqLos4ExURAAqtjFVU+Cr2vUwVfk
-Fp58c136MDmxv1sjNczDQ6ujyOV9cwMI5t0ibAw7T/JxkqfLltX8uZc6hPaBFQNW
-aJNgHNjKooTYSkrrBJS/nkv9zt9ORhjzEOETa0pMCEaKW+WtNCWcomOxJkhq1PTn
-V+17ZLLZ4iF4w4ApWW9lzEtVjr3bUibHGuSjB4gchHj0maMIbmVuOtNWqgWi3lVS
-wgD6Wh9ZEPvgdl+H3Ue1TmuI+ZIoy+2PMHntrJAy7Q6OOu9KbsLl3aDslxKNxNGO
-yv550QclwIhabZhMnXMzwvMC5RBNF5Yb05+RK6ZI1aATdTISCHfs1MKuS1gNSBGP
-Sr9TnT3TxmLkLb9g5+ytu58BmzQ5M2lalc75ii4WE5vDD241cGCflPFsFY+ODZBR
-9u0fqaqyUSopELgNFYXn/5dqWtpC/lANuLgLai93ATPcY5K8mB8pe9yXut9lO59W
-EPLwHnPt7BEpzTlm6vTfWzICn3sLDX814DRGqlxi02LSTq4TuLSRfDeGQWPJ8xEu
-gSTjinhyilCcTSBjkZVPzHpfNgrRbMZ6XRKItHk5+2m1XQuqFRChw9k/zuksrw2E
-BeD+8hExpr2k4H8kzD9iIDX7+JgafRi2zYwWHtGpkelerPQv/K3aEYxopWPzj9zJ
-wcu1OS+DX6R3v4p6iiF3vtKudJe5AQ0EVK8HqAEIAJTaC3AINpl8qDPK9qSq5zV+
-lfeVA9D0O3BqCA+iqZneW3c7mi7T7A2da+KpRGanywOJtibB2TF/jWrNrbltpbhO
-JAvsou0/edeZQ0xpTAYRt/gURgRLGvRveaY/EE/zyWAmLqz1FYJUoYcyAvGRl3Yi
-AgbeDBMsrCUpJF5S77sxg03/QEjpO6jicfFdSC7HvYwfC/KLOU3nckWKkElFJG1G
-/X0+cww3H2yl7smZ/a/rs4nolcPOl9pvtZPqSuyzW3Z3JBktaeVZPGMrxqtCOgQ4
-HCXhWSNdtuilO3r5Ojwt1mJLf1VAFm8oOB8/AZUeKDGNFJJl9VjIX6UAOhdYkEUA
-EQEAAYkCHwQYAQIACQUCVK8HqAIbIAAKCRAmEBti9pN2znd7EACmlHur1eB5p7Tm
-sOn8cHN7/3vbXqaGJab4q3i0Yg+0ZTmq3AmvjFnT9tsE1FxkSHM7cvtg9jSIZ4J2
-aqQu50x+heypV12VSMpSVMoI58YoX6IIj2vAxBjbNsUvpXemOzisYPdpCd4z9h+0
-C6b6vd3r1cWnE4SQoD0+QDJh0eXPSmESdF7DJPmKz/BvRJzJQW+XdV0+w+6+Dxex
-W3gFkqM5mix6BTDs4NoVqWgXHNDuoM/26RODm9FaI3tueFfszRxGq8X6DHFTWr0Z
-dHvZoDudz/LNNOXU/jsajcB0dBmbB3f2P3EjOlxsoau8bq145iltr97RmnHDqdPK
-du7uNcelXn6Qct63dyizFzvZh7LejXHslikupKe4pXccCCpc8HtQ6OoUNGXdVyWO
-0WgMKJ53NGLKxtiRpQrr+7D9YAXEi7KsfwDxcH1AIupVKgHAfs9NF06KOr5tYYi7
-JhaCAxlGZ5uz0AX/h0caLdrCoLQZ9deV8dRhXe1d1pVzuMc9e40RI0y+z/B/q+DJ
-23I23Q5kE6zuBfhJrgCUUj76cEU3PugDBlDkjAyjfgEkKGsyz0QohGYCwQq/aKEX
-eAJ+NrfkD9Jv1jWOafk0UEX7KyWLsCbnlfSkVY7QIYDPNgwwKC5dQD9EIYWyQb6u
-QnWuUai52+ANTEFuDj8tmeiwvTienIkCRwQoAQoAMQUCVNJ07iodAWtleSBkZXN0
-cm95ZWQsIHJlcGxhY2VkIHdpdGggbmV3IHZlcnNpb24ACgkQJhAbYvaTds66/w/7
-BcpolgxUGKvdObzd1bfM7uCXgahvwIOY6PAi3b2yFElRlkWNnUUSRq4ZcZnqcMF+
-eOWkKkomsTHD5z64vH0jBZxTVis6vMSAuWgmjOcWZzfDU9lecPtj/72cXOf912vZ
-0Jarlwfb+e48wCFtSyZWKr1OyC2yWZctu7K9r9SToKIKs4BM+DQMQksFKDTOjmT1
-5yORHoCDboliqSI7hrSEKCnlJmtWATitVmm8X3th87tf0vpZgMGbaoOxwl9/DcD7
-gBcRJQAur8d0AFfOfitU1oz56AR7O8G8b/B2RFHsKs0oo7S2Gv8i4sjFVK9AJt9c
-obIBYCi0F8IcZyv4N8U8lOf5/Y4GTBMIOJtxSHqFxerQ8mL14+0SubgRki77eUeN
-JFjYlJPKZdS/iLZq01Mp4/+oNcLi62FpBD0z0pcioGaI08erLAIgzDlR48aVsVZ4
-ZwJFzpSzLnHEz8aFxEIvbFzvAcq20e6ZlUtPrFzQerV27ZZQbDwaGD0/snTihi6k
-of9URScnbN0D7PLM8KLK9sKOUzKwjHCIl6WJ/+J+ITOtToTy1dDo2JkKMRxNHLYv
-KZ7RaQ3liTLw2HjdXwLtmWYomBP/uAghnvnJmLztlylmTEB8C72nbPKAhqk6XonZ
-+sCKbDbFTYOpYnyhXEarlYfest+hj1vibh3nkxrjeO+5AQ0EVNJ1IgEIAJwynfBE
-7wL03nQdEmO/D3ZaPnOT8jFORIXrjXsxuCxScYoIsSLqPWVuU5ddXTtBKZ8g95Cr
-CciHP/haERbkp52XhfKycB3AfNfm0CJH3pOa3PmWv6OsCfOMjM3asFOTqHNTK1XZ
-P9031Ostbhmj0np71FJKNO0rlVDizgbrHif6Hc/BNpUbdoidRy3G0V4vqUf/AyyJ
-uFPjy2CCmCq8QzQZZ9ppQe8FiCzes+3InGhNx82afdtLKnkhn5dLXV+c+8CONhGX
-H6hEVpqzXctP5s15kV/6qIU7suyNOm8K7+2rBojS7wH7z+sJ7EZy24aNNxZauBHn
-db3nXK9GT7cmgcUAEQEAAYkDPgQYAQoACQUCVNJ1IgIbAgEpCRAmEBti9pN2zsBd
-IAQZAQoABgUCVNJ1IgAKCRBqF2OKoEUM9e+4CACKtQ+EJkf2auqHlbGMx/+fq9EN
-CTOX/iSg9WvTrTzZFeGdweslgQOr1SBgVtRgekK1ffXX8VwM6mL7A7g2j7TXLFzW
-yu4kCrd+ZZVqvhvT5H4/cK0axKPq738FgyTJ6eQtjPYbnDwnN2iwlBOVF9rizi7T
-zqA/RrfZr+/pzoHRWXgDZ43x9bFM7IGDnJilV+yjnFeO/Z5DU9TV3qiJnpF5pExR
-ZliBNP80PTISkmnvhdH1eQIL+lIr0XOdTH7P6PWs1mpexwf+bttBQT1fonmV87Ep
-xtOZL15JnXBjmkqzD+fmdFOx36NWLZDYTHltm+HSJmS3wmVG+tkOyuCwqFvntQIP
-/2AG8xgVX5ZE77BAIsC9LW42qqRjHAFjFoOopTZ6htkb3eBkxsuujzGNJ2Dlcu9+
-KO58skhcuCF21B/elXqWtBuicw5IokUVYXd1T3xBSvKjWUWF3NlvKIUFfLEFP8EV
-qThD+5Mw+a5usIXNId6jXi2143Ig30u/OZgIx8FVjzs2Lj5cWixNBmkHTDGD55+t
-op3AIHnYyfcF3p2LoKLX22KH1+uSJdNcAlIb/m9Qrknd1pcBEJ4mu8ZP6PxVXUaA
-vsehhR3haY8s7EfUCVXZlA3Q3S8r7VTg/pDB67FhaJcc6rVXlKHdPtW8rzKI010J
-625omSYA7N+HlTGDL+E0DzYapkLleDHcwkvppl52yY8S/GNpwEVIeInw3iR+jPKh
-EKlhhx05HIDwBRBDOZDURZMmBRZZTXx0Ykp0QerjDAi17YJk8mpm6KNkZt0dWODg
-qNsK8haBoiKK3pEMeGub8QsONSwxx65vlxlCBWYtZ+gJh3aBnB6tDovZ6ytfZ1Mi
-bvZqOcOBFNzrPBNldVfdsiMfZzTtGbqUQV4qiqdYmg95xkFq0upinBvr5sBI8qln
-q+4vdZosivEt8hp6uMzaFKBbX2ktrIk1jUIMwhI6ZjBHBIlaz8HxSOTgNta3r0QO
-7UelMLWZ9w1LJWsaLWNhPXQxIA70WbLb8geMVq7VyuE+uQENBFTSdbQBCACE132Q
-pR7pocJTL+LrdLkXj9Em0fs2yXv1tRS5eW7tVIzc1XITsqjXThn5hzfJ5f33ONqv
-esqeaBakMMaW39I3SZKGHFoLwqaczGfBk4ihnsSmiGoyeMD2F9gTUCGxdT23tlmZ
-SlwDH6rAnXV1JFk3QEh/QmFwjAdDfkzpt8roWOiZRWYHKwC7I1eVC5OEadK+287/
-/RWS1mfieMaOiGIZTZqTDtGaokN3rLB62LygOUQjW20J9j4ZGIaHBvmf6dQ3LwBB
-xumeSsLxGq17VCZID9EPCAoTVPkuKs8ZfrKiLjAbuyZqgTm3oxHqStmJhGlKVn0Q
-a9IRfztb+NF0yqdNABEBAAGJAh8EGAEKAAkFAlTSdbQCGyAACgkQJhAbYvaTds4e
-fw/9Hdd/bHOfZACu0BrGS7dX+/2QmVZ6SP+yxegCQTeu4w0iZ+ohXVx4NUNzoBsg
-JqmnlY9+ulWUKMKQjTHJuC1W/4Md2rYLVMDvDl5xXY1fwkiGwAdjAVVQyJmQCjXL
-tKD50Bm1txiHARKScIuNoFj96c19pA+MUvZoLWXL52PNEKCHdi7mq6Vtu3ae3W4S
-QhFpXAlcm3CrKK52OxMFKTqMkk0r4/P+U5U9tdooElDJVoUIYoLfSr/rqPf7UrUA
-JNyk9AhajaYYgJ+Spw7FrnLoUJXgrQzRCSyDiWK6StHiCrzBej+4Co+m/N3ajqWY
-kZeFtvARPSNDxjFELxT3Jaj855WoR7DV/biAgvu3TwYcav4GYykYuq/hdFFy0Z0P
-QxSAL2Hu2s8f8T8rGjqED4++BeqTabDynKCT5dmRQ/fDw0LTTHeoxfveFKfegc8O
-R/nzYteGj71DBPpdaGTCZGDIYdSy3wb9a+9ezg2vEmP3JKMn1Z7DxP4LNOoL/ySu
-mIQIcrZWxWZSuPsiOm8FUWuvQ4iwzu+ZUC8kzNwQp7MFWPwh+DYHkp8K7m2AdjeJ
-EGPaIlhqTKIUrUEVUxkuTHGMExd/+gp3CIT5v3X08msnKrN4/HRU4x4wyUJqWVIB
-43Pv6Xfqz/1LayeY/PvMbHSSXOeXjl20iDCVxKt5qii8sfCZAg0EUmYFigEQAOeF
-OFMWA6lDAGSAlUU6g/pRDegFlNxFJhPHcDilxCLjLOIhJU6D0T1+HZh4bB4BkA9E
-qt6/FDzaW/mQO/xS+UI6cSH28fiWl8NqCKuIQCRxNzvJSYIkDJHzKDkqbtXTV+9s
-tNYhmKx/kSrADBV2Qhp6fkINjHF9rLu/iMEZfE3B1C7ieww4a5g3dOXQUGVaJ/Qz
-KEZPKGXqsxPaWXIqeUlodsKgCyle83VFda2qj9satyibcV82Z/dsP/wrELnwOYEu
-eGcN5q7q2iFI/yHfGvzoLF1hvVfPwTkhFWZmij80szRsbWEeSJREeImqjfpGgxqs
-USEJ/KgfC/3wfO55ZXVXDxlZxkcy4ciyRP/94jadxSfcHNPei7d5LHotmhLg10q1
-QqpTJPzYcNdj1xSAu50MD93ZhSLkHLZi+AZcVE6YqO2o5ONSq7mTQFMA6N9fn8hU
-ED7PbpdgmAjTVtaK8Pk8ji2G0l3zydfbx6+7pLA3R6/93VNPv6sazRYyKh9Yuel7
-4rXbzsm5D5alWF/39R9xxFsvmthflNCnFh0zMm/LVPEeKfMT6MRwSRjQdUGE62v9
-xrnolWI6UBCL0CDjtJuwMrUKDwHaE7gygRW6mQEX3ZEdERDX5GGcLxwdfki8T0Jv
-i1g/cNvJ39lRZC61tusKhos/DO7qfrzIjgm9AKOdABEBAAG0G1BoaWwgUGVubm9j
-ayA8cGRwQGV4aW0ub3JnPokCWAQTAQIAQgUCUmYKXAIbAwQLCQgHBRUKCQgLBBYD
-AgECHgECF4AiGGhrcDovL2hhLnBvb2wuc2tzLWtleXNlcnZlcnMubmV0LwAKCRBN
-HpAOFMHMBL2BD/4kqg1vkxbZmlIVCjPS/YYhsAzd445elkpvx56S66HOJwEK3h5g
-tJvuSBuIXQgfvfeqwWf4w1tFja5GiBTpRd0SSq3ZT2OOXOYpNrAnFDyRy13B7Pmd
-Cz1ibZtM/7W75SXWVL0bkuSzxTYO7v2VJ4XjEsZmBhj6i3JKidmR31a5gf1WBtky
-Eun9WV+KaQSKjaxbPlK+wTvWdXpClVNOR6izFGbxATowWQmZR1do8yLh64WPf0Ia
-/yg88cM7ZnnGKa6X9Tgr8vgJ4LyUgNmCPIX4eQKQ4PVTGB9M7hEobutQicvBceHB
-AMJI79GXzker9n17E7Fyo2uJzjIdWoKyCYqp1ASu4oBuk+LxnEW6nv2A48YnZSr5
-kF/6SRM9PVykWoEKIrj/GEHzo9dpgeg8EBrjQpJ76GyTqy/KJwRUxRw1M8wrSeGX
-X1tEJbRgbXih2k1zLjQVCq9rrNTf2nX30PEcMEoLiO9mbLYkDqIvhGAfcwjoB302
-oPuPlLfCnI//3HnhbBs1lZryLjjoWzMBbHK8E3HLruN6uvYxtnKY7rF7hsFJLB6j
-6kgeC8Li9ZjmID40/0vvyamUs6jsvIiS+1mDvCCYhOX/7G/19bl8gcOCCbDh9tC5
-bGSf0KpHu1EqaV7I+ny25g7TFX8AaPtuu2AmUi4P4JC1crBDESuigUBv07QcUGhp
-bCBQZW5ub2NrIDxwZHBAZ251cGcubmV0PokCWAQTAQIAQgUCUv0tJQIbAwQLCQgH
-BRUKCQgLBBYDAgECHgECF4AiGGhrcDovL2hhLnBvb2wuc2tzLWtleXNlcnZlcnMu
-bmV0LwAKCRBNHpAOFMHMBLNyEAConhqhQTA1q0tQ0b5NEAellt7aae2m1rtLC74T
-PArVMU5SZqcFdbhiRKo0s1QlI4V+SNZShkNH79pk7ltjx4B7Qy2H0WTjygNNULM3
-X2AalDxs0j3vPdi3TCm0ebLO04WNUbyPr1972mHjqaCE2JgTrEr5ZUebg7/7CsYV
-dtO3T1i3KAy5J0ODg65wqcf++TJs5YGJhQD6Xu5T6glndBxK+5ChHJ39Mz4GlCLr
-Wa87YKgQfyupZRNx3H7TMp9jbjFcpIXar8wFPvX+3K8eLmr03tMbCA62biuULrl0
-k54ZE9R/E0faqMAXydPSPc95B6BxxSeONoicFuwocESyJUKLo60FR42F671OBud2
-WqEGhjxO2/tIymPLUJEdESq1pKCqaq+dIZwVf/H99wPKFEvBhzzFvtdgkGIKsvAE
-LFK8dmRSaLzU51CLmjwzVodiKNLxAV4ma6Kp6V0lCGcqKXGZwkqO0+DUJ0//ZTRN
-ARcS5MQqV7rPVkS5ejqZTBU0xPOiWCkpvfzgmVZaw/9B+eb7uR9OBLxUiHo/rtDY
-uwhRkX+JtwvWBkZur0zpHwIeJn/nkvV47PdUgPuwIn1ZhlQWwAj5ryhNUaAsQYlV
-POUELHjsJSy/MpHUKbs3Zz/MoYHEQgB2TEf97/lS6H4LDGFOi11t49d7Xi7F5DMc
-L+fqd7QfUGhpbCBQZW5ub2NrIDxwZHBAc3BvZGh1aXMub3JnPokCWAQTAQIAQgUC
-UmYJ5AIbAwQLCQgHBRUKCQgLBBYDAgECHgECF4AiGGhrcDovL2hhLnBvb2wuc2tz
-LWtleXNlcnZlcnMubmV0LwAKCRBNHpAOFMHMBIaED/4v+2yqYRS87QasQ945CE5H
-eeTU2oKbqnZBgeK5FlPmHC0fWFBA8/iJsLB+TwfZ5pNlnYbowX01ixa9usW9qGDh
-nHAxnHeI8lRheZ36rNnbXMiHXE9fEzrWcTkgIy4iB5vlV1KBQ5UrQFcxGlexdLqq
-CENaSPxHYohusrBPBbk6V0KxNVonCACOdXL2ECPZcjA2TIFDjn9bAFO/DFh0pJuZ
-TVqzBlazqDxzL/YTwMGimKiy1SeQFoIZGbQNdYoXyG2TRCuQYX/qGCXAbbvym0eU
-TqQfzHQ4f0zXxeu5ZVZaspRUTSZiydXG+/4HEDeSICMtRXWl4aPXRG19u4A4lLob
-g6Ty2+Hez2RsvAtCwmgt0DQfqKDKnLdubFtM0LtmfPPQ/4vx9dfcO8jzcG1ZGWHL
-DjJoOscUBY/kheaA6Vi+68GZVfQPh8/qLDPU0PZ6/6PLtvm/XFsJjuBIwG2fy8QD
-UaE0O5zKGbcEnKQPTEF4cjLuusz5Kp3iu25VwmUEQNcFLKhUEI2bQ3r43wADJNHw
-GPY5olkHSflyhv5fWsZCL24H2WuQMmlEq/a+53hYD+WFu0w9sVE01wSZInNdCen0
-K5dhP9StLShPHFDlFxazGssV1LDRX0FGlyfw7LcW8vPSBFmq2/csH455QXqzFgJ3
-waeojCbZQn5zX9AI+XRsMrQnUGhpbCBQZW5ub2NrIDxwaGlsLnBlbm5vY2tAZ2xv
-Ym5peC5vcmc+iQJYBBMBAgBCBQJSZgnCAhsDBAsJCAcFFQoJCAsEFgMCAQIeAQIX
-gCIYaGtwOi8vaGEucG9vbC5za3Mta2V5c2VydmVycy5uZXQvAAoJEE0ekA4UwcwE
-nQ4P/jB+mcHiWC4qEhIfXln15ydho9j1BNAGCx3u/axC8Lu1Ykzq5MfMyTYbpiiL
-I4Wq2w1eXp6N2e9cif25nVo9yVISTxdd1wzZzehedbjz85rjtCUMRgYsQh4N52PH
-nYYlkk5ctjdvrENUJ17J7v92hogDY0qXhGply0pI9LeH6g//OyrcysHAVqbIgr/B
-yjYKgaOHRvzxdYB34Djw253NQkyqA7kio6SPegHhSVlfJceNFDuf+lJ4wXyB0wlU
-TIGFnJfE4Gl5bqOhKMLOqGr9BhUoGMj/wEKjh2Mcb9aHQy1p97IiODgj+J/mloqg
-9VDfC3+I/dh3E842rApu5aLrFn8nPjyz9LRcpBwPHPIjOibGeNMlLDW3VeEPNo4+
-/e/TU9O1fJJxioqKyytSnOs2ACwzVMH2EobfkhaSBe9VhmX2SB8TFErGc2JhQteC
-G6ueXCVqGPIcFsD1IQvUVFgxkS2IMld8vEXGZTK2jLWjJ+WH81Thij6MEoqGmtjz
-Siddr1uKNsxKp7XOioIG8r4ZEVDPvTiUiSp7dbQqVEXtI4NOIKheIqtURJ21t4Ww
-vMrIpJT1aZBrMhCIdn2xTl5NZyD7mfKnZfbdCsQxo501D6R4Flq3il0fPxsCPy6G
-T04rpaMFlE0VY4B35bGwikKy+tHIqouYFtyp+kHbDDW8nDE3tChQaGlsIFBlbm5v
-Y2sgPHBoaWwucGVubm9ja0BzcG9kaHVpcy5vcmc+iQJbBBMBAgBFAhsDBAsJCAcF
-FQoJCAsEFgMCAQIeAQIXgCIYaGtwOi8vaGEucG9vbC5za3Mta2V5c2VydmVycy5u
-ZXQvBQJSZgrxAhkBAAoJEE0ekA4UwcwEWhgP/1JmfyfHoIsCJEBXhSKb2YxcEuzu
-z6R/KhBvqyCFByjjmqh5P7SWsoTRUN1ntetQVRUGe8fK1vPcmnTjI5UVwYchNwVR
-Pr7WS66zD0Vie2UQROQB+XE3V0jgewojoSkw+fEXkLJi3q1AbHnFg0AtlxhfMl8P
-KXYzjgJJ/ZwHh+cAiRMNjy9MOK/bQlyDY6iTG9DUP0/Zny7FAq6+oyiuP1TT163L
-knFbVaEH/UdhbewQLs5GXufJ0R8TGP3VaSCiSk33kqOe4qvwFxkDN+7ioXR2A60y
-RAZNOsDd4KOxdwhUm8mNIWHne6WjFxGznrPv/VKRxUwwDV0clf7DZYvPJ0xCFLTx
-xC/9x1oKwpDB6fmqkA7DJ1GHJuKXM4O7EjVQ3SJPacU01tr2qC2BodYJG6PvzE2+
-FzGndtwQfb+eBYrEQ12Apd6rADrFnbAyd+FH6uwRxWCPweMCyUZpCF9ZQhjd20O1
-fDOSUhaHQUDa7NLcZA3Pzka0S4Rjkj4NPJd7r3ckXIwSgp3vwPBe9yUt/PZ09WbI
-YGYFy1z9kml2uycdsaY4WMQiA0unkpbkQN/WaraZltNTrfs5a47b/LWYeBe97n8P
-dczXAC3jSrj/wOJNb4as+bUVJ8U34BeUlJo0UCJPBINdRcKSiakjfGa8WAYEgbZl
-P9rRR5hZQvUaS4zytCxQaGlsIFBlbm5vY2sgPHBoaWwucGVubm9ja0BncnVtcHkt
-dHJvbGwub3JnPokCWAQTAQIAQgUCUmYKUAIbAwQLCQgHBRUKCQgLBBYDAgECHgEC
-F4AiGGhrcDovL2hhLnBvb2wuc2tzLWtleXNlcnZlcnMubmV0LwAKCRBNHpAOFMHM
-BFksD/4k7P55N/ZHdHuMU59DfQSvk4r6DNrGzZNvjiwpDa9GUdvFw2vXhFsxASFI
-A4i7fmkxVUzfy508+hkP3rZivqltnaie0HRSDhilruiJF8mwSWvJ1yGvmouJvT82
-lUyUqtw79lnEADw3NypRXIRP+oz3N3jZ0s3Wmil+Lj5A2tn7QLIqTcLLtX2YmmSt
-fjc8Kk+tt6gaT+r8pov2JDjU/gG5xtKG0LfPbO12y7+qY7dJFd4gNaXAub1O0qrt
-IWsUyNqxvG98DHD0ub/+NqQdzrzhBfW7QG8hzrSafkc5qvxBR2PwJW2F5RPRwURj
-+JkT1GPHZWFlUK8t6EG9w6kzL7i1xOYkxTjYK+1VFXXMQQRIy6d5a3+7ac2OtS3X
-qvhEBH8XBLdHi/0i3GQ8EAkNC3nB+p8RUrbJQbq7mzeZ5FuHOUbf2Uo9I8FCm0aK
-trVYFiLYh5joYYlXoE2Yo8rB9uMtttyvCcdIm+ewZCIQCF8MuA8PshXaOVwq/k60
-JIIJlo+r9vX0Zgq4hEQUHA3hYkxoXWGAn0TMVZ9TekZSIdhxAIo4VsJzll2Bc51L
-IgH3zJ0FxFBTcGdKU6mDUVhrIiDe29PPQkla3wCbuH9l7W0dgebTqVZX92hbQYgm
-E3h0UcX+vnCFFPm5qpdYs4puNrSgJF8Cn9LDUGFPvUN696wmE7QkUGhpbCBQZW5u
-b2NrIDxwaGlsQHBlbm5vY2stdGVjaC5jb20+iQJYBBMBCABCBQJXqC8TAhsDBAsJ
-CAcFFQoJCAsEFgMCAQIeAQIXgCIYaGtwOi8vaGEucG9vbC5za3Mta2V5c2VydmVy
-cy5uZXQvAAoJEE0ekA4UwcwE6b4P/jUOwdtIiNmAwYNWRJvlGoq7/l+gu8CIo18e
-i35j/r6LFFuwC0+vgEowZHCqLGIBpK6yliIX1S2voguGCpoxkalPdNEb2mcBODNz
-FUVscRqzjPMOD5VY7pipP9JFJJR1FNLKCdy2OCD+lTpQkmaBmKXaGanhJ/wkDZep
-TURn75WhgpDzzdISR9tPygZvKWeE8/Ov+RzL0caOcoR4yuI+dbld1bwz0hem4rBe
-XiT8+ZSW5F8OE6MirBMyHU3fHQjHvQ3Iy/UUsMyPxE0iIMmirkKwB//U6vCJTRf/
-2M2/k9h2DZYhsMpDkiFSI1q7jo9/zrEQEeQCX20atAPZJeaO+OLQPdBy8sghA2HD
-8UY+wZ+bfWTfQpUHvWVuPmfLUtFzulcBvE3rwJpNsgu499XZg9GJ2O8ulhOgJRHH
-z/ddaNYvSQXQvJt3woF6ElkJI9kF9MKdvlt3Rm4Lp9dfb7wmpnv5isYBte4MlVJp
-nHCg+ABZJdzm86HP6/LdfWNpZnCX5IHFgtZwdT30aUM55fVMpqCDyfM3zcnBPE5L
-pLabNyWoSYXllDy9J4FkuRkGr55bTijNK6WU47EF5U2+5BeLVbizuJ20DHcwEl5f
-p5IyZBdBmSrlltwtX6Qp1qRfaIeyiTHzx2Bz8LzONEL29swcsQxuRyf4Xovt6EIz
-4VNGqRc3uQINBFJmBYoBEAC6C/l0gjpwGcO+6BV0YP/eYSF8XxQ7BcEj+ooSs18j
-Zeg+9ih1yJyMWqrzXrREpPoIvxSTXgYN9cvc1hXSu0OxqCLjJ9R+wfIpUJyFoaQw
-AvuFfrnbwqUDoa/bSFXoUFxv/M9d9o8brO3ilgBouys3QTDTZuVttK6GQUZDYcgt
-gQsaKGQKvylwqmoldProvcetvNG2nTAnXYtNetF2r58jn/cVXS8t2Wn0wTs+b3WV
-kgnChnODJT9qaaoCFHhygNH6ERp+XlaqW81sNTkjyd+Wq+vXMvFzyk7i6ezplnYG
-vhEE1hPxYRDZEc9dEROgI95k0RzYXQvSahqoCyMS3DybqEPJJh2mxJim6UYHD5gc
-cVhTb7j3WfWoyMRZeEzb5bSesnkzrb4kRz6ZYvYF0EyvcWC7mSOtnIkQDjO/FfMo
-fRgtolBchHc1AOjBGVjRn39YhCDijo5cB5z+nhyK5BNSOQAtyrGOU+8mNSVDw4TW
-WjH2ZDJRnbE4NwSpDzVRbBEEPGILjIoPaPQ11IObjYHY8WQ+dxb+9e45WGjv2KlD
-S3UF3ABeLkjSYyPTTuH83gykU12gr60MrUQExdbG46mGjfTs/GOgzlItkEuQc4xZ
-kjk53jl1s1RjjFo+LxLpAYu1D0KpiclhNqWPbp6I9amEF5AeIWgDDOI46yC2Rtkb
-0wARAQABiQIfBBgBAgAJBQJSZgWKAhsMAAoJEE0ekA4UwcwEfrgP/1E7HYaMcyDT
-RbEy8GJt+grN+m07wLO4bnES2VzVN9X1ymWP407upZt7vy8LUN7d7AEXCMJ8KffH
-IhTN+tMbx/+xMqNhSVG5AYTlPfdaumL8jR7WvZXh6nRXZNbeGqofH36zlAbV1NiT
-SWBMxQZ6MbkW3z6QXvad/MTQFlcFouGlFHmvGdtSIBdg0e25Y+mrwXnyN1OgLJLg
-L1CzmSae944LSA8fi1EA/R+vwgJNkQPTWbuiFNKvH/UwOUXJ+JxKG/CamPT3Lgzw
-VoW6bKqDPsgWz2gSGBmN1Umb86n+xV7fu39BfWaEfpoY5g2dq+CLFYgxzymKOxj8
-oIBfy/2VZuX2Aj8Gzh8q/Q2b0iqlrLzfXViHLD7LTzHn0G/xOks2qkwvm90wM32m
-2qkniAGimeYD0MFpbL9cD0fRAhLkMsF4t1EUTIzSdZKouKF7DMI9eJe9RbqCcOiw
-6V9h456hwqFd7Z5fi3/SbHNS8weP004DUcVhSwNsAbMDCxSDlv/QNOmGc2QDRGiP
-QrWIhq1fVj1YfWq6dfkOvwI1qvgg8b9GybIasL5YuC0xW/GHPwo54xiFcGBoWkjh
-QwxzJFDCAlO80ugGRpgEqPis6Q7gAWYjQxHuvEtgCtUcWOmlIZDyxDbcvlP2VPt6
-mUjOkYtwOLN6xiYi7OGBxwcJU02OmG+NiQIlBBgBAgAPAhsMBQJXwTGaBQkF0dMQ
-AAoJEE0ekA4UwcwEg9AP/2QqpW7xqcuU4RQzCEJQhg+iiSx1AhFZyP/+rMMuPDvk
-CGXtEepUz67AcdWEHLKXOnQJjiFJ70jgBNtD1A+EU+kUMuWZt8QjFXyx5g0ua+nz
-oTXGjCx95uDeVz8UmmjtEf3WqwgdOeB5ezcLCbNpcotYHj7lx3eHnIbC+Tv/GQf6
-YFS+OWS1SmNkJ8XlYoDSQwx3rjcyx5Oa07fS3a7+nsYCyHjepVRt7BPI+567+bEp
-FIcmx+BEYp3XSHUsXzp31o2aVgndLaJdbi79TN8cL+v7QwKTGdrE0PQx4moT3ww8
-jR4gAPB+xKYHg3ArE5LeRTxvq+UAj8CrC35gtaiLSnAoYtfqS3hsqtGfRm8SigPy
-5qObH+9VrHW7f3EZFgPXHGJHig1xl2egq6AbJquG1Hg6+5AmaPIBlea7nDspfPoX
-+c83Vagc/70WN0EKn6dKx+EJ43XQsEqJhUzNE1mgOEwPWz39/I/Emu2ROVD5W0nb
-ZJq/TiIQI/cmKGmxkyjk5iF0y2se58tqMclXLyUfdmZyfLeDT7tcl3FoGghosiby
-Nze5rGPjl7qpEOAqK14HxpTCTtenrmPhebYTAL4qOByQB6DdbZOST8MRPBq84Vo8
-c1O8Rq74o2KIbAaotUHe0XlxFY7d4zwiWiWxIoOfbeD0JaQcYK/TBwBx8btmDFhm
-uDMEV8EwcRYJKwYBBAHaRw8BAQdAJf5CtBXUXVqiGpt1xQ4NlzBtqamtSgshdXad
-LIuJLHaJAn8EGAEIAAkFAlfBMHECGwIAagkQTR6QDhTBzARfIAQZFggABgUCV8Ew
-cQAKCRBREE5mjdBEgfmhAQCkv6N3THjDjvp6VDcXQzTTY1d3sUqi7L1qB4Ez6gdL
-iQD/bygVdVyoOtrP1/lsWfBfbjTsIMsprPUyneOKO9Gnogep/RAAkZflNkOvoJ0R
-Fjlw5MKzvTLTaraxU43p3GJwT5QDE4vWeql5/YWI6hu7h744AZhmeCbyg1AE01cD
-oRNz5SD7NRU/mnczCSkUALnYZYX3Ko6M5pm5DHVBmhbD9aFtraLH6tlJKLXM9rGs
-vyJCl7Tgy3cgXCYuXFiFPZn24MX+Wi1E5Nbk8hxaa3bIdht0vRdisan3n0OYo0aW
-muBMFtZN67BpBTD9I6Tw6Lzeq/7xh1k3K5rEvPeqHRVLHH29CcYxuyUOmLb6Fc65
-Mm4xWztS0+2wWBk85AhZ00Lf2i2WkdATrPx7NGWw5fssV/7UIU+Q+NuquzQPh84S
-v7KKWjQOP3mLGcJ7WU4PKR4STBAThd2WsgaMs52LTtD+IwtMZAMvTc9Ws1e3VqTy
-lMkjtJlGxC6Uvf5OpvnYfKcENu6LBLKOp0IYBn+hEKFatbq12Dduiz1iKK8+AizA
-J+vLS1zdYanbKAJtYW+AdmbFTyfC6ytONyIiHpvXHAb/B5vH+UE8yIrJEL4XXAup
-0kEOjts26jcbPxbvfe8FHD4NZIM8F5tbuST+TckfSNfUwJ2/7M7nC66vwN2oMsuV
-faZk5NFlUlwaDiNDgQnb1qQm/ltLBrVsVFc3Qra41IZu18etxsOaDRpTmyW/i+2z
-F5QWE4RzEccLAJ+BPLdIQX5xKVZYhsy4OARXwTClEgorBgEEAZdVAQUBAQdAt3Vx
-YCdOrV+5P3o39foPJbUE97JkCZsH/SLX7d4WK3UDAQgHiQIlBBgBCAAPBQJXwTCl
-AhsMBQkFo5qAAAoJEE0ekA4UwcwE5q4QAN/5x/N/8gldfGwarLCLrtHywZy0JMwJ
-ZcZjT0z5mBBTwNsP1Ib2k9tGqAeqR92IYuAEJI7UYNJ8aEMbDDbfOtuhecQupfXH
-yAahLrKSaCXj49m/nwBQGDESDSbaOU/j9YSwwrG2vFZESwhTUdhJha+Uple3vtj3
-H7JH+CvdCucjOTWSpdl0nf/64wPHbos+SfeS862UjLJnS6kq4GA+T8Wyh5ttYzho
-bdNZSRh1aU5clQNFLhe+O8GWTY81AI/t3wT0WLsavhUa3CqVPJM6vzHBT46weyim
-P0qoHRpo1sfJ2A4/YGc/+r8cwDimHpG9mIr2G3nx1Z8FhXxjQN4k1QFpMJ8LyHrO
-LS1oIpmNmwWzVDXuRQoerXXqOO61qEaorQi0buR6Y3uT0+DhnGmbXYvy07IaLlyk
-fAE6/CCUkIo5BNBBm0spChAud3Hhr95uLmey0JvEGXd4kjU+6QCnY0pI//2Px3Bm
-2V9d1o1+31dr8cbh5RkhbT9qrg+5QIOeWG3SsOQqKhOaK1l5VpdlpSMoE1OJxUOT
-TsA5RcDWlbSC6hQR+8AnUpGnB9eZDtTsAVzzcJfiphoQhCb0tyjgyeioSSPyA2SW
-/Xe8lk9swoG5eK2PI5rKQ+Av/f7vZgG3qMX9F7Ywl/Cewje6cXFaeMbOWzkrNGRN
-6Y7KlqQSpY4luQINBFfBMcwBEAC6AUNasY9Ibw9B064L2U4uflZq3N41ZUKEcrhA
-JZbDhPlKYqLPN0xwJrbUGFCkkTZF/5jsy/2YKir1ywjNPuvrIgqRwuyouTeJOLQX
-dAlbZHajVR8ljbQehdVxMJ2nPYHyuwRAQTjtYceMC2DFe/YrdWKa9p2x7z9hD7sx
-g3HrzXXtAj0Cp/F5fokQg5xnqGqUyEjV7AHajljsap0bOZpJLkXDhDYsgMtObWgt
-yZHKfmpKv3XdFgHUpxu+XX8hw6Q9FIS28DEOVRFCzGsY6tqRUMfBSnUvj+x3pF/g
-XNMH1HJF7u4nQ5nulhlyyDupXQ5BNIF0o1bEKMOLHc0TZ7wgJnPvZXFB27dE/U5j
-W839cu0e5eVGrJz21AwnwIUSDkVG+qIGjRIVys4ce65kgjCOJkL7yRJD5YCWW7hj
-T3/JU3l3lAQVwS6bR4Qi6Qsl0eKmhsjqkT7N3KDXbCSqmAp9b994bxYOeNyotFtQ
-APCmUVFykQYDMJTUdm0iZX40q8p7T+OhkWtmuL5xGpP9KU6IOWHRxgcRN7cVNOoo
-igE9mqwiTl236kxY7NQLilF5dzNbExtFSKf2h2aXyU+CpYE2aa6FTetMUxZnI2+o
-B1hGmVGoGBzdlhdXjrn9uZdiLOumSaWZ1mt/EMNJVT6aPIPSASkXx1Se7g8pCCW1
-K2OtgQARAQABiQIlBBgBCAAPBQJXwTHMAhsMBQkFo5qAAAoJEE0ekA4UwcwEBqUP
-/jLOHwYJeaJsgsi5I2v4HPUR0kZZNrAeraW66O+zkHyPehiU2dc4KeuWAP1YZ04c
-jUyusCN9QLSx4zbaZHPJjqStLQqsanYZ9qdeAVLgxp3A3ZFAt/19DlSMIf0cWmg2
-VY9md4Ex0rTfv50cGnaB8CLpEaKwwJJVkSG3YfqgBb/1rjWj7KubM4k3QNQ2UwG4
-ABs+mZ0f7VYpd9hQkRza8IcOQ/xpGIoNlweWqOrMm9Xk1XN3LQ2SUG5pPs56FHGW
-/Q4jN0zqCGVPv68T+ij50w//JOfrL98x+QOkTYHhFBUDsqmysBO7deBeQ772Rr3w
-qLVqmVyVovhSWn+r6QLX+OD80o2I7bJKAgxK4A2blBcjIA6zK7rKlh5zitACRs3B
-vmGYenCxF55n5BTY4osVE/8iG6c3i7NLZFRtsYwaA9TCYZxOUIW8cfZi2PyfMw0A
-mndldZ5SauaACfEdcIe7LE4Qu6vUz1qEr0DCG1+VetK0NKePSXMg/VV3RTi4mUOX
-mv/pSrJxDSQMPA3y6QdXqUtQl25nM6KzgcOpGah8UOdyDZccK6B3zlq1ngKE9U19
-e/FgZamUn49hqKNu6QhQ+2pgiehgRJ1Nmo6iyf+0O12kwTHWhXsb7ZbfA86OHlPu
-EjFDDW4kXkOSE3SRTP9GY5w7HVY1qUWy+2/FeYyW4syGuQINBFfD2hsBEACqLMDp
-uA+/9VWscimKTs7+k0BiuxfPwNJAYYznAVNFt+GE464v6YJNXpKt07BRzDpuivaD
-PobqtFXc2nvBHcCUOP6QTUP89rOC/bw039B+KRaPlQJTGbPKL/kqIXiK5ihjgSXd
-HDCmzNFHuec07pWgBMI+LYfZpKIHGsFVynIL53mmhxavGTCSzJrBd6pyhoeCzMsI
-ZAq6pZ0HKjfVWP7B3yBJfazCr2V/HkOmKV/vPJT+oflE4f+PP5tTuvEWE5UXM8VX
-nROMcxaNHLB43Pbh3A5neGgFm74Ha0tfWZHrZYnNCFRGbxp7PnfbKL+tZ8xtyQr1
-pQ+x1y8Bkxj1MgiOj55MmRmjxlVJ+L6zyB5Tw7kqsaBHiSDBWUz6SJz3pFD3X3GP
-D/nkNqhBhSzFM2qxHME3CkK+hU4jOEkcZpHhsjL+pXVudGNHIByDNj9lqP7vswg7
-cnGN7QIPdpBdvgcFg4qZS93LsLJlqhNDtCwd/Ut+QNT6xE51HflZ+3/su9FEjUFK
-ZMEtAu0TDoaf7iV9VyD84wjLWAm1GVXpDh1/WuSUBifMfTyHXyLN2y2Ja5D1mws1
-g2ywzHBW/2e3gUzYSd4JQEWLYld0kZhQ5V/Y9Y19jDpDUUgxkZmb5dnHRaGwmyx2
-7zReKqN5NF2tdeWsUMibZkEQdib0n+WnzuJMYwARAQABiQQ+BBgBCAAJBQJXw9ob
-AhsCAikJEE0ekA4UwcwEwV0gBBkBCAAGBQJXw9obAAoJEBPa2Zx+QVGcxvwP/2aI
-UD60sKExN2fLXj7mMZ/wWlDnCdqvTGD7lrk6r/fAQcaOAgajCMEXOPZXlPBhdQ4j
-xD3FLs52CNZkcwzXMbspz1lfIOk2U1UGhmnAyriY4Uf5cRu2RPR0HYwOBB0xr69S
-IrsmlX4pf1AnulE7CIY/oPBjB2XQRQ7ls8sMqmm+0TxRysaosHGu7Vbez5iKBm3p
-0rEh8TcVkgMivdUPue/ip+mCaDCfGeAiXLXWtiEiwaS3Pq+QzHhZtBvShWlc3k2m
-CFlrGQwovPxY5SqGs6QwifrmnGSSlyaAorDZcQEkZe/HP2/qXKb7uBD3/r8t2OE+
-BZKwJxW2fIpaO+u8k5EXSDzuxRqSNj3wYUI2+WNQzBmAyOZ6XBX4Pz0xZyahtXCz
-J+5deqCnEtJI1HdPSvM7STE6s6BmkhUl8weSAD+7v/HNPWvQXYFoeGFeqvoVOCqB
-7jJZUj+n/eUh9PxsOtwdJlvdoODuQIYyzuSapm6OPnBKg+v7Bp39Ym8j5Nfe3xqg
-+O6CQVH/qx3NoFrKfAaLKGsV++jnf894b23Y/fgu84Myt+Kn8uOrO6jbBwiWLkgn
-0uzmO57bi/6F7aMQwSxcMcAY3DhCoeXkeYq0QRZZd2raPbA5r278wPXWg/U5bHen
-GYX1COWlRehWqXkqR9ZJYY1hTT0/WSAK2ZLCGTK5tDYP/iiHbpeWlZhwgx9Jkfmg
-L+N5XoAW6oJna3tozS+xVM5pxTaTNO24vnQw+XQxkiCFwtf81chd/oXhjWpLg/K1
-vF0AWGomN9yS5dtKtlWZ0H/3KeEGkKf9iRp8j1bVNF6mBhb8Xl+nKLWiqE/uezx6
-OYBFJuj6WpCgbmaRUbmKpX7P++JuOosg0n+BzzJYAIKP4+/FLL35qSpLW+DuWZaX
-bvgS/OgjJUL8AQj8Nwk7ViRyhBRwSAvwpdcwvlAH1VfTHfpQ8a0jjN1Nzf8Tr9Ij
-o8NQnsa+5y6Pmf6l40j4C8HPsMB7SX8ptFig8lnBRPtzEWj54/WtXJwGRG10XW4r
-dQU5hR9Tufc+WFuRfwdLgrhPTnKGyVG9zOkTd9Cl4j58tEsju+m4HNkUN5goouvd
-xHSe/dmA6cQAWf6/nhJ/uSM3aJPSUOtZwPZO7/NzsMgkwZTLXbehm+9xWMkPRt1Q
-T7V5MgfxnxhVoIeoPAEYBo8t0P2GXVMNZdZkJPoViWGOei4iPE3rj6NBynIIoEZN
-DEJ0OQOUe6Naq5AaG/a6wPa9+ITzKY8VR5KMf3XgcKLBlntyyxTgnHY7j5VrhxU3
-+mUrnwg8LIN9Sx4oWDks/SEB7KN3KGjSgczn1k3GIJRF8BhYin5Cuw/+aD16w5gS
-HxUhIgwH2BbM5X8eopbp/csAmQENBFWABsQBCADTFfb9EHGGiDel/iFzU0ag1Ruo
-HfL/09z1y7iQlLynOAQTRRNwCWezmqpDp6zDFOf1Ldp0EdEQtUXva5g2lm3o56o+
-mnXrEQr11uZIcsfGIck7yV/y/17I7ApgXMPg/mcjifOTM9C7+Ptghf3jUhj4ErYM
-FQLelBGEZZifnnAoHLOEAH70DENCI08PfYRRG6lZDB09nPW7vVG8RbRUWjQyxQUW
-wXuq4gQohSFDqF4NE8zDHE/DgPJ/yFy+wFr2ab90DsE7vOYb42y95keKtTBp98/Y
-7/2xbzi8EYrXC+291dwZELMHnYLF5sO/fDcrDdwrde2cbZ+wtpJwtSYPNvVxABEB
-AAG0HkplcmVteSBIYXJyaXMgPGpnaEByZWRoYXQuY29tPokBOAQTAQIAIgUCVYAW
-BgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQvOWMjOQfMt/0Bwf6Ah3U
-WuUL1L2wChjHXktv0j8oQmL8CD1AUYkg+4NRTkZTm5ngZlNk4ZSJB7sonaEmzs30
-fw9zex8LMtCMnEHQYtFNb6r1M2QfMS8ZUdeaUNmlGHu8UnHqr+aTkQbQsvhs/UaL
-knWlOWqdsM29Z311yGA3BdlGxw/2wej+AtRSazT4dEISP8K8xfnoQmhIVUZ33aMV
-DF70iinmAfWfqUKhgRctrVMLgXxKtYiOeTGXDtm2dnvXTHOO3u0N2skwc6YwOxLj
-1XXwWL6KQJx77/2SqVHDJVeEkMEb9Wr/e/l1PggU+fYxLZ5/HWbGatNmoRFoYNuC
-GlpBL9XOuQK98PcIyrQmSmVyZW15IEhhcnJpcyAobm9uZSkgPGpnaEB3aXptYWls
-Lm9yZz6JATsEEwECACUCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheABQJVgBgE
-AhkBAAoJELzljIzkHzLfiIUH/3CjMhhGiA11jVp6MUWLFvr77LhvWuLKMy9YRQt4
-TMOVDSUxPpnc/FeFkgsPjLho/srPHSjNdrmporLjUQA8pCg+KmdEAfThDK0lsgRG
-/PxOi38t4JUpRzQb0NXE48EPTdzNOCqDPgSNXaq+csX6tNTRgF6+s0KW4qiwZJ37
-dG8tZW7SEGGf2kQsp9ck1JBdlv5OkcOFINn+AKuCUEQ6EDphZsNv/iDP7lMUp2T4
-H76IBBlIe/vMhJpuM34E9iAjjsD4xTgnJyhdoBScxzSXltrp8Y1oivOu4ThoBmuU
-/mj7uaVT0ybRAmp2pjFg8CKmUkatm/5hcfV+nm54QreX40a5AQ0EVYAGxAEIAOmE
-sdopOhG5H8TtMd6sGIKMNq3AJoRM4o5NjbNEFClpDfan8XZcgYtLwJzbv6CtlIpD
-plfRk3js74AXIUcXwMf3QhdkWklHdFvzOBdPyOctfTwMzfV4QJkedHMWEaU6arpY
-BSWoHcYoI9QJjZzh5NFfKhcu15PGtcJiiPjnL9ia+VmuWicE2M8EDIeI78s3P5Xt
-9m02w3s39caucttx018135IPUQ2ZssnxG/LKbGC5PIH+Rr0l2MccihAQnovXroHe
-GF8Iem3yILQY9mS2L0gyXQ2gnTb2MmbcmrWoRx4QGfkflAwafoWrriJfBOw7VMw1
-TClbHymO9XvBUjGMjxkAEQEAAYkBHwQYAQIACQUCVYAGxAIbDAAKCRC85YyM5B8y
-303oB/wJLYJOsxAV2GQYS0FeYviJ8PxQcWQFEEaYzxkvZ9ZQFNldPyat1Ew4rq1w
-+cpZoK9a8qvSSe33vSP8PICAWYfyGA6LfJy2KAV5xUOOOKUB4IkyrfyzW1gpiIsN
-sF0da12QD24dnCreV93dDFwQQ7dBqZAX507uHyAA5eUb6mjzseb4TTDPizAgHz5L
-fsnOvH267QtIUN8kJMr5MgoZrlSfwvE/HKr1aec0OHvbMrsGJGJ2T+zjQpw2h3zc
-0zgef+xsZ/ItryxLQXcwTRL6hxIw6K79kcc6LCktg1vBMnuy1nEayuC5Z5P0/5qb
-FsD9iUr3kt52y3C835Zwdnt374CumQGiBDzS0/URBACREmlUnPeSzfnC0m2oQV4e
-SzgYjskiLfwZ++Ql3zErPw0AphH7m95dZwAscTm3CQRHDDd/RYxkJMAYA+jmw8cV
-X1rXtQ2URRmzy2/I+qBU1NCPrqBjKRqrav9uhLCLGvEwdqWg2dqn8TMwNdlETbH+
-R0QQ/1lK8XtW0NiHC8I+NwCgj/8Av8ifdpVSnFp1QesTAVwdTbMD/icRYOZ5I94D
-SRk5GGnmD+lyhfj+ejYbuVEgg2igV9HuXJMnBKTnuwriuskTreeNQBvBCTltHrRe
-1LujAtlsbixooTgUU5jkzY+J/PeNfLd1J9uoqTGQ7GjT4SMfKuetSRBhcRZYvm9F
-M+54vsumKcXGK+qBfPVBHo1bk8goJxgBA/9tnrAoLIUPvs4d4ce9h5BGA2yG9Syn
-z3w1l8Zr+4coomUjbJFV86ZWKPM6nyb2RhDb20ESkZnCoDxZY+p5t9c3aiQJKQQV
-8Gj0tj3c7/OKoyMePgabH9752Q6upiZ5Ml3mfse/Kja4THRoPEjkQzAn77jxfves
-KiEh+fu6gsJ3cLQZVG9ueSBGaW5jaCA8ZG90QGRvdGF0LmF0PohiBBMRAgAaBQsH
-CgMEAxUDAgMWAgECF4ACGQEFAjzS3ywAEgkQ/8DxTITHG24HZUdQRwABASeAAJ99
-oc3W8UA0Peqdc5cX4Lbis7hI5QCgg7U7yZqSbW1bRDP8kufk/86S5g+0GlRvbnkg
-RmluY2ggPGZhbmZAZXhpbS5vcmc+iGAEExECACAFAkRka8wCGwMGCwkIBwMCBBUC
-CAMEFgIDAQIeAQIXgAAKCRD/wPFMhMcbblBiAJ9ggPC4h2/eyMlfUlypfFzLqQki
-LwCfd83Ub3FN2C01OLRovTWsmXWBaWC0HFRvbnkgRmluY2ggPGZhbmYyQGNhbS5h
-Yy51az6IZAQTEQIAHAUCPRc64wIbAwQLBwMCAxUCAwMWAgECHgECF4AAEgkQ/8Dx
-TITHG24HZUdQRwABAbmqAJ48Zhf7b9JQWWEiVO0m35yrUG4/7gCfc5OE/gBTg9P/
-1C/5UFC6wzPXtdy0HFRvbnkgRmluY2ggPGZhbmZAYXBhY2hlLm9yZz6IXwQTEQIA
-FwUCPNLYtgULBwoDBAMVAwIDFgIBAheAABIJEP/A8UyExxtuB2VHUEcAAQHATwCf
-QaJHzDZcMzhOrYjhobphXayiTboAnifEwKJ1DDVZxPxxWvxNoTvaPwm2tB1Ub255
-IEZpbmNoIDxmYW5mQEZyZWVCU0Qub3JnPohfBBMRAgAXBQI80tiTBQsHCgMEAxUD
-AgMWAgECF4AAEgkQ/8DxTITHG24HZUdQRwABAfCfAJ4santm5g2yaXD29CKE/OJ5
-4Sd5LwCfbDiwEI1mLyu0nScjBddGF9AiHx65Ag0EPNLUFRAIAJtkhGBrUaEVP2fO
-4wQpmujYfPc7+GT+Q0naKCXrMQ1vDK5ppsghiSr9TdVB3kdkev2oGxgsCfy2uPC/
-JuewQByYBmtKJuU6GDaRVXgMhpVwhcRraaDeYZm0GIDQEX3fWSlL07xxbzSZnewl
-SqUEAznHjLGN1pq9mvPBczq2hrAsd9TPHo/IB9JsVmHV9GYasHUSbVWx1S6ntU2k
-V2TyKpBS4luF1Z7y6yIWS9pwiZjTlWdUGSfUkkTu6sM59dBAxv9S5Q8TY44TUQfh
-HQhcLTz84UurU96i6cb99ZmN5uq6IP6NPIumhOJAqPvHSqly+Ez/oSzSyUoyZ0Sa
-j35E1C8AAwUH/0tkQh1bn/BhIyBO4S9z5wQfI+ZpR7npeKZ1aYQUjFzbULb27Y20
-HRujvXljFPoWB1oJO+oXULkCaNWI+72TYXzKRDqYWMaubwrYe5dHJ4hEDpmpqeG7
-W425rItDfhz2wKORc9vk+eHMHGZZhKamurmeH7hrVpe33BRfts5yvYWofYonWGF+
-KydBcrMp3AMbKGQMSOwcBiSpIJVn0HYJFIOWmthtKIMqfVmLWS2sqFKITbBKHBem
-P+97FVAc82dXxj6irB7/jBjdPX5/5B8HHOXWeEvuHSjZ+6efXFrTVbeh2u1alB0a
-X5kz4cb8Fl9Oziqc2Lx5HLgfkKiWgDAu4YOITgQYEQIABgUCPNLUFQASCRD/wPFM
-hMcbbgdlR1BHAAEBh+4AniTeOAdNc4fOd+lc1EMiNmo8+MkQAJ9cCqXvdHcqeQ6p
-c1DsXNhc4g8rvpkCDQROjXEBARAAzeS7Rq/35b643de5gjparUQdurY+huIwHOVV
-EWG3o0Bm22Mz+S/nwi3w6NNTGCyOo335JX6XA0R4dq/wArwPjQU01az/l1/PrPPm
-OPSnv9/a7eDVFgv7fVGiJFftID9wz2EANhrHjhsGhfFe79wV6ula8KMldipQ+LwG
-FGoSedlcbGRvvyIa72Z9jI5gMm9X482WK/+xl+evAinUWOVWlRaiyl3Qu2c0WTm4
-M0fN82mt3KAu5d3BUbZhkZrbQ4FCfEdzqqdl/aHvnspc6Zp3RGZMxj2YiPdFZmXI
-b7dV1Cf1UaUcD8Zib68/jSVlZLcw1NZKGrsjposgdnDuvkXEjGqECF/k6cqiWfeq
-3eirBwsk6HRd/d8bO99FduKUSV0m6iacgTUzo3dk/OejCPQiENEkb01CRrKeMfNo
-/t6yb0ihkwpT8BTiZCdCmkMjzCGrnT9D3bKlC0qB14gZN5Pso+rYPQmvOE67Eqy8
-dX7zOLAGaaqOaS64g25e44urVGaL6ltOjEU+6xQjIyVtAZPIz6dq/+QEnY799y48
-b6/vcHmByef6zSfTFFcN615sg21Ie/rgJv9ntuM9usROi7MSQfCc3UakUjKl3X/C
-bIrkC1qSmQcGKISw/hCivm36ar0wBx9/Vyz8/h8dT8oN/p5HECSB7GToh+bp3kMn
-+aCHdDEAEQEAAbQgRGF2aWQgV29vZGhvdXNlIDxkd213MkBleGltLm9yZz6JAjgE
-EwECACICGwMCHgECF4AFAk7PxLgGCwkIBwMCBhUIAgkKCwQWAgMBAAoJEGN2LNpn
-4vNZhm4QALEBYT7YFCeywswA3PH88h951uia3Cc5Gn4XBKbQxQQ4QRWHkrRhmINR
-qc7SMBUfxUtYnT+T2/Ei07OtRzKX1AjKN74mF+p7s8i7JCM2t7Kc+/xSIZIhpwgb
-f4OOjtUQ3RJoYjlL+ke8YomX6geMZV/IXN2nqj4a8CYkmzXCi2dg7uWf8v/p/hyk
-/DLYlD+HwxpRG6ANUkQ6zxTxgnzwihrnhaNsu2PAnWJo9G/Tfk8o5JuTRBn5qGr7
-SyQ0PUG5s8D2IPgMaABHhpoT9mYvVOundroC2RyusS9xzrTJC+BEvLZ+J3idAvT7
-/TfjJuOrPpkr2BUIZYr4MF+acG0QQUstsJdp7V27iINNN0jmlybbCl7RiIO8nCSf
-VRssgKbfJMnThvMGjYSSFPUz25gIgH95t8a/2rGR5nnBJQYbd+1Toj0vqc4PIuSA
-Lk8bF/fr0s1DwKUJGgbiUYA4moIY165he7/RVGVwm5qM49YgSaJWwintDCGox7kD
-JMBfOz1n0FVi5LLGCHmWosLt/CRpb+F+r0ix2g4d5kIU/JedT1kU8dOugLVb5bLu
-isK28h5J06k48VfTkzkSjOb8Nn4w7q78RUZ2zx8Ny5Y5+BFEKtmu7Bs9Pzs6698D
-HSaeZzqIuSTgn8ddu8iBjHZF/sw7wrZO1z2cKj6FW6bMen/bX+HbtCJEYXZpZCBX
-b29kaG91c2UgPGRhdmlkQHdvb2Rob3Uuc2U+iQI4BBMBAgAiAhsDAh4BAheABQJO
-z8S4BgsJCAcDAgYVCAIJCgsEFgIDAQAKCRBjdizaZ+LzWbR7D/4hKUfh04TLD2ZF
-sIWxrgEE/661lHaYZNi/rJAkhX73+bpPP5aVuWiqvFkYbcIvA4+PzSi8KXuKiLSb
-xtUDgqBKPWI9Zh2cOj2Ykl/+Qqp+TAPnjTde5+lc++MUm7K0QU2CJQZwvRwnLtwM
-vqsj7dlF37N46oSOqcPb6JRsDmJmoJUn1ylZhjys0qAw9A+3VVxXIIacsf7Oxr+5
-VDMTJmyclfGwbsAAEyYYEgopQ2R8Z+bEOVTdDYSC051oO0KUHidbRGU8/un7yM8R
-FtZSoPp88O4wdWyr9xbahSr4LYImoNUGpJLQQKf+EtMI4pKITDs5Nkl8S6q/Gkh8
-nhqleuVQ/jT35Uk0T1qzhX+8EaUAs4Bp/kUJ50K+V6C4wBMECoMDHXyvmgkKCkb1
-8g7tMgv1ea3gZXcOU7MUvhgSzcndLKZi+taGTgmO+bNNdOnA1MAxMJpoU45cWpVy
-Pp5nUg1E5/joQGW9VDJFLkoIArO50e2Ccx+beDPtD20zBO4Yga+hfrztlAP9aGUA
-r5Zxu49MpeClqTyTnCoFyAMbAJXSjaBEcnpIWghaUZinyvnneB8JpK4I4zjwBgwa
-N2+D6K0MTDJjyYw/bkRa4U6vv8L91NTH6avCpMJMdo9SeokLVuPGXxAH85JfzeK/
-q1bnjrGBca/HJSksT+3wtj7XCAKIKLQiRGF2aWQgV29vZGhvdXNlIDxkd213MkBr
-ZXJuZWwub3JnPokCOAQTAQIAIgIbAwIeAQIXgAUCTs/EuAYLCQgHAwIGFQgCCQoL
-BBYCAwEACgkQY3Ys2mfi81lOqg/9Ev3xFwdEWPZdknj63f4DruELPC7GYb5aY4mA
-NzmsLkl5qlbr6+JtTZOyvM5wmR/0zD6me3e7YvMWC3bQJplMExcRJVlTBrk9hdie
-P/0CGaY5iXFLLqSVbKyNNQ3BoES6vJBX4OAgnD5J5NmCy7pnplHF7hRiasK0YyCG
-2QcDtMdgq2AKkqRjaQ3r0kBblbNQbU1KMhVfww890wYIJ/1H51ep3IkCw9L1i/0C
-8Z9mBQbUBGW8k6Vd4wtvnPYs6LNBXHuDX9qZumClEALfdIx/WIQZZ5OIhB94FSC2
-06gP4pgMFJb+dgOrQU6Q46y8rRsArEJRkQBS0m1Nd5hTxYi+O5V2igbi2vvMw3ij
-emA+nEURCJku7/qb7vXhtfUYCK+9XUUIHkW6IadW5hRqt0+O24tnOsoj5yZWdbbS
-2tpH44F/lFO5VRhKkKVy+j9D5+WXsR2NLnujMpqLVezIZY+5H8QsNp9+nPXKaLy6
-kfg86Ou4C0gdOXY3M9h+j6METzPOehhPcU4Oep6uwdogFEP85cQH/YubpX/xrTmV
-VcXJPfYsDoR/SEvCN0ZW6HBRbXs5fJrCZeFwvAG+ytXJ6CY56vp9n9fHp1n1+WuE
-f8eMBJvWn9IaZYa4fUKNPp2FGj5eCRS95onmKngom8YL4nzEN2qRQ8edF1Sz9H78
-Osshh5+0JURhdmlkIFdvb2Rob3VzZSA8ZHdtdzJAaW5mcmFkZWFkLm9yZz6JAjsE
-EwECACUCGwMCHgECF4ACGQEFAk7PxK8GCwkIBwMCBhUIAgkKCwQWAgMBAAoJEGN2
-LNpn4vNZjlAP/0QmueyzFVNlUC3855fh5yDLpnucSwCrrxBZzudRu6bMbd3eTNaf
-2WLnIstHqQS+PWDnDq3tf2k4btROqkJizSPDvajME+slM0mTyuTTT9HbhE5VfgGN
-vW0FR0sS4id72VLsycjaho1NP2/JNXTs4tz9qisq/eHIjp2vJbjcgNUBdAGoUvsf
-6I/O3SZJM6j64LBjUbmm6yZZSUtQCTzcB96cEkKCPoXRatzFj0xHEGmCCEFWrTuH
-KczbC6VTgQfGOK3N9UeaSplrR1mEBij+M51T4rXqQvb52ko/L/UoAPNuk0TiRQs6
-YvQTy16cQEszkJvxBZUTS3ifSmEVfaWt8f9sbVfeWPm5USIG/HwsiNNy977wbbao
-BO25C+3rC4W7rFKqzYsRXnKKBWiTVtDs7gvQBdGqWRwJMj1crTDFgI09Gn+N/Xth
-IcC/STvJdDgaomxuv9oUqM9QMm1x8jVD+4nnEYPWpV4mtxjoA+gIW+Vv1PGJS+39
-+dlA2TEtDzJfGPE3YF0jjy8ycqw+y9ar6+nnspyrLafCUybXCafQ121+F+zIVfR6
-KKr+Xy5bdHUVuRWeP+EfWnaYuevRoMsY+29eURO6hh1S1ZYukpANJP7Nu2onAPOO
-P2e8X8TF23ZcYcON0/sneMnnCuWLQ/Z91ZjTDu8BjbCYPWqgUuD6f8Q2uQINBE6N
-cQEBEADahf5YXCjYAsBznLgpRFL47H0ThjvxJ7LX/bCPTo81X8T3u+kd82AFr6qN
-yc/da3mVBJ0HUMqOSGXTnT6ncvlxe56HaHX09ZWc9yONa+LLhWMvHh8cfS9Z6fH5
-I1WP0DrtLRofO99K+gGE8GflaETIoqGVCcKbHwcmBmyfJM7OcYbBNq1vMj7vsF6I
-VyYGsGCmLoAwjuZX3gO/mZSwiJGY4XHQQx4wiRLmhxl/HvcCiqNOZy3FaD8s+KBZ
-hXoOeAtj5g0vQleRcoLp6fWEXBz/eSaAC3y2P9egj7CWjsQ/8ky4dEq+96VD+Xr9
-GE0cKVFfAPDSCbC2cHBfFbLBDXlnizLgqBWEjJJ1jPAcG5pcdk4YlL0Nh73Zkp9E
-uB9nLs5bLsWsmcNBCsHXkgq/GuDKzkWzmVhgQ6YpdIM0PJ+ycmys5mErZjkU942R
-JID9xpO2tIsBoWQT5w0nvAOejjjoSFMVGIWKRwMpNyXo/MQ8IovahZwn1B/1CQgb
-aTP5unmAgyYgQ5bKvf7QVoFB30tu2SX9c8Inx2ma0tpI82GZXmEA4Crgok1q3LQR
-NO7TVEmE4I5c37HkWW7z9oyO59KZLI6jCQKcIZnTuNu3viKf1GC2fy26QgdnTZFI
-5VOlfRYbVzu/V7MrAG56i3lZjEJ7uXhBPNugxMXtxoegvbXj8wARAQABiQIfBBgB
-AgAJBQJOjXEBAhsMAAoJEGN2LNpn4vNZDq0QAMeOdXlaM4pLO6spFxElkUK7YwSD
-j2oaI3DKDfsMt5Y1cM1pn6DPJWFE0+I3HG7KuNj1ldcxDQJ75LQclgS0SJUkn3kM
-AFkZRcpB2rbXYpUoN/9dZyiPFj689EgqooiQVVv0mbyrnDMJIlQ3oj0DUGUfAY3K
-XBVSameDnIadMKsPauwWIuqaT6BookoVYajEG7meUs4fCIG8Kwi+Yz98dFScQbkv
-YSUGC34i9g+35KnQ6ZyY2n7hYQHRizfkuYOPk9iF8YLMaefw+SDGu62EH+eS5Ip5
-crNwNAzjdETHRs3fNVzWxHt4+8KYI+nRBBwSeQes+gx5IYPaBJ9u16Bb6ygK84GA
-pgxdYBT6d9O8GST5VSFFVa6bZDW2Gr7MUAW6O4jFLrflZx6qqef86AG/2y36pZgr
-pfTg4CJszLSncTxuLQS6fKxSuaoB+H4Xn3U0nWDkpmG8tHCEWZkPktBcDebp3K+i
-BGH/00oy24sTQPzj/m3z4STzBmMFyIo7/9Md1H0PahgTYbVDquDP2+uEIh0Bh4sx
-bw5VudBxre4VpVxMLfun2alD019JN1nTsCLsvknb6A2zyEb/bK/YArPkgC0eK/uG
-x+tSCVNJzJwdmtyx6lISsuRWgamakejZAQJ1RsYey4uxchVoIBosEr96HGAXC6QG
-qUGpmp4gd45ZOhyRmQINBEvFfPcBEADU5bFOcbVCBDsTGq3D+8AnA933S4iYvxPw
-Z2eRaT415jXQs91wNTpVwgY3xRzkjThXyt6FV7Cd+BdS4YvGHMvqKZCOgVnrzpxe
-Oqy7GsC10yfU+dA7GvZiY8hm4tw/bdkjTsQQeWePgWoCgmqpadKAn4iGh0u3fBZC
-7p4Z8Jsf02yqUFIy48OxKf/aeV8W5sTRr1m3HDmbjtUw/UbUW761GIgR11BxcPIa
-MaN7WAwKHNpIUqjIZyZ6z6tGGQ3fRwTAJwi1SROEU74DgRtRHn+pu2kp0pOsD3wz
-8yT95gL92CC2hkitTZB4+3uglmMvuQ9nwQjadPt8as3fB2mfGK7c9tLXMIq8bmDZ
-w+mWU/XRbU4vrCdQVyWH6LUI28/Wnj6LUEC6hZOnmiLSll3bapo0eca3LV9NVp0G
-aNy8QX7Op2KwE3mWkN4F7c2ZyGQ8MaL6qLlmE63Qe4QFy5wJ4H16NCvXHqGxm60b
-nJy51NmQJGd7YQeDa5AFbgFJ8V4Bg+f1LfIeh9rpHKiuZRPZDs8yxs0kFOQOjZ4u
-5HNn2AyuYvibT1CXj0X8OHzXpQ3SylBy+LE4xEnt6JPkQjXahwEwTotUxTJbu0fK
-9UvBB4mqzITs270HSowzEofhc/5YJSxlxQ8a3dPBBRjzUeTfNN6gBrEV/7YalfDm
-xHKEyFHepwARAQABtDtHcmFlbWUgRm93bGVyIChLZXkgY3JlYXRlZCAyMDEwLTA0
-LTE0KSA8Z3JhZW1lQGdyYWVtZWYubmV0PokCPAQTAQIAJgIbAwYLCQgHAwIEFQII
-AwQWAgMBAh4BAheABQJWrk32BQkSbZ7/AAoJEK1e27eT7FfklXUP/2XzrCgNOhRr
-DkonKQddWqzgz9DXPttmA9mNeXAM0gBDcnNUhFXiL5yjRyCmtmrjdiv2GE3yE5Xg
-arCoyb8tTve1Ps9ouzkbHQomDOdoTv8maL+p7MovSXr0jcJow9rVIdSP03BdFExZ
-u+H7nXrKoSKcBFIMdDVznMOGBO49Z4dXFeDQgZ8xafDpB0KqCHKMf5PNQ7iQ77uv
-3lIVFvX4svnPP5t0FEmFmwZ0YaKbRoa/I4fy0jHCpfCtEEtmrUuKIuQt8uzpYpo/
-BnH/yqXp/ajJ7x/P4n4IbLV+HSkX8Pxfh6ABeilHKvwmmNKYrNl2vCnYzUyWOyDF
-SEySxHAEN6hLdrExZNXDaLh73QKN/y8bl4sh3Ehml/DBhbktGteUBHt5M09OHvu+
-AYLpYx0iZUGzUd6hxkWlkJ2kPeToMcKfRLNGT+237VAvbGaNrXwFZXELwaptL22X
-uu2Tfn403/aD9ssk4L2v7GwJPeTQ1U9xH0742eWJf7OX7UMUoCG2HJf7Sg2nRtIg
-hK531cE1cv3i35EOHeVWClTeX2kc/9hwgzXAMWKrEe/3OMGPHQNNRsnPCsJHjhUY
-PRvS1pa6k1nuid8IZEeWskDT2PmSNWJQay9oA2ojcFua+UC/29upvBqO0O+/CtLH
-YvinLANDShtEZJ42rwbYEorT6OO8YIwbuQINBEvFfPcBEACtCHNuOn+pZjBOWmW0
-rqCnN9Oywq0h0Twk/UsqkhAijImgXrZLMoeylGA+UIsnuNl6e+x76Ke2z6H0Jytw
-IZEi9EqqZa3UhpN7JQ0ddNzkzE8tvYdicPdcXkZ99KBcHoPd25/N3fNJWJmbBv/b
-CQMW2J/zRo5QPokOjEl770xNS9wcXmA3ptTKbyzfQ4Wh8LALrJ3F9vZw8GsZFAmF
-NeMLCJ4Qhxk3MjCoQdzzRSTYEu4c7eYbE+biU/ZUgBMJH4Ed4urhOO81d9dDvf2C
-CdcJdftAYy/ACtTeq8tc3YzG+E4J+uplxxyD+IFP8U8Q5TyWdb5AU/rAWa1UdpwZ
-IQiaJfy4E1x+ac9BHAD1BZaCMv0fTcPxYm8m67GYUfFqRaI5Yd9sPvuzF8IDs2bo
-Nl5L60ce8ROOBtwRGp9daOHmhIlRKGoG7FPc1dTGjrVd5lWgzet+CHnWZ+HKYsNg
-W7cDo52Dwa8BjenK9OUxvzTNzwmz97cCioufv+ysUS9DY9tl7P0eHR1tehTM7HSA
-n/lCEU90j5/f/ozwBR8cDF8lLSMXlKybudjHteLFA/2/HbzWIEWVLpckmu3Xxpw9
-EF9xoiQmbJTmkWEIBBSLAALYtuygBbiGUdwPBeJQQUYliNpdgrwKXp9OIB8NFK99
-DvG7xB61569hUaekwnmB5uEU0wARAQABiQIlBBgBAgAPBQJLxXz3AhsMBQkSzAMA
-AAoJEK1e27eT7Ffkop4P/R97j+X8zfPt9gsABnU5zHtGS6jQ9Ahax+q0Dx0Vm7Wv
-qH8DC7RsSGp51YflfS4S3xNVGtQTUSV+z7H4cFUSD8f22RnubLUKOplVup6m3Dqz
-/Nosht8sU5Yo2mFmRNMFGo/gJF6vtqX15rPpPh0gHsEi3Toa7qzegnIVfuU14ZND
-tRnn5OmuJfFP9xO1PxIwqi+GaY06zkKbcmSw12xOwOCEt8kv4FGCx1FiSrFdHK4G
-fszvtzOG6VPbAROnERG2AbGQPXO0+m3bfYxSv8rxepSjo4f/1sXbVAX5AgH8wtcs
-iZLq7D+UrTdRe27dB/PmN+L4xz9UEmU//1rLzxOBfyWLfITF+65e4QZkrxIwVI2M
-4AVB9pAb+fSMmWMb4IU1tuSkeJT3k/bKeWdGVbXrDiSgd6XChBeui+Td/KR0Hbl+
-i3jeDm/6tYeCob4XQ4nhJ3dI9gI1S4YXJgGizhZmiWqipA41b12rQAB6ieU6aE/N
-OX7rwcNGaCwbyCgDOQfR7fiqxVF2xA8som/asBwAUWFZIMEhfijsn7fpK/7uGoN9
-2Eqfxvcwr7Rkp+bhCS6Wg0q+bgBn/02MB+9Uk9Yi9O7/DI8CqpwsiMzZ72ZMm4pT
-Lp8WFmqbxePWSxr4JA6XmApmC7sSlH75melFC6MCwaxpoRMbeH2mDhAfwbjXSPMO
-=XUWN
------END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFSu9kEBEADiOZ6fVyrEyePZIg48FXmKCqexKWOUIAOYLwyVbU7OjNrEUmj8
+Ywx9mMQACJJPcgUjOTxgGi0N9TBJpWlFDxAXPRdPr4GsPAq8wh3uVncC5qpaRdPj
+66/nZis9U3zuRufnYwuhQS3GBbFrV282aDz7DOwFUjrc56nfKj16i33lIgd6oNCP
+7hHN3ZkSOH+bcAdyhmX5jvVv3OxqF6uq8GAHpCkE1piERdcAVkZfPKdf7pbE0pD2
+BHGewoNmRucm8E7I4vP5+xUj+VsadJfJxD+FXDfGDfRtce46RiEh7VJHPvxgBoCU
+Gnvqjc/4z2gkD1LE/yPpdaPe+UG1stE2XV5ReXUQPxL3mMZXOai7QjTwcYlGY4YC
+YRhCMMkkHxRHpEefibTIJBlmChxcIYRGBJE2Y8xQ5oiyQycGqgJ+oSJ8ZLUBOpmB
+i4SKT0yjzB8cIB4w4ayOvp7DU0Y9+O/NRAZFQ9GC+4TlzebpZywMmSBMfthb0/xP
+hB5uh5VjvGnCQCn1J396hC+lcZeMQCQLl0mFodcpu3GvGUsdpXtxOnXv5VqO5/iz
+fPNeGNCuH2iOI3IRUEN62yjDM35TGBbJsZ4W1d56pSZSAumhXyI6Nqooj+fbj52X
+4ljnW0fxxHwMb3c33Fgv8IN6uya/GAhkRUOum9zf9gyEJZUcNnRmqadCuwARAQAB
+tDNIZWlrbyBTY2hsaXR0ZXJtYW5uIChEcmVzZGVuKSA8aHNAc2NobGl0dGVybWFu
+bi5kZT6JAlcEEwEKAEECGwECHgECF4ACGQEFCwkIBwMFFQoJCAsFFgIDAQAWIQTl
+yjMdRKuOTIBv2+4mEBti9pN2zgUCWmhlwgUJB6KMAQAKCRAmEBti9pN2znltEACa
+MD2eE28dV/wMu/G/3u4uJ6mLvb6gH51KC8KKUG2GXsnp3Bcx6RxEnsktOTRG8Zdm
+Q8Xk1QdTh6sREug62MgEKXPsNudRxzcMteISyvYAR4RkrZuHAHRxboRxB317AfFV
+iKon62bFvygOgO4MnTgfwXr7Zvm/gPONzFZgH7sdTICQXOylgce6NKojdM96FJ9F
+39A2hMcFGkzrGKWvnEIRKPngV1oIH+qfrRjNXiaSvHxpePkp/QFGXzjCQASEw76h
+2x9YytkhOkrOkFVFeuwPXCyenA3w8XOfzkRku5nuFmyeOhE1wUdP1zXDSI+5+rc/
+RV/FnQ9z1XvTBL2CypLJWANH4qWYF8cqvTP4RZBB5nTof/l9gF0+7qASYwEqplk7
+0MZHCjgadi4SB9I7w9j5bJBndSeNZPYc2w1eIPc6v+FM4BLtUU7VnTVfdhX0TY50
+iV7Y1rF3glJbWTG7btmEGRTX1GZWJcfsS46jDKqBW5LEC0WxtNFfPn4La5dY/VzC
+cVxyZdsz+HxKdq23rNZRLdKdSrTAsGgjRJIBevVa2fw2q8YXRVwtWY1dccr+Fxmp
+kSamoAlLH98HNUxDpkr5aaI0h8dxhMqX15esqW4UtdbGKxec1bC4AbJHzyCTNckd
+naDR+/umd0Jh0UmgccQzQuAy5X0t76Co6RSOMigqsrQ1SGVpa28gU2NobGl0dGVy
+bWFubiAoSFMxMi1SSVBFKSA8aHNAc2NobGl0dGVybWFubi5kZT6JAlYEEwEKAEAC
+GwEHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgBYhBOXKMx1Eq45MgG/b7iYQG2L2
+k3bOBQJaaGXJBQkHoowBAAoJECYQG2L2k3bOrfcP/Awnvbjd0ZRBsYeGjehzDSBk
+SeTMalsX1OGVeY2Muq/bGwc2MUQNOuHLXhpO0h0R0uaZOj8AqI2omP+kfvIIAp1l
+hH1cS+wAuJRMGimOJgaqcXBo6EZlCsA64dK+vcAoZiKlmQaYxKvXU1gv1fDcUczP
+b6g8GAPPL/3XJZ7TbrAgjWX8hWmveJAS1T82EZ5B6T7mQcSQfcPwyMLikdgxT0Wf
+G9peOCFXf3FbiIoElK8tN3xgvFwMc8znv4A5eV2Xq43ZpDV2WY6KphUxSL8Jozrr
+IdsQSp00jLkh31b9KGHN3Hwi4ig6A5zihxFJFWpqBpWbaRR7J8/YCP3uo3/NF7MT
+uOP0OuJ/7OFnpT2laDtNtg+apxnJ26zSCvcgUbhxmWPNiRVK36v799jjVpJSsv49
+or0Llnk7iZF72S3IEIx59EhpQOha5KbhKjjUBlEHbrCLFaRPgsU8SVeu0HAevBY+
+O23oXjW0OswgSAUDADICGj6CJEvz3CwVuSxhDjHUurIB3oSw2KQFba6pVbPyq2n5
+JJ387ZD6mmYrXJeTSMptPoWXxqCB2EK3eryp/ns947yIpyUQ/U0/chXpcV0hCafw
+32QkrM41aej5/r42TwOyFVUnPZzm25BzLEkx1m8FcQJDkrJf6XUwiqzcd5KpB3hl
+T5/rF40Udw3RSryKNB3v0dHq0egBEAABAQAAAAAAAAAAAAAAAP/Y/+AAEEpGSUYA
+AQEBAEgASAAA/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBAUKBwcGCAwKDAwLCgsL
+DQ4SEA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFBAUJBQUJFA0L
+DRQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU
+FBQU/8IAEQgAYQBQAwERAAIRAQMRAf/EABwAAAIDAQEBAQAAAAAAAAAAAAQFAwYH
+CAIBAP/EABsBAAIDAQEBAAAAAAAAAAAAAAIDAQQFAAYH/9oADAMBAAIQAxAAAAG9
+fPO/GubSEtBlGa96w7AivEFcmWBP87YnPpNAfdeJLIrVB5bw3TDYD3w4bu2LTZXY
+acWOgVnc64Pf8Ec9x669KlsTzp6t1iYG/tSFU0r7naLuwuQl12uih5tA9jqX6uq2
+jvAzpGF6McSNYyGJiilQ83NYWhp/p61ZWTds7d5z0VM584dN0L7eKFnVdTHT5L9N
+lFkLJva35/0DrrVIVyK3lLNTL2PPsXHG0OOvT44MFAwd0xttYpwTK1Q38ZNWPTkM
+3fF0OIvQZknQrAnUwPMrh54UPTFkg+osm3wFrVWJQNEvXACUD12FzzDoYjPXeS7g
+nWSR0nGJtlalBxLL5HGT12juo6HcKX+skxbGBn/EvUTEoWDLWe08e6Foxw5fK4kD
+5o5msxQloUADLA+1dXbvUH//xAAmEAACAgICAgICAgMAAAAAAAACAwEEAAUREgYT
+FCEiMyM1FTEy/9oACAEBAAEFAh54gp4AvUZvJ8dYrKN0sFQz7ztyx4H0EEdgn+AV
+sX2K5xi+MsfZSAxnq9LIzmSBbDrKZvr20zWWNhGTtuhVrgymkEXBTUUEP16Hjdoz
+XaovSqyv7Wdy7mp019t/4ZaUE1EkNSPWK3fUv4zdhDteu4LYesShtWHx48tCNjsd
+we12IVFoxOzQbWEa8m1j7PtqjS7NtA34pW18MuyE6hX+QsDaHjY0G2XFto1jW2l2
+AM5shS9iWprwqCszeskE962zirt1Vu5q2ddQPrfOlzY1Qa7cTYzSMVegV+qKZBra
+ztqE4yxFsNXuRva2xHOTswprhrbFx4EkvFtlM7guCwbISTYggUz0un4y2PsEWBYb
+JV7n5fuqeLdGb0W85DjwH/U/nIjETZniFxAQMxirnTNeInc7CEBH0AdmNqlVeEfT
+md2/Z53xGePqk7HP8g5o6/v2vkyYXduNJeTONiKtYOWEOePK4R2EWDni/wDeeTf2
+lz98f9bP9dfA/wBaX9LM/8QAJBEAAgIBBAEFAQEAAAAAAAAAAAECESEDEBIxBBMg
+IkFRYZH/2gAIAQMBAT8BSxkqhfFnPNGIrByOxd2cnJUL9stfZJR7TLbwjH2Pb+oq
+TF/dmv0wjPaOV9kYOZSXRQ/hh7TXHZtbR8SXpx1ZfZGCiqJad9Honkaa4EbWSXyj
+ndIeUor62STOBrriNcskouqW1EVy1IxIZPTjfIbjWTng8jWt0iMs0KHE620MT5M0
+v0q+xxU0eRqLRVEu7NFf6Kx5KONM0pYJan6S8hRXZqOWq7EQbjIvOCtmKbHJsorb
+ojK1XsYt62hWPavatn0IXuW0tl7frb//xAAoEQACAQQCAQMEAwEAAAAAAAAAAQID
+ERIxECEEEyJBMlFhgQUUIDP/2gAIAQIBAT8BpSxlhq5DFv03olRg+oK37MZItdWa
+MLqyKVTF+74HVb7HJkYRlG6J+NWqRTUbiozo1FCcdkqcaZOLqO8dHtTJSVzrQrJW
+IuKj7iNWUFaJSpQpRxpxsjBS+on4cZEv4+pD/mz+nSppZO7I04LSJUKc9oq+P6c7
+LR38opoSb0KDH+CfkpSdNbRlkyLMir7omSnsjSim5JlhuxFfY+XL7iaL20ZmSkmi
+FKVNJD/BcZf06U6n6LnbVhNofZHxHVpvuwlOldNGbkrF78eXb08I6MS6RdrR41F1
+5dkXZWPM3f4HjcXSMhTyjYls2QoTqOyKWFBYkvuVkpwdy3Xei/C2OCexRSLsTPgq
+fSyV7/ji/CXZLm5LRPLvlbESf+HolzFEnx8cyHwti0S3w9LmQz//xAA0EAABAwMB
+BgMHAgcAAAAAAAABAAIRAxIhMRMiQVFhcQQQMjNCUnKRobEjgRRDc4PC0fH/2gAI
+AQEABj8C1VvWUZG8RhmpQZZ6viUTp7sLdAlundSLbQbiOql2GgnXK9mY9Wn3QqNv
+tHqJz9EHkWNd7xVxfAHpRxCE7x+KFy6KMCcYVwJiMoAjXM81YSZ4f9Vt4z6uqB2N
+XkKjdP3TA6kwt4mlWbP0KqsqU3B7W3Y94KnX2l1MiRjVNe+6HZDV7Nv0UFlvVuFs
+HP3To5fq6nqsZ5ZwrfD0Kj/lHBUm1m7Jky5u0Ex2TfFube2qbalB+lv+097GWUb5
+ZQ4ALOvm951p7yve1k94V7XXcZHBbO51Dw4/l0d2e5VgY2nSbmOyaGez9DJ/KnLg
+3QLZk2nqVLTLVJT2YBdjKaTdS4YCDKTczo1EXFsc0/ZH1cUQ/dB3TH3+35UGAOSa
+9jJcNHoUaxEwtrScOyLA8Nd1WyfLGTndmED6qg4kJjKb3Y1eOATWTLCcTwXhWM3a
+IJbnjPFbd+eYTphpbp1VV7ocXHIKLGkbaoIps5K5zpfqZWTFenHHgjNS+dJQm7av
+y6Gz+yItdBzK2o3Xg7w6qnvfrgb0c1Lt1HfuJ91uq/iK+vM8Ft2Cze+kqjve0ZaU
+ZkStLD1XMoEenQjmENk2uK54Mfa09UTUe6uebiY/ZQHW/LhC+Z+JVGE3GJXhqjn2
+U272vRNHCOC1u7rGDy4KVf75bHZAKSroXrjpqgwzZxtQtGQPJoGpMKx2qzlFABQc
+qeCc/sPutbR5eFZzeqQbrZJ+qY1pha5KpPHrqE/QeXRMd8dQKXCR5UOzvwv7bfId
+14T+n/kfPwnzD8o91//EACUQAQACAgEEAgIDAQAAAAAAAAEAESExQVFhcYGhsZHB
+ENHw4f/aAAgBAQABPyE9Z0rcCdB2bi2gtW4HmMjIVWHuYcUCU5eYwoKFP2hBbZQu
+19sFWDFwPZOZgcDQquaH+Yxs5203VdHuFqNXouUu6EZ+Z1hTDzTyxnM2aDHT1EbU
+VngsBSjk5PuFkJ5DWp0M5q09Mths3DHf6UTfwkPHrMbbXRvQOEmjDiBef7oFSfCo
+7F8nSLaqvShWNQC6XDUeXtNSX2i4ae6lqNVvt8EfJrA7V0fcxG1jFyP/ACV9cKBq
+OiUZC2wXUN1ApsETZqunJIbCqcbC1m/dd4h6PcoosA95waBemcygowzxV8n1CAld
+Ax+GOkInmlb5kcXOqMULtdvdZzsfDvL+5eXLDgwcETK42ClB5gsmWhGyDjvAo+hw
+1rIV9xwyiycHKe4fkg6SxlMs8Vz6mlZG7809IE71q2QhtMSziU80qOcS/wCBscTU
+FAae5nJNf12vZAGlQczs+e8aURa6ew/GYQRAvyXqXlqPNGre4DAcqcwxTNC4oUqE
+bJclbiDq9rKPMLLuULL4ivJldr7qiB6YFVL8gr8TptMS6eyMT0lxwSMNwp9qE2vL
+aYjvXMHtP6jsaTjgGggcJu1d8kFcDC9Vv9TEDAt1EJs+D+J9kefEfIF+uQ4kBP2G
+O5AgTyx8JjS81gCjw0bY3cFQ+JV5iroWcvcomXk7O8cl2dMyCY6tvK8RFTCsu+0t
+eB/bO9OZeCphaTSkz8OWFmWqmDiurNXKDVUxz5gmEbUh+U18t/mMXgnTEypbiqsw
+6syzAGjrHFnPuRKNbJhRgUl/lgr4M/qH0ZfZhCE1tqWF5RdHUDxi+X6m59xaDSY6
+z8JCaFK2E1n+p1z/AGeZ9H8d8/DV/m13h//aAAwDAQACAAMAAAAQHHAkhuHnIM0X
+SVXk2hYx4dFqqkUj87TBa9n+afigH7l5i9yqnkoqIre5SNPbUU7e8//EACARAQAC
+AgMAAwEBAAAAAAAAAAEAESExEEFRYXGB8PH/2gAIAQMBAT8QJY8fHUttqO9u11G1
+pn57i0lUztYjpmDC1RtJ/vvzGVqhZWAwTm+S5AAjvGuag4qpoYZK6xBsVVKvHc9F
+xssZsNUdOxiNrVDNURCUkbmtMAKzN6jP9+Ro3K2JlQluitTtDF/XnsqCG7gTuE0y
+SzYzEEDMv2NsuS+6IktRiSqxBqcMtqULuW7FV5GEJTD3KR9t+iFGI4DdTIKIxu55
+8TCDQ/sMDuUCEb2bj6lkow7UclYgA7llruXDRg3PeBUHccjqV17gVDq4UPuZFMIV
+7U6HmWIXeZpANE2LcB5GDCQbXBEcJEoiomU1KiGY3H93LkuOYY8umbnDpRNQezUM
+qcumIHPGsORthO4bQ1P/xAAnEQEAAwACAQMDBAMAAAAAAAABABEhMUFhUYGxEHGR
+ocHh8CDR8f/aAAgBAgEBPxAGWhXj2uOieu61TMeZb0hy/Dc4lXFTKOT+PxEWfb48
+9yljhXxEJxbivXj++s0HnzzAbWfEqE5y9e3x94q5OgUteh4ilDGel+rLo7eeePHv
+Auvbfz79TB7ZCbx8fDXEVlJXiLjXOZV9/wB6ll8lS3giEAPzH0tr+9wAQTrd8k1E
+HO5sBzPacX+2fEwD1X5/3KVlH7SzUr9fecGj0uD16QLMNehi7WZ7GkFi9bELz+1n
+/ZyJOuiIqp0Yi15foXLB+sOdyK0snlFN7GGXfVXn7QKdQlYrbIFfmqfdgTmLY8Rt
+ouM0Tm4eP5ii1nv/ABFhc/M1yV5yWddv3fWJvSX6qDVUUuD94YDgjwlsbXiWYuu1
+ikmqqXS5Iwp+Jq2ARzt6lANr8zKjIZdC/wATNpBjVTGJoAgthUUy5XyRbRFIC/NI
+NcQg1lyZkqEuuoMiVVLHE2XOJmTJeoQ2XCGNXKgbKFY5QR3mJQfX6Gv0X2o/4lfp
+P3ncOPo7T//EACUQAQACAgMAAgICAwEAAAAAAAERIQAxQVFhcZGBoRCx0fDxwf/a
+AAgBAQABPxCUBCWAk8m945oGlsQalN+ZGw2ECd9zQQNzsxJ8GXOmKEjFkyxgQcQ+
+ZQEQmLyRUgMBGQ/Dn2MR5mTXYGgZibmeIMVlINayjJgqHH6y6LMN0kG5tGGYwsio
+XfaVyxhhBW4OFS/b8YuvJGyFPBN4CBa0SKESkjAu8aYmuwMtOwz/AJwXCcxJ7Adx
+9Y7KAGg3C8G2zcc4rIY2hYH5fqNzlwVTAoA2l+O0MkAZCKJIUiSYoVvBBCEBrgiw
+sKSRzlDgqv2M9tJw5VZocibdDM6JVvKs9CCIpCXTNZ2Y04tyRLZunfVYPlAK2zwX
+Hfxg9FANFHUsucm6Lgjin4cTkQ+W1rpAJJ3gaMdwQ1KFcoyNlZ7cMTOjbVXxjZuK
+MbQnR2rK5BiSzwTKntV65yVaC1QEKRAQCNFRluTDWhmuSWkQ0hkgJEp2v+MCcPT3
+ixFehj5gnJwAPiGY8weAkvaIdiRp5EZrxTqkYigJE6U+cCnlwd5kDwjARSogJarK
+FCsfjCWJRVIKg0LfgA4x+WIHpD9ivK41E0QCzpOMC2OmSs1iJxz/AMwoOFCWttD8
+1ktcZIkOARg5XPZjp5lYkI2JgV6kGItFG19eXHN3YbWUh2nZ7hD5WqQU1sSOeMM0
+RBHgE86x8bDCjOWpv2cnq4dB8v8AOCFKDbbqsZ4yFlzz4dTxvWb7UBTTcLl8ljgT
+KiRi2RwmHdMVgr3HejpRnhMPWE8lASSO5wG1MRdMelBXBBh8yYFRGx5iNe+YgikA
+EiRZ0jWDKpGjgBPMWQ3pGTXGhzKusGAB5oeNER5lONEQOShhBE/IY96ZaMNTFp/W
+UBix85oB5tcYKAEYSW4eZDKB2cgJV0xJ0icGD2ROsJD8WJ/OPY7JpXsnXxORpkBf
+IJo+0e5CKdoDhLtCNEr+cC0aAzIAQVNY4lH63ojxD+cnNBQlGe+OoxrKsBE+J0n0
+MFwDcGD5D+sv6PTba95HhBwyPgtCSKOtgYkbDJMkS0upbPX6ylD0FfZf7wO7B0mr
+mZwTYyVY2COq/vGeuYqZxJqGFc4ERwS5iE+LwNUiIiD4xTQrSDlTZg2kqyz78aNU
+dB9L3iQgNwcBo+8OaC3vJwOaFA+ZwGgwILT1VE+uVpWRTr0SFXihhAmpW2N/qM72
+dvOR3ok+Qf8AuHSQRRJzA8cRsxtVv85SwUvgcYiANqODFJ5dSVhk3wA/AwDaSYf+
+MON/JWK0kneU2jzC8arwrxZtC5JIfQ/eSsO8/onzHUJkK79xsC+Coko5lcNjYWVi
+tI6OXFemlMcCcPuGgEBLvizNH8FNf+152Z/Sf2fxc/f/AIf990z99/bn/9mJAlYE
+EwEKAEACGwEHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIXgBYhBOXKMx1Eq45MgG/b
+7iYQG2L2k3bOBQJaaGXQBQkHoowBAAoJECYQG2L2k3bO1k0QAIIrNpPvClBQaHao
+IhXRp4xAUG2pGNFlBbroQfRZYHqXXCEO2TQYZBgiGiCyzgTKBYJKiZVdTjcF7Mf6
+/mYe54YWxNddVpjMv6g1wAEvdMh1o4V33Di0/Xp/VWjzUIsrg9dpVutYmg8SKRZO
+b4QGRuLi7gZZSzlniu2YvTnLUZ64/nT6gAKNUP/tTvbSgO7FcunwwhPuUoiwVP5E
+7stvVfA7e4zucJdvmpTHeUP+LV+AyVWAmTua5ytEtAUOLrJHsPixGVKXNfij2cYE
+1EIZ3xyVF65uxC+41Y8wmjfbZ+30rTZ7pA2/dlhEg7LO3zGZ6xH9Ud0HIe3zY3ce
+uLdsGOlxBZrTePi+Y6ltLrLwzlkaR3+BvqP9g3Cb5m4G6y/KAPH4QeL2Ych5yITU
+qu7eMIjsoJHxy74fSxWr20vLW9rjBEFhZUxZykHEtbilFZdo++VuQqNKJLHlZ1F2
+P6peoWZ2wZ4+vW4rr63STYyQC8Tyc8Ng0+ftvG9pEyfxOjM6wwYVw1jGed2enSDp
+QRd79nkBgm2CtUPXjUwhTxXO/0ZnpIX2+0wBQuTvS4+2QnG6yKqdo34UfBUpifLo
+FUGd8ks1fc9bL+CrnUgFaN6nEJxHjlsihtPQG479R1gNEYLaOrbATHn2BgC8SN3o
+ChYdiVbWni4pUuEIItiFEIujlQN7tDpIZWlrbyBTY2hsaXR0ZXJtYW5uIChFeGlt
+IE1UQSBNYWludGFpbmVyKSA8aGVpa29AZXhpbS5vcmc+iQJTBBMBCgA+AhsBBQsJ
+CAcDBRUKCQgLBRYCAwEAAh4BAheAFiEE5cozHUSrjkyAb9vuJhAbYvaTds4FAlpo
+ZdYFCQeijAEACgkQJhAbYvaTds6yqg/4mANG2PHYlugP5NNdtfFHxM5VwPUSCumW
+BB8cUEW7VQYhOVUukzAFywzxY9rczSWbpDZwOlTZkB1EKi3cA3XoUR7p4+B02V2x
+TMm0YF3FMmKKXzKl37iGRhhPWrrrVLixnZ4Vi3YRB7M34Clr2+7//sLlEh1rZ4QU
+sRoxgSj5NGJGhYBU5DXjoJqPFWmRxC5hhelxSq0Dlbu9I0aAWIp/IbCkfIyTT0og
+IIVOF5OwvBOcey6ZGuSYPWu4v2F4QtWlUZD1J4Ez48hoOnJ2Dx0BU3stg2pkVIq+
+eCkMyAkCU+OKpvI90B3rFNPv+aFwXlYFJKh4A9wpGr8r57It8vx5IdY/hjLI/YeS
+D1de0vQ0xgK4OqJ382J479AKuGEIsHQ4040PD/xYGECvmmruQwQeiq2uVGMvN/oD
+R5gM7HBc/qqgDRl+GIwwhqjJaNnmDCizdoevXj2pCZ7mD+SKxyExRwxD/Hz/ekyt
+I99MVs6LqAbLQZGxBmxRACDZA/enAIf0vyjfVf43Ye2ZcTRqkvLcr54acLVJy4FI
+HU8BMwzDaAY49wgfIVtoMW1swFnz5t4XYN97H6PtH4sogNvEd1E2Nmnm/yTubocV
+ruLABq2Bium1A1cREMaURACrq8QH9EBsO8vjn28xhXxNVGC4NFmI33RlBvSGkA7l
+OHqaKk8gpLQ9SGVpa28gU2NobGl0dGVybWFubiAoSFMxMi1SSVBFKSA8aHNAbm9k
+bWFyYy5zY2hsaXR0ZXJtYW5uLmRlPokCVAQTAQoAPhYhBOXKMx1Eq45MgG/b7iYQ
+G2L2k3bOBQJafrGjAhsBBQkHoowBBQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJ
+ECYQG2L2k3bO1AIP/jaUoeWDHD75+6mg3fsqepXkf5NyZ6zrpeCwYfJBInknbA9Q
+OJH+uHWs6U8NIVTKpvF1bNWK/swa/9oG3igr7iys1WirrJCsxwc9px9BIzvhqkG9
+EicZbBCRWQLFYAXgi/2iNXl/SJ9YKO4ofn4P4CLNLDDr2oNZW2M3QHwOl27Y6pZn
+aGQvIzExLwC13D6wKxMTEbMiT9+cGw7pUyZy6jSsSdworsZmx4jQFmIFm7WTAx3C
+X9Usy9fVPH+qYQAoYVpeEs+/I1MhOpMp50/AkOqfu5/qVI/s2PUxIvctTJSk799H
+nDxKaECslTLGbUKjy8iGrMexNvSLaVJyhV+Dsd6tSmknLRIcIvRV6sRaOTCmuhaD
+10jsXTVlZ3ww9IFtymmB+dNuNbQjDHLyIQX0n2yz4MYrmtkYsl6DNM/ugYBXzXFd
+s3TcdRgbJh4gsi2GCHbQEzhGzrLjoacSYHFWqIL4qdJYEGfFwh7itXHQJNyJ4yEm
+bDXVLTiMdnYIzQiRIIFJdPgHgTOHcWeR5ymI7W9vhnXhRaCaw2aKGo7s1MH/4XQ7
+j/Mb0kHvbLVtyWIyZS7j3uPdKA1pTemmq0jF00dK6QA7OgDmkKwfCe+Deptm5UH1
+wvc3Op56vNL1Dza7EzP42rrVSBRtCUuZFsDsH1UbQYoFb/54SblKeWMK4Dk9uQEN
+BFSvBSoBCACiWhr0YXSdZ07/cOsfhe851y6lKTqgWhkxB2hB/Hzs2jGNHh6nLWJm
+1itk2B7GzpLtreU7JoCYfTNIHDyInkLJ0qljulUV3aUVtS9Lap4fP9dVU6cJdwNK
+Ed9hQsGm32B64lz00aahKUXwmOnSHcfGtJMOd5q1iWvAlrEwKbPgB8LZAjCa3wV9
+fta82bmqoRz/uoMuABWFsUE08tFSnKEufYl8xpczRo8Vo++rBLDSt4sil74vhNd2
+/ryNGrwU+MHNxcGCxb25XeomedwTfIdVW/mkgXeWzxXbiOXuT5bB/S91scWiY9sV
+FaixpvCUbTQ7bdfVMJ2rK0M1qXuIcLM9ABEBAAGJAh8EGAECAAkFAlSvBSoCGwwA
+CgkQJhAbYvaTds4v/hAAkeIaaI76Ht7sb0BQyZlSaIt1xnRAmHPB5spux5sjmoYX
+rHiyqxPkW5UQWzgdgRpcI/8yT3bEGm3/n/2HW4qqvFOiIiwvsRh0O1NAz12UsAI9
+OKdvP8lFcbXdofX0gBBylzaZXAo1JHio+c8tQmYEisZ1QY2Ht2bOadOvOIzxTfkN
+ycR/oOPUbMgrfB+P6ngMWnRhZevl00abFTrd79Elc4Dp9l63hlQ43oGGigQQaJkf
+FDewvjdUsQpRoZs6QUEerj4jXN5ztUz75KRb7vOM1a+ym+TdNpu08MB/g4S2XziM
+dCkLzrcDN6E5xdP7rRMdML4r/X+7sLYHfI9Q7WAgCscWQGtYi7p49SPliyrFcOEz
+e7cRgbi+pQHdvfNuD7W8vHzD6PT9jRvpRfkaRt9gBWLbtIbGDugVotH5x4W7PJvD
+XGFRVanv3mAd/qlfRdk4VGgFSWxIZzMIkr4GEnMObqzAS1tIFxN5mx+cfUaYEzhf
+T7vfS0azRnpVr/6ufsqBJP4nPO2I7r8Pe4F5RUL/A4e4w1wjcA97SVJmneJfnufF
+513bhOKgXI6FcaMTGXlFvs+2e0ZPZwWufiFlzFQxqhspkOwZWL2kZpP8u9Y9q4jN
+jTp/6RL+IJ0NQdCGu3MSP6+5kXjFjZhgrFtOmJwLvout1bSJUEUJLgF2trc/M4O5
+AQ0EVK8G2QEIALPvDu71Ar4rVJu2vnCt577voM0Xv1fxrf4j2EvD6ZEldXcIifrr
+p/NqojrrqdAV5RcrkLrH+62gZ+o+f0qLedc6Dk2JsdFzALrJGLW/GgUuGw9hka0d
+S5F5OgakGpHFrpW3ApuKiNEivsRdL4kDCGsSwSlgg7QLKZccWZvangQ9ZAHXq6RY
+47cxGf9nW5y4X70skLBZUQYjSgrIQVD1A0VEKeo/2BKNTyrQdVldBZSvv6E2Z+Qr
+w4NWec1bUQekRkA7YB8E5ueBKN9P0RPM3MtOxayC+EPHfejv1JkiKbjtToliNOx9
+q9GMSReFok2RMoYL/VFcCMEjc1ohKZAWN4EAEQEAAYkCRwQoAQoAMQUCVNJ01iod
+AWtleSBkZXN0cm95ZWQsIHJlcGxhY2VkIHdpdGggbmV3IHZlcnNpb24ACgkQJhAb
+YvaTds7nzBAApgOs4kCGlqbBp3wByAD3wCvLYBY8wt5IqqJXu6/dzQk3X2jBlyxJ
+HmWaSdpcfl8IBVomDjHZGU7WIUbBEF6RLaBfvzV6v9682S1YG8AQdf11mNu5z/vy
+To+X1elsEKWNsa0KUuL+AzuT+2AYTDKqv4+D51cEWEZuCSe6aOHRcn5xz7UeKr+m
+DPH7+bVuVA8ALGu6HYOa31AT9J+S/Gx4Y1wJVWmlYzYm9kBAjoFhUZSIoM5w7VuB
+1ohLOnjv/ZKEknZE1DMoxDakA+O1NRtN+rdXGQkKLenOSSgpWRmGB26H+0oDpEyo
+9gJe5GKYHVASLF9FbAEaUA9uAKZe1oVHwVwVa1IwWzbDbOJvF7RxZEsABR+qQwRQ
+8kDxpRnOwFozSjhCXPv5lfpWnz9q/id+N+BlVDKvCZ50ZyAKPde5hN2IYsuGNgDt
+fIfy6IrJWMJdFzaPwZxO0p9rQNMUy90zA+23f0gS/0WzibbGUiqGD2shTJ2Ai/X6
+KIk5QXmKqgXy0xSSjaaioSgJ7dxzz7DwGxOdyMqI3qdueDvu8iTNE7XTVgk3orDV
+s+8Z4W7wxqlc2rf98ozUIYkHHsb90jnjkIexzsJ3hhZfksXv7pAVzaNMBx4UbzU4
+ShgPo7VgdcNzARxNBiss7eI9IcEtbuoB4KSFUfsGYeNfJBi7JkewfR+JAz4EGAEC
+AAkFAlSvBtkCGwIBKQkQJhAbYvaTds7AXSAEGQECAAYFAlSvBtkACgkQkbTl3htk
+Kaeqegf/bhwfSagi9Fj4o+ESRISf/0FNN3S6CbrDE2LYzJL1tZLAZpSSTfU4boji
+6cZq5xruoxtuEeqfQb6JtZPggfJTsSfxE7JdstezjJLOT1ONrjyDepoVZ6pNlWkz
+OGp7AzxHdxnG+5xg/GuIMV1mNYeZ2L+oJN36zxE8I5Dx9bThUf9T8R6iif2yhAT7
+yz8x4D1/LKsEwj8BciKIQc/d2kCJYsmwwpmgdKYC8iieiVqfwFbg1EfhKo7uO+iP
+TkBzyyeQdVM78HEdqdnSQRSzK0AGzQvhcQjfTDly0L5cBXJMWhFSM+n1RjyUZEg3
+YTxyfnWSU0BpAis2N7vUNvo+ouizgTFREACq2MVVT4Kva9TBV+QWnnxzXfowObG/
+WyM1zMNDq6PI5X1zAwjm3SJsDDtP8nGSp8uW1fy5lzqE9oEVA1Zok2Ac2MqihNhK
+SusElL+eS/3O305GGPMQ4RNrSkwIRopb5a00JZyiY7EmSGrU9OdX7XtkstniIXjD
+gClZb2XMS1WOvdtSJsca5KMHiByEePSZowhuZW4601aqBaLeVVLCAPpaH1kQ++B2
+X4fdR7VOa4j5kijL7Y8wee2skDLtDo4670puwuXdoOyXEo3E0Y7K/nnRByXAiFpt
+mEydczPC8wLlEE0XlhvTn5ErpkjVoBN1MhIId+zUwq5LWA1IEY9Kv1OdPdPGYuQt
+v2Dn7K27nwGbNDkzaVqVzvmKLhYTm8MPbjVwYJ+U8WwVj44NkFH27R+pqrJRKikQ
+uA0Vhef/l2pa2kL+UA24uAtqL3cBM9xjkryYHyl73Je632U7n1YQ8vAec+3sESnN
+OWbq9N9bMgKfewsNfzXgNEaqXGLTYtJOrhO4tJF8N4ZBY8nzES6BJOOKeHKKUJxN
+IGORlU/Mel82CtFsxnpdEoi0eTn7abVdC6oVEKHD2T/O6SyvDYQF4P7yETGmvaTg
+fyTMP2IgNfv4mBp9GLbNjBYe0amR6V6s9C/8rdoRjGilY/OP3MnBy7U5L4NfpHe/
+inqKIXe+0q50l7kBDQRUrweoAQgAlNoLcAg2mXyoM8r2pKrnNX6V95UD0PQ7cGoI
+D6Kpmd5bdzuaLtPsDZ1r4qlEZqfLA4m2JsHZMX+Nas2tuW2luE4kC+yi7T9515lD
+TGlMBhG3+BRGBEsa9G95pj8QT/PJYCYurPUVglShhzIC8ZGXdiICBt4MEyysJSkk
+XlLvuzGDTf9ASOk7qOJx8V1ILse9jB8L8os5TedyRYqQSUUkbUb9fT5zDDcfbKXu
+yZn9r+uzieiVw86X2m+1k+pK7LNbdnckGS1p5Vk8YyvGq0I6BDgcJeFZI1226KU7
+evk6PC3WYkt/VUAWbyg4Hz8BlR4oMY0UkmX1WMhfpQA6F1iQRQARAQABiQIfBBgB
+AgAJBQJUrweoAhsgAAoJECYQG2L2k3bOd3sQAKaUe6vV4HmntOaw6fxwc3v/e9te
+poYlpvireLRiD7RlOarcCa+MWdP22wTUXGRIczty+2D2NIhngnZqpC7nTH6F7KlX
+XZVIylJUygjnxihfogiPa8DEGNs2xS+ld6Y7OKxg92kJ3jP2H7QLpvq93evVxacT
+hJCgPT5AMmHR5c9KYRJ0XsMk+YrP8G9EnMlBb5d1XT7D7r4PF7FbeAWSozmaLHoF
+MOzg2hWpaBcc0O6gz/bpE4Ob0Voje254V+zNHEarxfoMcVNavRl0e9mgO53P8s00
+5dT+OxqNwHR0GZsHd/Y/cSM6XGyhq7xurXjmKW2v3tGaccOp08p27u41x6VefpBy
+3rd3KLMXO9mHst6NceyWKS6kp7ildxwIKlzwe1Do6hQ0Zd1XJY7RaAwonnc0YsrG
+2JGlCuv7sP1gBcSLsqx/APFwfUAi6lUqAcB+z00XToo6vm1hiLsmFoIDGUZnm7PQ
+Bf+HRxot2sKgtBn115Xx1GFd7V3WlXO4xz17jREjTL7P8H+r4MnbcjbdDmQTrO4F
++EmuAJRSPvpwRTc+6AMGUOSMDKN+ASQoazLPRCiEZgLBCr9ooRd4An42t+QP0m/W
+NY5p+TRQRfsrJYuwJueV9KRVjtAhgM82DDAoLl1AP0QhhbJBvq5Cda5RqLnb4A1M
+QW4OPy2Z6LC9OJ6ciQJHBCgBCgAxBQJU0nTuKh0Ba2V5IGRlc3Ryb3llZCwgcmVw
+bGFjZWQgd2l0aCBuZXcgdmVyc2lvbgAKCRAmEBti9pN2zrr/D/sFymiWDFQYq905
+vN3Vt8zu4JeBqG/Ag5jo8CLdvbIUSVGWRY2dRRJGrhlxmepwwX545aQqSiaxMcPn
+Pri8fSMFnFNWKzq8xIC5aCaM5xZnN8NT2V5w+2P/vZxc5/3Xa9nQlquXB9v57jzA
+IW1LJlYqvU7ILbJZly27sr2v1JOgogqzgEz4NAxCSwUoNM6OZPXnI5EegINuiWKp
+IjuGtIQoKeUma1YBOK1Wabxfe2Hzu1/S+lmAwZtqg7HCX38NwPuAFxElAC6vx3QA
+V85+K1TWjPnoBHs7wbxv8HZEUewqzSijtLYa/yLiyMVUr0Am31yhsgFgKLQXwhxn
+K/g3xTyU5/n9jgZMEwg4m3FIeoXF6tDyYvXj7RK5uBGSLvt5R40kWNiUk8pl1L+I
+tmrTUynj/6g1wuLrYWkEPTPSlyKgZojTx6ssAiDMOVHjxpWxVnhnAkXOlLMuccTP
+xoXEQi9sXO8ByrbR7pmVS0+sXNB6tXbtllBsPBoYPT+ydOKGLqSh/1RFJyds3QPs
+8szwosr2wo5TMrCMcIiXpYn/4n4hM61OhPLV0OjYmQoxHE0cti8pntFpDeWJMvDY
+eN1fAu2ZZiiYE/+4CCGe+cmYvO2XKWZMQHwLvads8oCGqTpeidn6wIpsNsVNg6li
+fKFcRquVh96y36GPW+JuHeeTGuN477kBDQRU0nUiAQgAnDKd8ETvAvTedB0SY78P
+dlo+c5PyMU5EheuNezG4LFJxigixIuo9ZW5Tl11dO0EpnyD3kKsJyIc/+FoRFuSn
+nZeF8rJwHcB81+bQIkfek5rc+Za/o6wJ84yMzdqwU5Ooc1MrVdk/3TfU6y1uGaPS
+envUUko07SuVUOLOBuseJ/odz8E2lRt2iJ1HLcbRXi+pR/8DLIm4U+PLYIKYKrxD
+NBln2mlB7wWILN6z7cicaE3HzZp920sqeSGfl0tdX5z7wI42EZcfqERWmrNdy0/m
+zXmRX/qohTuy7I06bwrv7asGiNLvAfvP6wnsRnLbho03Flq4Eed1vedcr0ZPtyaB
+xQARAQABiQM+BBgBCgAJBQJU0nUiAhsCASkJECYQG2L2k3bOwF0gBBkBCgAGBQJU
+0nUiAAoJEGoXY4qgRQz177gIAIq1D4QmR/Zq6oeVsYzH/5+r0Q0JM5f+JKD1a9Ot
+PNkV4Z3B6yWBA6vVIGBW1GB6QrV99dfxXAzqYvsDuDaPtNcsXNbK7iQKt35llWq+
+G9Pkfj9wrRrEo+rvfwWDJMnp5C2M9hucPCc3aLCUE5UX2uLOLtPOoD9Gt9mv7+nO
+gdFZeANnjfH1sUzsgYOcmKVX7KOcV479nkNT1NXeqImekXmkTFFmWIE0/zQ9MhKS
+ae+F0fV5Agv6UivRc51Mfs/o9azWal7HB/5u20FBPV+ieZXzsSnG05kvXkmdcGOa
+SrMP5+Z0U7Hfo1YtkNhMeW2b4dImZLfCZUb62Q7K4LCoW+e1Ag//YAbzGBVflkTv
+sEAiwL0tbjaqpGMcAWMWg6ilNnqG2Rvd4GTGy66PMY0nYOVy734o7nyySFy4IXbU
+H96Vepa0G6JzDkiiRRVhd3VPfEFK8qNZRYXc2W8ohQV8sQU/wRWpOEP7kzD5rm6w
+hc0h3qNeLbXjciDfS785mAjHwVWPOzYuPlxaLE0GaQdMMYPnn62incAgedjJ9wXe
+nYugotfbYofX65Il01wCUhv+b1CuSd3WlwEQnia7xk/o/FVdRoC+x6GFHeFpjyzs
+R9QJVdmUDdDdLyvtVOD+kMHrsWFolxzqtVeUod0+1byvMojTXQnrbmiZJgDs34eV
+MYMv4TQPNhqmQuV4MdzCS+mmXnbJjxL8Y2nARUh4ifDeJH6M8qEQqWGHHTkcgPAF
+EEM5kNRFkyYFFllNfHRiSnRB6uMMCLXtgmTyamboo2Rm3R1Y4OCo2wryFoGiIore
+kQx4a5vxCw41LDHHrm+XGUIFZi1n6AmHdoGcHq0Oi9nrK19nUyJu9mo5w4EU3Os8
+E2V1V92yIx9nNO0ZupRBXiqKp1iaD3nGQWrS6mKcG+vmwEjyqWer7i91miyK8S3y
+Gnq4zNoUoFtfaS2siTWNQgzCEjpmMEcEiVrPwfFI5OA21revRA7tR6UwtZn3DUsl
+axotY2E9dDEgDvRZstvyB4xWrtXK4T6JAkYEKAEKADAWIQTlyjMdRKuOTIBv2+4m
+EBti9pN2zgUCWT6rDhIdAWhhcmR3YXJlIGZhaWx1cmUACgkQJhAbYvaTds7A9Q//
+fwupqkn+2LCKKU2OeZxLksOvFyRXhBxndQrIVlyPo3xOt7sZkQvhsQvff1HA9n9Y
+Aj/4Drp+AyHwnWxibKXMnD3SHSrhKQ1Crp7RUWOBe06mqAQzBL3L5LyXqtcb2u8g
+06DZway5YCDiif3LlNIf2AdjxMcg3lqAXCMm1cGRSiuoMZAG/eU14lNQKMODLlge
+dBnmpzUc26QcGlcltzzY9fFL1WhHmHpLwtM0eIuT0GW/dQUDKsgKa7oWJM/4UCZ/
+ug19mHSxtLrQGMNQG5JKIebPkM5SgL42fRHDMpnFNtWf4vu9dG42i8oWHNGZDOdv
+PGnzM0I0Km62F2zFTTljGQ9SEDxV1hQe99Lt8TeKoch3Y8QLELh5M0FDRVet//z6
+UxYivwAzPpOK31vLsUMhjVt0jPDnRi4PJ1DYZgKVvsMbgJ2eD/NgURefHqfltR1k
+7UZLtYEhGRVowXWZ8JNR5xDVczsxMNRmJ6JpnFw8Rfco8MLM7sWROzmYn/NhjJ+h
+y83cy36vlmlTYwvFqCJW1J26Y5GMiJ9ilLNeObyh0YSZlTDYfK2mnQmsId/nm23k
+NpuZBZcd2tPTIGPsn4YhzntNG3gOe+4LMshszAUS4ZMKeXOviDRtES74zJ8hVhrq
+Q5rHcHop2Er1ZprNTebsJpSmkHhqTqYjCv666GXvgcG5AQ0EVNJ1tAEIAITXfZCl
+HumhwlMv4ut0uReP0SbR+zbJe/W1FLl5bu1UjNzVchOyqNdOGfmHN8nl/fc42q96
+yp5oFqQwxpbf0jdJkoYcWgvCppzMZ8GTiKGexKaIajJ4wPYX2BNQIbF1Pbe2WZlK
+XAMfqsCddXUkWTdASH9CYXCMB0N+TOm3yuhY6JlFZgcrALsjV5ULk4Rp0r7bzv/9
+FZLWZ+J4xo6IYhlNmpMO0ZqiQ3essHrYvKA5RCNbbQn2PhkYhocG+Z/p1DcvAEHG
+6Z5KwvEarXtUJkgP0Q8IChNU+S4qzxl+sqIuMBu7JmqBObejEepK2YmEaUpWfRBr
+0hF/O1v40XTKp00AEQEAAYkCHwQYAQoACQUCVNJ1tAIbIAAKCRAmEBti9pN2zh5/
+D/0d139sc59kAK7QGsZLt1f7/ZCZVnpI/7LF6AJBN67jDSJn6iFdXHg1Q3OgGyAm
+qaeVj366VZQowpCNMcm4LVb/gx3atgtUwO8OXnFdjV/CSIbAB2MBVVDImZAKNcu0
+oPnQGbW3GIcBEpJwi42gWP3pzX2kD4xS9mgtZcvnY80QoId2LuarpW27dp7dbhJC
+EWlcCVybcKsornY7EwUpOoySTSvj8/5TlT212igSUMlWhQhigt9Kv+uo9/tStQAk
+3KT0CFqNphiAn5KnDsWucuhQleCtDNEJLIOJYrpK0eIKvMF6P7gKj6b83dqOpZiR
+l4W28BE9I0PGMUQvFPclqPznlahHsNX9uICC+7dPBhxq/gZjKRi6r+F0UXLRnQ9D
+FIAvYe7azx/xPysaOoQPj74F6pNpsPKcoJPl2ZFD98PDQtNMd6jF+94Up96Bzw5H
++fNi14aPvUME+l1oZMJkYMhh1LLfBv1r717ODa8SY/ckoyfVnsPE/gs06gv/JK6Y
+hAhytlbFZlK4+yI6bwVRa69DiLDO75lQLyTM3BCnswVY/CH4NgeSnwrubYB2N4kQ
+Y9oiWGpMohStQRVTGS5McYwTF3/6CncIhPm/dfTyaycqs3j8dFTjHjDJQmpZUgHj
+c+/pd+rP/UtrJ5j8+8xsdJJc55eOXbSIMJXEq3mqKLyx8IkCRgQoAQoAMBYhBOXK
+Mx1Eq45MgG/b7iYQG2L2k3bOBQJZPqseEh0BaGFyZHdhcmUgZmFpbHVyZQAKCRAm
+EBti9pN2zvUkD/9y9aiwD5OE5QPhxbUnbF+AAJeOIrviSBLpk2zSqhmN5XLfbcS3
+CU25rQuu3dAe31HRUHJBMUSb5GhPjpgIDJPEQApIZxeYcu861oG6pa4/PpEOWWlK
+yOzE9JwRo6jreDIjOqvTVT5+QiiGT3bmeudX8anj+4Eq4UVl+MKQ4ZByqyhis0tj
+dx4lNF6NH34CZlXPfazkN7rWmGJr8tBjYbe0wkWSHaXSncx7y0A0S5ioOb4aG5CJ
+5v7EMq6lTs5JbbVKSRAMJFjvOPBAHrOWRU0hmahWXXRnjj+J8qCNTUHCBbWgkHuF
+KdsdLZBhDpVs7EIveUg1w6e5luyyD+e22nHpQK1GWywtjnaNHKIYTeDWFVTvgfk8
+oK2K6uFtKjGQHjPeJot37z/w+PsLY8YMJefyfubdoOkmuDtqtfD8hB0z8k5RNWQ/
+BsrPgtJWctaLD7OLJUCRrl/VJ/J8ytBAterbHvWO+lQFu134qV7fQb4BwTsrgC6r
+KVWD0tMrU2Ltc87Q+ULMgIn/WCuFZk0AiUg9BgNSLvgXWrqatw2YSOsZd6AgucNp
+FvvOdgVNg6LBsgj/SbNNQDnRyQuKhphktll1c24/uQixgPJNA6ojHwQiCqQRYhZJ
+iE6Y8qkkqk4De/4IdyBA1cREgLpyqc7njswyprPpVXFDBzisMHrAYGSiHrkBDQRZ
+PqfeAQgA5b+99btbBH58zRUpB39U8R8v+Qv544x2p239oEkJ6N0GNZtLMpaqPpql
+p8BUcQHdVqeq35UPSDl9fQGswIgylrppO3CEznYPYTCAuma0aSeibcPS6F9F/OlD
+PPFjYpEqkIWqhuENL6ZajaIB7H7zH/VH1VxdyXMWjblZOLnDngW6j+a7IfSFzWHN
+J8MmKeTEDEG1FBxofvnbzQu8pz1MJO5E/9DF7Gv0XRmcYEdNwrxJmleJ8x6AJKE2
+5n3x0RJO4Wf1PIqzkyohGuGOPih0ncOUzwINbpbRpwXt9+PkWzj2o4O8Y5KHLR0w
+MNm3MMzgbmk82gXxM+NDm70HXRMAkwARAQABiQNsBBgBCAAgFiEE5cozHUSrjkyA
+b9vuJhAbYvaTds4FAlk+p94CGwIBQAkQJhAbYvaTds7AdCAEGQEIAB0WIQTQv9a5
+7KVpSm8Unc6vTMZ2prbBQgUCWT6n3gAKCRCvTMZ2prbBQmcSB/9RYspARPjzMf1M
+nZwYHfsL78MynfO+8ccVYFjA73njemCGaGSaP6GHE7g4dbzoFPdnwLkF71rLF/Pe
+qs+34DA3wIR9IbwlKKhLVymCrqxAsOdSglNg8z+OXlS73omm9f3Y6Gqngge19H5i
+9mXmgJZ9LdgSUhNO2NNZh9K5A381YzzcBLIInf3HmUUHdHFoYguvG78rBrRlEqWY
+hp/yG/JYbR/UbXhrNbEggf8LOeiQtODMD7kAwDCVfQjfZsBh995K0ml6/d6DFobu
+ZkkPAjhMAiWnsLYFCEqnpYnwQ4NEyNAhiB+AM62jj0nivvpmdunk2kQNtwZvpIRD
+IU4aJxkxrG8P/RVWJZyjzlPKC8qzlhPoNXc3bAlnVkO2F32f51nPx2kE9MFMFQp2
+agFR3YhCir8Gx61JsLKqp5gG7QysKeRDGzxf08ufrhcjpKoCIECCNCBUaBQ9P/Oy
+4BzleM9N0pyw0CXV3ca20GVnH7DUnXAJtTVElOBISKjV5tLS0eczEAHt3P7A5Qa1
+YRQBzRHVXbHjhdudz0maJucAERUwntqOsojoJo/bQkhggxCzuHPsfK5KUgcaKhcu
+KUhZYchL7JAlUxKxbmvvHgy2me5LUCxmhSsMN/uiWNiftDePt+O9dXQlUu/rCCyA
+pyyKODlwK8+ga1tYjBa72iteeKdqnBV7e1Z+bn+eFpFEgg6EqJc2gTaWb9hKLz0n
+/cybabpx7SAUd/nMjyxtslxtNH3Xj6Cnq/J8KYQOcHyAy7TAXsYguPmXOOBv19sA
+nU52IyeAqaREb1UEzUGpwQIOM7J5bH51ZSy3r2SSgQj53fRTv3Su13uqIPMWWGj4
+JMOgozutbFa17SJ+INDtuQMbwP4oc1Tv5hEhLoZiM92Gpg6IEmZMvUqZ2jXReEd8
+wfnXSgHvF1JTV1NF95icrIO9D8xramrSq2UaSetp5FCWZQzTihz4PiDHLU4JNw5o
+QMtb3dHtVC8+jT+b489s8qvWrCut4DZpfR07FbP15de4X53lpy+YI3N1uQENBFk+
+qAsBCACtRr15KNnY3mR3r+H+Cy0C0Wyow7gScBTXx+euP2RoO9xHurphg7rvvGEN
+WTfOlk/qzj9V2+BbwkU7tZa7uRC0fLxodKKr+QTO2BXxRGdipkQpjdflUxeascMT
+EG6WOIsNfmn2+uaPapKNedpTE2bf22hHGlooDqqmFjdfFU17dBWSMKJ8yQCgOCFJ
+5DVM3c0/t+teShLkXmVzU0G/rKrZDXjKZUlS1B7t46NhgY99ATi/go1/hs3lNMQP
++gpc/FM9IM6Y6eWXoS3F6nTbibavVdsx/qig8sbv6FqoEi2cDx2QyPlXjLCVlt2Z
+1kv+KhXX8fltjmBifFgiq/H35cZTABEBAAGJAjYEGAEIACAWIQTlyjMdRKuOTIBv
+2+4mEBti9pN2zgUCWT6oCwIbIAAKCRAmEBti9pN2zokbEADescj66he29QklIsHg
+Wj83vl1b5byfPO4taMY6sQ0w2joGOlaS/QFZSTkSxc96xlnMJ1gDMc+HMR+REoth
+sBg+1yuz32dzvV6+eBYA2nccfAqhirKk0iijF3WRBmbe2hiTLDIr0m1h1v0LuCEw
+/kH7PseXabJ/zxt6wHBq5XbIy/H2z8PRaSEUsb9qZdtMyjvcgvU45GksjFmGoWnl
+lHFVuxQ24xcIkPe91NTa++PfuOwwRqAWThYM0lCLfJJuQbaJUlggPNrsu4FsD1GC
+9GIJT5au/v89TUIuNfEZjCnqaQuOYg4MkkHB8lyuglIv8ny3T46aEK9Mux1Ok7d8
+i77QVtezDwAch2+yPHxRT2596Dh4NVqP4+99LPqtZY3rKuzAJD25nhie0z9M71Oc
+4JC39vNi53XV3ckIy6sUg/yTi1na6/W2+JrZVLeQW7bTCTunz0FbpTt3CQRk3cMO
+aSx7PLo6x48C9Ph/It+y7d8E6qoFCpj/Lt13lnnRQdsdb8Lnl8X6QC4AK7vcZAi+
+7QwNjuSJv1aSk5cVz6IU6UD/VQrjGyfUShaPOf1nNjwFj5nQ+BxVIUlxEB55KhUb
+UuIm00VUrdB+WpLDse6X1E+OyvnxvIbXQTdkoCMtf6K59HhcavM2VJZsXYJzyZLJ
+WCVmN59H5VX6KKo9GpK9QVBS3ZkCDQRSZgWKARAA54U4UxYDqUMAZICVRTqD+lEN
+6AWU3EUmE8dwOKXEIuMs4iElToPRPX4dmHhsHgGQD0Sq3r8UPNpb+ZA7/FL5Qjpx
+Ifbx+JaXw2oIq4hAJHE3O8lJgiQMkfMoOSpu1dNX72y01iGYrH+RKsAMFXZCGnp+
+Qg2McX2su7+IwRl8TcHULuJ7DDhrmDd05dBQZVon9DMoRk8oZeqzE9pZcip5SWh2
+wqALKV7zdUV1raqP2xq3KJtxXzZn92w//CsQufA5gS54Zw3mruraIUj/Id8a/Ogs
+XWG9V8/BOSEVZmaKPzSzNGxtYR5IlER4iaqN+kaDGqxRIQn8qB8L/fB87nlldVcP
+GVnGRzLhyLJE//3iNp3FJ9wc096Lt3ksei2aEuDXSrVCqlMk/Nhw12PXFIC7nQwP
+3dmFIuQctmL4BlxUTpio7ajk41KruZNAUwDo31+fyFQQPs9ul2CYCNNW1orw+TyO
+LYbSXfPJ19vHr7uksDdHr/3dU0+/qxrNFjIqH1i56XvitdvOybkPlqVYX/f1H3HE
+Wy+a2F+U0KcWHTMyb8tU8R4p8xPoxHBJGNB1QYTra/3GueiVYjpQEIvQIOO0m7Ay
+tQoPAdoTuDKBFbqZARfdkR0RENfkYZwvHB1+SLxPQm+LWD9w28nf2VFkLrW26wqG
+iz8M7up+vMiOCb0Ao50AEQEAAbQbUGhpbCBQZW5ub2NrIDxwZHBAZXhpbS5vcmc+
+iQJJBBMBCAAzAhsDAh4BAheAAwsJBwMVCggEFgMCARYhBKy7QyQ5Ot41Fdot2k0e
+kA4UwcwEBQJbSOj4AAoJEE0ekA4UwcwElYMP/RQyT71nLmskNNj9cjWXRASrUGgH
+cpW9u1j/fJ6QuGfbWF30ibewRN0ff7OOg0V8iI3BWGOdu0+xb+y+FWC5XP9ddS41
+Yn1FSDjMRWM0t1hTzjQtZS0e/5F6VM1mE3h+EZnhSkl9r1aKm5cuv9g2OxWulzDA
+69Q2/K97QenJND8KDMXX5neHc2bGnVbMPlhv3RoyNxajSSg6alOsrjdBEHEnsHI8
+rzQ6UGT1M0unxwsYCrt/AD6V09/UStR1oyWRE2WWCcHqS+MW4JXas40GR2JtUhnT
+T8zB3OGhT947rrQ2fbVOsz4fOhonTFrR+aboTQQpj4XFH8HERk/wO0XUBDuAGavM
+mnz7T8wn4CTEmiBB/M5rBXa/TMtI7VweZbK2Hj2ye+0tOnx3aRQbTO4C+if4MepM
+RsKZUYufRwQYnaBbDOs2B8/QYZxnOFT8+Y3dkYj4Erucsc740cEQbSuchB7IZq/K
+QAzY4DaUUvsucWObXQR5+PyOYEmE3v047Rh/pSH5XudrjPtyBlsmo23cIuBJK4rj
+c8C9PTEJnJG8MqoHZV4U88TQVo4PU11BURAwD2ZV0MCL2HqWmbJOREm4cMudXMq2
+rmCxGfEgjUQOk0+VOFyI+W+M5Etg3jWIrdB6pZbKXWrVa4MwEDvsAfvPyRxXsJIG
+R1RVewxJpxlP6upstBxQaGlsIFBlbm5vY2sgPHBkcEBnbnVwZy5uZXQ+iQJJBBMB
+CAAzAhsDAh4BAheAAwsJBwMVCggEFgMCARYhBKy7QyQ5Ot41Fdot2k0ekA4UwcwE
+BQJbSOj5AAoJEE0ekA4UwcwEmtsP+gP0nC6dtrEyoLYGACFkp7FNnuCtO0IR6ppP
+XFTXaGMjR3g+N3+s17ztdH5X6CUS5rIvDJQGgtAbqDQ6EgQbr/tNBKu5mGYSAvSO
+vjR0b6tmgc3FYl29tFjgpeUJRPa/nNdhUi+TY7pbEv0O9+gGD8lKoFNiHBjEooqJ
+CU+CH96uBy9n+BI81Xqc8cuAQNKMZd5TFLmbmwAnMIt8pPOHatrorJjfJM0Odk3G
+a5CuziVfJojDTgck/tFprpr8MSnAiIW21xQVzfRazFGUA9iRF/r0gJKSyQuZj9ke
+Kn8fFVQufMAJyBrsjykloaYkx2XPccB/isjFYEYp7Aa4qQASjCGkffNuYfo0JuTp
+8k7IUHPv3sb7TodYoWFgHh8z2Za6i2I5dKb+EgWs/eGDOrRyOdgbxgqJc3KpwMLS
+JwmagViQ+PiKcRV+uGZ57BSMsTwt1vT0iKsQhM0IY0htxcYR8HPdaJnvLkD+wmNi
+DAXejp+BUZ8s+7W3VN0gyktjrN0IMsi420mVOCS64IKBeb9qCqO+IE4JDny79a1q
+Kx82jAfLOkN1KYy8xYylXJTypLtA16OiFB9Tsq6DR+BYXE2n07CEOko439HGZJIM
+thLamu0JjGAwPGKqYXyVA0pwFPBQ0OXa00H6qCZz9FpVvRe1c035x/RdsmzqgVu3
+8C8bGPZ+tB9QaGlsIFBlbm5vY2sgPHBkcEBzcG9kaHVpcy5vcmc+iQJiBBMBCABM
+AhsDAh4BAheAAwsJBwMVCggEFgMCARYhBKy7QyQ5Ot41Fdot2k0ekA4UwcwEBQJb
+SOkXGBhmaW5nZXI6cGRwQHNwb2RodWlzLm9yZwAKCRBNHpAOFMHMBIaIEADH1dXX
+LSMW2SW60L6jMQRUNMKUKsekEpTdrzmfWJng050X6/0Rc2HGqbgUUC7R2w1bsUcl
+5RQuj4kcgnXBnxB5XfN41M/xJlzZOgh1yLEowyOBrEV+F9z+y/4IhViFP26CBujY
+StS/WMNMl4SwWlPWfLWy9rCSuD4DRGjZXx5tC73os3D4Vl8KUjFOg+yPefPsawjd
+KnPyAUv9aZNJ8MTG77xOAFQcbX/bSMjlw18s0hAMAHZ/3r9OMjYSz4gy880i1maY
+6EHfx75Tjyow2IqIisIB+NMkH+Se56FlMRL4636Dq+GWl6GWXhoRYs7Nmre1FPCY
+Fcn7iIve86BnYPcN+iNFWj2tvf6yXiYtRJFl1BL4xvE/er/QXk3eCDpncEf+4Q/x
+i0HUc48hHdL8T52L1oePUbgXYtH1fZn5Hr6pva53mPkmfY7p+NoDgvktVAuuviPc
+SlZKlzut2Y2QClvlGZ8Mle18m/28w44Q0Oi/i1Wg4i9lFGuu7jFMaDARnySOaU5u
+Bt8nd53v9GudYkW0aNQghsRgKCsXsGPLirJI1rObzRraI17ETTawgGzvYhZaz/Mo
+HPlFMNtGwGr53uewLGw58b2BEQaFIVHtadz2iuoXNPCmoVYNFNAf5mmXoUpJnt7V
++ZUnP0fOcXAHuerJ1ZbDDKnYxhEQLAbHCIyxprQnUGhpbCBQZW5ub2NrIDxwaGls
+LnBlbm5vY2tAZ2xvYm5peC5vcmc+iQJJBBMBCAAzAhsDAh4BAheAAwsJBwMVCggE
+FgMCARYhBKy7QyQ5Ot41Fdot2k0ekA4UwcwEBQJbSOjqAAoJEE0ekA4UwcwEPpYQ
+AJgBYmNAuP67KdxC6HjtcPe2/hQcFXSPYi2TJGfgJrgoMHdXQW008EPz3kaXkzxD
+pda9bNpUOcRL1u9riO/4V1f+yFvkZOrnkEhw4ebrvip4sbufvb6ezD9S/OuNnTdi
+eraJlltStrq6HXaUXe8VEIfqOiZPB+3DEbcL3AHw3dfEZDYygNFLji292ZpEoYp+
+QlKamdEcebOYH/AhsQUaEmJAACY/TVMgdWio04uCDZjicvBt5+nHsN7RTfTfuVED
+Z5XeVVfOifA7D5rNBZhiI9BjsUzx74j/GNP/e8kiMEcHAPF9BgzOXfK3sTQNsZxR
+zl+rZDt3ltg49N/5BcoyTW+SA7hM3U/CWqtNH0srKPkuKFhAbLr+mVZyA8AH6vk6
+iiBmOAO6MMzcV7ru2wwu/LtgEHfdiq6XYoFVjQXkH7SanNPpmyHFocc06gLmXWNc
+qn0pvepKzjjApCs+KaHx0G6DyGeCno1qC6P9huINs7n/6bLSyh/JSHnuLddJBiRS
+w8DwYy76WzPGENjkxKv9iVO7k3S+XOGkjtqaJZFMWOZ+l6VZ29Hr9ezYJuDu+CBC
+Z/7grbjnkE+4QmvOlPNFBHsfyFb5RoX7kZ5Fmtb7flA0x0TUgaWtjocQxGbZkYrE
+7xdySqfzBycPqLCqzmDAyrREoJOLc8HD5A8dEZNVYeTbtChQaGlsIFBlbm5vY2sg
+PHBoaWwucGVubm9ja0BzcG9kaHVpcy5vcmc+iQJuBBMBCABYAhsDAh4BAheAAhkB
+AwsJBwMVCggEFgMCARYhBKy7QyQ5Ot41Fdot2k0ekA4UwcwEBQJbSOkKIRhmaW5n
+ZXI6cGhpbC5wZW5ub2NrQHNwb2RodWlzLm9yZwAKCRBNHpAOFMHMBMFPD/0cePg3
+HswrdBK7aHJXrD5j+5ExH6W8VeRRH1Rk8bEOgf9lWw36qOxZF4Iz0kJZZcbsISfj
+K1/SRM8bQxofZRXVvdxpXOl6lOR/aGAL8kd/TYh/6H+TMFz5WZmTvGP2Jvgltk2W
++9I7n1xTboZ3GRZKqBzA+aAtceLWx/ofP0YRlduTIQboG0/WQsPbKORPKoM5syyE
+uAO9m9ZbvPJRRHX/O5yLgeXaQzGvkyHA8qYvYN267KEDVBqh9OSo9B5SOibk9LBs
+Kq2Yhl/XBUM5o3m0qml+sdwciNTUtQRyI7xBIjM7z1dGiHSJOo5DypUtQ0jgTVi3
+gYftu3lYiuV+FkWrhtTVNjtrKT1Q/CE9LtPx4RLhfuPz8yYRRhLunCJSriIWAHTM
++QIdBXCH6Hu3dig+W1gPd0+3+5oqJCyZu+Hk5c7O8RyVE7zhriS+Zw2oLhhUyUSa
+E9sldEpzwK0oAFp7sOtQcWR3Qbh0lKrK+Mh6AQ3f/+uUVJruqK2CI6D4SMP+BIl5
+RUOUGUKQ0qNjeJ7vuLtCkma1bCpr54B8S2uLGDe57ox6+99XClF0cv7WANWLKTBt
+gOooYgtTwZvbZCeiMZLIBN1qjyOcE7Hkv0Fjvsgt0NIz4vcN4Yx2AJxjTMECLoFs
+k3HkMWti75tQeCGMCptMMJ02yrUsTv7zOg+sr7QsUGhpbCBQZW5ub2NrIDxwaGls
+LnBlbm5vY2tAZ3J1bXB5LXRyb2xsLm9yZz6JAkkEEwEIADMCGwMCHgECF4ADCwkH
+AxUKCAQWAwIBFiEErLtDJDk63jUV2i3aTR6QDhTBzAQFAltI6PcACgkQTR6QDhTB
+zATWUBAAmvJG5cz6hJa9RgyQGzODGWZi2dj27u1Djjz34wY9xifqFxl1/s+EEZ6M
+L/i+UmIzprY++4h/NgoAQGDBkt/EkJojmVjhwr3VHRzoi8vREMFkyELi4lPC9GmJ
+QP7wslk+L2zEVUuGLbGW8YXAUnUhwmMk6DQrabgubc6W2xL1od6TQZw7CUuLtiqz
+j8/1d8Ck8lGjWwmSF7kPhW70gP1AK+CHIRb/wOVZzhK3TG5ZYF5QUGPF2lL6yGJe
+6aYsxfn8gV5MhikG8idbRxIDiSsbvQNeHMkjVGTnAdz+I6t+x+rhGko0INehjULY
+JroxAmwWTH/t8qFD4jHRapp8d8j0sCCxziOmHAI7bi8xQt6slh8cHkmEGpiIWued
+SaKLlcYeE6ZkNvo6hKqJCh6nah54fybmlUD7Fa1hCR76l4FSPNBoGo+UIuikob1s
+6SEetzQa7ZNiIvkCVEoMxXWHuNGbZUjec+6kN1mfTjspJLtVgPo4C8jL8icZ1TNh
+0NomLpjQz/0MAxCMaIsURmv4Dn7AdCwlW/jXEiR9gt1cjGY4xFZ6Nfcx1t906S7r
+bxb4O8BtyD9Lmm0SPLSRZRqlr1eyX7sHWuCFClxO9i4CD4XKQ+obU7veo6a6xTrn
+Ylh8HpGP/spY4qjyDIArvt8W7G/XJUmAUPAiloOmrMTaraThvcK0JFBoaWwgUGVu
+bm9jayA8cGhpbEBwZW5ub2NrLXRlY2guY29tPokCSQQTAQgAMwIbAwIeAQIXgAML
+CQcDFQoIBBYDAgEWIQSsu0MkOTreNRXaLdpNHpAOFMHMBAUCW0jo+gAKCRBNHpAO
+FMHMBPQDD/9mNS3hjVL+DG1m2opXB92yyVcg4GARpVmT9lRcpYk10MasaDh/plwt
+9cEZ4OKYVOJjEO6WWqMreBb17djr3vkB9jnhkTUyw4Y4vNcmdmlt5NnL89n4Eq5x
+m0TYMUfNyNoZEdtRFcH59WD9fk7TUhhPS8JrPBV+TmKrIlpuPXx4Vpx9K97Pq4rV
+9TpQZGGRcjbwSNKecAdI0WqZ0cfEAWMHVq/CPMQzmBWSOjrqUw5JiPX1mQN7RuWr
+vpWXDiR1s2PYhVI7tgaz5nV478OW3MmmLlz5to5z4C70FFzI46ylw5XGwCZPNrIO
+rezTZC+4GGj98pz583eg7HXS+5bt2FYeckClha9fs5mse/vXvleA7AGs9HoG3G8d
+3Nt9vCaj+pI/VTbOp9+gvtxfg4DSriGeNZoTQnzbkkVFQe/n9FYNtsco/MPugGc4
+w4fBpq0AIJw66raQsFXu/30+aICb1nU/RgyksXLlL7oQ8fyZ7xprfy6fAAsuvfu7
+lI85gaWs1NboLhP8lLDAD0/rg/Bu0YGfxEfguDEIrTTmN26+4i95TiffCqch54Wf
+AQtzV9CIkxsmPxVrrAh6HEKs7gEBFIaNew8L05uUzoQnwcl6xOcbGSJLyG1a6+6H
+5IakDkcnypy/LBOPrx8HyqO4fOR/4GJ3oV7hm+e3svnC52hVdmccr7gzBFfBMHEW
+CSsGAQQB2kcPAQEHQCX+QrQV1F1aohqbdcUODZcwbamprUoLIXV2nSyLiSx2iQJ/
+BBgBCAAJBQJXwTBxAhsCAGoJEE0ekA4UwcwEXyAEGRYIAAYFAlfBMHEACgkQURBO
+Zo3QRIH5oQEApL+jd0x4w476elQ3F0M002NXd7FKouy9ageBM+oHS4kA/28oFXVc
+qDraz9f5bFnwX2407CDLKaz1Mp3jijvRp6IHqf0QAJGX5TZDr6CdERY5cOTCs70y
+02q2sVON6dxicE+UAxOL1nqpef2FiOobu4e+OAGYZngm8oNQBNNXA6ETc+Ug+zUV
+P5p3MwkpFAC52GWF9yqOjOaZuQx1QZoWw/Whba2ix+rZSSi1zPaxrL8iQpe04Mt3
+IFwmLlxYhT2Z9uDF/lotROTW5PIcWmt2yHYbdL0XYrGp959DmKNGlprgTBbWTeuw
+aQUw/SOk8Oi83qv+8YdZNyuaxLz3qh0VSxx9vQnGMbslDpi2+hXOuTJuMVs7UtPt
+sFgZPOQIWdNC39otlpHQE6z8ezRlsOX7LFf+1CFPkPjbqrs0D4fOEr+yilo0Dj95
+ixnCe1lODykeEkwQE4XdlrIGjLOdi07Q/iMLTGQDL03PVrNXt1ak8pTJI7SZRsQu
+lL3+Tqb52HynBDbuiwSyjqdCGAZ/oRChWrW6tdg3bos9YiivPgIswCfry0tc3WGp
+2ygCbWFvgHZmxU8nwusrTjciIh6b1xwG/webx/lBPMiKyRC+F1wLqdJBDo7bNuo3
+Gz8W733vBRw+DWSDPBebW7kk/k3JH0jX1MCdv+zO5wuur8DdqDLLlX2mZOTRZVJc
+Gg4jQ4EJ29akJv5bSwa1bFRXN0K2uNSGbtfHrcbDmg0aU5slv4vtsxeUFhOEcxHH
+CwCfgTy3SEF+cSlWWIbMuQINBFfD2hsBEACqLMDpuA+/9VWscimKTs7+k0BiuxfP
+wNJAYYznAVNFt+GE464v6YJNXpKt07BRzDpuivaDPobqtFXc2nvBHcCUOP6QTUP8
+9rOC/bw039B+KRaPlQJTGbPKL/kqIXiK5ihjgSXdHDCmzNFHuec07pWgBMI+LYfZ
+pKIHGsFVynIL53mmhxavGTCSzJrBd6pyhoeCzMsIZAq6pZ0HKjfVWP7B3yBJfazC
+r2V/HkOmKV/vPJT+oflE4f+PP5tTuvEWE5UXM8VXnROMcxaNHLB43Pbh3A5neGgF
+m74Ha0tfWZHrZYnNCFRGbxp7PnfbKL+tZ8xtyQr1pQ+x1y8Bkxj1MgiOj55MmRmj
+xlVJ+L6zyB5Tw7kqsaBHiSDBWUz6SJz3pFD3X3GPD/nkNqhBhSzFM2qxHME3CkK+
+hU4jOEkcZpHhsjL+pXVudGNHIByDNj9lqP7vswg7cnGN7QIPdpBdvgcFg4qZS93L
+sLJlqhNDtCwd/Ut+QNT6xE51HflZ+3/su9FEjUFKZMEtAu0TDoaf7iV9VyD84wjL
+WAm1GVXpDh1/WuSUBifMfTyHXyLN2y2Ja5D1mws1g2ywzHBW/2e3gUzYSd4JQEWL
+Yld0kZhQ5V/Y9Y19jDpDUUgxkZmb5dnHRaGwmyx27zReKqN5NF2tdeWsUMibZkEQ
+dib0n+WnzuJMYwARAQABiQQ+BBgBCAAJBQJXw9obAhsCAikJEE0ekA4UwcwEwV0g
+BBkBCAAGBQJXw9obAAoJEBPa2Zx+QVGcxvwP/2aIUD60sKExN2fLXj7mMZ/wWlDn
+CdqvTGD7lrk6r/fAQcaOAgajCMEXOPZXlPBhdQ4jxD3FLs52CNZkcwzXMbspz1lf
+IOk2U1UGhmnAyriY4Uf5cRu2RPR0HYwOBB0xr69SIrsmlX4pf1AnulE7CIY/oPBj
+B2XQRQ7ls8sMqmm+0TxRysaosHGu7Vbez5iKBm3p0rEh8TcVkgMivdUPue/ip+mC
+aDCfGeAiXLXWtiEiwaS3Pq+QzHhZtBvShWlc3k2mCFlrGQwovPxY5SqGs6Qwifrm
+nGSSlyaAorDZcQEkZe/HP2/qXKb7uBD3/r8t2OE+BZKwJxW2fIpaO+u8k5EXSDzu
+xRqSNj3wYUI2+WNQzBmAyOZ6XBX4Pz0xZyahtXCzJ+5deqCnEtJI1HdPSvM7STE6
+s6BmkhUl8weSAD+7v/HNPWvQXYFoeGFeqvoVOCqB7jJZUj+n/eUh9PxsOtwdJlvd
+oODuQIYyzuSapm6OPnBKg+v7Bp39Ym8j5Nfe3xqg+O6CQVH/qx3NoFrKfAaLKGsV
+++jnf894b23Y/fgu84Myt+Kn8uOrO6jbBwiWLkgn0uzmO57bi/6F7aMQwSxcMcAY
+3DhCoeXkeYq0QRZZd2raPbA5r278wPXWg/U5bHenGYX1COWlRehWqXkqR9ZJYY1h
+TT0/WSAK2ZLCGTK5tDYP/iiHbpeWlZhwgx9JkfmgL+N5XoAW6oJna3tozS+xVM5p
+xTaTNO24vnQw+XQxkiCFwtf81chd/oXhjWpLg/K1vF0AWGomN9yS5dtKtlWZ0H/3
+KeEGkKf9iRp8j1bVNF6mBhb8Xl+nKLWiqE/uezx6OYBFJuj6WpCgbmaRUbmKpX7P
+++JuOosg0n+BzzJYAIKP4+/FLL35qSpLW+DuWZaXbvgS/OgjJUL8AQj8Nwk7ViRy
+hBRwSAvwpdcwvlAH1VfTHfpQ8a0jjN1Nzf8Tr9Ijo8NQnsa+5y6Pmf6l40j4C8HP
+sMB7SX8ptFig8lnBRPtzEWj54/WtXJwGRG10XW4rdQU5hR9Tufc+WFuRfwdLgrhP
+TnKGyVG9zOkTd9Cl4j58tEsju+m4HNkUN5goouvdxHSe/dmA6cQAWf6/nhJ/uSM3
+aJPSUOtZwPZO7/NzsMgkwZTLXbehm+9xWMkPRt1QT7V5MgfxnxhVoIeoPAEYBo8t
+0P2GXVMNZdZkJPoViWGOei4iPE3rj6NBynIIoEZNDEJ0OQOUe6Naq5AaG/a6wPa9
++ITzKY8VR5KMf3XgcKLBlntyyxTgnHY7j5VrhxU3+mUrnwg8LIN9Sx4oWDks/SEB
+7KN3KGjSgczn1k3GIJRF8BhYin5Cuw/+aD16w5gSHxUhIgwH2BbM5X8eopbp/csA
+uDgEWlPNvRIKKwYBBAGXVQEFAQEHQK1DtStYAOrv8CKh19A+Grx4WJusGieqP6kN
+cPu/o4dlAwEIB4kCPAQYAQgAJhYhBKy7QyQ5Ot41Fdot2k0ekA4UwcwEBQJaU829
+AhsMBQkEBFIAAAoJEE0ekA4UwcwEvIoQAJFgOFOsevc8GDLKxSV7hR3tytGLlfss
+Y9I+PGOt2Yi6S6v7dP833FLRS/TnoKlDof3A7dw87hNy/FFelm+GUz2gWCIjGEk0
+yWqvHYfjwryTug5713cmtKBdLSQ9vxD932ZH3mRcWNmaN5909nh8ugf2TCchQ5JR
+KmGSthukPE8tHk3T5hldlRW87V0gIexJoo70RrvPwnK4dzYaA9F8SZ+rFGYTpTaK
+A3/L2CwdPIDvi73PXAkieHISOc/u9YqPR4VS7P8zD8ckCpTNZ4iAOh0mFJsozMnT
+MGekw3cozJwKxsfWCwUMyQasc0d8Yx1fJIdIOrJeXvIWIFZQrhrwDqUVPyY4jhnK
+EiL7TQIrWuYKopH3AUmKWxA9T89/Oyrsjqu8ySDT//svOF8b4VUc66y8vR1s6oUh
+8XA7sCj8TA04yxi6vzux63xQIA4DUljcUyifcr4N/RwKcv4Nd2Yxg4zY9Y81/raM
+lvEysnKqJRBpExzUmO4Zgap39aRRwSCieH3NqYkZDsVsUrcAFcU22ir0wwba+S2X
+LkedTrTB7xrM5Tdzg7EjaHfrwONOobWBn8mDEUyYIW+kuwZxdS6h5wJZRXJ6p0vZ
+xEpxpe7WJ5r6S0oFlyPU0EYVBMFbHuG49Ml504sGWn0uwLuJJZ0sQ1zYU1BgAOnD
+J6kx5FwtQIR1uQINBFpTzYABEADF6zFzagbZqkqKDYFES5aEBZUuy8dDZGSr8zMB
+sGZ5A1OJHosGZCnVA7w9265rUIwPimQi7pOC30chLcfK2bkMFZUt4keC8wHaY50c
+VGIc4xA4MMrdkg2qSBDMP1H8+i/jrpbYKSq2dH3VmdFFqxvJs/XYh/ZU+dRMvfny
+8SULvi4Gp01P3PLIcn60WW0Xt40TbADl0ueCM2GxgCbbWjvt09MvMwmj506JewsW
+bUhFDjJH9W0196giDNbSi03vg9nI3lfpFC9Ao0mNcIobyffL1E6ounBeUcA7yoi/
+QPQTB0dtNcIvtLDLCGWTw/Z42FGJQFLdHEf0cINPIL5qiJlCvdVsHhVeHKA5azpB
+TlhvnHH4eSkGquKI8Q4PIzGsQM9PG/Aa02XC1hve+kRvKWMqFWjoTfc6TcI87oEV
+qMa9eNOZIk7UG46Y4dRkSBSwRVLanH3xRK+zPnMJ3X8ePGL6yna8W2XcHznR9xyN
+zA8GoNpA0CX+SyM+xpVH+ZHEBupvArqusqrS4RPaOkZ2uurUxOJ2N91Rj06IydlR
+qWkhFX0E7rEURnBxIZrMhVWbYRufn1QiRLXOtfZF122QfLx8L1oPakyVG07TPVnh
+gcmcDny45ZeUnz8V9/gyKDopT3OXdaxqe9Ovh052QwWg4HtnGBdlhJyhwQAy9yH0
+gEOPtQARAQABiQI8BBgBCAAmFiEErLtDJDk63jUV2i3aTR6QDhTBzAQFAlpTzYAC
+GwwFCQQEUgAACgkQTR6QDhTBzAQK/A//eWA/Xd2SRObD6CEw7fhmR3J5afqifNiS
+vxQbbkZ4SIshrEs5wZF26IUl9u8uHeSyCrIB/vB4/NmCk306ATNQ1jlnQd+OKVfi
+C5Qy5Xi1T8o5W+chsy3PCGvdzG2vgvACa6vyKB6O3lV6F+WQ6GAd5NA9536vW7KW
+AXfwG7TsTcPzmRmXYb5ZpfI4g9gLw9ih6OzcP5+C2BDppWoQjnVj1+t2lw0SuFv1
+k3H4MnhEJCcTCURu6I7J6gOtNW1YJB9XRJX/9G1RiEV8R8mTUZ1HTj3T7nYRZgna
+enRsnIs3l4++dFQgGJgWctLcqlwdBr0Vc+Q8gmbbQo+RZImHXByIp7Isu5GjNOaa
+FpHS6pnMa4YWy2Zb1w4TFDZtKQLJFh9xlnm9raJboLkUH5IMZwM9GSqeSe1baXxm
+3Rd05NAhqaB5C/3e4K0x06p2s/FTlnKKIXDWjE3LaGR22yiWN1hJ0UjMNp30IfKW
+XjqdNHeeuhFkh+LzlUW234gAiPp6b+S+/Mad/qMjsuYVdvEP3NN3Wa9myA6dYNV2
+TiFKH968gp7dibCzhq2VlKGDS9EElDxPyQ2Ksl8TQInWB87/OQIVcoSuyyW/5SCQ
+clcWTJiMl+u7iuYSVqeRRFpW8cXNqvBQvWlVh6xS06sdbViqUUAx9UHkDHznIZB6
+gWTwHQbPYW+4OARcPp9VEgorBgEEAZdVAQUBAQdAjaETDfBdtd++tzqzdA+vU1v7
+packRBfYKecXc29VuAkDAQgHiQI8BBgBCAAmFiEErLtDJDk63jUV2i3aTR6QDhTB
+zAQFAlw+n1UCGwwFCQPCZwAACgkQTR6QDhTBzATTuhAAiRBqsJJScqkUDI3sNOTQ
+TDk5hfAKsTYmMYa82/gKrPpEuF5bEb7G/YSW2Sw1vARB/wo3JC4GprZqVkMFFZBz
+yOI/TPT+ggpF+1sdu5WZaqIrpiTtjDF0VYB/1K54UNx1SY+98IXwmKkN3t3k1wo4
+2mM9XzFtQAS1GEP4TL8W+Q44BDql2DIigVBpI/IM9X6/JLNXzZ8io5CVbWbBUKfr
+ClVoJvzIs/Ns4hzjBYt8A2o7rl9IIdlE/6cYpAjOsJ835cEojhQh2vV7cpYY7qmV
++IeYQtofX6vbYZzc0qIZgFDy4D4l13986R/O9wzRyx7FKDuo1HRYQjgzEjRiXSCX
+0vPn3S6pv33IM628+ylD+Obw0Lf/UwGptECIKQiMJeJ+KxauUdhupJqmDh0BI1QW
+6A5WKUHLytCjEBJ48HVqavVupXqzILEO/xFBQr5CnTXMt0t3EyMEah09/gNPLhHa
+evLxehC2xKyzRfnuf6hdypofbpTn2vQXGUsOtvtz5aXjxm9OSmqCZJ7Q6DIMnEl0
+6UYzHAVGy0FDOJKn5x/zBsaEaPUwVKfuOUEof4rK2p+iEc97gx3gvCcRJ9WsWG0w
+wfvoXnilvwdPioElEE0MJCLG5Hyso2C+juzigcyv5NYjt8evcMZzyItsg6pOTjK+
+vOAvuIlW5ipRqLT95IG9zfG5Ag0EXD6fgQEQALK+iIAoBZ9Gg+pLcxBoMC0T6w0K
+6iEhMJTK6LmKRsVO04APxpGcx8815GIH8HrjJcblQcb1FEzMUY//I6Yj3VM/VIuC
+rRxByqWu/siW/A0qY6uJzie2wo383KkCek7afDXOzokJu4gvFqV+sMeX2y5jfJ0l
+dpLeeT7JTCaKEk4+BDibNbCoO7E3xBeqS/PRWhkrKG3Xo3p8eHH8GcnbRgJAtC1I
+PMXPl6uynB1JJU2XfBn7l3kYbDsqNhsdxMrNbsVeUZHR0tBNc4lxtECbmAJnt9lo
+8Tjid1fsB0Zm/DdQr/zIdf6hbcdFOljnfZCW7kg3fJ6kUnFVF9hy2dMxvVtzq0DK
+6da6GjYIq8NGDJbqey6Rhe7SXim0QpALPerGLGKO8WWLK2ImITWcUvDGmjLICH3P
+5leURI7OiaX9BRWcY5IRVDONFdGOE8WRBBT4vQoSLd/pPU+HFRiVq52D4ZlZbTxg
+svdq2vmbhdapI0xVw+P86qPewXbbVpJod2k82UanHQATjMSuQ0EdBWopV7UhQDHi
+mhe64rxQ6ElQOs+cqhFQGgis4oprx1seWW0Ur5rbhrOESKXsXZ9k5CBWoLmn0/RB
+tQBRVN9gZ1WPDIhktgCkOLAV0u8mL1SpeVv1rJmRmTxUkpGip3IwC1dDJI2q5b96
+gfCNJLyIyec1Fa/jABEBAAGJAjwEGAEIACYWIQSsu0MkOTreNRXaLdpNHpAOFMHM
+BAUCXD6fgQIbDAUJA8JnAAAKCRBNHpAOFMHMBJP2D/4rv9HtPbxFRmMpAG5v7MsI
+kvLy37GNdXd30wgdoyFWO6S9cQAPQaU04xML+RBziXF3XS/M3Rs3Kb3lhPhqS4U8
+rtNkzKNc2aoD5jcZZ0LLRbhfjeRdawPgwoi8lHhuE1zP2rBvBZSJJTvIuEShuAkW
+rk5BLs1gOGdLvdY01Ijr/fZzNDtwRAWFi4u+2hlKJIeTchK9BmBwIAUglUwMVEYV
+GzBwf7HJ8t28+f3fr3A/RL/5JH4DBJNzRNkqOMl+EUmPtciiCCCbieN9secUrkKW
+4626V0zOtK88aLry/8B+6AEL/ciYrYN6QQTY1eO+Qlv2NBbjSTWK+XHkYKnvsEY0
+n9a8vozc4IfYwWYjlBFw41J939NAvdgV1DyuaIeahCpkmhEqCk6SLS0W4hep6i+V
+quZg9ya8igsLCqD3QcXZOpBDsG8Teo8zA18HNjCO+H/MF/uXBZTME7rZh1OwmTOm
+urZdLFrE8H76GR823i2QxZ971uWEul1R3nTmZmT0BfO20ZrZdd3flkZNuOMv2EvE
+iG/uScOy2g40HskYDNivlJeDdMpQLEt5326J2MVAHYfl3JL2A+577aAYFWEfwj32
+FUpSaCvrd29drICL4nCAtGCL7iurNgGK1/sNMFrCjba8juP6wUQTmWsQzO06ER5r
+3Xbzl63taYLysDgSXPT+/5kBDQRVgAbEAQgA0xX2/RBxhog3pf4hc1NGoNUbqB3y
+/9Pc9cu4kJS8pzgEE0UTcAlns5qqQ6eswxTn9S3adBHRELVF72uYNpZt6OeqPpp1
+6xEK9dbmSHLHxiHJO8lf8v9eyOwKYFzD4P5nI4nzkzPQu/j7YIX941IY+BK2DBUC
+3pQRhGWYn55wKByzhAB+9AxDQiNPD32EURupWQwdPZz1u71RvEW0VFo0MsUFFsF7
+quIEKIUhQ6heDRPMwxxPw4Dyf8hcvsBa9mm/dA7BO7zmG+NsveZHirUwaffP2O/9
+sW84vBGK1wvtvdXcGRCzB52CxebDv3w3Kw3cK3XtnG2fsLaScLUmDzb1cQARAQAB
+tB5KZXJlbXkgSGFycmlzIDxqZ2hAcmVkaGF0LmNvbT6JATgEEwECACIFAlWAFgYC
+GwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJELzljIzkHzLf9AcH+gId1Frl
+C9S9sAoYx15Lb9I/KEJi/Ag9QFGJIPuDUU5GU5uZ4GZTZOGUiQe7KJ2hJs7N9H8P
+c3sfCzLQjJxB0GLRTW+q9TNkHzEvGVHXmlDZpRh7vFJx6q/mk5EG0LL4bP1Gi5J1
+pTlqnbDNvWd9dchgNwXZRscP9sHo/gLUUms0+HRCEj/CvMX56EJoSFVGd92jFQxe
+9Iop5gH1n6lCoYEXLa1TC4F8SrWIjnkxlw7ZtnZ710xzjt7tDdrJMHOmMDsS49V1
+8Fi+ikCce+/9kqlRwyVXhJDBG/Vq/3v5dT4IFPn2MS2efx1mxmrTZqERaGDbghpa
+QS/VzrkCvfD3CMq0JkplcmVteSBIYXJyaXMgKG5vbmUpIDxqZ2hAd2l6bWFpbC5v
+cmc+iQE7BBMBAgAlAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAUCVYAYBAIZ
+AQAKCRC85YyM5B8y34iFB/9wozIYRogNdY1aejFFixb6++y4b1riyjMvWEULeEzD
+lQ0lMT6Z3PxXhZILD4y4aP7Kzx0ozXa5qaKy41EAPKQoPipnRAH04QytJbIERvz8
+Tot/LeCVKUc0G9DVxOPBD03czTgqgz4EjV2qvnLF+rTU0YBevrNCluKosGSd+3Rv
+LWVu0hBhn9pELKfXJNSQXZb+TpHDhSDZ/gCrglBEOhA6YWbDb/4gz+5TFKdk+B++
+iAQZSHv7zISabjN+BPYgI47A+MU4JycoXaAUnMc0l5ba6fGNaIrzruE4aAZrlP5o
++7mlU9Mm0QJqdqYxYPAiplJGrZv+YXH1fp5ueEK3l+NGuQENBFWABsQBCADphLHa
+KToRuR/E7THerBiCjDatwCaETOKOTY2zRBQpaQ32p/F2XIGLS8Cc27+grZSKQ6ZX
+0ZN47O+AFyFHF8DH90IXZFpJR3Rb8zgXT8jnLX08DM31eECZHnRzFhGlOmq6WAUl
+qB3GKCPUCY2c4eTRXyoXLteTxrXCYoj45y/YmvlZrlonBNjPBAyHiO/LNz+V7fZt
+NsN7N/XGrnLbcdNfNd+SD1ENmbLJ8RvyymxguTyB/ka9JdjHHIoQEJ6L166B3hhf
+CHpt8iC0GPZkti9IMl0NoJ029jJm3Jq1qEceEBn5H5QMGn6Fq64iXwTsO1TMNUwp
+Wx8pjvV7wVIxjI8ZABEBAAGJAR8EGAECAAkFAlWABsQCGwwACgkQvOWMjOQfMt9N
+6Af8CS2CTrMQFdhkGEtBXmL4ifD8UHFkBRBGmM8ZL2fWUBTZXT8mrdRMOK6tcPnK
+WaCvWvKr0knt970j/DyAgFmH8hgOi3yctigFecVDjjilAeCJMq38s1tYKYiLDbBd
+HWtdkA9uHZwq3lfd3QxcEEO3QamQF+dO7h8gAOXlG+po87Hm+E0wz4swIB8+S37J
+zrx9uu0LSFDfJCTK+TIKGa5Un8LxPxyq9WnnNDh72zK7BiRidk/s40KcNod83NM4
+Hn/sbGfyLa8sS0F3ME0S+ocSMOiu/ZHHOiwpLYNbwTJ7stZxGsrguWeT9P+amxbA
+/YlK95LedstwvN+WcHZ7d++ArpkBogQ80tP1EQQAkRJpVJz3ks35wtJtqEFeHks4
+GI7JIi38GfvkJd8xKz8NAKYR+5veXWcALHE5twkERww3f0WMZCTAGAPo5sPHFV9a
+17UNlEUZs8tvyPqgVNTQj66gYykaq2r/boSwixrxMHaloNnap/EzMDXZRE2x/kdE
+EP9ZSvF7VtDYhwvCPjcAoI//AL/In3aVUpxadUHrEwFcHU2zA/4nEWDmeSPeA0kZ
+ORhp5g/pcoX4/no2G7lRIINooFfR7lyTJwSk57sK4rrJE63njUAbwQk5bR60XtS7
+owLZbG4saKE4FFOY5M2Pifz3jXy3dSfbqKkxkOxo0+EjHyrnrUkQYXEWWL5vRTPu
+eL7LpinFxivqgXz1QR6NW5PIKCcYAQP/bZ6wKCyFD77OHeHHvYeQRgNshvUsp898
+NZfGa/uHKKJlI2yRVfOmVijzOp8m9kYQ29tBEpGZwqA8WWPqebfXN2okCSkEFfBo
+9LY93O/ziqMjHj4Gmx/e+dkOrqYmeTJd5n7Hvyo2uEx0aDxI5EMwJ++48X73rCoh
+Ifn7uoLCd3C0GVRvbnkgRmluY2ggPGRvdEBkb3RhdC5hdD6IYgQTEQIAGgULBwoD
+BAMVAwIDFgIBAheAAhkBBQI80t8sABIJEP/A8UyExxtuB2VHUEcAAQEngACffaHN
+1vFAND3qnXOXF+C24rO4SOUAoIO1O8makm1tW0Qz/JLn5P/OkuYPtBpUb255IEZp
+bmNoIDxmYW5mQGV4aW0ub3JnPohgBBMRAgAgBQJEZGvMAhsDBgsJCAcDAgQVAggD
+BBYCAwECHgECF4AACgkQ/8DxTITHG25QYgCfYIDwuIdv3sjJX1JcqXxcy6kJIi8A
+n3fN1G9xTdgtNTi0aL01rJl1gWlgtBxUb255IEZpbmNoIDxmYW5mMkBjYW0uYWMu
+dWs+iGQEExECABwFAj0XOuMCGwMECwcDAgMVAgMDFgIBAh4BAheAABIJEP/A8UyE
+xxtuB2VHUEcAAQG5qgCePGYX+2/SUFlhIlTtJt+cq1BuP+4An3OThP4AU4PT/9Qv
++VBQusMz17XctBxUb255IEZpbmNoIDxmYW5mQGFwYWNoZS5vcmc+iF8EExECABcF
+AjzS2LYFCwcKAwQDFQMCAxYCAQIXgAASCRD/wPFMhMcbbgdlR1BHAAEBwE8An0Gi
+R8w2XDM4Tq2I4aG6YV2sok26AJ4nxMCidQw1WcT8cVr8TaE72j8JtrQdVG9ueSBG
+aW5jaCA8ZmFuZkBGcmVlQlNELm9yZz6IXwQTEQIAFwUCPNLYkwULBwoDBAMVAwID
+FgIBAheAABIJEP/A8UyExxtuB2VHUEcAAQHwnwCeLGp7ZuYNsmlw9vQihPzieeEn
+eS8An2w4sBCNZi8rtJ0nIwXXRhfQIh8euQINBDzS1BUQCACbZIRga1GhFT9nzuME
+KZro2Hz3O/hk/kNJ2igl6zENbwyuaabIIYkq/U3VQd5HZHr9qBsYLAn8trjwvybn
+sEAcmAZrSiblOhg2kVV4DIaVcIXEa2mg3mGZtBiA0BF931kpS9O8cW80mZ3sJUql
+BAM5x4yxjdaavZrzwXM6toawLHfUzx6PyAfSbFZh1fRmGrB1Em1VsdUup7VNpFdk
+8iqQUuJbhdWe8usiFkvacImY05VnVBkn1JJE7urDOfXQQMb/UuUPE2OOE1EH4R0I
+XC08/OFLq1PeounG/fWZjebquiD+jTyLpoTiQKj7x0qpcvhM/6Es0slKMmdEmo9+
+RNQvAAMFB/9LZEIdW5/wYSMgTuEvc+cEHyPmaUe56XimdWmEFIxc21C29u2NtB0b
+o715YxT6FgdaCTvqF1C5AmjViPu9k2F8ykQ6mFjGrm8K2HuXRyeIRA6Zqanhu1uN
+uayLQ34c9sCjkXPb5PnhzBxmWYSmprq5nh+4a1aXt9wUX7bOcr2FqH2KJ1hhfisn
+QXKzKdwDGyhkDEjsHAYkqSCVZ9B2CRSDlprYbSiDKn1Zi1ktrKhSiE2wShwXpj/v
+exVQHPNnV8Y+oqwe/4wY3T1+f+QfBxzl1nhL7h0o2funn1xa01W3odrtWpQdGl+Z
+M+HG/BZfTs4qnNi8eRy4H5ColoAwLuGDiE4EGBECAAYFAjzS1BUAEgkQ/8DxTITH
+G24HZUdQRwABAYfuAJ4k3jgHTXOHznfpXNRDIjZqPPjJEACfXAql73R3KnkOqXNQ
+7FzYXOIPK76ZAg0ETo1xAQEQAM3ku0av9+W+uN3XuYI6Wq1EHbq2PobiMBzlVRFh
+t6NAZttjM/kv58It8OjTUxgsjqN9+SV+lwNEeHav8AK8D40FNNWs/5dfz6zz5jj0
+p7/f2u3g1RYL+31RoiRX7SA/cM9hADYax44bBoXxXu/cFerpWvCjJXYqUPi8BhRq
+EnnZXGxkb78iGu9mfYyOYDJvV+PNliv/sZfnrwIp1FjlVpUWospd0LtnNFk5uDNH
+zfNprdygLuXdwVG2YZGa20OBQnxHc6qnZf2h757KXOmad0RmTMY9mIj3RWZlyG+3
+VdQn9VGlHA/GYm+vP40lZWS3MNTWShq7I6aLIHZw7r5FxIxqhAhf5OnKoln3qt3o
+qwcLJOh0Xf3fGzvfRXbilEldJuomnIE1M6N3ZPznowj0IhDRJG9NQkaynjHzaP7e
+sm9IoZMKU/AU4mQnQppDI8whq50/Q92ypQtKgdeIGTeT7KPq2D0JrzhOuxKsvHV+
+8ziwBmmqjmkuuINuXuOLq1Rmi+pbToxFPusUIyMlbQGTyM+nav/kBJ2O/fcuPG+v
+73B5gcnn+s0n0xRXDetebINtSHv64Cb/Z7bjPbrETouzEkHwnN1GpFIypd1/wmyK
+5AtakpkHBiiEsP4Qor5t+mq9MAcff1cs/P4fHU/KDf6eRxAkgexk6Ifm6d5DJ/mg
+h3QxABEBAAG0IERhdmlkIFdvb2Rob3VzZSA8ZHdtdzJAZXhpbS5vcmc+iQI4BBMB
+AgAiAhsDAh4BAheABQJOz8S4BgsJCAcDAgYVCAIJCgsEFgIDAQAKCRBjdizaZ+Lz
+WYZuEACxAWE+2BQnssLMANzx/PIfedbomtwnORp+FwSm0MUEOEEVh5K0YZiDUanO
+0jAVH8VLWJ0/k9vxItOzrUcyl9QIyje+Jhfqe7PIuyQjNreynPv8UiGSIacIG3+D
+jo7VEN0SaGI5S/pHvGKJl+oHjGVfyFzdp6o+GvAmJJs1wotnYO7ln/L/6f4cpPwy
+2JQ/h8MaURugDVJEOs8U8YJ88Ioa54WjbLtjwJ1iaPRv035PKOSbk0QZ+ahq+0sk
+ND1BubPA9iD4DGgAR4aaE/ZmL1Trp3a6AtkcrrEvcc60yQvgRLy2fid4nQL0+/03
+4ybjqz6ZK9gVCGWK+DBfmnBtEEFLLbCXae1du4iDTTdI5pcm2wpe0YiDvJwkn1Ub
+LICm3yTJ04bzBo2EkhT1M9uYCIB/ebfGv9qxkeZ5wSUGG3ftU6I9L6nODyLkgC5P
+Gxf369LNQ8ClCRoG4lGAOJqCGNeuYXu/0VRlcJuajOPWIEmiVsIp7QwhqMe5AyTA
+Xzs9Z9BVYuSyxgh5lqLC7fwkaW/hfq9IsdoOHeZCFPyXnU9ZFPHTroC1W+Wy7orC
+tvIeSdOpOPFX05M5Eozm/DZ+MO6u/EVGds8fDcuWOfgRRCrZruwbPT87OuvfAx0m
+nmc6iLkk4J/HXbvIgYx2Rf7MO8K2Ttc9nCo+hVumzHp/21/h27QiRGF2aWQgV29v
+ZGhvdXNlIDxkYXZpZEB3b29kaG91LnNlPokCOAQTAQIAIgIbAwIeAQIXgAUCTs/E
+uAYLCQgHAwIGFQgCCQoLBBYCAwEACgkQY3Ys2mfi81m0ew/+ISlH4dOEyw9mRbCF
+sa4BBP+utZR2mGTYv6yQJIV+9/m6Tz+WlbloqrxZGG3CLwOPj80ovCl7ioi0m8bV
+A4KgSj1iPWYdnDo9mJJf/kKqfkwD5403XufpXPvjFJuytEFNgiUGcL0cJy7cDL6r
+I+3ZRd+zeOqEjqnD2+iUbA5iZqCVJ9cpWYY8rNKgMPQPt1VcVyCGnLH+zsa/uVQz
+EyZsnJXxsG7AABMmGBIKKUNkfGfmxDlU3Q2EgtOdaDtClB4nW0RlPP7p+8jPERbW
+UqD6fPDuMHVsq/cW2oUq+C2CJqDVBqSS0ECn/hLTCOKSiEw7OTZJfEuqvxpIfJ4a
+pXrlUP409+VJNE9as4V/vBGlALOAaf5FCedCvleguMATBAqDAx18r5oJCgpG9fIO
+7TIL9Xmt4GV3DlOzFL4YEs3J3SymYvrWhk4JjvmzTXTpwNTAMTCaaFOOXFqVcj6e
+Z1INROf46EBlvVQyRS5KCAKzudHtgnMfm3gz7Q9tMwTuGIGvoX687ZQD/WhlAK+W
+cbuPTKXgpak8k5wqBcgDGwCV0o2gRHJ6SFoIWlGYp8r553gfCaSuCOM48AYMGjdv
+g+itDEwyY8mMP25EWuFOr7/C/dTUx+mrwqTCTHaPUnqJC1bjxl8QB/OSX83iv6tW
+546xgXGvxyUpLE/t8LY+1wgCiCi0IkRhdmlkIFdvb2Rob3VzZSA8ZHdtdzJAa2Vy
+bmVsLm9yZz6JAjgEEwECACICGwMCHgECF4AFAk7PxLgGCwkIBwMCBhUIAgkKCwQW
+AgMBAAoJEGN2LNpn4vNZTqoP/RL98RcHRFj2XZJ4+t3+A67hCzwuxmG+WmOJgDc5
+rC5JeapW6+vibU2TsrzOcJkf9Mw+pnt3u2LzFgt20CaZTBMXESVZUwa5PYXYnj/9
+AhmmOYlxSy6klWysjTUNwaBEuryQV+DgIJw+SeTZgsu6Z6ZRxe4UYmrCtGMghtkH
+A7THYKtgCpKkY2kN69JAW5WzUG1NSjIVX8MPPdMGCCf9R+dXqdyJAsPS9Yv9AvGf
+ZgUG1ARlvJOlXeMLb5z2LOizQVx7g1/ambpgpRAC33SMf1iEGWeTiIQfeBUgttOo
+D+KYDBSW/nYDq0FOkOOsvK0bAKxCUZEAUtJtTXeYU8WIvjuVdooG4tr7zMN4o3pg
+PpxFEQiZLu/6m+714bX1GAivvV1FCB5FuiGnVuYUardPjtuLZzrKI+cmVnW20tra
+R+OBf5RTuVUYSpClcvo/Q+fll7EdjS57ozKai1XsyGWPuR/ELDaffpz1ymi8upH4
+POjruAtIHTl2NzPYfo+jBE8zznoYT3FODnqersHaIBRD/OXEB/2Lm6V/8a05lVXF
+yT32LA6Ef0hLwjdGVuhwUW17OXyawmXhcLwBvsrVyegmOer6fZ/Xx6dZ9flrhH/H
+jASb1p/SGmWGuH1CjT6dhRo+XgkUveaJ5ip4KJvGC+J8xDdqkUPHnRdUs/R+/DrL
+IYeftCVEYXZpZCBXb29kaG91c2UgPGR3bXcyQGluZnJhZGVhZC5vcmc+iQI7BBMB
+AgAlAhsDAh4BAheAAhkBBQJOz8SvBgsJCAcDAgYVCAIJCgsEFgIDAQAKCRBjdiza
+Z+LzWY5QD/9EJrnssxVTZVAt/OeX4ecgy6Z7nEsAq68QWc7nUbumzG3d3kzWn9li
+5yLLR6kEvj1g5w6t7X9pOG7UTqpCYs0jw72ozBPrJTNJk8rk00/R24ROVX4Bjb1t
+BUdLEuIne9lS7MnI2oaNTT9vyTV07OLc/aorKv3hyI6dryW43IDVAXQBqFL7H+iP
+zt0mSTOo+uCwY1G5pusmWUlLUAk83AfenBJCgj6F0WrcxY9MRxBpgghBVq07hynM
+2wulU4EHxjitzfVHmkqZa0dZhAYo/jOdU+K16kL2+dpKPy/1KADzbpNE4kULOmL0
+E8tenEBLM5Cb8QWVE0t4n0phFX2lrfH/bG1X3lj5uVEiBvx8LIjTcve+8G22qATt
+uQvt6wuFu6xSqs2LEV5yigVok1bQ7O4L0AXRqlkcCTI9XK0wxYCNPRp/jf17YSHA
+v0k7yXQ4GqJsbr/aFKjPUDJtcfI1Q/uJ5xGD1qVeJrcY6APoCFvlb9TxiUvt/fnZ
+QNkxLQ8yXxjxN2BdI48vMnKsPsvWq+vp57Kcqy2nwlMm1wmn0NdtfhfsyFX0eiiq
+/l8uW3R1FbkVnj/hH1p2mLnr0aDLGPtvXlETuoYdUtWWLpKQDST+zbtqJwDzjj9n
+vF/Exdt2XGHDjdP7J3jJ5wrli0P2fdWY0w7vAY2wmD1qoFLg+n/ENrkCDQROjXEB
+ARAA2oX+WFwo2ALAc5y4KURS+Ox9E4Y78Sey1/2wj06PNV/E97vpHfNgBa+qjcnP
+3Wt5lQSdB1DKjkhl050+p3L5cXueh2h19PWVnPcjjWviy4VjLx4fHH0vWenx+SNV
+j9A67S0aHzvfSvoBhPBn5WhEyKKhlQnCmx8HJgZsnyTOznGGwTatbzI+77BeiFcm
+BrBgpi6AMI7mV94Dv5mUsIiRmOFx0EMeMIkS5ocZfx73AoqjTmctxWg/LPigWYV6
+DngLY+YNL0JXkXKC6en1hFwc/3kmgAt8tj/XoI+wlo7EP/JMuHRKvvelQ/l6/RhN
+HClRXwDw0gmwtnBwXxWywQ15Z4sy4KgVhIySdYzwHBuaXHZOGJS9DYe92ZKfRLgf
+Zy7OWy7FrJnDQQrB15IKvxrgys5Fs5lYYEOmKXSDNDyfsnJsrOZhK2Y5FPeNkSSA
+/caTtrSLAaFkE+cNJ7wDno446EhTFRiFikcDKTcl6PzEPCKL2oWcJ9Qf9QkIG2kz
++bp5gIMmIEOWyr3+0FaBQd9Lbtkl/XPCJ8dpmtLaSPNhmV5hAOAq4KJNaty0ETTu
+01RJhOCOXN+x5Flu8/aMjufSmSyOowkCnCGZ07jbt74in9Rgtn8tukIHZ02RSOVT
+pX0WG1c7v1ezKwBueot5WYxCe7l4QTzboMTF7caHoL214/MAEQEAAYkCHwQYAQIA
+CQUCTo1xAQIbDAAKCRBjdizaZ+LzWQ6tEADHjnV5WjOKSzurKRcRJZFCu2MEg49q
+GiNwyg37DLeWNXDNaZ+gzyVhRNPiNxxuyrjY9ZXXMQ0Ce+S0HJYEtEiVJJ95DABZ
+GUXKQdq212KVKDf/XWcojxY+vPRIKqKIkFVb9Jm8q5wzCSJUN6I9A1BlHwGNylwV
+Umpng5yGnTCrD2rsFiLqmk+gaKJKFWGoxBu5nlLOHwiBvCsIvmM/fHRUnEG5L2El
+Bgt+IvYPt+Sp0OmcmNp+4WEB0Ys35LmDj5PYhfGCzGnn8PkgxruthB/nkuSKeXKz
+cDQM43REx0bN3zVc1sR7ePvCmCPp0QQcEnkHrPoMeSGD2gSfbtegW+soCvOBgKYM
+XWAU+nfTvBkk+VUhRVWum2Q1thq+zFAFujuIxS635Wceqqnn/OgBv9st+qWYK6X0
+4OAibMy0p3E8bi0EunysUrmqAfh+F591NJ1g5KZhvLRwhFmZD5LQXA3m6dyvogRh
+/9NKMtuLE0D84/5t8+Ek8wZjBciKO//THdR9D2oYE2G1Q6rgz9vrhCIdAYeLMW8O
+VbnQca3uFaVcTC37p9mpQ9NfSTdZ07Ai7L5J2+gNs8hG/2yv2AKz5IAtHiv7hsfr
+UglTScycHZrcsepSErLkVoGpmpHo2QECdUbGHsuLsXIVaCAaLBK/ehxgFwukBqlB
+qZqeIHeOWTockZkCDQRLxXz3ARAA1OWxTnG1QgQ7Exqtw/vAJwPd90uImL8T8Gdn
+kWk+NeY10LPdcDU6VcIGN8Uc5I04V8rehVewnfgXUuGLxhzL6imQjoFZ686cXjqs
+uxrAtdMn1PnQOxr2YmPIZuLcP23ZI07EEHlnj4FqAoJqqWnSgJ+IhodLt3wWQu6e
+GfCbH9NsqlBSMuPDsSn/2nlfFubE0a9Ztxw5m47VMP1G1Fu+tRiIEddQcXDyGjGj
+e1gMChzaSFKoyGcmes+rRhkN30cEwCcItUkThFO+A4EbUR5/qbtpKdKTrA98M/Mk
+/eYC/dggtoZIrU2QePt7oJZjL7kPZ8EI2nT7fGrN3wdpnxiu3PbS1zCKvG5g2cPp
+llP10W1OL6wnUFclh+i1CNvP1p4+i1BAuoWTp5oi0pZd22qaNHnGty1fTVadBmjc
+vEF+zqdisBN5lpDeBe3NmchkPDGi+qi5ZhOt0HuEBcucCeB9ejQr1x6hsZutG5yc
+udTZkCRne2EHg2uQBW4BSfFeAYPn9S3yHofa6RyormUT2Q7PMsbNJBTkDo2eLuRz
+Z9gMrmL4m09Ql49F/Dh816UN0spQcvixOMRJ7eiT5EI12ocBME6LVMUyW7tHyvVL
+wQeJqsyE7Nu9B0qMMxKH4XP+WCUsZcUPGt3TwQUY81Hk3zTeoAaxFf+2GpXw5sRy
+hMhR3qcAEQEAAbQ7R3JhZW1lIEZvd2xlciAoS2V5IGNyZWF0ZWQgMjAxMC0wNC0x
+NCkgPGdyYWVtZUBncmFlbWVmLm5ldD6JAjwEEwECACYCGwMGCwkIBwMCBBUCCAME
+FgIDAQIeAQIXgAUCVq5N9gUJEm2e/wAKCRCtXtu3k+xX5JV1D/9l86woDToUaw5K
+JykHXVqs4M/Q1z7bZgPZjXlwDNIAQ3JzVIRV4i+co0cgprZq43Yr9hhN8hOV4Gqw
+qMm/LU73tT7PaLs5Gx0KJgznaE7/Jmi/qezKL0l69I3CaMPa1SHUj9NwXRRMWbvh
++516yqEinARSDHQ1c5zDhgTuPWeHVxXg0IGfMWnw6QdCqghyjH+TzUO4kO+7r95S
+FRb1+LL5zz+bdBRJhZsGdGGim0aGvyOH8tIxwqXwrRBLZq1LiiLkLfLs6WKaPwZx
+/8ql6f2oye8fz+J+CGy1fh0pF/D8X4egAXopRyr8JpjSmKzZdrwp2M1MljsgxUhM
+ksRwBDeoS3axMWTVw2i4e90Cjf8vG5eLIdxIZpfwwYW5LRrXlAR7eTNPTh77vgGC
+6WMdImVBs1HeocZFpZCdpD3k6DHCn0SzRk/tt+1QL2xmja18BWVxC8GqbS9tl7rt
+k35+NN/2g/bLJOC9r+xsCT3k0NVPcR9O+NnliX+zl+1DFKAhthyX+0oNp0bSIISu
+d9XBNXL94t+RDh3lVgpU3l9pHP/YcIM1wDFiqxHv9zjBjx0DTUbJzwrCR44VGD0b
+0taWupNZ7onfCGRHlrJA09j5kjViUGsvaANqI3BbmvlAv9vbqbwajtDvvwrSx2L4
+pywDQ0obRGSeNq8G2BKK0+jjvGCMG7kCDQRLxXz3ARAArQhzbjp/qWYwTlpltK6g
+pzfTssKtIdE8JP1LKpIQIoyJoF62SzKHspRgPlCLJ7jZenvse+ints+h9CcrcCGR
+IvRKqmWt1IaTeyUNHXTc5MxPLb2HYnD3XF5GffSgXB6D3dufzd3zSViZmwb/2wkD
+Ftif80aOUD6JDoxJe+9MTUvcHF5gN6bUym8s30OFofCwC6ydxfb2cPBrGRQJhTXj
+CwieEIcZNzIwqEHc80Uk2BLuHO3mGxPm4lP2VIATCR+BHeLq4TjvNXfXQ739ggnX
+CXX7QGMvwArU3qvLXN2MxvhOCfrqZcccg/iBT/FPEOU8lnW+QFP6wFmtVHacGSEI
+miX8uBNcfmnPQRwA9QWWgjL9H03D8WJvJuuxmFHxakWiOWHfbD77sxfCA7Nm6DZe
+S+tHHvETjgbcERqfXWjh5oSJUShqBuxT3NXUxo61XeZVoM3rfgh51mfhymLDYFu3
+A6Odg8GvAY3pyvTlMb80zc8Js/e3AoqLn7/srFEvQ2PbZez9Hh0dbXoUzOx0gJ/5
+QhFPdI+f3/6M8AUfHAxfJS0jF5Ssm7nYx7XixQP9vx281iBFlS6XJJrt18acPRBf
+caIkJmyU5pFhCAQUiwAC2LbsoAW4hlHcDwXiUEFGJYjaXYK8Cl6fTiAfDRSvfQ7x
+u8QeteevYVGnpMJ5gebhFNMAEQEAAYkCJQQYAQIADwUCS8V89wIbDAUJEswDAAAK
+CRCtXtu3k+xX5KKeD/0fe4/l/M3z7fYLAAZ1Ocx7Rkuo0PQIWsfqtA8dFZu1r6h/
+Awu0bEhqedWH5X0uEt8TVRrUE1Elfs+x+HBVEg/H9tkZ7my1CjqZVbqeptw6s/za
+LIbfLFOWKNphZkTTBRqP4CRer7al9eaz6T4dIB7BIt06Gu6s3oJyFX7lNeGTQ7UZ
+5+TpriXxT/cTtT8SMKovhmmNOs5Cm3JksNdsTsDghLfJL+BRgsdRYkqxXRyuBn7M
+77czhulT2wETpxERtgGxkD1ztPpt232MUr/K8XqUo6OH/9bF21QF+QIB/MLXLImS
+6uw/lK03UXtu3Qfz5jfi+Mc/VBJlP/9ay88TgX8li3yExfuuXuEGZK8SMFSNjOAF
+QfaQG/n0jJljG+CFNbbkpHiU95P2ynlnRlW16w4koHelwoQXrovk3fykdB25fot4
+3g5v+rWHgqG+F0OJ4Sd3SPYCNUuGFyYBos4WZolqoqQONW9dq0AAeonlOmhPzTl+
+68HDRmgsG8goAzkH0e34qsVRdsQPLKJv2rAcAFFhWSDBIX4o7J+36Sv+7hqDfdhK
+n8b3MK+0ZKfm4QkuloNKvm4AZ/9NjAfvVJPWIvTu/wyPAqqcLIjM2e9mTJuKUy6f
+FhZqm8Xj1ksa+CQOl5gKZgu7EpR++ZnpRQujAsGsaaETG3h9pg4QH8G410jzDpkC
+DQRWzusnARAAq4Bl6qL9ZGBVQr+lol8pXDZUdlAW6alGA+m0cxtrDRfEYo/i4ocA
+V9LrXNrf/MspjCwaVXyfw4I4kbk3mt1MX5sgnuXqrajhMViyLPQTMGE9k2XheMSw
+4OftnqttnWKoWC4pqnoCcwVzz/2FmEMXAkEULxHlyM/ytb2Wr9rHvwHzVF+an2Jj
+i9IQc9V19w0e/IS44KzFJ7diIVW49zp/NjXJU1hCBOaR4jwNyXkNCItyDMAE1ukx
+jhIsEnyZsPMEtStFrLCFkI8c+1M3nEwKXmkR/aWAf9lqPlWsO2vz5MXB8VUoXNYy
+njMjMsEe98KMz6+KB5o/MJ6DUWPkV7dhTd6O6Ju2yKMeD24vlkZpFU9yhKbAS3u+
+AYpfYidys068LHA3uAbj81eLC1zsRZ8hrWJFvvLbZ6XyFQtOAsLYdOZOpozblVtt
+NYATTHswNsQTyCU+so8x7TexJRKuqvM/+k6Boo9Vzz1yufxl9+3oKRZ40EZGzKR2
+eho+XMwYMifzHJMvCkYkKHFIuAfyKm7vjmyh1THHZcKGjuK5TRvcFH0+9M5o/XZ/
+bv4yMmiaYZU32SLZaIPkNabh7gbj57R9hxKXfaWDyfxFQqHRhbcEhbGMx15hz9Wm
+oE9u0Jv63OSck//p8P1ZxNNmWFfb8LXxcBItJ5lnDQsAyoPY7ahoyJUAEQEAAbRG
+TmlnZWwgTWV0aGVyaW5naGFtICgyMDE2IEtleSBSZXBsYWNpbmcgUHJldmlvdXMg
+S2V5cykgPG5pZ2VsQGV4aW0ub3JnPokCPQQTAQoAJwUCVs7rJwIbAwUJEs/3gAUL
+CQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRCFvPesZzWmgAOnD/9khxy8o+25seMI
+1KRtdqmXrsRzzAic4W/95Qo+M7m5iHBHlOMaVUJa5TSl5EkhTqfSqwHdvYbawS4M
+pWn24yZrz25V4tqL25bBoV15chDNw4p2FEcfwOEReavbdTyg+HOZGq9hxCeZ92uK
+E8fcgMjMRHX4Er79GCGHf6IdSTzL8w3xy7STbk0qnnigNp8vI7L5EsxhVc0vQb4j
+hVhhbVQShzl6w6ddZr8XtIxeXv/Wo1BbIFNKTfmjy8fpulTO/r0KUEr6LPKA9a3y
+hmT4F0BzA5K6Ni0yIr+g1R3bl4kYxrVDwgxHZpT19lot9JuDTPbviNBY0jqNHOTt
+ynZHz/cYIYLFRnmD4JJtrJ8oOnIeJ1Y5N/VNGQxK/5iDRFtCK7iIdw44IM+X0vnR
+uaR7OWtVZqQXI1wY4W2wCW53UyAtLAmyJlR+9XaxrTMbO2CnaaxETndp6LuoLwPq
+IvKVRjt3RLQWHtOcPkyO+hCkqDIFfTptfHW5hN/T7DUZns2ph76VVROv6vcilMI3
+DN2KSss+A48hL08MFB43APJ5ZHkYn7EjUr5pkYR4wjZoBPWKb2vGZ5xCiQbHpiWq
+lvO2iqbrh5+RU0HZWumw4Y1lJC+bZkvAmnj6iTeOKGVvrNwYqoNzLoHANpBhy4Bz
+xbwgxOjwyKYXlWKq93K2QSeDXENBm7kCDQRWzusnARAAyFpN4GmBS1QqEpgDJmV0
+4shmjU3AS0t382Y6AZhvSdav26m02HkY5ZJ2Fg9v2N0VsxlWuOz0+qyCw1anzqMx
+8trlvTpkpbbalgw3i86vA9nlHUhTRUPDcHOTo69CLI8B1SoHif0wZrh8/Oig4ZS3
+ykvp+F+GSTTiTCaeT02mZ5HTZb5L+NRfWJ1j7ifcbiWSYQPHB9iaF4KAAIzfHazX
+g8kBcbhUR9D4EJKe+bj45/KsvkfQXTrGT24P+Et27wD2BZegX+ZbRIBDpQcSM/CI
+QEUvPod1UxNlm7iZeXgSEyWZYg1nSxiCgNQQkEeSL0zRheDgHC/3N2stdvWgk34L
+zBztoEzytLy3lnmWG7BSZuUAONiyBME9miRt5etc9Z/dKd2Pj4DlJKCtX/+vFc02
+2q0QXBttYBm1/+Z7YaK4wlWK7RAXD4zbCgbpYJkIFzGMhanuWybufpgqzQSBpzsN
+Hir+02KRri3ghgfxBXxpVB1T/cgCu1JQ+sefdLxpWhDX0WoMDgADqr2mnsIbWjnF
+M2YLLKBV0naNBHeLqTLg4MbU4c27spZuwB+jBliYiV5l/BZbSOS6B66pmuu6WGNc
+gpnfFnSuKOq7GGHPdOWv0IdWlHs5qBRdMf6UlzTa6fuLEN7Z/+Et3SVXWpwcwhLf
+SsKwXBt3ZZD2C2GMrGF0tikAEQEAAYkCJQQYAQoADwUCVs7rJwIbDAUJEs/3gAAK
+CRCFvPesZzWmgPYaD/44M3GM/YcC757H5eu2lnxbVSc/4z42FPftsls8VNajOBL+
+SVPd3qnchyu7O0NZU1NA8qld/Xs6Uf/jEhEdMbZsifLtIgUyvNxHdn0wpo/zNDFm
+MxZdtMyGjfX+/X6a6RRjxJOI8EJ0FxaoTeAjCo/7o+YTaCmJ+kgJcdJFxXANRKeK
+rOuTzXF4SB3eiEbX6vZjJR+5ucfEs/ZgZmw/p0R7aHObBtv6zxOrmJySmGDI6iaH
+sPc+pJjxReoZhc/YuZZvagHxyXDgtGSis3/kvSsZ6S5hjEIzOOzf5EnizEO10bm5
+rLf7NWm3ikq2DVamJc/0bJftNWqAwhczrWrc1g9ZZZwRZR+PvC82zRHcPfDmWcHg
+NpdJe2X1R4wpGY8YjOJEHouEpt8+RwwN9mK7CqZLMW8rIO+JLDkAAvyh+x6kG9vx
+2ckRG1Z1N2ZI0M3Zpo6qPSukqcA1uZOthy94L374y5Apn6J+yNaxege5yEZK3mMQ
+xFRLfsCKlHia4aQTMOUCD4NRoPU/MHN5OZkDYaGrQ8fT4K/1/lMMW9y2Gi9YkNsX
+BoyGMdgMgRNQJ0cSWSYlYyx22FQ+PVR9F08TarHBwSMUUPzo0GtPavcqdXAsbfy0
+ubfVmCzt64fDowozkEAzsraGjSp+EoNLJleyM314Eqp0LEyumt0vJEnNK162rQ==
+=q8ga
+-----END PGP PUBLIC KEY BLOCK-----
version=3
-opts=pgpsigurlmangle=s/$/.asc/,uversionmangle=s/_/~/ \
-http://ftp.exim.org/pub/exim/exim4/exim-(\d.*)\.(?:tgz|tar\.(?:gz|bz2|xz))
+opts=pgpsigurlmangle=s/$/.asc/,uversionmangle=s/[_-]/~/g \
+https://downloads.exim.org/exim4/exim-(\d.*)\.(?:tgz|tar\.(?:gz|bz2|xz))
options, and new features, see the NewStuff file next to this ChangeLog.
+Exim version 4.92
+-----------------
+
+JH/01 Remove code calling the customisable local_scan function, unless a new
+ definition "HAVE_LOCAL_SCAN=yes" is present in the Local/Makefile.
+
+JH/02 Bug 1007: Avoid doing logging from signal-handlers, as that can result in
+ non-signal-safe functions being used.
+
+JH/03 Bug 2269: When presented with a received message having a stupidly large
+ number of DKIM-Signature headers, disable DKIM verification to avoid
+ a resource-consumption attack. The limit is set at twenty.
+
+JH/04 Add variables $arc_domains, $arc_oldest_pass for ARC verify. Fix the
+ report of oldest_pass in ${authres } in consequence, and separate out
+ some descriptions of reasons for verification fail.
+
+JH/05 Bug 2273: Cutthrough delivery left a window where the received messsage
+ files in the spool were present and unlocked. A queue-runner could spot
+ them, resulting in a duplicate delivery. Fix that by doing the unlock
+ after the unlink. Investigation by Tim Stewart. Take the opportunity to
+ add more error-checking on spoolfile handling while that code is being
+ messed with.
+
+PP/01 Refuse to open a spool data file (*-D) if it's a symlink.
+ No known attacks, no CVE, this is defensive hardening.
+
+JH/06 Bug 2275: The MIME ACL unlocked the received message files early, and
+ a queue-runner could start a delivery while other operations were ongoing.
+ Cutthrough delivery was a common victim, resulting in duplicate delivery.
+ Found and investigated by Tim Stewart. Fix by using the open message data
+ file handle rather than opening another, and not locally closing it (which
+ releases a lock) for that case, while creating the temporary .eml format
+ file for the MIME ACL. Also applies to "regex" and "spam" ACL conditions.
+
+JH/07 Bug 177: Make a random-recipient callout success visible in ACL, by setting
+ $sender_verify_failure/$recipient_verify_failure to "random".
+
+JH/08 When generating a selfsigned cert, use serial number 1 since zero is not
+ legitimate.
+
+JH/09 Bug 2274: Fix logging of cmdline args when starting in an unlinked cwd.
+ Previously this would segfault.
+
+JH/10 Fix ARC signing for case when DKIM signing failed. Previously this would
+ segfault.
+
+JH/11 Bug 2264: Exim now only follows CNAME chains one step by default. We'd
+ like zero, since the resolver should be doing this for us, But we need one
+ as a CNAME but no MX presence gets the CNAME returned; we need to check
+ that doesn't point to an MX to declare it "no MX returned" rather than
+ "error, loop". A new main option is added so the older capability of
+ following some limited number of chain links is maintained.
+
+JH/12 Add client-ip info to non-pass iprev ${authres } lines.
+
+JH/13 For receent Openssl versions (1.1 onward) use modern generic protocol
+ methods. These should support TLS 1.3; they arrived with TLS 1.3 and the
+ now-deprecated earlier definitions used only specified the range up to TLS
+ 1.2 (in the older-version library docs).
+
+JH/14 Bug 2284: Fix DKIM signing for body lines starting with a pair of dots.
+
+JH/15 Rework TLS client-side context management. Stop using a global, and
+ explicitly pass a context around. This enables future use of TLS for
+ connections to service-daemons (eg. malware scanning) while a client smtp
+ connection is using TLS; with cutthrough connections this is quite likely.
+
+JH/16 Fix ARC verification to do AS checks in reverse order.
+
+JH/17 Support a "tls" option on the ${readsocket } expansion item.
+
+JH/18 Bug 2287: Fix the protocol name (eg utf8esmtp) for multiple messages
+ using the SMTPUTF8 option on their MAIL FROM commands, in one connection.
+ Previously the "utf8" would be re-prepended for every additional message.
+
+JH/19 Reject MAIL FROM commands with SMTPUTF8 when the facility was not advertised.
+ Previously thery were accepted, resulting in issues when attempting to
+ forward messages to a non-supporting MTA.
+
+PP/02 Let -n work with printing macros too, not just options.
+
+JH/20 Bug 2296: Fix cutthrough for >1 address redirection. Previously only
+ one parent address was copied, and bogus data was used at delivery-logging
+ time. Either a crash (after delivery) or bogus log data could result.
+ Discovery and analysis by Tim Stewart.
+
+PP/03 Make ${utf8clean:} expansion operator detect incomplete final character.
+ Previously if the string ended mid-character, we did not insert the
+ promised '?' replacement.
+
+PP/04 Documentation: current string operators work on bytes, not codepoints.
+
+JH/21 Change as many as possible of the global flags into one-bit bitfields; these
+ should pack well giving a smaller memory footprint so better caching and
+ therefore performance. Group the declarations where this can't be done so
+ that the byte-sized flag variables are not interspersed among pointer
+ variables, giving a better chance of good packing by the compiler.
+
+JH/22 Bug 1896: Fix the envelope from for DMARC forensic reports to be possibly
+ non-null, to avoid issues with sites running BATV. Previously reports were
+ sent with an empty envelope sender so looked like bounces.
+
+JH/23 Bug 2318: Fix the noerror command within filters. It wasn't working.
+ The ignore_error flag wasn't being returned from the filter subprocess so
+ was not set for later routers. Investigation and fix by Matthias Kurz.
+
+JH/24 Bug 2310: Raise a msg:fail:internal event for each undelivered recipient,
+ and a msg:complete for the whole, when a message is manually removed using
+ -Mrm. Developement by Matthias Kurz, hacked on by JH.
+
+JH/25 Avoid fixed-size buffers for pathnames in DB access. This required using
+ a "Gnu special" function, asprintf() in the DB utility binary builds; I
+ hope that is portable enough.
+
+JH/26 Bug 2311: Fix DANE-TA verification under GnuTLS. Previously it was also
+ requiring a known-CA anchor certificate; make it now rely entirely on the
+ TLSA as an anchor. Checking the name on the leaf cert against the name
+ on the A-record for the host is still done for TA (but not for EE mode).
+
+JH/27 Fix logging of proxy address. Previously, a pointless "PRX=[]:0" would be
+ included in delivery lines for non-proxied connections, when compiled with
+ SUPPORT_SOCKS and running with proxy logging enabled.
+
+JH/28 Bug 2314: Fire msg:fail:delivery event even when error is being ignored.
+ Developement by Matthias Kurz, tweaked by JH. While in that bit of code,
+ move the existing event to fire before the normal logging of message
+ failure so that custom logging is bracketed by normal logging.
+
+JH/29 Bug 2322: A "fail" command in a non-system filter (file) now fires the
+ msg:fail:internal event. Developement by Matthias Kurz.
+
+JH/30 Bug 2329: Increase buffer size used for dns lookup from 2k, which was
+ far too small for todays use of crypto signatures stored there. Go all
+ the way to the max DNS message size of 64kB, even though this might be
+ overmuch for IOT constrained device use.
+
+JH/31 Fix a bad use of a copy function, which could be used to pointlessly
+ copy a string over itself. The library routine is documented as not
+ supporting overlapping copies, and on MacOS it actually raised a SIGABRT.
+
+JH/32 For main options check_spool_space and check_inode_space, where the
+ platform supports 64b integers, support more than the previous 2^31 kB
+ (i.e. more than 2 TB). Accept E, P and T multipliers in addition to
+ the previous G, M, k.
+
+JH/33 Bug 2338: Fix the cyrus-sasl authenticator to fill in the
+ $authenticated_fail_id variable on authentication failure. Previously
+ it was unset.
+
+JH/34 Increase RSA keysize of autogen selfsign cert from 1024 to 2048. RHEL 8.0
+ OpenSSL didn't want to use such a weak key. Do for GnuTLS also, and for
+ more-modern GnuTLS move from GNUTLS_SEC_PARAM_LOW to
+ GNUTLS_SEC_PARAM_MEDIUM.
+
+JH/35 OpenSSL: fail the handshake when SNI processing hits a problem, server
+ side. Previously we would continue as if no SNI had been received.
+
+JH/36 Harden the handling of string-lists. When a list consisted of a sole
+ "<" character, which should be a list-separator specification, we walked
+ off past the nul-terimation.
+
+JH/37 Bug 2341: Send "message delayed" warning MDNs (restricted to external
+ causes) even when the retry time is not yet met. Previously they were
+ not, meaning that when (say) an account was over-quota and temp-rejecting,
+ and multiple senders' messages were queued, only one sender would get
+ notified on each configured delay_warning cycle.
+
+JH/38 Bug 2351: Log failures to extract envelope addresses from message headers.
+
+JH/39 OpenSSL: clear the error stack after an SSL_accept(). With anon-auth
+ cipher-suites, an error can be left on the stack even for a succeeding
+ accept; this results in impossible error messages when a later operation
+ actually does fail.
+
+AM/01 Bug 2359: GnuTLS: repeat lowlevel read and write operations while they
+ return error codes indicating retry. Under TLS1.3 this becomes required.
+
+JH/40 Fix the feature-cache refresh for EXPERIMENTAL_PIPE_CONNECT. Previously
+ it only wrote the new authenticators, resulting in a lack of tracking of
+ peer changes of ESMTP extensions until the next cache flush.
+
+JH/41 Fix the loop reading a message header line to check for integer overflow,
+ and more-often against header_maxsize. Previously a crafted message could
+ induce a crash of the recive process; now the message is cleanly rejected.
+
+JH/42 Bug 2366: Fix the behaviour of the dkim_verify_signers option. It had
+ been totally disabled for all of 4.91. Discovery and fix by "Mad Alex".
+
+
+Exim version 4.91
+-----------------
+
+GF/01 DEFER rather than ERROR on redis cluster MOVED response.
+ When redis_servers is set to a list of > 1 element, and the Redis servers
+ in that list are in cluster configuration, convert the REDIS_REPLY_ERROR
+ case of MOVED into a DEFER case instead, thus moving the query onto the
+ next server in the list. For a cluster of N elements, all N servers must
+ be defined in redis_servers.
+
+GF/02 Catch and remove uninitialized value warning in exiqsumm
+ Check for existence of @ARGV before looking at $ARGV[0]
+
+JH/01 Replace the store_release() internal interface with store_newblock(),
+ which internalises the check required to safely use the old one, plus
+ the allocate and data copy operations duplicated in both (!) of the
+ extant use locations.
+
+JH/02 Disallow '/' characters in queue names specified for the "queue=" ACL
+ modifier. This matches the restriction on the commandline.
+
+JH/03 Fix pgsql lookup for multiple result-tuples with a single column.
+ Previously only the last row was returned.
+
+JH/04 Bug 2217: Tighten up the parsing of DKIM signature headers. Previously
+ we assumed that tags in the header were well-formed, and parsed the
+ element content after inspecting only the first char of the tag.
+ Assumptions at that stage could crash the receive process on malformed
+ input.
+
+JH/05 Bug 2215: Fix crash associated with dnsdb lookup done from DKIM ACL.
+ While running the DKIM ACL we operate on the Permanent memory pool so that
+ variables created with "set" persist to the DATA ACL. Also (at any time)
+ DNS lookups that fail create cache records using the Permanent pool. But
+ expansions release any allocations made on the current pool - so a dnsdb
+ lookup expansion done in the DKIM ACL releases the memory used for the
+ DNS negative-cache, and bad things result. Solution is to switch to the
+ Main pool for expansions.
+ While we're in that code, add checks on the DNS cache during store_reset,
+ active in the testsuite.
+ Problem spotted, and debugging aided, by Wolfgang Breyha.
+
+JH/06 Fix issue with continued-connections when the DNS shifts unreliably.
+ When none of the hosts presented to a transport match an already-open
+ connection, close it and proceed with the list. Previously we would
+ queue the message. Spotted by Lena with Yahoo, probably involving
+ round-robin DNS.
+
+JH/07 Bug 2214: Fix SMTP responses resulting from non-accept result of MIME ACL.
+ Previously a spurious "250 OK id=" response was appended to the proper
+ failure response.
+
+JH/08 The "support for" informational output now, which built with Content
+ Scanning support, has a line for the malware scanner interfaces compiled
+ in. Interface can be individually included or not at build time.
+
+JH/09 The "aveserver", "kavdaemon" and "mksd" interfaces are now not included
+ by the template makefile "src/EDITME". The "STREAM" support for an older
+ ClamAV interface method is removed.
+
+JH/10 Bug 2223: Fix mysql lookup returns for the no-data case (when the number of
+ rows affected is given instead).
+
+JH/11 The runtime Berkeley DB library version is now additionally output by
+ "exim -d -bV". Previously only the compile-time version was shown.
+
+JH/12 Bug 2230: Fix cutthrough routing for nonfirst messages in an initiating
+ SMTP connection. Previously, when one had more receipients than the
+ first, an abortive onward connection was made. Move to full support for
+ multiple onward connections in sequence, handling cutthrough connection
+ for all multi-message initiating connections.
+
+JH/13 Bug 2229: Fix cutthrough routing for nonstandard port numbers defined by
+ routers. Previously, a multi-recipient message would fail to match the
+ onward-connection opened for the first recipient, and cause its closure.
+
+JH/14 Bug 2174: A timeout on connect for a callout was also erroneously seen as
+ a timeout on read on a GnuTLS initiating connection, resulting in the
+ initiating connection being dropped. This mattered most when the callout
+ was marked defer_ok. Fix to keep the two timeout-detection methods
+ separate.
+
+JH/15 Relax results from ACL control request to enable cutthrough, in
+ unsupported situations, from error to silently (except under debug)
+ ignoring. This covers use with PRDR, frozen messages, queue-only and
+ fake-reject.
+
+HS/01 Fix Buffer overflow in base64d() (CVE-2018-6789)
+
+JH/16 Fix bug in DKIM verify: a buffer overflow could corrupt the malloc
+ metadata, resulting in a crash in free().
+
+PP/01 Fix broken Heimdal GSSAPI authenticator integration.
+ Broken in f2ed27cf5, missing an equals sign for specified-initialisers.
+ Broken also in d185889f4, with init system revamp.
+
+JH/17 Bug 2113: Fix conversation closedown with the Avast malware scanner.
+ Previously we abruptly closed the connection after reading a malware-
+ found indication; now we go on to read the "scan ok" response line,
+ and send a quit.
+
+JH/18 Bug 2239: Enforce non-usability of control=utf8_downconvert in the mail
+ ACL. Previously, a crash would result.
+
+JH/19 Speed up macro lookups during configuration file read, by skipping non-
+ macro text after a replacement (previously it was only once per line) and
+ by skipping builtin macros when searching for an uppercase lead character.
+
+JH/20 DANE support moved from Experimental to mainline. The Makefile control
+ for the build is renamed.
+
+JH/21 Fix memory leak during multi-message connections using STARTTLS. A buffer
+ was allocated for every new TLS startup, meaning one per message. Fix
+ by only allocating once (OpenSSL) or freeing on TLS-close (GnuTLS).
+
+JH/22 Bug 2236: When a DKIM verification result is overridden by ACL, DMARC
+ reported the original. Fix to report (as far as possible) the ACL
+ result replacing the original.
+
+JH/23 Fix memory leak during multi-message connections using STARTTLS under
+ OpenSSL. Certificate information is loaded for every new TLS startup,
+ and the resources needed to be freed.
+
+JH/24 Bug 2242: Fix exim_dbmbuild to permit directoryless filenames.
+
+JH/25 Fix utf8_downconvert propagation through a redirect router. Previously it
+ was not propagated.
+
+JH/26 Bug 2253: For logging delivery lines under PRDR, append the overall
+ DATA response info to the (existing) per-recipient response info for
+ the "C=" log element. It can have useful tracking info from the
+ destination system. Patch from Simon Arlott.
+
+JH/27 Bug 2251: Fix ldap lookups that return a single attribute having zero-
+ length value. Previously this would segfault.
+
+HS/02 Support Avast multiline protoocol, this allows passing flags to
+ newer versions of the scanner.
+
+JH/28 Ensure that variables possibly set during message acceptance are marked
+ dead before release of memory in the daemon loop. This stops complaints
+ about them when the debug_store option is enabled. Discovered specifically
+ for sender_rate_period, but applies to a whole set of variables.
+ Do the same for the queue-runner and queue-list loops, for variables set
+ from spool message files. Do the same for the SMTP per-message loop, for
+ certain variables indirectly set in ACL operations.
+
+JH/29 Bug 2250: Fix a longstanding bug in heavily-pipelined SMTP input (such
+ as a multi-recipient message from a mailinglist manager). The coding had
+ an arbitrary cutoff number of characters while checking for more input;
+ enforced by writing a NUL into the buffer. This corrupted long / fast
+ input. The problem was exposed more widely when more pipelineing of SMTP
+ responses was introduced, and one Exim system was feeding another.
+ The symptom is log complaints of SMTP syntax error (NUL chars) on the
+ receiving system, and refused recipients seen by the sending system
+ (propating to people being dropped from mailing lists).
+ Discovered and pinpointed by David Carter.
+
+JH/30 The (EXPERIMENTAL_DMARC) variable $dmarc_ar_header is withdrawn, being
+ replaced by the ${authresults } expansion.
+
+JH/31 Bug 2257: Fix pipe transport to not use a socket-only syscall.
+
+HS/03 Set a handler for SIGTERM and call exit(3) if running as PID 1. This
+ allows proper process termination in container environments.
+
+JH/32 Bug 2258: Fix spool_wireformat in combination with LMTP transport.
+ Previously the "final dot" had a newline after it; ensure it is CR,LF.
+
+JH/33 SPF: remove support for the "spf" ACL condition outcome values "err_temp"
+ and "err_perm", deprecated since 4.83 when the RFC-defined words
+ "temperror" and "permerror" were introduced.
+
+JH/34 Re-introduce enforcement of no cutthrough delivery on transports having
+ transport-filters or DKIM-signing. The restriction was lost in the
+ consolidation of verify-callout and delivery SMTP handling.
+ Extend the restriction to also cover ARC-signing.
+
+JH/35 Cutthrough: for a final-dot response timeout (and nonunderstood responses)
+ in defer=pass mode supply a 450 to the initiator. Previously the message
+ would be spooled.
+
+PP/02 DANE: add dane_require_tls_ciphers SMTP Transport option; if unset,
+ tls_require_ciphers is used as before.
+
+HS/03 Malware Avast: Better match the Avast multiline protocol. Add
+ "pass_unscanned". Only tmpfails from the scanner are written to
+ the paniclog, as they may require admin intervention (permission
+ denied, license issues). Other scanner errors (like decompression
+ bombs) do not cause a paniclog entry.
+
+JH/36 Fix reinitialisation of DKIM logging variable between messages.
+ Previously it was possible to log spurious information in receive log
+ lines.
+
+JH/37 Bug 2255: Revert the disable of the OpenSSL session caching. This
+ triggered odd behaviour from Outlook Express clients.
+
+PP/03 Add util/renew-opendmarc-tlds.sh script for safe renewal of public
+ suffix list.
+
+JH/38 DKIM: accept Ed25519 pubkeys in SubjectPublicKeyInfo-wrapped form,
+ since the IETF WG has not yet settled on that versus the original
+ "bare" representation.
+
+JH/39 Fix syslog logging for syslog_timestamp=no and log_selector +millisec.
+ Previously the millisecond value corrupted the output.
+ Fix also for syslog_pid=no and log_selector +pid, for which the pid
+ corrupted the output.
+
+
+Exim version 4.90
+-----------------
+
+JH/01 Rework error string handling in TLS interface so that the caller in
+ more cases is responsible for logging. This permits library-sourced
+ string to be attached to addresses during delivery, and collapses
+ pairs of long lines into single ones.
+
+PP/01 Allow PKG_CONFIG_PATH to be set in Local/Makefile and use it correctly
+ during configuration. Wildcards are allowed and expanded.
+
+JH/02 Rework error string handling in DKIM to pass more info back to callers.
+ This permits better logging.
+
+JH/03 Rework the transport continued-connection mechanism: when TLS is active,
+ do not close it down and have the child transport start it up again on
+ the passed-on TCP connection. Instead, proxy the child (and any
+ subsequent ones) for TLS via a unix-domain socket channel. Logging is
+ affected: the continued delivery log lines do not have any DNSSEC, TLS
+ Certificate or OCSP information. TLS cipher information is still logged.
+
+JH/04 Shorten the log line for daemon startup by collapsing adjacent sets of
+ identical IP addresses on different listening ports. Will also affect
+ "exiwhat" output.
+
+PP/02 Bug 2070: uClibc defines __GLIBC__ without providing glibc headers;
+ add noisy ifdef guards to special-case this sillyness.
+ Patch from Bernd Kuhls.
+
+JH/05 Tighten up the checking in isip4 (et al): dotted-quad components larger
+ than 255 are no longer allowed.
+
+JH/06 Default openssl_options to include +no_ticket, to reduce load on peers.
+ Disable the session-cache too, which might reduce our load. Since we
+ currrectly use a new context for every connection, both as server and
+ client, there is no benefit for these.
+ GnuTLS appears to not support tickets server-side by default (we don't
+ call gnutls_session_ticket_enable_server()) but client side is enabled
+ by default on recent versions (3.1.3 +) unless the PFS priority string
+ is used (3.2.4 +).
+
+PP/03 Add $SOURCE_DATE_EPOCH support for reproducible builds, per spec at
+ <https://reproducible-builds.org/specs/source-date-epoch/>.
+
+JH/07 Fix smtp transport use of limited max_rcpt under mua_wrapper. Previously
+ the check for any unsuccessful recipients did not notice the limit, and
+ erroneously found still-pending ones.
+
+JH/08 Pipeline CHUNKING command and data together, on kernels that support
+ MSG_MORE. Only in-clear (not on TLS connections).
+
+JH/09 Avoid using a temporary file during transport using dkim. Unless a
+ transport-filter is involved we can buffer the headers in memory for
+ creating the signature, and read the spool data file once for the
+ signature and again for transmission.
+
+JH/10 Enable use of sendfile in Linux builds as default. It was disabled in
+ 4.77 as the kernel support then wasn't solid, having issues in 64bit
+ mode. Now, it's been long enough. Add support for FreeBSD also.
+
+JH/11 Bug 2104: Fix continued use of a transport connection with TLS. In the
+ case where the routing stage had gathered several addresses to send to
+ a host before calling the transport for the first, we previously failed
+ to close down TLS in the old transport process before passing the TCP
+ connection to the new process. The new one sent a STARTTLS command
+ which naturally failed, giving a failed delivery and bloating the retry
+ database. Investigation and fix prototype from Wolfgang Breyha.
+
+JH/12 Fix check on SMTP command input synchronisation. Previously there were
+ false-negatives in the check that the sender had not preempted a response
+ or prompt from Exim (running as a server), due to that code's lack of
+ awareness of the SMTP input buffering.
+
+PP/04 Add commandline_checks_require_admin option.
+ Exim drops privileges sanely, various checks such as -be aren't a
+ security problem, as long as you trust local users with access to their
+ own account. When invoked by services which pass untrusted data to
+ Exim, this might be an issue. Set this option in main configuration
+ AND make fixes to the calling application, such as using `--` to stop
+ processing options.
+
+JH/13 Do pipelining under TLS. Previously, although safe, no advantage was
+ taken. Now take care to pack both (client) MAIL,RCPT,DATA, and (server)
+ responses to those, into a single TLS record each way (this usually means
+ a single packet). As a side issue, smtp_enforce_sync now works on TLS
+ connections.
+
+PP/05 OpenSSL/1.1: use DH_bits() for more accurate DH param sizes. This
+ affects you only if you're dancing at the edge of the param size limits.
+ If you are, and this message makes sense to you, then: raise the
+ configured limit or use OpenSSL 1.1. Nothing we can do for older
+ versions.
+
+JH/14 For the "sock" variant of the malware scanner interface, accept an empty
+ cmdline element to get the documented default one. Previously it was
+ inaccessible.
+
+JH/15 Fix a crash in the smtp transport caused when two hosts in succession
+ are unsuable for non-message-specific reasons - eg. connection timeout,
+ banner-time rejection.
+
+JH/16 Fix logging of delivery remote port, when specified by router, under
+ callout/hold.
+
+PP/06 Repair manualroute's ability to take options in any order, even if one
+ is the name of a transport.
+ Fixes bug 2140.
+
+HS/01 Cleanup, prevent repeated use of -p/-oMr (CVE-2017-1000369)
+
+JH/17 Change the list-building routines interface to use the expanding-string
+ triplet model, for better allocation and copying behaviour.
+
+JH/18 Prebuild the data-structure for "builtin" macros, for faster startup.
+ Previously it was constructed the first time a possibly-matching string
+ was met in the configuration file input during startup; now it is done
+ during compilation.
+
+JH/19 Bug 2141: Use the full-complex API for Berkeley DB rather than the legacy-
+ compatible one, to avoid the (poorly documented) possibility of a config
+ file in the working directory redirecting the DB files, possibly correpting
+ some existing file. CVE-2017-10140 assigned for BDB.
+
+JH/20 Bug 2147: Do not defer for a verify-with-callout-and-random which is not
+ cache-hot. Previously, although the result was properly cached, the
+ initial verify call returned a defer.
+
+JH/21 Bug 2151: Avoid using SIZE on the MAIL for a callout verify, on any but
+ the main verify for receipient in uncached-mode.
+
+JH/22 Retire historical build files to an "unsupported" subdir. These are
+ defined as "ones for which we have no current evidence of testing".
+
+JH/23 DKIM: enforce the DNS pubkey record "h" permitted-hashes optional field,
+ if present. Previously it was ignored.
+
+JH/24 Start using specified-initialisers in C structure init coding. This is
+ a C99 feature (it's 2017, so now considered safe).
+
+JH/25 Use one-bit bitfields for flags in the "addr" data structure. Previously
+ if was a fixed-sized field and bitmask ops via macros; it is now more
+ extensible.
+
+PP/07 GitHub PR 56: Apply MariaDB build fix.
+ Patch provided by Jaroslav Škarvada.
+
+PP/08 Bug 2161: Fix regression in sieve quoted-printable handling introduced
+ during Coverity cleanups [4.87 JH/47]
+ Diagnosis and fix provided by Michael Fischer v. Mollard.
+
+JH/26 Fix DKIM bug: when the pseudoheader generated for signing was exactly
+ the right size to place the terminating semicolon on its own folded
+ line, the header hash was calculated to an incorrect value thanks to
+ the (relaxed) space the fold became.
+
+HS/02 Fix Bug 2130: large writes from the transport subprocess were chunked
+ and confused the parent.
+
+JH/27 Fix SOCKS bug: an unitialized pointer was deref'd by the transport process
+ which could crash as a result. This could lead to undeliverable messages.
+
+JH/28 Logging: "next input sent too soon" now shows where input was truncated
+ for log purposes.
+
+JH/29 Fix queue_run_in_order to ignore the PID portion of the message ID. This
+ matters on fast-turnover and PID-randomising systems, which were getting
+ out-of-order delivery.
+
+JH/30 Fix a logging bug on aarch64: an unsafe routine was previously used for
+ a possibly-overlapping copy. The symptom was that "Remote host closed
+ connection in response to HELO" was logged instead of the actual 4xx
+ error for the HELO.
+
+JH/31 Fix CHUNKING code to properly flush the unwanted chunk after an error.
+ Previously only that bufferd was discarded, resulting in SYMTP command
+ desynchronisation.
+
+JH/32 DKIM: when a message has multiple signatures matching an identity given
+ in dkim_verify_signers, run the dkim acl once for each. Previously only
+ one run was done. Bug 2189.
+
+JH/33 Downgrade an unfound-list name (usually a typo in the config file) from
+ "panic the current process" to "deliberately defer". The panic log is
+ still written with the problem list name; the mail and reject logs now
+ get a temp-reject line for the message that was being handled, saying
+ something like "domains check lookup or other defer". The SMTP 451
+ message is still "Temporary local problem".
+
+JH/34 Bug 2199: Fix a use-after-free while reading smtp input for header lines.
+ A crafted sequence of BDAT commands could result in in-use memory beeing
+ freed. CVE-2017-16943.
+
+HS/03 Bug 2201: Fix checking for leading-dot on a line during headers reading
+ from SMTP input. Previously it was always done; now only done for DATA
+ and not BDAT commands. CVE-2017-16944.
+
+JH/35 Bug 2201: Flush received data in BDAT mode after detecting an error fatal
+ to the message (such as an overlong header line). Previously this was
+ not done and we did not exit BDAT mode. Followon from the previous item
+ though a different problem.
+
+
Exim version 4.89
-----------------
(3) I'm seeing:
"(gnutls_handshake): A TLS packet with unexpected length was received"
Why?
-(4) What's the deal with MD5?
+(4) What's the deal with MD5? (And SHA-1?)
(5) What happened to gnutls_require_kx / gnutls_require_mac /
gnutls_require_protocols?
(6) What's the deal with tls_dh_max_bits? What's DH?
-(4): What's the deal with MD5?
-------------------------------
+(4): What's the deal with MD5? (And SHA-1?)
+--------------------------------------------
MD5 is a hash algorithm. Hash algorithms are used to reduce a lot of data
down to a fairly short value, which is supposed to be extremely hard to
revocation protocols. This is just another of those ongoing costs you have
already paid for.
+The same has happened to SHA-1: there are real-world collision attacks against
+SHA-1, so SHA-1 is mostly defunct in certificates. GnuTLS no longer supports
+its use in TLS certificates.
+
(5): ... gnutls_require_kx / gnutls_require_mac / gnutls_require_protocols?
test from the snapshots or the Git before the documentation is updated. Once
the documentation is updated, this file is reduced to a short list.
+Version 4.92
+--------------
+
+ 1. ${l_header:<name>} and ${l_h:<name>} expansion items, giving a colon-sep
+ list when there are multiple headers having a given name. This matters
+ when individual headers are wrapped onto multiple lines; with previous
+ facilities hard to parse.
+
+ 2. The ${readsocket } expansion item now takes a "tls" option, doing the
+ obvious thing.
+
+ 3. EXPERIMENTAL_REQUIRETLS and EXPERIMENTAL_PIPE_CONNECT optional build
+ features. See the experimental.spec file.
+
+ 4. If built with SUPPORT_I18N a "utf8_downconvert" option on the smtp transport.
+
+ 5. A "pipelining" log_selector.
+
+ 6. Builtin macros for supported log_selector and openssl_options values.
+
+ 7. JSON variants of the ${extract } expansion item.
+
+ 8. A "noutf8" debug option, for disabling the UTF-8 characters in debug output.
+
+ 9. TCP Fast Open support on MacOS.
+
+Version 4.91
+--------------
+
+ 1. Dual-certificate stacks on servers now support OCSP stapling, under GnuTLS
+ version 3.5.6 or later.
+
+ 2. DANE is now supported under GnuTLS version 3.0.0 or later. Both GnuTLS and
+ OpenSSL versions are moved to mainline support from Experimental.
+ New SMTP transport option "dane_require_tls_ciphers".
+
+ 3. Feature macros for the compiled-in set of malware scanner interfaces.
+
+ 4. SPF support is promoted from Experimental to mainline status. The template
+ src/EDITME makefile does not enable its inclusion.
+
+ 5. Logging control for DKIM verification. The existing DKIM log line is
+ controlled by a "dkim_verbose" selector which is _not_ enabled by default.
+ A new tag "DKIM=<domain>" is added to <= lines by default, controlled by
+ a "dkim" log_selector.
+
+ 6. Receive duration on <= lines, under a new log_selector "receive_time".
+
+ 7. Options "ipv4_only" and "ipv4_prefer" on the dnslookup router and on
+ routing rules in the manualroute router.
+
+ 8. Expansion item ${sha3:<string>} / ${sha3_<N>:<string>} now also supported
+ under OpenSSL version 1.1.1 or later.
+
+ 9. DKIM operations can now use the Ed25519 algorithm in addition to RSA, under
+ GnuTLS 3.6.0 or OpenSSL 1.1.1 or later.
+
+10. Builtin feature-macros _CRYPTO_HASH_SHA3 and _CRYPTO_SIGN_ED25519, library
+ version dependent.
+
+11. "exim -bP macro <name>" returns caller-usable status.
+
+12. Expansion item ${authresults {<machine>}} for creating an
+ Authentication-Results: header.
+
+13. EXPERIMENTAL_ARC. See the experimental.spec file.
+ See also new util/renew-opendmarc-tlds.sh script for use with DMARC/ARC.
+
+14: A dane:fail event, intended to facilitate reporting.
+
+15. "Lightweight" support for Redis Cluster. Requires redis_servers list to
+ contain all the servers in the cluster, all of which must be reachable from
+ the running exim instance. If the cluster has master/slave replication, the
+ list must contain all the master and slave servers.
+
+16. Add an option to the Avast scanner interface: "pass_unscanned". This
+ allows to treat unscanned files as clean. Files may be unscanned for
+ several reasons: decompression bombs, broken archives.
+
+
+Version 4.90
+------------
+
+ 1. PKG_CONFIG_PATH can now be set in Local/Makefile;
+ wildcards will be expanded, values are collapsed.
+
+ 2. The ${readsocket } expansion now takes an option to not shutdown the
+ connection after sending the query string. The default remains to do so.
+
+ 3. An smtp transport option "hosts_noproxy_tls" to control whether multiple
+ deliveries on a single TCP connection can maintain a TLS connection
+ open. By default disabled for all hosts, doing so saves the cost of
+ making new TLS sessions, at the cost of having to proxy the data via
+ another process. Logging is also affected.
+
+ 4. A malware connection type for the FPSCAND protocol.
+
+ 5. An option for recipient verify callouts to hold the connection open for
+ further recipients and for delivery.
+
+ 6. The reproducible build $SOURCE_DATE_EPOCH environment variable is now
+ supported.
+
+ 7. Optionally, an alternate format for spool data-files which matches the
+ wire format - meaning more efficient reception and transmission (at the
+ cost of difficulty with standard Unix tools). Only used for messages
+ received using the ESMTP CHUNKING option, and when a new main-section
+ option "spool_wireformat" (false by default) is set.
+
+ 8. New main configuration option "commandline_checks_require_admin" to
+ restrict who can use various introspection options.
+
+ 9. New option modifier "no_check" for quota and quota_filecount
+ appendfile transport.
+
+10. Variable $smtp_command_history returning a comma-sep list of recent
+ SMTP commands.
+
+11. Millisecond timetamps in logs, on log_selector "millisec". Also affects
+ log elements QT, DT and D, and timstamps in debug output.
+
+12. TCP Fast Open logging. As a server, logs when the SMTP banner was sent
+ while still in SYN_RECV state; as a client logs when the connection
+ is opened with a TFO cookie.
+
+13. DKIM support for multiple signing, by domain and/or key-selector.
+ DKIM support for multiple hashes, and for alternate-identity tags.
+ Builtin macro with default list of signed headers.
+ Better syntax for specifying oversigning.
+ The DKIM ACL can override verification status, and status is visible in
+ the data ACL.
+
+14. Exipick understands -C|--config for an alternative Exim
+ configuration file.
+
+15. TCP Fast Open used, with data-on-SYN, for client SMTP via SOCKS5 proxy,
+ for ${readsocket } expansions, and for ClamAV.
+
+16. The "-be" expansion test mode now supports macros. Macros are expanded
+ in test lines, and new macros can be defined.
+
+17. Support for server-side dual-certificate-stacks (eg. RSA + ECDSA).
+
+
Version 4.89
------------
It adds new expansion variables $dmarc_ar_header, $dmarc_status,
$dmarc_status_text, and $dmarc_used_domain. It adds a new acl modifier
dmarc_status. It adds new control flags dmarc_disable_verify and
- dmarc_enable_forensic.
+ dmarc_enable_forensic. The default for the dmarc_tld_file option is
+ "/etc/exim/opendmarc.tlds" and can be changed via EDITME.
22. Add expansion variable $authenticated_fail_id, which is the username
provided to the authentication method which failed. It is available
longest line that was received as part of the message, not counting the
line termination character(s).
- 7. Host lists can now include +ignore_defer and +include_defer, analagous to
+ 7. Host lists can now include +ignore_defer and +include_defer, analogous to
+ignore_unknown and +include_unknown. These options should be used with
care, probably only in non-critical host lists such as whitelists.
acl_smtp_auth string* unset main 4.00
acl_smtp_connect string* unset main 4.11
acl_smtp_data string* unset main 4.00
-acl_smtp_data_prdr string* unset main 4.82 with experimental_prdr
+acl_smtp_data_prdr string* unset main 4.82 with experimental_prdr, 4.83 unless disable_prdr
acl_smtp_dkim string* unset main 4.70 unless disable_dkim
acl_smtp_etrn string* unset main 4.00
acl_smtp_expn string* unset main 4.00
allow_mx_to_ip boolean false main 3.14
allow_symlink boolean false appendfile
allow_utf8_domains boolean false main 4.14
+arc_sign string* unset smtp 4.91 with Experimental_ARC
auth_advertise_hosts host list "*" main 4.00
authenticated_sender string* unset smtp 4.14
authenticated_sender_force boolean false smtp 4.61
command_group string unset queryprogram 4.00
command_timeout time 5m smtp
command_user string unset queryprogram 4.00
+commandline_checks_require_admin boolean false main 4.90
condition string* unset routers 4.00
connect_timeout time 0s smtp 1.60
connection_max_messages integer 500 smtp 4.00 replaces batch_max
daemon_smtp_ports string unset main 1.75 pluralised in 4.21
daemon_startup_retries int 9 main 4.52
daemon_startup_sleep time 30s main 4.52
+dane_require_tls_ciphers string* unset smtp 4.91
data string unset redirect 4.00
data_timeout time 5m smtp
debug_print string* unset authenticators 4.00
unset routers 4.00
unset transports 2.00
-debug_store boolean false main 4.90
+debug_store boolean false main 4.90
delay_after_cutoff boolean true smtp
delay_warning time list 24h main
delay_warning_condition string* + main 1.73
dkim_selector string* unset smtp 4.70
dkim_sign_headers string* (RFC4871) smtp 4.70
dkim_strict string* unset smtp 4.70
+dkim_timestamps integer* unset smtp 4.92
dkim_verify_signers string* $dkim_signers main 4.70
directory string* unset appendfile
directory_file string* + appendfile
dmarc_tld_file string unset main 4.82 if experimental_dmarc
dns_again_means_nonexist domain list unset main 1.89
dns_check_names_pattern string + main 2.11
+dns_cname_loops integer 0 main 4.92 Set to 9 for older behaviour
dns_csa_search_limit integer 5 main 4.60
dns_csa_use_reverse boolean true main 4.60
dns_dnssec_ok integer -1 main 4.82
hosts_max_try integer 5 smtp 3.20
hosts_max_try_hardlimit integer 50 smtp 4.50
hosts_nopass_tls host list unset smtp 4.00
+hosts_noproxy_tls host list "*" smtp 4.90
hosts_override boolean false smtp 2.11
+hosts_pipe_connect host_list unset smtp 4.93 if experimental_pipe_connect
hosts_randomize boolean false manualroute 4.00
false smtp 3.14
hosts_require_auth host list unset smtp 4.00
+hosts_require_dane host list unset smtp 4.91 (4.85 experimental)
hosts_require_ocsp host list unset smtp 4.82 if experimental_ocsp
hosts_require_tls host list unset smtp 3.20
hosts_treat_as_local domain list unset main 1.95
hosts_try_auth host list unset smtp 4.00
+hosts_try_dane host list unset smtp 4.91 (4.85 experimental)
hosts_try_fastopen host list unset smtp 4.88
hosts_try_prdr host list unset smtp 4.82 if experimental_prdr
ibase_servers string unset main 4.23
pipe_as_creator boolean false pipe
pipe_transport string* unset redirect 4.00
pipelining_advertise_hosts host list "*" main 4.14
+pipelining__connect_advertise_hosts host list "*" main 4.92 if experimental_pipe_connect
port integer 0 iplookup 4.00
string "smtp" smtp
preserve_message_logs boolean false main
server_mail_auth_condition string* unset authenticators 3.22
server_mech string public_name cyrus_sasl,gsasl 4.43 (cyrus-only) 4.80 (others)
server_password string unset gsasl 4.80
+server_param1 string* unset tls (auth) 4.86
+server_param2 string* unset tls (auth) 4.86
+server_param3 string* unset tls (auth) 4.86
server_prompts string* unset plaintext 3.10
server_realm string unset cyrus_sasl,gsasl 4.43 (cyrus-only) 4.80 (others)
server_scram_iter string* unset gsasl 4.80
spamd_address string* + main 4.50 with content scan
split_spool_directory boolean false main 1.70
spool_directory string ++ main
+spool_wireformat boolean false main 4.90
sqlite_lock_timeout time 5s main 4.53
strict_acl_vars boolean false main 4.64
srv_fail_domains domain list unset dnslookup 4.43
timeout_frozen_after time 0s main 3.20
timezone string + main 3.15
tls_advertise_hosts host list * main 3.20
+tls_advertise_requiretls host list * main 4.92 if experimental_requiretls
tls_certificate string* unset main 3.20
unset smtp 3.20
tls_dh_max_bits integer 2236 main 4.80
use_shell boolean false pipe 1.70
user string + routers 4.00
unset transports 4.00 replaces individual options
+utf8_downconvert integer unset smtp 4.92 if SUPPORT_I18N
uucp_from_pattern string + main 1.75
uucp_from_sender string* "$1" main 1.75
verify boolean true routers 4.00
Berkeley DB 4.x
---------------
-The 4.x series is a developement of the 2.x and 3.x series, and the above
+The 4.x series is a development of the 2.x and 3.x series, and the above
comments apply.
test data. A line history is supported.
.sp
Long expansion expressions can be split over several lines by using backslash
-continuations. As in Exim's run time configuration, white space at the start of
+continuations. As in Exim's runtime configuration, white space at the start of
continuation lines is ignored. Each argument or data line is passed through the
string expansion mechanism, and the result is output. Variable values from the
configuration file (for example, \fI$qualify_domain\fP) are available, but no
files or databases you are using, you must exit and restart Exim before trying
the same lookup again. Otherwise, because each Exim process caches the results
of lookups, you will just get the same result as before.
+.sp
+Macro processing is done on lines before string\-expansion: new macros can be
+defined and macros will be expanded.
+Because macros in the config file are often used for secrets, those are only
+available to admin users.
.TP 10
\fB\-bem\fP <\fIfilename\fP>
This option operates like \fB\-be\fP except that it must be followed by the name
If \fBconfig\fP is given as an argument, the config is
output, as it was parsed, any include file resolved, any comment removed.
.sp
-If \fBconfig_file\fP is given as an argument, the name of the run time
+If \fBconfig_file\fP is given as an argument, the name of the runtime
configuration file is output. (\fBconfigure_file\fP works too, for
backward compatibility.)
If a list of configuration files was supplied, the value that is output here
are available, similarly to the drivers. Because macros are sometimes used
for storing passwords, this option is restricted.
The output format is one item per line.
+For the "\-bP macro <name>" form, if no such macro is found
+the exit status will be nonzero.
.TP 10
\fB\-bp\fP
This option requests a listing of the contents of the mail queue on the
admin user. However, the \fBqueue_list_requires_admin\fP option can be set false
to allow any user to see the queue.
.sp
-Each message on the queue is displayed as in the following example:
+Each message in the queue is displayed as in the following example:
.sp
25m 2.9K 0t5C6f\-0000c8\-00 <alice@wonderland.fict.example>
red.king@looking\-glass.fict.example
<other addresses>
.sp
-The first line contains the length of time the message has been on the queue
+The first line contains the length of time the message has been in the queue
(in this case 25 minutes), the size of the message (2.9K), the unique local
identifier for the message, and the message sender, as contained in the
envelope. For bounce messages, the sender address is empty, and appears as
of just "D".
.TP 10
\fB\-bpc\fP
-This option counts the number of messages on the queue, and writes the total
+This option counts the number of messages in the queue, and writes the total
to the standard output. It is restricted to admin users, unless
\fBqueue_list_requires_admin\fP is set false.
.TP 10
\fB\-bpr\fP
This option operates like \fB\-bp\fP, but the output is not sorted into
chronological order of message arrival. This can speed it up when there are
-lots of messages on the queue, and is particularly useful if the output is
+lots of messages in the queue, and is particularly useful if the output is
going to be post\-processed in a way that doesn't need the sorting.
.TP 10
\fB\-bpra\fP
number, and compilation date of the \fIexim\fP binary to the standard output.
It also lists the DBM library that is being used, the optional modules (such as
specific lookup types), the drivers that are included in the binary, and the
-name of the run time configuration file that is in use.
+name of the runtime configuration file that is in use.
.sp
As part of its operation, \fB\-bV\fP causes Exim to read and syntax check its
configuration file. However, this is a static check only. It cannot check
which the daemon will exit, which should cause inetd to listen once more.
.TP 10
\fB\-C\fP <\fIfilelist\fP>
-This option causes Exim to find the run time configuration file from the given
+This option causes Exim to find the runtime configuration file from the given
list instead of from the list specified by the CONFIGURE_FILE
-compile\-time setting. Usually, the list will consist of just a single file
-name, but it can be a colon\-separated list of names. In this case, the first
+compile\-time setting. Usually, the list will consist of just a single filename,
+but it can be a colon\-separated list of names. In this case, the first
file that exists is used. Failure to open an existing file stops Exim from
proceeding any further along the list, and an error is generated.
.sp
running as the Exim user, so when it re\-executes to regain privilege for the
delivery, the use of \fB\-C\fP causes privilege to be lost. However, root can
test reception and delivery using two separate commands (one to put a message
-on the queue, using \fB\-odq\fP, and another to do the delivery, using \fB\-M\fP).
+in the queue, using \fB\-odq\fP, and another to do the delivery, using \fB\-M\fP).
.sp
If ALT_CONFIG_PREFIX is defined in Local/Makefile, it specifies a
prefix string with which any file named in a \fB\-C\fP command line option
-must start. In addition, the file name must not contain the sequence /../.
+must start. In addition, the filename must not contain the sequence /../.
However, if the value of the \fB\-C\fP option is identical to the value of
CONFIGURE_FILE in Local/Makefile, Exim ignores \fB\-C\fP and proceeds as
usual. There is no default setting for ALT_CONFIG_PREFIX; when it is
-unset, any file name can be used with \fB\-C\fP.
+unset, any filename can be used with \fB\-C\fP.
.sp
ALT_CONFIG_PREFIX can be used to confine alternative configuration files
to a directory to which only root has access. This prevents someone who has
local_scan can be used by local_scan()
lookup general lookup code and all lookups
memory memory handling
- pid add pid to debug output lines
+ noutf8 modifier: avoid UTF\-8 line\-drawing
+ pid modifier: add pid to debug output lines
process_info setting info for the process log
queue_run queue runs
receive general message reception logic
retry retry handling
rewrite address rewriting
route address routing
- timestamp add timestamp to debug output lines
+ timestamp modifier: add timestamp to debug output lines
tls TLS logic
transport transports
uid changes of uid/gid and looking up uid/gid
The timestamp selector causes the current time to be inserted at the start
of all debug output lines. This can be useful when trying to track down delays
in processing.
+The noutf8 selector disables the use of
+UTF\-8 line\-drawing characters to group related information.
+When disabled. ascii\-art is used instead.
+Using the +all option does not set this modifier,
.sp
If the \fBdebug_print\fP option is set in any driver, it produces output whenever
any debugging is selected, or if \fB\-v\fP is used.
by Exim in conjunction with the \fB\-MC\fP option. It signifies that the
remote host supports the ESMTP DSN extension.
.TP 10
-\fB\-MCG\fP
+\fB\-MCG\fP <\fIqueue name\fP>
This option is not intended for use by external callers. It is used internally
by Exim in conjunction with the \fB\-MC\fP option. It signifies that an
-alternate queue is used, named by the following option.
+alternate queue is used, named by the following argument.
+.TP 10
+\fB\-MCK\fP
+This option is not intended for use by external callers. It is used internally
+by Exim in conjunction with the \fB\-MC\fP option. It signifies that a
+remote host supports the ESMTP CHUNKING extension.
.TP 10
\fB\-MCP\fP
This option is not intended for use by external callers. It is used internally
by Exim in conjunction with the \fB\-MC\fP option, and passes on the fact that the
host to which Exim is connected supports TLS encryption.
.TP 10
+\fB\-MCt\fP <\fIIP address\fP> <\fIport\fP> <\fIcipher\fP>
+This option is not intended for use by external callers. It is used internally
+by Exim in conjunction with the \fB\-MC\fP option, and passes on the fact that the
+connection is being proxied by a parent process for handling TLS encryption.
+The arguments give the local address and port being proxied, and the TLS cipher.
+.TP 10
\fB\-Mc\fP <\fImessage id\fP> <\fImessage id\fP> ...
-This option requests Exim to run a delivery attempt on each message in turn,
+This option requests Exim to run a delivery attempt on each message, in turn,
but unlike the \fB\-M\fP option, it does check for retry hints, and respects any
that are found. This option is not very useful to external callers. It is
provided mainly for internal use by Exim when it needs to re\-invoke itself in
bounce messages are sent; each message is simply forgotten. However, if any of
the messages are active, their status is not altered. This option can be used
only by an admin user or by the user who originally caused the message to be
-placed on the queue.
+placed in the queue.
.TP 10
\fB\-Mset\fP <\fImessage id\fP>
This option is useful only in conjunction with \fB\-be\fP (that is, when testing
.TP 10
\fB\-oA\fP <\fIfile name\fP>
This option is used by Sendmail in conjunction with \fB\-bi\fP to specify an
-alternative alias file name. Exim handles \fB\-bi\fP differently; see the
+alternative alias filename. Exim handles \fB\-bi\fP differently; see the
description above.
.TP 10
\fB\-oB\fP <\fIn\fP>
false and one of the queueing options in the configuration file is in effect.
.sp
If there is a temporary delivery error during foreground delivery, the
-message is left on the queue for later delivery, and the original reception
+message is left in the queue for later delivery, and the original reception
process exits.
.TP 10
\fB\-odi\fP
This option applies to all modes in which Exim accepts incoming messages,
including the listening daemon. It specifies that the accepting process should
not automatically start a delivery process for each message received. Messages
-are placed on the queue, and remain there until a subsequent queue runner
+are placed in the queue, and remain there until a subsequent queue runner
process encounters them. There are several configuration options (such as
\fBqueue_only\fP) that can be used to queue incoming messages under certain
conditions. This option overrides all of them and also \fB\-odqs\fP. It always
message, in the background by default, but in the foreground if \fB\-odi\fP is
also present. The recipient addresses are routed, and local deliveries are done
in the normal way. However, if any SMTP deliveries are required, they are not
-done at this time, so the message remains on the queue until a subsequent queue
+done at this time, so the message remains in the queue until a subsequent queue
runner process encounters it. Because routing was done, Exim knows which
messages are waiting for which hosts, and so a number of messages for the same
host can be sent in a single SMTP connection. The \fBqueue_smtp_domains\fP
or \fB\-bs\fP is used. For \fB\-bh\fP, the protocol is forced to one of the standard
SMTP protocol names. For \fB\-bs\fP, the protocol is always "local\-" followed by
one of those same names. For \fB\-bS\fP (batched SMTP) however, the protocol can
-be set by \fB\-oMr\fP.
+be set by \fB\-oMr\fP. Repeated use of this option is not supported.
.TP 10
\fB\-oMs\fP <\fIhost name\fP>
See \fB\-oMa\fP above for general remarks about the \fB\-oM\fP options. The \fB\-oMs\fP
\fB\-oX\fP <\fInumber or string\fP>
This option is relevant only when the \fB\-bd\fP (start listening daemon) option
is also given. It controls which ports and interfaces the daemon uses. When \fB\-oX\fP is used to start a daemon, no pid
-file is written unless \fB\-oP\fP is also present to specify a pid file name.
+file is written unless \fB\-oP\fP is also present to specify a pid filename.
.TP 10
\fB\-pd\fP
This option applies when an embedded Perl interpreter is linked with Exim. It overrides the setting of the \fBperl_at_start\fP
Note the Exim already has two private options, \fB\-pd\fP and \fB\-ps\fP, that refer
to embedded Perl. It is therefore impossible to set a protocol value of d
or s using this option (but that does not seem a real limitation).
+Repeated use of this option is not supported.
.TP 10
\fB\-q\fP
This option is normally restricted to admin users. However, there is a
\fB\-q[q]i...\fP
If the \fIi\fP flag is present, the queue runner runs delivery processes only for
those messages that haven't previously been tried. (\fIi\fP stands for "initial
-delivery".) This can be helpful if you are putting messages on the queue using
+delivery".) This can be helpful if you are putting messages in the queue using
\fB\-odq\fP and want a queue runner just to process the new messages.
.TP 10
\fB\-q[q][i]f...\fP
.TP 10
\fB\-q[q][i][f[f]]l\fP
The \fIl\fP (the letter "ell") flag specifies that only local deliveries are to
-be done. If a message requires any remote deliveries, it remains on the queue
+be done. If a message requires any remote deliveries, it remains in the queue
for later delivery.
.TP 10
\fB\-q[q][i][f[f]][l][G<name>[/<time>]]]\fP
-Sender Policy Framework (SPF) support
---------------------------------------------------------------
-
-To learn more about SPF, visit http://www.openspf.org. This
-document does not explain the SPF fundamentals, you should
-read and understand the implications of deploying SPF on your
-system before doing so.
-
-SPF support is added via the libspf2 library. Visit
-
- http://www.libspf2.org/
-
-to obtain a copy, then compile and install it. By default,
-this will put headers in /usr/local/include and the static
-library in /usr/local/lib.
-
-To compile Exim with SPF support, set these additional flags in
-Local/Makefile:
-
-EXPERIMENTAL_SPF=yes
-CFLAGS=-DSPF -I/usr/local/include
-EXTRALIBS_EXIM=-L/usr/local/lib -lspf2
-
-This assumes that the libspf2 files are installed in
-their default locations.
-
-You can now run SPF checks in incoming SMTP by using the "spf"
-ACL condition in either the MAIL, RCPT or DATA ACLs. When
-using it in the RCPT ACL, you can make the checks dependent on
-the RCPT address (or domain), so you can check SPF records
-only for certain target domains. This gives you the
-possibility to opt-out certain customers that do not want
-their mail to be subject to SPF checking.
-
-The spf condition takes a list of strings on its right-hand
-side. These strings describe the outcome of the SPF check for
-which the spf condition should succeed. Valid strings are:
-
- o pass The SPF check passed, the sending host
- is positively verified by SPF.
- o fail The SPF check failed, the sending host
- is NOT allowed to send mail for the domain
- in the envelope-from address.
- o softfail The SPF check failed, but the queried
- domain can't absolutely confirm that this
- is a forgery.
- o none The queried domain does not publish SPF
- records.
- o neutral The SPF check returned a "neutral" state.
- This means the queried domain has published
- a SPF record, but wants to allow outside
- servers to send mail under its domain as well.
- This should be treated like "none".
- o permerror This indicates a syntax error in the SPF
- record of the queried domain. You may deny
- messages when this occurs. (Changed in 4.83)
- o temperror This indicates a temporary error during all
- processing, including Exim's SPF processing.
- You may defer messages when this occurs.
- (Changed in 4.83)
- o err_temp Same as permerror, deprecated in 4.83, will be
- removed in a future release.
- o err_perm Same as temperror, deprecated in 4.83, will be
- removed in a future release.
-
-You can prefix each string with an exclamation mark to invert
-its meaning, for example "!fail" will match all results but
-"fail". The string list is evaluated left-to-right, in a
-short-circuit fashion. When a string matches the outcome of
-the SPF check, the condition succeeds. If none of the listed
-strings matches the outcome of the SPF check, the condition
-fails.
-
-Here is an example to fail forgery attempts from domains that
-publish SPF records:
-
-/* -----------------
-deny message = $sender_host_address is not allowed to send mail from ${if def:sender_address_domain {$sender_address_domain}{$sender_helo_name}}. \
- Please see http://www.openspf.org/Why?scope=${if def:sender_address_domain {mfrom}{helo}};identity=${if def:sender_address_domain {$sender_address}{$sender_helo_name}};ip=$sender_host_address
- spf = fail
---------------------- */
-
-You can also give special treatment to specific domains:
-
-/* -----------------
-deny message = AOL sender, but not from AOL-approved relay.
- sender_domains = aol.com
- spf = fail:neutral
---------------------- */
-
-Explanation: AOL publishes SPF records, but is liberal and
-still allows non-approved relays to send mail from aol.com.
-This will result in a "neutral" state, while mail from genuine
-AOL servers will result in "pass". The example above takes
-this into account and treats "neutral" like "fail", but only
-for aol.com. Please note that this violates the SPF draft.
-
-When the spf condition has run, it sets up several expansion
-variables.
-
- $spf_header_comment
- This contains a human-readable string describing the outcome
- of the SPF check. You can add it to a custom header or use
- it for logging purposes.
-
- $spf_received
- This contains a complete Received-SPF: header that can be
- added to the message. Please note that according to the SPF
- draft, this header must be added at the top of the header
- list. Please see section 10 on how you can do this.
-
- Note: in case of "Best-guess" (see below), the convention is
- to put this string in a header called X-SPF-Guess: instead.
-
- $spf_result
- This contains the outcome of the SPF check in string form,
- one of pass, fail, softfail, none, neutral, permerror or
- temperror.
-
- $spf_smtp_comment
- This contains a string that can be used in a SMTP response
- to the calling party. Useful for "fail".
-
-In addition to SPF, you can also perform checks for so-called
-"Best-guess". Strictly speaking, "Best-guess" is not standard
-SPF, but it is supported by the same framework that enables SPF
-capability. Refer to http://www.openspf.org/FAQ/Best_guess_record
-for a description of what it means.
-
-To access this feature, simply use the spf_guess condition in place
-of the spf one. For example:
-
-/* -----------------
-deny message = $sender_host_address doesn't look trustworthy to me
- spf_guess = fail
---------------------- */
-
-In case you decide to reject messages based on this check, you
-should note that although it uses the same framework, "Best-guess"
-is NOT SPF, and therefore you should not mention SPF at all in your
-reject message.
-
-When the spf_guess condition has run, it sets up the same expansion
-variables as when spf condition is run, described above.
-
-Additionally, since Best-guess is not standardized, you may redefine
-what "Best-guess" means to you by redefining spf_guess variable in
-global config. For example, the following:
-
-/* -----------------
-spf_guess = v=spf1 a/16 mx/16 ptr ?all
---------------------- */
-
-would relax host matching rules to a broader network range.
-
-
-A lookup expansion is also available. It takes an email
-address as the key and an IP address as the database:
-
- ${lookup {username@domain} spf {ip.ip.ip.ip}}
-
-The lookup will return the same result strings as they can appear in
-$spf_result (pass,fail,softfail,neutral,none,err_perm,err_temp).
-Currently, only IPv4 addresses are supported.
-
-
-
SRS (Sender Rewriting Scheme) Support
--------------------------------------------------------------
Use a reasonable IP. eg. one the sending cluster actually uses.
+
+
DMARC Support
--------------------------------------------------------------
are in /usr/local/lib.
1. To compile Exim with DMARC support, you must first enable SPF.
-Please read the above section on enabling the EXPERIMENTAL_SPF
+Please read the Local/Makefile comments on enabling the SUPPORT_SPF
feature. You must also have DKIM support, so you cannot set the
DISABLE_DKIM feature. Once both of those conditions have been met
you can enable DMARC in Local/Makefile:
during domain parsing. Maintained by Mozilla,
the most current version can be downloaded
from a link at http://publicsuffix.org/list/.
+ See also util/renew-opendmarc-tlds.sh script.
Optional:
dmarc_history_file Defines the location of a file to log results
directory of this file is writable by the user
exim runs as.
-dmarc_forensic_sender The email address to use when sending a
+dmarc_forensic_sender Alternate email address to use when sending a
forensic report detailing alignment failures
if a sender domain's dmarc record specifies it
and you have configured Exim to send them.
- Default: do-not-reply@$default_hostname
+
+ If set, this is expanded and used for the
+ From: header line; the address is extracted
+ from it and used for the envelope from.
+ If not set, the From: header is expanded from
+ the dsn_from option, and <> is used for the
+ envelope from.
+
+ Default: unset.
3. By default, the DMARC processing will run for any remote,
supports, including LDAP, Postgres, MySQL, etc, as long as the
result is a list of colon-separated strings.
+Performing the check sets up information used by the
+${authresults } expansion item.
+
Several expansion variables are set before the DATA ACL is
processed, and you can use them in this ACL. The following
expansion variables are available:
are "none", "reject" and "quarantine". It is blank when there
is any error, including no DMARC record.
- o $dmarc_ar_header
- This is the entire Authentication-Results header which you can
- add using an add_header modifier.
+A now-redundant variable $dmarc_ar_header has now been withdrawn.
+Use the ${authresults } expansion instead.
5. How to enable DMARC advanced operation:
warn dmarc_status = accept : none : off
!authenticated = *
log_message = DMARC DEBUG: $dmarc_status $dmarc_used_domain
- add_header = $dmarc_ar_header
warn dmarc_status = !accept
!authenticated = *
!authenticated = *
message = Message from $dmarc_used_domain failed sender's DMARC policy, REJECT
-
-
-DANE
-------------------------------------------------------------
-DNS-based Authentication of Named Entities, as applied
-to SMTP over TLS, provides assurance to a client that
-it is actually talking to the server it wants to rather
-than some attacker operating a Man In The Middle (MITM)
-operation. The latter can terminate the TLS connection
-you make, and make another one to the server (so both
-you and the server still think you have an encrypted
-connection) and, if one of the "well known" set of
-Certificate Authorities has been suborned - something
-which *has* been seen already (2014), a verifiable
-certificate (if you're using normal root CAs, eg. the
-Mozilla set, as your trust anchors).
-
-What DANE does is replace the CAs with the DNS as the
-trust anchor. The assurance is limited to a) the possibility
-that the DNS has been suborned, b) mistakes made by the
-admins of the target server. The attack surface presented
-by (a) is thought to be smaller than that of the set
-of root CAs.
-
-It also allows the server to declare (implicitly) that
-connections to it should use TLS. An MITM could simply
-fail to pass on a server's STARTTLS.
-
-DANE scales better than having to maintain (and
-side-channel communicate) copies of server certificates
-for every possible target server. It also scales
-(slightly) better than having to maintain on an SMTP
-client a copy of the standard CAs bundle. It also
-means not having to pay a CA for certificates.
-
-DANE requires a server operator to do three things:
-1) run DNSSEC. This provides assurance to clients
-that DNS lookups they do for the server have not
-been tampered with. The domain MX record applying
-to this server, its A record, its TLSA record and
-any associated CNAME records must all be covered by
-DNSSEC.
-2) add TLSA DNS records. These say what the server
-certificate for a TLS connection should be.
-3) offer a server certificate, or certificate chain,
-in TLS connections which is traceable to the one
-defined by (one of?) the TSLA records
-
-There are no changes to Exim specific to server-side
-operation of DANE.
-
-The TLSA record for the server may have "certificate
-usage" of DANE-TA(2) or DANE-EE(3). The latter specifies
-the End Entity directly, i.e. the certificate involved
-is that of the server (and should be the sole one transmitted
-during the TLS handshake); this is appropriate for a
-single system, using a self-signed certificate.
- DANE-TA usage is effectively declaring a specific CA
-to be used; this might be a private CA or a public,
-well-known one. A private CA at simplest is just
-a self-signed certificate which is used to sign
-cerver certificates, but running one securely does
-require careful arrangement. If a private CA is used
-then either all clients must be primed with it, or
-(probably simpler) the server TLS handshake must transmit
-the entire certificate chain from CA to server-certificate.
-If a public CA is used then all clients must be primed with it
-(losing one advantage of DANE) - but the attack surface is
-reduced from all public CAs to that single CA.
-DANE-TA is commonly used for several services and/or
-servers, each having a TLSA query-domain CNAME record,
-all of which point to a single TLSA record.
-
-The TLSA record should have a Selector field of SPKI(1)
-and a Matching Type field of SHA2-512(2).
-
-At the time of writing, https://www.huque.com/bin/gen_tlsa
-is useful for quickly generating TLSA records; and commands like
-
- openssl x509 -in -pubkey -noout <certificate.pem \
- | openssl rsa -outform der -pubin 2>/dev/null \
- | openssl sha512 \
- | awk '{print $2}'
-
-are workable for 4th-field hashes.
-
-For use with the DANE-TA model, server certificates
-must have a correct name (SubjectName or SubjectAltName).
-
-The use of OCSP-stapling should be considered, allowing
-for fast revocation of certificates (which would otherwise
-be limited by the DNS TTL on the TLSA records). However,
-this is likely to only be usable with DANE-TA. NOTE: the
-default of requesting OCSP for all hosts is modified iff
-DANE is in use, to:
-
- hosts_request_ocsp = ${if or { {= {0}{$tls_out_tlsa_usage}} \
- {= {4}{$tls_out_tlsa_usage}} } \
- {*}{}}
-
-The (new) variable $tls_out_tlsa_usage is a bitfield with
-numbered bits set for TLSA record usage codes.
-The zero above means DANE was not in use,
-the four means that only DANE-TA usage TLSA records were
-found. If the definition of hosts_request_ocsp includes the
-string "tls_out_tlsa_usage", they are re-expanded in time to
-control the OCSP request.
-
-This modification of hosts_request_ocsp is only done if
-it has the default value of "*". Admins who change it, and
-those who use hosts_require_ocsp, should consider the interaction
-with DANE in their OCSP settings.
-
-
-For client-side DANE there are two new smtp transport options,
-hosts_try_dane and hosts_require_dane.
-[ should they be domain-based rather than host-based? ]
-
-Hosts_require_dane will result in failure if the target host
-is not DNSSEC-secured.
-
-DANE will only be usable if the target host has DNSSEC-secured
-MX, A and TLSA records.
-
-A TLSA lookup will be done if either of the above options match
-and the host-lookup succeeded using dnssec.
-If a TLSA lookup is done and succeeds, a DANE-verified TLS connection
-will be required for the host. If it does not, the host will not
-be used; there is no fallback to non-DANE or non-TLS.
-
-If DANE is requested and useable (see above) the following transport
-options are ignored:
- hosts_require_tls
- tls_verify_hosts
- tls_try_verify_hosts
- tls_verify_certificates
- tls_crl
- tls_verify_cert_hostnames
-
-If DANE is not usable, whether requested or not, and CA-anchored
-verification evaluation is wanted, the above variables should be set
-appropriately.
-
-Currently dnssec_request_domains must be active (need to think about that)
-and dnssec_require_domains is ignored.
-
-If verification was successful using DANE then the "CV" item
-in the delivery log line will show as "CV=dane".
-
-There is a new variable $tls_out_dane which will have "yes" if
-verification succeeded using DANE and "no" otherwise (only useful
-in combination with EXPERIMENTAL_EVENT), and a new variable
-$tls_out_tlsa_usage (detailed above).
+ warn add_header = :at_start:${authresults {$primary_hostname}}
The spool files can then be processed by external processes and then
requeued into exim spool directories for final delivery.
+However, note carefully the warnings in the main documentation on
+qpool file formats.
The motivation/inspiration for the transport is to allow external
processes to access email queued by exim and have access to all the
The transport only takes one option:
* directory - This is used to specify the directory messages should be
-copied to
+copied to. Expanded.
The generic transport options (body_only, current_directory, disable_logging,
debug_print, delivery_date_add, envelope_to_add, event_action, group,
Experimental_QUEUEFILE in the line "Support for:".
+ARC support
+-----------
+Specification: https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-11
+Note that this is not an RFC yet, so may change.
+
+ARC is intended to support the utility of SPF and DKIM in the presence of
+intermediaries in the transmission path - forwarders and mailinglists -
+by establishing a cryptographically-signed chain in headers.
+
+Normally one would only bother doing ARC-signing when functioning as
+an intermediary. One might do verify for local destinations.
+
+ARC uses the notion of a "ADministrative Management Domain" (ADMD).
+Described in RFC 5598 (section 2.3), this is essentially the set of
+mail-handling systems that the mail transits. A label should be chosen to
+identify the ADMD. Messages should be ARC-verified on entry to the ADMD,
+and ARC-signed on exit from it.
+
+
+Verification
+--
+An ACL condition is provided to perform the "verifier actions" detailed
+in section 6 of the above specification. It may be called from the DATA ACL
+and succeeds if the result matches any of a given list.
+It also records the highest ARC instance number (the chain size)
+and verification result for later use in creating an Authentication-Results:
+standard header.
+
+ verify = arc/<acceptable_list> none:fail:pass
+
+ add_header = :at_start:${authresults {<admd-identifier>}}
+
+ Note that it would be wise to strip incoming messages of A-R headers
+ that claim to be from our own <admd-identifier>.
+
+There are four new variables:
+
+ $arc_state One of pass, fail, none
+ $arc_state_reason (if fail, why)
+ $arc_domains colon-sep list of ARC chain domains, in chain order.
+ problematic elements may have empty list elements
+ $arc_oldest_pass lowest passing instance number of chain
+
+Example:
+ logwrite = oldest-p-ams: <${reduce {$lh_ARC-Authentication-Results:} \
+ {} \
+ {${if = {$arc_oldest_pass} \
+ {${extract {i}{${extract {1}{;}{$item}}}}} \
+ {$item} {$value}}} \
+ }>
+
+Receive log lines for an ARC pass will be tagged "ARC".
+
+
+Signing
+--
+arc_sign = <admd-identifier> : <selector> : <privkey> [ : <options> ]
+An option on the smtp transport, which constructs and prepends to the message
+an ARC set of headers. The textually-first Authentication-Results: header
+is used as a basis (you must have added one on entry to the ADMD).
+Expanded as a whole; if unset, empty or forced-failure then no signing is done.
+If it is set, all of the first three elements must be non-empty.
+
+The fourth element is optional, and if present consists of a comma-separated list
+of options. The options implemented are
+
+ timestamps Add a t= tag to the generated AMS and AS headers, with the
+ current time.
+ expire[=<val>] Add an x= tag to the generated AMS header, with an expiry time.
+ If the value <val> is an plain number it is used unchanged.
+ If it starts with a '+' then the following number is added
+ to the current time, as an offset in seconds.
+ If a value is not given it defaults to a one month offset.
+
+[As of writing, gmail insist that a t= tag on the AS is mandatory]
+
+Caveats:
+ * There must be an Authentication-Results header, presumably added by an ACL
+ while receiving the message, for the same ADMD, for arc_sign to succeed.
+ This requires careful coordination between inbound and outbound logic.
+
+ Only one A-R header is taken account of. This is a limitation versus
+ the ARC spec (which says that all A-R headers from within the ADMD must
+ be used).
+
+ * If passing a message to another system, such as a mailing-list manager
+ (MLM), between receipt and sending, be wary of manipulations to headers made
+ by the MLM.
+ + For instance, Mailman with REMOVE_DKIM_HEADERS==3 might improve
+ deliverability in a pre-ARC world, but that option also renames the
+ Authentication-Results header, which breaks signing.
+
+ * Even if you use multiple DKIM keys for different domains, the ARC concept
+ should try to stick to one ADMD, so pick a primary domain and use that for
+ AR headers and outbound signing.
+
+Signing is not compatible with cutthrough delivery; any (before expansion)
+value set for the option will result in cutthrough delivery not being
+used via the transport in question.
+
+
+
+
+REQUIRETLS support
+------------------
+Ref: https://tools.ietf.org/html/draft-ietf-uta-smtp-require-tls-03
+
+If compiled with EXPERIMENTAL_REQUIRETLS support is included for this
+feature, where a REQUIRETLS option is added to the MAIL command.
+The client may not retry in clear if the MAIL+REQUIRETLS fails (or was never
+offered), and the server accepts an obligation that any onward transmission
+by SMTP of the messages accepted will also use REQUIRETLS - or generate a
+fail DSN.
+
+The Exim implementation includes
+- a main-part option tls_advertise_requiretls; host list, default "*"
+- an observability variable $requiretls returning yes/no
+- an ACL "control = requiretls" modifier for setting the requirement
+- Log lines and Received: headers capitalise the S in the protocol
+ element: "P=esmtpS"
+
+Differences from spec:
+- we support upgrading the requirement for REQUIRETLS, including adding
+ it from cold, within an MTA. The spec only define the sourcing MUA
+ as being able to source the requirement, and makes no mention of upgrade.
+- No support is coded for the RequireTLS header (which can be used
+ to annul DANE and/or STS policiy). [this can _almost_ be done in
+ transport option expansions, but not quite: it requires tha DANE-present
+ but STARTTLS-failing targets fallback to cleartext, which current DANE
+ coding specifically blocks]
+
+Note that REQUIRETLS is only advertised once a TLS connection is achieved
+(in contrast to STARTTLS). If you want to check the advertising, do something
+like "swaks -s 127.0.0.1 -tls -q HELO".
+
+
+
+
+Early pipelining support
+------------------------
+Ref: https://datatracker.ietf.org/doc/draft-harris-early-pipe/
+
+If compiled with EXPERIMENTAL_PIPE_CONNECT support is included for this feature.
+The server advertises the feature in its EHLO response, currently using the name
+"X_PIPE_CONNECT" (this will change, some time in the future).
+A client may cache this information, along with the rest of the EHLO response,
+and use it for later connections. Those later ones can send esmtp commands before
+a banner is received.
+
+Up to 1.5 roundtrip times can be taken out of cleartext connections, 2.5 on
+STARTTLS connections.
+
+In combination with the traditional PIPELINING feature the following example
+sequences are possible (among others):
+
+(client) (server)
+
+EHLO,MAIL,RCPT,DATA ->
+ <- banner,EHLO-resp,MAIL-ack,RCPT-ack,DATA-goahead
+message-data ->
+------
+
+EHLO,MAIL,RCPT,BDAT ->
+ <- banner,EHLO-resp,MAIL-ack,RCPT-ack
+message-data ->
+------
+
+EHLO,STARTTLS ->
+ <- banner,EHLO-resp,TLS-goahead
+TLS1.2-client-hello ->
+ <- TLS-server-hello,cert,hello-done
+client-Kex,change-cipher,finished ->
+ <- change-cipher,finished
+EHLO,MAIL,RCPT,DATA ->
+ <- EHLO-resp,MAIL-ack,RCPT-ack,DATA-goahead
+
+------
+(tls-on-connect)
+TLS1.2-client-hello ->
+ <- TLS-server-hello,cert,hello-done
+client-Kex,change-cipher,finished ->
+ <- change-cipher,finshed
+ <- banner
+EHLO,MAIL,RCPT,DATA ->
+ <- EHLO-resp,MAIL-ack,RCPT-ack,DATA-goahead
+
+Where the initial client packet is SMTP, it can combine with the TCP Fast Open
+feature and be sent in the TCP SYN.
+
+
+A main-section option "pipelining_connect_advertise_hosts" (default: *)
+and an smtp transport option "hosts_pipe_connect" (default: unset)
+control the feature.
+
+If the "pipelining" log_selector is enabled, the "L" field in server <=
+log lines has a period appended if the feature was advertised but not used;
+or has an asterisk appended if the feature was used. In client => lines
+the "L" field has an asterisk appended if the feature was used.
+
+The "retry_data_expire" option controls cache invalidation.
+Entries are also rewritten (or cleared) if the adverised features
+change.
+
+
+NOTE: since the EHLO command must be constructed before the connection is
+made it cannot depend on the interface IP address that will be used.
+Transport configurations should be checked for this. An example avoidance:
+
+ helo_data = ${if def:sending_ip_address \
+ {${lookup dnsdb{>! ptr=$sending_ip_address} \
+ {${sg{$value} {^([^!]*).*\$} {\$1}}} fail}} \
+ {$primary_hostname}}
+
+
+
+
--------------------------------------------------------------
End of file
--------------------------------------------------------------
Copyright (c) 2014 University of Cambridge
-Revision 4.89 07 Mar 2017 PH
+Revision 4.92 10 Feb 2019 PH
-------------------------------------------------------------------------------
This document describes the user interfaces to Exim's in-built mail filtering
facilities, and is copyright (c) University of Cambridge 2014. It corresponds
-to Exim version 4.89.
+to Exim version 4.92.
1.1 Introduction
e.g. logwrite "$tod_log $message_id processed"
It is possible to have more than one logfile command, to specify writing to
-different log files in different circumstances. Writing takes place at the end
-of the file, and a newline character is added to the end of each string if
-there isn't one already there. Newlines can be put in the middle of the string
-by using the "\n" escape sequence. Lines from simultaneous deliveries may get
-interleaved in the file, as there is no interlocking, so you should plan your
-logging with this in mind. However, data should not get lost.
+different log files in different circumstances. A previously opened log is
+closed on a subsequent logfile command. Writing takes place at the end of the
+file, and a newline character is added to the end of each string if there isn't
+one already there. Newlines can be put in the middle of the string by using the
+"\n" escape sequence. Lines from simultaneous deliveries may get interleaved in
+the file, as there is no interlocking, so you should plan your logging with
+this in mind. However, data should not get lost.
3.16 The finish command
So this only applies if you build Exim yourself.
+Insecure versions and ciphers
+-----------------------------
+
+Email delivery to MX hosts is usually done with automatic fallback to
+plaintext if TLS could not be negotiated. There are good historical reasons
+for this. You can and should avoid it by using DNSSEC for signing your DNS
+and publishing TLSA records, to enable "DANE" security. This signals to
+senders that they should be able to verify your certificates, and that they
+should not fallback to cleartext.
+
+In the absence of DANE, trying to increase the security of TLS by removing
+support for older generations of ciphers and protocols will actually _lower_
+the security, because the clients fallback to plaintext and retry anyway. As
+a result, you should give serious thought to enabling older features which are
+no longer default in OpenSSL.
+
+The examples below explicitly enable ssl3 and weak ciphers.
+
+We don't like this, but reality doesn't care and is messy.
+
+
Build
-----
This assumes that `/opt/openssl` is not in use. If it is, pick
something else. `/opt/exim/openssl` perhaps.
+If you pick a location shared amongst various local packages, such as
+`/usr/local` on Linux, then the new OpenSSL will be used by all of those
+packages. If that's what you want, great! If instead you want to
+ensure that only software you explicitly set to use the newer OpenSSL
+will try to use the new OpenSSL, then stick to something like
+`/opt/openssl`.
+
./config --prefix=/opt/openssl --openssldir=/etc/ssl \
-L/opt/openssl/lib -Wl,-R/opt/openssl/lib \
- enable-ssl-trace
+ enable-ssl-trace shared \
+ enable-ssl3 enable-ssl3-method enable-weak-ssl-ciphers
make
make install
+On some systems, the linker uses `-rpath` instead of `-R`; on such systems,
+replace the parameter starting `-Wl` with: `-Wl,-rpath,/opt/openssl/lib`.
+There are more variations on less common systems.
+
You now have an installed OpenSSL under /opt/openssl which will not be
used by any system programs.
choose the pkg-config approach in that file, but also tell Exim to add
the relevant directory into the rpath stamped into the binary:
+ PKG_CONFIG_PATH=/opt/openssl/lib/pkgconfig
+
SUPPORT_TLS=yes
USE_OPENSSL_PC=openssl
- EXTRALIBS_EXIM=-ldl -Wl,-rpath,/opt/openssl/lib
+ LDFLAGS+=-ldl -Wl,-rpath,/opt/openssl/lib
-The -ldl is needed by OpenSSL 1.1+ on Linux and is not needed on most
-other platforms.
+The -ldl is needed by OpenSSL 1.0.2+ on Linux and is not needed on most
+other platforms. The LDFLAGS is needed because `pkg-config` doesn't know
+how to emit information about RPATH-stamping, but we can still leverage
+`pkg-config` for everything else.
-Then tell pkg-config how to find the configuration files for your new
-OpenSSL install, and build Exim:
+Then build Exim:
- export PKG_CONFIG_PATH=/opt/openssl/lib/pkgconfig
make
sudo make install
-(From Exim 4.89, you can put that `PKG_CONFIG_PATH` directly into
- your `Local/Makefile` file.)
-
Confirming
----------
readelf -d $(which exim) | grep RPATH
+It is important to use `RPATH` and not `RUNPATH`!
+
+The gory details about `RUNPATH` (skip unless interested):
+The OpenSSL library might be opened indirectly by some other library
+which Exim depends upon. If the executable does have `RUNPATH` then
+that will inhibit using either of `RPATH` or `RUNPATH` from the
+executable for finding the OpenSSL library when that other library tries
+to load it.
+In fact, if the intermediate library has a `RUNPATH` stamped into it,
+then this will block `RPATH` too, and will create problems with Exim.
+If you're in such a situation, and those libraries were supplied to you
+instead of built by you, then you're reaching the limits of sane
+repairability and it's time to prioritize rebuilding your mail-server
+hosts to be a current OS release which natively pulls in an
+upstream-supported OpenSSL, or stick to the OS releases of Exim.
+
Very Advanced
-------------
Exim Maintainers
-Copyright (c) 2017 University of Cambridge
+Copyright (c) 2018 University of Cambridge
-Revision 4.89 07 Mar 2017 EM
+Revision 4.92 10 Feb 2019 EM
-------------------------------------------------------------------------------
1. Introduction
1.1. Exim documentation
- 1.2. FTP and web sites
+ 1.2. FTP site and websites
1.3. Mailing lists
- 1.4. Exim training
- 1.5. Bug reports
- 1.6. Where to find the Exim distribution
- 1.7. Limitations
- 1.8. Run time configuration
- 1.9. Calling interface
- 1.10. Terminology
+ 1.4. Bug reports
+ 1.5. Where to find the Exim distribution
+ 1.6. Limitations
+ 1.7. Runtime configuration
+ 1.8. Calling interface
+ 1.9. Terminology
2. Incorporated code
3. How Exim receives and delivers mail
5.2. Trusted and admin users
5.3. Command line options
-6. The Exim run time configuration file
+6. The Exim runtime configuration file
6.1. Using a different configuration file
6.2. Configuration file format
7. The default configuration file
- 7.1. Main configuration settings
- 7.2. ACL configuration
- 7.3. Router configuration
- 7.4. Transport configuration
- 7.5. Default retry rule
- 7.6. Rewriting configuration
- 7.7. Authenticators configuration
+ 7.1. Macros
+ 7.2. Main configuration settings
+ 7.3. ACL configuration
+ 7.4. Router configuration
+ 7.5. Transport configuration
+ 7.6. Default retry rule
+ 7.7. Rewriting configuration
+ 7.8. Authenticators configuration
8. Regular expressions
9. File and database lookups
13.1. Starting a listening daemon
13.2. Special IP listening addresses
13.3. Overriding local_interfaces and daemon_smtp_ports
- 13.4. Support for the obsolete SSMTP (or SMTPS) protocol
+ 13.4. Support for the submissions (aka SSMTP or SMTPS) protocol
13.5. IPv6 address scopes
13.6. Disabling IPv6
13.7. Examples of starting a listening daemon
41. The tls authenticator
42. Encrypted SMTP connections using TLS/SSL
- 42.1. Support for the legacy "ssmtp" (aka "smtps") protocol
+ 42.1. Support for the "submissions" (aka "ssmtp" and "smtps") protocol
42.2. OpenSSL vs GnuTLS
42.3. GnuTLS parameter computation
42.4. Requiring specific ciphers in OpenSSL
42.12. Certificates and all that
42.13. Certificate chains
42.14. Self-signed certificates
+ 42.15. DANE
43. Access control lists
56. Format of spool files
56.1. Format of the -H file
+ 56.2. Format of the -D file
-57. Support for DKIM (DomainKeys Identified Mail)
+57. DKIM and SPF
- 57.1. Signing outgoing messages
- 57.2. Verifying DKIM signatures in incoming mail
+ 57.1. DKIM (DomainKeys Identified Mail)
+ 57.2. Signing outgoing messages
+ 57.3. Verifying DKIM signatures in incoming mail
+ 57.4. SPF (Sender Policy Framework)
58. Proxies
BSD/OS (aka BSDI), Darwin (Mac OS X), DGUX, Dragonfly, FreeBSD, GNU/Hurd, GNU/
Linux, HI-OSF (Hitachi), HI-UX, HP-UX, IRIX, MIPS RISCOS, NetBSD, OpenBSD,
OpenUNIX, QNX, SCO, SCO SVR4.2 (aka UNIX-SV), Solaris (aka SunOS5), SunOS4,
-Tru64-Unix (formerly Digital UNIX, formerly DEC-OSF1), Ultrix, and Unixware.
+Tru64-Unix (formerly Digital UNIX, formerly DEC-OSF1), Ultrix, and UnixWare.
Some of these operating systems are no longer current and cannot easily be
tested, so the configuration files may no longer work in practice.
the file NOTICE. Exim is distributed under the terms of the GNU General Public
Licence, a copy of which may be found in the file LICENCE.
-The use, supply or promotion of Exim for the purpose of sending bulk,
-unsolicited electronic mail is incompatible with the basic aims of the program,
-which revolve around the free provision of a service that enhances the quality
-of personal communications. The author of Exim regards indiscriminate
-mass-mailing as an antisocial, irresponsible abuse of the Internet.
+The use, supply, or promotion of Exim for the purpose of sending bulk,
+unsolicited electronic mail is incompatible with the basic aims of Exim, which
+revolve around the free provision of a service that enhances the quality of
+personal communications. The author of Exim regards indiscriminate mass-mailing
+as an antisocial, irresponsible abuse of the Internet.
Exim owes a great deal to Smail 3 and its author, Ron Karr. Without the
experience of running and working on the Smail 3 code, I could never have
1.1 Exim documentation
----------------------
-This edition of the Exim specification applies to version 4.89 of Exim.
-Substantive changes from the 4.88 edition are marked in some renditions of the
+This edition of the Exim specification applies to version 4.92 of Exim.
+Substantive changes from the 4.91 edition are marked in some renditions of this
document; this paragraph is so marked if the rendition is capable of showing a
change indicator.
with general Unix system administration. Although there are some discussions
and examples in places, the information is mostly organized in a way that makes
it easy to look up, rather than in a natural order for sequential reading.
-Furthermore, the manual aims to cover every aspect of Exim in detail, including
-a number of rarely-used, special-purpose features that are unlikely to be of
-very wide interest.
+Furthermore, this manual aims to cover every aspect of Exim in detail,
+including a number of rarely-used, special-purpose features that are unlikely
+to be of very wide interest.
An "easier" discussion of Exim which provides more in-depth explanatory,
introductory, and tutorial material can be found in a book entitled The Exim
-SMTP Mail Server (second edition, 2007), published by UIT Cambridge (http://
+SMTP Mail Server (second edition, 2007), published by UIT Cambridge (https://
www.uit.co.uk/exim-book/).
-This book also contains a chapter that gives a general introduction to SMTP and
+The book also contains a chapter that gives a general introduction to SMTP and
Internet mail. Inevitably, however, the book is unlikely to be fully up-to-date
with the latest release of Exim. (Note that the earlier book about Exim,
published by O'Reilly, covers Exim 3, and many things have changed in Exim 4.)
The command man update-exim.conf is another source of Debian-specific
information.
-As the program develops, there may be features in newer versions that have not
-yet made it into this document, which is updated only when the most significant
+As Exim develops, there may be features in newer versions that have not yet
+made it into this document, which is updated only when the most significant
digit of the fractional part of the version number changes. Specifications of
new features that are not yet in this manual are placed in the file doc/
NewStuff in the Exim distribution.
they are not documented in this manual. Information about experimental features
can be found in the file doc/experimental.txt.
-All changes to the program (whether new features, bug fixes, or other kinds of
-change) are noted briefly in the file called doc/ChangeLog.
+All changes to Exim (whether new features, bug fixes, or other kinds of change)
+are noted briefly in the file called doc/ChangeLog.
This specification itself is available as an ASCII file in doc/spec.txt so that
it can easily be searched with a text editor. Other files in the doc directory
openssl.txt installing a current OpenSSL release
The main specification and the specification of the filtering language are also
-available in other formats (HTML, PostScript, PDF, and Texinfo). Section 1.6
+available in other formats (HTML, PostScript, PDF, and Texinfo). Section 1.5
below tells you how to get hold of these.
-1.2 FTP and web sites
----------------------
+1.2 FTP site and websites
+-------------------------
-The primary site for Exim source distributions is currently the University of
-Cambridge's FTP site, whose contents are described in Where to find the Exim
-distribution below. In addition, there is a web site and an FTP site at
-exim.org. These are now also hosted at the University of Cambridge. The
-exim.org site was previously hosted for a number of years by Energis Squared,
-formerly Planet Online Ltd, whose support I gratefully acknowledge.
+The primary site for Exim source distributions is the exim.org FTP site,
+available over HTTPS, HTTP and FTP. These services, and the exim.org website,
+are hosted at the University of Cambridge.
-As well as Exim distribution tar files, the Exim web site contains a number of
+As well as Exim distribution tar files, the Exim website contains a number of
differently formatted versions of the documentation. A recent addition to the
-online information is the Exim wiki (http://wiki.exim.org), which contains what
-used to be a separate FAQ, as well as various other examples, tips, and
-know-how that have been contributed by Exim users.
+online information is the Exim wiki (https://wiki.exim.org), which contains
+what used to be a separate FAQ, as well as various other examples, tips, and
+know-how that have been contributed by Exim users. The wiki site should always
+redirect to the correct place, which is currently provided by GitHub, and is
+open to editing by anyone with a GitHub account.
-An Exim Bugzilla exists at http://bugs.exim.org. You can use this to report
+An Exim Bugzilla exists at https://bugs.exim.org. You can use this to report
bugs, and also to add items to the wish list. Please search first to check that
-you are not duplicating a previous entry.
+you are not duplicating a previous entry. Please do not ask for configuration
+help in the bug-tracker.
1.3 Mailing lists
Debian-specific mailing list pkg-exim4-users@lists.alioth.debian.org via this
web page:
-http://lists.alioth.debian.org/mailman/listinfo/pkg-exim4-users
+https://alioth-lists.debian.net/cgi-bin/mailman/listinfo/pkg-exim4-users
-Please ask Debian-specific questions on this list and not on the general Exim
+Please ask Debian-specific questions on that list and not on the general Exim
lists.
-1.4 Exim training
------------------
-
-Training courses in Cambridge (UK) used to be run annually by the author of
-Exim, before he retired. At the time of writing, there are no plans to run
-further Exim courses in Cambridge. However, if that changes, relevant
-information will be posted at http://www-tus.csx.cam.ac.uk/courses/exim/.
-
-
-1.5 Bug reports
+1.4 Bug reports
---------------
Reports of obvious bugs can be emailed to bugs@exim.org or reported via the
-Bugzilla (http://bugs.exim.org). However, if you are unsure whether some
+Bugzilla (https://bugs.exim.org). However, if you are unsure whether some
behaviour is a bug or not, the best thing to do is to post a message to the
exim-dev mailing list and have it discussed.
-1.6 Where to find the Exim distribution
+1.5 Where to find the Exim distribution
---------------------------------------
-The master ftp site for the Exim distribution is
+The master distribution site for the Exim distribution is
-ftp://ftp.csx.cam.ac.uk/pub/software/email/exim
+https://downloads.exim.org/
-This is mirrored by
+The service is available over HTTPS, HTTP and FTP. We encourage people to
+migrate to HTTPS.
-ftp://ftp.exim.org/pub/exim
+The content served at https://downloads.exim.org/ is identical to the content
+served at https://ftp.exim.org/pub/exim and ftp://ftp.exim.org/pub/exim.
-The file references that follow are relative to the exim directories at these
-sites. There are now quite a number of independent mirror sites around the
-world. Those that I know about are listed in the file called Mirrors.
+If accessing via a hostname containing ftp, then the file references that
+follow are relative to the exim directories at these sites. If accessing via
+the hostname downloads then the subdirectories described here are top-level
+directories.
-Within the exim directory there are subdirectories called exim3 (for previous
-Exim 3 distributions), exim4 (for the latest Exim 4 distributions), and Testing
-for testing versions. In the exim4 subdirectory, the current release can always
-be found in files called
+There are now quite a number of independent mirror sites around the world.
+Those that I know about are listed in the file called Mirrors.
+Within the top exim directory there are subdirectories called exim3 (for
+previous Exim 3 distributions), exim4 (for the latest Exim 4 distributions),
+and Testing for testing versions. In the exim4 subdirectory, the current
+release can always be found in files called
+
+exim-n.nn.tar.xz
exim-n.nn.tar.gz
exim-n.nn.tar.bz2
-where n.nn is the highest such version number in the directory. The two files
-contain identical data; the only difference is the type of compression. The
-.bz2 file is usually a lot smaller than the .gz file.
+where n.nn is the highest such version number in the directory. The three files
+contain identical data; the only difference is the type of compression. The .xz
+file is usually the smallest, while the .gz file is the most portable to old
+systems.
The distributions will be PGP signed by an individual key of the Release
Coordinator. This key will have a uid containing an email address in the
exim.org domain and will have signatures from other people, including other
Exim maintainers. We expect that the key will be in the "strong set" of PGP
-keys. There should be a trust path to that key from Nigel Metheringham's PGP
-key, a version of which can be found in the release directory in the file
-nigel-pubkey.asc. All keys used will be available in public keyserver pools,
-such as pool.sks-keyservers.net.
-
-At time of last update, releases were being made by Phil Pennock and signed
-with key 0x403043153903637F, although that key is expected to be replaced in
-2013. A trust path from Nigel's key to Phil's can be observed at https://
-www.security.spodhuis.org/exim-trustpath.
+keys. There should be a trust path to that key from the Exim Maintainer's PGP
+keys, a version of which can be found in the release directory in the file
+Exim-Maintainers-Keyring.asc. All keys used will be available in public
+keyserver pools, such as pool.sks-keyservers.net.
-Releases have also been authorized to be performed by Todd Lyons who signs with
-key 0xC4F4F94804D29EBA. A direct trust path exists between previous RE Phil
-Pennock and Todd Lyons through a common associate.
+At the time of the last update, releases were being made by Jeremy Harris and
+signed with key 0xBCE58C8CE41F32DF. Other recent keys used for signing are
+those of Heiko Schlittermann, 0x26101B62F69376CE, and of Phil Pennock,
+0x4D1E900E14C1CC04.
The signatures for the tar bundles are in:
+exim-n.nn.tar.xz.asc
exim-n.nn.tar.gz.asc
exim-n.nn.tar.bz2.asc
-For each released version, the log of changes is made separately available in a
-separate file in the directory ChangeLogs so that it is possible to find out
-what has changed without having to download the entire distribution.
+For each released version, the log of changes is made available in a separate
+file in the directory ChangeLogs so that it is possible to find out what has
+changed without having to download the entire distribution.
The main distribution contains ASCII versions of this specification and other
documentation; other formats of the documents are available in separate files
exim-texinfo-n.nn.tar.gz
These tar files contain only the doc directory, not the complete distribution,
-and are also available in .bz2 as well as .gz forms.
+and are also available in .bz2 and .xz forms.
-1.7 Limitations
+1.6 Limitations
---------------
* Exim is designed for use as an Internet MTA, and therefore handles
straightforward interfaces to a number of common scanners are provided.
-1.8 Run time configuration
---------------------------
+1.7 Runtime configuration
+-------------------------
-Exim's run time configuration is held in a single text file that is divided
-into a number of sections. The entries in this file consist of keywords and
-values, in the style of Smail 3 configuration files. A default configuration
-file which is suitable for simple online installations is provided in the
-distribution, and is described in chapter 7 below.
+Exim's runtime configuration is held in a single text file that is divided into
+a number of sections. The entries in this file consist of keywords and values,
+in the style of Smail 3 configuration files. A default configuration file which
+is suitable for simple online installations is provided in the distribution,
+and is described in chapter 7 below.
-1.9 Calling interface
+1.8 Calling interface
---------------------
Like many MTAs, Exim has adopted the Sendmail command line interface so that it
sending mail, but you do not need to know anything about Sendmail in order to
run Exim. For actions other than sending messages, Sendmail-compatible options
also exist, but those that produce output (for example, -bp, which lists the
-messages on the queue) do so in Exim's own format. There are also some
+messages in the queue) do so in Exim's own format. There are also some
additional options that are compatible with Smail 3, and some further options
that are new to Exim. Chapter 5 documents all Exim's command line options. This
information is automatically made into the man page that forms part of the Exim
distribution.
-Control of messages on the queue can be done via certain privileged command
+Control of messages in the queue can be done via certain privileged command
line options. There is also an optional monitor program called eximon, which
displays current information in an X window, and which contains a menu
interface to Exim's command line administration options.
-1.10 Terminology
-----------------
+1.9 Terminology
+---------------
The body of a message is the actual data that the sender wants to transmit. It
-is the last part of a message, and is separated from the header (see below) by
-a blank line.
+is the last part of a message and is separated from the header (see below) by a
+blank line.
When a message cannot be delivered, it is normally returned to the sender in a
delivery failure message or a "non-delivery report" (NDR). The term bounce is
Long header lines can be split over several text lines by indenting the
continuations. The header is separated from the body by a blank line.
-The term local part, which is taken from RFC 2822, is used to refer to that
-part of an email address that precedes the @ sign. The part that follows the @
-sign is called the domain or mail domain.
+The term local part, which is taken from RFC 2822, is used to refer to the part
+of an email address that precedes the @ sign. The part that follows the @ sign
+is called the domain or mail domain.
The terms local delivery and remote delivery are used to distinguish delivery
to a file or a pipe on the local host from delivery by SMTP over TCP/IP to
Return path is another name that is used for the sender address in a message's
envelope.
-The term queue is used to refer to the set of messages awaiting delivery,
+The term queue is used to refer to the set of messages awaiting delivery
because this term is in widespread use in the context of MTAs. However, in
-Exim's case the reality is more like a pool than a queue, because there is
+Exim's case, the reality is more like a pool than a queue, because there is
normally no ordering of waiting messages.
The term queue runner is used to describe a process that scans the queue and
attempts to deliver those messages whose retry times have come. This term is
-used by other MTAs, and also relates to the command runq, but in Exim the
+used by other MTAs and also relates to the command runq, but in Exim the
waiting messages are normally processed in an unpredictable order.
The term spool directory is used for a directory in which Exim keeps the
-messages on its queue - that is, those that it is in the process of delivering.
+messages in its queue - that is, those that it is in the process of delivering.
This should not be confused with the directory in which local mailboxes are
stored, which is called a "spool directory" by some people. In the Exim
documentation, "spool" is always used in the first sense.
Free Software Foundation; either version 2 of the License, or (at your
option) any later version. This code implements Dan Bernstein's
Constant DataBase (cdb) spec. Information, the spec and sample code for
- cdb can be obtained from http://www.pobox.com/~djb/cdb.html. This
- implementation borrows some code from Dan Bernstein's implementation
- (which has no license restrictions applied to it).
+ cdb can be obtained from https://cr.yp.to/cdb.html. This implementation
+ borrows some code from Dan Bernstein's implementation (which has no
+ license restrictions applied to it).
* Client support for Microsoft's Secure Password Authentication is provided
by code contributed by Marc Prud'hommeaux. Server support was contributed
acknowledgment:
"This product includes software developed by Computing Services at
- Carnegie Mellon University (http://www.cmu.edu/computing/."
+ Carnegie Mellon University (https://www.cmu.edu/computing/."
CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
the distributed source code.
* Many people have contributed code fragments, some large, some small, that
- were not covered by any specific licence requirements. It is assumed that
+ were not covered by any specific license requirements. It is assumed that
the contributors are happy to see their code incorporated into Exim under
the GPL.
------------------
Policy controls are now an important feature of MTAs that are connected to the
-Internet. Perhaps their most important job is to stop MTAs being abused as
+Internet. Perhaps their most important job is to stop MTAs from being abused as
"open relays" by misguided individuals who send out vast amounts of unsolicited
-junk, and want to disguise its source. Exim provides flexible facilities for
+junk and want to disguise its source. Exim provides flexible facilities for
specifying policy controls on incoming mail:
* Exim 4 (unlike previous versions of Exim) implements policy controls on
encoding numbers in base 62. However, in the Darwin operating system (Mac OS X)
and when Exim is compiled to run under Cygwin, base 36 (avoiding the use of
lower case letters) is used instead, because the message id is used to
-construct file names, and the names of files in those systems are not always
+construct filenames, and the names of files in those systems are not always
case-sensitive.
The detail of the contents of the message id have changed as Exim has evolved.
* If the process runs Exim with the -bS option, the message is also read
non-interactively, but in this case the recipients are listed at the start
of the message in a series of SMTP RCPT commands, terminated by a DATA
- command. This is so-called "batch SMTP" format, but it isn't really SMTP.
- The SMTP commands are just another way of passing envelope addresses in a
+ command. This is called "batch SMTP" format, but it isn't really SMTP. The
+ SMTP commands are just another way of passing envelope addresses in a
non-interactive submission.
* If the process runs Exim with the -bs option, the message is read
qualification domain (which can be set by the qualify_domain configuration
option). For local or batch SMTP, a sender address that is passed using the
SMTP MAIL command is ignored. However, the system administrator may allow
-certain users ("trusted users") to specify a different sender address
+certain users ("trusted users") to specify a different sender addresses
unconditionally, or all users to specify certain forms of different sender
address. The -f option or the SMTP MAIL command is used to specify these
different addresses. See section 5.2 for details of trusted users, and the
sender addresses.
Messages received by either of the non-interactive mechanisms are subject to
-checking by the non-SMTP ACL, if one is defined. Messages received using SMTP
-(either over TCP/IP, or interacting with a local process) can be checked by a
+checking by the non-SMTP ACL if one is defined. Messages received using SMTP
+(either over TCP/IP or interacting with a local process) can be checked by a
number of ACLs that operate at different times during the SMTP session. Either
-individual recipients, or the entire message, can be rejected if local policy
+individual recipients or the entire message can be rejected if local policy
requirements are not met. The local_scan() function (see chapter 45) is run for
all incoming messages.
the two spool files consist of the message id, followed by "-H" for the file
containing the envelope and header, and "-D" for the data file.
-By default all these message files are held in a single directory called input
+By default, all these message files are held in a single directory called input
inside the general Exim spool directory. Some operating systems do not perform
very well if the number of files in a directory gets large; to improve
performance in such cases, the split_spool_directory option can be used. This
A message remains in the spool directory until it is completely delivered to
its recipients or to an error address, or until it is deleted by an
administrator or by the user who originally created it. In cases when delivery
-cannot proceed - for example, when a message can neither be delivered to its
+cannot proceed - for example when a message can neither be delivered to its
recipients nor returned to its sender, the message is marked "frozen" on the
spool, and no more deliveries are attempted.
There are options called ignore_bounce_errors_after and timeout_frozen_after,
which discard frozen messages after a certain time. The first applies only to
-frozen bounces, the second to any frozen messages.
+frozen bounces, the second to all frozen messages.
While Exim is working on a message, it writes information about each delivery
attempt to its main log file. This includes successful, unsuccessful, and
delayed deliveries for each recipient (see chapter 52). The log lines are also
written to a separate message log file for each message. These logs are solely
-for the benefit of the administrator, and are normally deleted along with the
+for the benefit of the administrator and are normally deleted along with the
spool files when processing of a message is complete. The use of individual
message logs can be disabled by setting no_message_logs; this might give an
improvement in performance on very busy systems.
Updating the spool file is done by writing a new file and renaming it, to
minimize the possibility of data loss.
-Should the system or the program crash after a successful delivery but before
-the spool file has been updated, the journal is left lying around. The next
-time Exim attempts to deliver the message, it reads the journal file and
-updates the spool file before proceeding. This minimizes the chances of double
-deliveries caused by crashes.
+Should the system or Exim crash after a successful delivery but before the
+spool file has been updated, the journal is left lying around. The next time
+Exim attempts to deliver the message, it reads the journal file and updates the
+spool file before proceeding. This minimizes the chances of double deliveries
+caused by crashes.
3.8 Processing an address for delivery
The main delivery processing elements of Exim are called routers and transports
, and collectively these are known as drivers. Code for a number of them is
provided in the source distribution, and compile-time options specify which
-ones are included in the binary. Run time options specify which ones are
+ones are included in the binary. Runtime options specify which ones are
actually used for delivering messages.
-Each driver that is specified in the run time configuration is an instance of
+Each driver that is specified in the runtime configuration is an instance of
that particular driver type. Multiple instances are allowed; for example, you
can set up several different smtp transports, each with different option values
that might specify different ports or different timeouts. Each instance has its
configuration.
The first router that is specified in a configuration is often one that handles
-addresses in domains that are not recognized specially by the local host. These
-are typically addresses for arbitrary domains on the Internet. A precondition
-is set up which looks for the special domains known to the host (for example,
-its own domain name), and the router is run for addresses that do not match.
-Typically, this is a router that looks up domains in the DNS in order to find
-the hosts to which this address routes. If it succeeds, the address is assigned
-to a suitable SMTP transport; if it does not succeed, the router is configured
-to fail the address.
+addresses in domains that are not recognized specifically by the local host.
+Typically these are addresses for arbitrary domains on the Internet. A
+precondition is set up which looks for the special domains known to the host
+(for example, its own domain name), and the router is run for addresses that do
+not match. Typically, this is a router that looks up domains in the DNS in
+order to find the hosts to which this address routes. If it succeeds, the
+address is assigned to a suitable SMTP transport; if it does not succeed, the
+router is configured to fail the address.
The second router is reached only when the domain is recognized as one that
"belongs" to the local host. This router does redirection - also known as
does not affect the way the routers work, but it is a state that can be
detected. By this means, a router can be skipped or made to behave differently
when verifying. A common example is a configuration in which the first router
-sends all messages to a message-scanning program, unless they have been
+sends all messages to a message-scanning program unless they have been
previously scanned. Thus, the first router accepts all addresses without any
checking, making it useless for verifying. Normally, the no_verify option would
be set for such a router, causing it to be skipped in verify mode.
following:
* accept: The router accepts the address, and either assigns it to a
- transport, or generates one or more "child" addresses. Processing the
- original address ceases, unless the unseen option is set on the router.
- This option can be used to set up multiple deliveries with different
- routing (for example, for keeping archive copies of messages). When unseen
- is set, the address is passed to the next router. Normally, however, an
- accept return marks the end of routing.
+ transport or generates one or more "child" addresses. Processing the
+ original address ceases unless the unseen option is set on the router. This
+ option can be used to set up multiple deliveries with different routing
+ (for example, for keeping archive copies of messages). When unseen is set,
+ the address is passed to the next router. Normally, however, an accept
+ return marks the end of routing.
Any child addresses generated by the router are processed independently,
starting with the first router by default. It is possible to change this by
redirect_router may be anywhere in the router configuration.
* pass: The router recognizes the address, but cannot handle it itself. It
- requests that the address be passed to another router. By default the
+ requests that the address be passed to another router. By default, the
address is passed to the next router, but this can be changed by setting
the pass_router option. However, (unlike redirect_router) the named router
must be below the current router (to avoid loops).
------------------------
Once routing is complete, Exim scans the addresses that are assigned to local
-and remote transports, and discards any duplicates that it finds. During this
-check, local parts are treated as case-sensitive. This happens only when
+and remote transports and discards any duplicates that it finds. During this
+check, local parts are treated case-sensitively. This happens only when
actually delivering a message; when testing routers with -bt, all the routed
addresses are shown.
condition first_delivery can be used to detect the first run of the system
filter.
- * Each recipient address is offered to each configured router in turn,
+ * Each recipient address is offered to each configured router, in turn,
subject to its preconditions, until one is able to handle it. If no router
can handle the address, that is, if they all decline, the address is
failed. Because routers can be targeted at particular domains, several
Exim's mechanism for retrying messages that fail to get delivered at the first
attempt is the queue runner process. You must either run an Exim daemon that
uses the -q option with a time interval to start queue runners at regular
-intervals, or use some other means (such as cron) to start them. If you do not
+intervals or use some other means (such as cron) to start them. If you do not
arrange for queue runners to be run, messages that fail temporarily at the
-first attempt will remain on your queue for ever. A queue runner process works
+first attempt will remain in your queue forever. A queue runner process works
its way through the queue, one message at a time, trying each delivery that has
passed its retry time. You can run several queue runners at once.
----------------------------------------
If a bounce message (either locally generated or received from a remote host)
-itself suffers a permanent delivery failure, the message is left on the queue,
+itself suffers a permanent delivery failure, the message is left in the queue,
but it is frozen, awaiting the attention of an administrator. There are options
that can be used to make Exim discard such failed messages, or to keep them for
only a short time (see timeout_frozen_after and ignore_bounce_errors_after).
Exim is distributed as a gzipped or bzipped tar file which, when unpacked,
creates a directory with the name of the current release (for example,
-exim-4.89) into which the following files are placed:
+exim-4.92) into which the following files are placed:
ACKNOWLEDGMENTS contains some acknowledgments
CHANGES contains a reference to where changes are documented
src remaining source files
util independent utilities
-The main utility programs are contained in the src directory, and are built
-with the Exim binary. The util directory contains a few optional scripts that
-may be useful to some sites.
+The main utility programs are contained in the src directory and are built with
+the Exim binary. The util directory contains a few optional scripts that may be
+useful to some sites.
4.2 Multiple machine architectures and operating systems
links to the sources are installed in this directory, which is where the actual
building takes place. In most cases, Exim can discover the machine architecture
and operating system for itself, but the defaults can be overridden if
-necessary.
+necessary. A C99-capable compiler will be required for the build.
4.3 PCRE library
Exim no longer has an embedded PCRE library as the vast majority of modern
systems include PCRE as a system library, although you may need to install the
-PCRE or PCRE development package for your operating system. If your system has
-a normal PCRE installation the Exim build process will need no further
-configuration. If the library or the headers are in an unusual location you
-will need to either set the PCRE_LIBS and INCLUDE directives appropriately, or
-set PCRE_CONFIG=yes to use the installed pcre-config command. If your operating
-system has no PCRE support then you will need to obtain and build the current
-PCRE from ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/. More
-information on PCRE is available at http://www.pcre.org/.
+PCRE package or the PCRE development package for your operating system. If your
+system has a normal PCRE installation the Exim build process will need no
+further configuration. If the library or the headers are in an unusual location
+you will need to either set the PCRE_LIBS and INCLUDE directives appropriately,
+or set PCRE_CONFIG=yes to use the installed pcre-config command. If your
+operating system has no PCRE support then you will need to obtain and build the
+current PCRE from ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/. More
+information on PCRE is available at https://www.pcre.org/.
4.4 DBM libraries
2. The GNU library, gdbm, operates on a single file. If used via its ndbm
compatibility interface it makes two different hard links to it with names
- dbmfile.dir and dbmfile.pag, but if used via its native interface, the file
- name is used unmodified.
+ dbmfile.dir and dbmfile.pag, but if used via its native interface, the
+ filename is used unmodified.
3. The Berkeley DB package, if called via its ndbm compatibility interface,
operates on a single file called dbmfile.db, but otherwise looks to the
5. To complicate things further, there are several very different versions of
the Berkeley DB package. Version 1.85 was stable for a very long time,
- releases 2.x and 3.x were current for a while, but the latest versions are
- now numbered 4.x. Maintenance of some of the earlier releases has ceased.
- All versions of Berkeley DB can be obtained from http://www.sleepycat.com/.
-
- 6. Yet another DBM library, called tdb, is available from http://
- download.sourceforge.net/tdb. It has its own interface, and also operates
- on a single file.
+ releases 2.x and 3.x were current for a while, but the latest versions when
+ Exim last revamped support were numbered 4.x. Maintenance of some of the
+ earlier releases has ceased. All versions of Berkeley DB could be obtained
+ from http://www.sleepycat.com/, which is now a redirect to their new
+ owner's page with far newer versions listed. It is probably wise to plan to
+ move your storage configurations away from Berkeley DB format, as today
+ there are smaller and simpler alternatives more suited to Exim's usage
+ model.
+
+ 6. Yet another DBM library, called tdb, is available from https://
+ sourceforge.net/projects/tdb/files/. It has its own interface, and also
+ operates on a single file.
Exim and its utilities can be compiled to use any of these interfaces. In order
to use any version of the Berkeley DB package in native mode, you must set
then read it and edit it appropriately.
There are three settings that you must supply, because Exim will not build
-without them. They are the location of the run time configuration file
+without them. They are the location of the runtime configuration file
(CONFIGURE_FILE), the directory in which Exim binaries will be installed
(BIN_DIRECTORY), and the identity of the Exim user (EXIM_USER and maybe
EXIM_GROUP as well). The value of CONFIGURE_FILE can in fact be a
-colon-separated list of file names; Exim uses the first of them that exists.
+colon-separated list of filenames; Exim uses the first of them that exists.
There are a few other parameters that can be specified either at build time or
-at run time, to enable the same binary to be used on a number of different
+at runtime, to enable the same binary to be used on a number of different
machines. However, if the locations of Exim's spool directory and log file
directory (if not within the spool directory) are fixed, it is recommended that
-you specify them in Local/Makefile instead of at run time, so that errors
+you specify them in Local/Makefile instead of at runtime, so that errors
detected early in Exim's execution (such as a malformed configuration file) can
be logged.
This is all the configuration that is needed in straightforward cases for known
operating systems. However, the building process is set up so that it is easy
to override options that are set by default or by operating-system-specific
-configuration files, for example to change the name of the C compiler, which
-defaults to gcc. See section 4.13 below for details of how to do this.
+configuration files, for example, to change the C compiler, which defaults to
+gcc. See section 4.13 below for details of how to do this.
4.6 Support for iconv()
operating system supports the iconv() function.
However, some of the operating systems that supply iconv() do not support very
-many conversions. The GNU libiconv library (available from http://www.gnu.org/
+many conversions. The GNU libiconv library (available from https://www.gnu.org/
software/libiconv/) can be installed on such systems to remedy this deficiency,
as well as on systems that do not supply iconv() at all. After installing
libiconv, you should add
lookup types (such as cdb) for which the code is entirely contained within
Exim, and no external include files or libraries are required. When a lookup
type is not included in the binary, attempts to configure Exim to use it cause
-run time configuration errors.
+runtime configuration errors.
Many systems now use a tool called pkg-config to encapsulate information about
how to compile against a library; Exim has some initial support for being able
OS/eximon.conf-<ostype> file is also optional. The default values in OS/
eximon.conf-Default can be overridden dynamically by setting environment
variables of the same name, preceded by EXIMON_. For example, setting
-EXIMON_LOG_DEPTH in the environment overrides the value of LOG_DEPTH at run
-time.
+EXIMON_LOG_DEPTH in the environment overrides the value of LOG_DEPTH at
+runtime.
4.16 Installing Exim binaries and scripts
possible to run Exim without making the binary setuid root (see chapter 55 for
details).
-Exim's run time configuration file is named by the CONFIGURE_FILE setting in
+Exim's runtime configuration file is named by the CONFIGURE_FILE setting in
Local/Makefile. If this names a single file, and the file does not exist, the
default configuration file src/configure.default is copied there by the
-installation script. If a run time configuration file already exists, it is
-left alone. If CONFIGURE_FILE is a colon-separated list, naming several
-alternative files, no default is installed.
+installation script. If a runtime configuration file already exists, it is left
+alone. If CONFIGURE_FILE is a colon-separated list, naming several alternative
+files, no default is installed.
One change is made to the default configuration file when it is installed: the
default configuration contains a router that references a system aliases file.
For the utility programs, old versions are renamed by adding the suffix .O to
their names. The Exim binary itself, however, is handled differently. It is
installed under a name that includes the version number and the compile number,
-for example exim-4.89-1. The script then arranges for a symbolic link called
+for example, exim-4.92-1. The script then arranges for a symbolic link called
exim to point to the binary. If you are updating a previous version of Exim,
the script takes care to ensure that the name exim is never absent from the
directory (as seen by other processes).
Not all systems use the GNU info system for documentation, and for this reason,
the Texinfo source of Exim's documentation is not included in the main
-distribution. Instead it is available separately from the ftp site (see section
-1.6).
+distribution. Instead it is available separately from the FTP site (see section
+1.5).
If you have defined INFO_DIRECTORY in Local/Makefile and the Texinfo source of
the documentation is found in the source tree, running "make install"
4.19 Testing
------------
-Having installed Exim, you can check that the run time configuration file is
+Having installed Exim, you can check that the runtime configuration file is
syntactically valid by running the following command, which assumes that the
Exim binary directory is within your PATH environment variable:
Testing a new version on a system that is already running Exim can most easily
be done by building a binary with a different CONFIGURE_FILE setting. From
-within the run time configuration, all other file and directory names that Exim
+within the runtime configuration, all other file and directory names that Exim
uses can be altered, in order to keep it entirely clear of the production
version.
data. A line history is supported.
Long expansion expressions can be split over several lines by using
- backslash continuations. As in Exim's run time configuration, white space
- at the start of continuation lines is ignored. Each argument or data line
- is passed through the string expansion mechanism, and the result is output.
+ backslash continuations. As in Exim's runtime configuration, white space at
+ the start of continuation lines is ignored. Each argument or data line is
+ passed through the string expansion mechanism, and the result is output.
Variable values from the configuration file (for example, $qualify_domain)
are available, but no message-specific values (such as $message_exim_id)
are set, because no message is being processed (but see -bem and -Mset).
trying the same lookup again. Otherwise, because each Exim process caches
the results of lookups, you will just get the same result as before.
+ Macro processing is done on lines before string-expansion: new macros can
+ be defined and macros will be expanded. Because macros in the config file
+ are often used for secrets, those are only available to admin users.
+
-bem <filename>
This option operates like -be except that it must be followed by the name
If config is given as an argument, the config is output, as it was parsed,
any include file resolved, any comment removed.
- If config_file is given as an argument, the name of the run time
+ If config_file is given as an argument, the name of the runtime
configuration file is output. (configure_file works too, for backward
compatibility.) If a list of configuration files was supplied, the value
that is output here is the name of the file that was actually used.
If invoked by an admin user, then macro, macro_list and macros are
available, similarly to the drivers. Because macros are sometimes used for
storing passwords, this option is restricted. The output format is one item
- per line.
+ per line. For the "-bP macro <name>" form, if no such macro is found the
+ exit status will be nonzero.
-bp
an admin user. However, the queue_list_requires_admin option can be set
false to allow any user to see the queue.
- Each message on the queue is displayed as in the following example:
+ Each message in the queue is displayed as in the following example:
25m 2.9K 0t5C6f-0000c8-00 <alice@wonderland.fict.example>
red.king@looking-glass.fict.example
<other addresses>
- The first line contains the length of time the message has been on the
+ The first line contains the length of time the message has been in the
queue (in this case 25 minutes), the size of the message (2.9K), the unique
local identifier for the message, and the message sender, as contained in
the envelope. For bounce messages, the sender address is empty, and appears
-bpc
- This option counts the number of messages on the queue, and writes the
+ This option counts the number of messages in the queue, and writes the
total to the standard output. It is restricted to admin users, unless
queue_list_requires_admin is set false.
This option operates like -bp, but the output is not sorted into
chronological order of message arrival. This can speed it up when there are
- lots of messages on the queue, and is particularly useful if the output is
+ lots of messages in the queue, and is particularly useful if the output is
going to be post-processed in a way that doesn't need the sorting.
-bpra
number, and compilation date of the exim binary to the standard output. It
also lists the DBM library that is being used, the optional modules (such
as specific lookup types), the drivers that are included in the binary, and
- the name of the run time configuration file that is in use.
+ the name of the runtime configuration file that is in use.
As part of its operation, -bV causes Exim to read and syntax check its
configuration file. However, this is a static check only. It cannot check
-C <filelist>
- This option causes Exim to find the run time configuration file from the
+ This option causes Exim to find the runtime configuration file from the
given list instead of from the list specified by the CONFIGURE_FILE
- compile-time setting. Usually, the list will consist of just a single file
- name, but it can be a colon-separated list of names. In this case, the
+ compile-time setting. Usually, the list will consist of just a single
+ filename, but it can be a colon-separated list of names. In this case, the
first file that exists is used. Failure to open an existing file stops Exim
from proceeding any further along the list, and an error is generated.
running as the Exim user, so when it re-executes to regain privilege for
the delivery, the use of -C causes privilege to be lost. However, root can
test reception and delivery using two separate commands (one to put a
- message on the queue, using -odq, and another to do the delivery, using -M
+ message in the queue, using -odq, and another to do the delivery, using -M
).
If ALT_CONFIG_PREFIX is defined in Local/Makefile, it specifies a prefix
string with which any file named in a -C command line option must start. In
- addition, the file name must not contain the sequence "/../". However, if
+ addition, the filename must not contain the sequence "/../". However, if
the value of the -C option is identical to the value of CONFIGURE_FILE in
Local/Makefile, Exim ignores -C and proceeds as usual. There is no default
- setting for ALT_CONFIG_PREFIX; when it is unset, any file name can be used
+ setting for ALT_CONFIG_PREFIX; when it is unset, any filename can be used
with -C.
ALT_CONFIG_PREFIX can be used to confine alternative configuration files to
local_scan can be used by local_scan() (see chapter 45)
lookup general lookup code and all lookups
memory memory handling
- pid add pid to debug output lines
+ noutf8 modifier: avoid UTF-8 line-drawing
+ pid modifier: add pid to debug output lines
process_info setting info for the process log
queue_run queue runs
receive general message reception logic
retry retry handling
rewrite address rewriting
route address routing
- timestamp add timestamp to debug output lines
+ timestamp modifier: add timestamp to debug output lines
tls TLS logic
transport transports
uid changes of uid/gid and looking up uid/gid
start of all debug output lines. This can be useful when trying to track
down delays in processing.
+ The "noutf8" selector disables the use of UTF-8 line-drawing characters to
+ group related information. When disabled. ascii-art is used instead. Using
+ the "+all" option does not set this modifier,
+
If the debug_print option is set in any driver, it produces output whenever
any debugging is selected, or if -v is used.
internally by Exim in conjunction with the -MC option. It signifies that
the remote host supports the ESMTP DSN extension.
--MCG
+-MCG <queue name>
This option is not intended for use by external callers. It is used
internally by Exim in conjunction with the -MC option. It signifies that an
- alternate queue is used, named by the following option.
+ alternate queue is used, named by the following argument.
+
+-MCK
+
+ This option is not intended for use by external callers. It is used
+ internally by Exim in conjunction with the -MC option. It signifies that a
+ remote host supports the ESMTP CHUNKING extension.
-MCP
internally by Exim in conjunction with the -MC option, and passes on the
fact that the host to which Exim is connected supports TLS encryption.
+-MCt <IP address> <port> <cipher>
+
+ This option is not intended for use by external callers. It is used
+ internally by Exim in conjunction with the -MC option, and passes on the
+ fact that the connection is being proxied by a parent process for handling
+ TLS encryption. The arguments give the local address and port being
+ proxied, and the TLS cipher.
+
-Mc <message id> <message id> ...
- This option requests Exim to run a delivery attempt on each message in
+ This option requests Exim to run a delivery attempt on each message, in
turn, but unlike the -M option, it does check for retry hints, and respects
any that are found. This option is not very useful to external callers. It
is provided mainly for internal use by Exim when it needs to re-invoke
bounce messages are sent; each message is simply forgotten. However, if any
of the messages are active, their status is not altered. This option can be
used only by an admin user or by the user who originally caused the message
- to be placed on the queue.
+ to be placed in the queue.
-Mset <message id>
-oA <file name>
This option is used by Sendmail in conjunction with -bi to specify an
- alternative alias file name. Exim handles -bi differently; see the
+ alternative alias filename. Exim handles -bi differently; see the
description above.
-oB <n>
effect.
If there is a temporary delivery error during foreground delivery, the
- message is left on the queue for later delivery, and the original reception
+ message is left in the queue for later delivery, and the original reception
process exits. See chapter 51 for a way of setting up a restricted
configuration that never queues messages.
This option applies to all modes in which Exim accepts incoming messages,
including the listening daemon. It specifies that the accepting process
should not automatically start a delivery process for each message
- received. Messages are placed on the queue, and remain there until a
+ received. Messages are placed in the queue, and remain there until a
subsequent queue runner process encounters them. There are several
configuration options (such as queue_only) that can be used to queue
incoming messages under certain conditions. This option overrides all of
message, in the background by default, but in the foreground if -odi is
also present. The recipient addresses are routed, and local deliveries are
done in the normal way. However, if any SMTP deliveries are required, they
- are not done at this time, so the message remains on the queue until a
+ are not done at this time, so the message remains in the queue until a
subsequent queue runner process encounters it. Because routing was done,
Exim knows which messages are waiting for which hosts, and so a number of
messages for the same host can be sent in a single SMTP connection. The
-bh, the protocol is forced to one of the standard SMTP protocol names (see
the description of $received_protocol in section 11.9). For -bs, the
protocol is always "local-" followed by one of those same names. For -bS
- (batched SMTP) however, the protocol can be set by -oMr.
+ (batched SMTP) however, the protocol can be set by -oMr. Repeated use of
+ this option is not supported.
-oMs <host name>
is also given. It controls which ports and interfaces the daemon uses.
Details of the syntax, and how it interacts with configuration file
options, are given in chapter 13. When -oX is used to start a daemon, no
- pid file is written unless -oP is also present to specify a pid file name.
+ pid file is written unless -oP is also present to specify a pid filename.
-pd
name and its colon can be omitted when only the protocol is to be set. Note
the Exim already has two private options, -pd and -ps, that refer to
embedded Perl. It is therefore impossible to set a protocol value of "d" or
- "s" using this option (but that does not seem a real limitation).
+ "s" using this option (but that does not seem a real limitation). Repeated
+ use of this option is not supported.
-q
If the i flag is present, the queue runner runs delivery processes only for
those messages that haven't previously been tried. (i stands for "initial
- delivery".) This can be helpful if you are putting messages on the queue
+ delivery".) This can be helpful if you are putting messages in the queue
using -odq and want a queue runner just to process the new messages.
-q[q][i]f...
-q[q][i][f[f]]l
The l (the letter "ell") flag specifies that only local deliveries are to
- be done. If a message requires any remote deliveries, it remains on the
+ be done. If a message requires any remote deliveries, it remains in the
queue for later delivery.
-q[q][i][f[f]][l][G<name>[/<time>]]]
===============================================================================
-6. THE EXIM RUN TIME CONFIGURATION FILE
+6. THE EXIM RUNTIME CONFIGURATION FILE
-Exim uses a single run time configuration file that is read whenever an Exim
+Exim uses a single runtime configuration file that is read whenever an Exim
binary is executed. Note that in normal operation, this happens frequently,
because Exim is designed to operate in a distributed manner, without central
control.
The name of the configuration file is compiled into the binary for security
reasons, and is specified by the CONFIGURE_FILE compilation option. In most
configurations, this specifies a single file. However, it is permitted to give
-a colon-separated list of file names, in which case Exim uses the first
-existing file in the list.
+a colon-separated list of filenames, in which case Exim uses the first existing
+file in the list.
-The run time configuration file must be owned by root or by the user that is
+The runtime configuration file must be owned by root or by the user that is
specified at compile time by the CONFIGURE_OWNER option (if set). The
configuration file must not be world-writeable, or group-writeable unless its
group is the root group or the one specified at compile time by the
CONFIGURE_GROUP option.
Warning: In a conventional configuration, where the Exim binary is setuid to
-root, anybody who is able to edit the run time configuration file has an easy
+root, anybody who is able to edit the runtime configuration file has an easy
way to run commands as root. If you specify a user or group in the
CONFIGURE_OWNER or CONFIGURE_GROUP options, then that user and/or any users who
are members of that group will trivially be able to obtain root privileges.
-Up to Exim version 4.72, the run time configuration file was also permitted to
+Up to Exim version 4.72, the runtime configuration file was also permitted to
be writeable by the Exim user and/or group. That has been changed in Exim 4.73
since it offered a simple privilege escalation for any attacker who managed to
compromise the Exim user account.
A default configuration file, which will work correctly in simple situations,
is provided in the file src/configure.default. If CONFIGURE_FILE defines just
-one file name, the installation process copies the default configuration to a
+one filename, the installation process copies the default configuration to a
new file of that name if it did not previously exist. If CONFIGURE_FILE is a
list, no default is automatically installed. Chapter 7 is a "walk-through"
discussion of the default configuration.
the caller is root. The reception works, but by that time, Exim is running as
the Exim user, so when it re-execs to regain privilege for the delivery, the
use of -C causes privilege to be lost. However, root can test reception and
-delivery using two separate commands (one to put a message on the queue, using
+delivery using two separate commands (one to put a message in the queue, using
-odq, and another to do the delivery, using -M).
If ALT_CONFIG_PREFIX is defined in Local/Makefile, it specifies a prefix string
with which any file named in a -C command line option must start. In addition,
-the file name must not contain the sequence "/../". There is no default setting
-for ALT_CONFIG_PREFIX; when it is unset, any file name can be used with -C.
+the filename must not contain the sequence "/../". There is no default setting
+for ALT_CONFIG_PREFIX; when it is unset, any filename can be used with -C.
One-off changes to a configuration can be specified by the -D command line
option, which defines and overrides values for macros used inside the
Some sites may wish to use the same Exim binary on different machines that
share a file system, but to use different configuration files on each machine.
If CONFIGURE_FILE_USE_NODE is defined in Local/Makefile, Exim first looks for a
-file whose name is the configuration file name followed by a dot and the
+file whose name is the configuration filename followed by a dot and the
machine's node name, as obtained from the uname() function. If this file does
-not exist, the standard name is tried. This processing occurs for each file
-name in the list given by CONFIGURE_FILE or -C.
+not exist, the standard name is tried. This processing occurs for each filename
+in the list given by CONFIGURE_FILE or -C.
In some esoteric situations different versions of Exim may be run under
different effective uids and the CONFIGURE_FILE_USE_EUID is defined to help
6.3 File inclusions in the configuration file
---------------------------------------------
-You can include other files inside Exim's run time configuration file by using
+You can include other files inside Exim's runtime configuration file by using
this syntax:
-.include <file name>
-.include_if_exists <file name>
+.include <filename>
+.include_if_exists <filename>
-on a line by itself. Double quotes round the file name are optional. If you use
+on a line by itself. Double quotes round the filename are optional. If you use
the first form, a configuration error occurs if the file does not exist; the
-second form does nothing for non-existent files.
-
-The first form allows a relative name. It is resolved relative to the directory
-of the including file. For the second form an absolute file name is required.
+second form does nothing for non-existent files. The first form allows a
+relative name. It is resolved relative to the directory of the including file.
+For the second form an absolute filename is required.
Includes may be nested to any depth, but remember that Exim reads its
configuration file often, so it is a good idea to keep them to a minimum. If
Once a macro is defined, all subsequent lines in the file (and any included
files) are scanned for the macro name; if there are several macros, the line is
-scanned for each in turn, in the order in which the macros are defined. The
+scanned for each, in turn, in the order in which the macros are defined. The
replacement text is not re-scanned for the current macro, though it is scanned
for subsequently defined macros. For this reason, a macro name may not contain
the name of a previously defined macro as a substring. You could, for example,
_DRIVER_ROUTER_* router drivers
_DRIVER_TRANSPORT_* transport drivers
_DRIVER_AUTHENTICATOR_* authenticator drivers
+ _LOG_* log_selector values
_OPT_MAIN_* main config options
_OPT_ROUTERS_* generic router options
_OPT_TRANSPORTS_* generic transport options
all in the default configuration.
-7.1 Main configuration settings
+7.1 Macros
+----------
+
+All macros should be defined before any options.
+
+One macro is specified, but commented out, in the default configuration:
+
+# ROUTER_SMARTHOST=MAIL.HOSTNAME.FOR.CENTRAL.SERVER.EXAMPLE
+
+If all off-site mail is expected to be delivered to a "smarthost", then set the
+hostname here and uncomment the macro. This will affect which router is used
+later on. If this is left commented out, then Exim will perform direct-to-MX
+deliveries using a dnslookup router.
+
+In addition to macros defined here, Exim includes a number of built-in macros
+to enable configuration to be guarded by a binary built with support for a
+given feature. See section 6.9 for more details.
+
+
+7.2 Main configuration settings
-------------------------------
-The main (global) configuration option settings must always come first in the
-file. The first thing you'll see in the file, after some initial comments, is
-the line
+The main (global) configuration option settings section must always come first
+in the file, after the macros. The first thing you'll see in the file, after
+some initial comments, is the line
# primary_hostname =
These are example settings that can be used when Exim is compiled with support
for TLS (aka SSL) as described in section 4.7. The first one specifies the list
of clients that are allowed to use TLS when connecting to this server; in this
-case the wildcard means all clients. The other options specify where Exim
+case, the wildcard means all clients. The other options specify where Exim
should find its TLS certificate and private key, which together prove the
server's identity to any clients that connect. More details are given in
chapter 42.
These options provide better support for roaming users who wish to use this
server for message submission. They are not much use unless you have turned on
TLS (as described in the previous paragraph) and authentication (about which
-more in section 7.7). The usual SMTP port 25 is often blocked on end-user
-networks, so RFC 4409 specifies that message submission should use port 587
-instead. However some software (notably Microsoft Outlook) cannot be configured
-to use port 587 correctly, so these settings also enable the non-standard
-"smtps" (aka "ssmtp") port 465 (see section 13.4).
+more in section 7.8). Mail submission from mail clients (MUAs) should be
+separate from inbound mail to your domain (MX delivery) for various good
+reasons (eg, ability to impose much saner TLS protocol and ciphersuite
+requirements without unintended consequences). RFC 6409 (previously 4409)
+specifies use of port 587 for SMTP Submission, which uses STARTTLS, so this is
+the "submission" port. RFC 8314 specifies use of port 465 as the "submissions"
+protocol, which should be used in preference to 587. You should also consider
+deploying SRV records to help clients find these ports. Older names for
+"submissions" are "smtps" and "ssmtp".
Two more commented-out options settings follow:
timeout_frozen_after = 7d
The first of these options specifies that failing bounce messages are to be
-discarded after 2 days on the queue. The second specifies that any frozen
+discarded after 2 days in the queue. The second specifies that any frozen
message (whether a bounce message or not) is to be timed out (and discarded)
after a week. In this configuration, the first setting ensures that no failing
bounce message ever lasts a week.
# add_environment = PATH=/usr/bin::/bin
-7.2 ACL configuration
+7.3 ACL configuration
---------------------
In the default configuration, the ACL section follows the main configuration.
convention of local parts constructed as "
first-initial.second-initial.family-name" when applied to someone like the
author of Exim, who has no second initial.) However, a local part starting with
-a dot or containing "/../" can cause trouble if it is used as part of a file
-name (for example, for a mailing list). This is also true for local parts that
-contain slashes. A pipe symbol can also be troublesome if the local part is
-incorporated unthinkingly into a shell command line.
+a dot or containing "/../" can cause trouble if it is used as part of a
+filename (for example, for a mailing list). This is also true for local parts
+that contain slashes. A pipe symbol can also be troublesome if the local part
+is incorporated unthinkingly into a shell command line.
The second rule above applies to all other domains, and is less strict. This
allows your own users to send outgoing messages to sites that use slashes and
Submission mode is again specified, on the grounds that such messages are most
likely to come from MUAs. The default configuration does not define any
authenticators, though it does include some nearly complete commented-out
-examples described in 7.7. This means that no client can in fact authenticate
+examples described in 7.8. This means that no client can in fact authenticate
until you complete the authenticator definitions.
require message = relay not permitted
This final line in the DATA ACL accepts the message unconditionally.
-7.3 Router configuration
+7.4 Router configuration
------------------------
The router configuration comes next in the default configuration, introduced by
begin routers
Routers are the modules in Exim that make decisions about where to send
-messages. An address is passed to each router in turn, until it is either
+messages. An address is passed to each router, in turn, until it is either
accepted, or failed. This means that the order in which you define the routers
matters. Each router is fully described in its own chapter later in this
manual. Here we give only brief overviews.
uncomment this router, you also need to uncomment the setting of
allow_domain_literals in the main part of the configuration.
+Which router is used next depends upon whether or not the ROUTER_SMARTHOST
+macro has been defined, per
+
+.ifdef ROUTER_SMARTHOST
+smarthost:
+#...
+.else
dnslookup:
- driver = dnslookup
+#...
+.endif
+
+If ROUTER_SMARTHOST has been defined, either at the top of the file or on the
+command-line, then we route all non-local mail to that smarthost; otherwise,
+we'll perform DNS lookups for direct-to-MX lookup. Any mail which is to a local
+domain will skip these routers because of the domains option.
+
+smarthost:
+ driver = manualroute
domains = ! +local_domains
- transport = remote_smtp
- ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
+ transport = smarthost_smtp
+ route_data = ROUTER_SMARTHOST
+ ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
no_more
-The first uncommented router handles addresses that do not involve any local
-domains. This is specified by the line
+This router only handles mail which is not to any local domains; this is
+specified by the line
domains = ! +local_domains
it is referring to a named list. Addresses in other domains are passed on to
the following routers.
+The name of the router driver is manualroute because we are manually specifying
+how mail should be routed onwards, instead of using DNS MX. While the name of
+this router instance is arbitrary, the driver option must be one of the driver
+modules that is in the Exim binary.
+
+With no pre-conditions other than domains, all mail for non-local domains will
+be handled by this router, and the no_more setting will ensure that no other
+routers will be used for messages matching the pre-conditions. See 3.12 for
+more on how the pre-conditions apply. For messages which are handled by this
+router, we provide a hostname to deliver to in route_data and the macro
+supplies the value; the address is then queued for the smarthost_smtp
+transport.
+
+dnslookup:
+ driver = dnslookup
+ domains = ! +local_domains
+ transport = remote_smtp
+ ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
+ no_more
+
+The domains option behaves as per smarthost, above.
+
The name of the router driver is dnslookup, and is specified by the driver
option. Do not be confused by the fact that the name of this router instance is
the same as the name of the driver. The instance name is arbitrary, but the
same purpose as they do for the userforward router.
-7.4 Transport configuration
+7.5 Transport configuration
---------------------------
Transports define mechanisms for actually delivering messages. They operate
begin transports
-One remote transport and four local transports are defined.
+Two remote transports and four local transports are defined.
remote_smtp:
driver = smtp
+ message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
+.ifdef _HAVE_DANE
+ dnssec_request_domains = *
+ hosts_try_dane = *
+.endif
+.ifdef _HAVE_PRDR
hosts_try_prdr = *
+.endif
This transport is used for delivering messages over SMTP connections. The list
-of remote hosts comes from the router. The hosts_try_prdr option enables an
-efficiency SMTP option. It is negotiated between client and server and not
-expected to cause problems but can be disabled if needed. All other options are
-defaulted.
+of remote hosts comes from the router. The message_size_limit usage is a hack
+to avoid sending on messages with over-long lines. The built-in macro
+_HAVE_DANE guards configuration to try to use DNSSEC for all queries and to use
+DANE for delivery; see section 42.15 for more details.
+
+The hosts_try_prdr option enables an efficiency SMTP option. It is negotiated
+between client and server and not expected to cause problems but can be
+disabled if needed. The built-in macro _HAVE_PRDR guards the use of the
+hosts_try_prdr configuration option.
+
+The other remote transport is used when delivering to a specific smarthost with
+whom there must be some kind of existing relationship, instead of the usual
+federated system.
+
+smarthost_smtp:
+ driver = smtp
+ message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
+ multi_domain
+ #
+.ifdef _HAVE_TLS
+ # Comment out any of these which you have to, then file a Support
+ # request with your smarthost provider to get things fixed:
+ hosts_require_tls = *
+ tls_verify_hosts = *
+ # As long as tls_verify_hosts is enabled, this won't matter, but if you
+ # have to comment it out then this will at least log whether you succeed
+ # or not:
+ tls_try_verify_hosts = *
+ #
+ # The SNI name should match the name which we'll expect to verify;
+ # many mail systems don't use SNI and this doesn't matter, but if it does,
+ # we need to send a name which the remote site will recognize.
+ # This _should_ be the name which the smarthost operators specified as
+ # the hostname for sending your mail to.
+ tls_sni = ROUTER_SMARTHOST
+ #
+.ifdef _HAVE_OPENSSL
+ tls_require_ciphers = HIGH:!aNULL:@STRENGTH
+.endif
+.ifdef _HAVE_GNUTLS
+ tls_require_ciphers = SECURE192:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1
+.endif
+.endif
+.ifdef _HAVE_PRDR
+ hosts_try_prdr = *
+.endif
+
+After the same message_size_limit hack, we then specify that this Transport can
+handle messages to multiple domains in one run. The assumption here is that
+you're routing all non-local mail to the same place and that place is happy to
+take all messages from you as quickly as possible. All other options depend
+upon built-in macros; if Exim was built without TLS support then no other
+options are defined. If TLS is available, then we configure "stronger than
+default" TLS ciphersuites and versions using the tls_require_ciphers option,
+where the value to be used depends upon the library providing TLS. Beyond that,
+the options adopt the stance that you should have TLS support available from
+your smarthost on today's Internet, so we turn on requiring TLS for the mail to
+be delivered, and requiring that the certificate be valid, and match the
+expected hostname. The tls_sni option can be used by service providers to
+select an appropriate certificate to present to you and here we re-use the
+ROUTER_SMARTHOST macro, because that is unaffected by CNAMEs present in DNS.
+You want to specify the hostname which you'll expect to validate for, and that
+should not be subject to insecure tampering via DNS results.
+
+For the hosts_try_prdr option see the previous transport.
+
+All other options are defaulted.
local_delivery:
driver = appendfile
filter files.
-7.5 Default retry rule
+7.6 Default retry rule
----------------------
The retry section of the configuration file contains rules which affect the way
temporary errors into permanent errors.
-7.6 Rewriting configuration
+7.7 Rewriting configuration
---------------------------
The rewriting section of the configuration, introduced by
rewriting rules in the default configuration file.
-7.7 Authenticators configuration
+7.8 Authenticators configuration
--------------------------------
The authenticators section of the configuration, introduced by
LOGIN. The server_advertise_condition setting controls when Exim offers
authentication to clients; in the examples, this is only when TLS or SSL has
been started, so to enable the authenticators you also need to add support for
-TLS as described in section 7.1.
+TLS as described in section 7.2.
The server_condition setting defines how to verify that the username and
password are correct. In the examples it just produces an error message. To
indexed files that are read frequently and never updated, except by total
re-creation. As such, it is particularly suitable for large files
containing aliases or other indexed data referenced by an MTA. Information
- about cdb can be found in several places:
+ about cdb and tools for building the files can be found in several places:
- http://www.pobox.com/~djb/cdb.html
- ftp://ftp.corpit.ru/pub/tinycdb/
- http://packages.debian.org/stable/utils/freecdb.html
+ https://cr.yp.to/cdb.html
+ http://www.corpit.ru/mjt/tinycdb.html
+ https://packages.debian.org/stable/utils/freecdb
+ https://github.com/philpennock/cdbtools (in Go)
A cdb distribution is not needed in order to build Exim with cdb support,
because the code for reading cdb files is included directly in Exim itself.
wildlsearch can not be turned into a DBM or cdb file, because those lookup
types support only literal keys.
+ * If Exim is built with SPF support, manual lookups can be done (as opposed
+ to the standard ACL condition method. For details see section 57.4.
+
9.4 Query-style lookup types
----------------------------
* redis: The format of the query is either a simple get or simple set, passed
to a Redis database. See section 9.21.
- * sqlite: The format of the query is a file name followed by an SQL statement
+ * sqlite: The format of the query is a filename followed by an SQL statement
that is passed to an SQLite database. See section 9.26.
* testdb: This is a lookup type that is used for testing Exim. It is not
9.26 More about SQLite
----------------------
-SQLite is different to the other SQL lookups because a file name is required in
+SQLite is different to the other SQL lookups because a filename is required in
addition to the SQL query. An SQLite database is a single file, and there is no
daemon as in the other SQL databases. The interface to Exim requires the name
of the file, as an absolute path, to be given at the start of the query. It is
${lookup redis{set keyname ${quote_redis:objvalue plus}}}
${lookup redis{get keyname}}
+As of release 4.91, "lightweight" support for Redis Cluster is available.
+Requires redis_servers list to contain all the servers in the cluster, all of
+which must be reachable from the running exim instance. If the cluster has
+master/slave replication, the list must contain all the master and slave
+servers.
+
+When the Redis Cluster returns a "MOVED" response to a query, Exim does not
+immediately follow the redirection but treats the response as a DEFER, moving
+on to the next server in the redis_servers list until the correct server is
+reached.
+
===============================================================================
10.3 File names in lists
------------------------
-If an item in a domain, host, address, or local part list is an absolute file
-name (beginning with a slash character), each line of the file is read and
+If an item in a domain, host, address, or local part list is an absolute
+filename (beginning with a slash character), each line of the file is read and
processed as if it were an independent item in the list, except that further
-file names are not allowed, and no expansion of the data from the file takes
+filenames are not allowed, and no expansion of the data from the file takes
place. Empty lines in the file are ignored, and the file may also contain
comment lines:
not#comment@x.y.z # but this is a comment
-Putting a file name in a list has the same effect as inserting each line of the
+Putting a filename in a list has the same effect as inserting each line of the
file as an item in the list (blank lines and comments excepted). However, there
is one important difference: the file is read each time the list is processed,
so if its contents vary over time, Exim's behaviour changes.
-If a file name is preceded by an exclamation mark, the sense of any match
-within the file is inverted. For example, if
+If a filename is preceded by an exclamation mark, the sense of any match within
+the file is inverted. For example, if
hold_domains = !/etc/nohold-domains
just as for any other single-key lookup type.
If you want to use a file to contain wild-card patterns that form part of a
-list, just give the file name on its own, without a search type, as described
-in the previous section. You could also use the wildlsearch or nwildlsearch,
-but there is no advantage in doing this.
+list, just give the filename on its own, without a search type, as described in
+the previous section. You could also use the wildlsearch or nwildlsearch, but
+there is no advantage in doing this.
10.5 Named lists
* If a pattern starts with the name of a single-key lookup type followed by a
semicolon (for example, "dbm;" or "lsearch;"), the remainder of the pattern
- must be a file name in a suitable format for the lookup type. For example,
+ must be a filename in a suitable format for the lookup type. For example,
for "cdb;" it must be an absolute path:
domains = cdb;/etc/mail/local_domains.cdb
The domain portion of an address is always lowercased before matching it to an
address list. The local part is lowercased by default, and any string
comparisons that take place are done caselessly. This means that the data in
-the address list itself, in files included as plain file names, and in any file
+the address list itself, in files included as plain filenames, and in any file
that is looked up using the "@@" mechanism, can be in any case. However, the
keys in files that are looked up by a search type other than lsearch (which
works caselessly) must be in lower case, because these lookups are not
===============================================================================
11. STRING EXPANSIONS
-Many strings in Exim's run time configuration are expanded before use. Some of
+Many strings in Exim's runtime configuration are expanded before use. Some of
them are expanded every time they are used; others are expanded only once.
When a string is being expanded it is copied verbatim from left to right except
If you want to test expansions that include variables whose values are taken
from a message, there are two other options that can be used. The -bem option
-is like -be except that it is followed by a file name. The file is read as a
+is like -be except that it is followed by a filename. The file is read as a
message before doing the test expansions. For example:
exim -bem /tmp/test.message '$h_subject:'
is an empty string. If the ACL returns defer the result is a forced-fail.
Otherwise the expansion fails.
+${authresults{<authserv-id>}}
+
+ This item returns a string suitable for insertion as an
+ Authentication-Results" header line. The given <authserv-id> is included in
+ the result; typically this will be a domain name identifying the system
+ performing the authentications. Methods that might be present in the result
+ include:
+
+ none
+ iprev
+ auth
+ spf
+ dkim
+
+ Example use (as an ACL modifier):
+
+ add_header = :at_start:${authresults {$primary_hostname}}
+
+ This is safe even if no authentication results are available.
+
${certextract{<field>}{<certificate>}{<string2>}{<string3>}}
The <certificate> must be a variable of type certificate. The field name is
This forces an expansion failure (see section 11.4); {<string2>} must be
present for "fail" to be recognized.
+${extract json{<key>}{<string1>}{<string2>}{<string3>}}
+
+ The key and <string1> are first expanded separately. Leading and trailing
+ white space is removed from the key (but not from any of the strings). The
+ key must not be empty and must not consist entirely of digits. The expanded
+ <string1> must be of the form:
+
+ { <"key1"> : <value1> , <"key2"> , <value2> ... }
+
+ The braces, commas and colons, and the quoting of the member name are
+ required; the spaces are optional. Matching of the key against the member
+ names is done case-sensitively.
+
+ The results of matching are handled as above.
+
${extract{<number>}{<separators>}{<string1>}{<string2>}{<string3>}}
The <number> argument must consist entirely of decimal digits, apart from
yields "99". Two successive separators mean that the field between them is
empty (for example, the fifth field above).
+${extract json{<number>}}{<string1>}{<string2>}{<string3>}}
+
+ The <number> argument must consist entirely of decimal digits, apart from
+ leading and trailing white space, which is ignored.
+
+ Field selection and result handling is as above; there is no choice of
+ field separator.
+
${filter{<string>}{<condition>}}
After expansion, <string> is interpreted as a list, colon-separated by
- default, but the separator can be changed in the usual way. For each item
- in this list, its value is place in $item, and then the condition is
+ default, but the separator can be changed in the usual way (6.21). For each
+ item in this list, its value is place in $item, and then the condition is
evaluated. If the condition is true, $item is added to the output as an
item in a new list; if the condition is false, the item is discarded. The
separator used for the output list is the same as the one used for the
input, but a separator setting is not included in the output. For example:
- ${filter{a:b:c}{!eq{$item}{b}}
+ ${filter{a:b:c}{!eq{$item}{b}}}
yields "a:c". At the end of the expansion, the value of $item is restored
to what it was before. See also the map and reduce expansion items.
$hash{4}{62}{monty python}} yields fbWx
$header_<header name>: or $h_<header name>:, $bheader_<header name>: or $bh_<
- header name>:, $rheader_<header name>: or $rh_<header name>:
+ header name>:, $lheader_<header name>: or $lh_<header name>:
- Substitute the contents of the named message header line, for example
+ "$rheader_<header name>: or $rh_<header name>:" Substitute the contents of
+ the named message header line, for example
$header_reply-to:
but internal newlines (caused by splitting the header line over several
physical lines) may be present.
- The difference between rheader, bheader, and header is in the way the data
+ The difference between the four pairs of expansions is in the way the data
in the header line is interpreted.
+ rheader gives the original "raw" content of the header line, with no
processing at all, and without the removal of leading and trailing
white space.
+ + lheader gives a colon-separated list, one element per header when there
+ are multiple headers with a given name. Any embedded colon characters
+ within an element are doubled, so normal Exim list-processing
+ facilities can be used. The terminating newline of each element is
+ removed; in other respects the content is "raw".
+
+ bheader removes leading and trailing white space, and then decodes
base64 or quoted-printable MIME "words" within the header text, but
does no character set translation. If decoding of what looks
filter. Header lines that are added to a particular copy of a message by a
router or transport are not accessible.
- For incoming SMTP messages, no header lines are visible in
-
- ACLs that are obeyed before the data phase completes,
-
- because the header structure is not set up until the message is received.
- They are visible in DKIM, PRDR and DATA ACLs. Header lines that are added
- in a RCPT ACL (for example) are saved until the message's incoming header
- lines are available, at which point they are added.
-
- When any of the above ACLs ar
-
- running, however, header lines added by earlier ACLs are visible.
+ For incoming SMTP messages, no header lines are visible in ACLs that are
+ obeyed before the data phase completes, because the header structure is not
+ set up until the message is received. They are visible in DKIM, PRDR and
+ DATA ACLs. Header lines that are added in a RCPT ACL (for example) are
+ saved until the message's incoming header lines are available, at which
+ point they are added. When any of the above ACLs ar running, however,
+ header lines added by earlier ACLs are visible.
Upper case and lower case letters are synonymous in header names. If the
following character is white space, the terminating colon may be omitted,
X-Spam-Scanned: header line. If you know the secret, you can check that
this header line is authentic by recomputing the authentication code from
the host name, message ID and the Message-id: header line. This can be done
- using Exim's -be option, or by other means, for example by using the
+ using Exim's -be option, or by other means, for example, by using the
hmac_md5_hex() function in Perl.
${if <condition> {<string1>}{<string2>}}
${length_<n>:<string>}
- The result of this item is either the first <n> characters or the whole of
- <string2>, whichever is the shorter. Do not confuse length with strlen,
- which gives the length of a string.
+ The result of this item is either the first <n> bytes or the whole of <
+ string2>, whichever is the shorter. Do not confuse length with strlen,
+ which gives the length of a string. All measurement is done in bytes and is
+ not UTF-8 aware.
${listextract{<number>}{<string1>}{<string2>}{<string3>}}
ignored).
After expansion, <string1> is interpreted as a list, colon-separated by
- default, but the separator can be changed in the usual way.
+ default, but the separator can be changed in the usual way (6.21).
The first field of the list is numbered one. If the number is negative, the
fields are counted from the end of the list, with the rightmost one
${map{<string1>}{<string2>}}
After expansion, <string1> is interpreted as a list, colon-separated by
- default, but the separator can be changed in the usual way. For each item
- in this list, its value is place in $item, and then <string2> is expanded
- and added to the output as an item in a new list. The separator used for
- the output list is the same as the one used for the input, but a separator
- setting is not included in the output. For example:
+ default, but the separator can be changed in the usual way (6.21). For each
+ item in this list, its value is place in $item, and then <string2> is
+ expanded and added to the output as an item in a new list. The separator
+ used for the output list is the same as the one used for the input, but a
+ separator setting is not included in the output. For example:
${map{a:b:c}{[$item]}} ${map{<- x-y-z}{($item)}}
${readfile{<file name>}{<eol string>}}
- The file name and end-of-line string are first expanded separately. The
- file is then read, and its contents replace the entire item. All newline
+ The filename and end-of-line string are first expanded separately. The file
+ is then read, and its contents replace the entire item. All newline
characters in the file are replaced by the end-of-line string if it is
present. Otherwise, newlines are left in the string. String expansion is
not applied to the contents of the file. If you want this, you must wrap
The redirect router has an option called forbid_filter_readfile which locks
out the use of this expansion item in filter files.
-${readsocket{<name>}{<request>}{<timeout>}{<eol string>}{<fail string>}}
+${readsocket{<name>}{<request>}{<options>}{<eol string>}{<fail string>}}
This item inserts data from a Unix domain or TCP socket into the expanded
string. The minimal way of using it uses just two arguments, as in these
${readsocket{/socket/name}{request string}{3s}}
+ The third argument is a list of options, of which the first element is the
+ timeout and must be present if the argument is given. Further elements are
+ options of form name=value. Two option types is currently recognised:
+ shutdown and tls. The first defines whether (the default) or not a shutdown
+ is done on the connection after sending the request. Example, to not do so
+ (preferred, eg. by some webservers):
+
+ ${readsocket{/socket/name}{request string}{3s:shutdown=no}}
+
+ The second, tls, controls the use of TLS on the connection. Example:
+
+ ${readsocket{/socket/name}{request string}{3s:tls=yes}}
+
+ The default is to not use TLS. If it is enabled, a shutdown as descripbed
+ above is never done.
+
A fourth argument allows you to change any newlines that are in the data
that is read, in the same way as for readfile (see above). This example
turns them into spaces:
This operation reduces a list to a single, scalar string. After expansion,
<string1> is interpreted as a list, colon-separated by default, but the
- separator can be changed in the usual way. Then <string2> is expanded and
- assigned to the $value variable. After this, each item in the <string1>
- list is assigned to $item in turn, and <string3> is expanded for each of
- them. The result of that expansion is assigned to $value before the next
- iteration. When the end of the list is reached, the final value of $value
- is added to the expansion output. The reduce expansion item can be used in
- a number of ways. For example, to add up a list of numbers:
+ separator can be changed in the usual way (6.21). Then <string2> is
+ expanded and assigned to the $value variable. After this, each item in the
+ <string1> list is assigned to $item, in turn, and <string3> is expanded for
+ each of them. The result of that expansion is assigned to $value before the
+ next iteration. When the end of the list is reached, the final value of
+ $value is added to the expansion output. The reduce expansion item can be
+ used in a number of ways. For example, to add up a list of numbers:
${reduce {<, 1,2,3}{0}{${eval:$value+$item}}}
$rheader_<header name>: or $rh_<header name>:
This item inserts "raw" header lines. It is described with the header
- expansion item above.
+ expansion item in section 11.5 above.
${run{<command> <args>}{<string1>}{<string2>}}
${sg{abcdefabcdef}{abc}{xyz}}
yields "xyzdefxyzdef". Because all three arguments are expanded before use,
- if any $ or \ characters are required in the regular expression or in the
- substitution string, they have to be escaped. For example:
+ if any $, } or \ characters are required in the regular expression or in
+ the substitution string, they have to be escaped. For example:
${sg{abcdef}{^(...)(...)\$}{\$2\$1}}
yields "K1=A K4=D K3=C". Note the use of "\N" to protect the contents of
the regular expression from string expansion.
+ The regular expression is compiled in 8-bit mode, working against bytes
+ rather than any Unicode-aware character handling.
+
${sort{<string>}{<comparator>}{<extractor>}}
After expansion, <string> is interpreted as a list, colon-separated by
- default, but the separator can be changed in the usual way. The <comparator
- > argument is interpreted as the operator of a two-argument expansion
- condition. The numeric operators plus ge, gt, le, lt (and ~i variants) are
- supported. The comparison should return true when applied to two values if
- the first value should sort before the second value. The <extractor>
- expansion is applied repeatedly to elements of the list, the element being
- placed in $item, to give values for comparison.
+ default, but the separator can be changed in the usual way (6.21). The <
+ comparator> argument is interpreted as the operator of a two-argument
+ expansion condition. The numeric operators plus ge, gt, le, lt (and ~i
+ variants) are supported. The comparison should return true when applied to
+ two values if the first value should sort before the second value. The <
+ extractor> expansion is applied repeatedly to elements of the list, the
+ element being placed in $item, to give values for comparison.
The item result is a sorted list, with the original list separator, of the
list elements (in full) of the original.
If the starting offset is greater than the string length the result is the
null string; if the length plus starting offset is greater than the string
length, the result is the right-hand part of the string, starting from the
- given offset. The first character in the string has offset zero.
+ given offset. The first byte (character) in the string has offset zero.
The substr expansion item can take negative offset values to count from the
- right-hand end of its operand. The last character is offset -1, the
+ right-hand end of its operand. The last byte (character) is offset -1, the
second-last is offset -2, and so on. Thus, for example,
${substr{-5}{2}{1234567}}
yields "1".
When the second number is omitted from substr, the remainder of the string
- is taken if the offset is positive. If it is negative, all characters in
- the string preceding the offset point are taken. For example, an offset of
- -1 and no length, as in these semantically identical examples:
+ is taken if the offset is positive. If it is negative, all bytes
+ (characters) in the string preceding the offset point are taken. For
+ example, an offset of -1 and no length, as in these semantically identical
+ examples:
${substr_-1:abcde}
${substr{-1}{abcde}}
yields all but the last character of the string, that is, "abcd".
+ All measurement is done in bytes and is not UTF-8 aware.
+
${tr{<subject>}{<characters>}{<replacements>}}
- This item does single-character translation on its subject string. The
- second argument is a list of characters to be translated in the subject
- string. Each matching character is replaced by the corresponding character
- from the replacement list. For example
+ This item does single-character (in bytes) translation on its subject
+ string. The second argument is a list of characters to be translated in the
+ subject string. Each matching character is replaced by the corresponding
+ character from the replacement list. For example
${tr{abcdea}{ac}{13}}
second, its last character is replicated. However, if it is empty, no
translation takes place.
+ All character handling is done in bytes and is not UTF-8 aware.
+
11.6 Expansion operators
------------------------
header line, and the effective address is extracted from it. If the string
does not parse successfully, the result is empty.
+ The parsing correctly handles SMTPUTF8 Unicode in the string.
+
${addresses:<string>}
The string (after expansion) is interpreted as a list of addresses in RFC
${addresses:>& Chief <ceo@up.stairs>, sec@base.ment (dogsbody)}
- expands to "ceo@up.stairs&sec@base.ment". Compare the address (singular)
- expansion item, which extracts the working address from a single RFC2822
- address. See the filter, map, and reduce items for ways of processing
- lists.
+ expands to "ceo@up.stairs&sec@base.ment". The string is expanded first, so
+ if the expanded string starts with >, it may change the output separator
+ unintentionally. This can be avoided by setting the output separator
+ explicitly:
+
+ ${addresses:>:$h_from:}
+
+ Compare the address (singular) expansion item, which extracts the working
+ address from a single RFC2822 address. See the filter, map, and reduce
+ items for ways of processing lists.
To clarify "list of addresses in RFC 2822 format" mentioned above, Exim
follows a strict interpretation of header line formatting. Exim parses the
"=2C". The second example below is passed the contents of "$header_from:",
meaning it gets de-mimed. Exim sees the decoded "," so it treats it as two
email addresses. The third example shows that the presence of a comma is
- skipped when it is quoted.
+ skipped when it is quoted. The fourth example shows SMTPUTF8 handling.
# exim -be '${addresses:From: \
=?iso-8859-2?Q?Last=2C_First?= <user@example.com>}'
Last:user@example.com
# exim -be '${addresses:From: "Last, First" <user@example.com>}'
user@example.com
+ # exim -be '${addresses:?????? <??????????@example.jp>}'
+ ??????????@example.jp
${base32:<digits>}
to base 62 and output as a string of six characters, including leading
zeros. In the few operating environments where Exim uses base 36 instead of
base 62 for its message identifiers (because those systems do not have
- case-sensitive file names), base 36 is used by this operator, despite its
+ case-sensitive filenames), base 36 is used by this operator, despite its
name. Note: Just to be absolutely clear: this is not base64 encoding.
${base62d:<base-62 digits>}
${hex2b64:<hexstring>}
This operator converts a hex string into one that is base64 encoded. This
- can be useful for processing the output of the MD5 and SHA-1 hashing
- functions.
+ can be useful for processing the output of the various hashing functions.
${hexquote:<string>}
This operator converts non-printable characters in a string into a hex
escape form. Byte values between 33 (!) and 126 (~) inclusive are left as
- is, and other byte values are converted to "\xNN", for example a byte value
- 127 is converted to "\x7f".
+ is, and other byte values are converted to "\xNN", for example, a byte
+ value 127 is converted to "\x7f".
${ipv6denorm:<string>}
${lc:$local_part}
+ Case is defined per the system C locale.
+
${length_<number>:<string>}
The length operator is a simpler interface to the length function that can
See the description of the general length item above for details. Note that
length is not the same as strlen. The abbreviation l can be used when
- length is used as an operator.
+ length is used as an operator. All measurement is done in bytes and is not
+ UTF-8 aware.
${listcount:<string>}
The string is interpreted as an RFC 2822 address and the local part is
extracted from it. If the string does not parse successfully, the result is
- empty.
+ empty. The parsing correctly handles SMTPUTF8 Unicode in the string.
${mask:<IP address>/<bit count>}
you are creating a new email address from the contents of $local_part (or
any other unknown data), you should always use this operator.
+ This quoting determination is not SMTPUTF8-aware, thus quoting non-ASCII
+ data will likely use the quoting form. Thus ${quote_local_part:??????} will
+ always become "??????".
+
${quote_<lookup-type>:<string>}
This operator applies lookup-specific quoting rules to the string. Each
default.
The sha3 expansion item is only supported if Exim has been compiled with
- GnuTLS 3.5.0 or later.
+ GnuTLS 3.5.0 or later, or OpenSSL 1.1.1 or later. The macro
+ "_CRYPTO_HASH_SHA3" will be defined if it is supported.
${stat:<string>}
${strlen:<string>}
The item is replace by the length of the expanded string, expressed as a
- decimal number. Note: Do not confuse strlen with length.
+ decimal number. Note: Do not confuse strlen with length. All measurement is
+ done in bytes and is not UTF-8 aware.
${substr_<start>_<length>:<string>}
${substr{<start>}{<length>}{<string>}}
See the description of the general substr item above for details. The
- abbreviation s can be used when substr is used as an operator.
+ abbreviation s can be used when substr is used as an operator. All
+ measurement is done in bytes and is not UTF-8 aware.
${time_eval:<string>}
${uc:<string>}
- This forces the letters in the string into upper-case.
+ This forces the letters in the string into upper-case. Case is defined per
+ the system C locale.
${utf8clean:<string>}
This replaces any invalid utf-8 sequence in the string by the character "?
".
+ In versions of Exim before 4.92, this did not correctly do so for a
+ truncated final codepoint's encoding, and the character would be silently
+ dropped. If you must handle detection of this scenario across both sets of
+ Exim behavior, the complexity will depend upon the task. For instance, to
+ detect if the first character is multibyte and a 1-byte extraction can be
+ successfully used as a path component (as is common for dividing up
+ delivery folders), you might use:
+
+ condition = ${if inlist{${utf8clean:${length_1:$local_part}}}{:?}{yes}{no}}
+
+ (which will false-positive if the first character of the local part is a
+ literal question mark).
+
${utf8_domain_to_alabel:<string>}, ${utf8_domain_from_alabel:<string>}, $
{utf8_localpart_to_alabel:<string>}, ${utf8_localpart_from_alabel:<string>}
The two substrings are first expanded. The condition is true if the two
resulting strings are identical. For eq the comparison includes the case of
- letters, whereas for eqi the comparison is case-independent.
+ letters, whereas for eqi the comparison is case-independent, where case is
+ defined per the system C locale.
exists {<file name>}
These conditions iterate over a list. The first argument is expanded to
form the list. By default, the list separator is a colon, but it can be
- changed by the normal method. The second argument is interpreted as a
- condition that is to be applied to each item in the list in turn. During
+ changed by the normal method (6.21). The second argument is interpreted as
+ a condition that is to be applied to each item in the list in turn. During
the interpretation of the condition, the current list item is placed in a
variable called $item.
The two substrings are first expanded. The condition is true if the first
string is lexically greater than or equal to the second string. For ge the
comparison includes the case of letters, whereas for gei the comparison is
- case-independent.
+ case-independent. Case and collation order are defined per the system C
+ locale.
gt {<string1>}{<string2>}, gti {<string1>}{<string2>}
The two substrings are first expanded. The condition is true if the first
string is lexically greater than the second string. For gt the comparison
includes the case of letters, whereas for gti the comparison is
- case-independent.
+ case-independent. Case and collation order are defined per the system C
+ locale.
inlist {<string1>}{<string2>}, inlisti {<string1>}{<string2>}
Both strings are expanded; the second string is treated as a list of simple
strings; if the first string is a member of the second, then the condition
- is true.
+ is true. For the case-independent inlisti condition, case is defined per
+ the system C locale.
These are simpler to use versions of the more powerful forany condition.
Examples, and the forany equivalents:
empty component (adjacent colons) is present. Only one empty component is
permitted.
- Note: The checks are just on the form of the address; actual numerical
- values are not considered. Thus, for example, 999.999.999.999 passes the
- IPv4 check. The main use of these tests is to distinguish between IP
- addresses and host names, or between IPv4 and IPv6 addresses. For example,
- you could use
+ Note: The checks used to be just on the form of the address; actual
+ numerical values were not considered. Thus, for example, 999.999.999.999
+ passed the IPv4 check. This is no longer the case.
+
+ The main use of these tests is to distinguish between IP addresses and host
+ names, or between IPv4 and IPv6 addresses. For example, you could use
${if isip4{$sender_host_address}...
The two substrings are first expanded. The condition is true if the first
string is lexically less than or equal to the second string. For le the
comparison includes the case of letters, whereas for lei the comparison is
- case-independent.
+ case-independent. Case and collation order are defined per the system C
+ locale.
lt {<string1>}{<string2>}, lti {<string1>}{<string2>}
The two substrings are first expanded. The condition is true if the first
string is lexically less than the second string. For lt the comparison
includes the case of letters, whereas for lti the comparison is
- case-independent.
+ case-independent. Case and collation order are defined per the system C
+ locale.
match {<string1>}{<string2>}
there is no circumflex, the expression is not anchored, and it may match
anywhere in the subject, not just at the start. If you want the pattern to
match at the end of the subject, you must include the "$" metacharacter at
- an appropriate point.
+ an appropriate point. All character handling is done in bytes and is not
+ UTF-8 aware, but we might change this in a future Exim release.
At the start of an if expansion the values of the numeric variable
substitutions $1 etc. are remembered. Obeying a match condition that
${if match_domain{a.b.c}{x.y.z:a.b.c:p.q.r}{yes}{no}}
In each case, the second argument may contain any of the allowable items
- for a list of the appropriate type. Also, because the second argument
- (after expansion) is a standard form of list, it is possible to refer to a
- named list. Thus, you can use conditions like this:
+ for a list of the appropriate type. Also, because the second argument is a
+ standard form of list, it is possible to refer to a named list. Thus, you
+ can use conditions like this:
${if match_domain{$domain}{+local_domains}{...
pam {<string1>:<string2>:...}
- Pluggable Authentication Modules (http://www.kernel.org/pub/linux/libs/pam/
- ) are a facility that is available in the latest releases of Solaris and in
- some GNU/Linux distributions. The Exim support, which is intended for use
- in conjunction with the SMTP AUTH command, is available only if Exim is
- compiled with
+ Pluggable Authentication Modules (https://mirrors.edge.kernel.org/pub/linux
+ /libs/pam/) are a facility that is available in the latest releases of
+ Solaris and in some GNU/Linux distributions. The Exim support, which is
+ intended for use in conjunction with the SMTP AUTH command, is available
+ only if Exim is compiled with
SUPPORT_PAM=yes
In some operating systems, PAM authentication can be done only from a
process running as root. Since Exim is running as the Exim user when
receiving messages, this means that PAM cannot be used directly in those
- systems. A patched version of the pam_unix module that comes with the Linux
- PAM package is available from http://www.e-admin.de/pam_exim/. The patched
- module allows one special uid/gid combination, in addition to root, to
- authenticate. If you build the patched module to allow the Exim user and
- group, PAM can then be used from an Exim authenticator.
+ systems.
pwcheck {<string1>:<string2>}
$authenticated_id (see chapter 33). For example, a user/password
authenticator configuration might preserve the user name for use in the
routers. Note that this is not the same information that is saved in
- $sender_host_authenticated. When a message is submitted locally (that is,
- not over a TCP connection) the value of $authenticated_id is normally the
- login name of the calling process. However, a trusted user can override
- this by means of the -oMai command line option.
+ $sender_host_authenticated.
+
+ When a message is submitted locally (that is, not over a TCP connection)
+ the value of $authenticated_id is normally the login name of the calling
+ process. However, a trusted user can override this by means of the -oMai
+ command line option. This second case also sets up information used by the
+ $authresults expansion item.
$authenticated_fail_id
The building process for Exim keeps a count of the number of times it has
been compiled. This serves to distinguish different compilations of the
- same version of the program.
+ same version of Exim.
$config_dir
The name of the main configuration file Exim is using.
-$dkim_cur_signer, $dkim_verify_status, $dkim_verify_reason, $dkim_domain,
- $dkim_identity, $dkim_selector, $dkim_algo, $dkim_canon_body,
- $dkim_canon_headers, $dkim_copiedheaders, $dkim_bodylength, $dkim_created,
- $dkim_expires, $dkim_headernames, $dkim_key_testing, $dkim_key_nosubdomains
- , $dkim_key_srvtype, $dkim_key_granularity, $dkim_key_notes,
- $dkim_key_length
+$dkim_verify_status
+
+ Results of DKIM verification. For details see section 57.3.
+
+$dkim_cur_signer, $dkim_verify_reason, $dkim_domain, $dkim_identity,
+ $dkim_selector, $dkim_algo, $dkim_canon_body, $dkim_canon_headers,
+ $dkim_copiedheaders, $dkim_bodylength, $dkim_created, $dkim_expires,
+ $dkim_headernames, $dkim_key_testing, $dkim_key_nosubdomains,
+ $dkim_key_srvtype, $dkim_key_granularity, $dkim_key_notes, $dkim_key_length
These variables are only available within the DKIM ACL. For details see
- chapter 57.
+ section 57.3.
$dkim_signers
When a message has been received this variable contains a colon-separated
list of signer domains and identities for the message. For details see
- chapter 57.
+ section 57.3.
$dnslist_domain, $dnslist_matched, $dnslist_text, $dnslist_value
This is not strictly an expansion variable. It is expansion syntax for
inserting the message header line with the given name. Note that the name
must be terminated by colon or white space, because it may contain a wide
- variety of characters. Note also that braces must not be used.
+ variety of characters. Note also that braces must not be used. See the full
+ description in section 11.5 above.
$headers_added
checking the result, the name is not accepted, and $host_lookup_deferred is
set to "1". See also $sender_host_name.
+ Performing these checks sets up information used by the $authresults
+ expansion item.
+
$host_lookup_failed
See $host_lookup_deferred.
When a message is being delivered to a file, pipe, or autoreply transport
as a result of aliasing or forwarding, $local_part is set to the local part
- of the parent address, not to the file name or command (see $address_file
+ of the parent address, not to the filename or command (see $address_file
and $address_pipe).
When an ACL is running for a RCPT command, $local_part contains the local
This variable contains the number of bytes in the longest line that was
received as part of the message, not counting the line termination
- character(s).
+ character(s). It is not valid if the spool_files_wireformat option is used.
$message_age
that separates the body from the header. Newlines are included in the
count. See also $message_size, $body_linecount, and $body_zerocount.
+ If the spool file is wireformat (see the spool_files_wireformat main
+ option) the CRLF line-terminators are included in the count.
+
$message_exim_id
When a message is being received or delivered, this variable contains the
In the MAIL and RCPT ACLs, the value is zero because at that stage the
message has not yet been received.
+ This variable is not valid if the spool_files_wireformat option is used.
+
$message_size
When a message is being processed, this variable contains its size in
line option.
As well as being useful in ACLs (including the "connect" ACL), these
- variable could be used, for example, to make the file name for a TLS
+ variable could be used, for example, to make the filename for a TLS
certificate depend on which interface and/or port is being used for the
incoming connection. The values of $received_ip_address and $received_port
are saved with any messages that are received, thus making these variables
variable is somewhat redundant, but is retained for backwards
compatibility.
+$smtp_command_history
+
+ A comma-separated list (with no whitespace) of the most-recent SMTP
+ commands received, in time-order left to right. Only a limited number of
+ commands are remembered.
+
$smtp_count_at_connection_start
This variable is set greater than zero only in processes spawned by the
is compiled with the content-scanning extension. For details, see section
44.2.
+$spf_header_comment, $spf_received, $spf_result, $spf_result_guessed,
+ $spf_smtp_comment
+
+ These variables are only available if Exim is built with SPF support. For
+ details see section 57.4.
+
$spool_directory
The name of Exim's spool directory.
of a certextract expansion item, md5, sha1 or sha256 operator, or a def
condition.
+ Note: Under versions of OpenSSL preceding 1.1.1, when a list of more than
+ one file is used for tls_certificate, this variable is not reliable.
+
$tls_in_peercert
This variable refers to the certificate presented by the peer of an inbound
for details of TLS support and chapter 30 for details of the smtp
transport.
+$tls_out_dane
+
+ DANE active status. See section 42.15.
+
$tls_in_ocsp
When a message is received from a remote client connection the result of
During outbound SMTP deliveries, this variable reflects the value of the
tls_sni option on the transport.
+$tls_out_tlsa_usage
+
+ Bitfield of TLSA record types found. See section 42.15.
+
$tod_bsdinbox
The time of day and the date, in the format required for BSD-style mailbox
listen. Each item may optionally also specify a port.
The default list separator in both cases is a colon, but this can be changed as
-described in section 6.20. When IPv6 addresses are involved, it is usually best
+described in section 6.21. When IPv6 addresses are involved, it is usually best
to change the separator to avoid having to double all the colons. For example:
local_interfaces = <; 127.0.0.1 ; \
configuration by -D is allowed only when the caller is root or exim.
The value of -oX is a list of items. The default colon separator can be changed
-in the usual way if required. If there are any items that do not contain dots
-or colons (that is, are not IP addresses), the value of daemon_smtp_ports is
-replaced by the list of those items. If there are any items that do contain
+in the usual way (6.21) if required. If there are any items that do not contain
+dots or colons (that is, are not IP addresses), the value of daemon_smtp_ports
+is replaced by the list of those items. If there are any items that do contain
dots or colons, the value of local_interfaces is replaced by those items. Thus,
for example,
daemon_smtp_ports is no longer relevant in this example.)
-13.4 Support for the obsolete SSMTP (or SMTPS) protocol
--------------------------------------------------------
+13.4 Support for the submissions (aka SSMTP or SMTPS) protocol
+--------------------------------------------------------------
-Exim supports the obsolete SSMTP protocol (also known as SMTPS) that was used
-before the STARTTLS command was standardized for SMTP. Some legacy clients
-still use this protocol. If the tls_on_connect_ports option is set to a list of
-port numbers or service names, connections to those ports must use SSMTP. The
-most common use of this option is expected to be
+Exim supports the use of TLS-on-connect, used by mail clients in the
+"submissions" protocol, historically also known as SMTPS or SSMTP. For some
+years, IETF Standards Track documents only blessed the STARTTLS-based
+Submission service (port 587) while common practice was to support the same
+feature set on port 465, but using TLS-on-connect. If your installation needs
+to provide service to mail clients (Mail User Agents, MUAs) then you should
+provide service on both the 587 and the 465 TCP ports.
+
+If the tls_on_connect_ports option is set to a list of port numbers or service
+names, connections to those ports must first establish TLS, before proceeding
+to the application layer use of the SMTP protocol.
+
+The common use of this option is expected to be
tls_on_connect_ports = 465
-because 465 is the usual port number used by the legacy clients. There is also
-a command line option -tls-on-connect, which forces all ports to behave in this
-way when a daemon is started.
+per RFC 8314. There is also a command line option -tls-on-connect, which forces
+all ports to behave in this way when a daemon is started.
Warning: Setting tls_on_connect_ports does not of itself cause the daemon to
listen on those ports. You must still specify them in daemon_smtp_ports,
===============================================================================
14. MAIN CONFIGURATION
-The first part of the run time configuration file contains three types of item:
+The first part of the runtime configuration file contains three types of item:
* Macro definitions: These lines start with an upper case letter. See section
6.4 for details of macro processing.
message_body_visible how much to show in $message_body
mua_wrapper run in "MUA wrapper" mode
print_topbitchars top-bit characters are printing
+spool_wireformat use wire-format spool data files when possible
timezone force time zone
14.3 Privilege controls
-----------------------
-admin_groups groups that are Exim admin users
-deliver_drop_privilege drop root for delivery processes
-local_from_check insert Sender: if necessary
-local_from_prefix for testing From: for local sender
-local_from_suffix for testing From: for local sender
-local_sender_retain keep Sender: from untrusted user
-never_users do not run deliveries as these
-prod_requires_admin forced delivery requires admin user
-queue_list_requires_admin queue listing requires admin user
-trusted_groups groups that are trusted
-trusted_users users that are trusted
+admin_groups groups that are Exim admin users
+commandline_checks_require_admin require admin for various checks
+deliver_drop_privilege drop root for delivery processes
+local_from_check insert Sender: if necessary
+local_from_prefix for testing From: for local sender
+local_from_suffix for testing From: for local sender
+local_sender_retain keep Sender: from untrusted user
+never_users do not run deliveries as these
+prod_requires_admin forced delivery requires admin user
+queue_list_requires_admin queue listing requires admin user
+trusted_groups groups that are trusted
+trusted_users users that are trusted
14.4 Logging
acl_smtp_vrfy ACL for VRFY
av_scanner specify virus scanner
check_rfc2047_length check length of RFC 2047 "encoded words"
+dns_cname_loops follow CNAMEs returned by resolver
dns_csa_search_limit control CSA parent search depth
dns_csa_use_reverse en/disable CSA IP reverse search
header_maxsize total size of message header
defaults to true. A more detailed analysis of the issues is provided by Dan
Bernstein:
-http://cr.yp.to/smtp/8bitmime.html
+https://cr.yp.to/smtp/8bitmime.html
To log received 8BITMIME status use
This option defines the ACL that is run for each DKIM signature (by default, or
as specified in the dkim_verify_signers option) of a received message. See
-chapter 57 for further details.
+section 57.3 for further details.
+----------------------------------------------------+
|acl_smtp_etrn|Use: main|Type: string*|Default: unset|
The CHUNKING extension (RFC3030) will be advertised in the EHLO message to
these hosts. Hosts may use the BDAT command as an alternate to DATA.
++-------------------------------------------------------------------------+
+|commandline_checks_require_admin|Use: main|Type: boolean|Default: "false"|
++-------------------------------------------------------------------------+
+
+This option restricts various basic checking features to require an
+administrative user. This affects most of the -b* options, such as -be.
+
+----------------------------------------------------+
|debug_store|Use: main|Type: boolean|Default: "false"|
+----------------------------------------------------+
intervals specified by this option. The data is a colon-separated list of times
after which to send warning messages. If the value of the option is an empty
string or a zero time, no warnings are sent. Up to 10 times may be given. If a
-message has been on the queue for longer than the last time, the last interval
+message has been in the queue for longer than the last time, the last interval
between the times is used to compute subsequent warning times. For example,
with
This option gives a list of DKIM domains for which the DKIM ACL is run. It is
expanded after the message is received; by default it runs the ACL once for
-each signature in the message. See chapter 57.
+each signature in the message. See section 57.3.
+--------------------------------------------------------------------+
|dns_again_means_nonexist|Use: main|Type: domain list*|Default: unset|
reversed and looked up in the reverse DNS, as described in more detail in
section 43.50.
++--------------------------------------------------+
+|dns_cname_loops|Use: main|Type: integer|Default: 1|
++--------------------------------------------------+
+
+This option controls the following of CNAME chains, needed if the resolver does
+not do it internally. As of 2018 most should, and the default can be left. If
+you have an ancient one, a value of 10 is likely needed.
+
+The default value of one CNAME-follow is needed thanks to the observed return
+for an MX request, given no MX presence but a CNAME to an A, of the CNAME.
+
+-------------------------------------------------+
|dns_dnssec_ok|Use: main|Type: integer|Default: -1|
+-------------------------------------------------+
server. This reduces security slightly, but improves interworking with older
implementations of TLS.
-option gnutls_allow_auto_pkcs11 main boolean unset This option will let GnuTLS
-(2.12.0 or later) autoload PKCS11 modules with the p11-kit configuration files
-in /etc/pkcs11/modules/.
++---------------------------------------------------------------+
+|gnutls_allow_auto_pkcs11|Use: main|Type: boolean|Default: unset|
++---------------------------------------------------------------+
-See http://www.gnutls.org/manual/gnutls.html#Smart-cards-and-HSMs for
+This option will let GnuTLS (2.12.0 or later) autoload PKCS11 modules with the
+p11-kit configuration files in /etc/pkcs11/modules/.
+
+See https://www.gnutls.org/manual/gnutls.html#Smart-cards-and-HSMs for
documentation.
+---------------------------------------------------------+
|hold_domains|Use: main|Type: domain list*|Default: unset|
+--------------------------------------------------------+
-This option allows mail for particular domains to be held on the queue
+This option allows mail for particular domains to be held in the queue
manually. The option is overridden if a message delivery is forced with the -M,
-qf, -Rf or -Sf options, and also while testing or verifying addresses using
-bt or -bv. Otherwise, if a domain matches an item in hold_domains, no routing
After a permanent delivery failure, bounce messages are frozen, because there
is no sender to whom they can be returned. When a frozen bounce message has
-been on the queue for more than the given time, it is unfrozen at the next
+been in the queue for more than the given time, it is unfrozen at the next
queue run, and a further delivery is attempted. If delivery fails again, the
bounce message is discarded. This makes it possible to keep failed bounce
messages around for a shorter time than the normal maximum retry time for
on a regular LDAP port. This is the LDAP equivalent of SMTP's "STARTTLS". This
is distinct from using "ldaps", which is the LDAP form of SSL-on-connect. In
the event of failure to negotiate TLS, the action taken is controlled by
-ldap_require_cert.
-
-This option is ignored for "ldapi" connections.
+ldap_require_cert. This option is ignored for "ldapi" connections.
+---------------------------------------------------+
|ldap_version|Use: main|Type: integer|Default: unset|
This option sets the path which is used to determine the names of Exim's log
files, or indicates that logging is to be to syslog, or both. It is expanded
when Exim is entered, so it can, for example, contain a reference to the host
-name. If no specific path is set for the log files at compile or run time, or
-if the option is unset at run time (i.e. "log_file_path = ") they are written
-in a sub-directory called log in Exim's spool directory. Chapter 52 contains
-further details about Exim's logging, and section 52.1 describes how the
-contents of log_file_path are used. If this string is fixed at your
-installation (contains no expansion variables) it is recommended that you do
-not set this option in the configuration file, but instead supply the path
-using LOG_FILE_PATH in Local/Makefile so that it is available to Exim for
-logging errors detected early on - in particular, failure to read the
-configuration file.
+name. If no specific path is set for the log files at compile or runtime, or if
+the option is unset at runtime (i.e. "log_file_path = ") they are written in a
+sub-directory called log in Exim's spool directory. Chapter 52 contains further
+details about Exim's logging, and section 52.1 describes how the contents of
+log_file_path are used. If this string is fixed at your installation (contains
+no expansion variables) it is recommended that you do not set this option in
+the configuration file, but instead supply the path using LOG_FILE_PATH in
+Local/Makefile so that it is available to Exim for logging errors detected
+early on - in particular, failure to read the configuration file.
+--------------------------------------------------+
|log_selector|Use: main|Type: string|Default: unset|
driver.
+-----------------------------------------------------------------------------+
-|openssl_options|Use: main|Type: string list|Default: +no_sslv2 +single_dh_use|
+|openssl_options| Use: | Type: string | Default: +no_sslv2 +single_dh_use|
+| | main | list | +no_ticket|
+-----------------------------------------------------------------------------+
This option allows an administrator to adjust the SSL options applied by
+---------------------------------------------------------+
The -M, -R, and -q command-line options require the caller to be an admin user
-unless prod_requires_admin is set false. See also queue_list_requires_admin.
+unless prod_requires_admin is set false. See also queue_list_requires_admin and
+commandline_checks_require_admin.
+--------------------------------------------------------+
|qualify_domain|Use: main|Type: string|Default: see below|
The -bp command-line option, which lists the messages that are on the queue,
requires the caller to be an admin user unless queue_list_requires_admin is set
-false. See also prod_requires_admin.
+false. See also prod_requires_admin and commandline_checks_require_admin.
+-------------------------------------------------+
|queue_only|Use: main|Type: boolean|Default: false|
+-------------------------------------------------+
If queue_only is set, a delivery process is not automatically started whenever
-a message is received. Instead, the message waits on the queue for the next
+a message is received. Instead, the message waits in the queue for the next
queue run. Even if queue_only is false, incoming messages may not get delivered
immediately when certain conditions (such as heavy load) occur.
When this option is set, a delivery process is started whenever a message is
received, routing is performed, and local deliveries take place. However, if
any SMTP deliveries are required for domains that match queue_smtp_domains,
-they are not immediately delivered, but instead the message waits on the queue
+they are not immediately delivered, but instead the message waits in the queue
for the next queue run. Since routing of the message has taken place, Exim
knows to which remote hosts it must be delivered, and so when the queue run
happens, multiple messages for the same host are delivered over a single SMTP
This option sets the timeout for accepting a non-SMTP message, that is, the
maximum time that Exim waits when reading a message on the standard input. If
-the value is zero, it will wait for ever. This setting is overridden by the -or
+the value is zero, it will wait forever. This setting is overridden by the -or
command line option. The timeout for incoming SMTP messages is controlled by
smtp_receive_timeout.
If the number of simultaneous incoming SMTP connections being handled via the
listening daemon exceeds this value, messages received by SMTP are just placed
-on the queue; no delivery processes are started automatically. The count is
+in the queue; no delivery processes are started automatically. The count is
fixed at the start of an SMTP connection. It cannot be updated in the
subprocess that receives messages, and so the queueing or not queueing applies
to all messages received in the same connection.
automatically when receiving messages via SMTP, whether via the daemon or by
the use of -bs or -bS. If the value of the option is greater than zero, and the
number of messages received in a single SMTP session exceeds this number,
-subsequent messages are placed on the queue, but no delivery processes are
+subsequent messages are placed in the queue, but no delivery processes are
started. This helps to limit the number of Exim processes when a server
restarts after downtime and there is a lot of mail waiting for it on other
systems. On large systems, the default should probably be increased, and on
hosts that match this option. See chapter 59 for details of Exim's support for
internationalisation.
-+-------------------------------------------------------+
-|spamd_address|Use: main|Type: string|Default: see below|
-+-------------------------------------------------------+
++-----------------------------------------------------------+
+|spamd_address|Use: main|Type: string|Default: 127.0.0.1 783|
++-----------------------------------------------------------+
This option is available when Exim is compiled with the content-scanning
-extension. It specifies how Exim connects to SpamAssassin's spamd daemon. The
-default value is
+extension. It specifies how Exim connects to SpamAssassin's spamd daemon. See
+section 44.2 for more details.
-127.0.0.1 783
++--------------------------------------------------------------------+
+|spf_guess|Use: main|Type: string|Default: v=spf1 a/24 mx/24 ptr ?all|
++--------------------------------------------------------------------+
-See section 44.2 for more details.
+This option is available when Exim is compiled with SPF support. See section
+57.4 for more details.
+------------------------------------------------------------+
|split_spool_directory|Use: main|Type: boolean|Default: false|
When split_spool_directory is set, the behaviour of queue runner processes
changes. Instead of creating a list of all messages in the queue, and then
-trying to deliver each one in turn, it constructs a list of those in one
+trying to deliver each one, in turn, it constructs a list of those in one
sub-directory and tries to deliver them, before moving on to the next
sub-directory. The sub-directories are processed in a random order. This
spreads out the scanning of the input directories, and uses less memory. It is
-particularly beneficial when there are lots of messages on the queue. However,
+particularly beneficial when there are lots of messages in the queue. However,
if queue_run_in_order is set, none of this new processing happens. The entire
queue has to be scanned and sorted before any deliveries can start.
By using this option to override the compiled-in path, it is possible to run
tests of Exim without using the standard spool.
++-------------------------------------------------------+
+|spool_wireformat|Use: main|Type: boolean|Default: false|
++-------------------------------------------------------+
+
+If this option is set, Exim may for some messages use an alternative format for
+data-files in the spool which matches the wire format. Doing this permits more
+efficient message reception and transmission. Currently it is only done for
+messages received using the ESMTP CHUNKING option.
+
+The following variables will not have useful values:
+
+$max_received_linelength
+$body_linecount
+$body_zerocount
+
+Users of the local_scan() API (see 45), and any external programs which are
+passed a reference to a message data file (except via the "regex", "malware" or
+"spam") ACL conditions) will need to be aware of the different formats
+potentially available.
+
+Using any of the ACL conditions noted will negate the reception benefit (as a
+Unix-mbox-format file is constructed for them). The transmission benefit is
+maintained.
+
+----------------------------------------------------+
|sqlite_lock_timeout|Use: main|Type: time|Default: 5s|
+----------------------------------------------------+
must be Exim filters; they cannot be Sieve filters. If the system filter
generates any deliveries to files or pipes, or any new mail messages, the
appropriate system_filter_..._transport option(s) must be set, to define which
-transports are to be used. Details of this facility are given in chapter 46.
-
-A forced expansion failure results in no filter operation.
+transports are to be used. Details of this facility are given in chapter 46. A
+forced expansion failure results in no filter operation.
+------------------------------------------------------------------------+
|system_filter_directory_transport|Use: main|Type: string*|Default: unset|
+-----------------------------------------------------+
If timeout_frozen_after is set to a time greater than zero, a frozen message of
-any kind that has been on the queue for longer than the given time is
+any kind that has been in the queue for longer than the given time is
automatically cancelled at the next queue run. If the frozen message is a
bounce message, it is just discarded; otherwise, a bounce is sent to the
sender, in a similar manner to cancellation by the -Mg command line option. If
message, see ignore_bounce_errors_after.
Note: the default value of zero means no timeouts; with this setting, frozen
-messages remain on the queue forever (except for any frozen bounce messages
+messages remain in the queue forever (except for any frozen bounce messages
that are released by ignore_bounce_errors_after).
+----------------------------------------------+
for incoming connections is not required the tls_advertise_hosts option should
be set empty.
-+------------------------------------------------------+
-|tls_certificate|Use: main|Type: string*|Default: unset|
-+------------------------------------------------------+
++-----------------------------------------------------+
+|tls_certificate|Use: main|Type: string|Default: list*|
++-----------------------------------------------------+
-The value of this option is expanded, and must then be the absolute path to a
-file which contains the server's certificates. The server's private key is also
-assumed to be in this file if tls_privatekey is unset. See chapter 42 for
-further details.
+The value of this option is expanded, and must then be a list of absolute paths
+to files which contains the server's certificates. Commonly only one file is
+needed. The server's private key is also assumed to be in this file if
+tls_privatekey is unset. See chapter 42 for further details.
Note: The certificates defined by this option are used only when Exim is
receiving incoming messages as a server. If you want to supply certificates for
use when sending messages as a client, you must set the tls_certificate option
in the relevant smtp transport.
+Note: If you use filenames based on IP addresses, change the list separator in
+the usual way (6.21) >to avoid confusion under IPv6.
+
+Note: Under versions of OpenSSL preceding 1.1.1, when a list of more than one
+file is used, the $tls_in_ourcert variable is unreliable.
+
+Note: OCSP stapling is not usable under OpenSSL when a list of more than one
+file is used.
+
If the option contains $tls_out_sni and Exim is built against OpenSSL, then if
the OpenSSL build supports TLS extensions and the TLS client sends the Server
Name Indication extension, then this option and others documented in 42.10 will
+----------------------------------------------+
This option specifies a certificate revocation list. The expanded value must be
-the name of a file that contains a CRL in PEM format.
+the name of a file that contains CRLs in PEM format.
+
+Under OpenSSL the option can specify a directory with CRL files.
+
+Note: Under OpenSSL the option must, if given, supply a CRL for each signing
+element of the certificate chain (i.e. all but the leaf). For the file variant
+this can be multiple PEM blocks in the one file.
See 42.10 for discussion of when this option might be re-expanded.
|tls_eccurve|Use: main|Type: string*|Default: "auto"|
+---------------------------------------------------+
-This option selects a EC curve for use by Exim.
+This option selects a EC curve for use by Exim when used with OpenSSL. It has
+no effect when Exim is used with GnuTLS.
After expansion it must contain a valid EC curve parameter, such as
"prime256v1", "secp384r1", or "P-512". Consult your OpenSSL manual for valid
Usable for GnuTLS 3.4.4 or 3.3.17 or OpenSSL 1.1.0 (or later).
+For GnuTLS 3.5.6 or later the expanded value of this option can be a list of
+files, to match a list given for the tls_certificate option. The ordering of
+the two lists must match.
+
+---------------------------------------------------------------+
|tls_on_connect_ports|Use: main|Type: string list|Default: unset|
+---------------------------------------------------------------+
This option specifies a list of incoming SSMTP (aka SMTPS) ports that should
-operate the obsolete SSMTP (SMTPS) protocol, where a TLS session is immediately
-set up without waiting for the client to issue a STARTTLS command. For further
+operate the SSMTP (SMTPS) protocol, where a TLS session is immediately set up
+without waiting for the client to issue a STARTTLS command. For further
details, see section 13.4.
-+-----------------------------------------------------+
-|tls_privatekey|Use: main|Type: string*|Default: unset|
-+-----------------------------------------------------+
++----------------------------------------------------+
+|tls_privatekey|Use: main|Type: string|Default: list*|
++----------------------------------------------------+
-The value of this option is expanded, and must then be the absolute path to a
-file which contains the server's private key. If this option is unset, or if
-the expansion is forced to fail, or the result is an empty string, the private
-key is assumed to be in the same file as the server's certificates. See chapter
-42 for further details.
+The value of this option is expanded, and must then be a list of absolute paths
+to files which contains the server's private keys. If this option is unset, or
+if the expansion is forced to fail, or the result is an empty string, the
+private key is assumed to be in the same file as the server's certificates. See
+chapter 42 for further details.
See 42.10 for discussion of when this option might be re-expanded.
This option defines a template file containing paragraphs of text to be used
for constructing the warning message which is sent by Exim when a message has
-been on the queue for a specified amount of time, as specified by delay_warning
+been in the queue for a specified amount of time, as specified by delay_warning
. Details of the file's contents are given in chapter 49. See also
bounce_message_file.
String expansion is not applied to this option. The argument must be a
colon-separated list of host names or IP addresses. The list separator can be
-changed (see section 6.20), and a port can be specified with each name or
+changed (see section 6.21), and a port can be specified with each name or
address. In fact, the format of each item is exactly the same as defined for
the list of hosts in a manualroute router (see section 20.5).
+---------------------------------------------------+
This option specifies a list of text headers, newline-separated (by default,
-changeable in the usual way), that is associated with any addresses that are
-accepted by the router. Each item is separately expanded, at routing time.
+changeable in the usual way 6.21), that is associated with any addresses that
+are accepted by the router. Each item is separately expanded, at routing time.
However, this option has no effect when an address is just being verified. The
way in which the text is used to add header lines at transport time is
described in section 47.17. New header lines are not actually added until the
+------------------------------------------------------+
This option specifies a list of text headers, colon-separated (by default,
-changeable in the usual way), that is associated with any addresses that are
-accepted by the router. Each item is separately expanded, at routing time.
+changeable in the usual way 6.21), that is associated with any addresses that
+are accepted by the router. Each item is separately expanded, at routing time.
However, this option has no effect when an address is just being verified. The
way in which the text is used to remove header lines at transport time is
described in section 47.17. Header lines are not actually removed until the
through the require_files list, expanding each item separately.
Because the list is split before expansion, any colons in expansion items must
-be doubled, or the facility for using a different list separator must be used.
-If any expansion is forced to fail, the item is ignored. Other expansion
+be doubled, or the facility for using a different list separator must be used (
+6.21). If any expansion is forced to fail, the item is ignored. Other expansion
failures cause routing of the address to be deferred.
If any expanded string is empty, it is ignored. Otherwise, except as described
in which preconditions are evaluated.) However, as these options are all
expanded, you can use the exists expansion condition to make such tests. The
require_files option is intended for checking files that the router may be
-going to use internally, or which are needed by a transport (for example
-.procmailrc).
+going to use internally, or which are needed by a transport (e.g., .procmailrc
+).
During delivery, the stat() function is run as root, but there is a facility
for some checking of the accessibility of a file by another user. This is not a
caused by a configuration error, and routing is deferred because the existence
or non-existence of the file cannot be determined. However, in some
circumstances it may be desirable to treat this condition as if the file did
-not exist. If the file name (or the exclamation mark that precedes the file
-name for non-existence) is preceded by a plus sign, the EACCES error is treated
-as if the file did not exist. For example:
+not exist. If the filename (or the exclamation mark that precedes the filename
+for non-existence) is preceded by a plus sign, the EACCES error is treated as
+if the file did not exist. For example:
require_files = +/some/file
MX records of equal priority are sorted by Exim into a random order. Exim then
looks for address records for the host names obtained from MX or SRV records.
When a host has more than one IP address, they are sorted into a random order,
-except that IPv6 addresses are always sorted before IPv4 addresses. If all the
-IP addresses found are discarded by a setting of the ignore_target_hosts
-generic option, the router declines.
+except that IPv6 addresses are sorted before IPv4 addresses. If all the IP
+addresses found are discarded by a setting of the ignore_target_hosts generic
+option, the router declines.
Unless they have the highest priority (lowest MX value), MX records that point
to the local host, or to any host name that matches hosts_treat_as_local, are
while the DNS configuration is not ready. However, it will result in any
message with mistyped domains also being queued.
++-------------------------------------------+
+|ipv4_only|Use: string*|Type: unset|Default:|
++-------------------------------------------+
+
+The string is expanded, and if the result is anything but a forced failure, or
+an empty string, or one of the strings ?0? or ?no? or ?false? (checked without
+regard to the case of the letters), only A records are used.
+
++---------------------------------------------+
+|ipv4_prefer|Use: string*|Type: unset|Default:|
++---------------------------------------------+
+
+The string is expanded, and if the result is anything but a forced failure, or
+an empty string, or one of the strings ?0? or ?no? or ?false? (checked without
+regard to the case of the letters), A records are sorted before AAAA records
+(inverting the default).
+
+-----------------------------------------------------------+
|mx_domains|Use: dnslookup|Type: domain list*|Default: unset|
+-----------------------------------------------------------+
A list of hosts, whether obtained via route_data or route_list, is always
separately expanded before use. If the expansion fails, the router declines.
The result of the expansion must be a colon-separated list of names and/or IP
-addresses, optionally also including ports. The format of each item in the list
-is described in the next section. The list separator can be changed as
-described in section 6.20.
+addresses, optionally also including ports. If the list is written with spaces,
+it must be protected with quotes. The format of each item in the list is
+described in the next section. The list separator can be changed as described
+in section 6.21.
If the list of hosts was obtained from a route_list item, the following
variables are set during its expansion:
20.7 How the options are used
-----------------------------
-The options are a sequence of words; in practice no more than three are ever
-present. One of the words can be the name of a transport; this overrides the
-transport option on the router for this particular routing rule only. The other
-words (if present) control randomization of the list of hosts on a per-rule
-basis, and how the IP addresses of the hosts are to be found when routing to a
-remote transport. These options are as follows:
+The options are a sequence of words, space-separated. One of the words can be
+the name of a transport; this overrides the transport option on the router for
+this particular routing rule only. The other words (if present) control
+randomization of the list of hosts on a per-rule basis, and how the IP
+addresses of the hosts are to be found when routing to a remote transport.
+These options are as follows:
* randomize: randomize the order of the hosts in this list, overriding the
setting of hosts_randomize for this routing rule only.
no address records are found. If there is a temporary DNS error (such as a
timeout), delivery is deferred.
+ * ipv4_only: in direct DNS lookups, look up only A records.
+
+ * ipv4_prefer: in direct DNS lookups, sort A records before AAAA records.
+
For example:
route_list = domain1 host1:host2:host3 randomize bydns;\
TRY_AGAIN. That is why the default action is to try a DNS lookup first. Only if
that gives a definite "no such host" is the local function called.
+Compatibility: From Exim 4.85 until fixed for 4.90, there was an inadvertent
+constraint that a transport name as an option had to be the last option
+specified.
+
If no IP address for a host can be found, what happens is controlled by the
host_find_failed option.
* Otherwise, the data must be a comma-separated list of redirection items, as
described in the next section.
-When a message is redirected to a file (a "mail folder"), the file name given
-in a non-filter redirection list must always be an absolute path. A filter may
+When a message is redirected to a file (a "mail folder"), the filename given in
+a non-filter redirection list must always be an absolute path. A filter may
generate a relative path - how this is handled depends on the transport's
configuration. See section 26.1 for a discussion of this issue for the
appendfile transport.
When the redirection data is not an Exim or Sieve filter, for example, if it
comes from a conventional alias or forward file, it consists of a list of
-addresses, file names, pipe commands, or certain special items (see section
-22.6 below). The special items can be individually enabled or disabled by means
-of options whose names begin with allow_ or forbid_, depending on their default
+addresses, filenames, pipe commands, or certain special items (see section 22.6
+below). The special items can be individually enabled or disabled by means of
+options whose names begin with allow_ or forbid_, depending on their default
values. The items in the list are separated by commas or newlines. If a comma
is required in an item, the entire item must be enclosed in double quotes.
/home/world/minbari
- is treated as a file name, but
+ is treated as a filename, but
/s=molari/o=babylon/@x400gate.way
- is treated as an address. For a file name, a transport must be specified
+ is treated as an address. For a filename, a transport must be specified
using the file_transport option. However, if the generated path name ends
with a forward slash character, it is interpreted as a directory name
- rather than a file name, and directory_transport is used instead.
+ rather than a filename, and directory_transport is used instead.
Normally, either the router or the transport specifies a user and a group
under which to run the delivery. The default is to use the Exim user and
During routing for message delivery (as opposed to verification), a
redirection containing :fail: causes an immediate failure of the incoming
- address, whereas :defer: causes the message to remain on the queue so that
+ address, whereas :defer: causes the message to remain in the queue so that
a subsequent delivery attempt can happen at a later time. If an address is
deferred for too long, it will ultimately fail, because the normal retry
rules still apply.
ending in a slash is specified as a new "address". The transport used is
specified by this option, which, after expansion, must be the name of a
configured transport. This should normally be an appendfile transport. When it
-is running, the file name is in $address_file.
+is running, the filename is in $address_file.
+-------------------------------------------------------------+
|filter_prepend_home|Use: redirect|Type: boolean|Default: true|
+------------------------------------------------------+
This option specifies a list of text headers, newline-separated (by default,
-changeable in the usual way), which are (separately) expanded and added to the
-header portion of a message as it is transported, as described in section 47.17
-. Additional header lines can also be specified by routers. If the result of
-the expansion is an empty string, or if the expansion is forced to fail, no
+changeable in the usual way 6.21), which are (separately) expanded and added to
+the header portion of a message as it is transported, as described in section
+47.17. Additional header lines can also be specified by routers. If the result
+of the expansion is an empty string, or if the expansion is forced to fail, no
action is taken. Other expansion failures are treated as errors and cause the
delivery to be deferred.
+---------------------------------------------------------+
This option specifies a list of header names, colon-separated (by default,
-changeable in the usual way); these headers are omitted from the message as it
-is transported, as described in section 47.17. Header removal can also be
+changeable in the usual way 6.21); these headers are omitted from the message
+as it is transported, as described in section 47.17. Header removal can also be
specified by routers. Each list item is separately expanded. If the result of
the expansion is an empty string, or if the expansion is forced to fail, no
action is taken. Other expansion failures are treated as errors and cause the
fileinto "folder23";
In this situation, the expansion of file or directory in the transport must
-transform the relative path into an appropriate absolute file name. In the case
+transform the relative path into an appropriate absolute filename. In the case
of Sieve filters, the name inbox must be handled. It is the name that is used
as a result of a "keep" action in the filter. This example shows one way of
handling this requirement:
The option must be set to one of the words "anywhere", "inhome", or
"belowhome". In the second and third cases, a home directory must have been set
-for the transport. This option is not useful when an explicit file name is
-given for normal mailbox deliveries. It is intended for the case when file
-names are generated from users' .forward files. These are usually handled by an
+for the transport. This option is not useful when an explicit filename is given
+for normal mailbox deliveries. It is intended for the case when filenames are
+generated from users' .forward files. These are usually handled by an
appendfile transport called address_file. See also file_must_exist.
+------------------------------------------------------+
The value of the option is expanded, and must then be a numerical value
(decimal point allowed), optionally followed by one of the letters K, M, or G,
-for kilobytes, megabytes, or gigabytes. If Exim is running on a system with
-large file support (Linux and FreeBSD have this), mailboxes larger than 2G can
-be handled.
+for kilobytes, megabytes, or gigabytes, optionally followed by a slash and
+further option modifiers. If Exim is running on a system with large file
+support (Linux and FreeBSD have this), mailboxes larger than 2G can be handled.
+
+The option modifier no_check can be used to force delivery even if the over
+quota condition is met. The quota gets updated as usual.
Note: A value of zero is interpreted as "no quota".
failure causes delivery to be deferred. A value of zero is interpreted as "no
quota".
+The option modifier no_check can be used to force delivery even if the over
+quota condition is met. The quota gets updated as usual.
+
+--------------------------------------------------------------+
|quota_is_inclusive|Use: appendfile|Type: boolean|Default: true|
+--------------------------------------------------------------+
This option applies when one of the delivery modes that writes a separate file
for each message is being used. When Exim wants to find the size of one of
these files in order to test the quota, it first checks quota_size_regex. If
-this is set to a regular expression that matches the file name, and it captures
+this is set to a regular expression that matches the filename, and it captures
one string, that string is interpreted as a representation of the file's size.
The value of quota_size_regex is not expanded.
This feature is useful only when users have no shell access to their mailboxes
- otherwise they could defeat the quota simply by renaming the files. This
facility can be used with maildir deliveries, by setting maildir_tag to add the
-file length to the file name. For example:
+file length to the filename. For example:
maildir_tag = ,S=$message_size
quota_size_regex = ,S=(\d+)
number of lines in the message.
The regular expression should not assume that the length is at the end of the
-file name (even though maildir_tag puts it there) because maildir MUAs
-sometimes add other information onto the ends of message file names.
+filename (even though maildir_tag puts it there) because maildir MUAs sometimes
+add other information onto the ends of message filenames.
Section 26.7 contains further information.
for writing as a new file. If this fails with an access error, delivery
is deferred.
- 2. Close the hitching post file, and hard link it to the lock file name.
+ 2. Close the hitching post file, and hard link it to the lock filename.
3. If the call to link() succeeds, creation of the lock file has
succeeded. Unlink the hitching post name.
is defined by the directory option (the "delivery directory"). If the delivery
is successful, the file is renamed into the new subdirectory.
-In the file name, <stime> is the current time of day in seconds, and <mtime> is
+In the filename, <stime> is the current time of day in seconds, and <mtime> is
the microsecond fraction of the time. After a maildir delivery, Exim checks
that the time-of-day clock has moved on by at least one microsecond before
-terminating the delivery process. This guarantees uniqueness for the file name.
+terminating the delivery process. This guarantees uniqueness for the filename.
However, as a precaution, Exim calls stat() for the file before opening it. If
any response other than ENOENT (does not exist) is given, Exim waits 2 seconds
and tries again, up to maildir_retries times.
does not apply to Cc: or Bcc: recipients.
If once is unset, or is set to an empty string, the message is always sent. By
-default, if once is set to a non-empty file name, the message is not sent if a
+default, if once is set to a non-empty filename, the message is not sent if a
potential recipient is already listed in the database. However, if the
once_repeat option specifies a time greater than zero, the message is sent if
that much time has elapsed since a message was last sent to this recipient. A
command = /bin/sh -c ${lookup{$local_part}lsearch{/some/file}}
Special handling takes place when an argument consists of precisely the text
-"$pipe_addresses". This is not a general expansion variable; the only place
-this string is recognized is when it appears as an argument for a pipe or
-transport filter command. It causes each address that is being handled to be
-inserted in the argument list at that point as a separate argument. This avoids
-any problems with spaces or shell metacharacters, and is of use when a pipe
-transport is handling groups of addresses in a batch.
+"$pipe_addresses" (no quotes). This is not a general expansion variable; the
+only place this string is recognized is when it appears as an argument for a
+pipe or transport filter command. It causes each address that is being handled
+to be inserted in the argument list at that point as a separate argument. This
+avoids any problems with spaces or shell metacharacters, and is of use when a
+pipe transport is handling groups of addresses in a batch.
If force_command is enabled on the transport, Special handling takes place for
an argument that consists of precisely the text "$address_pipe". It is handled
|path|Use: pipe|Type: string*|Default: /bin:/usr/bin|
+---------------------------------------------------+
-This option is expanded and
-
-specifies the string that is set up in the PATH environment variable of the
-subprocess. If the command option does not yield an absolute path name, the
-command is sought in the PATH directories, in the usual way. Warning: This does
-not apply to a command specified as a transport filter.
+This option is expanded and specifies the string that is set up in the PATH
+environment variable of the subprocess. If the command option does not yield an
+absolute path name, the command is sought in the PATH directories, in the usual
+way. Warning: This does not apply to a command specified as a transport filter.
+------------------------------------------------------+
|permit_coredump|Use: pipe|Type: boolean|Default: false|
over a single TCP/IP connection. If the value is zero, there is no limit. For
testing purposes, this value can be overridden by the -oB command line option.
++---------------------------------------------------------------+
+|dane_require_tls_ciphers|Use: smtp|Type: string*|Default: unset|
++---------------------------------------------------------------+
+
+This option may be used to override tls_require_ciphers for connections where
+DANE has been determined to be in effect. If not set, then tls_require_ciphers
+will be used. Normal SMTP delivery is not able to make strong demands of TLS
+cipher configuration, because delivery will fall back to plaintext. Once DANE
+has been determined to be in effect, there is no plaintext fallback and making
+the TLS cipherlist configuration stronger will increase security, rather than
+counter-intuitively decreasing it. If the option expands to be empty or is
+forced to fail, then it will be treated as unset and tls_require_ciphers will
+be used instead.
+
+---------------------------------------------+
|data_timeout|Use: smtp|Type: time|Default: 5m|
+---------------------------------------------+
the message. As a result, the overall timeout for a message depends on the size
of the message. Its value must not be zero. See also final_timeout.
-+--------------------------------------------------+
-|dkim_domain|Use: smtp|Type: string*|Default: unset|
-+--------------------------------------------------+
++-------------------------------------------------+
+|dkim_canon|Use: smtp|Type: string*|Default: unset|
++-------------------------------------------------+
+
++-------------------------------------------------+
+|dkim_domain|Use: smtp|Type: string|Default: list*|
++-------------------------------------------------+
+
++-------------------------------------------------+
+|dkim_hash|Use: smtp|Type: string*|Default: sha256|
++-------------------------------------------------+
+----------------------------------------------------+
-|dkim_selector|Use: smtp|Type: string*|Default: unset|
+|dkim_identity|Use: smtp|Type: string*|Default: unset|
+----------------------------------------------------+
+-------------------------------------------------------+
|dkim_private_key|Use: smtp|Type: string*|Default: unset|
+-------------------------------------------------------+
-+-------------------------------------------------+
-|dkim_canon|Use: smtp|Type: string*|Default: unset|
-+-------------------------------------------------+
++----------------------------------------------------+
+|dkim_selector|Use: smtp|Type: string*|Default: unset|
++----------------------------------------------------+
+--------------------------------------------------+
|dkim_strict|Use: smtp|Type: string*|Default: unset|
+--------------------------------------------------+
-+--------------------------------------------------------+
-|dkim_sign_headers|Use: smtp|Type: string*|Default: unset|
-+--------------------------------------------------------+
++----------------------------------------------------------+
+|dkim_sign_headers|Use: smtp|Type: string*|Default: per RFC|
++----------------------------------------------------------+
+
++------------------------------------------------------+
+|dkim_timestamps|Use: smtp|Type: string*|Default: unset|
++------------------------------------------------------+
-DKIM signing options. For details see section 57.1.
+DKIM signing options. For details see section 57.2.
+--------------------------------------------------------+
|delay_after_cutoff|Use: smtp|Type: boolean|Default: true|
message on the same connection. See section 42.11 for an explanation of when
this might be needed.
++-------------------------------------------------------+
+|hosts_noproxy_tls|Use: smtp|Type: host list*|Default: *|
++-------------------------------------------------------+
+
+For any host that matches this list, a TLS session which has been started will
+not be passed to a new delivery process for sending another message on the same
+session.
+
+The traditional implementation closes down TLS and re-starts it in the new
+process, on the same open TCP connection, for each successive message sent. If
+permitted by this option a pipe to to the new process is set up instead, and
+the original process maintains the TLS connection and proxies the SMTP
+connection from and to the new process and any subsequents. The new process has
+no access to TLS information, so cannot include it in logging.
+
+-----------------------------------------------------+
|hosts_override|Use: smtp|Type: boolean|Default: false|
+-----------------------------------------------------+
matches this list. tls_verify_certificates should also be set for the
transport.
++------------------------------------------------------------+
+|hosts_require_dane|Use: smtp|Type: host list*|Default: unset|
++------------------------------------------------------------+
+
+If built with DANE support, Exim will require that a DNSSEC-validated TLSA
+record is present for any host matching the list, and that a DANE-verified TLS
+connection is made. There will be no fallback to in-clear communication. See
+section 42.15.
+
+------------------------------------------------------------+
|hosts_require_ocsp|Use: smtp|Type: host list*|Default: unset|
+------------------------------------------------------------+
CHUNKING support, Exim will attempt to use BDAT commands rather than DATA. BDAT
will not be used in conjunction with a transport filter.
-+-------------------------------------------------------------+
-|hosts_try_fastopen|Use: smtp|Type: host list!!|Default: unset|
-+-------------------------------------------------------------+
++--------------------------------------------------------+
+|hosts_try_dane|Use: smtp|Type: host list*|Default: unset|
++--------------------------------------------------------+
+
+If built with DANE support, Exim will lookup a TLSA record for any host
+matching the list. If found and verified by DNSSEC, a DANE-verified TLS
+connection is made to that host; there will be no fallback to in-clear
+communication. See section 42.15.
+
++------------------------------------------------------------+
+|hosts_try_fastopen|Use: smtp|Type: host list*|Default: unset|
++------------------------------------------------------------+
This option provides a list of servers to which, provided the facility is
supported by this system, Exim will attempt to perform a TCP Fast Open. No data
must present a cookie in the SYN segment.
On (at least some) current Linux distributions the facility must be enabled in
-the kernel by the sysadmin before the support is usable.
+the kernel by the sysadmin before the support is usable. There is no option for
+control of the server side; if the system supports it it is always enabled.
+Note that lengthy operations in the connect ACL, such as DNSBL lookups, will
+still delay the emission of the SMTP banner.
+----------------------------------------------------+
|hosts_try_prdr|Use: smtp|Type: host list*|Default: *|
during the expansion of the string. Forced expansion failure, or an empty
string result causes the option to be ignored. Otherwise, after expansion, the
string must be a list of IP addresses, colon-separated by default, but the
-separator can be changed in the usual way. For example:
+separator can be changed in the usual way (6.21). For example:
interface = <; 192.168.123.123 ; 3ffe:ffff:836f::fe86:a061
If the value of this option begins with a digit it is taken as a port number;
otherwise it is looked up using getservbyname(). The default value is normally
-"smtp", but if protocol is set to "lmtp", the default is "lmtp". If the
-expansion fails, or if a port number cannot be found, delivery is deferred.
+"smtp", but if protocol is set to "lmtp" the default is "lmtp" and if protocol
+is set to "smtps" the default is "smtps". If the expansion fails, or if a port
+number cannot be found, delivery is deferred.
+
+Note that at least one Linux distribution has been seen failing to put "smtps"
+in its "/etc/services" file, resulting is such deferrals.
+---------------------------------------------+
|protocol|Use: smtp|Type: string|Default: smtp|
If this option is set to "smtps", the default value for the port option changes
to "smtps", and the transport initiates TLS immediately after connecting, as an
-outbound SSL-on-connect, instead of using STARTTLS to upgrade. The Internet
-standards bodies strongly discourage use of this mode.
+outbound SSL-on-connect, instead of using STARTTLS to upgrade.
+
+The Internet standards bodies used to strongly discourage use of this mode, but
+as of RFC 8314 it is perferred over STARTTLS for message submission (as
+distinct from MTA-MTA communication).
+---------------------------------------------------------------+
|retry_include_ip_address|Use: smtp|Type: boolean*|Default: true|
also be set. If both this option and tls_try_verify_hosts are unset operation
is as if this option selected all hosts.
++---------------------------------------------------------+
+|utf8_downconvert|Use: smtp|Type: integer!!|Default: unset|
++---------------------------------------------------------+
+
+If built with internationalization support, this option controls conversion of
+UTF-8 in message addresses to a-label form. For details see section 59.1.
+
30.5 How the limits for the number of hosts to try are used
-----------------------------------------------------------
31.3 Testing the rewriting rules that apply on input
----------------------------------------------------
-Exim's input rewriting configuration appears in a part of the run time
+Exim's input rewriting configuration appears in a part of the runtime
configuration file headed by "begin rewrite". It can be tested by the -brw
command line option. This takes an address (which can be a full RFC 2822
address) as its argument. The output is a list of how the address would be
delivered at the first attempt. If there are no retry rules (the section is
empty or not present), there are no retries. In this situation, temporary
errors are treated as permanent. The default configuration contains a single,
-general-purpose retry rule (see section 7.5). The -brt command line option can
+general-purpose retry rule (see section 7.6). The -brt command line option can
be used to test which retry rule will be used for a given address, domain and
error.
If the delivery is remote, there are two possibilities, controlled by the
delay_after_cutoff option of the smtp transport. The option is true by default.
-Until the post-cutoff retry time for one of the IP addresses is reached, the
-failing email address is bounced immediately, without a delivery attempt taking
-place. After that time, one new delivery attempt is made to those IP addresses
-that are past their retry times, and if that still fails, the address is
-bounced and new retry times are computed.
+Until the post-cutoff retry time for one of the IP addresses, as set by the
+retry_data_expire option, is reached, the failing email address is bounced
+immediately, without a delivery attempt taking place. After that time, one new
+delivery attempt is made to those IP addresses that are past their retry times,
+and if that still fails, the address is bounced and new retry times are
+computed.
In other words, when all the hosts for a given email address have been failing
for a long time, Exim bounces rather then defers until one of the hosts' retry
its delivery when others to the same address get through. In this situation,
because some messages are successfully delivered, the "retry clock" for the
host or address keeps getting reset by the successful deliveries, and so
-failing messages remain on the queue for ever because the cutoff time is never
+failing messages remain in the queue for ever because the cutoff time is never
reached.
Two exceptional actions are applied to prevent this happening. The first
===============================================================================
33. SMTP AUTHENTICATION
-The "authenticators" section of Exim's run time configuration is concerned with
+The "authenticators" section of Exim's runtime configuration is concerned with
SMTP authentication. This facility is an extension to the SMTP protocol,
described in RFC 2554, which allows a client SMTP host to authenticate itself
to a server. This is a common way for a server to recognize clients that are
messages in the variable $authenticated_id. It is also included in the log
lines for incoming messages. For example, a user/password authenticator
configuration might preserve the user name that was used to authenticate, and
-refer to it subsequently during delivery of the message. If expansion fails,
-the option is ignored.
+refer to it subsequently during delivery of the message. On a failing
+authentication the expansion result is instead saved in the
+$authenticated_fail_id variable. If expansion fails, the option is ignored.
+---------------------------------------------------------------------------+
|server_mail_auth_condition|Use: authenticators|Type: string*|Default: unset|
from which the message was received. This variable is empty if there was no
successful authentication.
+Successful authentication sets up information used by the $authresults
+expansion item.
+
33.4 Testing server authentication
----------------------------------
second case, Exim tries to deliver the message unauthenticated.
Note that the hostlist test for whether to do authentication can be confused if
-name-IP lookups change between the time the peer is decided on and the
-transport running. For example, with a manualroute router given a host name,
-and DNS "round-robin" use by that name: if the local resolver cache times out
-between the router and the transport running, the transport may get an IP for
-the name for its authentication check which does not match the connection peer
-IP. No authentication will then be done, despite the names being identical.
+name-IP lookups change between the time the peer is decided upon and the time
+that the transport runs. For example, with a manualroute router given a host
+name, and with DNS "round-robin" used by that name: if the local resolver cache
+times out between the router and the transport running, the transport may get
+an IP for the name for its authentication check which does not match the
+connection peer IP. No authentication will then be done, despite the names
+being identical.
For such cases use a separate transport which always authenticates.
authentication fails. If the result of the expansion is "1", "yes", or "true",
authentication succeeds and the generic server_set_id option is expanded and
saved in $authenticated_id. For any other result, a temporary error code is
-returned, with the expanded string as the error text
+returned, with the expanded string as the error text.
Warning: If you use a lookup in the expansion to find the user's password, be
sure to make the authentication fail if the user is unknown. There are good and
===============================================================================
36. THE CYRUS_SASL AUTHENTICATOR
-The code for this authenticator was provided by Matthew Byng-Maddick of A L
-Digital Ltd (http://www.aldigital.co.uk).
+The code for this authenticator was provided by Matthew Byng-Maddick while at A
+L Digital Ltd.
The cyrus_sasl authenticator provides server support for the Cyrus SASL library
implementation of the RFC 2222 ("Simple Authentication and Security Layer").
so can the cyrus_sasl authenticator. By default it uses the public name of the
driver to determine which mechanism to support.
-Where access to some kind of secret file is required, for example in GSSAPI or
+Where access to some kind of secret file is required, for example, in GSSAPI or
CRAM-MD5, it is worth noting that the authenticator runs as the Exim user, and
that the Cyrus SASL library has no way of escalating privileges by default. You
may also find you need to set environment variables, depending on the driver
|server_socket|Use: dovecot|Type: string|Default: unset|
+------------------------------------------------------+
-This option must specify the socket that is the interface to Dovecot
+This option must specify the UNIX socket that is the interface to Dovecot
authentication. The public_name option must specify an authentication mechanism
that Dovecot is configured to support. You can have several authenticators for
different mechanisms. For example:
particular new authentication mechanism will be supported without code changes
in Exim.
+Exim's gsasl authenticator does not have client-side support at this time; only
+the server-side support is implemented. Patches welcome.
+
+-------------------------------------------------------------+
|server_channelbinding|Use: gsasl|Type: boolean|Default: false|
+-------------------------------------------------------------+
+Do not set this true without consulting a cryptographic engineer.
+
Some authentication mechanisms are able to use external context at both ends of
the session to bind the authentication to that context, and fail the
authentication process if that context differs. Specifically, some TLS
ciphersuites can provide identifying information about the cryptographic
context.
-This means that certificate identity and verification becomes a non-issue, as a
-man-in-the-middle attack will cause the correct client and server to see
-different identifiers and authentication will fail.
+This should have meant that certificate identity and verification becomes a
+non-issue, as a man-in-the-middle attack will cause the correct client and
+server to see different identifiers and authentication will fail.
This is currently only supported when using the GnuTLS library. This is only
usable by mechanisms which support "channel binding"; at time of writing,
that's the SCRAM family.
This defaults off to ensure smooth upgrade across Exim releases, in case this
-option causes some clients to start failing. Some future release of Exim may
-switch the default to be true.
+option causes some clients to start failing. Some future release of Exim might
+have switched the default to be true.
+
+However, Channel Binding in TLS has proven to be broken in current versions. Do
+not plan to rely upon this feature for security, ever, without consulting with
+a subject matter expert (a cryptographic engineer).
+-----------------------------------------------------------+
|server_hostname|Use: gsasl|Type: string*|Default: see below|
The spa authenticator provides client support for Microsoft's Secure Password
Authentication mechanism, which is also sometimes known as NTLM (NT LanMan).
The code for client side of this authenticator was contributed by Marc
-Prud'hommeaux, and much of it is taken from the Samba project (http://
-www.samba.org). The code for the server side was subsequently contributed by
+Prud'hommeaux, and much of it is taken from the Samba project (https://
+www.samba.org/). The code for the server side was subsequently contributed by
Tom Kistner. The mechanism works as follows:
* After the AUTH command has been accepted, the client sends an SPA
driver = tls
server_param1 = ${certextract {subj_altname,mail,>:} \
{$tls_in_peercert}}
- server_condition = ${if forany {$auth1} \
+ server_condition = ${if and { {eq{$tls_in_certificate_verified}{1}} \
+ {forany {$auth1} \
{!= {0} \
{${lookup ldap{ldap:///\
mailname=${quote_ldap_dn:${lc:$item}},\
ou=users,LDAP_DC?mailid} {$value}{0} \
- } } } }
+ } } } }}}
server_set_id = ${if = {1}{${listcount:$auth1}} {$auth1}{}}
This accepts a client certificate that is verifiable against any of your
configured trust-anchors (which usually means the full set of public CAs) and
-which has a SAN with a good account name. Note that the client cert is on the
-wire in-clear, including the SAN, whereas a plaintext SMTP AUTH done inside TLS
-is not.
+which has a SAN with a good account name.
+
+Note that, up to TLS1.2, the client cert is on the wire in-clear, including the
+SAN, The account name is therefore guessable by an opponent. TLS 1.3 protects
+both server and client certificates, and is not vulnerable in this way.
+Likewise, a traditional plaintext SMTP AUTH done inside TLS is not.
Note that because authentication is traditionally an SMTP operation, the
authenticated ACL condition cannot be used in a connect- or helo-ACL.
to get TLS to work.
-42.1 Support for the legacy "ssmtp" (aka "smtps") protocol
-----------------------------------------------------------
+42.1 Support for the "submissions" (aka "ssmtp" and "smtps") protocol
+---------------------------------------------------------------------
+
+The history of port numbers for TLS in SMTP is a little messy and has been
+contentious. As of RFC 8314, the common practice of using the historically
+allocated port 465 for "email submission but with TLS immediately upon connect
+instead of using STARTTLS" is officially blessed by the IETF, and recommended
+by them in preference to STARTTLS.
-Early implementations of encrypted SMTP used a different TCP port from normal
-SMTP, and expected an encryption negotiation to start immediately, instead of
-waiting for a STARTTLS command from the client using the standard SMTP port.
-The protocol was called "ssmtp" or "smtps", and port 465 was allocated for this
-purpose.
+The name originally assigned to the port was "ssmtp" or "smtps", but as clarity
+emerged over the dual roles of SMTP, for MX delivery and Email Submission,
+nomenclature has shifted. The modern name is now "submissions".
-This approach was abandoned when encrypted SMTP was standardized, but there are
-still some legacy clients that use it. Exim supports these clients by means of
-the tls_on_connect_ports global option. Its value must be a list of port
-numbers; the most common use is expected to be:
+This approach was, for a while, officially abandoned when encrypted SMTP was
+standardized, but many clients kept using it, even as the TCP port number was
+reassigned for other use. Thus you may encounter guidance claiming that you
+shouldn't enable use of this port. In practice, a number of mail-clients have
+only ever supported submissions, not submission with STARTTLS upgrade. Ideally,
+offer both submission (587) and submissions (465) service.
+
+Exim supports TLS-on-connect by means of the tls_on_connect_ports global
+option. Its value must be a list of port numbers; the most common use is
+expected to be:
tls_on_connect_ports = 465
rather, it specifies different behaviour on a port that is defined elsewhere.
There is also a -tls-on-connect command line option. This overrides
-tls_on_connect_ports; it forces the legacy behaviour for all ports.
+tls_on_connect_ports; it forces the TLS-only behaviour for all ports.
42.2 OpenSSL vs GnuTLS
be configured in this way, let the Exim Maintainers know and we'll likely
use it).
+ * With GnuTLS, if an explicit list is used for the tls_privatekey main option
+ main option, it must be ordered to match the tls_certificate list.
+
* Some other recently added features may only be available in one or the
other. This should be documented with the feature. If the documentation
does not explicitly state that the feature is infeasible in the other TLS
There is a function in the OpenSSL library that can be passed a list of cipher
suites before the cipher negotiation takes place. This specifies which ciphers
-are acceptable. The list is colon separated and may contain names like
-DES-CBC3-SHA. Exim passes the expanded value of tls_require_ciphers directly to
-this function call. Many systems will install the OpenSSL manual-pages, so you
-may have ciphers(1) available to you. The following quotation from the OpenSSL
+
+are acceptable for TLS versions prior to 1.3.
+
+The list is colon separated and may contain names like DES-CBC3-SHA. Exim
+passes the expanded value of tls_require_ciphers directly to this function
+call. Many systems will install the OpenSSL manual-pages, so you may have
+ciphers(1) available to you. The following quotation from the OpenSSL
documentation specifies what forms of item are allowed in the cipher string:
* It can consist of a single cipher suite such as RC4-SHA.
{DEFAULT}\
{HIGH:!MD5:!SHA1}}
+This example will prefer ECDSA-authenticated ciphers over RSA ones:
+
+tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT
+
+For TLS version 1.3 the control available is less fine-grained and Exim does
+not provide access to it at present. The value of the tls_require_ciphers
+option is ignored when TLS version 1.3 is negotiated.
+
+As of writing the library default cipher suite list for TLSv1.3 is
+
+TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
+
42.5 Requiring specific ciphers or other parameters in GnuTLS
-------------------------------------------------------------
feature enhancements of GnuTLS.
Documentation of the strings accepted may be found in the GnuTLS manual, under
-"Priority strings". This is online as http://www.gnutls.org/manual/html_node/
+"Priority strings". This is online as https://www.gnutls.org/manual/html_node/
Priority-Strings.html, but beware that this relates to GnuTLS 3, which may be
newer than the version installed on your system. If you are using GnuTLS 3,
-then the example code http://www.gnutls.org/manual/gnutls.html#
+then the example code https://www.gnutls.org/manual/gnutls.html#
Listing-the-ciphersuites-in-a-priority-string on that site can be used to test
a given string.
When Exim has been built with TLS support, it advertises the availability of
the STARTTLS command to client hosts that match tls_advertise_hosts, but not to
-any others. The default value of this option is unset, which means that
-STARTTLS is not advertised at all. This default is chosen because you need to
-set some other options in order to make TLS available, and also it is sensible
-for systems that want to use TLS only as a client.
+any others. The default value of this option is *, which means that STARTTLS is
+always advertised. Set it to blank to never advertise; this is reasonable for
+systems that want to use TLS only as a client.
+
+If STARTTLS is to be used you need to set some other options in order to make
+TLS available.
If a client issues a STARTTLS command and there is some configuration problem
in the server, the command is rejected with a 454 error. If the client persists
Further protection requires some further configuration at the server end.
-It is rumoured that all existing clients that support TLS/SSL use RSA
-encryption. To make this work you need to set, in the server,
+To make TLS work you need to set, in the server,
tls_certificate = /some/file/name
tls_privatekey = /some/file/name
case. The certificate file may also contain intermediate certificates that need
to be sent to the client to enable it to authenticate the server's certificate.
+For dual-stack (eg. RSA and ECDSA) configurations, these options can be
+colon-separated lists of file paths. Ciphers using given authentication
+algorithms require the presence of a suitable certificate to supply the
+public-key. The server selects among the certificates to present to the client
+depending on the selected cipher, hence the priority ordering for ciphers will
+affect which certificate is used.
+
If you do not understand about certificates and keys, please try to find a
source of this background information, which is not Exim-specific. (There are a
few comments below in section 42.12.)
all TLS connections. For any host that matches one of these options, Exim
requests a certificate as part of the setup of the TLS session. The contents of
the certificate are verified by comparing it with a list of expected
-certificates. These may be the system default set (depending on library
-version), an explicit file or, depending on library version, a directory,
-identified by tls_verify_certificates.
+trust-anchors or certificates. These may be the system default set (depending
+on library version), an explicit file or, depending on library version, a
+directory, identified by tls_verify_certificates.
A file can contain multiple certificates, concatenated end to end. If a
directory is used (OpenSSL only), each certificate must be in a separate file,
where /cert/file contains a single certificate.
+There is no checking of names of the client against the certificate Subject
+Name or Subject Alternate Names.
+
The difference between tls_verify_hosts and tls_try_verify_hosts is what
happens if the client does not supply a certificate, or if the certificate does
not match any of the certificates in the collection named by
If the tls_verify_certificates option is set on the smtp transport, it
specifies a collection of expected server certificates. These may be the system
-default set (depending on library version), a file or, depending on library
-version, a directory, must name a file or, for OpenSSL only (not GnuTLS), a
-directory. The client verifies the server's certificate against this
+default set (depending on library version), a file, or (depending on library
+version) a directory. The client verifies the server's certificate against this
collection, taking into account any revoked certificates that are in the list
defined by tls_crl. Failure to verify fails the TLS connection unless either of
the tls_verify_hosts or tls_try_verify_hosts options are set.
verification to the listed servers. Verification either must or need not
succeed respectively.
+The tls_verify_cert_hostnames option lists hosts for which additional checks
+are made: that the host name (the one in the DNS A record) is valid for the
+certificate. The option defaults to always checking.
+
The smtp transport has two OCSP-related options: hosts_require_ocsp; a
host-list for which a Certificate Status is requested and required for the
connection to proceed. The default value is empty. hosts_request_ocsp; a
process to the next. This implementation does not fit well with the use of TLS,
because there is quite a lot of state information associated with a TLS
connection, not just a socket identification. Passing all the state information
-to a new process is not feasible. Consequently, Exim shuts down an existing TLS
-session before passing the socket to a new process. The new process may then
-try to start a new TLS session, and if successful, may try to re-authenticate
-if AUTH is in use, before sending the next message.
+to a new process is not feasible. Consequently, for sending using TLS Exim
+starts an additional proxy process for handling the encryption, piping the
+unencrypted data stream from and to the delivery processes.
+
+An older mode of operation can be enabled on a per-host basis by the
+hosts_noproxy_tls option on the smtp transport. If the host matches this list
+the proxy process described above is not used; instead Exim shuts down an
+existing TLS session being run by the delivery process before passing the
+socket to a new process. The new process may then try to start a new TLS
+session, and if successful, may try to re-authenticate if AUTH is in use,
+before sending the next message.
The RFC is not clear as to whether or not an SMTP session continues in clear
after TLS has been shut down, or whether TLS may be restarted again later, as
-------------------------------
In order to understand fully how TLS works, you need to know about
-certificates, certificate signing, and certificate authorities. This is not the
-place to give a tutorial, especially as I do not know very much about it
-myself. Some helpful introduction can be found in the FAQ for the SSL addition
-to Apache, currently at
+certificates, certificate signing, and certificate authorities. This is a large
+topic and an introductory guide is unsuitable for the Exim reference manual, so
+instead we provide pointers to existing documentation.
+
+The Apache web-server was for a long time the canonical guide, so their
+documentation is a good place to start; their SSL module's Introduction
+document is currently at
-http://www.modssl.org/docs/2.7/ssl_faq.html#ToC24
+https://httpd.apache.org/docs/current/ssl/ssl_intro.html
-Other parts of the modssl documentation are also helpful, and have links to
-further files. Eric Rescorla's book, SSL and TLS, published by Addison-Wesley
-(ISBN 0-201-61598-3), contains both introductory and more in-depth
-descriptions. Some sample programs taken from the book are available from
+and their FAQ is at
-http://www.rtfm.com/openssl-examples/
+https://httpd.apache.org/docs/current/ssl/ssl_faq.html
+
+Eric Rescorla's book, SSL and TLS, published by Addison-Wesley (ISBN
+0-201-61598-3) in 2001, contains both introductory and more in-depth
+descriptions. More recently Ivan Risti?'s book Bulletproof SSL and TLS,
+published by Feisty Duck (ISBN 978-1907117046) in 2013 is good. Ivan is the
+author of the popular TLS testing tools at https://www.ssllabs.com/.
42.13 Certificate chains
For information on creating self-signed CA certificates and using them to sign
user certificates, see the General implementation overview chapter of the
-Open-source PKI book, available online at http://ospkibook.sourceforge.net/.
+Open-source PKI book, available online at https://sourceforge.net/projects/
+ospkibook/.
+
+
+42.15 DANE
+----------
+
+DNS-based Authentication of Named Entities, as applied to SMTP over TLS,
+provides assurance to a client that it is actually talking to the server it
+wants to rather than some attacker operating a Man In The Middle (MITM)
+operation. The latter can terminate the TLS connection you make, and make
+another one to the server (so both you and the server still think you have an
+encrypted connection) and, if one of the "well known" set of Certificate
+Authorities has been suborned - something which *has* been seen already (2014),
+a verifiable certificate (if you're using normal root CAs, eg. the Mozilla set,
+as your trust anchors).
+
+What DANE does is replace the CAs with the DNS as the trust anchor. The
+assurance is limited to a) the possibility that the DNS has been suborned, b)
+mistakes made by the admins of the target server. The attack surface presented
+by (a) is thought to be smaller than that of the set of root CAs.
+
+It also allows the server to declare (implicitly) that connections to it should
+use TLS. An MITM could simply fail to pass on a server's STARTTLS.
+
+DANE scales better than having to maintain (and side-channel communicate)
+copies of server certificates for every possible target server. It also scales
+(slightly) better than having to maintain on an SMTP client a copy of the
+standard CAs bundle. It also means not having to pay a CA for certificates.
+
+DANE requires a server operator to do three things: 1) run DNSSEC. This
+provides assurance to clients that DNS lookups they do for the server have not
+been tampered with. The domain MX record applying to this server, its A record,
+its TLSA record and any associated CNAME records must all be covered by DNSSEC.
+2) add TLSA DNS records. These say what the server certificate for a TLS
+connection should be. 3) offer a server certificate, or certificate chain, in
+TLS connections which is is anchored by one of the TLSA records.
+
+There are no changes to Exim specific to server-side operation of DANE. Support
+for client-side operation of DANE can be included at compile time by defining
+SUPPORT_DANE=yes in Local/Makefile. If it has been included, the macro
+"_HAVE_DANE" will be defined.
+
+The TLSA record for the server may have "certificate usage" of DANE-TA(2) or
+DANE-EE(3). These are the "Trust Anchor" and "End Entity" variants. The latter
+specifies the End Entity directly, i.e. the certificate involved is that of the
+server (and if only DANE-EE is used then it should be the sole one transmitted
+during the TLS handshake); this is appropriate for a single system, using a
+self-signed certificate. DANE-TA usage is effectively declaring a specific CA
+to be used; this might be a private CA or a public, well-known one. A private
+CA at simplest is just a self-signed certificate (with certain attributes)
+which is used to sign server certificates, but running one securely does
+require careful arrangement. With DANE-TA, as implemented in Exim and commonly
+in other MTAs, the server TLS handshake must transmit the entire certificate
+chain from CA to server-certificate. DANE-TA is commonly used for several
+services and/or servers, each having a TLSA query-domain CNAME record, all of
+which point to a single TLSA record. DANE-TA and DANE-EE can both be used
+together.
+
+Our recommendation is to use DANE with a certificate from a public CA, because
+this enables a variety of strategies for remote clients to verify your
+certificate. You can then publish information both via DANE and another
+technology, "MTA-STS", described below.
+
+When you use DANE-TA to publish trust anchor information, you ask entities
+outside your administrative control to trust the Certificate Authority for
+connections to you. If using a private CA then you should expect others to
+still apply the technical criteria they'd use for a public CA to your
+certificates. In particular, you should probably try to follow current best
+practices for CA operation around hash algorithms and key sizes. Do not expect
+other organizations to lower their security expectations just because a
+particular profile might be reasonable for your own internal use.
+
+When this text was last updated, this in practice means to avoid use of SHA-1
+and MD5; if using RSA to use key sizes of at least 2048 bits (and no larger
+than 4096, for interoperability); to use keyUsage fields correctly; to use
+random serial numbers. The list of requirements is subject to change as best
+practices evolve. If you're not already using a private CA, or it doesn't meet
+these requirements, then we encourage you to avoid all these issues and use a
+public CA such as Let's Encrypt instead.
+
+The TLSA record should have a Selector field of SPKI(1) and a Matching Type
+field of SHA2-512(2).
+
+At the time of writing, https://www.huque.com/bin/gen_tlsa is useful for
+quickly generating TLSA records; and commands like
+
+ openssl x509 -in -pubkey -noout <certificate.pem \
+ | openssl rsa -outform der -pubin 2>/dev/null \
+ | openssl sha512 \
+ | awk '{print $2}'
+
+are workable for 4th-field hashes.
+
+For use with the DANE-TA model, server certificates must have a correct name
+(SubjectName or SubjectAltName).
+
+The Certificate issued by the CA published in the DANE-TA model should be
+issued using a strong hash algorithm. Exim, and importantly various other MTAs
+sending to you, will not re-enable hash algorithms which have been disabled by
+default in TLS libraries. This means no MD5 and no SHA-1. SHA2-256 is the
+minimum for reliable interoperability (and probably the maximum too, in 2018).
+
+The use of OCSP-stapling should be considered, allowing for fast revocation of
+certificates (which would otherwise be limited by the DNS TTL on the TLSA
+records). However, this is likely to only be usable with DANE-TA. NOTE: the
+default of requesting OCSP for all hosts is modified iff DANE is in use, to:
+
+ hosts_request_ocsp = ${if or { {= {0}{$tls_out_tlsa_usage}} \
+ {= {4}{$tls_out_tlsa_usage}} } \
+ {*}{}}
+
+The (new) variable $tls_out_tlsa_usage is a bitfield with numbered bits set for
+TLSA record usage codes. The zero above means DANE was not in use, the four
+means that only DANE-TA usage TLSA records were found. If the definition of
+hosts_request_ocsp includes the string "tls_out_tlsa_usage", they are
+re-expanded in time to control the OCSP request.
+
+This modification of hosts_request_ocsp is only done if it has the default
+value of "*". Admins who change it, and those who use hosts_require_ocsp,
+should consider the interaction with DANE in their OCSP settings.
+
+For client-side DANE there are three new smtp transport options, hosts_try_dane
+, hosts_require_dane and dane_require_tls_ciphers. The require variant will
+result in failure if the target host is not DNSSEC-secured.
+
+DANE will only be usable if the target host has DNSSEC-secured MX, A and TLSA
+records.
+
+A TLSA lookup will be done if either of the above options match and the
+host-lookup succeeded using dnssec. If a TLSA lookup is done and succeeds, a
+DANE-verified TLS connection will be required for the host. If it does not, the
+host will not be used; there is no fallback to non-DANE or non-TLS.
+
+If DANE is requested and usable, then the TLS cipher list configuration prefers
+to use the option dane_require_tls_ciphers and falls back to
+tls_require_ciphers only if that is unset. This lets you configure "decent
+crypto" for DANE and "better than nothing crypto" as the default. Note though
+that while GnuTLS lets the string control which versions of TLS/SSL will be
+negotiated, OpenSSL does not and you're limited to ciphersuite constraints.
+
+If DANE is requested and useable (see above) the following transport options
+are ignored:
+
+ hosts_require_tls
+ tls_verify_hosts
+ tls_try_verify_hosts
+ tls_verify_certificates
+ tls_crl
+ tls_verify_cert_hostnames
+
+If DANE is not usable, whether requested or not, and CA-anchored verification
+evaluation is wanted, the above variables should be set appropriately.
+
+Currently the dnssec_request_domains must be active and dnssec_require_domains
+is ignored.
+
+If verification was successful using DANE then the "CV" item in the delivery
+log line will show as "CV=dane".
+
+There is a new variable $tls_out_dane which will have "yes" if verification
+succeeded using DANE and "no" otherwise (only useful in combination with
+events; see 60), and a new variable $tls_out_tlsa_usage (detailed above).
+
+An event (see 60) of type "dane:fail" will be raised on failures to achieve
+DANE-verified connection, if one was either requested and offered, or required.
+This is intended to support TLS-reporting as defined in https://tools.ietf.org/
+html/draft-ietf-uta-smtp-tlsrpt-17. The $event_data will be one of the Result
+Types defined in Section 4.3 of that document.
+
+Under GnuTLS, DANE is only supported from version 3.0.0 onwards.
+
+DANE is specified in published RFCs and decouples certificate authority trust
+selection from a "race to the bottom" of "you must trust everything for mail to
+get through". There is an alternative technology called MTA-STS, which instead
+publishes MX trust anchor information on an HTTPS website. At the time this
+text was last updated, MTA-STS was still a draft, not yet an RFC. Exim has no
+support for MTA-STS as a client, but Exim mail server operators can choose to
+publish information describing their TLS configuration using MTA-STS to let
+those clients who do use that protocol derive trust information.
+
+The MTA-STS design requires a certificate from a public Certificate Authority
+which is recognized by clients sending to you. That selection of which CAs are
+trusted by others is outside your control.
+
+The most interoperable course of action is probably to use Let's Encrypt, with
+automated certificate renewal; to publish the anchor information in
+DNSSEC-secured DNS via TLSA records for DANE clients (such as Exim and Postfix)
+and to publish anchor information for MTA-STS as well. This is what is done for
+the exim.org domain itself (with caveats around occasionally broken MTA-STS
+because of incompatible specification changes prior to reaching RFC status).
===============================================================================
43. ACCESS CONTROL LISTS
-Access Control Lists (ACLs) are defined in a separate section of the run time
+Access Control Lists (ACLs) are defined in a separate section of the runtime
configuration file, headed by "begin acl". Each ACL definition starts with a
name, terminated by a colon. Here is a complete ACL section that contains just
one very small ACL:
This ACL is evaluated before acl_smtp_mime and acl_smtp_data.
-For details on the operation of DKIM, see chapter 57.
+For details on the operation of DKIM, see section 57.1.
43.8 The SMTP MIME ACL
{acl_check_rcpt} {acl_check_rcpt_submit} }
In the default configuration file there are some example settings for providing
-an RFC 4409 message submission service on port 587 and a non-standard "smtps"
-service on port 465. You can use a string expansion like this to choose an ACL
-for MUAs on these ports which is more appropriate for this purpose than the
-default ACL on port 25.
+an RFC 4409 message "submission" service on port 587 and an RFC 8314
+"submissions" service on port 465. You can use a string expansion like this to
+choose an ACL for MUAs on these ports which is more appropriate for this
+purpose than the default ACL on port 25.
The expanded string does not have to be the name of an ACL in the configuration
file; there are other possibilities. Having expanded the string, Exim searches
for an ACL as follows:
- * If the string begins with a slash, Exim uses it as a file name, and reads
+ * If the string begins with a slash, Exim uses it as a filename, and reads
its contents as an ACL. The lines are processed in the same way as lines in
the Exim configuration file. In particular, continuation lines are
supported, blank lines are ignored, as are lines whose first non-whitespace
Note that routers are used in verify mode, and cannot depend on content of
received headers. Note also that headers cannot be modified by any of the
post-data ACLs (DATA, MIME and DKIM). Headers may be modified by routers
- (subject to the above) and transports.
+ (subject to the above) and transports. The Received-By: header is generated
+ as soon as the body reception starts, rather than the traditional time
+ after the full message is received; this will affect the timestamp.
All the usual ACLs are called; if one results in the message being
rejected, all effort spent in delivery (including the costs on the ultimate
Cutthrough delivery is not supported via transport-filters or when DKIM
signing of outgoing messages is done, because it sends data to the ultimate
destination before the entire message has been received from the source. It
- is not supported for messages received with the SMTP PRDR
-
- or CHUNKING
-
+ is not supported for messages received with the SMTP PRDR or CHUNKING
options in use.
Should the ultimate destination system positively accept or reject the
control = debug/<options>
This control turns on debug logging, almost as though Exim had been invoked
- with "-d", with the output going to a new logfile, by default called
- debuglog. The filename can be adjusted with the tag option, which may
- access any variables already defined. The logging may be adjusted with the
- opts option, which takes the same values as the "-d" command-line option.
- Logging may be stopped, and the file removed, with the kill option. Some
- examples (which depend on variables that don't exist in all contexts):
+ with "-d", with the output going to a new logfile in the usual logs
+ directory, by default called debuglog. The filename can be adjusted with
+ the tag option, which may access any variables already defined. The logging
+ may be adjusted with the opts option, which takes the same values as the
+ "-d" command-line option. Logging started this way may be stopped, and the
+ file removed, with the kill option. Some examples (which depend on
+ variables that don't exist in all contexts):
control = debug
control = debug/tag=.$sender_host_address
control = dkim_disable_verify
This control turns off DKIM verification processing entirely. For details
- on the operation and configuration of DKIM, see chapter 57.
+ on the operation and configuration of DKIM, see section 57.1.
control = dscp/<value>
warn message = Remove internal headers
remove_header = $acl_c_ihdrs
-Removed header lines are accumulated during the MAIL, RCPT, and predata ACLs.
-They are removed from the message before processing the DATA and MIME ACLs.
-There is no harm in attempting to remove the same header twice nor is removing
-a non-existent header. Further header lines to be removed may be accumulated
+Header names for removal are accumulated during the MAIL, RCPT, and predata
+ACLs. Matching header lines are removed from the message before processing the
+DATA and MIME ACLs. If multiple header lines match, all are removed. There is
+no harm in attempting to remove the same header twice nor in removing a
+non-existent header. Further header lines to be removed may be accumulated
during the DATA and MIME ACLs, after which they are removed from the message,
if present. In the case of non-SMTP messages, headers to be removed are
accumulated during the non-SMTP ACLs, and are removed from the message after
does not share information between multiple incoming connections (but your
local name server cache should be active).
+There are a number of DNS lists to choose from, some commercial, some free, or
+free for small deployments. An overview can be found at https://
+en.wikipedia.org/wiki/Comparison_of_DNS_blacklists.
+
43.28 Specifying the IP address for a DNS list lookup
-----------------------------------------------------
-------------------------------------
There are some lists that are keyed on domain names rather than inverted IP
-addresses (see for example the domain based zones link at http://
+addresses (see, e.g., the domain based zones link at http://
www.rfc-ignorant.org/). No reversing of components is used with these lists.
You can change the name that is looked up in a DNS list by listing it after the
domain name, introduced by a slash. For example,
tested is indeed on the first list. The first domain is the one that is put in
$dnslist_domain. For example:
-reject message = \
+deny message = \
rejected because $sender_host_address is blacklisted \
at $dnslist_domain\n$dnslist_text
dnslists = \
several times, but because the results of the DNS lookups are cached, the DNS
calls themselves are not repeated. For example:
-reject dnslists = \
+deny dnslists = \
http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \
socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3 : \
misc.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.4 : \
"$local_part@$domain" with the per_rcpt option (see below) in a RCPT ACL.
Each ratelimit condition can have up to four options. A per_* option specifies
-what Exim measures the rate of, for example messages or recipients or bytes.
+what Exim measures the rate of, for example, messages or recipients or bytes.
You can adjust the measurement using the unique= and/or count= options. You can
also control when Exim updates the recorded rate using a strict, leaky, or
readonly option. The options are separated by a slash, like the other
The leaky (default) option means that the client's recorded rate is not updated
if it is above the limit. The effect of this is that Exim measures the client's
-average rate of successfully sent email, which cannot be greater than the
-maximum allowed. If the client is over the limit it may suffer some
-counter-measures (as specified in the ACL), but it will still be able to send
-email at the configured maximum rate, whatever the rate of its attempts. This
-is generally the better choice if you have clients that retry automatically.
-For example, it does not prevent a sender with an over-aggressive retry rate
-from getting any email through.
+average rate of successfully sent email,
+
+up to the given limit. This is appropriate if the countermeasure when the
+condition is true consists of refusing the message, and is generally the better
+choice if you have clients that retry automatically. If the action when true is
+anything more complex then this option is likely not what is wanted.
The strict option means that the client's recorded rate is always updated. The
effect of this is that Exim measures the client's average rate of attempts to
The main use of these variables is expected to be to distinguish between
rejections of MAIL and rejections of RCPT in callouts.
+The above variables may also be set after a successful address verification to:
+
+ * random: A random local-part callout succeeded
+
43.45 Callout verification
--------------------------
of the sender when checking recipients. If used indiscriminately, it
reduces the usefulness of callout caching.
+hold
+
+ This option applies to recipient callouts only. For example:
+
+ require verify = recipient/callout=use_sender,hold
+
+ It causes the connection to be held open and used for any further
+ recipients and for eventual delivery (should that be done quickly). Doing
+ this saves on TCP and SMTP startup costs, and TLS costs also when that is
+ used for the connections. The advantage is only gained if there are no
+ callout cache hits (which could be enforced by the no_cache option), if the
+ use_sender option is used, if neither the random nor the use_postmaster
+ option is used, and if no other callouts intervene.
+
If you use any of the parameters that set a non-empty sender for the MAIL
command (mailfrom, postmaster_mailfrom, use_postmaster, or use_sender), you
should think about possible loops. Recipient checking is usually done between
the original envelope sender address by using a simple key to add a hash of the
address and some time-based randomizing information. The prvs expansion item
creates a signed address, and the prvscheck expansion item checks one. The
-syntax of these expansion items is described in section 11.5.
+syntax of these expansion items is described in section 11.5. The validity
+period on signed addresses is seven days.
As an example, suppose the secret per-address keys are stored in an MySQL
database. A query to look up the key for an address could be defined as a macro
If the value of av_scanner starts with a dollar character, it is expanded
before use. The usual list-parsing of the content (see 6.20) applies. The
-following scanner types are supported in this release:
+following scanner types are supported in this release, though individual ones
+can be included or not at build time:
avast
This is the scanner daemon of Avast. It has been tested with Avast Core
- Security (currently at version 1.1.7). You can get a trial version at http:
- //www.avast.com or for Linux at http://www.avast.com/linux-server-antivirus
- . This scanner type takes one option, which can be either a full path to a
- UNIX socket, or host and port specifiers separated by white space. The host
- may be a name or an IP address; the port is either a single number or a
- pair of numbers with a dash between. Any further options are given, on
- separate lines, to the daemon as options before the main scan command. For
- example:
+ Security (currently at version 2.2.0). You can get a trial version at
+ https://www.avast.com or for Linux at https://www.avast.com/
+ linux-server-antivirus. This scanner type takes one option, which can be
+ either a full path to a UNIX socket, or host and port specifiers separated
+ by white space. The host may be a name or an IP address; the port is either
+ a single number or a pair of numbers with a dash between. A list of options
+ may follow. These options are interpreted on the Exim's side of the malware
+ scanner, or are given on separate lines to the daemon as options before the
+ main scan command.
+
+ If "pass_unscanned" is set, any files the Avast scanner can't scan (e.g.
+ decompression bombs, or invalid archives) are considered clean. Use with
+ care.
+
+ For example:
av_scanner = avast:/var/run/avast/scan.sock:FLAGS -fullfiles:SENSITIVITY -pup
+ av_scanner = avast:/var/run/avast/scan.sock:pass_unscanned:FLAGS -fullfiles:SENSITIVITY -pup
av_scanner = avast:192.168.2.22 5036
If you omit the argument, the default path /var/run/avast/scan.sock is
SENSITIVITY
PACK
+ If the scanner returns a temporary failure (e.g. license issues, or
+ permission problems), the message is deferred and a paniclog entry is
+ written. The usual "defer_ok" option is available.
+
aveserver
This is the scanner daemon of Kaspersky Version 5. You can get a trial
- version at http://www.kaspersky.com. This scanner type takes one option,
+ version at https://www.kaspersky.com/. This scanner type takes one option,
which is the path to the daemon's UNIX socket. The default is shown in this
example:
clamd
- This daemon-type scanner is GPL and free. You can get it at http://
+ This daemon-type scanner is GPL and free. You can get it at https://
www.clamav.net/. Some older versions of clamd do not seem to unpack MIME
containers, so it used to be recommended to unpack MIME attachments in the
MIME ACL. This is no longer believed to be necessary.
If the value of av_scanner points to a UNIX socket file or contains the
"local" option, then the ClamAV interface will pass a filename containing
- the data to be scanned, which will should normally result in less I/O
- happening and be more efficient. Normally in the TCP case, the data is
- streamed to ClamAV as Exim does not assume that there is a common
- filesystem with the remote host. There is an option WITH_OLD_CLAMAV_STREAM
- in src/EDITME available, should you be running a version of ClamAV prior to
- 0.95.
+ the data to be scanned, which should normally result in less I/O happening
+ and be more efficient. Normally in the TCP case, the data is streamed to
+ ClamAV as Exim does not assume that there is a common filesystem with the
+ remote host.
The final example shows that multiple TCP targets can be specified. Exim
will randomly use one for each incoming email (i.e. it load balances them).
drweb
- The DrWeb daemon scanner (http://www.sald.com/) interface takes one option,
+ The DrWeb daemon scanner (https://www.sald.ru/) interface takes one option,
either a full path to a UNIX socket, or host and port specifiers separated
by white space. The host may be a name or an IP address; the port is either
a single number or a pair of numbers with a dash between. For example:
av_scanner = f-protd:localhost 10200-10204
+ If you omit the argument, the default values shown above are used.
+
+f-prot6d
+
+ The f-prot6d scanner is accessed using the FPSCAND protocol over TCP. One
+ argument is taken, being a space-separated hostname and port number. For
+ example:
+
+ av_scanner = f-prot6d:localhost 10200
+
If you omit the argument, the default values show above are used.
fsecure
- The F-Secure daemon scanner (http://www.f-secure.com) takes one argument
+ The F-Secure daemon scanner (https://www.f-secure.com/) takes one argument
which is the path to a UNIX socket. For example:
av_scanner = fsecure:/path/to/.fsav
mksd
- This is a daemon type scanner that is aimed mainly at Polish users, though
- some parts of documentation are now available in English. You can get it at
- http://linux.mks.com.pl/. The only option for this scanner type is the
- maximum number of processes used simultaneously to scan the attachments,
- provided that mksd has been run with at least the same number of child
- processes. For example:
+ This was a daemon type scanner that is aimed mainly at Polish users, though
+ some documentation was available in English. The history can be shown at
+ https://en.wikipedia.org/wiki/Mks_vir and this appears to be a candidate
+ for removal from Exim, unless we are informed of other virus scanners which
+ use the same protocol to integrate. The only option for this scanner type
+ is the maximum number of processes used simultaneously to scan the
+ attachments, provided that mksd has been run with at least the same number
+ of child processes. For example:
av_scanner = mksd:2
on the local machine. There are four options: an address (which may be an
IP address and port, or the path of a Unix socket), a commandline to send
(may include a single %s which will be replaced with the path to the mail
- file to be scanned), an RE to trigger on from the returned data, an RE to
- extract malware_name from the returned data. For example:
+ file to be scanned), an RE to trigger on from the returned data, and an RE
+ to extract malware_name from the returned data. For example:
- av_scanner = sock:127.0.0.1 6001:%s:(SPAM|VIRUS):(.*)\$
+ av_scanner = sock:127.0.0.1 6001:%s:(SPAM|VIRUS):(.*)$
- Default for the socket specifier is /tmp/malware.sock. Default for the
- commandline is %s\n. Both regular-expressions are required.
+ Note that surrounding whitespace is stripped from each option, meaning
+ there is no way to specify a trailing newline. The socket specifier and
+ both regular-expressions are required. Default for the commandline is %s\n
+ (note this does have a trailing newline); specify an empty element to get
+ this.
sophie
Sophie is a daemon that uses Sophos' libsavi library to scan for viruses.
- You can get Sophie at http://www.clanfield.info/sophie/. The only option
- for this scanner type is the path to the UNIX socket that Sophie uses for
+ You can get Sophie at http://sophie.sourceforge.net/. The only option for
+ this scanner type is the path to the UNIX socket that Sophie uses for
client communication. For example:
av_scanner = sophie:/tmp/sophie
condition succeeds if a virus is found and its name matches the regular
expression. This allows you to take special actions on certain types of
virus. Note that "/" characters in the RE must be doubled due to the
- list-processing, unless the separator is changed (in the usual way).
+ list-processing, unless the separator is changed (in the usual way 6.21).
You can append a "defer_ok" element to the malware argument list to accept
messages even if there is a problem with the virus scanner. Otherwise, such a
and a report for the message. Support is also provided for Rspamd.
For more information about installation and configuration of SpamAssassin or
-Rspamd refer to their respective websites at http://spamassassin.apache.org and
-http://www.rspamd.com
+Rspamd refer to their respective websites at https://spamassassin.apache.org/
+and https://www.rspamd.com/
SpamAssassin can be installed with CPAN by running:
must set the spamd_address option in the global part of the Exim configuration
as follows (example):
-spamd_address = 192.168.99.45 387
+spamd_address = 192.168.99.45 783
The SpamAssassin protocol relies on a TCP half-close from the client. If your
SpamAssassin client side is running a Linux system with an iptables firewall,
spamd_address = 127.0.0.1 11333 variant=rspamd
As of version 2.60, SpamAssassin also supports communication over UNIX sockets.
-If you want to us these, supply spamd_address with an absolute file name
-instead of an address/port pair:
+If you want to us these, supply spamd_address with an absolute filename instead
+of an address/port pair:
spamd_address = /var/run/spamd_socket
You can have multiple spamd servers to improve scalability. These can reside on
other hardware reachable over the network. To specify multiple spamd servers,
put multiple address/port pairs in the spamd_address option, separated with
-colons (the separator can be changed in the usual way):
+colons (the separator can be changed in the usual way 6.21):
spamd_address = 192.168.2.10 783 : \
192.168.2.11 783 : \
Unix and TCP socket specifications may be mixed in any order. Each element of
the list is a list itself, space-separated by default and changeable in the
-usual way; take care to not double the separator.
+usual way (6.21); take care to not double the separator.
For TCP socket specifications a host name or IP (v4 or v6, but subject to
list-separator quoting rules) address can be used, and the port can be one or a
$spam_score
- The spam score of the message, for example "3.4" or "30.5". This is useful
+ The spam score of the message, for example, "3.4" or "30.5". This is useful
for inclusion in log or reject messages.
$spam_score_int
2. The string "default". In that case, the file is put in the temporary
"default" directory <spool_directory>/scan/<message_id>/ with a sequential
- file name consisting of the message id and a sequence number. The full path
+ filename consisting of the message id and a sequence number. The full path
and name is available in $mime_decoded_filename after decoding.
3. A full path name starting with a slash. If the full name is an existing
directory, it is used as a replacement for the default directory. The
filename is then sequentially assigned. If the path does not exist, it is
- used as the full path and file name.
+ used as the full path and filename.
4. If the string does not start with a slash, it is used as the filename, and
the default path is then used.
$mime_decoded_filename
This variable is set only after the decode modifier (see above) has been
- successfully run. It contains the full path and file name of the file
+ successfully run. It contains the full path and filename of the file
containing the decoded data.
$mime_filename
As an example, the following will ban "HTML mail" (including that sent with
alternative plain text), while allowing HTML files to be attached. HTML
- coverletter mail attached to non-HMTL coverletter mail will also be
+ coverletter mail attached to non-HTML coverletter mail will also be
allowed:
deny message = HTML mail is not accepted here
$mime_is_multipart
This variable has the value 1 (true) when the current part has the main
- type "multipart", for example "multipart/alternative" or "multipart/mixed".
- Since multipart entities only serve as containers for other parts, you may
- not want to carry out specific actions on them.
+ type "multipart", for example, "multipart/alternative" or "multipart/
+ mixed". Since multipart entities only serve as containers for other parts,
+ you may not want to carry out specific actions on them.
$mime_is_rfc822
-----------------------------------------------
To make use of the local scan function feature, you must tell Exim where your
-function is before building Exim, by setting LOCAL_SCAN_SOURCE in your Local/
-Makefile. A recommended place to put it is in the Local directory, so you might
-set
+function is before building Exim, by setting
+
+both HAVE_LOCAL_SCAN and
+LOCAL_SCAN_SOURCE in your Local/Makefile. A recommended place to put it is in
+the Local directory, so you might set
+
+HAVE_LOCAL_SCAN=yes
LOCAL_SCAN_SOURCE=Local/local_scan.c
for example. The function must be called local_scan(). It is called by Exim
commented template function (that just accepts the message) in the file _src/
local_scan.c_.
-If you want to make use of Exim's run time configuration file to set options
-for your local_scan() function, you must also set
+If you want to make use of Exim's runtime configuration file to set options for
+your local_scan() function, you must also set
LOCAL_SCAN_HAS_OPTIONS=yes
int body_linecount
- This variable contains the number of lines in the message's body.
+ This variable contains the number of lines in the message's body. It is not
+ valid if the spool_files_wireformat option is used.
int body_zerocount
This variable contains the number of binary zero bytes in the message's
- body.
+ body. It is not valid if the spool_files_wireformat option is used.
unsigned int debug_selector
===============================================================================
49. CUSTOMIZING BOUNCE AND WARNING MESSAGES
-When a message fails to be delivered, or remains on the queue for more than a
+When a message fails to be delivered, or remains in the queue for more than a
configured amount of time, Exim sends a message to the original sender, or to
an alternative configured address. The text of these messages is built into the
code of Exim, but it is possible to change it, either by adding a single
<$sender_address>
}}has not been delivered to all of its recipients after
-more than $warn_message_delay on the queue on $primary_hostname.
+more than $warn_message_delay in the queue on $primary_hostname.
The message identifier is: $message_exim_id
The subject of the message is: $h_subject
routers are tried, and so the whole delivery fails.
The forbid_pipe and forbid_file options prevent a local part from being
-expanded into a file name or a pipe delivery, which is usually inappropriate in
+expanded into a filename or a pipe delivery, which is usually inappropriate in
a mailing list.
The errors_to option specifies that any delivery errors caused by addresses
50.6 Variable Envelope Return Paths (VERP)
------------------------------------------
-Variable Envelope Return Paths - see http://cr.yp.to/proto/verp.txt - are a way
-of helping mailing list administrators discover which subscription address is
-the cause of a particular delivery failure. The idea is to encode the original
-recipient address in the outgoing envelope sender address, so that if the
-message is forwarded by another host and then subsequently bounces, the
+Variable Envelope Return Paths - see https://cr.yp.to/proto/verp.txt - are a
+way of helping mailing list administrators discover which subscription address
+is the cause of a particular delivery failure. The idea is to encode the
+original recipient address in the outgoing envelope sender address, so that if
+the message is forwarded by another host and then subsequently bounces, the
original recipient can be extracted from the recipient address of the bounce.
Envelope sender addresses can be modified by Exim using two different
ensures that if the lookup fails (leading to data being an empty string), Exim
gives up on the address without trying any subsequent routers.
-This one router can handle all the virtual domains because the alias file names
+This one router can handle all the virtual domains because the alias filenames
follow a fixed pattern. Permissions can be arranged so that appropriate people
can edit the different alias files. A successful aliasing operation results in
a new envelope recipient address, which is then routed from scratch.
--------------------------------------
It is tempting to arrange for incoming mail for the intermittently connected
-host to remain on Exim's queue until the client connects. However, this
+host to remain in Exim's queue until the client connects. However, this
approach does not scale very well. Two different kinds of waiting message are
being mixed up in the same queue - those that cannot be delivered because of
some temporary problem, and those that are waiting for their destination host
this has been seen to make syslog take 90% plus of CPU time.
The destination for Exim's logs is configured by setting LOG_FILE_PATH in Local
-/Makefile or by setting log_file_path in the run time configuration. This
-latter string is expanded, so it can contain, for example, references to the
-host name:
+/Makefile or by setting log_file_path in the runtime configuration. This latter
+string is expanded, so it can contain, for example, references to the host
+name:
log_file_path = /var/log/$primary_hostname/exim_%slog
It is generally advisable, however, to set the string in Local/Makefile rather
-than at run time, because then the setting is available right from the start of
+than at runtime, because then the setting is available right from the start of
Exim's execution. Otherwise, if there's something it wants to log before it has
read the configuration file (for example, an error in the configuration file)
it will not use the path you want, and may not be able to log at all.
log_file_path = $spool_directory/log/%slog
-If you do not specify anything at build time or run time, or if you unset the
-option at run time (i.e. "log_file_path = "), that is where the logs are
+If you do not specify anything at build time or runtime, or if you unset the
+option at runtime (i.e. "log_file_path = "), that is where the logs are
written.
-A log file path may also contain "%D" or "%M" if datestamped log file names are
+A log file path may also contain "%D" or "%M" if datestamped log filenames are
in use - see section 52.3 below.
Here are some examples of possible settings:
SMTP RCPT commands in one transaction) the second and subsequent addresses are
flagged with "->" instead of "=>". When two or more messages are delivered down
a single SMTP connection, an asterisk follows the IP address in the log lines
-for the second and subsequent messages.
+for the second and subsequent messages. When two or more messages are delivered
+down a single TLS connection, the DNS and some TLS-related information logged
+for the first message delivered will not be present in the log lines for the
+second and subsequent messages. TLS cipher information is still available.
When delivery is done in cutthrough mode it is flagged with ">>" and the log
line precedes the reception line, since cutthrough waits for a possible
command list for "no mail in SMTP session"
CV certificate verification status
D duration of "no mail in SMTP session"
+DKIM domain verified in incoming message
DN distinguished name from peer certificate
DS DNSSEC secured lookups
DT on => lines: time taken for a delivery
F sender address (on delivery lines)
H host name and IP address
I local interface used
-K CHUNKING extension used
id message id for incoming message
+K CHUNKING extension used
+L on <= and => lines: PIPELINING extension used
+M8S 8BITMIME status for incoming message
P on <= lines: protocol used
on => and ** lines: return path
PRDR PRDR extension used
on "Completed" lines: time spent on queue
R on <= lines: reference for local bounce
on => >> ** and == lines: router name
+RT on <= lines: time taken for reception
S size of message in bytes
SNI server name indication from TLS client hello
ST shadow transport name
T on <= lines: message subject (topic)
+TFO connection took advantage of TCP Fast Open
on => ** and == lines: transport name
U local user or RFC 1413 identity
X TLS cipher suite
failed. The delivery was discarded.
+ * DKIM: d= Verbose results of a DKIM verification attempt, if enabled for
+ logging and the message has a DKIM signature header.
+
52.15 Reducing or increasing what is logged
-------------------------------------------
*delay_delivery immediate delivery delayed
deliver_time time taken to perform delivery
delivery_size add S=nnn to => lines
+*dkim DKIM verified domain on <= lines
+ dkim_verbose separate full DKIM verification result line, per signature
*dnslist_defer defers of DNS list (aka RBL) lookups
dnssec DNSSEC secured lookups
*etrn ETRN commands
incoming_interface local interface on <= and => lines
incoming_port remote port on <= lines
*lost_incoming_connection as it says (includes timeouts)
+ millisec millisecond timestamps and RT,QT,DT,D times
outgoing_interface local interface on => lines
outgoing_port add remote port to => lines
*queue_run start and end queue runs
queue_time time on queue for one recipient
queue_time_overall time on queue for whole message
pid Exim process id
+ pipelining PIPELINING use, on <= and => lines
proxy proxy address on <= and => lines
+ receive_time time taken to receive message
received_recipients recipients on <= lines
received_sender sender on <= lines
*rejected_header header contents on reject log
* deliver_time: For each delivery, the amount of real time it has taken to
perform the actual delivery is logged as DT=<time>, for example, "DT=1s".
+ If millisecond logging is enabled, short times will be shown with greater
+ precision, eg. "DT=0.304s".
* delivery_size: For each delivery, the size of message delivered is added to
the "=>" line, tagged with S=.
+ * dkim: For message acceptance log lines, when an DKIM signature in the
+ header verifies successfully a tag of DKIM is added, with one of the
+ verified domains.
+
+ * dkim_verbose: A log entry is written for each attempted DKIM verification.
+
* dnslist_defer: A log entry is written if an attempt to look up a host in a
DNS black list suffers a temporary error.
* incoming_interface: The interface on which a message was received is added
to the "<=" line as an IP address in square brackets, tagged by I= and
followed by a colon and the port number. The local interface and port are
- also added to other SMTP log lines, for example "SMTP connection from", to
+ also added to other SMTP log lines, for example, "SMTP connection from", to
rejection lines, and (despite the name) to outgoing "=>" and "->" lines.
The latter can be disabled by turning off the outgoing_interface option.
* lost_incoming_connection: A log line is written when an incoming SMTP
connection is unexpectedly dropped.
+ * millisec: Timestamps have a period and three decimal places of finer
+ granularity appended to the seconds value.
+
* outgoing_interface: If incoming_interface is turned on, then the interface
on which a message was sent is added to delivery lines as an I= tag
followed by IP address in square brackets. You can disable this by turning
* pid: The current process id is added to every log line, in square brackets,
immediately after the time and date.
+ * pipelining: A field is added to delivery and accept log lines when the
+ ESMTP PIPELINING extension was used. The field is a single "L".
+
+ On accept lines, where PIPELINING was offered but not used by the client,
+ the field has a minus appended.
+
* queue_run: The start and end of every queue run are logged.
* queue_time: The amount of time the message has been in the queue on the
includes reception time as well as the delivery time for the current
address. This means that it may be longer than the difference between the
arrival and delivery log line times, because the arrival log line is not
- written until the message has been successfully received.
+ written until the message has been successfully received. If millisecond
+ logging is enabled, short times will be shown with greater precision, eg.
+ "QT=1.578s".
* queue_time_overall: The amount of time the message has been in the queue on
the local host is logged as QT=<time> on "Completed" lines, for example,
"QT=3m45s". The clock starts when Exim starts to receive the message, so it
includes reception time as well as the total delivery time.
+ * receive_time: For each message, the amount of real time it has taken to
+ perform the reception is logged as RT=<time>, for example, "RT=1s". If
+ millisecond logging is enabled, short times will be shown with greater
+ precision, eg. "RT=0.204s".
+
* received_recipients: The recipients of a message are listed in the main log
as soon as the message is received. The list appears at the end of the log
line that is written when a message is received, preceded by the word
* tls_certificate_verified: An extra item is added to <= and => log lines
when TLS is in use. The item is "CV=yes" if the peer's certificate was
- verified, and "CV=no" if not.
+ verified using a CA trust anchor, "CA=dane" if using a DNS trust anchor,
+ and "CV=no" if not.
* tls_cipher: When a message is sent or received over an encrypted
connection, the cipher suite used is added to the log line, preceded by X=.
53.15 exim_lock lock a mailbox file
Another utility that might be of use to sites with many MTAs is Tom Kistner's
-exilog. It provides log visualizations across multiple Exim servers. See http:/
-/duncanthrax.net/exilog/ for details.
+exilog. It provides log visualizations across multiple Exim servers. See https:
+//duncanthrax.net/exilog/ for details.
53.1 Finding out what Exim processes are doing (exiwhat)
-------------------------------------
The exiqsumm utility is a Perl script which reads the output of "exim -bp" and
-produces a summary of the messages on the queue. Thus, you use it by running a
+produces a summary of the messages in the queue. Thus, you use it by running a
command such as
exim -bp | exiqsumm
exigrep [-t<n>] [-I] [-l] [-M] [-v] <pattern> [<log file>] ...
-If no log file names are given on the command line, the standard input is read.
+If no log filenames are given on the command line, the standard input is read.
The -t argument specifies a number of seconds. It adds an additional condition
for message selection. Messages that are complete are shown only if they spent
-more than <n> seconds on the queue.
+more than <n> seconds in the queue.
By default, exigrep does case-insensitive matching. The -I option makes it
case-sensitive. This may give a performance improvement when searching large
John Jetmore's exipick utility is included in the Exim distribution. It lists
messages from the queue according to a variety of criteria. For details of
-exipick's facilities, visit the web page at http://www.exim.org/eximwiki/
-ToolExipickManPage or run exipick with the --help option.
+exipick's facilities, run exipick with the --help option.
53.6 Cycling log files (exicyclog)
the script's default, which is to find the setting from Exim's
configuration.
-Each time exicyclog is run the file names get "shuffled down" by one. If the
-main log file name is mainlog (the default) then when exicyclog is run mainlog
+Each time exicyclog is run the filenames get "shuffled down" by one. If the
+main log filename is mainlog (the default) then when exicyclog is run mainlog
becomes mainlog.01, the previous mainlog.01 becomes mainlog.02 and so on, up to
the limit that is set in the script or by the -k option. Log files whose
numbers exceed the limit are discarded. Reject logs are handled similarly.
--------------------------------
A Perl script called eximstats is provided for extracting statistical
-information from log files. The output is either plain text, or HTML. Exim log
-files are also supported by the Lire system produced by the LogReport
-Foundation http://www.logreport.org.
+information from log files. The output is either plain text, or HTML.
The eximstats script has been hacked about quite a bit over time. The latest
version is the result of some extensive revision by Steve Campbell. A lot of
The remainder of the output is in sections that can be independently disabled
or modified by various options. It consists of a summary of deliveries by
transport, histograms of messages received and delivered per time interval
-(default per hour), information about the time messages spent on the queue, a
+(default per hour), information about the time messages spent in the queue, a
list of relayed messages, lists of the top fifty sending hosts, local senders,
destination hosts, and destination local users by count and by volume, and a
list of delivery errors that occurred.
well.
If the native DB interface is in use (USE_DB is set in a compile-time
-configuration file - this is common in free versions of Unix) the two file
-names must be different, because in this mode the Berkeley DB functions create
-a single output file using exactly the name given. For example,
+configuration file - this is common in free versions of Unix) the two filenames
+must be different, because in this mode the Berkeley DB functions create a
+single output file using exactly the name given. For example,
exim_dbmbuild /etc/aliases /etc/aliases.db
suffixes are added to the second argument of exim_dbmbuild, so it can be the
same as the first. This is also the case when the Berkeley functions are used
in compatibility mode (though this is not recommended), because in that case it
-adds a .db suffix to the file name.
+adds a .db suffix to the filename.
If a duplicate key is encountered, the program outputs a warning, and when it
finishes, its return code is 1 rather than zero, unless the -noduperr option is
create a lock file and also to use fcntl() locking on the mailbox, which is the
same as Exim's default. The use of -flock or -fcntl requires that the file be
writeable; the use of -lockfile requires that the directory containing the file
-be writeable. Locking by lock file does not last for ever; Exim assumes that a
+be writeable. Locking by lock file does not last forever; Exim assumes that a
lock file is expired if it is more than 30 minutes old.
The -mbx option can be used with either or both of -fcntl or -flock. It assumes
Eximon*highlight: gray
End
-In order to see the contents of messages on the queue, and to operate on them,
+In order to see the contents of messages in the queue, and to operate on them,
eximon must either be run as root or by an admin user.
The command-line parameters of eximon are passed to eximon.bin and may contain
54.2 The stripcharts
--------------------
-The first stripchart is always a count of messages on the queue. Its name can
+The first stripchart is always a count of messages in the queue. Its name can
be configured by setting QUEUE_STRIPCHART_NAME in the Local/eximon.conf file.
The remaining stripcharts are defined in the configuration script by regular
expression matches on log file entries, making it possible to display, for
----------------------
The bottom section of the monitor window contains a list of all messages that
-are on the queue, which includes those currently being received or delivered,
+are in the queue, which includes those currently being received or delivered,
as well as those awaiting delivery. The size of this subwindow is controlled by
parameters in the configuration file Local/eximon.conf, and the frequency at
which it is updated is controlled by another parameter in the same file - the
an update of the queue display at any time.
When a host is down for some time, a lot of pending mail can build up for it,
-and this can make it hard to deal with other messages on the queue. To help
+and this can make it hard to deal with other messages in the queue. To help
with this situation there is a button next to "Update" called "Hide". If
pressed, a dialogue box called "Hide addresses ending with" is put up. If you
type anything in here and press "Return", the text is added to a chain of such
pressing the "Hide" button.
The queue display contains, for each unhidden queued message, the length of
-time it has been on the queue, the size of the message, the message id, the
+time it has been in the queue, the size of the message, the message id, the
message sender, and the first undelivered recipient, all on one line. If it is
a bounce message, the sender is shown as "<>". If there is more than one
recipient to which the message has not yet been delivered, subsequent ones are
* body: The contents of the spool file containing the body of the message are
displayed in a new text window. There is a default limit of 20,000 bytes to
the amount of data displayed. This can be changed by setting the BODY_MAX
- option at compile time, or the EXIMON_BODY_MAX option at run time.
+ option at compile time, or the EXIMON_BODY_MAX option at runtime.
* deliver message: A call to Exim is made using the -M option to request
delivery of the message. This causes an automatic thaw if the message is
penetrated the Exim (but not the root) account. These options are as follows:
* ALT_CONFIG_PREFIX can be set to a string that is required to match the
- start of any file names used with the -C option. When it is set, these file
- names are also not allowed to contain the sequence "/../". (However, if the
- value of the -C option is identical to the value of CONFIGURE_FILE in Local
- /Makefile, Exim ignores -C and proceeds as usual.) There is no default
+ start of any filenames used with the -C option. When it is set, these
+ filenames are also not allowed to contain the sequence "/../". (However, if
+ the value of the -C option is identical to the value of CONFIGURE_FILE in
+ Local/Makefile, Exim ignores -C and proceeds as usual.) There is no default
setting for ALT_CONFIG_PREFIX.
If the permitted configuration files are confined to a directory to which
obviously more secure if Exim does not run as root except when necessary. For
this reason, a user and group for Exim to use must be defined in Local/Makefile
. These are known as "the Exim user" and "the Exim group". Their values can be
-changed by the run time configuration, though this is not recommended. Often a
+changed by the runtime configuration, though this is not recommended. Often a
user called exim is used, but some sites use mail or another user name
altogether.
unprivileged), Exim must be built to allow group read access to its spool
files.
+By default, regular users are trusted to perform basic testing and
+introspection commands, as themselves. This setting can be tightened by setting
+the commandline_checks_require_admin option. This affects most of the checking
+options, such as -be and anything else -b*.
+
55.10 Spool files
-----------------
is insurance against disk crashes where the directory is lost but the files
themselves are recoverable.
+The file formats may be changed, or new formats added, at any release. Spool
+files are not intended as an interface to other programs and should not be used
+as such.
+
Some people are tempted into editing -D files in order to modify messages. You
need to be extremely careful if you do this; it is not recommended and you are
on your own if you do it. Here are some of the pitfalls:
-J file and uses it to update the -H file before starting the next delivery
attempt.
+Files whose names end with -K or .eml may also be seen in the spool. These are
+temporaries used for DKIM or malware processing, when that is used. They should
+be tidied up by normal operations; any old ones are probably relics of crashes
+and can be removed.
+
56.1 Format of the -H file
--------------------------
-body_linecount <number>
- This records the number of lines in the body of the message, and is always
- present.
+ This records the number of lines in the body of the message, and is present
+ unless -spool_file_wireformat is.
-body_zerocount <number>
If a message was scanned by SpamAssassin, this is present. It records the
value of $spam_score_int.
+-spool_file_wireformat
+
+ The -D file for this message is in wire-format (for ESMTP CHUNKING) rather
+ than Unix-format. The line-ending is CRLF rather than newline. There is
+ still, however, no leading-dot-stuffing.
+
-tls_certificate_verified
A TLS certificate was received from the client that sent this message, and
unqualified domain foundation.
+56.2 Format of the -D file
+--------------------------
+
+The data file is traditionally in Unix-standard format: lines are ended with an
+ASCII newline character. However, when the spool_wireformat main option is used
+some -D files can have an alternate format. This is flagged by a
+-spool_file_wireformat line in the corresponding -H file. The -D file lines
+(not including the first name-component line) are suitable for direct copying
+to the wire when transmitting using the ESMTP CHUNKING option, meaning lower
+processing overhead. Lines are terminated with an ASCII CRLF pair. There is no
+dot-stuffing (and no dot-termination).
+
+
===============================================================================
-57. SUPPORT FOR DKIM (DOMAINKEYS IDENTIFIED MAIL)
+57. DKIM AND SPF
+
+
+57.1 DKIM (DomainKeys Identified Mail)
+--------------------------------------
DKIM is a mechanism by which messages sent by some entity can be provably
linked to a domain which that entity controls. It permits reputation to be
tracked on a per-domain basis, rather than merely upon source IP address. DKIM
-is documented in RFC 4871.
+is documented in RFC 6376.
+
+As DKIM relies on the message being unchanged in transit, messages handled by a
+mailing-list (which traditionally adds to the message) will not match any
+original DKIM signature.
DKIM support is compiled into Exim by default if TLS support is present. It can
be disabled by setting DISABLE_DKIM=yes in Local/Makefile.
standard controls.
Please note that verification of DKIM signatures in incoming mail is turned on
-by default for logging purposes. For each signature in incoming email, exim
-will log a line displaying the most important signature details, and the
-signature status. Here is an example (with line-breaks added for clarity):
+by default for logging (in the <= line) purposes.
+
+Additional log detail can be enabled using the dkim_verbose log_selector. When
+set, for each signature in incoming email, exim will log a line displaying the
+most important signature details, and the signature status. Here is an example
+(with line-breaks added for clarity):
2009-09-09 10:22:28 1MlIRf-0003LU-U3 DKIM:
d=facebookmail.com s=q1-2009b
accept mail from relay sources (internal hosts or authenticated senders).
-57.1 Signing outgoing messages
+57.2 Signing outgoing messages
------------------------------
+For signing to be usable you must have published a DKIM record in DNS. Note
+that RFC 8301 says:
+
+rsa-sha1 MUST NOT be used for signing or verifying.
+
+Signers MUST use RSA keys of at least 1024 bits for all keys.
+Signers SHOULD use RSA keys of at least 2048 bits.
+
+Note also that the key content (the 'p=' field) in the DNS record is different
+between RSA and EC keys; for the former it is the base64 of the ASN.1 for the
+RSA public key (equivalent to the private-key .pem with the header/trailer
+stripped) but for EC keys it is the base64 of the pure key; no ASN.1 wrapping.
+
Signing is enabled by setting private options on the SMTP transport. These
options take (expandable) strings as arguments.
-+--------------------------------------------------+
-|dkim_domain|Use: smtp|Type: string*|Default: unset|
-+--------------------------------------------------+
++-------------------------------------------------+
+|dkim_domain|Use: smtp|Type: string|Default: list*|
++-------------------------------------------------+
-MANDATORY: The domain you want to sign with. The result of this expanded option
-is put into the $dkim_domain expansion variable. If it is empty after
-expansion, DKIM signing is not done.
+The domain(s) you want to sign with. After expansion, this can be a list. Each
+element in turn is put into the $dkim_domain expansion variable while expanding
+the remaining signing options. If it is empty after expansion, DKIM signing is
+not done, and no error will result even if dkim_strict is set.
-+----------------------------------------------------+
-|dkim_selector|Use: smtp|Type: string*|Default: unset|
-+----------------------------------------------------+
++---------------------------------------------------+
+|dkim_selector|Use: smtp|Type: string|Default: list*|
++---------------------------------------------------+
-MANDATORY: This sets the key selector string. You can use the $dkim_domain
-expansion variable to look up a matching selector. The result is put in the
-expansion variable $dkim_selector which may be used in the dkim_private_key
-option along with $dkim_domain.
+This sets the key selector string. After expansion, which can use $dkim_domain,
+this can be a list. Each element in turn is put in the expansion variable
+$dkim_selector which may be used in the dkim_private_key option along with
+$dkim_domain. If the option is empty after expansion, DKIM signing is not done
+for this domain, and no error will result even if dkim_strict is set.
+-------------------------------------------------------+
|dkim_private_key|Use: smtp|Type: string*|Default: unset|
+-------------------------------------------------------+
-MANDATORY: This sets the private key to use. You can use the $dkim_domain and
+This sets the private key to use. You can use the $dkim_domain and
$dkim_selector expansion variables to determine the private key to use. The
result can either
- * be a valid RSA private key in ASCII armor, including line breaks.
+ * be a valid RSA private key in ASCII armor (.pem file), including line
+ breaks
+
+ * with GnuTLS 3.6.0 or OpenSSL 1.1.1 or later, be a valid Ed25519 private key
+ (same format as above)
* start with a slash, in which case it is treated as a file that contains the
- private key.
+ private key
* be "0", "false" or the empty string, in which case the message will not be
signed. This case will not result in an error, even if dkim_strict is set.
+To generate keys under OpenSSL:
+
+openssl genrsa -out dkim_rsa.private 2048
+openssl rsa -in dkim_rsa.private -out /dev/stdout -pubout -outform PEM
+
+Take the base-64 lines from the output of the second command, concatenated, for
+the DNS TXT record. See section 3.6 of RFC6376 for the record specification.
+
+Under GnuTLS:
+
+certtool --generate-privkey --rsa --bits=2048 --password='' -8 --outfile=dkim_rsa.private
+certtool --load-privkey=dkim_rsa.private --pubkey-info
+
+Note that RFC 8301 says:
+
+Signers MUST use RSA keys of at least 1024 bits for all keys.
+Signers SHOULD use RSA keys of at least 2048 bits.
+
+Support for EC keys is being developed under https://datatracker.ietf.org/doc/
+draft-ietf-dcrup-dkim-crypto/. They are considerably smaller than RSA keys for
+equivalent protection. As they are a recent development, users should consider
+dual-signing (by setting a list of selectors, and an expansion for this option)
+for some transition period. The "_CRYPTO_SIGN_ED25519" macro will be defined if
+support is present for EC keys.
+
+OpenSSL 1.1.1 and GnuTLS 3.6.0 can create Ed25519 private keys:
+
+openssl genpkey -algorithm ed25519 -out dkim_ed25519.private
+certtool --generate-privkey --key-type=ed25519 --outfile=dkim_ed25519.private
+
+To produce the required public key value for a DNS record:
+
+openssl pkey -outform DER -pubout -in dkim_ed25519.private | tail -c +13 | base64
+certtool --load_privkey=dkim_ed25519.private --pubkey_info --outder | tail -c +13 | base64
+
+Note that the format of Ed25519 keys in DNS has not yet been decided; this
+release supports both of the leading candidates at this time, a future release
+will probably drop support for whichever proposal loses.
+
++-------------------------------------------------+
+|dkim_hash|Use: smtp|Type: string*|Default: sha256|
++-------------------------------------------------+
+
+Can be set to any one of the supported hash methods, which are:
+
+ * "sha1" - should not be used, is old and insecure
+
+ * "sha256" - the default
+
+ * "sha512" - possibly more secure but less well supported
+
+Note that RFC 8301 says:
+
+rsa-sha1 MUST NOT be used for signing or verifying.
+
++----------------------------------------------------+
+|dkim_identity|Use: smtp|Type: string*|Default: unset|
++----------------------------------------------------+
+
+If set after expansion, the value is used to set an "i=" tag in the signing
+header. The DKIM standards restrict the permissible syntax of this optional tag
+to a mail address, with possibly-empty local part, an @, and a domain identical
+to or subdomain of the "d=" tag value. Note that Exim does not check the value.
+
+-------------------------------------------------+
|dkim_canon|Use: smtp|Type: string*|Default: unset|
+-------------------------------------------------+
-OPTIONAL: This option sets the canonicalization method used when signing a
-message. The DKIM RFC currently supports two methods: "simple" and "relaxed".
-The option defaults to "relaxed" when unset. Note: the current implementation
-only supports using the same canonicalization method for both headers and body.
+This option sets the canonicalization method used when signing a message. The
+DKIM RFC currently supports two methods: "simple" and "relaxed". The option
+defaults to "relaxed" when unset. Note: the current implementation only
+supports signing with the same canonicalization method for both headers and
+body.
+--------------------------------------------------+
|dkim_strict|Use: smtp|Type: string*|Default: unset|
+--------------------------------------------------+
-OPTIONAL: This option defines how Exim behaves when signing a message that
-should be signed fails for some reason. When the expansion evaluates to either
-"1" or "true", Exim will defer. Otherwise Exim will send the message unsigned.
-You can use the $dkim_domain and $dkim_selector expansion variables here.
+This option defines how Exim behaves when signing a message that should be
+signed fails for some reason. When the expansion evaluates to either "1" or
+"true", Exim will defer. Otherwise Exim will send the message unsigned. You can
+use the $dkim_domain and $dkim_selector expansion variables here.
-+--------------------------------------------------------+
-|dkim_sign_headers|Use: smtp|Type: string*|Default: unset|
-+--------------------------------------------------------+
++------------------------------------------------------------+
+|dkim_sign_headers|Use: smtp|Type: string*|Default: see below|
++------------------------------------------------------------+
+
+If set, this option must expand to a colon-separated list of header names.
+Headers with these names, or the absence or such a header, will be included in
+the message signature. When unspecified, the header names listed in RFC4871
+will be used, whether or not each header is present in the message. The default
+list is available for the expansion in the macro "_DKIM_SIGN_HEADERS".
+
+If a name is repeated, multiple headers by that name (or the absence thereof)
+will be signed. The textually later headers in the headers part of the message
+are signed first, if there are multiples.
+
+A name can be prefixed with either an '=' or a '+' character. If an '=' prefix
+is used, all headers that are present with this name will be signed. If a '+'
+prefix if used, all headers that are present with this name will be signed, and
+one signature added for a missing header with the name will be appended.
+
++-------------------------------------------------------+
+|dkim_timestamps|Use: smtp|Type: integer*|Default: unset|
++-------------------------------------------------------+
+
+This option controls the inclusion of timestamp information in the signature.
+If not set, no such information will be included. Otherwise, must be an
+unsigned number giving an offset in seconds from the current time for the
+expiry tag (eg. 1209600 for two weeks); both creation (t=) and expiry (x=) tags
+will be included.
-OPTIONAL: When set, this option must expand to (or be specified as) a
-colon-separated list of header names. Headers with these names will be included
-in the message signature. When unspecified, the header names recommended in
-RFC4871 will be used.
+RFC 6376 lists these tags as RECOMMENDED.
-57.2 Verifying DKIM signatures in incoming mail
+57.3 Verifying DKIM signatures in incoming mail
-----------------------------------------------
-Verification of DKIM signatures in SMTP incoming email is implemented via the
-acl_smtp_dkim ACL. By default, this ACL is called once for each syntactically
-(!) correct signature in the incoming message. A missing ACL definition
-defaults to accept. If any ACL call does not accept, the message is not
-accepted. If a cutthrough delivery was in progress for the message it is
-summarily dropped (having wasted the transmission effort).
+Verification of DKIM signatures in SMTP incoming email is done for all messages
+for which an ACL control dkim_disable_verify has not been set. Performing
+verification sets up information used by the $authresults expansion item.
-To evaluate the signature in the ACL a large number of expansion variables
-containing the signature status and its details are set up during the runtime
-of the ACL.
+The acl_smtp_dkim ACL, which can examine and modify them. By default, this ACL
+is called once for each syntactically(!) correct signature in the incoming
+message. A missing ACL definition defaults to accept. If any ACL call does not
+accept, the message is not accepted. If a cutthrough delivery was in progress
+for the message, that is summarily dropped (having wasted the transmission
+effort).
+
+To evaluate the verification result in the ACL a large number of expansion
+variables containing the signature status and its details are set up during the
+runtime of the ACL.
Calling the ACL only for existing signatures is not sufficient to build more
advanced policies. For that reason, the global option dkim_verify_signers, and
If a domain or identity is listed several times in the (expanded) value of
dkim_verify_signers, the ACL is only called once for that domain or identity.
+If multiple signatures match a domain (or identity), the ACL is called once for
+each matching signature.
+
Inside the acl_smtp_dkim, the following expansion variables are available (from
most to least important):
$dkim_verify_status
- A string describing the general status of the signature. One of
+ Within the DKIM ACL, a string describing the general status of the
+ signature. One of
+ none: There is no signature in the message for the current domain or
identity (as reflected by $dkim_cur_signer).
+ pass: The signature passed verification. It is valid.
+ This variable can be overwritten using an ACL 'set' modifier. This might,
+ for instance, be done to enforce a policy restriction on hash-method or
+ key-size:
+
+ warn condition = ${if eq {$dkim_verify_status}{pass}}
+ condition = ${if eq {${length_3:$dkim_algo}}{rsa}}
+ condition = ${if or {{eq {$dkim_algo}{rsa-sha1}} \
+ {< {$dkim_key_length}{1024}}}}
+ logwrite = NOTE: forcing DKIM verify fail (was pass)
+ set dkim_verify_status = fail
+ set dkim_verify_reason = hash too weak or key too short
+
+ So long as a DKIM ACL is defined (it need do no more than accept), after
+ all the DKIM ACL runs have completed, the value becomes a colon-separated
+ list of the values after each run. This is maintained for the mime, prdr
+ and data ACLs.
+
$dkim_verify_reason
A string giving a little bit more detail when $dkim_verify_status is either
DKIM verification. It may of course also mean that the signature is
forged.
+ This variable can be overwritten, with any value, using an ACL 'set'
+ modifier.
+
$dkim_domain
The signing domain. IMPORTANT: This variable is only populated if there is
$dkim_algo
- The algorithm used. One of 'rsa-sha1' or 'rsa-sha256'.
+ The algorithm used. One of 'rsa-sha1' or 'rsa-sha256'. If running under
+ GnuTLS 3.6.0 or OpenSSL 1.1.1 or later, may also be 'ed25519-sha256'. The
+ "_CRYPTO_SIGN_ED25519" macro will be defined if support is present for EC
+ keys.
+
+ Note that RFC 8301 says:
+
+ rsa-sha1 MUST NOT be used for signing or verifying.
+
+ DKIM signatures identified as having been signed with historic
+ algorithms (currently, rsa-sha1) have permanently failed evaluation
+
+ To enforce this you must have a DKIM ACL which checks this variable and
+ overwrites the $dkim_verify_status variable as discussed above.
$dkim_canon_body
The body canonicalization method. One of 'relaxed' or 'simple'.
-dkim_canon_headers
+$dkim_canon_headers
The header canonicalization method. One of 'relaxed' or 'simple'.
limit was set by the signer, "9999999999999" is returned. This makes sure
that this variable always expands to an integer value.
+ Note: The presence of the signature tag specifying a signing body length is
+ one possible route to spoofing of valid DKIM signatures. A paranoid
+ implementation might wish to regard signature where this variable shows
+ less than the "no limit" return as being invalid.
+
$dkim_created
UNIX timestamp reflecting the date and time when the signature was created.
UNIX timestamp reflecting the date and time when the signer wants the
signature to be treated as "expired". When this was not specified by the
signer, "9999999999999" is returned. This makes it possible to do useful
- integer size comparisons against this value.
+ integer size comparisons against this value. Note that Exim does not check
+ this value.
$dkim_headernames
Number of bits in the key.
+ Note that RFC 8301 says:
+
+ Verifiers MUST NOT consider signatures using RSA keys of
+ less than 1024 bits as valid signatures.
+
+ To enforce this you must have a DKIM ACL which checks this variable and
+ overwrites the $dkim_verify_status variable as discussed above. As EC keys
+ are much smaller, the check should only do this for RSA keys.
+
In addition, two ACL conditions are provided:
dkim_signers
above for more information of what they mean.
+57.4 SPF (Sender Policy Framework)
+----------------------------------
+
+SPF is a mechanism whereby a domain may assert which IP addresses may transmit
+messages with its domain in the envelope from, documented by RFC 7208. For more
+information on SPF see http://www.openspf.org.
+
+Messages sent by a system not authorised will fail checking of such assertions.
+This includes retransmissions done by traditional forwarders.
+
+SPF verification support is built into Exim if SUPPORT_SPF=yes is set in Local/
+Makefile. The support uses the libspf2 library https://www.libspf2.org/. There
+is no Exim involvement in the transmission of messages; publishing certain DNS
+records is all that is required.
+
+For verification, an ACL condition and an expansion lookup are provided.
+Performing verification sets up information used by the $authresults expansion
+item.
+
+The ACL condition "spf" can be used at or after the MAIL ACL. It takes as an
+argument a list of strings giving the outcome of the SPF check, and will
+succeed for any matching outcome. Valid strings are:
+
+pass
+
+ The SPF check passed, the sending host is positively verified by SPF.
+
+fail
+
+ The SPF check failed, the sending host is NOT allowed to send mail for the
+ domain in the envelope-from address.
+
+softfail
+
+ The SPF check failed, but the queried domain can't absolutely confirm that
+ this is a forgery.
+
+none
+
+ The queried domain does not publish SPF records.
+
+neutral
+
+ The SPF check returned a "neutral" state. This means the queried domain has
+ published a SPF record, but wants to allow outside servers to send mail
+ under its domain as well. This should be treated like "none".
+
+permerror
+
+ This indicates a syntax error in the SPF record of the queried domain. You
+ may deny messages when this occurs.
+
+temperror
+
+ This indicates a temporary error during all processing, including Exim's
+ SPF processing. You may defer messages when this occurs.
+
+You can prefix each string with an exclamation mark to invert its meaning, for
+example "!fail" will match all results but "fail". The string list is evaluated
+left-to-right, in a short-circuit fashion.
+
+Example:
+
+deny spf = fail
+ message = $sender_host_address is not allowed to send mail from \
+ ${if def:sender_address_domain \
+ {$sender_address_domain}{$sender_helo_name}}. \
+ Please see http://www.openspf.org/Why?scope=\
+ ${if def:sender_address_domain {mfrom}{helo}};\
+ identity=${if def:sender_address_domain \
+ {$sender_address}{$sender_helo_name}};\
+ ip=$sender_host_address
+
+When the spf condition has run, it sets up several expansion variables:
+
+$spf_header_comment
+
+ This contains a human-readable string describing the outcome of the SPF
+ check. You can add it to a custom header or use it for logging purposes.
+
+$spf_received
+
+ This contains a complete Received-SPF: header that can be added to the
+ message. Please note that according to the SPF draft, this header must be
+ added at the top of the header list. Please see section 10 on how you can
+ do this.
+
+ Note: in case of "Best-guess" (see below), the convention is to put this
+ string in a header called X-SPF-Guess: instead.
+
+$spf_result
+
+ This contains the outcome of the SPF check in string form, one of pass,
+ fail, softfail, none, neutral, permerror or temperror.
+
+$spf_result_guessed
+
+ This boolean is true only if a best-guess operation was used and required
+ in order to obtain a result.
+
+$spf_smtp_comment
+
+ This contains a string that can be used in a SMTP response to the calling
+ party. Useful for "fail".
+
+In addition to SPF, you can also perform checks for so-called "Best-guess".
+Strictly speaking, "Best-guess" is not standard SPF, but it is supported by the
+same framework that enables SPF capability. Refer to http://www.openspf.org/FAQ
+/Best_guess_record for a description of what it means.
+
+To access this feature, simply use the spf_guess condition in place of the spf
+one. For example:
+
+deny spf_guess = fail
+ message = $sender_host_address doesn't look trustworthy to me
+
+In case you decide to reject messages based on this check, you should note that
+although it uses the same framework, "Best-guess" is not SPF, and therefore you
+should not mention SPF at all in your reject message.
+
+When the spf_guess condition has run, it sets up the same expansion variables
+as when spf condition is run, described above.
+
+Additionally, since Best-guess is not standardized, you may redefine what
+"Best-guess" means to you by redefining the main configuration spf_guess
+option. For example, the following:
+
+spf_guess = v=spf1 a/16 mx/16 ptr ?all
+
+would relax host matching rules to a broader network range.
+
+A lookup expansion is also available. It takes an email address as the key and
+an IP address as the database:
+
+ ${lookup {username@domain} spf {ip.ip.ip.ip}}
+
+The lookup will return the same result strings as can appear in $spf_result
+(pass,fail,softfail,neutral,none,err_perm,err_temp). Currently, only IPv4
+addresses are supported.
+
+
===============================================================================
58. PROXIES
"Proxy Protocol" to speak to it. To include this support, include
"SUPPORT_PROXY=yes" in Local/Makefile.
-It was built on specifications from: (http://haproxy.1wt.eu/download/1.5/doc/
-proxy-protocol.txt). That URL was revised in May 2014 to version 2 spec: (http:
-//git.1wt.eu/web?p=haproxy.git;a=commitdiff;h=afb768340c9d7e50d8e).
+It was built on the HAProxy specification, found at https://www.haproxy.org/
+download/1.8/doc/proxy-protocol.txt.
The purpose of this facility is so that an application load balancer, such as
HAProxy, can sit in front of several Exim servers to distribute load. Exim uses
IP of host being proxied or IP of remote interface of proxy
proxy_external_port
Port of host being proxied or Port on remote interface of proxy
-proxy_local_address
+proxy_local_address
IP of proxy server inbound or IP of local interface of proxy
-proxy_local_port
+proxy_local_port
Port of proxy server inbound or Port on local interface of proxy
-proxy_session boolean: SMTP connection via proxy
+proxy_session boolean: SMTP connection via proxy
If $proxy_session is set but $proxy_external_address is empty there was a
protocol error.
${utf8_localpart_to_alabel:str}
${utf8_localpart_from_alabel:str}
-ACLs may use the following modifier:
+The RCPT ACL may use the following modifier:
control = utf8_downconvert
control = utf8_downconvert/<value>
If mua_wrapper is set, the utf8_downconvert control is initially set to -1.
+The smtp transport has an option utf8_downconvert. If set it must expand to one
+of the three values described above, and it overrides any previously set value.
+
There is no explicit support for VRFY and EXPN. Configurations supporting these
should inspect $smtp_command_argument for an SMTPUTF8 argument.
The string is converted from the charset specified by the "headers charset"
command (in a filter file) or headers_charset main configuration option
(otherwise), to the modified UTF-7 encoding specified by RFC 2060, with the
-following exception: All occurences of <sep> (which has to be a single
+following exception: All occurrences of <sep> (which has to be a single
character) are replaced with periods ("."), and all periods and slashes that
are not <sep> and are not in the <specials> string are BASE64 encoded.
The current list of events is:
+dane:fail after transport per connection
msg:complete after main per message
msg:delivery after transport per recipient
msg:rcpt:host:defer after transport per recipient per host
msg:rcpt:defer after transport per recipient
msg:host:defer after transport per attempt
-msg:fail:delivery after main per recipient
+msg:fail:delivery after transport per recipient
msg:fail:internal after main per recipient
tcp:connect before transport per connection
tcp:close after transport per connection
or after the action is associates with. Those which fire before can be used to
affect that action (more on this below).
+The third column in the table above says what section of the configuration
+should define the event action.
+
An additional variable, $event_data, is filled with information varying with
the event type:
+dane:fail failure reason
msg:delivery smtp confirmation message
+msg:fail:internal failure reason
+msg:fail:delivery smtp error message
msg:rcpt:host:defer error string
msg:rcpt:defer error string
msg:host:defer error string
The expansion of the event_action option should normally return an empty
string. Should it return anything else the following will be forced:
-msg:delivery (ignored)
-msg:host:defer (ignored)
-msg:fail:delivery (ignored)
tcp:connect do not connect
-tcp:close (ignored)
tls:cert refuse verification
smtp:connect close connection
-No other use is made of the result string.
+All other message types ignore the result string, and no other use is made of
+it.
For a tcp:connect event, if the connection is being made to a proxy then the
address and port variables will be that of the proxy and not the target system.
, src/auths, or src/lookups); add a line for the new driver or lookup type
and add it to the definition of OBJ.
- 7. Create newdriver.h and newdriver.c in the appropriate sub-directory of src.
+ 7. Edit OS/Makefile-Base adding a .o file for the predefined-macros, to the
+ definition of OBJ_MACRO. Add a set of line to do the compile also.
+
+ 8. Create newdriver.h and newdriver.c in the appropriate sub-directory of src.
- 8. Edit scripts/MakeLinks and add commands to link the .h and .c files as for
+ 9. Edit scripts/MakeLinks and add commands to link the .h and .c files as for
other drivers and lookups.
Then all you need to do is write the code! A good way to start is to make a
* Exim Monitor *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
uschar *dkim_signing_domain = NULL;
uschar *dkim_signing_selector = NULL;
uschar *dkim_verify_signers = US"$dkim_signers";
-BOOL dkim_collect_input = FALSE;
+unsigned dkim_collect_input = 0;
BOOL dkim_disable_verify = FALSE;
#endif
int dsn_ret = 0;
uschar *dsn_envid = NULL;
+struct global_flags f = {
+ .sender_local = FALSE,
+};
+
#ifdef WITH_CONTENT_SCAN
int fake_response = OK;
#endif
int received_count = 0;
uschar *received_protocol = NULL;
-int received_time = 0;
+struct timeval received_time = { 0, 0 };
int recipients_count = 0;
recipient_item *recipients_list = NULL;
int recipients_list_max = 0;
uschar *sender_host_name = NULL;
int sender_host_port = 0;
uschar *sender_ident = NULL;
-BOOL sender_local = FALSE;
BOOL sender_set_untrusted = FALSE;
uschar *smtp_active_hostname = NULL;
BOOL timestamps_utc = FALSE;
tls_support tls_in = {
- -1, /* tls_active */
+ {-1}, /* tls_active */
0, /* bits */
FALSE, /* tls_certificate_verified */
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
FALSE, /* dane_verified */
0, /* tlsa_usage */
#endif
#include <pcre.h>
-/* Includes from the main source of Exim. We need to have MAXPACKET defined for
-the benefit of structs.h. One of these days I should tidy up this interface so
-that this kind of kludge isn't needed. */
+/* Includes from the main source of Exim. One of these days I should tidy up
+this interface so that this kind of kludge isn't needed. */
-#define MAXPACKET 1024
+#ifndef NS_MAXMSG
+# define NS_MAXMSG 65535
+#endif
typedef void hctx;
#include "config.h"
#include "local_scan.h"
#include "structs.h"
+#include "blob.h"
#include "globals.h"
#include "dbstuff.h"
#include "functions.h"
* Exim Monitor *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* This module contains code for scanning the main log,
if (strstric(buffer, US"frozen", FALSE) != NULL)
{
queue_item *qq = find_queue(id, queue_noop, 0);
- if (qq != NULL)
- {
- if (strstric(buffer, US"unfrozen", FALSE) != NULL)
- qq->frozen = FALSE;
- else qq->frozen = TRUE;
- }
+ if (qq)
+ qq->frozen = strstric(buffer, US"unfrozen", FALSE) == NULL;
}
/* Notice defer messages, and add the destination if it
* Exim Monitor *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Get the buffer for storing the string for the log display. */
-log_display_buffer = (uschar *)store_malloc(log_buffer_size);
+log_display_buffer = US store_malloc(log_buffer_size);
log_display_buffer[0] = 0;
/* Initialize the data structures for the stripcharts */
* Exim Monitor *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
bodyAction(Widget w, XtPointer client_data, XtPointer call_data)
{
int i;
-Widget text = text_create((uschar *)client_data, text_depth);
+Widget text = text_create(US client_data, text_depth);
FILE *f = NULL;
w = w; /* Keep picky compilers happy */
for (i = 0; i < (spool_is_split? 2:1); i++)
{
uschar * fname;
- message_subdir[0] = i != 0 ? ((uschar *)client_data)[5] : 0;
+ message_subdir[0] = i != 0 ? (US client_data)[5] : 0;
fname = spool_fname(US"input", message_subdir, US client_data, US"-D");
if ((f = fopen(CS fname, "r")))
break;
if (rc == 0 && Ustrcmp(action + Ustrlen(action) - 4, "-Mes") == 0)
{
queue_item *q = find_queue(id, queue_noop, 0);
- if (q != NULL)
+ if (q)
{
- if (q->sender != NULL) store_free(q->sender);
+ if (q->sender) store_free(q->sender);
q->sender = store_malloc(Ustrlen(address_arg) + 1);
Ustrcpy(q->sender, address_arg);
}
{
w = w; /* Keep picky compilers happy */
call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-v -M", US"");
+ActOnMessage(US client_data, US"-v -M", US"");
}
{
w = w; /* Keep picky compilers happy */
call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-Mf", US"");
+ActOnMessage(US client_data, US"-Mf", US"");
}
{
w = w; /* Keep picky compilers happy */
call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-Mt", US"");
+ActOnMessage(US client_data, US"-Mt", US"");
}
{
w = w; /* Keep picky compilers happy */
call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-v -Mg", US"");
+ActOnMessage(US client_data, US"-v -Mg", US"");
}
{
w = w; /* Keep picky compilers happy */
call_data = call_data;
-ActOnMessage((uschar *)client_data, US"-Mrm", US"");
+ActOnMessage(US client_data, US"-Mrm", US"");
}
{
uschar buffer[256];
header_line *h, *next;
-Widget text = text_create((uschar *)client_data, text_depth);
+Widget text = text_create(US client_data, text_depth);
void *reset_point;
w = w; /* Keep picky compilers happy */
reset_point = store_get(0);
-sprintf(CS buffer, "%s-H", (uschar *)client_data);
+sprintf(CS buffer, "%s-H", US client_data);
if (spool_read_header(buffer, TRUE, FALSE) != spool_read_OK)
{
if (errno == ERRNO_SPOOLFORMAT)
if (sender_address != NULL)
{
- text_showf(text, "%s sender: <%s>\n", sender_local? "Local" : "Remote",
+ text_showf(text, "%s sender: <%s>\n", f.sender_local ? "Local" : "Remote",
sender_address);
}
* Exim Monitor *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
address is lowercased to start with, unless it begins with
"*", which it does for error messages. */
-dest_item *find_dest(queue_item *q, uschar *name, int action, BOOL caseless)
+dest_item *
+find_dest(queue_item *q, uschar *name, int action, BOOL caseless)
{
dest_item *dd;
dest_item **d = &(q->destinations);
* Clean up a dead queue item *
*************************************************/
-static void clean_up(queue_item *p)
+static void
+clean_up(queue_item *p)
{
dest_item *dd = p->destinations;
while (dd != NULL)
* Set up new queue item *
*************************************************/
-static queue_item *set_up(uschar *name, int dir_char)
+static queue_item *
+set_up(uschar *name, int dir_char)
{
int i, rc, save_errno;
struct stat statdata;
q->next = q->prev = NULL;
q->destinations = NULL;
-Ustrcpy(q->name, name);
+Ustrncpy(q->name, name, sizeof(q->name));
q->seen = TRUE;
q->frozen = FALSE;
q->dir_char = dir_char;
else
{
- q->update_time = q->input_time = received_time;
+ q->update_time = q->input_time = received_time.tv_sec;
if ((p = strstric(sender_address+1, qualify_domain, FALSE)) != NULL &&
*(--p) == '@') *p = 0;
}
}
else
{
- deliver_freeze = FALSE;
+ f.deliver_freeze = FALSE;
sender_address = msg;
recipients_count = 0;
}
/* Now set up the remaining data. */
-q->frozen = deliver_freeze;
+q->frozen = f.deliver_freeze;
-if (sender_set_untrusted)
+if (f.sender_set_untrusted)
{
if (sender_address[0] == 0)
{
-queue_item *find_queue(uschar *name, int action, int dir_char)
+queue_item *
+find_queue(uschar *name, int action, int dir_char)
{
int first = 0;
int last = queue_index_size - 1;
* Exim Monitor *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "mytypes.h"
#include <string.h>
#include <stdlib.h>
+#include "version.h"
+
extern uschar *version_string;
extern uschar *version_date;
version_string = US"2.06";
+#ifdef EXIM_BUILD_DATE_OVERRIDE
+/* Reproducible build support; build tooling should have given us something looking like
+ * "25-Feb-2017 20:15:40" in EXIM_BUILD_DATE_OVERRIDE based on $SOURCE_DATE_EPOCH in environ
+ * per <https://reproducible-builds.org/specs/source-date-epoch/>
+ */
+version_date = US malloc(32);
+version_date[0] = 0;
+Ustrncat(version_date, EXIM_BUILD_DATE_OVERRIDE, 31);
+
+#else
Ustrcpy(today, __DATE__);
if (today[4] == ' ') i = 1;
today[3] = today[6] = '-';
-version_date = (uschar *)malloc(32);
+version_date = US malloc(32);
version_date[0] = 0;
Ustrncat(version_date, today+4+i, 3-i);
Ustrncat(version_date, today, 4);
Ustrncat(version_date, today+7, 4);
Ustrcat(version_date, " ");
Ustrcat(version_date, __TIME__);
+#endif
}
/* End of em_version.c */
#! /bin/sh
+LC_ALL=C
+export LC_ALL
# Shell script to build Makefile in a build directory. It must be called
# from inside the directory. It does its own checking of when to rebuild; it
# just got too horrendous to get it right in "make", because of the optionally
# existing configuration files.
#
-# Copyright (c) The Exim Maintainers 2016
+# Copyright (c) The Exim Maintainers 1995 - 2018
# First off, get the OS type, and check that there is a make file for it.
mf=Makefile
mft=$mf-t
mftt=$mf-tt
+mftepcp=$mf-tepcp
+mftepcp2=$mf-tepcp2
look_mf=lookups/Makefile
look_mf_pre=${look_mf}.predynamic
# Ensure the temporary does not exist and start the new one by setting
# the OSTYPE and ARCHTYPE variables.
-rm -f $mft $mftt $look_mf-t
+rm -f $mft $mftt $mftepcp $mftepcp2 $look_mf-t
(echo "OSTYPE=$ostype"; echo "ARCHTYPE=$archtype"; echo "") > $mft || exit 1
# Now concatenate the files to the temporary file. Copy the files using sed to
| sed 's/^TMPDIR=/EXIM_&/' \
>> $mft || exit 1
+# handle PKG_CONFIG_PATH because we need it in our env, and we want to handle
+# wildcards; note that this logic means all setting _appends_ values, never
+# replacing; if that's a problem, we can revisit.
+sed -n "s/^[$st]*PKG_CONFIG_PATH[$st]*[+]*=[$st]*//p" $mft | \
+ sed "s/[$st]*\$//" >> $mftepcp
+if test -s ./$mftepcp
+then
+ # expand any wildcards and strip spaces, to make it a real PATH-like variable
+ ( IFS=":${IFS-$st}"; for P in `cat ./$mftepcp`; do echo "$P"; done ) | xargs | sed "s/[$st]/:/g" >./$mftepcp2
+ sed "s/^/PKG_CONFIG_PATH='/" < ./$mftepcp2 | sed "s/\$/'/" > ./$mftepcp
+ . ./$mftepcp
+ export PKG_CONFIG_PATH
+ egrep -v "^[$st]*PKG_CONFIG_PATH[$st]*=" ./$mft > ./$mftt
+ rm -f ./$mft
+ (
+ echo "# Collapsed PKG_CONFIG_PATH in build-prep:"
+ sed "s/'//g" ./$mftepcp
+ echo "# End of collapsed PKG_CONFIG_PATH"
+ echo ""
+ cat ./$mftt
+ ) > ./$mft
+ rm -f ./$mftt
+fi
+rm -f ./$mftepcp ./$mftepcp2
+
# handle pkg-config
# beware portability of extended regexps with sed.
-
egrep "^[$st]*(AUTH|LOOKUP)_[A-Z0-9_]*[$st]*=[$st]*" $mft | \
sed "s/[$st]*=/='/" | \
sed "s/\$/'/" > $mftt
echo "CFLAGS += $tls_include"
echo "LDFLAGS += $tls_libs"
else
- echo "CFLAGS += $(libgcrypt-config --cflags)"
- echo "LDFLAGS += $(libgcrypt-config --libs)"
+ echo "CFLAGS += `libgcrypt-config --cflags`"
+ echo "LDFLAGS += `libgcrypt-config --libs`"
fi
fi
fi
fi
rm -f os.h
-# In order to accomodate for the fudge below, copy the file instead of
+# In order to accommodate for the fudge below, copy the file instead of
# symlinking it. Otherwise we pollute the clean copy with the fudge.
cp -p ../OS/os.h-$os os.h || exit 1
# Script to build links for all the exim source files from the system-
# specific build directory. It should be run from within that directory.
#
-# Copyright (c) The Exim Maintainers 2016
+# Copyright (c) The Exim Maintainers 1995 - 2018
test ! -d ../src && \
echo "*** $0 should be run in a system-specific subdirectory." && \
mkdir pdkim
cd pdkim
for f in README Makefile crypt_ver.h pdkim.c \
- pdkim.h hash.c hash.h rsa.c rsa.h blob.h
+ pdkim.h hash.c hash.h signing.c signing.h blob.h
do
ln -s ../../src/pdkim/$f $f
done
setenv.c environment.c \
sieve.c smtp_in.c smtp_out.c spool_in.c spool_out.c std-crypto.c store.c \
string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-gnu.c tls-openssl.c \
- tod.c transport.c tree.c verify.c version.c dkim.c dkim.h dmarc.c dmarc.h \
- valgrind.h memcheck.h
+ tod.c transport.c tree.c verify.c version.c \
+ dkim.c dkim.h dkim_transport.c dmarc.c dmarc.h \
+ valgrind.h memcheck.h \
+ macro_predef.c macro_predef.h
do
ln -s ../src/$f $f
done
done
# EXPERIMENTAL_*
-for f in bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-gnu.c dane-openssl.c \
+for f in arc.c bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \
danessl.h imap_utf7.c spf.c spf.h srs.c srs.h utf8.c
do
ln -s ../src/$f $f
#!/bin/sh
-# Copyright (c) The Exim Maintainers 2016
+# Copyright (c) The Exim Maintainers 1995 - 2018
set -e
LC_ALL=C
# Read version information that was generated by a previous run of
# this script, or during the release process.
-if [ -f ./version.sh ]
-then . ./version.sh
-elif [ -f ../src/version.sh ]
-then . ../src/version.sh
-fi
-
-# If this tree is a git working directory, use that to get version information.
-
-if [ -d ../../.git ] || [ "$1" = "release" ]
-then
- # Modify the output of git describe into separate parts for
- # the name "exim" and the release and variant versions.
- # Put a dot in the version number and remove a spurious g.
- if [ "$2" ]
- then
- description=$(git describe "$2")
- else
- description=$(git describe --dirty=-XX --match 'exim-4*')
- fi
- set $(echo "$description" | sed 's|-| |;s|_|.|;s|[-_]| _|;s|-g|-|')
- # Only update if we need to
- if [ "$2 $3" != "$EXIM_RELEASE_VERSION $EXIM_VARIANT_VERSION" ]
- then
- EXIM_RELEASE_VERSION="$2"
- EXIM_VARIANT_VERSION="$3"
- rm -f version.h
- fi
+if [ -f ./version.sh ]; then
+ . ./version.sh
+elif [ -f ../src/version.sh ]; then
+ . ../src/version.sh
+elif [ -d ../../.git ] || [ -f ../../.git ] || [ "$1" = release ]; then
+ # Modify the output of git describe into separate parts for
+ # the name "exim" and the release and variant versions.
+ # Put a dot in the version number and remove a spurious g.
+ if [ "$2" ]
+ then
+ description=$(git describe "$2")
+ else
+ description=$(git describe --dirty=-XX --match 'exim-4*')
+ fi
+ set $(echo "$description" | sed 's/-/ /; s/-g/-/')
+ # Only update if we need to
+ if [ "$2 $3" != "$EXIM_RELEASE_VERSION $EXIM_VARIANT_VERSION" ]
+ then
+ EXIM_RELEASE_VERSION="$2"
+ EXIM_VARIANT_VERSION="$3"
+ rm -f version.h
+ fi
+else
+ echo "Cannot determine the release number" >&2
+ exit
fi
# If you are maintaining a patched version of Exim, you can either
# create your own version.sh as part of your release process, or you
# can modify EXIM_VARIANT_VERSION at this point in this script.
-case "$EXIM_RELEASE_VERSION" in
-'') echo "*** Your copy of Exim lacks any version information."
- exit 1
-esac
+if test -z "$EXIM_RELEASE_VERSION"; then
+ echo "$0: Your copy of Exim lacks any version information." >&2
+ exit 1
+fi
EXIM_COMPILE_NUMBER=$(expr "${EXIM_COMPILE_NUMBER:-0}" + 1)
echo "$EXIM_COMPILE_NUMBER" >cnumber.h
+# Reproducible builds, accept a build timestamp override from environ per
+# <https://reproducible-builds.org/specs/source-date-epoch/>.
+# We require a fairly modern date(1) command here, which is not portable
+# to some of the systems Exim is built on. That's okay, because the scenarios
+# are:
+# 1) Local postmaster building, not using $SOURCE_DATE_EPOCH, doesn't matter
+# 2) Packaging folks who don't care about reproducible builds
+# 3) Packaging folks who care but are using systems where date Just Works
+# 3) Packaging folks who care and can put a modern date(1) in $PATH
+# 4) Packaging folks who care and can supply us with a clean patch to support
+# their requirements
+# 5) Packaging folks who care but won't do any work to support their strange
+# old systems and want us to do the work for them. We don't care either,
+# they're SOL and have to live without reproducible builds.
+#
+exim_build_date_override=''
+if [ ".${SOURCE_DATE_EPOCH:-}" != "." ]; then
+ fmt='+%d-%b-%Y %H:%M:%S'
+ # Non-reproducible, we use __DATE__ and __TIME__ in C, which respect timezone
+ # (think localtime, not gmtime); for reproduction between systems, UTC makes
+ # more sense and the examples available use UTC without explicitly mandating
+ # it. I think that we can switch behavior and use UTC for reproducible
+ # builds without it causing any problems: nothing really cares about timezone.
+ # GNU date: "date -d @TS"
+ # BSD date: "date -r TS"
+ exim_build_date_override="$(date -u -d "@${SOURCE_DATE_EPOCH}" "$fmt" 2>/dev/null || date -u -r "${SOURCE_DATE_EPOCH}" "$fmt" 2>/dev/null)"
+fi
+
( echo '# automatically generated file - see ../scripts/reversion'
echo EXIM_RELEASE_VERSION='"'"$EXIM_RELEASE_VERSION"'"'
+ test -n "$EXIM_VARIANT_VERSION" && \
echo EXIM_VARIANT_VERSION='"'"$EXIM_VARIANT_VERSION"'"'
echo EXIM_COMPILE_NUMBER='"'"$EXIM_COMPILE_NUMBER"'"'
+ if [ ".${exim_build_date_override:-}" != "." ]; then
+ echo EXIM_BUILD_DATE_OVERRIDE='"'"${exim_build_date_override}"'"'
+ fi
) >version.sh
if [ ! -f version.h ]
then
( echo '/* automatically generated file - see ../scripts/reversion */'
echo '#define EXIM_RELEASE_VERSION "'"$EXIM_RELEASE_VERSION"'"'
+ test -n "$EXIM_VARIANT_VERSION" && \
echo '#define EXIM_VARIANT_VERSION "'"$EXIM_VARIANT_VERSION"'"'
- echo '#define EXIM_VERSION_STR EXIM_RELEASE_VERSION EXIM_VARIANT_VERSION'
+ echo '#ifdef EXIM_VARIANT_VERSION'
+ echo '#define EXIM_VERSION_STR EXIM_RELEASE_VERSION "-" EXIM_VARIANT_VERSION'
+ echo '#else'
+ echo '#define EXIM_VERSION_STR EXIM_RELEASE_VERSION'
+ echo '#endif'
+ if [ ".${exim_build_date_override:-}" != "." ]; then
+ echo '#define EXIM_BUILD_DATE_OVERRIDE "'"${exim_build_date_override}"'"'
+ fi
) >version.h
fi
-echo ">>> version $EXIM_RELEASE_VERSION$EXIM_VARIANT_VERSION #$EXIM_COMPILE_NUMBER"
+echo ">>> version $EXIM_RELEASE_VERSION $EXIM_VARIANT_VERSION #$EXIM_COMPILE_NUMBER"
echo
# least one type of lookup. You should consider whether you want to build
# the Exim monitor or not.
+# If you need to override how pkg-config finds configuration files for
+# installed software, then you can set that here; wildcards will be expanded.
+
+# PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig : /opt/*/lib/pkgconfig
+
#------------------------------------------------------------------------------
# These settings determine which individual router drivers are included in the
# you perform upgrades and revert them. You should consider the benefit of
# embedding the Exim version number into LOOKUP_MODULE_DIR, so that you can
# maintain two concurrent sets of modules.
-#
+#
# *BEWARE*: ability to modify the files in LOOKUP_MODULE_DIR is equivalent to
# the ability to modify the Exim binary, which is often setuid root! The Exim
# developers only intend this functionality be used by OS software packagers
# LOOKUP_IBASE=yes
# LOOKUP_LDAP=yes
# LOOKUP_MYSQL=yes
+# LOOKUP_MYSQL_PC=mariadb
# LOOKUP_NIS=yes
# LOOKUP_NISPLUS=yes
# LOOKUP_ORACLE=yes
# PCRE_LIBS=-lpcre
+#------------------------------------------------------------------------------
+# Uncomment the following line to add DANE support
+# Note: Enabling this unconditionally overrides DISABLE_DNSSEC
+# For DANE under GnuTLS we need an additional library. See TLS_LIBS below.
+# SUPPORT_DANE=yes
+
#------------------------------------------------------------------------------
# Additional libraries and include directories may be required for some
# lookup styles (e.g. LDAP, MYSQL or PGSQL). LOOKUP_LIBS is included only on
# WITH_CONTENT_SCAN=yes
-#------------------------------------------------------------------------------
-# If you're using ClamAV and are backporting fixes to an old version, instead
-# of staying current (which is the more usual approach) then you may need to
-# use an older API which uses a STREAM command, now deprecated, instead of
-# zINSTREAM. If you need to set this, please let the Exim developers know, as
-# if nobody reports a need for it, we'll remove this option and clean up the
-# code. zINSTREAM was introduced with ClamAV 0.95.
-#
-# WITH_OLD_CLAMAV_STREAM=yes
+# If you have content scanning you may wish to only include some of the scanner
+# interfaces. Uncomment any of these lines to remove that code.
+
+# DISABLE_MAL_FFROTD=yes
+# DISABLE_MAL_FFROT6D=yes
+# DISABLE_MAL_DRWEB=yes
+# DISABLE_MAL_FSECURE=yes
+# DISABLE_MAL_SOPHIE=yes
+# DISABLE_MAL_CLAM=yes
+# DISABLE_MAL_AVAST=yes
+# DISABLE_MAL_SOCK=yes
+# DISABLE_MAL_CMDLINE=yes
+
+# These scanners are claimed to be no longer existent.
+
+DISABLE_MAL_AVE=yes
+DISABLE_MAL_KAV=yes
+DISABLE_MAL_MKS=yes
#------------------------------------------------------------------------------
# By default, Exim has support for checking the AD bit in a DNS response, to
# determine if DNSSEC validation was successful. If your system libraries
# do not support that bit, then set DISABLE_DNSSEC to "yes"
-# Note: Enabling EXPERIMENTAL_DANE unconditionally overrides this setting.
+# Note: Enabling SUPPORT_DANE unconditionally overrides this setting.
# DISABLE_DNSSEC=yes
# EXPERIMENTAL_DCC=yes
-# Uncomment the following lines to add SPF support. You need to have libspf2
-# installed on your system (www.libspf2.org). Depending on where it is installed
-# you may have to edit the CFLAGS and LDFLAGS lines.
-
-# EXPERIMENTAL_SPF=yes
-# CFLAGS += -I/usr/local/include
-# LDFLAGS += -lspf2
-
# Uncomment the following lines to add SRS (Sender rewriting scheme) support.
# You need to have libsrs_alt installed on your system (srs.mirtol.com).
# Depending on where it is installed you may have to edit the CFLAGS and
# LDFLAGS += -lsrs_alt
# Uncomment the following line to add DMARC checking capability, implemented
-# using libopendmarc libraries. You must have SPF support enabled also.
+# using libopendmarc libraries. You must have SPF and DKIM support enabled also.
# EXPERIMENTAL_DMARC=yes
+# DMARC_TLD_FILE= /etc/exim/opendmarc.tlds
# CFLAGS += -I/usr/local/include
# LDFLAGS += -lopendmarc
+# Uncomment the following line to add ARC (Authenticated Received Chain)
+# support. You must have SPF and DKIM support enabled also.
+# EXPERIMENTAL_ARC=yes
+
# Uncomment the following lines to add Brightmail AntiSpam support. You need
# to have the Brightmail client SDK installed. Please check the experimental
# documentation for implementation details. You need to edit the CFLAGS and
# CFLAGS += -I/opt/brightmail/bsdk-6.0/include
# LDFLAGS += -lxml2_single -lbmiclient_single -L/opt/brightmail/bsdk-6.0/lib
-# Uncomment the following line to add DANE support
-# Note: Enabling this unconditionally overrides DISABLE_DNSSEC
-# Note: DANE is only supported when using OpenSSL
-# EXPERIMENTAL_DANE=yes
-
# Uncomment the following to include extra information in fail DSN message (bounces)
# EXPERIMENTAL_DSN_INFO=yes
# Uncomment the following line to add queuefile transport support
# EXPERIMENTAL_QUEUEFILE=yes
+# Uncomment the following to add REQUIRETLS support.
+# You must also have SUPPORT_TLS enabled.
+# Ref: https://datatracker.ietf.org/doc/draft-fenton-smtp-require-tls
+# EXPERIMENTAL_REQUIRETLS=yes
+
###############################################################################
# THESE ARE THINGS YOU MIGHT WANT TO SPECIFY #
###############################################################################
# or
# TLS_LIBS=-L/opt/gnu/lib -lgnutls -ltasn1 -lgcrypt
+# For DANE under GnuTLS we need an additional library.
+# TLS_LIBS += -lgnutls-dane
+
# TLS_LIBS is included only on the command for linking Exim itself, not on any
# auxiliary programs. If the include files are not in a standard place, you can
# set TLS_INCLUDE to specify where they are, for example:
# LDFLAGS += -lidn -lidn2
+#------------------------------------------------------------------------------
+# Uncomment the following lines to add SPF support. You need to have libspf2
+# installed on your system (www.libspf2.org). Depending on where it is installed
+# you may have to edit the CFLAGS and LDFLAGS lines.
+
+# SUPPORT_SPF=yes
+# CFLAGS += -I/usr/local/include
+# LDFLAGS += -lspf2
+
+
#------------------------------------------------------------------------------
# Support for authentication via Radius is also available. The Exim support,
# which is intended for use in conjunction with the SMTP AUTH facilities,
# Note that this option adds to the size of the Exim binary, because the
# dynamic loading library is not otherwise included.
+# If libreadline is not in the normal library paths, then because Exim is
+# setuid you'll need to ensure that the correct directory is stamped into
+# the binary so that dlopen will find it.
+# Eg, on macOS/Darwin with a third-party install of libreadline, perhaps:
+
+# EXTRALIBS_EXIM+=-Wl,-rpath,/usr/local/opt/readline/lib
+
#------------------------------------------------------------------------------
# Uncomment this setting to include IPv6 support.
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for handling Access Control Lists (ACLs) */
/* ACL verbs */
static uschar *verbs[] = {
- US"accept",
- US"defer",
- US"deny",
- US"discard",
- US"drop",
- US"require",
- US"warn" };
+ [ACL_ACCEPT] = US"accept",
+ [ACL_DEFER] = US"defer",
+ [ACL_DENY] = US"deny",
+ [ACL_DISCARD] = US"discard",
+ [ACL_DROP] = US"drop",
+ [ACL_REQUIRE] = US"require",
+ [ACL_WARN] = US"warn"
+};
/* For each verb, the conditions for which "message" or "log_message" are used
are held as a bitmap. This is to avoid expanding the strings unnecessarily. For
the code. */
static int msgcond[] = {
- (1<<OK) | (1<<FAIL) | (1<<FAIL_DROP), /* accept */
- (1<<OK), /* defer */
- (1<<OK), /* deny */
- (1<<OK) | (1<<FAIL) | (1<<FAIL_DROP), /* discard */
- (1<<OK), /* drop */
- (1<<FAIL) | (1<<FAIL_DROP), /* require */
- (1<<OK) /* warn */
+ [ACL_ACCEPT] = BIT(OK) | BIT(FAIL) | BIT(FAIL_DROP),
+ [ACL_DEFER] = BIT(OK),
+ [ACL_DENY] = BIT(OK),
+ [ACL_DISCARD] = BIT(OK) | BIT(FAIL) | BIT(FAIL_DROP),
+ [ACL_DROP] = BIT(OK),
+ [ACL_REQUIRE] = BIT(FAIL) | BIT(FAIL_DROP),
+ [ACL_WARN] = BIT(OK)
};
/* ACL condition and modifier codes - keep in step with the table that
#ifdef WITH_CONTENT_SCAN
ACLC_SPAM,
#endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
ACLC_SPF,
ACLC_SPF_GUESS,
#endif
} condition_def;
static condition_def conditions[] = {
- { US"acl", FALSE, FALSE, 0 },
+ [ACLC_ACL] = { US"acl", FALSE, FALSE, 0 },
- { US"add_header", TRUE, TRUE,
- (unsigned int)
- ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
- (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ [ACLC_ADD_HEADER] = { US"add_header", TRUE, TRUE,
+ (unsigned int)
+ ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+ ACL_BIT_PREDATA | ACL_BIT_DATA |
#ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
#endif
- (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
- (1<<ACL_WHERE_DKIM)|
- (1<<ACL_WHERE_NOTSMTP_START)),
+ ACL_BIT_MIME | ACL_BIT_NOTSMTP |
+ ACL_BIT_DKIM |
+ ACL_BIT_NOTSMTP_START),
},
- { US"authenticated", FALSE, FALSE,
- (1<<ACL_WHERE_NOTSMTP)|
- (1<<ACL_WHERE_NOTSMTP_START)|
- (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO),
+ [ACLC_AUTHENTICATED] = { US"authenticated", FALSE, FALSE,
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START |
+ ACL_BIT_CONNECT | ACL_BIT_HELO,
},
#ifdef EXPERIMENTAL_BRIGHTMAIL
- { US"bmi_optin", TRUE, TRUE,
- (1<<ACL_WHERE_AUTH)|
- (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
- (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)|
+ [ACLC_BMI_OPTIN] = { US"bmi_optin", TRUE, TRUE,
+ ACL_BIT_AUTH |
+ ACL_BIT_CONNECT | ACL_BIT_HELO |
+ ACL_BIT_DATA | ACL_BIT_MIME |
# ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
# endif
- (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
- (1<<ACL_WHERE_MAILAUTH)|
- (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
- (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_PREDATA)|
- (1<<ACL_WHERE_NOTSMTP_START),
+ ACL_BIT_ETRN | ACL_BIT_EXPN |
+ ACL_BIT_MAILAUTH |
+ ACL_BIT_MAIL | ACL_BIT_STARTTLS |
+ ACL_BIT_VRFY | ACL_BIT_PREDATA |
+ ACL_BIT_NOTSMTP_START,
},
#endif
- { US"condition", TRUE, FALSE, 0 },
- { US"continue", TRUE, TRUE, 0 },
+ [ACLC_CONDITION] = { US"condition", TRUE, FALSE, 0 },
+ [ACLC_CONTINUE] = { US"continue", TRUE, TRUE, 0 },
/* Certain types of control are always allowed, so we let it through
always and check in the control processing itself. */
- { US"control", TRUE, TRUE, 0 },
+ [ACLC_CONTROL] = { US"control", TRUE, TRUE, 0 },
#ifdef EXPERIMENTAL_DCC
- { US"dcc", TRUE, FALSE,
- (unsigned int)
- ~((1<<ACL_WHERE_DATA)|
+ [ACLC_DCC] = { US"dcc", TRUE, FALSE,
+ (unsigned int)
+ ~(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
# endif
- (1<<ACL_WHERE_NOTSMTP)),
+ ACL_BIT_NOTSMTP),
},
#endif
#ifdef WITH_CONTENT_SCAN
- { US"decode", TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_MIME) },
+ [ACLC_DECODE] = { US"decode", TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME },
#endif
- { US"delay", TRUE, TRUE, (1<<ACL_WHERE_NOTQUIT) },
+ [ACLC_DELAY] = { US"delay", TRUE, TRUE, ACL_BIT_NOTQUIT },
#ifndef DISABLE_DKIM
- { US"dkim_signers", TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_DKIM) },
- { US"dkim_status", TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_DKIM) },
+ [ACLC_DKIM_SIGNER] = { US"dkim_signers", TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
+ [ACLC_DKIM_STATUS] = { US"dkim_status", TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
#endif
#ifdef EXPERIMENTAL_DMARC
- { US"dmarc_status", TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_DATA) },
+ [ACLC_DMARC_STATUS] = { US"dmarc_status", TRUE, FALSE, (unsigned int) ~ACL_BIT_DATA },
#endif
/* Explicit key lookups can be made in non-smtp ACLs so pass
always and check in the verify processing itself. */
- { US"dnslists", TRUE, FALSE, 0 },
+ [ACLC_DNSLISTS] = { US"dnslists", TRUE, FALSE, 0 },
- { US"domains", FALSE, FALSE,
- (unsigned int)
- ~((1<<ACL_WHERE_RCPT)
- |(1<<ACL_WHERE_VRFY)
+ [ACLC_DOMAINS] = { US"domains", FALSE, FALSE,
+ (unsigned int)
+ ~(ACL_BIT_RCPT | ACL_BIT_VRFY
#ifndef DISABLE_PRDR
- |(1<<ACL_WHERE_PRDR)
+ |ACL_BIT_PRDR
#endif
),
},
- { US"encrypted", FALSE, FALSE,
- (1<<ACL_WHERE_NOTSMTP)|
- (1<<ACL_WHERE_CONNECT)|
- (1<<ACL_WHERE_NOTSMTP_START)|
- (1<<ACL_WHERE_HELO),
+ [ACLC_ENCRYPTED] = { US"encrypted", FALSE, FALSE,
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START |
+ ACL_BIT_HELO,
},
- { US"endpass", TRUE, TRUE, 0 },
+ [ACLC_ENDPASS] = { US"endpass", TRUE, TRUE, 0 },
- { US"hosts", FALSE, FALSE,
- (1<<ACL_WHERE_NOTSMTP)|
- (1<<ACL_WHERE_NOTSMTP_START),
+ [ACLC_HOSTS] = { US"hosts", FALSE, FALSE,
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
},
- { US"local_parts", FALSE, FALSE,
- (unsigned int)
- ~((1<<ACL_WHERE_RCPT)
- |(1<<ACL_WHERE_VRFY)
- #ifndef DISABLE_PRDR
- |(1<<ACL_WHERE_PRDR)
- #endif
+ [ACLC_LOCAL_PARTS] = { US"local_parts", FALSE, FALSE,
+ (unsigned int)
+ ~(ACL_BIT_RCPT | ACL_BIT_VRFY
+#ifndef DISABLE_PRDR
+ | ACL_BIT_PRDR
+#endif
),
},
- { US"log_message", TRUE, TRUE, 0 },
- { US"log_reject_target", TRUE, TRUE, 0 },
- { US"logwrite", TRUE, TRUE, 0 },
+ [ACLC_LOG_MESSAGE] = { US"log_message", TRUE, TRUE, 0 },
+ [ACLC_LOG_REJECT_TARGET] = { US"log_reject_target", TRUE, TRUE, 0 },
+ [ACLC_LOGWRITE] = { US"logwrite", TRUE, TRUE, 0 },
#ifdef WITH_CONTENT_SCAN
- { US"malware", TRUE, FALSE,
- (unsigned int)
- ~((1<<ACL_WHERE_DATA)|
+ [ACLC_MALWARE] = { US"malware", TRUE, FALSE,
+ (unsigned int)
+ ~(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
# endif
- (1<<ACL_WHERE_NOTSMTP)),
+ ACL_BIT_NOTSMTP),
},
#endif
- { US"message", TRUE, TRUE, 0 },
+ [ACLC_MESSAGE] = { US"message", TRUE, TRUE, 0 },
#ifdef WITH_CONTENT_SCAN
- { US"mime_regex", TRUE, FALSE, (unsigned int) ~(1<<ACL_WHERE_MIME) },
+ [ACLC_MIME_REGEX] = { US"mime_regex", TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME },
#endif
- { US"queue", TRUE, TRUE,
- (1<<ACL_WHERE_NOTSMTP)|
+ [ACLC_QUEUE] = { US"queue", TRUE, TRUE,
+ ACL_BIT_NOTSMTP |
#ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
#endif
- (1<<ACL_WHERE_DATA),
+ ACL_BIT_DATA,
},
- { US"ratelimit", TRUE, FALSE, 0 },
- { US"recipients", FALSE, FALSE, (unsigned int) ~(1<<ACL_WHERE_RCPT) },
+ [ACLC_RATELIMIT] = { US"ratelimit", TRUE, FALSE, 0 },
+ [ACLC_RECIPIENTS] = { US"recipients", FALSE, FALSE, (unsigned int) ~ACL_BIT_RCPT },
#ifdef WITH_CONTENT_SCAN
- { US"regex", TRUE, FALSE,
- (unsigned int)
- ~((1<<ACL_WHERE_DATA)|
+ [ACLC_REGEX] = { US"regex", TRUE, FALSE,
+ (unsigned int)
+ ~(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
# endif
- (1<<ACL_WHERE_NOTSMTP)|
- (1<<ACL_WHERE_MIME)),
+ ACL_BIT_NOTSMTP |
+ ACL_BIT_MIME),
},
#endif
- { US"remove_header", TRUE, TRUE,
- (unsigned int)
- ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
- (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ [ACLC_REMOVE_HEADER] = { US"remove_header", TRUE, TRUE,
+ (unsigned int)
+ ~(ACL_BIT_MAIL|ACL_BIT_RCPT |
+ ACL_BIT_PREDATA | ACL_BIT_DATA |
#ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
#endif
- (1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
- (1<<ACL_WHERE_NOTSMTP_START)),
+ ACL_BIT_MIME | ACL_BIT_NOTSMTP |
+ ACL_BIT_NOTSMTP_START),
},
- { US"sender_domains", FALSE, FALSE,
- (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
- (1<<ACL_WHERE_HELO)|
- (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
- (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
- (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+ [ACLC_SENDER_DOMAINS] = { US"sender_domains", FALSE, FALSE,
+ ACL_BIT_AUTH | ACL_BIT_CONNECT |
+ ACL_BIT_HELO |
+ ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
+ ACL_BIT_ETRN | ACL_BIT_EXPN |
+ ACL_BIT_STARTTLS | ACL_BIT_VRFY,
},
- { US"senders", FALSE, FALSE,
- (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
- (1<<ACL_WHERE_HELO)|
- (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
- (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
- (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+ [ACLC_SENDERS] = { US"senders", FALSE, FALSE,
+ ACL_BIT_AUTH | ACL_BIT_CONNECT |
+ ACL_BIT_HELO |
+ ACL_BIT_MAILAUTH | ACL_BIT_QUIT |
+ ACL_BIT_ETRN | ACL_BIT_EXPN |
+ ACL_BIT_STARTTLS | ACL_BIT_VRFY,
},
- { US"set", TRUE, TRUE, 0 },
+ [ACLC_SET] = { US"set", TRUE, TRUE, 0 },
#ifdef WITH_CONTENT_SCAN
- { US"spam", TRUE, FALSE,
- (unsigned int)
- ~((1<<ACL_WHERE_DATA)|
+ [ACLC_SPAM] = { US"spam", TRUE, FALSE,
+ (unsigned int) ~(ACL_BIT_DATA |
# ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
# endif
- (1<<ACL_WHERE_NOTSMTP)),
+ ACL_BIT_NOTSMTP),
},
#endif
-#ifdef EXPERIMENTAL_SPF
- { US"spf", TRUE, FALSE,
- (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
- (1<<ACL_WHERE_HELO)|
- (1<<ACL_WHERE_MAILAUTH)|
- (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
- (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY)|
- (1<<ACL_WHERE_NOTSMTP)|
- (1<<ACL_WHERE_NOTSMTP_START),
+#ifdef SUPPORT_SPF
+ [ACLC_SPF] = { US"spf", TRUE, FALSE,
+ ACL_BIT_AUTH | ACL_BIT_CONNECT |
+ ACL_BIT_HELO | ACL_BIT_MAILAUTH |
+ ACL_BIT_ETRN | ACL_BIT_EXPN |
+ ACL_BIT_STARTTLS | ACL_BIT_VRFY |
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
},
- { US"spf_guess", TRUE, FALSE,
- (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)|
- (1<<ACL_WHERE_HELO)|
- (1<<ACL_WHERE_MAILAUTH)|
- (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
- (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY)|
- (1<<ACL_WHERE_NOTSMTP)|
- (1<<ACL_WHERE_NOTSMTP_START),
+ [ACLC_SPF_GUESS] = { US"spf_guess", TRUE, FALSE,
+ ACL_BIT_AUTH | ACL_BIT_CONNECT |
+ ACL_BIT_HELO | ACL_BIT_MAILAUTH |
+ ACL_BIT_ETRN | ACL_BIT_EXPN |
+ ACL_BIT_STARTTLS | ACL_BIT_VRFY |
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START,
},
#endif
- { US"udpsend", TRUE, TRUE, 0 },
+ [ACLC_UDPSEND] = { US"udpsend", TRUE, TRUE, 0 },
/* Certain types of verify are always allowed, so we let it through
always and check in the verify function itself */
- { US"verify", TRUE, FALSE,
- 0
- },
+ [ACLC_VERIFY] = { US"verify", TRUE, FALSE, 0 },
};
CONTROL_NO_PIPELINING,
CONTROL_QUEUE_ONLY,
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ CONTROL_REQUIRETLS,
+#endif
CONTROL_SUBMISSION,
CONTROL_SUPPRESS_LOCAL_FIXUPS,
#ifdef SUPPORT_I18N
} control_def;
static control_def controls_list[] = {
+ /* name has_option forbids */
+[CONTROL_AUTH_UNADVERTISED] =
{ US"allow_auth_unadvertised", FALSE,
- (unsigned)
- ~((1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO))
+ (unsigned)
+ ~(ACL_BIT_CONNECT | ACL_BIT_HELO)
},
#ifdef EXPERIMENTAL_BRIGHTMAIL
- { US"bmi_run", FALSE, 0 },
+[CONTROL_BMI_RUN] =
+ { US"bmi_run", FALSE, 0 },
#endif
- { US"caseful_local_part", FALSE, (unsigned) ~(1<<ACL_WHERE_RCPT) },
- { US"caselower_local_part", FALSE, (unsigned) ~(1<<ACL_WHERE_RCPT) },
- { US"cutthrough_delivery", TRUE, 0 },
- { US"debug", TRUE, 0 },
+[CONTROL_CASEFUL_LOCAL_PART] =
+ { US"caseful_local_part", FALSE, (unsigned) ~ACL_BIT_RCPT },
+[CONTROL_CASELOWER_LOCAL_PART] =
+ { US"caselower_local_part", FALSE, (unsigned) ~ACL_BIT_RCPT },
+[CONTROL_CUTTHROUGH_DELIVERY] =
+ { US"cutthrough_delivery", TRUE, 0 },
+[CONTROL_DEBUG] =
+ { US"debug", TRUE, 0 },
#ifndef DISABLE_DKIM
+[CONTROL_DKIM_VERIFY] =
{ US"dkim_disable_verify", FALSE,
- (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|
+ ACL_BIT_DATA | ACL_BIT_NOTSMTP |
# ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
# endif
- (1<<ACL_WHERE_NOTSMTP_START)
+ ACL_BIT_NOTSMTP_START
},
#endif
#ifdef EXPERIMENTAL_DMARC
+[CONTROL_DMARC_VERIFY] =
{ US"dmarc_disable_verify", FALSE,
- (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+ ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
},
+[CONTROL_DMARC_FORENSIC] =
{ US"dmarc_enable_forensic", FALSE,
- (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+ ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
},
#endif
+[CONTROL_DSCP] =
{ US"dscp", TRUE,
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)|(1<<ACL_WHERE_NOTQUIT)
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START | ACL_BIT_NOTQUIT
},
+[CONTROL_ENFORCE_SYNC] =
{ US"enforce_sync", FALSE,
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
},
/* Pseudo-value for decode errors */
+[CONTROL_ERROR] =
{ US"error", FALSE, 0 },
+[CONTROL_FAKEDEFER] =
{ US"fakedefer", TRUE,
- (unsigned)
- ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
- (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ (unsigned)
+ ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+ ACL_BIT_PREDATA | ACL_BIT_DATA |
#ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
#endif
- (1<<ACL_WHERE_MIME))
+ ACL_BIT_MIME)
},
+[CONTROL_FAKEREJECT] =
{ US"fakereject", TRUE,
- (unsigned)
- ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
- (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ (unsigned)
+ ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+ ACL_BIT_PREDATA | ACL_BIT_DATA |
#ifndef DISABLE_PRDR
- (1<<ACL_WHERE_PRDR)|
+ ACL_BIT_PRDR |
#endif
- (1<<ACL_WHERE_MIME))
+ ACL_BIT_MIME)
},
+[CONTROL_FREEZE] =
{ US"freeze", TRUE,
- (unsigned)
- ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
- (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
- // (1<<ACL_WHERE_PRDR)| /* Not allow one user to freeze for all */
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME))
+ (unsigned)
+ ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+ ACL_BIT_PREDATA | ACL_BIT_DATA |
+ // ACL_BIT_PRDR| /* Not allow one user to freeze for all */
+ ACL_BIT_NOTSMTP | ACL_BIT_MIME)
},
+[CONTROL_NO_CALLOUT_FLUSH] =
{ US"no_callout_flush", FALSE,
- (1<<ACL_WHERE_NOTSMTP)| (1<<ACL_WHERE_NOTSMTP_START)
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
},
+[CONTROL_NO_DELAY_FLUSH] =
{ US"no_delay_flush", FALSE,
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
},
+[CONTROL_NO_ENFORCE_SYNC] =
{ US"no_enforce_sync", FALSE,
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
},
#ifdef WITH_CONTENT_SCAN
+[CONTROL_NO_MBOX_UNSPOOL] =
{ US"no_mbox_unspool", FALSE,
- (unsigned)
- ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
- (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
- // (1<<ACL_WHERE_PRDR)| /* Not allow one user to freeze for all */
- (1<<ACL_WHERE_MIME))
+ (unsigned)
+ ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+ ACL_BIT_PREDATA | ACL_BIT_DATA |
+ // ACL_BIT_PRDR| /* Not allow one user to freeze for all */
+ ACL_BIT_MIME)
},
#endif
+[CONTROL_NO_MULTILINE] =
{ US"no_multiline_responses", FALSE,
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
},
+[CONTROL_NO_PIPELINING] =
{ US"no_pipelining", FALSE,
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_NOTSMTP_START)
+ ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
},
+[CONTROL_QUEUE_ONLY] =
{ US"queue_only", FALSE,
- (unsigned)
- ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|
- (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
- // (1<<ACL_WHERE_PRDR)| /* Not allow one user to freeze for all */
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME))
+ (unsigned)
+ ~(ACL_BIT_MAIL | ACL_BIT_RCPT |
+ ACL_BIT_PREDATA | ACL_BIT_DATA |
+ // ACL_BIT_PRDR| /* Not allow one user to freeze for all */
+ ACL_BIT_NOTSMTP | ACL_BIT_MIME)
+ },
+
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+[CONTROL_REQUIRETLS] =
+ { US"requiretls", FALSE,
+ (unsigned)
+ ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA |
+ ACL_BIT_DATA | ACL_BIT_MIME |
+ ACL_BIT_NOTSMTP)
},
+#endif
+
+[CONTROL_SUBMISSION] =
{ US"submission", TRUE,
- (unsigned)
- ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA))
+ (unsigned)
+ ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA)
},
+[CONTROL_SUPPRESS_LOCAL_FIXUPS] =
{ US"suppress_local_fixups", FALSE,
(unsigned)
- ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
- (1<<ACL_WHERE_NOTSMTP_START))
+ ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA |
+ ACL_BIT_NOTSMTP_START)
},
#ifdef SUPPORT_I18N
- { US"utf8_downconvert", TRUE, 0 }
+[CONTROL_UTF8_DOWNCONVERT] =
+ { US"utf8_downconvert", TRUE, (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY)
+ }
#endif
};
the aim is to make the usual configuration simple. */
static int csa_return_code[] = {
- OK, OK, OK, OK,
- FAIL, FAIL, FAIL, FAIL
+ [CSA_UNKNOWN] = OK,
+ [CSA_OK] = OK,
+ [CSA_DEFER_SRV] = OK,
+ [CSA_DEFER_ADDR] = OK,
+ [CSA_FAIL_EXPLICIT] = FAIL,
+ [CSA_FAIL_DOMAIN] = FAIL,
+ [CSA_FAIL_NOADDR] = FAIL,
+ [CSA_FAIL_MISMATCH] = FAIL
};
static uschar *csa_status_string[] = {
- US"unknown", US"ok", US"defer", US"defer",
- US"fail", US"fail", US"fail", US"fail"
+ [CSA_UNKNOWN] = US"unknown",
+ [CSA_OK] = US"ok",
+ [CSA_DEFER_SRV] = US"defer",
+ [CSA_DEFER_ADDR] = US"defer",
+ [CSA_FAIL_EXPLICIT] = US"fail",
+ [CSA_FAIL_DOMAIN] = US"fail",
+ [CSA_FAIL_NOADDR] = US"fail",
+ [CSA_FAIL_MISMATCH] = US"fail"
};
static uschar *csa_reason_string[] = {
- US"unknown",
- US"ok",
- US"deferred (SRV lookup failed)",
- US"deferred (target address lookup failed)",
- US"failed (explicit authorization required)",
- US"failed (host name not authorized)",
- US"failed (no authorized addresses)",
- US"failed (client address mismatch)"
+ [CSA_UNKNOWN] = US"unknown",
+ [CSA_OK] = US"ok",
+ [CSA_DEFER_SRV] = US"deferred (SRV lookup failed)",
+ [CSA_DEFER_ADDR] = US"deferred (target address lookup failed)",
+ [CSA_FAIL_EXPLICIT] = US"failed (explicit authorization required)",
+ [CSA_FAIL_DOMAIN] = US"failed (host name not authorized)",
+ [CSA_FAIL_NOADDR] = US"failed (no authorized addresses)",
+ [CSA_FAIL_MISMATCH] = US"failed (client address mismatch)"
};
/* Options for the ratelimit condition. Note that there are two variants of
(((var) == RATE_PER_WHAT) ? ((var) = RATE_##new) : ((var) = RATE_PER_CLASH))
static uschar *ratelimit_option_string[] = {
- US"?", US"!", US"per_addr", US"per_byte", US"per_cmd",
- US"per_conn", US"per_mail", US"per_rcpt", US"per_rcpt"
+ [RATE_PER_WHAT] = US"?",
+ [RATE_PER_CLASH] = US"!",
+ [RATE_PER_ADDR] = US"per_addr",
+ [RATE_PER_BYTE] = US"per_byte",
+ [RATE_PER_CMD] = US"per_cmd",
+ [RATE_PER_CONN] = US"per_conn",
+ [RATE_PER_MAIL] = US"per_mail",
+ [RATE_PER_RCPT] = US"per_rcpt",
+ [RATE_PER_ALLRCPTS] = US"per_rcpt"
};
/* Enable recursion between acl_check_internal() and acl_check_condition() */
compatibility. */
if (c == ACLC_SET)
+#ifndef DISABLE_DKIM
+ if ( Ustrncmp(s, "dkim_verify_status", 18) == 0
+ || Ustrncmp(s, "dkim_verify_reason", 18) == 0)
+ {
+ uschar * endptr = s+18;
+
+ if (isalnum(*endptr))
+ {
+ *error = string_sprintf("invalid variable name after \"set\" in ACL "
+ "modifier \"set %s\" "
+ "(only \"dkim_verify_status\" or \"dkim_verify_reason\" permitted)",
+ s);
+ return NULL;
+ }
+ cond->u.varname = string_copyn(s, 18);
+ s = endptr;
+ while (isspace(*s)) s++;
+ }
+ else
+#endif
{
uschar *endptr;
/* Loop for multiple header lines, taking care about continuations */
-for (p = q; *p != 0; )
+for (p = q; *p; p = q)
{
const uschar *s;
uschar * hdr;
for (;;)
{
q = Ustrchr(q, '\n'); /* we know there was a newline */
- if (*(++q) != ' ' && *q != '\t') break;
+ if (*++q != ' ' && *q != '\t') break;
}
/* If the line starts with a colon, interpret the instruction for where to
to the front of it. */
for (s = p; s < q - 1; s++)
- {
if (*s == ':' || !isgraph(*s)) break;
- }
- hdr = string_sprintf("%s%.*s", (*s == ':')? "" : "X-ACL-Warn: ", (int) (q - p), p);
+ hdr = string_sprintf("%s%.*s", *s == ':' ? "" : "X-ACL-Warn: ", (int) (q - p), p);
hlen = Ustrlen(hdr);
/* See if this line has already been added */
- while (*hptr != NULL)
+ while (*hptr)
{
if (Ustrncmp((*hptr)->text, hdr, hlen) == 0) break;
- hptr = &((*hptr)->next);
+ hptr = &(*hptr)->next;
}
/* Add if not previously present */
- if (*hptr == NULL)
+ if (!*hptr)
{
header_line *h = store_get(sizeof(header_line));
h->text = hdr;
h->type = newtype;
h->slen = hlen;
*hptr = h;
- hptr = &(h->next);
+ hptr = &h->next;
}
-
- /* Advance for next header line within the string */
-
- p = q;
}
}
uschar *
fn_hdrs_added(void)
{
-uschar * ret = NULL;
-int size = 0;
-int ptr = 0;
-header_line * h = acl_added_headers;
-uschar * s;
-uschar * cp;
+gstring * g = NULL;
+header_line * h;
-if (!h) return NULL;
-
-do
+for (h = acl_added_headers; h; h = h->next)
{
- s = h->text;
- while ((cp = Ustrchr(s, '\n')) != NULL)
- {
- if (cp[1] == '\0') break;
-
- /* contains embedded newline; needs doubling */
- ret = string_catn(ret, &size, &ptr, s, cp-s+1);
- ret = string_catn(ret, &size, &ptr, US"\n", 1);
- s = cp+1;
- }
- /* last bit of header */
-
- ret = string_catn(ret, &size, &ptr, s, cp-s+1); /* newline-sep list */
+ int i = h->slen;
+ if (h->text[i-1] == '\n') i--;
+ g = string_append_listele_n(g, '\n', h->text, i);
}
-while((h = h->next));
-ret[ptr-1] = '\0'; /* overwrite last newline */
-return ret;
+return g ? g->s : NULL;
}
static void
setup_remove_header(const uschar *hnames)
{
-if (*hnames != 0)
+if (*hnames)
acl_removed_headers = acl_removed_headers
? string_sprintf("%s : %s", acl_removed_headers, hnames)
: string_copy(hnames);
int length = Ustrlen(text) + 1;
log_write(0, LOG_MAIN, "%s", text);
logged = store_malloc(sizeof(string_item) + length);
- logged->text = (uschar *)logged + sizeof(string_item);
+ logged->text = US logged + sizeof(string_item);
memcpy(logged->text, text, length);
logged->next = acl_warn_logged;
acl_warn_logged = logged;
enum { VERIFY_REV_HOST_LKUP, VERIFY_CERT, VERIFY_HELO, VERIFY_CSA, VERIFY_HDR_SYNTAX,
VERIFY_NOT_BLIND, VERIFY_HDR_SNDR, VERIFY_SNDR, VERIFY_RCPT,
- VERIFY_HDR_NAMES_ASCII
+ VERIFY_HDR_NAMES_ASCII, VERIFY_ARC
};
typedef struct {
uschar * name;
unsigned alt_opt_sep; /* >0 Non-/ option separator (custom parser) */
} verify_type_t;
static verify_type_t verify_type_list[] = {
+ /* name value where no-opt opt-sep */
{ US"reverse_host_lookup", VERIFY_REV_HOST_LKUP, ~0, FALSE, 0 },
- { US"certificate", VERIFY_CERT, ~0, TRUE, 0 },
- { US"helo", VERIFY_HELO, ~0, TRUE, 0 },
+ { US"certificate", VERIFY_CERT, ~0, TRUE, 0 },
+ { US"helo", VERIFY_HELO, ~0, TRUE, 0 },
{ US"csa", VERIFY_CSA, ~0, FALSE, 0 },
- { US"header_syntax", VERIFY_HDR_SYNTAX, (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), TRUE, 0 },
- { US"not_blind", VERIFY_NOT_BLIND, (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), TRUE, 0 },
- { US"header_sender", VERIFY_HDR_SNDR, (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), FALSE, 0 },
- { US"sender", VERIFY_SNDR, (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)
- |(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP),
+ { US"header_syntax", VERIFY_HDR_SYNTAX, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+ { US"not_blind", VERIFY_NOT_BLIND, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+ { US"header_sender", VERIFY_HDR_SNDR, ACL_BIT_DATA | ACL_BIT_NOTSMTP, FALSE, 0 },
+ { US"sender", VERIFY_SNDR, ACL_BIT_MAIL | ACL_BIT_RCPT
+ |ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP,
FALSE, 6 },
- { US"recipient", VERIFY_RCPT, (1<<ACL_WHERE_RCPT), FALSE, 0 },
- { US"header_names_ascii", VERIFY_HDR_NAMES_ASCII, (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), TRUE, 0 }
+ { US"recipient", VERIFY_RCPT, ACL_BIT_RCPT, FALSE, 0 },
+ { US"header_names_ascii", VERIFY_HDR_NAMES_ASCII, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+#ifdef EXPERIMENTAL_ARC
+ { US"arc", VERIFY_ARC, ACL_BIT_DATA, FALSE , 0 },
+#endif
};
enum { CALLOUT_DEFER_OK, CALLOUT_NOCACHE, CALLOUT_RANDOM, CALLOUT_USE_SENDER,
CALLOUT_USE_POSTMASTER, CALLOUT_POSTMASTER, CALLOUT_FULLPOSTMASTER,
CALLOUT_MAILFROM, CALLOUT_POSTMASTER_MAILFROM, CALLOUT_MAXWAIT, CALLOUT_CONNECT,
- CALLOUT_TIME
+ CALLOUT_HOLD, CALLOUT_TIME /* TIME must be last */
};
typedef struct {
uschar * name;
BOOL timeval; /* Has a time value */
} callout_opt_t;
static callout_opt_t callout_opt_list[] = {
+ /* name value flag has-opt has-time */
{ US"defer_ok", CALLOUT_DEFER_OK, 0, FALSE, FALSE },
{ US"no_cache", CALLOUT_NOCACHE, vopt_callout_no_cache, FALSE, FALSE },
{ US"random", CALLOUT_RANDOM, vopt_callout_random, FALSE, FALSE },
{ US"mailfrom", CALLOUT_MAILFROM, 0, TRUE, FALSE },
{ US"maxwait", CALLOUT_MAXWAIT, 0, TRUE, TRUE },
{ US"connect", CALLOUT_CONNECT, 0, TRUE, TRUE },
+ { US"hold", CALLOUT_HOLD, vopt_callout_hold, FALSE, FALSE },
{ NULL, CALLOUT_TIME, 0, FALSE, TRUE }
};
uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
verify_type_t * vp;
-if (ss == NULL) goto BAD_VERIFY;
+if (!ss) goto BAD_VERIFY;
/* Handle name/address consistency verification in a separate function. */
for (vp= verify_type_list;
- (char *)vp < (char *)verify_type_list + sizeof(verify_type_list);
+ CS vp < CS verify_type_list + sizeof(verify_type_list);
vp++
)
if (vp->alt_opt_sep ? strncmpic(ss, vp->name, vp->alt_opt_sep) == 0
: strcmpic (ss, vp->name) == 0)
break;
-if ((char *)vp >= (char *)verify_type_list + sizeof(verify_type_list))
+if (CS vp >= CS verify_type_list + sizeof(verify_type_list))
goto BAD_VERIFY;
-if (vp->no_options && slash != NULL)
+if (vp->no_options && slash)
{
*log_msgptr = string_sprintf("unexpected '/' found in \"%s\" "
"(this verify item has no options)", arg);
return ERROR;
}
-if (!(vp->where_allowed & (1<<where)))
+if (!(vp->where_allowed & BIT(where)))
{
- *log_msgptr = string_sprintf("cannot verify %s in ACL for %s", vp->name, acl_wherenames[where]);
+ *log_msgptr = string_sprintf("cannot verify %s in ACL for %s",
+ vp->name, acl_wherenames[where]);
return ERROR;
}
switch(vp->value)
{
case VERIFY_REV_HOST_LKUP:
- if (sender_host_address == NULL) return OK;
+ if (!sender_host_address) return OK;
if ((rc = acl_verify_reverse(user_msgptr, log_msgptr)) == DEFER)
- while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
if (strcmpic(ss, US"defer_ok") == 0)
return OK;
return rc;
/* We can test the result of optional HELO verification that might have
occurred earlier. If not, we can attempt the verification now. */
- if (!helo_verified && !helo_verify_failed) smtp_verify_helo();
- return helo_verified? OK : FAIL;
+ if (!f.helo_verified && !f.helo_verify_failed) smtp_verify_helo();
+ return f.helo_verified ? OK : FAIL;
case VERIFY_CSA:
/* Do Client SMTP Authorization checks in a separate function, and turn the
DEBUG(D_acl) debug_printf_indent("CSA result %s\n", csa_status);
return csa_return_code[rc];
+#ifdef EXPERIMENTAL_ARC
+ case VERIFY_ARC:
+ { /* Do Authenticated Received Chain checks in a separate function. */
+ const uschar * condlist = CUS string_nextinlist(&list, &sep, NULL, 0);
+ int csep = 0;
+ uschar * cond;
+
+ if (!(arc_state = acl_verify_arc())) return DEFER;
+ DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state,
+ arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":"");
+
+ if (!condlist) condlist = US"none:pass";
+ while ((cond = string_nextinlist(&condlist, &csep, NULL, 0)))
+ if (Ustrcmp(arc_state, cond) == 0) return OK;
+ return FAIL;
+ }
+#endif
+
case VERIFY_HDR_SYNTAX:
- /* Check that all relevant header lines have the correct syntax. If there is
+ /* Check that all relevant header lines have the correct 5322-syntax. If there is
a syntax error, we return details of the error to the sender if configured to
send out full details. (But a "message" setting on the ACL can override, as
always). */
See RFC 5322, 2.2. and RFC 6532, 3. */
rc = verify_check_header_names_ascii(log_msgptr);
- if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
+ if (rc != OK && smtp_return_error_details && *log_msgptr)
*user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
return rc;
/* Check that no recipient of this message is "blind", that is, every envelope
recipient must be mentioned in either To: or Cc:. */
- rc = verify_check_notblind();
- if (rc != OK)
+ if ((rc = verify_check_notblind()) != OK)
{
*log_msgptr = string_sprintf("bcc recipient detected");
if (smtp_return_error_details)
uschar buffer[256];
while (isspace(*sublist)) sublist++;
- while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer)))
- != NULL)
+ while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer))))
{
callout_opt_t * op;
double period = 1.0F;
}
while (isspace(*opt)) opt++;
}
- if (op->timeval)
+ if (op->timeval && (period = readconf_readtime(opt, 0, FALSE)) < 0)
{
- period = readconf_readtime(opt, 0, FALSE);
- if (period < 0)
- {
- *log_msgptr = string_sprintf("bad time value in ACL condition "
- "\"verify %s\"", arg);
- return ERROR;
- }
+ *log_msgptr = string_sprintf("bad time value in ACL condition "
+ "\"verify %s\"", arg);
+ return ERROR;
}
switch(op->value)
{
if (!*user_msgptr && *log_msgptr)
*user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
- if (rc == DEFER) acl_temp_details = TRUE;
+ if (rc == DEFER) f.acl_temp_details = TRUE;
}
}
}
addr2.user_message : addr2.message;
/* Allow details for temporary error if the address is so flagged. */
- if (testflag((&addr2), af_pass_message)) acl_temp_details = TRUE;
+ if (testflag((&addr2), af_pass_message)) f.acl_temp_details = TRUE;
/* Make $address_data visible */
deliver_address_data = addr2.prop.address_data;
log_msgptr for error messages
format format string
... supplementary arguments
- ss ratelimit option name
- where ACL_WHERE_xxxx indicating which ACL this is
Returns: ERROR
*/
ratelimit_error(uschar **log_msgptr, const char *format, ...)
{
va_list ap;
-uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
+gstring * g =
+ string_cat(NULL, US"error in arguments to \"ratelimit\" condition: ");
+
va_start(ap, format);
-if (!string_vformat(buffer, sizeof(buffer), format, ap))
- log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "string_sprintf expansion was longer than " SIZE_T_FMT, sizeof(buffer));
+g = string_vformat(g, TRUE, format, ap);
va_end(ap);
-*log_msgptr = string_sprintf(
- "error in arguments to \"ratelimit\" condition: %s", buffer);
+
+gstring_reset_unused(g);
+*log_msgptr = string_from_gstring(g);
return ERROR;
}
size, which must be greater than or equal to zero. Zero is useful for
rate measurement as opposed to rate limiting. */
-sender_rate_limit = string_nextinlist(&arg, &sep, NULL, 0);
-if (sender_rate_limit == NULL)
+if (!(sender_rate_limit = string_nextinlist(&arg, &sep, NULL, 0)))
return ratelimit_error(log_msgptr, "sender rate limit not set");
limit = Ustrtod(sender_rate_limit, &ss);
constant. This must be strictly greater than zero, because zero leads to
run-time division errors. */
-sender_rate_period = string_nextinlist(&arg, &sep, NULL, 0);
-if (sender_rate_period == NULL) period = -1.0;
-else period = readconf_readtime(sender_rate_period, 0, FALSE);
+period = !(sender_rate_period = string_nextinlist(&arg, &sep, NULL, 0))
+ ? -1.0 : readconf_readtime(sender_rate_period, 0, FALSE);
if (period <= 0.0)
return ratelimit_error(log_msgptr,
"\"%s\" is not a time value", sender_rate_period);
/* Parse the other options. */
-while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
- != NULL)
+while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)))
{
if (strcmpic(ss, US"leaky") == 0) leaky = TRUE;
else if (strcmpic(ss, US"strict") == 0) strict = TRUE;
zero and let the recorded rate decay as if nothing happened. */
RATE_SET(mode, PER_MAIL);
if (where > ACL_WHERE_NOTSMTP) badacl = TRUE;
- else count = message_size < 0 ? 0.0 : (double)message_size;
+ else count = message_size < 0 ? 0.0 : (double)message_size;
}
else if (strcmpic(ss, US"per_addr") == 0)
{
RATE_SET(mode, PER_RCPT);
if (where != ACL_WHERE_RCPT) badacl = TRUE, unique = US"*";
- else unique = string_sprintf("%s@%s", deliver_localpart, deliver_domain);
+ else unique = string_sprintf("%s@%s", deliver_localpart, deliver_domain);
}
else if (strncmpic(ss, US"count=", 6) == 0)
{
uschar *e;
count = Ustrtod(ss+6, &e);
if (count < 0.0 || *e != '\0')
- return ratelimit_error(log_msgptr,
- "\"%s\" is not a positive number", ss);
+ return ratelimit_error(log_msgptr, "\"%s\" is not a positive number", ss);
}
else if (strncmpic(ss, US"unique=", 7) == 0)
unique = string_copy(ss + 7);
- else if (key == NULL)
+ else if (!key)
key = string_copy(ss);
else
key = string_sprintf("%s/%s", key, ss);
return ratelimit_error(log_msgptr, "conflicting update modes");
if (badacl && (leaky || strict) && !noupdate)
return ratelimit_error(log_msgptr,
- "\"%s\" must not have /leaky or /strict option in %s ACL",
+ "\"%s\" must not have /leaky or /strict option, or cannot be used in %s ACL",
ratelimit_option_string[mode], acl_wherenames[where]);
/* Set the default values of any unset options. In readonly mode we
omit it. The smoothing constant (sender_rate_period) and the per_xxx options
are added to the key because they alter the meaning of the stored data. */
-if (key == NULL)
- key = (sender_host_address == NULL)? US"" : sender_host_address;
+if (!key)
+ key = !sender_host_address ? US"" : sender_host_address;
key = string_sprintf("%s/%s/%s%s",
sender_rate_period,
if (readonly)
anchor = &ratelimiters_cmd;
-else switch(mode) {
-case RATE_PER_CONN:
- anchor = &ratelimiters_conn;
- store_pool = POOL_PERM;
- break;
-case RATE_PER_BYTE:
-case RATE_PER_MAIL:
-case RATE_PER_ALLRCPTS:
- anchor = &ratelimiters_mail;
- break;
-case RATE_PER_ADDR:
-case RATE_PER_CMD:
-case RATE_PER_RCPT:
- anchor = &ratelimiters_cmd;
- break;
-default:
- anchor = NULL; /* silence an "unused" complaint */
- log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "internal ACL error: unknown ratelimit mode %d", mode);
- break;
-}
+else switch(mode)
+ {
+ case RATE_PER_CONN:
+ anchor = &ratelimiters_conn;
+ store_pool = POOL_PERM;
+ break;
+ case RATE_PER_BYTE:
+ case RATE_PER_MAIL:
+ case RATE_PER_ALLRCPTS:
+ anchor = &ratelimiters_mail;
+ break;
+ case RATE_PER_ADDR:
+ case RATE_PER_CMD:
+ case RATE_PER_RCPT:
+ anchor = &ratelimiters_cmd;
+ break;
+ default:
+ anchor = NULL; /* silence an "unused" complaint */
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "internal ACL error: unknown ratelimit mode %d", mode);
+ break;
+ }
-t = tree_search(*anchor, key);
-if (t != NULL)
+if ((t = tree_search(*anchor, key)))
{
dbd = t->data.ptr;
/* The following few lines duplicate some of the code below. */
/* We aren't using a pre-computed rate, so get a previously recorded rate
from the database, which will be updated and written back if required. */
-dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE);
-if (dbm == NULL)
+if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE)))
{
store_pool = old_pool;
sender_rate = NULL;
gettimeofday(&tv, NULL);
-if (dbdb != NULL)
+if (dbdb)
{
/* Locate the basic ratelimit block inside the DB data. */
HDEBUG(D_acl) debug_printf_indent("ratelimit found key in database\n");
filter because we want its size to change if the limit changes. Note that
we keep the dbd pointer for copying the rate into the new data block. */
- if(unique != NULL && tv.tv_sec > dbdb->bloom_epoch + period)
+ if(unique && tv.tv_sec > dbdb->bloom_epoch + period)
{
HDEBUG(D_acl) debug_printf_indent("ratelimit discarding old Bloom filter\n");
dbdb = NULL;
/* Sanity check. */
- if(unique != NULL && dbdb_size < sizeof(*dbdb))
+ if(unique && dbdb_size < sizeof(*dbdb))
{
HDEBUG(D_acl) debug_printf_indent("ratelimit discarding undersize Bloom filter\n");
dbdb = NULL;
/* Allocate a new data block if the database lookup failed
or the Bloom filter passed its age limit. */
-if (dbdb == NULL)
+if (!dbdb)
{
- if (unique == NULL)
+ if (!unique)
{
/* No Bloom filter. This basic ratelimit block is initialized below. */
HDEBUG(D_acl) debug_printf_indent("ratelimit creating new rate data block\n");
/* Preserve any basic ratelimit data (which is our longer-term memory)
by copying it from the discarded block. */
- if (dbd != NULL)
+ if (dbd)
{
dbdb->dbd = *dbd;
dbd = &dbdb->dbd;
changes to the filter will be discarded and because count is already set to
zero. */
-if (unique != NULL && !readonly)
+if (unique && !readonly)
{
/* We identify unique events using a Bloom filter. (You can find my
notes on Bloom filters at http://fanf.livejournal.com/81696.html)
the new one, otherwise update the block from the database. The initial rate
is what would be computed by the code below for an infinite interval. */
-if (dbd == NULL)
+if (!dbd)
{
HDEBUG(D_acl) debug_printf_indent("ratelimit initializing new key's rate data\n");
dbd = &dbdb->dbd;
This matters for edge cases such as a limit of zero, when the client
should be completely blocked. */
-rc = (dbd->rate < limit)? FAIL : OK;
+rc = dbd->rate < limit ? FAIL : OK;
/* Update the state if the rate is low or if we are being strict. If we
are in leaky mode and the sender's rate is too high, we do not update
HDEBUG(D_acl)
debug_printf_indent("udpsend [%s]:%d %s\n", h->address, portnum, arg);
+/*XXX this could better use sendto */
r = s = ip_connectedsocket(SOCK_DGRAM, h->address, portnum, portnum,
- 1, NULL, &errstr);
+ 1, NULL, &errstr, NULL);
if (r < 0) goto defer;
len = Ustrlen(arg);
r = send(s, arg, len, 0);
int sep = -'/';
#endif
-for (; cb != NULL; cb = cb->next)
+for (; cb; cb = cb->next)
{
const uschar *arg;
int control_type;
arg = cb->arg;
else if (!(arg = expand_string(cb->arg)))
{
- if (expand_string_forcedfail) continue;
+ if (f.expand_string_forcedfail) continue;
*log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
cb->arg, expand_string_message);
- return search_find_defer ? DEFER : ERROR;
+ return f.search_find_defer ? DEFER : ERROR;
}
/* Show condition, and expanded condition if it's different */
if (cb->type == ACLC_SET)
{
- debug_printf("acl_%s ", cb->u.varname);
- lhswidth += 5 + Ustrlen(cb->u.varname);
+#ifndef DISABLE_DKIM
+ if ( Ustrcmp(cb->u.varname, "dkim_verify_status") == 0
+ || Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
+ {
+ debug_printf("%s ", cb->u.varname);
+ lhswidth += 19;
+ }
+ else
+#endif
+ {
+ debug_printf("acl_%s ", cb->u.varname);
+ lhswidth += 5 + Ustrlen(cb->u.varname);
+ }
}
debug_printf("= %s\n", cb->arg);
break;
case ACLC_AUTHENTICATED:
- rc = (sender_host_authenticated == NULL)? FAIL :
- match_isinlist(sender_host_authenticated, &arg, 0, NULL, NULL, MCL_STRING,
- TRUE, NULL);
+ rc = sender_host_authenticated ? match_isinlist(sender_host_authenticated,
+ &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL) : FAIL;
break;
#ifdef EXPERIMENTAL_BRIGHTMAIL
switch(control_type)
{
case CONTROL_AUTH_UNADVERTISED:
- allow_auth_unadvertised = TRUE;
+ f.allow_auth_unadvertised = TRUE;
break;
#ifdef EXPERIMENTAL_BRIGHTMAIL
#ifndef DISABLE_DKIM
case CONTROL_DKIM_VERIFY:
- dkim_disable_verify = TRUE;
+ f.dkim_disable_verify = TRUE;
#ifdef EXPERIMENTAL_DMARC
/* Since DKIM was blocked, skip DMARC too */
- dmarc_disable_verify = TRUE;
- dmarc_enable_forensic = FALSE;
+ f.dmarc_disable_verify = TRUE;
+ f.dmarc_enable_forensic = FALSE;
#endif
break;
#endif
#ifdef EXPERIMENTAL_DMARC
case CONTROL_DMARC_VERIFY:
- dmarc_disable_verify = TRUE;
+ f.dmarc_disable_verify = TRUE;
break;
case CONTROL_DMARC_FORENSIC:
- dmarc_enable_forensic = TRUE;
+ f.dmarc_enable_forensic = TRUE;
break;
#endif
#ifdef WITH_CONTENT_SCAN
case CONTROL_NO_MBOX_UNSPOOL:
- no_mbox_unspool = TRUE;
+ f.no_mbox_unspool = TRUE;
break;
#endif
case CONTROL_NO_MULTILINE:
- no_multiline_responses = TRUE;
+ f.no_multiline_responses = TRUE;
break;
case CONTROL_NO_PIPELINING:
- pipelining_enable = FALSE;
+ f.pipelining_enable = FALSE;
break;
case CONTROL_NO_DELAY_FLUSH:
- disable_delay_flush = TRUE;
+ f.disable_delay_flush = TRUE;
break;
case CONTROL_NO_CALLOUT_FLUSH:
- disable_callout_flush = TRUE;
+ f.disable_callout_flush = TRUE;
break;
case CONTROL_FAKEREJECT:
- cancel_cutthrough_connection("fakereject");
+ cancel_cutthrough_connection(TRUE, US"fakereject");
case CONTROL_FAKEDEFER:
fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
if (*p == '/')
break;
case CONTROL_FREEZE:
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
freeze_tell = freeze_tell_config; /* Reset to configured value */
if (Ustrncmp(p, "/no_tell", 8) == 0)
*log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
return ERROR;
}
- cancel_cutthrough_connection("item frozen");
+ cancel_cutthrough_connection(TRUE, US"item frozen");
break;
case CONTROL_QUEUE_ONLY:
- queue_only_policy = TRUE;
- cancel_cutthrough_connection("queueing forced");
+ f.queue_only_policy = TRUE;
+ cancel_cutthrough_connection(TRUE, US"queueing forced");
break;
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ case CONTROL_REQUIRETLS:
+ tls_requiretls |= REQUIRETLS_MSG;
+ break;
+#endif
case CONTROL_SUBMISSION:
originator_name = US"";
- submission_mode = TRUE;
+ f.submission_mode = TRUE;
while (*p == '/')
{
if (Ustrncmp(p, "/sender_retain", 14) == 0)
{
p += 14;
- active_local_sender_retain = TRUE;
- active_local_from_check = FALSE;
+ f.active_local_sender_retain = TRUE;
+ f.active_local_from_check = FALSE;
}
else if (Ustrncmp(p, "/domain=", 8) == 0)
{
break;
case CONTROL_SUPPRESS_LOCAL_FIXUPS:
- suppress_local_fixups = TRUE;
+ f.suppress_local_fixups = TRUE;
break;
case CONTROL_CUTTHROUGH_DELIVERY:
+ {
+ uschar * ignored = NULL;
#ifndef DISABLE_PRDR
if (prdr_requested)
#else
/* Too hard to think about for now. We might in future cutthrough
the case where both sides handle prdr and this-node prdr acl
is "accept" */
- *log_msgptr = string_sprintf("PRDR on %s reception\n", arg);
+ ignored = US"PRDR active";
else
{
- if (deliver_freeze)
- *log_msgptr = US"frozen";
- else if (queue_only_policy)
- *log_msgptr = US"queue-only";
+ if (f.deliver_freeze)
+ ignored = US"frozen";
+ else if (f.queue_only_policy)
+ ignored = US"queue-only";
else if (fake_response == FAIL)
- *log_msgptr = US"fakereject";
+ ignored = US"fakereject";
else
{
if (rcpt_count == 1)
{
- cutthrough.delivery = TRUE;
+ cutthrough.delivery = TRUE; /* control accepted */
while (*p == '/')
{
const uschar * pp = p+1;
p = pp;
}
}
- break;
+ else
+ ignored = US"nonfirst rcpt";
}
- *log_msgptr = string_sprintf("\"control=%s\" on %s item",
- arg, *log_msgptr);
}
- return ERROR;
+ DEBUG(D_acl) if (ignored)
+ debug_printf(" cutthrough request ignored on %s item\n", ignored);
+ }
+ break;
#ifdef SUPPORT_I18N
case CONTROL_UTF8_DOWNCONVERT:
else
{
- if (smtp_out != NULL && !disable_delay_flush)
+ if (smtp_out && !f.disable_delay_flush)
mac_smtp_fflush();
#if !defined(NO_POLL_H) && defined (POLLRDHUP)
HDEBUG(D_acl) debug_printf_indent("delay cancelled by peer close\n");
}
#else
- /* It appears to be impossible to detect that a TCP/IP connection has
- gone away without reading from it. This means that we cannot shorten
- the delay below if the client goes away, because we cannot discover
- that the client has closed its end of the connection. (The connection
- is actually in a half-closed state, waiting for the server to close its
- end.) It would be nice to be able to detect this state, so that the
- Exim process is not held up unnecessarily. However, it seems that we
- can't. The poll() function does not do the right thing, and in any case
- it is not always available.
- */
+ /* Lacking POLLRDHUP it appears to be impossible to detect that a
+ TCP/IP connection has gone away without reading from it. This means
+ that we cannot shorten the delay below if the client goes away,
+ because we cannot discover that the client has closed its end of the
+ connection. (The connection is actually in a half-closed state,
+ waiting for the server to close its end.) It would be nice to be able
+ to detect this state, so that the Exim process is not held up
+ unnecessarily. However, it seems that we can't. The poll() function
+ does not do the right thing, and in any case it is not always
+ available. */
while (delay > 0) delay = sleep(delay);
#endif
#ifndef DISABLE_DKIM
case ACLC_DKIM_SIGNER:
- if (dkim_cur_signer != NULL)
+ if (dkim_cur_signer)
rc = match_isinlist(dkim_cur_signer,
&arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
else
break;
case ACLC_DKIM_STATUS:
- rc = match_isinlist(dkim_exim_expand_query(DKIM_VERIFY_STATUS),
+ rc = match_isinlist(dkim_verify_status,
&arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
break;
#endif
#ifdef EXPERIMENTAL_DMARC
case ACLC_DMARC_STATUS:
- if (!dmarc_has_been_checked)
+ if (!f.dmarc_has_been_checked)
dmarc_process();
- dmarc_has_been_checked = TRUE;
+ f.dmarc_has_been_checked = TRUE;
/* used long way of dmarc_exim_expand_query() in case we need more
* view into the process in the future. */
rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
}
while (isspace(*s)) s++;
-
if (logbits == 0) logbits = LOG_MAIN;
log_write(0, logbits, "%s", string_printing(s));
}
#endif
case ACLC_QUEUE:
+ if (Ustrchr(arg, '/'))
+ {
+ *log_msgptr = string_sprintf(
+ "Directory separator not permitted in queue name: '%s'", arg);
+ return ERROR;
+ }
queue_name = string_copy_malloc(arg);
break;
break;
case ACLC_RECIPIENTS:
- rc = match_address_list((const uschar *)addr->address, TRUE, TRUE, &arg, NULL, -1, 0,
+ rc = match_address_list(CUS addr->address, TRUE, TRUE, &arg, NULL, -1, 0,
CUSS &recipient_data);
break;
{
uschar *sdomain;
sdomain = Ustrrchr(sender_address, '@');
- sdomain = (sdomain == NULL)? US"" : sdomain + 1;
+ sdomain = sdomain ? sdomain + 1 : US"";
rc = match_isinlist(sdomain, &arg, 0, &domainlist_anchor,
sender_domain_cache, MCL_DOMAIN, TRUE, NULL);
}
break;
case ACLC_SENDERS:
- rc = match_address_list((const uschar *)sender_address, TRUE, TRUE, &arg,
+ rc = match_address_list(CUS sender_address, TRUE, TRUE, &arg,
sender_address_cache, -1, 0, CUSS &sender_data);
break;
{
int old_pool = store_pool;
if ( cb->u.varname[0] == 'c'
+#ifndef DISABLE_DKIM
+ || cb->u.varname[0] == 'd'
+#endif
#ifndef DISABLE_EVENT
|| event_name /* An event is being delivered */
#endif
)
store_pool = POOL_PERM;
- acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
+#ifndef DISABLE_DKIM /* Overwriteable dkim result variables */
+ if (Ustrcmp(cb->u.varname, "dkim_verify_status") == 0)
+ dkim_verify_status = string_copy(arg);
+ else if (Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
+ dkim_verify_reason = string_copy(arg);
+ else
+#endif
+ acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
store_pool = old_pool;
}
break;
- #ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN
case ACLC_SPAM:
{
/* Separate the regular expression and any optional parameters. */
const uschar * list = arg;
uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
- /* Run the spam backend. */
+
rc = spam(CUSS &ss);
/* Modify return code based upon the existence of options. */
- while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
- != NULL) {
+ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
- {
- /* FAIL so that the message is passed to the next ACL */
- rc = FAIL;
- }
- }
+ rc = FAIL; /* FAIL so that the message is passed to the next ACL */
}
break;
- #endif
+#endif
- #ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
case ACLC_SPF:
rc = spf_process(&arg, sender_address, SPF_PROCESS_NORMAL);
break;
case ACLC_SPF_GUESS:
rc = spf_process(&arg, sender_address, SPF_PROCESS_GUESS);
break;
- #endif
+#endif
case ACLC_UDPSEND:
rc = acl_udpsend(arg, log_msgptr);
if (*epp && rc == OK) user_message = NULL;
-if (((1<<rc) & msgcond[verb]) != 0)
+if ((BIT(rc) & msgcond[verb]) != 0)
{
uschar *expmessage;
uschar *old_user_msgptr = *user_msgptr;
(rc == OK && (verb == ACL_ACCEPT || verb == ACL_DISCARD)))
*log_msgptr = *user_msgptr = NULL;
- if (user_message != NULL)
+ if (user_message)
{
acl_verify_message = old_user_msgptr;
expmessage = expand_string(user_message);
- if (expmessage == NULL)
+ if (!expmessage)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
user_message, expand_string_message);
}
else if (expmessage[0] != 0) *user_msgptr = expmessage;
}
- if (log_message != NULL)
+ if (log_message)
{
acl_verify_message = old_log_msgptr;
expmessage = expand_string(log_message);
- if (expmessage == NULL)
+ if (!expmessage)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand ACL message \"%s\": %s",
log_message, expand_string_message);
}
/* If no log message, default it to the user message */
- if (*log_msgptr == NULL) *log_msgptr = *user_msgptr;
+ if (!*log_msgptr) *log_msgptr = *user_msgptr;
}
acl_verify_message = NULL;
{
if (!(ss = expand_string(s)))
{
- if (expand_string_forcedfail) return OK;
+ if (f.expand_string_forcedfail) return OK;
*log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s,
expand_string_message);
return ERROR;
}
else ss = s;
-while (isspace(*ss))ss++;
+while (isspace(*ss)) ss++;
/* If we can't find a named ACL, the default is to parse it as an inline one.
(Unless it begins with a slash; non-existent files give rise to an error.) */
&& (where == ACL_WHERE_QUIT || where == ACL_WHERE_NOTQUIT);
*log_msgptr = *user_msgptr = NULL;
- acl_temp_details = FALSE;
+ f.acl_temp_details = FALSE;
HDEBUG(D_acl) debug_printf_indent("processing \"%s\"\n", verbs[acl->verb]);
{
if (search_error_message != NULL && *search_error_message != 0)
*log_msgptr = search_error_message;
- if (smtp_return_error_details) acl_temp_details = TRUE;
+ if (smtp_return_error_details) f.acl_temp_details = TRUE;
}
else
- {
- acl_temp_details = TRUE;
- }
+ f.acl_temp_details = TRUE;
if (acl->verb != ACL_WARN) return DEFER;
break;
{
HDEBUG(D_acl) debug_printf_indent("end of %s: DEFER\n", acl_name);
if (acl_quit_check) goto badquit;
- acl_temp_details = TRUE;
+ f.acl_temp_details = TRUE;
return DEFER;
}
break;
return ret;
bad:
-if (expand_string_forcedfail) return ERROR;
+if (f.expand_string_forcedfail) return ERROR;
*log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s",
tmp, expand_string_message);
-return search_find_defer?DEFER:ERROR;
+return f.search_find_defer ? DEFER : ERROR;
}
#ifndef DISABLE_PRDR
case ACL_WHERE_PRDR:
#endif
- if (host_checking_callout) /* -bhc mode */
- cancel_cutthrough_connection("host-checking mode");
+
+ if (f.host_checking_callout) /* -bhc mode */
+ cancel_cutthrough_connection(TRUE, US"host-checking mode");
else if ( rc == OK
&& cutthrough.delivery
&& rcpt_count > cutthrough.nrcpt
- && (rc = open_cutthrough_connection(addr)) == DEFER
)
- if (cutthrough.defer_pass)
- {
- uschar * s = addr->message;
- /* Horrid kludge to recover target's SMTP message */
- while (*s) s++;
- do --s; while (!isdigit(*s));
- if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s;
- acl_temp_details = TRUE;
- }
+ {
+ if ((rc = open_cutthrough_connection(addr)) == DEFER)
+ if (cutthrough.defer_pass)
+ {
+ uschar * s = addr->message;
+ /* Horrid kludge to recover target's SMTP message */
+ while (*s) s++;
+ do --s; while (!isdigit(*s));
+ if (*--s && isdigit(*s) && *--s && isdigit(*s)) *user_msgptr = s;
+ f.acl_temp_details = TRUE;
+ }
else
- {
- HDEBUG(D_acl) debug_printf_indent("cutthrough defer; will spool\n");
- rc = OK;
- }
+ {
+ HDEBUG(D_acl) debug_printf_indent("cutthrough defer; will spool\n");
+ rc = OK;
+ }
+ }
+ else HDEBUG(D_acl) if (cutthrough.delivery)
+ if (rcpt_count <= cutthrough.nrcpt)
+ debug_printf_indent("ignore cutthrough request; nonfirst message\n");
+ else if (rc != OK)
+ debug_printf_indent("ignore cutthrough request; ACL did not accept\n");
break;
case ACL_WHERE_PREDATA:
if (rc == OK)
cutthrough_predata();
else
- cancel_cutthrough_connection("predata acl not ok");
+ cancel_cutthrough_connection(TRUE, US"predata acl not ok");
break;
case ACL_WHERE_QUIT:
case ACL_WHERE_NOTQUIT:
- cancel_cutthrough_connection("quit or notquit");
+ /* Drop cutthrough conns, and drop heldopen verify conns if
+ the previous was not DATA */
+ {
+ uschar prev = smtp_connection_had[smtp_ch_index-2];
+ BOOL dropverify = !(prev == SCH_DATA || prev == SCH_BDAT);
+
+ cancel_cutthrough_connection(dropverify, US"quit or conndrop");
break;
+ }
default:
break;
}
deliver_domain = deliver_localpart = deliver_address_data =
- sender_address_data = NULL;
+ deliver_domain_data = sender_address_data = NULL;
/* A DISCARD response is permitted only for message ACLs, excluding the PREDATA
ACL, which is really in the middle of an SMTP command. */
*/
tree_node *
-acl_var_create(uschar *name)
+acl_var_create(uschar * name)
{
-tree_node *node, **root;
-root = (name[0] == 'c')? &acl_var_c : &acl_var_m;
-node = tree_search(*root, name);
-if (node == NULL)
+tree_node * node, ** root = name[0] == 'c' ? &acl_var_c : &acl_var_m;
+if (!(node = tree_search(*root, name)))
{
node = store_get(sizeof(tree_node) + Ustrlen(name));
Ustrcpy(node->name, name);
#
# abuse: the person dealing with network and mail abuse
# hostmaster: the person dealing with DNS problems
-# webmaster: the person dealing with your web site
+# webmaster: the person dealing with your website
####
--- /dev/null
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+/* Experimental ARC support for Exim
+ Copyright (c) Jeremy Harris 2018
+ License: GPL
+*/
+
+#include "exim.h"
+#ifdef EXPERIMENTAL_ARC
+# if !defined SUPPORT_SPF
+# error SPF must also be enabled for ARC
+# elif defined DISABLE_DKIM
+# error DKIM must also be enabled for ARC
+# else
+
+# include "functions.h"
+# include "pdkim/pdkim.h"
+# include "pdkim/signing.h"
+
+extern pdkim_ctx * dkim_verify_ctx;
+extern pdkim_ctx dkim_sign_ctx;
+
+#define ARC_SIGN_OPT_TSTAMP BIT(0)
+#define ARC_SIGN_OPT_EXPIRE BIT(1)
+
+#define ARC_SIGN_DEFAULT_EXPIRE_DELTA (60 * 60 * 24 * 30) /* one month */
+
+/******************************************************************************/
+
+typedef struct hdr_rlist {
+ struct hdr_rlist * prev;
+ BOOL used;
+ header_line * h;
+} hdr_rlist;
+
+typedef struct arc_line {
+ header_line * complete; /* including the header name; nul-term */
+ uschar * relaxed;
+
+ /* identified tag contents */
+ /*XXX t= for AS? */
+ blob i;
+ blob cv;
+ blob a;
+ blob b;
+ blob bh;
+ blob d;
+ blob h;
+ blob s;
+ blob c;
+ blob l;
+
+ /* tag content sub-portions */
+ blob a_algo;
+ blob a_hash;
+
+ blob c_head;
+ blob c_body;
+
+ /* modified copy of b= field in line */
+ blob rawsig_no_b_val;
+} arc_line;
+
+typedef struct arc_set {
+ struct arc_set * next;
+ struct arc_set * prev;
+
+ unsigned instance;
+ arc_line * hdr_aar;
+ arc_line * hdr_ams;
+ arc_line * hdr_as;
+
+ const uschar * ams_verify_done;
+ BOOL ams_verify_passed;
+} arc_set;
+
+typedef struct arc_ctx {
+ arc_set * arcset_chain;
+ arc_set * arcset_chain_last;
+} arc_ctx;
+
+#define ARC_HDR_AAR US"ARC-Authentication-Results:"
+#define ARC_HDRLEN_AAR 27
+#define ARC_HDR_AMS US"ARC-Message-Signature:"
+#define ARC_HDRLEN_AMS 22
+#define ARC_HDR_AS US"ARC-Seal:"
+#define ARC_HDRLEN_AS 9
+#define HDR_AR US"Authentication-Results:"
+#define HDRLEN_AR 23
+
+static time_t now;
+static time_t expire;
+static hdr_rlist * headers_rlist;
+static arc_ctx arc_sign_ctx = { NULL };
+static arc_ctx arc_verify_ctx = { NULL };
+
+
+/******************************************************************************/
+
+
+/* Get the instance number from the header.
+Return 0 on error */
+static unsigned
+arc_instance_from_hdr(const arc_line * al)
+{
+const uschar * s = al->i.data;
+if (!s || !al->i.len) return 0;
+return (unsigned) atoi(CCS s);
+}
+
+
+static uschar *
+skip_fws(uschar * s)
+{
+uschar c = *s;
+while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s;
+return s;
+}
+
+
+/* Locate instance struct on chain, inserting a new one if
+needed. The chain is in increasing-instance-number order
+by the "next" link, and we have a "prev" link also.
+*/
+
+static arc_set *
+arc_find_set(arc_ctx * ctx, unsigned i)
+{
+arc_set ** pas, * as, * next, * prev;
+
+for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain;
+ as = *pas; pas = &as->next)
+ {
+ if (as->instance > i) break;
+ if (as->instance == i)
+ {
+ DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i);
+ return as;
+ }
+ next = as->next;
+ prev = as;
+ }
+
+DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i);
+*pas = as = store_get(sizeof(arc_set));
+memset(as, 0, sizeof(arc_set));
+as->next = next;
+as->prev = prev;
+as->instance = i;
+if (next)
+ next->prev = as;
+else
+ ctx->arcset_chain_last = as;
+return as;
+}
+
+
+
+/* Insert a tag content into the line structure.
+Note this is a reference to existing data, not a copy.
+Check for already-seen tag.
+The string-pointer is on the '=' for entry. Update it past the
+content (to the ;) on return;
+*/
+
+static uschar *
+arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss)
+{
+uschar * s = *ss;
+uschar c = *++s;
+blob * b = (blob *)(US al + loff);
+size_t len = 0;
+
+/* [FWS] tag-value [FWS] */
+
+if (b->data) return US"fail";
+s = skip_fws(s); /* FWS */
+
+b->data = s;
+while ((c = *s) && c != ';') { len++; s++; }
+*ss = s;
+while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r'))
+ { s--; len--; } /* FWS */
+b->len = len;
+return NULL;
+}
+
+
+/* Inspect a header line, noting known tag fields.
+Check for duplicates. */
+
+static uschar *
+arc_parse_line(arc_line * al, header_line * h, unsigned off, BOOL instance_only)
+{
+uschar * s = h->text + off;
+uschar * r = NULL; /* compiler-quietening */
+uschar c;
+
+al->complete = h;
+
+if (!instance_only)
+ {
+ al->rawsig_no_b_val.data = store_get(h->slen + 1);
+ memcpy(al->rawsig_no_b_val.data, h->text, off); /* copy the header name blind */
+ r = al->rawsig_no_b_val.data + off;
+ al->rawsig_no_b_val.len = off;
+ }
+
+/* tag-list = tag-spec *( ";" tag-spec ) [ ";" ] */
+
+while ((c = *s))
+ {
+ char tagchar;
+ uschar * t;
+ unsigned i = 0;
+ uschar * fieldstart = s;
+ uschar * bstart = NULL, * bend;
+
+ /* tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */
+
+ s = skip_fws(s); /* FWS */
+ if (!*s) break;
+/* debug_printf("%s: consider '%s'\n", __FUNCTION__, s); */
+ tagchar = *s++;
+ s = skip_fws(s); /* FWS */
+ if (!*s) break;
+
+ if (!instance_only || tagchar == 'i') switch (tagchar)
+ {
+ case 'a': /* a= AMS algorithm */
+ {
+ if (*s != '=') return US"no 'a' value";
+ if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup";
+
+ /* substructure: algo-hash (eg. rsa-sha256) */
+
+ t = al->a_algo.data = al->a.data;
+ while (*t != '-')
+ if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value";
+ al->a_algo.len = i;
+ if (*t++ != '-') return US"no '-' in 'a' value";
+ al->a_hash.data = t;
+ al->a_hash.len = al->a.len - i - 1;
+ }
+ break;
+ case 'b':
+ {
+ gstring * g = NULL;
+
+ switch (*s)
+ {
+ case '=': /* b= AMS signature */
+ if (al->b.data) return US"already b data";
+ bstart = s+1;
+
+ /* The signature can have FWS inserted in the content;
+ make a stripped copy */
+
+ while ((c = *++s) && c != ';')
+ if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
+ g = string_catn(g, s, 1);
+ al->b.data = string_from_gstring(g);
+ al->b.len = g->ptr;
+ gstring_reset_unused(g);
+ bend = s;
+ break;
+ case 'h': /* bh= AMS body hash */
+ s = skip_fws(++s); /* FWS */
+ if (*s != '=') return US"no bh value";
+ if (al->bh.data) return US"already bh data";
+
+ /* The bodyhash can have FWS inserted in the content;
+ make a stripped copy */
+
+ while ((c = *++s) && c != ';')
+ if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
+ g = string_catn(g, s, 1);
+ al->bh.data = string_from_gstring(g);
+ al->bh.len = g->ptr;
+ gstring_reset_unused(g);
+ break;
+ default:
+ return US"b? tag";
+ }
+ }
+ break;
+ case 'c':
+ switch (*s)
+ {
+ case '=': /* c= AMS canonicalisation */
+ if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup";
+
+ /* substructure: head/body (eg. relaxed/simple)) */
+
+ t = al->c_head.data = al->c.data;
+ while (isalpha(*t))
+ if (!*t++ || ++i > al->a.len) break;
+ al->c_head.len = i;
+ if (*t++ == '/') /* /body is optional */
+ {
+ al->c_body.data = t;
+ al->c_body.len = al->c.len - i - 1;
+ }
+ else
+ {
+ al->c_body.data = US"simple";
+ al->c_body.len = 6;
+ }
+ break;
+ case 'v': /* cv= AS validity */
+ if (*++s != '=') return US"cv tag val";
+ if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s)) return US"cv tag dup";
+ break;
+ default:
+ return US"c? tag";
+ }
+ break;
+ case 'd': /* d= AMS domain */
+ if (*s != '=') return US"d tag val";
+ if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s)) return US"d tag dup";
+ break;
+ case 'h': /* h= AMS headers */
+ if (*s != '=') return US"h tag val";
+ if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s)) return US"h tag dup";
+ break;
+ case 'i': /* i= ARC set instance */
+ if (*s != '=') return US"i tag val";
+ if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s)) return US"i tag dup";
+ if (instance_only) goto done;
+ break;
+ case 'l': /* l= bodylength */
+ if (*s != '=') return US"l tag val";
+ if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s)) return US"l tag dup";
+ break;
+ case 's': /* s= AMS selector */
+ if (*s != '=') return US"s tag val";
+ if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s)) return US"s tag dup";
+ break;
+ }
+
+ while ((c = *s) && c != ';') s++;
+ if (c) s++; /* ; after tag-spec */
+
+ /* for all but the b= tag, copy the field including FWS. For the b=,
+ drop the tag content. */
+
+ if (!instance_only)
+ if (bstart)
+ {
+ size_t n = bstart - fieldstart;
+ memcpy(r, fieldstart, n); /* FWS "b=" */
+ r += n;
+ al->rawsig_no_b_val.len += n;
+ n = s - bend;
+ memcpy(r, bend, n); /* FWS ";" */
+ r += n;
+ al->rawsig_no_b_val.len += n;
+ }
+ else
+ {
+ size_t n = s - fieldstart;
+ memcpy(r, fieldstart, n);
+ r += n;
+ al->rawsig_no_b_val.len += n;
+ }
+ }
+
+if (!instance_only)
+ *r = '\0';
+
+done:
+/* debug_printf("%s: finshed\n", __FUNCTION__); */
+return NULL;
+}
+
+
+/* Insert one header line in the correct set of the chain,
+adding instances as needed and checking for duplicate lines.
+*/
+
+static uschar *
+arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff,
+ BOOL instance_only)
+{
+unsigned i;
+arc_set * as;
+arc_line * al = store_get(sizeof(arc_line)), ** alp;
+uschar * e;
+
+memset(al, 0, sizeof(arc_line));
+
+if ((e = arc_parse_line(al, h, off, instance_only)))
+ {
+ DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e);
+ return US"line parse";
+ }
+if (!(i = arc_instance_from_hdr(al))) return US"instance find";
+if (i > 50) return US"overlarge instance number";
+if (!(as = arc_find_set(ctx, i))) return US"set find";
+if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr";
+
+*alp = al;
+return NULL;
+}
+
+
+
+
+static const uschar *
+arc_try_header(arc_ctx * ctx, header_line * h, BOOL instance_only)
+{
+const uschar * e;
+
+/*debug_printf("consider hdr '%s'\n", h->text);*/
+if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0)
+ {
+ DEBUG(D_acl)
+ {
+ int len = h->slen;
+ uschar * s;
+ for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
+ s--, len--;
+ debug_printf("ARC: found AAR: %.*s\n", len, h->text);
+ }
+ if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar),
+ TRUE)))
+ {
+ DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e);
+ return US"inserting AAR";
+ }
+ }
+else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0)
+ {
+ arc_line * ams;
+
+ DEBUG(D_acl)
+ {
+ int len = h->slen;
+ uschar * s;
+ for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
+ s--, len--;
+ debug_printf("ARC: found AMS: %.*s\n", len, h->text);
+ }
+ if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams),
+ instance_only)))
+ {
+ DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e);
+ return US"inserting AMS";
+ }
+
+ /* defaults */
+ /*XXX dubious selection of ams here */
+ ams = ctx->arcset_chain->hdr_ams;
+ if (!ams->c.data)
+ {
+ ams->c_head.data = US"simple"; ams->c_head.len = 6;
+ ams->c_body = ams->c_head;
+ }
+ }
+else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0)
+ {
+ DEBUG(D_acl)
+ {
+ int len = h->slen;
+ uschar * s;
+ for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; )
+ s--, len--;
+ debug_printf("ARC: found AS: %.*s\n", len, h->text);
+ }
+ if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as),
+ instance_only)))
+ {
+ DEBUG(D_acl) debug_printf("inserting AS: %s\n", e);
+ return US"inserting AS";
+ }
+ }
+return NULL;
+}
+
+
+
+/* Gather the chain of arc sets from the headers.
+Check for duplicates while that is done. Also build the
+reverse-order headers list;
+
+Return: ARC state if determined, eg. by lack of any ARC chain.
+*/
+
+static const uschar *
+arc_vfy_collect_hdrs(arc_ctx * ctx)
+{
+header_line * h;
+hdr_rlist * r = NULL, * rprev = NULL;
+const uschar * e;
+
+DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n");
+for (h = header_list; h; h = h->next)
+ {
+ r = store_get(sizeof(hdr_rlist));
+ r->prev = rprev;
+ r->used = FALSE;
+ r->h = h;
+ rprev = r;
+
+ if ((e = arc_try_header(ctx, h, FALSE)))
+ {
+ arc_state_reason = string_sprintf("collecting headers: %s", e);
+ return US"fail";
+ }
+ }
+headers_rlist = r;
+
+if (!ctx->arcset_chain) return US"none";
+return NULL;
+}
+
+
+static BOOL
+arc_cv_match(arc_line * al, const uschar * s)
+{
+return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
+}
+
+/******************************************************************************/
+
+/* Return the hash of headers from the message that the AMS claims it
+signed.
+*/
+
+static void
+arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash)
+{
+const uschar * headernames = string_copyn(ams->h.data, ams->h.len);
+const uschar * hn;
+int sep = ':';
+hdr_rlist * r;
+BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
+int hashtype = pdkim_hashname_to_hashtype(
+ ams->a_hash.data, ams->a_hash.len);
+hctx hhash_ctx;
+const uschar * s;
+int len;
+
+if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+ {
+ DEBUG(D_acl)
+ debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
+ return;
+ }
+
+/* For each headername in the list from the AMS (walking in order)
+walk the message headers in reverse order, adding to the hash any
+found for the first time. For that last point, maintain used-marks
+on the list of message headers. */
+
+DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n");
+
+for (r = headers_rlist; r; r = r->prev)
+ r->used = FALSE;
+while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
+ for (r = headers_rlist; r; r = r->prev)
+ if ( !r->used
+ && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
+ )
+ {
+ if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE);
+
+ len = Ustrlen(s);
+ DEBUG(D_acl) pdkim_quoteprint(s, len);
+ exim_sha_update(&hhash_ctx, s, Ustrlen(s));
+ r->used = TRUE;
+ break;
+ }
+
+/* Finally add in the signature header (with the b= tag stripped); no CRLF */
+
+s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
+if (relaxed)
+ len = Ustrlen(s = pdkim_relax_header_n(s, len, FALSE));
+DEBUG(D_acl) pdkim_quoteprint(s, len);
+exim_sha_update(&hhash_ctx, s, len);
+
+exim_sha_finish(&hhash_ctx, hhash);
+DEBUG(D_acl)
+ { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); }
+return;
+}
+
+
+
+
+static pdkim_pubkey *
+arc_line_to_pubkey(arc_line * al)
+{
+uschar * dns_txt;
+pdkim_pubkey * p;
+
+if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
+ al->s.len, al->s.data, al->d.len, al->d.data))))
+ {
+ DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
+ return NULL;
+ }
+
+if ( !(p = pdkim_parse_pubkey_record(dns_txt))
+ || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
+ )
+ {
+ DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
+ return NULL;
+ }
+
+/* If the pubkey limits use to specified hashes, reject unusable
+signatures. XXX should we have looked for multiple dns records? */
+
+if (p->hashes)
+ {
+ const uschar * list = p->hashes, * ele;
+ int sep = ':';
+
+ while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
+ if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break;
+ if (!ele)
+ {
+ DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
+ p->hashes, (int)al->a.len, al->a.data);
+ return NULL;
+ }
+ }
+return p;
+}
+
+
+
+
+static pdkim_bodyhash *
+arc_ams_setup_vfy_bodyhash(arc_line * ams)
+{
+int canon_head, canon_body;
+long bodylen;
+
+if (!ams->c.data) ams->c.data = US"simple"; /* RFC 6376 (DKIM) default */
+pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
+bodylen = ams->l.data
+ ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
+
+return pdkim_set_bodyhash(dkim_verify_ctx,
+ pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
+ canon_body,
+ bodylen);
+}
+
+
+
+/* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
+and without a DKIM v= tag.
+*/
+
+static const uschar *
+arc_ams_verify(arc_ctx * ctx, arc_set * as)
+{
+arc_line * ams = as->hdr_ams;
+pdkim_bodyhash * b;
+pdkim_pubkey * p;
+blob sighash;
+blob hhash;
+ev_ctx vctx;
+int hashtype;
+const uschar * errstr;
+
+as->ams_verify_done = US"in-progress";
+
+/* Check the AMS has all the required tags:
+ "a=" algorithm
+ "b=" signature
+ "bh=" body hash
+ "d=" domain (for key lookup)
+ "h=" headers (included in signature)
+ "s=" key-selector (for key lookup)
+*/
+if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data
+ || !ams->h.data || !ams->s.data)
+ {
+ as->ams_verify_done = arc_state_reason = US"required tag missing";
+ return US"fail";
+ }
+
+
+/* The bodyhash should have been created earlier, and the dkim code should
+have managed calculating it during message input. Find the reference to it. */
+
+if (!(b = arc_ams_setup_vfy_bodyhash(ams)))
+ {
+ as->ams_verify_done = arc_state_reason = US"internal hash setup error";
+ return US"fail";
+ }
+
+DEBUG(D_acl)
+ {
+ debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n"
+ " Body %.*s computed: ",
+ as->instance, b->signed_body_bytes,
+ (int)ams->a_hash.len, ams->a_hash.data);
+ pdkim_hexprint(CUS b->bh.data, b->bh.len);
+ }
+
+/* We know the bh-tag blob is of a nul-term string, so safe as a string */
+
+if ( !ams->bh.data
+ || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
+ || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
+ )
+ {
+ DEBUG(D_acl)
+ {
+ debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance);
+ pdkim_hexprint(sighash.data, sighash.len);
+ debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance);
+ }
+ return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare";
+ }
+
+DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
+
+/* Get the public key from DNS */
+
+if (!(p = arc_line_to_pubkey(ams)))
+ return as->ams_verify_done = arc_state_reason = US"pubkey problem";
+
+/* We know the b-tag blob is of a nul-term string, so safe as a string */
+pdkim_decode_base64(ams->b.data, &sighash);
+
+arc_get_verify_hhash(ctx, ams, &hhash);
+
+/* Setup the interface to the signing library */
+
+if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
+ {
+ DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
+ as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
+ return US"fail";
+ }
+
+hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
+
+if ((errstr = exim_dkim_verify(&vctx,
+ pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
+ {
+ DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
+ return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
+ }
+
+DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
+as->ams_verify_passed = TRUE;
+return NULL;
+}
+
+
+
+/* Check the sets are instance-continuous and that all
+members are present. Check that no arc_seals are "fail".
+Set the highest instance number global.
+Verify the latest AMS.
+*/
+static uschar *
+arc_headers_check(arc_ctx * ctx)
+{
+arc_set * as;
+int inst;
+BOOL ams_fail_found = FALSE;
+
+if (!(as = ctx->arcset_chain_last))
+ return US"none";
+
+for(inst = as->instance; as; as = as->prev, inst--)
+ {
+ if (as->instance != inst)
+ arc_state_reason = string_sprintf("i=%d (sequence; expected %d)",
+ as->instance, inst);
+ else if (!as->hdr_aar || !as->hdr_ams || !as->hdr_as)
+ arc_state_reason = string_sprintf("i=%d (missing header)", as->instance);
+ else if (arc_cv_match(as->hdr_as, US"fail"))
+ arc_state_reason = string_sprintf("i=%d (cv)", as->instance);
+ else
+ goto good;
+
+ DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason);
+ return US"fail";
+
+ good:
+ /* Evaluate the oldest-pass AMS validation while we're here.
+ It does not affect the AS chain validation but is reported as
+ auxilary info. */
+
+ if (!ams_fail_found)
+ if (arc_ams_verify(ctx, as))
+ ams_fail_found = TRUE;
+ else
+ arc_oldest_pass = inst;
+ arc_state_reason = NULL;
+ }
+if (inst != 0)
+ {
+ arc_state_reason = string_sprintf("(sequence; expected i=%d)", inst);
+ DEBUG(D_acl) debug_printf("ARC chain fail %s\n", arc_state_reason);
+ return US"fail";
+ }
+
+arc_received = ctx->arcset_chain_last;
+arc_received_instance = arc_received->instance;
+
+/* We can skip the latest-AMS validation, if we already did it. */
+
+as = ctx->arcset_chain_last;
+if (!as->ams_verify_passed)
+ {
+ if (as->ams_verify_done)
+ {
+ arc_state_reason = as->ams_verify_done;
+ return US"fail";
+ }
+ if (!!arc_ams_verify(ctx, as))
+ return US"fail";
+ }
+return NULL;
+}
+
+
+/******************************************************************************/
+static const uschar *
+arc_seal_verify(arc_ctx * ctx, arc_set * as)
+{
+arc_line * hdr_as = as->hdr_as;
+arc_set * as2;
+int hashtype;
+hctx hhash_ctx;
+blob hhash_computed;
+blob sighash;
+ev_ctx vctx;
+pdkim_pubkey * p;
+const uschar * errstr;
+
+DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
+/*
+ 1. If the value of the "cv" tag on that seal is "fail", the
+ chain state is "fail" and the algorithm stops here. (This
+ step SHOULD be skipped if the earlier step (2.1) was
+ performed) [it was]
+
+ 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
+ == "none" && i != 1)) then the chain state is "fail" and the
+ algorithm stops here (note that the ordering of the logic is
+ structured for short-circuit evaluation).
+*/
+
+if ( as->instance == 1 && !arc_cv_match(hdr_as, US"none")
+ || arc_cv_match(hdr_as, US"none") && as->instance != 1
+ )
+ {
+ arc_state_reason = US"seal cv state";
+ return US"fail";
+ }
+
+/*
+ 3. Initialize a hash function corresponding to the "a" tag of
+ the ARC-Seal.
+*/
+
+hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
+
+if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+ {
+ DEBUG(D_acl)
+ debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
+ arc_state_reason = US"seal hash setup error";
+ return US"fail";
+ }
+
+/*
+ 4. Compute the canonicalized form of the ARC header fields, in
+ the order described in Section 5.4.2, using the "relaxed"
+ header canonicalization defined in Section 3.4.2 of
+ [RFC6376]. Pass the canonicalized result to the hash
+ function.
+
+Headers are CRLF-separated, but the last one is not crlf-terminated.
+*/
+
+DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n");
+for (as2 = ctx->arcset_chain;
+ as2 && as2->instance <= as->instance;
+ as2 = as2->next)
+ {
+ arc_line * al;
+ uschar * s;
+ int len;
+
+ al = as2->hdr_aar;
+ if (!(s = al->relaxed))
+ al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+ al->complete->slen, TRUE);
+ len = Ustrlen(s);
+ DEBUG(D_acl) pdkim_quoteprint(s, len);
+ exim_sha_update(&hhash_ctx, s, len);
+
+ al = as2->hdr_ams;
+ if (!(s = al->relaxed))
+ al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+ al->complete->slen, TRUE);
+ len = Ustrlen(s);
+ DEBUG(D_acl) pdkim_quoteprint(s, len);
+ exim_sha_update(&hhash_ctx, s, len);
+
+ al = as2->hdr_as;
+ if (as2->instance == as->instance)
+ s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
+ al->rawsig_no_b_val.len, FALSE);
+ else if (!(s = al->relaxed))
+ al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+ al->complete->slen, TRUE);
+ len = Ustrlen(s);
+ DEBUG(D_acl) pdkim_quoteprint(s, len);
+ exim_sha_update(&hhash_ctx, s, len);
+ }
+
+/*
+ 5. Retrieve the final digest from the hash function.
+*/
+
+exim_sha_finish(&hhash_ctx, &hhash_computed);
+DEBUG(D_acl)
+ {
+ debug_printf("ARC i=%d AS Header %.*s computed: ",
+ as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data);
+ pdkim_hexprint(hhash_computed.data, hhash_computed.len);
+ }
+
+
+/*
+ 6. Retrieve the public key identified by the "s" and "d" tags in
+ the ARC-Seal, as described in Section 4.1.6.
+*/
+
+if (!(p = arc_line_to_pubkey(hdr_as)))
+ return US"pubkey problem";
+
+/*
+ 7. Determine whether the signature portion ("b" tag) of the ARC-
+ Seal and the digest computed above are valid according to the
+ public key. (See also Section Section 8.4 for failure case
+ handling)
+
+ 8. If the signature is not valid, the chain state is "fail" and
+ the algorithm stops here.
+*/
+
+/* We know the b-tag blob is of a nul-term string, so safe as a string */
+pdkim_decode_base64(hdr_as->b.data, &sighash);
+
+if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx)))
+ {
+ DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
+ return US"fail";
+ }
+
+hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
+
+if ((errstr = exim_dkim_verify(&vctx,
+ pdkim_hashes[hashtype].exim_hashmethod,
+ &hhash_computed, &sighash)))
+ {
+ DEBUG(D_acl)
+ debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
+ arc_state_reason = US"seal sigverify error";
+ return US"fail";
+ }
+
+DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance);
+return NULL;
+}
+
+
+static const uschar *
+arc_verify_seals(arc_ctx * ctx)
+{
+arc_set * as = ctx->arcset_chain_last;
+
+if (!as)
+ return US"none";
+
+for ( ; as; as = as->prev) if (arc_seal_verify(ctx, as)) return US"fail";
+
+DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n");
+return NULL;
+}
+/******************************************************************************/
+
+/* Do ARC verification. Called from DATA ACL, on a verify = arc
+condition. No arguments; we are checking globals.
+
+Return: The ARC state, or NULL on error.
+*/
+
+const uschar *
+acl_verify_arc(void)
+{
+const uschar * res;
+
+memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx));
+
+if (!dkim_verify_ctx)
+ {
+ DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n");
+ return NULL;
+ }
+
+/* AS evaluation, per
+https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
+*/
+/* 1. Collect all ARC sets currently on the message. If there were
+ none, the ARC state is "none" and the algorithm stops here.
+*/
+
+if ((res = arc_vfy_collect_hdrs(&arc_verify_ctx)))
+ goto out;
+
+/* 2. If the form of any ARC set is invalid (e.g., does not contain
+ exactly one of each of the three ARC-specific header fields),
+ then the chain state is "fail" and the algorithm stops here.
+
+ 1. To avoid the overhead of unnecessary computation and delay
+ from crypto and DNS operations, the cv value for all ARC-
+ Seal(s) MAY be checked at this point. If any of the values
+ are "fail", then the overall state of the chain is "fail" and
+ the algorithm stops here.
+
+ 3. Conduct verification of the ARC-Message-Signature header field
+ bearing the highest instance number. If this verification fails,
+ then the chain state is "fail" and the algorithm stops here.
+*/
+
+if ((res = arc_headers_check(&arc_verify_ctx)))
+ goto out;
+
+/* 4. For each ARC-Seal from the "N"th instance to the first, apply the
+ following logic:
+
+ 1. If the value of the "cv" tag on that seal is "fail", the
+ chain state is "fail" and the algorithm stops here. (This
+ step SHOULD be skipped if the earlier step (2.1) was
+ performed)
+
+ 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv
+ == "none" && i != 1)) then the chain state is "fail" and the
+ algorithm stops here (note that the ordering of the logic is
+ structured for short-circuit evaluation).
+
+ 3. Initialize a hash function corresponding to the "a" tag of
+ the ARC-Seal.
+
+ 4. Compute the canonicalized form of the ARC header fields, in
+ the order described in Section 5.4.2, using the "relaxed"
+ header canonicalization defined in Section 3.4.2 of
+ [RFC6376]. Pass the canonicalized result to the hash
+ function.
+
+ 5. Retrieve the final digest from the hash function.
+
+ 6. Retrieve the public key identified by the "s" and "d" tags in
+ the ARC-Seal, as described in Section 4.1.6.
+
+ 7. Determine whether the signature portion ("b" tag) of the ARC-
+ Seal and the digest computed above are valid according to the
+ public key. (See also Section Section 8.4 for failure case
+ handling)
+
+ 8. If the signature is not valid, the chain state is "fail" and
+ the algorithm stops here.
+
+ 5. If all seals pass validation, then the chain state is "pass", and
+ the algorithm is complete.
+*/
+
+if ((res = arc_verify_seals(&arc_verify_ctx)))
+ goto out;
+
+res = US"pass";
+
+out:
+ return res;
+}
+
+/******************************************************************************/
+
+/* Prepend the header to the rlist */
+
+static hdr_rlist *
+arc_rlist_entry(hdr_rlist * list, const uschar * s, int len)
+{
+hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line));
+header_line * h = r->h = (header_line *)(r+1);
+
+r->prev = list;
+r->used = FALSE;
+h->next = NULL;
+h->type = 0;
+h->slen = len;
+h->text = US s;
+
+/* This works for either NL or CRLF lines; also nul-termination */
+while (*++s)
+ if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break;
+s++; /* move past end of line */
+
+return r;
+}
+
+
+/* Walk the given headers strings identifying each header, and construct
+a reverse-order list.
+*/
+
+static hdr_rlist *
+arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders)
+{
+const uschar * s;
+hdr_rlist * rheaders = NULL;
+
+s = sigheaders ? sigheaders->s : NULL;
+if (s) while (*s)
+ {
+ const uschar * s2 = s;
+
+ /* This works for either NL or CRLF lines; also nul-termination */
+ while (*++s2)
+ if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break;
+ s2++; /* move past end of line */
+
+ rheaders = arc_rlist_entry(rheaders, s, s2 - s);
+ s = s2;
+ }
+return rheaders;
+}
+
+
+
+/* Return the A-R content, without identity, with line-ending and
+NUL termination. */
+
+static BOOL
+arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret)
+{
+header_line * h;
+int ilen = Ustrlen(identity);
+
+ret->data = NULL;
+for(h = headers; h; h = h->next)
+ {
+ uschar * s = h->text, c;
+ int len = h->slen;
+
+ if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue;
+ s += HDRLEN_AR, len -= HDRLEN_AR; /* header name */
+ while ( len > 0
+ && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
+ s++, len--; /* FWS */
+ if (Ustrncmp(s, identity, ilen) != 0) continue;
+ s += ilen; len -= ilen; /* identity */
+ if (len <= 0) continue;
+ if ((c = *s) && c == ';') s++, len--; /* identity terminator */
+ while ( len > 0
+ && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n'))
+ s++, len--; /* FWS */
+ if (len <= 0) continue;
+ ret->data = s;
+ ret->len = len;
+ return TRUE;
+ }
+return FALSE;
+}
+
+
+
+/* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */
+
+static gstring *
+arc_sign_append_aar(gstring * g, arc_ctx * ctx,
+ const uschar * identity, int instance, blob * ar)
+{
+int aar_off = g ? g->ptr : 0;
+arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line));
+arc_line * al = (arc_line *)(as+1);
+header_line * h = (header_line *)(al+1);
+
+g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR);
+g = string_fmt_append(g, " i=%d; %s;\r\n\t", instance, identity);
+g = string_catn(g, US ar->data, ar->len);
+
+h->slen = g->ptr - aar_off;
+h->text = g->s + aar_off;
+al->complete = h;
+as->next = NULL;
+as->prev = ctx->arcset_chain_last;
+as->instance = instance;
+as->hdr_aar = al;
+if (instance == 1)
+ ctx->arcset_chain = as;
+else
+ ctx->arcset_chain_last->next = as;
+ctx->arcset_chain_last = as;
+
+DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text);
+return g;
+}
+
+
+
+static BOOL
+arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
+ blob * sig, const uschar * why)
+{
+hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
+ ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
+blob hhash;
+es_ctx sctx;
+const uschar * errstr;
+
+DEBUG(D_transport)
+ {
+ hctx hhash_ctx;
+ debug_printf("ARC: %s header data for signing:\n", why);
+ pdkim_quoteprint(hdata->s, hdata->ptr);
+
+ (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
+ exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
+ exim_sha_finish(&hhash_ctx, &hhash);
+ debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len);
+ }
+
+if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
+ {
+ hctx hhash_ctx;
+ (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
+ exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
+ exim_sha_finish(&hhash_ctx, &hhash);
+ }
+else
+ {
+ hhash.data = hdata->s;
+ hhash.len = hdata->ptr;
+ }
+
+if ( (errstr = exim_dkim_signing_init(privkey, &sctx))
+ || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
+ {
+ log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
+ DEBUG(D_transport)
+ debug_printf("private key, or private-key file content, was: '%s'\n",
+ privkey);
+ return FALSE;
+ }
+return TRUE;
+}
+
+
+
+static gstring *
+arc_sign_append_sig(gstring * g, blob * sig)
+{
+/*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/
+sig->data = pdkim_encode_base64(sig);
+sig->len = Ustrlen(sig->data);
+for (;;)
+ {
+ int len = MIN(sig->len, 74);
+ g = string_catn(g, sig->data, len);
+ if ((sig->len -= len) == 0) break;
+ sig->data += len;
+ g = string_catn(g, US"\r\n\t ", 5);
+ }
+g = string_catn(g, US";\r\n", 3);
+gstring_reset_unused(g);
+string_from_gstring(g);
+return g;
+}
+
+
+/* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */
+
+static gstring *
+arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
+ const uschar * identity, const uschar * selector, blob * bodyhash,
+ hdr_rlist * rheaders, const uschar * privkey, unsigned options)
+{
+uschar * s;
+gstring * hdata = NULL;
+int col;
+int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
+blob sig;
+int ams_off;
+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
+header_line * h = (header_line *)(al+1);
+
+/* debug_printf("%s\n", __FUNCTION__); */
+
+/* Construct the to-be-signed AMS pseudo-header: everything but the sig. */
+
+ams_off = g->ptr;
+g = string_fmt_append(g, "%s i=%d; a=rsa-sha256; c=relaxed; d=%s; s=%s",
+ ARC_HDR_AMS, instance, identity, selector); /*XXX hardwired a= */
+if (options & ARC_SIGN_OPT_TSTAMP)
+ g = string_fmt_append(g, "; t=%lu", (u_long)now);
+if (options & ARC_SIGN_OPT_EXPIRE)
+ g = string_fmt_append(g, "; x=%lu", (u_long)expire);
+g = string_fmt_append(g, ";\r\n\tbh=%s;\r\n\th=",
+ pdkim_encode_base64(bodyhash));
+
+for(col = 3; rheaders; rheaders = rheaders->prev)
+ {
+ const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS;
+ uschar * name, * htext = rheaders->h->text;
+ int sep = ':';
+
+ /* Spot headers of interest */
+
+ while ((name = string_nextinlist(&hnames, &sep, NULL, 0)))
+ {
+ int len = Ustrlen(name);
+ if (strncasecmp(CCS htext, CCS name, len) == 0)
+ {
+ /* If too long, fold line in h= field */
+
+ if (col + len > 78) g = string_catn(g, US"\r\n\t ", 5), col = 3;
+
+ /* Add name to h= list */
+
+ g = string_catn(g, name, len);
+ g = string_catn(g, US":", 1);
+ col += len + 1;
+
+ /* Accumulate header for hashing/signing */
+
+ hdata = string_cat(hdata,
+ pdkim_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */
+ break;
+ }
+ }
+ }
+
+/* Lose the last colon from the h= list */
+
+if (g->s[g->ptr - 1] == ':') g->ptr--;
+
+g = string_catn(g, US";\r\n\tb=;", 7);
+
+/* Include the pseudo-header in the accumulation */
+
+s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
+hdata = string_cat(hdata, s);
+
+/* Calculate the signature from the accumulation */
+/*XXX does that need further relaxation? there are spaces embedded in the b= strings! */
+
+if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS"))
+ return NULL;
+
+/* Lose the trailing semicolon from the psuedo-header, and append the signature
+(folded over lines) and termination to complete it. */
+
+g->ptr--;
+g = arc_sign_append_sig(g, &sig);
+
+h->slen = g->ptr - ams_off;
+h->text = g->s + ams_off;
+al->complete = h;
+ctx->arcset_chain_last->hdr_ams = al;
+
+DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text);
+return g;
+}
+
+
+
+/* Look for an arc= result in an A-R header blob. We know that its data
+happens to be a NUL-term string. */
+
+static uschar *
+arc_ar_cv_status(blob * ar)
+{
+const uschar * resinfo = ar->data;
+int sep = ';';
+uschar * methodspec, * s;
+
+while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0)))
+ if (Ustrncmp(methodspec, US"arc=", 4) == 0)
+ {
+ uschar c;
+ for (s = methodspec += 4;
+ (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++;
+ return string_copyn(methodspec, s - methodspec);
+ }
+return US"none";
+}
+
+
+
+/* Build the AS header and prepend it */
+
+static gstring *
+arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx,
+ int instance, const uschar * identity, const uschar * selector, blob * ar,
+ const uschar * privkey, unsigned options)
+{
+gstring * arcset;
+arc_set * as;
+uschar * status = arc_ar_cv_status(ar);
+arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line));
+header_line * h = (header_line *)(al+1);
+
+gstring * hdata = NULL;
+int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */
+blob sig;
+
+/*
+- Generate AS
+ - no body coverage
+ - no h= tag; implicit coverage
+ - arc status from A-R
+ - if fail:
+ - coverage is just the new ARC set
+ including self (but with an empty b= in self)
+ - if non-fail:
+ - all ARC set headers, set-number order, aar then ams then as,
+ including self (but with an empty b= in self)
+*/
+
+/* Construct the AS except for the signature */
+
+arcset = string_append(NULL, 9,
+ ARC_HDR_AS,
+ US" i=", string_sprintf("%d", instance),
+ US"; cv=", status,
+ US"; a=rsa-sha256; d=", identity, /*XXX hardwired */
+ US"; s=", selector); /*XXX same as AMS */
+if (options & ARC_SIGN_OPT_TSTAMP)
+ arcset = string_append(arcset, 2,
+ US"; t=", string_sprintf("%lu", (u_long)now));
+arcset = string_cat(arcset,
+ US";\r\n\t b=;");
+
+h->slen = arcset->ptr;
+h->text = arcset->s;
+al->complete = h;
+ctx->arcset_chain_last->hdr_as = al;
+
+/* For any but "fail" chain-verify status, walk the entire chain in order by
+instance. For fail, only the new arc-set. Accumulate the elements walked. */
+
+for (as = Ustrcmp(status, US"fail") == 0
+ ? ctx->arcset_chain_last : ctx->arcset_chain;
+ as; as = as->next)
+ {
+ /* Accumulate AAR then AMS then AS. Relaxed canonicalisation
+ is required per standard. */
+
+ h = as->hdr_aar->complete;
+ hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+ h = as->hdr_ams->complete;
+ hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+ h = as->hdr_as->complete;
+ hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
+ }
+
+/* Calculate the signature from the accumulation */
+
+if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS"))
+ return NULL;
+
+/* Lose the trailing semicolon */
+arcset->ptr--;
+arcset = arc_sign_append_sig(arcset, &sig);
+DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s);
+
+/* Finally, append the AMS and AAR to the new AS */
+
+return string_catn(arcset, arcset_interim->s, arcset_interim->ptr);
+}
+
+
+/**************************************/
+
+/* Return pointer to pdkim_bodyhash for given hash method, creating new
+method if needed.
+*/
+
+void *
+arc_ams_setup_sign_bodyhash(void)
+{
+int canon_head, canon_body;
+
+DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
+pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body); /*XXX hardwired */
+return pdkim_set_bodyhash(&dkim_sign_ctx,
+ pdkim_hashname_to_hashtype(US"sha256", 6), /*XXX hardwired */
+ canon_body,
+ -1);
+}
+
+
+
+void
+arc_sign_init(void)
+{
+memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx));
+}
+
+
+
+/* A "normal" header line, identified by DKIM processing. These arrive before
+the call to arc_sign(), which carries any newly-created DKIM headers - and
+those go textually before the normal ones in the message.
+
+We have to take the feed from DKIM as, in the transport-filter case, the
+headers are not in memory at the time of the call to arc_sign().
+
+Take a copy of the header and construct a reverse-order list.
+Also parse ARC-chain headers and build the chain struct, retaining pointers
+into the copies.
+*/
+
+static const uschar *
+arc_header_sign_feed(gstring * g)
+{
+uschar * s = string_copyn(g->s, g->ptr);
+headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr);
+return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);
+}
+
+
+
+/* ARC signing. Called from the smtp transport, if the arc_sign option is set.
+The dkim_exim_sign() function has already been called, so will have hashed the
+message body for us so long as we requested a hash previously.
+
+Arguments:
+ signspec Three-element colon-sep list: identity, selector, privkey.
+ Optional fourth element: comma-sep list of options.
+ Already expanded
+ sigheaders Any signature headers already generated, eg. by DKIM, or NULL
+ errstr Error string
+
+Return value
+ Set of headers to prepend to the message, including the supplied sigheaders
+ but not the plainheaders.
+*/
+
+gstring *
+arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr)
+{
+const uschar * identity, * selector, * privkey, * opts, * s;
+unsigned options = 0;
+int sep = 0;
+header_line * headers;
+hdr_rlist * rheaders;
+blob ar;
+int instance;
+gstring * g = NULL;
+pdkim_bodyhash * b;
+
+expire = now = 0;
+
+/* Parse the signing specification */
+
+identity = string_nextinlist(&signspec, &sep, NULL, 0);
+selector = string_nextinlist(&signspec, &sep, NULL, 0);
+if ( !*identity || !*selector
+ || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
+ {
+ log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)",
+ !*identity ? "identity" : !*selector ? "selector" : "private-key");
+ return sigheaders ? sigheaders : string_get(0);
+ }
+if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
+ return sigheaders ? sigheaders : string_get(0);
+
+if ((opts = string_nextinlist(&signspec, &sep, NULL, 0)))
+ {
+ int osep = ',';
+ while ((s = string_nextinlist(&opts, &osep, NULL, 0)))
+ if (Ustrcmp(s, "timestamps") == 0)
+ {
+ options |= ARC_SIGN_OPT_TSTAMP;
+ if (!now) now = time(NULL);
+ }
+ else if (Ustrncmp(s, "expire", 6) == 0)
+ {
+ options |= ARC_SIGN_OPT_EXPIRE;
+ if (*(s += 6) == '=')
+ if (*++s == '+')
+ {
+ if (!(expire = (time_t)atoi(CS ++s)))
+ expire = ARC_SIGN_DEFAULT_EXPIRE_DELTA;
+ if (!now) now = time(NULL);
+ expire += now;
+ }
+ else
+ expire = (time_t)atol(CS s);
+ else
+ {
+ if (!now) now = time(NULL);
+ expire = now + ARC_SIGN_DEFAULT_EXPIRE_DELTA;
+ }
+ }
+ }
+
+DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity);
+
+/* Make an rlist of any new DKIM headers, then add the "normals" rlist to it.
+Then scan the list for an A-R header. */
+
+string_from_gstring(sigheaders);
+if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders)))
+ {
+ hdr_rlist ** rp;
+ for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev;
+ *rp = rheaders;
+ }
+
+/* Finally, build a normal-order headers list */
+/*XXX only needed for hunt-the-AR? */
+/*XXX also, we really should be accepting any number of ADMD-matching ARs */
+ {
+ header_line * hnext = NULL;
+ for (rheaders = headers_rlist; rheaders;
+ hnext = rheaders->h, rheaders = rheaders->prev)
+ rheaders->h->next = hnext;
+ headers = hnext;
+ }
+
+if (!(arc_sign_find_ar(headers, identity, &ar)))
+ {
+ log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing");
+ return sigheaders ? sigheaders : string_get(0);
+ }
+
+/* We previously built the data-struct for the existing ARC chain, if any, using a headers
+feed from the DKIM module. Use that to give the instance number for the ARC set we are
+about to build. */
+
+DEBUG(D_transport)
+ if (arc_sign_ctx.arcset_chain_last)
+ debug_printf("ARC: existing chain highest instance: %d\n",
+ arc_sign_ctx.arcset_chain_last->instance);
+ else
+ debug_printf("ARC: no existing chain\n");
+
+instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1;
+
+/*
+- Generate AAR
+ - copy the A-R; prepend i= & identity
+*/
+
+g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
+
+/*
+- Generate AMS
+ - Looks fairly like a DKIM sig
+ - Cover all DKIM sig headers as well as the usuals
+ - ? oversigning?
+ - Covers the data
+ - we must have requested a suitable bodyhash previously
+*/
+
+b = arc_ams_setup_sign_bodyhash();
+g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector,
+ &b->bh, headers_rlist, privkey, options);
+
+/*
+- Generate AS
+ - no body coverage
+ - no h= tag; implicit coverage
+ - arc status from A-R
+ - if fail:
+ - coverage is just the new ARC set
+ including self (but with an empty b= in self)
+ - if non-fail:
+ - all ARC set headers, set-number order, aar then ams then as,
+ including self (but with an empty b= in self)
+*/
+
+if (g)
+ g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar,
+ privkey, options);
+
+/* Finally, append the dkim headers and return the lot. */
+
+if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr);
+(void) string_from_gstring(g);
+gstring_reset_unused(g);
+return g;
+}
+
+
+/******************************************************************************/
+
+/* Check to see if the line is an AMS and if so, set up to validate it.
+Called from the DKIM input processing. This must be done now as the message
+body data is hashed during input.
+
+We call the DKIM code to request a body-hash; it has the facility already
+and the hash parameters might be common with other requests.
+*/
+
+static const uschar *
+arc_header_vfy_feed(gstring * g)
+{
+header_line h;
+arc_line al;
+pdkim_bodyhash * b;
+uschar * errstr;
+
+if (!dkim_verify_ctx) return US"no dkim context";
+
+if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
+
+DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
+/* Parse the AMS header */
+
+h.next = NULL;
+h.slen = g->size;
+h.text = g->s;
+memset(&al, 0, sizeof(arc_line));
+if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE)))
+ {
+ DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr);
+ return US"line parsing error";
+ }
+
+/* defaults */
+if (!al.c.data)
+ {
+ al.c_body.data = US"simple"; al.c_body.len = 6;
+ al.c_head = al.c_body;
+ }
+
+/* Ask the dkim code to calc a bodyhash with those specs */
+
+if (!(b = arc_ams_setup_vfy_bodyhash(&al)))
+ return US"dkim hash setup fail";
+
+/* Discard the reference; search again at verify time, knowing that one
+should have been created here. */
+
+return NULL;
+}
+
+
+
+/* A header line has been identified by DKIM processing.
+
+Arguments:
+ g Header line
+ is_vfy TRUE for verify mode or FALSE for signing mode
+
+Return:
+ NULL for success, or an error string (probably unused)
+*/
+
+const uschar *
+arc_header_feed(gstring * g, BOOL is_vfy)
+{
+return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g);
+}
+
+
+
+/******************************************************************************/
+
+/* Construct the list of domains from the ARC chain after validation */
+
+uschar *
+fn_arc_domains(void)
+{
+arc_set * as;
+unsigned inst;
+gstring * g = NULL;
+
+for (as = arc_verify_ctx.arcset_chain, inst = 1; as; as = as->next, inst++)
+ {
+ arc_line * hdr_as = as->hdr_as;
+ if (hdr_as)
+ {
+ blob * d = &hdr_as->d;
+
+ for (; inst < as->instance; inst++)
+ g = string_catn(g, US":", 1);
+
+ g = d->data && d->len
+ ? string_append_listele_n(g, ':', d->data, d->len)
+ : string_catn(g, US":", 1);
+ }
+ else
+ g = string_catn(g, US":", 1);
+ }
+return g ? g->s : US"";
+}
+
+
+/* Construct an Authentication-Results header portion, for the ARC module */
+
+gstring *
+authres_arc(gstring * g)
+{
+if (arc_state)
+ {
+ arc_line * highest_ams;
+ int start = 0; /* Compiler quietening */
+ DEBUG(D_acl) start = g->ptr;
+
+ g = string_append(g, 2, US";\n\tarc=", arc_state);
+ if (arc_received_instance > 0)
+ {
+ g = string_fmt_append(g, " (i=%d)", arc_received_instance);
+ if (arc_state_reason)
+ g = string_append(g, 3, US"(", arc_state_reason, US")");
+ g = string_catn(g, US" header.s=", 10);
+ highest_ams = arc_received->hdr_ams;
+ g = string_catn(g, highest_ams->s.data, highest_ams->s.len);
+
+ g = string_fmt_append(g, " arc.oldest-pass=%d", arc_oldest_pass);
+
+ if (sender_host_address)
+ g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
+ }
+ else if (arc_state_reason)
+ g = string_append(g, 3, US" (", arc_state_reason, US")");
+ DEBUG(D_acl) debug_printf("ARC: authres '%.*s'\n",
+ g->ptr - start - 3, g->s + start + 3);
+ }
+else
+ DEBUG(D_acl) debug_printf("ARC: no authres\n");
+return g;
+}
+
+
+# endif /* SUPPORT_SPF */
+#endif /* EXPERIMENTAL_ARC */
+/* vi: aw ai sw=2
+ */
The third function performs authentication as a client. It receives a pointer
to the instance block, and four further arguments:
- The smtp_inblock item for the connection to the remote host.
+ The smtp_context item for the connection to the remote host.
The normal command-reading timeout value.
challenge_str = argv [3];
- if (spa_base64_to_bits ((char *)&challenge, sizeof(challenge),
- (const char *)(challenge_str))<0)
+ if (spa_base64_to_bits (CS &challenge, sizeof(challenge),
+ CCS (challenge_str))<0)
{
printf("bad base64 data in challenge: %s\n", challenge_str);
exit (1);
*/
/* get single value from an SMB buffer */
-# define SVAL(buf,pos) (*(uint16x *)((char *)(buf) + (pos)))
-# define IVAL(buf,pos) (*(uint32x *)((char *)(buf) + (pos)))
-# define SVALS(buf,pos) (*(int16x *)((char *)(buf) + (pos)))
-# define IVALS(buf,pos) (*(int32x *)((char *)(buf) + (pos)))
+# define SVAL(buf,pos) (*(uint16x *)(CS (buf) + (pos)))
+# define IVAL(buf,pos) (*(uint32x *)(CS (buf) + (pos)))
+# define SVALS(buf,pos) (*(int16x *)(CS (buf) + (pos)))
+# define IVALS(buf,pos) (*(int32x *)(CS (buf) + (pos)))
/* store single value in an SMB buffer */
# define SSVAL(buf,pos,val) SVAL(buf,pos)=((uint16x)(val))
memset (p21, '\0', 21);
memset (p14, '\0', 14);
- StrnCpy ((char *) p14, (char *) passwd, 14);
+ StrnCpy (CS p14, CS passwd, 14);
- strupper ((char *) p14);
+ strupper (CS p14);
E_P16 (p14, p21);
SMBOWFencrypt (p21, c8, p24);
#ifdef DEBUG_PASSWORD
DEBUG_X (100, ("spa_smb_encrypt: lm#, challenge, response\n"));
- dump_data (100, (char *) p21, 16);
- dump_data (100, (char *) c8, 8);
- dump_data (100, (char *) p24, 24);
+ dump_data (100, CS p21, 16);
+ dump_data (100, CS c8, 8);
+ dump_data (100, CS p24, 24);
#endif
}
int16x wpwd[129];
/* Password cannot be longer than 128 characters */
- len = strlen ((char *) passwd);
+ len = strlen (CS passwd);
if (len > 128)
len = 128;
/* Password must be converted to NT unicode */
#ifdef DEBUG_PASSWORD
DEBUG_X (100, ("nt_lm_owf_gen: pwd, nt#\n"));
dump_data (120, passwd, strlen (passwd));
- dump_data (100, (char *) nt_p16, 16);
+ dump_data (100, CS nt_p16, 16);
#endif
/* Mangle the passwords into Lanman format */
#ifdef DEBUG_PASSWORD
DEBUG_X (100, ("nt_lm_owf_gen: pwd, lm#\n"));
dump_data (120, passwd, strlen (passwd));
- dump_data (100, (char *) p16, 16);
+ dump_data (100, CS p16, 16);
#endif
/* clear out local copy of user's password (just being paranoid). */
memset (passwd, '\0', sizeof (passwd));
E_P24 (p21, ntlmchalresp, p24);
#ifdef DEBUG_PASSWORD
DEBUG_X (100, ("NTLMSSPOWFencrypt: p21, c8, p24\n"));
- dump_data (100, (char *) p21, 21);
- dump_data (100, (char *) ntlmchalresp, 8);
- dump_data (100, (char *) p24, 24);
+ dump_data (100, CS p21, 21);
+ dump_data (100, CS ntlmchalresp, 8);
+ dump_data (100, CS p24, 24);
#endif
}
#ifdef DEBUG_PASSWORD
DEBUG_X (100, ("spa_smb_nt_encrypt: nt#, challenge, response\n"));
- dump_data (100, (char *) p21, 16);
- dump_data (100, (char *) c8, 8);
- dump_data (100, (char *) p24, 24);
+ dump_data (100, CS p21, 16);
+ dump_data (100, CS c8, 8);
+ dump_data (100, CS p24, 24);
#endif
}
#define spa_bytes_add(ptr, header, buf, count) \
{ \
-if (buf != NULL && count) \
+if (buf != NULL && count != 0) /* we hate -Wint-in-bool-contex */ \
{ \
SSVAL(&ptr->header.len,0,count); \
SSVAL(&ptr->header.maxlen,0,count); \
#define GetUnicodeString(structPtr, header) \
unicodeToString(((char*)structPtr) + IVAL(&structPtr->header.offset,0) , SVAL(&structPtr->header.len,0)/2)
#define GetString(structPtr, header) \
-toString((((char *)structPtr) + IVAL(&structPtr->header.offset,0)), SVAL(&structPtr->header.len,0))
+toString(((CS structPtr) + IVAL(&structPtr->header.offset,0)), SVAL(&structPtr->header.len,0))
#ifdef notdef
}
else domain = d = strdup((cf & 0x1)?
- (const char *)GetUnicodeString(challenge, uDomain) :
- (const char *)GetString(challenge, uDomain));
+ CCS GetUnicodeString(challenge, uDomain) :
+ CCS GetString(challenge, uDomain));
spa_smb_encrypt (US password, challenge->challengeData, lmRespData);
spa_smb_nt_encrypt (US password, challenge->challengeData, ntRespData);
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
return OK;
}
-*errptr = (uschar *)pam_strerror(pamh, pam_error);
+*errptr = US pam_strerror(pamh, pam_error);
DEBUG(D_auth) debug_printf("PAM error: %s\n", *errptr);
if (pam_error == PAM_USER_UNKNOWN ||
if (cond == NULL)
{
- if (expand_string_forcedfail) return FAIL;
+ if (f.expand_string_forcedfail) return FAIL;
auth_defer_msg = expand_string_message;
return DEFER;
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
};
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_cram_md5_init(auth_instance *ablock) {}
+int auth_cram_md5_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_cram_md5_client(auth_instance *ablock, void *sx, int timeout,
+ uschar *buffer, int buffsize) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
/*************************************************
* Initialization entry point *
*************************************************/
}
}
+#endif /*!MACRO_PREDEF*/
#endif /* STAND_ALONE */
+#ifndef MACRO_PREDEF
/*************************************************
* Perform the CRAM-MD5 algorithm *
*************************************************/
if (len > 64)
{
md5_start(&base);
- md5_end(&base, (uschar *)secret, len, md5secret);
- secret = (uschar *)md5secret;
+ md5_end(&base, US secret, len, md5secret);
+ secret = US md5secret;
len = 16;
}
md5_start(&base);
md5_mid(&base, isecret);
-md5_end(&base, (uschar *)challenge, Ustrlen(challenge), md5secret);
+md5_end(&base, US challenge, Ustrlen(challenge), md5secret);
/* Compute the outer MD5 digest */
/* If we are running in the test harness, always send the same challenge,
an example string taken from the RFC. */
-if (running_in_test_harness)
+if (f.running_in_test_harness)
challenge = US"<1896.697170952@postoffice.reston.mci.net>";
/* No data should have been sent with the AUTH command */
if (secret == NULL)
{
- if (expand_string_forcedfail) return FAIL;
+ if (f.expand_string_forcedfail) return FAIL;
auth_defer_msg = expand_string_message;
return DEFER;
}
int
auth_cram_md5_client(
auth_instance *ablock, /* authenticator block */
- smtp_inblock *inblock, /* input connection */
- smtp_outblock *outblock, /* output connection */
+ void * sx, /* smtp connextion */
int timeout, /* command timeout */
uschar *buffer, /* for reading response */
int buffsize) /* size of buffer */
if (!secret || !name)
{
- if (expand_string_forcedfail)
+ if (f.expand_string_forcedfail)
{
*buffer = 0; /* No message */
return CANCELLED;
/* Initiate the authentication exchange and read the challenge, which arrives
in base 64. */
-if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n", ablock->public_name) < 0)
+if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
return FAIL_SEND;
-if (!smtp_read_response(inblock, buffer, buffsize, '3', timeout))
+if (!smtp_read_response(sx, buffer, buffsize, '3', timeout))
return FAIL;
if (b64decode(buffer + 4, &challenge) < 0)
*p++ = ' ';
for (i = 0; i < 16; i++)
- {
- sprintf(CS p, "%02x", digest[i]);
- p += 2;
- }
+ p += sprintf(CS p, "%02x", digest[i]);
/* Send the response, in base 64, and check the result. The response is
in big_buffer, but b64encode() returns its result in working store,
so calling smtp_write_command(), which uses big_buffer, is OK. */
buffer[0] = 0;
-if (smtp_write_command(outblock, FALSE, "%s\r\n", b64encode(big_buffer,
+if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(big_buffer,
p - big_buffer)) < 0) return FAIL_SEND;
-return smtp_read_response(inblock, (uschar *)buffer, buffsize, '2', timeout)
+return smtp_read_response(sx, US buffer, buffsize, '2', timeout)
? OK : FAIL;
}
#endif /* STAND_ALONE */
#endif
+#endif /*!MACRO_PREDEF*/
/* End of cram_md5.c */
extern void auth_cram_md5_init(auth_instance *);
extern int auth_cram_md5_server(auth_instance *, uschar *);
-extern int auth_cram_md5_client(auth_instance *, smtp_inblock *,
- smtp_outblock *, int, uschar *, int);
+extern int auth_cram_md5_client(auth_instance *, void *, int, uschar *, int);
/* End of cram_md5.h */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* This code was originally contributed by Matthew Byng-Maddick */
};
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_cyrus_sasl_init(auth_instance *ablock) {}
+int auth_cyrus_sasl_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_cyrus_sasl_client(auth_instance *ablock, void * sx,
+ int timeout, uschar *buffer, int buffsize) {return 0;}
+void auth_cyrus_sasl_version_report(FILE *f) {}
+
+#else /*!MACRO_PREDEF*/
+
+
+
+
/*************************************************
* Initialization entry point *
*************************************************/
char *realm_expanded;
sasl_conn_t *conn;
-sasl_callback_t cbs[]={
+sasl_callback_t cbs[] = {
{SASL_CB_GETOPT, NULL, NULL },
{SASL_CB_LIST_END, NULL, NULL}};
/* default the mechanism to our "public name" */
-if(ob->server_mech == NULL)
- ob->server_mech=string_copy(ablock->public_name);
+if (ob->server_mech == NULL)
+ ob->server_mech = string_copy(ablock->public_name);
expanded_hostname = expand_string(ob->server_hostname);
if (expanded_hostname == NULL)
"couldn't expand server_hostname [%s]: %s",
ablock->name, ob->server_hostname, expand_string_message);
-realm_expanded=NULL;
+realm_expanded = NULL;
if (ob->server_realm != NULL) {
realm_expanded = CS expand_string(ob->server_realm);
if (realm_expanded == NULL)
cbs[0].proc = (int(*)(void)) &mysasl_config;
cbs[0].context = ob->server_mech;
-rc=sasl_server_init(cbs, "exim");
-
-if( rc != SASL_OK )
+if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK )
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
"couldn't initialise Cyrus SASL library.", ablock->name);
-rc=sasl_server_new(CS ob->server_service, CS expanded_hostname,
- realm_expanded, NULL, NULL, NULL, 0, &conn);
-if( rc != SASL_OK )
+if ((rc = sasl_server_new(CS ob->server_service, CS expanded_hostname,
+ realm_expanded, NULL, NULL, NULL, 0, &conn)) != SASL_OK )
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
"couldn't initialise Cyrus SASL server connection.", ablock->name);
-rc=sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i);
-if( rc != SASL_OK )
+if ((rc = sasl_listmech(conn, NULL, "", ":", "", (const char **)&list, &len, &i)) != SASL_OK )
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
"couldn't get Cyrus SASL mechanism list.", ablock->name);
-i=':';
-listptr=list;
+i = ':';
+listptr = list;
-HDEBUG(D_auth) {
+HDEBUG(D_auth)
+ {
debug_printf("Initialised Cyrus SASL service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
ob->server_service, expanded_hostname, realm_expanded);
debug_printf("Cyrus SASL knows mechanisms: %s\n", list);
-}
+ }
/* the store_get / store_reset mechanism is hierarchical
* the hierarchy is stored for us behind our back. This point
* creates a hierarchy point for this function.
*/
-rs_point=store_get(0);
+rs_point = store_get(0);
/* loop until either we get to the end of the list, or we match the
* public name of this authenticator
*/
-while( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
+while ( ( buffer = string_nextinlist(&listptr, &i, NULL, 0) ) &&
strcmpic(buffer,ob->server_mech) );
-if(!buffer)
+if (!buffer)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
"Cyrus SASL doesn't know about mechanism %s.", ablock->name, ob->server_mech);
(auth_cyrus_sasl_options_block *)(ablock->options_block);
uschar *output, *out2, *input, *clear, *hname;
uschar *debug = NULL; /* Stops compiler complaining */
-sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
+sasl_callback_t cbs[] = {{SASL_CB_LIST_END, NULL, NULL}};
sasl_conn_t *conn;
-char *realm_expanded;
-int rc, i, firsttime=1, clen, *negotiated_ssf_ptr=NULL, negotiated_ssf;
+char * realm_expanded = NULL;
+int rc, i, firsttime = 1, clen, *negotiated_ssf_ptr = NULL, negotiated_ssf;
unsigned int inlen, outlen;
-input=data;
-inlen=Ustrlen(data);
+input = data;
+inlen = Ustrlen(data);
-HDEBUG(D_auth) debug=string_copy(data);
+HDEBUG(D_auth) debug = string_copy(data);
-hname=expand_string(ob->server_hostname);
-realm_expanded=NULL;
+hname = expand_string(ob->server_hostname);
if (hname && ob->server_realm)
- realm_expanded= CS expand_string(ob->server_realm);
-if((hname == NULL) ||
- ((realm_expanded == NULL) && (ob->server_realm != NULL)))
+ realm_expanded = CS expand_string(ob->server_realm);
+if (!hname || !realm_expanded && ob->server_realm)
{
auth_defer_msg = expand_string_message;
return DEFER;
}
-if(inlen)
+if (inlen)
{
- clen = b64decode(input, &clear);
- if(clen < 0)
- {
+ if ((clen = b64decode(input, &clear)) < 0)
return BAD64;
- }
- input=clear;
- inlen=clen;
+ input = clear;
+ inlen = clen;
}
-rc=sasl_server_init(cbs, "exim");
-if (rc != SASL_OK)
+if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
{
auth_defer_msg = US"couldn't initialise Cyrus SASL library";
return DEFER;
}
-rc=sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
+rc = sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
NULL, NULL, 0, &conn);
HDEBUG(D_auth)
debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
ob->server_service, hname, realm_expanded);
-if( rc != SASL_OK )
+if (rc != SASL_OK )
{
auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
sasl_done();
if (tls_in.cipher)
{
- rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits);
- if (rc != SASL_OK)
+ if ((rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits)) != SASL_OK)
{
HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
tls_in.bits, sasl_errstring(rc, NULL, NULL));
So the docs are too strict and we shouldn't worry about :: contractions. */
/* Set properties for remote and local host-ip;port */
-for (i=0; i < 2; ++i)
+for (i = 0; i < 2; ++i)
{
struct sockaddr_storage ss;
int (*query)(int, struct sockaddr *, socklen_t *);
}
sslen = sizeof(ss);
- rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen);
- if (rc < 0)
+ if ((rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen)) < 0)
{
HDEBUG(D_auth)
debug_printf("Failed to get %s address information: %s\n",
address = host_ntoa(-1, &ss, NULL, &port);
address_port = string_sprintf("%s;%d", address, port);
- rc = sasl_setprop(conn, propnum, address_port);
- if (rc != SASL_OK)
+ if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK)
{
s_err = sasl_errdetail(conn);
HDEBUG(D_auth)
label, address_port);
}
-rc=SASL_CONTINUE;
-
-while(rc==SASL_CONTINUE)
+for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
{
- if(firsttime)
+ if (firsttime)
{
- firsttime=0;
+ firsttime = 0;
HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
- rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
+ rc = sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
(const char **)(&output), &outlen);
}
else
{
/* make sure that we have a null-terminated string */
- out2=store_get(outlen+1);
- memcpy(out2,output,outlen);
- out2[outlen]='\0';
- if((rc=auth_get_data(&input, out2, outlen))!=OK)
+ out2 = string_copyn(output, outlen);
+
+ if ((rc = auth_get_data(&input, out2, outlen)) != OK)
{
/* we couldn't get the data, so free up the library before
* returning whatever error we get */
sasl_done();
return rc;
}
- inlen=Ustrlen(input);
+ inlen = Ustrlen(input);
- HDEBUG(D_auth) debug=string_copy(input);
- if(inlen)
+ HDEBUG(D_auth) debug = string_copy(input);
+ if (inlen)
{
- clen = b64decode(input, &clear);
- if(clen < 0)
+ if ((clen = b64decode(input, &clear)) < 0)
{
- sasl_dispose(&conn);
- sasl_done();
+ sasl_dispose(&conn);
+ sasl_done();
return BAD64;
}
- input=clear;
- inlen=clen;
+ input = clear;
+ inlen = clen;
}
HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
- rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
+ rc = sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
}
- if(rc==SASL_BADPROT)
+
+ if (rc == SASL_BADPROT)
{
sasl_dispose(&conn);
sasl_done();
return UNEXPECTED;
}
- else if( rc==SASL_FAIL || rc==SASL_BUFOVER
- || rc==SASL_BADMAC || rc==SASL_BADAUTH
- || rc==SASL_NOAUTHZ || rc==SASL_ENCRYPT
- || rc==SASL_EXPIRED || rc==SASL_DISABLED
- || rc==SASL_NOUSER )
+ if (rc == SASL_CONTINUE)
+ continue;
+
+ /* Get the username and copy it into $auth1 and $1. The former is now the
+ preferred variable; the latter is the original variable. */
+
+ if ((sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2))) != SASL_OK)
{
- /* these are considered permanent failure codes */
HDEBUG(D_auth)
- debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
+ debug_printf("Cyrus SASL library will not tell us the username: %s\n",
+ sasl_errstring(rc, NULL, NULL));
log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
- "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
+ "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
sasl_errstring(rc, NULL, NULL));
sasl_dispose(&conn);
sasl_done();
return FAIL;
}
- else if(rc==SASL_NOMECH)
- {
- /* this is a temporary failure, because the mechanism is not
- * available for this user. If it wasn't available at all, we
- * shouldn't have got here in the first place...
- */
- HDEBUG(D_auth)
- debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
- auth_defer_msg =
- string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
- sasl_dispose(&conn);
- sasl_done();
- return DEFER;
- }
- else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
- {
- /* Anything else is a temporary failure, and we'll let SASL print out
- * the error string for us
- */
- HDEBUG(D_auth)
- debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
- auth_defer_msg =
- string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
- sasl_dispose(&conn);
- sasl_done();
- return DEFER;
- }
- else if(rc==SASL_OK)
+ auth_vars[0] = expand_nstring[1] = string_copy(out2);
+ expand_nlength[1] = Ustrlen(out2);
+ expand_nmax = 1;
+
+ switch (rc)
{
- /* Get the username and copy it into $auth1 and $1. The former is now the
- preferred variable; the latter is the original variable. */
- rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
- if (rc != SASL_OK)
- {
+ case SASL_FAIL: case SASL_BUFOVER: case SASL_BADMAC: case SASL_BADAUTH:
+ case SASL_NOAUTHZ: case SASL_ENCRYPT: case SASL_EXPIRED:
+ case SASL_DISABLED: case SASL_NOUSER:
+ /* these are considered permanent failure codes */
HDEBUG(D_auth)
- debug_printf("Cyrus SASL library will not tell us the username: %s\n",
- sasl_errstring(rc, NULL, NULL));
+ debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
- "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
- sasl_errstring(rc, NULL, NULL));
+ "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
+ sasl_errstring(rc, NULL, NULL));
sasl_dispose(&conn);
sasl_done();
return FAIL;
- }
-
- auth_vars[0] = expand_nstring[1] = string_copy(out2);
- expand_nlength[1] = Ustrlen(expand_nstring[1]);
- expand_nmax = 1;
-
- HDEBUG(D_auth)
- debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
- ob->server_mech, auth_vars[0]);
- rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr));
- if (rc != SASL_OK)
- {
+ case SASL_NOMECH:
+ /* this is a temporary failure, because the mechanism is not
+ * available for this user. If it wasn't available at all, we
+ * shouldn't have got here in the first place...
+ */
HDEBUG(D_auth)
- debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
- sasl_errstring(rc, NULL, NULL));
- log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
- "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
- sasl_errstring(rc, NULL, NULL));
+ debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
+ auth_defer_msg =
+ string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
sasl_dispose(&conn);
sasl_done();
- return FAIL;
- }
- negotiated_ssf = *negotiated_ssf_ptr;
- HDEBUG(D_auth)
- debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
- if (negotiated_ssf > 0)
- {
+ return DEFER;
+
+ case SASL_OK:
HDEBUG(D_auth)
- debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
- log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
- "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
+ debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
+ ob->server_mech, auth_vars[0]);
+
+ if ((rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr)))!= SASL_OK)
+ {
+ HDEBUG(D_auth)
+ debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
+ sasl_errstring(rc, NULL, NULL));
+ log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
+ "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
+ sasl_errstring(rc, NULL, NULL));
+ sasl_dispose(&conn);
+ sasl_done();
+ return FAIL;
+ }
+ negotiated_ssf = *negotiated_ssf_ptr;
+ HDEBUG(D_auth)
+ debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
+ if (negotiated_ssf > 0)
+ {
+ HDEBUG(D_auth)
+ debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
+ log_write(0, LOG_REJECT, "%s authenticator (%s):\n "
+ "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
+ sasl_dispose(&conn);
+ sasl_done();
+ return FAIL;
+ }
+
+ /* close down the connection, freeing up library's memory */
sasl_dispose(&conn);
sasl_done();
- return FAIL;
- }
- /* close down the connection, freeing up library's memory */
- sasl_dispose(&conn);
- sasl_done();
+ /* Expand server_condition as an authorization check */
+ return auth_check_serv_cond(ablock);
- /* Expand server_condition as an authorization check */
- return auth_check_serv_cond(ablock);
+ default:
+ /* Anything else is a temporary failure, and we'll let SASL print out
+ * the error string for us
+ */
+ HDEBUG(D_auth)
+ debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
+ auth_defer_msg =
+ string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
+ sasl_dispose(&conn);
+ sasl_done();
+ return DEFER;
}
}
/* NOTREACHED */
void
auth_cyrus_sasl_version_report(FILE *f)
{
- const char *implementation, *version;
- sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
- fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
- " Runtime: %s [%s]\n",
- SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
- version, implementation);
+const char *implementation, *version;
+sasl_version_info(&implementation, &version, NULL, NULL, NULL, NULL);
+fprintf(f, "Library version: Cyrus SASL: Compile: %d.%d.%d\n"
+ " Runtime: %s [%s]\n",
+ SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP,
+ version, implementation);
}
/*************************************************
int
auth_cyrus_sasl_client(
auth_instance *ablock, /* authenticator block */
- smtp_inblock *inblock, /* input connection */
- smtp_outblock *outblock, /* output connection */
+ void * sx, /* connexction */
int timeout, /* command timeout */
uschar *buffer, /* for reading response */
int buffsize) /* size of buffer */
return FAIL;
}
+#endif /*!MACRO_PREDEF*/
#endif /* AUTH_CYRUS_SASL */
/* End of cyrus_sasl.c */
extern void auth_cyrus_sasl_init(auth_instance *);
extern int auth_cyrus_sasl_server(auth_instance *, uschar *);
-extern int auth_cyrus_sasl_client(auth_instance *, smtp_inblock *,
- smtp_outblock *, int, uschar *, int);
+extern int auth_cyrus_sasl_client(auth_instance *, void *, int, uschar *, int);
extern void auth_cyrus_sasl_version_report(FILE *f);
/* End of cyrus_sasl.h */
/*
* Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
- * Copyright (c) 2006-2016 The Exim Maintainers
+ * Copyright (c) 2006-2017 The Exim Maintainers
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
};
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_dovecot_init(auth_instance *ablock) {}
+int auth_dovecot_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_dovecot_client(auth_instance *ablock, void * sx,
+ int timeout, uschar *buffer, int buffsize) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
/* Static variables for reading from the socket */
static uschar sbuffer[256];
"AUTH\t%d\t%s\trip=%s\tlip=%s\tresp=%s\r\n",
VERSION_MAJOR, VERSION_MINOR, getpid(), cuid,
ablock->public_name, sender_host_address, interface_address,
- data ? (char *) data : "");
+ data ? CS data : "");
Subsequently, the command was modified to add "secured" and "valid-client-
cert" when relevant.
/* Expand server_condition as an authorization check */
return ret == OK ? auth_check_serv_cond(ablock) : ret;
}
+
+
+#endif /*!MACRO_PREDEF*/
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
{
int c;
int p = 0;
-smtp_printf("334 %s\r\n", b64encode(challenge, challen));
+smtp_printf("334 %s\r\n", FALSE, b64encode(challenge, challen));
while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
{
if (p >= big_buffer_size - 1) return BAD64;
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
{
int c;
int p = 0;
-smtp_printf("334 %s\r\n", challenge);
+smtp_printf("334 %s\r\n", FALSE, challenge);
while ((c = receive_getc(GETC_BUFFER_UNLIMITED)) != '\n' && c != EOF)
{
if (p >= big_buffer_size - 1) return BAD64;
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) Twitter Inc 2012
FALSE /* server_channelbinding */
};
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_gsasl_init(auth_instance *ablock) {}
+int auth_gsasl_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_gsasl_client(auth_instance *ablock, smtp_inblock * sx,
+ int timeout, uschar *buffer, int buffsize) {return 0;}
+void auth_gsasl_version_report(FILE *f) {}
+
+#else /*!MACRO_PREDEF*/
+
+
+
/* "Globals" for managing the gsasl interface. */
static Gsasl *gsasl_ctx = NULL;
ablock->name, gsasl_strerror_name(rc), gsasl_strerror(rc));
HDEBUG(D_auth) debug_printf("GNU SASL supports: %s\n", p);
- supported = gsasl_client_support_p(gsasl_ctx, (const char *)ob->server_mech);
+ supported = gsasl_client_support_p(gsasl_ctx, CCS ob->server_mech);
if (!supported)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s authenticator: "
"GNU SASL does not support mechanism \"%s\"",
debug_printf("GNU SASL: initialising session for %s, mechanism %s.\n",
ablock->name, ob->server_mech);
- rc = gsasl_server_start(gsasl_ctx, (const char *)ob->server_mech, &sctx);
+ rc = gsasl_server_start(gsasl_ctx, CCS ob->server_mech, &sctx);
if (rc != GSASL_OK) {
auth_defer_msg = string_sprintf("GNU SASL: session start failure: %s (%s)",
gsasl_strerror_name(rc), gsasl_strerror(rc));
gsasl_property_set(sctx, GSASL_QOPS, "qop-auth");
#ifdef SUPPORT_TLS
if (tls_channelbinding_b64) {
- /* Some auth mechanisms can ensure that both sides are talking withing the
+ /* Some auth mechanisms can ensure that both sides are talking within the
same security context; for TLS, this means that even if a bad certificate
has been accepted, they remain MitM-proof because both sides must be within
the same negotiated session; if someone is terminating one session and
HDEBUG(D_auth) debug_printf("Auth %s: Enabling channel-binding\n",
ablock->name);
gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE,
- (const char *) tls_channelbinding_b64);
+ CCS tls_channelbinding_b64);
} else {
HDEBUG(D_auth)
debug_printf("Auth %s: Not enabling channel-binding (data available)\n",
if ((rc == GSASL_NEEDS_MORE) ||
(to_send && *to_send))
exim_error =
- auth_get_no64_data((uschar **)&received, (uschar *)to_send);
+ auth_get_no64_data((uschar **)&received, US to_send);
if (to_send) {
free(to_send);
switch (prop) {
case GSASL_VALIDATE_SIMPLE:
/* GSASL_AUTHID, GSASL_AUTHZID, and GSASL_PASSWORD */
- propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHID);
+ propval = US gsasl_property_fast(sctx, GSASL_AUTHID);
auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
- propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
+ propval = US gsasl_property_fast(sctx, GSASL_AUTHZID);
auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
- propval = (uschar *) gsasl_property_fast(sctx, GSASL_PASSWORD);
+ propval = US gsasl_property_fast(sctx, GSASL_PASSWORD);
auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
expand_nmax = 3;
for (i = 1; i <= 3; ++i)
cbrc = GSASL_AUTHENTICATION_ERROR;
break;
}
- propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
+ propval = US gsasl_property_fast(sctx, GSASL_AUTHZID);
/* We always set $auth1, even if only to empty string. */
auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
expand_nlength[1] = Ustrlen(expand_nstring[1]);
cbrc = GSASL_AUTHENTICATION_ERROR;
break;
}
- propval = (uschar *) gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN);
+ propval = US gsasl_property_fast(sctx, GSASL_ANONYMOUS_TOKEN);
/* We always set $auth1, even if only to empty string. */
auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
expand_nlength[1] = Ustrlen(expand_nstring[1]);
First coding, we had these values swapped, but for consistency and prior
to the first release of Exim with this authenticator, they've been
switched to match the ordering of GSASL_VALIDATE_SIMPLE. */
- propval = (uschar *) gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
+ propval = US gsasl_property_fast(sctx, GSASL_GSSAPI_DISPLAY_NAME);
auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
- propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
+ propval = US gsasl_property_fast(sctx, GSASL_AUTHZID);
auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
expand_nmax = 2;
for (i = 1; i <= 2; ++i)
a new mechanism is added to the library. It *shouldn't* result in us
needing to add more glue, since avoiding that is a large part of the
point of SASL. */
- propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHID);
+ propval = US gsasl_property_fast(sctx, GSASL_AUTHID);
auth_vars[0] = expand_nstring[1] = propval ? propval : US"";
- propval = (uschar *) gsasl_property_fast(sctx, GSASL_AUTHZID);
+ propval = US gsasl_property_fast(sctx, GSASL_AUTHZID);
auth_vars[1] = expand_nstring[2] = propval ? propval : US"";
- propval = (uschar *) gsasl_property_fast(sctx, GSASL_REALM);
+ propval = US gsasl_property_fast(sctx, GSASL_REALM);
auth_vars[2] = expand_nstring[3] = propval ? propval : US"";
expand_nmax = 3;
for (i = 1; i <= 3; ++i)
tmps = CS expand_string(ob->server_password);
if (tmps == NULL) {
- sasl_error_should_defer = expand_string_forcedfail ? FALSE : TRUE;
+ sasl_error_should_defer = f.expand_string_forcedfail ? FALSE : TRUE;
HDEBUG(D_auth) debug_printf("server_password expansion failed, so "
"can't tell GNU SASL library the password for %s\n", auth_vars[0]);
return GSASL_AUTHENTICATION_ERROR;
int
auth_gsasl_client(
- auth_instance *ablock, /* authenticator block */
- smtp_inblock *inblock, /* connection inblock */
- smtp_outblock *outblock, /* connection outblock */
- int timeout, /* command timeout */
- uschar *buffer, /* buffer for reading response */
- int buffsize) /* size of buffer */
+ auth_instance *ablock, /* authenticator block */
+ smtp_inblock * sx, /* connection */
+ int timeout, /* command timeout */
+ uschar *buffer, /* buffer for reading response */
+ int buffsize) /* size of buffer */
{
HDEBUG(D_auth)
debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
GSASL_VERSION, runtime);
}
+#endif /*!MACRO_PREDEF*/
#endif /* AUTH_GSASL */
/* End of gsasl_exim.c */
extern void auth_gsasl_init(auth_instance *);
extern int auth_gsasl_server(auth_instance *, uschar *);
extern int auth_gsasl_client(auth_instance *, smtp_inblock *,
- smtp_outblock *, int, uschar *, int);
+ int, uschar *, int);
extern void auth_gsasl_version_report(FILE *f);
/* End of gsasl_exim.h */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) Twitter Inc 2012
US"smtp", /* server_service */
};
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_heimdal_gssapi_init(auth_instance *ablock) {}
+int auth_heimdal_gssapi_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_heimdal_gssapi_client(auth_instance *ablock, void * sx,
+ int timeout, uschar *buffer, int buffsize) {return 0;}
+void auth_heimdal_gssapi_version_report(FILE *f) {}
+
+#else /*!MACRO_PREDEF*/
+
+
+
/* "Globals" for managing the heimdal_gssapi interface. */
/* Utility functions */
error_out = FAIL;
goto ERROR_OUT;
}
- if (&gbufdesc_out.length != 0) {
+ if (gbufdesc_out.length != 0) {
error_out = auth_get_data(&from_client,
gbufdesc_out.value, gbufdesc_out.length);
if (error_out != OK)
const char *format, ...)
{
va_list ap;
- uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
OM_uint32 maj_stat, min_stat;
OM_uint32 msgcontext = 0;
gss_buffer_desc status_string;
+ gstring * g;
- va_start(ap, format);
- if (!string_vformat(buffer, sizeof(buffer), format, ap))
- log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "exim_gssapi_error_defer expansion larger than %lu",
- sizeof(buffer));
- va_end(ap);
+ HDEBUG(D_auth)
+ {
+ va_start(ap, format);
+ g = string_vformat(NULL, TRUE, format, ap);
+ va_end(ap);
+ }
auth_defer_msg = NULL;
do {
maj_stat = gss_display_status(&min_stat,
- major, GSS_C_GSS_CODE, GSS_C_NO_OID,
- &msgcontext, &status_string);
+ major, GSS_C_GSS_CODE, GSS_C_NO_OID, &msgcontext, &status_string);
- if (auth_defer_msg == NULL) {
+ if (!auth_defer_msg)
auth_defer_msg = string_copy(US status_string.value);
- }
HDEBUG(D_auth) debug_printf("heimdal %s: %.*s\n",
- buffer, (int)status_string.length, CS status_string.value);
+ string_from_gstring(g), (int)status_string.length,
+ CS status_string.value);
gss_release_buffer(&min_stat, &status_string);
} while (msgcontext != 0);
int
auth_heimdal_gssapi_client(
auth_instance *ablock, /* authenticator block */
- smtp_inblock *inblock, /* connection inblock */
- smtp_outblock *outblock, /* connection outblock */
+ void * sx, /* connection */
int timeout, /* command timeout */
uschar *buffer, /* buffer for reading response */
int buffsize) /* size of buffer */
heimdal_version, heimdal_long_version);
}
+#endif /*!MACRO_PREDEF*/
#endif /* AUTH_HEIMDAL_GSSAPI */
/* End of heimdal_gssapi.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#ifndef STAND_ALONE
{
md5 base;
int i = 0x01020304;
-uschar *ctest = (uschar *)(&i);
+uschar *ctest = US (&i);
uschar buffer[256];
uschar digest[16];
printf("Checking md5: %s-endian\n", (ctest[0] == 0x04)? "little" : "big");
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
};
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_plaintext_init(auth_instance *ablock) {}
+int auth_plaintext_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_plaintext_client(auth_instance *ablock, void * sx, int timeout,
+ uschar *buffer, int buffsize) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
/*************************************************
* Initialization entry point *
*************************************************/
int
auth_plaintext_client(
auth_instance *ablock, /* authenticator block */
- smtp_inblock *inblock, /* connection inblock */
- smtp_outblock *outblock, /* connection outblock */
+ void * sx, /* smtp connextion */
int timeout, /* command timeout */
uschar *buffer, /* buffer for reading response */
int buffsize) /* size of buffer */
sent one by one. The first one is sent with the AUTH command; the remainder are
sent in response to subsequent prompts. Each is expanded before being sent. */
-while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)) != NULL)
+while ((s = string_nextinlist(&text, &sep, big_buffer, big_buffer_size)))
{
int i, len, clear_len;
uschar *ss = expand_string(s);
sending a line containing "*". Save the failed expansion string, because it
is in big_buffer, and that gets used by the sending function. */
- if (ss == NULL)
+ if (!ss)
{
uschar *ssave = string_copy(s);
if (!first)
{
- if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
- (void) smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
+ if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
+ (void) smtp_read_response(sx, US buffer, buffsize, '2', timeout);
}
- if (expand_string_forcedfail)
+ if (f.expand_string_forcedfail)
{
*buffer = 0; /* No message */
return CANCELLED;
needed for the PLAIN mechanism. It must be doubled if really needed. */
for (i = 0; i < len; i++)
- {
if (ss[i] == '^')
- {
- if (ss[i+1] != '^') ss[i] = 0; else
+ if (ss[i+1] != '^')
+ ss[i] = 0;
+ else
{
i++;
len--;
memmove(ss + i, ss + i + 1, len - i);
}
- }
- }
/* The first string is attached to the AUTH command; others are sent
unembellished. */
if (first)
{
first = FALSE;
- if (smtp_write_command(outblock, FALSE, "AUTH %s%s%s\r\n",
- ablock->public_name, (len == 0)? "" : " ",
- b64encode(ss, len)) < 0)
+ if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s%s%s\r\n",
+ ablock->public_name, len == 0 ? "" : " ", b64encode(ss, len)) < 0)
return FAIL_SEND;
}
else
{
- if (smtp_write_command(outblock, FALSE, "%s\r\n",
- b64encode(ss, len)) < 0)
+ if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", b64encode(ss, len)) < 0)
return FAIL_SEND;
}
has succeeded. There may be more data to send, but is there any point
in provoking an error here? */
- if (smtp_read_response(inblock, US buffer, buffsize, '2', timeout)) return OK;
+ if (smtp_read_response(sx, US buffer, buffsize, '2', timeout)) return OK;
/* Not a success response. If errno != 0 there is some kind of transmission
error. Otherwise, check the response code in the buffer. If it starts with
/* If there is no more data to send, we have to cancel the authentication
exchange and return ERROR. */
- if (text == NULL)
+ if (!text)
{
- if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
- (void)smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
+ if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
+ (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
string_format(buffer, buffsize, "Too few items in client_send in %s "
"authenticator", ablock->name);
return ERROR;
uschar *save_bad = string_copy(buffer);
if (!ob->client_ignore_invalid_base64)
{
- if (smtp_write_command(outblock, FALSE, "*\r\n") >= 0)
- (void)smtp_read_response(inblock, US buffer, buffsize, '2', timeout);
+ if (smtp_write_command(sx, SCMD_FLUSH, "*\r\n") >= 0)
+ (void)smtp_read_response(sx, US buffer, buffsize, '2', timeout);
string_format(buffer, buffsize, "Invalid base64 string in server "
"response \"%s\"", save_bad);
return CANCELLED;
return FAIL;
}
+#endif /*!MACRO_PREDEF*/
/* End of plaintext.c */
extern void auth_plaintext_init(auth_instance *);
extern int auth_plaintext_server(auth_instance *, uschar *);
-extern int auth_plaintext_client(auth_instance *, smtp_inblock *,
- smtp_outblock *, int, uschar *, int);
+extern int auth_plaintext_client(auth_instance *, void *, int, uschar *, int);
/* End of plaintext.h */
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == -1) { return PWCHECK_FAIL; }
- memset((char *)&srvaddr, 0, sizeof(srvaddr));
+ memset(CS &srvaddr, 0, sizeof(srvaddr));
srvaddr.sun_family = AF_UNIX;
strncpy(srvaddr.sun_path, CYRUS_PWCHECK_SOCKET, sizeof(srvaddr.sun_path));
r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
return PWCHECK_FAIL;
}
- iov[0].iov_base = (char *)userid;
+ iov[0].iov_base = CS userid;
iov[0].iov_len = strlen(userid)+1;
- iov[1].iov_base = (char *)passwd;
+ iov[1].iov_base = CS passwd;
iov[1].iov_len = strlen(passwd)+1;
retry_writev(s, iov, 2);
return PWCHECK_FAIL;
}
- memset((char *)&srvaddr, 0, sizeof(srvaddr));
+ memset(CS &srvaddr, 0, sizeof(srvaddr));
srvaddr.sun_family = AF_UNIX;
strncpy(srvaddr.sun_path, CYRUS_SASLAUTHD_SOCKET,
sizeof(srvaddr.sun_path));
{
int n;
int nread = 0;
- char *buf = (char *)inbuf;
+ char *buf = CS inbuf;
if (nbyte == 0) return 0;
for (i = 0; i < iovcnt; i++) {
if (iov[i].iov_len > (unsigned) n) {
- iov[i].iov_base = (char *)iov[i].iov_base + n;
+ iov[i].iov_base = CS iov[i].iov_base + n;
iov[i].iov_len -= n;
break;
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* This file, which provides support for Microsoft's Secure Password
};
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_spa_init(auth_instance *ablock) {}
+int auth_spa_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_spa_client(auth_instance *ablock, void * sx, int timeout,
+ uschar *buffer, int buffsize) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
+
/*************************************************
* Initialization entry point *
*************************************************/
/* For interface, see auths/README */
-#define CVAL(buf,pos) (((unsigned char *)(buf))[pos])
+#define CVAL(buf,pos) ((US (buf))[pos])
#define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
#define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
#define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
return FAIL;
}
-if (spa_base64_to_bits((char *)(&request), sizeof(request), (const char *)(data)) < 0)
+if (spa_base64_to_bits(CS (&request), sizeof(request), CCS (data)) < 0)
{
DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
"request: %s\n", data);
}
/* dump client response */
-if (spa_base64_to_bits((char *)(&response), sizeof(response), (const char *)(data)) < 0)
+if (spa_base64_to_bits(CS (&response), sizeof(response), CCS (data)) < 0)
{
DEBUG(D_auth) debug_printf("auth_spa_server(): bad base64 data in "
"response: %s\n", data);
clearpass = expand_string(ob->spa_serverpassword);
if (clearpass == NULL)
{
- if (expand_string_forcedfail)
+ if (f.expand_string_forcedfail)
{
DEBUG(D_auth) debug_printf("auth_spa_server(): forced failure while "
"expanding spa_serverpassword\n");
int
auth_spa_client(
auth_instance *ablock, /* authenticator block */
- smtp_inblock *inblock, /* connection inblock */
- smtp_outblock *outblock, /* connection outblock */
+ void * sx, /* connection */
int timeout, /* command timeout */
uschar *buffer, /* buffer for reading response */
int buffsize) /* size of buffer */
{
- auth_spa_options_block *ob =
- (auth_spa_options_block *)(ablock->options_block);
- SPAAuthRequest request;
- SPAAuthChallenge challenge;
- SPAAuthResponse response;
- char msgbuf[2048];
- char *domain = NULL;
- char *username, *password;
-
- /* Code added by PH to expand the options */
-
- *buffer = 0; /* Default no message when cancelled */
-
- username = CS expand_string(ob->spa_username);
- if (username == NULL)
- {
- if (expand_string_forcedfail) return CANCELLED;
- string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
- "authenticator: %s", ob->spa_username, ablock->name,
- expand_string_message);
- return ERROR;
- }
-
- password = CS expand_string(ob->spa_password);
- if (password == NULL)
- {
- if (expand_string_forcedfail) return CANCELLED;
- string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
- "authenticator: %s", ob->spa_password, ablock->name,
- expand_string_message);
- return ERROR;
- }
-
- if (ob->spa_domain != NULL)
- {
- domain = CS expand_string(ob->spa_domain);
- if (domain == NULL)
- {
- if (expand_string_forcedfail) return CANCELLED;
- string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
- "authenticator: %s", ob->spa_domain, ablock->name,
- expand_string_message);
- return ERROR;
- }
- }
-
- /* Original code */
-
- if (smtp_write_command(outblock, FALSE, "AUTH %s\r\n",
- ablock->public_name) < 0)
- return FAIL_SEND;
-
- /* wait for the 3XX OK message */
- if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout))
- return FAIL;
-
- DSPA("\n\n%s authenticator: using domain %s\n\n",
- ablock->name, domain);
-
- spa_build_auth_request (&request, CS username, domain);
- spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
- spa_request_length(&request));
-
- DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name,
- msgbuf);
-
- /* send the encrypted password */
- if (smtp_write_command(outblock, FALSE, "%s\r\n", msgbuf) < 0)
- return FAIL_SEND;
-
- /* wait for the auth challenge */
- if (!smtp_read_response(inblock, (uschar *)buffer, buffsize, '3', timeout))
- return FAIL;
-
- /* convert the challenge into the challenge struct */
- DSPA("\n\n%s authenticator: challenge (%s)\n\n",
- ablock->name, buffer + 4);
- spa_base64_to_bits ((char *)(&challenge), sizeof(challenge), (const char *)(buffer + 4));
-
- spa_build_auth_response (&challenge, &response,
- CS username, CS password);
- spa_bits_to_base64 (US msgbuf, (unsigned char*)&response,
- spa_request_length(&response));
- DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name,
- msgbuf);
-
- /* send the challenge response */
- if (smtp_write_command(outblock, FALSE, "%s\r\n", msgbuf) < 0)
- return FAIL_SEND;
-
- /* If we receive a success response from the server, authentication
- has succeeded. There may be more data to send, but is there any point
- in provoking an error here? */
- if (smtp_read_response(inblock, US buffer, buffsize, '2', timeout))
- return OK;
-
- /* Not a success response. If errno != 0 there is some kind of transmission
- error. Otherwise, check the response code in the buffer. If it starts with
- '3', more data is expected. */
- if (errno != 0 || buffer[0] != '3')
- return FAIL;
-
- return FAIL;
+auth_spa_options_block *ob =
+ (auth_spa_options_block *)(ablock->options_block);
+SPAAuthRequest request;
+SPAAuthChallenge challenge;
+SPAAuthResponse response;
+char msgbuf[2048];
+char *domain = NULL;
+char *username, *password;
+
+/* Code added by PH to expand the options */
+
+*buffer = 0; /* Default no message when cancelled */
+
+if (!(username = CS expand_string(ob->spa_username)))
+ {
+ if (f.expand_string_forcedfail) return CANCELLED;
+ string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
+ "authenticator: %s", ob->spa_username, ablock->name,
+ expand_string_message);
+ return ERROR;
+ }
+
+if (!(password = CS expand_string(ob->spa_password)))
+ {
+ if (f.expand_string_forcedfail) return CANCELLED;
+ string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
+ "authenticator: %s", ob->spa_password, ablock->name,
+ expand_string_message);
+ return ERROR;
+ }
+
+if (ob->spa_domain)
+ if (!(domain = CS expand_string(ob->spa_domain)))
+ {
+ if (f.expand_string_forcedfail) return CANCELLED;
+ string_format(buffer, buffsize, "expansion of \"%s\" failed in %s "
+ "authenticator: %s", ob->spa_domain, ablock->name,
+ expand_string_message);
+ return ERROR;
+ }
+
+/* Original code */
+
+if (smtp_write_command(sx, SCMD_FLUSH, "AUTH %s\r\n", ablock->public_name) < 0)
+ return FAIL_SEND;
+
+/* wait for the 3XX OK message */
+if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
+ return FAIL;
+
+DSPA("\n\n%s authenticator: using domain %s\n\n", ablock->name, domain);
+
+spa_build_auth_request (&request, CS username, domain);
+spa_bits_to_base64 (US msgbuf, (unsigned char*)&request,
+ spa_request_length(&request));
+
+DSPA("\n\n%s authenticator: sending request (%s)\n\n", ablock->name, msgbuf);
+
+/* send the encrypted password */
+if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
+ return FAIL_SEND;
+
+/* wait for the auth challenge */
+if (!smtp_read_response(sx, US buffer, buffsize, '3', timeout))
+ return FAIL;
+
+/* convert the challenge into the challenge struct */
+DSPA("\n\n%s authenticator: challenge (%s)\n\n", ablock->name, buffer + 4);
+spa_base64_to_bits (CS (&challenge), sizeof(challenge), CCS (buffer + 4));
+
+spa_build_auth_response (&challenge, &response, CS username, CS password);
+spa_bits_to_base64 (US msgbuf, (unsigned char*)&response,
+ spa_request_length(&response));
+DSPA("\n\n%s authenticator: challenge response (%s)\n\n", ablock->name, msgbuf);
+
+/* send the challenge response */
+if (smtp_write_command(sx, SCMD_FLUSH, "%s\r\n", msgbuf) < 0)
+ return FAIL_SEND;
+
+/* If we receive a success response from the server, authentication
+has succeeded. There may be more data to send, but is there any point
+in provoking an error here? */
+
+if (smtp_read_response(sx, US buffer, buffsize, '2', timeout))
+ return OK;
+
+/* Not a success response. If errno != 0 there is some kind of transmission
+error. Otherwise, check the response code in the buffer. If it starts with
+'3', more data is expected. */
+
+if (errno != 0 || buffer[0] != '3')
+ return FAIL;
+
+return FAIL;
}
+#endif /*!MACRO_PREDEF*/
/* End of spa.c */
extern void auth_spa_init(auth_instance *);
extern int auth_spa_server(auth_instance *, uschar *);
-extern int auth_spa_client(auth_instance *, smtp_inblock *,
- smtp_outblock *, int, uschar *, int);
+extern int auth_spa_client(auth_instance *, void *, int, uschar *, int);
/* End of spa.h */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) Jeremy Harris 2016 */
+/* Copyright (c) Jeremy Harris 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* This file provides an Exim authenticator driver for
};
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+void auth_tls_init(auth_instance *ablock) {}
+int auth_tls_server(auth_instance *ablock, uschar *data) {return 0;}
+int auth_tls_client(auth_instance *ablock, void * sx,
+ int timeout, uschar *buffer, int buffsize) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
+
/*************************************************
* Initialization entry point *
*************************************************/
}
+#endif /*!MACRO_PREDEF*/
/* End of tls.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
auth_xtextencode(uschar *clear, int len)
{
uschar *code;
-uschar *p = (uschar *)clear;
+uschar *p = US clear;
uschar *pp;
int c = len;
int count = 1;
pp = code = store_get(count);
-p = (uschar *)clear;
+p = US clear;
c = len;
while (c-- > 0)
- {
if ((x = *p++) < 33 || x > 127 || x == '+' || x == '=')
- {
- sprintf(CS pp, "+%.02x", x); /* There's always room */
- pp += 3;
- }
- else *pp++ = x;
- }
+ pp += sprintf(CS pp, "+%.02x", x); /* There's always room */
+ else
+ *pp++ = x;
*pp = 0;
return code;
/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004, 2015 */
/* License: GPL */
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
};
int
-b64decode(uschar *code, uschar **ptr)
+b64decode(const uschar *code, uschar **ptr)
{
+
int x, y;
-uschar *result = store_get(3*(Ustrlen(code)/4) + 1);
+uschar *result;
-*ptr = result;
+{
+ int l = Ustrlen(code);
+ *ptr = result = store_get(1 + l/4 * 3 + l%4);
+}
/* Each cycle of the loop handles a quantum of 4 input bytes. For the last
quantum this may decode to 1, 2, or 3 output bytes. */
while (isspace(y = *code++)) ;
/* debug_printf("b64d: '%c'\n", y); */
- if (y == 0 || (y = dec64table[y]) == 255)
+ if (y > 127 || (y = dec64table[y]) == 255)
return -1;
*result++ = (x << 2) | (y >> 4);
while (len-- >0)
{
- register int x, y;
+ int x, y;
x = *clear++;
*p++ = enc64table[(x >> 2) & 63];
uschar *verdicts = NULL;
int i,j;
- err = bmiInitSystem(BMI_VERSION, (char *)bmi_config_file, &system);
+ err = bmiInitSystem(BMI_VERSION, CS bmi_config_file, &system);
if (bmiErrorIsFatal(err) == BMI_TRUE) {
err_loc = bmiErrorGetLocation(err);
err_type = bmiErrorGetType(err);
host_address = localhost;
else
host_address = sender_host_address;
- err = bmiProcessConnection((char *)host_address, message);
+ err = bmiProcessConnection(CS host_address, message);
if (bmiErrorIsFatal(err) == BMI_TRUE) {
err_loc = bmiErrorGetLocation(err);
err_type = bmiErrorGetType(err);
log_write(0, LOG_PANIC,
- "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, (char *)host_address);
+ "bmi error [loc %d type %d]: bmiProcessConnection() failed (IP %s).", (int)err_loc, (int)err_type, CS host_address);
bmiFreeMessage(message);
bmiFreeSystem(system);
return NULL;
};
/* Send envelope sender address */
- err = bmiProcessFROM((char *)sender_address, message);
+ err = bmiProcessFROM(CS sender_address, message);
if (bmiErrorIsFatal(err) == BMI_TRUE) {
err_loc = bmiErrorGetLocation(err);
err_type = bmiErrorGetType(err);
log_write(0, LOG_PANIC,
- "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, (char *)sender_address);
+ "bmi error [loc %d type %d]: bmiProcessFROM() failed (address %s).", (int)err_loc, (int)err_type, CS sender_address);
bmiFreeMessage(message);
bmiFreeSystem(system);
return NULL;
err = bmiOptinMset(optin, r->bmi_optin, ':');
if (bmiErrorIsFatal(err) == BMI_TRUE) {
log_write(0, LOG_PANIC|LOG_MAIN,
- "bmi warning: [loc %d type %d]: bmiOptinMSet() failed (address '%s', string '%s').", (int)err_loc, (int)err_type, (char *)r->address, (char *)r->bmi_optin);
+ "bmi warning: [loc %d type %d]: bmiOptinMSet() failed (address '%s', string '%s').", (int)err_loc, (int)err_type, CS r->address, CS r->bmi_optin);
if (optin != NULL)
bmiOptinFree(optin);
optin = NULL;
};
};
- err = bmiAccumulateTO((char *)r->address, optin, message);
+ err = bmiAccumulateTO(CS r->address, optin, message);
if (optin != NULL)
bmiOptinFree(optin);
err_loc = bmiErrorGetLocation(err);
err_type = bmiErrorGetType(err);
log_write(0, LOG_PANIC,
- "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, (char *)r->address);
+ "bmi error [loc %d type %d]: bmiAccumulateTO() failed (address %s).", (int)err_loc, (int)err_type, CS r->address);
bmiFreeMessage(message);
bmiFreeSystem(system);
return NULL;
header_list = header_list->next;
continue;
};
- err = bmiAccumulateHeaders((const char *)header_list->text, header_list->slen, message);
+ err = bmiAccumulateHeaders(CCS header_list->text, header_list->slen, message);
if (bmiErrorIsFatal(err) == BMI_TRUE) {
err_loc = bmiErrorGetLocation(err);
err_type = bmiErrorGetType(err);
do {
j = fread(data_buffer, 1, sizeof(data_buffer), data_file);
if (j > 0) {
- err = bmiAccumulateBody((const char *)data_buffer, j, message);
+ err = bmiAccumulateBody(CCS data_buffer, j, message);
if (bmiErrorIsFatal(err) == BMI_TRUE) {
err_loc = bmiErrorGetLocation(err);
err_type = bmiErrorGetType(err);
/* loop through verdicts */
verdict_ptr = bmi_verdicts;
- while ((verdict_str = (const char *)string_nextinlist(&verdict_ptr, &sep,
+ while ((verdict_str = CCS string_nextinlist(&verdict_ptr, &sep,
verdict_buffer,
Ustrlen(bmi_verdicts)+1)) != NULL) {
uschar *rcpt_domain;
/* compare address against our subject */
- rcpt_local_part = (unsigned char *)bmiRecipientAccessAddress(recipient);
+ rcpt_local_part = US bmiRecipientAccessAddress(recipient);
rcpt_domain = Ustrchr(rcpt_local_part,'@');
if (rcpt_domain == NULL) {
rcpt_domain = US"";
(strcmpic(rcpt_domain, bmi_domain) == 0) ) {
/* found verdict */
bmiFreeVerdict(verdict);
- return (uschar *)verdict_str;
+ return US verdict_str;
};
};
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
+#include <sys/time.h>
+#include <poll.h>
#include <pwd.h>
#include <grp.h>
{
off_t test_off_t = 0;
time_t test_time_t = 0;
+ino_t test_ino_t;
#if ! (__STDC_VERSION__ >= 199901L)
size_t test_size_t = 0;
ssize_t test_ssize_t = 0;
fprintf(new, "#ifndef OFF_T_FMT\n");
if (sizeof(test_off_t) > sizeof(test_long_t))
{
- fprintf(new, "#define OFF_T_FMT \"%%lld\"\n");
- fprintf(new, "#define LONGLONG_T long long int\n");
+ fprintf(new, "# define OFF_T_FMT \"%%lld\"\n");
+ fprintf(new, "# define LONGLONG_T long long int\n");
}
else
{
- fprintf(new, "#define OFF_T_FMT \"%%ld\"\n");
- fprintf(new, "#define LONGLONG_T long int\n");
+ fprintf(new, "# define OFF_T_FMT \"%%ld\"\n");
+ fprintf(new, "# define LONGLONG_T long int\n");
}
fprintf(new, "#endif\n\n");
fprintf(new, "#ifndef TIME_T_FMT\n");
if (sizeof(test_time_t) > sizeof(test_long_t))
{
- fprintf(new, "#define TIME_T_FMT \"%%lld\"\n");
- fprintf(new, "#undef LONGLONG_T\n");
- fprintf(new, "#define LONGLONG_T long long int\n");
+ fprintf(new, "# define TIME_T_FMT \"%%lld\"\n");
+ fprintf(new, "# undef LONGLONG_T\n");
+ fprintf(new, "# define LONGLONG_T long long int\n");
}
else
- {
- fprintf(new, "#define TIME_T_FMT \"%%ld\"\n");
- }
+ fprintf(new, "# define TIME_T_FMT \"%%ld\"\n");
+fprintf(new, "#endif\n\n");
+
+fprintf(new, "#ifndef INO_T_FMT\n");
+if (sizeof(test_ino_t) > sizeof(test_long_t))
+ fprintf(new, "# define INO_T_FMT \"%%llu\"\n");
+else
+ fprintf(new, "# define INO_T_FMT \"%%lu\"\n");
+fprintf(new, "#endif\n\n");
+
+fprintf(new, "#ifndef PID_T_FMT\n");
+fprintf(new, "# define PID_T_FMT \"%%lu\"\n");
fprintf(new, "#endif\n\n");
/* And for sizeof() results, size_t, which should with C99 be just %zu, deal
fprintf(new, "#define FIXED_NEVER_USERS %d", j);
for (i = 0; i < j; i++) fprintf(new, ", %d", (unsigned int)vector[i]);
fprintf(new, "\n");
+ free(vector);
}
continue;
}
"#define SUPPORT_CRYPTEQ\n");
}
+/* Check poll() for timer functionality.
+Some OS' have released with it broken. */
+
+ {
+ struct timeval before, after;
+ int rc;
+ size_t us;
+
+ gettimeofday(&before, NULL);
+ rc = poll(NULL, 0, 500);
+ gettimeofday(&after, NULL);
+
+ us = (after.tv_sec - before.tv_sec) * 1000000 +
+ (after.tv_usec - before.tv_usec);
+
+ if (us < 400000)
+ fprintf(new, "#define NO_POLL_H\n");
+ }
+
/* End off */
fprintf(new, "\n/* End of config.h */\n");
static void (*oldsignal)(int);
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+static uschar tls_requiretls_copy = 0;
+#endif
+
/*************************************************
* Ensure an fd has a given value *
int first_special = -1;
int n = 0;
int extra = pcount ? *pcount : 0;
-uschar **argv =
- store_get((extra + acount + MAX_CLMACROS + 18) * sizeof(char *));
+uschar **argv;
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls) extra++;
+#endif
+
+argv = store_get((extra + acount + MAX_CLMACROS + 18) * sizeof(char *));
/* In all case, the list starts out with the path, any macros, and a changed
config file. */
memcpy(argv + n, clmacros, clmacro_count * sizeof(uschar *));
n += clmacro_count;
}
-if (config_changed)
+if (f.config_changed)
{
argv[n++] = US"-C";
argv[n++] = config_main_filename;
if (debug_selector != 0)
argv[n++] = string_sprintf("-d=0x%x", debug_selector);
}
- if (dont_deliver) argv[n++] = US"-N";
- if (queue_smtp) argv[n++] = US"-odqs";
- if (synchronous_delivery) argv[n++] = US"-odi";
+ if (f.dont_deliver) argv[n++] = US"-N";
+ if (f.queue_smtp) argv[n++] = US"-odqs";
+ if (f.synchronous_delivery) argv[n++] = US"-odi";
if (connection_max_messages >= 0)
argv[n++] = string_sprintf("-oB%d", connection_max_messages);
if (*queue_name)
}
}
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls_copy & REQUIRETLS_MSG)
+ argv[n++] = US"-MS";
+#endif
+
/* Now add in any others that are in the call. Remember which they were,
for more helpful diagnosis on failure. */
if (pid == 0)
{
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ tls_requiretls_copy = tls_requiretls;
+#endif
force_fd(pfd[pipe_read], 0);
(void)close(pfd[pipe_write]);
if (debug_fd > 0) force_fd(debug_fd, 2);
- if (running_in_test_harness && !queue_only)
+ if (f.running_in_test_harness && !queue_only)
{
if (sender_authentication != NULL)
child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 9,
if (timeout > 0)
{
sigalrm_seen = FALSE;
- alarm(timeout);
+ ALARM(timeout);
}
for(;;)
if (rc == pid)
{
int lowbyte = status & 255;
- if (lowbyte == 0) yield = (status >> 8) & 255;
- else yield = -lowbyte;
+ yield = lowbyte == 0 ? (status >> 8) & 255 : -lowbyte;
break;
}
if (rc < 0)
{
- yield = (errno == EINTR && sigalrm_seen)? -256 : -257;
+ /* This "shouldn't happen" test does happen on MacOS: for some reason
+ I do not understand we seems to get an alarm signal despite not having
+ an active alarm set. There seems to be only one, so just go round again. */
+
+ if (errno == EINTR && sigalrm_seen && timeout <= 0) continue;
+
+ yield = (errno == EINTR && sigalrm_seen) ? -256 : -257;
break;
}
}
-if (timeout > 0) alarm(0);
+if (timeout > 0) ALARM_CLR(0);
signal(SIGCHLD, oldsignal); /* restore */
return yield;
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* The default settings for Exim configuration variables. A #define without
any data just defines the existence of the variable; it won't get included
in config.h unless some value is defined in Local/Makefile. If there is data,
-it's a default value. */
+it's a default value.
+
+Do not put spaces between # and the 'define'.
+*/
#define ALT_CONFIG_PREFIX
#define TRUSTED_CONFIG_LIST
#define FIXED_NEVER_USERS "root"
#define HAVE_CRYPT16
+#define HAVE_LOCAL_SCAN
#define HAVE_SA_LEN
#define HEADERS_CHARSET "ISO-8859-1"
#define HEADER_ADD_BUFFER_SIZE (8192 * 4)
#define STRING_SPRINTF_BUFFER_SIZE (8192 * 4)
#define SUPPORT_CRYPTEQ
+#define SUPPORT_DANE
#define SUPPORT_I18N
#define SUPPORT_I18N_2008
#define SUPPORT_MAILDIR
#define SUPPORT_PAM
#define SUPPORT_PROXY
#define SUPPORT_SOCKS
+#define SUPPORT_SPF
#define SUPPORT_TLS
#define SUPPORT_TRANSLATE_IP_ADDRESS
#define WHITELIST_D_MACROS
#define WITH_CONTENT_SCAN
-#define WITH_OLD_CLAMAV_STREAM
+#define DISABLE_MAL_FFROTD
+#define DISABLE_MAL_FFROT6D
+#define DISABLE_MAL_DRWEB
+#define DISABLE_MAL_AVE
+#define DISABLE_MAL_FSECURE
+#define DISABLE_MAL_KAV
+#define DISABLE_MAL_SOPHIE
+#define DISABLE_MAL_CLAM
+#define DISABLE_MAL_MKS
+#define DISABLE_MAL_AVAST
+#define DISABLE_MAL_SOCK
+#define DISABLE_MAL_CMDLINE
/* EXPERIMENTAL features */
+#define EXPERIMENTAL_ARC
#define EXPERIMENTAL_BRIGHTMAIL
-#define EXPERIMENTAL_DANE
#define EXPERIMENTAL_DCC
#define EXPERIMENTAL_DSN_INFO
#define EXPERIMENTAL_DMARC
+ #define DMARC_TLD_FILE "/etc/exim/opendmarc.tlds"
#define EXPERIMENTAL_LMDB
+#define EXPERIMENTAL_PIPE_CONNECT
+#define EXPERIMENTAL_REQUIRETLS
#define EXPERIMENTAL_QUEUEFILE
-#define EXPERIMENTAL_SPF
#define EXPERIMENTAL_SRS
+
/* For developers */
#define WANT_DEEPER_PRINTF_CHECKS
# configuration file. There are many more than are mentioned here. The
# manual is in the file doc/spec.txt in the Exim distribution as a plain
# ASCII file. Other formats (PostScript, Texinfo, HTML, PDF) are available
-# from the Exim ftp sites. The manual is also online at the Exim web sites.
+# from the Exim ftp sites. The manual is also online at the Exim website.
# This file is divided into several parts, all but the first of which are
+######################################################################
+# MACROS #
+######################################################################
+#
+
+# If you want to use a smarthost instead of sending directly to recipient
+# domains, uncomment this macro definition and set a real hostname.
+# An appropriately privileged user can then redirect email on the command-line
+# in emergencies, via -D.
+#
+# ROUTER_SMARTHOST=MAIL.HOSTNAME.FOR.CENTRAL.SERVER.EXAMPLE
+
######################################################################
# MAIN CONFIGURATION SETTINGS #
######################################################################
# manual for details. The lists above are used in the access control lists for
# checking incoming messages. The names of these ACLs are defined here:
-acl_smtp_rcpt = acl_check_rcpt
-acl_smtp_data = acl_check_data
+acl_smtp_rcpt = acl_check_rcpt
+.ifdef _HAVE_PRDR
+acl_smtp_data_prdr = acl_check_prdr
+.endif
+acl_smtp_data = acl_check_data
# You should not change those settings until you understand how ACLs work.
# tls_certificate = /etc/ssl/exim.crt
# tls_privatekey = /etc/ssl/exim.pem
+# For OpenSSL, prefer EC- over RSA-authenticated ciphers
+# tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT
+
# In order to support roaming users who wish to send email from anywhere,
# you may want to make Exim listen on other ports as well as port 25, in
# case these users need to send email from a network that blocks port 25.
host_lookup = *
+# The setting below causes Exim to try to initialize the system resolver
+# library with DNSSEC support. It has no effect if your library lacks
+# DNSSEC support.
+
+dns_dnssec_ok = 1
+
+
# The settings below cause Exim to make RFC 1413 (ident) callbacks
# for all incoming SMTP calls. You can limit the hosts to which these
# calls are made, and/or change the timeout that is used. If you set
# may request to use it. For multi-recipient mails we then can
# reject or accept per-user after the message is received.
#
+.ifdef _HAVE_PRDR
prdr_enable = true
+.endif
# By default, Exim expects all envelope addresses to be fully qualified, that
# detail than the default. Adjust to suit.
log_selector = +smtp_protocol_error +smtp_syntax_error \
- +tls_certificate_verified
+ +tls_certificate_verified
# If you want Exim to support the "percent hack" for certain domains,
# Insist that a HELO/EHLO was accepted.
- require message = nice hosts say HELO first
- condition = ${if def:sender_helo_name}
+ require message = nice hosts say HELO first
+ condition = ${if def:sender_helo_name}
# Insist that any other recipient address that we accept is either in one of
# our local domains, or is in a domain for which we explicitly allow
# require verify = csa
#############################################################################
+ #############################################################################
+ # If doing per-user content filtering then recipients with filters different
+ # to the first recipient must be deferred unless the sender talks PRDR.
+ #
+ # defer !condition = $prdr_requested
+ # condition = ${if > {0}{$receipients_count}}
+ # condition = ${if !eq {$acl_m_content_filter} \
+ # {${lookup PER_RCPT_CONTENT_FILTER}}}
+ # warn !condition = $prdr_requested
+ # condition = ${if > {0}{$receipients_count}}
+ # set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER}
+ #############################################################################
+
# At this point, the address has passed all the checks that have been
# configured, so we accept it unconditionally.
accept
+# This ACL is used once per recipient, for multi-recipient messages, if
+# we advertised PRDR. It can be used to perform receipient-dependent
+# header- and body- based filtering and rejections.
+# We set a variable to record that PRDR was active used, so that checking
+# in the data ACL can be skipped.
+
+.ifdef _HAVE_PRDR
+acl_check_prdr:
+ warn set acl_m_did_prdr = y
+.endif
+
+ #############################################################################
+ # do lookup on filtering, with $local_part@$domain, deny on filter match
+ #
+ # deny set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER}
+ # condition = ...
+ #############################################################################
+
+ accept
+
# This ACL is used after the contents of a message have been received. This
# is the ACL in which you can test a message's headers or body, and in
# particular, this is where you can invoke external virus or spam scanners.
got $max_received_linelength
condition = ${if > {$max_received_linelength}{998}}
+ # Deny if the headers contain badly-formed addresses.
+ #
+ deny !verify = header_syntax
+ message = header syntax
+ log_message = header syntax ($acl_verify_message)
+
# Deny if the message contains a virus. Before enabling this check, you
# must install a virus scanner and set the av_scanner option above.
#
# X-Spam_bar: $spam_bar\n\
# X-Spam_report: $spam_report
+ #############################################################################
+ # No more tests if PRDR was actively used.
+ # accept condition = ${if def:acl_m_did_prdr}
+ #
+ # To get here, all message recipients must have identical per-user
+ # content filtering (enforced by RCPT ACL). Do lookup for filter
+ # and deny on match.
+ #
+ # deny set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER}
+ # condition = ...
+ #############################################################################
+
+
# Accept the message.
accept
# transport = remote_smtp
+# This router can be used when you want to send all mail to a
+# server which handles DNS lookups for you; an ISP will typically run such
+# a server for their customers. The hostname in route_data comes from the
+# macro defined at the top of the file. If not defined, then we'll use the
+# dnslookup router below instead.
+# Beware that the hostname is specified again in the Transport.
+
+.ifdef ROUTER_SMARTHOST
+
+smarthost:
+ driver = manualroute
+ domains = ! +local_domains
+ transport = smarthost_smtp
+ route_data = ROUTER_SMARTHOST
+ ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
+ no_more
+
+.else
+
# This router routes addresses that are not in local domains by doing a DNS
# lookup on the domain name. The exclamation mark that appears in "domains = !
# +local_domains" is a negating operator, that is, it can be read as "not". The
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
# if ipv6-enabled then instead use:
# ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
+ dnssec_request_domains = *
no_more
-
-# This alternative router can be used when you want to send all mail to a
-# server which handles DNS lookups for you; an ISP will typically run such
-# a server for their customers. If you uncomment "smarthost" then you
-# should comment out "dnslookup" above. Setting a real hostname in route_data
-# wouldn't hurt either.
-
-# smarthost:
-# driver = manualroute
-# domains = ! +local_domains
-# transport = remote_smtp
-# route_data = MAIL.HOSTNAME.FOR.CENTRAL.SERVER.EXAMPLE
-# ignore_target_hosts = <; 0.0.0.0 ; 127.0.0.0/8 ; ::1
-# no_more
+# This closes the ROUTER_SMARTHOST ifdef around the choice of routing for
+# off-site mail.
+.endif
# The remaining routers handle addresses in the local domain(s), that is those
remote_smtp:
driver = smtp
message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
+.ifdef _HAVE_DANE
+ dnssec_request_domains = *
+ hosts_try_dane = *
+.endif
+.ifdef _HAVE_PRDR
+ hosts_try_prdr = *
+.endif
+
+
+# This transport is used for delivering messages to a smarthost, if the
+# smarthost router is enabled. This starts from the same basis as
+# "remote_smtp" but then turns on various security options, because
+# we assume that if you're told "use smarthost.example.org as the smarthost"
+# then there will be TLS available, with a verifiable certificate for that
+# hostname, using decent TLS.
+
+smarthost_smtp:
+ driver = smtp
+ message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
+ multi_domain
+ #
+.ifdef _HAVE_TLS
+ # Comment out any of these which you have to, then file a Support
+ # request with your smarthost provider to get things fixed:
+ hosts_require_tls = *
+ tls_verify_hosts = *
+ # As long as tls_verify_hosts is enabled, this won't matter, but if you
+ # have to comment it out then this will at least log whether you succeed
+ # or not:
+ tls_try_verify_hosts = *
+ #
+ # The SNI name should match the name which we'll expect to verify;
+ # many mail systems don't use SNI and this doesn't matter, but if it does,
+ # we need to send a name which the remote site will recognize.
+ # This _should_ be the name which the smarthost operators specified as
+ # the hostname for sending your mail to.
+ tls_sni = ROUTER_SMARTHOST
+ #
+.ifdef _HAVE_OPENSSL
+ tls_require_ciphers = HIGH:!aNULL:@STRENGTH
+.endif
+.ifdef _HAVE_GNUTLS
+ tls_require_ciphers = SECURE192:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1
+.endif
+.endif
+.ifdef _HAVE_PRDR
+ hosts_try_prdr = *
+.endif
# This transport is used for local delivery to user mailboxes in traditional
use warnings;
BEGIN { pop @INC if $INC[-1] eq '.' };
+use Getopt::Long;
+use File::Basename;
+
+GetOptions(
+ 'version' => sub {
+ print basename($0) . ": $0\n",
+ "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+ "perl(runtime): $^V\n";
+ exit 0;
+ },
+);
+
##################################################
# Analyse one line #
##################################################
use warnings;
BEGIN { pop @INC if $INC[-1] eq '.' };
+use Getopt::Long;
+use File::Basename;
+
+GetOptions(
+ 'version' => sub {
+ print basename($0) . ": $0\n",
+ "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+ "perl(runtime): $^V\n";
+ exit 0;
+ },
+);
+
# These are lists of main options which are abolished in Exim 4.
# The first contains options that are used to construct new options.
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with running Exim as a daemon */
/* An empty slot for initializing (Standard C does not allow constructor
expressions in assignments except as initializers in declarations). */
-static smtp_slot empty_smtp_slot = { 0, NULL };
+static smtp_slot empty_smtp_slot = { .pid = 0, .host_address = NULL };
static void
never_error(uschar *log_msg, uschar *smtp_msg, int was_errno)
{
-uschar *emsg = (was_errno <= 0)? US"" :
- string_sprintf(": %s", strerror(was_errno));
+uschar *emsg = was_errno <= 0
+ ? US"" : string_sprintf(": %s", strerror(was_errno));
log_write(0, LOG_MAIN|LOG_PANIC, "%s%s", log_msg, emsg);
-if (smtp_out != NULL) smtp_printf("421 %s\r\n", smtp_msg);
+if (smtp_out) smtp_printf("421 %s\r\n", FALSE, smtp_msg);
}
EXIM_SOCKLEN_T ifsize = sizeof(interface_sockaddr);
int dup_accept_socket = -1;
int max_for_this_host = 0;
-int wfsize = 0;
-int wfptr = 0;
int save_log_selector = *log_selector;
-uschar *whofrom = NULL;
+gstring * whofrom;
void *reset_point = store_get(0);
{
log_write(0, LOG_MAIN | ((errno == ECONNRESET)? 0 : LOG_PANIC),
"getsockname() failed: %s", strerror(errno));
- smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n");
+ smtp_printf("421 Local problem: getsockname() failed; please try again later\r\n", FALSE);
goto ERROR_RETURN;
}
the local interface data. This is for logging; at the end of this function the
memory is reclaimed. */
-whofrom = string_append(whofrom, &wfsize, &wfptr, 3, "[", sender_host_address, "]");
+whofrom = string_append(NULL, 3, "[", sender_host_address, "]");
if (LOGGING(incoming_port))
- whofrom = string_append(whofrom, &wfsize, &wfptr, 2, ":", string_sprintf("%d",
- sender_host_port));
+ whofrom = string_fmt_append(whofrom, ":%d", sender_host_port);
if (LOGGING(incoming_interface))
- whofrom = string_append(whofrom, &wfsize, &wfptr, 4, " I=[",
- interface_address, "]:", string_sprintf("%d", interface_port));
+ whofrom = string_fmt_append(whofrom, " I=[%s]:%d",
+ interface_address, interface_port);
-whofrom[wfptr] = 0; /* Terminate the newly-built string */
+(void) string_from_gstring(whofrom); /* Terminate the newly-built string */
/* Check maximum number of connections. We do not check for reserved
connections or unacceptable hosts here. That is done in the subprocess because
DEBUG(D_any) debug_printf("rejecting SMTP connection: count=%d max=%d\n",
smtp_accept_count, smtp_accept_max);
smtp_printf("421 Too many concurrent SMTP connections; "
- "please try again later.\r\n");
+ "please try again later.\r\n", FALSE);
log_write(L_connection_reject,
LOG_MAIN, "Connection from %s refused: too many connections",
- whofrom);
+ whofrom->s);
goto ERROR_RETURN;
}
{
DEBUG(D_any) debug_printf("rejecting SMTP connection: load average = %.2f\n",
(double)load_average/1000.0);
- smtp_printf("421 Too much load; please try again later.\r\n");
+ smtp_printf("421 Too much load; please try again later.\r\n", FALSE);
log_write(L_connection_reject,
LOG_MAIN, "Connection from %s refused: load average = %.2f",
- whofrom, (double)load_average/1000.0);
+ whofrom->s, (double)load_average/1000.0);
goto ERROR_RETURN;
}
}
uschar *expanded = expand_string(smtp_accept_max_per_host);
if (expanded == NULL)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
- "failed for %s: %s", whofrom, expand_string_message);
+ "failed for %s: %s", whofrom->s, expand_string_message);
}
/* For speed, interpret a decimal number inline here */
else
max_for_this_host = max_for_this_host * 10 + *s++ - '0';
if (*s != 0)
log_write(0, LOG_MAIN|LOG_PANIC, "expansion of smtp_accept_max_per_host "
- "for %s contains non-digit: %s", whofrom, expanded);
+ "for %s contains non-digit: %s", whofrom->s, expanded);
}
}
"IP address: count=%d max=%d\n",
host_accept_count, max_for_this_host);
smtp_printf("421 Too many concurrent SMTP connections "
- "from this IP address; please try again later.\r\n");
+ "from this IP address; please try again later.\r\n", FALSE);
log_write(L_connection_reject,
LOG_MAIN, "Connection from %s refused: too many connections "
- "from that IP address", whofrom);
+ "from that IP address", whofrom->s);
goto ERROR_RETURN;
}
}
save_log_selector &= ~L_smtp_connection;
else
log_write(L_smtp_connection, LOG_MAIN, "SMTP connection from %s "
- "(TCP/IP connection count = %d)", whofrom, smtp_accept_count + 1);
+ "(TCP/IP connection count = %d)", whofrom->s, smtp_accept_count + 1);
}
/* Now we can fork the accepting process; do a lookup tidy, just in case any
uschar * nah = expand_string(raw_active_hostname);
if (!nah)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand \"%s\" "
"(smtp_active_hostname): %s", raw_active_hostname,
expand_string_message);
smtp_printf("421 Local configuration error; "
- "please try again later.\r\n");
+ "please try again later.\r\n", FALSE);
mac_smtp_fflush();
search_tidyup();
_exit(EXIT_FAILURE);
finding the id, but turn it on again afterwards so that information about the
incoming connection is output. */
- if (debug_daemon) debug_selector = 0;
+ if (f.debug_daemon) debug_selector = 0;
verify_get_ident(IDENT_PORT);
host_build_sender_fullhost();
debug_selector = save_debug_selector;
/* Now disable debugging permanently if it's required only for the daemon
process. */
- if (debug_daemon) debug_selector = 0;
+ if (f.debug_daemon) debug_selector = 0;
/* If there are too many child processes for immediate delivery,
set the session_local_queue_only flag, which is initialized from the
search_tidyup(); /* Close cached databases */
if (!ok) /* Connection was dropped */
{
+ cancel_cutthrough_connection(TRUE, US"receive dropped");
mac_smtp_fflush();
smtp_log_no_mail(); /* Log no mail if configured */
_exit(EXIT_SUCCESS);
if (fcntl(fd, F_SETFL, O_NONBLOCK) == 0)
for(i = 16; read(fd, buf, sizeof(buf)) > 0 && i > 0; ) i--;
}
+ cancel_cutthrough_connection(TRUE, US"message setup dropped");
search_tidyup();
smtp_log_no_mail(); /* Log no mail if configured */
DEBUG(D_receive)
{
int i;
- if (sender_address != NULL)
+ if (sender_address)
debug_printf("Sender: %s\n", sender_address);
- if (recipients_list != NULL)
+ if (recipients_list)
{
debug_printf("Recipients:\n");
for (i = 0; i < recipients_count; i++)
/* Reclaim up the store used in accepting this message */
- return_path = sender_address = NULL;
- authenticated_sender = NULL;
- sending_ip_address = NULL;
- deliver_host_address = deliver_host =
- deliver_domain_orig = deliver_localpart_orig = NULL;
- dnslist_domain = dnslist_matched = NULL;
- callout_address = NULL;
-#ifndef DISABLE_DKIM
- dkim_cur_signer = NULL;
-#endif
- acl_var_m = NULL;
- store_reset(reset_point);
+ {
+ int r = receive_messagecount;
+ BOOL q = f.queue_only_policy;
+ smtp_reset(reset_point);
+ f.queue_only_policy = q;
+ receive_messagecount = r;
+ }
/* If queue_only is set or if there are too many incoming connections in
existence, session_local_queue_only will be TRUE. If it is not, check
If we are not root, we have to re-exec exim unless deliveries are being
done unprivileged. */
- else if (!queue_only_policy && !deliver_freeze)
+ else if (!f.queue_only_policy && !f.deliver_freeze)
{
pid_t dpid;
/* Don't ever molest the parent's SSL connection, but do clean up
the data structures if necessary. */
- #ifdef SUPPORT_TLS
- tls_close(TRUE, FALSE);
- #endif
+#ifdef SUPPORT_TLS
+ tls_close(NULL, TLS_NO_SHUTDOWN);
+#endif
/* Reset SIGHUP and SIGCHLD in the child in both cases. */
if (geteuid() != root_uid && !deliver_drop_privilege)
{
signal(SIGALRM, SIG_DFL);
- (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE,
- 2, US"-Mc", message_id);
+ delivery_re_exec(CEE_EXEC_PANIC);
/* Control does not return here. */
}
/* No need to re-exec; SIGALRM remains set to the default handler */
- (void)deliver_message(message_id, FALSE, FALSE);
+ (void) deliver_message(message_id, FALSE, FALSE);
search_tidyup();
_exit(EXIT_SUCCESS);
}
if (dpid > 0)
{
+ release_cutthrough_connection(US"passed for delivery");
DEBUG(D_any) debug_printf("forked delivery process %d\n", (int)dpid);
}
else
+ {
+ cancel_cutthrough_connection(TRUE, US"delivery fork failed");
log_write(0, LOG_MAIN|LOG_PANIC, "daemon: delivery process fork "
"failed: %s", strerror(errno));
+ }
}
}
}
DEBUG(D_any|D_v) debug_selector |= D_pid;
-if (inetd_wait_mode)
+if (f.inetd_wait_mode)
{
listen_socket_count = 1;
listen_sockets = store_get(sizeof(int));
}
-if (inetd_wait_mode || daemon_listen)
+if (f.inetd_wait_mode || f.daemon_listen)
{
/* If any option requiring a load average to be available during the
reception of a message is set, call os_getloadavg() while we are root
first, so that we can return non-zero if there are any syntax errors, and also
write to stderr. */
-if (daemon_listen && !inetd_wait_mode)
+if (f.daemon_listen && !f.inetd_wait_mode)
{
int *default_smtp_port;
int sep;
that contain neither a dot nor a colon are used to override daemon_smtp_port.
Any other items are used to override local_interfaces. */
- if (override_local_interfaces != NULL)
+ if (override_local_interfaces)
{
- uschar *new_smtp_port = NULL;
- uschar *new_local_interfaces = NULL;
- int portsize = 0;
- int portptr = 0;
- int ifacesize = 0;
- int ifaceptr = 0;
+ gstring * new_smtp_port = NULL;
+ gstring * new_local_interfaces = NULL;
if (override_pid_file_path == NULL) write_pid = FALSE;
while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
{
uschar joinstr[4];
- uschar **ptr;
- int *sizeptr;
- int *ptrptr;
+ gstring ** gp;
if (Ustrpbrk(s, ".:") == NULL)
- {
- ptr = &new_smtp_port;
- sizeptr = &portsize;
- ptrptr = &portptr;
- }
+ gp = &new_smtp_port;
else
- {
- ptr = &new_local_interfaces;
- sizeptr = &ifacesize;
- ptrptr = &ifaceptr;
- }
+ gp = &new_local_interfaces;
- if (*ptr == NULL)
+ if (!*gp)
{
joinstr[0] = sep;
joinstr[1] = ' ';
- *ptr = string_catn(*ptr, sizeptr, ptrptr, US"<", 1);
+ *gp = string_catn(*gp, US"<", 1);
}
- *ptr = string_catn(*ptr, sizeptr, ptrptr, joinstr, 2);
- *ptr = string_cat (*ptr, sizeptr, ptrptr, s);
+ *gp = string_catn(*gp, joinstr, 2);
+ *gp = string_cat (*gp, s);
}
- if (new_smtp_port != NULL)
+ if (new_smtp_port)
{
- new_smtp_port[portptr] = 0;
- daemon_smtp_port = new_smtp_port;
+ daemon_smtp_port = string_from_gstring(new_smtp_port);
DEBUG(D_any) debug_printf("daemon_smtp_port overridden by -oX:\n %s\n",
daemon_smtp_port);
}
- if (new_local_interfaces != NULL)
+ if (new_local_interfaces)
{
- new_local_interfaces[ifaceptr] = 0;
- local_interfaces = new_local_interfaces;
+ local_interfaces = string_from_gstring(new_local_interfaces);
local_iface_source = US"-oX data";
DEBUG(D_any) debug_printf("local_interfaces overridden by -oX:\n %s\n",
local_interfaces);
while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
if (!isdigit(*s))
{
+ gstring * g = NULL;
+
list = tls_in.on_connect_ports;
tls_in.on_connect_ports = NULL;
sep = 0;
{
if (!isdigit(*s))
{
- struct servent *smtp_service = getservbyname(CS s, "tcp");
+ struct servent * smtp_service = getservbyname(CS s, "tcp");
if (!smtp_service)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "TCP port \"%s\" not found", s);
- s= string_sprintf("%d", (int)ntohs(smtp_service->s_port));
+ s = string_sprintf("%d", (int)ntohs(smtp_service->s_port));
}
- tls_in.on_connect_ports = string_append_listele(tls_in.on_connect_ports,
- ':', s);
+ g = string_append_listele(g, ':', s);
}
+ if (g)
+ tls_in.on_connect_ports = g->s;
break;
}
In the same scan, fill in missing port numbers from the default list. When
there is more than one item in the list, extra items are created. */
- for (ipa = addresses; ipa != NULL; ipa = ipa->next)
+ for (ipa = addresses; ipa; ipa = ipa->next)
{
int i;
- if (Ustrcmp(ipa->address, "0.0.0.0") == 0) ipa->address[0] = 0;
+ if (Ustrcmp(ipa->address, "0.0.0.0") == 0)
+ ipa->address[0] = 0;
else if (Ustrcmp(ipa->address, "::0") == 0)
{
ipa->address[0] = ':';
if (daemon_smtp_port[0] <= 0)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "no port specified for interface "
"%s and daemon_smtp_port is unset; cannot start daemon",
- (ipa->address[0] == 0)? US"\"all IPv4\"" :
- (ipa->address[1] == 0)? US"\"all IPv6\"" : ipa->address);
+ ipa->address[0] == 0 ? US"\"all IPv4\"" :
+ ipa->address[1] == 0 ? US"\"all IPv6\"" : ipa->address);
+
ipa->port = default_smtp_port[0];
for (i = 1; default_smtp_port[i] > 0; i++)
{
ip_address_item *new = store_get(sizeof(ip_address_item));
+
memcpy(new->address, ipa->address, Ustrlen(ipa->address) + 1);
new->port = default_smtp_port[i];
new->next = ipa->next;
also simplifies the construction of the "daemon started" log line. */
pipa = &addresses;
- for (ipa = addresses; ipa != NULL; pipa = &(ipa->next), ipa = ipa->next)
+ for (ipa = addresses; ipa; pipa = &ipa->next, ipa = ipa->next)
{
ip_address_item *ipa2;
/* Handle an IPv4 wildcard */
if (ipa->address[0] == 0)
- {
- for (ipa2 = ipa; ipa2->next != NULL; ipa2 = ipa2->next)
+ for (ipa2 = ipa; ipa2->next; ipa2 = ipa2->next)
{
ip_address_item *ipa3 = ipa2->next;
if (ipa3->address[0] == ':' &&
break;
}
}
- }
/* Handle an IPv6 wildcard. */
else if (ipa->address[0] == ':' && ipa->address[1] == 0)
- {
- for (ipa2 = ipa; ipa2->next != NULL; ipa2 = ipa2->next)
+ for (ipa2 = ipa; ipa2->next; ipa2 = ipa2->next)
{
ip_address_item *ipa3 = ipa2->next;
if (ipa3->address[0] == 0 && ipa3->port == ipa->port)
break;
}
}
- }
}
/* Get a vector to remember all the sockets in */
- for (ipa = addresses; ipa != NULL; ipa = ipa->next)
+ for (ipa = addresses; ipa; ipa = ipa->next)
listen_socket_count++;
listen_sockets = store_get(sizeof(int) * listen_socket_count);
} /* daemon_listen but not inetd_wait_mode */
-if (daemon_listen)
+if (f.daemon_listen)
{
/* Do a sanity check on the max connects value just to save us from getting
setsid() for getting rid of the controlling terminal. For any OS that doesn't,
setsid() can be #defined as a no-op, or as something else. */
-if (background_daemon || inetd_wait_mode)
+if (f.background_daemon || f.inetd_wait_mode)
{
log_close_all(); /* Just in case anything was logged earlier */
search_tidyup(); /* Just in case any were used in reading the config. */
log_stderr = NULL; /* So no attempt to copy paniclog output */
}
-if (background_daemon)
+if (f.background_daemon)
{
/* If the parent process of this one has pid == 1, we are re-initializing the
daemon as the result of a SIGHUP. In this case, there is no need to do
/* We are now in the disconnected, daemon process (unless debugging). Set up
the listening sockets if required. */
-if (daemon_listen && !inetd_wait_mode)
+if (f.daemon_listen && !f.inetd_wait_mode)
{
int sk;
ip_address_item *ipa;
available. Just log failure (can get protocol not available, just like
socket creation can). */
- #ifdef IPV6_V6ONLY
+#ifdef IPV6_V6ONLY
if (af == AF_INET6 && wildcard &&
- setsockopt(listen_sockets[sk], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
+ setsockopt(listen_sockets[sk], IPPROTO_IPV6, IPV6_V6ONLY, CS (&on),
sizeof(on)) < 0)
log_write(0, LOG_MAIN, "Setting IPV6_V6ONLY on daemon's IPv6 wildcard "
"socket failed (%s): carrying on without it", strerror(errno));
- #endif /* IPV6_V6ONLY */
+#endif /* IPV6_V6ONLY */
/* Set SO_REUSEADDR so that the daemon can be restarted while a connection
is being handled. Without this, a connection will prevent reuse of the
smtp port for listening. */
if (setsockopt(listen_sockets[sk], SOL_SOCKET, SO_REUSEADDR,
- (uschar *)(&on), sizeof(on)) < 0)
+ US (&on), sizeof(on)) < 0)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "setting SO_REUSEADDR on socket "
"failed when starting daemon: %s", strerror(errno));
disable this because it breaks some broken clients. */
if (tcp_nodelay) setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_NODELAY,
- (uschar *)(&on), sizeof(on));
+ US (&on), sizeof(on));
/* Now bind the socket to the required port; if Exim is being restarted
it may not always be possible to bind immediately, even with SO_REUSEADDR
listen() stage instead. */
#ifdef TCP_FASTOPEN
- tcp_fastopen_ok = TRUE;
+ f.tcp_fastopen_ok = TRUE;
#endif
for(;;)
{
goto SKIP_SOCKET;
}
msg = US strerror(errno);
- addr = wildcard? ((af == AF_INET6)? US"(any IPv6)" : US"(any IPv4)") :
- ipa->address;
+ addr = wildcard
+ ? af == AF_INET6
+ ? US"(any IPv6)"
+ : US"(any IPv4)"
+ : ipa->address;
if (daemon_startup_retries <= 0)
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"socket bind() to port %d for address %s failed: %s: "
else
debug_printf("listening on %s port %d\n", ipa->address, ipa->port);
-#ifdef TCP_FASTOPEN
- if (setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
+#if defined(TCP_FASTOPEN) && !defined(__APPLE__)
+ if ( f.tcp_fastopen_ok
+ && setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
&smtp_connect_backlog, sizeof(smtp_connect_backlog)))
{
DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
- tcp_fastopen_ok = FALSE;
+ f.tcp_fastopen_ok = FALSE;
}
#endif
/* Start listening on the bound socket, establishing the maximum backlog of
connections that is allowed. On success, continue to the next address. */
- if (listen(listen_sockets[sk], smtp_connect_backlog) >= 0) continue;
+ if (listen(listen_sockets[sk], smtp_connect_backlog) >= 0)
+ {
+#if defined(TCP_FASTOPEN) && defined(__APPLE__)
+ if ( f.tcp_fastopen_ok
+ && setsockopt(listen_sockets[sk], IPPROTO_TCP, TCP_FASTOPEN,
+ &on, sizeof(on)))
+ {
+ DEBUG(D_any) debug_printf("setsockopt FASTOPEN: %s\n", strerror(errno));
+ f.tcp_fastopen_ok = FALSE;
+ }
+#endif
+ continue;
+ }
/* Listening has failed. In an IPv6 environment, as for bind(), if listen()
fails with the error EADDRINUSE and we are doing IPv4 wildcard listening
are going to ignore. We remove the address from the chain, and back up the
counts. */
- SKIP_SOCKET:
+ SKIP_SOCKET:
sk--; /* Back up the count */
listen_socket_count--; /* Reduce the total */
if (ipa == addresses) addresses = ipa->next; else
/* If we are not listening, we want to write a pid file only if -oP was
explicitly given. */
-else if (override_pid_file_path == NULL) write_pid = FALSE;
+else if (!override_pid_file_path)
+ write_pid = FALSE;
/* Write the pid to a known file for assistance in identification, if required.
We do this before giving up root privilege, because on some systems it is
The variable daemon_write_pid is used to control this. */
-if (running_in_test_harness || write_pid)
+if (f.running_in_test_harness || write_pid)
{
FILE *f;
- if (override_pid_file_path != NULL)
+ if (override_pid_file_path)
pid_file_path = override_pid_file_path;
if (pid_file_path[0] == 0)
pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
- f = modefopen(pid_file_path, "wb", 0644);
- if (f != NULL)
+ if ((f = modefopen(pid_file_path, "wb", 0644)))
{
(void)fprintf(f, "%d\n", (int)getpid());
(void)fclose(f);
DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
}
else
- {
DEBUG(D_any)
debug_printf("%s\n", string_open_failed(errno, "pid file %s",
pid_file_path));
- }
}
/* Set up the handler for SIGHUP, which causes a restart of the daemon. */
/* Log the start up of a daemon - at least one of listening or queue running
must be set up. */
-if (inetd_wait_mode)
+if (f.inetd_wait_mode)
{
uschar *p = big_buffer;
sigalrm_seen = 1;
}
-else if (daemon_listen)
+else if (f.daemon_listen)
{
int i, j;
int smtp_ports = 0;
int smtps_ports = 0;
- ip_address_item * ipa;
+ ip_address_item * ipa, * i2;
uschar * p = big_buffer;
uschar * qinfo = queue_interval > 0
? string_sprintf("-q%s", readconf_printtime(queue_interval))
for (j = 0; j < 2; j++)
{
for (i = 0, ipa = addresses; i < 10 && ipa; i++, ipa = ipa->next)
- {
- /* First time round, look for SMTP ports; second time round, look for
- SMTPS ports. For the first one of each, insert leading text. */
-
- if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
- {
- if (j == 0)
- {
- if (smtp_ports++ == 0)
- {
- memcpy(p, "SMTP on", 8);
- p += 7;
- }
- }
- else
- {
- if (smtps_ports++ == 0)
- {
- (void)sprintf(CS p, "%sSMTPS on",
- smtp_ports == 0 ? "" : " and for ");
- while (*p) p++;
- }
- }
-
- /* Now the information about the port (and sometimes interface) */
-
- if (ipa->address[0] == ':' && ipa->address[1] == 0)
- {
- if (ipa->next != NULL && ipa->next->address[0] == 0 &&
- ipa->next->port == ipa->port)
- {
- (void)sprintf(CS p, " port %d (IPv6 and IPv4)", ipa->port);
- ipa = ipa->next;
- }
- else if (ipa->v6_include_v4)
- (void)sprintf(CS p, " port %d (IPv6 with IPv4)", ipa->port);
- else
- (void)sprintf(CS p, " port %d (IPv6)", ipa->port);
- }
- else if (ipa->address[0] == 0)
- (void)sprintf(CS p, " port %d (IPv4)", ipa->port);
- else
- (void)sprintf(CS p, " [%s]:%d", ipa->address, ipa->port);
- while (*p != 0) p++;
- }
- }
+ {
+ /* First time round, look for SMTP ports; second time round, look for
+ SMTPS ports. For the first one of each, insert leading text. */
+
+ if (host_is_tls_on_connect_port(ipa->port) == (j > 0))
+ {
+ if (j == 0)
+ {
+ if (smtp_ports++ == 0)
+ {
+ memcpy(p, "SMTP on", 8);
+ p += 7;
+ }
+ }
+ else
+ if (smtps_ports++ == 0)
+ p += sprintf(CS p, "%sSMTPS on",
+ smtp_ports == 0 ? "" : " and for ");
+
+ /* Now the information about the port (and sometimes interface) */
+
+ if (ipa->address[0] == ':' && ipa->address[1] == 0)
+ { /* v6 wildcard */
+ if (ipa->next && ipa->next->address[0] == 0 &&
+ ipa->next->port == ipa->port)
+ {
+ p += sprintf(CS p, " port %d (IPv6 and IPv4)", ipa->port);
+ ipa = ipa->next;
+ }
+ else if (ipa->v6_include_v4)
+ p += sprintf(CS p, " port %d (IPv6 with IPv4)", ipa->port);
+ else
+ p += sprintf(CS p, " port %d (IPv6)", ipa->port);
+ }
+ else if (ipa->address[0] == 0) /* v4 wildcard */
+ p += sprintf(CS p, " port %d (IPv4)", ipa->port);
+ else /* check for previously-seen IP */
+ {
+ for (i2 = addresses; i2 != ipa; i2 = i2->next)
+ if ( host_is_tls_on_connect_port(i2->port) == (j > 0)
+ && Ustrcmp(ipa->address, i2->address) == 0
+ )
+ { /* found; append port to list */
+ if (p[-1] == '}') p--;
+ while (isdigit(*--p)) ;
+ p += 1 + sprintf(CS p+1, "%s%d,%d}", *p == ',' ? "" : "{",
+ i2->port, ipa->port);
+ break;
+ }
+ if (i2 == ipa) /* first-time IP */
+ p += sprintf(CS p, " [%s]:%d", ipa->address, ipa->port);
+ }
+ }
+ }
if (ipa)
{
}
sigalrm_seen = FALSE;
- alarm(resignal_interval);
+ ALARM(resignal_interval);
}
else
leave the above message, because it ties up with the "child ended"
debugging messages. */
- if (debug_daemon) debug_selector = 0;
+ if (f.debug_daemon) debug_selector = 0;
/* Close any open listening sockets in the child */
signal(SIGALRM, SIG_DFL);
*p++ = '-';
*p++ = 'q';
- if (queue_2stage) *p++ = 'q';
- if (queue_run_first_delivery) *p++ = 'i';
- if (queue_run_force) *p++ = 'f';
- if (deliver_force_thaw) *p++ = 'f';
- if (queue_run_local) *p++ = 'l';
+ if (f.queue_2stage) *p++ = 'q';
+ if (f.queue_run_first_delivery) *p++ = 'i';
+ if (f.queue_run_force) *p++ = 'f';
+ if (f.deliver_force_thaw) *p++ = 'f';
+ if (f.queue_run_local) *p++ = 'l';
*p = 0;
extra[0] = queue_name
? string_sprintf("%sG%s", opt, queue_name) : opt;
if (deliver_selectstring)
{
- extra[extracount++] = deliver_selectstring_regex ? US"-Rr" : US"-R";
+ extra[extracount++] = f.deliver_selectstring_regex ? US"-Rr" : US"-R";
extra[extracount++] = deliver_selectstring;
}
if (deliver_selectstring_sender)
{
- extra[extracount++] = deliver_selectstring_sender_regex
+ extra[extracount++] = f.deliver_selectstring_sender_regex
? US"-Sr" : US"-S";
extra[extracount++] = deliver_selectstring_sender;
}
/* Reset the alarm clock */
sigalrm_seen = FALSE;
- alarm(queue_interval);
+ ALARM(queue_interval);
}
} /* sigalrm_seen */
new OS. In fact, the later addition of listening on specific interfaces only
requires this way of working anyway. */
- if (daemon_listen)
+ if (f.daemon_listen)
{
int sk, lcount, select_errno;
int max_socket = 0;
errno = EINTR;
}
else
- {
lcount = select(max_socket + 1, (SELECT_ARG2_TYPE *)&select_listen,
NULL, NULL, NULL);
- }
if (lcount < 0)
{
while (lcount-- > 0)
{
int accept_socket = -1;
+
if (!select_failed)
- {
for (sk = 0; sk < listen_socket_count; sk++)
- {
if (FD_ISSET(listen_sockets[sk], &select_listen))
{
len = sizeof(accepted);
FD_CLR(listen_sockets[sk], &select_listen);
break;
}
- }
- }
/* If select or accept has failed and this was not caused by an
interruption, log the incident and try again. With asymmetric TCP/IP
getpid());
for (sk = 0; sk < listen_socket_count; sk++)
(void)close(listen_sockets[sk]);
- alarm(0);
+ ALARM_CLR(0);
signal(SIGHUP, SIG_IGN);
sighup_argv[0] = exim_path;
exim_nullstd();
+++ /dev/null
-/*************************************************
-* Exim - an Internet mail transport agent *
-*************************************************/
-
-/* Copyright (c) University of Cambridge 1995 - 2013 */
-/* See the file NOTICE for conditions of use and distribution. */
-
-/* This file (will) provide DANE support for Exim using the GnuTLS library,
-but is not yet an available supported implementation. This file is #included
-into dane.c when USE_GNUTLS has been set. */
-
-/* As of March 2014, the reference implementation for DANE that we are
-using was written by Viktor Dukhovny and it supports OpenSSL only. At
-some point we will add GnuTLS support, but for right now just abort the
-build and explain why. */
-
-
-#error No support for DANE using GnuTLS yet.
-
-
-/* End of dane-gnu.c */
* Author: Viktor Dukhovni
* License: THIS CODE IS IN THE PUBLIC DOMAIN.
*
- * Copyright (c) The Exim Maintainers 2014 - 2016
+ * Copyright (c) The Exim Maintainers 2014 - 2018
*/
#include <stdio.h>
#include <string.h>
#ifndef OPENSSL_NO_ERR
#define DANESSL_F_PLACEHOLDER 0 /* FIRST! Value TBD */
static ERR_STRING_DATA dane_str_functs[] = {
+ /* error string */
{DANESSL_F_PLACEHOLDER, "DANE library"}, /* FIRST!!! */
{DANESSL_F_ADD_SKID, "add_skid"},
{DANESSL_F_ADD_TLSA, "DANESSL_add_tlsa"},
{0, NULL}
};
static ERR_STRING_DATA dane_str_reasons[] = {
+ /* error string */
{DANESSL_R_BAD_CERT, "Bad TLSA record certificate"},
{DANESSL_R_BAD_CERT_PKEY, "Bad TLSA record certificate public key"},
{DANESSL_R_BAD_DATA_LENGTH, "Bad TLSA record digest length"},
{
case DANESSL_SELECTOR_CERT:
len = i2d_X509(cert, NULL);
- buf2 = buf = (unsigned char *) OPENSSL_malloc(len);
+ buf2 = buf = US OPENSSL_malloc(len);
if(buf) i2d_X509(cert, &buf2);
break;
case DANESSL_SELECTOR_SPKI:
len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL);
- buf2 = buf = (unsigned char *) OPENSSL_malloc(len);
+ buf2 = buf = US OPENSSL_malloc(len);
if(buf) i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &buf2);
break;
}
}
static int
-set_issuer_name(X509 *cert, AUTHORITY_KEYID *akid)
+set_issuer_name(X509 *cert, AUTHORITY_KEYID *akid, X509_NAME *subj)
{
X509_NAME *name = akid_issuer_name(akid);
/*
- * If subject's akid specifies an authority key identifer issuer name, we
+ * If subject's akid specifies an authority key identifier issuer name, we
* must use that.
*/
return X509_set_issuer_name(cert,
- name ? name : X509_get_subject_name(cert));
+ name ? name : subj);
}
static int
*/
if ( !X509_set_version(cert, 2)
|| !set_serial(cert, akid, subject)
- || !set_issuer_name(cert, akid)
+ || !set_issuer_name(cert, akid, name)
|| !X509_gmtime_adj(X509_getm_notBefore(cert), -30 * 86400L)
|| !X509_gmtime_adj(X509_getm_notAfter(cert), 30 * 86400L)
|| !X509_set_subject_name(cert, name)
return 0;
if (ASN1_STRING_type(gn->d.ia5) != V_ASN1_IA5STRING)
return 0;
-return check_name((const char *) ASN1_STRING_get0_data(gn->d.ia5),
+return check_name(CCS ASN1_STRING_get0_data(gn->d.ia5),
ASN1_STRING_length(gn->d.ia5));
}
if ((len = ASN1_STRING_to_UTF8(&namebuf, entry_str)) < 0)
return 0;
-if (len <= 0 || check_name((char *) namebuf, len) == 0)
+if (len <= 0 || check_name(CS namebuf, len) == 0)
{
OPENSSL_free(namebuf);
return 0;
}
-return (char *) namebuf;
+return CS namebuf;
}
static int
return 0;
}
- /* Support built-in standard one-digit mtypes */
- if (mdname && *mdname && mdname[1] == '\0')
- switch (*mdname - '0')
- {
- case DANESSL_MATCHING_FULL: mdname = 0; break;
- case DANESSL_MATCHING_2256: mdname = "sha256"; break;
- case DANESSL_MATCHING_2512: mdname = "sha512"; break;
- }
- if (mdname && *mdname && (md = EVP_get_digestbyname(mdname)) == 0)
- {
- DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_DIGEST);
- return 0;
- }
- if (mdname && *mdname && dlen != EVP_MD_size(md))
- {
- DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_DATA_LENGTH);
- return 0;
- }
- if (!data)
- {
- DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_NULL_DATA);
- return 0;
- }
+/* Support built-in standard one-digit mtypes */
+if (mdname && *mdname && mdname[1] == '\0')
+ switch (*mdname - '0')
+ {
+ case DANESSL_MATCHING_FULL: mdname = 0; break;
+ case DANESSL_MATCHING_2256: mdname = "sha256"; break;
+ case DANESSL_MATCHING_2512: mdname = "sha512"; break;
+ }
+if (mdname && *mdname && !(md = EVP_get_digestbyname(mdname)))
+ {
+ DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_DIGEST);
+ return 0;
+ }
+if (mdname && *mdname && dlen != EVP_MD_size(md))
+ {
+ DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_DATA_LENGTH);
+ return 0;
+ }
+if (!data)
+ {
+ DANEerr(DANESSL_F_ADD_TLSA, DANESSL_R_BAD_NULL_DATA);
+ return 0;
+ }
- /*
- * Full Certificate or Public Key when NULL or empty digest name
- */
- if (!mdname || !*mdname)
- {
- X509 *x = 0;
- EVP_PKEY *k = 0;
- const unsigned char *p = data;
+/*
+ * Full Certificate or Public Key when NULL or empty digest name
+ */
+if (!mdname || !*mdname)
+ {
+ X509 *x = 0;
+ EVP_PKEY *k = 0;
+ const unsigned char *p = data;
#define xklistinit(lvar, ltype, var, freeFunc) do { \
(lvar) = (ltype) OPENSSL_malloc(sizeof(*(lvar))); \
in a dummy argument to stop even pickier compilers complaining about infinite
loops. */
-#ifndef EXPERIMENTAL_DANE
+#ifndef SUPPORT_DANE
static void dummy(int x) { dummy(x-1); }
#else
# error DANE support requires that the DNS resolver library supports DNSSEC
# endif
-# ifdef USE_GNUTLS
-# include "dane-gnu.c"
-# else
+# ifndef USE_GNUTLS
# include "dane-openssl.c"
# endif
-#endif /* EXPERIMENTAL_DANE */
+#endif /* SUPPORT_DANE */
/* End of dane.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
BOOL read_only = flags == O_RDONLY;
BOOL created = FALSE;
flock_t lock_data;
-uschar buffer[256];
+uschar dirname[256], filename[256];
+
+DEBUG(D_hints_lookup) acl_level++;
/* The first thing to do is to open a separate file on which to lock. This
ensures that Exim has exclusive use of the database before it even tries to
unnecessarily, because usually the lock file will be there. If the directory
exists, there is no error. */
-sprintf(CS buffer, "%s/db/%s.lockfile", spool_directory, name);
+snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
+snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name);
-if ((dbblock->lockfd = Uopen(buffer, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
+if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
{
created = TRUE;
(void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE);
- dbblock->lockfd = Uopen(buffer, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
+ dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
}
if (dbblock->lockfd < 0)
{
log_write(0, LOG_MAIN, "%s",
- string_open_failed(errno, "database lock file %s", buffer));
+ string_open_failed(errno, "database lock file %s", filename));
errno = 0; /* Indicates locking failure */
+ DEBUG(D_hints_lookup) acl_level--;
return NULL;
}
lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
DEBUG(D_hints_lookup|D_retry|D_route|D_deliver)
- debug_printf("locking %s\n", buffer);
+ debug_printf_indent("locking %s\n", filename);
sigalrm_seen = FALSE;
-alarm(EXIMDB_LOCK_TIMEOUT);
+ALARM(EXIMDB_LOCK_TIMEOUT);
rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
-alarm(0);
+ALARM_CLR(0);
if (sigalrm_seen) errno = ETIMEDOUT;
if (rc < 0)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Failed to get %s lock for %s: %s",
- read_only ? "read" : "write", buffer,
+ read_only ? "read" : "write", filename,
errno == ETIMEDOUT ? "timed out" : strerror(errno));
(void)close(dbblock->lockfd);
errno = 0; /* Indicates locking failure */
+ DEBUG(D_hints_lookup) acl_level--;
return NULL;
}
-DEBUG(D_hints_lookup) debug_printf("locked %s\n", buffer);
+DEBUG(D_hints_lookup) debug_printf_indent("locked %s\n", filename);
/* At this point we have an opened and locked separate lock file, that is,
exclusive access to the database, so we can go ahead and open it. If we are
it easy to pin this down, there are now debug statements on either side of the
open call. */
-sprintf(CS buffer, "%s/db/%s", spool_directory, name);
-DEBUG(D_hints_lookup) debug_printf("EXIM_DBOPEN(%s)\n", buffer);
-EXIM_DBOPEN(buffer, flags, EXIMDB_MODE, &(dbblock->dbptr));
-DEBUG(D_hints_lookup) debug_printf("returned from EXIM_DBOPEN\n");
+snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name);
+EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));
if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR)
{
DEBUG(D_hints_lookup)
- debug_printf("%s appears not to exist: trying to create\n", buffer);
+ debug_printf_indent("%s appears not to exist: trying to create\n", filename);
created = TRUE;
- EXIM_DBOPEN(buffer, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
- DEBUG(D_hints_lookup) debug_printf("returned from EXIM_DBOPEN\n");
+ EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
}
save_errno = errno;
{
DIR *dd;
struct dirent *ent;
- uschar *lastname = Ustrrchr(buffer, '/') + 1;
+ uschar *lastname = Ustrrchr(filename, '/') + 1;
int namelen = Ustrlen(name);
*lastname = 0;
- dd = opendir(CS buffer);
+ dd = opendir(CS filename);
while ((ent = readdir(dd)))
if (Ustrncmp(ent->d_name, name, namelen) == 0)
{
struct stat statbuf;
Ustrcpy(lastname, ent->d_name);
- if (Ustat(buffer, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
+ if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
{
- DEBUG(D_hints_lookup) debug_printf("ensuring %s is owned by exim\n", buffer);
- if (Uchown(buffer, exim_uid, exim_gid))
- DEBUG(D_hints_lookup) debug_printf("failed setting %s to owned by exim\n", buffer);
+ DEBUG(D_hints_lookup) debug_printf_indent("ensuring %s is owned by exim\n", filename);
+ if (Uchown(filename, exim_uid, exim_gid))
+ DEBUG(D_hints_lookup) debug_printf_indent("failed setting %s to owned by exim\n", filename);
}
}
}
/* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
-log the event - also for debugging - but not if the file just doesn't exist. */
+log the event - also for debugging - but debug only if the file just doesn't
+exist. */
if (!dbblock->dbptr)
{
- if (save_errno != ENOENT)
- if (lof)
- log_write(0, LOG_MAIN, "%s", string_open_failed(save_errno, "DB file %s",
- buffer));
- else
- DEBUG(D_hints_lookup)
- debug_printf("%s", CS string_open_failed(save_errno, "DB file %s\n",
- buffer));
+ if (lof && save_errno != ENOENT)
+ log_write(0, LOG_MAIN, "%s", string_open_failed(save_errno, "DB file %s",
+ filename));
+ else
+ DEBUG(D_hints_lookup)
+ debug_printf_indent("%s\n", CS string_open_failed(save_errno, "DB file %s",
+ filename));
(void)close(dbblock->lockfd);
errno = save_errno;
+ DEBUG(D_hints_lookup) acl_level--;
return NULL;
}
DEBUG(D_hints_lookup)
- debug_printf("opened hints database %s: flags=%s\n", buffer,
+ debug_printf_indent("opened hints database %s: flags=%s\n", filename,
flags == O_RDONLY ? "O_RDONLY"
: flags == O_RDWR ? "O_RDWR"
: flags == (O_RDWR|O_CREAT) ? "O_RDWR|O_CREAT"
{
EXIM_DBCLOSE(dbblock->dbptr);
(void)close(dbblock->lockfd);
-DEBUG(D_hints_lookup) debug_printf("closed hints database and lockfile\n");
+DEBUG(D_hints_lookup)
+ { debug_printf_indent("closed hints database and lockfile\n"); acl_level--; }
}
memcpy(key_copy, key, klen);
-DEBUG(D_hints_lookup) debug_printf("dbfn_read: key=%s\n", key);
+DEBUG(D_hints_lookup) debug_printf_indent("dbfn_read: key=%s\n", key);
EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
EXIM_DATUM_INIT(result_datum); /* to be cleared before use. */
memcpy(key_copy, key, klen);
gptr->time_stamp = time(NULL);
-DEBUG(D_hints_lookup) debug_printf("dbfn_write: key=%s\n", key);
+DEBUG(D_hints_lookup) debug_printf_indent("dbfn_write: key=%s\n", key);
EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require the datum */
EXIM_DATUM_INIT(value_datum); /* to be cleared before use. */
int klen = Ustrlen(key) + 1;
uschar * key_copy = store_get(klen);
+DEBUG(D_hints_lookup) debug_printf_indent("dbfn_delete: key=%s\n", key);
+
memcpy(key_copy, key, klen);
EXIM_DATUM key_datum;
EXIM_DATUM_INIT(key_datum); /* Some DBM libraries require clearing */
uschar *yield;
value_datum = value_datum; /* dummy; not all db libraries use this */
+DEBUG(D_hints_lookup) debug_printf_indent("dbfn_scan\n");
+
/* Some dbm require an initialization */
if (start) EXIM_DBCREATE_CURSOR(dbblock->dbptr, cursor);
odb = dbfn_open(s, O_RDWR, dbblock + i, TRUE);
stop = clock();
- if (odb != NULL)
+ if (odb)
{
current = i;
printf("opened %d\n", current);
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* This header file contains macro definitions so that a variety of DBM
/* Access functions */
/* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed */
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+#define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
*(dbpp) = tdb_open(CS name, 0, TDB_DEFAULT, flags, mode)
/* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
#define EXIM_DBDELETE_CURSOR(cursor) free(cursor)
/* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db) tdb_close(db)
+#define EXIM_DBCLOSE__(db) tdb_close(db)
/* Datum access types - these are intended to be assignable */
#ifdef DB_VERSION_STRING
+# if DB_VERSION_MAJOR >= 6
+# error Version 6 and later BDB API is not supported
+# endif
+
/* The API changed (again!) between the 2.x and 3.x versions */
#if DB_VERSION_MAJOR >= 3
/***************** Berkeley db 3.x/4.x native definitions ******************/
/* Basic DB type */
-#define EXIM_DB DB
-
+# if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+# define EXIM_DB DB_ENV
/* Cursor type, for scanning */
-#define EXIM_CURSOR DBC
+# define EXIM_CURSOR DBC
/* The datum type used for queries */
-#define EXIM_DATUM DBT
+# define EXIM_DATUM DBT
/* Some text for messages */
-#define EXIM_DBTYPE "db (v3/4)"
+# define EXIM_DBTYPE "db (v4.1+)"
+
+/* Only more-recent versions. 5+ ? */
+# ifndef DB_FORCESYNC
+# define DB_FORCESYNC 0
+# endif
+
/* Access functions */
/* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed. The
-API changed for DB 4.1. */
+API changed for DB 4.1. - and we also starting using the "env" with a
+specified working dir, to avoid the DBCONFIG file trap. */
+
+# define ENV_TO_DB(env) ((DB *)((env)->app_private))
+
+# define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
+ if ( db_env_create(dbpp, 0) != 0 \
+ || ((*dbpp)->set_errcall(*dbpp, dbfn_bdb_error_callback), 0) \
+ || (*dbpp)->open(*dbpp, CS dirname, DB_CREATE|DB_INIT_MPOOL|DB_PRIVATE, 0) != 0\
+ ) \
+ *dbpp = NULL; \
+ else if (db_create((DB **) &((*dbpp)->app_private), *dbpp, 0) != 0) \
+ { \
+ ((DB_ENV *)(*dbpp))->close((DB_ENV *)(*dbpp), 0); \
+ *dbpp = NULL; \
+ } \
+ else if (ENV_TO_DB(*dbpp)->open(ENV_TO_DB(*dbpp), NULL, CS name, NULL, \
+ (flags) == O_RDONLY ? DB_UNKNOWN : DB_HASH, \
+ (flags) == O_RDONLY ? DB_RDONLY : DB_CREATE, \
+ mode) != 0 \
+ ) \
+ { \
+ ENV_TO_DB(*dbpp)->close(ENV_TO_DB(*dbpp), 0); \
+ ((DB_ENV *)(*dbpp))->close((DB_ENV *)(*dbpp), 0); \
+ *dbpp = NULL; \
+ }
-#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
- if (db_create(dbpp, NULL, 0) != 0 || \
- ((*dbpp)->set_errcall(*dbpp, dbfn_bdb_error_callback), \
- ((*dbpp)->open)(*dbpp, NULL, CS name, NULL, \
- ((flags) == O_RDONLY)? DB_UNKNOWN : DB_HASH, \
- ((flags) == O_RDONLY)? DB_RDONLY : DB_CREATE, \
- mode)) != 0) *(dbpp) = NULL
-#else
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+/* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
+# define EXIM_DBGET(db, key, data) \
+ (ENV_TO_DB(db)->get(ENV_TO_DB(db), NULL, &key, &data, 0) == 0)
+
+/* EXIM_DBPUT - returns nothing useful, assumes replace mode */
+# define EXIM_DBPUT(db, key, data) \
+ ENV_TO_DB(db)->put(ENV_TO_DB(db), NULL, &key, &data, 0)
+
+/* EXIM_DBPUTB - non-overwriting for use by dbmbuild */
+# define EXIM_DBPUTB(db, key, data) \
+ ENV_TO_DB(db)->put(ENV_TO_DB(db), NULL, &key, &data, DB_NOOVERWRITE)
+
+/* Return values from EXIM_DBPUTB */
+
+# define EXIM_DBPUTB_OK 0
+# define EXIM_DBPUTB_DUP DB_KEYEXIST
+
+/* EXIM_DBDEL */
+# define EXIM_DBDEL(db, key) ENV_TO_DB(db)->del(ENV_TO_DB(db), NULL, &key, 0)
+
+/* EXIM_DBCREATE_CURSOR - initialize for scanning operation */
+
+# define EXIM_DBCREATE_CURSOR(db, cursor) \
+ ENV_TO_DB(db)->cursor(ENV_TO_DB(db), NULL, cursor, 0)
+
+/* EXIM_DBSCAN - returns TRUE if data is returned, FALSE at end */
+# define EXIM_DBSCAN(db, key, data, first, cursor) \
+ ((cursor)->c_get(cursor, &key, &data, \
+ (first? DB_FIRST : DB_NEXT)) == 0)
+
+/* EXIM_DBDELETE_CURSOR - terminate scanning operation */
+# define EXIM_DBDELETE_CURSOR(cursor) \
+ (cursor)->c_close(cursor)
+
+/* EXIM_DBCLOSE */
+# define EXIM_DBCLOSE__(db) \
+ (ENV_TO_DB(db)->close(ENV_TO_DB(db), 0) , ((DB_ENV *)(db))->close((DB_ENV *)(db), DB_FORCESYNC))
+
+/* Datum access types - these are intended to be assignable. */
+
+# define EXIM_DATUM_SIZE(datum) (datum).size
+# define EXIM_DATUM_DATA(datum) (datum).data
+
+/* The whole datum structure contains other fields that must be cleared
+before use, but we don't have to free anything after reading data. */
+
+# define EXIM_DATUM_INIT(datum) memset(&datum, 0, sizeof(datum))
+# define EXIM_DATUM_FREE(datum)
+
+# else /* pre- 4.1 */
+
+# define EXIM_DB DB
+
+/* Cursor type, for scanning */
+# define EXIM_CURSOR DBC
+
+/* The datum type used for queries */
+# define EXIM_DATUM DBT
+
+/* Some text for messages */
+# define EXIM_DBTYPE "db (v3/4)"
+
+/* Access functions */
+
+/* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed. */
+
+# define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
if (db_create(dbpp, NULL, 0) != 0 || \
((*dbpp)->set_errcall(*dbpp, dbfn_bdb_error_callback), \
((*dbpp)->open)(*dbpp, CS name, NULL, \
((flags) == O_RDONLY)? DB_UNKNOWN : DB_HASH, \
((flags) == O_RDONLY)? DB_RDONLY : DB_CREATE, \
mode)) != 0) *(dbpp) = NULL
-#endif
/* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
-#define EXIM_DBGET(db, key, data) \
+# define EXIM_DBGET(db, key, data) \
((db)->get(db, NULL, &key, &data, 0) == 0)
/* EXIM_DBPUT - returns nothing useful, assumes replace mode */
-#define EXIM_DBPUT(db, key, data) \
+# define EXIM_DBPUT(db, key, data) \
(db)->put(db, NULL, &key, &data, 0)
/* EXIM_DBPUTB - non-overwriting for use by dbmbuild */
-#define EXIM_DBPUTB(db, key, data) \
+# define EXIM_DBPUTB(db, key, data) \
(db)->put(db, NULL, &key, &data, DB_NOOVERWRITE)
/* Return values from EXIM_DBPUTB */
-#define EXIM_DBPUTB_OK 0
-#define EXIM_DBPUTB_DUP DB_KEYEXIST
+# define EXIM_DBPUTB_OK 0
+# define EXIM_DBPUTB_DUP DB_KEYEXIST
/* EXIM_DBDEL */
-#define EXIM_DBDEL(db, key) (db)->del(db, NULL, &key, 0)
+# define EXIM_DBDEL(db, key) (db)->del(db, NULL, &key, 0)
/* EXIM_DBCREATE_CURSOR - initialize for scanning operation */
-#define EXIM_DBCREATE_CURSOR(db, cursor) \
+# define EXIM_DBCREATE_CURSOR(db, cursor) \
(db)->cursor(db, NULL, cursor, 0)
/* EXIM_DBSCAN - returns TRUE if data is returned, FALSE at end */
-#define EXIM_DBSCAN(db, key, data, first, cursor) \
+# define EXIM_DBSCAN(db, key, data, first, cursor) \
((cursor)->c_get(cursor, &key, &data, \
(first? DB_FIRST : DB_NEXT)) == 0)
/* EXIM_DBDELETE_CURSOR - terminate scanning operation */
-#define EXIM_DBDELETE_CURSOR(cursor) \
+# define EXIM_DBDELETE_CURSOR(cursor) \
(cursor)->c_close(cursor)
/* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db) (db)->close(db, 0)
+# define EXIM_DBCLOSE__(db) (db)->close(db, 0)
/* Datum access types - these are intended to be assignable. */
-#define EXIM_DATUM_SIZE(datum) (datum).size
-#define EXIM_DATUM_DATA(datum) (datum).data
+# define EXIM_DATUM_SIZE(datum) (datum).size
+# define EXIM_DATUM_DATA(datum) (datum).data
/* The whole datum structure contains other fields that must be cleared
before use, but we don't have to free anything after reading data. */
-#define EXIM_DATUM_INIT(datum) memset(&datum, 0, sizeof(datum))
-#define EXIM_DATUM_FREE(datum)
+# define EXIM_DATUM_INIT(datum) memset(&datum, 0, sizeof(datum))
+# define EXIM_DATUM_FREE(datum)
+
+# endif
#else /* DB_VERSION_MAJOR >= 3 */
/* Access functions */
/* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed */
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+#define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
if ((errno = db_open(CS name, DB_HASH, \
((flags) == O_RDONLY)? DB_RDONLY : DB_CREATE, \
mode, NULL, NULL, dbpp)) != 0) *(dbpp) = NULL
(cursor)->c_close(cursor)
/* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db) (db)->close(db, 0)
+#define EXIM_DBCLOSE__(db) (db)->close(db, 0)
/* Datum access types - these are intended to be assignable. */
/* Access functions */
/* EXIM_DBOPEN - sets *dbpp to point to an EXIM_DB, NULL if failed */
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+#define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
*(dbpp) = dbopen(CS name, flags, mode, DB_HASH, NULL)
/* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
#define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
/* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db) (db)->close(db)
+#define EXIM_DBCLOSE__(db) (db)->close(db)
/* Datum access types - these are intended to be assignable */
/* Access functions */
/* EXIM_DBOPEN - returns a EXIM_DB *, NULL if failed */
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+#define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
{ (*(dbpp)) = (EXIM_DB *) malloc(sizeof(EXIM_DB));\
if (*(dbpp) != NULL) { \
(*(dbpp))->lkey.dptr = NULL;\
#define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
/* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db) \
+#define EXIM_DBCLOSE__(db) \
{ gdbm_close((db)->gdbm);\
if ((db)->lkey.dptr != NULL) free((db)->lkey.dptr);\
free(db); }
/* Access functions */
/* EXIM_DBOPEN - returns a EXIM_DB *, NULL if failed */
-#define EXIM_DBOPEN(name, flags, mode, dbpp) \
+#define EXIM_DBOPEN__(name, dirname, flags, mode, dbpp) \
*(dbpp) = dbm_open(CS name, flags, mode)
/* EXIM_DBGET - returns TRUE if successful, FALSE otherwise */
#define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
/* EXIM_DBCLOSE */
-#define EXIM_DBCLOSE(db) dbm_close(db)
+#define EXIM_DBCLOSE__(db) dbm_close(db)
/* Datum access types - these are intended to be assignable */
#endif /* USE_GDBM */
+
+
+
+
+# ifdef COMPILE_UTILITY
+
+# define EXIM_DBOPEN(name, dirname, flags, mode, dbpp) \
+ EXIM_DBOPEN__(name, dirname, flags, mode, dbpp)
+# define EXIM_DBCLOSE(db) EXIM_DBCLOSE__(db)
+
+# else
+
+# define EXIM_DBOPEN(name, dirname, flags, mode, dbpp) \
+ do { \
+ DEBUG(D_hints_lookup) \
+ debug_printf_indent("EXIM_DBOPEN: file <%s> dir <%s> flags=%s\n", \
+ (name), (dirname), \
+ (flags) == O_RDONLY ? "O_RDONLY" \
+ : (flags) == O_RDWR ? "O_RDWR" \
+ : (flags) == (O_RDWR|O_CREAT) ? "O_RDWR|O_CREAT" \
+ : "??"); \
+ EXIM_DBOPEN__(name, dirname, flags, mode, dbpp); \
+ DEBUG(D_hints_lookup) debug_printf_indent("returned from EXIM_DBOPEN: %p\n", *dbpp); \
+ } while(0)
+# define EXIM_DBCLOSE(db) \
+ do { \
+ DEBUG(D_hints_lookup) debug_printf_indent("EXIM_DBCLOSE(%p)\n", db); \
+ EXIM_DBCLOSE__(db); \
+ } while(0)
+
+# endif
+
/********************* End of dbm library definitions **********************/
uschar bloom[40]; /* Bloom filter which may be larger than this */
} dbdata_ratelimit_unique;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+/* This structure records the EHLO responses, cleartext and crypted,
+for an IP, as bitmasks (cf. OPTION_TLS) */
+
+typedef struct {
+ unsigned short cleartext_features;
+ unsigned short crypted_features;
+ unsigned short cleartext_auths;
+ unsigned short crypted_auths;
+} ehlo_resp_precis;
+
+typedef struct {
+ time_t time_stamp;
+ /*************/
+ ehlo_resp_precis data;
+} dbdata_ehlo_resp;
+#endif
+
/* End of dbstuff.h */
* wbreyha@gmx.net
* See the file NOTICE for conditions of use and distribution.
*
- * Copyright (c) The Exim Maintainers 2015 - 2016
+ * Copyright (c) The Exim Maintainers 2015 - 2018
*/
/* This patch is based on code from Tom Kistners exiscan (ACL integration) and
/* If sockip contains an ip, we use a tcp socket, otherwise a UNIX socket */
if(Ustrcmp(sockip, "")){
- ipaddress = gethostbyname((char *)sockip);
- bzero((char *) &serv_addr_in, sizeof(serv_addr_in));
+ ipaddress = gethostbyname(CS sockip);
+ bzero(CS &serv_addr_in, sizeof(serv_addr_in));
serv_addr_in.sin_family = AF_INET;
- bcopy((char *)ipaddress->h_addr, (char *)&serv_addr_in.sin_addr.s_addr, ipaddress->h_length);
+ bcopy(CS ipaddress->h_addr, CS &serv_addr_in.sin_addr.s_addr, ipaddress->h_length);
serv_addr_in.sin_port = htons(portnr);
if ((sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0){
DEBUG(D_acl)
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
tree_printsub(tree_node *p, int pos, int barswitch)
{
int i;
-if (p->right != NULL) tree_printsub(p->right, pos+2, 1);
+if (p->right) tree_printsub(p->right, pos+2, 1);
for (i = 0; i <= pos-1; i++) debug_printf("%c", tree_printline[i]);
debug_printf("-->%s [%d]\n", p->name, p->balance);
tree_printline[pos] = barswitch? '|' : ' ';
-if (p->left != NULL)
+if (p->left)
{
tree_printline[pos+2] = '|';
tree_printsub(p->left, pos+2, 0);
{
int i;
for (i = 0; i < tree_printlinesize; i++) tree_printline[i] = ' ';
-if (p == NULL) debug_printf("Empty Tree\n"); else tree_printsub(p, 0, 0);
+if (!p) debug_printf("Empty Tree\n"); else tree_printsub(p, 0, 0);
debug_printf("---- End of tree ----\n");
}
debug_print_argv(const uschar ** argv)
{
debug_printf("exec");
-while (*argv != NULL) debug_printf(" %.256s", *argv++);
+while (*argv) debug_printf(" %.256s", *argv++);
debug_printf("\n");
}
void
debug_print_string(uschar *debug_string)
{
-if (debug_string == NULL) return;
+if (!debug_string) return;
HDEBUG(D_any|D_v)
{
uschar *s = expand_string(debug_string);
- if (s == NULL)
+ if (!s)
debug_printf("failed to expand debug_output \"%s\": %s\n", debug_string,
expand_string_message);
else if (s[0] != 0)
debug_printf_indent(const char * format, ...)
{
va_list ap;
-unsigned depth = acl_level + expand_level, i;
-
-if (!debug_file) return;
-if (depth > 0)
- {
- for (i = depth >> 2; i > 0; i--)
- fprintf(debug_file, " .");
- fprintf(debug_file, "%*s", depth & 3, "");
- }
-
va_start(ap, format);
-debug_vprintf(format, ap);
+debug_vprintf(acl_level + expand_level, format, ap);
va_end(ap);
}
-
void
debug_printf(const char *format, ...)
{
va_list ap;
va_start(ap, format);
-debug_vprintf(format, ap);
+debug_vprintf(0, format, ap);
va_end(ap);
}
void
-debug_vprintf(const char *format, va_list ap)
+debug_vprintf(int indent, const char *format, va_list ap)
{
int save_errno = errno;
{
DEBUG(D_timestamp)
{
- time_t now = time(NULL);
- struct tm *t = timestamps_utc? gmtime(&now) : localtime(&now);
- (void) sprintf(CS debug_ptr, "%02d:%02d:%02d ", t->tm_hour, t->tm_min,
- t->tm_sec);
- while(*debug_ptr != 0) debug_ptr++;
+ struct timeval now;
+ time_t tmp;
+ struct tm * t;
+
+ gettimeofday(&now, NULL);
+ tmp = now.tv_sec;
+ t = f.timestamps_utc ? gmtime(&tmp) : localtime(&tmp);
+ debug_ptr += sprintf(CS debug_ptr,
+ LOGGING(millisec) ? "%02d:%02d:%02d.%03d " : "%02d:%02d:%02d ",
+ t->tm_hour, t->tm_min, t->tm_sec, (int)(now.tv_usec/1000));
}
DEBUG(D_pid)
- {
- sprintf(CS debug_ptr, "%5d ", (int)getpid());
- while(*debug_ptr != 0) debug_ptr++;
- }
+ debug_ptr += sprintf(CS debug_ptr, "%5d ", (int)getpid());
/* Set up prefix if outputting for host checking and not debugging */
debug_prefix_length = debug_ptr - debug_buffer;
}
+if (indent > 0)
+ {
+ int i;
+ for (i = indent >> 2; i > 0; i--)
+ DEBUG(D_noutf8)
+ {
+ Ustrcpy(debug_ptr, " !");
+ debug_ptr += 4; /* 3 spaces + shriek */
+ debug_prefix_length += 4;
+ }
+ else
+ {
+ Ustrcpy(debug_ptr, " " UTF8_VERT_2DASH);
+ debug_ptr += 6; /* 3 spaces + 3 UTF-8 octets */
+ debug_prefix_length += 6;
+ }
+
+ Ustrncpy(debug_ptr, " ", indent &= 3);
+ debug_ptr += indent;
+ debug_prefix_length += indent;
+ }
+
/* Use the checked formatting routine to ensure that the buffer
does not overflow. Ensure there's space for a newline at the end. */
-if (!string_vformat(debug_ptr,
- sizeof(debug_buffer) - (debug_ptr - debug_buffer) - 1, format, ap))
{
- uschar *s = US"**** debug string too long - truncated ****\n";
- uschar *p = debug_buffer + Ustrlen(debug_buffer);
- int maxlen = sizeof(debug_buffer) - Ustrlen(s) - 3;
- if (p > debug_buffer + maxlen) p = debug_buffer + maxlen;
- if (p > debug_buffer && p[-1] != '\n') *p++ = '\n';
- Ustrcpy(p, s);
+ gstring gs = { .size = (int)sizeof(debug_buffer) - 1,
+ .ptr = debug_ptr - debug_buffer,
+ .s = debug_buffer };
+ if (!string_vformat(&gs, FALSE, format, ap))
+ {
+ uschar * s = US"**** debug string too long - truncated ****\n";
+ uschar * p = gs.s + gs.ptr;
+ int maxlen = gs.size - Ustrlen(s) - 2;
+ if (p > gs.s + maxlen) p = gs.s + maxlen;
+ if (p > gs.s && p[-1] != '\n') *p++ = '\n';
+ Ustrcpy(p, s);
+ while(*debug_ptr) debug_ptr++;
+ }
+ else
+ {
+ string_from_gstring(&gs);
+ debug_ptr = gs.s + gs.ptr;
+ }
}
-while(*debug_ptr != 0) debug_ptr++;
-
/* Output the line if it is complete. If we added any prefix data and there
are internal newlines, make sure the prefix is on the continuation lines,
as long as there is room in the buffer. We want to do just a single fprintf()
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* The main code for delivering a message. */
#include "exim.h"
+#include "transports/smtp.h"
+#include <sys/uio.h>
#include <assert.h>
+/*************************************************
+* read as much as requested *
+*************************************************/
+
+/* The syscall read(2) doesn't always returns as much as we want. For
+several reasons it might get less. (Not talking about signals, as syscalls
+are restartable). When reading from a network or pipe connection the sender
+might send in smaller chunks, with delays between these chunks. The read(2)
+may return such a chunk.
+
+The more the writer writes and the smaller the pipe between write and read is,
+the more we get the chance of reading leass than requested. (See bug 2130)
+
+This function read(2)s until we got all the data we *requested*.
+
+Note: This function may block. Use it only if you're sure about the
+amount of data you will get.
+
+Argument:
+ fd the file descriptor to read from
+ buffer pointer to a buffer of size len
+ len the requested(!) amount of bytes
+
+Returns: the amount of bytes read
+*/
+static ssize_t
+readn(int fd, void * buffer, size_t len)
+{
+ void * next = buffer;
+ void * end = buffer + len;
+
+ while (next < end)
+ {
+ ssize_t got = read(fd, next, end - next);
+
+ /* I'm not sure if there are signals that can interrupt us,
+ for now I assume the worst */
+ if (got == -1 && errno == EINTR) continue;
+ if (got <= 0) return next - buffer;
+ next += got;
+ }
+
+ return len;
+}
+
+
/*************************************************
* Make a new address item *
*************************************************/
addr2->transport_return = addr->transport_return;
addr2->basic_errno = addr->basic_errno;
addr2->more_errno = addr->more_errno;
+ addr2->delivery_usec = addr->delivery_usec;
addr2->special_action = addr->special_action;
addr2->message = addr->message;
addr2->user_message = addr->user_message;
fields on incoming lines only.
Arguments:
- s The log line buffer
- sizep Pointer to the buffer size
- ptrp Pointer to current index into buffer
+ g The log line
addr The address to be logged
Returns: New value for s
*/
-static uschar *
-d_log_interface(uschar *s, int *sizep, int *ptrp)
+static gstring *
+d_log_interface(gstring * g)
{
if (LOGGING(incoming_interface) && LOGGING(outgoing_interface)
&& sending_ip_address)
{
- s = string_append(s, sizep, ptrp, 2, US" I=[", sending_ip_address);
- s = LOGGING(outgoing_port)
- ? string_append(s, sizep, ptrp, 2, US"]:",
- string_sprintf("%d", sending_port))
- : string_catn(s, sizep, ptrp, US"]", 1);
+ g = string_fmt_append(g, " I=[%s]", sending_ip_address);
+ if (LOGGING(outgoing_port))
+ g = string_fmt_append(g, "%d", sending_port);
}
-return s;
+return g;
}
-static uschar *
-d_hostlog(uschar * s, int * sp, int * pp, address_item * addr)
+static gstring *
+d_hostlog(gstring * g, address_item * addr)
{
host_item * h = addr->host_used;
-s = string_append(s, sp, pp, 2, US" H=", h->name);
+g = string_append(g, 2, US" H=", h->name);
if (LOGGING(dnssec) && h->dnssec == DS_YES)
- s = string_catn(s, sp, pp, US" DS", 3);
+ g = string_catn(g, US" DS", 3);
-s = string_append(s, sp, pp, 3, US" [", h->address, US"]");
+g = string_append(g, 3, US" [", h->address, US"]");
if (LOGGING(outgoing_port))
- s = string_append(s, sp, pp, 2, US":", string_sprintf("%d", h->port));
+ g = string_fmt_append(g, ":%d", h->port);
#ifdef SUPPORT_SOCKS
if (LOGGING(proxy) && proxy_local_address)
{
- s = string_append(s, sp, pp, 3, US" PRX=[", proxy_local_address, US"]");
+ g = string_append(g, 3, US" PRX=[", proxy_local_address, US"]");
if (LOGGING(outgoing_port))
- s = string_append(s, sp, pp, 2, US":", string_sprintf("%d",
- proxy_local_port));
+ g = string_fmt_append(g, ":%d", proxy_local_port);
}
#endif
-return d_log_interface(s, sp, pp);
+g = d_log_interface(g);
+
+if (testflag(addr, af_tcp_fastopen))
+ g = string_catn(g, US" TFO*", testflag(addr, af_tcp_fastopen_data) ? 5 : 4);
+
+return g;
}
#ifdef SUPPORT_TLS
-static uschar *
-d_tlslog(uschar * s, int * sizep, int * ptrp, address_item * addr)
+static gstring *
+d_tlslog(gstring * s, address_item * addr)
{
if (LOGGING(tls_cipher) && addr->cipher)
- s = string_append(s, sizep, ptrp, 2, US" X=", addr->cipher);
+ s = string_append(s, 2, US" X=", addr->cipher);
if (LOGGING(tls_certificate_verified) && addr->cipher)
- s = string_append(s, sizep, ptrp, 2, US" CV=",
+ s = string_append(s, 2, US" CV=",
testflag(addr, af_cert_verified)
?
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
testflag(addr, af_dane_verified)
? "dane"
:
"yes"
: "no");
if (LOGGING(tls_peerdn) && addr->peerdn)
- s = string_append(s, sizep, ptrp, 3, US" DN=\"",
- string_printing(addr->peerdn), US"\"");
+ s = string_append(s, 3, US" DN=\"", string_printing(addr->peerdn), US"\"");
return s;
}
#endif
if (!(s = expand_string(action)) && *expand_string_message)
log_write(0, LOG_MAIN|LOG_PANIC,
"failed to expand event_action %s in %s: %s\n",
- event, transport_name, expand_string_message);
+ event, transport_name ? transport_name : US"main", expand_string_message);
event_name = event_data = NULL;
const uschar * save_address = deliver_host_address;
const int save_port = deliver_host_port;
-if (!addr->transport)
- return;
-
router_name = addr->router ? addr->router->name : NULL;
-transport_name = addr->transport->name;
deliver_domain = addr->domain;
deliver_localpart = addr->local_part;
deliver_host = addr->host_used ? addr->host_used->name : NULL;
-(void) event_raise(addr->transport->event_action, event,
- addr->host_used
- || Ustrcmp(addr->transport->driver_name, "smtp") == 0
- || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
- ? addr->message : NULL);
+if (!addr->transport)
+ {
+ if (Ustrcmp(event, "msg:fail:delivery") == 0)
+ {
+ /* An address failed with no transport involved. This happens when
+ a filter was used which triggered a fail command (in such a case
+ a transport isn't needed). Convert it to an internal fail event. */
+
+ (void) event_raise(event_action, US"msg:fail:internal", addr->message);
+ }
+ }
+else
+ {
+ transport_name = addr->transport->name;
+
+ (void) event_raise(addr->transport->event_action, event,
+ addr->host_used
+ || Ustrcmp(addr->transport->driver_name, "smtp") == 0
+ || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
+ || Ustrcmp(addr->transport->driver_name, "autoreply") == 0
+ ? addr->message : NULL);
+ }
deliver_host_port = save_port;
deliver_host_address = save_address;
Arguments:
addr the address being logged
yield the current dynamic buffer pointer
- sizeptr points to current size
- ptrptr points to current insert pointer
Returns: the new value of the buffer pointer
*/
-static uschar *
-string_get_localpart(address_item *addr, uschar *yield, int *sizeptr,
- int *ptrptr)
+static gstring *
+string_get_localpart(address_item * addr, gstring * yield)
{
uschar * s;
if (testflag(addr, af_utf8_downcvt))
s = string_localpart_utf8_to_alabel(s, NULL);
#endif
- yield = string_cat(yield, sizeptr, ptrptr, s);
+ yield = string_cat(yield, s);
}
s = addr->local_part;
if (testflag(addr, af_utf8_downcvt))
s = string_localpart_utf8_to_alabel(s, NULL);
#endif
-yield = string_cat(yield, sizeptr, ptrptr, s);
+yield = string_cat(yield, s);
s = addr->suffix;
if (testflag(addr, af_include_affixes) && s)
if (testflag(addr, af_utf8_downcvt))
s = string_localpart_utf8_to_alabel(s, NULL);
#endif
- yield = string_cat(yield, sizeptr, ptrptr, s);
+ yield = string_cat(yield, s);
}
return yield;
case, we include the affixes here too.
Arguments:
- str points to start of growing string, or NULL
- size points to current allocation for string
- ptr points to offset for append point; updated on exit
+ g points to growing-string struct
addr bottom (ultimate) address
all_parents if TRUE, include all parents
success TRUE for successful delivery
Returns: a growable string in dynamic store
*/
-static uschar *
-string_log_address(uschar * str, int * size, int * ptr,
+static gstring *
+string_log_address(gstring * g,
address_item *addr, BOOL all_parents, BOOL success)
{
BOOL add_topaddr = TRUE;
) )
{
if (testflag(addr, af_file) && addr->local_part[0] != '/')
- str = string_catn(str, size, ptr, CUS"save ", 5);
- str = string_get_localpart(addr, str, size, ptr);
+ g = string_catn(g, CUS"save ", 5);
+ g = string_get_localpart(addr, g);
}
/* Other deliveries start with the full address. It we have split it into local
else
{
- uschar * cmp = str + *ptr;
+ uschar * cmp = g->s + g->ptr;
if (addr->local_part)
{
const uschar * s;
- str = string_get_localpart(addr, str, size, ptr);
- str = string_catn(str, size, ptr, US"@", 1);
+ g = string_get_localpart(addr, g);
+ g = string_catn(g, US"@", 1);
s = addr->domain;
#ifdef SUPPORT_I18N
if (testflag(addr, af_utf8_downcvt))
s = string_localpart_utf8_to_alabel(s, NULL);
#endif
- str = string_cat(str, size, ptr, s);
+ g = string_cat(g, s);
}
else
- str = string_cat(str, size, ptr, addr->address);
+ g = string_cat(g, addr->address);
/* If the address we are going to print is the same as the top address,
and all parents are not being included, don't add on the top address. First
of all, do a caseless comparison; if this succeeds, do a caseful comparison
on the local parts. */
- str[*ptr] = 0;
+ string_from_gstring(g); /* ensure nul-terminated */
if ( strcmpic(cmp, topaddr->address) == 0
&& Ustrncmp(cmp, topaddr->address, Ustrchr(cmp, '@') - cmp) == 0
&& !addr->onetime_parent
address_item *addr2;
for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
{
- str = string_catn(str, size, ptr, s, 2);
- str = string_cat (str, size, ptr, addr2->address);
+ g = string_catn(g, s, 2);
+ g = string_cat (g, addr2->address);
if (!all_parents) break;
s = US", ";
}
- str = string_catn(str, size, ptr, US")", 1);
+ g = string_catn(g, US")", 1);
}
/* Add the top address if it is required */
if (add_topaddr)
- str = string_append(str, size, ptr, 3,
+ g = string_append(g, 3,
US" <",
addr->onetime_parent ? addr->onetime_parent : topaddr->address,
US">");
-return str;
+return g;
+}
+
+
+
+void
+timesince(struct timeval * diff, struct timeval * then)
+{
+gettimeofday(diff, NULL);
+diff->tv_sec -= then->tv_sec;
+if ((diff->tv_usec -= then->tv_usec) < 0)
+ {
+ diff->tv_sec--;
+ diff->tv_usec += 1000*1000;
+ }
+}
+
+
+
+uschar *
+string_timediff(struct timeval * diff)
+{
+static uschar buf[sizeof("0.000s")];
+
+if (diff->tv_sec >= 5 || !LOGGING(millisec))
+ return readconf_printtime((int)diff->tv_sec);
+
+sprintf(CS buf, "%u.%03us", (uint)diff->tv_sec, (uint)diff->tv_usec/1000);
+return buf;
}
+uschar *
+string_timesince(struct timeval * then)
+{
+struct timeval diff;
+
+timesince(&diff, then);
+return string_timediff(&diff);
+}
+
/******************************************************************************/
void
delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
{
-int size = 256; /* Used for a temporary, */
-int ptr = 0; /* expanding buffer, for */
-uschar * s; /* building log lines; */
+gstring * g; /* Used for a temporary, expanding buffer, for building log lines */
void * reset_point; /* released afterwards. */
/* Log the delivery on the main log. We use an extensible string to build up
lookup_dnssec_authenticated = NULL;
#endif
-s = reset_point = store_get(size);
+g = reset_point = string_get(256);
if (msg)
- s = string_append(s, &size, &ptr, 2, host_and_ident(TRUE), US" ");
+ g = string_append(g, 2, host_and_ident(TRUE), US" ");
else
{
- s[ptr++] = logchar;
- s = string_catn(s, &size, &ptr, US"> ", 2);
+ g->s[0] = logchar; g->ptr = 1;
+ g = string_catn(g, US"> ", 2);
}
-s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), TRUE);
+g = string_log_address(g, addr, LOGGING(all_parents), TRUE);
if (LOGGING(sender_on_delivery) || msg)
- s = string_append(s, &size, &ptr, 3, US" F=<",
+ g = string_append(g, 3, US" F=<",
#ifdef SUPPORT_I18N
testflag(addr, af_utf8_downcvt)
? string_address_utf8_to_alabel(sender_address, NULL)
US">");
if (*queue_name)
- s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+ g = string_append(g, 2, US" Q=", queue_name);
#ifdef EXPERIMENTAL_SRS
if(addr->prop.srs_sender)
- s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->prop.srs_sender, US">");
+ g = string_append(g, 3, US" SRS=<", addr->prop.srs_sender, US">");
#endif
/* You might think that the return path must always be set for a successful
being run at all. */
if (used_return_path && LOGGING(return_path_on_delivery))
- s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+ g = string_append(g, 3, US" P=<", used_return_path, US">");
if (msg)
- s = string_append(s, &size, &ptr, 2, US" ", msg);
+ g = string_append(g, 2, US" ", msg);
/* For a delivery from a system filter, there may not be a router */
if (addr->router)
- s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+ g = string_append(g, 2, US" R=", addr->router->name);
-s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+g = string_append(g, 2, US" T=", addr->transport->name);
if (LOGGING(delivery_size))
- s = string_append(s, &size, &ptr, 2, US" S=",
- string_sprintf("%d", transport_count));
+ g = string_fmt_append(g, " S=%d", transport_count);
/* Local delivery */
if (addr->transport->info->local)
{
if (addr->host_list)
- s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
- s = d_log_interface(s, &size, &ptr);
+ g = string_append(g, 2, US" H=", addr->host_list->name);
+ g = d_log_interface(g);
if (addr->shadow_message)
- s = string_cat(s, &size, &ptr, addr->shadow_message);
+ g = string_cat(g, addr->shadow_message);
}
/* Remote delivery */
{
if (addr->host_used)
{
- s = d_hostlog(s, &size, &ptr, addr);
+ g = d_hostlog(g, addr);
if (continue_sequence > 1)
- s = string_catn(s, &size, &ptr, US"*", 1);
+ g = string_catn(g, US"*", 1);
#ifndef DISABLE_EVENT
deliver_host_address = addr->host_used->address;
}
#ifdef SUPPORT_TLS
- s = d_tlslog(s, &size, &ptr, addr);
+ g = d_tlslog(g, addr);
#endif
if (addr->authenticator)
{
- s = string_append(s, &size, &ptr, 2, US" A=", addr->authenticator);
+ g = string_append(g, 2, US" A=", addr->authenticator);
if (addr->auth_id)
{
- s = string_append(s, &size, &ptr, 2, US":", addr->auth_id);
+ g = string_append(g, 2, US":", addr->auth_id);
if (LOGGING(smtp_mailauth) && addr->auth_sndr)
- s = string_append(s, &size, &ptr, 2, US":", addr->auth_sndr);
+ g = string_append(g, 2, US":", addr->auth_sndr);
}
}
+ if (LOGGING(pipelining))
+ {
+ if (testflag(addr, af_pipelining))
+ g = string_catn(g, US" L", 2);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (testflag(addr, af_early_pipe))
+ g = string_catn(g, US"*", 1);
+#endif
+ }
+
#ifndef DISABLE_PRDR
- if (addr->flags & af_prdr_used)
- s = string_catn(s, &size, &ptr, US" PRDR", 5);
+ if (testflag(addr, af_prdr_used))
+ g = string_catn(g, US" PRDR", 5);
#endif
- if (addr->flags & af_chunking_used)
- s = string_catn(s, &size, &ptr, US" K", 2);
+ if (testflag(addr, af_chunking_used))
+ g = string_catn(g, US" K", 2);
}
/* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
}
*p++ = '\"';
*p = 0;
- s = string_append(s, &size, &ptr, 2, US" C=", big_buffer);
+ g = string_append(g, 2, US" C=", big_buffer);
}
/* Time on queue and actual time taken to deliver */
if (LOGGING(queue_time))
- s = string_append(s, &size, &ptr, 2, US" QT=",
- readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
+ g = string_append(g, 2, US" QT=",
+ string_timesince(&received_time));
if (LOGGING(deliver_time))
- s = string_append(s, &size, &ptr, 2, US" DT=",
- readconf_printtime(addr->more_errno));
+ {
+ struct timeval diff = {.tv_sec = addr->more_errno, .tv_usec = addr->delivery_usec};
+ g = string_append(g, 2, US" DT=", string_timediff(&diff));
+ }
/* string_cat() always leaves room for the terminator. Release the
store we used to build the line after writing it. */
-s[ptr] = 0;
-log_write(0, flags, "%s", s);
+log_write(0, flags, "%s", string_from_gstring(g));
#ifndef DISABLE_EVENT
if (!msg) msg_event_raise(US"msg:delivery", addr);
deferral_log(address_item * addr, uschar * now,
int logflags, uschar * driver_name, uschar * driver_kind)
{
-int size = 256; /* Used for a temporary, */
-int ptr = 0; /* expanding buffer, for */
-uschar * s; /* building log lines; */
-void * reset_point; /* released afterwards. */
-
-uschar ss[32];
+gstring * g;
+void * reset_point;
/* Build up the line that is used for both the message log and the main
log. */
-s = reset_point = store_get(size);
+g = reset_point = string_get(256);
/* Create the address string for logging. Must not do this earlier, because
an OK result may be changed to FAIL when a pipe returns text. */
-s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE);
+g = string_log_address(g, addr, LOGGING(all_parents), FALSE);
if (*queue_name)
- s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+ g = string_append(g, 2, US" Q=", queue_name);
/* Either driver_name contains something and driver_kind contains
" router" or " transport" (note the leading space), or driver_name is
if (driver_name)
{
if (driver_kind[1] == 't' && addr->router)
- s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
- Ustrcpy(ss, " ?=");
- ss[1] = toupper(driver_kind[1]);
- s = string_append(s, &size, &ptr, 2, ss, driver_name);
+ g = string_append(g, 2, US" R=", addr->router->name);
+ g = string_fmt_append(g, " %c=%s", toupper(driver_kind[1]), driver_name);
}
else if (driver_kind)
- s = string_append(s, &size, &ptr, 2, US" ", driver_kind);
+ g = string_append(g, 2, US" ", driver_kind);
-/*XXX need an s+s+p sprintf */
-sprintf(CS ss, " defer (%d)", addr->basic_errno);
-s = string_cat(s, &size, &ptr, ss);
+g = string_fmt_append(g, " defer (%d)", addr->basic_errno);
if (addr->basic_errno > 0)
- s = string_append(s, &size, &ptr, 2, US": ",
+ g = string_append(g, 2, US": ",
US strerror(addr->basic_errno));
if (addr->host_used)
{
- s = string_append(s, &size, &ptr, 5,
+ g = string_append(g, 5,
US" H=", addr->host_used->name,
US" [", addr->host_used->address, US"]");
if (LOGGING(outgoing_port))
{
int port = addr->host_used->port;
- s = string_append(s, &size, &ptr, 2,
- US":", port == PORT_NONE ? US"25" : string_sprintf("%d", port));
+ g = string_fmt_append(g, ":%d", port == PORT_NONE ? 25 : port);
}
}
if (addr->message)
- s = string_append(s, &size, &ptr, 2, US": ", addr->message);
+ g = string_append(g, 2, US": ", addr->message);
-s[ptr] = 0;
+(void) string_from_gstring(g);
/* Log the deferment in the message log, but don't clutter it
up with retry-time defers after the first delivery attempt. */
-if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
- deliver_msglog("%s %s\n", now, s);
+if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
+ deliver_msglog("%s %s\n", now, g->s);
/* Write the main log and reset the store.
For errors of the type "retry time not reached" (also remotes skipped
log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
- "== %s", s);
+ "== %s", g->s);
store_reset(reset_point);
return;
static void
failure_log(address_item * addr, uschar * driver_kind, uschar * now)
{
-int size = 256; /* Used for a temporary, */
-int ptr = 0; /* expanding buffer, for */
-uschar * s; /* building log lines; */
-void * reset_point; /* released afterwards. */
+void * reset_point;
+gstring * g = reset_point = string_get(256);
-/* Build up the log line for the message and main logs */
+#ifndef DISABLE_EVENT
+/* Message failures for which we will send a DSN get their event raised
+later so avoid doing it here. */
-s = reset_point = store_get(size);
+if ( !addr->prop.ignore_error
+ && !(addr->dsn_flags & (rf_dsnflags & ~rf_notify_failure))
+ )
+ msg_event_raise(US"msg:fail:delivery", addr);
+#endif
+
+/* Build up the log line for the message and main logs */
/* Create the address string for logging. Must not do this earlier, because
an OK result may be changed to FAIL when a pipe returns text. */
-s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE);
+g = string_log_address(g, addr, LOGGING(all_parents), FALSE);
if (LOGGING(sender_on_delivery))
- s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
+ g = string_append(g, 3, US" F=<", sender_address, US">");
if (*queue_name)
- s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+ g = string_append(g, 2, US" Q=", queue_name);
/* Return path may not be set if no delivery actually happened */
if (used_return_path && LOGGING(return_path_on_delivery))
- s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+ g = string_append(g, 3, US" P=<", used_return_path, US">");
if (addr->router)
- s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+ g = string_append(g, 2, US" R=", addr->router->name);
if (addr->transport)
- s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+ g = string_append(g, 2, US" T=", addr->transport->name);
if (addr->host_used)
- s = d_hostlog(s, &size, &ptr, addr);
+ g = d_hostlog(g, addr);
#ifdef SUPPORT_TLS
-s = d_tlslog(s, &size, &ptr, addr);
+g = d_tlslog(g, addr);
#endif
if (addr->basic_errno > 0)
- s = string_append(s, &size, &ptr, 2, US": ", US strerror(addr->basic_errno));
+ g = string_append(g, 2, US": ", US strerror(addr->basic_errno));
if (addr->message)
- s = string_append(s, &size, &ptr, 2, US": ", addr->message);
+ g = string_append(g, 2, US": ", addr->message);
-s[ptr] = 0;
+(void) string_from_gstring(g);
/* Do the logging. For the message log, "routing failed" for those cases,
just to make it clearer. */
if (driver_kind)
- deliver_msglog("%s %s failed for %s\n", now, driver_kind, s);
+ deliver_msglog("%s %s failed for %s\n", now, driver_kind, g->s);
else
- deliver_msglog("%s %s\n", now, s);
-
-log_write(0, LOG_MAIN, "** %s", s);
+ deliver_msglog("%s %s\n", now, g->s);
-#ifndef DISABLE_EVENT
-msg_event_raise(US"msg:fail:delivery", addr);
-#endif
+log_write(0, LOG_MAIN, "** %s", g->s);
store_reset(reset_point);
return;
/* Set up driver kind and name for logging. Disable logging if the router or
transport has disabled it. */
-if (driver_type == DTYPE_TRANSPORT)
+if (driver_type == EXIM_DTYPE_TRANSPORT)
{
if (addr->transport)
{
driver_name = addr->transport->name;
driver_kind = US" transport";
- disable_logging = addr->transport->disable_logging;
+ f.disable_logging = addr->transport->disable_logging;
}
else driver_kind = US"transporting";
}
-else if (driver_type == DTYPE_ROUTER)
+else if (driver_type == EXIM_DTYPE_ROUTER)
{
if (addr->router)
{
driver_name = addr->router->name;
driver_kind = US" router";
- disable_logging = addr->router->disable_logging;
+ f.disable_logging = addr->router->disable_logging;
}
else driver_kind = US"routing";
}
log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
addr->address, tb->name, sp);
}
- (void)fclose(f);
+ (void)fclose(f);
}
/* Handle returning options, but only if there is an address to return
tls_out.cipher = addr->cipher;
tls_out.peerdn = addr->peerdn;
tls_out.ocsp = addr->ocsp;
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
tls_out.dane_verified = testflag(addr, af_dane_verified);
# endif
#endif
tls_out.cipher = NULL;
tls_out.peerdn = NULL;
tls_out.ocsp = OCSP_NOT_REQ;
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
tls_out.dane_verified = FALSE;
# endif
#endif
if (addr->special_action == SPECIAL_FREEZE)
{
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
update_spool = TRUE;
}
/* If doing a 2-stage queue run, we skip writing to either the message
log or the main log for SMTP defers. */
- if (!queue_2stage || addr->basic_errno != 0)
+ if (!f.queue_2stage || addr->basic_errno != 0)
deferral_log(addr, now, logflags, driver_name, driver_kind);
}
later (with a log entry). */
if (!*sender_address && message_age >= ignore_bounce_errors_after)
- setflag(addr, af_ignore_error);
+ addr->prop.ignore_error = TRUE;
/* Freeze the message if requested, or if this is a bounce message (or other
message with null sender) and this address does not have its own errors
to ignore occurs later, instead of sending a message. Logging of freezing
occurs later, just before writing the -H file. */
- if ( !testflag(addr, af_ignore_error)
+ if ( !addr->prop.ignore_error
&& ( addr->special_action == SPECIAL_FREEZE
|| (sender_address[0] == 0 && !addr->prop.errors_address)
) )
{
frozen_info = addr->special_action == SPECIAL_FREEZE
? US""
- : sender_local && !local_error_message
+ : f.sender_local && !f.local_error_message
? US" (message created with -f <>)"
: US" (delivery error message)";
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
update_spool = TRUE;
/* Ensure logging is turned on again in all cases */
-disable_logging = FALSE;
+f.disable_logging = FALSE;
}
if (format)
{
va_list ap;
- uschar buffer[512];
+ gstring * g;
+
va_start(ap, format);
- if (!string_vformat(buffer, sizeof(buffer), CS format, ap))
- log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "common_error expansion was longer than " SIZE_T_FMT, sizeof(buffer));
+ g = string_vformat(NULL, TRUE, CS format, ap);
va_end(ap);
- addr->message = string_copy(buffer);
+ addr->message = string_from_gstring(g);
}
for (addr2 = addr->next; addr2; addr2 = addr2->next)
uschar *new_return_path = expand_string(tp->return_path);
if (!new_return_path)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
common_error(TRUE, addr, ERRNO_EXPANDFAIL,
US"Failed to expand return path \"%s\" in %s transport: %s",
addr->return_filename =
spool_fname(US"msglog", message_subdir, message_id,
string_sprintf("-%d-%d", getpid(), return_count++));
-
+
if ((addr->return_file = open_msglog_file(addr->return_filename, 0400, &error)) < 0)
{
common_error(TRUE, addr, errno, US"Unable to %s file for %s transport "
|| (ret = write(pfd[pipe_write], &addr2->flags, sizeof(addr2->flags))) != sizeof(addr2->flags)
|| (ret = write(pfd[pipe_write], &addr2->basic_errno, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->more_errno, sizeof(int))) != sizeof(int)
+ || (ret = write(pfd[pipe_write], &addr2->delivery_usec, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->special_action, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->transport,
sizeof(transport_instance *))) != sizeof(transport_instance *)
len = read(pfd[pipe_read], &addr2->flags, sizeof(addr2->flags));
len = read(pfd[pipe_read], &addr2->basic_errno, sizeof(int));
len = read(pfd[pipe_read], &addr2->more_errno, sizeof(int));
+ len = read(pfd[pipe_read], &addr2->delivery_usec, sizeof(int));
len = read(pfd[pipe_read], &addr2->special_action, sizeof(int));
len = read(pfd[pipe_read], &addr2->transport,
sizeof(transport_instance *));
/* In the test harness, wait just a bit to let the subprocess finish off
any debug output etc first. */
- if (running_in_test_harness) millisleep(300);
+ if (f.running_in_test_harness) millisleep(300);
DEBUG(D_deliver) debug_printf("journalling %s", big_buffer);
len = Ustrlen(big_buffer);
next = addr->next;
addr->message = US"concurrency limit reached for transport";
addr->basic_errno = ERRNO_TRETRY;
- post_process_one(addr, DEFER, LOG_MAIN, DTYPE_TRANSPORT, 0);
+ post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0);
} while ((addr = next));
return TRUE;
}
while (addr_local)
{
- time_t delivery_start;
- int deliver_time;
+ struct timeval delivery_start;
+ struct timeval deliver_time;
address_item *addr2, *addr3, *nextaddr;
int logflags = LOG_MAIN;
- int logchar = dont_deliver? '*' : '=';
+ int logchar = f.dont_deliver? '*' : '=';
transport_instance *tp;
uschar * serialize_key = NULL;
if (!(tp = addr->transport))
{
logflags |= LOG_PANIC;
- disable_logging = FALSE; /* Jic */
+ f.disable_logging = FALSE; /* Jic */
addr->message = addr->router
? string_sprintf("No transport set by %s router", addr->router->name)
: string_sprintf("No transport set by system filter");
- post_process_one(addr, DEFER, logflags, DTYPE_TRANSPORT, 0);
+ post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
continue;
}
/* There are weird cases where logging is disabled */
- disable_logging = tp->disable_logging;
+ f.disable_logging = tp->disable_logging;
/* Check for batched addresses and possible amalgamation. Skip all the work
if either batch_max <= 1 or there aren't any other addresses for local
BOOL ok =
tp == next->transport
&& !previously_transported(next, TRUE)
- && (addr->flags & (af_pfr|af_file)) == (next->flags & (af_pfr|af_file))
+ && testflag(addr, af_pfr) == testflag(next, af_pfr)
+ && testflag(addr, af_file) == testflag(next, af_file)
&& (!uses_lp || Ustrcmp(next->local_part, addr->local_part) == 0)
&& (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0)
&& same_strings(next->prop.errors_address, addr->prop.errors_address)
while (addr)
{
addr2 = addr->next;
- post_process_one(addr, rc, logflags, DTYPE_TRANSPORT, 0);
+ post_process_one(addr, rc, logflags, EXIM_DTYPE_TRANSPORT, 0);
addr = addr2;
}
continue; /* With next batch of addresses */
retry_record->expired);
}
- if (queue_running && !deliver_force)
+ if (f.queue_running && !f.deliver_force)
{
ok = (now - retry_record->time_stamp > retry_data_expire)
|| (now >= retry_record->next_try)
this->basic_errno = ERRNO_LRETRY;
addr2 = addr3 ? (addr3->next = addr2->next)
: (addr = addr2->next);
- post_process_one(this, DEFER, logflags, DTYPE_TRANSPORT, 0);
+ post_process_one(this, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
}
}
do
{
addr = addr->next;
- post_process_one(addr, DEFER, logflags, DTYPE_TRANSPORT, 0);
+ post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
} while ((addr = addr2));
}
continue; /* Loop for the next set of addresses. */
single delivery. */
deliver_set_expansions(addr);
- delivery_start = time(NULL);
+
+ gettimeofday(&delivery_start, NULL);
deliver_local(addr, FALSE);
- deliver_time = (int)(time(NULL) - delivery_start);
+ timesince(&deliver_time, &delivery_start);
/* If a shadow transport (which must perforce be another local transport), is
defined, and its condition is met, we must pass the message to the shadow
/* Done with this address */
- if (result == OK) addr2->more_errno = deliver_time;
- post_process_one(addr2, result, logflags, DTYPE_TRANSPORT, logchar);
+ if (result == OK)
+ {
+ addr2->more_errno = deliver_time.tv_sec;
+ addr2->delivery_usec = deliver_time.tv_usec;
+ }
+ post_process_one(addr2, result, logflags, EXIM_DTYPE_TRANSPORT, logchar);
/* If a pipe delivery generated text to be sent back, the result may be
changed to FAIL, and we must copy this for subsequent addresses in the
often bigger) so even if we are reading while the subprocess is still going, we
should never have only a partial item in the buffer.
+hs12: This assumption is not true anymore, since we get quite large items (certificate
+information and such).
+
Argument:
poffset the offset of the parlist item
eop TRUE if the process has completed
address_item *addr = p->addr;
pid_t pid = p->pid;
int fd = p->fd;
-uschar *endptr = big_buffer;
-uschar *ptr = endptr;
+
uschar *msg = p->msg;
BOOL done = p->done;
-BOOL unfinished = TRUE;
-/* minimum size to read is header size including id, subid and length */
-int required = PIPE_HEADER_SIZE;
/* Loop through all items, reading from the pipe when necessary. The pipe
-is set up to be non-blocking, but there are two different Unix mechanisms in
-use. Exim uses O_NONBLOCK if it is defined. This returns 0 for end of file,
-and EAGAIN for no more data. If O_NONBLOCK is not defined, Exim uses O_NDELAY,
-which returns 0 for both end of file and no more data. We distinguish the
-two cases by taking 0 as end of file only when we know the process has
-completed.
-
-Each separate item is written to the pipe in a single write(), and as they are
-all short items, the writes will all be atomic and we should never find
-ourselves in the position of having read an incomplete item. "Short" in this
-case can mean up to about 1K in the case when there is a long error message
-associated with an address. */
-
-DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
- (int)p->pid, eop? "ended" : "not ended");
-
-while (!done)
- {
- retry_item *r, **rp;
- int remaining = endptr - ptr;
- uschar header[PIPE_HEADER_SIZE + 1];
- uschar id, subid;
- uschar *endc;
-
- /* Read (first time) or top up the chars in the buffer if necessary.
- There will be only one read if we get all the available data (i.e. don't
- fill the buffer completely). */
-
- if (remaining < required && unfinished)
- {
- int len;
- int available = big_buffer_size - remaining;
-
- if (remaining > 0) memmove(big_buffer, ptr, remaining);
+used to be non-blocking. But I do not see a reason for using non-blocking I/O
+here, as the preceding select() tells us, if data is available for reading.
- ptr = big_buffer;
- endptr = big_buffer + remaining;
- len = read(fd, endptr, available);
+A read() on a "selected" handle should never block, but(!) it may return
+less data then we expected. (The buffer size we pass to read() shouldn't be
+understood as a "request", but as a "limit".)
- DEBUG(D_deliver) debug_printf("read() yielded %d\n", len);
+Each separate item is written to the pipe in a timely manner. But, especially for
+larger items, the read(2) may already return partial data from the write(2).
- /* If the result is EAGAIN and the process is not complete, just
- stop reading any more and process what we have already. */
+The write is atomic mostly (depending on the amount written), but atomic does
+not imply "all or noting", it just is "not intermixed" with other writes on the
+same channel (pipe).
- if (len < 0)
- {
- if (!eop && errno == EAGAIN) len = 0; else
- {
- msg = string_sprintf("failed to read pipe from transport process "
- "%d for transport %s: %s", pid, addr->transport->driver_name,
- strerror(errno));
- break;
- }
- }
-
- /* If the length is zero (eof or no-more-data), just process what we
- already have. Note that if the process is still running and we have
- read all the data in the pipe (but less that "available") then we
- won't read any more, as "unfinished" will get set FALSE. */
-
- endptr += len;
- remaining += len;
- unfinished = len == available;
- }
+*/
- /* If we are at the end of the available data, exit the loop. */
- if (ptr >= endptr) break;
+DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
+ (int)p->pid, eop? "ended" : "not ended yet");
- /* copy and read header */
- memcpy(header, ptr, PIPE_HEADER_SIZE);
- header[PIPE_HEADER_SIZE] = '\0';
- id = header[0];
- subid = header[1];
- required = Ustrtol(header + 2, &endc, 10) + PIPE_HEADER_SIZE; /* header + data */
- if (*endc)
- {
- msg = string_sprintf("failed to read pipe from transport process "
- "%d for transport %s: error reading size from header", pid, addr->transport->driver_name);
+while (!done)
+ {
+ retry_item *r, **rp;
+ uschar pipeheader[PIPE_HEADER_SIZE+1];
+ uschar *id = &pipeheader[0];
+ uschar *subid = &pipeheader[1];
+ uschar *ptr = big_buffer;
+ size_t required = PIPE_HEADER_SIZE; /* first the pipehaeder, later the data */
+ ssize_t got;
+
+ DEBUG(D_deliver) debug_printf(
+ "expect %lu bytes (pipeheader) from tpt process %d\n", (u_long)required, pid);
+
+ /* We require(!) all the PIPE_HEADER_SIZE bytes here, as we know,
+ they're written in a timely manner, so waiting for the write shouldn't hurt a lot.
+ If we get less, we can assume the subprocess do be done and do not expect any further
+ information from it. */
+
+ if ((got = readn(fd, pipeheader, required)) != required)
+ {
+ msg = string_sprintf("got " SSIZE_T_FMT " of %d bytes (pipeheader) "
+ "from transport process %d for transport %s",
+ got, PIPE_HEADER_SIZE, pid, addr->transport->driver_name);
done = TRUE;
break;
}
+ pipeheader[PIPE_HEADER_SIZE] = '\0';
DEBUG(D_deliver)
- debug_printf("header read id:%c,subid:%c,size:%s,required:%d,remaining:%d,unfinished:%d\n",
- id, subid, header+2, required, remaining, unfinished);
+ debug_printf("got %ld bytes (pipeheader) from transport process %d\n",
+ (long) got, pid);
- /* is there room for the dataset we want to read ? */
- if (required > big_buffer_size - PIPE_HEADER_SIZE)
+ {
+ /* If we can't decode the pipeheader, the subprocess seems to have a
+ problem, we do not expect any furher information from it. */
+ char *endc;
+ required = Ustrtol(pipeheader+2, &endc, 10);
+ if (*endc)
{
- msg = string_sprintf("failed to read pipe from transport process "
- "%d for transport %s: big_buffer too small! required size=%d buffer size=%d", pid, addr->transport->driver_name,
- required, big_buffer_size - PIPE_HEADER_SIZE);
+ msg = string_sprintf("failed to read pipe "
+ "from transport process %d for transport %s: error decoding size from header",
+ pid, addr->transport->driver_name);
done = TRUE;
break;
}
+ }
- /* we wrote all datasets with atomic write() calls
- remaining < required only happens if big_buffer was too small
- to get all available data from pipe. unfinished has to be true
- as well. */
- if (remaining < required)
+ DEBUG(D_deliver)
+ debug_printf("expect %lu bytes (pipedata) from transport process %d\n",
+ (u_long)required, pid);
+
+ /* Same as above, the transport process will write the bytes announced
+ in a timely manner, so we can just wait for the bytes, getting less than expected
+ is considered a problem of the subprocess, we do not expect anything else from it. */
+ if ((got = readn(fd, big_buffer, required)) != required)
{
- if (unfinished)
- continue;
- msg = string_sprintf("failed to read pipe from transport process "
- "%d for transport %s: required size=%d > remaining size=%d and unfinished=false",
- pid, addr->transport->driver_name, required, remaining);
+ msg = string_sprintf("got only " SSIZE_T_FMT " of " SIZE_T_FMT
+ " bytes (pipedata) from transport process %d for transport %s",
+ got, required, pid, addr->transport->driver_name);
done = TRUE;
break;
}
- /* step behind the header */
- ptr += PIPE_HEADER_SIZE;
-
/* Handle each possible type of item, assuming the complete item is
available in store. */
- switch (id)
+ switch (*id)
{
/* Host items exist only if any hosts were marked unusable. Match
up by checking the IP address. */
case 'H':
- for (h = addrlist->host_list; h; h = h->next)
- {
- if (!h->address || Ustrcmp(h->address, ptr+2) != 0) continue;
- h->status = ptr[0];
- h->why = ptr[1];
- }
- ptr += 2;
- while (*ptr++);
- break;
+ for (h = addrlist->host_list; h; h = h->next)
+ {
+ if (!h->address || Ustrcmp(h->address, ptr+2) != 0) continue;
+ h->status = ptr[0];
+ h->why = ptr[1];
+ }
+ ptr += 2;
+ while (*ptr++);
+ break;
/* Retry items are sent in a preceding R item for each address. This is
kept separate to keep each message short enough to guarantee it won't
that a "delete" item is dropped in favour of an "add" item. */
case 'R':
- if (!addr) goto ADDR_MISMATCH;
+ if (!addr) goto ADDR_MISMATCH;
- DEBUG(D_deliver|D_retry)
- debug_printf("reading retry information for %s from subprocess\n",
- ptr+1);
+ DEBUG(D_deliver|D_retry)
+ debug_printf("reading retry information for %s from subprocess\n",
+ ptr+1);
- /* Cut out any "delete" items on the list. */
+ /* Cut out any "delete" items on the list. */
- for (rp = &(addr->retries); (r = *rp); rp = &r->next)
- if (Ustrcmp(r->key, ptr+1) == 0) /* Found item with same key */
- {
- if ((r->flags & rf_delete) == 0) break; /* It was not "delete" */
- *rp = r->next; /* Excise a delete item */
- DEBUG(D_deliver|D_retry)
- debug_printf(" existing delete item dropped\n");
- }
+ for (rp = &addr->retries; (r = *rp); rp = &r->next)
+ if (Ustrcmp(r->key, ptr+1) == 0) /* Found item with same key */
+ {
+ if (!(r->flags & rf_delete)) break; /* It was not "delete" */
+ *rp = r->next; /* Excise a delete item */
+ DEBUG(D_deliver|D_retry)
+ debug_printf(" existing delete item dropped\n");
+ }
- /* We want to add a delete item only if there is no non-delete item;
- however we still have to step ptr through the data. */
+ /* We want to add a delete item only if there is no non-delete item;
+ however we still have to step ptr through the data. */
- if (!r || (*ptr & rf_delete) == 0)
- {
- r = store_get(sizeof(retry_item));
- r->next = addr->retries;
- addr->retries = r;
- r->flags = *ptr++;
- r->key = string_copy(ptr);
- while (*ptr++);
- memcpy(&(r->basic_errno), ptr, sizeof(r->basic_errno));
- ptr += sizeof(r->basic_errno);
- memcpy(&(r->more_errno), ptr, sizeof(r->more_errno));
- ptr += sizeof(r->more_errno);
- r->message = (*ptr)? string_copy(ptr) : NULL;
- DEBUG(D_deliver|D_retry)
- debug_printf(" added %s item\n",
- ((r->flags & rf_delete) == 0)? "retry" : "delete");
- }
+ if (!r || !(*ptr & rf_delete))
+ {
+ r = store_get(sizeof(retry_item));
+ r->next = addr->retries;
+ addr->retries = r;
+ r->flags = *ptr++;
+ r->key = string_copy(ptr);
+ while (*ptr++);
+ memcpy(&r->basic_errno, ptr, sizeof(r->basic_errno));
+ ptr += sizeof(r->basic_errno);
+ memcpy(&r->more_errno, ptr, sizeof(r->more_errno));
+ ptr += sizeof(r->more_errno);
+ r->message = *ptr ? string_copy(ptr) : NULL;
+ DEBUG(D_deliver|D_retry) debug_printf(" added %s item\n",
+ r->flags & rf_delete ? "delete" : "retry");
+ }
- else
- {
- DEBUG(D_deliver|D_retry)
- debug_printf(" delete item not added: non-delete item exists\n");
- ptr++;
- while(*ptr++);
- ptr += sizeof(r->basic_errno) + sizeof(r->more_errno);
- }
+ else
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf(" delete item not added: non-delete item exists\n");
+ ptr++;
+ while(*ptr++);
+ ptr += sizeof(r->basic_errno) + sizeof(r->more_errno);
+ }
- while(*ptr++);
- break;
+ while(*ptr++);
+ break;
/* Put the amount of data written into the parlist block */
case 'S':
- memcpy(&(p->transport_count), ptr, sizeof(transport_count));
- ptr += sizeof(transport_count);
- break;
+ memcpy(&(p->transport_count), ptr, sizeof(transport_count));
+ ptr += sizeof(transport_count);
+ break;
/* Address items are in the order of items on the address chain. We
remember the current address value in case this function is called
#ifdef SUPPORT_TLS
case 'X':
- if (!addr) goto ADDR_MISMATCH; /* Below, in 'A' handler */
- switch (subid)
- {
- case '1':
- addr->cipher = NULL;
- addr->peerdn = NULL;
-
- if (*ptr)
- addr->cipher = string_copy(ptr);
- while (*ptr++);
- if (*ptr)
- addr->peerdn = string_copy(ptr);
- break;
-
- case '2':
- if (*ptr)
- (void) tls_import_cert(ptr, &addr->peercert);
- else
- addr->peercert = NULL;
- break;
+ if (!addr) goto ADDR_MISMATCH; /* Below, in 'A' handler */
+ switch (*subid)
+ {
+ case '1':
+ addr->cipher = NULL;
+ addr->peerdn = NULL;
- case '3':
- if (*ptr)
- (void) tls_import_cert(ptr, &addr->ourcert);
- else
- addr->ourcert = NULL;
- break;
+ if (*ptr)
+ addr->cipher = string_copy(ptr);
+ while (*ptr++);
+ if (*ptr)
+ addr->peerdn = string_copy(ptr);
+ break;
+
+ case '2':
+ if (*ptr)
+ (void) tls_import_cert(ptr, &addr->peercert);
+ else
+ addr->peercert = NULL;
+ break;
+
+ case '3':
+ if (*ptr)
+ (void) tls_import_cert(ptr, &addr->ourcert);
+ else
+ addr->ourcert = NULL;
+ break;
# ifndef DISABLE_OCSP
- case '4':
- addr->ocsp = OCSP_NOT_REQ;
- if (*ptr)
- addr->ocsp = *ptr - '0';
- break;
+ case '4':
+ addr->ocsp = *ptr ? *ptr - '0' : OCSP_NOT_REQ;
+ break;
# endif
- }
- while (*ptr++);
- break;
+ }
+ while (*ptr++);
+ break;
#endif /*SUPPORT_TLS*/
case 'C': /* client authenticator information */
- switch (subid)
- {
- case '1':
- addr->authenticator = (*ptr)? string_copy(ptr) : NULL;
- break;
- case '2':
- addr->auth_id = (*ptr)? string_copy(ptr) : NULL;
- break;
- case '3':
- addr->auth_sndr = (*ptr)? string_copy(ptr) : NULL;
- break;
- }
- while (*ptr++);
- break;
+ switch (*subid)
+ {
+ case '1': addr->authenticator = *ptr ? string_copy(ptr) : NULL; break;
+ case '2': addr->auth_id = *ptr ? string_copy(ptr) : NULL; break;
+ case '3': addr->auth_sndr = *ptr ? string_copy(ptr) : NULL; break;
+ }
+ while (*ptr++);
+ break;
#ifndef DISABLE_PRDR
case 'P':
- addr->flags |= af_prdr_used;
- break;
+ setflag(addr, af_prdr_used);
+ break;
+#endif
+
+ case 'L':
+ switch (*subid)
+ {
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case 2: setflag(addr, af_early_pipe); /*FALLTHROUGH*/
#endif
+ case 1: setflag(addr, af_pipelining); break;
+ }
+ break;
case 'K':
- addr->flags |= af_chunking_used;
- break;
+ setflag(addr, af_chunking_used);
+ break;
- case 'D':
- if (!addr) goto ADDR_MISMATCH;
- memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware));
- ptr += sizeof(addr->dsn_aware);
- DEBUG(D_deliver) debug_printf("DSN read: addr->dsn_aware = %d\n", addr->dsn_aware);
- break;
+ case 'T':
+ setflag(addr, af_tcp_fastopen_conn);
+ if (*subid > '0') setflag(addr, af_tcp_fastopen);
+ if (*subid > '1') setflag(addr, af_tcp_fastopen_data);
+ break;
- case 'A':
- if (!addr)
- {
- ADDR_MISMATCH:
- msg = string_sprintf("address count mismatch for data read from pipe "
- "for transport process %d for transport %s", pid,
- addrlist->transport->driver_name);
- done = TRUE;
+ case 'D':
+ if (!addr) goto ADDR_MISMATCH;
+ memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware));
+ ptr += sizeof(addr->dsn_aware);
+ DEBUG(D_deliver) debug_printf("DSN read: addr->dsn_aware = %d\n", addr->dsn_aware);
break;
- }
- switch (subid)
- {
-#ifdef SUPPORT_SOCKS
- case '2': /* proxy information; must arrive before A0 and applies to that addr XXX oops*/
- proxy_session = TRUE; /*XXX should this be cleared somewhere? */
- if (*ptr == 0)
- ptr++;
- else
- {
- proxy_local_address = string_copy(ptr);
- while(*ptr++);
- memcpy(&proxy_local_port, ptr, sizeof(proxy_local_port));
- ptr += sizeof(proxy_local_port);
- }
+ case 'A':
+ if (!addr)
+ {
+ ADDR_MISMATCH:
+ msg = string_sprintf("address count mismatch for data read from pipe "
+ "for transport process %d for transport %s", pid,
+ addrlist->transport->driver_name);
+ done = TRUE;
break;
-#endif
+ }
-#ifdef EXPERIMENTAL_DSN_INFO
- case '1': /* must arrive before A0, and applies to that addr */
- /* Two strings: smtp_greeting and helo_response */
- addr->smtp_greeting = string_copy(ptr);
- while(*ptr++);
- addr->helo_response = string_copy(ptr);
- while(*ptr++);
- break;
-#endif
+ switch (*subid)
+ {
+ #ifdef SUPPORT_SOCKS
+ case '2': /* proxy information; must arrive before A0 and applies to that addr XXX oops*/
+ proxy_session = TRUE; /*XXX should this be cleared somewhere? */
+ if (*ptr == 0)
+ ptr++;
+ else
+ {
+ proxy_local_address = string_copy(ptr);
+ while(*ptr++);
+ memcpy(&proxy_local_port, ptr, sizeof(proxy_local_port));
+ ptr += sizeof(proxy_local_port);
+ }
+ break;
+ #endif
- case '0':
- addr->transport_return = *ptr++;
- addr->special_action = *ptr++;
- memcpy(&(addr->basic_errno), ptr, sizeof(addr->basic_errno));
- ptr += sizeof(addr->basic_errno);
- memcpy(&(addr->more_errno), ptr, sizeof(addr->more_errno));
- ptr += sizeof(addr->more_errno);
- memcpy(&(addr->flags), ptr, sizeof(addr->flags));
- ptr += sizeof(addr->flags);
- addr->message = (*ptr)? string_copy(ptr) : NULL;
- while(*ptr++);
- addr->user_message = (*ptr)? string_copy(ptr) : NULL;
- while(*ptr++);
+ #ifdef EXPERIMENTAL_DSN_INFO
+ case '1': /* must arrive before A0, and applies to that addr */
+ /* Two strings: smtp_greeting and helo_response */
+ addr->smtp_greeting = string_copy(ptr);
+ while(*ptr++);
+ addr->helo_response = string_copy(ptr);
+ while(*ptr++);
+ break;
+ #endif
+
+ case '0':
+ DEBUG(D_deliver) debug_printf("A0 %s tret %d\n", addr->address, *ptr);
+ addr->transport_return = *ptr++;
+ addr->special_action = *ptr++;
+ memcpy(&addr->basic_errno, ptr, sizeof(addr->basic_errno));
+ ptr += sizeof(addr->basic_errno);
+ memcpy(&addr->more_errno, ptr, sizeof(addr->more_errno));
+ ptr += sizeof(addr->more_errno);
+ memcpy(&addr->delivery_usec, ptr, sizeof(addr->delivery_usec));
+ ptr += sizeof(addr->delivery_usec);
+ memcpy(&addr->flags, ptr, sizeof(addr->flags));
+ ptr += sizeof(addr->flags);
+ addr->message = *ptr ? string_copy(ptr) : NULL;
+ while(*ptr++);
+ addr->user_message = *ptr ? string_copy(ptr) : NULL;
+ while(*ptr++);
- /* Always two strings for host information, followed by the port number and DNSSEC mark */
+ /* Always two strings for host information, followed by the port number and DNSSEC mark */
- if (*ptr != 0)
- {
- h = store_get(sizeof(host_item));
- h->name = string_copy(ptr);
- while (*ptr++);
- h->address = string_copy(ptr);
- while(*ptr++);
- memcpy(&(h->port), ptr, sizeof(h->port));
- ptr += sizeof(h->port);
- h->dnssec = *ptr == '2' ? DS_YES
- : *ptr == '1' ? DS_NO
- : DS_UNK;
- ptr++;
- addr->host_used = h;
- }
- else ptr++;
+ if (*ptr)
+ {
+ h = store_get(sizeof(host_item));
+ h->name = string_copy(ptr);
+ while (*ptr++);
+ h->address = string_copy(ptr);
+ while(*ptr++);
+ memcpy(&h->port, ptr, sizeof(h->port));
+ ptr += sizeof(h->port);
+ h->dnssec = *ptr == '2' ? DS_YES
+ : *ptr == '1' ? DS_NO
+ : DS_UNK;
+ ptr++;
+ addr->host_used = h;
+ }
+ else ptr++;
- /* Finished with this address */
+ /* Finished with this address */
- addr = addr->next;
- break;
- }
- break;
+ addr = addr->next;
+ break;
+ }
+ break;
/* Local interface address/port */
case 'I':
- if (*ptr) sending_ip_address = string_copy(ptr);
- while (*ptr++) ;
- if (*ptr) sending_port = atoi(CS ptr);
- while (*ptr++) ;
- break;
+ if (*ptr) sending_ip_address = string_copy(ptr);
+ while (*ptr++) ;
+ if (*ptr) sending_port = atoi(CS ptr);
+ while (*ptr++) ;
+ break;
/* Z marks the logical end of the data. It is followed by '0' if
continue_transport was NULL at the end of transporting, otherwise '1'.
most normal messages it will remain NULL all the time. */
case 'Z':
- if (*ptr == '0')
- {
- continue_transport = NULL;
- continue_hostname = NULL;
- }
- done = TRUE;
- DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
- break;
+ if (*ptr == '0')
+ {
+ continue_transport = NULL;
+ continue_hostname = NULL;
+ }
+ done = TRUE;
+ DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
+ break;
/* Anything else is a disaster. */
default:
- msg = string_sprintf("malformed data (%d) read from pipe for transport "
- "process %d for transport %s", ptr[-1], pid,
- addr->transport->driver_name);
- done = TRUE;
- break;
+ msg = string_sprintf("malformed data (%d) read from pipe for transport "
+ "process %d for transport %s", ptr[-1], pid,
+ addr->transport->driver_name);
+ done = TRUE;
+ break;
}
}
p->done = done;
/* If the process hadn't finished, and we haven't seen the end of the data
-or suffered a disaster, update the rest of the state, and return FALSE to
+or if we suffered a disaster, update the rest of the state, and return FALSE to
indicate "not finished". */
if (!eop && !done)
addr->transport_return = DEFER;
addr->special_action = SPECIAL_FREEZE;
addr->message = msg;
+ log_write(0, LOG_MAIN|LOG_PANIC, "Delivery status for %s: %s\n", addr->address, addr->message);
}
/* Return TRUE to indicate we have got all we need from this process, even
addr->transport_return = DEFER;
}
(void)post_process_one(addr, addr->transport_return, logflags,
- DTYPE_TRANSPORT, addr->special_action);
+ EXIM_DTYPE_TRANSPORT, addr->special_action);
}
/* Next address */
maxpipe = 0;
FD_ZERO(&select_pipes);
for (poffset = 0; poffset < remote_max_parallel; poffset++)
- {
if (parlist[poffset].pid != 0)
{
int fd = parlist[poffset].fd;
FD_SET(fd, &select_pipes);
if (fd > maxpipe) maxpipe = fd;
}
- }
/* Stick in a 60-second timeout, just in case. */
{
readycount--;
if (par_read_pipe(poffset, FALSE)) /* Finished with this pipe */
- {
for (;;) /* Loop for signals */
{
pid_t endedpid = waitpid(pid, &status, 0);
"%d (errno = %d) from waitpid() for process %d",
(int)endedpid, errno, (int)pid);
}
- }
}
}
}
}
-
-
-
static void
-rmt_dlv_checked_write(int fd, char id, char subid, void * buf, int size)
+rmt_dlv_checked_write(int fd, char id, char subid, void * buf, ssize_t size)
{
-uschar writebuffer[PIPE_HEADER_SIZE + BIG_BUFFER_SIZE];
-int header_length;
-int ret;
+uschar pipe_header[PIPE_HEADER_SIZE+1];
+size_t total_len = PIPE_HEADER_SIZE + size;
+
+struct iovec iov[2] = {
+ { pipe_header, PIPE_HEADER_SIZE }, /* indication about the data to expect */
+ { buf, size } /* *the* data */
+};
+
+ssize_t ret;
/* we assume that size can't get larger then BIG_BUFFER_SIZE which currently is set to 16k */
/* complain to log if someone tries with buffer sizes we can't handle*/
-if (size > 99999)
+if (size > BIG_BUFFER_SIZE-1)
{
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "Failed writing transport result to pipe: can't handle buffers > 99999 bytes. truncating!\n");
- size = 99999;
+ "Failed writing transport result to pipe: can't handle buffers > %d bytes. truncating!\n",
+ BIG_BUFFER_SIZE-1);
+ size = BIG_BUFFER_SIZE;
}
-/* to keep the write() atomic we build header in writebuffer and copy buf behind */
-/* two write() calls would increase the complexity of reading from pipe */
+/* Should we check that we do not write more than PIPE_BUF? What would
+that help? */
/* convert size to human readable string prepended by id and subid */
-header_length = snprintf(CS writebuffer, PIPE_HEADER_SIZE+1, "%c%c%05d", id, subid, size);
-if (header_length != PIPE_HEADER_SIZE)
- {
+if (PIPE_HEADER_SIZE != snprintf(CS pipe_header, PIPE_HEADER_SIZE+1, "%c%c%05ld",
+ id, subid, (long)size))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "header snprintf failed\n");
- writebuffer[0] = '\0';
- }
-
-DEBUG(D_deliver) debug_printf("header write id:%c,subid:%c,size:%d,final:%s\n",
- id, subid, size, writebuffer);
-if (buf && size > 0)
- memcpy(writebuffer + PIPE_HEADER_SIZE, buf, size);
+DEBUG(D_deliver) debug_printf("header write id:%c,subid:%c,size:%ld,final:%s\n",
+ id, subid, (long)size, pipe_header);
-size += PIPE_HEADER_SIZE;
-if ((ret = write(fd, writebuffer, size)) != size)
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed writing transport result to pipe: %s\n",
- ret == -1 ? strerror(errno) : "short write");
+if ((ret = writev(fd, iov, 2)) != total_len)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "Failed writing transport result to pipe (%ld of %ld bytes): %s",
+ (long)ret, (long)total_len, ret == -1 ? strerror(errno) : "short write");
}
/*************************************************
if (!(tp = addr->transport))
{
- disable_logging = FALSE; /* Jic */
+ f.disable_logging = FALSE; /* Jic */
panicmsg = US"No transport set by router";
goto panic_continue;
}
uschar *new_return_path = expand_string(tp->return_path);
if (new_return_path)
return_path = new_return_path;
- else if (!expand_string_forcedfail)
+ else if (!f.expand_string_forcedfail)
{
panicmsg = string_sprintf("Failed to expand return path \"%s\": %s",
tp->return_path, expand_string_message);
if (tp->setup)
(void)((tp->setup)(addr->transport, addr, NULL, uid, gid, NULL));
+ /* If we have a connection still open from a verify stage (lazy-close)
+ treat it as if it is a continued connection (apart from the counter used
+ for the log line mark). */
+
+ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
+ {
+ DEBUG(D_deliver)
+ debug_printf("lazy-callout-close: have conn still open from verification\n");
+ continue_transport = cutthrough.transport;
+ continue_hostname = string_copy(cutthrough.host.name);
+ continue_host_address = string_copy(cutthrough.host.address);
+ continue_sequence = 1;
+ sending_ip_address = cutthrough.snd_ip;
+ sending_port = cutthrough.snd_port;
+ smtp_peer_options = cutthrough.peer_options;
+ }
+
/* If this is a run to continue delivery down an already-established
channel, check that this set of addresses matches the transport and
the channel. If it does not, defer the addresses. If a host list exists,
we must check that the continue host is on the list. Otherwise, the
host is set in the transport. */
- continue_more = FALSE; /* In case got set for the last lot */
+ f.continue_more = FALSE; /* In case got set for the last lot */
if (continue_transport)
{
BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
- if (ok && addr->host_list)
+
+ /* If the transport is about to override the host list do not check
+ it here but take the cost of running the transport process to discover
+ if the continued_hostname connection is suitable. This is a layering
+ violation which is unfortunate as it requires we haul in the smtp
+ include file. */
+
+ if (ok)
{
- host_item *h;
- ok = FALSE;
- for (h = addr->host_list; h; h = h->next)
- if (Ustrcmp(h->name, continue_hostname) == 0)
-/*XXX should also check port here */
- { ok = TRUE; break; }
+ smtp_transport_options_block * ob;
+
+ if ( !( Ustrcmp(tp->info->driver_name, "smtp") == 0
+ && (ob = (smtp_transport_options_block *)tp->options_block)
+ && ob->hosts_override && ob->hosts
+ )
+ && addr->host_list
+ )
+ {
+ host_item * h;
+ ok = FALSE;
+ for (h = addr->host_list; h; h = h->next)
+ if (Ustrcmp(h->name, continue_hostname) == 0)
+ /*XXX should also check port here */
+ { ok = TRUE; break; }
+ }
}
/* Addresses not suitable; defer or queue for fallback hosts (which
if (!ok)
{
- DEBUG(D_deliver) debug_printf("not suitable for continue_transport\n");
+ DEBUG(D_deliver) debug_printf("not suitable for continue_transport (%s)\n",
+ Ustrcmp(continue_transport, tp->name) != 0
+ ? string_sprintf("tpt %s vs %s", continue_transport, tp->name)
+ : string_sprintf("no host matching %s", continue_hostname));
if (serialize_key) enq_end(serialize_key);
if (addr->fallback_hosts && !fallback)
/* Set a flag indicating whether there are further addresses that list
the continued host. This tells the transport to leave the channel open,
- but not to pass it to another delivery process. */
+ but not to pass it to another delivery process. We'd like to do that
+ for non-continue_transport cases too but the knowlege of which host is
+ connected to is too hard to manage. Perhaps we need a finer-grain
+ interface to the transport. */
- for (next = addr_remote; next; next = next->next)
+ for (next = addr_remote; next && !f.continue_more; next = next->next)
{
host_item *h;
for (h = next->host_list; h; h = h->next)
if (Ustrcmp(h->name, continue_hostname) == 0)
- { continue_more = TRUE; break; }
+ { f.continue_more = TRUE; break; }
}
}
that it can use either of them, though it prefers O_NONBLOCK, which
distinguishes between EOF and no-more-data. */
+/* The data appears in a timely manner and we already did a select on
+all pipes, so I do not see a reason to use non-blocking IO here
+
#ifdef O_NONBLOCK
(void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
#else
(void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY);
#endif
+*/
/* If the maximum number of subprocesses already exist, wait for a process
to finish. If we ran out of file descriptors, parmax will have been reduced
transport_name = tp->name;
/* There are weird circumstances in which logging is disabled */
- disable_logging = tp->disable_logging;
+ f.disable_logging = tp->disable_logging;
/* Show pids on debug output if parallelism possible */
predictable settings for each delivery process, so do something explicit
here rather they rely on the fixed reset in the random number function. */
- random_seed = running_in_test_harness? 42 + 2*delivery_count : 0;
+ random_seed = f.running_in_test_harness ? 42 + 2*delivery_count : 0;
/* Set close-on-exec on the pipe so that it doesn't get passed on to
a new process that may be forked to do another delivery down the same
/* The certificate verification status goes into the flags */
if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tls_out.dane_verified) setflag(addr, af_dane_verified);
#endif
if (!addr->peerdn)
*ptr++ = 0;
else
- {
- ptr += sprintf(CS ptr, "%.512s", addr->peerdn);
- ptr++;
- }
+ ptr += sprintf(CS ptr, "%.512s", addr->peerdn) + 1;
rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer);
}
+ else if (continue_proxy_cipher)
+ {
+ ptr = big_buffer + sprintf(CS big_buffer, "%.128s", continue_proxy_cipher) + 1;
+ *ptr++ = 0;
+ rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer);
+ }
+
if (addr->peercert)
{
ptr = big_buffer;
}
#ifndef DISABLE_PRDR
- if (addr->flags & af_prdr_used)
+ if (testflag(addr, af_prdr_used))
rmt_dlv_checked_write(fd, 'P', '0', NULL, 0);
#endif
- if (addr->flags & af_chunking_used)
+ if (testflag(addr, af_pipelining))
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (testflag(addr, af_early_pipe))
+ rmt_dlv_checked_write(fd, 'L', '2', NULL, 0);
+ else
+#endif
+ rmt_dlv_checked_write(fd, 'L', '1', NULL, 0);
+
+ if (testflag(addr, af_chunking_used))
rmt_dlv_checked_write(fd, 'K', '0', NULL, 0);
+ if (testflag(addr, af_tcp_fastopen_conn))
+ rmt_dlv_checked_write(fd, 'T',
+ testflag(addr, af_tcp_fastopen) ? testflag(addr, af_tcp_fastopen_data)
+ ? '2' : '1' : '0',
+ NULL, 0);
+
memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
- DEBUG(D_deliver) debug_printf("DSN write: addr->dsn_aware = %d\n", addr->dsn_aware);
/* Retry information: for most success cases this will be null. */
{
sprintf(CS big_buffer, "%c%.500s", r->flags, r->key);
ptr = big_buffer + Ustrlen(big_buffer+2) + 3;
- memcpy(ptr, &(r->basic_errno), sizeof(r->basic_errno));
+ memcpy(ptr, &r->basic_errno, sizeof(r->basic_errno));
ptr += sizeof(r->basic_errno);
- memcpy(ptr, &(r->more_errno), sizeof(r->more_errno));
+ memcpy(ptr, &r->more_errno, sizeof(r->more_errno));
ptr += sizeof(r->more_errno);
if (!r->message) *ptr++ = 0; else
{
sprintf(CS big_buffer, "%c%c", addr->transport_return, addr->special_action);
ptr = big_buffer + 2;
- memcpy(ptr, &(addr->basic_errno), sizeof(addr->basic_errno));
+ memcpy(ptr, &addr->basic_errno, sizeof(addr->basic_errno));
ptr += sizeof(addr->basic_errno);
- memcpy(ptr, &(addr->more_errno), sizeof(addr->more_errno));
+ memcpy(ptr, &addr->more_errno, sizeof(addr->more_errno));
ptr += sizeof(addr->more_errno);
- memcpy(ptr, &(addr->flags), sizeof(addr->flags));
+ memcpy(ptr, &addr->delivery_usec, sizeof(addr->delivery_usec));
+ ptr += sizeof(addr->delivery_usec);
+ memcpy(ptr, &addr->flags, sizeof(addr->flags));
ptr += sizeof(addr->flags);
if (!addr->message) *ptr++ = 0; else
{
ptr += sprintf(CS ptr, "%.256s", addr->host_used->name) + 1;
ptr += sprintf(CS ptr, "%.64s", addr->host_used->address) + 1;
- memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port));
+ memcpy(ptr, &addr->host_used->port, sizeof(addr->host_used->port));
ptr += sizeof(addr->host_used->port);
/* DNS lookup status */
(void)close(pfd[pipe_write]);
+ /* If we have a connection still open from a verify stage (lazy-close)
+ release its TLS library context (if any) as responsibility was passed to
+ the delivery child process. */
+
+ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
+ {
+#ifdef SUPPORT_TLS
+ if (cutthrough.is_tls)
+ tls_close(cutthrough.cctx.tls_ctx, TLS_NO_SHUTDOWN);
+#endif
+ (void) close(cutthrough.cctx.sock);
+ release_cutthrough_connection(US"passed to transport proc");
+ }
+
/* Fork failed; defer with error message */
- if (pid < 0)
+ if (pid == -1)
{
(void)close(pfd[pipe_read]);
panicmsg = string_sprintf("fork failed for remote delivery to %s: %s",
newly created process get going before we create another process. This should
ensure repeatability in the tests. We only need to wait a tad. */
- else if (running_in_test_harness) millisleep(500);
+ else if (f.running_in_test_harness) millisleep(500);
continue;
static uschar *
next_emf(FILE *f, uschar *which)
{
-int size = 256;
-int ptr = 0;
-uschar *para, *yield;
+uschar *yield;
+gstring * para;
uschar buffer[256];
if (!f) return NULL;
if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0)
return NULL;
-para = store_get(size);
+para = string_get(256);
for (;;)
{
- para = string_cat(para, &size, &ptr, buffer);
+ para = string_cat(para, buffer);
if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0)
break;
}
-para[ptr] = 0;
-
-if ((yield = expand_string(para)))
+if ((yield = expand_string(string_from_gstring(para))))
return yield;
log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand string from "
print_address_error(address_item *addr, FILE *f, uschar *t)
{
int count = Ustrlen(t);
-uschar *s = testflag(addr, af_pass_message)? addr->message : NULL;
+uschar *s = testflag(addr, af_pass_message) ? addr->message : NULL;
if (!s && !(s = addr->user_message))
return;
it is obtained from a command line (the -M or -q options), and otherwise it is
known to be a valid message id. */
-Ustrcpy(message_id, id);
-deliver_force = forced;
+if (id != message_id)
+ Ustrcpy(message_id, id);
+f.deliver_force = forced;
return_count = 0;
message_size = 0;
if (rc != spool_read_hdrerror)
{
- received_time = 0;
+ received_time.tv_sec = received_time.tv_usec = 0;
+ /*XXX subsec precision?*/
for (i = 0; i < 6; i++)
- received_time = received_time * BASE_62 + tab62[id[i] - '0'];
+ received_time.tv_sec = received_time.tv_sec * BASE_62 + tab62[id[i] - '0'];
}
/* If we've had this malformed message too long, sling it. */
- if (now - received_time > keep_malformed)
+ if (now - received_time.tv_sec > keep_malformed)
{
Uunlink(spool_fname(US"msglog", message_subdir, id, US""));
Uunlink(spool_fname(US"input", message_subdir, id, US"-D"));
can happen, but in the default situation, unless forced, no delivery is
attempted. */
-if (deliver_freeze)
+if (f.deliver_freeze)
{
#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
/* Moving to another directory removes the message from Exim's view. Other
|| auto_thaw <= 0
|| now <= deliver_frozen_at + auto_thaw
)
- && ( !forced || !deliver_force_thaw
- || !admin_user || continue_hostname
+ && ( !forced || !f.deliver_force_thaw
+ || !f.admin_user || continue_hostname
) )
{
(void)close(deliver_datafile);
if (forced)
{
- deliver_manual_thaw = TRUE;
+ f.deliver_manual_thaw = TRUE;
log_write(0, LOG_MAIN, "Unfrozen by forced delivery");
}
else log_write(0, LOG_MAIN, "Unfrozen by auto-thaw");
/* We get here if any of the rules for unfreezing have triggered. */
- deliver_freeze = FALSE;
+ f.deliver_freeze = FALSE;
update_spool = TRUE;
}
ugid.uid_set = ugid.gid_set = TRUE;
}
else
- {
ugid.uid_set = ugid.gid_set = FALSE;
- }
return_path = sender_address;
- enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
- system_filtering = TRUE;
+ f.enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
+ f.system_filtering = TRUE;
/* Any error in the filter file causes a delivery to be abandoned. */
/* Reset things. If the filter message is an empty string, which can happen
for a filter "fail" or "freeze" command with no text, reset it to NULL. */
- system_filtering = FALSE;
- enable_dollar_recipients = FALSE;
+ f.system_filtering = FALSE;
+ f.enable_dollar_recipients = FALSE;
if (filter_message && filter_message[0] == 0) filter_message = NULL;
/* Save the values of the system filter variables so that user filters
unset "delivered", which is forced by the "freeze" command to make -bF
work properly. */
- else if (rc == FF_FREEZE && !deliver_manual_thaw)
+ else if (rc == FF_FREEZE && !f.deliver_manual_thaw)
{
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
process_recipients = RECIP_DEFER;
frozen_info = string_sprintf(" by the system filter%s%s",
uschar *type;
p->uid = uid;
p->gid = gid;
- setflag(p, af_uid_set |
- af_gid_set |
- af_allow_file |
- af_allow_pipe |
- af_allow_reply);
+ setflag(p, af_uid_set);
+ setflag(p, af_gid_set);
+ setflag(p, af_allow_file);
+ setflag(p, af_allow_pipe);
+ setflag(p, af_allow_reply);
/* Find the name of the system filter's appropriate pfr transport */
p = p->next;
if (!addr_last) addr_new = p; else addr_last->next = p;
badp->local_part = badp->address; /* Needed for log line */
- post_process_one(badp, DEFER, LOG_MAIN|LOG_PANIC, DTYPE_ROUTER, 0);
+ post_process_one(badp, DEFER, LOG_MAIN|LOG_PANIC, EXIM_DTYPE_ROUTER, 0);
continue;
}
} /* End of pfr handling */
complications for local addresses. */
if (process_recipients != RECIP_IGNORE)
- {
for (i = 0; i < recipients_count; i++)
- {
if (!tree_search(tree_nonrecipients, recipients_list[i].address))
{
recipient_item *r = recipients_list + i;
new->dsn_flags = r->dsn_flags & rf_dsnflags;
new->dsn_orcpt = r->orcpt;
DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s flags: %d\n",
- new->dsn_orcpt, new->dsn_flags);
+ new->dsn_orcpt ? new->dsn_orcpt : US"", new->dsn_flags);
switch (process_recipients)
{
/* RECIP_DEFER is set when a system filter freezes a message. */
case RECIP_DEFER:
- new->next = addr_defer;
- addr_defer = new;
- break;
+ new->next = addr_defer;
+ addr_defer = new;
+ break;
/* RECIP_FAIL_FILTER is set when a system filter has obeyed a "fail"
command. */
case RECIP_FAIL_FILTER:
- new->message =
- filter_message ? filter_message : US"delivery cancelled";
- setflag(new, af_pass_message);
- goto RECIP_QUEUE_FAILED; /* below */
+ new->message =
+ filter_message ? filter_message : US"delivery cancelled";
+ setflag(new, af_pass_message);
+ goto RECIP_QUEUE_FAILED; /* below */
/* RECIP_FAIL_TIMEOUT is set when a message is frozen, but is older
been logged. */
case RECIP_FAIL_TIMEOUT:
- new->message = US"delivery cancelled; message timed out";
- goto RECIP_QUEUE_FAILED; /* below */
+ new->message = US"delivery cancelled; message timed out";
+ goto RECIP_QUEUE_FAILED; /* below */
/* RECIP_FAIL is set when -Mg has been used. */
case RECIP_FAIL:
- new->message = US"delivery cancelled by administrator";
- /* Fall through */
+ new->message = US"delivery cancelled by administrator";
+ /* Fall through */
/* Common code for the failure cases above. If this is not a bounce
message, put the address on the failed list so that it is used to
The incident has already been logged. */
RECIP_QUEUE_FAILED:
- if (sender_address[0] != 0)
- {
- new->next = addr_failed;
- addr_failed = new;
- }
+ if (sender_address[0])
+ {
+ new->next = addr_failed;
+ addr_failed = new;
+ }
break;
is a bounce message, it will get frozen. */
case RECIP_FAIL_LOOP:
- new->message = US"Too many \"Received\" headers - suspected mail loop";
- post_process_one(new, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
- break;
+ new->message = US"Too many \"Received\" headers - suspected mail loop";
+ post_process_one(new, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+ break;
/* Value should be RECIP_ACCEPT; take this as the safe default. */
default:
- if (!addr_new) addr_new = new; else addr_last->next = new;
- addr_last = new;
- break;
+ if (!addr_new) addr_new = new; else addr_last->next = new;
+ addr_last = new;
+ break;
}
#ifndef DISABLE_EVENT
{
uschar * save_local = deliver_localpart;
const uschar * save_domain = deliver_domain;
+ uschar * addr = new->address, * errmsg = NULL;
+ int start, end, dom;
- deliver_localpart = expand_string(
- string_sprintf("${local_part:%s}", new->address));
- deliver_domain = expand_string(
- string_sprintf("${domain:%s}", new->address));
+ if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "failed to parse address '%.100s': %s\n", addr, errmsg);
+ else
+ {
+ deliver_localpart =
+ string_copyn(addr+start, dom ? (dom-1) - start : end - start);
+ deliver_domain = dom ? CUS string_copyn(addr+dom, end - dom) : CUS"";
- (void) event_raise(event_action,
- US"msg:fail:internal", new->message);
+ event_raise(event_action, US"msg:fail:internal", new->message);
- deliver_localpart = save_local;
- deliver_domain = save_domain;
+ deliver_localpart = save_local;
+ deliver_domain = save_domain;
+ }
}
#endif
}
- }
- }
DEBUG(D_deliver)
{
. If new addresses have been generated by the routers, da capo.
*/
-header_rewritten = FALSE; /* No headers rewritten yet */
+f.header_rewritten = FALSE; /* No headers rewritten yet */
while (addr_new) /* Loop until all addresses dealt with */
{
address_item *addr, *parent;
not exist. In both cases, dbm_file is NULL. */
if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
- {
DEBUG(D_deliver|D_retry|D_route|D_hints_lookup)
debug_printf("no retry data available\n");
- }
/* Scan the current batch of new addresses, to handle pipes, files and
autoreplies, and determine which others are ready for routing. */
addr->local_part = addr->address;
addr->message =
US"filter autoreply generated syntactically invalid recipient";
- setflag(addr, af_ignore_error);
- (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+ addr->prop.ignore_error = TRUE;
+ (void) post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue; /* with the next new address */
}
{
addr->basic_errno = ERRNO_FORBIDFILE;
addr->message = US"delivery to file forbidden";
- (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue; /* with the next new address */
}
}
{
addr->basic_errno = ERRNO_FORBIDPIPE;
addr->message = US"delivery to pipe forbidden";
- (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue; /* with the next new address */
}
}
{
addr->basic_errno = ERRNO_FORBIDREPLY;
addr->message = US"autoreply forbidden";
- (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue; /* with the next new address */
}
if (addr->basic_errno == ERRNO_BADTRANSPORT)
{
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue;
}
{
uschar *save = addr->transport->name;
addr->transport->name = US"**bypassed**";
- (void)post_process_one(addr, OK, LOG_MAIN, DTYPE_TRANSPORT, '=');
+ (void)post_process_one(addr, OK, LOG_MAIN, EXIM_DTYPE_TRANSPORT, '=');
addr->transport->name = save;
continue; /* with the next new address */
}
{
addr->message = US"cannot check percent_hack_domains";
addr->basic_errno = ERRNO_LISTDEFER;
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_NONE, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0);
continue;
}
addr->message = US"domain is held";
addr->basic_errno = ERRNO_HELD;
}
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_NONE, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0);
continue;
}
if ( domain_retry_record
&& now - domain_retry_record->time_stamp > retry_data_expire
)
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf("domain retry record present but expired\n");
domain_retry_record = NULL; /* Ignore if too old */
+ }
address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
if ( address_retry_record
&& now - address_retry_record->time_stamp > retry_data_expire
)
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf("address retry record present but expired\n");
address_retry_record = NULL; /* Ignore if too old */
+ }
if (!address_retry_record)
{
address_retry_record = dbfn_read(dbm_file, altkey);
if ( address_retry_record
&& now - address_retry_record->time_stamp > retry_data_expire)
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf("address<sender> retry record present but expired\n");
address_retry_record = NULL; /* Ignore if too old */
+ }
}
}
else
DEBUG(D_deliver|D_retry)
{
if (!domain_retry_record)
- debug_printf("no domain retry record\n");
+ debug_printf("no domain retry record\n");
+ else
+ debug_printf("have domain retry record; next_try = now%+d\n",
+ f.running_in_test_harness ? 0 :
+ (int)(domain_retry_record->next_try - now));
+
if (!address_retry_record)
- debug_printf("no address retry record\n");
+ debug_printf("no address retry record\n");
+ else
+ debug_printf("have address retry record; next_try = now%+d\n",
+ f.running_in_test_harness ? 0 :
+ (int)(address_retry_record->next_try - now));
}
/* If we are sending a message down an existing SMTP connection, we must
{
addr->message = US"reusing SMTP connection skips previous routing defer";
addr->basic_errno = ERRNO_RRETRY;
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+
+ addr->message = domain_retry_record->text;
+ setflag(addr, af_pass_message);
}
/* If we are in a queue run, defer routing unless there is no retry data or
which keep the retry record fresh, which can lead to us perpetually
deferring messages. */
- else if ( ( queue_running && !deliver_force
+ else if ( ( f.queue_running && !f.deliver_force
|| continue_hostname
)
&& ( ( domain_retry_record
{
addr->message = US"retry time not reached";
addr->basic_errno = ERRNO_RRETRY;
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+
+ /* For remote-retry errors (here and just above) that we've not yet
+ hit the rery time, use the error recorded in the retry database
+ as info in the warning message. This lets us send a message even
+ when we're not failing on a fresh attempt. We assume that this
+ info is not sensitive. */
+
+ addr->message = domain_retry_record
+ ? domain_retry_record->text : address_retry_record->text;
+ setflag(addr, af_pass_message);
}
/* The domain is OK for routing. Remember if retry data exists so it
those domains. During queue runs, queue_domains is forced to be unset.
Optimize by skipping this pass through the addresses if nothing is set. */
- if (!deliver_force && queue_domains)
+ if (!f.deliver_force && queue_domains)
{
address_item *okaddr = NULL;
while (addr_route)
if ((rc = match_isinlist(addr->domain, (const uschar **)&queue_domains, 0,
&domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL))
!= OK)
- {
if (rc == DEFER)
{
addr->basic_errno = ERRNO_LISTDEFER;
addr->message = US"queue_domains lookup deferred";
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
}
else
{
addr->next = okaddr;
okaddr = addr;
}
- }
else
{
addr->basic_errno = ERRNO_QUEUE_DOMAIN;
addr->message = US"domain is in queue_domains";
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
}
}
&addr_succeed, v_none)) == DEFER)
retry_add_item(addr,
addr->router->retry_use_local_part
- ? string_sprintf("R:%s@%s", addr->local_part, addr->domain)
+ ? string_sprintf("R:%s@%s", addr->local_part, addr->domain)
: string_sprintf("R:%s", addr->domain),
0);
if (rc != OK)
{
- (void)post_process_one(addr, rc, LOG_MAIN, DTYPE_ROUTER, 0);
+ (void)post_process_one(addr, rc, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
continue; /* route next address */
}
addr2->host_list = addr->host_list;
addr2->fallback_hosts = addr->fallback_hosts;
addr2->prop.errors_address = addr->prop.errors_address;
- copyflag(addr2, addr, af_hide_child | af_local_host_removed);
+ copyflag(addr2, addr, af_hide_child);
+ copyflag(addr2, addr, af_local_host_removed);
DEBUG(D_deliver|D_route)
- {
debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
"routing %s\n"
"Routing for %s copied from %s\n",
addr2->address, addr2->address, addr->address);
- }
}
}
} /* Continue with routing the next address. */
there is only address to be delivered - if it succeeds the spool write need not
happen. */
-if ( header_rewritten
+if ( f.header_rewritten
&& ( addr_local && (addr_local->next || addr_remote)
|| addr_remote && addr_remote->next
) )
{
/* Panic-dies on error */
(void)spool_write_header(message_id, SW_DELIVERING, NULL);
- header_rewritten = FALSE;
+ f.header_rewritten = FALSE;
}
-/* If there are any deliveries to be and we do not already have the journal
+/* If there are any deliveries to do and we do not already have the journal
file, create it. This is used to record successful deliveries as soon as
possible after each delivery is known to be complete. A file opened with
O_APPEND is used so that several processes can run simultaneously.
if (journal_fd < 0)
{
uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
-
+
if ((journal_fd = Uopen(fname,
#ifdef O_CLOEXEC
O_CLOEXEC |
DEBUG(D_deliver|D_transport)
debug_printf(">>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>\n");
do_local_deliveries();
- disable_logging = FALSE;
+ f.disable_logging = FALSE;
}
/* If queue_run_local is set, we do not want to attempt any remote deliveries,
so just queue them all. */
-if (queue_run_local)
+if (f.queue_run_local)
while (addr_remote)
{
address_item *addr = addr_remote;
addr->next = NULL;
addr->basic_errno = ERRNO_LOCAL_ONLY;
addr->message = US"remote deliveries suppressed";
- (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_TRANSPORT, 0);
+ (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0);
}
/* Handle remote deliveries */
if (remote_sort_domains) sort_remote_deliveries();
do_remote_deliveries(TRUE);
}
- disable_logging = FALSE;
+ f.disable_logging = FALSE;
}
DEBUG(D_deliver)
debug_printf(">>>>>>>>>>>>>>>> deliveries are done >>>>>>>>>>>>>>>>\n");
+cancel_cutthrough_connection(TRUE, US"deliveries are done");
/* Root privilege is no longer needed */
updating of the database if the -N flag is set, which is a debugging thing that
prevents actual delivery. */
-else if (!dont_deliver)
+else if (!f.dont_deliver)
retry_update(&addr_defer, &addr_failed, &addr_succeed);
/* Send DSN for successful messages if requested */
"DSN: envid: %s ret: %d\n"
"DSN: Final recipient: %s\n"
"DSN: Remote SMTP server supports DSN: %d\n",
- addr_dsntmp->router->name,
+ addr_dsntmp->router ? addr_dsntmp->router->name : US"(unknown)",
addr_dsntmp->address,
sender_address,
- addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags,
- dsn_envid, dsn_ret,
+ addr_dsntmp->dsn_orcpt ? addr_dsntmp->dsn_orcpt : US"NULL",
+ addr_dsntmp->dsn_flags,
+ dsn_envid ? dsn_envid : US"NULL", dsn_ret,
addr_dsntmp->address,
addr_dsntmp->dsn_aware
);
if ( ( addr_dsntmp->dsn_aware != dsn_support_yes
|| addr_dsntmp->dsn_flags & rf_dsnlasthop
)
- && addr_dsntmp->dsn_flags & rf_dsnflags
&& addr_dsntmp->dsn_flags & rf_notify_success
)
{
}
else /* Creation of child succeeded */
{
- FILE *f = fdopen(fd, "wb");
+ FILE * f = fdopen(fd, "wb");
/* header only as required by RFC. only failure DSN needs to honor RET=FULL */
uschar * bound;
- transport_ctx tctx = {0};
+ transport_ctx tctx = {{0}};
DEBUG(D_deliver)
debug_printf("sending error message to: %s\n", sender_address);
addr_dsntmp = addr_dsntmp->next)
fprintf(f, "<%s> (relayed %s)\n\n",
addr_dsntmp->address,
- (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1
- ? "via non DSN router"
- : addr_dsntmp->dsn_aware == dsn_support_no
- ? "to non-DSN-aware mailer"
- : "via non \"Remote SMTP\" router"
+ addr_dsntmp->dsn_flags & rf_dsnlasthop ? "via non DSN router"
+ : addr_dsntmp->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer"
+ : "via non \"Remote SMTP\" router"
);
fprintf(f, "--%s\n"
addr_dsntmp->host_used->name);
else
fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
- (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP");
+ addr_dsntmp->dsn_flags & rf_dsnlasthop ? "DSN" : "SMTP");
}
fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", bound);
/* Write the original email out */
+ tctx.u.fd = fd;
tctx.options = topt_add_return_path | topt_no_body;
- transport_write_message(fileno(f), &tctx, 0);
+ /*XXX hmm, retval ignored.
+ Could error for any number of reasons, and they are not handled. */
+ transport_write_message(&tctx, 0);
fflush(f);
fprintf(f,"\n--%s--\n", bound);
/* There are weird cases when logging is disabled in the transport. However,
there may not be a transport (address failed by a router). */
- disable_logging = FALSE;
+ f.disable_logging = FALSE;
if (addr_failed->transport)
- disable_logging = addr_failed->transport->disable_logging;
+ f.disable_logging = addr_failed->transport->disable_logging;
DEBUG(D_deliver)
debug_printf("processing failed address %s\n", addr_failed->address);
if (sender_address[0] == 0 && !addr_failed->prop.errors_address)
{
if ( !testflag(addr_failed, af_retry_timedout)
- && !testflag(addr_failed, af_ignore_error))
- {
+ && !addr_failed->prop.ignore_error)
log_write(0, LOG_MAIN|LOG_PANIC, "internal error: bounce message "
"failure is neither frozen nor ignored (it's been ignored)");
- }
- setflag(addr_failed, af_ignore_error);
+
+ addr_failed->prop.ignore_error = TRUE;
}
/* If the first address on the list has af_ignore_error set, just remove
it from the list, throw away any saved message file, log it, and
mark the recipient done. */
- if ( testflag(addr_failed, af_ignore_error)
- || ( addr_failed->dsn_flags & rf_dsnflags
- && (addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure
- ) )
+ if ( addr_failed->prop.ignore_error
+ || addr_failed->dsn_flags & (rf_dsnflags & ~rf_notify_failure)
+ )
{
addr = addr_failed;
addr_failed = addr->next;
if (addr->return_filename) Uunlink(addr->return_filename);
+#ifndef DISABLE_EVENT
+ msg_event_raise(US"msg:fail:delivery", addr);
+#endif
log_write(0, LOG_MAIN, "%s%s%s%s: error ignored",
addr->address,
!addr->parent ? US"" : US" <",
int filecount = 0;
int rcount = 0;
uschar *bcc, *emf_text;
- FILE *f = fdopen(fd, "wb");
- FILE *emf = NULL;
+ FILE * fp = fdopen(fd, "wb");
+ FILE * emf = NULL;
BOOL to_sender = strcmpic(sender_address, bounce_recipient) == 0;
int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) *
DELIVER_IN_BUFFER_SIZE;
if (testflag(addr, af_hide_child)) continue;
if (rcount >= 50)
{
- fprintf(f, "\n");
+ fprintf(fp, "\n");
rcount = 0;
}
- fprintf(f, "%s%s",
+ fprintf(fp, "%s%s",
rcount++ == 0
? "X-Failed-Recipients: "
: ",\n ",
? string_printing(addr->parent->address)
: string_printing(addr->address));
}
- if (rcount > 0) fprintf(f, "\n");
+ if (rcount > 0) fprintf(fp, "\n");
/* Output the standard headers */
if (errors_reply_to)
- fprintf(f, "Reply-To: %s\n", errors_reply_to);
- fprintf(f, "Auto-Submitted: auto-replied\n");
- moan_write_from(f);
- fprintf(f, "To: %s\n", bounce_recipient);
+ fprintf(fp, "Reply-To: %s\n", errors_reply_to);
+ fprintf(fp, "Auto-Submitted: auto-replied\n");
+ moan_write_from(fp);
+ fprintf(fp, "To: %s\n", bounce_recipient);
/* generate boundary string and output MIME-Headers */
bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
- fprintf(f, "Content-Type: multipart/report;"
+ fprintf(fp, "Content-Type: multipart/report;"
" report-type=delivery-status; boundary=%s\n"
"MIME-Version: 1.0\n",
bound);
/* Quietly copy to configured additional addresses if required. */
if ((bcc = moan_check_errorcopy(bounce_recipient)))
- fprintf(f, "Bcc: %s\n", bcc);
+ fprintf(fp, "Bcc: %s\n", bcc);
/* The texts for the message can be read from a template file; if there
isn't one, or if it is too short, built-in texts are used. The first
emf text is a Subject: and any other headers. */
if ((emf_text = next_emf(emf, US"header")))
- fprintf(f, "%s\n", emf_text);
+ fprintf(fp, "%s\n", emf_text);
else
- fprintf(f, "Subject: Mail delivery failed%s\n\n",
+ fprintf(fp, "Subject: Mail delivery failed%s\n\n",
to_sender? ": returning message to sender" : "");
/* output human readable part as text/plain section */
- fprintf(f, "--%s\n"
+ fprintf(fp, "--%s\n"
"Content-type: text/plain; charset=us-ascii\n\n",
bound);
if ((emf_text = next_emf(emf, US"intro")))
- fprintf(f, "%s", CS emf_text);
+ fprintf(fp, "%s", CS emf_text);
else
{
- fprintf(f,
+ fprintf(fp,
/* This message has been reworded several times. It seems to be confusing to
somebody, however it is worded. I have retreated to the original, simple
wording. */
"This message was created automatically by mail delivery software.\n");
if (bounce_message_text)
- fprintf(f, "%s", CS bounce_message_text);
+ fprintf(fp, "%s", CS bounce_message_text);
if (to_sender)
- fprintf(f,
+ fprintf(fp,
"\nA message that you sent could not be delivered to one or more of its\n"
"recipients. This is a permanent error. The following address(es) failed:\n");
else
- fprintf(f,
+ fprintf(fp,
"\nA message sent by\n\n <%s>\n\n"
"could not be delivered to one or more of its recipients. The following\n"
"address(es) failed:\n", sender_address);
}
- fputc('\n', f);
+ fputc('\n', fp);
/* Process the addresses, leaving them on the msgchain if they have a
file name for a return message. (There has already been a check in
paddr = &msgchain;
for (addr = msgchain; addr; addr = *paddr)
{
- if (print_address_information(addr, f, US" ", US"\n ", US""))
- print_address_error(addr, f, US"");
+ if (print_address_information(addr, fp, US" ", US"\n ", US""))
+ print_address_error(addr, fp, US"");
/* End the final line for the address */
- fputc('\n', f);
+ fputc('\n', fp);
/* Leave on msgchain if there's a return file. */
}
}
- fputc('\n', f);
+ fputc('\n', fp);
/* Get the next text, whether we need it or not, so as to be
positioned for the one after. */
address_item *nextaddr;
if (emf_text)
- fprintf(f, "%s", CS emf_text);
+ fprintf(fp, "%s", CS emf_text);
else
- fprintf(f,
+ fprintf(fp,
"The following text was generated during the delivery "
"attempt%s:\n", (filecount > 1)? "s" : "");
/* List all the addresses that relate to this file */
- fputc('\n', f);
+ fputc('\n', fp);
while(addr) /* Insurance */
{
- print_address_information(addr, f, US"------ ", US"\n ",
+ print_address_information(addr, fp, US"------ ", US"\n ",
US" ------\n");
if (addr->return_filename) break;
addr = addr->next;
}
- fputc('\n', f);
+ fputc('\n', fp);
/* Now copy the file */
if (!(fm = Ufopen(addr->return_filename, "rb")))
- fprintf(f, " +++ Exim error... failed to open text file: %s\n",
+ fprintf(fp, " +++ Exim error... failed to open text file: %s\n",
strerror(errno));
else
{
- while ((ch = fgetc(fm)) != EOF) fputc(ch, f);
+ while ((ch = fgetc(fm)) != EOF) fputc(ch, fp);
(void)fclose(fm);
}
Uunlink(addr->return_filename);
addr->next = handled_addr;
handled_addr = topaddr;
}
- fputc('\n', f);
+ fputc('\n', fp);
}
/* output machine readable part */
#ifdef SUPPORT_I18N
if (message_smtputf8)
- fprintf(f, "--%s\n"
+ fprintf(fp, "--%s\n"
"Content-type: message/global-delivery-status\n\n"
"Reporting-MTA: dns; %s\n",
bound, smtp_active_hostname);
else
#endif
- fprintf(f, "--%s\n"
+ fprintf(fp, "--%s\n"
"Content-type: message/delivery-status\n\n"
"Reporting-MTA: dns; %s\n",
bound, smtp_active_hostname);
/* must be decoded from xtext: see RFC 3461:6.3a */
uschar *xdec_envid;
if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
- fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid);
+ fprintf(fp, "Original-Envelope-ID: %s\n", dsn_envid);
else
- fprintf(f, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
+ fprintf(fp, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
}
- fputc('\n', f);
+ fputc('\n', fp);
for (addr = handled_addr; addr; addr = addr->next)
{
host_item * hu;
- fprintf(f, "Action: failed\n"
+ fprintf(fp, "Action: failed\n"
"Final-Recipient: rfc822;%s\n"
"Status: 5.0.0\n",
addr->address);
if ((hu = addr->host_used) && hu->name)
{
- const uschar * s;
- fprintf(f, "Remote-MTA: dns; %s\n", hu->name);
+ fprintf(fp, "Remote-MTA: dns; %s\n", hu->name);
#ifdef EXPERIMENTAL_DSN_INFO
+ {
+ const uschar * s;
if (hu->address)
{
uschar * p = hu->port == 25
? US"" : string_sprintf(":%d", hu->port);
- fprintf(f, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
+ fprintf(fp, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
}
if ((s = addr->smtp_greeting) && *s)
- fprintf(f, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s);
+ fprintf(fp, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s);
if ((s = addr->helo_response) && *s)
- fprintf(f, "X-Remote-MTA-helo-response: X-str; %s\n", s);
+ fprintf(fp, "X-Remote-MTA-helo-response: X-str; %s\n", s);
if ((s = addr->message) && *s)
- fprintf(f, "X-Exim-Diagnostic: X-str; %s\n", s);
+ fprintf(fp, "X-Exim-Diagnostic: X-str; %s\n", s);
+ }
#endif
- print_dsn_diagnostic_code(addr, f);
+ print_dsn_diagnostic_code(addr, fp);
}
- fputc('\n', f);
+ fputc('\n', fp);
}
/* Now copy the message, trying to give an intelligible comment if
bounce_return_size_limit is always honored.
*/
- fprintf(f, "--%s\n", bound);
+ fprintf(fp, "--%s\n", bound);
dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
dsnnotifyhdr = NULL;
if (message_smtputf8)
fputs(topt & topt_no_body ? "Content-type: message/global-headers\n\n"
: "Content-type: message/global\n\n",
- f);
+ fp);
else
#endif
fputs(topt & topt_no_body ? "Content-type: text/rfc822-headers\n\n"
: "Content-type: message/rfc822\n\n",
- f);
+ fp);
- fflush(f);
+ fflush(fp);
transport_filter_argv = NULL; /* Just in case */
return_path = sender_address; /* In case not previously set */
{ /* Dummy transport for headers add */
- transport_ctx tctx = {0};
+ transport_ctx tctx = {{0}};
transport_instance tb = {0};
+ tctx.u.fd = fileno(fp);
tctx.tblock = &tb;
tctx.options = topt;
tb.add_headers = dsnnotifyhdr;
- transport_write_message(fileno(f), &tctx, 0);
+ /*XXX no checking for failure! buggy! */
+ transport_write_message(&tctx, 0);
}
- fflush(f);
+ fflush(fp);
/* we never add the final text. close the file */
if (emf)
(void)fclose(emf);
- fprintf(f, "\n--%s--\n", bound);
+ fprintf(fp, "\n--%s--\n", bound);
/* Close the file, which should send an EOF to the child process
that is receiving the message. Wait for it to finish. */
- (void)fclose(f);
+ (void)fclose(fp);
rc = child_close(pid, 0); /* Waits for child to close, no timeout */
/* In the test harness, let the child do it's thing first. */
- if (running_in_test_harness) millisleep(500);
+ if (f.running_in_test_harness) millisleep(500);
/* If the process failed, there was some disaster in setting up the
error message. Unless the message is very old, ensure that addr_defer
if (rc != 0)
{
uschar *s = US"";
- if (now - received_time < retry_maximum_timeout && !addr_defer)
+ if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer)
{
addr_defer = (address_item *)(+1);
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
/* Panic-dies on error */
(void)spool_write_header(message_id, SW_DELIVERING, NULL);
}
}
-disable_logging = FALSE; /* In case left set */
+f.disable_logging = FALSE; /* In case left set */
/* Come here from the mua_wrapper case if routing goes wrong */
/* Log the end of this message, with queue time if requested. */
if (LOGGING(queue_time_overall))
- log_write(0, LOG_MAIN, "Completed QT=%s",
- readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
+ log_write(0, LOG_MAIN, "Completed QT=%s", string_timesince(&received_time));
else
log_write(0, LOG_MAIN, "Completed");
/* Unset deliver_freeze so that we won't try to move the spool files further down */
- deliver_freeze = FALSE;
+ f.deliver_freeze = FALSE;
#ifndef DISABLE_EVENT
(void) event_raise(event_action, US"msg:complete", NULL);
If all the deferred addresses have an error number that indicates "retry time
not reached", skip sending the warning message, because it won't contain the
reason for the delay. It will get sent at the next real delivery attempt.
+ Exception: for retries caused by a remote peer we use the error message
+ store in the retry DB as the reason.
However, if at least one address has tried, we'd better include all of them in
the message.
{
address_item *addr;
uschar *recipients = US"";
- BOOL delivery_attempted = FALSE;
+ BOOL want_warning_msg = FALSE;
deliver_domain = testflag(addr_defer, af_pfr)
? addr_defer->parent->domain : addr_defer->domain;
{
address_item *otaddr;
- if (addr->basic_errno > ERRNO_RETRY_BASE) delivery_attempted = TRUE;
+ if (addr->basic_errno > ERRNO_WARN_BASE) want_warning_msg = TRUE;
if (deliver_domain)
{
is not sent. Another attempt will be made at the next delivery attempt (if
it also defers). */
- if ( !queue_2stage
- && delivery_attempted
- && ( ((addr_defer->dsn_flags & rf_dsnflags) == 0)
- || (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay
+ if ( !f.queue_2stage
+ && want_warning_msg
+ && ( !(addr_defer->dsn_flags & rf_dsnflags)
+ || addr_defer->dsn_flags & rf_notify_delay
)
&& delay_warning[1] > 0
&& sender_address[0] != 0
{
int count;
int show_time;
- int queue_time = time(NULL) - received_time;
+ int queue_time = time(NULL) - received_time.tv_sec;
/* When running in the test harness, there's an option that allows us to
fudge this time so as to get repeatability of the tests. Take the first
time off the list. In queue runs, the list pointer gets updated in the
calling process. */
- if (running_in_test_harness && fudged_queue_times[0] != 0)
+ if (f.running_in_test_harness && fudged_queue_times[0] != 0)
{
int qt = readconf_readtime(fudged_queue_times, '/', FALSE);
if (qt >= 0)
DEBUG(D_deliver)
{
- debug_printf("time on queue = %s\n", readconf_printtime(queue_time));
+ debug_printf("time on queue = %s id %s addr %s\n", readconf_printtime(queue_time), message_id, addr_defer->address);
debug_printf("warning counts: required %d done %d\n", count,
warning_count);
}
FILE *wmf = NULL;
FILE *f = fdopen(fd, "wb");
uschar * bound;
- transport_ctx tctx = {0};
+ transport_ctx tctx = {{0}};
if (warn_message_file)
if (!(wmf = Ufopen(warn_message_file, "rb")))
fflush(f);
/* header only as required by RFC. only failure DSN needs to honor RET=FULL */
+ tctx.u.fd = fileno(f);
tctx.options = topt_add_return_path | topt_no_body;
transport_filter_argv = NULL; /* Just in case */
return_path = sender_address; /* In case not previously set */
/* Write the original email out */
- transport_write_message(fileno(f), &tctx, 0);
+ /*XXX no checking for failure! buggy! */
+ transport_write_message(&tctx, 0);
fflush(f);
fprintf(f,"\n--%s--\n", bound);
/* If this was a first delivery attempt, unset the first time flag, and
ensure that the spool gets updated. */
- if (deliver_firsttime)
+ if (f.deliver_firsttime)
{
- deliver_firsttime = FALSE;
+ f.deliver_firsttime = FALSE;
update_spool = TRUE;
}
For the "tell" message, we turn \n back into newline. Also, insert a newline
near the start instead of the ": " string. */
- if (deliver_freeze)
+ if (f.deliver_freeze)
{
- if (freeze_tell && freeze_tell[0] != 0 && !local_error_message)
+ if (freeze_tell && freeze_tell[0] != 0 && !f.local_error_message)
{
uschar *s = string_copy(frozen_info);
uschar *ss = Ustrstr(s, " by the system filter: ");
DEBUG(D_deliver)
debug_printf("delivery deferred: update_spool=%d header_rewritten=%d\n",
- update_spool, header_rewritten);
+ update_spool, f.header_rewritten);
- if (update_spool || header_rewritten)
+ if (update_spool || f.header_rewritten)
/* Panic-dies on error */
(void)spool_write_header(message_id, SW_DELIVERING, NULL);
}
/* Move the message off the spool if requested */
#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
- if (deliver_freeze && move_frozen_messages)
+ if (f.deliver_freeze && move_frozen_messages)
(void)spool_move_message(id, message_subdir, US"", US"F");
#endif
}
void
deliver_init(void)
{
+#ifdef EXIM_TFO_PROBE
+tfo_probe();
+#else
+f.tcp_fastopen_ok = TRUE;
+#endif
+
+
if (!regex_PIPELINING) regex_PIPELINING =
regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE);
regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
if (!regex_AUTH) regex_AUTH =
- regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
- FALSE, TRUE);
+ regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
#ifdef SUPPORT_TLS
if (!regex_STARTTLS) regex_STARTTLS =
regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+if (!regex_REQUIRETLS) regex_REQUIRETLS =
+ regex_must_compile(US"\\n250[\\s\\-]REQUIRETLS(\\s|\\n|$)", FALSE, TRUE);
+# endif
#endif
if (!regex_CHUNKING) regex_CHUNKING =
if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (!regex_EARLY_PIPE) regex_EARLY_PIPE =
+ regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE);
+#endif
}
int rc;
uschar * new_sender_address,
* save_sender_address;
-BOOL save_qr = queue_running;
+BOOL save_qr = f.queue_running;
uschar * spoolname;
/* make spool_open_datafile non-noisy on fail */
-queue_running = TRUE;
+f.queue_running = TRUE;
/* Side effect: message_subdir is set for the (possibly split) spool directory */
deliver_datafile = spool_open_datafile(id);
-queue_running = save_qr;
+f.queue_running = save_qr;
if (deliver_datafile < 0)
return NULL;
return new_sender_address;
}
+
+
+void
+delivery_re_exec(int exec_type)
+{
+uschar * where;
+
+if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
+ {
+ int channel_fd = cutthrough.cctx.sock;
+
+ smtp_peer_options = cutthrough.peer_options;
+ continue_sequence = 0;
+
+#ifdef SUPPORT_TLS
+ if (cutthrough.is_tls)
+ {
+ int pfd[2], pid;
+
+ smtp_peer_options |= OPTION_TLS;
+ sending_ip_address = cutthrough.snd_ip;
+ sending_port = cutthrough.snd_port;
+
+ where = US"socketpair";
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0)
+ goto fail;
+
+ where = US"fork";
+ if ((pid = fork()) < 0)
+ goto fail;
+
+ else if (pid == 0) /* child: fork again to totally disconnect */
+ {
+ if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
+ /* does not return */
+ smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size,
+ pfd, 5*60);
+ }
+
+ DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
+ close(pfd[0]);
+ waitpid(pid, NULL, 0);
+ (void) close(channel_fd); /* release the client socket */
+ channel_fd = pfd[1];
+ }
+#endif
+
+ transport_do_pass_socket(cutthrough.transport, cutthrough.host.name,
+ cutthrough.host.address, message_id, channel_fd);
+ }
+else
+ {
+ cancel_cutthrough_connection(TRUE, US"non-continued delivery");
+ (void) child_exec_exim(exec_type, FALSE, NULL, FALSE, 2, US"-Mc", message_id);
+ }
+return; /* compiler quietening; control does not reach here. */
+
+#ifdef SUPPORT_TLS
+fail:
+ log_write(0,
+ LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
+ "delivery re-exec %s failed: %s", where, strerror(errno));
+
+ /* Get here if exec_type == CEE_EXEC_EXIT.
+ Note: this must be _exit(), not exit(). */
+
+ _exit(EX_EXECFAILED);
+#endif
+}
+
/* vi: aw ai sw=2
*/
/* End of deliver.c */
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) The Exim Maintainers 2010 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "exim.h"
int mode, BOOL panic)
{
BOOL use_chown = parent == spool_directory && geteuid() == root_uid;
-uschar *p;
-const uschar *slash;
-int c = 1;
+uschar * p;
+uschar c = 1;
struct stat statbuf;
-uschar buffer[256];
+uschar * path;
-if (parent == NULL)
+if (parent)
{
- p = buffer + 1;
- slash = parent = CUS"";
+ path = string_sprintf("%s%s%s", parent, US"/", name);
+ p = path + Ustrlen(parent);
}
else
{
- p = buffer + Ustrlen(parent);
- slash = US"/";
+ path = string_copy(name);
+ p = path + 1;
}
-if (!string_format(buffer, sizeof(buffer), "%s%s%s", parent, slash, name))
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "name too long in directory_make");
+/* Walk the path creating any missing directories */
-while (c != 0 && *p != 0)
+while (c && *p)
{
- while (*p != 0 && *p != '/') p++;
+ while (*p && *p != '/') p++;
c = *p;
- *p = 0;
- if (Ustat(buffer, &statbuf) != 0)
+ *p = '\0';
+ if (Ustat(path, &statbuf) != 0)
{
- if (mkdir(CS buffer, mode) < 0 && errno != EEXIST)
- {
- if (!panic) return FALSE;
- log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "Failed to create directory \"%s\": %s\n", buffer, strerror(errno));
- }
+ if (mkdir(CS path, mode) < 0 && errno != EEXIST)
+ { p = US"create"; goto bad; }
/* Set the ownership if necessary. */
- if (use_chown && Uchown(buffer, exim_uid, exim_gid))
- {
- if (!panic) return FALSE;
- log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "Failed to set owner on directory \"%s\": %s\n", buffer, strerror(errno));
- }
+ if (use_chown && Uchown(path, exim_uid, exim_gid))
+ { p = US"set owner on"; goto bad; }
/* It appears that any mode bits greater than 0777 are ignored by
mkdir(), at least on some operating systems. Therefore, if the mode
contains any such bits, do an explicit mode setting. */
- if ((mode & 0777000) != 0) (void)Uchmod(buffer, mode);
+ if (mode & 0777000) (void) Uchmod(path, mode);
}
*p++ = c;
}
return TRUE;
+
+bad:
+ if (panic) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "Failed to %s directory \"%s\": %s\n", p, path, strerror(errno));
+ return FALSE;
}
/* End of directory.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge, 1995 - 2017 */
+/* Copyright (c) University of Cambridge, 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for DKIM support. Other DKIM relevant code is in
#ifndef DISABLE_DKIM
-#include "pdkim/pdkim.h"
+# include "pdkim/pdkim.h"
+
+# ifdef MACRO_PREDEF
+# include "macro_predef.h"
+
+void
+params_dkim(void)
+{
+builtin_macro_create_var(US"_DKIM_SIGN_HEADERS", US PDKIM_DEFAULT_SIGN_HEADERS);
+}
+# else /*!MACRO_PREDEF*/
+
+
+
+pdkim_ctx dkim_sign_ctx;
int dkim_verify_oldpool;
pdkim_ctx *dkim_verify_ctx = NULL;
-pdkim_signature *dkim_signatures = NULL;
pdkim_signature *dkim_cur_sig = NULL;
-static BOOL dkim_collect_error = FALSE;
+static const uschar * dkim_collect_error = NULL;
-static int
-dkim_exim_query_dns_txt(char *name, char *answer)
+#define DKIM_MAX_SIGNATURES 20
+
+
+
+/*XXX the caller only uses the first record if we return multiple.
+*/
+
+uschar *
+dkim_exim_query_dns_txt(uschar * name)
{
dns_answer dnsa;
dns_scan dnss;
dns_record *rr;
+gstring * g = NULL;
lookup_dnssec_authenticated = NULL;
-if (dns_lookup(&dnsa, US name, T_TXT, NULL) != DNS_SUCCEED)
- return PDKIM_FAIL; /*XXX better error detail? logging? */
+if (dns_lookup(&dnsa, name, T_TXT, NULL) != DNS_SUCCEED)
+ return NULL; /*XXX better error detail? logging? */
/* Search for TXT record */
if (rr->type == T_TXT)
{
int rr_offset = 0;
- int answer_offset = 0;
/* Copy record content to the answer buffer */
while (rr_offset < rr->size)
{
uschar len = rr->data[rr_offset++];
- snprintf(answer + answer_offset,
- PDKIM_DNS_TXT_MAX_RECLEN - answer_offset,
- "%.*s", (int)len, (char *) (rr->data + rr_offset));
+
+ g = string_catn(g, US(rr->data + rr_offset), len);
+ if (g->ptr >= PDKIM_DNS_TXT_MAX_RECLEN)
+ goto bad;
+
rr_offset += len;
- answer_offset += len;
- if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN)
- return PDKIM_FAIL; /*XXX better error detail? logging? */
}
- return PDKIM_OK;
+
+ /* check if this looks like a DKIM record */
+ if (Ustrncmp(g->s, "v=", 2) != 0 || strncasecmp(CS g->s, "v=dkim", 6) == 0)
+ {
+ gstring_reset_unused(g);
+ return string_from_gstring(g);
+ }
+
+ if (g) g->ptr = 0; /* overwrite previous record */
}
-return PDKIM_FAIL; /*XXX better error detail? logging? */
+bad:
+if (g) store_reset(g);
+return NULL; /*XXX better error detail? logging? */
}
/* Create new context */
dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
-dkim_collect_input = !!dkim_verify_ctx;
-dkim_collect_error = FALSE;
+dkim_collect_input = dkim_verify_ctx ? DKIM_MAX_SIGNATURES : 0;
+dkim_collect_error = NULL;
/* Start feed up with any cached data */
receive_get_cache();
store_pool = POOL_PERM;
if ( dkim_collect_input
- && (rc = pdkim_feed(dkim_verify_ctx, CS data, len)) != PDKIM_OK)
+ && (rc = pdkim_feed(dkim_verify_ctx, data, len)) != PDKIM_OK)
{
+ dkim_collect_error = pdkim_errstr(rc);
log_write(0, LOG_MAIN,
- "DKIM: validation error: %.100s", pdkim_errstr(rc));
- dkim_collect_error = TRUE;
- dkim_collect_input = FALSE;
+ "DKIM: validation error: %.100s", dkim_collect_error);
+ dkim_collect_input = 0;
}
store_pool = dkim_verify_oldpool;
}
-void
-dkim_exim_verify_finish(void)
+/* Log the result for the given signature */
+static void
+dkim_exim_verify_log_sig(pdkim_signature * sig)
{
-pdkim_signature * sig = NULL;
-int dkim_signers_size = 0;
-int dkim_signers_ptr = 0;
-int rc;
-
-store_pool = POOL_PERM;
-
-/* Delete eventual previous signature chain */
-
-dkim_signers = NULL;
-dkim_signatures = NULL;
-
-if (dkim_collect_error)
- {
- log_write(0, LOG_MAIN,
- "DKIM: Error while running this message through validation,"
- " disabling signature verification.");
- dkim_disable_verify = TRUE;
- goto out;
- }
-
-dkim_collect_input = FALSE;
-
-/* Finish DKIM operation and fetch link to signatures chain */
-
-if ((rc = pdkim_feed_finish(dkim_verify_ctx, &dkim_signatures)) != PDKIM_OK)
- {
- log_write(0, LOG_MAIN,
- "DKIM: validation error: %.100s", pdkim_errstr(rc));
- goto out;
+gstring * logmsg;
+uschar * s;
+
+if (!sig) return;
+
+/* Remember the domain for the first pass result */
+
+if ( !dkim_verify_overall
+ && dkim_verify_status
+ ? Ustrcmp(dkim_verify_status, US"pass") == 0
+ : sig->verify_status == PDKIM_VERIFY_PASS
+ )
+ dkim_verify_overall = string_copy(sig->domain);
+
+/* Rewrite the sig result if the ACL overrode it. This is only
+needed because the DMARC code (sigh) peeks at the dkim sigs.
+Mark the sig for this having been done. */
+
+if ( dkim_verify_status
+ && ( dkim_verify_status != dkim_exim_expand_query(DKIM_VERIFY_STATUS)
+ || dkim_verify_reason != dkim_exim_expand_query(DKIM_VERIFY_REASON)
+ ) )
+ { /* overridden by ACL */
+ sig->verify_ext_status = -1;
+ if (Ustrcmp(dkim_verify_status, US"fail") == 0)
+ sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_FAIL;
+ else if (Ustrcmp(dkim_verify_status, US"invalid") == 0)
+ sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_INVALID;
+ else if (Ustrcmp(dkim_verify_status, US"none") == 0)
+ sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_NONE;
+ else if (Ustrcmp(dkim_verify_status, US"pass") == 0)
+ sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_PASS;
+ else
+ sig->verify_status = -1;
}
-for (sig = dkim_signatures; sig; sig = sig->next)
- {
- int size = 0, ptr = 0;
- uschar * logmsg = NULL, * s;
-
- /* Log a line for each signature */
-
- if (!(s = sig->domain)) s = US"<UNSET>";
- logmsg = string_append(logmsg, &size, &ptr, 2, "d=", s);
- if (!(s = sig->selector)) s = US"<UNSET>";
- logmsg = string_append(logmsg, &size, &ptr, 2, " s=", s);
- logmsg = string_append(logmsg, &size, &ptr, 7,
- " c=", sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
- "/", sig->canon_body == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
- " a=", sig->algo == PDKIM_ALGO_RSA_SHA256
- ? "rsa-sha256"
- : sig->algo == PDKIM_ALGO_RSA_SHA1 ? "rsa-sha1" : "err",
- string_sprintf(" b=%d",
- (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : 0));
- if ((s= sig->identity)) string_append(logmsg, &size, &ptr, 2, " i=", s);
- if (sig->created > 0) string_append(logmsg, &size, &ptr, 1,
- string_sprintf(" t=%lu", sig->created));
- if (sig->expires > 0) string_append(logmsg, &size, &ptr, 1,
- string_sprintf(" x=%lu", sig->expires));
- if (sig->bodylength > -1) string_append(logmsg, &size, &ptr, 1,
- string_sprintf(" l=%lu", sig->bodylength));
-
+if (!LOGGING(dkim_verbose)) return;
+
+
+logmsg = string_catn(NULL, US"DKIM: ", 6);
+if (!(s = sig->domain)) s = US"<UNSET>";
+logmsg = string_append(logmsg, 2, "d=", s);
+if (!(s = sig->selector)) s = US"<UNSET>";
+logmsg = string_append(logmsg, 2, " s=", s);
+logmsg = string_fmt_append(logmsg, " c=%s/%s a=%s b=" SIZE_T_FMT,
+ sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
+ sig->canon_body == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
+ dkim_sig_to_a_tag(sig),
+ (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : (size_t)0);
+if ((s= sig->identity)) logmsg = string_append(logmsg, 2, " i=", s);
+if (sig->created > 0) logmsg = string_fmt_append(logmsg, " t=%lu",
+ sig->created);
+if (sig->expires > 0) logmsg = string_fmt_append(logmsg, " x=%lu",
+ sig->expires);
+if (sig->bodylength > -1) logmsg = string_fmt_append(logmsg, " l=%lu",
+ sig->bodylength);
+
+if (sig->verify_status & PDKIM_VERIFY_POLICY)
+ logmsg = string_append(logmsg, 5,
+ US" [", dkim_verify_status, US" - ", dkim_verify_reason, US"]");
+else
switch (sig->verify_status)
{
case PDKIM_VERIFY_NONE:
- logmsg = string_append(logmsg, &size, &ptr, 1, " [not verified]");
+ logmsg = string_cat(logmsg, US" [not verified]");
break;
case PDKIM_VERIFY_INVALID:
- logmsg = string_append(logmsg, &size, &ptr, 1, " [invalid - ");
+ logmsg = string_cat(logmsg, US" [invalid - ");
switch (sig->verify_ext_status)
{
case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
- logmsg = string_append(logmsg, &size, &ptr, 1,
- "public key record (currently?) unavailable]");
+ logmsg = string_cat(logmsg,
+ US"public key record (currently?) unavailable]");
break;
case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
- logmsg = string_append(logmsg, &size, &ptr, 1,
- "overlong public key record]");
+ logmsg = string_cat(logmsg, US"overlong public key record]");
break;
case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
- logmsg = string_append(logmsg, &size, &ptr, 1,
- "syntax error in public key record]");
+ logmsg = string_cat(logmsg, US"syntax error in public key record]");
break;
- case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
- logmsg = string_append(logmsg, &size, &ptr, 1,
- "signature tag missing or invalid]");
- break;
+ case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
+ logmsg = string_cat(logmsg, US"signature tag missing or invalid]");
+ break;
- case PDKIM_VERIFY_INVALID_DKIM_VERSION:
- logmsg = string_append(logmsg, &size, &ptr, 1,
- "unsupported DKIM version]");
- break;
+ case PDKIM_VERIFY_INVALID_DKIM_VERSION:
+ logmsg = string_cat(logmsg, US"unsupported DKIM version]");
+ break;
default:
- logmsg = string_append(logmsg, &size, &ptr, 1,
- "unspecified problem]");
+ logmsg = string_cat(logmsg, US"unspecified problem]");
}
break;
case PDKIM_VERIFY_FAIL:
- logmsg =
- string_append(logmsg, &size, &ptr, 1, " [verification failed - ");
+ logmsg = string_cat(logmsg, US" [verification failed - ");
switch (sig->verify_ext_status)
{
case PDKIM_VERIFY_FAIL_BODY:
- logmsg = string_append(logmsg, &size, &ptr, 1,
- "body hash mismatch (body probably modified in transit)]");
+ logmsg = string_cat(logmsg,
+ US"body hash mismatch (body probably modified in transit)]");
break;
case PDKIM_VERIFY_FAIL_MESSAGE:
- logmsg = string_append(logmsg, &size, &ptr, 1,
- "signature did not verify (headers probably modified in transit)]");
- break;
+ logmsg = string_cat(logmsg,
+ US"signature did not verify "
+ "(headers probably modified in transit)]");
+ break;
default:
- logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
+ logmsg = string_cat(logmsg, US"unspecified reason]");
}
break;
case PDKIM_VERIFY_PASS:
- logmsg =
- string_append(logmsg, &size, &ptr, 1, " [verification succeeded]");
+ logmsg = string_cat(logmsg, US" [verification succeeded]");
break;
}
- logmsg[ptr] = '\0';
- log_write(0, LOG_MAIN, "DKIM: %s", logmsg);
+log_write(0, LOG_MAIN, "%s", string_from_gstring(logmsg));
+return;
+}
+
+
+/* Log a line for each signature */
+void
+dkim_exim_verify_log_all(void)
+{
+pdkim_signature * sig;
+for (sig = dkim_signatures; sig; sig = sig->next) dkim_exim_verify_log_sig(sig);
+}
+
+
+void
+dkim_exim_verify_finish(void)
+{
+pdkim_signature * sig;
+int rc;
+gstring * g = NULL;
+const uschar * errstr = NULL;
+
+store_pool = POOL_PERM;
+
+/* Delete eventual previous signature chain */
+
+dkim_signers = NULL;
+dkim_signatures = NULL;
+
+if (dkim_collect_error)
+ {
+ log_write(0, LOG_MAIN,
+ "DKIM: Error during validation, disabling signature verification: %.100s",
+ dkim_collect_error);
+ f.dkim_disable_verify = TRUE;
+ goto out;
+ }
+
+dkim_collect_input = 0;
- /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
+/* Finish DKIM operation and fetch link to signatures chain */
- if (sig->domain)
- dkim_signers = string_append_listele(dkim_signers, ':', sig->domain);
+rc = pdkim_feed_finish(dkim_verify_ctx, (pdkim_signature **)&dkim_signatures,
+ &errstr);
+if (rc != PDKIM_OK && errstr)
+ log_write(0, LOG_MAIN, "DKIM: validation error: %s", errstr);
- if (sig->identity)
- dkim_signers = string_append_listele(dkim_signers, ':', sig->identity);
+/* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
- /* Process next signature */
+for (sig = dkim_signatures; sig; sig = sig->next)
+ {
+ if (sig->domain) g = string_append_listele(g, ':', sig->domain);
+ if (sig->identity) g = string_append_listele(g, ':', sig->identity);
}
+if (g) dkim_signers = g->s;
+
out:
store_pool = dkim_verify_oldpool;
}
-void
-dkim_exim_acl_setup(uschar * id)
+
+/* Args as per dkim_exim_acl_run() below */
+static int
+dkim_acl_call(uschar * id, gstring ** res_ptr,
+ uschar ** user_msgptr, uschar ** log_msgptr)
+{
+int rc;
+DEBUG(D_receive)
+ debug_printf("calling acl_smtp_dkim for dkim_cur_signer='%s'\n", id);
+
+rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, user_msgptr, log_msgptr);
+dkim_exim_verify_log_sig(dkim_cur_sig);
+*res_ptr = string_append_listele(*res_ptr, ':', dkim_verify_status);
+return rc;
+}
+
+
+
+/* For the given identity, run the DKIM ACL once for each matching signature.
+
+Arguments
+ id Identity to look for in dkim signatures
+ res_ptr ptr to growable string-list of status results,
+ appended to per ACL run
+ user_msgptr where to put a user error (for SMTP response)
+ log_msgptr where to put a logging message (not for SMTP response)
+
+Returns: OK access is granted by an ACCEPT verb
+ DISCARD access is granted by a DISCARD verb
+ FAIL access is denied
+ FAIL_DROP access is denied; drop the connection
+ DEFER can't tell at the moment
+ ERROR disaster
+*/
+
+int
+dkim_exim_acl_run(uschar * id, gstring ** res_ptr,
+ uschar ** user_msgptr, uschar ** log_msgptr)
{
pdkim_signature * sig;
uschar * cmp_val;
+int rc = -1;
-dkim_cur_sig = NULL;
+dkim_verify_status = US"none";
+dkim_verify_reason = US"";
dkim_cur_signer = id;
-if (dkim_disable_verify || !id || !dkim_verify_ctx)
- return;
+if (f.dkim_disable_verify || !id || !dkim_verify_ctx)
+ return OK;
-/* Find signature to run ACL on */
+/* Find signatures to run ACL on */
for (sig = dkim_signatures; sig; sig = sig->next)
if ( (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
&& strcmpic(cmp_val, id) == 0
)
{
- dkim_cur_sig = sig;
-
/* The "dkim_domain" and "dkim_selector" expansion variables have
- related globals, since they are used in the signing code too.
- Instead of inventing separate names for verification, we set
- them here. This is easy since a domain and selector is guaranteed
- to be in a signature. The other dkim_* expansion items are
- dynamically fetched from dkim_cur_sig at expansion time (see
- function below). */
+ related globals, since they are used in the signing code too.
+ Instead of inventing separate names for verification, we set
+ them here. This is easy since a domain and selector is guaranteed
+ to be in a signature. The other dkim_* expansion items are
+ dynamically fetched from dkim_cur_sig at expansion time (see
+ dkim_exim_expand_query() below). */
+ dkim_cur_sig = sig;
dkim_signing_domain = US sig->domain;
dkim_signing_selector = US sig->selector;
dkim_key_length = sig->sighash.len * 8;
- return;
+
+ /* These two return static strings, so we can compare the addr
+ later to see if the ACL overwrote them. Check that when logging */
+
+ dkim_verify_status = dkim_exim_expand_query(DKIM_VERIFY_STATUS);
+ dkim_verify_reason = dkim_exim_expand_query(DKIM_VERIFY_REASON);
+
+ if ((rc = dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr)) != OK)
+ return rc;
}
+
+if (rc != -1)
+ return rc;
+
+/* No matching sig found. Call ACL once anyway. */
+
+dkim_cur_sig = NULL;
+return dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr);
}
uschar *
dkim_exim_expand_query(int what)
{
-if (!dkim_verify_ctx || dkim_disable_verify || !dkim_cur_sig)
+if (!dkim_verify_ctx || f.dkim_disable_verify || !dkim_cur_sig)
return dkim_exim_expand_defaults(what);
switch (what)
{
case DKIM_ALGO:
- switch (dkim_cur_sig->algo)
- {
- case PDKIM_ALGO_RSA_SHA1: return US"rsa-sha1";
- case PDKIM_ALGO_RSA_SHA256:
- default: return US"rsa-sha256";
- }
+ return dkim_sig_to_a_tag(dkim_cur_sig);
case DKIM_BODYLENGTH:
return dkim_cur_sig->bodylength >= 0
- ? string_sprintf(OFF_T_FMT, (LONGLONG_T) dkim_cur_sig->bodylength)
+ ? string_sprintf("%ld", dkim_cur_sig->bodylength)
: dkim_exim_expand_defaults(what);
case DKIM_CANON_BODY:
}
case DKIM_CANON_HEADERS:
- switch (dkim_cur_sig->canon_headers)
- {
- case PDKIM_CANON_RELAXED: return US"relaxed";
- case PDKIM_CANON_SIMPLE:
- default: return US"simple";
- }
+ switch (dkim_cur_sig->canon_headers)
+ {
+ case PDKIM_CANON_RELAXED: return US"relaxed";
+ case PDKIM_CANON_SIMPLE:
+ default: return US"simple";
+ }
case DKIM_COPIEDHEADERS:
return dkim_cur_sig->copiedheaders
case DKIM_CREATED:
return dkim_cur_sig->created > 0
- ? string_sprintf("%llu", dkim_cur_sig->created)
+ ? string_sprintf("%lu", dkim_cur_sig->created)
: dkim_exim_expand_defaults(what);
case DKIM_EXPIRES:
return dkim_cur_sig->expires > 0
- ? string_sprintf("%llu", dkim_cur_sig->expires)
+ ? string_sprintf("%lu", dkim_cur_sig->expires)
: dkim_exim_expand_defaults(what);
case DKIM_HEADERNAMES:
}
-uschar *
-dkim_exim_sign(int dkim_fd, struct ob_dkim * dkim)
+void
+dkim_exim_sign_init(void)
{
-const uschar * dkim_domain;
+int old_pool = store_pool;
+store_pool = POOL_MAIN;
+pdkim_init_context(&dkim_sign_ctx, FALSE, &dkim_exim_query_dns_txt);
+store_pool = old_pool;
+}
+
+
+/* Generate signatures for the given file.
+If a prefix is given, prepend it to the file for the calculations.
+
+Return:
+ NULL: error; error string written
+ string: signature header(s), or a zero-length string (not an error)
+*/
+
+gstring *
+dkim_exim_sign(int fd, off_t off, uschar * prefix,
+ struct ob_dkim * dkim, const uschar ** errstr)
+{
+const uschar * dkim_domain = NULL;
int sep = 0;
-uschar *seen_items = NULL;
-int seen_items_size = 0;
-int seen_items_offset = 0;
-uschar itembuf[256];
-uschar *dkim_canon_expanded;
-uschar *dkim_sign_headers_expanded;
-uschar *dkim_private_key_expanded;
-pdkim_ctx *ctx = NULL;
-uschar *rc = NULL;
-uschar *sigbuf = NULL;
-int sigsize = 0;
-int sigptr = 0;
-pdkim_signature *signature;
-int pdkim_canon;
+gstring * seen_doms = NULL;
+pdkim_signature * sig;
+gstring * sigbuf;
int pdkim_rc;
int sread;
-char buf[4096];
+uschar buf[4096];
int save_errno = 0;
int old_pool = store_pool;
+uschar * errwhen;
+const uschar * s;
+
+if (dkim->dot_stuffed)
+ dkim_sign_ctx.flags |= PDKIM_DOT_TERM;
store_pool = POOL_MAIN;
-if (!(dkim_domain = expand_cstring(dkim->dkim_domain)))
- {
+if ((s = dkim->dkim_domain) && !(dkim_domain = expand_cstring(s)))
/* expansion error, do not send message. */
- log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
- "dkim_domain: %s", expand_string_message);
- goto bad;
- }
+ { errwhen = US"dkim_domain"; goto expand_bad; }
/* Set $dkim_domain expansion variable to each unique domain in list. */
-while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep,
- itembuf, sizeof(itembuf))))
+if (dkim_domain)
+ while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
{
- if (!dkim_signing_domain || dkim_signing_domain[0] == '\0')
+ const uschar * dkim_sel;
+ int sel_sep = 0;
+
+ if (dkim_signing_domain[0] == '\0')
continue;
/* Only sign once for each domain, no matter how often it
appears in the expanded list. */
- if (seen_items)
- {
- const uschar *seen_items_list = seen_items;
- if (match_isinlist(dkim_signing_domain,
- &seen_items_list, 0, NULL, NULL, MCL_STRING, TRUE,
- NULL) == OK)
- continue;
-
- seen_items =
- string_append(seen_items, &seen_items_size, &seen_items_offset, 1, ":");
- }
-
- seen_items =
- string_append(seen_items, &seen_items_size, &seen_items_offset, 1,
- dkim_signing_domain);
- seen_items[seen_items_offset] = '\0';
+ if (match_isinlist(dkim_signing_domain, CUSS &seen_doms,
+ 0, NULL, NULL, MCL_STRING, TRUE, NULL) == OK)
+ continue;
- /* Set up $dkim_selector expansion variable. */
+ seen_doms = string_append_listele(seen_doms, ':', dkim_signing_domain);
- if (!(dkim_signing_selector = expand_string(dkim->dkim_selector)))
- {
- log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
- "dkim_selector: %s", expand_string_message);
- goto bad;
- }
+ /* Set $dkim_selector expansion variable to each selector in list,
+ for this domain. */
- /* Get canonicalization to use */
-
- dkim_canon_expanded = dkim->dkim_canon
- ? expand_string(dkim->dkim_canon) : US"relaxed";
- if (!dkim_canon_expanded)
- {
- /* expansion error, do not send message. */
- log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
- "dkim_canon: %s", expand_string_message);
- goto bad;
- }
+ if (!(dkim_sel = expand_string(dkim->dkim_selector)))
+ { errwhen = US"dkim_selector"; goto expand_bad; }
- if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
- pdkim_canon = PDKIM_CANON_RELAXED;
- else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
- pdkim_canon = PDKIM_CANON_SIMPLE;
- else
+ while ((dkim_signing_selector = string_nextinlist(&dkim_sel, &sel_sep,
+ NULL, 0)))
{
- log_write(0, LOG_MAIN,
- "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
- dkim_canon_expanded);
- pdkim_canon = PDKIM_CANON_RELAXED;
- }
-
- dkim_sign_headers_expanded = NULL;
- if (dkim->dkim_sign_headers)
- if (!(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
+ uschar * dkim_canon_expanded;
+ int pdkim_canon;
+ uschar * dkim_sign_headers_expanded = NULL;
+ uschar * dkim_private_key_expanded;
+ uschar * dkim_hash_expanded;
+ uschar * dkim_identity_expanded = NULL;
+ uschar * dkim_timestamps_expanded = NULL;
+ unsigned long tval = 0, xval = 0;
+
+ /* Get canonicalization to use */
+
+ dkim_canon_expanded = dkim->dkim_canon
+ ? expand_string(dkim->dkim_canon) : US"relaxed";
+ if (!dkim_canon_expanded) /* expansion error, do not send message. */
+ { errwhen = US"dkim_canon"; goto expand_bad; }
+
+ if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
+ pdkim_canon = PDKIM_CANON_RELAXED;
+ else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
+ pdkim_canon = PDKIM_CANON_SIMPLE;
+ else
{
- log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
- "dkim_sign_headers: %s", expand_string_message);
- goto bad;
+ log_write(0, LOG_MAIN,
+ "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
+ dkim_canon_expanded);
+ pdkim_canon = PDKIM_CANON_RELAXED;
}
- /* else pass NULL, which means default header list */
- /* Get private key to use. */
+ if ( dkim->dkim_sign_headers
+ && !(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
+ { errwhen = US"dkim_sign_header"; goto expand_bad; }
+ /* else pass NULL, which means default header list */
- if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
- {
- log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
- "dkim_private_key: %s", expand_string_message);
- goto bad;
- }
+ /* Get private key to use. */
- if ( Ustrlen(dkim_private_key_expanded) == 0
- || Ustrcmp(dkim_private_key_expanded, "0") == 0
- || Ustrcmp(dkim_private_key_expanded, "false") == 0
- )
- continue; /* don't sign, but no error */
+ if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
+ { errwhen = US"dkim_private_key"; goto expand_bad; }
- if (dkim_private_key_expanded[0] == '/')
- {
- int privkey_fd, off = 0, len;
+ if ( Ustrlen(dkim_private_key_expanded) == 0
+ || Ustrcmp(dkim_private_key_expanded, "0") == 0
+ || Ustrcmp(dkim_private_key_expanded, "false") == 0
+ )
+ continue; /* don't sign, but no error */
- /* Looks like a filename, load the private key. */
+ if ( dkim_private_key_expanded[0] == '/'
+ && !(dkim_private_key_expanded =
+ expand_file_big_buffer(dkim_private_key_expanded)))
+ goto bad;
- memset(big_buffer, 0, big_buffer_size);
+ if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
+ { errwhen = US"dkim_hash"; goto expand_bad; }
+
+ if (dkim->dkim_identity)
+ if (!(dkim_identity_expanded = expand_string(dkim->dkim_identity)))
+ { errwhen = US"dkim_identity"; goto expand_bad; }
+ else if (!*dkim_identity_expanded)
+ dkim_identity_expanded = NULL;
+
+ if (dkim->dkim_timestamps)
+ if (!(dkim_timestamps_expanded = expand_string(dkim->dkim_timestamps)))
+ { errwhen = US"dkim_timestamps"; goto expand_bad; }
+ else
+ xval = (tval = (unsigned long) time(NULL))
+ + strtoul(CCS dkim_timestamps_expanded, NULL, 10);
+
+ if (!(sig = pdkim_init_sign(&dkim_sign_ctx, dkim_signing_domain,
+ dkim_signing_selector,
+ dkim_private_key_expanded,
+ dkim_hash_expanded,
+ errstr
+ )))
+ goto bad;
+ dkim_private_key_expanded[0] = '\0';
- if ((privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY)) < 0)
- {
- log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
- "private key file for reading: %s",
- dkim_private_key_expanded);
+ pdkim_set_optional(sig,
+ CS dkim_sign_headers_expanded,
+ CS dkim_identity_expanded,
+ pdkim_canon,
+ pdkim_canon, -1, tval, xval);
+
+ if (!pdkim_set_sig_bodyhash(&dkim_sign_ctx, sig))
goto bad;
- }
- do
+ if (!dkim_sign_ctx.sig) /* link sig to context chain */
+ dkim_sign_ctx.sig = sig;
+ else
{
- if ((len = read(privkey_fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
- {
- (void) close(privkey_fd);
- log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
- dkim_private_key_expanded);
- goto bad;
- }
- off += len;
+ pdkim_signature * n = dkim_sign_ctx.sig;
+ while (n->next) n = n->next;
+ n->next = sig;
}
- while (len > 0);
-
- (void) close(privkey_fd);
- big_buffer[off] = '\0';
- dkim_private_key_expanded = big_buffer;
}
+ }
- ctx = pdkim_init_sign(CS dkim_signing_domain,
- CS dkim_signing_selector,
- CS dkim_private_key_expanded,
- PDKIM_ALGO_RSA_SHA256,
- dkim->dot_stuffed,
- &dkim_exim_query_dns_txt
- );
- dkim_private_key_expanded[0] = '\0';
- pdkim_set_optional(ctx,
- CS dkim_sign_headers_expanded,
- NULL,
- pdkim_canon,
- pdkim_canon, -1, 0, 0);
-
- lseek(dkim_fd, 0, SEEK_SET);
-
- while ((sread = read(dkim_fd, &buf, sizeof(buf))) > 0)
- if ((pdkim_rc = pdkim_feed(ctx, buf, sread)) != PDKIM_OK)
- goto pk_bad;
+/* We may need to carry on with the data-feed even if there are no DKIM sigs to
+produce, if some other package (eg. ARC) is signing. */
+
+if (!dkim_sign_ctx.sig && !dkim->force_bodyhash)
+ {
+ DEBUG(D_transport) debug_printf("DKIM: no viable signatures to use\n");
+ sigbuf = string_get(1); /* return a zero-len string */
+ }
+else
+ {
+ if (prefix && (pdkim_rc = pdkim_feed(&dkim_sign_ctx, prefix, Ustrlen(prefix))) != PDKIM_OK)
+ goto pk_bad;
+
+ if (lseek(fd, off, SEEK_SET) < 0)
+ sread = -1;
+ else
+ while ((sread = read(fd, &buf, sizeof(buf))) > 0)
+ if ((pdkim_rc = pdkim_feed(&dkim_sign_ctx, buf, sread)) != PDKIM_OK)
+ goto pk_bad;
/* Handle failed read above. */
if (sread == -1)
goto bad;
}
- if ((pdkim_rc = pdkim_feed_finish(ctx, &signature)) != PDKIM_OK)
- goto pk_bad;
-
- sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
- US signature->signature_header, US"\r\n");
+ /* Build string of headers, one per signature */
- pdkim_free_ctx(ctx);
- ctx = NULL;
- }
+ if ((pdkim_rc = pdkim_feed_finish(&dkim_sign_ctx, &sig, errstr)) != PDKIM_OK)
+ goto pk_bad;
-if (sigbuf)
- {
- sigbuf[sigptr] = '\0';
- rc = sigbuf;
+ if (!sig)
+ {
+ DEBUG(D_transport) debug_printf("DKIM: no signatures to use\n");
+ sigbuf = string_get(1); /* return a zero-len string */
+ }
+ else for (sigbuf = NULL; sig; sig = sig->next)
+ sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n");
}
-else
- rc = US"";
CLEANUP:
- if (ctx)
- pdkim_free_ctx(ctx);
+ (void) string_from_gstring(sigbuf);
store_pool = old_pool;
errno = save_errno;
- return rc;
+ return sigbuf;
pk_bad:
log_write(0, LOG_MAIN|LOG_PANIC,
"DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
bad:
- rc = NULL;
+ sigbuf = NULL;
goto CLEANUP;
+
+expand_bad:
+ log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand %s: %s",
+ errwhen, expand_string_message);
+ goto bad;
+}
+
+
+
+
+gstring *
+authres_dkim(gstring * g)
+{
+pdkim_signature * sig;
+int start = 0; /* compiler quietening */
+
+DEBUG(D_acl) start = g->ptr;
+
+for (sig = dkim_signatures; sig; sig = sig->next)
+ {
+ g = string_catn(g, US";\n\tdkim=", 8);
+
+ if (sig->verify_status & PDKIM_VERIFY_POLICY)
+ g = string_append(g, 5,
+ US"policy (", dkim_verify_status, US" - ", dkim_verify_reason, US")");
+ else switch(sig->verify_status)
+ {
+ case PDKIM_VERIFY_NONE: g = string_cat(g, US"none"); break;
+ case PDKIM_VERIFY_INVALID:
+ switch (sig->verify_ext_status)
+ {
+ case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
+ g = string_cat(g, US"tmperror (pubkey unavailable)\n\t\t"); break;
+ case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
+ g = string_cat(g, US"permerror (overlong public key record)\n\t\t"); break;
+ case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
+ case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
+ g = string_cat(g, US"neutral (public key record import problem)\n\t\t");
+ break;
+ case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
+ g = string_cat(g, US"neutral (signature tag missing or invalid)\n\t\t");
+ break;
+ case PDKIM_VERIFY_INVALID_DKIM_VERSION:
+ g = string_cat(g, US"neutral (unsupported DKIM version)\n\t\t");
+ break;
+ default:
+ g = string_cat(g, US"permerror (unspecified problem)\n\t\t"); break;
+ }
+ break;
+ case PDKIM_VERIFY_FAIL:
+ switch (sig->verify_ext_status)
+ {
+ case PDKIM_VERIFY_FAIL_BODY:
+ g = string_cat(g,
+ US"fail (body hash mismatch; body probably modified in transit)\n\t\t");
+ break;
+ case PDKIM_VERIFY_FAIL_MESSAGE:
+ g = string_cat(g,
+ US"fail (signature did not verify; headers probably modified in transit)\n\t\t");
+ break;
+ default:
+ g = string_cat(g, US"fail (unspecified reason)\n\t\t");
+ break;
+ }
+ break;
+ case PDKIM_VERIFY_PASS: g = string_cat(g, US"pass"); break;
+ default: g = string_cat(g, US"permerror"); break;
+ }
+ if (sig->domain) g = string_append(g, 2, US" header.d=", sig->domain);
+ if (sig->identity) g = string_append(g, 2, US" header.i=", sig->identity);
+ if (sig->selector) g = string_append(g, 2, US" header.s=", sig->selector);
+ g = string_append(g, 2, US" header.a=", dkim_sig_to_a_tag(sig));
+ }
+
+DEBUG(D_acl)
+ if (g->ptr == start)
+ debug_printf("DKIM: no authres\n");
+ else
+ debug_printf("DKIM: authres '%.*s'\n", g->ptr - start - 3, g->s + start + 3);
+return g;
}
-#endif
+
+# endif /*!MACRO_PREDEF*/
+#endif /*!DISABLE_DKIM*/
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge, 1995 - 2016 */
+/* Copyright (c) University of Cambridge, 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
void dkim_exim_init(void);
-uschar *dkim_exim_sign(int, struct ob_dkim *);
+gstring * dkim_exim_sign(int, off_t, uschar *, struct ob_dkim *, const uschar **);
void dkim_exim_verify_init(BOOL);
void dkim_exim_verify_feed(uschar *, int);
void dkim_exim_verify_finish(void);
-void dkim_exim_acl_setup(uschar *);
+void dkim_exim_verify_log_all(void);
+int dkim_exim_acl_run(uschar *, gstring **, uschar **, uschar **);
uschar *dkim_exim_expand_query(int);
#define DKIM_ALGO 1
--- /dev/null
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Transport shim for dkim signing */
+
+
+#include "exim.h"
+
+#ifndef DISABLE_DKIM /* rest of file */
+
+
+static BOOL
+dkt_sign_fail(struct ob_dkim * dkim, int * errp)
+{
+if (dkim->dkim_strict)
+ {
+ uschar * dkim_strict_result = expand_string(dkim->dkim_strict);
+
+ if (dkim_strict_result)
+ if ( (strcmpic(dkim->dkim_strict, US"1") == 0) ||
+ (strcmpic(dkim->dkim_strict, US"true") == 0) )
+ {
+ /* Set errno to something halfway meaningful */
+ *errp = EACCES;
+ log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
+ " and dkim_strict is set. Deferring message delivery.");
+ return FALSE;
+ }
+ }
+return TRUE;
+}
+
+/* Send the file at in_fd down the output fd */
+
+static BOOL
+dkt_send_file(int out_fd, int in_fd, off_t off
+#ifdef OS_SENDFILE
+ , size_t size
+#endif
+ )
+{
+#ifdef OS_SENDFILE
+DEBUG(D_transport) debug_printf("send file fd=%d size=%u\n", out_fd, (unsigned)(size - off));
+#else
+DEBUG(D_transport) debug_printf("send file fd=%d\n", out_fd);
+#endif
+
+/*XXX should implement timeout, like transport_write_block_fd() ? */
+
+#ifdef OS_SENDFILE
+/* We can use sendfile() to shove the file contents
+ to the socket. However only if we don't use TLS,
+ as then there's another layer of indirection
+ before the data finally hits the socket. */
+if (tls_out.active.sock != out_fd)
+ {
+ ssize_t copied = 0;
+
+ while(copied >= 0 && off < size)
+ copied = os_sendfile(out_fd, in_fd, &off, size - off);
+ if (copied < 0)
+ return FALSE;
+ }
+else
+
+#endif
+
+ {
+ int sread, wwritten;
+
+ /* Rewind file */
+ if (lseek(in_fd, off, SEEK_SET) < 0) return FALSE;
+
+ /* Send file down the original fd */
+ while((sread = read(in_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) > 0)
+ {
+ uschar * p = deliver_out_buffer;
+ /* write the chunk */
+
+ while (sread)
+ {
+#ifdef SUPPORT_TLS
+ wwritten = tls_out.active.sock == out_fd
+ ? tls_write(tls_out.active.tls_ctx, p, sread, FALSE)
+ : write(out_fd, CS p, sread);
+#else
+ wwritten = write(out_fd, CS p, sread);
+#endif
+ if (wwritten == -1)
+ return FALSE;
+ p += wwritten;
+ sread -= wwritten;
+ }
+ }
+
+ if (sread == -1)
+ return FALSE;
+ }
+
+return TRUE;
+}
+
+
+
+
+/* This function is a wrapper around transport_write_message().
+ It is only called from the smtp transport if DKIM or Domainkeys support
+ is active and no transport filter is to be used.
+
+Arguments:
+ As for transport_write_message() in transort.c, with additional arguments
+ for DKIM.
+
+Returns: TRUE on success; FALSE (with errno) for any failure
+*/
+
+static BOOL
+dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
+ const uschar ** err)
+{
+int save_fd = tctx->u.fd;
+int save_options = tctx->options;
+BOOL save_wireformat = f.spool_file_wireformat;
+uschar * hdrs;
+gstring * dkim_signature;
+int hsize;
+const uschar * errstr;
+BOOL rc;
+
+DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
+
+/* Get headers in string for signing and transmission. Do CRLF
+and dotstuffing (but no body nor dot-termination) */
+
+tctx->u.msg = NULL;
+tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
+ | topt_output_string | topt_no_body;
+
+rc = transport_write_message(tctx, 0);
+hdrs = string_from_gstring(tctx->u.msg);
+hsize = tctx->u.msg->ptr;
+
+tctx->u.fd = save_fd;
+tctx->options = save_options;
+if (!rc) return FALSE;
+
+/* Get signatures for headers plus spool data file */
+
+#ifdef EXPERIMENTAL_ARC
+arc_sign_init();
+#endif
+
+/* The dotstuffed status of the datafile depends on whether it was stored
+in wireformat. */
+
+dkim->dot_stuffed = f.spool_file_wireformat;
+if (!(dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET,
+ hdrs, dkim, &errstr)))
+ if (!(rc = dkt_sign_fail(dkim, &errno)))
+ {
+ *err = errstr;
+ return FALSE;
+ }
+
+#ifdef EXPERIMENTAL_ARC
+if (dkim->arc_signspec) /* Prepend ARC headers */
+ {
+ uschar * e;
+ if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, &e)))
+ {
+ *err = e;
+ return FALSE;
+ }
+ }
+#endif
+
+/* Write the signature and headers into the deliver-out-buffer. This should
+mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
+(transport_write_message() sizes the BDAT for the buffered amount) - for short
+messages, the BDAT LAST command. We want no dotstuffing expansion here, it
+having already been done - but we have to say we want CRLF output format, and
+temporarily set the marker for possible already-CRLF input. */
+
+tctx->options &= ~topt_escape_headers;
+f.spool_file_wireformat = TRUE;
+transport_write_reset(0);
+if ( ( dkim_signature
+ && dkim_signature->ptr > 0
+ && !write_chunk(tctx, dkim_signature->s, dkim_signature->ptr)
+ )
+ || !write_chunk(tctx, hdrs, hsize)
+ )
+ return FALSE;
+
+f.spool_file_wireformat = save_wireformat;
+tctx->options = save_options | topt_no_headers | topt_continuation;
+
+if (!(transport_write_message(tctx, 0)))
+ return FALSE;
+
+tctx->options = save_options;
+return TRUE;
+}
+
+
+/* This function is a wrapper around transport_write_message().
+ It is only called from the smtp transport if DKIM or Domainkeys support
+ is active and a transport filter is to be used. The function sets up a
+ replacement fd into a -K file, then calls the normal function. This way, the
+ exact bits that exim would have put "on the wire" will end up in the file
+ (except for TLS encapsulation, which is the very very last thing). When we
+ are done signing the file, send the signed message down the original fd (or
+ TLS fd).
+
+Arguments:
+ As for transport_write_message() in transort.c, with additional arguments
+ for DKIM.
+
+Returns: TRUE on success; FALSE (with errno) for any failure
+*/
+
+static BOOL
+dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
+{
+int dkim_fd;
+int save_errno = 0;
+BOOL rc;
+uschar * dkim_spool_name;
+gstring * dkim_signature;
+int options, dlen;
+off_t k_file_size;
+const uschar * errstr;
+
+dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
+ string_sprintf("-%d-K", (int)getpid()));
+
+DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
+
+if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
+ {
+ /* Can't create spool file. Ugh. */
+ rc = FALSE;
+ save_errno = errno;
+ *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
+ goto CLEANUP;
+ }
+
+/* Call transport utility function to write the -K file; does the CRLF expansion
+(but, in the CHUNKING case, neither dot-stuffing nor dot-termination). */
+
+ {
+ int save_fd = tctx->u.fd;
+ tctx->u.fd = dkim_fd;
+ options = tctx->options;
+ tctx->options &= ~topt_use_bdat;
+
+ rc = transport_write_message(tctx, 0);
+
+ tctx->u.fd = save_fd;
+ tctx->options = options;
+ }
+
+/* Save error state. We must clean up before returning. */
+if (!rc)
+ {
+ save_errno = errno;
+ goto CLEANUP;
+ }
+
+#ifdef EXPERIMENTAL_ARC
+arc_sign_init();
+#endif
+
+/* Feed the file to the goats^W DKIM lib. At this point the dotstuffed
+status of the file depends on the output of transport_write_message() just
+above, which should be the result of the end_dot flag in tctx->options. */
+
+dkim->dot_stuffed = !!(options & topt_end_dot);
+if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
+ {
+ dlen = 0;
+ if (!(rc = dkt_sign_fail(dkim, &save_errno)))
+ {
+ *err = errstr;
+ goto CLEANUP;
+ }
+ }
+else
+ dlen = dkim_signature->ptr;
+
+#ifdef EXPERIMENTAL_ARC
+if (dkim->arc_signspec) /* Prepend ARC headers */
+ {
+ if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err)))
+ goto CLEANUP;
+ dlen = dkim_signature->ptr;
+ }
+#endif
+
+#ifndef OS_SENDFILE
+if (options & topt_use_bdat)
+#endif
+ if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
+ {
+ *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
+ goto CLEANUP;
+ }
+
+if (options & topt_use_bdat)
+ {
+ /* On big messages output a precursor chunk to get any pipelined
+ MAIL & RCPT commands flushed, then reap the responses so we can
+ error out on RCPT rejects before sending megabytes. */
+
+ if ( dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
+ && dlen > 0)
+ {
+ if ( tctx->chunk_cb(tctx, dlen, 0) != OK
+ || !transport_write_block(tctx,
+ dkim_signature->s, dlen, FALSE)
+ || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
+ )
+ goto err;
+ dlen = 0;
+ }
+
+ /* Send the BDAT command for the entire message, as a single LAST-marked
+ chunk. */
+
+ if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
+ goto err;
+ }
+
+if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
+ goto err;
+
+if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
+#ifdef OS_SENDFILE
+ , k_file_size
+#endif
+ ))
+ {
+ save_errno = errno;
+ rc = FALSE;
+ }
+
+CLEANUP:
+ /* unlink -K file */
+ if (dkim_fd >= 0) (void)close(dkim_fd);
+ Uunlink(dkim_spool_name);
+ errno = save_errno;
+ return rc;
+
+err:
+ save_errno = errno;
+ rc = FALSE;
+ goto CLEANUP;
+}
+
+
+
+/***************************************************************************************************
+* External interface to write the message, while signing it with DKIM and/or Domainkeys *
+***************************************************************************************************/
+
+/* This function is a wrapper around transport_write_message().
+ It is only called from the smtp transport if DKIM or Domainkeys support
+ is compiled in.
+
+Arguments:
+ As for transport_write_message() in transort.c, with additional arguments
+ for DKIM.
+
+Returns: TRUE on success; FALSE (with errno) for any failure
+*/
+
+BOOL
+dkim_transport_write_message(transport_ctx * tctx,
+ struct ob_dkim * dkim, const uschar ** err)
+{
+/* If we can't sign, just call the original function. */
+
+if ( !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
+ && !dkim->force_bodyhash)
+ return transport_write_message(tctx, 0);
+
+/* If there is no filter command set up, construct the message and calculate
+a dkim signature of it, send the signature and a reconstructed message. This
+avoids using a temprary file. */
+
+if ( !transport_filter_argv
+ || !*transport_filter_argv
+ || !**transport_filter_argv
+ )
+ return dkt_direct(tctx, dkim, err);
+
+/* Use the transport path to write a file, calculate a dkim signature,
+send the signature and then send the file. */
+
+return dkt_via_kfile(tctx, dkim, err);
+}
+
+#endif /* whole file */
+
+/* vi: aw ai sw=2
+*/
+/* End of dkim_transport.c */
#include "exim.h"
#ifdef EXPERIMENTAL_DMARC
-# if !defined EXPERIMENTAL_SPF
+# if !defined SUPPORT_SPF
# error SPF must also be enabled for DMARC
# elif defined DISABLE_DKIM
# error DKIM must also be enabled for DMARC
OPENDMARC_STATUS_T da, sa, action;
BOOL dmarc_abort = FALSE;
uschar *dmarc_pass_fail = US"skipped";
-extern pdkim_signature *dkim_signatures;
header_line *from_header = NULL;
extern SPF_response_t *spf_response;
int dmarc_spf_ares_result = 0;
} dmarc_exim_p;
static dmarc_exim_p dmarc_policy_description[] = {
+ /* name value */
{ US"", DMARC_RECORD_P_UNSPECIFIED },
{ US"none", DMARC_RECORD_P_NONE },
{ US"quarantine", DMARC_RECORD_P_QUARANTINE },
messages on the same SMTP connection (that come from the
same host with the same HELO string) */
-int dmarc_init()
+int
+dmarc_init()
{
int *netmask = NULL; /* Ignored */
int is_ipv6 = 0;
-char *tld_file = (dmarc_tld_file == NULL) ?
- "/etc/exim/opendmarc.tlds" :
- (char *)dmarc_tld_file;
/* Set some sane defaults. Also clears previous results when
* multiple messages in one connection. */
dmarc_abort = FALSE;
dmarc_pass_fail = US"skipped";
dmarc_used_domain = US"";
-dmarc_ar_header = NULL;
-dmarc_has_been_checked = FALSE;
+f.dmarc_has_been_checked = FALSE;
header_from_sender = NULL;
spf_sender_domain = NULL;
spf_human_readable = NULL;
/* ACLs have "control=dmarc_disable_verify" */
-if (dmarc_disable_verify == TRUE)
+if (f.dmarc_disable_verify == TRUE)
return OK;
(void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
opendmarc_policy_status_to_str(libdm_status));
dmarc_abort = TRUE;
}
-if (dmarc_tld_file == NULL)
+if (!dmarc_tld_file)
+ {
+ DEBUG(D_receive) debug_printf("DMARC: no dmarc_tld_file\n");
dmarc_abort = TRUE;
-else if (opendmarc_tld_read_file(tld_file, NULL, NULL, NULL))
+ }
+else if (opendmarc_tld_read_file(CS dmarc_tld_file, NULL, NULL, NULL))
{
log_write(0, LOG_MAIN|LOG_PANIC, "DMARC failure to load tld list %s: %d",
- tld_file, errno);
+ dmarc_tld_file, errno);
dmarc_abort = TRUE;
}
-if (sender_host_address == NULL)
+if (!sender_host_address)
+ {
+ DEBUG(D_receive) debug_printf("DMARC: no sender_host_address\n");
dmarc_abort = TRUE;
+ }
/* This catches locally originated email and startup errors above. */
if (!dmarc_abort)
{
is_ipv6 = string_is_ip_address(sender_host_address, netmask) == 6;
- dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6);
- if (dmarc_pctx == NULL)
+ if (!(dmarc_pctx = opendmarc_policy_connect_init(sender_host_address, is_ipv6)))
{
log_write(0, LOG_MAIN|LOG_PANIC,
"DMARC failure creating policy context: ip=%s", sender_host_address);
/* dmarc_store_data stores the header data so that subsequent
- * dmarc_process can access the data */
+dmarc_process can access the data */
-int dmarc_store_data(header_line *hdr) {
- /* No debug output because would change every test debug output */
- if (dmarc_disable_verify != TRUE)
- from_header = hdr;
- return OK;
+int
+dmarc_store_data(header_line *hdr)
+{
+/* No debug output because would change every test debug output */
+if (!f.dmarc_disable_verify)
+ from_header = hdr;
+return OK;
}
FILE *message_file = NULL;
/* Earlier ACL does not have *required* control=dmarc_enable_forensic */
-if (!dmarc_enable_forensic)
+if (!f.dmarc_enable_forensic)
return;
if ( dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT
eblock = add_to_eblock(eblock, US"Sender IP Address", sender_host_address);
eblock = add_to_eblock(eblock, US"Received Date", tod_stamp(tod_full));
eblock = add_to_eblock(eblock, US"SPF Alignment",
- (sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?US"yes":US"no");
+ sa == DMARC_POLICY_SPF_ALIGNMENT_PASS ? US"yes" : US"no");
eblock = add_to_eblock(eblock, US"DKIM Alignment",
- (da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?US"yes":US"no");
+ da == DMARC_POLICY_DKIM_ALIGNMENT_PASS ? US"yes" : US"no");
eblock = add_to_eblock(eblock, US"DMARC Results", dmarc_status_text);
- /* Set a sane default envelope sender */
- dsn_from = dmarc_forensic_sender ? dmarc_forensic_sender :
- dsn_from ? dsn_from :
- string_sprintf("do-not-reply@%s",primary_hostname);
+
for (c = 0; ruf[c]; c++)
{
recipient = string_copylc(ruf[c]);
recipient += 7;
DEBUG(D_receive)
debug_printf("DMARC forensic report to %s%s\n", recipient,
- (host_checking || running_in_test_harness) ? " (not really)" : "");
- if (host_checking || running_in_test_harness)
+ (host_checking || f.running_in_test_harness) ? " (not really)" : "");
+ if (host_checking || f.running_in_test_harness)
continue;
- save_sender = sender_address;
- sender_address = recipient;
- send_status = moan_to_sender(ERRMESS_DMARC_FORENSIC, eblock,
- header_list, message_file, FALSE);
- sender_address = save_sender;
- if (!send_status)
+ if (!moan_send_message(recipient, ERRMESS_DMARC_FORENSIC, eblock,
+ header_list, message_file, NULL))
log_write(0, LOG_MAIN|LOG_PANIC,
"failure to send DMARC forensic report to %s", recipient);
}
}
/* dmarc_process adds the envelope sender address to the existing
- context (if any), retrieves the result, sets up expansion
- strings and evaluates the condition outcome. */
+context (if any), retrieves the result, sets up expansion
+strings and evaluates the condition outcome. */
int
dmarc_process()
int sr, origin; /* used in SPF section */
int dmarc_spf_result = 0; /* stores spf into dmarc conn ctx */
int tmp_ans, c;
-pdkim_signature *sig = NULL;
+pdkim_signature * sig = dkim_signatures;
BOOL has_dmarc_record = TRUE;
u_char **ruf; /* forensic report addressees, if called for */
/* ACLs have "control=dmarc_disable_verify" */
-if (dmarc_disable_verify)
- {
- dmarc_ar_header = dmarc_auth_results_header(from_header, NULL);
+if (f.dmarc_disable_verify)
return OK;
- }
/* Store the header From: sender domain for this part of DMARC.
* If there is no from_header struct, then it's likely this message
* the entire DMARC system if we can't find a From: header....or if
* there was a previous error.
*/
-if (!from_header || dmarc_abort)
+if (!from_header)
+ {
+ DEBUG(D_receive) debug_printf("DMARC: no From: header\n");
dmarc_abort = TRUE;
-else
+ }
+else if (!dmarc_abort)
{
- uschar * errormsg;
- int dummy, domain;
- uschar * p;
- uschar saveend;
-
- parse_allow_group = TRUE;
- p = parse_find_address_end(from_header->text, FALSE);
- saveend = *p; *p = '\0';
- if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
- &dummy, &dummy, &domain, FALSE)))
- header_from_sender += domain;
- *p = saveend;
-
- /* The opendmarc library extracts the domain from the email address, but
- * only try to store it if it's not empty. Otherwise, skip out of DMARC. */
- if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
- dmarc_abort = TRUE;
- libdm_status = dmarc_abort ?
- DMARC_PARSE_OKAY :
- opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
- if (libdm_status != DMARC_PARSE_OKAY)
+ uschar * errormsg;
+ int dummy, domain;
+ uschar * p;
+ uschar saveend;
+
+ f.parse_allow_group = TRUE;
+ p = parse_find_address_end(from_header->text, FALSE);
+ saveend = *p; *p = '\0';
+ if ((header_from_sender = parse_extract_address(from_header->text, &errormsg,
+ &dummy, &dummy, &domain, FALSE)))
+ header_from_sender += domain;
+ *p = saveend;
+
+ /* The opendmarc library extracts the domain from the email address, but
+ * only try to store it if it's not empty. Otherwise, skip out of DMARC. */
+ if (!header_from_sender || (strcmp( CCS header_from_sender, "") == 0))
+ dmarc_abort = TRUE;
+ libdm_status = dmarc_abort
+ ? DMARC_PARSE_OKAY
+ : opendmarc_policy_store_from_domain(dmarc_pctx, header_from_sender);
+ if (libdm_status != DMARC_PARSE_OKAY)
{
log_write(0, LOG_MAIN|LOG_PANIC,
"failure to store header From: in DMARC: %s, header was '%s'",
* instead do this in the ACLs. */
if (!dmarc_abort && !sender_host_authenticated)
{
+ uschar * dmarc_domain;
+
/* Use the envelope sender domain for this part of DMARC */
spf_sender_domain = expand_string(US"$sender_address_domain");
if (!spf_response)
sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
ARES_RESULT_UNKNOWN;
origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
- spf_human_readable = (uschar *)spf_response->header_comment;
+ spf_human_readable = US spf_response->header_comment;
DEBUG(D_receive)
debug_printf("DMARC using SPF sender domain = %s\n", spf_sender_domain);
}
/* Now we cycle through the dkim signature results and put into
* the opendmarc context, further building the DMARC reply. */
- sig = dkim_signatures;
dkim_history_buffer = US"";
while (sig)
{
int dkim_result, dkim_ares_result, vs, ves;
- vs = sig->verify_status;
+
+ vs = sig->verify_status & ~PDKIM_VERIFY_POLICY;
ves = sig->verify_ext_status;
dkim_result = vs == PDKIM_VERIFY_PASS ? DMARC_POLICY_DKIM_OUTCOME_PASS :
vs == PDKIM_VERIFY_FAIL ? DMARC_POLICY_DKIM_OUTCOME_FAIL :
vs == PDKIM_VERIFY_INVALID ? DMARC_POLICY_DKIM_OUTCOME_TMPFAIL :
DMARC_POLICY_DKIM_OUTCOME_NONE;
- libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, (uschar *)sig->domain,
+ libdm_status = opendmarc_policy_store_dkim(dmarc_pctx, US sig->domain,
dkim_result, US"");
DEBUG(D_receive)
debug_printf("DMARC adding DKIM sender domain = %s\n", sig->domain);
}
/* Can't use exim's string manipulation functions so allocate memory
- * for libopendmarc using its max hostname length definition. */
+ for libopendmarc using its max hostname length definition. */
- uschar *dmarc_domain = (uschar *)calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar));
+ dmarc_domain = US calloc(DMARC_MAXHOSTNAMELEN, sizeof(uschar));
libdm_status = opendmarc_policy_fetch_utilized_domain(dmarc_pctx,
dmarc_domain, DMARC_MAXHOSTNAMELEN-1);
dmarc_used_domain = string_copy(dmarc_domain);
"failure to read domainname used for DMARC lookup: %s",
opendmarc_policy_status_to_str(libdm_status));
- libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
- dmarc_policy = libdm_status;
+ dmarc_policy = libdm_status = opendmarc_get_policy_to_enforce(dmarc_pctx);
switch(libdm_status)
{
case DMARC_POLICY_ABSENT: /* No DMARC record found */
log_write(0, LOG_MAIN|LOG_PANIC, "failure to read DMARC alignment: %s",
opendmarc_policy_status_to_str(libdm_status));
- if (has_dmarc_record == TRUE)
+ if (has_dmarc_record)
{
log_write(0, LOG_MAIN, "DMARC results: spf_domain=%s dmarc_domain=%s "
"spf_align=%s dkim_align=%s enforcement='%s'",
spf_sender_domain, dmarc_used_domain,
- (sa==DMARC_POLICY_SPF_ALIGNMENT_PASS) ?"yes":"no",
- (da==DMARC_POLICY_DKIM_ALIGNMENT_PASS)?"yes":"no",
+ sa==DMARC_POLICY_SPF_ALIGNMENT_PASS ?"yes":"no",
+ da==DMARC_POLICY_DKIM_ALIGNMENT_PASS ?"yes":"no",
dmarc_status_text);
history_file_status = dmarc_write_history_file();
/* Now get the forensic reporting addresses, if any */
}
}
-/* set some global variables here */
-dmarc_ar_header = dmarc_auth_results_header(from_header, NULL);
-
/* shut down libopendmarc */
-if ( dmarc_pctx != NULL )
+if (dmarc_pctx)
(void) opendmarc_policy_connect_shutdown(dmarc_pctx);
-if ( dmarc_disable_verify == FALSE )
+if (!f.dmarc_disable_verify)
(void) opendmarc_policy_library_shutdown(&dmarc_ctx);
return OK;
}
-int
+static int
dmarc_write_history_file()
{
int history_file_fd;
uschar *history_buffer = NULL;
if (!dmarc_history_file)
+ {
+ DEBUG(D_receive) debug_printf("DMARC history file not set\n");
return DMARC_HIST_DISABLED;
+ }
history_file_fd = log_create(dmarc_history_file);
if (history_file_fd < 0)
-{
+ {
log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s",
dmarc_history_file);
return DMARC_HIST_FILE_ERR;
-}
+ }
/* Generate the contents of the history file */
history_buffer = string_sprintf(
/* Write the contents to the history file */
DEBUG(D_receive)
debug_printf("DMARC logging history data for opendmarc reporting%s\n",
- (host_checking || running_in_test_harness) ? " (not really)" : "");
-if (host_checking || running_in_test_harness)
+ (host_checking || f.running_in_test_harness) ? " (not really)" : "");
+if (host_checking || f.running_in_test_harness)
{
DEBUG(D_receive)
debug_printf("DMARC history data for debugging:\n%s", history_buffer);
return DMARC_HIST_OK;
}
+
uschar *
dmarc_exim_expand_query(int what)
{
-if (dmarc_disable_verify || !dmarc_pctx)
+if (f.dmarc_disable_verify || !dmarc_pctx)
return dmarc_exim_expand_defaults(what);
-switch(what)
- {
- case DMARC_VERIFY_STATUS:
- return(dmarc_status);
- default:
- return US"";
- }
+if (what == DMARC_VERIFY_STATUS)
+ return dmarc_status;
+return US"";
}
uschar *
dmarc_exim_expand_defaults(int what)
{
-switch(what)
- {
- case DMARC_VERIFY_STATUS:
- return dmarc_disable_verify ? US"off" : US"none";
- default:
- return US"";
- }
+if (what == DMARC_VERIFY_STATUS)
+ return f.dmarc_disable_verify ? US"off" : US"none";
+return US"";
}
-uschar *
-dmarc_auth_results_header(header_line *from_header, uschar *hostname)
-{
-uschar *hdr_tmp = US"";
-
-/* Allow a server hostname to be passed to this function, but is
- * currently unused */
-if (!hostname)
- hostname = primary_hostname;
-hdr_tmp = string_sprintf("%s %s;", DMARC_AR_HEADER, hostname);
-
-#if 0
-/* I don't think this belongs here, but left it here commented out
- * because it was a lot of work to get working right. */
-if (spf_response != NULL) {
- uschar *dmarc_ar_spf = US"";
- int sr = 0;
- sr = spf_response->result;
- dmarc_ar_spf = (sr == SPF_RESULT_NEUTRAL) ? US"neutral" :
- (sr == SPF_RESULT_PASS) ? US"pass" :
- (sr == SPF_RESULT_FAIL) ? US"fail" :
- (sr == SPF_RESULT_SOFTFAIL) ? US"softfail" :
- US"none";
- hdr_tmp = string_sprintf("%s spf=%s (%s) smtp.mail=%s;",
- hdr_tmp, dmarc_ar_spf_result,
- spf_response->header_comment,
- expand_string(US"$sender_address") );
-}
-#endif
-hdr_tmp = string_sprintf("%s dmarc=%s", hdr_tmp, dmarc_pass_fail);
-if (header_from_sender)
- hdr_tmp = string_sprintf("%s header.from=%s",
- hdr_tmp, header_from_sender);
-return hdr_tmp;
+gstring *
+authres_dmarc(gstring * g)
+{
+if (f.dmarc_has_been_checked)
+ {
+ g = string_append(g, 2, US";\n\tdmarc=", dmarc_pass_fail);
+ if (header_from_sender)
+ g = string_append(g, 2, US" header.from=", header_from_sender);
+ }
+return g;
}
-# endif /* EXPERIMENTAL_SPF */
+# endif /* SUPPORT_SPF */
#endif /* EXPERIMENTAL_DMARC */
/* vi: aw ai sw=2
*/
#ifdef EXPERIMENTAL_DMARC
# include "opendmarc/dmarc.h"
-# ifdef EXPERIMENTAL_SPF
+# ifdef SUPPORT_SPF
# include "spf2/spf.h"
-# endif /* EXPERIMENTAL_SPF */
+# endif /* SUPPORT_SPF */
/* prototypes */
int dmarc_init();
uschar *dmarc_exim_expand_query(int);
uschar *dmarc_exim_expand_defaults(int);
uschar *dmarc_auth_results_header(header_line *,uschar *);
-int dmarc_write_history_file();
+static int dmarc_write_history_file();
#define DMARC_AR_HEADER US"Authentication-Results:"
#define DMARC_VERIFY_STATUS 1
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for interfacing with the DNS. */
{
int j;
for (j = 0; j < 32; j += 4)
- {
- sprintf(CS pp, "%x.", (v6[i] >> j) & 15);
- pp += 2;
- }
+ pp += sprintf(CS pp, "%x.", (v6[i] >> j) & 15);
}
Ustrcpy(pp, "ip6.arpa.");
* Cache a failed DNS lookup result *
*************************************************/
+static void
+dns_fail_tag(uschar * buf, const uschar * name, int dns_type)
+{
+res_state resp = os_get_dns_resolver_res();
+sprintf(CS buf, "%.255s-%s-%lx", name, dns_text_type(dns_type),
+ (unsigned long) resp->options);
+}
+
+
/* We cache failed lookup results so as not to experience timeouts many
times for the same domain. We need to retain the resolver options because they
may change. For successful lookups, we rely on resolver and/or name server
static int
dns_return(const uschar * name, int type, int rc)
{
-res_state resp = os_get_dns_resolver_res();
tree_node *node = store_get_perm(sizeof(tree_node) + 290);
-sprintf(CS node->name, "%.255s-%s-%lx", name, dns_text_type(type),
- (unsigned long) resp->options);
+dns_fail_tag(node->name, name, type);
node->data.val = rc;
(void)tree_insertnode(&tree_dns_fails, node);
return rc;
providing a list of domains for which this is treated as a non-existent
host.
+The dns_answer structure is pretty big; enough to hold a max-sized DNS message
+- so best allocated from fast-release memory. As of writing, all our callers
+use a stack-auto variable.
+
Arguments:
dnsa pointer to dns_answer structure
name name to look up
int rc = -1;
const uschar *save_domain;
#endif
-res_state resp = os_get_dns_resolver_res();
tree_node *previous;
uschar node_name[290];
have many addresses in the same domain. We rely on the resolver and name server
caching for successful lookups. */
-sprintf(CS node_name, "%.255s-%s-%lx", name, dns_text_type(type),
- (unsigned long) resp->options);
+dns_fail_tag(node_name, name, type);
if ((previous = tree_search(tree_dns_fails, node_name)))
{
DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: using cached value %s\n",
name, dns_text_type(type),
- (previous->data.val == DNS_NOMATCH)? "DNS_NOMATCH" :
- (previous->data.val == DNS_NODATA)? "DNS_NODATA" :
- (previous->data.val == DNS_AGAIN)? "DNS_AGAIN" :
- (previous->data.val == DNS_FAIL)? "DNS_FAIL" : "??");
+ previous->data.val == DNS_NOMATCH ? "DNS_NOMATCH" :
+ previous->data.val == DNS_NODATA ? "DNS_NODATA" :
+ previous->data.val == DNS_AGAIN ? "DNS_AGAIN" :
+ previous->data.val == DNS_FAIL ? "DNS_FAIL" : "??");
return previous->data.val;
}
DEBUG(D_dns)
debug_printf("DNS name '%s' utf8 conversion to alabel failed: %s\n", name,
errstr);
- host_find_failed_syntax = TRUE;
+ f.host_find_failed_syntax = TRUE;
return DNS_NOMATCH;
}
name = alabel;
DEBUG(D_dns)
debug_printf("DNS name syntax check failed: %s (%s)\n", name,
dns_text_type(type));
- host_find_failed_syntax = TRUE;
+ f.host_find_failed_syntax = TRUE;
return DNS_NOMATCH;
}
}
(res_search), we call fakens_search(), which recognizes certain special
domains, and interfaces to a fake nameserver for certain special zones. */
-dnsa->answerlen = running_in_test_harness
+dnsa->answerlen = f.running_in_test_harness
? fakens_search(name, type, dnsa->answer, sizeof(dnsa->answer))
: res_search(CCS name, C_IN, type, dnsa->answer, sizeof(dnsa->answer));
/* Look up the given domain name, using the given type. Follow CNAMEs if
necessary, but only so many times. There aren't supposed to be CNAME chains in
the DNS, but you are supposed to cope with them if you find them.
+By default, follow one CNAME since a resolver has been seen, faced with
+an MX request and a CNAME (to an A) but no MX present, returning the CNAME.
The assumption is made that if the resolver gives back records of the
requested type *and* a CNAME, we don't need to make another call to look up
const uschar *orig_name = name;
BOOL secure_so_far = TRUE;
-/* Loop to follow CNAME chains so far, but no further... */
+/* By default, assume the resolver follows CNAME chains (and returns NODATA for
+an unterminated one). If it also does that for a CNAME loop, fine; if it returns
+a CNAME (maybe the last?) whine about it. However, retain the coding for dumb
+resolvers hiding behind a config variable. Loop to follow CNAME chains so far,
+but no further... The testsuite tests the latter case, mostly assuming that the
+former will work. */
-for (i = 0; i < 10; i++)
+for (i = 0; i <= dns_cname_loops; i++)
{
uschar * data;
dns_record *rr, cname_rr, type_rr;
dns_scan dnss;
- int datalen, rc;
+ int rc;
/* DNS lookup failures get passed straight back. */
return DNS_FAIL;
data = store_get(256);
- if ((datalen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
- cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256)) < 0)
+ if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+ cname_rr.data, (DN_EXPAND_ARG4_TYPE)data, 256) < 0)
return DNS_FAIL;
name = data;
assertion field. */
case T_CSA:
{
- uschar *srvname, *namesuff, *tld, *p;
+ uschar *srvname, *namesuff, *tld;
int priority, weight, port;
int limit, rc, i;
BOOL ipv6;
&& (h->rcode == NOERROR || h->rcode == NXDOMAIN)
&& ntohs(h->qdcount) == 1 && ntohs(h->ancount) == 0
&& ntohs(h->nscount) >= 1)
- dnsa->answerlen = MAXPACKET;
+ dnsa->answerlen = sizeof(dnsa->answer);
for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
lookup_info **lookup_list;
int lookup_list_count = 0;
-static int lookup_list_init_done = 0;
-
/* Table of information about all possible authentication mechanisms. All
entries are always present if any mechanism is declared, but the functions are
set to NULL for those that are not compiled into the binary. */
#ifdef AUTH_CRAM_MD5
{
- US"cram_md5", /* lookup name */
- auth_cram_md5_options,
- &auth_cram_md5_options_count,
- &auth_cram_md5_option_defaults,
- sizeof(auth_cram_md5_options_block),
- auth_cram_md5_init, /* init function */
- auth_cram_md5_server, /* server function */
- auth_cram_md5_client, /* client function */
- NULL /* diagnostic function */
+ .driver_name = US"cram_md5", /* lookup name */
+ .options = auth_cram_md5_options,
+ .options_count = &auth_cram_md5_options_count,
+ .options_block = &auth_cram_md5_option_defaults,
+ .options_len = sizeof(auth_cram_md5_options_block),
+ .init = auth_cram_md5_init,
+ .servercode = auth_cram_md5_server,
+ .clientcode = auth_cram_md5_client,
+ .version_report = NULL
},
#endif
#ifdef AUTH_CYRUS_SASL
{
- US"cyrus_sasl", /* lookup name */
- auth_cyrus_sasl_options,
- &auth_cyrus_sasl_options_count,
- &auth_cyrus_sasl_option_defaults,
- sizeof(auth_cyrus_sasl_options_block),
- auth_cyrus_sasl_init, /* init function */
- auth_cyrus_sasl_server, /* server function */
- NULL, /* client function */
- auth_cyrus_sasl_version_report /* diagnostic function */
+ .driver_name = US"cyrus_sasl",
+ .options = auth_cyrus_sasl_options,
+ .options_count = &auth_cyrus_sasl_options_count,
+ .options_block = &auth_cyrus_sasl_option_defaults,
+ .options_len = sizeof(auth_cyrus_sasl_options_block),
+ .init = auth_cyrus_sasl_init,
+ .servercode = auth_cyrus_sasl_server,
+ .clientcode = NULL,
+ .version_report = auth_cyrus_sasl_version_report
},
#endif
#ifdef AUTH_DOVECOT
{
- US"dovecot", /* lookup name */
- auth_dovecot_options,
- &auth_dovecot_options_count,
- &auth_dovecot_option_defaults,
- sizeof(auth_dovecot_options_block),
- auth_dovecot_init, /* init function */
- auth_dovecot_server, /* server function */
- NULL, /* client function */
- NULL /* diagnostic function */
+ .driver_name = US"dovecot",
+ .options = auth_dovecot_options,
+ .options_count = &auth_dovecot_options_count,
+ .options_block = &auth_dovecot_option_defaults,
+ .options_len = sizeof(auth_dovecot_options_block),
+ .init = auth_dovecot_init,
+ .servercode = auth_dovecot_server,
+ .clientcode = NULL,
+ .version_report = NULL
},
#endif
#ifdef AUTH_GSASL
{
- US"gsasl", /* lookup name */
- auth_gsasl_options,
- &auth_gsasl_options_count,
- &auth_gsasl_option_defaults,
- sizeof(auth_gsasl_options_block),
- auth_gsasl_init, /* init function */
- auth_gsasl_server, /* server function */
- NULL, /* client function */
- auth_gsasl_version_report /* diagnostic function */
+ .driver_name = US"gsasl",
+ .options = auth_gsasl_options,
+ .options_count = &auth_gsasl_options_count,
+ .options_block = &auth_gsasl_option_defaults,
+ .options_len = sizeof(auth_gsasl_options_block),
+ .init = auth_gsasl_init,
+ .servercode = auth_gsasl_server,
+ .clientcode = NULL,
+ .version_report = auth_gsasl_version_report
},
#endif
#ifdef AUTH_HEIMDAL_GSSAPI
{
- US"heimdal_gssapi", /* lookup name */
- auth_heimdal_gssapi_options,
- &auth_heimdal_gssapi_options_count,
- &auth_heimdal_gssapi_option_defaults,
- sizeof(auth_heimdal_gssapi_options_block),
- auth_heimdal_gssapi_init, /* init function */
- auth_heimdal_gssapi_server, /* server function */
- NULL, /* client function */
- auth_heimdal_gssapi_version_report /* diagnostic function */
+ .driver_name = US"heimdal_gssapi",
+ .options = auth_heimdal_gssapi_options,
+ .options_count = &auth_heimdal_gssapi_options_count,
+ .options_block = &auth_heimdal_gssapi_option_defaults,
+ .options_len = sizeof(auth_heimdal_gssapi_options_block),
+ .init = auth_heimdal_gssapi_init,
+ .servercode = auth_heimdal_gssapi_server,
+ .clientcode = NULL,
+ .version_report = auth_heimdal_gssapi_version_report
},
#endif
#ifdef AUTH_PLAINTEXT
{
- US"plaintext", /* lookup name */
- auth_plaintext_options,
- &auth_plaintext_options_count,
- &auth_plaintext_option_defaults,
- sizeof(auth_plaintext_options_block),
- auth_plaintext_init, /* init function */
- auth_plaintext_server, /* server function */
- auth_plaintext_client, /* client function */
- NULL /* diagnostic function */
+ .driver_name = US"plaintext",
+ .options = auth_plaintext_options,
+ .options_count = &auth_plaintext_options_count,
+ .options_block = &auth_plaintext_option_defaults,
+ .options_len = sizeof(auth_plaintext_options_block),
+ .init = auth_plaintext_init,
+ .servercode = auth_plaintext_server,
+ .clientcode = auth_plaintext_client,
+ .version_report = NULL
},
#endif
#ifdef AUTH_SPA
{
- US"spa", /* lookup name */
- auth_spa_options,
- &auth_spa_options_count,
- &auth_spa_option_defaults,
- sizeof(auth_spa_options_block),
- auth_spa_init, /* init function */
- auth_spa_server, /* server function */
- auth_spa_client, /* client function */
- NULL /* diagnostic function */
+ .driver_name = US"spa",
+ .options = auth_spa_options,
+ .options_count = &auth_spa_options_count,
+ .options_block = &auth_spa_option_defaults,
+ .options_len = sizeof(auth_spa_options_block),
+ .init = auth_spa_init,
+ .servercode = auth_spa_server,
+ .clientcode = auth_spa_client,
+ .version_report = NULL
},
#endif
#ifdef AUTH_TLS
{
- US"tls", /* lookup name */
- auth_tls_options,
- &auth_tls_options_count,
- &auth_tls_option_defaults,
- sizeof(auth_tls_options_block),
- auth_tls_init, /* init function */
- auth_tls_server, /* server function */
- NULL, /* client function */
- NULL /* diagnostic function */
+ .driver_name = US"tls",
+ .options = auth_tls_options,
+ .options_count = &auth_tls_options_count,
+ .options_block = &auth_tls_option_defaults,
+ .options_len = sizeof(auth_tls_options_block),
+ .init = auth_tls_init,
+ .servercode = auth_tls_server,
+ .clientcode = NULL,
+ .version_report = NULL
},
#endif
-{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL }
+ { .driver_name = US"" } /* end marker */
};
+void
+auth_show_supported(FILE * f)
+{
+auth_info * ai;
+fprintf(f, "Authenticators:");
+for (ai = auths_available; ai->driver_name[0]; ai++)
+ fprintf(f, " %s", ai->driver_name);
+fprintf(f, "\n");
+}
+
/* Tables of information about which routers and transports are included in the
exim binary. */
router_info routers_available[] = {
#ifdef ROUTER_ACCEPT
{
- US"accept",
- accept_router_options,
- &accept_router_options_count,
- &accept_router_option_defaults,
- sizeof(accept_router_options_block),
- accept_router_init,
- accept_router_entry,
- NULL, /* no tidyup entry */
- ri_yestransport
+ .driver_name = US"accept",
+ .options = accept_router_options,
+ .options_count = &accept_router_options_count,
+ .options_block = &accept_router_option_defaults,
+ .options_len = sizeof(accept_router_options_block),
+ .init = accept_router_init,
+ .code = accept_router_entry,
+ .tidyup = NULL, /* no tidyup entry */
+ .ri_flags = ri_yestransport
},
#endif
#ifdef ROUTER_DNSLOOKUP
{
- US"dnslookup",
- dnslookup_router_options,
- &dnslookup_router_options_count,
- &dnslookup_router_option_defaults,
- sizeof(dnslookup_router_options_block),
- dnslookup_router_init,
- dnslookup_router_entry,
- NULL, /* no tidyup entry */
- ri_yestransport
+ .driver_name = US"dnslookup",
+ .options = dnslookup_router_options,
+ .options_count = &dnslookup_router_options_count,
+ .options_block = &dnslookup_router_option_defaults,
+ .options_len = sizeof(dnslookup_router_options_block),
+ .init = dnslookup_router_init,
+ .code = dnslookup_router_entry,
+ .tidyup = NULL, /* no tidyup entry */
+ .ri_flags = ri_yestransport
},
#endif
#ifdef ROUTER_IPLITERAL
{
- US"ipliteral",
- ipliteral_router_options,
- &ipliteral_router_options_count,
- &ipliteral_router_option_defaults,
- sizeof(ipliteral_router_options_block),
- ipliteral_router_init,
- ipliteral_router_entry,
- NULL, /* no tidyup entry */
- ri_yestransport
+ .driver_name = US"ipliteral",
+ .options = ipliteral_router_options,
+ .options_count = &ipliteral_router_options_count,
+ .options_block = &ipliteral_router_option_defaults,
+ .options_len = sizeof(ipliteral_router_options_block),
+ .init = ipliteral_router_init,
+ .code = ipliteral_router_entry,
+ .tidyup = NULL, /* no tidyup entry */
+ .ri_flags = ri_yestransport
},
#endif
#ifdef ROUTER_IPLOOKUP
{
- US"iplookup",
- iplookup_router_options,
- &iplookup_router_options_count,
- &iplookup_router_option_defaults,
- sizeof(iplookup_router_options_block),
- iplookup_router_init,
- iplookup_router_entry,
- NULL, /* no tidyup entry */
- ri_notransport
+ .driver_name = US"iplookup",
+ .options = iplookup_router_options,
+ .options_count = &iplookup_router_options_count,
+ .options_block = &iplookup_router_option_defaults,
+ .options_len = sizeof(iplookup_router_options_block),
+ .init = iplookup_router_init,
+ .code = iplookup_router_entry,
+ .tidyup = NULL, /* no tidyup entry */
+ .ri_flags = ri_notransport
},
#endif
#ifdef ROUTER_MANUALROUTE
{
- US"manualroute",
- manualroute_router_options,
- &manualroute_router_options_count,
- &manualroute_router_option_defaults,
- sizeof(manualroute_router_options_block),
- manualroute_router_init,
- manualroute_router_entry,
- NULL, /* no tidyup entry */
- 0
+ .driver_name = US"manualroute",
+ .options = manualroute_router_options,
+ .options_count = &manualroute_router_options_count,
+ .options_block = &manualroute_router_option_defaults,
+ .options_len = sizeof(manualroute_router_options_block),
+ .init = manualroute_router_init,
+ .code = manualroute_router_entry,
+ .tidyup = NULL, /* no tidyup entry */
+ .ri_flags = 0
},
#endif
#ifdef ROUTER_QUERYPROGRAM
{
- US"queryprogram",
- queryprogram_router_options,
- &queryprogram_router_options_count,
- &queryprogram_router_option_defaults,
- sizeof(queryprogram_router_options_block),
- queryprogram_router_init,
- queryprogram_router_entry,
- NULL, /* no tidyup entry */
- 0
+ .driver_name = US"queryprogram",
+ .options = queryprogram_router_options,
+ .options_count = &queryprogram_router_options_count,
+ .options_block = &queryprogram_router_option_defaults,
+ .options_len = sizeof(queryprogram_router_options_block),
+ .init = queryprogram_router_init,
+ .code = queryprogram_router_entry,
+ .tidyup = NULL, /* no tidyup entry */
+ .ri_flags = 0
},
#endif
#ifdef ROUTER_REDIRECT
{
- US"redirect",
- redirect_router_options,
- &redirect_router_options_count,
- &redirect_router_option_defaults,
- sizeof(redirect_router_options_block),
- redirect_router_init,
- redirect_router_entry,
- NULL, /* no tidyup entry */
- ri_notransport
+ .driver_name = US"redirect",
+ .options = redirect_router_options,
+ .options_count = &redirect_router_options_count,
+ .options_block = &redirect_router_option_defaults,
+ .options_len = sizeof(redirect_router_options_block),
+ .init = redirect_router_init,
+ .code = redirect_router_entry,
+ .tidyup = NULL, /* no tidyup entry */
+ .ri_flags = ri_notransport
},
#endif
-{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, 0 }
+ { US"" }
};
+void
+route_show_supported(FILE * f)
+{
+router_info * rr;
+fprintf(f, "Routers:");
+for (rr = routers_available; rr->driver_name[0]; rr++)
+ fprintf(f, " %s", rr->driver_name);
+fprintf(f, "\n");
+}
+
+
+
transport_info transports_available[] = {
#ifdef TRANSPORT_APPENDFILE
{
- US"appendfile", /* driver name */
- appendfile_transport_options, /* local options table */
- &appendfile_transport_options_count, /* number of entries */
- &appendfile_transport_option_defaults, /* private options defaults */
- sizeof(appendfile_transport_options_block), /* size of private block */
- appendfile_transport_init, /* init entry point */
- appendfile_transport_entry, /* main entry point */
- NULL, /* no tidyup entry */
- NULL, /* no closedown entry */
- TRUE, /* local flag */
+ .driver_name = US"appendfile",
+ .options = appendfile_transport_options,
+ .options_count = &appendfile_transport_options_count,
+ .options_block = &appendfile_transport_option_defaults, /* private options defaults */
+ .options_len = sizeof(appendfile_transport_options_block),
+ .init = appendfile_transport_init,
+ .code = appendfile_transport_entry,
+ .tidyup = NULL,
+ .closedown = NULL,
+ .local = TRUE
},
#endif
#ifdef TRANSPORT_AUTOREPLY
{
- US"autoreply", /* driver name */
- autoreply_transport_options, /* local options table */
- &autoreply_transport_options_count, /* number of entries */
- &autoreply_transport_option_defaults, /* private options defaults */
- sizeof(autoreply_transport_options_block), /* size of private block */
- autoreply_transport_init, /* init entry point */
- autoreply_transport_entry, /* main entry point */
- NULL, /* no tidyup entry */
- NULL, /* no closedown entry */
- TRUE /* local flag */
+ .driver_name = US"autoreply",
+ .options = autoreply_transport_options,
+ .options_count = &autoreply_transport_options_count,
+ .options_block = &autoreply_transport_option_defaults,
+ .options_len = sizeof(autoreply_transport_options_block),
+ .init = autoreply_transport_init,
+ .code = autoreply_transport_entry,
+ .tidyup = NULL,
+ .closedown = NULL,
+ .local = TRUE
},
#endif
#ifdef TRANSPORT_LMTP
{
- US"lmtp", /* driver name */
- lmtp_transport_options, /* local options table */
- &lmtp_transport_options_count, /* number of entries */
- &lmtp_transport_option_defaults, /* private options defaults */
- sizeof(lmtp_transport_options_block), /* size of private block */
- lmtp_transport_init, /* init entry point */
- lmtp_transport_entry, /* main entry point */
- NULL, /* no tidyup entry */
- NULL, /* no closedown entry */
- TRUE /* local flag */
+ .driver_name = US"lmtp",
+ .options = lmtp_transport_options,
+ .options_count = &lmtp_transport_options_count,
+ .options_block = &lmtp_transport_option_defaults,
+ .options_len = sizeof(lmtp_transport_options_block),
+ .init = lmtp_transport_init,
+ .code = lmtp_transport_entry,
+ .tidyup = NULL,
+ .closedown = NULL,
+ .local = TRUE
},
#endif
#ifdef TRANSPORT_PIPE
{
- US"pipe", /* driver name */
- pipe_transport_options, /* local options table */
- &pipe_transport_options_count, /* number of entries */
- &pipe_transport_option_defaults, /* private options defaults */
- sizeof(pipe_transport_options_block), /* size of private block */
- pipe_transport_init, /* init entry point */
- pipe_transport_entry, /* main entry point */
- NULL, /* no tidyup entry */
- NULL, /* no closedown entry */
- TRUE /* local flag */
+ .driver_name = US"pipe",
+ .options = pipe_transport_options,
+ .options_count = &pipe_transport_options_count,
+ .options_block = &pipe_transport_option_defaults,
+ .options_len = sizeof(pipe_transport_options_block),
+ .init = pipe_transport_init,
+ .code = pipe_transport_entry,
+ .tidyup = NULL,
+ .closedown = NULL,
+ .local = TRUE
},
#endif
#ifdef EXPERIMENTAL_QUEUEFILE
{
- US"queuefile", /* driver name */
- queuefile_transport_options, /* local options table */
- &queuefile_transport_options_count, /* number of entries */
- &queuefile_transport_option_defaults, /* private options defaults */
- sizeof(queuefile_transport_options_block), /* size of private block */
- queuefile_transport_init, /* init entry point */
- queuefile_transport_entry, /* main entry point */
- NULL, /* no tidyup entry */
- NULL, /* no closedown entry */
- TRUE /* local flag */
+ .driver_name = US"queuefile",
+ .options = queuefile_transport_options,
+ .options_count = &queuefile_transport_options_count,
+ .options_block = &queuefile_transport_option_defaults,
+ .options_len = sizeof(queuefile_transport_options_block),
+ .init = queuefile_transport_init,
+ .code = queuefile_transport_entry,
+ .tidyup = NULL,
+ .closedown = NULL,
+ .local = TRUE
},
#endif
#ifdef TRANSPORT_SMTP
{
- US"smtp", /* driver name */
- smtp_transport_options, /* local options table */
- &smtp_transport_options_count, /* number of entries */
- &smtp_transport_option_defaults, /* private options defaults */
- sizeof(smtp_transport_options_block), /* size of private block */
- smtp_transport_init, /* init entry point */
- smtp_transport_entry, /* main entry point */
- NULL, /* no tidyup entry */
- smtp_transport_closedown, /* close down passed channel */
- FALSE /* local flag */
+ .driver_name = US"smtp",
+ .options = smtp_transport_options,
+ .options_count = &smtp_transport_options_count,
+ .options_block = &smtp_transport_option_defaults,
+ .options_len = sizeof(smtp_transport_options_block),
+ .init = smtp_transport_init,
+ .code = smtp_transport_entry,
+ .tidyup = NULL,
+ .closedown = smtp_transport_closedown,
+ .local = FALSE
},
#endif
-{ US"", NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, FALSE }
+ { US"" }
};
+void
+transport_show_supported(FILE * f)
+{
+fprintf(f, "Transports:");
+#ifdef TRANSPORT_APPENDFILE
+ fprintf(f, " appendfile");
+ #ifdef SUPPORT_MAILDIR
+ fprintf(f, "/maildir"); /* damn these subclasses */
+ #endif
+ #ifdef SUPPORT_MAILSTORE
+ fprintf(f, "/mailstore");
+ #endif
+ #ifdef SUPPORT_MBX
+ fprintf(f, "/mbx");
+ #endif
+#endif
+#ifdef TRANSPORT_AUTOREPLY
+ fprintf(f, " autoreply");
+#endif
+#ifdef TRANSPORT_LMTP
+ fprintf(f, " lmtp");
+#endif
+#ifdef TRANSPORT_PIPE
+ fprintf(f, " pipe");
+#endif
+#ifdef EXPERIMENTAL_QUEUEFILE
+ fprintf(f, " queuefile");
+#endif
+#ifdef TRANSPORT_SMTP
+ fprintf(f, " smtp");
+#endif
+fprintf(f, "\n");
+}
+
+
+#ifndef MACRO_PREDEF
+
struct lookupmodulestr
{
void *dl;
#if defined(EXPERIMENTAL_LMDB)
extern lookup_module_info lmdb_lookup_module_info;
#endif
-#if defined(EXPERIMENTAL_SPF)
+#if defined(SUPPORT_SPF)
extern lookup_module_info spf_lookup_module_info;
#endif
#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
int moduleerrors = 0;
#endif
struct lookupmodulestr *p;
+ static BOOL lookup_list_init_done = FALSE;
+
if (lookup_list_init_done)
return;
- lookup_list_init_done = 1;
+ lookup_list_init_done = TRUE;
#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
addlookupmodule(NULL, &cdb_lookup_module_info);
addlookupmodule(NULL, &lmdb_lookup_module_info);
#endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
addlookupmodule(NULL, &spf_lookup_module_info);
#endif
lookupmodules = NULL;
}
+#endif /*!MACRO_PREDEF*/
/* End of drtables.c */
/* We don't have the full Exim headers dragged in, but this function
is used for debugging output. */
-extern int string_vformat(char *, int, char *, va_list);
+extern gstring * string_vformat(gstring *, BOOL, const char *, va_list);
/*************************************************
debug_printf(char *format, ...)
{
va_list ap;
-char buffer[1024];
+gstring * g = string_get(1024);
+void * reset_point = g;
va_start(ap, format);
-if (!string_vformat(buffer, sizeof(buffer), format, ap))
+if (!string_vformat(g, FALSE, format, ap))
{
- char *s = "**** debug string overflowed buffer ****\n";
- char *p = buffer + (int)strlen(buffer);
- int maxlen = sizeof(buffer) - (int)strlen(s) - 3;
- if (p > buffer + maxlen) p = buffer + maxlen;
- if (p > buffer && p[-1] != '\n') *p++ = '\n';
+ char * s = "**** debug string overflowed buffer ****\n";
+ char * p = CS g->s + g->ptr;
+ int maxlen = g->size - (int)strlen(s) - 3;
+ if (p > g->s + maxlen) p = g->s + maxlen;
+ if (p > g->s && p[-1] != '\n') *p++ = '\n';
strcpy(p, s);
}
-fprintf(stderr, "%s", buffer);
+fprintf(stderr, "%s", string_from_gstring(g));
fflush(stderr);
+store_reset(reset_point);
va_end(ap);
}
sigalrm_handler(int sig)
{
sig = sig; /* Keep picky compilers happy */
-sigalrm_seen = 1;
+sigalrm_seen = TRUE;
}
-k) keep=$2
shift
;;
+ --version)
+ echo "`basename $0`: $0"
+ echo "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION"
+ exit 0
+ ;;
*) echo "** exicyclog: unknown option $1"
exit 1
;;
use strict;
BEGIN { pop @INC if $INC[-1] eq '.' };
-# Copyright (c) 2007-2015 University of Cambridge.
+use Pod::Usage;
+use Getopt::Long;
+use File::Basename;
+
+# Copyright (c) 2007-2017 University of Cambridge.
# See the file NOTICE for conditions of use and distribution.
# Except when they appear in comments, the following placeholders in this
# Typical run time acceleration: 4 times
-use Getopt::Std qw(getopts);
use POSIX qw(mktime);
sub seconds {
my($year,$month,$day,$hour,$min,$sec,$tzs,$tzh,$tzm) =
- $_[0] =~ /^(\d{4})-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)(?>\s([+-])(\d\d)(\d\d))?/o;
+ $_[0] =~ /^(\d{4})-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)(?:.\d+)?(?>\s([+-])(\d\d)(\d\d))?/o;
my $seconds = mktime $sec, $min, $hour, $day, $month - 1, $year - 1900;
# This subroutine processes a single line (in $_) from a log file. Program
# defensively against short lines finding their way into the log.
-my (%saved, %id_list, $pattern, $queue_time, $insensitive, $invert);
+my (%saved, %id_list, $pattern);
+
+my $queue_time = -1;
+my $insensitive = 1;
+my $invert = 0;
+my $related = 0;
+my $use_pager = 1;
+my $literal = 0;
+
# If using "related" option, have to track extra message IDs
-my $related;
my $related_re='';
my @Mids = ();
if (!/^\d{4}-/o) { $_ =~ s/^.*? exim\b.*?: //o; }
return unless
- my($date,$id) = /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d (?:[+-]\d{4} )?)(?:\[\d+\] )?(\w{6}\-\w{6}\-\w{2})?/o;
+ my($date,$id) = /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?:\.\d+)? (?:[+-]\d{4} )?)(?:\[\d+\] )?(\w{6}\-\w{6}\-\w{2})?/o;
# Handle the case when the log line belongs to a specific message. We save
# lines for specific messages until the message is complete. Then either print
if (index($_, 'Completed') != -1 ||
index($_, 'SMTP data timeout') != -1 ||
(index($_, 'rejected') != -1 &&
- /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d (?:[+-]\d{4} )?)(?:\[\d+\] )?\w{6}\-\w{6}\-\w{2} rejected/o))
+ /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(?:\.\d+)? (?:[+-]\d{4} )?)(?:\[\d+\] )?\w{6}\-\w{6}\-\w{2} rejected/o))
{
if ($queue_time != -1 &&
$saved{$id} =~ /^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d ([+-]\d{4} )?)/o)
# which is an additional condition. The -M flag will also display "related"
# loglines (msgid from matched lines is searched in following lines).
-getopts('Ilvt:M',\my %args);
-$queue_time = $args{'t'}? $args{'t'} : -1;
-$insensitive = $args{'I'}? 0 : 1;
-$invert = $args{'v'}? 1 : 0;
-$related = $args{'M'}? 1 : 0;
-
-die "usage: exigrep [-I] [-l] [-M] [-t <seconds>] [-v] <pattern> [<log file>]...\n"
- if ($#ARGV < 0);
+GetOptions(
+ 'I|sensitive' => sub { $insensitive = 0 },
+ 'l|literal' => \$literal,
+ 'M|related' => \$related,
+ 't|queue-time=i' => \$queue_time,
+ 'pager!' => \$use_pager,
+ 'v|invert' => \$invert,
+ 'h|help' => sub { pod2usage(-exit => 0, -verbose => 1) },
+ 'm|man' => sub {
+ pod2usage(
+ -exit => 0,
+ -verbose => 2,
+ -noperldoc => system('perldoc -V 2>/dev/null >&2')
+ );
+ },
+ 'version' => sub {
+ print basename($0) . ": $0\n",
+ "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+ "perl(runtime): $]\n";
+ exit 0;
+ },
+) and @ARGV or pod2usage;
$pattern = shift @ARGV;
-$pattern = quotemeta $pattern if $args{l};
+$pattern = quotemeta $pattern if $literal;
+# Start a pager if output goes to a terminal
+if (-t 1 and $use_pager)
+ {
+ # for perl >= v5.10.x: foreach ($ENV{PAGER}//(), 'less', 'more')
+ foreach (defined $ENV{PAGER} ? $ENV{PAGER} : (), 'less', 'more')
+ {
+ local $ENV{LESS} .= ' --no-init --quit-if-one-screen';
+ open(my $pager, '|-', $_) or next;
+ select $pager;
+ last;
+ }
+ }
# If file arguments are given, open each one and process according as it is
# is compressed or not.
print "+++ $_ has not completed +++\n$saved{$_}\n";
}
-# End of exigrep
+__END__
+
+=head1 NAME
+
+exigrep - search Exim's main log
+
+=head1 SYNOPSIS
+
+B<exigrep> [options] pattern [log] ...
+
+=head1 DESCRIPTION
+
+The B<exigrep> utility is a Perl script that searches one or more main log
+files for entries that match a given pattern. When it finds a match,
+it extracts all the log entries for the relevant message, not just
+those that match the pattern. Thus, B<exigrep> can extract complete log
+entries for a given message, or all mail for a given user, or for a
+given host, for example.
+
+If no file names are given on the command line, the standard input is read.
+
+For known file extensions indicating compression (F<.gz>, F<.bz2>, F<.xz>, and F<.lzma>)
+a suitable de-compressor is used, if available.
+
+The output is sent through a pager if a terminal is connected to STDOUT. As
+pager are considered: C<$ENV{PAGER}>, C<less>, C<more>.
+
+=head1 OPTIONS
+
+=over
+
+=item B<-l>|B<--literal>
+
+This means 'literal', that is, treat all characters in the
+pattern as standing for themselves. Otherwise the pattern must be a
+Perl regular expression. The pattern match is case-insensitive.
+
+=item B<-t>|B<--queue-time> I<seconds>
+
+Limit the output to messages that spent at least I<seconds> in the
+queue.
+
+=item B<-I>|B<--sensitive>
+
+Do a case sensitive search.
+
+=item B<-v>|B<--invert>
+
+Invert the meaning of the search pattern. That is, print message log
+entries that are not related to that pattern.
+
+=item B<-M>|B<--related>
+
+Search for related messages too.
+
+=item B<--no-pager>
+
+Do not use a pager, even if STDOUT is connected to a terminal.
+
+=item B<-h>|B<--help>
+
+Print a short reference help. For more detailed help try L<exigrep(8)>,
+or C<exigrep -m>.
+
+=item B<-m>|B<--man>
+
+Print this manual page of B<exigrep>.
+
+=back
+
+=head1 SEE ALSO
+
+L<exim(8)>, L<perlre(1)>, L<Exim|http://exim.org/>
+
+=head1 AUTHOR
+
+This manual page was stitched together from spec.txt by Andreas Metzler L<ametzler at downhill.at.eu.org>
+and updated by Heiko Schlittermann L<hs@schlittermann.de>.
+
+=cut
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "exim.h"
-#ifdef __GLIBC__
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
# include <gnu/libc-version.h>
#endif
int ovector[3*(EXPAND_MAXN+1)];
uschar * s = string_copy(subject); /* de-constifying */
int n = pcre_exec(re, NULL, CS s, Ustrlen(s), 0,
- PCRE_EOPT | options, ovector, sizeof(ovector)/sizeof(int));
+ PCRE_EOPT | options, ovector, nelem(ovector));
BOOL yield = n >= 0;
if (n == 0) n = EXPAND_MAXN + 1;
if (yield)
{
int nn;
- expand_nmax = (setup < 0)? 0 : setup + 1;
- for (nn = (setup < 0)? 0 : 2; nn < n*2; nn += 2)
+ expand_nmax = setup < 0 ? 0 : setup + 1;
+ for (nn = setup < 0 ? 0 : 2; nn < n*2; nn += 2)
{
expand_nstring[expand_nmax] = s + ovector[nn];
expand_nlength[expand_nmax++] = ovector[nn+1] - ovector[nn];
void
set_process_info(const char *format, ...)
{
-int len = sprintf(CS process_info, "%5d ", (int)getpid());
+gstring gs = { .size = PROCESS_INFO_SIZE - 2, .ptr = 0, .s = process_info };
+gstring * g;
+int len;
va_list ap;
+
+g = string_fmt_append(&gs, "%5d ", (int)getpid());
+len = g->ptr;
va_start(ap, format);
-if (!string_vformat(process_info + len, PROCESS_INFO_SIZE - len - 2, format, ap))
- Ustrcpy(process_info + len, "**** string overflowed buffer ****");
-len = Ustrlen(process_info);
-process_info[len+0] = '\n';
-process_info[len+1] = '\0';
-process_info_len = len + 1;
+if (!string_vformat(g, FALSE, format, ap))
+ {
+ gs.ptr = len;
+ g = string_cat(&gs, US"**** string overflowed buffer ****");
+ }
+g = string_catn(g, US"\n", 1);
+string_from_gstring(g);
+process_info_len = g->ptr;
DEBUG(D_process_info) debug_printf("set_process_info: %s", process_info);
va_end(ap);
}
+/***********************************************
+* Handler for SIGTERM *
+***********************************************/
+static void
+term_handler(int sig)
+{
+ exit(1);
+}
/*************************************************
os_restarting_signal(sig, usr1_handler);
-fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE);
-if (fd < 0)
+if ((fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE)) < 0)
{
/* If we are already running as the Exim user, try to create it in the
current process (assuming spool_directory exists). Otherwise, if we are
Returns: -1, 0, or +1
*/
-int
+static int
exim_tvcmp(struct timeval *t1, struct timeval *t2)
{
if (t1->tv_sec > t2->tv_sec) return +1;
DEBUG(D_transport|D_receive)
{
- if (!running_in_test_harness)
+ if (!f.running_in_test_harness)
{
debug_printf("tick check: " TIME_T_FMT ".%06lu " TIME_T_FMT ".%06lu\n",
then_tv->tv_sec, (long) then_tv->tv_usec,
{
if (smtp_input)
{
- #ifdef SUPPORT_TLS
- tls_close(TRUE, FALSE); /* Shut down the TLS library */
- #endif
+#ifdef SUPPORT_TLS
+ tls_close(NULL, TLS_NO_SHUTDOWN); /* Shut down the TLS library */
+#endif
(void)close(fileno(smtp_in));
(void)close(fileno(smtp_out));
smtp_in = NULL;
if ((debug_selector & D_resolver) == 0) (void)close(1); /* stdout */
if (debug_selector == 0) /* stderr */
{
- if (!synchronous_delivery)
+ if (!f.synchronous_delivery)
{
(void)close(2);
log_stderr = NULL;
if (igflag)
{
struct passwd *pw = getpwuid(uid);
- if (pw != NULL)
- {
- if (initgroups(pw->pw_name, gid) != 0)
- log_write(0,LOG_MAIN|LOG_PANIC_DIE,"initgroups failed for uid=%ld: %s",
- (long int)uid, strerror(errno));
- }
- else log_write(0, LOG_MAIN|LOG_PANIC_DIE, "cannot run initgroups(): "
- "no passwd entry for uid=%ld", (long int)uid);
+ if (!pw)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "cannot run initgroups(): "
+ "no passwd entry for uid=%ld", (long int)uid);
+
+ if (initgroups(pw->pw_name, gid) != 0)
+ log_write(0,LOG_MAIN|LOG_PANIC_DIE,"initgroups failed for uid=%ld: %s",
+ (long int)uid, strerror(errno));
}
if (setgid(gid) < 0 || setuid(uid) < 0)
- {
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to set gid=%ld or uid=%ld "
"(euid=%ld): %s", (long int)gid, (long int)uid, (long int)euid, msg);
- }
}
/* Debugging output included uid/gid and all groups */
DEBUG(D_uid)
{
int group_count, save_errno;
- gid_t group_list[NGROUPS_MAX];
+ gid_t group_list[EXIM_GROUPLIST_SIZE];
debug_printf("changed uid/gid: %s\n uid=%ld gid=%ld pid=%ld\n", msg,
(long int)geteuid(), (long int)getegid(), (long int)getpid());
- group_count = getgroups(NGROUPS_MAX, group_list);
+ group_count = getgroups(nelem(group_list), group_list);
save_errno = errno;
debug_printf(" auxiliary group list:");
if (group_count > 0)
*/
void
-exim_exit(int rc)
+exim_exit(int rc, const uschar * process)
{
search_tidyup();
DEBUG(D_any)
- debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d terminating with rc=%d "
- ">>>>>>>>>>>>>>>>\n", (int)getpid(), rc);
+ debug_printf(">>>>>>>>>>>>>>>> Exim pid=%d %s%s%sterminating with rc=%d "
+ ">>>>>>>>>>>>>>>>\n", (int)getpid(),
+ process ? "(" : "", process, process ? ") " : "", rc);
exit(rc);
}
+/* Print error string, then die */
+static void
+exim_fail(const char * fmt, ...)
+{
+va_list ap;
+va_start(ap, fmt);
+vfprintf(stderr, fmt, ap);
+exit(EXIT_FAILURE);
+}
+
+
/*************************************************
* Extract port from host address *
{
int port = host_address_extract_port(address);
if (string_is_ip_address(address, NULL) == 0)
- {
- fprintf(stderr, "exim abandoned: \"%s\" is not an IP address\n", address);
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim abandoned: \"%s\" is not an IP address\n", address);
return port;
}
* Show supported features *
*************************************************/
-/* This function is called for -bV/--version and for -d to output the optional
-features of the current Exim binary.
-
-Arguments: a FILE for printing
-Returns: nothing
-*/
-
static void
-show_whats_supported(FILE *f)
+show_db_version(FILE * f)
{
- auth_info *authi;
-
#ifdef DB_VERSION_STRING
-fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING);
+DEBUG(D_any)
+ {
+ fprintf(f, "Library version: BDB: Compile: %s\n", DB_VERSION_STRING);
+ fprintf(f, " Runtime: %s\n",
+ db_version(NULL, NULL, NULL));
+ }
+else
+ fprintf(f, "Berkeley DB: %s\n", DB_VERSION_STRING);
+
#elif defined(BTREEVERSION) && defined(HASHVERSION)
#ifdef USE_DB
fprintf(f, "Probably Berkeley DB version 1.8x (native mode)\n");
#else
fprintf(f, "Probably Berkeley DB version 1.8x (compatibility mode)\n");
#endif
+
#elif defined(_DBM_RDONLY) || defined(dbm_dirfno)
fprintf(f, "Probably ndbm\n");
#elif defined(USE_TDB)
fprintf(f, "Probably GDBM (compatibility mode)\n");
#endif
#endif
+}
+
-fprintf(f, "Support for:");
+/* This function is called for -bV/--version and for -d to output the optional
+features of the current Exim binary.
+
+Arguments: a FILE for printing
+Returns: nothing
+*/
+
+static void
+show_whats_supported(FILE * fp)
+{
+auth_info * authi;
+
+DEBUG(D_any) {} else show_db_version(fp);
+
+fprintf(fp, "Support for:");
#ifdef SUPPORT_CRYPTEQ
- fprintf(f, " crypteq");
+ fprintf(fp, " crypteq");
#endif
#if HAVE_ICONV
- fprintf(f, " iconv()");
+ fprintf(fp, " iconv()");
#endif
#if HAVE_IPV6
- fprintf(f, " IPv6");
+ fprintf(fp, " IPv6");
#endif
#ifdef HAVE_SETCLASSRESOURCES
- fprintf(f, " use_setclassresources");
+ fprintf(fp, " use_setclassresources");
#endif
#ifdef SUPPORT_PAM
- fprintf(f, " PAM");
+ fprintf(fp, " PAM");
#endif
#ifdef EXIM_PERL
- fprintf(f, " Perl");
+ fprintf(fp, " Perl");
#endif
#ifdef EXPAND_DLFUNC
- fprintf(f, " Expand_dlfunc");
+ fprintf(fp, " Expand_dlfunc");
#endif
#ifdef USE_TCP_WRAPPERS
- fprintf(f, " TCPwrappers");
+ fprintf(fp, " TCPwrappers");
#endif
#ifdef SUPPORT_TLS
- #ifdef USE_GNUTLS
- fprintf(f, " GnuTLS");
- #else
- fprintf(f, " OpenSSL");
- #endif
+# ifdef USE_GNUTLS
+ fprintf(fp, " GnuTLS");
+# else
+ fprintf(fp, " OpenSSL");
+# endif
#endif
#ifdef SUPPORT_TRANSLATE_IP_ADDRESS
- fprintf(f, " translate_ip_address");
+ fprintf(fp, " translate_ip_address");
#endif
#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
- fprintf(f, " move_frozen_messages");
+ fprintf(fp, " move_frozen_messages");
#endif
#ifdef WITH_CONTENT_SCAN
- fprintf(f, " Content_Scanning");
+ fprintf(fp, " Content_Scanning");
+#endif
+#ifdef SUPPORT_DANE
+ fprintf(fp, " DANE");
#endif
#ifndef DISABLE_DKIM
- fprintf(f, " DKIM");
+ fprintf(fp, " DKIM");
#endif
#ifndef DISABLE_DNSSEC
- fprintf(f, " DNSSEC");
+ fprintf(fp, " DNSSEC");
#endif
#ifndef DISABLE_EVENT
- fprintf(f, " Event");
+ fprintf(fp, " Event");
#endif
#ifdef SUPPORT_I18N
- fprintf(f, " I18N");
+ fprintf(fp, " I18N");
#endif
#ifndef DISABLE_OCSP
- fprintf(f, " OCSP");
+ fprintf(fp, " OCSP");
#endif
#ifndef DISABLE_PRDR
- fprintf(f, " PRDR");
+ fprintf(fp, " PRDR");
#endif
#ifdef SUPPORT_PROXY
- fprintf(f, " PROXY");
+ fprintf(fp, " PROXY");
#endif
#ifdef SUPPORT_SOCKS
- fprintf(f, " SOCKS");
+ fprintf(fp, " SOCKS");
+#endif
+#ifdef SUPPORT_SPF
+ fprintf(fp, " SPF");
#endif
#ifdef TCP_FASTOPEN
- fprintf(f, " TCP_Fast_Open");
+ deliver_init();
+ if (f.tcp_fastopen_ok) fprintf(fp, " TCP_Fast_Open");
#endif
#ifdef EXPERIMENTAL_LMDB
- fprintf(f, " Experimental_LMDB");
+ fprintf(fp, " Experimental_LMDB");
#endif
#ifdef EXPERIMENTAL_QUEUEFILE
- fprintf(f, " Experimental_QUEUEFILE");
-#endif
-#ifdef EXPERIMENTAL_SPF
- fprintf(f, " Experimental_SPF");
+ fprintf(fp, " Experimental_QUEUEFILE");
#endif
#ifdef EXPERIMENTAL_SRS
- fprintf(f, " Experimental_SRS");
+ fprintf(fp, " Experimental_SRS");
#endif
-#ifdef EXPERIMENTAL_BRIGHTMAIL
- fprintf(f, " Experimental_Brightmail");
+#ifdef EXPERIMENTAL_ARC
+ fprintf(fp, " Experimental_ARC");
#endif
-#ifdef EXPERIMENTAL_DANE
- fprintf(f, " Experimental_DANE");
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ fprintf(fp, " Experimental_Brightmail");
#endif
#ifdef EXPERIMENTAL_DCC
- fprintf(f, " Experimental_DCC");
+ fprintf(fp, " Experimental_DCC");
#endif
#ifdef EXPERIMENTAL_DMARC
- fprintf(f, " Experimental_DMARC");
+ fprintf(fp, " Experimental_DMARC");
#endif
#ifdef EXPERIMENTAL_DSN_INFO
- fprintf(f, " Experimental_DSN_info");
+ fprintf(fp, " Experimental_DSN_info");
+#endif
+#ifdef EXPERIMENTAL_REQUIRETLS
+ fprintf(fp, " Experimental_REQUIRETLS");
#endif
-fprintf(f, "\n");
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ fprintf(fp, " Experimental_PIPE_CONNECT");
+#endif
+fprintf(fp, "\n");
-fprintf(f, "Lookups (built-in):");
+fprintf(fp, "Lookups (built-in):");
#if defined(LOOKUP_LSEARCH) && LOOKUP_LSEARCH!=2
- fprintf(f, " lsearch wildlsearch nwildlsearch iplsearch");
+ fprintf(fp, " lsearch wildlsearch nwildlsearch iplsearch");
#endif
#if defined(LOOKUP_CDB) && LOOKUP_CDB!=2
- fprintf(f, " cdb");
+ fprintf(fp, " cdb");
#endif
#if defined(LOOKUP_DBM) && LOOKUP_DBM!=2
- fprintf(f, " dbm dbmjz dbmnz");
+ fprintf(fp, " dbm dbmjz dbmnz");
#endif
#if defined(LOOKUP_DNSDB) && LOOKUP_DNSDB!=2
- fprintf(f, " dnsdb");
+ fprintf(fp, " dnsdb");
#endif
#if defined(LOOKUP_DSEARCH) && LOOKUP_DSEARCH!=2
- fprintf(f, " dsearch");
+ fprintf(fp, " dsearch");
#endif
#if defined(LOOKUP_IBASE) && LOOKUP_IBASE!=2
- fprintf(f, " ibase");
+ fprintf(fp, " ibase");
#endif
#if defined(LOOKUP_LDAP) && LOOKUP_LDAP!=2
- fprintf(f, " ldap ldapdn ldapm");
+ fprintf(fp, " ldap ldapdn ldapm");
#endif
#ifdef EXPERIMENTAL_LMDB
- fprintf(f, " lmdb");
+ fprintf(fp, " lmdb");
#endif
#if defined(LOOKUP_MYSQL) && LOOKUP_MYSQL!=2
- fprintf(f, " mysql");
+ fprintf(fp, " mysql");
#endif
#if defined(LOOKUP_NIS) && LOOKUP_NIS!=2
- fprintf(f, " nis nis0");
+ fprintf(fp, " nis nis0");
#endif
#if defined(LOOKUP_NISPLUS) && LOOKUP_NISPLUS!=2
- fprintf(f, " nisplus");
+ fprintf(fp, " nisplus");
#endif
#if defined(LOOKUP_ORACLE) && LOOKUP_ORACLE!=2
- fprintf(f, " oracle");
+ fprintf(fp, " oracle");
#endif
#if defined(LOOKUP_PASSWD) && LOOKUP_PASSWD!=2
- fprintf(f, " passwd");
+ fprintf(fp, " passwd");
#endif
#if defined(LOOKUP_PGSQL) && LOOKUP_PGSQL!=2
- fprintf(f, " pgsql");
+ fprintf(fp, " pgsql");
#endif
#if defined(LOOKUP_REDIS) && LOOKUP_REDIS!=2
- fprintf(f, " redis");
+ fprintf(fp, " redis");
#endif
#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
- fprintf(f, " sqlite");
+ fprintf(fp, " sqlite");
#endif
#if defined(LOOKUP_TESTDB) && LOOKUP_TESTDB!=2
- fprintf(f, " testdb");
+ fprintf(fp, " testdb");
#endif
#if defined(LOOKUP_WHOSON) && LOOKUP_WHOSON!=2
- fprintf(f, " whoson");
+ fprintf(fp, " whoson");
#endif
-fprintf(f, "\n");
+fprintf(fp, "\n");
-fprintf(f, "Authenticators:");
-#ifdef AUTH_CRAM_MD5
- fprintf(f, " cram_md5");
-#endif
-#ifdef AUTH_CYRUS_SASL
- fprintf(f, " cyrus_sasl");
-#endif
-#ifdef AUTH_DOVECOT
- fprintf(f, " dovecot");
-#endif
-#ifdef AUTH_GSASL
- fprintf(f, " gsasl");
-#endif
-#ifdef AUTH_HEIMDAL_GSSAPI
- fprintf(f, " heimdal_gssapi");
-#endif
-#ifdef AUTH_PLAINTEXT
- fprintf(f, " plaintext");
-#endif
-#ifdef AUTH_SPA
- fprintf(f, " spa");
-#endif
-#ifdef AUTH_TLS
- fprintf(f, " tls");
-#endif
-fprintf(f, "\n");
+auth_show_supported(fp);
+route_show_supported(fp);
+transport_show_supported(fp);
-fprintf(f, "Routers:");
-#ifdef ROUTER_ACCEPT
- fprintf(f, " accept");
-#endif
-#ifdef ROUTER_DNSLOOKUP
- fprintf(f, " dnslookup");
-#endif
-#ifdef ROUTER_IPLITERAL
- fprintf(f, " ipliteral");
-#endif
-#ifdef ROUTER_IPLOOKUP
- fprintf(f, " iplookup");
-#endif
-#ifdef ROUTER_MANUALROUTE
- fprintf(f, " manualroute");
-#endif
-#ifdef ROUTER_QUERYPROGRAM
- fprintf(f, " queryprogram");
-#endif
-#ifdef ROUTER_REDIRECT
- fprintf(f, " redirect");
-#endif
-fprintf(f, "\n");
-
-fprintf(f, "Transports:");
-#ifdef TRANSPORT_APPENDFILE
- fprintf(f, " appendfile");
- #ifdef SUPPORT_MAILDIR
- fprintf(f, "/maildir");
- #endif
- #ifdef SUPPORT_MAILSTORE
- fprintf(f, "/mailstore");
- #endif
- #ifdef SUPPORT_MBX
- fprintf(f, "/mbx");
- #endif
-#endif
-#ifdef TRANSPORT_AUTOREPLY
- fprintf(f, " autoreply");
-#endif
-#ifdef TRANSPORT_LMTP
- fprintf(f, " lmtp");
-#endif
-#ifdef TRANSPORT_PIPE
- fprintf(f, " pipe");
-#endif
-#ifdef EXPERIMENTAL_QUEUEFILE
- fprintf(f, " queuefile");
-#endif
-#ifdef TRANSPORT_SMTP
- fprintf(f, " smtp");
+#ifdef WITH_CONTENT_SCAN
+malware_show_supported(fp);
#endif
-fprintf(f, "\n");
if (fixed_never_users[0] > 0)
{
int i;
- fprintf(f, "Fixed never_users: ");
+ fprintf(fp, "Fixed never_users: ");
for (i = 1; i <= (int)fixed_never_users[0] - 1; i++)
- fprintf(f, "%d:", (unsigned int)fixed_never_users[i]);
- fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
+ fprintf(fp, "%d:", (unsigned int)fixed_never_users[i]);
+ fprintf(fp, "%d\n", (unsigned int)fixed_never_users[i]);
}
-fprintf(f, "Configure owner: %d:%d\n", config_uid, config_gid);
+fprintf(fp, "Configure owner: %d:%d\n", config_uid, config_gid);
-fprintf(f, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
+fprintf(fp, "Size of off_t: " SIZE_T_FMT "\n", sizeof(off_t));
/* Everything else is details which are only worth reporting when debugging.
Perhaps the tls_version_report should move into this too. */
/* clang defines __GNUC__ (at least, for me) so test for it first */
#if defined(__clang__)
- fprintf(f, "Compiler: CLang [%s]\n", __clang_version__);
+ fprintf(fp, "Compiler: CLang [%s]\n", __clang_version__);
#elif defined(__GNUC__)
- fprintf(f, "Compiler: GCC [%s]\n",
+ fprintf(fp, "Compiler: GCC [%s]\n",
# ifdef __VERSION__
__VERSION__
# else
# endif
);
#else
- fprintf(f, "Compiler: <unknown>\n");
+ fprintf(fp, "Compiler: <unknown>\n");
#endif
-#ifdef __GLIBC__
- fprintf(f, "Library version: Glibc: Compile: %d.%d\n",
+#if defined(__GLIBC__) && !defined(__UCLIBC__)
+ fprintf(fp, "Library version: Glibc: Compile: %d.%d\n",
__GLIBC__, __GLIBC_MINOR__);
if (__GLIBC_PREREQ(2, 1))
- fprintf(f, " Runtime: %s\n",
+ fprintf(fp, " Runtime: %s\n",
gnu_get_libc_version());
#endif
+show_db_version(fp);
+
#ifdef SUPPORT_TLS
- tls_version_report(f);
+ tls_version_report(fp);
#endif
#ifdef SUPPORT_I18N
- utf8_version_report(f);
+ utf8_version_report(fp);
#endif
for (authi = auths_available; *authi->driver_name != '\0'; ++authi)
if (authi->version_report)
- (*authi->version_report)(f);
+ (*authi->version_report)(fp);
/* PCRE_PRERELEASE is either defined and empty or a bare sequence of
characters; unless it's an ancient version of PCRE in which case it
#endif
#define QUOTE(X) #X
#define EXPAND_AND_QUOTE(X) QUOTE(X)
- fprintf(f, "Library version: PCRE: Compile: %d.%d%s\n"
+ fprintf(fp, "Library version: PCRE: Compile: %d.%d%s\n"
" Runtime: %s\n",
PCRE_MAJOR, PCRE_MINOR,
EXPAND_AND_QUOTE(PCRE_PRERELEASE) "",
init_lookup_list();
for (i = 0; i < lookup_list_count; i++)
if (lookup_list[i]->version_report)
- lookup_list[i]->version_report(f);
+ lookup_list[i]->version_report(fp);
#ifdef WHITELIST_D_MACROS
- fprintf(f, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
+ fprintf(fp, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
#else
- fprintf(f, "WHITELIST_D_MACROS unset\n");
+ fprintf(fp, "WHITELIST_D_MACROS unset\n");
#endif
#ifdef TRUSTED_CONFIG_LIST
- fprintf(f, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
+ fprintf(fp, "TRUSTED_CONFIG_LIST: \"%s\"\n", TRUSTED_CONFIG_LIST);
#else
- fprintf(f, "TRUSTED_CONFIG_LIST unset\n");
+ fprintf(fp, "TRUSTED_CONFIG_LIST unset\n");
#endif
} while (0);
"If the string is not recognised, you'll get this help (on stderr).\n"
"\n"
" exim -bI:help this information\n"
-" exim -bI:dscp dscp value keywords known\n"
-" exim -bI:sieve list of supported sieve extensions, one per line.\n"
+" exim -bI:dscp list of known dscp value keywords\n"
+" exim -bI:sieve list of supported sieve extensions\n"
);
return;
case CMDINFO_SIEVE:
local_part_quote(uschar *lpart)
{
BOOL needs_quote = FALSE;
-int size, ptr;
-uschar *yield;
+gstring * g;
uschar *t;
for (t = lpart; !needs_quote && *t != 0; t++)
if (!needs_quote) return lpart;
-size = ptr = 0;
-yield = string_catn(NULL, &size, &ptr, US"\"", 1);
+g = string_catn(NULL, US"\"", 1);
for (;;)
{
uschar *nq = US Ustrpbrk(lpart, "\\\"");
if (nq == NULL)
{
- yield = string_cat(yield, &size, &ptr, lpart);
+ g = string_cat(g, lpart);
break;
}
- yield = string_catn(yield, &size, &ptr, lpart, nq - lpart);
- yield = string_catn(yield, &size, &ptr, US"\\", 1);
- yield = string_catn(yield, &size, &ptr, nq, 1);
+ g = string_catn(g, lpart, nq - lpart);
+ g = string_catn(g, US"\\", 1);
+ g = string_catn(g, nq, 1);
lpart = nq + 1;
}
-yield = string_catn(yield, &size, &ptr, US"\"", 1);
-yield[ptr] = 0;
-return yield;
+g = string_catn(g, US"\"", 1);
+return string_from_gstring(g);
}
get_stdinput(char *(*fn_readline)(const char *), void(*fn_addhist)(const char *))
{
int i;
-int size = 0;
-int ptr = 0;
-uschar *yield = NULL;
+gstring * g = NULL;
-if (fn_readline == NULL) { printf("> "); fflush(stdout); }
+if (!fn_readline) { printf("> "); fflush(stdout); }
for (i = 0;; i++)
{
while (p < ss && isspace(*p)) p++; /* leading space after cont */
}
- yield = string_catn(yield, &size, &ptr, p, ss - p);
+ g = string_catn(g, p, ss - p);
#ifdef USE_READLINE
- if (fn_readline != NULL) free(readline_line);
+ if (fn_readline) free(readline_line);
#endif
- /* yield can only be NULL if ss==p */
- if (ss == p || yield[ptr-1] != '\\')
- {
- if (yield) yield[ptr] = 0;
+ /* g can only be NULL if ss==p */
+ if (ss == p || g->s[g->ptr-1] != '\\')
break;
- }
- yield[--ptr] = 0;
+
+ --g->ptr;
+ (void) string_from_gstring(g);
}
-if (yield == NULL) printf("\n");
-return yield;
+if (!g) printf("\n");
+return string_from_gstring(g);
}
/* Handle specific program invocation variants */
if (Ustrcmp(progname, US"-mailq") == 0)
- {
- fprintf(stderr,
+ exim_fail(
"mailq - list the contents of the mail queue\n\n"
"For a list of options, see the Exim documentation.\n");
- exit(EXIT_FAILURE);
- }
/* Generic usage - we output this whatever happens */
-fprintf(stderr,
+exim_fail(
"Exim is a Mail Transfer Agent. It is normally called by Mail User Agents,\n"
"not directly from a shell command line. Options and/or arguments control\n"
"what it does when called. For a list of options, see the Exim documentation.\n");
-
-exit(EXIT_FAILURE);
}
/* The list of commandline macros should be very short.
Accept the N*M complexity. */
-for (m = macros; m; m = m->next) if (m->command_line)
+for (m = macros_user; m; m = m->next) if (m->command_line)
{
found = FALSE;
for (w = whites; *w; ++w)
}
if (!found)
return FALSE;
- if (m->replacement == NULL)
+ if (!m->replacement)
continue;
- len = Ustrlen(m->replacement);
- if (len == 0)
+ if ((len = m->replen) == 0)
continue;
n = pcre_exec(regex_whitelisted_macro, NULL, CS m->replacement, len,
0, PCRE_EOPT, NULL, 0);
}
+/*************************************************
+* Expansion testing *
+*************************************************/
+
+/* Expand and print one item, doing macro-processing.
+
+Arguments:
+ item line for expansion
+*/
+
+static void
+expansion_test_line(uschar * line)
+{
+int len;
+BOOL dummy_macexp;
+
+Ustrncpy(big_buffer, line, big_buffer_size);
+big_buffer[big_buffer_size-1] = '\0';
+len = Ustrlen(big_buffer);
+
+(void) macros_expand(0, &len, &dummy_macexp);
+
+if (isupper(big_buffer[0]))
+ {
+ if (macro_read_assignment(big_buffer))
+ printf("Defined macro '%s'\n", mlast->name);
+ }
+else
+ if ((line = expand_string(big_buffer))) printf("%s\n", CS line);
+ else printf("Failed: %s\n", expand_string_message);
+}
+
+
+
/*************************************************
* Entry point and high-level code *
*************************************************/
int sender_address_domain = 0;
int test_retry_arg = -1;
int test_rewrite_arg = -1;
+gid_t original_egid;
BOOL arg_queue_only = FALSE;
BOOL bi_option = FALSE;
BOOL checking = FALSE;
struct stat statbuf;
pid_t passed_qr_pid = (pid_t)0;
int passed_qr_pipe = -1;
-gid_t group_list[NGROUPS_MAX];
+gid_t group_list[EXIM_GROUPLIST_SIZE];
/* For the -bI: flag */
enum commandline_info info_flag = CMDINFO_NONE;
if (route_finduser(US EXIM_USERNAME, &pw, &exim_uid))
{
if (exim_uid == 0)
- {
- fprintf(stderr, "exim: refusing to run with uid 0 for \"%s\"\n",
- EXIM_USERNAME);
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: refusing to run with uid 0 for \"%s\"\n", EXIM_USERNAME);
+
/* If ref:name uses a number as the name, route_finduser() returns
TRUE with exim_uid set and pw coerced to NULL. */
if (pw)
exim_gid = pw->pw_gid;
#ifndef EXIM_GROUPNAME
else
- {
- fprintf(stderr,
+ exim_fail(
"exim: ref:name should specify a usercode, not a group.\n"
"exim: can't let you get away with it unless you also specify a group.\n");
- exit(EXIT_FAILURE);
- }
#endif
}
else
- {
- fprintf(stderr, "exim: failed to find uid for user name \"%s\"\n",
- EXIM_USERNAME);
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: failed to find uid for user name \"%s\"\n", EXIM_USERNAME);
#endif
#ifdef EXIM_GROUPNAME
if (!route_findgroup(US EXIM_GROUPNAME, &exim_gid))
- {
- fprintf(stderr, "exim: failed to find gid for group name \"%s\"\n",
- EXIM_GROUPNAME);
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: failed to find gid for group name \"%s\"\n", EXIM_GROUPNAME);
#endif
#ifdef CONFIGURE_OWNERNAME
if (!route_finduser(US CONFIGURE_OWNERNAME, NULL, &config_uid))
- {
- fprintf(stderr, "exim: failed to find uid for user name \"%s\"\n",
+ exim_fail("exim: failed to find uid for user name \"%s\"\n",
CONFIGURE_OWNERNAME);
- exit(EXIT_FAILURE);
- }
#endif
/* We default the system_filter_user to be the Exim run-time user, as a
#ifdef CONFIGURE_GROUPNAME
if (!route_findgroup(US CONFIGURE_GROUPNAME, &config_gid))
- {
- fprintf(stderr, "exim: failed to find gid for group name \"%s\"\n",
+ exim_fail("exim: failed to find gid for group name \"%s\"\n",
CONFIGURE_GROUPNAME);
- exit(EXIT_FAILURE);
- }
#endif
/* In the Cygwin environment, some initialization used to need doing.
/* Check a field which is patched when we are running Exim within its
testing harness; do a fast initial check, and then the whole thing. */
-running_in_test_harness =
+f.running_in_test_harness =
*running_status == '<' && Ustrcmp(running_status, "<<<testing>>>") == 0;
+if (f.running_in_test_harness)
+ debug_store = TRUE;
/* The C standard says that the equivalent of setlocale(LC_ALL, "C") is obeyed
at the start of a program; however, it seems that some environments do not
because store_malloc writes a log entry on failure. */
if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
- {
- fprintf(stderr, "exim: failed to get store for log buffer\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: failed to get store for log buffer\n");
/* Initialize the default log options. */
set_process_info("initializing");
os_restarting_signal(SIGUSR1, usr1_handler);
+/* If running in a dockerized environment, the TERM signal is only
+delegated to the PID 1 if we request it by setting an signal handler */
+if (getpid() == 1) signal(SIGTERM, term_handler);
+
/* SIGHUP is used to get the daemon to reconfigure. It gets set as appropriate
in the daemon code. For the rest of Exim's uses, we ignore it. */
if ((namelen == 5 && Ustrcmp(argv[0], "rmail") == 0) ||
(namelen > 5 && Ustrncmp(argv[0] + namelen - 6, "/rmail", 6) == 0))
{
- dot_ends = FALSE;
+ f.dot_ends = FALSE;
called_as = US"-rmail";
errors_sender_rc = EXIT_SUCCESS;
}
normally be root, but in some esoteric environments it may not be. */
original_euid = geteuid();
+original_egid = getegid();
/* Get the real uid and gid. If the caller is root, force the effective uid/gid
to be the same as the real ones. This makes a difference only if Exim is setuid
if (real_uid == root_uid)
{
- rv = setgid(real_gid);
- if (rv)
- {
- fprintf(stderr, "exim: setgid(%ld) failed: %s\n",
+ if ((rv = setgid(real_gid)))
+ exim_fail("exim: setgid(%ld) failed: %s\n",
(long int)real_gid, strerror(errno));
- exit(EXIT_FAILURE);
- }
- rv = setuid(real_uid);
- if (rv)
- {
- fprintf(stderr, "exim: setuid(%ld) failed: %s\n",
+ if ((rv = setuid(real_uid)))
+ exim_fail("exim: setuid(%ld) failed: %s\n",
(long int)real_uid, strerror(errno));
- exit(EXIT_FAILURE);
- }
}
/* If neither the original real uid nor the original euid was root, Exim is
{
switchchar = arg[3];
argrest += 2;
- queue_2stage = TRUE;
+ f.queue_2stage = TRUE;
}
/* Make -r synonymous with -f, since it is a documented alias */
if (*argrest == 'd')
{
- daemon_listen = TRUE;
- if (*(++argrest) == 'f') background_daemon = FALSE;
+ f.daemon_listen = TRUE;
+ if (*(++argrest) == 'f') f.background_daemon = FALSE;
else if (*argrest != 0) { badarg = TRUE; break; }
}
filter_test |= checking = FTEST_SYSTEM;
if (*(++argrest) != 0) { badarg = TRUE; break; }
if (++i < argc) filter_test_sfile = argv[i]; else
- {
- fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: file name expected after %s\n", argv[i-1]);
}
/* -bf: Run user filter test
{
filter_test |= checking = FTEST_USER;
if (++i < argc) filter_test_ufile = argv[i]; else
- {
- fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: file name expected after %s\n", argv[i-1]);
}
else
{
if (++i >= argc)
- {
- fprintf(stderr, "exim: string expected after %s\n", arg);
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: string expected after %s\n", arg);
if (Ustrcmp(argrest, "d") == 0) ftest_domain = argv[i];
else if (Ustrcmp(argrest, "l") == 0) ftest_localpart = argv[i];
else if (Ustrcmp(argrest, "p") == 0) ftest_prefix = argv[i];
{
if (++i >= argc) { badarg = TRUE; break; }
sender_host_address = argv[i];
- host_checking = checking = log_testing_mode = TRUE;
- host_checking_callout = argrest[1] == 'c';
+ host_checking = checking = f.log_testing_mode = TRUE;
+ f.host_checking_callout = argrest[1] == 'c';
message_logs = FALSE;
}
else if (Ustrcmp(argrest, "nq") == 0)
{
- allow_unqualified_sender = FALSE;
- allow_unqualified_recipient = FALSE;
+ f.allow_unqualified_sender = FALSE;
+ f.allow_unqualified_recipient = FALSE;
}
/* -bpxx: List the contents of the mail queue, in various forms. If
/* -bt: address testing mode */
else if (Ustrcmp(argrest, "t") == 0)
- address_test_mode = checking = log_testing_mode = TRUE;
+ f.address_test_mode = checking = f.log_testing_mode = TRUE;
/* -bv: verify addresses */
else if (Ustrcmp(argrest, "v") == 0)
- verify_address_mode = checking = log_testing_mode = TRUE;
+ verify_address_mode = checking = f.log_testing_mode = TRUE;
/* -bvs: verify sender addresses */
else if (Ustrcmp(argrest, "vs") == 0)
{
- verify_address_mode = checking = log_testing_mode = TRUE;
+ verify_address_mode = checking = f.log_testing_mode = TRUE;
verify_as_sender = TRUE;
}
printf("%s\n", CS version_copyright);
version_printed = TRUE;
show_whats_supported(stdout);
- log_testing_mode = TRUE;
+ f.log_testing_mode = TRUE;
}
/* -bw: inetd wait mode, accept a listening socket as stdin */
else if (*argrest == 'w')
{
- inetd_wait_mode = TRUE;
- background_daemon = FALSE;
- daemon_listen = TRUE;
+ f.inetd_wait_mode = TRUE;
+ f.background_daemon = FALSE;
+ f.daemon_listen = TRUE;
if (*(++argrest) != '\0')
- {
- inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE);
- if (inetd_wait_timeout <= 0)
- {
- fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
- exit(EXIT_FAILURE);
- }
- }
+ if ((inetd_wait_timeout = readconf_readtime(argrest, 0, FALSE)) <= 0)
+ exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
}
else badarg = TRUE;
Ustrncmp(filename, ALT_CONFIG_PREFIX, len) != 0 ||
Ustrstr(filename, "/../") != NULL) &&
(Ustrcmp(filename, "/dev/null") != 0 || real_uid != root_uid))
- {
- fprintf(stderr, "-C Permission denied\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("-C Permission denied\n");
}
#endif
if (real_uid != root_uid)
&& real_uid != config_uid
#endif
)
- trusted_config = FALSE;
+ f.trusted_config = FALSE;
else
{
FILE *trust_list = Ufopen(TRUSTED_CONFIG_LIST, "rb");
) || /* or */
(statbuf.st_mode & 2) != 0) /* world writeable */
{
- trusted_config = FALSE;
+ f.trusted_config = FALSE;
fclose(trust_list);
}
else
int sep = 0;
const uschar *list = argrest;
uschar *filename;
- while (trusted_config && (filename = string_nextinlist(&list,
+ while (f.trusted_config && (filename = string_nextinlist(&list,
&sep, big_buffer, big_buffer_size)) != NULL)
{
for (i=0; i < nr_configs; i++)
}
if (i == nr_configs)
{
- trusted_config = FALSE;
+ f.trusted_config = FALSE;
break;
}
}
else
{
/* No valid prefixes found in trust_list file. */
- trusted_config = FALSE;
+ f.trusted_config = FALSE;
}
}
}
else
{
/* Could not open trust_list file. */
- trusted_config = FALSE;
+ f.trusted_config = FALSE;
}
}
#else
/* Not root; don't trust config */
- trusted_config = FALSE;
+ f.trusted_config = FALSE;
#endif
}
config_main_filelist = argrest;
- config_changed = TRUE;
+ f.config_changed = TRUE;
}
break;
/* -D: set up a macro definition */
case 'D':
- #ifdef DISABLE_D_OPTION
- fprintf(stderr, "exim: -D is not available in this Exim binary\n");
- exit(EXIT_FAILURE);
- #else
+#ifdef DISABLE_D_OPTION
+ exim_fail("exim: -D is not available in this Exim binary\n");
+#else
{
int ptr = 0;
macro_item *m;
while (isspace(*s)) s++;
if (*s < 'A' || *s > 'Z')
- {
- fprintf(stderr, "exim: macro name set by -D must start with "
+ exim_fail("exim: macro name set by -D must start with "
"an upper case letter\n");
- exit(EXIT_FAILURE);
- }
while (isalnum(*s) || *s == '_')
{
while (isspace(*s)) s++;
}
- for (m = macros; m; m = m->next)
+ for (m = macros_user; m; m = m->next)
if (Ustrcmp(m->name, name) == 0)
- {
- fprintf(stderr, "exim: duplicated -D in command line\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: duplicated -D in command line\n");
- m = macro_create(name, s, TRUE, FALSE);
+ m = macro_create(name, s, TRUE);
if (clmacro_count >= MAX_CLMACROS)
- {
- fprintf(stderr, "exim: too many -D options on command line\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: too many -D options on command line\n");
clmacros[clmacro_count++] = string_sprintf("-D%s=%s", m->name,
m->replacement);
}
debug_file = NULL;
if (*argrest == 'd')
{
- debug_daemon = TRUE;
+ f.debug_daemon = TRUE;
argrest++;
}
if (*argrest != 0)
message_reference at it, for logging. */
case 'E':
- local_error_message = TRUE;
+ f.local_error_message = TRUE;
if (mac_ismsgid(argrest)) message_reference = argrest;
break;
{ badarg = TRUE; break; }
}
originator_name = argrest;
- sender_name_forced = TRUE;
+ f.sender_name_forced = TRUE;
break;
#endif
allow_domain_literals = FALSE;
strip_trailing_dot = FALSE;
- if (sender_address == NULL)
- {
- fprintf(stderr, "exim: bad -f address \"%s\": %s\n", argrest, errmess);
- return EXIT_FAILURE;
- }
+ if (!sender_address)
+ exim_fail("exim: bad -f address \"%s\": %s\n", argrest, errmess);
}
- sender_address_forced = TRUE;
+ f.sender_address_forced = TRUE;
}
break;
not to be documented for sendmail but mailx (at least) uses it) */
case 'i':
- if (*argrest == 0) dot_ends = FALSE; else badarg = TRUE;
+ if (*argrest == 0) f.dot_ends = FALSE; else badarg = TRUE;
break;
if(++i < argc) argrest = argv[i]; else
{ badarg = TRUE; break; }
}
- sz = Ustrlen(argrest);
- if (sz > 32)
- {
- fprintf(stderr, "exim: the -L syslog name is too long: \"%s\"\n", argrest);
- return EXIT_FAILURE;
- }
+ if ((sz = Ustrlen(argrest)) > 32)
+ exim_fail("exim: the -L syslog name is too long: \"%s\"\n", argrest);
if (sz < 1)
- {
- fprintf(stderr, "exim: the -L syslog name is too short\n");
- return EXIT_FAILURE;
- }
+ exim_fail("exim: the -L syslog name is too short\n");
cmdline_syslog_name = argrest;
break;
EXIM_SOCKLEN_T size = sizeof(interface_sock);
if (argc != i + 6)
- {
- fprintf(stderr, "exim: too many or too few arguments after -MC\n");
- return EXIT_FAILURE;
- }
+ exim_fail("exim: too many or too few arguments after -MC\n");
if (msg_action_arg >= 0)
- {
- fprintf(stderr, "exim: incompatible arguments\n");
- return EXIT_FAILURE;
- }
+ exim_fail("exim: incompatible arguments\n");
continue_transport = argv[++i];
continue_hostname = argv[++i];
queue_run_pipe = passed_qr_pipe;
if (!mac_ismsgid(argv[i]))
- {
- fprintf(stderr, "exim: malformed message id %s after -MC option\n",
+ exim_fail("exim: malformed message id %s after -MC option\n",
argv[i]);
- return EXIT_FAILURE;
- }
- /* Set up $sending_ip_address and $sending_port */
+ /* Set up $sending_ip_address and $sending_port, unless proxied */
- if (getsockname(fileno(stdin), (struct sockaddr *)(&interface_sock),
- &size) == 0)
- sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
- &sending_port);
- else
- {
- fprintf(stderr, "exim: getsockname() failed after -MC option: %s\n",
- strerror(errno));
- return EXIT_FAILURE;
- }
+ if (!continue_proxy_cipher)
+ if (getsockname(fileno(stdin), (struct sockaddr *)(&interface_sock),
+ &size) == 0)
+ sending_ip_address = host_ntoa(-1, &interface_sock, NULL,
+ &sending_port);
+ else
+ exim_fail("exim: getsockname() failed after -MC option: %s\n",
+ strerror(errno));
- if (running_in_test_harness) millisleep(500);
+ if (f.running_in_test_harness) millisleep(500);
break;
}
else if (*argrest == 'C' && argrest[1] && !argrest[2])
{
- switch(argrest[1])
+ switch(argrest[1])
{
/* -MCA: set the smtp_authenticated flag; this is useful only when it
precedes -MC (see above). The flag indicates that the host to which
Exim is connected has accepted an AUTH sequence. */
- case 'A': smtp_authenticated = TRUE; break;
+ case 'A': f.smtp_authenticated = TRUE; break;
/* -MCD: set the smtp_use_dsn flag; this indicates that the host
that exim is connected to supports the esmtp extension DSN */
- case 'D': smtp_peer_options |= PEER_OFFERED_DSN; break;
+ case 'D': smtp_peer_options |= OPTION_DSN; break;
/* -MCG: set the queue name, to a non-default value */
/* -MCK: the peer offered CHUNKING. Must precede -MC */
- case 'K': smtp_peer_options |= PEER_OFFERED_CHUNKING; break;
+ case 'K': smtp_peer_options |= OPTION_CHUNKING; break;
/* -MCP: set the smtp_use_pipelining flag; this is useful only when
it preceded -MC (see above) */
- case 'P': smtp_peer_options |= PEER_OFFERED_PIPE; break;
+ case 'P': smtp_peer_options |= OPTION_PIPE; break;
/* -MCQ: pass on the pid of the queue-running process that started
this chain of deliveries and the fd of its synchronizing pipe; this
/* -MCS: set the smtp_use_size flag; this is useful only when it
precedes -MC (see above) */
- case 'S': smtp_peer_options |= PEER_OFFERED_SIZE; break;
+ case 'S': smtp_peer_options |= OPTION_SIZE; break;
#ifdef SUPPORT_TLS
+ /* -MCt: similar to -MCT below but the connection is still open
+ via a proxy process which handles the TLS context and coding.
+ Require three arguments for the proxied local address and port,
+ and the TLS cipher. */
+
+ case 't': if (++i < argc) sending_ip_address = argv[i];
+ else badarg = TRUE;
+ if (++i < argc) sending_port = (int)(Uatol(argv[i]));
+ else badarg = TRUE;
+ if (++i < argc) continue_proxy_cipher = argv[i];
+ else badarg = TRUE;
+ /*FALLTHROUGH*/
+
/* -MCT: set the tls_offered flag; this is useful only when it
precedes -MC (see above). The flag indicates that the host to which
Exim is connected has offered TLS support. */
- case 'T': smtp_peer_options |= PEER_OFFERED_TLS; break;
+ case 'T': smtp_peer_options |= OPTION_TLS; break;
#endif
default: badarg = TRUE; break;
}
- break;
+ break;
}
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ /* -MS set REQUIRETLS on (new) message */
+
+ else if (*argrest == 'S')
+ {
+ tls_requiretls |= REQUIRETLS_MSG;
+ break;
+ }
+#endif
+
/* -M[x]: various operations on the following list of message ids:
-M deliver the messages, ignoring next retry times and thawing
-Mc deliver the messages, checking next retry times, no thawing
else if (*argrest == 0)
{
msg_action = MSG_DELIVER;
- forced_delivery = deliver_force_thaw = TRUE;
+ forced_delivery = f.deliver_force_thaw = TRUE;
}
else if (Ustrcmp(argrest, "ar") == 0)
{
msg_action_arg = i + 1;
if (msg_action_arg >= argc)
- {
- fprintf(stderr, "exim: no message ids given after %s option\n", arg);
- return EXIT_FAILURE;
- }
+ exim_fail("exim: no message ids given after %s option\n", arg);
/* Some require only message ids to follow */
{
int j;
for (j = msg_action_arg; j < argc; j++) if (!mac_ismsgid(argv[j]))
- {
- fprintf(stderr, "exim: malformed message id %s after %s option\n",
+ exim_fail("exim: malformed message id %s after %s option\n",
argv[j], arg);
- return EXIT_FAILURE;
- }
goto END_ARG; /* Remaining args are ids */
}
else
{
if (!mac_ismsgid(argv[msg_action_arg]))
- {
- fprintf(stderr, "exim: malformed message id %s after %s option\n",
+ exim_fail("exim: malformed message id %s after %s option\n",
argv[msg_action_arg], arg);
- return EXIT_FAILURE;
- }
i++;
}
break;
case 'N':
if (*argrest == 0)
{
- dont_deliver = TRUE;
+ f.dont_deliver = TRUE;
debug_selector |= D_v;
debug_file = stderr;
}
if (*argrest == 0)
{
if (++i >= argc)
- {
- fprintf(stderr, "exim: string expected after -O\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: string expected after -O\n");
}
break;
if (alias_arg[0] == 0)
{
if (i+1 < argc) alias_arg = argv[++i]; else
- {
- fprintf(stderr, "exim: string expected after -oA\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: string expected after -oA\n");
}
}
if (p != NULL)
{
if (!isdigit(*p))
- {
- fprintf(stderr, "exim: number expected after -oB\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: number expected after -oB\n");
connection_max_messages = Uatoi(p);
}
}
else if (Ustrcmp(argrest, "db") == 0)
{
- synchronous_delivery = FALSE;
+ f.synchronous_delivery = FALSE;
arg_queue_only = FALSE;
queue_only_set = TRUE;
}
else if (Ustrcmp(argrest, "df") == 0 || Ustrcmp(argrest, "di") == 0)
{
- synchronous_delivery = TRUE;
+ f.synchronous_delivery = TRUE;
arg_queue_only = FALSE;
queue_only_set = TRUE;
}
else if (Ustrcmp(argrest, "dq") == 0)
{
- synchronous_delivery = FALSE;
+ f.synchronous_delivery = FALSE;
arg_queue_only = TRUE;
queue_only_set = TRUE;
}
else if (Ustrcmp(argrest, "dqs") == 0)
{
- queue_smtp = TRUE;
+ f.queue_smtp = TRUE;
arg_queue_only = FALSE;
queue_only_set = TRUE;
}
else if (Ustrcmp(argrest, "i") == 0 ||
Ustrcmp(argrest, "itrue") == 0)
- dot_ends = FALSE;
+ f.dot_ends = FALSE;
/* -oM*: Set various characteristics for an incoming message; actually
acted on for trusted callers only. */
else if (*argrest == 'M')
{
if (i+1 >= argc)
- {
- fprintf(stderr, "exim: data expected after -o%s\n", argrest);
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: data expected after -o%s\n", argrest);
/* -oMa: Set sender host address */
else if (Ustrcmp(argrest, "Mm") == 0)
{
if (!mac_ismsgid(argv[i+1]))
- {
- fprintf(stderr,"-oMm must be a valid message ID\n");
- exit(EXIT_FAILURE);
- }
- if (!trusted_config)
- {
- fprintf(stderr,"-oMm must be called by a trusted user/config\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("-oMm must be a valid message ID\n");
+ if (!f.trusted_config)
+ exim_fail("-oMm must be called by a trusted user/config\n");
message_reference = argv[++i];
}
/* -oMr: Received protocol */
- else if (Ustrcmp(argrest, "Mr") == 0) received_protocol = argv[++i];
+ else if (Ustrcmp(argrest, "Mr") == 0)
+
+ if (received_protocol)
+ exim_fail("received_protocol is set already\n");
+ else
+ received_protocol = argv[++i];
/* -oMs: Set sender host name */
}
else *tp = readconf_readtime(argrest + 1, 0, FALSE);
if (*tp < 0)
- {
- fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
}
/* -oX <list>: Override local_interfaces and/or default daemon ports */
which sets the host protocol and host name */
if (*argrest == 0)
- {
- if (i+1 < argc) argrest = argv[++i]; else
+ if (i+1 < argc)
+ argrest = argv[++i];
+ else
{ badarg = TRUE; break; }
- }
if (*argrest != 0)
{
- uschar *hn = Ustrchr(argrest, ':');
+ uschar *hn;
+
+ if (received_protocol)
+ exim_fail("received_protocol is set already\n");
+
+ hn = Ustrchr(argrest, ':');
if (hn == NULL)
- {
received_protocol = argrest;
- }
else
{
int old_pool = store_pool;
case 'q':
receiving_message = FALSE;
if (queue_interval >= 0)
- {
- fprintf(stderr, "exim: -q specified more than once\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: -q specified more than once\n");
/* -qq...: Do queue runs in a 2-stage manner */
if (*argrest == 'q')
{
- queue_2stage = TRUE;
+ f.queue_2stage = TRUE;
argrest++;
}
if (*argrest == 'i')
{
- queue_run_first_delivery = TRUE;
+ f.queue_run_first_delivery = TRUE;
argrest++;
}
if (*argrest == 'f')
{
- queue_run_force = TRUE;
+ f.queue_run_force = TRUE;
if (*++argrest == 'f')
{
- deliver_force_thaw = TRUE;
+ f.deliver_force_thaw = TRUE;
argrest++;
}
}
if (*argrest == 'l')
{
- queue_run_local = TRUE;
+ f.queue_run_local = TRUE;
argrest++;
}
else if ((queue_interval = readconf_readtime(*argrest ? argrest : argv[++i],
0, FALSE)) <= 0)
- {
- fprintf(stderr, "exim: bad time value %s: abandoned\n", argv[i]);
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: bad time value %s: abandoned\n", argv[i]);
break;
for (i = 0; i < nelem(rsopts); i++)
if (Ustrcmp(argrest, rsopts[i]) == 0)
{
- if (i != 2) queue_run_force = TRUE;
- if (i >= 2) deliver_selectstring_regex = TRUE;
- if (i == 1 || i == 4) deliver_force_thaw = TRUE;
+ if (i != 2) f.queue_run_force = TRUE;
+ if (i >= 2) f.deliver_selectstring_regex = TRUE;
+ if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
argrest += Ustrlen(rsopts[i]);
}
}
else if (i+1 < argc)
deliver_selectstring = argv[++i];
else
- {
- fprintf(stderr, "exim: string expected after -R\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: string expected after -R\n");
break;
for (i = 0; i < nelem(rsopts); i++)
if (Ustrcmp(argrest, rsopts[i]) == 0)
{
- if (i != 2) queue_run_force = TRUE;
- if (i >= 2) deliver_selectstring_sender_regex = TRUE;
- if (i == 1 || i == 4) deliver_force_thaw = TRUE;
+ if (i != 2) f.queue_run_force = TRUE;
+ if (i >= 2) f.deliver_selectstring_sender_regex = TRUE;
+ if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
argrest += Ustrlen(rsopts[i]);
}
}
else if (i+1 < argc)
deliver_selectstring_sender = argv[++i];
else
- {
- fprintf(stderr, "exim: string expected after -S\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: string expected after -S\n");
break;
/* -Tqt is an option that is exclusively for use by the testing suite.
tested. Otherwise variability of clock ticks etc. cause problems. */
case 'T':
- if (running_in_test_harness && Ustrcmp(argrest, "qt") == 0)
+ if (f.running_in_test_harness && Ustrcmp(argrest, "qt") == 0)
fudged_queue_times = argv[++i];
else badarg = TRUE;
break;
else if (Ustrcmp(argrest, "i") == 0)
{
extract_recipients = TRUE;
- dot_ends = FALSE;
+ f.dot_ends = FALSE;
}
/* -tls-on-connect: don't wait for STARTTLS (for old clients) */
case 'X':
if (*argrest == '\0')
if (++i >= argc)
- {
- fprintf(stderr, "exim: string expected after -X\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: string expected after -X\n");
break;
case 'z':
if (*argrest == '\0')
- if (++i < argc) log_oneline = argv[i]; else
- {
- fprintf(stderr, "exim: file name expected after %s\n", argv[i-1]);
- exit(EXIT_FAILURE);
- }
+ if (++i < argc)
+ log_oneline = argv[i];
+ else
+ exim_fail("exim: file name expected after %s\n", argv[i-1]);
break;
/* All other initial characters are errors */
/* Failed to recognize the option, or syntax error */
if (badarg)
- {
- fprintf(stderr, "exim abandoned: unknown, malformed, or incomplete "
+ exim_fail("exim abandoned: unknown, malformed, or incomplete "
"option %s\n", arg);
- exit(EXIT_FAILURE);
- }
}
/* Arguments have been processed. Check for incompatibilities. */
if ((
(smtp_input || extract_recipients || recipients_arg < argc) &&
- (daemon_listen || queue_interval >= 0 || bi_option ||
+ (f.daemon_listen || queue_interval >= 0 || bi_option ||
test_retry_arg >= 0 || test_rewrite_arg >= 0 ||
filter_test != FTEST_NONE || (msg_action_arg > 0 && !one_msg_action))
) ||
(
msg_action_arg > 0 &&
- (daemon_listen || queue_interval > 0 || list_options ||
+ (f.daemon_listen || queue_interval > 0 || list_options ||
(checking && msg_action != MSG_LOAD) ||
bi_option || test_retry_arg >= 0 || test_rewrite_arg >= 0)
) ||
(
- (daemon_listen || queue_interval > 0) &&
+ (f.daemon_listen || queue_interval > 0) &&
(sender_address != NULL || list_options || list_queue || checking ||
bi_option)
) ||
(
- daemon_listen && queue_interval == 0
+ f.daemon_listen && queue_interval == 0
) ||
(
- inetd_wait_mode && queue_interval >= 0
+ f.inetd_wait_mode && queue_interval >= 0
) ||
(
list_options &&
) ||
(
verify_address_mode &&
- (address_test_mode || smtp_input || extract_recipients ||
+ (f.address_test_mode || smtp_input || extract_recipients ||
filter_test != FTEST_NONE || bi_option)
) ||
(
- address_test_mode && (smtp_input || extract_recipients ||
+ f.address_test_mode && (smtp_input || extract_recipients ||
filter_test != FTEST_NONE || bi_option)
) ||
(
(!expansion_test || expansion_test_message != NULL)
)
)
- {
- fprintf(stderr, "exim: incompatible command-line options or arguments\n");
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: incompatible command-line options or arguments\n");
/* If debugging is set up, set the file and the file descriptor to pass on to
child processes. It should, of course, be 2 for stderr. Also, force the daemon
{
debug_file = stderr;
debug_fd = fileno(debug_file);
- background_daemon = FALSE;
- if (running_in_test_harness) millisleep(100); /* lets caller finish */
+ f.background_daemon = FALSE;
+ if (f.running_in_test_harness) millisleep(100); /* lets caller finish */
if (debug_selector != D_v) /* -v only doesn't show this */
{
debug_printf("Exim version %s uid=%ld gid=%ld pid=%d D=%x\n",
till after reading the config, which might specify the exim gid. Therefore,
save the group list here first. */
-group_count = getgroups(NGROUPS_MAX, group_list);
-if (group_count < 0)
- {
- fprintf(stderr, "exim: getgroups() failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
+if ((group_count = getgroups(nelem(group_list), group_list)) < 0)
+ exim_fail("exim: getgroups() failed: %s\n", strerror(errno));
/* There is a fundamental difference in some BSD systems in the matter of
groups. FreeBSD and BSDI are known to be different; NetBSD and OpenBSD are
list. Calling setgroups() with zero groups on a "different" system results in
an error return. The following code should cope with both types of system.
+ Unfortunately, recent MacOS, which should be a FreeBSD, "helpfully" succeeds
+ the "setgroups() with zero groups" - and changes the egid.
+ Thanks to that we had to stash the original_egid above, for use below
+ in the call to exim_setugid().
+
However, if this process isn't running as root, setgroups() can't be used
since you have to be root to run it, even if throwing away groups. Not being
root here happens only in some unusual configurations. We just ignore the
error. */
-if (setgroups(0, NULL) != 0)
- {
- if (setgroups(1, group_list) != 0 && !unprivileged)
- {
- fprintf(stderr, "exim: setgroups() failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
- }
+if (setgroups(0, NULL) != 0 && setgroups(1, group_list) != 0 && !unprivileged)
+ exim_fail("exim: setgroups() failed: %s\n", strerror(errno));
/* If the configuration file name has been altered by an argument on the
command line (either a new file name or a macro definition) and the caller is
configuration file changes and macro definitions haven't happened. */
if (( /* EITHER */
- (!trusted_config || /* Config changed, or */
+ (!f.trusted_config || /* Config changed, or */
!macros_trusted(opt_D_used)) && /* impermissible macros and */
real_uid != root_uid && /* Not root, and */
- !running_in_test_harness /* Not fudged */
+ !f.running_in_test_harness /* Not fudged */
) || /* OR */
expansion_test /* expansion testing */
|| /* OR */
Note that if the invoker is Exim, the logs remain available. Messing with
this causes unlogged successful deliveries. */
- if ((log_stderr != NULL) && (real_uid != exim_uid))
- really_exim = FALSE;
+ if (log_stderr && real_uid != exim_uid)
+ f.really_exim = FALSE;
}
/* Privilege is to be retained for the moment. It may be dropped later,
the real uid to the effective so that subsequent re-execs of Exim are done by a
privileged user. */
-else exim_setugid(geteuid(), getegid(), FALSE, US"forcing real = effective");
+else
+ exim_setugid(geteuid(), original_egid, FALSE, US"forcing real = effective");
/* If testing a filter, open the file(s) now, before wasting time doing other
setups and reading the message. */
-if ((filter_test & FTEST_SYSTEM) != 0)
- {
- filter_sfd = Uopen(filter_test_sfile, O_RDONLY, 0);
- if (filter_sfd < 0)
- {
- fprintf(stderr, "exim: failed to open %s: %s\n", filter_test_sfile,
+if (filter_test & FTEST_SYSTEM)
+ if ((filter_sfd = Uopen(filter_test_sfile, O_RDONLY, 0)) < 0)
+ exim_fail("exim: failed to open %s: %s\n", filter_test_sfile,
strerror(errno));
- return EXIT_FAILURE;
- }
- }
-if ((filter_test & FTEST_USER) != 0)
- {
- filter_ufd = Uopen(filter_test_ufile, O_RDONLY, 0);
- if (filter_ufd < 0)
- {
- fprintf(stderr, "exim: failed to open %s: %s\n", filter_test_ufile,
+if (filter_test & FTEST_USER)
+ if ((filter_ufd = Uopen(filter_test_ufile, O_RDONLY, 0)) < 0)
+ exim_fail("exim: failed to open %s: %s\n", filter_test_ufile,
strerror(errno));
- return EXIT_FAILURE;
- }
- }
/* Initialise lookup_list
If debugging, already called above via version reporting.
init_lookup_list();
#ifdef SUPPORT_I18N
-if (running_in_test_harness) smtputf8_advertise_hosts = NULL;
+if (f.running_in_test_harness) smtputf8_advertise_hosts = NULL;
#endif
/* Read the main runtime configuration data; this gives up if there
is a failure. It leaves the configuration file open so that the subsequent
configuration data for delivery can be read if needed.
-NOTE: immediatly after opening the configuration file we change the working
+NOTE: immediately after opening the configuration file we change the working
directory to "/"! Later we change to $spool_directory. We do it there, because
during readconf_main() some expansion takes place already. */
-/* Store the initial cwd before we change directories */
-if ((initial_cwd = os_getcwd(NULL, 0)) == NULL)
- {
- perror("exim: can't get the current working directory");
- exit(EXIT_FAILURE);
- }
+/* Store the initial cwd before we change directories. Can be NULL if the
+dir has already been unlinked. */
+initial_cwd = os_getcwd(NULL, 0);
/* checking:
-be[m] expansion test -
readconf_main(checking || list_options);
+
/* Now in directory "/" */
if (cleanup_environment() == FALSE)
for later interrogation. */
if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
- admin_user = TRUE;
+ f.admin_user = TRUE;
else
{
int i, j;
- for (i = 0; i < group_count; i++)
- {
- if (group_list[i] == exim_gid) admin_user = TRUE;
- else if (admin_groups != NULL)
- {
- for (j = 1; j <= (int)(admin_groups[0]); j++)
+ for (i = 0; i < group_count && !f.admin_user; i++)
+ if (group_list[i] == exim_gid)
+ f.admin_user = TRUE;
+ else if (admin_groups)
+ for (j = 1; j <= (int)admin_groups[0] && !f.admin_user; j++)
if (admin_groups[j] == group_list[i])
- { admin_user = TRUE; break; }
- }
- if (admin_user) break;
- }
+ f.admin_user = TRUE;
}
/* Another group of privileged users are the trusted users. These are root,
other message parameters as well. */
if (real_uid == root_uid || real_uid == exim_uid)
- trusted_caller = TRUE;
+ f.trusted_caller = TRUE;
else
{
int i, j;
- if (trusted_users != NULL)
- {
- for (i = 1; i <= (int)(trusted_users[0]); i++)
+ if (trusted_users)
+ for (i = 1; i <= (int)trusted_users[0] && !f.trusted_caller; i++)
if (trusted_users[i] == real_uid)
- { trusted_caller = TRUE; break; }
- }
+ f.trusted_caller = TRUE;
- if (!trusted_caller && trusted_groups != NULL)
- {
- for (i = 1; i <= (int)(trusted_groups[0]); i++)
- {
+ if (trusted_groups)
+ for (i = 1; i <= (int)trusted_groups[0] && !f.trusted_caller; i++)
if (trusted_groups[i] == real_gid)
- trusted_caller = TRUE;
- else for (j = 0; j < group_count; j++)
- {
+ f.trusted_caller = TRUE;
+ else for (j = 0; j < group_count && !f.trusted_caller; j++)
if (trusted_groups[i] == group_list[j])
- { trusted_caller = TRUE; break; }
- }
- if (trusted_caller) break;
- }
- }
+ f.trusted_caller = TRUE;
}
+/* At this point, we know if the user is privileged and some command-line
+options become possibly impermissible, depending upon the configuration file. */
+
+if (checking && commandline_checks_require_admin && !f.admin_user)
+ exim_fail("exim: those command-line flags are set to require admin\n");
+
/* Handle the decoding of logging options. */
decode_bits(log_selector, log_selector_size, log_notall,
/* If domain literals are not allowed, check the sender address that was
supplied with -f. Ditto for a stripped trailing dot. */
-if (sender_address != NULL)
+if (sender_address)
{
if (sender_address[sender_address_domain] == '[' && !allow_domain_literals)
- {
- fprintf(stderr, "exim: bad -f address \"%s\": domain literals not "
+ exim_fail("exim: bad -f address \"%s\": domain literals not "
"allowed\n", sender_address);
- return EXIT_FAILURE;
- }
if (f_end_dot && !strip_trailing_dot)
- {
- fprintf(stderr, "exim: bad -f address \"%s.\": domain is malformed "
+ exim_fail("exim: bad -f address \"%s.\": domain is malformed "
"(trailing dot not allowed)\n", sender_address);
- return EXIT_FAILURE;
- }
}
/* See if an admin user overrode our logging. */
-if (cmdline_syslog_name != NULL)
- {
- if (admin_user)
+if (cmdline_syslog_name)
+ if (f.admin_user)
{
syslog_processname = cmdline_syslog_name;
log_file_path = string_copy(CUS"syslog");
}
else
- {
/* not a panic, non-privileged users should not be able to spam paniclog */
- fprintf(stderr,
+ exim_fail(
"exim: you lack sufficient privilege to specify syslog process name\n");
- return EXIT_FAILURE;
- }
- }
/* Paranoia check of maximum lengths of certain strings. There is a check
on the length of the log file path in log.c, which will come into effect
"syslog_processname is longer than 32 chars: aborting");
if (log_oneline)
- if (admin_user)
+ if (f.admin_user)
{
log_write(0, LOG_MAIN, "%s", log_oneline);
return EXIT_SUCCESS;
timestamps_utc is set, because then all times are in UTC anyway. */
if (timezone_string && strcmpic(timezone_string, US"UTC") == 0)
- timestamps_utc = TRUE;
+ f.timestamps_utc = TRUE;
else
{
uschar *envtz = US getenv("TZ");
trusted configuration file (when deliver_drop_privilege is false). */
if ( removed_privilege
- && (!trusted_config || opt_D_used)
+ && (!f.trusted_config || opt_D_used)
&& real_uid == exim_uid)
if (deliver_drop_privilege)
- really_exim = TRUE; /* let logging work normally */
+ f.really_exim = TRUE; /* let logging work normally */
else
log_write(0, LOG_MAIN|LOG_PANIC,
"exim user lost privilege for using %s option",
- trusted_config? "-D" : "-C");
+ f.trusted_config? "-D" : "-C");
/* Start up Perl interpreter if Perl support is configured and there is a
perl_startup option, and the configuration or the command line specifies
{
uschar *errstr;
DEBUG(D_any) debug_printf("Starting Perl interpreter\n");
- errstr = init_perl(opt_perl_startup);
- if (errstr != NULL)
- {
- fprintf(stderr, "exim: error in perl_startup code: %s\n", errstr);
- return EXIT_FAILURE;
- }
+ if ((errstr = init_perl(opt_perl_startup)))
+ exim_fail("exim: error in perl_startup code: %s\n", errstr);
opt_perl_started = TRUE;
}
#endif /* EXIM_PERL */
Don't attempt it if logging is disabled, or if listing variables or if
verifying/testing addresses or expansions. */
-if (((debug_selector & D_any) != 0 || LOGGING(arguments))
- && really_exim && !list_options && !checking)
+if ( (debug_selector & D_any || LOGGING(arguments))
+ && f.really_exim && !list_options && !checking)
{
int i;
uschar *p = big_buffer;
Ustrcpy(p, "cwd= (failed)");
- Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+ if (!initial_cwd)
+ p += 13;
+ else
+ {
+ Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
+ p += 4 + Ustrlen(initial_cwd);
+ /* in case p is near the end and we don't provide enough space for
+ * string_format to be willing to write. */
+ *p = '\0';
+ }
- while (*p) p++;
(void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
while (*p) p++;
for (i = 0; i < argc; i++)
quote = US"";
while (*pp != 0) if (isspace(*pp++)) { quote = US"\""; break; }
}
- sprintf(CS p, " %s%.*s%s", quote, (int)(big_buffer_size -
+ p += sprintf(CS p, " %s%.*s%s", quote, (int)(big_buffer_size -
(p - big_buffer) - 4), printing, quote);
- while (*p) p++;
}
if (LOGGING(arguments))
int dummy;
(void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
dummy = /* quieten compiler */ Uchdir(spool_directory);
+ dummy = dummy; /* yet more compiler quietening, sigh */
}
/* Handle calls with the -bi option. This is a sendmail option to rebuild *the*
(argv[1] == NULL)? US"" : argv[1]);
execv(CS argv[0], (char *const *)argv);
- fprintf(stderr, "exim: exec failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
+ exim_fail("exim: exec failed: %s\n", strerror(errno));
}
else
{
configuration file. We leave these prints here to ensure that syslog setup,
logfile setup, and so on has already happened. */
-if (trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
-if (admin_user) DEBUG(D_any) debug_printf("admin user\n");
+if (f.trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
+if (f.admin_user) DEBUG(D_any) debug_printf("admin user\n");
/* Only an admin user may start the daemon or force a queue run in the default
configuration, but the queue run restriction can be relaxed. Only an admin
count. Only an admin user can use the test interface to scan for email
(because Exim will be in the spool dir and able to look at mails). */
-if (!admin_user)
+if (!f.admin_user)
{
BOOL debugset = (debug_selector & ~D_v) != 0;
- if (deliver_give_up || daemon_listen || malware_test_file ||
+ if (deliver_give_up || f.daemon_listen || malware_test_file ||
(count_queue && queue_list_requires_admin) ||
(list_queue && queue_list_requires_admin) ||
(queue_interval >= 0 && prod_requires_admin) ||
- (debugset && !running_in_test_harness))
- {
- fprintf(stderr, "exim:%s permission denied\n", debugset? " debugging" : "");
- exit(EXIT_FAILURE);
- }
+ (debugset && !f.running_in_test_harness))
+ exim_fail("exim:%s permission denied\n", debugset? " debugging" : "");
}
/* If the real user is not root or the exim uid, the argument for passing
if (real_uid != root_uid && real_uid != exim_uid &&
(continue_hostname != NULL ||
- (dont_deliver &&
- (queue_interval >= 0 || daemon_listen || msg_action_arg > 0)
- )) && !running_in_test_harness)
- {
- fprintf(stderr, "exim: Permission denied\n");
- return EXIT_FAILURE;
- }
+ (f.dont_deliver &&
+ (queue_interval >= 0 || f.daemon_listen || msg_action_arg > 0)
+ )) && !f.running_in_test_harness)
+ exim_fail("exim: Permission denied\n");
/* If the caller is not trusted, certain arguments are ignored when running for
real, but are permitted when checking things (-be, -bv, -bt, -bh, -bf, -bF).
Note that authority for performing certain actions on messages is tested in the
queue_action() function. */
-if (!trusted_caller && !checking)
+if (!f.trusted_caller && !checking)
{
sender_host_name = sender_host_address = interface_address =
sender_ident = received_protocol = NULL;
/* If the caller is trusted, then they can use -G to suppress_local_fixups. */
if (flag_G)
{
- if (trusted_caller)
+ if (f.trusted_caller)
{
- suppress_local_fixups = suppress_local_fixups_default = TRUE;
+ f.suppress_local_fixups = f.suppress_local_fixups_default = TRUE;
DEBUG(D_acl) debug_printf("suppress_local_fixups forced on by -G\n");
}
else
- {
- fprintf(stderr, "exim: permission denied (-G requires a trusted user)\n");
- return EXIT_FAILURE;
- }
+ exim_fail("exim: permission denied (-G requires a trusted user)\n");
}
/* If an SMTP message is being received check to see if the standard input is a
if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
{
- is_inetd = TRUE;
+ f.is_inetd = TRUE;
sender_host_address = host_ntoa(-1, (struct sockaddr *)(&inetd_sock),
NULL, &sender_host_port);
if (mua_wrapper) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Input from "
"inetd is not supported when mua_wrapper is set");
}
else
- {
- fprintf(stderr,
+ exim_fail(
"exim: Permission denied (unprivileged user, unprivileged port)\n");
- return EXIT_FAILURE;
- }
}
}
}
#ifdef LOAD_AVG_NEEDS_ROOT
if (receiving_message &&
(queue_only_load >= 0 ||
- (is_inetd && smtp_load_reserve >= 0)
+ (f.is_inetd && smtp_load_reserve >= 0)
))
{
load_average = OS_GETLOADAVG();
if (!unprivileged && /* originally had root AND */
!removed_privilege && /* still got root AND */
- !daemon_listen && /* not starting the daemon */
+ !f.daemon_listen && /* not starting the daemon */
queue_interval <= 0 && /* (either kind of daemon) */
( /* AND EITHER */
deliver_drop_privilege || /* requested unprivileged */
queue_interval < 0 && /* not running the queue */
(msg_action_arg < 0 || /* and */
msg_action != MSG_DELIVER) && /* not delivering and */
- (!checking || !address_test_mode) /* not address checking */
- )
- ))
- {
+ (!checking || !f.address_test_mode) /* not address checking */
+ ) ) )
exim_setugid(exim_uid, exim_gid, TRUE, US"privilege not needed");
- }
/* When we are retaining a privileged uid, we still change to the exim gid. */
there's no security risk. For me, it's { exim -bV } on a just-built binary,
no need to complain then. */
if (rv == -1)
- {
if (!(unprivileged || removed_privilege))
- {
- fprintf(stderr,
- "exim: changing group failed: %s\n", strerror(errno));
- exit(EXIT_FAILURE);
- }
+ exim_fail("exim: changing group failed: %s\n", strerror(errno));
else
DEBUG(D_any) debug_printf("changing group to %ld failed: %s\n",
(long int)exim_gid, strerror(errno));
- }
}
/* Handle a request to scan a file for malware */
int yield = EXIT_SUCCESS;
set_process_info("acting on specified messages");
+ /* ACL definitions may be needed when removing a message (-Mrm) because
+ event_action gets expanded */
+
+ if (msg_action == MSG_REMOVE)
+ readconf_rest();
+
if (!one_msg_action)
{
for (i = msg_action_arg; i < argc; i++)
}
/* We used to set up here to skip reading the ACL section, on
- (msg_action_arg > 0 || (queue_interval == 0 && !daemon_listen)
+ (msg_action_arg > 0 || (queue_interval == 0 && !f.daemon_listen)
Now, since the intro of the ${acl } expansion, ACL definitions may be
needed in transports so we lost the optimisation. */
if (test_retry_arg >= argc)
{
printf("-brt needs a domain or address argument\n");
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, US"main");
}
s1 = argv[test_retry_arg++];
s2 = NULL;
}
}
- yield = retry_find_config(s1, s2, basic_errno, more_errno);
- if (yield == NULL) printf("No retry information found\n"); else
+ if (!(yield = retry_find_config(s1, s2, basic_errno, more_errno)))
+ printf("No retry information found\n");
+ else
{
retry_rule *r;
more_errno = yield->more_errno;
printf("auth_failed ");
else printf("* ");
- for (r = yield->rules; r != NULL; r = r->next)
+ for (r = yield->rules; r; r = r->next)
{
printf("%c,%s", r->rule, readconf_printtime(r->timeout)); /* Do not */
printf(",%s", readconf_printtime(r->p1)); /* amalgamate */
printf("\n");
}
- exim_exit(EXIT_SUCCESS);
+ exim_exit(EXIT_SUCCESS, US"main");
}
/* Handle a request to list one or more configuration options */
if (list_options)
{
+ BOOL fail = FALSE;
set_process_info("listing variables");
- if (recipients_arg >= argc) readconf_print(US"all", NULL, flag_n);
- else for (i = recipients_arg; i < argc; i++)
+ if (recipients_arg >= argc)
+ fail = !readconf_print(US"all", NULL, flag_n);
+ else for (i = recipients_arg; i < argc; i++)
+ {
+ if (i < argc - 1 &&
+ (Ustrcmp(argv[i], "router") == 0 ||
+ Ustrcmp(argv[i], "transport") == 0 ||
+ Ustrcmp(argv[i], "authenticator") == 0 ||
+ Ustrcmp(argv[i], "macro") == 0 ||
+ Ustrcmp(argv[i], "environment") == 0))
{
- if (i < argc - 1 &&
- (Ustrcmp(argv[i], "router") == 0 ||
- Ustrcmp(argv[i], "transport") == 0 ||
- Ustrcmp(argv[i], "authenticator") == 0 ||
- Ustrcmp(argv[i], "macro") == 0 ||
- Ustrcmp(argv[i], "environment") == 0))
- {
- readconf_print(argv[i+1], argv[i], flag_n);
- i++;
- }
- else readconf_print(argv[i], NULL, flag_n);
+ fail |= !readconf_print(argv[i+1], argv[i], flag_n);
+ i++;
}
- exim_exit(EXIT_SUCCESS);
+ else
+ fail = !readconf_print(argv[i], NULL, flag_n);
+ }
+ exim_exit(fail ? EXIT_FAILURE : EXIT_SUCCESS, US"main");
}
if (list_config)
{
set_process_info("listing config");
- readconf_print(US"config", NULL, flag_n);
- exim_exit(EXIT_SUCCESS);
+ exim_exit(readconf_print(US"config", NULL, flag_n)
+ ? EXIT_SUCCESS : EXIT_FAILURE, US"main");
}
if (msg_action_arg > 0 && msg_action != MSG_LOAD)
{
- if (prod_requires_admin && !admin_user)
+ if (prod_requires_admin && !f.admin_user)
{
fprintf(stderr, "exim: Permission denied\n");
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, US"main");
}
set_process_info("delivering specified messages");
- if (deliver_give_up) forced_delivery = deliver_force_thaw = TRUE;
+ if (deliver_give_up) forced_delivery = f.deliver_force_thaw = TRUE;
for (i = msg_action_arg; i < argc; i++)
{
int status;
{
fprintf(stderr, "failed to fork delivery process for %s: %s\n", argv[i],
strerror(errno));
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, US"main");
}
else wait(&status);
}
- exim_exit(EXIT_SUCCESS);
+ exim_exit(EXIT_SUCCESS, US"main");
}
/* If only a single queue run is requested, without SMTP listening, we can just
turn into a queue runner, with an optional starting message id. */
-if (queue_interval == 0 && !daemon_listen)
+if (queue_interval == 0 && !f.daemon_listen)
{
DEBUG(D_queue_run) debug_printf("Single queue run%s%s%s%s\n",
(start_queue_run_id == NULL)? US"" : US" starting at ",
else
set_process_info("running the queue (single queue run)");
queue_run(start_queue_run_id, stop_queue_run_id, FALSE);
- exim_exit(EXIT_SUCCESS);
+ exim_exit(EXIT_SUCCESS, US"main");
}
/* If user name has not been set by -F, set it from the passwd entry
unless -f has been used to set the sender address by a trusted user. */
- if (originator_name == NULL)
+ if (!originator_name)
{
- if (sender_address == NULL ||
- (!trusted_caller && filter_test == FTEST_NONE))
+ if (!sender_address || (!f.trusted_caller && filter_test == FTEST_NONE))
{
uschar *name = US pw->pw_gecos;
uschar *amp = Ustrchr(name, '&');
replaced by a copy of the login name, and some even specify that
the first character should be upper cased, so that's what we do. */
- if (amp != NULL)
+ if (amp)
{
int loffset;
string_format(buffer, sizeof(buffer), "%.*s%n%s%s",
- amp - name, name, &loffset, originator_login, amp + 1);
+ (int)(amp - name), name, &loffset, originator_login, amp + 1);
buffer[loffset] = toupper(buffer[loffset]);
name = buffer;
}
/* If a pattern for matching the gecos field was supplied, apply
it and then expand the name string. */
- if (gecos_pattern != NULL && gecos_name != NULL)
+ if (gecos_pattern && gecos_name)
{
const pcre *re;
re = regex_must_compile(gecos_pattern, FALSE, TRUE); /* Use malloc */
{
uschar *new_name = expand_string(gecos_name);
expand_nmax = -1;
- if (new_name != NULL)
+ if (new_name)
{
DEBUG(D_receive) debug_printf("user name \"%s\" extracted from "
"gecos field \"%s\"\n", new_name, name);
configuration specifies something to use. When running in the test harness,
any setting of unknown_login overrides the actual name. */
-if (originator_login == NULL || running_in_test_harness)
+if (originator_login == NULL || f.running_in_test_harness)
{
if (unknown_login != NULL)
{
for incoming messages via the daemon. The daemon cannot be run in mua_wrapper
mode. */
-if (daemon_listen || inetd_wait_mode || queue_interval > 0)
+if (f.daemon_listen || f.inetd_wait_mode || queue_interval > 0)
{
if (mua_wrapper)
{
if (test_rewrite_arg >= 0)
{
- really_exim = FALSE;
+ f.really_exim = FALSE;
if (test_rewrite_arg >= argc)
{
printf("-brw needs an address argument\n");
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, US"main");
}
rewrite_test(argv[test_rewrite_arg]);
- exim_exit(EXIT_SUCCESS);
+ exim_exit(EXIT_SUCCESS, US"main");
}
/* A locally-supplied message is considered to be coming from a local user
message via SMTP (inetd invocation or otherwise). */
if ((sender_address == NULL && !smtp_input) ||
- (!trusted_caller && filter_test == FTEST_NONE))
+ (!f.trusted_caller && filter_test == FTEST_NONE))
{
- sender_local = TRUE;
+ f.sender_local = TRUE;
/* A trusted caller can supply authenticated_sender and authenticated_id
via -oMas and -oMai and if so, they will already be set. Otherwise, force
!checking)) /* Not running tests, including filter tests */
{
sender_address = originator_login;
- sender_address_forced = FALSE;
+ f.sender_address_forced = FALSE;
sender_address_domain = 0;
}
}
/* Remember whether an untrusted caller set the sender address */
-sender_set_untrusted = sender_address != originator_login && !trusted_caller;
+f.sender_set_untrusted = sender_address != originator_login && !f.trusted_caller;
/* Ensure that the sender address is fully qualified unless it is the empty
address, which indicates an error message, or doesn't exist (root caller, smtp
stdin. Set debug_level to at least D_v to get full output for address testing.
*/
-if (verify_address_mode || address_test_mode)
+if (verify_address_mode || f.address_test_mode)
{
int exit_value = 0;
int flags = vopt_qualify;
}
route_tidyup();
- exim_exit(exit_value);
+ exim_exit(exit_value, US"main");
}
/* Handle expansion checking. Either expand items on the command line, or read
if (msg_action_arg > 0 && msg_action == MSG_LOAD)
{
uschar spoolname[256]; /* Not big_buffer; used in spool_read_header() */
- if (!admin_user)
- {
- fprintf(stderr, "exim: permission denied\n");
- exit(EXIT_FAILURE);
- }
+ if (!f.admin_user)
+ exim_fail("exim: permission denied\n");
message_id = argv[msg_action_arg];
(void)string_format(spoolname, sizeof(spoolname), "%s-H", message_id);
if ((deliver_datafile = spool_open_datafile(message_id)) < 0)
/* Read a test message from a file. We fudge it up to be on stdin, saving
stdin itself for later reading of expansion strings. */
- else if (expansion_test_message != NULL)
+ else if (expansion_test_message)
{
int save_stdin = dup(0);
int fd = Uopen(expansion_test_message, O_RDONLY, 0);
if (fd < 0)
- {
- fprintf(stderr, "exim: failed to open %s: %s\n", expansion_test_message,
+ exim_fail("exim: failed to open %s: %s\n", expansion_test_message,
strerror(errno));
- return EXIT_FAILURE;
- }
(void) dup2(fd, 0);
filter_test = FTEST_USER; /* Fudge to make it look like filter test */
message_ended = END_NOTENDED;
clearerr(stdin); /* Required by Darwin */
}
+ /* Only admin users may see config-file macros this way */
+
+ if (!f.admin_user) macros_user = macros = mlast = NULL;
+
/* Allow $recipients for this testing */
- enable_dollar_recipients = TRUE;
+ f.enable_dollar_recipients = TRUE;
/* Expand command line items */
if (recipients_arg < argc)
- {
while (recipients_arg < argc)
- {
- uschar *s = argv[recipients_arg++];
- uschar *ss = expand_string(s);
- if (ss == NULL) printf ("Failed: %s\n", expand_string_message);
- else printf("%s\n", CS ss);
- }
- }
+ expansion_test_line(argv[recipients_arg++]);
/* Read stdin */
{
char *(*fn_readline)(const char *) = NULL;
void (*fn_addhist)(const char *) = NULL;
+ uschar * s;
- #ifdef USE_READLINE
+#ifdef USE_READLINE
void *dlhandle = set_readline(&fn_readline, &fn_addhist);
- #endif
+#endif
- for (;;)
- {
- uschar *ss;
- uschar *source = get_stdinput(fn_readline, fn_addhist);
- if (source == NULL) break;
- ss = expand_string(source);
- if (ss == NULL)
- printf ("Failed: %s\n", expand_string_message);
- else printf("%s\n", CS ss);
- }
+ while (s = get_stdinput(fn_readline, fn_addhist))
+ expansion_test_line(s);
- #ifdef USE_READLINE
- if (dlhandle != NULL) dlclose(dlhandle);
- #endif
+#ifdef USE_READLINE
+ if (dlhandle) dlclose(dlhandle);
+#endif
}
/* The data file will be open after -Mset */
deliver_datafile = -1;
}
- exim_exit(EXIT_SUCCESS);
+ exim_exit(EXIT_SUCCESS, US"main: expansion test");
}
uschar *nah = expand_string(raw_active_hostname);
if (nah == NULL)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand \"%s\" "
"(smtp_active_hostname): %s", raw_active_hostname,
expand_string_message);
if (!sender_ident_set)
{
sender_ident = NULL;
- if (running_in_test_harness && sender_host_port != 0 &&
+ if (f.running_in_test_harness && sender_host_port != 0 &&
interface_address != NULL && interface_port != 0)
verify_get_ident(1413);
}
smtp_input = TRUE;
smtp_in = stdin;
smtp_out = stdout;
- sender_local = FALSE;
- sender_host_notsocket = TRUE;
+ f.sender_local = FALSE;
+ f.sender_host_notsocket = TRUE;
debug_file = stderr;
debug_fd = fileno(debug_file);
fprintf(stdout, "\n**** SMTP testing session as if from host %s\n"
}
smtp_log_no_mail();
}
- exim_exit(EXIT_SUCCESS);
+ exim_exit(EXIT_SUCCESS, US"main");
}
{
if (version_printed)
{
+ if (Ustrchr(config_main_filelist, ':'))
+ printf("Configuration file search path is %s\n", config_main_filelist);
printf("Configuration file is %s\n", config_main_filename);
return EXIT_SUCCESS;
}
if (mua_wrapper)
{
- synchronous_delivery = TRUE;
+ f.synchronous_delivery = TRUE;
arg_error_handling = ERRORS_STDERR;
remote_max_parallel = 1;
deliver_drop_privilege = TRUE;
- queue_smtp = FALSE;
+ f.queue_smtp = FALSE;
queue_smtp_domains = NULL;
#ifdef SUPPORT_I18N
message_utf8_downconvert = -1; /* convert-if-needed */
logging being sent down the socket and make an identd call to get the
sender_ident. */
-else if (is_inetd)
+else if (f.is_inetd)
{
(void)fclose(stderr);
exim_nullstd(); /* Re-open to /dev/null */
case when it is forced by -oMa. However, we must flag that it isn't a socket,
so that the test for IP options is skipped for -bs input. */
-if (sender_host_address != NULL && sender_fullhost == NULL)
+if (sender_host_address && !sender_fullhost)
{
host_build_sender_fullhost();
set_process_info("handling incoming connection from %s via -oMa",
sender_fullhost);
- sender_host_notsocket = TRUE;
+ f.sender_host_notsocket = TRUE;
}
/* Otherwise, set the sender host as unknown except for inetd calls. This
prevents host checking in the case of -bs not from inetd and also for -bS. */
-else if (!is_inetd) sender_host_unknown = TRUE;
+else if (!f.is_inetd) f.sender_host_unknown = TRUE;
/* If stdout does not exist, then dup stdin to stdout. This can happen
if exim is started from inetd. In this case fd 0 will be set to the socket,
if (smtp_input)
{
- if (!is_inetd) set_process_info("accepting a local %sSMTP message from <%s>",
+ if (!f.is_inetd) set_process_info("accepting a local %sSMTP message from <%s>",
smtp_batched_input? "batched " : "",
(sender_address!= NULL)? sender_address : originator_login);
}
error code is given.) */
if ((!smtp_input || smtp_batched_input) && !receive_check_fs(0))
- {
- fprintf(stderr, "exim: insufficient disk space\n");
- return EXIT_FAILURE;
- }
+ exim_fail("exim: insufficient disk space\n");
/* If this is smtp input of any kind, real or batched, handle the start of the
SMTP session.
if (!smtp_start_session())
{
mac_smtp_fflush();
- exim_exit(EXIT_SUCCESS);
+ exim_exit(EXIT_SUCCESS, US"smtp_start toplevel");
}
}
else
{
thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
- if (expand_string_message != NULL)
- {
+ if (expand_string_message)
if (thismessage_size_limit == -1)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand "
"message_size_limit: %s", expand_string_message);
else
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "invalid value for "
"message_size_limit: %s", expand_string_message);
- }
}
/* Loop for several messages when reading SMTP input. If we fork any child
As a consequence of this, the waitpid() below is now excluded if we are sure
that SIG_IGN works. */
-if (!synchronous_delivery)
+if (!f.synchronous_delivery)
{
#ifdef SA_NOCLDWAIT
struct sigaction act;
if (smtp_batched_input && acl_not_smtp_start != NULL)
{
uschar *user_msg, *log_msg;
- enable_dollar_recipients = TRUE;
+ f.enable_dollar_recipients = TRUE;
(void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
&user_msg, &log_msg);
- enable_dollar_recipients = FALSE;
+ f.enable_dollar_recipients = FALSE;
}
/* Now get the data for the message */
more = receive_msg(extract_recipients);
if (message_id[0] == 0)
{
+ cancel_cutthrough_connection(TRUE, US"receive dropped");
if (more) goto moreloop;
smtp_log_no_mail(); /* Log no mail if configured */
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, US"receive toplevel");
}
}
else
{
+ cancel_cutthrough_connection(TRUE, US"message setup dropped");
smtp_log_no_mail(); /* Log no mail if configured */
- exim_exit((rc == 0)? EXIT_SUCCESS : EXIT_FAILURE);
+ exim_exit(rc ? EXIT_FAILURE : EXIT_SUCCESS, US"msg setup toplevel");
}
}
/* These options cannot be changed dynamically for non-SMTP messages */
- active_local_sender_retain = local_sender_retain;
- active_local_from_check = local_from_check;
+ f.active_local_sender_retain = local_sender_retain;
+ f.active_local_from_check = local_from_check;
/* Save before any rewriting */
if (error_handling == ERRORS_STDERR)
{
fprintf(stderr, "exim: too many recipients\n");
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, US"main");
}
else
- {
return
moan_to_sender(ERRMESS_TOOMANYRECIP, NULL, NULL, stdin, TRUE)?
errors_sender_rc : EXIT_FAILURE;
- }
#ifdef SUPPORT_I18N
{
allow_utf8_domains = b;
}
#endif
- if (domain == 0 && !allow_unqualified_recipient)
+ if (domain == 0 && !f.allow_unqualified_recipient)
{
recipient = NULL;
errmess = US"unqualified recipient address not allowed";
{
fprintf(stderr, "exim: bad recipient address \"%s\": %s\n",
string_printing(list[i]), errmess);
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, US"main");
}
else
{
if (acl_not_smtp_start)
{
uschar *user_msg, *log_msg;
- enable_dollar_recipients = TRUE;
+ f.enable_dollar_recipients = TRUE;
(void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
&user_msg, &log_msg);
- enable_dollar_recipients = FALSE;
+ f.enable_dollar_recipients = FALSE;
}
/* Pause for a while waiting for input. If none received in that time,
if (!receive_timeout)
{
- struct timeval t = { 30*60, 0 }; /* 30 minutes */
+ struct timeval t = { .tv_sec = 30*60, .tv_usec = 0 }; /* 30 minutes */
fd_set r;
FD_ZERO(&r); FD_SET(0, &r);
for real; when reading the headers of a message for filter testing,
it is TRUE if the headers were terminated by '.' and FALSE otherwise. */
- if (message_id[0] == 0) exim_exit(EXIT_FAILURE);
+ if (message_id[0] == 0) exim_exit(EXIT_FAILURE, US"main");
} /* Non-SMTP message reception */
/* If this is a filter testing run, there are headers in store, but
if (chdir("/")) /* Get away from wherever the user is running this from */
{
DEBUG(D_receive) debug_printf("chdir(\"/\") failed\n");
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, US"main");
}
/* Now we run either a system filter test, or a user filter test, or both.
explicitly. */
if ((filter_test & FTEST_SYSTEM) != 0)
- {
if (!filter_runtest(filter_sfd, filter_test_sfile, TRUE, more))
- exim_exit(EXIT_FAILURE);
- }
+ exim_exit(EXIT_FAILURE, US"main");
memcpy(filter_sn, filter_n, sizeof(filter_sn));
if ((filter_test & FTEST_USER) != 0)
- {
if (!filter_runtest(filter_ufd, filter_test_ufile, FALSE, more))
- exim_exit(EXIT_FAILURE);
- }
+ exim_exit(EXIT_FAILURE, US"main");
- exim_exit(EXIT_SUCCESS);
+ exim_exit(EXIT_SUCCESS, US"main");
}
/* Else act on the result of message reception. We should not get here unless
are ignored. */
if (mua_wrapper)
- local_queue_only = queue_only_policy = deliver_freeze = FALSE;
+ local_queue_only = f.queue_only_policy = f.deliver_freeze = FALSE;
/* Log the queueing here, when it will get a message id attached, but
not if queue_only is set (case 0). Case 1 doesn't happen here (too many
connections). */
- if (local_queue_only) switch(queue_only_reason)
+ if (local_queue_only)
{
- case 2:
- log_write(L_delay_delivery,
- LOG_MAIN, "no immediate delivery: more than %d messages "
- "received in one connection", smtp_accept_queue_per_connection);
- break;
+ cancel_cutthrough_connection(TRUE, US"no delivery; queueing");
+ switch(queue_only_reason)
+ {
+ case 2:
+ log_write(L_delay_delivery,
+ LOG_MAIN, "no immediate delivery: more than %d messages "
+ "received in one connection", smtp_accept_queue_per_connection);
+ break;
- case 3:
- log_write(L_delay_delivery,
- LOG_MAIN, "no immediate delivery: load average %.2f",
- (double)load_average/1000.0);
- break;
+ case 3:
+ log_write(L_delay_delivery,
+ LOG_MAIN, "no immediate delivery: load average %.2f",
+ (double)load_average/1000.0);
+ break;
+ }
}
+ else if (f.queue_only_policy || f.deliver_freeze)
+ cancel_cutthrough_connection(TRUE, US"no delivery; queueing");
+
/* Else do the delivery unless the ACL or local_scan() called for queue only
or froze the message. Always deliver in a separate process. A fork failure is
not a disaster, as the delivery will eventually happen on a subsequent queue
thereby defer the delivery if it tries to use (for example) a cached ldap
connection that the parent has called unbind on. */
- else if (!queue_only_policy && !deliver_freeze)
+ else
{
pid_t pid;
search_tidyup();
if (geteuid() != root_uid && !deliver_drop_privilege && !unprivileged)
{
- (void)child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE,
- 2, US"-Mc", message_id);
+ delivery_re_exec(CEE_EXEC_EXIT);
/* Control does not return here. */
}
if (pid < 0)
{
+ cancel_cutthrough_connection(TRUE, US"delivery fork failed");
log_write(0, LOG_MAIN|LOG_PANIC, "failed to fork automatic delivery "
"process: %s", strerror(errno));
}
+ else
+ {
+ release_cutthrough_connection(US"msg passed for delivery");
- /* In the parent, wait if synchronous delivery is required. This will
- always be the case in MUA wrapper mode. */
+ /* In the parent, wait if synchronous delivery is required. This will
+ always be the case in MUA wrapper mode. */
- else if (synchronous_delivery)
- {
- int status;
- while (wait(&status) != pid);
- if ((status & 0x00ff) != 0)
- log_write(0, LOG_MAIN|LOG_PANIC,
- "process %d crashed with signal %d while delivering %s",
- (int)pid, status & 0x00ff, message_id);
- if (mua_wrapper && (status & 0xffff) != 0) exim_exit(EXIT_FAILURE);
+ if (f.synchronous_delivery)
+ {
+ int status;
+ while (wait(&status) != pid);
+ if ((status & 0x00ff) != 0)
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "process %d crashed with signal %d while delivering %s",
+ (int)pid, status & 0x00ff, message_id);
+ if (mua_wrapper && (status & 0xffff) != 0) exim_exit(EXIT_FAILURE, US"main");
+ }
}
}
store_reset(reset_point);
}
-exim_exit(EXIT_SUCCESS); /* Never returns */
+exim_exit(EXIT_SUCCESS, US"main"); /* Never returns */
return 0; /* To stop compiler warning */
}
+
/* End of exim.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include <arpa/nameser.h>
-/* If arpa/nameser.h defines a maximum name server packet size, use it,
-provided it is greater than 2048. Otherwise go for a default. PACKETSZ was used
-for this, but it seems that NS_PACKETSZ is coming into use. */
-
-#if defined(NS_PACKETSZ) && NS_PACKETSZ >= 2048
- #define MAXPACKET NS_PACKETSZ
-#elif defined(PACKETSZ) && PACKETSZ >= 2048
- #define MAXPACKET PACKETSZ
-#else
- #define MAXPACKET 2048
-#endif
-
/* While IPv6 is still young the definitions of T_AAAA and T_A6 may not be
included in arpa/nameser.h. Fudge them here. */
#include "macros.h"
#include "dbstuff.h"
#include "structs.h"
+#include "blob.h"
#include "globals.h"
#include "hash.h"
#include "functions.h"
#ifdef EXPERIMENTAL_BRIGHTMAIL
# include "bmi_spam.h"
#endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
# include "spf.h"
#endif
#ifdef EXPERIMENTAL_SRS
#endif
/* DANE w/o DNSSEC is useless */
-#if defined(EXPERIMENTAL_DANE) && defined(DISABLE_DNSSEC)
-# undef DISABLE_DNSSEC
+#if defined(SUPPORT_DANE) && defined(DISABLE_DNSSEC)
+# error DANE support requires DNSSEC support
+#endif
+
+/* Some platforms (FreeBSD, OpenBSD, Solaris) do not seem to define this */
+
+#ifndef POLLRDHUP
+# define POLLRDHUP (POLLIN | POLLHUP)
+#endif
+
+/* Some platforms (Darwin) have to define a larger limit on groups membership */
+
+#ifndef EXIM_GROUPLIST_SIZE
+# define EXIM_GROUPLIST_SIZE NGROUPS_MAX
#endif
#endif
BEGIN { pop @INC if $INC[-1] eq '.' };
use FileHandle;
+use File::Basename;
use IPC::Open2;
+if ($ARGV[0] eq '--version') {
+ print basename($0) . ": $0\n",
+ "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+ "perl(runtime): $]\n";
+ exit 0;
+}
+
if (scalar(@ARGV) < 3)
{
print "Usage: exim_checkaccess <IP address> <email address> [exim options]\n";
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "exim.h"
+uschar * spool_directory = NULL; /* dummy for dbstuff.h */
#define max_insize 20000
#define max_outsize 100000
uschar keybuffer[256];
uschar temp_dbmname[512];
uschar real_dbmname[512];
+uschar dirname[512];
uschar *buffer = malloc(max_outsize);
uschar *line = malloc(max_insize);
Ustrcpy(temp_dbmname, argv[arg+1]);
Ustrcat(temp_dbmname, ".dbmbuild_temp");
+Ustrcpy(dirname, temp_dbmname);
+if ((bptr = Ustrrchr(dirname, '/')))
+ *bptr = '\0';
+else
+ Ustrcpy(dirname, ".");
+
/* It is apparently necessary to open with O_RDWR for this to work
with gdbm-1.7.3, though no reading is actually going to be done. */
-EXIM_DBOPEN(temp_dbmname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
+EXIM_DBOPEN(temp_dbmname, dirname, O_RDWR|O_CREAT|O_EXCL, 0644, &d);
if (d == NULL)
{
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
int rc;
struct flock lock_data;
BOOL read_only = flags == O_RDONLY;
-uschar buffer[256];
+uschar * dirname, * filename;
/* The first thing to do is to open a separate file on which to lock. This
ensures that Exim has exclusive use of the database before it even tries to
open it. If there is a database, there should be a lock file in existence. */
-sprintf(CS buffer, "%s/db/%.200s.lockfile", spool_directory, name);
+#ifdef COMPILE_UTILITY
+if ( asprintf(CSS &dirname, "%s/db", spool_directory) < 0
+ || asprintf(CSS &filename, "%s/%s.lockfile", dirname, name) < 0)
+ return NULL;
+#else
+dirname = string_sprintf("%s/db", spool_directory);
+filename = string_sprintf("%s/%s.lockfile", dirname, name);
+#endif
-dbblock->lockfd = Uopen(buffer, flags, 0);
+dbblock->lockfd = Uopen(filename, flags, 0);
if (dbblock->lockfd < 0)
{
- printf("** Failed to open database lock file %s: %s\n", buffer,
+ printf("** Failed to open database lock file %s: %s\n", filename,
strerror(errno));
return NULL;
}
/* Now we must get a lock on the opened lock file; do this with a blocking
lock that times out. */
-lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
+lock_data.l_type = read_only ? F_RDLCK : F_WRLCK;
lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
sigalrm_seen = FALSE;
os_non_restarting_signal(SIGALRM, sigalrm_handler);
-alarm(EXIMDB_LOCK_TIMEOUT);
+ALARM(EXIMDB_LOCK_TIMEOUT);
rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
-alarm(0);
+ALARM_CLR(0);
if (sigalrm_seen) errno = ETIMEDOUT;
if (rc < 0)
{
printf("** Failed to get %s lock for %s: %s",
flags & O_WRONLY ? "write" : "read",
- buffer,
+ filename,
errno == ETIMEDOUT ? "timed out" : strerror(errno));
(void)close(dbblock->lockfd);
return NULL;
/* At this point we have an opened and locked separate lock file, that is,
exclusive access to the database, so we can go ahead and open it. */
-sprintf(CS buffer, "%s/db/%s", spool_directory, name);
-EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));
+#ifdef COMPILE_UTILITY
+if (asprintf(CSS &filename, "%s/%s", dirname, name) < 0) return NULL;
+#else
+filename = string_sprintf("%s/%s", dirname, name);
+#endif
+EXIM_DBOPEN(filename, dirname, flags, 0, &(dbblock->dbptr));
-if (dbblock->dbptr == NULL)
+if (!dbblock->dbptr)
{
- printf("** Failed to open DBM file %s for %s:\n %s%s\n", buffer,
+ printf("** Failed to open DBM file %s for %s:\n %s%s\n", filename,
read_only? "reading" : "writing", strerror(errno),
#ifdef USE_DB
" (or Berkeley DB error while opening)"
dbdata_type = check_args(argc, argv, US"dumpdb", US"");
spool_directory = argv[1];
-dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
-if (dbm == NULL) exit(1);
+if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
+ exit(1);
/* Scan the file, formatting the information for each entry. Note
that data is returned in a malloc'ed block, in order that it be
correctly aligned. */
-key = dbfn_scan(dbm, TRUE, &cursor);
-while (key != NULL)
+for (key = dbfn_scan(dbm, TRUE, &cursor);
+ key;
+ key = dbfn_scan(dbm, FALSE, &cursor))
{
dbdata_retry *retry;
dbdata_wait *wait;
return 1;
}
Ustrcpy(keybuffer, key);
- value = dbfn_read_with_length(dbm, keybuffer, &length);
- if (value == NULL)
+ if (!(value = dbfn_read_with_length(dbm, keybuffer, &length)))
fprintf(stderr, "**** Entry \"%s\" was in the key scan, but the record "
"was not found in the file - something is wrong!\n",
CS keybuffer);
}
store_reset(value);
}
- key = dbfn_scan(dbm, FALSE, &cursor);
}
dbfn_close(dbm);
{
int verify = 1;
spool_directory = argv[1];
- dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
- if (dbm == NULL) continue;
+
+ if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
+ continue;
if (Ustrcmp(field, "d") == 0)
{
/* Handle a read request, or verify after an update. */
spool_directory = argv[1];
- dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE);
- if (dbm == NULL) continue;
+ if (!(dbm = dbfn_open(argv[2], O_RDONLY, &dbblock, FALSE)))
+ continue;
- record = dbfn_read_with_length(dbm, name, &oldlength);
- if (record == NULL)
+ if (!(record = dbfn_read_with_length(dbm, name, &oldlength)))
{
printf("record %s not found\n", name);
name[0] = 0;
printf("Tidying Exim hints database %s/db/%s\n", argv[1], argv[2]);
spool_directory = argv[1];
-dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE);
-if (dbm == NULL) exit(1);
+if (!(dbm = dbfn_open(argv[2], O_RDWR, &dbblock, FALSE)))
+ exit(1);
/* Prepare for building file names */
Therefore, we scan and build a list of all the keys. Then we use that to
read the records and possibly update them. */
-key = dbfn_scan(dbm, TRUE, &cursor);
-while (key != NULL)
+for (key = dbfn_scan(dbm, TRUE, &cursor);
+ key;
+ key = dbfn_scan(dbm, FALSE, &cursor))
{
key_item *k = store_get(sizeof(key_item) + Ustrlen(key));
k->next = keychain;
keychain = k;
Ustrcpy(k->key, key);
- key = dbfn_scan(dbm, FALSE, &cursor);
}
/* Now scan the collected keys and operate on the records, resetting
reset_point = store_get(0);
-while (keychain != NULL)
+while (keychain)
{
dbdata_generic *value;
# X11_LD_LIBRARY
# PROCESSED_FLAG
+#
+if test "x$1" = x--version
+then
+ echo "`basename $0`: $0"
+ echo "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION"
+ exit 0
+fi
# See if caller wants to invoke gdb
#!PERL_COMMAND
-# Copyright (c) 2001-2016 University of Cambridge.
+# Copyright (c) 2001-2017 University of Cambridge.
# See the file NOTICE for conditions of use and distribution.
# Perl script to generate statistics from one or more Exim log files.
=head1 AUTHOR
-There is a web site at http://www.exim.org - this contains details of the
+There is a website at https://www.exim.org - this contains details of the
mailing list exim-users@exim.org.
=head1 TO DO
BEGIN { pop @INC if $INC[-1] eq '.' };
use strict;
use IO::File;
+use File::Basename;
# use Time::Local; # PH/FANF
use POSIX;
+if (@ARGV and $ARGV[0] eq '--version') {
+ print basename($0) . ": $0\n",
+ "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+ "perl(runtime): $]\n";
+ exit 0;
+}
+
use vars qw($HAVE_GD_Graph_pie $HAVE_GD_Graph_linespoints $HAVE_Spreadsheet_WriteExcel);
eval { require GD::Graph::pie; };
$HAVE_GD_Graph_pie = $@ ? 0 : 1;
# POSIX::mktime. We expect the timestamp to be of the form
# "$year-$mon-$day $hour:$min:$sec", with month going from 1 to 12,
# and the year to be absolute (we do the necessary conversions). The
+# seconds value can be followed by decimals, which we ignore. The
# timestamp may be followed with an offset from UTC like "+$hh$mm"; if the
# offset is not present, and we have not been told that the log is in UTC
# (with the -utc option), then we adjust the time by the current local
# Is the timestamp the same as the last one?
return $last_time if ($last_timestamp eq $timestamp);
- return 0 unless ($timestamp =~ /^((\d{4})\-(\d\d)-(\d\d))\s(\d\d):(\d\d):(\d\d)( ([+-])(\d\d)(\d\d))?/o);
+ return 0 unless ($timestamp =~ /^((\d{4})\-(\d\d)-(\d\d))\s(\d\d):(\d\d):(\d\d)(?:\.\d+)?( ([+-])(\d\d)(\d\d))?/o);
unless ($last_date eq $1) {
$last_date = $1;
my $time = $date_seconds + ($5 * 3600) + ($6 * 60) + $7;
# SC. Use caching. Also note we want seconds not minutes.
- #my($this_offset) = ($10 * 60 + $11) * ($9 . "1") if defined $8;
+ #my($this_offset) = ($10 * 60 + $12) * ($9 . "1") if defined $8;
if (defined $8 && ($8 ne $last_offset)) {
$last_offset = $8;
$offset_seconds = ($10 * 60 + $11) * 60;
}
- if (defined $7) {
+ if (defined $8) {
#$time -= $this_offset;
$time -= $offset_seconds;
} elsif (defined $localtime_offset) {
$length = length($_);
next if ($length < 38);
- next unless /^(\\d{4}\\-\\d\\d-\\d\\d\\s(\\d\\d):(\\d\\d):\\d\\d( [-+]\\d\\d\\d\\d)?)( \\[\\d+\\])?/o;
-
- ($tod,$m_hour,$m_min) = ($1,$2,$3);
+ next unless /^
+ (\\d{4}\\-\\d\\d-\\d\\d\\s # 1: YYYYMMDD HHMMSS
+ (\\d\\d) # 2: HH
+ :
+ (\\d\\d) # 3: MM
+ :\\d\\d
+ )
+ (\\.\\d+)? # 4: subseconds
+ (\s[-+]\\d\\d\\d\\d)? # 5: tz-offset
+ (\s\\[\\d+\\])? # 6: pid
+ /ox;
+
+ $tod = defined($5) ? $1 . $5 : $1;
+ ($m_hour,$m_min) = ($2,$3);
# PH - watch for GMT offsets in the timestamp.
- if (defined($4)) {
+ if (defined($5)) {
$extra = 6;
next if ($length < 44);
}
$extra = 0;
}
+ # watch for subsecond precision
+ if (defined($4)) {
+ $extra += length($4);
+ next if ($length < 38 + $extra);
+ }
+
# PH - watch for PID added after the timestamp.
- if (defined($5)) {
- $extra += length($5);
+ if (defined($6)) {
+ $extra += length($6);
next if ($length < 38 + $extra);
}
eximmacdef=
exim_path=
+if test "x$1" = x--version
+then
+ echo "`basename $0`: $0"
+ echo "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION"
+ exit 0
+fi
+
if expr -- $1 : '\-' >/dev/null ; then
while expr -- $1 : '\-' >/dev/null ; do
if [ "$1" = "-C" ]; then
#!PERL_COMMAND
+# Copyright (c) 1995 - 2018 University of Cambridge.
+# See the file NOTICE for conditions of use and distribution.
+
# This variables should be set by the building process
my $spool = 'SPOOL_DIRECTORY'; # may be overridden later
use strict;
BEGIN { pop @INC if $INC[-1] eq '.' };
use Getopt::Long;
+use File::Basename;
my($p_name) = $0 =~ m|/?([^/]+)$|;
my $p_version = "20100323.0";
Getopt::Long::Configure("bundling_override");
GetOptions(
'spool=s' => \$G::spool, # exim spool dir
+ 'C|Config=s' => \$G::config, # use alternative Exim configuration file
'input-dir=s' => \$G::input_dir, # name of the "input" dir
'finput' => \$G::finput, # same as "--input-dir Finput"
'bp' => \$G::mailq_bp, # List the queue (noop - default)
'show-vars=s' => \$G::show_vars, # display the contents of these vars
'just-vars' => \$G::just_vars, # only display vars, no other info
'show-rules' => \$G::show_rules, # display compiled match rules
- 'show-tests' => \$G::show_tests # display tests as applied to each message
+ 'show-tests' => \$G::show_tests, # display tests as applied to each message
+ 'version' => sub {
+ print basename($0) . ": $0\n",
+ "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+ "perl(runtime): $]\n";
+ exit 0;
+ },
) || exit(1);
# if both freeze and thaw specified, only thaw as it is less destructive
$G::caseless = $G::caseful ? 0 : 1; # nocase by default, case if both
@G::recipients_crit = (); # holds per-recip criteria
$spool = defined $G::spool ? $G::spool
- : do { chomp($_ = `$exim -n -bP spool_directory`);
- $_ // $spool };
+ : do { chomp($_ = `$exim @{[defined $G::config ? "-C $G::config" : '']} -n -bP spool_directory`)
+ and $_ or $spool };
my $input_dir = $G::input_dir || ($G::finput ? "Finput" : "input");
my $count_only = 1 if ($G::mailq_bpc || $G::qgrep_c);
my $unsorted = 1 if ($G::mailq_bpr || $G::mailq_bpra ||
} else {
$c[-1]{cmp} .= $G::negate ? " ? 0 : 1" : " ? 1 : 0";
}
- # support the each_* psuedo variables. Steal the criteria off of the
+ # support the each_* pseudo variables. Steal the criteria off of the
# queue for special processing later
if ($c[-1]{var} =~ /^each_(recipients(_(un)?del)?)$/) {
my $var = $1;
Same as -bp, but only show undelivered messages (exim)
+=item -C | --config <config>
+
+Use <config> to determine the proper spool directory. (See C<--spool>
+or C<--input> for alternative ways to specify the directories to operate on.)
+
=item -c
Show a count of matching messages (exiqgrep)
=item --spool <path>
-Set the path to the exim spool to use. This value will have the argument to --input or 'input' appended, or be ignored if --input is a full path. If not specified, exipick uses the value from C<exim -bP spool_directory>, and if this fails, the F<SPOOL_DIRECTORY>
-from build time (F<Local/Makefile>) is used.
+Set the path to the exim spool to use. This value will have the argument to --input or 'input' appended, or be ignored if --input is a full path. If not specified, exipick uses the value from C<exim [-C config] -n -bP spool_directory>, and if this call fails, the F</opt/exim/spool> from build time (F<Local/Makefile>) is used. See also --config.
=item --show-rules
=item S + $each_recipients
-This is a psuedo variable which allows you to apply a test against each address in $recipients individually. Whereas '$recipients =~ /@aol.com/' will match if any recipient address contains aol.com, '$each_recipients =~ /@aol.com$/' will only be true if every recipient matches that pattern. Note that this obeys --and or --or being set. Using it with --or is very similar to just matching against $recipients, but with the added benefit of being able to use anchors at the beginning and end of each recipient address.
+This is a pseudo variable which allows you to apply a test against each address in $recipients individually. Whereas '$recipients =~ /@aol.com/' will match if any recipient address contains aol.com, '$each_recipients =~ /@aol.com$/' will only be true if every recipient matches that pattern. Note that this obeys --and or --or being set. Using it with --or is very similar to just matching against $recipients, but with the added benefit of being able to use anchors at the beginning and end of each recipient address.
=item S + $each_recipients_del
use strict;
BEGIN { pop @INC if $INC[-1] eq '.' };
+
use Getopt::Std;
+use File::Basename;
# Have this variable point to your exim binary.
my $exim = 'BIN_DIRECTORY/exim';
$base = 62;
};
+if ($ARGV[0] eq '--version') {
+ print basename($0) . ": $0\n",
+ "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+ "perl(runtime): $]\n";
+ exit 0;
+}
+
getopts('hf:r:y:o:s:C:zxlibRca',\%opt);
if ($ARGV[0]) { &help; exit;}
if ($opt{h}) { &help; exit;}
use warnings;
BEGIN { pop @INC if $INC[-1] eq '.' };
+use File::Basename;
+
+if (@ARGV && $ARGV[0] eq '--version') {
+ print basename($0) . ": $0\n",
+ "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+ "perl(runtime): $]\n";
+ exit 0;
+}
sub print_volume_rounded {
my($x) = pop @_;
# EXIWHAT_EGREP_ARG
# EXIWHAT_MULTIKILL_CMD
# EXIWHAT_MULTIKILL_ARG
+# RM_COMMAND
# PROCESSED_FLAG
# the script in the next Exim rebuild/install. However, it's best to
# arrange your build-time configuration file to get the correct values.
+rm=RM_COMMAND
+
# Some operating systems have a command that finds processes that match
# certain conditions (by default usually those running specific commands)
# and sends them signals. If such a command is defined for your OS, the
# See if this installation is using the esoteric "USE_NODE" feature of Exim,
# in which it uses the host's name as a suffix for the configuration file name.
+if test "x$1" = x--version
+then
+ echo "`basename $0`: $0"
+ echo "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION"
+ exit 0
+fi
+
if [ "CONFIGURE_FILE_USE_NODE" = "yes" ]; then
hostsuffix=.`uname -n`
fi
# Now do the job.
-/bin/rm -f ${log}
+$rm -f ${log}
if [ -f ${log} ]; then
echo "** Failed to remove ${log}"
exit 1
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
static int_eximarith_t expanded_string_integer(const uschar *, BOOL);
#ifdef STAND_ALONE
-#ifndef SUPPORT_CRYPTEQ
-#define SUPPORT_CRYPTEQ
-#endif
+# ifndef SUPPORT_CRYPTEQ
+# define SUPPORT_CRYPTEQ
+# endif
#endif
#ifdef LOOKUP_LDAP
-#include "lookups/ldap.h"
+# include "lookups/ldap.h"
#endif
#ifdef SUPPORT_CRYPTEQ
-#ifdef CRYPT_H
-#include <crypt.h>
-#endif
-#ifndef HAVE_CRYPT16
+# ifdef CRYPT_H
+# include <crypt.h>
+# endif
+# ifndef HAVE_CRYPT16
extern char* crypt16(char*, char*);
-#endif
+# endif
#endif
/* The handling of crypt16() is a mess. I will record below the analysis of the
static uschar *item_table[] = {
US"acl",
+ US"authresults",
US"certextract",
US"dlfunc",
US"env",
enum {
EITEM_ACL,
+ EITEM_AUTHRESULTS,
EITEM_CERTEXTRACT,
EITEM_DLFUNC,
EITEM_ENV,
{ "address_data", vtype_stringptr, &deliver_address_data },
{ "address_file", vtype_stringptr, &address_file },
{ "address_pipe", vtype_stringptr, &address_pipe },
+#ifdef EXPERIMENTAL_ARC
+ { "arc_domains", vtype_string_func, &fn_arc_domains },
+ { "arc_oldest_pass", vtype_int, &arc_oldest_pass },
+ { "arc_state", vtype_stringptr, &arc_state },
+ { "arc_state_reason", vtype_stringptr, &arc_state_reason },
+#endif
{ "authenticated_fail_id",vtype_stringptr, &authenticated_fail_id },
{ "authenticated_id", vtype_stringptr, &authenticated_id },
{ "authenticated_sender",vtype_stringptr, &authenticated_sender },
{ "dkim_key_testing", vtype_dkim, (void *)DKIM_KEY_TESTING },
{ "dkim_selector", vtype_stringptr, &dkim_signing_selector },
{ "dkim_signers", vtype_stringptr, &dkim_signers },
- { "dkim_verify_reason", vtype_dkim, (void *)DKIM_VERIFY_REASON },
- { "dkim_verify_status", vtype_dkim, (void *)DKIM_VERIFY_STATUS},
+ { "dkim_verify_reason", vtype_stringptr, &dkim_verify_reason },
+ { "dkim_verify_status", vtype_stringptr, &dkim_verify_status },
#endif
#ifdef EXPERIMENTAL_DMARC
- { "dmarc_ar_header", vtype_stringptr, &dmarc_ar_header },
{ "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy },
{ "dmarc_status", vtype_stringptr, &dmarc_status },
{ "dmarc_status_text", vtype_stringptr, &dmarc_status_text },
{ "local_part_data", vtype_stringptr, &deliver_localpart_data },
{ "local_part_prefix", vtype_stringptr, &deliver_localpart_prefix },
{ "local_part_suffix", vtype_stringptr, &deliver_localpart_suffix },
+#ifdef HAVE_LOCAL_SCAN
{ "local_scan_data", vtype_stringptr, &local_scan_data },
+#endif
{ "local_user_gid", vtype_gid, &local_user_gid },
{ "local_user_uid", vtype_uid, &local_user_uid },
{ "localhost_number", vtype_int, &host_number },
{ "received_ip_address", vtype_stringptr, &interface_address },
{ "received_port", vtype_int, &interface_port },
{ "received_protocol", vtype_stringptr, &received_protocol },
- { "received_time", vtype_int, &received_time },
+ { "received_time", vtype_int, &received_time.tv_sec },
{ "recipient_data", vtype_stringptr, &recipient_data },
{ "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure },
{ "recipients", vtype_string_func, &fn_recipients },
{ "regex_match_string", vtype_stringptr, ®ex_match_string },
#endif
{ "reply_address", vtype_reply, NULL },
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ { "requiretls", vtype_bool, &tls_requiretls },
+#endif
{ "return_path", vtype_stringptr, &return_path },
{ "return_size_limit", vtype_int, &bounce_return_size_limit },
{ "router_name", vtype_stringptr, &router_name },
{ "smtp_active_hostname", vtype_stringptr, &smtp_active_hostname },
{ "smtp_command", vtype_stringptr, &smtp_cmd_buffer },
{ "smtp_command_argument", vtype_stringptr, &smtp_cmd_argument },
+ { "smtp_command_history", vtype_string_func, &smtp_cmd_hist },
{ "smtp_count_at_connection_start", vtype_int, &smtp_accept_count },
{ "smtp_notquit_reason", vtype_stringptr, &smtp_notquit_reason },
{ "sn0", vtype_filter_int, &filter_sn[0] },
{ "spam_score", vtype_stringptr, &spam_score },
{ "spam_score_int", vtype_stringptr, &spam_score_int },
#endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
{ "spf_guess", vtype_stringptr, &spf_guess },
{ "spf_header_comment", vtype_stringptr, &spf_header_comment },
{ "spf_received", vtype_stringptr, &spf_received },
{ "spf_result", vtype_stringptr, &spf_result },
+ { "spf_result_guessed", vtype_bool, &spf_result_guessed },
{ "spf_smtp_comment", vtype_stringptr, &spf_smtp_comment },
#endif
{ "spool_directory", vtype_stringptr, &spool_directory },
{ "tls_out_bits", vtype_int, &tls_out.bits },
{ "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
{ "tls_out_cipher", vtype_stringptr, &tls_out.cipher },
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
{ "tls_out_dane", vtype_bool, &tls_out.dane_verified },
#endif
{ "tls_out_ocsp", vtype_int, &tls_out.ocsp },
#if defined(SUPPORT_TLS)
{ "tls_out_sni", vtype_stringptr, &tls_out.sni },
#endif
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
{ "tls_out_tlsa_usage", vtype_int, &tls_out.tlsa_usage },
#endif
static uschar *mtable_sticky[] =
{ US"--T", US"--t", US"-wT", US"-wt", US"r-T", US"r-t", US"rwT", US"rwt" };
+/* flags for find_header() */
+#define FH_EXISTS_ONLY BIT(0)
+#define FH_WANT_RAW BIT(1)
+#define FH_WANT_LIST BIT(2)
/*************************************************
uschar *ss = expand_string(condition);
if (ss == NULL)
{
- if (!expand_string_forcedfail && !search_find_defer)
+ if (!f.expand_string_forcedfail && !f.search_find_defer)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand condition \"%s\" "
"for %s %s: %s", condition, m1, m2, expand_string_message);
return FALSE;
*/
static uschar *
-expand_getkeyed(uschar *key, const uschar *s)
+expand_getkeyed(uschar * key, const uschar * s)
{
int length = Ustrlen(key);
while (isspace(*s)) s++;
/* Loop to search for the key */
-while (*s != 0)
+while (*s)
{
int dkeylength;
- uschar *data;
- const uschar *dkey = s;
+ uschar * data;
+ const uschar * dkey = s;
- while (*s != 0 && *s != '=' && !isspace(*s)) s++;
+ while (*s && *s != '=' && !isspace(*s)) s++;
dkeylength = s - dkey;
while (isspace(*s)) s++;
if (*s == '=') while (isspace((*(++s))));
static uschar *
expand_getlistele(int field, const uschar * list)
{
-const uschar * tlist= list;
-int sep= 0;
+const uschar * tlist = list;
+int sep = 0;
uschar dummy;
-if(field<0)
+if (field < 0)
{
- for(field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++;
- sep= 0;
+ for (field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++;
+ sep = 0;
}
-if(field==0) return NULL;
-while(--field>0 && (string_nextinlist(&list, &sep, &dummy, 1))) ;
+if (field == 0) return NULL;
+while (--field > 0 && (string_nextinlist(&list, &sep, &dummy, 1))) ;
return string_nextinlist(&list, &sep, NULL, 0);
}
/* If value2 is unset, just compute one number */
if (value2 < 0)
- {
- s = string_sprintf("%d", total % value1);
- }
+ s = string_sprintf("%lu", total % value1);
/* Otherwise do a div/mod hash */
else
{
total = total % (value1 * value2);
- s = string_sprintf("%d/%d", total/value2, total % value2);
+ s = string_sprintf("%lu/%lu", total/value2, total % value2);
}
*len = Ustrlen(s);
specific headers that contain lists of addresses, a comma is inserted between
them. Otherwise we use a straight concatenation. Because some messages can have
pathologically large number of lines, there is a limit on the length that is
-returned. Also, to avoid massive store use which would result from using
-string_cat() as it copies and extends strings, we do a preliminary pass to find
-out exactly how much store will be needed. On "normal" messages this will be
-pretty trivial.
+returned.
Arguments:
name the name of the header, without the leading $header_ or $h_,
or NULL if a concatenation of all headers is required
- exists_only TRUE if called from a def: test; don't need to build a string;
- just return a string that is not "" and not "0" if the header
- exists
newsize return the size of memory block that was obtained; may be NULL
if exists_only is TRUE
- want_raw TRUE if called for $rh_ or $rheader_ variables; no processing,
- other than concatenating, will be done on the header. Also used
- for $message_headers_raw.
+ flags FH_EXISTS_ONLY
+ set if called from a def: test; don't need to build a string;
+ just return a string that is not "" and not "0" if the header
+ exists
+ FH_WANT_RAW
+ set if called for $rh_ or $rheader_ items; no processing,
+ other than concatenating, will be done on the header. Also used
+ for $message_headers_raw.
+ FH_WANT_LIST
+ Double colon chars in the content, and replace newline with
+ colon between each element when concatenating; returning a
+ colon-sep list (elements might contain newlines)
charset name of charset to translate MIME words to; used only if
want_raw is false; if NULL, no translation is done (this is
used for $bh_ and $bheader_)
*/
static uschar *
-find_header(uschar *name, BOOL exists_only, int *newsize, BOOL want_raw,
- uschar *charset)
+find_header(uschar *name, int *newsize, unsigned flags, uschar *charset)
{
-BOOL found = name == NULL;
-int comma = 0;
-int len = found? 0 : Ustrlen(name);
-int i;
-uschar *yield = NULL;
-uschar *ptr = NULL;
-
-/* Loop for two passes - saves code repetition */
-
-for (i = 0; i < 2; i++)
- {
- int size = 0;
- header_line *h;
-
- for (h = header_list; size < header_insert_maxlen && h != NULL; h = h->next)
- {
- if (h->type != htype_old && h->text != NULL) /* NULL => Received: placeholder */
+BOOL found = !name;
+int len = name ? Ustrlen(name) : 0;
+BOOL comma = FALSE;
+header_line * h;
+gstring * g = NULL;
+
+for (h = header_list; h; h = h->next)
+ if (h->type != htype_old && h->text) /* NULL => Received: placeholder */
+ if (!name || (len <= h->slen && strncmpic(name, h->text, len) == 0))
{
- if (name == NULL || (len <= h->slen && strncmpic(name, h->text, len) == 0))
- {
- int ilen;
- uschar *t;
-
- if (exists_only) return US"1"; /* don't need actual string */
- found = TRUE;
- t = h->text + len; /* text to insert */
- if (!want_raw) /* unless wanted raw, */
- while (isspace(*t)) t++; /* remove leading white space */
- ilen = h->slen - (t - h->text); /* length to insert */
-
- /* Unless wanted raw, remove trailing whitespace, including the
- newline. */
+ uschar * s, * t;
+ size_t inc;
- if (!want_raw)
- while (ilen > 0 && isspace(t[ilen-1])) ilen--;
+ if (flags & FH_EXISTS_ONLY)
+ return US"1"; /* don't need actual string */
- /* Set comma = 1 if handling a single header and it's one of those
- that contains an address list, except when asked for raw headers. Only
- need to do this once. */
+ found = TRUE;
+ s = h->text + len; /* text to insert */
+ if (!(flags & FH_WANT_RAW)) /* unless wanted raw, */
+ while (isspace(*s)) s++; /* remove leading white space */
+ t = h->text + h->slen; /* end-point */
- if (!want_raw && name != NULL && comma == 0 &&
- Ustrchr("BCFRST", h->type) != NULL)
- comma = 1;
+ /* Unless wanted raw, remove trailing whitespace, including the
+ newline. */
- /* First pass - compute total store needed; second pass - compute
- total store used, including this header. */
-
- size += ilen + comma + 1; /* +1 for the newline */
+ if (flags & FH_WANT_LIST)
+ while (t > s && t[-1] == '\n') t--;
+ else if (!(flags & FH_WANT_RAW))
+ {
+ while (t > s && isspace(t[-1])) t--;
- /* Second pass - concatenate the data, up to a maximum. Note that
- the loop stops when size hits the limit. */
+ /* Set comma if handling a single header and it's one of those
+ that contains an address list, except when asked for raw headers. Only
+ need to do this once. */
- if (i != 0)
- {
- if (size > header_insert_maxlen)
- {
- ilen -= size - header_insert_maxlen - 1;
- comma = 0;
- }
- Ustrncpy(ptr, t, ilen);
- ptr += ilen;
+ if (name && !comma && Ustrchr("BCFRST", h->type)) comma = TRUE;
+ }
- /* For a non-raw header, put in the comma if needed, then add
- back the newline we removed above, provided there was some text in
- the header. */
+ /* Trim the header roughly if we're approaching limits */
+ inc = t - s;
+ if ((g ? g->ptr : 0) + inc > header_insert_maxlen)
+ inc = header_insert_maxlen - (g ? g->ptr : 0);
+
+ /* For raw just copy the data; for a list, add the data as a colon-sep
+ list-element; for comma-list add as an unchecked comma,newline sep
+ list-elemment; for other nonraw add as an unchecked newline-sep list (we
+ stripped trailing WS above including the newline). We ignore the potential
+ expansion due to colon-doubling, just leaving the loop if the limit is met
+ or exceeded. */
+
+ if (flags & FH_WANT_LIST)
+ g = string_append_listele_n(g, ':', s, (unsigned)inc);
+ else if (flags & FH_WANT_RAW)
+ {
+ g = string_catn(g, s, (unsigned)inc);
+ (void) string_from_gstring(g);
+ }
+ else if (inc > 0)
+ if (comma)
+ g = string_append2_listele_n(g, US",\n", s, (unsigned)inc);
+ else
+ g = string_append2_listele_n(g, US"\n", s, (unsigned)inc);
- if (!want_raw && ilen > 0)
- {
- if (comma != 0) *ptr++ = ',';
- *ptr++ = '\n';
- }
- }
- }
+ if (g && g->ptr >= header_insert_maxlen) break;
}
- }
- /* At end of first pass, return NULL if no header found. Then truncate size
- if necessary, and get the buffer to hold the data, returning the buffer size.
- */
-
- if (i == 0)
- {
- if (!found) return NULL;
- if (size > header_insert_maxlen) size = header_insert_maxlen;
- *newsize = size + 1;
- ptr = yield = store_get(*newsize);
- }
- }
+if (!found) return NULL; /* No header found */
+if (!g) return US"";
/* That's all we do for raw header expansion. */
-if (want_raw)
- {
- *ptr = 0;
- }
+*newsize = g->size;
+if (flags & FH_WANT_RAW)
+ return g->s;
-/* Otherwise, remove a final newline and a redundant added comma. Then we do
-RFC 2047 decoding, translating the charset if requested. The rfc2047_decode2()
-function can return an error with decoded data if the charset translation
-fails. If decoding fails, it returns NULL. */
+/* Otherwise do RFC 2047 decoding, translating the charset if requested.
+The rfc2047_decode2() function can return an error with decoded data if the
+charset translation fails. If decoding fails, it returns NULL. */
else
{
uschar *decoded, *error;
- if (ptr > yield && ptr[-1] == '\n') ptr--;
- if (ptr > yield && comma != 0 && ptr[-1] == ',') ptr--;
- *ptr = 0;
- decoded = rfc2047_decode2(yield, check_rfc2047_length, charset, '?', NULL,
+
+ decoded = rfc2047_decode2(g->s, check_rfc2047_length, charset, '?', NULL,
newsize, &error);
- if (error != NULL)
+ if (error)
{
DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
- " input was: %s\n", error, yield);
+ " input was: %s\n", error, g->s);
}
- if (decoded != NULL) yield = decoded;
+ return decoded ? decoded : g->s;
}
+}
-return yield;
+
+
+
+/* Append a "local" element to an Authentication-Results: header
+if this was a non-smtp message.
+*/
+
+static gstring *
+authres_local(gstring * g, const uschar * sysname)
+{
+if (!f.authentication_local)
+ return g;
+g = string_append(g, 3, US";\n\tlocal=pass (non-smtp, ", sysname, US")");
+if (authenticated_id) g = string_append(g, 2, " u=", authenticated_id);
+return g;
}
+/* Append an "iprev" element to an Authentication-Results: header
+if we have attempted to get the calling host's name.
+*/
+
+static gstring *
+authres_iprev(gstring * g)
+{
+if (sender_host_name)
+ g = string_append(g, 3, US";\n\tiprev=pass (", sender_host_name, US")");
+else if (host_lookup_deferred)
+ g = string_catn(g, US";\n\tiprev=temperror", 19);
+else if (host_lookup_failed)
+ g = string_catn(g, US";\n\tiprev=fail", 13);
+else
+ return g;
+
+if (sender_host_address)
+ g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
+return g;
+}
+
/*************************************************
static uschar *
fn_recipients(void)
{
-if (!enable_dollar_recipients) return NULL; else
+uschar * s;
+gstring * g = NULL;
+int i;
+
+if (!f.enable_dollar_recipients) return NULL;
+
+for (i = 0; i < recipients_count; i++)
{
- int size = 128;
- int ptr = 0;
- int i;
- uschar * s = store_get(size);
- for (i = 0; i < recipients_count; i++)
- {
- if (i != 0) s = string_catn(s, &size, &ptr, US", ", 2);
- s = string_cat(s, &size, &ptr, recipients_list[i].address);
- }
- s[ptr] = 0; /* string_cat() leaves room */
- return s;
+ s = recipients_list[i].address;
+ g = string_append2_listele_n(g, US", ", s, Ustrlen(s));
}
+return g ? g->s : NULL;
}
switch (vp->type)
{
case vtype_filter_int:
- if (!filter_running) return NULL;
+ if (!f.filter_running) return NULL;
/* Fall through */
/* VVVVVVVVVVVV */
case vtype_int:
return var_buffer;
case vtype_host_lookup: /* Lookup if not done so */
- if (sender_host_name == NULL && sender_host_address != NULL &&
- !host_lookup_failed && host_name_lookup() == OK)
+ if ( !sender_host_name && sender_host_address
+ && !host_lookup_failed && host_name_lookup() == OK)
host_build_sender_fullhost();
- return (sender_host_name == NULL)? US"" : sender_host_name;
+ return sender_host_name ? sender_host_name : US"";
case vtype_localpart: /* Get local part from address */
s = *((uschar **)(val));
return (domain == NULL)? US"" : domain + 1;
case vtype_msgheaders:
- return find_header(NULL, exists_only, newsize, FALSE, NULL);
+ return find_header(NULL, newsize, exists_only ? FH_EXISTS_ONLY : 0, NULL);
case vtype_msgheaders_raw:
- return find_header(NULL, exists_only, newsize, TRUE, NULL);
+ return find_header(NULL, newsize,
+ exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW, NULL);
case vtype_msgbody: /* Pointer to msgbody string */
case vtype_msgbody_end: /* Ditto, the end of the msg */
ss = (uschar **)(val);
- if (*ss == NULL && deliver_datafile >= 0) /* Read body when needed */
+ if (!*ss && deliver_datafile >= 0) /* Read body when needed */
{
uschar *body;
off_t start_offset = SPOOL_DATA_START_OFFSET;
{ if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; }
}
}
- return (*ss == NULL)? US"" : *ss;
+ return *ss ? *ss : US"";
case vtype_todbsdin: /* BSD inbox time of day */
return tod_stamp(tod_bsdin);
return tod_stamp(tod_log_datestamp_daily);
case vtype_reply: /* Get reply address */
- s = find_header(US"reply-to:", exists_only, newsize, TRUE,
- headers_charset);
- if (s != NULL) while (isspace(*s)) s++;
- if (s == NULL || *s == 0)
+ s = find_header(US"reply-to:", newsize,
+ exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW,
+ headers_charset);
+ if (s) while (isspace(*s)) s++;
+ if (!s || !*s)
{
*newsize = 0; /* For the *s==0 case */
- s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset);
+ s = find_header(US"from:", newsize,
+ exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW,
+ headers_charset);
}
- if (s != NULL)
+ if (s)
{
uschar *t;
while (isspace(*s)) s++;
while (t > s && isspace(t[-1])) t--;
*t = 0;
}
- return (s == NULL)? US"" : s;
+ return s ? s : US"";
case vtype_string_func:
{
case vtype_pspace:
{
int inodes;
- sprintf(CS var_buffer, "%d",
+ sprintf(CS var_buffer, PR_EXIM_ARITH,
receive_statvfs(val == (void *)TRUE, &inodes));
}
return var_buffer;
yield == NULL we are in a skipping state, and don't care about the answer. */
case ECOND_DEF:
- if (*s != ':')
{
- expand_string_message = US"\":\" expected after \"def\"";
- return NULL;
- }
+ uschar * t;
+
+ if (*s != ':')
+ {
+ expand_string_message = US"\":\" expected after \"def\"";
+ return NULL;
+ }
- s = read_name(name, 256, s+1, US"_");
+ s = read_name(name, 256, s+1, US"_");
- /* Test for a header's existence. If the name contains a closing brace
- character, this may be a user error where the terminating colon has been
- omitted. Set a flag to adjust a subsequent error message in this case. */
+ /* Test for a header's existence. If the name contains a closing brace
+ character, this may be a user error where the terminating colon has been
+ omitted. Set a flag to adjust a subsequent error message in this case. */
- if (Ustrncmp(name, "h_", 2) == 0 ||
- Ustrncmp(name, "rh_", 3) == 0 ||
- Ustrncmp(name, "bh_", 3) == 0 ||
- Ustrncmp(name, "header_", 7) == 0 ||
- Ustrncmp(name, "rheader_", 8) == 0 ||
- Ustrncmp(name, "bheader_", 8) == 0)
- {
- s = read_header_name(name, 256, s);
- /* {-for-text-editors */
- if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
- if (yield != NULL) *yield =
- (find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor;
- }
+ if ( ( *(t = name) == 'h'
+ || (*t == 'r' || *t == 'l' || *t == 'b') && *++t == 'h'
+ )
+ && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0)
+ )
+ {
+ s = read_header_name(name, 256, s);
+ /* {-for-text-editors */
+ if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
+ if (yield) *yield =
+ (find_header(name, NULL, FH_EXISTS_ONLY, NULL) != NULL) == testfor;
+ }
- /* Test for a variable's having a non-empty value. A non-existent variable
- causes an expansion failure. */
+ /* Test for a variable's having a non-empty value. A non-existent variable
+ causes an expansion failure. */
- else
- {
- uschar *value = find_variable(name, TRUE, yield == NULL, NULL);
- if (value == NULL)
+ else
{
- expand_string_message = (name[0] == 0)?
- string_sprintf("variable name omitted after \"def:\"") :
- string_sprintf("unknown variable \"%s\" after \"def:\"", name);
- check_variable_error_message(name);
- return NULL;
+ if (!(t = find_variable(name, TRUE, yield == NULL, NULL)))
+ {
+ expand_string_message = (name[0] == 0)?
+ string_sprintf("variable name omitted after \"def:\"") :
+ string_sprintf("unknown variable \"%s\" after \"def:\"", name);
+ check_variable_error_message(name);
+ return NULL;
+ }
+ if (yield) *yield = (t[0] != 0) == testfor;
}
- if (yield != NULL) *yield = (value[0] != 0) == testfor;
- }
- return s;
+ return s;
+ }
/* first_delivery tests for first delivery attempt */
case ECOND_FIRST_DELIVERY:
- if (yield != NULL) *yield = deliver_firsttime == testfor;
+ if (yield != NULL) *yield = f.deliver_firsttime == testfor;
return s;
uschar *sub[10];
uschar *user_msg;
BOOL cond = FALSE;
- int size = 0;
- int ptr = 0;
while (isspace(*s)) s++;
if (*s++ != '{') goto COND_FAILED_CURLY_START; /*}*/
case 3: return NULL;
}
- *resetok = FALSE; /* eval_acl() might allocate; do not reclaim */
- if (yield != NULL) switch(eval_acl(sub, nelem(sub), &user_msg))
+ if (yield != NULL)
+ {
+ *resetok = FALSE; /* eval_acl() might allocate; do not reclaim */
+ switch(eval_acl(sub, nelem(sub), &user_msg))
{
case OK:
cond = TRUE;
case FAIL:
lookup_value = NULL;
if (user_msg)
- {
- lookup_value = string_cat(NULL, &size, &ptr, user_msg);
- lookup_value[ptr] = '\0';
- }
+ lookup_value = string_copy(user_msg);
*yield = cond == testfor;
break;
case DEFER:
- expand_string_forcedfail = TRUE;
+ f.expand_string_forcedfail = TRUE;
/*FALLTHROUGH*/
default:
expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
return NULL;
}
+ }
return s;
}
"after \"%s\"", name);
return NULL;
}
- sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
- honour_dollar, resetok);
- if (sub[i] == NULL) return NULL;
+ if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
+ honour_dollar, resetok)))
+ return NULL;
+ DEBUG(D_expand) if (i == 1 && !sub2_honour_dollar && Ustrchr(sub[1], '$'))
+ debug_printf_indent("WARNING: the second arg is NOT expanded,"
+ " for security reasons\n");
if (*s++ != '}') goto COND_FAILED_CURLY_END;
/* Convert to numerical if required; we know that the names of all the
uschar *save_iterate_item = iterate_item;
int (*compare)(const uschar *, const uschar *);
- DEBUG(D_expand) debug_printf_indent("condition: %s\n", name);
+ DEBUG(D_expand) debug_printf_indent("condition: %s item: %s\n", name, sub[0]);
tempcond = FALSE;
compare = cond_type == ECOND_INLISTI
? strcmpic : (int (*)(const uschar *, const uschar *)) strcmp;
while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)))
+ {
+ DEBUG(D_expand) debug_printf_indent(" compare %s\n", iterate_item);
if (compare(sub[0], iterate_item) == 0)
{
tempcond = TRUE;
break;
}
+ }
iterate_item = save_iterate_item;
}
yes TRUE if the first string is to be used, else use the second
save_lookup a value to put back into lookup_value before the 2nd expansion
sptr points to the input string pointer
- yieldptr points to the output string pointer
- sizeptr points to the output string size
- ptrptr points to the output string pointer
+ yieldptr points to the output growable-string pointer
type "lookup", "if", "extract", "run", "env", "listextract" or
"certextract" for error message
resetok if not NULL, pointer to flag - write FALSE if unsafe to reset
static int
process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, const uschar **sptr,
- uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type, BOOL *resetok)
+ gstring ** yieldptr, uschar *type, BOOL *resetok)
{
int rc = 0;
const uschar *s = *sptr; /* Local value */
if (type[0] == 'i')
{
if (yes && !skipping)
- *yieldptr = string_catn(*yieldptr, sizeptr, ptrptr, US"true", 4);
+ *yieldptr = string_catn(*yieldptr, US"true", 4);
}
else
{
if (yes && lookup_value && !skipping)
- *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value);
+ *yieldptr = string_cat(*yieldptr, lookup_value);
lookup_value = save_lookup;
}
s++;
be the case if we were already skipping). */
sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE, resetok);
-if (sub1 == NULL && (yes || !expand_string_forcedfail)) goto FAILED;
-expand_string_forcedfail = FALSE;
+if (sub1 == NULL && (yes || !f.expand_string_forcedfail)) goto FAILED;
+f.expand_string_forcedfail = FALSE;
if (*s++ != '}')
{
errwhere = US"'yes' part did not end with '}'";
/* If we want the first string, add it to the output */
if (yes)
- *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub1);
+ *yieldptr = string_cat(*yieldptr, sub1);
/* If this is called from a lookup/env or a (cert)extract, we want to restore
$value to what it was at the start of the item, so that it has this value
if (*s == '{')
{
sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok);
- if (sub2 == NULL && (!yes || !expand_string_forcedfail)) goto FAILED;
- expand_string_forcedfail = FALSE;
+ if (sub2 == NULL && (!yes || !f.expand_string_forcedfail)) goto FAILED;
+ f.expand_string_forcedfail = FALSE;
if (*s++ != '}')
{
errwhere = US"'no' part did not start with '{'";
/* If we want the second string, add it to the output */
if (!yes)
- *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub2);
+ *yieldptr = string_cat(*yieldptr, sub2);
}
/* If there is no second string, but the word "fail" is present when the use of
}
expand_string_message =
string_sprintf("\"%s\" failed and \"fail\" requested", type);
- expand_string_forcedfail = TRUE;
+ f.expand_string_forcedfail = TRUE;
goto FAILED;
}
}
static uschar *
prvs_hmac_sha1(uschar *address, uschar *key, uschar *key_num, uschar *daystamp)
{
-uschar *hash_source, *p;
-int size = 0,offset = 0,i;
+gstring * hash_source;
+uschar * p;
+int i;
hctx h;
uschar innerhash[20];
uschar finalhash[20];
if (Ustrlen(key) > 64)
return NULL;
-hash_source = string_catn(NULL, &size, &offset, key_num, 1);
-hash_source = string_catn(hash_source, &size, &offset, daystamp, 3);
-hash_source = string_cat(hash_source, &size, &offset, address);
-hash_source[offset] = '\0';
+hash_source = string_catn(NULL, key_num, 1);
+hash_source = string_catn(hash_source, daystamp, 3);
+hash_source = string_cat(hash_source, address);
+(void) string_from_gstring(hash_source);
-DEBUG(D_expand) debug_printf_indent("prvs: hash source is '%s'\n", hash_source);
+DEBUG(D_expand)
+ debug_printf_indent("prvs: hash source is '%s'\n", hash_source->s);
memset(innerkey, 0x36, 64);
memset(outerkey, 0x5c, 64);
chash_start(HMAC_SHA1, &h);
chash_mid(HMAC_SHA1, &h, innerkey);
-chash_end(HMAC_SHA1, &h, hash_source, offset, innerhash);
+chash_end(HMAC_SHA1, &h, hash_source->s, hash_source->ptr, innerhash);
chash_start(HMAC_SHA1, &h);
chash_mid(HMAC_SHA1, &h, outerkey);
Arguments:
f the FILE
- yield pointer to the expandable string
- sizep pointer to the current size
- ptrp pointer to the current position
+ yield pointer to the expandable string struct
eol newline replacement string, or NULL
-Returns: new value of string pointer
+Returns: new pointer for expandable string, terminated if non-null
*/
-static uschar *
-cat_file(FILE *f, uschar *yield, int *sizep, int *ptrp, uschar *eol)
+static gstring *
+cat_file(FILE *f, gstring *yield, uschar *eol)
{
uschar buffer[1024];
{
int len = Ustrlen(buffer);
if (eol && buffer[len-1] == '\n') len--;
- yield = string_catn(yield, sizep, ptrp, buffer, len);
+ yield = string_catn(yield, buffer, len);
if (eol && buffer[len])
- yield = string_cat(yield, sizep, ptrp, eol);
+ yield = string_cat(yield, eol);
}
-if (yield) yield[*ptrp] = 0;
-
+(void) string_from_gstring(yield);
return yield;
}
+#ifdef SUPPORT_TLS
+static gstring *
+cat_file_tls(void * tls_ctx, gstring * yield, uschar * eol)
+{
+int rc;
+uschar * s;
+uschar buffer[1024];
+
+while ((rc = tls_read(tls_ctx, buffer, sizeof(buffer))) > 0)
+ for (s = buffer; rc--; s++)
+ yield = eol && *s == '\n'
+ ? string_cat(yield, eol) : string_catn(yield, s, 1);
+
+/* We assume that all errors, and any returns of zero bytes,
+are actually EOF. */
+
+(void) string_from_gstring(yield);
+return yield;
+}
+#endif
/*************************************************
+/* Return pointer to dewrapped string, with enclosing specified chars removed.
+The given string is modified on return. Leading whitespace is skipped while
+looking for the opening wrap character, then the rest is scanned for the trailing
+(non-escaped) wrap character. A backslash in the string will act as an escape.
+
+A nul is written over the trailing wrap, and a pointer to the char after the
+leading wrap is returned.
+
+Arguments:
+ s String for de-wrapping
+ wrap Two-char string, the first being the opener, second the closer wrapping
+ character
+Return:
+ Pointer to de-wrapped string, or NULL on error (with expand_string_message set).
+*/
+
+static uschar *
+dewrap(uschar * s, const uschar * wrap)
+{
+uschar * p = s;
+unsigned depth = 0;
+BOOL quotesmode = wrap[0] == wrap[1];
+
+while (isspace(*p)) p++;
+
+if (*p == *wrap)
+ {
+ s = ++p;
+ wrap++;
+ while (*p)
+ {
+ if (*p == '\\') p++;
+ else if (!quotesmode && *p == wrap[-1]) depth++;
+ else if (*p == *wrap)
+ if (depth == 0)
+ {
+ *p = '\0';
+ return s;
+ }
+ else
+ depth--;
+ p++;
+ }
+ }
+expand_string_message = string_sprintf("missing '%c'", *wrap);
+return NULL;
+}
+
+
+/* Pull off the leading array or object element, returning
+a copy in an allocated string. Update the list pointer.
+
+The element may itself be an abject or array.
+*/
+
+uschar *
+json_nextinlist(const uschar ** list)
+{
+unsigned array_depth = 0, object_depth = 0;
+const uschar * s = *list, * item;
+
+while (isspace(*s)) s++;
+
+for (item = s;
+ *s && (*s != ',' || array_depth != 0 || object_depth != 0);
+ s++)
+ switch (*s)
+ {
+ case '[': array_depth++; break;
+ case ']': array_depth--; break;
+ case '{': object_depth++; break;
+ case '}': object_depth--; break;
+ }
+*list = *s ? s+1 : s;
+item = string_copyn(item, s - item);
+DEBUG(D_expand) debug_printf_indent(" json ele: '%s'\n", item);
+return US item;
+}
+
+
+
/*************************************************
* Expand string *
*************************************************/
expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left,
BOOL skipping, BOOL honour_dollar, BOOL *resetok_p)
{
-int ptr = 0;
-int size = Ustrlen(string)+ 64;
-uschar *yield = store_get(size);
+gstring * yield = string_get(Ustrlen(string) + 64);
int item_type;
const uschar *s = string;
uschar *save_expand_nstring[EXPAND_MAXN+1];
expand_level++;
DEBUG(D_expand)
- debug_printf_indent("/%s: %s\n", skipping ? " scanning" : "considering", string);
+ DEBUG(D_noutf8)
+ debug_printf_indent("/%s: %s\n",
+ skipping ? "---scanning" : "considering", string);
+ else
+ debug_printf_indent(UTF8_DOWN_RIGHT "%s: %s\n",
+ skipping
+ ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning"
+ : "considering",
+ string);
-expand_string_forcedfail = FALSE;
+f.expand_string_forcedfail = FALSE;
expand_string_message = US"";
while (*s != 0)
{
const uschar * t = s + 2;
for (s = t; *s != 0; s++) if (*s == '\\' && s[1] == 'N') break;
- yield = string_catn(yield, &size, &ptr, t, s - t);
+ yield = string_catn(yield, t, s - t);
if (*s != 0) s += 2;
}
uschar ch[1];
ch[0] = string_interpret_escape(&s);
s++;
- yield = string_catn(yield, &size, &ptr, ch, 1);
+ yield = string_catn(yield, ch, 1);
}
continue;
if (*s != '$' || !honour_dollar)
{
- yield = string_catn(yield, &size, &ptr, s++, 1);
+ yield = string_catn(yield, s++, 1);
continue;
}
{
int len;
int newsize = 0;
+ gstring * g = NULL;
+ uschar * t;
s = read_name(name, sizeof(name), s, US"_");
/* If this is the first thing to be expanded, release the pre-allocated
buffer. */
- if (ptr == 0 && yield != NULL)
+ if (!yield)
+ g = store_get(sizeof(gstring));
+ else if (yield->ptr == 0)
{
if (resetok) store_reset(yield);
yield = NULL;
- size = 0;
+ g = store_get(sizeof(gstring)); /* alloc _before_ calling find_variable() */
}
/* Header */
- if (Ustrncmp(name, "h_", 2) == 0 ||
- Ustrncmp(name, "rh_", 3) == 0 ||
- Ustrncmp(name, "bh_", 3) == 0 ||
- Ustrncmp(name, "header_", 7) == 0 ||
- Ustrncmp(name, "rheader_", 8) == 0 ||
- Ustrncmp(name, "bheader_", 8) == 0)
+ if ( ( *(t = name) == 'h'
+ || (*t == 'r' || *t == 'l' || *t == 'b') && *++t == 'h'
+ )
+ && (*++t == '_' || Ustrncmp(t, "eader_", 6) == 0)
+ )
{
- BOOL want_raw = (name[0] == 'r')? TRUE : FALSE;
- uschar *charset = (name[0] == 'b')? NULL : headers_charset;
+ unsigned flags = *name == 'r' ? FH_WANT_RAW
+ : *name == 'l' ? FH_WANT_RAW|FH_WANT_LIST
+ : 0;
+ uschar * charset = *name == 'b' ? NULL : headers_charset;
+
s = read_header_name(name, sizeof(name), s);
- value = find_header(name, FALSE, &newsize, want_raw, charset);
+ value = find_header(name, &newsize, flags, charset);
/* If we didn't find the header, and the header contains a closing brace
character, this may be a user error where the terminating colon
has been omitted. Set a flag to adjust the error message in this case.
But there is no error here - nothing gets inserted. */
- if (value == NULL)
+ if (!value)
{
if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
continue;
size of that buffer. If this is the first thing in an expansion string,
yield will be NULL; just point it at the new store instead of copying. Many
expansion strings contain just one reference, so this is a useful
- optimization, especially for humungous headers. */
+ optimization, especially for humungous headers. We need to use a gstring
+ structure that is not allocated after that new-buffer, else a later store
+ reset in the middle of the buffer will make it inaccessible. */
len = Ustrlen(value);
- if (yield == NULL && newsize != 0)
+ if (!yield && newsize != 0)
{
- yield = value;
- size = newsize;
- ptr = len;
+ yield = g;
+ yield->size = newsize;
+ yield->ptr = len;
+ yield->s = value;
}
- else yield = string_catn(yield, &size, &ptr, value, len);
+ else
+ yield = string_catn(yield, value, len);
continue;
}
int n;
s = read_cnumber(&n, s);
if (n >= 0 && n <= expand_nmax)
- yield = string_catn(yield, &size, &ptr, expand_nstring[n],
- expand_nlength[n]);
+ yield = string_catn(yield, expand_nstring[n], expand_nlength[n]);
continue;
}
goto EXPAND_FAILED;
}
if (n >= 0 && n <= expand_nmax)
- yield = string_catn(yield, &size, &ptr, expand_nstring[n],
- expand_nlength[n]);
+ yield = string_catn(yield, expand_nstring[n], expand_nlength[n]);
continue;
}
DEBUG(D_expand)
debug_printf_indent("acl expansion yield: %s\n", user_msg);
if (user_msg)
- yield = string_cat(yield, &size, &ptr, user_msg);
+ yield = string_cat(yield, user_msg);
continue;
case DEFER:
- expand_string_forcedfail = TRUE;
+ f.expand_string_forcedfail = TRUE;
/*FALLTHROUGH*/
default:
expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
}
}
+ case EITEM_AUTHRESULTS:
+ /* ${authresults {mysystemname}} */
+ {
+ uschar *sub_arg[1];
+
+ switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name,
+ &resetok))
+ {
+ case 1: goto EXPAND_FAILED_CURLY;
+ case 2:
+ case 3: goto EXPAND_FAILED;
+ }
+
+ yield = string_append(yield, 3,
+ US"Authentication-Results: ", sub_arg[0], US"; none");
+ yield->ptr -= 6;
+
+ yield = authres_local(yield, sub_arg[0]);
+ yield = authres_iprev(yield);
+ yield = authres_smtpauth(yield);
+#ifdef SUPPORT_SPF
+ yield = authres_spf(yield);
+#endif
+#ifndef DISABLE_DKIM
+ yield = authres_dkim(yield);
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ yield = authres_dmarc(yield);
+#endif
+#ifdef EXPERIMENTAL_ARC
+ yield = authres_arc(yield);
+#endif
+ continue;
+ }
+
/* Handle conditionals - preserve the values of the numerical expansion
variables in case they get changed by a regular expression match in the
condition. If not, they retain their external settings. At the end
if (next_s == NULL) goto EXPAND_FAILED; /* message already set */
DEBUG(D_expand)
- {
- debug_printf_indent("|__condition: %.*s\n", (int)(next_s - s), s);
- debug_printf_indent("|_____result: %s\n", cond ? "true" : "false");
- }
+ DEBUG(D_noutf8)
+ {
+ debug_printf_indent("|--condition: %.*s\n", (int)(next_s - s), s);
+ debug_printf_indent("|-----result: %s\n", cond ? "true" : "false");
+ }
+ else
+ {
+ debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+ "condition: %.*s\n",
+ (int)(next_s - s), s);
+ debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+ "result: %s\n",
+ cond ? "true" : "false");
+ }
s = next_s;
lookup_value, /* value to reset for string2 */
&s, /* input pointer */
&yield, /* output pointer */
- &size, /* output size */
- &ptr, /* output current point */
US"if", /* condition type */
&resetok))
{
if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset,
sub_arg[1][0], sub_arg[2], &expand_string_message)))
goto EXPAND_FAILED;
- yield = string_cat(yield, &size, &ptr, encoded);
+ yield = string_cat(yield, encoded);
}
continue;
}
}
lookup_value = search_find(handle, filename, key, partial, affix,
affixlen, starflags, &expand_setup);
- if (search_find_defer)
+ if (f.search_find_defer)
{
expand_string_message =
string_sprintf("lookup of \"%s\" gave DEFER: %s",
save_lookup_value, /* value to reset for string2 */
&s, /* input pointer */
&yield, /* output pointer */
- &size, /* output size */
- &ptr, /* output current point */
US"lookup", /* condition type */
&resetok))
{
#else /* EXIM_PERL */
{
uschar *sub_arg[EXIM_PERL_MAX_ARGS + 2];
- uschar *new_yield;
+ gstring *new_yield;
if ((expand_forbid & RDO_PERL) != 0)
{
/* Call the function */
sub_arg[EXIM_PERL_MAX_ARGS + 1] = NULL;
- new_yield = call_perl_cat(yield, &size, &ptr, &expand_string_message,
+ new_yield = call_perl_cat(yield, &expand_string_message,
sub_arg[0], sub_arg + 1);
/* NULL yield indicates failure; if the message pointer has been set to
expand_string_message =
string_sprintf("Perl subroutine \"%s\" returned undef to force "
"failure", sub_arg[0]);
- expand_string_forcedfail = TRUE;
+ f.expand_string_forcedfail = TRUE;
}
goto EXPAND_FAILED;
}
/* Yield succeeded. Ensure forcedfail is unset, just in case it got
set during a callback from Perl. */
- expand_string_forcedfail = FALSE;
+ f.expand_string_forcedfail = FALSE;
yield = new_yield;
continue;
}
if (skipping) continue;
/* sub_arg[0] is the address */
- domain = Ustrrchr(sub_arg[0],'@');
- if ( (domain == NULL) || (domain == sub_arg[0]) || (Ustrlen(domain) == 1) )
+ if ( !(domain = Ustrrchr(sub_arg[0],'@'))
+ || domain == sub_arg[0] || Ustrlen(domain) == 1)
{
expand_string_message = US"prvs first argument must be a qualified email address";
goto EXPAND_FAILED;
}
- /* Calculate the hash. The second argument must be a single-digit
+ /* Calculate the hash. The third argument must be a single-digit
key number, or unset. */
- if (sub_arg[2] != NULL &&
- (!isdigit(sub_arg[2][0]) || sub_arg[2][1] != 0))
+ if ( sub_arg[2]
+ && (!isdigit(sub_arg[2][0]) || sub_arg[2][1] != 0))
{
- expand_string_message = US"prvs second argument must be a single digit";
+ expand_string_message = US"prvs third argument must be a single digit";
goto EXPAND_FAILED;
}
- p = prvs_hmac_sha1(sub_arg[0],sub_arg[1],sub_arg[2],prvs_daystamp(7));
- if (p == NULL)
+ p = prvs_hmac_sha1(sub_arg[0], sub_arg[1], sub_arg[2], prvs_daystamp(7));
+ if (!p)
{
expand_string_message = US"prvs hmac-sha1 conversion failed";
goto EXPAND_FAILED;
/* Now separate the domain from the local part */
*domain++ = '\0';
- yield = string_catn(yield, &size, &ptr, US"prvs=", 5);
- yield = string_catn(yield, &size, &ptr, sub_arg[2] ? sub_arg[2] : US"0", 1);
- yield = string_catn(yield, &size, &ptr, prvs_daystamp(7), 3);
- yield = string_catn(yield, &size, &ptr, p, 6);
- yield = string_catn(yield, &size, &ptr, US"=", 1);
- yield = string_cat (yield, &size, &ptr, sub_arg[0]);
- yield = string_catn(yield, &size, &ptr, US"@", 1);
- yield = string_cat (yield, &size, &ptr, domain);
+ yield = string_catn(yield, US"prvs=", 5);
+ yield = string_catn(yield, sub_arg[2] ? sub_arg[2] : US"0", 1);
+ yield = string_catn(yield, prvs_daystamp(7), 3);
+ yield = string_catn(yield, p, 6);
+ yield = string_catn(yield, US"=", 1);
+ yield = string_cat (yield, sub_arg[0]);
+ yield = string_catn(yield, US"@", 1);
+ yield = string_cat (yield, domain);
continue;
}
case EITEM_PRVSCHECK:
{
uschar *sub_arg[3];
- int mysize = 0, myptr = 0;
+ gstring * g;
const pcre *re;
uschar *p;
DEBUG(D_expand) debug_printf_indent("prvscheck domain: %s\n", domain);
/* Set up expansion variables */
- prvscheck_address = string_cat (NULL, &mysize, &myptr, local_part);
- prvscheck_address = string_catn(prvscheck_address, &mysize, &myptr, US"@", 1);
- prvscheck_address = string_cat (prvscheck_address, &mysize, &myptr, domain);
- prvscheck_address[myptr] = '\0';
+ g = string_cat (NULL, local_part);
+ g = string_catn(g, US"@", 1);
+ g = string_cat (g, domain);
+ prvscheck_address = string_from_gstring(g);
prvscheck_keynum = string_copy(key_num);
/* Now expand the second argument */
p = prvs_hmac_sha1(prvscheck_address, sub_arg[0], prvscheck_keynum,
daystamp);
- if (p == NULL)
+ if (!p)
{
expand_string_message = US"hmac-sha1 conversion failed";
goto EXPAND_FAILED;
(void)sscanf(CS now,"%u",&inow);
(void)sscanf(CS daystamp,"%u",&iexpire);
- /* When "iexpire" is < 7, a "flip" has occured.
+ /* When "iexpire" is < 7, a "flip" has occurred.
Adjust "inow" accordingly. */
if ( (iexpire < 7) && (inow >= 993) ) inow = 0;
prvscheck_result = US"1";
DEBUG(D_expand) debug_printf_indent("prvscheck: success, $pvrs_result set to 1\n");
}
- else
+ else
{
prvscheck_result = NULL;
DEBUG(D_expand) debug_printf_indent("prvscheck: signature expired, $pvrs_result unset\n");
case 3: goto EXPAND_FAILED;
}
- yield = string_cat(yield, &size, &ptr,
+ yield = string_cat(yield,
!sub_arg[0] || !*sub_arg[0] ? prvscheck_address : sub_arg[0]);
/* Reset the "internal" variables afterwards, because they are in
/* Open the file and read it */
- f = Ufopen(sub_arg[0], "rb");
- if (f == NULL)
+ if (!(f = Ufopen(sub_arg[0], "rb")))
{
expand_string_message = string_open_failed(errno, "%s", sub_arg[0]);
goto EXPAND_FAILED;
}
- yield = cat_file(f, yield, &size, &ptr, sub_arg[1]);
+ yield = cat_file(f, yield, sub_arg[1]);
(void)fclose(f);
continue;
}
- /* Handle "readsocket" to insert data from a Unix domain socket */
+ /* Handle "readsocket" to insert data from a socket, either
+ Inet or Unix domain */
case EITEM_READSOCK:
{
int fd;
int timeout = 5;
- int save_ptr = ptr;
- FILE *f;
- struct sockaddr_un sockun; /* don't call this "sun" ! */
- uschar *arg;
- uschar *sub_arg[4];
-
- if ((expand_forbid & RDO_READSOCK) != 0)
+ int save_ptr = yield->ptr;
+ FILE * fp;
+ uschar * arg;
+ uschar * sub_arg[4];
+ uschar * server_name = NULL;
+ host_item host;
+ BOOL do_shutdown = TRUE;
+ BOOL do_tls = FALSE; /* Only set under SUPPORT_TLS */
+ void * tls_ctx = NULL; /* ditto */
+ blob reqstr;
+
+ if (expand_forbid & RDO_READSOCK)
{
expand_string_message = US"socket insertions are not permitted";
goto EXPAND_FAILED;
case 3: goto EXPAND_FAILED;
}
- /* Sort out timeout, if given */
+ /* Grab the request string, if any */
+
+ reqstr.data = sub_arg[1];
+ reqstr.len = Ustrlen(sub_arg[1]);
+
+ /* Sort out timeout, if given. The second arg is a list with the first element
+ being a time value. Any more are options of form "name=value". Currently the
+ only option recognised is "shutdown". */
- if (sub_arg[2] != NULL)
+ if (sub_arg[2])
{
- timeout = readconf_readtime(sub_arg[2], 0, FALSE);
- if (timeout < 0)
+ const uschar * list = sub_arg[2];
+ uschar * item;
+ int sep = 0;
+
+ item = string_nextinlist(&list, &sep, NULL, 0);
+ if ((timeout = readconf_readtime(item, 0, FALSE)) < 0)
{
- expand_string_message = string_sprintf("bad time value %s",
- sub_arg[2]);
+ expand_string_message = string_sprintf("bad time value %s", item);
goto EXPAND_FAILED;
}
+
+ while ((item = string_nextinlist(&list, &sep, NULL, 0)))
+ if (Ustrncmp(item, US"shutdown=", 9) == 0)
+ { if (Ustrcmp(item + 9, US"no") == 0) do_shutdown = FALSE; }
+#ifdef SUPPORT_TLS
+ else if (Ustrncmp(item, US"tls=", 4) == 0)
+ { if (Ustrcmp(item + 9, US"no") != 0) do_tls = TRUE; }
+#endif
}
- else sub_arg[3] = NULL; /* No eol if no timeout */
+ else
+ sub_arg[3] = NULL; /* No eol if no timeout */
/* If skipping, we don't actually do anything. Otherwise, arrange to
connect to either an IP or a Unix socket. */
if (Ustrncmp(sub_arg[0], "inet:", 5) == 0)
{
int port;
- uschar *server_name = sub_arg[0] + 5;
- uschar *port_name = Ustrrchr(server_name, ':');
+ uschar * port_name;
+
+ server_name = sub_arg[0] + 5;
+ port_name = Ustrrchr(server_name, ':');
/* Sort out the port */
- if (port_name == NULL)
+ if (!port_name)
{
expand_string_message =
string_sprintf("missing port for readsocket %s", sub_arg[0]);
else
{
struct servent *service_info = getservbyname(CS port_name, "tcp");
- if (service_info == NULL)
+ if (!service_info)
{
expand_string_message = string_sprintf("unknown port \"%s\"",
port_name);
port = ntohs(service_info->s_port);
}
+ /*XXX we trust that the request is idempotent. Hmm. */
fd = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
- timeout, NULL, &expand_string_message);
+ timeout, &host, &expand_string_message,
+ do_tls ? NULL : &reqstr);
callout_address = NULL;
if (fd < 0)
- goto SOCK_FAIL;
+ goto SOCK_FAIL;
+ if (!do_tls)
+ reqstr.len = 0;
}
/* Handle a Unix domain socket */
else
{
+ struct sockaddr_un sockun; /* don't call this "sun" ! */
int rc;
+
if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
{
expand_string_message = string_sprintf("failed to create socket: %s",
sockun.sun_family = AF_UNIX;
sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
sub_arg[0]);
+ server_name = US sockun.sun_path;
sigalrm_seen = FALSE;
- alarm(timeout);
+ ALARM(timeout);
rc = connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun));
- alarm(0);
+ ALARM_CLR(0);
if (sigalrm_seen)
{
expand_string_message = US "socket connect timed out";
"%s: %s", sub_arg[0], strerror(errno));
goto SOCK_FAIL;
}
+ host.name = server_name;
+ host.address = US"";
}
DEBUG(D_expand) debug_printf_indent("connected to socket %s\n", sub_arg[0]);
+#ifdef SUPPORT_TLS
+ if (do_tls)
+ {
+ tls_support tls_dummy = {.sni=NULL};
+ uschar * errstr;
+
+ if (!(tls_ctx = tls_client_start(fd, &host, NULL, NULL,
+# ifdef SUPPORT_DANE
+ NULL,
+# endif
+ &tls_dummy, &errstr)))
+ {
+ expand_string_message = string_sprintf("TLS connect failed: %s", errstr);
+ goto SOCK_FAIL;
+ }
+ }
+#endif
+
/* Allow sequencing of test actions */
- if (running_in_test_harness) millisleep(100);
+ if (f.running_in_test_harness) millisleep(100);
- /* Write the request string, if not empty */
+ /* Write the request string, if not empty or already done */
- if (sub_arg[1][0] != 0)
+ if (reqstr.len)
{
- int len = Ustrlen(sub_arg[1]);
DEBUG(D_expand) debug_printf_indent("writing \"%s\" to socket\n",
- sub_arg[1]);
- if (write(fd, sub_arg[1], len) != len)
+ reqstr.data);
+ if ( (
+#ifdef SUPPORT_TLS
+ tls_ctx ? tls_write(tls_ctx, reqstr.data, reqstr.len, FALSE) :
+#endif
+ write(fd, reqstr.data, reqstr.len)) != reqstr.len)
{
expand_string_message = string_sprintf("request write to socket "
"failed: %s", strerror(errno));
recognise that it is their turn to do some work. Just in case some
system doesn't have this function, make it conditional. */
- #ifdef SHUT_WR
- shutdown(fd, SHUT_WR);
- #endif
+#ifdef SHUT_WR
+ if (!tls_ctx && do_shutdown) shutdown(fd, SHUT_WR);
+#endif
- if (running_in_test_harness) millisleep(100);
+ if (f.running_in_test_harness) millisleep(100);
/* Now we need to read from the socket, under a timeout. The function
that reads a file can be used. */
- f = fdopen(fd, "rb");
+ if (!tls_ctx)
+ fp = fdopen(fd, "rb");
sigalrm_seen = FALSE;
- alarm(timeout);
- yield = cat_file(f, yield, &size, &ptr, sub_arg[3]);
- alarm(0);
- (void)fclose(f);
+ ALARM(timeout);
+ yield =
+#ifdef SUPPORT_TLS
+ tls_ctx ? cat_file_tls(tls_ctx, yield, sub_arg[3]) :
+#endif
+ cat_file(fp, yield, sub_arg[3]);
+ ALARM_CLR(0);
+
+#ifdef SUPPORT_TLS
+ if (tls_ctx)
+ {
+ tls_close(tls_ctx, TRUE);
+ close(fd);
+ }
+ else
+#endif
+ (void)fclose(fp);
/* After a timeout, we restore the pointer in the result, that is,
make sure we add nothing from the socket. */
if (sigalrm_seen)
{
- ptr = save_ptr;
+ yield->ptr = save_ptr;
expand_string_message = US "socket read timed out";
goto SOCK_FAIL;
}
while (isspace(*s)) s++;
}
- readsock_done:
+ READSOCK_DONE:
if (*s++ != '}')
{
expand_string_message = US"missing '}' closing readsocket";
socket, or timeout on reading. If another substring follows, expand and
use it. Otherwise, those conditions give expand errors. */
- SOCK_FAIL:
+ SOCK_FAIL:
if (*s != '{') goto EXPAND_FAILED;
DEBUG(D_any) debug_printf("%s\n", expand_string_message);
if (!(arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok)))
goto EXPAND_FAILED;
- yield = string_cat(yield, &size, &ptr, arg);
+ yield = string_cat(yield, arg);
if (*s++ != '}')
{
expand_string_message = US"missing '}' closing failstring for readsocket";
goto EXPAND_FAILED_CURLY;
}
while (isspace(*s)) s++;
- goto readsock_done;
+ goto READSOCK_DONE;
}
/* Handle "run" to execute a program. */
const uschar **argv;
pid_t pid;
int fd_in, fd_out;
- int lsize = 0, lptr = 0;
if ((expand_forbid & RDO_RUN) != 0)
{
resetok = FALSE;
f = fdopen(fd_out, "rb");
sigalrm_seen = FALSE;
- alarm(60);
- lookup_value = cat_file(f, NULL, &lsize, &lptr, NULL);
- alarm(0);
+ ALARM(60);
+ lookup_value = string_from_gstring(cat_file(f, NULL, NULL));
+ ALARM_CLR(0);
(void)fclose(f);
/* Wait for the process to finish, applying the timeout, and inspect its
lookup_value, /* value to reset for string2 */
&s, /* input pointer */
&yield, /* output pointer */
- &size, /* output size */
- &ptr, /* output current point */
US"run", /* condition type */
&resetok))
{
case EITEM_TR:
{
- int oldptr = ptr;
+ int oldptr = yield->ptr;
int o2m;
uschar *sub[3];
case 3: goto EXPAND_FAILED;
}
- yield = string_cat(yield, &size, &ptr, sub[0]);
+ yield = string_cat(yield, sub[0]);
o2m = Ustrlen(sub[2]) - 1;
- if (o2m >= 0) for (; oldptr < ptr; oldptr++)
+ if (o2m >= 0) for (; oldptr < yield->ptr; oldptr++)
{
- uschar *m = Ustrrchr(sub[1], yield[oldptr]);
+ uschar *m = Ustrrchr(sub[1], yield->s[oldptr]);
if (m != NULL)
{
int o = m - sub[1];
- yield[oldptr] = sub[2][(o < o2m)? o : o2m];
+ yield->s[oldptr] = sub[2][(o < o2m)? o : o2m];
}
}
extract_substr(sub[2], val[0], val[1], &len);
if (ret == NULL) goto EXPAND_FAILED;
- yield = string_catn(yield, &size, &ptr, ret, len);
+ yield = string_catn(yield, ret, len);
continue;
}
DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n",
sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex);
- yield = string_catn(yield, &size, &ptr, finalhash_hex, hashlen*2);
+ yield = string_catn(yield, finalhash_hex, hashlen*2);
}
continue;
}
emptyopt = 0;
continue;
}
- yield = string_catn(yield, &size, &ptr, subject+moffset, slen-moffset);
+ yield = string_catn(yield, subject+moffset, slen-moffset);
break;
}
/* Copy the characters before the match, plus the expanded insertion. */
- yield = string_catn(yield, &size, &ptr, subject + moffset,
- ovector[0] - moffset);
+ yield = string_catn(yield, subject + moffset, ovector[0] - moffset);
insert = expand_string(sub[2]);
if (insert == NULL) goto EXPAND_FAILED;
- yield = string_cat(yield, &size, &ptr, insert);
+ yield = string_cat(yield, insert);
moffset = ovector[1];
moffsetextra = 0;
uschar *sub[3];
int save_expand_nmax =
save_expand_strings(save_expand_nstring, save_expand_nlength);
+ enum {extract_basic, extract_json} fmt = extract_basic;
+
+ while (isspace(*s)) s++;
+
+ /* Check for a format-variant specifier */
+
+ if (*s != '{') /*}*/
+ {
+ if (Ustrncmp(s, "json", 4) == 0) {fmt = extract_json; s += 4;}
+ }
/* While skipping we cannot rely on the data for expansions being
available (eg. $item) hence cannot decide on numeric vs. keyed.
if (skipping)
{
- while (isspace(*s)) s++;
- for (j = 5; j > 0 && *s == '{'; j--)
+ for (j = 5; j > 0 && *s == '{'; j--) /*'}'*/
{
if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))
- goto EXPAND_FAILED; /*{*/
+ goto EXPAND_FAILED; /*'{'*/
if (*s++ != '}')
{
expand_string_message = US"missing '{' for arg of extract";
}
while (isspace(*s)) s++;
}
- if ( Ustrncmp(s, "fail", 4) == 0
+ if ( Ustrncmp(s, "fail", 4) == 0 /*'{'*/
&& (s[4] == '}' || s[4] == ' ' || s[4] == '\t' || !s[4])
)
{
s += 4;
while (isspace(*s)) s++;
- }
+ } /*'{'*/
if (*s != '}')
{
expand_string_message = US"missing '}' closing extract";
else for (i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */
{
- while (isspace(*s)) s++;
- if (*s == '{') /*}*/
+ while (isspace(*s)) s++;
+ if (*s == '{') /*'}'*/
{
sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
- if (sub[i] == NULL) goto EXPAND_FAILED; /*{*/
+ if (sub[i] == NULL) goto EXPAND_FAILED; /*'{'*/
if (*s++ != '}')
{
expand_string_message = string_sprintf(
/* After removal of leading and trailing white space, the first
argument must not be empty; if it consists entirely of digits
(optionally preceded by a minus sign), this is a numerical
- extraction, and we expect 3 arguments. */
+ extraction, and we expect 3 arguments (normal) or 2 (json). */
if (i == 0)
{
if (*p == 0)
{
field_number *= x;
- j = 3; /* Need 3 args */
+ if (fmt != extract_json) j = 3; /* Need 3 args */
field_number_set = TRUE;
}
}
/* Extract either the numbered or the keyed substring into $value. If
skipping, just pretend the extraction failed. */
- lookup_value = skipping? NULL : field_number_set?
- expand_gettokened(field_number, sub[1], sub[2]) :
- expand_getkeyed(sub[0], sub[1]);
+ if (skipping)
+ lookup_value = NULL;
+ else switch (fmt)
+ {
+ case extract_basic:
+ lookup_value = field_number_set
+ ? expand_gettokened(field_number, sub[1], sub[2])
+ : expand_getkeyed(sub[0], sub[1]);
+ break;
+
+ case extract_json:
+ {
+ uschar * s, * item;
+ const uschar * list;
+
+ /* Array: Bracket-enclosed and comma-separated.
+ Object: Brace-enclosed, comma-sep list of name:value pairs */
+
+ if (!(s = dewrap(sub[1], field_number_set ? US"[]" : US"{}")))
+ {
+ expand_string_message =
+ string_sprintf("%s wrapping %s for extract json",
+ expand_string_message,
+ field_number_set ? "array" : "object");
+ goto EXPAND_FAILED_CURLY;
+ }
+
+ list = s;
+ if (field_number_set)
+ {
+ if (field_number <= 0)
+ {
+ expand_string_message = US"first argument of \"extract\" must "
+ "be greater than zero";
+ goto EXPAND_FAILED;
+ }
+ while (field_number > 0 && (item = json_nextinlist(&list)))
+ field_number--;
+ s = item;
+ lookup_value = s;
+ while (*s) s++;
+ while (--s >= lookup_value && isspace(*s)) *s = '\0';
+ }
+ else
+ {
+ lookup_value = NULL;
+ while ((item = json_nextinlist(&list)))
+ {
+ /* Item is: string name-sep value. string is quoted.
+ Dequote the string and compare with the search key. */
+
+ if (!(item = dewrap(item, US"\"\"")))
+ {
+ expand_string_message =
+ string_sprintf("%s wrapping string key for extract json",
+ expand_string_message);
+ goto EXPAND_FAILED_CURLY;
+ }
+ if (Ustrcmp(item, sub[0]) == 0) /*XXX should be a UTF8-compare */
+ {
+ s = item + Ustrlen(item) + 1;
+ while (isspace(*s)) s++;
+ if (*s != ':')
+ {
+ expand_string_message = string_sprintf(
+ "missing object value-separator for extract json");
+ goto EXPAND_FAILED_CURLY;
+ }
+ s++;
+ while (isspace(*s)) s++;
+ lookup_value = s;
+ break;
+ }
+ }
+ }
+ }
+ }
/* If no string follows, $value gets substituted; otherwise there can
be yes/no strings, as for lookup or if. */
save_lookup_value, /* value to reset for string2 */
&s, /* input pointer */
&yield, /* output pointer */
- &size, /* output size */
- &ptr, /* output current point */
US"extract", /* condition type */
&resetok))
{
/* Extract the numbered element into $value. If
skipping, just pretend the extraction failed. */
- lookup_value = skipping? NULL : expand_getlistele(field_number, sub[1]);
+ lookup_value = skipping ? NULL : expand_getlistele(field_number, sub[1]);
/* If no string follows, $value gets substituted; otherwise there can
be yes/no strings, as for lookup or if. */
save_lookup_value, /* value to reset for string2 */
&s, /* input pointer */
&yield, /* output pointer */
- &size, /* output size */
- &ptr, /* output current point */
US"listextract", /* condition type */
&resetok))
{
save_lookup_value, /* value to reset for string2 */
&s, /* input pointer */
&yield, /* output pointer */
- &size, /* output size */
- &ptr, /* output current point */
US"certextract", /* condition type */
&resetok))
{
case EITEM_REDUCE:
{
int sep = 0;
- int save_ptr = ptr;
+ int save_ptr = yield->ptr;
uschar outsep[2] = { '\0', '\0' };
const uschar *list, *expr, *temp;
uschar *save_iterate_item = iterate_item;
if (*s++ != '}')
{ /*{*/
expand_string_message = string_sprintf("missing } at end of condition "
- "or expression inside \"%s\"", name);
+ "or expression inside \"%s\"; could be an unquoted } in the content",
+ name);
goto EXPAND_FAILED;
}
item of the output list, add in a space if the new item begins with the
separator character, or is an empty string. */
- if (ptr != save_ptr && (temp[0] == *outsep || temp[0] == 0))
- yield = string_catn(yield, &size, &ptr, US" ", 1);
+ if (yield->ptr != save_ptr && (temp[0] == *outsep || temp[0] == 0))
+ yield = string_catn(yield, US" ", 1);
/* Add the string in "temp" to the output list that we are building,
This is done in chunks by searching for the separator character. */
{
size_t seglen = Ustrcspn(temp, outsep);
- yield = string_catn(yield, &size, &ptr, temp, seglen + 1);
+ yield = string_catn(yield, temp, seglen + 1);
/* If we got to the end of the string we output one character
too many; backup and end the loop. Otherwise arrange to double the
separator. */
- if (temp[seglen] == '\0') { ptr--; break; }
- yield = string_catn(yield, &size, &ptr, outsep, 1);
+ if (temp[seglen] == '\0') { yield->ptr--; break; }
+ yield = string_catn(yield, outsep, 1);
temp += seglen + 1;
}
/* Output a separator after the string: we will remove the redundant
final one at the end. */
- yield = string_catn(yield, &size, &ptr, outsep, 1);
+ yield = string_catn(yield, outsep, 1);
} /* End of iteration over the list loop */
/* REDUCE has generated no output above: output the final value of
if (item_type == EITEM_REDUCE)
{
- yield = string_cat(yield, &size, &ptr, lookup_value);
+ yield = string_cat(yield, lookup_value);
lookup_value = save_lookup_value; /* Restore $value */
}
the redundant final separator. Even though an empty item at the end of a
list does not count, this is tidier. */
- else if (ptr != save_ptr) ptr--;
+ else if (yield->ptr != save_ptr) yield->ptr--;
/* Restore preserved $item */
while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
{
uschar * dstitem;
- uschar * newlist = NULL;
- uschar * newkeylist = NULL;
+ gstring * newlist = NULL;
+ gstring * newkeylist = NULL;
uschar * srcfield;
DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
newkeylist = string_append_listele(newkeylist, sep, srcfield);
}
- dstlist = newlist;
- dstkeylist = newkeylist;
+ dstlist = newlist->s;
+ dstkeylist = newkeylist->s;
DEBUG(D_expand) debug_printf_indent("%s: dstlist = \"%s\"\n", name, dstlist);
DEBUG(D_expand) debug_printf_indent("%s: dstkeylist = \"%s\"\n", name, dstkeylist);
}
if (dstlist)
- yield = string_cat(yield, &size, &ptr, dstlist);
+ yield = string_cat(yield, dstlist);
/* Restore preserved $item */
iterate_item = save_iterate_item;
if(status == OK)
{
if (result == NULL) result = US"";
- yield = string_cat(yield, &size, &ptr, result);
+ yield = string_cat(yield, result);
continue;
}
else
{
expand_string_message = result == NULL ? US"(no message)" : result;
- if(status == FAIL_FORCED) expand_string_forcedfail = TRUE;
+ if(status == FAIL_FORCED) f.expand_string_forcedfail = TRUE;
else if(status != FAIL)
log_write(0, LOG_MAIN|LOG_PANIC, "dlfunc{%s}{%s} failed (%d): %s",
argv[0], argv[1], status, expand_string_message);
save_lookup_value, /* value to reset for string2 */
&s, /* input pointer */
&yield, /* output pointer */
- &size, /* output size */
- &ptr, /* output current point */
US"env", /* condition type */
&resetok))
{
int c;
uschar *arg = NULL;
uschar *sub;
+#ifdef SUPPORT_TLS
var_entry *vp = NULL;
+#endif
/* Owing to an historical mis-design, an underscore may be part of the
operator name, or it may introduce arguments. We therefore first scan the
{
uschar *t;
unsigned long int n = Ustrtoul(sub, &t, 10);
- uschar * s = NULL;
- int sz = 0, i = 0;
+ gstring * g = NULL;
if (*t != 0)
{
goto EXPAND_FAILED;
}
for ( ; n; n >>= 5)
- s = string_catn(s, &sz, &i, &base32_chars[n & 0x1f], 1);
+ g = string_catn(g, &base32_chars[n & 0x1f], 1);
- while (i > 0) yield = string_catn(yield, &size, &ptr, &s[--i], 1);
+ if (g) while (g->ptr > 0) yield = string_catn(yield, &g->s[--g->ptr], 1);
continue;
}
n = n * 32 + (t - base32_chars);
}
s = string_sprintf("%ld", n);
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
continue;
}
goto EXPAND_FAILED;
}
t = string_base62(n);
- yield = string_cat(yield, &size, &ptr, t);
+ yield = string_cat(yield, t);
continue;
}
case EOP_BASE62D:
{
- uschar buf[16];
uschar *tt = sub;
unsigned long int n = 0;
while (*tt != 0)
}
n = n * BASE_62 + (t - base62_chars);
}
- (void)sprintf(CS buf, "%ld", n);
- yield = string_cat(yield, &size, &ptr, buf);
+ yield = string_fmt_append(yield, "%ld", n);
continue;
}
expand_string_message);
goto EXPAND_FAILED;
}
- yield = string_cat(yield, &size, &ptr, expanded);
+ yield = string_cat(yield, expanded);
continue;
}
int count = 0;
uschar *t = sub - 1;
while (*(++t) != 0) { *t = tolower(*t); count++; }
- yield = string_catn(yield, &size, &ptr, sub, count);
+ yield = string_catn(yield, sub, count);
continue;
}
int count = 0;
uschar *t = sub - 1;
while (*(++t) != 0) { *t = toupper(*t); count++; }
- yield = string_catn(yield, &size, &ptr, sub, count);
+ yield = string_catn(yield, sub, count);
continue;
}
if (vp && *(void **)vp->value)
{
uschar * cp = tls_cert_fprt_md5(*(void **)vp->value);
- yield = string_cat(yield, &size, &ptr, cp);
+ yield = string_cat(yield, cp);
}
else
#endif
md5 base;
uschar digest[16];
int j;
- char st[33];
md5_start(&base);
md5_end(&base, sub, Ustrlen(sub), digest);
- for(j = 0; j < 16; j++) sprintf(st+2*j, "%02x", digest[j]);
- yield = string_cat(yield, &size, &ptr, US st);
+ for (j = 0; j < 16; j++)
+ yield = string_fmt_append(yield, "%02x", digest[j]);
}
continue;
if (vp && *(void **)vp->value)
{
uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value);
- yield = string_cat(yield, &size, &ptr, cp);
+ yield = string_cat(yield, cp);
}
else
#endif
hctx h;
uschar digest[20];
int j;
- char st[41];
sha1_start(&h);
sha1_end(&h, sub, Ustrlen(sub), digest);
- for(j = 0; j < 20; j++) sprintf(st+2*j, "%02X", digest[j]);
- yield = string_catn(yield, &size, &ptr, US st, 40);
+ for (j = 0; j < 20; j++)
+ yield = string_fmt_append(yield, "%02X", digest[j]);
}
continue;
if (vp && *(void **)vp->value)
{
uschar * cp = tls_cert_fprt_sha256(*(void **)vp->value);
- yield = string_cat(yield, &size, &ptr, cp);
+ yield = string_cat(yield, cp);
}
else
{
hctx h;
blob b;
- char st[3];
- if (!exim_sha_init(&h, HASH_SHA256))
+ if (!exim_sha_init(&h, HASH_SHA2_256))
{
expand_string_message = US"unrecognised sha256 variant";
goto EXPAND_FAILED;
exim_sha_update(&h, sub, Ustrlen(sub));
exim_sha_finish(&h, &b);
while (b.len-- > 0)
- {
- sprintf(st, "%02X", *b.data++);
- yield = string_catn(yield, &size, &ptr, US st, 2);
- }
+ yield = string_fmt_append(yield, "%02X", *b.data++);
}
#else
expand_string_message = US"sha256 only supported with TLS";
{
hctx h;
blob b;
- char st[3];
hashmethod m = !arg ? HASH_SHA3_256
: Ustrcmp(arg, "224") == 0 ? HASH_SHA3_224
: Ustrcmp(arg, "256") == 0 ? HASH_SHA3_256
exim_sha_update(&h, sub, Ustrlen(sub));
exim_sha_finish(&h, &b);
while (b.len-- > 0)
- {
- sprintf(st, "%02X", *b.data++);
- yield = string_catn(yield, &size, &ptr, US st, 2);
- }
+ yield = string_fmt_append(yield, "%02X", *b.data++);
}
continue;
#else
- expand_string_message = US"sha3 only supported with GnuTLS 3.5.0 +";
+ expand_string_message = US"sha3 only supported with GnuTLS 3.5.0 + or OpenSSL 1.1.1 +";
goto EXPAND_FAILED;
#endif
}
enc = b64encode(sub, out - sub);
- yield = string_cat(yield, &size, &ptr, enc);
+ yield = string_cat(yield, enc);
continue;
}
while (*(++t) != 0)
{
if (*t < 0x21 || 0x7E < *t)
- yield = string_catn(yield, &size, &ptr,
- string_sprintf("\\x%02x", *t), 4);
+ yield = string_fmt_append(yield, "\\x%02x", *t);
else
- yield = string_catn(yield, &size, &ptr, t, 1);
+ yield = string_catn(yield, t, 1);
}
continue;
}
{
int cnt = 0;
int sep = 0;
- uschar * cp;
uschar buffer[256];
while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
- cp = string_sprintf("%d", cnt);
- yield = string_cat(yield, &size, &ptr, cp);
+ yield = string_fmt_append(yield, "%d", cnt);
continue;
}
list = ((namedlist_block *)(t->data.ptr))->string;
- while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+ while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
{
uschar * buf = US" : ";
if (needsep)
- yield = string_catn(yield, &size, &ptr, buf, 3);
+ yield = string_catn(yield, buf, 3);
else
needsep = TRUE;
char * cp;
char tok[3];
tok[0] = sep; tok[1] = ':'; tok[2] = 0;
- while ((cp= strpbrk((const char *)item, tok)))
+ while ((cp= strpbrk(CCS item, tok)))
{
- yield = string_catn(yield, &size, &ptr, item, cp-(char *)item);
+ yield = string_catn(yield, item, cp - CS item);
if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
{
- yield = string_catn(yield, &size, &ptr, US"::", 2);
- item = (uschar *)cp;
+ yield = string_catn(yield, US"::", 2);
+ item = US cp;
}
else /* sep in item; should already be doubled; emit once */
{
- yield = string_catn(yield, &size, &ptr, (uschar *)tok, 1);
+ yield = string_catn(yield, US tok, 1);
if (*cp == sep) cp++;
- item = (uschar *)cp;
+ item = US cp;
}
}
}
- yield = string_cat(yield, &size, &ptr, item);
+ yield = string_cat(yield, item);
}
continue;
}
/* Convert to masked textual format and add to output. */
- yield = string_catn(yield, &size, &ptr, buffer,
+ yield = string_catn(yield, buffer,
host_nmtoa(count, binary, mask, buffer, '.'));
continue;
}
goto EXPAND_FAILED;
}
- yield = string_catn(yield, &size, &ptr, buffer,
- c == EOP_IPV6NORM
+ yield = string_catn(yield, buffer, c == EOP_IPV6NORM
? ipv6_nmtoa(binary, buffer)
: host_nmtoa(4, binary, -1, buffer, ':')
);
case EOP_LOCAL_PART:
case EOP_DOMAIN:
{
- uschar *error;
+ uschar * error;
int start, end, domain;
- uschar *t = parse_extract_address(sub, &error, &start, &end, &domain,
+ uschar * t = parse_extract_address(sub, &error, &start, &end, &domain,
FALSE);
- if (t != NULL)
- {
+ if (t)
if (c != EOP_DOMAIN)
{
if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1;
- yield = string_catn(yield, &size, &ptr, sub+start, end-start);
+ yield = string_catn(yield, sub+start, end-start);
}
else if (domain != 0)
{
domain += start;
- yield = string_catn(yield, &size, &ptr, sub+domain, end-domain);
+ yield = string_catn(yield, sub+domain, end-domain);
}
- }
continue;
}
{
uschar outsep[2] = { ':', '\0' };
uschar *address, *error;
- int save_ptr = ptr;
+ int save_ptr = yield->ptr;
int start, end, domain; /* Not really used */
while (isspace(*sub)) sub++;
- if (*sub == '>') { *outsep = *++sub; ++sub; }
- parse_allow_group = TRUE;
+ if (*sub == '>')
+ if (*outsep = *++sub) ++sub;
+ else
+ {
+ expand_string_message = string_sprintf("output separator "
+ "missing in expanding ${addresses:%s}", --sub);
+ goto EXPAND_FAILED;
+ }
+ f.parse_allow_group = TRUE;
for (;;)
{
if (address != NULL)
{
- if (ptr != save_ptr && address[0] == *outsep)
- yield = string_catn(yield, &size, &ptr, US" ", 1);
+ if (yield->ptr != save_ptr && address[0] == *outsep)
+ yield = string_catn(yield, US" ", 1);
for (;;)
{
size_t seglen = Ustrcspn(address, outsep);
- yield = string_catn(yield, &size, &ptr, address, seglen + 1);
+ yield = string_catn(yield, address, seglen + 1);
/* If we got to the end of the string we output one character
too many. */
- if (address[seglen] == '\0') { ptr--; break; }
- yield = string_catn(yield, &size, &ptr, outsep, 1);
+ if (address[seglen] == '\0') { yield->ptr--; break; }
+ yield = string_catn(yield, outsep, 1);
address += seglen + 1;
}
/* Output a separator after the string: we will remove the
redundant final one at the end. */
- yield = string_catn(yield, &size, &ptr, outsep, 1);
+ yield = string_catn(yield, outsep, 1);
}
if (saveend == '\0') break;
/* If we have generated anything, remove the redundant final
separator. */
- if (ptr != save_ptr) ptr--;
- parse_allow_group = FALSE;
+ if (yield->ptr != save_ptr) yield->ptr--;
+ f.parse_allow_group = FALSE;
continue;
}
if (needs_quote)
{
- yield = string_catn(yield, &size, &ptr, US"\"", 1);
+ yield = string_catn(yield, US"\"", 1);
t = sub - 1;
while (*(++t) != 0)
{
if (*t == '\n')
- yield = string_catn(yield, &size, &ptr, US"\\n", 2);
+ yield = string_catn(yield, US"\\n", 2);
else if (*t == '\r')
- yield = string_catn(yield, &size, &ptr, US"\\r", 2);
+ yield = string_catn(yield, US"\\r", 2);
else
{
if (*t == '\\' || *t == '"')
- yield = string_catn(yield, &size, &ptr, US"\\", 1);
- yield = string_catn(yield, &size, &ptr, t, 1);
+ yield = string_catn(yield, US"\\", 1);
+ yield = string_catn(yield, t, 1);
}
}
- yield = string_catn(yield, &size, &ptr, US"\"", 1);
+ yield = string_catn(yield, US"\"", 1);
}
- else yield = string_cat(yield, &size, &ptr, sub);
+ else yield = string_cat(yield, sub);
continue;
}
goto EXPAND_FAILED;
}
- yield = string_cat(yield, &size, &ptr, sub);
+ yield = string_cat(yield, sub);
continue;
}
while (*(++t) != 0)
{
if (!isalnum(*t))
- yield = string_catn(yield, &size, &ptr, US"\\", 1);
- yield = string_catn(yield, &size, &ptr, t, 1);
+ yield = string_catn(yield, US"\\", 1);
+ yield = string_catn(yield, t, 1);
}
continue;
}
case EOP_RFC2047:
{
uschar buffer[2048];
- const uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
- buffer, sizeof(buffer), FALSE);
- yield = string_cat(yield, &size, &ptr, string);
+ yield = string_cat(yield,
+ parse_quote_2047(sub, Ustrlen(sub), headers_charset,
+ buffer, sizeof(buffer), FALSE));
continue;
}
expand_string_message = error;
goto EXPAND_FAILED;
}
- yield = string_catn(yield, &size, &ptr, decoded, len);
+ yield = string_catn(yield, decoded, len);
continue;
}
GETUTF8INC(c, sub);
if (c > 255) c = '_';
buff[0] = c;
- yield = string_catn(yield, &size, &ptr, buff, 1);
+ yield = string_catn(yield, buff, 1);
}
continue;
}
{
int seq_len = 0, index = 0;
int bytes_left = 0;
- long codepoint = -1;
+ long codepoint = -1;
+ int complete;
uschar seq_buff[4]; /* accumulate utf-8 here */
while (*sub != 0)
{
- int complete = 0;
+ complete = 0;
uschar c = *sub++;
if (bytes_left)
complete = -1; /* error (RFC3629 limit) */
else
{ /* finished; output utf-8 sequence */
- yield = string_catn(yield, &size, &ptr, seq_buff, seq_len);
+ yield = string_catn(yield, seq_buff, seq_len);
index = 0;
}
}
{
if((c & 0x80) == 0) /* 1-byte sequence, US-ASCII, keep it */
{
- yield = string_catn(yield, &size, &ptr, &c, 1);
+ yield = string_catn(yield, &c, 1);
continue;
}
if((c & 0xe0) == 0xc0) /* 2-byte sequence */
if (complete != 0)
{
bytes_left = index = 0;
- yield = string_catn(yield, &size, &ptr, UTF8_REPLACEMENT_CHAR, 1);
+ yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
}
if ((complete == 1) && ((c & 0x80) == 0))
/* ASCII character follows incomplete sequence */
- yield = string_catn(yield, &size, &ptr, &c, 1);
+ yield = string_catn(yield, &c, 1);
}
+ /* If given a sequence truncated mid-character, we also want to report ?
+ * Eg, ${length_1:フィル} is one byte, not one character, so we expect
+ * ${utf8clean:${length_1:フィル}} to yield '?' */
+ if (bytes_left != 0)
+ {
+ yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1);
+ }
continue;
}
string_printing(sub), error);
goto EXPAND_FAILED;
}
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
continue;
}
string_printing(sub), error);
goto EXPAND_FAILED;
}
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
continue;
}
string_printing(sub), error);
goto EXPAND_FAILED;
}
- yield = string_cat(yield, &size, &ptr, s);
- DEBUG(D_expand) debug_printf_indent("yield: '%s'\n", yield);
+ yield = string_cat(yield, s);
+ DEBUG(D_expand) debug_printf_indent("yield: '%s'\n", yield->s);
continue;
}
string_printing(sub), error);
goto EXPAND_FAILED;
}
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
continue;
}
#endif /* EXPERIMENTAL_INTERNATIONAL */
case EOP_ESCAPE:
{
const uschar * t = string_printing(sub);
- yield = string_cat(yield, &size, &ptr, t);
+ yield = string_cat(yield, t);
continue;
}
for (s = sub; (c = *s); s++)
yield = c < 127 && c != '\\'
- ? string_catn(yield, &size, &ptr, s, 1)
- : string_catn(yield, &size, &ptr, string_sprintf("\\%03o", c), 4);
+ ? string_catn(yield, s, 1)
+ : string_fmt_append(yield, "\\%03o", c);
continue;
}
uschar *save_sub = sub;
uschar *error = NULL;
int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
- if (error != NULL)
+ if (error)
{
expand_string_message = string_sprintf("error in expression "
- "evaluation: %s (after processing \"%.*s\")", error, sub-save_sub,
- save_sub);
+ "evaluation: %s (after processing \"%.*s\")", error,
+ (int)(sub-save_sub), save_sub);
goto EXPAND_FAILED;
}
- sprintf(CS var_buffer, PR_EXIM_ARITH, n);
- yield = string_cat(yield, &size, &ptr, var_buffer);
+ yield = string_fmt_append(yield, PR_EXIM_ARITH, n);
continue;
}
- /* Handle time period formating */
+ /* Handle time period formatting */
case EOP_TIME_EVAL:
{
"Exim time interval in \"%s\" operator", sub, name);
goto EXPAND_FAILED;
}
- sprintf(CS var_buffer, "%d", n);
- yield = string_cat(yield, &size, &ptr, var_buffer);
+ yield = string_fmt_append(yield, "%d", n);
continue;
}
goto EXPAND_FAILED;
}
t = readconf_printtime(n);
- yield = string_cat(yield, &size, &ptr, t);
+ yield = string_cat(yield, t);
continue;
}
#else
uschar * s = b64encode(sub, Ustrlen(sub));
#endif
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
continue;
}
"well-formed for \"%s\" operator", sub, name);
goto EXPAND_FAILED;
}
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
continue;
}
/* strlen returns the length of the string */
case EOP_STRLEN:
- {
- uschar buff[24];
- (void)sprintf(CS buff, "%d", Ustrlen(sub));
- yield = string_cat(yield, &size, &ptr, buff);
+ yield = string_fmt_append(yield, "%d", Ustrlen(sub));
continue;
- }
/* length_n or l_n takes just the first n characters or the whole string,
whichever is the shorter;
int len;
uschar *ret;
- if (arg == NULL)
+ if (!arg)
{
expand_string_message = string_sprintf("missing values after %s",
name);
extract_substr(sub, value1, value2, &len);
if (ret == NULL) goto EXPAND_FAILED;
- yield = string_catn(yield, &size, &ptr, ret, len);
+ yield = string_catn(yield, ret, len);
continue;
}
case EOP_STAT:
{
- uschar *s;
uschar smode[12];
uschar **modetable[3];
int i;
mode_t mode;
struct stat st;
- if ((expand_forbid & RDO_EXISTS) != 0)
+ if (expand_forbid & RDO_EXISTS)
{
expand_string_message = US"Use of the stat() expansion is not permitted";
goto EXPAND_FAILED;
}
smode[10] = 0;
- s = string_sprintf("mode=%04lo smode=%s inode=%ld device=%ld links=%ld "
+ yield = string_fmt_append(yield,
+ "mode=%04lo smode=%s inode=%ld device=%ld links=%ld "
"uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld",
(long)(st.st_mode & 077777), smode, (long)st.st_ino,
(long)st.st_dev, (long)st.st_nlink, (long)st.st_uid,
(long)st.st_gid, st.st_size, (long)st.st_atime,
(long)st.st_mtime, (long)st.st_ctime);
- yield = string_cat(yield, &size, &ptr, s);
continue;
}
case EOP_RANDINT:
{
- int_eximarith_t max;
- uschar *s;
+ int_eximarith_t max = expanded_string_integer(sub, TRUE);
- max = expanded_string_integer(sub, TRUE);
- if (expand_string_message != NULL)
+ if (expand_string_message)
goto EXPAND_FAILED;
- s = string_sprintf("%d", vaguely_random_number((int)max));
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_fmt_append(yield, "%d", vaguely_random_number((int)max));
continue;
}
goto EXPAND_FAILED;
}
invert_address(reversed, sub);
- yield = string_cat(yield, &size, &ptr, reversed);
+ yield = string_cat(yield, reversed);
continue;
}
/* Unknown operator */
default:
- expand_string_message =
- string_sprintf("unknown expansion operator \"%s\"", name);
- goto EXPAND_FAILED;
+ expand_string_message =
+ string_sprintf("unknown expansion operator \"%s\"", name);
+ goto EXPAND_FAILED;
}
}
{
int len;
int newsize = 0;
- if (ptr == 0)
+ gstring * g = NULL;
+
+ if (!yield)
+ g = store_get(sizeof(gstring));
+ else if (yield->ptr == 0)
{
if (resetok) store_reset(yield);
yield = NULL;
- size = 0;
+ g = store_get(sizeof(gstring)); /* alloc _before_ calling find_variable() */
}
if (!(value = find_variable(name, FALSE, skipping, &newsize)))
{
len = Ustrlen(value);
if (!yield && newsize)
{
- yield = value;
- size = newsize;
- ptr = len;
+ yield = g;
+ yield->size = newsize;
+ yield->ptr = len;
+ yield->s = value;
}
else
- yield = string_catn(yield, &size, &ptr, value, len);
+ yield = string_catn(yield, value, len);
continue;
}
if (ket_ends && *s == 0)
{
- expand_string_message = malformed_header?
- US"missing } at end of string - could be header name not terminated by colon"
- :
- US"missing } at end of string";
+ expand_string_message = malformed_header
+ ? US"missing } at end of string - could be header name not terminated by colon"
+ : US"missing } at end of string";
goto EXPAND_FAILED;
}
added to the string. If so, set up an empty string. Add a terminating zero. If
left != NULL, return a pointer to the terminator. */
-if (yield == NULL) yield = store_get(1);
-yield[ptr] = 0;
-if (left != NULL) *left = s;
+if (!yield)
+ yield = string_get(1);
+(void) string_from_gstring(yield);
+if (left) *left = s;
/* Any stacking store that was used above the final string is no longer needed.
In many cases the final string will be the first one that was got and so there
will be optimal store usage. */
-if (resetok) store_reset(yield + ptr + 1);
+if (resetok) store_reset(yield->s + (yield->size = yield->ptr + 1));
else if (resetok_p) *resetok_p = FALSE;
DEBUG(D_expand)
- {
- debug_printf_indent("|__expanding: %.*s\n", (int)(s - string), string);
- debug_printf_indent("%s_____result: %s\n", skipping ? "|" : "\\", yield);
- if (skipping) debug_printf_indent("\\___skipping: result is not used\n");
- }
+ DEBUG(D_noutf8)
+ {
+ debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string);
+ debug_printf_indent("%sresult: %s\n",
+ skipping ? "|-----" : "\\_____", yield->s);
+ if (skipping)
+ debug_printf_indent("\\___skipping: result is not used\n");
+ }
+ else
+ {
+ debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+ "expanding: %.*s\n",
+ (int)(s - string), string);
+ debug_printf_indent("%s"
+ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+ "result: %s\n",
+ skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+ yield->s);
+ if (skipping)
+ debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+ "skipping: result is not used\n");
+ }
expand_level--;
-return yield;
+return yield->s;
/* This is the failure exit: easiest to program with a goto. We still need
to update the pointer to the terminator, for cases of nested calls with "fail".
that is a bad idea, because expand_string_message is in dynamic store. */
EXPAND_FAILED:
-if (left != NULL) *left = s;
+if (left) *left = s;
DEBUG(D_expand)
- {
- debug_printf_indent("|failed to expand: %s\n", string);
- debug_printf_indent("%s___error message: %s\n",
- expand_string_forcedfail ? "|" : "\\", expand_string_message);
- if (expand_string_forcedfail) debug_printf_indent("\\failure was forced\n");
- }
-if (resetok_p) *resetok_p = resetok;
+ DEBUG(D_noutf8)
+ {
+ debug_printf_indent("|failed to expand: %s\n", string);
+ debug_printf_indent("%serror message: %s\n",
+ f.expand_string_forcedfail ? "|---" : "\\___", expand_string_message);
+ if (f.expand_string_forcedfail)
+ debug_printf_indent("\\failure was forced\n");
+ }
+ else
+ {
+ debug_printf_indent(UTF8_VERT_RIGHT "failed to expand: %s\n",
+ string);
+ debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+ "error message: %s\n",
+ f.expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+ expand_string_message);
+ if (f.expand_string_forcedfail)
+ debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
+ }
+if (resetok_p && !resetok) *resetok_p = FALSE;
expand_level--;
return NULL;
}
due to a lookup deferring, search_find_defer will be TRUE
*/
-uschar *
-expand_string(uschar *string)
+const uschar *
+expand_cstring(const uschar * string)
{
-search_find_defer = FALSE;
-malformed_header = FALSE;
-return (Ustrpbrk(string, "$\\") == NULL)? string :
- expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
+if (Ustrpbrk(string, "$\\") != NULL)
+ {
+ int old_pool = store_pool;
+ uschar * s;
+
+ f.search_find_defer = FALSE;
+ malformed_header = FALSE;
+ store_pool = POOL_MAIN;
+ s = expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
+ store_pool = old_pool;
+ return s;
+ }
+return string;
}
-
-const uschar *
-expand_cstring(const uschar *string)
+uschar *
+expand_string(uschar * string)
{
-search_find_defer = FALSE;
-malformed_header = FALSE;
-return (Ustrpbrk(string, "$\\") == NULL)? string :
- expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL);
+return US expand_cstring(CUS string);
}
+
+
/*************************************************
* Expand and copy *
*************************************************/
expanded = expand_string(svalue);
if (expanded == NULL)
{
- if (expand_string_forcedfail)
+ if (f.expand_string_forcedfail)
{
DEBUG(dbg_opt) debug_printf("expansion of \"%s\" forced failure\n", oname);
*rvalue = bvalue;
{
return ( ( Ustrstr(s, "failed to expand") != NULL
|| Ustrstr(s, "expansion of ") != NULL
- )
+ )
&& ( Ustrstr(s, "mysql") != NULL
|| Ustrstr(s, "pgsql") != NULL
|| Ustrstr(s, "redis") != NULL
|| Ustrstr(s, "ldapi:") != NULL
|| Ustrstr(s, "ldapdn:") != NULL
|| Ustrstr(s, "ldapm:") != NULL
- ) )
+ ) )
? US"Temporary internal error" : s;
}
+/* Read given named file into big_buffer. Use for keying material etc.
+The content will have an ascii NUL appended.
+
+Arguments:
+ filename as it says
+
+Return: pointer to buffer, or NULL on error.
+*/
+
+uschar *
+expand_file_big_buffer(const uschar * filename)
+{
+int fd, off = 0, len;
+
+if ((fd = open(CS filename, O_RDONLY)) < 0)
+ {
+ log_write(0, LOG_MAIN | LOG_PANIC, "unable to open file for reading: %s",
+ filename);
+ return NULL;
+ }
+
+do
+ {
+ if ((len = read(fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
+ {
+ (void) close(fd);
+ log_write(0, LOG_MAIN|LOG_PANIC, "unable to read file: %s", filename);
+ return NULL;
+ }
+ off += len;
+ }
+while (len > 0);
+
+(void) close(fd);
+big_buffer[off] = '\0';
+return big_buffer;
+}
+
+
/*************************************************
* Error-checking for testsuite *
*************************************************/
typedef struct {
- const char * filename;
- int linenumber;
uschar * region_start;
uschar * region_end;
const uschar *var_name;
void
assert_no_variables(void * ptr, int len, const char * filename, int linenumber)
{
-err_ctx e = {filename, linenumber, ptr, US ptr + len, NULL };
+err_ctx e = { .region_start = ptr, .region_end = US ptr + len,
+ .var_name = NULL, .var_data = NULL };
int i;
var_entry * v;
if (v->type == vtype_stringptr)
assert_variable_notin(US v->name, *(USS v->value), &e);
+/* check dns and address trees */
+tree_walk(tree_dns_fails, assert_variable_notin, &e);
+tree_walk(tree_duplicates, assert_variable_notin, &e);
+tree_walk(tree_nonrecipients, assert_variable_notin, &e);
+tree_walk(tree_unusable, assert_variable_notin, &e);
+
if (e.var_name)
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"live variable '%s' destroyed by reset_store at %s:%d\n- value '%.64s'",
- e.var_name, e.filename, e.linenumber, e.var_data);
+ e.var_name, filename, linenumber, e.var_data);
}
}
else
{
- if (search_find_defer) printf("search_find deferred\n");
+ if (f.search_find_defer) printf("search_find deferred\n");
printf("Failed: %s\n", expand_string_message);
- if (expand_string_forcedfail) printf("Forced failure\n");
+ if (f.expand_string_forcedfail) printf("Forced failure\n");
printf("\n");
}
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* The count of string arguments */
-#define MAILARGS_STRING_COUNT (sizeof(mailargs)/sizeof(uschar *))
+#define MAILARGS_STRING_COUNT (nelem(mailargs))
/* The count of string arguments that are actually passed over as strings
(once_repeat is converted to an int). */
"match",
"matches"};
-static int cond_word_count = (sizeof(cond_words)/sizeof(uschar *));
+static int cond_word_count = nelem(cond_words);
static int cond_types[] = { cond_BEGINS, cond_BEGINS, cond_CONTAINS,
cond_CONTAINS, cond_ENDS, cond_ENDS, cond_IS, cond_MATCHES, cond_MATCHES,
"noerror", "pipe", "save", "seen", "testprint", "unseen", "vacation"
};
-static int command_list_count = sizeof(command_list)/sizeof(uschar *);
+static int command_list_count = nelem(command_list);
/* This table contains the number of expanded arguments in the bottom 4 bits.
If the top bit is set, it means that the default for the command is "seen". */
yield = FALSE;
}
- if (!system_filtering && second_argument.b != TRUE_UNSET)
+ if (!f.system_filtering && second_argument.b != TRUE_UNSET)
{
*error_pointer = string_sprintf("header addition and removal is "
"available only in system filters: near line %d of filter file",
scan Cc: (hence the FALSE argument). */
case cond_personal:
- yield = system_filtering? FALSE : filter_personal(c->left.a, FALSE);
+ yield = f.system_filtering? FALSE : filter_personal(c->left.a, FALSE);
break;
case cond_delivered:
and filter testing and verification. */
case cond_firsttime:
- yield = filter_test != FTEST_NONE || message_id[0] == 0 || deliver_firsttime;
+ yield = filter_test != FTEST_NONE || message_id[0] == 0 || f.deliver_firsttime;
break;
/* Only TRUE if a message is actually being processed; FALSE for address
testing and verification. */
case cond_manualthaw:
- yield = message_id[0] != 0 && deliver_manual_thaw;
+ yield = message_id[0] != 0 && f.deliver_manual_thaw;
break;
/* The foranyaddress condition loops through a list of addresses */
}
yield = FALSE;
- parse_allow_group = TRUE; /* Allow group syntax */
+ f.parse_allow_group = TRUE; /* Allow group syntax */
while (*pp != 0)
{
pp = p + 1;
}
- parse_allow_group = FALSE; /* Reset group syntax flags */
- parse_found_group = FALSE;
+ f.parse_allow_group = FALSE; /* Reset group syntax flags */
+ f.parse_found_group = FALSE;
break;
/* All other conditions have left and right values that need expanding;
s = expargs[1];
- if (s != NULL && !system_filtering)
+ if (s != NULL && !f.system_filtering)
{
uschar *ownaddress = expand_string(US"$local_part@$domain");
if (strcmpic(ownaddress, s) != 0)
addr = deliver_make_addr(expargs[0], TRUE); /* TRUE => copy s */
addr->prop.errors_address = (s == NULL)?
s : string_copy(s); /* Default is NULL */
- if (commands->noerror) setflag(addr, af_ignore_error);
+ if (commands->noerror) addr->prop.ignore_error = TRUE;
addr->next = *generated;
*generated = addr;
}
mode value. */
addr = deliver_make_addr(s, TRUE); /* TRUE => copy s */
- setflag(addr, af_pfr|af_file);
- if (commands->noerror) setflag(addr, af_ignore_error);
+ setflag(addr, af_pfr);
+ setflag(addr, af_file);
+ if (commands->noerror) addr->prop.ignore_error = TRUE;
addr->mode = mode;
addr->next = *generated;
*generated = addr;
has been split up into separate arguments. */
addr = deliver_make_addr(s, TRUE); /* TRUE => copy s */
- setflag(addr, af_pfr|af_expand_pipe);
- if (commands->noerror) setflag(addr, af_ignore_error);
+ setflag(addr, af_pfr);
+ setflag(addr, af_expand_pipe);
+ if (commands->noerror) addr->prop.ignore_error = TRUE;
addr->next = *generated;
*generated = addr;
case mail_command:
case vacation_command:
- if (return_path == NULL || return_path[0] == 0)
- {
+ if (return_path == NULL || return_path[0] == 0)
+ {
+ if (filter_test != FTEST_NONE)
+ printf("%s command ignored because return_path is empty\n",
+ command_list[commands->command]);
+ else DEBUG(D_filter) debug_printf("%s command ignored because return_path "
+ "is empty\n", command_list[commands->command]);
+ break;
+ }
+
+ /* Check the contents of the strings. The type of string can be deduced
+ from the value of i.
+
+ . If i is equal to mailarg_index_text it's a text string for the body,
+ where anything goes.
+
+ . If i is > mailarg_index_text, we are dealing with a file name, which
+ cannot contain non-printing characters.
+
+ . If i is less than mailarg_index_headers we are dealing with something
+ that will go in a single message header line, where newlines must be
+ followed by white space.
+
+ . If i is equal to mailarg_index_headers, we have a string that contains
+ one or more headers. Newlines that are not followed by white space must
+ be followed by a header name.
+ */
+
+ for (i = 0; i < MAILARGS_STRING_COUNT; i++)
+ {
+ uschar *p;
+ uschar *s = expargs[i];
+
+ if (s == NULL) continue;
+
+ if (i != mailarg_index_text) for (p = s; *p != 0; p++)
+ {
+ int c = *p;
+ if (i > mailarg_index_text)
+ {
+ if (!mac_isprint(c))
+ {
+ *error_pointer = string_sprintf("non-printing character in \"%s\" "
+ "in %s command", string_printing(s),
+ command_list[commands->command]);
+ return FF_ERROR;
+ }
+ }
+
+ /* i < mailarg_index_text */
+
+ else if (c == '\n' && !isspace(p[1]))
+ {
+ if (i < mailarg_index_headers)
+ {
+ *error_pointer = string_sprintf("\\n not followed by space in "
+ "\"%.1024s\" in %s command", string_printing(s),
+ command_list[commands->command]);
+ return FF_ERROR;
+ }
+
+ /* Check for the start of a new header line within the string */
+
+ else
+ {
+ uschar *pp;
+ for (pp = p + 1;; pp++)
+ {
+ c = *pp;
+ if (c == ':' && pp != p + 1) break;
+ if (c == 0 || c == ':' || isspace(*pp))
+ {
+ *error_pointer = string_sprintf("\\n not followed by space or "
+ "valid header name in \"%.1024s\" in %s command",
+ string_printing(s), command_list[commands->command]);
+ return FF_ERROR;
+ }
+ }
+ p = pp;
+ }
+ }
+ } /* Loop to scan the string */
+
+ /* The string is OK */
+
+ commands->args[i].u = s;
+ }
+
+ /* Proceed with mail or vacation command */
+
if (filter_test != FTEST_NONE)
- printf("%s command ignored because return_path is empty\n",
- command_list[commands->command]);
- else DEBUG(D_filter) debug_printf("%s command ignored because return_path "
- "is empty\n", command_list[commands->command]);
+ {
+ uschar *to = commands->args[mailarg_index_to].u;
+ indent();
+ printf("%sail to: %s%s%s\n", (commands->seen)? "Seen m" : "M",
+ to ? to : US"<default>",
+ commands->command == vacation_command ? " (vacation)" : "",
+ commands->noerror ? " (noerror)" : "");
+ for (i = 1; i < MAILARGS_STRING_COUNT; i++)
+ {
+ uschar *arg = commands->args[i].u;
+ if (arg)
+ {
+ int len = Ustrlen(mailargs[i]);
+ int indent = (debug_selector != 0)? output_indent : 0;
+ while (len++ < 7 + indent) printf(" ");
+ printf("%s: %s%s\n", mailargs[i], string_printing(arg),
+ (commands->args[mailarg_index_expand].u != NULL &&
+ Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
+ }
+ }
+ if (commands->args[mailarg_index_return].u)
+ printf("Return original message\n");
+ }
+ else
+ {
+ uschar *tt;
+ uschar *to = commands->args[mailarg_index_to].u;
+ gstring * log_addr = NULL;
+
+ if (!to) to = expand_string(US"$reply_address");
+ while (isspace(*to)) to++;
+
+ for (tt = to; *tt != 0; tt++) /* Get rid of newlines */
+ if (*tt == '\n') *tt = ' ';
+
+ DEBUG(D_filter)
+ {
+ debug_printf("Filter: %smail to: %s%s%s\n",
+ commands->seen ? "seen " : "",
+ to,
+ commands->command == vacation_command ? " (vacation)" : "",
+ commands->noerror ? " (noerror)" : "");
+ for (i = 1; i < MAILARGS_STRING_COUNT; i++)
+ {
+ uschar *arg = commands->args[i].u;
+ if (arg != NULL)
+ {
+ int len = Ustrlen(mailargs[i]);
+ while (len++ < 15) debug_printf(" ");
+ debug_printf("%s: %s%s\n", mailargs[i], string_printing(arg),
+ (commands->args[mailarg_index_expand].u != NULL &&
+ Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
+ }
+ }
+ }
+
+ /* Create the "address" for the autoreply. This is used only for logging,
+ as the actual recipients are extracted from the To: line by -t. We use the
+ same logic here to extract the working addresses (there may be more than
+ one). Just in case there are a vast number of addresses, stop when the
+ string gets too long. */
+
+ tt = to;
+ while (*tt != 0)
+ {
+ uschar *ss = parse_find_address_end(tt, FALSE);
+ uschar *recipient, *errmess;
+ int start, end, domain;
+ int temp = *ss;
+
+ *ss = 0;
+ recipient = parse_extract_address(tt, &errmess, &start, &end, &domain,
+ FALSE);
+ *ss = temp;
+
+ /* Ignore empty addresses and errors; an error will occur later if
+ there's something really bad. */
+
+ if (recipient)
+ {
+ log_addr = string_catn(log_addr, log_addr ? US"," : US">", 1);
+ log_addr = string_cat (log_addr, recipient);
+ }
+
+ /* Check size */
+
+ if (log_addr && log_addr->ptr > 256)
+ {
+ log_addr = string_catn(log_addr, US", ...", 5);
+ break;
+ }
+
+ /* Move on past this address */
+
+ tt = ss + (*ss ? 1 : 0);
+ while (isspace(*tt)) tt++;
+ }
+
+ if (log_addr)
+ addr = deliver_make_addr(string_from_gstring(log_addr), FALSE);
+ else
+ {
+ addr = deliver_make_addr(US ">**bad-reply**", FALSE);
+ setflag(addr, af_bad_reply);
+ }
+
+ setflag(addr, af_pfr);
+ if (commands->noerror) addr->prop.ignore_error = TRUE;
+ addr->next = *generated;
+ *generated = addr;
+
+ addr->reply = store_get(sizeof(reply_item));
+ addr->reply->from = NULL;
+ addr->reply->to = string_copy(to);
+ addr->reply->file_expand =
+ commands->args[mailarg_index_expand].u != NULL;
+ addr->reply->expand_forbid = expand_forbid;
+ addr->reply->return_message =
+ commands->args[mailarg_index_return].u != NULL;
+ addr->reply->once_repeat = 0;
+
+ if (commands->args[mailarg_index_once_repeat].u != NULL)
+ {
+ addr->reply->once_repeat =
+ readconf_readtime(commands->args[mailarg_index_once_repeat].u, 0,
+ FALSE);
+ if (addr->reply->once_repeat < 0)
+ {
+ *error_pointer = string_sprintf("Bad time value for \"once_repeat\" "
+ "in mail or vacation command: %s",
+ commands->args[mailarg_index_once_repeat].u);
+ return FF_ERROR;
+ }
+ }
+
+ /* Set up all the remaining string arguments (those other than "to") */
+
+ for (i = 1; i < mailargs_string_passed; i++)
+ {
+ uschar *ss = commands->args[i].u;
+ *(USS((US addr->reply) + reply_offsets[i])) =
+ ss ? string_copy(ss) : NULL;
+ }
+ }
break;
- }
-
- /* Check the contents of the strings. The type of string can be deduced
- from the value of i.
-
- . If i is equal to mailarg_index_text it's a text string for the body,
- where anything goes.
-
- . If i is > mailarg_index_text, we are dealing with a file name, which
- cannot contain non-printing characters.
-
- . If i is less than mailarg_index_headers we are dealing with something
- that will go in a single message header line, where newlines must be
- followed by white space.
-
- . If i is equal to mailarg_index_headers, we have a string that contains
- one or more headers. Newlines that are not followed by white space must
- be followed by a header name.
- */
-
- for (i = 0; i < MAILARGS_STRING_COUNT; i++)
- {
- uschar *p;
- uschar *s = expargs[i];
-
- if (s == NULL) continue;
-
- if (i != mailarg_index_text) for (p = s; *p != 0; p++)
- {
- int c = *p;
- if (i > mailarg_index_text)
- {
- if (!mac_isprint(c))
- {
- *error_pointer = string_sprintf("non-printing character in \"%s\" "
- "in %s command", string_printing(s),
- command_list[commands->command]);
- return FF_ERROR;
- }
- }
-
- /* i < mailarg_index_text */
-
- else if (c == '\n' && !isspace(p[1]))
- {
- if (i < mailarg_index_headers)
- {
- *error_pointer = string_sprintf("\\n not followed by space in "
- "\"%.1024s\" in %s command", string_printing(s),
- command_list[commands->command]);
- return FF_ERROR;
- }
-
- /* Check for the start of a new header line within the string */
-
- else
- {
- uschar *pp;
- for (pp = p + 1;; pp++)
- {
- c = *pp;
- if (c == ':' && pp != p + 1) break;
- if (c == 0 || c == ':' || isspace(*pp))
- {
- *error_pointer = string_sprintf("\\n not followed by space or "
- "valid header name in \"%.1024s\" in %s command",
- string_printing(s), command_list[commands->command]);
- return FF_ERROR;
- }
- }
- p = pp;
- }
- }
- } /* Loop to scan the string */
-
- /* The string is OK */
-
- commands->args[i].u = s;
- }
-
- /* Proceed with mail or vacation command */
-
- if (filter_test != FTEST_NONE)
- {
- uschar *to = commands->args[mailarg_index_to].u;
- indent();
- printf("%sail to: %s%s%s\n", (commands->seen)? "Seen m" : "M",
- (to == NULL)? US"<default>" : to,
- (commands->command == vacation_command)? " (vacation)" : "",
- (commands->noerror)? " (noerror)" : "");
- for (i = 1; i < MAILARGS_STRING_COUNT; i++)
- {
- uschar *arg = commands->args[i].u;
- if (arg != NULL)
- {
- int len = Ustrlen(mailargs[i]);
- int indent = (debug_selector != 0)? output_indent : 0;
- while (len++ < 7 + indent) printf(" ");
- printf("%s: %s%s\n", mailargs[i], string_printing(arg),
- (commands->args[mailarg_index_expand].u != NULL &&
- Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
- }
- }
- if (commands->args[mailarg_index_return].u != NULL)
- printf("Return original message\n");
- }
- else
- {
- uschar *tt;
- uschar *log_addr = NULL;
- uschar *to = commands->args[mailarg_index_to].u;
- int size = 0;
- int ptr = 0;
- int badflag = 0;
-
- if (to == NULL) to = expand_string(US"$reply_address");
- while (isspace(*to)) to++;
-
- for (tt = to; *tt != 0; tt++) /* Get rid of newlines */
- if (*tt == '\n') *tt = ' ';
-
- DEBUG(D_filter)
- {
- debug_printf("Filter: %smail to: %s%s%s\n",
- (commands->seen)? "seen " : "",
- to,
- (commands->command == vacation_command)? " (vacation)" : "",
- (commands->noerror)? " (noerror)" : "");
- for (i = 1; i < MAILARGS_STRING_COUNT; i++)
- {
- uschar *arg = commands->args[i].u;
- if (arg != NULL)
- {
- int len = Ustrlen(mailargs[i]);
- while (len++ < 15) debug_printf(" ");
- debug_printf("%s: %s%s\n", mailargs[i], string_printing(arg),
- (commands->args[mailarg_index_expand].u != NULL &&
- Ustrcmp(mailargs[i], "file") == 0)? " (expanded)" : "");
- }
- }
- }
-
- /* Create the "address" for the autoreply. This is used only for logging,
- as the actual recipients are extracted from the To: line by -t. We use the
- same logic here to extract the working addresses (there may be more than
- one). Just in case there are a vast number of addresses, stop when the
- string gets too long. */
-
- tt = to;
- while (*tt != 0)
- {
- uschar *ss = parse_find_address_end(tt, FALSE);
- uschar *recipient, *errmess;
- int start, end, domain;
- int temp = *ss;
-
- *ss = 0;
- recipient = parse_extract_address(tt, &errmess, &start, &end, &domain,
- FALSE);
- *ss = temp;
-
- /* Ignore empty addresses and errors; an error will occur later if
- there's something really bad. */
-
- if (recipient != NULL)
- {
- log_addr = string_catn(log_addr, &size, &ptr,
- log_addr ? US"," : US">", 1);
- log_addr = string_cat(log_addr, &size, &ptr, recipient);
- }
-
- /* Check size */
-
- if (ptr > 256)
- {
- log_addr = string_catn(log_addr, &size, &ptr, US", ...", 5);
- break;
- }
-
- /* Move on past this address */
-
- tt = ss + (*ss? 1:0);
- while (isspace(*tt)) tt++;
- }
-
- if (log_addr == NULL)
- {
- log_addr = string_sprintf(">**bad-reply**");
- badflag = af_bad_reply;
- }
- else log_addr[ptr] = 0;
-
- addr = deliver_make_addr(log_addr, FALSE);
- setflag(addr, (af_pfr|badflag));
- if (commands->noerror) setflag(addr, af_ignore_error);
- addr->next = *generated;
- *generated = addr;
- addr->reply = store_get(sizeof(reply_item));
- addr->reply->from = NULL;
- addr->reply->to = string_copy(to);
- addr->reply->file_expand =
- commands->args[mailarg_index_expand].u != NULL;
- addr->reply->expand_forbid = expand_forbid;
- addr->reply->return_message =
- commands->args[mailarg_index_return].u != NULL;
- addr->reply->once_repeat = 0;
-
- if (commands->args[mailarg_index_once_repeat].u != NULL)
- {
- addr->reply->once_repeat =
- readconf_readtime(commands->args[mailarg_index_once_repeat].u, 0,
- FALSE);
- if (addr->reply->once_repeat < 0)
- {
- *error_pointer = string_sprintf("Bad time value for \"once_repeat\" "
- "in mail or vacation command: %s",
- commands->args[mailarg_index_once_repeat]);
- return FF_ERROR;
- }
- }
-
- /* Set up all the remaining string arguments (those other than "to") */
-
- for (i = 1; i < mailargs_string_passed; i++)
- {
- uschar *ss = commands->args[i].u;
- *((uschar **)(((uschar *)(addr->reply)) + reply_offsets[i])) =
- (ss == NULL)? NULL : string_copy(ss);
- }
- }
- break;
case testprint_command:
- if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
- {
- const uschar *s = string_printing(expargs[0]);
- if (filter_test == FTEST_NONE)
- debug_printf("Filter: testprint: %s\n", s);
- else
- printf("Testprint: %s\n", s);
- }
+ if (filter_test != FTEST_NONE || (debug_selector & D_filter) != 0)
+ {
+ const uschar *s = string_printing(expargs[0]);
+ if (filter_test == FTEST_NONE)
+ debug_printf("Filter: testprint: %s\n", s);
+ else
+ printf("Testprint: %s\n", s);
+ }
}
commands = commands->next;
expect_endif = 0;
output_indent = 0;
-filter_running = TRUE;
+f.filter_running = TRUE;
for (i = 0; i < FILTER_VARIABLE_COUNT; i++) filter_n[i] = 0;
/* To save having to pass certain values about all the time, make them static.
if (log_fd >= 0) (void)close(log_fd);
expand_nmax = -1;
-filter_running = FALSE;
+f.filter_running = FALSE;
headers_charset = save_headers_charset;
DEBUG(D_route) debug_printf("Filter: end of processing\n");
if (!dot_ended && !feof(stdin))
{
- if (!dot_ends)
+ if (!f.dot_ends)
{
while ((ch = getc(stdin)) != EOF)
{
if (is_system)
{
- system_filtering = TRUE;
- enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
+ f.system_filtering = TRUE;
+ f.enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
yield = filter_interpret
(filebuf,
RDO_DEFER|RDO_FAIL|RDO_FILTER|RDO_FREEZE|RDO_REWRITE, &generated, &error);
- enable_dollar_recipients = FALSE;
- system_filtering = FALSE;
+ f.enable_dollar_recipients = FALSE;
+ f.system_filtering = FALSE;
}
else
{
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#ifdef EXIM_PERL
-extern uschar *call_perl_cat(uschar *, int *, int *, uschar **, uschar *,
- uschar **);
+extern gstring *call_perl_cat(gstring *, uschar **, uschar *,
+ uschar **) WARN_UNUSED_RESULT;
extern void cleanup_perl(void);
extern uschar *init_perl(uschar *);
#endif
extern uschar * tls_cert_fprt_sha1(void *);
extern uschar * tls_cert_fprt_sha256(void *);
-extern int tls_client_start(int, host_item *, address_item *,
- transport_instance *
-# ifdef EXPERIMENTAL_DANE
- , dns_answer *
+extern void * tls_client_start(int, host_item *, address_item *,
+ transport_instance *,
+# ifdef SUPPORT_DANE
+ dns_answer *,
# endif
- );
-extern void tls_close(BOOL, BOOL);
+ tls_support *, uschar **);
+extern void tls_close(void *, int);
+extern BOOL tls_could_read(void);
extern int tls_export_cert(uschar *, size_t, void *);
extern int tls_feof(void);
extern int tls_ferror(void);
extern void tls_free_cert(void **);
extern int tls_getc(unsigned);
+extern uschar *tls_getbuf(unsigned *);
extern void tls_get_cache(void);
extern int tls_import_cert(const uschar *, void **);
-extern int tls_read(BOOL, uschar *, size_t);
-extern int tls_server_start(const uschar *);
+extern int tls_read(void *, uschar *, size_t);
+extern int tls_server_start(const uschar *, uschar **);
extern BOOL tls_smtp_buffered(void);
extern int tls_ungetc(int);
-extern int tls_write(BOOL, const uschar *, size_t);
+extern int tls_write(void *, const uschar *, size_t, BOOL);
extern uschar *tls_validate_require_cipher(void);
extern void tls_version_report(FILE *);
# ifndef USE_GNUTLS
extern uschar * tls_field_from_dn(uschar *, const uschar *);
extern BOOL tls_is_name_for_cert(const uschar *, void *);
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
extern int tlsa_lookup(const host_item *, dns_answer *, BOOL);
# endif
extern tree_node *acl_var_create(uschar *);
extern void acl_var_write(uschar *, uschar *, void *);
+
+#ifdef EXPERIMENTAL_ARC
+extern void *arc_ams_setup_sign_bodyhash(void);
+extern const uschar *arc_header_feed(gstring *, BOOL);
+extern gstring *arc_sign(const uschar *, gstring *, uschar **);
+extern void arc_sign_init(void);
+extern const uschar *acl_verify_arc(void);
+extern uschar * fn_arc_domains(void);
+#endif
+
extern void assert_no_variables(void *, int, const char *, int);
extern int auth_call_pam(const uschar *, uschar **);
extern int auth_call_pwcheck(uschar *, uschar **);
extern int auth_get_data(uschar **, uschar *, int);
extern int auth_get_no64_data(uschar **, uschar *);
+extern void auth_show_supported(FILE *);
extern uschar *auth_xtextencode(uschar *, int);
extern int auth_xtextdecode(uschar *, uschar **);
+#ifdef EXPERIMENTAL_ARC
+extern gstring *authres_arc(gstring *);
+#endif
+#ifndef DISABLE_DKIM
+extern gstring *authres_dkim(gstring *);
+#endif
+#ifdef EXPERIMENTAL_DMARC
+extern gstring *authres_dmarc(gstring *);
+#endif
+extern gstring *authres_smtpauth(gstring *);
+#ifdef SUPPORT_SPF
+extern gstring *authres_spf(gstring *);
+#endif
+
extern uschar *b64encode(uschar *, int);
-extern int b64decode(uschar *, uschar **);
+extern int b64decode(const uschar *, uschar **);
extern int bdat_getc(unsigned);
+extern uschar *bdat_getbuf(unsigned *);
extern int bdat_ungetc(int);
extern void bdat_flush_data(void);
extern void bits_clear(unsigned int *, size_t, int *);
extern void bits_set(unsigned int *, size_t, int *);
-extern void cancel_cutthrough_connection(const char *);
+extern void cancel_cutthrough_connection(BOOL, const uschar *);
extern int check_host(void *, const uschar *, const uschar **, uschar **);
extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...);
extern pid_t child_open_uid(const uschar **, const uschar **, int,
uid_t *, gid_t *, int *, int *, uschar *, BOOL);
extern BOOL cleanup_environment(void);
+extern void cutthrough_data_puts(uschar *, int);
+extern void cutthrough_data_put_nl(void);
extern uschar *cutthrough_finaldot(void);
extern BOOL cutthrough_flush_send(void);
extern BOOL cutthrough_headers_send(void);
extern BOOL cutthrough_predata(void);
-extern BOOL cutthrough_puts(uschar *, int);
-extern BOOL cutthrough_put_nl(void);
+extern void release_cutthrough_connection(const uschar *);
extern void daemon_go(void);
extern void debug_printf_indent(const char *, ...) PRINTF_FUNCTION(1,2);
extern void debug_print_string(uschar *);
extern void debug_print_tree(tree_node *);
-extern void debug_vprintf(const char *, va_list);
+extern void debug_vprintf(int, const char *, va_list);
extern void decode_bits(unsigned int *, size_t, int *,
uschar *, bit_table *, int, uschar *, int);
extern address_item *deliver_make_addr(uschar *, BOOL);
extern void deliver_succeeded(address_item *);
extern uschar *deliver_get_sender_address (uschar *id);
+extern void delivery_re_exec(int);
extern BOOL directory_make(const uschar *, const uschar *, int, BOOL);
#ifndef DISABLE_DKIM
-extern BOOL dkim_transport_write_message(int, transport_ctx *,
- struct ob_dkim *);
+extern uschar *dkim_exim_query_dns_txt(uschar *);
+extern void dkim_exim_sign_init(void);
+
+extern BOOL dkim_transport_write_message(transport_ctx *,
+ struct ob_dkim *, const uschar ** errstr);
#endif
extern dns_address *dns_address_from_rr(dns_answer *, dns_record *);
extern int dns_basic_lookup(dns_answer *, const uschar *, int);
extern void msg_event_raise(const uschar *, const address_item *);
#endif
extern const uschar * exim_errstr(int);
-extern void exim_exit(int);
+extern void exim_exit(int, const uschar *);
extern void exim_nullstd(void);
extern void exim_setugid(uid_t, gid_t, BOOL, uschar *);
-extern int exim_tvcmp(struct timeval *, struct timeval *);
extern void exim_wait_tick(struct timeval *, int);
extern int exp_bool(address_item *addr,
uschar *mtype, uschar *mname, unsigned dgb_opt, uschar *oname, BOOL bvalue,
uschar *svalue, BOOL *rvalue);
extern BOOL expand_check_condition(uschar *, uschar *, uschar *);
+extern uschar *expand_file_big_buffer(const uschar *);
extern uschar *expand_string(uschar *); /* public, cannot make const */
extern const uschar *expand_cstring(const uschar *); /* ... so use this one */
extern uschar *expand_hide_passwords(uschar * );
extern uschar * fn_hdrs_added(void);
+extern void gstring_reset_unused(gstring *);
+
extern void header_add(int, const char *, ...);
extern int header_checkname(header_line *, BOOL);
extern BOOL header_match(uschar *, BOOL, BOOL, string_item *, int, ...);
extern void invert_address(uschar *, uschar *);
extern int ip_addr(void *, int, const uschar *, int);
extern int ip_bind(int, int, uschar *, int);
-extern int ip_connect(int, int, const uschar *, int, int, BOOL);
+extern int ip_connect(int, int, const uschar *, int, int, const blob *);
extern int ip_connectedsocket(int, const uschar *, int, int,
- int, host_item *, uschar **);
+ int, host_item *, uschar **, const blob *);
extern int ip_get_address_family(int);
extern void ip_keepalive(int, const uschar *, BOOL);
-extern int ip_recv(int, uschar *, int, int);
+extern int ip_recv(client_conn_ctx *, uschar *, int, int);
extern int ip_socket(int, int);
extern int ip_tcpsocket(const uschar *, uschar **, int);
extern int log_create_as_exim(uschar *);
extern void log_close_all(void);
-extern macro_item * macro_create(const uschar *, const uschar *, BOOL, BOOL);
+extern macro_item * macro_create(const uschar *, const uschar *, BOOL);
+extern BOOL macro_read_assignment(uschar *);
+extern uschar *macros_expand(int, int *, BOOL *);
extern void mainlog_close(void);
#ifdef WITH_CONTENT_SCAN
extern int malware(const uschar *, int);
extern int malware_in_file(uschar *);
extern void malware_init(void);
+extern void malware_show_supported(FILE *);
#endif
extern int match_address_list(const uschar *, BOOL, BOOL, const uschar **,
unsigned int *, int, int, const uschar **);
+extern int match_address_list_basic(const uschar *, const uschar **, int);
extern int match_check_list(const uschar **, int, tree_node **, unsigned int **,
int(*)(void *, const uschar *, const uschar **, uschar **), void *, int,
const uschar *, const uschar **);
extern BOOL moan_skipped_syntax_errors(uschar *, error_block *, uschar *,
BOOL, uschar *);
extern void moan_smtp_batch(uschar *, const char *, ...) PRINTF_FUNCTION(2,3);
+extern BOOL moan_send_message(uschar *, int, error_block *eblock,
+ header_line *, FILE *, uschar *);
extern void moan_tell_someone(uschar *, address_item *,
const uschar *, const char *, ...) PRINTF_FUNCTION(4,5);
extern BOOL moan_to_sender(int, error_block *, header_line *, FILE *, BOOL);
extern uschar *readconf_find_option(void *);
extern void readconf_main(BOOL);
extern void readconf_options_from_list(optionlist *, unsigned, const uschar *, uschar *);
-extern void readconf_options_routers(void);
-extern void readconf_options_transports(void);
-extern void readconf_print(uschar *, uschar *, BOOL);
+extern BOOL readconf_print(uschar *, uschar *, BOOL);
extern uschar *readconf_printtime(int);
extern uschar *readconf_readname(uschar *, int, uschar *);
extern int readconf_readtime(const uschar *, int, BOOL);
extern BOOL receive_check_fs(int);
extern BOOL receive_check_set_sender(uschar *);
extern BOOL receive_msg(BOOL);
-extern int receive_statvfs(BOOL, int *);
+extern int_eximarith_t receive_statvfs(BOOL, int *);
extern void receive_swallow_smtp(void);
#ifdef WITH_CONTENT_SCAN
extern int regex(const uschar **);
extern BOOL route_find_expanded_user(uschar *, uschar *, uschar *,
struct passwd **, uid_t *, uschar **);
extern void route_init(void);
+extern void route_show_supported(FILE *);
extern void route_tidyup(void);
extern uschar *search_find(void *, uschar *, uschar *, int, const uschar *, int,
extern void sigalrm_handler(int);
extern BOOL smtp_buffered(void);
extern void smtp_closedown(uschar *);
-extern int smtp_connect(host_item *, int, int, uschar *, int,
- transport_instance *);
+extern void smtp_command_timeout_exit(void);
+extern void smtp_command_sigterm_exit(void);
+extern void smtp_data_timeout_exit(void);
+extern void smtp_data_sigint_exit(void);
+extern uschar *smtp_cmd_hist(void);
+extern int smtp_connect(smtp_connect_args *, const blob *);
extern int smtp_sock_connect(host_item *, int, int, uschar *,
- transport_instance * tb, int);
+ transport_instance * tb, int, const blob *);
extern int smtp_feof(void);
extern int smtp_ferror(void);
extern uschar *smtp_get_connection_info(void);
uschar **, uschar *);
extern BOOL smtp_get_port(uschar *, address_item *, int *, uschar *);
extern int smtp_getc(unsigned);
+extern uschar *smtp_getbuf(unsigned *);
extern void smtp_get_cache(void);
extern int smtp_handle_acl_fail(int, int, uschar *, uschar *);
extern void smtp_log_no_mail(void);
extern void smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL);
-extern BOOL smtp_read_response(smtp_inblock *, uschar *, int, int, int);
+extern void smtp_proxy_tls(void *, uschar *, size_t, int *, int);
+extern BOOL smtp_read_response(void *, uschar *, int, int, int);
+extern void smtp_reset(void *);
extern void smtp_respond(uschar *, int, BOOL, uschar *);
extern void smtp_notquit_exit(uschar *, uschar *, uschar *, ...);
+extern void smtp_port_for_connect(host_item *, int);
extern void smtp_send_prohibition_message(int, uschar *);
extern int smtp_setup_msg(void);
extern BOOL smtp_start_session(void);
extern int smtp_ungetc(int);
extern BOOL smtp_verify_helo(void);
-extern int smtp_write_command(smtp_outblock *, BOOL, const char *, ...) PRINTF_FUNCTION(3,4);
+extern int smtp_write_command(void *, int, const char *, ...) PRINTF_FUNCTION(3,4);
#ifdef WITH_CONTENT_SCAN
extern int spam(const uschar **);
-extern FILE *spool_mbox(unsigned long *, const uschar *);
+extern FILE *spool_mbox(unsigned long *, const uschar *, uschar **);
#endif
-extern BOOL spool_move_message(uschar *, uschar *, uschar *, uschar *);
+extern void spool_clear_header_globals(void);
extern uschar *spool_dname(const uschar *, uschar *);
extern uschar *spool_fname(const uschar *, const uschar *, const uschar *, const uschar *);
-extern uschar *spool_sname(const uschar *, uschar *);
+extern BOOL spool_move_message(uschar *, uschar *, uschar *, uschar *);
extern int spool_open_datafile(uschar *);
extern int spool_open_temp(uschar *);
extern int spool_read_header(uschar *, BOOL, BOOL);
+extern uschar *spool_sname(const uschar *, uschar *);
extern int spool_write_header(uschar *, int, uschar **);
extern int stdin_getc(unsigned);
extern int stdin_feof(void);
extern int stdin_ferror(void);
extern int stdin_ungetc(int);
-extern uschar *string_append(uschar *, int *, int *, int, ...);
-extern uschar *string_append_listele(uschar *, uschar, const uschar *);
-extern uschar *string_append_listele_n(uschar *, uschar, const uschar *, unsigned);
+extern gstring *string_append(gstring *, int, ...) WARN_UNUSED_RESULT;
+extern gstring *string_append_listele(gstring *, uschar, const uschar *) WARN_UNUSED_RESULT;
+extern gstring *string_append_listele_n(gstring *, uschar, const uschar *, unsigned) WARN_UNUSED_RESULT;
+extern gstring *string_append2_listele_n(gstring *, const uschar *, const uschar *, unsigned) WARN_UNUSED_RESULT;
extern uschar *string_base62(unsigned long int);
-extern uschar *string_cat(uschar *, int *, int *, const uschar *);
-extern uschar *string_catn(uschar *, int *, int *, const uschar *, int);
+extern gstring *string_cat (gstring *, const uschar * ) WARN_UNUSED_RESULT;
+extern gstring *string_catn(gstring *, const uschar *, int) WARN_UNUSED_RESULT;
extern int string_compare_by_pointer(const void *, const void *);
extern uschar *string_copy_dnsdomain(uschar *);
extern uschar *string_copy_malloc(const uschar *);
extern uschar *string_copylc(const uschar *);
extern uschar *string_copynlc(uschar *, int);
extern uschar *string_dequote(const uschar **);
+extern gstring *string_fmt_append(gstring *, const char *, ...) ALMOST_PRINTF(2,3);
extern BOOL string_format(uschar *, int, const char *, ...) ALMOST_PRINTF(3,4);
extern uschar *string_format_size(int, uschar *);
+extern uschar *string_from_gstring(gstring *);
+extern gstring *string_get(unsigned);
extern int string_interpret_escape(const uschar **);
extern int string_is_ip_address(const uschar *, int *);
#ifdef SUPPORT_I18N
extern uschar *string_open_failed(int, const char *, ...) PRINTF_FUNCTION(2,3);
extern const uschar *string_printing2(const uschar *, BOOL);
extern uschar *string_split_message(uschar *);
+extern uschar *string_timediff(struct timeval *);
+extern uschar *string_timesince(struct timeval *);
extern uschar *string_unprinting(uschar *);
#ifdef SUPPORT_I18N
extern uschar *string_address_utf8_to_alabel(const uschar *, uschar **);
extern uschar *string_localpart_alabel_to_utf8(const uschar *, uschar **);
extern uschar *string_localpart_utf8_to_alabel(const uschar *, uschar **);
#endif
-extern BOOL string_vformat(uschar *, int, const char *, va_list);
+extern gstring *string_vformat(gstring *, BOOL, const char *, va_list);
extern int strcmpic(const uschar *, const uschar *);
extern int strncmpic(const uschar *, const uschar *, int);
extern uschar *strstric(uschar *, uschar *, BOOL);
+#ifdef EXIM_TFO_PROBE
+extern void tfo_probe(void);
+#endif
+extern void timesince(struct timeval * diff, struct timeval * then);
+extern void tls_modify_variables(tls_support *);
extern uschar *tod_stamp(int);
-extern void tls_modify_variables(tls_support *);
extern BOOL transport_check_waiting(const uschar *, const uschar *, int, uschar *,
BOOL *, oicf, void*);
extern void transport_init(void);
+extern void transport_do_pass_socket(const uschar *, const uschar *,
+ const uschar *, uschar *, int);
extern BOOL transport_pass_socket(const uschar *, const uschar *, const uschar *, uschar *,
int);
extern uschar *transport_rcpt_address(address_item *, BOOL);
extern BOOL transport_set_up_command(const uschar ***, uschar *,
BOOL, int, address_item *, uschar *, uschar **);
extern void transport_update_waiting(host_item *, uschar *);
-extern BOOL transport_write_block(int, uschar *, int);
+extern BOOL transport_write_block(transport_ctx *, uschar *, int, BOOL);
+extern void transport_write_reset(int);
extern BOOL transport_write_string(int, const char *, ...);
-extern BOOL transport_headers_send(int, transport_ctx *,
- BOOL (*)(int, transport_ctx *, uschar *, int));
-extern BOOL transport_write_message(int, transport_ctx *, int);
+extern BOOL transport_headers_send(transport_ctx *,
+ BOOL (*)(transport_ctx *, uschar *, int));
+extern void transport_show_supported(FILE *);
+extern BOOL transport_write_message(transport_ctx *, int);
extern void tree_add_duplicate(uschar *, address_item *);
extern void tree_add_nonrecipient(uschar *);
extern void tree_add_unusable(host_item *);
extern int verify_check_header_names_ascii(uschar **);
extern int verify_check_host(uschar **);
extern int verify_check_notblind(void);
-extern int verify_check_given_host(uschar **, host_item *);
+extern int verify_check_given_host(const uschar **, const host_item *);
extern int verify_check_this_host(const uschar **, unsigned int *,
const uschar*, const uschar *, const uschar **);
extern address_item *verify_checked_sender(uschar *);
extern BOOL verify_sender_preliminary(int *, uschar **);
extern void version_init(void);
+extern BOOL write_chunk(transport_ctx *, uschar *, int);
extern ssize_t write_to_fd_buf(int, const uschar *, size_t);
+
/* vi: aw
*/
/* End of functions.h */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* All the global variables are defined together in this one module, so
/* These variables are outside the #ifdef because it keeps the code less
cluttered in several places (e.g. during logging) if we can always refer to
-them. Also, the tls_ variables are now always visible. */
+them. Also, the tls_ variables are now always visible. Note that these are
+only used for smtp connections, not for service-daemon access. */
tls_support tls_in = {
- -1, /* tls_active */
- 0, /* tls_bits */
- FALSE,/* tls_certificate_verified */
-#ifdef EXPERIMENTAL_DANE
- FALSE,/* dane_verified */
- 0, /* tlsa_usage */
-#endif
- NULL, /* tls_cipher */
- FALSE,/* tls_on_connect */
- NULL, /* tls_on_connect_ports */
- NULL, /* tls_ourcert */
- NULL, /* tls_peercert */
- NULL, /* tls_peerdn */
- NULL, /* tls_sni */
- 0 /* tls_ocsp */
+ .active = {.sock = -1},
+ .bits = 0,
+ .certificate_verified = FALSE,
+#ifdef SUPPORT_DANE
+ .dane_verified = FALSE,
+ .tlsa_usage = 0,
+#endif
+ .cipher = NULL,
+ .on_connect = FALSE,
+ .on_connect_ports = NULL,
+ .ourcert = NULL,
+ .peercert = NULL,
+ .peerdn = NULL,
+ .sni = NULL,
+ .ocsp = OCSP_NOT_REQ
};
tls_support tls_out = {
- -1, /* tls_active */
- 0, /* tls_bits */
- FALSE,/* tls_certificate_verified */
-#ifdef EXPERIMENTAL_DANE
- FALSE,/* dane_verified */
- 0, /* tlsa_usage */
-#endif
- NULL, /* tls_cipher */
- FALSE,/* tls_on_connect */
- NULL, /* tls_on_connect_ports */
- NULL, /* tls_ourcert */
- NULL, /* tls_peercert */
- NULL, /* tls_peerdn */
- NULL, /* tls_sni */
- 0 /* tls_ocsp */
+ .active = {.sock = -1},
+ .bits = 0,
+ .certificate_verified = FALSE,
+#ifdef SUPPORT_DANE
+ .dane_verified = FALSE,
+ .tlsa_usage = 0,
+#endif
+ .cipher = NULL,
+ .on_connect = FALSE,
+ .on_connect_ports = NULL,
+ .ourcert = NULL,
+ .peercert = NULL,
+ .peerdn = NULL,
+ .sni = NULL,
+ .ocsp = OCSP_NOT_REQ
};
uschar *dsn_envid = NULL;
uschar *tls_privatekey = NULL;
BOOL tls_remember_esmtp = FALSE;
uschar *tls_require_ciphers = NULL;
+# ifdef EXPERIMENTAL_REQUIRETLS
+uschar tls_requiretls = 0; /* REQUIRETLS_MSG etc. bit #defines */
+uschar *tls_advertise_requiretls = US"*";
+const pcre *regex_REQUIRETLS = NULL;
+# endif
uschar *tls_try_verify_hosts = NULL;
uschar *tls_verify_certificates= US"system";
uschar *tls_verify_hosts = NULL;
incoming TCP/IP. The defaults use stdin. We never need these for any
stand-alone tests. */
-#ifndef STAND_ALONE
+#if !defined(STAND_ALONE) && !defined(MACRO_PREDEF)
int (*lwr_receive_getc)(unsigned) = stdin_getc;
+uschar * (*lwr_receive_getbuf)(unsigned *) = NULL;
int (*lwr_receive_ungetc)(int) = stdin_ungetc;
int (*receive_getc)(unsigned) = stdin_getc;
+uschar * (*receive_getbuf)(unsigned *) = NULL;
void (*receive_get_cache)(void)= NULL;
int (*receive_ungetc)(int) = stdin_ungetc;
int (*receive_feof)(void) = stdin_feof;
int address_expansions_count = sizeof(address_expansions)/sizeof(uschar **);
-/* General global variables */
+/******************************************************************************/
+/* General global variables. Boolean flags are done as a group
+so that only one bit each is needed, packed, for all those we never
+need to take a pointer - and only a char for the rest.
+This means a struct, unfortunately since it clutters the sourcecode. */
+
+struct global_flags f =
+{
+ .acl_temp_details = FALSE,
+ .active_local_from_check = FALSE,
+ .active_local_sender_retain = FALSE,
+ .address_test_mode = FALSE,
+ .admin_user = FALSE,
+ .allow_auth_unadvertised= FALSE,
+ .allow_unqualified_recipient = TRUE, /* For local messages */
+ .allow_unqualified_sender = TRUE, /* Reset for SMTP */
+ .authentication_local = FALSE,
+
+ .background_daemon = TRUE,
+
+ .chunking_offered = FALSE,
+ .config_changed = FALSE,
+ .continue_more = FALSE,
+
+ .daemon_listen = FALSE,
+ .debug_daemon = FALSE,
+ .deliver_firsttime = FALSE,
+ .deliver_force = FALSE,
+ .deliver_freeze = FALSE,
+ .deliver_force_thaw = FALSE,
+ .deliver_manual_thaw = FALSE,
+ .deliver_selectstring_regex = FALSE,
+ .deliver_selectstring_sender_regex = FALSE,
+ .disable_callout_flush = FALSE,
+ .disable_delay_flush = FALSE,
+ .disable_logging = FALSE,
+#ifndef DISABLE_DKIM
+ .dkim_disable_verify = FALSE,
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ .dmarc_has_been_checked = FALSE,
+ .dmarc_disable_verify = FALSE,
+ .dmarc_enable_forensic = FALSE,
+#endif
+ .dont_deliver = FALSE,
+ .dot_ends = TRUE,
+
+ .enable_dollar_recipients = FALSE,
+ .expand_string_forcedfail = FALSE,
+
+ .filter_running = FALSE,
+
+ .header_rewritten = FALSE,
+ .helo_verified = FALSE,
+ .helo_verify_failed = FALSE,
+ .host_checking_callout = FALSE,
+ .host_find_failed_syntax= FALSE,
+
+ .inetd_wait_mode = FALSE,
+ .is_inetd = FALSE,
+
+ .local_error_message = FALSE,
+ .log_testing_mode = FALSE,
+
+#ifdef WITH_CONTENT_SCAN
+ .no_mbox_unspool = FALSE,
+#endif
+ .no_multiline_responses = FALSE,
+
+ .parse_allow_group = FALSE,
+ .parse_found_group = FALSE,
+ .pipelining_enable = TRUE,
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+ .proxy_session_failed = FALSE,
+#endif
+
+ .queue_2stage = FALSE,
+ .queue_only_policy = FALSE,
+ .queue_run_first_delivery = FALSE,
+ .queue_run_force = FALSE,
+ .queue_run_local = FALSE,
+ .queue_running = FALSE,
+ .queue_smtp = FALSE,
+
+ .really_exim = TRUE,
+ .receive_call_bombout = FALSE,
+ .recipients_discarded = FALSE,
+ .running_in_test_harness = FALSE,
+
+ .search_find_defer = FALSE,
+ .sender_address_forced = FALSE,
+ .sender_host_notsocket = FALSE,
+ .sender_host_unknown = FALSE,
+ .sender_local = FALSE,
+ .sender_name_forced = FALSE,
+ .sender_set_untrusted = FALSE,
+ .smtp_authenticated = FALSE,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ .smtp_in_early_pipe_advertised = FALSE,
+ .smtp_in_early_pipe_no_auth = FALSE,
+ .smtp_in_early_pipe_used = FALSE,
+#endif
+ .smtp_in_pipelining_advertised = FALSE,
+ .smtp_in_pipelining_used = FALSE,
+ .spool_file_wireformat = FALSE,
+ .submission_mode = FALSE,
+ .suppress_local_fixups = FALSE,
+ .suppress_local_fixups_default = FALSE,
+ .synchronous_delivery = FALSE,
+ .system_filtering = FALSE,
+
+ .tcp_fastopen_ok = FALSE,
+ .tcp_in_fastopen = FALSE,
+ .tcp_in_fastopen_data = FALSE,
+ .tcp_in_fastopen_logged = FALSE,
+ .tcp_out_fastopen_logged= FALSE,
+ .timestamps_utc = FALSE,
+ .transport_filter_timed_out = FALSE,
+ .trusted_caller = FALSE,
+ .trusted_config = TRUE,
+};
+
+/******************************************************************************/
+/* These are the flags which are either variables or mainsection options,
+so an address is needed for access, or are exported to local_scan. */
+
+BOOL accept_8bitmime = TRUE; /* deliberately not RFC compliant */
+BOOL allow_domain_literals = FALSE;
+BOOL allow_mx_to_ip = FALSE;
+BOOL allow_utf8_domains = FALSE;
+BOOL authentication_failed = FALSE;
+
+BOOL bounce_return_body = TRUE;
+BOOL bounce_return_message = TRUE;
+BOOL check_rfc2047_length = TRUE;
+BOOL commandline_checks_require_admin = FALSE;
+
+#ifdef EXPERIMENTAL_DCC
+BOOL dcc_direct_add_header = FALSE;
+#endif
+BOOL debug_store = FALSE;
+BOOL delivery_date_remove = TRUE;
+BOOL deliver_drop_privilege = FALSE;
+#ifdef ENABLE_DISABLE_FSYNC
+BOOL disable_fsync = FALSE;
+#endif
+BOOL disable_ipv6 = FALSE;
+BOOL dns_csa_use_reverse = TRUE;
+BOOL drop_cr = FALSE; /* No longer used */
+
+BOOL envelope_to_remove = TRUE;
+BOOL exim_gid_set = TRUE; /* This gid is always set */
+BOOL exim_uid_set = TRUE; /* This uid is always set */
+BOOL extract_addresses_remove_arguments = TRUE;
+
+BOOL host_checking = FALSE;
+BOOL host_lookup_deferred = FALSE;
+BOOL host_lookup_failed = FALSE;
+BOOL ignore_fromline_local = FALSE;
+
+BOOL local_from_check = TRUE;
+BOOL local_sender_retain = FALSE;
+BOOL log_timezone = FALSE;
+BOOL message_body_newlines = FALSE;
+BOOL message_logs = TRUE;
+#ifdef SUPPORT_I18N
+BOOL message_smtputf8 = FALSE;
+#endif
+BOOL mua_wrapper = FALSE;
+
+BOOL preserve_message_logs = FALSE;
+BOOL print_topbitchars = FALSE;
+BOOL prod_requires_admin = TRUE;
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+BOOL proxy_session = FALSE;
+#endif
+
+BOOL queue_list_requires_admin = TRUE;
+BOOL queue_only = FALSE;
+BOOL queue_only_load_latch = TRUE;
+BOOL queue_only_override = TRUE;
+BOOL queue_run_in_order = FALSE;
+BOOL recipients_max_reject = FALSE;
+BOOL return_path_remove = TRUE;
+
+BOOL smtp_batched_input = FALSE;
+BOOL sender_helo_dnssec = FALSE;
+BOOL sender_host_dnssec = FALSE;
+BOOL smtp_accept_keepalive = TRUE;
+BOOL smtp_check_spool_space = TRUE;
+BOOL smtp_enforce_sync = TRUE;
+BOOL smtp_etrn_serialize = TRUE;
+BOOL smtp_input = FALSE;
+BOOL smtp_return_error_details = FALSE;
+#ifdef SUPPORT_SPF
+BOOL spf_result_guessed = FALSE;
+#endif
+BOOL split_spool_directory = FALSE;
+BOOL spool_wireformat = FALSE;
+#ifdef EXPERIMENTAL_SRS
+BOOL srs_usehash = TRUE;
+BOOL srs_usetimestamp = TRUE;
+#endif
+BOOL strict_acl_vars = FALSE;
+BOOL strip_excess_angle_brackets = FALSE;
+BOOL strip_trailing_dot = FALSE;
+BOOL syslog_duplication = TRUE;
+BOOL syslog_pid = TRUE;
+BOOL syslog_timestamp = TRUE;
+BOOL system_filter_gid_set = FALSE;
+BOOL system_filter_uid_set = FALSE;
+
+BOOL tcp_nodelay = TRUE;
+BOOL write_rejectlog = TRUE;
+
+/******************************************************************************/
header_line *acl_added_headers = NULL;
tree_node *acl_anchor = NULL;
uschar *acl_smtp_starttls = NULL;
uschar *acl_smtp_vrfy = NULL;
-BOOL acl_temp_details = FALSE;
tree_node *acl_var_c = NULL;
tree_node *acl_var_m = NULL;
uschar *acl_verify_message = NULL;
US"0" /* unknown; not relevant */
};
-BOOL active_local_from_check = FALSE;
-BOOL active_local_sender_retain = FALSE;
-BOOL accept_8bitmime = TRUE; /* deliberately not RFC compliant */
uschar *add_environment = NULL;
address_item *addr_duplicate = NULL;
address_item address_defaults = {
- NULL, /* next */
- NULL, /* parent */
- NULL, /* first */
- NULL, /* dupof */
- NULL, /* start_router */
- NULL, /* router */
- NULL, /* transport */
- NULL, /* host_list */
- NULL, /* host_used */
- NULL, /* fallback_hosts */
- NULL, /* reply */
- NULL, /* retries */
- NULL, /* address */
- NULL, /* unique */
- NULL, /* cc_local_part */
- NULL, /* lc_local_part */
- NULL, /* local_part */
- NULL, /* prefix */
- NULL, /* suffix */
- NULL, /* domain */
- NULL, /* address_retry_key */
- NULL, /* domain_retry_key */
- NULL, /* current_dir */
- NULL, /* home_dir */
- NULL, /* message */
- NULL, /* user_message */
- NULL, /* onetime_parent */
- NULL, /* pipe_expandn */
- NULL, /* return_filename */
- NULL, /* self_hostname */
- NULL, /* shadow_message */
+ .next = NULL,
+ .parent = NULL,
+ .first = NULL,
+ .dupof = NULL,
+ .start_router = NULL,
+ .router = NULL,
+ .transport = NULL,
+ .host_list = NULL,
+ .host_used = NULL,
+ .fallback_hosts = NULL,
+ .reply = NULL,
+ .retries = NULL,
+ .address = NULL,
+ .unique = NULL,
+ .cc_local_part = NULL,
+ .lc_local_part = NULL,
+ .local_part = NULL,
+ .prefix = NULL,
+ .suffix = NULL,
+ .domain = NULL,
+ .address_retry_key = NULL,
+ .domain_retry_key = NULL,
+ .current_dir = NULL,
+ .home_dir = NULL,
+ .message = NULL,
+ .user_message = NULL,
+ .onetime_parent = NULL,
+ .pipe_expandn = NULL,
+ .return_filename = NULL,
+ .self_hostname = NULL,
+ .shadow_message = NULL,
#ifdef SUPPORT_TLS
- NULL, /* cipher */
- NULL, /* ourcert */
- NULL, /* peercert */
- NULL, /* peerdn */
- OCSP_NOT_REQ, /* ocsp */
+ .cipher = NULL,
+ .ourcert = NULL,
+ .peercert = NULL,
+ .peerdn = NULL,
+ .ocsp = OCSP_NOT_REQ,
#endif
#ifdef EXPERIMENTAL_DSN_INFO
- NULL, /* smtp_greeting */
- NULL, /* helo_response */
-#endif
- NULL, /* authenticator */
- NULL, /* auth_id */
- NULL, /* auth_sndr */
- NULL, /* dsn_orcpt */
- 0, /* dsn_flags */
- 0, /* dsn_aware */
- (uid_t)(-1), /* uid */
- (gid_t)(-1), /* gid */
- 0, /* flags */
- { 0 }, /* domain_cache - any larger array should be zeroed */
- { 0 }, /* localpart_cache - ditto */
- -1, /* mode */
- 0, /* more_errno */
- ERRNO_UNKNOWNERROR, /* basic_errno */
- 0, /* child_count */
- -1, /* return_file */
- SPECIAL_NONE, /* special_action */
- DEFER, /* transport_return */
- { /* fields that are propagated to children */
- NULL, /* address_data */
- NULL, /* domain_data */
- NULL, /* localpart_data */
- NULL, /* errors_address */
- NULL, /* extra_headers */
- NULL, /* remove_headers */
+ .smtp_greeting = NULL,
+ .helo_response = NULL,
+#endif
+ .authenticator = NULL,
+ .auth_id = NULL,
+ .auth_sndr = NULL,
+ .dsn_orcpt = NULL,
+ .dsn_flags = 0,
+ .dsn_aware = 0,
+ .uid = (uid_t)(-1),
+ .gid = (gid_t)(-1),
+ .flags = { 0 },
+ .domain_cache = { 0 }, /* domain_cache - any larger array should be zeroed */
+ .localpart_cache = { 0 }, /* localpart_cache - ditto */
+ .mode = -1,
+ .more_errno = 0,
+ .delivery_usec = 0,
+ .basic_errno = ERRNO_UNKNOWNERROR,
+ .child_count = 0,
+ .return_file = -1,
+ .special_action = SPECIAL_NONE,
+ .transport_return = DEFER,
+ .prop = { /* fields that are propagated to children */
+ .address_data = NULL,
+ .domain_data = NULL,
+ .localpart_data = NULL,
+ .errors_address = NULL,
+ .extra_headers = NULL,
+ .remove_headers = NULL,
#ifdef EXPERIMENTAL_SRS
- NULL, /* srs_sender */
+ .srs_sender = NULL,
#endif
+ .ignore_error = FALSE,
#ifdef SUPPORT_I18N
- FALSE, /* utf8 */
+ .utf8_msg = FALSE,
+ .utf8_downcvt = FALSE,
+ .utf8_downcvt_maybe = FALSE
#endif
}
};
uschar *address_file = NULL;
uschar *address_pipe = NULL;
-BOOL address_test_mode = FALSE;
tree_node *addresslist_anchor = NULL;
int addresslist_count = 0;
gid_t *admin_groups = NULL;
-BOOL admin_user = FALSE;
-BOOL allow_auth_unadvertised= FALSE;
-BOOL allow_domain_literals = FALSE;
-BOOL allow_mx_to_ip = FALSE;
-BOOL allow_unqualified_recipient = TRUE; /* For local messages */
-BOOL allow_unqualified_sender = TRUE; /* Reset for SMTP */
-BOOL allow_utf8_domains = FALSE;
+
+#ifdef EXPERIMENTAL_ARC
+struct arc_set *arc_received = NULL;
+int arc_received_instance = 0;
+int arc_oldest_pass = 0;
+const uschar *arc_state = NULL;
+const uschar *arc_state_reason = NULL;
+#endif
+
uschar *authenticated_fail_id = NULL;
uschar *authenticated_id = NULL;
uschar *authenticated_sender = NULL;
-BOOL authentication_failed = FALSE;
auth_instance *auths = NULL;
uschar *auth_advertise_hosts = US"*";
auth_instance auth_defaults = {
- NULL, /* chain pointer */
- NULL, /* name */
- NULL, /* info */
- NULL, /* private options block pointer */
- NULL, /* driver_name */
- NULL, /* advertise_condition */
- NULL, /* client_condition */
- NULL, /* public_name */
- NULL, /* set_id */
- NULL, /* set_client_id */
- NULL, /* server_mail_auth_condition */
- NULL, /* server_debug_string */
- NULL, /* server_condition */
- FALSE, /* client */
- FALSE, /* server */
- FALSE /* advertised */
+ .next = NULL,
+ .name = NULL,
+ .info = NULL,
+ .options_block = NULL,
+ .driver_name = NULL,
+ .advertise_condition = NULL,
+ .client_condition = NULL,
+ .public_name = NULL,
+ .set_id = NULL,
+ .set_client_id = NULL,
+ .mail_auth_condition = NULL,
+ .server_debug_string = NULL,
+ .server_condition = NULL,
+ .client = FALSE,
+ .server = FALSE,
+ .advertised = FALSE
};
uschar *auth_defer_msg = US"reason not recorded";
uschar *auth_vars[AUTH_VARS];
int auto_thaw = 0;
#ifdef WITH_CONTENT_SCAN
-BOOL av_failed = FALSE;
+int av_failed = FALSE; /* boolean but accessed as vtype_int*/
uschar *av_scanner = US"sophie:/var/run/sophie"; /* AV scanner */
#endif
-BOOL background_daemon = TRUE;
-
#if BASE_62 == 62
uschar *base62_chars=
US"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int bmi_run = 0;
uschar *bmi_verdicts = NULL;
#endif
+int bsmtp_transaction_linecount = 0;
int body_8bitmime = 0;
int body_linecount = 0;
int body_zerocount = 0;
uschar *bounce_message_file = NULL;
uschar *bounce_message_text = NULL;
uschar *bounce_recipient = NULL;
-BOOL bounce_return_body = TRUE;
int bounce_return_linesize_limit = 998;
-BOOL bounce_return_message = TRUE;
int bounce_return_size_limit = 100*1024;
uschar *bounce_sender_authentication = NULL;
-int bsmtp_transaction_linecount = 0;
uschar *callout_address = NULL;
int callout_cache_domain_positive_expire = 7*24*60*60;
uschar *callout_random_local_part = US"$primary_hostname-$tod_epoch-testing";
uschar *check_dns_names_pattern= US"(?i)^(?>(?(1)\\.|())[^\\W](?>[a-z0-9/_-]*[^\\W])?)+(\\.?)$";
int check_log_inodes = 100;
-int check_log_space = 10*1024; /* 10K Kbyte == 10MB */
-BOOL check_rfc2047_length = TRUE;
+int_eximarith_t check_log_space = 10*1024; /* 10K Kbyte == 10MB */
int check_spool_inodes = 100;
-int check_spool_space = 10*1024; /* 10K Kbyte == 10MB */
+int_eximarith_t check_spool_space = 10*1024; /* 10K Kbyte == 10MB */
uschar *chunking_advertise_hosts = US"*";
unsigned chunking_datasize = 0;
unsigned chunking_data_left = 0;
-BOOL chunking_offered = FALSE;
chunking_state_t chunking_state= CHUNKING_NOT_OFFERED;
const pcre *regex_CHUNKING = NULL;
uschar *client_authenticated_sender = NULL;
int clmacro_count = 0;
uschar *clmacros[MAX_CLMACROS];
-BOOL config_changed = FALSE;
FILE *config_file = NULL;
const uschar *config_filename = NULL;
int config_lineno = 0;
#endif
int connection_max_messages= -1;
+uschar *continue_proxy_cipher = NULL;
uschar *continue_hostname = NULL;
uschar *continue_host_address = NULL;
-BOOL continue_more = FALSE;
int continue_sequence = 1;
uschar *continue_transport = NULL;
uschar *csa_status = NULL;
cut_t cutthrough = {
- FALSE, /* delivery: when to attempt */
- FALSE, /* on defer: spool locally */
- -1, /* fd: open connection */
- 0, /* nrcpt: number of addresses */
+ .callout_hold_only = FALSE, /* verify-only: normal delivery */
+ .delivery = FALSE, /* when to attempt */
+ .defer_pass = FALSE, /* on defer: spool locally */
+ .is_tls = FALSE, /* not a TLS conn yet */
+ .cctx = {.sock = -1}, /* open connection */
+ .nrcpt = 0, /* number of addresses */
};
-BOOL daemon_listen = FALSE;
uschar *daemon_smtp_port = US"smtp";
int daemon_startup_retries = 9;
int daemon_startup_sleep = 30;
#ifdef EXPERIMENTAL_DCC
-BOOL dcc_direct_add_header = FALSE;
uschar *dcc_header = NULL;
uschar *dcc_result = NULL;
uschar *dccifd_address = US"/usr/local/dcc/var/dccifd";
uschar *dccifd_options = US"header";
#endif
-BOOL debug_daemon = FALSE;
int debug_fd = -1;
FILE *debug_file = NULL;
int debug_notall[] = {
Di_memory,
+ Di_noutf8,
-1
};
-bit_table debug_options[] = { /* must be in alphabetical order */
+bit_table debug_options[] = { /* must be in alphabetical order and use
+ only the enum values from macro.h */
BIT_TABLE(D, acl),
BIT_TABLE(D, all),
BIT_TABLE(D, auth),
BIT_TABLE(D, local_scan),
BIT_TABLE(D, lookup),
BIT_TABLE(D, memory),
+ BIT_TABLE(D, noutf8),
BIT_TABLE(D, pid),
BIT_TABLE(D, process_info),
BIT_TABLE(D, queue_run),
int debug_options_count = nelem(debug_options);
unsigned int debug_selector = 0;
-BOOL debug_store = FALSE;
int delay_warning[DELAY_WARNING_SIZE] = { DELAY_WARNING_SIZE, 1, 24*60*60 };
uschar *delay_warning_condition=
US"${if or {"
"{ match{$h_precedence:}{(?i)bulk|list|junk} }"
"{ match{$h_auto-submitted:}{(?i)auto-generated|auto-replied} }"
"} {no}{yes}}";
-BOOL delivery_date_remove = TRUE;
uschar *deliver_address_data = NULL;
int deliver_datafile = -1;
const uschar *deliver_domain = NULL;
uschar *deliver_domain_data = NULL;
const uschar *deliver_domain_orig = NULL;
const uschar *deliver_domain_parent = NULL;
-BOOL deliver_drop_privilege = FALSE;
-BOOL deliver_firsttime = FALSE;
-BOOL deliver_force = FALSE;
-BOOL deliver_freeze = FALSE;
time_t deliver_frozen_at = 0;
uschar *deliver_home = NULL;
const uschar *deliver_host = NULL;
uschar *deliver_localpart_parent = NULL;
uschar *deliver_localpart_prefix = NULL;
uschar *deliver_localpart_suffix = NULL;
-BOOL deliver_force_thaw = FALSE;
-BOOL deliver_manual_thaw = FALSE;
uschar *deliver_out_buffer = NULL;
int deliver_queue_load_max = -1;
address_item *deliver_recipients = NULL;
uschar *deliver_selectstring = NULL;
-BOOL deliver_selectstring_regex = FALSE;
uschar *deliver_selectstring_sender = NULL;
-BOOL deliver_selectstring_sender_regex = FALSE;
-BOOL disable_callout_flush = FALSE;
-BOOL disable_delay_flush = FALSE;
-#ifdef ENABLE_DISABLE_FSYNC
-BOOL disable_fsync = FALSE;
-#endif
-BOOL disable_ipv6 = FALSE;
-BOOL disable_logging = FALSE;
#ifndef DISABLE_DKIM
-BOOL dkim_collect_input = FALSE;
+unsigned dkim_collect_input = 0;
uschar *dkim_cur_signer = NULL;
-BOOL dkim_disable_verify = FALSE;
int dkim_key_length = 0;
+void *dkim_signatures = NULL;
uschar *dkim_signers = NULL;
uschar *dkim_signing_domain = NULL;
uschar *dkim_signing_selector = NULL;
+uschar *dkim_verify_overall = NULL;
uschar *dkim_verify_signers = US"$dkim_signers";
+uschar *dkim_verify_status = NULL;
+uschar *dkim_verify_reason = NULL;
#endif
#ifdef EXPERIMENTAL_DMARC
-BOOL dmarc_has_been_checked = FALSE;
-uschar *dmarc_ar_header = NULL;
uschar *dmarc_domain_policy = NULL;
uschar *dmarc_forensic_sender = NULL;
uschar *dmarc_history_file = NULL;
uschar *dmarc_status_text = NULL;
uschar *dmarc_tld_file = NULL;
uschar *dmarc_used_domain = NULL;
-BOOL dmarc_disable_verify = FALSE;
-BOOL dmarc_enable_forensic = FALSE;
#endif
uschar *dns_again_means_nonexist = NULL;
int dns_csa_search_limit = 5;
-BOOL dns_csa_use_reverse = TRUE;
-#ifdef EXPERIMENTAL_DANE
+int dns_cname_loops = 1;
+#ifdef SUPPORT_DANE
int dns_dane_ok = -1;
#endif
uschar *dns_ipv4_lookup = NULL;
uschar *dnslist_value = NULL;
tree_node *domainlist_anchor = NULL;
int domainlist_count = 0;
-BOOL dont_deliver = FALSE;
-BOOL dot_ends = TRUE;
-BOOL drop_cr = FALSE; /* No longer used */
uschar *dsn_from = US DEFAULT_DSN_FROM;
-BOOL enable_dollar_recipients = FALSE;
-BOOL envelope_to_remove = TRUE;
int errno_quota = ERRNO_QUOTA;
uschar *errors_copy = NULL;
int error_handling = ERRORS_SENDER;
gid_t exim_gid = EXIM_GID;
-BOOL exim_gid_set = TRUE; /* This gid is always set */
uschar *exim_path = US BIN_DIRECTORY "/exim"
"\0<---------------Space to patch exim_path->";
uid_t exim_uid = EXIM_UID;
-BOOL exim_uid_set = TRUE; /* This uid is always set */
int expand_level = 0; /* Nesting depth, indent for debug */
int expand_forbid = 0;
int expand_nlength[EXPAND_MAXN+1];
int expand_nmax = -1;
uschar *expand_nstring[EXPAND_MAXN+1];
-BOOL expand_string_forcedfail = FALSE;
uschar *expand_string_message;
-BOOL extract_addresses_remove_arguments = TRUE;
uschar *extra_local_interfaces = NULL;
int fake_response = OK;
"legitimate message, it may still be "
"delivered to the target recipient(s).";
int filter_n[FILTER_VARIABLE_COUNT];
-BOOL filter_running = FALSE;
int filter_sn[FILTER_VARIABLE_COUNT];
int filter_test = FTEST_NONE;
uschar *filter_test_sfile = NULL;
uschar *gecos_pattern = NULL;
rewrite_rule *global_rewrite_rules = NULL;
+volatile sig_atomic_t had_command_timeout = 0;
+volatile sig_atomic_t had_command_sigterm = 0;
+volatile sig_atomic_t had_data_timeout = 0;
+volatile sig_atomic_t had_data_sigint = 0;
uschar *headers_charset = US HEADERS_CHARSET;
int header_insert_maxlen = 64 * 1024;
header_line *header_last = NULL;
int header_line_maxsize = 0;
header_name header_names[] = {
- { US"bcc", 3, TRUE, htype_bcc },
- { US"cc", 2, TRUE, htype_cc },
- { US"date", 4, TRUE, htype_date },
- { US"delivery-date", 13, FALSE, htype_delivery_date },
- { US"envelope-to", 11, FALSE, htype_envelope_to },
- { US"from", 4, TRUE, htype_from },
- { US"message-id", 10, TRUE, htype_id },
- { US"received", 8, FALSE, htype_received },
- { US"reply-to", 8, FALSE, htype_reply_to },
- { US"return-path", 11, FALSE, htype_return_path },
- { US"sender", 6, TRUE, htype_sender },
- { US"subject", 7, FALSE, htype_subject },
- { US"to", 2, TRUE, htype_to }
+ /* name len allow_resent htype */
+ { US"bcc", 3, TRUE, htype_bcc },
+ { US"cc", 2, TRUE, htype_cc },
+ { US"date", 4, TRUE, htype_date },
+ { US"delivery-date", 13, FALSE, htype_delivery_date },
+ { US"envelope-to", 11, FALSE, htype_envelope_to },
+ { US"from", 4, TRUE, htype_from },
+ { US"message-id", 10, TRUE, htype_id },
+ { US"received", 8, FALSE, htype_received },
+ { US"reply-to", 8, FALSE, htype_reply_to },
+ { US"return-path", 11, FALSE, htype_return_path },
+ { US"sender", 6, TRUE, htype_sender },
+ { US"subject", 7, FALSE, htype_subject },
+ { US"to", 2, TRUE, htype_to }
};
-int header_names_size = sizeof(header_names)/sizeof(header_name);
+int header_names_size = nelem(header_names);
-BOOL header_rewritten = FALSE;
uschar *helo_accept_junk_hosts = NULL;
uschar *helo_allow_chars = US"";
uschar *helo_lookup_domains = US"@ : @[]";
uschar *helo_try_verify_hosts = NULL;
-BOOL helo_verified = FALSE;
-BOOL helo_verify_failed = FALSE;
uschar *helo_verify_hosts = NULL;
const uschar *hex_digits = CUS"0123456789abcdef";
uschar *hold_domains = NULL;
-BOOL host_checking = FALSE;
-BOOL host_checking_callout = FALSE;
uschar *host_data = NULL;
-BOOL host_find_failed_syntax= FALSE;
uschar *host_lookup = NULL;
-BOOL host_lookup_deferred = FALSE;
-BOOL host_lookup_failed = FALSE;
uschar *host_lookup_order = US"bydns:byaddr";
uschar *host_lookup_msg = US"";
int host_number = 0;
uschar *hosts_connection_nolog = NULL;
int ignore_bounce_errors_after = 10*7*24*60*60; /* 10 weeks */
-BOOL ignore_fromline_local = FALSE;
uschar *ignore_fromline_hosts = NULL;
-BOOL inetd_wait_mode = FALSE;
int inetd_wait_timeout = -1;
uschar *initial_cwd = NULL;
uschar *interface_address = NULL;
int interface_port = -1;
-BOOL is_inetd = FALSE;
uschar *iterate_item = NULL;
int journal_fd = -1;
uschar *eldap_dn = NULL;
int load_average = -2;
-BOOL local_error_message = FALSE;
-BOOL local_from_check = TRUE;
uschar *local_from_prefix = NULL;
uschar *local_from_suffix = NULL;
uschar *local_interfaces = US"0.0.0.0";
#endif
+#ifdef HAVE_LOCAL_SCAN
uschar *local_scan_data = NULL;
int local_scan_timeout = 5*60;
-BOOL local_sender_retain = FALSE;
+#endif
gid_t local_user_gid = (gid_t)(-1);
uid_t local_user_uid = (uid_t)(-1);
Li_acl_warn_skipped,
Li_connection_reject,
Li_delay_delivery,
+ Li_dkim,
Li_dnslist_defer,
Li_etrn,
Li_host_lookup_failed,
BIT_TABLE(L, delay_delivery),
BIT_TABLE(L, deliver_time),
BIT_TABLE(L, delivery_size),
+#ifndef DISABLE_DKIM
+ BIT_TABLE(L, dkim),
+ BIT_TABLE(L, dkim_verbose),
+#endif
BIT_TABLE(L, dnslist_defer),
BIT_TABLE(L, dnssec),
BIT_TABLE(L, etrn),
BIT_TABLE(L, incoming_interface),
BIT_TABLE(L, incoming_port),
BIT_TABLE(L, lost_incoming_connection),
+ BIT_TABLE(L, millisec),
BIT_TABLE(L, outgoing_interface),
BIT_TABLE(L, outgoing_port),
BIT_TABLE(L, pid),
-#if defined(SUPPORT_PROXY) || defined (SUPPORT_SOCKS)
+ BIT_TABLE(L, pipelining),
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
BIT_TABLE(L, proxy),
#endif
BIT_TABLE(L, queue_run),
BIT_TABLE(L, queue_time),
BIT_TABLE(L, queue_time_overall),
+ BIT_TABLE(L, receive_time),
BIT_TABLE(L, received_recipients),
BIT_TABLE(L, received_sender),
BIT_TABLE(L, rejected_header),
unsigned int log_selector[log_selector_size]; /* initialized in main() */
uschar *log_selector_string = NULL;
FILE *log_stderr = NULL;
-BOOL log_testing_mode = FALSE;
-BOOL log_timezone = FALSE;
uschar *login_sender_address = NULL;
uschar *lookup_dnssec_authenticated = NULL;
int lookup_open_max = 25;
uschar *lookup_value = NULL;
-macro_item *macros = NULL;
-macro_item *mlast = NULL;
-BOOL macros_builtin_created = FALSE;
+macro_item *macros_user = NULL;
uschar *mailstore_basename = NULL;
#ifdef WITH_CONTENT_SCAN
uschar *malware_name = NULL; /* Virus Name */
int message_age = 0;
uschar *message_body = NULL;
uschar *message_body_end = NULL;
-BOOL message_body_newlines = FALSE;
int message_body_size = 0;
int message_body_visible = 500;
int message_ended = END_NOTSTARTED;
uschar message_id_option[MESSAGE_ID_LENGTH + 3];
uschar *message_id_external;
int message_linecount = 0;
-BOOL message_logs = TRUE;
int message_size = 0;
uschar *message_size_limit = US"50M";
#ifdef SUPPORT_I18N
-BOOL message_smtputf8 = FALSE;
int message_utf8_downconvert = 0; /* -1 ifneeded; 0 never; 1 always */
#endif
uschar message_subdir[2] = { 0, 0 };
int mime_part_count = -1;
#endif
-BOOL mua_wrapper = FALSE;
-
uid_t *never_users = NULL;
-#ifdef WITH_CONTENT_SCAN
-BOOL no_mbox_unspool = FALSE;
-#endif
-BOOL no_multiline_responses = FALSE;
const int on = 1; /* for setsockopt */
const int off = 0;
uschar *override_local_interfaces = NULL;
uschar *override_pid_file_path = NULL;
-BOOL parse_allow_group = FALSE;
-BOOL parse_found_group = FALSE;
uschar *percent_hack_domains = NULL;
uschar *pid_file_path = US PID_FILE_PATH
"\0<--------------Space to patch pid_file_path->";
-BOOL pipelining_enable = TRUE;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+uschar *pipe_connect_advertise_hosts = US"*";
+#endif
uschar *pipelining_advertise_hosts = US"*";
-BOOL preserve_message_logs = FALSE;
uschar *primary_hostname = NULL;
-BOOL print_topbitchars = FALSE;
uschar process_info[PROCESS_INFO_SIZE];
int process_info_len = 0;
uschar *process_log_path = NULL;
-BOOL prod_requires_admin = TRUE;
#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
-uschar *hosts_proxy = US"";
-uschar *proxy_external_address = US"";
+uschar *hosts_proxy = NULL;
+uschar *proxy_external_address = NULL;
int proxy_external_port = 0;
-uschar *proxy_local_address = US"";
+uschar *proxy_local_address = NULL;
int proxy_local_port = 0;
-BOOL proxy_session = FALSE;
-BOOL proxy_session_failed = FALSE;
#endif
uschar *prvscheck_address = NULL;
const uschar *qualify_domain_recipient = NULL;
uschar *qualify_domain_sender = NULL;
-BOOL queue_2stage = FALSE;
uschar *queue_domains = NULL;
int queue_interval = -1;
-BOOL queue_list_requires_admin = TRUE;
uschar *queue_name = US"";
-BOOL queue_only = FALSE;
uschar *queue_only_file = NULL;
int queue_only_load = -1;
-BOOL queue_only_load_latch = TRUE;
-BOOL queue_only_override = TRUE;
-BOOL queue_only_policy = FALSE;
-BOOL queue_run_first_delivery = FALSE;
-BOOL queue_run_force = FALSE;
-BOOL queue_run_in_order = FALSE;
-BOOL queue_run_local = FALSE;
uschar *queue_run_max = US"5";
pid_t queue_run_pid = (pid_t)0;
int queue_run_pipe = -1;
-BOOL queue_running = FALSE;
-BOOL queue_smtp = FALSE;
uschar *queue_smtp_domains = NULL;
-unsigned int random_seed = 0;
+uint32_t random_seed = 0;
tree_node *ratelimiters_cmd = NULL;
tree_node *ratelimiters_conn = NULL;
tree_node *ratelimiters_mail = NULL;
int rcpt_defer_count = 0;
gid_t real_gid;
uid_t real_uid;
-BOOL really_exim = TRUE;
-BOOL receive_call_bombout = FALSE;
int receive_linecount = 0;
int receive_messagecount = 0;
int receive_timeout = 0;
int received_headers_max = 30;
uschar *received_protocol = NULL;
-int received_time = 0;
+struct timeval received_time = { 0, 0 };
+struct timeval received_time_taken = { 0, 0 };
uschar *recipient_data = NULL;
uschar *recipient_unqualified_hosts = NULL;
uschar *recipient_verify_failure = NULL;
int recipients_count = 0;
-BOOL recipients_discarded = FALSE;
recipient_item *recipients_list = NULL;
int recipients_list_max = 0;
int recipients_max = 0;
-BOOL recipients_max_reject = FALSE;
const pcre *regex_AUTH = NULL;
const pcre *regex_check_dns_names = NULL;
const pcre *regex_From = NULL;
const pcre *regex_IGNOREQUOTA = NULL;
const pcre *regex_PIPELINING = NULL;
const pcre *regex_SIZE = NULL;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+const pcre *regex_EARLY_PIPE = NULL;
+#endif
const pcre *regex_ismsgid = NULL;
const pcre *regex_smtp_code = NULL;
uschar *regex_vars[REGEX_VARS];
int retry_maximum_timeout = 0; /* set from retry config */
retry_config *retries = NULL;
uschar *return_path = NULL;
-BOOL return_path_remove = TRUE;
int rewrite_existflags = 0;
uschar *rfc1413_hosts = US"@[]";
int rfc1413_query_timeout = 0;
-/* BOOL rfc821_domains = FALSE; <<< on the way out */
uid_t root_gid = ROOT_GID;
uid_t root_uid = ROOT_UID;
router_instance *routers = NULL;
router_instance router_defaults = {
- NULL, /* chain pointer */
- NULL, /* name */
- NULL, /* info */
- NULL, /* private options block pointer */
- NULL, /* driver name */
+ .next = NULL,
+ .name = NULL,
+ .info = NULL,
+ .options_block = NULL,
+ .driver_name = NULL,
- NULL, /* address_data */
+ .address_data = NULL,
#ifdef EXPERIMENTAL_BRIGHTMAIL
- NULL, /* bmi_rule */
-#endif
- NULL, /* cannot_route_message */
- NULL, /* condition */
- NULL, /* current_directory */
- NULL, /* debug_string */
- NULL, /* domains */
- NULL, /* errors_to */
- NULL, /* expand_gid */
- NULL, /* expand_uid */
- NULL, /* expand_more */
- NULL, /* expand_unseen */
- NULL, /* extra_headers */
- NULL, /* fallback_hosts */
- NULL, /* home_directory */
- NULL, /* ignore_target_hosts */
- NULL, /* local_parts */
- NULL, /* pass_router_name */
- NULL, /* prefix */
- NULL, /* redirect_router_name */
- NULL, /* remove_headers */
- NULL, /* require_files */
- NULL, /* router_home_directory */
- US"freeze", /* self */
- NULL, /* senders */
- NULL, /* suffix */
- NULL, /* translate_ip_address */
- NULL, /* transport_name */
-
- TRUE, /* address_test */
+ .bmi_rule = NULL,
+#endif
+ .cannot_route_message = NULL,
+ .condition = NULL,
+ .current_directory = NULL,
+ .debug_string = NULL,
+ .domains = NULL,
+ .errors_to = NULL,
+ .expand_gid = NULL,
+ .expand_uid = NULL,
+ .expand_more = NULL,
+ .expand_unseen = NULL,
+ .extra_headers = NULL,
+ .fallback_hosts = NULL,
+ .home_directory = NULL,
+ .ignore_target_hosts = NULL,
+ .local_parts = NULL,
+ .pass_router_name = NULL,
+ .prefix = NULL,
+ .redirect_router_name = NULL,
+ .remove_headers = NULL,
+ .require_files = NULL,
+ .router_home_directory = NULL,
+ .self = US"freeze",
+ .senders = NULL,
+ .suffix = NULL,
+ .translate_ip_address = NULL,
+ .transport_name = NULL,
+
+ .address_test = TRUE,
#ifdef EXPERIMENTAL_BRIGHTMAIL
- FALSE, /* bmi_deliver_alternate */
- FALSE, /* bmi_deliver_default */
- FALSE, /* bmi_dont_deliver */
-#endif
- TRUE, /* expn */
- FALSE, /* caseful_local_part */
- FALSE, /* check_local_user */
- FALSE, /* disable_logging */
- FALSE, /* fail_verify_recipient */
- FALSE, /* fail_verify_sender */
- FALSE, /* gid_set */
- FALSE, /* initgroups */
- TRUE_UNSET, /* log_as_local */
- TRUE, /* more */
- FALSE, /* pass_on_timeout */
- FALSE, /* prefix_optional */
- TRUE, /* repeat_use */
- TRUE_UNSET, /* retry_use_local_part - fudge "unset" */
- FALSE, /* same_domain_copy_routing */
- FALSE, /* self_rewrite */
- FALSE, /* suffix_optional */
- FALSE, /* verify_only */
- TRUE, /* verify_recipient */
- TRUE, /* verify_sender */
- FALSE, /* uid_set */
- FALSE, /* unseen */
- FALSE, /* dsn_lasthop */
-
- self_freeze, /* self_code */
- (uid_t)(-1), /* uid */
- (gid_t)(-1), /* gid */
-
- NULL, /* fallback_hostlist */
- NULL, /* transport instance */
- NULL, /* pass_router */
- NULL, /* redirect_router */
-
- { NULL, NULL }, /* dnssec_domains {require,request} */
+ .bmi_deliver_alternate = FALSE,
+ .bmi_deliver_default = FALSE,
+ .bmi_dont_deliver = FALSE,
+#endif
+ .expn = TRUE,
+ .caseful_local_part = FALSE,
+ .check_local_user = FALSE,
+ .disable_logging = FALSE,
+ .fail_verify_recipient = FALSE,
+ .fail_verify_sender = FALSE,
+ .gid_set = FALSE,
+ .initgroups = FALSE,
+ .log_as_local = TRUE_UNSET,
+ .more = TRUE,
+ .pass_on_timeout = FALSE,
+ .prefix_optional = FALSE,
+ .repeat_use = TRUE,
+ .retry_use_local_part = TRUE_UNSET,
+ .same_domain_copy_routing = FALSE,
+ .self_rewrite = FALSE,
+ .suffix_optional = FALSE,
+ .verify_only = FALSE,
+ .verify_recipient = TRUE,
+ .verify_sender = TRUE,
+ .uid_set = FALSE,
+ .unseen = FALSE,
+ .dsn_lasthop = FALSE,
+
+ .self_code = self_freeze,
+ .uid = (uid_t)(-1),
+ .gid = (gid_t)(-1),
+
+ .fallback_hostlist = NULL,
+ .transport = NULL,
+ .pass_router = NULL,
+ .redirect_router = NULL,
+
+ .dnssec = { NULL, NULL }, /* dnssec_domains {require,request} */
};
uschar *router_name = NULL;
ip_address_item *running_interfaces = NULL;
-BOOL running_in_test_harness = FALSE;
/* This is a weird one. The following string gets patched in the binary by the
script that sets up a copy of Exim for running in the test harness. It seems
int runrc = 0;
uschar *search_error_message = NULL;
-BOOL search_find_defer = FALSE;
uschar *self_hostname = NULL;
uschar *sender_address = NULL;
unsigned int sender_address_cache[(MAX_NAMED_LIST * 2)/32];
uschar *sender_address_data = NULL;
-BOOL sender_address_forced = FALSE;
uschar *sender_address_unrewritten = NULL;
uschar *sender_data = NULL;
unsigned int sender_domain_cache[(MAX_NAMED_LIST * 2)/32];
uschar *sender_fullhost = NULL;
-BOOL sender_helo_dnssec = FALSE;
uschar *sender_helo_name = NULL;
uschar **sender_host_aliases = &no_aliases;
uschar *sender_host_address = NULL;
uschar *sender_host_authenticated = NULL;
+uschar *sender_host_auth_pubname = NULL;
unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32];
-BOOL sender_host_dnssec = FALSE;
uschar *sender_host_name = NULL;
int sender_host_port = 0;
-BOOL sender_host_notsocket = FALSE;
-BOOL sender_host_unknown = FALSE;
uschar *sender_ident = NULL;
-BOOL sender_local = FALSE;
-BOOL sender_name_forced = FALSE;
uschar *sender_rate = NULL;
uschar *sender_rate_limit = NULL;
uschar *sender_rate_period = NULL;
uschar *sender_rcvhost = NULL;
-BOOL sender_set_untrusted = FALSE;
uschar *sender_unqualified_hosts = NULL;
uschar *sender_verify_failure = NULL;
address_item *sender_verified_list = NULL;
address_item *sender_verified_failed = NULL;
int sender_verified_rc = -1;
-BOOL sender_verified_responded = FALSE;
uschar *sending_ip_address = NULL;
int sending_port = -1;
SIGNAL_BOOL sigalrm_seen = FALSE;
+const uschar *sigalarm_setter = NULL;
uschar **sighup_argv = NULL;
int slow_lookup_log = 0; /* millisecs, zero disables */
int smtp_accept_count = 0;
-BOOL smtp_accept_keepalive = TRUE;
int smtp_accept_max = 20;
int smtp_accept_max_nonmail= 10;
uschar *smtp_accept_max_nonmail_hosts = US"*";
int smtp_accept_queue_per_connection = 10;
int smtp_accept_reserve = 0;
uschar *smtp_active_hostname = NULL;
-BOOL smtp_authenticated = FALSE;
uschar *smtp_banner = US"$smtp_active_hostname ESMTP "
"Exim $version_number $tod_full"
"\0<---------------Space to patch smtp_banner->";
-BOOL smtp_batched_input = FALSE;
-BOOL smtp_check_spool_space = TRUE;
int smtp_ch_index = 0;
uschar *smtp_cmd_argument = NULL;
uschar *smtp_cmd_buffer = NULL;
-time_t smtp_connection_start = 0;
+struct timeval smtp_connection_start = {0,0};
uschar smtp_connection_had[SMTP_HBUFF_SIZE];
int smtp_connect_backlog = 20;
double smtp_delay_mail = 0.0;
double smtp_delay_rcpt = 0.0;
-BOOL smtp_enforce_sync = TRUE;
FILE *smtp_in = NULL;
-BOOL smtp_input = FALSE;
int smtp_load_reserve = -1;
int smtp_mailcmd_count = 0;
FILE *smtp_out = NULL;
uschar *smtp_etrn_command = NULL;
-BOOL smtp_etrn_serialize = TRUE;
int smtp_max_synprot_errors= 3;
int smtp_max_unknown_commands = 3;
uschar *smtp_notquit_reason = NULL;
int smtp_receive_timeout = 5*60;
uschar *smtp_receive_timeout_s = NULL;
uschar *smtp_reserve_hosts = NULL;
-BOOL smtp_return_error_details = FALSE;
int smtp_rlm_base = 0;
double smtp_rlm_factor = 0.0;
int smtp_rlm_limit = 0;
uschar *spam_score = NULL;
uschar *spam_score_int = NULL;
#endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
uschar *spf_guess = US"v=spf1 a/24 mx/24 ptr ?all";
uschar *spf_header_comment = NULL;
uschar *spf_received = NULL;
uschar *spf_smtp_comment = NULL;
#endif
-BOOL split_spool_directory = FALSE;
+FILE *spool_data_file = NULL;
uschar *spool_directory = US SPOOL_DIRECTORY
"\0<--------------Space to patch spool_directory->";
#ifdef EXPERIMENTAL_SRS
uschar *srs_recipient = NULL;
uschar *srs_secrets = NULL;
uschar *srs_status = NULL;
-BOOL srs_usehash = TRUE;
-BOOL srs_usetimestamp = TRUE;
#endif
-BOOL strict_acl_vars = FALSE;
int string_datestamp_offset= -1;
int string_datestamp_length= 0;
int string_datestamp_type = -1;
-BOOL strip_excess_angle_brackets = FALSE;
-BOOL strip_trailing_dot = FALSE;
uschar *submission_domain = NULL;
-BOOL submission_mode = FALSE;
uschar *submission_name = NULL;
-BOOL suppress_local_fixups = FALSE;
-BOOL suppress_local_fixups_default = FALSE;
-BOOL synchronous_delivery = FALSE;
-BOOL syslog_duplication = TRUE;
int syslog_facility = LOG_MAIL;
-BOOL syslog_pid = TRUE;
uschar *syslog_processname = US"exim";
-BOOL syslog_timestamp = TRUE;
uschar *system_filter = NULL;
uschar *system_filter_directory_transport = NULL;
uschar *system_filter_reply_transport = NULL;
gid_t system_filter_gid = 0;
-BOOL system_filter_gid_set = FALSE;
uid_t system_filter_uid = (uid_t)-1;
-BOOL system_filter_uid_set = FALSE;
-BOOL system_filtering = FALSE;
-BOOL tcp_fastopen_ok = FALSE;
-BOOL tcp_nodelay = TRUE;
+blob tcp_fastopen_nodata = { .data = NULL, .len = 0 };
+tfo_state_t tcp_out_fastopen = TFO_NOT_USED;
#ifdef USE_TCP_WRAPPERS
uschar *tcp_wrappers_daemon_name = US TCP_WRAPPERS_DAEMON_NAME;
#endif
int test_harness_load_avg = 0;
int thismessage_size_limit = 0;
int timeout_frozen_after = 0;
-BOOL timestamps_utc = FALSE;
transport_instance *transports = NULL;
transport_instance transport_defaults = {
- NULL, /* chain pointer */
- NULL, /* name */
- NULL, /* info */
- NULL, /* private options block pointer */
- NULL, /* driver name */
- NULL, /* setup entry point */
- 1, /* batch_max */
- NULL, /* batch_id */
- NULL, /* home_dir */
- NULL, /* current_dir */
- NULL, /* expand-multi-domain */
- TRUE, /* multi-domain */
- FALSE, /* overrides_hosts */
- 100, /* max_addresses */
- 500, /* connection_max_messages */
- FALSE, /* deliver_as_creator */
- FALSE, /* disable_logging */
- FALSE, /* initgroups */
- FALSE, /* uid_set */
- FALSE, /* gid_set */
- (uid_t)(-1), /* uid */
- (gid_t)(-1), /* gid */
- NULL, /* expand_uid */
- NULL, /* expand_gid */
- NULL, /* warn_message */
- NULL, /* shadow */
- NULL, /* shadow_condition */
- NULL, /* filter_command */
- NULL, /* add_headers */
- NULL, /* remove_headers */
- NULL, /* return_path */
- NULL, /* debug_string */
- NULL, /* max_parallel */
- NULL, /* message_size_limit */
- NULL, /* headers_rewrite */
- NULL, /* rewrite_rules */
- 0, /* rewrite_existflags */
- 300, /* filter_timeout */
- FALSE, /* body_only */
- FALSE, /* delivery_date_add */
- FALSE, /* envelope_to_add */
- FALSE, /* headers_only */
- FALSE, /* rcpt_include_affixes */
- FALSE, /* return_path_add */
- FALSE, /* return_output */
- FALSE, /* return_fail_output */
- FALSE, /* log_output */
- FALSE, /* log_fail_output */
- FALSE, /* log_defer_output */
- TRUE_UNSET /* retry_use_local_part: BOOL, but set neither
- 1 nor 0 so can detect unset */
+ .next = NULL,
+ .name = NULL,
+ .info = NULL,
+ .options_block = NULL,
+ .driver_name = NULL,
+ .setup = NULL,
+ .batch_max = 1,
+ .batch_id = NULL,
+ .home_dir = NULL,
+ .current_dir = NULL,
+ .expand_multi_domain = NULL,
+ .multi_domain = TRUE,
+ .overrides_hosts = FALSE,
+ .max_addresses = 100,
+ .connection_max_messages = 500,
+ .deliver_as_creator = FALSE,
+ .disable_logging = FALSE,
+ .initgroups = FALSE,
+ .uid_set = FALSE,
+ .gid_set = FALSE,
+ .uid = (uid_t)(-1),
+ .gid = (gid_t)(-1),
+ .expand_uid = NULL,
+ .expand_gid = NULL,
+ .warn_message = NULL,
+ .shadow = NULL,
+ .shadow_condition = NULL,
+ .filter_command = NULL,
+ .add_headers = NULL,
+ .remove_headers = NULL,
+ .return_path = NULL,
+ .debug_string = NULL,
+ .max_parallel = NULL,
+ .message_size_limit = NULL,
+ .headers_rewrite = NULL,
+ .rewrite_rules = NULL,
+ .rewrite_existflags = 0,
+ .filter_timeout = 300,
+ .body_only = FALSE,
+ .delivery_date_add = FALSE,
+ .envelope_to_add = FALSE,
+ .headers_only = FALSE,
+ .rcpt_include_affixes = FALSE,
+ .return_path_add = FALSE,
+ .return_output = FALSE,
+ .return_fail_output = FALSE,
+ .log_output = FALSE,
+ .log_fail_output = FALSE,
+ .log_defer_output = FALSE,
+ .retry_use_local_part = TRUE_UNSET, /* retry_use_local_part: BOOL, but set neither
+ 1 nor 0 so can detect unset */
#ifndef DISABLE_EVENT
- ,NULL /* event_action */
+ .event_action = NULL
#endif
};
int transport_newlines;
const uschar **transport_filter_argv = NULL;
int transport_filter_timeout;
-BOOL transport_filter_timed_out = FALSE;
int transport_write_timeout= 0;
tree_node *tree_dns_fails = NULL;
tree_node *tree_nonrecipients = NULL;
tree_node *tree_unusable = NULL;
-BOOL trusted_caller = FALSE;
-BOOL trusted_config = TRUE;
gid_t *trusted_groups = NULL;
uid_t *trusted_users = NULL;
uschar *timezone_string = US TIMEZONE_DEFAULT;
uschar *verify_mode = NULL;
uschar *version_copyright =
- US"Copyright (c) University of Cambridge, 1995 - 2017\n"
- "(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2017";
+ US"Copyright (c) University of Cambridge, 1995 - 2018\n"
+ "(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2018";
uschar *version_date = US"?";
uschar *version_cnumber = US"????";
uschar *version_string = US"?";
int warning_count = 0;
uschar *warnmsg_delay = NULL;
uschar *warnmsg_recipients = NULL;
-BOOL write_rejectlog = TRUE;
/* End of globals.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Almost all the global variables are defined together in this one header, so
them. Also, the tls_ variables are now always visible. */
typedef struct {
- int active; /* fd/socket when in a TLS session */
+ client_conn_ctx active; /* fd/socket when in a TLS session, and ptr to TLS context */
int bits; /* bits used in TLS session */
BOOL certificate_verified; /* Client certificate verified */
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
BOOL dane_verified; /* ... via DANE */
int tlsa_usage; /* TLSA record(s) usage */
#endif
extern uschar *tls_ocsp_file; /* OCSP stapling proof file */
# endif
extern uschar *tls_privatekey; /* Private key file */
+# ifdef EXPERIMENTAL_REQUIRETLS
+extern uschar tls_requiretls; /* REQUIRETLS active for this message */
+extern uschar *tls_advertise_requiretls; /* hosts for which REQUIRETLS adv */
+extern const pcre *regex_REQUIRETLS; /* for recognising the command */
+# endif
extern BOOL tls_remember_esmtp; /* For YAEB */
extern uschar *tls_require_ciphers; /* So some can be avoided */
extern uschar *tls_try_verify_hosts; /* Optional client verification */
incoming TCP/IP. */
extern int (*lwr_receive_getc)(unsigned);
+extern uschar * (*lwr_receive_getbuf)(unsigned *);
extern int (*lwr_receive_ungetc)(int);
extern int (*receive_getc)(unsigned);
+extern uschar * (*receive_getbuf)(unsigned *);
extern void (*receive_get_cache)(void);
extern int (*receive_ungetc)(int);
extern int (*receive_feof)(void);
extern const uschar **address_expansions[ADDRESS_EXPANSIONS_COUNT];
+/* Flags for which we don't need an address ever so can use a bitfield */
+
+extern struct global_flags {
+ BOOL acl_temp_details :1; /* TRUE to give details for 4xx error */
+ BOOL active_local_from_check :1; /* For adding Sender: (switchable) */
+ BOOL active_local_sender_retain :1; /* For keeping Sender: (switchable) */
+ BOOL address_test_mode :1; /* True for -bt */
+ BOOL admin_user :1; /* True if caller can do admin */
+ BOOL allow_auth_unadvertised :1; /* As it says */
+ BOOL allow_unqualified_recipient :1; /* For local messages */ /* As it says */
+ BOOL allow_unqualified_sender :1; /* Reset for SMTP */ /* Ditto */
+ BOOL authentication_local :1; /* TRUE if non-smtp (implicit authentication) */
+
+ BOOL background_daemon :1; /* Set FALSE to keep in foreground */
+
+ BOOL chunking_offered :1;
+ BOOL config_changed :1; /* True if -C used */
+ BOOL continue_more :1; /* Flag more addresses waiting */
+
+ BOOL daemon_listen :1; /* True if listening required */
+ BOOL debug_daemon :1; /* Debug the daemon process only */
+ BOOL deliver_firsttime :1; /* True for first delivery attempt */
+ BOOL deliver_force :1; /* TRUE if delivery was forced */
+ BOOL deliver_freeze :1; /* TRUE if delivery is frozen */
+ BOOL deliver_force_thaw :1; /* TRUE to force thaw in queue run */
+ BOOL deliver_manual_thaw :1; /* TRUE if manually thawed */
+ BOOL deliver_selectstring_regex :1; /* String is regex */
+ BOOL deliver_selectstring_sender_regex :1; /* String is regex */
+ BOOL disable_callout_flush :1; /* Don't flush before callouts */
+ BOOL disable_delay_flush :1; /* Don't flush before "delay" in ACL */
+ BOOL disable_logging :1; /* Disables log writing when TRUE */
+#ifndef DISABLE_DKIM
+ BOOL dkim_disable_verify :1; /* Set via ACL control statement. When set, DKIM verification is disabled for the current message */
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ BOOL dmarc_has_been_checked :1; /* Global variable to check if test has been called yet */
+ BOOL dmarc_disable_verify :1; /* Set via ACL control statement. When set, DMARC verification is disabled for the current message */
+ BOOL dmarc_enable_forensic :1; /* Set via ACL control statement. When set, DMARC forensic reports are enabled for the current message */
+#endif
+ BOOL dont_deliver :1; /* TRUE for -N option */
+ BOOL dot_ends :1; /* TRUE if "." ends non-SMTP input */
+
+ BOOL enable_dollar_recipients :1; /* Make $recipients available */
+ BOOL expand_string_forcedfail :1; /* TRUE if failure was "expected" */
+
+ BOOL filter_running :1; /* TRUE while running a filter */
+
+ BOOL header_rewritten :1; /* TRUE if header changed by router */
+ BOOL helo_verified :1; /* True if HELO verified */
+ BOOL helo_verify_failed :1; /* True if attempt failed */
+ BOOL host_checking_callout :1; /* TRUE if real callout wanted */
+ BOOL host_find_failed_syntax :1; /* DNS syntax check failure */
+
+ BOOL inetd_wait_mode :1; /* Whether running in inetd wait mode */
+ BOOL is_inetd :1; /* True for inetd calls */
+
+ BOOL local_error_message :1; /* True if handling one of these */
+ BOOL log_testing_mode :1; /* TRUE in various testing modes */
+
+#ifdef WITH_CONTENT_SCAN
+ BOOL no_mbox_unspool :1; /* don't unlink files in /scan directory */
+#endif
+ BOOL no_multiline_responses :1; /* For broken clients */
+
+ BOOL parse_allow_group :1; /* Allow group syntax */
+ BOOL parse_found_group :1; /* In the middle of a group */
+ BOOL pipelining_enable :1; /* As it says */
+#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS)
+ BOOL proxy_session_failed :1; /* TRUE if required proxy negotiation failed */
+#endif
+
+ BOOL queue_2stage :1; /* Run queue in 2-stage manner */
+ BOOL queue_only_policy :1; /* ACL or local_scan wants queue_only */
+ BOOL queue_run_first_delivery :1; /* If TRUE, first deliveries only */
+ BOOL queue_run_force :1; /* TRUE to force during queue run */
+ BOOL queue_run_local :1; /* Local deliveries only in queue run */
+ BOOL queue_running :1; /* TRUE for queue running process and */
+ BOOL queue_smtp :1; /* Disable all immediate SMTP (-odqs)*/
+
+ BOOL really_exim :1; /* FALSE in utilities */
+ BOOL receive_call_bombout :1; /* Flag for crashing log */
+ BOOL recipients_discarded :1; /* By an ACL */
+ BOOL running_in_test_harness :1; /*TRUE when running_status is patched */
+
+ BOOL search_find_defer :1; /* Set TRUE if lookup deferred */
+ BOOL sender_address_forced :1; /* Set by -f */
+ BOOL sender_host_notsocket :1; /* Set for -bs and -bS */
+ BOOL sender_host_unknown :1; /* TRUE for -bs and -bS except inetd */
+ BOOL sender_local :1; /* TRUE for local senders */
+ BOOL sender_name_forced :1; /* Set by -F */
+ BOOL sender_set_untrusted :1; /* Sender set by untrusted caller */
+ BOOL smtp_authenticated :1; /* Sending client has authenticated */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL smtp_in_early_pipe_advertised :1; /* server advertised PIPE_CONNECT */
+ BOOL smtp_in_early_pipe_no_auth :1; /* too many authenticator names */
+ BOOL smtp_in_early_pipe_used :1; /* client did send early data */
+#endif
+ BOOL smtp_in_pipelining_advertised :1; /* server advertised PIPELINING */
+ BOOL smtp_in_pipelining_used :1; /* server noted client using PIPELINING */
+ BOOL spool_file_wireformat :1; /* current -D file has CRLF rather than NL */
+ BOOL submission_mode :1; /* Can be forced from ACL */
+ BOOL suppress_local_fixups :1; /* Can be forced from ACL */
+ BOOL suppress_local_fixups_default :1; /* former is reset to this; override with -G */
+ BOOL synchronous_delivery :1; /* TRUE if -odi is set */
+ BOOL system_filtering :1; /* TRUE when running system filter */
+
+ BOOL tcp_fastopen_ok :1; /* appears to be supported by kernel */
+ BOOL tcp_in_fastopen :1; /* conn usefully used fastopen */
+ BOOL tcp_in_fastopen_data :1; /* fastopen carried data */
+ BOOL tcp_in_fastopen_logged :1; /* one-time logging */
+ BOOL tcp_out_fastopen_logged :1; /* one-time logging */
+ BOOL timestamps_utc :1; /* Use UTC for all times */
+ BOOL transport_filter_timed_out :1; /* True if it did */
+ BOOL trusted_caller :1; /* Caller is trusted */
+ BOOL trusted_config :1; /* Configuration file is trusted */
+} f;
+
+
/* General global variables */
extern BOOL accept_8bitmime; /* Allow *BITMIME incoming */
extern uschar *acl_smtp_rcpt; /* ACL run for RCPT */
extern uschar *acl_smtp_starttls; /* ACL run for STARTTLS */
extern uschar *acl_smtp_vrfy; /* ACL run for VRFY */
-extern BOOL acl_temp_details; /* TRUE to give details for 4xx error */
extern tree_node *acl_var_c; /* ACL connection variables */
extern tree_node *acl_var_m; /* ACL message variables */
extern uschar *acl_verify_message; /* User message for verify failure */
extern string_item *acl_warn_logged; /* Logged lines */
extern uschar *acl_wherecodes[]; /* Response codes for ACL fails */
extern uschar *acl_wherenames[]; /* Names for messages */
-extern BOOL active_local_from_check;/* For adding Sender: (switchable) */
-extern BOOL active_local_sender_retain; /* For keeping Sender: (switchable) */
extern address_item *addr_duplicate; /* Duplicate address list */
extern address_item address_defaults; /* Default data for address item */
extern uschar *address_file; /* Name of file when delivering to one */
extern uschar *address_pipe; /* Pipe command when delivering to one */
-extern BOOL address_test_mode; /* True for -bt */
extern tree_node *addresslist_anchor; /* Tree of defined address lists */
extern int addresslist_count; /* Number defined */
extern gid_t *admin_groups; /* List of admin groups */
-extern BOOL admin_user; /* True if caller can do admin */
-extern BOOL allow_auth_unadvertised;/* As it says */
extern BOOL allow_domain_literals; /* As it says */
extern BOOL allow_mx_to_ip; /* Allow MX records to -> ip address */
-extern BOOL allow_unqualified_recipient; /* As it says */
-extern BOOL allow_unqualified_sender; /* Ditto */
+#ifdef EXPERIMENTAL_ARC
+struct arc_set *arc_received; /* highest ARC instance evaluation struct */
+extern int arc_received_instance; /* highest ARC instance number in headers */
+extern int arc_oldest_pass; /* lowest passing instance number in headers */
+extern const uschar *arc_state; /* verification state */
+extern const uschar *arc_state_reason;
+#endif
extern BOOL allow_utf8_domains; /* For experimenting */
extern uschar *authenticated_fail_id; /* ID that failed authentication */
extern uschar *authenticated_id; /* ID that was authenticated */
extern uschar *auth_vars[]; /* $authn variables */
extern int auto_thaw; /* Auto-thaw interval */
#ifdef WITH_CONTENT_SCAN
-extern BOOL av_failed; /* TRUE if the AV process failed */
+extern int av_failed; /* TRUE if the AV process failed */
extern uschar *av_scanner; /* AntiVirus scanner to use for the malware condition */
#endif
-extern BOOL background_daemon; /* Set FALSE to keep in foreground */
extern uschar *base62_chars; /* Table of base-62 characters */
extern uschar *bi_command; /* Command for -bi option */
extern uschar *big_buffer; /* Used for various temp things */
extern int bmi_run; /* Flag that determines if message should be run through Brightmail server */
extern uschar *bmi_verdicts; /* BASE64-encoded verdicts with recipient lists */
#endif
+extern int bsmtp_transaction_linecount; /* Start of last transaction */
extern int body_8bitmime; /* sender declared BODY= ; 7=7BIT, 8=8BITMIME */
extern uschar *bounce_message_file; /* Template file */
extern uschar *bounce_message_text; /* One-liner */
extern BOOL bounce_return_message; /* Include message in bounce */
extern int bounce_return_size_limit; /* Max amount to return */
extern uschar *bounce_sender_authentication; /* AUTH address for bounces */
-extern int bsmtp_transaction_linecount; /* Start of last transaction */
extern uschar *callout_address; /* Address used for a malware/spamd/verify etc. callout */
extern int callout_cache_domain_positive_expire; /* Time for positive domain callout cache records to expire */
extern uschar *callout_random_local_part; /* Local part to be used to check if server called will accept any local part */
extern uschar *check_dns_names_pattern;/* Regex for syntax check */
extern int check_log_inodes; /* Minimum for message acceptance */
-extern int check_log_space; /* Minimum for message acceptance */
+extern int_eximarith_t check_log_space; /* Minimum for message acceptance */
extern BOOL check_rfc2047_length; /* Check RFC 2047 encoded string length */
extern int check_spool_inodes; /* Minimum for message acceptance */
-extern int check_spool_space; /* Minimum for message acceptance */
+extern int_eximarith_t check_spool_space; /* Minimum for message acceptance */
extern uschar *chunking_advertise_hosts; /* RFC 3030 CHUNKING */
extern unsigned chunking_datasize;
extern unsigned chunking_data_left;
-extern BOOL chunking_offered;
extern chunking_state_t chunking_state;
extern uschar *client_authenticator; /* Authenticator name used for smtp delivery */
extern uschar *client_authenticated_id; /* "login" name used for SMTP AUTH */
extern uschar *client_authenticated_sender; /* AUTH option to SMTP MAIL FROM (not yet used) */
extern int clmacro_count; /* Number of command line macros */
extern uschar *clmacros[]; /* Copy of them, for re-exec */
+extern BOOL commandline_checks_require_admin; /* belt and braces for insecure setups */
extern int connection_max_messages;/* Max down one SMTP connection */
-extern BOOL config_changed; /* True if -C used */
extern FILE *config_file; /* Configuration file */
extern const uschar *config_filename; /* Configuration file name */
extern gid_t config_gid; /* Additional group owner */
extern uschar *config_main_filename; /* File name actually used */
extern uschar *config_main_directory; /* Directory where the main config file was found */
extern uid_t config_uid; /* Additional owner */
+extern uschar *continue_proxy_cipher; /* TLS cipher for proxied continued delivery */
extern uschar *continue_hostname; /* Host for continued delivery */
extern uschar *continue_host_address; /* IP address for ditto */
-extern BOOL continue_more; /* Flag more addresses waiting */
extern int continue_sequence; /* Sequence num for continued delivery */
extern uschar *continue_transport; /* Transport for continued delivery */
extern uschar *csa_status; /* Client SMTP Authorization result */
typedef struct {
+ unsigned callout_hold_only:1; /* Conn is only for verify callout */
unsigned delivery:1; /* When to attempt */
unsigned defer_pass:1; /* Pass 4xx to caller rather than spooling */
- int fd; /* Open connection */
+ unsigned is_tls:1; /* Conn has TLS active */
+ client_conn_ctx cctx; /* Open connection */
int nrcpt; /* Count of addresses */
+ uschar * transport; /* Name of transport */
uschar * interface; /* (address of) */
+ uschar * snd_ip; /* sending_ip_address */
+ int snd_port; /* sending_port */
+ unsigned peer_options; /* smtp_peer_options */
host_item host; /* Host used */
address_item addr; /* (Chain of) addresses */
} cut_t;
extern cut_t cutthrough; /* Deliver-concurrently */
-extern BOOL daemon_listen; /* True if listening required */
extern uschar *daemon_smtp_port; /* Can be a list of ports */
extern int daemon_startup_retries; /* Number of times to retry */
extern int daemon_startup_sleep; /* Sleep between retries */
extern uschar *dccifd_options; /* options for the dccifd daemon */
#endif
-extern BOOL debug_daemon; /* Debug the daemon process only */
extern int debug_fd; /* The fd for debug_file */
extern FILE *debug_file; /* Where to write debugging info */
extern int debug_notall[]; /* Debug options excluded from +all */
extern const uschar *deliver_domain_orig; /* The original local domain for delivery */
extern const uschar *deliver_domain_parent; /* The parent domain for delivery */
extern BOOL deliver_drop_privilege; /* TRUE for unprivileged delivery */
-extern BOOL deliver_firsttime; /* True for first delivery attempt */
-extern BOOL deliver_force; /* TRUE if delivery was forced */
-extern BOOL deliver_freeze; /* TRUE if delivery is frozen */
extern time_t deliver_frozen_at; /* Time of freezing */
extern uschar *deliver_home; /* Home directory for pipes */
extern const uschar *deliver_host; /* (First) host for routed local deliveries */
extern uschar *deliver_localpart_parent; /* The parent local part for delivery */
extern uschar *deliver_localpart_prefix; /* The stripped prefix, if any */
extern uschar *deliver_localpart_suffix; /* The stripped suffix, if any */
-extern BOOL deliver_force_thaw; /* TRUE to force thaw in queue run */
-extern BOOL deliver_manual_thaw; /* TRUE if manually thawed */
extern uschar *deliver_out_buffer; /* Buffer for copying file */
extern int deliver_queue_load_max; /* Different value for queue running */
extern address_item *deliver_recipients; /* Current set of addresses */
extern uschar *deliver_selectstring; /* For selecting by recipient */
-extern BOOL deliver_selectstring_regex; /* String is regex */
extern uschar *deliver_selectstring_sender; /* For selecting by sender */
-extern BOOL deliver_selectstring_sender_regex; /* String is regex */
-extern BOOL disable_callout_flush; /* Don't flush before callouts */
-extern BOOL disable_delay_flush; /* Don't flush before "delay" in ACL */
#ifdef ENABLE_DISABLE_FSYNC
extern BOOL disable_fsync; /* Not for normal use */
#endif
extern BOOL disable_ipv6; /* Don't do any IPv6 things */
-extern BOOL disable_logging; /* Disables log writing when TRUE */
#ifndef DISABLE_DKIM
-extern BOOL dkim_collect_input; /* Runtime flag that tracks wether SMTP input is fed to DKIM validation */
+extern unsigned dkim_collect_input; /* Runtime count of dkim signtures; tracks whether SMTP input is fed to DKIM validation */
extern uschar *dkim_cur_signer; /* Expansion variable, holds the current "signer" domain or identity during a acl_smtp_dkim run */
-extern BOOL dkim_disable_verify; /* Set via ACL control statement. When set, DKIM verification is disabled for the current message */
extern int dkim_key_length; /* Expansion variable, length of signing key in bits */
+extern void *dkim_signatures; /* Actually a (pdkim_signature *) but most files do not need to know */
extern uschar *dkim_signers; /* Expansion variable, holds colon-separated list of domains and identities that have signed a message */
extern uschar *dkim_signing_domain; /* Expansion variable, domain used for signing a message. */
extern uschar *dkim_signing_selector; /* Expansion variable, selector used for signing a message. */
+extern uschar *dkim_verify_overall; /* First successful domain verified, or null */
extern uschar *dkim_verify_signers; /* Colon-separated list of domains for each of which we call the DKIM ACL */
+extern uschar *dkim_verify_status; /* result for this signature */
+extern uschar *dkim_verify_reason; /* result for this signature */
#endif
#ifdef EXPERIMENTAL_DMARC
-extern BOOL dmarc_has_been_checked; /* Global variable to check if test has been called yet */
-extern uschar *dmarc_ar_header; /* Expansion variable, suggested header for dmarc auth results */
extern uschar *dmarc_domain_policy; /* Expansion for declared policy of used domain */
extern uschar *dmarc_forensic_sender; /* Set sender address for forensic reports */
extern uschar *dmarc_history_file; /* Expansion variable, file to store dmarc results */
extern uschar *dmarc_status_text; /* Expansion variable, human readable value */
extern uschar *dmarc_tld_file; /* Mozilla TLDs text file */
extern uschar *dmarc_used_domain; /* Expansion variable, domain libopendmarc chose for DMARC policy lookup */
-extern BOOL dmarc_disable_verify; /* Set via ACL control statement. When set, DMARC verification is disabled for the current message */
-extern BOOL dmarc_enable_forensic; /* Set via ACL control statement. When set, DMARC forensic reports are enabled for the current message */
#endif
extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */
extern int dns_csa_search_limit; /* How deep to search for CSA SRV records */
extern BOOL dns_csa_use_reverse; /* Check CSA in reverse DNS? (non-standard) */
+extern int dns_cname_loops; /* Follow CNAMEs returned by resolver to this depth */
extern uschar *dns_ipv4_lookup; /* For these domains, don't look for AAAA (or A6) */
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
extern int dns_dane_ok; /* Ok to use DANE when checking TLS authenticity */
#endif
extern int dns_retrans; /* Retransmission time setting */
extern uschar *dnslist_value; /* DNS (black) list IP address */
extern tree_node *domainlist_anchor; /* Tree of defined domain lists */
extern int domainlist_count; /* Number defined */
-extern BOOL dont_deliver; /* TRUE for -N option */
-extern BOOL dot_ends; /* TRUE if "." ends non-SMTP input */
/* This option is now a no-opt, retained for compatibility */
extern BOOL drop_cr; /* For broken local MUAs */
extern uschar *dsn_from; /* From: string for DSNs */
-extern BOOL enable_dollar_recipients; /* Make $recipients available */
extern BOOL envelope_to_remove; /* Remove envelope_to_headers */
extern int errno_quota; /* Quota errno in this OS */
extern int error_handling; /* Error handling style */
extern int expand_nlength[]; /* Lengths of numbered strings */
extern int expand_nmax; /* Max numerical value */
extern uschar *expand_nstring[]; /* Numbered strings */
-extern BOOL expand_string_forcedfail; /* TRUE if failure was "expected" */
extern BOOL extract_addresses_remove_arguments; /* Controls -t behaviour */
extern uschar *extra_local_interfaces; /* Local, non-listen interfaces */
extern int fake_response; /* Fake FAIL or DEFER response to data */
extern uschar *fake_response_text; /* User defined message for the above. Default is in globals.c. */
extern int filter_n[FILTER_VARIABLE_COUNT]; /* filter variables */
-extern BOOL filter_running; /* TRUE while running a filter */
extern int filter_sn[FILTER_VARIABLE_COUNT]; /* variables set by system filter */
extern int filter_test; /* Filter test type */
extern uschar *filter_test_sfile; /* System filter test file */
extern uschar *gecos_pattern; /* Pattern to match */
extern rewrite_rule *global_rewrite_rules; /* Chain of rewriting rules */
+extern volatile sig_atomic_t had_command_timeout; /* Alarm sighandler called */
+extern volatile sig_atomic_t had_command_sigterm; /* TERM sighandler called */
+extern volatile sig_atomic_t had_data_timeout; /* Alarm sighandler called */
+extern volatile sig_atomic_t had_data_sigint; /* TERM/INT sighandler called */
extern int header_insert_maxlen; /* Max for inserting headers */
extern int header_maxsize; /* Max total length for header */
extern int header_line_maxsize; /* Max for an individual line */
extern header_name header_names[]; /* Table of header names */
extern int header_names_size; /* Number of entries */
-extern BOOL header_rewritten; /* TRUE if header changed by router */
extern uschar *helo_accept_junk_hosts; /* Allowed to use junk arg */
extern uschar *helo_allow_chars; /* Rogue chars to allow in HELO/EHLO */
extern uschar *helo_lookup_domains; /* If these given, lookup host name */
extern uschar *helo_try_verify_hosts; /* Soft check HELO argument for these */
-extern BOOL helo_verified; /* True if HELO verified */
-extern BOOL helo_verify_failed; /* True if attempt failed */
extern uschar *helo_verify_hosts; /* Hard check HELO argument for these */
extern const uschar *hex_digits; /* Used in several places */
extern uschar *hold_domains; /* Hold up deliveries to these */
-extern BOOL host_find_failed_syntax;/* DNS syntax check failure */
-extern BOOL host_checking_callout; /* TRUE if real callout wanted */
extern uschar *host_data; /* Obtained from lookup in ACL */
extern uschar *host_lookup; /* For which IP addresses are always looked up */
extern BOOL host_lookup_deferred; /* TRUE if lookup deferred */
extern int ignore_bounce_errors_after; /* Keep them for this time. */
extern BOOL ignore_fromline_local; /* Local SMTP ignore fromline */
extern uschar *ignore_fromline_hosts; /* Hosts permitted to send "From " */
-extern BOOL inetd_wait_mode; /* Whether running in inetd wait mode */
extern int inetd_wait_timeout; /* Timeout for inetd wait mode */
extern uschar *initial_cwd; /* The directory we where in at startup */
-extern BOOL is_inetd; /* True for inetd calls */
extern uschar *iterate_item; /* Item from iterate list */
extern int journal_fd; /* Fd for journal file */
extern uschar *eldap_dn; /* Where LDAP DNs are left */
extern int load_average; /* Most recently read load average */
-extern BOOL local_error_message; /* True if handling one of these */
extern BOOL local_from_check; /* For adding Sender: (global value) */
extern uschar *local_from_prefix; /* Permitted prefixes */
extern uschar *local_from_suffix; /* Permitted suffixes */
extern uschar *local_interfaces; /* For forcing specific interfaces */
+#ifdef HAVE_LOCAL_SCAN
extern uschar *local_scan_data; /* Text returned by local_scan() */
extern optionlist local_scan_options[];/* Option list for local_scan() */
extern int local_scan_options_count; /* Size of the list */
extern int local_scan_timeout; /* Timeout for local_scan() */
+#endif
extern BOOL local_sender_retain; /* Retain Sender: (with no From: check) */
extern gid_t local_user_gid; /* As it says; may be set in routers */
extern uid_t local_user_uid; /* As it says; may be set in routers */
extern unsigned int log_selector[]; /* Bit map of logging options */
extern uschar *log_selector_string; /* As supplied in the config */
extern FILE *log_stderr; /* Copy of stderr for log use, or NULL */
-extern BOOL log_testing_mode; /* TRUE in various testing modes */
extern BOOL log_timezone; /* TRUE to include the timezone in log lines */
extern uschar *login_sender_address; /* The actual sender address */
extern lookup_info **lookup_list; /* Array of pointers to available lookups */
extern uschar *lookup_value; /* Value looked up from file */
extern macro_item *macros; /* Configuration macros */
+extern macro_item *macros_user; /* Non-builtin configuration macros */
extern macro_item *mlast; /* Last item in macro list */
-extern BOOL macros_builtin_created; /* Flag for lazy-create */
extern uschar *mailstore_basename; /* For mailstore deliveries */
#ifdef WITH_CONTENT_SCAN
extern uschar *malware_name; /* Name of virus or malware ("W32/Klez-H") */
extern uid_t *never_users; /* List of uids never to be used */
#ifdef WITH_CONTENT_SCAN
-extern BOOL no_mbox_unspool; /* don't unlink files in /scan directory */
#endif
-extern BOOL no_multiline_responses; /* For broken clients */
extern const int on; /* For setsockopt */
extern const int off;
extern uschar *override_local_interfaces; /* Value of -oX argument */
extern uschar *override_pid_file_path; /* Value of -oP argument */
-extern BOOL parse_allow_group; /* Allow group syntax */
-extern BOOL parse_found_group; /* In the middle of a group */
extern uschar *percent_hack_domains; /* Local domains for which '% operates */
extern uschar *pid_file_path; /* For writing daemon pids */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+extern uschar *pipe_connect_advertise_hosts; /* for banner/EHLO pipelining */
+#endif
extern uschar *pipelining_advertise_hosts; /* As it says */
-extern BOOL pipelining_enable; /* As it says */
#ifndef DISABLE_PRDR
extern BOOL prdr_enable; /* As it says */
extern BOOL prdr_requested; /* Connecting mail server wants PRDR */
extern uschar *proxy_local_address; /* IP of local interface of proxy */
extern int proxy_local_port; /* Port on local interface of proxy */
extern BOOL proxy_session; /* TRUE if receiving mail from valid proxy */
-extern BOOL proxy_session_failed; /* TRUE if required proxy negotiation failed */
#endif
extern uschar *prvscheck_address; /* Set during prvscheck expansion item */
extern const uschar *qualify_domain_recipient; /* Domain to qualify recipients with */
extern uschar *qualify_domain_sender; /* Domain to qualify senders with */
-extern BOOL queue_2stage; /* Run queue in 2-stage manner */
extern uschar *queue_domains; /* Queue these domains */
extern BOOL queue_list_requires_admin; /* TRUE if -bp requires admin */
-extern BOOL queue_run_first_delivery; /* If TRUE, first deliveries only */
-extern BOOL queue_run_force; /* TRUE to force during queue run */
-extern BOOL queue_run_local; /* Local deliveries only in queue run */
-extern BOOL queue_running; /* TRUE for queue running process and */
/* immediate children */
extern pid_t queue_run_pid; /* PID of the queue running process or 0 */
extern int queue_run_pipe; /* Pipe for synchronizing */
extern BOOL queue_only_load_latch; /* Latch queue_only_load TRUE */
extern uschar *queue_only_file; /* Queue if file exists/not-exists */
extern BOOL queue_only_override; /* Allow override from command line */
-extern BOOL queue_only_policy; /* ACL or local_scan wants queue_only */
extern BOOL queue_run_in_order; /* As opposed to random */
extern uschar *queue_run_max; /* Max queue runners */
-extern BOOL queue_smtp; /* Disable all immediate SMTP (-odqs)*/
extern uschar *queue_smtp_domains; /* Ditto, for these domains */
extern unsigned int random_seed; /* Seed for random numbers */
extern int rcpt_defer_count; /* Those that got 4xx */
extern gid_t real_gid; /* Real gid */
extern uid_t real_uid; /* Real user running program */
-extern BOOL really_exim; /* FALSE in utilities */
-extern BOOL receive_call_bombout; /* Flag for crashing log */
extern int receive_linecount; /* Mainly for BSMTP errors */
extern int receive_messagecount; /* Mainly for BSMTP errors */
extern int receive_timeout; /* For non-SMTP acceptance */
extern uschar *received_for; /* For "for" field */
extern uschar *received_header_text; /* Definition of Received: header */
extern int received_headers_max; /* Max count of Received: headers */
-extern int received_time; /* Time the message was received */
+extern struct timeval received_time; /* Time the message was received */
+extern struct timeval received_time_taken; /* Interval the message took to be received */
extern uschar *recipient_data; /* lookup data for recipients */
extern uschar *recipient_unqualified_hosts; /* Permitted unqualified recipients */
extern uschar *recipient_verify_failure; /* What went wrong */
-extern BOOL recipients_discarded; /* By an ACL */
extern int recipients_list_max; /* Maximum number fitting in list */
extern int recipients_max; /* Max permitted */
extern BOOL recipients_max_reject; /* If TRUE, reject whole message */
extern const pcre *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */
extern const pcre *regex_PIPELINING; /* For recognizing PIPELINING */
extern const pcre *regex_SIZE; /* For recognizing SIZE settings */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+extern const pcre *regex_EARLY_PIPE; /* For recognizing PIPE_CONNCT */
+#endif
extern const pcre *regex_ismsgid; /* Compiled r.e. for message it */
extern const pcre *regex_smtp_code; /* For recognizing SMTP codes */
extern uschar *regex_vars[]; /* $regexN variables */
extern router_instance *routers; /* Chain of instantiated routers */
extern router_instance router_defaults;/* Default values */
extern uschar *router_name; /* Name of router last started */
-extern BOOL running_in_test_harness; /*TRUE when running_status is patched */
extern ip_address_item *running_interfaces; /* Host's running interfaces */
extern uschar *running_status; /* Flag string for testing */
extern int runrc; /* rc from ${run} */
extern uschar *search_error_message; /* Details of lookup problem */
-extern BOOL search_find_defer; /* Set TRUE if lookup deferred */
extern uschar *self_hostname; /* Self host after routing->directors */
extern unsigned int sender_address_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for sender */
extern uschar *sender_address_data; /* address_data from sender verify */
-extern BOOL sender_address_forced; /* Set by -f */
extern uschar *sender_address_unrewritten; /* Set if rewritten by verify */
extern uschar *sender_data; /* lookup result for senders */
extern unsigned int sender_domain_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for sender domain */
extern BOOL sender_helo_dnssec; /* True if HELO verify used DNS and was DNSSEC */
extern uschar *sender_helo_name; /* Host name from HELO/EHLO */
extern uschar **sender_host_aliases; /* Points to list of alias names */
+extern uschar *sender_host_auth_pubname; /* Public-name of authentication method */
extern unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for incoming host */
extern BOOL sender_host_dnssec; /* true if sender_host_name verified in DNSSEC */
-extern BOOL sender_host_notsocket; /* Set for -bs and -bS */
-extern BOOL sender_host_unknown; /* TRUE for -bs and -bS except inetd */
extern uschar *sender_ident; /* Sender identity via RFC 1413 */
-extern BOOL sender_local; /* TRUE for local senders */
-extern BOOL sender_name_forced; /* Set by -F */
extern uschar *sender_rate; /* Sender rate computed by ACL */
extern uschar *sender_rate_limit; /* Configured rate limit */
extern uschar *sender_rate_period; /* Configured smoothing period */
extern uschar *sender_rcvhost; /* Host data for Received: */
-extern BOOL sender_set_untrusted; /* Sender set by untrusted caller */
extern uschar *sender_unqualified_hosts; /* Permitted unqualified senders */
extern uschar *sender_verify_failure; /* What went wrong */
extern address_item *sender_verified_list; /* Saved chain of sender verifies */
extern uschar *sending_ip_address; /* Address of outgoing (SMTP) interface */
extern int sending_port; /* Port of outgoing interface */
extern SIGNAL_BOOL sigalrm_seen; /* Flag for sigalrm_handler */
+extern const uschar *sigalarm_setter; /* For debug, set to callpoint of alarm() */
extern uschar **sighup_argv; /* Args for re-execing after SIGHUP */
extern int slow_lookup_log; /* Log DNS lookups taking longer than N millisecs */
extern int smtp_accept_count; /* Count of connections */
extern int smtp_accept_queue_per_connection; /* Queue after so many msgs */
extern int smtp_accept_reserve; /* Reserve these SMTP connections */
extern uschar *smtp_active_hostname; /* Hostname for this message */
-extern BOOL smtp_authenticated; /* Sending client has authenticated */
extern uschar *smtp_banner; /* Banner string (to be expanded) */
extern BOOL smtp_check_spool_space; /* TRUE to check SMTP SIZE value */
extern int smtp_ch_index; /* Index in smtp_connection_had */
extern uschar *smtp_cmd_argument; /* For all SMTP commands */
extern uschar *smtp_cmd_buffer; /* SMTP command buffer */
-extern time_t smtp_connection_start; /* Start time of SMTP connection */
+extern struct timeval smtp_connection_start; /* Start time of SMTP connection */
extern uschar smtp_connection_had[]; /* Recent SMTP commands */
extern int smtp_connect_backlog; /* Max backlog permitted */
extern double smtp_delay_mail; /* Current MAIL delay */
extern uschar *spam_score; /* the spam score (float) */
extern uschar *spam_score_int; /* spam_score * 10 (int) */
#endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
extern uschar *spf_guess; /* spf best-guess record */
extern uschar *spf_header_comment; /* spf header comment */
extern uschar *spf_received; /* Received-SPF: header */
extern uschar *spf_result; /* spf result in string form */
+extern BOOL spf_result_guessed; /* spf result is of best-guess operation */
extern uschar *spf_smtp_comment; /* spf comment to include in SMTP reply */
#endif
extern BOOL split_spool_directory; /* TRUE to use multiple subdirs */
+extern FILE *spool_data_file; /* handle for -D file */
extern uschar *spool_directory; /* Name of spool directory */
+extern BOOL spool_wireformat; /* can write wireformat -D files */
#ifdef EXPERIMENTAL_SRS
extern uschar *srs_config; /* SRS config secret:max age:hash length:use timestamp:use hash */
extern uschar *srs_db_address; /* SRS db address */
extern BOOL strip_excess_angle_brackets; /* Surrounding route-addrs */
extern BOOL strip_trailing_dot; /* Remove dots at ends of domains */
extern uschar *submission_domain; /* Domain for submission mode */
-extern BOOL submission_mode; /* Can be forced from ACL */
extern uschar *submission_name; /* User name set from ACL */
-extern BOOL suppress_local_fixups; /* Can be forced from ACL */
-extern BOOL suppress_local_fixups_default; /* former is reset to this; override with -G */
-extern BOOL synchronous_delivery; /* TRUE if -odi is set */
extern BOOL syslog_duplication; /* FALSE => no duplicate logging */
extern int syslog_facility; /* As defined by Syslog.h */
extern BOOL syslog_pid; /* TRUE if PID on syslogs */
extern BOOL system_filter_gid_set; /* TRUE if gid set */
extern uid_t system_filter_uid; /* Uid for running system filter */
extern BOOL system_filter_uid_set; /* TRUE if uid set */
-extern BOOL system_filtering; /* TRUE when running system filter */
-extern BOOL tcp_fastopen_ok; /* appears to be supported by kernel */
+extern blob tcp_fastopen_nodata; /* for zero-data TFO connect requests */
extern BOOL tcp_nodelay; /* Controls TCP_NODELAY on daemon */
+extern tfo_state_t tcp_out_fastopen; /* TCP fast open */
#ifdef USE_TCP_WRAPPERS
extern uschar *tcp_wrappers_daemon_name; /* tcpwrappers daemon lookup name */
#endif
extern int test_harness_load_avg; /* For use when testing */
extern int thismessage_size_limit; /* Limit for this message */
extern int timeout_frozen_after; /* Max time to keep frozen messages */
-extern BOOL timestamps_utc; /* Use UTC for all times */
extern uschar *transport_name; /* Name of transport last started */
extern int transport_count; /* Count of bytes transported */
extern int transport_newlines; /* Accurate count of number of newline chars transported */
extern const uschar **transport_filter_argv; /* For on-the-fly filtering */
extern int transport_filter_timeout; /* Timeout for same */
-extern BOOL transport_filter_timed_out; /* True if it did */
extern transport_info transports_available[]; /* Vector of available transports */
extern transport_instance *transports; /* Chain of instantiated transports */
extern tree_node *tree_nonrecipients; /* Tree of nonrecipient addresses */
extern tree_node *tree_unusable; /* Tree of unusable addresses */
-extern BOOL trusted_caller; /* Caller is trusted */
-extern BOOL trusted_config; /* Configuration file is trusted */
extern gid_t *trusted_groups; /* List of trusted groups */
extern uid_t *trusted_users; /* List of trusted users */
extern uschar *timezone_string; /* Required timezone setting */
/*
* Exim - an Internet mail transport agent
*
- * Copyright (C) 2016 Exim maintainers
- * Copyright (c) University of Cambridge 1995 - 2016
+ * Copyright (C) 2010 - 2018 Exim maintainers
+ * Copyright (c) University of Cambridge 1995 - 2009
*
* Hash interface functions
*/
BOOL
exim_sha_init(hctx * h, hashmethod m)
{
+/*XXX extend for sha512 */
switch (h->method = m)
{
- case HASH_SHA1: h->hashlen = 20; SHA1_Init (&h->u.sha1); break;
- case HASH_SHA256: h->hashlen = 32; SHA256_Init(&h->u.sha2); break;
- default: h->hashlen = 0; return FALSE;
+ case HASH_SHA1: h->hashlen = 20; SHA1_Init (&h->u.sha1); break;
+ case HASH_SHA2_256: h->hashlen = 32; SHA256_Init(&h->u.sha2_256); break;
+ case HASH_SHA2_384: h->hashlen = 48; SHA384_Init(&h->u.sha2_512); break;
+ case HASH_SHA2_512: h->hashlen = 64; SHA512_Init(&h->u.sha2_512); break;
+#ifdef EXIM_HAVE_SHA3
+ case HASH_SHA3_224: h->hashlen = 28;
+ EVP_DigestInit(h->u.mctx = EVP_MD_CTX_new(), EVP_sha3_224());
+ break;
+ case HASH_SHA3_256: h->hashlen = 32;
+ EVP_DigestInit(h->u.mctx = EVP_MD_CTX_new(), EVP_sha3_256());
+ break;
+ case HASH_SHA3_384: h->hashlen = 48;
+ EVP_DigestInit(h->u.mctx = EVP_MD_CTX_new(), EVP_sha3_384());
+ break;
+ case HASH_SHA3_512: h->hashlen = 64;
+ EVP_DigestInit(h->u.mctx = EVP_MD_CTX_new(), EVP_sha3_512());
+ break;
+#endif
+ default: h->hashlen = 0; return FALSE;
}
return TRUE;
}
{
switch (h->method)
{
- case HASH_SHA1: SHA1_Update (&h->u.sha1, data, len); break;
- case HASH_SHA256: SHA256_Update(&h->u.sha2, data, len); break;
+ case HASH_SHA1: SHA1_Update (&h->u.sha1, data, len); break;
+ case HASH_SHA2_256: SHA256_Update(&h->u.sha2_256, data, len); break;
+ case HASH_SHA2_384: SHA384_Update(&h->u.sha2_512, data, len); break;
+ case HASH_SHA2_512: SHA512_Update(&h->u.sha2_512, data, len); break;
+#ifdef EXIM_HAVE_SHA3
+ case HASH_SHA3_224:
+ case HASH_SHA3_256:
+ case HASH_SHA3_384:
+ case HASH_SHA3_512: EVP_DigestUpdate(h->u.mctx, data, len); break;
+#endif
/* should be blocked by init not handling these, but be explicit to
- * guard against accidents later (and hush up clang -Wswitch) */
+ guard against accidents later (and hush up clang -Wswitch) */
default: assert(0);
}
}
b->data = store_get(b->len = h->hashlen);
switch (h->method)
{
- case HASH_SHA1: SHA1_Final (b->data, &h->u.sha1); break;
- case HASH_SHA256: SHA256_Final(b->data, &h->u.sha2); break;
+ case HASH_SHA1: SHA1_Final (b->data, &h->u.sha1); break;
+ case HASH_SHA2_256: SHA256_Final(b->data, &h->u.sha2_256); break;
+ case HASH_SHA2_384: SHA384_Final(b->data, &h->u.sha2_512); break;
+ case HASH_SHA2_512: SHA512_Final(b->data, &h->u.sha2_512); break;
+#ifdef EXIM_HAVE_SHA3
+ case HASH_SHA3_224:
+ case HASH_SHA3_256:
+ case HASH_SHA3_384:
+ case HASH_SHA3_512: EVP_DigestFinal(h->u.mctx, b->data, NULL); break;
+#endif
default: assert(0);
}
}
BOOL
exim_sha_init(hctx * h, hashmethod m)
{
+/*XXX extend for sha512 */
switch (h->method = m)
{
- case HASH_SHA1: h->hashlen = 20; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA1); break;
- case HASH_SHA256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA256); break;
+ case HASH_SHA1: h->hashlen = 20; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA1); break;
+ case HASH_SHA2_256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA256); break;
+ case HASH_SHA2_384: h->hashlen = 48; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA384); break;
+ case HASH_SHA2_512: h->hashlen = 64; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA512); break;
#ifdef EXIM_HAVE_SHA3
+ case HASH_SHA3_224: h->hashlen = 28; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_224); break;
case HASH_SHA3_256: h->hashlen = 32; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_256); break;
+ case HASH_SHA3_384: h->hashlen = 48; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_384); break;
+ case HASH_SHA3_512: h->hashlen = 64; gnutls_hash_init(&h->sha, GNUTLS_DIG_SHA3_512); break;
#endif
default: h->hashlen = 0; return FALSE;
}
BOOL
exim_sha_init(hctx * h, hashmethod m)
{
+/*XXX extend for sha512 */
switch (h->method = m)
{
- case HASH_SHA1: h->hashlen = 20; gcry_md_open(&h->sha, GCRY_MD_SHA1, 0); break;
- case HASH_SHA256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA256, 0); break;
+ case HASH_SHA1: h->hashlen = 20; gcry_md_open(&h->sha, GCRY_MD_SHA1, 0); break;
+ case HASH_SHA2_256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA256, 0); break;
+ case HASH_SHA2_384: h->hashlen = 48; gcry_md_open(&h->sha, GCRY_MD_SHA384, 0); break;
+ case HASH_SHA2_512: h->hashlen = 64; gcry_md_open(&h->sha, GCRY_MD_SHA512, 0); break;
+ case HASH_SHA3_256: h->hashlen = 32; gcry_md_open(&h->sha, GCRY_MD_SHA3_256, 0); break;
+ case HASH_SHA3_384: h->hashlen = 48; gcry_md_open(&h->sha, GCRY_MD_SHA3_384, 0); break;
+ case HASH_SHA3_512: h->hashlen = 64; gcry_md_open(&h->sha, GCRY_MD_SHA3_512, 0); break;
default: h->hashlen = 0; return FALSE;
}
return TRUE;
BOOL
exim_sha_init(hctx * h, hashmethod m)
{
+/*XXX extend for sha512 */
switch (h->method = m)
{
case HASH_SHA1: h->hashlen = 20; sha1_starts(&h->u.sha1); break;
- case HASH_SHA256: h->hashlen = 32; sha2_starts(&h->u.sha2, 0); break;
+ case HASH_SHA2_256: h->hashlen = 32; sha2_starts(&h->u.sha2, 0); break;
default: h->hashlen = 0; return FALSE;
}
return TRUE;
switch (h->method)
{
case HASH_SHA1: sha1_update(h->u.sha1, US data, len); break;
- case HASH_SHA256: sha2_update(h->u.sha2, US data, len); break;
+ case HASH_SHA2_256: sha2_update(h->u.sha2, US data, len); break;
}
}
switch (h->method)
{
case HASH_SHA1: sha1_finish(h->u.sha1, b->data); break;
- case HASH_SHA256: sha2_finish(h->u.sha2, b->data); break;
+ case HASH_SHA2_256: sha2_finish(h->u.sha2, b->data); break;
}
}
#endif
-/******************************************************************************/
-
-/* Common to all library versions */
-int
-exim_sha_hashlen(hctx * h)
-{
-return h->method == HASH_SHA1 ? 20
- : h->method == HASH_SHA256 ? 32
- : 0;
-}
/******************************************************************************/
sha1 base;
int j;
int i = 0x01020304;
-uschar *ctest = (uschar *)(&i);
+uschar *ctest = US (&i);
uschar buffer[256];
uschar digest[20];
uschar s[41];
/*
* Exim - an Internet mail transport agent
*
- * Copyright (C) 2016 Exim maintainers
+ * Copyright (C) 1995 - 2018 Exim maintainers
*
* Hash interface functions
*/
#define HASH_H
#include "sha_ver.h"
-#include "blob.h"
#ifdef SHA_OPENSSL
# include <openssl/sha.h>
typedef enum hashmethod {
HASH_BADTYPE,
+ HASH_NULL,
HASH_SHA1,
- HASH_SHA256,
+
+ HASH_SHA2_256,
+ HASH_SHA2_384,
+ HASH_SHA2_512,
+
HASH_SHA3_224,
HASH_SHA3_256,
HASH_SHA3_384,
#ifdef SHA_OPENSSL
union {
SHA_CTX sha1; /* SHA1 block */
- SHA256_CTX sha2; /* SHA256 block */
+ SHA256_CTX sha2_256; /* SHA256 or 224 block */
+ SHA512_CTX sha2_512; /* SHA512 or 384 block */
+#ifdef EXIM_HAVE_SHA3
+ EVP_MD_CTX * mctx; /* SHA3 block */
+#endif
} u;
#elif defined(SHA_GNUTLS)
extern BOOL exim_sha_init(hctx *, hashmethod);
extern void exim_sha_update(hctx *, const uschar *a, int);
extern void exim_sha_finish(hctx *, blob *);
-extern int exim_sha_hashlen(hctx *);
#endif
/* End of File */
uschar *p, *q;
uschar buffer[HEADER_ADD_BUFFER_SIZE];
+gstring gs = { .size = HEADER_ADD_BUFFER_SIZE, .ptr = 0, .s = buffer };
-if (header_last == NULL) return;
+if (!header_last) return;
-if (!string_vformat(buffer, sizeof(buffer), format, ap))
+if (!string_vformat(&gs, FALSE, format, ap))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "string too long in header_add: "
- "%.100s ...", buffer);
+ "%.100s ...", string_from_gstring(&gs));
+string_from_gstring(&gs);
/* Find where to insert this header */
-if (name == NULL)
+if (!name)
{
if (after)
{
received header is allocated and when it is actually filled in. We want
that header to be first, so skip it for now. */
- if (header_list->text == NULL)
+ if (!header_list->text)
hptr = &header_list->next;
h = *hptr;
}
/* Find the first non-deleted header with the correct name. */
- for (hptr = &header_list; (h = *hptr) != NULL; hptr = &(h->next))
- {
- if (header_testname(h, name, len, TRUE)) break;
- }
+ for (hptr = &header_list; (h = *hptr); hptr = &h->next)
+ if (header_testname(h, name, len, TRUE))
+ break;
/* Handle the case where no header is found. To insert at the bottom, nothing
needs to be done. */
- if (h == NULL)
+ if (!h)
{
if (topnot)
{
true. In this case, we want to include deleted headers in the block. */
else if (after)
- {
for (;;)
{
- if (h->next == NULL || !header_testname(h, name, len, FALSE)) break;
+ if (!h->next || !header_testname(h, name, len, FALSE)) break;
hptr = &(h->next);
h = h->next;
}
- }
}
/* Loop for multiple header lines, taking care about continuations. At this
for (;;)
{
q = Ustrchr(q, '\n');
- if (q == NULL) q = p + Ustrlen(p);
+ if (!q) q = p + Ustrlen(p);
if (*(++q) != ' ' && *q != '\t') break;
}
*hptr = new;
hptr = &(new->next);
- if (h == NULL) header_last = new;
+ if (!h) header_last = new;
p = q;
}
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or
return 0;
if (random_seed == 0)
{
- if (running_in_test_harness) random_seed = 42; else
+ if (f.running_in_test_harness) random_seed = 42; else
{
int p = (int)getpid();
random_seed = (int)time(NULL) ^ ((p << 16) | p);
int fake_mx = MX_NONE; /* This value is actually -1 */
uschar *name;
-if (list == NULL) return;
+if (!list) return;
if (randomize) fake_mx--; /* Start at -2 for randomizing */
*anchor = NULL;
-while ((name = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
+while ((name = string_nextinlist(&list, &sep, NULL, 0)))
{
host_item *h;
h->why = hwhy_unknown;
h->last_try = 0;
- if (*anchor == NULL)
+ if (!*anchor)
{
h->next = NULL;
*anchor = h;
}
else
{
- while (hh->next != NULL && h->sort_key >= (hh->next)->sort_key)
+ while (hh->next && h->sort_key >= hh->next->sort_key)
hh = hh->next;
h->next = hh->next;
hh->next = h;
first place.
Because this data may survive over more than one incoming SMTP message, it has
-to be in permanent store.
+to be in permanent store. However, STARTTLS has to be forgotten and redone
+on a multi-message conn, so this will be called once per message then. Hence
+we use malloc, so we can free.
Arguments: none
Returns: nothing
host_build_sender_fullhost(void)
{
BOOL show_helo = TRUE;
-uschar *address;
+uschar * address, * fullhost, * rcvhost, * reset_point;
int len;
-int old_pool = store_pool;
-if (sender_host_address == NULL) return;
+if (!sender_host_address) return;
-store_pool = POOL_PERM;
+reset_point = store_get(0);
/* Set up address, with or without the port. After discussion, it seems that
the only format that doesn't cause trouble is [aaaa]:pppp. However, we can't
/* If there's no EHLO/HELO data, we can't show it. */
-if (sender_helo_name == NULL) show_helo = FALSE;
+if (!sender_helo_name) show_helo = FALSE;
/* If HELO/EHLO was followed by an IP literal, it's messy because of two
features of IPv6. Firstly, there's the "IPv6:" prefix (Exim is liberal and
/* Host name is not verified */
-if (sender_host_name == NULL)
+if (!sender_host_name)
{
uschar *portptr = Ustrstr(address, "]:");
- int size = 0;
- int ptr = 0;
+ gstring * g;
int adlen; /* Sun compiler doesn't like ++ in initializers */
- adlen = (portptr == NULL)? Ustrlen(address) : (++portptr - address);
- sender_fullhost = (sender_helo_name == NULL)? address :
- string_sprintf("(%s) %s", sender_helo_name, address);
+ adlen = portptr ? (++portptr - address) : Ustrlen(address);
+ fullhost = sender_helo_name
+ ? string_sprintf("(%s) %s", sender_helo_name, address)
+ : address;
- sender_rcvhost = string_catn(NULL, &size, &ptr, address, adlen);
+ g = string_catn(NULL, address, adlen);
- if (sender_ident != NULL || show_helo || portptr != NULL)
+ if (sender_ident || show_helo || portptr)
{
int firstptr;
- sender_rcvhost = string_catn(sender_rcvhost, &size, &ptr, US" (", 2);
- firstptr = ptr;
+ g = string_catn(g, US" (", 2);
+ firstptr = g->ptr;
- if (portptr != NULL)
- sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2, US"port=",
- portptr + 1);
+ if (portptr)
+ g = string_append(g, 2, US"port=", portptr + 1);
if (show_helo)
- sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2,
- (firstptr == ptr)? US"helo=" : US" helo=", sender_helo_name);
+ g = string_append(g, 2,
+ firstptr == g->ptr ? US"helo=" : US" helo=", sender_helo_name);
- if (sender_ident != NULL)
- sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2,
- (firstptr == ptr)? US"ident=" : US" ident=", sender_ident);
+ if (sender_ident)
+ g = string_append(g, 2,
+ firstptr == g->ptr ? US"ident=" : US" ident=", sender_ident);
- sender_rcvhost = string_catn(sender_rcvhost, &size, &ptr, US")", 1);
+ g = string_catn(g, US")", 1);
}
- sender_rcvhost[ptr] = 0; /* string_cat() always leaves room */
-
- /* Release store, because string_cat allocated a minimum of 100 bytes that
- are rarely completely used. */
-
- store_reset(sender_rcvhost + ptr + 1);
+ rcvhost = string_from_gstring(g);
}
/* Host name is known and verified. Unless we've already found that the HELO
if (show_helo)
{
- sender_fullhost = string_sprintf("%s (%s) %s", sender_host_name,
+ fullhost = string_sprintf("%s (%s) %s", sender_host_name,
sender_helo_name, address);
- sender_rcvhost = (sender_ident == NULL)?
- string_sprintf("%s (%s helo=%s)", sender_host_name,
- address, sender_helo_name) :
- string_sprintf("%s\n\t(%s helo=%s ident=%s)", sender_host_name,
- address, sender_helo_name, sender_ident);
+ rcvhost = sender_ident
+ ? string_sprintf("%s\n\t(%s helo=%s ident=%s)", sender_host_name,
+ address, sender_helo_name, sender_ident)
+ : string_sprintf("%s (%s helo=%s)", sender_host_name,
+ address, sender_helo_name);
}
else
{
- sender_fullhost = string_sprintf("%s %s", sender_host_name, address);
- sender_rcvhost = (sender_ident == NULL)?
- string_sprintf("%s (%s)", sender_host_name, address) :
- string_sprintf("%s (%s ident=%s)", sender_host_name, address,
- sender_ident);
+ fullhost = string_sprintf("%s %s", sender_host_name, address);
+ rcvhost = sender_ident
+ ? string_sprintf("%s (%s ident=%s)", sender_host_name, address,
+ sender_ident)
+ : string_sprintf("%s (%s)", sender_host_name, address);
}
}
-store_pool = old_pool;
+if (sender_fullhost) store_free(sender_fullhost);
+sender_fullhost = string_copy_malloc(fullhost);
+if (sender_rcvhost) store_free(sender_rcvhost);
+sender_rcvhost = string_copy_malloc(rcvhost);
+
+store_reset(reset_point);
DEBUG(D_host_lookup) debug_printf("sender_fullhost = %s\n", sender_fullhost);
DEBUG(D_host_lookup) debug_printf("sender_rcvhost = %s\n", sender_rcvhost);
uschar *
host_and_ident(BOOL useflag)
{
-if (sender_fullhost == NULL)
- {
- (void)string_format(big_buffer, big_buffer_size, "%s%s", useflag? "U=" : "",
- (sender_ident == NULL)? US"unknown" : sender_ident);
- }
+if (!sender_fullhost)
+ (void)string_format(big_buffer, big_buffer_size, "%s%s", useflag ? "U=" : "",
+ sender_ident ? sender_ident : US"unknown");
else
{
- uschar *flag = useflag? US"H=" : US"";
- uschar *iface = US"";
- if (LOGGING(incoming_interface) && interface_address != NULL)
+ uschar * flag = useflag ? US"H=" : US"";
+ uschar * iface = US"";
+ if (LOGGING(incoming_interface) && interface_address)
iface = string_sprintf(" I=[%s]:%d", interface_address, interface_port);
- if (sender_ident == NULL)
- (void)string_format(big_buffer, big_buffer_size, "%s%s%s",
- flag, sender_fullhost, iface);
- else
+ if (sender_ident)
(void)string_format(big_buffer, big_buffer_size, "%s%s%s U=%s",
flag, sender_fullhost, iface, sender_ident);
+ else
+ (void)string_format(big_buffer, big_buffer_size, "%s%s%s",
+ flag, sender_fullhost, iface);
}
return big_buffer;
}
{
int sep = 0;
uschar *s;
-uschar buffer[64];
-ip_address_item *yield = NULL;
-ip_address_item *last = NULL;
-ip_address_item *next;
+ip_address_item * yield = NULL, * last = NULL, * next;
-while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+while ((s = string_nextinlist(&list, &sep, NULL, 0)))
{
int ipv;
int port = host_address_extract_port(s); /* Leaves just the IP address */
- if ((ipv = string_is_ip_address(s, NULL)) == 0)
+
+ if (!(ipv = string_is_ip_address(s, NULL)))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Malformed IP address \"%s\" in %s",
s, name);
next->port = port;
next->v6_include_v4 = FALSE;
- if (yield == NULL) yield = last = next; else
+ if (!yield)
+ yield = last = next;
+ else
{
last->next = next;
last = next;
if (family == AF_INET6)
{
struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
- yield = (uschar *)inet_ntop(family, &(sk->sin6_addr), CS addr_buffer,
+ yield = US inet_ntop(family, &(sk->sin6_addr), CS addr_buffer,
sizeof(addr_buffer));
if (portptr != NULL) *portptr = ntohs(sk->sin6_port);
}
else
{
struct sockaddr_in *sk = (struct sockaddr_in *)arg;
- yield = (uschar *)inet_ntop(family, &(sk->sin_addr), CS addr_buffer,
+ yield = US inet_ntop(family, &(sk->sin_addr), CS addr_buffer,
sizeof(addr_buffer));
if (portptr != NULL) *portptr = ntohs(sk->sin_port);
}
}
else
{
- yield = (uschar *)inet_ntop(type, arg, CS addr_buffer, sizeof(addr_buffer));
+ yield = US inet_ntop(type, arg, CS addr_buffer, sizeof(addr_buffer));
}
/* If the result is a mapped IPv4 address, show it in V4 format. */
if (mask < 0)
*tt = 0;
else
- {
- sprintf(CS tt, "/%d", mask);
- while (*tt) tt++;
- }
+ tt += sprintf(CS tt, "/%d", mask);
return tt - buffer;
}
/* Copy and lowercase the name, which is in static storage in many systems.
Put it in permanent memory. */
-s = (uschar *)hosts->h_name;
+s = US hosts->h_name;
len = Ustrlen(s) + 1;
t = sender_host_name = store_get_perm(len);
while (*s != 0) *t++ = tolower(*s++);
/* For testing the case when a lookup does not complete, we have a special
reserved IP address. */
-if (running_in_test_harness &&
+if (f.running_in_test_harness &&
Ustrcmp(sender_host_address, "99.99.99.99") == 0)
{
HDEBUG(D_host_lookup)
truncated and dn_expand may fail. */
if (dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen,
- (uschar *)(rr->data), (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0)
+ US (rr->data), (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0)
{
log_write(0, LOG_MAIN, "host name alias list truncated for %s",
sender_host_address);
/* If we have failed to find a name, return FAIL and log when required.
NB host_lookup_msg must be in permanent store. */
-if (sender_host_name == NULL)
+if (!sender_host_name)
{
- if (host_checking || !log_testing_mode)
+ if (host_checking || !f.log_testing_mode)
log_write(L_host_lookup_failed, LOG_MAIN, "no host name found for IP "
"address %s", sender_host_address);
host_lookup_msg = US" (failed to find host name from IP address)";
save_hostname = sender_host_name; /* Save for error messages */
aliases = sender_host_aliases;
-for (hname = sender_host_name; hname != NULL; hname = *aliases++)
+for (hname = sender_host_name; hname; hname = *aliases++)
{
int rc;
BOOL ok = FALSE;
- host_item h;
- dnssec_domains d;
+ host_item h = { .next = NULL, .name = hname, .mx = MX_NONE, .address = NULL };
+ dnssec_domains d =
+ { .request = sender_host_dnssec ? US"*" : NULL, .require = NULL };
- h.next = NULL;
- h.name = hname;
- h.mx = MX_NONE;
- h.address = NULL;
- d.request = sender_host_dnssec ? US"*" : NULL;;
- d.require = NULL;
-
- if ( (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A,
+ if ( (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
NULL, NULL, NULL, &d, NULL, NULL)) == HOST_FOUND
|| rc == HOST_FOUND_LOCAL
)
h.dnssec == DS_YES ? "DNSSEC verified (AD)" : "unverified");
if (h.dnssec != DS_YES) sender_host_dnssec = FALSE;
- for (hh = &h; hh != NULL; hh = hh->next)
+ for (hh = &h; hh; hh = hh->next)
if (host_is_in_net(hh->address, sender_host_address, 0))
{
HDEBUG(D_host_lookup) debug_printf(" %s OK\n", hh->address);
/* Initialize the flag that gets set for DNS syntax check errors, so that the
interface to this function can be similar to host_find_bydns. */
-host_find_failed_syntax = FALSE;
+f.host_find_failed_syntax = FALSE;
/* Loop to look up both kinds of address in an IPv6 world */
if (slow_lookup_log) time_msec = get_time_in_ms();
#if HAVE_IPV6
- if (running_in_test_harness)
+ if (f.running_in_test_harness)
hostdata = host_fake_gethostbyname(host->name, af, &error_num);
else
{
}
#else /* not HAVE_IPV6 */
- if (running_in_test_harness)
+ if (f.running_in_test_harness)
hostdata = host_fake_gethostbyname(host->name, AF_INET, &error_num);
else
{
if (hostdata->h_name[0] != 0 &&
Ustrcmp(host->name, hostdata->h_name) != 0)
- host->name = string_copy_dnsdomain((uschar *)hostdata->h_name);
+ host->name = string_copy_dnsdomain(US hostdata->h_name);
if (fully_qualified_name != NULL) *fully_qualified_name = host->name;
/* Get the list of addresses. IPv4 and IPv6 addresses can be distinguished
HDEBUG(D_host_lookup) debug_printf("%s\n", msg);
if (temp_error) goto RETURN_AGAIN;
- if (host_checking || !log_testing_mode)
+ if (host_checking || !f.log_testing_mode)
log_write(L_host_lookup_failed, LOG_MAIN, "%s", msg);
return HOST_FIND_FAILED;
}
create additional host items for the additional addresses, copying all the
other fields, and randomizing the order.
-On IPv6 systems, A6 records are sought first (but only if support for A6 is
-configured - they may never become mainstream), then AAAA records are sought,
-and finally A records are sought as well.
+On IPv6 systems, AAAA records are sought first, then A records.
The host name may be changed if the DNS returns a different name - e.g. fully
qualified or changed via CNAME. If fully_qualified_name is not NULL, dns_lookup
to something)
dnssec_request if TRUE request the AD bit
dnssec_require if TRUE require the AD bit
+ whichrrs select ipv4, ipv6 results
Returns: HOST_FIND_FAILED couldn't find A record
HOST_FIND_AGAIN try again later
+ HOST_FIND_SECURITY dnssec required but not acheived
HOST_FOUND found AAAA and/or A record(s)
HOST_IGNORED found, but all IPs ignored
*/
set_address_from_dns(host_item *host, host_item **lastptr,
const uschar *ignore_target_hosts, BOOL allow_ip,
const uschar **fully_qualified_name,
- BOOL dnssec_request, BOOL dnssec_require)
+ BOOL dnssec_request, BOOL dnssec_require, int whichrrs)
{
dns_record *rr;
host_item *thishostlast = NULL; /* Indicates not yet filled in anything */
BOOL v6_find_again = FALSE;
+BOOL dnssec_fail = FALSE;
int i;
/* If allow_ip is set, a name which is an IP address returns that value
if (allow_ip && string_is_ip_address(host->name, NULL) != 0)
{
#ifndef STAND_ALONE
- if (ignore_target_hosts != NULL &&
- verify_check_this_host(&ignore_target_hosts, NULL, host->name,
+ if ( ignore_target_hosts
+ && verify_check_this_host(&ignore_target_hosts, NULL, host->name,
host->name, NULL) == OK)
return HOST_IGNORED;
#endif
}
/* On an IPv6 system, unless IPv6 is disabled, go round the loop up to twice,
-looking for AAAA records the first time. However, unless
-doing standalone testing, we force an IPv4 lookup if the domain matches
-dns_ipv4_lookup is set. On an IPv4 system, go round the
-loop once only, looking only for A records. */
+looking for AAAA records the first time. However, unless doing standalone
+testing, we force an IPv4 lookup if the domain matches dns_ipv4_lookup global.
+On an IPv4 system, go round the loop once only, looking only for A records. */
#if HAVE_IPV6
#ifndef STAND_ALONE
- if (disable_ipv6 || (dns_ipv4_lookup != NULL &&
- match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL,
- MCL_DOMAIN, TRUE, NULL) == OK))
+ if ( disable_ipv6
+ || !(whichrrs & HOST_FIND_BY_AAAA)
+ || (dns_ipv4_lookup
+ && match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, NULL, NULL,
+ MCL_DOMAIN, TRUE, NULL) == OK)
+ )
i = 0; /* look up A records only */
else
#endif /* STAND_ALONE */
{
static int types[] = { T_A, T_AAAA };
int type = types[i];
- int randoffset = (i == 0)? 500 : 0; /* Ensures v6 sorts before v4 */
+ int randoffset = i == (whichrrs & HOST_FIND_IPV4_FIRST ? 1 : 0)
+ ? 500 : 0; /* Ensures v6/4 sort order */
dns_answer dnsa;
dns_scan dnss;
{
if (dnssec_require)
{
- log_write(L_host_lookup_failed, LOG_MAIN,
- "dnssec fail on %s for %.256s",
+ dnssec_fail = TRUE;
+ DEBUG(D_host_lookup) debug_printf("dnssec fail on %s for %.256s",
i>0 ? "AAAA" : "A", host->name);
continue;
}
}
}
-/* Control gets here only if the econdookup (the A record) succeeded.
+/* Control gets here only if the second lookup (the A record) succeeded.
However, the address may not be filled in if it was ignored. */
-return host->address ? HOST_FOUND : HOST_IGNORED;
+return host->address
+ ? HOST_FOUND
+ : dnssec_fail
+ ? HOST_FIND_SECURITY
+ : HOST_IGNORED;
}
whichrrs flags indicating which RRs to look for:
HOST_FIND_BY_SRV => look for SRV
HOST_FIND_BY_MX => look for MX
- HOST_FIND_BY_A => look for A or AAAA
+ HOST_FIND_BY_A => look for A
+ HOST_FIND_BY_AAAA => look for AAAA
also flags indicating how the lookup is done
HOST_FIND_QUALIFY_SINGLE ) passed to the
HOST_FIND_SEARCH_PARENTS ) resolver
+ HOST_FIND_IPV4_FIRST => reverse usual result ordering
+ HOST_FIND_IPV4_ONLY => MX results elide ipv6
srv_service when SRV used, the service name
srv_fail_domains DNS errors for these domains => assume nonexist
mx_fail_domains DNS errors for these domains => assume nonexist
if there was a syntax error,
host_find_failed_syntax is set.
HOST_FIND_AGAIN Could not resolve at this time
+ HOST_FIND_SECURITY dnsssec required but not acheived
HOST_FOUND Host found
HOST_FOUND_LOCAL The lowest MX record points to this
machine, if MX records were found, or
dns_init((whichrrs & HOST_FIND_QUALIFY_SINGLE) != 0,
(whichrrs & HOST_FIND_SEARCH_PARENTS) != 0,
dnssec_request);
-host_find_failed_syntax = FALSE;
+f.host_find_failed_syntax = FALSE;
/* First, if requested, look for SRV records. The service name is given; we
assume TCP protocol. DNS domain names are constrained to a maximum of 256
characters, so the code below should be safe. */
-if ((whichrrs & HOST_FIND_BY_SRV) != 0)
+if (whichrrs & HOST_FIND_BY_SRV)
{
- uschar buffer[300];
- uschar *temp_fully_qualified_name = buffer;
+ gstring * g;
+ uschar * temp_fully_qualified_name;
int prefix_length;
- (void)sprintf(CS buffer, "_%s._tcp.%n%.256s", srv_service, &prefix_length,
- host->name);
+ g = string_fmt_append(NULL, "_%s._tcp.%n%.256s",
+ srv_service, &prefix_length, host->name);
+ temp_fully_qualified_name = string_from_gstring(g);
ind_type = T_SRV;
/* Search for SRV records. If the fully qualified name is different to
dnssec = DS_UNK;
lookup_dnssec_authenticated = NULL;
- rc = dns_lookup_timerwrap(&dnsa, buffer, ind_type, CUSS &temp_fully_qualified_name);
+ rc = dns_lookup_timerwrap(&dnsa, temp_fully_qualified_name, ind_type,
+ CUSS &temp_fully_qualified_name);
DEBUG(D_dns)
if ((dnssec_request || dnssec_require)
- & !dns_is_secure(&dnsa)
- & dns_is_aa(&dnsa))
+ && !dns_is_secure(&dnsa)
+ && dns_is_aa(&dnsa))
debug_printf("DNS lookup of %.256s (SRV) requested AD, but got AA\n", host->name);
if (dnssec_request)
{ dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; }
}
- if (temp_fully_qualified_name != buffer && fully_qualified_name != NULL)
+ if (temp_fully_qualified_name != g->s && fully_qualified_name != NULL)
*fully_qualified_name = temp_fully_qualified_name + prefix_length;
/* On DNS failures, we give the "try again" error unless the domain is
records. On DNS failures, we give the "try again" error unless the domain is
listed as one for which we continue. */
-if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0)
+if (rc != DNS_SUCCEED && whichrrs & HOST_FIND_BY_MX)
{
ind_type = T_MX;
dnssec = DS_UNK;
rc = dns_lookup_timerwrap(&dnsa, host->name, ind_type, fully_qualified_name);
DEBUG(D_dns)
- if ((dnssec_request || dnssec_require)
- & !dns_is_secure(&dnsa)
- & dns_is_aa(&dnsa))
+ if ( (dnssec_request || dnssec_require)
+ && !dns_is_secure(&dnsa)
+ && dns_is_aa(&dnsa))
debug_printf("DNS lookup of %.256s (MX) requested AD, but got AA\n", host->name);
if (dnssec_request)
- {
if (dns_is_secure(&dnsa))
{
DEBUG(D_host_lookup) debug_printf("%s MX DNSSEC\n", host->name);
{
dnssec = DS_NO; lookup_dnssec_authenticated = US"no";
}
- }
switch (rc)
{
case DNS_SUCCEED:
if (!dnssec_require || dns_is_secure(&dnsa))
break;
- log_write(L_host_lookup_failed, LOG_MAIN,
- "dnssec fail on MX for %.256s", host->name);
+ DEBUG(D_host_lookup)
+ debug_printf("dnssec fail on MX for %.256s", host->name);
+#ifndef STAND_ALONE
+ if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, NULL, NULL,
+ MCL_DOMAIN, TRUE, NULL) != OK)
+ { yield = HOST_FIND_SECURITY; goto out; }
+#endif
rc = DNS_FAIL;
/*FALLTHROUGH*/
case DNS_FAIL:
case DNS_AGAIN:
- #ifndef STAND_ALONE
+#ifndef STAND_ALONE
if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, NULL, NULL,
MCL_DOMAIN, TRUE, NULL) != OK)
- #endif
+#endif
{ yield = HOST_FIND_AGAIN; goto out; }
DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA "
"(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN");
if (rc != DNS_SUCCEED)
{
- if ((whichrrs & HOST_FIND_BY_A) == 0)
+ if (!(whichrrs & (HOST_FIND_BY_A | HOST_FIND_BY_AAAA)))
{
DEBUG(D_host_lookup) debug_printf("Address records are not being sought\n");
yield = HOST_FIND_FAILED;
host->dnssec = DS_UNK;
lookup_dnssec_authenticated = NULL;
rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE,
- fully_qualified_name, dnssec_request, dnssec_require);
+ fully_qualified_name, dnssec_request, dnssec_require, whichrrs);
/* If one or more address records have been found, check that none of them
are local. Since we know the host items all have their IP addresses
rr;
rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == ind_type)
{
- int precedence;
- int weight = 0; /* For SRV records */
+ int precedence, weight;
int port = PORT_NONE;
const uschar * s = rr->data; /* MUST be unsigned for GETSHORT */
uschar data[256];
if (ind_type == T_MX)
weight = random_number(500);
-
- /* SRV records are specified with a port and a weight. The weight is used
- in a special algorithm. However, to start with, we just use it to order the
- records of equal priority (precedence). */
-
else
{
+ /* SRV records are specified with a port and a weight. The weight is used
+ in a special algorithm. However, to start with, we just use it to order the
+ records of equal priority (precedence). */
GETSHORT(weight, s);
GETSHORT(port, s);
}
never know what junk might get into the DNS (and this case has been seen on
more than one occasion). */
- if (last != NULL) /* This is not the first record */
+ if (last) /* This is not the first record */
{
host_item *prev = NULL;
for (h = host; h != last->next; prev = h, h = h->next)
- {
if (strcmpic(h->name, data) == 0)
{
DEBUG(D_host_lookup)
debug_printf("discarded duplicate host %s (MX=%d)\n", data,
- (precedence > h->mx)? precedence : h->mx);
+ precedence > h->mx ? precedence : h->mx);
if (precedence >= h->mx) goto NEXT_MX_RR; /* Skip greater precedence */
if (h == host) /* Override first item */
{
if (h == last) last = prev;
break;
}
- }
}
/* If this is the first MX or SRV record, put the data into the existing host
block. Otherwise, add a new block in the correct place; if it has to be
before the first block, copy the first block's data to a new second block. */
- if (last == NULL)
+ if (!last)
{
host->name = string_copy_dnsdomain(data);
host->address = NULL;
host->dnssec = dnssec;
last = host;
}
+ else
/* Make a new host item and seek the correct insertion place */
-
- else
{
int sort_key = precedence * 1000 + weight;
host_item *next = store_get(sizeof(host_item));
host->next = next;
if (last == host) last = next;
}
+ else
/* Else scan down the items we have inserted as part of this exercise;
don't go further. */
-
- else
{
for (h = host; h != last; h = h->next)
- {
if (sort_key < h->next->sort_key)
{
next->next = h->next;
h->next = next;
break;
}
- }
/* Join on after the last host item that's part of this
processing if we haven't stopped sooner. */
for (ppptr = pptr, hhh = h;
hhh != hh;
- ppptr = &(hhh->next), hhh = hhh->next)
- {
- if (hhh->sort_key >= randomizer) break;
- }
+ ppptr = &hhh->next, hhh = hhh->next)
+ if (hhh->sort_key >= randomizer)
+ break;
/* hhh now points to the host that should go first; ppptr points to the
place that points to it. Unfortunately, if the start of the minilist is
hhh->next = temp.next;
h->next = hhh;
}
-
else
{
hhh->next = h; /* The rest of the chain follows it */
if (h->address) continue; /* Inserted by a multihomed host */
rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip,
- NULL, dnssec_request, dnssec_require);
+ NULL, dnssec_request, dnssec_require,
+ whichrrs & HOST_FIND_IPV4_ONLY
+ ? HOST_FIND_BY_A : HOST_FIND_BY_A | HOST_FIND_BY_AAAA);
if (rc != HOST_FOUND)
{
h->status = hstatus_unusable;
- if (rc == HOST_FIND_AGAIN)
+ switch (rc)
{
- yield = rc;
- h->why = hwhy_deferred;
+ case HOST_FIND_AGAIN: yield = rc; h->why = hwhy_deferred; break;
+ case HOST_FIND_SECURITY: yield = rc; h->why = hwhy_insecure; break;
+ case HOST_IGNORED: h->why = hwhy_ignored; break;
+ default: h->why = hwhy_failed; break;
}
- else
- h->why = rc == HOST_IGNORED ? hwhy_ignored : hwhy_failed;
}
}
exist. If we end up with just a single, ignored host, flatten its fields as if
nothing was found. */
-if (ignore_target_hosts != NULL)
+if (ignore_target_hosts)
{
host_item *prev = NULL;
for (h = host; h != last->next; h = h->next)
addresses of a multihomed host, but that should not matter. */
#if HAVE_IPV6
-if (h != last && !disable_ipv6)
+if (h != last && !disable_ipv6) for (h = host; h != last; h = h->next)
{
- for (h = host; h != last; h = h->next)
- {
- host_item temp;
- host_item *next = h->next;
- if (h->mx != next->mx || /* If next is different MX */
- h->address == NULL || /* OR this one is unset */
- Ustrchr(h->address, ':') != NULL || /* OR this one is IPv6 */
- (next->address != NULL &&
- Ustrchr(next->address, ':') == NULL)) /* OR next is IPv4 */
- continue; /* move on to next */
- temp = *h; /* otherwise, swap */
- temp.next = next->next;
- *h = *next;
- h->next = next;
- *next = temp;
- }
+ host_item temp;
+ host_item *next = h->next;
+
+ if ( h->mx != next->mx /* If next is different MX */
+ || !h->address /* OR this one is unset */
+ )
+ continue; /* move on to next */
+
+ if ( whichrrs & HOST_FIND_IPV4_FIRST
+ ? !Ustrchr(h->address, ':') /* OR this one is IPv4 */
+ || next->address
+ && Ustrchr(next->address, ':') /* OR next is IPv6 */
+
+ : Ustrchr(h->address, ':') /* OR this one is IPv6 */
+ || next->address
+ && !Ustrchr(next->address, ':') /* OR next is IPv4 */
+ )
+ continue; /* move on to next */
+
+ temp = *h; /* otherwise, swap */
+ temp.next = next->next;
+ *h = *next;
+ h->next = next;
+ *next = temp;
}
#endif
debug_printf("host_find_bydns yield = %s (%d); returned hosts:\n",
(yield == HOST_FOUND)? "HOST_FOUND" :
(yield == HOST_FOUND_LOCAL)? "HOST_FOUND_LOCAL" :
+ (yield == HOST_FIND_SECURITY)? "HOST_FIND_SECURITY" :
(yield == HOST_FIND_AGAIN)? "HOST_FIND_AGAIN" :
(yield == HOST_FIND_FAILED)? "HOST_FIND_FAILED" : "?",
yield);
int main(int argc, char **cargv)
{
host_item h;
-int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A;
+int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
BOOL byname = FALSE;
BOOL qualify_single = TRUE;
BOOL search_parents = FALSE;
if (Ustrcmp(buffer, "byname") == 0) byname = TRUE;
else if (Ustrcmp(buffer, "no_byname") == 0) byname = FALSE;
- else if (Ustrcmp(buffer, "a_only") == 0) whichrrs = HOST_FIND_BY_A;
+ else if (Ustrcmp(buffer, "a_only") == 0) whichrrs = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
else if (Ustrcmp(buffer, "mx_only") == 0) whichrrs = HOST_FIND_BY_MX;
else if (Ustrcmp(buffer, "srv_only") == 0) whichrrs = HOST_FIND_BY_SRV;
else if (Ustrcmp(buffer, "srv+a") == 0)
- whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_A;
+ whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
else if (Ustrcmp(buffer, "srv+mx") == 0)
whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX;
else if (Ustrcmp(buffer, "srv+mx+a") == 0)
- whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A;
+ whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
else if (Ustrcmp(buffer, "qualify_single") == 0) qualify_single = TRUE;
else if (Ustrcmp(buffer, "no_qualify_single") == 0) qualify_single = FALSE;
else if (Ustrcmp(buffer, "search_parents") == 0) search_parents = TRUE;
else if (Ustrcmp(buffer, "require_dnssec") == 0) require_dnssec = TRUE;
else if (Ustrcmp(buffer, "no_require_dnssec") == 0) require_dnssec = FALSE;
else if (Ustrcmp(buffer, "test_harness") == 0)
- running_in_test_harness = !running_in_test_harness;
+ f.running_in_test_harness = !f.running_in_test_harness;
else if (Ustrcmp(buffer, "ipv6") == 0) disable_ipv6 = !disable_ipv6;
else if (Ustrcmp(buffer, "res_debug") == 0)
{
: host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL,
&d, &fully_qualified_name, NULL);
- if (rc == HOST_FIND_FAILED) printf("Failed\n");
- else if (rc == HOST_FIND_AGAIN) printf("Again\n");
- else if (rc == HOST_FOUND_LOCAL) printf("Local\n");
+ switch (rc)
+ {
+ case HOST_FIND_FAILED: printf("Failed\n"); break;
+ case HOST_FIND_AGAIN: printf("Again\n"); break;
+ case HOST_FIND_SECURITY: printf("Security\n"); break;
+ case HOST_FOUND_LOCAL: printf("Local\n"); break;
+ }
}
printf("\n> ");
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
#include "exim.h"
#ifdef SUPPORT_I18N
{
static uschar encode_base64[64] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
-int ptr = 0;
-int size = 0;
size_t slen;
-uschar *sptr, *yield = NULL;
+uschar *sptr;
+gstring * yield = NULL;
int i = 0, j; /* compiler quietening */
uschar c = 0; /* compiler quietening */
BOOL base64mode = FALSE;
if (outptr > outbuf + sizeof(outbuf) - 3)
{
- yield = string_catn(yield, &size, &ptr, outbuf, outptr - outbuf);
+ yield = string_catn(yield, outbuf, outptr - outbuf);
outptr = outbuf;
}
iconv_close(icd);
#endif
-yield = string_catn(yield, &size, &ptr, outbuf, outptr - outbuf);
-if (yield[ptr-1] == '.')
- ptr--;
-yield[ptr] = '\0';
+yield = string_catn(yield, outbuf, outptr - outbuf);
+
+if (yield->s[yield->ptr-1] == '.')
+ yield->ptr--;
-return yield;
+return string_from_gstring(yield);
}
#endif /* whole file */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for doing things with sockets. With the advent of IPv6 this has
+/*************************************************
+*************************************************/
+
+#ifdef EXIM_TFO_PROBE
+void
+tfo_probe(void)
+{
+# ifdef TCP_FASTOPEN
+int sock, backlog = 5;
+
+if ( (sock = socket(SOCK_STREAM, AF_INET, 0)) < 0
+ && setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &backlog, sizeof(backlog))
+ )
+ f.tcp_fastopen_ok = TRUE;
+close(sock);
+# endif
+}
+#endif
+
+
/*************************************************
* Connect socket to remote host *
*************************************************/
address the remote address, in text form
port the remote port
timeout a timeout (zero for indefinite timeout)
- fastopen TRUE iff TCP_FASTOPEN can be used
+ fastopen_blob non-null iff TCP_FASTOPEN can be used; may indicate early-data to
+ be sent in SYN segment. Any such data must be idempotent.
Returns: 0 on success; -1 on failure, with errno set
*/
int
ip_connect(int sock, int af, const uschar *address, int port, int timeout,
- BOOL fastopen)
+ const blob * fastopen_blob)
{
struct sockaddr_in s_in4;
struct sockaddr *s_ptr;
callout_address = string_sprintf("[%s]:%d", address, port);
sigalrm_seen = FALSE;
-if (timeout > 0) alarm(timeout);
+if (timeout > 0) ALARM(timeout);
-#if defined(TCP_FASTOPEN) && defined(MSG_FASTOPEN)
+#ifdef TCP_FASTOPEN
/* TCP Fast Open, if the system has a cookie from a previous call to
this peer, can send data in the SYN packet. The peer can send data
before it gets our ACK of its SYN,ACK - the latter is useful for
-the SMTP banner. Is there any usage where the former might be?
-We might extend the ip_connect() args for data if so. For now,
-connect in FASTOPEN mode but with zero data.
-*/
+the SMTP banner. Other (than SMTP) cases of TCP connections can
+possibly use the data-on-syn, so support that too. */
-if (fastopen)
+if (fastopen_blob && f.tcp_fastopen_ok)
{
- if ( (rc = sendto(sock, NULL, 0, MSG_FASTOPEN, s_ptr, s_len)) < 0
- && errno == EOPNOTSUPP
- )
+# ifdef MSG_FASTOPEN
+ /* This is a Linux implementation. It might be useable on FreeBSD; I have
+ not checked. */
+
+ if ((rc = sendto(sock, fastopen_blob->data, fastopen_blob->len,
+ MSG_FASTOPEN | MSG_DONTWAIT, s_ptr, s_len)) >= 0)
+ /* seen for with-data, experimental TFO option, with-cookie case */
+ /* seen for with-data, proper TFO opt, with-cookie case */
+ {
+ DEBUG(D_transport|D_v)
+ debug_printf("TFO mode connection attempt to %s, %lu data\n",
+ address, (unsigned long)fastopen_blob->len);
+ /*XXX also seen on successful TFO, sigh */
+ tcp_out_fastopen = fastopen_blob->len > 0 ? TFO_ATTEMPTED_DATA : TFO_ATTEMPTED_NODATA;
+ }
+ else if (errno == EINPROGRESS) /* expected if we had no cookie for peer */
+ /* seen for no-data, proper TFO option, both cookie-request and with-cookie cases */
+ /* apparently no visibility of the diffference at this point */
+ /* seen for with-data, proper TFO opt, cookie-req */
+ /* with netwk delay, post-conn tcp_info sees unacked 1 for R, 2 for C; code in smtp_out.c */
+ /* ? older Experimental TFO option behaviour ? */
+ { /* queue unsent data */
+ DEBUG(D_transport|D_v) debug_printf("TFO mode sendto, %s data: EINPROGRESS\n",
+ fastopen_blob->len > 0 ? "with" : "no");
+ if (!fastopen_blob->data)
+ {
+ tcp_out_fastopen = TFO_ATTEMPTED_NODATA; /* we tried; unknown if useful yet */
+ rc = 0;
+ }
+ else
+ rc = send(sock, fastopen_blob->data, fastopen_blob->len, 0);
+ }
+ else if(errno == EOPNOTSUPP)
{
DEBUG(D_transport)
debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
- rc = connect(sock, s_ptr, s_len);
+ goto legacy_connect;
+ }
+# endif
+# ifdef EXIM_TFO_CONNECTX
+ /* MacOS */
+ sa_endpoints_t ends = {
+ .sae_srcif = 0, .sae_srcaddr = NULL, .sae_srcaddrlen = 0,
+ .sae_dstaddr = s_ptr, .sae_dstaddrlen = s_len };
+ struct iovec iov = {
+ .iov_base = fastopen_blob->data, .iov_len = fastopen_blob->len };
+ size_t len;
+
+ if ((rc = connectx(sock, &ends, SAE_ASSOCID_ANY,
+ CONNECT_DATA_IDEMPOTENT, &iov, 1, &len, NULL)) == 0)
+ {
+ DEBUG(D_transport|D_v)
+ debug_printf("TFO mode connection attempt to %s, %lu data\n",
+ address, (unsigned long)fastopen_blob->len);
+ tcp_out_fastopen = fastopen_blob->len > 0 ? TFO_ATTEMPTED_DATA : TFO_ATTEMPTED_NODATA;
+
+ if (len != fastopen_blob->len)
+ DEBUG(D_transport|D_v)
+ debug_printf(" only queued %lu data!\n", (unsigned long)len);
+ }
+ else if (errno == EINPROGRESS)
+ {
+ DEBUG(D_transport|D_v) debug_printf("TFO mode sendto, %s data: EINPROGRESS\n",
+ fastopen_blob->len > 0 ? "with" : "no");
+ if (!fastopen_blob->data)
+ {
+ tcp_out_fastopen = TFO_ATTEMPTED_NODATA; /* we tried; unknown if useful yet */
+ rc = 0;
+ }
+ else /* assume that no data was queued; block in send */
+ rc = send(sock, fastopen_blob->data, fastopen_blob->len, 0);
}
+# endif
}
else
-#endif
- rc = connect(sock, s_ptr, s_len);
+#endif /*TCP_FASTOPEN*/
+ {
+legacy_connect:
+ DEBUG(D_transport|D_v) if (fastopen_blob)
+ debug_printf("non-TFO mode connection attempt to %s, %lu data\n",
+ address, (unsigned long)fastopen_blob->len);
+ if ((rc = connect(sock, s_ptr, s_len)) >= 0)
+ if ( fastopen_blob && fastopen_blob->data && fastopen_blob->len
+ && send(sock, fastopen_blob->data, fastopen_blob->len, 0) < 0)
+ rc = -1;
+ }
save_errno = errno;
-alarm(0);
+ALARM_CLR(0);
/* There is a testing facility for simulating a connection timeout, as I
can't think of any other way of doing this. It converts a connection refused
into a timeout if the timeout is set to 999999. */
-if (running_in_test_harness && save_errno == ECONNREFUSED && timeout == 999999)
+if (f.running_in_test_harness && save_errno == ECONNREFUSED && timeout == 999999)
{
rc = -1;
save_errno = EINTR;
Arguments:
type SOCK_DGRAM or SOCK_STREAM
af AF_INET6 or AF_INET for the socket type
- address the remote address, in text form
+ hostname host name, or ip address (as text)
portlo,porthi the remote port range
timeout a timeout
- connhost if not NULL, host_item filled in with connection details
+ connhost if not NULL, host_item to be filled in with connection details
errstr pointer for allocated string on error
+ fastopen_blob with SOCK_STREAM, if non-null, request TCP Fast Open.
+ Additionally, optional idempotent early-data to send
Return:
socket fd, or -1 on failure (having allocated an error string)
*/
int
ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi,
- int timeout, host_item * connhost, uschar ** errstr)
+ int timeout, host_item * connhost, uschar ** errstr, const blob * fastopen_blob)
{
int namelen, port;
host_item shost;
host_item *h;
int af = 0, fd, fd4 = -1, fd6 = -1;
-BOOL fastopen = tcp_fastopen_ok && type == SOCK_STREAM;
shost.next = NULL;
shost.address = NULL;
}
for(port = portlo; port <= porthi; port++)
- if (ip_connect(fd, af, h->address, port, timeout, fastopen) == 0)
+ if (ip_connect(fd, af, h->address, port, timeout, fastopen_blob) == 0)
{
if (fd != fd6) close(fd6);
if (fd != fd4) close(fd4);
}
+/*XXX TFO? */
int
ip_tcpsocket(const uschar * hostport, uschar ** errstr, int tmo)
{
}
return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh,
- tmo, NULL, errstr);
+ tmo, NULL, errstr, NULL);
}
int
{
int fodder = 1;
if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
- (uschar *)(&fodder), sizeof(fodder)) != 0)
+ US (&fodder), sizeof(fodder)) != 0)
log_write(0, LOG_MAIN, "setsockopt(SO_KEEPALIVE) on connection %s %s "
"failed: %s", torf? "to":"from", address, strerror(errno));
}
do
{
- struct timeval tv = { time_left, 0 };
+ struct timeval tv = { .tv_sec = time_left, .tv_usec = 0 };
FD_ZERO (&select_inset);
FD_SET (fd, &select_inset);
result but no ready descriptor. Is this in fact possible?
Arguments:
- sock the socket
+ cctx the connection context (socket fd, possibly TLS context)
buffer to read into
bufsize the buffer size
timeout the timeout
*/
int
-ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
+ip_recv(client_conn_ctx * cctx, uschar * buffer, int buffsize, int timeout)
{
int rc;
-if (!fd_ready(sock, timeout))
+if (!fd_ready(cctx->sock, timeout))
return -1;
/* The socket is ready, read from it (via TLS if it's active). On EOF (i.e.
close down of the connection), set errno to zero; otherwise leave it alone. */
#ifdef SUPPORT_TLS
-if (tls_out.active == sock)
- rc = tls_read(FALSE, buffer, buffsize);
-else if (tls_in.active == sock)
- rc = tls_read(TRUE, buffer, buffsize);
+if (cctx->tls_ctx) /* client TLS */
+ rc = tls_read(cctx->tls_ctx, buffer, buffsize);
+else if (tls_in.active.sock == cctx->sock) /* server TLS */
+ rc = tls_read(NULL, buffer, buffsize);
else
#endif
- rc = recv(sock, buffer, buffsize, 0);
+ rc = recv(cctx->sock, buffer, buffsize, 0);
if (rc > 0) return rc;
if (rc == 0) errno = 0;
Local/local_scan.c, and edit the copy. To use your version instead of the
default, you must set
+HAVE_LOCAL_SCAN=yes
LOCAL_SCAN_SOURCE=Local/local_scan.c
in your Local/Makefile. This makes it easy to copy your version for use with
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* This file is the header that is the only Exim header to be included in the
extern BOOL receive_remove_recipient(uschar *);
extern uschar *rfc2047_decode(uschar *, BOOL, uschar *, int, int *, uschar **);
extern int smtp_fflush(void);
-extern void smtp_printf(const char *, ...) PRINTF_FUNCTION(1,2);
-extern void smtp_vprintf(const char *, va_list);
+extern void smtp_printf(const char *, BOOL, ...) PRINTF_FUNCTION(1,3);
+extern void smtp_vprintf(const char *, BOOL, va_list);
extern uschar *string_copy(const uschar *);
extern uschar *string_copyn(const uschar *, int);
extern uschar *string_sprintf(const char *, ...) ALMOST_PRINTF(1,2);
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for writing log files. The code for maintaining datestamped
Arguments:
priority syslog priority
- s the string to be written, the string may be modified!
+ s the string to be written
Returns: nothing
*/
static void
-write_syslog(int priority, uschar *s)
+write_syslog(int priority, const uschar *s)
{
int len, pass;
int linecount = 0;
-if (running_in_test_harness) return;
-
-if (!syslog_timestamp) s += log_timezone? 26 : 20;
if (!syslog_pid && LOGGING(pid))
- memmove(s + pid_position[0], s + pid_position[1], pid_position[1] - pid_position[0]);
+ s = string_sprintf("%.*s%s", (int)pid_position[0], s, s + pid_position[1]);
+if (!syslog_timestamp)
+ {
+ len = log_timezone ? 26 : 20;
+ if (LOGGING(millisec)) len += 4;
+ s += len;
+ }
len = Ustrlen(s);
#ifndef NO_OPENLOG
-if (!syslog_open)
+if (!syslog_open && !f.running_in_test_harness)
{
- #ifdef SYSLOG_LOG_PID
+# ifdef SYSLOG_LOG_PID
openlog(CS syslog_processname, LOG_PID|LOG_CONS, syslog_facility);
- #else
+# else
openlog(CS syslog_processname, LOG_CONS, syslog_facility);
- #endif
+# endif
syslog_open = TRUE;
}
#endif
{
int i;
int tlen;
- uschar *ss = s;
+ const uschar * ss = s;
for (i = 1, tlen = len; tlen > 0; i++)
{
int plen = tlen;
uschar *nlptr = Ustrchr(ss, '\n');
if (nlptr != NULL) plen = nlptr - ss;
- #ifndef SYSLOG_LONG_LINES
+#ifndef SYSLOG_LONG_LINES
if (plen > MAX_SYSLOG_LEN) plen = MAX_SYSLOG_LEN;
- #endif
+#endif
tlen -= plen;
if (ss[plen] == '\n') tlen--; /* chars left */
- if (pass == 0) linecount++; else
- {
+ if (pass == 0)
+ linecount++;
+ else if (f.running_in_test_harness)
+ if (linecount == 1)
+ fprintf(stderr, "SYSLOG: '%.*s'\n", plen, ss);
+ else
+ fprintf(stderr, "SYSLOG: '[%d%c%d] %.*s'\n", i,
+ ss[plen] == '\n' && tlen != 0 ? '\\' : '/',
+ linecount, plen, ss);
+ else
if (linecount == 1)
syslog(priority, "%.*s", plen, ss);
else
syslog(priority, "[%d%c%d] %.*s", i,
- (ss[plen] == '\n' && tlen != 0)? '\\' : '/',
+ ss[plen] == '\n' && tlen != 0 ? '\\' : '/',
linecount, plen, ss);
- }
+
ss += plen;
if (*ss == '\n') ss++;
}
static void
die(uschar *s1, uschar *s2)
{
-if (s1 != NULL)
+if (s1)
{
write_syslog(LOG_CRIT, s1);
- if (debug_file != NULL) debug_printf("%s\n", s1);
- if (log_stderr != NULL && log_stderr != debug_file)
+ if (debug_file) debug_printf("%s\n", s1);
+ if (log_stderr && log_stderr != debug_file)
fprintf(log_stderr, "%s\n", s1);
}
-if (receive_call_bombout) receive_bomb_out(NULL, s2); /* does not return */
+if (f.receive_call_bombout) receive_bomb_out(NULL, s2); /* does not return */
if (smtp_input) smtp_closedown(s2);
-exim_exit(EXIT_FAILURE);
+exim_exit(EXIT_FAILURE, NULL);
}
*lastslash = 0;
created = directory_make(NULL, name, LOG_DIRECTORY_MODE, FALSE);
DEBUG(D_any) debug_printf("%s log directory %s\n",
- created? "created" : "failed to create", name);
+ created ? "created" : "failed to create", name);
*lastslash = '/';
if (created) fd = Uopen(name,
#ifdef O_CLOEXEC
will be compared. The static slot for saving it is the same size as buffer,
and the text has been checked above to fit, so this use of strcpy() is OK. */
-if (type == lt_main)
+if (type == lt_main && string_datestamp_offset >= 0)
{
Ustrcpy(mainlog_name, buffer);
mainlog_datestamp = mainlog_name + string_datestamp_offset;
/* Ditto for the reject log */
-else if (type == lt_reject)
+else if (type == lt_reject && string_datestamp_offset >= 0)
{
Ustrcpy(rejectlog_name, buffer);
rejectlog_datestamp = rejectlog_name + string_datestamp_offset;
else if (string_datestamp_offset >= 0)
{
- uschar *from = buffer + string_datestamp_offset;
- uschar *to = from + string_datestamp_length;
+ uschar * from = buffer + string_datestamp_offset;
+ uschar * to = from + string_datestamp_length;
+
if (from == buffer || from[-1] == '/')
{
if (!isalnum(*to)) to++;
}
else
- {
if (!isalnum(from[-1])) from--;
- }
-
- /* This strcpy is ok, because we know that to is a substring of from. */
- Ustrcpy(from, to);
+ /* This copy is ok, because we know that to is a substring of from. But
+ due to overlap we must use memmove() not Ustrcpy(). */
+ memmove(from, to, Ustrlen(to)+1);
}
/* If the file name is too long, it is an unrecoverable disaster */
if (!ok)
- {
die(US"exim: log file path too long: aborting",
US"Logging failure; please try later");
- }
/* We now have the file name. Try to open an existing file. After a successful
open, arrange for automatic closure on exec(), and then return. */
Returns: updated pointer
*/
-static uschar *
-log_config_info(uschar *ptr, int flags)
+static gstring *
+log_config_info(gstring * g, int flags)
{
-Ustrcpy(ptr, "Exim configuration error");
-ptr += 24;
+g = string_cat(g, US"Exim configuration error");
-if ((flags & (LOG_CONFIG_FOR & ~LOG_CONFIG)) != 0)
- {
- Ustrcpy(ptr, " for ");
- return ptr + 5;
- }
+if (flags & (LOG_CONFIG_FOR & ~LOG_CONFIG))
+ return string_cat(g, US" for ");
-if ((flags & (LOG_CONFIG_IN & ~LOG_CONFIG)) != 0)
- {
- sprintf(CS ptr, " in line %d of %s", config_lineno, config_filename);
- while (*ptr) ptr++;
- }
+if (flags & (LOG_CONFIG_IN & ~LOG_CONFIG))
+ g = string_fmt_append(g, " in line %d of %s", config_lineno, config_filename);
-Ustrcpy(ptr, ":\n ");
-return ptr + 4;
+return string_catn(g, US":\n ", 4);
}
void
log_write(unsigned int selector, int flags, const char *format, ...)
{
-uschar *ptr;
-int length;
int paniclogfd;
ssize_t written_len;
+gstring gs = { .size = LOG_BUFFER_SIZE-1, .ptr = 0, .s = log_buffer };
+gstring * g;
va_list ap;
/* If panic_recurseflag is set, we have failed to open the panic log. This is
if (panic_recurseflag)
{
- uschar *extra = (panic_save_buffer == NULL)? US"" : panic_save_buffer;
- if (debug_file != NULL) debug_printf("%s%s", extra, log_buffer);
- if (log_stderr != NULL && log_stderr != debug_file)
+ uschar *extra = panic_save_buffer ? panic_save_buffer : US"";
+ if (debug_file) debug_printf("%s%s", extra, log_buffer);
+ if (log_stderr && log_stderr != debug_file)
fprintf(log_stderr, "%s%s", extra, log_buffer);
- if (*extra != 0) write_syslog(LOG_CRIT, extra);
+ if (*extra) write_syslog(LOG_CRIT, extra);
write_syslog(LOG_CRIT, log_buffer);
die(US"exim: could not open panic log - aborting: see message(s) above",
US"Unexpected log failure, please try later");
if (!(log_buffer = US malloc(LOG_BUFFER_SIZE)))
{
fprintf(stderr, "exim: failed to get store for log buffer\n");
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, NULL);
}
/* If we haven't already done so, inspect the setting of log_file_path to
int sep = ':'; /* Fixed separator - outside use */
uschar *s;
const uschar *ss = log_file_path;
+
logging_mode = 0;
while ((s = string_nextinlist(&ss, &sep, log_buffer, LOG_BUFFER_SIZE)))
{
if (Ustrcmp(s, "syslog") == 0)
logging_mode |= LOG_MODE_SYSLOG;
- else if ((logging_mode & LOG_MODE_FILE) != 0) multiple = TRUE;
+ else if (logging_mode & LOG_MODE_FILE)
+ multiple = TRUE;
else
{
logging_mode |= LOG_MODE_FILE;
/* Set up the ultimate default if necessary. Then revert to the old store
pool, and record that we've sorted out the path. */
- if ((logging_mode & LOG_MODE_FILE) != 0 && file_path[0] == 0)
+ if (logging_mode & LOG_MODE_FILE && !file_path[0])
file_path = string_sprintf("%s/log/%%slog", spool_directory);
store_pool = old_pool;
path_inspected = TRUE;
DEBUG(D_any|D_v)
{
int i;
- ptr = log_buffer;
- Ustrcpy(ptr, "LOG:");
- ptr += 4;
+ g = string_catn(&gs, US"LOG:", 4);
/* Show the selector that was passed into the call. */
{
unsigned int bitnum = log_options[i].bit;
if (bitnum < BITWORDSIZE && selector == BIT(bitnum))
- {
- *ptr++ = ' ';
- Ustrcpy(ptr, log_options[i].name);
- while (*ptr) ptr++;
- }
+ g = string_fmt_append(g, " %s", log_options[i].name);
}
- sprintf(CS ptr, "%s%s%s%s\n ",
- ((flags & LOG_MAIN) != 0)? " MAIN" : "",
- ((flags & LOG_PANIC) != 0)? " PANIC" : "",
- ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE)? " DIE" : "",
- ((flags & LOG_REJECT) != 0)? " REJECT" : "");
+ g = string_fmt_append(g, "%s%s%s%s\n ",
+ flags & LOG_MAIN ? " MAIN" : "",
+ flags & LOG_PANIC ? " PANIC" : "",
+ (flags & LOG_PANIC_DIE) == LOG_PANIC_DIE ? " DIE" : "",
+ flags & LOG_REJECT ? " REJECT" : "");
- while(*ptr) ptr++;
- if ((flags & LOG_CONFIG) != 0) ptr = log_config_info(ptr, flags);
+ if (flags & LOG_CONFIG) g = log_config_info(g, flags);
va_start(ap, format);
- if (!string_vformat(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer)-1, format, ap))
- Ustrcpy(ptr, "**** log string overflowed log buffer ****");
+ i = g->ptr;
+ if (!string_vformat(g, FALSE, format, ap))
+ {
+ g->ptr = i;
+ g = string_cat(g, US"**** log string overflowed log buffer ****");
+ }
va_end(ap);
- while(*ptr) ptr++;
- Ustrcat(ptr, "\n");
- debug_printf("%s", log_buffer);
- }
+ g->size = LOG_BUFFER_SIZE;
+ g = string_catn(g, US"\n", 1);
+ debug_printf("%s", string_from_gstring(g));
+ gs.size = LOG_BUFFER_SIZE-1; /* Having used the buffer for debug output, */
+ gs.ptr = 0; /* reset it for the real use. */
+ gs.s = log_buffer;
+ }
/* If no log file is specified, we are in a mess. */
-if ((flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT)) == 0)
+if (!(flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT)))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "log_write called with no log "
"flags set");
/* There are some weird circumstances in which logging is disabled. */
-if (disable_logging)
+if (f.disable_logging)
{
DEBUG(D_any) debug_printf("log writing disabled\n");
return;
/* Create the main message in the log buffer. Do not include the message id
when called by a utility. */
-ptr = log_buffer;
-sprintf(CS ptr, "%s ", tod_stamp(tod_log));
-while(*ptr) ptr++;
+g = string_fmt_append(&gs, "%s ", tod_stamp(tod_log));
if (LOGGING(pid))
{
- sprintf(CS ptr, "[%d] ", (int)getpid());
- if (!syslog_pid) pid_position[0] = ptr - log_buffer; /* remember begin … */
- while (*ptr) ptr++;
- if (!syslog_pid) pid_position[1] = ptr - log_buffer; /* … and end+1 of the PID */
+ if (!syslog_pid) pid_position[0] = g->ptr; /* remember begin … */
+ g = string_fmt_append(g, "[%d] ", (int)getpid());
+ if (!syslog_pid) pid_position[1] = g->ptr; /* … and end+1 of the PID */
}
-if (really_exim && message_id[0] != 0)
- {
- sprintf(CS ptr, "%s ", message_id);
- while(*ptr) ptr++;
- }
+if (f.really_exim && message_id[0] != 0)
+ g = string_fmt_append(g, "%s ", message_id);
-if ((flags & LOG_CONFIG) != 0) ptr = log_config_info(ptr, flags);
+if (flags & LOG_CONFIG)
+ g = log_config_info(g, flags);
va_start(ap, format);
-if (!string_vformat(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer)-1, format, ap))
- Ustrcpy(ptr, "**** log string overflowed log buffer ****\n");
-while(*ptr) ptr++;
+ {
+ int i = g->ptr;
+ if (!string_vformat(g, FALSE, format, ap))
+ {
+ g->ptr = i;
+ g = string_cat(g, US"**** log string overflowed log buffer ****\n");
+ }
+ }
va_end(ap);
/* Add the raw, unrewritten, sender to the message if required. This is done
this way because it kind of fits with LOG_RECIPIENTS. */
-if ((flags & LOG_SENDER) != 0 &&
- ptr < log_buffer + LOG_BUFFER_SIZE - 10 - Ustrlen(raw_sender))
- {
- sprintf(CS ptr, " from <%s>", raw_sender);
- while (*ptr) ptr++;
- }
+if ( flags & LOG_SENDER
+ && g->ptr < LOG_BUFFER_SIZE - 10 - Ustrlen(raw_sender))
+ g = string_fmt_append(g, " from <%s>", raw_sender);
/* Add list of recipients to the message if required; the raw list,
before rewriting, was saved in raw_recipients. There may be none, if an ACL
discarded them all. */
-if ((flags & LOG_RECIPIENTS) != 0 && ptr < log_buffer + LOG_BUFFER_SIZE - 6 &&
- raw_recipients_count > 0)
+if ( flags & LOG_RECIPIENTS
+ && g->ptr < LOG_BUFFER_SIZE - 6
+ && raw_recipients_count > 0)
{
int i;
- sprintf(CS ptr, " for");
- while (*ptr) ptr++;
+ g = string_fmt_append(g, " for");
for (i = 0; i < raw_recipients_count; i++)
{
- uschar *s = raw_recipients[i];
- if (log_buffer + LOG_BUFFER_SIZE - ptr < Ustrlen(s) + 3) break;
- sprintf(CS ptr, " %s", s);
- while (*ptr) ptr++;
+ uschar * s = raw_recipients[i];
+ if (LOG_BUFFER_SIZE - g->ptr < Ustrlen(s) + 3) break;
+ g = string_fmt_append(g, " %s", s);
}
}
-sprintf(CS ptr, "\n");
-while(*ptr) ptr++;
-length = ptr - log_buffer;
+g = string_catn(g, US"\n", 1);
+string_from_gstring(g);
/* Handle loggable errors when running a utility, or when address testing.
Write to log_stderr unless debugging (when it will already have been written),
or unless there is no log_stderr (expn called from daemon, for example). */
-if (!really_exim || log_testing_mode)
+if (!f.really_exim || f.log_testing_mode)
{
- if (debug_selector == 0 && log_stderr != NULL &&
- (selector == 0 || (selector & log_selector[0]) != 0))
- {
+ if ( !debug_selector
+ && log_stderr
+ && (selector == 0 || (selector & log_selector[0]) != 0)
+ )
if (host_checking)
fprintf(log_stderr, "LOG: %s", CS(log_buffer + 20)); /* no timestamp */
else
fprintf(log_stderr, "%s", CS log_buffer);
- }
- if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE);
+
+ if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE, US"");
return;
}
operation. This happens at midnight, at which point we want to roll over
the file. Closing it has the desired effect. */
- if (mainlog_datestamp != NULL)
+ if (mainlog_datestamp)
{
uschar *nowstamp = tod_stamp(string_datestamp_type);
if (Ustrncmp (mainlog_datestamp, nowstamp, Ustrlen(nowstamp)) != 0)
/* Failing to write to the log is disastrous */
- written_len = write_to_fd_buf(mainlogfd, log_buffer, length);
- if (written_len != length)
+ written_len = write_to_fd_buf(mainlogfd, g->s, g->ptr);
+ if (written_len != g->ptr)
{
- log_write_failed(US"main log", length, written_len);
+ log_write_failed(US"main log", g->ptr, written_len);
/* That function does not return */
}
}
the rejection is happening after the DATA phase), log the recipients and the
headers. */
-if ((flags & LOG_REJECT) != 0)
+if (flags & LOG_REJECT)
{
header_line *h;
- if (header_list != NULL && LOGGING(rejected_header))
+ if (header_list && LOGGING(rejected_header))
{
+ uschar * p = g->s + g->ptr;
+ int i;
+
if (recipients_count > 0)
{
- int i;
-
/* List the sender */
- string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+ string_format(p, LOG_BUFFER_SIZE - g->ptr,
"Envelope-from: <%s>\n", sender_address);
- while (*ptr) ptr++;
+ while (*p) p++;
+ g->ptr = p - g->s;
/* List up to 5 recipients */
- string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+ string_format(p, LOG_BUFFER_SIZE - g->ptr,
"Envelope-to: <%s>\n", recipients_list[0].address);
- while (*ptr) ptr++;
+ while (*p) p++;
+ g->ptr = p - g->s;
for (i = 1; i < recipients_count && i < 5; i++)
{
- string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer), " <%s>\n",
+ string_format(p, LOG_BUFFER_SIZE - g->ptr, " <%s>\n",
recipients_list[i].address);
- while (*ptr) ptr++;
+ while (*p) p++;
+ g->ptr = p - g->s;
}
if (i < recipients_count)
{
- (void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+ string_format(p, LOG_BUFFER_SIZE - g->ptr,
" ...\n");
- while (*ptr) ptr++;
+ while (*p) p++;
+ g->ptr = p - g->s;
}
}
/* A header with a NULL text is an unfilled in Received: header */
- for (h = header_list; h != NULL; h = h->next)
+ for (h = header_list; h; h = h->next) if (h->text)
{
- BOOL fitted;
- if (h->text == NULL) continue;
- fitted = string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
+ BOOL fitted = string_format(p, LOG_BUFFER_SIZE - g->ptr,
"%c %s", h->type, h->text);
- while(*ptr) ptr++;
+ while (*p) p++;
+ g->ptr = p - g->s;
if (!fitted) /* Buffer is full; truncate */
{
- ptr -= 100; /* For message and separator */
- if (ptr[-1] == '\n') ptr--;
- Ustrcpy(ptr, "\n*** truncated ***\n");
- while (*ptr) ptr++;
+ g->ptr -= 100; /* For message and separator */
+ if (g->s[g->ptr-1] == '\n') g->ptr--;
+ g = string_cat(g, US"\n*** truncated ***\n");
break;
}
}
-
- length = ptr - log_buffer;
}
/* Write to syslog or to a log file */
- if ((logging_mode & LOG_MODE_SYSLOG) != 0 &&
- (syslog_duplication || (flags & LOG_PANIC) == 0))
- write_syslog(LOG_NOTICE, log_buffer);
+ if ( logging_mode & LOG_MODE_SYSLOG
+ && (syslog_duplication || !(flags & LOG_PANIC)))
+ write_syslog(LOG_NOTICE, string_from_gstring(g));
/* Check for a change to the rejectlog file name when datestamping is in
operation. This happens at midnight, at which point we want to roll over
the file. Closing it has the desired effect. */
- if ((logging_mode & LOG_MODE_FILE) != 0)
+ if (logging_mode & LOG_MODE_FILE)
{
struct stat statbuf;
- if (rejectlog_datestamp != NULL)
+ if (rejectlog_datestamp)
{
uschar *nowstamp = tod_stamp(string_datestamp_type);
if (Ustrncmp (rejectlog_datestamp, nowstamp, Ustrlen(nowstamp)) != 0)
happening. */
if (rejectlogfd >= 0)
- {
if (Ustat(rejectlog_name, &statbuf) < 0 ||
statbuf.st_ino != rejectlog_inode)
{
rejectlogfd = -1;
rejectlog_inode = 0;
}
- }
/* Open the file if necessary, and write the data */
if (fstat(rejectlogfd, &statbuf) >= 0) rejectlog_inode = statbuf.st_ino;
}
- written_len = write_to_fd_buf(rejectlogfd, log_buffer, length);
- if (written_len != length)
+ written_len = write_to_fd_buf(rejectlogfd, g->s, g->ptr);
+ if (written_len != g->ptr)
{
- log_write_failed(US"reject log", length, written_len);
+ log_write_failed(US"reject log", g->ptr, written_len);
/* That function does not return */
}
}
attempt to write to the system log as a last-ditch try at telling somebody. In
all cases except mua_wrapper, try to write to log_stderr. */
-if ((flags & LOG_PANIC) != 0)
+if (flags & LOG_PANIC)
{
- if (log_stderr != NULL && log_stderr != debug_file && !mua_wrapper)
- fprintf(log_stderr, "%s", CS log_buffer);
+ if (log_stderr && log_stderr != debug_file && !mua_wrapper)
+ fprintf(log_stderr, "%s", CS string_from_gstring(g));
- if ((logging_mode & LOG_MODE_SYSLOG) != 0)
- {
+ if (logging_mode & LOG_MODE_SYSLOG)
write_syslog(LOG_ALERT, log_buffer);
- }
/* If this panic logging was caused by a failure to open the main log,
the original log line is in panic_save_buffer. Make an attempt to write it. */
- if ((logging_mode & LOG_MODE_FILE) != 0)
+ if (logging_mode & LOG_MODE_FILE)
{
panic_recurseflag = TRUE;
open_log(&paniclogfd, lt_panic, NULL); /* Won't return on failure */
panic_recurseflag = FALSE;
- if (panic_save_buffer != NULL)
+ if (panic_save_buffer)
{
int i = write(paniclogfd, panic_save_buffer, Ustrlen(panic_save_buffer));
i = i; /* compiler quietening */
}
- written_len = write_to_fd_buf(paniclogfd, log_buffer, length);
- if (written_len != length)
+ written_len = write_to_fd_buf(paniclogfd, g->s, g->ptr);
+ if (written_len != g->ptr)
{
int save_errno = errno;
write_syslog(LOG_CRIT, log_buffer);
sprintf(CS log_buffer, "write failed on panic log: length=%d result=%d "
- "errno=%d (%s)", length, (int)written_len, save_errno, strerror(save_errno));
- write_syslog(LOG_CRIT, log_buffer);
+ "errno=%d (%s)", g->ptr, (int)written_len, save_errno, strerror(save_errno));
+ write_syslog(LOG_CRIT, string_from_gstring(g));
flags |= LOG_PANIC_DIE;
}
{
uschar packbuf[8];
- if (lseek(cdbp->fileno, (off_t) cur_offset,SEEK_SET) == -1) return DEFER;
- if (cdb_bread(cdbp->fileno, packbuf,8) == -1) return DEFER;
+ if (lseek(cdbp->fileno, (off_t) cur_offset, SEEK_SET) == -1) return DEFER;
+ if (cdb_bread(cdbp->fileno, packbuf, 8) == -1) return DEFER;
item_hash = cdb_unpack(packbuf);
item_posn = cdb_unpack(packbuf + 4);
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
static void *
dbmdb_open(uschar *filename, uschar **errmsg)
{
+uschar * dirname = string_copy(filename);
+uschar * s;
EXIM_DB *yield = NULL;
-EXIM_DBOPEN(filename, O_RDONLY, 0, &yield);
+
+if ((s = Ustrrchr(dirname, '/'))) *s = '\0';
+EXIM_DBOPEN(filename, dirname, O_RDONLY, 0, &yield);
if (yield == NULL)
{
int save_errno = errno;
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
header files. */
#ifndef T_TXT
-#define T_TXT 16
+# define T_TXT 16
#endif
/* Many systems do not have T_SPF. */
#ifndef T_SPF
-#define T_SPF 99
+# define T_SPF 99
#endif
/* New TLSA record for DANE */
#ifndef T_TLSA
-#define T_TLSA 52
+# define T_TLSA 52
#endif
/* Table of recognized DNS record types and their integer values. */
uschar **result, uschar **errmsg, uint *do_cache)
{
int rc;
-int size = 256;
-int ptr = 0;
int sep = 0;
int defer_mode = PASS;
int dnssec_mode = OK;
const uschar *outsep2 = NULL;
uschar *equals, *domain, *found;
-/* Because we're the working in the search pool, we try to reclaim as much
+/* Because we're working in the search pool, we try to reclaim as much
store as possible later, so we preallocate the result here */
-uschar *yield = store_get(size);
+gstring * yield = string_get(256);
-dns_record *rr;
+dns_record * rr;
dns_answer dnsa;
dns_scan dnss;
/* Search the returned records */
- for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
- rr != NULL;
- rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); rr;
+ rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == searchtype)
{
- if (rr->type != searchtype) continue;
-
if (*do_cache > rr->ttl)
*do_cache = rr->ttl;
dns_address *da;
for (da = dns_address_from_rr(&dnsa, rr); da; da = da->next)
{
- if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1);
- yield = string_cat(yield, &size, &ptr, da->address);
+ if (yield->ptr) yield = string_catn(yield, outsep, 1);
+ yield = string_cat(yield, da->address);
}
continue;
}
/* Other kinds of record just have one piece of data each, but there may be
several of them, of course. */
- if (ptr != 0) yield = string_catn(yield, &size, &ptr, outsep, 1);
+ if (yield->ptr) yield = string_catn(yield, outsep, 1);
if (type == T_TXT || type == T_SPF)
{
- if (outsep2 == NULL)
- {
- /* output only the first item of data */
- yield = string_catn(yield, &size, &ptr, (uschar *)(rr->data+1),
- (rr->data)[0]);
- }
+ if (outsep2 == NULL) /* output only the first item of data */
+ yield = string_catn(yield, US (rr->data+1), (rr->data)[0]);
else
{
/* output all items */
{
uschar chunk_len = (rr->data)[data_offset++];
if (outsep2[0] != '\0' && data_offset != 1)
- yield = string_catn(yield, &size, &ptr, outsep2, 1);
- yield = string_catn(yield, &size, &ptr,
- US ((rr->data)+data_offset), chunk_len);
+ yield = string_catn(yield, outsep2, 1);
+ yield = string_catn(yield, US ((rr->data)+data_offset), chunk_len);
data_offset += chunk_len;
}
}
else if (type == T_TLSA)
{
uint8_t usage, selector, matching_type;
- uint16_t i, payload_length;
+ uint16_t payload_length;
uschar s[MAX_TLSA_EXPANDED_SIZE];
uschar * sp = s;
uschar * p = US rr->data;
sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2,
selector, *outsep2, matching_type, *outsep2);
/* Now append the cert/identifier, one hex char at a time */
- for (i=0;
- i < payload_length && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4);
- i++)
- sp += sprintf(CS sp, "%02x", (unsigned char)p[i]);
+ while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4))
+ sp += sprintf(CS sp, "%02x", *p++);
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
}
else /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */
{
case T_MX:
GETSHORT(priority, p);
sprintf(CS s, "%d%c", priority, *outsep2);
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
break;
case T_SRV:
GETSHORT(port, p);
sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2,
weight, *outsep2, port, *outsep2);
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
break;
case T_CSA:
}
s[1] = ' ';
- yield = string_catn(yield, &size, &ptr, s, 2);
+ yield = string_catn(yield, s, 2);
break;
default:
"domain=%s", dns_text_type(type), domain);
break;
}
- else yield = string_cat(yield, &size, &ptr, s);
+ else yield = string_cat(yield, s);
if (type == T_SOA && outsep2 != NULL)
{
unsigned long serial, refresh, retry, expire, minimum;
p += rc;
- yield = string_catn(yield, &size, &ptr, outsep2, 1);
+ yield = string_catn(yield, outsep2, 1);
rc = dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
(DN_EXPAND_ARG4_TYPE)s, sizeof(s));
"domain=%s", dns_text_type(type), domain);
break;
}
- else yield = string_cat(yield, &size, &ptr, s);
+ else yield = string_cat(yield, s);
p += rc;
GETLONG(serial, p); GETLONG(refresh, p);
sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu",
*outsep2, serial, *outsep2, refresh,
*outsep2, retry, *outsep2, expire, *outsep2, minimum);
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
}
}
} /* Loop for list of returned records */
/* Reclaim unused memory */
-store_reset(yield + ptr + 1);
+store_reset(yield->s + yield->ptr + 1);
-/* If ptr == 0 we have not found anything. Otherwise, insert the terminating
+/* If yield NULL we have not found anything. Otherwise, insert the terminating
zero and return the result. */
dns_retrans = save_retrans;
dns_retry = save_retry;
dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */
-if (ptr == 0) return failrc;
-yield[ptr] = 0;
-*result = yield;
+if (!yield || !yield->ptr) return failrc;
+
+*result = string_from_gstring(yield);
return OK;
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* The code in this module was contributed by Ard Biesheuvel. */
perform_ibase_search(uschar * query, uschar * server, uschar ** resultptr,
uschar ** errmsg, BOOL * defer_break)
{
- isc_stmt_handle stmth = NULL;
- XSQLDA *out_sqlda;
- XSQLVAR *var;
-
- char buffer[256];
- ISC_STATUS status[20], *statusp = status;
-
- int i;
- int ssize = 0;
- int offset = 0;
- int yield = DEFER;
- uschar *result = NULL;
- ibase_connection *cn;
- uschar *server_copy = NULL;
- uschar *sdata[3];
+isc_stmt_handle stmth = NULL;
+XSQLDA *out_sqlda;
+XSQLVAR *var;
+
+char buffer[256];
+ISC_STATUS status[20], *statusp = status;
+
+gstring * result;
+int i;
+int yield = DEFER;
+ibase_connection *cn;
+uschar *server_copy = NULL;
+uschar *sdata[3];
/* Disaggregate the parameters from the server argument. The order is host,
database, user, password. We can write to the string, since it is in a
nextinlist temporary buffer. The copy of the string that is used for caching
has the password removed. This copy is also used for debugging output. */
- for (i = 2; i > 0; i--) {
- uschar *pp = Ustrrchr(server, '|');
- if (pp == NULL) {
- *errmsg =
- string_sprintf("incomplete Interbase server data: %s",
- (i == 3) ? server : server_copy);
- *defer_break = TRUE;
- return DEFER;
- }
- *pp++ = 0;
- sdata[i] = pp;
- if (i == 2)
- server_copy = string_copy(server); /* sans password */
+for (i = 2; i > 0; i--)
+ {
+ uschar *pp = Ustrrchr(server, '|');
+
+ if (pp == NULL)
+ {
+ *errmsg = string_sprintf("incomplete Interbase server data: %s",
+ (i == 3) ? server : server_copy);
+ *defer_break = TRUE;
+ return DEFER;
}
- sdata[0] = server; /* What's left at the start */
+ *pp++ = 0;
+ sdata[i] = pp;
+ if (i == 2)
+ server_copy = string_copy(server); /* sans password */
+ }
+sdata[0] = server; /* What's left at the start */
/* See if we have a cached connection to the server */
- for (cn = ibase_connections; cn != NULL; cn = cn->next) {
- if (Ustrcmp(cn->server, server_copy) == 0) {
- break;
- }
- }
+for (cn = ibase_connections; cn != NULL; cn = cn->next)
+ if (Ustrcmp(cn->server, server_copy) == 0)
+ break;
/* Use a previously cached connection ? */
- if (cn != NULL) {
- static char db_info_options[] = { isc_info_base_level };
-
- /* test if the connection is alive */
- if (isc_database_info
- (status, &cn->dbh, sizeof(db_info_options), db_info_options,
- sizeof(buffer), buffer)) {
- /* error occurred: assume connection is down */
- DEBUG(D_lookup)
- debug_printf
- ("Interbase cleaning up cached connection: %s\n",
- cn->server);
- isc_detach_database(status, &cn->dbh);
- } else {
- DEBUG(D_lookup)
- debug_printf("Interbase using cached connection for %s\n",
- server_copy);
- }
- } else {
- cn = store_get(sizeof(ibase_connection));
- cn->server = server_copy;
- cn->dbh = NULL;
- cn->transh = NULL;
- cn->next = ibase_connections;
- ibase_connections = cn;
+if (cn)
+ {
+ static char db_info_options[] = { isc_info_base_level };
+
+ /* test if the connection is alive */
+ if (isc_database_info(status, &cn->dbh, sizeof(db_info_options),
+ db_info_options, sizeof(buffer), buffer))
+ {
+ /* error occurred: assume connection is down */
+ DEBUG(D_lookup)
+ debug_printf
+ ("Interbase cleaning up cached connection: %s\n",
+ cn->server);
+ isc_detach_database(status, &cn->dbh);
}
+ else
+ {
+ DEBUG(D_lookup) debug_printf("Interbase using cached connection for %s\n",
+ server_copy);
+ }
+ }
+else
+ {
+ cn = store_get(sizeof(ibase_connection));
+ cn->server = server_copy;
+ cn->dbh = NULL;
+ cn->transh = NULL;
+ cn->next = ibase_connections;
+ ibase_connections = cn;
+ }
/* If no cached connection, we must set one up. */
- if (cn->dbh == NULL || cn->transh == NULL) {
-
- char *dpb, *p;
- short dpb_length;
- static char trans_options[] =
- { isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed,
- isc_tpb_rec_version
- };
-
- /* Construct the database parameter buffer. */
- dpb = buffer;
- *dpb++ = isc_dpb_version1;
- *dpb++ = isc_dpb_user_name;
- *dpb++ = strlen(sdata[1]);
- for (p = sdata[1]; *p;)
- *dpb++ = *p++;
- *dpb++ = isc_dpb_password;
- *dpb++ = strlen(sdata[2]);
- for (p = sdata[2]; *p;)
- *dpb++ = *p++;
- dpb_length = dpb - buffer;
-
- DEBUG(D_lookup)
- debug_printf("new Interbase connection: database=%s user=%s\n",
- sdata[0], sdata[1]);
-
- /* Connect to the database */
- if (isc_attach_database
- (status, 0, sdata[0], &cn->dbh, dpb_length, buffer)) {
- isc_interprete(buffer, &statusp);
- *errmsg =
- string_sprintf("Interbase attach() failed: %s", buffer);
- *defer_break = FALSE;
- goto IBASE_EXIT;
- }
-
- /* Now start a read-only read-committed transaction */
- if (isc_start_transaction
- (status, &cn->transh, 1, &cn->dbh, sizeof(trans_options),
- trans_options)) {
- isc_interprete(buffer, &statusp);
- isc_detach_database(status, &cn->dbh);
- *errmsg =
- string_sprintf("Interbase start_transaction() failed: %s",
- buffer);
- *defer_break = FALSE;
- goto IBASE_EXIT;
- }
+if (cn->dbh == NULL || cn->transh == NULL)
+ {
+ char *dpb, *p;
+ short dpb_length;
+ static char trans_options[] =
+ { isc_tpb_version3, isc_tpb_read, isc_tpb_read_committed,
+ isc_tpb_rec_version
+ };
+
+ /* Construct the database parameter buffer. */
+ dpb = buffer;
+ *dpb++ = isc_dpb_version1;
+ *dpb++ = isc_dpb_user_name;
+ *dpb++ = strlen(sdata[1]);
+ for (p = sdata[1]; *p;)
+ *dpb++ = *p++;
+ *dpb++ = isc_dpb_password;
+ *dpb++ = strlen(sdata[2]);
+ for (p = sdata[2]; *p;)
+ *dpb++ = *p++;
+ dpb_length = dpb - buffer;
+
+ DEBUG(D_lookup)
+ debug_printf("new Interbase connection: database=%s user=%s\n",
+ sdata[0], sdata[1]);
+
+ /* Connect to the database */
+ if (isc_attach_database
+ (status, 0, sdata[0], &cn->dbh, dpb_length, buffer))
+ {
+ isc_interprete(buffer, &statusp);
+ *errmsg =
+ string_sprintf("Interbase attach() failed: %s", buffer);
+ *defer_break = FALSE;
+ goto IBASE_EXIT;
}
-/* Run the query */
- if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth)) {
- isc_interprete(buffer, &statusp);
- *errmsg =
- string_sprintf("Interbase alloc_statement() failed: %s",
- buffer);
- *defer_break = FALSE;
- goto IBASE_EXIT;
+ /* Now start a read-only read-committed transaction */
+ if (isc_start_transaction
+ (status, &cn->transh, 1, &cn->dbh, sizeof(trans_options),
+ trans_options))
+ {
+ isc_interprete(buffer, &statusp);
+ isc_detach_database(status, &cn->dbh);
+ *errmsg =
+ string_sprintf("Interbase start_transaction() failed: %s",
+ buffer);
+ *defer_break = FALSE;
+ goto IBASE_EXIT;
}
+ }
- out_sqlda = store_get(XSQLDA_LENGTH(1));
- out_sqlda->version = SQLDA_VERSION1;
- out_sqlda->sqln = 1;
-
- if (isc_dsql_prepare
- (status, &cn->transh, &stmth, 0, query, 1, out_sqlda)) {
- isc_interprete(buffer, &statusp);
- store_reset(out_sqlda);
- out_sqlda = NULL;
- *errmsg =
- string_sprintf("Interbase prepare_statement() failed: %s",
- buffer);
- *defer_break = FALSE;
- goto IBASE_EXIT;
- }
+/* Run the query */
+if (isc_dsql_allocate_statement(status, &cn->dbh, &stmth))
+ {
+ isc_interprete(buffer, &statusp);
+ *errmsg =
+ string_sprintf("Interbase alloc_statement() failed: %s",
+ buffer);
+ *defer_break = FALSE;
+ goto IBASE_EXIT;
+ }
+
+out_sqlda = store_get(XSQLDA_LENGTH(1));
+out_sqlda->version = SQLDA_VERSION1;
+out_sqlda->sqln = 1;
+
+if (isc_dsql_prepare
+ (status, &cn->transh, &stmth, 0, query, 1, out_sqlda))
+ {
+ isc_interprete(buffer, &statusp);
+ store_reset(out_sqlda);
+ out_sqlda = NULL;
+ *errmsg =
+ string_sprintf("Interbase prepare_statement() failed: %s",
+ buffer);
+ *defer_break = FALSE;
+ goto IBASE_EXIT;
+ }
/* re-allocate the output structure if there's more than one field */
- if (out_sqlda->sqln < out_sqlda->sqld) {
- XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld));
- if (isc_dsql_describe
- (status, &stmth, out_sqlda->version, new_sqlda)) {
- isc_interprete(buffer, &statusp);
- isc_dsql_free_statement(status, &stmth, DSQL_drop);
- store_reset(out_sqlda);
- out_sqlda = NULL;
- *errmsg =
- string_sprintf("Interbase describe_statement() failed: %s",
- buffer);
- *defer_break = FALSE;
- goto IBASE_EXIT;
- }
- out_sqlda = new_sqlda;
+if (out_sqlda->sqln < out_sqlda->sqld)
+ {
+ XSQLDA *new_sqlda = store_get(XSQLDA_LENGTH(out_sqlda->sqld));
+ if (isc_dsql_describe
+ (status, &stmth, out_sqlda->version, new_sqlda))
+ {
+ isc_interprete(buffer, &statusp);
+ isc_dsql_free_statement(status, &stmth, DSQL_drop);
+ store_reset(out_sqlda);
+ out_sqlda = NULL;
+ *errmsg = string_sprintf("Interbase describe_statement() failed: %s",
+ buffer);
+ *defer_break = FALSE;
+ goto IBASE_EXIT;
}
+ out_sqlda = new_sqlda;
+ }
/* allocate storage for every returned field */
- for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++) {
- switch (var->sqltype & ~1) {
- case SQL_VARYING:
- var->sqldata =
- (char *) store_get(sizeof(char) * var->sqllen + 2);
- break;
- case SQL_TEXT:
- var->sqldata =
- (char *) store_get(sizeof(char) * var->sqllen);
- break;
- case SQL_SHORT:
- var->sqldata = (char *) store_get(sizeof(short));
- break;
- case SQL_LONG:
- var->sqldata = (char *) store_get(sizeof(ISC_LONG));
- break;
+for (i = 0, var = out_sqlda->sqlvar; i < out_sqlda->sqld; i++, var++)
+ {
+ switch (var->sqltype & ~1)
+ {
+ case SQL_VARYING:
+ var->sqldata = CS store_get(sizeof(char) * var->sqllen + 2);
+ break;
+ case SQL_TEXT:
+ var->sqldata = CS store_get(sizeof(char) * var->sqllen);
+ break;
+ case SQL_SHORT:
+ var->sqldata = CS store_get(sizeof(short));
+ break;
+ case SQL_LONG:
+ var->sqldata = CS store_get(sizeof(ISC_LONG));
+ break;
#ifdef SQL_INT64
- case SQL_INT64:
- var->sqldata = (char *) store_get(sizeof(ISC_INT64));
- break;
+ case SQL_INT64:
+ var->sqldata = CS store_get(sizeof(ISC_INT64));
+ break;
#endif
- case SQL_FLOAT:
- var->sqldata = (char *) store_get(sizeof(float));
- break;
- case SQL_DOUBLE:
- var->sqldata = (char *) store_get(sizeof(double));
- break;
+ case SQL_FLOAT:
+ var->sqldata = CS store_get(sizeof(float));
+ break;
+ case SQL_DOUBLE:
+ var->sqldata = CS store_get(sizeof(double));
+ break;
#ifdef SQL_TIMESTAMP
- case SQL_DATE:
- var->sqldata = (char *) store_get(sizeof(ISC_QUAD));
- break;
+ case SQL_DATE:
+ var->sqldata = CS store_get(sizeof(ISC_QUAD));
+ break;
#else
- case SQL_TIMESTAMP:
- var->sqldata = (char *) store_get(sizeof(ISC_TIMESTAMP));
- break;
- case SQL_TYPE_DATE:
- var->sqldata = (char *) store_get(sizeof(ISC_DATE));
- break;
- case SQL_TYPE_TIME:
- var->sqldata = (char *) store_get(sizeof(ISC_TIME));
- break;
-#endif
- }
- if (var->sqltype & 1) {
- var->sqlind = (short *) store_get(sizeof(short));
- }
+ case SQL_TIMESTAMP:
+ var->sqldata = CS store_get(sizeof(ISC_TIMESTAMP));
+ break;
+ case SQL_TYPE_DATE:
+ var->sqldata = CS store_get(sizeof(ISC_DATE));
+ break;
+ case SQL_TYPE_TIME:
+ var->sqldata = CS store_get(sizeof(ISC_TIME));
+ break;
+ #endif
}
-
- /* finally, we're ready to execute the statement */
- if (isc_dsql_execute
- (status, &cn->transh, &stmth, out_sqlda->version, NULL)) {
- isc_interprete(buffer, &statusp);
- *errmsg =
- string_sprintf("Interbase describe_statement() failed: %s",
- buffer);
- isc_dsql_free_statement(status, &stmth, DSQL_drop);
- *defer_break = FALSE;
- goto IBASE_EXIT;
+ if (var->sqltype & 1)
+ var->sqlind = (short *) store_get(sizeof(short));
+ }
+
+/* finally, we're ready to execute the statement */
+if (isc_dsql_execute
+ (status, &cn->transh, &stmth, out_sqlda->version, NULL))
+ {
+ isc_interprete(buffer, &statusp);
+ *errmsg = string_sprintf("Interbase describe_statement() failed: %s",
+ buffer);
+ isc_dsql_free_statement(status, &stmth, DSQL_drop);
+ *defer_break = FALSE;
+ goto IBASE_EXIT;
+ }
+
+while (isc_dsql_fetch(status, &stmth, out_sqlda->version, out_sqlda) != 100L)
+ {
+ /* check if an error occurred */
+ if (status[0] & status[1])
+ {
+ isc_interprete(buffer, &statusp);
+ *errmsg =
+ string_sprintf("Interbase fetch() failed: %s", buffer);
+ isc_dsql_free_statement(status, &stmth, DSQL_drop);
+ *defer_break = FALSE;
+ goto IBASE_EXIT;
}
- while (isc_dsql_fetch(status, &stmth, out_sqlda->version, out_sqlda) !=
- 100L) {
- /* check if an error occurred */
- if (status[0] & status[1]) {
- isc_interprete(buffer, &statusp);
- *errmsg =
- string_sprintf("Interbase fetch() failed: %s", buffer);
- isc_dsql_free_statement(status, &stmth, DSQL_drop);
- *defer_break = FALSE;
- goto IBASE_EXIT;
- }
-
- if (result != NULL)
- result = string_catn(result, &ssize, &offset, US "\n", 1);
-
- /* Find the number of fields returned. If this is one, we don't add field
- names to the data. Otherwise we do. */
- if (out_sqlda->sqld == 1) {
- if (out_sqlda->sqlvar[0].sqlind == NULL || *out_sqlda->sqlvar[0].sqlind != -1) /* NULL value yields nothing */
- result =
- string_catn(result, &ssize, &offset, US buffer,
- fetch_field(buffer, sizeof(buffer),
- &out_sqlda->sqlvar[0]));
- }
-
- else
- for (i = 0; i < out_sqlda->sqld; i++) {
- int len = fetch_field(buffer, sizeof(buffer),
- &out_sqlda->sqlvar[i]);
-
- result =
- string_cat(result, &ssize, &offset,
- US out_sqlda->sqlvar[i].aliasname,
- out_sqlda->sqlvar[i].aliasname_length);
- result = string_catn(result, &ssize, &offset, US "=", 1);
-
- /* Quote the value if it contains spaces or is empty */
-
- if (*out_sqlda->sqlvar[i].sqlind == -1) { /* NULL value */
- result =
- string_catn(result, &ssize, &offset, US "\"\"", 2);
- }
-
- else if (buffer[0] == 0 || Ustrchr(buffer, ' ') != NULL) {
- int j;
- result =
- string_catn(result, &ssize, &offset, US "\"", 1);
- for (j = 0; j < len; j++) {
- if (buffer[j] == '\"' || buffer[j] == '\\')
- result =
- string_cat(result, &ssize, &offset,
- US "\\", 1);
- result =
- string_cat(result, &ssize, &offset,
- US buffer + j, 1);
- }
- result =
- string_catn(result, &ssize, &offset, US "\"", 1);
- } else {
- result =
- string_catn(result, &ssize, &offset, US buffer, len);
- }
- result = string_catn(result, &ssize, &offset, US " ", 1);
- }
+ if (result)
+ result = string_catn(result, US "\n", 1);
+
+ /* Find the number of fields returned. If this is one, we don't add field
+ names to the data. Otherwise we do. */
+ if (out_sqlda->sqld == 1)
+ {
+ if (out_sqlda->sqlvar[0].sqlind == NULL || *out_sqlda->sqlvar[0].sqlind != -1) /* NULL value yields nothing */
+ result = string_catn(result, US buffer,
+ fetch_field(buffer, sizeof(buffer),
+ &out_sqlda->sqlvar[0]));
}
+ else
+ for (i = 0; i < out_sqlda->sqld; i++)
+ {
+ int len = fetch_field(buffer, sizeof(buffer), &out_sqlda->sqlvar[i]);
+
+ result = string_catn(result, US out_sqlda->sqlvar[i].aliasname,
+ out_sqlda->sqlvar[i].aliasname_length);
+ result = string_catn(result, US "=", 1);
+
+ /* Quote the value if it contains spaces or is empty */
+
+ if (*out_sqlda->sqlvar[i].sqlind == -1) /* NULL value */
+ result = string_catn(result, US "\"\"", 2);
+
+ else if (buffer[0] == 0 || Ustrchr(buffer, ' ') != NULL)
+ {
+ int j;
+
+ result = string_catn(result, US "\"", 1);
+ for (j = 0; j < len; j++)
+ {
+ if (buffer[j] == '\"' || buffer[j] == '\\')
+ result = string_cat(result, US "\\", 1);
+ result = string_cat(result, US buffer + j, 1);
+ }
+ result = string_catn(result, US "\"", 1);
+ }
+ else
+ result = string_catn(result, US buffer, len);
+ result = string_catn(result, US " ", 1);
+ }
+ }
+
/* If result is NULL then no data has been found and so we return FAIL.
Otherwise, we must terminate the string which has been built; string_cat()
always leaves enough room for a terminating zero. */
- if (result == NULL) {
- yield = FAIL;
- *errmsg = US "Interbase: no data found";
- } else {
- result[offset] = 0;
- store_reset(result + offset + 1);
- }
+if (!result)
+ {
+ yield = FAIL;
+ *errmsg = US "Interbase: no data found";
+ }
+else
+ store_reset(result->s + result->ptr + 1);
/* Get here by goto from various error checks. */
- IBASE_EXIT:
+IBASE_EXIT:
- if (stmth != NULL)
- isc_dsql_free_statement(status, &stmth, DSQL_drop);
+if (stmth)
+ isc_dsql_free_statement(status, &stmth, DSQL_drop);
/* Non-NULL result indicates a successful result */
- if (result != NULL) {
- *resultptr = result;
- return OK;
- } else {
- DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
- return yield; /* FAIL or DEFER */
- }
+if (result)
+ {
+ *resultptr = string_from_gstring(result);
+ return OK;
+ }
+else
+ {
+ DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
+ return yield; /* FAIL or DEFER */
+ }
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Many thanks to Stuart Lynne for contributing the original code for this
uschar *attr;
uschar **attrp;
-uschar *data = NULL;
+gstring * data = NULL;
uschar *dn = NULL;
uschar *host;
uschar **values;
int msgid;
int rc, ldap_rc, ldap_parse_rc;
int port;
-int ptr = 0;
int rescount = 0;
-int size = 0;
BOOL attribute_found = FALSE;
BOOL ldapi = FALSE;
DEBUG(D_lookup)
debug_printf("perform_ldap_search: ldap%s URL = \"%s\" server=%s port=%d "
"sizelimit=%d timelimit=%d tcplimit=%d\n",
- (search_type == SEARCH_LDAP_MULTIPLE)? "m" :
- (search_type == SEARCH_LDAP_DN)? "dn" :
- (search_type == SEARCH_LDAP_AUTH)? "auth" : "",
+ search_type == SEARCH_LDAP_MULTIPLE ? "m" :
+ search_type == SEARCH_LDAP_DN ? "dn" :
+ search_type == SEARCH_LDAP_AUTH ? "auth" : "",
ldap_url, server, s_port, sizelimit, timelimit, tcplimit);
/* Check if LDAP thinks the URL is a valid LDAP URL. We assume that if the LDAP
expects NULL later in ldap_init() to mean "default", annoyingly. In OpenLDAP
2.0.11 this has changed (it uses NULL). */
-if ((ludp->lud_host == NULL || ludp->lud_host[0] == 0) && server != NULL)
+if ((!ludp->lud_host || !ludp->lud_host[0]) && server)
{
host = server;
port = s_port;
else
{
host = US ludp->lud_host;
- if (host != NULL && host[0] == 0) host = NULL;
+ if (host && !host[0]) host = NULL;
port = ludp->lud_port;
}
error, except in the default case. (But lud_scheme doesn't seem to exist in
older libraries.) */
-if (host != NULL)
+if (host)
{
if ((host[0] == '/' || Ustrncmp(host, "%2F", 3) == 0))
{
porttext[0] = 0; /* Remove port from messages */
}
- #if defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_OPENLDAP2
else if (strncmp(ludp->lud_scheme, "ldapi", 5) == 0)
{
*errmsg = string_sprintf("ldapi requires an absolute path (\"%s\" given)",
host);
goto RETURN_ERROR;
}
- #endif
+#endif
}
/* Count the attributes; we need this later to tell us how to format results */
-for (attrp = USS ludp->lud_attrs; attrp != NULL && *attrp != NULL; attrp++)
+for (attrp = USS ludp->lud_attrs; attrp && *attrp; attrp++)
attrs_requested++;
/* See if we can find a cached connection to this host. The port is not
(implying the library default), rather than to the empty string. Note that in
this case, there is no difference between ldap and ldapi. */
-for (lcp = ldap_connections; lcp != NULL; lcp = lcp->next)
+for (lcp = ldap_connections; lcp; lcp = lcp->next)
{
if ((host == NULL) != (lcp->host == NULL) ||
(host != NULL && strcmpic(lcp->host, host) != 0))
requests connection via a Unix socket. However, as far as I know, only OpenLDAP
supports the use of sockets, and the use of ldap_initialize(). */
-if (lcp == NULL)
+if (!lcp)
{
LDAP *ld;
- #ifdef LDAP_OPT_X_TLS_NEWCTX
+#ifdef LDAP_OPT_X_TLS_NEWCTX
int am_server = 0;
LDAP *ldsetctx;
- #else
+#else
LDAP *ldsetctx = NULL;
- #endif
+#endif
/* --------------------------- OpenLDAP ------------------------ */
non-existent). So we handle OpenLDAP differently here. Also, support for
ldapi seems to be OpenLDAP-only at present. */
- #ifdef LDAP_LIB_OPENLDAP2
+#ifdef LDAP_LIB_OPENLDAP2
/* We now need an empty string for the default host. Get some store in which
to build a URL for ldap_initialize(). In the ldapi case, it can't be bigger
int ch;
init_ptr = init_url + 8;
Ustrcpy(init_url, "ldapi://");
- while ((ch = *shost++) != 0)
- {
+ while ((ch = *shost++))
if (ch == '/')
- {
- Ustrncpy(init_ptr, "%2F", 3);
- init_ptr += 3;
- }
- else *init_ptr++ = ch;
- }
+ { Ustrncpy(init_ptr, "%2F", 3); init_ptr += 3; }
+ else
+ *init_ptr++ = ch;
*init_ptr = 0;
}
/* Call ldap_initialize() and check the result */
DEBUG(D_lookup) debug_printf("ldap_initialize with URL %s\n", init_url);
- rc = ldap_initialize(&ld, CS init_url);
- if (rc != LDAP_SUCCESS)
+ if ((rc = ldap_initialize(&ld, CS init_url)) != LDAP_SUCCESS)
{
*errmsg = string_sprintf("ldap_initialize: (error %d) URL \"%s\"\n",
rc, init_url);
/* For libraries other than OpenLDAP, use ldap_init(). */
- #else /* LDAP_LIB_OPENLDAP2 */
+#else /* LDAP_LIB_OPENLDAP2 */
ld = ldap_init(CS host, port);
- #endif /* LDAP_LIB_OPENLDAP2 */
+#endif /* LDAP_LIB_OPENLDAP2 */
/* -------------------------------------------------------------- */
/* Handle failure to initialize */
- if (ld == NULL)
+ if (!ld)
{
*errmsg = string_sprintf("failed to initialize for LDAP server %s%s - %s",
host, porttext, strerror(errno));
goto RETURN_ERROR;
}
- #ifdef LDAP_OPT_X_TLS_NEWCTX
+#ifdef LDAP_OPT_X_TLS_NEWCTX
ldsetctx = ld;
- #endif
+#endif
/* Set the TCP connect time limit if available. This is something that is
in Netscape SDK v4.1; I don't know about other libraries. */
- #ifdef LDAP_X_OPT_CONNECT_TIMEOUT
+#ifdef LDAP_X_OPT_CONNECT_TIMEOUT
if (tcplimit > 0)
{
int timeout1000 = tcplimit*1000;
int notimeout = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, (void *)¬imeout);
}
- #endif
+#endif
/* Set the TCP connect timeout. This works with OpenLDAP 2.2.14. */
- #ifdef LDAP_OPT_NETWORK_TIMEOUT
+#ifdef LDAP_OPT_NETWORK_TIMEOUT
if (tcplimit > 0)
ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, (void *)timeoutptr);
- #endif
+#endif
/* I could not get TLS to work until I set the version to 3. That version
seems to be the default nowadays. The RFC is dated 1997, so I would hope
if (eldap_version < 0)
{
- #ifdef LDAP_VERSION3
+#ifdef LDAP_VERSION3
eldap_version = LDAP_VERSION3;
- #else
+#else
eldap_version = 2;
- #endif
+#endif
}
- #ifdef LDAP_OPT_PROTOCOL_VERSION
+#ifdef LDAP_OPT_PROTOCOL_VERSION
ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void *)&eldap_version);
- #endif
+#endif
DEBUG(D_lookup) debug_printf("initialized for LDAP (v%d) server %s%s\n",
eldap_version, host, porttext);
/* If not using ldapi and TLS is available, set appropriate TLS options: hard
for "ldaps" and soft otherwise. */
- #ifdef LDAP_OPT_X_TLS
+#ifdef LDAP_OPT_X_TLS
if (!ldapi)
{
int tls_option;
- #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
- if (eldap_require_cert != NULL)
+# ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
+ if (eldap_require_cert)
{
- tls_option = LDAP_OPT_X_TLS_NEVER;
- if (Ustrcmp(eldap_require_cert, "hard") == 0)
- {
- tls_option = LDAP_OPT_X_TLS_HARD;
- }
- else if (Ustrcmp(eldap_require_cert, "demand") == 0)
- {
- tls_option = LDAP_OPT_X_TLS_DEMAND;
- }
- else if (Ustrcmp(eldap_require_cert, "allow") == 0)
- {
- tls_option = LDAP_OPT_X_TLS_ALLOW;
- }
- else if (Ustrcmp(eldap_require_cert, "try") == 0)
- {
- tls_option = LDAP_OPT_X_TLS_TRY;
- }
+ tls_option =
+ Ustrcmp(eldap_require_cert, "hard") == 0 ? LDAP_OPT_X_TLS_HARD
+ : Ustrcmp(eldap_require_cert, "demand") == 0 ? LDAP_OPT_X_TLS_DEMAND
+ : Ustrcmp(eldap_require_cert, "allow") == 0 ? LDAP_OPT_X_TLS_ALLOW
+ : Ustrcmp(eldap_require_cert, "try") == 0 ? LDAP_OPT_X_TLS_TRY
+ : LDAP_OPT_X_TLS_NEVER;
+
DEBUG(D_lookup)
debug_printf("Require certificate overrides LDAP_OPT_X_TLS option (%d)\n",
tls_option);
}
else
- #endif /* LDAP_OPT_X_TLS_REQUIRE_CERT */
+# endif /* LDAP_OPT_X_TLS_REQUIRE_CERT */
if (strncmp(ludp->lud_scheme, "ldaps", 5) == 0)
{
tls_option = LDAP_OPT_X_TLS_HARD;
}
ldap_set_option(ld, LDAP_OPT_X_TLS, (void *)&tls_option);
}
- #endif /* LDAP_OPT_X_TLS */
+#endif /* LDAP_OPT_X_TLS */
- #ifdef LDAP_OPT_X_TLS_CACERTFILE
- if (eldap_ca_cert_file != NULL)
- {
+#ifdef LDAP_OPT_X_TLS_CACERTFILE
+ if (eldap_ca_cert_file)
ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTFILE, eldap_ca_cert_file);
- }
- #endif
- #ifdef LDAP_OPT_X_TLS_CACERTDIR
- if (eldap_ca_cert_dir != NULL)
- {
+#endif
+#ifdef LDAP_OPT_X_TLS_CACERTDIR
+ if (eldap_ca_cert_dir)
ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CACERTDIR, eldap_ca_cert_dir);
- }
- #endif
- #ifdef LDAP_OPT_X_TLS_CERTFILE
- if (eldap_cert_file != NULL)
- {
+#endif
+#ifdef LDAP_OPT_X_TLS_CERTFILE
+ if (eldap_cert_file)
ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CERTFILE, eldap_cert_file);
- }
- #endif
- #ifdef LDAP_OPT_X_TLS_KEYFILE
- if (eldap_cert_key != NULL)
- {
+#endif
+#ifdef LDAP_OPT_X_TLS_KEYFILE
+ if (eldap_cert_key)
ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_KEYFILE, eldap_cert_key);
- }
- #endif
- #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE
- if (eldap_cipher_suite != NULL)
- {
+#endif
+#ifdef LDAP_OPT_X_TLS_CIPHER_SUITE
+ if (eldap_cipher_suite)
ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_CIPHER_SUITE, eldap_cipher_suite);
- }
- #endif
- #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
- if (eldap_require_cert != NULL)
+#endif
+#ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
+ if (eldap_require_cert)
{
- int cert_option = LDAP_OPT_X_TLS_NEVER;
- if (Ustrcmp(eldap_require_cert, "hard") == 0)
- {
- cert_option = LDAP_OPT_X_TLS_HARD;
- }
- else if (Ustrcmp(eldap_require_cert, "demand") == 0)
- {
- cert_option = LDAP_OPT_X_TLS_DEMAND;
- }
- else if (Ustrcmp(eldap_require_cert, "allow") == 0)
- {
- cert_option = LDAP_OPT_X_TLS_ALLOW;
- }
- else if (Ustrcmp(eldap_require_cert, "try") == 0)
- {
- cert_option = LDAP_OPT_X_TLS_TRY;
- }
+ int cert_option =
+ Ustrcmp(eldap_require_cert, "hard") == 0 ? LDAP_OPT_X_TLS_HARD
+ : Ustrcmp(eldap_require_cert, "demand") == 0 ? LDAP_OPT_X_TLS_DEMAND
+ : Ustrcmp(eldap_require_cert, "allow") == 0 ? LDAP_OPT_X_TLS_ALLOW
+ : Ustrcmp(eldap_require_cert, "try") == 0 ? LDAP_OPT_X_TLS_TRY
+ : LDAP_OPT_X_TLS_NEVER;
+
/* This ldap handle is set at compile time based on client libs. Older
* versions want it to be global and newer versions can force a reload
* of the TLS context (to reload these settings we are changing from the
* default that loaded at instantiation). */
rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_REQUIRE_CERT, &cert_option);
if (rc)
- {
DEBUG(D_lookup)
debug_printf("Unable to set TLS require cert_option(%d) globally: %s\n",
cert_option, ldap_err2string(rc));
- }
}
- #endif
- #ifdef LDAP_OPT_X_TLS_NEWCTX
- rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_NEWCTX, &am_server);
- if (rc)
- {
+#endif
+#ifdef LDAP_OPT_X_TLS_NEWCTX
+ if ((rc = ldap_set_option(ldsetctx, LDAP_OPT_X_TLS_NEWCTX, &am_server)))
DEBUG(D_lookup)
debug_printf("Unable to reload TLS context %d: %s\n",
rc, ldap_err2string(rc));
- }
#endif
/* Now add this connection to the chain of cached connections */
/* Found cached connection */
else
- {
DEBUG(D_lookup)
debug_printf("re-using cached connection to LDAP server %s%s\n",
host, porttext);
- }
/* Bind with the user/password supplied, or an anonymous bind if these values
are NULL, unless a cached connection is already bound with the same values. */
-if (!lcp->bound ||
- (lcp->user == NULL && user != NULL) ||
- (lcp->user != NULL && user == NULL) ||
- (lcp->user != NULL && user != NULL && Ustrcmp(lcp->user, user) != 0) ||
- (lcp->password == NULL && password != NULL) ||
- (lcp->password != NULL && password == NULL) ||
- (lcp->password != NULL && password != NULL &&
- Ustrcmp(lcp->password, password) != 0))
+if ( !lcp->bound
+ || !lcp->user && user
+ || lcp->user && !user
+ || lcp->user && user && Ustrcmp(lcp->user, user) != 0
+ || !lcp->password && password
+ || lcp->password && !password
+ || lcp->password && password && Ustrcmp(lcp->password, password) != 0
+ )
{
DEBUG(D_lookup) debug_printf("%sbinding with user=%s password=%s\n",
- (lcp->bound)? "re-" : "", user, password);
+ lcp->bound ? "re-" : "", user, password);
+
if (eldap_start_tls && !lcp->is_start_tls_called && !ldapi)
{
#if defined(LDAP_OPT_X_TLS) && !defined(LDAP_LIB_SOLARIS)
}
lcp->is_start_tls_called = TRUE;
#else
- DEBUG(D_lookup)
- debug_printf("TLS initiation not supported with this Exim and your LDAP library.\n");
+ DEBUG(D_lookup) debug_printf("TLS initiation not supported with this Exim"
+ " and your LDAP library.\n");
#endif
}
if ((msgid = ldap_bind(lcp->ld, CS user, CS password, LDAP_AUTH_SIMPLE))
goto RETURN_ERROR;
}
- if ((rc = ldap_result( lcp->ld, msgid, 1, timeoutptr, &result )) <= 0)
+ if ((rc = ldap_result(lcp->ld, msgid, 1, timeoutptr, &result)) <= 0)
{
*errmsg = string_sprintf("failed to bind the LDAP connection to server "
"%s%s - LDAP error: %s", host, porttext,
goto RETURN_ERROR;
}
- rc = ldap_result2error( lcp->ld, result, 0 );
+ rc = ldap_result2error(lcp->ld, result, 0);
/* Invalid credentials when just checking credentials returns FAIL. This
stops any further servers being tried. */
/* Successful bind */
lcp->bound = TRUE;
- lcp->user = (user == NULL)? NULL : string_copy(user);
- lcp->password = (password == NULL)? NULL : string_copy(password);
+ lcp->user = !user ? NULL : string_copy(user);
+ lcp->password = !password ? NULL : string_copy(password);
ldap_msgfree(result);
result = NULL;
if (msgid == -1)
{
- #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
int err;
ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
*errmsg = string_sprintf("ldap_search failed: %d, %s", err,
ldap_err2string(err));
-
- #else
+#else
*errmsg = string_sprintf("ldap_search failed");
- #endif
+#endif
goto RETURN_ERROR;
}
DEBUG(D_lookup) debug_printf("LDAP result loop\n");
for(e = ldap_first_entry(lcp->ld, result), valuecount = 0;
- e != NULL;
+ e;
e = ldap_next_entry(lcp->ld, e))
{
uschar *new_dn;
/* Results for multiple entries values are separated by newlines. */
- if (data != NULL) data = string_catn(data, &size, &ptr, US"\n", 1);
+ if (data) data = string_catn(data, US"\n", 1);
/* Get the DN from the last result. */
- new_dn = US ldap_get_dn(lcp->ld, e);
- if (new_dn != NULL)
+ if ((new_dn = US ldap_get_dn(lcp->ld, e)))
{
- if (dn != NULL)
+ if (dn)
{
- #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
ldap_memfree(dn);
- #else /* OPENLDAP 1, UMich, Solaris */
+#else /* OPENLDAP 1, UMich, Solaris */
free(dn);
- #endif
+#endif
}
/* Save for later */
dn = new_dn;
entries, the DNs will be concatenated, but we test for this case below, as
for SEARCH_LDAP_SINGLE, and give an error. */
- if (search_type == SEARCH_LDAP_DN) /* Do not amalgamate these into one */
- { /* condition, because of the else */
- if (new_dn != NULL) /* below, that's for the first only */
+ if (search_type == SEARCH_LDAP_DN) /* Do not amalgamate these into one */
+ { /* condition, because of the else */
+ if (new_dn) /* below, that's for the first only */
{
- data = string_cat(data, &size, &ptr, new_dn);
- data[ptr] = 0;
+ data = string_cat(data, new_dn);
+ (void) string_from_gstring(data);
attribute_found = TRUE;
}
}
If there are multiple values, they are given within the quotes, comma separated. */
else for (attr = US ldap_first_attribute(lcp->ld, e, &ber);
- attr != NULL;
- attr = US ldap_next_attribute(lcp->ld, e, ber))
+ attr; attr = US ldap_next_attribute(lcp->ld, e, ber))
{
DEBUG(D_lookup) debug_printf("LDAP attr loop\n");
{
/* Get array of values for this attribute. */
- if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr))
- != NULL)
+ if ((firstval = values = USS ldap_get_values(lcp->ld, e, CS attr)))
{
-
if (attrs_requested != 1)
{
if (insert_space)
- data = string_catn(data, &size, &ptr, US" ", 1);
+ data = string_catn(data, US" ", 1);
else
insert_space = TRUE;
- data = string_cat(data, &size, &ptr, attr);
- data = string_catn(data, &size, &ptr, US"=\"", 2);
+ data = string_cat(data, attr);
+ data = string_catn(data, US"=\"", 2);
}
- while (*values != NULL)
+ while (*values)
{
uschar *value = *values;
int len = Ustrlen(value);
attribute and append only every non first value. */
if (data && valuecount > 1)
- data = string_catn(data, &size, &ptr, US",", 1);
+ data = string_catn(data, US",", 1);
/* For multiple attributes, the data is in quotes. We must escape
internal quotes, backslashes, newlines, and must double commas. */
for (j = 0; j < len; j++)
{
if (value[j] == '\n')
- data = string_catn(data, &size, &ptr, US"\\n", 2);
+ data = string_catn(data, US"\\n", 2);
else if (value[j] == ',')
- data = string_catn(data, &size, &ptr, US",,", 2);
+ data = string_catn(data, US",,", 2);
else
{
if (value[j] == '\"' || value[j] == '\\')
- data = string_catn(data, &size, &ptr, US"\\", 1);
- data = string_catn(data, &size, &ptr, value+j, 1);
+ data = string_catn(data, US"\\", 1);
+ data = string_catn(data, value+j, 1);
}
}
}
int j;
for (j = 0; j < len; j++)
if (value[j] == ',')
- data = string_catn(data, &size, &ptr, US",,", 2);
+ data = string_catn(data, US",,", 2);
else
- data = string_catn(data, &size, &ptr, value+j, 1);
+ data = string_catn(data, value+j, 1);
}
/* Closing quote at the end of the data for a named attribute. */
if (attrs_requested != 1)
- data = string_catn(data, &size, &ptr, US"\"", 1);
+ data = string_catn(data, US"\"", 1);
/* Free the values */
}
}
- #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
/* Netscape and OpenLDAP2 LDAP's attrs are dynamically allocated and need
to be freed. UMich LDAP stores them in static storage and does not require
this. */
ldap_memfree(attr);
- #endif
+#endif
} /* End "for" loop for extracting attributes from an entry */
} /* End "for" loop for extracting entries from a result */
result = NULL;
} /* End "while" loop for multiple results */
-/* Terminate the dynamic string that we have built and reclaim unused store */
+/* Terminate the dynamic string that we have built and reclaim unused store.
+In the odd case of a single attribute with zero-length value, allocate
+an empty string. */
-if (data != NULL)
- {
- data[ptr] = 0;
- store_reset(data + ptr + 1);
- }
+if (!data) data = string_get(1);
+(void) string_from_gstring(data);
+gstring_reset_unused(data);
/* Copy the last dn into eldap_dn */
-if (dn != NULL)
+if (dn)
{
eldap_dn = string_copy(dn);
- #if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_NETSCAPE || defined LDAP_LIB_OPENLDAP2
ldap_memfree(dn);
- #else /* OPENLDAP 1, UMich, Solaris */
+#else /* OPENLDAP 1, UMich, Solaris */
free(dn);
- #endif
+#endif
}
DEBUG(D_lookup) debug_printf("search ended by ldap_result yielding %d\n",rc);
Annoyingly, the different implementations of LDAP have gone for different
methods of handling error codes and generating error messages. */
-if (rc == -1 || result == NULL)
+if (rc == -1 || !result)
{
int err;
DEBUG(D_lookup) debug_printf("ldap_result failed\n");
- #if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
+#if defined LDAP_LIB_SOLARIS || defined LDAP_LIB_OPENLDAP2
ldap_get_option(lcp->ld, LDAP_OPT_ERROR_NUMBER, &err);
*errmsg = string_sprintf("ldap_result failed: %d, %s",
err, ldap_err2string(err));
- #elif defined LDAP_LIB_NETSCAPE
+#elif defined LDAP_LIB_NETSCAPE
/* Dubious (surely 'matched' is spurious here?) */
(void)ldap_get_lderrno(lcp->ld, &matched, &error1);
*errmsg = string_sprintf("ldap_result failed: %s (%s)", error1, matched);
- #else /* UMich LDAP aka OpenLDAP 1.x */
+#else /* UMich LDAP aka OpenLDAP 1.x */
*errmsg = string_sprintf("ldap_result failed: %d, %s",
lcp->ld->ld_errno, ldap_err2string(lcp->ld->ld_errno));
- #endif
+#endif
goto RETURN_ERROR;
}
{
*errmsg = string_sprintf("LDAP search failed - error %d: %s%s%s%s%s",
rc,
- (error1 != NULL)? error1 : US"",
- (error2 != NULL && error2[0] != 0)? US"/" : US"",
- (error2 != NULL)? error2 : US"",
- (matched != NULL && matched[0] != 0)? US"/" : US"",
- (matched != NULL)? matched : US"");
+ error1 ? error1 : US"",
+ error2 && error2[0] ? US"/" : US"",
+ error2 ? error2 : US"",
+ matched && matched[0] ? US"/" : US"",
+ matched ? matched : US"");
- #if defined LDAP_NAME_ERROR
+#if defined LDAP_NAME_ERROR
if (LDAP_NAME_ERROR(rc))
- #elif defined NAME_ERROR /* OPENLDAP1 calls it this */
+#elif defined NAME_ERROR /* OPENLDAP1 calls it this */
if (NAME_ERROR(rc))
- #else
+#else
if (rc == LDAP_NO_SUCH_OBJECT)
- #endif
+#endif
{
DEBUG(D_lookup) debug_printf("lookup failure forced\n");
/* Otherwise, it's all worked */
-DEBUG(D_lookup) debug_printf("LDAP search: returning: %s\n", data);
-*res = data;
+DEBUG(D_lookup) debug_printf("LDAP search: returning: %s\n", data->s);
+*res = data->s;
RETURN_OK:
-if (result != NULL) ldap_msgfree(result);
+if (result) ldap_msgfree(result);
ldap_free_urldesc(ludp);
return OK;
DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
RETURN_ERROR_NOMSG:
-if (result != NULL) ldap_msgfree(result);
-if (ludp != NULL) ldap_free_urldesc(ludp);
+if (result) ldap_msgfree(result);
+if (ludp) ldap_free_urldesc(ludp);
#if defined LDAP_LIB_OPENLDAP2
- if (error2 != NULL) ldap_memfree(error2);
- if (matched != NULL) ldap_memfree(matched);
+ if (error2) ldap_memfree(error2);
+ if (matched) ldap_memfree(matched);
#endif
return error_yield;
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Header for the functions that are shared by the lookups */
extern int lf_check_file(int, uschar *, int, int, uid_t *, gid_t *,
const char *, uschar **);
-extern uschar *lf_quote(uschar *, uschar *, int, uschar *, int *, int *);
+extern gstring *lf_quote(uschar *, uschar *, int, gstring *);
extern int lf_sqlperform(const uschar *, const uschar *, const uschar *,
const uschar *, uschar **,
uschar **, uint *, int(*)(const uschar *, uschar *, uschar **,
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
name the field name
value the data value
vlength the data length
- result the result pointer
- asize points to the size variable
- aoffset points to the offset variable
+ result the result expanding-string
Returns: the result pointer (possibly updated)
*/
-uschar *
-lf_quote(uschar *name, uschar *value, int vlength, uschar *result, int *asize,
- int *aoffset)
+gstring *
+lf_quote(uschar *name, uschar *value, int vlength, gstring * result)
{
-result = string_append(result, asize, aoffset, 2, name, US"=");
+result = string_append(result, 2, name, US"=");
/* NULL is handled as an empty string */
if (value[0] == 0 || Ustrpbrk(value, " \t\n\r") != NULL || value[0] == '\"')
{
int j;
- result = string_catn(result, asize, aoffset, US"\"", 1);
+ result = string_catn(result, US"\"", 1);
for (j = 0; j < vlength; j++)
{
if (value[j] == '\"' || value[j] == '\\')
- result = string_catn(result, asize, aoffset, US"\\", 1);
- result = string_catn(result, asize, aoffset, US value+j, 1);
+ result = string_catn(result, US"\\", 1);
+ result = string_catn(result, US value+j, 1);
}
- result = string_catn(result, asize, aoffset, US"\"", 1);
+ result = string_catn(result, US"\"", 1);
}
else
- result = string_catn(result, asize, aoffset, US value, vlength);
+ result = string_catn(result, US value, vlength);
-return string_catn(result, asize, aoffset, US" ", 1);
+return string_catn(result, US" ", 1);
}
/* End of lf_quote.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
return DEFER;
}
- qserverlist = string_sprintf("%.*s", ss - s, s);
+ qserverlist = string_sprintf("%.*s", (int)(ss - s), s);
qsep = 0;
while ((qserver = string_nextinlist(&qserverlist, &qsep, qbuffer,
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 2016 */
+/* Copyright (c) University of Cambridge 2016 - 2018*/
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
dbkey.mv_data = CS keystring;
dbkey.mv_size = length;
-DEBUG(D_lookup) debug_printf("LMDB: lookup key: %s\n", (char *)keystring);
+DEBUG(D_lookup) debug_printf("LMDB: lookup key: %s\n", CS keystring);
if ((ret = mdb_get(lmdb_p->txn, lmdb_p->db_dbi, &dbkey, &data)) == 0)
{
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
Ufgets(buffer, sizeof(buffer), f) != NULL;
last_was_eol = this_is_eol)
{
- int ptr, size;
int p = Ustrlen(buffer);
int linekeylength;
BOOL this_is_comment;
- uschar *yield;
+ gstring * yield;
uschar *s = buffer;
/* Check whether this the final segment of a line. If it follows an
/* Reset dynamic store, if we need to, and revert to the search pool */
- if (reset_point != NULL)
+ if (reset_point)
{
store_reset(reset_point);
store_pool = old_pool;
Initialize, and copy the first segment of data. */
this_is_comment = FALSE;
- size = 100;
- ptr = 0;
- yield = store_get(size);
+ yield = string_get(100);
if (*s != 0)
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
/* Now handle continuations */
/* Join a physical or logical line continuation onto the result string. */
- yield = string_cat(yield, &size, &ptr, s);
+ yield = string_cat(yield, s);
}
- yield[ptr] = 0;
- store_reset(yield + ptr + 1);
- *result = yield;
+ store_reset(yield->s + yield->ptr + 1);
+ *result = string_from_gstring(yield);
return OK;
}
/* Reset dynamic store, if we need to */
-if (reset_point != NULL)
+if (reset_point)
{
store_reset(reset_point);
store_pool = old_pool;
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Thanks to Paul Kelly for contributing the original code for these
#include <mysql.h> /* The system header */
+/* We define symbols for *_VERSION_ID (numeric), *_VERSION_STR (char*)
+and *_BASE_STR (char*). It's a bit of guesswork. Especially for mariadb
+with versions before 10.2, as they do not define there there specific symbols.
+*/
+
+/* Newer (>= 10.2) MariaDB */
+#if defined MARIADB_VERSION_ID
+#define EXIM_MxSQL_VERSION_ID MARIADB_VERSION_ID
+
+/* MySQL defines MYSQL_VERSION_ID, and MariaDB does so */
+/* https://dev.mysql.com/doc/refman/5.7/en/c-api-server-client-versions.html */
+#elif defined LIBMYSQL_VERSION_ID
+#define EXIM_MxSQL_VERSION_ID LIBMYSQL_VERSION_ID
+#elif defined MYSQL_VERSION_ID
+#define EXIM_MxSQL_VERSION_ID MYSQL_VERSION_ID
+
+#else
+#define EXIM_MYSQL_VERSION_ID 0
+#endif
+
+/* Newer (>= 10.2) MariaDB */
+#ifdef MARIADB_CLIENT_VERSION_STR
+#define EXIM_MxSQL_VERSION_STR MARIADB_CLIENT_VERSION_STR
+
+/* Mysql uses MYSQL_SERVER_VERSION */
+#elif defined LIBMYSQL_VERSION
+#define EXIM_MxSQL_VERSION_STR LIBMYSQL_VERSION
+#elif defined MYSQL_SERVER_VERSION
+#define EXIM_MxSQL_VERSION_STR MYSQL_SERVER_VERSION
+
+#else
+#define EXIM_MxSQL_VERSION_STR "unknown"
+#endif
+
+#if defined MARIADB_BASE_VERSION
+#define EXIM_MxSQL_BASE_STR MARIADB_BASE_VERSION
+
+#elif defined MARIADB_PACKAGE_VERSION
+#define EXIM_MxSQL_BASE_STR "mariadb"
+
+#elif defined MYSQL_BASE_VERSION
+#define EXIM_MxSQL_BASE_STR MYSQL_BASE_VERSION
+
+#else
+#define EXIM_MxSQL_BASE_STR "n.A."
+#endif
+
/* Structure and anchor for caching connections. */
MYSQL_FIELD *fields;
int i;
-int ssize = 0;
-int offset = 0;
int yield = DEFER;
unsigned int num_fields;
-uschar *result = NULL;
+gstring * result = NULL;
mysql_connection *cn;
uschar *server_copy = NULL;
uschar *sdata[4];
want to cache the result; also the whole cache for the handle must be cleaned
up. Setting do_cache zero requests this. */
-if ((mysql_result = mysql_use_result(mysql_handle)) == NULL)
+if (!(mysql_result = mysql_use_result(mysql_handle)))
{
if ( mysql_field_count(mysql_handle) == 0 )
{
DEBUG(D_lookup) debug_printf("MYSQL: query was not one that returns data\n");
- result = string_sprintf("%d", mysql_affected_rows(mysql_handle));
+ result = string_cat(result,
+ string_sprintf("%d", mysql_affected_rows(mysql_handle)));
*do_cache = 0;
goto MYSQL_EXIT;
}
fields = mysql_fetch_fields(mysql_result);
-while ((mysql_row_data = mysql_fetch_row(mysql_result)) != NULL)
+while ((mysql_row_data = mysql_fetch_row(mysql_result)))
{
unsigned long *lengths = mysql_fetch_lengths(mysql_result);
- if (result != NULL)
- result = string_catn(result, &ssize, &offset, US"\n", 1);
+ if (result)
+ result = string_catn(result, US"\n", 1);
- if (num_fields == 1)
- {
- if (mysql_row_data[0] != NULL) /* NULL value yields nothing */
- result = string_catn(result, &ssize, &offset, US mysql_row_data[0],
- lengths[0]);
- }
+ if (num_fields != 1)
+ for (i = 0; i < num_fields; i++)
+ result = lf_quote(US fields[i].name, US mysql_row_data[i], lengths[i],
+ result);
- else for (i = 0; i < num_fields; i++)
- {
- result = lf_quote(US fields[i].name, US mysql_row_data[i], lengths[i],
- result, &ssize, &offset);
- }
+ else if (mysql_row_data[0] != NULL) /* NULL value yields nothing */
+ result = string_catn(result, US mysql_row_data[0], lengths[0]);
}
/* more results? -1 = no, >0 = error, 0 = yes (keep looping)
This is needed because of the CLIENT_MULTI_RESULTS on mysql_real_connect(),
we don't expect any more results. */
-while((i = mysql_next_result(mysql_handle)) >= 0) {
- if(i == 0) { /* Just ignore more results */
- DEBUG(D_lookup) debug_printf("MYSQL: got unexpected more results\n");
- continue;
- }
+while((i = mysql_next_result(mysql_handle)) >= 0)
+ {
+ if(i == 0) /* Just ignore more results */
+ {
+ DEBUG(D_lookup) debug_printf("MYSQL: got unexpected more results\n");
+ continue;
+ }
- *errmsg = string_sprintf("MYSQL: lookup result error when checking for more results: %s\n",
- mysql_error(mysql_handle));
- goto MYSQL_EXIT;
-}
+ *errmsg = string_sprintf(
+ "MYSQL: lookup result error when checking for more results: %s\n",
+ mysql_error(mysql_handle));
+ goto MYSQL_EXIT;
+ }
/* If result is NULL then no data has been found and so we return FAIL.
Otherwise, we must terminate the string which has been built; string_cat()
always leaves enough room for a terminating zero. */
-if (result == NULL)
+if (!result)
{
yield = FAIL;
*errmsg = US"MYSQL: no data found";
}
-else
- {
- result[offset] = 0;
- store_reset(result + offset + 1);
- }
/* Get here by goto from various error checks and from the case where no data
was read (e.g. an update query). */
/* Free mysal store for any result that was got; don't close the connection, as
it is cached. */
-if (mysql_result != NULL) mysql_free_result(mysql_result);
+if (mysql_result) mysql_free_result(mysql_result);
/* Non-NULL result indicates a successful result */
-if (result != NULL)
+if (result)
{
- *resultptr = result;
+ *resultptr = string_from_gstring(result);
+ store_reset(result->s + (result->size = result->ptr + 1));
return OK;
}
else
void
mysql_version_report(FILE *f)
{
-fprintf(f, "Library version: MySQL: Compile: %s [%s]\n"
- " Runtime: %s\n",
- MYSQL_SERVER_VERSION, MYSQL_COMPILATION_COMMENT,
- mysql_get_client_info());
+fprintf(f, "Library version: MySQL: Compile: %lu %s [%s]\n"
+ " Runtime: %lu %s\n",
+ (long)EXIM_MxSQL_VERSION_ID, EXIM_MxSQL_VERSION_STR, EXIM_MxSQL_BASE_STR,
+ mysql_get_client_version(), mysql_get_client_info());
#ifdef DYNLOOKUP
fprintf(f, " Exim version %s\n", EXIM_VERSION_STR);
#endif
code. */
static int
-nis_find(void *handle, uschar *filename, uschar *keystring, int length,
+nis_find(void *handle, uschar *filename, const uschar *keystring, int length,
uschar **result, uschar **errmsg, uint *do_cache)
{
int rc;
uschar *nis_data;
int nis_data_length;
do_cache = do_cache; /* Placate picky compilers */
-if ((rc = yp_match(CS handle, CS filename, CS keystring, length,
+if ((rc = yp_match(CCS handle, CCS filename, CCS keystring, length,
CSS &nis_data, &nis_data_length)) == 0)
{
*result = string_copy(nis_data);
/* See local README for interface description. */
static int
-nis0_find(void *handle, uschar *filename, uschar *keystring, int length,
+nis0_find(void *handle, uschar *filename, const uschar *keystring, int length,
uschar **result, uschar **errmsg, uint *do_cache)
{
int rc;
uschar *nis_data;
int nis_data_length;
do_cache = do_cache; /* Placate picky compilers */
-if ((rc = yp_match(CS handle, CS filename, CS keystring, length + 1,
+if ((rc = yp_match(CCS handle, CCS filename, CCS keystring, length + 1,
CSS &nis_data, &nis_data_length)) == 0)
{
*result = string_copy(nis_data);
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
equals sign. */
static int
-nisplus_find(void *handle, uschar *filename, uschar *query, int length,
+nisplus_find(void *handle, uschar *filename, const uschar *query, int length,
uschar **result, uschar **errmsg, uint *do_cache)
{
int i;
-int ssize = 0;
-int offset = 0;
int error_error = FAIL;
-uschar *field_name = NULL;
+const uschar * field_name = NULL;
nis_result *nrt = NULL;
nis_result *nre = NULL;
nis_object *tno, *eno;
struct entry_obj *eo;
struct table_obj *ta;
-uschar *p = query + length;
-uschar *yield = NULL;
+const uschar * p = query + length;
+gstring * yield = NULL;
do_cache = do_cache; /* Placate picky compilers */
while (p > query && p[-1] != ':') p--;
-if (p > query)
+if (p > query) /* get the query without the result-field */
{
+ uint len = p-1 - query;
field_name = p;
- p[-1] = 0;
+ query = string_copyn(query, len);
+ p = query + len;
}
-else p = query + length;
+else
+ p = query + length;
/* Now search backwards to find the comma that starts the
table name. */
*errmsg = string_sprintf("NIS+ error: %s is not a table", p);
goto NISPLUS_EXIT;
}
-ta = &(tno->zo_data.objdata_u.ta_data);
+ta = &tno->zo_data.objdata_u.ta_data;
/* Now look up the entry in the table, check that we got precisely one
object and that it is a table entry. */
/* Concatenate all fields if no specific one selected */
- if (field_name == NULL)
+ if (!field_name)
{
- yield = string_cat(yield, &ssize, &offset,US tc->tc_name);
- yield = string_catn(yield, &ssize, &offset, US"=", 1);
+ yield = string_cat (yield, tc->tc_name);
+ yield = string_catn(yield, US"=", 1);
/* Quote the value if it contains spaces or is empty */
if (value[0] == 0 || Ustrchr(value, ' ') != NULL)
{
int j;
- yield = string_catn(yield, &ssize, &offset, US"\"", 1);
+ yield = string_catn(yield, US"\"", 1);
for (j = 0; j < len; j++)
{
if (value[j] == '\"' || value[j] == '\\')
- yield = string_catn(yield, &ssize, &offset, US"\\", 1);
- yield = string_catn(yield, &ssize, &offset, value+j, 1);
+ yield = string_catn(yield, US"\\", 1);
+ yield = string_catn(yield, value+j, 1);
}
- yield = string_catn(yield, &ssize, &offset, US"\"", 1);
+ yield = string_catn(yield, US"\"", 1);
}
- else yield = string_catn(yield, &ssize, &offset, value, len);
+ else
+ yield = string_catn(yield, value, len);
- yield = string_catn(yield, &ssize, &offset, US" ", 1);
+ yield = string_catn(yield, US" ", 1);
}
/* When the specified field is found, grab its data and finish */
else if (Ustrcmp(field_name, tc->tc_name) == 0)
{
- yield = string_copyn(value, len);
+ yield = string_catn(yield, value, len);
goto NISPLUS_EXIT;
}
}
/* Error if a field name was specified and we didn't find it; if no
field name, ensure the concatenated data is zero-terminated. */
-if (field_name != NULL)
+if (field_name)
*errmsg = string_sprintf("NIS+ field %s not found for %s", field_name,
query);
else
- {
- yield[offset] = 0;
- store_reset(yield + offset + 1);
- }
+ store_reset(yield->s + yield->ptr + 1);
-/* Restore the colon in the query, and free result store before
-finishing. */
+/* Free result store before finishing. */
NISPLUS_EXIT:
-if (field_name != NULL) field_name[-1] = ':';
-if (nrt != NULL) nis_freeresult(nrt);
-if (nre != NULL) nis_freeresult(nre);
+if (nrt) nis_freeresult(nrt);
+if (nre) nis_freeresult(nre);
-if (yield != NULL)
+if (yield)
{
- *result = yield;
+ *result = string_from_gstring(yield);
return OK;
}
void *hda = NULL;
int i;
-int ssize = 0;
-int offset = 0;
int yield = DEFER;
unsigned int num_fields = 0;
-uschar *result = NULL;
+gstring * result = NULL;
oracle_connection *cn = NULL;
uschar *server_copy = NULL;
uschar *sdata[4];
-uschar tmp[1024];
/* Disaggregate the parameters from the server argument. The order is host,
database, user, password. We can write to the string, since it is in a
/* See if we have a cached connection to the server */
-for (cn = oracle_connections; cn != NULL; cn = cn->next)
- {
+for (cn = oracle_connections; cn; cn = cn->next)
if (strcmp(cn->server, server_copy) == 0)
{
oracle_handle = cn->handle;
hda = cn->hda_mem;
break;
}
- }
/* If no cached connection, we must set one up */
-if (cn == NULL)
+if (!cn)
{
DEBUG(D_lookup) debug_printf("ORACLE new connection: host=%s database=%s "
"user=%s\n", sdata[0], sdata[1], sdata[2]);
ofetch(cda);
if(cda->rc == NO_DATA_FOUND) break;
- if (result) result = string_catn(result, &ssize, &offset, "\n", 1);
+ if (result) result = string_catn(result, "\n", 1);
/* Single field - just add on the data */
if (num_fields == 1)
- result = string_catn(result, &ssize, &offset, def[0].buf, def[0].col_retlen);
+ result = string_catn(result, def[0].buf, def[0].col_retlen);
/* Multiple fields - precede by file name, removing {lead,trail}ing WS */
while (*s != 0 && isspace(*s)) s++;
slen = Ustrlen(s);
while (slen > 0 && isspace(s[slen-1])) slen--;
- result = string_catn(result, &ssize, &offset, s, slen);
- result = string_catn(result, &ssize, &offset, US"=", 1);
+ result = string_catn(result, s, slen);
+ result = string_catn(result, US"=", 1);
- /* int and float type wont ever need escaping. Otherwise, quote the value
+ /* int and float type won't ever need escaping. Otherwise, quote the value
if it contains spaces or is empty. */
if (desc[i].dbtype != INT_TYPE && desc[i].dbtype != FLOAT_TYPE &&
(def[i].buf[0] == 0 || strchr(def[i].buf, ' ') != NULL))
{
int j;
- result = string_catn(result, &ssize, &offset, "\"", 1);
+ result = string_catn(result, "\"", 1);
for (j = 0; j < def[i].col_retlen; j++)
{
if (def[i].buf[j] == '\"' || def[i].buf[j] == '\\')
- result = string_catn(result, &ssize, &offset, "\\", 1);
- result = string_catn(result, &ssize, &offset, def[i].buf+j, 1);
+ result = string_catn(result, "\\", 1);
+ result = string_catn(result, def[i].buf+j, 1);
}
- result = string_catn(result, &ssize, &offset, "\"", 1);
+ result = string_catn(result, "\"", 1);
}
else switch(desc[i].dbtype)
{
case INT_TYPE:
- sprintf(CS tmp, "%d", def[i].int_buf);
- result = string_cat(result, &ssize, &offset, tmp);
- break;
+ result = string_cat(result, string_sprintf("%d", def[i].int_buf));
+ break;
case FLOAT_TYPE:
- sprintf(CS tmp, "%f", def[i].flt_buf);
- result = string_cat(result, &ssize, &offset, tmp);
- break;
+ result = string_cat(result, string_sprintf("%f", def[i].flt_buf));
+ break;
case STRING_TYPE:
- result = string_catn(result, &ssize, &offset, def[i].buf,
- def[i].col_retlen);
- break;
+ result = string_catn(result, def[i].buf, def[i].col_retlen);
+ break;
default:
- *errmsg = string_sprintf("ORACLE: unknown field type %d", desc[i].dbtype);
- *defer_break = FALSE;
- result = NULL;
- goto ORACLE_EXIT;
+ *errmsg = string_sprintf("ORACLE: unknown field type %d", desc[i].dbtype);
+ *defer_break = FALSE;
+ result = NULL;
+ goto ORACLE_EXIT;
}
- result = string_catn(result, &ssize, &offset, " ", 1);
+ result = string_catn(result, " ", 1);
}
}
Otherwise, we must terminate the string which has been built; string_cat()
always leaves enough room for a terminating zero. */
-if (result == NULL)
+if (!result)
{
yield = FAIL;
*errmsg = "ORACLE: no data found";
}
else
- {
- result[offset] = 0;
- store_reset(result + offset + 1);
- }
+ store_reset(result->s + result->ptr + 1);
/* Get here by goto from various error checks. */
/* Non-NULL result indicates a successful result */
-if (result != NULL)
+if (result)
{
- *resultptr = result;
+ *resultptr = string_from_gstring(result);
return OK;
}
else
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Thanks to Petr Cech for contributing the original code for these
PGresult *pg_result = NULL;
int i;
-uschar *result = NULL;
-int ssize = 0;
-int offset = 0;
+gstring * result = NULL;
int yield = DEFER;
unsigned int num_fields, num_tuples;
pgsql_connection *cn;
for (i = 2; i >= 0; i--)
{
uschar *pp = Ustrrchr(server, '/');
- if (pp == NULL)
+ if (!pp)
{
*errmsg = string_sprintf("incomplete pgSQL server data: %s",
(i == 2)? server : server_copy);
start is the identification of the server (host or path). See if we have a
cached connection to the server. */
-for (cn = pgsql_connections; cn != NULL; cn = cn->next)
- {
+for (cn = pgsql_connections; cn; cn = cn->next)
if (Ustrcmp(cn->server, server_copy) == 0)
{
pg_conn = cn->handle;
break;
}
- }
/* If there is no cached connection, we must set one up. */
-if (cn == NULL)
+if (!cn)
{
uschar *port = US"";
uschar *last_slash, *last_dot, *p;
p = ++server;
- while (*p != 0 && *p != ')') p++;
+ while (*p && *p != ')') p++;
*p = 0;
last_slash = Ustrrchr(server, '/');
We have to call PQsetdbLogin with '/var/run/postgresql' as the hostname
argument and put '5432' into the port variable. */
- if (last_slash == NULL || last_dot == NULL)
+ if (!last_slash || !last_dot)
{
- *errmsg = string_sprintf("PGSQL invalid filename for socket: %s",
- server);
+ *errmsg = string_sprintf("PGSQL invalid filename for socket: %s", server);
*defer_break = TRUE;
return DEFER;
}
else
{
uschar *p;
- if ((p = Ustrchr(server, ':')) != NULL)
+ if ((p = Ustrchr(server, ':')))
{
*p++ = 0;
port = p;
}
- if (Ustrchr(server, '/') != NULL)
+ if (Ustrchr(server, '/'))
{
*errmsg = string_sprintf("unexpected slash in pgSQL server hostname: %s",
server);
/* Run the query */
- pg_result = PQexec(pg_conn, CS query);
- switch(PQresultStatus(pg_result))
- {
- case PGRES_EMPTY_QUERY:
- case PGRES_COMMAND_OK:
+pg_result = PQexec(pg_conn, CS query);
+switch(PQresultStatus(pg_result))
+ {
+ case PGRES_EMPTY_QUERY:
+ case PGRES_COMMAND_OK:
/* The command was successful but did not return any data since it was
- * not SELECT but either an INSERT, UPDATE or DELETE statement. Tell the
- * high level code to not cache this query, and clean the current cache for
- * this handle by setting *do_cache zero. */
- result = string_copy(US PQcmdTuples(pg_result));
- offset = Ustrlen(result);
+ not SELECT but either an INSERT, UPDATE or DELETE statement. Tell the
+ high level code to not cache this query, and clean the current cache for
+ this handle by setting *do_cache zero. */
+
+ result = string_cat(result, US PQcmdTuples(pg_result));
*do_cache = 0;
DEBUG(D_lookup) debug_printf("PGSQL: command does not return any data "
- "but was successful. Rows affected: %s\n", result);
+ "but was successful. Rows affected: %s\n", string_from_gstring(result));
+ break;
- case PGRES_TUPLES_OK:
+ case PGRES_TUPLES_OK:
break;
- default:
+ default:
/* This was the original code:
*errmsg = string_sprintf("PGSQL: query failed: %s\n",
- PQresultErrorMessage(pg_result));
+ PQresultErrorMessage(pg_result));
This was suggested by a user:
*/
*errmsg = string_sprintf("PGSQL: query failed: %s (%s) (%s)\n",
- PQresultErrorMessage(pg_result),
- PQresStatus(PQresultStatus(pg_result)), query);
+ PQresultErrorMessage(pg_result),
+ PQresStatus(PQresultStatus(pg_result)), query);
goto PGSQL_EXIT;
- }
+ }
/* Result is in pg_result. Find the number of fields returned. If this is one,
we don't add field names to the data. Otherwise we do. If the query did not
for (i = 0; i < num_tuples; i++)
{
- if (result != NULL)
- result = string_catn(result, &ssize, &offset, US"\n", 1);
-
- if (num_fields == 1)
- {
- result = string_catn(result, &ssize, &offset,
- US PQgetvalue(pg_result, i, 0), PQgetlength(pg_result, i, 0));
- }
+ if (result)
+ result = string_catn(result, US"\n", 1);
- else
+ if (num_fields == 1)
+ result = string_catn(result,
+ US PQgetvalue(pg_result, i, 0), PQgetlength(pg_result, i, 0));
+ else
{
int j;
for (j = 0; j < num_fields; j++)
{
uschar *tmp = US PQgetvalue(pg_result, i, j);
- result = lf_quote(US PQfname(pg_result, j), tmp, Ustrlen(tmp), result,
- &ssize, &offset);
+ result = lf_quote(US PQfname(pg_result, j), tmp, Ustrlen(tmp), result);
}
}
}
-/* If result is NULL then no data has been found and so we return FAIL.
-Otherwise, we must terminate the string which has been built; string_cat()
-always leaves enough room for a terminating zero. */
+/* If result is NULL then no data has been found and so we return FAIL. */
-if (result == NULL)
+if (!result)
{
yield = FAIL;
*errmsg = US"PGSQL: no data found";
}
-else
- {
- result[offset] = 0;
- store_reset(result + offset + 1);
- }
/* Get here by goto from various error checks. */
/* Free store for any result that was got; don't close the connection, as
it is cached. */
-if (pg_result != NULL) PQclear(pg_result);
+if (pg_result) PQclear(pg_result);
/* Non-NULL result indicates a successful result */
-if (result != NULL)
+if (result)
{
- *resultptr = result;
+ store_reset(result->s + result->ptr + 1);
+ *resultptr = string_from_gstring(result);
return OK;
}
else
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
redisReply *entry = NULL;
redisReply *tentry = NULL;
redis_connection *cn;
-int ssize = 0;
-int offset = 0;
int yield = DEFER;
int i, j;
-uschar *result = NULL;
+gstring * result = NULL;
uschar *server_copy = NULL;
-uschar *tmp, *ttmp;
uschar *sdata[3];
/* Disaggregate the parameters from the server argument.
for (i = 0; *s && i < nele(argv); i++)
{
- for (argv[i] = NULL, siz = ptr = 0; (c = *s) && !isspace(c); s++)
+ gstring * g;
+
+ for (g = NULL; (c = *s) && !isspace(c); s++)
if (c != '\\' || *++s) /* backslash protects next char */
- argv[i] = string_catn(argv[i], &siz, &ptr, s, 1);
- *(argv[i]+ptr) = '\0';
+ g = string_catn(g, s, 1);
+ argv[i] = string_from_gstring(g);
+
DEBUG(D_lookup) debug_printf("REDIS: argv[%d] '%s'\n", i, argv[i]);
while (isspace(*s)) s++;
}
{
case REDIS_REPLY_ERROR:
*errmsg = string_sprintf("REDIS: lookup result failed: %s\n", redis_reply->str);
- *defer_break = FALSE;
+
+ /* trap MOVED cluster responses and follow them */
+ if (Ustrncmp(redis_reply->str, "MOVED", 5) == 0)
+ {
+ DEBUG(D_lookup)
+ debug_printf("REDIS: cluster redirect %s\n", redis_reply->str);
+ /* follow redirect
+ This is cheating, we simply set defer_break = FALSE to move on to
+ the next server in the redis_servers list */
+ *defer_break = FALSE;
+ return DEFER;
+ } else {
+ *defer_break = TRUE;
+ }
*do_cache = 0;
goto REDIS_EXIT;
/* NOTREACHED */
case REDIS_REPLY_NIL:
DEBUG(D_lookup)
debug_printf("REDIS: query was not one that returned any data\n");
- result = string_sprintf("");
+ result = string_catn(result, US"", 1);
*do_cache = 0;
goto REDIS_EXIT;
/* NOTREACHED */
case REDIS_REPLY_INTEGER:
- ttmp = (redis_reply->integer != 0) ? US"true" : US"false";
- result = string_cat(result, &ssize, &offset, US ttmp);
+ result = string_cat(result, redis_reply->integer != 0 ? US"true" : US"false");
break;
case REDIS_REPLY_STRING:
case REDIS_REPLY_STATUS:
- result = string_catn(result, &ssize, &offset,
- US redis_reply->str, redis_reply->len);
+ result = string_catn(result, US redis_reply->str, redis_reply->len);
break;
case REDIS_REPLY_ARRAY:
entry = redis_reply->element[i];
if (result)
- result = string_catn(result, &ssize, &offset, US"\n", 1);
+ result = string_catn(result, US"\n", 1);
switch (entry->type)
{
case REDIS_REPLY_INTEGER:
- tmp = string_sprintf("%d", entry->integer);
- result = string_cat(result, &ssize, &offset, US tmp);
+ result = string_fmt_append(result, "%d", entry->integer);
break;
case REDIS_REPLY_STRING:
- result = string_catn(result, &ssize, &offset,
- US entry->str, entry->len);
+ result = string_catn(result, US entry->str, entry->len);
break;
case REDIS_REPLY_ARRAY:
for (j = 0; j < entry->elements; j++)
tentry = entry->element[j];
if (result)
- result = string_catn(result, &ssize, &offset, US"\n", 1);
+ result = string_catn(result, US"\n", 1);
switch (tentry->type)
{
case REDIS_REPLY_INTEGER:
- ttmp = string_sprintf("%d", tentry->integer);
- result = string_cat(result, &ssize, &offset, US ttmp);
+ result = string_fmt_append(result, "%d", tentry->integer);
break;
case REDIS_REPLY_STRING:
- result = string_catn(result, &ssize, &offset,
- US tentry->str, tentry->len);
+ result = string_catn(result, US tentry->str, tentry->len);
break;
case REDIS_REPLY_ARRAY:
DEBUG(D_lookup)
if (result)
- {
- result[offset] = 0;
- store_reset(result + offset + 1);
- }
+ store_reset(result->s + result->ptr + 1);
else
{
yield = FAIL;
if (result)
{
- *resultptr = result;
+ *resultptr = string_from_gstring(result);
return OK;
}
else
#include "../exim.h"
-#ifndef EXPERIMENTAL_SPF
+#ifndef SUPPORT_SPF
static void dummy(int x);
static void dummy2(int x) { dummy(x-1); }
static void dummy(int x) { dummy2(x-1); }
static lookup_info *_lookup_list[] = { &_lookup_info };
lookup_module_info spf_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
-#endif /* EXPERIMENTAL_SPF */
+#endif /* SUPPORT_SPF */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
sqlite3 *db = NULL;
int ret;
-ret = sqlite3_open((char *)filename, &db);
+ret = sqlite3_open(CS filename, &db);
if (ret != 0)
{
*errmsg = (void *)sqlite3_errmsg(db);
/* See local README for interface description. */
-struct strbuf {
- uschar *string;
- int size;
- int len;
-};
-
-static int sqlite_callback(void *arg, int argc, char **argv, char **azColName)
+static int
+sqlite_callback(void *arg, int argc, char **argv, char **azColName)
{
-struct strbuf *res = arg;
+gstring * res = *(gstring **)arg;
int i;
/* For second and subsequent results, insert \n */
-if (res->string != NULL)
- res->string = string_catn(res->string, &res->size, &res->len, US"\n", 1);
+if (res)
+ res = string_catn(res, US"\n", 1);
if (argc > 1)
{
for (i = 0; i < argc; i++)
{
uschar *value = US((argv[i] != NULL)? argv[i]:"<NULL>");
- res->string = lf_quote(US azColName[i], value, Ustrlen(value), res->string,
- &res->size, &res->len);
+ res = lf_quote(US azColName[i], value, Ustrlen(value), res);
}
}
else
- {
- res->string = string_append(res->string, &res->size, &res->len, 1,
- (argv[0] != NULL)? argv[0]:"<NULL>");
- }
+ res = string_cat(res, argv[0] ? US argv[0] : US "<NULL>");
-res->string[res->len] = 0;
+*(gstring **)arg = res;
return 0;
}
uschar **result, uschar **errmsg, uint *do_cache)
{
int ret;
-struct strbuf res = { NULL, 0, 0 };
+gstring * res = NULL;
-ret = sqlite3_exec(handle, (char *)query, sqlite_callback, &res, (char **)errmsg);
+ret = sqlite3_exec(handle, CS query, sqlite_callback, &res, (char **)errmsg);
if (ret != SQLITE_OK)
{
debug_printf("sqlite3_exec failed: %s\n", *errmsg);
return FAIL;
}
-if (res.string == NULL) *do_cache = 0;
+if (!res) *do_cache = 0;
-*result = res.string;
+*result = string_from_gstring(res);
return OK;
}
--- /dev/null
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Create a static data structure with the predefined macros, to be
+included in the main Exim build */
+
+#include "exim.h"
+#include "macro_predef.h"
+
+unsigned mp_index = 0;
+
+/* Global dummy variables */
+
+void fn_smtp_receive_timeout(const uschar * name, const uschar * str) {}
+uschar * syslog_facility_str;
+
+/******************************************************************************/
+
+void
+builtin_macro_create_var(const uschar * name, const uschar * val)
+{
+printf ("static macro_item p%d = { ", mp_index);
+if (mp_index == 0)
+ printf(".next=NULL,");
+else
+ printf(".next=&p%d,", mp_index-1);
+
+printf(" .command_line=FALSE, .namelen=%d, .replen=%d,"
+ " .name=US\"%s\", .replacement=US\"%s\" };\n",
+ Ustrlen(name), Ustrlen(val), CS name, CS val);
+mp_index++;
+}
+
+
+void
+builtin_macro_create(const uschar * name)
+{
+builtin_macro_create_var(name, US"y");
+}
+
+
+/* restricted snprintf */
+void
+spf(uschar * buf, int len, const uschar * fmt, ...)
+{
+va_list ap;
+va_start(ap, fmt);
+
+while (*fmt && len > 1)
+ if (*fmt == '%' && fmt[1] == 'T')
+ {
+ uschar * s = va_arg(ap, uschar *);
+ while (*s && len-- > 1)
+ *buf++ = toupper(*s++);
+ fmt += 2;
+ }
+ else
+ {
+ *buf++ = *fmt++; len--;
+ }
+*buf = '\0';
+va_end(ap);
+}
+
+void
+options_from_list(optionlist * opts, unsigned nopt,
+ const uschar * section, uschar * group)
+{
+int i;
+const uschar * s;
+uschar buf[64];
+
+/* The 'previously-defined-substring' rule for macros in config file
+lines is done thus for these builtin macros: we know that the table
+we source from is in strict alpha order, hence the builtins portion
+of the macros list is in reverse-alpha (we prepend them) - so longer
+macros that have substrings are always discovered first during
+expansion. */
+
+for (i = 0; i < nopt; i++) if (*(s = US opts[i].name) && *s != '*')
+ {
+ if (group)
+ spf(buf, sizeof(buf), CUS"_OPT_%T_%T_%T", section, group, s);
+ else
+ spf(buf, sizeof(buf), CUS"_OPT_%T_%T", section, s);
+ builtin_macro_create(buf);
+ }
+}
+
+
+/******************************************************************************/
+
+
+/* Create compile-time feature macros */
+static void
+features(void)
+{
+/* Probably we could work out a static initialiser for wherever
+macros are stored, but this will do for now. Some names are awkward
+due to conflicts with other common macros. */
+
+#ifdef SUPPORT_CRYPTEQ
+ builtin_macro_create(US"_HAVE_CRYPTEQ");
+#endif
+#if HAVE_ICONV
+ builtin_macro_create(US"_HAVE_ICONV");
+#endif
+#if HAVE_IPV6
+ builtin_macro_create(US"_HAVE_IPV6");
+#endif
+#ifdef HAVE_SETCLASSRESOURCES
+ builtin_macro_create(US"_HAVE_SETCLASSRESOURCES");
+#endif
+#ifdef SUPPORT_PAM
+ builtin_macro_create(US"_HAVE_PAM");
+#endif
+#ifdef EXIM_PERL
+ builtin_macro_create(US"_HAVE_PERL");
+#endif
+#ifdef EXPAND_DLFUNC
+ builtin_macro_create(US"_HAVE_DLFUNC");
+#endif
+#ifdef USE_TCP_WRAPPERS
+ builtin_macro_create(US"_HAVE_TCPWRAPPERS");
+#endif
+#ifdef SUPPORT_TLS
+ builtin_macro_create(US"_HAVE_TLS");
+# ifdef USE_GNUTLS
+ builtin_macro_create(US"_HAVE_GNUTLS");
+# else
+ builtin_macro_create(US"_HAVE_OPENSSL");
+# endif
+#endif
+#ifdef SUPPORT_TRANSLATE_IP_ADDRESS
+ builtin_macro_create(US"_HAVE_TRANSLATE_IP_ADDRESS");
+#endif
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+ builtin_macro_create(US"_HAVE_MOVE_FROZEN_MESSAGES");
+#endif
+#ifdef WITH_CONTENT_SCAN
+ builtin_macro_create(US"_HAVE_CONTENT_SCANNING");
+#endif
+#ifndef DISABLE_DKIM
+ builtin_macro_create(US"_HAVE_DKIM");
+#endif
+#ifndef DISABLE_DNSSEC
+ builtin_macro_create(US"_HAVE_DNSSEC");
+#endif
+#ifndef DISABLE_EVENT
+ builtin_macro_create(US"_HAVE_EVENT");
+#endif
+#ifdef SUPPORT_I18N
+ builtin_macro_create(US"_HAVE_I18N");
+#endif
+#ifndef DISABLE_OCSP
+ builtin_macro_create(US"_HAVE_OCSP");
+#endif
+#ifndef DISABLE_PRDR
+ builtin_macro_create(US"_HAVE_PRDR");
+#endif
+#ifdef SUPPORT_PROXY
+ builtin_macro_create(US"_HAVE_PROXY");
+#endif
+#ifdef SUPPORT_SOCKS
+ builtin_macro_create(US"_HAVE_SOCKS");
+#endif
+#ifdef TCP_FASTOPEN
+ builtin_macro_create(US"_HAVE_TCP_FASTOPEN");
+#endif
+#ifdef EXPERIMENTAL_LMDB
+ builtin_macro_create(US"_HAVE_LMDB");
+#endif
+#ifdef SUPPORT_SPF
+ builtin_macro_create(US"_HAVE_SPF");
+#endif
+#ifdef EXPERIMENTAL_SRS
+ builtin_macro_create(US"_HAVE_SRS");
+#endif
+#ifdef EXPERIMENTAL_ARC
+ builtin_macro_create(US"_HAVE_ARC");
+#endif
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ builtin_macro_create(US"_HAVE_BRIGHTMAIL");
+#endif
+#ifdef SUPPORT_DANE
+ builtin_macro_create(US"_HAVE_DANE");
+#endif
+#ifdef EXPERIMENTAL_DCC
+ builtin_macro_create(US"_HAVE_DCC");
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ builtin_macro_create(US"_HAVE_DMARC");
+#endif
+#ifdef EXPERIMENTAL_DSN_INFO
+ builtin_macro_create(US"_HAVE_DSN_INFO");
+#endif
+#ifdef EXPERIMENTAL_REQUIRETLS
+ builtin_macro_create(US"_HAVE_REQTLS");
+#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ builtin_macro_create(US"_HAVE_PIPE_CONNECT");
+#endif
+
+#ifdef LOOKUP_LSEARCH
+ builtin_macro_create(US"_HAVE_LOOKUP_LSEARCH");
+#endif
+#ifdef LOOKUP_CDB
+ builtin_macro_create(US"_HAVE_LOOKUP_CDB");
+#endif
+#ifdef LOOKUP_DBM
+ builtin_macro_create(US"_HAVE_LOOKUP_DBM");
+#endif
+#ifdef LOOKUP_DNSDB
+ builtin_macro_create(US"_HAVE_LOOKUP_DNSDB");
+#endif
+#ifdef LOOKUP_DSEARCH
+ builtin_macro_create(US"_HAVE_LOOKUP_DSEARCH");
+#endif
+#ifdef LOOKUP_IBASE
+ builtin_macro_create(US"_HAVE_LOOKUP_IBASE");
+#endif
+#ifdef LOOKUP_LDAP
+ builtin_macro_create(US"_HAVE_LOOKUP_LDAP");
+#endif
+#ifdef EXPERIMENTAL_LMDB
+ builtin_macro_create(US"_HAVE_LOOKUP_LMDB");
+#endif
+#ifdef LOOKUP_MYSQL
+ builtin_macro_create(US"_HAVE_LOOKUP_MYSQL");
+#endif
+#ifdef LOOKUP_NIS
+ builtin_macro_create(US"_HAVE_LOOKUP_NIS");
+#endif
+#ifdef LOOKUP_NISPLUS
+ builtin_macro_create(US"_HAVE_LOOKUP_NISPLUS");
+#endif
+#ifdef LOOKUP_ORACLE
+ builtin_macro_create(US"_HAVE_LOOKUP_ORACLE");
+#endif
+#ifdef LOOKUP_PASSWD
+ builtin_macro_create(US"_HAVE_LOOKUP_PASSWD");
+#endif
+#ifdef LOOKUP_PGSQL
+ builtin_macro_create(US"_HAVE_LOOKUP_PGSQL");
+#endif
+#ifdef LOOKUP_REDIS
+ builtin_macro_create(US"_HAVE_LOOKUP_REDIS");
+#endif
+#ifdef LOOKUP_SQLITE
+ builtin_macro_create(US"_HAVE_LOOKUP_SQLITE");
+#endif
+#ifdef LOOKUP_TESTDB
+ builtin_macro_create(US"_HAVE_LOOKUP_TESTDB");
+#endif
+#ifdef LOOKUP_WHOSON
+ builtin_macro_create(US"_HAVE_LOOKUP_WHOSON");
+#endif
+
+#ifdef TRANSPORT_APPENDFILE
+# ifdef SUPPORT_MAILDIR
+ builtin_macro_create(US"_HAVE_TRANSPORT_APPEND_MAILDIR");
+# endif
+# ifdef SUPPORT_MAILSTORE
+ builtin_macro_create(US"_HAVE_TRANSPORT_APPEND_MAILSTORE");
+# endif
+# ifdef SUPPORT_MBX
+ builtin_macro_create(US"_HAVE_TRANSPORT_APPEND_MBX");
+# endif
+#endif
+
+#ifdef WITH_CONTENT_SCAN
+features_malware();
+#endif
+
+features_crypto();
+}
+
+
+static void
+options(void)
+{
+options_main();
+options_routers();
+options_transports();
+options_auths();
+options_logging();
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+options_tls();
+#endif
+}
+
+static void
+params(void)
+{
+#ifndef DISABLE_DKIM
+params_dkim();
+#endif
+}
+
+
+int
+main(void)
+{
+printf("#include \"exim.h\"\n");
+features();
+options();
+params();
+
+printf("macro_item * macros = &p%d;\n", mp_index-1);
+printf("macro_item * mlast = &p0;\n");
+exit(0);
+}
--- /dev/null
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 2017 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* Global functions */
+
+extern void spf(uschar *, int, const uschar *, ...);
+extern void builtin_macro_create(const uschar *);
+extern void builtin_macro_create_var(const uschar *, const uschar *);
+extern void options_from_list(optionlist *, unsigned, const uschar *, uschar *);
+
+extern void features_malware(void);
+extern void features_crypto(void);
+extern void options_main(void);
+extern void options_routers(void);
+extern void options_transports(void);
+extern void options_auths(void);
+extern void options_logging(void);
+extern void params_dkim(void);
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+extern void options_tls(void);
+#endif
+
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* When running in the test harness, the load average is fudged. */
#define OS_GETLOADAVG() \
- (running_in_test_harness? (test_harness_load_avg += 10) : os_getloadavg())
+ (f.running_in_test_harness? (test_harness_load_avg += 10) : os_getloadavg())
-/* The address_item structure has a word full of 1-bit flags. These macros
+/* The address_item structure has a struct full of 1-bit flags. These macros
manipulate them. */
-#define setflag(addr,flag) addr->flags |= (flag)
-#define clearflag(addr,flag) addr->flags &= ~(flag)
+#define setflag(addr, flagname) addr->flags.flagname = TRUE
+#define clearflag(addr, flagname) addr->flags.flagname = FALSE
-#define testflag(addr,flag) ((addr->flags & (flag)) != 0)
-#define testflagsall(addr,flag) ((addr->flags & (flag)) == (flag))
+#define testflag(addr, flagname) (addr->flags.flagname)
-#define copyflag(addrnew,addrold,flag) \
- addrnew->flags = (addrnew->flags & ~(flag)) | (addrold->flags & (flag))
-
-#define orflag(addrnew,addrold,flag) \
- addrnew->flags |= addrold->flags & (flag)
+#define copyflag(addrnew, addrold, flagname) \
+ addrnew->flags.flagname = addrold->flags.flagname
/* For almost all calls to convert things to printing characters, we want to
a no-op once an SSL session is in progress. */
#ifdef SUPPORT_TLS
-#define mac_smtp_fflush() if (tls_in.active < 0) fflush(smtp_out);
+#define mac_smtp_fflush() if (tls_in.active.sock < 0) fflush(smtp_out);
#else
#define mac_smtp_fflush() fflush(smtp_out);
#endif
/* Debugging control */
-#define DEBUG(x) if ((debug_selector & (x)) != 0)
-#define HDEBUG(x) if (host_checking || (debug_selector & (x)) != 0)
+#define DEBUG(x) if (debug_selector & (x))
+#define HDEBUG(x) if (host_checking || (debug_selector & (x)))
#define PTR_CHK(ptr) \
do { \
/* For identifying types of driver */
enum {
- DTYPE_NONE,
- DTYPE_ROUTER,
- DTYPE_TRANSPORT
+ EXIM_DTYPE_NONE,
+ EXIM_DTYPE_ROUTER,
+ EXIM_DTYPE_TRANSPORT
};
/* Error numbers for generating error messages when reading a message on the
/* Options bits for debugging. DEBUG_BIT() declares both a bit index and the
corresponding mask. Di_all is a special value recognized by decode_bits().
+These must match the debug_options table in globals.c .
Exim's code assumes in a number of places that the debug_selector is one
word, and this is exposed in the local_scan ABI. The D_v and D_local_scan bit
DEBUG_BIT(load),
DEBUG_BIT(lookup),
DEBUG_BIT(memory),
+ DEBUG_BIT(noutf8),
DEBUG_BIT(pid),
DEBUG_BIT(process_info),
DEBUG_BIT(queue_run),
#define D_any (D_all & \
~(D_v | \
+ D_noutf8 | \
D_pid | \
D_timestamp) )
D_load | \
D_local_scan | \
D_memory | \
+ D_noutf8 | \
D_pid | \
D_timestamp | \
D_resolver))
Li_arguments,
Li_deliver_time,
Li_delivery_size,
+ Li_dkim,
+ Li_dkim_verbose,
Li_dnssec,
Li_ident_timeout,
Li_incoming_interface,
Li_incoming_port,
+ Li_millisec,
Li_outgoing_interface,
Li_outgoing_port,
Li_pid,
+ Li_pipelining,
Li_proxy,
Li_queue_time,
Li_queue_time_overall,
+ Li_receive_time,
Li_received_sender,
Li_received_recipients,
Li_rejected_header,
#ifdef SUPPORT_I18N
# define ERRNO_UTF8_FWD (-49) /* target not supporting SMTPUTF8 */
#endif
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+# define ERRNO_REQUIRETLS (-50) /* REQUIRETLS session not started */
+#endif
/* These must be last, so all retry deferments can easily be identified */
#define ERRNO_RETRY_BASE (-51) /* Base to test against */
#define ERRNO_RRETRY (-51) /* Not time for routing */
+
+#define ERRNO_WARN_BASE (-52) /* Base to test against */
#define ERRNO_LRETRY (-52) /* Not time for local delivery */
#define ERRNO_HRETRY (-53) /* Not time for any remote host */
#define ERRNO_LOCAL_ONLY (-54) /* Local-only delivery */
#define vopt_callout_no_cache 0x0040 /* disable callout cache */
#define vopt_callout_recipsender 0x0080 /* use real sender to verify recip */
#define vopt_callout_recippmaster 0x0100 /* use postmaster to verify recip */
-#define vopt_success_on_redirect 0x0200
+#define vopt_callout_hold 0x0200 /* lazy close connection */
+#define vopt_success_on_redirect 0x0400
/* Values for fields in callout cache records */
/* Reasons why a host is unusable (for clearer log messages) */
-enum { hwhy_unknown, hwhy_retry, hwhy_failed, hwhy_deferred, hwhy_ignored };
+enum { hwhy_unknown, hwhy_retry, hwhy_insecure, hwhy_failed, hwhy_deferred,
+ hwhy_ignored };
/* Domain lookup types for routers */
-enum { lk_default, lk_byname, lk_bydns };
+#define LK_DEFAULT BIT(0)
+#define LK_BYNAME BIT(1)
+#define LK_BYDNS BIT(2) /* those 3 should be mutually exclusive */
+
+#define LK_IPV4_ONLY BIT(3)
+#define LK_IPV4_PREFER BIT(4)
/* Values for the self_code fields */
enum {
HOST_FIND_FAILED, /* failed to find the host */
HOST_FIND_AGAIN, /* could not resolve at this time */
+ HOST_FIND_SECURITY, /* dnssec required but not acheived */
HOST_FOUND, /* found host */
HOST_FOUND_LOCAL, /* found, but MX points to local host */
HOST_IGNORED /* found but ignored - used internally only */
/* Flags for host_find_bydns() */
-#define HOST_FIND_BY_SRV 0x0001
-#define HOST_FIND_BY_MX 0x0002
-#define HOST_FIND_BY_A 0x0004
-#define HOST_FIND_QUALIFY_SINGLE 0x0008
-#define HOST_FIND_SEARCH_PARENTS 0x0010
+#define HOST_FIND_BY_SRV BIT(0)
+#define HOST_FIND_BY_MX BIT(1)
+#define HOST_FIND_BY_A BIT(2)
+#define HOST_FIND_BY_AAAA BIT(3)
+#define HOST_FIND_QUALIFY_SINGLE BIT(4)
+#define HOST_FIND_SEARCH_PARENTS BIT(5)
+#define HOST_FIND_IPV4_FIRST BIT(6)
+#define HOST_FIND_IPV4_ONLY BIT(7)
/* Actions applied to specific messages. */
#define topt_no_body 0x040 /* Omit body */
#define topt_escape_headers 0x080 /* Apply escape check to headers */
#define topt_use_bdat 0x100 /* prepend chunks with RFC3030 BDAT header */
+#define topt_output_string 0x200 /* create string rather than write to fd */
+#define topt_continuation 0x400 /* do not reset buffer */
+#define topt_not_socket 0x800 /* cannot do socket-only syscalls */
+
+/* Options for smtp_write_command */
+
+enum {
+ SCMD_FLUSH = 0, /* write to kernel */
+ SCMD_MORE, /* write to kernel, but likely more soon */
+ SCMD_BUFFER /* stash in application cmd output buffer */
+};
/* Flags for recipient_block, used in DSN support */
ACL_WHERE_UNKNOWN /* Currently used by a ${acl:name} expansion */
};
+#define ACL_BIT_RCPT BIT(ACL_WHERE_RCPT)
+#define ACL_BIT_MAIL BIT(ACL_WHERE_MAIL)
+#define ACL_BIT_PREDATA BIT(ACL_WHERE_PREDATA)
+#define ACL_BIT_MIME BIT(ACL_WHERE_MIME)
+#define ACL_BIT_DKIM BIT(ACL_WHERE_DKIM)
+#define ACL_BIT_DATA BIT(ACL_WHERE_DATA)
+#ifndef DISABLE_PRDR
+# define ACL_BIT_PRDR BIT(ACL_WHERE_PRDR)
+#endif
+#define ACL_BIT_NOTSMTP BIT(ACL_WHERE_NOTSMTP)
+#define ACL_BIT_AUTH BIT(ACL_WHERE_AUTH)
+#define ACL_BIT_CONNECT BIT(ACL_WHERE_CONNECT)
+#define ACL_BIT_ETRN BIT(ACL_WHERE_ETRN)
+#define ACL_BIT_EXPN BIT(ACL_WHERE_EXPN)
+#define ACL_BIT_HELO BIT(ACL_WHERE_HELO)
+#define ACL_BIT_MAILAUTH BIT(ACL_WHERE_MAILAUTH)
+#define ACL_BIT_NOTSMTP_START BIT(ACL_WHERE_NOTSMTP_START)
+#define ACL_BIT_NOTQUIT BIT(ACL_WHERE_NOTQUIT)
+#define ACL_BIT_QUIT BIT(ACL_WHERE_QUIT)
+#define ACL_BIT_STARTTLS BIT(ACL_WHERE_STARTTLS)
+#define ACL_BIT_VRFY BIT(ACL_WHERE_VRFY)
+#define ACL_BIT_DELIVERY BIT(ACL_WHERE_DELIVERY)
+#define ACL_BIT_UNKNOWN BIT(ACL_WHERE_UNKNOWN)
+
+
/* Situations for spool_write_header() */
enum { SW_RECEIVING, SW_DELIVERING, SW_MODIFYING };
/* Codes for ESMTP facilities offered by peer */
-#define PEER_OFFERED_TLS BIT(0)
-#define PEER_OFFERED_IGNQ BIT(1)
-#define PEER_OFFERED_PRDR BIT(2)
-#define PEER_OFFERED_UTF8 BIT(3)
-#define PEER_OFFERED_DSN BIT(4)
-#define PEER_OFFERED_PIPE BIT(5)
-#define PEER_OFFERED_SIZE BIT(6)
-#define PEER_OFFERED_CHUNKING BIT(7)
+#define OPTION_TLS BIT(0)
+#define OPTION_IGNQ BIT(1)
+#define OPTION_PRDR BIT(2)
+#define OPTION_UTF8 BIT(3)
+#define OPTION_DSN BIT(4)
+#define OPTION_PIPE BIT(5)
+#define OPTION_SIZE BIT(6)
+#define OPTION_CHUNKING BIT(7)
+#define OPTION_REQUIRETLS BIT(8)
+#define OPTION_EARLY_PIPE BIT(9)
+
+/* Codes for tls_requiretls requests (usually by sender) */
+
+#define REQUIRETLS_MSG BIT(0) /* REQUIRETLS onward use */
/* Argument for *_getc */
#define GETC_BUFFER_UNLIMITED UINT_MAX
+/* UTF-8 chars for line-drawing */
+
+#define UTF8_DOWN_RIGHT "\xE2\x94\x8c"
+#define UTF8_HORIZ "\xE2\x94\x80"
+#define UTF8_VERT_RIGHT "\xE2\x94\x9C"
+#define UTF8_UP_RIGHT "\xE2\x94\x94"
+#define UTF8_VERT_2DASH "\xE2\x95\x8E"
+
+
+/* Options on tls_close */
+#define TLS_NO_SHUTDOWN 0
+#define TLS_SHUTDOWN_NOWAIT 1
+#define TLS_SHUTDOWN_WAIT 2
+
+
+#ifdef COMPILE_UTILITY
+# define ALARM(seconds) alarm(seconds);
+# define ALARM_CLR(seconds) alarm(seconds);
+#else
+/* For debugging of odd alarm-signal problems, stash caller info while the
+alarm is active. Clear it down on cancelling the alarm so we can tell there
+should not be one active. */
+
+# define ALARM(seconds) \
+ debug_selector & D_any \
+ ? (sigalarm_setter = CUS __FUNCTION__, alarm(seconds)) : alarm(seconds);
+# define ALARM_CLR(seconds) \
+ debug_selector & D_any \
+ ? (sigalarm_setter = NULL, alarm(seconds)) : alarm(seconds);
+#endif
+
+#define AUTHS_REGEX US"\\n250[\\s\\-]AUTH\\s+([\\-\\w \\t]+)(?:\\n|$)"
+
+#define EARLY_PIPE_FEATURE_NAME "X_PIPE_CONNECT"
+#define EARLY_PIPE_FEATURE_LEN 14
+
/* End of macros.h */
/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
* License: GPL
- * Copyright (c) The Exim Maintainers 2016
+ * Copyright (c) The Exim Maintainers 2015 - 2018
*/
/* Code for calling virus (malware) scanners. Called from acl.c. */
#include "exim.h"
-#ifdef WITH_CONTENT_SCAN
+#ifdef WITH_CONTENT_SCAN /* entire file */
-typedef enum {M_FPROTD, M_DRWEB, M_AVES, M_FSEC, M_KAVD, M_CMDL,
- M_SOPHIE, M_CLAMD, M_SOCK, M_MKSD, M_AVAST} scanner_t;
+typedef enum {
+#ifndef DISABLE_MAL_FFROTD
+ M_FPROTD,
+#endif
+#ifndef DISABLE_MAL_FFROT6D
+ M_FPROT6D,
+#endif
+#ifndef DISABLE_MAL_DRWEB
+ M_DRWEB,
+#endif
+#ifndef DISABLE_MAL_AVE
+ M_AVES,
+#endif
+#ifndef DISABLE_MAL_FSECURE
+ M_FSEC,
+#endif
+#ifndef DISABLE_MAL_KAV
+ M_KAVD,
+#endif
+#ifndef DISABLE_MAL_SOPHIE
+ M_SOPHIE,
+#endif
+#ifndef DISABLE_MAL_CLAM
+ M_CLAMD,
+#endif
+#ifndef DISABLE_MAL_MKS
+ M_MKSD,
+#endif
+#ifndef DISABLE_MAL_AVAST
+ M_AVAST,
+#endif
+#ifndef DISABLE_MAL_SOCK
+ M_SOCK,
+#endif
+#ifndef DISABLE_MAL_CMDLINE
+ M_CMDL,
+#endif
+ M_DUMMY
+ } scanner_t;
typedef enum {MC_NONE, MC_TCP, MC_UNIX, MC_STRM} contype_t;
static struct scan
{
contype_t conn;
} m_scans[] =
{
+#ifndef DISABLE_MAL_FFROTD
{ M_FPROTD, US"f-protd", US"localhost 10200-10204", MC_TCP },
+#endif
+#ifndef DISABLE_MAL_FFROT6D
+ { M_FPROT6D, US"f-prot6d", US"localhost 10200", MC_TCP },
+#endif
+#ifndef DISABLE_MAL_DRWEB
{ M_DRWEB, US"drweb", US"/usr/local/drweb/run/drwebd.sock", MC_STRM },
+#endif
+#ifndef DISABLE_MAL_AVE
{ M_AVES, US"aveserver", US"/var/run/aveserver", MC_UNIX },
+#endif
+#ifndef DISABLE_MAL_FSECURE
{ M_FSEC, US"fsecure", US"/var/run/.fsav", MC_UNIX },
+#endif
+#ifndef DISABLE_MAL_KAV
{ M_KAVD, US"kavdaemon", US"/var/run/AvpCtl", MC_UNIX },
- { M_CMDL, US"cmdline", NULL, MC_NONE },
+#endif
+#ifndef DISABLE_MAL_SOPHIE
{ M_SOPHIE, US"sophie", US"/var/run/sophie", MC_UNIX },
+#endif
+#ifndef DISABLE_MAL_CLAM
{ M_CLAMD, US"clamd", US"/tmp/clamd", MC_NONE },
- { M_SOCK, US"sock", US"/tmp/malware.sock", MC_STRM },
+#endif
+#ifndef DISABLE_MAL_MKS
{ M_MKSD, US"mksd", NULL, MC_NONE },
+#endif
+#ifndef DISABLE_MAL_AVAST
{ M_AVAST, US"avast", US"/var/run/avast/scan.sock", MC_STRM },
+#endif
+#ifndef DISABLE_MAL_SOCK
+ { M_SOCK, US"sock", US"/tmp/malware.sock", MC_STRM },
+#endif
+#ifndef DISABLE_MAL_CMDLINE
+ { M_CMDL, US"cmdline", NULL, MC_NONE },
+#endif
{ -1, NULL, NULL, MC_NONE } /* end-marker */
};
+/******************************************************************************/
+# ifdef MACRO_PREDEF /* build solely to predefine macros */
+
+# include "macro_predef.h"
+
+void
+features_malware(void)
+{
+const struct scan * sc;
+const uschar * s;
+uschar * t;
+uschar buf[64];
+
+spf(buf, sizeof(buf), US"_HAVE_MALWARE_");
+
+for (sc = m_scans; sc->scancode != -1; sc++)
+ {
+ for(s = sc->name, t = buf+14; *s; s++) if (*s != '-') *t++ = toupper(*s);
+ *t = '\0';
+ builtin_macro_create(buf);
+ }
+}
+
+/******************************************************************************/
+# else /*!MACRO_PREDEF, main build*/
+
+
+#define MALWARE_TIMEOUT 120 /* default timeout, seconds */
+
+static const uschar * malware_regex_default = US ".+";
+static const pcre * malware_default_re = NULL;
+
+
+
+#ifndef DISABLE_MAL_CLAM
/* The maximum number of clamd servers that are supported in the configuration */
-#define MAX_CLAMD_SERVERS 32
-#define MAX_CLAMD_SERVERS_S "32"
+# define MAX_CLAMD_SERVERS 32
+# define MAX_CLAMD_SERVERS_S "32"
typedef struct clamd_address {
uschar * hostspec;
unsigned tcp_port;
unsigned retry;
} clamd_address;
-
-#ifndef nelements
-# define nelements(arr) (sizeof(arr) / sizeof(arr[0]))
#endif
-#define MALWARE_TIMEOUT 120 /* default timeout, seconds */
-
-
-#define DRWEBD_SCAN_CMD (1) /* scan file, buffer or diskfile */
-#define DRWEBD_RETURN_VIRUSES (1<<0) /* ask daemon return to us viruses names from report */
-#define DRWEBD_IS_MAIL (1<<19) /* say to daemon that format is "archive MAIL" */
-
-#define DERR_READ_ERR (1<<0) /* read error */
-#define DERR_NOMEMORY (1<<2) /* no memory */
-#define DERR_TIMEOUT (1<<9) /* scan timeout has run out */
-#define DERR_BAD_CALL (1<<15) /* wrong command */
+#ifndef DISABLE_MAL_DRWEB
+# define DRWEBD_SCAN_CMD (1) /* scan file, buffer or diskfile */
+# define DRWEBD_RETURN_VIRUSES (1<<0) /* ask daemon return to us viruses names from report */
+# define DRWEBD_IS_MAIL (1<<19) /* say to daemon that format is "archive MAIL" */
-
-static const uschar * malware_regex_default = US ".+";
-static const pcre * malware_default_re = NULL;
+# define DERR_READ_ERR (1<<0) /* read error */
+# define DERR_NOMEMORY (1<<2) /* no memory */
+# define DERR_TIMEOUT (1<<9) /* scan timeout has run out */
+# define DERR_BAD_CALL (1<<15) /* wrong command */
static const uschar * drweb_re_str = US "infected\\swith\\s*(.+?)$";
static const pcre * drweb_re = NULL;
+#endif
+#ifndef DISABLE_MAL_FSECURE
static const uschar * fsec_re_str = US "\\S{0,5}INFECTED\\t[^\\t]*\\t([^\\t]+)\\t\\S*$";
static const pcre * fsec_re = NULL;
+#endif
+#ifndef DISABLE_MAL_KAV
static const uschar * kav_re_sus_str = US "suspicion:\\s*(.+?)\\s*$";
static const uschar * kav_re_inf_str = US "infected:\\s*(.+?)\\s*$";
static const pcre * kav_re_sus = NULL;
static const pcre * kav_re_inf = NULL;
+#endif
+#ifndef DISABLE_MAL_AVAST
static const uschar * ava_re_clean_str = US "(?!\\\\)\\t\\[\\+\\]";
-static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d\\.\\d\\t\\d\\s(.*)";
+static const uschar * ava_re_virus_str = US "(?!\\\\)\\t\\[L\\]\\d+\\.0\\t0\\s(.*)";
+static const uschar * ava_re_error_str = US "(?!\\\\)\\t\\[E\\]\\d+\\.0\\tError\\s\\d+\\s(.*)";
static const pcre * ava_re_clean = NULL;
static const pcre * ava_re_virus = NULL;
+static const pcre * ava_re_error = NULL;
+#endif
+
+#ifndef DISABLE_MAL_FFROT6D
+static const uschar * fprot6d_re_error_str = US "^\\d+\\s<(.+?)>$";
+static const uschar * fprot6d_re_virus_str = US "^\\d+\\s<infected:\\s+(.+?)>\\s+.+$";
+static const pcre * fprot6d_re_error = NULL;
+static const pcre * fprot6d_re_virus = NULL;
+#endif
/******************************************************************************/
+#ifndef DISABLE_MAL_KAV
/* Routine to check whether a system is big- or little-endian.
Ripped from http://www.faqs.org/faqs/graphics/fileformats-faq/part4/section-7.html
Needed for proper kavdaemon implementation. Sigh. */
-#define BIG_MY_ENDIAN 0
-#define LITTLE_MY_ENDIAN 1
+# define BIG_MY_ENDIAN 0
+# define LITTLE_MY_ENDIAN 1
static int test_byte_order(void);
static inline int
test_byte_order()
{
short int word = 0x0001;
- char *byte = (char *) &word;
+ char *byte = CS &word;
return(byte[0] ? LITTLE_MY_ENDIAN : BIG_MY_ENDIAN);
}
+#endif
BOOL malware_ok = FALSE;
extern uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
+/* Some (currently avast only) use backslash escaped whitespace,
+this function undoes these escapes */
+
+static inline void
+unescape(uschar *p)
+{
+uschar *p0;
+for (; *p; ++p)
+ if (*p == '\\' && (isspace(p[1]) || p[1] == '\\'))
+ for (p0 = p; *p0; ++p0) *p0 = p0[1];
+}
+/* --- malware_*_defer --- */
static inline int
-malware_errlog_defer(const uschar * str)
+malware_panic_defer(const uschar * str)
{
log_write(0, LOG_MAIN|LOG_PANIC, "malware acl condition: %s", str);
return DEFER;
}
-
-static int
-m_errlog_defer(struct scan * scanent, const uschar * hostport,
+static inline int
+malware_log_defer(const uschar * str)
+{
+log_write(0, LOG_MAIN, "malware acl condition: %s", str);
+return DEFER;
+}
+/* --- m_*_defer --- */
+static inline int
+m_panic_defer(struct scan * scanent, const uschar * hostport,
const uschar * str)
{
-return malware_errlog_defer(string_sprintf("%s %s : %s",
+return malware_panic_defer(string_sprintf("%s %s : %s",
scanent->name, hostport ? hostport : CUS"", str));
}
-static int
-m_errlog_defer_3(struct scan * scanent, const uschar * hostport,
+static inline int
+m_log_defer(struct scan * scanent, const uschar * hostport,
+ const uschar * str)
+{
+return malware_log_defer(string_sprintf("%s %s : %s",
+ scanent->name, hostport ? hostport : CUS"", str));
+}
+/* --- m_*_defer_3 */
+static inline int
+m_panic_defer_3(struct scan * scanent, const uschar * hostport,
const uschar * str, int fd_to_close)
{
(void) close(fd_to_close);
-return m_errlog_defer(scanent, hostport, str);
+return m_panic_defer(scanent, hostport, str);
}
/*************************************************/
+#ifndef DISABLE_MAL_CLAM
/* Only used by the Clamav code, which is working from a list of servers and
uses the returned in_addr to get a second connection to the same system.
*/
static inline int
m_tcpsocket(const uschar * hostname, unsigned int port,
- host_item * host, uschar ** errstr)
+ host_item * host, uschar ** errstr, const blob * fastopen_blob)
{
-return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5, host, errstr);
+return ip_connectedsocket(SOCK_STREAM, hostname, port, port, 5,
+ host, errstr, fastopen_blob);
}
+#endif
static int
m_sock_send(int sock, uschar * buf, int cnt, uschar ** errstr)
{
int ovector[10*3];
int i = pcre_exec(cre, NULL, CS text, Ustrlen(text), 0, 0,
- ovector, nelements(ovector));
+ ovector, nelem(ovector));
uschar * substr = NULL;
if (i >= 2) /* Got it */
pcre_get_substring(CS text, ovector, i, 1, (const char **) &substr);
if (!(list_ele = string_nextinlist(list, sep, NULL, 0)))
*errstr = US listerr;
else
+ {
+ DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "RE: ",
+ string_printing(list_ele));
cre = m_pcre_compile(CUS list_ele, errstr);
+ }
return cre;
}
+#ifndef DISABLE_MAL_MKS
/* ============= private routines for the "mksd" scanner type ============== */
-#include <sys/uio.h>
+# include <sys/uio.h>
static inline int
mksd_writev (int sock, struct iovec * iov, int iovcnt)
while (i < 0 && errno == EINTR);
if (i <= 0)
{
- (void) malware_errlog_defer(
+ (void) malware_panic_defer(
US"unable to write to mksd UNIX socket (/var/run/mksd/socket)");
return -1;
}
static inline int
mksd_read_lines (int sock, uschar *av_buffer, int av_buffer_size, int tmo)
{
+client_conn_ctx cctx = {.sock = sock};
int offset = 0;
int i;
do
{
- i = ip_recv(sock, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
+ i = ip_recv(&cctx, av_buffer+offset, av_buffer_size-offset, tmo-time(NULL));
if (i <= 0)
{
- (void) malware_errlog_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
+ (void) malware_panic_defer(US"unable to read from mksd UNIX socket (/var/run/mksd/socket)");
return -1;
}
/* offset == av_buffer_size -> buffer full */
if (offset == av_buffer_size)
{
- (void) malware_errlog_defer(US"malformed reply received from mksd");
+ (void) malware_panic_defer(US"malformed reply received from mksd");
return -1;
}
} while (av_buffer[offset-1] != '\n');
case 'A': /* ERR */
if ((p = strchr (line, '\n')) != NULL)
*p = '\0';
- return m_errlog_defer(scanent, NULL,
+ return m_panic_defer(scanent, NULL,
string_sprintf("scanner failed: %s", line));
default: /* VIR */
return OK;
}
}
- return m_errlog_defer(scanent, NULL,
+ return m_panic_defer(scanent, NULL,
string_sprintf("malformed reply received: %s", line));
}
}
return mksd_parse_line (scanent, CS av_buffer);
}
+#endif /* MKSD */
+#ifndef DISABLE_MAL_CLAM
static int
clamd_option(clamd_address * cd, const uschar * optstr, int * subsep)
{
return FAIL;
return OK;
}
+#endif
+
+
/*************************************************
* Scan content for malware *
Arguments:
malware_re match condition for "malware="
- eml_filename the file holding the email to be scanned
+ scan_filename the file holding the email to be scanned, if we're faking
+ this up for the -bmalware test, else NULL
timeout if nonzero, non-default timeoutl
- faking whether or not we're faking this up for the -bmalware test
Returns: Exim message processing code (OK, FAIL, DEFER, ...)
where true means malware was found (condition applies)
*/
static int
-malware_internal(const uschar * malware_re, const uschar * eml_filename,
- int timeout, BOOL faking)
+malware_internal(const uschar * malware_re, const uschar * scan_filename,
+ int timeout)
{
int sep = 0;
const uschar *av_scanner_work = av_scanner;
uschar * errstr;
struct scan * scanent;
const uschar * scanner_options;
-int sock = -1;
+client_conn_ctx malware_daemon_ctx = {.sock = -1};
time_t tmo;
-
-/* make sure the eml mbox file is spooled up */
-if (!(mbox_file = spool_mbox(&mbox_size, faking ? eml_filename : NULL)))
- return malware_errlog_defer(US"error while creating mbox spool file");
-
-/* none of our current scanners need the mbox
- file as a stream, so we can close it right away */
-(void)fclose(mbox_file);
+uschar * eml_filename, * eml_dir;
if (!malware_re)
return FAIL; /* empty means "don't match anything" */
+/* Ensure the eml mbox file is spooled up */
+
+if (!(mbox_file = spool_mbox(&mbox_size, scan_filename, &eml_filename)))
+ return malware_panic_defer(US"error while creating mbox spool file");
+
+/* None of our current scanners need the mbox file as a stream (they use
+the name), so we can close it right away. Get the directory too. */
+
+(void) fclose(mbox_file);
+eml_dir = string_copyn(eml_filename, Ustrrchr(eml_filename, '/') - eml_filename);
+
/* parse 1st option */
- if ( (strcmpic(malware_re, US"false") == 0) ||
- (Ustrcmp(malware_re,"0") == 0) )
+if (strcmpic(malware_re, US"false") == 0 || Ustrcmp(malware_re,"0") == 0)
return FAIL; /* explicitly no matching */
/* special cases (match anything except empty) */
{
if ( !malware_default_re
&& !(malware_default_re = m_pcre_compile(malware_regex_default, &errstr)))
- return malware_errlog_defer(errstr);
+ return malware_panic_defer(errstr);
malware_re = malware_regex_default;
re = malware_default_re;
}
/* compile the regex, see if it works */
else if (!(re = m_pcre_compile(malware_re, &errstr)))
- return malware_errlog_defer(errstr);
-
-/* Reset sep that is set by previous string_nextinlist() call */
-sep = 0;
+ return malware_panic_defer(errstr);
/* if av_scanner starts with a dollar, expand it first */
if (*av_scanner == '$')
{
if (!(av_scanner_work = expand_string(av_scanner)))
- return malware_errlog_defer(
+ return malware_panic_defer(
string_sprintf("av_scanner starts with $, but expansion failed: %s",
expand_string_message));
{
/* find the scanner type from the av_scanner option */
if (!(scanner_name = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
- return malware_errlog_defer(US"av_scanner configuration variable is empty");
+ return malware_panic_defer(US"av_scanner configuration variable is empty");
if (!timeout) timeout = MALWARE_TIMEOUT;
tmo = time(NULL) + timeout;
for (scanent = m_scans; ; scanent++)
{
if (!scanent->name)
- return malware_errlog_defer(string_sprintf("unknown scanner type '%s'",
+ return malware_panic_defer(string_sprintf("unknown scanner type '%s'",
scanner_name));
if (strcmpic(scanner_name, US scanent->name) != 0)
continue;
+ DEBUG(D_acl) debug_printf_indent("Malware scan: %s tmo=%s\n",
+ scanner_name, readconf_printtime(timeout));
+
if (!(scanner_options = string_nextinlist(&av_scanner_work, &sep, NULL, 0)))
scanner_options = scanent->options_default;
if (scanent->conn == MC_NONE)
break;
+
+ DEBUG(D_acl) debug_printf_indent("%15s%10s%s\n", "", "socket: ", scanner_options);
switch(scanent->conn)
{
- case MC_TCP: sock = ip_tcpsocket(scanner_options, &errstr, 5); break;
- case MC_UNIX: sock = ip_unixsocket(scanner_options, &errstr); break;
- case MC_STRM: sock = ip_streamsocket(scanner_options, &errstr, 5); break;
- default: /* compiler quietening */ break;
+ case MC_TCP:
+ malware_daemon_ctx.sock = ip_tcpsocket(scanner_options, &errstr, 5); break;
+ case MC_UNIX:
+ malware_daemon_ctx.sock = ip_unixsocket(scanner_options, &errstr); break;
+ case MC_STRM:
+ malware_daemon_ctx.sock = ip_streamsocket(scanner_options, &errstr, 5); break;
+ default:
+ /* compiler quietening */ break;
}
- if (sock < 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
+ if (malware_daemon_ctx.sock < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
break;
}
- DEBUG(D_acl) debug_printf_indent("Malware scan: %s tmo %s\n", scanner_name, readconf_printtime(timeout));
switch (scanent->scancode)
{
+#ifndef DISABLE_MAL_FFROTD
case M_FPROTD: /* "f-protd" scanner type -------------------------------- */
{
uschar *fp_scan_option;
scanner_name, scanrequest);
/* send scan request */
- if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
+ if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
- while ((len = recv_line(sock, buf, sizeof(buf), tmo)) >= 0)
+ while ((len = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) >= 0)
if (len > 0)
{
if (Ustrstr(buf, US"<detected type=\"") != NULL)
}
if (len < -1)
{
- (void)close(sock);
+ (void)close(malware_daemon_ctx.sock);
return DEFER;
}
break;
} /* f-protd */
+#endif
+
+#ifndef DISABLE_MAL_FFROT6D
+ case M_FPROT6D: /* "f-prot6d" scanner type ----------------------------------- */
+ {
+ int bread;
+ uschar * e;
+ uschar * linebuffer;
+ uschar * scanrequest;
+ uschar av_buffer[1024];
+
+ if ((!fprot6d_re_virus && !(fprot6d_re_virus = m_pcre_compile(fprot6d_re_virus_str, &errstr)))
+ || (!fprot6d_re_error && !(fprot6d_re_error = m_pcre_compile(fprot6d_re_error_str, &errstr))))
+ return malware_panic_defer(errstr);
+
+ scanrequest = string_sprintf("SCAN FILE %s\n", eml_filename);
+ DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s: %s\n",
+ scanner_name, scanrequest);
+
+ if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest), &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
+
+ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+
+ if (bread <= 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+ string_sprintf("unable to read from socket (%s)", strerror(errno)),
+ malware_daemon_ctx.sock);
+
+ if (bread == sizeof(av_buffer))
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"buffer too small", malware_daemon_ctx.sock);
+
+ av_buffer[bread] = '\0';
+ linebuffer = string_copy(av_buffer);
+
+ m_sock_send(malware_daemon_ctx.sock, US"QUIT\n", 5, 0);
+
+ if ((e = m_pcre_exec(fprot6d_re_error, linebuffer)))
+ return m_panic_defer_3(scanent, CUS callout_address,
+ string_sprintf("scanner reported error (%s)", e), malware_daemon_ctx.sock);
+
+ if (!(malware_name = m_pcre_exec(fprot6d_re_virus, linebuffer)))
+ malware_name = NULL;
+
+ break;
+ } /* f-prot6d */
+#endif
+#ifndef DISABLE_MAL_DRWEB
case M_DRWEB: /* "drweb" scanner type ----------------------------------- */
/* v0.1 - added support for tcp sockets */
/* v0.0 - initial release -- support for unix sockets */
{
/* calc file size */
if ((drweb_fd = open(CCS eml_filename, O_RDONLY)) == -1)
- return m_errlog_defer_3(scanent, NULL,
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("can't open spool file %s: %s",
eml_filename, strerror(errno)),
- sock);
+ malware_daemon_ctx.sock);
if ((fsize = lseek(drweb_fd, 0, SEEK_END)) == -1)
{
- int err = errno;
+ int err;
+badseek: err = errno;
(void)close(drweb_fd);
- return m_errlog_defer_3(scanent, NULL,
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("can't seek spool file %s: %s",
eml_filename, strerror(err)),
- sock);
+ malware_daemon_ctx.sock);
}
fsize_uint = (unsigned int) fsize;
if ((off_t)fsize_uint != fsize)
{
(void)close(drweb_fd);
- return m_errlog_defer_3(scanent, NULL,
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("seeking spool file %s, size overflow",
eml_filename),
- sock);
+ malware_daemon_ctx.sock);
}
drweb_slen = htonl(fsize);
- lseek(drweb_fd, 0, SEEK_SET);
+ if (lseek(drweb_fd, 0, SEEK_SET) < 0)
+ goto badseek;
DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s remote scan [%s]\n",
scanner_name, scanner_options);
/* send scan request */
- if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
- (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
- (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) ||
- (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
+ if ((send(malware_daemon_ctx.sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
+ (send(malware_daemon_ctx.sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
+ (send(malware_daemon_ctx.sock, &drweb_fin, sizeof(drweb_fin), 0) < 0) ||
+ (send(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), 0) < 0))
{
(void)close(drweb_fd);
- return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+ return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
"unable to send commands to socket (%s)", scanner_options),
- sock);
+ malware_daemon_ctx.sock);
}
if (!(drweb_fbuf = US malloc(fsize_uint)))
{
(void)close(drweb_fd);
- return m_errlog_defer_3(scanent, NULL,
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("unable to allocate memory %u for file (%s)",
fsize_uint, eml_filename),
- sock);
+ malware_daemon_ctx.sock);
}
if ((result = read (drweb_fd, drweb_fbuf, fsize)) == -1)
int err = errno;
(void)close(drweb_fd);
free(drweb_fbuf);
- return m_errlog_defer_3(scanent, NULL,
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("can't read spool file %s: %s",
eml_filename, strerror(err)),
- sock);
+ malware_daemon_ctx.sock);
}
(void)close(drweb_fd);
/* send file body to socket */
- if (send(sock, drweb_fbuf, fsize, 0) < 0)
+ if (send(malware_daemon_ctx.sock, drweb_fbuf, fsize, 0) < 0)
{
free(drweb_fbuf);
- return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+ return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
"unable to send file body to socket (%s)", scanner_options),
- sock);
+ malware_daemon_ctx.sock);
}
}
else
scanner_name, scanner_options);
/* send scan request */
- if ((send(sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
- (send(sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
- (send(sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
- (send(sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
- (send(sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
- return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+ if ((send(malware_daemon_ctx.sock, &drweb_cmd, sizeof(drweb_cmd), 0) < 0) ||
+ (send(malware_daemon_ctx.sock, &drweb_flags, sizeof(drweb_flags), 0) < 0) ||
+ (send(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), 0) < 0) ||
+ (send(malware_daemon_ctx.sock, eml_filename, Ustrlen(eml_filename), 0) < 0) ||
+ (send(malware_daemon_ctx.sock, &drweb_fin, sizeof(drweb_fin), 0) < 0))
+ return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
"unable to send commands to socket (%s)", scanner_options),
- sock);
+ malware_daemon_ctx.sock);
}
/* wait for result */
- if (!recv_len(sock, &drweb_rc, sizeof(drweb_rc), tmo))
- return m_errlog_defer_3(scanent, CUS callout_address,
- US"unable to read return code", sock);
+ if (!recv_len(malware_daemon_ctx.sock, &drweb_rc, sizeof(drweb_rc), tmo))
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"unable to read return code", malware_daemon_ctx.sock);
drweb_rc = ntohl(drweb_rc);
- if (!recv_len(sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
- return m_errlog_defer_3(scanent, CUS callout_address,
- US"unable to read the number of viruses", sock);
+ if (!recv_len(malware_daemon_ctx.sock, &drweb_vnum, sizeof(drweb_vnum), tmo))
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"unable to read the number of viruses", malware_daemon_ctx.sock);
drweb_vnum = ntohl(drweb_vnum);
/* "virus(es) found" if virus number is > 0 */
if (drweb_vnum)
{
int i;
+ gstring * g = NULL;
/* setup default virus name */
malware_name = US"unknown";
/* read and concatenate virus names into one string */
for (i = 0; i < drweb_vnum; i++)
{
- int size = 0, off = 0, ovector[10*3];
+ int ovector[10*3];
+
/* read the size of report */
- if (!recv_len(sock, &drweb_slen, sizeof(drweb_slen), tmo))
- return m_errlog_defer_3(scanent, CUS callout_address,
- US"cannot read report size", sock);
+ if (!recv_len(malware_daemon_ctx.sock, &drweb_slen, sizeof(drweb_slen), tmo))
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"cannot read report size", malware_daemon_ctx.sock);
drweb_slen = ntohl(drweb_slen);
tmpbuf = store_get(drweb_slen);
/* read report body */
- if (!recv_len(sock, tmpbuf, drweb_slen, tmo))
- return m_errlog_defer_3(scanent, CUS callout_address,
- US"cannot read report string", sock);
+ if (!recv_len(malware_daemon_ctx.sock, tmpbuf, drweb_slen, tmo))
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"cannot read report string", malware_daemon_ctx.sock);
tmpbuf[drweb_slen] = '\0';
/* try matcher on the line, grab substring */
result = pcre_exec(drweb_re, NULL, CS tmpbuf, Ustrlen(tmpbuf), 0, 0,
- ovector, nelements(ovector));
+ ovector, nelem(ovector));
if (result >= 2)
{
const char * pre_malware_nb;
pcre_get_substring(CS tmpbuf, ovector, result, 1, &pre_malware_nb);
if (i==0) /* the first name we just copy to malware_name */
- malware_name = string_append(NULL, &size, &off,
- 1, pre_malware_nb);
+ g = string_cat(NULL, US pre_malware_nb);
+ /*XXX could be string_append_listele? */
else /* concatenate each new virus name to previous */
- malware_name = string_append(malware_name, &size, &off,
- 2, "/", pre_malware_nb);
+ g = string_append(g, 2, "/", pre_malware_nb);
pcre_free_substring(pre_malware_nb);
}
}
+ malware_name = string_from_gstring(g);
}
else
{
* DERR_CRC_ERROR, DERR_READSOCKET, DERR_WRITE_ERR
* and others are ignored */
if (drweb_s)
- return m_errlog_defer_3(scanent, CUS callout_address,
+ return m_panic_defer_3(scanent, CUS callout_address,
string_sprintf("drweb daemon retcode 0x%x (%s)", drweb_rc, drweb_s),
- sock);
+ malware_daemon_ctx.sock);
/* no virus found */
malware_name = NULL;
}
break;
} /* drweb */
+#endif
+#ifndef DISABLE_MAL_AVE
case M_AVES: /* "aveserver" scanner type -------------------------------- */
{
uschar buf[32768];
/* read aveserver's greeting and see if it is ready (2xx greeting) */
buf[0] = 0;
- recv_line(sock, buf, sizeof(buf), tmo);
+ recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo);
if (buf[0] != '2') /* aveserver is having problems */
- return m_errlog_defer_3(scanent, CUS callout_address,
+ return m_panic_defer_3(scanent, CUS callout_address,
string_sprintf("unavailable (Responded: %s).",
- ((buf[0] != 0) ? buf : (uschar *)"nothing") ),
- sock);
+ ((buf[0] != 0) ? buf : US "nothing") ),
+ malware_daemon_ctx.sock);
/* prepare our command */
(void)string_format(buf, sizeof(buf), "SCAN bPQRSTUW %s\r\n",
/* and send it */
DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s %s\n",
scanner_name, buf);
- if (m_sock_send(sock, buf, Ustrlen(buf), &errstr) < 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
+ if (m_sock_send(malware_daemon_ctx.sock, buf, Ustrlen(buf), &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
malware_name = NULL;
result = 0;
/* read response lines, find malware name and final response */
- while (recv_line(sock, buf, sizeof(buf), tmo) > 0)
+ while (recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo) > 0)
{
if (buf[0] == '2')
break;
if (buf[0] == '5') /* aveserver is having problems */
{
- result = m_errlog_defer(scanent, CUS callout_address,
+ result = m_panic_defer(scanent, CUS callout_address,
string_sprintf("unable to scan file %s (Responded: %s).",
eml_filename, buf));
break;
}
}
- if (m_sock_send(sock, US"quit\r\n", 6, &errstr) < 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
+ if (m_sock_send(malware_daemon_ctx.sock, US"quit\r\n", 6, &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
/* read aveserver's greeting and see if it is ready (2xx greeting) */
buf[0] = 0;
- recv_line(sock, buf, sizeof(buf), tmo);
+ recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo);
if (buf[0] != '2') /* aveserver is having problems */
- return m_errlog_defer_3(scanent, CUS callout_address,
+ return m_panic_defer_3(scanent, CUS callout_address,
string_sprintf("unable to quit dialogue (Responded: %s).",
- ((buf[0] != 0) ? buf : (uschar *)"nothing") ),
- sock);
+ ((buf[0] != 0) ? buf : US "nothing") ),
+ malware_daemon_ctx.sock);
if (result == DEFER)
{
- (void)close(sock);
+ (void)close(malware_daemon_ctx.sock);
return DEFER;
}
break;
} /* aveserver */
+#endif
+#ifndef DISABLE_MAL_FSECURE
case M_FSEC: /* "fsecure" scanner type ---------------------------------- */
{
int i, j, bread = 0;
scanner_name, scanner_options);
/* pass options */
memset(av_buffer, 0, sizeof(av_buffer));
- for (i = 0; i != nelements(cmdopt); i++)
+ for (i = 0; i != nelem(cmdopt); i++)
{
- if (m_sock_send(sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
+ if (m_sock_send(malware_daemon_ctx.sock, cmdopt[i], Ustrlen(cmdopt[i]), &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
- bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
if (bread > 0) av_buffer[bread]='\0';
if (bread < 0)
- return m_errlog_defer_3(scanent, CUS callout_address,
+ return m_panic_defer_3(scanent, CUS callout_address,
string_sprintf("unable to read answer %d (%s)", i, strerror(errno)),
- sock);
+ malware_daemon_ctx.sock);
for (j = 0; j < bread; j++)
if (av_buffer[j] == '\r' || av_buffer[j] == '\n')
av_buffer[j] ='@';
/* pass the mailfile to fsecure */
file_name = string_sprintf("SCAN\t%s\n", eml_filename);
- if (m_sock_send(sock, file_name, Ustrlen(file_name), &errstr) < 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
+ if (m_sock_send(malware_daemon_ctx.sock, file_name, Ustrlen(file_name), &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
/* set up match */
/* todo also SUSPICION\t */
{
errno = ETIMEDOUT;
i = av_buffer+sizeof(av_buffer)-p;
- if ((bread= ip_recv(sock, p, i-1, tmo-time(NULL))) < 0)
- return m_errlog_defer_3(scanent, CUS callout_address,
+ if ((bread= ip_recv(&malware_daemon_ctx, p, i-1, tmo-time(NULL))) < 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
string_sprintf("unable to read result (%s)", strerror(errno)),
- sock);
+ malware_daemon_ctx.sock);
for (p[bread] = '\0'; (q = Ustrchr(p, '\n')); p = q+1)
{
fsec_found:
break;
} /* fsecure */
+#endif
+#ifndef DISABLE_MAL_KAV
case M_KAVD: /* "kavdaemon" scanner type -------------------------------- */
{
time_t t;
scanner_name, scanner_options);
/* send scan request */
- if (m_sock_send(sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
+ if (m_sock_send(malware_daemon_ctx.sock, scanrequest, Ustrlen(scanrequest)+1, &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
/* wait for result */
- if (!recv_len(sock, tmpbuf, 2, tmo))
- return m_errlog_defer_3(scanent, CUS callout_address,
- US"unable to read 2 bytes from socket.", sock);
+ if (!recv_len(malware_daemon_ctx.sock, tmpbuf, 2, tmo))
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"unable to read 2 bytes from socket.", malware_daemon_ctx.sock);
/* get errorcode from one nibble */
kav_rc = tmpbuf[ test_byte_order()==LITTLE_MY_ENDIAN ? 0 : 1 ] & 0x0F;
switch(kav_rc)
{
case 5: case 6: /* improper kavdaemon configuration */
- return m_errlog_defer_3(scanent, CUS callout_address,
+ return m_panic_defer_3(scanent, CUS callout_address,
US"please reconfigure kavdaemon to NOT disinfect or remove infected files.",
- sock);
+ malware_daemon_ctx.sock);
case 1:
- return m_errlog_defer_3(scanent, CUS callout_address,
- US"reported 'scanning not completed' (code 1).", sock);
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"reported 'scanning not completed' (code 1).", malware_daemon_ctx.sock);
case 7:
- return m_errlog_defer_3(scanent, CUS callout_address,
- US"reported 'kavdaemon damaged' (code 7).", sock);
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"reported 'kavdaemon damaged' (code 7).", malware_daemon_ctx.sock);
}
/* code 8 is not handled, since it is ambiguous. It appears mostly on
if (report_flag == 1)
{
/* read report size */
- if (!recv_len(sock, &kav_reportlen, 4, tmo))
- return m_errlog_defer_3(scanent, CUS callout_address,
- US"cannot read report size", sock);
+ if (!recv_len(malware_daemon_ctx.sock, &kav_reportlen, 4, tmo))
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"cannot read report size", malware_daemon_ctx.sock);
/* it's possible that avp returns av_buffer[1] == 1 but the
reportsize is 0 (!?) */
/* coverity[tainted_data] */
while (kav_reportlen > 0)
{
- if ((bread = recv_line(sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0)
+ if ((bread = recv_line(malware_daemon_ctx.sock, tmpbuf, sizeof(tmpbuf), tmo)) < 0)
break;
kav_reportlen -= bread+1;
break;
}
+#endif
+#ifndef DISABLE_MAL_CMDLINE
case M_CMDL: /* "cmdline" scanner type ---------------------------------- */
{
const uschar *cmdline_scanner = scanner_options;
uschar *p;
if (!cmdline_scanner)
- return m_errlog_defer(scanent, NULL, errstr);
+ return m_panic_defer(scanent, NULL, errstr);
/* find scanner output trigger */
cmdline_trigger_re = m_pcre_nextinlist(&av_scanner_work, &sep,
"missing trigger specification", &errstr);
if (!cmdline_trigger_re)
- return m_errlog_defer(scanent, NULL, errstr);
+ return m_panic_defer(scanent, NULL, errstr);
/* find scanner name regex */
cmdline_regex_re = m_pcre_nextinlist(&av_scanner_work, &sep,
"missing virus name regex specification", &errstr);
if (!cmdline_regex_re)
- return m_errlog_defer(scanent, NULL, errstr);
+ return m_panic_defer(scanent, NULL, errstr);
/* prepare scanner call; despite the naming, file_name holds a directory
name which is documented as the value given to %s. */
{
int err = errno;
signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
- return m_errlog_defer(scanent, NULL,
+ return m_panic_defer(scanent, NULL,
string_sprintf("call (%s) failed: %s.", commandline, strerror(err)));
}
scanner_fd = fileno(scanner_out);
- file_name = string_sprintf("%s/scan/%s/%s_scanner_output",
- spool_directory, message_id, message_id);
+ file_name = string_sprintf("%s/%s_scanner_output", eml_dir, message_id);
if (!(scanner_record = modefopen(file_name, "wb", SPOOL_MODE)))
{
int err = errno;
(void) pclose(scanner_out);
signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
- return m_errlog_defer(scanent, NULL, string_sprintf(
+ return m_panic_defer(scanent, NULL, string_sprintf(
"opening scanner output file (%s) failed: %s.",
file_name, strerror(err)));
}
break;
(void) pclose(scanner_out);
signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
- return m_errlog_defer(scanent, NULL, string_sprintf(
+ return m_panic_defer(scanent, NULL, string_sprintf(
"unable to read from scanner (%s): %s",
commandline, strerror(err)));
}
/* short write */
(void) pclose(scanner_out);
signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
- return m_errlog_defer(scanent, NULL, string_sprintf(
+ return m_panic_defer(scanent, NULL, string_sprintf(
"short write on scanner output file (%s).", file_name));
}
putc('\n', scanner_record);
sep = pclose(scanner_out);
signal(SIGCHLD,eximsigchld); signal(SIGPIPE,eximsigpipe);
if (sep != 0)
- return m_errlog_defer(scanent, NULL,
+ return m_panic_defer(scanent, NULL,
sep == -1
? string_sprintf("running scanner failed: %s", strerror(sep))
: string_sprintf("scanner returned error code: %d", sep));
malware_name = NULL;
break;
} /* cmdline */
+#endif
+#ifndef DISABLE_MAL_SOPHIE
case M_SOPHIE: /* "sophie" scanner type --------------------------------- */
{
int bread = 0;
DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan [%s]\n",
scanner_name, scanner_options);
- if ( write(sock, file_name, Ustrlen(file_name)) < 0
- || write(sock, "\n", 1) != 1
+ if ( write(malware_daemon_ctx.sock, file_name, Ustrlen(file_name)) < 0
+ || write(malware_daemon_ctx.sock, "\n", 1) != 1
)
- return m_errlog_defer_3(scanent, CUS callout_address,
+ return m_panic_defer_3(scanent, CUS callout_address,
string_sprintf("unable to write to UNIX socket (%s)", scanner_options),
- sock);
+ malware_daemon_ctx.sock);
/* wait for result */
memset(av_buffer, 0, sizeof(av_buffer));
- if ((bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
- return m_errlog_defer_3(scanent, CUS callout_address,
+ if ((bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL))) <= 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
string_sprintf("unable to read from UNIX socket (%s)", scanner_options),
- sock);
+ malware_daemon_ctx.sock);
/* infected ? */
if (av_buffer[0] == '1') {
malware_name = string_copy(&av_buffer[2]);
}
else if (!strncmp(CS av_buffer, "-1", 2))
- return m_errlog_defer_3(scanent, CUS callout_address,
- US"scanner reported error", sock);
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"scanner reported error", malware_daemon_ctx.sock);
else /* all ok, no virus */
malware_name = NULL;
break;
}
+#endif
+#ifndef DISABLE_MAL_CLAM
case M_CLAMD: /* "clamd" scanner type ----------------------------------- */
{
/* This code was originally contributed by David Saez */
* The zINSTREAM command was introduced with ClamAV 0.95, which marked
* STREAM deprecated; see: http://wiki.clamav.net/bin/view/Main/UpgradeNotes095
* In Exim, we use SCAN if using a Unix-domain socket or explicitly told that
-* the TCP-connected daemon is actually local; otherwise we use zINSTREAM unless
-* WITH_OLD_CLAMAV_STREAM is defined.
+* the TCP-connected daemon is actually local; otherwise we use zINSTREAM
* See Exim bug 926 for details. */
uschar *p, *vname, *result_tag;
int bread=0;
- uschar * file_name;
uschar av_buffer[1024];
uschar *hostname = US"";
host_item connhost;
BOOL use_scan_command = FALSE;
clamd_address * cv[MAX_CLAMD_SERVERS];
int num_servers = 0;
-#ifdef WITH_OLD_CLAMAV_STREAM
- unsigned int port;
- uschar av_buffer2[1024];
- int sockData;
-#else
uint32_t send_size, send_final_zeroblock;
-#endif
+ blob cmd_str;
/*XXX if unixdomain socket, only one server supported. Needs fixing;
there's no reason we should not mix local and remote servers */
/* parse options */
if (clamd_option(cd, sublist, &subsep) != OK)
- return m_errlog_defer(scanent, NULL,
+ return m_panic_defer(scanent, NULL,
string_sprintf("bad option '%s'", scanner_options));
cv[0] = cd;
}
sublist = scanner_options;
if (!(cd->hostspec = string_nextinlist(&sublist, &subsep, NULL, 0)))
{
- (void) m_errlog_defer(scanent, NULL,
+ (void) m_panic_defer(scanent, NULL,
string_sprintf("missing address: '%s'", scanner_options));
continue;
}
if (!(s = string_nextinlist(&sublist, &subsep, NULL, 0)))
{
- (void) m_errlog_defer(scanent, NULL,
+ (void) m_panic_defer(scanent, NULL,
string_sprintf("missing port: '%s'", scanner_options));
continue;
}
/* parse options */
/*XXX should these options be common over scanner types? */
if (clamd_option(cd, sublist, &subsep) != OK)
- return m_errlog_defer(scanent, NULL,
+ return m_panic_defer(scanent, NULL,
string_sprintf("bad option '%s'", scanner_options));
cv[num_servers++] = cd;
if (num_servers >= MAX_CLAMD_SERVERS)
{
- (void) m_errlog_defer(scanent, NULL,
+ (void) m_panic_defer(scanent, NULL,
US"More than " MAX_CLAMD_SERVERS_S " clamd servers "
"specified; only using the first " MAX_CLAMD_SERVERS_S );
break;
/* check if we have at least one server */
if (!num_servers)
- return m_errlog_defer(scanent, NULL,
+ return m_panic_defer(scanent, NULL,
US"no useable server addresses in malware configuration option.");
}
/* See the discussion of response formats below to see why we really
don't like colons in filenames when passing filenames to ClamAV. */
if (use_scan_command && Ustrchr(eml_filename, ':'))
- return m_errlog_defer(scanent, NULL,
+ return m_panic_defer(scanent, NULL,
string_sprintf("local/SCAN mode incompatible with" \
" : in path to email filename [%s]", eml_filename));
+ /* Set up the very first data we will be sending */
+ if (!use_scan_command)
+ { cmd_str.data = US"zINSTREAM"; cmd_str.len = 10; }
+ else
+ {
+ cmd_str.data = string_sprintf("SCAN %s\n", eml_filename);
+ cmd_str.len = Ustrlen(cmd_str.data);
+ }
+
/* We have some network servers specified */
if (num_servers)
{
while (num_servers > 0)
{
- int i = random_number( num_servers );
+ int i = random_number(num_servers);
clamd_address * cd = cv[i];
DEBUG(D_acl) debug_printf_indent("trying server name %s, port %u\n",
* on both connections (as one host could resolve to multiple ips) */
for (;;)
{
- sock= m_tcpsocket(cd->hostspec, cd->tcp_port, &connhost, &errstr);
- if (sock >= 0)
+ /*XXX we trust that the cmd_str is ideempotent */
+ if ((malware_daemon_ctx.sock = m_tcpsocket(cd->hostspec, cd->tcp_port,
+ &connhost, &errstr, &cmd_str)) >= 0)
{
/* Connection successfully established with a server */
hostname = cd->hostspec;
+ cmd_str.len = 0;
break;
}
if (cd->retry <= 0) break;
while (cd->retry > 0) cd->retry = sleep(cd->retry);
}
- if (sock >= 0)
+ if (malware_daemon_ctx.sock >= 0)
break;
- (void) m_errlog_defer(scanent, CUS callout_address, errstr);
+ (void) m_panic_defer(scanent, CUS callout_address, errstr);
/* Remove the server from the list. XXX We should free the memory */
num_servers--;
}
if (num_servers == 0)
- return m_errlog_defer(scanent, NULL, US"all servers failed");
+ return m_panic_defer(scanent, NULL, US"all servers failed");
}
else
for (;;)
{
- if ((sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0)
+ if ((malware_daemon_ctx.sock = ip_unixsocket(cv[0]->hostspec, &errstr)) >= 0)
{
hostname = cv[0]->hostspec;
break;
}
if (cv[0]->retry <= 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
+ return m_panic_defer(scanent, CUS callout_address, errstr);
while (cv[0]->retry > 0) cv[0]->retry = sleep(cv[0]->retry);
}
if (!use_scan_command)
{
-#ifdef WITH_OLD_CLAMAV_STREAM
- /* "STREAM\n" command, get back a "PORT <N>\n" response, send data to
- * that port on a second connection; then in the scan-method-neutral
- * part, read the response back on the original connection. */
-
- DEBUG(D_acl) debug_printf_indent(
- "Malware scan: issuing %s old-style remote scan (PORT)\n",
- scanner_name);
-
- /* Pass the string to ClamAV (7 = "STREAM\n") */
- if (m_sock_send(sock, US"STREAM\n", 7, &errstr) < 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
-
- memset(av_buffer2, 0, sizeof(av_buffer2));
- bread = ip_recv(sock, av_buffer2, sizeof(av_buffer2), tmo-time(NULL));
-
- if (bread < 0)
- return m_errlog_defer_3(scanent, CUS callout_address,
- string_sprintf("unable to read PORT from socket (%s)",
- strerror(errno)),
- sock);
-
- if (bread == sizeof(av_buffer2))
- return m_errlog_defer_3(scanent, CUS callout_address,
- "buffer too small", sock);
-
- if (!(*av_buffer2))
- return m_errlog_defer_3(scanent, CUS callout_address,
- "ClamAV returned null", sock);
-
- av_buffer2[bread] = '\0';
- if( sscanf(CS av_buffer2, "PORT %u\n", &port) != 1 )
- return m_errlog_defer_3(scanent, CUS callout_address,
- string_sprintf("Expected port information from clamd, got '%s'",
- av_buffer2),
- sock);
-
- sockData = m_tcpsocket(connhost.address, port, NULL, &errstr);
- if (sockData < 0)
- return m_errlog_defer_3(scanent, CUS callout_address, errstr, sock);
-
-# define CLOSE_SOCKDATA (void)close(sockData)
-#else /* WITH_OLD_CLAMAV_STREAM not defined */
/* New protocol: "zINSTREAM\n" followed by a sequence of <length><data>
chunks, <n> a 4-byte number (network order), terminated by a zero-length
chunk. */
"Malware scan: issuing %s new-style remote scan (zINSTREAM)\n",
scanner_name);
- /* Pass the string to ClamAV (10 = "zINSTREAM\0") */
- if (send(sock, "zINSTREAM", 10, 0) < 0)
- return m_errlog_defer_3(scanent, CUS hostname,
- string_sprintf("unable to send zINSTREAM to socket (%s)",
- strerror(errno)),
- sock);
-
-# define CLOSE_SOCKDATA /**/
-#endif
+ /* Pass the string to ClamAV (10 = "zINSTREAM\0"), if not already sent */
+ if (cmd_str.len)
+ if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0)
+ return m_panic_defer_3(scanent, CUS hostname,
+ string_sprintf("unable to send zINSTREAM to socket (%s)",
+ strerror(errno)),
+ malware_daemon_ctx.sock);
/* calc file size */
if ((clam_fd = open(CS eml_filename, O_RDONLY)) < 0)
{
int err = errno;
- CLOSE_SOCKDATA;
- return m_errlog_defer_3(scanent, NULL,
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("can't open spool file %s: %s",
eml_filename, strerror(err)),
- sock);
+ malware_daemon_ctx.sock);
}
if ((fsize = lseek(clam_fd, 0, SEEK_END)) < 0)
{
- int err = errno;
- CLOSE_SOCKDATA; (void)close(clam_fd);
- return m_errlog_defer_3(scanent, NULL,
+ int err;
+b_seek: err = errno;
+ (void)close(clam_fd);
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("can't seek spool file %s: %s",
eml_filename, strerror(err)),
- sock);
+ malware_daemon_ctx.sock);
}
fsize_uint = (unsigned int) fsize;
if ((off_t)fsize_uint != fsize)
{
- CLOSE_SOCKDATA; (void)close(clam_fd);
- return m_errlog_defer_3(scanent, NULL,
+ (void)close(clam_fd);
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("seeking spool file %s, size overflow",
eml_filename),
- sock);
+ malware_daemon_ctx.sock);
}
- lseek(clam_fd, 0, SEEK_SET);
+ if (lseek(clam_fd, 0, SEEK_SET) < 0)
+ goto b_seek;
if (!(clamav_fbuf = US malloc(fsize_uint)))
{
- CLOSE_SOCKDATA; (void)close(clam_fd);
- return m_errlog_defer_3(scanent, NULL,
+ (void)close(clam_fd);
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("unable to allocate memory %u for file (%s)",
fsize_uint, eml_filename),
- sock);
+ malware_daemon_ctx.sock);
}
if ((result = read(clam_fd, clamav_fbuf, fsize_uint)) < 0)
{
int err = errno;
- free(clamav_fbuf); CLOSE_SOCKDATA; (void)close(clam_fd);
- return m_errlog_defer_3(scanent, NULL,
+ free(clamav_fbuf); (void)close(clam_fd);
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("can't read spool file %s: %s",
eml_filename, strerror(err)),
- sock);
+ malware_daemon_ctx.sock);
}
(void)close(clam_fd);
/* send file body to socket */
-#ifdef WITH_OLD_CLAMAV_STREAM
- if (send(sockData, clamav_fbuf, fsize_uint, 0) < 0)
- {
- free(clamav_fbuf); CLOSE_SOCKDATA;
- return m_errlog_defer_3(scanent, NULL,
- string_sprintf("unable to send file body to socket (%s:%u)",
- hostname, port),
- sock);
- }
-#else
send_size = htonl(fsize_uint);
send_final_zeroblock = 0;
- if ((send(sock, &send_size, sizeof(send_size), 0) < 0) ||
- (send(sock, clamav_fbuf, fsize_uint, 0) < 0) ||
- (send(sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
+ if ((send(malware_daemon_ctx.sock, &send_size, sizeof(send_size), 0) < 0) ||
+ (send(malware_daemon_ctx.sock, clamav_fbuf, fsize_uint, 0) < 0) ||
+ (send(malware_daemon_ctx.sock, &send_final_zeroblock, sizeof(send_final_zeroblock), 0) < 0))
{
free(clamav_fbuf);
- return m_errlog_defer_3(scanent, NULL,
+ return m_panic_defer_3(scanent, NULL,
string_sprintf("unable to send file body to socket (%s)", hostname),
- sock);
+ malware_daemon_ctx.sock);
}
-#endif
free(clamav_fbuf);
-
- CLOSE_SOCKDATA;
-#undef CLOSE_SOCKDATA
}
else
{ /* use scan command */
scanned twice, in the broken out files and from the original .eml.
Since ClamAV now handles emails (and has for quite some time) we can
just use the email file itself. */
- /* Pass the string to ClamAV (7 = "SCAN \n" + \0) */
- file_name = string_sprintf("SCAN %s\n", eml_filename);
+ /* Pass the string to ClamAV (7 = "SCAN \n" + \0), if not already sent */
DEBUG(D_acl) debug_printf_indent(
"Malware scan: issuing %s local-path scan [%s]\n",
scanner_name, scanner_options);
- if (send(sock, file_name, Ustrlen(file_name), 0) < 0)
- return m_errlog_defer_3(scanent, CUS callout_address,
- string_sprintf("unable to write to socket (%s)", strerror(errno)),
- sock);
+ if (cmd_str.len)
+ if (send(malware_daemon_ctx.sock, cmd_str.data, cmd_str.len, 0) < 0)
+ return m_panic_defer_3(scanent, CUS callout_address,
+ string_sprintf("unable to write to socket (%s)", strerror(errno)),
+ malware_daemon_ctx.sock);
/* Do not shut down the socket for writing; a user report noted that
* clamd 0.70 does not react well to this. */
/* Read the result */
memset(av_buffer, 0, sizeof(av_buffer));
- bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
- (void)close(sock);
- sock = -1;
+ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+ (void)close(malware_daemon_ctx.sock);
+ malware_daemon_ctx.sock = -1;
+ malware_daemon_ctx.tls_ctx = NULL;
if (bread <= 0)
- return m_errlog_defer(scanent, CUS callout_address,
+ return m_panic_defer(scanent, CUS callout_address,
string_sprintf("unable to read from socket (%s)",
errno == 0 ? "EOF" : strerror(errno)));
if (bread == sizeof(av_buffer))
- return m_errlog_defer(scanent, CUS callout_address,
+ return m_panic_defer(scanent, CUS callout_address,
US"buffer too small");
/* We're now assured of a NULL at the end of av_buffer */
passing a filename to clamd). */
if (!(*av_buffer))
- return m_errlog_defer(scanent, CUS callout_address,
+ return m_panic_defer(scanent, CUS callout_address,
US"ClamAV returned null");
/* strip newline at the end (won't be present for zINSTREAM)
/* colon in returned output? */
if(!(p = Ustrchr(av_buffer,':')))
- return m_errlog_defer(scanent, CUS callout_address, string_sprintf(
+ return m_panic_defer(scanent, CUS callout_address, string_sprintf(
"ClamAV returned malformed result (missing colon): %s",
av_buffer));
}
else if (Ustrcmp(result_tag, "ERROR") == 0)
- return m_errlog_defer(scanent, CUS callout_address,
+ return m_panic_defer(scanent, CUS callout_address,
string_sprintf("ClamAV returned: %s", av_buffer));
else if (Ustrcmp(result_tag, "OK") == 0)
}
else
- return m_errlog_defer(scanent, CUS callout_address,
+ return m_panic_defer(scanent, CUS callout_address,
string_sprintf("unparseable response from ClamAV: {%s}", av_buffer));
break;
} /* clamd */
+#endif
+#ifndef DISABLE_MAL_SOCK
case M_SOCK: /* "sock" scanner type ------------------------------------- */
/* This code was derived by Martin Poole from the clamd code contributed
by David Saez and the cmdline code
const pcre *sockline_name_re;
/* find scanner command line */
- if ((sockline_scanner = string_nextinlist(&av_scanner_work, &sep,
- NULL, 0)))
+ if ( (sockline_scanner = string_nextinlist(&av_scanner_work, &sep,
+ NULL, 0))
+ && *sockline_scanner
+ )
{ /* check for no expansions apart from one %s */
uschar * s = Ustrchr(sockline_scanner, '%');
if (s++)
if ((*s != 's' && *s != '%') || Ustrchr(s+1, '%'))
- return m_errlog_defer_3(scanent, NULL,
- US"unsafe sock scanner call spec", sock);
+ return m_panic_defer_3(scanent, NULL,
+ US"unsafe sock scanner call spec", malware_daemon_ctx.sock);
}
else
sockline_scanner = sockline_scanner_default;
+ DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "cmdline: ",
+ string_printing(sockline_scanner));
/* find scanner output trigger */
sockline_trig_re = m_pcre_nextinlist(&av_scanner_work, &sep,
"missing trigger specification", &errstr);
if (!sockline_trig_re)
- return m_errlog_defer_3(scanent, NULL, errstr, sock);
+ return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.sock);
/* find virus name regex */
sockline_name_re = m_pcre_nextinlist(&av_scanner_work, &sep,
"missing virus name regex specification", &errstr);
if (!sockline_name_re)
- return m_errlog_defer_3(scanent, NULL, errstr, sock);
+ return m_panic_defer_3(scanent, NULL, errstr, malware_daemon_ctx.sock);
/* prepare scanner call - security depends on expansions check above */
- commandline = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id, message_id);
- commandline = string_sprintf( CS sockline_scanner, CS commandline);
-
+ commandline = string_sprintf( CS sockline_scanner, CS eml_filename);
+ DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "expanded: ",
+ string_printing(commandline));
/* Pass the command string to the socket */
- if (m_sock_send(sock, commandline, Ustrlen(commandline), &errstr) < 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
+ if (m_sock_send(malware_daemon_ctx.sock, commandline, Ustrlen(commandline), &errstr) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
/* Read the result */
- bread = ip_recv(sock, av_buffer, sizeof(av_buffer), tmo-time(NULL));
+ bread = ip_recv(&malware_daemon_ctx, av_buffer, sizeof(av_buffer), tmo-time(NULL));
if (bread <= 0)
- return m_errlog_defer_3(scanent, CUS callout_address,
+ return m_panic_defer_3(scanent, CUS callout_address,
string_sprintf("unable to read from socket (%s)", strerror(errno)),
- sock);
+ malware_daemon_ctx.sock);
if (bread == sizeof(av_buffer))
- return m_errlog_defer_3(scanent, CUS callout_address,
- US"buffer too small", sock);
+ return m_panic_defer_3(scanent, CUS callout_address,
+ US"buffer too small", malware_daemon_ctx.sock);
av_buffer[bread] = '\0';
linebuffer = string_copy(av_buffer);
+ DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "answer: ",
+ string_printing(linebuffer));
/* try trigger match */
if (regex_match_and_setup(sockline_trig_re, linebuffer, 0, -1))
{
if (!(malware_name = m_pcre_exec(sockline_name_re, av_buffer)))
malware_name = US "unknown";
+ DEBUG(D_acl) debug_printf_indent("%15s%10s'%s'\n", "", "name: ",
+ string_printing(malware_name));
}
else /* no virus found */
malware_name = NULL;
break;
}
+#endif
+#ifndef DISABLE_MAL_MKS
case M_MKSD: /* "mksd" scanner type ------------------------------------- */
{
char *mksd_options_end;
|| mksd_maxproc < 1
|| mksd_maxproc > 32
)
- return m_errlog_defer(scanent, CUS callout_address,
+ return m_panic_defer(scanent, CUS callout_address,
string_sprintf("invalid option '%s'", scanner_options));
}
- if((sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
- return m_errlog_defer(scanent, CUS callout_address, errstr);
+ if((malware_daemon_ctx.sock = ip_unixsocket(US "/var/run/mksd/socket", &errstr)) < 0)
+ return m_panic_defer(scanent, CUS callout_address, errstr);
malware_name = NULL;
DEBUG(D_acl) debug_printf_indent("Malware scan: issuing %s scan\n", scanner_name);
- if ((retval = mksd_scan_packed(scanent, sock, eml_filename, tmo)) != OK)
+ if ((retval = mksd_scan_packed(scanent, malware_daemon_ctx.sock, eml_filename, tmo)) != OK)
{
- close (sock);
+ close (malware_daemon_ctx.sock);
return retval;
}
break;
}
+#endif
+#ifndef DISABLE_MAL_AVAST
case M_AVAST: /* "avast" scanner type ----------------------------------- */
{
- int ovector[1*3];
uschar buf[1024];
uschar * scanrequest;
enum {AVA_HELO, AVA_OPT, AVA_RSP, AVA_DONE} avast_stage;
int nread;
+ uschar * error_message = NULL;
+ BOOL more_data = FALSE;
+ BOOL strict = TRUE;
/* According to Martin Tuma @avast the protocol uses "escaped
whitespace", that is, every embedded whitespace is backslash
and the [ ] marker.
[+] - not infected
[L] - infected
- [E] - some error occured
- Such marker follows the first non-escaped TAB. */
+ [E] - some error occurred
+ Such marker follows the first non-escaped TAB. For more information
+ see avast-protocol(5)
+
+ We observed two cases:
+ -> SCAN /file
+ <- /file [E]0.0 Error 13 Permission denied
+ <- 451 SCAN Engine error 13 permission denied
+
+ -> SCAN /file
+ <- /file… [E]3.0 Error 41120 The file is a decompression bomb
+ <- /file… [+]2.0
+ <- /file… [+]2.0 0 Eicar Test Virus!!!
+ <- 200 SCAN OK
+
+ If the scanner returns 4xx, DEFER is a good decision, combined
+ with a panic log entry, to get the admin's attention.
+
+ If the scanner returns 200, we reject it as malware, if found any,
+ or, in case of an error, we set the malware message to the error
+ string.
+
+ Some of the >= 42000 errors are message related - usually some
+ broken archives etc, but some of them are e.g. license related.
+ Once the license expires the engine starts returning errors for
+ every scanning attempt. I¹ have the full list of the error codes
+ but it is not a public API and is subject to change. It is hard
+ for me to say what you should do in case of an engine error. You
+ can have a “Treat * unscanned file as infection” policy or “Treat
+ unscanned file as clean” policy. ¹) Jakub Bednar
+
+ */
+
if ( ( !ava_re_clean
&& !(ava_re_clean = m_pcre_compile(ava_re_clean_str, &errstr)))
|| ( !ava_re_virus
&& !(ava_re_virus = m_pcre_compile(ava_re_virus_str, &errstr)))
+ || ( !ava_re_error
+ && !(ava_re_error = m_pcre_compile(ava_re_error_str, &errstr)))
)
- return malware_errlog_defer(errstr);
+ return malware_panic_defer(errstr);
/* wait for result */
for (avast_stage = AVA_HELO;
- (nread = recv_line(sock, buf, sizeof(buf), tmo)) > 0;
+ (nread = recv_line(malware_daemon_ctx.sock, buf, sizeof(buf), tmo)) > 0;
)
{
int slen = Ustrlen(buf);
if (slen >= 1)
{
- DEBUG(D_acl) debug_printf_indent("got from avast: %s\n", buf);
+
+ /* Multi line responses are bracketed between 210 … and nnn … */
+ if (Ustrncmp(buf, "210", 3) == 0)
+ {
+ more_data = 1;
+ continue;
+ }
+ else if (more_data && isdigit(buf[0])) more_data = 0;
+
switch (avast_stage)
{
case AVA_HELO:
+ if (more_data) continue;
if (Ustrncmp(buf, "220", 3) != 0)
goto endloop; /* require a 220 */
goto sendreq;
case AVA_OPT:
- if (Ustrncmp(buf, "210", 3) == 0)
- break; /* ignore 210 responses */
+ if (more_data) continue;
if (Ustrncmp(buf, "200", 3) != 0)
goto endloop; /* require a 200 */
if ((scanrequest = string_nextinlist(&av_scanner_work, &sep,
NULL, 0)))
{
+ if (Ustrcmp(scanrequest, "pass_unscanned") == 0)
+ {
+ DEBUG(D_acl) debug_printf_indent("pass unscanned files as clean\n");
+ strict = FALSE;
+ goto sendreq;
+ }
scanrequest = string_sprintf("%s\n", scanrequest);
avast_stage = AVA_OPT; /* just sent option */
+ DEBUG(D_acl) debug_printf_indent("send to avast OPTION: %s", scanrequest);
}
else
{
- scanrequest = string_sprintf("SCAN %s/scan/%s\n",
- spool_directory, message_id);
+ scanrequest = string_sprintf("SCAN %s\n", eml_dir);
avast_stage = AVA_RSP; /* just sent command */
+ DEBUG(D_acl) debug_printf_indent("send to avast REQUEST: SCAN %s\n", eml_dir);
}
/* send config-cmd or scan-request to socket */
len = Ustrlen(scanrequest);
- if (send(sock, scanrequest, len, 0) < 0)
+ if (send(malware_daemon_ctx.sock, scanrequest, len, 0) == -1)
{
scanrequest[len-1] = '\0';
- return m_errlog_defer_3(scanent, CUS callout_address, string_sprintf(
+ return m_panic_defer_3(scanent, CUS callout_address, string_sprintf(
"unable to send request '%s' to socket (%s): %s",
- scanrequest, scanner_options, strerror(errno)), sock);
+ scanrequest, scanner_options, strerror(errno)), malware_daemon_ctx.sock);
}
break;
}
case AVA_RSP:
- if (Ustrncmp(buf, "210", 3) == 0)
- break; /* ignore the "210 SCAN DATA" message */
- if (pcre_exec(ava_re_clean, NULL, CS buf, slen,
- 0, 0, ovector, nelements(ovector)) > 0)
- break;
+ if (isdigit(buf[0])) /* We're done */
+ goto endloop;
- if ((malware_name = m_pcre_exec(ava_re_virus, buf)))
- { /* remove backslash in front of [whitespace|backslash] */
- uschar * p, * p0;
- for (p = malware_name; *p; ++p)
- if (*p == '\\' && (isspace(p[1]) || p[1] == '\\'))
- for (p0 = p; *p0; ++p0) *p0 = p0[1];
+ if (malware_name) /* Nothing else matters, just read on */
+ break;
- avast_stage = AVA_DONE;
- goto endloop;
- }
+ if (pcre_exec(ava_re_clean, NULL, CS buf, slen, 0, 0, NULL, 0) == 0)
+ break;
- if (Ustrncmp(buf, "200 SCAN OK", 11) == 0)
- { /* we're done finally */
- if (send(sock, "QUIT\n", 5, 0) < 0) /* courtesy */
- return m_errlog_defer_3(scanent, CUS callout_address,
- string_sprintf(
- "unable to send quit request to socket (%s): %s",
- scanner_options, strerror(errno)),
- sock);
- malware_name = NULL;
- avast_stage = AVA_DONE;
- goto endloop;
- }
+ if ((malware_name = m_pcre_exec(ava_re_virus, buf)))
+ {
+ unescape(malware_name);
+ DEBUG(D_acl)
+ debug_printf_indent("unescaped malware name: '%s'\n", malware_name);
+ break;
+ }
+
+ if (strict) /* treat scanner errors as malware */
+ {
+ if ((malware_name = m_pcre_exec(ava_re_error, buf)))
+ {
+ unescape(malware_name);
+ DEBUG(D_acl)
+ debug_printf_indent("unescaped error message: '%s'\n", malware_name);
+ break;
+ }
+ }
+ else if (pcre_exec(ava_re_error, NULL, CS buf, slen, 0, 0, NULL, 0) == 0)
+ {
+ log_write(0, LOG_MAIN, "internal scanner error (ignored): %s", buf);
+ break;
+ }
+
+ /* here also for any unexpected response from the scanner */
+ DEBUG(D_acl) debug_printf("avast response not handled: '%s'\n", buf);
- /* here for any unexpected response from the scanner */
goto endloop;
- case AVA_DONE: log_write(0, LOG_PANIC, "%s:%d:%s: should not happen",
+ default: log_write(0, LOG_PANIC, "%s:%d:%s: should not happen",
__FILE__, __LINE__, __FUNCTION__);
}
}
}
+
endloop:
- switch(avast_stage)
- {
- case AVA_HELO:
- case AVA_OPT:
- case AVA_RSP: return m_errlog_defer_3(scanent, CUS callout_address,
- nread >= 0
- ? string_sprintf(
- "invalid response from scanner: '%s'", buf)
- : nread == -1
- ? US"EOF from scanner"
- : US"timeout from scanner",
- sock);
- default: break;
- }
+ if (nread == -1) error_message = US"EOF from scanner";
+ else if (nread < 0) error_message = US"timeout from scanner";
+ else if (nread == 0) error_message = US"got nothing from scanner";
+ else if (buf[0] != '2') error_message = buf;
+
+ DEBUG(D_acl) debug_printf_indent("sent to avast QUIT\n");
+ if (send(malware_daemon_ctx.sock, "QUIT\n", 5, 0) == -1)
+ return m_panic_defer_3(scanent, CUS callout_address,
+ string_sprintf("unable to send quit request to socket (%s): %s",
+ scanner_options, strerror(errno)), malware_daemon_ctx.sock);
+
+ if (error_message)
+ return m_panic_defer_3(scanent, CUS callout_address, error_message, malware_daemon_ctx.sock);
+
}
- break;
+#endif
} /* scanner type switch */
- if (sock >= 0)
- (void) close (sock);
+ if (malware_daemon_ctx.sock >= 0)
+ (void) close (malware_daemon_ctx.sock);
malware_ok = TRUE; /* set "been here, done that" marker */
}
int
malware(const uschar * malware_re, int timeout)
{
-uschar * scan_filename;
-int ret;
+int ret = malware_internal(malware_re, NULL, timeout);
-scan_filename = string_sprintf("%s/scan/%s/%s.eml",
- spool_directory, message_id, message_id);
-ret = malware_internal(malware_re, scan_filename, timeout, FALSE);
if (ret == DEFER) av_failed = TRUE;
-
return ret;
}
return_path = US"";
recipients_list = NULL;
receive_add_recipient(US"malware-victim@example.net", -1);
-enable_dollar_recipients = TRUE;
+f.enable_dollar_recipients = TRUE;
-ret = malware_internal(US"*", eml_filename, 0, TRUE);
+ret = malware_internal(US"*", eml_filename, 0);
Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
spool_mbox_ok = 1;
{
if (!malware_default_re)
malware_default_re = regex_must_compile(malware_regex_default, FALSE, TRUE);
+
+#ifndef DISABLE_MAL_DRWEB
if (!drweb_re)
drweb_re = regex_must_compile(drweb_re_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_FSECURE
if (!fsec_re)
fsec_re = regex_must_compile(fsec_re_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_KAV
if (!kav_re_sus)
kav_re_sus = regex_must_compile(kav_re_sus_str, FALSE, TRUE);
if (!kav_re_inf)
kav_re_inf = regex_must_compile(kav_re_inf_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_AVAST
if (!ava_re_clean)
ava_re_clean = regex_must_compile(ava_re_clean_str, FALSE, TRUE);
if (!ava_re_virus)
ava_re_virus = regex_must_compile(ava_re_virus_str, FALSE, TRUE);
+if (!ava_re_error)
+ ava_re_error = regex_must_compile(ava_re_error_str, FALSE, TRUE);
+#endif
+#ifndef DISABLE_MAL_FFROT6D
+if (!fprot6d_re_error)
+ fprot6d_re_error = regex_must_compile(fprot6d_re_error_str, FALSE, TRUE);
+if (!fprot6d_re_virus)
+ fprot6d_re_virus = regex_must_compile(fprot6d_re_virus_str, FALSE, TRUE);
+#endif
}
+
+void
+malware_show_supported(FILE * f)
+{
+struct scan * sc;
+fprintf(f, "Malware:");
+for (sc = m_scans; sc->scancode != (scanner_t)-1; sc++) fprintf(f, " %s", sc->name);
+fprintf(f, "\n");
+}
+
+
+# endif /*!MACRO_PREDEF*/
#endif /*WITH_CONTENT_SCAN*/
/*
* vi: aw ai sw=2
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for matching strings */
for; partial matching is all handled inside search_find(). Note that there is
no search_close() because of the caching arrangements. */
-handle = search_open(filename, search_type, 0, NULL, NULL);
-if (handle == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
- search_error_message);
+if (!(handle = search_open(filename, search_type, 0, NULL, NULL)))
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", search_error_message);
result = search_find(handle, filename, keyquery, partial, affix, affixlen,
starflags, &expand_setup);
-if (result == NULL) return search_find_defer? DEFER : FAIL;
-if (valueptr != NULL) *valueptr = result;
+if (!result) return f.search_find_defer? DEFER : FAIL;
+if (valueptr) *valueptr = result;
expand_nmax = expand_setup;
return OK;
/* If the list is empty, the answer is no. Skip the debugging output for
an unnamed list. */
-if (*listptr == NULL)
+if (!*listptr)
{
- HDEBUG(D_lists)
- {
- if (ot != NULL) debug_printf("%s no (option unset)\n", ot);
- }
+ HDEBUG(D_lists) if (ot) debug_printf("%s no (option unset)\n", ot);
return FAIL;
}
/* If we are searching a domain list, and $domain is not set, set it to the
subject that is being sought for the duration of the expansion. */
- if (type == MCL_DOMAIN && deliver_domain == NULL)
+ if (type == MCL_DOMAIN && !deliver_domain)
{
check_string_block *cb = (check_string_block *)arg;
deliver_domain = string_copy(cb->subject);
list = expand_cstring(*listptr);
deliver_domain = NULL;
}
+ else
+ list = expand_cstring(*listptr);
- else list = expand_cstring(*listptr);
-
- if (list == NULL)
+ if (!list)
{
- if (expand_string_forcedfail)
+ if (f.expand_string_forcedfail)
{
HDEBUG(D_lists) debug_printf("expansion of \"%s\" forced failure: "
"assume not in this list\n", *listptr);
/* For an unnamed list, use the expanded version in comments */
-HDEBUG(D_any)
- {
- if (ot == NULL) ot = string_sprintf("%s in \"%s\"?", name, list);
- }
+HDEBUG(D_any) if (ot == NULL) ot = string_sprintf("%s in \"%s\"?", name, list);
/* Now scan the list and process each item in turn, until one of them matches,
or we hit an error. */
-while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
{
- uschar *ss = sss;
+ uschar * ss = sss;
/* Address lists may contain +caseful, to restore caseful matching of the
local part. We have to know the layout of the control block, unfortunately.
{
check_address_block *cb = (check_address_block *)arg;
uschar *at = Ustrrchr(cb->origaddress, '@');
- if (at != NULL)
+
+ if (at)
Ustrncpy(cb->address, cb->origaddress, at - cb->origaddress);
cb->caseless = FALSE;
continue;
yield = FAIL;
while (isspace((*(++ss))));
}
- else yield = OK;
+ else
+ yield = OK;
/* If the item does not begin with '/', it might be a + item for a named
list. Otherwise, it is just a single list entry that has to be matched.
if (*ss != '/')
{
- if (*ss == '+' && anchorptr != NULL)
+ if (*ss == '+' && anchorptr)
{
int bits = 0;
int offset = 0;
unsigned int *use_cache_bits = original_cache_bits;
uschar *cached = US"";
namedlist_block *nb;
- tree_node *t = tree_search(*anchorptr, ss+1);
-
- if (t == NULL)
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unknown named%s list \"%s\"",
- (type == MCL_DOMAIN)? " domain" :
- (type == MCL_HOST)? " host" :
- (type == MCL_ADDRESS)? " address" :
- (type == MCL_LOCALPART)? " local part" : "",
+ tree_node * t;
+
+ if (!(t = tree_search(*anchorptr, ss+1)))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "unknown named%s list \"%s\"",
+ type == MCL_DOMAIN ? " domain" :
+ type == MCL_HOST ? " host" :
+ type == MCL_ADDRESS ? " address" :
+ type == MCL_LOCALPART ? " local part" : "",
ss);
+ return DEFER;
+ }
nb = t->data.ptr;
/* If the list number is negative, it means that this list is not
because the pointer may be NULL from the start if caching is not
required. */
- if (use_cache_bits != NULL)
+ if (use_cache_bits)
{
offset = (nb->number)/16;
shift = ((nb->number)%16)*2;
wasn't before. Ensure that this is passed up to the next level.
Otherwise, remember the result of the search in the cache. */
- if (use_cache_bits == NULL)
- {
+ if (!use_cache_bits)
*cache_ptr = NULL;
- }
else
{
use_cache_bits[offset] |= bits << shift;
- if (valueptr != NULL)
+ if (valueptr)
{
int old_pool = store_pool;
namedlist_cacheblock *p;
p->key = string_copy(get_check_key(arg, type));
- p->data = (*valueptr == NULL)? NULL : string_copy(*valueptr);
+ p->data = *valueptr ? string_copy(*valueptr) : NULL;
store_pool = old_pool;
p->next = nb->cache_data;
nb->cache_data = p;
- if (*valueptr != NULL)
- {
+ if (*valueptr)
DEBUG(D_lists) debug_printf("data from lookup saved for "
"cache for %s: %s\n", ss, *valueptr);
- }
}
}
}
{
DEBUG(D_lists) debug_printf("cached %s match for %s\n",
((bits & (-bits)) == bits)? "yes" : "no", ss);
+
cached = US" - cached";
- if (valueptr != NULL)
+ if (valueptr)
{
const uschar *key = get_check_key(arg, type);
namedlist_cacheblock *p;
- for (p = nb->cache_data; p != NULL; p = p->next)
- {
+ for (p = nb->cache_data; p; p = p->next)
if (Ustrcmp(key, p->key) == 0)
{
*valueptr = p->data;
break;
}
- }
DEBUG(D_lists) debug_printf("cached lookup data = %s\n", *valueptr);
}
}
else
{
- uschar *error = NULL;
+ uschar * error = NULL;
switch ((func)(arg, ss, valueptr, &error))
{
case OK:
- HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\")\n", ot,
- (yield == OK)? "yes" : "no", sss);
- return yield;
+ HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\")\n", ot,
+ (yield == OK)? "yes" : "no", sss);
+ return yield;
case DEFER:
- if (error == NULL)
- error = string_sprintf("DNS lookup of \"%s\" deferred", ss);
- if (ignore_defer)
- {
- HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
- error);
- break;
- }
- if (include_defer)
- {
- log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
- return OK;
- }
- if (!search_error_message) search_error_message = error;
- goto DEFER_RETURN;
+ if (!error)
+ error = string_sprintf("DNS lookup of \"%s\" deferred", ss);
+ if (ignore_defer)
+ {
+ HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
+ error);
+ break;
+ }
+ if (include_defer)
+ {
+ log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
+ return OK;
+ }
+ if (!search_error_message) search_error_message = error;
+ goto DEFER_RETURN;
/* The ERROR return occurs when checking hosts, when either a forward
or reverse lookup has failed. It can also occur in a match_ip list if a
which it was. */
case ERROR:
- if (ignore_unknown)
- {
- HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
- error);
- }
- else
- {
- HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
- include_unknown? "yes":"no", error);
- if (!include_unknown)
- {
- if (LOGGING(unknown_in_list))
- log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
- return FAIL;
- }
- log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
- return OK;
- }
+ if (ignore_unknown)
+ {
+ HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
+ error);
+ }
+ else
+ {
+ HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
+ include_unknown? "yes":"no", error);
+ if (!include_unknown)
+ {
+ if (LOGGING(unknown_in_list))
+ log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
+ return FAIL;
+ }
+ log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
+ return OK;
+ }
}
}
}
else
{
int file_yield = yield; /* In case empty file */
- uschar *filename = ss;
- FILE *f = Ufopen(filename, "rb");
+ uschar * filename = ss;
+ FILE * f = Ufopen(filename, "rb");
uschar filebuffer[1024];
/* ot will be null in non-debugging cases, and anyway, we get better
wording by reworking it. */
- if (f == NULL)
+ if (!f)
{
- uschar *listname = readconf_find_option(listptr);
+ uschar * listname = readconf_find_option(listptr);
if (listname[0] == 0)
listname = string_sprintf("\"%s\"", *listptr);
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
switch ((func)(arg, ss, valueptr, &error))
{
case OK:
- (void)fclose(f);
- HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\" in %s)\n", ot,
- (yield == OK)? "yes" : "no", sss, filename);
- return file_yield;
+ (void)fclose(f);
+ HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\" in %s)\n", ot,
+ yield == OK ? "yes" : "no", sss, filename);
+ return file_yield;
case DEFER:
- if (error == NULL)
- error = string_sprintf("DNS lookup of %s deferred", ss);
- if (ignore_defer)
- {
- HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
- error);
- break;
- }
- (void)fclose(f);
- if (include_defer)
- {
- log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
- return OK;
- }
- goto DEFER_RETURN;
-
- case ERROR: /* host name lookup failed - this can only */
- if (ignore_unknown) /* be for an incoming host (not outgoing) */
- {
- HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
- error);
- }
- else
- {
- HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
- include_unknown? "yes":"no", error);
- (void)fclose(f);
- if (!include_unknown)
- {
- if (LOGGING(unknown_in_list))
- log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
- return FAIL;
- }
- log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
- return OK;
- }
+ if (!error)
+ error = string_sprintf("DNS lookup of %s deferred", ss);
+ if (ignore_defer)
+ {
+ HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
+ error);
+ break;
+ }
+ (void)fclose(f);
+ if (include_defer)
+ {
+ log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
+ return OK;
+ }
+ goto DEFER_RETURN;
+
+ case ERROR: /* host name lookup failed - this can only */
+ if (ignore_unknown) /* be for an incoming host (not outgoing) */
+ {
+ HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
+ error);
+ }
+ else
+ {
+ HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
+ include_unknown? "yes":"no", error);
+ (void)fclose(f);
+ if (!include_unknown)
+ {
+ if (LOGGING(unknown_in_list))
+ log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
+ return FAIL;
+ }
+ log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
+ return OK;
+ }
}
}
/* End of list reached: if the last item was negated yield OK, else FAIL. */
HDEBUG(D_lists)
- debug_printf("%s %s (end of list)\n", ot, (yield == OK)? "no":"yes");
-return (yield == OK)? FAIL : OK;
+ debug_printf("%s %s (end of list)\n", ot, yield == OK ? "no":"yes");
+return yield == OK ? FAIL : OK;
/* Something deferred */
valueptr);
}
+/* Simpler version of match_address_list; always caseless, expanding,
+no cache bits, no value-return.
+
+Arguments:
+ address address to test
+ listptr list to check against
+ sep separator character for the list;
+ may be 0 to get separator from the list;
+ may be UCHAR_MAX+1 for one-item list
+
+Returns: OK for a positive match, or end list after a negation;
+ FAIL for a negative match, or end list after non-negation;
+ DEFER if a lookup deferred
+*/
+
+int
+match_address_list_basic(const uschar *address, const uschar **listptr, int sep)
+{
+return match_address_list(address, TRUE, TRUE, listptr, NULL, -1, sep, NULL);
+}
+
/* End of match.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004, 2015
+/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2015
* License: GPL
- * Copyright (c) The Exim Maintainers 2016
+ * Copyright (c) The Exim Maintainers 2015 - 2018
*/
#include "exim.h"
uschar *mime_current_boundary = NULL;
static mime_header mime_header_list[] = {
+ /* name namelen value */
{ US"content-type:", 13, &mime_content_type },
{ US"content-disposition:", 20, &mime_content_disposition },
{ US"content-transfer-encoding:", 26, &mime_content_transfer_encoding },
static int mime_header_list_size = nelem(mime_header_list);
static mime_parameter mime_parameter_list[] = {
+ /* name namelen value */
{ US"name=", 5, &mime_filename },
{ US"filename=", 9, &mime_filename },
{ US"charset=", 8, &mime_charset },
{
/* Error from decoder. ipos is unchanged. */
mime_set_anomaly(MIME_ANOMALY_BROKEN_QP);
- *opos = '=';
- ++opos;
+ *opos++ = '=';
++ipos;
}
else if (decode_qp_result == -1)
break;
else if (decode_qp_result >= 0)
- {
- *opos = decode_qp_result;
- ++opos;
- }
+ *opos++ = decode_qp_result;
}
else
- {
- *opos = *ipos;
- ++opos;
- ++ipos;
- }
+ *opos++ = *ipos++;
}
/* something to write? */
len = opos - obuf;
{
int sep = 0;
const uschar *list = *listptr;
-uschar *option;
-uschar option_buffer[1024];
-uschar decode_path[1024];
+uschar * option;
+uschar * decode_path;
FILE *decode_file = NULL;
long f_pos = 0;
ssize_t size_counter = 0;
return FAIL;
/* build default decode path (will exist since MBOX must be spooled up) */
-(void)string_format(decode_path,1024,"%s/scan/%s",spool_directory,message_id);
+decode_path = string_sprintf("%s/scan/%s", spool_directory, message_id);
/* try to find 1st option */
-if ((option = string_nextinlist(&list, &sep,
- option_buffer,
- sizeof(option_buffer))) != NULL)
+if ((option = string_nextinlist(&list, &sep, NULL, 0)))
{
/* parse 1st option */
if ((Ustrcmp(option,"false") == 0) || (Ustrcmp(option,"0") == 0))
if ( ((c == '\t') || (c == ' ')) && (header_value_mode == 1) )
continue;
- /* we have hit a non-whitespace char, start copying value data */
- header_value_mode = 2;
+ /* we have hit a non-whitespace char, start copying value data */
+ header_value_mode = 2;
- if (c == '"') /* flip "quoted" mode */
- header_value_mode = header_value_mode==2 ? 3 : 2;
+ if (c == '"') /* flip "quoted" mode */
+ header_value_mode = header_value_mode==2 ? 3 : 2;
- /* leave value mode on unquoted ';' */
- if (header_value_mode == 2 && c == ';') {
- header_value_mode = 0;
- };
- /* -------------------------------- */
+ /* leave value mode on unquoted ';' */
+ if (header_value_mode == 2 && c == ';')
+ header_value_mode = 0;
+ /* -------------------------------- */
}
else
{
mime_param_val(uschar ** sp)
{
uschar * s = *sp;
-uschar * val = NULL;
-int size = 0, ptr = 0;
+gstring * val = NULL;
/* debug_printf_indent(" considering paramval '%s'\n", s); */
{
s++; /* skip opening " */
while (*s && *s != '"') /* " protects ; */
- val = string_catn(val, &size, &ptr, s++, 1);
+ val = string_catn(val, s++, 1);
if (*s) s++; /* skip closing " */
}
else
- val = string_catn(val, &size, &ptr, s++, 1);
-if (val) val[ptr] = '\0';
+ val = string_catn(val, s++, 1);
*sp = s;
-return val;
+return string_from_gstring(val);
}
static uschar *
static uschar *
rfc2231_to_2047(const uschar * fname, const uschar * charset, int * len)
{
-int size = 0, ptr = 0;
-uschar * val = string_catn(NULL, &size, &ptr, US"=?", 2);
+gstring * val = string_catn(NULL, US"=?", 2);
uschar c;
if (charset)
- val = string_cat(val, &size, &ptr, charset);
-val = string_catn(val, &size, &ptr, US"?Q?", 3);
+ val = string_cat(val, charset);
+val = string_catn(val, US"?Q?", 3);
while ((c = *fname))
if (c == '%' && isxdigit(fname[1]) && isxdigit(fname[2]))
{
- val = string_catn(val, &size, &ptr, US"=", 1);
- val = string_catn(val, &size, &ptr, ++fname, 2);
+ val = string_catn(val, US"=", 1);
+ val = string_catn(val, ++fname, 2);
fname += 2;
}
else
- val = string_catn(val, &size, &ptr, fname++, 1);
+ val = string_catn(val, fname++, 1);
-val = string_catn(val, &size, &ptr, US"?=", 2);
-val[*len = ptr] = '\0';
-return val;
+val = string_catn(val, US"?=", 2);
+*len = val->ptr;
+return string_from_gstring(val);
}
NULL, &err_msg);
DEBUG(D_acl) debug_printf_indent("MIME: plain-name %s\n", temp_string);
- size = Ustrlen(temp_string);
-
- if (size == slen)
+ if (!temp_string || (size = Ustrlen(temp_string)) == slen)
decoding_failed = TRUE;
else
/* build up a decoded filename over successive
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for sending messages to sender or to mailmaster. */
moan_write_from(FILE *f)
{
uschar *s = expand_string(dsn_from);
-if (s == NULL)
+if (!s)
{
log_write(0, LOG_MAIN|LOG_PANIC,
"Failed to expand dsn_from (using default): %s", expand_string_message);
Returns: TRUE if message successfully sent
*/
-static BOOL
+BOOL
moan_send_message(uschar *recipient, int ident, error_block *eblock,
header_line *headers, FILE *message_file, uschar *firstline)
{
int status;
int count = 0;
int size_limit = bounce_return_size_limit;
-FILE *f;
-int pid = child_open_exim(&fd);
+FILE * fp;
+int pid;
-/* Creation of child failed */
+#ifdef EXPERIMENTAL_DMARC
+uschar * s, * s2;
+
+/* For DMARC if there is a specific sender set, expand the variable for the
+header From: and grab the address from that for the envelope FROM. */
+
+if ( ident == ERRMESS_DMARC_FORENSIC
+ && dmarc_forensic_sender
+ && (s = expand_string(dmarc_forensic_sender))
+ && *s
+ && (s2 = expand_string(string_sprintf("${address:%s}", s)))
+ && *s2
+ )
+ pid = child_open_exim2(&fd, s2, bounce_sender_authentication);
+else
+ {
+ s = NULL;
+ pid = child_open_exim(&fd);
+ }
+
+#else
+pid = child_open_exim(&fd);
+#endif
if (pid < 0)
{
/* Creation of child succeeded */
-f = fdopen(fd, "wb");
-if (errors_reply_to) fprintf(f, "Reply-To: %s\n", errors_reply_to);
-fprintf(f, "Auto-Submitted: auto-replied\n");
-moan_write_from(f);
-fprintf(f, "To: %s\n", recipient);
+fp = fdopen(fd, "wb");
+if (errors_reply_to) fprintf(fp, "Reply-To: %s\n", errors_reply_to);
+fprintf(fp, "Auto-Submitted: auto-replied\n");
+
+#ifdef EXPERIMENTAL_DMARC
+if (s)
+ fprintf(fp, "From: %s\n", s);
+else
+#endif
+ moan_write_from(fp);
+
+fprintf(fp, "To: %s\n", recipient);
switch(ident)
{
case ERRMESS_BADARGADDRESS:
- fprintf(f,
+ fprintf(fp,
"Subject: Mail failure - malformed recipient address\n\n");
- fprintf(f,
+ fprintf(fp,
"A message that you sent contained a recipient address that was incorrectly\n"
"constructed:\n\n");
- fprintf(f, " %s %s\n", eblock->text1, eblock->text2);
+ fprintf(fp, " %s %s\n", eblock->text1, eblock->text2);
count = Ustrlen(eblock->text1);
if (count > 0 && eblock->text1[count-1] == '.')
- fprintf(f,
+ fprintf(fp,
"\nRecipient addresses must not end with a '.' character.\n");
- fprintf(f,
+ fprintf(fp,
"\nThe message has not been delivered to any recipients.\n");
break;
case ERRMESS_BADNOADDRESS:
case ERRMESS_BADADDRESS:
- fprintf(f,
+ fprintf(fp,
"Subject: Mail failure - malformed recipient address\n\n");
- fprintf(f,
+ fprintf(fp,
"A message that you sent contained one or more recipient addresses that were\n"
"incorrectly constructed:\n\n");
while (eblock != NULL)
{
- fprintf(f, " %s: %s\n", eblock->text1, eblock->text2);
+ fprintf(fp, " %s: %s\n", eblock->text1, eblock->text2);
count++;
eblock = eblock->next;
}
- fprintf(f, (count == 1)? "\nThis address has been ignored. " :
+ fprintf(fp, (count == 1)? "\nThis address has been ignored. " :
"\nThese addresses have been ignored. ");
- fprintf(f, (ident == ERRMESS_BADADDRESS)?
+ fprintf(fp, (ident == ERRMESS_BADADDRESS)?
"The other addresses in the message were\n"
"syntactically valid and have been passed on for an attempt at delivery.\n" :
break;
case ERRMESS_IGADDRESS:
- fprintf(f, "Subject: Mail failure - no recipient addresses\n\n");
- fprintf(f,
+ fprintf(fp, "Subject: Mail failure - no recipient addresses\n\n");
+ fprintf(fp,
"A message that you sent using the -t command line option contained no\n"
"addresses that were not also on the command line, and were therefore\n"
"suppressed. This left no recipient addresses, and so no delivery could\n"
break;
case ERRMESS_NOADDRESS:
- fprintf(f, "Subject: Mail failure - no recipient addresses\n\n");
- fprintf(f,
+ fprintf(fp, "Subject: Mail failure - no recipient addresses\n\n");
+ fprintf(fp,
"A message that you sent contained no recipient addresses, and therefore no\n"
"delivery could be attempted.\n");
break;
case ERRMESS_IOERR:
- fprintf(f, "Subject: Mail failure - system failure\n\n");
- fprintf(f,
+ fprintf(fp, "Subject: Mail failure - system failure\n\n");
+ fprintf(fp,
"A system failure was encountered while processing a message that you sent,\n"
"so it has not been possible to deliver it. The error was:\n\n%s\n",
eblock->text1);
break;
case ERRMESS_VLONGHEADER:
- fprintf(f, "Subject: Mail failure - overlong header section\n\n");
- fprintf(f,
+ fprintf(fp, "Subject: Mail failure - overlong header section\n\n");
+ fprintf(fp,
"A message that you sent contained a header section that was excessively\n"
"long and could not be handled by the mail transmission software. The\n"
"message has not been delivered to any recipients.\n");
break;
case ERRMESS_VLONGHDRLINE:
- fprintf(f, "Subject: Mail failure - overlong header line\n\n");
- fprintf(f,
+ fprintf(fp, "Subject: Mail failure - overlong header line\n\n");
+ fprintf(fp,
"A message that you sent contained a header line that was excessively\n"
"long and could not be handled by the mail transmission software. The\n"
"message has not been delivered to any recipients.\n");
break;
case ERRMESS_TOOBIG:
- fprintf(f, "Subject: Mail failure - message too big\n\n");
- fprintf(f,
+ fprintf(fp, "Subject: Mail failure - message too big\n\n");
+ fprintf(fp,
"A message that you sent was longer than the maximum size allowed on this\n"
"system. It was not delivered to any recipients.\n");
break;
case ERRMESS_TOOMANYRECIP:
- fprintf(f, "Subject: Mail failure - too many recipients\n\n");
- fprintf(f,
+ fprintf(fp, "Subject: Mail failure - too many recipients\n\n");
+ fprintf(fp,
"A message that you sent contained more recipients than allowed on this\n"
"system. It was not delivered to any recipients.\n");
break;
case ERRMESS_LOCAL_SCAN:
case ERRMESS_LOCAL_ACL:
- fprintf(f, "Subject: Mail failure - rejected by local scanning code\n\n");
- fprintf(f,
+ fprintf(fp, "Subject: Mail failure - rejected by local scanning code\n\n");
+ fprintf(fp,
"A message that you sent was rejected by the local scanning code that\n"
"checks incoming messages on this system.");
if (eblock->text1)
- fprintf(f, " The following error was given:\n\n %s", eblock->text1);
- fprintf(f, "\n");
+ fprintf(fp, " The following error was given:\n\n %s", eblock->text1);
+ fprintf(fp, "\n");
break;
#ifdef EXPERIMENTAL_DMARC
case ERRMESS_DMARC_FORENSIC:
bounce_return_message = TRUE;
bounce_return_body = FALSE;
- fprintf(f,
- "Subject: DMARC Forensic Report for %s from IP %s\n\n",
- ((eblock == NULL) ? US"Unknown" : eblock->text2),
+ fprintf(fp, "Subject: DMARC Forensic Report for %s from IP %s\n\n",
+ eblock ? eblock->text2 : US"Unknown",
sender_host_address);
- fprintf(f,
+ fprintf(fp,
"A message claiming to be from you has failed the published DMARC\n"
"policy for your domain.\n\n");
- while (eblock != NULL)
+ while (eblock)
{
- fprintf(f, " %s: %s\n", eblock->text1, eblock->text2);
+ fprintf(fp, " %s: %s\n", eblock->text1, eblock->text2);
count++;
eblock = eblock->next;
}
#endif
default:
- fprintf(f, "Subject: Mail failure\n\n");
- fprintf(f,
+ fprintf(fp, "Subject: Mail failure\n\n");
+ fprintf(fp,
"A message that you sent has caused the error routine to be entered with\n"
"an unknown error number (%d).\n", ident);
break;
{
if (bounce_return_body)
{
- fprintf(f, "\n"
+ fprintf(fp, "\n"
"------ This is a copy of your message, including all the headers.");
if (size_limit == 0 || size_limit > thismessage_size_limit)
size_limit = thismessage_size_limit;
k = US"K";
x >>= 10;
}
- fprintf(f, "\n"
+ fprintf(fp, "\n"
"------ No more than %d%s characters of the body are included.\n\n",
x, k);
}
- else fprintf(f, " ------\n\n");
+ else fprintf(fp, " ------\n\n");
}
else
{
- fprintf(f, "\n"
+ fprintf(fp, "\n"
"------ This is a copy of the headers that were received before the "
"error\n was detected.\n\n");
}
while (headers)
{
- if (headers->text != NULL) fprintf(f, "%s", CS headers->text);
+ if (headers->text != NULL) fprintf(fp, "%s", CS headers->text);
headers = headers->next;
}
if (ident != ERRMESS_VLONGHEADER && ident != ERRMESS_VLONGHDRLINE)
- fputc('\n', f);
+ fputc('\n', fp);
/* After early detection of an error, the message file may be STDIN,
in which case we might have to terminate on a line containing just "."
if (bounce_return_body && message_file)
{
- BOOL enddot = dot_ends && message_file == stdin;
+ BOOL enddot = f.dot_ends && message_file == stdin;
uschar * buf = store_get(bounce_return_linesize_limit+2);
- if (firstline) fprintf(f, "%s", CS firstline);
+ if (firstline) fprintf(fp, "%s", CS firstline);
while (fgets(CS buf, bounce_return_linesize_limit+2, message_file))
{
if (enddot && *buf == '.' && buf[1] == '\n')
{
- fputc('.', f);
+ fputc('.', fp);
break;
}
if (size_limit > 0 && len > size_limit - written)
{
buf[size_limit - written] = '\0';
- fputs(CS buf, f);
+ fputs(CS buf, fp);
break;
}
- fputs(CS buf, f);
+ fputs(CS buf, fp);
}
}
#ifdef EXPERIMENTAL_DMARC
/*XXX limit line length here? */
/* This doesn't print newlines, disable until can parse and fix
* output to be legible. */
- fprintf(f, "%s", expand_string(US"$message_body"));
+ fprintf(fp, "%s", expand_string(US"$message_body"));
}
#endif
}
/* Close the file, which should send an EOF to the child process
that is receiving the message. Wait for it to finish, without a timeout. */
-(void)fclose(f);
+(void)fclose(fp);
status = child_close(pid, 0); /* Waits for child to close */
if (status != 0)
{
/* Find the sender from a From line if permitted and possible */
-if (check_sender && message_file && trusted_caller &&
+if (check_sender && message_file && f.trusted_caller &&
Ufgets(big_buffer, BIG_BUFFER_SIZE, message_file) != NULL)
{
uschar *new_sender = NULL;
/* If viable sender address, send a message */
-if (sender_address && sender_address[0] && !local_error_message)
+if (sender_address && sender_address[0] && !f.local_error_message)
return moan_send_message(sender_address, ident, eblock, headers,
message_file, firstline);
fprintf(stderr, "The rest of the batch was abandoned.\n");
-exim_exit(yield);
+exim_exit(yield, US"batch");
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
the arguments of printf-like functions. This is done by a macro. */
#if defined(__GNUC__) || defined(__clang__)
-# define PRINTF_FUNCTION(A,B) __attribute__((format(printf,A,B)))
-# define ARG_UNUSED __attribute__((__unused__))
+# define PRINTF_FUNCTION(A,B) __attribute__((format(printf,A,B)))
+# define ARG_UNUSED __attribute__((__unused__))
+# define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
#else
# define PRINTF_FUNCTION(A,B)
# define ARG_UNUSED /**/
+# define WARN_UNUSED_RESULT /**/
#endif
#ifdef WANT_DEEPER_PRINTF_CHECKS
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#ifdef STAND_ALONE
-#include <signal.h>
-#include <stdio.h>
-#include <time.h>
+# include <signal.h>
+# include <stdio.h>
+# include <time.h>
+#endif
+
+#ifndef CS
+# define CS (char *)
+# define US (unsigned char *)
#endif
/* This source file contains "default" system-dependent functions which
}
if (lseek (avg_kd, avg_offset, 0) == -1L
- || read (avg_kd, (char *)(&avg), sizeof (avg)) != sizeof(avg))
+ || read (avg_kd, CS (&avg), sizeof (avg)) != sizeof(avg))
return -1;
return (int)(((double)avg/FSCALE)*1000.0);
ifc.V_ifc_flags = 0;
#endif
-if (ioctl(vs, V_GIFCONF, (char *)&ifc) < 0)
+if (ioctl(vs, V_GIFCONF, CS &ifc) < 0)
log_write(0, LOG_PANIC_DIE, "Unable to get interface configuration: %d %s",
errno, strerror(errno));
for (cp = buf; cp < buf + ifc.V_ifc_len; cp += len)
{
- memcpy((char *)&ifreq, cp, sizeof(ifreq));
+ memcpy(CS &ifreq, cp, sizeof(ifreq));
#ifndef HAVE_SA_LEN
len = sizeof(struct V_ifreq);
interface hasn't been "plumbed" to any protocol (IPv4 or IPv6). Therefore,
we now just treat this case as "down" as well. */
- if (ioctl(vs, V_GIFFLAGS, (char *)&ifreq) < 0)
+ if (ioctl(vs, V_GIFFLAGS, CS &ifreq) < 0)
{
continue;
/*************
GIFFLAGS may have wrecked the data. */
#ifndef SIOCGIFCONF_GIVES_ADDR
- if (ioctl(vs, V_GIFADDR, (char *)&ifreq) < 0)
+ if (ioctl(vs, V_GIFADDR, CS &ifreq) < 0)
log_write(0, LOG_PANIC_DIE, "Unable to get IP address for %s interface: "
"%d %s", ifreq.V_ifr_name, errno, strerror(errno));
addrp = &ifreq.V_ifr_addr;
int
os_unsetenv(const unsigned char * name)
{
-return unsetenv((char *)name);
+return unsetenv(CS name);
}
#endif
unsigned char *
os_getcwd(unsigned char * buffer, size_t size)
{
-return (unsigned char *) getcwd((char *)buffer, size);
+return US getcwd(CS buffer, size);
}
#else
#ifndef PATH_MAX
unsigned char *
os_getcwd(unsigned char * buffer, size_t size)
{
-char * b = (char *)buffer;
+char * b = CS buffer;
if (!size) size = PATH_MAX;
if (!b && !(b = malloc(size))) return NULL;
printf("Testing restarting signal; wait for handler message, then type a line\n");
strcpy(buffer, "*** default ***\n");
os_restarting_signal(SIGALRM, sigalrm_handler);
-alarm(2);
+ALARM(2);
if ((rc = read(fd, buffer, sizeof(buffer))) < 0)
printf("No data read\n");
else
buffer[rc] = 0;
printf("Read: %s", buffer);
}
-alarm(0);
+ALARM_CLR(0);
printf("Testing non-restarting signal; should read no data after handler message\n");
strcpy(buffer, "*** default ***\n");
os_non_restarting_signal(SIGALRM, sigalrm_handler);
-alarm(2);
+ALARM(2);
if ((rc = read(fd, buffer, sizeof(buffer))) < 0)
printf("No data read\n");
else
buffer[rc] = 0;
printf("Read: %s", buffer);
}
-alarm(0);
+ALARM_CLR(0);
printf("Testing load averages (last test - ^C to kill)\n");
for (;;)
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for parsing addresses */
if (*s == '\"')
{
*t++ = '\"';
- while ((c = *(++s)) != 0 && c != '\"')
+ while ((c = *++s) && c != '\"')
{
*t++ = c;
- if (c == '\\' && s[1] != 0) *t++ = *(++s);
+ if (c == '\\' && s[1]) *t++ = *++s;
}
if (c == '\"')
{
else while (!mac_iscntrl_or_special(*s) || *s == '\\')
{
c = *t++ = *s++;
- if (c == '\\' && *s != 0) *t++ = *s++;
+ if (c == '\\' && *s) *t++ = *s++;
}
/* Terminate the word and skip subsequent comment */
{
uschar *yield = store_get(Ustrlen(mailbox) + 1);
uschar *startptr, *endptr;
-uschar *s = (uschar *)mailbox;
-uschar *t = (uschar *)yield;
+uschar *s = US mailbox;
+uschar *t = US yield;
*domain = 0;
s = skip_comment(s);
startptr = s; /* In case addr-spec */
s = read_local_part(s, t, errorptr, TRUE); /* Dot separated words */
-if (*errorptr != NULL) goto PARSE_FAILED;
+if (*errorptr) goto PARSE_FAILED;
/* If the terminator is neither < nor @ then the format of the address
must either be a bare local-part (we are now at the end), or a phrase
end of string will produce a null local_part and therefore fail. We don't
need to keep updating t, as the phrase isn't to be kept. */
- while (*s != '<' && (!parse_allow_group || *s != ':'))
+ while (*s != '<' && (!f.parse_allow_group || *s != ':'))
{
s = read_local_part(s, t, errorptr, FALSE);
if (*errorptr)
if (*s == ':')
{
- parse_found_group = TRUE;
- parse_allow_group = FALSE;
+ f.parse_found_group = TRUE;
+ f.parse_allow_group = FALSE;
s++;
goto RESTART;
}
*errorptr = s[-1] == 0
? US"'>' missing at end of address"
: string_sprintf("malformed address: %.32s may not follow %.*s",
- s-1, s - (uschar *)mailbox - 1, mailbox);
+ s-1, (int)(s - US mailbox - 1), mailbox);
goto PARSE_FAILED;
}
PARSE_SUCCEEDED:
if (*s != 0)
{
- if (parse_found_group && *s == ';')
+ if (f.parse_found_group && *s == ';')
{
- parse_found_group = FALSE;
- parse_allow_group = TRUE;
+ f.parse_found_group = FALSE;
+ f.parse_allow_group = TRUE;
}
else
{
*errorptr = string_sprintf("malformed address: %.32s may not follow %.*s",
- s, s - (uschar *)mailbox, mailbox);
+ s, (int)(s - US mailbox), mailbox);
goto PARSE_FAILED;
}
}
-*start = startptr - (uschar *)mailbox; /* Return offsets */
+*start = startptr - US mailbox; /* Return offsets */
while (isspace(endptr[-1])) endptr--;
-*end = endptr - (uschar *)mailbox;
+*end = endptr - US mailbox;
/* Although this code has no limitation on the length of address extracted,
other parts of Exim may have limits, and in any case, RFC 2821 limits local
this. We must, however, keep the flags correct. */
PARSE_FAILED:
-if (parse_found_group && *s == ';')
+if (f.parse_found_group && *s == ';')
{
- parse_found_group = FALSE;
- parse_allow_group = TRUE;
+ f.parse_found_group = FALSE;
+ f.parse_allow_group = TRUE;
}
return NULL;
}
BOOL coded = FALSE;
BOOL first_byte = FALSE;
-if (charset == NULL) charset = US"iso-8859-1";
+if (!charset) charset = US"iso-8859-1";
/* We don't expect this to fail! */
}
else
{
- sprintf(CS t, "=%02X", ch);
- while (*t != 0) t++;
+ t += sprintf(CS t, "=%02X", ch);
coded = TRUE;
first_byte = !first_byte;
}
*t++ = '=';
*t = 0;
-return coded? buffer : string;
+return coded ? buffer : string;
}
printf("Testing parse_extract_address with group syntax\n");
-parse_allow_group = TRUE;
+f.parse_allow_group = TRUE;
while (Ufgets(buffer, sizeof(buffer), stdin) != NULL)
{
uschar *out;
# Make file for building the pdkim library.
+# Copyright (c) The Exim Maintainers 1995 - 2018
-OBJ = pdkim.o rsa.o
+OBJ = pdkim.o signing.o
pdkim.a: $(OBJ)
@$(RM_COMMAND) -f pdkim.a
$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -I. $*.c
pdkim.o: $(HDRS) crypt_ver.h pdkim.h pdkim.c
-rsa.o: $(HDRS) crypt_ver.h rsa.h rsa.c
+signing.o: $(HDRS) crypt_ver.h signing.h signing.c
# End
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) Jeremy Harris 2016 */
+/* Copyright (c) Jeremy Harris 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
-/* RSA and SHA routine selection for PDKIM */
+/* Signing and hashing routine selection for PDKIM */
#include "../exim.h"
#include "../sha_ver.h"
#ifdef USE_GNUTLS
# include <gnutls/gnutls.h>
-# if GNUTLS_VERSION_NUMBER >= 0x30000
-# define RSA_GNUTLS
+# if GNUTLS_VERSION_NUMBER >= 0x030000
+# define SIGN_GNUTLS
+# if GNUTLS_VERSION_NUMBER >= 0x030600
+# define SIGN_HAVE_ED25519
+# endif
# else
-# define RSA_GCRYPT
+# define SIGN_GCRYPT
# endif
#else
-# define RSA_OPENSSL
+# define SIGN_OPENSSL
+# if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10101000L
+# define SIGN_HAVE_ED25519
+# endif
+
#endif
* PDKIM - a RFC4871 (DKIM) implementation
*
* Copyright (C) 2009 - 2016 Tom Kistner <tom@duncanthrax.net>
- * Copyright (C) 2016 - 2017 Jeremy Harris <jgh@exim.org>
+ * Copyright (C) 2016 - 2018 Jeremy Harris <jgh@exim.org>
*
* http://duncanthrax.net/pdkim/
*
#include "crypt_ver.h"
-#ifdef RSA_OPENSSL
+#ifdef SIGN_OPENSSL
# include <openssl/rsa.h>
# include <openssl/ssl.h>
# include <openssl/err.h>
-#elif defined(RSA_GNUTLS)
+#elif defined(SIGN_GNUTLS)
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
#endif
#include "pdkim.h"
-#include "rsa.h"
+#include "signing.h"
#define PDKIM_SIGNATURE_VERSION "1"
#define PDKIM_PUB_RECORD_VERSION US "DKIM1"
#define PDKIM_MAX_HEADERS 512
#define PDKIM_MAX_BODY_LINE_LEN 16384
#define PDKIM_DNS_TXT_MAX_NAMELEN 1024
-#define PDKIM_DEFAULT_SIGN_HEADERS "From:Sender:Reply-To:Subject:Date:"\
- "Message-ID:To:Cc:MIME-Version:Content-Type:"\
- "Content-Transfer-Encoding:Content-ID:"\
- "Content-Description:Resent-Date:Resent-From:"\
- "Resent-Sender:Resent-To:Resent-Cc:"\
- "Resent-Message-ID:In-Reply-To:References:"\
- "List-Id:List-Help:List-Unsubscribe:"\
- "List-Subscribe:List-Post:List-Owner:List-Archive"
/* -------------------------------------------------------------------------- */
struct pdkim_stringlist {
US"dns/txt",
NULL
};
-const uschar * pdkim_algos[] = {
- US"rsa-sha256",
- US"rsa-sha1",
- NULL
-};
const uschar * pdkim_canons[] = {
US"simple",
US"relaxed",
NULL
};
-const uschar * pdkim_hashes[] = {
- US"sha256",
- US"sha1",
- NULL
+
+const pdkim_hashtype pdkim_hashes[] = {
+ { US"sha1", HASH_SHA1 },
+ { US"sha256", HASH_SHA2_256 },
+ { US"sha512", HASH_SHA2_512 }
};
+
const uschar * pdkim_keytypes[] = {
- US"rsa",
- NULL
+ [KEYTYPE_RSA] = US"rsa",
+#ifdef SIGN_HAVE_ED25519
+ [KEYTYPE_ED25519] = US"ed25519", /* Works for 3.6.0 GnuTLS, OpenSSL 1.1.1 */
+#endif
+
+#ifdef notyet_EC_dkim_extensions /* https://tools.ietf.org/html/draft-srose-dkim-ecc-00 */
+ US"eccp256",
+ US"eccp348",
+ US"ed448",
+#endif
};
typedef struct pdkim_combined_canon_entry {
- const uschar * str;
- int canon_headers;
- int canon_body;
+ const uschar * str;
+ int canon_headers;
+ int canon_body;
} pdkim_combined_canon_entry;
pdkim_combined_canon_entry pdkim_combined_canons[] = {
};
+static blob lineending = {.data = US"\r\n", .len = 2};
+
/* -------------------------------------------------------------------------- */
+uschar *
+dkim_sig_to_a_tag(const pdkim_signature * sig)
+{
+if ( sig->keytype < 0 || sig->keytype > nelem(pdkim_keytypes)
+ || sig->hashtype < 0 || sig->hashtype > nelem(pdkim_hashes))
+ return US"err";
+return string_sprintf("%s-%s",
+ pdkim_keytypes[sig->keytype], pdkim_hashes[sig->hashtype].dkim_hashname);
+}
+
+
+int
+pdkim_hashname_to_hashtype(const uschar * s, unsigned len)
+{
+int i;
+if (!len) len = Ustrlen(s);
+for (i = 0; i < nelem(pdkim_hashes); i++)
+ if (Ustrncmp(s, pdkim_hashes[i].dkim_hashname, len) == 0)
+ return i;
+return -1;
+}
+
+void
+pdkim_cstring_to_canons(const uschar * s, unsigned len,
+ int * canon_head, int * canon_body)
+{
+int i;
+if (!len) len = Ustrlen(s);
+for (i = 0; pdkim_combined_canons[i].str; i++)
+ if ( Ustrncmp(s, pdkim_combined_canons[i].str, len) == 0
+ && len == Ustrlen(pdkim_combined_canons[i].str))
+ {
+ *canon_head = pdkim_combined_canons[i].canon_headers;
+ *canon_body = pdkim_combined_canons[i].canon_body;
+ break;
+ }
+}
+
+
const char *
pdkim_verify_status_str(int status)
{
case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
+ case PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH: return "PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH";
case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE";
case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: return "PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD";
}
}
-const char *
+const uschar *
pdkim_errstr(int status)
{
switch(status)
{
- case PDKIM_OK: return "OK";
- case PDKIM_FAIL: return "FAIL";
- case PDKIM_ERR_RSA_PRIVKEY: return "RSA_PRIVKEY";
- case PDKIM_ERR_RSA_SIGNING: return "RSA SIGNING";
- case PDKIM_ERR_LONG_LINE: return "RSA_LONG_LINE";
- case PDKIM_ERR_BUFFER_TOO_SMALL: return "BUFFER_TOO_SMALL";
- case PDKIM_SIGN_PRIVKEY_WRAP: return "PRIVKEY_WRAP";
- case PDKIM_SIGN_PRIVKEY_B64D: return "PRIVKEY_B64D";
- default: return "(unknown)";
+ case PDKIM_OK: return US"OK";
+ case PDKIM_FAIL: return US"FAIL";
+ case PDKIM_ERR_RSA_PRIVKEY: return US"PRIVKEY";
+ case PDKIM_ERR_RSA_SIGNING: return US"SIGNING";
+ case PDKIM_ERR_LONG_LINE: return US"LONG_LINE";
+ case PDKIM_ERR_BUFFER_TOO_SMALL: return US"BUFFER_TOO_SMALL";
+ case PDKIM_ERR_EXCESS_SIGS: return US"EXCESS_SIGS";
+ case PDKIM_SIGN_PRIVKEY_WRAP: return US"PRIVKEY_WRAP";
+ case PDKIM_SIGN_PRIVKEY_B64D: return US"PRIVKEY_B64D";
+ default: return US"(unknown)";
}
}
/* -------------------------------------------------------------------------- */
/* Print debugging functions */
-static void
+void
pdkim_quoteprint(const uschar *data, int len)
{
int i;
debug_printf("\n");
}
-static void
+void
pdkim_hexprint(const uschar *data, int len)
{
int i;
/* Trim whitespace fore & aft */
static void
-pdkim_strtrim(uschar * str)
+pdkim_strtrim(gstring * str)
{
-uschar * p = str;
-uschar * q = str;
-while (*p == '\t' || *p == ' ') p++; /* skip whitespace */
-while (*p) {*q = *p; q++; p++;} /* dump the leading whitespace */
-*q = '\0';
-while (q != str && ( (*q == '\0') || (*q == '\t') || (*q == ' ') ) )
- { /* dump trailing whitespace */
- *q = '\0';
- q--;
- }
+uschar * p = str->s;
+uschar * q;
+
+while (*p == '\t' || *p == ' ') /* dump the leading whitespace */
+ { str->size--; str->ptr--; str->s++; }
+
+while ( str->ptr > 0
+ && ((q = str->s + str->ptr - 1), (*q == '\t' || *q == ' '))
+ )
+ str->ptr--; /* dump trailing whitespace */
+
+(void) string_from_gstring(str);
}
/* -------------------------------------------------------------------------- */
/* Matches the name of the passed raw "header" against
the passed colon-separated "tick", and invalidates
- the entry in tick. Returns OK or fail-code */
-/*XXX might be safer done using a pdkim_stringlist for "tick" */
+ the entry in tick. Entries can be prefixed for multi- or over-signing,
+ in which case do not invalidate.
+
+ Returns OK for a match, or fail-code
+*/
static int
header_name_match(const uschar * header, uschar * tick)
{
-uschar * hname;
-uschar * lcopy;
-uschar * p;
-uschar * q;
+const uschar * ticklist = tick;
+int sep = ':';
+BOOL multisign;
+uschar * hname, * p, * ele;
uschar * hcolon = Ustrchr(header, ':'); /* Get header name */
if (!hcolon)
/* if we had strncmpic() we wouldn't need this copy */
hname = string_copyn(header, hcolon-header);
-/* Copy tick-off list locally, so we can punch zeroes into it */
-p = lcopy = string_copy(tick);
-
-for (q = Ustrchr(p, ':'); q; q = Ustrchr(p, ':'))
+while (p = US ticklist, ele = string_nextinlist(&ticklist, &sep, NULL, 0))
{
- *q = '\0';
- if (strcmpic(p, hname) == 0)
- goto found;
-
- p = q+1;
+ switch (*ele)
+ {
+ case '=': case '+': multisign = TRUE; ele++; break;
+ default: multisign = FALSE; break;
}
-if (strcmpic(p, hname) == 0)
- goto found;
-
+ if (strcmpic(ele, hname) == 0)
+ {
+ if (!multisign)
+ *p = '_'; /* Invalidate this header name instance in tick-off list */
+ return PDKIM_OK;
+ }
+ }
return PDKIM_FAIL;
-
-found:
- /* Invalidate header name instance in tick-off list */
- tick[p-lcopy] = '_';
- return PDKIM_OK;
}
/* -------------------------------------------------------------------------- */
/* Performs "relaxed" canonicalization of a header. */
-static uschar *
-pdkim_relax_header(const uschar * header, int crlf)
+uschar *
+pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
{
BOOL past_field_name = FALSE;
BOOL seen_wsp = FALSE;
const uschar * p;
-uschar * relaxed = store_get(Ustrlen(header)+3);
+uschar * relaxed = store_get(len+3);
uschar * q = relaxed;
-for (p = header; *p; p++)
+for (p = header; p - header < len; p++)
{
uschar c = *p;
- /* Ignore CR & LF */
- if (c == '\r' || c == '\n')
+
+ if (c == '\r' || c == '\n') /* Ignore CR & LF */
continue;
if (c == '\t' || c == ' ')
{
else
if (!past_field_name && c == ':')
{
- if (seen_wsp) q--; /* This removes WSP before the colon */
- seen_wsp = TRUE; /* This removes WSP after the colon */
+ if (seen_wsp) q--; /* This removes WSP immediately before the colon */
+ seen_wsp = TRUE; /* This removes WSP immediately after the colon */
past_field_name = TRUE;
}
else
if (q > relaxed && q[-1] == ' ') q--; /* Squash eventual trailing SP */
-if (crlf) { *q++ = '\r'; *q++ = '\n'; }
+if (append_crlf) { *q++ = '\r'; *q++ = '\n'; }
*q = '\0';
return relaxed;
}
+uschar *
+pdkim_relax_header(const uschar * header, BOOL append_crlf)
+{
+return pdkim_relax_header_n(header, Ustrlen(header), append_crlf);
+}
+
+
/* -------------------------------------------------------------------------- */
#define PDKIM_QP_ERROR_DECODE -1
-static uschar *
-pdkim_decode_qp_char(uschar *qp_p, int *c)
+static const uschar *
+pdkim_decode_qp_char(const uschar *qp_p, int *c)
{
-uschar *initial_pos = qp_p;
+const uschar *initial_pos = qp_p;
/* Advance one char */
qp_p++;
/* -------------------------------------------------------------------------- */
static uschar *
-pdkim_decode_qp(uschar * str)
+pdkim_decode_qp(const uschar * str)
{
int nchar = 0;
uschar * q;
-uschar * p = str;
+const uschar * p = str;
uschar * n = store_get(Ustrlen(str)+1);
*n = '\0';
/* -------------------------------------------------------------------------- */
-static void
-pdkim_decode_base64(uschar *str, blob * b)
+void
+pdkim_decode_base64(const uschar * str, blob * b)
{
-int dlen;
-dlen = b64decode(str, &b->data);
+int dlen = b64decode(str, &b->data);
if (dlen < 0) b->data = NULL;
b->len = dlen;
}
-static uschar *
+uschar *
pdkim_encode_base64(blob * b)
{
return b64encode(b->data, b->len);
#define PDKIM_HDR_VALUE 2
static pdkim_signature *
-pdkim_parse_sig_header(pdkim_ctx *ctx, uschar * raw_hdr)
+pdkim_parse_sig_header(pdkim_ctx * ctx, uschar * raw_hdr)
{
-pdkim_signature *sig ;
+pdkim_signature * sig;
uschar *p, *q;
-uschar * cur_tag = NULL; int ts = 0, tl = 0;
-uschar * cur_val = NULL; int vs = 0, vl = 0;
+gstring * cur_tag = NULL;
+gstring * cur_val = NULL;
BOOL past_hname = FALSE;
BOOL in_b_val = FALSE;
int where = PDKIM_HDR_LIMBO;
sig->bodylength = -1;
/* Set so invalid/missing data error display is accurate */
-sig->algo = -1;
sig->version = 0;
+sig->keytype = -1;
+sig->hashtype = -1;
q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1);
if (where == PDKIM_HDR_TAG)
{
if (c >= 'a' && c <= 'z')
- cur_tag = string_catn(cur_tag, &ts, &tl, p, 1);
+ cur_tag = string_catn(cur_tag, p, 1);
if (c == '=')
{
- cur_tag[tl] = '\0';
- if (Ustrcmp(cur_tag, "b") == 0)
+ if (Ustrcmp(string_from_gstring(cur_tag), "b") == 0)
{
*q++ = '=';
in_b_val = TRUE;
if (c == ';' || c == '\0')
{
- if (tl && vl)
+ /* We must have both tag and value, and tags must be one char except
+ for the possibility of "bh". */
+
+ if ( cur_tag && cur_val
+ && (cur_tag->ptr == 1 || *cur_tag->s == 'b')
+ )
{
- cur_val[vl] = '\0';
+ (void) string_from_gstring(cur_val);
pdkim_strtrim(cur_val);
- DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag, cur_val);
+ DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->s, cur_val->s);
- switch (*cur_tag)
+ switch (*cur_tag->s)
{
- case 'b':
- pdkim_decode_base64(cur_val,
- cur_tag[1] == 'h' ? &sig->bodyhash : &sig->sighash);
+ case 'b': /* sig-data or body-hash */
+ switch (cur_tag->s[1])
+ {
+ case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break;
+ case 'h': if (cur_tag->ptr == 2)
+ pdkim_decode_base64(cur_val->s, &sig->bodyhash);
+ break;
+ default: break;
+ }
break;
- case 'v':
+ case 'v': /* version */
/* We only support version 1, and that is currently the
only version there is. */
sig->version =
- Ustrcmp(cur_val, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
+ Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
break;
- case 'a':
- for (i = 0; pdkim_algos[i]; i++)
- if (Ustrcmp(cur_val, pdkim_algos[i]) == 0)
- {
- sig->algo = i;
- break;
- }
- break;
- case 'c':
- for (i = 0; pdkim_combined_canons[i].str; i++)
- if (Ustrcmp(cur_val, pdkim_combined_canons[i].str) == 0)
- {
- sig->canon_headers = pdkim_combined_canons[i].canon_headers;
- sig->canon_body = pdkim_combined_canons[i].canon_body;
- break;
- }
+ case 'a': /* algorithm */
+ {
+ const uschar * list = cur_val->s;
+ int sep = '-';
+ uschar * elem;
+
+ if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+ for(i = 0; i < nelem(pdkim_keytypes); i++)
+ if (Ustrcmp(elem, pdkim_keytypes[i]) == 0)
+ { sig->keytype = i; break; }
+ if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+ for (i = 0; i < nelem(pdkim_hashes); i++)
+ if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0)
+ { sig->hashtype = i; break; }
+ }
+
+ case 'c': /* canonicalization */
+ pdkim_cstring_to_canons(cur_val->s, 0,
+ &sig->canon_headers, &sig->canon_body);
break;
- case 'q':
+ case 'q': /* Query method (for pubkey)*/
for (i = 0; pdkim_querymethods[i]; i++)
- if (Ustrcmp(cur_val, pdkim_querymethods[i]) == 0)
+ if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0)
{
- sig->querymethod = i;
+ sig->querymethod = i; /* we never actually use this */
break;
}
break;
- case 's':
- sig->selector = string_copy(cur_val); break;
- case 'd':
- sig->domain = string_copy(cur_val); break;
- case 'i':
- sig->identity = pdkim_decode_qp(cur_val); break;
- case 't':
- sig->created = strtoul(CS cur_val, NULL, 10); break;
- case 'x':
- sig->expires = strtoul(CS cur_val, NULL, 10); break;
- case 'l':
- sig->bodylength = strtol(CS cur_val, NULL, 10); break;
- case 'h':
- sig->headernames = string_copy(cur_val); break;
- case 'z':
- sig->copiedheaders = pdkim_decode_qp(cur_val); break;
+ case 's': /* Selector */
+ sig->selector = string_copyn(cur_val->s, cur_val->ptr); break;
+ case 'd': /* SDID */
+ sig->domain = string_copyn(cur_val->s, cur_val->ptr); break;
+ case 'i': /* AUID */
+ sig->identity = pdkim_decode_qp(cur_val->s); break;
+ case 't': /* Timestamp */
+ sig->created = strtoul(CS cur_val->s, NULL, 10); break;
+ case 'x': /* Expiration */
+ sig->expires = strtoul(CS cur_val->s, NULL, 10); break;
+ case 'l': /* Body length count */
+ sig->bodylength = strtol(CS cur_val->s, NULL, 10); break;
+ case 'h': /* signed header fields */
+ sig->headernames = string_copyn(cur_val->s, cur_val->ptr); break;
+ case 'z': /* Copied headfields */
+ sig->copiedheaders = pdkim_decode_qp(cur_val->s); break;
+/*XXX draft-ietf-dcrup-dkim-crypto-05 would need 'p' tag support
+for rsafp signatures. But later discussion is dropping those. */
default:
DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
break;
}
}
- tl = 0;
- vl = 0;
+ cur_tag = cur_val = NULL;
in_b_val = FALSE;
where = PDKIM_HDR_LIMBO;
}
else
- cur_val = string_catn(cur_val, &vs, &vl, p, 1);
+ cur_val = string_catn(cur_val, p, 1);
}
NEXT_CHAR:
*q++ = c;
}
+if (sig->keytype < 0 || sig->hashtype < 0) /* Cannot verify this signature */
+ return NULL;
+
*q = '\0';
/* Chomp raw header. The final newline must not be added to the signature. */
while (--q > sig->rawsig_no_b_val && (*q == '\r' || *q == '\n'))
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
-if (!exim_sha_init(&sig->body_hash_ctx,
- sig->algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256))
- {
- DEBUG(D_acl) debug_printf("PDKIM: hash init internal error\n");
+if (!pdkim_set_sig_bodyhash(ctx, sig))
return NULL;
- }
+
return sig;
}
/* -------------------------------------------------------------------------- */
-static pdkim_pubkey *
-pdkim_parse_pubkey_record(pdkim_ctx *ctx, const uschar *raw_record)
+pdkim_pubkey *
+pdkim_parse_pubkey_record(const uschar *raw_record)
{
-pdkim_pubkey *pub;
-const uschar *p;
-uschar * cur_tag = NULL; int ts = 0, tl = 0;
-uschar * cur_val = NULL; int vs = 0, vl = 0;
-int where = PDKIM_HDR_LIMBO;
+const uschar * ele;
+int sep = ';';
+pdkim_pubkey * pub;
pub = store_get(sizeof(pdkim_pubkey));
memset(pub, 0, sizeof(pdkim_pubkey));
-for (p = raw_record; ; p++)
+while ((ele = string_nextinlist(&raw_record, &sep, NULL, 0)))
+ {
+ const uschar * val;
+
+ if ((val = Ustrchr(ele, '=')))
{
- uschar c = *p;
+ int taglen = val++ - ele;
- /* Ignore FWS */
- if (c != '\r' && c != '\n') switch (where)
+ DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, ele, val);
+ switch (ele[0])
{
- case PDKIM_HDR_LIMBO: /* In limbo, just wait for a tag-char to appear */
- if (!(c >= 'a' && c <= 'z'))
- break;
- where = PDKIM_HDR_TAG;
- /*FALLTHROUGH*/
-
- case PDKIM_HDR_TAG:
- if (c >= 'a' && c <= 'z')
- cur_tag = string_catn(cur_tag, &ts, &tl, p, 1);
-
- if (c == '=')
- {
- cur_tag[tl] = '\0';
- where = PDKIM_HDR_VALUE;
- }
- break;
-
- case PDKIM_HDR_VALUE:
- if (c == ';' || c == '\0')
- {
- if (tl && vl)
- {
- cur_val[vl] = '\0';
- pdkim_strtrim(cur_val);
- DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag, cur_val);
-
- switch (cur_tag[0])
- {
- case 'v':
- pub->version = string_copy(cur_val); break;
- case 'h':
- case 'k':
-/* This field appears to never be used. Also, unclear why
-a 'k' (key-type_ would go in this field name. There is a field
-"keytype", also never used.
- pub->hashes = string_copy(cur_val);
-*/
- break;
- case 'g':
- pub->granularity = string_copy(cur_val); break;
- case 'n':
- pub->notes = pdkim_decode_qp(cur_val); break;
- case 'p':
- pdkim_decode_base64(US cur_val, &pub->key); break;
- case 's':
- pub->srvtype = string_copy(cur_val); break;
- case 't':
- if (Ustrchr(cur_val, 'y') != NULL) pub->testing = 1;
- if (Ustrchr(cur_val, 's') != NULL) pub->no_subdomaining = 1;
- break;
- default:
- DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
+ case 'v': pub->version = val; break;
+ case 'h': pub->hashes = val; break;
+ case 'k': pub->keytype = val; break;
+ case 'g': pub->granularity = val; break;
+ case 'n': pub->notes = pdkim_decode_qp(val); break;
+ case 'p': pdkim_decode_base64(val, &pub->key); break;
+ case 's': pub->srvtype = val; break;
+ case 't': if (Ustrchr(val, 'y')) pub->testing = 1;
+ if (Ustrchr(val, 's')) pub->no_subdomaining = 1;
break;
- }
- }
- tl = 0;
- vl = 0;
- where = PDKIM_HDR_LIMBO;
- }
- else
- cur_val = string_catn(cur_val, &vs, &vl, p, 1);
- break;
+ default: DEBUG(D_acl) debug_printf(" Unknown tag encountered\n"); break;
}
-
- if (c == '\0') break;
}
+ }
/* Set fallback defaults */
-if (!pub->version ) pub->version = string_copy(PDKIM_PUB_RECORD_VERSION);
-else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0) return NULL;
+if (!pub->version)
+ pub->version = string_copy(PDKIM_PUB_RECORD_VERSION);
+else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0)
+ {
+ DEBUG(D_acl) debug_printf(" Bad v= field\n");
+ return NULL;
+ }
-if (!pub->granularity) pub->granularity = string_copy(US"*");
-/*
-if (!pub->keytype ) pub->keytype = string_copy(US"rsa");
-*/
-if (!pub->srvtype ) pub->srvtype = string_copy(US"*");
+if (!pub->granularity) pub->granularity = US"*";
+if (!pub->keytype ) pub->keytype = US"rsa";
+if (!pub->srvtype ) pub->srvtype = US"*";
/* p= is required */
if (pub->key.data)
return pub;
+DEBUG(D_acl) debug_printf(" Missing p= field\n");
return NULL;
}
/* -------------------------------------------------------------------------- */
-static int
-pdkim_update_bodyhash(pdkim_ctx * ctx, const char * data, int len)
+/* Update one bodyhash with some additional data.
+If we have to relax the data for this sig, return our copy of it. */
+
+static blob *
+pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data)
{
-pdkim_signature * sig;
-uschar * relaxed_data = NULL; /* Cache relaxed version of data */
-int relaxed_len = 0;
+blob * canon_data = orig_data;
+/* Defaults to simple canon (no further treatment necessary) */
-/* Traverse all signatures, updating their hashes. */
-for (sig = ctx->sig; sig; sig = sig->next)
+if (b->canon_method == PDKIM_CANON_RELAXED)
{
- /* Defaults to simple canon (no further treatment necessary) */
- const uschar *canon_data = CUS data;
- int canon_len = len;
-
- if (sig->canon_body == PDKIM_CANON_RELAXED)
+ /* Relax the line if not done already */
+ if (!relaxed_data)
{
- /* Relax the line if not done already */
- if (!relaxed_data)
- {
- BOOL seen_wsp = FALSE;
- const char *p;
- int q = 0;
+ BOOL seen_wsp = FALSE;
+ const uschar * p, * r;
+ int q = 0;
- /* We want to be able to free this else we allocate
- for the entire message which could be many MB. Since
- we don't know what allocations the SHA routines might
- do, not safe to use store_get()/store_reset(). */
+ /* We want to be able to free this else we allocate
+ for the entire message which could be many MB. Since
+ we don't know what allocations the SHA routines might
+ do, not safe to use store_get()/store_reset(). */
- relaxed_data = store_malloc(len+1);
+ relaxed_data = store_malloc(sizeof(blob) + orig_data->len+1);
+ relaxed_data->data = US (relaxed_data+1);
- for (p = data; *p; p++)
- {
- char c = *p;
- if (c == '\r')
- {
- if (q > 0 && relaxed_data[q-1] == ' ')
- q--;
- }
- else if (c == '\t' || c == ' ')
- {
- c = ' '; /* Turns WSP into SP */
- if (seen_wsp)
- continue;
- seen_wsp = TRUE;
- }
- else
- seen_wsp = FALSE;
- relaxed_data[q++] = c;
+ for (p = orig_data->data, r = p + orig_data->len; p < r; p++)
+ {
+ char c = *p;
+ if (c == '\r')
+ {
+ if (q > 0 && relaxed_data->data[q-1] == ' ')
+ q--;
+ }
+ else if (c == '\t' || c == ' ')
+ {
+ c = ' '; /* Turns WSP into SP */
+ if (seen_wsp)
+ continue;
+ seen_wsp = TRUE;
}
- relaxed_data[q] = '\0';
- relaxed_len = q;
+ else
+ seen_wsp = FALSE;
+ relaxed_data->data[q++] = c;
}
- canon_data = relaxed_data;
- canon_len = relaxed_len;
+ relaxed_data->data[q] = '\0';
+ relaxed_data->len = q;
}
+ canon_data = relaxed_data;
+ }
- /* Make sure we don't exceed the to-be-signed body length */
- if ( sig->bodylength >= 0
- && sig->signed_body_bytes + (unsigned long)canon_len > sig->bodylength
- )
- canon_len = sig->bodylength - sig->signed_body_bytes;
+/* Make sure we don't exceed the to-be-signed body length */
+if ( b->bodylength >= 0
+ && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength
+ )
+ canon_data->len = b->bodylength - b->signed_body_bytes;
- if (canon_len > 0)
- {
- exim_sha_update(&sig->body_hash_ctx, CUS canon_data, canon_len);
- sig->signed_body_bytes += canon_len;
- DEBUG(D_acl) pdkim_quoteprint(canon_data, canon_len);
- }
+if (canon_data->len > 0)
+ {
+ exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len);
+ b->signed_body_bytes += canon_data->len;
+ DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len);
}
-if (relaxed_data) store_free(relaxed_data);
-return PDKIM_OK;
+return relaxed_data;
}
/* -------------------------------------------------------------------------- */
static void
-pdkim_finish_bodyhash(pdkim_ctx *ctx)
+pdkim_finish_bodyhash(pdkim_ctx * ctx)
{
-pdkim_signature *sig;
+pdkim_bodyhash * b;
+pdkim_signature * sig;
+
+for (b = ctx->bodyhash; b; b = b->next) /* Finish hashes */
+ {
+ DEBUG(D_acl) debug_printf("PDKIM: finish bodyhash %d/%d/%ld len %ld\n",
+ b->hashtype, b->canon_method, b->bodylength, b->signed_body_bytes);
+ exim_sha_finish(&b->body_hash_ctx, &b->bh);
+ }
/* Traverse all signatures */
for (sig = ctx->sig; sig; sig = sig->next)
- { /* Finish hashes */
- blob bh;
-
- exim_sha_finish(&sig->body_hash_ctx, &bh);
+ {
+ b = sig->calc_body_hash;
DEBUG(D_acl)
{
- debug_printf("PDKIM [%s] Body bytes hashed: %lu\n"
- "PDKIM [%s] Body hash computed: ",
- sig->domain, sig->signed_body_bytes, sig->domain);
- pdkim_hexprint(CUS bh.data, bh.len);
+ debug_printf("PDKIM [%s] Body bytes (%s) hashed: %lu\n"
+ "PDKIM [%s] Body %s computed: ",
+ sig->domain, pdkim_canons[b->canon_method], b->signed_body_bytes,
+ sig->domain, pdkim_hashes[b->hashtype].dkim_hashname);
+ pdkim_hexprint(CUS b->bh.data, b->bh.len);
}
/* SIGNING -------------------------------------------------------------- */
if (ctx->flags & PDKIM_MODE_SIGN)
{
- sig->bodyhash = bh;
-
/* If bodylength limit is set, and we have received less bytes
than the requested amount, effectively remove the limit tag. */
- if (sig->signed_body_bytes < sig->bodylength)
+ if (b->signed_body_bytes < sig->bodylength)
sig->bodylength = -1;
}
/* VERIFICATION --------------------------------------------------------- */
/* Be careful that the header sig included a bodyash */
- if (sig->bodyhash.data && memcmp(bh.data, sig->bodyhash.data, bh.len) == 0)
+ if ( sig->bodyhash.data
+ && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
{
- DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash verified OK\n", sig->domain);
+ DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash compared OK\n", sig->domain);
}
else
{
-static int
+static void
pdkim_body_complete(pdkim_ctx * ctx)
{
-pdkim_signature * sig = ctx->sig; /*XXX assumes only one sig */
+pdkim_bodyhash * b;
/* In simple body mode, if any empty lines were buffered,
replace with one. rfc 4871 3.4.3 */
it indicates that all linebreaks should be buffered, including
the one terminating a text line */
-if ( sig && sig->canon_body == PDKIM_CANON_SIMPLE
- && sig->signed_body_bytes == 0
- && ctx->num_buffered_crlf > 0
- )
- pdkim_update_bodyhash(ctx, "\r\n", 2);
+for (b = ctx->bodyhash; b; b = b->next)
+ if ( b->canon_method == PDKIM_CANON_SIMPLE
+ && b->signed_body_bytes == 0
+ && b->num_buffered_blanklines > 0
+ )
+ (void) pdkim_update_ctx_bodyhash(b, &lineending, NULL);
ctx->flags |= PDKIM_SEEN_EOD;
ctx->linebuf_offset = 0;
-return PDKIM_OK;
}
/* -------------------------------------------------------------------------- */
/* Call from pdkim_feed below for processing complete body lines */
+/* NOTE: the line is not NUL-terminated; but we have a count */
-static int
-pdkim_bodyline_complete(pdkim_ctx *ctx)
+static void
+pdkim_bodyline_complete(pdkim_ctx * ctx)
{
-char *p = ctx->linebuf;
-int n = ctx->linebuf_offset;
-pdkim_signature *sig = ctx->sig; /*XXX assumes only one sig */
+blob line = {.data = ctx->linebuf, .len = ctx->linebuf_offset};
+pdkim_bodyhash * b;
+blob * rnl = NULL;
+blob * rline = NULL;
/* Ignore extra data if we've seen the end-of-data marker */
-if (ctx->flags & PDKIM_SEEN_EOD) goto BAIL;
+if (ctx->flags & PDKIM_SEEN_EOD) goto all_skip;
/* We've always got one extra byte to stuff a zero ... */
-ctx->linebuf[ctx->linebuf_offset] = '\0';
+ctx->linebuf[line.len] = '\0';
/* Terminate on EOD marker */
if (ctx->flags & PDKIM_DOT_TERM)
{
- if (memcmp(p, ".\r\n", 3) == 0)
- return pdkim_body_complete(ctx);
+ if (memcmp(line.data, ".\r\n", 3) == 0)
+ { pdkim_body_complete(ctx); return; }
/* Unstuff dots */
- if (memcmp(p, "..", 2) == 0)
- {
- p++;
- n--;
- }
+ if (memcmp(line.data, "..", 2) == 0)
+ { line.data++; line.len--; }
}
/* Empty lines need to be buffered until we find a non-empty line */
-if (memcmp(p, "\r\n", 2) == 0)
+if (memcmp(line.data, "\r\n", 2) == 0)
{
- ctx->num_buffered_crlf++;
- goto BAIL;
+ for (b = ctx->bodyhash; b; b = b->next) b->num_buffered_blanklines++;
+ goto all_skip;
}
-if (sig && sig->canon_body == PDKIM_CANON_RELAXED)
+/* Process line for each bodyhash separately */
+for (b = ctx->bodyhash; b; b = b->next)
{
- /* Lines with just spaces need to be buffered too */
- char *check = p;
- while (memcmp(check, "\r\n", 2) != 0)
+ if (b->canon_method == PDKIM_CANON_RELAXED)
{
- char c = *check;
+ /* Lines with just spaces need to be buffered too */
+ uschar * cp = line.data;
+ char c;
- if (c != '\t' && c != ' ')
- goto PROCESS;
- check++;
+ while ((c = *cp))
+ {
+ if (c == '\r' && cp[1] == '\n') break;
+ if (c != ' ' && c != '\t') goto hash_process;
+ cp++;
+ }
+
+ b->num_buffered_blanklines++;
+ goto hash_skip;
}
- ctx->num_buffered_crlf++;
- goto BAIL;
-}
+hash_process:
+ /* At this point, we have a non-empty line, so release the buffered ones. */
-PROCESS:
-/* At this point, we have a non-empty line, so release the buffered ones. */
-while (ctx->num_buffered_crlf)
- {
- pdkim_update_bodyhash(ctx, "\r\n", 2);
- ctx->num_buffered_crlf--;
+ while (b->num_buffered_blanklines)
+ {
+ rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
+ b->num_buffered_blanklines--;
+ }
+
+ rline = pdkim_update_ctx_bodyhash(b, &line, rline);
+hash_skip: ;
}
-pdkim_update_bodyhash(ctx, p, n);
+if (rnl) store_free(rnl);
+if (rline) store_free(rline);
+
+all_skip:
-BAIL:
ctx->linebuf_offset = 0;
-return PDKIM_OK;
+return;
}
#define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:"
static int
-pdkim_header_complete(pdkim_ctx *ctx)
+pdkim_header_complete(pdkim_ctx * ctx)
{
+pdkim_signature * sig, * last_sig;
+
/* Special case: The last header can have an extra \r appended */
-if ( (ctx->cur_header_len > 1) &&
- (ctx->cur_header[(ctx->cur_header_len)-1] == '\r') )
- --ctx->cur_header_len;
-ctx->cur_header[ctx->cur_header_len] = '\0';
+if ( (ctx->cur_header->ptr > 1) &&
+ (ctx->cur_header->s[ctx->cur_header->ptr-1] == '\r') )
+ --ctx->cur_header->ptr;
+(void) string_from_gstring(ctx->cur_header);
+
+#ifdef EXPERIMENTAL_ARC
+/* Feed the header line to ARC processing */
+(void) arc_header_feed(ctx->cur_header, !(ctx->flags & PDKIM_MODE_SIGN));
+#endif
-ctx->num_headers++;
-if (ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
+if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
/* SIGNING -------------------------------------------------------------- */
if (ctx->flags & PDKIM_MODE_SIGN)
- {
- pdkim_signature *sig;
-
for (sig = ctx->sig; sig; sig = sig->next) /* Traverse all signatures */
/* Add header to the signed headers list (in reverse order) */
- sig->headers = pdkim_prepend_stringlist(sig->headers,
- ctx->cur_header);
- }
+ sig->headers = pdkim_prepend_stringlist(sig->headers, ctx->cur_header->s);
/* VERIFICATION ----------------------------------------------------------- */
/* DKIM-Signature: headers are added to the verification list */
DEBUG(D_acl)
{
debug_printf("PDKIM >> raw hdr: ");
- pdkim_quoteprint(CUS ctx->cur_header, Ustrlen(ctx->cur_header));
+ pdkim_quoteprint(CUS ctx->cur_header->s, ctx->cur_header->ptr);
}
#endif
- if (strncasecmp(CCS ctx->cur_header,
+ if (strncasecmp(CCS ctx->cur_header->s,
DKIM_SIGNATURE_HEADERNAME,
Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
{
- pdkim_signature * new_sig, * last_sig;
-
/* Create and chain new signature block. We could error-check for all
required tags here, but prefer to create the internal sig and expicitly
fail verification of it later. */
DEBUG(D_acl) debug_printf(
"PDKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
- new_sig = pdkim_parse_sig_header(ctx, ctx->cur_header);
+ sig = pdkim_parse_sig_header(ctx, ctx->cur_header->s);
if (!(last_sig = ctx->sig))
- ctx->sig = new_sig;
+ ctx->sig = sig;
else
{
while (last_sig->next) last_sig = last_sig->next;
- last_sig->next = new_sig;
+ last_sig->next = sig;
+ }
+
+ if (--dkim_collect_input == 0)
+ {
+ ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+ ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
+ return PDKIM_ERR_EXCESS_SIGS;
}
}
/* all headers are stored for signature verification */
- ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header);
+ ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
}
BAIL:
-*ctx->cur_header = '\0';
-ctx->cur_header_len = 0; /* leave buffer for reuse */
+ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0'; /* leave buffer for reuse */
return PDKIM_OK;
}
#define HEADER_BUFFER_FRAG_SIZE 256
DLLEXPORT int
-pdkim_feed(pdkim_ctx *ctx, char *data, int len)
+pdkim_feed(pdkim_ctx * ctx, uschar * data, int len)
{
int p, rc;
if (!data)
pdkim_body_complete(ctx);
-else for (p = 0; p<len; p++)
+else for (p = 0; p < len; p++)
{
uschar c = data[p];
else if (c == '\n')
{
ctx->flags &= ~PDKIM_SEEN_CR;
- if ((rc = pdkim_bodyline_complete(ctx)) != PDKIM_OK)
- return rc;
+ pdkim_bodyline_complete(ctx);
}
if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
else if (c == '\n')
{
if (!(ctx->flags & PDKIM_SEEN_CR)) /* emulate the CR */
- ctx->cur_header = string_catn(ctx->cur_header, &ctx->cur_header_size,
- &ctx->cur_header_len, CUS "\r", 1);
+ ctx->cur_header = string_catn(ctx->cur_header, CUS "\r", 1);
if (ctx->flags & PDKIM_SEEN_LF) /* Seen last header line */
{
ctx->flags &= ~PDKIM_SEEN_LF;
}
- if (ctx->cur_header_len < PDKIM_MAX_HEADER_LEN)
- ctx->cur_header = string_catn(ctx->cur_header, &ctx->cur_header_size,
- &ctx->cur_header_len, CUS &data[p], 1);
+ if (!ctx->cur_header || ctx->cur_header->ptr < PDKIM_MAX_HEADER_LEN)
+ ctx->cur_header = string_catn(ctx->cur_header, CUS &data[p], 1);
}
}
return PDKIM_OK;
-/* Extend a grwong header with a continuation-linebreak */
-static uschar *
-pdkim_hdr_cont(uschar * str, int * size, int * ptr, int * col)
+/* Extend a growing header with a continuation-linebreak */
+static gstring *
+pdkim_hdr_cont(gstring * str, int * col)
{
*col = 1;
-return string_catn(str, size, ptr, US"\r\n\t", 3);
+return string_catn(str, US"\r\n\t", 3);
}
*
* col: this int holds and receives column number (octets since last '\n')
* str: partial string to append to
- * size: current buffer size for str
- * ptr: current tail-pointer for str
* pad: padding, split line or space after before or after eg: ";"
* intro: - must join to payload eg "h=", usually the tag name
* payload: eg base64 data - long data can be split arbitrarily.
* names longer than 78, or bogus col. Input is assumed to be free of line breaks.
*/
-static uschar *
-pdkim_headcat(int * col, uschar * str, int * size, int * ptr,
+static gstring *
+pdkim_headcat(int * col, gstring * str,
const uschar * pad, const uschar * intro, const uschar * payload)
{
size_t l;
{
l = Ustrlen(pad);
if (*col + l > 78)
- str = pdkim_hdr_cont(str, size, ptr, col);
- str = string_catn(str, size, ptr, pad, l);
+ str = pdkim_hdr_cont(str, col);
+ str = string_catn(str, pad, l);
*col += l;
}
if (*col + l > 78)
{ /*can't fit intro - start a new line to make room.*/
- str = pdkim_hdr_cont(str, size, ptr, col);
+ str = pdkim_hdr_cont(str, col);
l = intro?Ustrlen(intro):0;
}
{ /* this fragment will not fit on a single line */
if (pad)
{
- str = string_catn(str, size, ptr, US" ", 1);
+ str = string_catn(str, US" ", 1);
*col += 1;
pad = NULL; /* only want this once */
l--;
{
size_t sl = Ustrlen(intro);
- str = string_catn(str, size, ptr, intro, sl);
+ str = string_catn(str, intro, sl);
*col += sl;
l -= sl;
intro = NULL; /* only want this once */
size_t sl = Ustrlen(payload);
size_t chomp = *col+sl < 77 ? sl : 78-*col;
- str = string_catn(str, size, ptr, payload, chomp);
+ str = string_catn(str, payload, chomp);
*col += chomp;
payload += chomp;
l -= chomp-1;
}
/* the while precondition tells us it didn't fit. */
- str = pdkim_hdr_cont(str, size, ptr, col);
+ str = pdkim_hdr_cont(str, col);
}
if (*col + l > 78)
{
- str = pdkim_hdr_cont(str, size, ptr, col);
+ str = pdkim_hdr_cont(str, col);
pad = NULL;
}
if (pad)
{
- str = string_catn(str, size, ptr, US" ", 1);
+ str = string_catn(str, US" ", 1);
*col += 1;
pad = NULL;
}
{
size_t sl = Ustrlen(intro);
- str = string_catn(str, size, ptr, intro, sl);
+ str = string_catn(str, intro, sl);
*col += sl;
l -= sl;
intro = NULL;
{
size_t sl = Ustrlen(payload);
- str = string_catn(str, size, ptr, payload, sl);
+ str = string_catn(str, payload, sl);
*col += sl;
}
/* -------------------------------------------------------------------------- */
+/* Signing: create signature header
+*/
static uschar *
-pdkim_create_header(pdkim_signature *sig, BOOL final)
+pdkim_create_header(pdkim_signature * sig, BOOL final)
{
uschar * base64_bh;
uschar * base64_b;
int col = 0;
-uschar * hdr; int hdr_size = 0, hdr_len = 0;
-uschar * canon_all; int can_size = 0, can_len = 0;
+gstring * hdr;
+gstring * canon_all;
-canon_all = string_cat (NULL, &can_size, &can_len,
- pdkim_canons[sig->canon_headers]);
-canon_all = string_catn(canon_all, &can_size, &can_len, US"/", 1);
-canon_all = string_cat (canon_all, &can_size, &can_len,
- pdkim_canons[sig->canon_body]);
-canon_all[can_len] = '\0';
+canon_all = string_cat (NULL, pdkim_canons[sig->canon_headers]);
+canon_all = string_catn(canon_all, US"/", 1);
+canon_all = string_cat (canon_all, pdkim_canons[sig->canon_body]);
+(void) string_from_gstring(canon_all);
-hdr = string_cat(NULL, &hdr_size, &hdr_len,
- US"DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
-col = hdr_len;
+hdr = string_cat(NULL, US"DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
+col = hdr->ptr;
/* Required and static bits */
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"a=",
- pdkim_algos[sig->algo]);
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"q=",
- pdkim_querymethods[sig->querymethod]);
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"c=",
- canon_all);
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"d=",
- sig->domain);
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"s=",
- sig->selector);
+hdr = pdkim_headcat(&col, hdr, US";", US"a=", dkim_sig_to_a_tag(sig));
+hdr = pdkim_headcat(&col, hdr, US";", US"q=", pdkim_querymethods[sig->querymethod]);
+hdr = pdkim_headcat(&col, hdr, US";", US"c=", canon_all->s);
+hdr = pdkim_headcat(&col, hdr, US";", US"d=", sig->domain);
+hdr = pdkim_headcat(&col, hdr, US";", US"s=", sig->selector);
/* list of header names can be split between items. */
{
if (c) *c ='\0';
if (!i)
- hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, NULL, NULL, US":");
+ hdr = pdkim_headcat(&col, hdr, NULL, NULL, US":");
- hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, s, i, n);
+ hdr = pdkim_headcat(&col, hdr, s, i, n);
if (!c)
break;
}
}
-base64_bh = pdkim_encode_base64(&sig->bodyhash);
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"bh=", base64_bh);
+base64_bh = pdkim_encode_base64(&sig->calc_body_hash->bh);
+hdr = pdkim_headcat(&col, hdr, US";", US"bh=", base64_bh);
/* Optional bits */
if (sig->identity)
- hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"i=", sig->identity);
+ hdr = pdkim_headcat(&col, hdr, US";", US"i=", sig->identity);
if (sig->created > 0)
{
uschar minibuf[20];
snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created);
- hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"t=", minibuf);
+ hdr = pdkim_headcat(&col, hdr, US";", US"t=", minibuf);
}
if (sig->expires > 0)
uschar minibuf[20];
snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires);
- hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"x=", minibuf);
+ hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf);
}
if (sig->bodylength >= 0)
uschar minibuf[20];
snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength);
- hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"l=", minibuf);
+ hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf);
}
/* Preliminary or final version? */
-base64_b = final ? pdkim_encode_base64(&sig->sighash) : US"";
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, US";", US"b=", base64_b);
+if (final)
+ {
+ base64_b = pdkim_encode_base64(&sig->sighash);
+ hdr = pdkim_headcat(&col, hdr, US";", US"b=", base64_b);
-/* add trailing semicolon: I'm not sure if this is actually needed */
-hdr = pdkim_headcat(&col, hdr, &hdr_size, &hdr_len, NULL, US";", US"");
+ /* add trailing semicolon: I'm not sure if this is actually needed */
+ hdr = pdkim_headcat(&col, hdr, NULL, US";", US"");
+ }
+else
+ {
+ /* To satisfy the rule "all surrounding whitespace [...] deleted"
+ ( RFC 6376 section 3.7 ) we ensure there is no whitespace here. Otherwise
+ the headcat routine could insert a linebreak which the relaxer would reduce
+ to a single space preceding the terminating semicolon, resulting in an
+ incorrect header-hash. */
+ hdr = pdkim_headcat(&col, hdr, US";", US"b=;", US"");
+ }
-hdr[hdr_len] = '\0';
-return hdr;
+return string_from_gstring(hdr);
}
/* -------------------------------------------------------------------------- */
+/* According to draft-ietf-dcrup-dkim-crypto-07 "keys are 256 bits" (referring
+to DNS, hence the pubkey). Check for more than 32 bytes; if so assume the
+alternate possible representation (still) being discussed: a
+SubjectPublickeyInfo wrapped key - and drop all but the trailing 32-bytes (it
+should be a DER, with exactly 12 leading bytes - but we could accept a BER also,
+which could be any size). We still rely on the crypto library for checking for
+undersize.
+
+When the RFC is published this should be re-addressed. */
+
+static void
+check_bare_ed25519_pubkey(pdkim_pubkey * p)
+{
+int excess = p->key.len - 32;
+if (excess > 0)
+ {
+ DEBUG(D_acl) debug_printf("PDKIM: unexpected pubkey len %lu\n", p->key.len);
+ p->key.data += excess; p->key.len = 32;
+ }
+}
+
+
static pdkim_pubkey *
-pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx)
+pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx,
+ const uschar ** errstr)
{
uschar * dns_txt_name, * dns_txt_reply;
pdkim_pubkey * p;
-const uschar * errstr;
/* Fetch public key for signing domain, from DNS */
dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
-dns_txt_reply = store_get(PDKIM_DNS_TXT_MAX_RECLEN);
-memset(dns_txt_reply, 0, PDKIM_DNS_TXT_MAX_RECLEN);
-
-if ( ctx->dns_txt_callback(CS dns_txt_name, CS dns_txt_reply) != PDKIM_OK
+if ( !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name))
|| dns_txt_reply[0] == '\0'
)
{
{
debug_printf(
"PDKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
- " Raw record: ");
+ " %s\n"
+ " Raw record: ",
+ dns_txt_name);
pdkim_quoteprint(CUS dns_txt_reply, Ustrlen(dns_txt_reply));
}
-if ( !(p = pdkim_parse_pubkey_record(ctx, CUS dns_txt_reply))
+if ( !(p = pdkim_parse_pubkey_record(CUS dns_txt_reply))
|| (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
)
{
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
/* Import public key */
-if ((errstr = exim_rsa_verify_init(&p->key, vctx)))
+
+/* Normally we use the signature a= tag to tell us the pubkey format.
+When signing under debug we do a test-import of the pubkey, and at that
+time we do not have a signature so we must interpret the pubkey k= tag
+instead. Assume writing on the sig is ok in that case. */
+
+if (sig->keytype < 0)
{
- DEBUG(D_acl) debug_printf("verify_init: %s\n", errstr);
+ int i;
+ for(i = 0; i < nelem(pdkim_keytypes); i++)
+ if (Ustrcmp(p->keytype, pdkim_keytypes[i]) == 0)
+ { sig->keytype = i; goto k_ok; }
+ DEBUG(D_acl) debug_printf("verify_init: unhandled keytype %s\n", p->keytype);
sig->verify_status = PDKIM_VERIFY_INVALID;
sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
return NULL;
}
+k_ok:
+
+if (sig->keytype == KEYTYPE_ED25519)
+ check_bare_ed25519_pubkey(p);
+if ((*errstr = exim_dkim_verify_init(&p->key,
+ sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER,
+ vctx)))
+ {
+ DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr);
+ sig->verify_status = PDKIM_VERIFY_INVALID;
+ sig->verify_ext_status = PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+ return NULL;
+ }
+
+vctx->keytype = sig->keytype;
return p;
}
/* -------------------------------------------------------------------------- */
DLLEXPORT int
-pdkim_feed_finish(pdkim_ctx *ctx, pdkim_signature **return_signatures)
+pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
+ const uschar ** err)
{
-pdkim_signature *sig = ctx->sig;
+pdkim_bodyhash * b;
+pdkim_signature * sig;
+BOOL verify_pass = FALSE;
/* Check if we must still flush a (partial) header. If that is the
case, the message has no body, and we must compute a body hash
out of '<CR><LF>' */
-if (ctx->cur_header && ctx->cur_header_len)
+if (ctx->cur_header && ctx->cur_header->ptr > 0)
{
- int rc = pdkim_header_complete(ctx);
- if (rc != PDKIM_OK) return rc;
- pdkim_update_bodyhash(ctx, "\r\n", 2);
+ blob * rnl = NULL;
+ int rc;
+
+ if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+ return rc;
+
+ for (b = ctx->bodyhash; b; b = b->next)
+ rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
+ if (rnl) store_free(rnl);
}
else
DEBUG(D_acl) debug_printf(
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-/* Build (and/or evaluate) body hash */
+/* Build (and/or evaluate) body hash. Do this even if no DKIM sigs, in case we
+have a hash to do for ARC. */
+
pdkim_finish_bodyhash(ctx);
-while (sig)
+if (!ctx->sig)
+ {
+ DEBUG(D_acl) debug_printf("PDKIM: no signatures\n");
+ *return_signatures = NULL;
+ return PDKIM_OK;
+ }
+
+for (sig = ctx->sig; sig; sig = sig->next)
{
- BOOL is_sha1 = sig->algo == PDKIM_ALGO_RSA_SHA1;
hctx hhash_ctx;
uschar * sig_hdr = US"";
blob hhash;
- blob hdata;
- int hdata_alloc = 0;
+ gstring * hdata = NULL;
+ es_ctx sctx;
- hdata.data = NULL;
- hdata.len = 0;
+ if ( !(ctx->flags & PDKIM_MODE_SIGN)
+ && sig->verify_status == PDKIM_VERIFY_FAIL)
+ {
+ DEBUG(D_acl)
+ debug_printf("PDKIM: [%s] abandoning this signature\n", sig->domain);
+ continue;
+ }
- if (!exim_sha_init(&hhash_ctx, is_sha1 ? HASH_SHA1 : HASH_SHA256))
+ /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA
+ suging only, as it happens) and for either GnuTLS and OpenSSL when we are
+ signing with EC (specifically, Ed25519). The former is because the GCrypt
+ signing operation is pure (does not do its own hash) so we must hash. The
+ latter is because we (stupidly, but this is what the IETF draft is saying)
+ must hash with the declared hash method, then pass the result to the library
+ hash-and-sign routine (because that's all the libraries are providing. And
+ we're stuck with whatever that hidden hash method is, too). We may as well
+ do this hash incrementally.
+ We don't need the hash we're calculating here for the GnuTLS and OpenSSL
+ cases of RSA signing, since those library routines can do hash-and-sign.
+
+ Some time in the future we could easily avoid doing the hash here for those
+ cases (which will be common for a long while. We could also change from
+ the current copy-all-the-headers-into-one-block, then call the hash-and-sign
+ implementation - to a proper incremental one. Unfortunately, GnuTLS just
+ cannot do incremental - either signing or verification. Unsure about GCrypt.
+ */
+
+ /*XXX The header hash is also used (so far) by the verify operation */
+
+ if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
{
- DEBUG(D_acl) debug_printf("PDKIM: hask setup internal error\n");
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "PDKIM: hash setup error, possibly nonhandled hashtype");
break;
}
+ if (ctx->flags & PDKIM_MODE_SIGN)
+ DEBUG(D_acl) debug_printf(
+ "PDKIM >> Headers to be signed: >>>>>>>>>>>>\n"
+ " %s\n",
+ sig->sign_headers);
+
DEBUG(D_acl) debug_printf(
- "PDKIM >> Header data for hash, canonicalized, in sequence >>>>>>>>>>>>>>\n");
+ "PDKIM >> Header data for hash, canonicalized (%-7s), in sequence >>\n",
+ pdkim_canons[sig->canon_headers]);
+
/* SIGNING ---------------------------------------------------------------- */
/* When signing, walk through our header list and add them to the hash. As we
if (ctx->flags & PDKIM_MODE_SIGN)
{
- uschar * headernames = NULL; /* Collected signed header names */
- int hs = 0, hl = 0;
+ gstring * g = NULL;
pdkim_stringlist *p;
const uschar * l;
uschar * s;
int sep = 0;
- for (p = sig->headers; p; p = p->next)
- if (header_name_match(p->value, sig->sign_headers) == PDKIM_OK)
+ /* Import private key, including the keytype which we need for building
+ the signature header */
+
+/*XXX extend for non-RSA algos */
+ if ((*err = exim_dkim_signing_init(CUS sig->privkey, &sctx)))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
+ return PDKIM_ERR_RSA_PRIVKEY;
+ }
+ sig->keytype = sctx.keytype;
+
+ for (sig->headernames = NULL, /* Collected signed header names */
+ p = sig->headers; p; p = p->next)
+ {
+ uschar * rh = p->value;
+
+ if (header_name_match(rh, sig->sign_headers) == PDKIM_OK)
{
- uschar * rh;
/* Collect header names (Note: colon presence is guaranteed here) */
- uschar * q = Ustrchr(p->value, ':');
+ g = string_append_listele_n(g, ':', rh, Ustrchr(rh, ':') - rh);
- headernames = string_catn(headernames, &hs, &hl,
- p->value, (q - US p->value) + (p->next ? 1 : 0));
-
- rh = sig->canon_headers == PDKIM_CANON_RELAXED
- ? pdkim_relax_header(p->value, 1) /* cook header for relaxed canon */
- : string_copy(CUS p->value); /* just copy it for simple canon */
+ if (sig->canon_headers == PDKIM_CANON_RELAXED)
+ rh = pdkim_relax_header(rh, TRUE); /* cook header for relaxed canon */
/* Feed header to the hash algorithm */
exim_sha_update(&hhash_ctx, CUS rh, Ustrlen(rh));
/* Remember headers block for signing (when the library cannot do incremental) */
- (void) exim_rsa_data_append(&hdata, &hdata_alloc, rh);
+ /*XXX we could avoid doing this for all but the GnuTLS/RSA case */
+ hdata = exim_dkim_data_append(hdata, rh);
DEBUG(D_acl) pdkim_quoteprint(rh, Ustrlen(rh));
}
+ }
+
+ /* Any headers we wanted to sign but were not present must also be listed.
+ Ignore elements that have been ticked-off or are marked as never-oversign. */
l = sig->sign_headers;
while((s = string_nextinlist(&l, &sep, NULL, 0)))
- if (*s != '_')
- { /*SSS string_append_listele() */
- if (hl > 0 && headernames[hl-1] != ':')
- headernames = string_catn(headernames, &hs, &hl, US":", 1);
-
- headernames = string_cat(headernames, &hs, &hl, s);
- }
- headernames[hl] = '\0';
-
- /* Copy headernames to signature struct */
- sig->headernames = headernames;
+ {
+ if (*s == '+') /* skip oversigning marker */
+ s++;
+ if (*s != '_' && *s != '=')
+ g = string_append_listele(g, ':', s);
+ }
+ sig->headernames = string_from_gstring(g);
/* Create signature header with b= omitted */
sig_hdr = pdkim_create_header(sig, FALSE);
/* cook header for relaxed canon, or just copy it for simple */
uschar * rh = sig->canon_headers == PDKIM_CANON_RELAXED
- ? pdkim_relax_header(hdrs->value, 1)
+ ? pdkim_relax_header(hdrs->value, TRUE)
: string_copy(CUS hdrs->value);
/* Feed header to the hash algorithm */
DEBUG(D_acl) debug_printf(
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ DEBUG(D_acl)
+ {
+ debug_printf(
+ "PDKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n");
+ pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
+ debug_printf(
+ "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ }
+
/* Relax header if necessary */
if (sig->canon_headers == PDKIM_CANON_RELAXED)
- sig_hdr = pdkim_relax_header(sig_hdr, 0);
+ sig_hdr = pdkim_relax_header(sig_hdr, FALSE);
DEBUG(D_acl)
{
- debug_printf(
- "PDKIM >> Signed DKIM-Signature header, canonicalized >>>>>>>>>>>>>>>>>\n");
+ debug_printf("PDKIM >> Signed DKIM-Signature header, canonicalized (%-7s) >>>>>>>\n",
+ pdkim_canons[sig->canon_headers]);
pdkim_quoteprint(CUS sig_hdr, Ustrlen(sig_hdr));
debug_printf(
"PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
DEBUG(D_acl)
{
- debug_printf("PDKIM [%s] Header hash computed: ", sig->domain);
+ debug_printf("PDKIM [%s] Header %s computed: ",
+ sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
pdkim_hexprint(hhash.data, hhash.len);
}
- /* Remember headers block for signing (when the library cannot do incremental) */
+ /* Remember headers block for signing (when the signing library cannot do
+ incremental) */
if (ctx->flags & PDKIM_MODE_SIGN)
- (void) exim_rsa_data_append(&hdata, &hdata_alloc, US sig_hdr);
+ hdata = exim_dkim_data_append(hdata, US sig_hdr);
/* SIGNING ---------------------------------------------------------------- */
if (ctx->flags & PDKIM_MODE_SIGN)
{
- es_ctx sctx;
- const uschar * errstr;
-
- /* Import private key */
- if ((errstr = exim_rsa_signing_init(US sig->rsa_privkey, &sctx)))
- {
- DEBUG(D_acl) debug_printf("signing_init: %s\n", errstr);
- return PDKIM_ERR_RSA_PRIVKEY;
- }
+ hashmethod hm = sig->keytype == KEYTYPE_ED25519
+#if defined(SIGN_OPENSSL)
+ ? HASH_NULL
+#else
+ ? HASH_SHA2_512
+#endif
+ : pdkim_hashes[sig->hashtype].exim_hashmethod;
- /* Do signing. With OpenSSL we are signing the hash of headers just
- calculated, with GnuTLS we have to sign an entire block of headers
- (due to available interfaces) and it recalculates the hash internally. */
+#ifdef SIGN_HAVE_ED25519
+ /* For GCrypt, and for EC, we pass the hash-of-headers to the signing
+ routine. For anything else we just pass the headers. */
-#if defined(RSA_OPENSSL) || defined(RSA_GCRYPT)
- hdata = hhash;
+ if (sig->keytype != KEYTYPE_ED25519)
#endif
+ {
+ hhash.data = hdata->s;
+ hhash.len = hdata->ptr;
+ }
- if ((errstr = exim_rsa_sign(&sctx, is_sha1, &hdata, &sig->sighash)))
+ if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash)))
{
- DEBUG(D_acl) debug_printf("signing: %s\n", errstr);
+ log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err);
return PDKIM_ERR_RSA_SIGNING;
}
else
{
ev_ctx vctx;
- const uschar * errstr;
- pdkim_pubkey * p;
+ hashmethod hm;
/* Make sure we have all required signature tags */
if (!( sig->domain && *sig->domain
&& sig->headernames && *sig->headernames
&& sig->bodyhash.data
&& sig->sighash.data
- && sig->algo > -1
+ && sig->keytype >= 0
+ && sig->hashtype >= 0
&& sig->version
) )
{
sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR;
DEBUG(D_acl) debug_printf(
- " Error in DKIM-Signature header: tags missing or invalid\n"
- "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ " Error in DKIM-Signature header: tags missing or invalid (%s)\n"
+ "PDKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n",
+ !(sig->domain && *sig->domain) ? "d="
+ : !(sig->selector && *sig->selector) ? "s="
+ : !(sig->headernames && *sig->headernames) ? "h="
+ : !sig->bodyhash.data ? "bh="
+ : !sig->sighash.data ? "b="
+ : sig->keytype < 0 || sig->hashtype < 0 ? "a="
+ : "v="
+ );
goto NEXT_VERIFY;
}
-
+
/* Make sure sig uses supported DKIM version (only v1) */
if (sig->version != 1)
{
goto NEXT_VERIFY;
}
- if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx)))
+ DEBUG(D_acl)
+ {
+ debug_printf( "PDKIM [%s] b from mail: ", sig->domain);
+ pdkim_hexprint(sig->sighash.data, sig->sighash.len);
+ }
+
+ if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
+ {
+ log_write(0, LOG_MAIN, "PDKIM: %s%s %s%s [failed key import]",
+ sig->domain ? "d=" : "", sig->domain ? sig->domain : US"",
+ sig->selector ? "s=" : "", sig->selector ? sig->selector : US"");
goto NEXT_VERIFY;
+ }
+
+ /* If the pubkey limits to a list of specific hashes, ignore sigs that
+ do not have the hash part of the sig algorithm matching */
+
+ if (sig->pubkey->hashes)
+ {
+ const uschar * list = sig->pubkey->hashes, * ele;
+ int sep = ':';
+ while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
+ if (Ustrcmp(ele, pdkim_hashes[sig->hashtype].dkim_hashname) == 0) break;
+ if (!ele)
+ {
+ DEBUG(D_acl) debug_printf("pubkey h=%s vs. sig a=%s_%s\n",
+ sig->pubkey->hashes,
+ pdkim_keytypes[sig->keytype],
+ pdkim_hashes[sig->hashtype].dkim_hashname);
+ sig->verify_status = PDKIM_VERIFY_FAIL;
+ sig->verify_ext_status = PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH;
+ goto NEXT_VERIFY;
+ }
+ }
+
+ hm = sig->keytype == KEYTYPE_ED25519
+#if defined(SIGN_OPENSSL)
+ ? HASH_NULL
+#else
+ ? HASH_SHA2_512
+#endif
+ : pdkim_hashes[sig->hashtype].exim_hashmethod;
/* Check the signature */
- if ((errstr = exim_rsa_verify(&vctx, is_sha1, &hhash, &sig->sighash)))
+
+ if ((*err = exim_dkim_verify(&vctx, hm, &hhash, &sig->sighash)))
{
- DEBUG(D_acl) debug_printf("headers verify: %s\n", errstr);
+ DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
sig->verify_status = PDKIM_VERIFY_FAIL;
sig->verify_ext_status = PDKIM_VERIFY_FAIL_MESSAGE;
goto NEXT_VERIFY;
/* We have a winner! (if bodyhash was correct earlier) */
if (sig->verify_status == PDKIM_VERIFY_NONE)
+ {
sig->verify_status = PDKIM_VERIFY_PASS;
+ verify_pass = TRUE;
+ }
NEXT_VERIFY:
DEBUG(D_acl)
{
- debug_printf("PDKIM [%s] signature status: %s",
- sig->domain, pdkim_verify_status_str(sig->verify_status));
+ debug_printf("PDKIM [%s] %s signature status: %s",
+ sig->domain, dkim_sig_to_a_tag(sig),
+ pdkim_verify_status_str(sig->verify_status));
if (sig->verify_ext_status > 0)
debug_printf(" (%s)\n",
pdkim_verify_ext_status_str(sig->verify_ext_status));
debug_printf("\n");
}
}
-
- sig = sig->next;
}
/* If requested, set return pointer to signature(s) */
if (return_signatures)
*return_signatures = ctx->sig;
-return PDKIM_OK;
+return ctx->flags & PDKIM_MODE_SIGN || verify_pass
+ ? PDKIM_OK : PDKIM_FAIL;
}
/* -------------------------------------------------------------------------- */
DLLEXPORT pdkim_ctx *
-pdkim_init_verify(int(*dns_txt_callback)(char *, char *), BOOL dot_stuffing)
+pdkim_init_verify(uschar * (*dns_txt_callback)(uschar *), BOOL dot_stuffing)
{
pdkim_ctx * ctx;
/* -------------------------------------------------------------------------- */
-DLLEXPORT pdkim_ctx *
-pdkim_init_sign(char * domain, char * selector, char * rsa_privkey, int algo,
- BOOL dot_stuffed, int(*dns_txt_callback)(char *, char *))
+DLLEXPORT pdkim_signature *
+pdkim_init_sign(pdkim_ctx * ctx,
+ uschar * domain, uschar * selector, uschar * privkey,
+ uschar * hashname, const uschar ** errstr)
{
-pdkim_ctx * ctx;
+int hashtype;
pdkim_signature * sig;
-if (!domain || !selector || !rsa_privkey)
+if (!domain || !selector || !privkey)
return NULL;
-ctx = store_get(sizeof(pdkim_ctx) + PDKIM_MAX_BODY_LINE_LEN + sizeof(pdkim_signature));
-memset(ctx, 0, sizeof(pdkim_ctx));
-
-ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
-ctx->linebuf = CS (ctx+1);
+/* Allocate & init one signature struct */
-DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
-
-sig = (pdkim_signature *)(ctx->linebuf + PDKIM_MAX_BODY_LINE_LEN);
+sig = store_get(sizeof(pdkim_signature));
memset(sig, 0, sizeof(pdkim_signature));
sig->bodylength = -1;
-ctx->sig = sig;
sig->domain = string_copy(US domain);
sig->selector = string_copy(US selector);
-sig->rsa_privkey = string_copy(US rsa_privkey);
-sig->algo = algo;
+sig->privkey = string_copy(US privkey);
+sig->keytype = -1;
-if (!exim_sha_init(&sig->body_hash_ctx,
- algo == PDKIM_ALGO_RSA_SHA1 ? HASH_SHA1 : HASH_SHA256))
+for (hashtype = 0; hashtype < nelem(pdkim_hashes); hashtype++)
+ if (Ustrcmp(hashname, pdkim_hashes[hashtype].dkim_hashname) == 0)
+ { sig->hashtype = hashtype; break; }
+if (hashtype >= nelem(pdkim_hashes))
{
- DEBUG(D_acl) debug_printf("PDKIM: hash setup internal error\n");
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "PDKIM: unrecognised hashname '%s'", hashname);
return NULL;
}
pdkim_signature s = *sig;
ev_ctx vctx;
- debug_printf("PDKIM (checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
- if (!pdkim_key_from_dns(ctx, &s, &vctx))
+ debug_printf("PDKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+ if (!pdkim_key_from_dns(ctx, &s, &vctx, errstr))
debug_printf("WARNING: bad dkim key in dns\n");
- debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+ debug_printf("PDKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
}
-return ctx;
+return sig;
}
/* -------------------------------------------------------------------------- */
-DLLEXPORT int
-pdkim_set_optional(pdkim_ctx *ctx,
- char *sign_headers,
- char *identity,
+DLLEXPORT void
+pdkim_set_optional(pdkim_signature * sig,
+ char * sign_headers,
+ char * identity,
int canon_headers,
int canon_body,
long bodylength,
unsigned long created,
unsigned long expires)
{
-pdkim_signature * sig = ctx->sig;
-
if (identity)
sig->identity = string_copy(US identity);
sig->created = created;
sig->expires = expires;
-return PDKIM_OK;
+return;
+}
+
+
+
+/* Set up a blob for calculating the bodyhash according to the
+given needs. Use an existing one if possible, or create a new one.
+
+Return: hashblob pointer, or NULL on error
+*/
+pdkim_bodyhash *
+pdkim_set_bodyhash(pdkim_ctx * ctx, int hashtype, int canon_method,
+ long bodylength)
+{
+pdkim_bodyhash * b;
+
+for (b = ctx->bodyhash; b; b = b->next)
+ if ( hashtype == b->hashtype
+ && canon_method == b->canon_method
+ && bodylength == b->bodylength)
+ {
+ DEBUG(D_receive) debug_printf("PDKIM: using existing bodyhash %d/%d/%ld\n",
+ hashtype, canon_method, bodylength);
+ return b;
+ }
+
+DEBUG(D_receive) debug_printf("PDKIM: new bodyhash %d/%d/%ld\n",
+ hashtype, canon_method, bodylength);
+b = store_get(sizeof(pdkim_bodyhash));
+b->next = ctx->bodyhash;
+b->hashtype = hashtype;
+b->canon_method = canon_method;
+b->bodylength = bodylength;
+if (!exim_sha_init(&b->body_hash_ctx, /*XXX hash method: extend for sha512 */
+ pdkim_hashes[hashtype].exim_hashmethod))
+ {
+ DEBUG(D_acl)
+ debug_printf("PDKIM: hash init error, possibly nonhandled hashtype\n");
+ return NULL;
+ }
+b->signed_body_bytes = 0;
+b->num_buffered_blanklines = 0;
+ctx->bodyhash = b;
+return b;
+}
+
+
+/* Set up a blob for calculating the bodyhash according to the
+needs of this signature. Use an existing one if possible, or
+create a new one.
+
+Return: hashblob pointer, or NULL on error (only used as a boolean).
+*/
+pdkim_bodyhash *
+pdkim_set_sig_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig)
+{
+pdkim_bodyhash * b = pdkim_set_bodyhash(ctx,
+ sig->hashtype, sig->canon_body, sig->bodylength);
+sig->calc_body_hash = b;
+return b;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void
+pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
+ uschar * (*dns_txt_callback)(uschar *))
+{
+memset(ctx, 0, sizeof(pdkim_ctx));
+ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN);
+DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
}
void
pdkim_init(void)
{
-exim_rsa_init();
+exim_dkim_init();
}
* PDKIM - a RFC4871 (DKIM) implementation
*
* Copyright (C) 2009 - 2012 Tom Kistner <tom@duncanthrax.net>
- * Copyright (c) 2016 - 2017 Jeremy Harris
+ * Copyright (c) 2016 - 2018 Jeremy Harris
*
* http://duncanthrax.net/pdkim/
*
#include "../blob.h"
#include "../hash.h"
+#define PDKIM_DEFAULT_SIGN_HEADERS "From:Sender:Reply-To:Subject:Date:"\
+ "Message-ID:To:Cc:MIME-Version:Content-Type:"\
+ "Content-Transfer-Encoding:Content-ID:"\
+ "Content-Description:Resent-Date:Resent-From:"\
+ "Resent-Sender:Resent-To:Resent-Cc:"\
+ "Resent-Message-ID:In-Reply-To:References:"\
+ "List-Id:List-Help:List-Unsubscribe:"\
+ "List-Subscribe:List-Post:List-Owner:List-Archive"
+
/* -------------------------------------------------------------------------- */
/* Length of the preallocated buffer for the "answer" from the dns/txt
callback function. This should match the maximum RDLENGTH from DNS. */
#define PDKIM_ERR_RSA_SIGNING -102
#define PDKIM_ERR_LONG_LINE -103
#define PDKIM_ERR_BUFFER_TOO_SMALL -104
-#define PDKIM_SIGN_PRIVKEY_WRAP -105
-#define PDKIM_SIGN_PRIVKEY_B64D -106
+#define PDKIM_ERR_EXCESS_SIGS -105
+#define PDKIM_SIGN_PRIVKEY_WRAP -106
+#define PDKIM_SIGN_PRIVKEY_B64D -107
/* -------------------------------------------------------------------------- */
/* Main/Extended verification status */
#define PDKIM_VERIFY_INVALID 1
#define PDKIM_VERIFY_FAIL 2
#define PDKIM_VERIFY_PASS 3
+#define PDKIM_VERIFY_POLICY BIT(31)
#define PDKIM_VERIFY_FAIL_BODY 1
#define PDKIM_VERIFY_FAIL_MESSAGE 2
-#define PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE 3
-#define PDKIM_VERIFY_INVALID_BUFFER_SIZE 4
-#define PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD 5
-#define PDKIM_VERIFY_INVALID_PUBKEY_IMPORT 6
-#define PDKIM_VERIFY_INVALID_SIGNATURE_ERROR 7
-#define PDKIM_VERIFY_INVALID_DKIM_VERSION 8
+#define PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH 3
+#define PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE 4
+#define PDKIM_VERIFY_INVALID_BUFFER_SIZE 5
+#define PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD 6
+#define PDKIM_VERIFY_INVALID_PUBKEY_IMPORT 7
+#define PDKIM_VERIFY_INVALID_SIGNATURE_ERROR 8
+#define PDKIM_VERIFY_INVALID_DKIM_VERSION 9
/* -------------------------------------------------------------------------- */
/* Some parameter values */
#define PDKIM_QUERYMETHOD_DNS_TXT 0
-#define PDKIM_ALGO_RSA_SHA256 0
-#define PDKIM_ALGO_RSA_SHA1 1
+/*#define PDKIM_ALGO_RSA_SHA256 0 */
+/*#define PDKIM_ALGO_RSA_SHA1 1 */
#define PDKIM_CANON_SIMPLE 0
#define PDKIM_CANON_RELAXED 1
-#define PDKIM_HASH_SHA256 0
-#define PDKIM_HASH_SHA1 1
-
-#define PDKIM_KEYTYPE_RSA 0
-
/* -------------------------------------------------------------------------- */
/* Some required forward declarations, please ignore */
typedef struct pdkim_stringlist pdkim_stringlist;
/* -------------------------------------------------------------------------- */
/* Public key as (usually) fetched from DNS */
typedef struct pdkim_pubkey {
- uschar *version; /* v= */
- uschar *granularity; /* g= */
+ const uschar * version; /* v= */
+ const uschar *granularity; /* g= */
-#ifdef notdef
- uschar *hashes; /* h= */
- uschar *keytype; /* k= */
-#endif
- uschar *srvtype; /* s= */
+ const uschar * hashes; /* h= */
+ const uschar * keytype; /* k= */
+ const uschar * srvtype; /* s= */
uschar *notes; /* n= */
blob key; /* p= */
int no_subdomaining; /* t=s */
} pdkim_pubkey;
+/* -------------------------------------------------------------------------- */
+/* Body-hash to be calculated */
+typedef struct pdkim_bodyhash {
+ struct pdkim_bodyhash * next;
+ int hashtype;
+ int canon_method;
+ long bodylength;
+
+ hctx body_hash_ctx;
+ unsigned long signed_body_bytes; /* done so far */
+ int num_buffered_blanklines;
+
+ blob bh; /* completed hash */
+} pdkim_bodyhash;
+
/* -------------------------------------------------------------------------- */
/* Signature as it appears in a DKIM-Signature header */
typedef struct pdkim_signature {
+ struct pdkim_signature * next;
/* Bits stored in a DKIM signature header --------------------------- */
/* (v=) The version, as an integer. Currently, always "1" */
int version;
- /* (a=) The signature algorithm. Either PDKIM_ALGO_RSA_SHA256
- or PDKIM_ALGO_RSA_SHA1 */
- int algo;
+ /* (a=) The signature algorithm. Either PDKIM_ALGO_RSA_SHA256 */
+ int keytype; /* pdkim_keytypes index */
+ int hashtype; /* pdkim_hashes index */
/* (c=x/) Header canonicalization method. Either PDKIM_CANON_SIMPLE
or PDKIM_CANON_RELAXED */
/* (bh=) Raw body hash data, along with its length in bytes */
blob bodyhash;
- /* Folded DKIM-Signature: header. Singing only, NULL for verifying.
+ /* Folded DKIM-Signature: header. Signing only, NULL for verifying.
Ready for insertion into the message. Note: Folded using CRLFTB,
but final line terminator is NOT included. Note2: This buffer is
free()d when you call pdkim_free_ctx(). */
Caution: is NULL if signing or if no record was retrieved. */
pdkim_pubkey *pubkey;
- /* Pointer to the next pdkim_signature signature. NULL if signing or if
- this is the last signature. */
- void *next;
-
/* Properties below this point are used internally only ------------- */
/* Per-signature helper variables ----------------------------------- */
- hctx body_hash_ctx;
+ pdkim_bodyhash *calc_body_hash; /* hash to be / being calculated */
+
+ pdkim_stringlist *headers; /* Raw headers included in the sig */
- unsigned long signed_body_bytes; /* How many body bytes we hashed */
- pdkim_stringlist *headers; /* Raw headers included in the sig */
/* Signing specific ------------------------------------------------- */
- uschar * rsa_privkey; /* Private RSA key */
+ uschar * privkey; /* Private key */
uschar * sign_headers; /* To-be-signed header names */
uschar * rawsig_no_b_val; /* Original signature header w/o b= tag value. */
} pdkim_signature;
/* One (signing) or several chained (verification) signatures */
pdkim_signature *sig;
+ /* One (signing) or several chained (verification) bodyhashes */
+ pdkim_bodyhash *bodyhash;
+
/* Callback for dns/txt query method (verification only) */
- int(*dns_txt_callback)(char *, char *);
+ uschar * (*dns_txt_callback)(uschar *);
/* Coder's little helpers */
- uschar *cur_header;
- int cur_header_size;
- int cur_header_len;
- char *linebuf;
+ gstring *cur_header;
+ uschar *linebuf;
int linebuf_offset;
- int num_buffered_crlf;
int num_headers;
pdkim_stringlist *headers; /* Raw headers for verification */
} pdkim_ctx;
+/******************************************************************************/
+
+typedef struct {
+ const uschar * dkim_hashname;
+ hashmethod exim_hashmethod;
+} pdkim_hashtype;
+extern const pdkim_hashtype pdkim_hashes[];
+
+/******************************************************************************/
+
+
/* -------------------------------------------------------------------------- */
/* API functions. Please see the sample code in sample/test_sign.c and
sample/test_verify.c for documentation.
void pdkim_init (void);
+void pdkim_init_context (pdkim_ctx *, BOOL, uschar * (*)(uschar *));
+
DLLEXPORT
-pdkim_ctx *pdkim_init_sign (char *, char *, char *, int,
- BOOL, int(*)(char *, char *));
+pdkim_signature *pdkim_init_sign (pdkim_ctx *,
+ uschar *, uschar *, uschar *, uschar *,
+ const uschar **);
DLLEXPORT
-pdkim_ctx *pdkim_init_verify (int(*)(char *, char *), BOOL);
+pdkim_ctx *pdkim_init_verify (uschar * (*)(uschar *), BOOL);
DLLEXPORT
-int pdkim_set_optional (pdkim_ctx *, char *, char *,int, int,
+void pdkim_set_optional (pdkim_signature *, char *, char *,int, int,
long,
unsigned long,
unsigned long);
+int pdkim_hashname_to_hashtype(const uschar *, unsigned);
+void pdkim_cstring_to_canons(const uschar *, unsigned, int *, int *);
+pdkim_bodyhash *pdkim_set_bodyhash(pdkim_ctx *, int, int, long);
+pdkim_bodyhash *pdkim_set_sig_bodyhash(pdkim_ctx *, pdkim_signature *);
+
DLLEXPORT
-int pdkim_feed (pdkim_ctx *, char *, int);
+int pdkim_feed (pdkim_ctx *, uschar *, int);
DLLEXPORT
-int pdkim_feed_finish (pdkim_ctx *, pdkim_signature **);
+int pdkim_feed_finish (pdkim_ctx *, pdkim_signature **, const uschar **);
DLLEXPORT
void pdkim_free_ctx (pdkim_ctx *);
-const char * pdkim_errstr(int);
+const uschar * pdkim_errstr(int);
+
+extern uschar * pdkim_encode_base64(blob *);
+extern void pdkim_decode_base64(const uschar *, blob *);
+extern void pdkim_hexprint(const uschar *, int);
+extern void pdkim_quoteprint(const uschar *, int);
+extern pdkim_pubkey * pdkim_parse_pubkey_record(const uschar *);
+extern uschar * pdkim_relax_header_n(const uschar *, int, BOOL);
+extern uschar * pdkim_relax_header(const uschar *, BOOL);
+extern uschar * dkim_sig_to_a_tag(const pdkim_signature *);
#ifdef __cplusplus
}
/*
* PDKIM - a RFC4871 (DKIM) implementation
*
- * Copyright (C) 2016 Exim maintainers
+ * Copyright (C) 1995 - 2018 Exim maintainers
*
* Hash interface functions
*/
#include "../blob.h"
#include "../hash.h"
-#ifdef RSA_OPENSSL
+#ifdef SIGN_OPENSSL
# include <openssl/rsa.h>
# include <openssl/ssl.h>
# include <openssl/err.h>
-#elif defined(RSA_GNUTLS)
+#elif defined(SIGN_GNUTLS)
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
#endif
+++ /dev/null
-/*
- * PDKIM - a RFC4871 (DKIM) implementation
- *
- * Copyright (C) 2016 Exim maintainers
- *
- * RSA signing/verification interface
- */
-
-#include "../exim.h"
-
-#ifndef DISABLE_DKIM /* entire file */
-
-#ifndef SUPPORT_TLS
-# error Need SUPPORT_TLS for DKIM
-#endif
-
-#include "crypt_ver.h"
-#include "rsa.h"
-
-
-/******************************************************************************/
-#ifdef RSA_GNUTLS
-
-void
-exim_rsa_init(void)
-{
-}
-
-
-/* accumulate data (gnutls-only). String to be appended must be nul-terminated. */
-blob *
-exim_rsa_data_append(blob * b, int * alloc, uschar * s)
-{
-int len = b->len;
-b->data = string_append(b->data, alloc, &len, 1, s);
-b->len = len;
-return b;
-}
-
-
-
-/* import private key from PEM string in memory.
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx)
-{
-gnutls_datum_t k;
-int rc;
-
-k.data = privkey_pem;
-k.size = strlen(privkey_pem);
-
-if ( (rc = gnutls_x509_privkey_init(&sign_ctx->rsa)) != GNUTLS_E_SUCCESS
- /*|| (rc = gnutls_x509_privkey_import(sign_ctx->rsa, &k,
- GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS */
- )
- return gnutls_strerror(rc);
-
-if ( /* (rc = gnutls_x509_privkey_init(&sign_ctx->rsa)) != GNUTLS_E_SUCCESS
- ||*/ (rc = gnutls_x509_privkey_import(sign_ctx->rsa, &k,
- GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS
- )
- return gnutls_strerror(rc);
-
-return NULL;
-}
-
-
-
-/* allocate mem for signature (when signing) */
-/* sign data (gnutls_only)
-OR
-sign hash.
-
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig)
-{
-gnutls_datum_t k;
-size_t sigsize = 0;
-int rc;
-const uschar * ret = NULL;
-
-/* Allocate mem for signature */
-k.data = data->data;
-k.size = data->len;
-(void) gnutls_x509_privkey_sign_data(sign_ctx->rsa,
- is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256,
- 0, &k, NULL, &sigsize);
-
-sig->data = store_get(sigsize);
-sig->len = sigsize;
-
-/* Do signing */
-if ((rc = gnutls_x509_privkey_sign_data(sign_ctx->rsa,
- is_sha1 ? GNUTLS_DIG_SHA1 : GNUTLS_DIG_SHA256,
- 0, &k, sig->data, &sigsize)) != GNUTLS_E_SUCCESS
- )
- ret = gnutls_strerror(rc);
-
-gnutls_x509_privkey_deinit(sign_ctx->rsa);
-return ret;
-}
-
-
-
-/* import public key (from DER in memory)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx)
-{
-gnutls_datum_t k;
-int rc;
-const uschar * ret = NULL;
-
-gnutls_pubkey_init(&verify_ctx->rsa);
-
-k.data = pubkey_der->data;
-k.size = pubkey_der->len;
-
-if ((rc = gnutls_pubkey_import(verify_ctx->rsa, &k, GNUTLS_X509_FMT_DER))
- != GNUTLS_E_SUCCESS)
- ret = gnutls_strerror(rc);
-return ret;
-}
-
-
-/* verify signature (of hash) (given pubkey & alleged sig)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig)
-{
-gnutls_datum_t k, s;
-int rc;
-const uschar * ret = NULL;
-
-k.data = data_hash->data;
-k.size = data_hash->len;
-s.data = sig->data;
-s.size = sig->len;
-if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->rsa,
- is_sha1 ? GNUTLS_SIGN_RSA_SHA1 : GNUTLS_SIGN_RSA_SHA256,
- 0, &k, &s)) < 0)
- ret = gnutls_strerror(rc);
-
-gnutls_pubkey_deinit(verify_ctx->rsa);
-return ret;
-}
-
-
-
-
-#elif defined(RSA_GCRYPT)
-/******************************************************************************/
-
-
-/* Internal service routine:
-Read and move past an asn.1 header, checking class & tag,
-optionally returning the data-length */
-
-static int
-as_tag(blob * der, uschar req_cls, long req_tag, long * alen)
-{
-int rc;
-uschar tag_class;
-int taglen;
-long tag, len;
-
-/* debug_printf_indent("as_tag: %02x %02x %02x %02x\n",
- der->data[0], der->data[1], der->data[2], der->data[3]); */
-
-if ((rc = asn1_get_tag_der(der->data++, der->len--, &tag_class, &taglen, &tag))
- != ASN1_SUCCESS)
- return rc;
-
-if (tag_class != req_cls || tag != req_tag) return ASN1_ELEMENT_NOT_FOUND;
-
-if ((len = asn1_get_length_der(der->data, der->len, &taglen)) < 0)
- return ASN1_DER_ERROR;
-if (alen) *alen = len;
-
-/* debug_printf_indent("as_tag: tlen %d dlen %d\n", taglen, (int)len); */
-
-der->data += taglen;
-der->len -= taglen;
-return rc;
-}
-
-/* Internal service routine:
-Read and move over an asn.1 integer, setting an MPI to the value
-*/
-
-static uschar *
-as_mpi(blob * der, gcry_mpi_t * mpi)
-{
-long alen;
-int rc;
-gcry_error_t gerr;
-
-/* integer; move past the header */
-if ((rc = as_tag(der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
- return US asn1_strerror(rc);
-
-/* read to an MPI */
-if ((gerr = gcry_mpi_scan(mpi, GCRYMPI_FMT_STD, der->data, alen, NULL)))
- return US gcry_strerror(gerr);
-
-/* move over the data */
-der->data += alen; der->len -= alen;
-return NULL;
-}
-
-
-
-void
-exim_rsa_init(void)
-{
-/* Version check should be the very first call because it
-makes sure that important subsystems are initialized. */
-if (!gcry_check_version (GCRYPT_VERSION))
- {
- fputs ("libgcrypt version mismatch\n", stderr);
- exit (2);
- }
-
-/* We don't want to see any warnings, e.g. because we have not yet
-parsed program options which might be used to suppress such
-warnings. */
-gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
-
-/* ... If required, other initialization goes here. Note that the
-process might still be running with increased privileges and that
-the secure memory has not been initialized. */
-
-/* Allocate a pool of 16k secure memory. This make the secure memory
-available and also drops privileges where needed. */
-gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
-
-/* It is now okay to let Libgcrypt complain when there was/is
-a problem with the secure memory. */
-gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
-
-/* ... If required, other initialization goes here. */
-
-/* Tell Libgcrypt that initialization has completed. */
-gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
-
-return;
-}
-
-
-
-
-/* Accumulate data (gnutls-only).
-String to be appended must be nul-terminated. */
-
-blob *
-exim_rsa_data_append(blob * b, int * alloc, uschar * s)
-{
-return b; /*dummy*/
-}
-
-
-
-/* import private key from PEM string in memory.
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx)
-{
-uschar * s1, * s2;
-blob der;
-long alen;
-int rc;
-
-/*
- * RSAPrivateKey ::= SEQUENCE
- * version Version,
- * modulus INTEGER, -- n
- * publicExponent INTEGER, -- e
- * privateExponent INTEGER, -- d
- * prime1 INTEGER, -- p
- * prime2 INTEGER, -- q
- * exponent1 INTEGER, -- d mod (p-1)
- * exponent2 INTEGER, -- d mod (q-1)
- * coefficient INTEGER, -- (inverse of q) mod p
- * otherPrimeInfos OtherPrimeInfos OPTIONAL
- */
-
-if ( !(s1 = Ustrstr(CS privkey_pem, "-----BEGIN RSA PRIVATE KEY-----"))
- || !(s2 = Ustrstr(CS (s1+=31), "-----END RSA PRIVATE KEY-----" ))
- )
- return US"Bad PEM wrapper";
-
-*s2 = '\0';
-
-if ((der.len = b64decode(s1, &der.data)) < 0)
- return US"Bad PEM-DER b64 decode";
-
-/* untangle asn.1 */
-
-/* sequence; just move past the header */
-if ((rc = as_tag(&der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
- != ASN1_SUCCESS) goto asn_err;
-
-/* integer version; move past the header, check is zero */
-if ((rc = as_tag(&der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
- goto asn_err;
-if (alen != 1 || *der.data != 0)
- return US"Bad version number";
-der.data++; der.len--;
-
-if ( (s1 = as_mpi(&der, &sign_ctx->n))
- || (s1 = as_mpi(&der, &sign_ctx->e))
- || (s1 = as_mpi(&der, &sign_ctx->d))
- || (s1 = as_mpi(&der, &sign_ctx->p))
- || (s1 = as_mpi(&der, &sign_ctx->q))
- || (s1 = as_mpi(&der, &sign_ctx->dp))
- || (s1 = as_mpi(&der, &sign_ctx->dq))
- || (s1 = as_mpi(&der, &sign_ctx->qp))
- )
- return s1;
-
-DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n");
- {
- uschar * s;
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->n);
- debug_printf_indent(" N : %s\n", s);
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->e);
- debug_printf_indent(" E : %s\n", s);
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->d);
- debug_printf_indent(" D : %s\n", s);
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->p);
- debug_printf_indent(" P : %s\n", s);
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->q);
- debug_printf_indent(" Q : %s\n", s);
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dp);
- debug_printf_indent(" DP: %s\n", s);
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dq);
- debug_printf_indent(" DQ: %s\n", s);
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->qp);
- debug_printf_indent(" QP: %s\n", s);
- }
-return NULL;
-
-asn_err: return US asn1_strerror(rc);
-}
-
-
-
-/* allocate mem for signature (when signing) */
-/* sign data (gnutls_only)
-OR
-sign hash.
-
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig)
-{
-gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL;
-gcry_mpi_t m_sig;
-uschar * errstr;
-gcry_error_t gerr;
-
-#define SIGSPACE 128
-sig->data = store_get(SIGSPACE);
-
-if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0)
- {
- gcry_mpi_swap (sign_ctx->p, sign_ctx->q);
- gcry_mpi_invm (sign_ctx->qp, sign_ctx->p, sign_ctx->q);
- }
-
-if ( (gerr = gcry_sexp_build (&s_key, NULL,
- "(private-key (rsa (n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
- sign_ctx->n, sign_ctx->e,
- sign_ctx->d, sign_ctx->p,
- sign_ctx->q, sign_ctx->qp))
- || (gerr = gcry_sexp_build (&s_hash, NULL,
- is_sha1
- ? "(data(flags pkcs1)(hash sha1 %b))"
- : "(data(flags pkcs1)(hash sha256 %b))",
- (int) data->len, CS data->data))
- || (gerr = gcry_pk_sign (&s_sig, s_hash, s_key))
- )
- return US gcry_strerror(gerr);
-
-/* gcry_sexp_dump(s_sig); */
-
-if ( !(s_sig = gcry_sexp_find_token(s_sig, "s", 0))
- )
- return US"no sig result";
-
-m_sig = gcry_sexp_nth_mpi(s_sig, 1, GCRYMPI_FMT_USG);
-
-DEBUG(D_acl)
- {
- uschar * s;
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig);
- debug_printf_indent(" SG: %s\n", s);
- }
-
-gerr = gcry_mpi_print(GCRYMPI_FMT_USG, sig->data, SIGSPACE, &sig->len, m_sig);
-if (gerr)
- {
- debug_printf_indent("signature conversion from MPI to buffer failed\n");
- return US gcry_strerror(gerr);
- }
-#undef SIGSPACE
-
-return NULL;
-}
-
-
-/* import public key (from DER in memory)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx)
-{
-/*
-in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi()
-*/
-uschar tag_class;
-int taglen;
-long alen;
-int rc;
-uschar * errstr;
-gcry_error_t gerr;
-uschar * stage = US"S1";
-
-/*
-sequence
- sequence
- OBJECT:rsaEncryption
- NULL
- BIT STRING:RSAPublicKey
- sequence
- INTEGER:Public modulus
- INTEGER:Public exponent
-
-openssl rsa -in aux-fixed/dkim/dkim.private -pubout -outform DER | od -t x1 | head;
-openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump;
-openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump -offset 22;
-*/
-
-/* sequence; just move past the header */
-if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
- != ASN1_SUCCESS) goto asn_err;
-
-/* sequence; skip the entire thing */
-DEBUG(D_acl) stage = US"S2";
-if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen))
- != ASN1_SUCCESS) goto asn_err;
-pubkey_der->data += alen; pubkey_der->len -= alen;
-
-
-/* bitstring: limit range to size of bitstring;
-move over header + content wrapper */
-DEBUG(D_acl) stage = US"BS";
-if ((rc = as_tag(pubkey_der, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS)
- goto asn_err;
-pubkey_der->len = alen;
-pubkey_der->data++; pubkey_der->len--;
-
-/* sequence; just move past the header */
-DEBUG(D_acl) stage = US"S3";
-if ((rc = as_tag(pubkey_der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
- != ASN1_SUCCESS) goto asn_err;
-
-/* read two integers */
-DEBUG(D_acl) stage = US"MPI";
-if ( (errstr = as_mpi(pubkey_der, &verify_ctx->n))
- || (errstr = as_mpi(pubkey_der, &verify_ctx->e))
- )
- return errstr;
-
-DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n");
- {
- uschar * s;
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->n);
- debug_printf_indent(" N : %s\n", s);
- gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->e);
- debug_printf_indent(" E : %s\n", s);
- }
-
-return NULL;
-
-asn_err:
-DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc));
- return US asn1_strerror(rc);
-}
-
-
-/* verify signature (of hash) (given pubkey & alleged sig)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig)
-{
-/*
-cf. libgnutls 2.8.5 _wrap_gcry_pk_verify()
-*/
-gcry_mpi_t m_sig;
-gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
-gcry_error_t gerr;
-uschar * stage;
-
-if ( (stage = US"pkey sexp build",
- gerr = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))",
- verify_ctx->n, verify_ctx->e))
- || (stage = US"data sexp build",
- gerr = gcry_sexp_build (&s_hash, NULL,
- is_sha1
- ? "(data(flags pkcs1)(hash sha1 %b))"
- : "(data(flags pkcs1)(hash sha256 %b))",
- (int) data_hash->len, CS data_hash->data))
- || (stage = US"sig mpi scan",
- gerr = gcry_mpi_scan(&m_sig, GCRYMPI_FMT_USG, sig->data, sig->len, NULL))
- || (stage = US"sig sexp build",
- gerr = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", m_sig))
- || (stage = US"verify",
- gerr = gcry_pk_verify (s_sig, s_hash, s_pkey))
- )
- {
- DEBUG(D_acl) debug_printf_indent("verify: error in stage '%s'\n", stage);
- return US gcry_strerror(gerr);
- }
-
-if (s_sig) gcry_sexp_release (s_sig);
-if (s_hash) gcry_sexp_release (s_hash);
-if (s_pkey) gcry_sexp_release (s_pkey);
-gcry_mpi_release (m_sig);
-gcry_mpi_release (verify_ctx->n);
-gcry_mpi_release (verify_ctx->e);
-
-return NULL;
-}
-
-
-
-
-#elif defined(RSA_OPENSSL)
-/******************************************************************************/
-
-void
-exim_rsa_init(void)
-{
-}
-
-
-/* accumulate data (gnutls-only) */
-blob *
-exim_rsa_data_append(blob * b, int * alloc, uschar * s)
-{
-return b; /*dummy*/
-}
-
-
-/* import private key from PEM string in memory.
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_signing_init(uschar * privkey_pem, es_ctx * sign_ctx)
-{
-uschar * p, * q;
-int len;
-
-/* Convert PEM to DER */
-if ( !(p = Ustrstr(privkey_pem, "-----BEGIN RSA PRIVATE KEY-----"))
- || !(q = Ustrstr(p+=31, "-----END RSA PRIVATE KEY-----"))
- )
- return US"Bad PEM wrapping";
-
-*q = '\0';
-if ((len = b64decode(p, &p)) < 0)
- return US"b64decode failed";
-
-if (!(sign_ctx->rsa = d2i_RSAPrivateKey(NULL, CUSS &p, len)))
- {
- char ssl_errstring[256];
- ERR_load_crypto_strings(); /*XXX move to a startup routine */
- ERR_error_string(ERR_get_error(), ssl_errstring);
- return string_copy(US ssl_errstring);
- }
-
-return NULL;
-}
-
-
-
-/* allocate mem for signature (when signing) */
-/* sign data (gnutls_only)
-OR
-sign hash.
-
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_sign(es_ctx * sign_ctx, BOOL is_sha1, blob * data, blob * sig)
-{
-uint len;
-const uschar * ret = NULL;
-
-/* Allocate mem for signature */
-len = RSA_size(sign_ctx->rsa);
-sig->data = store_get(len);
-sig->len = len;
-
-/* Do signing */
-if (RSA_sign(is_sha1 ? NID_sha1 : NID_sha256,
- CUS data->data, data->len,
- US sig->data, &len, sign_ctx->rsa) != 1)
- {
- char ssl_errstring[256];
- ERR_load_crypto_strings(); /*XXX move to a startup routine */
- ERR_error_string(ERR_get_error(), ssl_errstring);
- ret = string_copy(US ssl_errstring);
- }
-
-RSA_free(sign_ctx->rsa);
-return ret;;
-}
-
-
-
-/* import public key (from DER in memory)
-Return: nULL for success, or an error string */
-
-const uschar *
-exim_rsa_verify_init(blob * pubkey_der, ev_ctx * verify_ctx)
-{
-const uschar * p = CUS pubkey_der->data;
-const uschar * ret = NULL;
-
-if (!(verify_ctx->rsa = d2i_RSA_PUBKEY(NULL, &p, (long) pubkey_der->len)))
- {
- char ssl_errstring[256];
- ERR_load_crypto_strings(); /*XXX move to a startup routine */
- ERR_error_string(ERR_get_error(), ssl_errstring);
- ret = string_copy(CUS ssl_errstring);
- }
-return ret;
-}
-
-
-
-
-/* verify signature (of hash) (given pubkey & alleged sig)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_rsa_verify(ev_ctx * verify_ctx, BOOL is_sha1, blob * data_hash, blob * sig)
-{
-const uschar * ret = NULL;
-
-if (RSA_verify(is_sha1 ? NID_sha1 : NID_sha256,
- CUS data_hash->data, data_hash->len,
- US sig->data, (uint) sig->len, verify_ctx->rsa) != 1)
- {
- char ssl_errstring[256];
- ERR_load_crypto_strings(); /*XXX move to a startup routine */
- ERR_error_string(ERR_get_error(), ssl_errstring);
- ret = string_copy(US ssl_errstring);
- }
-return ret;
-}
-
-
-#endif
-/******************************************************************************/
-
-#endif /*DISABLE_DKIM*/
-/* End of File */
+++ /dev/null
-/*
- * PDKIM - a RFC4871 (DKIM) implementation
- *
- * Copyright (C) 2016 Exim maintainers
- *
- * RSA signing/verification interface
- */
-
-#include "../exim.h"
-
-#ifndef DISABLE_DKIM /* entire file */
-
-#include "crypt_ver.h"
-
-#ifdef RSA_OPENSSL
-# include <openssl/rsa.h>
-# include <openssl/ssl.h>
-# include <openssl/err.h>
-#elif defined(RSA_GNUTLS)
-# include <gnutls/gnutls.h>
-# include <gnutls/x509.h>
-# include <gnutls/abstract.h>
-#elif defined(RSA_GCRYPT)
-# include <gcrypt.h>
-# include <libtasn1.h>
-#endif
-
-#include "../blob.h"
-
-
-#ifdef RSA_OPENSSL
-
-typedef struct {
- RSA * rsa;
-} es_ctx;
-
-typedef struct {
- RSA * rsa;
-} ev_ctx;
-
-#elif defined(RSA_GNUTLS)
-
-typedef struct {
- gnutls_x509_privkey_t rsa;
-} es_ctx;
-
-typedef struct {
- gnutls_pubkey_t rsa;
-} ev_ctx;
-
-#elif defined(RSA_GCRYPT)
-
-typedef struct {
- gcry_mpi_t n;
- gcry_mpi_t e;
- gcry_mpi_t d;
- gcry_mpi_t p;
- gcry_mpi_t q;
- gcry_mpi_t dp;
- gcry_mpi_t dq;
- gcry_mpi_t qp;
-} es_ctx;
-
-typedef struct {
- gcry_mpi_t n;
- gcry_mpi_t e;
-} ev_ctx;
-
-#endif
-
-
-extern void exim_rsa_init(void);
-extern blob * exim_rsa_data_append(blob *, int *, uschar *);
-
-extern const uschar * exim_rsa_signing_init(uschar *, es_ctx *);
-extern const uschar * exim_rsa_sign(es_ctx *, BOOL, blob *, blob *);
-extern const uschar * exim_rsa_verify_init(blob *, ev_ctx *);
-extern const uschar * exim_rsa_verify(ev_ctx *, BOOL, blob *, blob *);
-
-#endif /*DISABLE_DKIM*/
-/* End of File */
--- /dev/null
+/*
+ * PDKIM - a RFC4871 (DKIM) implementation
+ *
+ * Copyright (C) 1995 - 2018 Exim maintainers
+ *
+ * signing/verification interface
+ */
+
+#include "../exim.h"
+#include "crypt_ver.h"
+#include "signing.h"
+
+
+#ifdef MACRO_PREDEF
+# include "../macro_predef.h"
+
+void
+features_crypto(void)
+{
+# ifdef SIGN_HAVE_ED25519
+ builtin_macro_create(US"_CRYPTO_SIGN_ED25519");
+# endif
+# ifdef EXIM_HAVE_SHA3
+ builtin_macro_create(US"_CRYPTO_HASH_SHA3");
+# endif
+}
+#else
+
+#ifndef DISABLE_DKIM /* rest of file */
+
+#ifndef SUPPORT_TLS
+# error Need SUPPORT_TLS for DKIM
+#endif
+
+
+/******************************************************************************/
+#ifdef SIGN_GNUTLS
+# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL 3
+
+
+/* Logging function which can be registered with
+ * gnutls_global_set_log_function()
+ * gnutls_global_set_log_level() 0..9
+ */
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+static void
+exim_gnutls_logger_cb(int level, const char *message)
+{
+size_t len = strlen(message);
+if (len < 1)
+ {
+ DEBUG(D_tls) debug_printf("GnuTLS<%d> empty debug message\n", level);
+ return;
+ }
+DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
+ message[len-1] == '\n' ? "" : "\n");
+}
+#endif
+
+
+
+void
+exim_dkim_init(void)
+{
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+DEBUG(D_tls)
+ {
+ gnutls_global_set_log_function(exim_gnutls_logger_cb);
+ /* arbitrarily chosen level; bump upto 9 for more */
+ gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
+ }
+#endif
+}
+
+
+/* accumulate data (gnutls-only). String to be appended must be nul-terminated. */
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return string_cat(g, s);
+}
+
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+gnutls_datum_t k = { .data = (void *)privkey_pem, .size = Ustrlen(privkey_pem) };
+gnutls_x509_privkey_t x509_key;
+const uschar * where;
+int rc;
+
+if ( (where = US"internal init", rc = gnutls_x509_privkey_init(&x509_key))
+ || (rc = gnutls_privkey_init(&sign_ctx->key))
+ || (where = US"privkey PEM-block import",
+ rc = gnutls_x509_privkey_import(x509_key, &k, GNUTLS_X509_FMT_PEM))
+ || (where = US"internal privkey transfer",
+ rc = gnutls_privkey_import_x509(sign_ctx->key, x509_key, 0))
+ )
+ return string_sprintf("%s: %s", where, gnutls_strerror(rc));
+
+switch (rc = gnutls_privkey_get_pk_algorithm(sign_ctx->key, NULL))
+ {
+ case GNUTLS_PK_RSA: sign_ctx->keytype = KEYTYPE_RSA; break;
+#ifdef SIGN_HAVE_ED25519
+ case GNUTLS_PK_EDDSA_ED25519: sign_ctx->keytype = KEYTYPE_ED25519; break;
+#endif
+ default: return rc < 0
+ ? CUS gnutls_strerror(rc)
+ : string_sprintf("Unhandled key type: %d '%s'", rc, gnutls_pk_get_name(rc));
+ }
+
+return NULL;
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* hash & sign data. No way to do incremental.
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+{
+gnutls_datum_t k_data = { .data = data->data, .size = data->len };
+gnutls_digest_algorithm_t dig;
+gnutls_datum_t k_sig;
+int rc;
+
+switch (hash)
+ {
+ case HASH_SHA1: dig = GNUTLS_DIG_SHA1; break;
+ case HASH_SHA2_256: dig = GNUTLS_DIG_SHA256; break;
+ case HASH_SHA2_512: dig = GNUTLS_DIG_SHA512; break;
+ default: return US"nonhandled hash type";
+ }
+
+if ((rc = gnutls_privkey_sign_data(sign_ctx->key, dig, 0, &k_data, &k_sig)))
+ return CUS gnutls_strerror(rc);
+
+/* Don't care about deinit for the key; shortlived process */
+
+sig->data = k_sig.data;
+sig->len = k_sig.size;
+return NULL;
+}
+
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
+{
+gnutls_datum_t k;
+int rc;
+const uschar * ret = NULL;
+
+gnutls_pubkey_init(&verify_ctx->key);
+k.data = pubkey->data;
+k.size = pubkey->len;
+
+switch(fmt)
+ {
+ case KEYFMT_DER:
+ if ((rc = gnutls_pubkey_import(verify_ctx->key, &k, GNUTLS_X509_FMT_DER)))
+ ret = US gnutls_strerror(rc);
+ break;
+#ifdef SIGN_HAVE_ED25519
+ case KEYFMT_ED25519_BARE:
+ if ((rc = gnutls_pubkey_import_ecc_raw(verify_ctx->key,
+ GNUTLS_ECC_CURVE_ED25519, &k, NULL)))
+ ret = US gnutls_strerror(rc);
+ break;
+#endif
+ default:
+ ret = US"pubkey format not handled";
+ break;
+ }
+return ret;
+}
+
+
+/* verify signature (of hash if RSA sig, of data if EC sig. No way to do incremental)
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
+{
+gnutls_datum_t k = { .data = data_hash->data, .size = data_hash->len };
+gnutls_datum_t s = { .data = sig->data, .size = sig->len };
+int rc;
+const uschar * ret = NULL;
+
+#ifdef SIGN_HAVE_ED25519
+if (verify_ctx->keytype == KEYTYPE_ED25519)
+ {
+ if ((rc = gnutls_pubkey_verify_data2(verify_ctx->key,
+ GNUTLS_SIGN_EDDSA_ED25519, 0, &k, &s)) < 0)
+ ret = US gnutls_strerror(rc);
+ }
+else
+#endif
+ {
+ gnutls_sign_algorithm_t algo;
+ switch (hash)
+ {
+ case HASH_SHA1: algo = GNUTLS_SIGN_RSA_SHA1; break;
+ case HASH_SHA2_256: algo = GNUTLS_SIGN_RSA_SHA256; break;
+ case HASH_SHA2_512: algo = GNUTLS_SIGN_RSA_SHA512; break;
+ default: return US"nonhandled hash type";
+ }
+
+ if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->key, algo, 0, &k, &s)) < 0)
+ ret = US gnutls_strerror(rc);
+ }
+
+gnutls_pubkey_deinit(verify_ctx->key);
+return ret;
+}
+
+
+
+
+#elif defined(SIGN_GCRYPT)
+/******************************************************************************/
+/* This variant is used under pre-3.0.0 GnuTLS. Only rsa-sha1 and rsa-sha256 */
+
+
+/* Internal service routine:
+Read and move past an asn.1 header, checking class & tag,
+optionally returning the data-length */
+
+static int
+as_tag(blob * der, uschar req_cls, long req_tag, long * alen)
+{
+int rc;
+uschar tag_class;
+int taglen;
+long tag, len;
+
+debug_printf_indent("as_tag: %02x %02x %02x %02x\n",
+ der->data[0], der->data[1], der->data[2], der->data[3]);
+
+if ((rc = asn1_get_tag_der(der->data++, der->len--, &tag_class, &taglen, &tag))
+ != ASN1_SUCCESS)
+ return rc;
+
+if (tag_class != req_cls || tag != req_tag) return ASN1_ELEMENT_NOT_FOUND;
+
+if ((len = asn1_get_length_der(der->data, der->len, &taglen)) < 0)
+ return ASN1_DER_ERROR;
+if (alen) *alen = len;
+
+/* debug_printf_indent("as_tag: tlen %d dlen %d\n", taglen, (int)len); */
+
+der->data += taglen;
+der->len -= taglen;
+return rc;
+}
+
+/* Internal service routine:
+Read and move over an asn.1 integer, setting an MPI to the value
+*/
+
+static uschar *
+as_mpi(blob * der, gcry_mpi_t * mpi)
+{
+long alen;
+int rc;
+gcry_error_t gerr;
+
+debug_printf_indent("%s\n", __FUNCTION__);
+
+/* integer; move past the header */
+if ((rc = as_tag(der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
+ return US asn1_strerror(rc);
+
+/* read to an MPI */
+if ((gerr = gcry_mpi_scan(mpi, GCRYMPI_FMT_STD, der->data, alen, NULL)))
+ return US gcry_strerror(gerr);
+
+/* move over the data */
+der->data += alen; der->len -= alen;
+return NULL;
+}
+
+
+
+void
+exim_dkim_init(void)
+{
+/* Version check should be the very first call because it
+makes sure that important subsystems are initialized. */
+if (!gcry_check_version (GCRYPT_VERSION))
+ {
+ fputs ("libgcrypt version mismatch\n", stderr);
+ exit (2);
+ }
+
+/* We don't want to see any warnings, e.g. because we have not yet
+parsed program options which might be used to suppress such
+warnings. */
+gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+
+/* ... If required, other initialization goes here. Note that the
+process might still be running with increased privileges and that
+the secure memory has not been initialized. */
+
+/* Allocate a pool of 16k secure memory. This make the secure memory
+available and also drops privileges where needed. */
+gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+
+/* It is now okay to let Libgcrypt complain when there was/is
+a problem with the secure memory. */
+gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
+
+/* ... If required, other initialization goes here. */
+
+/* Tell Libgcrypt that initialization has completed. */
+gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+
+return;
+}
+
+
+
+
+/* Accumulate data (gnutls-only).
+String to be appended must be nul-terminated. */
+
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return g; /*dummy*/
+}
+
+
+
+/* import private key from PEM string in memory.
+Only handles RSA keys.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+uschar * s1, * s2;
+blob der;
+long alen;
+int rc;
+
+/*XXX will need extension to _spot_ as well as handle a
+non-RSA key? I think...
+So... this is not a PrivateKeyInfo - which would have a field
+identifying the keytype - PrivateKeyAlgorithmIdentifier -
+but a plain RSAPrivateKey (wrapped in PEM-headers. Can we
+use those as a type tag? What forms are there? "BEGIN EC PRIVATE KEY" (cf. ec(1ssl))
+
+How does OpenSSL PEM_read_bio_PrivateKey() deal with it?
+gnutls_x509_privkey_import() ?
+*/
+
+/*
+ * RSAPrivateKey ::= SEQUENCE
+ * version Version,
+ * modulus INTEGER, -- n
+ * publicExponent INTEGER, -- e
+ * privateExponent INTEGER, -- d
+ * prime1 INTEGER, -- p
+ * prime2 INTEGER, -- q
+ * exponent1 INTEGER, -- d mod (p-1)
+ * exponent2 INTEGER, -- d mod (q-1)
+ * coefficient INTEGER, -- (inverse of q) mod p
+ * otherPrimeInfos OtherPrimeInfos OPTIONAL
+
+ * ECPrivateKey ::= SEQUENCE {
+ * version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+ * privateKey OCTET STRING,
+ * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+ * publicKey [1] BIT STRING OPTIONAL
+ * }
+ * Hmm, only 1 useful item, and not even an integer? Wonder how we might use it...
+
+- actually, gnutls_x509_privkey_import() appears to require a curve name parameter
+ value for that is an OID? a local-only integer (it's an enum in GnuTLS)?
+
+
+Useful cmds:
+ ssh-keygen -t ecdsa -f foo.privkey
+ ssh-keygen -t ecdsa -b384 -f foo.privkey
+ ssh-keygen -t ecdsa -b521 -f foo.privkey
+ ssh-keygen -t ed25519 -f foo.privkey
+
+ < foo openssl pkcs8 -in /dev/stdin -inform PEM -nocrypt -topk8 -outform DER | od -x
+
+ openssl asn1parse -in foo -inform PEM -dump
+ openssl asn1parse -in foo -inform PEM -dump -stroffset 24 (??)
+(not good for ed25519)
+
+ */
+
+if ( !(s1 = Ustrstr(CS privkey_pem, "-----BEGIN RSA PRIVATE KEY-----"))
+ || !(s2 = Ustrstr(CS (s1+=31), "-----END RSA PRIVATE KEY-----" ))
+ )
+ return US"Bad PEM wrapper";
+
+*s2 = '\0';
+
+if ((der.len = b64decode(s1, &der.data)) < 0)
+ return US"Bad PEM-DER b64 decode";
+
+/* untangle asn.1 */
+
+/* sequence; just move past the header */
+if ((rc = as_tag(&der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+ != ASN1_SUCCESS) goto asn_err;
+
+/* integer version; move past the header, check is zero */
+if ((rc = as_tag(&der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
+ goto asn_err;
+if (alen != 1 || *der.data != 0)
+ return US"Bad version number";
+der.data++; der.len--;
+
+if ( (s1 = as_mpi(&der, &sign_ctx->n))
+ || (s1 = as_mpi(&der, &sign_ctx->e))
+ || (s1 = as_mpi(&der, &sign_ctx->d))
+ || (s1 = as_mpi(&der, &sign_ctx->p))
+ || (s1 = as_mpi(&der, &sign_ctx->q))
+ || (s1 = as_mpi(&der, &sign_ctx->dp))
+ || (s1 = as_mpi(&der, &sign_ctx->dq))
+ || (s1 = as_mpi(&der, &sign_ctx->qp))
+ )
+ return s1;
+
+#ifdef extreme_debug
+DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n");
+ {
+ uschar * s;
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->n);
+ debug_printf_indent(" N : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->e);
+ debug_printf_indent(" E : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->d);
+ debug_printf_indent(" D : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->p);
+ debug_printf_indent(" P : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->q);
+ debug_printf_indent(" Q : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dp);
+ debug_printf_indent(" DP: %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dq);
+ debug_printf_indent(" DQ: %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->qp);
+ debug_printf_indent(" QP: %s\n", s);
+ }
+#endif
+
+sign_ctx->keytype = KEYTYPE_RSA;
+return NULL;
+
+asn_err: return US asn1_strerror(rc);
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* sign already-hashed data.
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+{
+char * sexp_hash;
+gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL;
+gcry_mpi_t m_sig;
+uschar * errstr;
+gcry_error_t gerr;
+
+/*XXX will need extension for hash types (though, possibly, should
+be re-specced to not rehash but take an already-hashed value? Actually
+current impl looks WRONG - it _is_ given a hash so should not be
+re-hashing. Has this been tested?
+
+Will need extension for non-RSA sugning algos. */
+
+switch (hash)
+ {
+ case HASH_SHA1: sexp_hash = "(data(flags pkcs1)(hash sha1 %b))"; break;
+ case HASH_SHA2_256: sexp_hash = "(data(flags pkcs1)(hash sha256 %b))"; break;
+ default: return US"nonhandled hash type";
+ }
+
+#define SIGSPACE 128
+sig->data = store_get(SIGSPACE);
+
+if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0)
+ {
+ gcry_mpi_swap (sign_ctx->p, sign_ctx->q);
+ gcry_mpi_invm (sign_ctx->qp, sign_ctx->p, sign_ctx->q);
+ }
+
+if ( (gerr = gcry_sexp_build (&s_key, NULL,
+ "(private-key (rsa (n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
+ sign_ctx->n, sign_ctx->e,
+ sign_ctx->d, sign_ctx->p,
+ sign_ctx->q, sign_ctx->qp))
+ || (gerr = gcry_sexp_build (&s_hash, NULL, sexp_hash,
+ (int) data->len, CS data->data))
+ || (gerr = gcry_pk_sign (&s_sig, s_hash, s_key))
+ )
+ return US gcry_strerror(gerr);
+
+/* gcry_sexp_dump(s_sig); */
+
+if ( !(s_sig = gcry_sexp_find_token(s_sig, "s", 0))
+ )
+ return US"no sig result";
+
+m_sig = gcry_sexp_nth_mpi(s_sig, 1, GCRYMPI_FMT_USG);
+
+#ifdef extreme_debug
+DEBUG(D_acl)
+ {
+ uschar * s;
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig);
+ debug_printf_indent(" SG: %s\n", s);
+ }
+#endif
+
+gerr = gcry_mpi_print(GCRYMPI_FMT_USG, sig->data, SIGSPACE, &sig->len, m_sig);
+if (gerr)
+ {
+ debug_printf_indent("signature conversion from MPI to buffer failed\n");
+ return US gcry_strerror(gerr);
+ }
+#undef SIGSPACE
+
+return NULL;
+}
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
+{
+/*
+in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi()
+*/
+uschar tag_class;
+int taglen;
+long alen;
+int rc;
+uschar * errstr;
+gcry_error_t gerr;
+uschar * stage = US"S1";
+
+if (fmt != KEYFMT_DER) return US"pubkey format not handled";
+
+/*
+sequence
+ sequence
+ OBJECT:rsaEncryption
+ NULL
+ BIT STRING:RSAPublicKey
+ sequence
+ INTEGER:Public modulus
+ INTEGER:Public exponent
+
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout -outform DER | od -t x1 | head;
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump;
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump -offset 22;
+*/
+
+/* sequence; just move past the header */
+if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+ != ASN1_SUCCESS) goto asn_err;
+
+/* sequence; skip the entire thing */
+DEBUG(D_acl) stage = US"S2";
+if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen))
+ != ASN1_SUCCESS) goto asn_err;
+pubkey->data += alen; pubkey->len -= alen;
+
+
+/* bitstring: limit range to size of bitstring;
+move over header + content wrapper */
+DEBUG(D_acl) stage = US"BS";
+if ((rc = as_tag(pubkey, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS)
+ goto asn_err;
+pubkey->len = alen;
+pubkey->data++; pubkey->len--;
+
+/* sequence; just move past the header */
+DEBUG(D_acl) stage = US"S3";
+if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+ != ASN1_SUCCESS) goto asn_err;
+
+/* read two integers */
+DEBUG(D_acl) stage = US"MPI";
+if ( (errstr = as_mpi(pubkey, &verify_ctx->n))
+ || (errstr = as_mpi(pubkey, &verify_ctx->e))
+ )
+ return errstr;
+
+#ifdef extreme_debug
+DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n");
+ {
+ uschar * s;
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->n);
+ debug_printf_indent(" N : %s\n", s);
+ gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->e);
+ debug_printf_indent(" E : %s\n", s);
+ }
+
+#endif
+return NULL;
+
+asn_err:
+DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc));
+ return US asn1_strerror(rc);
+}
+
+
+/* verify signature (of hash)
+XXX though we appear to be doing a hash, too!
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
+{
+/*
+cf. libgnutls 2.8.5 _wrap_gcry_pk_verify()
+*/
+char * sexp_hash;
+gcry_mpi_t m_sig;
+gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
+gcry_error_t gerr;
+uschar * stage;
+
+/*XXX needs extension for SHA512 */
+switch (hash)
+ {
+ case HASH_SHA1: sexp_hash = "(data(flags pkcs1)(hash sha1 %b))"; break;
+ case HASH_SHA2_256: sexp_hash = "(data(flags pkcs1)(hash sha256 %b))"; break;
+ default: return US"nonhandled hash type";
+ }
+
+if ( (stage = US"pkey sexp build",
+ gerr = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))",
+ verify_ctx->n, verify_ctx->e))
+ || (stage = US"data sexp build",
+ gerr = gcry_sexp_build (&s_hash, NULL, sexp_hash,
+ (int) data_hash->len, CS data_hash->data))
+ || (stage = US"sig mpi scan",
+ gerr = gcry_mpi_scan(&m_sig, GCRYMPI_FMT_USG, sig->data, sig->len, NULL))
+ || (stage = US"sig sexp build",
+ gerr = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", m_sig))
+ || (stage = US"verify",
+ gerr = gcry_pk_verify (s_sig, s_hash, s_pkey))
+ )
+ {
+ DEBUG(D_acl) debug_printf_indent("verify: error in stage '%s'\n", stage);
+ return US gcry_strerror(gerr);
+ }
+
+if (s_sig) gcry_sexp_release (s_sig);
+if (s_hash) gcry_sexp_release (s_hash);
+if (s_pkey) gcry_sexp_release (s_pkey);
+gcry_mpi_release (m_sig);
+gcry_mpi_release (verify_ctx->n);
+gcry_mpi_release (verify_ctx->e);
+
+return NULL;
+}
+
+
+
+
+#elif defined(SIGN_OPENSSL)
+/******************************************************************************/
+
+void
+exim_dkim_init(void)
+{
+ERR_load_crypto_strings();
+}
+
+
+/* accumulate data (was gnutls-only but now needed for OpenSSL non-EC too
+because now using hash-and-sign interface) */
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return string_cat(g, s);
+}
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+BIO * bp = BIO_new_mem_buf(privkey_pem, -1);
+
+if (!(sign_ctx->key = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL)))
+ return string_sprintf("privkey PEM-block import: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+sign_ctx->keytype =
+#ifdef SIGN_HAVE_ED25519
+ EVP_PKEY_type(EVP_PKEY_id(sign_ctx->key)) == EVP_PKEY_ED25519
+ ? KEYTYPE_ED25519 : KEYTYPE_RSA;
+#else
+ KEYTYPE_RSA;
+#endif
+return NULL;
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* hash & sign data. Incremental not supported.
+
+Return: NULL for success with the signaature in the sig blob, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
+{
+const EVP_MD * md;
+EVP_MD_CTX * ctx;
+size_t siglen;
+
+switch (hash)
+ {
+ case HASH_NULL: md = NULL; break; /* Ed25519 signing */
+ case HASH_SHA1: md = EVP_sha1(); break;
+ case HASH_SHA2_256: md = EVP_sha256(); break;
+ case HASH_SHA2_512: md = EVP_sha512(); break;
+ default: return US"nonhandled hash type";
+ }
+
+#ifdef SIGN_HAVE_ED25519
+if ( (ctx = EVP_MD_CTX_new())
+ && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
+ && EVP_DigestSign(ctx, NULL, &siglen, NULL, 0) > 0
+ && (sig->data = store_get(siglen))
+
+ /* Obtain the signature (slen could change here!) */
+ && EVP_DigestSign(ctx, sig->data, &siglen, data->data, data->len) > 0
+ )
+ {
+ EVP_MD_CTX_destroy(ctx);
+ sig->len = siglen;
+ return NULL;
+ }
+#else
+/*XXX renamed to EVP_MD_CTX_new() in 1.1.0 */
+if ( (ctx = EVP_MD_CTX_create())
+ && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
+ && EVP_DigestSignUpdate(ctx, data->data, data->len) > 0
+ && EVP_DigestSignFinal(ctx, NULL, &siglen) > 0
+ && (sig->data = store_get(siglen))
+
+ /* Obtain the signature (slen could change here!) */
+ && EVP_DigestSignFinal(ctx, sig->data, &siglen) > 0
+ )
+ {
+ EVP_MD_CTX_destroy(ctx);
+ sig->len = siglen;
+ return NULL;
+ }
+#endif
+
+if (ctx) EVP_MD_CTX_destroy(ctx);
+return US ERR_error_string(ERR_get_error(), NULL);
+}
+
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx)
+{
+const uschar * s = pubkey->data;
+uschar * ret = NULL;
+
+switch(fmt)
+ {
+ case KEYFMT_DER:
+ /*XXX hmm, we never free this */
+ if (!(verify_ctx->key = d2i_PUBKEY(NULL, &s, pubkey->len)))
+ ret = US ERR_error_string(ERR_get_error(), NULL);
+ break;
+#ifdef SIGN_HAVE_ED25519
+ case KEYFMT_ED25519_BARE:
+ if (!(verify_ctx->key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
+ s, pubkey->len)))
+ ret = US ERR_error_string(ERR_get_error(), NULL);
+ break;
+#endif
+ default:
+ ret = US"pubkey format not handled";
+ break;
+ }
+
+return ret;
+}
+
+
+
+
+/* verify signature (of hash, except Ed25519 where of-data)
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data, blob * sig)
+{
+const EVP_MD * md;
+
+switch (hash)
+ {
+ case HASH_NULL: md = NULL; break;
+ case HASH_SHA1: md = EVP_sha1(); break;
+ case HASH_SHA2_256: md = EVP_sha256(); break;
+ case HASH_SHA2_512: md = EVP_sha512(); break;
+ default: return US"nonhandled hash type";
+ }
+
+#ifdef SIGN_HAVE_ED25519
+if (!md)
+ {
+ EVP_MD_CTX * ctx;
+
+ if ((ctx = EVP_MD_CTX_new()))
+ {
+ if ( EVP_DigestVerifyInit(ctx, NULL, md, NULL, verify_ctx->key) > 0
+ && EVP_DigestVerify(ctx, sig->data, sig->len, data->data, data->len) > 0
+ )
+ { EVP_MD_CTX_free(ctx); return NULL; }
+ EVP_MD_CTX_free(ctx);
+ }
+ }
+else
+#endif
+ {
+ EVP_PKEY_CTX * ctx;
+
+ if ((ctx = EVP_PKEY_CTX_new(verify_ctx->key, NULL)))
+ {
+ if ( EVP_PKEY_verify_init(ctx) > 0
+ && EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) > 0
+ && EVP_PKEY_CTX_set_signature_md(ctx, md) > 0
+ && EVP_PKEY_verify(ctx, sig->data, sig->len,
+ data->data, data->len) == 1
+ )
+ { EVP_PKEY_CTX_free(ctx); return NULL; }
+ EVP_PKEY_CTX_free(ctx);
+
+ DEBUG(D_tls)
+ if (Ustrcmp(ERR_reason_error_string(ERR_peek_error()), "wrong signature length") == 0)
+ debug_printf("sig len (from msg hdr): %d, expected (from dns pubkey) %d\n",
+ (int) sig->len, EVP_PKEY_size(verify_ctx->key));
+ }
+ }
+
+return US ERR_error_string(ERR_get_error(), NULL);
+}
+
+
+
+#endif
+/******************************************************************************/
+
+#endif /*DISABLE_DKIM*/
+#endif /*MACRO_PREDEF*/
+/* End of File */
--- /dev/null
+/*
+ * PDKIM - a RFC4871 (DKIM) implementation
+ *
+ * Copyright (C) 1995 - 2018 Exim maintainers
+ *
+ * RSA signing/verification interface
+ */
+
+#include "../exim.h"
+
+#ifndef DISABLE_DKIM /* entire file */
+
+#include "crypt_ver.h"
+
+#ifdef SIGN_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(SIGN_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+# include <gnutls/abstract.h>
+#elif defined(SIGN_GCRYPT)
+# include <gcrypt.h>
+# include <libtasn1.h>
+#endif
+
+#include "../blob.h"
+
+typedef enum {
+ KEYTYPE_RSA,
+ KEYTYPE_ED25519
+} keytype;
+
+typedef enum {
+ KEYFMT_DER, /* an asn.1 structure */
+ KEYFMT_ED25519_BARE /* just the key */
+} keyformat;
+
+
+#ifdef SIGN_OPENSSL
+
+typedef struct {
+ keytype keytype;
+ EVP_PKEY * key;
+} es_ctx;
+
+typedef struct {
+ keytype keytype;
+ EVP_PKEY * key;
+} ev_ctx;
+
+#elif defined(SIGN_GNUTLS)
+
+typedef struct {
+ keytype keytype;
+ gnutls_privkey_t key;
+} es_ctx;
+
+typedef struct {
+ keytype keytype;
+ gnutls_pubkey_t key;
+} ev_ctx;
+
+#elif defined(SIGN_GCRYPT)
+
+typedef struct {
+ keytype keytype;
+ gcry_mpi_t n;
+ gcry_mpi_t e;
+ gcry_mpi_t d;
+ gcry_mpi_t p;
+ gcry_mpi_t q;
+ gcry_mpi_t dp;
+ gcry_mpi_t dq;
+ gcry_mpi_t qp;
+} es_ctx;
+
+typedef struct {
+ keytype keytype;
+ gcry_mpi_t n;
+ gcry_mpi_t e;
+} ev_ctx;
+
+#endif
+
+
+extern void exim_dkim_init(void);
+extern gstring * exim_dkim_data_append(gstring *, uschar *);
+
+extern const uschar * exim_dkim_signing_init(const uschar *, es_ctx *);
+extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, blob *, blob *);
+extern const uschar * exim_dkim_verify_init(blob *, keyformat, ev_ctx *);
+extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, blob *, blob *);
+
+#endif /*DISABLE_DKIM*/
+/* End of File */
*************************************************/
/* Copyright (c) 1998 Malcolm Beattie */
+/* Copyright (C) 1999 - 2018 Exim maintainers */
/* Modified by PH to get rid of the "na" usage, March 1999.
Modified further by PH for general tidying for Exim 4.
str = expand_string(US SvPV(ST(0), len));
ST(0) = sv_newmortal();
if (str != NULL)
- sv_setpv(ST(0), (const char *) str);
- else if (!expand_string_forcedfail)
+ sv_setpv(ST(0), CCS str);
+ else if (!f.expand_string_forcedfail)
croak("syntax error in Exim::expand_string argument: %s",
expand_string_message);
}
interp_perl = 0;
}
-uschar *
-call_perl_cat(uschar *yield, int *sizep, int *ptrp, uschar **errstrp,
- uschar *name, uschar **arg)
+gstring *
+call_perl_cat(gstring * yield, uschar **errstrp, uschar *name, uschar **arg)
{
dSP;
SV *sv;
return NULL;
}
str = US SvPV(sv, len);
- yield = string_catn(yield, sizep, ptrp, str, (int)len);
+ yield = string_catn(yield, str, (int)len);
FREETMPS;
LEAVE;
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions that operate on the input queue. */
queue_filename **append = &first;
while (a && b)
- if (Ustrcmp(a->text, b->text) < 0)
+ {
+ int d;
+ if ((d = Ustrncmp(a->text, b->text, 6)) == 0)
+ d = Ustrcmp(a->text + 14, b->text + 14);
+ if (d < 0)
{
*append = a;
append= &a->next;
append= &b->next;
b = b->next;
}
+ }
*append = a ? a : b;
return first;
if (root[j])
{
next = merge_queue_lists(next, root[j]);
- root[j] = (j == LOG2_MAXNODES - 1)? next : NULL;
+ root[j] = j == LOG2_MAXNODES - 1 ? next : NULL;
}
else
{
void
queue_run(uschar *start_id, uschar *stop_id, BOOL recurse)
{
-BOOL force_delivery = queue_run_force || deliver_selectstring != NULL ||
+BOOL force_delivery = f.queue_run_force || deliver_selectstring != NULL ||
deliver_selectstring_sender != NULL;
const pcre *selectstring_regex = NULL;
const pcre *selectstring_regex_sender = NULL;
queue_domains = NULL;
queue_smtp_domains = NULL;
-queue_smtp = queue_2stage;
+f.queue_smtp = f.queue_2stage;
queue_run_pid = getpid();
-queue_running = TRUE;
+f.queue_running = TRUE;
/* Log the true start of a queue run, and fancy options */
uschar extras[8];
uschar *p = extras;
- if (queue_2stage) *p++ = 'q';
- if (queue_run_first_delivery) *p++ = 'i';
- if (queue_run_force) *p++ = 'f';
- if (deliver_force_thaw) *p++ = 'f';
- if (queue_run_local) *p++ = 'l';
+ if (f.queue_2stage) *p++ = 'q';
+ if (f.queue_run_first_delivery) *p++ = 'i';
+ if (f.queue_run_force) *p++ = 'f';
+ if (f.deliver_force_thaw) *p++ = 'f';
+ if (f.queue_run_local) *p++ = 'l';
*p = 0;
p = big_buffer;
- sprintf(CS p, "pid=%d", (int)queue_run_pid);
- while (*p != 0) p++;
+ p += sprintf(CS p, "pid=%d", (int)queue_run_pid);
if (extras[0] != 0)
- {
- sprintf(CS p, " -q%s", extras);
- while (*p != 0) p++;
- }
+ p += sprintf(CS p, " -q%s", extras);
- if (deliver_selectstring != NULL)
- {
- sprintf(CS p, " -R%s %s", deliver_selectstring_regex? "r" : "",
+ if (deliver_selectstring)
+ p += sprintf(CS p, " -R%s %s", f.deliver_selectstring_regex? "r" : "",
deliver_selectstring);
- while (*p != 0) p++;
- }
- if (deliver_selectstring_sender != NULL)
- {
- sprintf(CS p, " -S%s %s", deliver_selectstring_sender_regex? "r" : "",
+ if (deliver_selectstring_sender)
+ p += sprintf(CS p, " -S%s %s", f.deliver_selectstring_sender_regex? "r" : "",
deliver_selectstring_sender);
- while (*p != 0) p++;
- }
log_detail = string_copy(big_buffer);
if (*queue_name)
/* If deliver_selectstring is a regex, compile it. */
-if (deliver_selectstring != NULL && deliver_selectstring_regex)
+if (deliver_selectstring && f.deliver_selectstring_regex)
selectstring_regex = regex_must_compile(deliver_selectstring, TRUE, FALSE);
-if (deliver_selectstring_sender != NULL && deliver_selectstring_sender_regex)
+if (deliver_selectstring_sender && f.deliver_selectstring_sender_regex)
selectstring_regex_sender =
regex_must_compile(deliver_selectstring_sender, TRUE, FALSE);
When the first argument of queue_get_spool_list() is -1 (for queue_run_in_
order), it scans all directories and makes a single message list. */
-for (i = (queue_run_in_order? -1 : 0);
- i <= (queue_run_in_order? -1 : subcount);
+for (i = queue_run_in_order ? -1 : 0;
+ i <= (queue_run_in_order ? -1 : subcount);
i++)
{
- queue_filename *f;
+ queue_filename * fq;
void *reset_point1 = store_get(0);
DEBUG(D_queue_run)
debug_printf("queue running subdirectory '%c'\n", subdirs[i]);
}
- for (f = queue_get_spool_list(i, subdirs, &subcount, !queue_run_in_order);
- f;
- f = f->next)
+ for (fq = queue_get_spool_list(i, subdirs, &subcount, !queue_run_in_order);
+ fq;
+ fq = fq->next)
{
pid_t pid;
int status;
/* Unless deliveries are forced, if deliver_queue_load_max is non-negative,
check that the load average is low enough to permit deliveries. */
- if (!queue_run_force && deliver_queue_load_max >= 0)
+ if (!f.queue_run_force && deliver_queue_load_max >= 0)
if ((load_average = os_getloadavg()) > deliver_queue_load_max)
{
log_write(L_queue_run, LOG_MAIN, "Abandon queue run: %s (load %.2f, max %.2f)",
/* Skip this message unless it's within the ID limits */
- if (stop_id && Ustrncmp(f->text, stop_id, MESSAGE_ID_LENGTH) > 0)
+ if (stop_id && Ustrncmp(fq->text, stop_id, MESSAGE_ID_LENGTH) > 0)
continue;
- if (start_id && Ustrncmp(f->text, start_id, MESSAGE_ID_LENGTH) < 0)
+ if (start_id && Ustrncmp(fq->text, start_id, MESSAGE_ID_LENGTH) < 0)
continue;
/* Check that the message still exists */
- message_subdir[0] = f->dir_uschar;
- if (Ustat(spool_fname(US"input", message_subdir, f->text, US""), &statbuf) < 0)
+ message_subdir[0] = fq->dir_uschar;
+ if (Ustat(spool_fname(US"input", message_subdir, fq->text, US""), &statbuf) < 0)
continue;
/* There are some tests that require the reading of the header file. Ensure
message when many are not going to be delivered. */
if (deliver_selectstring || deliver_selectstring_sender ||
- queue_run_first_delivery)
+ f.queue_run_first_delivery)
{
BOOL wanted = TRUE;
- BOOL orig_dont_deliver = dont_deliver;
+ BOOL orig_dont_deliver = f.dont_deliver;
void *reset_point2 = store_get(0);
/* Restore the original setting of dont_deliver after reading the header,
follow. If the message is chosen for delivery, the header is read again
in the deliver_message() function, in a subprocess. */
- if (spool_read_header(f->text, FALSE, TRUE) != spool_read_OK) continue;
- dont_deliver = orig_dont_deliver;
+ if (spool_read_header(fq->text, FALSE, TRUE) != spool_read_OK) continue;
+ f.dont_deliver = orig_dont_deliver;
/* Now decide if we want to deliver this message. As we have read the
header file, we might as well do the freeze test now, and save forking
another process. */
- if (deliver_freeze && !deliver_force_thaw)
+ if (f.deliver_freeze && !f.deliver_force_thaw)
{
log_write(L_skip_delivery, LOG_MAIN, "Message is frozen");
wanted = FALSE;
/* Check first_delivery in the case when there are no message logs. */
- else if (queue_run_first_delivery && !deliver_firsttime)
+ else if (f.queue_run_first_delivery && !f.deliver_firsttime)
{
- DEBUG(D_queue_run) debug_printf("%s: not first delivery\n", f->text);
+ DEBUG(D_queue_run) debug_printf("%s: not first delivery\n", fq->text);
wanted = FALSE;
}
/* Sender matching */
else if ( deliver_selectstring_sender
- && !(deliver_selectstring_sender_regex
+ && !(f.deliver_selectstring_sender_regex
? (pcre_exec(selectstring_regex_sender, NULL,
CS sender_address, Ustrlen(sender_address), 0, PCRE_EOPT,
NULL, 0) >= 0)
) )
{
DEBUG(D_queue_run) debug_printf("%s: sender address did not match %s\n",
- f->text, deliver_selectstring_sender);
+ fq->text, deliver_selectstring_sender);
wanted = FALSE;
}
for (i = 0; i < recipients_count; i++)
{
uschar *address = recipients_list[i].address;
- if ( (deliver_selectstring_regex
+ if ( (f.deliver_selectstring_regex
? (pcre_exec(selectstring_regex, NULL, CS address,
Ustrlen(address), 0, PCRE_EOPT, NULL, 0) >= 0)
: (strstric(address, deliver_selectstring, FALSE) != NULL)
{
DEBUG(D_queue_run)
debug_printf("%s: no recipient address matched %s\n",
- f->text, deliver_selectstring);
+ fq->text, deliver_selectstring);
wanted = FALSE;
}
}
/* Recover store used when reading the header */
- received_protocol = NULL;
- sender_address = sender_ident = NULL;
- authenticated_id = authenticated_sender = NULL;
+ spool_clear_header_globals();
store_reset(reset_point2);
if (!wanted) continue; /* With next message */
}
/* Now deliver the message; get the id by cutting the -H off the file
name. The return of the process is zero if a delivery was attempted. */
- set_process_info("running queue: %s", f->text);
- f->text[SPOOL_NAME_LENGTH-2] = 0;
+ set_process_info("running queue: %s", fq->text);
+ fq->text[SPOOL_NAME_LENGTH-2] = 0;
if ((pid = fork()) == 0)
{
int rc;
- if (running_in_test_harness) millisleep(100);
+ if (f.running_in_test_harness) millisleep(100);
(void)close(pfd[pipe_read]);
- rc = deliver_message(f->text, force_delivery, FALSE);
+ rc = deliver_message(fq->text, force_delivery, FALSE);
_exit(rc == DELIVER_NOT_ATTEMPTED);
}
if (pid < 0)
then wait for the first level process to terminate. */
(void)close(pfd[pipe_write]);
- set_process_info("running queue: waiting for %s (%d)", f->text, pid);
+ set_process_info("running queue: waiting for %s (%d)", fq->text, pid);
while (wait(&status) != pid);
/* A zero return means a delivery was attempted; turn off the force flag
for any subsequent calls unless queue_force is set. */
- if ((status & 0xffff) == 0) force_delivery = queue_run_force;
+ if ((status & 0xffff) == 0) force_delivery = f.queue_run_force;
/* If the process crashed, tell somebody */
else if ((status & 0x00ff) != 0)
log_write(0, LOG_MAIN|LOG_PANIC,
"queue run: process %d crashed with signal %d while delivering %s",
- (int)pid, status & 0x00ff, f->text);
+ (int)pid, status & 0x00ff, fq->text);
/* Before continuing, wait till the pipe gets closed at the far end. This
tells us that any children created by the delivery to re-use any SMTP
/* If we are in the test harness, and this is not the first of a 2-stage
queue run, update fudged queue times. */
- if (running_in_test_harness && !queue_2stage)
+ if (f.running_in_test_harness && !f.queue_2stage)
{
uschar *fqtnext = Ustrchr(fudged_queue_times, '/');
if (fqtnext != NULL) fudged_queue_times = fqtnext + 1;
}
} /* End loop for list of messages */
+ tree_nonrecipients = NULL;
store_reset(reset_point1); /* Scavenge list of messages */
/* If this was the first time through for random order processing, and
/* If queue_2stage is true, we do it all again, with the 2stage flag
turned off. */
-if (queue_2stage)
+if (f.queue_2stage)
{
- queue_2stage = FALSE;
+ f.queue_2stage = FALSE;
queue_run(start_id, stop_id, TRUE);
}
int subcount;
int now = (int)time(NULL);
void *reset_point;
-queue_filename *f = NULL;
+queue_filename * qf = NULL;
uschar subdirs[64];
/* If given a list of messages, build a chain containing their ids. */
sprintf(CS next->text, "%s-H", list[i]);
next->dir_uschar = '*';
next->next = NULL;
- if (i == 0) f = next; else last->next = next;
+ if (i == 0) qf = next; else last->next = next;
last = next;
}
}
/* Otherwise get a list of the entire queue, in order if necessary. */
else
- f = queue_get_spool_list(
+ qf = queue_get_spool_list(
-1, /* entire queue */
subdirs, /* for holding sub list */
&subcount, /* for subcount */
/* Now scan the chain and print information, resetting store used
each time. */
-for (reset_point = store_get(0); f; f = f->next)
+for (reset_point = store_get(0);
+ qf;
+ spool_clear_header_globals(), store_reset(reset_point), qf = qf->next
+ )
{
int rc, save_errno;
int size = 0;
BOOL env_read;
message_size = 0;
- message_subdir[0] = f->dir_uschar;
- rc = spool_read_header(f->text, FALSE, count <= 0);
- if (rc == spool_read_notopen && errno == ENOENT && count <= 0) goto next;
+ message_subdir[0] = qf->dir_uschar;
+ rc = spool_read_header(qf->text, FALSE, count <= 0);
+ if (rc == spool_read_notopen && errno == ENOENT && count <= 0)
+ continue;
save_errno = errno;
env_read = (rc == spool_read_OK || rc == spool_read_hdrerror);
int ptr;
FILE *jread;
struct stat statbuf;
- uschar * fname = spool_fname(US"input", message_subdir, f->text, US"");
+ uschar * fname = spool_fname(US"input", message_subdir, qf->text, US"");
ptr = Ustrlen(fname)-1;
fname[ptr] = 'D';
if (Ustat(fname, &statbuf) == 0)
size = message_size + statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
- i = (now - received_time)/60; /* minutes on queue */
+ i = (now - received_time.tv_sec)/60; /* minutes on queue */
if (i > 90)
{
i = (i + 30)/60;
}
fprintf(stdout, "%s ", string_format_size(size, big_buffer));
- for (i = 0; i < 16; i++) fputc(f->text[i], stdout);
+ for (i = 0; i < 16; i++) fputc(qf->text[i], stdout);
if (env_read && sender_address)
{
printf(" <%s>", sender_address);
- if (sender_set_untrusted) printf(" (%s)", originator_login);
+ if (f.sender_set_untrusted) printf(" (%s)", originator_login);
}
if (rc != spool_read_OK)
if (save_errno == ERRNO_SPOOLFORMAT)
{
struct stat statbuf;
- uschar * fname = spool_fname(US"input", message_subdir, f->text, US"");
+ uschar * fname = spool_fname(US"input", message_subdir, qf->text, US"");
if (Ustat(fname, &statbuf) == 0)
printf("*** spool format error: size=" OFF_T_FMT " ***",
if (rc != spool_read_hdrerror)
{
printf("\n\n");
- goto next;
+ continue;
}
}
- if (deliver_freeze) printf(" *** frozen ***");
+ if (f.deliver_freeze) printf(" *** frozen ***");
printf("\n");
tree_node *delivered =
tree_search(tree_nonrecipients, recipients_list[i].address);
if (!delivered || option != 1)
- printf(" %s %s\n", (delivered != NULL)? "D":" ",
- recipients_list[i].address);
+ printf(" %s %s\n",
+ delivered ? "D" : " ", recipients_list[i].address);
if (delivered) delivered->data.val = TRUE;
}
if (option == 2 && tree_nonrecipients)
queue_list_extras(tree_nonrecipients);
printf("\n");
}
-
-next:
- received_protocol = NULL;
- sender_fullhost = sender_helo_name =
- sender_rcvhost = sender_host_address = sender_address = sender_ident = NULL;
- sender_host_authenticated = authenticated_sender = authenticated_id = NULL;
- interface_address = NULL;
- acl_var_m = NULL;
-
- store_reset(reset_point);
}
}
int fd, i, rc;
uschar *subdirectory, *suffix;
- if (!admin_user)
+ if (!f.admin_user)
{
printf("Permission denied\n");
return FALSE;
{
yield = FALSE;
printf("Spool data file for %s does not exist\n", id);
- if (action != MSG_REMOVE || !admin_user) return FALSE;
+ if (action != MSG_REMOVE || !f.admin_user) return FALSE;
printf("Continuing, to ensure all files removed\n");
}
else
printf("Spool read error for %s: %s\n", spoolname, strerror(errno));
else
printf("Spool format error for %s\n", spoolname);
- if (action != MSG_REMOVE || !admin_user)
+ if (action != MSG_REMOVE || !f.admin_user)
{
(void)close(deliver_datafile);
deliver_datafile = -1;
mess about, but the original sender is permitted to remove a message. That's
why we leave this check until after the headers are read. */
-if (!admin_user && (action != MSG_REMOVE || real_uid != originator_uid))
+if (!f.admin_user && (action != MSG_REMOVE || real_uid != originator_uid))
{
printf("Permission denied\n");
(void)close(deliver_datafile);
switch(action)
{
case MSG_SHOW_COPY:
- deliver_in_buffer = store_malloc(DELIVER_IN_BUFFER_SIZE);
- deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
- transport_write_message(1, NULL, 0);
- break;
+ {
+ transport_ctx tctx = {{0}};
+ deliver_in_buffer = store_malloc(DELIVER_IN_BUFFER_SIZE);
+ deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
+ tctx.u.fd = 1;
+ (void) transport_write_message(&tctx, 0);
+ break;
+ }
case MSG_FREEZE:
- if (deliver_freeze)
+ if (f.deliver_freeze)
{
yield = FALSE;
printf("is already frozen\n");
}
else
{
- deliver_freeze = TRUE;
- deliver_manual_thaw = FALSE;
+ f.deliver_freeze = TRUE;
+ f.deliver_manual_thaw = FALSE;
deliver_frozen_at = time(NULL);
if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
{
case MSG_THAW:
- if (!deliver_freeze)
+ if (!f.deliver_freeze)
{
yield = FALSE;
printf("is not frozen\n");
}
else
{
- deliver_freeze = FALSE;
- deliver_manual_thaw = TRUE;
+ f.deliver_freeze = FALSE;
+ f.deliver_manual_thaw = TRUE;
if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
{
printf("is no longer frozen\n");
else printf("has been removed or did not exist\n");
if (removed)
{
+#ifndef DISABLE_EVENT
+ for (i = 0; i < recipients_count; i++)
+ {
+ tree_node *delivered =
+ tree_search(tree_nonrecipients, recipients_list[i].address);
+ if (!delivered)
+ {
+ uschar * save_local = deliver_localpart;
+ const uschar * save_domain = deliver_domain;
+ uschar * addr = recipients_list[i].address, * errmsg = NULL;
+ int start, end, dom;
+
+ if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "failed to parse address '%.100s'\n: %s", addr, errmsg);
+ else
+ {
+ deliver_localpart =
+ string_copyn(addr+start, dom ? (dom-1) - start : end - start);
+ deliver_domain = dom
+ ? CUS string_copyn(addr+dom, end - dom) : CUS"";
+
+ event_raise(event_action, US"msg:fail:internal",
+ string_sprintf("message removed by %s", username));
+
+ deliver_localpart = save_local;
+ deliver_domain = save_domain;
+ }
+ }
+ }
+ (void) event_raise(event_action, US"msg:complete", NULL);
+#endif
log_write(0, LOG_MAIN, "removed by %s", username);
log_write(0, LOG_MAIN, "Completed");
}
case MSG_MARK_ALL_DELIVERED:
for (i = 0; i < recipients_count; i++)
- {
tree_add_nonrecipient(recipients_list[i].address);
- }
+
if (spool_write_header(id, SW_MODIFYING, &errmsg) >= 0)
{
printf("has been modified\n");
void
queue_check_only(void)
{
-BOOL *set;
int sep = 0;
struct stat statbuf;
const uschar *s;
-uschar *ss, *name;
+uschar *ss;
uschar buffer[1024];
if (queue_only_file == NULL) return;
{
if (Ustrncmp(ss, "smtp", 4) == 0)
{
- name = US"queue_smtp";
- set = &queue_smtp;
ss += 4;
+ if (Ustat(ss, &statbuf) == 0)
+ {
+ f.queue_smtp = TRUE;
+ DEBUG(D_receive) debug_printf("queue_smtp set because %s exists\n", ss);
+ }
}
else
{
- name = US"queue_only";
- set = &queue_only;
- }
-
- if (Ustat(ss, &statbuf) == 0)
- {
- *set = TRUE;
- DEBUG(D_receive) debug_printf("%s set because %s exists\n", name, ss);
+ if (Ustat(ss, &statbuf) == 0)
+ {
+ queue_only = TRUE;
+ DEBUG(D_receive) debug_printf("queue_only set because %s exists\n", ss);
+ }
}
}
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* This module contains code for extracting addresses from a forwarding list
slash = Ustrrchr(big_buffer, '/');
Ustrcpy(slash+1, ".");
- alarm(30);
+ ALARM(30);
rc = Ustat(big_buffer, &statbuf);
if (rc != 0 && errno == EACCES && !sigalrm_seen)
{
rc = Ustat(big_buffer, &statbuf);
}
saved_errno = errno;
- alarm(0);
+ ALARM_CLR(0);
DEBUG(D_route) debug_printf("stat(%s)=%d\n", big_buffer, rc);
}
}
else data = rdata->string;
-*filtertype = system_filtering? FILTER_EXIM : rda_is_filter(data);
+*filtertype = f.system_filtering ? FILTER_EXIM : rda_is_filter(data);
/* Filter interpretation is done by a general function that is also called from
the filter testing option (-bf). There are two versions: one for Exim filtering
/* This function is passed a forward list string (unexpanded) or the name of a
file (unexpanded) whose contents are the forwarding list. The list may in fact
be a filter program if it starts with "#Exim filter" or "#Sieve filter". Other
-types of filter, with different inital tag strings, may be introduced in due
+types of filter, with different initial tag strings, may be introduced in due
course.
The job of the function is to process the forwarding list or filter. It is
data = expand_string(rdata->string);
if (data == NULL)
{
- if (expand_string_forcedfail) return FF_NOTDELIVERED;
+ if (f.expand_string_forcedfail) return FF_NOTDELIVERED;
*error = string_sprintf("failed to expand \"%s\": %s", rdata->string,
expand_string_message);
return FF_ERROR;
original header lines that were removed, and then any header lines that were
added but not subsequently removed. */
- if (system_filtering)
+ if (f.system_filtering)
{
int i = 0;
header_line *h;
yield == FF_FAIL || yield == FF_FREEZE)
{
address_item *addr;
- for (addr = *generated; addr != NULL; addr = addr->next)
+ for (addr = *generated; addr; addr = addr->next)
{
int reply_options = 0;
+ int ig_err = addr->prop.ignore_error ? 1 : 0;
if ( rda_write_string(fd, addr->address) != 0
- || write(fd, &(addr->mode), sizeof(addr->mode))
- != sizeof(addr->mode)
- || write(fd, &(addr->flags), sizeof(addr->flags))
- != sizeof(addr->flags)
+ || write(fd, &addr->mode, sizeof(addr->mode)) != sizeof(addr->mode)
+ || write(fd, &addr->flags, sizeof(addr->flags)) != sizeof(addr->flags)
|| rda_write_string(fd, addr->prop.errors_address) != 0
+ || write(fd, &ig_err, sizeof(ig_err)) != sizeof(ig_err)
)
goto bad;
- if (addr->pipe_expandn != NULL)
+ if (addr->pipe_expandn)
{
uschar **pp;
- for (pp = addr->pipe_expandn; *pp != NULL; pp++)
+ for (pp = addr->pipe_expandn; *pp; pp++)
if (rda_write_string(fd, *pp) != 0)
goto bad;
}
if (rda_write_string(fd, NULL) != 0)
goto bad;
- if (addr->reply == NULL)
+ if (!addr->reply)
{
if (write(fd, &reply_options, sizeof(int)) != sizeof(int)) /* 0 means no reply */
goto bad;
/* If this is a system filter, read the identify of any original header lines
that were removed, and then read data for any new ones that were added. */
-if (system_filtering)
+if (f.system_filtering)
{
int hn = 0;
header_line *h = header_list;
/* Next comes the mode and the flags fields */
- if (read(fd, &(addr->mode), sizeof(addr->mode)) != sizeof(addr->mode) ||
- read(fd, &(addr->flags), sizeof(addr->flags)) != sizeof(addr->flags) ||
- !rda_read_string(fd, &(addr->prop.errors_address))) goto DISASTER;
+ if ( read(fd, &addr->mode, sizeof(addr->mode)) != sizeof(addr->mode)
+ || read(fd, &addr->flags, sizeof(addr->flags)) != sizeof(addr->flags)
+ || !rda_read_string(fd, &addr->prop.errors_address)
+ || read(fd, &i, sizeof(i)) != sizeof(i)
+ )
+ goto DISASTER;
+ addr->prop.ignore_error = (i != 0);
/* Next comes a possible setting for $thisaddress and any numerical
variables for pipe expansion, terminated by a NULL string. The maximum
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for reading the configuration file, and for displaying
#include "exim.h"
-extern char **environ;
-
-static void fn_smtp_receive_timeout(const uschar * name, const uschar * str);
-static void save_config_line(const uschar* line);
-static void save_config_position(const uschar *file, int line);
-static void print_config(BOOL admin, BOOL terse);
-static void readconf_options_auths(void);
-
-
-#define CSTATE_STACK_SIZE 10
-
-const uschar *config_directory = NULL;
-
-
-/* Structure for chain (stack) of .included files */
-
-typedef struct config_file_item {
- struct config_file_item *next;
- const uschar *filename;
- const uschar *directory;
- FILE *file;
- int lineno;
-} config_file_item;
-
-/* Structure for chain of configuration lines (-bP config) */
-
-typedef struct config_line_item {
- struct config_line_item *next;
- uschar *line;
-} config_line_item;
-
-static config_line_item* config_lines;
-
-/* Structure of table of conditional words and their state transitions */
-
-typedef struct cond_item {
- uschar *name;
- int namelen;
- int action1;
- int action2;
- int pushpop;
-} cond_item;
-
-/* Structure of table of syslog facility names and values */
-
-typedef struct syslog_fac_item {
- uschar *name;
- int value;
-} syslog_fac_item;
-
-/* constants */
-static const char * const hidden = "<value not displayable>";
-
-/* Static variables */
-
-static config_file_item *config_file_stack = NULL; /* For includes */
-
-static uschar *syslog_facility_str = NULL;
-static uschar next_section[24];
-static uschar time_buffer[24];
-
-/* State variables for conditional loading (.ifdef / .else / .endif) */
-
-static int cstate = 0;
-static int cstate_stack_ptr = -1;
-static int cstate_stack[CSTATE_STACK_SIZE];
-
-/* Table of state transitions for handling conditional inclusions. There are
-four possible state transitions:
-
- .ifdef true
- .ifdef false
- .elifdef true (or .else)
- .elifdef false
-
-.endif just causes the previous cstate to be popped off the stack */
-
-static int next_cstate[3][4] =
- {
- /* State 0: reading from file, or reading until next .else or .endif */
- { 0, 1, 2, 2 },
- /* State 1: condition failed, skipping until next .else or .endif */
- { 2, 2, 0, 1 },
- /* State 2: skipping until .endif */
- { 2, 2, 2, 2 },
- };
-
-/* Table of conditionals and the states to set. For each name, there are four
-values: the length of the name (to save computing it each time), the state to
-set if a macro was found in the line, the state to set if a macro was not found
-in the line, and a stack manipulation setting which is:
-
- -1 pull state value off the stack
- 0 don't alter the stack
- +1 push value onto stack, before setting new state
-*/
-
-static cond_item cond_list[] = {
- { US"ifdef", 5, 0, 1, 1 },
- { US"ifndef", 6, 1, 0, 1 },
- { US"elifdef", 7, 2, 3, 0 },
- { US"elifndef", 8, 3, 2, 0 },
- { US"else", 4, 2, 2, 0 },
- { US"endif", 5, 0, 0, -1 }
-};
-
-static int cond_list_size = sizeof(cond_list)/sizeof(cond_item);
-
-/* Table of syslog facility names and their values */
-
-static syslog_fac_item syslog_list[] = {
- { US"mail", LOG_MAIL },
- { US"user", LOG_USER },
- { US"news", LOG_NEWS },
- { US"uucp", LOG_UUCP },
- { US"local0", LOG_LOCAL0 },
- { US"local1", LOG_LOCAL1 },
- { US"local2", LOG_LOCAL2 },
- { US"local3", LOG_LOCAL3 },
- { US"local4", LOG_LOCAL4 },
- { US"local5", LOG_LOCAL5 },
- { US"local6", LOG_LOCAL6 },
- { US"local7", LOG_LOCAL7 },
- { US"daemon", LOG_DAEMON }
-};
-
-static int syslog_list_size = sizeof(syslog_list)/sizeof(syslog_fac_item);
+#ifdef MACRO_PREDEF
+# include "macro_predef.h"
+#endif
+#define READCONF_DEBUG if (FALSE) /* Change to TRUE to enable */
+static uschar * syslog_facility_str;
+static void fn_smtp_receive_timeout(const uschar *, const uschar *);
/*************************************************
* Main configuration options *
{ "check_spool_inodes", opt_int, &check_spool_inodes },
{ "check_spool_space", opt_Kint, &check_spool_space },
{ "chunking_advertise_hosts", opt_stringptr, &chunking_advertise_hosts },
+ { "commandline_checks_require_admin", opt_bool,&commandline_checks_require_admin },
{ "daemon_smtp_port", opt_stringptr|opt_hidden, &daemon_smtp_port },
{ "daemon_smtp_ports", opt_stringptr, &daemon_smtp_port },
{ "daemon_startup_retries", opt_int, &daemon_startup_retries },
#endif
{ "dns_again_means_nonexist", opt_stringptr, &dns_again_means_nonexist },
{ "dns_check_names_pattern", opt_stringptr, &check_dns_names_pattern },
+ { "dns_cname_loops", opt_int, &dns_cname_loops },
{ "dns_csa_search_limit", opt_int, &dns_csa_search_limit },
{ "dns_csa_use_reverse", opt_bool, &dns_csa_use_reverse },
{ "dns_dnssec_ok", opt_int, &dns_dnssec_ok },
{ "local_from_prefix", opt_stringptr, &local_from_prefix },
{ "local_from_suffix", opt_stringptr, &local_from_suffix },
{ "local_interfaces", opt_stringptr, &local_interfaces },
+#ifdef HAVE_LOCAL_SCAN
{ "local_scan_timeout", opt_time, &local_scan_timeout },
+#endif
{ "local_sender_retain", opt_bool, &local_sender_retain },
{ "localhost_number", opt_stringptr, &host_number_string },
{ "log_file_path", opt_stringptr, &log_file_path },
#endif
{ "pid_file_path", opt_stringptr, &pid_file_path },
{ "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts },
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ { "pipelining_connect_advertise_hosts", opt_stringptr,
+ &pipe_connect_advertise_hosts },
+#endif
#ifndef DISABLE_PRDR
{ "prdr_enable", opt_bool, &prdr_enable },
#endif
#ifdef WITH_CONTENT_SCAN
{ "spamd_address", opt_stringptr, &spamd_address },
#endif
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
{ "spf_guess", opt_stringptr, &spf_guess },
#endif
{ "split_spool_directory", opt_bool, &split_spool_directory },
{ "spool_directory", opt_stringptr, &spool_directory },
+ { "spool_wireformat", opt_bool, &spool_wireformat },
#ifdef LOOKUP_SQLITE
{ "sqlite_lock_timeout", opt_int, &sqlite_lock_timeout },
#endif
{ "timezone", opt_stringptr, &timezone_string },
{ "tls_advertise_hosts", opt_stringptr, &tls_advertise_hosts },
#ifdef SUPPORT_TLS
+# ifdef EXPERIMENTAL_REQUIRETLS
+ { "tls_advertise_requiretls", opt_stringptr, &tls_advertise_requiretls },
+# endif
{ "tls_certificate", opt_stringptr, &tls_certificate },
{ "tls_crl", opt_stringptr, &tls_crl },
{ "tls_dh_max_bits", opt_int, &tls_dh_max_bits },
{ "write_rejectlog", opt_bool, &write_rejectlog }
};
+#ifndef MACRO_PREDEF
static int optionlist_config_size = nelem(optionlist_config);
+#endif
+
+
+#ifdef MACRO_PREDEF
+
+static void fn_smtp_receive_timeout(const uschar * name, const uschar * str) {/*Dummy*/}
+
+void
+options_main(void)
+{
+options_from_list(optionlist_config, nelem(optionlist_config), US"MAIN", NULL);
+}
+
+void
+options_auths(void)
+{
+struct auth_info * ai;
+uschar buf[64];
+
+options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL);
+
+for (ai = auths_available; ai->driver_name[0]; ai++)
+ {
+ spf(buf, sizeof(buf), US"_DRIVER_AUTHENTICATOR_%T", ai->driver_name);
+ builtin_macro_create(buf);
+ options_from_list(ai->options, (unsigned)*ai->options_count, US"AUTHENTICATOR", ai->driver_name);
+ }
+}
+
+void
+options_logging(void)
+{
+bit_table * bp;
+uschar buf[64];
+
+for (bp = log_options; bp < log_options + log_options_count; bp++)
+ {
+ spf(buf, sizeof(buf), US"_LOG_%T", bp->name);
+ builtin_macro_create(buf);
+ }
+}
+
+
+#else /*!MACRO_PREDEF*/
+
+extern char **environ;
+
+static void save_config_line(const uschar* line);
+static void save_config_position(const uschar *file, int line);
+static void print_config(BOOL admin, BOOL terse);
+
+
+#define CSTATE_STACK_SIZE 10
+
+const uschar *config_directory = NULL;
+
+
+/* Structure for chain (stack) of .included files */
+
+typedef struct config_file_item {
+ struct config_file_item *next;
+ const uschar *filename;
+ const uschar *directory;
+ FILE *file;
+ int lineno;
+} config_file_item;
+
+/* Structure for chain of configuration lines (-bP config) */
+
+typedef struct config_line_item {
+ struct config_line_item *next;
+ uschar *line;
+} config_line_item;
+
+static config_line_item* config_lines;
+
+/* Structure of table of conditional words and their state transitions */
+
+typedef struct cond_item {
+ uschar *name;
+ int namelen;
+ int action1;
+ int action2;
+ int pushpop;
+} cond_item;
+
+/* Structure of table of syslog facility names and values */
+
+typedef struct syslog_fac_item {
+ uschar *name;
+ int value;
+} syslog_fac_item;
+
+/* constants */
+static const char * const hidden = "<value not displayable>";
+
+/* Static variables */
+
+static config_file_item *config_file_stack = NULL; /* For includes */
+
+static uschar *syslog_facility_str = NULL;
+static uschar next_section[24];
+static uschar time_buffer[24];
+
+/* State variables for conditional loading (.ifdef / .else / .endif) */
+
+static int cstate = 0;
+static int cstate_stack_ptr = -1;
+static int cstate_stack[CSTATE_STACK_SIZE];
+
+/* Table of state transitions for handling conditional inclusions. There are
+four possible state transitions:
+
+ .ifdef true
+ .ifdef false
+ .elifdef true (or .else)
+ .elifdef false
+
+.endif just causes the previous cstate to be popped off the stack */
+
+static int next_cstate[3][4] =
+ {
+ /* State 0: reading from file, or reading until next .else or .endif */
+ { 0, 1, 2, 2 },
+ /* State 1: condition failed, skipping until next .else or .endif */
+ { 2, 2, 0, 1 },
+ /* State 2: skipping until .endif */
+ { 2, 2, 2, 2 },
+ };
+
+/* Table of conditionals and the states to set. For each name, there are four
+values: the length of the name (to save computing it each time), the state to
+set if a macro was found in the line, the state to set if a macro was not found
+in the line, and a stack manipulation setting which is:
+
+ -1 pull state value off the stack
+ 0 don't alter the stack
+ +1 push value onto stack, before setting new state
+*/
+
+static cond_item cond_list[] = {
+ { US"ifdef", 5, 0, 1, 1 },
+ { US"ifndef", 6, 1, 0, 1 },
+ { US"elifdef", 7, 2, 3, 0 },
+ { US"elifndef", 8, 3, 2, 0 },
+ { US"else", 4, 2, 2, 0 },
+ { US"endif", 5, 0, 0, -1 }
+};
+
+static int cond_list_size = sizeof(cond_list)/sizeof(cond_item);
+
+/* Table of syslog facility names and their values */
+
+static syslog_fac_item syslog_list[] = {
+ { US"mail", LOG_MAIL },
+ { US"user", LOG_USER },
+ { US"news", LOG_NEWS },
+ { US"uucp", LOG_UUCP },
+ { US"local0", LOG_LOCAL0 },
+ { US"local1", LOG_LOCAL1 },
+ { US"local2", LOG_LOCAL2 },
+ { US"local3", LOG_LOCAL3 },
+ { US"local4", LOG_LOCAL4 },
+ { US"local5", LOG_LOCAL5 },
+ { US"local6", LOG_LOCAL6 },
+ { US"local7", LOG_LOCAL7 },
+ { US"daemon", LOG_DAEMON }
+};
+
+static int syslog_list_size = sizeof(syslog_list)/sizeof(syslog_fac_item);
+
for (i = 0; i < *ri->options_count; i++)
{
if ((ri->options[i].type & opt_mask) != opt_stringptr) continue;
- if (p == (char *)(r->options_block) + (long int)(ri->options[i].value))
+ if (p == CS (r->options_block) + (long int)(ri->options[i].value))
return US ri->options[i].name;
}
}
optionlist * op = &ti->options[i];
if ((op->type & opt_mask) != opt_stringptr) continue;
if (p == ( op->type & opt_public
- ? (char *)t
- : (char *)t->options_block
+ ? CS t
+ : CS t->options_block
)
+ (long int)op->value)
return US op->name;
* Deal with an assignment to a macro *
*************************************************/
-/* We have a new definition. The macro_item structure includes a final vector
-called "name" which is one byte long. Thus, adding "namelen" gives us enough
-room to store the "name" string.
-If a builtin macro we place at head of list, else tail. This lets us lazy-create
-builtins. */
+/* We have a new definition; append to the list.
+
+Args:
+ name Name of the macro; will be copied
+ val Expansion result for the macro; will be copied
+*/
macro_item *
-macro_create(const uschar * name, const uschar * val,
- BOOL command_line, BOOL builtin)
+macro_create(const uschar * name, const uschar * val, BOOL command_line)
{
-unsigned namelen = Ustrlen(name);
-macro_item * m = store_get(sizeof(macro_item) + namelen);
+macro_item * m = store_get(sizeof(macro_item));
-/* fprintf(stderr, "%s: '%s' '%s'\n", __FUNCTION__, name, val) */
-if (!macros)
- {
- macros = m;
- mlast = m;
- m->next = NULL;
- }
-else if (builtin)
- {
- m->next = macros;
- macros = m;
- }
-else
- {
- mlast->next = m;
- mlast = m;
- m->next = NULL;
- }
+READCONF_DEBUG fprintf(stderr, "%s: '%s' '%s'\n", __FUNCTION__, name, val);
+m->next = NULL;
m->command_line = command_line;
-m->namelen = namelen;
+m->namelen = Ustrlen(name);
+m->replen = Ustrlen(val);
+m->name = string_copy(name);
m->replacement = string_copy(val);
-Ustrcpy(m->name, name);
+if (mlast)
+ mlast->next = m;
+else
+ macros = m;
+mlast = m;
+if (!macros_user)
+ macros_user = m;
return m;
}
Arguments:
s points to the start of the logical line
-Returns: nothing
+Returns: FALSE iff fatal error
*/
-static void
-read_macro_assignment(uschar *s)
+BOOL
+macro_read_assignment(uschar *s)
{
uschar name[64];
int namelen = 0;
while (isalnum(*s) || *s == '_')
{
if (namelen >= sizeof(name) - 1)
- log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+ {
+ log_write(0, LOG_PANIC|LOG_CONFIG_IN,
"macro name too long (maximum is " SIZE_T_FMT " characters)", sizeof(name) - 1);
+ return FALSE;
+ }
name[namelen++] = *s++;
}
name[namelen] = 0;
while (isspace(*s)) s++;
if (*s++ != '=')
- log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "malformed macro definition");
+ {
+ log_write(0, LOG_PANIC|LOG_CONFIG_IN, "malformed macro definition");
+ return FALSE;
+ }
if (*s == '=')
{
if (Ustrcmp(m->name, name) == 0)
{
if (!m->command_line && !redef)
- log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "macro \"%s\" is already "
- "defined (use \"==\" if you want to redefine it", name);
+ {
+ log_write(0, LOG_CONFIG|LOG_PANIC, "macro \"%s\" is already "
+ "defined (use \"==\" if you want to redefine it)", name);
+ return FALSE;
+ }
break;
}
if (m->namelen < namelen && Ustrstr(name, m->name) != NULL)
- log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
+ {
+ log_write(0, LOG_CONFIG|LOG_PANIC, "\"%s\" cannot be defined as "
"a macro because previously defined macro \"%s\" is a substring",
name, m->name);
+ return FALSE;
+ }
/* We cannot have this test, because it is documented that a substring
macro is permitted (there is even an example).
/* Check for an overriding command-line definition. */
-if (m && m->command_line) return;
+if (m && m->command_line) return TRUE;
/* Redefinition must refer to an existing macro. */
if (redef)
if (m)
+ {
+ m->replen = Ustrlen(s);
m->replacement = string_copy(s);
+ }
else
- log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "can't redefine an undefined macro "
+ {
+ log_write(0, LOG_CONFIG|LOG_PANIC, "can't redefine an undefined macro "
"\"%s\"", name);
+ return FALSE;
+ }
/* We have a new definition. */
else
- (void) macro_create(name, s, FALSE, FALSE);
+ (void) macro_create(name, s, FALSE);
+return TRUE;
}
-/*************************************************/
-/* Create compile-time feature macros */
-static void
-readconf_features(void)
+/* Process line for macros. The line is in big_buffer starting at offset len.
+Expand big_buffer if needed. Handle definitions of new macros, and
+macro expansions, rewriting the line in the buffer.
+
+Arguments:
+ len Offset in buffer of start of line
+ newlen Pointer to offset of end of line, updated on return
+ macro_found Pointer to return that a macro was expanded
+
+Return: pointer to first nonblank char in line
+*/
+
+uschar *
+macros_expand(int len, int * newlen, BOOL * macro_found)
{
-/* Probably we could work out a static initialiser for wherever
-macros are stored, but this will do for now. Some names are awkward
-due to conflicts with other common macros. */
+uschar * ss = big_buffer + len;
+uschar * s;
+macro_item * m;
-#ifdef SUPPORT_CRYPTEQ
- macro_create(US"_HAVE_CRYPTEQ", US"y", FALSE, TRUE);
-#endif
-#if HAVE_ICONV
- macro_create(US"_HAVE_ICONV", US"y", FALSE, TRUE);
-#endif
-#if HAVE_IPV6
- macro_create(US"_HAVE_IPV6", US"y", FALSE, TRUE);
-#endif
-#ifdef HAVE_SETCLASSRESOURCES
- macro_create(US"_HAVE_SETCLASSRESOURCES", US"y", FALSE, TRUE);
-#endif
-#ifdef SUPPORT_PAM
- macro_create(US"_HAVE_PAM", US"y", FALSE, TRUE);
-#endif
-#ifdef EXIM_PERL
- macro_create(US"_HAVE_PERL", US"y", FALSE, TRUE);
-#endif
-#ifdef EXPAND_DLFUNC
- macro_create(US"_HAVE_DLFUNC", US"y", FALSE, TRUE);
-#endif
-#ifdef USE_TCP_WRAPPERS
- macro_create(US"_HAVE_TCPWRAPPERS", US"y", FALSE, TRUE);
-#endif
-#ifdef SUPPORT_TLS
- macro_create(US"_HAVE_TLS", US"y", FALSE, TRUE);
-# ifdef USE_GNUTLS
- macro_create(US"_HAVE_GNUTLS", US"y", FALSE, TRUE);
-# else
- macro_create(US"_HAVE_OPENSSL", US"y", FALSE, TRUE);
-# endif
-#endif
-#ifdef SUPPORT_TRANSLATE_IP_ADDRESS
- macro_create(US"_HAVE_TRANSLATE_IP_ADDRESS", US"y", FALSE, TRUE);
-#endif
-#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
- macro_create(US"_HAVE_MOVE_FROZEN_MESSAGES", US"y", FALSE, TRUE);
-#endif
-#ifdef WITH_CONTENT_SCAN
- macro_create(US"_HAVE_CONTENT_SCANNING", US"y", FALSE, TRUE);
-#endif
-#ifndef DISABLE_DKIM
- macro_create(US"_HAVE_DKIM", US"y", FALSE, TRUE);
-#endif
-#ifndef DISABLE_DNSSEC
- macro_create(US"_HAVE_DNSSEC", US"y", FALSE, TRUE);
-#endif
-#ifndef DISABLE_EVENT
- macro_create(US"_HAVE_EVENT", US"y", FALSE, TRUE);
-#endif
-#ifdef SUPPORT_I18N
- macro_create(US"_HAVE_I18N", US"y", FALSE, TRUE);
-#endif
-#ifndef DISABLE_OCSP
- macro_create(US"_HAVE_OCSP", US"y", FALSE, TRUE);
-#endif
-#ifndef DISABLE_PRDR
- macro_create(US"_HAVE_PRDR", US"y", FALSE, TRUE);
-#endif
-#ifdef SUPPORT_PROXY
- macro_create(US"_HAVE_PROXY", US"y", FALSE, TRUE);
-#endif
-#ifdef SUPPORT_SOCKS
- macro_create(US"_HAVE_SOCKS", US"y", FALSE, TRUE);
-#endif
-#ifdef TCP_FASTOPEN
- macro_create(US"_HAVE_TCP_FASTOPEN", US"y", FALSE, TRUE);
-#endif
-#ifdef EXPERIMENTAL_LMDB
- macro_create(US"_HAVE_LMDB", US"y", FALSE, TRUE);
-#endif
-#ifdef EXPERIMENTAL_SPF
- macro_create(US"_HAVE_SPF", US"y", FALSE, TRUE);
-#endif
-#ifdef EXPERIMENTAL_SRS
- macro_create(US"_HAVE_SRS", US"y", FALSE, TRUE);
-#endif
-#ifdef EXPERIMENTAL_BRIGHTMAIL
- macro_create(US"_HAVE_BRIGHTMAIL", US"y", FALSE, TRUE);
-#endif
-#ifdef EXPERIMENTAL_DANE
- macro_create(US"_HAVE_DANE", US"y", FALSE, TRUE);
-#endif
-#ifdef EXPERIMENTAL_DCC
- macro_create(US"_HAVE_DCC", US"y", FALSE, TRUE);
-#endif
-#ifdef EXPERIMENTAL_DMARC
- macro_create(US"_HAVE_DMARC", US"y", FALSE, TRUE);
-#endif
-#ifdef EXPERIMENTAL_DSN_INFO
- macro_create(US"_HAVE_DSN_INFO", US"y", FALSE, TRUE);
-#endif
+/* Find the true start of the physical line - leading spaces are always
+ignored. */
-#ifdef LOOKUP_LSEARCH
- macro_create(US"_HAVE_LOOKUP_LSEARCH", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_CDB
- macro_create(US"_HAVE_LOOKUP_CDB", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_DBM
- macro_create(US"_HAVE_LOOKUP_DBM", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_DNSDB
- macro_create(US"_HAVE_LOOKUP_DNSDB", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_DSEARCH
- macro_create(US"_HAVE_LOOKUP_DSEARCH", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_IBASE
- macro_create(US"_HAVE_LOOKUP_IBASE", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_LDAP
- macro_create(US"_HAVE_LOOKUP_LDAP", US"y", FALSE, TRUE);
-#endif
-#ifdef EXPERIMENTAL_LMDB
- macro_create(US"_HAVE_LOOKUP_LMDB", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_MYSQL
- macro_create(US"_HAVE_LOOKUP_MYSQL", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_NIS
- macro_create(US"_HAVE_LOOKUP_NIS", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_NISPLUS
- macro_create(US"_HAVE_LOOKUP_NISPLUS", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_ORACLE
- macro_create(US"_HAVE_LOOKUP_ORACLE", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_PASSWD
- macro_create(US"_HAVE_LOOKUP_PASSWD", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_PGSQL
- macro_create(US"_HAVE_LOOKUP_PGSQL", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_REDIS
- macro_create(US"_HAVE_LOOKUP_REDIS", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_SQLITE
- macro_create(US"_HAVE_LOOKUP_SQLITE", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_TESTDB
- macro_create(US"_HAVE_LOOKUP_TESTDB", US"y", FALSE, TRUE);
-#endif
-#ifdef LOOKUP_WHOSON
- macro_create(US"_HAVE_LOOKUP_WHOSON", US"y", FALSE, TRUE);
-#endif
+while (isspace(*ss)) ss++;
+
+/* Process the physical line for macros. If this is the start of the logical
+line, skip over initial text at the start of the line if it starts with an
+upper case character followed by a sequence of name characters and an equals
+sign, because that is the definition of a new macro, and we don't do
+replacement therein. */
+
+s = ss;
+if (len == 0 && isupper(*s))
+ {
+ while (isalnum(*s) || *s == '_') s++;
+ while (isspace(*s)) s++;
+ if (*s != '=') s = ss; /* Not a macro definition */
+ }
+
+/* Skip leading chars which cannot start a macro name, to avoid multiple
+pointless rescans in Ustrstr calls. */
+
+while (*s && !isupper(*s) && !(*s == '_' && isupper(s[1]))) s++;
+
+/* For each defined macro, scan the line (from after XXX= if present),
+replacing all occurrences of the macro. */
+
+*macro_found = FALSE;
+if (*s) for (m = *s == '_' ? macros : macros_user; m; m = m->next)
+ {
+ uschar * p, *pp;
+ uschar * t;
+
+ while (*s && !isupper(*s) && !(*s == '_' && isupper(s[1]))) s++;
+ if (!*s) break;
+
+ t = s;
+ while ((p = Ustrstr(t, m->name)) != NULL)
+ {
+ int moveby;
-#ifdef TRANSPORT_APPENDFILE
-# ifdef SUPPORT_MAILDIR
- macro_create(US"_HAVE_TRANSPORT_APPEND_MAILDIR", US"y", FALSE, TRUE);
-# endif
-# ifdef SUPPORT_MAILSTORE
- macro_create(US"_HAVE_TRANSPORT_APPEND_MAILSTORE", US"y", FALSE, TRUE);
-# endif
-# ifdef SUPPORT_MBX
- macro_create(US"_HAVE_TRANSPORT_APPEND_MBX", US"y", FALSE, TRUE);
-# endif
-#endif
-}
+ READCONF_DEBUG fprintf(stderr, "%s: matched '%s' in '%.*s'\n", __FUNCTION__,
+ m->name, (int) Ustrlen(ss)-1, ss);
+ /* Expand the buffer if necessary */
+ while (*newlen - m->namelen + m->replen + 1 > big_buffer_size)
+ {
+ int newsize = big_buffer_size + BIG_BUFFER_SIZE;
+ uschar *newbuffer = store_malloc(newsize);
+ memcpy(newbuffer, big_buffer, *newlen + 1);
+ p = newbuffer + (p - big_buffer);
+ s = newbuffer + (s - big_buffer);
+ ss = newbuffer + (ss - big_buffer);
+ t = newbuffer + (t - big_buffer);
+ big_buffer_size = newsize;
+ store_free(big_buffer);
+ big_buffer = newbuffer;
+ }
-void
-readconf_options_from_list(optionlist * opts, unsigned nopt, const uschar * section, uschar * group)
-{
-int i;
-const uschar * s;
-
-/* The 'previously-defined-substring' rule for macros in config file
-lines is done so for these builtin macros: we know that the table
-we source from is in strict alpha order, hence the builtins portion
-of the macros list is in reverse-alpha (we prepend them) - so longer
-macros that have substrings are always discovered first during
-expansion. */
-
-for (i = 0; i < nopt; i++) if (*(s = US opts[i].name) && *s != '*')
- if (group)
- macro_create(string_sprintf("_OPT_%T_%T_%T", section, group, s), US"y", FALSE, TRUE);
- else
- macro_create(string_sprintf("_OPT_%T_%T", section, s), US"y", FALSE, TRUE);
-}
+ /* Shuffle the remaining characters up or down in the buffer before
+ copying in the replacement text. Don't rescan the replacement for this
+ same macro. */
+ pp = p + m->namelen;
+ if ((moveby = m->replen - m->namelen) != 0)
+ {
+ memmove(p + m->replen, pp, (big_buffer + *newlen) - pp + 1);
+ *newlen += moveby;
+ }
+ Ustrncpy(p, m->replacement, m->replen);
+ t = p + m->replen;
+ while (*t && !isupper(*t) && !(*t == '_' && isupper(t[1]))) t++;
+ *macro_found = TRUE;
+ }
+ }
-static void
-readconf_options(void)
-{
-readconf_options_from_list(optionlist_config, nelem(optionlist_config), US"MAIN", NULL);
-readconf_options_routers();
-readconf_options_transports();
-readconf_options_auths();
-}
+/* An empty macro replacement at the start of a line could mean that ss no
+longer points to the first non-blank character. */
-static void
-macros_create_builtin(void)
-{
-readconf_features();
-readconf_options();
-macros_builtin_created = TRUE;
+while (isspace(*ss)) ss++;
+return ss;
}
-
/*************************************************
* Read configuration line *
*************************************************/
int len = 0; /* Of logical line so far */
int newlen;
uschar *s, *ss;
-macro_item *m;
BOOL macro_found;
/* Loop for handling continuation lines, skipping comments, and dealing with
newlen += Ustrlen(big_buffer + newlen);
}
- /* Find the true start of the physical line - leading spaces are always
- ignored. */
-
- ss = big_buffer + len;
- while (isspace(*ss)) ss++;
-
- /* Process the physical line for macros. If this is the start of the logical
- line, skip over initial text at the start of the line if it starts with an
- upper case character followed by a sequence of name characters and an equals
- sign, because that is the definition of a new macro, and we don't do
- replacement therein. */
-
- s = ss;
- if (len == 0 && isupper(*s))
- {
- while (isalnum(*s) || *s == '_') s++;
- while (isspace(*s)) s++;
- if (*s != '=') s = ss; /* Not a macro definition */
- }
-
- /* If the builtin macros are not yet defined, and the line contains an
- underscrore followed by an one of the three possible chars used by
- builtins, create them. */
-
- if (!macros_builtin_created)
- {
- const uschar * t, * p;
- uschar c;
- for (t = s; (p = CUstrchr(t, '_')); t = p+1)
- if (c = p[1], c == 'O' || c == 'D' || c == 'H')
- {
-/* fprintf(stderr, "%s: builtins create triggered by '%s'\n", __FUNCTION__, s); */
- macros_create_builtin();
- break;
- }
- }
-
- /* For each defined macro, scan the line (from after XXX= if present),
- replacing all occurrences of the macro. */
-
- macro_found = FALSE;
- for (m = macros; m; m = m->next)
- {
- uschar *p, *pp;
- uschar *t = s;
-
- while ((p = Ustrstr(t, m->name)) != NULL)
- {
- int moveby;
- int replen = Ustrlen(m->replacement);
-
-/* fprintf(stderr, "%s: matched '%s' in '%s'\n", __FUNCTION__, m->name, t) */
- /* Expand the buffer if necessary */
-
- while (newlen - m->namelen + replen + 1 > big_buffer_size)
- {
- int newsize = big_buffer_size + BIG_BUFFER_SIZE;
- uschar *newbuffer = store_malloc(newsize);
- memcpy(newbuffer, big_buffer, newlen + 1);
- p = newbuffer + (p - big_buffer);
- s = newbuffer + (s - big_buffer);
- ss = newbuffer + (ss - big_buffer);
- t = newbuffer + (t - big_buffer);
- big_buffer_size = newsize;
- store_free(big_buffer);
- big_buffer = newbuffer;
- }
-
- /* Shuffle the remaining characters up or down in the buffer before
- copying in the replacement text. Don't rescan the replacement for this
- same macro. */
-
- pp = p + m->namelen;
- if ((moveby = replen - m->namelen) != 0)
- {
- memmove(p + replen, pp, (big_buffer + newlen) - pp + 1);
- newlen += moveby;
- }
- Ustrncpy(p, m->replacement, replen);
- t = p + replen;
- macro_found = TRUE;
- }
- }
-
- /* An empty macro replacement at the start of a line could mean that ss no
- longer points to the first non-blank character. */
-
- while (isspace(*ss)) ss++;
+ ss = macros_expand(len, &newlen, ¯o_found);
/* Check for comment lines - these are physical lines. */
/* Handle conditionals, which are also applied to physical lines. Conditions
are of the form ".ifdef ANYTEXT" and are treated as true if any macro
- expansion occured on the rest of the line. A preliminary test for the leading
+ expansion occurred on the rest of the line. A preliminary test for the leading
'.' saves effort on most lines. */
if (*ss == '.')
"absolute path \"%s\"", ss);
else
{
- int offset = 0;
- int size = 0;
- ss = string_append(NULL, &size, &offset, 3, config_directory, "/", ss);
- ss[offset] = '\0'; /* string_append() does not zero terminate the string! */
+ gstring * g = string_append(NULL, 3, config_directory, "/", ss);
+ ss = string_from_gstring(g);
}
if (include_if_exists != 0 && (Ustat(ss, &statbuf) != 0)) continue;
if (ol == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"Exim internal error: missing set flag for %s", name);
return (data_block == NULL)? (BOOL *)(ol->value) :
- (BOOL *)((uschar *)data_block + (long int)(ol->value));
+ (BOOL *)(US data_block + (long int)(ol->value));
}
int sep_i = -(int)sep_o;
const uschar * list = sptr;
uschar * s;
- uschar * list_o = *str_target;
+ gstring * list_o = NULL;
+
+ if (*str_target)
+ {
+ list_o = string_get(Ustrlen(*str_target) + Ustrlen(sptr));
+ list_o = string_cat(list_o, *str_target);
+ }
while ((s = string_nextinlist(&list, &sep_i, NULL, 0)))
list_o = string_append_listele(list_o, sep_o, s);
+
if (list_o)
- *str_target = string_copy_malloc(list_o);
+ *str_target = string_copy_malloc(string_from_gstring(list_o));
}
else
{
}
else
{
- chain = (rewrite_rule **)((uschar *)data_block + (long int)(ol2->value));
- flagptr = (int *)((uschar *)data_block + (long int)(ol3->value));
+ chain = (rewrite_rule **)(US data_block + (long int)(ol2->value));
+ flagptr = (int *)(US data_block + (long int)(ol3->value));
}
while ((p = string_nextinlist(CUSS &sptr, &sep, big_buffer, BIG_BUFFER_SIZE)))
if (data_block == NULL)
*((uschar **)(ol2->value)) = ss;
else
- *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = ss;
+ *((uschar **)(US data_block + (long int)(ol2->value))) = ss;
if (ss != NULL)
{
if (data_block == NULL)
*((uid_t *)(ol->value)) = uid;
else
- *((uid_t *)((uschar *)data_block + (long int)(ol->value))) = uid;
+ *((uid_t *)(US data_block + (long int)(ol->value))) = uid;
/* Set the flag indicating a fixed value is set */
if (data_block == NULL)
*((gid_t *)(ol2->value)) = pw->pw_gid;
else
- *((gid_t *)((uschar *)data_block + (long int)(ol2->value))) = pw->pw_gid;
+ *((gid_t *)(US data_block + (long int)(ol2->value))) = pw->pw_gid;
*set_flag = TRUE;
}
}
if (data_block == NULL)
*((uschar **)(ol2->value)) = ss;
else
- *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = ss;
+ *((uschar **)(US data_block + (long int)(ol2->value))) = ss;
if (ss != NULL)
{
if (data_block == NULL)
*((gid_t *)(ol->value)) = gid;
else
- *((gid_t *)((uschar *)data_block + (long int)(ol->value))) = gid;
+ *((gid_t *)(US data_block + (long int)(ol->value))) = gid;
*(get_set_flag(name, oltop, last, data_block)) = TRUE;
break;
if (data_block == NULL)
*((uid_t **)(ol->value)) = list;
else
- *((uid_t **)((uschar *)data_block + (long int)(ol->value))) = list;
+ *((uid_t **)(US data_block + (long int)(ol->value))) = list;
p = op;
while (count-- > 1)
if (data_block == NULL)
*((gid_t **)(ol->value)) = list;
else
- *((gid_t **)((uschar *)data_block + (long int)(ol->value))) = list;
+ *((gid_t **)(US data_block + (long int)(ol->value))) = list;
p = op;
while (count-- > 1)
if (data_block == NULL)
*((uschar **)(ol2->value)) = sptr;
else
- *((uschar **)((uschar *)data_block + (long int)(ol2->value))) = sptr;
+ *((uschar **)(US data_block + (long int)(ol2->value))) = sptr;
freesptr = FALSE;
break;
}
int bit = 1 << ((ol->type >> 16) & 31);
int *ptr = (data_block == NULL)?
(int *)(ol->value) :
- (int *)((uschar *)data_block + (long int)ol->value);
+ (int *)(US data_block + (long int)ol->value);
if (boolvalue) *ptr |= bit; else *ptr &= ~bit;
break;
}
if (data_block == NULL)
*((BOOL *)(ol->value)) = boolvalue;
else
- *((BOOL *)((uschar *)data_block + (long int)(ol->value))) = boolvalue;
+ *((BOOL *)(US data_block + (long int)(ol->value))) = boolvalue;
/* Verify fudge */
if (data_block == NULL)
*((BOOL *)(ol2->value)) = boolvalue;
else
- *((BOOL *)((uschar *)data_block + (long int)(ol2->value))) = boolvalue;
+ *((BOOL *)(US data_block + (long int)(ol2->value))) = boolvalue;
}
}
if (data_block == NULL)
*((BOOL *)(ol2->value)) = TRUE;
else
- *((BOOL *)((uschar *)data_block + (long int)(ol2->value))) = TRUE;
+ *((BOOL *)(US data_block + (long int)(ol2->value))) = TRUE;
}
}
break;
inttype = US"octal ";
/* Integer: a simple(ish) case; allow octal and hex formats, and
- suffixes K, M and G. The different types affect output, not input. */
+ suffixes K, M, G, and T. The different types affect output, not input. */
case opt_mkint:
case opt_int:
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%sinteger expected for %s",
inttype, name);
- if (errno != ERANGE)
- if (tolower(*endptr) == 'k')
- {
- if (lvalue > INT_MAX/1024 || lvalue < INT_MIN/1024) errno = ERANGE;
- else lvalue *= 1024;
- endptr++;
- }
- else if (tolower(*endptr) == 'm')
- {
- if (lvalue > INT_MAX/(1024*1024) || lvalue < INT_MIN/(1024*1024))
- errno = ERANGE;
- else lvalue *= 1024*1024;
- endptr++;
- }
- else if (tolower(*endptr) == 'g')
- {
- if (lvalue > INT_MAX/(1024*1024*1024) || lvalue < INT_MIN/(1024*1024*1024))
- errno = ERANGE;
- else lvalue *= 1024*1024*1024;
- endptr++;
- }
+ if (errno != ERANGE && *endptr)
+ {
+ uschar * mp = US"TtGgMmKk\0"; /* YyZzEePpTtGgMmKk */
+
+ if ((mp = Ustrchr(mp, *endptr)))
+ {
+ endptr++;
+ do
+ {
+ if (lvalue > INT_MAX/1024 || lvalue < INT_MIN/1024)
+ {
+ errno = ERANGE;
+ break;
+ }
+ lvalue *= 1024;
+ }
+ while (*(mp += 2));
+ }
+ }
if (errno == ERANGE || lvalue > INT_MAX || lvalue < INT_MIN)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
"absolute value of integer \"%s\" is too large (overflow)", s);
while (isspace(*endptr)) endptr++;
- if (*endptr != 0)
+ if (*endptr)
extra_chars_error(endptr, inttype, US"integer value for ", name);
value = (int)lvalue;
}
- if (data_block == NULL)
- *((int *)(ol->value)) = value;
+ if (data_block)
+ *(int *)(US data_block + (long int)ol->value) = value;
else
- *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+ *(int *)ol->value = value;
break;
- /* Integer held in K: again, allow octal and hex formats, and suffixes K, M
- and G. */
- /*XXX consider moving to int_eximarith_t (but mind the overflow test 0415) */
+ /* Integer held in K: again, allow formats and suffixes as above. */
case opt_Kint:
{
uschar *endptr;
errno = 0;
- value = strtol(CS s, CSS &endptr, intbase);
+ int_eximarith_t lvalue = strtol(CS s, CSS &endptr, intbase);
if (endptr == s)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "%sinteger expected for %s",
inttype, name);
- if (errno != ERANGE)
- if (tolower(*endptr) == 'g')
- {
- if (value > INT_MAX/(1024*1024) || value < INT_MIN/(1024*1024))
- errno = ERANGE;
- else
- value *= 1024*1024;
- endptr++;
- }
- else if (tolower(*endptr) == 'm')
- {
- if (value > INT_MAX/1024 || value < INT_MIN/1024)
- errno = ERANGE;
- else
- value *= 1024;
- endptr++;
- }
- else if (tolower(*endptr) == 'k')
- endptr++;
+ if (errno != ERANGE && *endptr)
+ {
+ uschar * mp = US"ZzEePpTtGgMmKk\0"; /* YyZzEePpTtGgMmKk */
+
+ if ((mp = Ustrchr(mp, *endptr)))
+ {
+ endptr++;
+ while (*(mp += 2))
+ {
+ if (lvalue > EXIM_ARITH_MAX/1024 || lvalue < EXIM_ARITH_MIN/1024)
+ {
+ errno = ERANGE;
+ break;
+ }
+ lvalue *= 1024;
+ }
+ }
else
- value = (value + 512)/1024;
+ lvalue = (lvalue + 512)/1024;
+ }
if (errno == ERANGE) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
"absolute value of integer \"%s\" is too large (overflow)", s);
while (isspace(*endptr)) endptr++;
if (*endptr != 0)
extra_chars_error(endptr, inttype, US"integer value for ", name);
- }
- if (data_block == NULL)
- *((int *)(ol->value)) = value;
- else
- *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
- break;
+ if (data_block)
+ *(int_eximarith_t *)(US data_block + (long int)ol->value) = lvalue;
+ else
+ *(int_eximarith_t *)ol->value = lvalue;
+ break;
+ }
/* Fixed-point number: held to 3 decimal places. */
if (data_block == NULL)
*((int *)(ol->value)) = value;
else
- *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+ *((int *)(US data_block + (long int)(ol->value))) = value;
break;
/* There's a special routine to read time values. */
if (data_block == NULL)
*((int *)(ol->value)) = value;
else
- *((int *)((uschar *)data_block + (long int)(ol->value))) = value;
+ *((int *)(US data_block + (long int)(ol->value))) = value;
break;
/* A time list is a list of colon-separated times, with the first
int count = 0;
int *list = (data_block == NULL)?
(int *)(ol->value) :
- (int *)((uschar *)data_block + (long int)(ol->value));
+ (int *)(US data_block + (long int)(ol->value));
if (*s != 0) for (count = 1; count <= list[0] - 2; count++)
{
d = t % 7;
w = t/7;
-if (w > 0) { sprintf(CS p, "%dw", w); while (*p) p++; }
-if (d > 0) { sprintf(CS p, "%dd", d); while (*p) p++; }
-if (h > 0) { sprintf(CS p, "%dh", h); while (*p) p++; }
-if (m > 0) { sprintf(CS p, "%dm", m); while (*p) p++; }
+if (w > 0) p += sprintf(CS p, "%dw", w);
+if (d > 0) p += sprintf(CS p, "%dd", d);
+if (h > 0) p += sprintf(CS p, "%dh", h);
+if (m > 0) p += sprintf(CS p, "%dm", m);
if (s > 0 || p == time_buffer) sprintf(CS p, "%ds", s);
return time_buffer;
last one more than the offset of the last entry in optop
no_labels do not show "foo = " at the start.
-Returns: nothing
+Returns: boolean success
*/
-static void
+static BOOL
print_ol(optionlist *ol, uschar *name, void *options_block,
optionlist *oltop, int last, BOOL no_labels)
{
uschar *s;
uschar name2[64];
-if (ol == NULL)
+if (!ol)
{
printf("%s is not a known option\n", name);
- return;
+ return FALSE;
}
/* Non-admin callers cannot see options that have been flagged secure by the
"hide" prefix. */
-if (!admin_user && (ol->type & opt_secure) != 0)
+if (!f.admin_user && ol->type & opt_secure)
{
if (no_labels)
printf("%s\n", hidden);
else
printf("%s = %s\n", name, hidden);
- return;
+ return TRUE;
}
/* Else show the value of the option */
value = ol->value;
-if (options_block != NULL)
+if (options_block)
{
- if ((ol->type & opt_public) == 0)
+ if (!(ol->type & opt_public))
options_block = (void *)(((driver_instance *)options_block)->options_block);
- value = (void *)((uschar *)options_block + (long int)value);
+ value = (void *)(US options_block + (long int)value);
}
switch(ol->type & opt_mask)
{
case opt_stringptr:
case opt_rewrite: /* Show the text value */
- s = *((uschar **)value);
- if (!no_labels) printf("%s = ", name);
- printf("%s\n", (s == NULL)? US"" : string_printing2(s, FALSE));
- break;
+ s = *(USS value);
+ if (!no_labels) printf("%s = ", name);
+ printf("%s\n", s ? string_printing2(s, FALSE) : US"");
+ break;
case opt_int:
- if (!no_labels) printf("%s = ", name);
- printf("%d\n", *((int *)value));
- break;
+ if (!no_labels) printf("%s = ", name);
+ printf("%d\n", *((int *)value));
+ break;
case opt_mkint:
{
printf("%d\n", x);
}
}
- break;
+ break;
case opt_Kint:
{
- int x = *((int *)value);
+ int_eximarith_t x = *((int_eximarith_t *)value);
if (!no_labels) printf("%s = ", name);
if (x == 0) printf("0\n");
- else if ((x & 1023) == 0) printf("%dM\n", x >> 10);
- else printf("%dK\n", x);
+ else if ((x & ((1<<30)-1)) == 0) printf(PR_EXIM_ARITH "T\n", x >> 30);
+ else if ((x & ((1<<20)-1)) == 0) printf(PR_EXIM_ARITH "G\n", x >> 20);
+ else if ((x & ((1<<10)-1)) == 0) printf(PR_EXIM_ARITH "M\n", x >> 10);
+ else printf(PR_EXIM_ARITH "K\n", x);
}
- break;
+ break;
case opt_octint:
- if (!no_labels) printf("%s = ", name);
- printf("%#o\n", *((int *)value));
- break;
+ if (!no_labels) printf("%s = ", name);
+ printf("%#o\n", *((int *)value));
+ break;
/* Can be negative only when "unset", in which case integer */
printf("\n");
}
}
- break;
+ break;
/* If the numerical value is unset, try for the string value */
case opt_expand_uid:
- if (! *get_set_flag(name, oltop, last, options_block))
- {
- sprintf(CS name2, "*expand_%.50s", name);
- ol2 = find_option(name2, oltop, last);
- if (ol2 != NULL)
+ if (! *get_set_flag(name, oltop, last, options_block))
{
- void *value2 = ol2->value;
- if (options_block != NULL)
- value2 = (void *)((uschar *)options_block + (long int)value2);
- s = *((uschar **)value2);
- if (!no_labels) printf("%s = ", name);
- printf("%s\n", (s == NULL)? US"" : string_printing(s));
- break;
+ sprintf(CS name2, "*expand_%.50s", name);
+ if ((ol2 = find_option(name2, oltop, last)))
+ {
+ void *value2 = ol2->value;
+ if (options_block)
+ value2 = (void *)(US options_block + (long int)value2);
+ s = *(USS value2);
+ if (!no_labels) printf("%s = ", name);
+ printf("%s\n", s ? string_printing(s) : US"");
+ break;
+ }
}
- }
- /* Else fall through */
+ /* Else fall through */
case opt_uid:
- if (!no_labels) printf("%s = ", name);
- if (! *get_set_flag(name, oltop, last, options_block))
- printf("\n");
- else
- {
- pw = getpwuid(*((uid_t *)value));
- if (pw == NULL)
- printf("%ld\n", (long int)(*((uid_t *)value)));
- else printf("%s\n", pw->pw_name);
- }
- break;
+ if (!no_labels) printf("%s = ", name);
+ if (! *get_set_flag(name, oltop, last, options_block))
+ printf("\n");
+ else
+ if ((pw = getpwuid(*((uid_t *)value))))
+ printf("%s\n", pw->pw_name);
+ else
+ printf("%ld\n", (long int)(*((uid_t *)value)));
+ break;
/* If the numerical value is unset, try for the string value */
case opt_expand_gid:
- if (! *get_set_flag(name, oltop, last, options_block))
- {
- sprintf(CS name2, "*expand_%.50s", name);
- ol2 = find_option(name2, oltop, last);
- if (ol2 != NULL && (ol2->type & opt_mask) == opt_stringptr)
+ if (! *get_set_flag(name, oltop, last, options_block))
{
- void *value2 = ol2->value;
- if (options_block != NULL)
- value2 = (void *)((uschar *)options_block + (long int)value2);
- s = *((uschar **)value2);
- if (!no_labels) printf("%s = ", name);
- printf("%s\n", (s == NULL)? US"" : string_printing(s));
- break;
+ sprintf(CS name2, "*expand_%.50s", name);
+ if ( (ol2 = find_option(name2, oltop, last))
+ && (ol2->type & opt_mask) == opt_stringptr)
+ {
+ void *value2 = ol2->value;
+ if (options_block)
+ value2 = (void *)(US options_block + (long int)value2);
+ s = *(USS value2);
+ if (!no_labels) printf("%s = ", name);
+ printf("%s\n", s ? string_printing(s) : US"");
+ break;
+ }
}
- }
- /* Else fall through */
+ /* Else fall through */
case opt_gid:
- if (!no_labels) printf("%s = ", name);
- if (! *get_set_flag(name, oltop, last, options_block))
- printf("\n");
- else
- {
- gr = getgrgid(*((int *)value));
- if (gr == NULL)
- printf("%ld\n", (long int)(*((int *)value)));
- else printf("%s\n", gr->gr_name);
- }
- break;
+ if (!no_labels) printf("%s = ", name);
+ if (! *get_set_flag(name, oltop, last, options_block))
+ printf("\n");
+ else
+ if ((gr = getgrgid(*((int *)value))))
+ printf("%s\n", gr->gr_name);
+ else
+ printf("%ld\n", (long int)(*((int *)value)));
+ break;
case opt_uidlist:
- uidlist = *((uid_t **)value);
- if (!no_labels) printf("%s =", name);
- if (uidlist != NULL)
- {
- int i;
- uschar sep = ' ';
- if (no_labels) sep = '\0';
- for (i = 1; i <= (int)(uidlist[0]); i++)
+ uidlist = *((uid_t **)value);
+ if (!no_labels) printf("%s =", name);
+ if (uidlist)
{
- uschar *name = NULL;
- pw = getpwuid(uidlist[i]);
- if (pw != NULL) name = US pw->pw_name;
- if (sep != '\0') printf("%c", sep);
- if (name != NULL) printf("%s", name);
- else printf("%ld", (long int)(uidlist[i]));
- sep = ':';
+ int i;
+ uschar sep = no_labels ? '\0' : ' ';
+ for (i = 1; i <= (int)(uidlist[0]); i++)
+ {
+ uschar *name = NULL;
+ if ((pw = getpwuid(uidlist[i]))) name = US pw->pw_name;
+ if (sep != '\0') printf("%c", sep);
+ if (name) printf("%s", name);
+ else printf("%ld", (long int)(uidlist[i]));
+ sep = ':';
+ }
}
- }
- printf("\n");
- break;
+ printf("\n");
+ break;
case opt_gidlist:
- gidlist = *((gid_t **)value);
- if (!no_labels) printf("%s =", name);
- if (gidlist != NULL)
- {
- int i;
- uschar sep = ' ';
- if (no_labels) sep = '\0';
- for (i = 1; i <= (int)(gidlist[0]); i++)
+ gidlist = *((gid_t **)value);
+ if (!no_labels) printf("%s =", name);
+ if (gidlist)
{
- uschar *name = NULL;
- gr = getgrgid(gidlist[i]);
- if (gr != NULL) name = US gr->gr_name;
- if (sep != '\0') printf("%c", sep);
- if (name != NULL) printf("%s", name);
- else printf("%ld", (long int)(gidlist[i]));
- sep = ':';
+ int i;
+ uschar sep = no_labels ? '\0' : ' ';
+ for (i = 1; i <= (int)(gidlist[0]); i++)
+ {
+ uschar *name = NULL;
+ if ((gr = getgrgid(gidlist[i]))) name = US gr->gr_name;
+ if (sep != '\0') printf("%c", sep);
+ if (name) printf("%s", name);
+ else printf("%ld", (long int)(gidlist[i]));
+ sep = ':';
+ }
}
- }
- printf("\n");
- break;
+ printf("\n");
+ break;
case opt_time:
- if (!no_labels) printf("%s = ", name);
- printf("%s\n", readconf_printtime(*((int *)value)));
- break;
+ if (!no_labels) printf("%s = ", name);
+ printf("%s\n", readconf_printtime(*((int *)value)));
+ break;
case opt_timelist:
{
int *list = (int *)value;
if (!no_labels) printf("%s = ", name);
for (i = 0; i < list[1]; i++)
- printf("%s%s", (i == 0)? "" : ":", readconf_printtime(list[i+2]));
+ printf("%s%s", i == 0 ? "" : ":", readconf_printtime(list[i+2]));
printf("\n");
}
- break;
+ break;
case opt_bit:
- printf("%s%s\n", ((*((int *)value)) & (1 << ((ol->type >> 16) & 31)))?
- "" : "no_", name);
- break;
+ printf("%s%s\n", ((*((int *)value)) & (1 << ((ol->type >> 16) & 31)))?
+ "" : "no_", name);
+ break;
case opt_expand_bool:
- sprintf(CS name2, "*expand_%.50s", name);
- ol2 = find_option(name2, oltop, last);
- if (ol2 != NULL && ol2->value != NULL)
- {
- void *value2 = ol2->value;
- if (options_block != NULL)
- value2 = (void *)((uschar *)options_block + (long int)value2);
- s = *((uschar **)value2);
- if (s != NULL)
+ sprintf(CS name2, "*expand_%.50s", name);
+ if ((ol2 = find_option(name2, oltop, last)) && ol2->value)
{
- if (!no_labels) printf("%s = ", name);
- printf("%s\n", string_printing(s));
- break;
+ void *value2 = ol2->value;
+ if (options_block)
+ value2 = (void *)(US options_block + (long int)value2);
+ s = *(USS value2);
+ if (s)
+ {
+ if (!no_labels) printf("%s = ", name);
+ printf("%s\n", string_printing(s));
+ break;
+ }
+ /* s == NULL => string not set; fall through */
}
- /* s == NULL => string not set; fall through */
- }
- /* Fall through */
+ /* Fall through */
case opt_bool:
case opt_bool_verify:
case opt_bool_set:
- printf("%s%s\n", (*((BOOL *)value))? "" : "no_", name);
- break;
+ printf("%s%s\n", (*((BOOL *)value))? "" : "no_", name);
+ break;
}
+return TRUE;
}
type NULL or driver type name, as described above
no_labels avoid the "foo = " at the start of an item
-Returns: nothing
+Returns: Boolean success
*/
-void
+BOOL
readconf_print(uschar *name, uschar *type, BOOL no_labels)
{
BOOL names_only = FALSE;
macro_item *m;
int size = 0;
-if (type == NULL)
+if (!type)
{
if (*name == '+')
{
&hostlist_anchor, &localpartlist_anchor };
for (i = 0; i < 4; i++)
- {
- t = tree_search(*(anchors[i]), name+1);
- if (t != NULL)
+ if ((t = tree_search(*(anchors[i]), name+1)))
{
found = TRUE;
if (no_labels)
printf("%slist %s = %s\n", types[i], name+1,
((namedlist_block *)(t->data.ptr))->string);
}
- }
if (!found)
printf("no address, domain, host, or local part list called \"%s\" "
"exists\n", name+1);
- return;
+ return found;
}
if ( Ustrcmp(name, "configure_file") == 0
|| Ustrcmp(name, "config_file") == 0)
{
printf("%s\n", CS config_main_filename);
- return;
+ return TRUE;
}
if (Ustrcmp(name, "all") == 0)
{
for (ol = optionlist_config;
ol < optionlist_config + nelem(optionlist_config); ol++)
- {
- if ((ol->type & opt_hidden) == 0)
- print_ol(ol, US ol->name, NULL,
- optionlist_config, nelem(optionlist_config),
- no_labels);
- }
- return;
+ if (!(ol->type & opt_hidden))
+ (void) print_ol(ol, US ol->name, NULL,
+ optionlist_config, nelem(optionlist_config),
+ no_labels);
+ return TRUE;
}
if (Ustrcmp(name, "local_scan") == 0)
{
- #ifndef LOCAL_SCAN_HAS_OPTIONS
+#ifndef LOCAL_SCAN_HAS_OPTIONS
printf("local_scan() options are not supported\n");
- #else
+ return FALSE;
+#else
for (ol = local_scan_options;
ol < local_scan_options + local_scan_options_count; ol++)
- {
- print_ol(ol, US ol->name, NULL, local_scan_options,
- local_scan_options_count, no_labels);
- }
- #endif
- return;
+ (void) print_ol(ol, US ol->name, NULL, local_scan_options,
+ local_scan_options_count, no_labels);
+ return TRUE;
+#endif
}
if (Ustrcmp(name, "config") == 0)
{
- print_config(admin_user, no_labels);
- return;
+ print_config(f.admin_user, no_labels);
+ return TRUE;
}
if (Ustrcmp(name, "routers") == 0)
type = US"transport";
name = NULL;
}
-
else if (Ustrcmp(name, "authenticators") == 0)
{
type = US"authenticator";
name = NULL;
}
-
else if (Ustrcmp(name, "macros") == 0)
{
type = US"macro";
name = NULL;
}
-
else if (Ustrcmp(name, "router_list") == 0)
{
type = US"router";
name = NULL;
names_only = TRUE;
}
-
else if (Ustrcmp(name, "transport_list") == 0)
{
type = US"transport";
name = NULL;
names_only = TRUE;
}
-
else if (Ustrcmp(name, "authenticator_list") == 0)
{
type = US"authenticator";
name = NULL;
names_only = TRUE;
}
-
else if (Ustrcmp(name, "macro_list") == 0)
{
type = US"macro";
name = NULL;
names_only = TRUE;
}
-
else if (Ustrcmp(name, "environment") == 0)
{
if (environ)
puts(CS *p);
}
}
- return;
+ return TRUE;
}
else
- {
- print_ol(find_option(name, optionlist_config, nelem(optionlist_config)),
+ return print_ol(find_option(name,
+ optionlist_config, nelem(optionlist_config)),
name, NULL, optionlist_config, nelem(optionlist_config), no_labels);
- return;
- }
}
/* Handle the options for a router or transport. Skip options that are flagged
{
/* People store passwords in macros and they were previously not available
for printing. So we have an admin_users restriction. */
- if (!admin_user)
+ if (!f.admin_user)
{
fprintf(stderr, "exim: permission denied\n");
- exit(EXIT_FAILURE);
+ return FALSE;
}
- if (!macros_builtin_created) macros_create_builtin();
for (m = macros; m; m = m->next)
if (!name || Ustrcmp(name, m->name) == 0)
{
if (names_only)
printf("%s\n", CS m->name);
+ else if (no_labels)
+ printf("%s\n", CS m->replacement);
else
printf("%s=%s\n", CS m->name, CS m->replacement);
if (name)
- return;
+ return TRUE;
}
- if (name)
- printf("%s %s not found\n", type, name);
- return;
+ if (!name) return TRUE;
+
+ printf("%s %s not found\n", type, name);
+ return FALSE;
}
if (names_only)
{
- for (; d != NULL; d = d->next) printf("%s\n", CS d->name);
- return;
+ for (; d; d = d->next) printf("%s\n", CS d->name);
+ return TRUE;
}
/* Either search for a given driver, or print all of them */
-for (; d != NULL; d = d->next)
+for (; d; d = d->next)
{
- if (name == NULL)
+ BOOL rc = FALSE;
+ if (!name)
printf("\n%s %s:\n", d->name, type);
else if (Ustrcmp(d->name, name) != 0) continue;
for (ol = ol2; ol < ol2 + size; ol++)
- {
- if ((ol->type & opt_hidden) == 0)
- print_ol(ol, US ol->name, d, ol2, size, no_labels);
- }
+ if (!(ol->type & opt_hidden))
+ rc |= print_ol(ol, US ol->name, d, ol2, size, no_labels);
for (ol = d->info->options;
ol < d->info->options + *(d->info->options_count); ol++)
- {
- if ((ol->type & opt_hidden) == 0)
- print_ol(ol, US ol->name, d, d->info->options, *(d->info->options_count), no_labels);
- }
- if (name != NULL) return;
+ if (!(ol->type & opt_hidden))
+ rc |= print_ol(ol, US ol->name, d, d->info->options,
+ *d->info->options_count, no_labels);
+
+ if (name) return rc;
}
-if (name != NULL) printf("%s %s not found\n", type, name);
+if (!name) return TRUE;
+
+printf("%s %s not found\n", type, name);
+return FALSE;
}
exim_setugid(exim_uid, exim_gid, FALSE,
US"calling tls_validate_require_cipher");
- errmsg = tls_validate_require_cipher();
- if (errmsg)
- {
+ if ((errmsg = tls_validate_require_cipher()))
log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
"tls_require_ciphers invalid: %s", errmsg);
- }
fflush(NULL);
_exit(0);
}
/* relative configuration file name: working dir + / + basename(filename) */
uschar buf[PATH_MAX];
- int offset = 0;
- int size = 0;
+ gstring * g;
if (os_getcwd(buf, PATH_MAX) == NULL)
{
perror("exim: getcwd");
exit(EXIT_FAILURE);
}
- config_main_directory = string_cat(NULL, &size, &offset, buf);
+ g = string_cat(NULL, buf);
/* If the dir does not end with a "/", append one */
- if (config_main_directory[offset-1] != '/')
- config_main_directory = string_catn(config_main_directory, &size, &offset, US"/", 1);
+ if (g->s[g->ptr-1] != '/')
+ g = string_catn(g, US"/", 1);
/* If the config file contains a "/", extract the directory part */
if (last_slash)
- config_main_directory = string_catn(config_main_directory, &size, &offset, filename, last_slash - filename);
+ g = string_catn(g, filename, last_slash - filename);
- config_main_directory[offset] = '\0';
+ config_main_directory = string_from_gstring(g);
}
config_directory = config_main_directory;
}
/* Check the status of the file we have opened, if we have retained root
privileges and the file isn't /dev/null (which *should* be 0666). */
-if (trusted_config && Ustrcmp(filename, US"/dev/null"))
+if (f.trusted_config && Ustrcmp(filename, US"/dev/null"))
{
if (fstat(fileno(config_file), &statbuf) != 0)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to stat configuration file %s",
letter. If we see something starting with an upper case letter, it is taken as
a macro definition. */
-while ((s = get_config_line()) != NULL)
+while ((s = get_config_line()))
{
- if (isupper(s[0])) read_macro_assignment(s);
+ if (config_lineno == 1 && Ustrstr(s, "\xef\xbb\xbf") == s)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+ "found unexpected BOM (Byte Order Mark)");
+
+ if (isupper(s[0]))
+ { if (!macro_read_assignment(s)) exim_exit(EXIT_FAILURE, US""); }
else if (Ustrncmp(s, "domainlist", 10) == 0)
read_named_list(&domainlist_anchor, &domainlist_count,
openlog(). Default is LOG_MAIL set in globals.c. Allow the user to omit the
leading "log_". */
-if (syslog_facility_str != NULL)
+if (syslog_facility_str)
{
int i;
uschar *s = syslog_facility_str;
s += 4;
for (i = 0; i < syslog_list_size; i++)
- {
if (strcmpic(s, syslog_list[i].name) == 0)
{
syslog_facility = syslog_list[i].value;
break;
}
- }
if (i >= syslog_list_size)
- {
log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
"failed to interpret syslog_facility \"%s\"", syslog_facility_str);
- }
}
/* Expand pid_file_path */
if (*pid_file_path != 0)
{
- s = expand_string(pid_file_path);
- if (s == NULL)
+ if (!(s = expand_string(pid_file_path)))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand pid_file_path "
"\"%s\": %s", pid_file_path, expand_string_message);
pid_file_path = s;
/* Set default value of process_log_path */
-if (process_log_path == NULL || *process_log_path =='\0')
+if (!process_log_path || *process_log_path =='\0')
process_log_path = string_sprintf("%s/exim-process.info", spool_directory);
/* Compile the regex for matching a UUCP-style "From_" line in an incoming
/* Unpick the SMTP rate limiting options, if set */
-if (smtp_ratelimit_mail != NULL)
- {
+if (smtp_ratelimit_mail)
unpick_ratelimit(smtp_ratelimit_mail, &smtp_rlm_threshold,
&smtp_rlm_base, &smtp_rlm_factor, &smtp_rlm_limit);
- }
-if (smtp_ratelimit_rcpt != NULL)
- {
+if (smtp_ratelimit_rcpt)
unpick_ratelimit(smtp_ratelimit_rcpt, &smtp_rlr_threshold,
&smtp_rlr_base, &smtp_rlr_factor, &smtp_rlr_limit);
- }
/* The qualify domains default to the primary host name */
-if (qualify_domain_sender == NULL)
+if (!qualify_domain_sender)
qualify_domain_sender = primary_hostname;
-if (qualify_domain_recipient == NULL)
+if (!qualify_domain_recipient)
qualify_domain_recipient = qualify_domain_sender;
/* Setting system_filter_user in the configuration sets the gid as well if a
if (system_filter_uid_set && !system_filter_gid_set)
{
struct passwd *pw = getpwuid(system_filter_uid);
- if (pw == NULL)
+ if (!pw)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to look up uid %ld",
(long int)system_filter_uid);
system_filter_gid = pw->pw_gid;
/* If the errors_reply_to field is set, check that it is syntactically valid
and ensure it contains a domain. */
-if (errors_reply_to != NULL)
+if (errors_reply_to)
{
uschar *errmess;
int start, end, domain;
uschar *recipient = parse_extract_address(errors_reply_to, &errmess,
&start, &end, &domain, FALSE);
- if (recipient == NULL)
+ if (!recipient)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
"error in errors_reply_to (%s): %s", errors_reply_to, errmess);
so that it can be computed from the host name, for example. We do this last
so as to ensure that everything else is set up before the expansion. */
-if (host_number_string != NULL)
+if (host_number_string)
{
long int n;
uschar *end;
uschar *s = expand_string(host_number_string);
- if (s == NULL)
+
+ if (!s)
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"failed to expand localhost_number \"%s\": %s",
host_number_string, expand_string_message);
#ifdef SUPPORT_TLS
/* If tls_verify_hosts is set, tls_verify_certificates must also be set */
-if ((tls_verify_hosts != NULL || tls_try_verify_hosts != NULL) &&
- tls_verify_certificates == NULL)
+if ((tls_verify_hosts || tls_try_verify_hosts) && !tls_verify_certificates)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
"tls_%sverify_hosts is set, but tls_verify_certificates is not set",
- (tls_verify_hosts != NULL)? "" : "try_");
+ tls_verify_hosts ? "" : "try_");
/* This also checks that the library linkage is working and we can call
routines in it, so call even if tls_require_ciphers is unset */
"tls_dh_max_bits is too small, must be at least 1024 for interop");
/* If openssl_options is set, validate it */
-if (openssl_options != NULL)
+if (openssl_options)
{
# ifdef USE_GNUTLS
log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
"openssl_options is set but we're using GnuTLS");
# else
long dummy;
- if (!(tls_openssl_options_parse(openssl_options, &dummy)))
+ if (!tls_openssl_options_parse(openssl_options, &dummy))
log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
"openssl_options parse error: %s", openssl_options);
# endif
driver_info *dd;
for (dd = drivers_available; dd->driver_name[0] != 0;
- dd = (driver_info *)(((uschar *)dd) + size_of_info))
+ dd = (driver_info *)((US dd) + size_of_info))
{
if (Ustrcmp(d->driver_name, dd->driver_name) == 0)
{
(d->info->init)(d);
d = NULL;
}
- read_macro_assignment(buffer);
+ if (!macro_read_assignment(buffer)) exim_exit(EXIT_FAILURE, US"");
continue;
}
int type = ol->type & opt_mask;
if (type != opt_stringptr) continue;
options_block = ((ol->type & opt_public) == 0)? d->options_block : (void *)d;
- value = *(uschar **)((uschar *)options_block + (long int)(ol->value));
+ value = *(uschar **)(US options_block + (long int)(ol->value));
if (value != NULL && (ss = Ustrstr(value, s)) != NULL)
{
if (ss <= value || (ss[-1] != '$' && ss[-1] != '{') ||
static int values[] =
{ 'A', 'M', RTEF_CTOUT, RTEF_CTOUT|'A', RTEF_CTOUT|'M' };
- for (i = 0; i < sizeof(extras)/sizeof(uschar *); i++)
+ for (i = 0; i < nelem(extras); i++)
if (strncmpic(x, extras[i], xlen) == 0)
{
*more_errno = values[i];
break;
}
- if (i >= sizeof(extras)/sizeof(uschar *))
+ if (i >= nelem(extras))
if (strncmpic(x, US"DNS", xlen) == 0)
log_write(0, LOG_MAIN|LOG_PANIC, "\"timeout_dns\" is no longer "
"available in retry rules (it has never worked) - treated as "
* Initialize authenticators *
*************************************************/
-static void
-readconf_options_auths(void)
-{
-struct auth_info * ai;
-
-readconf_options_from_list(optionlist_auths, optionlist_auths_size, US"AUTHENTICATORS", NULL);
-
-for (ai = auths_available; ai->driver_name[0]; ai++)
- {
- macro_create(string_sprintf("_DRIVER_AUTHENTICATOR_%T", ai->driver_name), US"y", FALSE, TRUE);
- readconf_options_from_list(ai->options, (unsigned)*ai->options_count, US"AUTHENTICATOR", ai->driver_name);
- }
-}
-
-
/* Read the authenticators section of the configuration file.
Arguments: none
auths_init(void)
{
auth_instance *au, *bu;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+int nauths = 0;
+#endif
readconf_driver_init(US"authenticator",
(driver_instance **)(&auths), /* chain anchor */
"(%s and %s) have the same public name (%s)",
au->client ? US"client" : US"server", au->name, bu->name,
au->public_name);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ nauths++;
+#endif
}
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+f.smtp_in_early_pipe_no_auth = nauths > 16;
+#endif
}
acl_line = get_config_line();
-while(acl_line != NULL)
+while(acl_line)
{
uschar name[64];
tree_node *node;
p = readconf_readname(name, sizeof(name), acl_line);
if (isupper(*name) && *p == '=')
{
- read_macro_assignment(acl_line);
+ if (!macro_read_assignment(acl_line)) exim_exit(EXIT_FAILURE, US"");
acl_line = get_config_line();
continue;
}
#else
uschar *p;
-while ((p = get_config_line()) != NULL)
+while ((p = get_config_line()))
{
(void) readconf_handle_option(p, local_scan_options, local_scan_options_count,
NULL, US"local_scan option \"%s\" unknown");
{
int bit;
int first = 0;
- int last = sizeof(section_list) / sizeof(uschar *);
+ int last = nelem(section_list);
int mid = last/2;
int n = Ustrlen(next_section);
void
readconf_save_config(const uschar *s)
{
- save_config_line(string_sprintf("# Exim Configuration (%s)",
- running_in_test_harness ? US"X" : s));
+save_config_line(string_sprintf("# Exim Configuration (%s)",
+ f.running_in_test_harness ? US"X" : s));
}
static void
save_config_position(const uschar *file, int line)
{
- save_config_line(string_sprintf("# %d \"%s\"", line, file));
+save_config_line(string_sprintf("# %d \"%s\"", line, file));
}
/* Append a pre-parsed logical line to the config lines store,
this operates on a global (static) list that holds all the pre-parsed
config lines, we do no further processing here, output formatting and
honouring of <hide> or macros will be done during output */
+
static void
save_config_line(const uschar* line)
{
}
}
+#endif /*!MACRO_PREDEF*/
/* vi: aw ai sw=2
*/
/* End of readconf.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for receiving a message and setting up spool files. */
#include "exim.h"
+#include <setjmp.h>
#ifdef EXPERIMENTAL_DCC
extern int dcc_ok;
* Local static variables *
*************************************************/
-static FILE *data_file = NULL;
static int data_fd = -1;
static uschar *spool_name = US"";
enum CH_STATE {LF_SEEN, MID_LINE, CR_SEEN};
+#ifdef HAVE_LOCAL_SCAN
+jmp_buf local_scan_env; /* error-handling context for local_scan */
+unsigned had_local_scan_crash;
+unsigned had_local_scan_timeout;
+#endif
+
/*************************************************
* Non-SMTP character reading functions *
int
stdin_getc(unsigned lim)
{
-return getc(stdin);
+int c = getc(stdin);
+
+if (had_data_timeout)
+ {
+ fprintf(stderr, "exim: timed out while reading - message abandoned\n");
+ log_write(L_lost_incoming_connection,
+ LOG_MAIN, "timed out while reading local message");
+ receive_bomb_out(US"data-timeout", NULL); /* Does not return */
+ }
+if (had_data_sigint)
+ {
+ if (filter_test == FTEST_NONE)
+ {
+ fprintf(stderr, "\nexim: %s received - message abandoned\n",
+ had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+ log_write(0, LOG_MAIN, "%s received while reading local message",
+ had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+ }
+ receive_bomb_out(US"signal-exit", NULL); /* Does not return */
+ }
+return c;
}
int
receive_check_set_sender(uschar *newsender)
{
uschar *qnewsender;
-if (trusted_caller) return TRUE;
-if (newsender == NULL || untrusted_set_sender == NULL) return FALSE;
-qnewsender = (Ustrchr(newsender, '@') != NULL)?
- newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
-return
- match_address_list(qnewsender, TRUE, TRUE, CUSS &untrusted_set_sender, NULL, -1,
- 0, NULL) == OK;
+if (f.trusted_caller) return TRUE;
+if (!newsender || !untrusted_set_sender) return FALSE;
+qnewsender = Ustrchr(newsender, '@')
+ ? newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
+return match_address_list_basic(qnewsender, CUSS &untrusted_set_sender, 0) == OK;
}
All values are -1 if the STATFS functions are not available.
*/
-int
+int_eximarith_t
receive_statvfs(BOOL isspool, int *inodeptr)
{
#ifdef HAVE_STATFS
log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
"%s directory %s: %s", name, path, strerror(errno));
smtp_closedown(US"spool or log directory problem");
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, NULL);
}
*inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
/* Disks are getting huge. Take care with computing the size in kilobytes. */
-return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
+return (int_eximarith_t)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
#else
/* Unable to find partition sizes in this environment. */
BOOL
receive_check_fs(int msg_size)
{
-int space, inodes;
+int_eximarith_t space;
+int inodes;
if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
{
space = receive_statvfs(TRUE, &inodes);
DEBUG(D_receive)
- debug_printf("spool directory space = %dK inodes = %d "
- "check_space = %dK inodes = %d msg_size = %d\n",
+ debug_printf("spool directory space = " PR_EXIM_ARITH "K inodes = %d "
+ "check_space = " PR_EXIM_ARITH "K inodes = %d msg_size = %d\n",
space, inodes, check_spool_space, check_spool_inodes, msg_size);
if ((space >= 0 && space < check_spool_space) ||
(inodes >= 0 && inodes < check_spool_inodes))
{
- log_write(0, LOG_MAIN, "spool directory space check failed: space=%d "
- "inodes=%d", space, inodes);
+ log_write(0, LOG_MAIN, "spool directory space check failed: space="
+ PR_EXIM_ARITH " inodes=%d", space, inodes);
return FALSE;
}
}
space = receive_statvfs(FALSE, &inodes);
DEBUG(D_receive)
- debug_printf("log directory space = %dK inodes = %d "
- "check_space = %dK inodes = %d\n",
+ debug_printf("log directory space = " PR_EXIM_ARITH "K inodes = %d "
+ "check_space = " PR_EXIM_ARITH "K inodes = %d\n",
space, inodes, check_log_space, check_log_inodes);
- if ((space >= 0 && space < check_log_space) ||
- (inodes >= 0 && inodes < check_log_inodes))
+ if ( space >= 0 && space < check_log_space
+ || inodes >= 0 && inodes < check_log_inodes)
{
- log_write(0, LOG_MAIN, "log directory space check failed: space=%d "
- "inodes=%d", space, inodes);
+ log_write(0, LOG_MAIN, "log directory space check failed: space=" PR_EXIM_ARITH
+ " inodes=%d", space, inodes);
return FALSE;
}
}
/* Now close the file if it is open, either as a fd or a stream. */
-if (data_file != NULL)
+if (spool_data_file)
+ {
+ (void)fclose(spool_data_file);
+ spool_data_file = NULL;
+ }
+else if (data_fd >= 0)
{
- (void)fclose(data_file);
- data_file = NULL;
-} else if (data_fd >= 0) {
(void)close(data_fd);
data_fd = -1;
}
/* Exit from the program (non-BSMTP cases) */
-exim_exit(EXIT_FAILURE);
+exim_exit(EXIT_FAILURE, NULL);
}
static void
data_timeout_handler(int sig)
{
-uschar *msg = NULL;
-
-sig = sig; /* Keep picky compilers happy */
-
-if (smtp_input)
- {
- msg = US"SMTP incoming data timeout";
- log_write(L_lost_incoming_connection,
- LOG_MAIN, "SMTP data timeout (message abandoned) on connection "
- "from %s F=<%s>",
- (sender_fullhost != NULL)? sender_fullhost : US"local process",
- sender_address);
- }
-else
- {
- fprintf(stderr, "exim: timed out while reading - message abandoned\n");
- log_write(L_lost_incoming_connection,
- LOG_MAIN, "timed out while reading local message");
- }
-
-receive_bomb_out(US"data-timeout", msg); /* Does not return */
+had_data_timeout = sig;
}
+#ifdef HAVE_LOCAL_SCAN
/*************************************************
* local_scan() timeout *
*************************************************/
/* Handler function for timeouts that occur while running a local_scan()
-function.
+function. Posix recommends against calling longjmp() from a signal-handler,
+but the GCC manual says you can so we will, and trust that it's better than
+calling probably non-signal-safe funxtions during logging from within the
+handler, even with other compilers.
+
+See also https://cwe.mitre.org/data/definitions/745.html which also lists
+it as unsafe.
+
+This is all because we have no control over what might be written for a
+local-scan function, so cannot sprinkle had-signal checks after each
+call-site. At least with the default "do-nothing" function we won't
+ever get here.
Argument: the signal number
Returns: nothing
static void
local_scan_timeout_handler(int sig)
{
-sig = sig; /* Keep picky compilers happy */
-log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
- "message temporarily rejected (size %d)", message_size);
-/* Does not return */
-receive_bomb_out(US"local-scan-timeout", US"local verification problem");
+had_local_scan_timeout = sig;
+siglongjmp(local_scan_env, 1);
}
static void
local_scan_crash_handler(int sig)
{
-log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with "
- "signal %d - message temporarily rejected (size %d)", sig, message_size);
-/* Does not return */
-receive_bomb_out(US"local-scan-error", US"local verification problem");
+had_local_scan_crash = sig;
+siglongjmp(local_scan_env, 1);
}
+#endif /*HAVE_LOCAL_SCAN*/
+
/*************************************************
* SIGTERM or SIGINT received *
static void
data_sigterm_sigint_handler(int sig)
{
-uschar *msg = NULL;
-
-if (smtp_input)
- {
- msg = US"Service not available - SIGTERM or SIGINT received";
- log_write(0, LOG_MAIN, "%s closed after %s", smtp_get_connection_info(),
- (sig == SIGTERM)? "SIGTERM" : "SIGINT");
- }
-else
- {
- if (filter_test == FTEST_NONE)
- {
- fprintf(stderr, "\nexim: %s received - message abandoned\n",
- (sig == SIGTERM)? "SIGTERM" : "SIGINT");
- log_write(0, LOG_MAIN, "%s received while reading local message",
- (sig == SIGTERM)? "SIGTERM" : "SIGINT");
- }
- }
-
-receive_bomb_out(US"signal-exit", msg); /* Does not return */
+had_data_sigint = sig;
}
{
recipient_item *oldlist = recipients_list;
int oldmax = recipients_list_max;
- recipients_list_max = recipients_list_max? 2*recipients_list_max : 50;
+ recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50;
recipients_list = store_get(recipients_list_max * sizeof(recipient_item));
if (oldlist != NULL)
memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item));
/* Handle the case when only EOF terminates the message */
-if (!dot_ends)
+if (!f.dot_ends)
{
register int last_ch = '\n';
{
message_size++;
if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
if (ch != '\r') ch_state = 1; else continue;
}
break;
if (ch == '.')
{
uschar c= ch;
- (void) cutthrough_puts(&c, 1);
+ cutthrough_data_puts(&c, 1);
}
ch_state = 1;
break;
message_size++;
body_linecount++;
if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
if (ch == '\r')
{
ch_state = 2;
if (message_size > thismessage_size_limit) return END_SIZE;
}
if(ch == '\n')
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
else
{
uschar c = ch;
- (void) cutthrough_puts(&c, 1);
+ cutthrough_data_puts(&c, 1);
}
}
for(;;)
{
- switch ((ch = (bdat_getc)(GETC_BUFFER_UNLIMITED)))
+ switch ((ch = bdat_getc(GETC_BUFFER_UNLIMITED)))
{
case EOF: return END_EOF;
case ERR: return END_PROTOCOL;
{
message_size++;
if (fout && fputc('\n', fout) == EOF) return END_WERROR;
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
if (ch == '\r') continue; /* don't write CR */
ch_state = MID_LINE;
}
if (message_size > thismessage_size_limit) return END_SIZE;
}
if(ch == '\n')
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
else
{
uschar c = ch;
- (void) cutthrough_puts(&c, 1);
+ cutthrough_data_puts(&c, 1);
}
}
/*NOTREACHED*/
}
+static int
+read_message_bdat_smtp_wire(FILE *fout)
+{
+int ch;
+
+/* Remember that this message uses wireformat. */
+
+DEBUG(D_receive) debug_printf("CHUNKING: %s\n",
+ fout ? "writing spoolfile in wire format" : "flushing input");
+f.spool_file_wireformat = TRUE;
+
+for (;;)
+ {
+ if (chunking_data_left > 0)
+ {
+ unsigned len = MAX(chunking_data_left, thismessage_size_limit - message_size + 1);
+ uschar * buf = bdat_getbuf(&len);
+
+ if (!buf) return END_EOF;
+ message_size += len;
+ if (fout && fwrite(buf, len, 1, fout) != 1) return END_WERROR;
+ }
+ else switch (ch = bdat_getc(GETC_BUFFER_UNLIMITED))
+ {
+ case EOF: return END_EOF;
+ case EOD: return END_DOT;
+ case ERR: return END_PROTOCOL;
+
+ default:
+ message_size++;
+ /*XXX not done:
+ linelength
+ max_received_linelength
+ body_linecount
+ body_zerocount
+ */
+ if (fout && fputc(ch, fout) == EOF) return END_WERROR;
+ break;
+ }
+ if (message_size > thismessage_size_limit) return END_SIZE;
+ }
+/*NOTREACHED*/
+}
+
void
receive_swallow_smtp(void)
{
-/*XXX CHUNKING: not enough. read chunks until RSET? */
if (message_ended >= END_NOTENDED)
- message_ended = read_message_data_smtp(NULL);
+ message_ended = chunking_state <= CHUNKING_OFFERED
+ ? read_message_data_smtp(NULL)
+ : read_message_bdat_smtp_wire(NULL);
}
else
fprintf(stderr, "exim: %s%s\n", text2, text1); /* Sic */
(void)fclose(f);
-exim_exit(error_rc);
+exim_exit(error_rc, US"");
}
case ACL_WHERE_DKIM:
case ACL_WHERE_MIME:
case ACL_WHERE_DATA:
- if (cutthrough.fd >= 0 && (acl_removed_headers || acl_added_headers))
+ if ( cutthrough.cctx.sock >= 0 && cutthrough.delivery
+ && (acl_removed_headers || acl_added_headers))
{
log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
" will not take effect on cutthrough deliveries");
}
}
-if (acl_removed_headers != NULL)
+if (acl_removed_headers)
{
DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers removed by %s ACL:\n", acl_name);
- for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
+ for (h = header_list; h; h = h->next) if (h->type != htype_old)
{
const uschar * list = acl_removed_headers;
int sep = ':'; /* This is specified as a colon-separated list */
DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
}
-if (acl_added_headers == NULL) return;
+if (!acl_added_headers) return;
DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers added by %s ACL:\n", acl_name);
-for (h = acl_added_headers; h != NULL; h = next)
+for (h = acl_added_headers; h; h = next)
{
next = h->next;
switch(h->type)
{
case htype_add_top:
- h->next = header_list;
- header_list = h;
- DEBUG(D_receive|D_acl) debug_printf_indent(" (at top)");
- break;
+ h->next = header_list;
+ header_list = h;
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (at top)");
+ break;
case htype_add_rec:
- if (last_received == NULL)
- {
- last_received = header_list;
- while (!header_testname(last_received, US"Received", 8, FALSE))
- last_received = last_received->next;
- while (last_received->next != NULL &&
- header_testname(last_received->next, US"Received", 8, FALSE))
- last_received = last_received->next;
- }
- h->next = last_received->next;
- last_received->next = h;
- DEBUG(D_receive|D_acl) debug_printf_indent(" (after Received:)");
- break;
+ if (!last_received)
+ {
+ last_received = header_list;
+ while (!header_testname(last_received, US"Received", 8, FALSE))
+ last_received = last_received->next;
+ while (last_received->next &&
+ header_testname(last_received->next, US"Received", 8, FALSE))
+ last_received = last_received->next;
+ }
+ h->next = last_received->next;
+ last_received->next = h;
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (after Received:)");
+ break;
case htype_add_rfc:
- /* add header before any header which is NOT Received: or Resent- */
- last_received = header_list;
- while ( (last_received->next != NULL) &&
- ( (header_testname(last_received->next, US"Received", 8, FALSE)) ||
- (header_testname_incomplete(last_received->next, US"Resent-", 7, FALSE)) ) )
- last_received = last_received->next;
- /* last_received now points to the last Received: or Resent-* header
- in an uninterrupted chain of those header types (seen from the beginning
- of all headers. Our current header must follow it. */
- h->next = last_received->next;
- last_received->next = h;
- DEBUG(D_receive|D_acl) debug_printf_indent(" (before any non-Received: or Resent-*: header)");
- break;
+ /* add header before any header which is NOT Received: or Resent- */
+ last_received = header_list;
+ while ( last_received->next &&
+ ( (header_testname(last_received->next, US"Received", 8, FALSE)) ||
+ (header_testname_incomplete(last_received->next, US"Resent-", 7, FALSE)) ) )
+ last_received = last_received->next;
+ /* last_received now points to the last Received: or Resent-* header
+ in an uninterrupted chain of those header types (seen from the beginning
+ of all headers. Our current header must follow it. */
+ h->next = last_received->next;
+ last_received->next = h;
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (before any non-Received: or Resent-*: header)");
+ break;
default:
- h->next = NULL;
- header_last->next = h;
- break;
+ h->next = NULL;
+ header_last->next = h;
+ DEBUG(D_receive|D_acl) debug_printf_indent(" ");
+ break;
}
- if (h->next == NULL) header_last = h;
+ if (!h->next) header_last = h;
/* Check for one of the known header types (From:, To:, etc.) though in
practice most added headers are going to be "other". Lower case
h->type = header_checkname(h, FALSE);
if (h->type >= 'a') h->type = htype_other;
- DEBUG(D_receive|D_acl) debug_printf_indent(" %s", header_last->text);
+ DEBUG(D_receive|D_acl) debug_printf("%s", h->text);
}
acl_added_headers = NULL;
Arguments:
s the dynamic string
- sizeptr points to the size variable
- ptrptr points to the pointer variable
Returns: the extended string
*/
-static uschar *
-add_host_info_for_log(uschar * s, int * sizeptr, int * ptrptr)
+static gstring *
+add_host_info_for_log(gstring * g)
{
if (sender_fullhost)
{
if (LOGGING(dnssec) && sender_host_dnssec) /*XXX sender_helo_dnssec? */
- s = string_cat(s, sizeptr, ptrptr, US" DS");
- s = string_append(s, sizeptr, ptrptr, 2, US" H=", sender_fullhost);
- if (LOGGING(incoming_interface) && interface_address != NULL)
- {
- s = string_cat(s, sizeptr, ptrptr,
- string_sprintf(" I=[%s]:%d", interface_address, interface_port));
- }
+ g = string_catn(g, US" DS", 3);
+ g = string_append(g, 2, US" H=", sender_fullhost);
+ if (LOGGING(incoming_interface) && interface_address)
+ g = string_fmt_append(g, " I=[%s]:%d", interface_address, interface_port);
}
-if (sender_ident != NULL)
- s = string_append(s, sizeptr, ptrptr, 2, US" U=", sender_ident);
-if (received_protocol != NULL)
- s = string_append(s, sizeptr, ptrptr, 2, US" P=", received_protocol);
-return s;
+if (f.tcp_in_fastopen && !f.tcp_in_fastopen_logged)
+ {
+ g = string_catn(g, US" TFO*", f.tcp_in_fastopen_data ? 5 : 4);
+ f.tcp_in_fastopen_logged = TRUE;
+ }
+if (sender_ident)
+ g = string_append(g, 2, US" U=", sender_ident);
+if (received_protocol)
+ g = string_append(g, 2, US" P=", received_protocol);
+if (LOGGING(pipelining) && f.smtp_in_pipelining_advertised)
+ {
+ g = string_catn(g, US" L", 2);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (f.smtp_in_early_pipe_used)
+ g = string_catn(g, US"*", 1);
+ else if (f.smtp_in_early_pipe_advertised)
+ g = string_catn(g, US".", 1);
+#endif
+ if (!f.smtp_in_pipelining_used)
+ g = string_catn(g, US"-", 1);
+ }
+return g;
}
uschar **blackholed_by_ptr)
{
FILE *mbox_file;
-uschar rfc822_file_path[2048];
+uschar * rfc822_file_path = NULL;
unsigned long mbox_size;
header_line *my_headerlist;
uschar *user_msg, *log_msg;
int mime_part_count_buffer = -1;
+uschar * mbox_filename;
int rc = OK;
-memset(CS rfc822_file_path,0,2048);
-
/* check if it is a MIME message */
-my_headerlist = header_list;
-while (my_headerlist != NULL)
- {
- /* skip deleted headers */
- if (my_headerlist->type == '*')
- {
- my_headerlist = my_headerlist->next;
- continue;
- }
- if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0)
+
+for (my_headerlist = header_list; my_headerlist; my_headerlist = my_headerlist->next)
+ if ( my_headerlist->type != '*' /* skip deleted headers */
+ && strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0
+ )
{
DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n");
goto DO_MIME_ACL;
}
- my_headerlist = my_headerlist->next;
- }
DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n");
return TRUE;
DO_MIME_ACL:
+
/* make sure the eml mbox file is spooled up */
-mbox_file = spool_mbox(&mbox_size, NULL);
-if (mbox_file == NULL) {
- /* error while spooling */
+if (!(mbox_file = spool_mbox(&mbox_size, NULL, &mbox_filename)))
+ { /* error while spooling */
log_write(0, LOG_MAIN|LOG_PANIC,
"acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
Uunlink(spool_name);
message_id[0] = 0; /* Indicate no message accepted */
*smtp_reply_ptr = US""; /* Indicate reply already sent */
return FALSE; /* Indicate skip to end of receive function */
-};
+ }
mime_is_rfc822 = 0;
rc = mime_acl_check(acl, mbox_file, NULL, &user_msg, &log_msg);
(void)fclose(mbox_file);
-if (Ustrlen(rfc822_file_path) > 0)
+if (rfc822_file_path)
{
mime_part_count = mime_part_count_buffer;
{
log_write(0, LOG_PANIC,
"acl_smtp_mime: can't unlink RFC822 spool file, skipping.");
- goto END_MIME_ACL;
+ goto END_MIME_ACL;
}
+ rfc822_file_path = NULL;
}
/* check if we must check any message/rfc822 attachments */
if (rc == OK)
{
- uschar temp_path[1024];
+ uschar * scandir = string_copyn(mbox_filename,
+ Ustrrchr(mbox_filename, '/') - mbox_filename);
struct dirent * entry;
DIR * tempdir;
- (void) string_format(temp_path, sizeof(temp_path), "%s/scan/%s",
- spool_directory, message_id);
-
- tempdir = opendir(CS temp_path);
- for (;;)
- {
- if (!(entry = readdir(tempdir)))
- break;
+ for (tempdir = opendir(CS scandir); entry = readdir(tempdir); )
if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
{
- (void) string_format(rfc822_file_path, sizeof(rfc822_file_path),
- "%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
- DEBUG(D_receive) debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
- rfc822_file_path);
+ rfc822_file_path = string_sprintf("%s/%s", scandir, entry->d_name);
+ DEBUG(D_receive)
+ debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
+ rfc822_file_path);
break;
}
- }
closedir(tempdir);
- if (entry)
+ if (rfc822_file_path)
{
if ((mbox_file = Ufopen(rfc822_file_path, "rb")))
{
{
recipients_count = 0;
*blackholed_by_ptr = US"MIME ACL";
+ cancel_cutthrough_connection(TRUE, US"mime acl discard");
}
else if (rc != OK)
{
Uunlink(spool_name);
+ cancel_cutthrough_connection(TRUE, US"mime acl not ok");
unspool_mbox();
#ifdef EXPERIMENTAL_DCC
dcc_ok = 0;
#endif
- if ( smtp_input
- && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+ if (smtp_input)
{
- *smtp_yield_ptr = FALSE; /* No more messages after dropped connection */
+ if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+ *smtp_yield_ptr = FALSE; /* No more messages after dropped connection */
*smtp_reply_ptr = US""; /* Indicate reply already sent */
}
message_id[0] = 0; /* Indicate no message accepted */
int rc = FAIL;
int msg_size = 0;
int process_info_len = Ustrlen(process_info);
-int error_rc = (error_handling == ERRORS_SENDER)?
- errors_sender_rc : EXIT_FAILURE;
+int error_rc = error_handling == ERRORS_SENDER
+ ? errors_sender_rc : EXIT_FAILURE;
int header_size = 256;
-int start, end, domain, size, sptr;
-int id_resolution;
+int start, end, domain;
+int id_resolution = 0;
int had_zero = 0;
int prevlines_length = 0;
-register int ptr = 0;
+int ptr = 0;
BOOL contains_resent_headers = FALSE;
BOOL extracted_ignored = FALSE;
uschar *frozen_by = NULL;
uschar *queued_by = NULL;
-uschar *errmsg, *s;
+uschar *errmsg;
+gstring * g;
struct stat statbuf;
/* Final message to give to SMTP caller, and messages from ACLs */
uschar *timestamp;
int tslen;
+
/* Release any open files that might have been cached while preparing to
accept the message - e.g. by verifying addresses - because reading a message
might take a fair bit of real time. */
cutthrough delivery with the no-spool option. It shouldn't be possible
to set up the combination, but just in case kill any ongoing connection. */
if (extract_recip || !smtp_input)
- cancel_cutthrough_connection("not smtp input");
+ cancel_cutthrough_connection(TRUE, US"not smtp input");
/* Initialize the chain of headers by setting up a place-holder for Received:
header. Temporarily mark it as "old", i.e. not to be used. We keep header_last
yet, initialize the size and warning count, and deal with no size limit. */
message_id[0] = 0;
-data_file = NULL;
+spool_data_file = NULL;
data_fd = -1;
spool_name = US"";
message_size = 0;
#ifndef DISABLE_DKIM
/* Call into DKIM to set up the context. In CHUNKING mode
we clear the dot-stuffing flag */
-if (smtp_input && !smtp_batched_input && !dkim_disable_verify)
+if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify)
dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
#endif
second, and for that we use the global variable received_time. This is for
things like ultimate message timeouts. */
-received_time = message_id_tv.tv_sec;
+received_time = message_id_tv;
/* If SMTP input, set the special handler for timeouts. The alarm() calls
happen in the smtp_getc() function when it refills its buffer. */
-if (smtp_input) os_non_restarting_signal(SIGALRM, data_timeout_handler);
+had_data_timeout = 0;
+if (smtp_input)
+ os_non_restarting_signal(SIGALRM, data_timeout_handler);
/* If not SMTP input, timeout happens only if configured, and we just set a
single timeout for the whole message. */
else if (receive_timeout > 0)
{
os_non_restarting_signal(SIGALRM, data_timeout_handler);
- alarm(receive_timeout);
+ ALARM(receive_timeout);
}
/* SIGTERM and SIGINT are caught always. */
+had_data_sigint = 0;
signal(SIGTERM, data_sigterm_sigint_handler);
signal(SIGINT, data_sigterm_sigint_handler);
(and sometimes lunatic messages can have ones that are 100s of K long) we
call store_release() for strings that have been copied - if the string is at
the start of a block (and therefore the only thing in it, because we aren't
- doing any other gets), the block gets freed. We can only do this because we
- know there are no other calls to store_get() going on. */
+ doing any other gets), the block gets freed. We can only do this release if
+ there were no allocations since the once that we want to free. */
if (ptr >= header_size - 4)
{
int oldsize = header_size;
- /* header_size += 256; */
+
+ if (header_size >= INT_MAX/2)
+ goto OVERSIZE;
header_size *= 2;
+
if (!store_extend(next->text, oldsize, header_size))
- {
- uschar *newtext = store_get(header_size);
- memcpy(newtext, next->text, ptr);
- store_release(next->text);
- next->text = newtext;
- }
+ next->text = store_newblock(next->text, header_size, ptr);
}
/* Cope with receiving a binary zero. There is dispute about whether
prevent further reading), and break out of the loop, having freed the
empty header, and set next = NULL to indicate no data line. */
- if (ptr == 0 && ch == '.' && (smtp_input || dot_ends))
+ if (ptr == 0 && ch == '.' && f.dot_ends)
{
ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
if (ch == '\r')
if (message_size >= header_maxsize)
{
+OVERSIZE:
next->text[ptr] = 0;
next->slen = ptr;
next->type = htype_other;
log_write(0, LOG_MAIN, "ridiculously long message header received from "
"%s (more than %d characters): message abandoned",
- sender_host_unknown? sender_ident : sender_fullhost, header_maxsize);
+ f.sender_host_unknown ? sender_ident : sender_fullhost, header_maxsize);
if (smtp_input)
{
if (nextch == ' ' || nextch == '\t')
{
next->text[ptr++] = nextch;
- message_size++;
+ if (++message_size >= header_maxsize)
+ goto OVERSIZE;
continue; /* Iterate the loop */
}
else if (nextch != EOF) (receive_ungetc)(nextch); /* For next time */
these lines in SMTP messages. There is now an option to ignore them from
specified hosts or networks. Sigh. */
- if (header_last == header_list &&
- (!smtp_input
- ||
- (sender_host_address != NULL &&
- verify_check_host(&ignore_fromline_hosts) == OK)
- ||
- (sender_host_address == NULL && ignore_fromline_local)
- ) &&
- regex_match_and_setup(regex_From, next->text, 0, -1))
+ if ( header_last == header_list
+ && ( !smtp_input
+ || ( sender_host_address
+ && verify_check_host(&ignore_fromline_hosts) == OK
+ )
+ || (!sender_host_address && ignore_fromline_local)
+ )
+ && regex_match_and_setup(regex_From, next->text, 0, -1)
+ )
{
- if (!sender_address_forced)
+ if (!f.sender_address_forced)
{
uschar *uucp_sender = expand_string(uucp_from_sender);
- if (uucp_sender == NULL)
- {
+ if (!uucp_sender)
log_write(0, LOG_MAIN|LOG_PANIC,
"expansion of \"%s\" failed after matching "
"\"From \" line: %s", uucp_from_sender, expand_string_message);
- }
else
{
int start, end, domain;
uschar *errmess;
uschar *newsender = parse_extract_address(uucp_sender, &errmess,
&start, &end, &domain, TRUE);
- if (newsender != NULL)
+ if (newsender)
{
if (domain == 0 && newsender[0] != 0)
newsender = rewrite_address_qualify(newsender, FALSE);
{
sender_address = newsender;
- if (trusted_caller || filter_test != FTEST_NONE)
+ if (f.trusted_caller || filter_test != FTEST_NONE)
{
authenticated_sender = NULL;
originator_name = US"";
- sender_local = FALSE;
+ f.sender_local = FALSE;
}
if (filter_test != FTEST_NONE)
{
log_write(0, LOG_MAIN, "overlong message header line received from "
"%s (more than %d characters): message abandoned",
- sender_host_unknown? sender_ident : sender_fullhost,
+ f.sender_host_unknown ? sender_ident : sender_fullhost,
header_line_maxsize);
if (smtp_input)
}
else
- {
give_local_error(ERRMESS_VLONGHDRLINE,
string_sprintf("message header line longer than %d characters "
"received: message not accepted", header_line_maxsize), US"",
error_rc, stdin, header_list->next);
/* Does not return */
- }
}
/* Note if any resent- fields exist. */
sender_address,
sender_fullhost ? " H=" : "", sender_fullhost ? sender_fullhost : US"",
sender_ident ? " U=" : "", sender_ident ? sender_ident : US"");
- smtp_printf("552 Message header not CRLF terminated\r\n");
+ smtp_printf("552 Message header not CRLF terminated\r\n", FALSE);
bdat_flush_data();
smtp_reply = US"";
goto TIDYUP; /* Skip to end of function */
switch (header_checkname(h, is_resent))
{
case htype_bcc:
- h->type = htype_bcc; /* Both Bcc: and Resent-Bcc: */
- break;
+ h->type = htype_bcc; /* Both Bcc: and Resent-Bcc: */
+ break;
case htype_cc:
- h->type = htype_cc; /* Both Cc: and Resent-Cc: */
- break;
+ h->type = htype_cc; /* Both Cc: and Resent-Cc: */
+ break;
- /* Record whether a Date: or Resent-Date: header exists, as appropriate. */
+ /* Record whether a Date: or Resent-Date: header exists, as appropriate. */
case htype_date:
- if (!resents_exist || is_resent) date_header_exists = TRUE;
- break;
+ if (!resents_exist || is_resent) date_header_exists = TRUE;
+ break;
- /* Same comments as about Return-Path: below. */
+ /* Same comments as about Return-Path: below. */
case htype_delivery_date:
- if (delivery_date_remove) h->type = htype_old;
- break;
+ if (delivery_date_remove) h->type = htype_old;
+ break;
- /* Same comments as about Return-Path: below. */
+ /* Same comments as about Return-Path: below. */
case htype_envelope_to:
- if (envelope_to_remove) h->type = htype_old;
- break;
+ if (envelope_to_remove) h->type = htype_old;
+ break;
- /* Mark all "From:" headers so they get rewritten. Save the one that is to
- be used for Sender: checking. For Sendmail compatibility, if the "From:"
- header consists of just the login id of the user who called Exim, rewrite
- it with the gecos field first. Apply this rule to Resent-From: if there
- are resent- fields. */
+ /* Mark all "From:" headers so they get rewritten. Save the one that is to
+ be used for Sender: checking. For Sendmail compatibility, if the "From:"
+ header consists of just the login id of the user who called Exim, rewrite
+ it with the gecos field first. Apply this rule to Resent-From: if there
+ are resent- fields. */
case htype_from:
- h->type = htype_from;
- if (!resents_exist || is_resent)
- {
- from_header = h;
- if (!smtp_input)
- {
- int len;
- uschar *s = Ustrchr(h->text, ':') + 1;
- while (isspace(*s)) s++;
- len = h->slen - (s - h->text) - 1;
- if (Ustrlen(originator_login) == len &&
- strncmpic(s, originator_login, len) == 0)
- {
- uschar *name = is_resent? US"Resent-From" : US"From";
- header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
- originator_login, qualify_domain_sender);
- from_header = header_last;
- h->type = htype_old;
- DEBUG(D_receive|D_rewrite)
- debug_printf("rewrote \"%s:\" header using gecos\n", name);
- }
- }
- }
- break;
+ h->type = htype_from;
+ if (!resents_exist || is_resent)
+ {
+ from_header = h;
+ if (!smtp_input)
+ {
+ int len;
+ uschar *s = Ustrchr(h->text, ':') + 1;
+ while (isspace(*s)) s++;
+ len = h->slen - (s - h->text) - 1;
+ if (Ustrlen(originator_login) == len &&
+ strncmpic(s, originator_login, len) == 0)
+ {
+ uschar *name = is_resent? US"Resent-From" : US"From";
+ header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
+ originator_login, qualify_domain_sender);
+ from_header = header_last;
+ h->type = htype_old;
+ DEBUG(D_receive|D_rewrite)
+ debug_printf("rewrote \"%s:\" header using gecos\n", name);
+ }
+ }
+ }
+ break;
- /* Identify the Message-id: header for generating "in-reply-to" in the
- autoreply transport. For incoming logging, save any resent- value. In both
- cases, take just the first of any multiples. */
+ /* Identify the Message-id: header for generating "in-reply-to" in the
+ autoreply transport. For incoming logging, save any resent- value. In both
+ cases, take just the first of any multiples. */
case htype_id:
- if (msgid_header == NULL && (!resents_exist || is_resent))
- {
- msgid_header = h;
- h->type = htype_id;
- }
- break;
+ if (!msgid_header && (!resents_exist || is_resent))
+ {
+ msgid_header = h;
+ h->type = htype_id;
+ }
+ break;
- /* Flag all Received: headers */
+ /* Flag all Received: headers */
case htype_received:
- h->type = htype_received;
- received_count++;
- break;
+ h->type = htype_received;
+ received_count++;
+ break;
- /* "Reply-to:" is just noted (there is no resent-reply-to field) */
+ /* "Reply-to:" is just noted (there is no resent-reply-to field) */
case htype_reply_to:
- h->type = htype_reply_to;
- break;
+ h->type = htype_reply_to;
+ break;
- /* The Return-path: header is supposed to be added to messages when
- they leave the SMTP system. We shouldn't receive messages that already
- contain Return-path. However, since Exim generates Return-path: on
- local delivery, resent messages may well contain it. We therefore
- provide an option (which defaults on) to remove any Return-path: headers
- on input. Removal actually means flagging as "old", which prevents the
- header being transmitted with the message. */
+ /* The Return-path: header is supposed to be added to messages when
+ they leave the SMTP system. We shouldn't receive messages that already
+ contain Return-path. However, since Exim generates Return-path: on
+ local delivery, resent messages may well contain it. We therefore
+ provide an option (which defaults on) to remove any Return-path: headers
+ on input. Removal actually means flagging as "old", which prevents the
+ header being transmitted with the message. */
case htype_return_path:
- if (return_path_remove) h->type = htype_old;
+ if (return_path_remove) h->type = htype_old;
- /* If we are testing a mail filter file, use the value of the
- Return-Path: header to set up the return_path variable, which is not
- otherwise set. However, remove any <> that surround the address
- because the variable doesn't have these. */
+ /* If we are testing a mail filter file, use the value of the
+ Return-Path: header to set up the return_path variable, which is not
+ otherwise set. However, remove any <> that surround the address
+ because the variable doesn't have these. */
- if (filter_test != FTEST_NONE)
- {
- uschar *start = h->text + 12;
- uschar *end = start + Ustrlen(start);
- while (isspace(*start)) start++;
- while (end > start && isspace(end[-1])) end--;
- if (*start == '<' && end[-1] == '>')
- {
- start++;
- end--;
- }
- return_path = string_copyn(start, end - start);
- printf("Return-path taken from \"Return-path:\" header line\n");
- }
- break;
+ if (filter_test != FTEST_NONE)
+ {
+ uschar *start = h->text + 12;
+ uschar *end = start + Ustrlen(start);
+ while (isspace(*start)) start++;
+ while (end > start && isspace(end[-1])) end--;
+ if (*start == '<' && end[-1] == '>')
+ {
+ start++;
+ end--;
+ }
+ return_path = string_copyn(start, end - start);
+ printf("Return-path taken from \"Return-path:\" header line\n");
+ }
+ break;
/* If there is a "Sender:" header and the message is locally originated,
and from an untrusted caller and suppress_local_fixups is not set, or if we
set.) */
case htype_sender:
- h->type = ((!active_local_sender_retain &&
- (
- (sender_local && !trusted_caller && !suppress_local_fixups)
- || submission_mode
- )
- ) &&
- (!resents_exist||is_resent))?
- htype_old : htype_sender;
- break;
+ h->type = !f.active_local_sender_retain
+ && ( f.sender_local && !f.trusted_caller && !f.suppress_local_fixups
+ || f.submission_mode
+ )
+ && (!resents_exist || is_resent)
+ ? htype_old : htype_sender;
+ break;
- /* Remember the Subject: header for logging. There is no Resent-Subject */
+ /* Remember the Subject: header for logging. There is no Resent-Subject */
case htype_subject:
- subject_header = h;
- break;
+ subject_header = h;
+ break;
- /* "To:" gets flagged, and the existence of a recipient header is noted,
- whether it's resent- or not. */
+ /* "To:" gets flagged, and the existence of a recipient header is noted,
+ whether it's resent- or not. */
case htype_to:
- h->type = htype_to;
- /****
- to_or_cc_header_exists = TRUE;
- ****/
- break;
+ h->type = htype_to;
+ /****
+ to_or_cc_header_exists = TRUE;
+ ****/
+ break;
}
}
uschar *s = Ustrchr(h->text, ':') + 1;
while (isspace(*s)) s++;
- parse_allow_group = TRUE; /* Allow address group syntax */
+ f.parse_allow_group = TRUE; /* Allow address group syntax */
while (*s != 0)
{
/* Check on maximum */
if (recipients_max > 0 && ++rcount > recipients_max)
- {
give_local_error(ERRMESS_TOOMANYRECIP, US"too many recipients",
US"message rejected: ", error_rc, stdin, NULL);
/* Does not return */
- }
/* Make a copy of the address, and remove any internal newlines. These
may be present as a result of continuations of the header line. The
while (isspace(*s)) s++;
} /* Next address */
- parse_allow_group = FALSE; /* Reset group syntax flags */
- parse_found_group = FALSE;
+ f.parse_allow_group = FALSE; /* Reset group syntax flags */
+ f.parse_found_group = FALSE;
/* If this was the bcc: header, mark it "old", which means it
will be kept on the spool, but not transmitted as part of the
NOTE: If ever the format of message ids is changed, the regular expression for
checking that a string is in this format must be updated in a corresponding
way. It appears in the initializing code in exim.c. The macro MESSAGE_ID_LENGTH
-must also be changed to reflect the correct string length. Then, of course,
-other programs that rely on the message id format will need updating too. */
+must also be changed to reflect the correct string length. The queue-sort code
+needs to know the layout. Then, of course, other programs that rely on the
+message id format will need updating too. */
Ustrncpy(message_id, string_base62((long int)(message_id_tv.tv_sec)), 6);
message_id[6] = '-';
left in id_resolution so that an appropriate wait can be done after receiving
the message, if necessary (we hope it won't be). */
-if (host_number_string != NULL)
+if (host_number_string)
{
- id_resolution = (BASE_62 == 62)? 5000 : 10000;
+ id_resolution = BASE_62 == 62 ? 5000 : 10000;
sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
string_base62((long int)(
host_number * (1000000/id_resolution) +
else
{
- id_resolution = (BASE_62 == 62)? 500 : 1000;
+ id_resolution = BASE_62 == 62 ? 500 : 1000;
sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
string_base62((long int)(message_id_tv.tv_usec/id_resolution)) + 4);
}
messages. This can be user-configured if required, but we had better flatten
any illegal characters therein. */
-if (msgid_header == NULL &&
- ((sender_host_address == NULL && !suppress_local_fixups)
- || submission_mode))
+if ( !msgid_header
+ && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
{
uschar *p;
uschar *id_text = US"";
/* Permit only letters, digits, dots, and hyphens in the domain */
- if (message_id_domain != NULL)
+ if (message_id_domain)
{
uschar *new_id_domain = expand_string(message_id_domain);
- if (new_id_domain == NULL)
+ if (!new_id_domain)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
log_write(0, LOG_MAIN|LOG_PANIC,
"expansion of \"%s\" (message_id_header_domain) "
"failed: %s", message_id_domain, expand_string_message);
}
- else if (*new_id_domain != 0)
+ else if (*new_id_domain)
{
id_domain = new_id_domain;
- for (p = id_domain; *p != 0; p++)
+ for (p = id_domain; *p; p++)
if (!isalnum(*p) && *p != '.') *p = '-'; /* No need to test '-' ! */
}
}
/* Permit all characters except controls and RFC 2822 specials in the
additional text part. */
- if (message_id_text != NULL)
+ if (message_id_text)
{
uschar *new_id_text = expand_string(message_id_text);
- if (new_id_text == NULL)
+ if (!new_id_text)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
log_write(0, LOG_MAIN|LOG_PANIC,
"expansion of \"%s\" (message_id_header_text) "
"failed: %s", message_id_text, expand_string_message);
}
- else if (*new_id_text != 0)
+ else if (*new_id_text)
{
id_text = new_id_text;
- for (p = id_text; *p != 0; p++)
- if (mac_iscntrl_or_special(*p)) *p = '-';
+ for (p = id_text; *p; p++) if (mac_iscntrl_or_special(*p)) *p = '-';
}
}
untrusted user to set anything in the envelope (which might then get info
From:) but we still want to ensure a valid Sender: if it is required. */
-if (from_header == NULL &&
- ((sender_host_address == NULL && !suppress_local_fixups)
- || submission_mode))
+if ( !from_header
+ && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
{
uschar *oname = US"";
force its value or if we have a non-SMTP message for which -f was not used
to set the sender. */
- if (sender_host_address == NULL)
+ if (!sender_host_address)
{
- if (!trusted_caller || sender_name_forced ||
- (!smtp_input && !sender_address_forced))
+ if (!f.trusted_caller || f.sender_name_forced ||
+ (!smtp_input && !f.sender_address_forced))
oname = originator_name;
}
/* For non-locally submitted messages, the only time we use the originator
name is when it was forced by the /name= option on control=submission. */
- else
- {
- if (submission_name != NULL) oname = submission_name;
- }
+ else if (submission_name) oname = submission_name;
/* Envelope sender is empty */
- if (sender_address[0] == 0)
+ if (!*sender_address)
{
uschar *fromstart, *fromend;
- fromstart = string_sprintf("%sFrom: %s%s", resent_prefix,
- oname, (oname[0] == 0)? "" : " <");
- fromend = (oname[0] == 0)? US"" : US">";
+ fromstart = string_sprintf("%sFrom: %s%s",
+ resent_prefix, oname, *oname ? " <" : "");
+ fromend = *oname ? US">" : US"";
- if (sender_local || local_error_message)
- {
+ if (f.sender_local || f.local_error_message)
header_add(htype_from, "%s%s@%s%s\n", fromstart,
local_part_quote(originator_login), qualify_domain_sender,
fromend);
- }
- else if (submission_mode && authenticated_id != NULL)
+
+ else if (f.submission_mode && authenticated_id)
{
- if (submission_domain == NULL)
- {
+ if (!submission_domain)
header_add(htype_from, "%s%s@%s%s\n", fromstart,
local_part_quote(authenticated_id), qualify_domain_sender,
fromend);
- }
- else if (submission_domain[0] == 0) /* empty => whole address set */
- {
+
+ else if (!*submission_domain) /* empty => whole address set */
header_add(htype_from, "%s%s%s\n", fromstart, authenticated_id,
fromend);
- }
+
else
- {
header_add(htype_from, "%s%s@%s%s\n", fromstart,
- local_part_quote(authenticated_id), submission_domain,
- fromend);
- }
+ local_part_quote(authenticated_id), submission_domain, fromend);
+
from_header = header_last; /* To get it checked for Sender: */
}
}
{
header_add(htype_from, "%sFrom: %s%s%s%s\n", resent_prefix,
oname,
- (oname[0] == 0)? "" : " <",
- (sender_address_unrewritten == NULL)?
- sender_address : sender_address_unrewritten,
- (oname[0] == 0)? "" : ">");
+ *oname ? " <" : "",
+ sender_address_unrewritten ? sender_address_unrewritten : sender_address,
+ *oname ? ">" : "");
from_header = header_last; /* To get it checked for Sender: */
}
here. If the From: header contains more than one address, then the call to
parse_extract_address fails, and a Sender: header is inserted, as required. */
-if (from_header != NULL &&
- (active_local_from_check &&
- ((sender_local && !trusted_caller && !suppress_local_fixups) ||
- (submission_mode && authenticated_id != NULL))
- ))
+if ( from_header
+ && ( f.active_local_from_check
+ && ( f.sender_local && !f.trusted_caller && !f.suppress_local_fixups
+ || f.submission_mode && authenticated_id
+ ) ) )
{
BOOL make_sender = TRUE;
int start, end, domain;
&start, &end, &domain, FALSE);
uschar *generated_sender_address;
- if (submission_mode)
- {
- if (submission_domain == NULL)
- {
- generated_sender_address = string_sprintf("%s@%s",
- local_part_quote(authenticated_id), qualify_domain_sender);
- }
- else if (submission_domain[0] == 0) /* empty => full address */
- {
- generated_sender_address = string_sprintf("%s",
- authenticated_id);
- }
- else
- {
- generated_sender_address = string_sprintf("%s@%s",
- local_part_quote(authenticated_id), submission_domain);
- }
- }
- else
- generated_sender_address = string_sprintf("%s@%s",
- local_part_quote(originator_login), qualify_domain_sender);
+ generated_sender_address = f.submission_mode
+ ? !submission_domain
+ ? string_sprintf("%s@%s",
+ local_part_quote(authenticated_id), qualify_domain_sender)
+ : !*submission_domain /* empty => full address */
+ ? string_sprintf("%s", authenticated_id)
+ : string_sprintf("%s@%s",
+ local_part_quote(authenticated_id), submission_domain)
+ : string_sprintf("%s@%s",
+ local_part_quote(originator_login), qualify_domain_sender);
/* Remove permitted prefixes and suffixes from the local part of the From:
address before doing the comparison with the generated sender. */
- if (from_address != NULL)
+ if (from_address)
{
int slen;
- uschar *at = (domain == 0)? NULL : from_address + domain - 1;
+ uschar *at = domain ? from_address + domain - 1 : NULL;
- if (at != NULL) *at = 0;
+ if (at) *at = 0;
from_address += route_check_prefix(from_address, local_from_prefix);
slen = route_check_suffix(from_address, local_from_suffix);
if (slen > 0)
memmove(from_address+slen, from_address, Ustrlen(from_address)-slen);
from_address += slen;
}
- if (at != NULL) *at = '@';
+ if (at) *at = '@';
- if (strcmpic(generated_sender_address, from_address) == 0 ||
- (domain == 0 && strcmpic(from_address, originator_login) == 0))
+ if ( strcmpic(generated_sender_address, from_address) == 0
+ || (!domain && strcmpic(from_address, originator_login) == 0))
make_sender = FALSE;
}
appropriate rewriting rules. */
if (make_sender)
- {
- if (submission_mode && submission_name == NULL)
+ if (f.submission_mode && !submission_name)
header_add(htype_sender, "%sSender: %s\n", resent_prefix,
generated_sender_address);
else
header_add(htype_sender, "%sSender: %s <%s>\n",
resent_prefix,
- submission_mode? submission_name : originator_name,
+ f.submission_mode ? submission_name : originator_name,
generated_sender_address);
- }
/* Ensure that a non-null envelope sender address corresponds to the
submission mode sender address. */
- if (submission_mode && sender_address[0] != 0)
+ if (f.submission_mode && *sender_address)
{
- if (sender_address_unrewritten == NULL)
+ if (!sender_address_unrewritten)
sender_address_unrewritten = sender_address;
sender_address = generated_sender_address;
if (Ustrcmp(sender_address_unrewritten, generated_sender_address) != 0)
/* If there are any rewriting rules, apply them to the sender address, unless
it has already been rewritten as part of verification for SMTP input. */
-if (global_rewrite_rules != NULL && sender_address_unrewritten == NULL &&
- sender_address[0] != 0)
+if (global_rewrite_rules && !sender_address_unrewritten && *sender_address)
{
sender_address = rewrite_address(sender_address, FALSE, TRUE,
global_rewrite_rules, rewrite_existflags);
As per Message-Id, we prepend if resending, else append.
*/
-if (!date_header_exists &&
- ((sender_host_address == NULL && !suppress_local_fixups)
- || submission_mode))
+if ( !date_header_exists
+ && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
header_add_at_position(!resents_exist, NULL, FALSE, htype_other,
"%sDate: %s\n", resent_prefix, tod_stamp(tod_full));
DEBUG(D_receive)
{
debug_printf(">>Headers after rewriting and local additions:\n");
- for (h = header_list->next; h != NULL; h = h->next)
+ for (h = header_list->next; h; h = h->next)
debug_printf("%c %s", h->type, h->text);
debug_printf("\n");
}
Could we do onward CHUNKING given inbound CHUNKING?
*/
if (chunking_state > CHUNKING_OFFERED)
- cancel_cutthrough_connection("chunking active");
+ cancel_cutthrough_connection(FALSE, US"chunking active");
/* Cutthrough delivery:
We have to create the Received header now rather than at the end of reception,
so the timestamp behaviour is a change to the normal case.
-XXX Ensure this gets documented XXX.
Having created it, send the headers to the destination. */
-if (cutthrough.fd >= 0)
+
+if (cutthrough.cctx.sock >= 0 && cutthrough.delivery)
{
if (received_count > received_headers_max)
{
- cancel_cutthrough_connection("too many headers");
+ cancel_cutthrough_connection(TRUE, US"too many headers");
if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */
log_write(0, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
"Too many \"Received\" headers",
sender_address,
- (sender_fullhost == NULL)? "" : " H=",
- (sender_fullhost == NULL)? US"" : sender_fullhost,
- (sender_ident == NULL)? "" : " U=",
- (sender_ident == NULL)? US"" : sender_ident);
+ sender_fullhost ? "H=" : "", sender_fullhost ? sender_fullhost : US"",
+ sender_ident ? "U=" : "", sender_ident ? sender_ident : US"");
message_id[0] = 0; /* Indicate no message accepted */
smtp_reply = US"550 Too many \"Received\" headers - suspected mail loop";
goto TIDYUP; /* Skip to end of function */
are problems when Exim is run under Cygwin (I'm told). See comments in
spool_in.c, where the same locking is done. */
-data_file = fdopen(data_fd, "w+");
+spool_data_file = fdopen(data_fd, "w+");
lock_data.l_type = F_WRLCK;
lock_data.l_whence = SEEK_SET;
lock_data.l_start = 0;
format); write it (remembering that it might contain binary zeros). The result
of fwrite() isn't inspected; instead we call ferror() below. */
-fprintf(data_file, "%s-D\n", message_id);
-if (next != NULL)
+fprintf(spool_data_file, "%s-D\n", message_id);
+if (next)
{
uschar *s = next->text;
int len = next->slen;
- len = fwrite(s, 1, len, data_file); len = len; /* compiler quietening */
- body_linecount++; /* Assumes only 1 line */
+ if (fwrite(s, 1, len, spool_data_file) == len) /* "if" for compiler quietening */
+ body_linecount++; /* Assumes only 1 line */
}
/* Note that we might already be at end of file, or the logical end of file
(indicated by '.'), or might have encountered an error while writing the
message id or "next" line. */
-if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
+if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
{
if (smtp_input)
{
- message_ended = chunking_state > CHUNKING_OFFERED
- ? read_message_bdat_smtp(data_file)
- : read_message_data_smtp(data_file);
+ message_ended = chunking_state <= CHUNKING_OFFERED
+ ? read_message_data_smtp(spool_data_file)
+ : spool_wireformat
+ ? read_message_bdat_smtp_wire(spool_data_file)
+ : read_message_bdat_smtp(spool_data_file);
receive_linecount++; /* The terminating "." line */
}
- else message_ended = read_message_data(data_file);
+ else
+ message_ended = read_message_data(spool_data_file);
receive_linecount += body_linecount; /* For BSMTP errors mainly */
message_linecount += body_linecount;
if (smtp_input)
{
Uunlink(spool_name); /* Lose data file when closed */
- cancel_cutthrough_connection("sender closed connection");
+ cancel_cutthrough_connection(TRUE, US"sender closed connection");
message_id[0] = 0; /* Indicate no message accepted */
smtp_reply = handle_lost_connection(US"");
smtp_yield = FALSE;
case END_SIZE:
Uunlink(spool_name); /* Lose the data file when closed */
- cancel_cutthrough_connection("mail too big");
+ cancel_cutthrough_connection(TRUE, US"mail too big");
if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */
log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
"message too big: read=%d max=%d",
sender_address,
- (sender_fullhost == NULL)? "" : " H=",
- (sender_fullhost == NULL)? US"" : sender_fullhost,
- (sender_ident == NULL)? "" : " U=",
- (sender_ident == NULL)? US"" : sender_ident,
+ sender_fullhost ? " H=" : "",
+ sender_fullhost ? sender_fullhost : US"",
+ sender_ident ? " U=" : "",
+ sender_ident ? sender_ident : US"",
message_size,
thismessage_size_limit);
}
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
give_local_error(ERRMESS_TOOBIG,
string_sprintf("message too big (max=%d)", thismessage_size_limit),
- US"message rejected: ", error_rc, data_file, header_list);
+ US"message rejected: ", error_rc, spool_data_file, header_list);
/* Does not return */
}
break;
case END_PROTOCOL:
Uunlink(spool_name); /* Lose the data file when closed */
- cancel_cutthrough_connection("sender protocol error");
+ cancel_cutthrough_connection(TRUE, US"sender protocol error");
smtp_reply = US""; /* Response already sent */
message_id[0] = 0; /* Indicate no message accepted */
goto TIDYUP; /* Skip to end of function */
the input in cases of output errors, since the far end doesn't expect to see
anything until the terminating dot line is sent. */
-if (fflush(data_file) == EOF || ferror(data_file) ||
- EXIMfsync(fileno(data_file)) < 0 || (receive_ferror)())
+if (fflush(spool_data_file) == EOF || ferror(spool_data_file) ||
+ EXIMfsync(fileno(spool_data_file)) < 0 || (receive_ferror)())
{
uschar *msg_errno = US strerror(errno);
BOOL input_error = (receive_ferror)() != 0;
uschar *msg = string_sprintf("%s error (%s) while receiving message from %s",
input_error? "Input read" : "Spool write",
msg_errno,
- (sender_fullhost != NULL)? sender_fullhost : sender_ident);
+ sender_fullhost ? sender_fullhost : sender_ident);
log_write(0, LOG_MAIN, "Message abandoned: %s", msg);
Uunlink(spool_name); /* Lose the data file */
- cancel_cutthrough_connection("error writing spoolfile");
+ cancel_cutthrough_connection(TRUE, US"error writing spoolfile");
if (smtp_input)
{
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
- give_local_error(ERRMESS_IOERR, msg, US"", error_rc, data_file,
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ give_local_error(ERRMESS_IOERR, msg, US"", error_rc, spool_data_file,
header_list);
/* Does not return */
}
/* No I/O errors were encountered while writing the data file. */
DEBUG(D_receive) debug_printf("Data file written for message %s\n", message_id);
+if (LOGGING(receive_time)) timesince(&received_time_taken, &received_time);
/* If there were any bad addresses extracted by -t, or there were no recipients
exit. (This can't be SMTP, which always ensures there's at least one
syntactically good recipient address.) */
-if (extract_recip && (bad_addresses != NULL || recipients_count == 0))
+if (extract_recip && (bad_addresses || recipients_count == 0))
{
DEBUG(D_receive)
{
if (recipients_count == 0) debug_printf("*** No recipients\n");
- if (bad_addresses != NULL)
+ if (bad_addresses)
{
- error_block *eblock = bad_addresses;
+ error_block * eblock;
debug_printf("*** Bad address(es)\n");
- while (eblock != NULL)
- {
+ for (eblock = bad_addresses; eblock; eblock = eblock->next)
debug_printf(" %s: %s\n", eblock->text1, eblock->text2);
- eblock = eblock->next;
- }
}
}
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ log_write(0, LOG_MAIN|LOG_PANIC, "%s %s found in headers",
+ message_id, bad_addresses ? "bad addresses" : "no recipients");
+
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
/* If configured to send errors to the sender, but this fails, force
a failure error code. We use a special one for no recipients so that it
if (error_handling == ERRORS_SENDER)
{
if (!moan_to_sender(
- (bad_addresses == NULL)?
- (extracted_ignored? ERRMESS_IGADDRESS : ERRMESS_NOADDRESS) :
- (recipients_list == NULL)? ERRMESS_BADNOADDRESS : ERRMESS_BADADDRESS,
- bad_addresses, header_list, data_file, FALSE))
- error_rc = (bad_addresses == NULL)? EXIT_NORECIPIENTS : EXIT_FAILURE;
+ bad_addresses
+ ? recipients_list ? ERRMESS_BADADDRESS : ERRMESS_BADNOADDRESS
+ : extracted_ignored ? ERRMESS_IGADDRESS : ERRMESS_NOADDRESS,
+ bad_addresses, header_list, spool_data_file, FALSE
+ ) )
+ error_rc = bad_addresses ? EXIT_FAILURE : EXIT_NORECIPIENTS;
}
else
{
- if (bad_addresses == NULL)
- {
+ if (!bad_addresses)
if (extracted_ignored)
fprintf(stderr, "exim: all -t recipients overridden by command line\n");
else
fprintf(stderr, "exim: no recipients in message\n");
- }
else
{
fprintf(stderr, "exim: invalid address%s",
- (bad_addresses->next == NULL)? ":" : "es:\n");
- while (bad_addresses != NULL)
- {
+ bad_addresses->next ? "es:\n" : ":");
+ for ( ; bad_addresses; bad_addresses = bad_addresses->next)
fprintf(stderr, " %s: %s\n", bad_addresses->text1,
bad_addresses->text2);
- bad_addresses = bad_addresses->next;
- }
}
}
if (recipients_count == 0 || error_handling == ERRORS_STDERR)
{
Uunlink(spool_name);
- (void)fclose(data_file);
- exim_exit(error_rc);
+ (void)fclose(spool_data_file);
+ exim_exit(error_rc, US"receiving");
}
}
code. */
/*XXX eventually add excess Received: check for cutthrough case back when classifying them */
-if (received_header->text == NULL) /* Non-cutthrough case */
+if (!received_header->text) /* Non-cutthrough case */
{
received_header_gen();
deliver_datafile = data_fd;
user_msg = NULL;
-enable_dollar_recipients = TRUE;
+f.enable_dollar_recipients = TRUE;
if (recipients_count == 0)
- blackholed_by = recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
+ blackholed_by = f.recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
else
{
{
#ifndef DISABLE_DKIM
- if (!dkim_disable_verify)
+ if (!f.dkim_disable_verify)
{
- /* Finish verification, this will log individual signature results to
- the mainlog */
+ /* Finish verification */
dkim_exim_verify_finish();
/* Check if we must run the DKIM ACL */
if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
{
- uschar *dkim_verify_signers_expanded =
+ uschar * dkim_verify_signers_expanded =
expand_string(dkim_verify_signers);
- if (!dkim_verify_signers_expanded)
+ gstring * results = NULL;
+ int signer_sep = 0;
+ const uschar * ptr;
+ uschar * item;
+ gstring * seen_items = NULL;
+ int old_pool = store_pool;
+
+ store_pool = POOL_PERM; /* Allow created variables to live to data ACL */
+
+ if (!(ptr = dkim_verify_signers_expanded))
log_write(0, LOG_MAIN|LOG_PANIC,
"expansion of dkim_verify_signers option failed: %s",
expand_string_message);
- else
- {
- int sep = 0;
- const uschar *ptr = dkim_verify_signers_expanded;
- uschar *item = NULL;
- uschar *seen_items = NULL;
- int seen_items_size = 0;
- int seen_items_offset = 0;
- /* Default to OK when no items are present */
- rc = OK;
- while ((item = string_nextinlist(&ptr, &sep, NULL, 0)))
- {
- /* Prevent running ACL for an empty item */
- if (!item || !*item) continue;
-
- /* Only run ACL once for each domain or identity,
- no matter how often it appears in the expanded list. */
- if (seen_items)
- {
- uschar *seen_item = NULL;
- const uschar *seen_items_list = seen_items;
- BOOL seen_this_item = FALSE;
-
- while ((seen_item = string_nextinlist(&seen_items_list, &sep,
- NULL, 0)))
- if (Ustrcmp(seen_item,item) == 0)
- {
- seen_this_item = TRUE;
- break;
- }
-
- if (seen_this_item)
- {
- DEBUG(D_receive)
- debug_printf("acl_smtp_dkim: skipping signer %s, "
- "already seen\n", item);
- continue;
- }
-
- seen_items = string_append(seen_items, &seen_items_size,
- &seen_items_offset, 1, ":");
- }
-
- seen_items = string_append(seen_items, &seen_items_size,
- &seen_items_offset, 1, item);
- seen_items[seen_items_offset] = '\0';
-
- DEBUG(D_receive)
- debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n",
- item);
-
- dkim_exim_acl_setup(item);
- rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim,
- &user_msg, &log_msg);
-
- if (rc != OK)
+ /* Default to OK when no items are present */
+ rc = OK;
+ while ((item = string_nextinlist(&ptr, &signer_sep, NULL, 0)))
+ {
+ /* Prevent running ACL for an empty item */
+ if (!item || !*item) continue;
+
+ /* Only run ACL once for each domain or identity,
+ no matter how often it appears in the expanded list. */
+ if (seen_items)
+ {
+ uschar * seen_item;
+ const uschar * seen_items_list = string_from_gstring(seen_items);
+ int seen_sep = ':';
+ BOOL seen_this_item = FALSE;
+
+ while ((seen_item = string_nextinlist(&seen_items_list, &seen_sep,
+ NULL, 0)))
+ if (Ustrcmp(seen_item,item) == 0)
+ {
+ seen_this_item = TRUE;
+ break;
+ }
+
+ if (seen_this_item)
{
DEBUG(D_receive)
- debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
- "skipping remaining items\n", rc, item);
- cancel_cutthrough_connection("dkim acl not ok");
- break;
+ debug_printf("acl_smtp_dkim: skipping signer %s, "
+ "already seen\n", item);
+ continue;
}
- }
- add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
- if (rc == DISCARD)
- {
- recipients_count = 0;
- blackholed_by = US"DKIM ACL";
- if (log_msg != NULL)
- blackhole_log_msg = string_sprintf(": %s", log_msg);
- }
- else if (rc != OK)
- {
- Uunlink(spool_name);
- if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
- smtp_yield = FALSE; /* No more messages after dropped connection */
- smtp_reply = US""; /* Indicate reply already sent */
- message_id[0] = 0; /* Indicate no message accepted */
- goto TIDYUP; /* Skip to end of function */
- }
- }
+
+ seen_items = string_catn(seen_items, US":", 1);
+ }
+ seen_items = string_cat(seen_items, item);
+
+ rc = dkim_exim_acl_run(item, &results, &user_msg, &log_msg);
+ if (rc != OK)
+ {
+ DEBUG(D_receive)
+ debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
+ "skipping remaining items\n", rc, item);
+ cancel_cutthrough_connection(TRUE, US"dkim acl not ok");
+ break;
+ }
+ }
+ dkim_verify_status = string_from_gstring(results);
+ store_pool = old_pool;
+ add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
+ if (rc == DISCARD)
+ {
+ recipients_count = 0;
+ blackholed_by = US"DKIM ACL";
+ if (log_msg)
+ blackhole_log_msg = string_sprintf(": %s", log_msg);
+ }
+ else if (rc != OK)
+ {
+ Uunlink(spool_name);
+ if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
+ smtp_yield = FALSE; /* No more messages after dropped connection */
+ smtp_reply = US""; /* Indicate reply already sent */
+ message_id[0] = 0; /* Indicate no message accepted */
+ goto TIDYUP; /* Skip to end of function */
+ }
}
+ else
+ dkim_exim_verify_log_all();
}
#endif /* DISABLE_DKIM */
#ifdef WITH_CONTENT_SCAN
- if (recipients_count > 0 &&
- acl_smtp_mime != NULL &&
- !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by))
+ if ( recipients_count > 0
+ && acl_smtp_mime
+ && !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by)
+ )
goto TIDYUP;
#endif /* WITH_CONTENT_SCAN */
int all_pass = OK;
int all_fail = FAIL;
- smtp_printf("353 PRDR content analysis beginning\r\n");
+ smtp_printf("353 PRDR content analysis beginning\r\n", TRUE);
/* Loop through recipients, responses must be in same order received */
for (c = 0; recipients_count > c; c++)
{
{
recipients_count = 0;
blackholed_by = US"DATA ACL";
- if (log_msg != NULL)
+ if (log_msg)
blackhole_log_msg = string_sprintf(": %s", log_msg);
- cancel_cutthrough_connection("data acl discard");
+ cancel_cutthrough_connection(TRUE, US"data acl discard");
}
else if (rc != OK)
{
Uunlink(spool_name);
- cancel_cutthrough_connection("data acl not ok");
+ cancel_cutthrough_connection(TRUE, US"data acl not ok");
#ifdef WITH_CONTENT_SCAN
unspool_mbox();
#endif
{
#ifdef WITH_CONTENT_SCAN
- if (acl_not_smtp_mime != NULL &&
- !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply,
- &blackholed_by))
+ if ( acl_not_smtp_mime
+ && !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply,
+ &blackholed_by)
+ )
goto TIDYUP;
#endif /* WITH_CONTENT_SCAN */
- if (acl_not_smtp != NULL)
+ if (acl_not_smtp)
{
uschar *user_msg, *log_msg;
+ f.authentication_local = TRUE;
rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg);
if (rc == DISCARD)
{
recipients_count = 0;
blackholed_by = US"non-SMTP ACL";
- if (log_msg != NULL)
+ if (log_msg)
blackhole_log_msg = string_sprintf(": %s", log_msg);
}
else if (rc != OK)
/* The ACL can specify where rejections are to be logged, possibly
nowhere. The default is main and reject logs. */
- if (log_reject_target != 0)
+ if (log_reject_target)
log_write(0, log_reject_target, "F=<%s> rejected by non-SMTP ACL: %s",
sender_address, log_msg);
- if (user_msg == NULL) user_msg = US"local configuration problem";
+ if (!user_msg) user_msg = US"local configuration problem";
if (smtp_batched_input)
- {
moan_smtp_batch(NULL, "%d %s", 550, user_msg);
/* Does not return */
- }
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
give_local_error(ERRMESS_LOCAL_ACL, user_msg,
- US"message rejected by non-SMTP ACL: ", error_rc, data_file,
+ US"message rejected by non-SMTP ACL: ", error_rc, spool_data_file,
header_list);
/* Does not return */
}
/* The applicable ACLs have been run */
- if (deliver_freeze) frozen_by = US"ACL"; /* for later logging */
- if (queue_only_policy) queued_by = US"ACL";
+ if (f.deliver_freeze) frozen_by = US"ACL"; /* for later logging */
+ if (f.queue_only_policy) queued_by = US"ACL";
}
#ifdef WITH_CONTENT_SCAN
#endif
+#ifdef HAVE_LOCAL_SCAN
/* The final check on the message is to run the scan_local() function. The
version supplied with Exim always accepts, but this is a hook for sysadmins to
supply their own checking code. The local_scan() function is run even when all
/* Arrange to catch crashes in local_scan(), so that the -D file gets
deleted, and the incident gets logged. */
-os_non_restarting_signal(SIGSEGV, local_scan_crash_handler);
-os_non_restarting_signal(SIGFPE, local_scan_crash_handler);
-os_non_restarting_signal(SIGILL, local_scan_crash_handler);
-os_non_restarting_signal(SIGBUS, local_scan_crash_handler);
-
-DEBUG(D_receive) debug_printf("calling local_scan(); timeout=%d\n",
- local_scan_timeout);
-local_scan_data = NULL;
-
-os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
-if (local_scan_timeout > 0) alarm(local_scan_timeout);
-rc = local_scan(data_fd, &local_scan_data);
-alarm(0);
-os_non_restarting_signal(SIGALRM, sigalrm_handler);
-
-enable_dollar_recipients = FALSE;
-
-store_pool = POOL_MAIN; /* In case changed */
-DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
- local_scan_data);
-
-os_non_restarting_signal(SIGSEGV, SIG_DFL);
-os_non_restarting_signal(SIGFPE, SIG_DFL);
-os_non_restarting_signal(SIGILL, SIG_DFL);
-os_non_restarting_signal(SIGBUS, SIG_DFL);
+if (sigsetjmp(local_scan_env, 1) == 0)
+ {
+ had_local_scan_crash = 0;
+ os_non_restarting_signal(SIGSEGV, local_scan_crash_handler);
+ os_non_restarting_signal(SIGFPE, local_scan_crash_handler);
+ os_non_restarting_signal(SIGILL, local_scan_crash_handler);
+ os_non_restarting_signal(SIGBUS, local_scan_crash_handler);
+
+ DEBUG(D_receive) debug_printf("calling local_scan(); timeout=%d\n",
+ local_scan_timeout);
+ local_scan_data = NULL;
+
+ had_local_scan_timeout = 0;
+ os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
+ if (local_scan_timeout > 0) ALARM(local_scan_timeout);
+ rc = local_scan(data_fd, &local_scan_data);
+ ALARM_CLR(0);
+ os_non_restarting_signal(SIGALRM, sigalrm_handler);
+
+ f.enable_dollar_recipients = FALSE;
+
+ store_pool = POOL_MAIN; /* In case changed */
+ DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
+ local_scan_data);
+
+ os_non_restarting_signal(SIGSEGV, SIG_DFL);
+ os_non_restarting_signal(SIGFPE, SIG_DFL);
+ os_non_restarting_signal(SIGILL, SIG_DFL);
+ os_non_restarting_signal(SIGBUS, SIG_DFL);
+ }
+else
+ {
+ if (had_local_scan_crash)
+ {
+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with "
+ "signal %d - message temporarily rejected (size %d)",
+ had_local_scan_crash, message_size);
+ receive_bomb_out(US"local-scan-error", US"local verification problem");
+ /* Does not return */
+ }
+ if (had_local_scan_timeout)
+ {
+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
+ "message temporarily rejected (size %d)", message_size);
+ receive_bomb_out(US"local-scan-timeout", US"local verification problem");
+ /* Does not return */
+ }
+ }
/* The length check is paranoia against some runaway code, and also because
(for a success return) lines in the spool file are read into big_buffer. */
-if (local_scan_data != NULL)
+if (local_scan_data)
{
int len = Ustrlen(local_scan_data);
if (len > LOCAL_SCAN_MAX_RETURN) len = LOCAL_SCAN_MAX_RETURN;
if (rc == LOCAL_SCAN_ACCEPT_FREEZE)
{
- if (!deliver_freeze) /* ACL might have already frozen */
+ if (!f.deliver_freeze) /* ACL might have already frozen */
{
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
frozen_by = US"local_scan()";
}
}
else if (rc == LOCAL_SCAN_ACCEPT_QUEUE)
{
- if (!queue_only_policy) /* ACL might have already queued */
+ if (!f.queue_only_policy) /* ACL might have already queued */
{
- queue_only_policy = TRUE;
+ f.queue_only_policy = TRUE;
queued_by = US"local_scan()";
}
rc = LOCAL_SCAN_ACCEPT;
if (rc == LOCAL_SCAN_ACCEPT)
{
- if (local_scan_data != NULL)
+ if (local_scan_data)
{
uschar *s;
for (s = local_scan_data; *s != 0; s++) if (*s == '\n') *s = ' ';
else
{
uschar *istemp = US"";
- uschar *s = NULL;
uschar *smtp_code;
- int size = 0;
- int sptr = 0;
+ gstring * g;
errmsg = local_scan_data;
switch(rc)
{
default:
- log_write(0, LOG_MAIN, "invalid return %d from local_scan(). Temporary "
- "rejection given", rc);
- goto TEMPREJECT;
+ log_write(0, LOG_MAIN, "invalid return %d from local_scan(). Temporary "
+ "rejection given", rc);
+ goto TEMPREJECT;
case LOCAL_SCAN_REJECT_NOLOGHDR:
- BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
- /* Fall through */
+ BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
+ /* Fall through */
case LOCAL_SCAN_REJECT:
- smtp_code = US"550";
- if (errmsg == NULL) errmsg = US"Administrative prohibition";
- break;
+ smtp_code = US"550";
+ if (!errmsg) errmsg = US"Administrative prohibition";
+ break;
case LOCAL_SCAN_TEMPREJECT_NOLOGHDR:
- BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
- /* Fall through */
+ BIT_CLEAR(log_selector, log_selector_size, Li_rejected_header);
+ /* Fall through */
case LOCAL_SCAN_TEMPREJECT:
TEMPREJECT:
- smtp_code = US"451";
- if (errmsg == NULL) errmsg = US"Temporary local problem";
- istemp = US"temporarily ";
- break;
+ smtp_code = US"451";
+ if (!errmsg) errmsg = US"Temporary local problem";
+ istemp = US"temporarily ";
+ break;
}
- s = string_append(s, &size, &sptr, 2, US"F=",
- (sender_address[0] == 0)? US"<>" : sender_address);
- s = add_host_info_for_log(s, &size, &sptr);
- s[sptr] = 0;
+ g = string_append(NULL, 2, US"F=",
+ sender_address[0] == 0 ? US"<>" : sender_address);
+ g = add_host_info_for_log(g);
log_write(0, LOG_MAIN|LOG_REJECT, "%s %srejected by local_scan(): %.256s",
- s, istemp, string_printing(errmsg));
+ string_from_gstring(g), istemp, string_printing(errmsg));
if (smtp_input)
{
goto TIDYUP; /* Skip to end of function */
}
else
- {
moan_smtp_batch(NULL, "%s %s", smtp_code, errmsg);
/* Does not return */
- }
}
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
give_local_error(ERRMESS_LOCAL_SCAN, errmsg,
- US"message rejected by local scan code: ", error_rc, data_file,
+ US"message rejected by local scan code: ", error_rc, spool_data_file,
header_list);
/* Does not return */
}
signal(SIGTERM, SIG_IGN);
signal(SIGINT, SIG_IGN);
+#endif /* HAVE_LOCAL_SCAN */
/* Ensure the first time flag is set in the newly-received message. */
-deliver_firsttime = TRUE;
+f.deliver_firsttime = TRUE;
#ifdef EXPERIMENTAL_BRIGHTMAIL
if (bmi_run == 1)
if (mua_wrapper)
{
- deliver_freeze = FALSE;
- queue_only_policy = FALSE;
+ f.deliver_freeze = FALSE;
+ f.queue_only_policy = FALSE;
}
/* Keep the data file open until we have written the header file, in order to
don't write the header file, and we unlink the data file. If writing the header
file fails, we have failed to accept this message. */
-if (host_checking || blackholed_by != NULL)
+if (host_checking || blackholed_by)
{
header_line *h;
Uunlink(spool_name);
msg_size = 0; /* Compute size for log line */
- for (h = header_list; h != NULL; h = h->next)
+ for (h = header_list; h; h = h->next)
if (h->type != '*') msg_size += h->slen;
}
}
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
- give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, data_file,
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
header_list);
/* Does not return */
}
receive_messagecount++;
-/* In SMTP sessions we may receive several in one connection. After each one,
-we wait for the clock to tick at the level of message-id granularity. This is
-so that the combination of time+pid is unique, even on systems where the pid
-can be re-used within our time interval. We can't shorten the interval without
-re-designing the message-id. See comments above where the message id is
-created. This is Something For The Future. */
-
-message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
-exim_wait_tick(&message_id_tv, id_resolution);
-
/* Add data size to written header size. We do not count the initial file name
that is in the file, but we do add one extra for the notional blank line that
precedes the data. This total differs from message_size in that it include the
added Received: header and any other headers that got created locally. */
-fflush(data_file);
+if (fflush(spool_data_file))
+ {
+ errmsg = string_sprintf("Spool write error: %s", strerror(errno));
+ log_write(0, LOG_MAIN, "%s\n", errmsg);
+ Uunlink(spool_name); /* Lose the data file */
+
+ if (smtp_input)
+ {
+ smtp_reply = US"451 Error in writing spool file";
+ message_id[0] = 0; /* Indicate no message accepted */
+ goto TIDYUP;
+ }
+ else
+ {
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
+ header_list);
+ /* Does not return */
+ }
+ }
fstat(data_fd, &statbuf);
msg_size += statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
/* Generate a "message received" log entry. We do this by building up a dynamic
-string as required. Since we commonly want to add two items at a time, use a
-macro to simplify the coding. We log the arrival of a new message while the
+string as required. We log the arrival of a new message while the
file is still locked, just in case the machine is *really* fast, and delivers
it first! Include any message id that is in the message - since the syntax of a
message id is actually an addr-spec, we can use the parse routine to canonicalize
it. */
-size = 256;
-sptr = 0;
-s = store_get(size);
+g = string_get(256);
-s = string_append(s, &size, &sptr, 2,
+g = string_append(g, 2,
fake_response == FAIL ? US"(= " : US"<= ",
sender_address[0] == 0 ? US"<>" : sender_address);
if (message_reference)
- s = string_append(s, &size, &sptr, 2, US" R=", message_reference);
+ g = string_append(g, 2, US" R=", message_reference);
-s = add_host_info_for_log(s, &size, &sptr);
+g = add_host_info_for_log(g);
#ifdef SUPPORT_TLS
if (LOGGING(tls_cipher) && tls_in.cipher)
- s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher);
+ g = string_append(g, 2, US" X=", tls_in.cipher);
if (LOGGING(tls_certificate_verified) && tls_in.cipher)
- s = string_append(s, &size, &sptr, 2, US" CV=",
- tls_in.certificate_verified ? "yes":"no");
+ g = string_append(g, 2, US" CV=", tls_in.certificate_verified ? "yes":"no");
if (LOGGING(tls_peerdn) && tls_in.peerdn)
- s = string_append(s, &size, &sptr, 3, US" DN=\"",
- string_printing(tls_in.peerdn), US"\"");
+ g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
if (LOGGING(tls_sni) && tls_in.sni)
- s = string_append(s, &size, &sptr, 3, US" SNI=\"",
- string_printing(tls_in.sni), US"\"");
+ g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
#endif
if (sender_host_authenticated)
{
- s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated);
+ g = string_append(g, 2, US" A=", sender_host_authenticated);
if (authenticated_id)
{
- s = string_append(s, &size, &sptr, 2, US":", authenticated_id);
+ g = string_append(g, 2, US":", authenticated_id);
if (LOGGING(smtp_mailauth) && authenticated_sender)
- s = string_append(s, &size, &sptr, 2, US":", authenticated_sender);
+ g = string_append(g, 2, US":", authenticated_sender);
}
}
#ifndef DISABLE_PRDR
if (prdr_requested)
- s = string_catn(s, &size, &sptr, US" PRDR", 5);
+ g = string_catn(g, US" PRDR", 5);
#endif
#ifdef SUPPORT_PROXY
if (proxy_session && LOGGING(proxy))
- s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address);
+ g = string_append(g, 2, US" PRX=", proxy_local_address);
#endif
if (chunking_state > CHUNKING_OFFERED)
- s = string_catn(s, &size, &sptr, US" K", 2);
+ g = string_catn(g, US" K", 2);
sprintf(CS big_buffer, "%d", msg_size);
-s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
+g = string_append(g, 2, US" S=", big_buffer);
/* log 8BITMIME mode announced in MAIL_FROM
0 ... no BODY= used
if (LOGGING(8bitmime))
{
sprintf(CS big_buffer, "%d", body_8bitmime);
- s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer);
+ g = string_append(g, 2, US" M8S=", big_buffer);
}
+#ifndef DISABLE_DKIM
+if (LOGGING(dkim) && dkim_verify_overall)
+ g = string_append(g, 2, US" DKIM=", dkim_verify_overall);
+# ifdef EXPERIMENTAL_ARC
+if (LOGGING(dkim) && arc_state && Ustrcmp(arc_state, "pass") == 0)
+ g = string_catn(g, US" ARC", 4);
+# endif
+#endif
+
+if (LOGGING(receive_time))
+ g = string_append(g, 2, US" RT=", string_timediff(&received_time_taken));
+
if (*queue_name)
- s = string_append(s, &size, &sptr, 2, US" Q=", queue_name);
+ g = string_append(g, 2, US" Q=", queue_name);
/* If an addr-spec in a message-id contains a quoted string, it can contain
any characters except " \ and CR and so in particular it can contain NL!
&errmsg, &start, &end, &domain, FALSE);
allow_domain_literals = save_allow_domain_literals;
if (old_id != NULL)
- s = string_append(s, &size, &sptr, 2, US" id=", string_printing(old_id));
+ g = string_append(g, 2, US" id=", string_printing(old_id));
}
/* If subject logging is turned on, create suitable printing-character
text. By expanding $h_subject: we make use of the MIME decoding. */
-if (LOGGING(subject) && subject_header != NULL)
+if (LOGGING(subject) && subject_header)
{
int i;
uschar *p = big_buffer;
}
*p++ = '\"';
*p = 0;
- s = string_append(s, &size, &sptr, 2, US" T=", string_printing(big_buffer));
+ g = string_append(g, 2, US" T=", string_printing(big_buffer));
}
/* Terminate the string: string_cat() and string_append() leave room, but do
not put the zero in. */
-s[sptr] = 0;
+(void) string_from_gstring(g);
/* Create a message log file if message logs are being used and this message is
not blackholed. Write the reception stuff to it. We used to leave message log
creation until the first delivery, but this has proved confusing for some
people. */
-if (message_logs && blackholed_by == NULL)
+if (message_logs && !blackholed_by)
{
int fd;
-
- spool_name = spool_fname(US"msglog", message_subdir, message_id, US"");
+ uschar * m_name = spool_fname(US"msglog", message_subdir, message_id, US"");
- if ( (fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
+ if ( (fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
&& errno == ENOENT
)
{
(void)directory_make(spool_directory,
spool_sname(US"msglog", message_subdir),
MSGLOG_DIRECTORY_MODE, TRUE);
- fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
+ fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
}
if (fd < 0)
- {
log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open message log %s: %s",
- spool_name, strerror(errno));
- }
-
+ m_name, strerror(errno));
else
{
FILE *message_log = fdopen(fd, "a");
- if (message_log == NULL)
+ if (!message_log)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
- spool_name, strerror(errno));
+ m_name, strerror(errno));
(void)close(fd);
}
else
{
uschar *now = tod_stamp(tod_log);
- fprintf(message_log, "%s Received from %s\n", now, s+3);
- if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
+ fprintf(message_log, "%s Received from %s\n", now, g->s+3);
+ if (f.deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
frozen_by);
- if (queue_only_policy) fprintf(message_log,
+ if (f.queue_only_policy) fprintf(message_log,
"%s no immediate delivery: queued%s%s by %s\n", now,
*queue_name ? " in " : "", *queue_name ? CS queue_name : "",
queued_by);
arrival, and outputting an SMTP response. While writing to the log, set a flag
to cause a call to receive_bomb_out() if the log cannot be opened. */
-receive_call_bombout = TRUE;
+f.receive_call_bombout = TRUE;
/* Before sending an SMTP response in a TCP/IP session, we check to see if the
connection has gone away. This can only be done if there is no unconsumed input
connection will vanish between the time of this test and the sending of the
response, but the chance of this happening should be small. */
-if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
+if (smtp_input && sender_host_address && !f.sender_host_notsocket &&
!receive_smtp_buffered())
{
struct timeval tv;
/* Re-use the log line workspace */
- sptr = 0;
- s = string_cat(s, &size, &sptr, US"SMTP connection lost after final dot");
- s = add_host_info_for_log(s, &size, &sptr);
- s[sptr] = 0;
- log_write(0, LOG_MAIN, "%s", s);
+ g->ptr = 0;
+ g = string_cat(g, US"SMTP connection lost after final dot");
+ g = add_host_info_for_log(g);
+ log_write(0, LOG_MAIN, "%s", string_from_gstring(g));
/* Delete the files for this aborted message. */
- Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+ Uunlink(spool_name);
Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
XXX We do not handle queue-only, freezing, or blackholes.
*/
-if(cutthrough.fd >= 0)
+if(cutthrough.cctx.sock >= 0 && cutthrough.delivery)
{
- uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the message */
+ uschar * msg = cutthrough_finaldot(); /* Ask the target system to accept the message */
/* Logging was done in finaldot() */
switch(msg[0])
{
case '4': /* Temp-reject. Keep spoolfiles and accept, unless defer-pass mode.
... for which, pass back the exact error */
if (cutthrough.defer_pass) smtp_reply = string_copy_malloc(msg);
- /*FALLTRHOUGH*/
+ cutthrough_done = TMP_REJ; /* Avoid the usual immediate delivery attempt */
+ break; /* message_id needed for SMTP accept below */
default: /* Unknown response, or error. Treat as temp-reject. */
+ if (cutthrough.defer_pass) smtp_reply = US"450 Onward transmission not accepted";
cutthrough_done = TMP_REJ; /* Avoid the usual immediate delivery attempt */
break; /* message_id needed for SMTP accept below */
#endif
{
log_write(0, LOG_MAIN |
- (LOGGING(received_recipients)? LOG_RECIPIENTS : 0) |
- (LOGGING(received_sender)? LOG_SENDER : 0),
- "%s", s);
+ (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+ (LOGGING(received_sender) ? LOG_SENDER : 0),
+ "%s", g->s);
/* Log any control actions taken by an ACL or local_scan(). */
- if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
- if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
+ if (f.deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
+ if (f.queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
"no immediate delivery: queued%s%s by %s",
*queue_name ? " in " : "", *queue_name ? CS queue_name : "",
queued_by);
}
-receive_call_bombout = FALSE;
+f.receive_call_bombout = FALSE;
-store_reset(s); /* The store for the main log message can be reused */
+store_reset(g); /* The store for the main log message can be reused */
/* If the message is frozen, and freeze_tell is set, do the telling. */
-if (deliver_freeze && freeze_tell != NULL && freeze_tell[0] != 0)
- {
+if (f.deliver_freeze && freeze_tell && freeze_tell[0])
moan_tell_someone(freeze_tell, NULL, US"Message frozen on arrival",
"Message %s was frozen on arrival by %s.\nThe sender is <%s>.\n",
message_id, frozen_by, sender_address);
- }
/* Either a message has been successfully received and written to the two spool
files, or an error in writing the spool has occurred for an SMTP message, or
-an SMTP message has been rejected for policy reasons. (For a non-SMTP message
-we will have already given up because there's no point in carrying on!) In
-either event, we must now close (and thereby unlock) the data file. In the
-successful case, this leaves the message on the spool, ready for delivery. In
-the error case, the spool file will be deleted. Then tidy up store, interact
-with an SMTP call if necessary, and return.
+an SMTP message has been rejected for policy reasons, or a message was passed on
+by cutthrough delivery. (For a non-SMTP message we will have already given up
+because there's no point in carrying on!) For non-cutthrough we must now close
+(and thereby unlock) the data file. In the successful case, this leaves the
+message on the spool, ready for delivery. In the error case, the spool file will
+be deleted. Then tidy up store, interact with an SMTP call if necessary, and
+return.
+
+For cutthrough we hold the data file locked until we have deleted it, otherwise
+a queue-runner could grab it in the window.
A fflush() was done earlier in the expectation that any write errors on the
data file will be flushed(!) out thereby. Nevertheless, it is theoretically
possible for fclose() to fail - but what to do? What has happened to the lock
-if this happens? */
+if this happens? We can at least log it; if it is observed on some platform
+then we can think about properly declaring the message not-received. */
TIDYUP:
-process_info[process_info_len] = 0; /* Remove message id */
-if (data_file != NULL) (void)fclose(data_file); /* Frees the lock */
+/* In SMTP sessions we may receive several messages in one connection. After
+each one, we wait for the clock to tick at the level of message-id granularity.
+This is so that the combination of time+pid is unique, even on systems where the
+pid can be re-used within our time interval. We can't shorten the interval
+without re-designing the message-id. See comments above where the message id is
+created. This is Something For The Future.
+Do this wait any time we have created a message-id, even if we rejected the
+message. This gives unique IDs for logging done by ACLs. */
+
+if (id_resolution != 0)
+ {
+ message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
+ exim_wait_tick(&message_id_tv, id_resolution);
+ id_resolution = 0;
+ }
+
+
+process_info[process_info_len] = 0; /* Remove message id */
+if (spool_data_file && cutthrough_done == NOT_TRIED)
+ {
+ if (fclose(spool_data_file)) /* Frees the lock */
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "spoolfile error on close: %s", strerror(errno));
+ spool_data_file = NULL;
+ }
/* Now reset signal handlers to their defaults */
else if (chunking_state > CHUNKING_OFFERED)
{
- smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n",
+ smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", FALSE,
chunking_datasize, message_size+message_linecount, message_id);
chunking_state = CHUNKING_OFFERED;
}
else
- smtp_printf("250 OK id=%s\r\n", message_id);
+ smtp_printf("250 OK id=%s\r\n", FALSE, message_id);
if (host_checking)
fprintf(stdout,
/* smtp_reply is set non-empty */
else if (smtp_reply[0] != 0)
- if (fake_response != OK && (smtp_reply[0] == '2'))
- smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
+ if (fake_response != OK && smtp_reply[0] == '2')
+ smtp_respond(fake_response == DEFER ? US"450" : US"550", 3, TRUE,
fake_response_text);
else
- smtp_printf("%.1024s\r\n", smtp_reply);
+ smtp_printf("%.1024s\r\n", FALSE, smtp_reply);
switch (cutthrough_done)
{
log_write(0, LOG_MAIN, "Completed");/* Delivery was done */
case PERM_REJ:
/* Delete spool files */
- Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+ Uunlink(spool_name);
Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
- message_id[0] = 0; /* Prevent a delivery from starting */
break;
case TMP_REJ:
if (cutthrough.defer_pass)
{
- Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+ Uunlink(spool_name);
Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
}
- message_id[0] = 0; /* Prevent a delivery from starting */
default:
break;
}
- cutthrough.delivery = FALSE;
- cutthrough.defer_pass = FALSE;
+ if (cutthrough_done != NOT_TRIED)
+ {
+ if (spool_data_file)
+ {
+ (void) fclose(spool_data_file); /* Frees the lock; do not care if error */
+ spool_data_file = NULL;
+ }
+ message_id[0] = 0; /* Prevent a delivery from starting */
+ cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
+ cutthrough.defer_pass = FALSE;
+ }
}
/* For batched SMTP, generate an error message on failure, and do
if (blackholed_by)
{
- const uschar *detail = local_scan_data
- ? string_printing(local_scan_data)
- : string_sprintf("(%s discarded recipients)", blackholed_by);
+ const uschar *detail =
+#ifdef HAVE_LOCAL_SCAN
+ local_scan_data ? string_printing(local_scan_data) :
+#endif
+ string_sprintf("(%s discarded recipients)", blackholed_by);
log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
log_write(0, LOG_MAIN, "Completed");
message_id[0] = 0;
/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003-2015
* License: GPL
- * Copyright (c) The Exim Maintainers 2016
+ * Copyright (c) The Exim Maintainers 2016 - 2018
*/
/* Code for matching regular expressions against headers and body.
if (!mime_stream) /* We are in the DATA ACL */
{
- if (!(mbox_file = spool_mbox(&mbox_size, NULL)))
+ if (!(mbox_file = spool_mbox(&mbox_size, NULL, NULL)))
{ /* error while spooling */
log_write(0, LOG_MAIN|LOG_PANIC,
"regex acl condition: error while creating mbox spool file");
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with retrying unsuccessful deliveries. */
dbdata_retry *retry_record, time_t now)
{
BOOL address_timeout;
+retry_config * retry;
DEBUG(D_retry)
{
debug_printf("retry time not reached: checking ultimate address timeout\n");
- debug_printf(" now=%d first_failed=%d next_try=%d expired=%d\n",
- (int)now, (int)retry_record->first_failed,
- (int)retry_record->next_try, retry_record->expired);
+ debug_printf(" now=" TIME_T_FMT " first_failed=" TIME_T_FMT
+ " next_try=" TIME_T_FMT " expired=%c\n",
+ now, retry_record->first_failed,
+ retry_record->next_try, retry_record->expired ? 'T' : 'F');
}
-retry_config *retry =
- retry_find_config(retry_key+2, domain,
+retry = retry_find_config(retry_key+2, domain,
retry_record->basic_errno, retry_record->more_errno);
-if (retry != NULL && retry->rules != NULL)
+if (retry && retry->rules)
{
retry_rule *last_rule;
- for (last_rule = retry->rules;
- last_rule->next != NULL;
- last_rule = last_rule->next);
+ for (last_rule = retry->rules; last_rule->next; last_rule = last_rule->next) ;
DEBUG(D_retry)
- debug_printf(" received_time=%d diff=%d timeout=%d\n",
- received_time, (int)(now - received_time), last_rule->timeout);
- address_timeout = (now - received_time > last_rule->timeout);
+ debug_printf(" received_time=" TIME_T_FMT " diff=%d timeout=%d\n",
+ received_time.tv_sec, (int)(now - received_time.tv_sec), last_rule->timeout);
+ address_timeout = (now - received_time.tv_sec > last_rule->timeout);
}
else
{
become unusable during this delivery process (i.e. those that will get put into
the retry database when it is updated). */
-node = tree_search(tree_unusable, host_key);
-if (node != NULL)
+if ((node = tree_search(tree_unusable, host_key)))
{
DEBUG(D_transport|D_retry) debug_printf("found in tree of unusables\n");
host->status = (node->data.val > 255)?
/* Open the retry database, giving up if there isn't one. Otherwise, search for
the retry records, and then close the database again. */
-if ((dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)) == NULL)
+if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
{
DEBUG(D_deliver|D_retry|D_hints_lookup)
debug_printf("no retry data available\n");
/* Ignore the data if it is too old - too long since it was written */
-if (host_retry_record == NULL)
+if (!host_retry_record)
{
DEBUG(D_transport|D_retry) debug_printf("no host retry record\n");
}
DEBUG(D_transport|D_retry) debug_printf("host retry record too old\n");
}
-if (message_retry_record == NULL)
+if (!message_retry_record)
{
DEBUG(D_transport|D_retry) debug_printf("no message retry record\n");
}
flag and return FALSE. Otherwise arrange to return TRUE if this is an expired
host. */
-if (host_retry_record != NULL)
+if (host_retry_record)
{
*retry_host_key = host_key;
/* We have not reached the next try time. Check for the ultimate address
timeout if the host has not expired. */
- if (now < host_retry_record->next_try && !deliver_force)
+ if (now < host_retry_record->next_try && !f.deliver_force)
{
if (!host_retry_record->expired &&
retry_ultimate_address_timeout(host_key, domain,
for reaching its retry time (or forcing). If not, mark the host unusable,
unless the ultimate address timeout has been reached. */
-if (message_retry_record != NULL)
+if (message_retry_record)
{
*retry_message_key = message_key;
- if (now < message_retry_record->next_try && !deliver_force)
+ if (now < message_retry_record->next_try && !f.deliver_force)
{
if (!retry_ultimate_address_timeout(host_key, domain,
message_retry_record, now))
{
retry_item *rti = store_get(sizeof(retry_item));
host_item * host = addr->host_used;
+
rti->next = addr->retries;
addr->retries = rti;
rti->key = key;
/* Scan the configured retry items. */
-for (yield = retries; yield != NULL; yield = yield->next)
+for (yield = retries; yield; yield = yield->next)
{
const uschar *plist = yield->pattern;
const uschar *slist = yield->senders;
/* If the "senders" condition is set, check it. Note that sender_address may
be null during -brt checking, in which case we do not use this rule. */
- if (slist != NULL && (sender_address == NULL ||
- match_address_list(sender_address, TRUE, TRUE, &slist, NULL, -1, 0,
- NULL) != OK))
+ if ( slist
+ && ( !sender_address
+ || match_address_list_basic(sender_address, &slist, 0) != OK
+ ) )
continue;
/* Check for a match between the address list item at the start of this retry
rule and either the main or alternate keys. */
- if (match_address_list(key, TRUE, TRUE, &plist, NULL, -1, UCHAR_MAX+1,
- NULL) == OK ||
- (alternate != NULL &&
- match_address_list(alternate, TRUE, TRUE, &plist, NULL, -1,
- UCHAR_MAX+1, NULL) == OK))
+ if ( match_address_list_basic(key, &plist, UCHAR_MAX+1) == OK
+ || ( alternate
+ && match_address_list_basic(alternate, &plist, UCHAR_MAX+1) == OK
+ ) )
break;
}
}
DEBUG(D_retry)
- {
if (rti->flags & rf_host)
debug_printf("retry for %s (%s) = %s %d %d\n", rti->key,
addr->domain, retry->pattern, retry->basic_errno,
else
debug_printf("retry for %s = %s %d %d\n", rti->key, retry->pattern,
retry->basic_errno, retry->more_errno);
- }
/* Set up the message for the database retry record. Because DBM
records have a maximum data length, we enforce a limit. There isn't
this is a small bit of code, and it does no harm to leave it in place,
just in case. */
- if ( received_time <= retry_record->first_failed
+ if ( received_time.tv_sec <= retry_record->first_failed
&& addr == endaddr
&& !retry_record->expired
&& rule)
retry_rule *last_rule;
for (last_rule = rule; last_rule->next; last_rule = last_rule->next)
;
- if (now - received_time > last_rule->timeout)
+ if (now - received_time.tv_sec > last_rule->timeout)
{
DEBUG(D_retry) debug_printf("on queue longer than maximum retry\n");
timedout_count++;
timed_out = TRUE;
}
else
- {
DEBUG(D_retry)
debug_printf("timed out but some hosts were skipped\n");
- }
} /* Loop for an address and its parents */
/* If this is a deferred address, and retry processing was requested by
for (;; addr = addr->next)
{
setflag(addr, af_retry_timedout);
- addr->message = (addr->message == NULL)? US"retry timeout exceeded" :
- string_sprintf("%s: retry timeout exceeded", addr->message);
- addr->user_message = (addr->user_message == NULL)?
- US"retry timeout exceeded" :
- string_sprintf("%s: retry timeout exceeded", addr->user_message);
+ addr->message = addr->message
+ ? string_sprintf("%s: retry timeout exceeded", addr->message)
+ : US"retry timeout exceeded";
+ addr->user_message = addr->user_message
+ ? string_sprintf("%s: retry timeout exceeded", addr->user_message)
+ : US"retry timeout exceeded";
log_write(0, LOG_MAIN, "** %s%s%s%s: retry timeout exceeded",
addr->address,
- (addr->parent == NULL)? US"" : US" <",
- (addr->parent == NULL)? US"" : addr->parent->address,
- (addr->parent == NULL)? US"" : US">");
+ addr->parent ? US" <" : US"",
+ addr->parent ? addr->parent->address : US"",
+ addr->parent ? US">" : US"");
if (addr == endaddr) break;
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with rewriting headers */
uschar *key = expand_string(rule->key);
if (key == NULL)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand \"%s\" while "
"checking for SMTP rewriting: %s", rule->key, expand_string_message);
continue;
if (new == NULL)
{
- if (expand_string_forcedfail)
+ if (f.expand_string_forcedfail)
{ if ((rule->flags & rewrite_quit) != 0) break; else continue; }
expand_string_message = expand_hide_passwords(expand_string_message);
start = Ustrlen(pf1) + start + new - p1;
end = start + Ustrlen(newparsed);
- new = string_sprintf("%s%.*s%s", pf1, p2 - p1, p1, pf2);
+ new = string_sprintf("%s%.*s%s", pf1, (int)(p2 - p1), p1, pf2);
}
/* Now accept the whole thing */
DEBUG(D_rewrite)
debug_printf("rewrite_one_header: type=%c:\n %s", h->type, h->text);
-parse_allow_group = TRUE; /* Allow group syntax */
+f.parse_allow_group = TRUE; /* Allow group syntax */
/* Loop for multiple addresses in the header. We have to go through them all
in case any need qualifying, even if there's no rewriting. Pathological headers
/* Can only qualify if permitted; if not, no rewrite. */
- if (changed && ((is_recipient && !allow_unqualified_recipient) ||
- (!is_recipient && !allow_unqualified_sender)))
+ if (changed && ((is_recipient && !f.allow_unqualified_recipient) ||
+ (!is_recipient && !f.allow_unqualified_sender)))
{
store_reset(loop_reset_point);
continue;
}
}
-parse_allow_group = FALSE; /* Reset group flags */
-parse_found_group = FALSE;
+f.parse_allow_group = FALSE; /* Reset group flags */
+f.parse_found_group = FALSE;
/* If a rewrite happened and "replace" is true, put the new header into the
chain following the old one, and mark the old one as replaced. */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* This file contains a function for decoding message header lines that may
while (*string != 0)
{
- register int ch = *string++;
+ int ch = *string++;
if (ch == '_') *ptr++ = ' ';
else if (ch == '=')
rfc2047_decode2(uschar *string, BOOL lencheck, uschar *target, int zeroval,
int *lenptr, int *sizeptr, uschar **error)
{
-int ptr = 0;
int size = Ustrlen(string);
size_t dlen;
-uschar *dptr, *yield;
+uschar *dptr;
+gstring *yield;
uschar *mimeword, *q1, *q2, *endword;
*error = NULL;
mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr);
-if (mimeword == NULL)
+if (!mimeword)
{
- if (lenptr != NULL) *lenptr = size;
+ if (lenptr) *lenptr = size;
return string;
}
translated into a multibyte code such as UTF-8. That's why we use the dynamic
string building code. */
-yield = store_get(++size);
+yield = store_get(sizeof(gstring) + ++size);
+yield->size = size;
+yield->ptr = 0;
+yield->s = US(yield + 1);
-while (mimeword != NULL)
+while (mimeword)
{
#if HAVE_ICONV
#endif
if (mimeword != string)
- yield = string_catn(yield, &size, &ptr, string, mimeword - string);
+ yield = string_catn(yield, string, mimeword - string);
/* Do a charset translation if required. This is supported only on hosts
that have the iconv() function. Translation errors set error, but carry on,
/* Add the new string onto the result */
- yield = string_catn(yield, &size, &ptr, tptr, tlen);
+ yield = string_catn(yield, tptr, tlen);
}
#if HAVE_ICONV
string = endword + 2;
mimeword = decode_mimeword(string, lencheck, &q1, &q2, &endword, &dlen, &dptr);
- if (mimeword != NULL)
+ if (mimeword)
{
uschar *s = string;
while (isspace(*s)) s++;
/* Copy the remaining characters of the string, zero-terminate it, and return
the length as well if requested. */
-yield = string_cat(yield, &size, &ptr, string);
-yield[ptr] = 0;
-if (lenptr != NULL) *lenptr = ptr;
-if (sizeptr != NULL) *sizeptr = size;
-return yield;
+yield = string_cat(yield, string);
+
+if (lenptr) *lenptr = yield->ptr;
+if (sizeptr) *sizeptr = yield->size;
+return string_from_gstring(yield);
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with routing, and the list of generic router options. */
(void *)offsetof(router_instance, verify_sender) }
};
-int optionlist_routers_size = sizeof(optionlist_routers)/sizeof(optionlist);
+int optionlist_routers_size = nelem(optionlist_routers);
+#ifdef MACRO_PREDEF
+
+# include "macro_predef.h"
+
void
-readconf_options_routers(void)
+options_routers(void)
{
struct router_info * ri;
+uschar buf[64];
-readconf_options_from_list(optionlist_routers, nelem(optionlist_routers), US"ROUTERS", NULL);
+options_from_list(optionlist_routers, nelem(optionlist_routers), US"ROUTERS", NULL);
for (ri = routers_available; ri->driver_name[0]; ri++)
{
- macro_create(string_sprintf("_DRIVER_ROUTER_%T", ri->driver_name), US"y", FALSE, TRUE);
- readconf_options_from_list(ri->options, (unsigned)*ri->options_count, US"ROUTER", ri->driver_name);
+ spf(buf, sizeof(buf), US"_DRIVER_ROUTER_%T", ri->driver_name);
+ builtin_macro_create(buf);
+ options_from_list(ri->options, (unsigned)*ri->options_count, US"ROUTER", ri->driver_name);
}
}
+#else /*!MACRO_PREDEF*/
+
/*************************************************
* Set router pointer from name *
*************************************************/
/* Check for transport or no transport on certain routers */
- if ((r->info->ri_flags & ri_yestransport) != 0 &&
- r->transport_name == NULL &&
- !r->verify_only)
+ if ( (r->info->ri_flags & ri_yestransport)
+ && !r->transport_name && !r->verify_only)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "%s router:\n "
"a transport is required for this router", r->name);
- if ((r->info->ri_flags & ri_notransport) != 0 &&
- r->transport_name != NULL)
+ if ((r->info->ri_flags & ri_notransport) && r->transport_name)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG, "%s router:\n "
"a transport must not be defined for this router", r->name);
/* Check redirect_router and pass_router are valid */
- if (r->redirect_router_name != NULL)
+ if (r->redirect_router_name)
set_router(r, r->redirect_router_name, &(r->redirect_router), FALSE);
- if (r->pass_router_name != NULL)
+ if (r->pass_router_name)
set_router(r, r->pass_router_name, &(r->pass_router), TRUE);
+#ifdef notdef
DEBUG(D_route) debug_printf("DSN: %s %s\n", r->name,
r->dsn_lasthop ? "lasthop set" : "propagating DSN");
+#endif
}
}
if (!ss)
{
- if (expand_string_forcedfail) continue;
+ if (f.expand_string_forcedfail) continue;
*perror = string_sprintf("failed to expand \"%s\" for require_files: %s",
check, expand_string_message);
goto RETURN_DEFER;
sender_data = NULL;
local_user_gid = (gid_t)(-1);
local_user_uid = (uid_t)(-1);
-search_find_defer = FALSE;
+f.search_find_defer = FALSE;
/* Skip this router if not verifying and it has verify_only set */
/* Skip this router if testing an address (-bt) and address_test is not set */
-if (address_test_mode && !r->address_test)
+if (f.address_test_mode && !r->address_test)
{
DEBUG(D_route) debug_printf("%s router skipped: address_test is unset\n",
r->name);
uschar *router_home = expand_string(r->router_home_directory);
if (!router_home)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
*perror = string_sprintf("failed to expand \"%s\" for "
"router_home_directory: %s", r->router_home_directory,
DEBUG(D_route) debug_printf("checking \"condition\" \"%.80s\"...\n", r->condition);
if (!expand_check_condition(r->condition, r->name, US"router"))
{
- if (search_find_defer)
+ if (f.search_find_defer)
{
*perror = US"condition check lookup defer";
DEBUG(D_route) debug_printf("%s\n", *perror);
/* Copy the propagated flags and address_data from the original. */
-copyflag(new, addr, af_propagate);
+new->prop.ignore_error = addr->prop.ignore_error;
new->prop.address_data = addr->prop.address_data;
new->dsn_flags = addr->dsn_flags;
new->dsn_orcpt = addr->dsn_orcpt;
/* There are some weird cases where logging is disabled */
- disable_logging = r->disable_logging;
+ f.disable_logging = r->disable_logging;
/* Record the last router to handle the address, and set the default
next router. */
deliver_address_data = expand_string(r->address_data);
if (!deliver_address_data)
{
- if (expand_string_forcedfail)
+ if (f.expand_string_forcedfail)
{
DEBUG(D_route) debug_printf("forced failure in expansion of \"%s\" "
"(address_data): decline action taken\n", r->address_data);
pw = &pwcopy;
}
- /* Run the router, and handle the consequences. */
-
- /* ... but let us check on DSN before. If this should be the last hop for DSN
- set flag. */
+ /* If this should be the last hop for DSN flag the addr. */
if (r->dsn_lasthop && !(addr->dsn_flags & rf_dsnlasthop))
{
HDEBUG(D_route) debug_printf("DSN: last hop for %s\n", addr->address);
}
+ /* Run the router, and handle the consequences. */
+
HDEBUG(D_route) debug_printf("calling %s router\n", r->name);
yield = (r->info->code)(r, addr, pw, verify, paddr_local, paddr_remote,
uschar *expmessage = expand_string(addr->router->cannot_route_message);
if (!expmessage)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
"cannot_route_message in %s router: %s", addr->router->name,
expand_string_message);
if (!newaddress)
{
- if (expand_string_forcedfail) continue;
+ if (f.expand_string_forcedfail) continue;
addr->basic_errno = ERRNO_EXPANDFAIL;
addr->message = string_sprintf("translate_ip_address expansion "
"failed: %s", expand_string_message);
deliver_set_expansions(NULL);
router_name = NULL;
-disable_logging = FALSE;
+f.disable_logging = FALSE;
return yield;
}
+#endif /*!MACRO_PREDEF*/
/* End of route.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
};
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+void accept_router_init(router_instance *rblock) {}
+int accept_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
/*************************************************
* Initialization entry point *
rc = rf_get_errors_address(addr, rblock, verify, &errors_to);
if (rc != OK) return rc;
-/* Set up the additional and removeable headers for the address. */
+/* Set up the additional and removable headers for the address. */
rc = rf_get_munge_headers(addr, rblock, &extra_headers, &remove_headers);
if (rc != OK) return rc;
return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)? OK : DEFER;
}
+#endif /*!MACRO_PREDEF*/
/* End of routers/accept.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
(void *)(offsetof(dnslookup_router_options_block, check_srv)) },
{ "fail_defer_domains", opt_stringptr,
(void *)(offsetof(dnslookup_router_options_block, fail_defer_domains)) },
+ { "ipv4_only", opt_stringptr,
+ (void *)(offsetof(dnslookup_router_options_block, ipv4_only)) },
+ { "ipv4_prefer", opt_stringptr,
+ (void *)(offsetof(dnslookup_router_options_block, ipv4_prefer)) },
{ "mx_domains", opt_stringptr,
(void *)(offsetof(dnslookup_router_options_block, mx_domains)) },
{ "mx_fail_domains", opt_stringptr,
int dnslookup_router_options_count =
sizeof(dnslookup_router_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+dnslookup_router_options_block dnslookup_router_option_defaults = {0};
+void dnslookup_router_init(router_instance *rblock) {}
+int dnslookup_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
+
/* Default private options block for the dnslookup router. */
dnslookup_router_options_block dnslookup_router_option_defaults = {
- FALSE, /* check_secondary_mx */
- TRUE, /* qualify_single */
- FALSE, /* search_parents */
- TRUE, /* rewrite_headers */
- NULL, /* widen_domains */
- NULL, /* mx_domains */
- NULL, /* mx_fail_domains */
- NULL, /* srv_fail_domains */
- NULL, /* check_srv */
- NULL /* fail_defer_domains */
+ .check_secondary_mx = FALSE,
+ .qualify_single = TRUE,
+ .search_parents = FALSE,
+ .rewrite_headers = TRUE,
+ .widen_domains = NULL,
+ .mx_domains = NULL,
+ .mx_fail_domains = NULL,
+ .srv_fail_domains = NULL,
+ .check_srv = NULL,
+ .fail_defer_domains = NULL,
+ .ipv4_only = NULL,
+ .ipv4_prefer = NULL,
};
host_item h;
int rc;
int widen_sep = 0;
-int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A;
+int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
dnslookup_router_options_block *ob =
(dnslookup_router_options_block *)(rblock->options_block);
uschar *srv_service = NULL;
if (ob->check_srv)
{
if ( !(srv_service = expand_string(ob->check_srv))
- && !expand_string_forcedfail)
+ && !f.expand_string_forcedfail)
{
addr->message = string_sprintf("%s router: failed to expand \"%s\": %s",
rblock->name, ob->check_srv, expand_string_message);
}
else return DECLINE;
+ /* Check if we must request only. or prefer, ipv4 */
+
+ if ( ob->ipv4_only
+ && expand_check_condition(ob->ipv4_only, rblock->name, US"router"))
+ flags = flags & ~HOST_FIND_BY_AAAA | HOST_FIND_IPV4_ONLY;
+ else if (f.search_find_defer)
+ return DEFER;
+ if ( ob->ipv4_prefer
+ && expand_check_condition(ob->ipv4_prefer, rblock->name, US"router"))
+ flags |= HOST_FIND_IPV4_FIRST;
+ else if (f.search_find_defer)
+ return DEFER;
+
/* Set up the rest of the initial host item. Others may get chained on if
there is more than one IP address. We set it up here instead of outside the
loop so as to re-initialize if a previous try succeeded but was rejected
/* Unfortunately, we cannot set the mx_only option in advance, because the
DNS lookup may extend an unqualified name. Therefore, we must do the test
- subsequently. We use the same logic as that for widen_domains above to avoid
+ stoubsequently. We use the same logic as that for widen_domains above to avoid
requesting a header rewrite that cannot work. */
if (verify != v_sender || !ob->rewrite_headers || addr->parent)
if (ob->search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
}
- rc = host_find_bydns(&h, CUS rblock->ignore_target_hosts, flags, srv_service,
- ob->srv_fail_domains, ob->mx_fail_domains,
- &rblock->dnssec, &fully_qualified_name, &removed);
+ rc = host_find_bydns(&h, CUS rblock->ignore_target_hosts, flags,
+ srv_service, ob->srv_fail_domains, ob->mx_fail_domains,
+ &rblock->dnssec,
+ &fully_qualified_name, &removed);
+
if (removed) setflag(addr, af_local_host_removed);
/* If host found with only address records, test for the domain's being in
/* Deferral returns forthwith, and anything other than failure breaks the
loop. */
+ if (rc == HOST_FIND_SECURITY)
+ {
+ addr->message = US"host lookup done insecurely";
+ return DEFER;
+ }
if (rc == HOST_FIND_AGAIN)
{
if (rblock->pass_on_timeout)
/* If there's a syntax error, do not continue with any widening, and note
the error. */
- if (host_find_failed_syntax)
+ if (f.host_find_failed_syntax)
{
addr->message = string_sprintf("mail domain \"%s\" is syntactically "
"invalid", h.name);
rc = rf_get_errors_address(addr, rblock, verify, &addr->prop.errors_address);
if (rc != OK) return rc;
-/* Set up the additional and removeable headers for this address. */
+/* Set up the additional and removable headers for this address. */
rc = rf_get_munge_headers(addr, rblock, &addr->prop.extra_headers,
&addr->prop.remove_headers);
OK : DEFER;
}
+#endif /*!MACRO_PREDEF*/
/* End of routers/dnslookup.c */
/* vi: aw ai sw=2
*/
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Private structure for the private options. */
uschar *srv_fail_domains;
uschar *check_srv;
uschar *fail_defer_domains;
+ uschar *ipv4_only;
+ uschar *ipv4_prefer;
} dnslookup_router_options_block;
/* Data for reading the private options. */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
ipliteral_router_options_block ipliteral_router_option_defaults = { 0 };
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+void ipliteral_router_init(router_instance *rblock) {}
+int ipliteral_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
/*************************************************
* Initialization entry point *
rc = rf_get_errors_address(addr, rblock, verify, &addr->prop.errors_address);
if (rc != OK) return rc;
-/* Set up the additional and removeable headers for this address. */
+/* Set up the additional and removable headers for this address. */
rc = rf_get_munge_headers(addr, rblock, &addr->prop.extra_headers,
&addr->prop.remove_headers);
OK : DEFER;
}
+#endif /*!MACRO_PREDEF*/
/* End of routers/ipliteral.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
int iplookup_router_options_count =
sizeof(iplookup_router_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+iplookup_router_options_block iplookup_router_option_defaults = {0};
+void iplookup_router_init(router_instance *rblock) {}
+int iplookup_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
/* Default private options block for the iplookup router. */
iplookup_router_options_block iplookup_router_option_defaults = {
for (h = host; h; h = h->next)
{
- int host_af, query_socket;
+ int host_af;
+ client_conn_ctx query_cctx = {0};
/* Skip any hosts for which we have no address */
host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
- query_socket = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
+ query_cctx.sock = ip_socket(ob->protocol == ip_udp ? SOCK_DGRAM:SOCK_STREAM,
host_af);
- if (query_socket < 0)
+ if (query_cctx.sock < 0)
{
if (ob->optional) return PASS;
addr->message = string_sprintf("failed to create socket in %s router",
/* Connect to the remote host, under a timeout. In fact, timeouts can occur
here only for TCP calls; for a UDP socket, "connect" always works (the
router will timeout later on the read call). */
+/*XXX could take advantage of TFO */
- if (ip_connect(query_socket, host_af, h->address,ob->port, ob->timeout,
- ob->protocol != ip_udp) < 0)
+ if (ip_connect(query_cctx.sock, host_af, h->address,ob->port, ob->timeout,
+ ob->protocol == ip_udp ? NULL : &tcp_fastopen_nodata) < 0)
{
- close(query_socket);
+ close(query_cctx.sock);
DEBUG(D_route)
debug_printf("connection to %s failed: %s\n", h->address,
strerror(errno));
/* Send the query. If it fails, just continue with the next address. */
- if (send(query_socket, query, query_len, 0) < 0)
+ if (send(query_cctx.sock, query, query_len, 0) < 0)
{
DEBUG(D_route) debug_printf("send to %s failed\n", h->address);
- (void)close(query_socket);
+ (void)close(query_cctx.sock);
continue;
}
/* Read the response and close the socket. If the read fails, try the
next IP address. */
- count = ip_recv(query_socket, reply, sizeof(reply) - 1, ob->timeout);
- (void)close(query_socket);
+ count = ip_recv(&query_cctx, reply, sizeof(reply) - 1, ob->timeout);
+ (void)close(query_cctx.sock);
if (count <= 0)
{
DEBUG(D_route) debug_printf("%s from %s\n", (errno == ETIMEDOUT)?
new_addr = deliver_make_addr(reroute, TRUE);
new_addr->parent = addr;
-copyflag(new_addr, addr, af_propagate);
new_addr->prop = addr->prop;
if (addr->child_count == USHRT_MAX)
new_addr->next = *addr_new;
*addr_new = new_addr;
-/* Set up the errors address, if any, and the additional and removeable headers
+/* Set up the errors address, if any, and the additional and removable headers
for this new address. */
rc = rf_get_errors_address(addr, rblock, verify, &new_addr->prop.errors_address);
return OK;
}
+#endif /*!MACRO_PREDEF*/
/* End of routers/iplookup.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
int manualroute_router_options_count =
sizeof(manualroute_router_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+manualroute_router_options_block manualroute_router_option_defaults = {0};
+void manualroute_router_init(router_instance *rblock) {}
+int manualroute_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
/* Default private options block for the manualroute router. */
manualroute_router_options_block manualroute_router_option_defaults = {
{
while (*s != 0 && isspace(*s)) s++;
-if (domain != NULL)
+if (domain)
{
- if (*s == 0) return FALSE; /* missing data */
+ if (!*s) return FALSE; /* missing data */
*domain = string_dequote(&s);
- while (*s != 0 && isspace(*s)) s++;
+ while (*s && isspace(*s)) s++;
}
*hostlist = string_dequote(&s);
-while (*s != 0 && isspace(*s)) s++;
+while (*s && isspace(*s)) s++;
*options = s;
return TRUE;
}
/* The initialization check ensures that either route_list or route_data is
set. */
-if (ob->route_list != NULL)
+if (ob->route_list)
{
int sep = -(';'); /* Default is semicolon */
listptr = ob->route_list;
}
}
- if (route_item == NULL) return DECLINE; /* No pattern in the list matched */
+ if (!route_item) return DECLINE; /* No pattern in the list matched */
}
/* Handle a single routing item in route_data. If it expands to an empty
else
{
- route_item = rf_expand_data(addr, ob->route_data, &rc);
- if (route_item == NULL) return rc;
+ if (!(route_item = rf_expand_data(addr, ob->route_data, &rc)))
+ return rc;
(void) parse_route_item(route_item, NULL, &hostlist, &options);
- if (hostlist[0] == 0) return DECLINE;
+ if (!hostlist[0]) return DECLINE;
}
/* Expand the hostlist item. It may then pointing to an empty string, or to a
single host or a list of hosts; options is pointing to the rest of the
routelist item, which is either empty or contains various option words. */
-DEBUG(D_route) debug_printf("original list of hosts = \"%s\" options = %s\n",
+DEBUG(D_route) debug_printf("original list of hosts = '%s' options = '%s'\n",
hostlist, options);
newhostlist = expand_string_copy(hostlist);
/* If the expansion was forced to fail, just decline. Otherwise there is a
configuration problem. */
-if (newhostlist == NULL)
+if (!newhostlist)
{
- if (expand_string_forcedfail) return DECLINE;
+ if (f.expand_string_forcedfail) return DECLINE;
addr->message = string_sprintf("%s router: failed to expand \"%s\": %s",
rblock->name, hostlist, expand_string_message);
return DEFER;
}
else hostlist = newhostlist;
-DEBUG(D_route) debug_printf("expanded list of hosts = \"%s\" options = %s\n",
+DEBUG(D_route) debug_printf("expanded list of hosts = '%s' options = '%s'\n",
hostlist, options);
/* Set default lookup type and scan the options */
-lookup_type = lk_default;
+lookup_type = LK_DEFAULT;
-while (*options != 0)
+while (*options)
{
unsigned n;
const uschar *s = options;
if (Ustrncmp(s, "randomize", n) == 0) randomize = TRUE;
else if (Ustrncmp(s, "no_randomize", n) == 0) randomize = FALSE;
- else if (Ustrncmp(s, "byname", n) == 0) lookup_type = lk_byname;
- else if (Ustrncmp(s, "bydns", n) == 0) lookup_type = lk_bydns;
+ else if (Ustrncmp(s, "byname", n) == 0)
+ lookup_type = lookup_type & ~(LK_DEFAULT | LK_BYDNS) | LK_BYNAME;
+ else if (Ustrncmp(s, "bydns", n) == 0)
+ lookup_type = lookup_type & ~(LK_DEFAULT | LK_BYNAME) & LK_BYDNS;
+ else if (Ustrncmp(s, "ipv4_prefer", n) == 0) lookup_type |= LK_IPV4_PREFER;
+ else if (Ustrncmp(s, "ipv4_only", n) == 0) lookup_type |= LK_IPV4_ONLY;
else
{
transport_instance *t;
- for (t = transports; t != NULL; t = t->next)
- if (Ustrcmp(t->name, s) == 0)
+ for (t = transports; t; t = t->next)
+ if (Ustrncmp(t->name, s, n) == 0)
{
transport = t;
individual_transport_set = TRUE;
break;
}
- if (t == NULL)
+ if (!t)
{
s = string_sprintf("unknown routing option or transport name \"%s\"", s);
log_write(0, LOG_MAIN, "Error in %s router: %s", rblock->name, s);
rc = rf_get_errors_address(addr, rblock, verify, &addr->prop.errors_address);
if (rc != OK) return rc;
-/* Set up the additional and removeable headers for this address. */
+/* Set up the additional and removable headers for this address. */
rc = rf_get_munge_headers(addr, rblock, &addr->prop.extra_headers,
&addr->prop.remove_headers);
/* Deal with the case of a local transport. The host list is passed over as a
single text string that ends up in $host. */
-if (transport != NULL && transport->info->local)
+if (transport && transport->info->local)
{
- if (hostlist[0] != 0)
+ if (hostlist[0])
{
host_item *h;
addr->host_list = h = store_get(sizeof(host_item));
list is mandatory in either case, except when verifying, in which case the
address is just accepted. */
-if (hostlist[0] == 0)
+if (!hostlist[0])
{
if (verify != v_none) goto ROUTED;
addr->message = string_sprintf("error in %s router: no host(s) specified "
/* Otherwise we finish the routing here by building a chain of host items
for the list of configured hosts, and then finding their addresses. */
-host_build_hostlist(&(addr->host_list), hostlist, randomize);
+host_build_hostlist(&addr->host_list, hostlist, randomize);
rc = rf_lookup_hostlist(rblock, addr, rblock->ignore_target_hosts, lookup_type,
ob->hff_code, addr_new);
if (rc != OK) return rc;
be ignored, in which case we will end up with an empty host list. What happens
is controlled by host_all_ignored. */
-if (addr->host_list == NULL)
+if (!addr->host_list)
{
int i;
DEBUG(D_route) debug_printf("host_find_failed ignored every host\n");
return OK;
}
+#endif /*!MACRO_PREDEF*/
/* End of routers/manualroute.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
int queryprogram_router_options_count =
sizeof(queryprogram_router_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+queryprogram_router_options_block queryprogram_router_option_defaults = {0};
+void queryprogram_router_init(router_instance *rblock) {}
+int queryprogram_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
/* Default private options block for the queryprogram router. */
queryprogram_router_options_block queryprogram_router_option_defaults = {
{
while (generated != NULL)
{
+ BOOL ignore_error = addr->prop.ignore_error;
address_item *next = generated;
+
generated = next->next;
next->parent = addr;
- orflag(next, addr, af_propagate);
next->prop = *addr_prop;
+ next->prop.ignore_error = next->prop.ignore_error || ignore_error;
next->start_router = rblock->redirect_router;
next->next = *addr_new;
if (*s != 0)
{
- int lookup_type = lk_default;
+ int lookup_type = LK_DEFAULT;
uschar *ss = expand_string(US"${extract{lookup}{$value}}");
lookup_value = NULL;
if (*ss != 0)
{
- if (Ustrcmp(ss, "byname") == 0) lookup_type = lk_byname;
- else if (Ustrcmp(ss, "bydns") == 0) lookup_type = lk_bydns;
+ if (Ustrcmp(ss, "byname") == 0) lookup_type = LK_BYNAME;
+ else if (Ustrcmp(ss, "bydns") == 0) lookup_type = LK_BYDNS;
else
{
addr->message = string_sprintf("bad lookup type \"%s\" yielded by "
OK : DEFER;
}
+#endif /*!MACRO_PREDEF*/
/* End of routers/queryprogram.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
int redirect_router_options_count =
sizeof(redirect_router_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy entries */
+redirect_router_options_block redirect_router_option_defaults = {0};
+void redirect_router_init(router_instance *rblock) {}
+int redirect_router_entry(router_instance *rblock, address_item *addr,
+ struct passwd *pw, int verify, address_item **addr_local,
+ address_item **addr_remote, address_item **addr_new,
+ address_item **addr_succeed) {return 0;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
/* Default private options block for the redirect router. */
redirect_router_options_block redirect_router_option_defaults = {
if (ob->one_time)
{
ob->forbid_pipe = ob->forbid_file = ob->forbid_filter_reply = TRUE;
- if (rblock->extra_headers != NULL || rblock->remove_headers != NULL)
+ if (rblock->extra_headers || rblock->remove_headers)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
"\"headers_add\" and \"headers_remove\" are not permitted with "
"\"one_time\"", rblock->name);
- if (rblock->unseen || rblock->expand_unseen != NULL)
+ if (rblock->unseen || rblock->expand_unseen)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
"\"unseen\" may not be used with \"one_time\"", rblock->name);
}
if (ob->check_owner == TRUE_UNSET)
ob->check_owner = rblock->check_local_user ||
- (ob->owners != NULL && ob->owners[0] != 0);
+ (ob->owners && ob->owners[0] != 0);
if (ob->check_group == TRUE_UNSET)
ob->check_group = (rblock->check_local_user && (ob->modemask & 020) == 0) ||
/* If explicit qualify domain set, the preserve option is locked out */
-if (ob->qualify_domain != NULL && ob->qualify_preserve_domain)
+if (ob->qualify_domain && ob->qualify_preserve_domain)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n "
"only one of \"qualify_domain\" or \"qualify_preserve_domain\" must be set",
rblock->name);
generated = next->next;
next->parent = addr;
- orflag(next, addr, af_ignore_error);
next->start_router = rblock->redirect_router;
if (addr->child_count == USHRT_MAX)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s router generated more than %d "
/* Don't do the "one_time" thing for the first pass of a 2-stage queue run. */
- if (ob->one_time && !queue_2stage)
+ if (ob->one_time && !f.queue_2stage)
{
for (parent = addr; parent->parent; parent = parent->parent) ;
next->onetime_parent = parent->address;
If so, we must take care to re-instate it when we copy in the propagated
data so that it overrides any errors_to setting on the router. */
- next->prop = *addr_prop;
- if (errors_address != NULL) next->prop.errors_address = errors_address;
+ {
+ BOOL ignore_error = next->prop.ignore_error;
+ next->prop = *addr_prop;
+ next->prop.ignore_error = ignore_error || addr->prop.ignore_error;
+ }
+ if (errors_address) next->prop.errors_address = errors_address;
/* For pipes, files, and autoreplies, record this router as handling them,
because they don't go through the routing process again. Then set up uid,
}
#ifdef SUPPORT_I18N
- next->prop.utf8_msg = string_is_utf8(next->address)
- || (sender_address && string_is_utf8(sender_address));
+ if (!next->prop.utf8_msg)
+ next->prop.utf8_msg = string_is_utf8(next->address)
+ || (sender_address && string_is_utf8(sender_address));
#endif
DEBUG(D_route)
addr_prop.srs_sender = NULL;
#endif
#ifdef SUPPORT_I18N
-addr_prop.utf8_msg = FALSE; /*XXX should we not copy this from the parent? */
-addr_prop.utf8_downcvt = FALSE;
-addr_prop.utf8_downcvt_maybe = FALSE;
+addr_prop.utf8_msg = addr->prop.utf8_msg;
+addr_prop.utf8_downcvt = addr->prop.utf8_downcvt;
+addr_prop.utf8_downcvt_maybe = addr->prop.utf8_downcvt_maybe;
#endif
/* When verifying and testing addresses, the "logwrite" command in filters
must be bypassed. */
-if (verify == v_none && !address_test_mode) options |= RDO_REALLOG;
+if (verify == v_none && !f.address_test_mode) options |= RDO_REALLOG;
/* Sort out the fixed or dynamic uid/gid. This uid is used (a) for reading the
file (and interpreting a filter) and (b) for running the transports for
high so that their completion does not mark the original address done. */
case FF_FREEZE:
- if (!deliver_manual_thaw)
+ if (!f.deliver_manual_thaw)
{
if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop))
!= OK) return xrc;
if (!moan_skipped_syntax_errors(
rblock->name, /* For message content */
eblock, /* Ditto */
- (verify != v_none || address_test_mode)?
+ (verify != v_none || f.address_test_mode)?
NULL : ob->syntax_errors_to, /* Who to mail */
generated != NULL, /* True if not all failed */
ob->syntax_errors_text)) /* Custom message */
if (frc == FF_DELIVERED)
{
- if (generated == NULL && verify == v_none && !address_test_mode)
+ if (generated == NULL && verify == v_none && !f.address_test_mode)
{
log_write(0, LOG_MAIN, "=> %s <%s> R=%s", discarded, addr->address,
rblock->name);
next->next = *addr_new;
*addr_new = next;
- /* Copy relevant flags (af_propagate is a name for the set), and set the
- data that propagates. */
+ /* Set the data that propagates. */
- copyflag(next, addr, af_propagate);
next->prop = addr_prop;
DEBUG(D_route) debug_printf("%s router autogenerated %s\n%s%s%s",
return yield;
}
+#endif /*!MACRO_PREDEF*/
/* End of routers/redirect.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
{
address_item *parent = store_get(sizeof(address_item));
uschar *at = Ustrrchr(addr->address, '@');
-uschar *address = string_sprintf("%.*s@%s", at - addr->address, addr->address,
- domain);
+uschar *address = string_sprintf("%.*s@%s",
+ (int)(at - addr->address), addr->address, domain);
DEBUG(D_route) debug_printf("domain changed to %s\n", domain);
if (newh != NULL)
{
h = newh;
- header_rewritten = TRUE;
+ f.header_rewritten = TRUE;
}
}
}
{
uschar *yield = expand_string(s);
if (yield != NULL) return yield;
-if (expand_string_forcedfail)
+if (f.expand_string_forcedfail)
{
DEBUG(D_route) debug_printf("forced failure for expansion of \"%s\"\n", s);
*prc = DECLINE;
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
if (s == NULL)
{
- if (expand_string_forcedfail)
+ if (f.expand_string_forcedfail)
{
DEBUG(D_route)
debug_printf("forced expansion failure - ignoring errors_to\n");
if (*s == 0)
{
- setflag(addr, af_ignore_error); /* For locally detected errors */
+ addr->prop.ignore_error = TRUE; /* For locally detected errors */
*errors_to = US""; /* Return path for SMTP */
return OK;
}
}
else
{
- BOOL save_address_test_mode = address_test_mode;
+ BOOL save_address_test_mode = f.address_test_mode;
int save1 = 0;
int i;
const uschar ***p;
for (i = 0, p = address_expansions; *p != NULL;)
address_expansions_save[i++] = **p++;
- address_test_mode = FALSE;
+ f.address_test_mode = FALSE;
/* NOTE: the address is verified as a recipient, not a sender. This is
perhaps confusing. It isn't immediately obvious what to do: we want to have
DEBUG(D_route|D_verify)
debug_printf("------ End verifying errors address %s ------\n", s);
- address_test_mode = save_address_test_mode;
+ f.address_test_mode = save_address_test_mode;
for (i = 0, p = address_expansions; *p != NULL;)
**p++ = address_expansions_save[i++];
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
while ((s = string_nextinlist(&list, &sep, NULL, 0)))
if (!(s = expand_string(s)))
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
addr->message = string_sprintf(
"%s router failed to expand add_headers item \"%s\": %s",
const uschar * list = rblock->remove_headers;
int sep = ':';
uschar * s;
+ gstring * g = NULL;
+
+ if (*remove_headers)
+ g = string_cat(NULL, *remove_headers);
while ((s = string_nextinlist(&list, &sep, NULL, 0)))
if (!(s = expand_string(s)))
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
addr->message = string_sprintf(
"%s router failed to expand remove_headers item \"%s\": %s",
}
}
else if (*s)
- *remove_headers = string_append_listele(*remove_headers, ':', s);
+ g = string_append_listele(g, ':', s);
+
+ if (g)
+ *remove_headers = g->s;
}
return OK;
}
-/* vi: aw ai sw=4
+/* vi: aw ai sw=2
*/
/* End of rf_get_munge_headers.c */
rblock the router block
addr the address being routed
ignore_target_hosts list of hosts to ignore
- lookup_type lk_default or lk_byname or lk_bydns
+ lookup_type LK_DEFAULT or LK_BYNAME or LK_BYDNS,
+ plus LK_IPV4_{ONLY,PREFER}
hff_code what to do for host find failed
addr_new passed to rf_self_action for self=reroute
len = Ustrlen(h->name);
if (len > 3 && strcmpic(h->name + len - 3, US"/mx") == 0)
{
+ int whichrrs = lookup_type & LK_IPV4_ONLY
+ ? HOST_FIND_BY_MX | HOST_FIND_IPV4_ONLY
+ : lookup_type & LK_IPV4_PREFER
+ ? HOST_FIND_BY_MX | HOST_FIND_IPV4_FIRST
+ : HOST_FIND_BY_MX;
+
DEBUG(D_route|D_host_lookup)
debug_printf("doing DNS MX lookup for %s\n", h->name);
h->name = string_copyn(h->name, len - 3);
rc = host_find_bydns(h,
ignore_target_hosts,
- HOST_FIND_BY_MX, /* look only for MX records */
- NULL, /* SRV service not relevant */
- NULL, /* failing srv domains not relevant */
- NULL, /* no special mx failing domains */
+ whichrrs, /* look only for MX records */
+ NULL, /* SRV service not relevant */
+ NULL, /* failing srv domains not relevant */
+ NULL, /* no special mx failing domains */
&rblock->dnssec, /* dnssec request/require */
- NULL, /* fully_qualified_name */
- NULL); /* indicate local host removed */
+ NULL, /* fully_qualified_name */
+ NULL); /* indicate local host removed */
}
/* If explicitly configured to look up by name, or if the "host name" is
actually an IP address, do a byname lookup. */
- else if (lookup_type == lk_byname || string_is_ip_address(h->name, NULL) != 0)
+ else if (lookup_type & LK_BYNAME || string_is_ip_address(h->name, NULL) != 0)
{
DEBUG(D_route|D_host_lookup) debug_printf("calling host_find_byname\n");
rc = host_find_byname(h, ignore_target_hosts, HOST_FIND_QUALIFY_SINGLE,
else
{
BOOL removed;
+ int whichrrs = lookup_type & LK_IPV4_ONLY
+ ? HOST_FIND_BY_A
+ : lookup_type & LK_IPV4_PREFER
+ ? HOST_FIND_BY_A | HOST_FIND_BY_AAAA | HOST_FIND_IPV4_FIRST
+ : HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
+
DEBUG(D_route|D_host_lookup) debug_printf("doing DNS lookup\n");
- switch (rc = host_find_bydns(h, ignore_target_hosts, HOST_FIND_BY_A, NULL,
+ switch (rc = host_find_bydns(h, ignore_target_hosts, whichrrs, NULL,
NULL, NULL,
&rblock->dnssec, /* domains for request/require */
&canonical_name, &removed))
if (removed) setflag(addr, af_local_host_removed);
break;
case HOST_FIND_FAILED:
- if (lookup_type == lk_default)
+ if (lookup_type & LK_DEFAULT)
{
DEBUG(D_route|D_host_lookup)
debug_printf("DNS lookup failed: trying getipnodebyname\n");
/* Temporary failure defers, unless pass_on_timeout is set */
+ if (rc == HOST_FIND_SECURITY)
+ {
+ addr->message = string_sprintf("host lookup for %s done insecurely" , h->name);
+ addr->basic_errno = ERRNO_DNSDEFER;
+ return DEFER;
+ }
if (rc == HOST_FIND_AGAIN)
{
if (rblock->pass_on_timeout)
addr->message =
string_sprintf("lookup of host \"%s\" failed in %s router%s",
h->name, rblock->name,
- host_find_failed_syntax? ": syntax error in name" : "");
+ f.host_find_failed_syntax? ": syntax error in name" : "");
if (hff_code == hff_defer) return DEFER;
if (hff_code == hff_fail) return FAIL;
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
/* Handle a local transport */
-if (addr->transport != NULL && addr->transport->info->local)
+if (addr->transport && addr->transport->info->local)
{
ugid_block ugid;
When getting the home directory out of the password information, set the
flag that prevents expansion later. */
- if (pw != NULL)
+ if (pw)
{
addr->uid = pw->pw_uid;
addr->gid = pw->pw_gid;
- setflag(addr, af_uid_set|af_gid_set|af_home_expanded);
+ setflag(addr, af_uid_set);
+ setflag(addr, af_gid_set);
+ setflag(addr, af_home_expanded);
addr->home_dir = string_copy(US pw->pw_dir);
}
otherwise use the expanded value of router_home_directory. The flag also
tells the transport not to re-expand it. */
- if (rblock->home_directory != NULL)
+ if (rblock->home_directory)
{
addr->home_dir = rblock->home_directory;
clearflag(addr, af_home_expanded);
}
- else if (addr->home_dir == NULL && testflag(addr, af_home_expanded))
+ else if (!addr->home_dir && testflag(addr, af_home_expanded))
addr->home_dir = deliver_home;
addr->current_dir = rblock->current_directory;
rf_self_action(address_item *addr, host_item *host, int code, BOOL rewrite,
uschar *new, address_item **addr_new)
{
-uschar *msg = (host->mx >= 0)?
- US"lowest numbered MX record points to local host" :
- US"remote host address is the local host";
+uschar * msg = host->mx >= 0
+ ? US"lowest numbered MX record points to local host"
+ : US"remote host address is the local host";
switch (code)
{
case self_freeze:
- /* If there is no message id, this is happening during an address
- verification, so give information about the address that is being verified,
- and where it has come from. Otherwise, during message delivery, the normal
- logging for the address will be sufficient. */
-
- if (message_id[0] == 0)
- {
- if (sender_fullhost == NULL)
- {
- log_write(0, LOG_MAIN, "%s: %s (while routing <%s>)", msg,
- addr->domain, addr->address);
- }
+ /* If there is no message id, this is happening during an address
+ verification, so give information about the address that is being verified,
+ and where it has come from. Otherwise, during message delivery, the normal
+ logging for the address will be sufficient. */
+
+ if (message_id[0] == 0)
+ if (sender_fullhost)
+ log_write(0, LOG_MAIN, "%s: %s (while verifying <%s> from host %s)",
+ msg, addr->domain, addr->address, sender_fullhost);
+ else
+ log_write(0, LOG_MAIN, "%s: %s (while routing <%s>)", msg,
+ addr->domain, addr->address);
else
- {
- log_write(0, LOG_MAIN, "%s: %s (while verifying <%s> from host %s)",
- msg, addr->domain, addr->address, sender_fullhost);
- }
- }
- else
- log_write(0, LOG_MAIN, "%s: %s", msg, addr->domain);
-
- addr->message = msg;
- addr->special_action = SPECIAL_FREEZE;
- return DEFER;
+ log_write(0, LOG_MAIN, "%s: %s", msg, addr->domain);
+
+ addr->message = msg;
+ addr->special_action = SPECIAL_FREEZE;
+ return DEFER;
case self_defer:
- addr->message = msg;
- return DEFER;
+ addr->message = msg;
+ return DEFER;
case self_reroute:
- DEBUG(D_route)
- {
- debug_printf("%s: %s", msg, addr->domain);
- debug_printf(": domain changed to %s\n", new);
- }
- rf_change_domain(addr, new, rewrite, addr_new);
- return REROUTED;
+ DEBUG(D_route)
+ debug_printf("%s: %s: domain changed to %s\n", msg, addr->domain, new);
+ rf_change_domain(addr, new, rewrite, addr_new);
+ return REROUTED;
case self_send:
- DEBUG(D_route)
- {
- debug_printf("%s: %s", msg, addr->domain);
- debug_printf(": configured to try delivery anyway\n");
- }
- return OK;
+ DEBUG(D_route)
+ debug_printf("%s: %s: configured to try delivery anyway\n", msg, addr->domain);
+ return OK;
case self_pass: /* This is soft failure; pass to next router */
- DEBUG(D_route)
- {
- debug_printf("%s: %s", msg, addr->domain);
- debug_printf(": passed to next router (self = pass)\n");
- }
- addr->message = msg;
- addr->self_hostname = string_copy(host->name);
- return PASS;
+ DEBUG(D_route)
+ debug_printf("%s: %s: passed to next router (self = pass)\n", msg, addr->domain);
+ addr->message = msg;
+ addr->self_hostname = string_copy(host->name);
+ return PASS;
case self_fail:
- DEBUG(D_route)
- {
- debug_printf("%s: %s", msg, addr->domain);
- debug_printf(": address failed (self = fail)\n");
- }
- addr->message = msg;
- setflag(addr, af_pass_message);
- return FAIL;
+ DEBUG(D_route)
+ debug_printf("%s: %s: address failed (self = fail)\n", msg, addr->domain);
+ addr->message = msg;
+ setflag(addr, af_pass_message);
+ return FAIL;
}
return DEFER; /* paranoia */
static uschar *
internal_search_find(void *handle, uschar *filename, uschar *keystring)
{
-tree_node *t = (tree_node *)handle;
-search_cache *c = (search_cache *)(t->data.ptr);
-expiring_data *e;
-uschar *data = NULL;
+tree_node * t = (tree_node *)handle;
+search_cache * c = (search_cache *)(t->data.ptr);
+expiring_data * e = NULL; /* compiler quietening */
+uschar * data = NULL;
int search_type = t->name[0] - '0';
int old_pool = store_pool;
the callers don't have to test for NULL, set an empty string. */
search_error_message = US"";
-search_find_defer = FALSE;
+f.search_find_defer = FALSE;
DEBUG(D_lookup) debug_printf("internal_search_find: file=\"%s\"\n "
"type=%s key=\"%s\"\n", filename,
if (lookup_list[search_type]->find(c->handle, filename, keystring, keylength,
&data, &search_error_message, &do_cache) == DEFER)
- search_find_defer = TRUE;
+ f.search_find_defer = TRUE;
/* A record that has been found is now in data, which is either NULL
or points to a bit of dynamic store. Cache the result of the lookup if
{
if (data)
debug_printf("lookup yielded: %s\n", data);
- else if (search_find_defer)
+ else if (f.search_find_defer)
debug_printf("lookup deferred: %s\n", search_error_message);
else debug_printf("lookup failed\n");
}
entry but could have been partial, flag to set up variables. */
yield = internal_search_find(handle, filename, keystring);
-if (search_find_defer) return NULL;
+if (f.search_find_defer) return NULL;
if (yield != NULL) { if (partial >= 0) set_null_wild = TRUE; }
/* Not matched a complete entry; handle partial lookups, but only if the full
Ustrcpy(keystring2 + affixlen, keystring);
DEBUG(D_lookup) debug_printf("trying partial match %s\n", keystring2);
yield = internal_search_find(handle, filename, keystring2);
- if (search_find_defer) return NULL;
+ if (f.search_find_defer) return NULL;
}
/* The key in its entirety did not match a wild entry; try chopping off
DEBUG(D_lookup) debug_printf("trying partial match %s\n", keystring3);
yield = internal_search_find(handle, filename, keystring3);
- if (search_find_defer) return NULL;
+ if (f.search_find_defer) return NULL;
if (yield != NULL)
{
/* First variable is the wild part; second is the fixed part. Take care
DEBUG(D_lookup) debug_printf("trying default match %s\n", atat);
yield = internal_search_find(handle, filename, atat);
*atat = savechar;
- if (search_find_defer) return NULL;
+ if (f.search_find_defer) return NULL;
if (yield != NULL && expand_setup != NULL && *expand_setup >= 0)
{
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) Jeremy Harris 2016 */
+/* Copyright (c) Jeremy Harris 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* SHA routine selection */
# if GNUTLS_VERSION_NUMBER >= 0x020a00
# define SHA_GNUTLS
# if GNUTLS_VERSION_NUMBER >= 0x030500
-# define EXIM_HAVE_SHA3
+# define EXIM_HAVE_SHA3 /*MMMM*/
# endif
# else
# define SHA_GCRYPT
# else
# define SHA_OPENSSL
+# include <openssl/ssl.h>
+# if (OPENSSL_VERSION_NUMBER >= 0x10101000L) && !defined(LIBRESSL_VERSION_NUMBER)
+# define EXIM_HAVE_SHA3
+# endif
# endif
#else
*************************************************/
/* Copyright (c) Michael Haardt 2003 - 2015
- * Copyright (c) The Exim Maintainers 2016
+ * Copyright (c) The Exim Maintainers 2016 - 2018
* See the file NOTICE for conditions of use and distribution.
*/
#include "exim.h"
#if HAVE_ICONV
-#include <iconv.h>
+# include <iconv.h>
#endif
/* Define this for RFC compliant \r\n end-of-line terminators. */
static const struct String str_cc={ str_cc_c, 2 };
static uschar str_bcc_c[]="Bcc";
static const struct String str_bcc={ str_bcc_c, 3 };
+#ifdef ENVELOPE_AUTH
static uschar str_auth_c[]="auth";
static const struct String str_auth={ str_auth_c, 4 };
+#endif
static uschar str_sender_c[]="Sender";
static const struct String str_sender={ str_sender_c, 6 };
static uschar str_resent_from_c[]="Resent-From";
else
{ /* encoded char */
new += sprintf(CS new,"=%02X",ch);
- new+=3;
}
line+=3;
}
-1 syntax error
*/
-static int parse_mailto_uri(struct Sieve *filter, const uschar *uri, string_item **recipient, struct String *header, struct String *subject, struct String *body)
+static int
+parse_mailto_uri(struct Sieve *filter, const uschar *uri,
+ string_item **recipient, struct String *header, struct String *subject,
+ struct String *body)
{
const uschar *start;
struct String to, hname;
-struct String hvalue = {NULL, 0};
-int capacity;
+struct String hvalue = {.character = NULL, .length = 0};
string_item *new;
if (Ustrncmp(uri,"mailto:",7))
for (start=uri; *uri && *uri!='?' && (*uri!='%' || *(uri+1)!='2' || tolower(*(uri+2))!='c'); ++uri);
if (uri>start)
{
- capacity=0;
- to.character= NULL;
- to.length=0;
- to.character=string_catn(to.character, &capacity, &to.length, start, uri-start);
- to.character[to.length]='\0';
+ gstring * g = string_catn(NULL, start, uri-start);
+
+ to.character = string_from_gstring(g);
+ to.length = g->ptr;
if (uri_decode(&to)==-1)
{
filter->errmsg=US"Invalid URI encoding";
return -1;
}
- new=store_get(sizeof(string_item));
- new->text=store_get(to.length+1);
- if (to.length) memcpy(new->text,to.character,to.length);
- new->text[to.length]='\0';
- new->next=*recipient;
- *recipient=new;
+ new=store_get(sizeof(string_item));
+ new->text=store_get(to.length+1);
+ if (to.length) memcpy(new->text,to.character,to.length);
+ new->text[to.length]='\0';
+ new->next=*recipient;
+ *recipient=new;
}
else
{
for (start=uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(),%",*uri)); ++uri);
if (uri>start)
{
- capacity=0;
- hname.character= NULL;
- hname.length=0;
- hname.character = string_catn(hname.character, &capacity, &hname.length, start, uri-start);
- hname.character[hname.length]='\0';
+ gstring * g = string_catn(NULL, start, uri-start);
+
+ hname.character = string_from_gstring(g);
+ hname.length = g->ptr;
if (uri_decode(&hname)==-1)
{
filter->errmsg=US"Invalid URI encoding";
for (start=uri; *uri && (isalnum(*uri) || strchr("$-_.+!*'(),%",*uri)); ++uri);
if (uri>start)
{
- capacity=0;
- hvalue.character= NULL;
- hvalue.length=0;
- hvalue.character=string_catn(hvalue.character,&capacity,&hvalue.length,start,uri-start);
- hvalue.character[hvalue.length]='\0';
+ gstring * g = string_catn(NULL, start, uri-start);
+
+ hname.character = string_from_gstring(g);
+ hname.length = g->ptr;
if (uri_decode(&hvalue)==-1)
{
filter->errmsg=US"Invalid URI encoding";
for (i=ignore; i<end && !eq_asciicase(&hname,i,0); ++i);
if (i==end)
{
- if (header->length==-1) header->length=0;
- capacity=header->length;
- header->character=string_catn(header->character,&capacity,&header->length,hname.character,hname.length);
- header->character=string_catn(header->character,&capacity,&header->length,CUS ": ",2);
- header->character=string_catn(header->character,&capacity,&header->length,hvalue.character,hvalue.length);
- header->character=string_catn(header->character,&capacity,&header->length,CUS "\n",1);
- header->character[header->length]='\0';
+ gstring * g;
+
+ if (header->length==-1) header->length = 0;
+
+ g = string_catn(NULL, header->character, header->length);
+ g = string_catn(g, hname.character, hname.length);
+ g = string_catn(g, CUS ": ", 2);
+ g = string_catn(g, hvalue.character, hvalue.length);
+ g = string_catn(g, CUS "\n", 1);
+
+ header->character = string_from_gstring(g);
+ header->length = g->ptr;
}
}
if (*uri=='&') ++uri;
Returns: quoted string
*/
-static const uschar *quote(const struct String *header)
+static const uschar *
+quote(const struct String *header)
{
-uschar *quoted=NULL;
-int size=0,ptr=0;
+gstring * quoted = NULL;
size_t l;
const uschar *h;
switch (*h)
{
case '\0':
- {
- quoted=string_catn(quoted,&size,&ptr,CUS "\\0",2);
+ quoted = string_catn(quoted, CUS "\\0", 2);
break;
- }
case '$':
case '{':
case '}':
- {
- quoted=string_catn(quoted,&size,&ptr,CUS "\\",1);
- }
+ quoted = string_catn(quoted, CUS "\\", 1);
default:
- {
- quoted=string_catn(quoted,&size,&ptr,h,1);
- }
+ quoted = string_catn(quoted, h, 1);
}
++h;
--l;
}
-quoted=string_catn(quoted,&size,&ptr,CUS "",1);
-return quoted;
+quoted = string_catn(quoted, CUS "", 1);
+return string_from_gstring(quoted);
}
Returns: nothing
*/
-static void add_addr(address_item **generated, uschar *addr, int file, int maxage, int maxmessages, int maxstorage)
+static void
+add_addr(address_item **generated, uschar *addr, int file, int maxage, int maxmessages, int maxstorage)
{
address_item *new_addr;
for (new_addr=*generated; new_addr; new_addr=new_addr->next)
- {
- if (Ustrcmp(new_addr->address,addr)==0 && (file ? testflag(new_addr, af_pfr|af_file) : 1))
+ if ( Ustrcmp(new_addr->address,addr) == 0
+ && ( !file
+ || testflag(new_addr, af_pfr)
+ || testflag(new_addr, af_file)
+ )
+ )
{
if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
- {
debug_printf("Repeated %s `%s' ignored.\n",file ? "fileinto" : "redirect", addr);
- }
+
return;
}
- }
if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
- {
debug_printf("%s `%s'\n",file ? "fileinto" : "redirect", addr);
- }
-new_addr=deliver_make_addr(addr,TRUE);
+
+new_addr = deliver_make_addr(addr,TRUE);
if (file)
{
- setflag(new_addr, af_pfr|af_file);
+ setflag(new_addr, af_pfr);
+ setflag(new_addr, af_file);
new_addr->mode = 0;
}
new_addr->prop.errors_address = NULL;
0 identifier not matched
*/
-static int parse_string(struct Sieve *filter, struct String *data)
+static int
+parse_string(struct Sieve *filter, struct String *data)
{
-int dataCapacity=0;
+gstring * g = NULL;
+
+data->length = 0;
+data->character = NULL;
-data->length=0;
-data->character=(uschar*)0;
if (*filter->pc=='"') /* quoted string */
{
++filter->pc;
{
if (*filter->pc=='"') /* end of string */
{
- int foo=data->length;
-
++filter->pc;
+
+ if (g)
+ {
+ data->character = string_from_gstring(g);
+ data->length = g->ptr;
+ }
+ else
+ data->character = US"\0";
/* that way, there will be at least one character allocated */
- data->character=string_catn(data->character,&dataCapacity,&foo,CUS "",1);
+
#ifdef ENCODED_CHARACTER
if (filter->require_encoded_character
&& string_decode(filter,data)==-1)
}
else if (*filter->pc=='\\' && *(filter->pc+1)) /* quoted character */
{
- data->character=string_catn(data->character,&dataCapacity,&data->length,filter->pc+1,1);
+ g = string_catn(g, filter->pc+1, 1);
filter->pc+=2;
}
else /* regular character */
#else
if (*filter->pc=='\n')
{
- data->character=string_catn(data->character,&dataCapacity,&data->length,US"\r",1);
+ g = string_catn(g, US"\r", 1);
++filter->line;
}
#endif
- data->character=string_catn(data->character,&dataCapacity,&data->length,filter->pc,1);
+ g = string_catn(g, filter->pc, 1);
filter->pc++;
}
}
if (*filter->pc=='\n') /* end of line */
#endif
{
- data->character=string_catn(data->character,&dataCapacity,&data->length,CUS "\r\n",2);
+ g = string_catn(g, CUS "\r\n", 2);
#ifdef RFC_EOL
filter->pc+=2;
#else
if (*filter->pc=='.' && *(filter->pc+1)=='\n') /* end of string */
#endif
{
- int foo=data->length;
+ if (g)
+ {
+ data->character = string_from_gstring(g);
+ data->length = g->ptr;
+ }
+ else
+ data->character = US"\0";
+ /* that way, there will be at least one character allocated */
- /* that way, there will be at least one character allocated */
- data->character=string_catn(data->character,&dataCapacity,&foo,CUS "",1);
#ifdef RFC_EOL
filter->pc+=3;
#else
}
else if (*filter->pc=='.' && *(filter->pc+1)=='.') /* remove dot stuffing */
{
- data->character=string_catn(data->character,&dataCapacity,&data->length,CUS ".",1);
+ g = string_catn(g, CUS ".", 1);
filter->pc+=2;
}
}
else /* regular character */
{
- data->character=string_catn(data->character,&dataCapacity,&data->length,filter->pc,1);
+ g = string_catn(g, filter->pc, 1);
filter->pc++;
}
}
if (dataLength+1 >= dataCapacity) /* increase buffer */
{
struct String *new;
- int newCapacity; /* Don't amalgamate with next line; some compilers grumble */
dataCapacity = dataCapacity ? dataCapacity * 2 : 4;
new = store_get(sizeof(struct String) * dataCapacity);
filter->errmsg=CUS "header string expansion failed";
return -1;
}
- parse_allow_group = TRUE;
+ f.parse_allow_group = TRUE;
while (*header_value && !*cond)
{
uschar *error;
if (saveend == 0) break;
header_value = end_addr + 1;
}
- parse_allow_group = FALSE;
- parse_found_group = FALSE;
+ f.parse_allow_group = FALSE;
+ f.parse_found_group = FALSE;
}
}
return 1;
if (exec)
{
address_item *addr;
- int capacity,start;
uschar *buffer;
int buffer_capacity;
- struct String key;
md5 base;
uschar digest[16];
uschar hexdigest[33];
int i;
- uschar *once;
+ gstring * once;
if (filter_personal(aliases,TRUE))
{
}
/* build oncelog filename */
- key.character=(uschar*)0;
- key.length=0;
- capacity=0;
+ md5_start(&base);
+
if (handle.length==-1)
{
- if (subject.length!=-1) key.character=string_catn(key.character,&capacity,&key.length,subject.character,subject.length);
- if (from.length!=-1) key.character=string_catn(key.character,&capacity,&key.length,from.character,from.length);
- key.character=string_catn(key.character,&capacity,&key.length,reason_is_mime?US"1":US"0",1);
- key.character=string_catn(key.character,&capacity,&key.length,reason.character,reason.length);
+ gstring * key = NULL;
+ if (subject.length!=-1) key =string_catn(key, subject.character, subject.length);
+ if (from.length!=-1) key = string_catn(key, from.character, from.length);
+ key = string_catn(key, reason_is_mime?US"1":US"0", 1);
+ key = string_catn(key, reason.character, reason.length);
+ md5_end(&base, key->s, key->ptr, digest);
}
else
- key=handle;
- md5_start(&base);
- md5_end(&base, key.character, key.length, digest);
+ md5_end(&base, handle.character, handle.length, digest);
+
for (i = 0; i < 16; i++) sprintf(CS (hexdigest+2*i), "%02X", digest[i]);
+
if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
- {
debug_printf("Sieve: mail was personal, vacation file basename: %s\n", hexdigest);
- }
+
if (filter_test == FTEST_NONE)
{
- capacity=Ustrlen(filter->vacation_directory);
- start=capacity;
- once=string_catn(filter->vacation_directory,&capacity,&start,US"/",1);
- once=string_catn(once,&capacity,&start,hexdigest,33);
- once[start] = '\0';
+ once = string_cat (NULL, filter->vacation_directory);
+ once = string_catn(once, US"/", 1);
+ once = string_catn(once, hexdigest, 33);
/* process subject */
subject_def=expand_string(US"${if def:header_subject {true}{false}}");
if (Ustrcmp(subject_def,"true")==0)
{
+ gstring * g = string_catn(NULL, US"Auto: ", 6);
+
expand_header(&subject,&str_subject);
- capacity=6;
- start=6;
- subject.character=string_catn(US"Auto: ",&capacity,&start,subject.character,subject.length);
- subject.length=start;
+ g = string_catn(g, subject.character, subject.length);
+ subject.character = string_from_gstring(g);
+ subject.length = g->ptr;
}
else
{
addr = deliver_make_addr(string_sprintf(">%.256s", sender_address), FALSE);
setflag(addr, af_pfr);
- setflag(addr, af_ignore_error);
+ addr->prop.ignore_error = TRUE;
addr->next = *generated;
*generated = addr;
addr->reply = store_get(sizeof(reply_item));
buffer=store_get(buffer_capacity);
/* deconst cast safe as we pass in a non-const item */
addr->reply->subject = US parse_quote_2047(subject.character, subject.length, US"utf-8", buffer, buffer_capacity, TRUE);
- addr->reply->oncelog=once;
+ addr->reply->oncelog = string_from_gstring(once);
addr->reply->once_repeat=days*86400;
/* build body and MIME headers */
for
(
- mime_body=reason.character,reason_end=reason.character+reason.length;
- mime_body<(reason_end-(sizeof(nlnl)-1)) && memcmp(mime_body,nlnl,(sizeof(nlnl)-1));
+ mime_body = reason.character, reason_end = reason.character + reason.length;
+ mime_body < (reason_end-(sizeof(nlnl)-1)) && memcmp(mime_body, nlnl, (sizeof(nlnl)-1));
++mime_body
);
- capacity = 0;
- start = 0;
- addr->reply->headers = string_catn(NULL,&capacity,&start,reason.character,mime_body-reason.character);
- addr->reply->headers[start] = '\0';
- capacity = 0;
- start = 0;
+
+ addr->reply->headers = string_copyn(reason.character, mime_body-reason.character);
+
if (mime_body+(sizeof(nlnl)-1)<reason_end) mime_body+=(sizeof(nlnl)-1);
else mime_body=reason_end-1;
- addr->reply->text = string_catn(NULL,&capacity,&start,mime_body,reason_end-mime_body);
- addr->reply->text[start] = '\0';
+ addr->reply->text = string_copyn(mime_body, reason_end-mime_body);
}
else
{
- struct String qp = { NULL, 0 }; /* Keep compiler happy (PH) */
+ struct String qp = { .character = NULL, .length = 0 }; /* Keep compiler happy (PH) */
- capacity = 0;
- start = reason.length;
addr->reply->headers = US"MIME-Version: 1.0\n"
"Content-Type: text/plain;\n"
"\tcharset=\"utf-8\"\n"
}
}
else if ((filter_test != FTEST_NONE && debug_selector != 0) || (debug_selector & D_filter) != 0)
- {
debug_printf("Sieve: mail was not personal, vacation would ignore it\n");
- }
}
}
else break;
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for handling an incoming SMTP call. */
* Local static variables *
*************************************************/
-static auth_instance *authenticated_by;
-static BOOL auth_advertised;
+static struct {
+ BOOL auth_advertised :1;
#ifdef SUPPORT_TLS
-static BOOL tls_advertised;
+ BOOL tls_advertised :1;
+# ifdef EXPERIMENTAL_REQUIRETLS
+ BOOL requiretls_advertised :1;
+# endif
#endif
-static BOOL dsn_advertised;
-static BOOL esmtp;
-static BOOL helo_required = FALSE;
-static BOOL helo_verify = FALSE;
-static BOOL helo_seen;
-static BOOL helo_accept_junk;
-static BOOL count_nonmail;
-static BOOL pipelining_advertised;
-static BOOL rcpt_smtp_response_same;
-static BOOL rcpt_in_progress;
-static int nonmail_command_count;
-static BOOL smtp_exit_function_called = 0;
+ BOOL dsn_advertised :1;
+ BOOL esmtp :1;
+ BOOL helo_required :1;
+ BOOL helo_verify :1;
+ BOOL helo_seen :1;
+ BOOL helo_accept_junk :1;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL pipe_connect_acceptable :1;
+#endif
+ BOOL rcpt_smtp_response_same :1;
+ BOOL rcpt_in_progress :1;
+ BOOL smtp_exit_function_called :1;
#ifdef SUPPORT_I18N
-static BOOL smtputf8_advertised;
+ BOOL smtputf8_advertised :1;
#endif
+} fl = {
+ .helo_required = FALSE,
+ .helo_verify = FALSE,
+ .smtp_exit_function_called = FALSE,
+};
+
+static auth_instance *authenticated_by;
+static int count_nonmail;
+static int nonmail_command_count;
static int synprot_error_count;
static int unknown_command_count;
static int sync_cmd_limit;
{ "helo", sizeof("helo")-1, HELO_CMD, TRUE, FALSE },
{ "ehlo", sizeof("ehlo")-1, EHLO_CMD, TRUE, FALSE },
{ "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE },
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
{ "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE },
- { "tls_auth", 0, TLS_AUTH_CMD, FALSE, TRUE },
- #endif
+ { "tls_auth", 0, TLS_AUTH_CMD, FALSE, FALSE },
+#endif
/* If you change anything above here, also fix the definitions below. */
ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
#ifdef SUPPORT_I18N
ENV_MAIL_OPT_UTF8,
+#endif
+#ifdef EXPERIMENTAL_REQUIRETLS
+ ENV_MAIL_OPT_REQTLS,
#endif
};
typedef struct {
{ US"ENVID", ENV_MAIL_OPT_ENVID, TRUE },
#ifdef SUPPORT_I18N
{ US"SMTPUTF8",ENV_MAIL_OPT_UTF8, FALSE }, /* rfc6531 */
+#endif
+#ifdef EXPERIMENTAL_REQUIRETLS
+ /* https://tools.ietf.org/html/draft-ietf-uta-smtp-require-tls-03 */
+ { US"REQUIRETLS",ENV_MAIL_OPT_REQTLS, FALSE },
#endif
/* keep this the last entry */
{ US"NULL", ENV_MAIL_OPT_NULL, FALSE },
*************************************************/
/* Synchronization checks can never be perfect because a packet may be on its
-way but not arrived when the check is done. Such checks can in any case only be
-done when TLS is not in use. Normally, the checks happen when commands are
-read: Exim ensures that there is no more input in the input buffer. In normal
-cases, the response to the command will be fast, and there is no further check.
+way but not arrived when the check is done. Normally, the checks happen when
+commands are read: Exim ensures that there is no more input in the input buffer.
+In normal cases, the response to the command will be fast, and there is no
+further check.
However, for some commands an ACL is run, and that can include delays. In those
cases, it is useful to do another check on the input just before sending the
*/
static BOOL
-check_sync(void)
+wouldblock_reading(void)
{
int fd, rc;
fd_set fds;
struct timeval tzero;
-if (!smtp_enforce_sync || sender_host_address == NULL ||
- sender_host_notsocket || tls_in.active >= 0)
- return TRUE;
+#ifdef SUPPORT_TLS
+if (tls_in.active.sock >= 0)
+ return !tls_could_read();
+#endif
+
+if (smtp_inptr < smtp_inend)
+ return FALSE;
fd = fileno(smtp_in);
FD_ZERO(&fds);
if (rc < 0) return TRUE; /* End of file or error */
smtp_ungetc(rc);
-rc = smtp_inend - smtp_inptr;
-if (rc > 150) rc = 150;
-smtp_inptr[rc] = 0;
return FALSE;
}
+static BOOL
+check_sync(void)
+{
+if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket)
+ return TRUE;
+
+return wouldblock_reading();
+}
+
+
+/* If there's input waiting (and we're doing pipelineing) then we can pipeline
+a reponse with the one following. */
+
+static BOOL
+pipeline_response(void)
+{
+if ( !smtp_enforce_sync || !sender_host_address
+ || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised)
+ return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_pipelining_used = TRUE;
+return TRUE;
+}
+
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+static BOOL
+pipeline_connect_sends(void)
+{
+if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable)
+ return FALSE;
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_early_pipe_used = TRUE;
+return TRUE;
+}
+#endif
/*************************************************
* Log incomplete transactions *
+void
+smtp_command_timeout_exit(void)
+{
+log_write(L_lost_incoming_connection,
+ LOG_MAIN, "SMTP command timeout on%s connection from %s",
+ tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
+if (smtp_batched_input)
+ moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
+smtp_notquit_exit(US"command-timeout", US"421",
+ US"%s: SMTP command timeout - closing connection",
+ smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_command_sigterm_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
+if (smtp_batched_input)
+ moan_smtp_batch(NULL, "421 SIGTERM received"); /* Does not return */
+smtp_notquit_exit(US"signal-exit", US"421",
+ US"%s: Service not available - closing connection", smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_data_timeout_exit(void)
+{
+log_write(L_lost_incoming_connection,
+ LOG_MAIN, "SMTP data timeout (message abandoned) on connection from %s F=<%s>",
+ sender_fullhost ? sender_fullhost : US"local process", sender_address);
+receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
+/* Does not return */
+}
+
+void
+smtp_data_sigint_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after %s",
+ smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT");
+receive_bomb_out(US"signal-exit",
+ US"Service not available - SIGTERM or SIGINT received");
+/* Does not return */
+}
+
+
+
+/* Refill the buffer, and notify DKIM verification code.
+Return false for error or EOF.
+*/
+
+static BOOL
+smtp_refill(unsigned lim)
+{
+int rc, save_errno;
+if (!smtp_out) return FALSE;
+fflush(smtp_out);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
+
+/* Limit amount read, so non-message data is not fed to DKIM.
+Take care to not touch the safety NUL at the end of the buffer. */
+
+rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
+save_errno = errno;
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
+if (rc <= 0)
+ {
+ /* Must put the error text in fixed store, because this might be during
+ header reading, where it releases unused store above the header. */
+ if (rc < 0)
+ {
+ if (had_command_timeout) /* set by signal handler */
+ smtp_command_timeout_exit(); /* does not return */
+ if (had_command_sigterm)
+ smtp_command_sigterm_exit();
+ if (had_data_timeout)
+ smtp_data_timeout_exit();
+ if (had_data_sigint)
+ smtp_data_sigint_exit();
+
+ smtp_had_error = save_errno;
+ smtp_read_error = string_copy_malloc(
+ string_sprintf(" (error: %s)", strerror(save_errno)));
+ }
+ else
+ smtp_had_eof = 1;
+ return FALSE;
+ }
+#ifndef DISABLE_DKIM
+dkim_exim_verify_feed(smtp_inbuffer, rc);
+#endif
+smtp_inend = smtp_inbuffer + rc;
+smtp_inptr = smtp_inbuffer;
+return TRUE;
+}
+
/*************************************************
* SMTP version of getc() *
*************************************************/
smtp_getc(unsigned lim)
{
if (smtp_inptr >= smtp_inend)
- {
- int rc, save_errno;
- if (!smtp_out) return EOF;
- fflush(smtp_out);
- if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-
- /* Limit amount read, so non-message data is not fed to DKIM */
-
- rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE, lim));
- save_errno = errno;
- alarm(0);
- if (rc <= 0)
- {
- /* Must put the error text in fixed store, because this might be during
- header reading, where it releases unused store above the header. */
- if (rc < 0)
- {
- smtp_had_error = save_errno;
- smtp_read_error = string_copy_malloc(
- string_sprintf(" (error: %s)", strerror(save_errno)));
- }
- else smtp_had_eof = 1;
+ if (!smtp_refill(lim))
return EOF;
- }
-#ifndef DISABLE_DKIM
- dkim_exim_verify_feed(smtp_inbuffer, rc);
-#endif
- smtp_inend = smtp_inbuffer + rc;
- smtp_inptr = smtp_inbuffer;
- }
return *smtp_inptr++;
}
+uschar *
+smtp_getbuf(unsigned * len)
+{
+unsigned size;
+uschar * buf;
+
+if (smtp_inptr >= smtp_inend)
+ if (!smtp_refill(*len))
+ { *len = 0; return NULL; }
+
+if ((size = smtp_inend - smtp_inptr) > *len) size = *len;
+buf = smtp_inptr;
+smtp_inptr += size;
+*len = size;
+return buf;
+}
+
void
smtp_get_cache(void)
{
for(;;)
{
#ifndef DISABLE_DKIM
- BOOL dkim_save;
+ unsigned dkim_save;
#endif
if (chunking_data_left > 0)
return lwr_receive_getc(chunking_data_left--);
receive_getc = lwr_receive_getc;
+ receive_getbuf = lwr_receive_getbuf;
receive_ungetc = lwr_receive_ungetc;
#ifndef DISABLE_DKIM
dkim_save = dkim_collect_input;
- dkim_collect_input = FALSE;
+ dkim_collect_input = 0;
#endif
/* Unless PIPELINING was offered, there should be no next command
until after we ack that chunk */
- if (!pipelining_advertised && !check_sync())
+ if (!f.smtp_in_pipelining_advertised && !check_sync())
{
+ unsigned n = smtp_inend - smtp_inptr;
+ if (n > 32) n = 32;
+
incomplete_transaction_log(US"sync failure");
log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
"(next input sent too soon: pipelining was not advertised): "
- "rejected \"%s\" %s next input=\"%s\"",
+ "rejected \"%s\" %s next input=\"%s\"%s",
smtp_cmd_buffer, host_and_ident(TRUE),
- string_printing(smtp_inptr));
- (void) synprot_error(L_smtp_protocol_error, 554, NULL,
- US"SMTP synchronization error");
+ string_printing(string_copyn(smtp_inptr, n)),
+ smtp_inend - smtp_inptr > n ? "..." : "");
+ (void) synprot_error(L_smtp_protocol_error, 554, NULL,
+ US"SMTP synchronization error");
goto repeat_until_rset;
}
return EOD;
}
- smtp_printf("250 %u byte chunk received\r\n", chunking_datasize);
+ smtp_printf("250 %u byte chunk received\r\n", FALSE, chunking_datasize);
chunking_state = CHUNKING_OFFERED;
DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
case NOOP_CMD:
HAD(SCH_NOOP);
- smtp_printf("250 OK\r\n");
+ smtp_printf("250 OK\r\n", FALSE);
goto next_cmd;
case BDAT_CMD:
}
receive_getc = bdat_getc;
+ receive_getbuf = bdat_getbuf; /* r~getbuf is never actually used */
receive_ungetc = bdat_ungetc;
#ifndef DISABLE_DKIM
dkim_collect_input = dkim_save;
}
}
+uschar *
+bdat_getbuf(unsigned * len)
+{
+uschar * buf;
+
+if (chunking_data_left <= 0)
+ { *len = 0; return NULL; }
+
+if (*len > chunking_data_left) *len = chunking_data_left;
+buf = lwr_receive_getbuf(len); /* Either smtp_getbuf or tls_getbuf */
+chunking_data_left -= *len;
+return buf;
+}
+
void
bdat_flush_data(void)
{
-while (chunking_data_left > 0)
- if (lwr_receive_getc(chunking_data_left--) < 0)
- break;
+while (chunking_data_left)
+ {
+ unsigned n = chunking_data_left;
+ if (!bdat_getbuf(&n)) break;
+ }
receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
receive_ungetc = lwr_receive_ungetc;
if (chunking_state != CHUNKING_LAST)
Arguments:
format format string
+ more further data expected
... optional arguments
Returns: nothing
*/
void
-smtp_printf(const char *format, ...)
+smtp_printf(const char *format, BOOL more, ...)
{
va_list ap;
-va_start(ap, format);
-smtp_vprintf(format, ap);
+va_start(ap, more);
+smtp_vprintf(format, more, ap);
va_end(ap);
}
call another vararg function, only a function which accepts a va_list. */
void
-smtp_vprintf(const char *format, va_list ap)
+smtp_vprintf(const char *format, BOOL more, va_list ap)
{
+gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
BOOL yield;
-yield = string_vformat(big_buffer, big_buffer_size, format, ap);
+yield = !! string_vformat(&gs, FALSE, format, ap);
+string_from_gstring(&gs);
DEBUG(D_receive)
{
void *reset_point = store_get(0);
uschar *msg_copy, *cr, *end;
- msg_copy = string_copy(big_buffer);
- end = msg_copy + Ustrlen(msg_copy);
+ msg_copy = string_copy(gs.s);
+ end = msg_copy + gs.ptr;
while ((cr = Ustrchr(msg_copy, '\r')) != NULL) /* lose CRs */
- memmove(cr, cr + 1, (end--) - cr);
+ memmove(cr, cr + 1, (end--) - cr);
debug_printf("SMTP>> %s", msg_copy);
store_reset(reset_point);
}
{
log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
smtp_closedown(US"Unexpected error");
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, NULL);
}
/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
do it that way, so as not to have to mess with the code for the RCPT command,
which sometimes uses smtp_printf() and sometimes smtp_respond(). */
-if (rcpt_in_progress)
+if (fl.rcpt_in_progress)
{
if (rcpt_smtp_response == NULL)
rcpt_smtp_response = string_copy(big_buffer);
- else if (rcpt_smtp_response_same &&
+ else if (fl.rcpt_smtp_response_same &&
Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
- rcpt_smtp_response_same = FALSE;
- rcpt_in_progress = FALSE;
+ fl.rcpt_smtp_response_same = FALSE;
+ fl.rcpt_in_progress = FALSE;
}
/* Now write the string */
#ifdef SUPPORT_TLS
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
{
- if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer)) < 0)
+ if (tls_write(NULL, gs.s, gs.ptr, more) < 0)
smtp_write_error = -1;
}
else
#endif
-if (fprintf(smtp_out, "%s", big_buffer) < 0) smtp_write_error = -1;
+if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1;
}
int
smtp_fflush(void)
{
-if (tls_in.active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
+if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
return smtp_write_error;
}
static void
command_timeout_handler(int sig)
{
-sig = sig; /* Keep picky compilers happy */
-log_write(L_lost_incoming_connection,
- LOG_MAIN, "SMTP command timeout on%s connection from %s",
- (tls_in.active >= 0)? " TLS" : "",
- host_and_ident(FALSE));
-if (smtp_batched_input)
- moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
-smtp_notquit_exit(US"command-timeout", US"421",
- US"%s: SMTP command timeout - closing connection", smtp_active_hostname);
-exim_exit(EXIT_FAILURE);
+had_command_timeout = sig;
}
static void
command_sigterm_handler(int sig)
{
-sig = sig; /* Keep picky compilers happy */
-log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
-if (smtp_batched_input)
- moan_smtp_batch(NULL, "421 SIGTERM received"); /* Does not return */
-smtp_notquit_exit(US"signal-exit", US"421",
- US"%s: Service not available - closing connection", smtp_active_hostname);
-exim_exit(EXIT_FAILURE);
+had_command_sigterm = sig;
}
static int
swallow_until_crlf(int fd, uschar *base, int already, int capacity)
{
- uschar *to = base + already;
- uschar *cr;
- int have = 0;
- int ret;
- int last = 0;
-
- /* For "PROXY UNKNOWN\r\n" we, at time of writing, expect to have read
- up through the \r; for the _normal_ case, we haven't yet seen the \r. */
- cr = memchr(base, '\r', already);
- if (cr != NULL)
- {
- if ((cr - base) < already - 1)
- {
- /* \r and presumed \n already within what we have; probably not
- actually proxy protocol, but abort cleanly. */
- return 0;
- }
- /* \r is last character read, just need one more. */
- last = 1;
- }
+uschar *to = base + already;
+uschar *cr;
+int have = 0;
+int ret;
+int last = 0;
- while (capacity > 0)
+/* For "PROXY UNKNOWN\r\n" we, at time of writing, expect to have read
+up through the \r; for the _normal_ case, we haven't yet seen the \r. */
+
+cr = memchr(base, '\r', already);
+if (cr != NULL)
+ {
+ if ((cr - base) < already - 1)
{
- do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR);
- if (ret == -1)
- return -1;
- have++;
- if (last)
- return have;
- if (*to == '\r')
- last = 1;
- capacity--;
- to++;
+ /* \r and presumed \n already within what we have; probably not
+ actually proxy protocol, but abort cleanly. */
+ return 0;
}
- // reached end without having room for a final newline, abort
- errno = EOVERFLOW;
- return -1;
+ /* \r is last character read, just need one more. */
+ last = 1;
+ }
+
+while (capacity > 0)
+ {
+ do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR);
+ if (ret == -1)
+ return -1;
+ have++;
+ if (last)
+ return have;
+ if (*to == '\r')
+ last = 1;
+ capacity--;
+ to++;
+ }
+
+/* reached end without having room for a final newline, abort */
+errno = EOVERFLOW;
+return -1;
}
/*************************************************
do
{
/* The inbound host was declared to be a Proxy Protocol host, so
- don't do a PEEK into the data, actually slurp up enough to be
- "safe". Can't take it all because TLS-on-connect clients follow
- immediately with TLS handshake. */
+ don't do a PEEK into the data, actually slurp up enough to be
+ "safe". Can't take it all because TLS-on-connect clients follow
+ immediately with TLS handshake. */
ret = recv(fd, &hdr, PROXY_INITIAL_READ, 0);
}
while (ret == -1 && errno == EINTR);
ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
/* May 2014: haproxy combined the version and command into one byte to
- allow two full bytes for the length field in order to proxy SSL
- connections. SSL Proxy is not supported in this version of Exim, but
- must still separate values here. */
+ allow two full bytes for the length field in order to proxy SSL
+ connections. SSL Proxy is not supported in this version of Exim, but
+ must still separate values here. */
if (ver != 0x02)
{
DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n");
DEBUG(D_receive) debug_printf("Bytes read not within PROXY header: %d\n", ret - size);
/* Step through the string looking for the required fields. Ensure
- strict adherence to required formatting, exit for any error. */
+ strict adherence to required formatting, exit for any error. */
p += 5;
if (!isspace(*(p++)))
{
}
else
{
- proxy_session_failed = TRUE;
+ f.proxy_session_failed = TRUE;
DEBUG(D_receive)
debug_printf("Failure to extract proxied host, only QUIT allowed\n");
}
smtp_cmd_list *p;
BOOL hadnull = FALSE;
+had_command_timeout = 0;
os_non_restarting_signal(SIGALRM, command_timeout_handler);
while ((c = (receive_getc)(buffer_lim)) != '\n' && c != EOF)
{
#ifdef SUPPORT_PROXY
/* Only allow QUIT command if Proxy Protocol parsing failed */
- if (proxy_session && proxy_session_failed && p->cmd != QUIT_CMD)
+ if (proxy_session && f.proxy_session_failed && p->cmd != QUIT_CMD)
continue;
#endif
if ( p->len
check_sync && /* Local flag set */
smtp_enforce_sync && /* Global flag set */
sender_host_address != NULL && /* Not local input */
- !sender_host_notsocket) /* Really is a socket */
+ !f.sender_host_notsocket) /* Really is a socket */
return BADSYN_CMD;
/* The variables $smtp_command and $smtp_command_argument point into the
#ifdef SUPPORT_PROXY
/* Only allow QUIT command if Proxy Protocol parsing failed */
-if (proxy_session && proxy_session_failed)
+if (proxy_session && f.proxy_session_failed)
return PROXY_FAIL_IGNORE_CMD;
#endif
/* Enforce synchronization for unknown commands */
-if (smtp_inptr < smtp_inend && /* Outstanding input */
- check_sync && /* Local flag set */
- smtp_enforce_sync && /* Global flag set */
- sender_host_address != NULL && /* Not local input */
- !sender_host_notsocket) /* Really is a socket */
+if ( smtp_inptr < smtp_inend /* Outstanding input */
+ && check_sync /* Local flag set */
+ && smtp_enforce_sync /* Global flag set */
+ && sender_host_address /* Not local input */
+ && !f.sender_host_notsocket) /* Really is a socket */
return BADSYN_CMD;
return OTHER_CMD;
void
smtp_closedown(uschar *message)
{
-if (smtp_in == NULL || smtp_batched_input) return;
+if (!smtp_in || smtp_batched_input) return;
receive_swallow_smtp();
-smtp_printf("421 %s\r\n", message);
+smtp_printf("421 %s\r\n", FALSE, message);
for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
{
case EOF_CMD:
- return;
+ return;
case QUIT_CMD:
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
- mac_smtp_fflush();
- return;
+ smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
+ mac_smtp_fflush();
+ return;
case RSET_CMD:
- smtp_printf("250 Reset OK\r\n");
- break;
+ smtp_printf("250 Reset OK\r\n", FALSE);
+ break;
default:
- smtp_printf("421 %s\r\n", message);
- break;
+ smtp_printf("421 %s\r\n", FALSE, message);
+ break;
}
}
if (host_checking)
return string_sprintf("SMTP connection from %s", hostname);
-if (sender_host_unknown || sender_host_notsocket)
+if (f.sender_host_unknown || f.sender_host_notsocket)
return string_sprintf("SMTP connection from %s", sender_ident);
-if (is_inetd)
+if (f.is_inetd)
return string_sprintf("SMTP connection from %s (via inetd)", hostname);
-if (LOGGING(incoming_interface) && interface_address != NULL)
+if (LOGGING(incoming_interface) && interface_address)
return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
interface_address, interface_port);
/* Append TLS-related information to a log line
Arguments:
- s String under construction: allocated string to extend, or NULL
- sizep Pointer to current allocation size (update on return), or NULL
- ptrp Pointer to index for new entries in string (update on return), or NULL
+ g String under construction: allocated string to extend, or NULL
Returns: Allocated string or NULL
*/
-static uschar *
-s_tlslog(uschar * s, int * sizep, int * ptrp)
+static gstring *
+s_tlslog(gstring * g)
{
- int size = sizep ? *sizep : 0;
- int ptr = ptrp ? *ptrp : 0;
-
- if (LOGGING(tls_cipher) && tls_in.cipher != NULL)
- s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher);
- if (LOGGING(tls_certificate_verified) && tls_in.cipher != NULL)
- s = string_append(s, &size, &ptr, 2, US" CV=",
- tls_in.certificate_verified? "yes":"no");
- if (LOGGING(tls_peerdn) && tls_in.peerdn != NULL)
- s = string_append(s, &size, &ptr, 3, US" DN=\"",
- string_printing(tls_in.peerdn), US"\"");
- if (LOGGING(tls_sni) && tls_in.sni != NULL)
- s = string_append(s, &size, &ptr, 3, US" SNI=\"",
- string_printing(tls_in.sni), US"\"");
-
- if (s)
- {
- s[ptr] = '\0';
- if (sizep) *sizep = size;
- if (ptrp) *ptrp = ptr;
- }
- return s;
+if (LOGGING(tls_cipher) && tls_in.cipher)
+ g = string_append(g, 2, US" X=", tls_in.cipher);
+if (LOGGING(tls_certificate_verified) && tls_in.cipher)
+ g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no");
+if (LOGGING(tls_peerdn) && tls_in.peerdn)
+ g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
+if (LOGGING(tls_sni) && tls_in.sni)
+ g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
+return g;
}
#endif
void
smtp_log_no_mail(void)
{
-int size, ptr, i;
-uschar *s, *sep;
+int i;
+uschar * sep, * s;
+gstring * g = NULL;
if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
return;
-s = NULL;
-size = ptr = 0;
-
-if (sender_host_authenticated != NULL)
+if (sender_host_authenticated)
{
- s = string_append(s, &size, &ptr, 2, US" A=", sender_host_authenticated);
- if (authenticated_id != NULL)
- s = string_append(s, &size, &ptr, 2, US":", authenticated_id);
+ g = string_append(g, 2, US" A=", sender_host_authenticated);
+ if (authenticated_id) g = string_append(g, 2, US":", authenticated_id);
}
#ifdef SUPPORT_TLS
-s = s_tlslog(s, &size, &ptr);
+g = s_tlslog(g);
#endif
-sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
- US" C=..." : US" C=";
+sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE ? US" C=..." : US" C=";
+
for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
- {
if (smtp_connection_had[i] != SCH_NONE)
{
- s = string_append(s, &size, &ptr, 2, sep,
- smtp_names[smtp_connection_had[i]]);
+ g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
sep = US",";
}
- }
for (i = 0; i < smtp_ch_index; i++)
{
- s = string_append(s, &size, &ptr, 2, sep, smtp_names[smtp_connection_had[i]]);
+ g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
sep = US",";
}
-if (s != NULL) s[ptr] = 0; else s = US"";
-log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s",
- host_and_ident(FALSE),
- readconf_printtime( (int) ((long)time(NULL) - (long)smtp_connection_start)),
- s);
+if (!(s = string_from_gstring(g))) s = US"";
+
+log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
+ f.tcp_in_fastopen ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO " : US"",
+ host_and_ident(FALSE), string_timesince(&smtp_connection_start), s);
+}
+
+
+/* Return list of recent smtp commands */
+
+uschar *
+smtp_cmd_hist(void)
+{
+int i;
+gstring * list = NULL;
+uschar * s;
+
+for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
+ if (smtp_connection_had[i] != SCH_NONE)
+ list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
+
+for (i = 0; i < smtp_ch_index; i++)
+ list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
+
+s = string_from_gstring(list);
+return s ? s : US"";
}
+
/*************************************************
* Check HELO line and set sender_helo_name *
*************************************************/
{
uschar *start = s;
uschar *end = s + Ustrlen(s);
-BOOL yield = helo_accept_junk;
+BOOL yield = fl.helo_accept_junk;
/* Discard any previous helo name */
-if (sender_helo_name != NULL)
+if (sender_helo_name)
{
store_free(sender_helo_name);
sender_helo_name = NULL;
/* Skip tests if junk is permitted. */
if (!yield)
- {
+
/* Allow the new standard form for IPv6 address literals, namely,
[IPv6:....], and because someone is bound to use it, allow an equivalent
IPv4 form. Allow plain addresses as well. */
/* Non-literals must be alpha, dot, hyphen, plus any non-valid chars
that have been configured (usually underscore - sigh). */
- else if (*s != 0)
- {
- yield = TRUE;
- while (*s != 0)
- {
+ else if (*s)
+ for (yield = TRUE; *s; s++)
if (!isalnum(*s) && *s != '.' && *s != '-' &&
Ustrchr(helo_allow_chars, *s) == NULL)
{
yield = FALSE;
break;
}
- s++;
- }
- }
- }
/* Save argument if OK */
n = v;
if (*v == '=')
-{
+ {
while(isalpha(n[-1])) n--;
/* RFC says SP, but TAB seen in wild and other major MTAs accept it */
if (!isspace(n[-1])) return FALSE;
n[-1] = 0;
-}
+ }
else
-{
+ {
n++;
if (v == smtp_cmd_data) return FALSE;
-}
+ }
*v++ = 0;
*name = n;
*value = v;
*************************************************/
/* This function is called whenever the SMTP session is reset from
-within either of the setup functions.
+within either of the setup functions; also from the daemon loop.
Argument: the stacking pool storage reset point
Returns: nothing
*/
-static void
+void
smtp_reset(void *reset_point)
{
recipients_list = NULL;
rcpt_count = rcpt_defer_count = rcpt_fail_count =
raw_recipients_count = recipients_count = recipients_list_max = 0;
-cancel_cutthrough_connection("smtp reset");
message_linecount = 0;
message_size = -1;
acl_added_headers = NULL;
acl_removed_headers = NULL;
-queue_only_policy = FALSE;
+f.queue_only_policy = FALSE;
rcpt_smtp_response = NULL;
-rcpt_smtp_response_same = TRUE;
-rcpt_in_progress = FALSE;
-deliver_freeze = FALSE; /* Can be set by ACL */
+fl.rcpt_smtp_response_same = TRUE;
+fl.rcpt_in_progress = FALSE;
+f.deliver_freeze = FALSE; /* Can be set by ACL */
freeze_tell = freeze_tell_config; /* Can be set by ACL */
fake_response = OK; /* Can be set by ACL */
#ifdef WITH_CONTENT_SCAN
-no_mbox_unspool = FALSE; /* Can be set by ACL */
+f.no_mbox_unspool = FALSE; /* Can be set by ACL */
#endif
-submission_mode = FALSE; /* Can be set by ACL */
-suppress_local_fixups = suppress_local_fixups_default; /* Can be set by ACL */
-active_local_from_check = local_from_check; /* Can be set by ACL */
-active_local_sender_retain = local_sender_retain; /* Can be set by ACL */
+f.submission_mode = FALSE; /* Can be set by ACL */
+f.suppress_local_fixups = f.suppress_local_fixups_default; /* Can be set by ACL */
+f.active_local_from_check = local_from_check; /* Can be set by ACL */
+f.active_local_sender_retain = local_sender_retain; /* Can be set by ACL */
sending_ip_address = NULL;
return_path = sender_address = NULL;
sender_data = NULL; /* Can be set by ACL */
-deliver_localpart_orig = NULL;
-deliver_domain_orig = NULL;
+deliver_localpart_parent = deliver_localpart_orig = NULL;
+deliver_domain_parent = deliver_domain_orig = NULL;
callout_address = NULL;
submission_name = NULL; /* Can be set by ACL */
raw_sender = NULL; /* After SMTP rewrite, before qualifying */
bmi_verdicts = NULL;
#endif
dnslist_domain = dnslist_matched = NULL;
+#ifdef SUPPORT_SPF
+spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
+spf_result_guessed = FALSE;
+#endif
#ifndef DISABLE_DKIM
-dkim_signers = NULL;
-dkim_disable_verify = FALSE;
-dkim_collect_input = FALSE;
+dkim_cur_signer = dkim_signers =
+dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
+dkim_cur_signer = dkim_signers = dkim_signing_domain = dkim_signing_selector = NULL;
+f.dkim_disable_verify = FALSE;
+dkim_collect_input = 0;
+dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
+dkim_key_length = 0;
+#endif
+#ifdef EXPERIMENTAL_DMARC
+f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FALSE;
+dmarc_domain_policy = dmarc_status = dmarc_status_text =
+dmarc_used_domain = NULL;
+#endif
+#ifdef EXPERIMENTAL_ARC
+arc_state = arc_state_reason = NULL;
#endif
dsn_ret = 0;
dsn_envid = NULL;
#ifndef DISABLE_PRDR
prdr_requested = FALSE;
#endif
-#ifdef EXPERIMENTAL_SPF
-spf_header_comment = NULL;
-spf_received = NULL;
-spf_result = NULL;
-spf_smtp_comment = NULL;
-#endif
#ifdef SUPPORT_I18N
message_smtputf8 = FALSE;
#endif
if ((receive_feof)()) return 0; /* Treat EOF as QUIT */
+cancel_cutthrough_connection(TRUE, US"smtp_setup_batch_msg");
smtp_reset(reset_point); /* Reset for start of message */
/* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
case HELO_CMD:
case EHLO_CMD:
- check_helo(smtp_cmd_data);
- /* Fall through */
+ check_helo(smtp_cmd_data);
+ /* Fall through */
case RSET_CMD:
- smtp_reset(reset_point);
- bsmtp_transaction_linecount = receive_linecount;
- break;
+ cancel_cutthrough_connection(TRUE, US"RSET received");
+ smtp_reset(reset_point);
+ bsmtp_transaction_linecount = receive_linecount;
+ break;
/* The MAIL FROM command requires an address as an operand. All we
it is the canonical extracted address which is all that is kept. */
case MAIL_CMD:
- smtp_mailcmd_count++; /* Count for no-mail log */
- if (sender_address != NULL)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
+ smtp_mailcmd_count++; /* Count for no-mail log */
+ if (sender_address != NULL)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
- if (smtp_cmd_data[0] == 0)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 MAIL FROM must have an address operand");
+ if (smtp_cmd_data[0] == 0)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "501 MAIL FROM must have an address operand");
- /* Reset to start of message */
+ /* Reset to start of message */
- smtp_reset(reset_point);
+ cancel_cutthrough_connection(TRUE, US"MAIL received");
+ smtp_reset(reset_point);
- /* Apply SMTP rewrite */
+ /* Apply SMTP rewrite */
- raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
- rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE,
- US"", global_rewrite_rules) : smtp_cmd_data;
+ raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
+ rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE,
+ US"", global_rewrite_rules) : smtp_cmd_data;
- /* Extract the address; the TRUE flag allows <> as valid */
+ /* Extract the address; the TRUE flag allows <> as valid */
- raw_sender =
- parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
- TRUE);
+ raw_sender =
+ parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
+ TRUE);
- if (raw_sender == NULL)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
+ if (!raw_sender)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
- sender_address = string_copy(raw_sender);
+ sender_address = string_copy(raw_sender);
- /* Qualify unqualified sender addresses if permitted to do so. */
+ /* Qualify unqualified sender addresses if permitted to do so. */
- if (sender_domain == 0 && sender_address[0] != 0 && sender_address[0] != '@')
- {
- if (allow_unqualified_sender)
- {
- sender_address = rewrite_address_qualify(sender_address, FALSE);
- DEBUG(D_receive) debug_printf("unqualified address %s accepted "
- "and rewritten\n", raw_sender);
- }
- /* The function moan_smtp_batch() does not return. */
- else moan_smtp_batch(smtp_cmd_buffer, "501 sender address must contain "
- "a domain");
- }
- break;
+ if ( !sender_domain
+ && sender_address[0] != 0 && sender_address[0] != '@')
+ if (f.allow_unqualified_sender)
+ {
+ sender_address = rewrite_address_qualify(sender_address, FALSE);
+ DEBUG(D_receive) debug_printf("unqualified address %s accepted "
+ "and rewritten\n", raw_sender);
+ }
+ /* The function moan_smtp_batch() does not return. */
+ else
+ moan_smtp_batch(smtp_cmd_buffer, "501 sender address must contain "
+ "a domain");
+ break;
/* The RCPT TO command requires an address as an operand. All we do
extracted address. */
case RCPT_CMD:
- if (sender_address == NULL)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given");
+ if (!sender_address)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given");
- if (smtp_cmd_data[0] == 0)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 RCPT TO must have an address operand");
+ if (smtp_cmd_data[0] == 0)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer,
+ "501 RCPT TO must have an address operand");
- /* Check maximum number allowed */
+ /* Check maximum number allowed */
- if (recipients_max > 0 && recipients_count + 1 > recipients_max)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
- recipients_max_reject? "552": "452");
+ if (recipients_max > 0 && recipients_count + 1 > recipients_max)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
+ recipients_max_reject? "552": "452");
- /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
- recipient address */
+ /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
+ recipient address */
- recipient = rewrite_existflags & rewrite_smtp
- ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
- global_rewrite_rules)
- : smtp_cmd_data;
+ recipient = rewrite_existflags & rewrite_smtp
+ ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+ global_rewrite_rules)
+ : smtp_cmd_data;
- recipient = parse_extract_address(recipient, &errmess, &start, &end,
- &recipient_domain, FALSE);
+ recipient = parse_extract_address(recipient, &errmess, &start, &end,
+ &recipient_domain, FALSE);
- if (!recipient)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
+ if (!recipient)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
- /* If the recipient address is unqualified, qualify it if permitted. Then
- add it to the list of recipients. */
+ /* If the recipient address is unqualified, qualify it if permitted. Then
+ add it to the list of recipients. */
- if (recipient_domain == 0)
- {
- if (allow_unqualified_recipient)
- {
- DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
- recipient);
- recipient = rewrite_address_qualify(recipient, TRUE);
- }
- /* The function moan_smtp_batch() does not return. */
- else moan_smtp_batch(smtp_cmd_buffer, "501 recipient address must contain "
- "a domain");
- }
- receive_add_recipient(recipient, -1);
- break;
+ if (!recipient_domain)
+ if (f.allow_unqualified_recipient)
+ {
+ DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+ recipient);
+ recipient = rewrite_address_qualify(recipient, TRUE);
+ }
+ /* The function moan_smtp_batch() does not return. */
+ else
+ moan_smtp_batch(smtp_cmd_buffer,
+ "501 recipient address must contain a domain");
+
+ receive_add_recipient(recipient, -1);
+ break;
/* The DATA command is legal only if it follows successful MAIL FROM
command is encountered. */
case DATA_CMD:
- if (sender_address == NULL || recipients_count <= 0)
- {
- /* The function moan_smtp_batch() does not return. */
- if (sender_address == NULL)
- moan_smtp_batch(smtp_cmd_buffer,
- "503 MAIL FROM:<sender> command must precede DATA");
+ if (!sender_address || recipients_count <= 0)
+ /* The function moan_smtp_batch() does not return. */
+ if (!sender_address)
+ moan_smtp_batch(smtp_cmd_buffer,
+ "503 MAIL FROM:<sender> command must precede DATA");
+ else
+ moan_smtp_batch(smtp_cmd_buffer,
+ "503 RCPT TO:<recipient> must precede DATA");
else
- moan_smtp_batch(smtp_cmd_buffer,
- "503 RCPT TO:<recipient> must precede DATA");
- }
- else
- {
- done = 3; /* DATA successfully achieved */
- message_ended = END_NOTENDED; /* Indicate in middle of message */
- }
- break;
+ {
+ done = 3; /* DATA successfully achieved */
+ message_ended = END_NOTENDED; /* Indicate in middle of message */
+ }
+ break;
/* The VRFY, EXPN, HELP, ETRN, and NOOP commands are ignored. */
case HELP_CMD:
case NOOP_CMD:
case ETRN_CMD:
- bsmtp_transaction_linecount = receive_linecount;
- break;
+ bsmtp_transaction_linecount = receive_linecount;
+ break;
case EOF_CMD:
case QUIT_CMD:
- done = 2;
- break;
+ done = 2;
+ break;
case BADARG_CMD:
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected argument data");
- break;
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected argument data");
+ break;
case BADCHAR_CMD:
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected NULL in SMTP command");
- break;
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected NULL in SMTP command");
+ break;
default:
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "500 Command unrecognized");
- break;
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "500 Command unrecognized");
+ break;
}
}
-/*************************************************
-* Start an SMTP session *
-*************************************************/
+#ifdef SUPPORT_TLS
+static BOOL
+smtp_log_tls_fail(uschar * errstr)
+{
+uschar * conn_info = smtp_get_connection_info();
-/* This function is called at the start of an SMTP session. Thereafter,
-smtp_setup_msg() is called to initiate each separate message. This
-function does host-specific testing, and outputs the banner line.
+if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5;
+/* I'd like to get separated H= here, but too hard for now */
-Arguments: none
-Returns: FALSE if the session can not continue; something has
- gone wrong, or the connection to the host is blocked
-*/
+log_write(0, LOG_MAIN, "TLS error on %s %s", conn_info, errstr);
+return FALSE;
+}
+#endif
+
+
+
+
+#ifdef TCP_FASTOPEN
+static void
+tfo_in_check(void)
+{
+# ifdef TCP_INFO
+struct tcp_info tinfo;
+socklen_t len = sizeof(tinfo);
+
+if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
+#ifdef TCPI_OPT_SYN_DATA /* FreeBSD 11 does not seem to have this yet */
+ if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
+ {
+ DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (ACKd data-on-SYN)\n");
+ f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE;
+ }
+ else
+#endif
+ if (tinfo.tcpi_state == TCP_SYN_RECV)
+ {
+ DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
+ f.tcp_in_fastopen = TRUE;
+ }
+# endif
+}
+#endif
+
+
+/*************************************************
+* Start an SMTP session *
+*************************************************/
+
+/* This function is called at the start of an SMTP session. Thereafter,
+smtp_setup_msg() is called to initiate each separate message. This
+function does host-specific testing, and outputs the banner line.
+
+Arguments: none
+Returns: FALSE if the session can not continue; something has
+ gone wrong, or the connection to the host is blocked
+*/
BOOL
smtp_start_session(void)
{
-int size = 256;
-int ptr, esclen;
+int esclen;
uschar *user_msg, *log_msg;
uschar *code, *esc;
-uschar *p, *s, *ss;
+uschar *p, *s;
+gstring * ss;
-smtp_connection_start = time(NULL);
+gettimeofday(&smtp_connection_start, NULL);
for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++)
smtp_connection_had[smtp_ch_index] = SCH_NONE;
smtp_ch_index = 0;
/* Default values for certain variables */
-helo_seen = esmtp = helo_accept_junk = FALSE;
+fl.helo_seen = fl.esmtp = fl.helo_accept_junk = FALSE;
smtp_mailcmd_count = 0;
count_nonmail = TRUE_UNSET;
synprot_error_count = unknown_command_count = nonmail_command_count = 0;
smtp_delay_mail = smtp_rlm_base;
-auth_advertised = FALSE;
-pipelining_advertised = FALSE;
-pipelining_enable = TRUE;
+fl.auth_advertised = FALSE;
+f.smtp_in_pipelining_advertised = f.smtp_in_pipelining_used = FALSE;
+f.pipelining_enable = TRUE;
sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
-smtp_exit_function_called = FALSE; /* For avoiding loop in not-quit exit */
+fl.smtp_exit_function_called = FALSE; /* For avoiding loop in not-quit exit */
/* If receiving by -bs from a trusted user, or testing with -bh, we allow
authentication settings from -oMaa to remain in force. */
-if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL;
+if (!host_checking && !f.sender_host_notsocket)
+ sender_host_auth_pubname = sender_host_authenticated = NULL;
authenticated_by = NULL;
#ifdef SUPPORT_TLS
tls_in.ourcert = tls_in.peercert = NULL;
tls_in.sni = NULL;
tls_in.ocsp = OCSP_NOT_REQ;
-tls_advertised = FALSE;
+fl.tls_advertised = FALSE;
+# ifdef EXPERIMENTAL_REQUIRETLS
+fl.requiretls_advertised = FALSE;
+# endif
#endif
-dsn_advertised = FALSE;
+fl.dsn_advertised = FALSE;
#ifdef SUPPORT_I18N
-smtputf8_advertised = FALSE;
+fl.smtputf8_advertised = FALSE;
#endif
/* Reset ACL connection variables */
(sender_host_address ? protocols : protocols_local) [pnormal];
/* Set up the buffer for inputting using direct read() calls, and arrange to
-call the local functions instead of the standard C ones. */
+call the local functions instead of the standard C ones. Place a NUL at the
+end of the buffer to safety-stop C-string reads from it. */
-if (!(smtp_inbuffer = (uschar *)malloc(IN_BUFFER_SIZE)))
+if (!(smtp_inbuffer = US malloc(IN_BUFFER_SIZE)))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer");
+smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0';
receive_getc = smtp_getc;
+receive_getbuf = smtp_getbuf;
receive_get_cache = smtp_get_cache;
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
/* Set up the message size limit; this may be host-specific */
thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
-if (expand_string_message != NULL)
+if (expand_string_message)
{
if (thismessage_size_limit == -1)
log_write(0, LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: "
If smtp_accept_max and smtp_accept_reserve are set, keep some connections in
reserve for certain hosts and/or networks. */
-if (!sender_host_unknown)
+if (!f.sender_host_unknown)
{
int rc;
BOOL reserved_host = FALSE;
How to do this properly in IPv6 is not yet known. */
- #if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)
+#if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)
#ifdef GLIBC_IP_OPTIONS
#if (!defined __GLIBC__) || (__GLIBC__ < 2)
#define OPTSTYLE 3
#endif
- if (!host_checking && !sender_host_notsocket)
+ if (!host_checking && !f.sender_host_notsocket)
{
#if OPTSTYLE == 1
EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
DEBUG(D_receive) debug_printf("checking for IP options\n");
- if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, (uschar *)(ipopt),
+ if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, US (ipopt),
&optlen) < 0)
{
if (errno != ENOPROTOOPT)
{
log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s",
host_and_ident(FALSE), strerror(errno));
- smtp_printf("451 SMTP service not available\r\n");
+ smtp_printf("451 SMTP service not available\r\n", FALSE);
return FALSE;
}
}
struct in_addr addr;
#if OPTSTYLE == 1
- uschar *optstart = (uschar *)(ipopt->__data);
+ uschar *optstart = US (ipopt->__data);
#elif OPTSTYLE == 2
- uschar *optstart = (uschar *)(ipopt->ip_opts);
+ uschar *optstart = US (ipopt->ip_opts);
#else
- uschar *optstart = (uschar *)(ipopt->ipopt_list);
+ uschar *optstart = US (ipopt->ipopt_list);
#endif
DEBUG(D_receive) debug_printf("IP options exist\n");
p += Ustrlen(p);
for (opt = optstart; opt != NULL &&
- opt < (uschar *)(ipopt) + optlen;)
+ opt < US (ipopt) + optlen;)
{
switch (*opt)
{
Ustrcat(p, "[ ");
p += 2;
for (i = 0; i < opt[1]; i++)
- {
- sprintf(CS p, "%2.2x ", opt[i]);
- p += 3;
- }
+ p += sprintf(CS p, "%2.2x ", opt[i]);
*p++ = ']';
}
opt += opt[1];
log_write(0, LOG_MAIN|LOG_REJECT,
"connection from %s refused (IP options)", host_and_ident(FALSE));
- smtp_printf("554 SMTP service not available\r\n");
+ smtp_printf("554 SMTP service not available\r\n", FALSE);
return FALSE;
}
else DEBUG(D_receive) debug_printf("no IP options found\n");
}
- #endif /* HAVE_IPV6 && !defined(NO_IP_OPTIONS) */
+#endif /* HAVE_IPV6 && !defined(NO_IP_OPTIONS) */
/* Set keep-alive in socket options. The option is on by default. This
setting is an attempt to get rid of some hanging connections that stick in
read() when the remote end (usually a dialup) goes away. */
- if (smtp_accept_keepalive && !sender_host_notsocket)
+ if (smtp_accept_keepalive && !f.sender_host_notsocket)
ip_keepalive(fileno(smtp_out), sender_host_address, FALSE);
/* If the current host matches host_lookup, set the name by doing a
{
log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection "
"from %s (host_reject_connection)", host_and_ident(FALSE));
- smtp_printf("554 SMTP service not available\r\n");
+ smtp_printf("554 SMTP service not available\r\n", FALSE);
return FALSE;
}
log_write(L_connection_reject,
LOG_MAIN|LOG_REJECT, "refused connection from %s "
"(tcp wrappers)", host_and_ident(FALSE));
- smtp_printf("554 SMTP service not available\r\n");
+ smtp_printf("554 SMTP service not available\r\n", FALSE);
}
else
{
log_write(L_connection_reject,
LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
"(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
- smtp_printf("451 Temporary local problem - please try later\r\n");
+ smtp_printf("451 Temporary local problem - please try later\r\n", FALSE);
}
return FALSE;
}
host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
smtp_printf("421 %s: Too many concurrent SMTP connections; "
- "please try again later\r\n", smtp_active_hostname);
+ "please try again later\r\n", FALSE, smtp_active_hostname);
return FALSE;
}
reserved_host = TRUE;
LOG_MAIN, "temporarily refused connection from %s: not in "
"reserve list and load average = %.2f", host_and_ident(FALSE),
(double)load_average/1000.0);
- smtp_printf("421 %s: Too much load; please try again later\r\n",
+ smtp_printf("421 %s: Too much load; please try again later\r\n", FALSE,
smtp_active_hostname);
return FALSE;
}
addresses in the headers. For a site that permits no qualification, this
won't take long, however. */
- allow_unqualified_sender =
+ f.allow_unqualified_sender =
verify_check_host(&sender_unqualified_hosts) == OK;
- allow_unqualified_recipient =
+ f.allow_unqualified_recipient =
verify_check_host(&recipient_unqualified_hosts) == OK;
/* Determine whether HELO/EHLO is required for this host. The requirement
can be hard or soft. */
- helo_required = verify_check_host(&helo_verify_hosts) == OK;
- if (!helo_required)
- helo_verify = verify_check_host(&helo_try_verify_hosts) == OK;
+ fl.helo_required = verify_check_host(&helo_verify_hosts) == OK;
+ if (!fl.helo_required)
+ fl.helo_verify = verify_check_host(&helo_try_verify_hosts) == OK;
/* Determine whether this hosts is permitted to send syntactic junk
after a HELO or EHLO command. */
- helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
+ fl.helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
}
/* For batch SMTP input we are now done. */
#ifdef SUPPORT_PROXY
proxy_session = FALSE;
-proxy_session_failed = FALSE;
+f.proxy_session_failed = FALSE;
if (check_proxy_protocol_host())
setup_proxy_protocol_host();
#endif
smtps port for use with older style SSL MTAs. */
#ifdef SUPPORT_TLS
- if (tls_in.on_connect && tls_server_start(tls_require_ciphers) != OK)
- return FALSE;
+ if (tls_in.on_connect)
+ {
+ if (tls_server_start(tls_require_ciphers, &user_msg) != OK)
+ return smtp_log_tls_fail(user_msg);
+ cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+ }
#endif
/* Run the connect ACL if it exists */
first, and output it in one fell swoop. This gives a better chance of it
ending up as a single packet. */
-ss = store_get(size);
-ptr = 0;
+ss = string_get(256);
p = s;
do /* At least once, in case we have an empty string */
{
int len;
uschar *linebreak = Ustrchr(p, '\n');
- ss = string_catn(ss, &size, &ptr, code, 3);
- if (linebreak == NULL)
+ ss = string_catn(ss, code, 3);
+ if (!linebreak)
{
len = Ustrlen(p);
- ss = string_catn(ss, &size, &ptr, US" ", 1);
+ ss = string_catn(ss, US" ", 1);
}
else
{
len = linebreak - p;
- ss = string_catn(ss, &size, &ptr, US"-", 1);
+ ss = string_catn(ss, US"-", 1);
}
- ss = string_catn(ss, &size, &ptr, esc, esclen);
- ss = string_catn(ss, &size, &ptr, p, len);
- ss = string_catn(ss, &size, &ptr, US"\r\n", 2);
+ ss = string_catn(ss, esc, esclen);
+ ss = string_catn(ss, p, len);
+ ss = string_catn(ss, US"\r\n", 2);
p += len;
- if (linebreak != NULL) p++;
+ if (linebreak) p++;
}
-while (*p != 0);
-
-ss[ptr] = 0; /* string_cat leaves room for this */
+while (*p);
/* Before we write the banner, check that there is no input pending, unless
this synchronisation check is disabled. */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+fl.pipe_connect_acceptable =
+ sender_host_address && verify_check_host(&pipe_connect_advertise_hosts) == OK;
+
if (!check_sync())
- {
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
- "synchronization error (input sent without waiting for greeting): "
- "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
- string_printing(smtp_inptr));
- smtp_printf("554 SMTP synchronization error\r\n");
- return FALSE;
- }
+ if (fl.pipe_connect_acceptable)
+ f.smtp_in_early_pipe_used = TRUE;
+ else
+#else
+if (!check_sync())
+#endif
+ {
+ unsigned n = smtp_inend - smtp_inptr;
+ if (n > 32) n = 32;
+
+ log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
+ "synchronization error (input sent without waiting for greeting): "
+ "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
+ string_printing(string_copyn(smtp_inptr, n)));
+ smtp_printf("554 SMTP synchronization error\r\n", FALSE);
+ return FALSE;
+ }
/* Now output the banner */
+/*XXX the ehlo-resp code does its own tls/nontls bit. Maybe subroutine that? */
+
+smtp_printf("%s",
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ fl.pipe_connect_acceptable && pipeline_connect_sends(),
+#else
+ FALSE,
+#endif
+ string_from_gstring(ss));
+
+/* Attempt to see if we sent the banner before the last ACK of the 3-way
+handshake arrived. If so we must have managed a TFO. */
+
+#ifdef TCP_FASTOPEN
+tfo_in_check();
+#endif
-smtp_printf("%s", ss);
return TRUE;
}
if (code > 0)
{
- smtp_printf("%d%c%s%s%s\r\n", code, (yield == 1)? '-' : ' ',
- (data == NULL)? US"" : data, (data == NULL)? US"" : US": ", errmess);
+ smtp_printf("%d%c%s%s%s\r\n", FALSE, code, yield == 1 ? '-' : ' ',
+ data ? data : US"", data ? US": " : US"", errmess);
if (yield == 1)
- smtp_printf("%d Too many syntax or protocol errors\r\n", code);
+ smtp_printf("%d Too many syntax or protocol errors\r\n", FALSE, code);
}
return yield;
int esclen = 0;
uschar *esc = US"";
-if (!final && no_multiline_responses) return;
+if (!final && f.no_multiline_responses) return;
if (codelen > 4)
{
do it that way, so as not to have to mess with the code for the RCPT command,
which sometimes uses smtp_printf() and sometimes smtp_respond(). */
-if (rcpt_in_progress)
+if (fl.rcpt_in_progress)
{
if (rcpt_smtp_response == NULL)
rcpt_smtp_response = string_copy(msg);
- else if (rcpt_smtp_response_same &&
+ else if (fl.rcpt_smtp_response_same &&
Ustrcmp(rcpt_smtp_response, msg) != 0)
- rcpt_smtp_response_same = FALSE;
- rcpt_in_progress = FALSE;
+ fl.rcpt_smtp_response_same = FALSE;
+ fl.rcpt_in_progress = FALSE;
}
-/* Not output the message, splitting it up into multiple lines if necessary. */
+/* Now output the message, splitting it up into multiple lines if necessary.
+We only handle pipelining these responses as far as nonfinal/final groups,
+not the whole MAIL/RCPT/DATA response set. */
for (;;)
{
uschar *nl = Ustrchr(msg, '\n');
if (nl == NULL)
{
- smtp_printf("%.3s%c%.*s%s\r\n", code, final? ' ':'-', esclen, esc, msg);
+ smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
return;
}
- else if (nl[1] == 0 || no_multiline_responses)
+ else if (nl[1] == 0 || f.no_multiline_responses)
{
- smtp_printf("%.3s%c%.*s%.*s\r\n", code, final? ' ':'-', esclen, esc,
+ smtp_printf("%.3s%c%.*s%.*s\r\n", !final, code, final ? ' ':'-', esclen, esc,
(int)(nl - msg), msg);
return;
}
else
{
- smtp_printf("%.3s-%.*s%.*s\r\n", code, esclen, esc, (int)(nl - msg), msg);
+ smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg);
msg = nl + 1;
while (isspace(*msg)) msg++;
}
/* This function is called when acl_check() fails. As well as calls from within
this module, it is called from receive.c for an ACL after DATA. It sorts out
-logging the incident, and sets up the error response. A message containing
+logging the incident, and sends the error response. A message containing
newlines is turned into a multiline SMTP response, but for logging, only the
first line is used.
if (sender_verified_failed &&
!testflag(sender_verified_failed, af_sverify_told))
{
- BOOL save_rcpt_in_progress = rcpt_in_progress;
- rcpt_in_progress = FALSE; /* So as not to treat these as the error */
+ BOOL save_rcpt_in_progress = fl.rcpt_in_progress;
+ fl.rcpt_in_progress = FALSE; /* So as not to treat these as the error */
setflag(sender_verified_failed, af_sverify_told);
sender_verified_failed->address,
sender_verified_failed->user_message));
- rcpt_in_progress = save_rcpt_in_progress;
+ fl.rcpt_in_progress = save_rcpt_in_progress;
}
/* Sort out text for logging */
be re-implemented in a tidier fashion. */
else
- if (acl_temp_details && user_msg)
+ if (f.acl_temp_details && user_msg)
{
if ( smtp_return_error_details
&& sender_verified_failed
if (log_reject_target != 0)
{
#ifdef SUPPORT_TLS
- uschar * tls = s_tlslog(NULL, NULL, NULL);
+ gstring * g = s_tlslog(NULL);
+ uschar * tls = string_from_gstring(g);
if (!tls) tls = US"";
#else
uschar * tls = US"";
uschar *user_msg = NULL;
uschar *log_msg = NULL;
-/* Check for recursive acll */
+/* Check for recursive call */
-if (smtp_exit_function_called)
+if (fl.smtp_exit_function_called)
{
log_write(0, LOG_PANIC, "smtp_notquit_exit() called more than once (%s)",
reason);
return;
}
-smtp_exit_function_called = TRUE;
+fl.smtp_exit_function_called = TRUE;
/* Call the not-QUIT ACL, if there is one, unless no reason is given. */
log_msg);
}
+/* If the connection was dropped, we certainly are no longer talking TLS */
+tls_in.active.sock = -1;
+
/* Write an SMTP response if we are expected to give one. As the default
-responses are all internal, they should always fit in the buffer, but code a
-warning, just in case. Note that string_vformat() still leaves a complete
-string, even if it is incomplete. */
+responses are all internal, they should be reasonable size. */
if (code && defaultrespond)
{
smtp_respond(code, 3, TRUE, user_msg);
else
{
- uschar buffer[128];
+ gstring * g;
va_list ap;
+
va_start(ap, defaultrespond);
- if (!string_vformat(buffer, sizeof(buffer), CS defaultrespond, ap))
- log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_notquit_exit()");
- smtp_printf("%s %s\r\n", code, buffer);
+ g = string_vformat(NULL, TRUE, CS defaultrespond, ap);
va_end(ap);
+ smtp_printf("%s %s\r\n", FALSE, code, string_from_gstring(g));
}
mac_smtp_fflush();
}
else if (sender_host_address == NULL)
{
HDEBUG(D_receive) debug_printf("no client IP address: assume success\n");
- helo_verified = TRUE;
+ f.helo_verified = TRUE;
}
/* Deal with the more common case when there is a sending IP address */
else if (sender_helo_name[0] == '[')
{
- helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address,
+ f.helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address,
Ustrlen(sender_host_address)) == 0;
- #if HAVE_IPV6
- if (!helo_verified)
+#if HAVE_IPV6
+ if (!f.helo_verified)
{
if (strncmpic(sender_host_address, US"::ffff:", 7) == 0)
- helo_verified = Ustrncmp(sender_helo_name + 1,
+ f.helo_verified = Ustrncmp(sender_helo_name + 1,
sender_host_address + 7, Ustrlen(sender_host_address) - 7) == 0;
}
- #endif
+#endif
HDEBUG(D_receive)
- { if (helo_verified) debug_printf("matched host address\n"); }
+ { if (f.helo_verified) debug_printf("matched host address\n"); }
}
/* Do a reverse lookup if one hasn't already given a positive or negative
/* If a host name is known, check it and all its aliases. */
if (sender_host_name)
- if ((helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0))
+ if ((f.helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0))
{
sender_helo_dnssec = sender_host_dnssec;
HDEBUG(D_receive) debug_printf("matched host name\n");
{
uschar **aliases = sender_host_aliases;
while (*aliases)
- if ((helo_verified = strcmpic(*aliases++, sender_helo_name) == 0))
+ if ((f.helo_verified = strcmpic(*aliases++, sender_helo_name) == 0))
{
sender_helo_dnssec = sender_host_dnssec;
break;
}
- HDEBUG(D_receive) if (helo_verified)
+ HDEBUG(D_receive) if (f.helo_verified)
debug_printf("matched alias %s\n", *(--aliases));
}
/* Final attempt: try a forward lookup of the helo name */
- if (!helo_verified)
+ if (!f.helo_verified)
{
int rc;
host_item h;
HDEBUG(D_receive) debug_printf("getting IP address for %s\n",
sender_helo_name);
- rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A,
+ rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
NULL, NULL, NULL, &d, NULL, NULL);
if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
for (hh = &h; hh; hh = hh->next)
if (Ustrcmp(hh->address, sender_host_address) == 0)
{
- helo_verified = TRUE;
+ f.helo_verified = TRUE;
if (h.dnssec == DS_YES) sender_helo_dnssec = TRUE;
HDEBUG(D_receive)
{
}
}
-if (!helo_verified) helo_verify_failed = TRUE; /* We've tried ... */
+if (!f.helo_verified) f.helo_verify_failed = TRUE; /* We've tried ... */
return yield;
}
{
if (set_id) authenticated_id = string_copy_malloc(set_id);
sender_host_authenticated = au->name;
+ sender_host_auth_pubname = au->public_name;
authentication_failed = FALSE;
authenticated_fail_id = NULL; /* Impossible to already be set? */
received_protocol =
(sender_host_address ? protocols : protocols_local)
- [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)];
+ [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
*s = *ss = US"235 Authentication succeeded";
authenticated_by = au;
break;
qualify_recipient(uschar ** recipient, uschar * smtp_cmd_data, uschar * tag)
{
int rd;
-if (allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
+if (f.allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
{
DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
*recipient);
*recipient = rewrite_address_qualify(*recipient, TRUE);
return rd;
}
-smtp_printf("501 %s: recipient address must contain a domain\r\n",
+smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE,
smtp_cmd_data);
log_write(L_smtp_syntax_error,
LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s",
if (*user_msgp)
smtp_respond(US"221", 3, TRUE, *user_msgp);
else
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+ smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
#ifdef SUPPORT_TLS
-tls_close(TRUE, TRUE);
+tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
#endif
log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
{
HAD(SCH_RSET);
incomplete_transaction_log(US"RSET");
-smtp_printf("250 Reset OK\r\n");
+smtp_printf("250 Reset OK\r\n", FALSE);
cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
}
smtp_reset(reset_point);
message_ended = END_NOTSTARTED;
-chunking_state = chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
+chunking_state = f.chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE;
cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
#ifdef SUPPORT_TLS
cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
-cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
#endif
/* Set the local signal handler for SIGTERM - it tries to end off tidily */
+had_command_sigterm = 0;
os_non_restarting_signal(SIGTERM, command_sigterm_handler);
/* Batched SMTP is handled in a different function. */
void (*oldsignal)(int);
pid_t pid;
int start, end, sender_domain, recipient_domain;
- int ptr, size, rc;
+ int rc;
int c;
auth_instance *au;
uschar *orcpt = NULL;
- int flags;
+ int dsn_flags;
+ gstring * g;
#ifdef AUTH_TLS
/* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
- if ( tls_in.active >= 0
+ if ( tls_in.active.sock >= 0
&& tls_in.peercert
&& tls_in.certificate_verified
&& cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd
)
{
cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
- if ( acl_smtp_auth
- && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
- &user_msg, &log_msg)) != OK
- )
- {
- done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
- continue;
- }
for (au = auths; au; au = au->next)
if (strcmpic(US"tls", au->driver_name) == 0)
{
- smtp_cmd_data = NULL;
-
- if (smtp_in_auth(au, &s, &ss) == OK)
- { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
+ if ( acl_smtp_auth
+ && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
+ &user_msg, &log_msg)) != OK
+ )
+ done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
else
- { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); }
+ {
+ smtp_cmd_data = NULL;
+
+ if (smtp_in_auth(au, &s, &ss) == OK)
+ { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
+ else
+ { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); }
+ }
break;
}
}
US &off, sizeof(off));
#endif
- switch(smtp_read_command(TRUE, GETC_BUFFER_UNLIMITED))
+ switch(smtp_read_command(
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ !fl.pipe_connect_acceptable,
+#else
+ TRUE,
+#endif
+ GETC_BUFFER_UNLIMITED))
{
/* The AUTH command is not permitted to occur inside a transaction, and may
occur successfully only once per connection. Actually, that isn't quite
AUTHS will eventually hit the nonmail threshold. */
case AUTH_CMD:
- HAD(SCH_AUTH);
- authentication_failed = TRUE;
- cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
+ HAD(SCH_AUTH);
+ authentication_failed = TRUE;
+ cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
- if (!auth_advertised && !allow_auth_unadvertised)
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"AUTH command used when not advertised");
- break;
- }
- if (sender_host_authenticated)
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"already authenticated");
- break;
- }
- if (sender_address)
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"not permitted in mail transaction");
- break;
- }
+ if (!fl.auth_advertised && !f.allow_auth_unadvertised)
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"AUTH command used when not advertised");
+ break;
+ }
+ if (sender_host_authenticated)
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"already authenticated");
+ break;
+ }
+ if (sender_address)
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"not permitted in mail transaction");
+ break;
+ }
- /* Check the ACL */
+ /* Check the ACL */
- if ( acl_smtp_auth
- && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
- &user_msg, &log_msg)) != OK
- )
- {
- done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
- break;
- }
+ if ( acl_smtp_auth
+ && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
+ &user_msg, &log_msg)) != OK
+ )
+ {
+ done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
+ break;
+ }
- /* Find the name of the requested authentication mechanism. */
+ /* Find the name of the requested authentication mechanism. */
- s = smtp_cmd_data;
- while ((c = *smtp_cmd_data) != 0 && !isspace(c))
- {
- if (!isalnum(c) && c != '-' && c != '_')
- {
- done = synprot_error(L_smtp_syntax_error, 501, NULL,
- US"invalid character in authentication mechanism name");
- goto COMMAND_LOOP;
- }
- smtp_cmd_data++;
- }
+ s = smtp_cmd_data;
+ while ((c = *smtp_cmd_data) != 0 && !isspace(c))
+ {
+ if (!isalnum(c) && c != '-' && c != '_')
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"invalid character in authentication mechanism name");
+ goto COMMAND_LOOP;
+ }
+ smtp_cmd_data++;
+ }
- /* If not at the end of the line, we must be at white space. Terminate the
- name and move the pointer on to any data that may be present. */
+ /* If not at the end of the line, we must be at white space. Terminate the
+ name and move the pointer on to any data that may be present. */
- if (*smtp_cmd_data != 0)
- {
- *smtp_cmd_data++ = 0;
- while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
- }
+ if (*smtp_cmd_data != 0)
+ {
+ *smtp_cmd_data++ = 0;
+ while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
+ }
- /* Search for an authentication mechanism which is configured for use
- as a server and which has been advertised (unless, sigh, allow_auth_
- unadvertised is set). */
+ /* Search for an authentication mechanism which is configured for use
+ as a server and which has been advertised (unless, sigh, allow_auth_
+ unadvertised is set). */
- for (au = auths; au; au = au->next)
- if (strcmpic(s, au->public_name) == 0 && au->server &&
- (au->advertised || allow_auth_unadvertised))
- break;
+ for (au = auths; au; au = au->next)
+ if (strcmpic(s, au->public_name) == 0 && au->server &&
+ (au->advertised || f.allow_auth_unadvertised))
+ break;
- if (au)
- {
- c = smtp_in_auth(au, &s, &ss);
+ if (au)
+ {
+ c = smtp_in_auth(au, &s, &ss);
- smtp_printf("%s\r\n", s);
- if (c != OK)
- log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
- au->name, host_and_ident(FALSE), ss);
- }
- else
- done = synprot_error(L_smtp_protocol_error, 504, NULL,
- string_sprintf("%s authentication mechanism not supported", s));
+ smtp_printf("%s\r\n", FALSE, s);
+ if (c != OK)
+ log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
+ au->name, host_and_ident(FALSE), ss);
+ }
+ else
+ done = synprot_error(L_smtp_protocol_error, 504, NULL,
+ string_sprintf("%s authentication mechanism not supported", s));
- break; /* AUTH_CMD */
+ break; /* AUTH_CMD */
/* The HELO/EHLO commands are permitted to appear in the middle of a
session as well as at the beginning. They have the effect of a reset in
it did the reset first. */
case HELO_CMD:
- HAD(SCH_HELO);
- hello = US"HELO";
- esmtp = FALSE;
- goto HELO_EHLO;
+ HAD(SCH_HELO);
+ hello = US"HELO";
+ fl.esmtp = FALSE;
+ goto HELO_EHLO;
case EHLO_CMD:
- HAD(SCH_EHLO);
- hello = US"EHLO";
- esmtp = TRUE;
+ HAD(SCH_EHLO);
+ hello = US"EHLO";
+ fl.esmtp = TRUE;
HELO_EHLO: /* Common code for HELO and EHLO */
- cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
- cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
+ cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
+ cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
- /* Reject the HELO if its argument was invalid or non-existent. A
- successful check causes the argument to be saved in malloc store. */
+ /* Reject the HELO if its argument was invalid or non-existent. A
+ successful check causes the argument to be saved in malloc store. */
- if (!check_helo(smtp_cmd_data))
- {
- smtp_printf("501 Syntactically invalid %s argument(s)\r\n", hello);
+ if (!check_helo(smtp_cmd_data))
+ {
+ smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello);
- log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
- "invalid argument(s): %s", hello, host_and_ident(FALSE),
- (*smtp_cmd_argument == 0)? US"(no argument given)" :
- string_printing(smtp_cmd_argument));
+ log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
+ "invalid argument(s): %s", hello, host_and_ident(FALSE),
+ *smtp_cmd_argument == 0 ? US"(no argument given)" :
+ string_printing(smtp_cmd_argument));
- if (++synprot_error_count > smtp_max_synprot_errors)
- {
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
- "syntax or protocol errors (last command was \"%s\")",
- host_and_ident(FALSE), string_printing(smtp_cmd_buffer));
- done = 1;
- }
+ if (++synprot_error_count > smtp_max_synprot_errors)
+ {
+ log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+ "syntax or protocol errors (last command was \"%s\")",
+ host_and_ident(FALSE), string_printing(smtp_cmd_buffer));
+ done = 1;
+ }
- break;
- }
+ break;
+ }
- /* If sender_host_unknown is true, we have got here via the -bs interface,
- not called from inetd. Otherwise, we are running an IP connection and the
- host address will be set. If the helo name is the primary name of this
- host and we haven't done a reverse lookup, force one now. If helo_required
- is set, ensure that the HELO name matches the actual host. If helo_verify
- is set, do the same check, but softly. */
+ /* If sender_host_unknown is true, we have got here via the -bs interface,
+ not called from inetd. Otherwise, we are running an IP connection and the
+ host address will be set. If the helo name is the primary name of this
+ host and we haven't done a reverse lookup, force one now. If helo_required
+ is set, ensure that the HELO name matches the actual host. If helo_verify
+ is set, do the same check, but softly. */
- if (!sender_host_unknown)
- {
- BOOL old_helo_verified = helo_verified;
- uschar *p = smtp_cmd_data;
+ if (!f.sender_host_unknown)
+ {
+ BOOL old_helo_verified = f.helo_verified;
+ uschar *p = smtp_cmd_data;
- while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
- *p = 0;
+ while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
+ *p = 0;
- /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
- because otherwise the log can be confusing. */
+ /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
+ because otherwise the log can be confusing. */
- if (sender_host_name == NULL &&
- (deliver_domain = sender_helo_name, /* set $domain */
- match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
- &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK)
- (void)host_name_lookup();
+ if ( !sender_host_name
+ && match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
+ &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
+ (void)host_name_lookup();
- /* Rebuild the fullhost info to include the HELO name (and the real name
- if it was looked up.) */
+ /* Rebuild the fullhost info to include the HELO name (and the real name
+ if it was looked up.) */
- host_build_sender_fullhost(); /* Rebuild */
- set_process_info("handling%s incoming connection from %s",
- (tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE));
+ host_build_sender_fullhost(); /* Rebuild */
+ set_process_info("handling%s incoming connection from %s",
+ tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
- /* Verify if configured. This doesn't give much security, but it does
- make some people happy to be able to do it. If helo_required is set,
- (host matches helo_verify_hosts) failure forces rejection. If helo_verify
- is set (host matches helo_try_verify_hosts), it does not. This is perhaps
- now obsolescent, since the verification can now be requested selectively
- at ACL time. */
+ /* Verify if configured. This doesn't give much security, but it does
+ make some people happy to be able to do it. If helo_required is set,
+ (host matches helo_verify_hosts) failure forces rejection. If helo_verify
+ is set (host matches helo_try_verify_hosts), it does not. This is perhaps
+ now obsolescent, since the verification can now be requested selectively
+ at ACL time. */
- helo_verified = helo_verify_failed = sender_helo_dnssec = FALSE;
- if (helo_required || helo_verify)
- {
- BOOL tempfail = !smtp_verify_helo();
- if (!helo_verified)
- {
- if (helo_required)
- {
- smtp_printf("%d %s argument does not match calling host\r\n",
- tempfail? 451 : 550, hello);
- log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
- tempfail? "temporarily " : "",
- hello, sender_helo_name, host_and_ident(FALSE));
- helo_verified = old_helo_verified;
- break; /* End of HELO/EHLO processing */
- }
- HDEBUG(D_all) debug_printf("%s verification failed but host is in "
- "helo_try_verify_hosts\n", hello);
- }
- }
- }
+ f.helo_verified = f.helo_verify_failed = sender_helo_dnssec = FALSE;
+ if (fl.helo_required || fl.helo_verify)
+ {
+ BOOL tempfail = !smtp_verify_helo();
+ if (!f.helo_verified)
+ {
+ if (fl.helo_required)
+ {
+ smtp_printf("%d %s argument does not match calling host\r\n", FALSE,
+ tempfail? 451 : 550, hello);
+ log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
+ tempfail? "temporarily " : "",
+ hello, sender_helo_name, host_and_ident(FALSE));
+ f.helo_verified = old_helo_verified;
+ break; /* End of HELO/EHLO processing */
+ }
+ HDEBUG(D_all) debug_printf("%s verification failed but host is in "
+ "helo_try_verify_hosts\n", hello);
+ }
+ }
+ }
-#ifdef EXPERIMENTAL_SPF
- /* set up SPF context */
- spf_init(sender_helo_name, sender_host_address);
+#ifdef SUPPORT_SPF
+ /* set up SPF context */
+ spf_init(sender_helo_name, sender_host_address);
#endif
- /* Apply an ACL check if one is defined; afterwards, recheck
- synchronization in case the client started sending in a delay. */
+ /* Apply an ACL check if one is defined; afterwards, recheck
+ synchronization in case the client started sending in a delay. */
- if (acl_smtp_helo)
- if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
- &user_msg, &log_msg)) != OK)
- {
- done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
- sender_helo_name = NULL;
- host_build_sender_fullhost(); /* Rebuild */
- break;
- }
- else if (!check_sync()) goto SYNC_FAILURE;
+ if (acl_smtp_helo)
+ if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
+ &user_msg, &log_msg)) != OK)
+ {
+ done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
+ if (sender_helo_name)
+ {
+ store_free(sender_helo_name);
+ sender_helo_name = NULL;
+ }
+ host_build_sender_fullhost(); /* Rebuild */
+ break;
+ }
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ else if (!fl.pipe_connect_acceptable && !check_sync())
+#else
+ else if (!check_sync())
+#endif
+ goto SYNC_FAILURE;
- /* Generate an OK reply. The default string includes the ident if present,
- and also the IP address if present. Reflecting back the ident is intended
- as a deterrent to mail forgers. For maximum efficiency, and also because
- some broken systems expect each response to be in a single packet, arrange
- that the entire reply is sent in one write(). */
+ /* Generate an OK reply. The default string includes the ident if present,
+ and also the IP address if present. Reflecting back the ident is intended
+ as a deterrent to mail forgers. For maximum efficiency, and also because
+ some broken systems expect each response to be in a single packet, arrange
+ that the entire reply is sent in one write(). */
- auth_advertised = FALSE;
- pipelining_advertised = FALSE;
+ fl.auth_advertised = FALSE;
+ f.smtp_in_pipelining_advertised = FALSE;
#ifdef SUPPORT_TLS
- tls_advertised = FALSE;
+ fl.tls_advertised = FALSE;
+# ifdef EXPERIMENTAL_REQUIRETLS
+ fl.requiretls_advertised = FALSE;
+# endif
#endif
- dsn_advertised = FALSE;
+ fl.dsn_advertised = FALSE;
#ifdef SUPPORT_I18N
- smtputf8_advertised = FALSE;
+ fl.smtputf8_advertised = FALSE;
#endif
- smtp_code = US"250 "; /* Default response code plus space*/
- if (user_msg == NULL)
- {
- s = string_sprintf("%.3s %s Hello %s%s%s",
- smtp_code,
- smtp_active_hostname,
- (sender_ident == NULL)? US"" : sender_ident,
- (sender_ident == NULL)? US"" : US" at ",
- (sender_host_name == NULL)? sender_helo_name : sender_host_name);
-
- ptr = Ustrlen(s);
- size = ptr + 1;
+ smtp_code = US"250 "; /* Default response code plus space*/
+ if (!user_msg)
+ {
+ g = string_fmt_append(NULL, "%.3s %s Hello %s%s%s",
+ smtp_code,
+ smtp_active_hostname,
+ sender_ident ? sender_ident : US"",
+ sender_ident ? US" at " : US"",
+ sender_host_name ? sender_host_name : sender_helo_name);
+
+ if (sender_host_address)
+ g = string_fmt_append(g, " [%s]", sender_host_address);
+ }
- if (sender_host_address != NULL)
- {
- s = string_catn(s, &size, &ptr, US" [", 2);
- s = string_cat (s, &size, &ptr, sender_host_address);
- s = string_catn(s, &size, &ptr, US"]", 1);
- }
- }
+ /* A user-supplied EHLO greeting may not contain more than one line. Note
+ that the code returned by smtp_message_code() includes the terminating
+ whitespace character. */
- /* A user-supplied EHLO greeting may not contain more than one line. Note
- that the code returned by smtp_message_code() includes the terminating
- whitespace character. */
+ else
+ {
+ char *ss;
+ int codelen = 4;
+ smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE);
+ s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
+ if ((ss = strpbrk(CS s, "\r\n")) != NULL)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
+ "newlines: message truncated: %s", string_printing(s));
+ *ss = 0;
+ }
+ g = string_cat(NULL, s);
+ }
- else
- {
- char *ss;
- int codelen = 4;
- smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE);
- s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
- if ((ss = strpbrk(CS s, "\r\n")) != NULL)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
- "newlines: message truncated: %s", string_printing(s));
- *ss = 0;
- }
- ptr = Ustrlen(s);
- size = ptr + 1;
- }
+ g = string_catn(g, US"\r\n", 2);
- s = string_catn(s, &size, &ptr, US"\r\n", 2);
+ /* If we received EHLO, we must create a multiline response which includes
+ the functions supported. */
- /* If we received EHLO, we must create a multiline response which includes
- the functions supported. */
+ if (fl.esmtp)
+ {
+ g->s[3] = '-';
- if (esmtp)
- {
- s[3] = '-';
+ /* I'm not entirely happy with this, as an MTA is supposed to check
+ that it has enough room to accept a message of maximum size before
+ it sends this. However, there seems little point in not sending it.
+ The actual size check happens later at MAIL FROM time. By postponing it
+ till then, VRFY and EXPN can be used after EHLO when space is short. */
- /* I'm not entirely happy with this, as an MTA is supposed to check
- that it has enough room to accept a message of maximum size before
- it sends this. However, there seems little point in not sending it.
- The actual size check happens later at MAIL FROM time. By postponing it
- till then, VRFY and EXPN can be used after EHLO when space is short. */
+ if (thismessage_size_limit > 0)
+ g = string_fmt_append(g, "%.3s-SIZE %d\r\n", smtp_code,
+ thismessage_size_limit);
+ else
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-SIZE\r\n", 7);
+ }
- if (thismessage_size_limit > 0)
- {
- sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
- thismessage_size_limit);
- s = string_cat(s, &size, &ptr, big_buffer);
- }
- else
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-SIZE\r\n", 7);
- }
+ /* Exim does not do protocol conversion or data conversion. It is 8-bit
+ clean; if it has an 8-bit character in its hand, it just sends it. It
+ cannot therefore specify 8BITMIME and remain consistent with the RFCs.
+ However, some users want this option simply in order to stop MUAs
+ mangling messages that contain top-bit-set characters. It is therefore
+ provided as an option. */
- /* Exim does not do protocol conversion or data conversion. It is 8-bit
- clean; if it has an 8-bit character in its hand, it just sends it. It
- cannot therefore specify 8BITMIME and remain consistent with the RFCs.
- However, some users want this option simply in order to stop MUAs
- mangling messages that contain top-bit-set characters. It is therefore
- provided as an option. */
+ if (accept_8bitmime)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-8BITMIME\r\n", 11);
+ }
- if (accept_8bitmime)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-8BITMIME\r\n", 11);
- }
+ /* Advertise DSN support if configured to do so. */
+ if (verify_check_host(&dsn_advertise_hosts) != FAIL)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-DSN\r\n", 6);
+ fl.dsn_advertised = TRUE;
+ }
- /* Advertise DSN support if configured to do so. */
- if (verify_check_host(&dsn_advertise_hosts) != FAIL)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-DSN\r\n", 6);
- dsn_advertised = TRUE;
- }
+ /* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is
+ permitted to issue them; a check is made when any host actually tries. */
- /* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is
- permitted to issue them; a check is made when any host actually tries. */
+ if (acl_smtp_etrn)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-ETRN\r\n", 7);
+ }
+ if (acl_smtp_vrfy)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-VRFY\r\n", 7);
+ }
+ if (acl_smtp_expn)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-EXPN\r\n", 7);
+ }
- if (acl_smtp_etrn)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-ETRN\r\n", 7);
- }
- if (acl_smtp_vrfy)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-VRFY\r\n", 7);
- }
- if (acl_smtp_expn)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-EXPN\r\n", 7);
- }
+ /* Exim is quite happy with pipelining, so let the other end know that
+ it is safe to use it, unless advertising is disabled. */
- /* Exim is quite happy with pipelining, so let the other end know that
- it is safe to use it, unless advertising is disabled. */
+ if ( f.pipelining_enable
+ && verify_check_host(&pipelining_advertise_hosts) == OK)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-PIPELINING\r\n", 13);
+ sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
+ f.smtp_in_pipelining_advertised = TRUE;
- if (pipelining_enable &&
- verify_check_host(&pipelining_advertise_hosts) == OK)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-PIPELINING\r\n", 13);
- sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
- pipelining_advertised = TRUE;
- }
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (fl.pipe_connect_acceptable)
+ {
+ f.smtp_in_early_pipe_advertised = TRUE;
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-" EARLY_PIPE_FEATURE_NAME "\r\n", EARLY_PIPE_FEATURE_LEN+3);
+ }
+#endif
+ }
- /* If any server authentication mechanisms are configured, advertise
- them if the current host is in auth_advertise_hosts. The problem with
- advertising always is that some clients then require users to
- authenticate (and aren't configurable otherwise) even though it may not
- be necessary (e.g. if the host is in host_accept_relay).
+ /* If any server authentication mechanisms are configured, advertise
+ them if the current host is in auth_advertise_hosts. The problem with
+ advertising always is that some clients then require users to
+ authenticate (and aren't configurable otherwise) even though it may not
+ be necessary (e.g. if the host is in host_accept_relay).
- RFC 2222 states that SASL mechanism names contain only upper case
- letters, so output the names in upper case, though we actually recognize
- them in either case in the AUTH command. */
+ RFC 2222 states that SASL mechanism names contain only upper case
+ letters, so output the names in upper case, though we actually recognize
+ them in either case in the AUTH command. */
- if ( auths
+ if ( auths
#ifdef AUTH_TLS
- && !sender_host_authenticated
+ && !sender_host_authenticated
#endif
- && verify_check_host(&auth_advertise_hosts) == OK
- )
- {
- auth_instance *au;
- BOOL first = TRUE;
- for (au = auths; au; au = au->next)
- if (au->server && (au->advertise_condition == NULL ||
- expand_check_condition(au->advertise_condition, au->name,
- US"authenticator")))
+ && verify_check_host(&auth_advertise_hosts) == OK
+ )
+ {
+ auth_instance *au;
+ BOOL first = TRUE;
+ for (au = auths; au; au = au->next)
{
- int saveptr;
- if (first)
+ au->advertised = FALSE;
+ if (au->server)
{
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-AUTH", 5);
- first = FALSE;
- auth_advertised = TRUE;
+ DEBUG(D_auth+D_expand) debug_printf_indent(
+ "Evaluating advertise_condition for %s athenticator\n",
+ au->public_name);
+ if ( !au->advertise_condition
+ || expand_check_condition(au->advertise_condition, au->name,
+ US"authenticator")
+ )
+ {
+ int saveptr;
+ if (first)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-AUTH", 5);
+ first = FALSE;
+ fl.auth_advertised = TRUE;
+ }
+ saveptr = g->ptr;
+ g = string_catn(g, US" ", 1);
+ g = string_cat (g, au->public_name);
+ while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]);
+ au->advertised = TRUE;
+ }
}
- saveptr = ptr;
- s = string_catn(s, &size, &ptr, US" ", 1);
- s = string_cat (s, &size, &ptr, au->public_name);
- while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
- au->advertised = TRUE;
}
- else
- au->advertised = FALSE;
- if (!first) s = string_catn(s, &size, &ptr, US"\r\n", 2);
- }
+ if (!first) g = string_catn(g, US"\r\n", 2);
+ }
- /* RFC 3030 CHUNKING */
+ /* RFC 3030 CHUNKING */
- if (verify_check_host(&chunking_advertise_hosts) != FAIL)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-CHUNKING\r\n", 11);
- chunking_offered = TRUE;
- chunking_state = CHUNKING_OFFERED;
- }
+ if (verify_check_host(&chunking_advertise_hosts) != FAIL)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-CHUNKING\r\n", 11);
+ f.chunking_offered = TRUE;
+ chunking_state = CHUNKING_OFFERED;
+ }
- /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
- if it has been included in the binary, and the host matches
- tls_advertise_hosts. We must *not* advertise if we are already in a
- secure connection. */
+ /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
+ if it has been included in the binary, and the host matches
+ tls_advertise_hosts. We must *not* advertise if we are already in a
+ secure connection. */
#ifdef SUPPORT_TLS
- if (tls_in.active < 0 &&
- verify_check_host(&tls_advertise_hosts) != FAIL)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-STARTTLS\r\n", 11);
- tls_advertised = TRUE;
- }
+ if (tls_in.active.sock < 0 &&
+ verify_check_host(&tls_advertise_hosts) != FAIL)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-STARTTLS\r\n", 11);
+ fl.tls_advertised = TRUE;
+ }
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+ /* Advertise REQUIRETLS only once we are in a secure connection */
+ if ( tls_in.active.sock >= 0
+ && verify_check_host(&tls_advertise_requiretls) != FAIL)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-REQUIRETLS\r\n", 13);
+ fl.requiretls_advertised = TRUE;
+ }
+# endif
#endif
#ifndef DISABLE_PRDR
- /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
- if (prdr_enable)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-PRDR\r\n", 7);
- }
+ /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
+ if (prdr_enable)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-PRDR\r\n", 7);
+ }
#endif
#ifdef SUPPORT_I18N
- if ( accept_8bitmime
- && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-SMTPUTF8\r\n", 11);
- smtputf8_advertised = TRUE;
- }
+ if ( accept_8bitmime
+ && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-SMTPUTF8\r\n", 11);
+ fl.smtputf8_advertised = TRUE;
+ }
#endif
- /* Finish off the multiline reply with one that is always available. */
-
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US" HELP\r\n", 7);
- }
+ /* Finish off the multiline reply with one that is always available. */
- /* Terminate the string (for debug), write it, and note that HELO/EHLO
- has been seen. */
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US" HELP\r\n", 7);
+ }
- s[ptr] = 0;
+ /* Terminate the string (for debug), write it, and note that HELO/EHLO
+ has been seen. */
#ifdef SUPPORT_TLS
- if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else
+ if (tls_in.active.sock >= 0)
+ (void)tls_write(NULL, g->s, g->ptr,
+# ifdef EXPERIMENTAL_PIPE_CONNECT
+ fl.pipe_connect_acceptable && pipeline_connect_sends());
+# else
+ FALSE);
+# endif
+ else
#endif
- {
- int i = fwrite(s, 1, ptr, smtp_out); i = i; /* compiler quietening */
- }
- DEBUG(D_receive)
- {
- uschar *cr;
- while ((cr = Ustrchr(s, '\r')) != NULL) /* lose CRs */
- memmove(cr, cr + 1, (ptr--) - (cr - s));
- debug_printf("SMTP>> %s", s);
- }
- helo_seen = TRUE;
-
- /* Reset the protocol and the state, abandoning any previous message. */
- received_protocol =
- (sender_host_address ? protocols : protocols_local)
- [ (esmtp
- ? pextend + (sender_host_authenticated ? pauthed : 0)
- : pnormal)
- + (tls_in.active >= 0 ? pcrpted : 0)
- ];
- smtp_reset(reset_point);
- toomany = FALSE;
- break; /* HELO/EHLO */
+ {
+ int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening */
+ }
+ DEBUG(D_receive)
+ {
+ uschar *cr;
+ (void) string_from_gstring(g);
+ while ((cr = Ustrchr(g->s, '\r')) != NULL) /* lose CRs */
+ memmove(cr, cr + 1, (g->ptr--) - (cr - g->s));
+ debug_printf("SMTP>> %s", g->s);
+ }
+ fl.helo_seen = TRUE;
- /* The MAIL command requires an address as an operand. All we do
+ /* Reset the protocol and the state, abandoning any previous message. */
+ received_protocol =
+ (sender_host_address ? protocols : protocols_local)
+ [ (fl.esmtp
+ ? pextend + (sender_host_authenticated ? pauthed : 0)
+ : pnormal)
+ + (tls_in.active.sock >= 0 ? pcrpted : 0)
+ ];
+ cancel_cutthrough_connection(TRUE, US"sent EHLO response");
+ smtp_reset(reset_point);
+ toomany = FALSE;
+ break; /* HELO/EHLO */
+
+
+ /* The MAIL command requires an address as an operand. All we do
here is to parse it for syntactic correctness. The form "<>" is
a special case which converts into an empty string. The start/end
pointers in the original are not used further for this address, as
it is the canonical extracted address which is all that is kept. */
case MAIL_CMD:
- HAD(SCH_MAIL);
- smtp_mailcmd_count++; /* Count for limit and ratelimit */
- was_rej_mail = TRUE; /* Reset if accepted */
- env_mail_type_t * mail_args; /* Sanity check & validate args */
+ HAD(SCH_MAIL);
+ smtp_mailcmd_count++; /* Count for limit and ratelimit */
+ was_rej_mail = TRUE; /* Reset if accepted */
+ env_mail_type_t * mail_args; /* Sanity check & validate args */
- if (helo_required && !helo_seen)
- {
- smtp_printf("503 HELO or EHLO required\r\n");
- log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
- "HELO/EHLO given", host_and_ident(FALSE));
- break;
- }
+ if (fl.helo_required && !fl.helo_seen)
+ {
+ smtp_printf("503 HELO or EHLO required\r\n", FALSE);
+ log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
+ "HELO/EHLO given", host_and_ident(FALSE));
+ break;
+ }
- if (sender_address != NULL)
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"sender already given");
- break;
- }
+ if (sender_address)
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"sender already given");
+ break;
+ }
- if (smtp_cmd_data[0] == 0)
- {
- done = synprot_error(L_smtp_protocol_error, 501, NULL,
- US"MAIL must have an address operand");
- break;
- }
+ if (!*smtp_cmd_data)
+ {
+ done = synprot_error(L_smtp_protocol_error, 501, NULL,
+ US"MAIL must have an address operand");
+ break;
+ }
- /* Check to see if the limit for messages per connection would be
- exceeded by accepting further messages. */
+ /* Check to see if the limit for messages per connection would be
+ exceeded by accepting further messages. */
- if (smtp_accept_max_per_connection > 0 &&
- smtp_mailcmd_count > smtp_accept_max_per_connection)
- {
- smtp_printf("421 too many messages in this connection\r\n");
- log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
- "messages in one connection", host_and_ident(TRUE));
- break;
- }
+ if (smtp_accept_max_per_connection > 0 &&
+ smtp_mailcmd_count > smtp_accept_max_per_connection)
+ {
+ smtp_printf("421 too many messages in this connection\r\n", FALSE);
+ log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
+ "messages in one connection", host_and_ident(TRUE));
+ break;
+ }
- /* Reset for start of message - even if this is going to fail, we
- obviously need to throw away any previous data. */
+ /* Reset for start of message - even if this is going to fail, we
+ obviously need to throw away any previous data. */
- smtp_reset(reset_point);
- toomany = FALSE;
- sender_data = recipient_data = NULL;
+ cancel_cutthrough_connection(TRUE, US"MAIL received");
+ smtp_reset(reset_point);
+ toomany = FALSE;
+ sender_data = recipient_data = NULL;
- /* Loop, checking for ESMTP additions to the MAIL FROM command. */
+ /* Loop, checking for ESMTP additions to the MAIL FROM command. */
- if (esmtp) for(;;)
- {
- uschar *name, *value, *end;
- unsigned long int size;
- BOOL arg_error = FALSE;
+ if (fl.esmtp) for(;;)
+ {
+ uschar *name, *value, *end;
+ unsigned long int size;
+ BOOL arg_error = FALSE;
- if (!extract_option(&name, &value)) break;
+ if (!extract_option(&name, &value)) break;
- for (mail_args = env_mail_type_list;
- mail_args->value != ENV_MAIL_OPT_NULL;
- mail_args++
- )
- if (strcmpic(name, mail_args->name) == 0)
- break;
- if (mail_args->need_value && strcmpic(value, US"") == 0)
- break;
-
- switch(mail_args->value)
- {
- /* Handle SIZE= by reading the value. We don't do the check till later,
- in order to be able to log the sender address on failure. */
- case ENV_MAIL_OPT_SIZE:
- if (((size = Ustrtoul(value, &end, 10)), *end == 0))
- {
- if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
- size = INT_MAX;
- message_size = (int)size;
- }
- else
- arg_error = TRUE;
- break;
+ for (mail_args = env_mail_type_list;
+ mail_args->value != ENV_MAIL_OPT_NULL;
+ mail_args++
+ )
+ if (strcmpic(name, mail_args->name) == 0)
+ break;
+ if (mail_args->need_value && strcmpic(value, US"") == 0)
+ break;
- /* If this session was initiated with EHLO and accept_8bitmime is set,
- Exim will have indicated that it supports the BODY=8BITMIME option. In
- fact, it does not support this according to the RFCs, in that it does not
- take any special action for forwarding messages containing 8-bit
- characters. That is why accept_8bitmime is not the default setting, but
- some sites want the action that is provided. We recognize both "8BITMIME"
- and "7BIT" as body types, but take no action. */
- case ENV_MAIL_OPT_BODY:
- if (accept_8bitmime) {
- if (strcmpic(value, US"8BITMIME") == 0)
- body_8bitmime = 8;
- else if (strcmpic(value, US"7BIT") == 0)
- body_8bitmime = 7;
- else
+ switch(mail_args->value)
+ {
+ /* Handle SIZE= by reading the value. We don't do the check till later,
+ in order to be able to log the sender address on failure. */
+ case ENV_MAIL_OPT_SIZE:
+ if (((size = Ustrtoul(value, &end, 10)), *end == 0))
{
- body_8bitmime = 0;
- done = synprot_error(L_smtp_syntax_error, 501, NULL,
- US"invalid data for BODY");
- goto COMMAND_LOOP;
- }
- DEBUG(D_receive) debug_printf("8BITMIME: %d\n", body_8bitmime);
+ if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
+ size = INT_MAX;
+ message_size = (int)size;
+ }
+ else
+ arg_error = TRUE;
break;
- }
- arg_error = TRUE;
- break;
- /* Handle the two DSN options, but only if configured to do so (which
- will have caused "DSN" to be given in the EHLO response). The code itself
- is included only if configured in at build time. */
+ /* If this session was initiated with EHLO and accept_8bitmime is set,
+ Exim will have indicated that it supports the BODY=8BITMIME option. In
+ fact, it does not support this according to the RFCs, in that it does not
+ take any special action for forwarding messages containing 8-bit
+ characters. That is why accept_8bitmime is not the default setting, but
+ some sites want the action that is provided. We recognize both "8BITMIME"
+ and "7BIT" as body types, but take no action. */
+ case ENV_MAIL_OPT_BODY:
+ if (accept_8bitmime) {
+ if (strcmpic(value, US"8BITMIME") == 0)
+ body_8bitmime = 8;
+ else if (strcmpic(value, US"7BIT") == 0)
+ body_8bitmime = 7;
+ else
+ {
+ body_8bitmime = 0;
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"invalid data for BODY");
+ goto COMMAND_LOOP;
+ }
+ DEBUG(D_receive) debug_printf("8BITMIME: %d\n", body_8bitmime);
+ break;
+ }
+ arg_error = TRUE;
+ break;
- case ENV_MAIL_OPT_RET:
- if (dsn_advertised)
- {
- /* Check if RET has already been set */
- if (dsn_ret > 0)
- {
- synprot_error(L_smtp_syntax_error, 501, NULL,
- US"RET can be specified once only");
- goto COMMAND_LOOP;
- }
- dsn_ret = strcmpic(value, US"HDRS") == 0
- ? dsn_ret_hdrs
- : strcmpic(value, US"FULL") == 0
- ? dsn_ret_full
- : 0;
- DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret);
- /* Check for invalid invalid value, and exit with error */
- if (dsn_ret == 0)
+ /* Handle the two DSN options, but only if configured to do so (which
+ will have caused "DSN" to be given in the EHLO response). The code itself
+ is included only if configured in at build time. */
+
+ case ENV_MAIL_OPT_RET:
+ if (fl.dsn_advertised)
{
- synprot_error(L_smtp_syntax_error, 501, NULL,
- US"Value for RET is invalid");
- goto COMMAND_LOOP;
+ /* Check if RET has already been set */
+ if (dsn_ret > 0)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"RET can be specified once only");
+ goto COMMAND_LOOP;
+ }
+ dsn_ret = strcmpic(value, US"HDRS") == 0
+ ? dsn_ret_hdrs
+ : strcmpic(value, US"FULL") == 0
+ ? dsn_ret_full
+ : 0;
+ DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret);
+ /* Check for invalid invalid value, and exit with error */
+ if (dsn_ret == 0)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"Value for RET is invalid");
+ goto COMMAND_LOOP;
+ }
}
- }
- break;
- case ENV_MAIL_OPT_ENVID:
- if (dsn_advertised)
- {
- /* Check if the dsn envid has been already set */
- if (dsn_envid != NULL)
+ break;
+ case ENV_MAIL_OPT_ENVID:
+ if (fl.dsn_advertised)
{
- synprot_error(L_smtp_syntax_error, 501, NULL,
- US"ENVID can be specified once only");
- goto COMMAND_LOOP;
+ /* Check if the dsn envid has been already set */
+ if (dsn_envid)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"ENVID can be specified once only");
+ goto COMMAND_LOOP;
+ }
+ dsn_envid = string_copy(value);
+ DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid);
}
- dsn_envid = string_copy(value);
- DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid);
- }
- break;
-
- /* Handle the AUTH extension. If the value given is not "<>" and either
- the ACL says "yes" or there is no ACL but the sending host is
- authenticated, we set it up as the authenticated sender. However, if the
- authenticator set a condition to be tested, we ignore AUTH on MAIL unless
- the condition is met. The value of AUTH is an xtext, which means that +,
- = and cntrl chars are coded in hex; however "<>" is unaffected by this
- coding. */
- case ENV_MAIL_OPT_AUTH:
- if (Ustrcmp(value, "<>") != 0)
- {
- int rc;
- uschar *ignore_msg;
+ break;
- if (auth_xtextdecode(value, &authenticated_sender) < 0)
- {
- /* Put back terminator overrides for error message */
- value[-1] = '=';
- name[-1] = ' ';
- done = synprot_error(L_smtp_syntax_error, 501, NULL,
- US"invalid data for AUTH");
- goto COMMAND_LOOP;
- }
- if (acl_smtp_mailauth == NULL)
- {
- ignore_msg = US"client not authenticated";
- rc = (sender_host_authenticated != NULL)? OK : FAIL;
- }
- else
- {
- ignore_msg = US"rejected by ACL";
- rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
- &user_msg, &log_msg);
- }
+ /* Handle the AUTH extension. If the value given is not "<>" and either
+ the ACL says "yes" or there is no ACL but the sending host is
+ authenticated, we set it up as the authenticated sender. However, if the
+ authenticator set a condition to be tested, we ignore AUTH on MAIL unless
+ the condition is met. The value of AUTH is an xtext, which means that +,
+ = and cntrl chars are coded in hex; however "<>" is unaffected by this
+ coding. */
+ case ENV_MAIL_OPT_AUTH:
+ if (Ustrcmp(value, "<>") != 0)
+ {
+ int rc;
+ uschar *ignore_msg;
- switch (rc)
- {
- case OK:
- if (authenticated_by == NULL ||
- authenticated_by->mail_auth_condition == NULL ||
- expand_check_condition(authenticated_by->mail_auth_condition,
- authenticated_by->name, US"authenticator"))
- break; /* Accept the AUTH */
-
- ignore_msg = US"server_mail_auth_condition failed";
- if (authenticated_id != NULL)
- ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
- ignore_msg, authenticated_id);
-
- /* Fall through */
-
- case FAIL:
- authenticated_sender = NULL;
- log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
- value, host_and_ident(TRUE), ignore_msg);
- break;
-
- /* Should only get DEFER or ERROR here. Put back terminator
- overrides for error message */
-
- default:
+ if (auth_xtextdecode(value, &authenticated_sender) < 0)
+ {
+ /* Put back terminator overrides for error message */
value[-1] = '=';
name[-1] = ' ';
- (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
- log_msg);
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"invalid data for AUTH");
goto COMMAND_LOOP;
- }
- }
- break;
+ }
+ if (!acl_smtp_mailauth)
+ {
+ ignore_msg = US"client not authenticated";
+ rc = sender_host_authenticated ? OK : FAIL;
+ }
+ else
+ {
+ ignore_msg = US"rejected by ACL";
+ rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
+ &user_msg, &log_msg);
+ }
+
+ switch (rc)
+ {
+ case OK:
+ if (authenticated_by == NULL ||
+ authenticated_by->mail_auth_condition == NULL ||
+ expand_check_condition(authenticated_by->mail_auth_condition,
+ authenticated_by->name, US"authenticator"))
+ break; /* Accept the AUTH */
+
+ ignore_msg = US"server_mail_auth_condition failed";
+ if (authenticated_id != NULL)
+ ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
+ ignore_msg, authenticated_id);
+
+ /* Fall through */
+
+ case FAIL:
+ authenticated_sender = NULL;
+ log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
+ value, host_and_ident(TRUE), ignore_msg);
+ break;
+
+ /* Should only get DEFER or ERROR here. Put back terminator
+ overrides for error message */
+
+ default:
+ value[-1] = '=';
+ name[-1] = ' ';
+ (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
+ log_msg);
+ goto COMMAND_LOOP;
+ }
+ }
+ break;
#ifndef DISABLE_PRDR
- case ENV_MAIL_OPT_PRDR:
- if (prdr_enable)
- prdr_requested = TRUE;
- break;
+ case ENV_MAIL_OPT_PRDR:
+ if (prdr_enable)
+ prdr_requested = TRUE;
+ break;
#endif
#ifdef SUPPORT_I18N
- case ENV_MAIL_OPT_UTF8:
- if (smtputf8_advertised)
- {
- int old_pool = store_pool;
+ case ENV_MAIL_OPT_UTF8:
+ if (!fl.smtputf8_advertised)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"SMTPUTF8 used when not advertised");
+ goto COMMAND_LOOP;
+ }
DEBUG(D_receive) debug_printf("smtputf8 requested\n");
message_smtputf8 = allow_utf8_domains = TRUE;
- store_pool = POOL_PERM;
- received_protocol = string_sprintf("utf8%s", received_protocol);
- store_pool = old_pool;
+ if (Ustrncmp(received_protocol, US"utf8", 4) != 0)
+ {
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
+ received_protocol = string_sprintf("utf8%s", received_protocol);
+ store_pool = old_pool;
+ }
+ break;
+#endif
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ case ENV_MAIL_OPT_REQTLS:
+ {
+ uschar * r, * t;
+
+ if (!fl.requiretls_advertised)
+ {
+ done = synprot_error(L_smtp_syntax_error, 555, NULL,
+ US"unadvertised MAIL option: REQUIRETLS");
+ goto COMMAND_LOOP;
+ }
+
+ DEBUG(D_receive) debug_printf("requiretls requested\n");
+ tls_requiretls = REQUIRETLS_MSG;
+
+ r = string_copy_malloc(received_protocol);
+ if ((t = Ustrrchr(r, 's'))) *t = 'S';
+ received_protocol = r;
}
- break;
+ break;
#endif
- /* No valid option. Stick back the terminator characters and break
- the loop. Do the name-terminator second as extract_option sets
- value==name when it found no equal-sign.
- An error for a malformed address will occur. */
- case ENV_MAIL_OPT_NULL:
- value[-1] = '=';
- name[-1] = ' ';
- arg_error = TRUE;
- break;
- default: assert(0);
- }
- /* Break out of for loop if switch() had bad argument or
- when start of the email address is reached */
- if (arg_error) break;
- }
+ /* No valid option. Stick back the terminator characters and break
+ the loop. Do the name-terminator second as extract_option sets
+ value==name when it found no equal-sign.
+ An error for a malformed address will occur. */
+ case ENV_MAIL_OPT_NULL:
+ value[-1] = '=';
+ name[-1] = ' ';
+ arg_error = TRUE;
+ break;
- /* If we have passed the threshold for rate limiting, apply the current
- delay, and update it for next time, provided this is a limited host. */
+ default: assert(0);
+ }
+ /* Break out of for loop if switch() had bad argument or
+ when start of the email address is reached */
+ if (arg_error) break;
+ }
- if (smtp_mailcmd_count > smtp_rlm_threshold &&
- verify_check_host(&smtp_ratelimit_hosts) == OK)
- {
- DEBUG(D_receive) debug_printf("rate limit MAIL: delay %.3g sec\n",
- smtp_delay_mail/1000.0);
- millisleep((int)smtp_delay_mail);
- smtp_delay_mail *= smtp_rlm_factor;
- if (smtp_delay_mail > (double)smtp_rlm_limit)
- smtp_delay_mail = (double)smtp_rlm_limit;
- }
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ if (tls_requiretls & REQUIRETLS_MSG)
+ {
+ /* Ensure headers-only bounces whether a RET option was given or not. */
- /* Now extract the address, first applying any SMTP-time rewriting. The
- TRUE flag allows "<>" as a sender address. */
+ DEBUG(D_receive) if (dsn_ret == dsn_ret_full)
+ debug_printf("requiretls override: dsn_ret_full -> dsn_ret_hdrs\n");
+ dsn_ret = dsn_ret_hdrs;
+ }
+#endif
- raw_sender = rewrite_existflags & rewrite_smtp
- ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
- global_rewrite_rules)
- : smtp_cmd_data;
+ /* If we have passed the threshold for rate limiting, apply the current
+ delay, and update it for next time, provided this is a limited host. */
- raw_sender =
- parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
- TRUE);
+ if (smtp_mailcmd_count > smtp_rlm_threshold &&
+ verify_check_host(&smtp_ratelimit_hosts) == OK)
+ {
+ DEBUG(D_receive) debug_printf("rate limit MAIL: delay %.3g sec\n",
+ smtp_delay_mail/1000.0);
+ millisleep((int)smtp_delay_mail);
+ smtp_delay_mail *= smtp_rlm_factor;
+ if (smtp_delay_mail > (double)smtp_rlm_limit)
+ smtp_delay_mail = (double)smtp_rlm_limit;
+ }
- if (!raw_sender)
- {
- done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
- break;
- }
+ /* Now extract the address, first applying any SMTP-time rewriting. The
+ TRUE flag allows "<>" as a sender address. */
- sender_address = raw_sender;
+ raw_sender = rewrite_existflags & rewrite_smtp
+ ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+ global_rewrite_rules)
+ : smtp_cmd_data;
- /* If there is a configured size limit for mail, check that this message
- doesn't exceed it. The check is postponed to this point so that the sender
- can be logged. */
+ raw_sender =
+ parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
+ TRUE);
- if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
- {
- smtp_printf("552 Message size exceeds maximum permitted\r\n");
- log_write(L_size_reject,
- LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
- "message too big: size%s=%d max=%d",
- sender_address,
- host_and_ident(TRUE),
- (message_size == INT_MAX)? ">" : "",
- message_size,
- thismessage_size_limit);
- sender_address = NULL;
- break;
- }
+ if (!raw_sender)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
+ break;
+ }
- /* Check there is enough space on the disk unless configured not to.
- When smtp_check_spool_space is set, the check is for thismessage_size_limit
- plus the current message - i.e. we accept the message only if it won't
- reduce the space below the threshold. Add 5000 to the size to allow for
- overheads such as the Received: line and storing of recipients, etc.
- By putting the check here, even when SIZE is not given, it allow VRFY
- and EXPN etc. to be used when space is short. */
-
- if (!receive_check_fs(
- (smtp_check_spool_space && message_size >= 0)?
- message_size + 5000 : 0))
- {
- smtp_printf("452 Space shortage, please try later\r\n");
- sender_address = NULL;
- break;
- }
+ sender_address = raw_sender;
- /* If sender_address is unqualified, reject it, unless this is a locally
- generated message, or the sending host or net is permitted to send
- unqualified addresses - typically local machines behaving as MUAs -
- in which case just qualify the address. The flag is set above at the start
- of the SMTP connection. */
+ /* If there is a configured size limit for mail, check that this message
+ doesn't exceed it. The check is postponed to this point so that the sender
+ can be logged. */
- if (sender_domain == 0 && sender_address[0] != 0)
- {
- if (allow_unqualified_sender)
- {
- sender_domain = Ustrlen(sender_address) + 1;
- sender_address = rewrite_address_qualify(sender_address, FALSE);
- DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
- raw_sender);
- }
- else
- {
- smtp_printf("501 %s: sender address must contain a domain\r\n",
- smtp_cmd_data);
- log_write(L_smtp_syntax_error,
- LOG_MAIN|LOG_REJECT,
- "unqualified sender rejected: <%s> %s%s",
- raw_sender,
- host_and_ident(TRUE),
- host_lookup_msg);
- sender_address = NULL;
- break;
- }
- }
+ if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
+ {
+ smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE);
+ log_write(L_size_reject,
+ LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
+ "message too big: size%s=%d max=%d",
+ sender_address,
+ host_and_ident(TRUE),
+ (message_size == INT_MAX)? ">" : "",
+ message_size,
+ thismessage_size_limit);
+ sender_address = NULL;
+ break;
+ }
- /* Apply an ACL check if one is defined, before responding. Afterwards,
- when pipelining is not advertised, do another sync check in case the ACL
- delayed and the client started sending in the meantime. */
+ /* Check there is enough space on the disk unless configured not to.
+ When smtp_check_spool_space is set, the check is for thismessage_size_limit
+ plus the current message - i.e. we accept the message only if it won't
+ reduce the space below the threshold. Add 5000 to the size to allow for
+ overheads such as the Received: line and storing of recipients, etc.
+ By putting the check here, even when SIZE is not given, it allow VRFY
+ and EXPN etc. to be used when space is short. */
+
+ if (!receive_check_fs(
+ (smtp_check_spool_space && message_size >= 0)?
+ message_size + 5000 : 0))
+ {
+ smtp_printf("452 Space shortage, please try later\r\n", FALSE);
+ sender_address = NULL;
+ break;
+ }
- if (acl_smtp_mail)
- {
- rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
- if (rc == OK && !pipelining_advertised && !check_sync())
- goto SYNC_FAILURE;
- }
- else
- rc = OK;
+ /* If sender_address is unqualified, reject it, unless this is a locally
+ generated message, or the sending host or net is permitted to send
+ unqualified addresses - typically local machines behaving as MUAs -
+ in which case just qualify the address. The flag is set above at the start
+ of the SMTP connection. */
- if (rc == OK || rc == DISCARD)
- {
- if (!user_msg)
- smtp_printf("%s%s%s", US"250 OK",
- #ifndef DISABLE_PRDR
- prdr_requested ? US", PRDR Requested" : US"",
- #else
- US"",
- #endif
- US"\r\n");
+ if (!sender_domain && *sender_address)
+ if (f.allow_unqualified_sender)
+ {
+ sender_domain = Ustrlen(sender_address) + 1;
+ sender_address = rewrite_address_qualify(sender_address, FALSE);
+ DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+ raw_sender);
+ }
+ else
+ {
+ smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE,
+ smtp_cmd_data);
+ log_write(L_smtp_syntax_error,
+ LOG_MAIN|LOG_REJECT,
+ "unqualified sender rejected: <%s> %s%s",
+ raw_sender,
+ host_and_ident(TRUE),
+ host_lookup_msg);
+ sender_address = NULL;
+ break;
+ }
+
+ /* Apply an ACL check if one is defined, before responding. Afterwards,
+ when pipelining is not advertised, do another sync check in case the ACL
+ delayed and the client started sending in the meantime. */
+
+ if (acl_smtp_mail)
+ {
+ rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
+ if (rc == OK && !f.smtp_in_pipelining_advertised && !check_sync())
+ goto SYNC_FAILURE;
+ }
else
- {
- #ifndef DISABLE_PRDR
- if (prdr_requested)
- user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
- #endif
- smtp_user_msg(US"250", user_msg);
- }
- smtp_delay_rcpt = smtp_rlr_base;
- recipients_discarded = (rc == DISCARD);
- was_rej_mail = FALSE;
- }
- else
- {
- done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg);
- sender_address = NULL;
- }
- break;
+ rc = OK;
+
+ if (rc == OK || rc == DISCARD)
+ {
+ BOOL more = pipeline_response();
+
+ if (!user_msg)
+ smtp_printf("%s%s%s", more, US"250 OK",
+ #ifndef DISABLE_PRDR
+ prdr_requested ? US", PRDR Requested" : US"",
+ #else
+ US"",
+ #endif
+ US"\r\n");
+ else
+ {
+ #ifndef DISABLE_PRDR
+ if (prdr_requested)
+ user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
+ #endif
+ smtp_user_msg(US"250", user_msg);
+ }
+ smtp_delay_rcpt = smtp_rlr_base;
+ f.recipients_discarded = (rc == DISCARD);
+ was_rej_mail = FALSE;
+ }
+ else
+ {
+ done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg);
+ sender_address = NULL;
+ }
+ break;
/* The RCPT command requires an address as an operand. There may be any
are not used, as we keep only the extracted address. */
case RCPT_CMD:
- HAD(SCH_RCPT);
- rcpt_count++;
- was_rcpt = rcpt_in_progress = TRUE;
+ HAD(SCH_RCPT);
+ rcpt_count++;
+ was_rcpt = fl.rcpt_in_progress = TRUE;
- /* There must be a sender address; if the sender was rejected and
- pipelining was advertised, we assume the client was pipelining, and do not
- count this as a protocol error. Reset was_rej_mail so that further RCPTs
- get the same treatment. */
+ /* There must be a sender address; if the sender was rejected and
+ pipelining was advertised, we assume the client was pipelining, and do not
+ count this as a protocol error. Reset was_rej_mail so that further RCPTs
+ get the same treatment. */
- if (sender_address == NULL)
- {
- if (pipelining_advertised && last_was_rej_mail)
- {
- smtp_printf("503 sender not yet given\r\n");
- was_rej_mail = TRUE;
- }
- else
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"sender not yet given");
- was_rcpt = FALSE; /* Not a valid RCPT */
- }
- rcpt_fail_count++;
- break;
- }
+ if (sender_address == NULL)
+ {
+ if (f.smtp_in_pipelining_advertised && last_was_rej_mail)
+ {
+ smtp_printf("503 sender not yet given\r\n", FALSE);
+ was_rej_mail = TRUE;
+ }
+ else
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"sender not yet given");
+ was_rcpt = FALSE; /* Not a valid RCPT */
+ }
+ rcpt_fail_count++;
+ break;
+ }
- /* Check for an operand */
+ /* Check for an operand */
- if (smtp_cmd_data[0] == 0)
- {
- done = synprot_error(L_smtp_syntax_error, 501, NULL,
- US"RCPT must have an address operand");
- rcpt_fail_count++;
- break;
- }
+ if (smtp_cmd_data[0] == 0)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"RCPT must have an address operand");
+ rcpt_fail_count++;
+ break;
+ }
- /* Set the DSN flags orcpt and dsn_flags from the session*/
- orcpt = NULL;
- flags = 0;
+ /* Set the DSN flags orcpt and dsn_flags from the session*/
+ orcpt = NULL;
+ dsn_flags = 0;
- if (esmtp) for(;;)
- {
- uschar *name, *value;
+ if (fl.esmtp) for(;;)
+ {
+ uschar *name, *value;
- if (!extract_option(&name, &value))
- break;
+ if (!extract_option(&name, &value))
+ break;
- if (dsn_advertised && strcmpic(name, US"ORCPT") == 0)
- {
- /* Check whether orcpt has been already set */
- if (orcpt)
+ if (fl.dsn_advertised && strcmpic(name, US"ORCPT") == 0)
{
- synprot_error(L_smtp_syntax_error, 501, NULL,
- US"ORCPT can be specified once only");
- goto COMMAND_LOOP;
- }
- orcpt = string_copy(value);
- DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt);
- }
+ /* Check whether orcpt has been already set */
+ if (orcpt)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"ORCPT can be specified once only");
+ goto COMMAND_LOOP;
+ }
+ orcpt = string_copy(value);
+ DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt);
+ }
- else if (dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
- {
- /* Check if the notify flags have been already set */
- if (flags > 0)
+ else if (fl.dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
{
- synprot_error(L_smtp_syntax_error, 501, NULL,
- US"NOTIFY can be specified once only");
- goto COMMAND_LOOP;
- }
- if (strcmpic(value, US"NEVER") == 0)
- flags |= rf_notify_never;
- else
- {
- uschar *p = value;
- while (*p != 0)
- {
- uschar *pp = p;
- while (*pp != 0 && *pp != ',') pp++;
- if (*pp == ',') *pp++ = 0;
- if (strcmpic(p, US"SUCCESS") == 0)
- {
- DEBUG(D_receive) debug_printf("DSN: Setting notify success\n");
- flags |= rf_notify_success;
- }
- else if (strcmpic(p, US"FAILURE") == 0)
- {
- DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n");
- flags |= rf_notify_failure;
- }
- else if (strcmpic(p, US"DELAY") == 0)
- {
- DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n");
- flags |= rf_notify_delay;
- }
- else
+ /* Check if the notify flags have been already set */
+ if (dsn_flags > 0)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"NOTIFY can be specified once only");
+ goto COMMAND_LOOP;
+ }
+ if (strcmpic(value, US"NEVER") == 0)
+ dsn_flags |= rf_notify_never;
+ else
+ {
+ uschar *p = value;
+ while (*p != 0)
{
- /* Catch any strange values */
- synprot_error(L_smtp_syntax_error, 501, NULL,
- US"Invalid value for NOTIFY parameter");
- goto COMMAND_LOOP;
- }
- p = pp;
- }
- DEBUG(D_receive) debug_printf("DSN Flags: %x\n", flags);
- }
- }
+ uschar *pp = p;
+ while (*pp != 0 && *pp != ',') pp++;
+ if (*pp == ',') *pp++ = 0;
+ if (strcmpic(p, US"SUCCESS") == 0)
+ {
+ DEBUG(D_receive) debug_printf("DSN: Setting notify success\n");
+ dsn_flags |= rf_notify_success;
+ }
+ else if (strcmpic(p, US"FAILURE") == 0)
+ {
+ DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n");
+ dsn_flags |= rf_notify_failure;
+ }
+ else if (strcmpic(p, US"DELAY") == 0)
+ {
+ DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n");
+ dsn_flags |= rf_notify_delay;
+ }
+ else
+ {
+ /* Catch any strange values */
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"Invalid value for NOTIFY parameter");
+ goto COMMAND_LOOP;
+ }
+ p = pp;
+ }
+ DEBUG(D_receive) debug_printf("DSN Flags: %x\n", dsn_flags);
+ }
+ }
- /* Unknown option. Stick back the terminator characters and break
- the loop. An error for a malformed address will occur. */
+ /* Unknown option. Stick back the terminator characters and break
+ the loop. An error for a malformed address will occur. */
- else
- {
- DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
- name[-1] = ' ';
- value[-1] = '=';
- break;
- }
- }
+ else
+ {
+ DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
+ name[-1] = ' ';
+ value[-1] = '=';
+ break;
+ }
+ }
- /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
- as a recipient address */
+ /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
+ as a recipient address */
- recipient = rewrite_existflags & rewrite_smtp
- ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
- global_rewrite_rules)
- : smtp_cmd_data;
+ recipient = rewrite_existflags & rewrite_smtp
+ ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+ global_rewrite_rules)
+ : smtp_cmd_data;
- if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end,
- &recipient_domain, FALSE)))
- {
- done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
- rcpt_fail_count++;
- break;
- }
+ if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end,
+ &recipient_domain, FALSE)))
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
+ rcpt_fail_count++;
+ break;
+ }
- /* If the recipient address is unqualified, reject it, unless this is a
- locally generated message. However, unqualified addresses are permitted
- from a configured list of hosts and nets - typically when behaving as
- MUAs rather than MTAs. Sad that SMTP is used for both types of traffic,
- really. The flag is set at the start of the SMTP connection.
+ /* If the recipient address is unqualified, reject it, unless this is a
+ locally generated message. However, unqualified addresses are permitted
+ from a configured list of hosts and nets - typically when behaving as
+ MUAs rather than MTAs. Sad that SMTP is used for both types of traffic,
+ really. The flag is set at the start of the SMTP connection.
- RFC 1123 talks about supporting "the reserved mailbox postmaster"; I always
- assumed this meant "reserved local part", but the revision of RFC 821 and
- friends now makes it absolutely clear that it means *mailbox*. Consequently
- we must always qualify this address, regardless. */
+ RFC 1123 talks about supporting "the reserved mailbox postmaster"; I always
+ assumed this meant "reserved local part", but the revision of RFC 821 and
+ friends now makes it absolutely clear that it means *mailbox*. Consequently
+ we must always qualify this address, regardless. */
- if (!recipient_domain)
- if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
- US"recipient")))
- {
- rcpt_fail_count++;
- break;
- }
+ if (!recipient_domain)
+ if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
+ US"recipient")))
+ {
+ rcpt_fail_count++;
+ break;
+ }
- /* Check maximum allowed */
+ /* Check maximum allowed */
- if (rcpt_count > recipients_max && recipients_max > 0)
- {
- if (recipients_max_reject)
- {
- rcpt_fail_count++;
- smtp_printf("552 too many recipients\r\n");
- if (!toomany)
- log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
- "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
- }
- else
- {
- rcpt_defer_count++;
- smtp_printf("452 too many recipients\r\n");
- if (!toomany)
- log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
- "temporarily rejected: sender=<%s> %s", sender_address,
- host_and_ident(TRUE));
- }
+ if (rcpt_count > recipients_max && recipients_max > 0)
+ {
+ if (recipients_max_reject)
+ {
+ rcpt_fail_count++;
+ smtp_printf("552 too many recipients\r\n", FALSE);
+ if (!toomany)
+ log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
+ "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
+ }
+ else
+ {
+ rcpt_defer_count++;
+ smtp_printf("452 too many recipients\r\n", FALSE);
+ if (!toomany)
+ log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
+ "temporarily rejected: sender=<%s> %s", sender_address,
+ host_and_ident(TRUE));
+ }
- toomany = TRUE;
- break;
- }
+ toomany = TRUE;
+ break;
+ }
- /* If we have passed the threshold for rate limiting, apply the current
- delay, and update it for next time, provided this is a limited host. */
+ /* If we have passed the threshold for rate limiting, apply the current
+ delay, and update it for next time, provided this is a limited host. */
- if (rcpt_count > smtp_rlr_threshold &&
- verify_check_host(&smtp_ratelimit_hosts) == OK)
- {
- DEBUG(D_receive) debug_printf("rate limit RCPT: delay %.3g sec\n",
- smtp_delay_rcpt/1000.0);
- millisleep((int)smtp_delay_rcpt);
- smtp_delay_rcpt *= smtp_rlr_factor;
- if (smtp_delay_rcpt > (double)smtp_rlr_limit)
- smtp_delay_rcpt = (double)smtp_rlr_limit;
- }
+ if (rcpt_count > smtp_rlr_threshold &&
+ verify_check_host(&smtp_ratelimit_hosts) == OK)
+ {
+ DEBUG(D_receive) debug_printf("rate limit RCPT: delay %.3g sec\n",
+ smtp_delay_rcpt/1000.0);
+ millisleep((int)smtp_delay_rcpt);
+ smtp_delay_rcpt *= smtp_rlr_factor;
+ if (smtp_delay_rcpt > (double)smtp_rlr_limit)
+ smtp_delay_rcpt = (double)smtp_rlr_limit;
+ }
- /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
- for them. Otherwise, check the access control list for this recipient. As
- there may be a delay in this, re-check for a synchronization error
- afterwards, unless pipelining was advertised. */
+ /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
+ for them. Otherwise, check the access control list for this recipient. As
+ there may be a delay in this, re-check for a synchronization error
+ afterwards, unless pipelining was advertised. */
- if (recipients_discarded)
- rc = DISCARD;
- else
- if ( (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
- &log_msg)) == OK
- && !pipelining_advertised && !check_sync())
- goto SYNC_FAILURE;
+ if (f.recipients_discarded)
+ rc = DISCARD;
+ else
+ if ( (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
+ &log_msg)) == OK
+ && !f.smtp_in_pipelining_advertised && !check_sync())
+ goto SYNC_FAILURE;
- /* The ACL was happy */
+ /* The ACL was happy */
- if (rc == OK)
- {
- if (user_msg)
- smtp_user_msg(US"250", user_msg);
- else
- smtp_printf("250 Accepted\r\n");
- receive_add_recipient(recipient, -1);
+ if (rc == OK)
+ {
+ BOOL more = pipeline_response();
+
+ if (user_msg)
+ smtp_user_msg(US"250", user_msg);
+ else
+ smtp_printf("250 Accepted\r\n", more);
+ receive_add_recipient(recipient, -1);
- /* Set the dsn flags in the recipients_list */
- recipients_list[recipients_count-1].orcpt = orcpt;
- recipients_list[recipients_count-1].dsn_flags = flags;
+ /* Set the dsn flags in the recipients_list */
+ recipients_list[recipients_count-1].orcpt = orcpt;
+ recipients_list[recipients_count-1].dsn_flags = dsn_flags;
- DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n",
- recipients_list[recipients_count-1].orcpt,
- recipients_list[recipients_count-1].dsn_flags);
- }
+ DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n",
+ recipients_list[recipients_count-1].orcpt,
+ recipients_list[recipients_count-1].dsn_flags);
+ }
+
+ /* The recipient was discarded */
+
+ else if (rc == DISCARD)
+ {
+ if (user_msg)
+ smtp_user_msg(US"250", user_msg);
+ else
+ smtp_printf("250 Accepted\r\n", FALSE);
+ rcpt_fail_count++;
+ discarded = TRUE;
+ log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
+ "discarded by %s ACL%s%s", host_and_ident(TRUE),
+ sender_address_unrewritten? sender_address_unrewritten : sender_address,
+ smtp_cmd_argument, f.recipients_discarded? "MAIL" : "RCPT",
+ log_msg ? US": " : US"", log_msg ? log_msg : US"");
+ }
- /* The recipient was discarded */
+ /* Either the ACL failed the address, or it was deferred. */
- else if (rc == DISCARD)
- {
- if (user_msg)
- smtp_user_msg(US"250", user_msg);
else
- smtp_printf("250 Accepted\r\n");
- rcpt_fail_count++;
- discarded = TRUE;
- log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
- "discarded by %s ACL%s%s", host_and_ident(TRUE),
- sender_address_unrewritten? sender_address_unrewritten : sender_address,
- smtp_cmd_argument, recipients_discarded? "MAIL" : "RCPT",
- log_msg ? US": " : US"", log_msg ? log_msg : US"");
- }
-
- /* Either the ACL failed the address, or it was deferred. */
-
- else
- {
- if (rc == FAIL) rcpt_fail_count++; else rcpt_defer_count++;
- done = smtp_handle_acl_fail(ACL_WHERE_RCPT, rc, user_msg, log_msg);
- }
- break;
+ {
+ if (rc == FAIL) rcpt_fail_count++; else rcpt_defer_count++;
+ done = smtp_handle_acl_fail(ACL_WHERE_RCPT, rc, user_msg, log_msg);
+ }
+ break;
/* The DATA command is legal only if it follows successful MAIL FROM
with the DATA rejection (an idea suggested by Tony Finch). */
case BDAT_CMD:
- HAD(SCH_BDAT);
{
int n;
+ HAD(SCH_BDAT);
if (chunking_state != CHUNKING_OFFERED)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
(int)chunking_state, chunking_data_left);
+ /* push the current receive_* function on the "stack", and
+ replace them by bdat_getc(), which in turn will use the lwr_receive_*
+ functions to do the dirty work. */
lwr_receive_getc = receive_getc;
+ lwr_receive_getbuf = receive_getbuf;
lwr_receive_ungetc = receive_ungetc;
+
receive_getc = bdat_getc;
receive_ungetc = bdat_ungetc;
+ f.dot_ends = FALSE;
+
goto DATA_BDAT;
}
case DATA_CMD:
- HAD(SCH_DATA);
+ HAD(SCH_DATA);
+ f.dot_ends = TRUE;
DATA_BDAT: /* Common code for DATA and BDAT */
- if (!discarded && recipients_count <= 0)
- {
- if (rcpt_smtp_response_same && rcpt_smtp_response != NULL)
- {
- uschar *code = US"503";
- int len = Ustrlen(rcpt_smtp_response);
- smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
- "this error:");
- /* Responses from smtp_printf() will have \r\n on the end */
- if (len > 2 && rcpt_smtp_response[len-2] == '\r')
- rcpt_smtp_response[len-2] = 0;
- smtp_respond(code, 3, FALSE, rcpt_smtp_response);
- }
- if (pipelining_advertised && last_was_rcpt)
- smtp_printf("503 Valid RCPT command must precede %s\r\n",
- smtp_names[smtp_connection_had[smtp_ch_index-1]]);
- else
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- smtp_connection_had[smtp_ch_index-1] == SCH_DATA
- ? US"valid RCPT command must precede DATA"
- : US"valid RCPT command must precede BDAT");
-
- if (chunking_state > CHUNKING_OFFERED)
- bdat_flush_data();
- break;
- }
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ fl.pipe_connect_acceptable = FALSE;
+#endif
+ if (!discarded && recipients_count <= 0)
+ {
+ if (fl.rcpt_smtp_response_same && rcpt_smtp_response != NULL)
+ {
+ uschar *code = US"503";
+ int len = Ustrlen(rcpt_smtp_response);
+ smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
+ "this error:");
+ /* Responses from smtp_printf() will have \r\n on the end */
+ if (len > 2 && rcpt_smtp_response[len-2] == '\r')
+ rcpt_smtp_response[len-2] = 0;
+ smtp_respond(code, 3, FALSE, rcpt_smtp_response);
+ }
+ if (f.smtp_in_pipelining_advertised && last_was_rcpt)
+ smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
+ smtp_names[smtp_connection_had[smtp_ch_index-1]]);
+ else
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ smtp_connection_had[smtp_ch_index-1] == SCH_DATA
+ ? US"valid RCPT command must precede DATA"
+ : US"valid RCPT command must precede BDAT");
- if (toomany && recipients_max_reject)
- {
- sender_address = NULL; /* This will allow a new MAIL without RSET */
- sender_address_unrewritten = NULL;
- smtp_printf("554 Too many recipients\r\n");
- break;
- }
+ if (chunking_state > CHUNKING_OFFERED)
+ bdat_flush_data();
+ break;
+ }
- if (chunking_state > CHUNKING_OFFERED)
- rc = OK; /* No predata ACL or go-ahead output for BDAT */
- else
- {
- /* If there is an ACL, re-check the synchronization afterwards, since the
- ACL may have delayed. To handle cutthrough delivery enforce a dummy call
- to get the DATA command sent. */
+ if (toomany && recipients_max_reject)
+ {
+ sender_address = NULL; /* This will allow a new MAIL without RSET */
+ sender_address_unrewritten = NULL;
+ smtp_printf("554 Too many recipients\r\n", FALSE);
+ break;
+ }
- if (acl_smtp_predata == NULL && cutthrough.fd < 0)
- rc = OK;
+ if (chunking_state > CHUNKING_OFFERED)
+ rc = OK; /* No predata ACL or go-ahead output for BDAT */
else
{
- uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
- enable_dollar_recipients = TRUE;
- rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
- &log_msg);
- enable_dollar_recipients = FALSE;
- if (rc == OK && !check_sync())
- goto SYNC_FAILURE;
+ /* If there is an ACL, re-check the synchronization afterwards, since the
+ ACL may have delayed. To handle cutthrough delivery enforce a dummy call
+ to get the DATA command sent. */
- if (rc != OK)
- { /* Either the ACL failed the address, or it was deferred. */
- done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
- break;
+ if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0)
+ rc = OK;
+ else
+ {
+ uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
+ f.enable_dollar_recipients = TRUE;
+ rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
+ &log_msg);
+ f.enable_dollar_recipients = FALSE;
+ if (rc == OK && !check_sync())
+ goto SYNC_FAILURE;
+
+ if (rc != OK)
+ { /* Either the ACL failed the address, or it was deferred. */
+ done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
+ break;
+ }
}
- }
- if (user_msg)
- smtp_user_msg(US"354", user_msg);
- else
- smtp_printf(
- "354 Enter message, ending with \".\" on a line by itself\r\n");
- }
+ if (user_msg)
+ smtp_user_msg(US"354", user_msg);
+ else
+ smtp_printf(
+ "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
+ }
#ifdef TCP_QUICKACK
- if (smtp_in) /* all ACKs needed to ramp window up for bulk data */
- (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
- US &on, sizeof(on));
+ if (smtp_in) /* all ACKs needed to ramp window up for bulk data */
+ (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+ US &on, sizeof(on));
#endif
- done = 3;
- message_ended = END_NOTENDED; /* Indicate in middle of data */
+ done = 3;
+ message_ended = END_NOTENDED; /* Indicate in middle of data */
- break;
+ break;
case VRFY_CMD:
if (!(address = parse_extract_address(smtp_cmd_data, &errmess,
&start, &end, &recipient_domain, FALSE)))
{
- smtp_printf("501 %s\r\n", errmess);
+ smtp_printf("501 %s\r\n", FALSE, errmess);
break;
}
break;
}
- smtp_printf("%s\r\n", s);
+ smtp_printf("%s\r\n", FALSE, s);
}
break;
}
case EXPN_CMD:
- HAD(SCH_EXPN);
- rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
- if (rc != OK)
- done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
- else
- {
- BOOL save_log_testing_mode = log_testing_mode;
- address_test_mode = log_testing_mode = TRUE;
- (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE),
- smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
- NULL, NULL, NULL);
- address_test_mode = FALSE;
- log_testing_mode = save_log_testing_mode; /* true for -bh */
- }
- break;
+ HAD(SCH_EXPN);
+ rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
+ if (rc != OK)
+ done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
+ else
+ {
+ BOOL save_log_testing_mode = f.log_testing_mode;
+ f.address_test_mode = f.log_testing_mode = TRUE;
+ (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE),
+ smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
+ NULL, NULL, NULL);
+ f.address_test_mode = FALSE;
+ f.log_testing_mode = save_log_testing_mode; /* true for -bh */
+ }
+ break;
#ifdef SUPPORT_TLS
case STARTTLS_CMD:
- HAD(SCH_STARTTLS);
- if (!tls_advertised)
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"STARTTLS command used when not advertised");
- break;
- }
-
- /* Apply an ACL check if one is defined */
+ HAD(SCH_STARTTLS);
+ if (!fl.tls_advertised)
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"STARTTLS command used when not advertised");
+ break;
+ }
- if ( acl_smtp_starttls
- && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
- &user_msg, &log_msg)) != OK
- )
- {
- done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
- break;
- }
+ /* Apply an ACL check if one is defined */
- /* RFC 2487 is not clear on when this command may be sent, though it
- does state that all information previously obtained from the client
- must be discarded if a TLS session is started. It seems reasonable to
- do an implied RSET when STARTTLS is received. */
-
- incomplete_transaction_log(US"STARTTLS");
- smtp_reset(reset_point);
- toomany = FALSE;
- cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
-
- /* There's an attack where more data is read in past the STARTTLS command
- before TLS is negotiated, then assumed to be part of the secure session
- when used afterwards; we use segregated input buffers, so are not
- vulnerable, but we want to note when it happens and, for sheer paranoia,
- ensure that the buffer is "wiped".
- Pipelining sync checks will normally have protected us too, unless disabled
- by configuration. */
-
- if (receive_smtp_buffered())
- {
- DEBUG(D_any)
- debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
- if (tls_in.active < 0)
- smtp_inend = smtp_inptr = smtp_inbuffer;
- /* and if TLS is already active, tls_server_start() should fail */
- }
+ if ( acl_smtp_starttls
+ && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
+ &user_msg, &log_msg)) != OK
+ )
+ {
+ done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
+ break;
+ }
- /* There is nothing we value in the input buffer and if TLS is successfully
- negotiated, we won't use this buffer again; if TLS fails, we'll just read
- fresh content into it. The buffer contains arbitrary content from an
- untrusted remote source; eg: NOOP <shellcode>\r\nSTARTTLS\r\n
- It seems safest to just wipe away the content rather than leave it as a
- target to jump to. */
+ /* RFC 2487 is not clear on when this command may be sent, though it
+ does state that all information previously obtained from the client
+ must be discarded if a TLS session is started. It seems reasonable to
+ do an implied RSET when STARTTLS is received. */
+
+ incomplete_transaction_log(US"STARTTLS");
+ cancel_cutthrough_connection(TRUE, US"STARTTLS received");
+ smtp_reset(reset_point);
+ toomany = FALSE;
+ cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
+
+ /* There's an attack where more data is read in past the STARTTLS command
+ before TLS is negotiated, then assumed to be part of the secure session
+ when used afterwards; we use segregated input buffers, so are not
+ vulnerable, but we want to note when it happens and, for sheer paranoia,
+ ensure that the buffer is "wiped".
+ Pipelining sync checks will normally have protected us too, unless disabled
+ by configuration. */
+
+ if (receive_smtp_buffered())
+ {
+ DEBUG(D_any)
+ debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
+ if (tls_in.active.sock < 0)
+ smtp_inend = smtp_inptr = smtp_inbuffer;
+ /* and if TLS is already active, tls_server_start() should fail */
+ }
- memset(smtp_inbuffer, 0, IN_BUFFER_SIZE);
+ /* There is nothing we value in the input buffer and if TLS is successfully
+ negotiated, we won't use this buffer again; if TLS fails, we'll just read
+ fresh content into it. The buffer contains arbitrary content from an
+ untrusted remote source; eg: NOOP <shellcode>\r\nSTARTTLS\r\n
+ It seems safest to just wipe away the content rather than leave it as a
+ target to jump to. */
- /* Attempt to start up a TLS session, and if successful, discard all
- knowledge that was obtained previously. At least, that's what the RFC says,
- and that's what happens by default. However, in order to work round YAEB,
- there is an option to remember the esmtp state. Sigh.
+ memset(smtp_inbuffer, 0, IN_BUFFER_SIZE);
- We must allow for an extra EHLO command and an extra AUTH command after
- STARTTLS that don't add to the nonmail command count. */
+ /* Attempt to start up a TLS session, and if successful, discard all
+ knowledge that was obtained previously. At least, that's what the RFC says,
+ and that's what happens by default. However, in order to work round YAEB,
+ there is an option to remember the esmtp state. Sigh.
- if ((rc = tls_server_start(tls_require_ciphers)) == OK)
- {
- if (!tls_remember_esmtp)
- helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
- cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
- cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
- cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
- if (sender_helo_name != NULL)
- {
- store_free(sender_helo_name);
- sender_helo_name = NULL;
- host_build_sender_fullhost(); /* Rebuild */
- set_process_info("handling incoming TLS connection from %s",
- host_and_ident(FALSE));
- }
- received_protocol =
- (sender_host_address ? protocols : protocols_local)
- [ (esmtp
- ? pextend + (sender_host_authenticated ? pauthed : 0)
- : pnormal)
- + (tls_in.active >= 0 ? pcrpted : 0)
- ];
+ We must allow for an extra EHLO command and an extra AUTH command after
+ STARTTLS that don't add to the nonmail command count. */
- sender_host_authenticated = NULL;
- authenticated_id = NULL;
- sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
- DEBUG(D_tls) debug_printf("TLS active\n");
- break; /* Successful STARTTLS */
- }
+ s = NULL;
+ if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK)
+ {
+ if (!tls_remember_esmtp)
+ fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_advertised = FALSE;
+ cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
+ cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
+ cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+ if (sender_helo_name)
+ {
+ store_free(sender_helo_name);
+ sender_helo_name = NULL;
+ host_build_sender_fullhost(); /* Rebuild */
+ set_process_info("handling incoming TLS connection from %s",
+ host_and_ident(FALSE));
+ }
+ received_protocol =
+ (sender_host_address ? protocols : protocols_local)
+ [ (fl.esmtp
+ ? pextend + (sender_host_authenticated ? pauthed : 0)
+ : pnormal)
+ + (tls_in.active.sock >= 0 ? pcrpted : 0)
+ ];
+
+ sender_host_auth_pubname = sender_host_authenticated = NULL;
+ authenticated_id = NULL;
+ sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
+ DEBUG(D_tls) debug_printf("TLS active\n");
+ break; /* Successful STARTTLS */
+ }
+ else
+ (void) smtp_log_tls_fail(s);
- /* Some local configuration problem was discovered before actually trying
- to do a TLS handshake; give a temporary error. */
+ /* Some local configuration problem was discovered before actually trying
+ to do a TLS handshake; give a temporary error. */
- else if (rc == DEFER)
- {
- smtp_printf("454 TLS currently unavailable\r\n");
- break;
- }
+ if (rc == DEFER)
+ {
+ smtp_printf("454 TLS currently unavailable\r\n", FALSE);
+ break;
+ }
- /* Hard failure. Reject everything except QUIT or closed connection. One
- cause for failure is a nested STARTTLS, in which case tls_in.active remains
- set, but we must still reject all incoming commands. */
+ /* Hard failure. Reject everything except QUIT or closed connection. One
+ cause for failure is a nested STARTTLS, in which case tls_in.active remains
+ set, but we must still reject all incoming commands. Another is a handshake
+ failure - and there may some encrypted data still in the pipe to us, which we
+ see as garbage commands. */
- DEBUG(D_tls) debug_printf("TLS failed to start\n");
- while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
- {
- case EOF_CMD:
- log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
- smtp_get_connection_info());
- smtp_notquit_exit(US"tls-failed", NULL, NULL);
- done = 2;
- break;
+ DEBUG(D_tls) debug_printf("TLS failed to start\n");
+ while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
+ {
+ case EOF_CMD:
+ log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
+ smtp_get_connection_info());
+ smtp_notquit_exit(US"tls-failed", NULL, NULL);
+ done = 2;
+ break;
- /* It is perhaps arguable as to which exit ACL should be called here,
- but as it is probably a situation that almost never arises, it
- probably doesn't matter. We choose to call the real QUIT ACL, which in
- some sense is perhaps "right". */
-
- case QUIT_CMD:
- user_msg = NULL;
- if ( acl_smtp_quit
- && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
- &log_msg)) == ERROR))
- log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
- log_msg);
- if (user_msg)
- smtp_respond(US"221", 3, TRUE, user_msg);
- else
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
- log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
- smtp_get_connection_info());
- done = 2;
- break;
+ /* It is perhaps arguable as to which exit ACL should be called here,
+ but as it is probably a situation that almost never arises, it
+ probably doesn't matter. We choose to call the real QUIT ACL, which in
+ some sense is perhaps "right". */
+
+ case QUIT_CMD:
+ user_msg = NULL;
+ if ( acl_smtp_quit
+ && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
+ &log_msg)) == ERROR))
+ log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
+ log_msg);
+ if (user_msg)
+ smtp_respond(US"221", 3, TRUE, user_msg);
+ else
+ smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
+ log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+ smtp_get_connection_info());
+ done = 2;
+ break;
- default:
- smtp_printf("554 Security failure\r\n");
- break;
- }
- tls_close(TRUE, TRUE);
- break;
+ default:
+ smtp_printf("554 Security failure\r\n", FALSE);
+ break;
+ }
+ tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
+ break;
#endif
message. */
case QUIT_CMD:
- smtp_quit_handler(&user_msg, &log_msg);
- done = 2;
- break;
+ smtp_quit_handler(&user_msg, &log_msg);
+ done = 2;
+ break;
case RSET_CMD:
- smtp_rset_handler();
- smtp_reset(reset_point);
- toomany = FALSE;
- break;
+ smtp_rset_handler();
+ cancel_cutthrough_connection(TRUE, US"RSET received");
+ smtp_reset(reset_point);
+ toomany = FALSE;
+ break;
case NOOP_CMD:
- HAD(SCH_NOOP);
- smtp_printf("250 OK\r\n");
- break;
+ HAD(SCH_NOOP);
+ smtp_printf("250 OK\r\n", FALSE);
+ break;
/* Show ETRN/EXPN/VRFY if there's an ACL for checking hosts; if actually
response. */
case HELP_CMD:
- HAD(SCH_HELP);
- smtp_printf("214-Commands supported:\r\n");
- {
- uschar buffer[256];
- buffer[0] = 0;
- Ustrcat(buffer, " AUTH");
- #ifdef SUPPORT_TLS
- if (tls_in.active < 0 &&
- verify_check_host(&tls_advertise_hosts) != FAIL)
- Ustrcat(buffer, " STARTTLS");
- #endif
- Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT");
- Ustrcat(buffer, " NOOP QUIT RSET HELP");
- if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
- if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
- if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
- smtp_printf("214%s\r\n", buffer);
- }
- break;
+ HAD(SCH_HELP);
+ smtp_printf("214-Commands supported:\r\n", TRUE);
+ {
+ uschar buffer[256];
+ buffer[0] = 0;
+ Ustrcat(buffer, " AUTH");
+ #ifdef SUPPORT_TLS
+ if (tls_in.active.sock < 0 &&
+ verify_check_host(&tls_advertise_hosts) != FAIL)
+ Ustrcat(buffer, " STARTTLS");
+ #endif
+ Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT");
+ Ustrcat(buffer, " NOOP QUIT RSET HELP");
+ if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
+ if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
+ if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
+ smtp_printf("214%s\r\n", FALSE, buffer);
+ }
+ break;
case EOF_CMD:
- incomplete_transaction_log(US"connection lost");
- smtp_notquit_exit(US"connection-lost", US"421",
- US"%s lost input connection", smtp_active_hostname);
+ incomplete_transaction_log(US"connection lost");
+ smtp_notquit_exit(US"connection-lost", US"421",
+ US"%s lost input connection", smtp_active_hostname);
+
+ /* Don't log by default unless in the middle of a message, as some mailers
+ just drop the call rather than sending QUIT, and it clutters up the logs.
+ */
+
+ if (sender_address || recipients_count > 0)
+ log_write(L_lost_incoming_connection, LOG_MAIN,
+ "unexpected %s while reading SMTP command from %s%s%s D=%s",
+ f.sender_host_unknown ? "EOF" : "disconnection",
+ f.tcp_in_fastopen_logged
+ ? US""
+ : f.tcp_in_fastopen
+ ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO "
+ : US"",
+ host_and_ident(FALSE), smtp_read_error,
+ string_timesince(&smtp_connection_start)
+ );
- /* Don't log by default unless in the middle of a message, as some mailers
- just drop the call rather than sending QUIT, and it clutters up the logs.
- */
+ else
+ log_write(L_smtp_connection, LOG_MAIN, "%s %slost%s D=%s",
+ smtp_get_connection_info(),
+ f.tcp_in_fastopen && !f.tcp_in_fastopen_logged ? US"TFO " : US"",
+ smtp_read_error,
+ string_timesince(&smtp_connection_start)
+ );
+
+ done = 1;
+ break;
- if (sender_address != NULL || recipients_count > 0)
- log_write(L_lost_incoming_connection,
- LOG_MAIN,
- "unexpected %s while reading SMTP command from %s%s",
- sender_host_unknown? "EOF" : "disconnection",
- host_and_ident(FALSE), smtp_read_error);
- else log_write(L_smtp_connection, LOG_MAIN, "%s lost%s",
- smtp_get_connection_info(), smtp_read_error);
+ case ETRN_CMD:
+ HAD(SCH_ETRN);
+ if (sender_address)
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"ETRN is not permitted inside a transaction");
+ break;
+ }
- done = 1;
- break;
+ log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
+ host_and_ident(FALSE));
+ if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
+ &user_msg, &log_msg)) != OK)
+ {
+ done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg);
+ break;
+ }
- case ETRN_CMD:
- HAD(SCH_ETRN);
- if (sender_address != NULL)
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"ETRN is not permitted inside a transaction");
- break;
- }
+ /* Compute the serialization key for this command. */
- log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
- host_and_ident(FALSE));
+ etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data);
- if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
- &user_msg, &log_msg)) != OK)
- {
- done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg);
- break;
- }
+ /* If a command has been specified for running as a result of ETRN, we
+ permit any argument to ETRN. If not, only the # standard form is permitted,
+ since that is strictly the only kind of ETRN that can be implemented
+ according to the RFC. */
- /* Compute the serialization key for this command. */
+ if (smtp_etrn_command)
+ {
+ uschar *error;
+ BOOL rc;
+ etrn_command = smtp_etrn_command;
+ deliver_domain = smtp_cmd_data;
+ rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
+ US"ETRN processing", &error);
+ deliver_domain = NULL;
+ if (!rc)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
+ error);
+ smtp_printf("458 Internal failure\r\n", FALSE);
+ break;
+ }
+ }
- etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data);
+ /* Else set up to call Exim with the -R option. */
- /* If a command has been specified for running as a result of ETRN, we
- permit any argument to ETRN. If not, only the # standard form is permitted,
- since that is strictly the only kind of ETRN that can be implemented
- according to the RFC. */
+ else
+ {
+ if (*smtp_cmd_data++ != '#')
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"argument must begin with #");
+ break;
+ }
+ etrn_command = US"exim -R";
+ argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE,
+ *queue_name ? 4 : 2,
+ US"-R", smtp_cmd_data,
+ US"-MCG", queue_name);
+ }
- if (smtp_etrn_command != NULL)
- {
- uschar *error;
- BOOL rc;
- etrn_command = smtp_etrn_command;
- deliver_domain = smtp_cmd_data;
- rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
- US"ETRN processing", &error);
- deliver_domain = NULL;
- if (!rc)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
- error);
- smtp_printf("458 Internal failure\r\n");
- break;
- }
- }
+ /* If we are host-testing, don't actually do anything. */
- /* Else set up to call Exim with the -R option. */
+ if (host_checking)
+ {
+ HDEBUG(D_any)
+ {
+ debug_printf("ETRN command is: %s\n", etrn_command);
+ debug_printf("ETRN command execution skipped\n");
+ }
+ if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
+ else smtp_user_msg(US"250", user_msg);
+ break;
+ }
- else
- {
- if (*smtp_cmd_data++ != '#')
- {
- done = synprot_error(L_smtp_syntax_error, 501, NULL,
- US"argument must begin with #");
- break;
- }
- etrn_command = US"exim -R";
- argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE,
- *queue_name ? 4 : 2,
- US"-R", smtp_cmd_data,
- US"-MCG", queue_name);
- }
- /* If we are host-testing, don't actually do anything. */
+ /* If ETRN queue runs are to be serialized, check the database to
+ ensure one isn't already running. */
- if (host_checking)
- {
- HDEBUG(D_any)
- {
- debug_printf("ETRN command is: %s\n", etrn_command);
- debug_printf("ETRN command execution skipped\n");
- }
- if (user_msg == NULL) smtp_printf("250 OK\r\n");
- else smtp_user_msg(US"250", user_msg);
- break;
- }
+ if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
+ {
+ smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data);
+ break;
+ }
+ /* Fork a child process and run the command. We don't want to have to
+ wait for the process at any point, so set SIGCHLD to SIG_IGN before
+ forking. It should be set that way anyway for external incoming SMTP,
+ but we save and restore to be tidy. If serialization is required, we
+ actually run the command in yet another process, so we can wait for it
+ to complete and then remove the serialization lock. */
- /* If ETRN queue runs are to be serialized, check the database to
- ensure one isn't already running. */
+ oldsignal = signal(SIGCHLD, SIG_IGN);
- if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
- {
- smtp_printf("458 Already processing %s\r\n", smtp_cmd_data);
- break;
- }
+ if ((pid = fork()) == 0)
+ {
+ smtp_input = FALSE; /* This process is not associated with the */
+ (void)fclose(smtp_in); /* SMTP call any more. */
+ (void)fclose(smtp_out);
- /* Fork a child process and run the command. We don't want to have to
- wait for the process at any point, so set SIGCHLD to SIG_IGN before
- forking. It should be set that way anyway for external incoming SMTP,
- but we save and restore to be tidy. If serialization is required, we
- actually run the command in yet another process, so we can wait for it
- to complete and then remove the serialization lock. */
+ signal(SIGCHLD, SIG_DFL); /* Want to catch child */
- oldsignal = signal(SIGCHLD, SIG_IGN);
+ /* If not serializing, do the exec right away. Otherwise, fork down
+ into another process. */
- if ((pid = fork()) == 0)
- {
- smtp_input = FALSE; /* This process is not associated with the */
- (void)fclose(smtp_in); /* SMTP call any more. */
- (void)fclose(smtp_out);
+ if (!smtp_etrn_serialize || (pid = fork()) == 0)
+ {
+ DEBUG(D_exec) debug_print_argv(argv);
+ exim_nullstd(); /* Ensure std{in,out,err} exist */
+ execv(CS argv[0], (char *const *)argv);
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s",
+ etrn_command, strerror(errno));
+ _exit(EXIT_FAILURE); /* paranoia */
+ }
- signal(SIGCHLD, SIG_DFL); /* Want to catch child */
+ /* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That
+ is, we are in the first subprocess, after forking again. All we can do
+ for a failing fork is to log it. Otherwise, wait for the 2nd process to
+ complete, before removing the serialization. */
- /* If not serializing, do the exec right away. Otherwise, fork down
- into another process. */
+ if (pid < 0)
+ log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
+ "failed: %s", strerror(errno));
+ else
+ {
+ int status;
+ DEBUG(D_any) debug_printf("waiting for serialized ETRN process %d\n",
+ (int)pid);
+ (void)wait(&status);
+ DEBUG(D_any) debug_printf("serialized ETRN process %d ended\n",
+ (int)pid);
+ }
- if (!smtp_etrn_serialize || (pid = fork()) == 0)
- {
- DEBUG(D_exec) debug_print_argv(argv);
- exim_nullstd(); /* Ensure std{in,out,err} exist */
- execv(CS argv[0], (char *const *)argv);
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s",
- etrn_command, strerror(errno));
- _exit(EXIT_FAILURE); /* paranoia */
- }
+ enq_end(etrn_serialize_key);
+ _exit(EXIT_SUCCESS);
+ }
- /* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That
- is, we are in the first subprocess, after forking again. All we can do
- for a failing fork is to log it. Otherwise, wait for the 2nd process to
- complete, before removing the serialization. */
+ /* Back in the top level SMTP process. Check that we started a subprocess
+ and restore the signal state. */
if (pid < 0)
- log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
- "failed: %s", strerror(errno));
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
+ strerror(errno));
+ smtp_printf("458 Unable to fork process\r\n", FALSE);
+ if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
+ }
else
- {
- int status;
- DEBUG(D_any) debug_printf("waiting for serialized ETRN process %d\n",
- (int)pid);
- (void)wait(&status);
- DEBUG(D_any) debug_printf("serialized ETRN process %d ended\n",
- (int)pid);
- }
-
- enq_end(etrn_serialize_key);
- _exit(EXIT_SUCCESS);
- }
-
- /* Back in the top level SMTP process. Check that we started a subprocess
- and restore the signal state. */
-
- if (pid < 0)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
- strerror(errno));
- smtp_printf("458 Unable to fork process\r\n");
- if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
- }
- else
- {
- if (user_msg == NULL) smtp_printf("250 OK\r\n");
- else smtp_user_msg(US"250", user_msg);
- }
+ {
+ if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
+ else smtp_user_msg(US"250", user_msg);
+ }
- signal(SIGCHLD, oldsignal);
- break;
+ signal(SIGCHLD, oldsignal);
+ break;
case BADARG_CMD:
- done = synprot_error(L_smtp_syntax_error, 501, NULL,
- US"unexpected argument data");
- break;
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"unexpected argument data");
+ break;
/* This currently happens only for NULLs, but could be extended. */
case BADCHAR_CMD:
- done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */
- US"NULL character(s) present (shown as '?')");
- smtp_printf("501 NULL characters are not allowed in SMTP commands\r\n");
- break;
+ done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */
+ US"NUL character(s) present (shown as '?')");
+ smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n",
+ FALSE);
+ break;
case BADSYN_CMD:
SYNC_FAILURE:
- if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE)
- smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1;
- c = smtp_inend - smtp_inptr;
- if (c > 150) c = 150;
- smtp_inptr[c] = 0;
- incomplete_transaction_log(US"sync failure");
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
- "(next input sent too soon: pipelining was%s advertised): "
- "rejected \"%s\" %s next input=\"%s\"",
- pipelining_advertised? "" : " not",
- smtp_cmd_buffer, host_and_ident(TRUE),
- string_printing(smtp_inptr));
- smtp_notquit_exit(US"synchronization-error", US"554",
- US"SMTP synchronization error");
- done = 1; /* Pretend eof - drops connection */
- break;
+ if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE)
+ smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1;
+ c = smtp_inend - smtp_inptr;
+ if (c > 150) c = 150; /* limit logged amount */
+ smtp_inptr[c] = 0;
+ incomplete_transaction_log(US"sync failure");
+ log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
+ "(next input sent too soon: pipelining was%s advertised): "
+ "rejected \"%s\" %s next input=\"%s\"",
+ f.smtp_in_pipelining_advertised ? "" : " not",
+ smtp_cmd_buffer, host_and_ident(TRUE),
+ string_printing(smtp_inptr));
+ smtp_notquit_exit(US"synchronization-error", US"554",
+ US"SMTP synchronization error");
+ done = 1; /* Pretend eof - drops connection */
+ break;
case TOO_MANY_NONMAIL_CMD:
- s = smtp_cmd_buffer;
- while (*s != 0 && !isspace(*s)) s++;
- incomplete_transaction_log(US"too many non-mail commands");
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
- "nonmail commands (last was \"%.*s\")", host_and_ident(FALSE),
- (int)(s - smtp_cmd_buffer), smtp_cmd_buffer);
- smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands");
- done = 1; /* Pretend eof - drops connection */
- break;
+ s = smtp_cmd_buffer;
+ while (*s != 0 && !isspace(*s)) s++;
+ incomplete_transaction_log(US"too many non-mail commands");
+ log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+ "nonmail commands (last was \"%.*s\")", host_and_ident(FALSE),
+ (int)(s - smtp_cmd_buffer), smtp_cmd_buffer);
+ smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands");
+ done = 1; /* Pretend eof - drops connection */
+ break;
#ifdef SUPPORT_PROXY
case PROXY_FAIL_IGNORE_CMD:
- smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
- break;
+ smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE);
+ break;
#endif
default:
- if (unknown_command_count++ >= smtp_max_unknown_commands)
- {
- log_write(L_smtp_syntax_error, LOG_MAIN,
- "SMTP syntax error in \"%s\" %s %s",
- string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
- US"unrecognized command");
- incomplete_transaction_log(US"unrecognized command");
- smtp_notquit_exit(US"bad-commands", US"500",
- US"Too many unrecognized commands");
- done = 2;
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
- "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),
- string_printing(smtp_cmd_buffer));
- }
- else
- done = synprot_error(L_smtp_syntax_error, 500, NULL,
- US"unrecognized command");
- break;
+ if (unknown_command_count++ >= smtp_max_unknown_commands)
+ {
+ log_write(L_smtp_syntax_error, LOG_MAIN,
+ "SMTP syntax error in \"%s\" %s %s",
+ string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
+ US"unrecognized command");
+ incomplete_transaction_log(US"unrecognized command");
+ smtp_notquit_exit(US"bad-commands", US"500",
+ US"Too many unrecognized commands");
+ done = 2;
+ log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+ "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),
+ string_printing(smtp_cmd_buffer));
+ }
+ else
+ done = synprot_error(L_smtp_syntax_error, 500, NULL,
+ US"unrecognized command");
+ break;
}
/* This label is used by goto's inside loops that want to break out to
return done - 2; /* Convert yield values */
}
+
+
+gstring *
+authres_smtpauth(gstring * g)
+{
+if (!sender_host_authenticated)
+ return g;
+
+g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname);
+
+if (Ustrcmp(sender_host_auth_pubname, "tls") != 0)
+ g = string_append(g, 2, US") smtp.auth=", authenticated_id);
+else if (authenticated_id)
+ g = string_append(g, 2, US") x509.auth=", authenticated_id);
+else
+ g = string_catn(g, US") reason=x509.auth", 17);
+
+if (authenticated_sender)
+ g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);
+return g;
+}
+
+
+
/* vi: aw ai sw=2
*/
/* End of smtp_in.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* A number of functions for driving outgoing SMTP calls. */
if (!(expint = expand_string(istring)))
{
- if (expand_string_forcedfail) return TRUE;
+ if (f.expand_string_forcedfail) return TRUE;
addr->transport_return = PANIC;
addr->message = string_sprintf("failed to expand \"interface\" "
"option for %s: %s", msg, expand_string_message);
{
uschar *pstring = expand_string(rstring);
-if (pstring == NULL)
+if (!pstring)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("failed to expand \"%s\" (\"port\" option) "
else
{
struct servent *smtp_service = getservbyname(CS pstring, "tcp");
- if (smtp_service == NULL)
+ if (!smtp_service)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("TCP port \"%s\" is not defined for %s",
+#ifdef TCP_FASTOPEN
+static void
+tfo_out_check(int sock)
+{
+# if defined(TCP_INFO) && defined(EXIM_HAVE_TCPI_UNACKED)
+struct tcp_info tinfo;
+socklen_t len = sizeof(tinfo);
+
+switch (tcp_out_fastopen)
+ {
+ /* This is a somewhat dubious detection method; totally undocumented so likely
+ to fail in future kernels. There seems to be no documented way. What we really
+ want to know is if the server sent smtp-banner data before our ACK of his SYN,ACK
+ hit him. What this (possibly?) detects is whether we sent a TFO cookie with our
+ SYN, as distinct from a TFO request. This gets a false-positive when the server
+ key is rotated; we send the old one (which this test sees) but the server returns
+ the new one and does not send its SMTP banner before we ACK his SYN,ACK.
+ To force that rotation case:
+ '# echo -n "00000000-00000000-00000000-0000000" >/proc/sys/net/ipv4/tcp_fastopen_key'
+ The kernel seems to be counting unack'd packets. */
+
+ case TFO_ATTEMPTED_NODATA:
+ if ( getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+ && tinfo.tcpi_state == TCP_SYN_SENT
+ && tinfo.tcpi_unacked > 1
+ )
+ {
+ DEBUG(D_transport|D_v)
+ debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.tcpi_unacked);
+ tcp_out_fastopen = TFO_USED_NODATA;
+ }
+ break;
+
+ /* When called after waiting for received data we should be able
+ to tell if data we sent was accepted. */
+
+ case TFO_ATTEMPTED_DATA:
+ if ( getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+ && tinfo.tcpi_state == TCP_ESTABLISHED
+ )
+ if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
+ {
+ DEBUG(D_transport|D_v) debug_printf("TFO: data was acked\n");
+ tcp_out_fastopen = TFO_USED_DATA;
+ }
+ else
+ {
+ DEBUG(D_transport|D_v) debug_printf("TFO: had to retransmit\n");
+ tcp_out_fastopen = TFO_NOT_USED;
+ }
+ break;
+ }
+# endif
+}
+#endif
+
+
+/* Arguments as for smtp_connect(), plus
+ early_data if non-NULL, idenmpotent data to be sent -
+ preferably in the TCP SYN segment
+
+Returns: connected socket number, or -1 with errno set
+*/
+
int
smtp_sock_connect(host_item * host, int host_af, int port, uschar * interface,
- transport_instance * tb, int timeout)
+ transport_instance * tb, int timeout, const blob * early_data)
{
smtp_transport_options_block * ob =
(smtp_transport_options_block *)tb->options_block;
int dscp_option;
int sock;
int save_errno = 0;
-BOOL fastopen = FALSE;
+const blob * fastopen_blob = NULL;
+
#ifndef DISABLE_EVENT
deliver_host_address = host->address;
(void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value));
}
-#ifdef TCP_FASTOPEN
-if (verify_check_given_host (&ob->hosts_try_fastopen, host) == OK) fastopen = TRUE;
-#endif
-
/* Bind to a specific interface if requested. Caller must ensure the interface
is the same type (IPv4 or IPv6) as the outgoing address. */
}
/* Connect to the remote host, and add keepalive to the socket before returning
-it, if requested. */
+it, if requested. If the build supports TFO, request it - and if the caller
+requested some early-data then include that in the TFO request. If there is
+early-data but no TFO support, send it after connecting. */
-else if (ip_connect(sock, host_af, host->address, port, timeout, fastopen) < 0)
- save_errno = errno;
+else
+ {
+#ifdef TCP_FASTOPEN
+ if (verify_check_given_host(CUSS &ob->hosts_try_fastopen, host) == OK)
+ fastopen_blob = early_data ? early_data : &tcp_fastopen_nodata;
+#endif
+
+ if (ip_connect(sock, host_af, host->address, port, timeout, fastopen_blob) < 0)
+ save_errno = errno;
+ else if (early_data && !fastopen_blob && early_data->data && early_data->len)
+ {
+ HDEBUG(D_transport|D_acl|D_v)
+ debug_printf("sending %ld nonTFO early-data\n", (long)early_data->len);
+
+#ifdef TCP_QUICKACK
+ (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
+ if (send(sock, early_data->data, early_data->len, 0) < 0)
+ save_errno = errno;
+ }
+ }
/* Either bind() or connect() failed */
return -1;
}
-/* Both bind() and connect() succeeded */
+/* Both bind() and connect() succeeded, and any early-data */
else
{
union sockaddr_46 interface_sock;
EXIM_SOCKLEN_T size = sizeof(interface_sock);
+
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("connected\n");
if (getsockname(sock, (struct sockaddr *)(&interface_sock), &size) == 0)
sending_ip_address = host_ntoa(-1, &interface_sock, NULL, &sending_port);
close(sock);
return -1;
}
+
if (ob->keepalive) ip_keepalive(sock, host->address, TRUE);
+#ifdef TCP_FASTOPEN
+ tfo_out_check(sock);
+#endif
return sock;
}
}
+
+
+
+
+void
+smtp_port_for_connect(host_item * host, int port)
+{
+if (host->port != PORT_NONE)
+ {
+ HDEBUG(D_transport|D_acl|D_v)
+ debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port,
+ host->port);
+ port = host->port;
+ }
+else host->port = port; /* Set the port actually used */
+}
+
+
/*************************************************
* Connect to remote host *
*************************************************/
non-IPv6 systems, to enable the code to be less messy. However, on such systems
host->address will always be an IPv4 address.
-The port field in the host item is used if it is set (usually router from SRV
-records or elsewhere). In other cases, the default passed as an argument is
-used, and the host item is updated with its value.
-
Arguments:
- host host item containing name and address (and sometimes port)
- host_af AF_INET or AF_INET6
- port default remote port to connect to, in host byte order, for those
- hosts whose port setting is PORT_NONE
- interface outgoing interface address or NULL
- timeout timeout value or 0
- tb transport
+ sc details for making connection: host, af, interface, transport
+ early_data if non-NULL, data to be sent - preferably in the TCP SYN segment
Returns: connected socket number, or -1 with errno set
*/
int
-smtp_connect(host_item *host, int host_af, int port, uschar *interface,
- int timeout, transport_instance * tb)
+smtp_connect(smtp_connect_args * sc, const blob * early_data)
{
-#ifdef SUPPORT_SOCKS
-smtp_transport_options_block * ob =
- (smtp_transport_options_block *)tb->options_block;
-#endif
+int port = sc->host->port;
+smtp_transport_options_block * ob = sc->ob;
-if (host->port != PORT_NONE)
- {
- HDEBUG(D_transport|D_acl|D_v)
- debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port,
- host->port);
- port = host->port;
- }
-else host->port = port; /* Set the port actually used */
-
-callout_address = string_sprintf("[%s]:%d", host->address, port);
+callout_address = string_sprintf("[%s]:%d", sc->host->address, port);
HDEBUG(D_transport|D_acl|D_v)
{
uschar * s = US" ";
- if (interface) s = string_sprintf(" from %s ", interface);
+ if (sc->interface) s = string_sprintf(" from %s ", sc->interface);
#ifdef SUPPORT_SOCKS
if (ob->socks_proxy) s = string_sprintf("%svia proxy ", s);
#endif
- debug_printf_indent("Connecting to %s %s%s... ", host->name, callout_address, s);
+ debug_printf_indent("Connecting to %s %s%s... ", sc->host->name, callout_address, s);
}
/* Create and connect the socket */
#ifdef SUPPORT_SOCKS
if (ob->socks_proxy)
- return socks_sock_connect(host, host_af, port, interface, tb, timeout);
+ {
+ int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface,
+ sc->tblock, ob->connect_timeout);
+
+ if (sock >= 0)
+ {
+ if (early_data && early_data->data && early_data->len)
+ if (send(sock, early_data->data, early_data->len, 0) < 0)
+ {
+ int save_errno = errno;
+ HDEBUG(D_transport|D_acl|D_v)
+ {
+ debug_printf_indent("failed: %s", CUstrerror(save_errno));
+ if (save_errno == ETIMEDOUT)
+ debug_printf(" (timeout=%s)", readconf_printtime(ob->connect_timeout));
+ debug_printf("\n");
+ }
+ (void)close(sock);
+ sock = -1;
+ errno = save_errno;
+ }
+ }
+ return sock;
+ }
#endif
-return smtp_sock_connect(host, host_af, port, interface, tb, timeout);
+return smtp_sock_connect(sc->host, sc->host_af, port, sc->interface,
+ sc->tblock, ob->connect_timeout, early_data);
}
Argument:
outblock the SMTP output block
+ mode further data expected, or plain
Returns: TRUE if OK, FALSE on error, with errno set
*/
static BOOL
-flush_buffer(smtp_outblock *outblock)
+flush_buffer(smtp_outblock * outblock, int mode)
{
int rc;
int n = outblock->ptr - outblock->buffer;
+BOOL more = mode == SCMD_MORE;
+
+HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
+ more ? " (more expected)" : "");
-HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes\n", n);
#ifdef SUPPORT_TLS
-if (tls_out.active == outblock->sock)
- rc = tls_write(FALSE, outblock->buffer, n);
+if (outblock->cctx->tls_ctx)
+ rc = tls_write(outblock->cctx->tls_ctx, outblock->buffer, n, more);
else
#endif
- rc = send(outblock->sock, outblock->buffer, n, 0);
+
+ {
+ if (outblock->conn_args)
+ {
+ blob early_data = { .data = outblock->buffer, .len = n };
+
+ /* We ignore the more-flag if we're doing a connect with early-data, which
+ means we won't get BDAT+data. A pity, but wise due to the idempotency
+ requirement: TFO with data can, in rare cases, replay the data to the
+ receiver. */
+
+ if ( (outblock->cctx->sock = smtp_connect(outblock->conn_args, &early_data))
+ < 0)
+ return FALSE;
+ outblock->conn_args = NULL;
+ rc = n;
+ }
+ else
+
+ rc = send(outblock->cctx->sock, outblock->buffer, n,
+#ifdef MSG_MORE
+ more ? MSG_MORE : 0
+#else
+ 0
+#endif
+ );
+ }
if (rc <= 0)
{
any error message.
Arguments:
- outblock contains buffer for pipelining, and socket
- noflush if TRUE, save the command in the output buffer, for pipelining
+ sx SMTP connection, contains buffer for pipelining, and socket
+ mode buffer, write-with-more-likely, write
format a format, starting with one of
of HELO, MAIL FROM, RCPT TO, DATA, ".", or QUIT.
If NULL, flush pipeline buffer only.
*/
int
-smtp_write_command(smtp_outblock *outblock, BOOL noflush, const char *format, ...)
+smtp_write_command(void * sx, int mode, const char *format, ...)
{
-int count;
+smtp_outblock * outblock = &((smtp_context *)sx)->outblock;
int rc = 0;
-va_list ap;
if (format)
{
+ gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
+ va_list ap;
+
va_start(ap, format);
- if (!string_vformat(big_buffer, big_buffer_size, CS format, ap))
+ if (!string_vformat(&gs, FALSE, CS format, ap))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
"SMTP");
va_end(ap);
- count = Ustrlen(big_buffer);
+ string_from_gstring(&gs);
- if (count > outblock->buffersize)
+ if (gs.ptr > outblock->buffersize)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing "
"SMTP");
- if (count > outblock->buffersize - (outblock->ptr - outblock->buffer))
+ if (gs.ptr > outblock->buffersize - (outblock->ptr - outblock->buffer))
{
rc = outblock->cmd_count; /* flush resets */
- if (!flush_buffer(outblock)) return -1;
+ if (!flush_buffer(outblock, SCMD_FLUSH)) return -1;
}
- Ustrncpy(CS outblock->ptr, big_buffer, count);
- outblock->ptr += count;
+ Ustrncpy(CS outblock->ptr, gs.s, gs.ptr);
+ outblock->ptr += gs.ptr;
outblock->cmd_count++;
- count -= 2;
- big_buffer[count] = 0; /* remove \r\n for error message */
+ gs.ptr -= 2; string_from_gstring(&gs); /* remove \r\n for error message */
/* We want to hide the actual data sent in AUTH transactions from reflections
and logs. While authenticating, a flag is set in the outblock to enable this.
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> %s\n", big_buffer);
}
-if (!noflush)
+if (mode != SCMD_BUFFER)
{
rc += outblock->cmd_count; /* flush resets */
- if (!flush_buffer(outblock)) return -1;
+ if (!flush_buffer(outblock, mode)) return -1;
}
return rc;
uschar *p = buffer;
uschar *ptr = inblock->ptr;
uschar *ptrend = inblock->ptrend;
-int sock = inblock->sock;
+client_conn_ctx * cctx = inblock->cctx;
/* Loop for reading multiple packets or reading another packet after emptying
a previously-read one. */
/* Need to read a new input packet. */
- if((rc = ip_recv(sock, inblock->buffer, inblock->buffersize, timeout)) <= 0)
+ if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timeout)) <= 0)
{
- if (!errno)
- DEBUG(D_deliver|D_transport|D_acl) debug_printf_indent(" SMTP(closed)<<\n");
+ DEBUG(D_deliver|D_transport|D_acl)
+ debug_printf_indent(errno ? " SMTP(%s)<<\n" : " SMTP(closed)<<\n",
+ strerror(errno));
break;
}
the error code will be in errno.
Arguments:
- inblock the SMTP input block (contains holding buffer, socket, etc.)
+ sx the SMTP connection (contains input block with holding buffer,
+ socket, etc.)
buffer where to put the response
size the size of the buffer
okdigit the expected first digit of the response
Returns: TRUE if a valid, non-error response was received; else FALSE
*/
+/*XXX could move to smtp transport; no other users */
BOOL
-smtp_read_response(smtp_inblock *inblock, uschar *buffer, int size, int okdigit,
+smtp_read_response(void * sx0, uschar *buffer, int size, int okdigit,
int timeout)
{
+smtp_context * sx = sx0;
uschar *ptr = buffer;
-int count;
+int count = 0;
errno = 0; /* Ensure errno starts out zero */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (sx->pending_BANNER || sx->pending_EHLO)
+ if (smtp_reap_early_pipe(sx, &count) != OK)
+ {
+ DEBUG(D_transport) debug_printf("failed reaping pipelined cmd responsess\n");
+ return FALSE;
+ }
+#endif
+
/* This is a loop to read and concatenate the lines that make up a multi-line
response. */
for (;;)
{
- if ((count = read_response_line(inblock, ptr, size, timeout)) < 0)
+ if ((count = read_response_line(&sx->inblock, ptr, size, timeout)) < 0)
return FALSE;
HDEBUG(D_transport|D_acl|D_v)
- debug_printf_indent(" %s %s\n", (ptr == buffer)? "SMTP<<" : " ", ptr);
+ debug_printf_indent(" %s %s\n", ptr == buffer ? "SMTP<<" : " ", ptr);
/* Check the format of the response: it must start with three digits; if
these are followed by a space or end of line, the response is complete. If
size -= count + 1;
}
+#ifdef TCP_FASTOPEN
+ tfo_out_check(sx->cctx.sock);
+#endif
+
/* Return a value that depends on the SMTP return code. On some systems a
non-zero value of errno has been seen at this point, so ensure it is zero,
because the caller of this function looks at errno when FALSE is returned, to
/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
* License: GPL
- * Copyright (c) The Exim Maintainers 2016
+ * Copyright (c) The Exim Maintainers 2016 - 2018
*/
/* Code for calling spamassassin's spamd. Called from acl.c. */
uschar user_name_buffer[128];
unsigned long mbox_size;
FILE *mbox_file;
-int spamd_sock = -1;
+client_conn_ctx spamd_cctx = {.sock = -1};
uschar spamd_buffer[32600];
int i, j, offset, result;
uschar spamd_version[8];
return override ? OK : spam_rc;
/* make sure the eml mbox file is spooled up */
-mbox_file = spool_mbox(&mbox_size, NULL);
-if (mbox_file == NULL)
- {
- /* error while spooling */
+if (!(mbox_file = spool_mbox(&mbox_size, NULL, NULL)))
+ { /* error while spooling */
log_write(0, LOG_MAIN|LOG_PANIC,
"%s error while creating mbox spool file", loglabel);
return DEFER;
/* Check how many spamd servers we have
and register their addresses */
sep = 0; /* default colon-sep */
- while ((address = string_nextinlist(&spamd_address_list_ptr, &sep,
- NULL, 0)) != NULL)
+ while ((address = string_nextinlist(&spamd_address_list_ptr, &sep, NULL, 0)))
{
const uschar * sublist;
int sublist_sep = -(int)' '; /* default space-sep */
for (;;)
{
- if ( (spamd_sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0
+ /*XXX could potentially use TFO early-data here */
+ if ( (spamd_cctx.sock = ip_streamsocket(sd->hostspec, &errstr, 5)) >= 0
|| sd->retry <= 0
)
break;
DEBUG(D_acl) debug_printf_indent("spamd: server %s: retry conn\n", sd->hostspec);
while (sd->retry > 0) sd->retry = sleep(sd->retry);
}
- if (spamd_sock >= 0)
+ if (spamd_cctx.sock >= 0)
break;
log_write(0, LOG_MAIN, "%s spamd: %s", loglabel, errstr);
}
}
-(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
-/* now we are connected to spamd on spamd_sock */
+(void)fcntl(spamd_cctx.sock, F_SETFL, O_NONBLOCK);
+/* now we are connected to spamd on spamd_cctx.sock */
if (sd->is_rspamd)
- { /* rspamd variant */
- uschar *req_str;
- const uschar * helo;
- const uschar * fcrdns;
- const uschar * authid;
-
- req_str = string_sprintf("CHECK RSPAMC/1.3\r\nContent-length: %lu\r\n"
- "Queue-Id: %s\r\nFrom: <%s>\r\nRecipient-Number: %d\r\n",
- mbox_size, message_id, sender_address, recipients_count);
+ {
+ gstring * req_str;
+ const uschar * s;
+
+ req_str = string_append(NULL, 8,
+ "CHECK RSPAMC/1.3\r\nContent-length: ", string_sprintf("%lu\r\n", mbox_size),
+ "Queue-Id: ", message_id,
+ "\r\nFrom: <", sender_address,
+ ">\r\nRecipient-Number: ", string_sprintf("%d\r\n", recipients_count));
+
for (i = 0; i < recipients_count; i ++)
- req_str = string_sprintf("%sRcpt: <%s>\r\n", req_str, recipients_list[i].address);
- if ((helo = expand_string(US"$sender_helo_name")) != NULL && *helo != '\0')
- req_str = string_sprintf("%sHelo: %s\r\n", req_str, helo);
- if ((fcrdns = expand_string(US"$sender_host_name")) != NULL && *fcrdns != '\0')
- req_str = string_sprintf("%sHostname: %s\r\n", req_str, fcrdns);
- if (sender_host_address != NULL)
- req_str = string_sprintf("%sIP: %s\r\n", req_str, sender_host_address);
- if ((authid = expand_string(US"$authenticated_id")) != NULL && *authid != '\0')
- req_str = string_sprintf("%sUser: %s\r\n", req_str, authid);
- req_str = string_sprintf("%s\r\n", req_str);
- wrote = send(spamd_sock, req_str, Ustrlen(req_str), 0);
+ req_str = string_append(req_str, 3,
+ "Rcpt: <", recipients_list[i].address, ">\r\n");
+ if ((s = expand_string(US"$sender_helo_name")) && *s)
+ req_str = string_append(req_str, 3, "Helo: ", s, "\r\n");
+ if ((s = expand_string(US"$sender_host_name")) && *s)
+ req_str = string_append(req_str, 3, "Hostname: ", s, "\r\n");
+ if (sender_host_address)
+ req_str = string_append(req_str, 3, "IP: ", sender_host_address, "\r\n");
+ if ((s = expand_string(US"$authenticated_id")) && *s)
+ req_str = string_append(req_str, 3, "User: ", s, "\r\n");
+ req_str = string_catn(req_str, US"\r\n", 2);
+ wrote = send(spamd_cctx.sock, req_str->s, req_str->ptr, 0);
}
else
{ /* spamassassin variant */
user_name,
mbox_size);
/* send our request */
- wrote = send(spamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0);
+ wrote = send(spamd_cctx.sock, spamd_buffer, Ustrlen(spamd_buffer), 0);
}
if (wrote == -1)
{
- (void)close(spamd_sock);
+ (void)close(spamd_cctx.sock);
log_write(0, LOG_MAIN|LOG_PANIC,
"%s spamd %s send failed: %s", loglabel, callout_address, strerror(errno));
goto defer;
* broken in more recent versions (up to 10.4).
*/
#ifndef NO_POLL_H
-pollfd.fd = spamd_sock;
+pollfd.fd = spamd_cctx.sock;
pollfd.events = POLLOUT;
#endif
-(void)fcntl(spamd_sock, F_SETFL, O_NONBLOCK);
+(void)fcntl(spamd_cctx.sock, F_SETFL, O_NONBLOCK);
do
{
read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file);
select_tv.tv_sec = 1;
select_tv.tv_usec = 0;
FD_ZERO(&select_fd);
- FD_SET(spamd_sock, &select_fd);
- result = select(spamd_sock+1, NULL, &select_fd, NULL, &select_tv);
+ FD_SET(spamd_cctx.sock, &select_fd);
+ result = select(spamd_cctx.sock+1, NULL, &select_fd, NULL, &select_tv);
#endif
/* End Erik's patch */
log_write(0, LOG_MAIN|LOG_PANIC,
"%s timed out writing spamd %s, socket", loglabel, callout_address);
}
- (void)close(spamd_sock);
+ (void)close(spamd_cctx.sock);
goto defer;
}
- wrote = send(spamd_sock,spamd_buffer + offset,read - offset,0);
+ wrote = send(spamd_cctx.sock,spamd_buffer + offset,read - offset,0);
if (wrote == -1)
{
log_write(0, LOG_MAIN|LOG_PANIC,
"%s %s on spamd %s socket", loglabel, callout_address, strerror(errno));
- (void)close(spamd_sock);
+ (void)close(spamd_cctx.sock);
goto defer;
}
if (offset + wrote != read)
{
log_write(0, LOG_MAIN|LOG_PANIC,
"%s error reading spool file: %s", loglabel, strerror(errno));
- (void)close(spamd_sock);
+ (void)close(spamd_cctx.sock);
goto defer;
}
/* we're done sending, close socket for writing */
if (!sd->is_rspamd)
- shutdown(spamd_sock,SHUT_WR);
+ shutdown(spamd_cctx.sock,SHUT_WR);
/* read spamd response using what's left of the timeout. */
memset(spamd_buffer, 0, sizeof(spamd_buffer));
offset = 0;
-while ((i = ip_recv(spamd_sock,
+while ((i = ip_recv(&spamd_cctx,
spamd_buffer + offset,
sizeof(spamd_buffer) - offset - 1,
sd->timeout - time(NULL) + start)) > 0)
{
log_write(0, LOG_MAIN|LOG_PANIC,
"%s error reading from spamd %s, socket: %s", loglabel, callout_address, strerror(errno));
- (void)close(spamd_sock);
+ (void)close(spamd_cctx.sock);
return DEFER;
}
/* reading done */
-(void)close(spamd_sock);
+(void)close(spamd_cctx.sock);
if (sd->is_rspamd)
{ /* rspamd variant of reply */
-/*************************************************
-* Exim - an Internet mail transport agent *
-*************************************************/
-
-/* Experimental SPF support.
- Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
- License: GPL
- Copyright (c) The Exim Maintainers 2016
-*/
-
-/* Code for calling spf checks via libspf-alt. Called from acl.c. */
-
-#include "exim.h"
-#ifdef EXPERIMENTAL_SPF
-
-/* must be kept in numeric order */
-static spf_result_id spf_result_id_list[] = {
- { US"invalid", 0},
- { US"neutral", 1 },
- { US"pass", 2 },
- { US"fail", 3 },
- { US"softfail", 4 },
- { US"none", 5 },
- { US"err_temp", 6 }, /* Deprecated Apr 2014 */
- { US"err_perm", 7 }, /* Deprecated Apr 2014 */
- { US"temperror", 6 }, /* RFC 4408 defined */
- { US"permerror", 7 } /* RFC 4408 defined */
-};
-
-SPF_server_t *spf_server = NULL;
-SPF_request_t *spf_request = NULL;
-SPF_response_t *spf_response = NULL;
-SPF_response_t *spf_response_2mx = NULL;
-
-/* spf_init sets up a context that can be re-used for several
- messages on the same SMTP connection (that come from the
- same host with the same HELO string) */
-
-int spf_init(uschar *spf_helo_domain, uschar *spf_remote_addr) {
-
- spf_server = SPF_server_new(SPF_DNS_CACHE, 0);
-
- if ( spf_server == NULL ) {
- debug_printf("spf: SPF_server_new() failed.\n");
- return 0;
- }
-
- if (SPF_server_set_rec_dom(spf_server, CS primary_hostname)) {
- debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n", primary_hostname);
- spf_server = NULL;
- return 0;
- }
-
- spf_request = SPF_request_new(spf_server);
-
- if (SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
- && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)) {
- debug_printf("spf: SPF_request_set_ipv4_str() and SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
- spf_server = NULL;
- spf_request = NULL;
- return 0;
- }
-
- if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain)) {
- debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n", spf_helo_domain);
- spf_server = NULL;
- spf_request = NULL;
- return 0;
- }
-
- return 1;
-}
-
-
-/* spf_process adds the envelope sender address to the existing
- context (if any), retrieves the result, sets up expansion
- strings and evaluates the condition outcome. */
-
-int spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action) {
- int sep = 0;
- const uschar *list = *listptr;
- uschar *spf_result_id;
- uschar spf_result_id_buffer[128];
- int rc = SPF_RESULT_PERMERROR;
-
- if (!(spf_server && spf_request)) {
- /* no global context, assume temp error and skip to evaluation */
- rc = SPF_RESULT_PERMERROR;
- goto SPF_EVALUATE;
- };
-
- if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender)) {
- /* Invalid sender address. This should be a real rare occurence */
- rc = SPF_RESULT_PERMERROR;
- goto SPF_EVALUATE;
- }
-
- /* get SPF result */
- if (action == SPF_PROCESS_FALLBACK)
- SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
- else
- SPF_request_query_mailfrom(spf_request, &spf_response);
-
- /* set up expansion items */
- spf_header_comment = (uschar *)SPF_response_get_header_comment(spf_response);
- spf_received = (uschar *)SPF_response_get_received_spf(spf_response);
- spf_result = (uschar *)SPF_strresult(SPF_response_result(spf_response));
- spf_smtp_comment = (uschar *)SPF_response_get_smtp_comment(spf_response);
-
- rc = SPF_response_result(spf_response);
-
- /* We got a result. Now see if we should return OK or FAIL for it */
- SPF_EVALUATE:
- debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
-
- if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
- return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
-
- while ((spf_result_id = string_nextinlist(&list, &sep,
- spf_result_id_buffer,
- sizeof(spf_result_id_buffer))) != NULL) {
- int negate = 0;
- int result = 0;
-
- /* Check for negation */
- if (spf_result_id[0] == '!') {
- negate = 1;
- spf_result_id++;
- };
-
- /* Check the result identifier */
- result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name);
- if (!negate && result==0) return OK;
- if (negate && result!=0) return OK;
- };
-
- /* no match */
- return FAIL;
-}
-
-#endif
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Experimental SPF support.
+ Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
+ License: GPL
+ Copyright (c) The Exim Maintainers 2015 - 2018
+*/
+
+/* Code for calling spf checks via libspf-alt. Called from acl.c. */
+
+#include "exim.h"
+#ifdef SUPPORT_SPF
+
+/* must be kept in numeric order */
+static spf_result_id spf_result_id_list[] = {
+ /* name value */
+ { US"invalid", 0},
+ { US"neutral", 1 },
+ { US"pass", 2 },
+ { US"fail", 3 },
+ { US"softfail", 4 },
+ { US"none", 5 },
+ { US"temperror", 6 }, /* RFC 4408 defined */
+ { US"permerror", 7 } /* RFC 4408 defined */
+};
+
+SPF_server_t *spf_server = NULL;
+SPF_request_t *spf_request = NULL;
+SPF_response_t *spf_response = NULL;
+SPF_response_t *spf_response_2mx = NULL;
+
+
+/* spf_init sets up a context that can be re-used for several
+ messages on the same SMTP connection (that come from the
+ same host with the same HELO string)
+
+Return: Boolean success */
+
+BOOL
+spf_init(uschar *spf_helo_domain, uschar *spf_remote_addr)
+{
+spf_server = SPF_server_new(SPF_DNS_CACHE, 0);
+
+if (!spf_server)
+ {
+ DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n");
+ return FALSE;
+ }
+
+if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
+ {
+ DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n",
+ primary_hostname);
+ spf_server = NULL;
+ return FALSE;
+ }
+
+spf_request = SPF_request_new(spf_server);
+
+if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr)
+ && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr)
+ )
+ {
+ DEBUG(D_receive)
+ debug_printf("spf: SPF_request_set_ipv4_str() and "
+ "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr);
+ spf_server = NULL;
+ spf_request = NULL;
+ return FALSE;
+ }
+
+if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
+ {
+ DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n",
+ spf_helo_domain);
+ spf_server = NULL;
+ spf_request = NULL;
+ return FALSE;
+ }
+
+return TRUE;
+}
+
+
+/* spf_process adds the envelope sender address to the existing
+ context (if any), retrieves the result, sets up expansion
+ strings and evaluates the condition outcome.
+
+Return: OK/FAIL */
+
+int
+spf_process(const uschar **listptr, uschar *spf_envelope_sender, int action)
+{
+int sep = 0;
+const uschar *list = *listptr;
+uschar *spf_result_id;
+int rc = SPF_RESULT_PERMERROR;
+
+if (!(spf_server && spf_request))
+ /* no global context, assume temp error and skip to evaluation */
+ rc = SPF_RESULT_PERMERROR;
+
+else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender))
+ /* Invalid sender address. This should be a real rare occurrence */
+ rc = SPF_RESULT_PERMERROR;
+
+else
+ {
+ /* get SPF result */
+ if (action == SPF_PROCESS_FALLBACK)
+ {
+ SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess);
+ spf_result_guessed = TRUE;
+ }
+ else
+ SPF_request_query_mailfrom(spf_request, &spf_response);
+
+ /* set up expansion items */
+ spf_header_comment = US SPF_response_get_header_comment(spf_response);
+ spf_received = US SPF_response_get_received_spf(spf_response);
+ spf_result = US SPF_strresult(SPF_response_result(spf_response));
+ spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response);
+
+ rc = SPF_response_result(spf_response);
+ }
+
+/* We got a result. Now see if we should return OK or FAIL for it */
+DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc);
+
+if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none")))
+ return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK);
+
+while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0)))
+ {
+ BOOL negate, result;
+
+ if ((negate = spf_result_id[0] == '!'))
+ spf_result_id++;
+
+ result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0;
+ if (negate != result) return OK;
+ }
+
+/* no match */
+return FAIL;
+}
+
+
+
+gstring *
+authres_spf(gstring * g)
+{
+uschar * s;
+if (!spf_result) return g;
+
+g = string_append(g, 2, US";\n\tspf=", spf_result);
+if (spf_result_guessed)
+ g = string_cat(g, US" (best guess record for domain)");
+
+s = expand_string(US"$sender_address_domain");
+return s && *s
+ ? string_append(g, 2, US" smtp.mailfrom=", s)
+ : string_cat(g, US" smtp.mailfrom=<>");
+}
+
+
+#endif
Copyright (c) The Exim Maintainers 2016
*/
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
/* Yes, we do have ns_type. spf.h redefines it if we don't set this. Doh */
#ifndef HAVE_NS_TYPE
} spf_result_id;
/* prototypes */
-int spf_init(uschar *,uschar *);
-int spf_process(const uschar **, uschar *, int);
+BOOL spf_init(uschar *,uschar *);
+int spf_process(const uschar **, uschar *, int);
#define SPF_PROCESS_NORMAL 0
#define SPF_PROCESS_GUESS 1
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for reading spool files. When compiling for a utility (eximon),
fname = spool_fname(US"input", message_subdir, id, US"-D");
DEBUG(D_deliver) debug_printf("Trying spool file %s\n", fname);
+ /* We protect against symlink attacks both in not propagating the
+ * file-descriptor to other processes as we exec, and also ensuring that we
+ * don't even open symlinks.
+ * No -D file inside the spool area should be a symlink.
+ */
if ((fd = Uopen(fname,
#ifdef O_CLOEXEC
O_CLOEXEC |
+#endif
+#ifdef O_NOFOLLOW
+ O_NOFOLLOW |
#endif
O_RDWR | O_APPEND, 0)) >= 0)
break;
if (errno == ENOENT)
{
if (i == 0) continue;
- if (!queue_running)
+ if (!f.queue_running)
log_write(0, LOG_MAIN, "Spool%s%s file %s-D not found",
*queue_name ? US" Q=" : US"",
*queue_name ? queue_name : US"",
-/*************************************************
-* Read spool header file *
-*************************************************/
-
-/* This function reads a spool header file and places the data into the
-appropriate global variables. The header portion is always read, but header
-structures are built only if read_headers is set true. It isn't, for example,
-while generating -bp output.
-
-It may be possible for blocks of nulls (binary zeroes) to get written on the
-end of a file if there is a system crash during writing. It was observed on an
-earlier version of Exim that omitted to fsync() the files - this is thought to
-have been the cause of that incident, but in any case, this code must be robust
-against such an event, and if such a file is encountered, it must be treated as
-malformed.
-
-As called from deliver_message() (at least) we are running as root.
-
-Arguments:
- name name of the header file, including the -H
- read_headers TRUE if in-store header structures are to be built
- subdir_set TRUE is message_subdir is already set
-
-Returns: spool_read_OK success
- spool_read_notopen open failed
- spool_read_enverror error in the envelope portion
- spool_read_hdrerror error in the header portion
-*/
-
-int
-spool_read_header(uschar *name, BOOL read_headers, BOOL subdir_set)
-{
-FILE *f = NULL;
-int n;
-int rcount = 0;
-long int uid, gid;
-BOOL inheader = FALSE;
-uschar *p;
-
/* Reset all the global variables to their default values. However, there is
one exception. DO NOT change the default value of dont_deliver, because it may
be forced by an external setting. */
+void
+spool_clear_header_globals(void)
+{
acl_var_c = acl_var_m = NULL;
authenticated_id = NULL;
authenticated_sender = NULL;
-allow_unqualified_recipient = FALSE;
-allow_unqualified_sender = FALSE;
+f.allow_unqualified_recipient = FALSE;
+f.allow_unqualified_sender = FALSE;
body_linecount = 0;
body_zerocount = 0;
-deliver_firsttime = FALSE;
-deliver_freeze = FALSE;
+f.deliver_firsttime = FALSE;
+f.deliver_freeze = FALSE;
deliver_frozen_at = 0;
-deliver_manual_thaw = FALSE;
-/* dont_deliver must NOT be reset */
+f.deliver_manual_thaw = FALSE;
+/* f.dont_deliver must NOT be reset */
header_list = header_last = NULL;
host_lookup_deferred = FALSE;
host_lookup_failed = FALSE;
interface_address = NULL;
interface_port = 0;
-local_error_message = FALSE;
+f.local_error_message = FALSE;
+#ifdef HAVE_LOCAL_SCAN
local_scan_data = NULL;
+#endif
max_received_linelength = 0;
message_linecount = 0;
received_protocol = NULL;
sender_host_port = 0;
sender_host_authenticated = NULL;
sender_ident = NULL;
-sender_local = FALSE;
-sender_set_untrusted = FALSE;
+f.sender_local = FALSE;
+f.sender_set_untrusted = FALSE;
smtp_active_hostname = primary_hostname;
+#ifndef COMPILE_UTILITY
+f.spool_file_wireformat = FALSE;
+#endif
tree_nonrecipients = NULL;
#ifdef EXPERIMENTAL_BRIGHTMAIL
#ifndef DISABLE_DKIM
dkim_signers = NULL;
-dkim_disable_verify = FALSE;
-dkim_collect_input = FALSE;
+f.dkim_disable_verify = FALSE;
+dkim_collect_input = 0;
#endif
#ifdef SUPPORT_TLS
tls_in.certificate_verified = FALSE;
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
tls_in.dane_verified = FALSE;
# endif
tls_in.cipher = NULL;
tls_in.peerdn = NULL;
tls_in.sni = NULL;
tls_in.ocsp = OCSP_NOT_REQ;
+# if defined(EXPERIMENTAL_REQUIRETLS) && !defined(COMPILE_UTILITY)
+tls_requiretls = 0;
+# endif
#endif
#ifdef WITH_CONTENT_SCAN
dsn_ret = 0;
dsn_envid = NULL;
+}
+
+
+/*************************************************
+* Read spool header file *
+*************************************************/
+
+/* This function reads a spool header file and places the data into the
+appropriate global variables. The header portion is always read, but header
+structures are built only if read_headers is set true. It isn't, for example,
+while generating -bp output.
+
+It may be possible for blocks of nulls (binary zeroes) to get written on the
+end of a file if there is a system crash during writing. It was observed on an
+earlier version of Exim that omitted to fsync() the files - this is thought to
+have been the cause of that incident, but in any case, this code must be robust
+against such an event, and if such a file is encountered, it must be treated as
+malformed.
+
+As called from deliver_message() (at least) we are running as root.
+
+Arguments:
+ name name of the header file, including the -H
+ read_headers TRUE if in-store header structures are to be built
+ subdir_set TRUE is message_subdir is already set
+
+Returns: spool_read_OK success
+ spool_read_notopen open failed
+ spool_read_enverror error in the envelope portion
+ spool_read_hdrerror error in the header portion
+*/
+
+int
+spool_read_header(uschar *name, BOOL read_headers, BOOL subdir_set)
+{
+FILE * fp = NULL;
+int n;
+int rcount = 0;
+long int uid, gid;
+BOOL inheader = FALSE;
+uschar *p;
+
+/* Reset all the global variables to their default values. However, there is
+one exception. DO NOT change the default value of dont_deliver, because it may
+be forced by an external setting. */
+
+spool_clear_header_globals();
/* Generate the full name and open the file. If message_subdir is already
set, just look in the given directory. Otherwise, look in both the split
if (!subdir_set)
message_subdir[0] = split_spool_directory == (n == 0) ? name[5] : 0;
- if ((f = Ufopen(spool_fname(US"input", message_subdir, name, US""), "rb")))
+ if ((fp = Ufopen(spool_fname(US"input", message_subdir, name, US""), "rb")))
break;
if (n != 0 || subdir_set || errno != ENOENT)
return spool_read_notopen;
/* The first line of a spool file contains the message id followed by -H (i.e.
the file name), in order to make the file self-identifying. */
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
if (Ustrlen(big_buffer) != MESSAGE_ID_LENGTH + 3 ||
Ustrncmp(big_buffer, name, MESSAGE_ID_LENGTH + 2) != 0)
goto SPOOL_FORMAT_ERROR;
sender, enclosed in <>. The third contains the time the message was received,
and the number of warning messages for delivery delays that have been sent. */
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
p = big_buffer + Ustrlen(big_buffer);
while (p > big_buffer && isspace(p[-1])) p--;
originator_gid = (gid_t)gid;
/* envelope from */
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
n = Ustrlen(big_buffer);
if (n < 3 || big_buffer[0] != '<' || big_buffer[n-2] != '>')
goto SPOOL_FORMAT_ERROR;
sender_address[n-3] = 0;
/* time */
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
-if (sscanf(CS big_buffer, "%d %d", &received_time, &warning_count) != 2)
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
+if (sscanf(CS big_buffer, TIME_T_FMT " %d", &received_time.tv_sec, &warning_count) != 2)
goto SPOOL_FORMAT_ERROR;
+received_time.tv_usec = 0;
-message_age = time(NULL) - received_time;
+message_age = time(NULL) - received_time.tv_sec;
#ifndef COMPILE_UTILITY
DEBUG(D_deliver) debug_printf("user=%s uid=%ld gid=%ld sender=%s\n",
for (;;)
{
int len;
- if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+ if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
if (big_buffer[0] != '-') break;
while ( (len = Ustrlen(big_buffer)) == big_buffer_size-1
&& big_buffer[len-1] != '\n'
buf = store_get_perm(big_buffer_size *= 2);
memcpy(buf, big_buffer, --len);
big_buffer = buf;
- if (Ufgets(big_buffer+len, big_buffer_size-len, f) == NULL)
+ if (Ufgets(big_buffer+len, big_buffer_size-len, fp) == NULL)
goto SPOOL_READ_ERROR;
}
big_buffer[len-1] = 0;
tree_node *node;
endptr = Ustrchr(big_buffer + 6, ' ');
if (endptr == NULL) goto SPOOL_FORMAT_ERROR;
- name = string_sprintf("%c%.*s", big_buffer[4], endptr - big_buffer - 6,
- big_buffer + 6);
+ name = string_sprintf("%c%.*s", big_buffer[4],
+ (int)(endptr - big_buffer - 6), big_buffer + 6);
if (sscanf(CS endptr, " %d", &count) != 1) goto SPOOL_FORMAT_ERROR;
node = acl_var_create(name);
node->data.ptr = store_get(count + 1);
- if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
+ if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR;
((uschar*)node->data.ptr)[count] = 0;
}
else if (Ustrcmp(p, "llow_unqualified_recipient") == 0)
- allow_unqualified_recipient = TRUE;
+ f.allow_unqualified_recipient = TRUE;
else if (Ustrcmp(p, "llow_unqualified_sender") == 0)
- allow_unqualified_sender = TRUE;
+ f.allow_unqualified_sender = TRUE;
else if (Ustrncmp(p, "uth_id", 6) == 0)
authenticated_id = string_copy(big_buffer + 9);
node->data.ptr = store_get(count + 1);
/* We sanity-checked the count, so disable the Coverity error */
/* coverity[tainted_data] */
- if (fread(node->data.ptr, 1, count+1, f) < count) goto SPOOL_READ_ERROR;
+ if (fread(node->data.ptr, 1, count+1, fp) < count) goto SPOOL_READ_ERROR;
(US node->data.ptr)[count] = '\0';
}
break;
case 'd':
if (Ustrcmp(p, "eliver_firsttime") == 0)
- deliver_firsttime = TRUE;
+ f.deliver_firsttime = TRUE;
/* Check if the dsn flags have been set in the header file */
else if (Ustrncmp(p, "sn_ret", 6) == 0)
dsn_ret= atoi(CS big_buffer + 8);
case 'f':
if (Ustrncmp(p, "rozen", 5) == 0)
{
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
if (sscanf(CS big_buffer+7, TIME_T_FMT, &deliver_frozen_at) != 1)
goto SPOOL_READ_ERROR;
}
break;
case 'l':
- if (Ustrcmp(p, "ocal") == 0) sender_local = TRUE;
+ if (Ustrcmp(p, "ocal") == 0)
+ f.sender_local = TRUE;
else if (Ustrcmp(big_buffer, "-localerror") == 0)
- local_error_message = TRUE;
+ f.local_error_message = TRUE;
+#ifdef HAVE_LOCAL_SCAN
else if (Ustrncmp(p, "ocal_scan ", 10) == 0)
local_scan_data = string_copy(big_buffer + 12);
+#endif
break;
case 'm':
- if (Ustrcmp(p, "anual_thaw") == 0) deliver_manual_thaw = TRUE;
+ if (Ustrcmp(p, "anual_thaw") == 0) f.deliver_manual_thaw = TRUE;
else if (Ustrncmp(p, "ax_received_linelength", 22) == 0)
max_received_linelength = Uatoi(big_buffer + 24);
break;
case 'N':
- if (*p == 0) dont_deliver = TRUE; /* -N */
+ if (*p == 0) f.dont_deliver = TRUE; /* -N */
break;
case 'r':
if (Ustrncmp(p, "eceived_protocol", 16) == 0)
received_protocol = string_copy(big_buffer + 19);
+ else if (Ustrncmp(p, "eceived_time_usec", 17) == 0)
+ {
+ unsigned usec;
+ if (sscanf(CS big_buffer + 21, "%u", &usec) == 1)
+ received_time.tv_usec = usec;
+ }
break;
case 's':
if (Ustrncmp(p, "ender_set_untrusted", 19) == 0)
- sender_set_untrusted = TRUE;
+ f.sender_set_untrusted = TRUE;
#ifdef WITH_CONTENT_SCAN
else if (Ustrncmp(p, "pam_bar ", 8) == 0)
spam_bar = string_copy(big_buffer + 10);
else if (Ustrncmp(p, "pam_score_int ", 14) == 0)
spam_score_int = string_copy(big_buffer + 16);
#endif
+#ifndef COMPILE_UTILITY
+ else if (Ustrncmp(p, "pool_file_wireformat", 20) == 0)
+ f.spool_file_wireformat = TRUE;
+#endif
#if defined(SUPPORT_I18N) && !defined(COMPILE_UTILITY)
else if (Ustrncmp(p, "mtputf8", 7) == 0)
message_smtputf8 = TRUE;
#ifdef SUPPORT_TLS
case 't':
- if (Ustrncmp(p, "ls_certificate_verified", 23) == 0)
- tls_in.certificate_verified = TRUE;
- else if (Ustrncmp(p, "ls_cipher", 9) == 0)
- tls_in.cipher = string_copy(big_buffer + 12);
+ if (Ustrncmp(p, "ls_", 3) == 0)
+ {
+ uschar * q = p + 3;
+ if (Ustrncmp(q, "certificate_verified", 20) == 0)
+ tls_in.certificate_verified = TRUE;
+ else if (Ustrncmp(q, "cipher", 6) == 0)
+ tls_in.cipher = string_copy(big_buffer + 12);
# ifndef COMPILE_UTILITY /* tls support fns not built in */
- else if (Ustrncmp(p, "ls_ourcert", 10) == 0)
- (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert);
- else if (Ustrncmp(p, "ls_peercert", 11) == 0)
- (void) tls_import_cert(big_buffer + 14, &tls_in.peercert);
+ else if (Ustrncmp(q, "ourcert", 7) == 0)
+ (void) tls_import_cert(big_buffer + 13, &tls_in.ourcert);
+ else if (Ustrncmp(q, "peercert", 8) == 0)
+ (void) tls_import_cert(big_buffer + 14, &tls_in.peercert);
+# endif
+ else if (Ustrncmp(q, "peerdn", 6) == 0)
+ tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
+ else if (Ustrncmp(q, "sni", 3) == 0)
+ tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
+ else if (Ustrncmp(q, "ocsp", 4) == 0)
+ tls_in.ocsp = big_buffer[10] - '0';
+# if defined(EXPERIMENTAL_REQUIRETLS) && !defined(COMPILE_UTILITY)
+ else if (Ustrncmp(q, "requiretls", 10) == 0)
+ tls_requiretls = strtol(CS big_buffer+16, NULL, 0);
# endif
- else if (Ustrncmp(p, "ls_peerdn", 9) == 0)
- tls_in.peerdn = string_unprinting(string_copy(big_buffer + 12));
- else if (Ustrncmp(p, "ls_sni", 6) == 0)
- tls_in.sni = string_unprinting(string_copy(big_buffer + 9));
- else if (Ustrncmp(p, "ls_ocsp", 7) == 0)
- tls_in.ocsp = big_buffer[10] - '0';
+ }
break;
#endif
#ifndef COMPILE_UTILITY
DEBUG(D_deliver)
- debug_printf("sender_local=%d ident=%s\n", sender_local,
+ debug_printf("sender_local=%d ident=%s\n", f.sender_local,
(sender_ident == NULL)? US"unset" : sender_ident);
#endif /* COMPILE_UTILITY */
containing "XX", indicating no tree. */
if (Ustrncmp(big_buffer, "XX\n", 3) != 0 &&
- !read_nonrecipients_tree(&tree_nonrecipients, f, big_buffer, big_buffer_size))
+ !read_nonrecipients_tree(&tree_nonrecipients, fp, big_buffer, big_buffer_size))
goto SPOOL_FORMAT_ERROR;
#ifndef COMPILE_UTILITY
buffer. It contains the count of recipients which follow on separate lines.
Apply an arbitrary sanity check.*/
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
if (sscanf(CS big_buffer, "%d", &rcount) != 1 || rcount > 16384)
goto SPOOL_FORMAT_ERROR;
uschar *errors_to = NULL;
uschar *p;
- if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+ if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
nn = Ustrlen(big_buffer);
if (nn < 2) goto SPOOL_FORMAT_ERROR;
list if requested to do so. */
inheader = TRUE;
-if (Ufgets(big_buffer, big_buffer_size, f) == NULL) goto SPOOL_READ_ERROR;
+if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
if (big_buffer[0] != '\n') goto SPOOL_FORMAT_ERROR;
-while ((n = fgetc(f)) != EOF)
+while ((n = fgetc(fp)) != EOF)
{
header_line *h;
uschar flag[4];
int i;
if (!isdigit(n)) goto SPOOL_FORMAT_ERROR;
- if(ungetc(n, f) == EOF || fscanf(f, "%d%c ", &n, flag) == EOF)
+ if(ungetc(n, fp) == EOF || fscanf(fp, "%d%c ", &n, flag) == EOF)
goto SPOOL_READ_ERROR;
if (flag[0] != '*') message_size += n; /* Omit non-transmitted headers */
for (i = 0; i < n; i++)
{
- int c = fgetc(f);
+ int c = fgetc(fp);
if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
if (c == '\n' && h->type != htype_old) message_linecount++;
h->text[i] = c;
else for (i = 0; i < n; i++)
{
- int c = fgetc(f);
+ int c = fgetc(fp);
if (c == 0 || c == EOF) goto SPOOL_FORMAT_ERROR;
}
}
message_linecount += body_linecount;
-fclose(f);
+fclose(fp);
return spool_read_OK;
DEBUG(D_any) debug_printf("Error while reading spool file %s\n", name);
#endif /* COMPILE_UTILITY */
- fclose(f);
+ fclose(fp);
errno = n;
return inheader? spool_read_hdrerror : spool_read_enverror;
}
DEBUG(D_any) debug_printf("Format error in spool file %s\n", name);
#endif /* COMPILE_UTILITY */
-fclose(f);
+fclose(fp);
errno = ERRNO_SPOOLFORMAT;
return inheader? spool_read_hdrerror : spool_read_enverror;
}
/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
* License: GPL
- * Copyright (c) The Exim Maintainers 2016
+ * Copyright (c) The Exim Maintainers 2016 - 2018
*/
/* Code for setting up a MBOX style spool file inside a /scan/<msgid>
int spool_mbox_ok = 0;
uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
-/* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size
- * normally, source_file_override is NULL */
+/*
+Create an MBOX-style message file from the spooled files.
+
+Returns a pointer to the FILE, and puts the size in bytes into mbox_file_size.
+If mbox_fname is non-null, fill in a pointer to the name.
+Normally, source_file_override is NULL
+*/
FILE *
-spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override)
+spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override,
+ uschar ** mbox_fname)
{
uschar message_subdir[2];
uschar buffer[16384];
uschar *temp_string;
uschar *mbox_path;
-FILE *mbox_file = NULL;
-FILE *data_file = NULL;
-FILE *yield = NULL;
+FILE *mbox_file = NULL, *l_data_file = NULL, *yield = NULL;
header_line *my_headerlist;
struct stat statbuf;
int i, j;
-void *reset_point = store_get(0);
+void *reset_point;
+
+mbox_path = string_sprintf("%s/scan/%s/%s.eml",
+ spool_directory, message_id, message_id);
+if (mbox_fname) *mbox_fname = mbox_path;
-mbox_path = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id,
- message_id);
+reset_point = store_get(0);
/* Skip creation if already spooled out as mbox file */
if (!spool_mbox_ok)
}
/* open [message_id].eml file for writing */
- mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE);
- if (mbox_file == NULL)
+
+ if (!(mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE)))
{
log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
"scan file %s", mbox_path));
"${if def:sender_address{X-Envelope-From: <${sender_address}>\n}}"
"${if def:recipients{X-Envelope-To: ${recipients}\n}}");
- if (temp_string != NULL)
- {
- i = fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file);
- if (i != 1)
+ if (temp_string)
+ if (fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file) != 1)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
mailbox headers to %s", mbox_path);
goto OUT;
}
- }
- /* write all header lines to mbox file */
- my_headerlist = header_list;
- for (my_headerlist = header_list; my_headerlist != NULL;
- my_headerlist = my_headerlist->next)
- {
- /* skip deleted headers */
- if (my_headerlist->type == '*') continue;
+ /* write all non-deleted header lines to mbox file */
- i = fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file);
- if (i != 1)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
- message headers to %s", mbox_path);
- goto OUT;
- }
- }
+ for (my_headerlist = header_list; my_headerlist;
+ my_headerlist = my_headerlist->next)
+ if (my_headerlist->type != '*')
+ if (fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file) != 1)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
+ message headers to %s", mbox_path);
+ goto OUT;
+ }
/* End headers */
if (fwrite("\n", 1, 1, mbox_file) != 1)
goto OUT;
}
- /* copy body file */
- if (source_file_override == NULL)
+ /* Copy body file. If the main receive still has it open then it is holding
+ a lock, and we must not close it (which releases the lock), so just use the
+ global file handle. */
+ if (source_file_override)
+ l_data_file = Ufopen(source_file_override, "rb");
+ else if (spool_data_file)
+ l_data_file = spool_data_file;
+ else
{
message_subdir[1] = '\0';
for (i = 0; i < 2; i++)
{
message_subdir[0] = split_spool_directory == (i == 0) ? message_id[5] : 0;
temp_string = spool_fname(US"input", message_subdir, message_id, US"-D");
- if ((data_file = Ufopen(temp_string, "rb"))) break;
+ if ((l_data_file = Ufopen(temp_string, "rb"))) break;
}
}
- else
- data_file = Ufopen(source_file_override, "rb");
- if (!data_file)
+ if (!l_data_file)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
message_id);
/* The code used to use this line, but it doesn't work in Cygwin.
- (void)fread(data_buffer, 1, 18, data_file);
-
+ (void)fread(data_buffer, 1, 18, l_data_file);
+
What's happening is that spool_mbox used to use an fread to jump over the
file header. That fails under Cygwin because the header is locked, but
doing an fseek succeeds. We have to output the leading newline
explicitly, because the one in the file is parted of the locked area. */
if (!source_file_override)
- (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
+ (void)fseek(l_data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
do
{
- j = fread(buffer, 1, sizeof(buffer), data_file);
+ uschar * s;
+
+ if (!f.spool_file_wireformat || source_file_override)
+ j = fread(buffer, 1, sizeof(buffer), l_data_file);
+ else /* needs CRLF -> NL */
+ if ((s = US fgets(CS buffer, sizeof(buffer), l_data_file)))
+ {
+ uschar * p = s + Ustrlen(s) - 1;
+
+ if (*p == '\n' && p[-1] == '\r')
+ *--p = '\n';
+ else if (*p == '\r')
+ ungetc(*p--, l_data_file);
+
+ j = p - buffer;
+ }
+ else
+ j = 0;
if (j > 0)
- {
- i = fwrite(buffer, j, 1, mbox_file);
- if (i != 1)
+ if (fwrite(buffer, j, 1, mbox_file) != 1)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
message body to %s", mbox_path);
goto OUT;
}
- }
} while (j > 0);
(void)fclose(mbox_file);
*mbox_file_size = statbuf.st_size;
OUT:
-if (data_file) (void)fclose(data_file);
+if (l_data_file && !spool_data_file) (void)fclose(l_data_file);
if (mbox_file) (void)fclose(mbox_file);
store_reset(reset_point);
return yield;
spam_ok = 0;
malware_ok = 0;
-if (spool_mbox_ok && !no_mbox_unspool)
+if (spool_mbox_ok && !f.no_mbox_unspool)
{
uschar *mbox_path;
uschar *file_path;
mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
- tempdir = opendir(CS mbox_path);
- if (!tempdir)
+ if (!(tempdir = opendir(CS mbox_path)))
{
debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
/* Just in case we still can: */
return;
}
/* loop thru dir & delete entries */
- while((entry = readdir(tempdir)) != NULL)
+ while((entry = readdir(tempdir)))
{
uschar *name = US entry->d_name;
int dummy;
file_path = string_sprintf("%s/%s", mbox_path, name);
debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
- dummy = unlink(CS file_path);
+ dummy = unlink(CS file_path); dummy = dummy; /* compiler quietening */
}
closedir(tempdir);
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for writing spool files, and moving them about. */
int fd;
int i;
int size_correction;
-FILE *f;
+FILE * fp;
header_line *h;
struct stat statbuf;
uschar * tname;
if ((fd = spool_open_temp(tname)) < 0)
return spool_write_error(where, errmsg, US"open", NULL, NULL);
-f = fdopen(fd, "wb");
+fp = fdopen(fd, "wb");
DEBUG(D_receive|D_deliver) debug_printf("Writing spool header file: %s\n", tname);
/* We now have an open file to which the header data is to be written. Start
address is enclosed in <> because it might be the null address. Then write the
received time and the number of warning messages that have been sent. */
-fprintf(f, "%s-H\n", message_id);
-fprintf(f, "%.63s %ld %ld\n", originator_login, (long int)originator_uid,
+fprintf(fp, "%s-H\n", message_id);
+fprintf(fp, "%.63s %ld %ld\n", originator_login, (long int)originator_uid,
(long int)originator_gid);
-fprintf(f, "<%s>\n", sender_address);
-fprintf(f, "%d %d\n", received_time, warning_count);
+fprintf(fp, "<%s>\n", sender_address);
+fprintf(fp, "%d %d\n", (int)received_time.tv_sec, warning_count);
+
+fprintf(fp, "-received_time_usec .%06d\n", (int)received_time.tv_usec);
/* If there is information about a sending host, remember it. The HELO
data can be set for local SMTP as well as remote. */
-if (sender_helo_name != NULL)
- fprintf(f, "-helo_name %s\n", sender_helo_name);
+if (sender_helo_name)
+ fprintf(fp, "-helo_name %s\n", sender_helo_name);
-if (sender_host_address != NULL)
+if (sender_host_address)
{
- fprintf(f, "-host_address %s.%d\n", sender_host_address, sender_host_port);
- if (sender_host_name != NULL)
- fprintf(f, "-host_name %s\n", sender_host_name);
- if (sender_host_authenticated != NULL)
- fprintf(f, "-host_auth %s\n", sender_host_authenticated);
+ fprintf(fp, "-host_address %s.%d\n", sender_host_address, sender_host_port);
+ if (sender_host_name)
+ fprintf(fp, "-host_name %s\n", sender_host_name);
+ if (sender_host_authenticated)
+ fprintf(fp, "-host_auth %s\n", sender_host_authenticated);
}
/* Also about the interface a message came in on */
-if (interface_address != NULL)
- fprintf(f, "-interface_address %s.%d\n", interface_address, interface_port);
+if (interface_address)
+ fprintf(fp, "-interface_address %s.%d\n", interface_address, interface_port);
if (smtp_active_hostname != primary_hostname)
- fprintf(f, "-active_hostname %s\n", smtp_active_hostname);
+ fprintf(fp, "-active_hostname %s\n", smtp_active_hostname);
/* Likewise for any ident information; for local messages this is
likely to be the same as originator_login, but will be different if
the originator was root, forcing a different ident. */
-if (sender_ident != NULL) fprintf(f, "-ident %s\n", sender_ident);
+if (sender_ident) fprintf(fp, "-ident %s\n", sender_ident);
/* Ditto for the received protocol */
-if (received_protocol != NULL)
- fprintf(f, "-received_protocol %s\n", received_protocol);
+if (received_protocol)
+ fprintf(fp, "-received_protocol %s\n", received_protocol);
/* Preserve any ACL variables that are set. */
-tree_walk(acl_var_c, &acl_var_write, f);
-tree_walk(acl_var_m, &acl_var_write, f);
+tree_walk(acl_var_c, &acl_var_write, fp);
+tree_walk(acl_var_m, &acl_var_write, fp);
/* Now any other data that needs to be remembered. */
-fprintf(f, "-body_linecount %d\n", body_linecount);
-fprintf(f, "-max_received_linelength %d\n", max_received_linelength);
-
-if (body_zerocount > 0) fprintf(f, "-body_zerocount %d\n", body_zerocount);
-
-if (authenticated_id != NULL)
- fprintf(f, "-auth_id %s\n", authenticated_id);
-if (authenticated_sender != NULL)
- fprintf(f, "-auth_sender %s\n", authenticated_sender);
-
-if (allow_unqualified_recipient) fprintf(f, "-allow_unqualified_recipient\n");
-if (allow_unqualified_sender) fprintf(f, "-allow_unqualified_sender\n");
-if (deliver_firsttime) fprintf(f, "-deliver_firsttime\n");
-if (deliver_freeze) fprintf(f, "-frozen " TIME_T_FMT "\n", deliver_frozen_at);
-if (dont_deliver) fprintf(f, "-N\n");
-if (host_lookup_deferred) fprintf(f, "-host_lookup_deferred\n");
-if (host_lookup_failed) fprintf(f, "-host_lookup_failed\n");
-if (sender_local) fprintf(f, "-local\n");
-if (local_error_message) fprintf(f, "-localerror\n");
-if (local_scan_data != NULL) fprintf(f, "-local_scan %s\n", local_scan_data);
+if (f.spool_file_wireformat)
+ fprintf(fp, "-spool_file_wireformat\n");
+else
+ fprintf(fp, "-body_linecount %d\n", body_linecount);
+fprintf(fp, "-max_received_linelength %d\n", max_received_linelength);
+
+if (body_zerocount > 0) fprintf(fp, "-body_zerocount %d\n", body_zerocount);
+
+if (authenticated_id)
+ fprintf(fp, "-auth_id %s\n", authenticated_id);
+if (authenticated_sender)
+ fprintf(fp, "-auth_sender %s\n", authenticated_sender);
+
+if (f.allow_unqualified_recipient) fprintf(fp, "-allow_unqualified_recipient\n");
+if (f.allow_unqualified_sender) fprintf(fp, "-allow_unqualified_sender\n");
+if (f.deliver_firsttime) fprintf(fp, "-deliver_firsttime\n");
+if (f.deliver_freeze) fprintf(fp, "-frozen " TIME_T_FMT "\n", deliver_frozen_at);
+if (f.dont_deliver) fprintf(fp, "-N\n");
+if (host_lookup_deferred) fprintf(fp, "-host_lookup_deferred\n");
+if (host_lookup_failed) fprintf(fp, "-host_lookup_failed\n");
+if (f.sender_local) fprintf(fp, "-local\n");
+if (f.local_error_message) fprintf(fp, "-localerror\n");
+#ifdef HAVE_LOCAL_SCAN
+if (local_scan_data) fprintf(fp, "-local_scan %s\n", local_scan_data);
+#endif
#ifdef WITH_CONTENT_SCAN
-if (spam_bar) fprintf(f,"-spam_bar %s\n", spam_bar);
-if (spam_score) fprintf(f,"-spam_score %s\n", spam_score);
-if (spam_score_int) fprintf(f,"-spam_score_int %s\n", spam_score_int);
+if (spam_bar) fprintf(fp,"-spam_bar %s\n", spam_bar);
+if (spam_score) fprintf(fp,"-spam_score %s\n", spam_score);
+if (spam_score_int) fprintf(fp,"-spam_score_int %s\n", spam_score_int);
#endif
-if (deliver_manual_thaw) fprintf(f, "-manual_thaw\n");
-if (sender_set_untrusted) fprintf(f, "-sender_set_untrusted\n");
+if (f.deliver_manual_thaw) fprintf(fp, "-manual_thaw\n");
+if (f.sender_set_untrusted) fprintf(fp, "-sender_set_untrusted\n");
#ifdef EXPERIMENTAL_BRIGHTMAIL
-if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts);
+if (bmi_verdicts) fprintf(fp, "-bmi_verdicts %s\n", bmi_verdicts);
#endif
#ifdef SUPPORT_TLS
-if (tls_in.certificate_verified) fprintf(f, "-tls_certificate_verified\n");
-if (tls_in.cipher) fprintf(f, "-tls_cipher %s\n", tls_in.cipher);
+if (tls_in.certificate_verified) fprintf(fp, "-tls_certificate_verified\n");
+if (tls_in.cipher) fprintf(fp, "-tls_cipher %s\n", tls_in.cipher);
if (tls_in.peercert)
{
(void) tls_export_cert(big_buffer, big_buffer_size, tls_in.peercert);
- fprintf(f, "-tls_peercert %s\n", CS big_buffer);
+ fprintf(fp, "-tls_peercert %s\n", CS big_buffer);
}
-if (tls_in.peerdn) fprintf(f, "-tls_peerdn %s\n", string_printing(tls_in.peerdn));
-if (tls_in.sni) fprintf(f, "-tls_sni %s\n", string_printing(tls_in.sni));
+if (tls_in.peerdn) fprintf(fp, "-tls_peerdn %s\n", string_printing(tls_in.peerdn));
+if (tls_in.sni) fprintf(fp, "-tls_sni %s\n", string_printing(tls_in.sni));
if (tls_in.ourcert)
{
(void) tls_export_cert(big_buffer, big_buffer_size, tls_in.ourcert);
- fprintf(f, "-tls_ourcert %s\n", CS big_buffer);
+ fprintf(fp, "-tls_ourcert %s\n", CS big_buffer);
}
-if (tls_in.ocsp) fprintf(f, "-tls_ocsp %d\n", tls_in.ocsp);
+if (tls_in.ocsp) fprintf(fp, "-tls_ocsp %d\n", tls_in.ocsp);
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+if (tls_requiretls) fprintf(fp, "-tls_requiretls 0x%x\n", tls_requiretls);
+# endif
#endif
#ifdef SUPPORT_I18N
if (message_smtputf8)
{
- fprintf(f, "-smtputf8\n");
+ fprintf(fp, "-smtputf8\n");
if (message_utf8_downconvert)
- fprintf(f, "-utf8_%sdowncvt\n", message_utf8_downconvert < 0 ? "opt" : "");
+ fprintf(fp, "-utf8_%sdowncvt\n", message_utf8_downconvert < 0 ? "opt" : "");
}
#endif
/* Write the dsn flags to the spool header file */
DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_envid %s\n", dsn_envid);
-if (dsn_envid != NULL) fprintf(f, "-dsn_envid %s\n", dsn_envid);
+if (dsn_envid) fprintf(fp, "-dsn_envid %s\n", dsn_envid);
DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_ret %d\n", dsn_ret);
-if (dsn_ret != 0) fprintf(f, "-dsn_ret %d\n", dsn_ret);
+if (dsn_ret) fprintf(fp, "-dsn_ret %d\n", dsn_ret);
/* To complete the envelope, write out the tree of non-recipients, followed by
the list of recipients. These won't be disjoint the first time, when no
checking has been done. If a recipient is a "one-time" alias, it is followed by
a space and its parent address number (pno). */
-tree_write(tree_nonrecipients, f);
-fprintf(f, "%d\n", recipients_count);
+tree_write(tree_nonrecipients, fp);
+fprintf(fp, "%d\n", recipients_count);
for (i = 0; i < recipients_count; i++)
{
recipient_item *r = recipients_list + i;
DEBUG(D_deliver) debug_printf("DSN: Flags :%d\n", r->dsn_flags);
if (r->pno < 0 && r->errors_to == NULL && r->dsn_flags == 0)
- fprintf(f, "%s\n", r->address);
+ fprintf(fp, "%s\n", r->address);
else
{
uschar * errors_to = r->errors_to ? r->errors_to : US"";
adding new values upfront and add flag 0x02 */
uschar * orcpt = r->orcpt ? r->orcpt : US"";
- fprintf(f, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt),
+ fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt),
r->dsn_flags, errors_to, Ustrlen(errors_to), r->pno);
}
/* Put a blank line before the headers */
-fprintf(f, "\n");
+fprintf(fp, "\n");
/* Save the size of the file so far so we can subtract it from the final length
to get the actual size of the headers. */
-fflush(f);
+fflush(fp);
if (fstat(fd, &statbuf))
- return spool_write_error(where, errmsg, US"fstat", tname, f);
+ return spool_write_error(where, errmsg, US"fstat", tname, fp);
size_correction = statbuf.st_size;
/* Finally, write out the message's headers. To make it easier to read them
These are saved as a record for debugging. Don't included them in the message's
size. */
-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h; h = h->next)
{
- fprintf(f, "%03d%c %s", h->slen, h->type, h->text);
+ fprintf(fp, "%03d%c %s", h->slen, h->type, h->text);
size_correction += 5;
if (h->type == '*') size_correction += h->slen;
}
/* Flush and check for any errors while writing */
-if (fflush(f) != 0 || ferror(f))
- return spool_write_error(where, errmsg, US"write", tname, f);
+if (fflush(fp) != 0 || ferror(fp))
+ return spool_write_error(where, errmsg, US"write", tname, fp);
/* Force the file's contents to be written to disk. Note that fflush()
just pushes it out of C, and fclose() doesn't guarantee to do the write
either. That's just the way Unix works... */
-if (EXIMfsync(fileno(f)) < 0)
- return spool_write_error(where, errmsg, US"sync", tname, f);
+if (EXIMfsync(fileno(fp)) < 0)
+ return spool_write_error(where, errmsg, US"sync", tname, fp);
/* Get the size of the file, and close it. */
if (fstat(fd, &statbuf) != 0)
return spool_write_error(where, errmsg, US"fstat", tname, NULL);
-if (fclose(f) != 0)
+if (fclose(fp) != 0)
return spool_write_error(where, errmsg, US"close", tname, NULL);
/* Rename the file to its correct name, thereby replacing any previous
*************************************************/
/* Copyright (c) Phil Pennock 2012, 2016
+ * Copyright (c) The Exim Maintainers 2017 - 2018
* But almost everything here is fixed published constants from RFCs, so also:
* Copyright (C) The Internet Society (2003)
* Copyright (C) The IETF Trust (2008)
/* KEEP SORTED ALPHABETICALLY;
* duplicate PEM are okay, if we want aliases, but names must be alphabetical */
static struct dh_constant dh_constants[] = {
- { "default", dh_exim_20160529_3 },
- { "exim.dev.20160529.1", dh_exim_20160529_1 },
- { "exim.dev.20160529.2", dh_exim_20160529_2 },
- { "exim.dev.20160529.3", dh_exim_20160529_3 },
- { "ffdhe2048", dh_ffdhe2048_pem },
- { "ffdhe3072", dh_ffdhe3072_pem },
- { "ffdhe4096", dh_ffdhe4096_pem },
- { "ffdhe6144", dh_ffdhe6144_pem },
- { "ffdhe8192", dh_ffdhe8192_pem },
- { "ike1", dh_ike_1_pem },
- { "ike14", dh_ike_14_pem },
- { "ike15", dh_ike_15_pem },
- { "ike16", dh_ike_16_pem },
- { "ike17", dh_ike_17_pem },
- { "ike18", dh_ike_18_pem },
- { "ike2", dh_ike_2_pem },
- { "ike22", dh_ike_22_pem },
- { "ike23", dh_ike_23_pem },
- { "ike24", dh_ike_24_pem },
- { "ike5", dh_ike_5_pem },
+ /* label pem */
+ { "default", dh_exim_20160529_3 },
+ { "exim.dev.20160529.1", dh_exim_20160529_1 },
+ { "exim.dev.20160529.2", dh_exim_20160529_2 },
+ { "exim.dev.20160529.3", dh_exim_20160529_3 },
+ { "ffdhe2048", dh_ffdhe2048_pem },
+ { "ffdhe3072", dh_ffdhe3072_pem },
+ { "ffdhe4096", dh_ffdhe4096_pem },
+ { "ffdhe6144", dh_ffdhe6144_pem },
+ { "ffdhe8192", dh_ffdhe8192_pem },
+ { "ike1", dh_ike_1_pem },
+ { "ike14", dh_ike_14_pem },
+ { "ike15", dh_ike_15_pem },
+ { "ike16", dh_ike_16_pem },
+ { "ike17", dh_ike_17_pem },
+ { "ike18", dh_ike_18_pem },
+ { "ike2", dh_ike_2_pem },
+ { "ike22", dh_ike_22_pem },
+ { "ike23", dh_ike_23_pem },
+ { "ike24", dh_ike_24_pem },
+ { "ike5", dh_ike_5_pem },
};
static const int dh_constants_count =
sizeof(dh_constants) / sizeof(struct dh_constant);
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Exim gets and frees all its store through these functions. In the original
#else
DEBUG(D_memory)
{
- if (running_in_test_harness)
+ if (f.running_in_test_harness)
debug_printf("---%d Get %5d\n", store_pool, size);
else
debug_printf("---%d Get %6p %5d %-14s %4d\n", store_pool,
(void) VALGRIND_MAKE_MEM_UNDEFINED(store_last_get[store_pool], size);
/* Update next pointer and number of bytes left in the current block. */
-next_yield[store_pool] = (void *)((char *)next_yield[store_pool] + size);
+next_yield[store_pool] = (void *)(CS next_yield[store_pool] + size);
yield_length[store_pool] -= size;
return store_last_get[store_pool];
if (rounded_oldsize % alignment != 0)
rounded_oldsize += alignment - (rounded_oldsize % alignment);
-if ((char *)ptr + rounded_oldsize != (char *)(next_yield[store_pool]) ||
+if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) ||
inc > yield_length[store_pool] + rounded_oldsize - oldsize)
return FALSE;
#else
DEBUG(D_memory)
{
- if (running_in_test_harness)
+ if (f.running_in_test_harness)
debug_printf("---%d Ext %5d\n", store_pool, newsize);
else
debug_printf("---%d Ext %6p %5d %-14s %4d\n", store_pool, ptr, newsize,
#endif /* COMPILE_UTILITY */
if (newsize % alignment != 0) newsize += alignment - (newsize % alignment);
-next_yield[store_pool] = (char *)ptr + newsize;
+next_yield[store_pool] = CS ptr + newsize;
yield_length[store_pool] -= newsize - rounded_oldsize;
(void) VALGRIND_MAKE_MEM_UNDEFINED(ptr + oldsize, inc);
return TRUE;
newlength = bc + b->length - CS ptr;
#ifndef COMPILE_UTILITY
-if (running_in_test_harness || debug_store)
+if (debug_store)
{
assert_no_variables(ptr, newlength, filename, linenumber);
- if (running_in_test_harness)
+ if (f.running_in_test_harness)
{
(void) VALGRIND_MAKE_MEM_DEFINED(ptr, newlength);
memset(ptr, 0xF0, newlength);
{
b = b->next;
#ifndef COMPILE_UTILITY
- if (running_in_test_harness || debug_store)
+ if (debug_store)
assert_no_variables(b, b->length + ALIGNED_SIZEOF_STOREBLOCK,
filename, linenumber);
#endif
while ((b = bb))
{
#ifndef COMPILE_UTILITY
- if (running_in_test_harness || debug_store)
+ if (debug_store)
assert_no_variables(b, b->length + ALIGNED_SIZEOF_STOREBLOCK,
filename, linenumber);
#endif
#else
DEBUG(D_memory)
{
- if (running_in_test_harness)
+ if (f.running_in_test_harness)
debug_printf("---%d Rst ** %d\n", store_pool, pool_malloc);
else
debug_printf("---%d Rst %6p ** %-14s %4d %d\n", store_pool, ptr,
* Release store *
************************************************/
-/* This function is specifically provided for use when reading very
-long strings, e.g. header lines. When the string gets longer than a
-complete block, it gets copied to a new block. It is helpful to free
-the old block iff the previous copy of the string is at its start,
-and therefore the only thing in it. Otherwise, for very long strings,
-dead store can pile up somewhat disastrously. This function checks that
-the pointer it is given is the first thing in a block, and if so,
-releases that block.
+/* This function checks that the pointer it is given is the first thing in a
+block, and if so, releases that block.
Arguments:
block block of store to consider
Returns: nothing
*/
-void
-store_release_3(void *block, const char *filename, int linenumber)
+static void
+store_release_3(void * block, const char * filename, int linenumber)
{
-storeblock *b;
+storeblock * b;
/* It will never be the first block, so no need to check that. */
-for (b = chainbase[store_pool]; b != NULL; b = b->next)
+for (b = chainbase[store_pool]; b; b = b->next)
{
- storeblock *bb = b->next;
- if (bb != NULL && (char *)block == (char *)bb + ALIGNED_SIZEOF_STOREBLOCK)
+ storeblock * bb = b->next;
+ if (bb && CS block == CS bb + ALIGNED_SIZEOF_STOREBLOCK)
{
b->next = bb->next;
pool_malloc -= bb->length + ALIGNED_SIZEOF_STOREBLOCK;
/* Cut out the debugging stuff for utilities, but stop picky compilers
from giving warnings. */
- #ifdef COMPILE_UTILITY
+#ifdef COMPILE_UTILITY
filename = filename;
linenumber = linenumber;
- #else
+#else
DEBUG(D_memory)
- {
- if (running_in_test_harness)
+ if (f.running_in_test_harness)
debug_printf("-Release %d\n", pool_malloc);
else
debug_printf("-Release %6p %-20s %4d %d\n", (void *)bb, filename,
linenumber, pool_malloc);
- }
- if (running_in_test_harness)
+
+ if (f.running_in_test_harness)
memset(bb, 0xF0, bb->length+ALIGNED_SIZEOF_STOREBLOCK);
- #endif /* COMPILE_UTILITY */
+#endif /* COMPILE_UTILITY */
free(bb);
return;
}
+/************************************************
+* Move store *
+************************************************/
+
+/* Allocate a new block big enough to expend to the given size and
+copy the current data into it. Free the old one if possible.
+
+This function is specifically provided for use when reading very
+long strings, e.g. header lines. When the string gets longer than a
+complete block, it gets copied to a new block. It is helpful to free
+the old block iff the previous copy of the string is at its start,
+and therefore the only thing in it. Otherwise, for very long strings,
+dead store can pile up somewhat disastrously. This function checks that
+the pointer it is given is the first thing in a block, and that nothing
+has been allocated since. If so, releases that block.
+
+Arguments:
+ block
+ newsize
+ len
+
+Returns: new location of data
+*/
+
+void *
+store_newblock_3(void * block, int newsize, int len,
+ const char * filename, int linenumber)
+{
+BOOL release_ok = store_last_get[store_pool] == block;
+uschar * newtext = store_get(newsize);
+
+memcpy(newtext, block, len);
+if (release_ok) store_release_3(block, filename, linenumber);
+return (void *)newtext;
+}
+
+
/*************************************************
/* If running in test harness, spend time making sure all the new store
is not filled with zeros so as to catch problems. */
-if (running_in_test_harness)
+if (f.running_in_test_harness)
{
memset(yield, 0xF0, (size_t)size);
DEBUG(D_memory) debug_printf("--Malloc %5d %d %d\n", size, pool_malloc,
#else
DEBUG(D_memory)
{
- if (running_in_test_harness)
+ if (f.running_in_test_harness)
debug_printf("----Free\n");
else
debug_printf("----Free %6p %-20s %4d\n", block, filename, linenumber);
#define store_get(size) store_get_3(size, __FILE__, __LINE__)
#define store_get_perm(size) store_get_perm_3(size, __FILE__, __LINE__)
#define store_malloc(size) store_malloc_3(size, __FILE__, __LINE__)
-#define store_release(addr) store_release_3(addr, __FILE__, __LINE__)
+#define store_newblock(addr,newsize,datalen) \
+ store_newblock_3(addr, newsize, datalen, __FILE__, __LINE__)
#define store_reset(addr) store_reset_3(addr, __FILE__, __LINE__)
/* The real functions */
-extern BOOL store_extend_3(void *, int, int, const char *, int); /* The */
-extern void store_free_3(void *, const char *, int); /* value of the */
-extern void *store_get_3(int, const char *, int); /* 2nd arg is */
-extern void *store_get_perm_3(int, const char *, int); /* __FILE__ in */
-extern void *store_malloc_3(int, const char *, int); /* every call, */
-extern void store_release_3(void *, const char *, int); /* so give its */
-extern void store_reset_3(void *, const char *, int); /* correct type */
+/* The value of the 2nd arg is __FILE__ in every call, so give its correct type */
+extern BOOL store_extend_3(void *, int, int, const char *, int);
+extern void store_free_3(void *, const char *, int);
+extern void *store_get_3(int, const char *, int);
+extern void *store_get_perm_3(int, const char *, int);
+extern void *store_malloc_3(int, const char *, int);
+extern void *store_newblock_3(void *, int, int, const char *, int);
+extern void store_reset_3(void *, const char *, int);
#endif /* STORE_H */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Miscellaneous string-handling functions. Some are not required for
#include "exim.h"
+#include <assert.h>
#ifndef COMPILE_UTILITY
/* If an optional mask is permitted, check for it. If found, pass back the
offset. */
-if (maskptr != NULL)
+if (maskptr)
{
const uschar *ss = s + Ustrlen(s);
*maskptr = 0;
if we hit the / that introduces a mask or the % that introduces the
interface specifier (scope id) of a link-local address. */
- if (*s == 0 || *s == '%' || *s == '/') return had_double_colon? yield : 0;
+ if (*s == 0 || *s == '%' || *s == '/') return had_double_colon ? yield : 0;
/* If a component starts with an additional colon, we have hit a double
colon. This is permitted to appear once only, and counts as at least
for (i = 0; i < 4; i++)
{
+ long n;
+ uschar * end;
+
if (i != 0 && *s++ != '.') return 0;
- if (!isdigit(*s++)) return 0;
- if (isdigit(*s) && isdigit(*(++s))) s++;
+ n = strtol(CCS s, CSS &end, 10);
+ if (n > 255 || n < 0 || end <= s || end > s+3) return 0;
+ s = end;
}
-return (*s == 0 || (*s == '/' && maskptr != NULL && *maskptr != 0))?
- yield : 0;
+return !*s || (*s == '/' && maskptr && *maskptr != 0) ? yield : 0;
}
#endif /* COMPILE_UTILITY */
/* First find the end of the string */
if (*s != '\"')
- {
while (*s != 0 && !isspace(*s)) s++;
- }
else
{
s++;
- while (*s != 0 && *s != '\"')
+ while (*s && *s != '\"')
{
if (*s == '\\') (void)string_interpret_escape(&s);
s++;
}
- if (*s != 0) s++;
+ if (*s) s++;
}
/* Get enough store to copy into */
* Format a string and save it *
*************************************************/
-/* The formatting is done by string_format, which checks the length of
+/* The formatting is done by string_vformat, which checks the length of
everything.
Arguments:
uschar *
string_sprintf(const char *format, ...)
{
-va_list ap;
+#ifdef COMPILE_UTILITY
uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
+gstring g = { .size = STRING_SPRINTF_BUFFER_SIZE, .ptr = 0, .s = buffer };
+gstring * gp = &g;
+#else
+gstring * gp = string_get(STRING_SPRINTF_BUFFER_SIZE);
+#endif
+gstring * gp2;
+va_list ap;
+
va_start(ap, format);
-if (!string_vformat(buffer, sizeof(buffer), format, ap))
- log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "string_sprintf expansion was longer than " SIZE_T_FMT
- "; format string was (%s)\nexpansion started '%.32s'",
- sizeof(buffer), format, buffer);
+gp2 = string_vformat(gp, FALSE, format, ap);
+gp->s[gp->ptr] = '\0';
va_end(ap);
-return string_copy(buffer);
+
+if (!gp2)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "string_sprintf expansion was longer than %d; format string was (%s)\n"
+ "expansion started '%.32s'",
+ gp->size, format, gp->s);
+
+#ifdef COMPILE_UTILITY
+return string_copy(gp->s);
+#else
+gstring_reset_unused(gp);
+return gp->s;
+#endif
}
+#ifdef COMPILE_UTILITY
+/* Dummy version for this function; it should never be called */
+static void
+gstring_grow(gstring * g, int p, int count)
+{
+assert(FALSE);
+}
+#endif
+
+
+
#ifndef COMPILE_UTILITY
/*************************************************
* Get next string from separated list *
const uschar *s = *listptr;
BOOL sep_is_special;
-if (s == NULL) return NULL;
+if (!s) return NULL;
/* This allows for a fixed specified separator to be an iscntrl() character,
but at the time of implementation, this is never the case. However, it's best
if (*s == '<' && (ispunct(s[1]) || iscntrl(s[1])))
{
sep = s[1];
- s += 2;
+ if (*++s) ++s;
while (isspace(*s) && *s != sep) s++;
}
else
- {
- sep = (sep == 0)? ':' : -sep;
- }
+ sep = sep ? -sep : ':';
*separator = sep;
}
/* An empty string has no list elements */
-if (*s == 0) return NULL;
+if (!*s) return NULL;
/* Note whether whether or not the separator is an iscntrl() character. */
/* Handle the case when a buffer is provided. */
-if (buffer != NULL)
+if (buffer)
{
int p = 0;
- for (; *s != 0; s++)
+ for (; *s; s++)
{
if (*s == sep && (*(++s) != sep || sep_is_special)) break;
if (p < buflen - 1) buffer[p++] = *s;
}
while (p > 0 && isspace(buffer[p-1])) p--;
- buffer[p] = 0;
+ buffer[p] = '\0';
}
/* Handle the case when a buffer is not provided. */
else
{
- int size = 0;
- int ptr = 0;
const uschar *ss;
+ gstring * g = NULL;
/* We know that *s != 0 at this point. However, it might be pointing to a
separator, which could indicate an empty string, or (if an ispunct()
for (;;)
{
- for (ss = s + 1; *ss != 0 && *ss != sep; ss++);
- buffer = string_catn(buffer, &size, &ptr, s, ss-s);
+ for (ss = s + 1; *ss && *ss != sep; ss++) ;
+ g = string_catn(g, s, ss-s);
s = ss;
- if (*s == 0 || *(++s) != sep || sep_is_special) break;
+ if (!*s || *++s != sep || sep_is_special) break;
}
- while (ptr > 0 && isspace(buffer[ptr-1])) ptr--;
- buffer[ptr] = 0;
+ while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--;
+ buffer = string_from_gstring(g);
+ gstring_reset_unused(g);
}
/* Update the current pointer and return the new string */
*listptr = s;
return buffer;
}
-#endif /* COMPILE_UTILITY */
-#ifndef COMPILE_UTILITY
+static const uschar *
+Ustrnchr(const uschar * s, int c, unsigned * len)
+{
+unsigned siz = *len;
+while (siz)
+ {
+ if (!*s) return NULL;
+ if (*s == c)
+ {
+ *len = siz;
+ return s;
+ }
+ s++;
+ siz--;
+ }
+return NULL;
+}
+
+
/************************************************
* Add element to separated list *
************************************************/
-/* This function is used to build a list, returning
-an allocated null-terminated growable string. The
-given element has any embedded separator characters
+/* This function is used to build a list, returning an allocated null-terminated
+growable string. The given element has any embedded separator characters
doubled.
+Despite having the same growable-string interface as string_cat() the list is
+always returned null-terminated.
+
Arguments:
- list points to the start of the list that is being built, or NULL
+ list expanding-string for the list that is being built, or NULL
if this is a new list that has no contents yet
sep list separator character
ele new element to be appended to the list
Returns: pointer to the start of the list, changed if copied for expansion.
*/
-uschar *
-string_append_listele(uschar * list, uschar sep, const uschar * ele)
+gstring *
+string_append_listele(gstring * list, uschar sep, const uschar * ele)
{
-uschar * new = NULL;
-int sz = 0, off = 0;
uschar * sp;
-if (list)
- {
- new = string_cat (new, &sz, &off, list);
- new = string_catn(new, &sz, &off, &sep, 1);
- }
+if (list && list->ptr)
+ list = string_catn(list, &sep, 1);
while((sp = Ustrchr(ele, sep)))
{
- new = string_catn(new, &sz, &off, ele, sp-ele+1);
- new = string_catn(new, &sz, &off, &sep, 1);
+ list = string_catn(list, ele, sp-ele+1);
+ list = string_catn(list, &sep, 1);
ele = sp+1;
}
-new = string_cat(new, &sz, &off, ele);
-new[off] = '\0';
-return new;
+list = string_cat(list, ele);
+(void) string_from_gstring(list);
+return list;
}
-static const uschar *
-Ustrnchr(const uschar * s, int c, unsigned * len)
+gstring *
+string_append_listele_n(gstring * list, uschar sep, const uschar * ele,
+ unsigned len)
{
-unsigned siz = *len;
-while (siz)
- {
- if (!*s) return NULL;
- if (*s == c)
- {
- *len = siz;
- return s;
- }
- s++;
- siz--;
- }
-return NULL;
-}
-
-uschar *
-string_append_listele_n(uschar * list, uschar sep, const uschar * ele,
- unsigned len)
-{
-uschar * new = NULL;
-int sz = 0, off = 0;
const uschar * sp;
-if (list)
- {
- new = string_cat (new, &sz, &off, list);
- new = string_catn(new, &sz, &off, &sep, 1);
- }
+if (list && list->ptr)
+ list = string_catn(list, &sep, 1);
while((sp = Ustrnchr(ele, sep, &len)))
{
- new = string_catn(new, &sz, &off, ele, sp-ele+1);
- new = string_catn(new, &sz, &off, &sep, 1);
+ list = string_catn(list, ele, sp-ele+1);
+ list = string_catn(list, &sep, 1);
ele = sp+1;
len--;
}
-new = string_catn(new, &sz, &off, ele, len);
-new[off] = '\0';
-return new;
+list = string_catn(list, ele, len);
+(void) string_from_gstring(list);
+return list;
+}
+
+
+
+/* A slightly-bogus listmaker utility; the separator is a string so
+can be multiple chars - there is no checking for the element content
+containing any of the separator. */
+
+gstring *
+string_append2_listele_n(gstring * list, const uschar * sepstr,
+ const uschar * ele, unsigned len)
+{
+if (list && list->ptr)
+ list = string_cat(list, sepstr);
+
+list = string_catn(list, ele, len);
+(void) string_from_gstring(list);
+return list;
+}
+
+
+
+/************************************************/
+/* Create a growable-string with some preassigned space */
+
+gstring *
+string_get(unsigned size)
+{
+gstring * g = store_get(sizeof(gstring) + size);
+g->size = size;
+g->ptr = 0;
+g->s = US(g + 1);
+return g;
+}
+
+/* NUL-terminate the C string in the growable-string, and return it. */
+
+uschar *
+string_from_gstring(gstring * g)
+{
+if (!g) return NULL;
+g->s[g->ptr] = '\0';
+return g->s;
+}
+
+void
+gstring_reset_unused(gstring * g)
+{
+store_reset(g->s + (g->size = g->ptr + 1));
+}
+
+
+/* Add more space to a growable-string.
+
+Arguments:
+ g the growable-string
+ p current end of data
+ count amount to grow by
+*/
+
+static void
+gstring_grow(gstring * g, int p, int count)
+{
+int oldsize = g->size;
+
+/* Mostly, string_cat() is used to build small strings of a few hundred
+characters at most. There are times, however, when the strings are very much
+longer (for example, a lookup that returns a vast number of alias addresses).
+To try to keep things reasonable, we use increments whose size depends on the
+existing length of the string. */
+
+unsigned inc = oldsize < 4096 ? 127 : 1023;
+g->size = ((p + count + inc) & ~inc) + 1;
+
+/* Try to extend an existing allocation. If the result of calling
+store_extend() is false, either there isn't room in the current memory block,
+or this string is not the top item on the dynamic store stack. We then have
+to get a new chunk of store and copy the old string. When building large
+strings, it is helpful to call store_release() on the old string, to release
+memory blocks that have become empty. (The block will be freed if the string
+is at its start.) However, we can do this only if we know that the old string
+was the last item on the dynamic memory stack. This is the case if it matches
+store_last_get. */
+
+if (!store_extend(g->s, oldsize, g->size))
+ g->s = store_newblock(g->s, g->size, p);
}
-#endif /* COMPILE_UTILITY */
-#ifndef COMPILE_UTILITY
/*************************************************
* Add chars to string *
*************************************************/
-
/* This function is used when building up strings of unknown length. Room is
always left for a terminating zero to be added to the string that is being
built. This function does not require the string that is being added to be NUL
Arguments:
string points to the start of the string that is being built, or NULL
if this is a new string that has no contents yet
- size points to a variable that holds the current capacity of the memory
- block (updated if changed)
- ptr points to a variable that holds the offset at which to add
- characters, updated to the new offset
s points to characters to add
count count of characters to add; must not exceed the length of s, if s
- is a C string. If -1 given, strlen(s) is used.
-
-If string is given as NULL, *size and *ptr should both be zero.
+ is a C string.
Returns: pointer to the start of the string, changed if copied for expansion.
Note that a NUL is not added, though space is left for one. This is
*/
/* coverity[+alloc] */
-uschar *
-string_catn(uschar *string, int *size, int *ptr, const uschar *s, int count)
+gstring *
+string_catn(gstring * g, const uschar *s, int count)
{
-int p = *ptr;
+int p;
-if (p + count >= *size)
+if (!g)
{
- int oldsize = *size;
-
- /* Mostly, string_cat() is used to build small strings of a few hundred
- characters at most. There are times, however, when the strings are very much
- longer (for example, a lookup that returns a vast number of alias addresses).
- To try to keep things reasonable, we use increments whose size depends on the
- existing length of the string. */
-
- int inc = (oldsize < 4096)? 100 : 1024;
- while (*size <= p + count) *size += inc;
-
- /* New string */
-
- if (string == NULL) string = store_get(*size);
-
- /* Try to extend an existing allocation. If the result of calling
- store_extend() is false, either there isn't room in the current memory block,
- or this string is not the top item on the dynamic store stack. We then have
- to get a new chunk of store and copy the old string. When building large
- strings, it is helpful to call store_release() on the old string, to release
- memory blocks that have become empty. (The block will be freed if the string
- is at its start.) However, we can do this only if we know that the old string
- was the last item on the dynamic memory stack. This is the case if it matches
- store_last_get. */
-
- else if (!store_extend(string, oldsize, *size))
- {
- BOOL release_ok = store_last_get[store_pool] == string;
- uschar *newstring = store_get(*size);
- memcpy(newstring, string, p);
- if (release_ok) store_release(string);
- string = newstring;
- }
+ unsigned inc = count < 4096 ? 127 : 1023;
+ unsigned size = ((count + inc) & ~inc) + 1;
+ g = string_get(size);
}
+p = g->ptr;
+if (p + count >= g->size)
+ gstring_grow(g, p, count);
+
/* Because we always specify the exact number of characters to copy, we can
use memcpy(), which is likely to be more efficient than strncopy() because the
-latter has to check for zero bytes.
+latter has to check for zero bytes. */
-The Coverity annotation deals with the lack of correlated variable tracking;
-common use is a null string and zero size and pointer, on first use for a
-string being built. The "if" above then allocates, but Coverity assume that
-the "if" might not happen and whines for a null-deref done by the memcpy(). */
-
-/* coverity[deref_parm_field_in_call] : FALSE */
-memcpy(string + p, s, count);
-*ptr = p + count;
-return string;
+memcpy(g->s + p, s, count);
+g->ptr = p + count;
+return g;
}
-
-
-uschar *
-string_cat(uschar *string, int *size, int *ptr, const uschar *s)
+
+
+gstring *
+string_cat(gstring *string, const uschar *s)
{
-return string_catn(string, size, ptr, s, Ustrlen(s));
+return string_catn(string, s, Ustrlen(s));
}
-#endif /* COMPILE_UTILITY */
-#ifndef COMPILE_UTILITY
/*************************************************
* Append strings to another string *
*************************************************/
It calls string_cat() to do the dirty work.
Arguments:
- string points to the start of the string that is being built, or NULL
+ string expanding-string that is being built, or NULL
if this is a new string that has no contents yet
- size points to a variable that holds the current capacity of the memory
- block (updated if changed)
- ptr points to a variable that holds the offset at which to add
- characters, updated to the new offset
count the number of strings to append
... "count" uschar* arguments, which must be valid zero-terminated
C strings
The string is not zero-terminated - see string_cat() above.
*/
-uschar *
-string_append(uschar *string, int *size, int *ptr, int count, ...)
+__inline__ gstring *
+string_append(gstring *string, int count, ...)
{
va_list ap;
-int i;
va_start(ap, count);
-for (i = 0; i < count; i++)
+while (count-- > 0)
{
uschar *t = va_arg(ap, uschar *);
- string = string_cat(string, size, ptr, t);
+ string = string_cat(string, t);
}
va_end(ap);
The formats are the usual printf() ones, with some omissions (never used) and
three additions for strings: %S forces lower case, %T forces upper case, and
-%#s or %#S prints nothing for a NULL string. Without thr # "NULL" is printed
+%#s or %#S prints nothing for a NULL string. Without the # "NULL" is printed
(useful in debugging). There is also the addition of %D and %M, which insert
the date in the form used for datestamped log files.
*/
BOOL
-string_format(uschar *buffer, int buflen, const char *format, ...)
+string_format(uschar * buffer, int buflen, const char * format, ...)
{
-BOOL yield;
+gstring g = { .size = buflen, .ptr = 0, .s = buffer }, *gp;
va_list ap;
va_start(ap, format);
-yield = string_vformat(buffer, buflen, format, ap);
+gp = string_vformat(&g, FALSE, format, ap);
va_end(ap);
-return yield;
+g.s[g.ptr] = '\0';
+return !!gp;
}
-BOOL
-string_vformat(uschar *buffer, int buflen, const char *format, va_list ap)
+
+
+
+/* Bulid or append to a growing-string, sprintf-style.
+
+If the "extend" argument is true, the string passed in can be NULL,
+empty, or non-empty.
+
+If the "extend" argument is false, the string passed in may not be NULL,
+will not be grown, and is usable in the original place after return.
+The return value can be NULL to signify overflow.
+
+Returns the possibly-new (if copy for growth was needed) string,
+not nul-terminated.
+*/
+
+gstring *
+string_vformat(gstring * g, BOOL extend, const char *format, va_list ap)
{
-/* We assume numbered ascending order, C does not guarantee that */
-enum { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 };
+enum ltypes { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 };
+
+int width, precision, off, lim;
+const char * fp = format; /* Deliberately not unsigned */
-BOOL yield = TRUE;
-int width, precision;
-const char *fp = format; /* Deliberately not unsigned */
-uschar *p = buffer;
-uschar *last = buffer + buflen - 1;
+string_datestamp_offset = -1; /* Datestamp not inserted */
+string_datestamp_length = 0; /* Datestamp not inserted */
+string_datestamp_type = 0; /* Datestamp not inserted */
-string_datestamp_offset = -1; /* Datestamp not inserted */
-string_datestamp_length = 0; /* Datestamp not inserted */
-string_datestamp_type = 0; /* Datestamp not inserted */
+#ifdef COMPILE_UTILITY
+assert(!extend);
+assert(g);
+#else
+
+/* Ensure we have a string, to save on checking later */
+if (!g) g = string_get(16);
+#endif /*!COMPILE_UTILITY*/
+
+lim = g->size - 1; /* leave one for a nul */
+off = g->ptr; /* remember initial offset in gstring */
/* Scan the format and handle the insertions */
-while (*fp != 0)
+while (*fp)
{
int length = L_NORMAL;
int *nptr;
int slen;
- const char *null = "NULL"; /* ) These variables */
- const char *item_start, *s; /* ) are deliberately */
- char newformat[16]; /* ) not unsigned */
+ const char *null = "NULL"; /* ) These variables */
+ const char *item_start, *s; /* ) are deliberately */
+ char newformat[16]; /* ) not unsigned */
+ char * gp = CS g->s + g->ptr; /* ) */
/* Non-% characters just get copied verbatim */
if (*fp != '%')
{
- if (p >= last) { yield = FALSE; break; }
- *p++ = (uschar)*fp++;
+ /* Avoid string_copyn() due to COMPILE_UTILITY */
+ if (g->ptr >= lim - 1)
+ {
+ if (!extend) return NULL;
+ gstring_grow(g, g->ptr, 1);
+ lim = g->size - 1;
+ }
+ g->s[g->ptr++] = (uschar) *fp++;
continue;
}
}
if (*fp == '.')
- {
if (*(++fp) == '*')
{
precision = va_arg(ap, int);
fp++;
}
else
- {
- precision = 0;
- while (isdigit((uschar)*fp))
- precision = precision*10 + *fp++ - '0';
- }
- }
+ for (precision = 0; isdigit((uschar)*fp); fp++)
+ precision = precision*10 + *fp - '0';
/* Skip over 'h', 'L', 'l', 'll' and 'z', remembering the item length */
else if (*fp == 'L')
{ fp++; length = L_LONGDOUBLE; }
else if (*fp == 'l')
- {
if (fp[1] == 'l')
- {
- fp += 2;
- length = L_LONGLONG;
- }
+ { fp += 2; length = L_LONGLONG; }
else
- {
- fp++;
- length = L_LONG;
- }
- }
+ { fp++; length = L_LONG; }
else if (*fp == 'z')
{ fp++; length = L_SIZE; }
switch (*fp++)
{
case 'n':
- nptr = va_arg(ap, int *);
- *nptr = p - buffer;
- break;
+ nptr = va_arg(ap, int *);
+ *nptr = g->ptr - off;
+ break;
case 'd':
case 'o':
case 'u':
case 'x':
case 'X':
- if (p >= last - ((length > L_LONG)? 24 : 12))
- { yield = FALSE; goto END_FORMAT; }
- strncpy(newformat, item_start, fp - item_start);
- newformat[fp - item_start] = 0;
-
- /* Short int is promoted to int when passing through ..., so we must use
- int for va_arg(). */
+ width = length > L_LONG ? 24 : 12;
+ if (g->ptr >= lim - width)
+ {
+ if (!extend) return NULL;
+ gstring_grow(g, g->ptr, width);
+ lim = g->size - 1;
+ gp = CS g->s + g->ptr;
+ }
+ strncpy(newformat, item_start, fp - item_start);
+ newformat[fp - item_start] = 0;
+
+ /* Short int is promoted to int when passing through ..., so we must use
+ int for va_arg(). */
+
+ switch(length)
+ {
+ case L_SHORT:
+ case L_NORMAL:
+ g->ptr += sprintf(gp, newformat, va_arg(ap, int)); break;
+ case L_LONG:
+ g->ptr += sprintf(gp, newformat, va_arg(ap, long int)); break;
+ case L_LONGLONG:
+ g->ptr += sprintf(gp, newformat, va_arg(ap, LONGLONG_T)); break;
+ case L_SIZE:
+ g->ptr += sprintf(gp, newformat, va_arg(ap, size_t)); break;
+ }
+ break;
- switch(length)
+ case 'p':
{
- case L_SHORT:
- case L_NORMAL: sprintf(CS p, newformat, va_arg(ap, int)); break;
- case L_LONG: sprintf(CS p, newformat, va_arg(ap, long int)); break;
- case L_LONGLONG: sprintf(CS p, newformat, va_arg(ap, LONGLONG_T)); break;
- case L_SIZE: sprintf(CS p, newformat, va_arg(ap, size_t)); break;
+ void * ptr;
+ if (g->ptr >= lim - 24)
+ {
+ if (!extend) return NULL;
+ gstring_grow(g, g->ptr, 24);
+ lim = g->size - 1;
+ gp = CS g->s + g->ptr;
+ }
+ /* sprintf() saying "(nil)" for a null pointer seems unreliable.
+ Handle it explicitly. */
+ if ((ptr = va_arg(ap, void *)))
+ {
+ strncpy(newformat, item_start, fp - item_start);
+ newformat[fp - item_start] = 0;
+ g->ptr += sprintf(gp, newformat, ptr);
+ }
+ else
+ g->ptr += sprintf(gp, "(nil)");
}
- while (*p) p++;
- break;
-
- case 'p':
- if (p >= last - 24) { yield = FALSE; goto END_FORMAT; }
- strncpy(newformat, item_start, fp - item_start);
- newformat[fp - item_start] = 0;
- sprintf(CS p, newformat, va_arg(ap, void *));
- while (*p) p++;
break;
/* %f format is inherently insecure if the numbers that it may be
case 'E':
case 'g':
case 'G':
- if (precision < 0) precision = 6;
- if (p >= last - precision - 8) { yield = FALSE; goto END_FORMAT; }
- strncpy(newformat, item_start, fp - item_start);
- newformat[fp-item_start] = 0;
- if (length == L_LONGDOUBLE)
- sprintf(CS p, newformat, va_arg(ap, long double));
- else
- sprintf(CS p, newformat, va_arg(ap, double));
- while (*p) p++;
- break;
+ if (precision < 0) precision = 6;
+ if (g->ptr >= lim - precision - 8)
+ {
+ if (!extend) return NULL;
+ gstring_grow(g, g->ptr, precision+8);
+ lim = g->size - 1;
+ gp = CS g->s + g->ptr;
+ }
+ strncpy(newformat, item_start, fp - item_start);
+ newformat[fp-item_start] = 0;
+ if (length == L_LONGDOUBLE)
+ g->ptr += sprintf(gp, newformat, va_arg(ap, long double));
+ else
+ g->ptr += sprintf(gp, newformat, va_arg(ap, double));
+ break;
/* String types */
case '%':
- if (p >= last) { yield = FALSE; goto END_FORMAT; }
- *p++ = '%';
- break;
+ if (g->ptr >= lim - 1)
+ {
+ if (!extend) return NULL;
+ gstring_grow(g, g->ptr, 1);
+ lim = g->size - 1;
+ }
+ g->s[g->ptr++] = (uschar) '%';
+ break;
case 'c':
- if (p >= last) { yield = FALSE; goto END_FORMAT; }
- *p++ = va_arg(ap, int);
- break;
+ if (g->ptr >= lim - 1)
+ {
+ if (!extend) return NULL;
+ gstring_grow(g, g->ptr, 1);
+ lim = g->size - 1;
+ }
+ g->s[g->ptr++] = (uschar) va_arg(ap, int);
+ break;
case 'D': /* Insert daily datestamp for log file names */
- s = CS tod_stamp(tod_log_datestamp_daily);
- string_datestamp_offset = p - buffer; /* Passed back via global */
- string_datestamp_length = Ustrlen(s); /* Passed back via global */
- string_datestamp_type = tod_log_datestamp_daily;
- slen = string_datestamp_length;
- goto INSERT_STRING;
+ s = CS tod_stamp(tod_log_datestamp_daily);
+ string_datestamp_offset = g->ptr; /* Passed back via global */
+ string_datestamp_length = Ustrlen(s); /* Passed back via global */
+ string_datestamp_type = tod_log_datestamp_daily;
+ slen = string_datestamp_length;
+ goto INSERT_STRING;
case 'M': /* Insert monthly datestamp for log file names */
- s = CS tod_stamp(tod_log_datestamp_monthly);
- string_datestamp_offset = p - buffer; /* Passed back via global */
- string_datestamp_length = Ustrlen(s); /* Passed back via global */
- string_datestamp_type = tod_log_datestamp_monthly;
- slen = string_datestamp_length;
- goto INSERT_STRING;
+ s = CS tod_stamp(tod_log_datestamp_monthly);
+ string_datestamp_offset = g->ptr; /* Passed back via global */
+ string_datestamp_length = Ustrlen(s); /* Passed back via global */
+ string_datestamp_type = tod_log_datestamp_monthly;
+ slen = string_datestamp_length;
+ goto INSERT_STRING;
case 's':
case 'S': /* Forces *lower* case */
case 'T': /* Forces *upper* case */
- s = va_arg(ap, char *);
+ s = va_arg(ap, char *);
- if (s == NULL) s = null;
- slen = Ustrlen(s);
+ if (!s) s = null;
+ slen = Ustrlen(s);
INSERT_STRING: /* Come to from %D or %M above */
- /* If the width is specified, check that there is a precision
- set; if not, set it to the width to prevent overruns of long
- strings. */
-
- if (width >= 0)
{
- if (precision < 0) precision = width;
- }
+ BOOL truncated = FALSE;
- /* If a width is not specified and the precision is specified, set
- the width to the precision, or the string length if shorted. */
+ /* If the width is specified, check that there is a precision
+ set; if not, set it to the width to prevent overruns of long
+ strings. */
- else if (precision >= 0)
- {
- width = (precision < slen)? precision : slen;
- }
+ if (width >= 0)
+ {
+ if (precision < 0) precision = width;
+ }
- /* If neither are specified, set them both to the string length. */
+ /* If a width is not specified and the precision is specified, set
+ the width to the precision, or the string length if shorted. */
- else width = precision = slen;
+ else if (precision >= 0)
+ width = precision < slen ? precision : slen;
- /* Check string space, and add the string to the buffer if ok. If
- not OK, add part of the string (debugging uses this to show as
- much as possible). */
+ /* If neither are specified, set them both to the string length. */
- if (p == last)
- {
- yield = FALSE;
- goto END_FORMAT;
- }
- if (p >= last - width)
- {
- yield = FALSE;
- width = precision = last - p - 1;
- if (width < 0) width = 0;
- if (precision < 0) precision = 0;
+ else
+ width = precision = slen;
+
+ if (!extend)
+ {
+ if (g->ptr == lim) return NULL;
+ if (g->ptr >= lim - width)
+ {
+ truncated = TRUE;
+ width = precision = lim - g->ptr - 1;
+ if (width < 0) width = 0;
+ if (precision < 0) precision = 0;
+ }
+ }
+ else if (g->ptr >= lim - width)
+ {
+ gstring_grow(g, g->ptr, width - (lim - g->ptr));
+ lim = g->size - 1;
+ gp = CS g->s + g->ptr;
+ }
+
+ g->ptr += sprintf(gp, "%*.*s", width, precision, s);
+ if (fp[-1] == 'S')
+ while (*gp) { *gp = tolower(*gp); gp++; }
+ else if (fp[-1] == 'T')
+ while (*gp) { *gp = toupper(*gp); gp++; }
+
+ if (truncated) return NULL;
+ break;
}
- sprintf(CS p, "%*.*s", width, precision, s);
- if (fp[-1] == 'S')
- while (*p) { *p = tolower(*p); p++; }
- else if (fp[-1] == 'T')
- while (*p) { *p = toupper(*p); p++; }
- else
- while (*p) p++;
- if (!yield) goto END_FORMAT;
- break;
/* Some things are never used in Exim; also catches junk. */
default:
- strncpy(newformat, item_start, fp - item_start);
- newformat[fp-item_start] = 0;
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "string_format: unsupported type "
- "in \"%s\" in \"%s\"", newformat, format);
- break;
+ strncpy(newformat, item_start, fp - item_start);
+ newformat[fp-item_start] = 0;
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "string_format: unsupported type "
+ "in \"%s\" in \"%s\"", newformat, format);
+ break;
}
}
-/* Ensure string is complete; return TRUE if got to the end of the format */
+return g;
+}
-END_FORMAT:
-*p = 0;
-return yield;
+
+#ifndef COMPILE_UTILITY
+
+gstring *
+string_fmt_append(gstring * g, const char *format, ...)
+{
+va_list ap;
+va_start(ap, format);
+g = string_vformat(g, TRUE, format, ap);
+va_end(ap);
+return g;
}
-#ifndef COMPILE_UTILITY
/*************************************************
* Generate an "open failed" message *
*************************************************/
string_open_failed(int eno, const char *format, ...)
{
va_list ap;
-uschar buffer[1024];
+gstring * g = string_get(1024);
-Ustrcpy(buffer, "failed to open ");
-va_start(ap, format);
+g = string_catn(g, US"failed to open ", 15);
/* Use the checked formatting routine to ensure that the buffer
does not overflow. It should not, since this is called only for internally
specified messages. If it does, the message just gets truncated, and there
doesn't seem much we can do about that. */
-(void)string_vformat(buffer+15, sizeof(buffer) - 15, format, ap);
+va_start(ap, format);
+(void) string_vformat(g, FALSE, format, ap);
+string_from_gstring(g);
+gstring_reset_unused(g);
va_end(ap);
-return (eno == EACCES)?
- string_sprintf("%s: %s (euid=%ld egid=%ld)", buffer, strerror(eno),
- (long int)geteuid(), (long int)getegid()) :
- string_sprintf("%s: %s", buffer, strerror(eno));
+return eno == EACCES
+ ? string_sprintf("%s: %s (euid=%ld egid=%ld)", g->s, strerror(eno),
+ (long int)geteuid(), (long int)getegid())
+ : string_sprintf("%s: %s", g->s, strerror(eno));
}
#endif /* COMPILE_UTILITY */
+
/*************************************************
**************************************************
* Stand-alone test program *
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
struct transport_info;
struct router_info;
+/* Growable-string */
+typedef struct gstring {
+ int size; /* Current capacity of string memory */
+ int ptr; /* Offset at which to append further chars */
+ uschar * s; /* The string memory */
+} gstring;
+
/* Structure for remembering macros for the configuration file */
typedef struct macro_item {
- struct macro_item *next;
- BOOL command_line;
- unsigned namelen;
- uschar * replacement;
- uschar name[1];
+ struct macro_item * next;
+ BOOL command_line;
+ unsigned namelen;
+ unsigned replen;
+ const uschar * name;
+ const uschar * replacement;
} macro_item;
/* Structure for bit tables for debugging and logging */
CHUNKING_ACTIVE,
CHUNKING_LAST} chunking_state_t;
+typedef enum { TFO_NOT_USED = 0,
+ TFO_ATTEMPTED_NODATA,
+ TFO_ATTEMPTED_DATA,
+ TFO_USED_NODATA,
+ TFO_USED_DATA } tfo_state_t;
+
/* Structure for holding information about a host for use mainly by routers,
but also used when checking lists of hosts and when transporting. Looking up
host addresses is done using this structure. */
#define tc_chunk_last BIT(1) /* annotate chunk SMTP cmd as LAST */
struct transport_context;
-typedef int (*tpt_chunk_cmd_cb)(int fd, struct transport_context * tctx,
- unsigned len, unsigned flags);
+typedef int (*tpt_chunk_cmd_cb)(struct transport_context *, unsigned, unsigned);
/* Structure for information about a delivery-in-progress */
typedef struct transport_context {
+ union { /* discriminated by option topt_output_string */
+ int fd; /* file descriptor to write message to */
+ gstring * msg; /* allocated string with written message */
+ } u;
transport_instance * tblock; /* transport */
struct address_item * addr;
uschar * check_string; /* string replacement */
uschar *); /* rest of AUTH command */
int (*clientcode)( /* client function */
struct auth_instance *,
- struct smtp_inblock *, /* socket and input buffer */
- struct smtp_outblock *, /* socket and output buffer */
+ void *, /* smtp conn, with socket, output and input buffers */
int, /* command timeout */
uschar *, /* buffer for reading response */
int); /* sizeof buffer */
#ifdef EXPERIMENTAL_SRS
uschar *srs_sender; /* Change return path when delivering */
#endif
+ BOOL ignore_error:1; /* ignore delivery error */
#ifdef SUPPORT_I18N
BOOL utf8_msg:1; /* requires SMTPUTF8 processing */
BOOL utf8_downcvt:1; /* mandatory downconvert on delivery */
#endif
} address_item_propagated;
-/* Bits for the flags field below */
-
-#define af_allow_file 0x00000001 /* allow file in generated address */
-#define af_allow_pipe 0x00000002 /* allow pipe in generated address */
-#define af_allow_reply 0x00000004 /* allow autoreply in generated address */
-#define af_dr_retry_exists 0x00000008 /* router retry record exists */
-#define af_expand_pipe 0x00000010 /* expand pipe arguments */
-#define af_file 0x00000020 /* file delivery; always with pfr */
-#define af_gid_set 0x00000040 /* gid field is set */
-#define af_home_expanded 0x00000080 /* home_dir is already expanded */
-#define af_ignore_error 0x00000100 /* ignore delivery error */
-#define af_initgroups 0x00000200 /* use initgroups() for local transporting */
-#define af_local_host_removed 0x00000400 /* local host was backup */
-#define af_lt_retry_exists 0x00000800 /* local transport retry exists */
-#define af_pfr 0x00001000 /* pipe or file or reply delivery */
-#define af_retry_skipped 0x00002000 /* true if retry caused some skipping */
-#define af_retry_timedout 0x00004000 /* true if retry timed out */
-#define af_uid_set 0x00008000 /* uid field is set */
-#define af_hide_child 0x00010000 /* hide child in bounce/defer msgs */
-#define af_sverify_told 0x00020000 /* sender verify failure notified */
-#define af_verify_pmfail 0x00040000 /* verify failure was postmaster callout */
-#define af_verify_nsfail 0x00080000 /* verify failure was null sender callout */
-#define af_homonym 0x00100000 /* an ancestor has same address */
-#define af_verify_routed 0x00200000 /* for cached sender verify: routed OK */
-#define af_verify_callout 0x00400000 /* for cached sender verify: callout was specified */
-#define af_include_affixes 0x00800000 /* delivered with affixes in RCPT */
-#define af_cert_verified 0x01000000 /* delivered with verified TLS cert */
-#define af_pass_message 0x02000000 /* pass message in bounces */
-#define af_bad_reply 0x04000000 /* filter could not generate autoreply */
-#ifndef DISABLE_PRDR
-# define af_prdr_used 0x08000000 /* delivery used SMTP PRDR */
-#endif
-#define af_chunking_used 0x10000000 /* delivery used SMTP CHUNKING */
-#define af_force_command 0x20000000 /* force_command in pipe transport */
-#ifdef EXPERIMENTAL_DANE
-# define af_dane_verified 0x40000000 /* TLS cert verify done with DANE */
-#endif
-#ifdef SUPPORT_I18N
-# define af_utf8_downcvt 0x80000000 /* downconvert was done for delivery */
-#endif
-
-/* These flags must be propagated when a child is created */
-
-#define af_propagate (af_ignore_error)
/* The main address structure. Note that fields that are to be copied to
generated addresses should be put in the address_item_propagated structure (see
uid_t uid; /* uid for transporting */
gid_t gid; /* gid for transporting */
- unsigned int flags; /* a row of bits, defined above */
+ /* flags */
+ struct {
+ BOOL af_allow_file:1; /* allow file in generated address */
+ BOOL af_allow_pipe:1; /* allow pipe in generated address */
+ BOOL af_allow_reply:1; /* allow autoreply in generated address */
+ BOOL af_dr_retry_exists:1; /* router retry record exists */
+ BOOL af_expand_pipe:1; /* expand pipe arguments */
+ BOOL af_file:1; /* file delivery; always with pfr */
+ BOOL af_gid_set:1; /* gid field is set */
+ BOOL af_home_expanded:1; /* home_dir is already expanded */
+ BOOL af_initgroups:1; /* use initgroups() for local transporting */
+ BOOL af_local_host_removed:1; /* local host was backup */
+ BOOL af_lt_retry_exists:1; /* local transport retry exists */
+ BOOL af_pfr:1; /* pipe or file or reply delivery */
+ BOOL af_retry_skipped:1; /* true if retry caused some skipping */
+ BOOL af_retry_timedout:1; /* true if retry timed out */
+ BOOL af_uid_set:1; /* uid field is set */
+ BOOL af_hide_child:1; /* hide child in bounce/defer msgs */
+ BOOL af_sverify_told:1; /* sender verify failure notified */
+ BOOL af_verify_pmfail:1; /* verify failure was postmaster callout */
+ BOOL af_verify_nsfail:1; /* verify failure was null sender callout */
+ BOOL af_homonym:1; /* an ancestor has same address */
+ BOOL af_verify_routed:1; /* for cached sender verify: routed OK */
+ BOOL af_verify_callout:1; /* for cached sender verify: callout was specified */
+ BOOL af_include_affixes:1; /* delivered with affixes in RCPT */
+ BOOL af_cert_verified:1; /* delivered with verified TLS cert */
+ BOOL af_pass_message:1; /* pass message in bounces */
+ BOOL af_bad_reply:1; /* filter could not generate autoreply */
+ BOOL af_tcp_fastopen_conn:1; /* delivery connection used TCP Fast Open */
+ BOOL af_tcp_fastopen:1; /* delivery usefully used TCP Fast Open */
+ BOOL af_tcp_fastopen_data:1; /* delivery sent SMTP commands on TCP Fast Open */
+ BOOL af_pipelining:1; /* delivery used (traditional) pipelining */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL af_early_pipe:1; /* delivery used connect-time pipelining */
+#endif
+#ifndef DISABLE_PRDR
+ BOOL af_prdr_used:1; /* delivery used SMTP PRDR */
+#endif
+ BOOL af_chunking_used:1; /* delivery used SMTP CHUNKING */
+ BOOL af_force_command:1; /* force_command in pipe transport */
+#ifdef SUPPORT_DANE
+ BOOL af_dane_verified:1; /* TLS cert verify done with DANE */
+#endif
+#ifdef SUPPORT_I18N
+ BOOL af_utf8_downcvt:1; /* downconvert was done for delivery */
+#endif
+ } flags;
+
unsigned int domain_cache[(MAX_NAMED_LIST * 2)/32];
unsigned int localpart_cache[(MAX_NAMED_LIST * 2)/32];
int mode; /* mode for local transporting to a file */
int more_errno; /* additional error information */
/* (may need to hold a timestamp) */
+ unsigned int delivery_usec; /* subsecond part of delivery time */
short int basic_errno; /* status after failure */
unsigned short child_count; /* number of child addresses */
const uschar *data; /* pointer to data */
} dns_record;
-/* Structure for holding the result of a DNS query. */
+/* Structure for holding the result of a DNS query. A touch over
+64k big, so take care to release as soon as possible. */
typedef struct {
int answerlen; /* length of the answer */
- uschar answer[MAXPACKET]; /* the answer itself */
+ uschar answer[NS_MAXMSG]; /* the answer itself */
} dns_answer;
/* Structure for holding the intermediate data while scanning a DNS answer
typedef struct sha1 {
unsigned int H[5];
unsigned int length;
- }
-sha1;
+} sha1;
+
+/* Information for making an smtp connection */
+typedef struct {
+ transport_instance * tblock;
+ void * ob; /* smtp_transport_options_block * */
+ host_item * host;
+ int host_af;
+ uschar * interface;
+} smtp_connect_args;
+
+/* A client-initiated connection. If TLS, the second element is non-NULL */
+typedef struct {
+ int sock;
+ void * tls_ctx;
+} client_conn_ctx;
+
/* Structure used to hold incoming packets of SMTP responses for a specific
socket. The packets which may contain multiple lines (and in some cases,
multiple responses). */
typedef struct smtp_inblock {
- int sock; /* the socket */
+ client_conn_ctx * cctx; /* the connection */
int buffersize; /* the size of the buffer */
uschar *ptr; /* current position in the buffer */
uschar *ptrend; /* end of data in the buffer */
is in use. */
typedef struct smtp_outblock {
- int sock; /* the socket */
+ client_conn_ctx * cctx; /* the connection */
int cmd_count; /* count of buffered commands */
int buffersize; /* the size of the buffer */
BOOL authenticating; /* TRUE when authenticating */
uschar *ptr; /* current position in the buffer */
uschar *buffer; /* the buffer itself */
+
+ smtp_connect_args * conn_args; /* to make connection, if not yet made */
} smtp_outblock;
/* Structure to hold information about the source of redirection information */
/* DKIM information for transport */
struct ob_dkim {
uschar *dkim_domain;
+ uschar *dkim_identity;
uschar *dkim_private_key;
uschar *dkim_selector;
uschar *dkim_canon;
uschar *dkim_sign_headers;
uschar *dkim_strict;
+ uschar *dkim_hash;
+ uschar *dkim_timestamps;
BOOL dot_stuffed;
+ BOOL force_bodyhash;
+#ifdef EXPERIMENTAL_ARC
+ uschar *arc_signspec;
+#endif
};
/* End of structs.h */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) Phil Pennock 2012 */
#include <gnutls/x509.h>
/* man-page is incorrect, gnutls_rnd() is not in gnutls.h: */
#include <gnutls/crypto.h>
+
/* needed to disable PKCS11 autoload unless requested */
#if GNUTLS_VERSION_NUMBER >= 0x020c00
# include <gnutls/pkcs11.h>
#if GNUTLS_VERSION_NUMBER >= 0x030014
# define SUPPORT_SYSDEFAULT_CABUNDLE
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030104
+# define GNUTLS_CERT_VFY_STATUS_PRINT
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030109
+# define SUPPORT_CORK
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
+# define SUPPORT_SRV_OCSP_STACK
+#endif
+
+#ifdef SUPPORT_DANE
+# if GNUTLS_VERSION_NUMBER >= 0x030000
+# define DANESSL_USAGE_DANE_TA 2
+# define DANESSL_USAGE_DANE_EE 3
+# else
+# error GnuTLS version too early for DANE
+# endif
+# if GNUTLS_VERSION_NUMBER < 0x999999
+# define GNUTLS_BROKEN_DANE_VALIDATION
+# endif
+#endif
#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
#endif
+#ifdef SUPPORT_DANE
+# include <gnutls/dane.h>
+#endif
/* GnuTLS 2 vs 3
/* Values for verify_requirement */
enum peer_verify_requirement
- { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
+ { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED, VERIFY_DANE };
/* This holds most state for server or client; with this, we can set up an
outbound TLS-enabled connection in an ACL callout, while not stomping all
int fd_in;
int fd_out;
BOOL peer_cert_verified;
+ BOOL peer_dane_verified;
BOOL trigger_sni_changes;
BOOL have_set_peerdn;
- const struct host_item *host;
- gnutls_x509_crt_t peercert;
+ const struct host_item *host; /* NULL if server */
+ gnutls_x509_crt_t peercert;
uschar *peerdn;
uschar *ciphersuite;
uschar *received_sni;
uschar *exp_tls_verify_certificates;
uschar *exp_tls_crl;
uschar *exp_tls_require_ciphers;
- uschar *exp_tls_ocsp_file;
const uschar *exp_tls_verify_cert_hostnames;
#ifndef DISABLE_EVENT
uschar *event_action;
#endif
+#ifdef SUPPORT_DANE
+ char * const * dane_data;
+ const int * dane_data_len;
+#endif
tls_support *tlsp; /* set in tls_init() */
uschar *xfer_buffer;
int xfer_buffer_lwm;
int xfer_buffer_hwm;
- int xfer_eof;
- int xfer_error;
+ BOOL xfer_eof; /*XXX never gets set! */
+ BOOL xfer_error;
} exim_gnutls_state_st;
static const exim_gnutls_state_st exim_gnutls_state_init = {
- NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE,
- NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL, NULL,
- NULL,
+ .session = NULL,
+ .x509_cred = NULL,
+ .priority_cache = NULL,
+ .verify_requirement = VERIFY_NONE,
+ .fd_in = -1,
+ .fd_out = -1,
+ .peer_cert_verified = FALSE,
+ .peer_dane_verified = FALSE,
+ .trigger_sni_changes =FALSE,
+ .have_set_peerdn = FALSE,
+ .host = NULL,
+ .peercert = NULL,
+ .peerdn = NULL,
+ .ciphersuite = NULL,
+ .received_sni = NULL,
+
+ .tls_certificate = NULL,
+ .tls_privatekey = NULL,
+ .tls_sni = NULL,
+ .tls_verify_certificates = NULL,
+ .tls_crl = NULL,
+ .tls_require_ciphers =NULL,
+
+ .exp_tls_certificate = NULL,
+ .exp_tls_privatekey = NULL,
+ .exp_tls_verify_certificates = NULL,
+ .exp_tls_crl = NULL,
+ .exp_tls_require_ciphers = NULL,
+ .exp_tls_verify_cert_hostnames = NULL,
#ifndef DISABLE_EVENT
- NULL,
+ .event_action = NULL,
#endif
- NULL,
- NULL, 0, 0, 0, 0,
+ .tlsp = NULL,
+
+ .xfer_buffer = NULL,
+ .xfer_buffer_lwm = 0,
+ .xfer_buffer_hwm = 0,
+ .xfer_eof = FALSE,
+ .xfer_error = FALSE,
};
/* Not only do we have our own APIs which don't pass around state, assuming
XXX But see gnutls_session_get_ptr()
*/
-static exim_gnutls_state_st state_server, state_client;
+static exim_gnutls_state_st state_server;
/* dh_params are initialised once within the lifetime of a process using TLS;
if we used TLS in a long-lived daemon, we'd have to reconsider this. But we
/* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup
the library logging; a value less than 0 disables the calls to set up logging
-callbacks. */
+callbacks. Possibly GNuTLS also looks for an environment variable
+"GNUTLS_DEBUG_LEVEL". */
#ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL
# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1
#endif
# define EXIM_SERVER_DH_BITS_PRE2_12 1024
#endif
-#define exim_gnutls_err_check(Label) do { \
- if (rc != GNUTLS_E_SUCCESS) { return tls_error((Label), gnutls_strerror(rc), host); } } while (0)
+#define exim_gnutls_err_check(rc, Label) do { \
+ if ((rc) != GNUTLS_E_SUCCESS) \
+ return tls_error((Label), US gnutls_strerror(rc), host, errstr); \
+ } while (0)
-#define expand_check_tlsvar(Varname) expand_check(state->Varname, US #Varname, &state->exp_##Varname)
+#define expand_check_tlsvar(Varname, errstr) \
+ expand_check(state->Varname, US #Varname, &state->exp_##Varname, errstr)
#if GNUTLS_VERSION_NUMBER >= 0x020c00
# define HAVE_GNUTLS_SESSION_CHANNEL_BINDING
usually obtained from gnutls_strerror()
host NULL if setting up a server;
the connected host if setting up a client
+ errstr pointer to returned error string
Returns: OK/DEFER/FAIL
*/
static int
-tls_error(const uschar *prefix, const char *msg, const host_item *host)
+tls_error(const uschar *prefix, const uschar *msg, const host_item *host,
+ uschar ** errstr)
{
-if (host)
- {
- log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s)%s%s",
- host->name, host->address, prefix, msg ? ": " : "", msg ? msg : "");
- return FAIL;
- }
-else
- {
- uschar *conn_info = smtp_get_connection_info();
- if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
- conn_info += 5;
- /* I'd like to get separated H= here, but too hard for now */
- log_write(0, LOG_MAIN, "TLS error on %s (%s)%s%s",
- conn_info, prefix, msg ? ": " : "", msg ? msg : "");
- return DEFER;
- }
+if (errstr)
+ *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : US"");
+return host ? FAIL : DEFER;
}
static void
record_io_error(exim_gnutls_state_st *state, int rc, uschar *when, uschar *text)
{
-const char *msg;
+const uschar * msg;
+uschar * errstr;
if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
- msg = CS string_sprintf("%s: %s", US gnutls_strerror(rc),
+ msg = string_sprintf("%s: %s", US gnutls_strerror(rc),
US gnutls_alert_get_name(gnutls_alert_get(state->session)));
else
- msg = gnutls_strerror(rc);
+ msg = US gnutls_strerror(rc);
-tls_error(when, msg, state->host);
+(void) tls_error(when, msg, state->host, &errstr);
+
+if (state->host)
+ log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection %s",
+ state->host->name, state->host->address, errstr);
+else
+ {
+ uschar * conn_info = smtp_get_connection_info();
+ if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5;
+ /* I'd like to get separated H= here, but too hard for now */
+ log_write(0, LOG_MAIN, "TLS error on %s %s", conn_info, errstr);
+ }
}
#endif
tls_support * tlsp = state->tlsp;
-tlsp->active = state->fd_out;
+tlsp->active.sock = state->fd_out;
+tlsp->active.tls_ctx = state;
cipher = gnutls_cipher_get(state->session);
/* returns size in "bytes" */
DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
tlsp->certificate_verified = state->peer_cert_verified;
+#ifdef SUPPORT_DANE
+tlsp->dane_verified = state->peer_dane_verified;
+#endif
/* note that tls_channelbinding_b64 is not saved to the spool file, since it's
only available for use for authenticators while this TLS session is running. */
*/
static int
-init_server_dh(void)
+init_server_dh(uschar ** errstr)
{
int fd, rc;
unsigned int dh_bits;
DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n");
rc = gnutls_dh_params_init(&dh_server_params);
-exim_gnutls_err_check(US"gnutls_dh_params_init");
+exim_gnutls_err_check(rc, US"gnutls_dh_params_init");
m.data = NULL;
m.size = 0;
-if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam))
+if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr))
return DEFER;
if (!exp_tls_dhparam)
else if (exp_tls_dhparam[0] != '/')
{
if (!(m.data = US std_dh_prime_named(exp_tls_dhparam)))
- return tls_error(US"No standard prime named", CS exp_tls_dhparam, NULL);
+ return tls_error(US"No standard prime named", exp_tls_dhparam, NULL, errstr);
m.size = Ustrlen(m.data);
}
else
if (m.data)
{
rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
- exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3");
DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n");
return OK;
}
different filename and ensure we have sufficient bits. */
dh_bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_NORMAL);
if (!dh_bits)
- return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL);
+ return tls_error(US"gnutls_sec_param_to_pk_bits() failed", NULL, NULL, errstr);
DEBUG(D_tls)
debug_printf("GnuTLS tells us that for D-H PK, NORMAL is %d bits.\n",
dh_bits);
{
if (!string_format(filename_buf, sizeof(filename_buf),
"%s/gnutls-params-%d", spool_directory, dh_bits))
- return tls_error(US"overlong filename", NULL, NULL);
+ return tls_error(US"overlong filename", NULL, NULL, errstr);
filename = filename_buf;
}
{
saved_errno = errno;
(void)close(fd);
- return tls_error(US"TLS cache stat failed", strerror(saved_errno), NULL);
+ return tls_error(US"TLS cache stat failed", US strerror(saved_errno), NULL, errstr);
}
if (!S_ISREG(statbuf.st_mode))
{
(void)close(fd);
- return tls_error(US"TLS cache not a file", NULL, NULL);
+ return tls_error(US"TLS cache not a file", NULL, NULL, errstr);
}
if (!(fp = fdopen(fd, "rb")))
{
saved_errno = errno;
(void)close(fd);
return tls_error(US"fdopen(TLS cache stat fd) failed",
- strerror(saved_errno), NULL);
+ US strerror(saved_errno), NULL, errstr);
}
m.size = statbuf.st_size;
if (!(m.data = malloc(m.size)))
{
fclose(fp);
- return tls_error(US"malloc failed", strerror(errno), NULL);
+ return tls_error(US"malloc failed", US strerror(errno), NULL, errstr);
}
if (!(sz = fread(m.data, m.size, 1, fp)))
{
saved_errno = errno;
fclose(fp);
free(m.data);
- return tls_error(US"fread failed", strerror(saved_errno), NULL);
+ return tls_error(US"fread failed", US strerror(saved_errno), NULL, errstr);
}
fclose(fp);
rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM);
free(m.data);
- exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3");
DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename);
}
}
else
return tls_error(string_open_failed(errno, "\"%s\" for reading", filename),
- NULL, NULL);
+ NULL, NULL, errstr);
/* If ret < 0, either the cache file does not exist, or the data it contains
is not useful. One particular case of this is when upgrading from an older
if ((PATH_MAX - Ustrlen(filename)) < 10)
return tls_error(US"Filename too long to generate replacement",
- CS filename, NULL);
+ filename, NULL, errstr);
- temp_fn = string_copy(US "%s.XXXXXXX");
+ temp_fn = string_copy(US"%s.XXXXXXX");
if ((fd = mkstemp(CS temp_fn)) < 0) /* modifies temp_fn */
- return tls_error(US"Unable to open temp file", strerror(errno), NULL);
+ return tls_error(US"Unable to open temp file", US strerror(errno), NULL, errstr);
(void)fchown(fd, exim_uid, exim_gid); /* Probably not necessary */
/* GnuTLS overshoots!
debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
dh_bits_gen);
rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen);
- exim_gnutls_err_check(US"gnutls_dh_params_generate2");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_generate2");
/* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,
and I confirmed that a NULL call to get the size first is how the GnuTLS
rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
m.data, &sz);
if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
- exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3(NULL) sizing");
m.size = sz;
if (!(m.data = malloc(m.size)))
- return tls_error(US"memory allocation failed", strerror(errno), NULL);
+ return tls_error(US"memory allocation failed", US strerror(errno), NULL, errstr);
/* this will return a size 1 less than the allocation size above */
rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM,
if (rc != GNUTLS_E_SUCCESS)
{
free(m.data);
- exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3() real");
+ exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3() real");
}
m.size = sz; /* shrink by 1, probably */
{
free(m.data);
return tls_error(US"TLS cache write D-H params failed",
- strerror(errno), NULL);
+ US strerror(errno), NULL, errstr);
}
free(m.data);
if ((sz = write_to_fd_buf(fd, US"\n", 1)) != 1)
return tls_error(US"TLS cache write D-H params final newline failed",
- strerror(errno), NULL);
+ US strerror(errno), NULL, errstr);
if ((rc = close(fd)))
- return tls_error(US"TLS cache write close() failed", strerror(errno), NULL);
+ return tls_error(US"TLS cache write close() failed", US strerror(errno), NULL, errstr);
if (Urename(temp_fn, filename) < 0)
return tls_error(string_sprintf("failed to rename \"%s\" as \"%s\"",
- temp_fn, filename), strerror(errno), NULL);
+ temp_fn, filename), US strerror(errno), NULL, errstr);
DEBUG(D_tls) debug_printf("wrote D-H parameters to file \"%s\"\n", filename);
}
/* Create and install a selfsigned certificate, for use in server mode */
static int
-tls_install_selfsign(exim_gnutls_state_st * state)
+tls_install_selfsign(exim_gnutls_state_st * state, uschar ** errstr)
{
gnutls_x509_crt_t cert = NULL;
time_t now;
where = US"generating pkey";
if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA,
#ifdef SUPPORT_PARAM_TO_PK_BITS
- gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_LOW),
+# ifndef GNUTLS_SEC_PARAM_MEDIUM
+# define GNUTLS_SEC_PARAM_MEDIUM GNUTLS_SEC_PARAM_HIGH
+# endif
+ gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_MEDIUM),
#else
- 1024,
+ 2048,
#endif
0)))
goto err;
where = US"configuring cert";
-now = 0;
+now = 1;
if ( (rc = gnutls_x509_crt_set_version(cert, 3))
|| (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
|| (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
return rc;
err:
- rc = tls_error(where, gnutls_strerror(rc), NULL);
+ rc = tls_error(where, US gnutls_strerror(rc), NULL, errstr);
goto out;
}
+/* Add certificate and key, from files.
+
+Return:
+ Zero or negative: good. Negate value for certificate index if < 0.
+ Greater than zero: FAIL or DEFER code.
+*/
+
+static int
+tls_add_certfile(exim_gnutls_state_st * state, const host_item * host,
+ uschar * certfile, uschar * keyfile, uschar ** errstr)
+{
+int rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
+ CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM);
+if (rc < 0)
+ return tls_error(
+ string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile),
+ US gnutls_strerror(rc), host, errstr);
+return -rc;
+}
+
+
/*************************************************
* Variables re-expanded post-SNI *
*************************************************/
Arguments:
state exim_gnutls_state_st *
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
static int
-tls_expand_session_files(exim_gnutls_state_st *state)
+tls_expand_session_files(exim_gnutls_state_st * state, uschar ** errstr)
{
struct stat statbuf;
int rc;
if (!host) /* server */
if (!state->received_sni)
{
- if (state->tls_certificate &&
- (Ustrstr(state->tls_certificate, US"tls_sni") ||
- Ustrstr(state->tls_certificate, US"tls_in_sni") ||
- Ustrstr(state->tls_certificate, US"tls_out_sni")
- ))
+ if ( state->tls_certificate
+ && ( Ustrstr(state->tls_certificate, US"tls_sni")
+ || Ustrstr(state->tls_certificate, US"tls_in_sni")
+ || Ustrstr(state->tls_certificate, US"tls_out_sni")
+ ) )
{
DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n");
state->trigger_sni_changes = TRUE;
}
rc = gnutls_certificate_allocate_credentials(&state->x509_cred);
-exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials");
+exim_gnutls_err_check(rc, US"gnutls_certificate_allocate_credentials");
+
+#ifdef SUPPORT_SRV_OCSP_STACK
+gnutls_certificate_set_flags(state->x509_cred, GNUTLS_CERTIFICATE_API_V2);
+#endif
/* remember: expand_check_tlsvar() is expand_check() but fiddling with
state members, assuming consistent naming; and expand_check() returns
/* check if we at least have a certificate, before doing expensive
D-H generation. */
-if (!expand_check_tlsvar(tls_certificate))
+if (!expand_check_tlsvar(tls_certificate, errstr))
return DEFER;
/* certificate is mandatory in server, optional in client */
|| !*state->exp_tls_certificate
)
if (!host)
- return tls_install_selfsign(state);
+ return tls_install_selfsign(state, errstr);
else
DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
-if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey))
+if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey, errstr))
return DEFER;
/* tls_privatekey is optional, defaulting to same file as certificate */
DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n");
}
- rc = gnutls_certificate_set_x509_key_file(state->x509_cred,
- CS state->exp_tls_certificate, CS state->exp_tls_privatekey,
- GNUTLS_X509_FMT_PEM);
- exim_gnutls_err_check(
- string_sprintf("cert/key setup: cert=%s key=%s",
- state->exp_tls_certificate, state->exp_tls_privatekey));
- DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
- } /* tls_certificate */
+ if (!host) /* server */
+ {
+ const uschar * clist = state->exp_tls_certificate;
+ const uschar * klist = state->exp_tls_privatekey;
+ const uschar * olist;
+ int csep = 0, ksep = 0, osep = 0, cnt = 0;
+ uschar * cfile, * kfile, * ofile;
+
+#ifndef DISABLE_OCSP
+ if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &ofile, errstr))
+ return DEFER;
+ olist = ofile;
+#endif
+ while (cfile = string_nextinlist(&clist, &csep, NULL, 0))
+
+ if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0)))
+ return tls_error(US"cert/key setup: out of keys", NULL, host, errstr);
+ else if (0 < (rc = tls_add_certfile(state, host, cfile, kfile, errstr)))
+ return rc;
+ else
+ {
+ int gnutls_cert_index = -rc;
+ DEBUG(D_tls) debug_printf("TLS: cert/key %s registered\n", cfile);
-/* Set the OCSP stapling server info */
+ /* Set the OCSP stapling server info */
#ifndef DISABLE_OCSP
-if ( !host /* server */
- && tls_ocsp_file
- )
- {
- if (gnutls_buggy_ocsp)
- {
- DEBUG(D_tls) debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
+ if (tls_ocsp_file)
+ if (gnutls_buggy_ocsp)
+ {
+ DEBUG(D_tls)
+ debug_printf("GnuTLS library is buggy for OCSP; avoiding\n");
+ }
+ else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0)))
+ {
+ /* Use the full callback method for stapling just to get
+ observability. More efficient would be to read the file once only,
+ if it never changed (due to SNI). Would need restart on file update,
+ or watch datestamp. */
+
+# ifdef SUPPORT_SRV_OCSP_STACK
+ rc = gnutls_certificate_set_ocsp_status_request_function2(
+ state->x509_cred, gnutls_cert_index,
+ server_ocsp_stapling_cb, ofile);
+
+ exim_gnutls_err_check(rc,
+ US"gnutls_certificate_set_ocsp_status_request_function2");
+# else
+ if (cnt++ > 0)
+ {
+ DEBUG(D_tls)
+ debug_printf("oops; multiple OCSP files not supported\n");
+ break;
+ }
+ gnutls_certificate_set_ocsp_status_request_function(
+ state->x509_cred, server_ocsp_stapling_cb, ofile);
+# endif
+
+ DEBUG(D_tls) debug_printf("OCSP response file = %s\n", ofile);
+ }
+ else
+ DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n");
+#endif
+ }
}
else
{
- if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
- &state->exp_tls_ocsp_file))
- return DEFER;
-
- /* Use the full callback method for stapling just to get observability.
- More efficient would be to read the file once only, if it never changed
- (due to SNI). Would need restart on file update, or watch datestamp. */
-
- gnutls_certificate_set_ocsp_status_request_function(state->x509_cred,
- server_ocsp_stapling_cb, state->exp_tls_ocsp_file);
-
- DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file);
+ if (0 < (rc = tls_add_certfile(state, host,
+ state->exp_tls_certificate, state->exp_tls_privatekey, errstr)))
+ return rc;
+ DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
}
- }
-#endif
+
+ } /* tls_certificate */
/* Set the trusted CAs file if one is provided, and then add the CRL if one is
if (state->tls_verify_certificates && *state->tls_verify_certificates)
{
- if (!expand_check_tlsvar(tls_verify_certificates))
+ if (!expand_check_tlsvar(tls_verify_certificates, errstr))
return DEFER;
#ifndef SUPPORT_SYSDEFAULT_CABUNDLE
if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0)
state->exp_tls_verify_certificates = NULL;
#endif
if (state->tls_crl && *state->tls_crl)
- if (!expand_check_tlsvar(tls_crl))
+ if (!expand_check_tlsvar(tls_crl, errstr))
return DEFER;
if (!(state->exp_tls_verify_certificates &&
if (cert_count < 0)
{
rc = cert_count;
- exim_gnutls_err_check(US"setting certificate trust");
+ exim_gnutls_err_check(rc, US"setting certificate trust");
}
DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count);
if (cert_count < 0)
{
rc = cert_count;
- exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file");
+ exim_gnutls_err_check(rc, US"gnutls_certificate_set_x509_crl_file");
}
DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count);
}
Arguments:
state exim_gnutls_state_st *
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
static int
-tls_set_remaining_x509(exim_gnutls_state_st *state)
+tls_set_remaining_x509(exim_gnutls_state_st *state, uschar ** errstr)
{
int rc;
const host_item *host = state->host; /* macro should be reconsidered? */
{
if (!dh_server_params)
{
- rc = init_server_dh();
+ rc = init_server_dh(errstr);
if (rc != OK) return rc;
}
gnutls_certificate_set_dh_params(state->x509_cred, dh_server_params);
/* Link the credentials to the session. */
rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred);
-exim_gnutls_err_check(US"gnutls_credentials_set");
+exim_gnutls_err_check(rc, US"gnutls_credentials_set");
return OK;
}
crl CRL file
require_ciphers tls_require_ciphers setting
caller_state returned state-info structure
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
const uschar *cas,
const uschar *crl,
const uschar *require_ciphers,
- exim_gnutls_state_st **caller_state)
+ exim_gnutls_state_st **caller_state,
+ tls_support * tlsp,
+ uschar ** errstr)
{
exim_gnutls_state_st *state;
int rc;
if (!gnutls_allow_auto_pkcs11)
{
rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
- exim_gnutls_err_check(US"gnutls_pkcs11_init");
+ exim_gnutls_err_check(rc, US"gnutls_pkcs11_init");
}
#endif
rc = gnutls_global_init();
- exim_gnutls_err_check(US"gnutls_global_init");
+ exim_gnutls_err_check(rc, US"gnutls_global_init");
#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
DEBUG(D_tls)
{
gnutls_global_set_log_function(exim_gnutls_logger_cb);
- /* arbitrarily chosen level; bump upto 9 for more */
+ /* arbitrarily chosen level; bump up to 9 for more */
gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
}
#endif
if (host)
{
- state = &state_client;
+ /* For client-side sessions we allocate a context. This lets us run
+ several in parallel. */
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
+ state = store_get(sizeof(exim_gnutls_state_st));
+ store_pool = old_pool;
+
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
- state->tlsp = &tls_out;
+ state->tlsp = tlsp;
DEBUG(D_tls) debug_printf("initialising GnuTLS client session\n");
rc = gnutls_init(&state->session, GNUTLS_CLIENT);
}
{
state = &state_server;
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
- state->tlsp = &tls_in;
+ state->tlsp = tlsp;
DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n");
rc = gnutls_init(&state->session, GNUTLS_SERVER);
}
-exim_gnutls_err_check(US"gnutls_init");
+exim_gnutls_err_check(rc, US"gnutls_init");
state->host = host;
DEBUG(D_tls)
debug_printf("Expanding various TLS configuration options for session credentials.\n");
-rc = tls_expand_session_files(state);
-if (rc != OK) return rc;
+if ((rc = tls_expand_session_files(state, errstr)) != OK) return rc;
/* These are all other parts of the x509_cred handling, since SNI in GnuTLS
requires a new structure afterwards. */
-rc = tls_set_remaining_x509(state);
-if (rc != OK) return rc;
+if ((rc = tls_set_remaining_x509(state, errstr)) != OK) return rc;
/* set SNI in client, only */
if (host)
{
- if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni))
+ if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni, errstr))
return DEFER;
if (state->tlsp->sni && *state->tlsp->sni)
{
sz = Ustrlen(state->tlsp->sni);
rc = gnutls_server_name_set(state->session,
GNUTLS_NAME_DNS, state->tlsp->sni, sz);
- exim_gnutls_err_check(US"gnutls_server_name_set");
+ exim_gnutls_err_check(rc, US"gnutls_server_name_set");
}
}
else if (state->tls_sni)
DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \
- "have an SNI set for a client [%s]\n", state->tls_sni);
+ "have an SNI set for a server [%s]\n", state->tls_sni);
/* This is the priority string support,
http://www.gnutls.org/manual/html_node/Priority-Strings.html
if (state->tls_require_ciphers && *state->tls_require_ciphers)
{
- if (!expand_check_tlsvar(tls_require_ciphers))
+ if (!expand_check_tlsvar(tls_require_ciphers, errstr))
return DEFER;
if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
{
p = US exim_default_gnutls_priority;
}
-exim_gnutls_err_check(string_sprintf(
+exim_gnutls_err_check(rc, string_sprintf(
"gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
p, errpos - CS p, errpos));
rc = gnutls_priority_set(state->session, state->priority_cache);
-exim_gnutls_err_check(US"gnutls_priority_set");
+exim_gnutls_err_check(rc, US"gnutls_priority_set");
gnutls_db_set_cache_expiration(state->session, ssl_session_timeout);
Arguments:
state exim_gnutls_state_st *
+ errstr pointer to error string
Returns: OK/DEFER/FAIL
*/
static int
-peer_status(exim_gnutls_state_st *state)
+peer_status(exim_gnutls_state_st *state, uschar ** errstr)
{
uschar cipherbuf[256];
const gnutls_datum_t *cert_list;
cert_list, cert_list_size);
if (state->verify_requirement >= VERIFY_REQUIRED)
return tls_error(US"certificate verification failed",
- "no certificate received from peer", state->host);
+ US"no certificate received from peer", state->host, errstr);
return OK;
}
ct = gnutls_certificate_type_get(state->session);
if (ct != GNUTLS_CRT_X509)
{
- const char *ctn = gnutls_certificate_type_get_name(ct);
+ const uschar *ctn = US gnutls_certificate_type_get_name(ct);
DEBUG(D_tls)
debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
if (state->verify_requirement >= VERIFY_REQUIRED)
return tls_error(US"certificate verification not possible, unhandled type",
- ctn, state->host);
+ ctn, state->host, errstr);
return OK;
}
DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \
(Label), gnutls_strerror(rc)); \
if (state->verify_requirement >= VERIFY_REQUIRED) \
- return tls_error((Label), gnutls_strerror(rc), state->host); \
+ return tls_error((Label), US gnutls_strerror(rc), state->host, errstr); \
return OK; \
} \
} while (0)
the peer information, but that's too new for some OSes.
Arguments:
- state exim_gnutls_state_st *
- error where to put an error message
+ state exim_gnutls_state_st *
+ errstr where to put an error message
Returns:
FALSE if the session should be rejected
*/
static BOOL
-verify_certificate(exim_gnutls_state_st *state, const char **error)
+verify_certificate(exim_gnutls_state_st * state, uschar ** errstr)
{
int rc;
-unsigned int verify;
+uint verify;
+
+if (state->verify_requirement == VERIFY_NONE)
+ return TRUE;
-*error = NULL;
+DEBUG(D_tls) debug_printf("TLS: checking peer certificate\n");
+*errstr = NULL;
-if ((rc = peer_status(state)) != OK)
+if ((rc = peer_status(state, errstr)) != OK)
{
verify = GNUTLS_CERT_INVALID;
- *error = "certificate not supplied";
+ *errstr = US"certificate not supplied";
}
else
+
+ {
+#ifdef SUPPORT_DANE
+ if (state->verify_requirement == VERIFY_DANE && state->host)
+ {
+ /* Using dane_verify_session_crt() would be easy, as it does it all for us
+ including talking to a DNS resolver. But we want to do that bit ourselves
+ as the testsuite intercepts and fakes its own DNS environment. */
+
+ dane_state_t s;
+ dane_query_t r;
+ uint lsize;
+ const gnutls_datum_t * certlist =
+ gnutls_certificate_get_peers(state->session, &lsize);
+ int usage = tls_out.tlsa_usage;
+
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ /* Split the TLSA records into two sets, TA and EE selectors. Run the
+ dane-verification separately so that we know which selector verified;
+ then we know whether to do name-verification (needed for TA but not EE). */
+
+ if (usage == ((1<<DANESSL_USAGE_DANE_TA) | (1<<DANESSL_USAGE_DANE_EE)))
+ { /* a mixed-usage bundle */
+ int i, j, nrec;
+ const char ** dd;
+ int * ddl;
+
+ for(nrec = 0; state->dane_data_len[nrec]; ) nrec++;
+ nrec++;
+
+ dd = store_get(nrec * sizeof(uschar *));
+ ddl = store_get(nrec * sizeof(int));
+ nrec--;
+
+ if ((rc = dane_state_init(&s, 0)))
+ goto tlsa_prob;
+
+ for (usage = DANESSL_USAGE_DANE_EE;
+ usage >= DANESSL_USAGE_DANE_TA; usage--)
+ { /* take records with this usage */
+ for (j = i = 0; i < nrec; i++)
+ if (state->dane_data[i][0] == usage)
+ {
+ dd[j] = state->dane_data[i];
+ ddl[j++] = state->dane_data_len[i];
+ }
+ if (j)
+ {
+ dd[j] = NULL;
+ ddl[j] = 0;
+
+ if ((rc = dane_raw_tlsa(s, &r, (char * const *)dd, ddl, 1, 0)))
+ goto tlsa_prob;
+
+ if ((rc = dane_verify_crt_raw(s, certlist, lsize,
+ gnutls_certificate_type_get(state->session),
+ r, 0,
+ usage == DANESSL_USAGE_DANE_EE
+ ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+ &verify)))
+ {
+ DEBUG(D_tls)
+ debug_printf("TLSA record problem: %s\n", dane_strerror(rc));
+ }
+ else if (verify == 0) /* verification passed */
+ {
+ usage = 1 << usage;
+ break;
+ }
+ }
+ }
+
+ if (rc) goto tlsa_prob;
+ }
+ else
+# endif
+ {
+ if ( (rc = dane_state_init(&s, 0))
+ || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len,
+ 1, 0))
+ || (rc = dane_verify_crt_raw(s, certlist, lsize,
+ gnutls_certificate_type_get(state->session),
+ r, 0,
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ usage == (1 << DANESSL_USAGE_DANE_EE)
+ ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0,
+# else
+ 0,
+# endif
+ &verify))
+ )
+ goto tlsa_prob;
+ }
+
+ if (verify != 0) /* verification failed */
+ {
+ gnutls_datum_t str;
+ (void) dane_verification_status_print(verify, &str, 0);
+ *errstr = US str.data; /* don't bother to free */
+ goto badcert;
+ }
+
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ /* If a TA-mode TLSA record was used for verification we must additionally
+ verify the cert name (but not the CA chain). For EE-mode, skip it. */
+
+ if (usage & (1 << DANESSL_USAGE_DANE_EE))
+# endif
+ {
+ state->peer_dane_verified = state->peer_cert_verified = TRUE;
+ goto goodcert;
+ }
+# ifdef GNUTLS_BROKEN_DANE_VALIDATION
+ /* Assume that the name on the A-record is the one that should be matching
+ the cert. An alternate view is that the domain part of the email address
+ is also permissible. */
+
+ if (gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+ CS state->host->name))
+ {
+ state->peer_dane_verified = state->peer_cert_verified = TRUE;
+ goto goodcert;
+ }
+# endif
+ }
+#endif /*SUPPORT_DANE*/
+
rc = gnutls_certificate_verify_peers2(state->session, &verify);
+ }
-/* Handle the result of verification. INVALID seems to be set as well
-as REVOKED, but leave the test for both. */
+/* Handle the result of verification. INVALID is set if any others are. */
-if (rc < 0 ||
- verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)
- )
+if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED))
{
state->peer_cert_verified = FALSE;
- if (!*error)
- *error = verify & GNUTLS_CERT_REVOKED
- ? "certificate revoked" : "certificate invalid";
+ if (!*errstr)
+ {
+#ifdef GNUTLS_CERT_VFY_STATUS_PRINT
+ DEBUG(D_tls)
+ {
+ gnutls_datum_t txt;
+
+ if (gnutls_certificate_verification_status_print(verify,
+ gnutls_certificate_type_get(state->session), &txt, 0)
+ == GNUTLS_E_SUCCESS)
+ {
+ debug_printf("%s\n", txt.data);
+ gnutls_free(txt.data);
+ }
+ }
+#endif
+ *errstr = verify & GNUTLS_CERT_REVOKED
+ ? US"certificate revoked" : US"certificate invalid";
+ }
DEBUG(D_tls)
debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n",
- *error, state->peerdn ? state->peerdn : US"<unset>");
+ *errstr, state->peerdn ? state->peerdn : US"<unset>");
if (state->verify_requirement >= VERIFY_REQUIRED)
- {
- gnutls_alert_send(state->session,
- GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
- return FALSE;
- }
+ goto badcert;
DEBUG(D_tls)
debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n");
}
else
{
- if (state->exp_tls_verify_cert_hostnames)
+ /* Client side, check the server's certificate name versus the name on the
+ A-record for the connection we made. What to do for server side - what name
+ to use for client? We document that there is no such checking for server
+ side. */
+
+ if ( state->exp_tls_verify_cert_hostnames
+ && !gnutls_x509_crt_check_hostname(state->tlsp->peercert,
+ CS state->exp_tls_verify_cert_hostnames)
+ )
{
- int sep = 0;
- const uschar * list = state->exp_tls_verify_cert_hostnames;
- uschar * name;
- while ((name = string_nextinlist(&list, &sep, NULL, 0)))
- if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name))
- break;
- if (!name)
- {
- DEBUG(D_tls)
- debug_printf("TLS certificate verification failed: cert name mismatch\n");
- if (state->verify_requirement >= VERIFY_REQUIRED)
- {
- gnutls_alert_send(state->session,
- GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
- return FALSE;
- }
- return TRUE;
- }
+ DEBUG(D_tls)
+ debug_printf("TLS certificate verification failed: cert name mismatch\n");
+ if (state->verify_requirement >= VERIFY_REQUIRED)
+ goto badcert;
+ return TRUE;
}
+
state->peer_cert_verified = TRUE;
DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n",
state->peerdn ? state->peerdn : US"<unset>");
}
-state->tlsp->peerdn = state->peerdn;
+goodcert:
+ state->tlsp->peerdn = state->peerdn;
+ return TRUE;
-return TRUE;
+#ifdef SUPPORT_DANE
+tlsa_prob:
+ *errstr = string_sprintf("TLSA record problem: %s",
+ rc == DANE_E_REQUESTED_DATA_NOT_AVAILABLE ? "none usable" : dane_strerror(rc));
+#endif
+
+badcert:
+ gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE);
+ return FALSE;
}
exim_gnutls_state_st *state = &state_server;
unsigned int sni_type;
int rc, old_pool;
+uschar * dummy_errstr;
rc = gnutls_server_name_get(session, sni_name, &data_len, &sni_type, 0);
if (rc != GNUTLS_E_SUCCESS)
else
debug_printf("TLS failure: gnutls_server_name_get(): %s [%d]\n",
gnutls_strerror(rc), rc);
- };
+ }
return 0;
}
if (!state->trigger_sni_changes)
return 0;
-rc = tls_expand_session_files(state);
-if (rc != OK)
+if ((rc = tls_expand_session_files(state, &dummy_errstr)) != OK)
{
/* If the setup of certs/etc failed before handshake, TLS would not have
been offered. The best we can do now is abort. */
return GNUTLS_E_APPLICATION_ERROR_MIN;
}
-rc = tls_set_remaining_x509(state);
+rc = tls_set_remaining_x509(state, &dummy_errstr);
if (rc != OK) return GNUTLS_E_APPLICATION_ERROR_MIN;
return 0;
gnutls_datum_t * ocsp_response)
{
int ret;
+DEBUG(D_tls) debug_printf("OCSP stapling callback: %s\n", US ptr);
if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
{
DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n",
- (char *)ptr);
+ CS ptr);
tls_in.ocsp = OCSP_NOT_RESP;
return GNUTLS_E_NO_CERTIFICATE_STATUS;
}
uschar * yield;
exim_gnutls_state_st * state = gnutls_session_get_ptr(session);
-cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
-if (cert_list)
+if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size)))
while (cert_list_size--)
{
- rc = import_cert(&cert_list[cert_list_size], &crt);
- if (rc != GNUTLS_E_SUCCESS)
+ if ((rc = import_cert(&cert_list[cert_list_size], &crt)) != GNUTLS_E_SUCCESS)
{
DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
cert_list_size, gnutls_strerror(rc));
Arguments:
require_ciphers list of allowed ciphers or NULL
+ errstr pointer to error string
Returns: OK on success
DEFER for errors before the start of the negotiation
*/
int
-tls_server_start(const uschar *require_ciphers)
+tls_server_start(const uschar * require_ciphers, uschar ** errstr)
{
int rc;
-const char *error;
-exim_gnutls_state_st *state = NULL;
+exim_gnutls_state_st * state = NULL;
/* Check for previous activation */
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
{
- tls_error(US"STARTTLS received after TLS started", "", NULL);
- smtp_printf("554 Already in TLS\r\n");
+ tls_error(US"STARTTLS received after TLS started", US "", NULL, errstr);
+ smtp_printf("554 Already in TLS\r\n", FALSE);
return FAIL;
}
DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
-rc = tls_init(NULL, tls_certificate, tls_privatekey,
+if ((rc = tls_init(NULL, tls_certificate, tls_privatekey,
NULL, tls_verify_certificates, tls_crl,
- require_ciphers, &state);
-if (rc != OK) return rc;
+ require_ciphers, &state, &tls_in, errstr)) != OK) return rc;
/* If this is a host for which certificate verification is mandatory or
optional, set up appropriately. */
if (!state->tlsp->on_connect)
{
- smtp_printf("220 TLS go ahead\r\n");
+ smtp_printf("220 TLS go ahead\r\n", FALSE);
fflush(smtp_out);
}
/* Now negotiate the TLS session. We put our own timer on it, since it seems
-that the GnuTLS library doesn't. */
+that the GnuTLS library doesn't.
+From 3.1.0 there is gnutls_handshake_set_timeout() - but it requires you
+to set (and clear down afterwards) up a pull-timeout callback function that does
+a select, so we're no better off unless avoiding signals becomes an issue. */
gnutls_transport_set_ptr2(state->session,
(gnutls_transport_ptr_t)(long) fileno(smtp_in),
state->fd_out = fileno(smtp_out);
sigalrm_seen = FALSE;
-if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
do
rc = gnutls_handshake(state->session);
while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
-alarm(0);
+ALARM_CLR(0);
if (rc != GNUTLS_E_SUCCESS)
{
if (sigalrm_seen)
{
- tls_error(US"gnutls_handshake", "timed out", NULL);
+ tls_error(US"gnutls_handshake", US"timed out", NULL, errstr);
gnutls_db_remove_session(state->session);
}
else
{
- tls_error(US"gnutls_handshake", gnutls_strerror(rc), NULL);
+ tls_error(US"gnutls_handshake", US gnutls_strerror(rc), NULL, errstr);
(void) gnutls_alert_send_appropriate(state->session, rc);
gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->x509_cred);
/* Verify after the fact */
-if ( state->verify_requirement != VERIFY_NONE
- && !verify_certificate(state, &error))
+if (!verify_certificate(state, errstr))
{
if (state->verify_requirement != VERIFY_OPTIONAL)
{
- tls_error(US"certificate verification failed", error, NULL);
+ (void) tls_error(US"certificate verification failed", *errstr, NULL, errstr);
return FAIL;
}
DEBUG(D_tls)
debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
- error);
+ *errstr);
}
/* Figure out peer DN, and if authenticated, etc. */
-if ((rc = peer_status(state)) != OK) return rc;
+if ((rc = peer_status(state, NULL)) != OK) return rc;
/* Sets various Exim expansion variables; always safe within server */
state->xfer_buffer = store_malloc(ssl_xfer_buffer_size);
receive_getc = tls_getc;
+receive_getbuf = tls_getbuf;
receive_get_cache = tls_get_cache;
receive_ungetc = tls_ungetc;
receive_feof = tls_feof;
tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state,
smtp_transport_options_block * ob)
{
-if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
{
state->exp_tls_verify_cert_hostnames =
#ifdef SUPPORT_I18N
}
+
+
+#ifdef SUPPORT_DANE
+/* Given our list of RRs from the TLSA lookup, build a lookup block in
+GnuTLS-DANE's preferred format. Hang it on the state str for later
+use in DANE verification.
+
+We point at the dnsa data not copy it, so it must remain valid until
+after verification is done.*/
+
+static BOOL
+dane_tlsa_load(exim_gnutls_state_st * state, dns_answer * dnsa)
+{
+dns_record * rr;
+dns_scan dnss;
+int i;
+const char ** dane_data;
+int * dane_data_len;
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 1;
+ rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ ) if (rr->type == T_TLSA) i++;
+
+dane_data = store_get(i * sizeof(uschar *));
+dane_data_len = store_get(i * sizeof(int));
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0;
+ rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+ ) if (rr->type == T_TLSA && rr->size > 3)
+ {
+ const uschar * p = rr->data;
+ uint8_t usage = p[0], sel = p[1], type = p[2];
+
+ DEBUG(D_tls)
+ debug_printf("TLSA: %d %d %d size %d\n", usage, sel, type, rr->size);
+
+ if ( (usage != DANESSL_USAGE_DANE_TA && usage != DANESSL_USAGE_DANE_EE)
+ || (sel != 0 && sel != 1)
+ )
+ continue;
+ switch(type)
+ {
+ case 0: /* Full: cannot check at present */
+ break;
+ case 1: if (rr->size != 3 + 256/8) continue; /* sha2-256 */
+ break;
+ case 2: if (rr->size != 3 + 512/8) continue; /* sha2-512 */
+ break;
+ default: continue;
+ }
+
+ tls_out.tlsa_usage |= 1<<usage;
+ dane_data[i] = CS p;
+ dane_data_len[i++] = rr->size;
+ }
+
+if (!i) return FALSE;
+
+dane_data[i] = NULL;
+dane_data_len[i] = 0;
+
+state->dane_data = (char * const *)dane_data;
+state->dane_data_len = dane_data_len;
+return TRUE;
+}
+#endif
+
+
+
/*************************************************
* Start a TLS session in a client *
*************************************************/
Arguments:
fd the fd of the connection
- host connected host (for messages)
+ host connected host (for messages and option-tests)
addr the first address (not used)
tb transport (always smtp)
-
-Returns: OK/DEFER/FAIL (because using common functions),
- but for a client, DEFER and FAIL have the same meaning
+ tlsa_dnsa non-NULL, either request or require dane for this host, and
+ a TLSA record found. Therefore, dane verify required.
+ Which implies cert must be requested and supplied, dane
+ verify must pass, and cert verify irrelevant (incl.
+ hostnames), and (caller handled) require_tls
+ tlsp record details of channel configuration
+ errstr error string pointer
+
+Returns: Pointer to TLS session context, or NULL on error
*/
-int
+void *
tls_client_start(int fd, host_item *host,
address_item *addr ARG_UNUSED,
- transport_instance *tb
-#ifdef EXPERIMENTAL_DANE
- , dns_answer * unused_tlsa_dnsa
+ transport_instance * tb,
+#ifdef SUPPORT_DANE
+ dns_answer * tlsa_dnsa,
#endif
- )
+ tls_support * tlsp, uschar ** errstr)
{
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)tb->options_block;
+smtp_transport_options_block *ob = tb
+ ? (smtp_transport_options_block *)tb->options_block
+ : &smtp_transport_option_defaults;
int rc;
-const char *error;
-exim_gnutls_state_st *state = NULL;
+exim_gnutls_state_st * state = NULL;
+uschar *cipher_list = NULL;
+
#ifndef DISABLE_OCSP
BOOL require_ocsp =
- verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+ verify_check_given_host(CUSS &ob->hosts_require_ocsp, host) == OK;
BOOL request_ocsp = require_ocsp ? TRUE
- : verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+ : verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
#endif
DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
-if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa && ob->dane_require_tls_ciphers)
+ {
+ /* not using expand_check_tlsvar because not yet in state */
+ if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
+ &cipher_list, errstr))
+ return NULL;
+ cipher_list = cipher_list && *cipher_list
+ ? ob->dane_require_tls_ciphers : ob->tls_require_ciphers;
+ }
+#endif
+
+if (!cipher_list)
+ cipher_list = ob->tls_require_ciphers;
+
+if (tls_init(host, ob->tls_certificate, ob->tls_privatekey,
ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
- ob->tls_require_ciphers, &state)) != OK)
- return rc;
+ cipher_list, &state, tlsp, errstr) != OK)
+ return NULL;
{
int dh_min_bits = ob->tls_dh_min_bits;
set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only
the specified host patterns if one of them is defined */
-if ( ( state->exp_tls_verify_certificates
- && !ob->tls_verify_hosts
- && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
- )
- || verify_check_given_host(&ob->tls_verify_hosts, host) == OK
- )
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa && dane_tlsa_load(state, tlsa_dnsa))
+ {
+ DEBUG(D_tls)
+ debug_printf("TLS: server certificate DANE required.\n");
+ state->verify_requirement = VERIFY_DANE;
+ gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
+ }
+else
+#endif
+ if ( ( state->exp_tls_verify_certificates
+ && !ob->tls_verify_hosts
+ && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
+ )
+ || verify_check_given_host(CUSS &ob->tls_verify_hosts, host) == OK
+ )
{
tls_client_setup_hostname_checks(host, state, ob);
DEBUG(D_tls)
state->verify_requirement = VERIFY_REQUIRED;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
}
-else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
+else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK)
{
tls_client_setup_hostname_checks(host, state, ob);
DEBUG(D_tls)
DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
NULL, 0, NULL)) != OK)
- return tls_error(US"cert-status-req",
- gnutls_strerror(rc), state->host);
- tls_out.ocsp = OCSP_NOT_RESP;
+ {
+ tls_error(US"cert-status-req", US gnutls_strerror(rc), state->host, errstr);
+ return NULL;
+ }
+ tlsp->ocsp = OCSP_NOT_RESP;
}
#endif
#ifndef DISABLE_EVENT
-if (tb->event_action)
+if (tb && tb->event_action)
{
state->event_action = tb->event_action;
gnutls_session_set_ptr(state->session, state);
/* There doesn't seem to be a built-in timeout on connection. */
sigalrm_seen = FALSE;
-alarm(ob->command_timeout);
+ALARM(ob->command_timeout);
do
- {
rc = gnutls_handshake(state->session);
- } while ((rc == GNUTLS_E_AGAIN) ||
- (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen));
-alarm(0);
+while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen);
+ALARM_CLR(0);
if (rc != GNUTLS_E_SUCCESS)
+ {
if (sigalrm_seen)
{
gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_USER_CANCELED);
- return tls_error(US"gnutls_handshake", "timed out", state->host);
+ tls_error(US"gnutls_handshake", US"timed out", state->host, errstr);
}
else
- return tls_error(US"gnutls_handshake", gnutls_strerror(rc), state->host);
+ tls_error(US"gnutls_handshake", US gnutls_strerror(rc), state->host, errstr);
+ return NULL;
+ }
DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
/* Verify late */
-if (state->verify_requirement != VERIFY_NONE &&
- !verify_certificate(state, &error))
- return tls_error(US"certificate verification failed", error, state->host);
+if (!verify_certificate(state, errstr))
+ {
+ tls_error(US"certificate verification failed", *errstr, state->host, errstr);
+ return NULL;
+ }
#ifndef DISABLE_OCSP
if (require_ocsp)
gnutls_free(printed.data);
}
else
- (void) tls_error(US"ocsp decode", gnutls_strerror(rc), state->host);
+ (void) tls_error(US"ocsp decode", US gnutls_strerror(rc), state->host, errstr);
}
if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
{
- tls_out.ocsp = OCSP_FAILED;
- return tls_error(US"certificate status check failed", NULL, state->host);
+ tlsp->ocsp = OCSP_FAILED;
+ tls_error(US"certificate status check failed", NULL, state->host, errstr);
+ return NULL;
}
DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
- tls_out.ocsp = OCSP_VFIED;
+ tlsp->ocsp = OCSP_VFIED;
}
#endif
/* Figure out peer DN, and if authenticated, etc. */
-if ((rc = peer_status(state)) != OK)
- return rc;
+if (peer_status(state, errstr) != OK)
+ return NULL;
/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
extract_exim_vars_from_tls_state(state);
-return OK;
+return state;
}
daemon, to shut down the TLS library, without actually doing a shutdown (which
would tamper with the TLS session in the parent process).
-Arguments: TRUE if gnutls_bye is to be called
+Arguments:
+ ct_ctx client context pointer, or NULL for the one global server context
+ shutdown 1 if TLS close-alert is to be sent,
+ 2 if also response to be waited for
+
Returns: nothing
*/
void
-tls_close(BOOL is_server, BOOL shutdown)
+tls_close(void * ct_ctx, int shutdown)
{
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
-if (!state->tlsp || state->tlsp->active < 0) return; /* TLS was not active */
+if (!state->tlsp || state->tlsp->active.sock < 0) return; /* TLS was not active */
if (shutdown)
{
- DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n");
- gnutls_bye(state->session, GNUTLS_SHUT_WR);
+ DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+ shutdown > 1 ? " (with response-wait)" : "");
+
+ ALARM(2);
+ gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR);
+ ALARM_CLR(0);
}
gnutls_deinit(state->session);
gnutls_certificate_free_credentials(state->x509_cred);
-state->tlsp->active = -1;
+state->tlsp->active.sock = -1;
+state->tlsp->active.tls_ctx = NULL;
+if (state->xfer_buffer) store_free(state->xfer_buffer);
memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init));
+}
+
-if ((state_server.session == NULL) && (state_client.session == NULL))
+
+
+static BOOL
+tls_refill(unsigned lim)
+{
+exim_gnutls_state_st * state = &state_server;
+ssize_t inbytes;
+
+DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
+ state->session, state->xfer_buffer, ssl_xfer_buffer_size);
+
+sigalrm_seen = FALSE;
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
+
+do
+ inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
+ MIN(ssl_xfer_buffer_size, lim));
+while (inbytes == GNUTLS_E_AGAIN);
+
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
+
+if (had_command_timeout) /* set by signal handler */
+ smtp_command_timeout_exit(); /* does not return */
+if (had_command_sigterm)
+ smtp_command_sigterm_exit();
+if (had_data_timeout)
+ smtp_data_timeout_exit();
+if (had_data_sigint)
+ smtp_data_sigint_exit();
+
+/* Timeouts do not get this far. A zero-byte return appears to mean that the
+TLS session has been closed down, not that the socket itself has been closed
+down. Revert to non-TLS handling. */
+
+if (sigalrm_seen)
{
- gnutls_global_deinit();
- exim_gnutls_base_init_done = FALSE;
+ DEBUG(D_tls) debug_printf("Got tls read timeout\n");
+ state->xfer_error = TRUE;
+ return FALSE;
}
-}
+else if (inbytes == 0)
+ {
+ DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
+
+ receive_getc = smtp_getc;
+ receive_getbuf = smtp_getbuf;
+ receive_get_cache = smtp_get_cache;
+ receive_ungetc = smtp_ungetc;
+ receive_feof = smtp_feof;
+ receive_ferror = smtp_ferror;
+ receive_smtp_buffered = smtp_buffered;
+
+ gnutls_deinit(state->session);
+ gnutls_certificate_free_credentials(state->x509_cred);
+
+ state->session = NULL;
+ state->tlsp->active.sock = -1;
+ state->tlsp->active.tls_ctx = NULL;
+ state->tlsp->bits = 0;
+ state->tlsp->certificate_verified = FALSE;
+ tls_channelbinding_b64 = NULL;
+ state->tlsp->cipher = NULL;
+ state->tlsp->peercert = NULL;
+ state->tlsp->peerdn = NULL;
+ return FALSE;
+ }
+/* Handle genuine errors */
+else if (inbytes < 0)
+ {
+ DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__);
+ record_io_error(state, (int) inbytes, US"recv", NULL);
+ state->xfer_error = TRUE;
+ return FALSE;
+ }
+#ifndef DISABLE_DKIM
+dkim_exim_verify_feed(state->xfer_buffer, inbytes);
+#endif
+state->xfer_buffer_hwm = (int) inbytes;
+state->xfer_buffer_lwm = 0;
+return TRUE;
+}
/*************************************************
* TLS version of getc *
This feeds DKIM and should be used for all message-body reads.
-Arguments: lim Maximum amount to read/bufffer
+Arguments: lim Maximum amount to read/buffer
Returns: the next character or EOF
*/
int
tls_getc(unsigned lim)
{
-exim_gnutls_state_st *state = &state_server;
-if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
- {
- ssize_t inbytes;
-
- DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
- state->session, state->xfer_buffer, ssl_xfer_buffer_size);
-
- if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
- inbytes = gnutls_record_recv(state->session, state->xfer_buffer,
- MIN(ssl_xfer_buffer_size, lim));
- alarm(0);
-
- /* Timeouts do not get this far; see command_timeout_handler().
- A zero-byte return appears to mean that the TLS session has been
- closed down, not that the socket itself has been closed down. Revert to
- non-TLS handling. */
-
- if (sigalrm_seen)
- {
- DEBUG(D_tls) debug_printf("Got tls read timeout\n");
- state->xfer_error = 1;
- return EOF;
- }
-
- else if (inbytes == 0)
- {
- DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
-
- receive_getc = smtp_getc;
- receive_get_cache = smtp_get_cache;
- receive_ungetc = smtp_ungetc;
- receive_feof = smtp_feof;
- receive_ferror = smtp_ferror;
- receive_smtp_buffered = smtp_buffered;
+exim_gnutls_state_st * state = &state_server;
- gnutls_deinit(state->session);
- gnutls_certificate_free_credentials(state->x509_cred);
+if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
+ if (!tls_refill(lim))
+ return state->xfer_error ? EOF : smtp_getc(lim);
- state->session = NULL;
- state->tlsp->active = -1;
- state->tlsp->bits = 0;
- state->tlsp->certificate_verified = FALSE;
- tls_channelbinding_b64 = NULL;
- state->tlsp->cipher = NULL;
- state->tlsp->peercert = NULL;
- state->tlsp->peerdn = NULL;
+/* Something in the buffer; return next uschar */
- return smtp_getc(lim);
- }
+return state->xfer_buffer[state->xfer_buffer_lwm++];
+}
- /* Handle genuine errors */
+uschar *
+tls_getbuf(unsigned * len)
+{
+exim_gnutls_state_st * state = &state_server;
+unsigned size;
+uschar * buf;
- else if (inbytes < 0)
+if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm)
+ if (!tls_refill(*len))
{
- record_io_error(state, (int) inbytes, US"recv", NULL);
- state->xfer_error = 1;
- return EOF;
+ if (!state->xfer_error) return smtp_getbuf(len);
+ *len = 0;
+ return NULL;
}
-#ifndef DISABLE_DKIM
- dkim_exim_verify_feed(state->xfer_buffer, inbytes);
-#endif
- state->xfer_buffer_hwm = (int) inbytes;
- state->xfer_buffer_lwm = 0;
- }
-
-/* Something in the buffer; return next uschar */
-return state->xfer_buffer[state->xfer_buffer_lwm++];
+if ((size = state->xfer_buffer_hwm - state->xfer_buffer_lwm) > *len)
+ size = *len;
+buf = &state->xfer_buffer[state->xfer_buffer_lwm];
+state->xfer_buffer_lwm += size;
+*len = size;
+return buf;
}
+
void
tls_get_cache()
{
}
+BOOL
+tls_could_read(void)
+{
+return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm
+ || gnutls_record_check_pending(state_server.session) > 0;
+}
+
+
/*************************************************
then the caller must feed DKIM.
Arguments:
+ ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len size of buffer
Returns: the number of bytes read
- -1 after a failed read
+ -1 after a failed read, including EOF
*/
int
-tls_read(BOOL is_server, uschar *buff, size_t len)
+tls_read(void * ct_ctx, uschar *buff, size_t len)
{
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
ssize_t inbytes;
if (len > INT_MAX)
debug_printf("Calling gnutls_record_recv(%p, %p, " SIZE_T_FMT ")\n",
state->session, buff, len);
-inbytes = gnutls_record_recv(state->session, buff, len);
+do
+ inbytes = gnutls_record_recv(state->session, buff, len);
+while (inbytes == GNUTLS_E_AGAIN);
+
if (inbytes > 0) return inbytes;
if (inbytes == 0)
{
DEBUG(D_tls) debug_printf("Got TLS_EOF\n");
}
-else record_io_error(state, (int)inbytes, US"recv", NULL);
+else
+ {
+ DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__);
+ record_io_error(state, (int)inbytes, US"recv", NULL);
+ }
return -1;
}
/*
Arguments:
- is_server channel specifier
+ ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len number of bytes
+ more more data expected soon
Returns: the number of bytes after a successful write,
-1 after a failed write
*/
int
-tls_write(BOOL is_server, const uschar *buff, size_t len)
+tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more)
{
ssize_t outbytes;
size_t left = len;
-exim_gnutls_state_st *state = is_server ? &state_server : &state_client;
+exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
+#ifdef SUPPORT_CORK
+static BOOL corked = FALSE;
+
+if (more && !corked) gnutls_record_cork(state->session);
+#endif
+
+DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
+ buff, left, more ? ", more" : "");
-DEBUG(D_tls) debug_printf("tls_do_write(%p, " SIZE_T_FMT ")\n", buff, left);
while (left > 0)
{
DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
buff, left);
- outbytes = gnutls_record_send(state->session, buff, left);
+
+ do
+ outbytes = gnutls_record_send(state->session, buff, left);
+ while (outbytes == GNUTLS_E_AGAIN);
DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
if (outbytes < 0)
{
+ DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__);
record_io_error(state, outbytes, US"send", NULL);
return -1;
}
len = INT_MAX;
}
+#ifdef SUPPORT_CORK
+if (more != corked)
+ {
+ if (!more) (void) gnutls_record_uncork(state->session, 0);
+ corked = more;
+ }
+#endif
+
return (int) len;
}
uschar *expciphers = NULL;
gnutls_priority_t priority_cache;
const char *errpos;
+uschar * dummy_errstr;
#define validate_check_rc(Label) do { \
if (rc != GNUTLS_E_SUCCESS) { if (exim_gnutls_base_init_done) gnutls_global_deinit(); \
if (!(tls_require_ciphers && *tls_require_ciphers))
return_deinit(NULL);
-if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers,
+ &dummy_errstr))
return_deinit(US"failed to expand tls_require_ciphers");
if (!(expciphers && *expciphers))
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Portions Copyright (c) The OpenSSL Project 1999 */
#ifndef DISABLE_OCSP
# include <openssl/ocsp.h>
#endif
-#ifdef EXPERIMENTAL_DANE
-# include <danessl.h>
+#ifdef SUPPORT_DANE
+# include "danessl.h"
#endif
#ifndef LIBRESSL_VERSION_NUMBER
# if OPENSSL_VERSION_NUMBER >= 0x010100000L
# define EXIM_HAVE_OPENSSL_CHECKHOST
+# define EXIM_HAVE_OPENSSL_DH_BITS
+# define EXIM_HAVE_OPENSSL_TLS_METHOD
+# else
+# define EXIM_NEED_OPENSSL_INIT
# endif
# if OPENSSL_VERSION_NUMBER >= 0x010000000L \
&& (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L
# define DISABLE_OCSP
#endif
+#ifdef EXIM_HAVE_OPENSSL_CHECKHOST
+# include <openssl/x509v3.h>
+#endif
+
+/*************************************************
+* OpenSSL option parse *
+*************************************************/
+
+typedef struct exim_openssl_option {
+ uschar *name;
+ long value;
+} exim_openssl_option;
+/* We could use a macro to expand, but we need the ifdef and not all the
+options document which version they were introduced in. Policylet: include
+all options unless explicitly for DTLS, let the administrator choose which
+to apply.
+
+This list is current as of:
+ ==> 1.0.1b <==
+Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
+Plus SSL_OP_NO_TLSv1_3 for 1.1.2-dev
+*/
+static exim_openssl_option exim_openssl_options[] = {
+/* KEEP SORTED ALPHABETICALLY! */
+#ifdef SSL_OP_ALL
+ { US"all", SSL_OP_ALL },
+#endif
+#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+ { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
+#endif
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+ { US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE },
+#endif
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+ { US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS },
+#endif
+#ifdef SSL_OP_EPHEMERAL_RSA
+ { US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA },
+#endif
+#ifdef SSL_OP_LEGACY_SERVER_CONNECT
+ { US"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT },
+#endif
+#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+ { US"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER },
+#endif
+#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
+ { US"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG },
+#endif
+#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
+ { US"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING },
+#endif
+#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
+ { US"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG },
+#endif
+#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+ { US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG },
+#endif
+#ifdef SSL_OP_NO_COMPRESSION
+ { US"no_compression", SSL_OP_NO_COMPRESSION },
+#endif
+#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+ { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
+#endif
+#ifdef SSL_OP_NO_SSLv2
+ { US"no_sslv2", SSL_OP_NO_SSLv2 },
+#endif
+#ifdef SSL_OP_NO_SSLv3
+ { US"no_sslv3", SSL_OP_NO_SSLv3 },
+#endif
+#ifdef SSL_OP_NO_TICKET
+ { US"no_ticket", SSL_OP_NO_TICKET },
+#endif
+#ifdef SSL_OP_NO_TLSv1
+ { US"no_tlsv1", SSL_OP_NO_TLSv1 },
+#endif
+#ifdef SSL_OP_NO_TLSv1_1
+#if SSL_OP_NO_TLSv1_1 == 0x00000400L
+ /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */
+#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring
+#else
+ { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 },
+#endif
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+ { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 },
+#endif
+#ifdef SSL_OP_NO_TLSv1_3
+ { US"no_tlsv1_3", SSL_OP_NO_TLSv1_3 },
+#endif
+#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
+ { US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
+#endif
+#ifdef SSL_OP_SINGLE_DH_USE
+ { US"single_dh_use", SSL_OP_SINGLE_DH_USE },
+#endif
+#ifdef SSL_OP_SINGLE_ECDH_USE
+ { US"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE },
+#endif
+#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+ { US"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG },
+#endif
+#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+ { US"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG },
+#endif
+#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
+ { US"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG },
+#endif
+#ifdef SSL_OP_TLS_D5_BUG
+ { US"tls_d5_bug", SSL_OP_TLS_D5_BUG },
+#endif
+#ifdef SSL_OP_TLS_ROLLBACK_BUG
+ { US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG },
+#endif
+};
+
+#ifndef MACRO_PREDEF
+static int exim_openssl_options_size = nelem(exim_openssl_options);
+#endif
+
+#ifdef MACRO_PREDEF
+void
+options_tls(void)
+{
+struct exim_openssl_option * o;
+uschar buf[64];
+
+for (o = exim_openssl_options;
+ o < exim_openssl_options + nelem(exim_openssl_options); o++)
+ {
+ /* Trailing X is workaround for problem with _OPT_OPENSSL_NO_TLSV1
+ being a ".ifdef _OPT_OPENSSL_NO_TLSV1_3" match */
+
+ spf(buf, sizeof(buf), US"_OPT_OPENSSL_%T_X", o->name);
+ builtin_macro_create(buf);
+ }
+}
+#else
+
+/******************************************************************************/
+
/* Structure for collecting random data for seeding. */
typedef struct randstuff {
Simple case: client, `client_ctx`
As a client, we can be doing a callout or cut-through delivery while receiving
a message. So we have a client context, which should have options initialised
- from the SMTP Transport.
+ from the SMTP Transport. We may also concurrently want to make TLS connections
+ to utility daemons, so client-contexts are allocated and passed around in call
+ args rather than using a gobal.
Server:
There are two cases: with and without ServerNameIndication from the client.
configuration.
*/
-static SSL_CTX *client_ctx = NULL;
+typedef struct {
+ SSL_CTX * ctx;
+ SSL * ssl;
+} exim_openssl_client_tls_ctx;
+
static SSL_CTX *server_ctx = NULL;
-static SSL *client_ssl = NULL;
static SSL *server_ssl = NULL;
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
typedef struct tls_ext_ctx_cb {
uschar *certificate;
uschar *privatekey;
-#ifndef DISABLE_OCSP
BOOL is_server;
+#ifndef DISABLE_OCSP
STACK_OF(X509) *verify_stack; /* chain for verifying the proof */
union {
struct {
static int
setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
- int (*cert_vfy_cb)(int, X509_STORE_CTX *) );
+ int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr );
/* Callbacks */
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
host NULL if setting up a server;
the connected host if setting up a client
msg error message or NULL if we should ask OpenSSL
+ errstr pointer to output error message
Returns: OK/DEFER/FAIL
*/
static int
-tls_error(uschar * prefix, const host_item * host, uschar * msg)
+tls_error(uschar * prefix, const host_item * host, uschar * msg, uschar ** errstr)
{
if (!msg)
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
- msg = (uschar *)ssl_errstring;
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ msg = US ssl_errstring;
}
-if (host)
- {
- log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s): %s",
- host->name, host->address, prefix, msg);
- return FAIL;
- }
-else
- {
- uschar *conn_info = smtp_get_connection_info();
- if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
- conn_info += 5;
- /* I'd like to get separated H= here, but too hard for now */
- log_write(0, LOG_MAIN, "TLS error on %s (%s): %s",
- conn_info, prefix, msg);
- return DEFER;
- }
+msg = string_sprintf("(%s): %s", prefix, msg);
+DEBUG(D_tls) debug_printf("TLS error '%s'\n", msg);
+if (errstr) *errstr = msg;
+return host ? FAIL : DEFER;
}
-#ifdef EXIM_HAVE_EPHEM_RSA_KEX
/*************************************************
* Callback to generate RSA key *
*************************************************/
/*
Arguments:
- s SSL connection
+ s SSL connection (not used)
export not used
keylength keylength
#endif
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (RSA_generate_key): %s",
ssl_errstring);
return NULL;
}
return rsa_key;
}
-#endif
X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i);
if(tmp_obj->type == X509_LU_X509)
{
- X509 * current_cert= tmp_obj->data.x509;
- X509_NAME_oneline(X509_get_subject_name(current_cert), CS name, sizeof(name));
- name[sizeof(name)-1] = '\0';
- debug_printf(" %s\n", name);
+ X509_NAME * sn = X509_get_subject_name(tmp_obj->data.x509);
+ if (X509_NAME_oneline(sn, CS name, sizeof(name)))
+ {
+ name[sizeof(name)-1] = '\0';
+ debug_printf(" %s\n", name);
+ }
}
}
}
*/
static int
-verify_callback(int preverify_ok, X509_STORE_CTX *x509ctx,
- tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
+verify_callback(int preverify_ok, X509_STORE_CTX * x509ctx,
+ tls_support * tlsp, BOOL * calledp, BOOL * optionalp)
{
X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
int depth = X509_STORE_CTX_get_error_depth(x509ctx);
uschar dn[256];
-X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn));
+if (!X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)))
+ {
+ DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n");
+ log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address);
+ return 0;
+ }
dn[sizeof(dn)-1] = '\0';
if (preverify_ok == 0)
{
- log_write(0, LOG_MAIN, "[%s] SSL verify error: depth=%d error=%s cert=%s",
- tlsp == &tls_out ? deliver_host_address : sender_host_address,
- depth,
- X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)),
- dn);
+ uschar * extra = verify_mode ? string_sprintf(" (during %c-verify for [%s])",
+ *verify_mode, sender_host_address)
+ : US"";
+ log_write(0, LOG_MAIN, "[%s] SSL verify error%s: depth=%d error=%s cert=%s",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ extra, depth,
+ X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)), dn);
*calledp = TRUE;
if (!*optionalp)
{
if ( tlsp == &tls_out
&& ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames)))
- /* client, wanting hostname check */
+ /* client, wanting hostname check */
{
#ifdef EXIM_HAVE_OPENSSL_CHECKHOST
if (rc < 0)
{
log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
- deliver_host_address);
+ tlsp == &tls_out ? deliver_host_address : sender_host_address);
name = NULL;
}
break;
if (!tls_is_name_for_cert(verify_cert_hostnames, cert))
#endif
{
+ uschar * extra = verify_mode
+ ? string_sprintf(" (during %c-verify for [%s])",
+ *verify_mode, sender_host_address)
+ : US"";
log_write(0, LOG_MAIN,
- "[%s] SSL verify error: certificate name mismatch: "
- "DN=\"%s\" H=\"%s\"",
- deliver_host_address, dn, verify_cert_hostnames);
+ "[%s] SSL verify error%s: certificate name mismatch: DN=\"%s\" H=\"%s\"",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ extra, dn, verify_cert_hostnames);
*calledp = TRUE;
if (!*optionalp)
{
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
/* This gets called *by* the dane library verify callback, which interposes
itself.
BOOL dummy_called, optional = FALSE;
#endif
-X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn));
+if (!X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)))
+ {
+ DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n");
+ log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
+ deliver_host_address);
+ return 0;
+ }
dn[sizeof(dn)-1] = '\0';
DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s depth %d %s\n",
#endif
if (preverify_ok == 1)
- tls_out.dane_verified =
- tls_out.certificate_verified = TRUE;
+ {
+ tls_out.dane_verified = tls_out.certificate_verified = TRUE;
+#ifndef DISABLE_OCSP
+ if (client_static_cbinfo->u_ocsp.client.verify_store)
+ { /* client, wanting stapling */
+ /* Add the server cert's signing chain as the one
+ for the verification of the OCSP stapled information. */
+
+ if (!X509_STORE_add_cert(client_static_cbinfo->u_ocsp.client.verify_store,
+ cert))
+ ERR_clear_error();
+ sk_X509_push(client_static_cbinfo->verify_stack, cert);
+ }
+#endif
+ }
else
{
int err = X509_STORE_CTX_get_error(x509ctx);
return preverify_ok;
}
-#endif /*EXPERIMENTAL_DANE*/
+#endif /*SUPPORT_DANE*/
/*************************************************
static void
info_callback(SSL *s, int where, int ret)
{
-where = where;
-ret = ret;
-DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s));
+DEBUG(D_tls)
+ {
+ const uschar * str;
+
+ if (where & SSL_ST_CONNECT)
+ str = US"SSL_connect";
+ else if (where & SSL_ST_ACCEPT)
+ str = US"SSL_accept";
+ else
+ str = US"SSL info (undefined)";
+
+ if (where & SSL_CB_LOOP)
+ debug_printf("%s: %s\n", str, SSL_state_string_long(s));
+ else if (where & SSL_CB_ALERT)
+ debug_printf("SSL3 alert %s:%s:%s\n",
+ str = where & SSL_CB_READ ? US"read" : US"write",
+ SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
+ else if (where & SSL_CB_EXIT)
+ if (ret == 0)
+ debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s));
+ else if (ret < 0)
+ debug_printf("%s: error in %s\n", str, SSL_state_string_long(s));
+ else if (where & SSL_CB_HANDSHAKE_START)
+ debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s));
+ else if (where & SSL_CB_HANDSHAKE_DONE)
+ debug_printf("%s: hshake done: %s\n", str, SSL_state_string_long(s));
+ }
}
sctx The current SSL CTX (inbound or outbound)
dhparam DH parameter file or fixed parameter identity string
host connected host, if client; NULL if server
+ errstr error string pointer
Returns: TRUE if OK (nothing to set up, or setup worked)
*/
static BOOL
-init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host)
+init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host, uschar ** errstr)
{
BIO *bio;
DH *dh;
uschar *dhexpanded;
const char *pem;
+int dh_bitsize;
-if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded))
+if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr))
return FALSE;
if (!dhexpanded || !*dhexpanded)
if (!(bio = BIO_new_file(CS dhexpanded, "r")))
{
tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
- host, US strerror(errno));
+ host, US strerror(errno), errstr);
return FALSE;
}
}
if (!(pem = std_dh_prime_named(dhexpanded)))
{
tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
- host, US strerror(errno));
+ host, US strerror(errno), errstr);
return FALSE;
}
bio = BIO_new_mem_buf(CS pem, -1);
{
BIO_free(bio);
tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
- host, NULL);
+ host, NULL, errstr);
return FALSE;
}
+/* note: our default limit of 2236 is not a multiple of 8; the limit comes from
+ * an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with
+ * 2236. But older OpenSSL can only report in bytes (octets), not bits.
+ * If someone wants to dance at the edge, then they can raise the limit or use
+ * current libraries. */
+#ifdef EXIM_HAVE_OPENSSL_DH_BITS
+/* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022
+ * This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */
+dh_bitsize = DH_bits(dh);
+#else
+dh_bitsize = 8 * DH_size(dh);
+#endif
+
/* Even if it is larger, we silently return success rather than cause things
* to fail out, so that a too-large DH will not knock out all TLS; it's a
* debatable choice. */
-if ((8*DH_size(dh)) > tls_dh_max_bits)
+if (dh_bitsize > tls_dh_max_bits)
{
DEBUG(D_tls)
- debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d",
- 8*DH_size(dh), tls_dh_max_bits);
+ debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d\n",
+ dh_bitsize, tls_dh_max_bits);
}
else
{
SSL_CTX_set_tmp_dh(sctx, dh);
DEBUG(D_tls)
debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
- dhexpanded ? dhexpanded : US"default", 8*DH_size(dh));
+ dhexpanded ? dhexpanded : US"default", dh_bitsize);
}
DH_free(dh);
Arguments:
sctx The current SSL CTX (inbound or outbound)
host connected host, if client; NULL if server
+ errstr error string pointer
Returns: TRUE if OK (nothing to set up, or setup worked)
*/
static BOOL
-init_ecdh(SSL_CTX * sctx, host_item * host)
+init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr)
{
#ifdef OPENSSL_NO_ECDH
return TRUE;
return TRUE;
# else
-if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve))
+if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr))
return FALSE;
if (!exp_curve || !*exp_curve)
return TRUE;
#if OPENSSL_VERSION_NUMBER < 0x10002000L
DEBUG(D_tls) debug_printf(
"ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
- exp_curve = "prime256v1";
+ exp_curve = US"prime256v1";
#else
# if defined SSL_CTRL_SET_ECDH_AUTO
DEBUG(D_tls) debug_printf(
# endif
)
{
- tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'",
- exp_curve),
- host, NULL);
+ tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve),
+ host, NULL, errstr);
return FALSE;
}
if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
{
- tls_error(US"Unable to create ec curve", host, NULL);
+ tls_error(US"Unable to create ec curve", host, NULL, errstr);
return FALSE;
}
not to the stability of the interface. */
if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
- tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL);
+ tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr);
else
DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve);
SNI handling.
Separately we might try to replace using OCSP_basic_verify() - which seems to not
-be a public interface into the OpenSSL library (there's no manual entry) -
+be a public interface into the OpenSSL library (there's no manual entry) -
But what with? We also use OCSP_basic_verify in the client stapling callback.
And there we NEED it; we must verify that status... unless the
library does it for us anyway? */
{
DEBUG(D_tls)
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
debug_printf("OCSP response verify failure: %s\n", US ssl_errstring);
}
goto bad;
}
supply_response:
- cbinfo->u_ocsp.server.response = resp;
+ cbinfo->u_ocsp.server.response = resp; /*XXX stack?*/
return;
bad:
- if (running_in_test_harness)
+ if (f.running_in_test_harness)
{
extern char ** environ;
uschar ** p;
- if (environ) for (p = USS environ; *p != NULL; p++)
+ if (environ) for (p = USS environ; *p; p++)
if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
{
DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
/* Create and install a selfsigned certificate, for use in server mode */
static int
-tls_install_selfsign(SSL_CTX * sctx)
+tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr)
{
X509 * x509 = NULL;
EVP_PKEY * pkey;
goto err;
where = US"generating pkey";
- /* deprecated, use RSA_generate_key_ex() */
-if (!(rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL)))
+if (!(rsa = rsa_callback(NULL, 0, 2048)))
goto err;
where = US"assigning pkey";
goto err;
X509_set_version(x509, 2); /* N+1 - version 3 */
-ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
+ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60); /* 1 hour */
X509_set_pubkey(x509, pkey);
return OK;
err:
- (void) tls_error(where, NULL, NULL);
+ (void) tls_error(where, NULL, NULL, errstr);
if (x509) X509_free(x509);
if (pkey) EVP_PKEY_free(pkey);
return DEFER;
+static int
+tls_add_certfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+ uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_certificate file %s\n", file);
+if (!SSL_CTX_use_certificate_chain_file(sctx, CS file))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_certificate_chain_file file=%s", file),
+ cbinfo->host, NULL, errstr);
+return 0;
+}
+
+static int
+tls_add_pkeyfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+ uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", file);
+if (!SSL_CTX_use_PrivateKey_file(sctx, CS file, SSL_FILETYPE_PEM))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_PrivateKey_file file=%s", file), cbinfo->host, NULL, errstr);
+return 0;
+}
+
+
/*************************************************
* Expand key and cert file specs *
*************************************************/
Arguments:
sctx the SSL_CTX* to update
cbinfo various parts of session state
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
static int
-tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo)
+tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo,
+ uschar ** errstr)
{
uschar *expanded;
if (!cbinfo->certificate)
{
- if (cbinfo->host) /* client */
+ if (!cbinfo->is_server) /* client */
return OK;
- /* server */
- if (tls_install_selfsign(sctx) != OK)
+ /* server */
+ if (tls_install_selfsign(sctx, errstr) != OK)
return DEFER;
}
else
{
+ int err;
+
if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
Ustrstr(cbinfo->certificate, US"tls_out_sni")
)
reexpand_tls_files_for_sni = TRUE;
- if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded))
+ if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded, errstr))
return DEFER;
- if (expanded != NULL)
- {
- DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
- if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded))
- return tls_error(string_sprintf(
- "SSL_CTX_use_certificate_chain_file file=%s", expanded),
- cbinfo->host, NULL);
- }
+ if (expanded)
+ if (cbinfo->is_server)
+ {
+ const uschar * file_list = expanded;
+ int sep = 0;
+ uschar * file;
- if (cbinfo->privatekey != NULL &&
- !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded))
+ while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ if ((err = tls_add_certfile(sctx, cbinfo, file, errstr)))
+ return err;
+ }
+ else /* would there ever be a need for multiple client certs? */
+ if ((err = tls_add_certfile(sctx, cbinfo, expanded, errstr)))
+ return err;
+
+ if ( cbinfo->privatekey
+ && !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr))
return DEFER;
/* If expansion was forced to fail, key_expanded will be NULL. If the result
key is in the same file as the certificate. */
if (expanded && *expanded)
- {
- DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
- if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM))
- return tls_error(string_sprintf(
- "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
- }
+ if (cbinfo->is_server)
+ {
+ const uschar * file_list = expanded;
+ int sep = 0;
+ uschar * file;
+
+ while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ if ((err = tls_add_pkeyfile(sctx, cbinfo, file, errstr)))
+ return err;
+ }
+ else /* would there ever be a need for multiple client certs? */
+ if ((err = tls_add_pkeyfile(sctx, cbinfo, expanded, errstr)))
+ return err;
}
#ifndef DISABLE_OCSP
if (cbinfo->is_server && cbinfo->u_ocsp.server.file)
{
- if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded))
+ /*XXX stack*/
+ if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded, errstr))
return DEFER;
if (expanded && *expanded)
tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
int rc;
int old_pool = store_pool;
+uschar * dummy_errstr;
if (!servername)
return SSL_TLSEXT_ERR_OK;
not confident that memcpy wouldn't break some internal reference counting.
Especially since there's a references struct member, which would be off. */
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(server_sni = SSL_CTX_new(TLS_server_method())))
+#else
if (!(server_sni = SSL_CTX_new(SSLv23_server_method())))
+#endif
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
- return SSL_TLSEXT_ERR_NOACK;
+ goto bad;
}
/* Not sure how many of these are actually needed, since SSL object
SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
-if ( !init_dh(server_sni, cbinfo->dhparam, NULL)
- || !init_ecdh(server_sni, NULL)
+if ( !init_dh(server_sni, cbinfo->dhparam, NULL, &dummy_errstr)
+ || !init_ecdh(server_sni, NULL, &dummy_errstr)
)
- return SSL_TLSEXT_ERR_NOACK;
+ goto bad;
+
+if ( cbinfo->server_cipher_list
+ && !SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list))
+ goto bad;
-if (cbinfo->server_cipher_list)
- SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
#ifndef DISABLE_OCSP
if (cbinfo->u_ocsp.server.file)
{
#endif
if ((rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE,
- verify_callback_server)) != OK)
- return SSL_TLSEXT_ERR_NOACK;
+ verify_callback_server, &dummy_errstr)) != OK)
+ goto bad;
/* do this after setup_certs, because this can require the certs for verifying
OCSP information. */
-if ((rc = tls_expand_session_files(server_sni, cbinfo)) != OK)
- return SSL_TLSEXT_ERR_NOACK;
+if ((rc = tls_expand_session_files(server_sni, cbinfo, &dummy_errstr)) != OK)
+ goto bad;
DEBUG(D_tls) debug_printf("Switching SSL context.\n");
SSL_set_SSL_CTX(s, server_sni);
-
return SSL_TLSEXT_ERR_OK;
+
+bad: return SSL_TLSEXT_ERR_ALERT_FATAL;
}
#endif /* EXIM_HAVE_OPENSSL_TLSEXT */
tls_server_stapling_cb(SSL *s, void *arg)
{
const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
-uschar *response_der;
+uschar *response_der; /*XXX blob */
int response_der_len;
+/*XXX stack: use SSL_get_certificate() to see which cert; from that work
+out which ocsp blob to send. Unfortunately, SSL_get_certificate is known
+buggy in current OpenSSL; it returns the last cert loaded always rather than
+the one actually presented. So we can't support a stack of OCSP proofs at
+this time. */
+
DEBUG(D_tls)
debug_printf("Received TLS status request (OCSP stapling); %s response\n",
cbinfo->u_ocsp.server.response ? "have" : "lack");
return SSL_TLSEXT_ERR_NOACK;
response_der = NULL;
-response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, /*XXX stack*/
&response_der);
if (response_der_len <= 0)
return SSL_TLSEXT_ERR_NOACK;
int status, reason;
ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
- DEBUG(D_tls) bp = BIO_new_fp(stderr, BIO_NOCLOSE);
+ DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
/*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */
cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
{
tls_out.ocsp = OCSP_FAILED;
- if (LOGGING(tls_cipher))
- log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
+ if (LOGGING(tls_cipher)) log_write(0, LOG_MAIN,
+ "Received TLS cert status response, itself unverifiable: %s",
+ ERR_reason_error_string(ERR_peek_error()));
BIO_printf(bp, "OCSP response verify failure\n");
ERR_print_errors(bp);
+ OCSP_RESPONSE_print(bp, rsp, 0);
goto failed;
}
ocsp_file file of stapling info (server); flag for require ocsp (client)
addr address if client; NULL if server (for some randomness)
cbp place to put allocated callback context
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
uschar *privatekey,
#ifndef DISABLE_OCSP
- uschar *ocsp_file,
+ uschar *ocsp_file, /*XXX stack, in server*/
#endif
- address_item *addr, tls_ext_ctx_cb ** cbp)
+ address_item *addr, tls_ext_ctx_cb ** cbp, uschar ** errstr)
{
+SSL_CTX * ctx;
long init_options;
int rc;
-BOOL okay;
tls_ext_ctx_cb * cbinfo;
cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
cbinfo->certificate = certificate;
cbinfo->privatekey = privatekey;
+cbinfo->is_server = host==NULL;
#ifndef DISABLE_OCSP
cbinfo->verify_stack = NULL;
-if ((cbinfo->is_server = host==NULL))
+if (!host)
{
cbinfo->u_ocsp.server.file = ocsp_file;
cbinfo->u_ocsp.server.file_expanded = NULL;
cbinfo->event_action = NULL;
#endif
+#ifdef EXIM_NEED_OPENSSL_INIT
SSL_load_error_strings(); /* basic set up */
OpenSSL_add_ssl_algorithms();
+#endif
#ifdef EXIM_HAVE_SHA256
/* SHA256 is becoming ever more popular. This makes sure it gets added to the
By disabling with openssl_options, we can let admins re-enable with the
existing knob. */
-*ctxp = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method());
-
-if (!*ctxp) return tls_error(US"SSL_CTX_new", host, NULL);
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(ctx = SSL_CTX_new(host ? TLS_client_method() : TLS_server_method())))
+#else
+if (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method())))
+#endif
+ return tls_error(US"SSL_CTX_new", host, NULL, errstr);
/* It turns out that we need to seed the random number generator this early in
order to get the full complement of ciphers to work. It took me roughly a day
gettimeofday(&r.tv, NULL);
r.p = getpid();
- RAND_seed((uschar *)(&r), sizeof(r));
- RAND_seed((uschar *)big_buffer, big_buffer_size);
- if (addr != NULL) RAND_seed((uschar *)addr, sizeof(addr));
+ RAND_seed(US (&r), sizeof(r));
+ RAND_seed(US big_buffer, big_buffer_size);
+ if (addr != NULL) RAND_seed(US addr, sizeof(addr));
if (!RAND_status())
return tls_error(US"RAND_status", host,
- US"unable to seed random number generator");
+ US"unable to seed random number generator", errstr);
}
/* Set up the information callback, which outputs if debugging is at a suitable
level. */
-DEBUG(D_tls) SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
+DEBUG(D_tls) SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
/* Automatically re-try reads/writes after renegotiation. */
-(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
+(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
/* Apply administrator-supplied work-arounds.
Historically we applied just one requested option,
No OpenSSL version number checks: the options we accept depend upon the
availability of the option value macros from OpenSSL. */
-okay = tls_openssl_options_parse(openssl_options, &init_options);
-if (!okay)
- return tls_error(US"openssl_options parsing failed", host, NULL);
+if (!tls_openssl_options_parse(openssl_options, &init_options))
+ return tls_error(US"openssl_options parsing failed", host, NULL, errstr);
if (init_options)
{
DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
- if (!(SSL_CTX_set_options(*ctxp, init_options)))
+ if (!(SSL_CTX_set_options(ctx, init_options)))
return tls_error(string_sprintf(
- "SSL_CTX_set_option(%#lx)", init_options), host, NULL);
+ "SSL_CTX_set_option(%#lx)", init_options), host, NULL, errstr);
}
else
DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
+/* We'd like to disable session cache unconditionally, but foolish Outlook
+Express clients then give up the first TLS connection and make a second one
+(which works). Only when there is an IMAP service on the same machine.
+Presumably OE is trying to use the cache for A on B. Leave it enabled for
+now, until we work out a decent way of presenting control to the config. It
+will never be used because we use a new context every time. */
+#ifdef notdef
+(void) SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+#endif
+
/* Initialize with DH parameters if supplied */
/* Initialize ECDH temp key parameter selection */
-if ( !init_dh(*ctxp, dhparam, host)
- || !init_ecdh(*ctxp, host)
+if ( !init_dh(ctx, dhparam, host, errstr)
+ || !init_ecdh(ctx, host, errstr)
)
return DEFER;
/* Set up certificate and key (and perhaps OCSP info) */
-if ((rc = tls_expand_session_files(*ctxp, cbinfo)) != OK)
+if ((rc = tls_expand_session_files(ctx, cbinfo, errstr)) != OK)
return rc;
/* If we need to handle SNI or OCSP, do so */
}
# endif
-if (host == NULL) /* server */
+if (!host) /* server */
{
# ifndef DISABLE_OCSP
/* We check u_ocsp.server.file, not server.response, because we care about if
callback is invoked. */
if (cbinfo->u_ocsp.server.file)
{
- SSL_CTX_set_tlsext_status_cb(server_ctx, tls_server_stapling_cb);
- SSL_CTX_set_tlsext_status_arg(server_ctx, cbinfo);
+ SSL_CTX_set_tlsext_status_cb(ctx, tls_server_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
}
# endif
/* We always do this, so that $tls_sni is available even if not used in
tls_certificate */
- SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
- SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
+ SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
+ SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
}
# ifndef DISABLE_OCSP
else /* client */
DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n");
return FAIL;
}
- SSL_CTX_set_tlsext_status_cb(*ctxp, tls_client_stapling_cb);
- SSL_CTX_set_tlsext_status_arg(*ctxp, cbinfo);
+ SSL_CTX_set_tlsext_status_cb(ctx, tls_client_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
}
# endif
#endif
#ifdef EXIM_HAVE_EPHEM_RSA_KEX
/* Set up the RSA callback */
-SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
+SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
#endif
/* Finally, set the timeout, and we are done */
-SSL_CTX_set_timeout(*ctxp, ssl_session_timeout);
+SSL_CTX_set_timeout(ctx, ssl_session_timeout);
DEBUG(D_tls) debug_printf("Initialized TLS\n");
*cbp = cbinfo;
+*ctxp = ctx;
return OK;
}
static void
construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
{
-/* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't
+/* With OpenSSL 1.0.0a, 'c' needs to be const but the documentation doesn't
yet reflect that. It should be a safe change anyway, even 0.9.8 versions have
the accessor functions use const in the prototype. */
-const SSL_CIPHER *c;
-const uschar *ver;
-ver = (const uschar *)SSL_get_version(ssl);
+const uschar * ver = CUS SSL_get_version(ssl);
+const SSL_CIPHER * c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
-c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
SSL_CIPHER_get_bits(c, bits);
string_format(cipherbuf, bsize, "%s:%s:%u", ver,
static void
-peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned bsize)
+peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned siz)
{
/*XXX we might consider a list-of-certs variable for the cert chain.
SSL_get_peer_cert_chain(SSL*). We'd need a new variable type and support
in list-handling functions, also consider the difference between the entire
chain and the elements sent by the peer. */
+tlsp->peerdn = NULL;
+
/* Will have already noted peercert on a verify fail; possibly not the leaf */
if (!tlsp->peercert)
tlsp->peercert = SSL_get_peer_certificate(ssl);
/* Beware anonymous ciphers which lead to server_cert being NULL */
if (tlsp->peercert)
- {
- X509_NAME_oneline(X509_get_subject_name(tlsp->peercert), CS peerdn, bsize);
- peerdn[bsize-1] = '\0';
- tlsp->peerdn = peerdn; /*XXX a static buffer... */
- }
-else
- tlsp->peerdn = NULL;
+ if (!X509_NAME_oneline(X509_get_subject_name(tlsp->peercert), CS peerdn, siz))
+ { DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n"); }
+ else
+ {
+ peerdn[siz-1] = '\0';
+ tlsp->peerdn = peerdn; /*XXX a static buffer... */
+ }
}
* Set up for verifying certificates *
*************************************************/
+#ifndef DISABLE_OCSP
/* Load certs from file, return TRUE on success */
static BOOL
BIO * bp;
X509 * x;
+while (sk_X509_num(verify_stack) > 0)
+ X509_free(sk_X509_pop(verify_stack));
+
if (!(bp = BIO_new_file(CS file, "r"))) return FALSE;
while ((x = PEM_read_bio_X509(bp, NULL, 0, NULL)))
sk_X509_push(verify_stack, x);
BIO_free(bp);
return TRUE;
}
+#endif
-/* Called by both client and server startup
+/* Called by both client and server startup; on the server possibly
+repeated after a Server Name Indication.
Arguments:
sctx SSL_CTX* to initialise
optional TRUE if called from a server for a host in tls_try_verify_hosts;
otherwise passed as FALSE
cert_vfy_cb Callback function for certificate verification
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
static int
setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
- int (*cert_vfy_cb)(int, X509_STORE_CTX *) )
+ int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr)
{
uschar *expcerts, *expcrl;
-if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
+if (!expand_check(certs, US"tls_verify_certificates", &expcerts, errstr))
return DEFER;
+DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts);
if (expcerts && *expcerts)
{
CA bundle. Then add the ones specified in the config, if any. */
if (!SSL_CTX_set_default_verify_paths(sctx))
- return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
+ return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL, errstr);
if (Ustrcmp(expcerts, "system") != 0)
{
)
{
log_write(0, LOG_MAIN|LOG_PANIC,
- "failed to load cert hain from %s", file);
+ "failed to load cert chain from %s", file);
return DEFER;
}
#endif
if ( (!file || statbuf.st_size > 0)
&& !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
- return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
+ return tls_error(US"SSL_CTX_load_verify_locations", host, NULL, errstr);
/* Load the list of CAs for which we will accept certs, for sending
to the client. This is only for the one-file tls_verify_certificates
{
STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
+ SSL_CTX_set_client_CA_list(sctx, names);
DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
sk_X509_NAME_num(names));
- SSL_CTX_set_client_CA_list(sctx, names);
}
}
}
OpenSSL will then handle the verify against CA certs and CRLs by
itself in the verify callback." */
- if (!expand_check(crl, US"tls_crl", &expcrl)) return DEFER;
+ if (!expand_check(crl, US"tls_crl", &expcrl, errstr)) return DEFER;
if (expcrl && *expcrl)
{
struct stat statbufcrl;
DEBUG(D_tls) debug_printf("SSL CRL value is a file %s\n", file);
}
if (X509_STORE_load_locations(cvstore, CS file, CS dir) == 0)
- return tls_error(US"X509_STORE_load_locations", host, NULL);
+ return tls_error(US"X509_STORE_load_locations", host, NULL, errstr);
/* setting the flags to check against the complete crl chain */
/* If verification is optional, don't fail if no certificate */
SSL_CTX_set_verify(sctx,
- SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
+ SSL_VERIFY_PEER | (optional ? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
cert_vfy_cb);
}
Arguments:
require_ciphers allowed ciphers
+ errstr pointer to error message
Returns: OK on success
DEFER for errors before the start of the negotiation
*/
int
-tls_server_start(const uschar *require_ciphers)
+tls_server_start(const uschar * require_ciphers, uschar ** errstr)
{
int rc;
-uschar *expciphers;
-tls_ext_ctx_cb *cbinfo;
+uschar * expciphers;
+tls_ext_ctx_cb * cbinfo;
static uschar peerdn[256];
static uschar cipherbuf[256];
/* Check for previous activation */
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
{
- tls_error(US"STARTTLS received after TLS started", NULL, US"");
- smtp_printf("554 Already in TLS\r\n");
+ tls_error(US"STARTTLS received after TLS started", NULL, US"", errstr);
+ smtp_printf("554 Already in TLS\r\n", FALSE);
return FAIL;
}
rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
#ifndef DISABLE_OCSP
- tls_ocsp_file,
+ tls_ocsp_file, /*XXX stack*/
#endif
- NULL, &server_static_cbinfo);
+ NULL, &server_static_cbinfo, errstr);
if (rc != OK) return rc;
cbinfo = server_static_cbinfo;
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers, errstr))
return FAIL;
/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
were historically separated by underscores. So that I can use either form in my
tests, and also for general convenience, we turn underscores into hyphens here.
+
+XXX SSL_CTX_set_cipher_list() is replaced by SSL_CTX_set_ciphersuites()
+for TLS 1.3 . Since we do not call it at present we get the default list:
+TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
*/
if (expciphers)
while (*s != 0) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers))
- return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
+ return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr);
cbinfo->server_cipher_list = expciphers;
}
optional, set up appropriately. */
tls_in.certificate_verified = FALSE;
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
tls_in.dane_verified = FALSE;
#endif
server_verify_callback_called = FALSE;
if (verify_check_host(&tls_verify_hosts) == OK)
{
rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
- FALSE, verify_callback_server);
+ FALSE, verify_callback_server, errstr);
if (rc != OK) return rc;
server_verify_optional = FALSE;
}
else if (verify_check_host(&tls_try_verify_hosts) == OK)
{
rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
- TRUE, verify_callback_server);
+ TRUE, verify_callback_server, errstr);
if (rc != OK) return rc;
server_verify_optional = TRUE;
}
/* Prepare for new connection */
-if (!(server_ssl = SSL_new(server_ctx))) return tls_error(US"SSL_new", NULL, NULL);
+if (!(server_ssl = SSL_new(server_ctx)))
+ return tls_error(US"SSL_new", NULL, NULL, errstr);
/* Warning: we used to SSL_clear(ssl) here, it was removed.
*
SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
if (!tls_in.on_connect)
{
- smtp_printf("220 TLS go ahead\r\n");
+ smtp_printf("220 TLS go ahead\r\n", FALSE);
fflush(smtp_out);
}
DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
sigalrm_seen = FALSE;
-if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
rc = SSL_accept(server_ssl);
-alarm(0);
+ALARM_CLR(0);
if (rc <= 0)
{
- tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL);
- if (ERR_get_error() == 0)
- log_write(0, LOG_MAIN,
- "TLS client disconnected cleanly (rejected our certificate?)");
+ (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
return FAIL;
}
DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
+ERR_clear_error(); /* Even success can leave errors in the stack. Seen with
+ anon-authentication ciphersuite negociated. */
/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize things. */
smtp_read_response()/ip_recv().
Hence no need to duplicate for _in and _out.
*/
-ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
+if (!ssl_xfer_buffer) ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
-ssl_xfer_eof = ssl_xfer_error = 0;
+ssl_xfer_eof = ssl_xfer_error = FALSE;
receive_getc = tls_getc;
+receive_getbuf = tls_getbuf;
receive_get_cache = tls_get_cache;
receive_ungetc = tls_ungetc;
receive_feof = tls_feof;
receive_ferror = tls_ferror;
receive_smtp_buffered = tls_smtp_buffered;
-tls_in.active = fileno(smtp_out);
+tls_in.active.sock = fileno(smtp_out);
+tls_in.active.tls_ctx = NULL; /* not using explicit ctx for server-side */
return OK;
}
static int
tls_client_basic_ctx_init(SSL_CTX * ctx,
- host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo
- )
+ host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo,
+ uschar ** errstr)
{
int rc;
/* stick to the old behaviour for compatibility if tls_verify_certificates is
if ( ( !ob->tls_verify_hosts
&& (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
)
- || (verify_check_given_host(&ob->tls_verify_hosts, host) == OK)
+ || verify_check_given_host(CUSS &ob->tls_verify_hosts, host) == OK
)
client_verify_optional = FALSE;
-else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
+else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK)
client_verify_optional = TRUE;
else
return OK;
if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
- ob->tls_crl, host, client_verify_optional, verify_callback_client)) != OK)
+ ob->tls_crl, host, client_verify_optional, verify_callback_client,
+ errstr)) != OK)
return rc;
-if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
{
cbinfo->verify_cert_hostnames =
#ifdef SUPPORT_I18N
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
static int
-dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa)
+dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa, uschar ** errstr)
{
dns_record * rr;
dns_scan dnss;
int found = 0;
if (DANESSL_init(ssl, NULL, hostnames) != 1)
- return tls_error(US"hostnames load", host, NULL);
+ return tls_error(US"hostnames load", host, NULL, errstr);
for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
rr;
rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
- ) if (rr->type == T_TLSA)
+ ) if (rr->type == T_TLSA && rr->size > 3)
{
const uschar * p = rr->data;
uint8_t usage, selector, mtype;
switch (DANESSL_add_tlsa(ssl, usage, selector, mdname, p, rr->size - 3))
{
default:
- return tls_error(US"tlsa load", host, NULL);
+ return tls_error(US"tlsa load", host, NULL, errstr);
case 0: /* action not taken */
case 1: break;
}
log_write(0, LOG_MAIN, "DANE error: No usable TLSA records");
return DEFER;
}
-#endif /*EXPERIMENTAL_DANE*/
+#endif /*SUPPORT_DANE*/
Argument:
fd the fd of the connection
- host connected host (for messages)
- addr the first address
+ host connected host (for messages and option-tests)
+ addr the first address (for some randomness; can be NULL)
tb transport (always smtp)
tlsa_dnsa tlsa lookup, if DANE, else null
+ tlsp record details of channel configuration here; must be non-NULL
+ errstr error string pointer
-Returns: OK on success
- FAIL otherwise - note that tls_error() will not give DEFER
- because this is not a server
+Returns: Pointer to TLS session context, or NULL on error
*/
-int
+void *
tls_client_start(int fd, host_item *host, address_item *addr,
- transport_instance *tb
-#ifdef EXPERIMENTAL_DANE
- , dns_answer * tlsa_dnsa
+ transport_instance * tb,
+#ifdef SUPPORT_DANE
+ dns_answer * tlsa_dnsa,
#endif
- )
+ tls_support * tlsp, uschar ** errstr)
{
-smtp_transport_options_block * ob =
- (smtp_transport_options_block *)tb->options_block;
+smtp_transport_options_block * ob = tb
+ ? (smtp_transport_options_block *)tb->options_block
+ : &smtp_transport_option_defaults;
+exim_openssl_client_tls_ctx * exim_client_ctx;
static uschar peerdn[256];
uschar * expciphers;
int rc;
BOOL require_ocsp = FALSE;
#endif
-#ifdef EXPERIMENTAL_DANE
-tls_out.tlsa_usage = 0;
+rc = store_pool;
+store_pool = POOL_PERM;
+exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx));
+store_pool = rc;
+
+#ifdef SUPPORT_DANE
+tlsp->tlsa_usage = 0;
#endif
#ifndef DISABLE_OCSP
{
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
if ( tlsa_dnsa
&& ob->hosts_request_ocsp[0] == '*'
&& ob->hosts_request_ocsp[1] == '\0'
# endif
if ((require_ocsp =
- verify_check_given_host(&ob->hosts_require_ocsp, host) == OK))
+ verify_check_given_host(CUSS &ob->hosts_require_ocsp, host) == OK))
request_ocsp = TRUE;
else
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
if (!request_ocsp)
# endif
request_ocsp =
- verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+ verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
}
#endif
-rc = tls_init(&client_ctx, host, NULL,
+rc = tls_init(&exim_client_ctx->ctx, host, NULL,
ob->tls_certificate, ob->tls_privatekey,
#ifndef DISABLE_OCSP
(void *)(long)request_ocsp,
#endif
- addr, &client_static_cbinfo);
-if (rc != OK) return rc;
+ addr, &client_static_cbinfo, errstr);
+if (rc != OK) return NULL;
-tls_out.certificate_verified = FALSE;
+tlsp->certificate_verified = FALSE;
client_verify_callback_called = FALSE;
-if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
- &expciphers))
- return FAIL;
+expciphers = NULL;
+#ifdef SUPPORT_DANE
+if (tlsa_dnsa)
+ {
+ /* We fall back to tls_require_ciphers if unset, empty or forced failure, but
+ other failures should be treated as problems. */
+ if (ob->dane_require_tls_ciphers &&
+ !expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
+ &expciphers, errstr))
+ return NULL;
+ if (expciphers && *expciphers == '\0')
+ expciphers = NULL;
+ }
+#endif
+if (!expciphers &&
+ !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
+ &expciphers, errstr))
+ return NULL;
/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
are separated by underscores. So that I can use either form in my tests, and
also for general convenience, we turn underscores into hyphens here. */
-if (expciphers != NULL)
+if (expciphers)
{
uschar *s = expciphers;
- while (*s != 0) { if (*s == '_') *s = '-'; s++; }
+ while (*s) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
- if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers))
- return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
+ if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers))
+ {
+ tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr);
+ return NULL;
+ }
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tlsa_dnsa)
{
- SSL_CTX_set_verify(client_ctx,
+ SSL_CTX_set_verify(exim_client_ctx->ctx,
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
verify_callback_client_dane);
if (!DANESSL_library_init())
- return tls_error(US"library init", host, NULL);
- if (DANESSL_CTX_init(client_ctx) <= 0)
- return tls_error(US"context init", host, NULL);
+ {
+ tls_error(US"library init", host, NULL, errstr);
+ return NULL;
+ }
+ if (DANESSL_CTX_init(exim_client_ctx->ctx) <= 0)
+ {
+ tls_error(US"context init", host, NULL, errstr);
+ return NULL;
+ }
}
else
#endif
- if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob, client_static_cbinfo))
- != OK)
- return rc;
+ if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob,
+ client_static_cbinfo, errstr) != OK)
+ return NULL;
-if ((client_ssl = SSL_new(client_ctx)) == NULL)
- return tls_error(US"SSL_new", host, NULL);
-SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx));
-SSL_set_fd(client_ssl, fd);
-SSL_set_connect_state(client_ssl);
+if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
+ {
+ tls_error(US"SSL_new", host, NULL, errstr);
+ return NULL;
+ }
+SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
+SSL_set_fd(exim_client_ctx->ssl, fd);
+SSL_set_connect_state(exim_client_ctx->ssl);
if (ob->tls_sni)
{
- if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni))
- return FAIL;
- if (tls_out.sni == NULL)
+ if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr))
+ return NULL;
+ if (!tlsp->sni)
{
DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
}
- else if (!Ustrlen(tls_out.sni))
- tls_out.sni = NULL;
+ else if (!Ustrlen(tlsp->sni))
+ tlsp->sni = NULL;
else
{
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
- DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
- SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
+ DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni);
+ SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni);
#else
log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n",
- tls_out.sni);
+ tlsp->sni);
#endif
}
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tlsa_dnsa)
- if ((rc = dane_tlsa_load(client_ssl, host, tlsa_dnsa)) != OK)
- return rc;
+ if (dane_tlsa_load(exim_client_ctx->ssl, host, tlsa_dnsa, errstr) != OK)
+ return NULL;
#endif
#ifndef DISABLE_OCSP
/* Request certificate status at connection-time. If the server
does OCSP stapling we will get the callback (set in tls_init()) */
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
if (request_ocsp)
{
const uschar * s;
{ /* Re-eval now $tls_out_tlsa_usage is populated. If
this means we avoid the OCSP request, we wasted the setup
cost in tls_init(). */
- require_ocsp = verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+ require_ocsp = verify_check_given_host(CUSS &ob->hosts_require_ocsp, host) == OK;
request_ocsp = require_ocsp
- || verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+ || verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
}
}
# endif
if (request_ocsp)
{
- SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
+ SSL_set_tlsext_status_type(exim_client_ctx->ssl, TLSEXT_STATUSTYPE_ocsp);
client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
- tls_out.ocsp = OCSP_NOT_RESP;
+ tlsp->ocsp = OCSP_NOT_RESP;
}
#endif
#ifndef DISABLE_EVENT
-client_static_cbinfo->event_action = tb->event_action;
+client_static_cbinfo->event_action = tb ? tb->event_action : NULL;
#endif
/* There doesn't seem to be a built-in timeout on connection. */
DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
sigalrm_seen = FALSE;
-alarm(ob->command_timeout);
-rc = SSL_connect(client_ssl);
-alarm(0);
+ALARM(ob->command_timeout);
+rc = SSL_connect(exim_client_ctx->ssl);
+ALARM_CLR(0);
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tlsa_dnsa)
- DANESSL_cleanup(client_ssl);
+ DANESSL_cleanup(exim_client_ctx->ssl);
#endif
if (rc <= 0)
- return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);
+ {
+ tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr);
+ return NULL;
+ }
DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
-peer_cert(client_ssl, &tls_out, peerdn, sizeof(peerdn));
+peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));
-construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
-tls_out.cipher = cipherbuf;
+construct_cipher_name(exim_client_ctx->ssl, cipherbuf, sizeof(cipherbuf), &tlsp->bits);
+tlsp->cipher = cipherbuf;
/* Record the certificate we presented */
{
- X509 * crt = SSL_get_certificate(client_ssl);
- tls_out.ourcert = crt ? X509_dup(crt) : NULL;
+ X509 * crt = SSL_get_certificate(exim_client_ctx->ssl);
+ tlsp->ourcert = crt ? X509_dup(crt) : NULL;
}
-tls_out.active = fd;
-return OK;
+tlsp->active.sock = fd;
+tlsp->active.tls_ctx = exim_client_ctx;
+return exim_client_ctx;
}
-/*************************************************
-* TLS version of getc *
-*************************************************/
-
-/* This gets the next byte from the TLS input buffer. If the buffer is empty,
-it refills the buffer via the SSL reading function.
-
-Arguments: lim Maximum amount to read/buffer
-Returns: the next character or EOF
+static BOOL
+tls_refill(unsigned lim)
+{
+int error;
+int inbytes;
-Only used by the server-side TLS.
-*/
+DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
+ ssl_xfer_buffer, ssl_xfer_buffer_size);
-int
-tls_getc(unsigned lim)
-{
-if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
- {
- int error;
- int inbytes;
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
+inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer,
+ MIN(ssl_xfer_buffer_size, lim));
+error = SSL_get_error(server_ssl, inbytes);
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
- DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
- ssl_xfer_buffer, ssl_xfer_buffer_size);
+if (had_command_timeout) /* set by signal handler */
+ smtp_command_timeout_exit(); /* does not return */
+if (had_command_sigterm)
+ smtp_command_sigterm_exit();
+if (had_data_timeout)
+ smtp_data_timeout_exit();
+if (had_data_sigint)
+ smtp_data_sigint_exit();
- if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
- inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer,
- MIN(ssl_xfer_buffer_size, lim));
- error = SSL_get_error(server_ssl, inbytes);
- alarm(0);
+/* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
+closed down, not that the socket itself has been closed down. Revert to
+non-SSL handling. */
- /* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
- closed down, not that the socket itself has been closed down. Revert to
- non-SSL handling. */
+switch(error)
+ {
+ case SSL_ERROR_NONE:
+ break;
- if (error == SSL_ERROR_ZERO_RETURN)
- {
+ case SSL_ERROR_ZERO_RETURN:
DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
receive_getc = smtp_getc;
+ receive_getbuf = smtp_getbuf;
receive_get_cache = smtp_get_cache;
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
receive_ferror = smtp_ferror;
receive_smtp_buffered = smtp_buffered;
+ if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
+ SSL_shutdown(server_ssl);
+
+#ifndef DISABLE_OCSP
+ sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
+ server_static_cbinfo->verify_stack = NULL;
+#endif
SSL_free(server_ssl);
+ SSL_CTX_free(server_ctx);
+ server_ctx = NULL;
server_ssl = NULL;
- tls_in.active = -1;
+ tls_in.active.sock = -1;
+ tls_in.active.tls_ctx = NULL;
tls_in.bits = 0;
tls_in.cipher = NULL;
tls_in.peerdn = NULL;
tls_in.sni = NULL;
- return smtp_getc(lim);
- }
+ return FALSE;
/* Handle genuine errors */
-
- else if (error == SSL_ERROR_SSL)
- {
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ case SSL_ERROR_SSL:
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
- ssl_xfer_error = 1;
- return EOF;
- }
+ ssl_xfer_error = TRUE;
+ return FALSE;
- else if (error != SSL_ERROR_NONE)
- {
+ default:
DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
- ssl_xfer_error = 1;
- return EOF;
- }
+ DEBUG(D_tls) if (error == SSL_ERROR_SYSCALL)
+ debug_printf(" - syscall %s\n", strerror(errno));
+ ssl_xfer_error = TRUE;
+ return FALSE;
+ }
#ifndef DISABLE_DKIM
- dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
#endif
- ssl_xfer_buffer_hwm = inbytes;
- ssl_xfer_buffer_lwm = 0;
- }
+ssl_xfer_buffer_hwm = inbytes;
+ssl_xfer_buffer_lwm = 0;
+return TRUE;
+}
+
+
+/*************************************************
+* TLS version of getc *
+*************************************************/
+
+/* This gets the next byte from the TLS input buffer. If the buffer is empty,
+it refills the buffer via the SSL reading function.
+
+Arguments: lim Maximum amount to read/buffer
+Returns: the next character or EOF
+
+Only used by the server-side TLS.
+*/
+
+int
+tls_getc(unsigned lim)
+{
+if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
+ if (!tls_refill(lim))
+ return ssl_xfer_error ? EOF : smtp_getc(lim);
/* Something in the buffer; return next uschar */
return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
}
+uschar *
+tls_getbuf(unsigned * len)
+{
+unsigned size;
+uschar * buf;
+
+if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
+ if (!tls_refill(*len))
+ {
+ if (!ssl_xfer_error) return smtp_getbuf(len);
+ *len = 0;
+ return NULL;
+ }
+
+if ((size = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm) > *len)
+ size = *len;
+buf = &ssl_xfer_buffer[ssl_xfer_buffer_lwm];
+ssl_xfer_buffer_lwm += size;
+*len = size;
+return buf;
+}
+
+
void
tls_get_cache()
{
}
+BOOL
+tls_could_read(void)
+{
+return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm || SSL_pending(server_ssl) > 0;
+}
+
/*************************************************
* Read bytes from TLS channel *
/*
Arguments:
+ ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len size of buffer
Returns: the number of bytes read
- -1 after a failed read
+ -1 after a failed read, including EOF
Only used by the client-side TLS.
*/
int
-tls_read(BOOL is_server, uschar *buff, size_t len)
+tls_read(void * ct_ctx, uschar *buff, size_t len)
{
-SSL *ssl = is_server ? server_ssl : client_ssl;
+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
int inbytes;
int error;
return -1;
}
else if (error != SSL_ERROR_NONE)
- {
return -1;
- }
return inbytes;
}
/*
Arguments:
- is_server channel specifier
+ ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len number of bytes
+ more further data expected soon
Returns: the number of bytes after a successful write,
-1 after a failed write
*/
int
-tls_write(BOOL is_server, const uschar *buff, size_t len)
+tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
{
-int outbytes;
-int error;
-int left = len;
-SSL *ssl = is_server ? server_ssl : client_ssl;
+int outbytes, error, left;
+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
+static gstring * corked = NULL;
+
+DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
+ buff, (unsigned long)len, more ? ", more" : "");
-DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
-while (left > 0)
+/* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when
+"more" is notified. This hack is only ok if small amounts are involved AND only
+one stream does it, in one context (i.e. no store reset). Currently it is used
+for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */
+/*XXX + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
+a store reset there. */
+
+if (!ct_ctx && (more || corked))
+ {
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ int save_pool = store_pool;
+ store_pool = POOL_PERM;
+#endif
+
+ corked = string_catn(corked, buff, len);
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ store_pool = save_pool;
+#endif
+
+ if (more)
+ return len;
+ buff = CUS corked->s;
+ len = corked->ptr;
+ corked = NULL;
+ }
+
+for (left = len; left > 0;)
{
- DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left);
+ DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
outbytes = SSL_write(ssl, CS buff, left);
error = SSL_get_error(ssl, outbytes);
DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
switch (error)
{
case SSL_ERROR_SSL:
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
return -1;
daemon, to shut down the TLS library, without actually doing a shutdown (which
would tamper with the SSL session in the parent process).
-Arguments: TRUE if SSL_shutdown is to be called
+Arguments:
+ ct_ctx client TLS context pointer, or NULL for the one global server context
+ shutdown 1 if TLS close-alert is to be sent,
+ 2 if also response to be waited for
+
Returns: nothing
Used by both server-side and client-side TLS.
*/
void
-tls_close(BOOL is_server, BOOL shutdown)
+tls_close(void * ct_ctx, int shutdown)
{
-SSL **sslp = is_server ? &server_ssl : &client_ssl;
-int *fdp = is_server ? &tls_in.active : &tls_out.active;
+exim_openssl_client_tls_ctx * o_ctx = ct_ctx;
+SSL_CTX **ctxp = o_ctx ? &o_ctx->ctx : &server_ctx;
+SSL **sslp = o_ctx ? &o_ctx->ssl : &server_ssl;
+int *fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock;
if (*fdp < 0) return; /* TLS was not active */
if (shutdown)
{
- DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n");
- SSL_shutdown(*sslp);
+ int rc;
+ DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+ shutdown > 1 ? " (with response-wait)" : "");
+
+ if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */
+ && shutdown > 1)
+ {
+ ALARM(2);
+ rc = SSL_shutdown(*sslp); /* wait for response */
+ ALARM_CLR(0);
+ }
+
+ if (rc < 0) DEBUG(D_tls)
+ {
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ debug_printf("SSL_shutdown: %s\n", ssl_errstring);
+ }
+ }
+
+#ifndef DISABLE_OCSP
+if (!o_ctx) /* server side */
+ {
+ sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
+ server_static_cbinfo->verify_stack = NULL;
}
+#endif
+SSL_CTX_free(*ctxp);
SSL_free(*sslp);
+*ctxp = NULL;
*sslp = NULL;
-
*fdp = -1;
}
/* this duplicates from tls_init(), we need a better "init just global
state, for no specific purpose" singleton function of our own */
+#ifdef EXIM_NEED_OPENSSL_INIT
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
+#endif
#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
/* SHA256 is becoming ever more popular. This makes sure it gets added to the
list of available digests. */
if (!(tls_require_ciphers && *tls_require_ciphers))
return NULL;
-if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers,
+ &err))
return US"failed to expand tls_require_ciphers";
if (!(expciphers && *expciphers))
err = NULL;
-ctx = SSL_CTX_new(SSLv23_server_method());
-if (!ctx)
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(ctx = SSL_CTX_new(TLS_server_method())))
+#else
+if (!(ctx = SSL_CTX_new(SSLv23_server_method())))
+#endif
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring);
}
if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
- err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed", expciphers);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed: %s",
+ expciphers, ssl_errstring);
}
SSL_CTX_free(ctx);
gettimeofday(&r.tv, NULL);
r.p = getpid();
- RAND_seed((uschar *)(&r), sizeof(r));
+ RAND_seed(US (&r), sizeof(r));
}
/* We're after pseudo-random, not random; if we still don't have enough data
in the internal PRNG then our options are limited. We could sleep and hope
Returns success or failure in parsing
*/
-struct exim_openssl_option {
- uschar *name;
- long value;
-};
-/* We could use a macro to expand, but we need the ifdef and not all the
-options document which version they were introduced in. Policylet: include
-all options unless explicitly for DTLS, let the administrator choose which
-to apply.
-
-This list is current as of:
- ==> 1.0.1b <==
-Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
-*/
-static struct exim_openssl_option exim_openssl_options[] = {
-/* KEEP SORTED ALPHABETICALLY! */
-#ifdef SSL_OP_ALL
- { US"all", SSL_OP_ALL },
-#endif
-#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
- { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
-#endif
-#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
- { US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE },
-#endif
-#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
- { US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS },
-#endif
-#ifdef SSL_OP_EPHEMERAL_RSA
- { US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA },
-#endif
-#ifdef SSL_OP_LEGACY_SERVER_CONNECT
- { US"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT },
-#endif
-#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
- { US"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER },
-#endif
-#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
- { US"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG },
-#endif
-#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
- { US"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING },
-#endif
-#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
- { US"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG },
-#endif
-#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
- { US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG },
-#endif
-#ifdef SSL_OP_NO_COMPRESSION
- { US"no_compression", SSL_OP_NO_COMPRESSION },
-#endif
-#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
- { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
-#endif
-#ifdef SSL_OP_NO_SSLv2
- { US"no_sslv2", SSL_OP_NO_SSLv2 },
-#endif
-#ifdef SSL_OP_NO_SSLv3
- { US"no_sslv3", SSL_OP_NO_SSLv3 },
-#endif
-#ifdef SSL_OP_NO_TICKET
- { US"no_ticket", SSL_OP_NO_TICKET },
-#endif
-#ifdef SSL_OP_NO_TLSv1
- { US"no_tlsv1", SSL_OP_NO_TLSv1 },
-#endif
-#ifdef SSL_OP_NO_TLSv1_1
-#if SSL_OP_NO_TLSv1_1 == 0x00000400L
- /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */
-#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring
-#else
- { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 },
-#endif
-#endif
-#ifdef SSL_OP_NO_TLSv1_2
- { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 },
-#endif
-#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
- { US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
-#endif
-#ifdef SSL_OP_SINGLE_DH_USE
- { US"single_dh_use", SSL_OP_SINGLE_DH_USE },
-#endif
-#ifdef SSL_OP_SINGLE_ECDH_USE
- { US"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE },
-#endif
-#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
- { US"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG },
-#endif
-#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
- { US"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG },
-#endif
-#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
- { US"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG },
-#endif
-#ifdef SSL_OP_TLS_D5_BUG
- { US"tls_d5_bug", SSL_OP_TLS_D5_BUG },
-#endif
-#ifdef SSL_OP_TLS_ROLLBACK_BUG
- { US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG },
-#endif
-};
-static int exim_openssl_options_size =
- sizeof(exim_openssl_options)/sizeof(struct exim_openssl_option);
static BOOL
uschar keep_c;
BOOL adding, item_parsed;
-result = 0L;
+result = SSL_OP_NO_TICKET;
/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
* from default because it increases BEAST susceptibility. */
#ifdef SSL_OP_NO_SSLv2
result |= SSL_OP_SINGLE_DH_USE;
#endif
-if (option_spec == NULL)
+if (!option_spec)
{
*results = result;
return TRUE;
return TRUE;
}
+#endif /*!MACRO_PREDEF*/
/* vi: aw ai sw=2
*/
/* End of tls-openssl.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* This module provides TLS (aka SSL) support for Exim. The code for OpenSSL is
#include "exim.h"
#include "transports/smtp.h"
+#if defined(MACRO_PREDEF) && defined(SUPPORT_TLS)
+# ifndef USE_GNUTLS
+# include "macro_predef.h"
+# include "tls-openssl.c"
+# endif
+#endif
+
+#ifndef MACRO_PREDEF
+
/* This module is compiled only when it is specifically requested in the
build-time configuration. However, some compilers don't like compiling empty
modules, so keep them happy with a dummy when skipping the rest. Make it
static uschar *ssl_xfer_buffer = NULL;
static int ssl_xfer_buffer_lwm = 0;
static int ssl_xfer_buffer_hwm = 0;
-static int ssl_xfer_eof = 0;
-static int ssl_xfer_error = 0;
+static int ssl_xfer_eof = FALSE;
+static BOOL ssl_xfer_error = FALSE;
#endif
uschar *tls_channelbinding_b64 = NULL;
*/
static BOOL
-expand_check(const uschar *s, const uschar *name, uschar **result)
+expand_check(const uschar *s, const uschar *name, uschar **result, uschar ** errstr)
{
-if (s == NULL) *result = NULL; else
+if (!s)
+ *result = NULL;
+else if ( !(*result = expand_string(US s)) /* need to clean up const more */
+ && !f.expand_string_forcedfail
+ )
{
- *result = expand_string(US s); /* need to clean up const some more */
- if (*result == NULL && !expand_string_forcedfail)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "expansion of %s failed: %s", name,
- expand_string_message);
- return FALSE;
- }
+ *errstr = US"Internal error";
+ log_write(0, LOG_MAIN|LOG_PANIC, "expansion of %s failed: %s", name,
+ expand_string_message);
+ return FALSE;
}
return TRUE;
}
int
tls_feof(void)
{
-return ssl_xfer_eof;
+return (int)ssl_xfer_eof;
}
int
tls_ferror(void)
{
-return ssl_xfer_error;
+return (int)ssl_xfer_error;
}
uschar * ele;
uschar * match = NULL;
int len;
-uschar * list = NULL;
+gstring * list = NULL;
while ((ele = string_nextinlist(&mod, &insep, NULL, 0)))
if (ele[0] != '>')
|| Ustrncmp(ele, match, len) == 0 && ele[len] == '='
)
list = string_append_listele(list, outsep, ele+len+1);
-return list;
+return string_from_gstring(list);
}
return FALSE;
}
#endif /*SUPPORT_TLS*/
+#endif /*!MACRO_PREDEF*/
/* vi: aw ai sw=2
*/
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) Jeremy Harris 2014 - 2015 */
+/* Copyright (c) Jeremy Harris 2014 - 2018 */
/* This file provides TLS/SSL support for Exim using the GnuTLS library,
one of the available supported implementations. This file is #included into
return string_sprintf("%u", (unsigned)t);
cp = store_get(len);
-if (timestamps_utc)
+if (f.timestamps_utc)
{
uschar * tz = to_tz(US"GMT0");
len = strftime(CS cp, len, "%b %e %T %Y %Z", gmtime(&t));
bin, &sz)))
return g_err("gs0", __FUNCTION__, ret);
-for(dp = txt, sp = bin; sz; dp += 2, sp++, sz--)
- sprintf(CS dp, "%.2x", *sp);
+for(dp = txt, sp = bin; sz; sz--)
+ dp += sprintf(CS dp, "%.2x", *sp++);
for(sp = txt; sp[0]=='0' && sp[1]; ) sp++; /* leading zeroes */
return string_copy(sp);
}
if (gnutls_x509_crt_get_signature((gnutls_x509_crt_t)cert, CS cp1, &len) != 0)
return g_err("gs1", __FUNCTION__, ret);
-for(cp3 = cp2 = cp1+len; cp1 < cp2; cp3 += 3, cp1++)
- sprintf(CS cp3, "%.2x ", *cp1);
+for(cp3 = cp2 = cp1+len; cp1 < cp2; cp1++)
+ cp3 += sprintf(CS cp3, "%.2x ", *cp1);
cp3[-1]= '\0';
return cp2;
int ret;
ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
- oid, idx, CS cp1, &siz, &crit);
+ CS oid, idx, CS cp1, &siz, &crit);
if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER)
return g_err("ge0", __FUNCTION__, ret);
cp1 = store_get(siz*4 + 1);
ret = gnutls_x509_crt_get_extension_by_oid ((gnutls_x509_crt_t)cert,
- oid, idx, CS cp1, &siz, &crit);
+ CS oid, idx, CS cp1, &siz, &crit);
if (ret < 0)
return g_err("ge1", __FUNCTION__, ret);
/* binary data, DER encoded */
/* just dump for now */
-for(cp3 = cp2 = cp1+siz; cp1 < cp2; cp3 += 3, cp1++)
- sprintf(CS cp3, "%.2x ", *cp1);
+for(cp3 = cp2 = cp1+siz; cp1 < cp2; cp1++)
+ cp3 += sprintf(CS cp3, "%.2x ", *cp1);
cp3[-1]= '\0';
return cp2;
uschar *
tls_cert_subject_altname(void * cert, uschar * mod)
{
-uschar * list = NULL;
+gstring * list = NULL;
int index;
size_t siz;
int ret;
(gnutls_x509_crt_t)cert, index, NULL, &siz, NULL))
{
case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
- return list; /* no more elements; normal exit */
+ return string_from_gstring(list); /* no more elements; normal exit */
case GNUTLS_E_SHORT_MEMORY_BUFFER:
break;
int ret;
uschar sep = '\n';
int index;
-uschar * list = NULL;
+gstring * list = NULL;
if (mod)
if (*mod == '>' && *++mod) sep = *mod++;
index, GNUTLS_IA_OCSP_URI, &uri, NULL);
if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
- return list;
+ return string_from_gstring(list);
if (ret < 0)
return g_err("gai", __FUNCTION__, ret);
- list = string_append_listele(list, sep,
- string_copyn(uri.data, uri.size));
+ list = string_append_listele_n(list, sep, uri.data, uri.size);
}
/*NOTREACHED*/
size_t siz;
uschar sep = '\n';
int index;
-uschar * list = NULL;
+gstring * list = NULL;
uschar * ele;
if (mod)
(gnutls_x509_crt_t)cert, index, NULL, &siz, NULL, NULL))
{
case GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE:
- return list;
+ return string_from_gstring(list);
case GNUTLS_E_SHORT_MEMORY_BUFFER:
break;
default:
return g_err("gc0", __FUNCTION__, ret);
}
- ele = store_get(siz+1);
+ ele = store_get(siz);
if ((ret = gnutls_x509_crt_get_crl_dist_points(
(gnutls_x509_crt_t)cert, index, ele, &siz, NULL, NULL)) < 0)
return g_err("gc1", __FUNCTION__, ret);
- ele[siz] = '\0';
- list = string_append_listele(list, sep, ele);
+ list = string_append_listele_n(list, sep, ele, siz);
}
/*NOTREACHED*/
}
if ((ret = gnutls_x509_crt_get_fingerprint(cert, algo, cp, &siz)) < 0)
return g_err("gf1", __FUNCTION__, ret);
-for (cp3 = cp2 = cp+siz; cp < cp2; cp++, cp3+=2)
- sprintf(CS cp3, "%02X",*cp);
+for (cp3 = cp2 = cp+siz; cp < cp2; cp++)
+ cp3 += sprintf(CS cp3, "%02X", *cp);
return cp2;
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) Jeremy Harris 2014 - 2016 */
+/* Copyright (c) Jeremy Harris 2014 - 2018 */
/* This module provides TLS (aka SSL) support for Exim using the OpenSSL
library. It is #included into the tls.c file when that library is used.
# define EXIM_HAVE_ASN1_MACROS
#endif
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+# define ASN1_STRING_get0_data ASN1_STRING_data
+#endif
/*****************************************************
* Export/import a certificate, binary/printable
time_t t = mktime(&tm); /* make the tm self-consistent */
if (mod && Ustrcmp(mod, "int") == 0) /* seconds since epoch */
- s = string_sprintf("%u", t);
+ s = string_sprintf(TIME_T_FMT, t);
else
{
- if (!timestamps_utc) /* decoded string in local TZ */
+ if (!f.timestamps_utc) /* decoded string in local TZ */
{ /* shift to local TZ */
restore_tz(tz);
mod_tz = FALSE;
uschar *
tls_cert_version(void * cert, uschar * mod)
{
-return string_sprintf("%d", X509_get_version((X509 *)cert));
+return string_sprintf("%ld", X509_get_version((X509 *)cert));
}
uschar *
while(len)
{
- sprintf(CS cp2, "%.2x ", *cp1++);
- cp2 += 3;
+ cp2 += sprintf(CS cp2, "%.2x ", *cp1++);
len--;
}
cp2[-1] = '\0';
uschar *
tls_cert_subject_altname(void * cert, uschar * mod)
{
-uschar * list = NULL;
+gstring * list = NULL;
STACK_OF(GENERAL_NAME) * san = (STACK_OF(GENERAL_NAME) *)
X509_get_ext_d2i((X509 *)cert, NID_subject_alt_name, NULL, NULL);
uschar osep = '\n';
{
case GEN_DNS:
tag = US"DNS";
- ele = ASN1_STRING_data(namePart->d.dNSName);
+ ele = US ASN1_STRING_get0_data(namePart->d.dNSName);
len = ASN1_STRING_length(namePart->d.dNSName);
break;
case GEN_URI:
tag = US"URI";
- ele = ASN1_STRING_data(namePart->d.uniformResourceIdentifier);
+ ele = US ASN1_STRING_get0_data(namePart->d.uniformResourceIdentifier);
len = ASN1_STRING_length(namePart->d.uniformResourceIdentifier);
break;
case GEN_EMAIL:
tag = US"MAIL";
- ele = ASN1_STRING_data(namePart->d.rfc822Name);
+ ele = US ASN1_STRING_get0_data(namePart->d.rfc822Name);
len = ASN1_STRING_length(namePart->d.rfc822Name);
break;
default:
}
sk_GENERAL_NAME_free(san);
-return list;
+return string_from_gstring(list);
}
uschar *
int adsnum = sk_ACCESS_DESCRIPTION_num(ads);
int i;
uschar sep = '\n';
-uschar * list = NULL;
+gstring * list = NULL;
if (mod)
if (*mod == '>' && *++mod) sep = *mod++;
ACCESS_DESCRIPTION * ad = sk_ACCESS_DESCRIPTION_value(ads, i);
if (ad && OBJ_obj2nid(ad->method) == NID_ad_OCSP)
- {
- uschar * ele = ASN1_STRING_data(ad->location->d.ia5);
- int len = ASN1_STRING_length(ad->location->d.ia5);
- list = string_append_listele_n(list, sep, ele, len);
- }
+ list = string_append_listele_n(list, sep,
+ US ASN1_STRING_get0_data(ad->location->d.ia5),
+ ASN1_STRING_length(ad->location->d.ia5));
}
sk_ACCESS_DESCRIPTION_free(ads);
-return list;
+return string_from_gstring(list);
}
uschar *
int dpsnum = sk_DIST_POINT_num(dps);
int i;
uschar sep = '\n';
-uschar * list = NULL;
+gstring * list = NULL;
if (mod)
if (*mod == '>' && *++mod) sep = *mod++;
if ( (np = sk_GENERAL_NAME_value(names, j))
&& np->type == GEN_URI
)
- {
- uschar * ele = ASN1_STRING_data(np->d.uniformResourceIdentifier);
- int len = ASN1_STRING_length(np->d.uniformResourceIdentifier);
- list = string_append_listele_n(list, sep, ele, len);
- }
+ list = string_append_listele_n(list, sep,
+ US ASN1_STRING_get0_data(np->d.uniformResourceIdentifier),
+ ASN1_STRING_length(np->d.uniformResourceIdentifier));
}
sk_DIST_POINT_free(dps);
-return list;
+return string_from_gstring(list);
}
-/*************************************************
-* Exim - an Internet mail transport agent *
-*************************************************/
-
-/* Copyright (c) University of Cambridge 1995 - 2014 */
-/* See the file NOTICE for conditions of use and distribution. */
-
-/* A function for returning the time of day in various formats */
-
-
-#include "exim.h"
-
-/* #define TESTING_LOG_DATESTAMP */
-
-
-static uschar timebuf[sizeof("www, dd-mmm-yyyy hh:mm:ss +zzzz")];
-
-
-/*************************************************
-* Return timestamp *
-*************************************************/
-
-/* The log timestamp format is dd-mmm-yy so as to be non-confusing on both
-sides of the Atlantic. We calculate an explicit numerical offset from GMT for
-the full datestamp and BSD inbox datestamp. Note that on some systems
-localtime() and gmtime() re-use the same store, so we must save the local time
-values before calling gmtime(). If timestamps_utc is set, don't use
-localtime(); all times are then in UTC (with offset +0000).
-
-There are also some contortions to get the day of the month without
-a leading zero for the full stamp, since Ustrftime() doesn't provide this
-option.
-
-Argument: type of timestamp required:
- tod_bsdin BSD inbox format
- tod_epoch Unix epoch format
- tod_epochl Unix epoch/usec format
- tod_full full date and time
- tod_log log file data line format,
- with zone if log_timezone is TRUE
- tod_log_bare always without zone
- tod_log_datestamp_daily for log file names when datestamped daily
- tod_log_datestamp_monthly for log file names when datestamped monthly
- tod_log_zone always with zone
- tod_mbx MBX inbox format
- tod_zone just the timezone offset
- tod_zulu time in 8601 zulu format
-
-Returns: pointer to fixed buffer containing the timestamp
-*/
-
-uschar *
-tod_stamp(int type)
-{
-time_t now;
-struct tm *t;
-
-if (type == tod_epoch_l)
- {
- struct timeval tv;
- gettimeofday(&tv, NULL);
- /* Unix epoch/usec format */
- (void) sprintf(CS timebuf, TIME_T_FMT "%06ld", tv.tv_sec, (long) tv.tv_usec );
- return timebuf;
- }
-
-now = time(NULL);
-
-/* Vary log type according to timezone requirement */
-
-if (type == tod_log) type = log_timezone? tod_log_zone : tod_log_bare;
-
-/* Styles that don't need local time */
-
-else if (type == tod_epoch)
- {
- (void) sprintf(CS timebuf, TIME_T_FMT, now); /* Unix epoch format */
- return timebuf; /* NB the above will be wrong if time_t is FP */
- }
-
-else if (type == tod_zulu)
- {
- t = gmtime(&now);
- (void) sprintf(CS timebuf, "%04d%02d%02d%02d%02d%02dZ",
- 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min,
- t->tm_sec);
- return timebuf;
- }
-
-/* Convert to local time or UTC */
-
-t = timestamps_utc? gmtime(&now) : localtime(&now);
-
-switch(type)
- {
- case tod_log_bare: /* Format used in logging without timezone */
- (void) sprintf(CS timebuf, "%04d-%02d-%02d %02d:%02d:%02d",
- 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday,
- t->tm_hour, t->tm_min, t->tm_sec);
- break;
-
- /* Format used as suffix of log file name when 'log_datestamp' is active. For
- testing purposes, it changes the file every second. */
-
- #ifdef TESTING_LOG_DATESTAMP
- case tod_log_datestamp_daily:
- case tod_log_datestamp_monthly:
- (void) sprintf(CS timebuf, "%04d%02d%02d%02d%02d",
- 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min);
- break;
-
- #else
- case tod_log_datestamp_daily:
- (void) sprintf(CS timebuf, "%04d%02d%02d",
- 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday);
- break;
-
- case tod_log_datestamp_monthly:
- (void) sprintf(CS timebuf, "%04d%02d",
- 1900 + t->tm_year, 1 + t->tm_mon);
- break;
- #endif
-
- /* Format used in BSD inbox separator lines. Sort-of documented in RFC 976
- ("UUCP Mail Interchange Format Standard") but only by example, not by
- explicit definition. The examples show no timezone offsets, and some MUAs
- appear to be sensitive to this, so Exim has been changed to remove the
- timezone offsets that originally appeared. */
-
- case tod_bsdin:
- {
- int len = Ustrftime(timebuf, sizeof(timebuf), "%a %b %d %H:%M:%S", t);
- Ustrftime(timebuf + len, sizeof(timebuf) - len, " %Y", t);
- }
- break;
-
- /* Other types require the GMT offset to be calculated, or just set up in the
- case of UTC timestamping. We need to take a copy of the local time first. */
-
- default:
- {
- int diff_hour, diff_min;
- struct tm local;
- memcpy(&local, t, sizeof(struct tm));
-
- if (timestamps_utc)
- {
- diff_hour = diff_min = 0;
- }
- else
- {
- struct tm *gmt = gmtime(&now);
- diff_min = 60*(local.tm_hour - gmt->tm_hour) + local.tm_min - gmt->tm_min;
- if (local.tm_year != gmt->tm_year)
- diff_min += (local.tm_year > gmt->tm_year)? 1440 : -1440;
- else if (local.tm_yday != gmt->tm_yday)
- diff_min += (local.tm_yday > gmt->tm_yday)? 1440 : -1440;
- diff_hour = diff_min/60;
- diff_min = abs(diff_min - diff_hour*60);
- }
-
- switch(type)
- {
- case tod_log_zone: /* Format used in logging with timezone */
- (void) sprintf(CS timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+03d%02d",
- 1900 + local.tm_year, 1 + local.tm_mon, local.tm_mday,
- local.tm_hour, local.tm_min, local.tm_sec,
- diff_hour, diff_min);
- break;
-
- case tod_zone: /* Just the timezone offset */
- (void) sprintf(CS timebuf, "%+03d%02d", diff_hour, diff_min);
- break;
-
- /* tod_mbx: format used in MBX mailboxes - subtly different to tod_full */
-
- #ifdef SUPPORT_MBX
- case tod_mbx:
- {
- int len;
- (void) sprintf(CS timebuf, "%02d-", local.tm_mday);
- len = Ustrlen(timebuf);
- len += Ustrftime(timebuf + len, sizeof(timebuf) - len, "%b-%Y %H:%M:%S",
- &local);
- (void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);
- }
- break;
- #endif
-
- /* tod_full: format used in Received: headers (use as default just in case
- called with a junk type value) */
-
- default:
- {
- int len = Ustrftime(timebuf, sizeof(timebuf), "%a, ", &local);
- (void) sprintf(CS timebuf + len, "%02d ", local.tm_mday);
- len += Ustrlen(timebuf + len);
- len += Ustrftime(timebuf + len, sizeof(timebuf) - len, "%b %Y %H:%M:%S",
- &local);
- (void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);
- }
- break;
- }
- }
- break;
- }
-
-return timebuf;
-}
-
-/* End of tod.c */
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+/* A function for returning the time of day in various formats */
+
+
+#include "exim.h"
+
+/* #define TESTING_LOG_DATESTAMP */
+
+
+static uschar timebuf[sizeof("www, dd-mmm-yyyy hh:mm:ss.ddd +zzzz")];
+
+
+/*************************************************
+* Return timestamp *
+*************************************************/
+
+/* The log timestamp format is dd-mmm-yy so as to be non-confusing on both
+sides of the Atlantic. We calculate an explicit numerical offset from GMT for
+the full datestamp and BSD inbox datestamp. Note that on some systems
+localtime() and gmtime() re-use the same store, so we must save the local time
+values before calling gmtime(). If timestamps_utc is set, don't use
+localtime(); all times are then in UTC (with offset +0000).
+
+There are also some contortions to get the day of the month without
+a leading zero for the full stamp, since Ustrftime() doesn't provide this
+option.
+
+Argument: type of timestamp required:
+ tod_bsdin BSD inbox format
+ tod_epoch Unix epoch format
+ tod_epochl Unix epoch/usec format
+ tod_full full date and time
+ tod_log log file data line format,
+ with zone if log_timezone is TRUE
+ tod_log_bare always without zone
+ tod_log_datestamp_daily for log file names when datestamped daily
+ tod_log_datestamp_monthly for log file names when datestamped monthly
+ tod_log_zone always with zone
+ tod_mbx MBX inbox format
+ tod_zone just the timezone offset
+ tod_zulu time in 8601 zulu format
+
+Returns: pointer to fixed buffer containing the timestamp
+*/
+
+uschar *
+tod_stamp(int type)
+{
+struct timeval now;
+struct tm * t;
+
+gettimeofday(&now, NULL);
+
+/* Styles that don't need local time */
+
+switch(type)
+ {
+ case tod_epoch:
+ (void) sprintf(CS timebuf, TIME_T_FMT, now.tv_sec); /* Unix epoch format */
+ return timebuf; /* NB the above will be wrong if time_t is FP */
+
+ case tod_epoch_l:
+ /* Unix epoch/usec format */
+ (void) sprintf(CS timebuf, TIME_T_FMT "%06ld", now.tv_sec, (long) now.tv_usec );
+ return timebuf;
+
+ case tod_zulu:
+ t = gmtime(&now.tv_sec);
+ (void) sprintf(CS timebuf, "%04u%02u%02u%02u%02u%02uZ",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday, (uint)t->tm_hour, (uint)t->tm_min,
+ (uint)t->tm_sec);
+ return timebuf;
+ }
+
+/* Vary log type according to timezone requirement */
+
+if (type == tod_log) type = log_timezone ? tod_log_zone : tod_log_bare;
+
+/* Convert to local time or UTC */
+
+t = f.timestamps_utc ? gmtime(&now.tv_sec) : localtime(&now.tv_sec);
+
+switch(type)
+ {
+ case tod_log_bare: /* Format used in logging without timezone */
+#ifndef COMPILE_UTILITY
+ if (LOGGING(millisec))
+ sprintf(CS timebuf, "%04u-%02u-%02u %02u:%02u:%02u.%03u",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
+ (uint)t->tm_hour, (uint)t->tm_min, (uint)t->tm_sec,
+ (uint)(now.tv_usec/1000));
+ else
+#endif
+ sprintf(CS timebuf, "%04u-%02u-%02u %02u:%02u:%02u",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
+ (uint)t->tm_hour, (uint)t->tm_min, (uint)t->tm_sec);
+
+ break;
+
+ /* Format used as suffix of log file name when 'log_datestamp' is active. For
+ testing purposes, it changes the file every second. */
+
+#ifdef TESTING_LOG_DATESTAMP
+ case tod_log_datestamp_daily:
+ case tod_log_datestamp_monthly:
+ sprintf(CS timebuf, "%04u%02u%02u%02u%02u",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday,
+ (uint)t->tm_hour, (uint)t->tm_min);
+ break;
+
+#else
+ case tod_log_datestamp_daily:
+ sprintf(CS timebuf, "%04u%02u%02u",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon, (uint)t->tm_mday);
+ break;
+
+ case tod_log_datestamp_monthly:
+#ifndef COMPILE_UTILITY
+ sprintf(CS timebuf, "%04u%02u",
+ 1900 + (uint)t->tm_year, 1 + (uint)t->tm_mon);
+#endif
+ break;
+#endif
+
+ /* Format used in BSD inbox separator lines. Sort-of documented in RFC 976
+ ("UUCP Mail Interchange Format Standard") but only by example, not by
+ explicit definition. The examples show no timezone offsets, and some MUAs
+ appear to be sensitive to this, so Exim has been changed to remove the
+ timezone offsets that originally appeared. */
+
+ case tod_bsdin:
+ {
+ int len = Ustrftime(timebuf, sizeof(timebuf), "%a %b %d %H:%M:%S", t);
+ Ustrftime(timebuf + len, sizeof(timebuf) - len, " %Y", t);
+ }
+ break;
+
+ /* Other types require the GMT offset to be calculated, or just set up in the
+ case of UTC timestamping. We need to take a copy of the local time first. */
+
+ default:
+ {
+ int diff_hour, diff_min;
+ struct tm local;
+ memcpy(&local, t, sizeof(struct tm));
+
+ if (f.timestamps_utc)
+ diff_hour = diff_min = 0;
+ else
+ {
+ struct tm * gmt = gmtime(&now.tv_sec);
+
+ diff_min = 60*(local.tm_hour - gmt->tm_hour) + local.tm_min - gmt->tm_min;
+ if (local.tm_year != gmt->tm_year)
+ diff_min += (local.tm_year > gmt->tm_year)? 1440 : -1440;
+ else if (local.tm_yday != gmt->tm_yday)
+ diff_min += (local.tm_yday > gmt->tm_yday)? 1440 : -1440;
+ diff_hour = diff_min/60;
+ diff_min = abs(diff_min - diff_hour*60);
+ }
+
+ switch(type)
+ {
+ case tod_log_zone: /* Format used in logging with timezone */
+#ifndef COMPILE_UTILITY
+ if (LOGGING(millisec))
+ (void) sprintf(CS timebuf,
+ "%04u-%02u-%02u %02u:%02u:%02u.%03u %+03d%02d",
+ 1900 + (uint)local.tm_year, 1 + (uint)local.tm_mon, (uint)local.tm_mday,
+ (uint)local.tm_hour, (uint)local.tm_min, (uint)local.tm_sec, (uint)(now.tv_usec/1000),
+ diff_hour, diff_min);
+ else
+#endif
+ (void) sprintf(CS timebuf,
+ "%04u-%02u-%02u %02u:%02u:%02u %+03d%02d",
+ 1900 + (uint)local.tm_year, 1 + (uint)local.tm_mon, (uint)local.tm_mday,
+ (uint)local.tm_hour, (uint)local.tm_min, (uint)local.tm_sec,
+ diff_hour, diff_min);
+ break;
+
+ case tod_zone: /* Just the timezone offset */
+ (void) sprintf(CS timebuf, "%+03d%02d", diff_hour, diff_min);
+ break;
+
+ /* tod_mbx: format used in MBX mailboxes - subtly different to tod_full */
+
+ #ifdef SUPPORT_MBX
+ case tod_mbx:
+ {
+ int len;
+ (void) sprintf(CS timebuf, "%02u-", (uint)local.tm_mday);
+ len = Ustrlen(timebuf);
+ len += Ustrftime(timebuf + len, sizeof(timebuf) - len, "%b-%Y %H:%M:%S",
+ &local);
+ (void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);
+ }
+ break;
+ #endif
+
+ /* tod_full: format used in Received: headers (use as default just in case
+ called with a junk type value) */
+
+ default:
+ {
+ int len = Ustrftime(timebuf, sizeof(timebuf), "%a, ", &local);
+ (void) sprintf(CS timebuf + len, "%02u ", (uint)local.tm_mday);
+ len += Ustrlen(timebuf + len);
+ len += Ustrftime(timebuf + len, sizeof(timebuf) - len, "%b %Y %H:%M:%S",
+ &local);
+ (void) sprintf(CS timebuf + len, " %+03d%02d", diff_hour, diff_min);
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+return timebuf;
+}
+
+/* End of tod.c */
use warnings;
BEGIN { pop @INC if $INC[-1] eq '.' };
+use File::Basename;
+
+if ($ARGV[0] eq '--version') {
+ print basename($0) . ": $0\n",
+ "build: EXIM_RELEASE_VERSIONEXIM_VARIANT_VERSION\n",
+ "perl(runtime): $]\n";
+ exit 0;
+}
# If the filter is called with any arguments, insert them into the message
# as X-Arg headers, just to verify what they are.
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* General functions concerned with transportation, and generic options for all
#include "exim.h"
-#ifdef HAVE_LINUX_SENDFILE
-#include <sys/sendfile.h>
-#endif
-
-/* Structure for keeping list of addresses that have been added to
-Envelope-To:, in order to avoid duplication. */
-
-struct aci {
- struct aci *next;
- address_item *ptr;
- };
-
-
-/* Static data for write_chunk() */
-
-static uschar *chunk_ptr; /* chunk pointer */
-static uschar *nl_check; /* string to look for at line start */
-static int nl_check_length; /* length of same */
-static uschar *nl_escape; /* string to insert */
-static int nl_escape_length; /* length of same */
-static int nl_partial_match; /* length matched at chunk end */
-
-
/* Generic options for transports, all of which live inside transport_instance
data blocks and which therefore have the opt_public flag set. Note that there
are other options living inside this structure which can be set only from
certain transports. */
optionlist optionlist_transports[] = {
+ /* name type value */
{ "*expand_group", opt_stringptr|opt_hidden|opt_public,
(void *)offsetof(transport_instance, expand_gid) },
{ "*expand_user", opt_stringptr|opt_hidden|opt_public,
int optionlist_transports_size = nelem(optionlist_transports);
+#ifdef MACRO_PREDEF
+
+# include "macro_predef.h"
void
-readconf_options_transports(void)
+options_transports(void)
{
struct transport_info * ti;
+uschar buf[64];
-readconf_options_from_list(optionlist_transports, nelem(optionlist_transports), US"TRANSPORTS", NULL);
+options_from_list(optionlist_transports, nelem(optionlist_transports), US"TRANSPORTS", NULL);
for (ti = transports_available; ti->driver_name[0]; ti++)
{
- macro_create(string_sprintf("_DRIVER_TRANSPORT_%T", ti->driver_name), US"y", FALSE, TRUE);
- readconf_options_from_list(ti->options, (unsigned)*ti->options_count, US"TRANSPORT", ti->driver_name);
+ spf(buf, sizeof(buf), US"_DRIVER_TRANSPORT_%T", ti->driver_name);
+ builtin_macro_create(buf);
+ options_from_list(ti->options, (unsigned)*ti->options_count, US"TRANSPORT", ti->driver_name);
}
}
+#else /*!MACRO_PREDEF*/
+
+/* Structure for keeping list of addresses that have been added to
+Envelope-To:, in order to avoid duplication. */
+
+struct aci {
+ struct aci *next;
+ address_item *ptr;
+ };
+
+
+/* Static data for write_chunk() */
+
+static uschar *chunk_ptr; /* chunk pointer */
+static uschar *nl_check; /* string to look for at line start */
+static int nl_check_length; /* length of same */
+static uschar *nl_escape; /* string to insert */
+static int nl_escape_length; /* length of same */
+static int nl_partial_match; /* length matched at chunk end */
+
+
/*************************************************
* Initialize transport list *
*************************************************/
longstop.
Arguments:
- fd file descriptor to write to
+ tctx transport context: file descriptor or string to write to
block block of bytes to write
len number of bytes to write
+ more further data expected soon
Returns: TRUE on success, FALSE on failure (with errno preserved);
transport_count is incremented by the number of bytes written
*/
-BOOL
-transport_write_block(int fd, uschar *block, int len)
+static BOOL
+transport_write_block_fd(transport_ctx * tctx, uschar *block, int len, BOOL more)
{
int i, rc, save_errno;
int local_timeout = transport_write_timeout;
+int fd = tctx->u.fd;
/* This loop is for handling incomplete writes and other retries. In most
normal cases, it is only ever executed once. */
for (i = 0; i < 100; i++)
{
DEBUG(D_transport)
- debug_printf("writing data block fd=%d size=%d timeout=%d\n",
- fd, len, local_timeout);
+ debug_printf("writing data block fd=%d size=%d timeout=%d%s\n",
+ fd, len, local_timeout, more ? " (more expected)" : "");
/* This code makes use of alarm() in order to implement the timeout. This
isn't a very tidy way of doing things. Using non-blocking I/O with select()
if (transport_write_timeout <= 0) /* No timeout wanted */
{
- #ifdef SUPPORT_TLS
- if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
- #endif
- rc = write(fd, block, len);
+ rc =
+#ifdef SUPPORT_TLS
+ tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
+#endif
+#ifdef MSG_MORE
+ more && !(tctx->options & topt_not_socket)
+ ? send(fd, block, len, MSG_MORE) :
+#endif
+ write(fd, block, len);
save_errno = errno;
}
else
{
- alarm(local_timeout);
+ ALARM(local_timeout);
+
+ rc =
#ifdef SUPPORT_TLS
- if (tls_out.active == fd)
- rc = tls_write(FALSE, block, len);
- else
+ tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
#endif
- rc = write(fd, block, len);
+#ifdef MSG_MORE
+ more && !(tctx->options & topt_not_socket)
+ ? send(fd, block, len, MSG_MORE) :
+#endif
+ write(fd, block, len);
+
save_errno = errno;
- local_timeout = alarm(0);
+ local_timeout = ALARM_CLR(0);
if (sigalrm_seen)
{
errno = ETIMEDOUT;
}
+BOOL
+transport_write_block(transport_ctx * tctx, uschar *block, int len, BOOL more)
+{
+if (!(tctx->options & topt_output_string))
+ return transport_write_block_fd(tctx, block, len, more);
+
+/* Write to expanding-string. NOTE: not NUL-terminated */
+
+if (!tctx->u.msg)
+ tctx->u.msg = string_get(1024);
+
+tctx->u.msg = string_catn(tctx->u.msg, block, len);
+return TRUE;
+}
+
+
/*************************************************
BOOL
transport_write_string(int fd, const char *format, ...)
{
+transport_ctx tctx = {{0}};
+gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
va_list ap;
+
va_start(ap, format);
-if (!string_vformat(big_buffer, big_buffer_size, format, ap))
+if (!string_vformat(&gs, FALSE, format, ap))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong formatted string in transport");
va_end(ap);
-return transport_write_block(fd, big_buffer, Ustrlen(big_buffer));
+tctx.u.fd = fd;
+return transport_write_block(&tctx, gs.s, gs.ptr, FALSE);
}
+void
+transport_write_reset(int options)
+{
+if (!(options & topt_continuation)) chunk_ptr = deliver_out_buffer;
+nl_partial_match = -1;
+nl_check_length = nl_escape_length = 0;
+}
+
+
+
/*************************************************
* Write character chunk *
*************************************************/
chunk was NL, or matched part of the data that has to be escaped.
Arguments:
- fd file descript to write to
+ tctx transport context - processing to be done during output,
+ and file descriptor to write to
chunk pointer to data to write
len length of data to write
- tctx transport context - processing to be done during output
In addition, the static nl_xxx variables must be set as required.
Returns: TRUE on success, FALSE on failure (with errno preserved)
*/
-static BOOL
-write_chunk(int fd, transport_ctx * tctx, uschar *chunk, int len)
+BOOL
+write_chunk(transport_ctx * tctx, uschar *chunk, int len)
{
uschar *start = chunk;
uschar *end = chunk + len;
/* If CHUNKING, prefix with BDAT (size) NON-LAST. Also, reap responses
from previous SMTP commands. */
- if (tctx && tctx->options & topt_use_bdat && tctx->chunk_cb)
+ if (tctx->options & topt_use_bdat && tctx->chunk_cb)
{
- if ( tctx->chunk_cb(fd, tctx, (unsigned)len, 0) != OK
- || !transport_write_block(fd, deliver_out_buffer, len)
- || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+ if ( tctx->chunk_cb(tctx, (unsigned)len, 0) != OK
+ || !transport_write_block(tctx, deliver_out_buffer, len, FALSE)
+ || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
)
return FALSE;
}
else
- if (!transport_write_block(fd, deliver_out_buffer, len))
+ if (!transport_write_block(tctx, deliver_out_buffer, len, FALSE))
return FALSE;
chunk_ptr = deliver_out_buffer;
}
+ /* Remove CR before NL if required */
+
+ if ( *ptr == '\r' && ptr[1] == '\n'
+ && !(tctx->options & topt_use_crlf)
+ && f.spool_file_wireformat
+ )
+ ptr++;
+
if ((ch = *ptr) == '\n')
{
int left = end - ptr - 1; /* count of chars left after NL */
/* Insert CR before NL if required */
- if (tctx && tctx->options & topt_use_crlf) *chunk_ptr++ = '\r';
+ if (tctx->options & topt_use_crlf && !f.spool_file_wireformat)
+ *chunk_ptr++ = '\r';
*chunk_ptr++ = '\n';
transport_newlines++;
plen = (addr->prefix == NULL)? 0 : Ustrlen(addr->prefix);
slen = Ustrlen(addr->suffix);
-return string_sprintf("%.*s@%s", (at - addr->address - plen - slen),
+return string_sprintf("%.*s@%s", (int)(at - addr->address - plen - slen),
addr->address + plen, at + 1);
}
pplist address of anchor of the list of addresses not to output
pdlist address of anchor of the list of processed addresses
first TRUE if this is the first address; set it FALSE afterwards
- fd the file descriptor to write to
tctx transport context - processing to be done during output
+ and the file descriptor to write to
Returns: FALSE if writing failed
*/
static BOOL
write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist,
- BOOL *first, int fd, transport_ctx * tctx)
+ BOOL *first, transport_ctx * tctx)
{
address_item *pp;
struct aci *ppp;
address_item *dup;
for (dup = addr_duplicate; dup; dup = dup->next)
if (dup->dupof == pp) /* a dup of our address */
- if (!write_env_to(dup, pplist, pdlist, first, fd, tctx))
+ if (!write_env_to(dup, pplist, pdlist, first, tctx))
return FALSE;
if (!pp->parent) break;
}
*pplist = ppp;
ppp->ptr = pp;
-if (!*first && !write_chunk(fd, tctx, US",\n ", 3)) return FALSE;
+if (!*first && !write_chunk(tctx, US",\n ", 3)) return FALSE;
*first = FALSE;
-return write_chunk(fd, tctx, pp->address, Ustrlen(pp->address));
+return write_chunk(tctx, pp->address, Ustrlen(pp->address));
}
Arguments:
addr (chain of) addresses (for extra headers), or NULL;
only the first address is used
- fd file descriptor to write the message to
tctx transport context
sendfn function for output (transport or verify)
Returns: TRUE on success; FALSE on failure.
*/
BOOL
-transport_headers_send(int fd, transport_ctx * tctx,
- BOOL (*sendfn)(int fd, transport_ctx * tctx, uschar * s, int len))
+transport_headers_send(transport_ctx * tctx,
+ BOOL (*sendfn)(transport_ctx * tctx, uschar * s, int len))
{
header_line *h;
const uschar *list;
int len;
if (i == 0)
- if (!(s = expand_string(s)) && !expand_string_forcedfail)
+ if (!(s = expand_string(s)) && !f.expand_string_forcedfail)
{
errno = ERRNO_CHHEADER_FAIL;
return FALSE;
if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
tblock->rewrite_existflags, FALSE)))
{
- if (!sendfn(fd, tctx, hh->text, hh->slen)) return FALSE;
+ if (!sendfn(tctx, hh->text, hh->slen)) return FALSE;
store_reset(reset_point);
continue; /* With the next header line */
}
/* Either no rewriting rules, or it didn't get rewritten */
- if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
+ if (!sendfn(tctx, h->text, h->slen)) return FALSE;
}
/* Header removed */
else
- {
DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", h->text);
- }
}
/* Add on any address-specific headers. If there are multiple addresses,
hprev = h;
if (i == 1)
{
- if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
+ if (!sendfn(tctx, h->text, h->slen)) return FALSE;
DEBUG(D_transport)
debug_printf("added header line(s):\n%s---\n", h->text);
}
int len = Ustrlen(s);
if (len > 0)
{
- if (!sendfn(fd, tctx, s, len)) return FALSE;
- if (s[len-1] != '\n' && !sendfn(fd, tctx, US"\n", 1))
+ if (!sendfn(tctx, s, len)) return FALSE;
+ if (s[len-1] != '\n' && !sendfn(tctx, US"\n", 1))
return FALSE;
DEBUG(D_transport)
{
}
}
}
- else if (!expand_string_forcedfail)
+ else if (!f.expand_string_forcedfail)
{ errno = ERRNO_CHHEADER_FAIL; return FALSE; }
}
/* Separate headers from body with a blank line */
-return sendfn(fd, tctx, US"\n", 1);
+return sendfn(tctx, US"\n", 1);
}
transport_write_timeout non-zero.
Arguments:
- fd file descriptor to write the message to
tctx
+ (fd, msg) Either and fd, to write the message to,
+ or a string: if null write message to allocated space
+ otherwire take content as headers.
addr (chain of) addresses (for extra headers), or NULL;
only the first address is used
tblock optional transport instance block (NULL signifies NULL/0):
*/
static BOOL
-internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit)
+internal_transport_write_message(transport_ctx * tctx, int size_limit)
{
-int len;
+int len, size = 0;
/* Initialize pointer in output buffer. */
-chunk_ptr = deliver_out_buffer;
+transport_write_reset(tctx->options);
/* Set up the data for start-of-line data checking and escaping */
-nl_partial_match = -1;
if (tctx->check_string && tctx->escape_string)
{
nl_check = tctx->check_string;
nl_escape = tctx->escape_string;
nl_escape_length = Ustrlen(nl_escape);
}
-else
- nl_check_length = nl_escape_length = 0;
/* Whether the escaping mechanism is applied to headers or not is controlled by
an option (set for SMTP, not otherwise). Negate the length if not wanted till
nl_check_length = -nl_check_length;
/* Write the headers if required, including any that have to be added. If there
-are header rewriting rules, apply them. */
+are header rewriting rules, apply them. The datasource is not the -D spoolfile
+so temporarily hide the global that adjusts for its format. */
if (!(tctx->options & topt_no_headers))
{
+ BOOL save_wireformat = f.spool_file_wireformat;
+ f.spool_file_wireformat = FALSE;
+
/* Add return-path: if requested. */
if (tctx->options & topt_add_return_path)
uschar buffer[ADDRESS_MAXLENGTH + 20];
int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
return_path);
- if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
+ if (!write_chunk(tctx, buffer, n)) goto bad;
}
/* Add envelope-to: if requested */
struct aci *dlist = NULL;
void *reset_point = store_get(0);
- if (!write_chunk(fd, tctx, US"Envelope-to: ", 13)) return FALSE;
+ if (!write_chunk(tctx, US"Envelope-to: ", 13)) goto bad;
/* Pick up from all the addresses. The plist and dlist variables are
anchors for lists of addresses already handled; they have to be defined at
this level because write_env_to() calls itself recursively. */
for (p = tctx->addr; p; p = p->next)
- if (!write_env_to(p, &plist, &dlist, &first, fd, tctx))
- return FALSE;
+ if (!write_env_to(p, &plist, &dlist, &first, tctx)) goto bad;
/* Add a final newline and reset the store used for tracking duplicates */
- if (!write_chunk(fd, tctx, US"\n", 1)) return FALSE;
+ if (!write_chunk(tctx, US"\n", 1)) goto bad;
store_reset(reset_point);
}
if (tctx->options & topt_add_delivery_date)
{
- uschar buffer[100];
- int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
- if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
+ uschar * s = tod_stamp(tod_full);
+
+ if ( !write_chunk(tctx, US"Delivery-date: ", 15)
+ || !write_chunk(tctx, s, Ustrlen(s))
+ || !write_chunk(tctx, US"\n", 1)) goto bad;
}
/* Then the message's headers. Don't write any that are flagged as "old";
match any entries therein. Then check addr->prop.remove_headers too, provided that
addr is not NULL. */
- if (!transport_headers_send(fd, tctx, &write_chunk))
+ if (!transport_headers_send(tctx, &write_chunk))
+ {
+bad:
+ f.spool_file_wireformat = save_wireformat;
return FALSE;
+ }
+
+ f.spool_file_wireformat = save_wireformat;
}
/* When doing RFC3030 CHUNKING output, work out how much data would be in a
if (tctx->options & topt_use_bdat)
{
off_t fsize;
- int hsize, size = 0;
+ int hsize;
if ((hsize = chunk_ptr - deliver_out_buffer) < 0)
hsize = 0;
if (size_limit > 0 && fsize > size_limit)
fsize = size_limit;
size = hsize + fsize;
- if (tctx->options & topt_use_crlf)
+ if (tctx->options & topt_use_crlf && !f.spool_file_wireformat)
size += body_linecount; /* account for CRLF-expansion */
+
+ /* With topt_use_bdat we never do dot-stuffing; no need to
+ account for any expansion due to that. */
}
/* If the message is large, emit first a non-LAST chunk with just the
{
DEBUG(D_transport)
debug_printf("sending small initial BDAT; hsize=%d\n", hsize);
- if ( tctx->chunk_cb(fd, tctx, hsize, 0) != OK
- || !transport_write_block(fd, deliver_out_buffer, hsize)
- || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+ if ( tctx->chunk_cb(tctx, hsize, 0) != OK
+ || !transport_write_block(tctx, deliver_out_buffer, hsize, FALSE)
+ || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
)
return FALSE;
chunk_ptr = deliver_out_buffer;
size -= hsize;
}
- /* Emit a LAST datachunk command. */
+ /* Emit a LAST datachunk command, and unmark the context for further
+ BDAT commands. */
- if (tctx->chunk_cb(fd, tctx, size, tc_chunk_last) != OK)
+ if (tctx->chunk_cb(tctx, size, tc_chunk_last) != OK)
return FALSE;
-
tctx->options &= ~topt_use_bdat;
}
is positioned at the start of its file (following the message id), then write
it, applying the size limit if required. */
+/* If we have a wireformat -D file (CRNL lines, non-dotstuffed, no ending dot)
+and we want to send a body without dotstuffing or ending-dot, in-clear,
+then we can just dump it using sendfile.
+This should get used for CHUNKING output and also for writing the -K file for
+dkim signing, when we had CHUNKING input. */
+
+#ifdef OS_SENDFILE
+if ( f.spool_file_wireformat
+ && !(tctx->options & (topt_no_body | topt_end_dot))
+ && !nl_check_length
+ && tls_out.active.sock != tctx->u.fd
+ )
+ {
+ ssize_t copied = 0;
+ off_t offset = SPOOL_DATA_START_OFFSET;
+
+ /* Write out any header data in the buffer */
+
+ if ((len = chunk_ptr - deliver_out_buffer) > 0)
+ {
+ if (!transport_write_block(tctx, deliver_out_buffer, len, TRUE))
+ return FALSE;
+ size -= len;
+ }
+
+ DEBUG(D_transport) debug_printf("using sendfile for body\n");
+
+ while(size > 0)
+ {
+ if ((copied = os_sendfile(tctx->u.fd, deliver_datafile, &offset, size)) <= 0) break;
+ size -= copied;
+ }
+ return copied >= 0;
+ }
+#else
+DEBUG(D_transport) debug_printf("cannot use sendfile for body: no support\n");
+#endif
+
+DEBUG(D_transport)
+ if (!(tctx->options & topt_no_body))
+ debug_printf("cannot use sendfile for body: %s\n",
+ !f.spool_file_wireformat ? "spoolfile not wireformat"
+ : tctx->options & topt_end_dot ? "terminating dot wanted"
+ : nl_check_length ? "dot- or From-stuffing wanted"
+ : "TLS output wanted");
+
if (!(tctx->options & topt_no_body))
{
int size = size_limit;
while ( (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
&& (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
{
- if (!write_chunk(fd, tctx, deliver_in_buffer, len))
+ if (!write_chunk(tctx, deliver_in_buffer, len))
return FALSE;
size -= len;
}
if (len != 0) return FALSE;
}
-/* Finished with the check string */
+/* Finished with the check string, and spool-format consideration */
nl_check_length = nl_escape_length = 0;
+f.spool_file_wireformat = FALSE;
/* If requested, add a terminating "." line (SMTP output). */
-if (tctx->options & topt_end_dot && !write_chunk(fd, tctx, US".\n", 2))
+if (tctx->options & topt_end_dot && !write_chunk(tctx, US".\n", 2))
return FALSE;
/* Write out any remaining data in the buffer before returning. */
return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
- transport_write_block(fd, deliver_out_buffer, len);
-}
-
-
-#ifndef DISABLE_DKIM
-
-/***************************************************************************************************
-* External interface to write the message, while signing it with DKIM and/or Domainkeys *
-***************************************************************************************************/
-
-/* This function is a wrapper around transport_write_message().
- It is only called from the smtp transport if DKIM or Domainkeys support
- is compiled in. The function sets up a replacement fd into a -K file,
- then calls the normal function. This way, the exact bits that exim would
- have put "on the wire" will end up in the file (except for TLS
- encapsulation, which is the very very last thing). When we are done
- signing the file, send the signed message down the original fd (or TLS fd).
-
-Arguments:
- as for internal_transport_write_message() above, with additional arguments
- for DKIM.
-
-Returns: TRUE on success; FALSE (with errno) for any failure
-*/
-
-BOOL
-dkim_transport_write_message(int out_fd, transport_ctx * tctx,
- struct ob_dkim * dkim)
-{
-int dkim_fd;
-int save_errno = 0;
-BOOL rc;
-uschar * dkim_spool_name;
-int sread = 0;
-int wwritten = 0;
-uschar *dkim_signature = NULL;
-int siglen = 0;
-off_t k_file_size;
-int options;
-
-/* If we can't sign, just call the original function. */
-
-if (!(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector))
- return transport_write_message(out_fd, tctx, 0);
-
-dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
- string_sprintf("-%d-K", (int)getpid()));
-
-if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
- {
- /* Can't create spool file. Ugh. */
- rc = FALSE;
- save_errno = errno;
- goto CLEANUP;
- }
-
-/* Call original function to write the -K file; does the CRLF expansion
-(but, in the CHUNKING case, not dot-stuffing and dot-termination). */
-
-options = tctx->options;
-tctx->options &= ~topt_use_bdat;
-rc = transport_write_message(dkim_fd, tctx, 0);
-tctx->options = options;
-
-/* Save error state. We must clean up before returning. */
-if (!rc)
- {
- save_errno = errno;
- goto CLEANUP;
- }
-
-/* Rewind file and feed it to the goats^W DKIM lib */
-dkim->dot_stuffed = !!(options & topt_end_dot);
-lseek(dkim_fd, 0, SEEK_SET);
-if ((dkim_signature = dkim_exim_sign(dkim_fd, dkim)))
- siglen = Ustrlen(dkim_signature);
-else if (dkim->dkim_strict)
- {
- uschar *dkim_strict_result = expand_string(dkim->dkim_strict);
- if (dkim_strict_result)
- if ( (strcmpic(dkim->dkim_strict,US"1") == 0) ||
- (strcmpic(dkim->dkim_strict,US"true") == 0) )
- {
- /* Set errno to something halfway meaningful */
- save_errno = EACCES;
- log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
- " and dkim_strict is set. Deferring message delivery.");
- rc = FALSE;
- goto CLEANUP;
- }
- }
-
-#ifndef HAVE_LINUX_SENDFILE
-if (options & topt_use_bdat)
-#endif
- k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
-
-if (options & topt_use_bdat)
- {
-
- /* On big messages output a precursor chunk to get any pipelined
- MAIL & RCPT commands flushed, then reap the responses so we can
- error out on RCPT rejects before sending megabytes. */
-
- if (siglen + k_file_size > DELIVER_OUT_BUFFER_SIZE && siglen > 0)
- {
- if ( tctx->chunk_cb(out_fd, tctx, siglen, 0) != OK
- || !transport_write_block(out_fd, dkim_signature, siglen)
- || tctx->chunk_cb(out_fd, tctx, 0, tc_reap_prev) != OK
- )
- goto err;
- siglen = 0;
- }
-
- if (tctx->chunk_cb(out_fd, tctx, siglen + k_file_size, tc_chunk_last) != OK)
- goto err;
- }
-
-if(siglen > 0 && !transport_write_block(out_fd, dkim_signature, siglen))
- goto err;
-
-#ifdef HAVE_LINUX_SENDFILE
-/* We can use sendfile() to shove the file contents
- to the socket. However only if we don't use TLS,
- as then there's another layer of indirection
- before the data finally hits the socket. */
-if (tls_out.active != out_fd)
- {
- ssize_t copied = 0;
- off_t offset = 0;
-
- /* Rewind file */
- lseek(dkim_fd, 0, SEEK_SET);
-
- while(copied >= 0 && offset < k_file_size)
- copied = sendfile(out_fd, dkim_fd, &offset, k_file_size - offset);
- if (copied < 0)
- goto err;
- }
-else
-
-#endif
-
- {
- /* Rewind file */
- lseek(dkim_fd, 0, SEEK_SET);
-
- /* Send file down the original fd */
- while((sread = read(dkim_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) >0)
- {
- uschar * p = deliver_out_buffer;
- /* write the chunk */
-
- while (sread)
- {
-#ifdef SUPPORT_TLS
- wwritten = tls_out.active == out_fd
- ? tls_write(FALSE, p, sread)
- : write(out_fd, CS p, sread);
-#else
- wwritten = write(out_fd, CS p, sread);
-#endif
- if (wwritten == -1)
- goto err;
- p += wwritten;
- sread -= wwritten;
- }
- }
-
- if (sread == -1)
- {
- save_errno = errno;
- rc = FALSE;
- }
- }
-
-CLEANUP:
- /* unlink -K file */
- (void)close(dkim_fd);
- Uunlink(dkim_spool_name);
- errno = save_errno;
- return rc;
-
-err:
- save_errno = errno;
- rc = FALSE;
- goto CLEANUP;
+ transport_write_block(tctx, deliver_out_buffer, len, FALSE);
}
-#endif
the real work, passing over all the arguments from this function. Otherwise,
set up a filtering process, fork another process to call the internal function
to write to the filter, and in this process just suck from the filter and write
-down the given fd. At the end, tidy up the pipes and the processes.
+down the fd in the transport context. At the end, tidy up the pipes and the
+processes.
-XXX
Arguments: as for internal_transport_write_message() above
Returns: TRUE on success; FALSE (with errno) for any failure
*/
BOOL
-transport_write_message(int fd, transport_ctx * tctx, int size_limit)
+transport_write_message(transport_ctx * tctx, int size_limit)
{
BOOL last_filter_was_NL = TRUE;
+BOOL save_spool_file_wireformat = f.spool_file_wireformat;
int rc, len, yield, fd_read, fd_write, save_errno;
int pfd[2] = {-1, -1};
pid_t filter_pid, write_pid;
-static transport_ctx dummy_tctx = {0};
-if (!tctx) tctx = &dummy_tctx;
-
-transport_filter_timed_out = FALSE;
+f.transport_filter_timed_out = FALSE;
/* If there is no filter command set up, call the internal function that does
the actual work, passing it the incoming fd, and return its result. */
|| !*transport_filter_argv
|| !**transport_filter_argv
)
- return internal_transport_write_message(fd, tctx, size_limit);
+ return internal_transport_write_message(tctx, size_limit);
/* Otherwise the message must be written to a filter process and read back
before being written to the incoming fd. First set up the special processing to
write_pid = (pid_t)(-1);
{
- int bits = fcntl(fd, F_GETFD);
- (void)fcntl(fd, F_SETFD, bits | FD_CLOEXEC);
+ int bits = fcntl(tctx->u.fd, F_GETFD);
+ (void)fcntl(tctx->u.fd, F_SETFD, bits | FD_CLOEXEC);
filter_pid = child_open(USS transport_filter_argv, NULL, 077,
&fd_write, &fd_read, FALSE);
- (void)fcntl(fd, F_SETFD, bits & ~FD_CLOEXEC);
+ (void)fcntl(tctx->u.fd, F_SETFD, bits & ~FD_CLOEXEC);
}
if (filter_pid < 0) goto TIDY_UP; /* errno set */
(void)close(pfd[pipe_read]);
nl_check_length = nl_escape_length = 0;
+ tctx->u.fd = fd_write;
tctx->check_string = tctx->escape_string = NULL;
tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat);
- rc = internal_transport_write_message(fd_write, tctx, size_limit);
+ rc = internal_transport_write_message(tctx, size_limit);
save_errno = errno;
if ( write(pfd[pipe_write], (void *)&rc, sizeof(BOOL))
!= sizeof(int)
|| write(pfd[pipe_write], (void *)&tctx->addr->more_errno, sizeof(int))
!= sizeof(int)
+ || write(pfd[pipe_write], (void *)&tctx->addr->delivery_usec, sizeof(int))
+ != sizeof(int)
)
rc = FALSE; /* compiler quietening */
_exit(0);
/* When testing, let the subprocess get going */
-if (running_in_test_harness) millisleep(250);
+if (f.running_in_test_harness) millisleep(250);
DEBUG(D_transport)
debug_printf("process %d writing to transport filter\n", (int)write_pid);
/* Copy the output of the filter, remembering if the last character was NL. If
no data is returned, that counts as "ended with NL" (default setting of the
-variable is TRUE). */
+variable is TRUE). The output should always be unix-format as we converted
+any wireformat source on writing input to the filter. */
+f.spool_file_wireformat = FALSE;
chunk_ptr = deliver_out_buffer;
for (;;)
{
sigalrm_seen = FALSE;
- alarm(transport_filter_timeout);
+ ALARM(transport_filter_timeout);
len = read(fd_read, deliver_in_buffer, DELIVER_IN_BUFFER_SIZE);
- alarm(0);
+ ALARM_CLR(0);
if (sigalrm_seen)
{
errno = ETIMEDOUT;
- transport_filter_timed_out = TRUE;
+ f.transport_filter_timed_out = TRUE;
goto TIDY_UP;
}
if (len > 0)
{
- if (!write_chunk(fd, tctx, deliver_in_buffer, len)) goto TIDY_UP;
+ if (!write_chunk(tctx, deliver_in_buffer, len)) goto TIDY_UP;
last_filter_was_NL = (deliver_in_buffer[len-1] == '\n');
}
sure. Also apply a paranoia timeout. */
TIDY_UP:
+f.spool_file_wireformat = save_spool_file_wireformat;
save_errno = errno;
(void)close(fd_read);
else if (!ok)
{
int dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
- dummy = read(pfd[pipe_read], (void *)&(tctx->addr->more_errno), sizeof(int));
+ dummy = read(pfd[pipe_read], (void *)&tctx->addr->more_errno, sizeof(int));
+ dummy = read(pfd[pipe_read], (void *)&tctx->addr->delivery_usec, sizeof(int));
+ dummy = dummy; /* compiler quietening */
yield = FALSE;
}
}
if (yield)
{
nl_check_length = nl_escape_length = 0;
+ f.spool_file_wireformat = FALSE;
if ( tctx->options & topt_end_dot
&& ( last_filter_was_NL
- ? !write_chunk(fd, tctx, US".\n", 2)
- : !write_chunk(fd, tctx, US"\n.\n", 3)
+ ? !write_chunk(tctx, US".\n", 2)
+ : !write_chunk(tctx, US"\n.\n", 3)
) )
yield = FALSE;
else
yield = (len = chunk_ptr - deliver_out_buffer) <= 0
- || transport_write_block(fd, deliver_out_buffer, len);
+ || transport_write_block(tctx, deliver_out_buffer, len, FALSE);
}
else
errno = save_errno; /* From some earlier error */
void
transport_update_waiting(host_item *hostlist, uschar *tpname)
{
-uschar buffer[256];
const uschar *prevname = US"";
host_item *host;
open_db dbblock;
/* Open the database for this transport */
-sprintf(CS buffer, "wait-%.200s", tpname);
-dbm_file = dbfn_open(buffer, O_RDWR, &dbblock, TRUE);
-if (dbm_file == NULL) return;
+if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", tpname),
+ O_RDWR, &dbblock, TRUE)))
+ return;
/* Scan the list of hosts for which this message is waiting, and ensure
that the message id is in each host record. */
-for (host = hostlist; host!= NULL; host = host->next)
+for (host = hostlist; host; host = host->next)
{
BOOL already = FALSE;
dbdata_wait *host_record;
uschar *s;
int i, host_length;
+ uschar buffer[256];
/* Skip if this is the same host as we just processed; otherwise remember
the name for next time. */
/* Look up the host record; if there isn't one, make an empty one. */
- host_record = dbfn_read(dbm_file, host->name);
- if (host_record == NULL)
+ if (!(host_record = dbfn_read(dbm_file, host->name)))
{
host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH);
host_record->count = host_record->sequence = 0;
for (s = host_record->text; s < host_record->text + host_length;
s += MESSAGE_ID_LENGTH)
- {
if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
{ already = TRUE; break; }
- }
/* If we haven't found this message in the main record, search any
continuation records that exist. */
{
dbdata_wait *cont;
sprintf(CS buffer, "%.200s:%d", host->name, i);
- cont = dbfn_read(dbm_file, buffer);
- if (cont != NULL)
+ if ((cont = dbfn_read(dbm_file, buffer)))
{
int clen = cont->count * MESSAGE_ID_LENGTH;
for (s = cont->text; s < cont->text + clen; s += MESSAGE_ID_LENGTH)
- {
if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
{ already = TRUE; break; }
- }
}
}
int host_length;
open_db dbblock;
open_db *dbm_file;
-uschar buffer[256];
int i;
struct stat statbuf;
/* Open the waiting information database. */
-sprintf(CS buffer, "wait-%.200s", transport_name);
-dbm_file = dbfn_open(buffer, O_RDWR, &dbblock, TRUE);
-if (dbm_file == NULL) return FALSE;
+if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", transport_name),
+ O_RDWR, &dbblock, TRUE)))
+ return FALSE;
/* See if there is a record for this host; if not, there's nothing to do. */
}
}
-/* Jeremy: check for a continuation record, this code I do not know how to
-test but the code should work */
+ /* Check for a continuation record. */
while (host_length <= 0)
{
int i;
dbdata_wait * newr = NULL;
+ uschar buffer[256];
/* Search for a continuation */
* Deliver waiting message down same socket *
*************************************************/
+/* Just the regain-root-privilege exec portion */
+void
+transport_do_pass_socket(const uschar *transport_name, const uschar *hostname,
+ const uschar *hostaddress, uschar *id, int socket_fd)
+{
+int i = 20;
+const uschar **argv;
+
+/* Set up the calling arguments; use the standard function for the basics,
+but we have a number of extras that may be added. */
+
+argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
+
+if (f.smtp_authenticated) argv[i++] = US"-MCA";
+if (smtp_peer_options & OPTION_CHUNKING) argv[i++] = US"-MCK";
+if (smtp_peer_options & OPTION_DSN) argv[i++] = US"-MCD";
+if (smtp_peer_options & OPTION_PIPE) argv[i++] = US"-MCP";
+if (smtp_peer_options & OPTION_SIZE) argv[i++] = US"-MCS";
+#ifdef SUPPORT_TLS
+if (smtp_peer_options & OPTION_TLS)
+ if (tls_out.active.sock >= 0 || continue_proxy_cipher)
+ {
+ argv[i++] = US"-MCt";
+ argv[i++] = sending_ip_address;
+ argv[i++] = string_sprintf("%d", sending_port);
+ argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher;
+ }
+ else
+ argv[i++] = US"-MCT";
+#endif
+
+if (queue_run_pid != (pid_t)0)
+ {
+ argv[i++] = US"-MCQ";
+ argv[i++] = string_sprintf("%d", queue_run_pid);
+ argv[i++] = string_sprintf("%d", queue_run_pipe);
+ }
+
+argv[i++] = US"-MC";
+argv[i++] = US transport_name;
+argv[i++] = US hostname;
+argv[i++] = US hostaddress;
+argv[i++] = string_sprintf("%d", continue_sequence + 1);
+argv[i++] = id;
+argv[i++] = NULL;
+
+/* Arrange for the channel to be on stdin. */
+
+if (socket_fd != 0)
+ {
+ (void)dup2(socket_fd, 0);
+ (void)close(socket_fd);
+ }
+
+DEBUG(D_exec) debug_print_argv(argv);
+exim_nullstd(); /* Ensure std{out,err} exist */
+execv(CS argv[0], (char *const *)argv);
+
+DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
+_exit(errno); /* Note: must be _exit(), NOT exit() */
+}
+
+
+
/* Fork a new exim process to deliver the message, and do a re-exec, both to
get a clean delivery process, and to regain root privilege in cases where it
has been given away.
if ((pid = fork()) == 0)
{
- int i = 17;
- const uschar **argv;
-
/* Disconnect entirely from the parent process. If we are running in the
test harness, wait for a bit to allow the previous process time to finish,
write the log, etc., so that the output is always in the same order for
automatic comparison. */
- if ((pid = fork()) != 0) _exit(EXIT_SUCCESS);
- if (running_in_test_harness) sleep(1);
-
- /* Set up the calling arguments; use the standard function for the basics,
- but we have a number of extras that may be added. */
-
- argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
-
- if (smtp_authenticated) argv[i++] = US"-MCA";
-
- if (smtp_peer_options & PEER_OFFERED_CHUNKING) argv[i++] = US"-MCK";
- if (smtp_peer_options & PEER_OFFERED_DSN) argv[i++] = US"-MCD";
- if (smtp_peer_options & PEER_OFFERED_PIPE) argv[i++] = US"-MCP";
- if (smtp_peer_options & PEER_OFFERED_SIZE) argv[i++] = US"-MCS";
-#ifdef SUPPORT_TLS
- if (smtp_peer_options & PEER_OFFERED_TLS) argv[i++] = US"-MCT";
-#endif
-
- if (queue_run_pid != (pid_t)0)
+ if ((pid = fork()) != 0)
{
- argv[i++] = US"-MCQ";
- argv[i++] = string_sprintf("%d", queue_run_pid);
- argv[i++] = string_sprintf("%d", queue_run_pipe);
+ DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (final-pid %d)\n", pid);
+ _exit(EXIT_SUCCESS);
}
+ if (f.running_in_test_harness) sleep(1);
- argv[i++] = US"-MC";
- argv[i++] = US transport_name;
- argv[i++] = US hostname;
- argv[i++] = US hostaddress;
- argv[i++] = string_sprintf("%d", continue_sequence + 1);
- argv[i++] = id;
- argv[i++] = NULL;
-
- /* Arrange for the channel to be on stdin. */
-
- if (socket_fd != 0)
- {
- (void)dup2(socket_fd, 0);
- (void)close(socket_fd);
- }
-
- DEBUG(D_exec) debug_print_argv(argv);
- exim_nullstd(); /* Ensure std{out,err} exist */
- execv(CS argv[0], (char *const *)argv);
-
- DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
- _exit(errno); /* Note: must be _exit(), NOT exit() */
+ transport_do_pass_socket(transport_name, hostname, hostaddress,
+ id, socket_fd);
}
/* If the process creation succeeded, wait for the first-level child, which
{
int rc;
while ((rc = wait(&status)) != pid && (rc >= 0 || errno != ECHILD));
- DEBUG(D_transport) debug_printf("transport_pass_socket succeeded\n");
+ DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (inter-pid %d)\n", pid);
return TRUE;
}
else
while (isspace(*s)) s++;
}
-argv[argcount] = (uschar *)0;
+argv[argcount] = US 0;
/* If *s != 0 we have run out of argument slots. */
DEBUG(D_transport)
{
debug_printf("direct command:\n");
- for (i = 0; argv[i] != (uschar *)0; i++)
+ for (i = 0; argv[i] != US 0; i++)
debug_printf(" argv[%d] = %s\n", i, string_printing(argv[i]));
}
addr->parent != NULL &&
Ustrcmp(addr->parent->address, "system-filter") == 0;
- for (i = 0; argv[i] != (uschar *)0; i++)
+ for (i = 0; argv[i] != US 0; i++)
{
/* Handle special fudge for passing an address list */
while (isspace(*s)) s++; /* strip space after arg */
}
- address_pipe_argv[address_pipe_argcount] = (uschar *)0;
+ address_pipe_argv[address_pipe_argcount] = US 0;
/* If *s != 0 we have run out of argument slots. */
if (*s != 0)
* [argv 0][argv 1][argv 2=pipeargv[0]][argv 3=pipeargv[1]][old argv 3][0]
*/
for (address_pipe_i = 0;
- address_pipe_argv[address_pipe_i] != (uschar *)0;
+ address_pipe_argv[address_pipe_i] != US 0;
address_pipe_i++)
{
argv[i++] = address_pipe_argv[address_pipe_i];
else
{
const uschar *expanded_arg;
- enable_dollar_recipients = allow_dollar_recipients;
+ f.enable_dollar_recipients = allow_dollar_recipients;
expanded_arg = expand_cstring(argv[i]);
- enable_dollar_recipients = FALSE;
+ f.enable_dollar_recipients = FALSE;
if (expanded_arg == NULL)
{
DEBUG(D_transport)
{
debug_printf("direct command after expansion:\n");
- for (i = 0; argv[i] != (uschar *)0; i++)
+ for (i = 0; argv[i] != US 0; i++)
debug_printf(" argv[%d] = %s\n", i, string_printing(argv[i]));
}
}
return TRUE;
}
+#endif /*!MACRO_PREDEF*/
/* vi: aw ai sw=2
*/
/* End of transport.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#endif
-/* Encodings for mailbox formats, and their names. MBX format is actually
-supported only if SUPPORT_MBX is set. */
-
-enum { mbf_unix, mbf_mbx, mbf_smail, mbf_maildir, mbf_mailstore };
-
-static const char *mailbox_formats[] = {
- "unix", "mbx", "smail", "maildir", "mailstore" };
-
-
-/* Check warn threshold only if quota size set or not a percentage threshold
- percentage check should only be done if quota > 0 */
-
-#define THRESHOLD_CHECK (ob->quota_warn_threshold_value > 0 && \
- (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0))
-
-
/* Options specific to the appendfile transport. They must be in alphabetic
order (note that "_" comes before the lower case letters). Some of them are
stored in the publicly visible instance block - these are flagged with the
int appendfile_transport_options_count =
sizeof(appendfile_transport_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+appendfile_transport_options_block appendfile_transport_option_defaults = {0};
+void appendfile_transport_init(transport_instance *tblock) {}
+BOOL appendfile_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else /*!MACRO_PREDEF*/
+
/* Default private options block for the appendfile transport. */
appendfile_transport_options_block appendfile_transport_option_defaults = {
FALSE, /* mailstore_format */
FALSE, /* mbx_format */
FALSE, /* quota_warn_threshold_is_percent */
- TRUE /* quota_is_inclusive */
+ TRUE, /* quota_is_inclusive */
+ FALSE, /* quota_no_check */
+ FALSE /* quota_filecount_no_check */
};
+/* Encodings for mailbox formats, and their names. MBX format is actually
+supported only if SUPPORT_MBX is set. */
+
+enum { mbf_unix, mbf_mbx, mbf_smail, mbf_maildir, mbf_mailstore };
+
+static const char *mailbox_formats[] = {
+ "unix", "mbx", "smail", "maildir", "mailstore" };
+
+
+/* Check warn threshold only if quota size set or not a percentage threshold
+ percentage check should only be done if quota > 0 */
+
+#define THRESHOLD_CHECK (ob->quota_warn_threshold_value > 0 && \
+ (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0))
+
+
/*************************************************
* Setup entry point *
for (i = 0; i < 5; i++)
{
double d;
+ int no_check = 0;
uschar *which = NULL;
- if (q == NULL) d = default_value; else
+ if (q == NULL) d = default_value;
+ else
{
uschar *rest;
uschar *s = expand_string(q);
- if (s == NULL)
+ if (!s)
{
*errmsg = string_sprintf("Expansion of \"%s\" in %s transport failed: "
"%s", q, tblock->name, expand_string_message);
- return search_find_defer? DEFER : FAIL;
+ return f.search_find_defer ? DEFER : FAIL;
}
d = Ustrtod(s, &rest);
else if (tolower(*rest) == 'g') { d *= 1024.0*1024.0*1024.0; rest++; }
else if (*rest == '%' && i == 2)
{
- if (ob->quota_value <= 0 && !ob->maildir_use_size_file) d = 0;
+ if (ob->quota_value <= 0 && !ob->maildir_use_size_file)
+ d = 0;
else if ((int)d < 0 || (int)d > 100)
{
*errmsg = string_sprintf("Invalid quota_warn_threshold percentage (%d)"
rest++;
}
+
+ /* For quota and quota_filecount there may be options
+ appended. Currently only "no_check", so we can be lazy parsing it */
+ if (i < 2 && Ustrstr(rest, "/no_check") == rest)
+ {
+ no_check = 1;
+ rest += sizeof("/no_check") - 1;
+ }
+
while (isspace(*rest)) rest++;
if (*rest != 0)
switch (i)
{
case 0:
- if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4) which = US"quota";
- ob->quota_value = (off_t)d;
- q = ob->quota_filecount;
- break;
+ if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
+ which = US"quota";
+ ob->quota_value = (off_t)d;
+ ob->quota_no_check = no_check;
+ q = ob->quota_filecount;
+ break;
case 1:
- if (d >= 2.0*1024.0*1024.0*1024.0) which = US"quota_filecount";
- ob->quota_filecount_value = (int)d;
- q = ob->quota_warn_threshold;
- break;
+ if (d >= 2.0*1024.0*1024.0*1024.0)
+ which = US"quota_filecount";
+ ob->quota_filecount_value = (int)d;
+ ob->quota_filecount_no_check = no_check;
+ q = ob->quota_warn_threshold;
+ break;
case 2:
if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
- which = US"quota_warn_threshold";
- ob->quota_warn_threshold_value = (off_t)d;
- q = ob->mailbox_size_string;
- default_value = -1.0;
- break;
+ which = US"quota_warn_threshold";
+ ob->quota_warn_threshold_value = (off_t)d;
+ q = ob->mailbox_size_string;
+ default_value = -1.0;
+ break;
case 3:
- if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
- which = US"mailbox_size";;
- ob->mailbox_size_value = (off_t)d;
- q = ob->mailbox_filecount_string;
- break;
+ if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
+ which = US"mailbox_size";;
+ ob->mailbox_size_value = (off_t)d;
+ q = ob->mailbox_filecount_string;
+ break;
case 4:
- if (d >= 2.0*1024.0*1024.0*1024.0) which = US"mailbox_filecount";
- ob->mailbox_filecount_value = (int)d;
- break;
+ if (d >= 2.0*1024.0*1024.0*1024.0)
+ which = US"mailbox_filecount";
+ ob->mailbox_filecount_value = (int)d;
+ break;
}
- if (which != NULL)
+ if (which)
{
*errmsg = string_sprintf("%s value %.10g is too large (overflow) in "
"%s transport", which, d, tblock->name);
driver options. Only one of body_only and headers_only can be set. */
ob->options |=
- (tblock->body_only? topt_no_headers : 0) |
- (tblock->headers_only? topt_no_body : 0) |
- (tblock->return_path_add? topt_add_return_path : 0) |
- (tblock->delivery_date_add? topt_add_delivery_date : 0) |
- (tblock->envelope_to_add? topt_add_envelope_to : 0) |
- ((ob->use_crlf || ob->mbx_format)? topt_use_crlf : 0);
+ (tblock->body_only ? topt_no_headers : 0) |
+ (tblock->headers_only ? topt_no_body : 0) |
+ (tblock->return_path_add ? topt_add_return_path : 0) |
+ (tblock->delivery_date_add ? topt_add_delivery_date : 0) |
+ (tblock->envelope_to_add ? topt_add_envelope_to : 0) |
+ ((ob->use_crlf || ob->mbx_format) ? topt_use_crlf : 0);
}
/* Connect never fails for a UDP socket, so don't set a timeout. */
- (void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0, FALSE);
+ (void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0, NULL);
rc = send(sock, buffer, Ustrlen(buffer) + 1, 0);
(void)close(sock);
{
if (fcntltime > 0)
{
- alarm(fcntltime);
+ ALARM(fcntltime);
yield = fcntl(fd, F_SETLKW, &lock_data);
save_errno = errno;
- alarm(0);
+ ALARM_CLR(0);
errno = save_errno;
}
else yield = fcntl(fd, F_SETLK, &lock_data);
#ifndef NO_FLOCK
if (doflock && (yield >= 0))
{
- int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
+ int flocktype = (fcntltype == F_WRLCK) ? LOCK_EX : LOCK_SH;
if (flocktime > 0)
{
- alarm(flocktime);
+ ALARM(flocktime);
yield = flock(fd, flocktype);
save_errno = errno;
- alarm(0);
+ ALARM_CLR(0);
errno = save_errno;
}
else yield = flock(fd, flocktype | LOCK_NB);
int used;
off_t size;
struct stat statbuf;
+transport_ctx tctx = { .u={.fd = to_fd}, .options = topt_not_socket };
/* If the current mailbox size is zero, write a header block */
(long int)time(NULL));
for (i = 0; i < MBX_NUSERFLAGS; i++)
sprintf (CS(s += Ustrlen(s)), "\015\012");
- if (!transport_write_block (to_fd, deliver_out_buffer, MBX_HDRSIZE))
+ if (!transport_write_block (&tctx, deliver_out_buffer, MBX_HDRSIZE, FALSE))
return DEFER;
}
/* Rewind the temporary file, and copy it over in chunks. */
-lseek(from_fd, 0 , SEEK_SET);
+if (lseek(from_fd, 0 , SEEK_SET) < 0) return DEFER;
while (size > 0)
{
if (len == 0) errno = ERRNO_MBXLENGTH;
return DEFER;
}
- if (!transport_write_block(to_fd, deliver_out_buffer, used + len))
+ if (!transport_write_block(&tctx, deliver_out_buffer, used + len, FALSE))
return DEFER;
size -= len;
used = 0;
uid_t uid = geteuid(); /* See note above */
gid_t gid = getegid();
int mbformat;
-int mode = (addr->mode > 0)? addr->mode : ob->mode;
+int mode = (addr->mode > 0) ? addr->mode : ob->mode;
off_t saved_size = -1;
off_t mailbox_size = ob->mailbox_size_value;
int mailbox_filecount = ob->mailbox_filecount_value;
addr->transport_return = PANIC;
addr->message = string_sprintf("mail%s_format requires \"directory\" "
"to be specified for the %s transport",
- ob->maildir_format? "dir" : "store", tblock->name);
+ ob->maildir_format ? "dir" : "store", tblock->name);
return FALSE;
}
{
mbformat =
#ifdef SUPPORT_MAILDIR
- (ob->maildir_format)? mbf_maildir :
+ (ob->maildir_format) ? mbf_maildir :
#endif
#ifdef SUPPORT_MAILSTORE
- (ob->mailstore_format)? mbf_mailstore :
+ (ob->mailstore_format) ? mbf_mailstore :
#endif
mbf_smail;
}
{
mbformat =
#ifdef SUPPORT_MBX
- (ob->mbx_format)? mbf_mbx :
+ (ob->mbx_format) ? mbf_mbx :
#endif
mbf_unix;
}
DEBUG(D_transport)
{
debug_printf("appendfile: mode=%o notify_comsat=%d quota=" OFF_T_FMT
+ "%s%s"
" warning=" OFF_T_FMT "%s\n"
" %s=%s format=%s\n message_prefix=%s\n message_suffix=%s\n "
"maildir_use_size_file=%s\n",
mode, ob->notify_comsat, ob->quota_value,
+ ob->quota_no_check ? " (no_check)" : "",
+ ob->quota_filecount_no_check ? " (no_check_filecount)" : "",
ob->quota_warn_threshold_value,
- ob->quota_warn_threshold_is_percent? "%" : "",
- isdirectory? "directory" : "file",
+ ob->quota_warn_threshold_is_percent ? "%" : "",
+ isdirectory ? "directory" : "file",
path, mailbox_formats[mbformat],
- (ob->message_prefix == NULL)? US"null" : string_printing(ob->message_prefix),
- (ob->message_suffix == NULL)? US"null" : string_printing(ob->message_suffix),
- (ob->maildir_use_size_file)? "yes" : "no");
+ (ob->message_prefix == NULL) ? US"null" : string_printing(ob->message_prefix),
+ (ob->message_suffix == NULL) ? US"null" : string_printing(ob->message_suffix),
+ (ob->maildir_use_size_file) ? "yes" : "no");
if (!isdirectory) debug_printf(" locking by %s%s%s%s%s\n",
- ob->use_lockfile? "lockfile " : "",
- ob->use_mbx_lock? "mbx locking (" : "",
- ob->use_fcntl? "fcntl " : "",
- ob->use_flock? "flock" : "",
- ob->use_mbx_lock? ")" : "");
+ ob->use_lockfile ? "lockfile " : "",
+ ob->use_mbx_lock ? "mbx locking (" : "",
+ ob->use_fcntl ? "fcntl " : "",
+ ob->use_flock ? "flock" : "",
+ ob->use_mbx_lock ? ")" : "");
}
/* If the -N option is set, can't do any more. */
-if (dont_deliver)
+if (f.dont_deliver)
{
DEBUG(D_transport)
debug_printf("*** delivery by %s transport bypassed by -N option\n",
int sleep_before_retry = TRUE;
file_opened = FALSE;
- if((use_lstat? Ulstat(filename, &statbuf) : Ustat(filename, &statbuf)) != 0)
+ if((use_lstat ? Ulstat(filename, &statbuf) : Ustat(filename, &statbuf)) != 0)
{
/* Let's hope that failure to stat (other than non-existence) is a
rare event. */
get a shared lock. */
fd = Uopen(filename, O_RDWR | O_APPEND | O_CREAT |
- (use_lstat? O_EXCL : 0), mode);
+ (use_lstat ? O_EXCL : 0), mode);
if (fd < 0)
{
if (errno == EEXIST) continue;
addr->basic_errno = ERRNO_BADUGID;
addr->message = string_sprintf("mailbox %s%s has wrong uid "
"(%ld != %ld)", filename,
- islink? " (symlink)" : "",
+ islink ? " (symlink)" : "",
(long int)(statbuf.st_uid), (long int)uid);
goto RETURN;
}
{
addr->basic_errno = ERRNO_BADUGID;
addr->message = string_sprintf("mailbox %s%s has wrong gid (%d != %d)",
- filename, islink? " (symlink)" : "", statbuf.st_gid, gid);
+ filename, islink ? " (symlink)" : "", statbuf.st_gid, gid);
goto RETURN;
}
{
addr->basic_errno = ERRNO_NOTREGULAR;
addr->message = string_sprintf("mailbox %s%s has too many links (%d)",
- filename, islink? " (symlink)" : "", statbuf.st_nlink);
+ filename, islink ? " (symlink)" : "", statbuf.st_nlink);
goto RETURN;
}
{
addr->basic_errno = ERRNO_NOTREGULAR;
addr->message = string_sprintf("mailbox %s is not a regular file%s",
- filename, ob->allow_fifo? " or named pipe" : "");
+ filename, ob->allow_fifo ? " or named pipe" : "");
goto RETURN;
}
a FIFO is opened WRONLY + NDELAY so that it fails if there is no process
reading the pipe. */
- fd = Uopen(filename, isfifo? (O_WRONLY|O_NDELAY) : (O_RDWR|O_APPEND),
+ fd = Uopen(filename, isfifo ? (O_WRONLY|O_NDELAY) : (O_RDWR|O_APPEND),
mode);
if (fd < 0)
{
{
addr->basic_errno = ERRNO_INODECHANGED;
addr->message = string_sprintf("opened mailbox %s inode number changed "
- "from %d to %ld", filename, inode, statbuf.st_ino);
+ "from " INO_T_FMT " to " INO_T_FMT, filename, inode, statbuf.st_ino);
addr->special_action = SPECIAL_FREEZE;
goto RETURN;
}
addr->basic_errno = ERRNO_NOTREGULAR;
addr->message =
string_sprintf("opened mailbox %s is no longer a %s", filename,
- isfifo? "named pipe" : "regular file");
+ isfifo ? "named pipe" : "regular file");
addr->special_action = SPECIAL_FREEZE;
goto RETURN;
}
{
uschar *s = path + check_path_len;
while (*s == '/') s++;
- s = (*s == 0)? US "new" : string_sprintf("%s/new", s);
+ s = (*s == 0) ? US "new" : string_sprintf("%s/new", s);
if (pcre_exec(dir_regex, NULL, CS s, Ustrlen(s), 0, 0, NULL, 0) < 0)
{
disable_quota = TRUE;
$message_size is accurately known. */
if (nametag != NULL && expand_string(nametag) == NULL &&
- !expand_string_forcedfail)
+ !f.expand_string_forcedfail)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("Expansion of \"%s\" (maildir_tag "
uschar *basename;
(void)gettimeofday(&msg_tv, NULL);
- basename = string_sprintf(TIME_T_FMT ".H%luP%lu.%s",
+ basename = string_sprintf(TIME_T_FMT ".H%luP" PID_T_FMT ".%s",
msg_tv.tv_sec, msg_tv.tv_usec, getpid(), primary_hostname);
filename = dataname = string_sprintf("tmp/%s", basename);
if (i >= ob->maildir_retries)
{
addr->message = string_sprintf ("failed to open %s (%d tr%s)",
- filename, i, (i == 1)? "y" : "ies");
+ filename, i, (i == 1) ? "y" : "ies");
addr->basic_errno = errno;
if (errno == errno_quota || errno == ENOSPC)
addr->user_message = US"mailbox is full";
uschar *s = expand_string(ob->mailstore_prefix);
if (s == NULL)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("Expansion of \"%s\" (mailstore "
uschar *s = expand_string(ob->mailstore_suffix);
if (s == NULL)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("Expansion of \"%s\" (mailstore "
debug_printf("Exim quota = " OFF_T_FMT " old size = " OFF_T_FMT
" this message = %d (%sincluded)\n",
ob->quota_value, mailbox_size, message_size,
- ob->quota_is_inclusive? "" : "not ");
+ ob->quota_is_inclusive ? "" : "not ");
debug_printf(" file count quota = %d count = %d\n",
ob->quota_filecount_value, mailbox_filecount);
}
- if (mailbox_size + (ob->quota_is_inclusive? message_size:0) > ob->quota_value)
- {
- DEBUG(D_transport) debug_printf("mailbox quota exceeded\n");
- yield = DEFER;
- errno = ERRNO_EXIMQUOTA;
- }
- else if (ob->quota_filecount_value > 0 &&
- mailbox_filecount + (ob->quota_is_inclusive ? 1:0) >
- ob->quota_filecount_value)
+
+ if (mailbox_size + (ob->quota_is_inclusive ? message_size:0) > ob->quota_value)
{
- DEBUG(D_transport) debug_printf("mailbox file count quota exceeded\n");
- yield = DEFER;
- errno = ERRNO_EXIMQUOTA;
- filecount_msg = US" filecount";
+
+ if (!ob->quota_no_check)
+ {
+ DEBUG(D_transport) debug_printf("mailbox quota exceeded\n");
+ yield = DEFER;
+ errno = ERRNO_EXIMQUOTA;
+ }
+ else DEBUG(D_transport) debug_printf("mailbox quota exceeded but ignored\n");
+
}
+
+ if (ob->quota_filecount_value > 0
+ && mailbox_filecount + (ob->quota_is_inclusive ? 1:0) >
+ ob->quota_filecount_value)
+ if(!ob->quota_filecount_no_check)
+ {
+ DEBUG(D_transport) debug_printf("mailbox file count quota exceeded\n");
+ yield = DEFER;
+ errno = ERRNO_EXIMQUOTA;
+ filecount_msg = US" filecount";
+ }
+ else DEBUG(D_transport) if (ob->quota_filecount_no_check)
+ debug_printf("mailbox file count quota exceeded but ignored\n");
+
}
/* If we are writing in MBX format, what we actually do is to write the message
transport_newlines++;
for (a = addr; a != NULL; a = a->next)
{
- address_item *b = testflag(a, af_pfr)? a->parent: a;
+ address_item *b = testflag(a, af_pfr) ? a->parent: a;
if (!transport_write_string(fd, "RCPT TO:<%s>%s\n",
transport_rcpt_address(b, tblock->rcpt_include_affixes), cr))
{ yield = DEFER; break; }
if (yield == OK)
{
transport_ctx tctx = {
- tblock,
- addr,
- ob->check_string,
- ob->escape_string,
- ob->options
+ .u = {.fd=fd},
+ .tblock = tblock,
+ .addr = addr,
+ .check_string = ob->check_string,
+ .escape_string = ob->escape_string,
+ .options = ob->options | topt_not_socket
};
- if (!transport_write_message(fd, &tctx, 0))
+ if (!transport_write_message(&tctx, 0))
yield = DEFER;
}
}
else /* Want a repeatable time when in test harness */
{
- addr->more_errno = running_in_test_harness? 10 :
+ addr->more_errno = f.running_in_test_harness ? 10 :
(int)time(NULL) - statbuf.st_mtime;
}
DEBUG(D_transport)
addr->user_message = US"mailbox is full";
DEBUG(D_transport) debug_printf("System quota exceeded for %s%s%s\n",
dataname,
- isdirectory? US"" : US": time since file read = ",
- isdirectory? US"" : readconf_printtime(addr->more_errno));
+ isdirectory ? US"" : US": time since file read = ",
+ isdirectory ? US"" : readconf_printtime(addr->more_errno));
}
/* Handle Exim's own quota-imposition */
addr->user_message = US"mailbox is full";
DEBUG(D_transport) debug_printf("Exim%s quota exceeded for %s%s%s\n",
filecount_msg, dataname,
- isdirectory? US"" : US": time since file read = ",
- isdirectory? US"" : readconf_printtime(addr->more_errno));
+ isdirectory ? US"" : US": time since file read = ",
+ isdirectory ? US"" : readconf_printtime(addr->more_errno));
}
/* Handle a process failure while writing via a filter; the return
yield = PANIC;
addr->message = string_sprintf("transport filter process failed (%d) "
"while writing to %s%s", addr->more_errno, dataname,
- (addr->more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
+ (addr->more_errno == EX_EXECFAILED) ? ": unable to execute command" : "");
}
/* Handle failure to expand header changes */
{
addr->basic_errno = errno;
addr->message = string_sprintf("close() error for %s",
- (ob->mailstore_format)? dataname : filename);
+ (ob->mailstore_format) ? dataname : filename);
yield = DEFER;
}
return FALSE;
}
+#endif /*!MACRO_PREDEF*/
/* End of transport/appendfile.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Private structure for the private options. */
BOOL mbx_format;
BOOL quota_warn_threshold_is_percent;
BOOL quota_is_inclusive;
+ BOOL quota_no_check;
+ BOOL quota_filecount_no_check;
} appendfile_transport_options_block;
/* Restricted creation options */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
int autoreply_transport_options_count =
sizeof(autoreply_transport_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+autoreply_transport_options_block autoreply_transport_option_defaults = {0};
+void autoreply_transport_init(transport_instance *tblock) {}
+BOOL autoreply_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else /*!MACRO_PREDEF*/
+
+
/* Default private options block for the autoreply transport. */
autoreply_transport_options_block autoreply_transport_option_defaults = {
header_line *h;
time_t now = time(NULL);
time_t once_repeat_sec = 0;
-FILE *f;
+FILE *fp;
FILE *ff = NULL;
autoreply_transport_options_block *ob =
file_expand = ob->file_expand;
return_message = ob->return_message;
- if ((from != NULL &&
- (from = checkexpand(from, addr, tblock->name, cke_hdr)) == NULL) ||
- (reply_to != NULL &&
- (reply_to = checkexpand(reply_to, addr, tblock->name, cke_hdr)) == NULL) ||
- (to != NULL &&
- (to = checkexpand(to, addr, tblock->name, cke_hdr)) == NULL) ||
- (cc != NULL &&
- (cc = checkexpand(cc, addr, tblock->name, cke_hdr)) == NULL) ||
- (bcc != NULL &&
- (bcc = checkexpand(bcc, addr, tblock->name, cke_hdr)) == NULL) ||
- (subject != NULL &&
- (subject = checkexpand(subject, addr, tblock->name, cke_hdr)) == NULL) ||
- (headers != NULL &&
- (headers = checkexpand(headers, addr, tblock->name, cke_text)) == NULL) ||
- (text != NULL &&
- (text = checkexpand(text, addr, tblock->name, cke_text)) == NULL) ||
- (file != NULL &&
- (file = checkexpand(file, addr, tblock->name, cke_file)) == NULL) ||
- (logfile != NULL &&
- (logfile = checkexpand(logfile, addr, tblock->name, cke_file)) == NULL) ||
- (oncelog != NULL &&
- (oncelog = checkexpand(oncelog, addr, tblock->name, cke_file)) == NULL) ||
- (oncerepeat != NULL &&
- (oncerepeat = checkexpand(oncerepeat, addr, tblock->name, cke_file)) == NULL))
+ if ( from && !(from = checkexpand(from, addr, tblock->name, cke_hdr))
+ || reply_to && !(reply_to = checkexpand(reply_to, addr, tblock->name, cke_hdr))
+ || to && !(to = checkexpand(to, addr, tblock->name, cke_hdr))
+ || cc && !(cc = checkexpand(cc, addr, tblock->name, cke_hdr))
+ || bcc && !(bcc = checkexpand(bcc, addr, tblock->name, cke_hdr))
+ || subject && !(subject = checkexpand(subject, addr, tblock->name, cke_hdr))
+ || headers && !(headers = checkexpand(headers, addr, tblock->name, cke_text))
+ || text && !(text = checkexpand(text, addr, tblock->name, cke_text))
+ || file && !(file = checkexpand(file, addr, tblock->name, cke_file))
+ || logfile && !(logfile = checkexpand(logfile, addr, tblock->name, cke_file))
+ || oncelog && !(oncelog = checkexpand(oncelog, addr, tblock->name, cke_file))
+ || oncerepeat && !(oncerepeat = checkexpand(oncerepeat, addr, tblock->name, cke_file))
+ )
return FALSE;
- if (oncerepeat != NULL)
+ if (oncerepeat)
{
once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE);
if (once_repeat_sec < 0)
/* If the never_mail option is set, we have to scan all the recipients and
remove those that match. */
-if (ob->never_mail != NULL)
+if (ob->never_mail)
{
const uschar *never_mail = expand_string(ob->never_mail);
- if (never_mail == NULL)
+ if (!never_mail)
{
addr->transport_return = FAIL;
addr->message = string_sprintf("Failed to expand \"%s\" for "
return FALSE;
}
- if (to != NULL) check_never_mail(&to, never_mail);
- if (cc != NULL) check_never_mail(&cc, never_mail);
- if (bcc != NULL) check_never_mail(&bcc, never_mail);
+ if (to) check_never_mail(&to, never_mail);
+ if (cc) check_never_mail(&cc, never_mail);
+ if (bcc) check_never_mail(&bcc, never_mail);
- if (to == NULL && cc == NULL && bcc == NULL)
+ if (!to && !cc && !bcc)
{
DEBUG(D_transport)
debug_printf("*** all recipients removed by never_mail\n");
/* If the -N option is set, can't do any more. */
-if (dont_deliver)
+if (f.dont_deliver)
{
DEBUG(D_transport)
debug_printf("*** delivery by %s transport bypassed by -N option\n",
set, instead of a dbm file, we use a regular file containing a circular buffer
recipient cache. */
-if (oncelog != NULL && *oncelog != 0 && to != NULL)
+if (oncelog && *oncelog != 0 && to)
{
time_t then = 0;
if (ob->once_file_size > 0)
{
- uschar *p;
+ uschar * p, * nextp;
struct stat statbuf;
cache_fd = Uopen(oncelog, O_CREAT|O_RDWR, ob->mode);
zero. If we find a match, put the time into "then", and the place where it
was found into "cache_time". Otherwise, "then" is left at zero. */
- p = cache_buff;
- while (p < cache_buff + cache_size)
+ for (p = cache_buff; p < cache_buff + cache_size; p = nextp)
{
uschar *s = p + sizeof(time_t);
- uschar *nextp = s + Ustrlen(s) + 1;
+ nextp = s + Ustrlen(s) + 1;
if (Ustrcmp(to, s) == 0)
{
memcpy(&then, p, sizeof(time_t));
cache_time = p;
break;
}
- p = nextp;
}
}
else
{
EXIM_DATUM key_datum, result_datum;
- EXIM_DBOPEN(oncelog, O_RDWR|O_CREAT, ob->mode, &dbm_file);
- if (dbm_file == NULL)
+ uschar * dirname = string_copy(oncelog);
+ uschar * s;
+
+ if ((s = Ustrrchr(dirname, '/'))) *s = '\0';
+ EXIM_DBOPEN(oncelog, dirname, O_RDWR|O_CREAT, ob->mode, &dbm_file);
+ if (!dbm_file)
{
addr->transport_return = DEFER;
addr->message = string_sprintf("Failed to open %s file %s when sending "
can be abolished. */
if (EXIM_DATUM_SIZE(result_datum) == sizeof(time_t))
- {
memcpy(&then, EXIM_DATUM_DATA(result_datum), sizeof(time_t));
- }
- else then = now;
+ else
+ then = now;
}
}
/* We are going to send a message. Ensure any requested file is available. */
-if (file != NULL)
+if (file)
{
ff = Ufopen(file, "rb");
- if (ff == NULL && !ob->file_optional)
+ if (!ff && !ob->file_optional)
{
addr->transport_return = DEFER;
addr->message = string_sprintf("Failed to open file %s when sending "
addr->message = string_sprintf("Failed to create child process to send "
"message from %s transport: %s", tblock->name, strerror(errno));
DEBUG(D_transport) debug_printf("%s\n", addr->message);
+ if (dbm_file) EXIM_DBCLOSE(dbm_file);
return FALSE;
}
as the -t option is used. The "headers" stuff *must* be last in case there
are newlines in it which might, if placed earlier, screw up other headers. */
-f = fdopen(fd, "wb");
+fp = fdopen(fd, "wb");
-if (from != NULL) fprintf(f, "From: %s\n", from);
-if (reply_to != NULL) fprintf(f, "Reply-To: %s\n", reply_to);
-if (to != NULL) fprintf(f, "To: %s\n", to);
-if (cc != NULL) fprintf(f, "Cc: %s\n", cc);
-if (bcc != NULL) fprintf(f, "Bcc: %s\n", bcc);
-if (subject != NULL) fprintf(f, "Subject: %s\n", subject);
+if (from) fprintf(fp, "From: %s\n", from);
+if (reply_to) fprintf(fp, "Reply-To: %s\n", reply_to);
+if (to) fprintf(fp, "To: %s\n", to);
+if (cc) fprintf(fp, "Cc: %s\n", cc);
+if (bcc) fprintf(fp, "Bcc: %s\n", bcc);
+if (subject) fprintf(fp, "Subject: %s\n", subject);
/* Generate In-Reply-To from the message_id header; there should
always be one, but code defensively. */
-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h; h = h->next)
if (h->type == htype_id) break;
-if (h != NULL)
+if (h)
{
message_id = Ustrchr(h->text, ':') + 1;
while (isspace(*message_id)) message_id++;
- fprintf(f, "In-Reply-To: %s", message_id);
+ fprintf(fp, "In-Reply-To: %s", message_id);
}
/* Generate a References header if there is at least one of Message-ID:,
References:, or In-Reply-To: (see RFC 2822). */
-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h; h = h->next)
if (h->type != htype_old && strncmpic(US"References:", h->text, 11) == 0)
break;
-if (h == NULL)
- for (h = header_list; h != NULL; h = h->next)
+if (!h)
+ for (h = header_list; h; h = h->next)
if (h->type != htype_old && strncmpic(US"In-Reply-To:", h->text, 12) == 0)
break;
Keep the first message ID for the thread root and the last few for
the position inside the thread, up to a maximum of 12 altogether. */
-if (h != NULL || message_id != NULL)
+if (h || message_id)
{
- fprintf(f, "References:");
- if (h != NULL)
+ fprintf(fp, "References:");
+ if (h)
{
uschar *s, *id, *error;
uschar *referenced_ids[12];
int i;
s = Ustrchr(h->text, ':') + 1;
- parse_allow_group = FALSE;
+ f.parse_allow_group = FALSE;
while (*s != 0 && (s = parse_message_id(s, &id, &error)) != NULL)
{
- if (reference_count == sizeof(referenced_ids)/sizeof(uschar *))
+ if (reference_count == nelem(referenced_ids))
{
memmove(referenced_ids + 1, referenced_ids + 2,
sizeof(referenced_ids) - 2*sizeof(uschar *));
}
else referenced_ids[reference_count++] = id;
}
- for (i = 0; i < reference_count; ++i) fprintf(f, " %s", referenced_ids[i]);
+ for (i = 0; i < reference_count; ++i) fprintf(fp, " %s", referenced_ids[i]);
}
/* The message id will have a newline on the end of it. */
- if (message_id != NULL) fprintf(f, " %s", message_id);
- else fprintf(f, "\n");
+ if (message_id) fprintf(fp, " %s", message_id);
+ else fprintf(fp, "\n");
}
/* Add an Auto-Submitted: header */
-fprintf(f, "Auto-Submitted: auto-replied\n");
+fprintf(fp, "Auto-Submitted: auto-replied\n");
/* Add any specially requested headers */
-if (headers != NULL) fprintf(f, "%s\n", headers);
-fprintf(f, "\n");
+if (headers) fprintf(fp, "%s\n", headers);
+fprintf(fp, "\n");
-if (text != NULL)
+if (text)
{
- fprintf(f, "%s", CS text);
- if (text[Ustrlen(text)-1] != '\n') fprintf(f, "\n");
+ fprintf(fp, "%s", CS text);
+ if (text[Ustrlen(text)-1] != '\n') fprintf(fp, "\n");
}
-if (ff != NULL)
+if (ff)
{
while (Ufgets(big_buffer, big_buffer_size, ff) != NULL)
{
uschar *s = expand_string(big_buffer);
DEBUG(D_transport)
{
- if (s == NULL)
+ if (!s)
debug_printf("error while expanding line from file:\n %s\n %s\n",
big_buffer, expand_string_message);
}
- fprintf(f, "%s", (s == NULL)? CS big_buffer : CS s);
+ fprintf(fp, "%s", s ? CS s : CS big_buffer);
}
- else fprintf(f, "%s", CS big_buffer);
+ else fprintf(fp, "%s", CS big_buffer);
}
(void) fclose(ff);
}
:
US"------ This is a copy of the message, including all the headers.\n";
transport_ctx tctx = {
- tblock,
- addr,
- NULL, NULL,
- (tblock->body_only ? topt_no_headers : 0) |
- (tblock->headers_only ? topt_no_body : 0) |
- (tblock->return_path_add ? topt_add_return_path : 0) |
- (tblock->delivery_date_add ? topt_add_delivery_date : 0) |
- (tblock->envelope_to_add ? topt_add_envelope_to : 0)
+ .u = {.fd = fileno(fp)},
+ .tblock = tblock,
+ .addr = addr,
+ .check_string = NULL,
+ .escape_string = NULL,
+ .options = (tblock->body_only ? topt_no_headers : 0)
+ | (tblock->headers_only ? topt_no_body : 0)
+ | (tblock->return_path_add ? topt_add_return_path : 0)
+ | (tblock->delivery_date_add ? topt_add_delivery_date : 0)
+ | (tblock->envelope_to_add ? topt_add_envelope_to : 0)
+ | topt_not_socket
};
if (bounce_return_size_limit > 0 && !tblock->headers_only)
DELIVER_IN_BUFFER_SIZE;
if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
{
- fprintf(f, "\n%s"
+ fprintf(fp, "\n%s"
"------ The body of the message is " OFF_T_FMT " characters long; only the first\n"
"------ %d or so are included here.\n\n", rubric, statbuf.st_size,
(max/1000)*1000);
}
- else fprintf(f, "\n%s\n", rubric);
+ else fprintf(fp, "\n%s\n", rubric);
}
- else fprintf(f, "\n%s\n", rubric);
+ else fprintf(fp, "\n%s\n", rubric);
- fflush(f);
+ fflush(fp);
transport_count = 0;
- transport_write_message(fileno(f), &tctx, bounce_return_size_limit);
+ transport_write_message(&tctx, bounce_return_size_limit);
}
/* End the message and wait for the child process to end; no timeout. */
-(void)fclose(f);
+(void)fclose(fp);
rc = child_close(pid, 0);
/* Update the "sent to" log whatever the yield. This errs on the side of
{
uschar *from = cache_buff;
int size = cache_size;
- (void)lseek(cache_fd, 0, SEEK_SET);
- if (cache_time == NULL)
+ if (lseek(cache_fd, 0, SEEK_SET) == 0)
{
- cache_time = from + size;
- memcpy(cache_time + sizeof(time_t), to, add_size - sizeof(time_t));
- size += add_size;
-
- if (cache_size > 0 && size > ob->once_file_size)
+ if (!cache_time)
{
- from += sizeof(time_t) + Ustrlen(from + sizeof(time_t)) + 1;
- size -= (from - cache_buff);
+ cache_time = from + size;
+ memcpy(cache_time + sizeof(time_t), to, add_size - sizeof(time_t));
+ size += add_size;
+
+ if (cache_size > 0 && size > ob->once_file_size)
+ {
+ from += sizeof(time_t) + Ustrlen(from + sizeof(time_t)) + 1;
+ size -= (from - cache_buff);
+ }
}
- }
- memcpy(cache_time, &now, sizeof(time_t));
- if(write(cache_fd, from, size) != size)
- DEBUG(D_transport) debug_printf("Problem writing cache file %s for %s "
- "transport\n", oncelog, tblock->name);
+ memcpy(cache_time, &now, sizeof(time_t));
+ if(write(cache_fd, from, size) != size)
+ DEBUG(D_transport) debug_printf("Problem writing cache file %s for %s "
+ "transport\n", oncelog, tblock->name);
+ }
}
/* Update DBM file */
-else if (dbm_file != NULL)
+else if (dbm_file)
{
EXIM_DATUM key_datum, value_datum;
EXIM_DATUM_INIT(key_datum); /* Some DBM libraries need to have */
message, we do not fail. */
if (rc != 0)
- {
if (rc == EXIT_NORECIPIENTS)
{
DEBUG(D_any) debug_printf("%s transport: message contained no recipients\n",
"transport (%d)", tblock->name, rc);
goto END_OFF;
}
- }
/* Log the sending of the message if successful and required. If the file
fails to open, it's hard to know what to do. We cannot write to the Exim
different processes. The log_buffer can be used exactly as for main log
writing. */
-if (logfile != NULL)
+if (logfile)
{
int log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode);
if (log_fd >= 0)
DEBUG(D_transport) debug_printf("logging message details\n");
sprintf(CS ptr, "%s\n", tod_stamp(tod_log));
while(*ptr) ptr++;
- if (from != NULL)
+ if (from)
{
(void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
" From: %s\n", from);
while(*ptr) ptr++;
}
- if (to != NULL)
+ if (to)
{
(void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
" To: %s\n", to);
while(*ptr) ptr++;
}
- if (cc != NULL)
+ if (cc)
{
(void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
" Cc: %s\n", cc);
while(*ptr) ptr++;
}
- if (bcc != NULL)
+ if (bcc)
{
(void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
" Bcc: %s\n", bcc);
while(*ptr) ptr++;
}
- if (subject != NULL)
+ if (subject)
{
(void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
" Subject: %s\n", subject);
while(*ptr) ptr++;
}
- if (headers != NULL)
+ if (headers)
{
(void)string_format(ptr, LOG_BUFFER_SIZE - (ptr-log_buffer),
" %s\n", headers);
}
END_OFF:
-if (dbm_file != NULL) EXIM_DBCLOSE(dbm_file);
+if (dbm_file) EXIM_DBCLOSE(dbm_file);
if (cache_fd > 0) (void)close(cache_fd);
DEBUG(D_transport) debug_printf("%s transport succeeded\n", tblock->name);
return FALSE;
}
+#endif /*!MACRO_PREDEF*/
/* End of transport/autoreply.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
int lmtp_transport_options_count =
sizeof(lmtp_transport_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+lmtp_transport_options_block lmtp_transport_option_defaults = {0};
+void lmtp_transport_init(transport_instance *tblock) {}
+BOOL lmtp_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else /*!MACRO_PREDEF*/
+
+
/* Default private options block for the lmtp transport. */
lmtp_transport_options_block lmtp_transport_option_defaults = {
static BOOL
lmtp_write_command(int fd, const char *format, ...)
{
-int count, rc;
+gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
+int rc;
va_list ap;
+
va_start(ap, format);
-if (!string_vformat(big_buffer, big_buffer_size, CS format, ap))
+if (!string_vformat(&gs, FALSE, CS format, ap))
{
va_end(ap);
errno = ERRNO_SMTPFORMAT;
return FALSE;
}
va_end(ap);
-count = Ustrlen(big_buffer);
-DEBUG(D_transport|D_v) debug_printf(" LMTP>> %s", big_buffer);
-rc = write(fd, big_buffer, count);
-big_buffer[count-2] = 0; /* remove \r\n for debug and error message */
+DEBUG(D_transport|D_v) debug_printf(" LMTP>> %s", string_from_gstring(&gs));
+rc = write(fd, gs.s, gs.ptr);
+gs.ptr -= 2; string_from_gstring(&gs); /* remove \r\n for debug and error message */
if (rc > 0) return TRUE;
DEBUG(D_transport) debug_printf("write failed: %s\n", strerror(errno));
return FALSE;
*readptr = 0; /* In case nothing gets read */
sigalrm_seen = FALSE;
- alarm(timeout);
+ ALARM(timeout);
rc = Ufgets(readptr, size-1, f);
save_errno = errno;
- alarm(0);
+ ALARM_CLR(0);
errno = save_errno;
if (rc != NULL) break; /* A line has been read */
return FALSE;
/* If the -N option is set, can't do any more. Presume all has gone well. */
- if (dont_deliver)
+ if (f.dont_deliver)
goto MINUS_N;
/* As this is a local transport, we are already running with the required
}
/* If the -N option is set, can't do any more. Presume all has gone well. */
- if (dont_deliver)
+ if (f.dont_deliver)
goto MINUS_N;
sockun.sun_family = AF_UNIX;
{
BOOL ok;
transport_ctx tctx = {
+ {fd_in},
tblock,
addrlist,
US".", US"..",
debug_printf(" LMTP>> writing message and terminating \".\"\n");
transport_count = 0;
- ok = transport_write_message(fd_in, &tctx, 0);
+ ok = transport_write_message(&tctx, 0);
/* Failure can either be some kind of I/O disaster (including timeout),
or the failure of a transport filter or the expansion of added headers. */
{
const uschar *s = string_printing(buffer);
/* de-const safe here as string_printing known to have alloc'n'copied */
- addr->message = (s == buffer)? (uschar *)string_copy(s) : US s;
+ addr->message = (s == buffer)? US string_copy(s) : US s;
}
}
/* If the response has failed badly, use it for all the remaining pending
return FALSE;
}
+#endif /*!MACRO_PREDEF*/
/* End of transport/lmtp.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
int pipe_transport_options_count =
sizeof(pipe_transport_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+pipe_transport_options_block pipe_transport_option_defaults = {0};
+void pipe_transport_init(transport_instance *tblock) {}
+BOOL pipe_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else /*!MACRO_PREDEF*/
+
+
/* Default private options block for the pipe transport. */
pipe_transport_options_block pipe_transport_option_defaults = {
/* We have to take special action to handle the special "variable" called
$pipe_addresses, which is not recognized by the normal expansion function. */
-DEBUG(D_transport)
- debug_printf("shell pipe command before expansion:\n %s\n", cmd);
-
if (expand_arguments)
{
- uschar *s = cmd;
- uschar *p = Ustrstr(cmd, "pipe_addresses");
+ uschar * p = Ustrstr(cmd, "pipe_addresses");
+ gstring * g = NULL;
+
+ DEBUG(D_transport)
+ debug_printf("shell pipe command before expansion:\n %s\n", cmd);
+
+ /* Allow $recipients in the expansion iff it comes from a system filter */
+
+ f.enable_dollar_recipients = addr && addr->parent &&
+ Ustrcmp(addr->parent->address, "system-filter") == 0;
if (p != NULL && (
(p > cmd && p[-1] == '$') ||
{
address_item *ad;
uschar *q = p + 14;
- int size = Ustrlen(cmd) + 64;
- int offset;
if (p[-1] == '{') { q++; p--; }
- s = store_get(size);
- offset = p - cmd - 1;
- Ustrncpy(s, cmd, offset);
+ g = string_get(Ustrlen(cmd) + 64);
+ g = string_catn(g, cmd, p - cmd - 1);
- for (ad = addr; ad != NULL; ad = ad->next)
+ for (ad = addr; ad; ad = ad->next)
{
/*XXX string_append_listele() ? */
- if (ad != addr) s = string_catn(s, &size, &offset, US" ", 1);
- s = string_cat(s, &size, &offset, ad->address);
+ if (ad != addr) g = string_catn(g, US" ", 1);
+ g = string_cat(g, ad->address);
}
- s = string_cat(s, &size, &offset, q);
- s[offset] = 0;
+ g = string_cat(g, q);
+ argv[2] = (cmd = string_from_gstring(g)) ? expand_string(cmd) : NULL;
}
+ else
+ argv[2] = expand_string(cmd);
- /* Allow $recipients in the expansion iff it comes from a system filter */
-
- enable_dollar_recipients = addr != NULL &&
- addr->parent != NULL &&
- Ustrcmp(addr->parent->address, "system-filter") == 0;
- argv[2] = expand_string(s);
- enable_dollar_recipients = FALSE;
+ f.enable_dollar_recipients = FALSE;
- if (argv[2] == NULL)
+ if (!argv[2])
{
- addr->transport_return = search_find_defer? DEFER : expand_fail;
+ addr->transport_return = f.search_find_defer ? DEFER : expand_fail;
addr->message = string_sprintf("Expansion of command \"%s\" "
"in %s transport failed: %s",
cmd, tname, expand_string_message);
DEBUG(D_transport)
debug_printf("shell pipe command after expansion:\n %s\n", argv[2]);
}
-else argv[2] = cmd;
+else
+ {
+ DEBUG(D_transport)
+ debug_printf("shell pipe command (no expansion):\n %s\n", cmd);
+ argv[2] = cmd;
+ }
-argv[3] = (uschar *)0;
+argv[3] = US 0;
return TRUE;
}
uschar *cmd, *ss;
uschar *eol = ob->use_crlf ? US"\r\n" : US"\n";
transport_ctx tctx = {
- tblock,
- addr,
- ob->check_string,
- ob->escape_string,
- ob->options /* set at initialization time */
+ .tblock = tblock,
+ .addr = addr,
+ .check_string = ob->check_string,
+ .escape_string = ob->escape_string,
+ ob->options | topt_not_socket /* set at initialization time */
};
DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);
if (addr->host_list != NULL)
envp[envcount++] = string_sprintf("HOST=%s", addr->host_list->name);
-if (timestamps_utc) envp[envcount++] = US"TZ=UTC";
+if (f.timestamps_utc) envp[envcount++] = US"TZ=UTC";
else if (timezone_string != NULL && timezone_string[0] != 0)
envp[envcount++] = string_sprintf("TZ=%s", timezone_string);
}
}
-while ((ss = string_nextinlist(&envlist, &envsep, big_buffer, big_buffer_size))
- != NULL)
+while ((ss = string_nextinlist(&envlist, &envsep, big_buffer, big_buffer_size)))
{
- if (envcount > sizeof(envp)/sizeof(uschar *) - 2)
+ if (envcount > nelem(envp) - 2)
{
addr->transport_return = DEFER;
addr->message = string_sprintf("too many environment settings for "
/* If the -N option is set, can't do any more. */
-if (dont_deliver)
+if (f.dont_deliver)
{
DEBUG(D_transport)
debug_printf("*** delivery by %s transport bypassed by -N option",
strerror(errno));
return FALSE;
}
+tctx.u.fd = fd_in;
/* Now fork a process to handle the output that comes down the pipe. */
ignore all writing errors. (When in the test harness, we do do a short sleep so
any debugging output is likely to be in the same order.) */
-if (running_in_test_harness) millisleep(500);
+if (f.running_in_test_harness) millisleep(500);
DEBUG(D_transport) debug_printf("Writing message to pipe\n");
uschar *prefix = expand_string(ob->message_prefix);
if (prefix == NULL)
{
- addr->transport_return = search_find_defer? DEFER : PANIC;
+ addr->transport_return = f.search_find_defer? DEFER : PANIC;
addr->message = string_sprintf("Expansion of \"%s\" (prefix for %s "
"transport) failed: %s", ob->message_prefix, tblock->name,
expand_string_message);
return FALSE;
}
- if (!transport_write_block(fd_in, prefix, Ustrlen(prefix)))
+ if (!transport_write_block(&tctx, prefix, Ustrlen(prefix), FALSE))
goto END_WRITE;
}
/* Now the actual message */
-if (!transport_write_message(fd_in, &tctx, 0))
+if (!transport_write_message(&tctx, 0))
goto END_WRITE;
/* Now any configured suffix */
uschar *suffix = expand_string(ob->message_suffix);
if (!suffix)
{
- addr->transport_return = search_find_defer? DEFER : PANIC;
+ addr->transport_return = f.search_find_defer? DEFER : PANIC;
addr->message = string_sprintf("Expansion of \"%s\" (suffix for %s "
"transport) failed: %s", ob->message_suffix, tblock->name,
expand_string_message);
return FALSE;
}
- if (!transport_write_block(fd_in, suffix, Ustrlen(suffix)))
+ if (!transport_write_block(&tctx, suffix, Ustrlen(suffix), FALSE))
goto END_WRITE;
}
if (errno == ETIMEDOUT)
{
addr->message = string_sprintf("%stimeout while writing to pipe",
- transport_filter_timed_out? "transport filter " : "");
+ f.transport_filter_timed_out ? "transport filter " : "");
addr->transport_return = ob->timeout_defer? DEFER : FAIL;
timeout = 1;
}
This prevents the transport_filter timeout message from getting overwritten
by the exit error which is not the cause of the problem. */
- else if (transport_filter_timed_out)
+ else if (f.transport_filter_timed_out)
{
killpg(pid, SIGKILL);
kill(outpid, SIGKILL);
else if (!ob->ignore_status)
{
uschar *ss;
- int size, ptr, i;
+ gstring * g;
+ int i;
/* If temp_errors is "*" all codes are temporary. Initialization checks
that it's either "*" or a list of numbers. If not "*", scan the list of
addr->message = string_sprintf("Child process of %s transport returned "
"%d", tblock->name, rc);
-
- ptr = Ustrlen(addr->message);
- size = ptr + 1;
+ g = string_cat(NULL, addr->message);
/* If the return code is > 128, it often means that a shell command
was terminated by a signal. */
if (*ss != 0)
{
- addr->message = string_catn(addr->message, &size, &ptr, US" ", 1);
- addr->message = string_cat (addr->message, &size, &ptr, ss);
+ g = string_catn(g, US" ", 1);
+ g = string_cat (g, ss);
}
/* Now add the command and arguments */
- addr->message = string_catn(addr->message, &size, &ptr,
- US" from command:", 14);
+ g = string_catn(g, US" from command:", 14);
for (i = 0; i < sizeof(argv)/sizeof(int *) && argv[i] != NULL; i++)
{
BOOL quote = FALSE;
- addr->message = string_catn(addr->message, &size, &ptr, US" ", 1);
+ g = string_catn(g, US" ", 1);
if (Ustrpbrk(argv[i], " \t") != NULL)
{
quote = TRUE;
- addr->message = string_catn(addr->message, &size, &ptr, US"\"", 1);
+ g = string_catn(g, US"\"", 1);
}
- addr->message = string_cat(addr->message, &size, &ptr, argv[i]);
+ g = string_cat(g, argv[i]);
if (quote)
- addr->message = string_catn(addr->message, &size, &ptr, US"\"", 1);
+ g = string_catn(g, US"\"", 1);
}
/* Add previous filter timeout message, if present. */
if (*tmsg)
- addr->message = string_cat(addr->message, &size, &ptr, tmsg);
+ g = string_cat(g, tmsg);
- addr->message[ptr] = 0; /* Ensure concatenated string terminated */
+ addr->message = string_from_gstring(g);
}
}
}
return FALSE;
}
+#endif /*!MACRO_PREDEF*/
/* End of transport/pipe.c */
/* Copyright (c) Andrew Colin Kissa <andrew@topdog.za.net> 2016 */
/* Copyright (c) University of Cambridge 2016 */
+/* Copyright (c) The Exim Maintainers 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
(void *)offsetof(queuefile_transport_options_block, dirname) },
};
+
/* Size of the options list. An extern variable has to be used so that its
address can appear in the tables drtables.c. */
int queuefile_transport_options_count =
sizeof(queuefile_transport_options) / sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+queuefile_transport_options_block queuefile_transport_option_defaults = {0};
+void queuefile_transport_init(transport_instance *tblock) {}
+BOOL queuefile_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else /*!MACRO_PREDEF*/
+
+
+
/* Default private options block for the appendfile transport. */
queuefile_transport_options_block queuefile_transport_option_defaults = {
Arguments:
tb the transport block
addr address_item being processed
+ dstpath destination directory name
sdfd int Source directory fd
ddfd int Destination directory fd
link_file BOOL use linkat instead of data copy
static BOOL
copy_spool_files(transport_instance * tb, address_item * addr,
- int sdfd, int ddfd, BOOL link_file, int srcfd)
+ const uschar * dstpath, int sdfd, int ddfd, BOOL link_file, int srcfd)
{
BOOL is_hdr_file = srcfd < 0;
const uschar * suffix = srcfd < 0 ? US"H" : US"D";
int dstfd;
const uschar * filename = string_sprintf("%s-%s", message_id, suffix);
const uschar * srcpath = spool_fname(US"input", message_subdir, message_id, suffix);
-const uschar * dstpath = string_sprintf("%s/%s-%s",
- ((queuefile_transport_options_block *) tb->options_block)->dirname,
- message_id, suffix);
-const uschar * s;
-const uschar * op;
+const uschar * s, * op;
+
+dstpath = string_sprintf("%s/%s-%s", dstpath, message_id, suffix);
if (link_file)
{
(queuefile_transport_options_block *) tblock->options_block;
BOOL can_link;
uschar * sourcedir = spool_dname(US"input", message_subdir);
-uschar * s;
+uschar * s, * dstdir;
struct stat dstatbuf, sstatbuf;
int ddfd = -1, sdfd = -1;
# define O_NOFOLLOW 0
#endif
-if (ob->dirname[0] != '/')
+if (!(dstdir = expand_string(ob->dirname)))
+ {
+ addr->message = string_sprintf("%s transport: failed to expand dirname option",
+ tblock->name);
+ addr->transport_return = DEFER;
+ return FALSE;
+ }
+if (*dstdir != '/')
{
addr->transport_return = PANIC;
addr->message = string_sprintf("%s transport directory: "
- "%s is not absolute", tblock->name, ob->dirname);
+ "%s is not absolute", tblock->name, dstdir);
return FALSE;
}
/* Open the source and destination directories and check if they are
on the same filesystem, so we can hard-link files rather than copying. */
-if ( (s = ob->dirname,
+if ( (s = dstdir,
(ddfd = Uopen(s, O_RDONLY | O_DIRECTORY | O_NOFOLLOW, 0)) < 0)
|| (s = sourcedir,
(sdfd = Uopen(sourcedir, O_RDONLY | O_DIRECTORY | O_NOFOLLOW, 0)) < 0)
return FALSE;
}
-if ( (s = ob->dirname, fstat(ddfd, &dstatbuf) < 0)
- || (s = sourcedir, fstat(sdfd, &sstatbuf) < 0)
+if ( (s = dstdir, fstat(ddfd, &dstatbuf) < 0)
+ || (s = sourcedir, fstat(sdfd, &sstatbuf) < 0)
)
{
addr->transport_return = PANIC;
}
can_link = (dstatbuf.st_dev == sstatbuf.st_dev);
-if (dont_deliver)
+if (f.dont_deliver)
{
DEBUG(D_transport)
debug_printf("*** delivery by %s transport bypassed by -N option\n",
DEBUG(D_transport)
debug_printf("%s transport, copying header file\n", tblock->name);
-if (!copy_spool_files(tblock, addr, sdfd, ddfd, can_link, -1))
+if (!copy_spool_files(tblock, addr, dstdir, sdfd, ddfd, can_link, -1))
goto RETURN;
DEBUG(D_transport)
debug_printf("%s transport, copying data file\n", tblock->name);
-if (!copy_spool_files(tblock, addr, sdfd, ddfd, can_link, deliver_datafile))
+if (!copy_spool_files(tblock, addr, dstdir, sdfd, ddfd, can_link,
+ deliver_datafile))
{
DEBUG(D_transport)
debug_printf("%s transport, copying data file failed, "
"unlinking the header file\n", tblock->name);
- Uunlink(string_sprintf("%s/%s-H", ob->dirname, message_id));
+ Uunlink(string_sprintf("%s/%s-H", dstdir, message_id));
goto RETURN;
}
put in the first address of a batch. */
return FALSE;
}
+
+#endif /*!MACRO_PREDEF*/
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#include "../exim.h"
(void *)offsetof(smtp_transport_options_block, address_retry_include_sender) },
{ "allow_localhost", opt_bool,
(void *)offsetof(smtp_transport_options_block, allow_localhost) },
+#ifdef EXPERIMENTAL_ARC
+ { "arc_sign", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, arc_sign) },
+#endif
{ "authenticated_sender", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, authenticated_sender) },
{ "authenticated_sender_force", opt_bool,
(void *)offsetof(smtp_transport_options_block, connect_timeout) },
{ "connection_max_messages", opt_int | opt_public,
(void *)offsetof(transport_instance, connection_max_messages) },
+# ifdef SUPPORT_DANE
+ { "dane_require_tls_ciphers", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dane_require_tls_ciphers) },
+# endif
{ "data_timeout", opt_time,
(void *)offsetof(smtp_transport_options_block, data_timeout) },
{ "delay_after_cutoff", opt_bool,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_canon) },
{ "dkim_domain", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_domain) },
+ { "dkim_hash", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_hash) },
+ { "dkim_identity", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_identity) },
{ "dkim_private_key", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_private_key) },
{ "dkim_selector", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_sign_headers) },
{ "dkim_strict", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, dkim.dkim_strict) },
+ { "dkim_timestamps", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dkim.dkim_timestamps) },
#endif
{ "dns_qualify_single", opt_bool,
(void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
#ifdef SUPPORT_TLS
{ "hosts_nopass_tls", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_nopass_tls) },
+ { "hosts_noproxy_tls", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_noproxy_tls) },
#endif
{ "hosts_override", opt_bool,
(void *)offsetof(smtp_transport_options_block, hosts_override) },
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ { "hosts_pipe_connect", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_pipe_connect) },
+#endif
{ "hosts_randomize", opt_bool,
(void *)offsetof(smtp_transport_options_block, hosts_randomize) },
#if defined(SUPPORT_TLS) && !defined(DISABLE_OCSP)
{ "hosts_require_auth", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
#ifdef SUPPORT_TLS
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
{ "hosts_require_dane", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_dane) },
# endif
(void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
{ "hosts_try_chunking", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_try_chunking) },
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
{ "hosts_try_dane", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_try_dane) },
#endif
{ "serialize_hosts", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, serialize_hosts) },
{ "size_addition", opt_int,
- (void *)offsetof(smtp_transport_options_block, size_addition) }
+ (void *)offsetof(smtp_transport_options_block, size_addition) },
#ifdef SUPPORT_SOCKS
- ,{ "socks_proxy", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, socks_proxy) }
+ { "socks_proxy", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, socks_proxy) },
#endif
#ifdef SUPPORT_TLS
- ,{ "tls_certificate", opt_stringptr,
+ { "tls_certificate", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_certificate) },
{ "tls_crl", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_crl) },
{ "tls_verify_certificates", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_verify_certificates) },
{ "tls_verify_hosts", opt_stringptr,
- (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) }
+ (void *)offsetof(smtp_transport_options_block, tls_verify_hosts) },
+#endif
+#ifdef SUPPORT_I18N
+ { "utf8_downconvert", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, utf8_downconvert) },
#endif
};
/* Size of the options list. An extern variable has to be used so that its
address can appear in the tables drtables.c. */
-int smtp_transport_options_count =
- sizeof(smtp_transport_options)/sizeof(optionlist);
+int smtp_transport_options_count = nelem(smtp_transport_options);
+
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+smtp_transport_options_block smtp_transport_option_defaults = {0};
+void smtp_transport_init(transport_instance *tblock) {}
+BOOL smtp_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+void smtp_transport_closedown(transport_instance *tblock) {}
+
+#else /*!MACRO_PREDEF*/
+
/* Default private options block for the smtp transport. */
smtp_transport_options_block smtp_transport_option_defaults = {
- NULL, /* hosts */
- NULL, /* fallback_hosts */
- NULL, /* hostlist */
- NULL, /* fallback_hostlist */
- NULL, /* authenticated_sender */
- US"$primary_hostname", /* helo_data */
- NULL, /* interface */
- NULL, /* port */
- US"smtp", /* protocol */
- NULL, /* DSCP */
- NULL, /* serialize_hosts */
- NULL, /* hosts_try_auth */
- NULL, /* hosts_require_auth */
- US"*", /* hosts_try_chunking */
-#ifdef EXPERIMENTAL_DANE
- NULL, /* hosts_try_dane */
- NULL, /* hosts_require_dane */
+ .hosts = NULL,
+ .fallback_hosts = NULL,
+ .hostlist = NULL,
+ .fallback_hostlist = NULL,
+ .helo_data = US"$primary_hostname",
+ .interface = NULL,
+ .port = NULL,
+ .protocol = US"smtp",
+ .dscp = NULL,
+ .serialize_hosts = NULL,
+ .hosts_try_auth = NULL,
+ .hosts_require_auth = NULL,
+ .hosts_try_chunking = US"*",
+#ifdef SUPPORT_DANE
+ .hosts_try_dane = NULL,
+ .hosts_require_dane = NULL,
+ .dane_require_tls_ciphers = NULL,
#endif
- NULL, /* hosts_try_fastopen */
+ .hosts_try_fastopen = NULL,
#ifndef DISABLE_PRDR
- US"*", /* hosts_try_prdr */
+ .hosts_try_prdr = US"*",
#endif
#ifndef DISABLE_OCSP
- US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */
- NULL, /* hosts_require_ocsp */
+ .hosts_request_ocsp = US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */
+ .hosts_require_ocsp = NULL,
+#endif
+ .hosts_require_tls = NULL,
+ .hosts_avoid_tls = NULL,
+ .hosts_verify_avoid_tls = NULL,
+ .hosts_avoid_pipelining = NULL,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ .hosts_pipe_connect = NULL,
#endif
- NULL, /* hosts_require_tls */
- NULL, /* hosts_avoid_tls */
- NULL, /* hosts_verify_avoid_tls */
- NULL, /* hosts_avoid_pipelining */
- NULL, /* hosts_avoid_esmtp */
- NULL, /* hosts_nopass_tls */
- 5*60, /* command_timeout */
- 5*60, /* connect_timeout; shorter system default overrides */
- 5*60, /* data timeout */
- 10*60, /* final timeout */
- 1024, /* size_addition */
- 5, /* hosts_max_try */
- 50, /* hosts_max_try_hardlimit */
- TRUE, /* address_retry_include_sender */
- FALSE, /* allow_localhost */
- FALSE, /* authenticated_sender_force */
- FALSE, /* gethostbyname */
- TRUE, /* dns_qualify_single */
- FALSE, /* dns_search_parents */
- { NULL, NULL }, /* dnssec_domains {request,require} */
- TRUE, /* delay_after_cutoff */
- FALSE, /* hosts_override */
- FALSE, /* hosts_randomize */
- TRUE, /* keepalive */
- FALSE, /* lmtp_ignore_quota */
- NULL, /* expand_retry_include_ip_address */
- TRUE /* retry_include_ip_address */
+ .hosts_avoid_esmtp = NULL,
+#ifdef SUPPORT_TLS
+ .hosts_nopass_tls = NULL,
+ .hosts_noproxy_tls = US"*",
+#endif
+ .command_timeout = 5*60,
+ .connect_timeout = 5*60,
+ .data_timeout = 5*60,
+ .final_timeout = 10*60,
+ .size_addition = 1024,
+ .hosts_max_try = 5,
+ .hosts_max_try_hardlimit = 50,
+ .address_retry_include_sender = TRUE,
+ .allow_localhost = FALSE,
+ .authenticated_sender_force = FALSE,
+ .gethostbyname = FALSE,
+ .dns_qualify_single = TRUE,
+ .dns_search_parents = FALSE,
+ .dnssec = { .request=NULL, .require=NULL },
+ .delay_after_cutoff = TRUE,
+ .hosts_override = FALSE,
+ .hosts_randomize = FALSE,
+ .keepalive = TRUE,
+ .lmtp_ignore_quota = FALSE,
+ .expand_retry_include_ip_address = NULL,
+ .retry_include_ip_address = TRUE,
#ifdef SUPPORT_SOCKS
- ,NULL /* socks_proxy */
+ .socks_proxy = NULL,
#endif
#ifdef SUPPORT_TLS
- ,NULL, /* tls_certificate */
- NULL, /* tls_crl */
- NULL, /* tls_privatekey */
- NULL, /* tls_require_ciphers */
- NULL, /* tls_sni */
- US"system", /* tls_verify_certificates */
- EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
- /* tls_dh_min_bits */
- TRUE, /* tls_tempfail_tryclear */
- NULL, /* tls_verify_hosts */
- US"*", /* tls_try_verify_hosts */
- US"*" /* tls_verify_cert_hostnames */
+ .tls_certificate = NULL,
+ .tls_crl = NULL,
+ .tls_privatekey = NULL,
+ .tls_require_ciphers = NULL,
+ .tls_sni = NULL,
+ .tls_verify_certificates = US"system",
+ .tls_dh_min_bits = EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
+ .tls_tempfail_tryclear = TRUE,
+ .tls_verify_hosts = NULL,
+ .tls_try_verify_hosts = US"*",
+ .tls_verify_cert_hostnames = US"*",
+#endif
+#ifdef SUPPORT_I18N
+ .utf8_downconvert = NULL,
#endif
#ifndef DISABLE_DKIM
- , {NULL, /* dkim_canon */
- NULL, /* dkim_domain */
- NULL, /* dkim_private_key */
- NULL, /* dkim_selector */
- NULL, /* dkim_sign_headers */
- NULL, /* dkim_strict */
- FALSE} /* dot_stuffed */
+ .dkim =
+ {.dkim_domain = NULL,
+ .dkim_identity = NULL,
+ .dkim_private_key = NULL,
+ .dkim_selector = NULL,
+ .dkim_canon = NULL,
+ .dkim_sign_headers = NULL,
+ .dkim_strict = NULL,
+ .dkim_hash = US"sha256",
+ .dkim_timestamps = NULL,
+ .dot_stuffed = FALSE,
+ .force_bodyhash = FALSE,
+# ifdef EXPERIMENTAL_ARC
+ .arc_signspec = NULL,
+# endif
+ },
+# ifdef EXPERIMENTAL_ARC
+ .arc_sign = NULL,
+# endif
#endif
};
static uschar *mail_command; /* Points to MAIL cmd for error messages */
static uschar *data_command = US""; /* Points to DATA cmd for error messages */
static BOOL update_waiting; /* TRUE to update the "wait" database */
+
+/*XXX move to smtp_context */
static BOOL pipelining_active; /* current transaction is in pipe mode */
+static unsigned ehlo_response(uschar * buf, unsigned checks);
+
+
/*************************************************
* Setup entry point *
*************************************************/
smtp_transport_setup(transport_instance *tblock, address_item *addrlist,
transport_feedback *tf, uid_t uid, gid_t gid, uschar **errmsg)
{
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)(tblock->options_block);
+smtp_transport_options_block *ob = SOB tblock->options_block;
errmsg = errmsg; /* Keep picky compilers happy */
uid = uid;
void
smtp_transport_init(transport_instance *tblock)
{
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)(tblock->options_block);
+smtp_transport_options_block *ob = SOB tblock->options_block;
/* Retry_use_local_part defaults FALSE if unset */
/* Set the default port according to the protocol */
-if (ob->port == NULL)
- ob->port = (strcmpic(ob->protocol, US"lmtp") == 0)? US"lmtp" :
- (strcmpic(ob->protocol, US"smtps") == 0)? US"smtps" : US"smtp";
+if (!ob->port)
+ ob->port = strcmpic(ob->protocol, US"lmtp") == 0
+ ? US"lmtp"
+ : strcmpic(ob->protocol, US"smtps") == 0
+ ? US"smtps" : US"smtp";
/* Set up the setup entry point, to be called before subprocesses for this
transport. */
/* This writes to the main log and to the message log.
Arguments:
- addr the address item containing error information
host the current host
+ detail the current message (addr_item->message)
+ basic_errno the errno (addr_item->basic_errno)
Returns: nothing
*/
static void
-write_logs(address_item *addr, host_item *host)
+write_logs(const host_item *host, const uschar *suffix, int basic_errno)
{
-uschar * message = LOGGING(outgoing_port)
- ? string_sprintf("H=%s [%s]:%d", host->name, host->address,
+gstring * message = LOGGING(outgoing_port)
+ ? string_fmt_append(NULL, "H=%s [%s]:%d", host->name, host->address,
host->port == PORT_NONE ? 25 : host->port)
- : string_sprintf("H=%s [%s]", host->name, host->address);
+ : string_fmt_append(NULL, "H=%s [%s]", host->name, host->address);
-if (addr->message)
+if (suffix)
{
- message = string_sprintf("%s: %s", message, addr->message);
- if (addr->basic_errno > 0)
- message = string_sprintf("%s: %s", message, strerror(addr->basic_errno));
- log_write(0, LOG_MAIN, "%s", message);
- deliver_msglog("%s %s\n", tod_stamp(tod_log), message);
+ message = string_fmt_append(message, ": %s", suffix);
+ if (basic_errno > 0)
+ message = string_fmt_append(message, ": %s", strerror(basic_errno));
}
else
- {
- const uschar * s = exim_errstr(addr->basic_errno);
- log_write(0, LOG_MAIN, "%s %s", message, s);
- deliver_msglog("%s %s %s\n", tod_stamp(tod_log), message, s);
- }
+ message = string_fmt_append(message, " %s", exim_errstr(basic_errno));
+
+log_write(0, LOG_MAIN, "%s", string_from_gstring(message));
+deliver_msglog("%s %s\n", tod_stamp(tod_log), message->s);
}
static void
}
#endif
+/*************************************************
+* Reap SMTP specific responses *
+*************************************************/
+static int
+smtp_discard_responses(smtp_context * sx, smtp_transport_options_block * ob,
+ int count)
+{
+uschar flushbuffer[4096];
+
+while (count-- > 0)
+ {
+ if (!smtp_read_response(sx, flushbuffer, sizeof(flushbuffer),
+ '2', ob->command_timeout)
+ && (errno != 0 || flushbuffer[0] == 0))
+ break;
+ }
+return count;
+}
+
+
+/* Return boolean success */
+
+static BOOL
+smtp_reap_banner(smtp_context * sx)
+{
+BOOL good_response = smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+ '2', (SOB sx->conn_args.ob)->command_timeout);
+#ifdef EXPERIMENTAL_DSN_INFO
+sx->smtp_greeting = string_copy(sx->buffer);
+#endif
+return good_response;
+}
+
+static BOOL
+smtp_reap_ehlo(smtp_context * sx)
+{
+if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
+ (SOB sx->conn_args.ob)->command_timeout))
+ {
+ if (errno != 0 || sx->buffer[0] == 0 || sx->lmtp)
+ {
+#ifdef EXPERIMENTAL_DSN_INFO
+ sx->helo_response = string_copy(sx->buffer);
+#endif
+ return FALSE;
+ }
+ sx->esmtp = FALSE;
+ }
+#ifdef EXPERIMENTAL_DSN_INFO
+sx->helo_response = string_copy(sx->buffer);
+#endif
+return TRUE;
+}
+
+
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+static uschar *
+ehlo_cache_key(const smtp_context * sx)
+{
+host_item * host = sx->conn_args.host;
+return Ustrchr(host->address, ':')
+ ? string_sprintf("[%s]:%d.EHLO", host->address,
+ host->port == PORT_NONE ? sx->port : host->port)
+ : string_sprintf("%s:%d.EHLO", host->address,
+ host->port == PORT_NONE ? sx->port : host->port);
+}
+
+static void
+write_ehlo_cache_entry(const smtp_context * sx)
+{
+open_db dbblock, * dbm_file;
+
+if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+ {
+ uschar * ehlo_resp_key = ehlo_cache_key(sx);
+ dbdata_ehlo_resp er = { .data = sx->ehlo_resp };
+
+ HDEBUG(D_transport) debug_printf("writing clr %04x/%04x cry %04x/%04x\n",
+ sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths,
+ sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths);
+
+ dbfn_write(dbm_file, ehlo_resp_key, &er, (int)sizeof(er));
+ dbfn_close(dbm_file);
+ }
+}
+
+static void
+invalidate_ehlo_cache_entry(smtp_context * sx)
+{
+open_db dbblock, * dbm_file;
+
+if ( sx->early_pipe_active
+ && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+ {
+ uschar * ehlo_resp_key = ehlo_cache_key(sx);
+ dbfn_delete(dbm_file, ehlo_resp_key);
+ dbfn_close(dbm_file);
+ }
+}
+
+static BOOL
+read_ehlo_cache_entry(smtp_context * sx)
+{
+open_db dbblock;
+open_db * dbm_file;
+
+if (!(dbm_file = dbfn_open(US"misc", O_RDONLY, &dbblock, FALSE)))
+ { DEBUG(D_transport) debug_printf("ehlo-cache: no misc DB\n"); }
+else
+ {
+ uschar * ehlo_resp_key = ehlo_cache_key(sx);
+ dbdata_ehlo_resp * er;
+
+ if (!(er = dbfn_read(dbm_file, ehlo_resp_key)))
+ { DEBUG(D_transport) debug_printf("no ehlo-resp record\n"); }
+ else if (time(NULL) - er->time_stamp > retry_data_expire)
+ {
+ DEBUG(D_transport) debug_printf("ehlo-resp record too old\n");
+ dbfn_close(dbm_file);
+ if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE)))
+ dbfn_delete(dbm_file, ehlo_resp_key);
+ }
+ else
+ {
+ sx->ehlo_resp = er->data;
+ dbfn_close(dbm_file);
+ DEBUG(D_transport) debug_printf(
+ "EHLO response bits from cache: cleartext 0x%04x crypted 0x%04x\n",
+ er->data.cleartext_features, er->data.crypted_features);
+ return TRUE;
+ }
+ dbfn_close(dbm_file);
+ }
+return FALSE;
+}
+
+
+
+/* Return an auths bitmap for the set of AUTH methods offered by the server
+which match our authenticators. */
+
+static unsigned short
+study_ehlo_auths(smtp_context * sx)
+{
+uschar * names;
+auth_instance * au;
+uschar authnum;
+unsigned short authbits = 0;
+
+if (!sx->esmtp) return 0;
+if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+if (!regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)) return 0;
+expand_nmax = -1; /* reset */
+names = string_copyn(expand_nstring[1], expand_nlength[1]);
+
+for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client)
+ {
+ const uschar * list = names;
+ int sep = ' ';
+ uschar name[32];
+
+ while (string_nextinlist(&list, &sep, name, sizeof(name)))
+ if (strcmpic(au->public_name, name) == 0)
+ { authbits |= BIT(authnum); break; }
+ }
+
+DEBUG(D_transport)
+ debug_printf("server offers %s AUTH, methods '%s', bitmap 0x%04x\n",
+ tls_out.active.sock >= 0 ? "crypted" : "plaintext", names, authbits);
+
+if (tls_out.active.sock >= 0)
+ sx->ehlo_resp.crypted_auths = authbits;
+else
+ sx->ehlo_resp.cleartext_auths = authbits;
+return authbits;
+}
+
+
+
+
+/* Wait for and check responses for early-pipelining.
+
+Called from the lower-level smtp_read_response() function
+used for general code that assume synchronisation, if context
+flags indicate outstanding early-pipelining commands. Also
+called fom sync_responses() which handles pipelined commands.
+
+Arguments:
+ sx smtp connection context
+ countp number of outstanding responses, adjusted on return
+
+Return:
+ OK all well
+ FAIL SMTP error in response
+*/
+int
+smtp_reap_early_pipe(smtp_context * sx, int * countp)
+{
+BOOL pending_BANNER = sx->pending_BANNER;
+BOOL pending_EHLO = sx->pending_EHLO;
+
+sx->pending_BANNER = FALSE; /* clear early to avoid recursion */
+sx->pending_EHLO = FALSE;
+
+if (pending_BANNER)
+ {
+ DEBUG(D_transport) debug_printf("%s expect banner\n", __FUNCTION__);
+ (*countp)--;
+ if (!smtp_reap_banner(sx))
+ {
+ DEBUG(D_transport) debug_printf("bad banner\n");
+ goto fail;
+ }
+ }
+
+if (pending_EHLO)
+ {
+ unsigned peer_offered;
+ unsigned short authbits = 0, * ap;
+
+ DEBUG(D_transport) debug_printf("%s expect ehlo\n", __FUNCTION__);
+ (*countp)--;
+ if (!smtp_reap_ehlo(sx))
+ {
+ DEBUG(D_transport) debug_printf("bad response for EHLO\n");
+ goto fail;
+ }
+
+ /* Compare the actual EHLO response to the cached value we assumed;
+ on difference, dump or rewrite the cache and arrange for a retry. */
+
+ ap = tls_out.active.sock < 0
+ ? &sx->ehlo_resp.cleartext_auths : &sx->ehlo_resp.crypted_auths;
+
+ peer_offered = ehlo_response(sx->buffer,
+ (tls_out.active.sock < 0 ? OPTION_TLS : OPTION_REQUIRETLS)
+ | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
+ | OPTION_UTF8 | OPTION_EARLY_PIPE
+ );
+ if ( peer_offered != sx->peer_offered
+ || (authbits = study_ehlo_auths(sx)) != *ap)
+ {
+ HDEBUG(D_transport)
+ debug_printf("EHLO %s extensions changed, 0x%04x/0x%04x -> 0x%04x/0x%04x\n",
+ tls_out.active.sock < 0 ? "cleartext" : "crypted",
+ sx->peer_offered, *ap, peer_offered, authbits);
+ *(tls_out.active.sock < 0
+ ? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) = peer_offered;
+ *ap = authbits;
+ if (peer_offered & OPTION_EARLY_PIPE)
+ write_ehlo_cache_entry(sx);
+ else
+ invalidate_ehlo_cache_entry(sx);
+
+ return OK; /* just carry on */
+ }
+ }
+return OK;
+
+fail:
+ invalidate_ehlo_cache_entry(sx);
+ (void) smtp_discard_responses(sx, sx->conn_args.ob, *countp);
+ return FAIL;
+}
+#endif
+
+
/*************************************************
* Synchronize SMTP responses *
*************************************************/
-1 timeout while reading RCPT response
-2 I/O or other non-response error for RCPT
-3 DATA or MAIL failed - errno and buffer set
+ -4 banner or EHLO failed (early-pipelining)
*/
static int
sync_responses(smtp_context * sx, int count, int pending_DATA)
{
-address_item *addr = sx->sync_addr;
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)sx->tblock->options_block;
+address_item * addr = sx->sync_addr;
+smtp_transport_options_block * ob = sx->conn_args.ob;
int yield = 0;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (smtp_reap_early_pipe(sx, &count) != OK)
+ return -4;
+#endif
+
/* Handle the response for a MAIL command. On error, reinstate the original
command in big_buffer for error message use, and flush any further pending
responses before returning, except after I/O errors and timeouts. */
if (sx->pending_MAIL)
{
+ DEBUG(D_transport) debug_printf("%s expect mail\n", __FUNCTION__);
count--;
- if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', ob->command_timeout))
{
DEBUG(D_transport) debug_printf("bad response for MAIL\n");
Ustrcpy(big_buffer, mail_command); /* Fits, because it came from there! */
if (errno == 0 && sx->buffer[0] != 0)
{
- uschar flushbuffer[4096];
int save_errno = 0;
if (sx->buffer[0] == '4')
{
save_errno = ERRNO_MAIL4XX;
addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
}
- while (count-- > 0)
- {
- if (!smtp_read_response(&sx->inblock, flushbuffer, sizeof(flushbuffer),
- '2', ob->command_timeout)
- && (errno != 0 || flushbuffer[0] == 0))
- break;
- }
+ count = smtp_discard_responses(sx, ob, count);
errno = save_errno;
}
while (count-- > 0) /* Mark any pending addrs with the host used */
{
while (addr->transport_return != PENDING_DEFER) addr = addr->next;
- addr->host_used = sx->host;
+ addr->host_used = sx->conn_args.host;
addr = addr->next;
}
return -3;
while (count-- > 0)
{
- while (addr->transport_return != PENDING_DEFER) addr = addr->next;
+ while (addr->transport_return != PENDING_DEFER)
+ if (!(addr = addr->next))
+ return -2;
/* The address was accepted */
- addr->host_used = sx->host;
+ addr->host_used = sx->conn_args.host;
- if (smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
+ DEBUG(D_transport) debug_printf("%s expect rcpt\n", __FUNCTION__);
+ if (smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
'2', ob->command_timeout))
{
yield |= 1;
else if (errno == ETIMEDOUT)
{
uschar *message = string_sprintf("SMTP timeout after RCPT TO:<%s>",
- transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
+ transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
set_errno_nohost(sx->first_addr, ETIMEDOUT, message, DEFER, FALSE);
retry_add_item(addr, addr->address_retry_key, 0);
update_waiting = FALSE;
else if (errno != 0 || sx->buffer[0] == 0)
{
string_format(big_buffer, big_buffer_size, "RCPT TO:<%s>",
- transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes));
+ transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes));
return -2;
}
{
addr->message =
string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
- "%s", transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes),
+ "%s", transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes),
string_printing(sx->buffer));
setflag(addr, af_pass_message);
if (!sx->verify)
- msglog_line(sx->host, addr->message);
+ msglog_line(sx->conn_args.host, addr->message);
/* The response was 5xx */
/* Log temporary errors if there are more hosts to be tried.
If not, log this last one in the == line. */
- if (sx->host->next)
- log_write(0, LOG_MAIN, "H=%s [%s]: %s",
- sx->host->name, sx->host->address, addr->message);
+ if (sx->conn_args.host->next)
+ if (LOGGING(outgoing_port))
+ log_write(0, LOG_MAIN, "H=%s [%s]:%d %s", sx->conn_args.host->name,
+ sx->conn_args.host->address,
+ sx->port == PORT_NONE ? 25 : sx->port, addr->message);
+ else
+ log_write(0, LOG_MAIN, "H=%s [%s]: %s", sx->conn_args.host->name,
+ sx->conn_args.host->address, addr->message);
#ifndef DISABLE_EVENT
else
/* Handle a response to DATA. If we have not had any good recipients, either
previously or in this block, the response is ignored. */
-if (pending_DATA != 0 &&
- !smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
- '3', ob->command_timeout))
+if (pending_DATA != 0)
{
- int code;
- uschar *msg;
- BOOL pass_message;
- if (pending_DATA > 0 || (yield & 1) != 0)
+ DEBUG(D_transport) debug_printf("%s expect data\n", __FUNCTION__);
+ if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer),
+ '3', ob->command_timeout))
{
- if (errno == 0 && sx->buffer[0] == '4')
+ int code;
+ uschar *msg;
+ BOOL pass_message;
+ if (pending_DATA > 0 || (yield & 1) != 0)
{
- errno = ERRNO_DATA4XX;
- sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+ if (errno == 0 && sx->buffer[0] == '4')
+ {
+ errno = ERRNO_DATA4XX;
+ sx->first_addr->more_errno |= ((sx->buffer[1] - '0')*10 + sx->buffer[2] - '0') << 8;
+ }
+ return -3;
}
- return -3;
+ (void)check_response(sx->conn_args.host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
+ DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
+ "is in use and there were no good recipients\n", msg);
}
- (void)check_response(sx->host, &errno, 0, sx->buffer, &code, &msg, &pass_message);
- DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
- "is in use and there were no good recipients\n", msg);
}
/* All responses read and handled; MAIL (if present) received 2xx and DATA (if
-/* Do the client side of smtp-level authentication */
-/*
+
+
+/* Try an authenticator's client entry */
+
+static int
+try_authenticator(smtp_context * sx, auth_instance * au)
+{
+smtp_transport_options_block * ob = sx->conn_args.ob; /* transport options */
+host_item * host = sx->conn_args.host; /* host to deliver to */
+int rc;
+
+sx->outblock.authenticating = TRUE;
+rc = (au->info->clientcode)(au, sx, ob->command_timeout,
+ sx->buffer, sizeof(sx->buffer));
+sx->outblock.authenticating = FALSE;
+DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc);
+
+/* A temporary authentication failure must hold up delivery to
+this host. After a permanent authentication failure, we carry on
+to try other authentication methods. If all fail hard, try to
+deliver the message unauthenticated unless require_auth was set. */
+
+switch(rc)
+ {
+ case OK:
+ f.smtp_authenticated = TRUE; /* stops the outer loop */
+ client_authenticator = au->name;
+ if (au->set_client_id)
+ client_authenticated_id = expand_string(au->set_client_id);
+ break;
+
+ /* Failure after writing a command */
+
+ case FAIL_SEND:
+ return FAIL_SEND;
+
+ /* Failure after reading a response */
+
+ case FAIL:
+ if (errno != 0 || sx->buffer[0] != '5') return FAIL;
+ log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+ au->name, host->name, host->address, sx->buffer);
+ break;
+
+ /* Failure by some other means. In effect, the authenticator
+ decided it wasn't prepared to handle this case. Typically this
+ is the result of "fail" in an expansion string. Do we need to
+ log anything here? Feb 2006: a message is now put in the buffer
+ if logging is required. */
+
+ case CANCELLED:
+ if (*sx->buffer != 0)
+ log_write(0, LOG_MAIN, "%s authenticator cancelled "
+ "authentication H=%s [%s] %s", au->name, host->name,
+ host->address, sx->buffer);
+ break;
+
+ /* Internal problem, message in buffer. */
+
+ case ERROR:
+ set_errno_nohost(sx->addrlist, ERRNO_AUTHPROB, string_copy(sx->buffer),
+ DEFER, FALSE);
+ return ERROR;
+ }
+return OK;
+}
+
+
+
+
+/* Do the client side of smtp-level authentication.
+
Arguments:
- buffer EHLO response from server (gets overwritten)
- addrlist chain of potential addresses to deliver
- host host to deliver to
- ob transport options
- ibp, obp comms channel control blocks
+ sx smtp connection context
+
+sx->buffer should have the EHLO response from server (gets overwritten)
Returns:
OK Success, or failed (but not required): global "smtp_authenticated" set
FAIL - response
*/
-int
-smtp_auth(uschar *buffer, unsigned bufsize, address_item *addrlist, host_item *host,
- smtp_transport_options_block *ob, BOOL is_esmtp,
- smtp_inblock *ibp, smtp_outblock *obp)
+static int
+smtp_auth(smtp_context * sx)
{
-int require_auth;
-uschar *fail_reason = US"server did not advertise AUTH support";
+host_item * host = sx->conn_args.host; /* host to deliver to */
+smtp_transport_options_block * ob = sx->conn_args.ob; /* transport options */
+int require_auth = verify_check_given_host(CUSS &ob->hosts_require_auth, host);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+unsigned short authbits = tls_out.active.sock >= 0
+ ? sx->ehlo_resp.crypted_auths : sx->ehlo_resp.cleartext_auths;
+#endif
+uschar * fail_reason = US"server did not advertise AUTH support";
-smtp_authenticated = FALSE;
+f.smtp_authenticated = FALSE;
client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
-require_auth = verify_check_given_host(&ob->hosts_require_auth, host);
-if (is_esmtp && !regex_AUTH) regex_AUTH =
- regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
- FALSE, TRUE);
+if (!regex_AUTH)
+ regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
+
+/* Is the server offering AUTH? */
-if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+if ( sx->esmtp
+ &&
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ sx->early_pipe_active ? authbits
+ :
+#endif
+ regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)
+ )
{
- uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
+ uschar * names = NULL;
expand_nmax = -1; /* reset */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (!sx->early_pipe_active)
+#endif
+ names = string_copyn(expand_nstring[1], expand_nlength[1]);
+
/* Must not do this check until after we have saved the result of the
- regex match above. */
+ regex match above as the check could be another RE. */
- if (require_auth == OK ||
- verify_check_given_host(&ob->hosts_try_auth, host) == OK)
+ if ( require_auth == OK
+ || verify_check_given_host(CUSS &ob->hosts_try_auth, host) == OK)
{
- auth_instance *au;
- fail_reason = US"no common mechanisms were found";
+ auth_instance * au;
DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+ fail_reason = US"no common mechanisms were found";
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_active)
+ {
+ /* Scan our authenticators (which support use by a client and were offered
+ by the server (checked at cache-write time)), not suppressed by
+ client_condition. If one is found, attempt to authenticate by calling its
+ client function. We are limited to supporting up to 16 authenticator
+ public-names by the number of bits in a short. */
+
+ uschar bitnum;
+ int rc;
+
+ for (bitnum = 0, au = auths;
+ !f.smtp_authenticated && au && bitnum < 16;
+ bitnum++, au = au->next) if (authbits & BIT(bitnum))
+ {
+ if ( au->client_condition
+ && !expand_check_condition(au->client_condition, au->name,
+ US"client authenticator"))
+ {
+ DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+ au->name, "client_condition is false");
+ continue;
+ }
+
+ /* Found data for a listed mechanism. Call its client entry. Set
+ a flag in the outblock so that data is overwritten after sending so
+ that reflections don't show it. */
+
+ fail_reason = US"authentication attempt(s) failed";
+
+ if ((rc = try_authenticator(sx, au)) != OK)
+ return rc;
+ }
+ }
+ else
+#endif
/* Scan the configured authenticators looking for one which is configured
for use as a client, which is not suppressed by client_condition, and
If one is found, attempt to authenticate by calling its client function.
*/
- for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
+ for (au = auths; !f.smtp_authenticated && au; au = au->next)
{
uschar *p = names;
- if (!au->client ||
- (au->client_condition != NULL &&
- !expand_check_condition(au->client_condition, au->name,
- US"client authenticator")))
+
+ if ( !au->client
+ || ( au->client_condition
+ && !expand_check_condition(au->client_condition, au->name,
+ US"client authenticator")))
{
DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
au->name,
/* Loop to scan supported server mechanisms */
- while (*p != 0)
+ while (*p)
{
- int rc;
int len = Ustrlen(au->public_name);
+ int rc;
+
while (isspace(*p)) p++;
if (strncmpic(au->public_name, p, len) != 0 ||
that reflections don't show it. */
fail_reason = US"authentication attempt(s) failed";
- obp->authenticating = TRUE;
- rc = (au->info->clientcode)(au, ibp, obp,
- ob->command_timeout, buffer, bufsize);
- obp->authenticating = FALSE;
- DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
- au->name, rc);
-
- /* A temporary authentication failure must hold up delivery to
- this host. After a permanent authentication failure, we carry on
- to try other authentication methods. If all fail hard, try to
- deliver the message unauthenticated unless require_auth was set. */
-
- switch(rc)
- {
- case OK:
- smtp_authenticated = TRUE; /* stops the outer loop */
- client_authenticator = au->name;
- if (au->set_client_id != NULL)
- client_authenticated_id = expand_string(au->set_client_id);
- break;
-
- /* Failure after writing a command */
-
- case FAIL_SEND:
- return FAIL_SEND;
- /* Failure after reading a response */
-
- case FAIL:
- if (errno != 0 || buffer[0] != '5') return FAIL;
- log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
- au->name, host->name, host->address, buffer);
- break;
-
- /* Failure by some other means. In effect, the authenticator
- decided it wasn't prepared to handle this case. Typically this
- is the result of "fail" in an expansion string. Do we need to
- log anything here? Feb 2006: a message is now put in the buffer
- if logging is required. */
-
- case CANCELLED:
- if (*buffer != 0)
- log_write(0, LOG_MAIN, "%s authenticator cancelled "
- "authentication H=%s [%s] %s", au->name, host->name,
- host->address, buffer);
- break;
-
- /* Internal problem, message in buffer. */
-
- case ERROR:
- set_errno_nohost(addrlist, ERRNO_AUTHPROB, string_copy(buffer),
- DEFER, FALSE);
- return ERROR;
- }
+ if ((rc = try_authenticator(sx, au)) != OK)
+ return rc;
break; /* If not authenticated, try next authenticator */
} /* Loop for scanning supported server mechanisms */
/* If we haven't authenticated, but are required to, give up. */
-if (require_auth == OK && !smtp_authenticated)
+if (require_auth == OK && !f.smtp_authenticated)
{
- set_errno_nohost(addrlist, ERRNO_AUTHFAIL,
+ set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL,
string_sprintf("authentication required but %s", fail_reason), DEFER,
FALSE);
return DEFER;
addrlist chain of potential addresses to deliver
ob transport options
-Globals smtp_authenticated
+Globals f.smtp_authenticated
client_authenticated_sender
Return True on error, otherwise buffer has (possibly empty) terminated string
*/
uschar *local_authenticated_sender = authenticated_sender;
#ifdef notdef
- debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, smtp_authenticated?"Y":"N");
+ debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, f.smtp_authenticated?"Y":"N");
#endif
if (ob->authenticated_sender != NULL)
uschar *new = expand_string(ob->authenticated_sender);
if (new == NULL)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
uschar *message = string_sprintf("failed to expand "
"authenticated_sender: %s", expand_string_message);
/* Add the authenticated sender address if present */
-if ((smtp_authenticated || ob->authenticated_sender_force) &&
+if ((f.smtp_authenticated || ob->authenticated_sender_force) &&
local_authenticated_sender != NULL)
{
string_format(buffer, bufsize, " AUTH=%s",
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
/* Lookup TLSA record for host/port.
Return: OK success with dnssec; DANE mode
DEFER Do not use this host now, may retry later
/* move this out to host.c given the similarity to dns_lookup() ? */
uschar buffer[300];
const uschar * fullname = buffer;
+int rc;
+BOOL sec;
/* TLSA lookup string */
(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name);
-switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname))
+rc = dns_lookup(dnsa, buffer, T_TLSA, &fullname);
+sec = dns_is_secure(dnsa);
+DEBUG(D_transport)
+ debug_printf("TLSA lookup ret %d %sDNSSEC\n", rc, sec ? "" : "not ");
+
+switch (rc)
{
+ case DNS_AGAIN:
+ return DEFER; /* just defer this TLS'd conn */
+
case DNS_SUCCEED:
- if (!dns_is_secure(dnsa))
+ if (sec)
{
- log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
- return DEFER;
- }
- return OK;
+ DEBUG(D_transport)
+ {
+ dns_scan dnss;
+ dns_record * rr;
+ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+ if (rr->type == T_TLSA && rr->size > 3)
+ {
+ uint16_t payload_length = rr->size - 3;
+ uschar s[MAX_TLSA_EXPANDED_SIZE], * sp = s, * p = US rr->data;
- case DNS_AGAIN:
- return DEFER; /* just defer this TLS'd conn */
+ sp += sprintf(CS sp, "%d ", *p++); /* usage */
+ sp += sprintf(CS sp, "%d ", *p++); /* selector */
+ sp += sprintf(CS sp, "%d ", *p++); /* matchtype */
+ while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4))
+ sp += sprintf(CS sp, "%02x", *p++);
+
+ debug_printf(" %s\n", s);
+ }
+ }
+ return OK;
+ }
+ log_write(0, LOG_MAIN,
+ "DANE error: TLSA lookup for %s not DNSSEC", host->name);
+ /*FALLTRHOUGH*/
case DNS_NODATA: /* no TLSA RR for this lookup */
case DNS_NOMATCH: /* no records at all for this lookup */
#endif
uschar * save_sender_address = sender_address;
uschar * local_identity = NULL;
-smtp_transport_options_block * ob =
- (smtp_transport_options_block *)tblock->options_block;
+smtp_transport_options_block * ob = SOB tblock->options_block;
sender_address = sender;
-static uschar
-ehlo_response(uschar * buf, uschar checks)
+static unsigned
+ehlo_response(uschar * buf, unsigned checks)
{
size_t bsize = Ustrlen(buf);
+/* debug_printf("%s: check for 0x%04x\n", __FUNCTION__, checks); */
+
#ifdef SUPPORT_TLS
-if ( checks & PEER_OFFERED_TLS
+# ifdef EXPERIMENTAL_REQUIRETLS
+if ( checks & OPTION_REQUIRETLS
+ && pcre_exec(regex_REQUIRETLS, NULL, CS buf,bsize, 0, PCRE_EOPT, NULL,0) < 0)
+# endif
+ checks &= ~OPTION_REQUIRETLS;
+
+if ( checks & OPTION_TLS
&& pcre_exec(regex_STARTTLS, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_TLS;
#endif
+ checks &= ~OPTION_TLS;
-if ( checks & PEER_OFFERED_IGNQ
+if ( checks & OPTION_IGNQ
&& pcre_exec(regex_IGNOREQUOTA, NULL, CS buf, bsize, 0,
PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_IGNQ;
+ checks &= ~OPTION_IGNQ;
-if ( checks & PEER_OFFERED_CHUNKING
+if ( checks & OPTION_CHUNKING
&& pcre_exec(regex_CHUNKING, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_CHUNKING;
+ checks &= ~OPTION_CHUNKING;
#ifndef DISABLE_PRDR
-if ( checks & PEER_OFFERED_PRDR
+if ( checks & OPTION_PRDR
&& pcre_exec(regex_PRDR, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_PRDR;
#endif
+ checks &= ~OPTION_PRDR;
#ifdef SUPPORT_I18N
-if ( checks & PEER_OFFERED_UTF8
+if ( checks & OPTION_UTF8
&& pcre_exec(regex_UTF8, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_UTF8;
#endif
+ checks &= ~OPTION_UTF8;
-if ( checks & PEER_OFFERED_DSN
+if ( checks & OPTION_DSN
&& pcre_exec(regex_DSN, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_DSN;
+ checks &= ~OPTION_DSN;
-if ( checks & PEER_OFFERED_PIPE
+if ( checks & OPTION_PIPE
&& pcre_exec(regex_PIPELINING, NULL, CS buf, bsize, 0,
PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_PIPE;
+ checks &= ~OPTION_PIPE;
-if ( checks & PEER_OFFERED_SIZE
+if ( checks & OPTION_SIZE
&& pcre_exec(regex_SIZE, NULL, CS buf, bsize, 0, PCRE_EOPT, NULL, 0) < 0)
- checks &= ~PEER_OFFERED_SIZE;
+ checks &= ~OPTION_SIZE;
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if ( checks & OPTION_EARLY_PIPE
+ && pcre_exec(regex_EARLY_PIPE, NULL, CS buf, bsize, 0,
+ PCRE_EOPT, NULL, 0) < 0)
+#endif
+ checks &= ~OPTION_EARLY_PIPE;
+/* debug_printf("%s: found 0x%04x\n", __FUNCTION__, checks); */
return checks;
}
If given a nonzero size, first flush any buffered SMTP commands
then emit the command.
-Reap previous SMTP command responses if requested.
-Reap one SMTP command response if requested.
+Reap previous SMTP command responses if requested, and always reap
+the response from a previous BDAT command.
+
+Args:
+ tctx transport context
+ chunk_size value for SMTP BDAT command
+ flags
+ tc_chunk_last add LAST option to SMTP BDAT command
+ tc_reap_prev reap response to previous SMTP commands
Returns: OK or ERROR
*/
static int
-smtp_chunk_cmd_callback(int fd, transport_ctx * tctx,
- unsigned chunk_size, unsigned flags)
+smtp_chunk_cmd_callback(transport_ctx * tctx, unsigned chunk_size,
+ unsigned flags)
{
-smtp_transport_options_block * ob =
- (smtp_transport_options_block *)(tctx->tblock->options_block);
+smtp_transport_options_block * ob = SOB tctx->tblock->options_block;
smtp_context * sx = tctx->smtp_context;
int cmd_count = 0;
int prev_cmd_count;
-/* Write SMTP chunk header command */
+/* Write SMTP chunk header command. If not reaping responses, note that
+there may be more writes (like, the chunk data) done soon. */
if (chunk_size > 0)
{
- if((cmd_count = smtp_write_command(&sx->outblock, FALSE, "BDAT %u%s\r\n",
- chunk_size,
- flags & tc_chunk_last ? " LAST" : "")
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL new_conn = !!(sx->outblock.conn_args);
+#endif
+ if((cmd_count = smtp_write_command(sx,
+ flags & tc_reap_prev ? SCMD_FLUSH : SCMD_MORE,
+ "BDAT %u%s\r\n", chunk_size, flags & tc_chunk_last ? " LAST" : "")
) < 0) return ERROR;
if (flags & tc_chunk_last)
data_command = string_copy(big_buffer); /* Save for later error message */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ /* That command write could have been the one that made the connection.
+ Copy the fd from the client conn ctx (smtp transport specific) to the
+ generic transport ctx. */
+
+ if (new_conn)
+ tctx->u.fd = sx->outblock.cctx->sock;
+#endif
}
prev_cmd_count = cmd_count += sx->cmd_count;
case 0: break; /* No 2xx or 5xx, but no probs */
case -1: /* Timeout on RCPT */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: /* non-2xx for pipelined banner or EHLO */
+#endif
default: return ERROR; /* I/O error, or any MAIL/DATA error */
}
cmd_count = 1;
{
DEBUG(D_transport) debug_printf("look for one response for BDAT\n");
- if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
+ if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
ob->command_timeout))
{
if (errno == 0 && sx->buffer[0] == '4')
+
+
/*************************************************
* Make connection for given message *
*************************************************/
int
smtp_setup_conn(smtp_context * sx, BOOL suppress_tls)
{
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
dns_answer tlsa_dnsa;
#endif
+smtp_transport_options_block * ob = sx->conn_args.tblock->options_block;
BOOL pass_message = FALSE;
uschar * message = NULL;
int yield = OK;
int rc;
-sx->ob = (smtp_transport_options_block *) sx->tblock->options_block;
+sx->conn_args.ob = ob;
-sx->lmtp = strcmpic(sx->ob->protocol, US"lmtp") == 0;
-sx->smtps = strcmpic(sx->ob->protocol, US"smtps") == 0;
+sx->lmtp = strcmpic(ob->protocol, US"lmtp") == 0;
+sx->smtps = strcmpic(ob->protocol, US"smtps") == 0;
sx->ok = FALSE;
sx->send_rset = TRUE;
sx->send_quit = TRUE;
sx->utf8_needed = FALSE;
#endif
sx->dsn_all_lasthop = TRUE;
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
sx->dane = FALSE;
-sx->dane_required = verify_check_given_host(&sx->ob->hosts_require_dane, sx->host) == OK;
+sx->dane_required =
+ verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK;
+#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+sx->early_pipe_active = sx->early_pipe_ok = FALSE;
+sx->ehlo_resp.cleartext_features = sx->ehlo_resp.crypted_features = 0;
+sx->pending_BANNER = sx->pending_EHLO = FALSE;
#endif
-if ((sx->max_rcpt = sx->tblock->max_addresses) == 0) sx->max_rcpt = 999999;
+if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999;
sx->peer_offered = 0;
+sx->avoid_option = 0;
sx->igquotstr = US"";
-if (!sx->helo_data) sx->helo_data = sx->ob->helo_data;
+if (!sx->helo_data) sx->helo_data = ob->helo_data;
#ifdef EXPERIMENTAL_DSN_INFO
sx->smtp_greeting = NULL;
sx->helo_response = NULL;
sx->outblock.ptr = sx->outbuffer;
sx->outblock.cmd_count = 0;
sx->outblock.authenticating = FALSE;
+sx->outblock.conn_args = NULL;
/* Reset the parameters of a TLS session. */
the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled
specially so they can be identified for retries. */
-if (continue_hostname == NULL)
+if (!continue_hostname)
{
if (sx->verify)
- HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->interface, sx->port);
+ HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", sx->conn_args.interface, sx->port);
- /* This puts port into host->port */
- sx->inblock.sock = sx->outblock.sock =
- smtp_connect(sx->host, sx->host_af, sx->port, sx->interface,
- sx->ob->connect_timeout, sx->tblock);
+ /* Get the actual port the connection will use, into sx->conn_args.host */
- if (sx->inblock.sock < 0)
- {
- uschar * msg = NULL;
- if (sx->verify)
- {
- msg = US strerror(errno);
- HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
- }
- set_errno_nohost(sx->addrlist,
- errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
- sx->verify ? string_sprintf("could not connect: %s", msg)
- : NULL,
- DEFER, FALSE);
- sx->send_quit = FALSE;
- return DEFER;
- }
+ smtp_port_for_connect(sx->conn_args.host, sx->port);
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
+ /* Do TLSA lookup for DANE */
{
tls_out.dane_verified = FALSE;
tls_out.tlsa_usage = 0;
- if (sx->host->dnssec == DS_YES)
+ if (sx->conn_args.host->dnssec == DS_YES)
{
if( sx->dane_required
- || verify_check_given_host(&sx->ob->hosts_try_dane, sx->host) == OK
+ || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK
)
- switch (rc = tlsa_lookup(sx->host, &tlsa_dnsa, sx->dane_required))
+ switch (rc = tlsa_lookup(sx->conn_args.host, &tlsa_dnsa, sx->dane_required))
{
- case OK: sx->dane = TRUE; break;
+ case OK: sx->dane = TRUE;
+ ob->tls_tempfail_tryclear = FALSE;
+ break;
case FAIL_FORCED: break;
default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
string_sprintf("DANE error: tlsa lookup %s",
rc == DEFER ? "DEFER" : "FAIL"),
rc, FALSE);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->conn_args.tblock->event_action,
+ US"dane:fail", sx->dane_required
+ ? US"dane-required" : US"dnssec-invalid");
+# endif
return rc;
}
}
else if (sx->dane_required)
{
set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER,
- string_sprintf("DANE error: %s lookup not DNSSEC", sx->host->name),
+ string_sprintf("DANE error: %s lookup not DNSSEC", sx->conn_args.host->name),
FAIL, FALSE);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->conn_args.tblock->event_action,
+ US"dane:fail", US"dane-required");
+# endif
return FAIL;
}
-
- if (sx->dane)
- sx->ob->tls_tempfail_tryclear = FALSE;
}
#endif /*DANE*/
+ /* Make the TCP connection */
+
+ sx->cctx.tls_ctx = NULL;
+ sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
+ sx->avoid_option = sx->peer_offered = smtp_peer_options = 0;
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (verify_check_given_host(CUSS &ob->hosts_pipe_connect, sx->conn_args.host) == OK)
+ {
+ sx->early_pipe_ok = TRUE;
+ if ( read_ehlo_cache_entry(sx)
+ && sx->ehlo_resp.cleartext_features & OPTION_EARLY_PIPE)
+ {
+ DEBUG(D_transport) debug_printf("Using cached cleartext PIPE_CONNECT\n");
+ sx->early_pipe_active = TRUE;
+ sx->peer_offered = sx->ehlo_resp.cleartext_features;
+ }
+ }
+
+ if (sx->early_pipe_active)
+ sx->outblock.conn_args = &sx->conn_args;
+ else
+#endif
+ {
+ if ((sx->cctx.sock = smtp_connect(&sx->conn_args, NULL)) < 0)
+ {
+ uschar * msg = NULL;
+ if (sx->verify)
+ {
+ msg = US strerror(errno);
+ HDEBUG(D_verify) debug_printf("connect: %s\n", msg);
+ }
+ set_errno_nohost(sx->addrlist,
+ errno == ETIMEDOUT ? ERRNO_CONNECTTIMEOUT : errno,
+ sx->verify ? string_sprintf("could not connect: %s", msg)
+ : NULL,
+ DEFER, FALSE);
+ sx->send_quit = FALSE;
+ return DEFER;
+ }
+ }
/* Expand the greeting message while waiting for the initial response. (Makes
sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
delayed till here so that $sending_interface and $sending_port are set. */
+/*XXX early-pipe: they still will not be. Is there any way to find out what they
+will be? Somehow I doubt it. */
if (sx->helo_data)
if (!(sx->helo_data = expand_string(sx->helo_data)))
if (!sx->smtps)
{
- BOOL good_response;
-
-#ifdef TCP_QUICKACK
- (void) setsockopt(sx->inblock.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_active)
+ {
+ sx->pending_BANNER = TRUE; /* sync_responses() must eventually handle */
+ sx->outblock.cmd_count = 1;
+ }
+ else
#endif
- good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
- '2', sx->ob->command_timeout);
-#ifdef EXPERIMENTAL_DSN_INFO
- sx->smtp_greeting = string_copy(sx->buffer);
+ {
+#ifdef TCP_QUICKACK
+ (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_QUICKACK, US &off,
+ sizeof(off));
#endif
- if (!good_response) goto RESPONSE_FAILED;
+ if (!smtp_reap_banner(sx))
+ goto RESPONSE_FAILED;
+ }
#ifndef DISABLE_EVENT
{
uschar * s;
- lookup_dnssec_authenticated = sx->host->dnssec==DS_YES ? US"yes"
- : sx->host->dnssec==DS_NO ? US"no" : NULL;
- s = event_raise(sx->tblock->event_action, US"smtp:connect", sx->buffer);
+ lookup_dnssec_authenticated = sx->conn_args.host->dnssec==DS_YES ? US"yes"
+ : sx->conn_args.host->dnssec==DS_NO ? US"no" : NULL;
+ s = event_raise(sx->conn_args.tblock->event_action, US"smtp:connect", sx->buffer);
if (s)
{
set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL,
mailers use upper case for some reason (the RFC is quite clear about case
independence) so, for peace of mind, I gave in. */
- sx->esmtp = verify_check_given_host(&sx->ob->hosts_avoid_esmtp, sx->host) != OK;
+ sx->esmtp = verify_check_given_host(CUSS &ob->hosts_avoid_esmtp, sx->conn_args.host) != OK;
/* Alas; be careful, since this goto is not an error-out, so conceivably
we might set data between here and the target which we assume to exist
#ifdef SUPPORT_TLS
if (sx->smtps)
{
- smtp_peer_options |= PEER_OFFERED_TLS;
+ smtp_peer_options |= OPTION_TLS;
suppress_tls = FALSE;
- sx->ob->tls_tempfail_tryclear = FALSE;
+ ob->tls_tempfail_tryclear = FALSE;
smtp_command = US"SSL-on-connect";
goto TLS_NEGOTIATE;
}
if (sx->esmtp)
{
- if (smtp_write_command(&sx->outblock, FALSE, "%s %s\r\n",
- sx->lmtp ? "LHLO" : "EHLO", sx->helo_data) < 0)
+ if (smtp_write_command(sx,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ sx->early_pipe_active ? SCMD_BUFFER :
+#endif
+ SCMD_FLUSH,
+ "%s %s\r\n", sx->lmtp ? "LHLO" : "EHLO", sx->helo_data) < 0)
goto SEND_FAILED;
sx->esmtp_sent = TRUE;
- if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
- sx->ob->command_timeout))
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_active)
{
- if (errno != 0 || sx->buffer[0] == 0 || sx->lmtp)
+ sx->pending_EHLO = TRUE;
+
+ /* If we have too many authenticators to handle and might need to AUTH
+ for this transport, pipeline no further as we will need the
+ list of auth methods offered. Reap the banner and EHLO. */
+
+ if ( (ob->hosts_require_auth || ob->hosts_try_auth)
+ && f.smtp_in_early_pipe_no_auth)
{
-#ifdef EXPERIMENTAL_DSN_INFO
- sx->helo_response = string_copy(sx->buffer);
-#endif
- goto RESPONSE_FAILED;
+ DEBUG(D_transport) debug_printf("may need to auth, so pipeline no further\n");
+ if (smtp_write_command(sx, SCMD_FLUSH, NULL) < 0)
+ goto SEND_FAILED;
+ if (sync_responses(sx, 2, 0) != 0)
+ {
+ HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+ goto RESPONSE_FAILED;
+ }
+ sx->early_pipe_active = FALSE;
}
- sx->esmtp = FALSE;
}
-#ifdef EXPERIMENTAL_DSN_INFO
- sx->helo_response = string_copy(sx->buffer);
+ else
#endif
+ if (!smtp_reap_ehlo(sx))
+ goto RESPONSE_FAILED;
}
else
DEBUG(D_transport)
debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
- if (!sx->esmtp)
- {
- BOOL good_response;
- int n = sizeof(sx->buffer);
- uschar * rsp = sx->buffer;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (!sx->early_pipe_active)
+#endif
+ if (!sx->esmtp)
+ {
+ BOOL good_response;
+ int n = sizeof(sx->buffer);
+ uschar * rsp = sx->buffer;
- if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
- { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
+ if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2)
+ { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; }
- if (smtp_write_command(&sx->outblock, FALSE, "HELO %s\r\n", sx->helo_data) < 0)
- goto SEND_FAILED;
- good_response = smtp_read_response(&sx->inblock, rsp, n,
- '2', sx->ob->command_timeout);
+ if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0)
+ goto SEND_FAILED;
+ good_response = smtp_read_response(sx, rsp, n, '2', ob->command_timeout);
#ifdef EXPERIMENTAL_DSN_INFO
- sx->helo_response = string_copy(rsp);
+ sx->helo_response = string_copy(rsp);
#endif
- if (!good_response)
- {
- /* Handle special logging for a closed connection after HELO
- when had previously sent EHLO */
-
- if (rsp != sx->buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
+ if (!good_response)
{
- errno = ERRNO_SMTPCLOSED;
- goto EHLOHELO_FAILED;
+ /* Handle special logging for a closed connection after HELO
+ when had previously sent EHLO */
+
+ if (rsp != sx->buffer && rsp[0] == 0 && (errno == 0 || errno == ECONNRESET))
+ {
+ errno = ERRNO_SMTPCLOSED;
+ goto EHLOHELO_FAILED;
+ }
+ memmove(sx->buffer, rsp, Ustrlen(rsp));
+ goto RESPONSE_FAILED;
}
- Ustrncpy(sx->buffer, rsp, sizeof(sx->buffer)/2);
- goto RESPONSE_FAILED;
}
- }
-
- sx->peer_offered = smtp_peer_options = 0;
if (sx->esmtp || sx->lmtp)
{
- sx->peer_offered = ehlo_response(sx->buffer,
- PEER_OFFERED_TLS /* others checked later */
- );
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (!sx->early_pipe_active)
+#endif
+ {
+ sx->peer_offered = ehlo_response(sx->buffer,
+ OPTION_TLS /* others checked later */
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ | (sx->early_pipe_ok
+ ? OPTION_IGNQ
+ | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
+#ifdef SUPPORT_I18N
+ | OPTION_UTF8
+#endif
+ | OPTION_EARLY_PIPE
+ : 0
+ )
+#endif
+ );
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_ok)
+ {
+ sx->ehlo_resp.cleartext_features = sx->peer_offered;
+
+ if ( (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE))
+ == (OPTION_PIPE | OPTION_EARLY_PIPE))
+ {
+ DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n");
+ sx->ehlo_resp.cleartext_auths = study_ehlo_auths(sx);
+ write_ehlo_cache_entry(sx);
+ }
+ }
+#endif
+ }
/* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
#ifdef SUPPORT_TLS
- smtp_peer_options |= sx->peer_offered & PEER_OFFERED_TLS;
+ smtp_peer_options |= sx->peer_offered & OPTION_TLS;
#endif
}
}
-/* For continuing deliveries down the same channel, the socket is the standard
-input, and we don't need to redo EHLO here (but may need to do so for TLS - see
-below). Set up the pointer to where subsequent commands will be left, for
+/* For continuing deliveries down the same channel, having re-exec'd the socket
+is the standard input; for a socket held open from verify it is recorded
+in the cutthrough context block. Either way we don't need to redo EHLO here
+(but may need to do so for TLS - see below).
+Set up the pointer to where subsequent commands will be left, for
error messages. Note that smtp_peer_options will have been
set from the command line if they were set in the process that passed the
connection on. */
else
{
- sx->inblock.sock = sx->outblock.sock = fileno(stdin);
+ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
+ {
+ sx->cctx = cutthrough.cctx;
+ sx->conn_args.host->port = sx->port = cutthrough.host.port;
+ }
+ else
+ {
+ sx->cctx.sock = 0; /* stdin */
+ sx->cctx.tls_ctx = NULL;
+ smtp_port_for_connect(sx->conn_args.host, sx->port); /* Record the port that was used */
+ }
+ sx->inblock.cctx = sx->outblock.cctx = &sx->cctx;
smtp_command = big_buffer;
- sx->host->port = sx->port; /* Record the port that was used */
sx->helo_data = NULL; /* ensure we re-expand ob->helo_data */
+
+ /* For a continued connection with TLS being proxied for us, or a
+ held-open verify connection with TLS, nothing more to do. */
+
+ if ( continue_proxy_cipher
+ || (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only
+ && cutthrough.is_tls)
+ )
+ {
+ sx->peer_offered = smtp_peer_options;
+ sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
+ HDEBUG(D_transport) debug_printf("continued connection, %s TLS\n",
+ continue_proxy_cipher ? "proxied" : "verify conn with");
+ return OK;
+ }
+ HDEBUG(D_transport) debug_printf("continued connection, no TLS\n");
}
/* If TLS is available on this connection, whether continued or not, attempt to
for error analysis. */
#ifdef SUPPORT_TLS
-if ( smtp_peer_options & PEER_OFFERED_TLS
+if ( smtp_peer_options & OPTION_TLS
&& !suppress_tls
- && verify_check_given_host(&sx->ob->hosts_avoid_tls, sx->host) != OK
+ && verify_check_given_host(CUSS &ob->hosts_avoid_tls, sx->conn_args.host) != OK
&& ( !sx->verify
- || verify_check_given_host(&sx->ob->hosts_verify_avoid_tls, sx->host) != OK
+ || verify_check_given_host(CUSS &ob->hosts_verify_avoid_tls, sx->conn_args.host) != OK
) )
{
uschar buffer2[4096];
- if (smtp_write_command(&sx->outblock, FALSE, "STARTTLS\r\n") < 0)
+
+ if (smtp_write_command(sx, SCMD_FLUSH, "STARTTLS\r\n") < 0)
goto SEND_FAILED;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ /* If doing early-pipelining reap the banner and EHLO-response but leave
+ the response for the STARTTLS we just sent alone. */
+
+ if (sx->early_pipe_active && sync_responses(sx, 2, 0) != 0)
+ {
+ HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+ goto RESPONSE_FAILED;
+ }
+#endif
+
/* If there is an I/O error, transmission of this message is deferred. If
there is a temporary rejection of STARRTLS and tls_tempfail_tryclear is
false, we also defer. However, if there is a temporary rejection of STARTTLS
STARTTLS, we carry on. This means we will try to send the message in clear,
unless the host is in hosts_require_tls (tested below). */
- if (!smtp_read_response(&sx->inblock, buffer2, sizeof(buffer2), '2',
- sx->ob->command_timeout))
+ if (!smtp_read_response(sx, buffer2, sizeof(buffer2), '2', ob->command_timeout))
{
if ( errno != 0
|| buffer2[0] == 0
- || (buffer2[0] == '4' && !sx->ob->tls_tempfail_tryclear)
+ || (buffer2[0] == '4' && !ob->tls_tempfail_tryclear)
)
{
Ustrncpy(sx->buffer, buffer2, sizeof(sx->buffer));
TLS_NEGOTIATE:
{
address_item * addr;
- int rc = tls_client_start(sx->inblock.sock, sx->host, sx->addrlist, sx->tblock
-# ifdef EXPERIMENTAL_DANE
- , sx->dane ? &tlsa_dnsa : NULL
+ uschar * errstr;
+ sx->cctx.tls_ctx = tls_client_start(sx->cctx.sock, sx->conn_args.host,
+ sx->addrlist, sx->conn_args.tblock,
+# ifdef SUPPORT_DANE
+ sx->dane ? &tlsa_dnsa : NULL,
# endif
- );
+ &tls_out, &errstr);
- /* TLS negotiation failed; give an error. From outside, this function may
- be called again to try in clear on a new connection, if the options permit
- it for this host. */
-
- if (rc != OK)
+ if (!sx->cctx.tls_ctx)
{
-# ifdef EXPERIMENTAL_DANE
- if (sx->dane) log_write(0, LOG_MAIN,
- "DANE attempt failed; no TLS connection to %s [%s]",
- sx->host->name, sx->host->address);
+ /* TLS negotiation failed; give an error. From outside, this function may
+ be called again to try in clear on a new connection, if the options permit
+ it for this host. */
+ DEBUG(D_tls) debug_printf("TLS session fail: %s\n", errstr);
+
+# ifdef SUPPORT_DANE
+ if (sx->dane)
+ {
+ log_write(0, LOG_MAIN,
+ "DANE attempt failed; TLS connection to %s [%s]: %s",
+ sx->conn_args.host->name, sx->conn_args.host->address, errstr);
+# ifndef DISABLE_EVENT
+ (void) event_raise(sx->conn_args.tblock->event_action,
+ US"dane:fail", US"validation-failure"); /* could do with better detail */
+# endif
+ }
# endif
errno = ERRNO_TLSFAILURE;
- message = US"failure while setting up TLS session";
+ message = string_sprintf("TLS session: %s", errstr);
sx->send_quit = FALSE;
goto TLS_FAILED;
}
expand it here. $sending_ip_address and $sending_port are set up right at the
start of the Exim process (in exim.c). */
-if (tls_out.active >= 0)
+if (tls_out.active.sock >= 0)
{
- char *greeting_cmd;
- BOOL good_response;
+ uschar * greeting_cmd;
- if (!sx->helo_data && !(sx->helo_data = expand_string(sx->ob->helo_data)))
+ if (!sx->helo_data && !(sx->helo_data = expand_string(ob->helo_data)))
{
uschar *message = string_sprintf("failed to expand helo_data: %s",
expand_string_message);
goto SEND_QUIT;
}
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ /* For SMTPS there is no cleartext early-pipe; use the crypted permission bit.
+ We're unlikely to get the group sent and delivered before the server sends its
+ banner, but it's still worth sending as a group.
+ For STARTTLS allow for cleartext early-pipe but no crypted early-pipe, but not
+ the reverse. */
+
+ if (sx->smtps ? sx->early_pipe_ok : sx->early_pipe_active)
+ {
+ sx->peer_offered = sx->ehlo_resp.crypted_features;
+ if ((sx->early_pipe_active =
+ !!(sx->ehlo_resp.crypted_features & OPTION_EARLY_PIPE)))
+ DEBUG(D_transport) debug_printf("Using cached crypted PIPE_CONNECT\n");
+ }
+#endif
+
/* For SMTPS we need to wait for the initial OK response. */
if (sx->smtps)
- {
- good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
- '2', sx->ob->command_timeout);
-#ifdef EXPERIMENTAL_DSN_INFO
- sx->smtp_greeting = string_copy(sx->buffer);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_active)
+ {
+ sx->pending_BANNER = TRUE;
+ sx->outblock.cmd_count = 1;
+ }
+ else
#endif
- if (!good_response) goto RESPONSE_FAILED;
- }
+ if (!smtp_reap_banner(sx))
+ goto RESPONSE_FAILED;
- if (sx->esmtp)
- greeting_cmd = "EHLO";
+ if (sx->lmtp)
+ greeting_cmd = US"LHLO";
+ else if (sx->esmtp)
+ greeting_cmd = US"EHLO";
else
{
- greeting_cmd = "HELO";
+ greeting_cmd = US"HELO";
DEBUG(D_transport)
debug_printf("not sending EHLO (host matches hosts_avoid_esmtp)\n");
}
- if (smtp_write_command(&sx->outblock, FALSE, "%s %s\r\n",
- sx->lmtp ? "LHLO" : greeting_cmd, sx->helo_data) < 0)
+ if (smtp_write_command(sx,
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ sx->early_pipe_active ? SCMD_BUFFER :
+#endif
+ SCMD_FLUSH,
+ "%s %s\r\n", greeting_cmd, sx->helo_data) < 0)
goto SEND_FAILED;
- good_response = smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer),
- '2', sx->ob->command_timeout);
-#ifdef EXPERIMENTAL_DSN_INFO
- sx->helo_response = string_copy(sx->buffer);
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx->early_pipe_active)
+ sx->pending_EHLO = TRUE;
+ else
#endif
- if (!good_response) goto RESPONSE_FAILED;
- smtp_peer_options = 0;
+ {
+ if (!smtp_reap_ehlo(sx))
+ goto RESPONSE_FAILED;
+ smtp_peer_options = 0;
+ }
}
/* If the host is required to use a secure channel, ensure that we
have one. */
else if ( sx->smtps
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
|| sx->dane
# endif
- || verify_check_given_host(&sx->ob->hosts_require_tls, sx->host) == OK
+# ifdef EXPERIMENTAL_REQUIRETLS
+ || tls_requiretls & REQUIRETLS_MSG
+# endif
+ || verify_check_given_host(CUSS &ob->hosts_require_tls, sx->conn_args.host) == OK
)
{
- errno = ERRNO_TLSREQUIRED;
+ errno =
+# ifdef EXPERIMENTAL_REQUIRETLS
+ tls_requiretls & REQUIRETLS_MSG ? ERRNO_REQUIRETLS :
+# endif
+ ERRNO_TLSREQUIRED;
message = string_sprintf("a TLS session is required, but %s",
- smtp_peer_options & PEER_OFFERED_TLS
+ smtp_peer_options & OPTION_TLS
? "an attempt to start TLS failed" : "the server did not offer TLS support");
+# if defined(SUPPORT_DANE) && !defined(DISABLE_EVENT)
+ if (sx->dane)
+ (void) event_raise(sx->conn_args.tblock->event_action, US"dane:fail",
+ smtp_peer_options & OPTION_TLS
+ ? US"validation-failure" /* could do with better detail */
+ : US"starttls-not-supported");
+# endif
goto TLS_FAILED;
}
#endif /*SUPPORT_TLS*/
if (continue_hostname == NULL
#ifdef SUPPORT_TLS
- || tls_out.active >= 0
+ || tls_out.active.sock >= 0
#endif
)
{
if (sx->esmtp || sx->lmtp)
{
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (!sx->early_pipe_active)
+#endif
+ {
sx->peer_offered = ehlo_response(sx->buffer,
0 /* no TLS */
- | (sx->lmtp && sx->ob->lmtp_ignore_quota ? PEER_OFFERED_IGNQ : 0)
- | PEER_OFFERED_CHUNKING
- | PEER_OFFERED_PRDR
-#ifdef SUPPORT_I18N
- | (sx->addrlist->prop.utf8_msg ? PEER_OFFERED_UTF8 : 0)
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ | (sx->lmtp && ob->lmtp_ignore_quota ? OPTION_IGNQ : 0)
+ | OPTION_DSN | OPTION_PIPE | OPTION_SIZE
+ | OPTION_CHUNKING | OPTION_PRDR | OPTION_UTF8 | OPTION_REQUIRETLS
+ | (tls_out.active.sock >= 0 ? OPTION_EARLY_PIPE : 0) /* not for lmtp */
+
+#else
+
+ | (sx->lmtp && ob->lmtp_ignore_quota ? OPTION_IGNQ : 0)
+ | OPTION_CHUNKING
+ | OPTION_PRDR
+# ifdef SUPPORT_I18N
+ | (sx->addrlist->prop.utf8_msg ? OPTION_UTF8 : 0)
/*XXX if we hand peercaps on to continued-conn processes,
must not depend on this addr */
+# endif
+ | OPTION_DSN
+ | OPTION_PIPE
+ | (ob->size_addition >= 0 ? OPTION_SIZE : 0)
+# if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ | (tls_requiretls & REQUIRETLS_MSG ? OPTION_REQUIRETLS : 0)
+# endif
#endif
- | PEER_OFFERED_DSN
- | PEER_OFFERED_PIPE
- | (sx->ob->size_addition >= 0 ? PEER_OFFERED_SIZE : 0)
);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (tls_out.active.sock >= 0)
+ sx->ehlo_resp.crypted_features = sx->peer_offered;
+#endif
+ }
/* Set for IGNOREQUOTA if the response to LHLO specifies support and the
lmtp_ignore_quota option was set. */
- sx->igquotstr = sx->peer_offered & PEER_OFFERED_IGNQ ? US" IGNOREQUOTA" : US"";
+ sx->igquotstr = sx->peer_offered & OPTION_IGNQ ? US" IGNOREQUOTA" : US"";
/* If the response to EHLO specified support for the SIZE parameter, note
this, provided size_addition is non-negative. */
- smtp_peer_options |= sx->peer_offered & PEER_OFFERED_SIZE;
+ smtp_peer_options |= sx->peer_offered & OPTION_SIZE;
/* Note whether the server supports PIPELINING. If hosts_avoid_esmtp matched
the current host, esmtp will be false, so PIPELINING can never be used. If
the current host matches hosts_avoid_pipelining, don't do it. */
- if ( sx->peer_offered & PEER_OFFERED_PIPE
- && verify_check_given_host(&sx->ob->hosts_avoid_pipelining, sx->host) != OK)
- smtp_peer_options |= PEER_OFFERED_PIPE;
+ if ( sx->peer_offered & OPTION_PIPE
+ && verify_check_given_host(CUSS &ob->hosts_avoid_pipelining, sx->conn_args.host) != OK)
+ smtp_peer_options |= OPTION_PIPE;
DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
- smtp_peer_options & PEER_OFFERED_PIPE ? "" : "not ");
+ smtp_peer_options & OPTION_PIPE ? "" : "not ");
- if ( sx->peer_offered & PEER_OFFERED_CHUNKING
- && verify_check_given_host(&sx->ob->hosts_try_chunking, sx->host) != OK)
- sx->peer_offered &= ~PEER_OFFERED_CHUNKING;
+ if ( sx->peer_offered & OPTION_CHUNKING
+ && verify_check_given_host(CUSS &ob->hosts_try_chunking, sx->conn_args.host) != OK)
+ sx->peer_offered &= ~OPTION_CHUNKING;
- if (sx->peer_offered & PEER_OFFERED_CHUNKING)
- {DEBUG(D_transport) debug_printf("CHUNKING usable\n");}
+ if (sx->peer_offered & OPTION_CHUNKING)
+ DEBUG(D_transport) debug_printf("CHUNKING usable\n");
#ifndef DISABLE_PRDR
- if ( sx->peer_offered & PEER_OFFERED_PRDR
- && verify_check_given_host(&sx->ob->hosts_try_prdr, sx->host) != OK)
- sx->peer_offered &= ~PEER_OFFERED_PRDR;
+ if ( sx->peer_offered & OPTION_PRDR
+ && verify_check_given_host(CUSS &ob->hosts_try_prdr, sx->conn_args.host) != OK)
+ sx->peer_offered &= ~OPTION_PRDR;
- if (sx->peer_offered & PEER_OFFERED_PRDR)
- {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+ if (sx->peer_offered & OPTION_PRDR)
+ DEBUG(D_transport) debug_printf("PRDR usable\n");
#endif
/* Note if the server supports DSN */
- smtp_peer_options |= sx->peer_offered & PEER_OFFERED_DSN;
+ smtp_peer_options |= sx->peer_offered & OPTION_DSN;
DEBUG(D_transport) debug_printf("%susing DSN\n",
- sx->peer_offered & PEER_OFFERED_DSN ? "" : "not ");
+ sx->peer_offered & OPTION_DSN ? "" : "not ");
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ if (sx->peer_offered & OPTION_REQUIRETLS)
+ {
+ smtp_peer_options |= OPTION_REQUIRETLS;
+ DEBUG(D_transport) debug_printf(
+ tls_requiretls & REQUIRETLS_MSG
+ ? "using REQUIRETLS\n" : "REQUIRETLS offered\n");
+ }
+#endif
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if ( sx->early_pipe_ok
+ && !sx->early_pipe_active
+ && tls_out.active.sock >= 0
+ && smtp_peer_options & OPTION_PIPE
+ && ( sx->ehlo_resp.cleartext_features | sx->ehlo_resp.crypted_features)
+ & OPTION_EARLY_PIPE)
+ {
+ DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n");
+ sx->ehlo_resp.crypted_auths = study_ehlo_auths(sx);
+ write_ehlo_cache_entry(sx);
+ }
+#endif
/* Note if the response to EHLO specifies support for the AUTH extension.
If it has, check that this host is one we want to authenticate to, and do
the business. The host name and address must be available when the
authenticator's client driver is running. */
- switch (yield = smtp_auth(sx->buffer, sizeof(sx->buffer), sx->addrlist, sx->host,
- sx->ob, sx->esmtp, &sx->inblock, &sx->outblock))
+ switch (yield = smtp_auth(sx))
{
default: goto SEND_QUIT;
case OK: break;
}
}
}
-pipelining_active = !!(smtp_peer_options & PEER_OFFERED_PIPE);
+sx->pipelining_used = pipelining_active = !!(smtp_peer_options & OPTION_PIPE);
/* The setting up of the SMTP call is now complete. Any subsequent errors are
message-specific. */
#ifdef SUPPORT_I18N
if (sx->addrlist->prop.utf8_msg)
{
+ uschar * s;
+
+ /* If the transport sets a downconversion mode it overrides any set by ACL
+ for the message. */
+
+ if ((s = ob->utf8_downconvert))
+ {
+ if (!(s = expand_string(s)))
+ {
+ message = string_sprintf("failed to expand utf8_downconvert: %s",
+ expand_string_message);
+ set_errno_nohost(sx->addrlist, ERRNO_EXPANDFAIL, message, DEFER, FALSE);
+ yield = DEFER;
+ goto SEND_QUIT;
+ }
+ switch (*s)
+ {
+ case '1': sx->addrlist->prop.utf8_downcvt = TRUE;
+ sx->addrlist->prop.utf8_downcvt_maybe = FALSE;
+ break;
+ case '0': sx->addrlist->prop.utf8_downcvt = FALSE;
+ sx->addrlist->prop.utf8_downcvt_maybe = FALSE;
+ break;
+ case '-': if (s[1] == '1')
+ {
+ sx->addrlist->prop.utf8_downcvt = FALSE;
+ sx->addrlist->prop.utf8_downcvt_maybe = TRUE;
+ }
+ break;
+ }
+ }
+
sx->utf8_needed = !sx->addrlist->prop.utf8_downcvt
&& !sx->addrlist->prop.utf8_downcvt_maybe;
DEBUG(D_transport) if (!sx->utf8_needed)
}
/* If this is an international message we need the host to speak SMTPUTF8 */
-if (sx->utf8_needed && !(sx->peer_offered & PEER_OFFERED_UTF8))
+if (sx->utf8_needed && !(sx->peer_offered & OPTION_UTF8))
{
errno = ERRNO_UTF8_FWD;
goto RESPONSE_FAILED;
}
+#endif /*SUPPORT_I18N*/
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ /*XXX should tls_requiretls actually be per-addr? */
+
+if ( tls_requiretls & REQUIRETLS_MSG
+ && !(sx->peer_offered & OPTION_REQUIRETLS)
+ )
+ {
+ sx->setting_up = TRUE;
+ errno = ERRNO_REQUIRETLS;
+ message = US"REQUIRETLS support is required from the server"
+ " but it was not offered";
+ DEBUG(D_transport) debug_printf("%s\n", message);
+ goto TLS_FAILED;
+ }
#endif
return OK;
RESPONSE_FAILED:
message = NULL;
- sx->send_quit = check_response(sx->host, &errno, sx->addrlist->more_errno,
+ sx->send_quit = check_response(sx->conn_args.host, &errno, sx->addrlist->more_errno,
sx->buffer, &code, &message, &pass_message);
+ yield = DEFER;
goto FAILED;
SEND_FAILED:
code = '4';
message = US string_sprintf("send() to %s [%s] failed: %s",
- sx->host->name, sx->host->address, strerror(errno));
+ sx->conn_args.host->name, sx->conn_args.host->address, strerror(errno));
sx->send_quit = FALSE;
+ yield = DEFER;
goto FAILED;
- /* This label is jumped to directly when a TLS negotiation has failed,
- or was not done for a host for which it is required. Values will be set
- in message and errno, and setting_up will always be true. Treat as
- a temporary error. */
-
EHLOHELO_FAILED:
code = '4';
message = string_sprintf("Remote host closed connection in response to %s"
" (EHLO response was: %s)", smtp_command, sx->buffer);
sx->send_quit = FALSE;
+ yield = DEFER;
goto FAILED;
+ /* This label is jumped to directly when a TLS negotiation has failed,
+ or was not done for a host for which it is required. Values will be set
+ in message and errno, and setting_up will always be true. Treat as
+ a temporary error. */
+
#ifdef SUPPORT_TLS
TLS_FAILED:
- code = '4';
+# ifdef EXPERIMENTAL_REQUIRETLS
+ if (errno == ERRNO_REQUIRETLS)
+ code = '5', yield = FAIL;
+ /*XXX DSN will be labelled 500; prefer 530 5.7.4 */
+ else
+# endif
+ code = '4', yield = DEFER;
goto FAILED;
#endif
/* The failure happened while setting up the call; see if the failure was
a 5xx response (this will either be on connection, or following HELO - a 5xx
- after EHLO causes it to try HELO). If so, fail all addresses, as this host is
- never going to accept them. For other errors during setting up (timeouts or
- whatever), defer all addresses, and yield DEFER, so that the host is not
- tried again for a while. */
+ after EHLO causes it to try HELO). If so, and there are no more hosts to try,
+ fail all addresses, as this host is never going to accept them. For other
+ errors during setting up (timeouts or whatever), defer all addresses, and
+ yield DEFER, so that the host is not tried again for a while.
+
+ XXX This peeking for another host feels like a layering violation. We want
+ to note the host as unusable, but down here we shouldn't know if this was
+ the last host to try for the addr(list). Perhaps the upper layer should be
+ the one to do set_errno() ? The problem is that currently the addr is where
+ errno etc. are stashed, but until we run out of hosts to try the errors are
+ host-specific. Maybe we should enhance the host_item definition? */
FAILED:
sx->ok = FALSE; /* For when reached by GOTO */
-
- yield = code == '5'
+ set_errno(sx->addrlist, errno, message,
+ sx->conn_args.host->next
+ ? DEFER
+ : code == '5'
#ifdef SUPPORT_I18N
- || errno == ERRNO_UTF8_FWD
+ || errno == ERRNO_UTF8_FWD
#endif
- ? FAIL : DEFER;
-
- set_errno(sx->addrlist, errno, message, yield, pass_message, sx->host
+ ? FAIL : DEFER,
+ pass_message, sx->conn_args.host
#ifdef EXPERIMENTAL_DSN_INFO
, sx->smtp_greeting, sx->helo_response
#endif
SEND_QUIT:
if (sx->send_quit)
- (void)smtp_write_command(&sx->outblock, FALSE, "QUIT\r\n");
+ (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n");
#ifdef SUPPORT_TLS
-tls_close(FALSE, TRUE);
+if (sx->cctx.tls_ctx)
+ {
+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ sx->cctx.tls_ctx = NULL;
+ }
#endif
/* Close the socket, and return the appropriate value, first setting
works because the NULL setting is passed back to the calling process, and
remote_max_parallel is forced to 1 when delivering over an existing connection,
-
-If all went well and continue_more is set, we shouldn't actually get here if
-there are further addresses, as the return above will be taken. However,
-writing RSET might have failed, or there may be other addresses whose hosts are
-specified in the transports, and therefore not visible at top level, in which
-case continue_more won't get set. */
+*/
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
if (sx->send_quit)
{
- shutdown(sx->outblock.sock, SHUT_WR);
- if (fcntl(sx->inblock.sock, F_SETFL, O_NONBLOCK) == 0)
- for (rc = 16; read(sx->inblock.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
+ shutdown(sx->cctx.sock, SHUT_WR);
+ if (fcntl(sx->cctx.sock, F_SETFL, O_NONBLOCK) == 0)
+ for (rc = 16; read(sx->cctx.sock, sx->inbuffer, sizeof(sx->inbuffer)) > 0 && rc > 0;)
rc--; /* drain socket */
sx->send_quit = FALSE;
}
-(void)close(sx->inblock.sock);
-sx->inblock.sock = sx->outblock.sock = -1;
+(void)close(sx->cctx.sock);
+sx->cctx.sock = -1;
#ifndef DISABLE_EVENT
-(void) event_raise(sx->tblock->event_action, US"tcp:close", NULL);
+(void) event_raise(sx->conn_args.tblock->event_action, US"tcp:close", NULL);
#endif
continue_transport = NULL;
*p = 0;
-/* If we know the receiving MTA supports the SIZE qualification,
+/* If we know the receiving MTA supports the SIZE qualification, and we know it,
send it, adding something to the message size to allow for imprecision
and things that get added en route. Exim keeps the number of lines
in a message, so we can give an accurate value for the original message, but we
need some additional to handle added headers. (Double "." characters don't get
included in the count.) */
-if (sx->peer_offered & PEER_OFFERED_SIZE)
+if ( message_size > 0
+ && sx->peer_offered & OPTION_SIZE && !(sx->avoid_option & OPTION_SIZE))
{
- sprintf(CS p, " SIZE=%d", message_size+message_linecount+sx->ob->size_addition);
+/*XXX problem here under spool_files_wireformat?
+Or just forget about lines? Or inflate by a fixed proportion? */
+
+ sprintf(CS p, " SIZE=%d", message_size+message_linecount+(SOB sx->conn_args.ob)->size_addition);
while (*p) p++;
}
#ifndef DISABLE_PRDR
-/* If it supports Per-Recipient Data Reponses, and we have omre than one recipient,
+/* If it supports Per-Recipient Data Responses, and we have more than one recipient,
request that */
sx->prdr_active = FALSE;
-if (sx->peer_offered & PEER_OFFERED_PRDR)
+if (sx->peer_offered & OPTION_PRDR)
for (addr = addrlist; addr; addr = addr->next)
if (addr->transport_return == PENDING_DEFER)
{
/* If it supports internationalised messages, and this meesage need that,
request it */
-if ( sx->peer_offered & PEER_OFFERED_UTF8
+if ( sx->peer_offered & OPTION_UTF8
&& addrlist->prop.utf8_msg
&& !addrlist->prop.utf8_downcvt
)
Ustrcpy(p, " SMTPUTF8"), p += 9;
#endif
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls & REQUIRETLS_MSG)
+ Ustrcpy(p, " REQUIRETLS") , p += 11;
+#endif
+
/* check if all addresses have DSN-lasthop flag; do not send RET and ENVID if so */
for (sx->dsn_all_lasthop = TRUE, addr = addrlist, address_count = 0;
addr && address_count < sx->max_rcpt;
/* Add any DSN flags to the mail command */
-if (sx->peer_offered & PEER_OFFERED_DSN && !sx->dsn_all_lasthop)
+if (sx->peer_offered & OPTION_DSN && !sx->dsn_all_lasthop)
{
if (dsn_ret == dsn_ret_hdrs)
{ Ustrcpy(p, " RET=HDRS"); p += 9; }
otherwise no check - this feature is expected to be used with LMTP and other
cases where non-standard addresses (e.g. without domains) might be required. */
-if (smtp_mail_auth_str(p, sizeof(sx->buffer) - (p-sx->buffer), addrlist, sx->ob))
+if (smtp_mail_auth_str(p, sizeof(sx->buffer) - (p-sx->buffer), addrlist, sx->conn_args.ob))
return ERROR;
return OK;
/* Add any DSN flags to the rcpt command */
-if (sx->peer_offered & PEER_OFFERED_DSN && !(addr->dsn_flags & rf_dsnlasthop))
+if (sx->peer_offered & OPTION_DSN && !(addr->dsn_flags & rf_dsnlasthop))
{
if (addr->dsn_flags & rf_dsnflags)
{
the delivery log line. */
if ( sx->addrlist->prop.utf8_msg
- && (sx->addrlist->prop.utf8_downcvt || !(sx->peer_offered & PEER_OFFERED_UTF8))
+ && (sx->addrlist->prop.utf8_downcvt || !(sx->peer_offered & OPTION_UTF8))
)
{
if (s = string_address_utf8_to_alabel(s, &errstr), errstr)
}
#endif
- rc = smtp_write_command(&sx->outblock, pipelining_active,
+ rc = smtp_write_command(sx, pipelining_active ? SCMD_BUFFER : SCMD_FLUSH,
"MAIL FROM:<%s>%s\r\n", s, sx->buffer);
}
return -5;
case +1: /* Cmd was sent */
- if (!smtp_read_response(&sx->inblock, sx->buffer, sizeof(sx->buffer), '2',
- sx->ob->command_timeout))
+ if (!smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2',
+ (SOB sx->conn_args.ob)->command_timeout))
{
if (errno == 0 && sx->buffer[0] == '4')
{
BOOL no_flush;
uschar * rcpt_addr;
- addr->dsn_aware = sx->peer_offered & PEER_OFFERED_DSN
+ addr->dsn_aware = sx->peer_offered & OPTION_DSN
? dsn_support_yes : dsn_support_no;
address_count++;
- no_flush = pipelining_active && !sx->verify && (!mua_wrapper || addr->next);
+ no_flush = pipelining_active && !sx->verify
+ && (!mua_wrapper || addr->next && address_count < sx->max_rcpt);
build_rcptcmd_options(sx, addr);
yield as OK, because this error can often mean that there is a problem with
just one address, so we don't want to delay the host. */
- rcpt_addr = transport_rcpt_address(addr, sx->tblock->rcpt_include_affixes);
+ rcpt_addr = transport_rcpt_address(addr, sx->conn_args.tblock->rcpt_include_affixes);
#ifdef SUPPORT_I18N
if ( testflag(sx->addrlist, af_utf8_downcvt)
}
#endif
- count = smtp_write_command(&sx->outblock, no_flush, "RCPT TO:<%s>%s%s\r\n",
- rcpt_addr, sx->igquotstr, sx->buffer);
+ count = smtp_write_command(sx, no_flush ? SCMD_BUFFER : SCMD_FLUSH,
+ "RCPT TO:<%s>%s%s\r\n", rcpt_addr, sx->igquotstr, sx->buffer);
if (count < 0) return -5;
if (count > 0)
case -1: return -3; /* Timeout on RCPT */
case -2: return -2; /* non-MAIL read i/o error */
default: return -1; /* any MAIL error */
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: return -1; /* non-2xx for pipelined banner or EHLO */
+#endif
}
sx->pending_MAIL = FALSE; /* Dealt with MAIL */
}
}
+#ifdef SUPPORT_TLS
+/*****************************************************
+* Proxy TLS connection for another transport process *
+******************************************************/
+/*
+Close the unused end of the pipe, fork once more, then use the given buffer
+as a staging area, and select on both the given fd and the TLS'd client-fd for
+data to read (per the coding in ip_recv() and fd_ready() this is legitimate).
+Do blocking full-size writes, and reads under a timeout. Once both input
+channels are closed, exit the process.
+
+Arguments:
+ ct_ctx tls context
+ buf space to use for buffering
+ bufsiz size of buffer
+ pfd pipe filedescriptor array; [0] is comms to proxied process
+ timeout per-read timeout, seconds
+*/
+
+void
+smtp_proxy_tls(void * ct_ctx, uschar * buf, size_t bsize, int * pfd,
+ int timeout)
+{
+fd_set rfds, efds;
+int max_fd = MAX(pfd[0], tls_out.active.sock) + 1;
+int rc, i, fd_bits, nbytes;
+
+close(pfd[1]);
+if ((rc = fork()))
+ {
+ DEBUG(D_transport) debug_printf("proxy-proc final-pid %d\n", rc);
+ _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+ }
+
+if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
+set_process_info("proxying TLS connection for continued transport");
+FD_ZERO(&rfds);
+FD_SET(tls_out.active.sock, &rfds);
+FD_SET(pfd[0], &rfds);
+
+for (fd_bits = 3; fd_bits; )
+ {
+ time_t time_left = timeout;
+ time_t time_start = time(NULL);
+
+ /* wait for data */
+ efds = rfds;
+ do
+ {
+ struct timeval tv = { time_left, 0 };
+
+ rc = select(max_fd,
+ (SELECT_ARG2_TYPE *)&rfds, NULL, (SELECT_ARG2_TYPE *)&efds, &tv);
+
+ if (rc < 0 && errno == EINTR)
+ if ((time_left -= time(NULL) - time_start) > 0) continue;
+
+ if (rc <= 0)
+ {
+ DEBUG(D_transport) if (rc == 0) debug_printf("%s: timed out\n", __FUNCTION__);
+ goto done;
+ }
+
+ if (FD_ISSET(tls_out.active.sock, &efds) || FD_ISSET(pfd[0], &efds))
+ {
+ DEBUG(D_transport) debug_printf("select: exceptional cond on %s fd\n",
+ FD_ISSET(pfd[0], &efds) ? "proxy" : "tls");
+ goto done;
+ }
+ }
+ while (rc < 0 || !(FD_ISSET(tls_out.active.sock, &rfds) || FD_ISSET(pfd[0], &rfds)));
+
+ /* handle inbound data */
+ if (FD_ISSET(tls_out.active.sock, &rfds))
+ if ((rc = tls_read(ct_ctx, buf, bsize)) <= 0)
+ {
+ fd_bits &= ~1;
+ FD_CLR(tls_out.active.sock, &rfds);
+ shutdown(pfd[0], SHUT_WR);
+ timeout = 5;
+ }
+ else
+ {
+ for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+ if ((i = write(pfd[0], buf + nbytes, rc - nbytes)) < 0) goto done;
+ }
+ else if (fd_bits & 1)
+ FD_SET(tls_out.active.sock, &rfds);
+
+ /* handle outbound data */
+ if (FD_ISSET(pfd[0], &rfds))
+ if ((rc = read(pfd[0], buf, bsize)) <= 0)
+ {
+ fd_bits = 0;
+ tls_close(ct_ctx, TLS_SHUTDOWN_NOWAIT);
+ ct_ctx = NULL;
+ }
+ else
+ {
+ for (nbytes = 0; rc - nbytes > 0; nbytes += i)
+ if ((i = tls_write(ct_ctx, buf + nbytes, rc - nbytes, FALSE)) < 0)
+ goto done;
+ }
+ else if (fd_bits & 2)
+ FD_SET(pfd[0], &rfds);
+ }
+
+done:
+ if (f.running_in_test_harness) millisleep(100); /* let logging complete */
+ exim_exit(0, US"TLS proxy");
+}
+#endif
+
+
/*************************************************
* Deliver address list to given host *
*************************************************/
failed by one of them.
host host to deliver to
host_af AF_INET or AF_INET6
- port default TCP/IP port to use, in host byte order
+ defport default TCP/IP port to use if host does not specify, in host
+ byte order
interface interface to bind to, or NULL
tblock transport instance block
message_defer set TRUE if yield is OK, but all addresses were deferred
*/
static int
-smtp_deliver(address_item *addrlist, host_item *host, int host_af, int port,
+smtp_deliver(address_item *addrlist, host_item *host, int host_af, int defport,
uschar *interface, transport_instance *tblock,
BOOL *message_defer, BOOL suppress_tls)
{
address_item *addr;
+smtp_transport_options_block * ob = SOB tblock->options_block;
int yield = OK;
int save_errno;
int rc;
-time_t start_delivery_time = time(NULL);
+struct timeval start_delivery_time;
BOOL pass_message = FALSE;
uschar *message = NULL;
uschar new_message_id[MESSAGE_ID_LENGTH + 1];
-uschar *p;
smtp_context sx;
+gettimeofday(&start_delivery_time, NULL);
suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */
*message_defer = FALSE;
sx.addrlist = addrlist;
-sx.host = host;
-sx.host_af = host_af,
-sx.port = port;
-sx.interface = interface;
+sx.conn_args.host = host;
+sx.conn_args.host_af = host_af,
+sx.port = defport;
+sx.conn_args.interface = interface;
sx.helo_data = NULL;
-sx.tblock = tblock;
+sx.conn_args.tblock = tblock;
sx.verify = FALSE;
+sx.sync_addr = sx.first_addr = addrlist;
/* Get the channel set up ready for a message (MAIL FROM being the next
SMTP command to send */
if (tblock->filter_command)
{
- BOOL rc;
- uschar fbuf[64];
- sprintf(CS fbuf, "%.50s transport", tblock->name);
- rc = transport_set_up_command(&transport_filter_argv, tblock->filter_command,
- TRUE, DEFER, addrlist, fbuf, NULL);
transport_filter_timeout = tblock->filter_timeout;
/* On failure, copy the error to all addresses, abandon the SMTP call, and
yield ERROR. */
- if (!rc)
+ if (!transport_set_up_command(&transport_filter_argv,
+ tblock->filter_command, TRUE, DEFER, addrlist,
+ string_sprintf("%.50s transport", tblock->name), NULL))
{
set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
FALSE);
if ( transport_filter_argv
&& *transport_filter_argv
&& **transport_filter_argv
- && sx.peer_offered & PEER_OFFERED_CHUNKING
+ && sx.peer_offered & OPTION_CHUNKING
)
{
- sx.peer_offered &= ~PEER_OFFERED_CHUNKING;
+ sx.peer_offered &= ~OPTION_CHUNKING;
DEBUG(D_transport) debug_printf("CHUNKING not usable due to transport filter\n");
}
}
-
/* For messages that have more than the maximum number of envelope recipients,
we want to send several transactions down the same SMTP connection. (See
comments in deliver.c as to how this reconciles, heuristically, with
SEND_MESSAGE:
sx.from_addr = return_path;
-sx.first_addr = sx.sync_addr = addrlist;
+sx.sync_addr = sx.first_addr;
sx.ok = FALSE;
sx.send_rset = TRUE;
sx.completed_addr = FALSE;
-/* Initiate a message transfer. */
+/* If we are a continued-connection-after-verify the MAIL and RCPT
+commands were already sent; do not re-send but do mark the addrs as
+having been accepted up to RCPT stage. A traditional cont-conn
+always has a sequence number greater than one. */
-switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+if (continue_hostname && continue_sequence == 1)
{
- case 0: break;
- case -1: case -2: goto RESPONSE_FAILED;
- case -3: goto END_OFF;
- case -4: goto SEND_QUIT;
- default: goto SEND_FAILED;
- }
+ address_item * addr;
-/* If we are an MUA wrapper, abort if any RCPTs were rejected, either
-permanently or temporarily. We should have flushed and synced after the last
-RCPT. */
+ sx.peer_offered = smtp_peer_options;
+ sx.pending_MAIL = FALSE;
+ sx.ok = TRUE;
+ sx.next_addr = NULL;
-if (mua_wrapper)
+ for (addr = addrlist; addr; addr = addr->next)
+ addr->transport_return = PENDING_OK;
+ }
+else
{
- address_item *badaddr;
- for (badaddr = sx.first_addr; badaddr; badaddr = badaddr->next)
- if (badaddr->transport_return != PENDING_OK)
- {
- /*XXX could we find a better errno than 0 here? */
- set_errno_nohost(addrlist, 0, badaddr->message, FAIL,
- testflag(badaddr, af_pass_message));
- sx.ok = FALSE;
- break;
- }
+ /* Initiate a message transfer. */
+
+ switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+ {
+ case 0: break;
+ case -1: case -2: goto RESPONSE_FAILED;
+ case -3: goto END_OFF;
+ case -4: goto SEND_QUIT;
+ default: goto SEND_FAILED;
+ }
+
+ /* If we are an MUA wrapper, abort if any RCPTs were rejected, either
+ permanently or temporarily. We should have flushed and synced after the last
+ RCPT. */
+
+ if (mua_wrapper)
+ {
+ address_item * a;
+ unsigned cnt;
+
+ for (a = sx.first_addr, cnt = 0; a && cnt < sx.max_rcpt; a = a->next, cnt++)
+ if (a->transport_return != PENDING_OK)
+ {
+ /*XXX could we find a better errno than 0 here? */
+ set_errno_nohost(addrlist, 0, a->message, FAIL,
+ testflag(a, af_pass_message));
+ sx.ok = FALSE;
+ break;
+ }
+ }
}
/* If ok is TRUE, we know we have got at least one good recipient, and must now
If using CHUNKING, do not send a BDAT until we know how big a chunk we want
to send is. */
-if ( !(sx.peer_offered & PEER_OFFERED_CHUNKING)
+if ( !(sx.peer_offered & OPTION_CHUNKING)
&& (sx.ok || (pipelining_active && !mua_wrapper)))
{
- int count = smtp_write_command(&sx.outblock, FALSE, "DATA\r\n");
+ int count = smtp_write_command(&sx, SCMD_FLUSH, "DATA\r\n");
if (count < 0) goto SEND_FAILED;
switch(sync_responses(&sx, count, sx.ok ? +1 : -1))
case 0: break; /* No 2xx or 5xx, but no probs */
case -1: goto END_OFF; /* Timeout on RCPT */
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+#endif
default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
}
pipelining_active = FALSE;
well as body. Set the appropriate timeout value to be used for each chunk.
(Haven't been able to make it work using select() for writing yet.) */
-if (!(sx.peer_offered & PEER_OFFERED_CHUNKING) && !sx.ok)
+if (!(sx.peer_offered & OPTION_CHUNKING) && !sx.ok)
{
/* Save the first address of the next batch. */
sx.first_addr = sx.next_addr;
else
{
transport_ctx tctx = {
- tblock,
- addrlist,
- US".", US"..", /* Escaping strings */
- topt_use_crlf | topt_escape_headers
+ .u = {.fd = sx.cctx.sock}, /*XXX will this need TLS info? */
+ .tblock = tblock,
+ .addr = addrlist,
+ .check_string = US".",
+ .escape_string = US"..", /* Escaping strings */
+ .options =
+ topt_use_crlf | topt_escape_headers
| (tblock->body_only ? topt_no_headers : 0)
| (tblock->headers_only ? topt_no_body : 0)
| (tblock->return_path_add ? topt_add_return_path : 0)
of responses. The callback needs a whole bunch of state so set up
a transport-context structure to be passed around. */
- if (sx.peer_offered & PEER_OFFERED_CHUNKING)
+ if (sx.peer_offered & OPTION_CHUNKING)
{
tctx.check_string = tctx.escape_string = NULL;
tctx.options |= topt_use_bdat;
sx.buffer[0] = 0;
sigalrm_seen = FALSE;
- transport_write_timeout = sx.ob->data_timeout;
+ transport_write_timeout = ob->data_timeout;
smtp_command = US"sending data block"; /* For error messages */
DEBUG(D_transport|D_v)
- if (sx.peer_offered & PEER_OFFERED_CHUNKING)
+ if (sx.peer_offered & OPTION_CHUNKING)
debug_printf(" will write message using CHUNKING\n");
else
debug_printf(" SMTP>> writing message and terminating \".\"\n");
transport_count = 0;
#ifndef DISABLE_DKIM
- sx.ok = dkim_transport_write_message(sx.inblock.sock, &tctx, &sx.ob->dkim);
+ dkim_exim_sign_init();
+# ifdef EXPERIMENTAL_ARC
+ {
+ uschar * s = ob->arc_sign;
+ if (s)
+ {
+ if (!(ob->dkim.arc_signspec = s = expand_string(s)))
+ {
+ if (!f.expand_string_forcedfail)
+ {
+ message = US"failed to expand arc_sign";
+ sx.ok = FALSE;
+ goto SEND_FAILED;
+ }
+ }
+ else if (*s)
+ {
+ /* Ask dkim code to hash the body for ARC */
+ (void) arc_ams_setup_sign_bodyhash();
+ ob->dkim.force_bodyhash = TRUE;
+ }
+ }
+ }
+# endif
+ sx.ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
#else
- sx.ok = transport_write_message(sx.inblock.sock, &tctx, 0);
+ sx.ok = transport_write_message(&tctx, 0);
#endif
/* transport_write_message() uses write() because it is called from other
Or, when CHUNKING, it can be a protocol-detected failure. */
if (!sx.ok)
- goto RESPONSE_FAILED;
+ if (message) goto SEND_FAILED;
+ else goto RESPONSE_FAILED;
/* We used to send the terminating "." explicitly here, but because of
buffering effects at both ends of TCP/IP connections, you don't gain
smtp_command = US"end of data";
- if (sx.peer_offered & PEER_OFFERED_CHUNKING && sx.cmd_count > 1)
+ if (sx.peer_offered & OPTION_CHUNKING && sx.cmd_count > 1)
{
/* Reap any outstanding MAIL & RCPT commands, but not a DATA-go-ahead */
switch(sync_responses(&sx, sx.cmd_count-1, 0))
case 0: break; /* No 2xx or 5xx, but no probs */
case -1: goto END_OFF; /* Timeout on RCPT */
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ case -4: HDEBUG(D_transport)
+ debug_printf("failed reaping pipelined cmd responses\n");
+#endif
default: goto RESPONSE_FAILED; /* I/O error, or any MAIL/DATA error */
}
}
#ifndef DISABLE_PRDR
- /* For PRDR we optionally get a partial-responses warning
- * followed by the individual responses, before going on with
- * the overall response. If we don't get the warning then deal
- * with per non-PRDR. */
+ /* For PRDR we optionally get a partial-responses warning followed by the
+ individual responses, before going on with the overall response. If we don't
+ get the warning then deal with per non-PRDR. */
+
if(sx.prdr_active)
{
- sx.ok = smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '3',
- sx.ob->final_timeout);
+ sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '3', ob->final_timeout);
if (!sx.ok && errno == 0) switch(sx.buffer[0])
{
case '2': sx.prdr_active = FALSE;
if (!sx.lmtp)
{
- sx.ok = smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
- sx.ob->final_timeout);
+ sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
+ ob->final_timeout);
if (!sx.ok && errno == 0 && sx.buffer[0] == '4')
{
errno = ERRNO_DATA4XX;
if (sx.ok)
{
int flag = '=';
- int delivery_time = (int)(time(NULL) - start_delivery_time);
+ struct timeval delivery_time;
int len;
- uschar *conf = NULL;
+ uschar * conf = NULL;
+ timesince(&delivery_time, &start_delivery_time);
sx.send_rset = FALSE;
pipelining_active = FALSE;
{
const uschar *s = string_printing(sx.buffer);
/* deconst cast ok here as string_printing was checked to have alloc'n'copied */
- conf = (s == sx.buffer)? (uschar *)string_copy(s) : US s;
+ conf = (s == sx.buffer)? US string_copy(s) : US s;
}
/* Process all transported addresses - for LMTP or PRDR, read a status for
if (sx.lmtp)
#endif
{
- if (!smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
- sx.ob->final_timeout))
+ if (!smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
+ ob->final_timeout))
{
if (errno != 0 || sx.buffer[0] == 0) goto RESPONSE_FAILED;
addr->message = string_sprintf(
actual host that was used. */
addr->transport_return = OK;
- addr->more_errno = delivery_time;
+ addr->more_errno = delivery_time.tv_sec;
+ addr->delivery_usec = delivery_time.tv_usec;
addr->host_used = host;
addr->special_action = flag;
addr->message = conf;
+
+ if (tcp_out_fastopen)
+ {
+ setflag(addr, af_tcp_fastopen_conn);
+ if (tcp_out_fastopen >= TFO_USED_NODATA) setflag(addr, af_tcp_fastopen);
+ if (tcp_out_fastopen >= TFO_USED_DATA) setflag(addr, af_tcp_fastopen_data);
+ }
+ if (sx.pipelining_used) setflag(addr, af_pipelining);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ if (sx.early_pipe_active) setflag(addr, af_early_pipe);
+#endif
#ifndef DISABLE_PRDR
- if (sx.prdr_active) addr->flags |= af_prdr_used;
+ if (sx.prdr_active) setflag(addr, af_prdr_used);
#endif
- if (sx.peer_offered & PEER_OFFERED_CHUNKING) addr->flags |= af_chunking_used;
+ if (sx.peer_offered & OPTION_CHUNKING) setflag(addr, af_chunking_used);
flag = '-';
#ifndef DISABLE_PRDR
else
sprintf(CS sx.buffer, "%.500s\n", addr->unique);
- DEBUG(D_deliver) debug_printf("journalling %s\n", sx.buffer);
+ DEBUG(D_deliver) debug_printf("S:journalling %s\n", sx.buffer);
len = Ustrlen(CS sx.buffer);
if (write(journal_fd, sx.buffer, len) != len)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
#ifndef DISABLE_PRDR
if (sx.prdr_active)
{
+ const uschar * overall_message;
+
/* PRDR - get the final, overall response. For any non-success
upgrade all the address statuses. */
- sx.ok = smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer), '2',
- sx.ob->final_timeout);
+
+ sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2',
+ ob->final_timeout);
if (!sx.ok)
{
if(errno == 0 && sx.buffer[0] == '4')
goto RESPONSE_FAILED;
}
- /* Update the journal, or setup retry. */
+ /* Append the overall response to the individual PRDR response for logging
+ and update the journal, or setup retry. */
+
+ overall_message = string_printing(sx.buffer);
+ for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
+ if (addr->transport_return == OK)
+ addr->message = string_sprintf("%s\\n%s", addr->message, overall_message);
+
for (addr = addrlist; addr != sx.first_addr; addr = addr->next)
if (addr->transport_return == OK)
{
{
save_errno = errno;
code = '4';
- message = US string_sprintf("send() to %s [%s] failed: %s",
- host->name, host->address, strerror(save_errno));
+ message = string_sprintf("send() to %s [%s] failed: %s",
+ host->name, host->address, message ? message : US strerror(save_errno));
sx.send_quit = FALSE;
goto FAILED;
}
set_rc = DEFER;
if (save_errno > 0)
message = US string_sprintf("%s: %s", message, strerror(save_errno));
- if (host->next != NULL) log_write(0, LOG_MAIN, "%s", message);
- msglog_line(host, message);
+
+ write_logs(host, message, sx.first_addr ? sx.first_addr->basic_errno : 0);
+
*message_defer = TRUE;
}
}
else
{
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ /* If we were early-pipelinng and the actual EHLO response did not match
+ the cached value we assumed, we could have detected it and passed a
+ custom errno through to here. It would be nice to RSET and retry right
+ away, but to reliably do that we eould need an extra synch point before
+ we committed to data and that would discard half the gained roundrips.
+ Or we could summarily drop the TCP connection. but that is also ugly.
+ Instead, we ignore the possibility (having freshened the cache) and rely
+ on the server telling us with a nonmessage error if we have tried to
+ do something it no longer supports. */
+#endif
set_rc = DEFER;
yield = (save_errno == ERRNO_CHHEADER_FAIL ||
- save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
+ save_errno == ERRNO_FILTER_FAIL) ? ERROR : DEFER;
}
}
many as to hit the configured limit. The function transport_check_waiting looks
for a waiting message and returns its id. Then transport_pass_socket tries to
set up a continued delivery by passing the socket on to another process. The
-variable send_rset is FALSE if a message has just been successfully transfered.
+variable send_rset is FALSE if a message has just been successfully transferred.
If we are already sending down a continued channel, there may be further
addresses not yet delivered that are aimed at the same host, but which have not
DEBUG(D_transport)
debug_printf("ok=%d send_quit=%d send_rset=%d continue_more=%d "
"yield=%d first_address is %sNULL\n", sx.ok, sx.send_quit,
- sx.send_rset, continue_more, yield, sx.first_addr ? "not " : "");
+ sx.send_rset, f.continue_more, yield, sx.first_addr ? "not " : "");
if (sx.completed_addr && sx.ok && sx.send_quit)
{
t_compare.current_sender_address = sender_address;
if ( sx.first_addr != NULL
- || continue_more
- || ( ( tls_out.active < 0
- || verify_check_given_host(&sx.ob->hosts_nopass_tls, host) != OK
+ || f.continue_more
+ || (
+#ifdef SUPPORT_TLS
+ ( tls_out.active.sock < 0 && !continue_proxy_cipher
+ || verify_check_given_host(CUSS &ob->hosts_nopass_tls, host) != OK
)
&&
+#endif
transport_check_waiting(tblock->name, host->name,
tblock->connection_max_messages, new_message_id, &more,
(oicf)smtp_are_same_identities, (void*)&t_compare)
BOOL pass_message;
if (sx.send_rset)
- if (! (sx.ok = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0))
+ if (! (sx.ok = smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0))
{
msg = US string_sprintf("send() to %s [%s] failed: %s", host->name,
host->address, strerror(errno));
sx.send_quit = FALSE;
}
- else if (! (sx.ok = smtp_read_response(&sx.inblock, sx.buffer,
- sizeof(sx.buffer), '2', sx.ob->command_timeout)))
+ else if (! (sx.ok = smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer),
+ '2', ob->command_timeout)))
{
int code;
sx.send_quit = check_response(host, &errno, 0, sx.buffer, &code, &msg,
if (sx.ok)
{
- if (sx.first_addr != NULL) /* More addresses still to be sent */
+#ifdef SUPPORT_TLS
+ int pfd[2];
+#endif
+ int socket_fd = sx.cctx.sock;
+
+
+ if (sx.first_addr != NULL) /* More addresses still to be sent */
{ /* in this run of the transport */
continue_sequence++; /* Causes * in logging */
goto SEND_MESSAGE;
}
- if (continue_more) return yield; /* More addresses for another run */
- /* Pass the socket to a new Exim process. Before doing so, we must shut
- down TLS. Not all MTAs allow for the continuation of the SMTP session
- when TLS is shut down. We test for this by sending a new EHLO. If we
- don't get a good response, we don't attempt to pass the socket on. */
+ /* Unless caller said it already has more messages listed for this host,
+ pass the connection on to a new Exim process (below, the call to
+ transport_pass_socket). If the caller has more ready, just return with
+ the connection still open. */
#ifdef SUPPORT_TLS
- if (tls_out.active >= 0)
- {
- tls_close(FALSE, TRUE);
- smtp_peer_options = smtp_peer_options_wrap;
- sx.ok = !sx.smtps
- && smtp_write_command(&sx.outblock, FALSE,
- "EHLO %s\r\n", sx.helo_data) >= 0
- && smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
- '2', sx.ob->command_timeout);
- }
+ if (tls_out.active.sock >= 0)
+ if ( f.continue_more
+ || verify_check_given_host(CUSS &ob->hosts_noproxy_tls, host) == OK)
+ {
+ /* Before passing the socket on, or returning to caller with it still
+ open, we must shut down TLS. Not all MTAs allow for the continuation
+ of the SMTP session when TLS is shut down. We test for this by sending
+ a new EHLO. If we don't get a good response, we don't attempt to pass
+ the socket on. */
+
+ tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_WAIT);
+ sx.cctx.tls_ctx = NULL;
+ smtp_peer_options = smtp_peer_options_wrap;
+ sx.ok = !sx.smtps
+ && smtp_write_command(&sx, SCMD_FLUSH, "EHLO %s\r\n", sx.helo_data)
+ >= 0
+ && smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer),
+ '2', ob->command_timeout);
+
+ if (sx.ok && f.continue_more)
+ return yield; /* More addresses for another run */
+ }
+ else
+ {
+ /* Set up a pipe for proxying TLS for the new transport process */
+
+ smtp_peer_options |= OPTION_TLS;
+ if (sx.ok = (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) == 0))
+ socket_fd = pfd[1];
+ else
+ set_errno(sx.first_addr, errno, US"internal allocation problem",
+ DEFER, FALSE, host
+# ifdef EXPERIMENTAL_DSN_INFO
+ , sx.smtp_greeting, sx.helo_response
+# endif
+ );
+ }
+ else
#endif
+ if (f.continue_more)
+ return yield; /* More addresses for another run */
/* If the socket is successfully passed, we mustn't send QUIT (or
indeed anything!) from here. */
propagate it from the initial
*/
if (sx.ok && transport_pass_socket(tblock->name, host->name,
- host->address, new_message_id, sx.inblock.sock))
+ host->address, new_message_id, socket_fd))
+ {
sx.send_quit = FALSE;
+
+ /* We have passed the client socket to a fresh transport process.
+ If TLS is still active, we need to proxy it for the transport we
+ just passed the baton to. Fork a child to to do it, and return to
+ get logging done asap. Which way to place the work makes assumptions
+ about post-fork prioritisation which may not hold on all platforms. */
+#ifdef SUPPORT_TLS
+ if (tls_out.active.sock >= 0)
+ {
+ int pid = fork();
+ if (pid == 0) /* child; fork again to disconnect totally */
+ {
+ if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
+ /* does not return */
+ smtp_proxy_tls(sx.cctx.tls_ctx, sx.buffer, sizeof(sx.buffer), pfd,
+ ob->command_timeout);
+ }
+
+ if (pid > 0) /* parent */
+ {
+ DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
+ close(pfd[0]);
+ /* tidy the inter-proc to disconn the proxy proc */
+ waitpid(pid, NULL, 0);
+ tls_close(sx.cctx.tls_ctx, TLS_NO_SHUTDOWN);
+ sx.cctx.tls_ctx = NULL;
+ (void)close(sx.cctx.sock);
+ sx.cctx.sock = -1;
+ continue_transport = NULL;
+ continue_hostname = NULL;
+ return yield;
+ }
+ log_write(0, LOG_PANIC_DIE, "fork failed");
+ }
+#endif
+ }
}
/* If RSET failed and there are addresses left, they get deferred. */
-
- else set_errno(sx.first_addr, errno, msg, DEFER, FALSE, host
+ else
+ set_errno(sx.first_addr, errno, msg, DEFER, FALSE, host
#ifdef EXPERIMENTAL_DSN_INFO
, sx.smtp_greeting, sx.helo_response
#endif
operation, the old commented-out code was removed on 17-Sep-99. */
SEND_QUIT:
-if (sx.send_quit) (void)smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n");
+if (sx.send_quit) (void)smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n");
END_OFF:
#ifdef SUPPORT_TLS
-tls_close(FALSE, TRUE);
+tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+sx.cctx.tls_ctx = NULL;
#endif
/* Close the socket, and return the appropriate value, first setting
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
if (sx.send_quit)
{
- shutdown(sx.outblock.sock, SHUT_WR);
- if (fcntl(sx.inblock.sock, F_SETFL, O_NONBLOCK) == 0)
- for (rc = 16; read(sx.inblock.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;)
+ shutdown(sx.cctx.sock, SHUT_WR);
+ millisleep(f.running_in_test_harness ? 200 : 20);
+ if (fcntl(sx.cctx.sock, F_SETFL, O_NONBLOCK) == 0)
+ for (rc = 16; read(sx.cctx.sock, sx.inbuffer, sizeof(sx.inbuffer)) > 0 && rc > 0;)
rc--; /* drain socket */
}
-(void)close(sx.inblock.sock);
+(void)close(sx.cctx.sock);
#ifndef DISABLE_EVENT
(void) event_raise(tblock->event_action, US"tcp:close", NULL);
void
smtp_transport_closedown(transport_instance *tblock)
{
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)tblock->options_block;
-smtp_inblock inblock;
-smtp_outblock outblock;
+smtp_transport_options_block * ob = SOB tblock->options_block;
+client_conn_ctx cctx;
+smtp_context sx;
uschar buffer[256];
uschar inbuffer[4096];
uschar outbuffer[16];
-inblock.sock = fileno(stdin);
-inblock.buffer = inbuffer;
-inblock.buffersize = sizeof(inbuffer);
-inblock.ptr = inbuffer;
-inblock.ptrend = inbuffer;
-
-outblock.sock = inblock.sock;
-outblock.buffersize = sizeof(outbuffer);
-outblock.buffer = outbuffer;
-outblock.ptr = outbuffer;
-outblock.cmd_count = 0;
-outblock.authenticating = FALSE;
-
-(void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
-(void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
- ob->command_timeout);
-(void)close(inblock.sock);
+/*XXX really we need an active-smtp-client ctx, rather than assuming stdout */
+cctx.sock = fileno(stdin);
+cctx.tls_ctx = cctx.sock == tls_out.active.sock ? tls_out.active.tls_ctx : NULL;
+
+sx.inblock.cctx = &cctx;
+sx.inblock.buffer = inbuffer;
+sx.inblock.buffersize = sizeof(inbuffer);
+sx.inblock.ptr = inbuffer;
+sx.inblock.ptrend = inbuffer;
+
+sx.outblock.cctx = &cctx;
+sx.outblock.buffersize = sizeof(outbuffer);
+sx.outblock.buffer = outbuffer;
+sx.outblock.ptr = outbuffer;
+sx.outblock.cmd_count = 0;
+sx.outblock.authenticating = FALSE;
+
+(void)smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n");
+(void)smtp_read_response(&sx, buffer, sizeof(buffer), '2', ob->command_timeout);
+(void)close(cctx.sock);
}
address_item *addrlist) /* addresses we are working on */
{
int cutoff_retry;
-int port;
+int defport;
int hosts_defer = 0;
int hosts_fail = 0;
int hosts_looked_up = 0;
uschar *expanded_hosts = NULL;
uschar *pistring;
uschar *tid = string_sprintf("%s transport", tblock->name);
-smtp_transport_options_block *ob =
- (smtp_transport_options_block *)(tblock->options_block);
+smtp_transport_options_block *ob = SOB tblock->options_block;
host_item *hostlist = addrlist->host_list;
-host_item *host = NULL;
+host_item *host;
DEBUG(D_transport)
{
{
debug_printf("hostlist:\n");
for (host = hostlist; host; host = host->next)
- debug_printf(" %s:%d\n", host->name, host->port);
+ debug_printf(" '%s' IP %s port %d\n", host->name, host->address, host->port);
}
- if (continue_hostname) debug_printf("already connected to %s [%s]\n",
- continue_hostname, continue_host_address);
+ if (continue_hostname)
+ debug_printf("already connected to %s [%s] (on fd %d)\n",
+ continue_hostname, continue_host_address,
+ cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0);
}
/* Set the flag requesting that these hosts be added to the waiting
a host list with hosts_override set, use the host list supplied with the
transport. It is an error for this not to exist. */
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+if (tls_requiretls & REQUIRETLS_MSG)
+ ob->tls_tempfail_tryclear = FALSE; /*XXX surely we should have a local for this
+ rather than modifying the transport? */
+#endif
+
if (!hostlist || (ob->hosts_override && ob->hosts))
{
if (!ob->hosts)
{
addrlist->message = string_sprintf("failed to expand list of hosts "
"\"%s\" in %s transport: %s", s, tblock->name, expand_string_message);
- addrlist->transport_return = search_find_defer ? DEFER : PANIC;
+ addrlist->transport_return = f.search_find_defer ? DEFER : PANIC;
return FALSE; /* Only top address has status */
}
DEBUG(D_transport) debug_printf("expanded list of hosts \"%s\" to "
/* Sort out the default port. */
-if (!smtp_get_port(ob->port, addrlist, &port, tid)) return FALSE;
+if (!smtp_get_port(ob->port, addrlist, &defport, tid)) return FALSE;
/* For each host-plus-IP-address on the list:
{
host_item *nexthost = NULL;
int unexpired_hosts_tried = 0;
+ BOOL continue_host_tried = FALSE;
+retry_non_continued:
for (host = hostlist;
host
&& unexpired_hosts_tried < ob->hosts_max_try
/* Find by name if so configured, or if it's an IP address. We don't
just copy the IP address, because we need the test-for-local to happen. */
- flags = HOST_FIND_BY_A;
+ flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
if (ob->dns_qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
if (ob->dns_search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
commonly points to a configuration error, but the best action is still
to carry on for the next host. */
- if (rc == HOST_FIND_AGAIN || rc == HOST_FIND_FAILED)
+ if (rc == HOST_FIND_AGAIN || rc == HOST_FIND_SECURITY || rc == HOST_FIND_FAILED)
{
retry_add_item(addrlist, string_sprintf("R:%s", host->name), 0);
expired = FALSE;
{
if (addr->transport_return != DEFER) continue;
addr->basic_errno = ERRNO_UNKNOWNHOST;
- addr->message =
- string_sprintf("failed to lookup IP address for %s", host->name);
+ addr->message = string_sprintf(
+ rc == HOST_FIND_SECURITY
+ ? "lookup of IP address for %s was insecure"
+ : "failed to lookup IP address for %s",
+ host->name);
}
continue;
}
result of the lookup. Set expired FALSE, to save the outer loop executing
twice. */
- if ( continue_hostname
- && ( Ustrcmp(continue_hostname, host->name) != 0
- || Ustrcmp(continue_host_address, host->address) != 0
- ) )
- {
- expired = FALSE;
- continue; /* With next host */
- }
+ if (continue_hostname)
+ if ( Ustrcmp(continue_hostname, host->name) != 0
+ || Ustrcmp(continue_host_address, host->address) != 0
+ )
+ {
+ expired = FALSE;
+ continue; /* With next host */
+ }
+ else
+ continue_host_tried = TRUE;
/* Reset the default next host in case a multihomed host whose addresses
are not looked up till just above added to the host list. */
were not in it. We don't want to hold up all SMTP deliveries! Except when
doing a two-stage queue run, don't do this if forcing. */
- if ((!deliver_force || queue_2stage) && (queue_smtp ||
+ if ((!f.deliver_force || f.queue_2stage) && (f.queue_smtp ||
match_isinlist(addrlist->domain,
(const uschar **)&queue_smtp_domains, 0,
&domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
the default. */
pistring = string_sprintf(":%d", host->port == PORT_NONE
- ? port : host->port);
+ ? defport : host->port);
if (Ustrcmp(pistring, ":25") == 0) pistring = US"";
/* Select IPv4 or IPv6, and choose an outgoing interface. If the interface
host_is_expired = retry_check_address(addrlist->domain, host, pistring,
incl_ip, &retry_host_key, &retry_message_key);
- DEBUG(D_transport) debug_printf("%s [%s]%s status = %s\n", host->name,
- (host->address == NULL)? US"" : host->address, pistring,
- (host->status == hstatus_usable)? "usable" :
- (host->status == hstatus_unusable)? "unusable" :
- (host->status == hstatus_unusable_expired)? "unusable (expired)" : "?");
+ DEBUG(D_transport) debug_printf("%s [%s]%s retry-status = %s\n", host->name,
+ host->address ? host->address : US"", pistring,
+ host->status == hstatus_usable ? "usable"
+ : host->status == hstatus_unusable ? "unusable"
+ : host->status == hstatus_unusable_expired ? "unusable (expired)" : "?");
/* Skip this address if not usable at this time, noting if it wasn't
actually expired, both locally and in the address. */
{
case hwhy_retry: hosts_retry++; break;
case hwhy_failed: hosts_fail++; break;
+ case hwhy_insecure:
case hwhy_deferred: hosts_defer++; break;
}
{
if ( !host->address
|| host->status != hstatus_unusable_expired
- || host->last_try > received_time)
+ || host->last_try > received_time.tv_sec)
continue;
DEBUG(D_transport) debug_printf("trying expired host %s [%s]%s\n",
host->name, host->address, pistring);
sending the message down a pre-existing connection. */
if ( !continue_hostname
- && verify_check_given_host(&ob->serialize_hosts, host) == OK)
+ && verify_check_given_host(CUSS &ob->serialize_hosts, host) == OK)
{
serialize_key = string_sprintf("host-serialize-%s", host->name);
if (!enq_start(serialize_key, 1))
message_id, host->name, host->address, addrlist->address,
addrlist->next ? ", ..." : "");
- set_process_info("delivering %s to %s [%s] (%s%s)",
- message_id, host->name, host->address, addrlist->address,
+ set_process_info("delivering %s to %s [%s]%s (%s%s)",
+ message_id, host->name, host->address, pistring, addrlist->address,
addrlist->next ? ", ..." : "");
/* This is not for real; don't do the delivery. If there are
any remaining hosts, list them. */
- if (dont_deliver)
+ if (f.dont_deliver)
{
host_item *host2;
set_errno_nohost(addrlist, 0, NULL, OK, FALSE);
/* Attempt the delivery. */
total_hosts_tried++;
- rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+ rc = smtp_deliver(addrlist, thost, host_af, defport, interface, tblock,
&message_defer, FALSE);
/* Yield is one of:
if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL
&& first_addr->basic_errno != ERRNO_TLSFAILURE)
- write_logs(first_addr, host);
+ write_logs(host, first_addr->message, first_addr->basic_errno);
#ifndef DISABLE_EVENT
if (rc == DEFER)
if ( rc == DEFER
&& first_addr->basic_errno == ERRNO_TLSFAILURE
&& ob->tls_tempfail_tryclear
- && verify_check_given_host(&ob->hosts_require_tls, host) != OK
+ && verify_check_given_host(CUSS &ob->hosts_require_tls, host) != OK
)
{
- log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted "
- "to %s [%s] (not in hosts_require_tls)", host->name, host->address);
+ log_write(0, LOG_MAIN,
+ "%s: delivering unencrypted to H=%s [%s] (not in hosts_require_tls)",
+ first_addr->message, host->name, host->address);
first_addr = prepare_addresses(addrlist, host);
- rc = smtp_deliver(addrlist, thost, host_af, port, interface, tblock,
+ rc = smtp_deliver(addrlist, thost, host_af, defport, interface, tblock,
&message_defer, TRUE);
if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
- write_logs(first_addr, host);
+ write_logs(host, first_addr->message, first_addr->basic_errno);
# ifndef DISABLE_EVENT
if (rc == DEFER)
deferred_event_raise(first_addr, host);
: rc == ERROR ? US"ERROR"
: US"?";
- set_process_info("delivering %s: just tried %s [%s] for %s%s: result %s",
- message_id, host->name, host->address, addrlist->address,
+ set_process_info("delivering %s: just tried %s [%s]%s for %s%s: result %s",
+ message_id, host->name, host->address, pistring, addrlist->address,
addrlist->next ? " (& others)" : "", rs);
/* Release serialization if set up */
for (last_rule = retry->rules;
last_rule->next;
last_rule = last_rule->next);
- timedout = time(NULL) - received_time > last_rule->timeout;
+ timedout = time(NULL) - received_time.tv_sec > last_rule->timeout;
}
else timedout = TRUE; /* No rule => timed out */
"hosts_max_try (message older than host's retry time)\n");
}
}
+
+ DEBUG(D_transport)
+ {
+ if (unexpired_hosts_tried >= ob->hosts_max_try)
+ debug_printf("reached transport hosts_max_try limit %d\n",
+ ob->hosts_max_try);
+ if (total_hosts_tried >= ob->hosts_max_try_hardlimit)
+ debug_printf("reached transport hosts_max_try_hardlimit limit %d\n",
+ ob->hosts_max_try_hardlimit);
+ }
+
+ if (f.running_in_test_harness) millisleep(500); /* let server debug out */
} /* End of loop for trying multiple hosts. */
+ /* If we failed to find a matching host in the list, for an already-open
+ connection, just close it and start over with the list. This can happen
+ for routing that changes from run to run, or big multi-IP sites with
+ round-robin DNS. */
+
+ if (continue_hostname && !continue_host_tried)
+ {
+ int fd = cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0;
+
+ DEBUG(D_transport) debug_printf("no hosts match already-open connection\n");
+#ifdef SUPPORT_TLS
+ /* A TLS conn could be open for a cutthrough, but not for a plain continued-
+ transport */
+/*XXX doublecheck that! */
+
+ if (cutthrough.cctx.sock >= 0 && cutthrough.is_tls)
+ {
+ (void) tls_write(cutthrough.cctx.tls_ctx, US"QUIT\r\n", 6, FALSE);
+ tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ cutthrough.cctx.tls_ctx = NULL;
+ cutthrough.is_tls = FALSE;
+ }
+ else
+#else
+ (void) write(fd, US"QUIT\r\n", 6);
+#endif
+ (void) close(fd);
+ cutthrough.cctx.sock = -1;
+ continue_hostname = NULL;
+ goto retry_non_continued;
+ }
+
/* This is the end of the loop that repeats iff expired is TRUE and
ob->delay_after_cutoff is FALSE. The second time round we will
try those hosts that haven't been tried since the message arrived. */
setflag(addr, af_retry_skipped);
}
- if (queue_smtp) /* no deliveries attempted */
+ if (f.queue_smtp) /* no deliveries attempted */
{
addr->transport_return = DEFER;
addr->basic_errno = 0;
return TRUE; /* Each address has its status */
}
+#endif /*!MACRO_PREDEF*/
/* vi: aw ai sw=2
*/
/* End of transport/smtp.c */
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#define DELIVER_BUFFER_SIZE 4096
uschar *hosts_try_auth;
uschar *hosts_require_auth;
uschar *hosts_try_chunking;
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
uschar *hosts_try_dane;
uschar *hosts_require_dane;
+ uschar *dane_require_tls_ciphers;
#endif
uschar *hosts_try_fastopen;
#ifndef DISABLE_PRDR
uschar *hosts_avoid_tls;
uschar *hosts_verify_avoid_tls;
uschar *hosts_avoid_pipelining;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ uschar *hosts_pipe_connect;
+#endif
uschar *hosts_avoid_esmtp;
+#ifdef SUPPORT_TLS
uschar *hosts_nopass_tls;
+ uschar *hosts_noproxy_tls;
+#endif
int command_timeout;
int connect_timeout;
int data_timeout;
uschar *tls_try_verify_hosts;
uschar *tls_verify_cert_hostnames;
#endif
+#ifdef SUPPORT_I18N
+ uschar *utf8_downconvert;
+#endif
#ifndef DISABLE_DKIM
struct ob_dkim dkim;
#endif
+#ifdef EXPERIMENTAL_ARC
+ uschar *arc_sign;
+#endif
} smtp_transport_options_block;
+#define SOB (smtp_transport_options_block *)
+
+
/* smtp connect context */
typedef struct {
uschar * from_addr;
address_item * addrlist;
- host_item * host;
- int host_af;
+
+ smtp_connect_args conn_args;
int port;
- uschar * interface;
BOOL verify:1;
BOOL lmtp:1;
BOOL smtps:1;
BOOL ok:1;
BOOL setting_up:1;
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL early_pipe_ok:1;
+ BOOL early_pipe_active:1;
+#endif
BOOL esmtp:1;
BOOL esmtp_sent:1;
+ BOOL pipelining_used:1;
#ifndef DISABLE_PRDR
BOOL prdr_active:1;
#endif
BOOL utf8_needed:1;
#endif
BOOL dsn_all_lasthop:1;
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE)
+#if defined(SUPPORT_TLS) && defined(SUPPORT_DANE)
BOOL dane:1;
BOOL dane_required:1;
+#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ BOOL pending_BANNER:1;
+ BOOL pending_EHLO:1;
#endif
BOOL pending_MAIL:1;
BOOL pending_BDAT:1;
int max_rcpt;
int cmd_count;
- uschar peer_offered;
+ unsigned peer_offered;
+ unsigned avoid_option;
uschar * igquotstr;
uschar * helo_data;
#ifdef EXPERIMENTAL_DSN_INFO
uschar * smtp_greeting;
uschar * helo_response;
#endif
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+ ehlo_resp_precis ehlo_resp;
+#endif
address_item * first_addr;
address_item * next_addr;
address_item * sync_addr;
- smtp_inblock inblock;
- smtp_outblock outblock;
+ client_conn_ctx cctx;
+ smtp_inblock inblock;
+ smtp_outblock outblock;
uschar buffer[DELIVER_BUFFER_SIZE];
uschar inbuffer[4096];
uschar outbuffer[4096];
-
- transport_instance * tblock;
- smtp_transport_options_block * ob;
} smtp_context;
extern int smtp_setup_conn(smtp_context *, BOOL);
extern int smtp_write_mail_and_rcpt_cmds(smtp_context *, int *);
+extern int smtp_reap_early_pipe(smtp_context *, int *);
/* Data for reading the private options. */
-extern int smtp_auth(uschar *, unsigned, address_item *, host_item *,
- smtp_transport_options_block *, BOOL,
- smtp_inblock *, smtp_outblock *);
extern BOOL smtp_mail_auth_str(uschar *, unsigned,
address_item *, smtp_transport_options_block *);
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) Jeremy Harris 2015 */
+/* Copyright (c) Jeremy Harris 2015 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* SOCKS version 5 proxy, client-mode */
static void
socks_option(socks_opts * sob, const uschar * opt)
{
-const uschar * s;
-
if (Ustrncmp(opt, "auth=", 5) == 0)
{
opt += 5;
else if (Ustrncmp(opt, "pass=", 5) == 0)
sob->auth_pwd = opt + 5;
else if (Ustrncmp(opt, "port=", 5) == 0)
- sob->port = atoi(opt + 5);
+ sob->port = atoi(CCS opt + 5);
else if (Ustrncmp(opt, "tmo=", 4) == 0)
- sob->timeout = atoi(opt + 4);
+ sob->timeout = atoi(CCS opt + 4);
else if (Ustrncmp(opt, "pri=", 4) == 0)
- sob->priority = atoi(opt + 4);
+ sob->priority = atoi(CCS opt + 4);
else if (Ustrncmp(opt, "weight=", 7) == 0)
- sob->weight = atoi(opt + 7);
+ sob->weight = atoi(CCS opt + 7);
return;
}
for (i = 0; i<len; i++) debug_printf(" %02x", s[i]);
debug_printf("\n");
}
- if ( send(fd, s, len, 0) < 0
- || !fd_ready(fd, tmo-time(NULL))
- || read(fd, s, 2) != 2
- )
+ if (send(fd, s, len, 0) < 0)
+ return FAIL;
+#ifdef TCP_QUICKACK
+ (void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
+ if (!fd_ready(fd, tmo-time(NULL)) || read(fd, s, 2) != 2)
return FAIL;
HDEBUG(D_transport|D_acl|D_v)
debug_printf_indent(" SOCKS<< %02x %02x\n", s[0], s[1]);
unsigned nproxies;
socks_opts * sob;
unsigned size;
+blob early_data;
if (!timeout) timeout = 24*60*60; /* use 1 day for "indefinite" */
tmo = time(NULL) + timeout;
socks_option(sob, option);
}
+/* Set up the socks protocol method-selection message,
+for sending on connection */
+
+state = US"method select";
+buf[0] = 5; buf[1] = 1; buf[2] = sob->auth_type;
+early_data.data = buf;
+early_data.len = 3;
+
/* Try proxies until a connection succeeds */
for(;;)
sob = &proxies[idx];
/* bodge up a host struct for the proxy */
- proxy.address = sob->proxy_host;
+ proxy.address = proxy.name = sob->proxy_host;
proxy_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
+ /*XXX we trust that the method-select command is idempotent */
if ((fd = smtp_sock_connect(&proxy, proxy_af, sob->port,
- interface, tb, sob->timeout)) >= 0)
+ interface, tb, sob->timeout, &early_data)) >= 0)
{
proxy_local_address = string_copy(proxy.address);
proxy_local_port = sob->port;
}
/* Do the socks protocol stuff */
-/* Send method-selection */
-state = US"method select";
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SOCKS>> 05 01 %02x\n", sob->auth_type);
-buf[0] = 5; buf[1] = 1; buf[2] = sob->auth_type;
-if (send(fd, buf, 3, 0) < 0)
- goto snd_err;
/* expect method response */
+#ifdef TCP_QUICKACK
+(void) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
+
if ( !fd_ready(fd, tmo-time(NULL))
|| read(fd, buf, 2) != 2
)
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions in support of the use of maildirsize files for handling quotas in
uschar buffer[256];
sprintf(CS buffer, "%d 1\n", size);
len = Ustrlen(buffer);
-(void)lseek(fd, 0, SEEK_END);
-len = write(fd, buffer, len);
-DEBUG(D_transport)
- debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
+if (lseek(fd, 0, SEEK_END) >= 0)
+ {
+ len = write(fd, buffer, len);
+ DEBUG(D_transport)
+ debug_printf("added '%.*s' to maildirsize file\n", len-1, buffer);
+ }
}
tree_node *
tree_search(tree_node *p, const uschar *name)
{
-while (p != NULL)
+while (p)
{
int c = Ustrcmp(name, p->name);
if (c == 0) return p;
- p = (c < 0)? p->left : p->right;
+ p = c < 0 ? p->left : p->right;
}
return NULL;
}
void
tree_walk(tree_node *p, void (*f)(uschar*, uschar*, void*), void *ctx)
{
-if (p == NULL) return;
+if (!p) return;
f(p->name, p->data.ptr, ctx);
-if (p->left != NULL) tree_walk(p->left, f, ctx);
-if (p->right != NULL) tree_walk(p->right, f, ctx);
+tree_walk(p->left, f, ctx);
+tree_walk(p->right, f, ctx);
}
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) Jeremy Harris 2015, 2016 */
+/* Copyright (c) Jeremy Harris 2015 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
break;
}
}
-if ((rc = idn2_lookup_u8(CCS s, &s1, IDN2_NFC_INPUT)) != IDN2_OK)
+if ((rc = idn2_lookup_u8((const uint8_t *) s, &s1, IDN2_NFC_INPUT)) != IDN2_OK)
{
if (err) *err = US idn2_strerror(rc);
return NULL;
#ifdef SUPPORT_I18N_2008
const uschar * label;
int sep = '.';
-uschar * s = NULL;
+gstring * g = NULL;
while (label = string_nextinlist(&alabel, &sep, NULL, 0))
if ( string_is_alabel(label)
)
return NULL;
else
- s = string_append_listele(s, '.', label);
-return s;
+ g = string_append_listele(g, '.', label);
+return string_from_gstring(g);
#else
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with verifying things. The original code for callout
#define CUTTHROUGH_CMD_TIMEOUT 30 /* timeout for cutthrough-routing calls */
#define CUTTHROUGH_DATA_TIMEOUT 60 /* timeout for cutthrough-routing calls */
-static smtp_outblock ctblock;
+static smtp_context ctctx;
uschar ctbuffer[8192];
#define MT_NOT 1
#define MT_ALL 2
-static uschar cutthrough_response(char, uschar **, int);
+static uschar cutthrough_response(client_conn_ctx *, char, uschar **, int);
time_t now;
dbdata_callout_cache *cache_record;
-cache_record = dbfn_read_with_length(dbm_file, key, &length);
-
-if (cache_record == NULL)
+if (!(cache_record = dbfn_read_with_length(dbm_file, key, &length)))
{
HDEBUG(D_verify) debug_printf("callout cache: no %s record found for %s\n", type, key);
return NULL;
if ( cache_record->result == ccache_reject
|| *from_address == 0 && cache_record->result == ccache_reject_mfnull)
{
- setflag(addr, af_verify_nsfail);
HDEBUG(D_verify)
debug_printf("callout cache: domain gave initial rejection, or "
"does not accept HELO or MAIL FROM:<>\n");
case ccache_accept:
HDEBUG(D_verify)
debug_printf("callout cache: domain accepts random addresses\n");
+ *failure_ptr = US"random";
dbfn_close(dbm_file);
return TRUE; /* Default yield is OK */
deliver_domain = addr->domain;
transport_name = addr->transport->name;
- host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
+ host_af = Ustrchr(host->address, ':') ? AF_INET6 : AF_INET;
- if (!smtp_get_interface(tf->interface, host_af, addr, &interface,
- US"callout") ||
- !smtp_get_port(tf->port, addr, &port, US"callout"))
+ if ( !smtp_get_interface(tf->interface, host_af, addr, &interface,
+ US"callout")
+ || !smtp_get_port(tf->port, addr, &port, US"callout")
+ )
log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
addr->message);
+ smtp_port_for_connect(host, port);
+
if ( ( interface == cutthrough.interface
|| ( interface
&& cutthrough.interface
&& Ustrcmp(interface, cutthrough.interface) == 0
) )
- && port == cutthrough.host.port
+ && host->port == cutthrough.host.port
)
{
uschar * resp = NULL;
/* Match! Send the RCPT TO, set done from the response */
done =
- smtp_write_command(&ctblock, FALSE, "RCPT TO:<%.1000s>\r\n",
- transport_rcpt_address(addr,
- addr->transport->rcpt_include_affixes)) >= 0 &&
- cutthrough_response('2', &resp, CUTTHROUGH_DATA_TIMEOUT) == '2';
+ smtp_write_command(&ctctx, SCMD_FLUSH, "RCPT TO:<%.1000s>\r\n",
+ transport_rcpt_address(addr,
+ addr->transport->rcpt_include_affixes)) >= 0
+ && cutthrough_response(&cutthrough.cctx, '2', &resp,
+ CUTTHROUGH_DATA_TIMEOUT) == '2';
/* This would go horribly wrong if a callout fail was ignored by ACL.
We punt by abandoning cutthrough on a reject, like the
}
else
{
- cancel_cutthrough_connection("recipient rejected");
+ cancel_cutthrough_connection(TRUE, US"recipient rejected");
if (!resp || errno == ETIMEDOUT)
{
HDEBUG(D_verify) debug_printf("SMTP timeout\n");
break; /* host_list */
}
if (!done)
- cancel_cutthrough_connection("incompatible connection");
+ cancel_cutthrough_connection(TRUE, US"incompatible connection");
return done;
}
vopt_callout_random => do the "random" thing
vopt_callout_recipsender => use real sender for recipient
vopt_callout_recippmaster => use postmaster for recipient
+ vopt_callout_hold => lazy close connection
se_mailfrom MAIL FROM address for sender verify; NULL => ""
pm_mailfrom if non-NULL, do the postmaster check with this sender
if (cached_callout_lookup(addr, address_key, from_address,
&options, &pm_mailfrom, &yield, failure_ptr,
&new_domain_record, &old_domain_cache_result))
+ {
+ cancel_cutthrough_connection(TRUE, US"cache-hit");
goto END_CALLOUT;
+ }
if (!addr->transport)
{
and cause the client to time out. So in this case we forgo the PIPELINING
optimization. */
- if (smtp_out && !disable_callout_flush) mac_smtp_fflush();
+ if (smtp_out && !f.disable_callout_flush) mac_smtp_fflush();
clearflag(addr, af_verify_pmfail); /* postmaster callout flag */
clearflag(addr, af_verify_nsfail); /* null sender callout flag */
coding means skipping this whole loop and doing the append separately. */
/* Can we re-use an open cutthrough connection? */
- if ( cutthrough.fd >= 0
+ if ( cutthrough.cctx.sock >= 0
&& (options & (vopt_callout_recipsender | vopt_callout_recippmaster))
== vopt_callout_recipsender
&& !random_local_part
addr->message);
sx.addrlist = addr;
- sx.host = host;
- sx.host_af = host_af,
+ sx.conn_args.host = host;
+ sx.conn_args.host_af = host_af,
sx.port = port;
- sx.interface = interface;
+ sx.conn_args.interface = interface;
sx.helo_data = tf->helo_data;
- sx.tblock = addr->transport;
+ sx.conn_args.tblock = addr->transport;
sx.verify = TRUE;
tls_retry_connection:
if ( yield == DEFER
&& addr->basic_errno == ERRNO_TLSFAILURE
&& ob->tls_tempfail_tryclear
- && verify_check_given_host(&ob->hosts_require_tls, host) != OK
+ && verify_check_given_host(CUSS &ob->hosts_require_tls, host) != OK
)
{
- log_write(0, LOG_MAIN, "TLS session failure:"
- " callout unencrypted to %s [%s] (not in hosts_require_tls)",
- host->name, host->address);
+ log_write(0, LOG_MAIN,
+ "%s: callout unencrypted to %s [%s] (not in hosts_require_tls)",
+ addr->message, host->name, host->address);
addr->transport_return = PENDING_DEFER;
yield = smtp_setup_conn(&sx, TRUE);
}
sx.send_rset = TRUE;
sx.completed_addr = FALSE;
- new_domain_record.result =
- old_domain_cache_result == ccache_reject_mfnull
+ new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull
? ccache_reject_mfnull : ccache_accept;
/* Do the random local part check first. Temporarily replace the recipient
}
#endif
- /* This would be ok for 1st rcpt of a cutthrough (XXX do we have a count?) , but no way to
- handle a subsequent because of the RSET. So refuse to support any. */
- cancel_cutthrough_connection("random-recipient");
+ /* This would be ok for 1st rcpt of a cutthrough (the case handled here;
+ subsequents are done in cutthrough_multi()), but no way to
+ handle a subsequent because of the RSET vaporising the MAIL FROM.
+ So refuse to support any. Most cutthrough use will not involve
+ random_local_part, so no loss. */
+ cancel_cutthrough_connection(TRUE, US"random-recipient");
addr->address = string_sprintf("%s@%.1000s",
random_local_part, rcpt_domain);
postmaster-verify.
The sync_responses() would need to be taught about it and we'd
need another return code filtering out to here.
+
+ Avoid using a SIZE option on the MAIL for all random-rcpt checks.
*/
+ sx.avoid_option = OPTION_SIZE;
+
/* Remember when we last did a random test */
new_domain_record.random_stamp = time(NULL);
if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0)
switch(addr->transport_return)
{
- case PENDING_OK:
+ case PENDING_OK: /* random was accepted, unfortunately */
new_domain_record.random_result = ccache_accept;
- break;
- case FAIL:
+ yield = OK; /* Only usable verify result we can return */
+ done = TRUE;
+ *failure_ptr = US"random";
+ goto no_conn;
+ case FAIL: /* rejected: the preferred result */
new_domain_record.random_result = ccache_reject;
+ sx.avoid_option = 0;
/* Between each check, issue RSET, because some servers accept only
one recipient after MAIL FROM:<>.
XXX We don't care about that for postmaster_full. Should we? */
if ((done =
- smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0 &&
- smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
- '2', callout)))
+ smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0 &&
+ smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', callout)))
break;
HDEBUG(D_acl|D_v)
debug_printf_indent("problem after random/rset/mfrom; reopen conn\n");
random_local_part = NULL;
#ifdef SUPPORT_TLS
- tls_close(FALSE, TRUE);
+ tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
#endif
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
- (void)close(sx.inblock.sock);
- sx.inblock.sock = sx.outblock.sock = -1;
+ (void)close(sx.cctx.sock);
+ sx.cctx.sock = -1;
#ifndef DISABLE_EVENT
(void) event_raise(addr->transport->event_action,
US"tcp:close", NULL);
sx.send_rset = TRUE;
sx.completed_addr = FALSE;
goto tls_retry_connection;
+ case DEFER: /* 4xx response to random */
+ break; /* Just to be clear. ccache_unknown, !done. */
}
/* Re-setup for main verify, or for the error message when failing */
else
done = TRUE;
- /* Main verify. If the host is accepting all local parts, as determined
- by the "random" check, we don't need to waste time doing any further
- checking. */
+ /* Main verify. For rcpt-verify use SIZE if we know it and we're not cacheing;
+ for sndr-verify never use it. */
if (done)
{
+ if (!(options & vopt_is_recipient && options & vopt_callout_no_cache))
+ sx.avoid_option = OPTION_SIZE;
+
done = FALSE;
switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
{
case PENDING_OK: done = TRUE;
new_address_record.result = ccache_accept;
break;
- case FAIL: done = TRUE;
+ case FAIL: done = TRUE;
yield = FAIL;
*failure_ptr = US"recipient";
new_address_record.result = ccache_reject;
break;
- default: break;
+ default: break;
}
break;
/* Could possibly shift before main verify, just above, and be ok
for cutthrough. But no way to handle a subsequent rcpt, so just
refuse any */
- cancel_cutthrough_connection("postmaster verify");
+ cancel_cutthrough_connection(TRUE, US"postmaster verify");
HDEBUG(D_acl|D_v) debug_printf_indent("Cutthrough cancelled by presence of postmaster verify\n");
- done = smtp_write_command(&sx.outblock, FALSE, "RSET\r\n") >= 0
- && smtp_read_response(&sx.inblock, sx.buffer,
- sizeof(sx.buffer), '2', callout);
+ done = smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0
+ && smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', callout);
if (done)
{
sx.ok = FALSE;
sx.send_rset = TRUE;
sx.completed_addr = FALSE;
+ sx.avoid_option = OPTION_SIZE;
if( smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0
&& addr->transport_return == PENDING_OK
done = TRUE;
else
done = (options & vopt_callout_fullpm) != 0
- && smtp_write_command(&sx.outblock, FALSE,
+ && smtp_write_command(&sx, SCMD_FLUSH,
"RCPT TO:<postmaster>\r\n") >= 0
- && smtp_read_response(&sx.inblock, sx.buffer,
+ && smtp_read_response(&sx, sx.buffer,
sizeof(sx.buffer), '2', callout);
/* Sort out the cache record */
done = TRUE;
}
break;
+#endif
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ case ERRNO_REQUIRETLS:
+ addr->user_message = US"530 5.7.4 REQUIRETLS support required";
+ yield = FAIL;
+ done = TRUE;
+ break;
#endif
case ECONNREFUSED:
sx.send_quit = FALSE;
if (*sx.buffer == 0) Ustrcpy(sx.buffer, US"connection dropped");
/*XXX test here is ugly; seem to have a split of responsibility for
- building this message. Need to reationalise. Where is it done
+ building this message. Need to rationalise. Where is it done
before here, and when not?
Not == 5xx resp to MAIL on main-verify
*/
/* Cutthrough - on a successful connect and recipient-verify with
use-sender and we are 1st rcpt and have no cutthrough conn so far
- here is where we want to leave the conn open */
- if ( cutthrough.delivery
+ here is where we want to leave the conn open. Ditto for a lazy-close
+ verify. */
+
+ if (cutthrough.delivery)
+ {
+ if (addr->transport->filter_command)
+ {
+ cutthrough.delivery= FALSE;
+ HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n");
+ }
+#ifndef DISABLE_DKIM
+ if (ob->dkim.dkim_domain)
+ {
+ cutthrough.delivery= FALSE;
+ HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of DKIM signing\n");
+ }
+#endif
+#ifdef EXPERIMENTAL_ARC
+ if (ob->arc_sign)
+ {
+ cutthrough.delivery= FALSE;
+ HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of ARC signing\n");
+ }
+#endif
+ }
+
+ if ( (cutthrough.delivery || options & vopt_callout_hold)
&& rcpt_count == 1
&& done
&& yield == OK
== vopt_callout_recipsender
&& !random_local_part
&& !pm_mailfrom
- && cutthrough.fd < 0
+ && cutthrough.cctx.sock < 0
&& !sx.lmtp
)
{
- HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for cutthrough delivery\n");
-
- cutthrough.fd = sx.outblock.sock; /* We assume no buffer in use in the outblock */
- cutthrough.nrcpt = 1;
- cutthrough.interface = interface;
- cutthrough.host = *host;
- cutthrough.addr = *addr; /* Save the address_item for later logging */
- cutthrough.addr.next = NULL;
+ address_item * parent, * caddr;
+
+ HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for %s\n",
+ cutthrough.delivery
+ ? "cutthrough delivery" : "potential further verifies and delivery");
+
+ cutthrough.callout_hold_only = !cutthrough.delivery;
+ cutthrough.is_tls = tls_out.active.sock >= 0;
+ /* We assume no buffer in use in the outblock */
+ cutthrough.cctx = sx.cctx;
+ cutthrough.nrcpt = 1;
+ cutthrough.transport = addr->transport->name;
+ cutthrough.interface = interface;
+ cutthrough.snd_port = sending_port;
+ cutthrough.peer_options = smtp_peer_options;
+ cutthrough.host = *host;
+ {
+ int oldpool = store_pool;
+ store_pool = POOL_PERM;
+ cutthrough.snd_ip = string_copy(sending_ip_address);
+ cutthrough.host.name = string_copy(host->name);
+ cutthrough.host.address = string_copy(host->address);
+ store_pool = oldpool;
+ }
+
+ /* Save the address_item and parent chain for later logging */
+ cutthrough.addr = *addr;
+ cutthrough.addr.next = NULL;
cutthrough.addr.host_used = &cutthrough.host;
- if (addr->parent)
- *(cutthrough.addr.parent = store_get(sizeof(address_item))) =
- *addr->parent;
- ctblock.buffer = ctbuffer;
- ctblock.buffersize = sizeof(ctbuffer);
- ctblock.ptr = ctbuffer;
- /* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
- ctblock.sock = cutthrough.fd;
+ for (caddr = &cutthrough.addr, parent = addr->parent;
+ parent;
+ caddr = caddr->parent, parent = parent->parent)
+ *(caddr->parent = store_get(sizeof(address_item))) = *parent;
+
+ ctctx.outblock.buffer = ctbuffer;
+ ctctx.outblock.buffersize = sizeof(ctbuffer);
+ ctctx.outblock.ptr = ctbuffer;
+ /* ctctx.outblock.cmd_count = 0; ctctx.outblock.authenticating = FALSE; */
+ ctctx.outblock.cctx = &cutthrough.cctx;
}
else
{
- /* Ensure no cutthrough on multiple address verifies */
+ /* Ensure no cutthrough on multiple verifies that were incompatible */
if (options & vopt_callout_recipsender)
- cancel_cutthrough_connection("not usable for cutthrough");
+ cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
if (sx.send_quit)
{
- (void) smtp_write_command(&sx.outblock, FALSE, "QUIT\r\n");
+ (void) smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n");
/* Wait a short time for response, and discard it */
- smtp_read_response(&sx.inblock, sx.buffer, sizeof(sx.buffer),
- '2', 1);
+ smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', 1);
}
- if (sx.inblock.sock >= 0)
+ if (sx.cctx.sock >= 0)
{
#ifdef SUPPORT_TLS
- tls_close(FALSE, TRUE);
+ if (sx.cctx.tls_ctx)
+ {
+ tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ sx.cctx.tls_ctx = NULL;
+ }
#endif
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
- (void)close(sx.inblock.sock);
- sx.inblock.sock = sx.outblock.sock = -1;
+ (void)close(sx.cctx.sock);
+ sx.cctx.sock = -1;
#ifndef DISABLE_EVENT
(void) event_raise(addr->transport->event_action, US"tcp:close", NULL);
#endif
one was requested and a recipient-verify wasn't subsequently done.
*/
int
-open_cutthrough_connection( address_item * addr )
+open_cutthrough_connection(address_item * addr)
{
address_item addr2;
int rc;
static BOOL
cutthrough_send(int n)
{
-if(cutthrough.fd < 0)
+if(cutthrough.cctx.sock < 0)
return TRUE;
if(
#ifdef SUPPORT_TLS
- (tls_out.active == cutthrough.fd) ? tls_write(FALSE, ctblock.buffer, n) :
+ cutthrough.is_tls
+ ? tls_write(cutthrough.cctx.tls_ctx, ctctx.outblock.buffer, n, FALSE)
+ :
#endif
- send(cutthrough.fd, ctblock.buffer, n, 0) > 0
+ send(cutthrough.cctx.sock, ctctx.outblock.buffer, n, 0) > 0
)
{
transport_count += n;
- ctblock.ptr= ctblock.buffer;
+ ctctx.outblock.ptr= ctctx.outblock.buffer;
return TRUE;
}
{
while(n--)
{
- if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize)
- if(!cutthrough_send(ctblock.buffersize))
+ if(ctctx.outblock.ptr >= ctctx.outblock.buffer+ctctx.outblock.buffersize)
+ if(!cutthrough_send(ctctx.outblock.buffersize))
return FALSE;
- *ctblock.ptr++ = *cp++;
+ *ctctx.outblock.ptr++ = *cp++;
}
return TRUE;
}
/* Buffered output of counted data block. Return boolean success */
-BOOL
+static BOOL
cutthrough_puts(uschar * cp, int n)
{
-if (cutthrough.fd < 0) return TRUE;
-if (_cutthrough_puts(cp, n)) return TRUE;
-cancel_cutthrough_connection("transmit failed");
+if (cutthrough.cctx.sock < 0) return TRUE;
+if (_cutthrough_puts(cp, n)) return TRUE;
+cancel_cutthrough_connection(TRUE, US"transmit failed");
return FALSE;
}
+void
+cutthrough_data_puts(uschar * cp, int n)
+{
+if (cutthrough.delivery) (void) cutthrough_puts(cp, n);
+return;
+}
+
static BOOL
_cutthrough_flush_send(void)
{
-int n= ctblock.ptr-ctblock.buffer;
+int n = ctctx.outblock.ptr - ctctx.outblock.buffer;
if(n>0)
if(!cutthrough_send(n))
cutthrough_flush_send(void)
{
if (_cutthrough_flush_send()) return TRUE;
-cancel_cutthrough_connection("transmit failed");
+cancel_cutthrough_connection(TRUE, US"transmit failed");
return FALSE;
}
-BOOL
+static BOOL
cutthrough_put_nl(void)
{
return cutthrough_puts(US"\r\n", 2);
}
+void
+cutthrough_data_put_nl(void)
+{
+cutthrough_data_puts(US"\r\n", 2);
+}
+
+
/* Get and check response from cutthrough target */
static uschar
-cutthrough_response(char expect, uschar ** copy, int timeout)
+cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout)
{
-smtp_inblock inblock;
+smtp_context sx = {0};
uschar inbuffer[4096];
uschar responsebuffer[4096];
-inblock.buffer = inbuffer;
-inblock.buffersize = sizeof(inbuffer);
-inblock.ptr = inbuffer;
-inblock.ptrend = inbuffer;
-inblock.sock = cutthrough.fd;
-/* this relies on (inblock.sock == tls_out.active) */
-if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, timeout))
- cancel_cutthrough_connection("target timeout on read");
+sx.inblock.buffer = inbuffer;
+sx.inblock.buffersize = sizeof(inbuffer);
+sx.inblock.ptr = inbuffer;
+sx.inblock.ptrend = inbuffer;
+sx.inblock.cctx = cctx;
+if(!smtp_read_response(&sx, responsebuffer, sizeof(responsebuffer), expect, timeout))
+ cancel_cutthrough_connection(TRUE, US"target timeout on read");
-if(copy != NULL)
+if(copy)
{
uschar * cp;
*copy = cp = string_copy(responsebuffer);
BOOL
cutthrough_predata(void)
{
-if(cutthrough.fd < 0)
+if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only)
return FALSE;
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> DATA\n");
cutthrough_flush_send();
/* Assume nothing buffered. If it was it gets ignored. */
-return cutthrough_response('3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3';
+return cutthrough_response(&cutthrough.cctx, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) == '3';
}
-/* fd and tctx args only to match write_chunk() */
+/* tctx arg only to match write_chunk() */
static BOOL
-cutthrough_write_chunk(int fd, transport_ctx * tctx, uschar * s, int len)
+cutthrough_write_chunk(transport_ctx * tctx, uschar * s, int len)
{
uschar * s2;
while(s && (s2 = Ustrchr(s, '\n')))
{
transport_ctx tctx;
-if(cutthrough.fd < 0)
+if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only)
return FALSE;
/* We share a routine with the mainline transport to handle header add/remove/rewrites,
*/
HDEBUG(D_acl) debug_printf_indent("----------- start cutthrough headers send -----------\n");
+tctx.u.fd = cutthrough.cctx.sock;
tctx.tblock = cutthrough.addr.transport;
tctx.addr = &cutthrough.addr;
tctx.check_string = US".";
tctx.escape_string = US"..";
+/*XXX check under spool_files_wireformat. Might be irrelevant */
tctx.options = topt_use_crlf;
-if (!transport_headers_send(cutthrough.fd, &tctx, &cutthrough_write_chunk))
+if (!transport_headers_send(&tctx, &cutthrough_write_chunk))
return FALSE;
HDEBUG(D_acl) debug_printf_indent("----------- done cutthrough headers send ------------\n");
static void
-close_cutthrough_connection(const char * why)
+close_cutthrough_connection(const uschar * why)
{
-if(cutthrough.fd >= 0)
+int fd = cutthrough.cctx.sock;
+if(fd >= 0)
{
/* We could be sending this after a bunch of data, but that is ok as
the only way to cancel the transfer in dataphase is to drop the tcp
conn before the final dot.
*/
- ctblock.ptr = ctbuffer;
+ client_conn_ctx tmp_ctx = cutthrough.cctx;
+ ctctx.outblock.ptr = ctbuffer;
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n");
_cutthrough_puts(US"QUIT\r\n", 6); /* avoid recursion */
_cutthrough_flush_send();
+ cutthrough.cctx.sock = -1; /* avoid recursion via read timeout */
+ cutthrough.nrcpt = 0; /* permit re-cutthrough on subsequent message */
/* Wait a short time for response, and discard it */
- cutthrough_response('2', NULL, 1);
+ cutthrough_response(&tmp_ctx, '2', NULL, 1);
- #ifdef SUPPORT_TLS
- tls_close(FALSE, TRUE);
- #endif
+#ifdef SUPPORT_TLS
+ if (cutthrough.is_tls)
+ {
+ tls_close(cutthrough.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ cutthrough.cctx.tls_ctx = NULL;
+ cutthrough.is_tls = FALSE;
+ }
+#endif
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
- (void)close(cutthrough.fd);
- cutthrough.fd = -1;
+ (void)close(fd);
HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why);
}
-ctblock.ptr = ctbuffer;
+ctctx.outblock.ptr = ctbuffer;
}
void
-cancel_cutthrough_connection(const char * why)
+cancel_cutthrough_connection(BOOL close_noncutthrough_verifies, const uschar * why)
{
-close_cutthrough_connection(why);
-cutthrough.delivery = FALSE;
+if (cutthrough.delivery || close_noncutthrough_verifies)
+ close_cutthrough_connection(why);
+cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
+}
+
+
+void
+release_cutthrough_connection(const uschar * why)
+{
+if (cutthrough.cctx.sock < 0) return;
+HDEBUG(D_acl) debug_printf_indent("release cutthrough conn: %s\n", why);
+cutthrough.cctx.sock = -1;
+cutthrough.cctx.tls_ctx = NULL;
+cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
}
)
return cutthrough.addr.message;
-res = cutthrough_response('2', &cutthrough.addr.message, CUTTHROUGH_DATA_TIMEOUT);
+res = cutthrough_response(&cutthrough.cctx, '2', &cutthrough.addr.message,
+ CUTTHROUGH_DATA_TIMEOUT);
for (addr = &cutthrough.addr; addr; addr = addr->next)
{
addr->message = cutthrough.addr.message;
{
case '2':
delivery_log(LOG_MAIN, addr, (int)'>', NULL);
- close_cutthrough_connection("delivered");
+ close_cutthrough_connection(US"delivered");
break;
case '4':
va_start(ap, format);
if (smtp_out && (f == smtp_out))
- smtp_vprintf(format, ap);
+ smtp_vprintf(format, FALSE, ap);
else
vfprintf(f, format, ap);
va_end(ap);
*/
int
-verify_address(address_item *vaddr, FILE *f, int options, int callout,
- int callout_overall, int callout_connect, uschar *se_mailfrom,
+verify_address(address_item * vaddr, FILE * fp, int options, int callout,
+ int callout_overall, int callout_connect, uschar * se_mailfrom,
uschar *pm_mailfrom, BOOL *routed)
{
BOOL allok = TRUE;
-BOOL full_info = (f == NULL)? FALSE : (debug_selector != 0);
+BOOL full_info = fp ? debug_selector != 0 : FALSE;
BOOL expn = (options & vopt_expn) != 0;
BOOL success_on_redirect = (options & vopt_success_on_redirect) != 0;
int i;
int yield = OK;
int verify_type = expn? v_expn :
- address_test_mode? v_none :
+ f.address_test_mode? v_none :
options & vopt_is_recipient? v_recipient : v_sender;
address_item *addr_list;
address_item *addr_new = NULL;
if (parse_find_at(address) == NULL)
{
- if ((options & vopt_qualify) == 0)
+ if (!(options & vopt_qualify))
{
- if (f != NULL)
- respond_printf(f, "%sA domain is required for \"%s\"%s\n",
+ if (fp)
+ respond_printf(fp, "%sA domain is required for \"%s\"%s\n",
ko_prefix, address, cr);
*failure_ptr = US"qualify";
return FAIL;
DEBUG(D_verify)
{
debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
- debug_printf("%s %s\n", address_test_mode? "Testing" : "Verifying", address);
+ debug_printf("%s %s\n", f.address_test_mode? "Testing" : "Verifying", address);
}
/* Rewrite and report on it. Clear the domain and local part caches - these
may have been set by domains and local part tests during an ACL. */
-if (global_rewrite_rules != NULL)
+if (global_rewrite_rules)
{
uschar *old = address;
address = rewrite_address(address, options & vopt_is_recipient, FALSE,
{
for (i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->localpart_cache[i] = 0;
for (i = 0; i < (MAX_NAMED_LIST * 2)/32; i++) vaddr->domain_cache[i] = 0;
- if (f != NULL && !expn) fprintf(f, "Address rewritten as: %s\n", address);
+ if (fp && !expn) fprintf(fp, "Address rewritten as: %s\n", address);
}
}
/* If this is the real sender address, we must update sender_address at
this point, because it may be referred to in the routers. */
-if ((options & (vopt_fake_sender|vopt_is_recipient)) == 0)
+if (!(options & (vopt_fake_sender|vopt_is_recipient)))
sender_address = address;
/* If the address was rewritten to <> no verification can be done, and we have
to return OK. This rewriting is permitted only for sender addresses; for other
addresses, such rewriting fails. */
-if (address[0] == 0) return OK;
+if (!address[0]) return OK;
/* Flip the legacy TLS-related variables over to the outbound set in case
they're used in the context of a transport used by verification. Reset them
if (testflag(addr, af_pfr))
{
allok = FALSE;
- if (f != NULL)
+ if (fp)
{
BOOL allow;
if (addr->address[0] == '>')
{
allow = testflag(addr, af_allow_reply);
- fprintf(f, "%s -> mail %s", addr->parent->address, addr->address + 1);
+ fprintf(fp, "%s -> mail %s", addr->parent->address, addr->address + 1);
}
else
{
- allow = (addr->address[0] == '|')?
- testflag(addr, af_allow_pipe) : testflag(addr, af_allow_file);
- fprintf(f, "%s -> %s", addr->parent->address, addr->address);
+ allow = addr->address[0] == '|'
+ ? testflag(addr, af_allow_pipe) : testflag(addr, af_allow_file);
+ fprintf(fp, "%s -> %s", addr->parent->address, addr->address);
}
if (addr->basic_errno == ERRNO_BADTRANSPORT)
- fprintf(f, "\n*** Error in setting up pipe, file, or autoreply:\n"
+ fprintf(fp, "\n*** Error in setting up pipe, file, or autoreply:\n"
"%s\n", addr->message);
else if (allow)
- fprintf(f, "\n transport = %s\n", addr->transport->name);
+ fprintf(fp, "\n transport = %s\n", addr->transport->name);
else
- fprintf(f, " *** forbidden ***\n");
+ fprintf(fp, " *** forbidden ***\n");
}
continue;
}
transport. */
transport_feedback tf = {
- NULL, /* interface (=> any) */
- US"smtp", /* port */
- US"smtp", /* protocol */
- NULL, /* hosts */
- US"$smtp_active_hostname", /* helo_data */
- FALSE, /* hosts_override */
- FALSE, /* hosts_randomize */
- FALSE, /* gethostbyname */
- TRUE, /* qualify_single */
- FALSE /* search_parents */
+ .interface = NULL, /* interface (=> any) */
+ .port = US"smtp",
+ .protocol = US"smtp",
+ .hosts = NULL,
+ .helo_data = US"$smtp_active_hostname",
+ .hosts_override = FALSE,
+ .hosts_randomize = FALSE,
+ .gethostbyname = FALSE,
+ .qualify_single = TRUE,
+ .search_parents = FALSE
};
/* If verification yielded a remote transport, we want to use that
additional host items being inserted into the chain. Hence we must
save the next host first. */
- flags = HOST_FIND_BY_A;
+ flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
(void)host_find_byname(host, NULL, flags, NULL, TRUE);
else
{
- dnssec_domains * dnssec_domains = NULL;
+ const dnssec_domains * dsp = NULL;
if (Ustrcmp(tp->driver_name, "smtp") == 0)
{
smtp_transport_options_block * ob =
(smtp_transport_options_block *) tp->options_block;
- dnssec_domains = &ob->dnssec;
+ dsp = &ob->dnssec;
}
- (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
- dnssec_domains, NULL, NULL);
+ (void) host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
+ dsp, NULL, NULL);
}
}
}
if (host_list)
{
HDEBUG(D_verify) debug_printf("Attempting full verification using callout\n");
- if (host_checking && !host_checking_callout)
+ if (host_checking && !f.host_checking_callout)
{
HDEBUG(D_verify)
debug_printf("... callout omitted by default when host testing\n"
#endif
rc = do_callout(addr, host_list, &tf, callout, callout_overall,
callout_connect, options, se_mailfrom, pm_mailfrom);
+#ifdef SUPPORT_TLS
+ deliver_set_expansions(NULL);
+#endif
}
}
else
{
HDEBUG(D_verify) debug_printf("Cannot do callout: neither router nor "
- "transport provided a host list\n");
+ "transport provided a host list, or transport is not smtp\n");
}
}
}
if (rc == FAIL)
{
allok = FALSE;
- if (f)
+ if (fp)
{
address_item *p = addr->parent;
- respond_printf(f, "%s%s %s", ko_prefix,
+ respond_printf(fp, "%s%s %s", ko_prefix,
full_info ? addr->address : address,
- address_test_mode ? "is undeliverable" : "failed to verify");
- if (!expn && admin_user)
+ f.address_test_mode ? "is undeliverable" : "failed to verify");
+ if (!expn && f.admin_user)
{
if (addr->basic_errno > 0)
- respond_printf(f, ": %s", strerror(addr->basic_errno));
+ respond_printf(fp, ": %s", strerror(addr->basic_errno));
if (addr->message)
- respond_printf(f, ": %s", addr->message);
+ respond_printf(fp, ": %s", addr->message);
}
/* Show parents iff doing full info */
if (full_info) while (p)
{
- respond_printf(f, "%s\n <-- %s", cr, p->address);
+ respond_printf(fp, "%s\n <-- %s", cr, p->address);
p = p->parent;
}
- respond_printf(f, "%s\n", cr);
+ respond_printf(fp, "%s\n", cr);
}
- cancel_cutthrough_connection("routing hard fail");
+ cancel_cutthrough_connection(TRUE, US"routing hard fail");
if (!full_info)
{
else if (rc == DEFER)
{
allok = FALSE;
- if (f)
+ if (fp)
{
address_item *p = addr->parent;
- respond_printf(f, "%s%s cannot be resolved at this time", ko_prefix,
+ respond_printf(fp, "%s%s cannot be resolved at this time", ko_prefix,
full_info? addr->address : address);
- if (!expn && admin_user)
+ if (!expn && f.admin_user)
{
if (addr->basic_errno > 0)
- respond_printf(f, ": %s", strerror(addr->basic_errno));
+ respond_printf(fp, ": %s", strerror(addr->basic_errno));
if (addr->message)
- respond_printf(f, ": %s", addr->message);
+ respond_printf(fp, ": %s", addr->message);
else if (addr->basic_errno <= 0)
- respond_printf(f, ": unknown error");
+ respond_printf(fp, ": unknown error");
}
/* Show parents iff doing full info */
if (full_info) while (p)
{
- respond_printf(f, "%s\n <-- %s", cr, p->address);
+ respond_printf(fp, "%s\n <-- %s", cr, p->address);
p = p->parent;
}
- respond_printf(f, "%s\n", cr);
+ respond_printf(fp, "%s\n", cr);
}
- cancel_cutthrough_connection("routing soft fail");
+ cancel_cutthrough_connection(TRUE, US"routing soft fail");
if (!full_info)
{
if (!addr_new)
if (!addr_local && !addr_remote)
- respond_printf(f, "250 mail to <%s> is discarded\r\n", address);
+ respond_printf(fp, "250 mail to <%s> is discarded\r\n", address);
else
- respond_printf(f, "250 <%s>\r\n", address);
+ respond_printf(fp, "250 <%s>\r\n", address);
else do
{
address_item *addr2 = addr_new;
addr_new = addr2->next;
if (!addr_new) ok_prefix = US"250 ";
- respond_printf(f, "%s<%s>\r\n", ok_prefix, addr2->address);
+ respond_printf(fp, "%s<%s>\r\n", ok_prefix, addr2->address);
} while (addr_new);
yield = OK;
goto out;
) )
)
{
- if (f) fprintf(f, "%s %s\n",
- address, address_test_mode ? "is deliverable" : "verified");
+ if (fp) fprintf(fp, "%s %s\n",
+ address, f.address_test_mode ? "is deliverable" : "verified");
/* If we have carried on to verify a child address, we want the value
of $address_data to be that of the child */
/* If stopped because more than one new address, cannot cutthrough */
if (addr_new && addr_new->next)
- cancel_cutthrough_connection("multiple addresses from routing");
+ cancel_cutthrough_connection(TRUE, US"multiple addresses from routing");
yield = OK;
goto out;
} /* Loop for generated addresses */
/* Display the full results of the successful routing, including any generated
-addresses. Control gets here only when full_info is set, which requires f not
+addresses. Control gets here only when full_info is set, which requires fp not
to be NULL, and this occurs only when a top-level verify is called with the
debugging switch on.
if (allok && !addr_local && !addr_remote)
{
- fprintf(f, "mail to %s is discarded\n", address);
+ fprintf(fp, "mail to %s is discarded\n", address);
goto out;
}
addr_list = addr->next;
- fprintf(f, "%s", CS addr->address);
+ fprintf(fp, "%s", CS addr->address);
#ifdef EXPERIMENTAL_SRS
if(addr->prop.srs_sender)
- fprintf(f, " [srs = %s]", addr->prop.srs_sender);
+ fprintf(fp, " [srs = %s]", addr->prop.srs_sender);
#endif
/* If the address is a duplicate, show something about it. */
{
tree_node *tnode;
if ((tnode = tree_search(tree_duplicates, addr->unique)))
- fprintf(f, " [duplicate, would not be delivered]");
+ fprintf(fp, " [duplicate, would not be delivered]");
else tree_add_duplicate(addr->unique, addr);
}
/* Now show its parents */
for (p = addr->parent; p; p = p->parent)
- fprintf(f, "\n <-- %s", p->address);
- fprintf(f, "\n ");
+ fprintf(fp, "\n <-- %s", p->address);
+ fprintf(fp, "\n ");
/* Show router, and transport */
- fprintf(f, "router = %s, transport = %s\n",
+ fprintf(fp, "router = %s, transport = %s\n",
addr->router->name, tp ? tp->name : US"unset");
/* Show any hosts that are set up by a router unless the transport
}
for (h = addr->host_list; h; h = h->next)
{
- fprintf(f, " host %-*s ", maxlen, h->name);
+ fprintf(fp, " host %-*s ", maxlen, h->name);
if (h->address)
- fprintf(f, "[%s%-*c", h->address, maxaddlen+1 - Ustrlen(h->address), ']');
+ fprintf(fp, "[%s%-*c", h->address, maxaddlen+1 - Ustrlen(h->address), ']');
else if (tp->info->local)
- fprintf(f, " %-*s ", maxaddlen, ""); /* Omit [unknown] for local */
+ fprintf(fp, " %-*s ", maxaddlen, ""); /* Omit [unknown] for local */
else
- fprintf(f, "[%s%-*c", "unknown", maxaddlen+1 - 7, ']');
+ fprintf(fp, "[%s%-*c", "unknown", maxaddlen+1 - 7, ']');
- if (h->mx >= 0) fprintf(f, " MX=%d", h->mx);
- if (h->port != PORT_NONE) fprintf(f, " port=%d", h->port);
- if (running_in_test_harness && h->dnssec == DS_YES) fputs(" AD", f);
- if (h->status == hstatus_unusable) fputs(" ** unusable **", f);
- fputc('\n', f);
+ if (h->mx >= 0) fprintf(fp, " MX=%d", h->mx);
+ if (h->port != PORT_NONE) fprintf(fp, " port=%d", h->port);
+ if (f.running_in_test_harness && h->dnssec == DS_YES) fputs(" AD", fp);
+ if (h->status == hstatus_unusable) fputs(" ** unusable **", fp);
+ fputc('\n', fp);
}
}
}
*************************************************/
/* This function checks those header lines that contain addresses, and verifies
-that all the addresses therein are syntactially correct.
+that all the addresses therein are 5322-syntactially correct.
Arguments:
msgptr where to put an error message
uschar *colon, *s;
int yield = OK;
-for (h = header_list; h != NULL && yield == OK; h = h->next)
+for (h = header_list; h && yield == OK; h = h->next)
{
if (h->type != htype_from &&
h->type != htype_reply_to &&
/* Loop for multiple addresses in the header, enabling group syntax. Note
that we have to reset this after the header has been scanned. */
- parse_allow_group = TRUE;
+ f.parse_allow_group = TRUE;
- while (*s != 0)
+ while (*s)
{
uschar *ss = parse_find_address_end(s, FALSE);
uschar *recipient, *errmess;
/* Permit an unqualified address only if the message is local, or if the
sending host is configured to be permitted to send them. */
- if (recipient != NULL && domain == 0)
+ if (recipient && !domain)
{
if (h->type == htype_from || h->type == htype_sender)
{
- if (!allow_unqualified_sender) recipient = NULL;
+ if (!f.allow_unqualified_sender) recipient = NULL;
}
else
{
- if (!allow_unqualified_recipient) recipient = NULL;
+ if (!f.allow_unqualified_recipient) recipient = NULL;
}
if (recipient == NULL) errmess = US"unqualified address not permitted";
}
/* It's an error if no address could be extracted, except for the special
case of an empty address. */
- if (recipient == NULL && Ustrcmp(errmess, "empty address") != 0)
+ if (!recipient && Ustrcmp(errmess, "empty address") != 0)
{
uschar *verb = US"is";
uschar *t = ss;
/* deconst cast ok as we're passing a non-const to string_printing() */
*msgptr = US string_printing(
string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s",
- errmess, tt - h->text, h->text, verb, len, s));
+ errmess, (int)(tt - h->text), h->text, verb, len, s));
yield = FAIL;
break; /* Out of address loop */
/* Advance to the next address */
- s = ss + (terminator? 1:0);
+ s = ss + (terminator ? 1 : 0);
while (isspace(*s)) s++;
} /* Next address */
- parse_allow_group = FALSE;
- parse_found_group = FALSE;
+ f.parse_allow_group = FALSE;
+ f.parse_found_group = FALSE;
} /* Next header unless yield has been set FALSE */
return yield;
header_line *h;
uschar *colon, *s;
-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h; h = h->next)
{
- colon = Ustrchr(h->text, ':');
- for(s = h->text; s < colon; s++)
- {
- if ((*s < 33) || (*s > 126))
- {
- *msgptr = string_sprintf("Invalid character in header \"%.*s\" found",
- colon - h->text, h->text);
- return FAIL;
- }
- }
+ colon = Ustrchr(h->text, ':');
+ for(s = h->text; s < colon; s++)
+ if ((*s < 33) || (*s > 126))
+ {
+ *msgptr = string_sprintf("Invalid character in header \"%.*s\" found",
+ colon - h->text, h->text);
+ return FAIL;
+ }
}
return OK;
}
/* Loop for multiple addresses in the header, enabling group syntax. Note
that we have to reset this after the header has been scanned. */
- parse_allow_group = TRUE;
+ f.parse_allow_group = TRUE;
while (*s != 0)
{
while (isspace(*s)) s++;
} /* Next address */
- parse_allow_group = FALSE;
- parse_found_group = FALSE;
+ f.parse_allow_group = FALSE;
+ f.parse_found_group = FALSE;
} /* Next header (if found is false) */
if (!found) return FAIL;
/* Scan the addresses in the header, enabling group syntax. Note that we
have to reset this after the header has been scanned. */
- parse_allow_group = TRUE;
+ f.parse_allow_group = TRUE;
while (*s != 0)
{
while (ss > s && isspace(ss[-1])) ss--;
*log_msgptr = string_sprintf("syntax error in '%.*s' header when "
"scanning for sender: %s in \"%.*s\"",
- endname - h->text, h->text, *log_msgptr, ss - s, s);
+ (int)(endname - h->text), h->text, *log_msgptr, (int)(ss - s), s);
yield = FAIL;
done = TRUE;
break;
{
*verrno = vaddr->basic_errno;
if (smtp_return_error_details)
- {
*user_msgptr = string_sprintf("Rejected after DATA: "
"could not verify \"%.*s\" header address\n%s: %s",
- endname - h->text, h->text, vaddr->address, vaddr->message);
- }
+ (int)(endname - h->text), h->text, vaddr->address, vaddr->message);
}
/* Success or defer */
s = ss;
} /* Next address */
- parse_allow_group = FALSE;
- parse_found_group = FALSE;
+ f.parse_allow_group = FALSE;
+ f.parse_found_group = FALSE;
} /* Next header, unless done */
} /* Next header type unless done */
void
verify_get_ident(int port)
{
-int sock, host_af, qlen;
+client_conn_ctx ident_conn_ctx = {0};
+int host_af, qlen;
int received_sender_port, received_interface_port, n;
uschar *p;
+blob early_data;
uschar buffer[2048];
/* Default is no ident. Check whether we want to do an ident check for this
address, the incoming interface address will also be IPv6. */
host_af = Ustrchr(sender_host_address, ':') == NULL ? AF_INET : AF_INET6;
-if ((sock = ip_socket(SOCK_STREAM, host_af)) < 0) return;
+if ((ident_conn_ctx.sock = ip_socket(SOCK_STREAM, host_af)) < 0) return;
-if (ip_bind(sock, host_af, interface_address, 0) < 0)
+if (ip_bind(ident_conn_ctx.sock, host_af, interface_address, 0) < 0)
{
DEBUG(D_ident) debug_printf("bind socket for ident failed: %s\n",
strerror(errno));
goto END_OFF;
}
-if (ip_connect(sock, host_af, sender_host_address, port,
- rfc1413_query_timeout, TRUE) < 0)
+/* Construct and send the query. */
+
+qlen = snprintf(CS buffer, sizeof(buffer), "%d , %d\r\n",
+ sender_host_port, interface_port);
+early_data.data = buffer;
+early_data.len = qlen;
+
+/*XXX we trust that the query is idempotent */
+if (ip_connect(ident_conn_ctx.sock, host_af, sender_host_address, port,
+ rfc1413_query_timeout, &early_data) < 0)
{
if (errno == ETIMEDOUT && LOGGING(ident_timeout))
log_write(0, LOG_MAIN, "ident connection to %s timed out",
goto END_OFF;
}
-/* Construct and send the query. */
-
-sprintf(CS buffer, "%d , %d\r\n", sender_host_port, interface_port);
-qlen = Ustrlen(buffer);
-if (send(sock, buffer, qlen, 0) < 0)
- {
- DEBUG(D_ident) debug_printf("ident send failed: %s\n", strerror(errno));
- goto END_OFF;
- }
-
/* Read a response line. We put it into the rest of the buffer, using several
recv() calls if necessary. */
int size = sizeof(buffer) - (p - buffer);
if (size <= 0) goto END_OFF; /* Buffer filled without seeing \n. */
- count = ip_recv(sock, p, size, rfc1413_query_timeout);
+ count = ip_recv(&ident_conn_ctx, p, size, rfc1413_query_timeout);
if (count <= 0) goto END_OFF; /* Read error or EOF */
/* Scan what we just read, to see if we have reached the terminating \r\n. Be
DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident);
END_OFF:
-(void)close(sock);
+(void)close(ident_conn_ctx.sock);
return;
}
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", search_error_message);
result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL);
- if (valueptr != NULL) *valueptr = result;
- return (result != NULL)? OK : search_find_defer? DEFER: FAIL;
+ if (valueptr) *valueptr = result;
+ return result ? OK : f.search_find_defer ? DEFER: FAIL;
}
/* The pattern is not an IP address or network reference of any kind. That is,
/* Not a query-style lookup; must ensure the host name is present, and then we
do a check on the name and all its aliases. */
-if (sender_host_name == NULL)
+if (!sender_host_name)
{
HDEBUG(D_host_lookup)
debug_printf("sender host name required, to match against %s\n", ss);
/* Match on the sender host name, using the general matching function */
-switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE,
- valueptr))
+switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE, valueptr))
{
case OK: return OK;
case DEFER: return DEFER;
/* If there are aliases, try matching on them. */
aliases = sender_host_aliases;
-while (*aliases != NULL)
- {
+while (*aliases)
switch(match_check_string(*aliases++, ss, -1, TRUE, TRUE, TRUE, valueptr))
{
case OK: return OK;
case DEFER: return DEFER;
}
- }
return FAIL;
}
int rc;
unsigned int *local_cache_bits = cache_bits;
const uschar *save_host_address = deliver_host_address;
-check_host_block cb;
-cb.host_name = host_name;
-cb.host_address = host_address;
+check_host_block cb = { .host_name = host_name, .host_address = host_address };
-if (valueptr != NULL) *valueptr = NULL;
+if (valueptr) *valueptr = NULL;
/* If the host address starts off ::ffff: it is an IPv6 address in
IPv4-compatible mode. Find the IPv4 part for checking against IPv4
addresses. */
-cb.host_ipv4 = (Ustrncmp(host_address, "::ffff:", 7) == 0)?
- host_address + 7 : host_address;
+cb.host_ipv4 = Ustrncmp(host_address, "::ffff:", 7) == 0
+ ? host_address + 7 : host_address;
/* During the running of the check, put the IP address into $host_address. In
the case of calls from the smtp transport, it will already be there. However,
* Check the given host item matches a list *
*************************************************/
int
-verify_check_given_host(uschar **listptr, host_item *host)
+verify_check_given_host(const uschar **listptr, const host_item *host)
{
-return verify_check_this_host(CUSS listptr, NULL, host->name, host->address, NULL);
+return verify_check_this_host(listptr, NULL, host->name, host->address, NULL);
}
/*************************************************
/* If the lookup succeeded, cache the RHS address. The code allows for
more than one address - this was for complete generality and the possible
- use of A6 records. However, A6 records have been reduced to experimental
- status (August 2001) and may die out. So they may never get used at all,
- let alone in dnsbl records. However, leave the code here, just in case.
+ use of A6 records. However, A6 records are no longer supported. Leave the code
+ here, just in case.
Quite apart from one A6 RR generating multiple addresses, there are DNS
lists that return more than one A record, so we must handle multiple
for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
rr;
rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
- {
if (rr->type == T_A)
{
dns_address *da = dns_address_from_rr(&dnsa, rr);
if (da)
{
*addrp = da;
- while (da->next != NULL) da = da->next;
- addrp = &(da->next);
+ while (da->next) da = da->next;
+ addrp = &da->next;
if (ttl > rr->ttl) ttl = rr->ttl;
}
}
- }
/* If we didn't find any A records, change the return code. This can
happen when there is a CNAME record but there are no A records for what
it points to. */
- if (cb->rhs == NULL) cb->rc = DNS_NODATA;
+ if (!cb->rhs) cb->rc = DNS_NODATA;
}
cb->expiry = time(NULL)+ttl;
records. For A6 records (currently not expected to be used) there may be
multiple addresses from a single record. */
- for (da = cb->rhs->next; da != NULL; da = da->next)
+ for (da = cb->rhs->next; da; da = da->next)
addlist = string_sprintf("%s, %s", addlist, da->address);
HDEBUG(D_dnsbl) debug_printf("DNS lookup for %s succeeded (yielding %s)\n",
/* Address list check; this can be either for equality, or via a bitmask.
In the latter case, all the bits must match. */
- if (iplist != NULL)
+ if (iplist)
{
- for (da = cb->rhs; da != NULL; da = da->next)
+ for (da = cb->rhs; da; da = da->next)
{
int ipsep = ',';
uschar ip[46];
/* Handle exact matching */
if (!bitmask)
- {
- while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL)
- {
- if (Ustrcmp(CS da->address, ip) == 0) break;
- }
- }
+ {
+ while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))))
+ if (Ustrcmp(CS da->address, ip) == 0)
+ break;
+ }
/* Handle bitmask matching */
/* Scan the returned addresses, skipping any that are IPv6 */
- while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL)
+ while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))))
{
if (host_aton(ip, address) != 1) continue;
if ((address[0] & mask) == address[0]) break;
switch(match_type)
{
case 0:
- res = US"was no match";
- break;
+ res = US"was no match"; break;
case MT_NOT:
- res = US"was an exclude match";
- break;
+ res = US"was an exclude match"; break;
case MT_ALL:
- res = US"was an IP address that did not match";
- break;
+ res = US"was an IP address that did not match"; break;
case MT_NOT|MT_ALL:
- res = US"were no IP addresses that did not match";
- break;
+ res = US"were no IP addresses that did not match"; break;
}
debug_printf("=> but we are not accepting this block class because\n");
debug_printf("=> there %s for %s%c%s\n",
{
dns_record *rr;
for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
- rr != NULL;
+ rr;
rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
if (rr->type == T_TXT) break;
- if (rr != NULL)
+ if (rr)
{
int len = (rr->data)[0];
if (len > 511) len = 127;
store_pool = POOL_PERM;
- cb->text = string_sprintf("%.*s", len, (const uschar *)(rr->data+1));
+ cb->text = string_sprintf("%.*s", len, CUS (rr->data+1));
store_pool = old_pool;
}
}
*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) The Exim Maintainers 2010 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Function for setting up the version string. */
sprintf(CS version_cnumber, CS version_cnumber_format, cnumber);
version_string = US EXIM_VERSION_STR "\0<<eximversion>>";
+#ifdef EXIM_BUILD_DATE_OVERRIDE
+/* Reproducible build support; build tooling should have given us something looking like
+ * "25-Feb-2017 20:15:40" in EXIM_BUILD_DATE_OVERRIDE based on $SOURCE_DATE_EPOCH in environ
+ * per <https://reproducible-builds.org/specs/source-date-epoch/>
+ */
+version_date = date_buffer;
+version_date[0] = 0;
+Ustrncat(version_date, EXIM_BUILD_DATE_OVERRIDE, sizeof(date_buffer));
+
+#else
Ustrcpy(today, __DATE__);
if (today[4] == ' ') today[4] = '0';
today[3] = today[6] = '-';
Ustrncat(version_date, today+7, 4);
Ustrcat(version_date, " ");
Ustrcat(version_date, __TIME__);
+#endif
}
/* End of version.c */
--- /dev/null
+/* automatically generated file - see ../scripts/reversion */
+#define EXIM_RELEASE_VERSION "4.92"
+#ifdef EXIM_VARIANT_VERSION
+#define EXIM_VERSION_STR EXIM_RELEASE_VERSION "-" EXIM_VARIANT_VERSION
+#else
+#define EXIM_VERSION_STR EXIM_RELEASE_VERSION
+#endif
-# initial version automatically generated from ./release-process/scripts/mk_exim_release
-EXIM_RELEASE_VERSION=4.89
-EXIM_VARIANT_VERSION=
-EXIM_COMPILE_NUMBER=0
+# automatically generated file - see ../scripts/reversion
+EXIM_RELEASE_VERSION="4.92"
+EXIM_COMPILE_NUMBER="1"
--- /dev/null
+#!/bin/sh -eu
+#
+# Short version of this script:
+# curl -f -o /var/cache/exim/opendmarc.tlds https://publicsuffix.org/list/public_suffix_list.dat
+# but run as Exim runtime user, writing to a place it can write to, and with
+# sanity checks and atomic replacement.
+#
+# For now, we deliberately leave the invalid file around for analysis
+# with .<pid> suffix.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~8< cut here >8~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+# Create a cron-job as the Exim run-time user to invoke this daily, with a
+# single parameter, 'cron'. Eg:
+#
+# 3 4 * * * /usr/local/sbin/renew-opendmarc-tlds.sh cron
+#
+# That will, at 3 minutes past the 4th hour (in whatever timezone cron is
+# running it) invoke this script with 'cron'; we will then sleep between 10 and
+# 50 seconds, before continuing.
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~8< cut here >8~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+# This should be "pretty portable"; the only things it depends upon are:
+# * a POSIX shell which additionally implements 'local' (dash works)
+# * the 'curl' command; change the fetch_candidate() function to replace that
+# * the 'stat' command, to get the size of a file; else Perl
+# + change size_of() if need be; it's defined per-OS
+# * the 'hexdump' command and /dev/urandom existing
+# + used when invoked with 'cron', to avoid retrieving on a minute boundary
+# and contending with many other automated systems.
+# + with bash/zsh, can replace with: $(( 10 + ( RANDOM % 40 ) ))
+# + on Debian/Ubuntu systems, hexdump is in the 'bsdmainutils' package.
+
+# Consider putting an email address inside the parentheses, something like
+# noc@example.org or other reachable address, so that if something goes wrong
+# and the server operators need to step in, they can see from logs who to
+# contact instead of just blocking your IP:
+readonly CurlUserAgent='renew-opendmarc-tlds/0.1 (distributed with Exim)'
+
+# change this to your Exim run-time user (exim -n -bP exim_user) :
+readonly RuntimeUser='_exim'
+
+# Do not make this a directory which untrusted users can write to:
+readonly StateDir='/var/cache/exim'
+
+readonly URL='https://publicsuffix.org/list/public_suffix_list.dat'
+
+readonly TargetShortFile='opendmarc.tlds'
+
+# When replacing, new file must be at least this percentage the size of
+# the old one or it's an error:
+readonly MinNewSizeRation=90
+
+# Each of these regexps must be matched by the file, or it's an error:
+readonly MustExistRegexps='
+ ^ac\.uk$
+ ^org$
+ ^tech$
+ '
+
+# =======================8< end of configuration >8=======================
+
+set -eu
+
+readonly FullTargetPath="${StateDir}/${TargetShortFile}"
+readonly WorkingFile="${FullTargetPath}.$$"
+
+progname="$(basename "$0")"
+note() { printf >&2 '%s: %s\n' "$progname" "$*"; }
+die() { note "$@"; exit 1; }
+
+# guard against stomping on file-permissions
+[ ".$(id -un)" = ".${RuntimeUser:?}" ] || \
+ die "must be invoked as ${RuntimeUser}"
+
+fetch_candidate() {
+ curl --user-agent "$CurlUserAgent" -fSs -o "${WorkingFile}" "${URL}"
+}
+
+case $(uname -s) in
+*BSD|Darwin)
+ size_of() { stat -f %z "$1"; }
+ ;;
+Linux)
+ size_of() { stat -c %s "$1"; }
+ ;;
+*)
+ # why do we live in a world where Perl is the safe portable solution
+ # to getting the size of a file?
+ size_of() { perl -le 'print((stat($ARGV[0]))[7])' -- "$1"; }
+ ;;
+esac
+
+sanity_check_candidate() {
+ local new_size prev_size re
+ new_size="$(size_of "$WorkingFile")"
+
+ for re in $MustExistRegexps; do
+ grep -qs "$re" -- "$WorkingFile" || \
+ die "regexp $re not found in $WorkingFile"
+ done
+
+ if ! prev_size="$(size_of "$FullTargetPath")"; then
+ note "missing previous file, can't size-compare: $FullTargetPath"
+ # We're sane by definition, probably initial fetch, and the
+ # stat failure and this note will be printed. That's fine; if
+ # a cron invocation is missing the file then something has gone
+ # badly wrong.
+ return 0
+ fi
+ local ratio
+ ratio=$(expr $new_size \* 100 / $prev_size)
+ if [ $ratio -lt $MinNewSizeRation ]; then
+ die "New $TargetShortFile candidate only ${ratio}% size of old; $new_size vs $prev_size"
+ fi
+}
+
+if [ "${1:-.}" = "cron" ]; then
+ shift
+ # Don't pull on-the-minute, wait for off-cycle-peak
+ sleep $(( ($(dd if=/dev/urandom bs=1 count=1 2>/dev/null | hexdump -e '1/1 "%u"') % 40) + 10))
+fi
+
+umask 022
+fetch_candidate
+sanity_check_candidate
+mv -- "$WorkingFile" "$FullTargetPath"