summaryrefslogtreecommitdiffstats
path: root/contrib/sendmail/src
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/sendmail/src')
-rw-r--r--contrib/sendmail/src/Makefile17
-rw-r--r--contrib/sendmail/src/Makefile.m4102
-rw-r--r--contrib/sendmail/src/README1771
-rw-r--r--contrib/sendmail/src/SECURITY202
-rw-r--r--contrib/sendmail/src/TRACEFLAGS93
-rw-r--r--contrib/sendmail/src/TUNING231
-rw-r--r--contrib/sendmail/src/alias.c1014
-rw-r--r--contrib/sendmail/src/aliases65
-rw-r--r--contrib/sendmail/src/aliases.5121
-rw-r--r--contrib/sendmail/src/arpadate.c203
-rw-r--r--contrib/sendmail/src/bf.c858
-rw-r--r--contrib/sendmail/src/bf.h32
-rw-r--r--contrib/sendmail/src/collect.c1085
-rw-r--r--contrib/sendmail/src/conf.c6047
-rw-r--r--contrib/sendmail/src/conf.h211
-rw-r--r--contrib/sendmail/src/control.c428
-rw-r--r--contrib/sendmail/src/convtime.c202
-rw-r--r--contrib/sendmail/src/daemon.c4363
-rw-r--r--contrib/sendmail/src/deliver.c6168
-rw-r--r--contrib/sendmail/src/domain.c1248
-rw-r--r--contrib/sendmail/src/envelope.c1229
-rw-r--r--contrib/sendmail/src/err.c1163
-rw-r--r--contrib/sendmail/src/headers.c1919
-rw-r--r--contrib/sendmail/src/helpfile136
-rw-r--r--contrib/sendmail/src/macro.c594
-rw-r--r--contrib/sendmail/src/mailq.181
-rw-r--r--contrib/sendmail/src/main.c4340
-rw-r--r--contrib/sendmail/src/map.c7372
-rw-r--r--contrib/sendmail/src/mci.c1489
-rw-r--r--contrib/sendmail/src/milter.c3892
-rw-r--r--contrib/sendmail/src/mime.c1261
-rw-r--r--contrib/sendmail/src/newaliases.150
-rw-r--r--contrib/sendmail/src/parseaddr.c3183
-rw-r--r--contrib/sendmail/src/queue.c8636
-rw-r--r--contrib/sendmail/src/readcf.c4305
-rw-r--r--contrib/sendmail/src/recipient.c2023
-rw-r--r--contrib/sendmail/src/sasl.c283
-rw-r--r--contrib/sendmail/src/savemail.c1702
-rw-r--r--contrib/sendmail/src/sendmail.8712
-rw-r--r--contrib/sendmail/src/sendmail.h2554
-rw-r--r--contrib/sendmail/src/sfsasl.c739
-rw-r--r--contrib/sendmail/src/sfsasl.h23
-rw-r--r--contrib/sendmail/src/shmticklib.c78
-rw-r--r--contrib/sendmail/src/sm_resolve.c447
-rw-r--r--contrib/sendmail/src/sm_resolve.h142
-rw-r--r--contrib/sendmail/src/srvrsmtp.c4238
-rw-r--r--contrib/sendmail/src/stab.c469
-rw-r--r--contrib/sendmail/src/stats.c202
-rw-r--r--contrib/sendmail/src/statusd_shm.h44
-rw-r--r--contrib/sendmail/src/sysexits.c162
-rw-r--r--contrib/sendmail/src/timers.c231
-rw-r--r--contrib/sendmail/src/timers.h33
-rw-r--r--contrib/sendmail/src/tls.c1486
-rw-r--r--contrib/sendmail/src/trace.c224
-rw-r--r--contrib/sendmail/src/udb.c1318
-rw-r--r--contrib/sendmail/src/usersmtp.c3313
-rw-r--r--contrib/sendmail/src/util.c2743
-rw-r--r--contrib/sendmail/src/version.c18
58 files changed, 87295 insertions, 0 deletions
diff --git a/contrib/sendmail/src/Makefile b/contrib/sendmail/src/Makefile
new file mode 100644
index 0000000..c86bbf5
--- /dev/null
+++ b/contrib/sendmail/src/Makefile
@@ -0,0 +1,17 @@
+# $Id: Makefile,v 8.11 1999/09/23 22:36:42 ca Exp $
+
+SHELL= /bin/sh
+BUILD= ./Build
+OPTIONS= $(CONFIG) $(FLAGS)
+
+all: FRC
+ $(SHELL) $(BUILD) $(OPTIONS) $@
+clean: FRC
+ $(SHELL) $(BUILD) $(OPTIONS) $@
+install: FRC
+ $(SHELL) $(BUILD) $(OPTIONS) $@
+
+fresh: FRC
+ $(SHELL) $(BUILD) $(OPTIONS) -c
+
+FRC:
diff --git a/contrib/sendmail/src/Makefile.m4 b/contrib/sendmail/src/Makefile.m4
new file mode 100644
index 0000000..6143bbe
--- /dev/null
+++ b/contrib/sendmail/src/Makefile.m4
@@ -0,0 +1,102 @@
+dnl $Id: Makefile.m4,v 8.91.2.3 2002/07/29 22:08:09 gshapiro Exp $
+include(confBUILDTOOLSDIR`/M4/switch.m4')
+
+define(`confREQUIRE_LIBSM', `true')
+bldPRODUCT_START(`executable', `sendmail')
+define(`bldBIN_TYPE', `G')
+define(`bldINSTALL_DIR', `')
+define(`bldSOURCES', `main.c alias.c arpadate.c bf.c collect.c conf.c control.c convtime.c daemon.c deliver.c domain.c envelope.c err.c headers.c macro.c map.c mci.c milter.c mime.c parseaddr.c queue.c readcf.c recipient.c sasl.c savemail.c sfsasl.c shmticklib.c sm_resolve.c srvrsmtp.c stab.c stats.c sysexits.c timers.c tls.c trace.c udb.c usersmtp.c util.c version.c ')
+PREPENDDEF(`confENVDEF', `confMAPDEF')
+bldPUSH_SMLIB(`sm')
+bldPUSH_SMLIB(`smutil')
+
+dnl hack: /etc/mail is not defined as "location of .cf" in the build system
+define(`bldTARGET_INST_DEP', ifdef(`confINST_DEP', `confINST_DEP',
+`${DESTDIR}/etc/mail/submit.cf ${DESTDIR}${MSPQ}'))dnl
+define(`bldTARGET_LINKS', ifdef(`confLINKS', `confLINKS',
+`${DESTDIR}${UBINDIR}/newaliases ${DESTDIR}${UBINDIR}/mailq ${DESTDIR}${UBINDIR}/hoststat ${DESTDIR}${UBINDIR}/purgestat')
+)dnl
+
+# location of sendmail statistics file (usually /etc/mail/ or /var/log)
+STDIR= ifdef(`confSTDIR', `confSTDIR', `/etc/mail')
+
+# statistics file name
+STFILE= ifdef(`confSTFILE', `confSTFILE', `statistics')
+MSPSTFILE=ifdef(`confMSP_STFILE', `confMSP_STFILE', `sm-client.st')
+
+# full path to installed statistics file (usually ${STDIR}/statistics)
+STPATH= ${STDIR}/${STFILE}
+
+# location of sendmail helpfile file (usually /etc/mail)
+HFDIR= ifdef(`confHFDIR', `confHFDIR', `/etc/mail')
+
+# full path to installed help file (usually ${HFDIR}/helpfile)
+HFFILE= ${HFDIR}/ifdef(`confHFFILE', `confHFFILE', `helpfile')
+
+ifdef(`confSMSRCADD', `APPENDDEF(`confSRCADD', `confSMSRCADD')')
+ifdef(`confSMOBJADD', `APPENDDEF(`confOBJADD', `confSMOBJADD')')
+
+bldPUSH_TARGET(`statistics')
+divert(bldTARGETS_SECTION)
+statistics:
+ ${CP} /dev/null statistics
+ chmod ifdef(`confSTMODE', `confSTMODE', `0600') statistics
+
+${DESTDIR}/etc/mail/submit.cf:
+ @echo "Please read INSTALL if anything fails while installing the binary."
+ @echo "${DESTDIR}/etc/mail/submit.cf will be installed now."
+ cd ${SRCDIR}/cf/cf && make install-submit-cf
+
+MSPQ=ifdef(`confMSP_QUEUE_DIR', `confMSP_QUEUE_DIR', `/var/spool/clientmqueue')
+
+${DESTDIR}${MSPQ}:
+ @echo "Please read INSTALL if anything fails while installing the binary."
+ @echo "You must have setup a new user ${MSPQOWN} and a new group ${GBINGRP}"
+ @echo "as explained in sendmail/SECURITY."
+ mkdir -p ${DESTDIR}${MSPQ}
+ chown ${MSPQOWN} ${DESTDIR}${MSPQ}
+ chgrp ${GBINGRP} ${DESTDIR}${MSPQ}
+ chmod 0770 ${DESTDIR}${MSPQ}
+
+divert(0)
+
+ifdef(`confSETUSERID_INSTALL', `bldPUSH_INSTALL_TARGET(`install-set-user-id')')
+ifdef(`confMTA_INSTALL', `bldPUSH_INSTALL_TARGET(`install-sm-mta')')
+ifdef(`confNO_HELPFILE_INSTALL',, `bldPUSH_INSTALL_TARGET(`install-hf')')
+ifdef(`confNO_STATISTICS_INSTALL',, `bldPUSH_INSTALL_TARGET(`install-st')')
+divert(bldTARGETS_SECTION)
+
+install-set-user-id: bldCURRENT_PRODUCT ifdef(`confNO_HELPFILE_INSTALL',, `install-hf') ifdef(`confNO_STATISTICS_INSTALL',, `install-st') ifdef(`confNO_MAN_BUILD',, `install-docs')
+ ${INSTALL} -c -o ${S`'BINOWN} -g ${S`'BINGRP} -m ${S`'BINMODE} bldCURRENT_PRODUCT ${DESTDIR}${M`'BINDIR}
+ for i in ${sendmailTARGET_LINKS}; do \
+ rm -f $$i; \
+ ${LN} ${LNOPTS} ${M`'BINDIR}/sendmail $$i; \
+ done
+
+define(`confMTA_LINKS', `${DESTDIR}${UBINDIR}/newaliases ${DESTDIR}${UBINDIR}/mailq ${DESTDIR}${UBINDIR}/hoststat ${DESTDIR}${UBINDIR}/purgestat')
+install-sm-mta: bldCURRENT_PRODUCT
+ ${INSTALL} -c -o ${M`'BINOWN} -g ${M`'BINGRP} -m ${M`'BINMODE} bldCURRENT_PRODUCT ${DESTDIR}${M`'BINDIR}/sm-mta
+ for i in confMTA_LINKS; do \
+ rm -f $$i; \
+ ${LN} ${LNOPTS} ${M`'BINDIR}/sm-mta $$i; \
+ done
+
+install-hf:
+ if [ ! -d ${DESTDIR}${HFDIR} ]; then mkdir -p ${DESTDIR}${HFDIR}; else :; fi
+ ${INSTALL} -c -o ${UBINOWN} -g ${UBINGRP} -m 444 helpfile ${DESTDIR}${HFFILE}
+
+install-st: statistics
+ if [ ! -d ${DESTDIR}${STDIR} ]; then mkdir -p ${DESTDIR}${STDIR}; else :; fi
+ ${INSTALL} -c -o ${SBINOWN} -g ${UBINGRP} -m ifdef(`confSTMODE', `confSTMODE', `0600') statistics ${DESTDIR}${STPATH}
+
+install-submit-st: statistics ${DESTDIR}${MSPQ}
+ ${INSTALL} -c -o ${MSPQOWN} -g ${GBINGRP} -m ifdef(`confSTMODE', `confSTMODE', `0600') statistics ${DESTDIR}${MSPQ}/${MSPSTFILE}
+
+divert(0)
+bldPRODUCT_END
+
+bldPRODUCT_START(`manpage', `sendmail')
+define(`bldSOURCES', `sendmail.8 aliases.5 mailq.1 newaliases.1')
+bldPRODUCT_END
+
+bldFINISH
diff --git a/contrib/sendmail/src/README b/contrib/sendmail/src/README
new file mode 100644
index 0000000..b8c31ec
--- /dev/null
+++ b/contrib/sendmail/src/README
@@ -0,0 +1,1771 @@
+# Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+# Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+# Copyright (c) 1988
+# The Regents of the University of California. All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+#
+# $Id: README,v 8.355.2.3 2002/06/21 22:44:56 gshapiro Exp $
+#
+
+This directory contains the source files for sendmail(TM).
+
+ *******************************************************************
+ !! Read sendmail/SECURITY for important installation information !!
+ *******************************************************************
+
+ **********************************************************
+ ** Read below for more details on building sendmail. **
+ **********************************************************
+
+**************************************************************************
+** IMPORTANT: Read the appropriate paragraphs in the section on **
+** ``Operating System and Compile Quirks''. **
+**************************************************************************
+
+For detailed instructions, please read the document ../doc/op/op.me:
+
+ cd ../doc/op ; make op.ps op.txt
+
+Sendmail is a trademark of Sendmail, Inc.
+
+
++-------------------+
+| BUILDING SENDMAIL |
++-------------------+
+
+By far, the easiest way to compile sendmail is to use the "Build"
+script:
+
+ sh Build
+
+This uses the "uname" command to figure out what architecture you are
+on and creates a proper Makefile accordingly. It also creates a
+subdirectory per object format, so that multiarchitecture support is
+easy. In general this should be all you need. IRIX 6.x users should
+read the note below in the OPERATING SYSTEM AND COMPILE QUIRKS section.
+
+If you need to look at other include or library directories, use the
+-I or -L flags on the command line, e.g.,
+
+ sh Build -I/usr/sww/include -L/usr/sww/lib
+
+It's also possible to create local site configuration in the file
+site.config.m4 (or another file settable with the -f flag). This
+file contains M4 definitions for various compilation values; the
+most useful are:
+
+confMAPDEF -D flags to specify database types to be included
+ (see below)
+confENVDEF -D flags to specify other environment information
+confINCDIRS -I flags for finding include files during compilation
+confLIBDIRS -L flags for finding libraries during linking
+confLIBS -l flags for selecting libraries during linking
+confLDOPTS other ld(1) linker options
+
+Others can be found by examining Makefile.m4. Please read
+../devtools/README for more information about the site.config.m4
+file.
+
+You can recompile from scratch using the -c flag with the Build
+command. This removes the existing compilation directory for the
+current platform and builds a new one. The -c flag must also
+be used if any site.*.m4 file in devtools/Site/ is changed.
+
+Porting to a new Unix-based system should be a matter of creating
+an appropriate configuration file in the devtools/OS/ directory.
+
+
++----------------------+
+| DATABASE DEFINITIONS |
++----------------------+
+
+There are several database formats that can be used for the alias files
+and for general maps. When used for alias files they interact in an
+attempt to be backward compatible.
+
+The options are:
+
+NEWDB The new Berkeley DB package. Some systems (e.g., BSD/OS and
+ Digital UNIX 4.0) have some version of this package
+ pre-installed. If your system does not have Berkeley DB
+ pre-installed, or the version installed is not version 2.0
+ or greater (e.g., is Berkeley DB 1.85 or 1.86), get the
+ current version from http://www.sleepycat.com/. DO NOT
+ use a version from any of the University of California,
+ Berkeley "Net" or other distributions. If you are still
+ running BSD/386 1.x, you will need to upgrade the included
+ Berkeley DB library to a current version. NEWDB is included
+ automatically if the Build script can find a library named
+ libdb.a or libdb.so.
+NDBM The older NDBM implementation -- the very old V7 DBM
+ implementation is no longer supported.
+NIS Network Information Services. To use this you must have
+ NIS support on your system.
+NISPLUS NIS+ (the revised NIS released with Solaris 2). You must
+ have NIS+ support on your system to use this flag.
+HESIOD Support for Hesiod (from the DEC/Athena distribution). You
+ must already have Hesiod support on your system for this to
+ work. You may be able to get this to work with the MIT/Athena
+ version of Hesiod, but that's likely to be a lot of work.
+ BIND 8.X also includes Hesiod support.
+LDAPMAP Lightweight Directory Access Protocol support. You will
+ have to install the UMich or OpenLDAP
+ (http://www.openldap.org/) ldap and lber libraries to use
+ this flag.
+MAP_REGEX Regular Expression support. You will need to use an
+ operating system which comes with the POSIX regex()
+ routines or install a regexp library such as libregex from
+ the Free Software Foundation.
+DNSMAP DNS map support. Requires NAMED_BIND.
+PH_MAP PH map support. You will need the libphclient library from
+ the nph package (http://www-dev.cso.uiuc.edu/ph/nph/).
+MAP_NSD nsd map support (IRIX 6.5 and later).
+
+>>> NOTE WELL for NEWDB support: If you want to get ndbm support, for
+>>> Berkeley DB versions under 2.0, it is CRITICAL that you remove
+>>> ndbm.o from libdb.a before you install it and DO NOT install ndbm.h;
+>>> for Berkeley DB versions 2.0 through 2.3.14, remove dbm.o from libdb.a
+>>> before you install it. If you don't delete these, there is absolutely
+>>> no point to including -DNDBM, since it will just get you another
+>>> (inferior) API to the same format database. These files OVERRIDE
+>>> calls to ndbm routines -- in particular, if you leave ndbm.h in,
+>>> you can find yourself using the new db package even if you don't
+>>> define NEWDB. Berkeley DB versions later than 2.3.14 do not need
+>>> to be modified. Please also consult the README in the top level
+>>> directory of the sendmail distribution for other important information.
+>>>
+>>> Further note: DO NOT remove your existing /usr/include/ndbm.h --
+>>> you need that one. But do not install an updated ndbm.h in
+>>> /usr/include, /usr/local/include, or anywhere else.
+
+If NEWDB and NDBM are defined (but not NIS), then sendmail will read
+NDBM format alias files, but the next time a newaliases is run the
+format will be converted to NEWDB; that format will be used forever
+more. This is intended as a transition feature.
+
+If NEWDB, NDBM, and NIS are all defined and the name of the file includes
+the string "/yp/", sendmail will rebuild BOTH the NEWDB and NDBM format
+alias files. However, it will only read the NEWDB file; the NDBM format
+file is used only by the NIS subsystem. This is needed because the NIS
+maps on an NIS server are built directly from the NDBM files.
+
+If NDBM and NIS are defined (regardless of the definition of NEWDB),
+and the filename includes the string "/yp/", sendmail adds the special
+tokens "YP_LAST_MODIFIED" and "YP_MASTER_NAME", both of which are
+required if the NDBM file is to be used as an NIS map.
+
+All of these flags are normally defined in a confMAPDEF setting in your
+site.config.m4.
+
+If you define NEWDB or HESIOD you get the User Database (USERDB)
+automatically. Generally you do want to have NEWDB for it to do
+anything interesting. See above for getting the Berkeley DB
+package (i.e., NEWDB). There is no separate "user database"
+package -- don't bother searching for it on the net.
+
+Hesiod and LDAP require libraries that may not be installed with your
+system. These are outside of my ability to provide support. See the
+"Quirks" section for more information.
+
+The regex map can be used to see if an address matches a certain regular
+expression. For example, all-numerics local parts are common spam
+addresses, so "^[0-9]+$" would match this. By using such a map in a
+check_* rule-set, you can block a certain range of addresses that would
+otherwise be considered valid.
+
+
++---------------+
+| COMPILE FLAGS |
++---------------+
+
+Wherever possible, I try to make sendmail pull in the correct
+compilation options needed to compile on various environments based on
+automatically defined symbols. Some machines don't seem to have useful
+symbols available, requiring that a compilation flag be defined in
+the Makefile; see the devtools/OS subdirectory for the supported
+architectures.
+
+If you are a system to which sendmail has already been ported you
+should not have to touch the following symbols. But if you are porting,
+you may have to tweak the following compilation flags in conf.h in order
+to get it to compile and link properly:
+
+SYSTEM5 Adjust for System V (not necessarily Release 4).
+SYS5SIGNALS Use System V signal semantics -- the signal handler
+ is automatically dropped when the signal is caught.
+ If this is not set, use POSIX/BSD semantics, where the
+ signal handler stays in force until an exec or an
+ explicit delete. Implied by SYSTEM5.
+SYS5SETPGRP Use System V setpgrp() semantics. Implied by SYSTEM5.
+HASNICE Define this to zero if you lack the nice(2) system call.
+HASRRESVPORT Define this to zero if you lack the rresvport(3) system call.
+HASFCHMOD Define this to one if you have the fchmod(2) system call.
+ This improves security.
+HASFCHOWN Define this to one if you have the fchown(2) system call.
+ This is required for the TrustedUser option if sendmail
+ must rebuild an (alias) map.
+HASFLOCK Set this if you prefer to use the flock(2) system call
+ rather than using fcntl-based locking. Fcntl locking
+ has some semantic gotchas, but many vendor systems
+ also interface it to lockd(8) to do NFS-style locking.
+ Unfortunately, may vendors implementations of fcntl locking
+ is just plain broken (e.g., locks are never released,
+ causing your sendmail to deadlock; when the kernel runs
+ out of locks your system crashes). For this reason, I
+ recommend always defining this unless you are absolutely
+ certain that your fcntl locking implementation really works.
+HASUNAME Set if you have the "uname" system call. Implied by
+ SYSTEM5.
+HASUNSETENV Define this if your system library has the "unsetenv"
+ subroutine.
+HASSETSID Define this if you have the setsid(2) system call. This
+ is implied if your system appears to be POSIX compliant.
+HASINITGROUPS Define this if you have the initgroups(3) routine.
+HASSETVBUF Define this if you have the setvbuf(3) library call.
+ If you don't, setlinebuf will be used instead. This
+ defaults on if your compiler defines __STDC__.
+HASSETREUID Define this if you have setreuid(2) ***AND*** root can
+ use setreuid to change to an arbitrary user. This second
+ condition is not satisfied on AIX 3.x. You may find that
+ your system has setresuid(2), (for example, on HP-UX) in
+ which case you will also have to #define setreuid(r, e)
+ to be the appropriate call. Some systems (such as Solaris)
+ have a compatibility routine that doesn't work properly,
+ but may have "saved user ids" properly implemented so you
+ can ``#define setreuid(r, e) seteuid(e)'' and have it work.
+ The important thing is that you have a call that will set
+ the effective uid independently of the real or saved uid
+ and be able to set the effective uid back again when done.
+ There's a test program in ../test/t_setreuid.c that will
+ try things on your system. Setting this improves the
+ security, since sendmail doesn't have to read .forward
+ and :include: files as root. There are certain attacks
+ that may be unpreventable without this call.
+USESETEUID Define this to 1 if you have a seteuid(2) system call that
+ will allow root to set only the effective user id to an
+ arbitrary value ***AND*** you have saved user ids. This is
+ preferable to HASSETREUID if these conditions are fulfilled.
+ These are the semantics of the to-be-released revision of
+ Posix.1. The test program ../test/t_seteuid.c will try
+ this out on your system. If you define both HASSETREUID
+ and USESETEUID, the former is ignored.
+HASSETEGID Define this if you have setegid(2) and it can be
+ used to set the saved gid. Please run t_dropgid in
+ test/ if you are not sure whether the call works.
+HASSETREGID Define this if you have setregid(2) and it can be
+ used to set the saved gid. Please run t_dropgid in
+ test/ if you are not sure whether the call works.
+HASSETRESGID Define this if you have setresgid(2) and it can be
+ used to set the saved gid. Please run t_dropgid in
+ test/ if you are not sure whether the call works.
+HASLSTAT Define this if you have symbolic links (and thus the
+ lstat(2) system call). This improves security. Unlike
+ most other options, this one is on by default, so you
+ need to #undef it in conf.h if you don't have symbolic
+ links (these days everyone does).
+HASSETRLIMIT Define this to 1 if you have the setrlimit(2) syscall.
+ You can define it to 0 to force it off. It is assumed
+ if you are running a BSD-like system.
+HASULIMIT Define this if you have the ulimit(2) syscall (System V
+ style systems). HASSETRLIMIT overrides, as it is more
+ general.
+HASWAITPID Define this if you have the waitpid(2) syscall.
+HASGETDTABLESIZE
+ Define this if you have the getdtablesize(2) syscall.
+HAS_ST_GEN Define this to 1 if your system has the st_gen field in
+ the stat structure (see stat(2)).
+HASSRANDOMDEV Define this if your system has the srandomdev(3) function
+ call.
+HASURANDOMDEV Define this if your system has /dev/urandom(4).
+HASSTRERROR Define this if you have the libc strerror(3) function (which
+ should be declared in <errno.h>), and it should be used
+ instead of sys_errlist.
+SM_CONF_GETOPT Define this as 0 if you need a reimplementation of getopt(3).
+ On some systems, getopt does very odd things if called
+ to scan the arguments twice. This flag will ask sendmail
+ to compile in a local version of getopt that works
+ properly.
+NEEDSTRTOL Define this if your standard C library does not define
+ strtol(3). This will compile in a local version.
+NEEDFSYNC Define this if your standard C library does not define
+ fsync(2). This will try to simulate the operation using
+ fcntl(2); if that is not available it does nothing, which
+ isn't great, but at least it compiles and runs.
+HASGETUSERSHELL Define this to 1 if you have getusershell(3) in your
+ standard C library. If this is not defined, or is defined
+ to be 0, sendmail will scan the /etc/shells file (no
+ NIS-style support, defaults to /bin/sh and /bin/csh if
+ that file does not exist) to get a list of unrestricted
+ user shells. This is used to determine whether users
+ are allowed to forward their mail to a program or a file.
+NEEDPUTENV Define this if your system needs am emulation of the
+ putenv(3) call. Define to 1 to implement it in terms
+ of setenv(3) or to 2 to do it in terms of primitives.
+NOFTRUNCATE Define this if you don't have the ftruncate(2) syscall.
+ If you don't have this system call, there is an unavoidable
+ race condition that occurs when creating alias databases.
+GIDSET_T The type of entries in a gidset passed as the second
+ argument to getgroups(2). Historically this has been an
+ int, so this is the default, but some systems (such as
+ IRIX) pass it as a gid_t, which is an unsigned short.
+ This will make a difference, so it is important to get
+ this right! However, it is only an issue if you have
+ group sets.
+SLEEP_T The type returned by the system sleep() function.
+ Defaults to "unsigned int". Don't worry about this
+ if you don't have compilation problems.
+ARBPTR_T The type of an arbitrary pointer -- defaults to "void *".
+ If you are an very old compiler you may need to define
+ this to be "char *".
+SOCKADDR_LEN_T The type used for the third parameter to accept(2),
+ getsockname(2), and getpeername(2), representing the
+ length of a struct sockaddr. Defaults to int.
+SOCKOPT_LEN_T The type used for the fifth parameter to getsockopt(2)
+ and setsockopt(2), representing the length of the option
+ buffer. Defaults to int.
+LA_TYPE The type of load average your kernel supports. These
+ can be one of:
+ LA_ZERO (1) -- it always returns the load average as
+ "zero" (and does so on all architectures).
+ LA_INT (2) to read /dev/kmem for the symbol avenrun and
+ interpret as a long integer.
+ LA_FLOAT (3) same, but interpret the result as a floating
+ point number.
+ LA_SHORT (6) to interpret as a short integer.
+ LA_SUBR (4) if you have the getloadavg(3) routine in your
+ system library.
+ LA_MACH (5) to use MACH-style load averages (calls
+ processor_set_info()),
+ LA_PROCSTR (7) to read /proc/loadavg and interpret it
+ as a string representing a floating-point
+ number (Linux-style).
+ LA_READKSYM (8) is an implementation suitable for some
+ versions of SVr4 that uses the MIOC_READKSYM ioctl
+ call to read /dev/kmem.
+ LA_DGUX (9) is a special implementation for DG/UX that uses
+ the dg_sys_info system call.
+ LA_HPUX (10) is an HP-UX specific version that uses the
+ pstat_getdynamic system call.
+ LA_IRIX6 (11) is an IRIX 6.x specific version that adapts
+ to 32 or 64 bit kernels; it is otherwise very similar
+ to LA_INT.
+ LA_KSTAT (12) uses the (Solaris-specific) kstat(3k)
+ implementation.
+ LA_DEVSHORT (13) reads a short from a system file (default:
+ /dev/table/avenrun) and scales it in the same manner
+ as LA_SHORT.
+ LA_INT, LA_SHORT, LA_FLOAT, and LA_READKSYM have several
+ other parameters that they try to divine: the name of your
+ kernel, the name of the variable in the kernel to examine,
+ the number of bits of precision in a fixed point load average,
+ and so forth. LA_DEVSHORT uses _PATH_AVENRUN to find the
+ device to be read to find the load average.
+ In desperation, use LA_ZERO. The actual code is in
+ conf.c -- it can be tweaked if you are brave.
+FSHIFT For LA_INT, LA_SHORT, and LA_READKSYM, this is the number
+ of bits of load average after the binary point -- i.e.,
+ the number of bits to shift right in order to scale the
+ integer to get the true integer load average. Defaults to 8.
+_PATH_UNIX The path to your kernel. Needed only for LA_INT, LA_SHORT,
+ and LA_FLOAT. Defaults to "/unix" on System V, "/vmunix"
+ everywhere else.
+LA_AVENRUN For LA_INT, LA_SHORT, and LA_FLOAT, the name of the kernel
+ variable that holds the load average. Defaults to "avenrun"
+ on System V, "_avenrun" everywhere else.
+SFS_TYPE Encodes how your kernel can locate the amount of free
+ space on a disk partition. This can be set to SFS_NONE
+ (0) if you have no way of getting this information,
+ SFS_USTAT (1) if you have the ustat(2) system call,
+ SFS_4ARGS (2) if you have a four-argument statfs(2)
+ system call (and the include file is <sys/statfs.h>),
+ SFS_VFS (3), SFS_MOUNT (4), SFS_STATFS (5) if you have
+ the two-argument statfs(2) system call with includes in
+ <sys/vfs.h>, <sys/mount.h>, or <sys/statfs.h> respectively,
+ or SFS_STATVFS (6) if you have the two-argument statvfs(2)
+ call. The default if nothing is defined is SFS_NONE.
+SFS_BAVAIL with SFS_4ARGS you can also set SFS_BAVAIL to the field name
+ in the statfs structure that holds the useful information;
+ this defaults to f_bavail.
+SPT_TYPE Encodes how your system can display what a process is doing
+ on a ps(1) command (SPT stands for Set Process Title). Can
+ be set to:
+ SPT_NONE (0) -- Don't try to set the process title at all.
+ SPT_REUSEARGV (1) -- Pad out your argv with the information;
+ this is the default if none specified.
+ SPT_BUILTIN (2) -- The system library has setproctitle.
+ SPT_PSTAT (3) -- Use the PSTAT_SETCMD option to pstat(2)
+ to set the process title; this is used by HP-UX.
+ SPT_PSSTRINGS (4) -- Use the magic PS_STRINGS pointer (4.4BSD).
+ SPT_SYSMIPS (5) -- Use sysmips() supported by NEWS-OS 6.
+ SPT_SCO (6) -- Write kernel u. area.
+ SPT_CHANGEARGV (7) -- Write pointers to our own strings into
+ the existing argv vector.
+SPT_PADCHAR Character used to pad the process title; if undefined,
+ the space character (0x20) is used. This is ignored if
+ SPT_TYPE != SPT_REUSEARGV
+ERRLIST_PREDEFINED
+ If set, assumes that some header file defines sys_errlist.
+ This may be needed if you get type conflicts on this
+ variable -- otherwise don't worry about it.
+WAITUNION The wait(2) routine takes a "union wait" argument instead
+ of an integer argument. This is for compatibility with
+ old versions of BSD.
+SCANF You can set this to extend the F command to accept a
+ scanf string -- this gives you a primitive parser for
+ class definitions -- BUT it can make you vulnerable to
+ core dumps if the target file is poorly formed.
+SYSLOG_BUFSIZE You can define this to be the size of the buffer that
+ syslog accepts. If it is not defined, it assumes a
+ 1024-byte buffer. If the buffer is very small (under
+ 256 bytes) the log message format changes -- each
+ e-mail message will log many more messages, since it
+ will log each piece of information as a separate line
+ in syslog.
+BROKEN_RES_SEARCH
+ On Ultrix (and maybe other systems?) if you use the
+ res_search routine with an unknown host name, it returns
+ -1 but sets h_errno to 0 instead of HOST_NOT_FOUND. If
+ you set this, sendmail considers 0 to be the same as
+ HOST_NOT_FOUND.
+NAMELISTMASK If defined, values returned by nlist(3) are masked
+ against this value before use -- a common value is
+ 0x7fffffff to strip off the top bit.
+BSD4_4_SOCKADDR If defined, socket addresses have an sa_len field that
+ defines the length of this address.
+SAFENFSPATHCONF Set this to 1 if and only if you have verified that a
+ pathconf(2) call with _PC_CHOWN_RESTRICTED argument on an
+ NFS filesystem where the underlying system allows users to
+ give away files to other users returns <= 0. Be sure you
+ try both on NFS V2 and V3. Some systems assume that their
+ local policy apply to NFS servers -- this is a bad
+ assumption! The test/t_pathconf.c program will try this
+ for you -- you have to run it in a directory that is
+ mounted from a server that allows file giveaway.
+SIOCGIFCONF_IS_BROKEN
+ Set this if your system has an SIOCGIFCONF ioctl defined,
+ but it doesn't behave the same way as "most" systems (BSD,
+ Solaris, SunOS, HP-UX, etc.)
+SIOCGIFNUM_IS_BROKEN
+ Set this if your system has an SIOCGIFNUM ioctl defined,
+ but it doesn't behave the same way as "most" systems
+ (Solaris, HP-UX).
+FAST_PID_RECYCLE
+ Set this if your system can reuse the same PID in the same
+ second.
+SO_REUSEADDR_IS_BROKEN
+ Set this if your system has a setsockopt() SO_REUSEADDR
+ flag but doesn't pay attention to it when trying to bind a
+ socket to a recently closed port.
+NEEDSGETIPNODE Set this if your system supports IPv6 but doesn't include
+ the getipnodeby{name,addr}() functions. Set automatically
+ for Linux's glibc.
+PIPELINING Support SMTP PIPELINING (set by default).
+USING_NETSCAPE_LDAP
+ Deprecated in favor of SM_CONF_LDAP_MEMFREE. See
+ libsm/README.
+NEEDLINK Set this if your system doesn't have a link() call. It
+ will create a copy of the file instead of a hardlink.
+USE_ENVIRON Set this to 1 to access process environment variables from
+ the external variable environ instead of the third
+ parameter of main().
+USE_DOUBLE_FORK By default this is on (1). Set it to 0 to suppress the
+ extra fork() used to avoid intermediate zombies.
+
+
++-----------------------+
+| COMPILE-TIME FEATURES |
++-----------------------+
+
+There are a bunch of features that you can decide to compile in, such
+as selecting various database packages and special protocol support.
+Several are assumed based on other compilation flags -- if you want to
+"un-assume" something, you probably need to edit conf.h. Compilation
+flags that add support for special features include:
+
+NDBM Include support for "new" DBM library for aliases and maps.
+ Normally defined in the Makefile.
+NEWDB Include support for Berkeley DB package (hash & btree)
+ for aliases and maps. Normally defined in the Makefile.
+ If the version of NEWDB you have is the old one that does
+ not include the "fd" call (this call was added in version
+ 1.5 of the Berkeley DB code), you must upgrade to the
+ current version of Berkeley DB.
+NIS Define this to get NIS (YP) support for aliases and maps.
+ Normally defined in the Makefile.
+NISPLUS Define this to get NIS+ support for aliases and maps.
+ Normally defined in the Makefile.
+HESIOD Define this to get Hesiod support for aliases and maps.
+ Normally defined in the Makefile.
+NETINFO Define this to get NeXT NetInfo support for aliases and maps.
+ Normally defined in the Makefile.
+LDAPMAP Define this to get LDAP support for maps.
+PH_MAP Define this to get PH support for maps.
+MAP_NSD Define this to get nsd support for maps.
+USERDB Define this to 1 to include support for the User Information
+ Database. Implied by NEWDB or HESIOD. You can use
+ -DUSERDB=0 to explicitly turn it off.
+IDENTPROTO Define this as 1 to get IDENT (RFC 1413) protocol support.
+ This is assumed unless you are running on Ultrix or
+ HP-UX, both of which have a problem in the UDP
+ implementation. You can define it to be 0 to explicitly
+ turn off IDENT protocol support. If defined off, the code
+ is actually still compiled in, but it defaults off; you
+ can turn it on by setting the IDENT timeout in the
+ configuration file.
+IP_SRCROUTE Define this to 1 to get IP source routing information
+ displayed in the Received: header. This is assumed on
+ most systems, but some (e.g., Ultrix) apparently have a
+ broken version of getsockopt that doesn't properly
+ support the IP_OPTIONS call. You probably want this if
+ your OS can cope with it. Symptoms of failure will be that
+ it won't compile properly (that is, no support for fetching
+ IP_OPTIONs), or it compiles but source-routed TCP connections
+ either refuse to open or open and hang for no apparent reason.
+ Ultrix and AIX3 are known to fail this way.
+LOG Set this to get syslog(3) support. Defined by default
+ in conf.h. You want this if at all possible.
+NETINET Set this to get TCP/IP support. Defined by default
+ in conf.h. You probably want this.
+NETINET6 Set this to get IPv6 support. Other configuration may
+ be needed in conf.h for your particular operating system.
+ Also, DaemonPortOptions must be set appropriately for
+ sendmail to accept IPv6 connections.
+NETISO Define this to get ISO networking support.
+NETUNIX Define this to get Unix domain networking support. Defined
+ by default. A few bizarre systems (SCO, ISC, Altos) don't
+ support this networking domain.
+NETNS Define this to get NS networking support.
+NETX25 Define this to get X.25 networking support.
+NAMED_BIND If non-zero, include DNS (name daemon) support, including
+ MX support. The specs say you must use this if you run
+ SMTP. You don't have to be running a name server daemon
+ on your machine to need this -- any use of the DNS resolver,
+ including remote access to another machine, requires this
+ option. Defined by default in conf.h. Define it to zero
+ ONLY on machines that do not use DNS in any way.
+MATCHGECOS Permit fuzzy matching of user names against the full
+ name (GECOS) field in the /etc/passwd file. This should
+ probably be on, since you can disable it from the config
+ file if you want to. Defined by default in conf.h.
+MIME8TO7 If non-zero, include 8 to 7 bit MIME conversions. This
+ also controls advertisement of 8BITMIME in the ESMTP
+ startup dialogue.
+MIME7TO8 If non-zero, include 7 to 8 bit MIME conversions.
+HES_GETMAILHOST Define this to 1 if you are using Hesiod with the
+ hes_getmailhost() routine. This is included with the MIT
+ Hesiod distribution, but not with the DEC Hesiod distribution.
+XDEBUG Do additional internal checking. These don't cost too
+ much; you might as well leave this on.
+TCPWRAPPERS Turns on support for the TCP wrappers library (-lwrap).
+ See below for further information.
+SECUREWARE Enable calls to the SecureWare luid enabling/changing routines.
+ SecureWare is a C2 security package added to several UNIX's
+ (notably ConvexOS) to get a C2 Secure system. This
+ option causes mail delivery to be done with the luid of the
+ recipient.
+SHARE_V1 Support for the fair share scheduler, version 1. Setting to
+ 1 causes final delivery to be done using the recipients
+ resource limitations. So far as I know, this is only
+ supported on ConvexOS.
+SASL Enables SMTP AUTH (RFC 2554). This requires the Cyrus SASL
+ library (ftp://ftp.andrew.cmu.edu/pub/cyrus-mail/). Please
+ install at least version 1.5.13. See below for further
+ information: SASL COMPILATION AND CONFIGURATION. If your
+ SASL library is older than 1.5.10, you have to set this
+ to its version number using a simple conversion: a.b.c
+ -> c + b*100 + a*10000, e.g. for 1.5.9 define SASL=10509.
+ Note: Using an older version than 1.5.5 of Cyrus SASL is
+ not supported. Starting with version 1.5.10, setting SASL=1
+ is sufficient. Any value other than 1 (or 0) will be
+ compared with the actual version found and if there is a
+ mismatch, compilation will fail.
+EGD Define this if your system has EGD installed, see
+ http://egd.sourceforge.net/ . It should be used to
+ seed the PRNG for STARTTLS if HASURANDOMDEV is not defined.
+STARTTLS Enables SMTP STARTTLS (RFC 2487). This requires OpenSSL
+ (http://www.OpenSSL.org/); use OpenSSL 0.9.5a or later
+ (if compatible with this version), do not use 0.9.3.
+ See STARTTLS COMPILATION AND CONFIGURATION for further
+ information.
+TLS_NO_RSA Turn off support for RSA algorithms in STARTTLS.
+MILTER Turn on support for external filters using the Milter API.
+ See libmilter/README for more information.
+REQUIRES_DIR_FSYNC Turn on support for file systems that require to
+ call fsync() for a directory if the meta-data in it has
+ been changed. This should be turned on at least for
+ ReiserFS; it is enabled by default for Linux. An alternative
+ to this compile time flag is to mount the queue directory
+ without the -async option, or using chattr +S on Linux.
+DBMMODE The default file permissions to use when creating new
+ database files for maps and aliases. Defaults to 0640.
+
+Generic notice: If you enable a compile time option that needs
+libraries or include files that don't come with sendmail or are
+installed in a location that your C compiler doesn't use by default
+you should set confINCDIRS and confLIBDIRS as explained in the
+first section: BUILDING SENDMAIL.
+
+
++---------------------+
+| DNS/RESOLVER ISSUES |
++---------------------+
+
+Many systems have old versions of the resolver library. At a minimum,
+you should be running BIND 4.8.3; older versions may compile, but they
+have known bugs that should give you pause.
+
+Common problems in old versions include "undefined" errors for
+dn_skipname.
+
+Some people have had a problem with BIND 4.9; it uses some routines
+that it expects to be externally defined such as strerror(). It may
+help to link with "-l44bsd" to solve this problem. This has apparently
+been fixed in later versions of BIND, starting around 4.9.3. In other
+words, if you use 4.9.0 through 4.9.2, you need -l44bsd; for earlier or
+later versions, you do not.
+
+!PLEASE! be sure to link with the same version of the resolver as
+the header files you used -- some people have used the 4.9 headers
+and linked with BIND 4.8 or vice versa, and it doesn't work.
+Unfortunately, it doesn't fail in an obvious way -- things just
+subtly don't work.
+
+WILDCARD MX RECORDS ARE A BAD IDEA! The only situation in which they
+work reliably is if you have two versions of DNS, one in the real world
+which has a wildcard pointing to your firewall, and a completely
+different version of the database internally that does not include
+wildcard MX records that match your domain. ANYTHING ELSE WILL GIVE
+YOU HEADACHES!
+
+When attempting to canonify a hostname, some broken name servers will
+return SERVFAIL (a temporary failure) on T_AAAA (IPv6) lookups. If you
+want to excuse this behavior, include WorkAroundBrokenAAAA in
+ResolverOptions. However, instead, we recommend catching the problem and
+reporting it to the name server administrator so we can rid the world of
+broken name servers.
+
+
++----------------------------------------+
+| STARTTLS COMPILATION AND CONFIGURATION |
++----------------------------------------+
+
+Please read the documentation accompanying the OpenSSL library. You
+have to compile and install the OpenSSL libraries before you can compile
+sendmail. See devtools/README how to set the correct compile time
+parameters; you should at least set the following variables:
+
+APPENDDEF(`conf_sendmail_ENVDEF', `-DSTARTTLS')
+APPENDDEF(`conf_sendmail_LIBS', `-lssl -lcrypto')
+
+If you have installed the OpenSSL libraries and include files in
+a location that your C compiler doesn't use by default you should
+set confINCDIRS and confLIBDIRS as explained in the first section:
+BUILDING SENDMAIL.
+
+Configuration information can be found in doc/op/op.me (required
+certificates) and cf/README (how to tell sendmail about certificates).
+
+To perform an initial test, connect to your sendmail daemon
+(telnet localhost 25) and issue a EHLO localhost and see whether
+250-STARTTLS
+is in the response. If it isn't, run the daemon with
+-O LogLevel=14
+and try again. Then take a look at the logfile and see whether
+there are any problems listed about permissions (unsafe files)
+or the validity of X.509 certificates.
+
+Further information can be found via:
+http://www.sendmail.org/tips/
+
+
++------------------------------------+
+| SASL COMPILATION AND CONFIGURATION |
++------------------------------------+
+
+Please read the documentation accompanying the Cyrus SASL library
+(INSTALL and README). If you use Berkeley DB for Cyrus SASL then
+you must compile sendmail with the same version of Berkeley DB.
+See devtools/README how to set the correct compile time parameters;
+you should at least set the following variables:
+
+APPENDDEF(`conf_sendmail_ENVDEF', `-DSASL')
+APPENDDEF(`conf_sendmail_LIBS', `-lsasl')
+
+If you have installed the Cyrus SASL library and include files in
+a location that your C compiler doesn't use by default you should
+set confINCDIRS and confLIBDIRS as explained in the first section:
+BUILDING SENDMAIL.
+
+You have to select and install authentication mechanisms and tell
+sendmail where to find the sasl library and the include files (see
+devtools/README for the parameters to set). Setup the required
+users and passwords as explained in the SASL documentation. See
+also cf/README for authentication related options (especially
+DefaultAuthInfo if you want authentication between MTAs).
+
+To perform an initial test, connect to your sendmail daemon
+(telnet localhost 25) and issue a EHLO localhost and see whether
+250-AUTH ....
+is in the response. If it isn't, run the daemon with
+-O LogLevel=14
+and try again. Then take a look at the logfile and see whether
+there are any security related problems listed (unsafe files).
+
+Further information can be found via:
+http://www.sendmail.org/tips/
+
+
++-------------------------------------+
+| OPERATING SYSTEM AND COMPILE QUIRKS |
++-------------------------------------+
+
+GCC problems
+ When compiling with "gcc -O -Wall" specify "-DSM_OMIT_BOGUS_WARNINGS"
+ too (see include/sm/cdefs.h for more info).
+
+ *****************************************************************
+ ** IMPORTANT: DO NOT USE OPTIMIZATION (``-O'') IF YOU ARE **
+ ** RUNNING GCC 2.4.x or 2.5.x. THERE IS A BUG IN THE GCC **
+ ** OPTIMIZER THAT CAUSES SENDMAIL COMPILES TO FAIL MISERABLY. **
+ *****************************************************************
+
+ Jim Wilson of Cygnus believes he has found the problem -- it will
+ probably be fixed in GCC 2.5.6 -- but until this is verified, be
+ very suspicious of gcc -O. This problem is reported to have been
+ fixed in gcc 2.6.
+
+ A bug in gcc 2.5.5 caused problems compiling sendmail 8.6.5 with
+ optimization on a Sparc. If you are using gcc 2.5.5, youi should
+ upgrade to the latest version of gcc.
+
+ Apparently GCC 2.7.0 on the Pentium processor has optimization
+ problems. I recommend against using -O on that architecture. This
+ has been seen on FreeBSD 2.0.5 RELEASE.
+
+ Solaris 2.X users should use version 2.7.2.3 over 2.7.2.
+
+ We have been told there are problems with gcc 2.8.0. If you are
+ using this version, you should upgrade to 2.8.1 or later.
+
+GDBM GDBM does not work with sendmail 8.8 because the additional
+ security checks and file locking cause problems. Unfortunately,
+ gdbm does not provide a compile flag in its version of ndbm.h so
+ the code can adapt. Until the GDBM authors can fix these problems,
+ GDBM will not be supported. Please use Berkeley DB instead.
+
+Configuration file location
+ Up to 8.6, sendmail tried to find the sendmail.cf file in the same
+ place as the vendors had put it, even when this was obviously
+ stupid. As of 8.7, sendmail ALWAYS looks for /etc/sendmail.cf.
+ Beginning with 8.10, sendmail uses /etc/mail/sendmail.cf.
+ You can get sendmail to use the stupid vendor .cf location by
+ adding -DUSE_VENDOR_CF_PATH during compilation, but this may break
+ support programs and scripts that need to find sendmail.cf. You
+ are STRONGLY urged to use symbolic links if you want to use the
+ vendor location rather than changing the location in the sendmail
+ binary.
+
+ NETINFO systems use NETINFO to determine the location of
+ sendmail.cf. The full path to sendmail.cf is stored as the value of
+ the "sendmail.cf" property in the "/locations/sendmail"
+ subdirectory of NETINFO. Set the value of this property to
+ "/etc/mail/sendmail.cf" (without the quotes) to use this new
+ default location for Sendmail 8.10.0 and higher.
+
+ControlSocket permissions
+ Paraphrased from BIND 8.2.1's README:
+
+ Solaris and other pre-4.4BSD kernels do not respect ownership or
+ protections on UNIX-domain sockets. The short term fix for this is to
+ override the default path and put such control sockets into root-
+ owned directories which do not permit non-root to r/w/x through them.
+ The long term fix is for all kernels to upgrade to 4.4BSD semantics.
+
+HP MPE/iX
+ The MPE-specific code within sendmail emulates a set-user-id root
+ environment for the sendmail binary. But there is no root uid 0 on
+ MPE, nor is there any support for set-user-id programs. Even when
+ sendmail thinks it is running as uid 0, it will still have the file
+ access rights of the underlying non-zero uid, but because sendmail is
+ an MPE priv-mode program it will still be able to call setuid() to
+ successfully switch to a new uid.
+
+ MPE setgid() semantics don't quite work the way sendmail expects, so
+ special emulation is done here also.
+
+ This uid/gid emulation is enabled via the setuid/setgid file mode bits
+ which are not currently used by MPE. Code in libsm/mpeix.c examines
+ these bits and enables emulation if they have been set, i.e.,
+ chmod u+s,g+s /SENDMAIL/CURRENT/SENDMAIL.
+
+SunOS 4.x (Solaris 1.x)
+ You may have to use -lresolv on SunOS. However, beware that
+ this links in a new version of gethostbyname that does not
+ understand NIS, so you must have all of your hosts in DNS.
+
+ Some people have reported problems with the SunOS version of
+ -lresolv and/or in.named, and suggest that you get a newer
+ version. The symptoms are delays when you connect to the
+ SMTP server on a SunOS machine or having your domain added to
+ addresses inappropriately. There is a version of BIND
+ version 4.9 on gatekeeper.DEC.COM in pub/BSD/bind/4.9.
+
+ There is substantial disagreement about whether you can make
+ this work with resolv+, which allows you to specify a search-path
+ of services. Some people report that it works fine, others
+ claim it doesn't work at all (including causing sendmail to
+ drop core when it tries to do multiple resolv+ lookups for a
+ single job). I haven't tried resolv+, as we use DNS exclusively.
+
+ Should you want to try resolv+, it is on ftp.uu.net in
+ /networking/ip/dns.
+
+ Apparently getservbyname() can fail under moderate to high
+ load under some circumstances. This will exhibit itself as
+ the message ``554 makeconnection: service "smtp" unknown''.
+ The problem has been traced to one or more blank lines in
+ /etc/services on the NIS server machine. Delete these
+ and it should work. This info is thanks to Brian Bartholomew
+ <bb@math.ufl.edu> of I-Kinetics, Inc.
+
+ NOTE: The SunOS 4.X linker uses library paths specified during
+ compilation using -L for run-time shared library searches.
+ Therefore, it is vital that relative and unsafe directory paths not
+ be used when compiling sendmail.
+
+SunOS 4.0.2 (Sun 386i)
+ Date: Fri, 25 Aug 1995 11:13:58 +0200 (MET DST)
+ From: teus@oce.nl
+
+ Sendmail 8.7.Beta.12 compiles and runs nearly out of the box with the
+ following changes:
+ * Don't use /usr/5bin in your PATH, but make /usr/5bin/uname
+ available as "uname" command.
+ * Use the defines "-DBSD4_3 -DNAMED_BIND=0" in
+ devtools/OS/SunOS.4.0, which is selected via the "uname" command.
+ I recommend to make available the db-library on the system first
+ (and change the Makefile to use this library).
+ Note that the sendmail.cf and aliases files are found in /etc.
+
+SunOS 4.1.3, 4.1.3_U1
+ Sendmail causes crashes on SunOS 4.1.3 and 4.1.3_U1. According
+ to Sun bug number 1077939:
+
+ If an application does a getsockopt() on a SOCK_STREAM (TCP) socket
+ after the other side of the connection has sent a TCP RESET for
+ the stream, the kernel gets a Bus Trap in the tcp_ctloutput() or
+ ip_ctloutput() routine.
+
+ For 4.1.3, this is fixed in patch 100584-08, available on the
+ Sunsolve 2.7.1 or later CDs. For 4.1.3_U1, this was fixed in patch
+ 101790-01 (SunOS 4.1.3_U1: TCP socket and reset problems), later
+ obsoleted by patch 102010-05.
+
+ Sun patch 100584-08 is not currently publicly available on their
+ ftp site but a user has reported it can be found at other sites
+ using a web search engine.
+
+Solaris 2.x (SunOS 5.x)
+ To compile for Solaris, the Makefile built by Build must
+ include a SOLARIS definition which reflects the Solaris version
+ (i.e. -DSOLARIS=20400 for 2.4 or -DSOLARIS=20501 for 2.5.1).
+ If you are using gcc, make sure -I/usr/include is not used (or
+ it might complain about TopFrame). If you are using Sun's cc,
+ make sure /opt/SUNWspro/bin/cc is used instead of /usr/ucb/cc
+ (or it might complain about tm_zone).
+
+ The Solaris 2.x (x <= 3) "syslog" function is apparently limited
+ to something about 90 characters because of a kernel limitation.
+ If you have source code, you can probably up this number. You
+ can get patches that fix this problem: the patch ids are:
+
+ Solaris 2.1 100834
+ Solaris 2.2 100999
+ Solaris 2.3 101318
+
+ Be sure you have the appropriate patch installed or you won't
+ see system logging.
+
+Solaris 2.4 (SunOS 5.4)
+ If you include /usr/lib at the end of your LD_LIBRARY_PATH you run
+ the risk of getting the wrong libraries under some circumstances.
+ This is because of a new feature in Solaris 2.4, described by
+ Rod.Evans@Eng.Sun.COM:
+
+ >> Prior to SunOS 5.4, any LD_LIBRARY_PATH setting was ignored by the
+ >> runtime linker if the application was setxid (secure), thus your
+ >> applications search path would be:
+ >>
+ >> /usr/local/lib LD_LIBRARY_PATH component - IGNORED
+ >> /usr/lib LD_LIBRARY_PATH component - IGNORED
+ >> /usr/local/lib RPATH - honored
+ >> /usr/lib RPATH - honored
+ >>
+ >> the effect is that path 3 would be the first used, and this would
+ >> satisfy your resolv.so lookup.
+ >>
+ >> In SunOS 5.4 we made the LD_LIBRARY_PATH a little more flexible.
+ >> People who developed setxid applications wanted to be able to alter
+ >> the library search path to some degree to allow for their own
+ >> testing and debugging mechanisms. It was decided that the only
+ >> secure way to do this was to allow a `trusted' path to be used in
+ >> LD_LIBRARY_PATH. The only trusted directory we presently define
+ >> is /usr/lib. Thus a set-user-ID root developer could play with some
+ >> alternative shared object implementations and place them in
+ >> /usr/lib (being root we assume they'ed have access to write in this
+ >> directory). This change was made as part of 1155380 - after a
+ >> *huge* amount of discussion regarding the security aspect of things.
+ >>
+ >> So, in SunOS 5.4 your applications search path would be:
+ >>
+ >> /usr/local/lib from LD_LIBRARY_PATH - IGNORED (untrustworthy)
+ >> /usr/lib from LD_LIBRARY_PATH - honored (trustworthy)
+ >> /usr/local/lib from RPATH - honored
+ >> /usr/lib from RPATH - honored
+ >>
+ >> here, path 2 would be the first used.
+
+Solaris 2.5.1 (SunOS 5.5.1) and 2.6 (SunOS 5.6)
+ Apparently Solaris 2.5.1 patch 103663-01 installs a new
+ /usr/include/resolv.h file that defines the __P macro without
+ checking to see if it is already defined. This new resolv.h is also
+ included in the Solaris 2.6 distribution. This causes compile
+ warnings such as:
+
+ In file included from daemon.c:51:
+ /usr/include/resolv.h:208: warning: `__P' redefined
+ cdefs.h:58: warning: this is the location of the previous definition
+
+ These warnings can be safely ignored or you can create a resolv.h
+ file in the obj.SunOS.5.5.1.* or obj.SunOS.5.6.* directory that reads:
+
+ #undef __P
+ #include "/usr/include/resolv.h"
+
+ This problem was fixed in Solaris 7 (Sun bug ID 4081053).
+
+Solaris 7 (SunOS 5.7)
+ Solaris 7 includes LDAP libraries but the implementation was
+ lacking a few things. The following settings can be placed in
+ devtools/Site/site.SunOS.5.7.m4 if you plan on using those
+ libraries.
+
+ APPENDDEF(`confMAPDEF', `-DLDAPMAP')
+ APPENDDEF(`confENVDEF', `-DLDAP_VERSION_MAX=3')
+ APPENDDEF(`confLIBS', `-lldap')
+
+ Also, Sun's patch 107555 is needed to prevent a crash in the call
+ to ldap_set_option for LDAP_OPT_REFERRALS in ldapmap_setopts if
+ LDAP support is compiled in sendmail.
+
+Solaris 8 and later (SunOS 5.8 and later)
+ Solaris 8 and later can optionally install LDAP support. If you
+ have installed the Entire Distribution meta-cluster, you can use
+ the following in devtools/Site/site.SunOS.5.8.m4 (or other
+ appropriately versioned file) to enable LDAP:
+
+ APPENDDEF(`confMAPDEF', `-DLDAPMAP')
+ APPENDDEF(`confLIBS', `-lldap')
+
+Solaris 9 and later (SunOS 5.9 and later)
+ Solaris 9 and later have a revised LDAP library, libldap.so.5,
+ which is derived from a Netscape implementation, thus requiring
+ that SM_CONF_LDAP_MEMFREE be defined in conjunction with LDAPMAP:
+
+ APPENDDEF(`confMAPDEF', `-DLDAPMAP')
+ APPENDDEF(`confENVDEF', `-DSM_CONF_LDAP_MEMFREE')
+ APPENDDEF(`confLIBS', `-lldap')
+
+Solaris
+ If you are using dns for hostname resolution on Solaris, make sure
+ that the 'dns' entry is last on the hosts line in
+ '/etc/nsswitch.conf'. For example, use:
+
+ hosts: nisplus files dns
+
+ Do not use:
+
+ host: nisplus dns [NOTFOUND=return] files
+
+ Note that 'nisplus' above is an illustration. The same comment
+ applies no matter what naming services you are using. If you have
+ anything other than dns last, even after "[NOTFOUND=return]",
+ sendmail may not be able to determine whether an error was
+ temporary or permanent. The error returned by the solaris
+ gethostbyname() is the error for the last lookup used, and other
+ naming services do not have the same concept of temporary failure.
+
+Ultrix
+ By default, the IDENT protocol is turned off on Ultrix. If you
+ are running Ultrix 4.4 or later, or if you have included patch
+ CXO-8919 for Ultrix 4.2 or 4.3 to fix the TCP problem, you can turn
+ IDENT on in the configuration file by setting the "ident" timeout.
+
+ The Ultrix 4.5 Y2K patch (ULTV45-022-1) has changed the resolver
+ included in libc.a. Unfortunately, the __RES symbol hasn't changed
+ and therefore, sendmail can no longer automatically detect the
+ newer version. If you get a compiler error:
+
+ /lib/libc.a(gethostent.o): local_hostname_length: multiply defined
+
+ Then rebuild with this in devtools/Site/site.ULTRIX.m4:
+
+ APPENDDEF(`conf_sendmail_ENVDEF', `-DNEEDLOCAL_HOSTNAME_LENGTH=0')
+
+Digital UNIX (formerly DEC OSF/1)
+ If you are compiling on OSF/1 (DEC Alpha), you must use
+ -L/usr/shlib (otherwise it core dumps on startup). You may also
+ need -mld to get the nlist() function, although some versions
+ apparently don't need this.
+
+ Also, the enclosed makefile removed /usr/sbin/smtpd; if you need
+ it, just create the link to the sendmail binary.
+
+ On DEC OSF/1 3.2 or earlier, the MatchGECOS option doesn't work
+ properly due to a bug in the getpw* routines. If you want to use
+ this, use -DDEC_OSF_BROKEN_GETPWENT=1. The problem is fixed in 3.2C.
+
+ Digital's mail delivery agent, /bin/mail (aka /bin/binmail), will
+ only preserve the envelope sender in the "From " header if
+ DefaultUserID is set to daemon. Setting this to mailnull will
+ cause all mail to have the header "From mailnull ...". To use
+ a different DefaultUserID, you will need to use a different mail
+ delivery agent (such as mail.local found in the sendmail
+ distribution).
+
+ On Digital UNIX 4.0 and later, Berkeley DB 1.85 is included with the
+ operating system and already has the ndbm.o module removed. However,
+ Digital has modified the original Berkeley DB db.h include file.
+ This results in the following warning while compiling map.c and udb.c:
+
+ cc: Warning: /usr/include/db.h, line 74: The redefinition of the macro
+ "__signed" conflicts with a current definition because the replacement
+ lists differ. The redefinition is now in effect.
+ #define __signed signed
+ ------------------------^
+
+ This warning can be ignored.
+
+ Digital UNIX's linker checks /usr/ccs/lib/ before /usr/lib/.
+ If you have installed a new version of BIND in /usr/include
+ and /usr/lib, you will experience difficulties as Digital ships
+ libresolv.a in /usr/ccs/lib/ as well. Be sure to replace both
+ copies of libresolv.a.
+
+IRIX
+ The header files on SGI IRIX are completely prototyped, and as
+ a result you can sometimes get some warning messages during
+ compilation. These can be ignored. There are two errors in
+ deliver only if you are using gcc, both of the form ``warning:
+ passing arg N of `execve' from incompatible pointer type''.
+ Also, if you compile with -DNIS, you will get a complaint
+ about a declaration of struct dom_binding in a prototype
+ when compiling map.c; this is not important because the
+ function being prototyped is not used in that file.
+
+ In order to compile sendmail you will have had to install
+ the developers' option in order to get the necessary include
+ files.
+
+ If you compile with -lmalloc (the fast memory allocator), you may
+ get warning messages such as the following:
+
+ ld32: WARNING 85: definition of _calloc in /usr/lib32/libmalloc.so
+ preempts that definition in /usr/lib32/mips3/libc.so.
+ ld32: WARNING 85: definition of _malloc in /usr/lib32/libmalloc.so
+ preempts that definition in /usr/lib32/mips3/libc.so.
+ ld32: WARNING 85: definition of _realloc in /usr/lib32/libmalloc.so
+ preempts that definition in /usr/lib32/mips3/libc.so.
+ ld32: WARNING 85: definition of _free in /usr/lib32/libmalloc.so
+ preempts that definition in /usr/lib32/mips3/libc.so.
+ ld32: WARNING 85: definition of _cfree in /usr/lib32/libmalloc.so
+ preempts that definition in /usr/lib32/mips3/libc.so.
+
+ These are unavoidable and innocuous -- just ignore them.
+
+ According to Dave Sill <de5@ornl.gov>, there is a version of the
+ Berkeley DB library patched to run on Irix 6.2 available from
+ http://reality.sgi.com/ariel/freeware/#db .
+
+IRIX 6.x
+ If you are using XFS filesystem, avoid using the -32 ABI switch to
+ the cc compiler if possible.
+
+ Broken inet_aton and inet_ntoa on IRIX using gcc: There's
+ a problem with gcc on IRIX, i.e., gcc can't pass structs
+ less than 16 bits long unless they are 8 bits; IRIX 6.2 has
+ some other sized structs. See
+ http://www.bitmechanic.com/mail-archives/mysql/current/0418.html
+ This problem seems to be fixed by gcc v2.95.2, gcc v2.8.1
+ is reported as broken. Check your gcc version for this bug
+ before installing sendmail.
+
+IRIX 6.4
+ The IRIX 6.5.4 version of /bin/m4 does not work properly with
+ sendmail. Either install fw_m4.sw.m4 off the Freeware_May99 CD and
+ use /usr/freeware/bin/m4 or install and use GNU m4.
+
+NeXT or NEXTSTEP
+ NEXTSTEP 3.3 and earlier ship with the old DBM library. Also,
+ Berkeley DB does not currently run on NEXTSTEP.
+
+ If you are compiling on NEXTSTEP, you will have to create an
+ empty file "unistd.h" and create a file "dirent.h" containing:
+
+ #include <sys/dir.h>
+ #define dirent direct
+
+ (devtools/OS/NeXT should try to do both of these for you.)
+
+ Apparently, there is a bug in getservbyname on Nextstep 3.0
+ that causes it to fail under some circumstances with the
+ message "SYSERR: service "smtp" unknown" logged. You should
+ be able to work around this by including the line:
+
+ OOPort=25
+
+ in your .cf file.
+
+BSDI (BSD/386) 1.0, NetBSD 0.9, FreeBSD 1.0
+ The "m4" from BSDI won't handle the config files properly.
+ I haven't had a chance to test this myself.
+
+ The M4 shipped in FreeBSD and NetBSD 0.9 don't handle the config
+ files properly. One must use either GNU m4 1.1 or the PD-M4
+ recently posted in comp.os.386bsd.bugs (and maybe others).
+ NetBSD-current includes the PD-M4 (as stated in the NetBSD file
+ CHANGES).
+
+ FreeBSD 1.0 RELEASE has uname(2) now. Use -DUSEUNAME in order to
+ use it (look into devtools/OS/FreeBSD). NetBSD-current may have
+ it too but it has not been verified.
+
+ The latest version of Berkeley DB uses a different naming
+ scheme than the version that is supplied with your release. This
+ means you will be able to use the current version of Berkeley DB
+ with sendmail as long you use the new db.h when compiling
+ sendmail and link it against the new libdb.a or libdb.so. You
+ should probably keep the original db.h in /usr/include and the
+ new db.h in /usr/local/include.
+
+4.3BSD
+ If you are running a "virgin" version of 4.3BSD, you'll have
+ a very old resolver and be missing some header files. The
+ header files are simple -- create empty versions and everything
+ will work fine. For the resolver you should really port a new
+ version (4.8.3 or later) of the resolver; 4.9 is available on
+ gatekeeper.DEC.COM in pub/BSD/bind/4.9. If you are really
+ determined to continue to use your old, buggy version (or as
+ a shortcut to get sendmail working -- I'm sure you have the
+ best intentions to port a modern version of BIND), you can
+ copy ../contrib/oldbind.compat.c into sendmail and add the
+ following to devtools/Site/site.config.m4:
+
+ APPENDDEF(`confOBJADD', `oldbind.compat.o')
+
+OpenBSD (up to 2.9 Release), NetBSD, FreeBSD (up to 4.3-RELEASE)
+ m4 from *BSD won't handle libsm/Makefile.m4 properly, since the
+ maximum length for strings is too short. You need to use GNU m4
+ or patch m4, see for example:
+ http://FreeBSD.org/cgi/cvsweb.cgi/src/usr.bin/m4/eval.c.diff?r1=1.11&r2=1.12
+
+A/UX
+ Date: Tue, 12 Oct 1993 18:28:28 -0400 (EDT)
+ From: "Eric C. Hagberg" <hagberg@med.cornell.edu>
+ Subject: Fix for A/UX ndbm
+
+ I guess this isn't really a sendmail bug, however, it is something
+ that A/UX users should be aware of when compiling sendmail 8.6.
+
+ Apparently, the calls that sendmail is using to the ndbm routines
+ in A/UX 3.0.x contain calls to "broken" routines, in that the
+ aliases database will break when it gets "just a little big"
+ (sorry I don't have exact numbers here, but it broke somewhere
+ around 20-25 aliases for me.), making all aliases non-functional
+ after exceeding this point.
+
+ What I did was to get the gnu-dbm-1.6 package, compile it, and
+ then re-compile sendmail with "-lgdbm", "-DNDBM", and using the
+ ndbm.h header file that comes with the gnu-package. This makes
+ things behave properly.
+ [NOTE: see comment above about GDBM]
+
+ I suppose porting the New Berkeley DB package is another route,
+ however, I made a quick attempt at it, and found it difficult
+ (not easy at least); the gnu-dbm package "configured" and
+ compiled easily.
+
+ [NOTE: Berkeley DB version 2.X runs on A/UX and can be used for
+ database maps.]
+
+SCO Unix
+ From: Thomas Essebier <tom@stallion.oz.au>
+ Organisation: Stallion Technologies Pty Ltd.
+
+ It will probably help those who are trying to configure sendmail 8.6.9
+ to know that if they are on SCO, they had better set
+ OI-dnsrch
+ or they will core dump as soon as they try to use the resolver.
+ i.e., although SCO has _res.dnsrch defined, and is kinda BIND 4.8.3,
+ it does not inititialise it, nor does it understand 'search' in
+ /etc/named.boot.
+ - sigh -
+
+ According to SCO, the m4 which ships with UnixWare 2.1.2 is broken.
+ We recommend installing GNU m4 before attempting to build sendmail.
+
+ On some versions a bogus error value is listed if connections
+ time out (large negative number). To avoid this explicitly set
+ Timeout.connect to a reasonable value (several minutes).
+
+DG/UX
+ Doug Anderson <dlander@afterlife.ncsc.mil> has successfully run
+ V8 on the DG/UX 5.4.2 and 5.4R3.x platforms under heavy usage.
+ Originally, the DG /bin/mail program wasn't compatible with
+ the V8 sendmail, since the DG /bin/mail requires the environment
+ variable "_FORCE_MAIL_LOCAL_=yes" be set. Version 8.7 now includes
+ this in the environment before invoking the local mailer. Some
+ have used procmail to avoid this problem in the past. It works
+ but some have experienced file locking problems with their DG/UX
+ ports of procmail.
+
+Apollo DomainOS
+ If you are compiling on Apollo, you will have to create an empty
+ file "unistd.h" (for DomainOS 10.3 and earlier) and create a file
+ "dirent.h" containing:
+
+ #include <sys/dir.h>
+ #define dirent direct
+
+ (devtools/OS/DomainOS will attempt to do both of these for you.)
+
+HP-UX 8.00
+ Date: Mon, 24 Jan 1994 13:25:45 +0200
+ From: Kimmo Suominen <Kimmo.Suominen@lut.fi>
+ Subject: 8.6.5 w/ HP-UX 8.00 on s300
+
+ Just compiled and fought with sendmail 8.6.5 on a HP9000/360 (i.e.,
+ a series 300 machine) running HP-UX 8.00.
+
+ I was getting segmentation fault when delivering to a local user.
+ With debugging I saw it was faulting when doing _free@libc... *sigh*
+ It seems the new implementation of malloc on s300 is buggy as of 8.0,
+ so I tried out the one in -lmalloc (malloc(3X)). With that it seems
+ to work just dandy.
+
+ When linking, you will get the following error:
+
+ ld: multiply defined symbol _freespace in file /usr/lib/libmalloc.a
+
+ but you can just ignore it. You might want to add this info to the
+ README file for the future...
+
+Linux
+ Something broke between versions 0.99.13 and 0.99.14 of Linux: the
+ flock() system call gives errors. If you are running .14, you must
+ not use flock. You can do this with -DHASFLOCK=0. We have also
+ been getting complaints since version 2.4.X was released. Unless
+ the bug is fixed before sendmail 8.13 is shipped, 8.13 will change
+ the default locking method to fcntl() for Linux kernel version 2.4
+ and later. Be sure to update other sendmail related programs to
+ match locking techniques (some examples, besides makemap and
+ mail.local, include procmail, mailx, mutt, elm, etc).
+
+ Around the inclusion of bind-4.9.3 & Linux libc-4.6.20, the
+ initialization of the _res structure changed. If /etc/hosts.conf
+ was configured as "hosts, bind" the resolver code could return
+ "Name server failure" errors. This is supposedly fixed in
+ later versions of libc (>= 4.6.29?), and later versions of
+ sendmail (> 8.6.10) try to work around the problem.
+
+ Some older versions (< 4.6.20?) of the libc/include files conflict
+ with sendmail's version of cdefs.h. Deleting sendmail's version
+ on those systems should be non-harmful, and new versions don't care.
+
+ NOTE ON LINUX & BIND: By default, the Makefile generated for Linux
+ includes header files in /usr/local/include and libraries in
+ /usr/local/lib. If you've installed BIND on your system, the header
+ files typically end up in the search path and you need to add
+ "-lresolv" to the LIBS line in your Makefile. Really old versions
+ may need to include "-l44bsd" as well (particularly if the link phase
+ complains about missing strcasecmp, strncasecmp or strpbrk).
+ Complaints about an undefined reference to `__dn_skipname' in
+ domain.o are a sure sign that you need to add -lresolv to LIBS.
+ Newer versions of Linux are basically threaded BIND, so you may or
+ may not see complaints if you accidentally mix BIND
+ headers/libraries with virginal libc. If you have BIND headers in
+ /usr/local/include (resolv.h, etc) you *should* be adding -lresolv
+ to LIBS. Data structures may change and you'd be asking for a
+ core dump.
+
+ A number of problems have been reported regarding the Linux 2.2.0
+ kernel. So far, these problems have been tracked down to syslog()
+ and DNS resolution. We believe the problem is with the poll()
+ implementation in the Linux 2.2.0 kernel and poll()-aware versions
+ of glib (at least up to 2.0.111).
+
+glibc
+ glibc 2.2.1 (and possibly other versions) changed the value of
+ __RES in resolv.h but failed to actually provide the IPv6 API
+ changes that the change implied. Therefore, compiling with
+ -DNETINET6 fails.
+
+ Workarounds:
+ 1) Compile without -DNETINET6
+ 2) Build against a real BIND 8.2.2 include/lib tree
+ 3) Wait for glibc to fix it
+
+AIX 4.X
+ The AIX 4.X linker uses library paths specified during compilation
+ using -L for run-time shared library searches. Therefore, it is
+ vital that relative and unsafe directory paths not be using when
+ compiling sendmail. Because of this danger, by default, compiles
+ on AIX use the -blibpath option to limit shared libraries to
+ /usr/lib and /lib. If you need to allow more directories, such as
+ /usr/local/lib, modify your devtools/Site/site.AIX.4.2.m4,
+ site.AIX.4.3.m4, and/or site.AIX.4.x.m4 file(s) and set confLDOPTS
+ approriately. For example:
+
+ define(`confLDOPTS', `-blibpath:/usr/lib:/lib:/usr/local/lib')
+
+ Be sure to only add (safe) system directories.
+
+ The AIX version of GNU ld also exhibits this problem. If you are
+ using that version, instead of -blibpath, use its -rpath option.
+ For example:
+
+ gcc -Wl,-rpath /usr/lib -Wl,-rpath /lib -Wl,-rpath /usr/local/lib
+
+AIX 4.X If the test program t-event (and most others) in libsm fails,
+ check your compiler settings. It seems that the flags -qnoro or
+ -qnoroconst on some AIX versions trigger a compiler bug. Check
+ your compiler settings or use cc instead of xlc.
+
+AIX 4.0-4.2, maybe some AIX 4.3 versions
+ The AIX m4 implements a different mechanism for ifdef which is
+ inconsistent with other versions of m4. Therefore, it will not
+ work properly with the sendmail Build architecture or m4
+ configuration method. To work around this problem, please use
+ GNU m4 from ftp://ftp.gnu.org/pub/gnu/.
+ The problem seems to be solved in AIX 4.3.3 at least.
+
+AIX 4.3.3
+ From: Valdis.Kletnieks@vt.edu
+ Date: Sun, 02 Jul 2000 03:58:02 -0400
+
+ Under AIX 4.3.3, after applying bos.adt.include 4.3.3.12 to close the
+ BIND 8.2.2 security holes, you can no longer build with -DNETINET6
+ because they changed the value of __RES in resolv.h but failed to
+ actually provide the API changes that the change implied.
+
+ Workarounds:
+ 1) Compile without -DNETINET6
+ 2) Build against a real BIND 8.2.2 include/lib tree
+ 3) Wait for IBM to fix it
+
+AIX 3.x
+ This version of sendmail does not support MB, MG, and MR resource
+ records, which are supported by AIX sendmail.
+
+ Several people have reported that the IBM-supplied named returns
+ fairly random results -- the named should be replaced. It is not
+ necessary to replace the resolver, which will simplify installation.
+ A new BIND resolver can be found at http://www.isc.org/isc/.
+
+AIX 3.1.x
+ The supplied load average code only works correctly for AIX 3.2.x.
+ For 3.1, use -DLA_TYPE=LA_SUBR and get the latest ``monitor''
+ package by Jussi Maki <jmaki@hut.fi> from ftp.funet.fi in the
+ directory pub/unix/AIX/rs6000/monitor-1.12.tar.Z; use the loadavgd
+ daemon, and the getloadavg subroutine supplied with that package.
+ If you don't care about load average throttling, just turn off
+ load average checking using -DLA_TYPE=LA_ZERO.
+
+RISC/os
+ RISC/os from MIPS is a merged AT&T/Berkeley system. When you
+ compile on that platform you will get duplicate definitions
+ on many files. You can ignore these.
+
+System V Release 4 Based Systems
+ There is a single devtools OS that is intended for all SVR4-based
+ systems (built from devtools/OS/SVR4). It defines __svr4__,
+ which is predefined by some compilers. If your compiler already
+ defines this compile variable, you can delete the definition from
+ the generated Makefile or create a devtools/Site/site.config.m4
+ file.
+
+ It's been tested on Dell Issue 2.2.
+
+DELL SVR4
+ Date: Mon, 06 Dec 1993 10:42:29 EST
+ From: "Kimmo Suominen" <kim@grendel.lut.fi>
+ Message-ID: <2d0352f9.lento29@lento29.UUCP>
+ To: eric@cs.berkeley.edu
+ Cc: sendmail@cs.berkeley.edu
+ Subject: Notes for DELL SVR4
+
+ Eric,
+
+ Here are some notes for compiling Sendmail 8.6.4 on DELL SVR4. I ran
+ across these things when helping out some people who contacted me by
+ e-mail.
+
+ 1) Use gcc 2.4.5 (or later?). Dell distributes gcc 2.1 with their
+ Issue 2.2 Unix. It is too old, and gives you problems with
+ clock.c, because sigset_t won't get defined in <sys/signal.h>.
+ This is due to a problematic protection rule in there, and is
+ fixed with gcc 2.4.5.
+
+ 2) If you don't use the new Berkeley DB (-DNEWDB), then you need
+ to add "-lc -lucb" to the libraries to link with. This is because
+ the -ldbm distributed by Dell needs the bcopy, bcmp and bzero
+ functions. It is important that you specify both libraries in
+ the given order to be sure you only get the BSTRING functions
+ from the UCB library (and not the signal routines etc.).
+
+ 3) Don't leave out "-lelf" even if compiling with "-lc -lucb".
+ The UCB library also has another copy of the nlist routines,
+ but we do want the ones from "-lelf".
+
+ If anyone needs a compiled gcc 2.4.5 and/or a ported DB library, they
+ can use anonymous ftp to fetch them from lut.fi in the /kim directory.
+ They are copies of what I use on grendel.lut.fi, and offering them
+ does not imply that I would also support them. I have sent the DB
+ port for SVR4 back to Keith Bostic for inclusion in the official
+ distribution, but I haven't heard anything from him as of today.
+
+ - gcc-2.4.5-svr4.tar.gz (gcc 2.4.5 and the corresponding libg++)
+ - db-1.72.tar.gz (with source, objects and a installed copy)
+
+ Cheers
+ + Kim
+ --
+ * Kimmo.Suominen@lut.fi * SysVr4 enthusiast at GRENDEL.LUT.FI *
+ * KIM@FINFILES.BITNET * Postmaster and Hostmaster at LUT.FI *
+ * + 358 200 865 718 * Unix area moderator at NIC.FUNET.FI *
+
+ConvexOS 10.1 and below
+ In order to use the name server, you must create the file
+ /etc/use_nameserver. If this file does not exist, the call
+ to res_init() will fail and you will have absolutely no
+ access to DNS, including MX records.
+
+Amdahl UTS 2.1.5
+ In order to get UTS to work, you will have to port BIND 4.9.
+ The vendor's BIND is reported to be ``totally inadequate.''
+ See sendmail/contrib/AmdahlUTS.patch for the patches necessary
+ to get BIND 4.9 compiled for UTS.
+
+UnixWare
+ According to Alexander Kolbasov <sasha@unitech.gamma.ru>,
+ the m4 on UnixWare 2.0 (still in Beta) will core dump on the
+ config files. GNU m4 and the m4 from UnixWare 1.x both work.
+
+ According to Larry Rosenman <ler@lerami.lerctr.org>:
+
+ UnixWare 2.1.[23]'s m4 chokes (not obviously) when
+ processing the 8.9.0 cf files.
+
+ I had a LOCAL_RULE_0 that wound up AFTER the
+ SBasic_check_rcpt rules using the SCO supplied M4.
+ GNU M4 works fine.
+
+UNICOS 8.0.3.4
+ Some people have reported that the -O flag on UNICOS can cause
+ problems. You may want to turn this off if you have problems
+ running sendmail. Reported by Jerry G. DeLapp <jgd@acl.lanl.gov>.
+
+Darwin/Mac OS X (10.X.X)
+ The linker errors produced regarding getopt() and it's associated
+ variables can safely be ignored.
+
+ From Mike Zimmerman <zimmy@torrentnet.com>:
+
+ From scratch here is what Darwin users need to do to the standard
+ 10.0.0, 10.0.1 install to get sendmail working.
+ From http://www.macosx.com/forums/showthread.php?s=6dac0e9e1f3fd118a4870a8a9b559491&threadid=2242:
+ 1. chmod g-w / /private /private/etc
+ 2. Properly set HOSTNAME in /etc/hostconfig to your FQDN:
+ HOSTNAME=-my.domain.com-
+ 3. Edit /etc/rc.boot:
+ hostname my.domain.com
+ domainname domain.com
+ 4. Edit /System/Library/StartupItems/Sendmail/Sendmail:
+ Remove the "&" after the sendmail command:
+ /usr/sbin/sendmail -bd -q1h
+
+ From Carsten Klapp <carsten.klapp@home.com>:
+
+ The easiest workaround is to remove the group-writable permission
+ for the root directory and the symbolic /etc inherits this
+ change. While this does fix sendmail, the unfortunate side-effect
+ is the OS X admin will no longer be able to manipulate icons in the
+ top level of the Startup disk unless logged into the GUI as the
+ superuser.
+
+ In applying the alternate workaround, care must be taken while
+ swapping the symlink /etc with the directory /private/etc. In all
+ likelihood any admin who is concerned with this sendmail error has
+ enough experience to not accidentally harm anything in the process.
+
+ a. Swap the /etc symlink with /private/etc (as superuser):
+ rm /etc
+ mv /private/etc /etc
+ ln -s /etc /private/etc
+
+ b. Set / to group unwritable (as superuser):
+ chmod g-w /
+
+Darwin/Mac OS X (10.1.5)
+ Apple's upgrade to sendmail 8.12 is incorrectly configured. You
+ will need to manually fix it up by doing the following:
+
+ 1. chown smmsp:smmsp /var/spool/clientmqueue
+ 2. chmod 2770 /var/spool/clientmqueue
+ 3. chgrp smmsp /usr/sbin/sendmail
+ 4. chmod g+s /usr/sbin/sendmail
+
+ From Daniel J. Luke <dluke@geeklair.net>:
+
+ It appears that setting the sendmail.cf property in
+ /locations/sendmail in NetInfo on Mac OS X 10.1.5 with sendmail
+ 8.12.4 causes 'bad things' to happen.
+
+ Specifically sendmail instances that should be getting their config
+ from /etc/mail/submit.cf don't (so mail/mutt/perl scripts which
+ open pipes to sendmail stop working as sendmail tries to write to
+ /var/spool/mqueue and cannot as sendmail is no longer suid root).
+
+ Removing the entry from NetInfo fixes this problem.
+
+GNU getopt
+ I'm told that GNU getopt has a problem in that it gets confused
+ by the double call. Use the version in conf.c instead.
+
+BIND 4.9.2 and Ultrix
+ If you are running on Ultrix, be sure you read conf/Info.Ultrix
+ in the BIND distribution very carefully -- there is information
+ in there that you need to know in order to avoid errors of the
+ form:
+
+ /lib/libc.a(gethostent.o): sethostent: multiply defined
+ /lib/libc.a(gethostent.o): endhostent: multiply defined
+ /lib/libc.a(gethostent.o): gethostbyname: multiply defined
+ /lib/libc.a(gethostent.o): gethostbyaddr: multiply defined
+
+ during the link stage.
+
+BIND 8.X
+ BIND 8.X returns HOST_NOT_FOUND instead of TRY_AGAIN on temporary
+ DNS failures when trying to find the hostname associated with an IP
+ address (gethostbyaddr()). This can cause problems as
+ $&{client_name} based lookups in class R ($=R) and the access
+ database won't succeed.
+
+ This will be fixed in BIND 8.2.1. For earlier versions, this can
+ be fixed by making "dns" the last name service queried for host
+ resolution in /etc/irs.conf:
+
+ hosts local continue
+ hosts dns
+
+strtoul
+ Some compilers (notably gcc) claim to be ANSI C but do not
+ include the ANSI-required routine "strtoul". If your compiler
+ has this problem, you will get an error in srvrsmtp.c on the
+ code:
+
+ # ifdef defined(__STDC__) && !defined(BROKEN_ANSI_LIBRARY)
+ e->e_msgsize = strtoul(vp, (char **) NULL, 10);
+ # else
+ e->e_msgsize = strtol(vp, (char **) NULL, 10);
+ # endif
+
+ You can use -DBROKEN_ANSI_LIBRARY to get around this problem.
+
+Listproc 6.0c
+ Date: 23 Sep 1995 23:56:07 GMT
+ Message-ID: <95925101334.~INN-AUMa00187.comp-news@dl.ac.uk>
+ From: alansz@mellers1.psych.berkeley.edu (Alan Schwartz)
+ Subject: Listproc 6.0c + Sendmail 8.7 [Helpful hint]
+
+ Just upgraded to sendmail 8.7, and discovered that listproc 6.0c
+ breaks, because it, by default, sends a blank "HELO" rather than
+ a "HELO hostname" when using the 'system' or 'telnet' mailmethod.
+
+ The fix is to include -DZMAILER in the compilation, which will
+ cause it to use "HELO hostname" (which Z-mail apparently requires
+ as well. :)
+
+OpenSSL
+ OpenSSL versions prior to 0.9.6 use a macro named Free which
+ conflicts with existing macro names on some platforms, such as
+ AIX.
+ Do not use 0.9.3, but OpenSSL 0.9.5a or later if compatible with
+ 0.9.5a.
+
+PH
+ PH support is provided by Mark Roth <roth@uiuc.edu>. The map is
+ described at http://www-dev.cso.uiuc.edu/sendmail/ .
+
+ NOTE: The "spacedname" pseudo-field which was used by earlier
+ versions of the PH map code is no longer supported! See the URL
+ listed above for more information.
+
+ Please contact Mark Roth for support and questions regarding the
+ map.
+
+TCP Wrappers
+ If you are using -DTCPWRAPPERS to get TCP Wrappers support you will
+ also need to install libwrap.a and modify your site.config.m4 file
+ or the generated Makefile to include -lwrap in the LIBS line
+ (make sure that INCDIRS and LIBDIRS point to where the tcpd.h and
+ libwrap.a can be found).
+
+ TCP Wrappers is available at ftp://ftp.porcupine.org/pub/security/.
+
+ If you have alternate MX sites for your site, be sure that all of
+ your MX sites reject the same set of hosts. If not, a bad guy whom
+ you reject will connect to your site, fail, and move on to the next
+ MX site, which will accept the mail for you and forward it on to you.
+
+Regular Expressions (MAP_REGEX)
+ If sendmail linking fails with:
+
+ undefined reference to 'regcomp'
+
+ or sendmail gives an error about a regular expression with:
+
+ pattern-compile-error: : Operation not applicable
+
+ Your libc does not include a running version of POSIX-regex. Use
+ librx or regex.o from the GNU Free Software Foundation,
+ ftp://ftp.gnu.org/pub/gnu/rx-?.?.tar.gz or
+ ftp://ftp.gnu.org/pub/gnu/regex-?.?.tar.gz.
+ You can also use the regex-lib by Henry Spencer,
+ ftp://ftp.funet.fi/pub/languages/C/spencer/regex.shar.gz
+ Make sure, your compiler reads regex.h from the distribution,
+ not from /usr/include, otherwise sendmail will dump a core.
+
+
++--------------+
+| MANUAL PAGES |
++--------------+
+
+The manual pages have been written against the -man macros, and
+should format correctly with any reasonable *roff.
+
+
++-----------------+
+| DEBUGGING HOOKS |
++-----------------+
+
+As of 8.6.5, sendmail daemons will catch a SIGUSR1 signal and log
+some debugging output (logged at LOG_DEBUG severity). The
+information dumped is:
+
+ * The value of the $j macro.
+ * A warning if $j is not in the set $=w.
+ * A list of the open file descriptors.
+ * The contents of the connection cache.
+ * If ruleset 89 is defined, it is evaluated and the results printed.
+
+This allows you to get information regarding the runtime state of the
+daemon on the fly. This should not be done too frequently, since
+the process of rewriting may lose memory which will not be recovered.
+Also, ruleset 89 may call non-reentrant routines, so there is a small
+non-zero probability that this will cause other problems. It is
+really only for debugging serious problems.
+
+A typical formulation of ruleset 89 would be:
+
+ R$* $@ $>0 some test address
+
+
++-----------------------------+
+| DESCRIPTION OF SOURCE FILES |
++-----------------------------+
+
+The following list describes the files in this directory:
+
+Build Shell script for building sendmail.
+Makefile A convenience for calling ./Build.
+Makefile.m4 A template for constructing a makefile based on the
+ information in the devtools directory.
+README This file.
+TRACEFLAGS My own personal list of the trace flags -- not guaranteed
+ to be particularly up to date.
+alias.c Does name aliasing in all forms.
+aliases.5 Man page describing the format of the aliases file.
+arpadate.c A subroutine which creates ARPANET standard dates.
+bf.c Routines to implement memory-buffered file system using
+ hooks provided by libsm now (formerly Torek stdio library).
+bf.h Buffered file I/O function declarations and
+ data structure and function declarations for bf.c.
+collect.c The routine that actually reads the mail into a temp
+ file. It also does a certain amount of parsing of
+ the header, etc.
+conf.c The configuration file. This contains information
+ that is presumed to be quite static and non-
+ controversial, or code compiled in for efficiency
+ reasons. Most of the configuration is in sendmail.cf.
+conf.h Configuration that must be known everywhere.
+control.c Routines to implement control socket.
+convtime.c A routine to sanely process times.
+daemon.c Routines to implement daemon mode.
+deliver.c Routines to deliver mail.
+domain.c Routines that interface with DNS (the Domain Name
+ System).
+envelope.c Routines to manipulate the envelope structure.
+err.c Routines to print error messages.
+headers.c Routines to process message headers.
+helpfile An example helpfile for the SMTP HELP command and -bt mode.
+macro.c The macro expander. This is used internally to
+ insert information from the configuration file.
+mailq.1 Man page for the mailq command.
+main.c The main routine to sendmail. This file also
+ contains some miscellaneous routines.
+makesendmail A convenience for calling ./Build.
+map.c Support for database maps.
+mci.c Routines that handle mail connection information caching.
+milter.c MTA portions of the mail filter API.
+mime.c MIME conversion routines.
+newaliases.1 Man page for the newaliases command.
+parseaddr.c The routines which do address parsing.
+queue.c Routines to implement message queueing.
+readcf.c The routine that reads the configuration file and
+ translates it to internal form.
+recipient.c Routines that manipulate the recipient list.
+sasl.c Routines to interact with Cyrys-SASL.
+savemail.c Routines which save the letter on processing errors.
+sendmail.8 Man page for the sendmail command.
+sendmail.h Main header file for sendmail.
+sfsasl.c I/O interface between SASL/TLS and the MTA.
+sfsasl.h Header file for sfsasl.c.
+shmticklib.c Routines for shared memory counters.
+sm_resolve.c Routines for DNS lookups (for DNS map type).
+sm_resolve.h Header file for sm_resolve.c.
+srvrsmtp.c Routines to implement server SMTP.
+stab.c Routines to manage the symbol table.
+stats.c Routines to collect and post the statistics.
+statusd_shm.h Data structure and function declarations for shmticklib.c.
+sysexits.c List of error messages associated with error codes
+ in sysexits.h.
+sysexits.h List of error codes for systems that lack their own.
+timers.c Routines to provide microtimers.
+timers.h Data structure and function declarations for timers.h.
+tls.c Routines for TLS.
+trace.c The trace package. These routines allow setting and
+ testing of trace flags with a high granularity.
+udb.c The user database interface module.
+usersmtp.c Routines to implement user SMTP.
+util.c Some general purpose routines used by sendmail.
+version.c The version number and information about this
+ version of sendmail.
+
+(Version $Revision: 8.355.2.3 $, last update $Date: 2002/06/21 22:44:56 $ )
diff --git a/contrib/sendmail/src/SECURITY b/contrib/sendmail/src/SECURITY
new file mode 100644
index 0000000..e42c024
--- /dev/null
+++ b/contrib/sendmail/src/SECURITY
@@ -0,0 +1,202 @@
+# Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# $Id: SECURITY,v 1.50 2002/03/29 19:45:48 ca Exp $
+#
+
+This file gives some hints how to configure and run sendmail for
+people who are very security conscious (you should be...).
+
+Even though sendmail goes through great lengths to assure that it
+can't be compromised even if the system it is running on is
+incorrectly or insecurely configured, it can't work around everything.
+This has been demonstrated by recent OS problems which have
+subsequently been used to compromise the root account using sendmail
+as a vector. One way to minimize the possibility of such problems
+is to install sendmail without set-user-ID root, which avoids local
+exploits. This configuration, which is the default starting with
+8.12, is described in the first section of this security guide.
+
+
+*****************************************************
+** sendmail configuration without set-user-ID root **
+*****************************************************
+
+sendmail needs to run as root for several purposes:
+
+- bind to port 25
+- call the local delivery agent (LDA) as root (or other user) if the LDA
+ isn't set-user-ID root (unless some other method of storing e-mail in
+ local mailboxes is used).
+- read .forward files
+- write e-mail submitted via the command line to the queue directory.
+
+Only the last item requires a set-user-ID/set-group-ID program to
+avoid problems with a world-writable directory. It is however
+sufficient to have a set-group-ID program and a group-writable
+queue directory. The other requirements listed above can be
+fulfilled by a sendmail daemon that is started by root. Hence this
+section explains how to use two sendmail configurations to accomplish
+the goal to have a sendmail binary that is not set-user-ID root,
+and hence is not open to system configuration/OS problems or at
+least less problematic in presence of those.
+
+The default configuration starting with sendmail 8.12 uses one
+sendmail binary which acts differently based on operation mode and
+supplied options.
+
+sendmail must be a set-group-ID (default group: smmsp, recommended
+gid: 25) program to allow for queueing mail in a group-writable
+directory. Two .cf files are required: sendmail.cf for the daemon
+and submit.cf for the submission program. The following permissions
+should be used:
+
+-r-xr-sr-x root smmsp ... /PATH/TO/sendmail
+drwxrwx--- smmsp smmsp ... /var/spool/clientmqueue
+drwx------ root wheel ... /var/spool/mqueue
+-r--r--r-- root wheel ... /etc/mail/sendmail.cf
+-r--r--r-- root wheel ... /etc/mail/submit.cf
+
+[Notice: On some OS "wheel" is not used but "bin" or "root" instead,
+however, this is not important here.]
+
+That is, the owner of sendmail is root, the group is smmsp, and
+the binary is set-group-ID. The client mail queue is owned by
+smmsp with group smmsp and is group writable. The client mail
+queue directory must be writable by smmsp, but it must not be
+accessible for others. That is, do not use world read or execute
+permissions. In submit.cf the option UseMSP must be set, and
+QueueFileMode must be set to 0660. submit.cf is available in
+cf/cf/, which has been built from cf/cf/submit.mc. The file can
+be used as-is, if you want to add more options, use cf/cf/submit.mc
+as starting point and read cf/README: MESSAGE SUBMISSION PROGRAM
+carefully.
+
+The .cf file is chosen based on the operation mode. For -bm (default),
+-bs, and -t it is submit.cf (if it exists) for all others it is
+sendmail.cf. This selection can be changed by -Ac or -Am (alternative
+.cf file: client or mta).
+
+The daemon must be started by root as usual, e.g.,
+
+/PATH/TO/sendmail -L sm-mta -bd -q1h
+
+(replace /PATH/TO with the right path for your OS, e.g.,
+/usr/sbin or /usr/lib).
+
+Notice: if you run sendmail from inetd (which in general is not a
+good idea), you must specify -Am in addition to -bs.
+
+Mail will end up in the client queue if the daemon doesn't accept
+connections or if an address is temporarily not resolvable. The
+latter problem can be minimized by using
+
+ FEATURE(`nocanonify', `canonify_hosts')
+ define(`confDIRECT_SUBMISSION_MODIFIERS', `C')
+
+which, however, may have undesired side effects. See cf/README for
+a discussion. In general it is necessary to clean the queue either
+via a cronjob or by running a daemon, e.g.,
+
+/PATH/TO/sendmail -L sm-msp-queue -Ac -q30m
+
+If the option UseMSP is not set, sendmail will complain during
+queue runs about bogus file permission. If you want a queue runner
+for the client queue, you probably have to change OS specific
+scripts to accomplish this (check the man pages of your OS for more
+information.) You can start this program as root, it will change
+its user id to RunAsUser (smmsp by default, recommended uid: 25).
+This way smmsp does not need a valid shell.
+
+Summary
+-------
+
+This is a brief summary how the two configuration files are used:
+
+sendmail.cf For the MTA (mail transmission agent)
+ The MTA is started by root as daemon:
+
+ /PATH/TO/sendmail -L sm-mta -bd -q1h
+
+ it accepts SMTP connections (on ports 25 and 587 by default);
+ it runs the main queue (/var/spool/mqueue by default).
+
+submit.cf For the MSP (mail submission program)
+ The MSP is used to submit e-mails, hence it is invoked
+ by programs (and maybe users); it does not run as SMTP
+ daemon; it uses /var/spool/clientmqueue by default; it
+ can be started to run that queue periodically:
+
+ /PATH/TO/sendmail -L sm-msp-queue -Ac -q30m
+
+
+Hints and Troubleshooting
+-------------------------
+
+RunAsUser: FEATURE(`msp') sets the option RunAsUser to smmsp.
+This user must have the group smmsp, i.e., the same group as the
+clientmqueue directory. If you specify a user whose primary group
+is not the same as that of the clientmqueue directory, then you
+should explicitly set the group, e.g.,
+
+ FEATURE(`msp')
+ define(`confRUN_AS_USER', `mailmsp:smmsp')
+
+STARTTLS: If sendmail is compiled with STARTTLS support on a platform
+that does not have HASURANDOMDEV defined, you either need to specify
+the RandFile option (as for the MTA), or you have to turn off
+STARTTLS in the MSP, e.g.,
+
+ DAEMON_OPTIONS(`Name=NoMTA, Addr=127.0.0.1, M=S')
+ FEATURE(`msp')
+ CLIENT_OPTIONS(`Family=inet, Address=0.0.0.0, M=S')
+
+The first option is used to turn off STARTTLS when the MSP is
+invoked with -bs as some MUAs do.
+
+
+What doesn't work anymore
+-------------------------
+
+Normal users can't use mailq anymore to see the MTA mail queue.
+There are several ways around it, e.g., changing QueueFileMode
+or giving users access via a program like sudo.
+
+sendmail -bv may give misleading output for normal users since it
+may not be able to access certain files, e.g., .forward files of
+other users.
+
+
+Alternative
+-----------
+
+Instead of having one set-group-ID binary, it is possible to use
+two with different permissions: one for message submission
+(set-group-ID), one acting as daemon etc, which is only executable
+by root. In that case it is possible to remove features from
+the message submission program to have a smaller binary.
+You can use
+
+ sh ./Build install-sm-mta
+
+to install a sendmail program to act as daemon etc under the name
+sm-mta.
+
+Set-User-Id
+-----------
+
+If you really have to install sendmail set-user-ID root, first build
+the sendmail package normally using
+
+ sh ./Build
+
+Then you can use
+
+ sh ./Build install-set-user-id
+
+to install the package in the old (pre-8.12) way. Make sure that
+no submit.cf file is installed.
diff --git a/contrib/sendmail/src/TRACEFLAGS b/contrib/sendmail/src/TRACEFLAGS
new file mode 100644
index 0000000..2aad39b
--- /dev/null
+++ b/contrib/sendmail/src/TRACEFLAGS
@@ -0,0 +1,93 @@
+# $Id: TRACEFLAGS,v 8.37.2.1 2002/07/01 20:55:47 gshapiro Exp $
+0, 4 main.c main canonical name, UUCP node name, a.k.a.s
+0, 15 main.c main print configuration
+0, 44 util.c printav print address of each string
+0, 101 main.c main print version and exit
+1 main.c main print from person
+2 main.c finis
+3 conf.c getla, shouldqueue
+4 conf.c enoughspace
+5 clock.c setevent, clrevent, tick
+6 savemail.c savemail, returntosender
+7 queue.c queuename
+8 domain.c getmxrr, getcanonname
+9 daemon.c getauthinfo IDENT protocol
+9 daemon.c maphostname
+10 deliver.c deliver
+11 deliver.c openmailer, mailfile
+12 parseaddr.c remotename
+13 deliver.c sendall, sendenvelope
+14 headers.c commaize
+15 daemon.c getrequests
+16 daemon.c makeconnection
+17 deliver.c hostsignature
+17 domain.c mxrand
+18 usersmtp.c reply, smtpmessage, smtpinit, smtpmailfrom, smtpdata
+19 srvrsmtp.c smtp
+20 parseaddr.c parseaddr
+21 parseaddr.c rewrite
+22 parseaddr.c prescan
+24 parseaddr.c buildaddr, allocaddr
+25 recipient.c sendtolist
+26 recipient.c recipient
+27 alias.c alias
+27 alias.c readaliases
+27 alias.c forward
+27 recipient.c include
+28 udb.c udbexpand, udbsender
+29 parseaddr.c maplocaluser
+29 recipient.c recipient (local users), finduser
+30 collect.c collect
+30 collect.c eatfrom
+31 headers.c chompheader
+32 headers.c eatheader
+33 headers.c crackaddr
+34 headers.c putheader
+35 macro.c expand, define
+36 stab.c stab
+37 readcf.c (many)
+38 map.c initmaps, setupmaps (bogus map)
+39 map.c map_rewrite
+40 queue.c queueup, orderq, dowork
+41 queue.c orderq
+42 mci.c mci_get
+43 mime.c mime8to7
+44 recipient.c writable
+44 safefile.c safefile, safedirpath, filechanged
+45 envelope.c setsender
+46 envelope.c openxscript
+47 main.c drop_privileges
+48 parseaddr.c rscheck
+48 conf.c validate_connection
+49 conf.c checkcompat
+50 envelope.c dropenvelope
+51 queue.c unlockqueue
+52 main.c disconnect
+53 util.c xfclose
+54 err.c putoutmsg
+55 conf.c lockfile
+56 mci.c persistent host status
+57 util.c snprintf
+58 bf.c bf* routines
+60 map.c
+61 conf.c sm_gethostbyname
+62 multiple file descriptor checking
+63 queue.c runqueue process watching
+64 multiple Milter
+65 main.c permission checks
+66 srvrsmtp.c conformance checks
+69 queue.c scheduling
+#if _FFR_QUARANTINE
+70 queue.c quarantining
+#endif /* _FFR_QUARANTINE */
+71,>99 milter.c quarantine on errors
+80 content length
+81 sun remote mode
+91 mci.c syslogging of MCI cache information
+94,>99 srvrsmtp.c cause commands to fail (for protocol testing)
+95 srvrsmtp.c AUTH
+95 usersmtp.c AUTH
+96 tls.c Activate SSL_CTX_set_info_callback()
+97 srvrsmtp.c Trace automode settings for I/O
+98 * timers
+99 main.c avoid backgrounding (no printed output)
diff --git a/contrib/sendmail/src/TUNING b/contrib/sendmail/src/TUNING
new file mode 100644
index 0000000..52da793
--- /dev/null
+++ b/contrib/sendmail/src/TUNING
@@ -0,0 +1,231 @@
+# Copyright (c) 2001-2002 Sendmail, Inc. and its suppliers.
+# All rights reserved.
+#
+# By using this file, you agree to the terms and conditions set
+# forth in the LICENSE file which can be found at the top level of
+# the sendmail distribution.
+#
+# $Id: TUNING,v 1.18 2002/03/03 03:38:21 ca Exp $
+#
+
+********************************************
+** This is a DRAFT, comments are welcome! **
+********************************************
+
+
+If the default configuration of sendmail does not achieve the
+required performance, there are several configuration options that
+can be changed to accomplish higher performance. However, before
+those options are changed it is necessary to understand why the
+performance is not as good as desired. This may also involve hardware
+and software (OS) configurations which are not extensively explored
+in this document. We assume that your system is not limited by
+network bandwidth because optimizing for this situation is beyond
+the scope of this guide. In almost all other cases performance will
+be limited by disk I/O.
+
+
+This text assumes that all options which are mentioned here are
+familiar to the reader, they are explained in the Sendmail Installation
+and Operations Guide; doc/op/op.txt.
+
+There are basically three different scenarios which are treated
+in the following:
+* Mailing Lists and Large Aliases (1-n Mailing)
+* 1-1 Mass Mailing
+* High Volume Mail
+
+Depending on your requirements, these may need different options
+to optimize sendmail for the particular purpose. It is also possible
+to configure sendmail to achieve good performance in all cases, but
+it will not be optimal for any specific purpose. For example, it
+is non-trivival to combine low latency (fast delivery of incoming
+mail) with high overall throughput.
+
+Before we explore the different scenarios, a basic discussion about
+disk I/O, delivery modes, and queue control is required.
+
+
+* Disk I/O
+-----------------------------------------------
+
+In general mail will be written to disk up before a delivery attempt
+is made. This is required for reliability and should only be changed
+in a few specific cases that are mentioned later on. To achieve
+better disk I/O performance the queue directories can be spread
+over several disks to distribute the load. This is some basic tuning
+that should be done in all cases where the I/O speed of a single
+disk is exceeded, which is true for almost every high-volume
+situation except if a special disk subsystem with large (NV)RAM
+buffer is used.
+
+Depending on your OS there might be ways to speed up I/O, e.g.,
+using softupdates or turning on the noatime mount option. If this
+is done make sure the filesystem is still reliable, i.e., if fsync()
+returns without an error, the file has really been committed to
+disk.
+
+
+* Queueing Strategies and DeliveryMode
+-----------------------------------------------
+
+There are basically three delivery modes:
+
+background: incoming mail will be immediately delivered by a new process
+interactive: incoming mail will be immediately delivered by the same process
+queue: incoming mail will be queued and delivered by a queue runner later on
+
+The first offers the lowest latency without the disadvantage of the
+second, which keep the connection from the sender open until the
+delivery to the next hop succeeded or failed. However, it does not
+allow for a good control over the number of delivery processes other
+than limiting the total number of direct children of the daemon
+processes (MaxChildren) or by load control options (RefuseLA,
+DelayLA). Moreover, it can't make as good use as 'queue' mode can
+for connection caching.
+
+Interactive DeliveryMode should only be used in rare cases, e.g.,
+if the delivery time to the next hop is a known quantity or if the
+sender is under local control and it does not matter if it has to
+wait for delivery.
+
+Queueing up e-mail before delivery is done by a queue runner allows
+the best load control but does not achieve as low latency as the
+other two modes. However, this mode is probably also best for
+concurrent delivery since the number of queue runners can be specified
+on a queue group basis. Persistent queue runners (-qp) can be used
+to minimize the overhead for creating processes because they just
+sleep for the specified interval (which shold be short) instead of
+exiting after a queue run.
+
+
+* Queue Groups
+-----------------------------------------------
+
+In most situations disk I/O is a bottleneck which can be mitigated
+by spreading the load over several disks. This can easily be achieved
+with different queue directories. sendmail 8.12 introduces queue
+groups which are collections of queue directories with similar
+properties, i.e., number of processes to run the queues in the
+group, maximum number of recipients within an e-mail (envelope),
+etc. Queue groups allow control over the behaviour of different
+queues. Depending on the setup, it is usually possible to have
+several queue runners delivering mails concurrently which should
+increase throughput. The number of queue runners can be controlled
+per queue group (Runner=) and overall (MaxQueueChildren).
+
+
+* DNS Lookups
+-----------------------------------------------
+
+sendmail performs by default host name canonifications by using
+host name lookups. This process is meant to replace unqualified
+host name with qualified host names, and CNAMEs with the non-aliased
+name. However, these lookups can take a while for large address
+lists, e.g., mailing lists. If you can assure by other means that
+host names are canonical, you should use
+
+ FEATURE(`nocanonify', `canonify_hosts')
+
+in your .mc file. For further information on this feature and
+additional options see cf/README. If sendmail is invoked directly
+to send e-mail then either the -G option should be used or
+
+ define(`confDIRECT_SUBMISSION_MODIFIERS', `C')
+
+should be added to the .mc file.
+
+
+* Mailing Lists and Large Aliases (1-n Mailing)
+-----------------------------------------------
+
+Before 8.12 sendmail delivers an e-mail sequentially to all its
+recipients. For mailing lists or large aliases the overall delivery
+time can be substantial, especially if some of the recipients are
+located at hosts that are slow to accept e-mail. Some mailing list
+software therefore "split" up e-mails into smaller pieces with
+fewer recipients. sendmail 8.12 can do this itself, either across
+queue groups or within a queue directory. The latter is controlled
+by the 'r=' field of a queue group declaration.
+
+Let's assume a simple example: a mailing lists where most of
+the recipients are at three domains: the local one (local.domain)
+and two remotes (one.domain, two.domain) and the rest is splittered
+over several other domains. For this case it is useful to specify
+three queue groups:
+
+QUEUE_GROUP(`local', `P=/var/spool/mqueue/local, F=f, R=2, I=1m')dnl
+QUEUE_GROUP(`one', `P=/var/spool/mqueue/one, F=f, r=50, R=3')dnl
+QUEUE_GROUP(`two', `P=/var/spool/mqueue/two, F=f, r=30, R=4')dnl
+QUEUE_GROUP(`remote', `P=/var/spool/mqueue/remote, F=f, r=5, R=8, I=2m')dnl
+define(`ESMTP_MAILER_QGRP', `remote')dnl
+define(`confSPLIT_ACROSS_QUEUEGROUPS', `True')dnl
+define(`confDELIVERY_MODE', `q')dnl
+define(`confMAX_QUEUE_CHILDREN', `50')dnl
+define(`confMIN_QUEUE_AGE', `27m')dnl
+
+and specify the queuegroup ruleset as follows:
+
+LOCAL_RULESETS
+Squeuegroup
+R$* @ local.domain $# local
+R$* @ $* one.domain $# one
+R$* @ $* two.domain $# two
+R$* @ $* $# remote
+R$* $# mqueue
+
+Now it is necessary to control the number of queue runners, which
+is done by MaxQueueChildren. Starting the daemon with the option
+-q5m assures that the first delivery attempt for each e-mail is
+done within 5 minutes, however, there are also individual queue
+intervals for the queue groups as specified above. MinQueueAge
+is set to 27 minutes to avoid that entries are run too often.
+
+Notice: if envelope splitting happens due to alias expansion, and
+DeliveryMode is not 'i'nteractive, then only one envelope is sent
+immediately. The rest (after splitting) are queued up and queue
+runners must come along and take care of them. Hence it is essential
+that the queue interval is very short.
+
+
+* 1-1 Mass Mailing
+-----------------------------------------------
+
+In this case some program generates e-mails which are sent to
+individual recipients (or at most very few per e-mail). A simple
+way to achieve high throughput is to set the delivery mode to
+'interactive', turn off the SuperSafe option and make sure that the
+program that generates the mails can deal with mail losses if the
+server loses power. In no other case should SuperSafe be set to
+'false'. If these conditions are met, sendmail does not need to
+commit mails to disk but can buffer them in memory which will greatly
+enhance performance, especially compared to normal disk subsystems, e.g.,
+non solid-state disks.
+
+
+* High Volume Mail
+-----------------------------------------------
+
+For high volume mail it is necessary to be able to control the load
+on the system. Therefore the 'queue' delivery mode should be used,
+and all options related to number of processes and the load should
+be set to reasonable values. It is important not to accept mail
+faster than it can be delivered otherwise the system will be
+overwhelmed. Hence RefuseLA should be lower than QueueLA, the number
+of daemon children should probably be lower than the number of queue
+runnners (MaxChildren vs. MaxQueueChildren). DelayLA is a new option
+in 8.12 which allows delaying connections instead of rejecting them.
+This may result in a smoother load distribution depending on how
+the mails are submitted to sendmail.
+
+
+* Miscellaneous
+-----------------------------------------------
+
+Other options that are interesting to tweak performance are
+(in no particular order):
+
+SuperSafe: if interactive DeliveryMode is used, then this can
+be set to the new value "interactive" in 8.12 to save some disk
+synchronizations which are not really necessary in that mode.
+
diff --git a/contrib/sendmail/src/alias.c b/contrib/sendmail/src/alias.c
new file mode 100644
index 0000000..f5bd746
--- /dev/null
+++ b/contrib/sendmail/src/alias.c
@@ -0,0 +1,1014 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: alias.c,v 8.214 2002/05/24 20:50:16 gshapiro Exp $")
+
+#define SEPARATOR ':'
+# define ALIAS_SPEC_SEPARATORS " ,/:"
+
+static MAP *AliasFileMap = NULL; /* the actual aliases.files map */
+static int NAliasFileMaps; /* the number of entries in AliasFileMap */
+
+static char *aliaslookup __P((char *, int *, char *));
+
+/*
+** ALIAS -- Compute aliases.
+**
+** Scans the alias file for an alias for the given address.
+** If found, it arranges to deliver to the alias list instead.
+** Uses libdbm database if -DDBM.
+**
+** Parameters:
+** a -- address to alias.
+** sendq -- a pointer to the head of the send queue
+** to put the aliases in.
+** aliaslevel -- the current alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Aliases found are expanded.
+**
+** Deficiencies:
+** It should complain about names that are aliased to
+** nothing.
+*/
+
+void
+alias(a, sendq, aliaslevel, e)
+ register ADDRESS *a;
+ ADDRESS **sendq;
+ int aliaslevel;
+ register ENVELOPE *e;
+{
+ register char *p;
+ char *owner;
+ auto int status = EX_OK;
+ char obuf[MAXNAME + 7];
+
+ if (tTd(27, 1))
+ sm_dprintf("alias(%s)\n", a->q_user);
+
+ /* don't realias already aliased names */
+ if (!QS_IS_OK(a->q_state))
+ return;
+
+ if (NoAlias)
+ return;
+
+ e->e_to = a->q_paddr;
+
+ /*
+ ** Look up this name.
+ **
+ ** If the map was unavailable, we will queue this message
+ ** until the map becomes available; otherwise, we could
+ ** bounce messages inappropriately.
+ */
+
+#if _FFR_REDIRECTEMPTY
+ /*
+ ** envelope <> can't be sent to mailing lists, only owner-
+ ** send spam of this type to owner- of the list
+ ** ---- to stop spam from going to mailing lists!
+ */
+
+ if (e->e_sender != NULL && *e->e_sender == '\0')
+ {
+ /* Look for owner of alias */
+ (void) sm_strlcpyn(obuf, sizeof obuf, 2, "owner-", a->q_user);
+ if (aliaslookup(obuf, &status, a->q_host) != NULL)
+ {
+ if (LogLevel > 8)
+ syslog(LOG_WARNING,
+ "possible spam from <> to list: %s, redirected to %s\n",
+ a->q_user, obuf);
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, obuf);
+ }
+ }
+#endif /* _FFR_REDIRECTEMPTY */
+
+ p = aliaslookup(a->q_user, &status, a->q_host);
+ if (status == EX_TEMPFAIL || status == EX_UNAVAILABLE)
+ {
+ a->q_state = QS_QUEUEUP;
+ if (e->e_message == NULL)
+ e->e_message = "alias database unavailable";
+
+ /* XXX msg only per recipient? */
+ if (a->q_message == NULL)
+ a->q_message = "alias database unavailable";
+ return;
+ }
+ if (p == NULL)
+ return;
+
+ /*
+ ** Match on Alias.
+ ** Deliver to the target list.
+ */
+
+ if (tTd(27, 1))
+ sm_dprintf("%s (%s, %s) aliased to %s\n",
+ a->q_paddr, a->q_host, a->q_user, p);
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ a->q_state = QS_VERIFIED;
+ return;
+ }
+ message("aliased to %s", shortenstring(p, MAXSHORTSTR));
+ if (LogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "alias %.100s => %s",
+ a->q_paddr, shortenstring(p, MAXSHORTSTR));
+ a->q_flags &= ~QSELFREF;
+ if (tTd(27, 5))
+ {
+ sm_dprintf("alias: QS_EXPANDED ");
+ printaddr(a, false);
+ }
+ a->q_state = QS_EXPANDED;
+
+ /*
+ ** Always deliver aliased items as the default user.
+ ** Setting q_gid to 0 forces deliver() to use DefUser
+ ** instead of the alias name for the call to initgroups().
+ */
+
+ a->q_uid = DefUid;
+ a->q_gid = 0;
+ a->q_fullname = NULL;
+ a->q_flags |= QGOODUID|QALIAS;
+
+ (void) sendtolist(p, a, sendq, aliaslevel + 1, e);
+
+ if (bitset(QSELFREF, a->q_flags) && QS_IS_EXPANDED(a->q_state))
+ a->q_state = QS_OK;
+
+ /*
+ ** Look for owner of alias
+ */
+
+ if (strncmp(a->q_user, "owner-", 6) == 0 ||
+ strlen(a->q_user) > sizeof obuf - 7)
+ (void) sm_strlcpy(obuf, "owner-owner", sizeof obuf);
+ else
+ (void) sm_strlcpyn(obuf, sizeof obuf, 2, "owner-", a->q_user);
+ owner = aliaslookup(obuf, &status, a->q_host);
+ if (owner == NULL)
+ return;
+
+ /* reflect owner into envelope sender */
+ if (strpbrk(owner, ",:/|\"") != NULL)
+ owner = obuf;
+ a->q_owner = sm_rpool_strdup_x(e->e_rpool, owner);
+
+ /* announce delivery to this alias; NORECEIPT bit set later */
+ if (e->e_xfp != NULL)
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Message delivered to mailing list %s\n",
+ a->q_paddr);
+ e->e_flags |= EF_SENDRECEIPT;
+ a->q_flags |= QDELIVERED|QEXPANDED;
+}
+/*
+** ALIASLOOKUP -- look up a name in the alias file.
+**
+** Parameters:
+** name -- the name to look up.
+** pstat -- a pointer to a place to put the status.
+** av -- argument for %1 expansion.
+**
+** Returns:
+** the value of name.
+** NULL if unknown.
+**
+** Side Effects:
+** none.
+**
+** Warnings:
+** The return value will be trashed across calls.
+*/
+
+static char *
+aliaslookup(name, pstat, av)
+ char *name;
+ int *pstat;
+ char *av;
+{
+ static MAP *map = NULL;
+#if _FFR_ALIAS_DETAIL
+ int i;
+ char *argv[4];
+#endif /* _FFR_ALIAS_DETAIL */
+
+ if (map == NULL)
+ {
+ STAB *s = stab("aliases", ST_MAP, ST_FIND);
+
+ if (s == NULL)
+ return NULL;
+ map = &s->s_map;
+ }
+ DYNOPENMAP(map);
+
+ /* special case POstMastER -- always use lower case */
+ if (sm_strcasecmp(name, "postmaster") == 0)
+ name = "postmaster";
+
+#if _FFR_ALIAS_DETAIL
+ i = 0;
+ argv[i++] = name;
+ argv[i++] = av;
+
+ /* XXX '+' is hardwired here as delimiter! */
+ if (av != NULL && *av == '+')
+ argv[i++] = av + 1;
+ argv[i++] = NULL;
+ return (*map->map_class->map_lookup)(map, name, argv, pstat);
+#else /* _FFR_ALIAS_DETAIL */
+ return (*map->map_class->map_lookup)(map, name, NULL, pstat);
+#endif /* _FFR_ALIAS_DETAIL */
+}
+/*
+** SETALIAS -- set up an alias map
+**
+** Called when reading configuration file.
+**
+** Parameters:
+** spec -- the alias specification
+**
+** Returns:
+** none.
+*/
+
+void
+setalias(spec)
+ char *spec;
+{
+ register char *p;
+ register MAP *map;
+ char *class;
+ STAB *s;
+
+ if (tTd(27, 8))
+ sm_dprintf("setalias(%s)\n", spec);
+
+ for (p = spec; p != NULL; )
+ {
+ char buf[50];
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ spec = p;
+
+ if (NAliasFileMaps >= MAXMAPSTACK)
+ {
+ syserr("Too many alias databases defined, %d max",
+ MAXMAPSTACK);
+ return;
+ }
+ if (AliasFileMap == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.files sequence",
+ sizeof buf);
+ AliasFileMap = makemapentry(buf);
+ if (AliasFileMap == NULL)
+ {
+ syserr("setalias: cannot create aliases.files map");
+ return;
+ }
+ }
+ (void) sm_snprintf(buf, sizeof buf, "Alias%d", NAliasFileMaps);
+ s = stab(buf, ST_MAP, ST_ENTER);
+ map = &s->s_map;
+ memset(map, '\0', sizeof *map);
+ map->map_mname = s->s_name;
+ p = strpbrk(p, ALIAS_SPEC_SEPARATORS);
+ if (p != NULL && *p == SEPARATOR)
+ {
+ /* map name */
+ *p++ = '\0';
+ class = spec;
+ spec = p;
+ }
+ else
+ {
+ class = "implicit";
+ map->map_mflags = MF_INCLNULL;
+ }
+
+ /* find end of spec */
+ if (p != NULL)
+ {
+ bool quoted = false;
+
+ for (; *p != '\0'; p++)
+ {
+ /*
+ ** Don't break into a quoted string.
+ ** Needed for ldap maps which use
+ ** commas in their specifications.
+ */
+
+ if (*p == '"')
+ quoted = !quoted;
+ else if (*p == ',' && !quoted)
+ break;
+ }
+
+ /* No more alias specifications follow */
+ if (*p == '\0')
+ p = NULL;
+ }
+ if (p != NULL)
+ *p++ = '\0';
+
+ if (tTd(27, 20))
+ sm_dprintf(" map %s:%s %s\n", class, s->s_name, spec);
+
+ /* look up class */
+ s = stab(class, ST_MAPCLASS, ST_FIND);
+ if (s == NULL)
+ {
+ syserr("setalias: unknown alias class %s", class);
+ }
+ else if (!bitset(MCF_ALIASOK, s->s_mapclass.map_cflags))
+ {
+ syserr("setalias: map class %s can't handle aliases",
+ class);
+ }
+ else
+ {
+ map->map_class = &s->s_mapclass;
+ map->map_mflags |= MF_ALIAS;
+ if (map->map_class->map_parse(map, spec))
+ {
+ map->map_mflags |= MF_VALID;
+ AliasFileMap->map_stack[NAliasFileMaps++] = map;
+ }
+ }
+ }
+}
+/*
+** ALIASWAIT -- wait for distinguished @:@ token to appear.
+**
+** This can decide to reopen or rebuild the alias file
+**
+** Parameters:
+** map -- a pointer to the map descriptor for this alias file.
+** ext -- the filename extension (e.g., ".db") for the
+** database file.
+** isopen -- if set, the database is already open, and we
+** should check for validity; otherwise, we are
+** just checking to see if it should be created.
+**
+** Returns:
+** true -- if the database is open when we return.
+** false -- if the database is closed when we return.
+*/
+
+bool
+aliaswait(map, ext, isopen)
+ MAP *map;
+ char *ext;
+ bool isopen;
+{
+ bool attimeout = false;
+ time_t mtime;
+ struct stat stb;
+ char buf[MAXPATHLEN];
+
+ if (tTd(27, 3))
+ sm_dprintf("aliaswait(%s:%s)\n",
+ map->map_class->map_cname, map->map_file);
+ if (bitset(MF_ALIASWAIT, map->map_mflags))
+ return isopen;
+ map->map_mflags |= MF_ALIASWAIT;
+
+ if (SafeAlias > 0)
+ {
+ auto int st;
+ unsigned int sleeptime = 2;
+ unsigned int loopcount = 0; /* only used for debugging */
+ time_t toolong = curtime() + SafeAlias;
+
+ while (isopen &&
+ map->map_class->map_lookup(map, "@", NULL, &st) == NULL)
+ {
+ if (curtime() > toolong)
+ {
+ /* we timed out */
+ attimeout = true;
+ break;
+ }
+
+ /*
+ ** Close and re-open the alias database in case
+ ** the one is mv'ed instead of cp'ed in.
+ */
+
+ if (tTd(27, 2))
+ {
+ loopcount++;
+ sm_dprintf("aliaswait: sleeping for %u seconds (loopcount = %u)\n",
+ sleeptime, loopcount);
+ }
+
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ (void) sleep(sleeptime);
+ sleeptime *= 2;
+ if (sleeptime > 60)
+ sleeptime = 60;
+ isopen = map->map_class->map_open(map, O_RDONLY);
+ }
+ }
+
+ /* see if we need to go into auto-rebuild mode */
+ if (!bitset(MCF_REBUILDABLE, map->map_class->map_cflags))
+ {
+ if (tTd(27, 3))
+ sm_dprintf("aliaswait: not rebuildable\n");
+ map->map_mflags &= ~MF_ALIASWAIT;
+ return isopen;
+ }
+ if (stat(map->map_file, &stb) < 0)
+ {
+ if (tTd(27, 3))
+ sm_dprintf("aliaswait: no source file\n");
+ map->map_mflags &= ~MF_ALIASWAIT;
+ return isopen;
+ }
+ mtime = stb.st_mtime;
+ if (sm_strlcpyn(buf, sizeof buf, 2,
+ map->map_file, ext == NULL ? "" : ext) >= sizeof buf)
+ {
+ if (LogLevel > 3)
+ sm_syslog(LOG_INFO, NOQID,
+ "alias database %s%s name too long",
+ map->map_file, ext == NULL ? "" : ext);
+ message("alias database %s%s name too long",
+ map->map_file, ext == NULL ? "" : ext);
+ }
+
+ if (stat(buf, &stb) < 0 || stb.st_mtime < mtime || attimeout)
+ {
+ if (LogLevel > 3)
+ sm_syslog(LOG_INFO, NOQID,
+ "alias database %s out of date", buf);
+ message("Warning: alias database %s out of date", buf);
+ }
+ map->map_mflags &= ~MF_ALIASWAIT;
+ return isopen;
+}
+/*
+** REBUILDALIASES -- rebuild the alias database.
+**
+** Parameters:
+** map -- the database to rebuild.
+** automatic -- set if this was automatically generated.
+**
+** Returns:
+** true if successful; false otherwise.
+**
+** Side Effects:
+** Reads the text version of the database, builds the
+** DBM or DB version.
+*/
+
+bool
+rebuildaliases(map, automatic)
+ register MAP *map;
+ bool automatic;
+{
+ SM_FILE_T *af;
+ bool nolock = false;
+ bool success = false;
+ long sff = SFF_OPENASROOT|SFF_REGONLY|SFF_NOLOCK;
+ sigfunc_t oldsigint, oldsigquit;
+#ifdef SIGTSTP
+ sigfunc_t oldsigtstp;
+#endif /* SIGTSTP */
+
+ if (!bitset(MCF_REBUILDABLE, map->map_class->map_cflags))
+ return false;
+
+ if (!bitnset(DBS_LINKEDALIASFILEINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ if (!bitnset(DBS_GROUPWRITABLEALIASFILE, DontBlameSendmail))
+ sff |= SFF_NOGWFILES;
+ if (!bitnset(DBS_WORLDWRITABLEALIASFILE, DontBlameSendmail))
+ sff |= SFF_NOWWFILES;
+
+ /* try to lock the source file */
+ if ((af = safefopen(map->map_file, O_RDWR, 0, sff)) == NULL)
+ {
+ struct stat stb;
+
+ if ((errno != EACCES && errno != EROFS) || automatic ||
+ (af = safefopen(map->map_file, O_RDONLY, 0, sff)) == NULL)
+ {
+ int saveerr = errno;
+
+ if (tTd(27, 1))
+ sm_dprintf("Can't open %s: %s\n",
+ map->map_file, sm_errstring(saveerr));
+ if (!automatic && !bitset(MF_OPTIONAL, map->map_mflags))
+ message("newaliases: cannot open %s: %s",
+ map->map_file, sm_errstring(saveerr));
+ errno = 0;
+ return false;
+ }
+ nolock = true;
+ if (tTd(27, 1) ||
+ fstat(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), &stb) < 0 ||
+ bitset(S_IWUSR|S_IWGRP|S_IWOTH, stb.st_mode))
+ message("warning: cannot lock %s: %s",
+ map->map_file, sm_errstring(errno));
+ }
+
+ /* see if someone else is rebuilding the alias file */
+ if (!nolock &&
+ !lockfile(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), map->map_file,
+ NULL, LOCK_EX|LOCK_NB))
+ {
+ /* yes, they are -- wait until done */
+ message("Alias file %s is locked (maybe being rebuilt)",
+ map->map_file);
+ if (OpMode != MD_INITALIAS)
+ {
+ /* wait for other rebuild to complete */
+ (void) lockfile(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL),
+ map->map_file, NULL, LOCK_EX);
+ }
+ (void) sm_io_close(af, SM_TIME_DEFAULT);
+ errno = 0;
+ return false;
+ }
+
+ oldsigint = sm_signal(SIGINT, SIG_IGN);
+ oldsigquit = sm_signal(SIGQUIT, SIG_IGN);
+#ifdef SIGTSTP
+ oldsigtstp = sm_signal(SIGTSTP, SIG_IGN);
+#endif /* SIGTSTP */
+
+ if (map->map_class->map_open(map, O_RDWR))
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_NOTICE, NOQID,
+ "alias database %s %srebuilt by %s",
+ map->map_file, automatic ? "auto" : "",
+ username());
+ }
+ map->map_mflags |= MF_OPEN|MF_WRITABLE;
+ map->map_pid = CurrentPid;
+ readaliases(map, af, !automatic, true);
+ success = true;
+ }
+ else
+ {
+ if (tTd(27, 1))
+ sm_dprintf("Can't create database for %s: %s\n",
+ map->map_file, sm_errstring(errno));
+ if (!automatic)
+ syserr("Cannot create database for alias file %s",
+ map->map_file);
+ }
+
+ /* close the file, thus releasing locks */
+ (void) sm_io_close(af, SM_TIME_DEFAULT);
+
+ /* add distinguished entries and close the database */
+ if (bitset(MF_OPEN, map->map_mflags))
+ {
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ }
+
+ /* restore the old signals */
+ (void) sm_signal(SIGINT, oldsigint);
+ (void) sm_signal(SIGQUIT, oldsigquit);
+#ifdef SIGTSTP
+ (void) sm_signal(SIGTSTP, oldsigtstp);
+#endif /* SIGTSTP */
+ return success;
+}
+/*
+** READALIASES -- read and process the alias file.
+**
+** This routine implements the part of initaliases that occurs
+** when we are not going to use the DBM stuff.
+**
+** Parameters:
+** map -- the alias database descriptor.
+** af -- file to read the aliases from.
+** announcestats -- announce statistics regarding number of
+** aliases, longest alias, etc.
+** logstats -- lot the same info.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Reads aliasfile into the symbol table.
+** Optionally, builds the .dir & .pag files.
+*/
+
+void
+readaliases(map, af, announcestats, logstats)
+ register MAP *map;
+ SM_FILE_T *af;
+ bool announcestats;
+ bool logstats;
+{
+ register char *p;
+ char *rhs;
+ bool skipping;
+ long naliases, bytes, longest;
+ ADDRESS al, bl;
+ char line[BUFSIZ];
+
+ /*
+ ** Read and interpret lines
+ */
+
+ FileName = map->map_file;
+ LineNumber = 0;
+ naliases = bytes = longest = 0;
+ skipping = false;
+ while (sm_io_fgets(af, SM_TIME_DEFAULT, line, sizeof line) != NULL)
+ {
+ int lhssize, rhssize;
+ int c;
+
+ LineNumber++;
+ p = strchr(line, '\n');
+
+ /* XXX what if line="a\\" ? */
+ while (p != NULL && p > line && p[-1] == '\\')
+ {
+ p--;
+ if (sm_io_fgets(af, SM_TIME_DEFAULT, p,
+ SPACELEFT(line, p)) == NULL)
+ break;
+ LineNumber++;
+ p = strchr(p, '\n');
+ }
+ if (p != NULL)
+ *p = '\0';
+ else if (!sm_io_eof(af))
+ {
+ errno = 0;
+ syserr("554 5.3.0 alias line too long");
+
+ /* flush to end of line */
+ while ((c = sm_io_getc(af, SM_TIME_DEFAULT)) !=
+ SM_IO_EOF && c != '\n')
+ continue;
+
+ /* skip any continuation lines */
+ skipping = true;
+ continue;
+ }
+ switch (line[0])
+ {
+ case '#':
+ case '\0':
+ skipping = false;
+ continue;
+
+ case ' ':
+ case '\t':
+ if (!skipping)
+ syserr("554 5.3.5 Non-continuation line starts with space");
+ skipping = true;
+ continue;
+ }
+ skipping = false;
+
+ /*
+ ** Process the LHS
+ ** Find the colon separator, and parse the address.
+ ** It should resolve to a local name -- this will
+ ** be checked later (we want to optionally do
+ ** parsing of the RHS first to maximize error
+ ** detection).
+ */
+
+ for (p = line; *p != '\0' && *p != ':' && *p != '\n'; p++)
+ continue;
+ if (*p++ != ':')
+ {
+ syserr("554 5.3.5 missing colon");
+ continue;
+ }
+ if (parseaddr(line, &al, RF_COPYALL, ':', NULL, CurEnv, true)
+ == NULL)
+ {
+ syserr("554 5.3.5 %.40s... illegal alias name", line);
+ continue;
+ }
+
+ /*
+ ** Process the RHS.
+ ** 'al' is the internal form of the LHS address.
+ ** 'p' points to the text of the RHS.
+ */
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ rhs = p;
+ for (;;)
+ {
+ register char *nlp;
+
+ nlp = &p[strlen(p)];
+ if (nlp > p && nlp[-1] == '\n')
+ *--nlp = '\0';
+
+ if (CheckAliases)
+ {
+ /* do parsing & compression of addresses */
+ while (*p != '\0')
+ {
+ auto char *delimptr;
+
+ while ((isascii(*p) && isspace(*p)) ||
+ *p == ',')
+ p++;
+ if (*p == '\0')
+ break;
+ if (parseaddr(p, &bl, RF_COPYNONE, ',',
+ &delimptr, CurEnv, true)
+ == NULL)
+ usrerr("553 5.3.5 %s... bad address", p);
+ p = delimptr;
+ }
+ }
+ else
+ {
+ p = nlp;
+ }
+
+ /* see if there should be a continuation line */
+ c = sm_io_getc(af, SM_TIME_DEFAULT);
+ if (!sm_io_eof(af))
+ (void) sm_io_ungetc(af, SM_TIME_DEFAULT, c);
+ if (c != ' ' && c != '\t')
+ break;
+
+ /* read continuation line */
+ if (sm_io_fgets(af, SM_TIME_DEFAULT, p,
+ sizeof line - (p-line)) == NULL)
+ break;
+ LineNumber++;
+
+ /* check for line overflow */
+ if (strchr(p, '\n') == NULL && !sm_io_eof(af))
+ {
+ usrerr("554 5.3.5 alias too long");
+ while ((c = sm_io_getc(af, SM_TIME_DEFAULT))
+ != SM_IO_EOF && c != '\n')
+ continue;
+ skipping = true;
+ break;
+ }
+ }
+
+ if (skipping)
+ continue;
+
+ if (!bitnset(M_ALIASABLE, al.q_mailer->m_flags))
+ {
+ syserr("554 5.3.5 %s... cannot alias non-local names",
+ al.q_paddr);
+ continue;
+ }
+
+ /*
+ ** Insert alias into symbol table or database file.
+ **
+ ** Special case pOStmaStER -- always make it lower case.
+ */
+
+ if (sm_strcasecmp(al.q_user, "postmaster") == 0)
+ makelower(al.q_user);
+
+ lhssize = strlen(al.q_user);
+ rhssize = strlen(rhs);
+ if (rhssize > 0)
+ {
+ /* is RHS empty (just spaces)? */
+ p = rhs;
+ while (isascii(*p) && isspace(*p))
+ p++;
+ }
+ if (rhssize == 0 || *p == '\0')
+ {
+ syserr("554 5.3.5 %.40s... missing value for alias",
+ line);
+
+ }
+ else
+ {
+ map->map_class->map_store(map, al.q_user, rhs);
+
+ /* statistics */
+ naliases++;
+ bytes += lhssize + rhssize;
+ if (rhssize > longest)
+ longest = rhssize;
+ }
+
+#if 0
+ /*
+ ** address strings are now stored in the envelope rpool,
+ ** and therefore cannot be freed.
+ */
+ if (al.q_paddr != NULL)
+ sm_free(al.q_paddr); /* disabled */
+ if (al.q_host != NULL)
+ sm_free(al.q_host); /* disabled */
+ if (al.q_user != NULL)
+ sm_free(al.q_user); /* disabled */
+#endif /* 0 */
+ }
+
+ CurEnv->e_to = NULL;
+ FileName = NULL;
+ if (Verbose || announcestats)
+ message("%s: %ld aliases, longest %ld bytes, %ld bytes total",
+ map->map_file, naliases, longest, bytes);
+ if (LogLevel > 7 && logstats)
+ sm_syslog(LOG_INFO, NOQID,
+ "%s: %ld aliases, longest %ld bytes, %ld bytes total",
+ map->map_file, naliases, longest, bytes);
+}
+/*
+** FORWARD -- Try to forward mail
+**
+** This is similar but not identical to aliasing.
+**
+** Parameters:
+** user -- the name of the user who's mail we would like
+** to forward to. It must have been verified --
+** i.e., the q_home field must have been filled
+** in.
+** sendq -- a pointer to the head of the send queue to
+** put this user's aliases in.
+** aliaslevel -- the current alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** New names are added to send queues.
+*/
+
+void
+forward(user, sendq, aliaslevel, e)
+ ADDRESS *user;
+ ADDRESS **sendq;
+ int aliaslevel;
+ register ENVELOPE *e;
+{
+ char *pp;
+ char *ep;
+ bool got_transient;
+
+ if (tTd(27, 1))
+ sm_dprintf("forward(%s)\n", user->q_paddr);
+
+ if (!bitnset(M_HASPWENT, user->q_mailer->m_flags) ||
+ !QS_IS_OK(user->q_state))
+ return;
+ if (ForwardPath != NULL && *ForwardPath == '\0')
+ return;
+ if (user->q_home == NULL)
+ {
+ syserr("554 5.3.0 forward: no home");
+ user->q_home = "/no/such/directory";
+ }
+
+ /* good address -- look for .forward file in home */
+ macdefine(&e->e_macro, A_PERM, 'z', user->q_home);
+ macdefine(&e->e_macro, A_PERM, 'u', user->q_user);
+ macdefine(&e->e_macro, A_PERM, 'h', user->q_host);
+ if (ForwardPath == NULL)
+ ForwardPath = newstr("\201z/.forward");
+
+ got_transient = false;
+ for (pp = ForwardPath; pp != NULL; pp = ep)
+ {
+ int err;
+ char buf[MAXPATHLEN];
+ struct stat st;
+
+ ep = strchr(pp, SEPARATOR);
+ if (ep != NULL)
+ *ep = '\0';
+ expand(pp, buf, sizeof buf, e);
+ if (ep != NULL)
+ *ep++ = SEPARATOR;
+ if (buf[0] == '\0')
+ continue;
+ if (tTd(27, 3))
+ sm_dprintf("forward: trying %s\n", buf);
+
+ err = include(buf, true, user, sendq, aliaslevel, e);
+ if (err == 0)
+ break;
+ else if (transienterror(err))
+ {
+ /* we may have to suspend this message */
+ got_transient = true;
+ if (tTd(27, 2))
+ sm_dprintf("forward: transient error on %s\n",
+ buf);
+ if (LogLevel > 2)
+ {
+ char *curhost = CurHostName;
+
+ CurHostName = NULL;
+ sm_syslog(LOG_ERR, e->e_id,
+ "forward %s: transient error: %s",
+ buf, sm_errstring(err));
+ CurHostName = curhost;
+ }
+
+ }
+ else
+ {
+ switch (err)
+ {
+ case ENOENT:
+ break;
+
+ case E_SM_WWDIR:
+ case E_SM_GWDIR:
+ /* check if it even exists */
+ if (stat(buf, &st) < 0 && errno == ENOENT)
+ {
+ if (bitnset(DBS_DONTWARNFORWARDFILEINUNSAFEDIRPATH,
+ DontBlameSendmail))
+ break;
+ }
+ /* FALLTHROUGH */
+
+#if _FFR_FORWARD_SYSERR
+ case E_SM_NOSLINK:
+ case E_SM_NOHLINK:
+ case E_SM_REGONLY:
+ case E_SM_ISEXEC:
+ case E_SM_WWFILE:
+ case E_SM_GWFILE:
+ syserr("forward: %s: %s", buf, sm_errstring(err));
+ break;
+#endif /* _FFR_FORWARD_SYSERR */
+
+ default:
+ if (LogLevel > (RunAsUid == 0 ? 2 : 10))
+ sm_syslog(LOG_WARNING, e->e_id,
+ "forward %s: %s", buf,
+ sm_errstring(err));
+ if (Verbose)
+ message("forward: %s: %s",
+ buf, sm_errstring(err));
+ break;
+ }
+ }
+ }
+ if (pp == NULL && got_transient)
+ {
+ /*
+ ** There was no successful .forward open and at least one
+ ** transient open. We have to defer this address for
+ ** further delivery.
+ */
+
+ message("transient .forward open error: message queued");
+ user->q_state = QS_QUEUEUP;
+ return;
+ }
+}
diff --git a/contrib/sendmail/src/aliases b/contrib/sendmail/src/aliases
new file mode 100644
index 0000000..2d06ae3
--- /dev/null
+++ b/contrib/sendmail/src/aliases
@@ -0,0 +1,65 @@
+#
+# $Id: aliases,v 8.5 2002/06/05 22:54:26 gshapiro Exp $
+# @(#)aliases 8.2 (Berkeley) 3/5/94
+#
+# Aliases in this file will NOT be expanded in the header from
+# Mail, but WILL be visible over networks.
+#
+# >>>>>>>>>> The program "newaliases" must be run after
+# >> NOTE >> this file is updated for any changes to
+# >>>>>>>>>> show through to sendmail.
+#
+#
+# See also RFC 2142, `MAILBOX NAMES FOR COMMON SERVICES, ROLES
+# AND FUNCTIONS', May 1997
+
+# Pretty much everything else in this file points to "root", so
+# you should forward root's email to the system administrator.
+# Delivering mail to root's mailbox or reading mail as root is
+# inadvisable.
+
+# Uncomment and *CHANGE* this!
+# root: insert-human-being-here
+
+# Basic system aliases -- these MUST be present
+MAILER-DAEMON: postmaster
+postmaster: root
+
+# General redirections for pseudo accounts
+bin: root
+daemon: root
+games: root
+mailnull: postmaster
+smmsp: postmaster
+ingres: root
+nobody: root
+system: root
+toor: root
+
+# Well-known aliases
+manager: root
+dumper: root
+operator: root
+
+# RFC 2142: BUSINESS-RELATED MAILBOX NAMES
+# info: root
+# marketing: root
+# sales: root
+# support: root
+
+# RFC 2142: NETWORK OPERATIONS MAILBOX NAMES
+abuse: root
+noc: root
+security: root
+
+# RFC 2142: SUPPORT MAILBOX NAMES FOR SPECIFIC INTERNET SERVICES
+hostmaster: root
+usenet: root
+news: usenet
+webmaster: root
+www: webmaster
+uucp: root
+ftp: root
+
+# Trap decode to catch security attacks
+decode: root
diff --git a/contrib/sendmail/src/aliases.5 b/contrib/sendmail/src/aliases.5
new file mode 100644
index 0000000..1678475
--- /dev/null
+++ b/contrib/sendmail/src/aliases.5
@@ -0,0 +1,121 @@
+.\" Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers.
+.\" All rights reserved.
+.\" Copyright (c) 1983, 1997 Eric P. Allman. All rights reserved.
+.\" Copyright (c) 1985, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" By using this file, you agree to the terms and conditions set
+.\" forth in the LICENSE file which can be found at the top level of
+.\" the sendmail distribution.
+.\"
+.\"
+.\" $Id: aliases.5,v 8.17 2000/12/14 23:09:46 gshapiro Exp $
+.\"
+.TH ALIASES 5 "$Date: 2000/12/14 23:09:46 $"
+.SH NAME
+aliases
+\- aliases file for sendmail
+.SH SYNOPSIS
+.B aliases
+.SH DESCRIPTION
+This file describes user
+ID
+aliases used by
+sendmail.
+The file resides in
+/etc/mail
+and
+is formatted as a series of lines of the form
+.IP
+name: addr_1, addr_2, addr_3, . . .
+.PP
+The
+.I name
+is the name to alias, and the
+.I addr_n
+are the aliases for that name.
+.I addr_n
+can be another alias, a local username, a local filename,
+a command,
+an include file,
+or an external address.
+.TP
+.B Local Username
+username
+.IP
+The username must be available via getpwnam(3).
+.TP
+.B Local Filename
+/path/name
+.IP
+Messages are appended to the file specified by the full pathname
+(starting with a slash (/))
+.TP
+.B Command
+|command
+.IP
+A command starts with a pipe symbol (|),
+it receives messages via standard input.
+.TP
+.B Include File
+:include: /path/name
+.IP
+The aliases in pathname are added to the aliases for
+.I name.
+.TP
+.B E-Mail Address
+user@domain
+.IP
+An e-mail address in RFC 822 format.
+.PP
+Lines beginning with white space are continuation lines.
+Another way to continue lines is by placing a backslash
+directly before a newline.
+Lines beginning with
+#
+are comments.
+.PP
+Aliasing occurs only on local names.
+Loops can not occur, since no message will be sent to any person more than once.
+.PP
+After aliasing has been done, local and valid recipients who have a
+``.forward''
+file in their home directory have messages forwarded to the
+list of users defined in that file.
+.PP
+This is only the raw data file; the actual aliasing information is
+placed into a binary format in the file
+/etc/mail/aliases.db
+using the program
+newaliases(1).
+A
+newaliases
+command should be executed each time the aliases file is changed for the
+change to take effect.
+.SH SEE ALSO
+newaliases(1),
+dbm(3),
+dbopen(3),
+db_open(3),
+sendmail(8)
+.PP
+.I
+SENDMAIL Installation and Operation Guide.
+.PP
+.I
+SENDMAIL An Internetwork Mail Router.
+.SH BUGS
+If you have compiled
+sendmail
+with DBM support instead of NEWDB,
+you may have encountered problems in
+dbm(3)
+restricting a single alias to about 1000 bytes of information.
+You can get longer aliases by ``chaining''; that is, make the last name in
+the alias be a dummy name which is a continuation alias.
+.SH HISTORY
+The
+.B aliases
+file format appeared in
+4.0BSD.
+.\" $FreeBSD$
diff --git a/contrib/sendmail/src/arpadate.c b/contrib/sendmail/src/arpadate.c
new file mode 100644
index 0000000..16082cd
--- /dev/null
+++ b/contrib/sendmail/src/arpadate.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: arpadate.c,v 8.30 2001/09/11 04:05:12 gshapiro Exp $")
+
+/*
+** ARPADATE -- Create date in ARPANET format
+**
+** Parameters:
+** ud -- unix style date string. if NULL, one is created.
+**
+** Returns:
+** pointer to an ARPANET date field
+**
+** Side Effects:
+** none
+**
+** WARNING:
+** date is stored in a local buffer -- subsequent
+** calls will overwrite.
+**
+** Bugs:
+** Timezone is computed from local time, rather than
+** from wherever (and whenever) the message was sent.
+** To do better is very hard.
+**
+** Some sites are now inserting the timezone into the
+** local date. This routine should figure out what
+** the format is and work appropriately.
+*/
+
+#ifndef TZNAME_MAX
+# define TZNAME_MAX 50 /* max size of timezone */
+#endif /* ! TZNAME_MAX */
+
+/* values for TZ_TYPE */
+#define TZ_NONE 0 /* no character timezone support */
+#define TZ_TM_NAME 1 /* use tm->tm_name */
+#define TZ_TM_ZONE 2 /* use tm->tm_zone */
+#define TZ_TZNAME 3 /* use tzname[] */
+#define TZ_TIMEZONE 4 /* use timezone() */
+
+char *
+arpadate(ud)
+ register char *ud;
+{
+ register char *p;
+ register char *q;
+ register int off;
+ register int i;
+ register struct tm *lt;
+ time_t t;
+ struct tm gmt;
+ char *tz;
+ static char b[43 + TZNAME_MAX];
+
+ /*
+ ** Get current time.
+ ** This will be used if a null argument is passed and
+ ** to resolve the timezone.
+ */
+
+ /* SM_REQUIRE(ud == NULL || strlen(ud) >= 23); */
+ t = curtime();
+ if (ud == NULL)
+ ud = ctime(&t);
+
+ /*
+ ** Crack the UNIX date line in a singularly unoriginal way.
+ */
+
+ q = b;
+
+ p = &ud[0]; /* Mon */
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = ',';
+ *q++ = ' ';
+
+ p = &ud[8]; /* 16 */
+ if (*p == ' ')
+ p++;
+ else
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = ' ';
+
+ p = &ud[4]; /* Sep */
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = ' ';
+
+ p = &ud[20]; /* 1979 */
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = *p++;
+ *q++ = ' ';
+
+ p = &ud[11]; /* 01:03:52 */
+ for (i = 8; i > 0; i--)
+ *q++ = *p++;
+
+ /*
+ ** should really get the timezone from the time in "ud" (which
+ ** is only different if a non-null arg was passed which is different
+ ** from the current time), but for all practical purposes, returning
+ ** the current local zone will do (its all that is ever needed).
+ */
+
+ gmt = *gmtime(&t);
+ lt = localtime(&t);
+
+ off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
+
+ /* assume that offset isn't more than a day ... */
+ if (lt->tm_year < gmt.tm_year)
+ off -= 24 * 60;
+ else if (lt->tm_year > gmt.tm_year)
+ off += 24 * 60;
+ else if (lt->tm_yday < gmt.tm_yday)
+ off -= 24 * 60;
+ else if (lt->tm_yday > gmt.tm_yday)
+ off += 24 * 60;
+
+ *q++ = ' ';
+ if (off == 0)
+ {
+ *q++ = 'G';
+ *q++ = 'M';
+ *q++ = 'T';
+ }
+ else
+ {
+ tz = NULL;
+#if TZ_TYPE == TZ_TM_NAME
+ tz = lt->tm_name;
+#endif /* TZ_TYPE == TZ_TM_NAME */
+#if TZ_TYPE == TZ_TM_ZONE
+ tz = lt->tm_zone;
+#endif /* TZ_TYPE == TZ_TM_ZONE */
+#if TZ_TYPE == TZ_TZNAME
+ {
+ extern char *tzname[];
+
+ if (lt->tm_isdst > 0)
+ tz = tzname[1];
+ else if (lt->tm_isdst == 0)
+ tz = tzname[0];
+ else
+ tz = NULL;
+ }
+#endif /* TZ_TYPE == TZ_TZNAME */
+#if TZ_TYPE == TZ_TIMEZONE
+ {
+ extern char *timezone();
+
+ tz = timezone(off, lt->tm_isdst);
+ }
+#endif /* TZ_TYPE == TZ_TIMEZONE */
+ if (off < 0)
+ {
+ off = -off;
+ *q++ = '-';
+ }
+ else
+ *q++ = '+';
+
+ if (off >= 24*60) /* should be impossible */
+ off = 23*60+59; /* if not, insert silly value */
+
+ *q++ = (off / 600) + '0';
+ *q++ = (off / 60) % 10 + '0';
+ off %= 60;
+ *q++ = (off / 10) + '0';
+ *q++ = (off % 10) + '0';
+ if (tz != NULL && *tz != '\0')
+ {
+ *q++ = ' ';
+ *q++ = '(';
+ while (*tz != '\0' && q < &b[sizeof b - 3])
+ *q++ = *tz++;
+ *q++ = ')';
+ }
+ }
+ *q = '\0';
+
+ return b;
+}
diff --git a/contrib/sendmail/src/bf.c b/contrib/sendmail/src/bf.c
new file mode 100644
index 0000000..f678308
--- /dev/null
+++ b/contrib/sendmail/src/bf.c
@@ -0,0 +1,858 @@
+/*
+ * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * Contributed by Exactis.com, Inc.
+ *
+ */
+
+/*
+** This is in transition. Changed from the original bf_torek.c code
+** to use sm_io function calls directly rather than through stdio
+** translation layer. Will be made a built-in file type of libsm
+** next (once safeopen() linkable from libsm).
+*/
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: bf.c,v 8.54.2.2 2002/06/21 19:58:40 gshapiro Exp $")
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "sendmail.h"
+#include "bf.h"
+
+#include <syslog.h>
+
+/* bf io functions */
+static ssize_t sm_bfread __P((SM_FILE_T *, char *, size_t));
+static ssize_t sm_bfwrite __P((SM_FILE_T *, const char *, size_t));
+static off_t sm_bfseek __P((SM_FILE_T *, off_t, int));
+static int sm_bfclose __P((SM_FILE_T *));
+
+static int sm_bfopen __P((SM_FILE_T *, const void *, int, const void *));
+static int sm_bfsetinfo __P((SM_FILE_T *, int , void *));
+static int sm_bfgetinfo __P((SM_FILE_T *, int , void *));
+
+/*
+** Data structure for storing information about each buffered file
+** (Originally in sendmail/bf_torek.h for the curious.)
+*/
+
+struct bf
+{
+ bool bf_committed; /* Has this buffered file been committed? */
+ bool bf_ondisk; /* On disk: committed or buffer overflow */
+ long bf_flags;
+ int bf_disk_fd; /* If on disk, associated file descriptor */
+ char *bf_buf; /* Memory buffer */
+ int bf_bufsize; /* Length of above buffer */
+ int bf_buffilled; /* Bytes of buffer actually filled */
+ char *bf_filename; /* Name of buffered file, if ever committed */
+ MODE_T bf_filemode; /* Mode of buffered file, if ever committed */
+ off_t bf_offset; /* Currect file offset */
+ int bf_size; /* Total current size of file */
+};
+
+#ifdef BF_STANDALONE
+# define OPEN(fn, omode, cmode, sff) open(fn, omode, cmode)
+#else /* BF_STANDALONE */
+# define OPEN(fn, omode, cmode, sff) safeopen(fn, omode, cmode, sff)
+#endif /* BF_STANDALONE */
+
+struct bf_info
+{
+ char *bi_filename;
+ MODE_T bi_fmode;
+ size_t bi_bsize;
+ long bi_flags;
+};
+
+/*
+** SM_BFOPEN -- the "base" open function called by sm_io_open() for the
+** internal, file-type-specific info setup.
+**
+** Parameters:
+** fp -- file pointer being filled-in for file being open'd
+** info -- information about file being opened
+** flags -- ignored
+** rpool -- ignored (currently)
+**
+** Returns:
+** Failure: -1 and sets errno
+** Success: 0 (zero)
+*/
+
+static int
+sm_bfopen(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ char *filename;
+ MODE_T fmode;
+ size_t bsize;
+ long sflags;
+ struct bf *bfp;
+ int l;
+ struct stat st;
+
+ filename = ((struct bf_info *) info)->bi_filename;
+ fmode = ((struct bf_info *) info)->bi_fmode;
+ bsize = ((struct bf_info *) info)->bi_bsize;
+ sflags = ((struct bf_info *) info)->bi_flags;
+
+ /* Sanity checks */
+ if (*filename == '\0')
+ {
+ /* Empty filename string */
+ errno = ENOENT;
+ return -1;
+ }
+ if (stat(filename, &st) == 0)
+ {
+ /* File already exists on disk */
+ errno = EEXIST;
+ return -1;
+ }
+
+ /* Allocate memory */
+ bfp = (struct bf *) sm_malloc(sizeof(struct bf));
+ if (bfp == NULL)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* Assign data buffer */
+ /* A zero bsize is valid, just don't allocate memory */
+ if (bsize > 0)
+ {
+ bfp->bf_buf = (char *) sm_malloc(bsize);
+ if (bfp->bf_buf == NULL)
+ {
+ bfp->bf_bufsize = 0;
+ sm_free(bfp);
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+ else
+ bfp->bf_buf = NULL;
+
+ /* Nearly home free, just set all the parameters now */
+ bfp->bf_committed = false;
+ bfp->bf_ondisk = false;
+ bfp->bf_flags = sflags;
+ bfp->bf_bufsize = bsize;
+ bfp->bf_buffilled = 0;
+ l = strlen(filename) + 1;
+ bfp->bf_filename = (char *) sm_malloc(l);
+ if (bfp->bf_filename == NULL)
+ {
+ if (bfp->bf_buf != NULL)
+ sm_free(bfp->bf_buf);
+ sm_free(bfp);
+ errno = ENOMEM;
+ return -1;
+ }
+ (void) sm_strlcpy(bfp->bf_filename, filename, l);
+ bfp->bf_filemode = fmode;
+ bfp->bf_offset = 0;
+ bfp->bf_size = 0;
+ bfp->bf_disk_fd = -1;
+ fp->f_cookie = bfp;
+
+ if (tTd(58, 8))
+ sm_dprintf("sm_bfopen(%s)\n", filename);
+
+ return 0;
+}
+
+/*
+** BFOPEN -- create a new buffered file
+**
+** Parameters:
+** filename -- the file's name
+** fmode -- what mode the file should be created as
+** bsize -- amount of buffer space to allocate (may be 0)
+** flags -- if running under sendmail, passed directly to safeopen
+**
+** Returns:
+** a SM_FILE_T * which may then be used with stdio functions,
+** or NULL on failure. SM_FILE_T * is opened for writing
+** "SM_IO_WHAT_VECTORS").
+**
+** Side Effects:
+** none.
+**
+** Sets errno:
+** any value of errno specified by sm_io_setinfo_type()
+** any value of errno specified by sm_io_open()
+** any value of errno specified by sm_io_setinfo()
+*/
+
+#ifdef __STDC__
+/*
+** XXX This is a temporary hack since MODE_T on HP-UX 10.x is short.
+** If we use K&R here, the compiler will complain about
+** Inconsistent parameter list declaration
+** due to the change from short to int.
+*/
+
+SM_FILE_T *
+bfopen(char *filename, MODE_T fmode, size_t bsize, long flags)
+#else /* __STDC__ */
+SM_FILE_T *
+bfopen(filename, fmode, bsize, flags)
+ char *filename;
+ MODE_T fmode;
+ size_t bsize;
+ long flags;
+#endif /* __STDC__ */
+{
+ MODE_T omask;
+ SM_FILE_T SM_IO_SET_TYPE(vector, BF_FILE_TYPE, sm_bfopen, sm_bfclose,
+ sm_bfread, sm_bfwrite, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo,
+ SM_TIME_FOREVER);
+ struct bf_info info;
+
+ /*
+ ** Apply current umask to fmode as it may change by the time
+ ** the file is actually created. fmode becomes the true
+ ** permissions of the file, which OPEN() must obey.
+ */
+
+ omask = umask(0);
+ fmode &= ~omask;
+ (void) umask(omask);
+
+ SM_IO_INIT_TYPE(vector, BF_FILE_TYPE, sm_bfopen, sm_bfclose,
+ sm_bfread, sm_bfwrite, sm_bfseek, sm_bfgetinfo, sm_bfsetinfo,
+ SM_TIME_FOREVER);
+ info.bi_filename = filename;
+ info.bi_fmode = fmode;
+ info.bi_bsize = bsize;
+ info.bi_flags = flags;
+
+ return sm_io_open(&vector, SM_TIME_DEFAULT, &info, SM_IO_RDWR, NULL);
+}
+
+/*
+** SM_BFGETINFO -- returns info about an open file pointer
+**
+** Parameters:
+** fp -- file pointer to get info about
+** what -- type of info to obtain
+** valp -- thing to return the info in
+*/
+
+static int
+sm_bfgetinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ struct bf *bfp;
+
+ bfp = (struct bf *) fp->f_cookie;
+ switch (what)
+ {
+ case SM_IO_WHAT_FD:
+ return bfp->bf_disk_fd;
+ case SM_IO_WHAT_SIZE:
+ return bfp->bf_size;
+ default:
+ return -1;
+ }
+}
+
+/*
+** SM_BFCLOSE -- close a buffered file
+**
+** Parameters:
+** fp -- cookie of file to close
+**
+** Returns:
+** 0 to indicate success
+**
+** Side Effects:
+** deletes backing file, sm_frees memory.
+**
+** Sets errno:
+** never.
+*/
+
+static int
+sm_bfclose(fp)
+ SM_FILE_T *fp;
+{
+ struct bf *bfp;
+
+ /* Cast cookie back to correct type */
+ bfp = (struct bf *) fp->f_cookie;
+
+ /* Need to clean up the file */
+ if (bfp->bf_ondisk && !bfp->bf_committed)
+ unlink(bfp->bf_filename);
+ sm_free(bfp->bf_filename);
+
+ if (bfp->bf_disk_fd != -1)
+ close(bfp->bf_disk_fd);
+
+ /* Need to sm_free the buffer */
+ if (bfp->bf_bufsize > 0)
+ sm_free(bfp->bf_buf);
+
+ /* Finally, sm_free the structure */
+ sm_free(bfp);
+ return 0;
+}
+
+/*
+** SM_BFREAD -- read a buffered file
+**
+** Parameters:
+** cookie -- cookie of file to read
+** buf -- buffer to fill
+** nbytes -- how many bytes to read
+**
+** Returns:
+** number of bytes read or -1 indicate failure
+**
+** Side Effects:
+** none.
+**
+*/
+
+static ssize_t
+sm_bfread(fp, buf, nbytes)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t nbytes;
+{
+ struct bf *bfp;
+ ssize_t count = 0; /* Number of bytes put in buf so far */
+ int retval;
+
+ /* Cast cookie back to correct type */
+ bfp = (struct bf *) fp->f_cookie;
+
+ if (bfp->bf_offset < bfp->bf_buffilled)
+ {
+ /* Need to grab some from buffer */
+ count = nbytes;
+ if ((bfp->bf_offset + count) > bfp->bf_buffilled)
+ count = bfp->bf_buffilled - bfp->bf_offset;
+
+ memcpy(buf, bfp->bf_buf + bfp->bf_offset, count);
+ }
+
+ if ((bfp->bf_offset + nbytes) > bfp->bf_buffilled)
+ {
+ /* Need to grab some from file */
+ if (!bfp->bf_ondisk)
+ {
+ /* Oops, the file doesn't exist. EOF. */
+ if (tTd(58, 8))
+ sm_dprintf("sm_bfread(%s): to disk\n",
+ bfp->bf_filename);
+ goto finished;
+ }
+
+ /* Catch a read() on an earlier failed write to disk */
+ if (bfp->bf_disk_fd < 0)
+ {
+ errno = EIO;
+ return -1;
+ }
+
+ if (lseek(bfp->bf_disk_fd,
+ bfp->bf_offset + count, SEEK_SET) < 0)
+ {
+ if ((errno == EINVAL) || (errno == ESPIPE))
+ {
+ /*
+ ** stdio won't be expecting these
+ ** errnos from read()! Change them
+ ** into something it can understand.
+ */
+
+ errno = EIO;
+ }
+ return -1;
+ }
+
+ while (count < nbytes)
+ {
+ retval = read(bfp->bf_disk_fd,
+ buf + count,
+ nbytes - count);
+ if (retval < 0)
+ {
+ /* errno is set implicitly by read() */
+ return -1;
+ }
+ else if (retval == 0)
+ goto finished;
+ else
+ count += retval;
+ }
+ }
+
+finished:
+ bfp->bf_offset += count;
+ return count;
+}
+
+/*
+** SM_BFSEEK -- seek to a position in a buffered file
+**
+** Parameters:
+** fp -- fp of file to seek
+** offset -- position to seek to
+** whence -- how to seek
+**
+** Returns:
+** new file offset or -1 indicate failure
+**
+** Side Effects:
+** none.
+**
+*/
+
+static off_t
+sm_bfseek(fp, offset, whence)
+ SM_FILE_T *fp;
+ off_t offset;
+ int whence;
+
+{
+ struct bf *bfp;
+
+ /* Cast cookie back to correct type */
+ bfp = (struct bf *) fp->f_cookie;
+
+ switch (whence)
+ {
+ case SEEK_SET:
+ bfp->bf_offset = offset;
+ break;
+
+ case SEEK_CUR:
+ bfp->bf_offset += offset;
+ break;
+
+ case SEEK_END:
+ bfp->bf_offset = bfp->bf_size + offset;
+ break;
+
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ return bfp->bf_offset;
+}
+
+/*
+** SM_BFWRITE -- write to a buffered file
+**
+** Parameters:
+** fp -- fp of file to write
+** buf -- data buffer
+** nbytes -- how many bytes to write
+**
+** Returns:
+** number of bytes written or -1 indicate failure
+**
+** Side Effects:
+** may create backing file if over memory limit for file.
+**
+*/
+
+static ssize_t
+sm_bfwrite(fp, buf, nbytes)
+ SM_FILE_T *fp;
+ const char *buf;
+ size_t nbytes;
+{
+ struct bf *bfp;
+ ssize_t count = 0; /* Number of bytes written so far */
+ int retval;
+
+ /* Cast cookie back to correct type */
+ bfp = (struct bf *) fp->f_cookie;
+
+ /* If committed, go straight to disk */
+ if (bfp->bf_committed)
+ {
+ if (lseek(bfp->bf_disk_fd, bfp->bf_offset, SEEK_SET) < 0)
+ {
+ if ((errno == EINVAL) || (errno == ESPIPE))
+ {
+ /*
+ ** stdio won't be expecting these
+ ** errnos from write()! Change them
+ ** into something it can understand.
+ */
+
+ errno = EIO;
+ }
+ return -1;
+ }
+
+ count = write(bfp->bf_disk_fd, buf, nbytes);
+ if (count < 0)
+ {
+ /* errno is set implicitly by write() */
+ return -1;
+ }
+ goto finished;
+ }
+
+ if (bfp->bf_offset < bfp->bf_bufsize)
+ {
+ /* Need to put some in buffer */
+ count = nbytes;
+ if ((bfp->bf_offset + count) > bfp->bf_bufsize)
+ count = bfp->bf_bufsize - bfp->bf_offset;
+
+ memcpy(bfp->bf_buf + bfp->bf_offset, buf, count);
+ if ((bfp->bf_offset + count) > bfp->bf_buffilled)
+ bfp->bf_buffilled = bfp->bf_offset + count;
+ }
+
+ if ((bfp->bf_offset + nbytes) > bfp->bf_bufsize)
+ {
+ /* Need to put some in file */
+ if (!bfp->bf_ondisk)
+ {
+ MODE_T omask;
+
+ /* Clear umask as bf_filemode are the true perms */
+ omask = umask(0);
+ retval = OPEN(bfp->bf_filename,
+ O_RDWR | O_CREAT | O_TRUNC,
+ bfp->bf_filemode, bfp->bf_flags);
+ (void) umask(omask);
+
+ /* Couldn't create file: failure */
+ if (retval < 0)
+ {
+ /*
+ ** stdio may not be expecting these
+ ** errnos from write()! Change to
+ ** something which it can understand.
+ ** Note that ENOSPC and EDQUOT are saved
+ ** because they are actually valid for
+ ** write().
+ */
+
+ if (!(errno == ENOSPC
+#ifdef EDQUOT
+ || errno == EDQUOT
+#endif /* EDQUOT */
+ ))
+ errno = EIO;
+
+ return -1;
+ }
+ bfp->bf_disk_fd = retval;
+ bfp->bf_ondisk = true;
+ }
+
+ /* Catch a write() on an earlier failed write to disk */
+ if (bfp->bf_ondisk && bfp->bf_disk_fd < 0)
+ {
+ errno = EIO;
+ return -1;
+ }
+
+ if (lseek(bfp->bf_disk_fd,
+ bfp->bf_offset + count, SEEK_SET) < 0)
+ {
+ if ((errno == EINVAL) || (errno == ESPIPE))
+ {
+ /*
+ ** stdio won't be expecting these
+ ** errnos from write()! Change them into
+ ** something which it can understand.
+ */
+
+ errno = EIO;
+ }
+ return -1;
+ }
+
+ while (count < nbytes)
+ {
+ retval = write(bfp->bf_disk_fd, buf + count,
+ nbytes - count);
+ if (retval < 0)
+ {
+ /* errno is set implicitly by write() */
+ return -1;
+ }
+ else
+ count += retval;
+ }
+ }
+
+finished:
+ bfp->bf_offset += count;
+ if (bfp->bf_offset > bfp->bf_size)
+ bfp->bf_size = bfp->bf_offset;
+ return count;
+}
+
+/*
+** BFREWIND -- rewinds the SM_FILE_T *
+**
+** Parameters:
+** fp -- SM_FILE_T * to rewind
+**
+** Returns:
+** 0 on success, -1 on error
+**
+** Side Effects:
+** rewinds the SM_FILE_T * and puts it into read mode. Normally
+** one would bfopen() a file, write to it, then bfrewind() and
+** fread(). If fp is not a buffered file, this is equivalent to
+** rewind().
+**
+** Sets errno:
+** any value of errno specified by sm_io_rewind()
+*/
+
+int
+bfrewind(fp)
+ SM_FILE_T *fp;
+{
+ (void) sm_io_flush(fp, SM_TIME_DEFAULT);
+ sm_io_clearerr(fp); /* quicker just to do it */
+ return sm_io_seek(fp, SM_TIME_DEFAULT, 0, SM_IO_SEEK_SET);
+}
+
+/*
+** SM_BFCOMMIT -- "commits" the buffered file
+**
+** Parameters:
+** fp -- SM_FILE_T * to commit to disk
+**
+** Returns:
+** 0 on success, -1 on error
+**
+** Side Effects:
+** Forces the given SM_FILE_T * to be written to disk if it is not
+** already, and ensures that it will be kept after closing. If
+** fp is not a buffered file, this is a no-op.
+**
+** Sets errno:
+** any value of errno specified by open()
+** any value of errno specified by write()
+** any value of errno specified by lseek()
+*/
+
+static int
+sm_bfcommit(fp)
+ SM_FILE_T *fp;
+{
+ struct bf *bfp;
+ int retval;
+ int byteswritten;
+
+ /* Get associated bf structure */
+ bfp = (struct bf *) fp->f_cookie;
+
+ /* If already committed, noop */
+ if (bfp->bf_committed)
+ return 0;
+
+ /* Do we need to open a file? */
+ if (!bfp->bf_ondisk)
+ {
+ int save_errno;
+ MODE_T omask;
+ struct stat st;
+
+ if (tTd(58, 8))
+ {
+ sm_dprintf("bfcommit(%s): to disk\n", bfp->bf_filename);
+ if (tTd(58, 32))
+ sm_dprintf("bfcommit(): filemode %o flags %ld\n",
+ bfp->bf_filemode, bfp->bf_flags);
+ }
+
+ if (stat(bfp->bf_filename, &st) == 0)
+ {
+ errno = EEXIST;
+ return -1;
+ }
+
+ /* Clear umask as bf_filemode are the true perms */
+ omask = umask(0);
+ retval = OPEN(bfp->bf_filename, O_RDWR | O_CREAT | O_EXCL,
+ bfp->bf_filemode, bfp->bf_flags);
+ save_errno = errno;
+ (void) umask(omask);
+
+ /* Couldn't create file: failure */
+ if (retval < 0)
+ {
+ /* errno is set implicitly by open() */
+ errno = save_errno;
+ return -1;
+ }
+
+ bfp->bf_disk_fd = retval;
+ bfp->bf_ondisk = true;
+ }
+
+ /* Write out the contents of our buffer, if we have any */
+ if (bfp->bf_buffilled > 0)
+ {
+ byteswritten = 0;
+
+ if (lseek(bfp->bf_disk_fd, 0, SEEK_SET) < 0)
+ {
+ /* errno is set implicitly by lseek() */
+ return -1;
+ }
+
+ while (byteswritten < bfp->bf_buffilled)
+ {
+ retval = write(bfp->bf_disk_fd,
+ bfp->bf_buf + byteswritten,
+ bfp->bf_buffilled - byteswritten);
+ if (retval < 0)
+ {
+ /* errno is set implicitly by write() */
+ return -1;
+ }
+ else
+ byteswritten += retval;
+ }
+ }
+ bfp->bf_committed = true;
+
+ /* Invalidate buf; all goes to file now */
+ bfp->bf_buffilled = 0;
+ if (bfp->bf_bufsize > 0)
+ {
+ /* Don't need buffer anymore; free it */
+ bfp->bf_bufsize = 0;
+ sm_free(bfp->bf_buf);
+ }
+ return 0;
+}
+
+/*
+** SM_BFTRUNCATE -- rewinds and truncates the SM_FILE_T *
+**
+** Parameters:
+** fp -- SM_FILE_T * to truncate
+**
+** Returns:
+** 0 on success, -1 on error
+**
+** Side Effects:
+** rewinds the SM_FILE_T *, truncates it to zero length, and puts
+** it into write mode.
+**
+** Sets errno:
+** any value of errno specified by fseek()
+** any value of errno specified by ftruncate()
+*/
+
+static int
+sm_bftruncate(fp)
+ SM_FILE_T *fp;
+{
+ struct bf *bfp;
+
+ if (bfrewind(fp) < 0)
+ return -1;
+
+ /* Get bf structure */
+ bfp = (struct bf *) fp->f_cookie;
+ bfp->bf_buffilled = 0;
+ bfp->bf_size = 0;
+
+ /* Need to zero the buffer */
+ if (bfp->bf_bufsize > 0)
+ memset(bfp->bf_buf, '\0', bfp->bf_bufsize);
+ if (bfp->bf_ondisk)
+ {
+#if NOFTRUNCATE
+ /* XXX: Not much we can do except rewind it */
+ errno = EINVAL;
+ return -1;
+#else /* NOFTRUNCATE */
+ return ftruncate(bfp->bf_disk_fd, 0);
+#endif /* NOFTRUNCATE */
+ }
+ return 0;
+}
+
+/*
+** SM_BFSETINFO -- set/change info for an open file pointer
+**
+** Parameters:
+** fp -- file pointer to get info about
+** what -- type of info to set/change
+** valp -- thing to set/change the info to
+**
+*/
+
+static int
+sm_bfsetinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ struct bf *bfp;
+ int bsize;
+
+ /* Get bf structure */
+ bfp = (struct bf *) fp->f_cookie;
+ switch (what)
+ {
+ case SM_BF_SETBUFSIZE:
+ bsize = *((int *) valp);
+ bfp->bf_bufsize = bsize;
+
+ /* A zero bsize is valid, just don't allocate memory */
+ if (bsize > 0)
+ {
+ bfp->bf_buf = (char *) sm_malloc(bsize);
+ if (bfp->bf_buf == NULL)
+ {
+ bfp->bf_bufsize = 0;
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+ else
+ bfp->bf_buf = NULL;
+ return 0;
+ case SM_BF_COMMIT:
+ return sm_bfcommit(fp);
+ case SM_BF_TRUNCATE:
+ return sm_bftruncate(fp);
+ case SM_BF_TEST:
+ return 1; /* always */
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
diff --git a/contrib/sendmail/src/bf.h b/contrib/sendmail/src/bf.h
new file mode 100644
index 0000000..5a02292
--- /dev/null
+++ b/contrib/sendmail/src/bf.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: bf.h,v 8.16 2002/04/15 02:37:09 ca Exp $
+ *
+ * Contributed by Exactis.com, Inc.
+ *
+ */
+
+#ifndef BF_H
+# define BF_H 1
+
+extern SM_FILE_T *bfopen __P((char *, MODE_T, size_t, long));
+extern int bfcommit __P((SM_FILE_T *));
+extern int bfrewind __P((SM_FILE_T *));
+extern int bftruncate __P((SM_FILE_T *));
+extern int bfclose __P((SM_FILE_T *));
+extern bool bftest __P((SM_FILE_T *));
+
+/* "what" flags for sm_io_setinfo() for the SM_FILE_TYPE file type */
+# define SM_BF_SETBUFSIZE 1000 /* set buffer size */
+# define SM_BF_COMMIT 1001 /* commit file to disk */
+# define SM_BF_TRUNCATE 1002 /* truncate the file */
+# define SM_BF_TEST 1003 /* historical support; temp */
+
+# define BF_FILE_TYPE "SendmailBufferedFile"
+#endif /* ! BF_H */
diff --git a/contrib/sendmail/src/collect.c b/contrib/sendmail/src/collect.c
new file mode 100644
index 0000000..a4149fb
--- /dev/null
+++ b/contrib/sendmail/src/collect.c
@@ -0,0 +1,1085 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: collect.c,v 8.242.2.2 2002/08/16 14:56:01 ca Exp $")
+
+static void collecttimeout __P((time_t));
+static void dferror __P((SM_FILE_T *volatile, char *, ENVELOPE *));
+static void eatfrom __P((char *volatile, ENVELOPE *));
+static void collect_doheader __P((ENVELOPE *));
+static SM_FILE_T *collect_dfopen __P((ENVELOPE *));
+static SM_FILE_T *collect_eoh __P((ENVELOPE *, int, int));
+
+/*
+** COLLECT_EOH -- end-of-header processing in collect()
+**
+** Called by collect() when it encounters the blank line
+** separating the header from the message body, or when it
+** encounters EOF in a message that contains only a header.
+**
+** Parameters:
+** e -- envelope
+** numhdrs -- number of headers
+** hdrslen -- length of headers
+**
+** Results:
+** NULL, or handle to open data file
+**
+** Side Effects:
+** end-of-header check ruleset is invoked.
+** envelope state is updated.
+** headers may be added and deleted.
+** selects the queue.
+** opens the data file.
+*/
+
+static SM_FILE_T *
+collect_eoh(e, numhdrs, hdrslen)
+ ENVELOPE *e;
+ int numhdrs;
+ int hdrslen;
+{
+ char hnum[16];
+ char hsize[16];
+
+ /* call the end-of-header check ruleset */
+ (void) sm_snprintf(hnum, sizeof hnum, "%d", numhdrs);
+ (void) sm_snprintf(hsize, sizeof hsize, "%d", hdrslen);
+ if (tTd(30, 10))
+ sm_dprintf("collect: rscheck(\"check_eoh\", \"%s $| %s\")\n",
+ hnum, hsize);
+ (void) rscheck("check_eoh", hnum, hsize, e, RSF_UNSTRUCTURED|RSF_COUNT,
+ 3, NULL, e->e_id);
+
+ /*
+ ** Process the header,
+ ** select the queue, open the data file.
+ */
+
+ collect_doheader(e);
+ return collect_dfopen(e);
+}
+
+/*
+** COLLECT_DOHEADER -- process header in collect()
+**
+** Called by collect() after it has finished parsing the header,
+** but before it selects the queue and creates the data file.
+** The results of processing the header will affect queue selection.
+**
+** Parameters:
+** e -- envelope
+**
+** Results:
+** none.
+**
+** Side Effects:
+** envelope state is updated.
+** headers may be added and deleted.
+*/
+
+static void
+collect_doheader(e)
+ ENVELOPE *e;
+{
+ /*
+ ** Find out some information from the headers.
+ ** Examples are who is the from person & the date.
+ */
+
+ eatheader(e, true, false);
+
+ if (GrabTo && e->e_sendqueue == NULL)
+ usrerr("No recipient addresses found in header");
+
+ /*
+ ** If we have a Return-Receipt-To:, turn it into a DSN.
+ */
+
+ if (RrtImpliesDsn && hvalue("return-receipt-to", e->e_header) != NULL)
+ {
+ ADDRESS *q;
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ if (!bitset(QHASNOTIFY, q->q_flags))
+ q->q_flags |= QHASNOTIFY|QPINGONSUCCESS;
+ }
+
+ /*
+ ** Add an appropriate recipient line if we have none.
+ */
+
+ if (hvalue("to", e->e_header) != NULL ||
+ hvalue("cc", e->e_header) != NULL ||
+ hvalue("apparently-to", e->e_header) != NULL)
+ {
+ /* have a valid recipient header -- delete Bcc: headers */
+ e->e_flags |= EF_DELETE_BCC;
+ }
+ else if (hvalue("bcc", e->e_header) == NULL)
+ {
+ /* no valid recipient headers */
+ register ADDRESS *q;
+ char *hdr = NULL;
+
+ /* create a recipient field */
+ switch (NoRecipientAction)
+ {
+ case NRA_ADD_APPARENTLY_TO:
+ hdr = "Apparently-To";
+ break;
+
+ case NRA_ADD_TO:
+ hdr = "To";
+ break;
+
+ case NRA_ADD_BCC:
+ addheader("Bcc", " ", 0, e);
+ break;
+
+ case NRA_ADD_TO_UNDISCLOSED:
+ addheader("To", "undisclosed-recipients:;", 0, e);
+ break;
+ }
+
+ if (hdr != NULL)
+ {
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (q->q_alias != NULL)
+ continue;
+ if (tTd(30, 3))
+ sm_dprintf("Adding %s: %s\n",
+ hdr, q->q_paddr);
+ addheader(hdr, q->q_paddr, 0, e);
+ }
+ }
+ }
+}
+
+/*
+** COLLECT_DFOPEN -- open the message data file
+**
+** Called by collect() after it has finished processing the header.
+** Queue selection occurs at this point, possibly based on the
+** envelope's recipient list and on header information.
+**
+** Parameters:
+** e -- envelope
+**
+** Results:
+** NULL, or a pointer to an open data file,
+** into which the message body will be written by collect().
+**
+** Side Effects:
+** Calls syserr, sets EF_FATALERRS and returns NULL
+** if there is insufficient disk space.
+** Aborts process if data file could not be opened.
+** Otherwise, the queue is selected,
+** e->e_{dfino,dfdev,msgsize,flags} are updated,
+** and a pointer to an open data file is returned.
+*/
+
+static SM_FILE_T *
+collect_dfopen(e)
+ ENVELOPE *e;
+{
+ MODE_T oldumask = 0;
+ int dfd;
+ struct stat stbuf;
+ SM_FILE_T *df;
+ char *dfname;
+
+ if (!setnewqueue(e))
+ return NULL;
+
+ dfname = queuename(e, DATAFL_LETTER);
+ if (bitset(S_IWGRP, QueueFileMode))
+ oldumask = umask(002);
+ df = bfopen(dfname, QueueFileMode, DataFileBufferSize,
+ SFF_OPENASROOT);
+ if (bitset(S_IWGRP, QueueFileMode))
+ (void) umask(oldumask);
+ if (df == NULL)
+ {
+ syserr("@Cannot create %s", dfname);
+ e->e_flags |= EF_NO_BODY_RETN;
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ dfd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL);
+ if (dfd < 0 || fstat(dfd, &stbuf) < 0)
+ e->e_dfino = -1;
+ else
+ {
+ e->e_dfdev = stbuf.st_dev;
+ e->e_dfino = stbuf.st_ino;
+ }
+ e->e_flags |= EF_HAS_DF;
+ return df;
+}
+
+/*
+** COLLECT -- read & parse message header & make temp file.
+**
+** Creates a temporary file name and copies the standard
+** input to that file. Leading UNIX-style "From" lines are
+** stripped off (after important information is extracted).
+**
+** Parameters:
+** fp -- file to read.
+** smtpmode -- if set, we are running SMTP: give an RFC821
+** style message to say we are ready to collect
+** input, and never ignore a single dot to mean
+** end of message.
+** hdrp -- the location to stash the header.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** If successful,
+** - Data file is created and filled, and e->e_dfp is set.
+** - The from person may be set.
+** If the "enough disk space" check fails,
+** - syserr is called.
+** - e->e_dfp is NULL.
+** - e->e_flags & EF_FATALERRS is set.
+** - collect() returns.
+** If data file cannot be created, the process is terminated.
+*/
+
+static jmp_buf CtxCollectTimeout;
+static bool volatile CollectProgress;
+static SM_EVENT *volatile CollectTimeout = NULL;
+
+/* values for input state machine */
+#define IS_NORM 0 /* middle of line */
+#define IS_BOL 1 /* beginning of line */
+#define IS_DOT 2 /* read a dot at beginning of line */
+#define IS_DOTCR 3 /* read ".\r" at beginning of line */
+#define IS_CR 4 /* read a carriage return */
+
+/* values for message state machine */
+#define MS_UFROM 0 /* reading Unix from line */
+#define MS_HEADER 1 /* reading message header */
+#define MS_BODY 2 /* reading message body */
+#define MS_DISCARD 3 /* discarding rest of message */
+
+void
+collect(fp, smtpmode, hdrp, e)
+ SM_FILE_T *fp;
+ bool smtpmode;
+ HDR **hdrp;
+ register ENVELOPE *e;
+{
+ register SM_FILE_T *volatile df;
+ volatile bool ignrdot;
+ volatile time_t dbto;
+ register char *volatile bp;
+ volatile int c;
+ volatile bool inputerr;
+ bool headeronly;
+ char *volatile buf;
+ volatile int buflen;
+ volatile int istate;
+ volatile int mstate;
+ volatile int hdrslen;
+ volatile int numhdrs;
+ volatile int afd;
+ unsigned char *volatile pbp;
+ unsigned char peekbuf[8];
+ char bufbuf[MAXLINE];
+
+ df = NULL;
+ ignrdot = smtpmode ? false : IgnrDot;
+ dbto = smtpmode ? TimeOuts.to_datablock : 0;
+ c = SM_IO_EOF;
+ inputerr = false;
+ headeronly = hdrp != NULL;
+ hdrslen = 0;
+ numhdrs = 0;
+ HasEightBits = false;
+ buf = bp = bufbuf;
+ buflen = sizeof bufbuf;
+ pbp = peekbuf;
+ istate = IS_BOL;
+ mstate = SaveFrom ? MS_HEADER : MS_UFROM;
+ CollectProgress = false;
+
+ /*
+ ** Tell ARPANET to go ahead.
+ */
+
+ if (smtpmode)
+ message("354 Enter mail, end with \".\" on a line by itself");
+
+ if (tTd(30, 2))
+ sm_dprintf("collect\n");
+
+ /*
+ ** Read the message.
+ **
+ ** This is done using two interleaved state machines.
+ ** The input state machine is looking for things like
+ ** hidden dots; the message state machine is handling
+ ** the larger picture (e.g., header versus body).
+ */
+
+ if (dbto != 0)
+ {
+ /* handle possible input timeout */
+ if (setjmp(CtxCollectTimeout) != 0)
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "timeout waiting for input from %s during message collect",
+ CURHOSTNAME);
+ errno = 0;
+ usrerr("451 4.4.1 timeout waiting for input during message collect");
+ goto readerr;
+ }
+ CollectTimeout = sm_setevent(dbto, collecttimeout, dbto);
+ }
+
+ e->e_msgsize = 0;
+ for (;;)
+ {
+ if (tTd(30, 35))
+ sm_dprintf("top, istate=%d, mstate=%d\n", istate,
+ mstate);
+ for (;;)
+ {
+ if (pbp > peekbuf)
+ c = *--pbp;
+ else
+ {
+ while (!sm_io_eof(fp) && !sm_io_error(fp))
+ {
+ errno = 0;
+ c = sm_io_getc(fp, SM_TIME_DEFAULT);
+ if (c == SM_IO_EOF && errno == EINTR)
+ {
+ /* Interrupted, retry */
+ sm_io_clearerr(fp);
+ continue;
+ }
+ break;
+ }
+ CollectProgress = true;
+ if (TrafficLogFile != NULL && !headeronly)
+ {
+ if (istate == IS_BOL)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "%05d <<< ",
+ (int) CurrentPid);
+ if (c == SM_IO_EOF)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "[EOF]\n");
+ else
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ c);
+ }
+ if (c == SM_IO_EOF)
+ goto readerr;
+ if (SevenBitInput)
+ c &= 0x7f;
+ else
+ HasEightBits |= bitset(0x80, c);
+ }
+ if (tTd(30, 94))
+ sm_dprintf("istate=%d, c=%c (0x%x)\n",
+ istate, (char) c, c);
+ switch (istate)
+ {
+ case IS_BOL:
+ if (c == '.')
+ {
+ istate = IS_DOT;
+ continue;
+ }
+ break;
+
+ case IS_DOT:
+ if (c == '\n' && !ignrdot &&
+ !bitset(EF_NL_NOT_EOL, e->e_flags))
+ goto readerr;
+ else if (c == '\r' &&
+ !bitset(EF_CRLF_NOT_EOL, e->e_flags))
+ {
+ istate = IS_DOTCR;
+ continue;
+ }
+ else if (ignrdot ||
+ (c != '.' &&
+ OpMode != MD_SMTP &&
+ OpMode != MD_DAEMON &&
+ OpMode != MD_ARPAFTP))
+
+ {
+ *pbp++ = c;
+ c = '.';
+ }
+ break;
+
+ case IS_DOTCR:
+ if (c == '\n' && !ignrdot)
+ goto readerr;
+ else
+ {
+ /* push back the ".\rx" */
+ *pbp++ = c;
+ if (OpMode != MD_SMTP &&
+ OpMode != MD_DAEMON &&
+ OpMode != MD_ARPAFTP)
+ {
+ *pbp++ = '\r';
+ c = '.';
+ }
+ else
+ c = '\r';
+ }
+ break;
+
+ case IS_CR:
+ if (c == '\n')
+ istate = IS_BOL;
+ else
+ {
+ (void) sm_io_ungetc(fp, SM_TIME_DEFAULT,
+ c);
+ c = '\r';
+ istate = IS_NORM;
+ }
+ goto bufferchar;
+ }
+
+ if (c == '\r' && !bitset(EF_CRLF_NOT_EOL, e->e_flags))
+ {
+ istate = IS_CR;
+ continue;
+ }
+ else if (c == '\n' && !bitset(EF_NL_NOT_EOL,
+ e->e_flags))
+ istate = IS_BOL;
+ else
+ istate = IS_NORM;
+
+bufferchar:
+ if (!headeronly)
+ {
+ /* no overflow? */
+ if (e->e_msgsize >= 0)
+ {
+ e->e_msgsize++;
+ if (MaxMessageSize > 0 &&
+ !bitset(EF_TOOBIG, e->e_flags) &&
+ e->e_msgsize > MaxMessageSize)
+ e->e_flags |= EF_TOOBIG;
+ }
+ }
+ switch (mstate)
+ {
+ case MS_BODY:
+ /* just put the character out */
+ if (!bitset(EF_TOOBIG, e->e_flags))
+ (void) sm_io_putc(df, SM_TIME_DEFAULT,
+ c);
+
+ /* FALLTHROUGH */
+
+ case MS_DISCARD:
+ continue;
+ }
+
+ /* header -- buffer up */
+ if (bp >= &buf[buflen - 2])
+ {
+ char *obuf;
+
+ if (mstate != MS_HEADER)
+ break;
+
+ /* out of space for header */
+ obuf = buf;
+ if (buflen < MEMCHUNKSIZE)
+ buflen *= 2;
+ else
+ buflen += MEMCHUNKSIZE;
+ buf = xalloc(buflen);
+ memmove(buf, obuf, bp - obuf);
+ bp = &buf[bp - obuf];
+ if (obuf != bufbuf)
+ sm_free(obuf); /* XXX */
+ }
+
+ /*
+ ** XXX Notice: the logic here is broken.
+ ** An input to sendmail that doesn't contain a
+ ** header but starts immediately with the body whose
+ ** first line contain characters which match the
+ ** following "if" will cause problems: those
+ ** characters will NOT appear in the output...
+ ** Do we care?
+ */
+
+ if (c >= 0200 && c <= 0237)
+ {
+#if 0 /* causes complaints -- figure out something for 8.n+1 */
+ usrerr("Illegal character 0x%x in header", c);
+#else /* 0 */
+ /* EMPTY */
+#endif /* 0 */
+ }
+ else if (c != '\0')
+ {
+ *bp++ = c;
+ ++hdrslen;
+ if (!headeronly &&
+ MaxHeadersLength > 0 &&
+ hdrslen > MaxHeadersLength)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "headers too large (%d max) from %s during message collect",
+ MaxHeadersLength,
+ CURHOSTNAME);
+ errno = 0;
+ e->e_flags |= EF_CLRQUEUE;
+ e->e_status = "5.6.0";
+ usrerrenh(e->e_status,
+ "552 Headers too large (%d max)",
+ MaxHeadersLength);
+ mstate = MS_DISCARD;
+ }
+ }
+ if (istate == IS_BOL)
+ break;
+ }
+ *bp = '\0';
+
+nextstate:
+ if (tTd(30, 35))
+ sm_dprintf("nextstate, istate=%d, mstate=%d, line = \"%s\"\n",
+ istate, mstate, buf);
+ switch (mstate)
+ {
+ case MS_UFROM:
+ mstate = MS_HEADER;
+#ifndef NOTUNIX
+ if (strncmp(buf, "From ", 5) == 0)
+ {
+ bp = buf;
+ eatfrom(buf, e);
+ continue;
+ }
+#endif /* ! NOTUNIX */
+ /* FALLTHROUGH */
+
+ case MS_HEADER:
+ if (!isheader(buf))
+ {
+ mstate = MS_BODY;
+ goto nextstate;
+ }
+
+ /* check for possible continuation line */
+ do
+ {
+ sm_io_clearerr(fp);
+ errno = 0;
+ c = sm_io_getc(fp, SM_TIME_DEFAULT);
+ } while (c == SM_IO_EOF && errno == EINTR);
+ if (c != SM_IO_EOF)
+ (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c);
+ if (c == ' ' || c == '\t')
+ {
+ /* yep -- defer this */
+ continue;
+ }
+
+ /* trim off trailing CRLF or NL */
+ if (*--bp != '\n' || *--bp != '\r')
+ bp++;
+ *bp = '\0';
+
+ if (bitset(H_EOH, chompheader(buf,
+ CHHDR_CHECK | CHHDR_USER,
+ hdrp, e)))
+ {
+ mstate = MS_BODY;
+ goto nextstate;
+ }
+ numhdrs++;
+ break;
+
+ case MS_BODY:
+ if (tTd(30, 1))
+ sm_dprintf("EOH\n");
+
+ if (headeronly)
+ goto readerr;
+
+ df = collect_eoh(e, numhdrs, hdrslen);
+ if (df == NULL)
+ e->e_flags |= EF_TOOBIG;
+
+ bp = buf;
+
+ /* toss blank line */
+ if ((!bitset(EF_CRLF_NOT_EOL, e->e_flags) &&
+ bp[0] == '\r' && bp[1] == '\n') ||
+ (!bitset(EF_NL_NOT_EOL, e->e_flags) &&
+ bp[0] == '\n'))
+ {
+ break;
+ }
+
+ /* if not a blank separator, write it out */
+ if (!bitset(EF_TOOBIG, e->e_flags))
+ {
+ while (*bp != '\0')
+ (void) sm_io_putc(df, SM_TIME_DEFAULT,
+ *bp++);
+ }
+ break;
+ }
+ bp = buf;
+ }
+
+readerr:
+ if ((sm_io_eof(fp) && smtpmode) || sm_io_error(fp))
+ {
+ const char *errmsg;
+
+ if (sm_io_eof(fp))
+ errmsg = "unexpected close";
+ else
+ errmsg = sm_errstring(errno);
+ if (tTd(30, 1))
+ sm_dprintf("collect: premature EOM: %s\n", errmsg);
+ if (LogLevel > 1)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "collect: premature EOM: %s", errmsg);
+ inputerr = true;
+ }
+
+ /* reset global timer */
+ if (CollectTimeout != NULL)
+ sm_clrevent(CollectTimeout);
+
+ if (headeronly)
+ return;
+
+ if (mstate != MS_BODY)
+ {
+ /* no body or discard, so we never opened the data file */
+ SM_ASSERT(df == NULL);
+ df = collect_eoh(e, numhdrs, hdrslen);
+ }
+
+ if (df == NULL)
+ {
+ /* skip next few clauses */
+ /* EMPTY */
+ }
+ else if (sm_io_flush(df, SM_TIME_DEFAULT) != 0 || sm_io_error(df))
+ {
+ dferror(df, "sm_io_flush||sm_io_error", e);
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ else if (SuperSafe != SAFE_REALLY)
+ {
+ /* skip next few clauses */
+ /* EMPTY */
+ }
+ else if (sm_io_setinfo(df, SM_BF_COMMIT, NULL) < 0 && errno != EINVAL)
+ {
+ int save_errno = errno;
+
+ if (save_errno == EEXIST)
+ {
+ char *dfile;
+ struct stat st;
+ int dfd;
+
+ dfile = queuename(e, DATAFL_LETTER);
+ if (stat(dfile, &st) < 0)
+ st.st_size = -1;
+ errno = EEXIST;
+ syserr("@collect: bfcommit(%s): already on disk, size = %ld",
+ dfile, (long) st.st_size);
+ dfd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL);
+ if (dfd >= 0)
+ dumpfd(dfd, true, true);
+ }
+ errno = save_errno;
+ dferror(df, "bfcommit", e);
+ flush_errors(true);
+ finis(save_errno != EEXIST, true, ExitStat);
+ }
+ else if ((afd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL)) >= 0 &&
+ fsync(afd) < 0)
+ {
+ dferror(df, "fsync", e);
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ else if (sm_io_close(df, SM_TIME_DEFAULT) < 0)
+ {
+ dferror(df, "sm_io_close", e);
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ else
+ {
+ /* everything is happily flushed to disk */
+ df = NULL;
+
+ /* remove from available space in filesystem */
+ updfs(e, false, true);
+ }
+
+ /* An EOF when running SMTP is an error */
+ if (inputerr && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ {
+ char *host;
+ char *problem;
+ ADDRESS *q;
+
+ host = RealHostName;
+ if (host == NULL)
+ host = "localhost";
+
+ if (sm_io_eof(fp))
+ problem = "unexpected close";
+ else if (sm_io_error(fp))
+ problem = "I/O error";
+ else
+ problem = "read timeout";
+ if (LogLevel > 0 && sm_io_eof(fp))
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "collect: %s on connection from %.100s, sender=%s",
+ problem, host,
+ shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
+ if (sm_io_eof(fp))
+ usrerr("451 4.4.1 collect: %s on connection from %s, from=%s",
+ problem, host,
+ shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
+ else
+ syserr("451 4.4.1 collect: %s on connection from %s, from=%s",
+ problem, host,
+ shortenstring(e->e_from.q_paddr, MAXSHORTSTR));
+
+ /* don't return an error indication */
+ e->e_to = NULL;
+ e->e_flags &= ~EF_FATALERRS;
+ e->e_flags |= EF_CLRQUEUE;
+
+ /* Don't send any message notification to sender */
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ q->q_state = QS_FATALERR;
+ }
+
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+ /* Log collection information. */
+ if (bitset(EF_LOGSENDER, e->e_flags) && LogLevel > 4)
+ {
+ logsender(e, e->e_msgid);
+ e->e_flags &= ~EF_LOGSENDER;
+ }
+
+ /* check for message too large */
+ if (bitset(EF_TOOBIG, e->e_flags))
+ {
+ e->e_flags |= EF_NO_BODY_RETN|EF_CLRQUEUE;
+ if (!bitset(EF_FATALERRS, e->e_flags))
+ {
+ e->e_status = "5.2.3";
+ usrerrenh(e->e_status,
+ "552 Message exceeds maximum fixed size (%ld)",
+ MaxMessageSize);
+ if (LogLevel > 6)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "message size (%ld) exceeds maximum (%ld)",
+ e->e_msgsize, MaxMessageSize);
+ }
+ }
+
+ /* check for illegal 8-bit data */
+ if (HasEightBits)
+ {
+ e->e_flags |= EF_HAS8BIT;
+ if (!bitset(MM_PASS8BIT|MM_MIME8BIT, MimeMode) &&
+ !bitset(EF_IS_MIME, e->e_flags))
+ {
+ e->e_status = "5.6.1";
+ usrerrenh(e->e_status, "554 Eight bit data not allowed");
+ }
+ }
+ else
+ {
+ /* if it claimed to be 8 bits, well, it lied.... */
+ if (e->e_bodytype != NULL &&
+ sm_strcasecmp(e->e_bodytype, "8BITMIME") == 0)
+ e->e_bodytype = "7BIT";
+ }
+
+ if (SuperSafe == SAFE_REALLY && !bitset(EF_FATALERRS, e->e_flags))
+ {
+ char *dfname = queuename(e, DATAFL_LETTER);
+ if ((e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname,
+ SM_IO_RDONLY, NULL)) == NULL)
+ {
+ /* we haven't acked receipt yet, so just chuck this */
+ syserr("@Cannot reopen %s", dfname);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ }
+ else
+ e->e_dfp = df;
+
+ /* collect statistics */
+ if (OpMode != MD_VERIFY)
+ markstats(e, (ADDRESS *) NULL, STATS_NORMAL);
+}
+
+static void
+collecttimeout(timeout)
+ time_t timeout;
+{
+ int save_errno = errno;
+
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ if (CollectProgress)
+ {
+ /* reset the timeout */
+ CollectTimeout = sm_sigsafe_setevent(timeout, collecttimeout,
+ timeout);
+ CollectProgress = false;
+ }
+ else
+ {
+ /* event is done */
+ CollectTimeout = NULL;
+ }
+
+ /* if no progress was made or problem resetting event, die now */
+ if (CollectTimeout == NULL)
+ {
+ errno = ETIMEDOUT;
+ longjmp(CtxCollectTimeout, 1);
+ }
+ errno = save_errno;
+}
+/*
+** DFERROR -- signal error on writing the data file.
+**
+** Called by collect(). Collect() always terminates the process
+** immediately after calling dferror(), which means that the SMTP
+** session will be terminated, which means that any error message
+** issued by dferror must be a 421 error, as per RFC 821.
+**
+** Parameters:
+** df -- the file pointer for the data file.
+** msg -- detailed message.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Gives an error message.
+** Arranges for following output to go elsewhere.
+*/
+
+static void
+dferror(df, msg, e)
+ SM_FILE_T *volatile df;
+ char *msg;
+ register ENVELOPE *e;
+{
+ char *dfname;
+
+ dfname = queuename(e, DATAFL_LETTER);
+ setstat(EX_IOERR);
+ if (errno == ENOSPC)
+ {
+#if STAT64 > 0
+ struct stat64 st;
+#else /* STAT64 > 0 */
+ struct stat st;
+#endif /* STAT64 > 0 */
+ long avail;
+ long bsize;
+
+ e->e_flags |= EF_NO_BODY_RETN;
+
+ if (
+#if STAT64 > 0
+ fstat64(sm_io_getinfo(df, SM_IO_WHAT_FD, NULL), &st)
+#else /* STAT64 > 0 */
+ fstat(sm_io_getinfo(df, SM_IO_WHAT_FD, NULL), &st)
+#endif /* STAT64 > 0 */
+ < 0)
+ st.st_size = 0;
+ (void) sm_io_reopen(SmFtStdio, SM_TIME_DEFAULT, dfname,
+ SM_IO_WRONLY, NULL, df);
+ if (st.st_size <= 0)
+ (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
+ "\n*** Mail could not be accepted");
+ else
+ (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
+ "\n*** Mail of at least %llu bytes could not be accepted\n",
+ (ULONGLONG_T) st.st_size);
+ (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
+ "*** at %s due to lack of disk space for temp file.\n",
+ MyHostName);
+ avail = freediskspace(qid_printqueue(e->e_qgrp, e->e_qdir),
+ &bsize);
+ if (avail > 0)
+ {
+ if (bsize > 1024)
+ avail *= bsize / 1024;
+ else if (bsize < 1024)
+ avail /= 1024 / bsize;
+ (void) sm_io_fprintf(df, SM_TIME_DEFAULT,
+ "*** Currently, %ld kilobytes are available for mail temp files.\n",
+ avail);
+ }
+#if 0
+ /* Wrong response code; should be 421. */
+ e->e_status = "4.3.1";
+ usrerrenh(e->e_status, "452 Out of disk space for temp file");
+#else /* 0 */
+ syserr("421 4.3.1 Out of disk space for temp file");
+#endif /* 0 */
+ }
+ else
+ syserr("421 4.3.0 collect: Cannot write %s (%s, uid=%d, gid=%d)",
+ dfname, msg, (int) geteuid(), (int) getegid());
+ if (sm_io_reopen(SmFtStdio, SM_TIME_DEFAULT, SM_PATH_DEVNULL,
+ SM_IO_WRONLY, NULL, df) == NULL)
+ sm_syslog(LOG_ERR, e->e_id,
+ "dferror: sm_io_reopen(\"/dev/null\") failed: %s",
+ sm_errstring(errno));
+}
+/*
+** EATFROM -- chew up a UNIX style from line and process
+**
+** This does indeed make some assumptions about the format
+** of UNIX messages.
+**
+** Parameters:
+** fm -- the from line.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** extracts what information it can from the header,
+** such as the date.
+*/
+
+#ifndef NOTUNIX
+
+static char *DowList[] =
+{
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
+};
+
+static char *MonthList[] =
+{
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ NULL
+};
+
+static void
+eatfrom(fm, e)
+ char *volatile fm;
+ register ENVELOPE *e;
+{
+ register char *p;
+ register char **dt;
+
+ if (tTd(30, 2))
+ sm_dprintf("eatfrom(%s)\n", fm);
+
+ /* find the date part */
+ p = fm;
+ while (*p != '\0')
+ {
+ /* skip a word */
+ while (*p != '\0' && *p != ' ')
+ p++;
+ while (*p == ' ')
+ p++;
+ if (strlen(p) < 17)
+ {
+ /* no room for the date */
+ return;
+ }
+ if (!(isascii(*p) && isupper(*p)) ||
+ p[3] != ' ' || p[13] != ':' || p[16] != ':')
+ continue;
+
+ /* we have a possible date */
+ for (dt = DowList; *dt != NULL; dt++)
+ if (strncmp(*dt, p, 3) == 0)
+ break;
+ if (*dt == NULL)
+ continue;
+
+ for (dt = MonthList; *dt != NULL; dt++)
+ {
+ if (strncmp(*dt, &p[4], 3) == 0)
+ break;
+ }
+ if (*dt != NULL)
+ break;
+ }
+
+ if (*p != '\0')
+ {
+ char *q, buf[25];
+
+ /* we have found a date */
+ (void) sm_strlcpy(buf, p, sizeof(buf));
+ q = arpadate(buf);
+ macdefine(&e->e_macro, A_TEMP, 'a', q);
+ }
+}
+#endif /* ! NOTUNIX */
diff --git a/contrib/sendmail/src/conf.c b/contrib/sendmail/src/conf.c
new file mode 100644
index 0000000..e97b09d
--- /dev/null
+++ b/contrib/sendmail/src/conf.c
@@ -0,0 +1,6047 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: conf.c,v 8.972.2.5 2002/08/16 14:56:01 ca Exp $")
+
+#include <sendmail/pathnames.h>
+
+# include <sys/ioctl.h>
+# include <sys/param.h>
+
+#include <limits.h>
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+#if HASULIMIT && defined(HPUX11)
+# include <ulimit.h>
+#endif /* HASULIMIT && defined(HPUX11) */
+
+
+static void setupmaps __P((void));
+static void setupmailers __P((void));
+static void setupqueues __P((void));
+static int get_num_procs_online __P((void));
+
+
+/*
+** CONF.C -- Sendmail Configuration Tables.
+**
+** Defines the configuration of this installation.
+**
+** Configuration Variables:
+** HdrInfo -- a table describing well-known header fields.
+** Each entry has the field name and some flags,
+** which are described in sendmail.h.
+**
+** Notes:
+** I have tried to put almost all the reasonable
+** configuration information into the configuration
+** file read at runtime. My intent is that anything
+** here is a function of the version of UNIX you
+** are running, or is really static -- for example
+** the headers are a superset of widely used
+** protocols. If you find yourself playing with
+** this file too much, you may be making a mistake!
+*/
+
+
+/*
+** Header info table
+** Final (null) entry contains the flags used for any other field.
+**
+** Not all of these are actually handled specially by sendmail
+** at this time. They are included as placeholders, to let
+** you know that "someday" I intend to have sendmail do
+** something with them.
+*/
+
+struct hdrinfo HdrInfo[] =
+{
+ /* originator fields, most to least significant */
+ { "resent-sender", H_FROM|H_RESENT, NULL },
+ { "resent-from", H_FROM|H_RESENT, NULL },
+ { "resent-reply-to", H_FROM|H_RESENT, NULL },
+ { "sender", H_FROM, NULL },
+ { "from", H_FROM, NULL },
+ { "reply-to", H_FROM, NULL },
+ { "errors-to", H_FROM|H_ERRORSTO, NULL },
+ { "full-name", H_ACHECK, NULL },
+ { "return-receipt-to", H_RECEIPTTO, NULL },
+ { "disposition-notification-to", H_FROM, NULL },
+
+ /* destination fields */
+ { "to", H_RCPT, NULL },
+ { "resent-to", H_RCPT|H_RESENT, NULL },
+ { "cc", H_RCPT, NULL },
+ { "resent-cc", H_RCPT|H_RESENT, NULL },
+ { "bcc", H_RCPT|H_BCC, NULL },
+ { "resent-bcc", H_RCPT|H_BCC|H_RESENT, NULL },
+ { "apparently-to", H_RCPT, NULL },
+
+ /* message identification and control */
+ { "message-id", 0, NULL },
+ { "resent-message-id", H_RESENT, NULL },
+ { "message", H_EOH, NULL },
+ { "text", H_EOH, NULL },
+
+ /* date fields */
+ { "date", 0, NULL },
+ { "resent-date", H_RESENT, NULL },
+
+ /* trace fields */
+ { "received", H_TRACE|H_FORCE, NULL },
+ { "x400-received", H_TRACE|H_FORCE, NULL },
+ { "via", H_TRACE|H_FORCE, NULL },
+ { "mail-from", H_TRACE|H_FORCE, NULL },
+
+ /* miscellaneous fields */
+ { "comments", H_FORCE|H_ENCODABLE, NULL },
+ { "return-path", H_FORCE|H_ACHECK|H_BINDLATE, NULL },
+ { "content-transfer-encoding", H_CTE, NULL },
+ { "content-type", H_CTYPE, NULL },
+ { "content-length", H_ACHECK, NULL },
+ { "subject", H_ENCODABLE, NULL },
+ { "x-authentication-warning", H_FORCE, NULL },
+
+ { NULL, 0, NULL }
+};
+
+
+
+/*
+** Privacy values
+*/
+
+struct prival PrivacyValues[] =
+{
+ { "public", PRIV_PUBLIC },
+ { "needmailhelo", PRIV_NEEDMAILHELO },
+ { "needexpnhelo", PRIV_NEEDEXPNHELO },
+ { "needvrfyhelo", PRIV_NEEDVRFYHELO },
+ { "noexpn", PRIV_NOEXPN },
+ { "novrfy", PRIV_NOVRFY },
+ { "restrictexpand", PRIV_RESTRICTEXPAND },
+ { "restrictmailq", PRIV_RESTRICTMAILQ },
+ { "restrictqrun", PRIV_RESTRICTQRUN },
+ { "noetrn", PRIV_NOETRN },
+ { "noverb", PRIV_NOVERB },
+ { "authwarnings", PRIV_AUTHWARNINGS },
+ { "noreceipts", PRIV_NORECEIPTS },
+ { "nobodyreturn", PRIV_NOBODYRETN },
+ { "goaway", PRIV_GOAWAY },
+ { NULL, 0 }
+};
+
+/*
+** DontBlameSendmail values
+*/
+
+struct dbsval DontBlameSendmailValues[] =
+{
+ { "safe", DBS_SAFE },
+ { "assumesafechown", DBS_ASSUMESAFECHOWN },
+ { "groupwritabledirpathsafe", DBS_GROUPWRITABLEDIRPATHSAFE },
+ { "groupwritableforwardfilesafe",
+ DBS_GROUPWRITABLEFORWARDFILESAFE },
+ { "groupwritableincludefilesafe",
+ DBS_GROUPWRITABLEINCLUDEFILESAFE },
+ { "groupwritablealiasfile", DBS_GROUPWRITABLEALIASFILE },
+ { "worldwritablealiasfile", DBS_WORLDWRITABLEALIASFILE },
+ { "forwardfileinunsafedirpath", DBS_FORWARDFILEINUNSAFEDIRPATH },
+ { "includefileinunsafedirpath", DBS_INCLUDEFILEINUNSAFEDIRPATH },
+ { "mapinunsafedirpath", DBS_MAPINUNSAFEDIRPATH },
+ { "linkedaliasfileinwritabledir",
+ DBS_LINKEDALIASFILEINWRITABLEDIR },
+ { "linkedclassfileinwritabledir",
+ DBS_LINKEDCLASSFILEINWRITABLEDIR },
+ { "linkedforwardfileinwritabledir",
+ DBS_LINKEDFORWARDFILEINWRITABLEDIR },
+ { "linkedincludefileinwritabledir",
+ DBS_LINKEDINCLUDEFILEINWRITABLEDIR },
+ { "linkedmapinwritabledir", DBS_LINKEDMAPINWRITABLEDIR },
+ { "linkedserviceswitchfileinwritabledir",
+ DBS_LINKEDSERVICESWITCHFILEINWRITABLEDIR },
+ { "filedeliverytohardlink", DBS_FILEDELIVERYTOHARDLINK },
+ { "filedeliverytosymlink", DBS_FILEDELIVERYTOSYMLINK },
+ { "writemaptohardlink", DBS_WRITEMAPTOHARDLINK },
+ { "writemaptosymlink", DBS_WRITEMAPTOSYMLINK },
+ { "writestatstohardlink", DBS_WRITESTATSTOHARDLINK },
+ { "writestatstosymlink", DBS_WRITESTATSTOSYMLINK },
+ { "forwardfileingroupwritabledirpath",
+ DBS_FORWARDFILEINGROUPWRITABLEDIRPATH },
+ { "includefileingroupwritabledirpath",
+ DBS_INCLUDEFILEINGROUPWRITABLEDIRPATH },
+ { "classfileinunsafedirpath", DBS_CLASSFILEINUNSAFEDIRPATH },
+ { "errorheaderinunsafedirpath", DBS_ERRORHEADERINUNSAFEDIRPATH },
+ { "helpfileinunsafedirpath", DBS_HELPFILEINUNSAFEDIRPATH },
+ { "forwardfileinunsafedirpathsafe",
+ DBS_FORWARDFILEINUNSAFEDIRPATHSAFE },
+ { "includefileinunsafedirpathsafe",
+ DBS_INCLUDEFILEINUNSAFEDIRPATHSAFE },
+ { "runprograminunsafedirpath", DBS_RUNPROGRAMINUNSAFEDIRPATH },
+ { "runwritableprogram", DBS_RUNWRITABLEPROGRAM },
+ { "nonrootsafeaddr", DBS_NONROOTSAFEADDR },
+ { "truststickybit", DBS_TRUSTSTICKYBIT },
+ { "dontwarnforwardfileinunsafedirpath",
+ DBS_DONTWARNFORWARDFILEINUNSAFEDIRPATH },
+ { "insufficiententropy", DBS_INSUFFICIENTENTROPY },
+ { "groupreadablesasldbfile", DBS_GROUPREADABLESASLDBFILE },
+ { "groupwritablesasldbfile", DBS_GROUPWRITABLESASLDBFILE },
+ { "groupwritableforwardfile", DBS_GROUPWRITABLEFORWARDFILE },
+ { "groupwritableincludefile", DBS_GROUPWRITABLEINCLUDEFILE },
+ { "worldwritableforwardfile", DBS_WORLDWRITABLEFORWARDFILE },
+ { "worldwritableincludefile", DBS_WORLDWRITABLEINCLUDEFILE },
+ { "groupreadablekeyfile", DBS_GROUPREADABLEKEYFILE },
+#if _FFR_GROUPREADABLEAUTHINFOFILE
+ { "groupreadableadefaultauthinfofile",
+ DBS_GROUPREADABLEAUTHINFOFILE },
+#endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
+ { NULL, 0 }
+};
+
+/*
+** Miscellaneous stuff.
+*/
+
+int DtableSize = 50; /* max open files; reset in 4.2bsd */
+/*
+** SETDEFAULTS -- set default values
+**
+** Some of these must be initialized using direct code since they
+** depend on run-time values. So let's do all of them this way.
+**
+** Parameters:
+** e -- the default envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Initializes a bunch of global variables to their
+** default values.
+*/
+
+#define MINUTES * 60
+#define HOURS * 60 MINUTES
+#define DAYS * 24 HOURS
+
+#ifndef MAXRULERECURSION
+# define MAXRULERECURSION 50 /* max ruleset recursion depth */
+#endif /* ! MAXRULERECURSION */
+
+void
+setdefaults(e)
+ register ENVELOPE *e;
+{
+ int i;
+ int numprocs;
+ struct passwd *pw;
+
+ numprocs = get_num_procs_online();
+ SpaceSub = ' '; /* option B */
+ QueueLA = 8 * numprocs; /* option x */
+ RefuseLA = 12 * numprocs; /* option X */
+ WkRecipFact = 30000L; /* option y */
+ WkClassFact = 1800L; /* option z */
+ WkTimeFact = 90000L; /* option Z */
+ QueueFactor = WkRecipFact * 20; /* option q */
+#if _FFR_QUARANTINE
+ QueueMode = QM_NORMAL; /* what queue items to act upon */
+#endif /* _FFR_QUARANTINE */
+ FileMode = (RealUid != geteuid()) ? 0644 : 0600;
+ /* option F */
+ QueueFileMode = (RealUid != geteuid()) ? 0644 : 0600;
+ /* option QueueFileMode */
+
+ if (((pw = sm_getpwnam("mailnull")) != NULL && pw->pw_uid != 0) ||
+ ((pw = sm_getpwnam("sendmail")) != NULL && pw->pw_uid != 0) ||
+ ((pw = sm_getpwnam("daemon")) != NULL && pw->pw_uid != 0))
+ {
+ DefUid = pw->pw_uid; /* option u */
+ DefGid = pw->pw_gid; /* option g */
+ DefUser = newstr(pw->pw_name);
+ }
+ else
+ {
+ DefUid = 1; /* option u */
+ DefGid = 1; /* option g */
+ setdefuser();
+ }
+ TrustedUid = 0;
+ if (tTd(37, 4))
+ sm_dprintf("setdefaults: DefUser=%s, DefUid=%d, DefGid=%d\n",
+ DefUser != NULL ? DefUser : "<1:1>",
+ (int) DefUid, (int) DefGid);
+ CheckpointInterval = 10; /* option C */
+ MaxHopCount = 25; /* option h */
+ set_delivery_mode(SM_FORK, e); /* option d */
+ e->e_errormode = EM_PRINT; /* option e */
+ e->e_qgrp = NOQGRP;
+ e->e_qdir = NOQDIR;
+ e->e_xfqgrp = NOQGRP;
+ e->e_xfqdir = NOQDIR;
+ e->e_ctime = curtime();
+ SevenBitInput = false; /* option 7 */
+ MaxMciCache = 1; /* option k */
+ MciCacheTimeout = 5 MINUTES; /* option K */
+ LogLevel = 9; /* option L */
+#if MILTER
+ MilterLogLevel = -1;
+#endif /* MILTER */
+ inittimeouts(NULL, false); /* option r */
+ PrivacyFlags = PRIV_PUBLIC; /* option p */
+ MeToo = true; /* option m */
+ SendMIMEErrors = true; /* option f */
+ SuperSafe = SAFE_REALLY; /* option s */
+ clrbitmap(DontBlameSendmail); /* DontBlameSendmail option */
+#if MIME8TO7
+ MimeMode = MM_CVTMIME|MM_PASS8BIT; /* option 8 */
+#else /* MIME8TO7 */
+ MimeMode = MM_PASS8BIT;
+#endif /* MIME8TO7 */
+ for (i = 0; i < MAXTOCLASS; i++)
+ {
+ TimeOuts.to_q_return[i] = 5 DAYS; /* option T */
+ TimeOuts.to_q_warning[i] = 0; /* option T */
+ }
+ ServiceSwitchFile = "/etc/mail/service.switch";
+ ServiceCacheMaxAge = (time_t) 10;
+ HostsFile = _PATH_HOSTS;
+ PidFile = newstr(_PATH_SENDMAILPID);
+ MustQuoteChars = "@,;:\\()[].'";
+ MciInfoTimeout = 30 MINUTES;
+ MaxRuleRecursion = MAXRULERECURSION;
+ MaxAliasRecursion = 10;
+ MaxMacroRecursion = 10;
+ ColonOkInAddr = true;
+ DontLockReadFiles = true;
+ DontProbeInterfaces = DPI_PROBEALL;
+ DoubleBounceAddr = "postmaster";
+ MaxHeadersLength = MAXHDRSLEN;
+ MaxForwardEntries = 0;
+ FastSplit = 1;
+#if SASL
+ AuthMechanisms = newstr(AUTH_MECHANISMS);
+ MaxSLBits = INT_MAX;
+#endif /* SASL */
+#if STARTTLS
+ TLS_Srv_Opts = TLS_I_SRV;
+#endif /* STARTTLS */
+#ifdef HESIOD_INIT
+ HesiodContext = NULL;
+#endif /* HESIOD_INIT */
+#if NETINET6
+ /* Detect if IPv6 is available at run time */
+ i = socket(AF_INET6, SOCK_STREAM, 0);
+ if (i >= 0)
+ {
+ InetMode = AF_INET6;
+ (void) close(i);
+ }
+ else
+ InetMode = AF_INET;
+#else /* NETINET6 */
+ InetMode = AF_INET;
+#endif /* NETINET6 */
+ ControlSocketName = NULL;
+ memset(&ConnectOnlyTo, '\0', sizeof ConnectOnlyTo);
+ DataFileBufferSize = 4096;
+ XscriptFileBufferSize = 4096;
+ for (i = 0; i < MAXRWSETS; i++)
+ RuleSetNames[i] = NULL;
+#if MILTER
+ InputFilters[0] = NULL;
+#endif /* MILTER */
+ setupmaps();
+ setupqueues();
+ setupmailers();
+ setupheaders();
+}
+
+
+/*
+** SETDEFUSER -- set/reset DefUser using DefUid (for initgroups())
+*/
+
+void
+setdefuser()
+{
+ struct passwd *defpwent;
+ static char defuserbuf[40];
+
+ DefUser = defuserbuf;
+ defpwent = sm_getpwuid(DefUid);
+ (void) sm_strlcpy(defuserbuf,
+ (defpwent == NULL || defpwent->pw_name == NULL)
+ ? "nobody" : defpwent->pw_name,
+ sizeof defuserbuf);
+ if (tTd(37, 4))
+ sm_dprintf("setdefuser: DefUid=%d, DefUser=%s\n",
+ (int) DefUid, DefUser);
+}
+/*
+** SETUPQUEUES -- initialize default queues
+**
+** The mqueue QUEUE structure gets filled in after readcf() but
+** we need something to point to now for the mailer setup,
+** which use "mqueue" as default queue.
+*/
+
+static void
+setupqueues()
+{
+ char buf[100];
+
+ MaxRunnersPerQueue = 1;
+ (void) sm_strlcpy(buf, "mqueue, P=/var/spool/mqueue", sizeof buf);
+ makequeue(buf, false);
+}
+/*
+** SETUPMAILERS -- initialize default mailers
+*/
+
+static void
+setupmailers()
+{
+ char buf[100];
+
+ (void) sm_strlcpy(buf, "prog, P=/bin/sh, F=lsouDq9, T=X-Unix/X-Unix/X-Unix, A=sh -c \201u",
+ sizeof buf);
+ makemailer(buf);
+
+ (void) sm_strlcpy(buf, "*file*, P=[FILE], F=lsDFMPEouq9, T=X-Unix/X-Unix/X-Unix, A=FILE \201u",
+ sizeof buf);
+ makemailer(buf);
+
+ (void) sm_strlcpy(buf, "*include*, P=/dev/null, F=su, A=INCLUDE \201u",
+ sizeof buf);
+ makemailer(buf);
+ initerrmailers();
+}
+/*
+** SETUPMAPS -- set up map classes
+*/
+
+#define MAPDEF(name, ext, flags, parse, open, close, lookup, store) \
+ { \
+ extern bool parse __P((MAP *, char *)); \
+ extern bool open __P((MAP *, int)); \
+ extern void close __P((MAP *)); \
+ extern char *lookup __P((MAP *, char *, char **, int *)); \
+ extern void store __P((MAP *, char *, char *)); \
+ s = stab(name, ST_MAPCLASS, ST_ENTER); \
+ s->s_mapclass.map_cname = name; \
+ s->s_mapclass.map_ext = ext; \
+ s->s_mapclass.map_cflags = flags; \
+ s->s_mapclass.map_parse = parse; \
+ s->s_mapclass.map_open = open; \
+ s->s_mapclass.map_close = close; \
+ s->s_mapclass.map_lookup = lookup; \
+ s->s_mapclass.map_store = store; \
+ }
+
+static void
+setupmaps()
+{
+ register STAB *s;
+
+#if NEWDB
+ MAPDEF("hash", ".db", MCF_ALIASOK|MCF_REBUILDABLE,
+ map_parseargs, hash_map_open, db_map_close,
+ db_map_lookup, db_map_store);
+
+ MAPDEF("btree", ".db", MCF_ALIASOK|MCF_REBUILDABLE,
+ map_parseargs, bt_map_open, db_map_close,
+ db_map_lookup, db_map_store);
+#endif /* NEWDB */
+
+#if NDBM
+ MAPDEF("dbm", ".dir", MCF_ALIASOK|MCF_REBUILDABLE,
+ map_parseargs, ndbm_map_open, ndbm_map_close,
+ ndbm_map_lookup, ndbm_map_store);
+#endif /* NDBM */
+
+#if NIS
+ MAPDEF("nis", NULL, MCF_ALIASOK,
+ map_parseargs, nis_map_open, null_map_close,
+ nis_map_lookup, null_map_store);
+#endif /* NIS */
+
+#if NISPLUS
+ MAPDEF("nisplus", NULL, MCF_ALIASOK,
+ map_parseargs, nisplus_map_open, null_map_close,
+ nisplus_map_lookup, null_map_store);
+#endif /* NISPLUS */
+
+#if LDAPMAP
+ MAPDEF("ldap", NULL, MCF_ALIASOK|MCF_NOTPERSIST,
+ ldapmap_parseargs, ldapmap_open, ldapmap_close,
+ ldapmap_lookup, null_map_store);
+#endif /* LDAPMAP */
+
+#if PH_MAP
+ MAPDEF("ph", NULL, MCF_NOTPERSIST,
+ ph_map_parseargs, ph_map_open, ph_map_close,
+ ph_map_lookup, null_map_store);
+#endif /* PH_MAP */
+
+#if MAP_NSD
+ /* IRIX 6.5 nsd support */
+ MAPDEF("nsd", NULL, MCF_ALIASOK,
+ map_parseargs, null_map_open, null_map_close,
+ nsd_map_lookup, null_map_store);
+#endif /* MAP_NSD */
+
+#if HESIOD
+ MAPDEF("hesiod", NULL, MCF_ALIASOK|MCF_ALIASONLY,
+ map_parseargs, hes_map_open, hes_map_close,
+ hes_map_lookup, null_map_store);
+#endif /* HESIOD */
+
+#if NETINFO
+ MAPDEF("netinfo", NULL, MCF_ALIASOK,
+ map_parseargs, ni_map_open, null_map_close,
+ ni_map_lookup, null_map_store);
+#endif /* NETINFO */
+
+#if 0
+ MAPDEF("dns", NULL, 0,
+ dns_map_init, null_map_open, null_map_close,
+ dns_map_lookup, null_map_store);
+#endif /* 0 */
+
+#if NAMED_BIND
+# if DNSMAP
+# if _FFR_DNSMAP_ALIASABLE
+ MAPDEF("dns", NULL, MCF_ALIASOK,
+ dns_map_parseargs, dns_map_open, null_map_close,
+ dns_map_lookup, null_map_store);
+# else /* _FFR_DNSMAP_ALIASABLE */
+ MAPDEF("dns", NULL, 0,
+ dns_map_parseargs, dns_map_open, null_map_close,
+ dns_map_lookup, null_map_store);
+# endif /* _FFR_DNSMAP_ALIASABLE */
+# endif /* DNSMAP */
+#endif /* NAMED_BIND */
+
+#if NAMED_BIND
+ /* best MX DNS lookup */
+ MAPDEF("bestmx", NULL, MCF_OPTFILE,
+ map_parseargs, null_map_open, null_map_close,
+ bestmx_map_lookup, null_map_store);
+#endif /* NAMED_BIND */
+
+ MAPDEF("host", NULL, 0,
+ host_map_init, null_map_open, null_map_close,
+ host_map_lookup, null_map_store);
+
+ MAPDEF("text", NULL, MCF_ALIASOK,
+ map_parseargs, text_map_open, null_map_close,
+ text_map_lookup, null_map_store);
+
+ MAPDEF("stab", NULL, MCF_ALIASOK|MCF_ALIASONLY,
+ map_parseargs, stab_map_open, null_map_close,
+ stab_map_lookup, stab_map_store);
+
+ MAPDEF("implicit", NULL, MCF_ALIASOK|MCF_ALIASONLY|MCF_REBUILDABLE,
+ map_parseargs, impl_map_open, impl_map_close,
+ impl_map_lookup, impl_map_store);
+
+ /* access to system passwd file */
+ MAPDEF("user", NULL, MCF_OPTFILE,
+ map_parseargs, user_map_open, null_map_close,
+ user_map_lookup, null_map_store);
+
+ /* dequote map */
+ MAPDEF("dequote", NULL, 0,
+ dequote_init, null_map_open, null_map_close,
+ dequote_map, null_map_store);
+
+#if MAP_REGEX
+ MAPDEF("regex", NULL, 0,
+ regex_map_init, null_map_open, null_map_close,
+ regex_map_lookup, null_map_store);
+#endif /* MAP_REGEX */
+
+#if USERDB
+ /* user database */
+ MAPDEF("userdb", ".db", 0,
+ map_parseargs, null_map_open, null_map_close,
+ udb_map_lookup, null_map_store);
+#endif /* USERDB */
+
+ /* arbitrary programs */
+ MAPDEF("program", NULL, MCF_ALIASOK,
+ map_parseargs, null_map_open, null_map_close,
+ prog_map_lookup, null_map_store);
+
+ /* sequenced maps */
+ MAPDEF("sequence", NULL, MCF_ALIASOK,
+ seq_map_parse, null_map_open, null_map_close,
+ seq_map_lookup, seq_map_store);
+
+ /* switched interface to sequenced maps */
+ MAPDEF("switch", NULL, MCF_ALIASOK,
+ map_parseargs, switch_map_open, null_map_close,
+ seq_map_lookup, seq_map_store);
+
+ /* null map lookup -- really for internal use only */
+ MAPDEF("null", NULL, MCF_ALIASOK|MCF_OPTFILE,
+ map_parseargs, null_map_open, null_map_close,
+ null_map_lookup, null_map_store);
+
+ /* syslog map -- logs information to syslog */
+ MAPDEF("syslog", NULL, 0,
+ syslog_map_parseargs, null_map_open, null_map_close,
+ syslog_map_lookup, null_map_store);
+
+ /* macro storage map -- rulesets can set macros */
+ MAPDEF("macro", NULL, 0,
+ dequote_init, null_map_open, null_map_close,
+ macro_map_lookup, null_map_store);
+
+ /* arithmetic map -- add/subtract/compare */
+ MAPDEF("arith", NULL, 0,
+ dequote_init, null_map_open, null_map_close,
+ arith_map_lookup, null_map_store);
+
+ if (tTd(38, 2))
+ {
+ /* bogus map -- always return tempfail */
+ MAPDEF("bogus", NULL, MCF_ALIASOK|MCF_OPTFILE,
+ map_parseargs, null_map_open, null_map_close,
+ bogus_map_lookup, null_map_store);
+ }
+}
+
+#undef MAPDEF
+/*
+** INITHOSTMAPS -- initial host-dependent maps
+**
+** This should act as an interface to any local service switch
+** provided by the host operating system.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Should define maps "host" and "users" as necessary
+** for this OS. If they are not defined, they will get
+** a default value later. It should check to make sure
+** they are not defined first, since it's possible that
+** the config file has provided an override.
+*/
+
+void
+inithostmaps()
+{
+ register int i;
+ int nmaps;
+ char *maptype[MAXMAPSTACK];
+ short mapreturn[MAXMAPACTIONS];
+ char buf[MAXLINE];
+
+ /*
+ ** Set up default hosts maps.
+ */
+
+#if 0
+ nmaps = switch_map_find("hosts", maptype, mapreturn);
+ for (i = 0; i < nmaps; i++)
+ {
+ if (strcmp(maptype[i], "files") == 0 &&
+ stab("hosts.files", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "hosts.files text -k 0 -v 1 /etc/hosts",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# if NAMED_BIND
+ else if (strcmp(maptype[i], "dns") == 0 &&
+ stab("hosts.dns", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "hosts.dns dns A", sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NAMED_BIND */
+# if NISPLUS
+ else if (strcmp(maptype[i], "nisplus") == 0 &&
+ stab("hosts.nisplus", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "hosts.nisplus nisplus -k name -v address hosts.org_dir",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NISPLUS */
+# if NIS
+ else if (strcmp(maptype[i], "nis") == 0 &&
+ stab("hosts.nis", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "hosts.nis nis -k 0 -v 1 hosts.byname",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NIS */
+# if NETINFO
+ else if (strcmp(maptype[i], "netinfo") == 0 &&
+ stab("hosts.netinfo", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "hosts.netinfo netinfo -v name /machines",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NETINFO */
+ }
+#endif /* 0 */
+
+ /*
+ ** Make sure we have a host map.
+ */
+
+ if (stab("host", ST_MAP, ST_FIND) == NULL)
+ {
+ /* user didn't initialize: set up host map */
+ (void) sm_strlcpy(buf, "host host", sizeof buf);
+#if NAMED_BIND
+ if (ConfigLevel >= 2)
+ (void) sm_strlcat(buf, " -a. -D", sizeof buf);
+#endif /* NAMED_BIND */
+ (void) makemapentry(buf);
+ }
+
+ /*
+ ** Set up default aliases maps
+ */
+
+ nmaps = switch_map_find("aliases", maptype, mapreturn);
+ for (i = 0; i < nmaps; i++)
+ {
+ if (strcmp(maptype[i], "files") == 0 &&
+ stab("aliases.files", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.files null",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#if NISPLUS
+ else if (strcmp(maptype[i], "nisplus") == 0 &&
+ stab("aliases.nisplus", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.nisplus nisplus -kalias -vexpansion mail_aliases.org_dir",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif /* NISPLUS */
+#if NIS
+ else if (strcmp(maptype[i], "nis") == 0 &&
+ stab("aliases.nis", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.nis nis mail.aliases",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif /* NIS */
+#if NETINFO
+ else if (strcmp(maptype[i], "netinfo") == 0 &&
+ stab("aliases.netinfo", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.netinfo netinfo -z, /aliases",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif /* NETINFO */
+#if HESIOD
+ else if (strcmp(maptype[i], "hesiod") == 0 &&
+ stab("aliases.hesiod", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases.hesiod hesiod aliases",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif /* HESIOD */
+ }
+ if (stab("aliases", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "aliases switch aliases", sizeof buf);
+ (void) makemapentry(buf);
+ }
+
+#if 0 /* "user" map class is a better choice */
+ /*
+ ** Set up default users maps.
+ */
+
+ nmaps = switch_map_find("passwd", maptype, mapreturn);
+ for (i = 0; i < nmaps; i++)
+ {
+ if (strcmp(maptype[i], "files") == 0 &&
+ stab("users.files", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "users.files text -m -z: -k0 -v6 /etc/passwd",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# if NISPLUS
+ else if (strcmp(maptype[i], "nisplus") == 0 &&
+ stab("users.nisplus", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "users.nisplus nisplus -m -kname -vhome passwd.org_dir",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NISPLUS */
+# if NIS
+ else if (strcmp(maptype[i], "nis") == 0 &&
+ stab("users.nis", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "users.nis nis -m passwd.byname",
+ sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* NIS */
+# if HESIOD
+ else if (strcmp(maptype[i], "hesiod") == 0 &&
+ stab("users.hesiod", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "users.hesiod hesiod", sizeof buf);
+ (void) makemapentry(buf);
+ }
+# endif /* HESIOD */
+ }
+ if (stab("users", ST_MAP, ST_FIND) == NULL)
+ {
+ (void) sm_strlcpy(buf, "users switch -m passwd", sizeof buf);
+ (void) makemapentry(buf);
+ }
+#endif /* 0 */
+}
+/*
+** SWITCH_MAP_FIND -- find the list of types associated with a map
+**
+** This is the system-dependent interface to the service switch.
+**
+** Parameters:
+** service -- the name of the service of interest.
+** maptype -- an out-array of strings containing the types
+** of access to use for this service. There can
+** be at most MAXMAPSTACK types for a single service.
+** mapreturn -- an out-array of return information bitmaps
+** for the map.
+**
+** Returns:
+** The number of map types filled in, or -1 for failure.
+**
+** Side effects:
+** Preserves errno so nothing in the routine clobbers it.
+*/
+
+#if defined(SOLARIS) || (defined(sony_news) && defined(__svr4))
+# define _USE_SUN_NSSWITCH_
+#endif /* defined(SOLARIS) || (defined(sony_news) && defined(__svr4)) */
+
+#if _FFR_HPUX_NSSWITCH
+# ifdef __hpux
+# define _USE_SUN_NSSWITCH_
+# endif /* __hpux */
+#endif /* _FFR_HPUX_NSSWITCH */
+
+#ifdef _USE_SUN_NSSWITCH_
+# include <nsswitch.h>
+#endif /* _USE_SUN_NSSWITCH_ */
+
+#if defined(ultrix) || (defined(__osf__) && defined(__alpha))
+# define _USE_DEC_SVC_CONF_
+#endif /* defined(ultrix) || (defined(__osf__) && defined(__alpha)) */
+
+#ifdef _USE_DEC_SVC_CONF_
+# include <sys/svcinfo.h>
+#endif /* _USE_DEC_SVC_CONF_ */
+
+int
+switch_map_find(service, maptype, mapreturn)
+ char *service;
+ char *maptype[MAXMAPSTACK];
+ short mapreturn[MAXMAPACTIONS];
+{
+ int svcno = 0;
+ int save_errno = errno;
+
+#ifdef _USE_SUN_NSSWITCH_
+ struct __nsw_switchconfig *nsw_conf;
+ enum __nsw_parse_err pserr;
+ struct __nsw_lookup *lk;
+ static struct __nsw_lookup lkp0 =
+ { "files", {1, 0, 0, 0}, NULL, NULL };
+ static struct __nsw_switchconfig lkp_default =
+ { 0, "sendmail", 3, &lkp0 };
+
+ for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
+ mapreturn[svcno] = 0;
+
+ if ((nsw_conf = __nsw_getconfig(service, &pserr)) == NULL)
+ lk = lkp_default.lookups;
+ else
+ lk = nsw_conf->lookups;
+ svcno = 0;
+ while (lk != NULL && svcno < MAXMAPSTACK)
+ {
+ maptype[svcno] = lk->service_name;
+ if (lk->actions[__NSW_NOTFOUND] == __NSW_RETURN)
+ mapreturn[MA_NOTFOUND] |= 1 << svcno;
+ if (lk->actions[__NSW_TRYAGAIN] == __NSW_RETURN)
+ mapreturn[MA_TRYAGAIN] |= 1 << svcno;
+ if (lk->actions[__NSW_UNAVAIL] == __NSW_RETURN)
+ mapreturn[MA_TRYAGAIN] |= 1 << svcno;
+ svcno++;
+ lk = lk->next;
+ }
+ errno = save_errno;
+ return svcno;
+#endif /* _USE_SUN_NSSWITCH_ */
+
+#ifdef _USE_DEC_SVC_CONF_
+ struct svcinfo *svcinfo;
+ int svc;
+
+ for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
+ mapreturn[svcno] = 0;
+
+ svcinfo = getsvc();
+ if (svcinfo == NULL)
+ goto punt;
+ if (strcmp(service, "hosts") == 0)
+ svc = SVC_HOSTS;
+ else if (strcmp(service, "aliases") == 0)
+ svc = SVC_ALIASES;
+ else if (strcmp(service, "passwd") == 0)
+ svc = SVC_PASSWD;
+ else
+ {
+ errno = save_errno;
+ return -1;
+ }
+ for (svcno = 0; svcno < SVC_PATHSIZE && svcno < MAXMAPSTACK; svcno++)
+ {
+ switch (svcinfo->svcpath[svc][svcno])
+ {
+ case SVC_LOCAL:
+ maptype[svcno] = "files";
+ break;
+
+ case SVC_YP:
+ maptype[svcno] = "nis";
+ break;
+
+ case SVC_BIND:
+ maptype[svcno] = "dns";
+ break;
+
+# ifdef SVC_HESIOD
+ case SVC_HESIOD:
+ maptype[svcno] = "hesiod";
+ break;
+# endif /* SVC_HESIOD */
+
+ case SVC_LAST:
+ errno = save_errno;
+ return svcno;
+ }
+ }
+ errno = save_errno;
+ return svcno;
+#endif /* _USE_DEC_SVC_CONF_ */
+
+#if !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_)
+ /*
+ ** Fall-back mechanism.
+ */
+
+ STAB *st;
+ static time_t servicecachetime; /* time service switch was cached */
+ time_t now = curtime();
+
+ for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
+ mapreturn[svcno] = 0;
+
+ if ((now - servicecachetime) > (time_t) ServiceCacheMaxAge)
+ {
+ /* (re)read service switch */
+ register SM_FILE_T *fp;
+ long sff = SFF_REGONLY|SFF_OPENASROOT|SFF_NOLOCK;
+
+ if (!bitnset(DBS_LINKEDSERVICESWITCHFILEINWRITABLEDIR,
+ DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+
+ if (ConfigFileRead)
+ servicecachetime = now;
+ fp = safefopen(ServiceSwitchFile, O_RDONLY, 0, sff);
+ if (fp != NULL)
+ {
+ char buf[MAXLINE];
+
+ while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf,
+ sizeof buf) != NULL)
+ {
+ register char *p;
+
+ p = strpbrk(buf, "#\n");
+ if (p != NULL)
+ *p = '\0';
+ p = strpbrk(buf, " \t");
+ if (p != NULL)
+ *p++ = '\0';
+ if (buf[0] == '\0')
+ continue;
+ if (p == NULL)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "Bad line on %.100s: %.100s",
+ ServiceSwitchFile,
+ buf);
+ continue;
+ }
+ while (isspace(*p))
+ p++;
+ if (*p == '\0')
+ continue;
+
+ /*
+ ** Find/allocate space for this service entry.
+ ** Space for all of the service strings
+ ** are allocated at once. This means
+ ** that we only have to free the first
+ ** one to free all of them.
+ */
+
+ st = stab(buf, ST_SERVICE, ST_ENTER);
+ if (st->s_service[0] != NULL)
+ sm_free((void *) st->s_service[0]); /* XXX */
+ p = newstr(p);
+ for (svcno = 0; svcno < MAXMAPSTACK; )
+ {
+ if (*p == '\0')
+ break;
+ st->s_service[svcno++] = p;
+ p = strpbrk(p, " \t");
+ if (p == NULL)
+ break;
+ *p++ = '\0';
+ while (isspace(*p))
+ p++;
+ }
+ if (svcno < MAXMAPSTACK)
+ st->s_service[svcno] = NULL;
+ }
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ }
+ }
+
+ /* look up entry in cache */
+ st = stab(service, ST_SERVICE, ST_FIND);
+ if (st != NULL && st->s_service[0] != NULL)
+ {
+ /* extract data */
+ svcno = 0;
+ while (svcno < MAXMAPSTACK)
+ {
+ maptype[svcno] = st->s_service[svcno];
+ if (maptype[svcno++] == NULL)
+ break;
+ }
+ errno = save_errno;
+ return --svcno;
+ }
+#endif /* !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_) */
+
+#if !defined(_USE_SUN_NSSWITCH_)
+ /* if the service file doesn't work, use an absolute fallback */
+# ifdef _USE_DEC_SVC_CONF_
+ punt:
+# endif /* _USE_DEC_SVC_CONF_ */
+ for (svcno = 0; svcno < MAXMAPACTIONS; svcno++)
+ mapreturn[svcno] = 0;
+ svcno = 0;
+ if (strcmp(service, "aliases") == 0)
+ {
+ maptype[svcno++] = "files";
+# if defined(AUTO_NETINFO_ALIASES) && defined (NETINFO)
+ maptype[svcno++] = "netinfo";
+# endif /* defined(AUTO_NETINFO_ALIASES) && defined (NETINFO) */
+# ifdef AUTO_NIS_ALIASES
+# if NISPLUS
+ maptype[svcno++] = "nisplus";
+# endif /* NISPLUS */
+# if NIS
+ maptype[svcno++] = "nis";
+# endif /* NIS */
+# endif /* AUTO_NIS_ALIASES */
+ errno = save_errno;
+ return svcno;
+ }
+ if (strcmp(service, "hosts") == 0)
+ {
+# if NAMED_BIND
+ maptype[svcno++] = "dns";
+# else /* NAMED_BIND */
+# if defined(sun) && !defined(BSD)
+ /* SunOS */
+ maptype[svcno++] = "nis";
+# endif /* defined(sun) && !defined(BSD) */
+# endif /* NAMED_BIND */
+# if defined(AUTO_NETINFO_HOSTS) && defined (NETINFO)
+ maptype[svcno++] = "netinfo";
+# endif /* defined(AUTO_NETINFO_HOSTS) && defined (NETINFO) */
+ maptype[svcno++] = "files";
+ errno = save_errno;
+ return svcno;
+ }
+ errno = save_errno;
+ return -1;
+#endif /* !defined(_USE_SUN_NSSWITCH_) */
+}
+/*
+** USERNAME -- return the user id of the logged in user.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** The login name of the logged in user.
+**
+** Side Effects:
+** none.
+**
+** Notes:
+** The return value is statically allocated.
+*/
+
+char *
+username()
+{
+ static char *myname = NULL;
+ extern char *getlogin();
+ register struct passwd *pw;
+
+ /* cache the result */
+ if (myname == NULL)
+ {
+ myname = getlogin();
+ if (myname == NULL || myname[0] == '\0')
+ {
+ pw = sm_getpwuid(RealUid);
+ if (pw != NULL)
+ myname = pw->pw_name;
+ }
+ else
+ {
+ uid_t uid = RealUid;
+
+ if ((pw = sm_getpwnam(myname)) == NULL ||
+ (uid != 0 && uid != pw->pw_uid))
+ {
+ pw = sm_getpwuid(uid);
+ if (pw != NULL)
+ myname = pw->pw_name;
+ }
+ }
+ if (myname == NULL || myname[0] == '\0')
+ {
+ syserr("554 5.3.0 Who are you?");
+ myname = "postmaster";
+ }
+ else if (strpbrk(myname, ",;:/|\"\\") != NULL)
+ myname = addquotes(myname, NULL);
+ else
+ myname = sm_pstrdup_x(myname);
+ }
+ return myname;
+}
+/*
+** TTYPATH -- Get the path of the user's tty
+**
+** Returns the pathname of the user's tty. Returns NULL if
+** the user is not logged in or if s/he has write permission
+** denied.
+**
+** Parameters:
+** none
+**
+** Returns:
+** pathname of the user's tty.
+** NULL if not logged in or write permission denied.
+**
+** Side Effects:
+** none.
+**
+** WARNING:
+** Return value is in a local buffer.
+**
+** Called By:
+** savemail
+*/
+
+char *
+ttypath()
+{
+ struct stat stbuf;
+ register char *pathn;
+ extern char *ttyname();
+ extern char *getlogin();
+
+ /* compute the pathname of the controlling tty */
+ if ((pathn = ttyname(2)) == NULL && (pathn = ttyname(1)) == NULL &&
+ (pathn = ttyname(0)) == NULL)
+ {
+ errno = 0;
+ return NULL;
+ }
+
+ /* see if we have write permission */
+ if (stat(pathn, &stbuf) < 0 || !bitset(S_IWOTH, stbuf.st_mode))
+ {
+ errno = 0;
+ return NULL;
+ }
+
+ /* see if the user is logged in */
+ if (getlogin() == NULL)
+ return NULL;
+
+ /* looks good */
+ return pathn;
+}
+/*
+** CHECKCOMPAT -- check for From and To person compatible.
+**
+** This routine can be supplied on a per-installation basis
+** to determine whether a person is allowed to send a message.
+** This allows restriction of certain types of internet
+** forwarding or registration of users.
+**
+** If the hosts are found to be incompatible, an error
+** message should be given using "usrerr" and an EX_ code
+** should be returned. You can also set to->q_status to
+** a DSN-style status code.
+**
+** EF_NO_BODY_RETN can be set in e->e_flags to suppress the
+** body during the return-to-sender function; this should be done
+** on huge messages. This bit may already be set by the ESMTP
+** protocol.
+**
+** Parameters:
+** to -- the person being sent to.
+**
+** Returns:
+** an exit status
+**
+** Side Effects:
+** none (unless you include the usrerr stuff)
+*/
+
+int
+checkcompat(to, e)
+ register ADDRESS *to;
+ register ENVELOPE *e;
+{
+ if (tTd(49, 1))
+ sm_dprintf("checkcompat(to=%s, from=%s)\n",
+ to->q_paddr, e->e_from.q_paddr);
+
+#ifdef EXAMPLE_CODE
+ /* this code is intended as an example only */
+ register STAB *s;
+
+ s = stab("arpa", ST_MAILER, ST_FIND);
+ if (s != NULL && strcmp(e->e_from.q_mailer->m_name, "local") != 0 &&
+ to->q_mailer == s->s_mailer)
+ {
+ usrerr("553 No ARPA mail through this machine: see your system administration");
+ /* e->e_flags |= EF_NO_BODY_RETN; to suppress body on return */
+ to->q_status = "5.7.1";
+ return EX_UNAVAILABLE;
+ }
+#endif /* EXAMPLE_CODE */
+ return EX_OK;
+}
+/*
+** INIT_MD -- do machine dependent initializations
+**
+** Systems that have global modes that should be set should do
+** them here rather than in main.
+*/
+
+#ifdef _AUX_SOURCE
+# include <compat.h>
+#endif /* _AUX_SOURCE */
+
+#if SHARE_V1
+# include <shares.h>
+#endif /* SHARE_V1 */
+
+void
+init_md(argc, argv)
+ int argc;
+ char **argv;
+{
+#ifdef _AUX_SOURCE
+ setcompat(getcompat() | COMPAT_BSDPROT);
+#endif /* _AUX_SOURCE */
+
+#ifdef SUN_EXTENSIONS
+ init_md_sun();
+#endif /* SUN_EXTENSIONS */
+
+#if _CONVEX_SOURCE
+ /* keep gethostby*() from stripping the local domain name */
+ set_domain_trim_off();
+#endif /* _CONVEX_SOURCE */
+#ifdef __QNX__
+ /*
+ ** Due to QNX's network distributed nature, you can target a tcpip
+ ** stack on a different node in the qnx network; this patch lets
+ ** this feature work. The __sock_locate() must be done before the
+ ** environment is clear.
+ */
+ __sock_locate();
+#endif /* __QNX__ */
+#if SECUREWARE || defined(_SCO_unix_)
+ set_auth_parameters(argc, argv);
+
+# ifdef _SCO_unix_
+ /*
+ ** This is required for highest security levels (the kernel
+ ** won't let it call set*uid() or run setuid binaries without
+ ** it). It may be necessary on other SECUREWARE systems.
+ */
+
+ if (getluid() == -1)
+ setluid(0);
+# endif /* _SCO_unix_ */
+#endif /* SECUREWARE || defined(_SCO_unix_) */
+
+
+#ifdef VENDOR_DEFAULT
+ VendorCode = VENDOR_DEFAULT;
+#else /* VENDOR_DEFAULT */
+ VendorCode = VENDOR_BERKELEY;
+#endif /* VENDOR_DEFAULT */
+}
+/*
+** INIT_VENDOR_MACROS -- vendor-dependent macro initializations
+**
+** Called once, on startup.
+**
+** Parameters:
+** e -- the global envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** vendor-dependent.
+*/
+
+void
+init_vendor_macros(e)
+ register ENVELOPE *e;
+{
+}
+/*
+** GETLA -- get the current load average
+**
+** This code stolen from la.c.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** The current load average as an integer.
+**
+** Side Effects:
+** none.
+*/
+
+/* try to guess what style of load average we have */
+#define LA_ZERO 1 /* always return load average as zero */
+#define LA_INT 2 /* read kmem for avenrun; interpret as long */
+#define LA_FLOAT 3 /* read kmem for avenrun; interpret as float */
+#define LA_SUBR 4 /* call getloadavg */
+#define LA_MACH 5 /* MACH load averages (as on NeXT boxes) */
+#define LA_SHORT 6 /* read kmem for avenrun; interpret as short */
+#define LA_PROCSTR 7 /* read string ("1.17") from /proc/loadavg */
+#define LA_READKSYM 8 /* SVR4: use MIOC_READKSYM ioctl call */
+#define LA_DGUX 9 /* special DGUX implementation */
+#define LA_HPUX 10 /* special HPUX implementation */
+#define LA_IRIX6 11 /* special IRIX 6.2 implementation */
+#define LA_KSTAT 12 /* special Solaris kstat(3k) implementation */
+#define LA_DEVSHORT 13 /* read short from a device */
+#define LA_ALPHAOSF 14 /* Digital UNIX (OSF/1 on Alpha) table() call */
+#define LA_PSET 15 /* Solaris per-processor-set load average */
+
+/* do guesses based on general OS type */
+#ifndef LA_TYPE
+# define LA_TYPE LA_ZERO
+#endif /* ! LA_TYPE */
+
+#ifndef FSHIFT
+# if defined(unixpc)
+# define FSHIFT 5
+# endif /* defined(unixpc) */
+
+# if defined(__alpha) || defined(IRIX)
+# define FSHIFT 10
+# endif /* defined(__alpha) || defined(IRIX) */
+
+#endif /* ! FSHIFT */
+
+#ifndef FSHIFT
+# define FSHIFT 8
+#endif /* ! FSHIFT */
+
+#ifndef FSCALE
+# define FSCALE (1 << FSHIFT)
+#endif /* ! FSCALE */
+
+#ifndef LA_AVENRUN
+# ifdef SYSTEM5
+# define LA_AVENRUN "avenrun"
+# else /* SYSTEM5 */
+# define LA_AVENRUN "_avenrun"
+# endif /* SYSTEM5 */
+#endif /* ! LA_AVENRUN */
+
+/* _PATH_KMEM should be defined in <paths.h> */
+#ifndef _PATH_KMEM
+# define _PATH_KMEM "/dev/kmem"
+#endif /* ! _PATH_KMEM */
+
+#if (LA_TYPE == LA_INT) || (LA_TYPE == LA_FLOAT) || (LA_TYPE == LA_SHORT)
+
+# include <nlist.h>
+
+/* _PATH_UNIX should be defined in <paths.h> */
+# ifndef _PATH_UNIX
+# if defined(SYSTEM5)
+# define _PATH_UNIX "/unix"
+# else /* defined(SYSTEM5) */
+# define _PATH_UNIX "/vmunix"
+# endif /* defined(SYSTEM5) */
+# endif /* ! _PATH_UNIX */
+
+# ifdef _AUX_SOURCE
+struct nlist Nl[2];
+# else /* _AUX_SOURCE */
+struct nlist Nl[] =
+{
+ { LA_AVENRUN },
+ { 0 },
+};
+# endif /* _AUX_SOURCE */
+# define X_AVENRUN 0
+
+int
+getla()
+{
+ int j;
+ static int kmem = -1;
+# if LA_TYPE == LA_INT
+ long avenrun[3];
+# else /* LA_TYPE == LA_INT */
+# if LA_TYPE == LA_SHORT
+ short avenrun[3];
+# else /* LA_TYPE == LA_SHORT */
+ double avenrun[3];
+# endif /* LA_TYPE == LA_SHORT */
+# endif /* LA_TYPE == LA_INT */
+ extern int errno;
+ extern off_t lseek();
+
+ if (kmem < 0)
+ {
+# ifdef _AUX_SOURCE
+ (void) sm_strlcpy(Nl[X_AVENRUN].n_name, LA_AVENRUN,
+ sizeof Nl[X_AVENRUN].n_name);
+ Nl[1].n_name[0] = '\0';
+# endif /* _AUX_SOURCE */
+
+# if defined(_AIX3) || defined(_AIX4)
+ if (knlist(Nl, 1, sizeof Nl[0]) < 0)
+# else /* defined(_AIX3) || defined(_AIX4) */
+ if (nlist(_PATH_UNIX, Nl) < 0)
+# endif /* defined(_AIX3) || defined(_AIX4) */
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: nlist(%s): %s\n", _PATH_UNIX,
+ sm_errstring(errno));
+ return -1;
+ }
+ if (Nl[X_AVENRUN].n_value == 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: nlist(%s, %s) ==> 0\n",
+ _PATH_UNIX, LA_AVENRUN);
+ return -1;
+ }
+# ifdef NAMELISTMASK
+ Nl[X_AVENRUN].n_value &= NAMELISTMASK;
+# endif /* NAMELISTMASK */
+
+ kmem = open(_PATH_KMEM, 0, 0);
+ if (kmem < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: open(/dev/kmem): %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
+ fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: fcntl(/dev/kmem, FD_CLOEXEC): %s\n",
+ sm_errstring(errno));
+ (void) close(kmem);
+ kmem = -1;
+ return -1;
+ }
+ }
+ if (tTd(3, 20))
+ sm_dprintf("getla: symbol address = %#lx\n",
+ (unsigned long) Nl[X_AVENRUN].n_value);
+ if (lseek(kmem, (off_t) Nl[X_AVENRUN].n_value, SEEK_SET) == -1 ||
+ read(kmem, (char *) avenrun, sizeof(avenrun)) < sizeof(avenrun))
+ {
+ /* thank you Ian */
+ if (tTd(3, 1))
+ sm_dprintf("getla: lseek or read: %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+# if (LA_TYPE == LA_INT) || (LA_TYPE == LA_SHORT)
+ if (tTd(3, 5))
+ {
+# if LA_TYPE == LA_SHORT
+ sm_dprintf("getla: avenrun = %d", avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %d, %d", avenrun[1], avenrun[2]);
+# else /* LA_TYPE == LA_SHORT */
+ sm_dprintf("getla: avenrun = %ld", avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %ld, %ld", avenrun[1], avenrun[2]);
+# endif /* LA_TYPE == LA_SHORT */
+ sm_dprintf("\n");
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n",
+ (int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+ return ((int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+# else /* (LA_TYPE == LA_INT) || (LA_TYPE == LA_SHORT) */
+ if (tTd(3, 5))
+ {
+ sm_dprintf("getla: avenrun = %g", avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %g, %g", avenrun[1], avenrun[2]);
+ sm_dprintf("\n");
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", (int) (avenrun[0] +0.5));
+ return ((int) (avenrun[0] + 0.5));
+# endif /* (LA_TYPE == LA_INT) || (LA_TYPE == LA_SHORT) */
+}
+
+#endif /* (LA_TYPE == LA_INT) || (LA_TYPE == LA_FLOAT) || (LA_TYPE == LA_SHORT) */
+
+#if LA_TYPE == LA_READKSYM
+
+# include <sys/ksym.h>
+
+int
+getla()
+{
+ int j;
+ static int kmem = -1;
+ long avenrun[3];
+ extern int errno;
+ struct mioc_rksym mirk;
+
+ if (kmem < 0)
+ {
+ kmem = open("/dev/kmem", 0, 0);
+ if (kmem < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: open(/dev/kmem): %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
+ fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: fcntl(/dev/kmem, FD_CLOEXEC): %s\n",
+ sm_errstring(errno));
+ (void) close(kmem);
+ kmem = -1;
+ return -1;
+ }
+ }
+ mirk.mirk_symname = LA_AVENRUN;
+ mirk.mirk_buf = avenrun;
+ mirk.mirk_buflen = sizeof(avenrun);
+ if (ioctl(kmem, MIOC_READKSYM, &mirk) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: ioctl(MIOC_READKSYM) failed: %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (tTd(3, 5))
+ {
+ sm_dprintf("getla: avenrun = %d", avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %d, %d", avenrun[1], avenrun[2]);
+ sm_dprintf("\n");
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n",
+ (int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+ return ((int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+}
+
+#endif /* LA_TYPE == LA_READKSYM */
+
+#if LA_TYPE == LA_DGUX
+
+# include <sys/dg_sys_info.h>
+
+int
+getla()
+{
+ struct dg_sys_info_load_info load_info;
+
+ dg_sys_info((long *)&load_info,
+ DG_SYS_INFO_LOAD_INFO_TYPE, DG_SYS_INFO_LOAD_VERSION_0);
+
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", (int) (load_info.one_minute + 0.5));
+
+ return ((int) (load_info.one_minute + 0.5));
+}
+
+#endif /* LA_TYPE == LA_DGUX */
+
+#if LA_TYPE == LA_HPUX
+
+/* forward declarations to keep gcc from complaining */
+struct pst_dynamic;
+struct pst_status;
+struct pst_static;
+struct pst_vminfo;
+struct pst_diskinfo;
+struct pst_processor;
+struct pst_lv;
+struct pst_swapinfo;
+
+# include <sys/param.h>
+# include <sys/pstat.h>
+
+int
+getla()
+{
+ struct pst_dynamic pstd;
+
+ if (pstat_getdynamic(&pstd, sizeof(struct pst_dynamic),
+ (size_t) 1, 0) == -1)
+ return 0;
+
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", (int) (pstd.psd_avg_1_min + 0.5));
+
+ return (int) (pstd.psd_avg_1_min + 0.5);
+}
+
+#endif /* LA_TYPE == LA_HPUX */
+
+#if LA_TYPE == LA_SUBR
+
+int
+getla()
+{
+ double avenrun[3];
+
+ if (getloadavg(avenrun, sizeof(avenrun) / sizeof(avenrun[0])) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: getloadavg failed: %s",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", (int) (avenrun[0] +0.5));
+ return ((int) (avenrun[0] + 0.5));
+}
+
+#endif /* LA_TYPE == LA_SUBR */
+
+#if LA_TYPE == LA_MACH
+
+/*
+** This has been tested on NEXTSTEP release 2.1/3.X.
+*/
+
+# if defined(NX_CURRENT_COMPILER_RELEASE) && NX_CURRENT_COMPILER_RELEASE > NX_COMPILER_RELEASE_3_0
+# include <mach/mach.h>
+# else /* defined(NX_CURRENT_COMPILER_RELEASE) && NX_CURRENT_COMPILER_RELEASE > NX_COMPILER_RELEASE_3_0 */
+# include <mach.h>
+# endif /* defined(NX_CURRENT_COMPILER_RELEASE) && NX_CURRENT_COMPILER_RELEASE > NX_COMPILER_RELEASE_3_0 */
+
+int
+getla()
+{
+ processor_set_t default_set;
+ kern_return_t error;
+ unsigned int info_count;
+ struct processor_set_basic_info info;
+ host_t host;
+
+ error = processor_set_default(host_self(), &default_set);
+ if (error != KERN_SUCCESS)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: processor_set_default failed: %s",
+ sm_errstring(errno));
+ return -1;
+ }
+ info_count = PROCESSOR_SET_BASIC_INFO_COUNT;
+ if (processor_set_info(default_set, PROCESSOR_SET_BASIC_INFO,
+ &host, (processor_set_info_t)&info,
+ &info_count) != KERN_SUCCESS)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: processor_set_info failed: %s",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n",
+ (int) ((info.load_average + (LOAD_SCALE / 2)) /
+ LOAD_SCALE));
+ return (int) (info.load_average + (LOAD_SCALE / 2)) / LOAD_SCALE;
+}
+
+#endif /* LA_TYPE == LA_MACH */
+
+#if LA_TYPE == LA_PROCSTR
+# if SM_CONF_BROKEN_STRTOD
+ ERROR: This OS has most likely a broken strtod() implemenentation.
+ ERROR: The function is required for getla().
+ ERROR: Check the compilation options _LA_PROCSTR and
+ ERROR: _SM_CONF_BROKEN_STRTOD (without the leading _).
+# endif /* SM_CONF_BROKEN_STRTOD */
+
+/*
+** Read /proc/loadavg for the load average. This is assumed to be
+** in a format like "0.15 0.12 0.06".
+**
+** Initially intended for Linux. This has been in the kernel
+** since at least 0.99.15.
+*/
+
+# ifndef _PATH_LOADAVG
+# define _PATH_LOADAVG "/proc/loadavg"
+# endif /* ! _PATH_LOADAVG */
+
+int
+getla()
+{
+ double avenrun;
+ register int result;
+ SM_FILE_T *fp;
+
+ fp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, _PATH_LOADAVG, SM_IO_RDONLY,
+ NULL);
+ if (fp == NULL)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: sm_io_open(%s): %s\n",
+ _PATH_LOADAVG, sm_errstring(errno));
+ return -1;
+ }
+ result = sm_io_fscanf(fp, SM_TIME_DEFAULT, "%lf", &avenrun);
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ if (result != 1)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: sm_io_fscanf() = %d: %s\n",
+ result, sm_errstring(errno));
+ return -1;
+ }
+
+ if (tTd(3, 1))
+ sm_dprintf("getla(): %.2f\n", avenrun);
+
+ return ((int) (avenrun + 0.5));
+}
+
+#endif /* LA_TYPE == LA_PROCSTR */
+
+#if LA_TYPE == LA_IRIX6
+
+# include <sys/sysmp.h>
+
+int
+getla(void)
+{
+ int j;
+ static int kmem = -1;
+ int avenrun[3];
+
+ if (kmem < 0)
+ {
+ kmem = open(_PATH_KMEM, 0, 0);
+ if (kmem < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: open(%s): %s\n", _PATH_KMEM,
+ sm_errstring(errno));
+ return -1;
+ }
+ if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
+ fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: fcntl(/dev/kmem, FD_CLOEXEC): %s\n",
+ sm_errstring(errno));
+ (void) close(kmem);
+ kmem = -1;
+ return -1;
+ }
+ }
+
+ if (lseek(kmem, (sysmp(MP_KERNADDR, MPKA_AVENRUN) & 0x7fffffff), SEEK_SET) == -1 ||
+ read(kmem, (char *) avenrun, sizeof(avenrun)) < sizeof(avenrun))
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: lseek or read: %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (tTd(3, 5))
+ {
+ sm_dprintf("getla: avenrun = %ld", (long int) avenrun[0]);
+ if (tTd(3, 15))
+ sm_dprintf(", %ld, %ld",
+ (long int) avenrun[1], (long int) avenrun[2]);
+ sm_dprintf("\n");
+ }
+
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n",
+ (int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+ return ((int) (avenrun[0] + FSCALE/2) >> FSHIFT);
+
+}
+#endif /* LA_TYPE == LA_IRIX6 */
+
+#if LA_TYPE == LA_KSTAT
+
+# include <kstat.h>
+
+int
+getla()
+{
+ static kstat_ctl_t *kc = NULL;
+ static kstat_t *ksp = NULL;
+ kstat_named_t *ksn;
+ int la;
+
+ if (kc == NULL) /* if not initialized before */
+ kc = kstat_open();
+ if (kc == NULL)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: kstat_open(): %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (ksp == NULL)
+ ksp = kstat_lookup(kc, "unix", 0, "system_misc");
+ if (ksp == NULL)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: kstat_lookup(): %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (kstat_read(kc, ksp, NULL) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: kstat_read(): %s\n",
+ sm_errstring(errno));
+ return -1;
+ }
+ ksn = (kstat_named_t *) kstat_data_lookup(ksp, "avenrun_1min");
+ la = ((double) ksn->value.ul + FSCALE/2) / FSCALE;
+ /* kstat_close(kc); /o do not close for fast access */
+ return la;
+}
+
+#endif /* LA_TYPE == LA_KSTAT */
+
+#if LA_TYPE == LA_DEVSHORT
+
+/*
+** Read /dev/table/avenrun for the load average. This should contain
+** three shorts for the 1, 5, and 15 minute loads. We only read the
+** first, since that's all we care about.
+**
+** Intended for SCO OpenServer 5.
+*/
+
+# ifndef _PATH_AVENRUN
+# define _PATH_AVENRUN "/dev/table/avenrun"
+# endif /* ! _PATH_AVENRUN */
+
+int
+getla()
+{
+ static int afd = -1;
+ short avenrun;
+ int loadav;
+ int r;
+
+ errno = EBADF;
+
+ if (afd == -1 || lseek(afd, 0L, SEEK_SET) == -1)
+ {
+ if (errno != EBADF)
+ return -1;
+ afd = open(_PATH_AVENRUN, O_RDONLY|O_SYNC);
+ if (afd < 0)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "can't open %s: %s",
+ _PATH_AVENRUN, sm_errstring(errno));
+ return -1;
+ }
+ }
+
+ r = read(afd, &avenrun, sizeof avenrun);
+
+ if (tTd(3, 5))
+ sm_dprintf("getla: avenrun = %d\n", avenrun);
+ loadav = (int) (avenrun + FSCALE/2) >> FSHIFT;
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", loadav);
+ return loadav;
+}
+
+#endif /* LA_TYPE == LA_DEVSHORT */
+
+#if LA_TYPE == LA_ALPHAOSF
+struct rtentry;
+struct mbuf;
+# include <sys/table.h>
+
+int
+getla()
+{
+ int ave = 0;
+ struct tbl_loadavg tab;
+
+ if (table(TBL_LOADAVG, 0, &tab, 1, sizeof(tab)) == -1)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: table %s\n", sm_errstring(errno));
+ return -1;
+ }
+
+ if (tTd(3, 1))
+ sm_dprintf("getla: scale = %d\n", tab.tl_lscale);
+
+ if (tab.tl_lscale)
+ ave = ((tab.tl_avenrun.l[2] + (tab.tl_lscale/2)) /
+ tab.tl_lscale);
+ else
+ ave = (int) (tab.tl_avenrun.d[2] + 0.5);
+
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", ave);
+
+ return ave;
+}
+
+#endif /* LA_TYPE == LA_ALPHAOSF */
+
+#if LA_TYPE == LA_PSET
+
+int
+getla()
+{
+ double avenrun[3];
+
+ if (pset_getloadavg(PS_MYID, avenrun,
+ sizeof(avenrun) / sizeof(avenrun[0])) < 0)
+ {
+ if (tTd(3, 1))
+ sm_dprintf("getla: pset_getloadavg failed: %s",
+ sm_errstring(errno));
+ return -1;
+ }
+ if (tTd(3, 1))
+ sm_dprintf("getla: %d\n", (int) (avenrun[0] +0.5));
+ return ((int) (avenrun[0] + 0.5));
+}
+
+#endif /* LA_TYPE == LA_PSET */
+
+#if LA_TYPE == LA_ZERO
+
+int
+getla()
+{
+ if (tTd(3, 1))
+ sm_dprintf("getla: ZERO\n");
+ return 0;
+}
+
+#endif /* LA_TYPE == LA_ZERO */
+
+/*
+ * Copyright 1989 Massachusetts Institute of Technology
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. M.I.T. makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors: Many and varied...
+ */
+
+/* Non Apollo stuff removed by Don Lewis 11/15/93 */
+#ifndef lint
+SM_UNUSED(static char rcsid[]) = "@(#)$OrigId: getloadavg.c,v 1.16 1991/06/21 12:51:15 paul Exp $";
+#endif /* ! lint */
+
+#ifdef apollo
+# undef volatile
+# include <apollo/base.h>
+
+/* ARGSUSED */
+int getloadavg( call_data )
+ caddr_t call_data; /* pointer to (double) return value */
+{
+ double *avenrun = (double *) call_data;
+ int i;
+ status_$t st;
+ long loadav[3];
+
+ proc1_$get_loadav(loadav, &st);
+ *avenrun = loadav[0] / (double) (1 << 16);
+ return 0;
+}
+#endif /* apollo */
+/*
+** SM_GETLA -- get the current load average
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Set CurrentLA to the current load average.
+** Set {load_avg} in GlobalMacros to the current load average.
+*/
+
+void
+sm_getla()
+{
+ char labuf[8];
+
+ CurrentLA = getla();
+ (void) sm_snprintf(labuf, sizeof labuf, "%d", CurrentLA);
+ macdefine(&GlobalMacros, A_TEMP, macid("{load_avg}"), labuf);
+}
+/*
+** SHOULDQUEUE -- should this message be queued or sent?
+**
+** Compares the message cost to the load average to decide.
+**
+** Note: Do NOT change this API! It is documented in op.me
+** and theoretically the user can change this function...
+**
+** Parameters:
+** pri -- the priority of the message in question.
+** ct -- the message creation time (unused, but see above).
+**
+** Returns:
+** true -- if this message should be queued up for the
+** time being.
+** false -- if the load is low enough to send this message.
+**
+** Side Effects:
+** none.
+*/
+
+/* ARGSUSED1 */
+bool
+shouldqueue(pri, ct)
+ long pri;
+ time_t ct;
+{
+ bool rval;
+
+ if (tTd(3, 30))
+ sm_dprintf("shouldqueue: CurrentLA=%d, pri=%ld: ",
+ CurrentLA, pri);
+ if (CurrentLA < QueueLA)
+ {
+ if (tTd(3, 30))
+ sm_dprintf("false (CurrentLA < QueueLA)\n");
+ return false;
+ }
+# if 0 /* this code is reported to cause oscillation around RefuseLA */
+ if (CurrentLA >= RefuseLA && QueueLA < RefuseLA)
+ {
+ if (tTd(3, 30))
+ sm_dprintf("TRUE (CurrentLA >= RefuseLA)\n");
+ return true;
+ }
+# endif /* 0 */
+ rval = pri > (QueueFactor / (CurrentLA - QueueLA + 1));
+ if (tTd(3, 30))
+ sm_dprintf("%s (by calculation)\n", rval ? "true" : "false");
+ return rval;
+}
+/*
+** REFUSECONNECTIONS -- decide if connections should be refused
+**
+** Parameters:
+** name -- daemon name (for error messages only)
+** e -- the current envelope.
+** d -- number of daemon
+** active -- was this daemon actually active?
+**
+** Returns:
+** true if incoming SMTP connections should be refused
+** (for now).
+** false if we should accept new work.
+**
+** Side Effects:
+** Sets process title when it is rejecting connections.
+*/
+
+bool
+refuseconnections(name, e, d, active)
+ char *name;
+ ENVELOPE *e;
+ int d;
+ bool active;
+{
+ static time_t lastconn[MAXDAEMONS];
+ static int conncnt[MAXDAEMONS];
+
+#if XLA
+ if (!xla_smtp_ok())
+ return true;
+#endif /* XLA */
+
+ if (ConnRateThrottle > 0)
+ {
+ time_t now;
+
+ now = curtime();
+ if (active)
+ {
+ if (now != lastconn[d])
+ {
+ lastconn[d] = now;
+ conncnt[d] = 1;
+ }
+ else if (conncnt[d]++ > ConnRateThrottle)
+ {
+#define D_MSG_CRT "deferring connections on daemon %s: %d per second"
+ /* sleep to flatten out connection load */
+ sm_setproctitle(true, e, D_MSG_CRT,
+ name, ConnRateThrottle);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, D_MSG_CRT,
+ name, ConnRateThrottle);
+ (void) sleep(1);
+ }
+ }
+ else if (now != lastconn[d])
+ conncnt[d] = 0;
+ }
+
+ sm_getla();
+ if (RefuseLA > 0 && CurrentLA >= RefuseLA)
+ {
+# define R_MSG_LA "rejecting connections on daemon %s: load average: %d"
+ sm_setproctitle(true, e, R_MSG_LA, name, CurrentLA);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, R_MSG_LA, name, CurrentLA);
+ return true;
+ }
+
+ if (DelayLA > 0 && CurrentLA >= DelayLA)
+ {
+ time_t now;
+ static time_t log_delay = (time_t) 0;
+
+# define MIN_DELAY_LOG 90 /* wait before logging this again */
+# define D_MSG_LA "delaying connections on daemon %s: load average=%d >= %d"
+ /* sleep to flatten out connection load */
+ sm_setproctitle(true, e, D_MSG_LA, name, DelayLA);
+ if (LogLevel > 8 && (now = curtime()) > log_delay)
+ {
+ sm_syslog(LOG_INFO, NOQID, D_MSG_LA,
+ name, CurrentLA, DelayLA);
+ log_delay = now + MIN_DELAY_LOG;
+ }
+ (void) sleep(1);
+ }
+
+ if (MaxChildren > 0 && CurChildren >= MaxChildren)
+ {
+ proc_list_probe();
+ if (CurChildren >= MaxChildren)
+ {
+#define R_MSG_CHILD "rejecting connections on daemon %s: %d children, max %d"
+ sm_setproctitle(true, e, R_MSG_CHILD,
+ name, CurChildren, MaxChildren);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, R_MSG_CHILD,
+ name, CurChildren, MaxChildren);
+ return true;
+ }
+ }
+ return false;
+}
+/*
+** SETPROCTITLE -- set process title for ps
+**
+** Parameters:
+** fmt -- a printf style format string.
+** a, b, c -- possible parameters to fmt.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Clobbers argv of our main procedure so ps(1) will
+** display the title.
+*/
+
+#define SPT_NONE 0 /* don't use it at all */
+#define SPT_REUSEARGV 1 /* cover argv with title information */
+#define SPT_BUILTIN 2 /* use libc builtin */
+#define SPT_PSTAT 3 /* use pstat(PSTAT_SETCMD, ...) */
+#define SPT_PSSTRINGS 4 /* use PS_STRINGS->... */
+#define SPT_SYSMIPS 5 /* use sysmips() supported by NEWS-OS 6 */
+#define SPT_SCO 6 /* write kernel u. area */
+#define SPT_CHANGEARGV 7 /* write our own strings into argv[] */
+
+#ifndef SPT_TYPE
+# define SPT_TYPE SPT_REUSEARGV
+#endif /* ! SPT_TYPE */
+
+
+#if SPT_TYPE != SPT_NONE && SPT_TYPE != SPT_BUILTIN
+
+# if SPT_TYPE == SPT_PSTAT
+# include <sys/pstat.h>
+# endif /* SPT_TYPE == SPT_PSTAT */
+# if SPT_TYPE == SPT_PSSTRINGS
+# include <machine/vmparam.h>
+# include <sys/exec.h>
+# ifndef PS_STRINGS /* hmmmm.... apparently not available after all */
+# undef SPT_TYPE
+# define SPT_TYPE SPT_REUSEARGV
+# else /* ! PS_STRINGS */
+# ifndef NKPDE /* FreeBSD 2.0 */
+# define NKPDE 63
+typedef unsigned int *pt_entry_t;
+# endif /* ! NKPDE */
+# endif /* ! PS_STRINGS */
+# endif /* SPT_TYPE == SPT_PSSTRINGS */
+
+# if SPT_TYPE == SPT_PSSTRINGS || SPT_TYPE == SPT_CHANGEARGV
+# define SETPROC_STATIC static
+# else /* SPT_TYPE == SPT_PSSTRINGS || SPT_TYPE == SPT_CHANGEARGV */
+# define SETPROC_STATIC
+# endif /* SPT_TYPE == SPT_PSSTRINGS || SPT_TYPE == SPT_CHANGEARGV */
+
+# if SPT_TYPE == SPT_SYSMIPS
+# include <sys/sysmips.h>
+# include <sys/sysnews.h>
+# endif /* SPT_TYPE == SPT_SYSMIPS */
+
+# if SPT_TYPE == SPT_SCO
+# include <sys/immu.h>
+# include <sys/dir.h>
+# include <sys/user.h>
+# include <sys/fs/s5param.h>
+# if PSARGSZ > MAXLINE
+# define SPT_BUFSIZE PSARGSZ
+# endif /* PSARGSZ > MAXLINE */
+# endif /* SPT_TYPE == SPT_SCO */
+
+# ifndef SPT_PADCHAR
+# define SPT_PADCHAR ' '
+# endif /* ! SPT_PADCHAR */
+
+#endif /* SPT_TYPE != SPT_NONE && SPT_TYPE != SPT_BUILTIN */
+
+#ifndef SPT_BUFSIZE
+# define SPT_BUFSIZE MAXLINE
+#endif /* ! SPT_BUFSIZE */
+
+#if _FFR_SPT_ALIGN
+
+/*
+** It looks like the Compaq Tru64 5.1A now aligns argv and envp to
+** 64 bit alignment, so unless each piece of argv and envp is a multiple
+** of 8 bytes (including terminating NULL), initsetproctitle() won't use
+** any of the space beyond argv[0]. Be sure to set SPT_ALIGN_SIZE if
+** you use this FFR.
+*/
+
+# ifdef SPT_ALIGN_SIZE
+# define SPT_ALIGN(x, align) (((((x) + SPT_ALIGN_SIZE) >> (align)) << (align)) - 1)
+# else /* SPT_ALIGN_SIZE */
+# define SPT_ALIGN(x, align) (x)
+# endif /* SPT_ALIGN_SIZE */
+#else /* _FFR_SPT_ALIGN */
+# define SPT_ALIGN(x, align) (x)
+#endif /* _FFR_SPT_ALIGN */
+
+/*
+** Pointers for setproctitle.
+** This allows "ps" listings to give more useful information.
+*/
+
+static char **Argv = NULL; /* pointer to argument vector */
+static char *LastArgv = NULL; /* end of argv */
+#if SPT_TYPE != SPT_BUILTIN
+static void setproctitle __P((const char *, ...));
+#endif /* SPT_TYPE != SPT_BUILTIN */
+
+void
+initsetproctitle(argc, argv, envp)
+ int argc;
+ char **argv;
+ char **envp;
+{
+ register int i;
+ int align;
+ extern char **environ;
+
+ /*
+ ** Move the environment so setproctitle can use the space at
+ ** the top of memory.
+ */
+
+ if (envp != NULL)
+ {
+ for (i = 0; envp[i] != NULL; i++)
+ continue;
+ environ = (char **) xalloc(sizeof (char *) * (i + 1));
+ for (i = 0; envp[i] != NULL; i++)
+ environ[i] = newstr(envp[i]);
+ environ[i] = NULL;
+ }
+
+ /*
+ ** Save start and extent of argv for setproctitle.
+ */
+
+ Argv = argv;
+
+ /*
+ ** Determine how much space we can use for setproctitle.
+ ** Use all contiguous argv and envp pointers starting at argv[0]
+ */
+
+ align = -1;
+#if _FFR_SPT_ALIGN
+# ifdef SPT_ALIGN_SIZE
+ for (i = SPT_ALIGN_SIZE; i > 0; i >>= 1)
+ align++;
+# endif /* SPT_ALIGN_SIZE */
+#endif /* _FFR_SPT_ALIGN */
+
+ for (i = 0; i < argc; i++)
+ {
+ if (i == 0 || LastArgv + 1 == argv[i])
+ LastArgv = argv[i] + SPT_ALIGN(strlen(argv[i]), align);
+ }
+ for (i = 0; LastArgv != NULL && envp != NULL && envp[i] != NULL; i++)
+ {
+ if (LastArgv + 1 == envp[i])
+ LastArgv = envp[i] + SPT_ALIGN(strlen(envp[i]), align);
+ }
+}
+
+#if SPT_TYPE != SPT_BUILTIN
+
+/*VARARGS1*/
+static void
+# ifdef __STDC__
+setproctitle(const char *fmt, ...)
+# else /* __STDC__ */
+setproctitle(fmt, va_alist)
+ const char *fmt;
+ va_dcl
+# endif /* __STDC__ */
+{
+# if SPT_TYPE != SPT_NONE
+ register int i;
+ register char *p;
+ SETPROC_STATIC char buf[SPT_BUFSIZE];
+ SM_VA_LOCAL_DECL
+# if SPT_TYPE == SPT_PSTAT
+ union pstun pst;
+# endif /* SPT_TYPE == SPT_PSTAT */
+# if SPT_TYPE == SPT_SCO
+ int j;
+ off_t seek_off;
+ static int kmem = -1;
+ static pid_t kmempid = -1;
+ struct user u;
+# endif /* SPT_TYPE == SPT_SCO */
+
+ p = buf;
+
+ /* print sendmail: heading for grep */
+ (void) sm_strlcpy(p, "sendmail: ", SPACELEFT(buf, p));
+ p += strlen(p);
+
+ /* print the argument string */
+ SM_VA_START(ap, fmt);
+ (void) sm_vsnprintf(p, SPACELEFT(buf, p), fmt, ap);
+ SM_VA_END(ap);
+
+ i = (int) strlen(buf);
+ if (i < 0)
+ return;
+
+# if SPT_TYPE == SPT_PSTAT
+ pst.pst_command = buf;
+ pstat(PSTAT_SETCMD, pst, i, 0, 0);
+# endif /* SPT_TYPE == SPT_PSTAT */
+# if SPT_TYPE == SPT_PSSTRINGS
+ PS_STRINGS->ps_nargvstr = 1;
+ PS_STRINGS->ps_argvstr = buf;
+# endif /* SPT_TYPE == SPT_PSSTRINGS */
+# if SPT_TYPE == SPT_SYSMIPS
+ sysmips(SONY_SYSNEWS, NEWS_SETPSARGS, buf);
+# endif /* SPT_TYPE == SPT_SYSMIPS */
+# if SPT_TYPE == SPT_SCO
+ if (kmem < 0 || kmempid != CurrentPid)
+ {
+ if (kmem >= 0)
+ (void) close(kmem);
+ kmem = open(_PATH_KMEM, O_RDWR, 0);
+ if (kmem < 0)
+ return;
+ if ((j = fcntl(kmem, F_GETFD, 0)) < 0 ||
+ fcntl(kmem, F_SETFD, j | FD_CLOEXEC) < 0)
+ {
+ (void) close(kmem);
+ kmem = -1;
+ return;
+ }
+ kmempid = CurrentPid;
+ }
+ buf[PSARGSZ - 1] = '\0';
+ seek_off = UVUBLK + (off_t) u.u_psargs - (off_t) &u;
+ if (lseek(kmem, (off_t) seek_off, SEEK_SET) == seek_off)
+ (void) write(kmem, buf, PSARGSZ);
+# endif /* SPT_TYPE == SPT_SCO */
+# if SPT_TYPE == SPT_REUSEARGV
+ if (LastArgv == NULL)
+ return;
+
+ if (i > LastArgv - Argv[0] - 2)
+ {
+ i = LastArgv - Argv[0] - 2;
+ buf[i] = '\0';
+ }
+ (void) sm_strlcpy(Argv[0], buf, i + 1);
+ p = &Argv[0][i];
+ while (p < LastArgv)
+ *p++ = SPT_PADCHAR;
+ Argv[1] = NULL;
+# endif /* SPT_TYPE == SPT_REUSEARGV */
+# if SPT_TYPE == SPT_CHANGEARGV
+ Argv[0] = buf;
+ Argv[1] = 0;
+# endif /* SPT_TYPE == SPT_CHANGEARGV */
+# endif /* SPT_TYPE != SPT_NONE */
+}
+
+#endif /* SPT_TYPE != SPT_BUILTIN */
+/*
+** SM_SETPROCTITLE -- set process task and set process title for ps
+**
+** Possibly set process status and call setproctitle() to
+** change the ps display.
+**
+** Parameters:
+** status -- whether or not to store as process status
+** e -- the current envelope.
+** fmt -- a printf style format string.
+** a, b, c -- possible parameters to fmt.
+**
+** Returns:
+** none.
+*/
+
+/*VARARGS2*/
+void
+#ifdef __STDC__
+sm_setproctitle(bool status, ENVELOPE *e, const char *fmt, ...)
+#else /* __STDC__ */
+sm_setproctitle(status, e, fmt, va_alist)
+ bool status;
+ ENVELOPE *e;
+ const char *fmt;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char buf[SPT_BUFSIZE];
+ SM_VA_LOCAL_DECL
+
+ /* print the argument string */
+ SM_VA_START(ap, fmt);
+ (void) sm_vsnprintf(buf, sizeof buf, fmt, ap);
+ SM_VA_END(ap);
+
+ if (status)
+ proc_list_set(CurrentPid, buf);
+
+ if (ProcTitlePrefix != NULL)
+ {
+ char prefix[SPT_BUFSIZE];
+
+ expand(ProcTitlePrefix, prefix, sizeof prefix, e);
+ setproctitle("%s: %s", prefix, buf);
+ }
+ else
+ setproctitle("%s", buf);
+}
+/*
+** WAITFOR -- wait for a particular process id.
+**
+** Parameters:
+** pid -- process id to wait for.
+**
+** Returns:
+** status of pid.
+** -1 if pid never shows up.
+**
+** Side Effects:
+** none.
+*/
+
+int
+waitfor(pid)
+ pid_t pid;
+{
+ int st;
+ pid_t i;
+
+ do
+ {
+ errno = 0;
+ i = sm_wait(&st);
+ if (i > 0)
+ proc_list_drop(i, st, NULL);
+ } while ((i >= 0 || errno == EINTR) && i != pid);
+ if (i < 0)
+ return -1;
+ return st;
+}
+/*
+** SM_WAIT -- wait
+**
+** Parameters:
+** status -- pointer to status (return value)
+**
+** Returns:
+** pid
+*/
+
+pid_t
+sm_wait(status)
+ int *status;
+{
+# ifdef WAITUNION
+ union wait st;
+# else /* WAITUNION */
+ auto int st;
+# endif /* WAITUNION */
+ pid_t i;
+# if defined(ISC_UNIX) || defined(_SCO_unix_)
+ int savesig;
+# endif /* defined(ISC_UNIX) || defined(_SCO_unix_) */
+
+# if defined(ISC_UNIX) || defined(_SCO_unix_)
+ savesig = sm_releasesignal(SIGCHLD);
+# endif /* defined(ISC_UNIX) || defined(_SCO_unix_) */
+ i = wait(&st);
+# if defined(ISC_UNIX) || defined(_SCO_unix_)
+ if (savesig > 0)
+ sm_blocksignal(SIGCHLD);
+# endif /* defined(ISC_UNIX) || defined(_SCO_unix_) */
+# ifdef WAITUNION
+ *status = st.w_status;
+# else /* WAITUNION */
+ *status = st;
+# endif /* WAITUNION */
+ return i;
+}
+/*
+** REAPCHILD -- pick up the body of my child, lest it become a zombie
+**
+** Parameters:
+** sig -- the signal that got us here (unused).
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Picks up extant zombies.
+** Control socket exits may restart/shutdown daemon.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED0 */
+SIGFUNC_DECL
+reapchild(sig)
+ int sig;
+{
+ int save_errno = errno;
+ int st;
+ pid_t pid;
+# if HASWAITPID
+ auto int status;
+ int count;
+
+ count = 0;
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
+ {
+ st = status;
+ if (count++ > 1000)
+ break;
+# else /* HASWAITPID */
+# ifdef WNOHANG
+ union wait status;
+
+ while ((pid = wait3(&status, WNOHANG, (struct rusage *) NULL)) > 0)
+ {
+ st = status.w_status;
+# else /* WNOHANG */
+ auto int status;
+
+ /*
+ ** Catch one zombie -- we will be re-invoked (we hope) if there
+ ** are more. Unreliable signals probably break this, but this
+ ** is the "old system" situation -- waitpid or wait3 are to be
+ ** strongly preferred.
+ */
+
+ if ((pid = wait(&status)) > 0)
+ {
+ st = status;
+# endif /* WNOHANG */
+# endif /* HASWAITPID */
+ /* Drop PID and check if it was a control socket child */
+ proc_list_drop(pid, st, NULL);
+ }
+ FIX_SYSV_SIGNAL(sig, reapchild);
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** GETDTABLESIZE -- return number of file descriptors
+**
+** Only on non-BSD systems
+**
+** Parameters:
+** none
+**
+** Returns:
+** size of file descriptor table
+**
+** Side Effects:
+** none
+*/
+
+#ifdef SOLARIS
+# include <sys/resource.h>
+#endif /* SOLARIS */
+
+int
+getdtsize()
+{
+# ifdef RLIMIT_NOFILE
+ struct rlimit rl;
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) >= 0)
+ return rl.rlim_cur;
+# endif /* RLIMIT_NOFILE */
+
+# if HASGETDTABLESIZE
+ return getdtablesize();
+# else /* HASGETDTABLESIZE */
+# ifdef _SC_OPEN_MAX
+ return sysconf(_SC_OPEN_MAX);
+# else /* _SC_OPEN_MAX */
+ return NOFILE;
+# endif /* _SC_OPEN_MAX */
+# endif /* HASGETDTABLESIZE */
+}
+/*
+** UNAME -- get the UUCP name of this system.
+*/
+
+#if !HASUNAME
+
+int
+uname(name)
+ struct utsname *name;
+{
+ SM_FILE_T *file;
+ char *n;
+
+ name->nodename[0] = '\0';
+
+ /* try /etc/whoami -- one line with the node name */
+ if ((file = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, "/etc/whoami",
+ SM_IO_RDONLY, NULL)) != NULL)
+ {
+ (void) sm_io_fgets(file, SM_TIME_DEFAULT, name->nodename,
+ NODE_LENGTH + 1);
+ (void) sm_io_close(file, SM_TIME_DEFAULT);
+ n = strchr(name->nodename, '\n');
+ if (n != NULL)
+ *n = '\0';
+ if (name->nodename[0] != '\0')
+ return 0;
+ }
+
+ /* try /usr/include/whoami.h -- has a #define somewhere */
+ if ((file = sm_io_open(SmFtStdio, SM_TIME_DEFAULT,
+ "/usr/include/whoami.h", SM_IO_RDONLY, NULL))
+ != NULL)
+ {
+ char buf[MAXLINE];
+
+ while (sm_io_fgets(file, SM_TIME_DEFAULT,
+ buf, sizeof buf) != NULL)
+ {
+ if (sm_io_sscanf(buf, "#define sysname \"%*[^\"]\"",
+ NODE_LENGTH, name->nodename) > 0)
+ break;
+ }
+ (void) sm_io_close(file, SM_TIME_DEFAULT);
+ if (name->nodename[0] != '\0')
+ return 0;
+ }
+
+# if 0
+ /*
+ ** Popen is known to have security holes.
+ */
+
+ /* try uuname -l to return local name */
+ if ((file = popen("uuname -l", "r")) != NULL)
+ {
+ (void) sm_io_fgets(file, SM_TIME_DEFAULT, name,
+ NODE_LENGTH + 1);
+ (void) pclose(file);
+ n = strchr(name, '\n');
+ if (n != NULL)
+ *n = '\0';
+ if (name->nodename[0] != '\0')
+ return 0;
+ }
+# endif /* 0 */
+
+ return -1;
+}
+#endif /* !HASUNAME */
+/*
+** INITGROUPS -- initialize groups
+**
+** Stub implementation for System V style systems
+*/
+
+#if !HASINITGROUPS
+
+initgroups(name, basegid)
+ char *name;
+ int basegid;
+{
+ return 0;
+}
+
+#endif /* !HASINITGROUPS */
+/*
+** SETGROUPS -- set group list
+**
+** Stub implementation for systems that don't have group lists
+*/
+
+#ifndef NGROUPS_MAX
+
+int
+setgroups(ngroups, grouplist)
+ int ngroups;
+ GIDSET_T grouplist[];
+{
+ return 0;
+}
+
+#endif /* ! NGROUPS_MAX */
+/*
+** SETSID -- set session id (for non-POSIX systems)
+*/
+
+#if !HASSETSID
+
+pid_t
+setsid __P ((void))
+{
+# ifdef TIOCNOTTY
+ int fd;
+
+ fd = open("/dev/tty", O_RDWR, 0);
+ if (fd >= 0)
+ {
+ (void) ioctl(fd, TIOCNOTTY, (char *) 0);
+ (void) close(fd);
+ }
+# endif /* TIOCNOTTY */
+# ifdef SYS5SETPGRP
+ return setpgrp();
+# else /* SYS5SETPGRP */
+ return setpgid(0, CurrentPid);
+# endif /* SYS5SETPGRP */
+}
+
+#endif /* !HASSETSID */
+/*
+** FSYNC -- dummy fsync
+*/
+
+#if NEEDFSYNC
+
+fsync(fd)
+ int fd;
+{
+# ifdef O_SYNC
+ return fcntl(fd, F_SETFL, O_SYNC);
+# else /* O_SYNC */
+ /* nothing we can do */
+ return 0;
+# endif /* O_SYNC */
+}
+
+#endif /* NEEDFSYNC */
+/*
+** DGUX_INET_ADDR -- inet_addr for DG/UX
+**
+** Data General DG/UX version of inet_addr returns a struct in_addr
+** instead of a long. This patches things. Only needed on versions
+** prior to 5.4.3.
+*/
+
+#ifdef DGUX_5_4_2
+
+# undef inet_addr
+
+long
+dgux_inet_addr(host)
+ char *host;
+{
+ struct in_addr haddr;
+
+ haddr = inet_addr(host);
+ return haddr.s_addr;
+}
+
+#endif /* DGUX_5_4_2 */
+/*
+** GETOPT -- for old systems or systems with bogus implementations
+*/
+
+#if !SM_CONF_GETOPT
+
+/*
+ * Copyright (c) 1985 Regents of the University of California.
+ * All rights reserved. The Berkeley software License Agreement
+ * specifies the terms and conditions for redistribution.
+ */
+
+
+/*
+** this version hacked to add `atend' flag to allow state machine
+** to reset if invoked by the program to scan args for a 2nd time
+*/
+
+# if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)getopt.c 4.3 (Berkeley) 3/9/86";
+# endif /* defined(LIBC_SCCS) && !defined(lint) */
+
+/*
+** get option letter from argument vector
+*/
+# ifdef _CONVEX_SOURCE
+extern int optind, opterr, optopt;
+extern char *optarg;
+# else /* _CONVEX_SOURCE */
+int opterr = 1; /* if error message should be printed */
+int optind = 1; /* index into parent argv vector */
+int optopt = 0; /* character checked for validity */
+char *optarg = NULL; /* argument associated with option */
+# endif /* _CONVEX_SOURCE */
+
+# define BADCH (int)'?'
+# define EMSG ""
+# define tell(s) if (opterr) \
+ {sm_io_fputs(smioerr, SM_TIME_DEFAULT, *nargv); \
+ (void) sm_io_fputs(smioerr, SM_TIME_DEFAULT, s); \
+ (void) sm_io_putc(smioerr, SM_TIME_DEFAULT, optopt); \
+ (void) sm_io_putc(smioerr, SM_TIME_DEFAULT, '\n'); \
+ return BADCH;}
+
+int
+getopt(nargc,nargv,ostr)
+ int nargc;
+ char *const *nargv;
+ const char *ostr;
+{
+ static char *place = EMSG; /* option letter processing */
+ static char atend = 0;
+ register char *oli = NULL; /* option letter list index */
+
+ if (atend) {
+ atend = 0;
+ place = EMSG;
+ }
+ if(!*place) { /* update scanning pointer */
+ if (optind >= nargc || *(place = nargv[optind]) != '-' || !*++place) {
+ atend++;
+ return -1;
+ }
+ if (*place == '-') { /* found "--" */
+ ++optind;
+ atend++;
+ return -1;
+ }
+ } /* option letter okay? */
+ if ((optopt = (int)*place++) == (int)':' || !(oli = strchr(ostr,optopt))) {
+ if (!*place) ++optind;
+ tell(": illegal option -- ");
+ }
+ if (oli && *++oli != ':') { /* don't need argument */
+ optarg = NULL;
+ if (!*place) ++optind;
+ }
+ else { /* need an argument */
+ if (*place) optarg = place; /* no white space */
+ else if (nargc <= ++optind) { /* no arg */
+ place = EMSG;
+ tell(": option requires an argument -- ");
+ }
+ else optarg = nargv[optind]; /* white space */
+ place = EMSG;
+ ++optind;
+ }
+ return optopt; /* dump back option letter */
+}
+
+#endif /* !SM_CONF_GETOPT */
+/*
+** USERSHELLOK -- tell if a user's shell is ok for unrestricted use
+**
+** Parameters:
+** user -- the name of the user we are checking.
+** shell -- the user's shell from /etc/passwd
+**
+** Returns:
+** true -- if it is ok to use this for unrestricted access.
+** false -- if the shell is restricted.
+*/
+
+#if !HASGETUSERSHELL
+
+# ifndef _PATH_SHELLS
+# define _PATH_SHELLS "/etc/shells"
+# endif /* ! _PATH_SHELLS */
+
+# if defined(_AIX3) || defined(_AIX4)
+# include <userconf.h>
+# if _AIX4 >= 40200
+# include <userpw.h>
+# endif /* _AIX4 >= 40200 */
+# include <usersec.h>
+# endif /* defined(_AIX3) || defined(_AIX4) */
+
+static char *DefaultUserShells[] =
+{
+ "/bin/sh", /* standard shell */
+# ifdef MPE
+ "/SYS/PUB/CI",
+# else /* MPE */
+ "/usr/bin/sh",
+ "/bin/csh", /* C shell */
+ "/usr/bin/csh",
+# endif /* MPE */
+# ifdef __hpux
+# ifdef V4FS
+ "/usr/bin/rsh", /* restricted Bourne shell */
+ "/usr/bin/ksh", /* Korn shell */
+ "/usr/bin/rksh", /* restricted Korn shell */
+ "/usr/bin/pam",
+ "/usr/bin/keysh", /* key shell (extended Korn shell) */
+ "/usr/bin/posix/sh",
+# else /* V4FS */
+ "/bin/rsh", /* restricted Bourne shell */
+ "/bin/ksh", /* Korn shell */
+ "/bin/rksh", /* restricted Korn shell */
+ "/bin/pam",
+ "/usr/bin/keysh", /* key shell (extended Korn shell) */
+ "/bin/posix/sh",
+ "/sbin/sh"
+# endif /* V4FS */
+# endif /* __hpux */
+# if defined(_AIX3) || defined(_AIX4)
+ "/bin/ksh", /* Korn shell */
+ "/usr/bin/ksh",
+ "/bin/tsh", /* trusted shell */
+ "/usr/bin/tsh",
+ "/bin/bsh", /* Bourne shell */
+ "/usr/bin/bsh",
+# endif /* defined(_AIX3) || defined(_AIX4) */
+# if defined(__svr4__) || defined(__svr5__)
+ "/bin/ksh", /* Korn shell */
+ "/usr/bin/ksh",
+# endif /* defined(__svr4__) || defined(__svr5__) */
+# ifdef sgi
+ "/sbin/sh", /* SGI's shells really live in /sbin */
+ "/usr/bin/sh",
+ "/sbin/bsh", /* classic borne shell */
+ "/bin/bsh",
+ "/usr/bin/bsh",
+ "/sbin/csh", /* standard csh */
+ "/bin/csh",
+ "/usr/bin/csh",
+ "/sbin/jsh", /* classic borne shell w/ job control*/
+ "/bin/jsh",
+ "/usr/bin/jsh",
+ "/bin/ksh", /* Korn shell */
+ "/sbin/ksh",
+ "/usr/bin/ksh",
+ "/sbin/tcsh", /* Extended csh */
+ "/bin/tcsh",
+ "/usr/bin/tcsh",
+# endif /* sgi */
+ NULL
+};
+
+#endif /* !HASGETUSERSHELL */
+
+#define WILDCARD_SHELL "/SENDMAIL/ANY/SHELL/"
+
+bool
+usershellok(user, shell)
+ char *user;
+ char *shell;
+{
+# if HASGETUSERSHELL
+ register char *p;
+ extern char *getusershell();
+
+ if (shell == NULL || shell[0] == '\0' || wordinclass(user, 't') ||
+ ConfigLevel <= 1)
+ return true;
+
+ setusershell();
+ while ((p = getusershell()) != NULL)
+ if (strcmp(p, shell) == 0 || strcmp(p, WILDCARD_SHELL) == 0)
+ break;
+ endusershell();
+ return p != NULL;
+# else /* HASGETUSERSHELL */
+# if USEGETCONFATTR
+ auto char *v;
+# endif /* USEGETCONFATTR */
+ register SM_FILE_T *shellf;
+ char buf[MAXLINE];
+
+ if (shell == NULL || shell[0] == '\0' || wordinclass(user, 't') ||
+ ConfigLevel <= 1)
+ return true;
+
+# if USEGETCONFATTR
+ /*
+ ** Naturally IBM has a "better" idea.....
+ **
+ ** What a crock. This interface isn't documented, it is
+ ** considered part of the security library (-ls), and it
+ ** only works if you are running as root (since the list
+ ** of valid shells is obviously a source of great concern).
+ ** I recommend that you do NOT define USEGETCONFATTR,
+ ** especially since you are going to have to set up an
+ ** /etc/shells anyhow to handle the cases where getconfattr
+ ** fails.
+ */
+
+ if (getconfattr(SC_SYS_LOGIN, SC_SHELLS, &v, SEC_LIST) == 0 && v != NULL)
+ {
+ while (*v != '\0')
+ {
+ if (strcmp(v, shell) == 0 || strcmp(v, WILDCARD_SHELL) == 0)
+ return true;
+ v += strlen(v) + 1;
+ }
+ return false;
+ }
+# endif /* USEGETCONFATTR */
+
+ shellf = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, _PATH_SHELLS,
+ SM_IO_RDONLY, NULL);
+ if (shellf == NULL)
+ {
+ /* no /etc/shells; see if it is one of the std shells */
+ char **d;
+
+ if (errno != ENOENT && LogLevel > 3)
+ sm_syslog(LOG_ERR, NOQID,
+ "usershellok: cannot open %s: %s",
+ _PATH_SHELLS, sm_errstring(errno));
+
+ for (d = DefaultUserShells; *d != NULL; d++)
+ {
+ if (strcmp(shell, *d) == 0)
+ return true;
+ }
+ return false;
+ }
+
+ while (sm_io_fgets(shellf, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ register char *p, *q;
+
+ p = buf;
+ while (*p != '\0' && *p != '#' && *p != '/')
+ p++;
+ if (*p == '#' || *p == '\0')
+ continue;
+ q = p;
+ while (*p != '\0' && *p != '#' && !(isascii(*p) && isspace(*p)))
+ p++;
+ *p = '\0';
+ if (strcmp(shell, q) == 0 || strcmp(WILDCARD_SHELL, q) == 0)
+ {
+ (void) sm_io_close(shellf, SM_TIME_DEFAULT);
+ return true;
+ }
+ }
+ (void) sm_io_close(shellf, SM_TIME_DEFAULT);
+ return false;
+# endif /* HASGETUSERSHELL */
+}
+/*
+** FREEDISKSPACE -- see how much free space is on the queue filesystem
+**
+** Only implemented if you have statfs.
+**
+** Parameters:
+** dir -- the directory in question.
+** bsize -- a variable into which the filesystem
+** block size is stored.
+**
+** Returns:
+** The number of blocks free on the queue filesystem.
+** -1 if the statfs call fails.
+**
+** Side effects:
+** Puts the filesystem block size into bsize.
+*/
+
+/* statfs types */
+# define SFS_NONE 0 /* no statfs implementation */
+# define SFS_USTAT 1 /* use ustat */
+# define SFS_4ARGS 2 /* use four-argument statfs call */
+# define SFS_VFS 3 /* use <sys/vfs.h> implementation */
+# define SFS_MOUNT 4 /* use <sys/mount.h> implementation */
+# define SFS_STATFS 5 /* use <sys/statfs.h> implementation */
+# define SFS_STATVFS 6 /* use <sys/statvfs.h> implementation */
+
+# ifndef SFS_TYPE
+# define SFS_TYPE SFS_NONE
+# endif /* ! SFS_TYPE */
+
+# if SFS_TYPE == SFS_USTAT
+# include <ustat.h>
+# endif /* SFS_TYPE == SFS_USTAT */
+# if SFS_TYPE == SFS_4ARGS || SFS_TYPE == SFS_STATFS
+# include <sys/statfs.h>
+# endif /* SFS_TYPE == SFS_4ARGS || SFS_TYPE == SFS_STATFS */
+# if SFS_TYPE == SFS_VFS
+# include <sys/vfs.h>
+# endif /* SFS_TYPE == SFS_VFS */
+# if SFS_TYPE == SFS_MOUNT
+# include <sys/mount.h>
+# endif /* SFS_TYPE == SFS_MOUNT */
+# if SFS_TYPE == SFS_STATVFS
+# include <sys/statvfs.h>
+# endif /* SFS_TYPE == SFS_STATVFS */
+
+long
+freediskspace(dir, bsize)
+ char *dir;
+ long *bsize;
+{
+# if SFS_TYPE == SFS_NONE
+ if (bsize != NULL)
+ *bsize = 4096L;
+
+ /* assume free space is plentiful */
+ return (long) LONG_MAX;
+# else /* SFS_TYPE == SFS_NONE */
+# if SFS_TYPE == SFS_USTAT
+ struct ustat fs;
+ struct stat statbuf;
+# define FSBLOCKSIZE DEV_BSIZE
+# define SFS_BAVAIL f_tfree
+# else /* SFS_TYPE == SFS_USTAT */
+# if defined(ultrix)
+ struct fs_data fs;
+# define SFS_BAVAIL fd_bfreen
+# define FSBLOCKSIZE 1024L
+# else /* defined(ultrix) */
+# if SFS_TYPE == SFS_STATVFS
+ struct statvfs fs;
+# define FSBLOCKSIZE fs.f_frsize
+# else /* SFS_TYPE == SFS_STATVFS */
+ struct statfs fs;
+# define FSBLOCKSIZE fs.f_bsize
+# endif /* SFS_TYPE == SFS_STATVFS */
+# endif /* defined(ultrix) */
+# endif /* SFS_TYPE == SFS_USTAT */
+# ifndef SFS_BAVAIL
+# define SFS_BAVAIL f_bavail
+# endif /* ! SFS_BAVAIL */
+
+# if SFS_TYPE == SFS_USTAT
+ if (stat(dir, &statbuf) == 0 && ustat(statbuf.st_dev, &fs) == 0)
+# else /* SFS_TYPE == SFS_USTAT */
+# if SFS_TYPE == SFS_4ARGS
+ if (statfs(dir, &fs, sizeof fs, 0) == 0)
+# else /* SFS_TYPE == SFS_4ARGS */
+# if SFS_TYPE == SFS_STATVFS
+ if (statvfs(dir, &fs) == 0)
+# else /* SFS_TYPE == SFS_STATVFS */
+# if defined(ultrix)
+ if (statfs(dir, &fs) > 0)
+# else /* defined(ultrix) */
+ if (statfs(dir, &fs) == 0)
+# endif /* defined(ultrix) */
+# endif /* SFS_TYPE == SFS_STATVFS */
+# endif /* SFS_TYPE == SFS_4ARGS */
+# endif /* SFS_TYPE == SFS_USTAT */
+ {
+ if (bsize != NULL)
+ *bsize = FSBLOCKSIZE;
+ if (fs.SFS_BAVAIL <= 0)
+ return 0;
+ else if (fs.SFS_BAVAIL > LONG_MAX)
+ return (long) LONG_MAX;
+ else
+ return (long) fs.SFS_BAVAIL;
+ }
+ return -1;
+# endif /* SFS_TYPE == SFS_NONE */
+}
+/*
+** ENOUGHDISKSPACE -- is there enough free space on the queue file systems?
+**
+** Parameters:
+** msize -- the size to check against. If zero, we don't yet
+** know how big the message will be, so just check for
+** a "reasonable" amount.
+** e -- envelope, or NULL -- controls logging
+**
+** Returns:
+** true if in every queue group there is at least one
+** queue directory whose file system contains enough free space.
+** false otherwise.
+**
+** Side Effects:
+** If there is not enough disk space and e != NULL
+** then sm_syslog is called.
+*/
+
+bool
+enoughdiskspace(msize, e)
+ long msize;
+ ENVELOPE *e;
+{
+ int i;
+
+ if (MinBlocksFree <= 0 && msize <= 0)
+ {
+ if (tTd(4, 80))
+ sm_dprintf("enoughdiskspace: no threshold\n");
+ return true;
+ }
+
+ filesys_update();
+ for (i = 0; i < NumQueue; ++i)
+ {
+ if (pickqdir(Queue[i], msize, e) < 0)
+ return false;
+ }
+ return true;
+}
+/*
+** TRANSIENTERROR -- tell if an error code indicates a transient failure
+**
+** This looks at an errno value and tells if this is likely to
+** go away if retried later.
+**
+** Parameters:
+** err -- the errno code to classify.
+**
+** Returns:
+** true if this is probably transient.
+** false otherwise.
+*/
+
+bool
+transienterror(err)
+ int err;
+{
+ switch (err)
+ {
+ case EIO: /* I/O error */
+ case ENXIO: /* Device not configured */
+ case EAGAIN: /* Resource temporarily unavailable */
+ case ENOMEM: /* Cannot allocate memory */
+ case ENODEV: /* Operation not supported by device */
+ case ENFILE: /* Too many open files in system */
+ case EMFILE: /* Too many open files */
+ case ENOSPC: /* No space left on device */
+ case ETIMEDOUT: /* Connection timed out */
+#ifdef ESTALE
+ case ESTALE: /* Stale NFS file handle */
+#endif /* ESTALE */
+#ifdef ENETDOWN
+ case ENETDOWN: /* Network is down */
+#endif /* ENETDOWN */
+#ifdef ENETUNREACH
+ case ENETUNREACH: /* Network is unreachable */
+#endif /* ENETUNREACH */
+#ifdef ENETRESET
+ case ENETRESET: /* Network dropped connection on reset */
+#endif /* ENETRESET */
+#ifdef ECONNABORTED
+ case ECONNABORTED: /* Software caused connection abort */
+#endif /* ECONNABORTED */
+#ifdef ECONNRESET
+ case ECONNRESET: /* Connection reset by peer */
+#endif /* ECONNRESET */
+#ifdef ENOBUFS
+ case ENOBUFS: /* No buffer space available */
+#endif /* ENOBUFS */
+#ifdef ESHUTDOWN
+ case ESHUTDOWN: /* Can't send after socket shutdown */
+#endif /* ESHUTDOWN */
+#ifdef ECONNREFUSED
+ case ECONNREFUSED: /* Connection refused */
+#endif /* ECONNREFUSED */
+#ifdef EHOSTDOWN
+ case EHOSTDOWN: /* Host is down */
+#endif /* EHOSTDOWN */
+#ifdef EHOSTUNREACH
+ case EHOSTUNREACH: /* No route to host */
+#endif /* EHOSTUNREACH */
+#ifdef EDQUOT
+ case EDQUOT: /* Disc quota exceeded */
+#endif /* EDQUOT */
+#ifdef EPROCLIM
+ case EPROCLIM: /* Too many processes */
+#endif /* EPROCLIM */
+#ifdef EUSERS
+ case EUSERS: /* Too many users */
+#endif /* EUSERS */
+#ifdef EDEADLK
+ case EDEADLK: /* Resource deadlock avoided */
+#endif /* EDEADLK */
+#ifdef EISCONN
+ case EISCONN: /* Socket already connected */
+#endif /* EISCONN */
+#ifdef EINPROGRESS
+ case EINPROGRESS: /* Operation now in progress */
+#endif /* EINPROGRESS */
+#ifdef EALREADY
+ case EALREADY: /* Operation already in progress */
+#endif /* EALREADY */
+#ifdef EADDRINUSE
+ case EADDRINUSE: /* Address already in use */
+#endif /* EADDRINUSE */
+#ifdef EADDRNOTAVAIL
+ case EADDRNOTAVAIL: /* Can't assign requested address */
+#endif /* EADDRNOTAVAIL */
+#ifdef ETXTBSY
+ case ETXTBSY: /* (Apollo) file locked */
+#endif /* ETXTBSY */
+#if defined(ENOSR) && (!defined(ENOBUFS) || (ENOBUFS != ENOSR))
+ case ENOSR: /* Out of streams resources */
+#endif /* defined(ENOSR) && (!defined(ENOBUFS) || (ENOBUFS != ENOSR)) */
+#ifdef ENOLCK
+ case ENOLCK: /* No locks available */
+#endif /* ENOLCK */
+ case E_SM_OPENTIMEOUT: /* PSEUDO: open timed out */
+ return true;
+ }
+
+ /* nope, must be permanent */
+ return false;
+}
+/*
+** LOCKFILE -- lock a file using flock or (shudder) fcntl locking
+**
+** Parameters:
+** fd -- the file descriptor of the file.
+** filename -- the file name (for error messages).
+** ext -- the filename extension.
+** type -- type of the lock. Bits can be:
+** LOCK_EX -- exclusive lock.
+** LOCK_NB -- non-blocking.
+** LOCK_UN -- unlock.
+**
+** Returns:
+** true if the lock was acquired.
+** false otherwise.
+*/
+
+bool
+lockfile(fd, filename, ext, type)
+ int fd;
+ char *filename;
+ char *ext;
+ int type;
+{
+ int i;
+ int save_errno;
+# if !HASFLOCK
+ int action;
+ struct flock lfd;
+
+ if (ext == NULL)
+ ext = "";
+
+ memset(&lfd, '\0', sizeof lfd);
+ if (bitset(LOCK_UN, type))
+ lfd.l_type = F_UNLCK;
+ else if (bitset(LOCK_EX, type))
+ lfd.l_type = F_WRLCK;
+ else
+ lfd.l_type = F_RDLCK;
+
+ if (bitset(LOCK_NB, type))
+ action = F_SETLK;
+ else
+ action = F_SETLKW;
+
+ if (tTd(55, 60))
+ sm_dprintf("lockfile(%s%s, action=%d, type=%d): ",
+ filename, ext, action, lfd.l_type);
+
+ while ((i = fcntl(fd, action, &lfd)) < 0 && errno == EINTR)
+ continue;
+ if (i >= 0)
+ {
+ if (tTd(55, 60))
+ sm_dprintf("SUCCESS\n");
+ return true;
+ }
+ save_errno = errno;
+
+ if (tTd(55, 60))
+ sm_dprintf("(%s) ", sm_errstring(save_errno));
+
+ /*
+ ** On SunOS, if you are testing using -oQ/tmp/mqueue or
+ ** -oA/tmp/aliases or anything like that, and /tmp is mounted
+ ** as type "tmp" (that is, served from swap space), the
+ ** previous fcntl will fail with "Invalid argument" errors.
+ ** Since this is fairly common during testing, we will assume
+ ** that this indicates that the lock is successfully grabbed.
+ */
+
+ if (save_errno == EINVAL)
+ {
+ if (tTd(55, 60))
+ sm_dprintf("SUCCESS\n");
+ return true;
+ }
+
+ if (!bitset(LOCK_NB, type) ||
+ (save_errno != EACCES && save_errno != EAGAIN))
+ {
+ int omode = fcntl(fd, F_GETFL, 0);
+ uid_t euid = geteuid();
+
+ errno = save_errno;
+ syserr("cannot lockf(%s%s, fd=%d, type=%o, omode=%o, euid=%d)",
+ filename, ext, fd, type, omode, euid);
+ dumpfd(fd, true, true);
+ }
+# else /* !HASFLOCK */
+ if (ext == NULL)
+ ext = "";
+
+ if (tTd(55, 60))
+ sm_dprintf("lockfile(%s%s, type=%o): ", filename, ext, type);
+
+ while ((i = flock(fd, type)) < 0 && errno == EINTR)
+ continue;
+ if (i >= 0)
+ {
+ if (tTd(55, 60))
+ sm_dprintf("SUCCESS\n");
+ return true;
+ }
+ save_errno = errno;
+
+ if (tTd(55, 60))
+ sm_dprintf("(%s) ", sm_errstring(save_errno));
+
+ if (!bitset(LOCK_NB, type) || save_errno != EWOULDBLOCK)
+ {
+ int omode = fcntl(fd, F_GETFL, 0);
+ uid_t euid = geteuid();
+
+ errno = save_errno;
+ syserr("cannot flock(%s%s, fd=%d, type=%o, omode=%o, euid=%d)",
+ filename, ext, fd, type, omode, euid);
+ dumpfd(fd, true, true);
+ }
+# endif /* !HASFLOCK */
+ if (tTd(55, 60))
+ sm_dprintf("FAIL\n");
+ errno = save_errno;
+ return false;
+}
+/*
+** CHOWNSAFE -- tell if chown is "safe" (executable only by root)
+**
+** Unfortunately, given that we can't predict other systems on which
+** a remote mounted (NFS) filesystem will be mounted, the answer is
+** almost always that this is unsafe.
+**
+** Note also that many operating systems have non-compliant
+** implementations of the _POSIX_CHOWN_RESTRICTED variable and the
+** fpathconf() routine. According to IEEE 1003.1-1990, if
+** _POSIX_CHOWN_RESTRICTED is defined and not equal to -1, then
+** no non-root process can give away the file. However, vendors
+** don't take NFS into account, so a comfortable value of
+** _POSIX_CHOWN_RESTRICTED tells us nothing.
+**
+** Also, some systems (e.g., IRIX 6.2) return 1 from fpathconf()
+** even on files where chown is not restricted. Many systems get
+** this wrong on NFS-based filesystems (that is, they say that chown
+** is restricted [safe] on NFS filesystems where it may not be, since
+** other systems can access the same filesystem and do file giveaway;
+** only the NFS server knows for sure!) Hence, it is important to
+** get the value of SAFENFSPATHCONF correct -- it should be defined
+** _only_ after testing (see test/t_pathconf.c) a system on an unsafe
+** NFS-based filesystem to ensure that you can get meaningful results.
+** If in doubt, assume unsafe!
+**
+** You may also need to tweak IS_SAFE_CHOWN -- it should be a
+** condition indicating whether the return from pathconf indicates
+** that chown is safe (typically either > 0 or >= 0 -- there isn't
+** even any agreement about whether a zero return means that a file
+** is or is not safe). It defaults to "> 0".
+**
+** If the parent directory is safe (writable only by owner back
+** to the root) then we can relax slightly and trust fpathconf
+** in more circumstances. This is really a crock -- if this is an
+** NFS mounted filesystem then we really know nothing about the
+** underlying implementation. However, most systems pessimize and
+** return an error (EINVAL or EOPNOTSUPP) on NFS filesystems, which
+** we interpret as unsafe, as we should. Thus, this heuristic gets
+** us into a possible problem only on systems that have a broken
+** pathconf implementation and which are also poorly configured
+** (have :include: files in group- or world-writable directories).
+**
+** Parameters:
+** fd -- the file descriptor to check.
+** safedir -- set if the parent directory is safe.
+**
+** Returns:
+** true -- if the chown(2) operation is "safe" -- that is,
+** only root can chown the file to an arbitrary user.
+** false -- if an arbitrary user can give away a file.
+*/
+
+#ifndef IS_SAFE_CHOWN
+# define IS_SAFE_CHOWN > 0
+#endif /* ! IS_SAFE_CHOWN */
+
+bool
+chownsafe(fd, safedir)
+ int fd;
+ bool safedir;
+{
+# if (!defined(_POSIX_CHOWN_RESTRICTED) || _POSIX_CHOWN_RESTRICTED != -1) && \
+ (defined(_PC_CHOWN_RESTRICTED) || defined(_GNU_TYPES_H))
+ int rval;
+
+ /* give the system administrator a chance to override */
+ if (bitnset(DBS_ASSUMESAFECHOWN, DontBlameSendmail))
+ return true;
+
+ /*
+ ** Some systems (e.g., SunOS) seem to have the call and the
+ ** #define _PC_CHOWN_RESTRICTED, but don't actually implement
+ ** the call. This heuristic checks for that.
+ */
+
+ errno = 0;
+ rval = fpathconf(fd, _PC_CHOWN_RESTRICTED);
+# if SAFENFSPATHCONF
+ return errno == 0 && rval IS_SAFE_CHOWN;
+# else /* SAFENFSPATHCONF */
+ return safedir && errno == 0 && rval IS_SAFE_CHOWN;
+# endif /* SAFENFSPATHCONF */
+# else /* (!defined(_POSIX_CHOWN_RESTRICTED) || _POSIX_CHOWN_RESTRICTED != -1) && ... */
+ return bitnset(DBS_ASSUMESAFECHOWN, DontBlameSendmail);
+# endif /* (!defined(_POSIX_CHOWN_RESTRICTED) || _POSIX_CHOWN_RESTRICTED != -1) && ... */
+}
+/*
+** RESETLIMITS -- reset system controlled resource limits
+**
+** This is to avoid denial-of-service attacks
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+#if HASSETRLIMIT
+# ifdef RLIMIT_NEEDS_SYS_TIME_H
+# include <sys/time.h>
+# endif /* RLIMIT_NEEDS_SYS_TIME_H */
+# include <sys/resource.h>
+#endif /* HASSETRLIMIT */
+#ifndef FD_SETSIZE
+# define FD_SETSIZE 256
+#endif /* ! FD_SETSIZE */
+
+void
+resetlimits()
+{
+#if HASSETRLIMIT
+ struct rlimit lim;
+
+ lim.rlim_cur = lim.rlim_max = RLIM_INFINITY;
+ (void) setrlimit(RLIMIT_CPU, &lim);
+ (void) setrlimit(RLIMIT_FSIZE, &lim);
+# ifdef RLIMIT_NOFILE
+ lim.rlim_cur = lim.rlim_max = FD_SETSIZE;
+ (void) setrlimit(RLIMIT_NOFILE, &lim);
+# endif /* RLIMIT_NOFILE */
+#else /* HASSETRLIMIT */
+# if HASULIMIT
+ (void) ulimit(2, 0x3fffff);
+ (void) ulimit(4, FD_SETSIZE);
+# endif /* HASULIMIT */
+#endif /* HASSETRLIMIT */
+ errno = 0;
+}
+/*
+** SETVENDOR -- process vendor code from V configuration line
+**
+** Parameters:
+** vendor -- string representation of vendor.
+**
+** Returns:
+** true -- if ok.
+** false -- if vendor code could not be processed.
+**
+** Side Effects:
+** It is reasonable to set mode flags here to tweak
+** processing in other parts of the code if necessary.
+** For example, if you are a vendor that uses $%y to
+** indicate YP lookups, you could enable that here.
+*/
+
+bool
+setvendor(vendor)
+ char *vendor;
+{
+ if (sm_strcasecmp(vendor, "Berkeley") == 0)
+ {
+ VendorCode = VENDOR_BERKELEY;
+ return true;
+ }
+
+ /* add vendor extensions here */
+
+#ifdef SUN_EXTENSIONS
+ if (sm_strcasecmp(vendor, "Sun") == 0)
+ {
+ VendorCode = VENDOR_SUN;
+ return true;
+ }
+#endif /* SUN_EXTENSIONS */
+
+#if defined(VENDOR_NAME) && defined(VENDOR_CODE)
+ if (sm_strcasecmp(vendor, VENDOR_NAME) == 0)
+ {
+ VendorCode = VENDOR_CODE;
+ return true;
+ }
+#endif /* defined(VENDOR_NAME) && defined(VENDOR_CODE) */
+
+ return false;
+}
+/*
+** GETVENDOR -- return vendor name based on vendor code
+**
+** Parameters:
+** vendorcode -- numeric representation of vendor.
+**
+** Returns:
+** string containing vendor name.
+*/
+
+char *
+getvendor(vendorcode)
+ int vendorcode;
+{
+#if defined(VENDOR_NAME) && defined(VENDOR_CODE)
+ /*
+ ** Can't have the same switch case twice so need to
+ ** handle VENDOR_CODE outside of switch. It might
+ ** match one of the existing VENDOR_* codes.
+ */
+
+ if (vendorcode == VENDOR_CODE)
+ return VENDOR_NAME;
+#endif /* defined(VENDOR_NAME) && defined(VENDOR_CODE) */
+
+ switch (vendorcode)
+ {
+ case VENDOR_BERKELEY:
+ return "Berkeley";
+
+ case VENDOR_SUN:
+ return "Sun";
+
+ case VENDOR_HP:
+ return "HP";
+
+ case VENDOR_IBM:
+ return "IBM";
+
+ case VENDOR_SENDMAIL:
+ return "Sendmail";
+
+ default:
+ return "Unknown";
+ }
+}
+/*
+** VENDOR_PRE_DEFAULTS, VENDOR_POST_DEFAULTS -- set vendor-specific defaults
+**
+** Vendor_pre_defaults is called before reading the configuration
+** file; vendor_post_defaults is called immediately after.
+**
+** Parameters:
+** e -- the global environment to initialize.
+**
+** Returns:
+** none.
+*/
+
+#if SHARE_V1
+int DefShareUid; /* default share uid to run as -- unused??? */
+#endif /* SHARE_V1 */
+
+void
+vendor_pre_defaults(e)
+ ENVELOPE *e;
+{
+#if SHARE_V1
+ /* OTHERUID is defined in shares.h, do not be alarmed */
+ DefShareUid = OTHERUID;
+#endif /* SHARE_V1 */
+#if defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES)
+ sun_pre_defaults(e);
+#endif /* defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES) */
+#ifdef apollo
+ /*
+ ** stupid domain/os can't even open
+ ** /etc/mail/sendmail.cf without this
+ */
+
+ setuserenv("ISP", NULL);
+ setuserenv("SYSTYPE", NULL);
+#endif /* apollo */
+}
+
+
+void
+vendor_post_defaults(e)
+ ENVELOPE *e;
+{
+#ifdef __QNX__
+ char *p;
+
+ /* Makes sure the SOCK environment variable remains */
+ if (p = getextenv("SOCK"))
+ setuserenv("SOCK", p);
+#endif /* __QNX__ */
+#if defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES)
+ sun_post_defaults(e);
+#endif /* defined(SUN_EXTENSIONS) && defined(SUN_DEFAULT_VALUES) */
+}
+/*
+** VENDOR_DAEMON_SETUP -- special vendor setup needed for daemon mode
+*/
+
+void
+vendor_daemon_setup(e)
+ ENVELOPE *e;
+{
+#if HASSETLOGIN
+ (void) setlogin(RunAsUserName);
+#endif /* HASSETLOGIN */
+#if SECUREWARE
+ if (getluid() != -1)
+ {
+ usrerr("Daemon cannot have LUID");
+ finis(false, true, EX_USAGE);
+ }
+#endif /* SECUREWARE */
+}
+/*
+** VENDOR_SET_UID -- do setup for setting a user id
+**
+** This is called when we are still root.
+**
+** Parameters:
+** uid -- the uid we are about to become.
+**
+** Returns:
+** none.
+*/
+
+void
+vendor_set_uid(uid)
+ UID_T uid;
+{
+ /*
+ ** We need to setup the share groups (lnodes)
+ ** and add auditing information (luid's)
+ ** before we loose our ``root''ness.
+ */
+#if SHARE_V1
+ if (setupshares(uid, syserr) != 0)
+ syserr("Unable to set up shares");
+#endif /* SHARE_V1 */
+#if SECUREWARE
+ (void) setup_secure(uid);
+#endif /* SECUREWARE */
+}
+/*
+** VALIDATE_CONNECTION -- check connection for rationality
+**
+** If the connection is rejected, this routine should log an
+** appropriate message -- but should never issue any SMTP protocol.
+**
+** Parameters:
+** sap -- a pointer to a SOCKADDR naming the peer.
+** hostname -- the name corresponding to sap.
+** e -- the current envelope.
+**
+** Returns:
+** error message from rejection.
+** NULL if not rejected.
+*/
+
+#if TCPWRAPPERS
+# include <tcpd.h>
+
+/* tcpwrappers does no logging, but you still have to declare these -- ugh */
+int allow_severity = LOG_INFO;
+int deny_severity = LOG_NOTICE;
+#endif /* TCPWRAPPERS */
+
+char *
+validate_connection(sap, hostname, e)
+ SOCKADDR *sap;
+ char *hostname;
+ ENVELOPE *e;
+{
+#if TCPWRAPPERS
+ char *host;
+ char *addr;
+ extern int hosts_ctl();
+#endif /* TCPWRAPPERS */
+
+ if (tTd(48, 3))
+ sm_dprintf("validate_connection(%s, %s)\n",
+ hostname, anynet_ntoa(sap));
+
+ if (rscheck("check_relay", hostname, anynet_ntoa(sap),
+ e, RSF_RMCOMM|RSF_COUNT, 3, NULL, NOQID) != EX_OK)
+ {
+ static char reject[BUFSIZ*2];
+ extern char MsgBuf[];
+
+ if (tTd(48, 4))
+ sm_dprintf(" ... validate_connection: BAD (rscheck)\n");
+
+ if (strlen(MsgBuf) >= 3)
+ (void) sm_strlcpy(reject, MsgBuf, sizeof reject);
+ else
+ (void) sm_strlcpy(reject, "Access denied", sizeof reject);
+
+ return reject;
+ }
+
+#if TCPWRAPPERS
+ if (hostname[0] == '[' && hostname[strlen(hostname) - 1] == ']')
+ host = "unknown";
+ else
+ host = hostname;
+ addr = anynet_ntoa(sap);
+
+# if NETINET6
+ /* TCP/Wrappers don't want the IPv6: protocol label */
+ if (addr != NULL && sm_strncasecmp(addr, "IPv6:", 5) == 0)
+ addr += 5;
+# endif /* NETINET6 */
+
+ if (!hosts_ctl("sendmail", host, addr, STRING_UNKNOWN))
+ {
+ if (tTd(48, 4))
+ sm_dprintf(" ... validate_connection: BAD (tcpwrappers)\n");
+ if (LogLevel > 3)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "tcpwrappers (%s, %s) rejection",
+ host, addr);
+ return "Access denied";
+ }
+#endif /* TCPWRAPPERS */
+ if (tTd(48, 4))
+ sm_dprintf(" ... validate_connection: OK\n");
+ return NULL;
+}
+
+/*
+** STRTOL -- convert string to long integer
+**
+** For systems that don't have it in the C library.
+**
+** This is taken verbatim from the 4.4-Lite C library.
+*/
+
+#if NEEDSTRTOL
+
+# if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)strtol.c 8.1 (Berkeley) 6/4/93";
+# endif /* defined(LIBC_SCCS) && !defined(lint) */
+
+/*
+** Convert a string to a long integer.
+**
+** Ignores `locale' stuff. Assumes that the upper and lower case
+** alphabets and digits are each contiguous.
+*/
+
+long
+strtol(nptr, endptr, base)
+ const char *nptr;
+ char **endptr;
+ register int base;
+{
+ register const char *s = nptr;
+ register unsigned long acc;
+ register int c;
+ register unsigned long cutoff;
+ register int neg = 0, any, cutlim;
+
+ /*
+ ** Skip white space and pick up leading +/- sign if any.
+ ** If base is 0, allow 0x for hex and 0 for octal, else
+ ** assume decimal; if base is already 16, allow 0x.
+ */
+ do {
+ c = *s++;
+ } while (isspace(c));
+ if (c == '-') {
+ neg = 1;
+ c = *s++;
+ } else if (c == '+')
+ c = *s++;
+ if ((base == 0 || base == 16) &&
+ c == '0' && (*s == 'x' || *s == 'X')) {
+ c = s[1];
+ s += 2;
+ base = 16;
+ }
+ if (base == 0)
+ base = c == '0' ? 8 : 10;
+
+ /*
+ ** Compute the cutoff value between legal numbers and illegal
+ ** numbers. That is the largest legal value, divided by the
+ ** base. An input number that is greater than this value, if
+ ** followed by a legal input character, is too big. One that
+ ** is equal to this value may be valid or not; the limit
+ ** between valid and invalid numbers is then based on the last
+ ** digit. For instance, if the range for longs is
+ ** [-2147483648..2147483647] and the input base is 10,
+ ** cutoff will be set to 214748364 and cutlim to either
+ ** 7 (neg==0) or 8 (neg==1), meaning that if we have accumulated
+ ** a value > 214748364, or equal but the next digit is > 7 (or 8),
+ ** the number is too big, and we will return a range error.
+ **
+ ** Set any if any `digits' consumed; make it negative to indicate
+ ** overflow.
+ */
+ cutoff = neg ? -(unsigned long) LONG_MIN : LONG_MAX;
+ cutlim = cutoff % (unsigned long) base;
+ cutoff /= (unsigned long) base;
+ for (acc = 0, any = 0;; c = *s++) {
+ if (isdigit(c))
+ c -= '0';
+ else if (isalpha(c))
+ c -= isupper(c) ? 'A' - 10 : 'a' - 10;
+ else
+ break;
+ if (c >= base)
+ break;
+ if (any < 0 || acc > cutoff || acc == cutoff && c > cutlim)
+ any = -1;
+ else {
+ any = 1;
+ acc *= base;
+ acc += c;
+ }
+ }
+ if (any < 0) {
+ acc = neg ? LONG_MIN : LONG_MAX;
+ errno = ERANGE;
+ } else if (neg)
+ acc = -acc;
+ if (endptr != 0)
+ *endptr = (char *)(any ? s - 1 : nptr);
+ return acc;
+}
+
+#endif /* NEEDSTRTOL */
+/*
+** STRSTR -- find first substring in string
+**
+** Parameters:
+** big -- the big (full) string.
+** little -- the little (sub) string.
+**
+** Returns:
+** A pointer to the first instance of little in big.
+** big if little is the null string.
+** NULL if little is not contained in big.
+*/
+
+#if NEEDSTRSTR
+
+char *
+strstr(big, little)
+ char *big;
+ char *little;
+{
+ register char *p = big;
+ int l;
+
+ if (*little == '\0')
+ return big;
+ l = strlen(little);
+
+ while ((p = strchr(p, *little)) != NULL)
+ {
+ if (strncmp(p, little, l) == 0)
+ return p;
+ p++;
+ }
+ return NULL;
+}
+
+#endif /* NEEDSTRSTR */
+/*
+** SM_GETHOSTBY{NAME,ADDR} -- compatibility routines for gethostbyXXX
+**
+** Some operating systems have wierd problems with the gethostbyXXX
+** routines. For example, Solaris versions at least through 2.3
+** don't properly deliver a canonical h_name field. This tries to
+** work around these problems.
+**
+** Support IPv6 as well as IPv4.
+*/
+
+#if NETINET6 && NEEDSGETIPNODE
+
+# ifndef AI_DEFAULT
+# define AI_DEFAULT 0 /* dummy */
+# endif /* ! AI_DEFAULT */
+# ifndef AI_ADDRCONFIG
+# define AI_ADDRCONFIG 0 /* dummy */
+# endif /* ! AI_ADDRCONFIG */
+# ifndef AI_V4MAPPED
+# define AI_V4MAPPED 0 /* dummy */
+# endif /* ! AI_V4MAPPED */
+# ifndef AI_ALL
+# define AI_ALL 0 /* dummy */
+# endif /* ! AI_ALL */
+
+static struct hostent *
+getipnodebyname(name, family, flags, err)
+ char *name;
+ int family;
+ int flags;
+ int *err;
+{
+ bool resv6 = true;
+ struct hostent *h;
+
+ if (family == AF_INET6)
+ {
+ /* From RFC2133, section 6.1 */
+ resv6 = bitset(RES_USE_INET6, _res.options);
+ _res.options |= RES_USE_INET6;
+ }
+ SM_SET_H_ERRNO(0);
+ h = gethostbyname(name);
+ if (!resv6)
+ _res.options &= ~RES_USE_INET6;
+ *err = h_errno;
+ return h;
+}
+
+static struct hostent *
+getipnodebyaddr(addr, len, family, err)
+ char *addr;
+ int len;
+ int family;
+ int *err;
+{
+ struct hostent *h;
+
+ SM_SET_H_ERRNO(0);
+ h = gethostbyaddr(addr, len, family);
+ *err = h_errno;
+ return h;
+}
+
+void
+freehostent(h)
+ struct hostent *h;
+{
+ /*
+ ** Stub routine -- if they don't have getipnodeby*(),
+ ** they probably don't have the free routine either.
+ */
+
+ return;
+}
+#endif /* NETINET6 && NEEDSGETIPNODE */
+
+struct hostent *
+sm_gethostbyname(name, family)
+ char *name;
+ int family;
+{
+ int save_errno;
+ struct hostent *h = NULL;
+#if (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4))
+# if SOLARIS == 20300 || SOLARIS == 203
+ static struct hostent hp;
+ static char buf[1000];
+ extern struct hostent *_switch_gethostbyname_r();
+
+ if (tTd(61, 10))
+ sm_dprintf("_switch_gethostbyname_r(%s)... ", name);
+ h = _switch_gethostbyname_r(name, &hp, buf, sizeof(buf), &h_errno);
+ save_errno = errno;
+# else /* SOLARIS == 20300 || SOLARIS == 203 */
+ extern struct hostent *__switch_gethostbyname();
+
+ if (tTd(61, 10))
+ sm_dprintf("__switch_gethostbyname(%s)... ", name);
+ h = __switch_gethostbyname(name);
+ save_errno = errno;
+# endif /* SOLARIS == 20300 || SOLARIS == 203 */
+#else /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) */
+ int nmaps;
+# if NETINET6
+ int flags = AI_DEFAULT|AI_ALL;
+ int err;
+# endif /* NETINET6 */
+ char *maptype[MAXMAPSTACK];
+ short mapreturn[MAXMAPACTIONS];
+ char hbuf[MAXNAME];
+
+ if (tTd(61, 10))
+ sm_dprintf("sm_gethostbyname(%s, %d)... ", name, family);
+
+# if NETINET6
+# if ADDRCONFIG_IS_BROKEN
+ flags &= ~AI_ADDRCONFIG;
+# endif /* ADDRCONFIG_IS_BROKEN */
+ h = getipnodebyname(name, family, flags, &err);
+ SM_SET_H_ERRNO(err);
+# else /* NETINET6 */
+ h = gethostbyname(name);
+# endif /* NETINET6 */
+
+ save_errno = errno;
+ if (h == NULL)
+ {
+ if (tTd(61, 10))
+ sm_dprintf("failure\n");
+
+ nmaps = switch_map_find("hosts", maptype, mapreturn);
+ while (--nmaps >= 0)
+ {
+ if (strcmp(maptype[nmaps], "nis") == 0 ||
+ strcmp(maptype[nmaps], "files") == 0)
+ break;
+ }
+
+ if (nmaps >= 0)
+ {
+ /* try short name */
+ if (strlen(name) > sizeof hbuf - 1)
+ {
+ errno = save_errno;
+ return NULL;
+ }
+ (void) sm_strlcpy(hbuf, name, sizeof hbuf);
+ (void) shorten_hostname(hbuf);
+
+ /* if it hasn't been shortened, there's no point */
+ if (strcmp(hbuf, name) != 0)
+ {
+ if (tTd(61, 10))
+ sm_dprintf("sm_gethostbyname(%s, %d)... ",
+ hbuf, family);
+
+# if NETINET6
+ h = getipnodebyname(hbuf, family, flags, &err);
+ SM_SET_H_ERRNO(err);
+ save_errno = errno;
+# else /* NETINET6 */
+ h = gethostbyname(hbuf);
+ save_errno = errno;
+# endif /* NETINET6 */
+ }
+ }
+ }
+#endif /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) || (defined(sony_news) && defined(__svr4)) */
+ if (tTd(61, 10))
+ {
+ if (h == NULL)
+ sm_dprintf("failure\n");
+ else
+ {
+ sm_dprintf("%s\n", h->h_name);
+ if (tTd(61, 11))
+ {
+#if NETINET6
+ struct in6_addr ia6;
+ char buf6[INET6_ADDRSTRLEN];
+#else /* NETINET6 */
+ struct in_addr ia;
+#endif /* NETINET6 */
+ size_t i;
+
+ if (h->h_aliases != NULL)
+ for (i = 0; h->h_aliases[i] != NULL;
+ i++)
+ sm_dprintf("\talias: %s\n",
+ h->h_aliases[i]);
+ for (i = 0; h->h_addr_list[i] != NULL; i++)
+ {
+ char *addr;
+
+#if NETINET6
+ memmove(&ia6, h->h_addr_list[i],
+ IN6ADDRSZ);
+ addr = anynet_ntop(&ia6,
+ buf6, sizeof buf6);
+#else /* NETINET6 */
+ memmove(&ia, h->h_addr_list[i],
+ INADDRSZ);
+ addr = (char *) inet_ntoa(ia);
+#endif /* NETINET6 */
+ if (addr != NULL)
+ sm_dprintf("\taddr: %s\n", addr);
+ }
+ }
+ }
+ }
+ errno = save_errno;
+ return h;
+}
+
+struct hostent *
+sm_gethostbyaddr(addr, len, type)
+ char *addr;
+ int len;
+ int type;
+{
+ struct hostent *hp;
+
+#if NETINET6
+ if (type == AF_INET6 &&
+ IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *) addr))
+ {
+ /* Avoid reverse lookup for IPv6 unspecified address */
+ SM_SET_H_ERRNO(HOST_NOT_FOUND);
+ return NULL;
+ }
+#endif /* NETINET6 */
+
+#if (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204)
+# if SOLARIS == 20300 || SOLARIS == 203
+ {
+ static struct hostent he;
+ static char buf[1000];
+ extern struct hostent *_switch_gethostbyaddr_r();
+
+ hp = _switch_gethostbyaddr_r(addr, len, type, &he,
+ buf, sizeof(buf), &h_errno);
+ }
+# else /* SOLARIS == 20300 || SOLARIS == 203 */
+ {
+ extern struct hostent *__switch_gethostbyaddr();
+
+ hp = __switch_gethostbyaddr(addr, len, type);
+ }
+# endif /* SOLARIS == 20300 || SOLARIS == 203 */
+#else /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) */
+# if NETINET6
+ {
+ int err;
+
+ hp = getipnodebyaddr(addr, len, type, &err);
+ SM_SET_H_ERRNO(err);
+ }
+# else /* NETINET6 */
+ hp = gethostbyaddr(addr, len, type);
+# endif /* NETINET6 */
+#endif /* (SOLARIS > 10000 && SOLARIS < 20400) || (defined(SOLARIS) && SOLARIS < 204) */
+ return hp;
+}
+/*
+** SM_GETPW{NAM,UID} -- wrapper for getpwnam and getpwuid
+*/
+
+struct passwd *
+sm_getpwnam(user)
+ char *user;
+{
+#ifdef _AIX4
+ extern struct passwd *_getpwnam_shadow(const char *, const int);
+
+ return _getpwnam_shadow(user, 0);
+#else /* _AIX4 */
+ return getpwnam(user);
+#endif /* _AIX4 */
+}
+
+struct passwd *
+sm_getpwuid(uid)
+ UID_T uid;
+{
+#if defined(_AIX4) && 0
+ extern struct passwd *_getpwuid_shadow(const int, const int);
+
+ return _getpwuid_shadow(uid,0);
+#else /* defined(_AIX4) && 0 */
+ return getpwuid(uid);
+#endif /* defined(_AIX4) && 0 */
+}
+/*
+** SECUREWARE_SETUP_SECURE -- Convex SecureWare setup
+**
+** Set up the trusted computing environment for C2 level security
+** under SecureWare.
+**
+** Parameters:
+** uid -- uid of the user to initialize in the TCB
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Initialized the user in the trusted computing base
+*/
+
+#if SECUREWARE
+
+# include <sys/security.h>
+# include <prot.h>
+
+void
+secureware_setup_secure(uid)
+ UID_T uid;
+{
+ int rc;
+
+ if (getluid() != -1)
+ return;
+
+ if ((rc = set_secure_info(uid)) != SSI_GOOD_RETURN)
+ {
+ switch (rc)
+ {
+ case SSI_NO_PRPW_ENTRY:
+ syserr("No protected passwd entry, uid = %d",
+ (int) uid);
+ break;
+
+ case SSI_LOCKED:
+ syserr("Account has been disabled, uid = %d",
+ (int) uid);
+ break;
+
+ case SSI_RETIRED:
+ syserr("Account has been retired, uid = %d",
+ (int) uid);
+ break;
+
+ case SSI_BAD_SET_LUID:
+ syserr("Could not set LUID, uid = %d", (int) uid);
+ break;
+
+ case SSI_BAD_SET_PRIVS:
+ syserr("Could not set kernel privs, uid = %d",
+ (int) uid);
+
+ default:
+ syserr("Unknown return code (%d) from set_secure_info(%d)",
+ rc, (int) uid);
+ break;
+ }
+ finis(false, true, EX_NOPERM);
+ }
+}
+#endif /* SECUREWARE */
+/*
+** ADD_HOSTNAMES -- Add a hostname to class 'w' based on IP address
+**
+** Add hostnames to class 'w' based on the IP address read from
+** the network interface.
+**
+** Parameters:
+** sa -- a pointer to a SOCKADDR containing the address
+**
+** Returns:
+** 0 if successful, -1 if host lookup fails.
+*/
+
+static int
+add_hostnames(sa)
+ SOCKADDR *sa;
+{
+ struct hostent *hp;
+ char **ha;
+ char hnb[MAXHOSTNAMELEN];
+
+ /* lookup name with IP address */
+ switch (sa->sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ hp = sm_gethostbyaddr((char *) &sa->sin.sin_addr,
+ sizeof(sa->sin.sin_addr),
+ sa->sa.sa_family);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ hp = sm_gethostbyaddr((char *) &sa->sin6.sin6_addr,
+ sizeof(sa->sin6.sin6_addr),
+ sa->sa.sa_family);
+ break;
+#endif /* NETINET6 */
+
+ default:
+ /* Give warning about unsupported family */
+ if (LogLevel > 3)
+ sm_syslog(LOG_WARNING, NOQID,
+ "Unsupported address family %d: %.100s",
+ sa->sa.sa_family, anynet_ntoa(sa));
+ return -1;
+ }
+
+ if (hp == NULL)
+ {
+ int save_errno = errno;
+
+ if (LogLevel > 3 &&
+#if NETINET6
+ !(sa->sa.sa_family == AF_INET6 &&
+ IN6_IS_ADDR_LINKLOCAL(&sa->sin6.sin6_addr)) &&
+#endif /* NETINET6 */
+ true)
+ sm_syslog(LOG_WARNING, NOQID,
+ "gethostbyaddr(%.100s) failed: %d",
+ anynet_ntoa(sa),
+#if NAMED_BIND
+ h_errno
+#else /* NAMED_BIND */
+ -1
+#endif /* NAMED_BIND */
+ );
+ errno = save_errno;
+ return -1;
+ }
+
+ /* save its cname */
+ if (!wordinclass((char *) hp->h_name, 'w'))
+ {
+ setclass('w', (char *) hp->h_name);
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", hp->h_name);
+
+ if (sm_snprintf(hnb, sizeof hnb, "[%s]", hp->h_name) < sizeof hnb
+ && !wordinclass((char *) hnb, 'w'))
+ setclass('w', hnb);
+ }
+ else
+ {
+ if (tTd(0, 43))
+ sm_dprintf("\ta.k.a.: %s (already in $=w)\n", hp->h_name);
+ }
+
+ /* save all it aliases name */
+ for (ha = hp->h_aliases; ha != NULL && *ha != NULL; ha++)
+ {
+ if (!wordinclass(*ha, 'w'))
+ {
+ setclass('w', *ha);
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", *ha);
+ if (sm_snprintf(hnb, sizeof hnb,
+ "[%s]", *ha) < sizeof hnb &&
+ !wordinclass((char *) hnb, 'w'))
+ setclass('w', hnb);
+ }
+ else
+ {
+ if (tTd(0, 43))
+ sm_dprintf("\ta.k.a.: %s (already in $=w)\n",
+ *ha);
+ }
+ }
+#if NETINET6
+ freehostent(hp);
+#endif /* NETINET6 */
+ return 0;
+}
+/*
+** LOAD_IF_NAMES -- load interface-specific names into $=w
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Loads $=w with the names of all the interfaces.
+*/
+
+#if !NETINET
+# define SIOCGIFCONF_IS_BROKEN 1 /* XXX */
+#endif /* !NETINET */
+
+#if defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN
+struct rtentry;
+struct mbuf;
+# ifndef SUNOS403
+# include <sys/time.h>
+# endif /* ! SUNOS403 */
+# if (_AIX4 >= 40300) && !defined(_NET_IF_H)
+# undef __P
+# endif /* (_AIX4 >= 40300) && !defined(_NET_IF_H) */
+# include <net/if.h>
+#endif /* defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN */
+
+void
+load_if_names()
+{
+# if NETINET6 && defined(SIOCGLIFCONF)
+# ifdef __hpux
+
+ /*
+ ** Unfortunately, HP has changed all of the structures,
+ ** making life difficult for implementors.
+ */
+
+# define lifconf if_laddrconf
+# define lifc_len iflc_len
+# define lifc_buf iflc_buf
+# define lifreq if_laddrreq
+# define lifr_addr iflr_addr
+# define lifr_name iflr_name
+# define lifr_flags iflr_flags
+# define ss_family sa_family
+# undef SIOCGLIFNUM
+# endif /* __hpux */
+
+ int s;
+ int i;
+ size_t len;
+ int numifs;
+ char *buf;
+ struct lifconf lifc;
+# ifdef SIOCGLIFNUM
+ struct lifnum lifn;
+# endif /* SIOCGLIFNUM */
+
+ s = socket(InetMode, SOCK_DGRAM, 0);
+ if (s == -1)
+ return;
+
+ /* get the list of known IP address from the kernel */
+# ifdef __hpux
+ i = ioctl(s, SIOCGIFNUM, (char *) &numifs);
+# endif /* __hpux */
+# ifdef SIOCGLIFNUM
+ lifn.lifn_family = AF_UNSPEC;
+ lifn.lifn_flags = 0;
+ i = ioctl(s, SIOCGLIFNUM, (char *)&lifn);
+ numifs = lifn.lifn_count;
+# endif /* SIOCGLIFNUM */
+
+# if defined(__hpux) || defined(SIOCGLIFNUM)
+ if (i < 0)
+ {
+ /* can't get number of interfaces -- fall back */
+ if (tTd(0, 4))
+ sm_dprintf("SIOCGLIFNUM failed: %s\n",
+ sm_errstring(errno));
+ numifs = -1;
+ }
+ else if (tTd(0, 42))
+ sm_dprintf("system has %d interfaces\n", numifs);
+ if (numifs < 0)
+# endif /* defined(__hpux) || defined(SIOCGLIFNUM) */
+ numifs = MAXINTERFACES;
+
+ if (numifs <= 0)
+ {
+ (void) close(s);
+ return;
+ }
+
+ len = lifc.lifc_len = numifs * sizeof (struct lifreq);
+ buf = lifc.lifc_buf = xalloc(lifc.lifc_len);
+# ifndef __hpux
+ lifc.lifc_family = AF_UNSPEC;
+ lifc.lifc_flags = 0;
+# endif /* __hpux */
+ if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0)
+ {
+ if (tTd(0, 4))
+ sm_dprintf("SIOCGLIFCONF failed: %s\n",
+ sm_errstring(errno));
+ (void) close(s);
+ sm_free(buf);
+ return;
+ }
+
+ /* scan the list of IP address */
+ if (tTd(0, 40))
+ sm_dprintf("scanning for interface specific names, lifc_len=%ld\n",
+ (long) len);
+
+ for (i = 0; i < len && i >= 0; )
+ {
+ int flags;
+ struct lifreq *ifr = (struct lifreq *)&buf[i];
+ SOCKADDR *sa = (SOCKADDR *) &ifr->lifr_addr;
+ int af = ifr->lifr_addr.ss_family;
+ char *addr;
+ char *name;
+ struct in6_addr ia6;
+ struct in_addr ia;
+# ifdef SIOCGLIFFLAGS
+ struct lifreq ifrf;
+# endif /* SIOCGLIFFLAGS */
+ char ip_addr[256];
+ char buf6[INET6_ADDRSTRLEN];
+
+ /*
+ ** We must close and recreate the socket each time
+ ** since we don't know what type of socket it is now
+ ** (each status function may change it).
+ */
+
+ (void) close(s);
+
+ s = socket(af, SOCK_DGRAM, 0);
+ if (s == -1)
+ {
+ sm_free(buf); /* XXX */
+ return;
+ }
+
+ /*
+ ** If we don't have a complete ifr structure,
+ ** don't try to use it.
+ */
+
+ if ((len - i) < sizeof *ifr)
+ break;
+
+# ifdef BSD4_4_SOCKADDR
+ if (sa->sa.sa_len > sizeof ifr->lifr_addr)
+ i += sizeof ifr->lifr_name + sa->sa.sa_len;
+ else
+# endif /* BSD4_4_SOCKADDR */
+ i += sizeof *ifr;
+
+ if (tTd(0, 20))
+ sm_dprintf("%s\n", anynet_ntoa(sa));
+
+ if (af != AF_INET && af != AF_INET6)
+ continue;
+
+# ifdef SIOCGLIFFLAGS
+ memset(&ifrf, '\0', sizeof(struct lifreq));
+ (void) sm_strlcpy(ifrf.lifr_name, ifr->lifr_name,
+ sizeof(ifrf.lifr_name));
+ if (ioctl(s, SIOCGLIFFLAGS, (char *) &ifrf) < 0)
+ {
+ if (tTd(0, 4))
+ sm_dprintf("SIOCGLIFFLAGS failed: %s\n",
+ sm_errstring(errno));
+ continue;
+ }
+
+ name = ifr->lifr_name;
+ flags = ifrf.lifr_flags;
+
+ if (tTd(0, 41))
+ sm_dprintf("\tflags: %lx\n", (unsigned long) flags);
+
+ if (!bitset(IFF_UP, flags))
+ continue;
+# endif /* SIOCGLIFFLAGS */
+
+ ip_addr[0] = '\0';
+
+ /* extract IP address from the list*/
+ switch (af)
+ {
+ case AF_INET6:
+# ifdef __KAME__
+ /* convert into proper scoped address */
+ if ((IN6_IS_ADDR_LINKLOCAL(&sa->sin6.sin6_addr) ||
+ IN6_IS_ADDR_SITELOCAL(&sa->sin6.sin6_addr)) &&
+ sa->sin6.sin6_scope_id == 0)
+ {
+ struct in6_addr *ia6p;
+
+ ia6p = &sa->sin6.sin6_addr;
+ sa->sin6.sin6_scope_id = ntohs(ia6p->s6_addr[3] |
+ ((unsigned int)ia6p->s6_addr[2] << 8));
+ ia6p->s6_addr[2] = ia6p->s6_addr[3] = 0;
+ }
+# endif /* __KAME__ */
+ ia6 = sa->sin6.sin6_addr;
+ if (IN6_IS_ADDR_UNSPECIFIED(&ia6))
+ {
+ addr = anynet_ntop(&ia6, buf6, sizeof buf6);
+ message("WARNING: interface %s is UP with %s address",
+ name, addr == NULL ? "(NULL)" : addr);
+ continue;
+ }
+
+ /* save IP address in text from */
+ addr = anynet_ntop(&ia6, buf6, sizeof buf6);
+ if (addr != NULL)
+ (void) sm_snprintf(ip_addr, sizeof ip_addr,
+ "[%.*s]",
+ (int) sizeof ip_addr - 3,
+ addr);
+ break;
+
+ case AF_INET:
+ ia = sa->sin.sin_addr;
+ if (ia.s_addr == INADDR_ANY ||
+ ia.s_addr == INADDR_NONE)
+ {
+ message("WARNING: interface %s is UP with %s address",
+ name, inet_ntoa(ia));
+ continue;
+ }
+
+ /* save IP address in text from */
+ (void) sm_snprintf(ip_addr, sizeof ip_addr, "[%.*s]",
+ (int) sizeof ip_addr - 3, inet_ntoa(ia));
+ break;
+ }
+
+ if (*ip_addr == '\0')
+ continue;
+
+ if (!wordinclass(ip_addr, 'w'))
+ {
+ setclass('w', ip_addr);
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", ip_addr);
+ }
+
+# ifdef SIOCGLIFFLAGS
+ /* skip "loopback" interface "lo" */
+ if (DontProbeInterfaces == DPI_SKIPLOOPBACK &&
+ bitset(IFF_LOOPBACK, flags))
+ continue;
+# endif /* SIOCGLIFFLAGS */
+ (void) add_hostnames(sa);
+ }
+ sm_free(buf); /* XXX */
+ (void) close(s);
+# else /* NETINET6 && defined(SIOCGLIFCONF) */
+# if defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN
+ int s;
+ int i;
+ struct ifconf ifc;
+ int numifs;
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s == -1)
+ return;
+
+ /* get the list of known IP address from the kernel */
+# if defined(SIOCGIFNUM) && !SIOCGIFNUM_IS_BROKEN
+ if (ioctl(s, SIOCGIFNUM, (char *) &numifs) < 0)
+ {
+ /* can't get number of interfaces -- fall back */
+ if (tTd(0, 4))
+ sm_dprintf("SIOCGIFNUM failed: %s\n",
+ sm_errstring(errno));
+ numifs = -1;
+ }
+ else if (tTd(0, 42))
+ sm_dprintf("system has %d interfaces\n", numifs);
+ if (numifs < 0)
+# endif /* defined(SIOCGIFNUM) && !SIOCGIFNUM_IS_BROKEN */
+ numifs = MAXINTERFACES;
+
+ if (numifs <= 0)
+ {
+ (void) close(s);
+ return;
+ }
+ ifc.ifc_len = numifs * sizeof (struct ifreq);
+ ifc.ifc_buf = xalloc(ifc.ifc_len);
+ if (ioctl(s, SIOCGIFCONF, (char *)&ifc) < 0)
+ {
+ if (tTd(0, 4))
+ sm_dprintf("SIOCGIFCONF failed: %s\n",
+ sm_errstring(errno));
+ (void) close(s);
+ return;
+ }
+
+ /* scan the list of IP address */
+ if (tTd(0, 40))
+ sm_dprintf("scanning for interface specific names, ifc_len=%d\n",
+ ifc.ifc_len);
+
+ for (i = 0; i < ifc.ifc_len && i >= 0; )
+ {
+ int af;
+ struct ifreq *ifr = (struct ifreq *) &ifc.ifc_buf[i];
+ SOCKADDR *sa = (SOCKADDR *) &ifr->ifr_addr;
+# if NETINET6
+ char *addr;
+ struct in6_addr ia6;
+# endif /* NETINET6 */
+ struct in_addr ia;
+# ifdef SIOCGIFFLAGS
+ struct ifreq ifrf;
+# endif /* SIOCGIFFLAGS */
+ char ip_addr[256];
+# if NETINET6
+ char buf6[INET6_ADDRSTRLEN];
+# endif /* NETINET6 */
+
+ /*
+ ** If we don't have a complete ifr structure,
+ ** don't try to use it.
+ */
+
+ if ((ifc.ifc_len - i) < sizeof *ifr)
+ break;
+
+# ifdef BSD4_4_SOCKADDR
+ if (sa->sa.sa_len > sizeof ifr->ifr_addr)
+ i += sizeof ifr->ifr_name + sa->sa.sa_len;
+ else
+# endif /* BSD4_4_SOCKADDR */
+ i += sizeof *ifr;
+
+ if (tTd(0, 20))
+ sm_dprintf("%s\n", anynet_ntoa(sa));
+
+ af = ifr->ifr_addr.sa_family;
+ if (af != AF_INET
+# if NETINET6
+ && af != AF_INET6
+# endif /* NETINET6 */
+ )
+ continue;
+
+# ifdef SIOCGIFFLAGS
+ memset(&ifrf, '\0', sizeof(struct ifreq));
+ (void) sm_strlcpy(ifrf.ifr_name, ifr->ifr_name,
+ sizeof(ifrf.ifr_name));
+ (void) ioctl(s, SIOCGIFFLAGS, (char *) &ifrf);
+ if (tTd(0, 41))
+ sm_dprintf("\tflags: %lx\n",
+ (unsigned long) ifrf.ifr_flags);
+# define IFRFREF ifrf
+# else /* SIOCGIFFLAGS */
+# define IFRFREF (*ifr)
+# endif /* SIOCGIFFLAGS */
+
+ if (!bitset(IFF_UP, IFRFREF.ifr_flags))
+ continue;
+
+ ip_addr[0] = '\0';
+
+ /* extract IP address from the list*/
+ switch (af)
+ {
+ case AF_INET:
+ ia = sa->sin.sin_addr;
+ if (ia.s_addr == INADDR_ANY ||
+ ia.s_addr == INADDR_NONE)
+ {
+ message("WARNING: interface %s is UP with %s address",
+ ifr->ifr_name, inet_ntoa(ia));
+ continue;
+ }
+
+ /* save IP address in text from */
+ (void) sm_snprintf(ip_addr, sizeof ip_addr, "[%.*s]",
+ (int) sizeof ip_addr - 3,
+ inet_ntoa(ia));
+ break;
+
+# if NETINET6
+ case AF_INET6:
+# ifdef __KAME__
+ /* convert into proper scoped address */
+ if ((IN6_IS_ADDR_LINKLOCAL(&sa->sin6.sin6_addr) ||
+ IN6_IS_ADDR_SITELOCAL(&sa->sin6.sin6_addr)) &&
+ sa->sin6.sin6_scope_id == 0)
+ {
+ struct in6_addr *ia6p;
+
+ ia6p = &sa->sin6.sin6_addr;
+ sa->sin6.sin6_scope_id = ntohs(ia6p->s6_addr[3] |
+ ((unsigned int)ia6p->s6_addr[2] << 8));
+ ia6p->s6_addr[2] = ia6p->s6_addr[3] = 0;
+ }
+# endif /* __KAME__ */
+ ia6 = sa->sin6.sin6_addr;
+ if (IN6_IS_ADDR_UNSPECIFIED(&ia6))
+ {
+ addr = anynet_ntop(&ia6, buf6, sizeof buf6);
+ message("WARNING: interface %s is UP with %s address",
+ ifr->ifr_name,
+ addr == NULL ? "(NULL)" : addr);
+ continue;
+ }
+
+ /* save IP address in text from */
+ addr = anynet_ntop(&ia6, buf6, sizeof buf6);
+ if (addr != NULL)
+ (void) sm_snprintf(ip_addr, sizeof ip_addr,
+ "[%.*s]",
+ (int) sizeof ip_addr - 3,
+ addr);
+ break;
+
+# endif /* NETINET6 */
+ }
+
+ if (ip_addr[0] == '\0')
+ continue;
+
+ if (!wordinclass(ip_addr, 'w'))
+ {
+ setclass('w', ip_addr);
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", ip_addr);
+ }
+
+ /* skip "loopback" interface "lo" */
+ if (DontProbeInterfaces == DPI_SKIPLOOPBACK &&
+ bitset(IFF_LOOPBACK, IFRFREF.ifr_flags))
+ continue;
+
+ (void) add_hostnames(sa);
+ }
+ sm_free(ifc.ifc_buf); /* XXX */
+ (void) close(s);
+# undef IFRFREF
+# endif /* defined(SIOCGIFCONF) && !SIOCGIFCONF_IS_BROKEN */
+# endif /* NETINET6 && defined(SIOCGLIFCONF) */
+}
+/*
+** ISLOOPBACK -- is socket address in the loopback net?
+**
+** Parameters:
+** sa -- socket address.
+**
+** Returns:
+** true -- is socket address in the loopback net?
+** false -- otherwise
+**
+*/
+
+bool
+isloopback(sa)
+ SOCKADDR sa;
+{
+#if NETINET6
+ if (IN6_IS_ADDR_LOOPBACK(&sa.sin6.sin6_addr))
+ return true;
+#else /* NETINET6 */
+ /* XXX how to correctly extract IN_LOOPBACKNET part? */
+ if (((ntohl(sa.sin.sin_addr.s_addr) & IN_CLASSA_NET)
+ >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
+ return true;
+#endif /* NETINET6 */
+ return false;
+}
+/*
+** GET_NUM_PROCS_ONLINE -- return the number of processors currently online
+**
+** Parameters:
+** none.
+**
+** Returns:
+** The number of processors online.
+*/
+
+static int
+get_num_procs_online()
+{
+ int nproc = 0;
+
+#ifdef USESYSCTL
+# if defined(CTL_HW) && defined(HW_NCPU)
+ size_t sz;
+ int mib[2];
+
+ mib[0] = CTL_HW;
+ mib[1] = HW_NCPU;
+ sz = (size_t) sizeof nproc;
+ (void) sysctl(mib, 2, &nproc, &sz, NULL, 0);
+# endif /* defined(CTL_HW) && defined(HW_NCPU) */
+#else /* USESYSCTL */
+# ifdef _SC_NPROCESSORS_ONLN
+ nproc = (int) sysconf(_SC_NPROCESSORS_ONLN);
+# else /* _SC_NPROCESSORS_ONLN */
+# ifdef __hpux
+# include <sys/pstat.h>
+ struct pst_dynamic psd;
+
+ if (pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0) != -1)
+ nproc = psd.psd_proc_cnt;
+# endif /* __hpux */
+# endif /* _SC_NPROCESSORS_ONLN */
+#endif /* USESYSCTL */
+
+ if (nproc <= 0)
+ nproc = 1;
+ return nproc;
+}
+/*
+** SEED_RANDOM -- seed the random number generator
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+void
+seed_random()
+{
+#if HASSRANDOMDEV
+ srandomdev();
+#else /* HASSRANDOMDEV */
+ long seed;
+ struct timeval t;
+
+ seed = (long) CurrentPid;
+ if (gettimeofday(&t, NULL) >= 0)
+ seed += t.tv_sec + t.tv_usec;
+
+# if HASRANDOM
+ (void) srandom(seed);
+# else /* HASRANDOM */
+ (void) srand((unsigned int) seed);
+# endif /* HASRANDOM */
+#endif /* HASSRANDOMDEV */
+}
+/*
+** SM_SYSLOG -- syslog wrapper to keep messages under SYSLOG_BUFSIZE
+**
+** Parameters:
+** level -- syslog level
+** id -- envelope ID or NULL (NOQUEUE)
+** fmt -- format string
+** arg... -- arguments as implied by fmt.
+**
+** Returns:
+** none
+*/
+
+/* VARARGS3 */
+void
+#ifdef __STDC__
+sm_syslog(int level, const char *id, const char *fmt, ...)
+#else /* __STDC__ */
+sm_syslog(level, id, fmt, va_alist)
+ int level;
+ const char *id;
+ const char *fmt;
+ va_dcl
+#endif /* __STDC__ */
+{
+ static char *buf = NULL;
+ static size_t bufsize;
+ char *begin, *end;
+ int save_errno;
+ int seq = 1;
+ int idlen;
+ char buf0[MAXLINE];
+ char *newstring;
+ extern int SyslogPrefixLen;
+ SM_VA_LOCAL_DECL
+
+ save_errno = errno;
+ if (id == NULL)
+ {
+ id = "NOQUEUE";
+ idlen = strlen(id) + SyslogPrefixLen;
+ }
+ else if (strcmp(id, NOQID) == 0)
+ {
+ id = "";
+ idlen = SyslogPrefixLen;
+ }
+ else
+ idlen = strlen(id) + SyslogPrefixLen;
+
+ if (buf == NULL)
+ {
+ buf = buf0;
+ bufsize = sizeof buf0;
+ }
+
+ for (;;)
+ {
+ int n;
+
+ /* print log message into buf */
+ SM_VA_START(ap, fmt);
+ n = sm_vsnprintf(buf, bufsize, fmt, ap);
+ SM_VA_END(ap);
+ SM_ASSERT(n > 0);
+ if (n < bufsize)
+ break;
+
+ /* String too small, redo with correct size */
+ bufsize = n + 1;
+ if (buf != buf0)
+ {
+ sm_free(buf);
+ buf = NULL;
+ }
+ buf = sm_malloc_x(bufsize);
+ }
+
+ /* clean up buf after it has been expanded with args */
+ newstring = str2prt(buf);
+ if ((strlen(newstring) + idlen + 1) < SYSLOG_BUFSIZE)
+ {
+#if LOG
+ if (*id == '\0')
+ syslog(level, "%s", newstring);
+ else
+ syslog(level, "%s: %s", id, newstring);
+#else /* LOG */
+ /*XXX should do something more sensible */
+ if (*id == '\0')
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s\n",
+ newstring);
+ else
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: %s\n", id, newstring);
+#endif /* LOG */
+ if (buf == buf0)
+ buf = NULL;
+ errno = save_errno;
+ return;
+ }
+
+/*
+** additional length for splitting: " ..." + 3, where 3 is magic to
+** have some data for the next entry.
+*/
+
+#define SL_SPLIT 7
+
+ begin = newstring;
+ idlen += 5; /* strlen("[999]"), see below */
+ while (*begin != '\0' &&
+ (strlen(begin) + idlen) > SYSLOG_BUFSIZE)
+ {
+ char save;
+
+ if (seq >= 999)
+ {
+ /* Too many messages */
+ break;
+ }
+ end = begin + SYSLOG_BUFSIZE - idlen - SL_SPLIT;
+ while (end > begin)
+ {
+ /* Break on comma or space */
+ if (*end == ',' || *end == ' ')
+ {
+ end++; /* Include separator */
+ break;
+ }
+ end--;
+ }
+ /* No separator, break midstring... */
+ if (end == begin)
+ end = begin + SYSLOG_BUFSIZE - idlen - SL_SPLIT;
+ save = *end;
+ *end = 0;
+#if LOG
+ syslog(level, "%s[%d]: %s ...", id, seq++, begin);
+#else /* LOG */
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s[%d]: %s ...\n", id, seq++, begin);
+#endif /* LOG */
+ *end = save;
+ begin = end;
+ }
+ if (seq >= 999)
+#if LOG
+ syslog(level, "%s[%d]: log terminated, too many parts",
+ id, seq);
+#else /* LOG */
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s[%d]: log terminated, too many parts\n", id, seq);
+#endif /* LOG */
+ else if (*begin != '\0')
+#if LOG
+ syslog(level, "%s[%d]: %s", id, seq, begin);
+#else /* LOG */
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s[%d]: %s\n", id, seq, begin);
+#endif /* LOG */
+ if (buf == buf0)
+ buf = NULL;
+ errno = save_errno;
+}
+/*
+** HARD_SYSLOG -- call syslog repeatedly until it works
+**
+** Needed on HP-UX, which apparently doesn't guarantee that
+** syslog succeeds during interrupt handlers.
+*/
+
+#if defined(__hpux) && !defined(HPUX11)
+
+# define MAXSYSLOGTRIES 100
+# undef syslog
+# ifdef V4FS
+# define XCNST const
+# define CAST (const char *)
+# else /* V4FS */
+# define XCNST
+# define CAST
+# endif /* V4FS */
+
+void
+# ifdef __STDC__
+hard_syslog(int pri, XCNST char *msg, ...)
+# else /* __STDC__ */
+hard_syslog(pri, msg, va_alist)
+ int pri;
+ XCNST char *msg;
+ va_dcl
+# endif /* __STDC__ */
+{
+ int i;
+ char buf[SYSLOG_BUFSIZE];
+ SM_VA_LOCAL_DECL
+
+ SM_VA_START(ap, msg);
+ (void) sm_vsnprintf(buf, sizeof buf, msg, ap);
+ SM_VA_END(ap);
+
+ for (i = MAXSYSLOGTRIES; --i >= 0 && syslog(pri, CAST "%s", buf) < 0; )
+ continue;
+}
+
+# undef CAST
+#endif /* defined(__hpux) && !defined(HPUX11) */
+#if NEEDLOCAL_HOSTNAME_LENGTH
+/*
+** LOCAL_HOSTNAME_LENGTH
+**
+** This is required to get sendmail to compile against BIND 4.9.x
+** on Ultrix.
+**
+** Unfortunately, a Compaq Y2K patch kit provides it without
+** bumping __RES in /usr/include/resolv.h so we can't automatically
+** figure out whether it is needed.
+*/
+
+int
+local_hostname_length(hostname)
+ char *hostname;
+{
+ size_t len_host, len_domain;
+
+ if (!*_res.defdname)
+ res_init();
+ len_host = strlen(hostname);
+ len_domain = strlen(_res.defdname);
+ if (len_host > len_domain &&
+ (sm_strcasecmp(hostname + len_host - len_domain,
+ _res.defdname) == 0) &&
+ hostname[len_host - len_domain - 1] == '.')
+ return len_host - len_domain - 1;
+ else
+ return 0;
+}
+#endif /* NEEDLOCAL_HOSTNAME_LENGTH */
+
+#if NEEDLINK
+/*
+** LINK -- clone a file
+**
+** Some OS's lacks link() and hard links. Since sendmail is using
+** link() as an efficient way to clone files, this implementation
+** will simply do a file copy.
+**
+** NOTE: This link() replacement is not a generic replacement as it
+** does not handle all of the semantics of the real link(2).
+**
+** Parameters:
+** source -- pathname of existing file.
+** target -- pathname of link (clone) to be created.
+**
+** Returns:
+** 0 -- success.
+** -1 -- failure, see errno for details.
+*/
+
+int
+link(source, target)
+ const char *source;
+ const char *target;
+{
+ int save_errno;
+ int sff;
+ int src = -1, dst = -1;
+ ssize_t readlen;
+ ssize_t writelen;
+ char buf[BUFSIZ];
+ struct stat st;
+
+ sff = SFF_REGONLY|SFF_OPENASROOT;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+
+ /* Open the original file */
+ src = safeopen((char *)source, O_RDONLY, 0, sff);
+ if (src < 0)
+ goto fail;
+
+ /* Obtain the size and the mode */
+ if (fstat(src, &st) < 0)
+ goto fail;
+
+ /* Create the duplicate copy */
+ sff &= ~SFF_NOLOCK;
+ sff |= SFF_CREAT;
+ dst = safeopen((char *)target, O_CREAT|O_EXCL|O_WRONLY,
+ st.st_mode, sff);
+ if (dst < 0)
+ goto fail;
+
+ /* Copy all of the bytes one buffer at a time */
+ while ((readlen = read(src, &buf, sizeof(buf))) > 0)
+ {
+ ssize_t left = readlen;
+ char *p = buf;
+
+ while (left > 0 &&
+ (writelen = write(dst, p, (size_t) left)) >= 0)
+ {
+ left -= writelen;
+ p += writelen;
+ }
+ if (writelen < 0)
+ break;
+ }
+
+ /* Any trouble reading? */
+ if (readlen < 0 || writelen < 0)
+ goto fail;
+
+ /* Close the input file */
+ if (close(src) < 0)
+ {
+ src = -1;
+ goto fail;
+ }
+ src = -1;
+
+ /* Close the output file */
+ if (close(dst) < 0)
+ {
+ /* don't set dst = -1 here so we unlink the file */
+ goto fail;
+ }
+
+ /* Success */
+ return 0;
+
+ fail:
+ save_errno = errno;
+ if (src >= 0)
+ (void) close(src);
+ if (dst >= 0)
+ {
+ (void) unlink(target);
+ (void) close(dst);
+ }
+ errno = save_errno;
+ return -1;
+}
+#endif /* NEEDLINK */
+
+/*
+** Compile-Time options
+*/
+
+char *CompileOptions[] =
+{
+#if NAMED_BIND
+# if DNSMAP
+ "DNSMAP",
+# endif /* DNSMAP */
+#endif /* NAMED_BIND */
+#if EGD
+ "EGD",
+#endif /* EGD */
+#if HESIOD
+ "HESIOD",
+#endif /* HESIOD */
+#if HES_GETMAILHOST
+ "HES_GETMAILHOST",
+#endif /* HES_GETMAILHOST */
+#if LDAPMAP
+ "LDAPMAP",
+#endif /* LDAPMAP */
+#if LOG
+ "LOG",
+#endif /* LOG */
+#if MAP_NSD
+ "MAP_NSD",
+#endif /* MAP_NSD */
+#if MAP_REGEX
+ "MAP_REGEX",
+#endif /* MAP_REGEX */
+#if MATCHGECOS
+ "MATCHGECOS",
+#endif /* MATCHGECOS */
+#if MILTER
+ "MILTER",
+#endif /* MILTER */
+#if MIME7TO8
+ "MIME7TO8",
+#endif /* MIME7TO8 */
+#if MIME8TO7
+ "MIME8TO7",
+#endif /* MIME8TO7 */
+#if NAMED_BIND
+ "NAMED_BIND",
+#endif /* NAMED_BIND */
+#if NDBM
+ "NDBM",
+#endif /* NDBM */
+#if NETINET
+ "NETINET",
+#endif /* NETINET */
+#if NETINET6
+ "NETINET6",
+#endif /* NETINET6 */
+#if NETINFO
+ "NETINFO",
+#endif /* NETINFO */
+#if NETISO
+ "NETISO",
+#endif /* NETISO */
+#if NETNS
+ "NETNS",
+#endif /* NETNS */
+#if NETUNIX
+ "NETUNIX",
+#endif /* NETUNIX */
+#if NETX25
+ "NETX25",
+#endif /* NETX25 */
+#if NEWDB
+ "NEWDB",
+#endif /* NEWDB */
+#if NIS
+ "NIS",
+#endif /* NIS */
+#if NISPLUS
+ "NISPLUS",
+#endif /* NISPLUS */
+#if NO_DH
+ "NO_DH",
+#endif /* NO_DH */
+#if PH_MAP
+ "PH_MAP",
+#endif /* PH_MAP */
+#ifdef PICKY_HELO_CHECK
+ "PICKY_HELO_CHECK",
+#endif /* PICKY_HELO_CHECK */
+#if PIPELINING
+ "PIPELINING",
+#endif /* PIPELINING */
+#if SASL
+# if SASL >= 20000
+ "SASLv2",
+# else /* SASL >= 20000 */
+ "SASL",
+# endif /* SASL >= 20000 */
+#endif /* SASL */
+#if SCANF
+ "SCANF",
+#endif /* SCANF */
+#if SMTPDEBUG
+ "SMTPDEBUG",
+#endif /* SMTPDEBUG */
+#if STARTTLS
+ "STARTTLS",
+#endif /* STARTTLS */
+#if SUID_ROOT_FILES_OK
+ "SUID_ROOT_FILES_OK",
+#endif /* SUID_ROOT_FILES_OK */
+#if TCPWRAPPERS
+ "TCPWRAPPERS",
+#endif /* TCPWRAPPERS */
+#if TLS_NO_RSA
+ "TLS_NO_RSA",
+#endif /* TLS_NO_RSA */
+#if TLS_VRFY_PER_CTX
+ "TLS_VRFY_PER_CTX",
+#endif /* TLS_VRFY_PER_CTX */
+#if USERDB
+ "USERDB",
+#endif /* USERDB */
+#if USE_LDAP_INIT
+ "USE_LDAP_INIT",
+#endif /* USE_LDAP_INIT */
+#if XDEBUG
+ "XDEBUG",
+#endif /* XDEBUG */
+#if XLA
+ "XLA",
+#endif /* XLA */
+ NULL
+};
+
+
+/*
+** OS compile options.
+*/
+
+char *OsCompileOptions[] =
+{
+#if ADDRCONFIG_IS_BROKEN
+ "ADDRCONFIG_IS_BROKEN",
+#endif /* ADDRCONFIG_IS_BROKEN */
+#ifdef AUTO_NETINFO_HOSTS
+ "AUTO_NETINFO_HOSTS",
+#endif /* AUTO_NETINFO_HOSTS */
+#ifdef AUTO_NIS_ALIASES
+ "AUTO_NIS_ALIASES",
+#endif /* AUTO_NIS_ALIASES */
+#if BROKEN_RES_SEARCH
+ "BROKEN_RES_SEARCH",
+#endif /* BROKEN_RES_SEARCH */
+#ifdef BSD4_4_SOCKADDR
+ "BSD4_4_SOCKADDR",
+#endif /* BSD4_4_SOCKADDR */
+#if BOGUS_O_EXCL
+ "BOGUS_O_EXCL",
+#endif /* BOGUS_O_EXCL */
+#if DEC_OSF_BROKEN_GETPWENT
+ "DEC_OSF_BROKEN_GETPWENT",
+#endif /* DEC_OSF_BROKEN_GETPWENT */
+#if FAST_PID_RECYCLE
+ "FAST_PID_RECYCLE",
+#endif /* FAST_PID_RECYCLE */
+#if HASFCHOWN
+ "HASFCHOWN",
+#endif /* HASFCHOWN */
+#if HASFCHMOD
+ "HASFCHMOD",
+#endif /* HASFCHMOD */
+#if HASFLOCK
+ "HASFLOCK",
+#endif /* HASFLOCK */
+#if HASGETDTABLESIZE
+ "HASGETDTABLESIZE",
+#endif /* HASGETDTABLESIZE */
+#if HASGETUSERSHELL
+ "HASGETUSERSHELL",
+#endif /* HASGETUSERSHELL */
+#if HASINITGROUPS
+ "HASINITGROUPS",
+#endif /* HASINITGROUPS */
+#if HASLSTAT
+ "HASLSTAT",
+#endif /* HASLSTAT */
+#if HASNICE
+ "HASNICE",
+#endif /* HASNICE */
+#if HASRANDOM
+ "HASRANDOM",
+#endif /* HASRANDOM */
+#if HASRRESVPORT
+ "HASRRESVPORT",
+#endif /* HASRRESVPORT */
+#if HASSETEGID
+ "HASSETEGID",
+#endif /* HASSETEGID */
+#if HASSETLOGIN
+ "HASSETLOGIN",
+#endif /* HASSETLOGIN */
+#if HASSETREGID
+ "HASSETREGID",
+#endif /* HASSETREGID */
+#if HASSETRESGID
+ "HASSETRESGID",
+#endif /* HASSETRESGID */
+#if HASSETREUID
+ "HASSETREUID",
+#endif /* HASSETREUID */
+#if HASSETRLIMIT
+ "HASSETRLIMIT",
+#endif /* HASSETRLIMIT */
+#if HASSETSID
+ "HASSETSID",
+#endif /* HASSETSID */
+#if HASSETUSERCONTEXT
+ "HASSETUSERCONTEXT",
+#endif /* HASSETUSERCONTEXT */
+#if HASSETVBUF
+ "HASSETVBUF",
+#endif /* HASSETVBUF */
+#if HAS_ST_GEN
+ "HAS_ST_GEN",
+#endif /* HAS_ST_GEN */
+#if HASSRANDOMDEV
+ "HASSRANDOMDEV",
+#endif /* HASSRANDOMDEV */
+#if HASURANDOMDEV
+ "HASURANDOMDEV",
+#endif /* HASURANDOMDEV */
+#if HASSTRERROR
+ "HASSTRERROR",
+#endif /* HASSTRERROR */
+#if HASULIMIT
+ "HASULIMIT",
+#endif /* HASULIMIT */
+#if HASUNAME
+ "HASUNAME",
+#endif /* HASUNAME */
+#if HASUNSETENV
+ "HASUNSETENV",
+#endif /* HASUNSETENV */
+#if HASWAITPID
+ "HASWAITPID",
+#endif /* HASWAITPID */
+#if IDENTPROTO
+ "IDENTPROTO",
+#endif /* IDENTPROTO */
+#if IP_SRCROUTE
+ "IP_SRCROUTE",
+#endif /* IP_SRCROUTE */
+#if O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL
+ "LOCK_ON_OPEN",
+#endif /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
+#if NEEDFSYNC
+ "NEEDFSYNC",
+#endif /* NEEDFSYNC */
+#if NEEDLINK
+ "NEEDLINK",
+#endif /* NEEDLINK */
+#if NEEDLOCAL_HOSTNAME_LENGTH
+ "NEEDLOCAL_HOSTNAME_LENGTH",
+#endif /* NEEDLOCAL_HOSTNAME_LENGTH */
+#if NEEDSGETIPNODE
+ "NEEDSGETIPNODE",
+#endif /* NEEDSGETIPNODE */
+#if NEEDSTRSTR
+ "NEEDSTRSTR",
+#endif /* NEEDSTRSTR */
+#if NEEDSTRTOL
+ "NEEDSTRTOL",
+#endif /* NEEDSTRTOL */
+#ifdef NO_GETSERVBYNAME
+ "NO_GETSERVBYNAME",
+#endif /* NO_GETSERVBYNAME */
+#if NOFTRUNCATE
+ "NOFTRUNCATE",
+#endif /* NOFTRUNCATE */
+#if REQUIRES_DIR_FSYNC
+ "REQUIRES_DIR_FSYNC",
+#endif /* REQUIRES_DIR_FSYNC */
+#if RLIMIT_NEEDS_SYS_TIME_H
+ "RLIMIT_NEEDS_SYS_TIME_H",
+#endif /* RLIMIT_NEEDS_SYS_TIME_H */
+#if SAFENFSPATHCONF
+ "SAFENFSPATHCONF",
+#endif /* SAFENFSPATHCONF */
+#if SECUREWARE
+ "SECUREWARE",
+#endif /* SECUREWARE */
+#if SHARE_V1
+ "SHARE_V1",
+#endif /* SHARE_V1 */
+#if SIOCGIFCONF_IS_BROKEN
+ "SIOCGIFCONF_IS_BROKEN",
+#endif /* SIOCGIFCONF_IS_BROKEN */
+#if SIOCGIFNUM_IS_BROKEN
+ "SIOCGIFNUM_IS_BROKEN",
+#endif /* SIOCGIFNUM_IS_BROKEN */
+#if SNPRINTF_IS_BROKEN
+ "SNPRINTF_IS_BROKEN",
+#endif /* SNPRINTF_IS_BROKEN */
+#if SO_REUSEADDR_IS_BROKEN
+ "SO_REUSEADDR_IS_BROKEN",
+#endif /* SO_REUSEADDR_IS_BROKEN */
+#if SYS5SETPGRP
+ "SYS5SETPGRP",
+#endif /* SYS5SETPGRP */
+#if SYSTEM5
+ "SYSTEM5",
+#endif /* SYSTEM5 */
+#if USE_DOUBLE_FORK
+ "USE_DOUBLE_FORK",
+#endif /* USE_DOUBLE_FORK */
+#if USE_ENVIRON
+ "USE_ENVIRON",
+#endif /* USE_ENVIRON */
+#if USE_SA_SIGACTION
+ "USE_SA_SIGACTION",
+#endif /* USE_SA_SIGACTION */
+#if USE_SIGLONGJMP
+ "USE_SIGLONGJMP",
+#endif /* USE_SIGLONGJMP */
+#if USEGETCONFATTR
+ "USEGETCONFATTR",
+#endif /* USEGETCONFATTR */
+#if USESETEUID
+ "USESETEUID",
+#endif /* USESETEUID */
+#ifdef USESYSCTL
+ "USESYSCTL",
+#endif /* USESYSCTL */
+#if USING_NETSCAPE_LDAP
+ "USING_NETSCAPE_LDAP",
+#endif /* USING_NETSCAPE_LDAP */
+#ifdef WAITUNION
+ "WAITUNION",
+#endif /* WAITUNION */
+ NULL
+};
+
+/*
+** FFR compile options.
+*/
+
+char *FFRCompileOptions[] =
+{
+#if _FFR_ADAPTIVE_EOL
+ "_FFR_ADAPTIVE_EOL",
+#endif /* _FFR_ADAPTIVE_EOL */
+#if _FFR_ALLOW_SASLINFO
+ "_FFR_ALLOW_SASLINFO",
+#endif /* _FFR_ALLOW_SASLINFO */
+#if _FFR_ALLOW_S0_ERROR_4XX
+ "_FFR_ALLOW_S0_ERROR_4XX",
+#endif /* _FFR_ALLOW_S0_ERROR_4XX */
+#if _FFR_BESTMX_BETTER_TRUNCATION
+ "_FFR_BESTMX_BETTER_TRUNCATION",
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
+#if _FFR_CACHE_LPC
+/* Christophe Wolfhugel of France Telecom Oleane */
+ "_FFR_CACHE_LPC",
+#endif /* _FFR_CACHE_LPC */
+#if _FFR_CATCH_BROKEN_MTAS
+ "_FFR_CATCH_BROKEN_MTAS",
+#endif /* _FFR_CATCH_BROKEN_MTAS */
+#if _FFR_CATCH_LONG_STRINGS
+ "_FFR_CATCH_LONG_STRINGS",
+#endif /* _FFR_CATCH_LONG_STRINGS */
+#if _FFR_CHECK_EOM
+ "_FFR_CHECK_EOM",
+#endif /* _FFR_CHECK_EOM */
+#if _FFR_CHK_QUEUE
+ "_FFR_CHK_QUEUE",
+#endif /* _FFR_CHK_QUEUE */
+#if _FFR_CONTROL_MSTAT
+ "_FFR_CONTROL_MSTAT",
+#endif /* _FFR_CONTROL_MSTAT */
+#if _FFR_DAEMON_NETUNIX
+ "_FFR_DAEMON_NETUNIX",
+#endif /* _FFR_DAEMON_NETUNIX */
+#if _FFR_DEPRECATE_MAILER_FLAG_I
+ "_FFR_DEPRECATE_MAILER_FLAG_I",
+#endif /* _FFR_DEPRECATE_MAILER_FLAG_I */
+#if _FFR_DIGUNIX_SAFECHOWN
+/* Problem noted by Anne Bennett of Concordia University */
+ "_FFR_DIGUNIX_SAFECHOWN",
+#endif /* _FFR_DIGUNIX_SAFECHOWN */
+#if _FFR_DNSMAP_ALIASABLE
+/* Don Lewis of TDK */
+ "_FFR_DNSMAP_ALIASABLE",
+#endif /* _FFR_DNSMAP_ALIASABLE */
+#if _FFR_DNSMAP_BASE
+ "_FFR_DNSMAP_BASE",
+#endif /* _FFR_DNSMAP_BASE */
+#if _FFR_DNSMAP_MULTI
+ "_FFR_DNSMAP_MULTI",
+# if _FFR_DNSMAP_MULTILIMIT
+ "_FFR_DNSMAP_MULTILIMIT",
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+#endif /* _FFR_DNSMAP_MULTI */
+#if _FFR_DONTLOCKFILESFORREAD_OPTION
+ "_FFR_DONTLOCKFILESFORREAD_OPTION",
+#endif /* _FFR_DONTLOCKFILESFORREAD_OPTION */
+# if _FFR_DONT_STOP_LOOKING
+/* Noted by Neil Rickert of Northern Illinois University */
+ "_FFR_DONT_STOP_LOOKING",
+# endif /* _FFR_DONT_STOP_LOOKING */
+#if _FFR_DOTTED_USERNAMES
+ "_FFR_DOTTED_USERNAMES",
+#endif /* _FFR_DOTTED_USERNAMES */
+#if _FFR_DROP_TRUSTUSER_WARNING
+ "_FFR_DROP_TRUSTUSER_WARNING",
+#endif /* _FFR_DROP_TRUSTUSER_WARNING */
+#if _FFR_FIX_DASHT
+ "_FFR_FIX_DASHT",
+#endif /* _FFR_FIX_DASHT */
+#if _FFR_FORWARD_SYSERR
+ "_FFR_FORWARD_SYSERR",
+#endif /* _FFR_FORWARD_SYSERR */
+#if _FFR_GEN_ORCPT
+ "_FFR_GEN_ORCPT",
+#endif /* _FFR_GEN_ORCPT */
+#if _FFR_GROUPREADABLEAUTHINFOFILE
+ "_FFR_GROUPREADABLEAUTHINFOFILE",
+#endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
+#if _FFR_HANDLE_ISO8859_GECOS
+/* Peter Eriksson of Linkopings universitet */
+ "_FFR_HANDLE_ISO8859_GECOS",
+#endif /* _FFR_HANDLE_ISO8859_GECOS */
+#if _FFR_HDR_TYPE
+ "_FFR_HDR_TYPE",
+#endif /* _FFR_HDR_TYPE */
+#if _FFR_HPUX_NSSWITCH
+ "_FFR_HPUX_NSSWITCH",
+#endif /* _FFR_HPUX_NSSWITCH */
+#if _FFR_IGNORE_EXT_ON_HELO
+ "_FFR_IGNORE_EXT_ON_HELO",
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+#if _FFR_LDAP_RECURSION
+/* Andrew Baucom */
+ "_FFR_LDAP_RECURSION",
+#endif /* _FFR_LDAP_RECURSION */
+#if _FFR_LDAP_SETVERSION
+ "_FFR_LDAP_SETVERSION",
+#endif /* _FFR_LDAP_SETVERSION */
+#if _FFR_LDAP_URI
+ "_FFR_LDAP_URI",
+#endif /* _FFR_LDAP_URI */
+#if _FFR_MAX_FORWARD_ENTRIES
+/* Randall S. Winchester of the University of Maryland */
+ "_FFR_MAX_FORWARD_ENTRIES",
+#endif /* _FFR_MAX_FORWARD_ENTRIES */
+#if MILTER
+# if _FFR_MILTER_PERDAEMON
+ "_FFR_MILTER_PERDAEMON",
+# endif /* _FFR_MILTER_PERDAEMON */
+#endif /* MILTER */
+#if _FFR_NODELAYDSN_ON_HOLD
+/* Steven Pitzl */
+ "_FFR_NODELAYDSN_ON_HOLD",
+#endif /* _FFR_NODELAYDSN_ON_HOLD */
+#if _FFR_NONSTOP_PERSISTENCE
+/* Suggested by Jan Krueger of digitalanswers communications consulting gmbh. */
+ "_FFR_NONSTOP_PERSISTENCE",
+#endif /* _FFR_NONSTOP_PERSISTENCE */
+#if _FFR_NO_PIPE
+ "_FFR_NO_PIPE",
+#endif /* _FFR_NO_PIPE */
+#if _FFR_QUARANTINE
+ "_FFR_QUARANTINE",
+#endif /* _FFR_QUARANTINE */
+#if _FFR_QUEUEDELAY
+ "_FFR_QUEUEDELAY",
+#endif /* _FFR_QUEUEDELAY */
+#if _FFR_QUEUE_GROUP_SORTORDER
+/* XXX: Still need to actually use qgrp->qg_sortorder */
+ "_FFR_QUEUE_GROUP_SORTORDER",
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+#if _FFR_QUEUE_MACRO
+ "_FFR_QUEUE_MACRO",
+#endif /* _FFR_QUEUE_MACRO */
+#if _FFR_QUEUE_RUN_PARANOIA
+ "_FFR_QUEUE_RUN_PARANOIA",
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+#if _FFR_QUEUE_SCHED_DBG
+ "_FFR_QUEUE_SCHED_DBG",
+#endif /* _FFR_QUEUE_SCHED_DBG */
+#if _FFR_REDIRECTEMPTY
+ "_FFR_REDIRECTEMPTY",
+#endif /* _FFR_REDIRECTEMPTY */
+#if _FFR_RESET_MACRO_GLOBALS
+ "_FFR_RESET_MACRO_GLOBALS",
+#endif /* _FFR_RESET_MACRO_GLOBALS */
+#if _FFR_RESPOND_ALL
+ /* in vacation */
+ "_FFR_RESPOND_ALL",
+#endif /* _FFR_RESPOND_ALL */
+#if _FFR_RHS
+ "_FFR_RHS",
+#endif /* _FFR_RHS */
+#if _FFR_SASL_OPT_M
+ "_FFR_SASL_OPT_M",
+#endif /* _FFR_SASL_OPT_M */
+#if _FFR_SELECT_SHM
+ "_FFR_SELECT_SHM",
+#endif /* _FFR_SELECT_SHM */
+#if _FFR_SHM_STATUS
+ "_FFR_SHM_STATUS",
+#endif /* _FFR_SHM_STATUS */
+#if _FFR_SMFI_OPENSOCKET
+ "_FFR_SMFI_OPENSOCKET",
+#endif /* _FFR_SMFI_OPENSOCKET */
+#if _FFR_SMTP_SSL
+ "_FFR_SMTP_SSL",
+#endif /* _FFR_SMTP_SSL */
+#if _FFR_SOFT_BOUNCE
+ "_FFR_SOFT_BOUNCE",
+#endif /* _FFR_SOFT_BOUNCE */
+#if _FFR_SPT_ALIGN
+/* Chris Adams of HiWAAY Informations Services */
+ "_FFR_SPT_ALIGN",
+#endif /* _FFR_SPT_ALIGN */
+#if _FFR_TIMERS
+ "_FFR_TIMERS",
+#endif /* _FFR_TIMERS */
+#if _FFR_TLS_1
+ "_FFR_TLS_1",
+#endif /* _FFR_TLS_1 */
+#if _FFR_TRUSTED_QF
+ "_FFR_TRUSTED_QF",
+#endif /* _FFR_TRUSTED_QF */
+#if _FFR_USE_SETLOGIN
+/* Peter Philipp */
+ "_FFR_USE_SETLOGIN",
+#endif /* _FFR_USE_SETLOGIN */
+ NULL
+};
+
diff --git a/contrib/sendmail/src/conf.h b/contrib/sendmail/src/conf.h
new file mode 100644
index 0000000..f14f3c4
--- /dev/null
+++ b/contrib/sendmail/src/conf.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ *
+ * $Id: conf.h,v 8.563 2002/06/04 02:13:50 geir Exp $
+ */
+
+/*
+** CONF.H -- All user-configurable parameters for sendmail
+**
+** Send updates to sendmail@Sendmail.ORG so they will be
+** included in the next release.
+*/
+
+/* $FreeBSD$ */
+
+#ifndef CONF_H
+#define CONF_H 1
+
+#ifdef __GNUC__
+struct rusage; /* forward declaration to get gcc to shut up in wait.h */
+#endif /* __GNUC__ */
+
+# include <sys/param.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# ifndef __QNX__
+/* in QNX this grabs bogus LOCK_* manifests */
+# include <sys/file.h>
+# endif /* ! __QNX__ */
+# include <sys/wait.h>
+# include <limits.h>
+# include <fcntl.h>
+# include <signal.h>
+# include <netdb.h>
+# include <pwd.h>
+# include <grp.h>
+
+/* make sure TOBUFSIZ isn't larger than system limit for size of exec() args */
+#ifdef ARG_MAX
+# if ARG_MAX > 4096
+# define SM_ARG_MAX 4096
+# else /* ARG_MAX > 4096 */
+# define SM_ARG_MAX ARG_MAX
+# endif /* ARG_MAX > 4096 */
+#else /* ARG_MAX */
+# define SM_ARG_MAX 4096
+#endif /* ARG_MAX */
+
+/**********************************************************************
+** Table sizes, etc....
+** There shouldn't be much need to change these....
+** If you do, be careful, none should be set anywhere near INT_MAX
+**********************************************************************/
+
+#define MAXLINE 2048 /* max line length */
+#define MAXNAME 256 /* max length of a name */
+#define MAXPV 256 /* max # of parms to mailers */
+#define MAXATOM 1000 /* max atoms per address */
+#define MAXRWSETS 200 /* max # of sets of rewriting rules */
+#define MAXPRIORITIES 25 /* max values for Precedence: field */
+#define MAXMXHOSTS 100 /* max # of MX records for one host */
+#define SMTPLINELIM 990 /* maximum SMTP line length */
+#define MAXKEY 128 /* maximum size of a database key */
+#define MEMCHUNKSIZE 1024 /* chunk size for memory allocation */
+#define MAXUSERENVIRON 100 /* max envars saved, must be >= 3 */
+#define MAXMAPSTACK 12 /* max # of stacked or sequenced maps */
+#if MILTER
+# define MAXFILTERS 25 /* max # of milter filters */
+# define MAXFILTERMACROS 50 /* max # of macros per milter cmd */
+#endif /* MILTER */
+#define MAXSMTPARGS 20 /* max # of ESMTP args for MAIL/RCPT */
+#define MAXTOCLASS 8 /* max # of message timeout classes */
+#define MAXRESTOTYPES 3 /* max # of resolver timeout types */
+#define MAXMIMEARGS 20 /* max args in Content-Type: */
+#define MAXMIMENESTING 20 /* max MIME multipart nesting */
+#define QUEUESEGSIZE 1000 /* increment for queue size */
+
+/*
+** MAXQFNAME == 2 (size of "qf", "df" prefix)
+** + 8 (base 60 encoded date, time & sequence number)
+** + 10 (base 10 encoded 32 bit process id)
+** + 1 (terminating NUL character).
+*/
+
+#define MAXQFNAME 21 /* max qf file name length + 1 */
+#define MACBUFSIZE 4096 /* max expanded macro buffer size */
+#define TOBUFSIZE SM_ARG_MAX /* max buffer to hold address list */
+#define MAXSHORTSTR 203 /* max short string length */
+#define MAXMACNAMELEN 25 /* max macro name length */
+#define MAXMACROID 0377 /* max macro id number */
+ /* Must match (BITMAPBITS - 1) */
+#ifndef MAXHDRSLEN
+# define MAXHDRSLEN (32 * 1024) /* max size of message headers */
+#endif /* ! MAXHDRSLEN */
+#define MAXDAEMONS 10 /* max number of ports to listen to */
+#ifndef MAXINTERFACES
+# define MAXINTERFACES 512 /* number of interfaces to probe */
+#endif /* MAXINTERFACES */
+#ifndef MAXSYMLINKS
+# define MAXSYMLINKS 32 /* max number of symlinks in a path */
+#endif /* ! MAXSYMLINKS */
+#define MAXLINKPATHLEN (MAXPATHLEN * MAXSYMLINKS) /* max link-expanded file */
+#define DATA_PROGRESS_TIMEOUT 300 /* how often to check DATA progress */
+#define ENHSCLEN 10 /* max len of enhanced status code */
+#define DEFAULT_MAX_RCPT 100 /* max number of RCPTs per envelope */
+#define MAXQUEUEGROUPS 50 /* max # of queue groups */
+ /* must be less than BITMAPBITS for DoQueueRun */
+#define MAXWORKGROUPS 50 /* max # of work groups */
+#define MAXFILESYS BITMAPBITS /* max # of queue file systems
+ * must be <= BITMAPBITS */
+#ifndef FILESYS_UPDATE_INTERVAL
+# define FILESYS_UPDATE_INTERVAL 300 /* how often to update FileSys table */
+#endif /* FILESYS_UPDATE_INTERVAL */
+
+#ifndef SM_DEFAULT_TTL
+# define SM_DEFAULT_TTL 3600 /* default TTL for services that don't have one */
+#endif /* SM_DEFAULT_TTL */
+
+#if SASL
+# ifndef AUTH_MECHANISMS
+# if STARTTLS
+# define AUTH_MECHANISMS "EXTERNAL GSSAPI KERBEROS_V4 DIGEST-MD5 CRAM-MD5"
+# else /* STARTTLS */
+# define AUTH_MECHANISMS "GSSAPI KERBEROS_V4 DIGEST-MD5 CRAM-MD5"
+# endif /* STARTTLS */
+# endif /* ! AUTH_MECHANISMS */
+#endif /* SASL */
+
+/*
+** Default database permissions (alias, maps, etc.)
+** Used by sendmail and libsmdb
+*/
+
+#ifndef DBMMODE
+# define DBMMODE 0640
+#endif /* ! DBMMODE */
+
+
+/**********************************************************************
+** Compilation options.
+** #define these to 1 if they are available;
+** #define them to 0 otherwise.
+** All can be overridden from Makefile.
+**********************************************************************/
+
+#ifndef NETINET
+# define NETINET 1 /* include internet support */
+#endif /* ! NETINET */
+
+#ifndef NETINET6
+# define NETINET6 0 /* do not include IPv6 support */
+#endif /* ! NETINET6 */
+
+#ifndef NETISO
+# define NETISO 0 /* do not include ISO socket support */
+#endif /* ! NETISO */
+
+#ifndef NAMED_BIND
+# define NAMED_BIND 1 /* use Berkeley Internet Domain Server */
+#endif /* ! NAMED_BIND */
+
+#ifndef XDEBUG
+# define XDEBUG 1 /* enable extended debugging */
+#endif /* ! XDEBUG */
+
+#ifndef MATCHGECOS
+# define MATCHGECOS 1 /* match user names from gecos field */
+#endif /* ! MATCHGECOS */
+
+#ifndef DSN
+# define DSN 1 /* include delivery status notification code */
+#endif /* ! DSN */
+
+#if !defined(USERDB) && (defined(NEWDB) || defined(HESIOD))
+# define USERDB 1 /* look in user database */
+#endif /* !defined(USERDB) && (defined(NEWDB) || defined(HESIOD)) */
+
+#ifndef MIME8TO7
+# define MIME8TO7 1 /* 8->7 bit MIME conversions */
+#endif /* ! MIME8TO7 */
+
+#ifndef MIME7TO8
+# define MIME7TO8 1 /* 7->8 bit MIME conversions */
+#endif /* ! MIME7TO8 */
+
+#if NAMED_BIND
+# ifndef DNSMAP
+# define DNSMAP 1 /* DNS map type */
+# endif /* ! DNSMAP */
+#endif /* NAMED_BIND */
+
+#ifndef PIPELINING
+# define PIPELINING 1 /* SMTP PIPELINING */
+#endif /* PIPELINING */
+
+/**********************************************************************
+** End of site-specific configuration.
+**********************************************************************/
+
+#include <sm/conf.h>
+
+#endif /* ! CONF_H */
diff --git a/contrib/sendmail/src/control.c b/contrib/sendmail/src/control.c
new file mode 100644
index 0000000..88ff72f
--- /dev/null
+++ b/contrib/sendmail/src/control.c
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: control.c,v 8.118 2002/03/19 00:23:27 gshapiro Exp $")
+
+/* values for cmd_code */
+#define CMDERROR 0 /* bad command */
+#define CMDRESTART 1 /* restart daemon */
+#define CMDSHUTDOWN 2 /* end daemon */
+#define CMDHELP 3 /* help */
+#define CMDSTATUS 4 /* daemon status */
+#define CMDMEMDUMP 5 /* dump memory, to find memory leaks */
+#if _FFR_CONTROL_MSTAT
+# define CMDMSTAT 6 /* daemon status, more info, tagged data */
+#endif /* _FFR_CONTROL_MSTAT */
+
+struct cmd
+{
+ char *cmd_name; /* command name */
+ int cmd_code; /* internal code, see below */
+};
+
+static struct cmd CmdTab[] =
+{
+ { "help", CMDHELP },
+ { "restart", CMDRESTART },
+ { "shutdown", CMDSHUTDOWN },
+ { "status", CMDSTATUS },
+ { "memdump", CMDMEMDUMP },
+#if _FFR_CONTROL_MSTAT
+ { "mstat", CMDMSTAT },
+#endif /* _FFR_CONTROL_MSTAT */
+ { NULL, CMDERROR }
+};
+
+
+
+int ControlSocket = -1;
+
+/*
+** OPENCONTROLSOCKET -- create/open the daemon control named socket
+**
+** Creates and opens a named socket for external control over
+** the sendmail daemon.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** 0 if successful, -1 otherwise
+*/
+
+int
+opencontrolsocket()
+{
+# if NETUNIX
+ int save_errno;
+ int rval;
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
+ struct sockaddr_un controladdr;
+
+ if (ControlSocketName == NULL || *ControlSocketName == '\0')
+ return 0;
+
+ if (strlen(ControlSocketName) >= sizeof controladdr.sun_path)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ rval = safefile(ControlSocketName, RunAsUid, RunAsGid, RunAsUserName,
+ sff, S_IRUSR|S_IWUSR, NULL);
+
+ /* if not safe, don't create */
+ if (rval != 0)
+ {
+ errno = rval;
+ return -1;
+ }
+
+ ControlSocket = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (ControlSocket < 0)
+ return -1;
+
+ (void) unlink(ControlSocketName);
+ memset(&controladdr, '\0', sizeof controladdr);
+ controladdr.sun_family = AF_UNIX;
+ (void) sm_strlcpy(controladdr.sun_path, ControlSocketName,
+ sizeof controladdr.sun_path);
+
+ if (bind(ControlSocket, (struct sockaddr *) &controladdr,
+ sizeof controladdr) < 0)
+ {
+ save_errno = errno;
+ clrcontrol();
+ errno = save_errno;
+ return -1;
+ }
+
+ if (geteuid() == 0)
+ {
+ uid_t u = 0;
+
+ if (RunAsUid != 0)
+ u = RunAsUid;
+ else if (TrustedUid != 0)
+ u = TrustedUid;
+
+ if (u != 0 &&
+ chown(ControlSocketName, u, -1) < 0)
+ {
+ save_errno = errno;
+ sm_syslog(LOG_ALERT, NOQID,
+ "ownership change on %s to uid %d failed: %s",
+ ControlSocketName, (int) u,
+ sm_errstring(save_errno));
+ message("050 ownership change on %s to uid %d failed: %s",
+ ControlSocketName, (int) u,
+ sm_errstring(save_errno));
+ closecontrolsocket(true);
+ errno = save_errno;
+ return -1;
+ }
+ }
+
+ if (chmod(ControlSocketName, S_IRUSR|S_IWUSR) < 0)
+ {
+ save_errno = errno;
+ closecontrolsocket(true);
+ errno = save_errno;
+ return -1;
+ }
+
+ if (listen(ControlSocket, 8) < 0)
+ {
+ save_errno = errno;
+ closecontrolsocket(true);
+ errno = save_errno;
+ return -1;
+ }
+# endif /* NETUNIX */
+ return 0;
+}
+/*
+** CLOSECONTROLSOCKET -- close the daemon control named socket
+**
+** Close a named socket.
+**
+** Parameters:
+** fullclose -- if set, close the socket and remove it;
+** otherwise, just remove it
+**
+** Returns:
+** none.
+*/
+
+void
+closecontrolsocket(fullclose)
+ bool fullclose;
+{
+# if NETUNIX
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
+
+ if (ControlSocket >= 0)
+ {
+ int rval;
+
+ if (fullclose)
+ {
+ (void) close(ControlSocket);
+ ControlSocket = -1;
+ }
+
+ rval = safefile(ControlSocketName, RunAsUid, RunAsGid,
+ RunAsUserName, sff, S_IRUSR|S_IWUSR, NULL);
+
+ /* if not safe, don't unlink */
+ if (rval != 0)
+ return;
+
+ if (unlink(ControlSocketName) < 0)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "Could not remove control socket: %s",
+ sm_errstring(errno));
+ return;
+ }
+ }
+# endif /* NETUNIX */
+ return;
+}
+/*
+** CLRCONTROL -- reset the control connection
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** releases any resources used by the control interface.
+*/
+
+void
+clrcontrol()
+{
+# if NETUNIX
+ if (ControlSocket >= 0)
+ (void) close(ControlSocket);
+ ControlSocket = -1;
+# endif /* NETUNIX */
+}
+/*
+** CONTROL_COMMAND -- read and process command from named socket
+**
+** Read and process the command from the opened socket.
+** Exits when done since it is running in a forked child.
+**
+** Parameters:
+** sock -- the opened socket from getrequests()
+** e -- the current envelope
+**
+** Returns:
+** none.
+*/
+
+static jmp_buf CtxControlTimeout;
+
+/* ARGSUSED0 */
+static void
+controltimeout(timeout)
+ time_t timeout;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxControlTimeout, 1);
+}
+
+void
+control_command(sock, e)
+ int sock;
+ ENVELOPE *e;
+{
+ volatile int exitstat = EX_OK;
+ SM_FILE_T *s = NULL;
+ SM_EVENT *ev = NULL;
+ SM_FILE_T *traffic;
+ SM_FILE_T *oldout;
+ char *cmd;
+ char *p;
+ struct cmd *c;
+ char cmdbuf[MAXLINE];
+ char inp[MAXLINE];
+
+ sm_setproctitle(false, e, "control cmd read");
+
+ if (TimeOuts.to_control > 0)
+ {
+ /* handle possible input timeout */
+ if (setjmp(CtxControlTimeout) != 0)
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "timeout waiting for input during control command");
+ exit(EX_IOERR);
+ }
+ ev = sm_setevent(TimeOuts.to_control, controltimeout,
+ TimeOuts.to_control);
+ }
+
+ s = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &sock,
+ SM_IO_RDWR, NULL);
+ if (s == NULL)
+ {
+ int save_errno = errno;
+
+ (void) close(sock);
+ errno = save_errno;
+ exit(EX_IOERR);
+ }
+ (void) sm_io_setvbuf(s, SM_TIME_DEFAULT, NULL,
+ SM_IO_NBF, SM_IO_BUFSIZ);
+
+ if (sm_io_fgets(s, SM_TIME_DEFAULT, inp, sizeof inp) == NULL)
+ {
+ (void) sm_io_close(s, SM_TIME_DEFAULT);
+ exit(EX_IOERR);
+ }
+ (void) sm_io_flush(s, SM_TIME_DEFAULT);
+
+ /* clean up end of line */
+ fixcrlf(inp, true);
+
+ sm_setproctitle(false, e, "control: %s", inp);
+
+ /* break off command */
+ for (p = inp; isascii(*p) && isspace(*p); p++)
+ continue;
+ cmd = cmdbuf;
+ while (*p != '\0' &&
+ !(isascii(*p) && isspace(*p)) &&
+ cmd < &cmdbuf[sizeof cmdbuf - 2])
+ *cmd++ = *p++;
+ *cmd = '\0';
+
+ /* throw away leading whitespace */
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* decode command */
+ for (c = CmdTab; c->cmd_name != NULL; c++)
+ {
+ if (sm_strcasecmp(c->cmd_name, cmdbuf) == 0)
+ break;
+ }
+
+ switch (c->cmd_code)
+ {
+ case CMDHELP: /* get help */
+ traffic = TrafficLogFile;
+ TrafficLogFile = NULL;
+ oldout = OutChannel;
+ OutChannel = s;
+ help("control", e);
+ TrafficLogFile = traffic;
+ OutChannel = oldout;
+ break;
+
+ case CMDRESTART: /* restart the daemon */
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n");
+ exitstat = EX_RESTART;
+ break;
+
+ case CMDSHUTDOWN: /* kill the daemon */
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n");
+ exitstat = EX_SHUTDOWN;
+ break;
+
+ case CMDSTATUS: /* daemon status */
+ proc_list_probe();
+ {
+ int qgrp;
+ long bsize;
+ long free;
+
+ /* XXX need to deal with different partitions */
+ qgrp = e->e_qgrp;
+ if (!ISVALIDQGRP(qgrp))
+ qgrp = 0;
+ free = freediskspace(Queue[qgrp]->qg_qdir, &bsize);
+
+ /*
+ ** Prevent overflow and don't lose
+ ** precision (if bsize == 512)
+ */
+
+ if (free > 0)
+ free = (long)((double) free *
+ ((double) bsize / 1024));
+
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "%d/%d/%ld/%d\r\n",
+ CurChildren, MaxChildren,
+ free, getla());
+ }
+ proc_list_display(s, "");
+ break;
+
+# if _FFR_CONTROL_MSTAT
+ case CMDMSTAT: /* daemon status, extended, tagged format */
+ proc_list_probe();
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "C:%d\r\nM:%d\r\nL:%d\r\n",
+ CurChildren, MaxChildren,
+ getla());
+ printnqe(s, "Q:");
+ disk_status(s, "D:");
+ proc_list_display(s, "P:");
+ break;
+# endif /* _FFR_CONTROL_MSTAT */
+
+ case CMDMEMDUMP: /* daemon memory dump, to find memory leaks */
+# if SM_HEAP_CHECK
+ /* dump the heap, if we are checking for memory leaks */
+ if (sm_debug_active(&SmHeapCheck, 2))
+ {
+ sm_heap_report(s, sm_debug_level(&SmHeapCheck) - 1);
+ }
+ else
+ {
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "Memory dump unavailable.\r\n");
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "To fix, run sendmail with -dsm_check_heap.4\r\n");
+ }
+# else /* SM_HEAP_CHECK */
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "Memory dump unavailable.\r\n");
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "To fix, rebuild with -DSM_HEAP_CHECK\r\n");
+# endif /* SM_HEAP_CHECK */
+ break;
+
+ case CMDERROR: /* unknown command */
+ (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
+ "Bad command (%s)\r\n", cmdbuf);
+ break;
+ }
+ (void) sm_io_close(s, SM_TIME_DEFAULT);
+ if (ev != NULL)
+ sm_clrevent(ev);
+ exit(exitstat);
+}
diff --git a/contrib/sendmail/src/convtime.c b/contrib/sendmail/src/convtime.c
new file mode 100644
index 0000000..36edc1a
--- /dev/null
+++ b/contrib/sendmail/src/convtime.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: convtime.c,v 8.39 2001/09/11 04:05:13 gshapiro Exp $")
+
+/*
+** CONVTIME -- convert time
+**
+** Takes a time as an ascii string with a trailing character
+** giving units:
+** s -- seconds
+** m -- minutes
+** h -- hours
+** d -- days (default)
+** w -- weeks
+** For example, "3d12h" is three and a half days.
+**
+** Parameters:
+** p -- pointer to ascii time.
+** units -- default units if none specified.
+**
+** Returns:
+** time in seconds.
+**
+** Side Effects:
+** none.
+*/
+
+time_t
+convtime(p, units)
+ char *p;
+ int units;
+{
+ register time_t t, r;
+ register char c;
+ bool pos = true;
+
+ r = 0;
+ if (sm_strcasecmp(p, "now") == 0)
+ return NOW;
+ if (*p == '-')
+ {
+ pos = false;
+ ++p;
+ }
+ while (*p != '\0')
+ {
+ t = 0;
+ while ((c = *p++) != '\0' && isascii(c) && isdigit(c))
+ t = t * 10 + (c - '0');
+ if (c == '\0')
+ {
+ c = units;
+ p--;
+ }
+ else if (strchr("wdhms", c) == NULL)
+ {
+ usrerr("Invalid time unit `%c'", c);
+ c = units;
+ }
+ switch (c)
+ {
+ case 'w': /* weeks */
+ t *= 7;
+ /* FALLTHROUGH */
+
+ case 'd': /* days */
+ /* FALLTHROUGH */
+ default:
+ t *= 24;
+ /* FALLTHROUGH */
+
+ case 'h': /* hours */
+ t *= 60;
+ /* FALLTHROUGH */
+
+ case 'm': /* minutes */
+ t *= 60;
+ /* FALLTHROUGH */
+
+ case 's': /* seconds */
+ break;
+ }
+ r += t;
+ }
+
+ return pos ? r : -r;
+}
+/*
+** PINTVL -- produce printable version of a time interval
+**
+** Parameters:
+** intvl -- the interval to be converted
+** brief -- if true, print this in an extremely compact form
+** (basically used for logging).
+**
+** Returns:
+** A pointer to a string version of intvl suitable for
+** printing or framing.
+**
+** Side Effects:
+** none.
+**
+** Warning:
+** The string returned is in a static buffer.
+*/
+
+#define PLURAL(n) ((n) == 1 ? "" : "s")
+
+char *
+pintvl(intvl, brief)
+ time_t intvl;
+ bool brief;
+{
+ static char buf[256];
+ register char *p;
+ int wk, dy, hr, mi, se;
+
+ if (intvl == 0 && !brief)
+ return "zero seconds";
+ if (intvl == NOW)
+ return "too long";
+
+ /* decode the interval into weeks, days, hours, minutes, seconds */
+ se = intvl % 60;
+ intvl /= 60;
+ mi = intvl % 60;
+ intvl /= 60;
+ hr = intvl % 24;
+ intvl /= 24;
+ if (brief)
+ {
+ dy = intvl;
+ wk = 0;
+ }
+ else
+ {
+ dy = intvl % 7;
+ intvl /= 7;
+ wk = intvl;
+ }
+
+ /* now turn it into a sexy form */
+ p = buf;
+ if (brief)
+ {
+ if (dy > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "%d+", dy);
+ p += strlen(p);
+ }
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "%02d:%02d:%02d",
+ hr, mi, se);
+ return buf;
+ }
+
+ /* use the verbose form */
+ if (wk > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), ", %d week%s", wk,
+ PLURAL(wk));
+ p += strlen(p);
+ }
+ if (dy > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), ", %d day%s", dy,
+ PLURAL(dy));
+ p += strlen(p);
+ }
+ if (hr > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), ", %d hour%s", hr,
+ PLURAL(hr));
+ p += strlen(p);
+ }
+ if (mi > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), ", %d minute%s", mi,
+ PLURAL(mi));
+ p += strlen(p);
+ }
+ if (se > 0)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), ", %d second%s", se,
+ PLURAL(se));
+ p += strlen(p);
+ }
+
+ return (buf + 2);
+}
diff --git a/contrib/sendmail/src/daemon.c b/contrib/sendmail/src/daemon.c
new file mode 100644
index 0000000..28e96ff
--- /dev/null
+++ b/contrib/sendmail/src/daemon.c
@@ -0,0 +1,4363 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: daemon.c,v 8.613 2002/06/05 21:26:35 gshapiro Exp $")
+
+#if defined(SOCK_STREAM) || defined(__GNU_LIBRARY__)
+# define USE_SOCK_STREAM 1
+#endif /* defined(SOCK_STREAM) || defined(__GNU_LIBRARY__) */
+
+#if defined(USE_SOCK_STREAM)
+# if NETINET || NETINET6
+# include <arpa/inet.h>
+# endif /* NETINET || NETINET6 */
+# if NAMED_BIND
+# ifndef NO_DATA
+# define NO_DATA NO_ADDRESS
+# endif /* ! NO_DATA */
+# endif /* NAMED_BIND */
+#endif /* defined(USE_SOCK_STREAM) */
+
+#if STARTTLS
+# include <openssl/rand.h>
+#endif /* STARTTLS */
+
+#include <sys/time.h>
+
+#if IP_SRCROUTE && NETINET
+# include <netinet/in_systm.h>
+# include <netinet/ip.h>
+# if HAS_IN_H
+# include <netinet/in.h>
+# ifndef IPOPTION
+# define IPOPTION ip_opts
+# define IP_LIST ip_opts
+# define IP_DST ip_dst
+# endif /* ! IPOPTION */
+# else /* HAS_IN_H */
+# include <netinet/ip_var.h>
+# ifndef IPOPTION
+# define IPOPTION ipoption
+# define IP_LIST ipopt_list
+# define IP_DST ipopt_dst
+# endif /* ! IPOPTION */
+# endif /* HAS_IN_H */
+#endif /* IP_SRCROUTE && NETINET */
+
+#include <sm/fdset.h>
+
+/* structure to describe a daemon or a client */
+struct daemon
+{
+ int d_socket; /* fd for socket */
+ SOCKADDR d_addr; /* socket for incoming */
+ unsigned short d_port; /* port number */
+ int d_listenqueue; /* size of listen queue */
+ int d_tcprcvbufsize; /* size of TCP receive buffer */
+ int d_tcpsndbufsize; /* size of TCP send buffer */
+ time_t d_refuse_connections_until;
+ bool d_firsttime;
+ int d_socksize;
+ BITMAP256 d_flags; /* flags; see sendmail.h */
+ char *d_mflags; /* flags for use in macro */
+ char *d_name; /* user-supplied name */
+#if MILTER
+# if _FFR_MILTER_PERDAEMON
+ char *d_inputfilterlist;
+ struct milter *d_inputfilters[MAXFILTERS];
+# endif /* _FFR_MILTER_PERDAEMON */
+#endif /* MILTER */
+};
+
+typedef struct daemon DAEMON_T;
+
+static void connecttimeout __P((void));
+static int opendaemonsocket __P((DAEMON_T *, bool));
+static unsigned short setupdaemon __P((SOCKADDR *));
+static void getrequests_checkdiskspace __P((ENVELOPE *e));
+
+/*
+** DAEMON.C -- routines to use when running as a daemon.
+**
+** This entire file is highly dependent on the 4.2 BSD
+** interprocess communication primitives. No attempt has
+** been made to make this file portable to Version 7,
+** Version 6, MPX files, etc. If you should try such a
+** thing yourself, I recommend chucking the entire file
+** and starting from scratch. Basic semantics are:
+**
+** getrequests(e)
+** Opens a port and initiates a connection.
+** Returns in a child. Must set InChannel and
+** OutChannel appropriately.
+** clrdaemon()
+** Close any open files associated with getting
+** the connection; this is used when running the queue,
+** etc., to avoid having extra file descriptors during
+** the queue run and to avoid confusing the network
+** code (if it cares).
+** makeconnection(host, port, mci, e, enough)
+** Make a connection to the named host on the given
+** port. Returns zero on success, else an exit status
+** describing the error.
+** host_map_lookup(map, hbuf, avp, pstat)
+** Convert the entry in hbuf into a canonical form.
+*/
+
+static DAEMON_T Daemons[MAXDAEMONS];
+static int NDaemons = 0; /* actual number of daemons */
+
+static time_t NextDiskSpaceCheck = 0;
+
+/*
+** GETREQUESTS -- open mail IPC port and get requests.
+**
+** Parameters:
+** e -- the current envelope.
+**
+** Returns:
+** pointer to flags.
+**
+** Side Effects:
+** Waits until some interesting activity occurs. When
+** it does, a child is created to process it, and the
+** parent waits for completion. Return from this
+** routine is always in the child. The file pointers
+** "InChannel" and "OutChannel" should be set to point
+** to the communication channel.
+** May restart persistent queue runners if they have ended
+** for some reason.
+*/
+
+BITMAP256 *
+getrequests(e)
+ ENVELOPE *e;
+{
+ int t;
+ int idx, curdaemon = -1;
+ int i, olddaemon = 0;
+#if XDEBUG
+ bool j_has_dot;
+#endif /* XDEBUG */
+ char status[MAXLINE];
+ SOCKADDR sa;
+ SOCKADDR_LEN_T len = sizeof sa;
+#if _FFR_QUEUE_RUN_PARANOIA
+ time_t lastrun;
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+# if NETUNIX
+ extern int ControlSocket;
+# endif /* NETUNIX */
+ extern ENVELOPE BlankEnvelope;
+ extern bool refuseconnections __P((char *, ENVELOPE *, int, bool));
+
+
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ Daemons[idx].d_port = setupdaemon(&(Daemons[idx].d_addr));
+ Daemons[idx].d_firsttime = true;
+ Daemons[idx].d_refuse_connections_until = (time_t) 0;
+ }
+
+ /*
+ ** Try to actually open the connection.
+ */
+
+ if (tTd(15, 1))
+ {
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ sm_dprintf("getrequests: daemon %s: port %d\n",
+ Daemons[idx].d_name,
+ ntohs(Daemons[idx].d_port));
+ }
+ }
+
+ /* get a socket for the SMTP connection */
+ for (idx = 0; idx < NDaemons; idx++)
+ Daemons[idx].d_socksize = opendaemonsocket(&Daemons[idx], true);
+
+ if (opencontrolsocket() < 0)
+ sm_syslog(LOG_WARNING, NOQID,
+ "daemon could not open control socket %s: %s",
+ ControlSocketName, sm_errstring(errno));
+
+ /* If there are any queue runners released reapchild() co-ord's */
+ (void) sm_signal(SIGCHLD, reapchild);
+
+ /* write the pid to file, command line args to syslog */
+ log_sendmail_pid(e);
+
+#if XDEBUG
+ {
+ char jbuf[MAXHOSTNAMELEN];
+
+ expand("\201j", jbuf, sizeof jbuf, e);
+ j_has_dot = strchr(jbuf, '.') != NULL;
+ }
+#endif /* XDEBUG */
+
+ /* Add parent process as first item */
+ proc_list_add(CurrentPid, "Sendmail daemon", PROC_DAEMON, 0, -1);
+
+ if (tTd(15, 1))
+ {
+ for (idx = 0; idx < NDaemons; idx++)
+ sm_dprintf("getrequests: daemon %s: %d\n",
+ Daemons[idx].d_name,
+ Daemons[idx].d_socket);
+ }
+
+ for (;;)
+ {
+ register pid_t pid;
+ auto SOCKADDR_LEN_T lotherend;
+ bool timedout = false;
+ bool control = false;
+ int save_errno;
+ int pipefd[2];
+ time_t now;
+#if STARTTLS
+ long seed;
+#endif /* STARTTLS */
+
+ /* see if we are rejecting connections */
+ (void) sm_blocksignal(SIGALRM);
+
+ if (ShutdownRequest != NULL)
+ shutdown_daemon();
+ else if (RestartRequest != NULL)
+ restart_daemon();
+ else if (RestartWorkGroup)
+ restart_marked_work_groups();
+
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ /*
+ ** XXX do this call outside the loop?
+ ** no: refuse_connections may sleep().
+ */
+
+ now = curtime();
+ if (now < Daemons[idx].d_refuse_connections_until)
+ continue;
+ if (bitnset(D_DISABLE, Daemons[idx].d_flags))
+ continue;
+ if (refuseconnections(Daemons[idx].d_name, e, idx,
+ curdaemon == idx))
+ {
+ if (Daemons[idx].d_socket >= 0)
+ {
+ /* close socket so peer fails quickly */
+ (void) close(Daemons[idx].d_socket);
+ Daemons[idx].d_socket = -1;
+ }
+
+ /* refuse connections for next 15 seconds */
+ Daemons[idx].d_refuse_connections_until = now + 15;
+ }
+ else if (Daemons[idx].d_socket < 0 ||
+ Daemons[idx].d_firsttime)
+ {
+ if (!Daemons[idx].d_firsttime && LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "accepting connections again for daemon %s",
+ Daemons[idx].d_name);
+
+ /* arrange to (re)open the socket if needed */
+ (void) opendaemonsocket(&Daemons[idx], false);
+ Daemons[idx].d_firsttime = false;
+ }
+ }
+
+ /* May have been sleeping above, check again */
+ if (ShutdownRequest != NULL)
+ shutdown_daemon();
+ else if (RestartRequest != NULL)
+ restart_daemon();
+ else if (RestartWorkGroup)
+ restart_marked_work_groups();
+
+ getrequests_checkdiskspace(e);
+
+#if XDEBUG
+ /* check for disaster */
+ {
+ char jbuf[MAXHOSTNAMELEN];
+
+ expand("\201j", jbuf, sizeof jbuf, e);
+ if (!wordinclass(jbuf, 'w'))
+ {
+ dumpstate("daemon lost $j");
+ sm_syslog(LOG_ALERT, NOQID,
+ "daemon process doesn't have $j in $=w; see syslog");
+ abort();
+ }
+ else if (j_has_dot && strchr(jbuf, '.') == NULL)
+ {
+ dumpstate("daemon $j lost dot");
+ sm_syslog(LOG_ALERT, NOQID,
+ "daemon process $j lost dot; see syslog");
+ abort();
+ }
+ }
+#endif /* XDEBUG */
+
+#if 0
+ /*
+ ** Andrew Sun <asun@ieps-sun.ml.com> claims that this will
+ ** fix the SVr4 problem. But it seems to have gone away,
+ ** so is it worth doing this?
+ */
+
+ if (DaemonSocket >= 0 &&
+ SetNonBlocking(DaemonSocket, false) < 0)
+ log an error here;
+#endif /* 0 */
+ (void) sm_releasesignal(SIGALRM);
+
+ for (;;)
+ {
+ bool setproc = false;
+ int highest = -1;
+ fd_set readfds;
+ struct timeval timeout;
+
+ if (ShutdownRequest != NULL)
+ shutdown_daemon();
+ else if (RestartRequest != NULL)
+ restart_daemon();
+ else if (RestartWorkGroup)
+ restart_marked_work_groups();
+
+ FD_ZERO(&readfds);
+
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ /* wait for a connection */
+ if (Daemons[idx].d_socket >= 0)
+ {
+ if (!setproc &&
+ !bitnset(D_ETRNONLY,
+ Daemons[idx].d_flags))
+ {
+ sm_setproctitle(true, e,
+ "accepting connections");
+ setproc = true;
+ }
+ if (Daemons[idx].d_socket > highest)
+ highest = Daemons[idx].d_socket;
+ SM_FD_SET(Daemons[idx].d_socket,
+ &readfds);
+ }
+ }
+
+#if NETUNIX
+ if (ControlSocket >= 0)
+ {
+ if (ControlSocket > highest)
+ highest = ControlSocket;
+ SM_FD_SET(ControlSocket, &readfds);
+ }
+#endif /* NETUNIX */
+
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+
+ t = select(highest + 1, FDSET_CAST &readfds,
+ NULL, NULL, &timeout);
+
+ /* Did someone signal while waiting? */
+ if (ShutdownRequest != NULL)
+ shutdown_daemon();
+ else if (RestartRequest != NULL)
+ restart_daemon();
+ else if (RestartWorkGroup)
+ restart_marked_work_groups();
+
+
+
+ curdaemon = -1;
+ if (doqueuerun())
+ {
+ (void) runqueue(true, false, false, false);
+#if _FFR_QUEUE_RUN_PARANOIA
+ lastrun = now;
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+ }
+#if _FFR_QUEUE_RUN_PARANOIA
+ else if (QueueIntvl > 0 &&
+ lastrun + QueueIntvl + 60 < now)
+ {
+
+ /*
+ ** set lastrun unconditionally to avoid
+ ** calling checkqueuerunner() all the time.
+ ** That's also why we currently ignore the
+ ** result of the function call.
+ */
+
+ (void) checkqueuerunner();
+ lastrun = now;
+ }
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+
+ if (t <= 0)
+ {
+ timedout = true;
+ break;
+ }
+
+ control = false;
+ errno = 0;
+
+ /* look "round-robin" for an active socket */
+ if ((idx = olddaemon + 1) >= NDaemons)
+ idx = 0;
+ for (i = 0; i < NDaemons; i++)
+ {
+ if (Daemons[idx].d_socket >= 0 &&
+ SM_FD_ISSET(Daemons[idx].d_socket,
+ &readfds))
+ {
+ lotherend = Daemons[idx].d_socksize;
+ memset(&RealHostAddr, '\0',
+ sizeof RealHostAddr);
+ t = accept(Daemons[idx].d_socket,
+ (struct sockaddr *)&RealHostAddr,
+ &lotherend);
+
+ /*
+ ** If remote side closes before
+ ** accept() finishes, sockaddr
+ ** might not be fully filled in.
+ */
+
+ if (t >= 0 &&
+ (lotherend == 0 ||
+# ifdef BSD4_4_SOCKADDR
+ RealHostAddr.sa.sa_len == 0 ||
+# endif /* BSD4_4_SOCKADDR */
+ RealHostAddr.sa.sa_family != Daemons[idx].d_addr.sa.sa_family))
+ {
+ (void) close(t);
+ t = -1;
+ errno = EINVAL;
+ }
+ olddaemon = curdaemon = idx;
+ break;
+ }
+ if (++idx >= NDaemons)
+ idx = 0;
+ }
+#if NETUNIX
+ if (curdaemon == -1 && ControlSocket >= 0 &&
+ SM_FD_ISSET(ControlSocket, &readfds))
+ {
+ struct sockaddr_un sa_un;
+
+ lotherend = sizeof sa_un;
+ memset(&sa_un, '\0', sizeof sa_un);
+ t = accept(ControlSocket,
+ (struct sockaddr *)&sa_un,
+ &lotherend);
+
+ /*
+ ** If remote side closes before
+ ** accept() finishes, sockaddr
+ ** might not be fully filled in.
+ */
+
+ if (t >= 0 &&
+ (lotherend == 0 ||
+# ifdef BSD4_4_SOCKADDR
+ sa_un.sun_len == 0 ||
+# endif /* BSD4_4_SOCKADDR */
+ sa_un.sun_family != AF_UNIX))
+ {
+ (void) close(t);
+ t = -1;
+ errno = EINVAL;
+ }
+ if (t >= 0)
+ control = true;
+ }
+#else /* NETUNIX */
+ if (curdaemon == -1)
+ {
+ /* No daemon to service */
+ continue;
+ }
+#endif /* NETUNIX */
+ if (t >= 0 || errno != EINTR)
+ break;
+ }
+ if (timedout)
+ {
+ timedout = false;
+ continue;
+ }
+ save_errno = errno;
+ (void) sm_blocksignal(SIGALRM);
+ if (t < 0)
+ {
+ errno = save_errno;
+ syserr("getrequests: accept");
+
+ /* arrange to re-open the socket next time around */
+ (void) close(Daemons[curdaemon].d_socket);
+ Daemons[curdaemon].d_socket = -1;
+#if SO_REUSEADDR_IS_BROKEN
+ /*
+ ** Give time for bound socket to be released.
+ ** This creates a denial-of-service if you can
+ ** force accept() to fail on affected systems.
+ */
+
+ Daemons[curdaemon].d_refuse_connections_until = curtime() + 15;
+#endif /* SO_REUSEADDR_IS_BROKEN */
+ continue;
+ }
+
+ if (!control)
+ {
+ /* set some daemon related macros */
+ switch (Daemons[curdaemon].d_addr.sa.sa_family)
+ {
+ case AF_UNSPEC:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "unspec");
+ break;
+#if _FFR_DAEMON_NETUNIX
+# if NETUNIX
+ case AF_UNIX:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "local");
+ break;
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+#if NETINET
+ case AF_INET:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "inet");
+ break;
+#endif /* NETINET */
+#if NETINET6
+ case AF_INET6:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "inet6");
+ break;
+#endif /* NETINET6 */
+#if NETISO
+ case AF_ISO:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "iso");
+ break;
+#endif /* NETISO */
+#if NETNS
+ case AF_NS:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "ns");
+ break;
+#endif /* NETNS */
+#if NETX25
+ case AF_CCITT:
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_family}"), "x.25");
+ break;
+#endif /* NETX25 */
+ }
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_name}"),
+ Daemons[curdaemon].d_name);
+ if (Daemons[curdaemon].d_mflags != NULL)
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_flags}"),
+ Daemons[curdaemon].d_mflags);
+ else
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_flags}"), "");
+ }
+
+ /*
+ ** Create a subprocess to process the mail.
+ */
+
+ if (tTd(15, 2))
+ sm_dprintf("getrequests: forking (fd = %d)\n", t);
+
+ /*
+ ** Advance state of PRNG.
+ ** This is necessary because otherwise all child processes
+ ** will produce the same PRN sequence and hence the selection
+ ** of a queue directory (and other things, e.g., MX selection)
+ ** are not "really" random.
+ */
+#if STARTTLS
+ /* XXX get some better "random" data? */
+ seed = get_random();
+ RAND_seed((void *) &NextDiskSpaceCheck,
+ sizeof NextDiskSpaceCheck);
+ RAND_seed((void *) &now, sizeof now);
+ RAND_seed((void *) &seed, sizeof seed);
+#else /* STARTTLS */
+ (void) get_random();
+#endif /* STARTTLS */
+
+#if NAMED_BIND
+ /*
+ ** Update MX records for FallBackMX.
+ ** Let's hope this is fast otherwise we screw up the
+ ** response time.
+ */
+
+ if (FallBackMX != NULL)
+ (void) getfallbackmxrr(FallBackMX);
+#endif /* NAMED_BIND */
+
+#if !PROFILING
+ /*
+ ** Create a pipe to keep the child from writing to the
+ ** socket until after the parent has closed it. Otherwise
+ ** the parent may hang if the child has closed it first.
+ */
+
+ if (pipe(pipefd) < 0)
+ pipefd[0] = pipefd[1] = -1;
+
+ (void) sm_blocksignal(SIGCHLD);
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("daemon: cannot fork");
+ if (pipefd[0] != -1)
+ {
+ (void) close(pipefd[0]);
+ (void) close(pipefd[1]);
+ }
+ (void) sm_releasesignal(SIGCHLD);
+ (void) sleep(10);
+ (void) close(t);
+ continue;
+ }
+
+#else /* !PROFILING */
+ pid = 0;
+#endif /* !PROFILING */
+
+ if (pid == 0)
+ {
+ char *p;
+ SM_FILE_T *inchannel, *outchannel = NULL;
+
+ /*
+ ** CHILD -- return to caller.
+ ** Collect verified idea of sending host.
+ ** Verify calling user id if possible here.
+ */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+
+ (void) sm_releasesignal(SIGALRM);
+ (void) sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_DFL);
+ (void) sm_signal(SIGTERM, intsig);
+
+ /* turn on profiling */
+ /* SM_PROF(0); */
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ sm_exc_newthread(fatal_error);
+
+ if (!control)
+ {
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{daemon_addr}"),
+ anynet_ntoa(&Daemons[curdaemon].d_addr));
+ (void) sm_snprintf(status, sizeof status, "%d",
+ ntohs(Daemons[curdaemon].d_port));
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{daemon_port}"), status);
+ }
+
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ if (Daemons[idx].d_socket >= 0)
+ (void) close(Daemons[idx].d_socket);
+ Daemons[idx].d_socket = -1;
+ }
+ clrcontrol();
+
+ /* Avoid SMTP daemon actions if control command */
+ if (control)
+ {
+ /* Add control socket process */
+ proc_list_add(CurrentPid,
+ "console socket child",
+ PROC_CONTROL_CHILD, 0, -1);
+ }
+ else
+ {
+ proc_list_clear();
+
+ /* clean up background delivery children */
+ (void) sm_signal(SIGCHLD, reapchild);
+
+ /* Add parent process as first child item */
+ proc_list_add(CurrentPid, "daemon child",
+ PROC_DAEMON_CHILD, 0, -1);
+
+ /* don't schedule queue runs if ETRN */
+ QueueIntvl = 0;
+
+ sm_setproctitle(true, e, "startup with %s",
+ anynet_ntoa(&RealHostAddr));
+ }
+
+#if !PROFILING
+ if (pipefd[0] != -1)
+ {
+ auto char c;
+
+ /*
+ ** Wait for the parent to close the write end
+ ** of the pipe, which we will see as an EOF.
+ ** This guarantees that we won't write to the
+ ** socket until after the parent has closed
+ ** the pipe.
+ */
+
+ /* close the write end of the pipe */
+ (void) close(pipefd[1]);
+
+ /* we shouldn't be interrupted, but ... */
+ while (read(pipefd[0], &c, 1) < 0 &&
+ errno == EINTR)
+ continue;
+ (void) close(pipefd[0]);
+ }
+#endif /* !PROFILING */
+
+ /* control socket processing */
+ if (control)
+ {
+ control_command(t, e);
+ /* NOTREACHED */
+ exit(EX_SOFTWARE);
+ }
+
+ /* determine host name */
+ p = hostnamebyanyaddr(&RealHostAddr);
+ if (strlen(p) > MAXNAME) /* XXX - 1 ? */
+ p[MAXNAME] = '\0';
+ RealHostName = newstr(p);
+ if (RealHostName[0] == '[')
+ {
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{client_resolve}"),
+ h_errno == TRY_AGAIN ? "TEMP" : "FAIL");
+ }
+ else
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{client_resolve}"), "OK");
+ sm_setproctitle(true, e, "startup with %s", p);
+ markstats(e, NULL, STATS_CONNECT);
+
+ if ((inchannel = sm_io_open(SmFtStdiofd,
+ SM_TIME_DEFAULT,
+ (void *) &t,
+ SM_IO_RDONLY,
+ NULL)) == NULL ||
+ (t = dup(t)) < 0 ||
+ (outchannel = sm_io_open(SmFtStdiofd,
+ SM_TIME_DEFAULT,
+ (void *) &t,
+ SM_IO_WRONLY,
+ NULL)) == NULL)
+ {
+ syserr("cannot open SMTP server channel, fd=%d",
+ t);
+ finis(false, true, EX_OK);
+ }
+ sm_io_automode(inchannel, outchannel);
+
+ InChannel = inchannel;
+ OutChannel = outchannel;
+ DisConnected = false;
+
+#if XLA
+ if (!xla_host_ok(RealHostName))
+ {
+ message("421 4.4.5 Too many SMTP sessions for this host");
+ finis(false, true, EX_OK);
+ }
+#endif /* XLA */
+ /* find out name for interface of connection */
+ if (getsockname(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
+ NULL), &sa.sa, &len) == 0)
+ {
+ p = hostnamebyanyaddr(&sa);
+ if (tTd(15, 9))
+ sm_dprintf("getreq: got name %s\n", p);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{if_name}"), p);
+
+ /*
+ ** Do this only if it is not the loopback
+ ** interface.
+ */
+
+ if (!isloopback(sa))
+ {
+ char *addr;
+ char family[5];
+
+ addr = anynet_ntoa(&sa);
+ (void) sm_snprintf(family,
+ sizeof(family),
+ "%d", sa.sa.sa_family);
+ macdefine(&BlankEnvelope.e_macro,
+ A_TEMP,
+ macid("{if_addr}"), addr);
+ macdefine(&BlankEnvelope.e_macro,
+ A_TEMP,
+ macid("{if_family}"), family);
+ if (tTd(15, 7))
+ sm_dprintf("getreq: got addr %s and family %s\n",
+ addr, family);
+ }
+ else
+ {
+ macdefine(&BlankEnvelope.e_macro,
+ A_PERM,
+ macid("{if_addr}"), NULL);
+ macdefine(&BlankEnvelope.e_macro,
+ A_PERM,
+ macid("{if_family}"), NULL);
+ }
+ }
+ else
+ {
+ if (tTd(15, 7))
+ sm_dprintf("getreq: getsockname failed\n");
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_name}"), NULL);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_addr}"), NULL);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_family}"), NULL);
+ }
+ break;
+ }
+
+ /* parent -- keep track of children */
+ if (control)
+ {
+ (void) sm_snprintf(status, sizeof status,
+ "control socket server child");
+ proc_list_add(pid, status, PROC_CONTROL, 0, -1);
+ }
+ else
+ {
+ (void) sm_snprintf(status, sizeof status,
+ "SMTP server child for %s",
+ anynet_ntoa(&RealHostAddr));
+ proc_list_add(pid, status, PROC_DAEMON, 0, -1);
+ }
+ (void) sm_releasesignal(SIGCHLD);
+
+ /* close the read end of the synchronization pipe */
+ if (pipefd[0] != -1)
+ {
+ (void) close(pipefd[0]);
+ pipefd[0] = -1;
+ }
+
+ /* close the port so that others will hang (for a while) */
+ (void) close(t);
+
+ /* release the child by closing the read end of the sync pipe */
+ if (pipefd[1] != -1)
+ {
+ (void) close(pipefd[1]);
+ pipefd[1] = -1;
+ }
+ }
+ if (tTd(15, 2))
+ sm_dprintf("getreq: returning\n");
+
+#if MILTER
+# if _FFR_MILTER_PERDAEMON
+ /* set the filters for this daemon */
+ if (Daemons[curdaemon].d_inputfilterlist != NULL)
+ {
+ for (i = 0;
+ (Daemons[curdaemon].d_inputfilters[i] != NULL &&
+ i < MAXFILTERS);
+ i++)
+ {
+ InputFilters[i] = Daemons[curdaemon].d_inputfilters[i];
+ }
+ if (i < MAXFILTERS)
+ InputFilters[i] = NULL;
+ }
+# endif /* _FFR_MILTER_PERDAEMON */
+#endif /* MILTER */
+ return &Daemons[curdaemon].d_flags;
+}
+
+/*
+** GETREQUESTS_CHECKDISKSPACE -- check available diskspace.
+**
+** Parameters:
+** e -- envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Modifies Daemon flags (D_ETRNONLY) if not enough disk space.
+*/
+
+static void
+getrequests_checkdiskspace(e)
+ ENVELOPE *e;
+{
+ bool logged = false;
+ int idx;
+ time_t now;
+
+ now = curtime();
+ if (now < NextDiskSpaceCheck)
+ return;
+
+ /* Check if there is available disk space in all queue groups. */
+ if (!enoughdiskspace(0, NULL))
+ {
+ for (idx = 0; idx < NDaemons; ++idx)
+ {
+ if (bitnset(D_ETRNONLY, Daemons[idx].d_flags))
+ continue;
+
+ /* log only if not logged before */
+ if (!logged)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "rejecting new messages: min free: %ld",
+ MinBlocksFree);
+ sm_setproctitle(true, e,
+ "rejecting new messages: min free: %ld",
+ MinBlocksFree);
+ logged = true;
+ }
+ setbitn(D_ETRNONLY, Daemons[idx].d_flags);
+ }
+ }
+ else
+ {
+ for (idx = 0; idx < NDaemons; ++idx)
+ {
+ if (!bitnset(D_ETRNONLY, Daemons[idx].d_flags))
+ continue;
+
+ /* log only if not logged before */
+ if (!logged)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "accepting new messages (again)");
+ logged = true;
+ }
+
+ /* title will be set later */
+ clrbitn(D_ETRNONLY, Daemons[idx].d_flags);
+ }
+ }
+
+ /* only check disk space once a minute */
+ NextDiskSpaceCheck = now + 60;
+}
+
+/*
+** OPENDAEMONSOCKET -- open SMTP socket
+**
+** Deals with setting all appropriate options.
+**
+** Parameters:
+** d -- the structure for the daemon to open.
+** firsttime -- set if this is the initial open.
+**
+** Returns:
+** Size in bytes of the daemon socket addr.
+**
+** Side Effects:
+** Leaves DaemonSocket set to the open socket.
+** Exits if the socket cannot be created.
+*/
+
+#define MAXOPENTRIES 10 /* maximum number of tries to open connection */
+
+static int
+opendaemonsocket(d, firsttime)
+ DAEMON_T *d;
+ bool firsttime;
+{
+ int on = 1;
+ int fdflags;
+ SOCKADDR_LEN_T socksize = 0;
+ int ntries = 0;
+ int save_errno;
+
+ if (tTd(15, 2))
+ sm_dprintf("opendaemonsocket(%s)\n", d->d_name);
+
+ do
+ {
+ if (ntries > 0)
+ (void) sleep(5);
+ if (firsttime || d->d_socket < 0)
+ {
+#if _FFR_DAEMON_NETUNIX
+# if NETUNIX
+ if (d->d_addr.sa.sa_family == AF_UNIX)
+ {
+ int rval;
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_ROOTOK|SFF_EXECOK|SFF_CREAT;
+
+ /* if not safe, don't use it */
+ rval = safefile(d->d_addr.sunix.sun_path,
+ RunAsUid, RunAsGid,
+ RunAsUserName, sff,
+ S_IRUSR|S_IWUSR, NULL);
+ if (rval != 0)
+ {
+ save_errno = errno;
+ syserr("opendaemonsocket: daemon %s: unsafe domain socket %s",
+ d->d_name,
+ d->d_addr.sunix.sun_path);
+ goto fail;
+ }
+
+ /* Don't try to overtake an existing socket */
+ (void) unlink(d->d_addr.sunix.sun_path);
+ }
+# endif /* NETUNIX */
+#endif /* _FFR_DOMAIN_NETUNIX */
+ d->d_socket = socket(d->d_addr.sa.sa_family,
+ SOCK_STREAM, 0);
+ if (d->d_socket < 0)
+ {
+ save_errno = errno;
+ syserr("opendaemonsocket: daemon %s: can't create server SMTP socket",
+ d->d_name);
+ fail:
+ if (bitnset(D_OPTIONAL, d->d_flags) &&
+ (!transienterror(save_errno) ||
+ ntries >= MAXOPENTRIES - 1))
+ {
+ syserr("opendaemonsocket: daemon %s: optional socket disabled",
+ d->d_name);
+ setbitn(D_DISABLE, d->d_flags);
+ d->d_socket = -1;
+ return -1;
+ }
+ severe:
+ if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, NOQID,
+ "daemon %s: problem creating SMTP socket",
+ d->d_name);
+ d->d_socket = -1;
+ continue;
+ }
+
+ /* turn on network debugging? */
+ if (tTd(15, 101))
+ (void) setsockopt(d->d_socket, SOL_SOCKET,
+ SO_DEBUG, (char *)&on,
+ sizeof on);
+
+ (void) setsockopt(d->d_socket, SOL_SOCKET,
+ SO_REUSEADDR, (char *)&on, sizeof on);
+ (void) setsockopt(d->d_socket, SOL_SOCKET,
+ SO_KEEPALIVE, (char *)&on, sizeof on);
+
+#ifdef SO_RCVBUF
+ if (d->d_tcprcvbufsize > 0)
+ {
+ if (setsockopt(d->d_socket, SOL_SOCKET,
+ SO_RCVBUF,
+ (char *) &d->d_tcprcvbufsize,
+ sizeof(d->d_tcprcvbufsize)) < 0)
+ syserr("opendaemonsocket: daemon %s: setsockopt(SO_RCVBUF)", d->d_name);
+ }
+#endif /* SO_RCVBUF */
+#ifdef SO_SNDBUF
+ if (d->d_tcpsndbufsize > 0)
+ {
+ if (setsockopt(d->d_socket, SOL_SOCKET,
+ SO_SNDBUF,
+ (char *) &d->d_tcpsndbufsize,
+ sizeof(d->d_tcpsndbufsize)) < 0)
+ syserr("opendaemonsocket: daemon %s: setsockopt(SO_SNDBUF)", d->d_name);
+ }
+#endif /* SO_SNDBUF */
+
+ if ((fdflags = fcntl(d->d_socket, F_GETFD, 0)) == -1 ||
+ fcntl(d->d_socket, F_SETFD,
+ fdflags | FD_CLOEXEC) == -1)
+ {
+ save_errno = errno;
+ syserr("opendaemonsocket: daemon %s: failed to %s close-on-exec flag: %s",
+ d->d_name,
+ fdflags == -1 ? "get" : "set",
+ sm_errstring(save_errno));
+ (void) close(d->d_socket);
+ goto severe;
+ }
+
+ switch (d->d_addr.sa.sa_family)
+ {
+#if _FFR_DAEMON_NETUNIX
+# ifdef NETUNIX
+ case AF_UNIX:
+ socksize = sizeof d->d_addr.sunix;
+ break;
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+#if NETINET
+ case AF_INET:
+ socksize = sizeof d->d_addr.sin;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ socksize = sizeof d->d_addr.sin6;
+ break;
+#endif /* NETINET6 */
+
+#if NETISO
+ case AF_ISO:
+ socksize = sizeof d->d_addr.siso;
+ break;
+#endif /* NETISO */
+
+ default:
+ socksize = sizeof d->d_addr;
+ break;
+ }
+
+ if (bind(d->d_socket, &d->d_addr.sa, socksize) < 0)
+ {
+ /* probably another daemon already */
+ save_errno = errno;
+ syserr("opendaemonsocket: daemon %s: cannot bind",
+ d->d_name);
+ (void) close(d->d_socket);
+ goto fail;
+ }
+ }
+ if (!firsttime &&
+ listen(d->d_socket, d->d_listenqueue) < 0)
+ {
+ save_errno = errno;
+ syserr("opendaemonsocket: daemon %s: cannot listen",
+ d->d_name);
+ (void) close(d->d_socket);
+ goto severe;
+ }
+ return socksize;
+ } while (ntries++ < MAXOPENTRIES && transienterror(save_errno));
+ syserr("!opendaemonsocket: daemon %s: server SMTP socket wedged: exiting",
+ d->d_name);
+ /* NOTREACHED */
+ return -1; /* avoid compiler warning on IRIX */
+}
+/*
+** SETUPDAEMON -- setup socket for daemon
+**
+** Parameters:
+** daemonaddr -- socket for daemon
+**
+** Returns:
+** port number on which daemon should run
+**
+*/
+
+static unsigned short
+setupdaemon(daemonaddr)
+ SOCKADDR *daemonaddr;
+{
+ unsigned short port;
+
+ /*
+ ** Set up the address for the mailer.
+ */
+
+ if (daemonaddr->sa.sa_family == AF_UNSPEC)
+ {
+ memset(daemonaddr, '\0', sizeof *daemonaddr);
+#if NETINET
+ daemonaddr->sa.sa_family = AF_INET;
+#endif /* NETINET */
+ }
+
+ switch (daemonaddr->sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (daemonaddr->sin.sin_addr.s_addr == 0)
+ daemonaddr->sin.sin_addr.s_addr = INADDR_ANY;
+ port = daemonaddr->sin.sin_port;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(&daemonaddr->sin6.sin6_addr))
+ daemonaddr->sin6.sin6_addr = in6addr_any;
+ port = daemonaddr->sin6.sin6_port;
+ break;
+#endif /* NETINET6 */
+
+ default:
+ /* unknown protocol */
+ port = 0;
+ break;
+ }
+ if (port == 0)
+ {
+#ifdef NO_GETSERVBYNAME
+ port = htons(25);
+#else /* NO_GETSERVBYNAME */
+ {
+ register struct servent *sp;
+
+ sp = getservbyname("smtp", "tcp");
+ if (sp == NULL)
+ {
+ syserr("554 5.3.5 service \"smtp\" unknown");
+ port = htons(25);
+ }
+ else
+ port = sp->s_port;
+ }
+#endif /* NO_GETSERVBYNAME */
+ }
+
+ switch (daemonaddr->sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ daemonaddr->sin.sin_port = port;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ daemonaddr->sin6.sin6_port = port;
+ break;
+#endif /* NETINET6 */
+
+ default:
+ /* unknown protocol */
+ break;
+ }
+ return port;
+}
+/*
+** CLRDAEMON -- reset the daemon connection
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** releases any resources used by the passive daemon.
+*/
+
+void
+clrdaemon()
+{
+ int i;
+
+ for (i = 0; i < NDaemons; i++)
+ {
+ if (Daemons[i].d_socket >= 0)
+ (void) close(Daemons[i].d_socket);
+ Daemons[i].d_socket = -1;
+ }
+}
+
+/*
+** GETMODIFIERS -- get modifier flags
+**
+** Parameters:
+** v -- the modifiers (input text line).
+** modifiers -- pointer to flag field to represent modifiers.
+**
+** Returns:
+** (xallocat()ed) string representation of modifiers.
+**
+** Side Effects:
+** fills in modifiers.
+*/
+
+char *
+getmodifiers(v, modifiers)
+ char *v;
+ BITMAP256 modifiers;
+{
+ int l;
+ char *h, *f, *flags;
+
+ /* maximum length of flags: upper case Option -> "OO " */
+ l = 3 * strlen(v) + 3;
+
+ /* is someone joking? */
+ if (l < 0 || l > 256)
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_ERR, NOQID,
+ "getmodifiers too long, ignored");
+ return NULL;
+ }
+ flags = xalloc(l);
+ f = flags;
+ clrbitmap(modifiers);
+ for (h = v; *h != '\0'; h++)
+ {
+ if (isascii(*h) && !isspace(*h) && isprint(*h))
+ {
+ setbitn(*h, modifiers);
+ if (flags != f)
+ *flags++ = ' ';
+ *flags++ = *h;
+ if (isupper(*h))
+ *flags++ = *h;
+ }
+ }
+ *flags++ = '\0';
+ return f;
+}
+
+/*
+** CHKDAEMONMODIFIERS -- check whether all daemons have set a flag.
+**
+** Parameters:
+** flag -- the flag to test.
+**
+** Returns:
+** true iff all daemons have set flag.
+*/
+
+bool
+chkdaemonmodifiers(flag)
+ int flag;
+{
+ int i;
+
+ for (i = 0; i < NDaemons; i++)
+ if (!bitnset((char) flag, Daemons[i].d_flags))
+ return false;
+ return true;
+}
+
+/*
+** SETSOCKADDROPTIONS -- set options for SOCKADDR (daemon or client)
+**
+** Parameters:
+** p -- the options line.
+** d -- the daemon structure to fill in.
+**
+** Returns:
+** none.
+*/
+
+static void
+setsockaddroptions(p, d)
+ register char *p;
+ DAEMON_T *d;
+{
+#if NETISO
+ short portno;
+#endif /* NETISO */
+ char *port = NULL;
+ char *addr = NULL;
+
+#if NETINET
+ if (d->d_addr.sa.sa_family == AF_UNSPEC)
+ d->d_addr.sa.sa_family = AF_INET;
+#endif /* NETINET */
+
+ while (p != NULL)
+ {
+ register char *f;
+ register char *v;
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ f = p;
+ p = strchr(p, ',');
+ if (p != NULL)
+ *p++ = '\0';
+ v = strchr(f, '=');
+ if (v == NULL)
+ continue;
+ while (isascii(*++v) && isspace(*v))
+ continue;
+ if (isascii(*f) && islower(*f))
+ *f = toupper(*f);
+
+ switch (*f)
+ {
+ case 'F': /* address family */
+ if (isascii(*v) && isdigit(*v))
+ d->d_addr.sa.sa_family = atoi(v);
+#if _FFR_DAEMON_NETUNIX
+# ifdef NETUNIX
+ else if (sm_strcasecmp(v, "unix") == 0 ||
+ sm_strcasecmp(v, "local") == 0)
+ d->d_addr.sa.sa_family = AF_UNIX;
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+#if NETINET
+ else if (sm_strcasecmp(v, "inet") == 0)
+ d->d_addr.sa.sa_family = AF_INET;
+#endif /* NETINET */
+#if NETINET6
+ else if (sm_strcasecmp(v, "inet6") == 0)
+ d->d_addr.sa.sa_family = AF_INET6;
+#endif /* NETINET6 */
+#if NETISO
+ else if (sm_strcasecmp(v, "iso") == 0)
+ d->d_addr.sa.sa_family = AF_ISO;
+#endif /* NETISO */
+#if NETNS
+ else if (sm_strcasecmp(v, "ns") == 0)
+ d->d_addr.sa.sa_family = AF_NS;
+#endif /* NETNS */
+#if NETX25
+ else if (sm_strcasecmp(v, "x.25") == 0)
+ d->d_addr.sa.sa_family = AF_CCITT;
+#endif /* NETX25 */
+ else
+ syserr("554 5.3.5 Unknown address family %s in Family=option",
+ v);
+ break;
+
+ case 'A': /* address */
+ addr = v;
+ break;
+
+#if MILTER
+# if _FFR_MILTER_PERDAEMON
+ case 'I':
+ d->d_inputfilterlist = v;
+ break;
+# endif /* _FFR_MILTER_PERDAEMON */
+#endif /* MILTER */
+
+ case 'P': /* port */
+ port = v;
+ break;
+
+ case 'L': /* listen queue size */
+ d->d_listenqueue = atoi(v);
+ break;
+
+ case 'M': /* modifiers (flags) */
+ d->d_mflags = getmodifiers(v, d->d_flags);
+ break;
+
+ case 'S': /* send buffer size */
+ d->d_tcpsndbufsize = atoi(v);
+ break;
+
+ case 'R': /* receive buffer size */
+ d->d_tcprcvbufsize = atoi(v);
+ break;
+
+ case 'N': /* name */
+ d->d_name = v;
+ break;
+
+ default:
+ syserr("554 5.3.5 PortOptions parameter \"%s\" unknown",
+ f);
+ }
+ }
+
+ /* Check addr and port after finding family */
+ if (addr != NULL)
+ {
+ switch (d->d_addr.sa.sa_family)
+ {
+#if _FFR_DAEMON_NETUNIX
+# if NETUNIX
+ case AF_UNIX:
+ if (strlen(addr) >= sizeof(d->d_addr.sunix.sun_path))
+ {
+ errno = ENAMETOOLONG;
+ syserr("setsockaddroptions: domain socket name too long: %s > %d",
+ addr, sizeof(d->d_addr.sunix.sun_path));
+ break;
+ }
+
+ /* file safety check done in opendaemonsocket() */
+ (void) memset(&d->d_addr.sunix.sun_path, '\0',
+ sizeof(d->d_addr.sunix.sun_path));
+ (void) sm_strlcpy((char *)&d->d_addr.sunix.sun_path,
+ addr,
+ sizeof(d->d_addr.sunix.sun_path));
+ break;
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+#if NETINET
+ case AF_INET:
+ if (!isascii(*addr) || !isdigit(*addr) ||
+ ((d->d_addr.sin.sin_addr.s_addr = inet_addr(addr))
+ == INADDR_NONE))
+ {
+ register struct hostent *hp;
+
+ hp = sm_gethostbyname(addr, AF_INET);
+ if (hp == NULL)
+ syserr("554 5.3.0 host \"%s\" unknown",
+ addr);
+ else
+ {
+ while (*(hp->h_addr_list) != NULL &&
+ hp->h_addrtype != AF_INET)
+ hp->h_addr_list++;
+ if (*(hp->h_addr_list) == NULL)
+ syserr("554 5.3.0 host \"%s\" unknown",
+ addr);
+ else
+ memmove(&d->d_addr.sin.sin_addr,
+ *(hp->h_addr_list),
+ INADDRSZ);
+# if NETINET6
+ freehostent(hp);
+ hp = NULL;
+# endif /* NETINET6 */
+ }
+ }
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (anynet_pton(AF_INET6, addr,
+ &d->d_addr.sin6.sin6_addr) != 1)
+ {
+ register struct hostent *hp;
+
+ hp = sm_gethostbyname(addr, AF_INET6);
+ if (hp == NULL)
+ syserr("554 5.3.0 host \"%s\" unknown",
+ addr);
+ else
+ {
+ while (*(hp->h_addr_list) != NULL &&
+ hp->h_addrtype != AF_INET6)
+ hp->h_addr_list++;
+ if (*(hp->h_addr_list) == NULL)
+ syserr("554 5.3.0 host \"%s\" unknown",
+ addr);
+ else
+ memmove(&d->d_addr.sin6.sin6_addr,
+ *(hp->h_addr_list),
+ IN6ADDRSZ);
+ freehostent(hp);
+ hp = NULL;
+ }
+ }
+ break;
+#endif /* NETINET6 */
+
+ default:
+ syserr("554 5.3.5 address= option unsupported for family %d",
+ d->d_addr.sa.sa_family);
+ break;
+ }
+ }
+
+ if (port != NULL)
+ {
+ switch (d->d_addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (isascii(*port) && isdigit(*port))
+ d->d_addr.sin.sin_port = htons((unsigned short)
+ atoi((const char *) port));
+ else
+ {
+# ifdef NO_GETSERVBYNAME
+ syserr("554 5.3.5 invalid port number: %s",
+ port);
+# else /* NO_GETSERVBYNAME */
+ register struct servent *sp;
+
+ sp = getservbyname(port, "tcp");
+ if (sp == NULL)
+ syserr("554 5.3.5 service \"%s\" unknown",
+ port);
+ else
+ d->d_addr.sin.sin_port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (isascii(*port) && isdigit(*port))
+ d->d_addr.sin6.sin6_port = htons((unsigned short)
+ atoi(port));
+ else
+ {
+# ifdef NO_GETSERVBYNAME
+ syserr("554 5.3.5 invalid port number: %s",
+ port);
+# else /* NO_GETSERVBYNAME */
+ register struct servent *sp;
+
+ sp = getservbyname(port, "tcp");
+ if (sp == NULL)
+ syserr("554 5.3.5 service \"%s\" unknown",
+ port);
+ else
+ d->d_addr.sin6.sin6_port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ break;
+#endif /* NETINET6 */
+
+#if NETISO
+ case AF_ISO:
+ /* assume two byte transport selector */
+ if (isascii(*port) && isdigit(*port))
+ portno = htons((unsigned short) atoi(port));
+ else
+ {
+# ifdef NO_GETSERVBYNAME
+ syserr("554 5.3.5 invalid port number: %s",
+ port);
+# else /* NO_GETSERVBYNAME */
+ register struct servent *sp;
+
+ sp = getservbyname(port, "tcp");
+ if (sp == NULL)
+ syserr("554 5.3.5 service \"%s\" unknown",
+ port);
+ else
+ portno = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ memmove(TSEL(&d->d_addr.siso),
+ (char *) &portno, 2);
+ break;
+#endif /* NETISO */
+
+ default:
+ syserr("554 5.3.5 Port= option unsupported for family %d",
+ d->d_addr.sa.sa_family);
+ break;
+ }
+ }
+}
+/*
+** SETDAEMONOPTIONS -- set options for running the MTA daemon
+**
+** Parameters:
+** p -- the options line.
+**
+** Returns:
+** true if successful, false otherwise.
+**
+** Side Effects:
+** increments number of daemons.
+*/
+
+#define DEF_LISTENQUEUE 10
+
+struct dflags
+{
+ char *d_name;
+ int d_flag;
+};
+
+static struct dflags DaemonFlags[] =
+{
+ { "AUTHREQ", D_AUTHREQ },
+ { "BINDIF", D_BINDIF },
+ { "CANONREQ", D_CANONREQ },
+ { "IFNHELO", D_IFNHELO },
+ { "FQMAIL", D_FQMAIL },
+ { "FQRCPT", D_FQRCPT },
+#if _FFR_SMTP_SSL
+ { "SMTPS", D_SMTPS },
+#endif /* _FFR_SMTP_SSL */
+ { "UNQUALOK", D_UNQUALOK },
+ { "NOAUTH", D_NOAUTH },
+ { "NOCANON", D_NOCANON },
+ { "NOETRN", D_NOETRN },
+ { "NOTLS", D_NOTLS },
+ { "ETRNONLY", D_ETRNONLY },
+ { "OPTIONAL", D_OPTIONAL },
+ { "DISABLE", D_DISABLE },
+ { "ISSET", D_ISSET },
+ { NULL, 0 }
+};
+
+static void
+printdaemonflags(d)
+ DAEMON_T *d;
+{
+ register struct dflags *df;
+ bool first = true;
+
+ for (df = DaemonFlags; df->d_name != NULL; df++)
+ {
+ if (!bitnset(df->d_flag, d->d_flags))
+ continue;
+ if (first)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "<%s",
+ df->d_name);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, ",%s",
+ df->d_name);
+ first = false;
+ }
+ if (!first)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, ">");
+}
+
+bool
+setdaemonoptions(p)
+ register char *p;
+{
+ if (NDaemons >= MAXDAEMONS)
+ return false;
+ Daemons[NDaemons].d_socket = -1;
+ Daemons[NDaemons].d_listenqueue = DEF_LISTENQUEUE;
+ clrbitmap(Daemons[NDaemons].d_flags);
+ setsockaddroptions(p, &Daemons[NDaemons]);
+
+#if MILTER
+# if _FFR_MILTER_PERDAEMON
+ if (Daemons[NDaemons].d_inputfilterlist != NULL)
+ Daemons[NDaemons].d_inputfilterlist = newstr(Daemons[NDaemons].d_inputfilterlist);
+# endif /* _FFR_MILTER_PERDAEMON */
+#endif /* MILTER */
+
+ if (Daemons[NDaemons].d_name != NULL)
+ Daemons[NDaemons].d_name = newstr(Daemons[NDaemons].d_name);
+ else
+ {
+ char num[30];
+
+ (void) sm_snprintf(num, sizeof num, "Daemon%d", NDaemons);
+ Daemons[NDaemons].d_name = newstr(num);
+ }
+
+ if (tTd(37, 1))
+ {
+ sm_dprintf("Daemon %s flags: ", Daemons[NDaemons].d_name);
+ printdaemonflags(&Daemons[NDaemons]);
+ sm_dprintf("\n");
+ }
+ ++NDaemons;
+ return true;
+}
+/*
+** INITDAEMON -- initialize daemon if not yet done.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+**
+** Side Effects:
+** initializes structure for one daemon.
+*/
+
+void
+initdaemon()
+{
+ if (NDaemons == 0)
+ {
+ Daemons[NDaemons].d_socket = -1;
+ Daemons[NDaemons].d_listenqueue = DEF_LISTENQUEUE;
+ Daemons[NDaemons].d_name = "Daemon0";
+ NDaemons = 1;
+ }
+}
+/*
+** SETCLIENTOPTIONS -- set options for running the client
+**
+** Parameters:
+** p -- the options line.
+**
+** Returns:
+** none.
+*/
+
+static DAEMON_T ClientSettings[AF_MAX + 1];
+
+void
+setclientoptions(p)
+ register char *p;
+{
+ int family;
+ DAEMON_T d;
+
+ memset(&d, '\0', sizeof d);
+ setsockaddroptions(p, &d);
+
+ /* grab what we need */
+ family = d.d_addr.sa.sa_family;
+ STRUCTCOPY(d, ClientSettings[family]);
+ setbitn(D_ISSET, ClientSettings[family].d_flags); /* mark as set */
+ if (d.d_name != NULL)
+ ClientSettings[family].d_name = newstr(d.d_name);
+ else
+ {
+ char num[30];
+
+ (void) sm_snprintf(num, sizeof num, "Client%d", family);
+ ClientSettings[family].d_name = newstr(num);
+ }
+}
+/*
+** ADDR_FAMILY -- determine address family from address
+**
+** Parameters:
+** addr -- the string representation of the address
+**
+** Returns:
+** AF_INET, AF_INET6 or AF_UNSPEC
+**
+** Side Effects:
+** none.
+*/
+
+static int
+addr_family(addr)
+ char *addr;
+{
+#if NETINET6
+ SOCKADDR clt_addr;
+#endif /* NETINET6 */
+
+#if NETINET
+ if (inet_addr(addr) != INADDR_NONE)
+ {
+ if (tTd(16, 9))
+ sm_dprintf("addr_family(%s): INET\n", addr);
+ return AF_INET;
+ }
+#endif /* NETINET */
+#if NETINET6
+ if (anynet_pton(AF_INET6, addr, &clt_addr.sin6.sin6_addr) == 1)
+ {
+ if (tTd(16, 9))
+ sm_dprintf("addr_family(%s): INET6\n", addr);
+ return AF_INET6;
+ }
+#endif /* NETINET6 */
+#if _FFR_DAEMON_NETUNIX
+# if NETUNIX
+ if (*addr == '/')
+ {
+ if (tTd(16, 9))
+ sm_dprintf("addr_family(%s): LOCAL\n", addr);
+ return AF_UNIX;
+ }
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+ if (tTd(16, 9))
+ sm_dprintf("addr_family(%s): UNSPEC\n", addr);
+ return AF_UNSPEC;
+}
+
+/*
+** CHKCLIENTMODIFIERS -- check whether all clients have set a flag.
+**
+** Parameters:
+** flag -- the flag to test.
+**
+** Returns:
+** true iff all configured clients have set the flag.
+*/
+
+bool
+chkclientmodifiers(flag)
+ int flag;
+{
+ int i;
+ bool flagisset;
+
+ flagisset = false;
+ for (i = 0; i < AF_MAX; i++)
+ {
+ if (bitnset(D_ISSET, ClientSettings[i].d_flags))
+ {
+ if (!bitnset((char) flag, ClientSettings[i].d_flags))
+ return false;
+ flagisset = true;
+ }
+ }
+ return flagisset;
+}
+
+#if MILTER
+# if _FFR_MILTER_PERDAEMON
+/*
+** SETUP_DAEMON_FILTERS -- Parse per-socket filters
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+void
+setup_daemon_milters()
+{
+ int idx;
+
+ if (OpMode == MD_SMTP)
+ {
+ /* no need to configure the daemons */
+ return;
+ }
+
+ for (idx = 0; idx < NDaemons; idx++)
+ {
+ if (Daemons[idx].d_inputfilterlist != NULL)
+ {
+ milter_config(Daemons[idx].d_inputfilterlist,
+ Daemons[idx].d_inputfilters,
+ MAXFILTERS);
+ }
+ }
+}
+# endif /* _FFR_MILTER_PERDAEMON */
+#endif /* MILTER */
+/*
+** MAKECONNECTION -- make a connection to an SMTP socket on a machine.
+**
+** Parameters:
+** host -- the name of the host.
+** port -- the port number to connect to.
+** mci -- a pointer to the mail connection information
+** structure to be filled in.
+** e -- the current envelope.
+** enough -- time at which to stop further connection attempts.
+** (0 means no limit)
+**
+** Returns:
+** An exit code telling whether the connection could be
+** made and if not why not.
+**
+** Side Effects:
+** none.
+*/
+
+static jmp_buf CtxConnectTimeout;
+
+SOCKADDR CurHostAddr; /* address of current host */
+
+int
+makeconnection(host, port, mci, e, enough)
+ char *host;
+ volatile unsigned int port;
+ register MCI *mci;
+ ENVELOPE *e;
+ time_t enough;
+{
+ register volatile int addrno = 0;
+ volatile int s;
+ register struct hostent *volatile hp = (struct hostent *) NULL;
+ SOCKADDR addr;
+ SOCKADDR clt_addr;
+ int save_errno = 0;
+ volatile SOCKADDR_LEN_T addrlen;
+ volatile bool firstconnect;
+ SM_EVENT *volatile ev = NULL;
+#if NETINET6
+ volatile bool v6found = false;
+#endif /* NETINET6 */
+ volatile int family = InetMode;
+ SOCKADDR_LEN_T len;
+ volatile SOCKADDR_LEN_T socksize = 0;
+ volatile bool clt_bind;
+ BITMAP256 d_flags;
+ char *p;
+ extern ENVELOPE BlankEnvelope;
+
+ /* retranslate {daemon_flags} into bitmap */
+ clrbitmap(d_flags);
+ if ((p = macvalue(macid("{daemon_flags}"), e)) != NULL)
+ {
+ for (; *p != '\0'; p++)
+ {
+ if (!(isascii(*p) && isspace(*p)))
+ setbitn(bitidx(*p), d_flags);
+ }
+ }
+
+#if NETINET6
+ v4retry:
+#endif /* NETINET6 */
+ clt_bind = false;
+
+ /* Set up the address for outgoing connection. */
+ if (bitnset(D_BINDIF, d_flags) &&
+ (p = macvalue(macid("{if_addr}"), e)) != NULL &&
+ *p != '\0')
+ {
+#if NETINET6
+ char p6[INET6_ADDRSTRLEN];
+#endif /* NETINET6 */
+
+ memset(&clt_addr, '\0', sizeof clt_addr);
+
+ /* infer the address family from the address itself */
+ clt_addr.sa.sa_family = addr_family(p);
+ switch (clt_addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ clt_addr.sin.sin_addr.s_addr = inet_addr(p);
+ if (clt_addr.sin.sin_addr.s_addr != INADDR_NONE &&
+ clt_addr.sin.sin_addr.s_addr != INADDR_LOOPBACK)
+ {
+ clt_bind = true;
+ socksize = sizeof (struct sockaddr_in);
+ }
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (inet_addr(p) != INADDR_NONE)
+ (void) sm_snprintf(p6, sizeof p6,
+ "IPv6:::ffff:%s", p);
+ else
+ (void) sm_strlcpy(p6, p, sizeof p6);
+ if (anynet_pton(AF_INET6, p6,
+ &clt_addr.sin6.sin6_addr) == 1 &&
+ !IN6_IS_ADDR_LOOPBACK(&clt_addr.sin6.sin6_addr))
+ {
+ clt_bind = true;
+ socksize = sizeof (struct sockaddr_in6);
+ }
+ break;
+#endif /* NETINET6 */
+
+#if 0
+ default:
+ syserr("554 5.3.5 Address= option unsupported for family %d",
+ clt_addr.sa.sa_family);
+ break;
+#endif /* 0 */
+ }
+ if (clt_bind)
+ family = clt_addr.sa.sa_family;
+ }
+
+ /* D_BINDIF not set or not available, fallback to ClientPortOptions */
+ if (!clt_bind)
+ {
+ STRUCTCOPY(ClientSettings[family].d_addr, clt_addr);
+ switch (clt_addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (clt_addr.sin.sin_addr.s_addr == 0)
+ clt_addr.sin.sin_addr.s_addr = INADDR_ANY;
+ else
+ clt_bind = true;
+ if (clt_addr.sin.sin_port != 0)
+ clt_bind = true;
+ socksize = sizeof (struct sockaddr_in);
+ break;
+#endif /* NETINET */
+#if NETINET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(&clt_addr.sin6.sin6_addr))
+ clt_addr.sin6.sin6_addr = in6addr_any;
+ else
+ clt_bind = true;
+ socksize = sizeof (struct sockaddr_in6);
+ if (clt_addr.sin6.sin6_port != 0)
+ clt_bind = true;
+ break;
+#endif /* NETINET6 */
+#if NETISO
+ case AF_ISO:
+ socksize = sizeof clt_addr.siso;
+ clt_bind = true;
+ break;
+#endif /* NETISO */
+ default:
+ break;
+ }
+ }
+
+ /*
+ ** Set up the address for the mailer.
+ ** Accept "[a.b.c.d]" syntax for host name.
+ */
+
+ SM_SET_H_ERRNO(0);
+ errno = 0;
+ memset(&CurHostAddr, '\0', sizeof CurHostAddr);
+ memset(&addr, '\0', sizeof addr);
+ SmtpPhase = mci->mci_phase = "initial connection";
+ CurHostName = host;
+
+ if (host[0] == '[')
+ {
+ p = strchr(host, ']');
+ if (p != NULL)
+ {
+#if NETINET
+ unsigned long hid = INADDR_NONE;
+#endif /* NETINET */
+#if NETINET6
+ struct sockaddr_in6 hid6;
+#endif /* NETINET6 */
+
+ *p = '\0';
+#if NETINET6
+ memset(&hid6, '\0', sizeof hid6);
+#endif /* NETINET6 */
+#if NETINET
+ if (family == AF_INET &&
+ (hid = inet_addr(&host[1])) != INADDR_NONE)
+ {
+ addr.sin.sin_family = AF_INET;
+ addr.sin.sin_addr.s_addr = hid;
+ }
+ else
+#endif /* NETINET */
+#if NETINET6
+ if (family == AF_INET6 &&
+ anynet_pton(AF_INET6, &host[1],
+ &hid6.sin6_addr) == 1)
+ {
+ addr.sin6.sin6_family = AF_INET6;
+ addr.sin6.sin6_addr = hid6.sin6_addr;
+ }
+ else
+#endif /* NETINET6 */
+ {
+ /* try it as a host name (avoid MX lookup) */
+ hp = sm_gethostbyname(&host[1], family);
+ if (hp == NULL && p[-1] == '.')
+ {
+#if NAMED_BIND
+ int oldopts = _res.options;
+
+ _res.options &= ~(RES_DEFNAMES|RES_DNSRCH);
+#endif /* NAMED_BIND */
+ p[-1] = '\0';
+ hp = sm_gethostbyname(&host[1],
+ family);
+ p[-1] = '.';
+#if NAMED_BIND
+ _res.options = oldopts;
+#endif /* NAMED_BIND */
+ }
+ *p = ']';
+ goto gothostent;
+ }
+ *p = ']';
+ }
+ if (p == NULL)
+ {
+ extern char MsgBuf[];
+
+ usrerrenh("5.1.2",
+ "553 Invalid numeric domain spec \"%s\"",
+ host);
+ mci_setstat(mci, EX_NOHOST, "5.1.2", MsgBuf);
+ errno = EINVAL;
+ return EX_NOHOST;
+ }
+ }
+ else
+ {
+ /* contortion to get around SGI cc complaints */
+ {
+ p = &host[strlen(host) - 1];
+ hp = sm_gethostbyname(host, family);
+ if (hp == NULL && *p == '.')
+ {
+#if NAMED_BIND
+ int oldopts = _res.options;
+
+ _res.options &= ~(RES_DEFNAMES|RES_DNSRCH);
+#endif /* NAMED_BIND */
+ *p = '\0';
+ hp = sm_gethostbyname(host, family);
+ *p = '.';
+#if NAMED_BIND
+ _res.options = oldopts;
+#endif /* NAMED_BIND */
+ }
+ }
+gothostent:
+ if (hp == NULL)
+ {
+#if NAMED_BIND
+ /* check for name server timeouts */
+# if NETINET6
+ if (WorkAroundBrokenAAAA && family == AF_INET6 &&
+ errno == ETIMEDOUT)
+ {
+ /*
+ ** An attempt with family AF_INET may
+ ** succeed By skipping the next section
+ ** of code, we will try AF_INET before
+ ** failing.
+ */
+
+ if (tTd(16, 10))
+ sm_dprintf("makeconnection: WorkAroundBrokenAAAA: Trying AF_INET lookup (AF_INET6 failed)\n");
+ }
+ else
+# endif /* NETINET6 */
+ {
+ if (errno == ETIMEDOUT ||
+ h_errno == TRY_AGAIN ||
+ (errno == ECONNREFUSED && UseNameServer))
+ {
+ save_errno = errno;
+ mci_setstat(mci, EX_TEMPFAIL,
+ "4.4.3", NULL);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ }
+#endif /* NAMED_BIND */
+#if NETINET6
+ /*
+ ** Try v6 first, then fall back to v4.
+ ** If we found a v6 address, but no v4
+ ** addresses, then TEMPFAIL.
+ */
+
+ if (family == AF_INET6)
+ {
+ family = AF_INET;
+ goto v4retry;
+ }
+ if (v6found)
+ goto v6tempfail;
+#endif /* NETINET6 */
+ save_errno = errno;
+ mci_setstat(mci, EX_NOHOST, "5.1.2", NULL);
+ errno = save_errno;
+ return EX_NOHOST;
+ }
+ addr.sa.sa_family = hp->h_addrtype;
+ switch (hp->h_addrtype)
+ {
+#if NETINET
+ case AF_INET:
+ memmove(&addr.sin.sin_addr,
+ hp->h_addr,
+ INADDRSZ);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ hp->h_addr,
+ IN6ADDRSZ);
+ break;
+#endif /* NETINET6 */
+
+ default:
+ if (hp->h_length > sizeof addr.sa.sa_data)
+ {
+ syserr("makeconnection: long sa_data: family %d len %d",
+ hp->h_addrtype, hp->h_length);
+ mci_setstat(mci, EX_NOHOST, "5.1.2", NULL);
+ errno = EINVAL;
+ return EX_NOHOST;
+ }
+ memmove(addr.sa.sa_data, hp->h_addr, hp->h_length);
+ break;
+ }
+ addrno = 1;
+ }
+
+ /*
+ ** Determine the port number.
+ */
+
+ if (port == 0)
+ {
+#ifdef NO_GETSERVBYNAME
+ port = htons(25);
+#else /* NO_GETSERVBYNAME */
+ register struct servent *sp = getservbyname("smtp", "tcp");
+
+ if (sp == NULL)
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_ERR, NOQID,
+ "makeconnection: service \"smtp\" unknown");
+ port = htons(25);
+ }
+ else
+ port = sp->s_port;
+#endif /* NO_GETSERVBYNAME */
+ }
+
+#if NETINET6
+ if (addr.sa.sa_family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&addr.sin6.sin6_addr) &&
+ ClientSettings[AF_INET].d_addr.sa.sa_family != 0)
+ {
+ /*
+ ** Ignore mapped IPv4 address since
+ ** there is a ClientPortOptions setting
+ ** for IPv4.
+ */
+
+ goto nextaddr;
+ }
+#endif /* NETINET6 */
+
+ switch (addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ addr.sin.sin_port = port;
+ addrlen = sizeof (struct sockaddr_in);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ addr.sin6.sin6_port = port;
+ addrlen = sizeof (struct sockaddr_in6);
+ break;
+#endif /* NETINET6 */
+
+#if NETISO
+ case AF_ISO:
+ /* assume two byte transport selector */
+ memmove(TSEL((struct sockaddr_iso *) &addr), (char *) &port, 2);
+ addrlen = sizeof (struct sockaddr_iso);
+ break;
+#endif /* NETISO */
+
+ default:
+ syserr("Can't connect to address family %d", addr.sa.sa_family);
+ mci_setstat(mci, EX_NOHOST, "5.1.2", NULL);
+ errno = EINVAL;
+#if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+#endif /* NETINET6 */
+ return EX_NOHOST;
+ }
+
+ /*
+ ** Try to actually open the connection.
+ */
+
+#if XLA
+ /* if too many connections, don't bother trying */
+ if (!xla_noqueue_ok(host))
+ {
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return EX_TEMPFAIL;
+ }
+#endif /* XLA */
+
+ firstconnect = true;
+ for (;;)
+ {
+ if (tTd(16, 1))
+ sm_dprintf("makeconnection (%s [%s].%d (%d))\n",
+ host, anynet_ntoa(&addr), ntohs(port),
+ (int) addr.sa.sa_family);
+
+ /* save for logging */
+ CurHostAddr = addr;
+
+#if HASRRESVPORT
+ if (bitnset(M_SECURE_PORT, mci->mci_mailer->m_flags))
+ {
+ int rport = IPPORT_RESERVED - 1;
+
+ s = rresvport(&rport);
+ }
+ else
+#endif /* HASRRESVPORT */
+ {
+ s = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+ }
+ if (s < 0)
+ {
+ save_errno = errno;
+ syserr("makeconnection: cannot create socket");
+#if XLA
+ xla_host_end(host);
+#endif /* XLA */
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+#if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+#endif /* NETINET6 */
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+#ifdef SO_SNDBUF
+ if (ClientSettings[family].d_tcpsndbufsize > 0)
+ {
+ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
+ (char *) &ClientSettings[family].d_tcpsndbufsize,
+ sizeof(ClientSettings[family].d_tcpsndbufsize)) < 0)
+ syserr("makeconnection: setsockopt(SO_SNDBUF)");
+ }
+#endif /* SO_SNDBUF */
+#ifdef SO_RCVBUF
+ if (ClientSettings[family].d_tcprcvbufsize > 0)
+ {
+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
+ (char *) &ClientSettings[family].d_tcprcvbufsize,
+ sizeof(ClientSettings[family].d_tcprcvbufsize)) < 0)
+ syserr("makeconnection: setsockopt(SO_RCVBUF)");
+ }
+#endif /* SO_RCVBUF */
+
+ if (tTd(16, 1))
+ sm_dprintf("makeconnection: fd=%d\n", s);
+
+ /* turn on network debugging? */
+ if (tTd(16, 101))
+ {
+ int on = 1;
+
+ (void) setsockopt(s, SOL_SOCKET, SO_DEBUG,
+ (char *)&on, sizeof on);
+ }
+ if (e->e_xfp != NULL) /* for debugging */
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+ errno = 0; /* for debugging */
+
+ if (clt_bind)
+ {
+ int on = 1;
+
+ switch (clt_addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (clt_addr.sin.sin_port != 0)
+ (void) setsockopt(s, SOL_SOCKET,
+ SO_REUSEADDR,
+ (char *) &on,
+ sizeof on);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (clt_addr.sin6.sin6_port != 0)
+ (void) setsockopt(s, SOL_SOCKET,
+ SO_REUSEADDR,
+ (char *) &on,
+ sizeof on);
+ break;
+#endif /* NETINET6 */
+ }
+
+ if (bind(s, &clt_addr.sa, socksize) < 0)
+ {
+ save_errno = errno;
+ (void) close(s);
+ errno = save_errno;
+ syserr("makeconnection: cannot bind socket [%s]",
+ anynet_ntoa(&clt_addr));
+#if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+#endif /* NETINET6 */
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ }
+
+ /*
+ ** Linux seems to hang in connect for 90 minutes (!!!).
+ ** Time out the connect to avoid this problem.
+ */
+
+ if (setjmp(CtxConnectTimeout) == 0)
+ {
+ int i;
+
+ if (e->e_ntries <= 0 && TimeOuts.to_iconnect != 0)
+ ev = sm_setevent(TimeOuts.to_iconnect,
+ connecttimeout, 0);
+ else if (TimeOuts.to_connect != 0)
+ ev = sm_setevent(TimeOuts.to_connect,
+ connecttimeout, 0);
+ else
+ ev = NULL;
+
+ switch (ConnectOnlyTo.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ addr.sin.sin_addr.s_addr = ConnectOnlyTo.sin.sin_addr.s_addr;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ &ConnectOnlyTo.sin6.sin6_addr,
+ IN6ADDRSZ);
+ break;
+#endif /* NETINET6 */
+ }
+ i = connect(s, (struct sockaddr *) &addr, addrlen);
+ save_errno = errno;
+ if (ev != NULL)
+ sm_clrevent(ev);
+ if (i >= 0)
+ break;
+ }
+ else
+ save_errno = errno;
+
+ /* couldn't connect.... figure out why */
+ (void) close(s);
+
+ /* if running demand-dialed connection, try again */
+ if (DialDelay > 0 && firstconnect &&
+ bitnset(M_DIALDELAY, mci->mci_mailer->m_flags))
+ {
+ if (tTd(16, 1))
+ sm_dprintf("Connect failed (%s); trying again...\n",
+ sm_errstring(save_errno));
+ firstconnect = false;
+ (void) sleep(DialDelay);
+ continue;
+ }
+
+ if (LogLevel > 13)
+ sm_syslog(LOG_INFO, e->e_id,
+ "makeconnection (%s [%s]) failed: %s",
+ host, anynet_ntoa(&addr),
+ sm_errstring(save_errno));
+
+#if NETINET6
+nextaddr:
+#endif /* NETINET6 */
+ if (hp != NULL && hp->h_addr_list[addrno] != NULL &&
+ (enough == 0 || curtime() < enough))
+ {
+ if (tTd(16, 1))
+ sm_dprintf("Connect failed (%s); trying new address....\n",
+ sm_errstring(save_errno));
+ switch (addr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ memmove(&addr.sin.sin_addr,
+ hp->h_addr_list[addrno++],
+ INADDRSZ);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ hp->h_addr_list[addrno++],
+ IN6ADDRSZ);
+ break;
+#endif /* NETINET6 */
+
+ default:
+ memmove(addr.sa.sa_data,
+ hp->h_addr_list[addrno++],
+ hp->h_length);
+ break;
+ }
+ continue;
+ }
+ errno = save_errno;
+
+#if NETINET6
+ if (family == AF_INET6)
+ {
+ if (tTd(16, 1))
+ sm_dprintf("Connect failed (%s); retrying with AF_INET....\n",
+ sm_errstring(save_errno));
+ v6found = true;
+ family = AF_INET;
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+ goto v4retry;
+ }
+ v6tempfail:
+#endif /* NETINET6 */
+ /* couldn't open connection */
+#if NETINET6
+ /* Don't clobber an already saved errno from v4retry */
+ if (errno > 0)
+#endif /* NETINET6 */
+ save_errno = errno;
+ if (tTd(16, 1))
+ sm_dprintf("Connect failed (%s)\n",
+ sm_errstring(save_errno));
+#if XLA
+ xla_host_end(host);
+#endif /* XLA */
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.1", NULL);
+#if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+#endif /* NETINET6 */
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+#if NETINET6
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+#endif /* NETINET6 */
+
+ /* connection ok, put it into canonical form */
+ mci->mci_out = NULL;
+ if ((mci->mci_out = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &s,
+ SM_IO_WRONLY, NULL)) == NULL ||
+ (s = dup(s)) < 0 ||
+ (mci->mci_in = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &s,
+ SM_IO_RDONLY, NULL)) == NULL)
+ {
+ save_errno = errno;
+ syserr("cannot open SMTP client channel, fd=%d", s);
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+ if (mci->mci_out != NULL)
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ (void) close(s);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ sm_io_automode(mci->mci_out, mci->mci_in);
+
+ /* set {client_flags} */
+ if (ClientSettings[addr.sa.sa_family].d_mflags != NULL)
+ {
+ macdefine(&mci->mci_macro, A_PERM,
+ macid("{client_flags}"),
+ ClientSettings[addr.sa.sa_family].d_mflags);
+ }
+ else
+ macdefine(&mci->mci_macro, A_PERM,
+ macid("{client_flags}"), "");
+
+ /* "add" {client_flags} to bitmap */
+ if (bitnset(D_IFNHELO, ClientSettings[addr.sa.sa_family].d_flags))
+ {
+ /* look for just this one flag */
+ setbitn(D_IFNHELO, d_flags);
+ }
+
+ /* find out name for Interface through which we connect */
+ len = sizeof addr;
+ if (getsockname(s, &addr.sa, &len) == 0)
+ {
+ char *name;
+ char family[5];
+
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{if_addr_out}"), anynet_ntoa(&addr));
+ (void) sm_snprintf(family, sizeof(family), "%d",
+ addr.sa.sa_family);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{if_family_out}"), family);
+
+ name = hostnamebyanyaddr(&addr);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{if_name_out}"), name);
+ if (LogLevel > 11)
+ {
+ /* log connection information */
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP outgoing connect on %.40s", name);
+ }
+ if (bitnset(D_IFNHELO, d_flags))
+ {
+ if (name[0] != '[' && strchr(name, '.') != NULL)
+ mci->mci_heloname = newstr(name);
+ }
+ }
+ else
+ {
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_name_out}"), NULL);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_addr_out}"), NULL);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{if_family_out}"), NULL);
+ }
+ mci_setstat(mci, EX_OK, NULL, NULL);
+ return EX_OK;
+}
+
+static void
+connecttimeout()
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxConnectTimeout, 1);
+}
+/*
+** MAKECONNECTION_DS -- make a connection to a domain socket.
+**
+** Parameters:
+** mux_path -- the path of the socket to connect to.
+** mci -- a pointer to the mail connection information
+** structure to be filled in.
+**
+** Returns:
+** An exit code telling whether the connection could be
+** made and if not why not.
+**
+** Side Effects:
+** none.
+*/
+
+#if NETUNIX
+int
+makeconnection_ds(mux_path, mci)
+ char *mux_path;
+ register MCI *mci;
+{
+ int sock;
+ int rval, save_errno;
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_ROOTOK|SFF_EXECOK;
+ struct sockaddr_un unix_addr;
+
+ /* if not safe, don't connect */
+ rval = safefile(mux_path, RunAsUid, RunAsGid, RunAsUserName,
+ sff, S_IRUSR|S_IWUSR, NULL);
+
+ if (rval != 0)
+ {
+ syserr("makeconnection_ds: unsafe domain socket");
+ mci_setstat(mci, EX_TEMPFAIL, "4.3.5", NULL);
+ errno = rval;
+ return EX_TEMPFAIL;
+ }
+
+ /* prepare address structure */
+ memset(&unix_addr, '\0', sizeof unix_addr);
+ unix_addr.sun_family = AF_UNIX;
+
+ if (strlen(mux_path) >= sizeof unix_addr.sun_path)
+ {
+ syserr("makeconnection_ds: domain socket name too long");
+
+ /* XXX why TEMPFAIL but 5.x.y ? */
+ mci_setstat(mci, EX_TEMPFAIL, "5.3.5", NULL);
+ errno = ENAMETOOLONG;
+ return EX_UNAVAILABLE;
+ }
+ (void) sm_strlcpy(unix_addr.sun_path, mux_path,
+ sizeof unix_addr.sun_path);
+
+ /* initialize domain socket */
+ sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock == -1)
+ {
+ save_errno = errno;
+ syserr("makeconnection_ds: could not create domain socket");
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+ /* connect to server */
+ if (connect(sock, (struct sockaddr *) &unix_addr,
+ sizeof(unix_addr)) == -1)
+ {
+ save_errno = errno;
+ syserr("Could not connect to socket %s", mux_path);
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.1", NULL);
+ (void) close(sock);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+
+ /* connection ok, put it into canonical form */
+ mci->mci_out = NULL;
+ if ((mci->mci_out = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &sock, SM_IO_WRONLY, NULL))
+ == NULL
+ || (sock = dup(sock)) < 0 ||
+ (mci->mci_in = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &sock, SM_IO_RDONLY, NULL))
+ == NULL)
+ {
+ save_errno = errno;
+ syserr("cannot open SMTP client channel, fd=%d", sock);
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+ if (mci->mci_out != NULL)
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ (void) close(sock);
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ sm_io_automode(mci->mci_out, mci->mci_in);
+
+ mci_setstat(mci, EX_OK, NULL, NULL);
+ errno = 0;
+ return EX_OK;
+}
+#endif /* NETUNIX */
+/*
+** SHUTDOWN_DAEMON -- Performs a clean shutdown of the daemon
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** closes control socket, exits.
+*/
+
+void
+shutdown_daemon()
+{
+ int i;
+ char *reason;
+
+ sm_allsignals(true);
+
+ reason = ShutdownRequest;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+
+ if (LogLevel > 79)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "interrupt (%s)",
+ reason == NULL ? "implicit call" : reason);
+
+ FileName = NULL;
+ closecontrolsocket(true);
+#if XLA
+ xla_all_end();
+#endif /* XLA */
+
+ for (i = 0; i < NDaemons; i++)
+ {
+ if (Daemons[i].d_socket >= 0)
+ {
+ (void) close(Daemons[i].d_socket);
+ Daemons[i].d_socket = -1;
+
+#if _FFR_DAEMON_NETUNIX
+# if NETUNIX
+ /* Remove named sockets */
+ if (Daemons[i].d_addr.sa.sa_family == AF_UNIX)
+ {
+ int rval;
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_MUSTOWN|SFF_EXECOK|SFF_CREAT;
+
+ /* if not safe, don't use it */
+ rval = safefile(Daemons[i].d_addr.sunix.sun_path,
+ RunAsUid, RunAsGid,
+ RunAsUserName, sff,
+ S_IRUSR|S_IWUSR, NULL);
+ if (rval == 0 &&
+ unlink(Daemons[i].d_addr.sunix.sun_path) < 0)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "Could not remove daemon %s socket: %s: %s",
+ Daemons[i].d_name,
+ Daemons[i].d_addr.sunix.sun_path,
+ sm_errstring(errno));
+ }
+ }
+# endif /* NETUNIX */
+#endif /* _FFR_DAEMON_NETUNIX */
+ }
+ }
+
+ finis(false, true, EX_OK);
+}
+/*
+** RESTART_DAEMON -- Performs a clean restart of the daemon
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** restarts the daemon or exits if restart fails.
+*/
+
+/* Make a non-DFL/IGN signal a noop */
+#define SM_NOOP_SIGNAL(sig, old) \
+do \
+{ \
+ (old) = sm_signal((sig), sm_signal_noop); \
+ if ((old) == SIG_IGN || (old) == SIG_DFL) \
+ (void) sm_signal((sig), (old)); \
+} while (0)
+
+void
+restart_daemon()
+{
+ bool drop;
+ int i;
+ int save_errno;
+ char *reason;
+ sigfunc_t ignore, oalrm, ousr1;
+ extern int DtableSize;
+
+ /* clear the events to turn off SIGALRMs */
+ sm_clear_events();
+ sm_allsignals(true);
+
+ reason = RestartRequest;
+ RestartRequest = NULL;
+ PendingSignal = 0;
+
+ if (SaveArgv[0][0] != '/')
+ {
+ if (LogLevel > 3)
+ sm_syslog(LOG_INFO, NOQID,
+ "could not restart: need full path");
+ finis(false, true, EX_OSFILE);
+ /* NOTREACHED */
+ }
+ if (LogLevel > 3)
+ sm_syslog(LOG_INFO, NOQID, "restarting %s due to %s",
+ SaveArgv[0],
+ reason == NULL ? "implicit call" : reason);
+
+ closecontrolsocket(true);
+#if SM_CONF_SHM
+ cleanup_shm(DaemonPid == getpid());
+#endif /* SM_CONF_SHM */
+
+ /*
+ ** Want to drop to the user who started the process in all cases
+ ** *but* when running as "smmsp" for the clientmqueue queue run
+ ** daemon. In that case, UseMSP will be true, RunAsUid should not
+ ** be root, and RealUid should be either 0 or RunAsUid.
+ */
+
+ drop = !(UseMSP && RunAsUid != 0 &&
+ (RealUid == 0 || RealUid == RunAsUid));
+
+ if (drop_privileges(drop) != EX_OK)
+ {
+ if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, NOQID,
+ "could not drop privileges: %s",
+ sm_errstring(errno));
+ finis(false, true, EX_OSERR);
+ /* NOTREACHED */
+ }
+
+ /* arrange for all the files to be closed */
+ for (i = 3; i < DtableSize; i++)
+ {
+ register int j;
+
+ if ((j = fcntl(i, F_GETFD, 0)) != -1)
+ (void) fcntl(i, F_SETFD, j | FD_CLOEXEC);
+ }
+
+ /*
+ ** Need to allow signals before execve() to make them "harmless".
+ ** However, the default action can be "terminate", so it isn't
+ ** really harmless. Setting signals to IGN will cause them to be
+ ** ignored in the new process to, so that isn't a good alternative.
+ */
+
+ SM_NOOP_SIGNAL(SIGALRM, oalrm);
+ SM_NOOP_SIGNAL(SIGCHLD, ignore);
+ SM_NOOP_SIGNAL(SIGHUP, ignore);
+ SM_NOOP_SIGNAL(SIGINT, ignore);
+ SM_NOOP_SIGNAL(SIGPIPE, ignore);
+ SM_NOOP_SIGNAL(SIGTERM, ignore);
+#ifdef SIGUSR1
+ SM_NOOP_SIGNAL(SIGUSR1, ousr1);
+#endif /* SIGUSR1 */
+
+ /* Turn back on signals */
+ sm_allsignals(false);
+
+ (void) execve(SaveArgv[0], (ARGV_T) SaveArgv, (ARGV_T) ExternalEnviron);
+ save_errno = errno;
+
+ /* block signals again and restore needed signals */
+ sm_allsignals(true);
+
+ /* For finis() events */
+ (void) sm_signal(SIGALRM, oalrm);
+
+#ifdef SIGUSR1
+ /* For debugging finis() */
+ (void) sm_signal(SIGUSR1, ousr1);
+#endif /* SIGUSR1 */
+
+ errno = save_errno;
+ if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, NOQID, "could not exec %s: %s",
+ SaveArgv[0], sm_errstring(errno));
+ finis(false, true, EX_OSFILE);
+ /* NOTREACHED */
+}
+/*
+** MYHOSTNAME -- return the name of this host.
+**
+** Parameters:
+** hostbuf -- a place to return the name of this host.
+** size -- the size of hostbuf.
+**
+** Returns:
+** A list of aliases for this host.
+**
+** Side Effects:
+** Adds numeric codes to $=w.
+*/
+
+struct hostent *
+myhostname(hostbuf, size)
+ char hostbuf[];
+ int size;
+{
+ register struct hostent *hp;
+
+ if (gethostname(hostbuf, size) < 0 || hostbuf[0] == '\0')
+ (void) sm_strlcpy(hostbuf, "localhost", size);
+ hp = sm_gethostbyname(hostbuf, InetMode);
+#if NETINET && NETINET6
+ if (hp == NULL && InetMode == AF_INET6)
+ {
+ /*
+ ** It's possible that this IPv6 enabled machine doesn't
+ ** actually have any IPv6 interfaces and, therefore, no
+ ** IPv6 addresses. Fall back to AF_INET.
+ */
+
+ hp = sm_gethostbyname(hostbuf, AF_INET);
+ }
+#endif /* NETINET && NETINET6 */
+ if (hp == NULL)
+ return NULL;
+ if (strchr(hp->h_name, '.') != NULL || strchr(hostbuf, '.') == NULL)
+ (void) cleanstrcpy(hostbuf, hp->h_name, size);
+
+#if NETINFO
+ if (strchr(hostbuf, '.') == NULL)
+ {
+ char *domainname;
+
+ domainname = ni_propval("/locations", NULL, "resolver",
+ "domain", '\0');
+ if (domainname != NULL &&
+ strlen(domainname) + strlen(hostbuf) + 1 < size)
+ (void) sm_strlcat2(hostbuf, ".", domainname, size);
+ }
+#endif /* NETINFO */
+
+ /*
+ ** If there is still no dot in the name, try looking for a
+ ** dotted alias.
+ */
+
+ if (strchr(hostbuf, '.') == NULL)
+ {
+ char **ha;
+
+ for (ha = hp->h_aliases; ha != NULL && *ha != NULL; ha++)
+ {
+ if (strchr(*ha, '.') != NULL)
+ {
+ (void) cleanstrcpy(hostbuf, *ha, size - 1);
+ hostbuf[size - 1] = '\0';
+ break;
+ }
+ }
+ }
+
+ /*
+ ** If _still_ no dot, wait for a while and try again -- it is
+ ** possible that some service is starting up. This can result
+ ** in excessive delays if the system is badly configured, but
+ ** there really isn't a way around that, particularly given that
+ ** the config file hasn't been read at this point.
+ ** All in all, a bit of a mess.
+ */
+
+ if (strchr(hostbuf, '.') == NULL &&
+ !getcanonname(hostbuf, size, true, NULL))
+ {
+ sm_syslog(LOG_CRIT, NOQID,
+ "My unqualified host name (%s) unknown; sleeping for retry",
+ hostbuf);
+ message("My unqualified host name (%s) unknown; sleeping for retry",
+ hostbuf);
+ (void) sleep(60);
+ if (!getcanonname(hostbuf, size, true, NULL))
+ {
+ sm_syslog(LOG_ALERT, NOQID,
+ "unable to qualify my own domain name (%s) -- using short name",
+ hostbuf);
+ message("WARNING: unable to qualify my own domain name (%s) -- using short name",
+ hostbuf);
+ }
+ }
+ return hp;
+}
+/*
+** ADDRCMP -- compare two host addresses
+**
+** Parameters:
+** hp -- hostent structure for the first address
+** ha -- actual first address
+** sa -- second address
+**
+** Returns:
+** 0 -- if ha and sa match
+** else -- they don't match
+*/
+
+static int
+addrcmp(hp, ha, sa)
+ struct hostent *hp;
+ char *ha;
+ SOCKADDR *sa;
+{
+#if NETINET6
+ unsigned char *a;
+#endif /* NETINET6 */
+
+ switch (sa->sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (hp->h_addrtype == AF_INET)
+ return memcmp(ha, (char *) &sa->sin.sin_addr, INADDRSZ);
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ a = (unsigned char *) &sa->sin6.sin6_addr;
+
+ /* Straight binary comparison */
+ if (hp->h_addrtype == AF_INET6)
+ return memcmp(ha, a, IN6ADDRSZ);
+
+ /* If IPv4-mapped IPv6 address, compare the IPv4 section */
+ if (hp->h_addrtype == AF_INET &&
+ IN6_IS_ADDR_V4MAPPED(&sa->sin6.sin6_addr))
+ return memcmp(a + IN6ADDRSZ - INADDRSZ, ha, INADDRSZ);
+ break;
+#endif /* NETINET6 */
+ }
+ return -1;
+}
+/*
+** GETAUTHINFO -- get the real host name associated with a file descriptor
+**
+** Uses RFC1413 protocol to try to get info from the other end.
+**
+** Parameters:
+** fd -- the descriptor
+** may_be_forged -- an outage that is set to true if the
+** forward lookup of RealHostName does not match
+** RealHostAddr; set to false if they do match.
+**
+** Returns:
+** The user@host information associated with this descriptor.
+*/
+
+static jmp_buf CtxAuthTimeout;
+
+static void
+authtimeout()
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxAuthTimeout, 1);
+}
+
+char *
+getauthinfo(fd, may_be_forged)
+ int fd;
+ bool *may_be_forged;
+{
+ unsigned short SM_NONVOLATILE port = 0;
+ SOCKADDR_LEN_T falen;
+ register char *volatile p = NULL;
+ SOCKADDR la;
+ SOCKADDR_LEN_T lalen;
+#ifndef NO_GETSERVBYNAME
+ register struct servent *sp;
+# if NETINET
+ static unsigned short port4 = 0;
+# endif /* NETINET */
+# if NETINET6
+ static unsigned short port6 = 0;
+# endif /* NETINET6 */
+#endif /* ! NO_GETSERVBYNAME */
+ volatile int s;
+ int i = 0;
+ size_t len;
+ SM_EVENT *ev;
+ int nleft;
+ struct hostent *hp;
+ char *ostype = NULL;
+ char **ha;
+ char ibuf[MAXNAME + 1];
+ static char hbuf[MAXNAME * 2 + 11];
+
+ *may_be_forged = false;
+ falen = sizeof RealHostAddr;
+ if (isatty(fd) || (i = getpeername(fd, &RealHostAddr.sa, &falen)) < 0 ||
+ falen <= 0 || RealHostAddr.sa.sa_family == 0)
+ {
+ if (i < 0)
+ {
+ /*
+ ** ENOTSOCK is OK: bail on anything else, but reset
+ ** errno in this case, so a mis-report doesn't
+ ** happen later.
+ */
+
+ if (errno != ENOTSOCK)
+ return NULL;
+ errno = 0;
+ }
+ (void) sm_strlcpyn(hbuf, sizeof hbuf, 2, RealUserName,
+ "@localhost");
+ if (tTd(9, 1))
+ sm_dprintf("getauthinfo: %s\n", hbuf);
+ return hbuf;
+ }
+
+ if (RealHostName == NULL)
+ {
+ /* translate that to a host name */
+ RealHostName = newstr(hostnamebyanyaddr(&RealHostAddr));
+ if (strlen(RealHostName) > MAXNAME)
+ RealHostName[MAXNAME] = '\0'; /* XXX - 1 ? */
+ }
+
+ /* cross check RealHostName with forward DNS lookup */
+ if (anynet_ntoa(&RealHostAddr)[0] != '[' &&
+ RealHostName[0] != '[')
+ {
+ int family;
+
+ family = RealHostAddr.sa.sa_family;
+#if NETINET6 && NEEDSGETIPNODE
+ /*
+ ** If RealHostAddr is an IPv6 connection with an
+ ** IPv4-mapped address, we need RealHostName's IPv4
+ ** address(es) for addrcmp() to compare against
+ ** RealHostAddr.
+ **
+ ** Actually, we only need to do this for systems
+ ** which NEEDSGETIPNODE since the real getipnodebyname()
+ ** already does V4MAPPED address via the AI_V4MAPPEDCFG
+ ** flag. A better fix to this problem is to add this
+ ** functionality to our stub getipnodebyname().
+ */
+
+ if (family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&RealHostAddr.sin6.sin6_addr))
+ family = AF_INET;
+#endif /* NETINET6 && NEEDSGETIPNODE */
+
+ /* try to match the reverse against the forward lookup */
+ hp = sm_gethostbyname(RealHostName, family);
+ if (hp == NULL)
+ *may_be_forged = true;
+ else
+ {
+ for (ha = hp->h_addr_list; *ha != NULL; ha++)
+ {
+ if (addrcmp(hp, *ha, &RealHostAddr) == 0)
+ break;
+ }
+ *may_be_forged = *ha == NULL;
+#if NETINET6
+ freehostent(hp);
+ hp = NULL;
+#endif /* NETINET6 */
+ }
+ }
+
+ if (TimeOuts.to_ident == 0)
+ goto noident;
+
+ lalen = sizeof la;
+ switch (RealHostAddr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (getsockname(fd, &la.sa, &lalen) < 0 ||
+ lalen <= 0 ||
+ la.sa.sa_family != AF_INET)
+ {
+ /* no ident info */
+ goto noident;
+ }
+ port = RealHostAddr.sin.sin_port;
+
+ /* create ident query */
+ (void) sm_snprintf(ibuf, sizeof ibuf, "%d,%d\r\n",
+ ntohs(RealHostAddr.sin.sin_port),
+ ntohs(la.sin.sin_port));
+
+ /* create local address */
+ la.sin.sin_port = 0;
+
+ /* create foreign address */
+# ifdef NO_GETSERVBYNAME
+ RealHostAddr.sin.sin_port = htons(113);
+# else /* NO_GETSERVBYNAME */
+
+ /*
+ ** getservbyname() consumes about 5% of the time
+ ** when receiving a small message (almost all of the time
+ ** spent in this routine).
+ ** Hence we store the port in a static variable
+ ** to save this time.
+ ** The portnumber shouldn't change very often...
+ ** This code makes the assumption that the port number
+ ** is not 0.
+ */
+
+ if (port4 == 0)
+ {
+ sp = getservbyname("auth", "tcp");
+ if (sp != NULL)
+ port4 = sp->s_port;
+ else
+ port4 = htons(113);
+ }
+ RealHostAddr.sin.sin_port = port4;
+ break;
+# endif /* NO_GETSERVBYNAME */
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (getsockname(fd, &la.sa, &lalen) < 0 ||
+ lalen <= 0 ||
+ la.sa.sa_family != AF_INET6)
+ {
+ /* no ident info */
+ goto noident;
+ }
+ port = RealHostAddr.sin6.sin6_port;
+
+ /* create ident query */
+ (void) sm_snprintf(ibuf, sizeof ibuf, "%d,%d\r\n",
+ ntohs(RealHostAddr.sin6.sin6_port),
+ ntohs(la.sin6.sin6_port));
+
+ /* create local address */
+ la.sin6.sin6_port = 0;
+
+ /* create foreign address */
+# ifdef NO_GETSERVBYNAME
+ RealHostAddr.sin6.sin6_port = htons(113);
+# else /* NO_GETSERVBYNAME */
+ if (port6 == 0)
+ {
+ sp = getservbyname("auth", "tcp");
+ if (sp != NULL)
+ port6 = sp->s_port;
+ else
+ port6 = htons(113);
+ }
+ RealHostAddr.sin6.sin6_port = port6;
+ break;
+# endif /* NO_GETSERVBYNAME */
+#endif /* NETINET6 */
+ default:
+ /* no ident info */
+ goto noident;
+ }
+
+ s = -1;
+ if (setjmp(CtxAuthTimeout) != 0)
+ {
+ if (s >= 0)
+ (void) close(s);
+ goto noident;
+ }
+
+ /* put a timeout around the whole thing */
+ ev = sm_setevent(TimeOuts.to_ident, authtimeout, 0);
+
+
+ /* connect to foreign IDENT server using same address as SMTP socket */
+ s = socket(la.sa.sa_family, SOCK_STREAM, 0);
+ if (s < 0)
+ {
+ sm_clrevent(ev);
+ goto noident;
+ }
+ if (bind(s, &la.sa, lalen) < 0 ||
+ connect(s, &RealHostAddr.sa, lalen) < 0)
+ goto closeident;
+
+ if (tTd(9, 10))
+ sm_dprintf("getauthinfo: sent %s", ibuf);
+
+ /* send query */
+ if (write(s, ibuf, strlen(ibuf)) < 0)
+ goto closeident;
+
+ /* get result */
+ p = &ibuf[0];
+ nleft = sizeof ibuf - 1;
+ while ((i = read(s, p, nleft)) > 0)
+ {
+ p += i;
+ nleft -= i;
+ *p = '\0';
+ if (strchr(ibuf, '\n') != NULL || nleft <= 0)
+ break;
+ }
+ (void) close(s);
+ sm_clrevent(ev);
+ if (i < 0 || p == &ibuf[0])
+ goto noident;
+
+ if (*--p == '\n' && *--p == '\r')
+ p--;
+ *++p = '\0';
+
+ if (tTd(9, 3))
+ sm_dprintf("getauthinfo: got %s\n", ibuf);
+
+ /* parse result */
+ p = strchr(ibuf, ':');
+ if (p == NULL)
+ {
+ /* malformed response */
+ goto noident;
+ }
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ if (sm_strncasecmp(p, "userid", 6) != 0)
+ {
+ /* presumably an error string */
+ goto noident;
+ }
+ p += 6;
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p++ != ':')
+ {
+ /* either useridxx or malformed response */
+ goto noident;
+ }
+
+ /* p now points to the OSTYPE field */
+ while (isascii(*p) && isspace(*p))
+ p++;
+ ostype = p;
+ p = strchr(p, ':');
+ if (p == NULL)
+ {
+ /* malformed response */
+ goto noident;
+ }
+ else
+ {
+ char *charset;
+
+ *p = '\0';
+ charset = strchr(ostype, ',');
+ if (charset != NULL)
+ *charset = '\0';
+ }
+
+ /* 1413 says don't do this -- but it's broken otherwise */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+
+ /* p now points to the authenticated name -- copy carefully */
+ if (sm_strncasecmp(ostype, "other", 5) == 0 &&
+ (ostype[5] == ' ' || ostype[5] == '\0'))
+ {
+ (void) sm_strlcpy(hbuf, "IDENT:", sizeof hbuf);
+ cleanstrcpy(&hbuf[6], p, MAXNAME);
+ }
+ else
+ cleanstrcpy(hbuf, p, MAXNAME);
+ len = strlen(hbuf);
+ (void) sm_strlcpyn(&hbuf[len], sizeof hbuf - len, 2, "@",
+ RealHostName == NULL ? "localhost" : RealHostName);
+ goto postident;
+
+closeident:
+ (void) close(s);
+ sm_clrevent(ev);
+
+noident:
+ /* put back the original incoming port */
+ switch (RealHostAddr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (port > 0)
+ RealHostAddr.sin.sin_port = port;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (port > 0)
+ RealHostAddr.sin6.sin6_port = port;
+ break;
+#endif /* NETINET6 */
+ }
+
+ if (RealHostName == NULL)
+ {
+ if (tTd(9, 1))
+ sm_dprintf("getauthinfo: NULL\n");
+ return NULL;
+ }
+ (void) sm_strlcpy(hbuf, RealHostName, sizeof hbuf);
+
+postident:
+#if IP_SRCROUTE
+# ifndef GET_IPOPT_DST
+# define GET_IPOPT_DST(dst) (dst)
+# endif /* ! GET_IPOPT_DST */
+ /*
+ ** Extract IP source routing information.
+ **
+ ** Format of output for a connection from site a through b
+ ** through c to d:
+ ** loose: @site-c@site-b:site-a
+ ** strict: !@site-c@site-b:site-a
+ **
+ ** o - pointer within ipopt_list structure.
+ ** q - pointer within ls/ss rr route data
+ ** p - pointer to hbuf
+ */
+
+ if (RealHostAddr.sa.sa_family == AF_INET)
+ {
+ SOCKOPT_LEN_T ipoptlen;
+ int j;
+ unsigned char *q;
+ unsigned char *o;
+ int l;
+ struct IPOPTION ipopt;
+
+ ipoptlen = sizeof ipopt;
+ if (getsockopt(fd, IPPROTO_IP, IP_OPTIONS,
+ (char *) &ipopt, &ipoptlen) < 0)
+ goto noipsr;
+ if (ipoptlen == 0)
+ goto noipsr;
+ o = (unsigned char *) ipopt.IP_LIST;
+ while (o != NULL && o < (unsigned char *) &ipopt + ipoptlen)
+ {
+ switch (*o)
+ {
+ case IPOPT_EOL:
+ o = NULL;
+ break;
+
+ case IPOPT_NOP:
+ o++;
+ break;
+
+ case IPOPT_SSRR:
+ case IPOPT_LSRR:
+ /*
+ ** Source routing.
+ ** o[0] is the option type (loose/strict).
+ ** o[1] is the length of this option,
+ ** including option type and
+ ** length.
+ ** o[2] is the pointer into the route
+ ** data.
+ ** o[3] begins the route data.
+ */
+
+ p = &hbuf[strlen(hbuf)];
+ l = sizeof hbuf - (hbuf - p) - 6;
+ (void) sm_snprintf(p, SPACELEFT(hbuf, p),
+ " [%s@%.*s",
+ *o == IPOPT_SSRR ? "!" : "",
+ l > 240 ? 120 : l / 2,
+ inet_ntoa(GET_IPOPT_DST(ipopt.IP_DST)));
+ i = strlen(p);
+ p += i;
+ l -= strlen(p);
+
+ j = o[1] / sizeof(struct in_addr) - 1;
+
+ /* q skips length and router pointer to data */
+ q = &o[3];
+ for ( ; j >= 0; j--)
+ {
+ struct in_addr addr;
+
+ memcpy(&addr, q, sizeof(addr));
+ (void) sm_snprintf(p,
+ SPACELEFT(hbuf, p),
+ "%c%.*s",
+ j != 0 ? '@' : ':',
+ l > 240 ? 120 :
+ j == 0 ? l : l / 2,
+ inet_ntoa(addr));
+ i = strlen(p);
+ p += i;
+ l -= i + 1;
+ q += sizeof(struct in_addr);
+ }
+ o += o[1];
+ break;
+
+ default:
+ /* Skip over option */
+ o += o[1];
+ break;
+ }
+ }
+ (void) sm_snprintf(p, SPACELEFT(hbuf, p), "]");
+ goto postipsr;
+ }
+
+noipsr:
+#endif /* IP_SRCROUTE */
+ if (RealHostName != NULL && RealHostName[0] != '[')
+ {
+ p = &hbuf[strlen(hbuf)];
+ (void) sm_snprintf(p, SPACELEFT(hbuf, p), " [%.100s]",
+ anynet_ntoa(&RealHostAddr));
+ }
+ if (*may_be_forged)
+ {
+ p = &hbuf[strlen(hbuf)];
+ (void) sm_strlcpy(p, " (may be forged)", SPACELEFT(hbuf, p));
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{client_resolve}"), "FORGED");
+ }
+
+#if IP_SRCROUTE
+postipsr:
+#endif /* IP_SRCROUTE */
+
+ /* put back the original incoming port */
+ switch (RealHostAddr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ if (port > 0)
+ RealHostAddr.sin.sin_port = port;
+ break;
+#endif /* NETINET */
+
+#if NETINET6
+ case AF_INET6:
+ if (port > 0)
+ RealHostAddr.sin6.sin6_port = port;
+ break;
+#endif /* NETINET6 */
+ }
+
+ if (tTd(9, 1))
+ sm_dprintf("getauthinfo: %s\n", hbuf);
+ return hbuf;
+}
+/*
+** HOST_MAP_LOOKUP -- turn a hostname into canonical form
+**
+** Parameters:
+** map -- a pointer to this map.
+** name -- the (presumably unqualified) hostname.
+** av -- unused -- for compatibility with other mapping
+** functions.
+** statp -- an exit status (out parameter) -- set to
+** EX_TEMPFAIL if the name server is unavailable.
+**
+** Returns:
+** The mapping, if found.
+** NULL if no mapping found.
+**
+** Side Effects:
+** Looks up the host specified in hbuf. If it is not
+** the canonical name for that host, return the canonical
+** name (unless MF_MATCHONLY is set, which will cause the
+** status only to be returned).
+*/
+
+char *
+host_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ register struct hostent *hp;
+#if NETINET
+ struct in_addr in_addr;
+#endif /* NETINET */
+#if NETINET6
+ struct in6_addr in6_addr;
+#endif /* NETINET6 */
+ char *cp, *ans = NULL;
+ register STAB *s;
+ time_t now;
+#if NAMED_BIND
+ time_t SM_NONVOLATILE retrans = 0;
+ int SM_NONVOLATILE retry = 0;
+#endif /* NAMED_BIND */
+ char hbuf[MAXNAME + 1];
+
+ /*
+ ** See if we have already looked up this name. If so, just
+ ** return it (unless expired).
+ */
+
+ now = curtime();
+ s = stab(name, ST_NAMECANON, ST_ENTER);
+ if (bitset(NCF_VALID, s->s_namecanon.nc_flags) &&
+ s->s_namecanon.nc_exp >= now)
+ {
+ if (tTd(9, 1))
+ sm_dprintf("host_map_lookup(%s) => CACHE %s\n",
+ name,
+ s->s_namecanon.nc_cname == NULL
+ ? "NULL"
+ : s->s_namecanon.nc_cname);
+ errno = s->s_namecanon.nc_errno;
+ SM_SET_H_ERRNO(s->s_namecanon.nc_herrno);
+ *statp = s->s_namecanon.nc_stat;
+ if (*statp == EX_TEMPFAIL)
+ {
+ CurEnv->e_status = "4.4.3";
+ message("851 %s: Name server timeout",
+ shortenstring(name, 33));
+ }
+ if (*statp != EX_OK)
+ return NULL;
+ if (s->s_namecanon.nc_cname == NULL)
+ {
+ syserr("host_map_lookup(%s): bogus NULL cache entry, errno = %d, h_errno = %d",
+ name,
+ s->s_namecanon.nc_errno,
+ s->s_namecanon.nc_herrno);
+ return NULL;
+ }
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ cp = map_rewrite(map, name, strlen(name), NULL);
+ else
+ cp = map_rewrite(map,
+ s->s_namecanon.nc_cname,
+ strlen(s->s_namecanon.nc_cname),
+ av);
+ return cp;
+ }
+
+ /*
+ ** If we are running without a regular network connection (usually
+ ** dial-on-demand) and we are just queueing, we want to avoid DNS
+ ** lookups because those could try to connect to a server.
+ */
+
+ if (CurEnv->e_sendmode == SM_DEFER &&
+ bitset(MF_DEFER, map->map_mflags))
+ {
+ if (tTd(9, 1))
+ sm_dprintf("host_map_lookup(%s) => DEFERRED\n", name);
+ *statp = EX_TEMPFAIL;
+ return NULL;
+ }
+
+ /*
+ ** If first character is a bracket, then it is an address
+ ** lookup. Address is copied into a temporary buffer to
+ ** strip the brackets and to preserve name if address is
+ ** unknown.
+ */
+
+ if (tTd(9, 1))
+ sm_dprintf("host_map_lookup(%s) => ", name);
+#if NAMED_BIND
+ if (map->map_timeout > 0)
+ {
+ retrans = _res.retrans;
+ _res.retrans = map->map_timeout;
+ }
+ if (map->map_retry > 0)
+ {
+ retry = _res.retry;
+ _res.retry = map->map_retry;
+ }
+#endif /* NAMED_BIND */
+
+ /* set default TTL */
+ s->s_namecanon.nc_exp = now + SM_DEFAULT_TTL;
+ if (*name != '[')
+ {
+ int ttl;
+
+ (void) sm_strlcpy(hbuf, name, sizeof hbuf);
+ if (getcanonname(hbuf, sizeof hbuf - 1, !HasWildcardMX, &ttl))
+ {
+ ans = hbuf;
+ if (ttl > 0)
+ s->s_namecanon.nc_exp = now + SM_MIN(ttl,
+ SM_DEFAULT_TTL);
+ }
+ }
+ else
+ {
+ if ((cp = strchr(name, ']')) == NULL)
+ {
+ if (tTd(9, 1))
+ sm_dprintf("FAILED\n");
+ return NULL;
+ }
+ *cp = '\0';
+
+ hp = NULL;
+#if NETINET
+ if ((in_addr.s_addr = inet_addr(&name[1])) != INADDR_NONE)
+ hp = sm_gethostbyaddr((char *)&in_addr,
+ INADDRSZ, AF_INET);
+#endif /* NETINET */
+#if NETINET6
+ if (hp == NULL &&
+ anynet_pton(AF_INET6, &name[1], &in6_addr) == 1)
+ hp = sm_gethostbyaddr((char *)&in6_addr,
+ IN6ADDRSZ, AF_INET6);
+#endif /* NETINET6 */
+ *cp = ']';
+
+ if (hp != NULL)
+ {
+ /* found a match -- copy out */
+ ans = denlstring((char *) hp->h_name, true, true);
+#if NETINET6
+ if (ans == hp->h_name)
+ {
+ static char n[MAXNAME + 1];
+
+ /* hp->h_name is about to disappear */
+ (void) sm_strlcpy(n, ans, sizeof n);
+ ans = n;
+ }
+ freehostent(hp);
+ hp = NULL;
+#endif /* NETINET6 */
+ }
+ }
+#if NAMED_BIND
+ if (map->map_timeout > 0)
+ _res.retrans = retrans;
+ if (map->map_retry > 0)
+ _res.retry = retry;
+#endif /* NAMED_BIND */
+
+ s->s_namecanon.nc_flags |= NCF_VALID; /* will be soon */
+
+ /* Found an answer */
+ if (ans != NULL)
+ {
+ s->s_namecanon.nc_stat = *statp = EX_OK;
+ if (s->s_namecanon.nc_cname != NULL)
+ sm_free(s->s_namecanon.nc_cname);
+ s->s_namecanon.nc_cname = sm_strdup_x(ans);
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ cp = map_rewrite(map, name, strlen(name), NULL);
+ else
+ cp = map_rewrite(map, ans, strlen(ans), av);
+ if (tTd(9, 1))
+ sm_dprintf("FOUND %s\n", ans);
+ return cp;
+ }
+
+
+ /* No match found */
+ s->s_namecanon.nc_errno = errno;
+#if NAMED_BIND
+ s->s_namecanon.nc_herrno = h_errno;
+ if (tTd(9, 1))
+ sm_dprintf("FAIL (%d)\n", h_errno);
+ switch (h_errno)
+ {
+ case TRY_AGAIN:
+ if (UseNameServer)
+ {
+ CurEnv->e_status = "4.4.3";
+ message("851 %s: Name server timeout",
+ shortenstring(name, 33));
+ }
+ *statp = EX_TEMPFAIL;
+ break;
+
+ case HOST_NOT_FOUND:
+ case NO_DATA:
+ *statp = EX_NOHOST;
+ break;
+
+ case NO_RECOVERY:
+ *statp = EX_SOFTWARE;
+ break;
+
+ default:
+ *statp = EX_UNAVAILABLE;
+ break;
+ }
+#else /* NAMED_BIND */
+ if (tTd(9, 1))
+ sm_dprintf("FAIL\n");
+ *statp = EX_NOHOST;
+#endif /* NAMED_BIND */
+ s->s_namecanon.nc_stat = *statp;
+ return NULL;
+}
+/*
+** HOST_MAP_INIT -- initialize host class structures
+**
+** Parameters:
+** map -- a pointer to this map.
+** args -- argument string.
+**
+** Returns:
+** true.
+*/
+
+bool
+host_map_init(map, args)
+ MAP *map;
+ char *args;
+{
+ register char *p = args;
+
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'T':
+ map->map_tapp = ++p;
+ break;
+
+ case 'm':
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 't':
+ map->map_mflags |= MF_NODEFER;
+ break;
+
+ case 'S': /* only for consistency */
+ map->map_spacesub = *++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ case 'd':
+ {
+ char *h;
+
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ h = strchr(p, ' ');
+ if (h != NULL)
+ *h = '\0';
+ map->map_timeout = convtime(p, 's');
+ if (h != NULL)
+ *h = ' ';
+ }
+ break;
+
+ case 'r':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ map->map_retry = atoi(p);
+ break;
+ }
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+ if (map->map_app != NULL)
+ map->map_app = newstr(map->map_app);
+ if (map->map_tapp != NULL)
+ map->map_tapp = newstr(map->map_tapp);
+ return true;
+}
+
+#if NETINET6
+/*
+** ANYNET_NTOP -- convert an IPv6 network address to printable form.
+**
+** Parameters:
+** s6a -- a pointer to an in6_addr structure.
+** dst -- buffer to store result in
+** dst_len -- size of dst buffer
+**
+** Returns:
+** A printable version of that structure.
+*/
+
+char *
+anynet_ntop(s6a, dst, dst_len)
+ struct in6_addr *s6a;
+ char *dst;
+ size_t dst_len;
+{
+ register char *ap;
+
+ if (IN6_IS_ADDR_V4MAPPED(s6a))
+ ap = (char *) inet_ntop(AF_INET,
+ &s6a->s6_addr[IN6ADDRSZ - INADDRSZ],
+ dst, dst_len);
+ else
+ {
+ char *d;
+ size_t sz;
+
+ /* Save pointer to beginning of string */
+ d = dst;
+
+ /* Add IPv6: protocol tag */
+ sz = sm_strlcpy(dst, "IPv6:", dst_len);
+ if (sz >= dst_len)
+ return NULL;
+ dst += sz;
+ dst_len -= sz;
+ ap = (char *) inet_ntop(AF_INET6, s6a, dst, dst_len);
+
+ /* Restore pointer to beginning of string */
+ if (ap != NULL)
+ ap = d;
+ }
+ return ap;
+}
+
+/*
+** ANYNET_PTON -- convert printed form to network address.
+**
+** Wrapper for inet_pton() which handles IPv6: labels.
+**
+** Parameters:
+** family -- address family
+** src -- string
+** dst -- destination address structure
+**
+** Returns:
+** 1 if the address was valid
+** 0 if the address wasn't parseable
+** -1 if error
+*/
+
+int
+anynet_pton(family, src, dst)
+ int family;
+ const char *src;
+ void *dst;
+{
+ if (family == AF_INET6 && sm_strncasecmp(src, "IPv6:", 5) == 0)
+ src += 5;
+ return inet_pton(family, src, dst);
+}
+#endif /* NETINET6 */
+/*
+** ANYNET_NTOA -- convert a network address to printable form.
+**
+** Parameters:
+** sap -- a pointer to a sockaddr structure.
+**
+** Returns:
+** A printable version of that sockaddr.
+*/
+
+#ifdef USE_SOCK_STREAM
+
+# if NETLINK
+# include <net/if_dl.h>
+# endif /* NETLINK */
+
+char *
+anynet_ntoa(sap)
+ register SOCKADDR *sap;
+{
+ register char *bp;
+ register char *ap;
+ int l;
+ static char buf[100];
+
+ /* check for null/zero family */
+ if (sap == NULL)
+ return "NULLADDR";
+ if (sap->sa.sa_family == 0)
+ return "0";
+
+ switch (sap->sa.sa_family)
+ {
+# if NETUNIX
+ case AF_UNIX:
+ if (sap->sunix.sun_path[0] != '\0')
+ (void) sm_snprintf(buf, sizeof buf, "[UNIX: %.64s]",
+ sap->sunix.sun_path);
+ else
+ (void) sm_strlcpy(buf, "[UNIX: localhost]", sizeof buf);
+ return buf;
+# endif /* NETUNIX */
+
+# if NETINET
+ case AF_INET:
+ return (char *) inet_ntoa(sap->sin.sin_addr);
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ ap = anynet_ntop(&sap->sin6.sin6_addr, buf, sizeof buf);
+ if (ap != NULL)
+ return ap;
+ break;
+# endif /* NETINET6 */
+
+# if NETLINK
+ case AF_LINK:
+ (void) sm_snprintf(buf, sizeof buf, "[LINK: %s]",
+ link_ntoa((struct sockaddr_dl *) &sap->sa));
+ return buf;
+# endif /* NETLINK */
+ default:
+ /* this case is needed when nothing is #defined */
+ /* in order to keep the switch syntactically correct */
+ break;
+ }
+
+ /* unknown family -- just dump bytes */
+ (void) sm_snprintf(buf, sizeof buf, "Family %d: ", sap->sa.sa_family);
+ bp = &buf[strlen(buf)];
+ ap = sap->sa.sa_data;
+ for (l = sizeof sap->sa.sa_data; --l >= 0; )
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), "%02x:",
+ *ap++ & 0377);
+ bp += 3;
+ }
+ *--bp = '\0';
+ return buf;
+}
+/*
+** HOSTNAMEBYANYADDR -- return name of host based on address
+**
+** Parameters:
+** sap -- SOCKADDR pointer
+**
+** Returns:
+** text representation of host name.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+hostnamebyanyaddr(sap)
+ register SOCKADDR *sap;
+{
+ register struct hostent *hp;
+# if NAMED_BIND
+ int saveretry;
+# endif /* NAMED_BIND */
+# if NETINET6
+ struct in6_addr in6_addr;
+# endif /* NETINET6 */
+
+# if NAMED_BIND
+ /* shorten name server timeout to avoid higher level timeouts */
+ saveretry = _res.retry;
+ if (_res.retry * _res.retrans > 20)
+ _res.retry = 20 / _res.retrans;
+# endif /* NAMED_BIND */
+
+ switch (sap->sa.sa_family)
+ {
+# if NETINET
+ case AF_INET:
+ hp = sm_gethostbyaddr((char *) &sap->sin.sin_addr,
+ INADDRSZ, AF_INET);
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ hp = sm_gethostbyaddr((char *) &sap->sin6.sin6_addr,
+ IN6ADDRSZ, AF_INET6);
+ break;
+# endif /* NETINET6 */
+
+# if NETISO
+ case AF_ISO:
+ hp = sm_gethostbyaddr((char *) &sap->siso.siso_addr,
+ sizeof sap->siso.siso_addr, AF_ISO);
+ break;
+# endif /* NETISO */
+
+# if NETUNIX
+ case AF_UNIX:
+ hp = NULL;
+ break;
+# endif /* NETUNIX */
+
+ default:
+ hp = sm_gethostbyaddr(sap->sa.sa_data, sizeof sap->sa.sa_data,
+ sap->sa.sa_family);
+ break;
+ }
+
+# if NAMED_BIND
+ _res.retry = saveretry;
+# endif /* NAMED_BIND */
+
+# if NETINET || NETINET6
+ if (hp != NULL && hp->h_name[0] != '['
+# if NETINET6
+ && inet_pton(AF_INET6, hp->h_name, &in6_addr) != 1
+# endif /* NETINET6 */
+# if NETINET
+ && inet_addr(hp->h_name) == INADDR_NONE
+# endif /* NETINET */
+ )
+ {
+ char *name;
+
+ name = denlstring((char *) hp->h_name, true, true);
+# if NETINET6
+ if (name == hp->h_name)
+ {
+ static char n[MAXNAME + 1];
+
+ /* Copy the string, hp->h_name is about to disappear */
+ (void) sm_strlcpy(n, name, sizeof n);
+ name = n;
+ }
+ freehostent(hp);
+# endif /* NETINET6 */
+ return name;
+ }
+# endif /* NETINET || NETINET6 */
+
+# if NETINET6
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+# endif /* NETINET6 */
+
+# if NETUNIX
+ if (sap->sa.sa_family == AF_UNIX && sap->sunix.sun_path[0] == '\0')
+ return "localhost";
+# endif /* NETUNIX */
+ {
+ static char buf[203];
+
+ (void) sm_snprintf(buf, sizeof buf, "[%.200s]",
+ anynet_ntoa(sap));
+ return buf;
+ }
+}
+#endif /* USE_SOCK_STREAM */
diff --git a/contrib/sendmail/src/deliver.c b/contrib/sendmail/src/deliver.c
new file mode 100644
index 0000000..46f5a91
--- /dev/null
+++ b/contrib/sendmail/src/deliver.c
@@ -0,0 +1,6168 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+#include <sys/time.h>
+
+SM_RCSID("@(#)$Id: deliver.c,v 8.940.2.3 2002/08/16 14:56:01 ca Exp $")
+
+#if HASSETUSERCONTEXT
+# include <login_cap.h>
+#endif /* HASSETUSERCONTEXT */
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+#if STARTTLS || SASL
+# include "sfsasl.h"
+#endif /* STARTTLS || SASL */
+
+void markfailure __P((ENVELOPE *, ADDRESS *, MCI *, int, bool));
+static int deliver __P((ENVELOPE *, ADDRESS *));
+static void dup_queue_file __P((ENVELOPE *, ENVELOPE *, int));
+static void mailfiletimeout __P((void));
+static int parse_hostsignature __P((char *, char **, MAILER *));
+static void sendenvelope __P((ENVELOPE *, int));
+extern MCI *mci_new __P((SM_RPOOL_T *));
+static int coloncmp __P((const char *, const char *));
+
+#if STARTTLS
+static int starttls __P((MAILER *, MCI *, ENVELOPE *));
+static int endtlsclt __P((MCI *));
+#endif /* STARTTLS */
+# if STARTTLS || SASL
+static bool iscltflgset __P((ENVELOPE *, int));
+# endif /* STARTTLS || SASL */
+
+/*
+** SENDALL -- actually send all the messages.
+**
+** Parameters:
+** e -- the envelope to send.
+** mode -- the delivery mode to use. If SM_DEFAULT, use
+** the current e->e_sendmode.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Scans the send lists and sends everything it finds.
+** Delivers any appropriate error messages.
+** If we are running in a non-interactive mode, takes the
+** appropriate action.
+*/
+
+void
+sendall(e, mode)
+ ENVELOPE *e;
+ int mode;
+{
+ register ADDRESS *q;
+ char *owner;
+ int otherowners;
+ int save_errno;
+ register ENVELOPE *ee;
+ ENVELOPE *splitenv = NULL;
+ int oldverbose = Verbose;
+ bool somedeliveries = false, expensive = false;
+ pid_t pid;
+
+ /*
+ ** If this message is to be discarded, don't bother sending
+ ** the message at all.
+ */
+
+ if (bitset(EF_DISCARD, e->e_flags))
+ {
+ if (tTd(13, 1))
+ sm_dprintf("sendall: discarding id %s\n", e->e_id);
+ e->e_flags |= EF_CLRQUEUE;
+ if (LogLevel > 9)
+ logundelrcpts(e, "discarded", 9, true);
+ else if (LogLevel > 4)
+ sm_syslog(LOG_INFO, e->e_id, "discarded");
+ markstats(e, NULL, STATS_REJECT);
+ return;
+ }
+
+ /*
+ ** If we have had global, fatal errors, don't bother sending
+ ** the message at all if we are in SMTP mode. Local errors
+ ** (e.g., a single address failing) will still cause the other
+ ** addresses to be sent.
+ */
+
+ if (bitset(EF_FATALERRS, e->e_flags) &&
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ {
+ e->e_flags |= EF_CLRQUEUE;
+ return;
+ }
+
+ /* determine actual delivery mode */
+ if (mode == SM_DEFAULT)
+ {
+ mode = e->e_sendmode;
+ if (mode != SM_VERIFY && mode != SM_DEFER &&
+ shouldqueue(e->e_msgpriority, e->e_ctime))
+ mode = SM_QUEUE;
+ }
+
+ if (tTd(13, 1))
+ {
+ sm_dprintf("\n===== SENDALL: mode %c, id %s, e_from ",
+ mode, e->e_id);
+ printaddr(&e->e_from, false);
+ sm_dprintf("\te_flags = ");
+ printenvflags(e);
+ sm_dprintf("sendqueue:\n");
+ printaddr(e->e_sendqueue, true);
+ }
+
+ /*
+ ** Do any preprocessing necessary for the mode we are running.
+ ** Check to make sure the hop count is reasonable.
+ ** Delete sends to the sender in mailing lists.
+ */
+
+ CurEnv = e;
+ if (tTd(62, 1))
+ checkfds(NULL);
+
+ if (e->e_hopcount > MaxHopCount)
+ {
+ char *recip;
+
+ if (e->e_sendqueue != NULL &&
+ e->e_sendqueue->q_paddr != NULL)
+ recip = e->e_sendqueue->q_paddr;
+ else
+ recip = "(nobody)";
+
+ errno = 0;
+ queueup(e, WILL_BE_QUEUED(mode), false);
+ e->e_flags |= EF_FATALERRS|EF_PM_NOTIFY|EF_CLRQUEUE;
+ ExitStat = EX_UNAVAILABLE;
+ syserr("554 5.4.6 Too many hops %d (%d max): from %s via %s, to %s",
+ e->e_hopcount, MaxHopCount, e->e_from.q_paddr,
+ RealHostName == NULL ? "localhost" : RealHostName,
+ recip);
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ q->q_state = QS_BADADDR;
+ q->q_status = "5.4.6";
+ q->q_rstatus = "554 5.4.6 Too many hops";
+ }
+ return;
+ }
+
+ /*
+ ** Do sender deletion.
+ **
+ ** If the sender should be queued up, skip this.
+ ** This can happen if the name server is hosed when you
+ ** are trying to send mail. The result is that the sender
+ ** is instantiated in the queue as a recipient.
+ */
+
+ if (!bitset(EF_METOO, e->e_flags) &&
+ !QS_IS_QUEUEUP(e->e_from.q_state))
+ {
+ if (tTd(13, 5))
+ {
+ sm_dprintf("sendall: QS_SENDER ");
+ printaddr(&e->e_from, false);
+ }
+ e->e_from.q_state = QS_SENDER;
+ (void) recipient(&e->e_from, &e->e_sendqueue, 0, e);
+ }
+
+ /*
+ ** Handle alias owners.
+ **
+ ** We scan up the q_alias chain looking for owners.
+ ** We discard owners that are the same as the return path.
+ */
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ register struct address *a;
+
+ for (a = q; a != NULL && a->q_owner == NULL; a = a->q_alias)
+ continue;
+ if (a != NULL)
+ q->q_owner = a->q_owner;
+
+ if (q->q_owner != NULL &&
+ !QS_IS_DEAD(q->q_state) &&
+ strcmp(q->q_owner, e->e_from.q_paddr) == 0)
+ q->q_owner = NULL;
+ }
+
+ if (tTd(13, 25))
+ {
+ sm_dprintf("\nAfter first owner pass, sendq =\n");
+ printaddr(e->e_sendqueue, true);
+ }
+
+ owner = "";
+ otherowners = 1;
+ while (owner != NULL && otherowners > 0)
+ {
+ if (tTd(13, 28))
+ sm_dprintf("owner = \"%s\", otherowners = %d\n",
+ owner, otherowners);
+ owner = NULL;
+ otherowners = bitset(EF_SENDRECEIPT, e->e_flags) ? 1 : 0;
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (tTd(13, 30))
+ {
+ sm_dprintf("Checking ");
+ printaddr(q, false);
+ }
+ if (QS_IS_DEAD(q->q_state))
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... QS_IS_DEAD\n");
+ continue;
+ }
+ if (tTd(13, 29) && !tTd(13, 30))
+ {
+ sm_dprintf("Checking ");
+ printaddr(q, false);
+ }
+
+ if (q->q_owner != NULL)
+ {
+ if (owner == NULL)
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... First owner = \"%s\"\n",
+ q->q_owner);
+ owner = q->q_owner;
+ }
+ else if (owner != q->q_owner)
+ {
+ if (strcmp(owner, q->q_owner) == 0)
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... Same owner = \"%s\"\n",
+ owner);
+
+ /* make future comparisons cheap */
+ q->q_owner = owner;
+ }
+ else
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... Another owner \"%s\"\n",
+ q->q_owner);
+ otherowners++;
+ }
+ owner = q->q_owner;
+ }
+ else if (tTd(13, 40))
+ sm_dprintf(" ... Same owner = \"%s\"\n",
+ owner);
+ }
+ else
+ {
+ if (tTd(13, 40))
+ sm_dprintf(" ... Null owner\n");
+ otherowners++;
+ }
+
+ if (QS_IS_BADADDR(q->q_state))
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... QS_IS_BADADDR\n");
+ continue;
+ }
+
+ if (QS_IS_QUEUEUP(q->q_state))
+ {
+ MAILER *m = q->q_mailer;
+
+ /*
+ ** If we have temporary address failures
+ ** (e.g., dns failure) and a fallback MX is
+ ** set, send directly to the fallback MX host.
+ */
+
+ if (FallBackMX != NULL &&
+ !wordinclass(FallBackMX, 'w') &&
+ mode != SM_VERIFY &&
+ !bitnset(M_NOMX, m->m_flags) &&
+ strcmp(m->m_mailer, "[IPC]") == 0 &&
+ m->m_argv[0] != NULL &&
+ strcmp(m->m_argv[0], "TCP") == 0)
+ {
+ int len;
+ char *p;
+
+ if (tTd(13, 30))
+ sm_dprintf(" ... FallBackMX\n");
+
+ len = strlen(FallBackMX) + 1;
+ p = sm_rpool_malloc_x(e->e_rpool, len);
+ (void) sm_strlcpy(p, FallBackMX, len);
+ q->q_state = QS_OK;
+ q->q_host = p;
+ }
+ else
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... QS_IS_QUEUEUP\n");
+ continue;
+ }
+ }
+
+ /*
+ ** If this mailer is expensive, and if we don't
+ ** want to make connections now, just mark these
+ ** addresses and return. This is useful if we
+ ** want to batch connections to reduce load. This
+ ** will cause the messages to be queued up, and a
+ ** daemon will come along to send the messages later.
+ */
+
+ if (NoConnect && !Verbose &&
+ bitnset(M_EXPENSIVE, q->q_mailer->m_flags))
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... expensive\n");
+ q->q_state = QS_QUEUEUP;
+ expensive = true;
+ }
+ else if (bitnset(M_HOLD, q->q_mailer->m_flags) &&
+ QueueLimitId == NULL &&
+ QueueLimitSender == NULL &&
+ QueueLimitRecipient == NULL)
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... hold\n");
+ q->q_state = QS_QUEUEUP;
+ expensive = true;
+ }
+#if _FFR_QUARANTINE
+ else if (QueueMode != QM_QUARANTINE &&
+ e->e_quarmsg != NULL)
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... quarantine: %s\n",
+ e->e_quarmsg);
+ q->q_state = QS_QUEUEUP;
+ expensive = true;
+ }
+#endif /* _FFR_QUARANTINE */
+ else
+ {
+ if (tTd(13, 30))
+ sm_dprintf(" ... deliverable\n");
+ somedeliveries = true;
+ }
+ }
+
+ if (owner != NULL && otherowners > 0)
+ {
+ /*
+ ** Split this envelope into two.
+ */
+
+ ee = (ENVELOPE *) sm_rpool_malloc_x(e->e_rpool,
+ sizeof *ee);
+ STRUCTCOPY(*e, *ee);
+ ee->e_message = NULL;
+ ee->e_id = NULL;
+ assign_queueid(ee);
+
+ if (tTd(13, 1))
+ sm_dprintf("sendall: split %s into %s, owner = \"%s\", otherowners = %d\n",
+ e->e_id, ee->e_id, owner,
+ otherowners);
+
+ ee->e_header = copyheader(e->e_header, ee->e_rpool);
+ ee->e_sendqueue = copyqueue(e->e_sendqueue,
+ ee->e_rpool);
+ ee->e_errorqueue = copyqueue(e->e_errorqueue,
+ ee->e_rpool);
+ ee->e_flags = e->e_flags & ~(EF_INQUEUE|EF_CLRQUEUE|EF_FATALERRS|EF_SENDRECEIPT|EF_RET_PARAM);
+ ee->e_flags |= EF_NORECEIPT;
+ setsender(owner, ee, NULL, '\0', true);
+ if (tTd(13, 5))
+ {
+ sm_dprintf("sendall(split): QS_SENDER ");
+ printaddr(&ee->e_from, false);
+ }
+ ee->e_from.q_state = QS_SENDER;
+ ee->e_dfp = NULL;
+ ee->e_lockfp = NULL;
+ ee->e_xfp = NULL;
+ ee->e_qgrp = e->e_qgrp;
+ ee->e_qdir = e->e_qdir;
+ ee->e_errormode = EM_MAIL;
+ ee->e_sibling = splitenv;
+ ee->e_statmsg = NULL;
+#if _FFR_QUARANTINE
+ if (e->e_quarmsg != NULL)
+ ee->e_quarmsg = sm_rpool_strdup_x(ee->e_rpool,
+ e->e_quarmsg);
+#endif /* _FFR_QUARANTINE */
+ splitenv = ee;
+
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (q->q_owner == owner)
+ {
+ q->q_state = QS_CLONED;
+ if (tTd(13, 6))
+ sm_dprintf("\t... stripping %s from original envelope\n",
+ q->q_paddr);
+ }
+ }
+ for (q = ee->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (q->q_owner != owner)
+ {
+ q->q_state = QS_CLONED;
+ if (tTd(13, 6))
+ sm_dprintf("\t... dropping %s from cloned envelope\n",
+ q->q_paddr);
+ }
+ else
+ {
+ /* clear DSN parameters */
+ q->q_flags &= ~(QHASNOTIFY|Q_PINGFLAGS);
+ q->q_flags |= DefaultNotify & ~QPINGONSUCCESS;
+ if (tTd(13, 6))
+ sm_dprintf("\t... moving %s to cloned envelope\n",
+ q->q_paddr);
+ }
+ }
+
+ if (mode != SM_VERIFY && bitset(EF_HAS_DF, e->e_flags))
+ dup_queue_file(e, ee, DATAFL_LETTER);
+
+ /*
+ ** Give the split envelope access to the parent
+ ** transcript file for errors obtained while
+ ** processing the recipients (done before the
+ ** envelope splitting).
+ */
+
+ if (e->e_xfp != NULL)
+ ee->e_xfp = sm_io_dup(e->e_xfp);
+
+ /* failed to dup e->e_xfp, start a new transcript */
+ if (ee->e_xfp == NULL)
+ openxscript(ee);
+
+ if (mode != SM_VERIFY && LogLevel > 4)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: clone: owner=%s",
+ ee->e_id, owner);
+ }
+ }
+
+ if (owner != NULL)
+ {
+ setsender(owner, e, NULL, '\0', true);
+ if (tTd(13, 5))
+ {
+ sm_dprintf("sendall(owner): QS_SENDER ");
+ printaddr(&e->e_from, false);
+ }
+ e->e_from.q_state = QS_SENDER;
+ e->e_errormode = EM_MAIL;
+ e->e_flags |= EF_NORECEIPT;
+ e->e_flags &= ~EF_FATALERRS;
+ }
+
+ /* if nothing to be delivered, just queue up everything */
+ if (!somedeliveries && !WILL_BE_QUEUED(mode) &&
+ mode != SM_VERIFY)
+ {
+ time_t now;
+
+ if (tTd(13, 29))
+ sm_dprintf("No deliveries: auto-queuing\n");
+ mode = SM_QUEUE;
+ now = curtime();
+
+ /* treat this as a delivery in terms of counting tries */
+ e->e_dtime = now;
+ if (!expensive)
+ e->e_ntries++;
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ ee->e_dtime = now;
+ if (!expensive)
+ ee->e_ntries++;
+ }
+ }
+
+ if ((WILL_BE_QUEUED(mode) || mode == SM_FORK ||
+ (mode != SM_VERIFY && SuperSafe == SAFE_REALLY)) &&
+ (!bitset(EF_INQUEUE, e->e_flags) || splitenv != NULL))
+ {
+ bool msync;
+
+ /*
+ ** Be sure everything is instantiated in the queue.
+ ** Split envelopes first in case the machine crashes.
+ ** If the original were done first, we may lose
+ ** recipients.
+ */
+
+#if !HASFLOCK
+ msync = false;
+#else /* !HASFLOCK */
+ msync = mode == SM_FORK;
+#endif /* !HASFLOCK */
+
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ queueup(ee, WILL_BE_QUEUED(mode), msync);
+ queueup(e, WILL_BE_QUEUED(mode), msync);
+ }
+
+ if (tTd(62, 10))
+ checkfds("after envelope splitting");
+
+ /*
+ ** If we belong in background, fork now.
+ */
+
+ if (tTd(13, 20))
+ {
+ sm_dprintf("sendall: final mode = %c\n", mode);
+ if (tTd(13, 21))
+ {
+ sm_dprintf("\n================ Final Send Queue(s) =====================\n");
+ sm_dprintf("\n *** Envelope %s, e_from=%s ***\n",
+ e->e_id, e->e_from.q_paddr);
+ printaddr(e->e_sendqueue, true);
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ sm_dprintf("\n *** Envelope %s, e_from=%s ***\n",
+ ee->e_id, ee->e_from.q_paddr);
+ printaddr(ee->e_sendqueue, true);
+ }
+ sm_dprintf("==========================================================\n\n");
+ }
+ }
+ switch (mode)
+ {
+ case SM_VERIFY:
+ Verbose = 2;
+ break;
+
+ case SM_QUEUE:
+ case SM_DEFER:
+#if HASFLOCK
+ queueonly:
+#endif /* HASFLOCK */
+ if (e->e_nrcpts > 0)
+ e->e_flags |= EF_INQUEUE;
+ dropenvelope(e, splitenv != NULL, true);
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ if (ee->e_nrcpts > 0)
+ ee->e_flags |= EF_INQUEUE;
+ dropenvelope(ee, false, true);
+ }
+ return;
+
+ case SM_FORK:
+ if (e->e_xfp != NULL)
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+
+#if !HASFLOCK
+ /*
+ ** Since fcntl locking has the interesting semantic that
+ ** the lock is owned by a process, not by an open file
+ ** descriptor, we have to flush this to the queue, and
+ ** then restart from scratch in the child.
+ */
+
+ {
+ /* save id for future use */
+ char *qid = e->e_id;
+
+ /* now drop the envelope in the parent */
+ e->e_flags |= EF_INQUEUE;
+ dropenvelope(e, splitenv != NULL, false);
+
+ /* arrange to reacquire lock after fork */
+ e->e_id = qid;
+ }
+
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ /* save id for future use */
+ char *qid = ee->e_id;
+
+ /* drop envelope in parent */
+ ee->e_flags |= EF_INQUEUE;
+ dropenvelope(ee, false, false);
+
+ /* and save qid for reacquisition */
+ ee->e_id = qid;
+ }
+
+#endif /* !HASFLOCK */
+
+ /*
+ ** Since the delivery may happen in a child and the parent
+ ** does not wait, the parent may close the maps thereby
+ ** removing any shared memory used by the map. Therefore,
+ ** close the maps now so the child will dynamically open
+ ** them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("deliver: fork 1");
+#if HASFLOCK
+ goto queueonly;
+#else /* HASFLOCK */
+ e->e_id = NULL;
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ ee->e_id = NULL;
+ return;
+#endif /* HASFLOCK */
+ }
+ else if (pid > 0)
+ {
+#if HASFLOCK
+ /* be sure we leave the temp files to our child */
+ /* close any random open files in the envelope */
+ closexscript(e);
+ if (e->e_dfp != NULL)
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_dfp = NULL;
+ e->e_flags &= ~EF_HAS_DF;
+
+ /* can't call unlockqueue to avoid unlink of xfp */
+ if (e->e_lockfp != NULL)
+ (void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
+ else
+ syserr("%s: sendall: null lockfp", e->e_id);
+ e->e_lockfp = NULL;
+#endif /* HASFLOCK */
+
+ /* make sure the parent doesn't own the envelope */
+ e->e_id = NULL;
+
+#if USE_DOUBLE_FORK
+ /* catch intermediate zombie */
+ (void) waitfor(pid);
+#endif /* USE_DOUBLE_FORK */
+ return;
+ }
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** Since we have accepted responsbility for the message,
+ ** change the SIGTERM handler. intsig() (the old handler)
+ ** would remove the envelope if this was a command line
+ ** message submission.
+ */
+
+ (void) sm_signal(SIGTERM, SIG_DFL);
+
+#if USE_DOUBLE_FORK
+ /* double fork to avoid zombies */
+ pid = fork();
+ if (pid > 0)
+ exit(EX_OK);
+ save_errno = errno;
+#endif /* USE_DOUBLE_FORK */
+
+ CurrentPid = getpid();
+
+ /* be sure we are immune from the terminal */
+ disconnect(2, e);
+ clearstats();
+
+ /* prevent parent from waiting if there was an error */
+ if (pid < 0)
+ {
+ errno = save_errno;
+ syserr("deliver: fork 2");
+#if HASFLOCK
+ e->e_flags |= EF_INQUEUE;
+#else /* HASFLOCK */
+ e->e_id = NULL;
+#endif /* HASFLOCK */
+ finis(true, true, ExitStat);
+ }
+
+ /* be sure to give error messages in child */
+ QuickAbort = false;
+
+ /*
+ ** Close any cached connections.
+ **
+ ** We don't send the QUIT protocol because the parent
+ ** still knows about the connection.
+ **
+ ** This should only happen when delivering an error
+ ** message.
+ */
+
+ mci_flush(false, NULL);
+
+#if HASFLOCK
+ break;
+#else /* HASFLOCK */
+
+ /*
+ ** Now reacquire and run the various queue files.
+ */
+
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ ENVELOPE *sibling = ee->e_sibling;
+
+ (void) dowork(ee->e_qgrp, ee->e_qdir, ee->e_id,
+ false, false, ee);
+ ee->e_sibling = sibling;
+ }
+ (void) dowork(e->e_qgrp, e->e_qdir, e->e_id,
+ false, false, e);
+ finis(true, true, ExitStat);
+#endif /* HASFLOCK */
+ }
+
+ sendenvelope(e, mode);
+ dropenvelope(e, true, true);
+ for (ee = splitenv; ee != NULL; ee = ee->e_sibling)
+ {
+ CurEnv = ee;
+ if (mode != SM_VERIFY)
+ openxscript(ee);
+ sendenvelope(ee, mode);
+ dropenvelope(ee, true, true);
+ }
+ CurEnv = e;
+
+ Verbose = oldverbose;
+ if (mode == SM_FORK)
+ finis(true, true, ExitStat);
+}
+
+static void
+sendenvelope(e, mode)
+ register ENVELOPE *e;
+ int mode;
+{
+ register ADDRESS *q;
+ bool didany;
+
+ if (tTd(13, 10))
+ sm_dprintf("sendenvelope(%s) e_flags=0x%lx\n",
+ e->e_id == NULL ? "[NOQUEUE]" : e->e_id,
+ e->e_flags);
+ if (LogLevel > 80)
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "sendenvelope, flags=0x%lx",
+ e->e_flags);
+
+ /*
+ ** If we have had global, fatal errors, don't bother sending
+ ** the message at all if we are in SMTP mode. Local errors
+ ** (e.g., a single address failing) will still cause the other
+ ** addresses to be sent.
+ */
+
+ if (bitset(EF_FATALERRS, e->e_flags) &&
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ {
+ e->e_flags |= EF_CLRQUEUE;
+ return;
+ }
+
+ /*
+ ** Don't attempt deliveries if we want to bounce now
+ ** or if deliver-by time is exceeded.
+ */
+
+ if (!bitset(EF_RESPONSE, e->e_flags) &&
+ (TimeOuts.to_q_return[e->e_timeoutclass] == NOW ||
+ (IS_DLVR_RETURN(e) && e->e_deliver_by > 0 &&
+ curtime() > e->e_ctime + e->e_deliver_by)))
+ return;
+
+ /*
+ ** Run through the list and send everything.
+ **
+ ** Set EF_GLOBALERRS so that error messages during delivery
+ ** result in returned mail.
+ */
+
+ e->e_nsent = 0;
+ e->e_flags |= EF_GLOBALERRS;
+
+ macdefine(&e->e_macro, A_PERM, macid("{envid}"), e->e_envid);
+ macdefine(&e->e_macro, A_PERM, macid("{bodytype}"), e->e_bodytype);
+ didany = false;
+
+ if (!bitset(EF_SPLIT, e->e_flags))
+ {
+ ENVELOPE *oldsib;
+ ENVELOPE *ee;
+
+ /*
+ ** Save old sibling and set it to NULL to avoid
+ ** queueing up the same envelopes again.
+ ** This requires that envelopes in that list have
+ ** been take care of before (or at some other place).
+ */
+
+ oldsib = e->e_sibling;
+ e->e_sibling = NULL;
+ if (!split_by_recipient(e) &&
+ bitset(EF_FATALERRS, e->e_flags))
+ {
+ if (OpMode == MD_SMTP || OpMode == MD_DAEMON)
+ e->e_flags |= EF_CLRQUEUE;
+ return;
+ }
+ for (ee = e->e_sibling; ee != NULL; ee = ee->e_sibling)
+ queueup(ee, false, true);
+
+ /* clean up */
+ for (ee = e->e_sibling; ee != NULL; ee = ee->e_sibling)
+ {
+ /* now unlock the job */
+ closexscript(ee);
+ unlockqueue(ee);
+
+ /* this envelope is marked unused */
+ if (ee->e_dfp != NULL)
+ {
+ (void) sm_io_close(ee->e_dfp, SM_TIME_DEFAULT);
+ ee->e_dfp = NULL;
+ }
+ ee->e_id = NULL;
+ ee->e_flags &= ~EF_HAS_DF;
+ }
+ e->e_sibling = oldsib;
+ }
+
+ /* now run through the queue */
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+#if XDEBUG
+ char wbuf[MAXNAME + 20];
+
+ (void) sm_snprintf(wbuf, sizeof wbuf, "sendall(%.*s)",
+ MAXNAME, q->q_paddr);
+ checkfd012(wbuf);
+#endif /* XDEBUG */
+ if (mode == SM_VERIFY)
+ {
+ e->e_to = q->q_paddr;
+ if (QS_IS_SENDABLE(q->q_state))
+ {
+ if (q->q_host != NULL && q->q_host[0] != '\0')
+ message("deliverable: mailer %s, host %s, user %s",
+ q->q_mailer->m_name,
+ q->q_host,
+ q->q_user);
+ else
+ message("deliverable: mailer %s, user %s",
+ q->q_mailer->m_name,
+ q->q_user);
+ }
+ }
+ else if (QS_IS_OK(q->q_state))
+ {
+ /*
+ ** Checkpoint the send list every few addresses
+ */
+
+ if (CheckpointInterval > 0 &&
+ e->e_nsent >= CheckpointInterval)
+ {
+ queueup(e, false, false);
+ e->e_nsent = 0;
+ }
+ (void) deliver(e, q);
+ didany = true;
+ }
+ }
+ if (didany)
+ {
+ e->e_dtime = curtime();
+ e->e_ntries++;
+ }
+
+#if XDEBUG
+ checkfd012("end of sendenvelope");
+#endif /* XDEBUG */
+}
+
+#if REQUIRES_DIR_FSYNC
+/*
+** SYNC_DIR -- fsync a directory based on a filename
+**
+** Parameters:
+** filename -- path of file
+** panic -- panic?
+**
+** Returns:
+** none
+*/
+
+void
+sync_dir(filename, panic)
+ char *filename;
+ bool panic;
+{
+ int dirfd;
+ char *dirp;
+ char dir[MAXPATHLEN];
+
+ /* filesystems which require the directory be synced */
+ dirp = strrchr(filename, '/');
+ if (dirp != NULL)
+ {
+ if (sm_strlcpy(dir, filename, sizeof dir) >= sizeof dir)
+ return;
+ dir[dirp - filename] = '\0';
+ dirp = dir;
+ }
+ else
+ dirp = ".";
+ dirfd = open(dirp, O_RDONLY, 0700);
+ if (tTd(40,32))
+ sm_syslog(LOG_INFO, NOQID, "sync_dir: %s: fsync(%d)",
+ dirp, dirfd);
+ if (dirfd >= 0)
+ {
+ if (fsync(dirfd) < 0)
+ {
+ if (panic)
+ syserr("!sync_dir: cannot fsync directory %s",
+ dirp);
+ else if (LogLevel > 1)
+ sm_syslog(LOG_ERR, NOQID,
+ "sync_dir: cannot fsync directory %s: %s",
+ dirp, sm_errstring(errno));
+ }
+ (void) close(dirfd);
+ }
+}
+#endif /* REQUIRES_DIR_FSYNC */
+/*
+** DUP_QUEUE_FILE -- duplicate a queue file into a split queue
+**
+** Parameters:
+** e -- the existing envelope
+** ee -- the new envelope
+** type -- the queue file type (e.g., DATAFL_LETTER)
+**
+** Returns:
+** none
+*/
+
+static void
+dup_queue_file(e, ee, type)
+ ENVELOPE *e, *ee;
+ int type;
+{
+ char f1buf[MAXPATHLEN], f2buf[MAXPATHLEN];
+
+ ee->e_dfp = NULL;
+ ee->e_xfp = NULL;
+
+ /*
+ ** Make sure both are in the same directory.
+ */
+
+ (void) sm_strlcpy(f1buf, queuename(e, type), sizeof f1buf);
+ (void) sm_strlcpy(f2buf, queuename(ee, type), sizeof f2buf);
+
+ /* Force the df to disk if it's not there yet */
+ if (type == DATAFL_LETTER && e->e_dfp != NULL &&
+ sm_io_setinfo(e->e_dfp, SM_BF_COMMIT, NULL) < 0 &&
+ errno != EINVAL)
+ {
+ syserr("!dup_queue_file: can't commit %s", f1buf);
+ /* NOTREACHED */
+ }
+
+ if (link(f1buf, f2buf) < 0)
+ {
+ int save_errno = errno;
+
+ syserr("sendall: link(%s, %s)", f1buf, f2buf);
+ if (save_errno == EEXIST)
+ {
+ if (unlink(f2buf) < 0)
+ {
+ syserr("!sendall: unlink(%s): permanent",
+ f2buf);
+ /* NOTREACHED */
+ }
+ if (link(f1buf, f2buf) < 0)
+ {
+ syserr("!sendall: link(%s, %s): permanent",
+ f1buf, f2buf);
+ /* NOTREACHED */
+ }
+ }
+ }
+ SYNC_DIR(f2buf, true);
+}
+/*
+** DOFORK -- do a fork, retrying a couple of times on failure.
+**
+** This MUST be a macro, since after a vfork we are running
+** two processes on the same stack!!!
+**
+** Parameters:
+** none.
+**
+** Returns:
+** From a macro??? You've got to be kidding!
+**
+** Side Effects:
+** Modifies the ==> LOCAL <== variable 'pid', leaving:
+** pid of child in parent, zero in child.
+** -1 on unrecoverable error.
+**
+** Notes:
+** I'm awfully sorry this looks so awful. That's
+** vfork for you.....
+*/
+
+#define NFORKTRIES 5
+
+#ifndef FORK
+# define FORK fork
+#endif /* ! FORK */
+
+#define DOFORK(fORKfN) \
+{\
+ register int i;\
+\
+ for (i = NFORKTRIES; --i >= 0; )\
+ {\
+ pid = fORKfN();\
+ if (pid >= 0)\
+ break;\
+ if (i > 0)\
+ (void) sleep((unsigned) NFORKTRIES - i);\
+ }\
+}
+/*
+** DOFORK -- simple fork interface to DOFORK.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** pid of child in parent.
+** zero in child.
+** -1 on error.
+**
+** Side Effects:
+** returns twice, once in parent and once in child.
+*/
+
+pid_t
+dofork()
+{
+ register pid_t pid = -1;
+
+ DOFORK(fork);
+ return pid;
+}
+
+/*
+** COLONCMP -- compare host-signatures up to first ':' or EOS
+**
+** This takes two strings which happen to be host-signatures and
+** compares them. If the lowest preference portions of the MX-RR's
+** match (up to ':' or EOS, whichever is first), then we have
+** match. This is used for coattail-piggybacking messages during
+** message delivery.
+** If the signatures are the same up to the first ':' the remainder of
+** the signatures are then compared with a normal strcmp(). This saves
+** re-examining the first part of the signatures.
+**
+** Parameters:
+** a - first host-signature
+** b - second host-signature
+**
+** Returns:
+** HS_MATCH_NO -- no "match".
+** HS_MATCH_FIRST -- "match" for the first MX preference
+** (up to the first colon (':')).
+** HS_MATCH_FULL -- match for the entire MX record.
+**
+** Side Effects:
+** none.
+*/
+
+#define HS_MATCH_NO 0
+#define HS_MATCH_FIRST 1
+#define HS_MATCH_FULL 2
+
+static int
+coloncmp(a, b)
+ register const char *a;
+ register const char *b;
+{
+ int ret = HS_MATCH_NO;
+ int braclev = 0;
+
+ while (*a == *b++)
+ {
+ /* Need to account for IPv6 bracketed addresses */
+ if (*a == '[')
+ braclev++;
+ else if (*a == '[' && braclev > 0)
+ braclev--;
+ else if (*a == ':' && braclev <= 0)
+ {
+ ret = HS_MATCH_FIRST;
+ a++;
+ break;
+ }
+ else if (*a == '\0')
+ return HS_MATCH_FULL; /* a full match */
+ a++;
+ }
+ if (ret == HS_MATCH_NO &&
+ braclev <= 0 &&
+ ((*a == '\0' && *(b - 1) == ':') ||
+ (*a == ':' && *(b - 1) == '\0')))
+ return HS_MATCH_FIRST;
+ if (ret == HS_MATCH_FIRST && strcmp(a, b) == 0)
+ return HS_MATCH_FULL;
+
+ return ret;
+}
+/*
+** DELIVER -- Deliver a message to a list of addresses.
+**
+** This routine delivers to everyone on the same host as the
+** user on the head of the list. It is clever about mailers
+** that don't handle multiple users. It is NOT guaranteed
+** that it will deliver to all these addresses however -- so
+** deliver should be called once for each address on the
+** list.
+** Deliver tries to be as opportunistic as possible about piggybacking
+** messages. Some definitions to make understanding easier follow below.
+** Piggybacking occurs when an existing connection to a mail host can
+** be used to send the same message to more than one recipient at the
+** same time. So "no piggybacking" means one message for one recipient
+** per connection. "Intentional piggybacking" happens when the
+** recipients' host address (not the mail host address) is used to
+** attempt piggybacking. Recipients with the same host address
+** have the same mail host. "Coincidental piggybacking" relies on
+** piggybacking based on all the mail host addresses in the MX-RR. This
+** is "coincidental" in the fact it could not be predicted until the
+** MX Resource Records for the hosts were obtained and examined. For
+** example (preference order and equivalence is important, not values):
+** domain1 IN MX 10 mxhost-A
+** IN MX 20 mxhost-B
+** domain2 IN MX 4 mxhost-A
+** IN MX 8 mxhost-B
+** Domain1 and domain2 can piggyback the same message to mxhost-A or
+** mxhost-B (if mxhost-A cannot be reached).
+** "Coattail piggybacking" relaxes the strictness of "coincidental
+** piggybacking" in the hope that most significant (lowest value)
+** MX preference host(s) can create more piggybacking. For example
+** (again, preference order and equivalence is important, not values):
+** domain3 IN MX 100 mxhost-C
+** IN MX 100 mxhost-D
+** IN MX 200 mxhost-E
+** domain4 IN MX 50 mxhost-C
+** IN MX 50 mxhost-D
+** IN MX 80 mxhost-F
+** A message for domain3 and domain4 can piggyback to mxhost-C if mxhost-C
+** is available. Same with mxhost-D because in both RR's the preference
+** value is the same as mxhost-C, respectively.
+** So deliver attempts coattail piggybacking when possible. If the
+** first MX preference level hosts cannot be used then the piggybacking
+** reverts to coincidental piggybacking. Using the above example you
+** cannot deliver to mxhost-F for domain3 regardless of preference value.
+** ("Coattail" from "riding on the coattails of your predecessor" meaning
+** gaining benefit from a predecessor effort with no or little addition
+** effort. The predecessor here being the preceding MX RR).
+**
+** Parameters:
+** e -- the envelope to deliver.
+** firstto -- head of the address list to deliver to.
+**
+** Returns:
+** zero -- successfully delivered.
+** else -- some failure, see ExitStat for more info.
+**
+** Side Effects:
+** The standard input is passed off to someone.
+*/
+
+#ifndef NO_UID
+# define NO_UID -1
+#endif /* ! NO_UID */
+#ifndef NO_GID
+# define NO_GID -1
+#endif /* ! NO_GID */
+
+static int
+deliver(e, firstto)
+ register ENVELOPE *e;
+ ADDRESS *firstto;
+{
+ char *host; /* host being sent to */
+ char *user; /* user being sent to */
+ char **pvp;
+ register char **mvp;
+ register char *p;
+ register MAILER *m; /* mailer for this recipient */
+ ADDRESS *volatile ctladdr;
+#if HASSETUSERCONTEXT
+ ADDRESS *volatile contextaddr = NULL;
+#endif /* HASSETUSERCONTEXT */
+ register MCI *volatile mci;
+ register ADDRESS *SM_NONVOLATILE to = firstto;
+ volatile bool clever = false; /* running user smtp to this mailer */
+ ADDRESS *volatile tochain = NULL; /* users chain in this mailer call */
+ int rcode; /* response code */
+ SM_NONVOLATILE int lmtp_rcode = EX_OK;
+ SM_NONVOLATILE int nummxhosts = 0; /* number of MX hosts available */
+ SM_NONVOLATILE int hostnum = 0; /* current MX host index */
+ char *firstsig; /* signature of firstto */
+ volatile pid_t pid = -1;
+ char *volatile curhost;
+ SM_NONVOLATILE unsigned short port = 0;
+ SM_NONVOLATILE time_t enough = 0;
+#if NETUNIX
+ char *SM_NONVOLATILE mux_path = NULL; /* path to UNIX domain socket */
+#endif /* NETUNIX */
+ time_t xstart;
+ bool suidwarn;
+ bool anyok; /* at least one address was OK */
+ SM_NONVOLATILE bool goodmxfound = false; /* at least one MX was OK */
+ bool ovr;
+#if _FFR_QUARANTINE
+ bool quarantine;
+#endif /* _FFR_QUARANTINE */
+ int strsize;
+ int rcptcount;
+ int ret;
+ static int tobufsize = 0;
+ static char *tobuf = NULL;
+ char *rpath; /* translated return path */
+ int mpvect[2];
+ int rpvect[2];
+ char *mxhosts[MAXMXHOSTS + 1];
+ char *pv[MAXPV + 1];
+ char buf[MAXNAME + 1];
+ char cbuf[MAXPATHLEN];
+
+ errno = 0;
+ if (!QS_IS_OK(to->q_state))
+ return 0;
+
+ suidwarn = geteuid() == 0;
+
+ m = to->q_mailer;
+ host = to->q_host;
+ CurEnv = e; /* just in case */
+ e->e_statmsg = NULL;
+ SmtpError[0] = '\0';
+ xstart = curtime();
+
+ if (tTd(10, 1))
+ sm_dprintf("\n--deliver, id=%s, mailer=%s, host=`%s', first user=`%s'\n",
+ e->e_id, m->m_name, host, to->q_user);
+ if (tTd(10, 100))
+ printopenfds(false);
+
+ /*
+ ** Clear {client_*} macros if this is a bounce message to
+ ** prevent rejection by check_compat ruleset.
+ */
+
+ if (bitset(EF_RESPONSE, e->e_flags))
+ {
+ macdefine(&e->e_macro, A_PERM, macid("{client_name}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_addr}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_port}"), "");
+ macdefine(&e->e_macro, A_PERM, macid("{client_resolve}"), "");
+ }
+
+ SM_TRY
+ {
+ ADDRESS *skip_back = NULL;
+
+ /*
+ ** Do initial argv setup.
+ ** Insert the mailer name. Notice that $x expansion is
+ ** NOT done on the mailer name. Then, if the mailer has
+ ** a picky -f flag, we insert it as appropriate. This
+ ** code does not check for 'pv' overflow; this places a
+ ** manifest lower limit of 4 for MAXPV.
+ ** The from address rewrite is expected to make
+ ** the address relative to the other end.
+ */
+
+ /* rewrite from address, using rewriting rules */
+ rcode = EX_OK;
+ if (bitnset(M_UDBENVELOPE, e->e_from.q_mailer->m_flags))
+ p = e->e_sender;
+ else
+ p = e->e_from.q_paddr;
+ rpath = remotename(p, m, RF_SENDERADDR|RF_CANONICAL, &rcode, e);
+ if (strlen(rpath) > MAXSHORTSTR)
+ {
+ rpath = shortenstring(rpath, MAXSHORTSTR);
+
+ /* avoid bogus errno */
+ errno = 0;
+ syserr("remotename: huge return path %s", rpath);
+ }
+ rpath = sm_rpool_strdup_x(e->e_rpool, rpath);
+ macdefine(&e->e_macro, A_PERM, 'g', rpath);
+ macdefine(&e->e_macro, A_PERM, 'h', host);
+ Errors = 0;
+ pvp = pv;
+ *pvp++ = m->m_argv[0];
+
+ /* insert -f or -r flag as appropriate */
+ if (FromFlag &&
+ (bitnset(M_FOPT, m->m_flags) ||
+ bitnset(M_ROPT, m->m_flags)))
+ {
+ if (bitnset(M_FOPT, m->m_flags))
+ *pvp++ = "-f";
+ else
+ *pvp++ = "-r";
+ *pvp++ = rpath;
+ }
+
+ /*
+ ** Append the other fixed parts of the argv. These run
+ ** up to the first entry containing "$u". There can only
+ ** be one of these, and there are only a few more slots
+ ** in the pv after it.
+ */
+
+ for (mvp = m->m_argv; (p = *++mvp) != NULL; )
+ {
+ /* can't use strchr here because of sign extension problems */
+ while (*p != '\0')
+ {
+ if ((*p++ & 0377) == MACROEXPAND)
+ {
+ if (*p == 'u')
+ break;
+ }
+ }
+
+ if (*p != '\0')
+ break;
+
+ /* this entry is safe -- go ahead and process it */
+ expand(*mvp, buf, sizeof buf, e);
+ *pvp++ = sm_rpool_strdup_x(e->e_rpool, buf);
+ if (pvp >= &pv[MAXPV - 3])
+ {
+ syserr("554 5.3.5 Too many parameters to %s before $u",
+ pv[0]);
+ rcode = -1;
+ goto cleanup;
+ }
+ }
+
+ /*
+ ** If we have no substitution for the user name in the argument
+ ** list, we know that we must supply the names otherwise -- and
+ ** SMTP is the answer!!
+ */
+
+ if (*mvp == NULL)
+ {
+ /* running LMTP or SMTP */
+ clever = true;
+ *pvp = NULL;
+ }
+ else if (bitnset(M_LMTP, m->m_flags))
+ {
+ /* not running LMTP */
+ sm_syslog(LOG_ERR, NULL,
+ "Warning: mailer %s: LMTP flag (F=z) turned off",
+ m->m_name);
+ clrbitn(M_LMTP, m->m_flags);
+ }
+
+ /*
+ ** At this point *mvp points to the argument with $u. We
+ ** run through our address list and append all the addresses
+ ** we can. If we run out of space, do not fret! We can
+ ** always send another copy later.
+ */
+
+ e->e_to = NULL;
+ strsize = 2;
+ rcptcount = 0;
+ ctladdr = NULL;
+ if (firstto->q_signature == NULL)
+ firstto->q_signature = hostsignature(firstto->q_mailer,
+ firstto->q_host);
+ firstsig = firstto->q_signature;
+
+ for (; to != NULL; to = to->q_next)
+ {
+ /* avoid sending multiple recipients to dumb mailers */
+ if (tochain != NULL && !bitnset(M_MUSER, m->m_flags))
+ break;
+
+ /* if already sent or not for this host, don't send */
+ if (!QS_IS_OK(to->q_state)) /* already sent; look at next */
+ continue;
+
+ /*
+ ** Must be same mailer to keep grouping rcpts.
+ ** If mailers don't match: continue; sendqueue is not
+ ** sorted by mailers, so don't break;
+ */
+
+ if (to->q_mailer != firstto->q_mailer)
+ continue;
+
+ if (to->q_signature == NULL) /* for safety */
+ to->q_signature = hostsignature(to->q_mailer,
+ to->q_host);
+
+ /*
+ ** This is for coincidental and tailcoat piggybacking messages
+ ** to the same mail host. While the signatures are identical
+ ** (that's the MX-RR's are identical) we can do coincidental
+ ** piggybacking. We try hard for coattail piggybacking
+ ** with the same mail host when the next recipient has the
+ ** same host at lowest preference. It may be that this
+ ** won't work out, so 'skip_back' is maintained if a backup
+ ** to coincidental piggybacking or full signature must happen.
+ */
+
+ ret = firstto == to ? HS_MATCH_FULL :
+ coloncmp(to->q_signature, firstsig);
+ if (ret == HS_MATCH_FULL)
+ skip_back = to;
+ else if (ret == HS_MATCH_NO)
+ break;
+
+ if (!clever)
+ {
+ /* avoid overflowing tobuf */
+ strsize += strlen(to->q_paddr) + 1;
+ if (strsize > TOBUFSIZE)
+ break;
+ }
+
+ if (++rcptcount > to->q_mailer->m_maxrcpt)
+ break;
+
+ if (tTd(10, 1))
+ {
+ sm_dprintf("\nsend to ");
+ printaddr(to, false);
+ }
+
+ /* compute effective uid/gid when sending */
+ if (bitnset(M_RUNASRCPT, to->q_mailer->m_flags))
+# if HASSETUSERCONTEXT
+ contextaddr = ctladdr = getctladdr(to);
+# else /* HASSETUSERCONTEXT */
+ ctladdr = getctladdr(to);
+# endif /* HASSETUSERCONTEXT */
+
+ if (tTd(10, 2))
+ {
+ sm_dprintf("ctladdr=");
+ printaddr(ctladdr, false);
+ }
+
+ user = to->q_user;
+ e->e_to = to->q_paddr;
+
+ /*
+ ** Check to see that these people are allowed to
+ ** talk to each other.
+ ** Check also for overflow of e_msgsize.
+ */
+
+ if (m->m_maxsize != 0 &&
+ (e->e_msgsize > m->m_maxsize || e->e_msgsize < 0))
+ {
+ e->e_flags |= EF_NO_BODY_RETN;
+ if (bitnset(M_LOCALMAILER, to->q_mailer->m_flags))
+ to->q_status = "5.2.3";
+ else
+ to->q_status = "5.3.4";
+
+ /* set to->q_rstatus = NULL; or to the following? */
+ usrerrenh(to->q_status,
+ "552 Message is too large; %ld bytes max",
+ m->m_maxsize);
+ markfailure(e, to, NULL, EX_UNAVAILABLE, false);
+ giveresponse(EX_UNAVAILABLE, to->q_status, m,
+ NULL, ctladdr, xstart, e, to);
+ continue;
+ }
+ SM_SET_H_ERRNO(0);
+ ovr = true;
+
+ /* do config file checking of compatibility */
+#if _FFR_QUARANTINE
+ quarantine = (e->e_quarmsg != NULL);
+#endif /* _FFR_QUARANTINE */
+ rcode = rscheck("check_compat", e->e_from.q_paddr, to->q_paddr,
+ e, RSF_RMCOMM|RSF_COUNT, 3, NULL,
+ e->e_id);
+ if (rcode == EX_OK)
+ {
+ /* do in-code checking if not discarding */
+ if (!bitset(EF_DISCARD, e->e_flags))
+ {
+ rcode = checkcompat(to, e);
+ ovr = false;
+ }
+ }
+ if (rcode != EX_OK)
+ {
+ markfailure(e, to, NULL, rcode, ovr);
+ giveresponse(rcode, to->q_status, m,
+ NULL, ctladdr, xstart, e, to);
+ continue;
+ }
+#if _FFR_QUARANTINE
+ if (!quarantine && e->e_quarmsg != NULL)
+ {
+ /*
+ ** check_compat or checkcompat() has tried
+ ** to quarantine but that isn't supported.
+ ** Revert the attempt.
+ */
+
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), "");
+ }
+#endif /* _FFR_QUARANTINE */
+ if (bitset(EF_DISCARD, e->e_flags))
+ {
+ if (tTd(10, 5))
+ {
+ sm_dprintf("deliver: discarding recipient ");
+ printaddr(to, false);
+ }
+
+ /* pretend the message was sent */
+ /* XXX should we log something here? */
+ to->q_state = QS_DISCARDED;
+
+ /*
+ ** Remove discard bit to prevent discard of
+ ** future recipients. This is safe because the
+ ** true "global discard" has been handled before
+ ** we get here.
+ */
+
+ e->e_flags &= ~EF_DISCARD;
+ continue;
+ }
+
+ /*
+ ** Strip quote bits from names if the mailer is dumb
+ ** about them.
+ */
+
+ if (bitnset(M_STRIPQ, m->m_flags))
+ {
+ stripquotes(user);
+ stripquotes(host);
+ }
+
+ /* hack attack -- delivermail compatibility */
+ if (m == ProgMailer && *user == '|')
+ user++;
+
+ /*
+ ** If an error message has already been given, don't
+ ** bother to send to this address.
+ **
+ ** >>>>>>>>>> This clause assumes that the local mailer
+ ** >> NOTE >> cannot do any further aliasing; that
+ ** >>>>>>>>>> function is subsumed by sendmail.
+ */
+
+ if (!QS_IS_OK(to->q_state))
+ continue;
+
+ /*
+ ** See if this user name is "special".
+ ** If the user name has a slash in it, assume that this
+ ** is a file -- send it off without further ado. Note
+ ** that this type of addresses is not processed along
+ ** with the others, so we fudge on the To person.
+ */
+
+ if (strcmp(m->m_mailer, "[FILE]") == 0)
+ {
+ macdefine(&e->e_macro, A_PERM, 'u', user);
+ p = to->q_home;
+ if (p == NULL && ctladdr != NULL)
+ p = ctladdr->q_home;
+ macdefine(&e->e_macro, A_PERM, 'z', p);
+ expand(m->m_argv[1], buf, sizeof buf, e);
+ if (strlen(buf) > 0)
+ rcode = mailfile(buf, m, ctladdr, SFF_CREAT, e);
+ else
+ {
+ syserr("empty filename specification for mailer %s",
+ m->m_name);
+ rcode = EX_CONFIG;
+ }
+ giveresponse(rcode, to->q_status, m, NULL,
+ ctladdr, xstart, e, to);
+ markfailure(e, to, NULL, rcode, true);
+ e->e_nsent++;
+ if (rcode == EX_OK)
+ {
+ to->q_state = QS_SENT;
+ if (bitnset(M_LOCALMAILER, m->m_flags) &&
+ bitset(QPINGONSUCCESS, to->q_flags))
+ {
+ to->q_flags |= QDELIVERED;
+ to->q_status = "2.1.5";
+ (void) sm_io_fprintf(e->e_xfp,
+ SM_TIME_DEFAULT,
+ "%s... Successfully delivered\n",
+ to->q_paddr);
+ }
+ }
+ to->q_statdate = curtime();
+ markstats(e, to, STATS_NORMAL);
+ continue;
+ }
+
+ /*
+ ** Address is verified -- add this user to mailer
+ ** argv, and add it to the print list of recipients.
+ */
+
+ /* link together the chain of recipients */
+ to->q_tchain = tochain;
+ tochain = to;
+ e->e_to = "[CHAIN]";
+
+ macdefine(&e->e_macro, A_PERM, 'u', user); /* to user */
+ p = to->q_home;
+ if (p == NULL && ctladdr != NULL)
+ p = ctladdr->q_home;
+ macdefine(&e->e_macro, A_PERM, 'z', p); /* user's home */
+
+ /* set the ${dsn_notify} macro if applicable */
+ if (bitset(QHASNOTIFY, to->q_flags))
+ {
+ char notify[MAXLINE];
+
+ notify[0] = '\0';
+ if (bitset(QPINGONSUCCESS, to->q_flags))
+ (void) sm_strlcat(notify, "SUCCESS,",
+ sizeof notify);
+ if (bitset(QPINGONFAILURE, to->q_flags))
+ (void) sm_strlcat(notify, "FAILURE,",
+ sizeof notify);
+ if (bitset(QPINGONDELAY, to->q_flags))
+ (void) sm_strlcat(notify, "DELAY,",
+ sizeof notify);
+
+ /* Set to NEVER or drop trailing comma */
+ if (notify[0] == '\0')
+ (void) sm_strlcat(notify, "NEVER",
+ sizeof notify);
+ else
+ notify[strlen(notify) - 1] = '\0';
+
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{dsn_notify}"), notify);
+ }
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_notify}"), NULL);
+
+ /*
+ ** Expand out this user into argument list.
+ */
+
+ if (!clever)
+ {
+ expand(*mvp, buf, sizeof buf, e);
+ *pvp++ = sm_rpool_strdup_x(e->e_rpool, buf);
+ if (pvp >= &pv[MAXPV - 2])
+ {
+ /* allow some space for trailing parms */
+ break;
+ }
+ }
+ }
+
+ /* see if any addresses still exist */
+ if (tochain == NULL)
+ {
+ rcode = 0;
+ goto cleanup;
+ }
+
+ /* print out messages as full list */
+ strsize = 1;
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ strsize += strlen(to->q_paddr) + 1;
+ if (strsize < TOBUFSIZE)
+ strsize = TOBUFSIZE;
+ if (strsize > tobufsize)
+ {
+ SM_FREE_CLR(tobuf);
+ tobuf = sm_pmalloc_x(strsize);
+ tobufsize = strsize;
+ }
+ p = tobuf;
+ *p = '\0';
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ {
+ (void) sm_strlcpyn(p, tobufsize - (p - tobuf), 2,
+ ",", to->q_paddr);
+ p += strlen(p);
+ }
+ e->e_to = tobuf + 1;
+
+ /*
+ ** Fill out any parameters after the $u parameter.
+ */
+
+ if (!clever)
+ {
+ while (*++mvp != NULL)
+ {
+ expand(*mvp, buf, sizeof buf, e);
+ *pvp++ = sm_rpool_strdup_x(e->e_rpool, buf);
+ if (pvp >= &pv[MAXPV])
+ syserr("554 5.3.0 deliver: pv overflow after $u for %s",
+ pv[0]);
+ }
+ }
+ *pvp++ = NULL;
+
+ /*
+ ** Call the mailer.
+ ** The argument vector gets built, pipes
+ ** are created as necessary, and we fork & exec as
+ ** appropriate.
+ ** If we are running SMTP, we just need to clean up.
+ */
+
+ /* XXX this seems a bit wierd */
+ if (ctladdr == NULL && m != ProgMailer && m != FileMailer &&
+ bitset(QGOODUID, e->e_from.q_flags))
+ ctladdr = &e->e_from;
+
+#if NAMED_BIND
+ if (ConfigLevel < 2)
+ _res.options &= ~(RES_DEFNAMES | RES_DNSRCH); /* XXX */
+#endif /* NAMED_BIND */
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("openmailer:");
+ printav(pv);
+ }
+ errno = 0;
+ SM_SET_H_ERRNO(0);
+ CurHostName = NULL;
+
+ /*
+ ** Deal with the special case of mail handled through an IPC
+ ** connection.
+ ** In this case we don't actually fork. We must be
+ ** running SMTP for this to work. We will return a
+ ** zero pid to indicate that we are running IPC.
+ ** We also handle a debug version that just talks to stdin/out.
+ */
+
+ curhost = NULL;
+ SmtpPhase = NULL;
+ mci = NULL;
+
+#if XDEBUG
+ {
+ char wbuf[MAXLINE];
+
+ /* make absolutely certain 0, 1, and 2 are in use */
+ (void) sm_snprintf(wbuf, sizeof wbuf, "%s... openmailer(%s)",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name);
+ checkfd012(wbuf);
+ }
+#endif /* XDEBUG */
+
+ /* check for 8-bit available */
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ bitnset(M_7BITS, m->m_flags) &&
+ (bitset(EF_DONT_MIME, e->e_flags) ||
+ !(bitset(MM_MIME8BIT, MimeMode) ||
+ (bitset(EF_IS_MIME, e->e_flags) &&
+ bitset(MM_CVTMIME, MimeMode)))))
+ {
+ e->e_status = "5.6.3";
+ usrerrenh(e->e_status,
+ "554 Cannot send 8-bit data to 7-bit destination");
+ rcode = EX_DATAERR;
+ goto give_up;
+ }
+
+ if (tTd(62, 8))
+ checkfds("before delivery");
+
+ /* check for Local Person Communication -- not for mortals!!! */
+ if (strcmp(m->m_mailer, "[LPC]") == 0)
+ {
+#if _FFR_CACHE_LPC
+ if (clever)
+ {
+ /* flush any expired connections */
+ (void) mci_scan(NULL);
+
+ /* try to get a cached connection or just a slot */
+ mci = mci_get(m->m_name, m);
+ if (mci->mci_host == NULL)
+ mci->mci_host = m->m_name;
+ CurHostName = mci->mci_host;
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ message("Using cached SMTP/LPC connection for %s...",
+ m->m_name);
+ mci->mci_deliveries++;
+ goto do_transfer;
+ }
+ }
+ else
+ {
+ mci = mci_new(e->e_rpool);
+ }
+ mci->mci_in = smioin;
+ mci->mci_out = smioout;
+ mci->mci_mailer = m;
+ mci->mci_host = m->m_name;
+ if (clever)
+ {
+ mci->mci_state = MCIS_OPENING;
+ mci_cache(mci);
+ }
+ else
+ mci->mci_state = MCIS_OPEN;
+#else /* _FFR_CACHE_LPC */
+ mci = mci_new(e->e_rpool);
+ mci->mci_in = smioin;
+ mci->mci_out = smioout;
+ mci->mci_state = clever ? MCIS_OPENING : MCIS_OPEN;
+ mci->mci_mailer = m;
+#endif /* _FFR_CACHE_LPC */
+ }
+ else if (strcmp(m->m_mailer, "[IPC]") == 0)
+ {
+ register int i;
+
+ if (pv[0] == NULL || pv[1] == NULL || pv[1][0] == '\0')
+ {
+ syserr("null destination for %s mailer", m->m_mailer);
+ rcode = EX_CONFIG;
+ goto give_up;
+ }
+
+# if NETUNIX
+ if (strcmp(pv[0], "FILE") == 0)
+ {
+ curhost = CurHostName = "localhost";
+ mux_path = pv[1];
+ }
+ else
+# endif /* NETUNIX */
+ {
+ CurHostName = pv[1];
+ curhost = hostsignature(m, pv[1]);
+ }
+
+ if (curhost == NULL || curhost[0] == '\0')
+ {
+ syserr("null host signature for %s", pv[1]);
+ rcode = EX_CONFIG;
+ goto give_up;
+ }
+
+ if (!clever)
+ {
+ syserr("554 5.3.5 non-clever IPC");
+ rcode = EX_CONFIG;
+ goto give_up;
+ }
+ if (pv[2] != NULL
+# if NETUNIX
+ && mux_path == NULL
+# endif /* NETUNIX */
+ )
+ {
+ port = htons((unsigned short) atoi(pv[2]));
+ if (port == 0)
+ {
+# ifdef NO_GETSERVBYNAME
+ syserr("Invalid port number: %s", pv[2]);
+# else /* NO_GETSERVBYNAME */
+ struct servent *sp = getservbyname(pv[2], "tcp");
+
+ if (sp == NULL)
+ syserr("Service %s unknown", pv[2]);
+ else
+ port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ }
+
+ nummxhosts = parse_hostsignature(curhost, mxhosts, m);
+ if (TimeOuts.to_aconnect > 0)
+ enough = curtime() + TimeOuts.to_aconnect;
+tryhost:
+ while (hostnum < nummxhosts)
+ {
+ char sep = ':';
+ char *endp;
+ static char hostbuf[MAXNAME + 1];
+
+# if NETINET6
+ if (*mxhosts[hostnum] == '[')
+ {
+ endp = strchr(mxhosts[hostnum] + 1, ']');
+ if (endp != NULL)
+ endp = strpbrk(endp + 1, ":,");
+ }
+ else
+ endp = strpbrk(mxhosts[hostnum], ":,");
+# else /* NETINET6 */
+ endp = strpbrk(mxhosts[hostnum], ":,");
+# endif /* NETINET6 */
+ if (endp != NULL)
+ {
+ sep = *endp;
+ *endp = '\0';
+ }
+
+ if (hostnum == 1 && skip_back != NULL)
+ {
+ /*
+ ** Coattail piggybacking is no longer an
+ ** option with the mail host next to be tried
+ ** no longer the lowest MX preference
+ ** (hostnum == 1 meaning we're on the second
+ ** preference). We do not try to coattail
+ ** piggyback more than the first MX preference.
+ ** Revert 'tochain' to last location for
+ ** coincidental piggybacking. This works this
+ ** easily because the q_tchain kept getting
+ ** added to the top of the linked list.
+ */
+
+ tochain = skip_back;
+ }
+
+ if (*mxhosts[hostnum] == '\0')
+ {
+ syserr("deliver: null host name in signature");
+ hostnum++;
+ if (endp != NULL)
+ *endp = sep;
+ continue;
+ }
+ (void) sm_strlcpy(hostbuf, mxhosts[hostnum],
+ sizeof hostbuf);
+ hostnum++;
+ if (endp != NULL)
+ *endp = sep;
+
+ /* see if we already know that this host is fried */
+ CurHostName = hostbuf;
+ mci = mci_get(hostbuf, m);
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ char *type;
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("openmailer: ");
+ mci_dump(mci, false);
+ }
+ CurHostName = mci->mci_host;
+ if (bitnset(M_LMTP, m->m_flags))
+ type = "L";
+ else if (bitset(MCIF_ESMTP, mci->mci_flags))
+ type = "ES";
+ else
+ type = "S";
+ message("Using cached %sMTP connection to %s via %s...",
+ type, hostbuf, m->m_name);
+ mci->mci_deliveries++;
+ break;
+ }
+ mci->mci_mailer = m;
+ if (mci->mci_exitstat != EX_OK)
+ {
+ if (mci->mci_exitstat == EX_TEMPFAIL)
+ goodmxfound = true;
+ continue;
+ }
+
+ if (mci_lock_host(mci) != EX_OK)
+ {
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.5", NULL);
+ goodmxfound = true;
+ continue;
+ }
+
+ /* try the connection */
+ sm_setproctitle(true, e, "%s %s: %s",
+ qid_printname(e),
+ hostbuf, "user open");
+# if NETUNIX
+ if (mux_path != NULL)
+ {
+ message("Connecting to %s via %s...",
+ mux_path, m->m_name);
+ i = makeconnection_ds((char *) mux_path, mci);
+ }
+ else
+# endif /* NETUNIX */
+ {
+ if (port == 0)
+ message("Connecting to %s via %s...",
+ hostbuf, m->m_name);
+ else
+ message("Connecting to %s port %d via %s...",
+ hostbuf, ntohs(port),
+ m->m_name);
+ i = makeconnection(hostbuf, port, mci, e,
+ enough);
+ }
+ mci->mci_errno = errno;
+ mci->mci_lastuse = curtime();
+ mci->mci_deliveries = 0;
+ mci->mci_exitstat = i;
+# if NAMED_BIND
+ mci->mci_herrno = h_errno;
+# endif /* NAMED_BIND */
+
+ /*
+ ** Have we tried long enough to get a connection?
+ ** If yes, skip to the fallback MX hosts
+ ** (if existent).
+ */
+
+ if (enough > 0 && mci->mci_lastuse >= enough)
+ {
+ int h;
+# if NAMED_BIND
+ extern int NumFallBackMXHosts;
+# else /* NAMED_BIND */
+ const int NumFallBackMXHosts = 0;
+# endif /* NAMED_BIND */
+
+ if (hostnum < nummxhosts && LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Timeout.to_aconnect occurred before exhausting all addresses");
+
+ /* turn off timeout if fallback available */
+ if (NumFallBackMXHosts > 0)
+ enough = 0;
+
+ /* skip to a fallback MX host */
+ h = nummxhosts - NumFallBackMXHosts;
+ if (hostnum < h)
+ hostnum = h;
+ }
+ if (i == EX_OK)
+ {
+ goodmxfound = true;
+ markstats(e, firstto, STATS_CONNECT);
+ mci->mci_state = MCIS_OPENING;
+ mci_cache(mci);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "%05d === CONNECT %s\n",
+ (int) CurrentPid,
+ hostbuf);
+ break;
+ }
+ else
+ {
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: makeconnection => stat=%d, errno=%d\n",
+ i, errno);
+ if (i == EX_TEMPFAIL)
+ goodmxfound = true;
+ mci_unlock_host(mci);
+ }
+
+ /* enter status of this host */
+ setstat(i);
+
+ /* should print some message here for -v mode */
+ }
+ if (mci == NULL)
+ {
+ syserr("deliver: no host name");
+ rcode = EX_SOFTWARE;
+ goto give_up;
+ }
+ mci->mci_pid = 0;
+ }
+ else
+ {
+ /* flush any expired connections */
+ (void) mci_scan(NULL);
+ mci = NULL;
+
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ /* try to get a cached connection */
+ mci = mci_get(m->m_name, m);
+ if (mci->mci_host == NULL)
+ mci->mci_host = m->m_name;
+ CurHostName = mci->mci_host;
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ message("Using cached LMTP connection for %s...",
+ m->m_name);
+ mci->mci_deliveries++;
+ goto do_transfer;
+ }
+ }
+
+ /* announce the connection to verbose listeners */
+ if (host == NULL || host[0] == '\0')
+ message("Connecting to %s...", m->m_name);
+ else
+ message("Connecting to %s via %s...", host, m->m_name);
+ if (TrafficLogFile != NULL)
+ {
+ char **av;
+
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d === EXEC", (int) CurrentPid);
+ for (av = pv; *av != NULL; av++)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT, " %s",
+ *av);
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "\n");
+ }
+
+#if XDEBUG
+ checkfd012("before creating mail pipe");
+#endif /* XDEBUG */
+
+ /* create a pipe to shove the mail through */
+ if (pipe(mpvect) < 0)
+ {
+ syserr("%s... openmailer(%s): pipe (to mailer)",
+ shortenstring(e->e_to, MAXSHORTSTR), m->m_name);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+
+#if XDEBUG
+ /* make sure we didn't get one of the standard I/O files */
+ if (mpvect[0] < 3 || mpvect[1] < 3)
+ {
+ syserr("%s... openmailer(%s): bogus mpvect %d %d",
+ shortenstring(e->e_to, MAXSHORTSTR), m->m_name,
+ mpvect[0], mpvect[1]);
+ printopenfds(true);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+
+ /* make sure system call isn't dead meat */
+ checkfdopen(mpvect[0], "mpvect[0]");
+ checkfdopen(mpvect[1], "mpvect[1]");
+ if (mpvect[0] == mpvect[1] ||
+ (e->e_lockfp != NULL &&
+ (mpvect[0] == sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD,
+ NULL) ||
+ mpvect[1] == sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD,
+ NULL))))
+ {
+ if (e->e_lockfp == NULL)
+ syserr("%s... openmailer(%s): overlapping mpvect %d %d",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, mpvect[0], mpvect[1]);
+ else
+ syserr("%s... openmailer(%s): overlapping mpvect %d %d, lockfp = %d",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, mpvect[0], mpvect[1],
+ sm_io_getinfo(e->e_lockfp,
+ SM_IO_WHAT_FD, NULL));
+ }
+#endif /* XDEBUG */
+
+ /* create a return pipe */
+ if (pipe(rpvect) < 0)
+ {
+ syserr("%s... openmailer(%s): pipe (from mailer)",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name);
+ (void) close(mpvect[0]);
+ (void) close(mpvect[1]);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+#if XDEBUG
+ checkfdopen(rpvect[0], "rpvect[0]");
+ checkfdopen(rpvect[1], "rpvect[1]");
+#endif /* XDEBUG */
+
+ /*
+ ** Actually fork the mailer process.
+ ** DOFORK is clever about retrying.
+ **
+ ** Dispose of SIGCHLD signal catchers that may be laying
+ ** around so that endmailer will get it.
+ */
+
+ if (e->e_xfp != NULL) /* for debugging */
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+
+
+ DOFORK(FORK);
+ /* pid is set by DOFORK */
+
+ if (pid < 0)
+ {
+ /* failure */
+ syserr("%s... openmailer(%s): cannot fork",
+ shortenstring(e->e_to, MAXSHORTSTR), m->m_name);
+ (void) close(mpvect[0]);
+ (void) close(mpvect[1]);
+ (void) close(rpvect[0]);
+ (void) close(rpvect[1]);
+ if (tTd(11, 1))
+ sm_dprintf("openmailer: NULL\n");
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+ else if (pid == 0)
+ {
+ int i;
+ int save_errno;
+ int sff;
+ int new_euid = NO_UID;
+ int new_ruid = NO_UID;
+ int new_gid = NO_GID;
+ char *user = NULL;
+ struct stat stb;
+ extern int DtableSize;
+
+ CurrentPid = getpid();
+
+ /* clear the events to turn off SIGALRMs */
+ sm_clear_events();
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+
+ if (e->e_lockfp != NULL)
+ (void) close(sm_io_getinfo(e->e_lockfp,
+ SM_IO_WHAT_FD,
+ NULL));
+
+ /* child -- set up input & exec mailer */
+ (void) sm_signal(SIGALRM, sm_signal_noop);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_IGN);
+ (void) sm_signal(SIGINT, SIG_IGN);
+ (void) sm_signal(SIGTERM, SIG_DFL);
+# ifdef SIGUSR1
+ (void) sm_signal(SIGUSR1, sm_signal_noop);
+# endif /* SIGUSR1 */
+
+ if (m != FileMailer || stat(tochain->q_user, &stb) < 0)
+ stb.st_mode = 0;
+
+# if HASSETUSERCONTEXT
+ /*
+ ** Set user resources.
+ */
+
+ if (contextaddr != NULL)
+ {
+ struct passwd *pwd;
+
+ if (contextaddr->q_ruser != NULL)
+ pwd = sm_getpwnam(contextaddr->q_ruser);
+ else
+ pwd = sm_getpwnam(contextaddr->q_user);
+ if (pwd != NULL &&
+ setusercontext(NULL, pwd, pwd->pw_uid,
+ LOGIN_SETRESOURCES|LOGIN_SETPRIORITY) == -1 &&
+ suidwarn)
+ {
+ syserr("openmailer: setusercontext() failed");
+ exit(EX_TEMPFAIL);
+ }
+ }
+# endif /* HASSETUSERCONTEXT */
+
+#if HASNICE
+ /* tweak niceness */
+ if (m->m_nice != 0)
+ (void) nice(m->m_nice);
+#endif /* HASNICE */
+
+ /* reset group id */
+ if (bitnset(M_SPECIFIC_UID, m->m_flags))
+ new_gid = m->m_gid;
+ else if (bitset(S_ISGID, stb.st_mode))
+ new_gid = stb.st_gid;
+ else if (ctladdr != NULL && ctladdr->q_gid != 0)
+ {
+ if (!DontInitGroups)
+ {
+ user = ctladdr->q_ruser;
+ if (user == NULL)
+ user = ctladdr->q_user;
+
+ if (initgroups(user,
+ ctladdr->q_gid) == -1
+ && suidwarn)
+ {
+ syserr("openmailer: initgroups(%s, %d) failed",
+ user, ctladdr->q_gid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = ctladdr->q_gid;
+ if (setgroups(1, gidset) == -1
+ && suidwarn)
+ {
+ syserr("openmailer: setgroups() failed");
+ exit(EX_TEMPFAIL);
+ }
+ }
+ new_gid = ctladdr->q_gid;
+ }
+ else
+ {
+ if (!DontInitGroups)
+ {
+ user = DefUser;
+ if (initgroups(DefUser, DefGid) == -1 &&
+ suidwarn)
+ {
+ syserr("openmailer: initgroups(%s, %d) failed",
+ DefUser, DefGid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = DefGid;
+ if (setgroups(1, gidset) == -1
+ && suidwarn)
+ {
+ syserr("openmailer: setgroups() failed");
+ exit(EX_TEMPFAIL);
+ }
+ }
+ if (m->m_gid == 0)
+ new_gid = DefGid;
+ else
+ new_gid = m->m_gid;
+ }
+ if (new_gid != NO_GID)
+ {
+ if (RunAsUid != 0 &&
+ bitnset(M_SPECIFIC_UID, m->m_flags) &&
+ new_gid != getgid() &&
+ new_gid != getegid())
+ {
+ /* Only root can change the gid */
+ syserr("openmailer: insufficient privileges to change gid, RunAsUid=%d, new_gid=%d, gid=%d, egid=%d",
+ (int) RunAsUid, (int) new_gid,
+ (int) getgid(), (int) getegid());
+ exit(EX_TEMPFAIL);
+ }
+
+ if (setgid(new_gid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setgid(%ld) failed",
+ (long) new_gid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ /* change root to some "safe" directory */
+ if (m->m_rootdir != NULL)
+ {
+ expand(m->m_rootdir, cbuf, sizeof cbuf, e);
+ if (tTd(11, 20))
+ sm_dprintf("openmailer: chroot %s\n",
+ cbuf);
+ if (chroot(cbuf) < 0)
+ {
+ syserr("openmailer: Cannot chroot(%s)",
+ cbuf);
+ exit(EX_TEMPFAIL);
+ }
+ if (chdir("/") < 0)
+ {
+ syserr("openmailer: cannot chdir(/)");
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ /* reset user id */
+ endpwent();
+ sm_mbdb_terminate();
+ if (bitnset(M_SPECIFIC_UID, m->m_flags))
+ {
+ new_euid = m->m_uid;
+
+ /*
+ ** Undo the effects of the uid change in main
+ ** for signal handling. The real uid may
+ ** be used by mailer in adding a "From "
+ ** line.
+ */
+
+ if (RealUid != 0 && RealUid != getuid())
+ {
+# if MAILER_SETUID_METHOD == USE_SETEUID
+# if HASSETREUID
+ if (setreuid(RealUid, geteuid()) < 0)
+ {
+ syserr("openmailer: setreuid(%d, %d) failed",
+ (int) RealUid, (int) geteuid());
+ exit(EX_OSERR);
+ }
+# endif /* HASSETREUID */
+# endif /* MAILER_SETUID_METHOD == USE_SETEUID */
+# if MAILER_SETUID_METHOD == USE_SETREUID
+ new_ruid = RealUid;
+# endif /* MAILER_SETUID_METHOD == USE_SETREUID */
+ }
+ }
+ else if (bitset(S_ISUID, stb.st_mode))
+ new_ruid = stb.st_uid;
+ else if (ctladdr != NULL && ctladdr->q_uid != 0)
+ new_ruid = ctladdr->q_uid;
+ else if (m->m_uid != 0)
+ new_ruid = m->m_uid;
+ else
+ new_ruid = DefUid;
+
+# if _FFR_USE_SETLOGIN
+ /* run disconnected from terminal and set login name */
+ if (setsid() >= 0 &&
+ ctladdr != NULL && ctladdr->q_uid != 0 &&
+ new_euid == ctladdr->q_uid)
+ {
+ struct passwd *pwd;
+
+ pwd = sm_getpwuid(ctladdr->q_uid);
+ if (pwd != NULL && suidwarn)
+ (void) setlogin(pwd->pw_name);
+ endpwent();
+ }
+# endif /* _FFR_USE_SETLOGIN */
+
+ if (new_euid != NO_UID)
+ {
+ if (RunAsUid != 0 && new_euid != RunAsUid)
+ {
+ /* Only root can change the uid */
+ syserr("openmailer: insufficient privileges to change uid, new_euid=%d, RunAsUid=%d",
+ (int) new_euid, (int) RunAsUid);
+ exit(EX_TEMPFAIL);
+ }
+
+ vendor_set_uid(new_euid);
+# if MAILER_SETUID_METHOD == USE_SETEUID
+ if (seteuid(new_euid) < 0 && suidwarn)
+ {
+ syserr("openmailer: seteuid(%ld) failed",
+ (long) new_euid);
+ exit(EX_TEMPFAIL);
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETEUID */
+# if MAILER_SETUID_METHOD == USE_SETREUID
+ if (setreuid(new_ruid, new_euid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setreuid(%ld, %ld) failed",
+ (long) new_ruid, (long) new_euid);
+ exit(EX_TEMPFAIL);
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETREUID */
+# if MAILER_SETUID_METHOD == USE_SETUID
+ if (new_euid != geteuid() && setuid(new_euid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setuid(%ld) failed",
+ (long) new_euid);
+ exit(EX_TEMPFAIL);
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETUID */
+ }
+ else if (new_ruid != NO_UID)
+ {
+ vendor_set_uid(new_ruid);
+ if (setuid(new_ruid) < 0 && suidwarn)
+ {
+ syserr("openmailer: setuid(%ld) failed",
+ (long) new_ruid);
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ if (tTd(11, 2))
+ sm_dprintf("openmailer: running as r/euid=%d/%d, r/egid=%d/%d\n",
+ (int) getuid(), (int) geteuid(),
+ (int) getgid(), (int) getegid());
+
+ /* move into some "safe" directory */
+ if (m->m_execdir != NULL)
+ {
+ char *q;
+
+ for (p = m->m_execdir; p != NULL; p = q)
+ {
+ q = strchr(p, ':');
+ if (q != NULL)
+ *q = '\0';
+ expand(p, cbuf, sizeof cbuf, e);
+ if (q != NULL)
+ *q++ = ':';
+ if (tTd(11, 20))
+ sm_dprintf("openmailer: trydir %s\n",
+ cbuf);
+ if (cbuf[0] != '\0' &&
+ chdir(cbuf) >= 0)
+ break;
+ }
+ }
+
+ /* Check safety of program to be run */
+ sff = SFF_ROOTOK|SFF_EXECOK;
+ if (!bitnset(DBS_RUNWRITABLEPROGRAM,
+ DontBlameSendmail))
+ sff |= SFF_NOGWFILES|SFF_NOWWFILES;
+ if (bitnset(DBS_RUNPROGRAMINUNSAFEDIRPATH,
+ DontBlameSendmail))
+ sff |= SFF_NOPATHCHECK;
+ else
+ sff |= SFF_SAFEDIRPATH;
+ ret = safefile(m->m_mailer, getuid(), getgid(),
+ user, sff, 0, NULL);
+ if (ret != 0)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Warning: program %s unsafe: %s",
+ m->m_mailer, sm_errstring(ret));
+
+ /* arrange to filter std & diag output of command */
+ (void) close(rpvect[0]);
+ if (dup2(rpvect[1], STDOUT_FILENO) < 0)
+ {
+ syserr("%s... openmailer(%s): cannot dup pipe %d for stdout",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, rpvect[1]);
+ _exit(EX_OSERR);
+ }
+ (void) close(rpvect[1]);
+
+ if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0)
+ {
+ syserr("%s... openmailer(%s): cannot dup stdout for stderr",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name);
+ _exit(EX_OSERR);
+ }
+
+ /* arrange to get standard input */
+ (void) close(mpvect[1]);
+ if (dup2(mpvect[0], STDIN_FILENO) < 0)
+ {
+ syserr("%s... openmailer(%s): cannot dup pipe %d for stdin",
+ shortenstring(e->e_to, MAXSHORTSTR),
+ m->m_name, mpvect[0]);
+ _exit(EX_OSERR);
+ }
+ (void) close(mpvect[0]);
+
+ /* arrange for all the files to be closed */
+ for (i = 3; i < DtableSize; i++)
+ {
+ register int j;
+
+ if ((j = fcntl(i, F_GETFD, 0)) != -1)
+ (void) fcntl(i, F_SETFD,
+ j | FD_CLOEXEC);
+ }
+
+# if !_FFR_USE_SETLOGIN
+ /* run disconnected from terminal */
+ (void) setsid();
+# endif /* !_FFR_USE_SETLOGIN */
+
+ /* try to execute the mailer */
+ (void) execve(m->m_mailer, (ARGV_T) pv,
+ (ARGV_T) UserEnviron);
+ save_errno = errno;
+ syserr("Cannot exec %s", m->m_mailer);
+ if (bitnset(M_LOCALMAILER, m->m_flags) ||
+ transienterror(save_errno))
+ _exit(EX_OSERR);
+ _exit(EX_UNAVAILABLE);
+ }
+
+ /*
+ ** Set up return value.
+ */
+
+ if (mci == NULL)
+ {
+ if (clever)
+ {
+ /*
+ ** Allocate from general heap, not
+ ** envelope rpool, because this mci
+ ** is going to be cached.
+ */
+
+ mci = mci_new(NULL);
+ }
+ else
+ {
+ /*
+ ** Prevent a storage leak by allocating
+ ** this from the envelope rpool.
+ */
+
+ mci = mci_new(e->e_rpool);
+ }
+ }
+ mci->mci_mailer = m;
+ if (clever)
+ {
+ mci->mci_state = MCIS_OPENING;
+ mci_cache(mci);
+ }
+ else
+ {
+ mci->mci_state = MCIS_OPEN;
+ }
+ mci->mci_pid = pid;
+ (void) close(mpvect[0]);
+ mci->mci_out = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &(mpvect[1]), SM_IO_WRONLY,
+ NULL);
+ if (mci->mci_out == NULL)
+ {
+ syserr("deliver: cannot create mailer output channel, fd=%d",
+ mpvect[1]);
+ (void) close(mpvect[1]);
+ (void) close(rpvect[0]);
+ (void) close(rpvect[1]);
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+
+ (void) close(rpvect[1]);
+ mci->mci_in = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &(rpvect[0]), SM_IO_RDONLY,
+ NULL);
+ if (mci->mci_in == NULL)
+ {
+ syserr("deliver: cannot create mailer input channel, fd=%d",
+ mpvect[1]);
+ (void) close(rpvect[0]);
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ mci->mci_out = NULL;
+ rcode = EX_OSERR;
+ goto give_up;
+ }
+ }
+
+ /*
+ ** If we are in SMTP opening state, send initial protocol.
+ */
+
+ if (bitnset(M_7BITS, m->m_flags) &&
+ (!clever || mci->mci_state == MCIS_OPENING))
+ mci->mci_flags |= MCIF_7BIT;
+ if (clever && mci->mci_state != MCIS_CLOSED)
+ {
+# if STARTTLS || SASL
+ int dotpos;
+ char *srvname;
+ extern SOCKADDR CurHostAddr;
+# endif /* STARTTLS || SASL */
+
+# if SASL
+# define DONE_AUTH(f) bitset(MCIF_AUTHACT, f)
+# endif /* SASL */
+# if STARTTLS
+# define DONE_STARTTLS(f) bitset(MCIF_TLSACT, f)
+# endif /* STARTTLS */
+# define ONLY_HELO(f) bitset(MCIF_ONLY_EHLO, f)
+# define SET_HELO(f) f |= MCIF_ONLY_EHLO
+# define CLR_HELO(f) f &= ~MCIF_ONLY_EHLO
+
+# if STARTTLS || SASL
+ /* don't use CurHostName, it is changed in many places */
+ if (mci->mci_host != NULL)
+ {
+ srvname = mci->mci_host;
+ dotpos = strlen(srvname) - 1;
+ if (dotpos >= 0)
+ {
+ if (srvname[dotpos] == '.')
+ srvname[dotpos] = '\0';
+ else
+ dotpos = -1;
+ }
+ }
+ else if (mci->mci_mailer != NULL)
+ {
+ srvname = mci->mci_mailer->m_name;
+ dotpos = -1;
+ }
+ else
+ {
+ srvname = "local";
+ dotpos = -1;
+ }
+
+ /* don't set {server_name} to NULL or "": see getauth() */
+ macdefine(&mci->mci_macro, A_TEMP, macid("{server_name}"),
+ srvname);
+
+ /* CurHostAddr is set by makeconnection() and mci_get() */
+ if (CurHostAddr.sa.sa_family != 0)
+ {
+ macdefine(&mci->mci_macro, A_TEMP,
+ macid("{server_addr}"),
+ anynet_ntoa(&CurHostAddr));
+ }
+ else if (mci->mci_mailer != NULL)
+ {
+ /* mailer name is unique, use it as address */
+ macdefine(&mci->mci_macro, A_PERM,
+ macid("{server_addr}"),
+ mci->mci_mailer->m_name);
+ }
+ else
+ {
+ /* don't set it to NULL or "": see getauth() */
+ macdefine(&mci->mci_macro, A_PERM,
+ macid("{server_addr}"), "0");
+ }
+
+ /* undo change of srvname (mci->mci_host) */
+ if (dotpos >= 0)
+ srvname[dotpos] = '.';
+
+reconnect: /* after switching to an encrypted connection */
+# endif /* STARTTLS || SASL */
+
+ /* set the current connection information */
+ e->e_mci = mci;
+# if SASL
+ mci->mci_saslcap = NULL;
+# endif /* SASL */
+ smtpinit(m, mci, e, ONLY_HELO(mci->mci_flags));
+ CLR_HELO(mci->mci_flags);
+
+ if (IS_DLVR_RETURN(e))
+ {
+ /*
+ ** Check whether other side can deliver e-mail
+ ** fast enough
+ */
+
+ if (!bitset(MCIF_DLVR_BY, mci->mci_flags))
+ {
+ e->e_status = "5.4.7";
+ usrerrenh(e->e_status,
+ "554 Server does not support Deliver By");
+ rcode = EX_UNAVAILABLE;
+ goto give_up;
+ }
+ if (e->e_deliver_by > 0 &&
+ e->e_deliver_by - (curtime() - e->e_ctime) <
+ mci->mci_min_by)
+ {
+ e->e_status = "5.4.7";
+ usrerrenh(e->e_status,
+ "554 Message can't be delivered in time; %ld < %ld",
+ e->e_deliver_by - (curtime() - e->e_ctime),
+ mci->mci_min_by);
+ rcode = EX_UNAVAILABLE;
+ goto give_up;
+ }
+ }
+
+# if STARTTLS
+ /* first TLS then AUTH to provide a security layer */
+ if (mci->mci_state != MCIS_CLOSED &&
+ !DONE_STARTTLS(mci->mci_flags))
+ {
+ int olderrors;
+ bool usetls;
+ bool saveQuickAbort = QuickAbort;
+ bool saveSuprErrs = SuprErrs;
+ char *host = NULL;
+
+ rcode = EX_OK;
+ usetls = bitset(MCIF_TLS, mci->mci_flags);
+ if (usetls)
+ usetls = !iscltflgset(e, D_NOTLS);
+
+ if (usetls)
+ {
+ host = macvalue(macid("{server_name}"), e);
+ olderrors = Errors;
+ QuickAbort = false;
+ SuprErrs = true;
+ if (rscheck("try_tls", host, NULL, e,
+ RSF_RMCOMM, 7, host, NOQID) != EX_OK
+ || Errors > olderrors)
+ usetls = false;
+ SuprErrs = saveSuprErrs;
+ QuickAbort = saveQuickAbort;
+ }
+
+ if (usetls)
+ {
+ if ((rcode = starttls(m, mci, e)) == EX_OK)
+ {
+ /* start again without STARTTLS */
+ mci->mci_flags |= MCIF_TLSACT;
+ }
+ else
+ {
+ char *s;
+
+ /*
+ ** TLS negotation failed, what to do?
+ ** fall back to unencrypted connection
+ ** or abort? How to decide?
+ ** set a macro and call a ruleset.
+ */
+
+ mci->mci_flags &= ~MCIF_TLS;
+ switch (rcode)
+ {
+ case EX_TEMPFAIL:
+ s = "TEMP";
+ break;
+ case EX_USAGE:
+ s = "USAGE";
+ break;
+ case EX_PROTOCOL:
+ s = "PROTOCOL";
+ break;
+ case EX_SOFTWARE:
+ s = "SOFTWARE";
+ break;
+
+ /* everything else is a failure */
+ default:
+ s = "FAILURE";
+ rcode = EX_TEMPFAIL;
+ }
+ macdefine(&e->e_macro, A_PERM,
+ macid("{verify}"), s);
+ }
+ }
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{verify}"), "NONE");
+ olderrors = Errors;
+ QuickAbort = false;
+ SuprErrs = true;
+
+ /*
+ ** rcode == EX_SOFTWARE is special:
+ ** the TLS negotation failed
+ ** we have to drop the connection no matter what
+ ** However, we call tls_server to give it the chance
+ ** to log the problem and return an appropriate
+ ** error code.
+ */
+
+ if (rscheck("tls_server",
+ macvalue(macid("{verify}"), e),
+ NULL, e, RSF_RMCOMM|RSF_COUNT, 5,
+ host, NOQID) != EX_OK ||
+ Errors > olderrors ||
+ rcode == EX_SOFTWARE)
+ {
+ char enhsc[ENHSCLEN];
+ extern char MsgBuf[];
+
+ if (ISSMTPCODE(MsgBuf) &&
+ extenhsc(MsgBuf + 4, ' ', enhsc) > 0)
+ {
+ p = sm_rpool_strdup_x(e->e_rpool,
+ MsgBuf);
+ }
+ else
+ {
+ p = "403 4.7.0 server not authenticated.";
+ (void) sm_strlcpy(enhsc, "4.7.0",
+ sizeof enhsc);
+ }
+ SuprErrs = saveSuprErrs;
+ QuickAbort = saveQuickAbort;
+
+ if (rcode == EX_SOFTWARE)
+ {
+ /* drop the connection */
+ mci->mci_state = MCIS_QUITING;
+ if (mci->mci_in != NULL)
+ {
+ (void) sm_io_close(mci->mci_in,
+ SM_TIME_DEFAULT);
+ mci->mci_in = NULL;
+ }
+ mci->mci_flags &= ~MCIF_TLSACT;
+ (void) endmailer(mci, e, pv);
+ }
+ else
+ {
+ /* abort transfer */
+ smtpquit(m, mci, e);
+ }
+
+ /* avoid bogus error msg */
+ mci->mci_errno = 0;
+
+ /* temp or permanent failure? */
+ rcode = (*p == '4') ? EX_TEMPFAIL
+ : EX_UNAVAILABLE;
+ mci_setstat(mci, rcode, enhsc, p);
+
+ /*
+ ** hack to get the error message into
+ ** the envelope (done in giveresponse())
+ */
+
+ (void) sm_strlcpy(SmtpError, p,
+ sizeof SmtpError);
+ }
+ QuickAbort = saveQuickAbort;
+ SuprErrs = saveSuprErrs;
+ if (DONE_STARTTLS(mci->mci_flags) &&
+ mci->mci_state != MCIS_CLOSED)
+ {
+ SET_HELO(mci->mci_flags);
+ mci->mci_flags &= ~MCIF_EXTENS;
+ goto reconnect;
+ }
+ }
+# endif /* STARTTLS */
+# if SASL
+ /* if other server supports authentication let's authenticate */
+ if (mci->mci_state != MCIS_CLOSED &&
+ mci->mci_saslcap != NULL &&
+ !DONE_AUTH(mci->mci_flags) && !iscltflgset(e, D_NOAUTH))
+ {
+ /* Should we require some minimum authentication? */
+ if ((ret = smtpauth(m, mci, e)) == EX_OK)
+ {
+ int result;
+ sasl_ssf_t *ssf = NULL;
+
+ /* Get security strength (features) */
+ result = sasl_getprop(mci->mci_conn, SASL_SSF,
+# if SASL >= 20000
+ (const void **) &ssf);
+# else /* SASL >= 20000 */
+ (void **) &ssf);
+# endif /* SASL >= 20000 */
+
+ /* XXX authid? */
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, NOQID,
+ "AUTH=client, relay=%.100s, mech=%.16s, bits=%d",
+ mci->mci_host,
+ macvalue(macid("{auth_type}"), e),
+ result == SASL_OK ? *ssf : 0);
+
+ /*
+ ** Only switch to encrypted connection
+ ** if a security layer has been negotiated
+ */
+
+ if (result == SASL_OK && *ssf > 0)
+ {
+ /*
+ ** Convert I/O layer to use SASL.
+ ** If the call fails, the connection
+ ** is aborted.
+ */
+
+ if (sfdcsasl(&mci->mci_in,
+ &mci->mci_out,
+ mci->mci_conn) == 0)
+ {
+ mci->mci_flags &= ~MCIF_EXTENS;
+ mci->mci_flags |= MCIF_AUTHACT|
+ MCIF_ONLY_EHLO;
+ goto reconnect;
+ }
+ syserr("AUTH TLS switch failed in client");
+ }
+ /* else? XXX */
+ mci->mci_flags |= MCIF_AUTHACT;
+
+ }
+ else if (ret == EX_TEMPFAIL)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "AUTH=client, relay=%.100s, temporary failure, connection abort",
+ mci->mci_host);
+ smtpquit(m, mci, e);
+
+ /* avoid bogus error msg */
+ mci->mci_errno = 0;
+ rcode = EX_TEMPFAIL;
+ mci_setstat(mci, rcode, "4.7.1", p);
+
+ /*
+ ** hack to get the error message into
+ ** the envelope (done in giveresponse())
+ */
+
+ (void) sm_strlcpy(SmtpError,
+ "Temporary AUTH failure",
+ sizeof SmtpError);
+ }
+ }
+# endif /* SASL */
+ }
+
+
+do_transfer:
+ /* clear out per-message flags from connection structure */
+ mci->mci_flags &= ~(MCIF_CVT7TO8|MCIF_CVT8TO7);
+
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ bitnset(M_7BITS, m->m_flags))
+ mci->mci_flags |= MCIF_CVT8TO7;
+
+#if MIME7TO8
+ if (bitnset(M_MAKE8BIT, m->m_flags) &&
+ !bitset(MCIF_7BIT, mci->mci_flags) &&
+ (p = hvalue("Content-Transfer-Encoding", e->e_header)) != NULL &&
+ (sm_strcasecmp(p, "quoted-printable") == 0 ||
+ sm_strcasecmp(p, "base64") == 0) &&
+ (p = hvalue("Content-Type", e->e_header)) != NULL)
+ {
+ /* may want to convert 7 -> 8 */
+ /* XXX should really parse it here -- and use a class XXX */
+ if (sm_strncasecmp(p, "text/plain", 10) == 0 &&
+ (p[10] == '\0' || p[10] == ' ' || p[10] == ';'))
+ mci->mci_flags |= MCIF_CVT7TO8;
+ }
+#endif /* MIME7TO8 */
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("openmailer: ");
+ mci_dump(mci, false);
+ }
+
+#if _FFR_CLIENT_SIZE
+ /*
+ ** See if we know the maximum size and
+ ** abort if the message is too big.
+ **
+ ** NOTE: _FFR_CLIENT_SIZE is untested.
+ */
+
+ if (bitset(MCIF_SIZE, mci->mci_flags) &&
+ mci->mci_maxsize > 0 &&
+ e->e_msgsize > mci->mci_maxsize)
+ {
+ e->e_flags |= EF_NO_BODY_RETN;
+ if (bitnset(M_LOCALMAILER, m->m_flags))
+ e->e_status = "5.2.3";
+ else
+ e->e_status = "5.3.4";
+
+ usrerrenh(e->e_status,
+ "552 Message is too large; %ld bytes max",
+ mci->mci_maxsize);
+ rcode = EX_DATAERR;
+
+ /* Need an e_message for error */
+ (void) sm_snprintf(SmtpError, sizeof SmtpError,
+ "Message is too large; %ld bytes max",
+ mci->mci_maxsize);
+ goto give_up;
+ }
+#endif /* _FFR_CLIENT_SIZE */
+
+ if (mci->mci_state != MCIS_OPEN)
+ {
+ /* couldn't open the mailer */
+ rcode = mci->mci_exitstat;
+ errno = mci->mci_errno;
+ SM_SET_H_ERRNO(mci->mci_herrno);
+ if (rcode == EX_OK)
+ {
+ /* shouldn't happen */
+ syserr("554 5.3.5 deliver: mci=%lx rcode=%d errno=%d state=%d sig=%s",
+ (unsigned long) mci, rcode, errno,
+ mci->mci_state, firstsig);
+ mci_dump_all(true);
+ rcode = EX_SOFTWARE;
+ }
+ else if (nummxhosts > hostnum)
+ {
+ /* try next MX site */
+ goto tryhost;
+ }
+ }
+ else if (!clever)
+ {
+ /*
+ ** Format and send message.
+ */
+
+ putfromline(mci, e);
+ (*e->e_puthdr)(mci, e->e_header, e, M87F_OUTER);
+ (*e->e_putbody)(mci, e, NULL);
+
+ /* get the exit status */
+ rcode = endmailer(mci, e, pv);
+ if (rcode == EX_TEMPFAIL && SmtpError[0] == '\0')
+ {
+ /*
+ ** Need an e_message for mailq display.
+ ** We set SmtpError as
+ */
+
+ (void) sm_snprintf(SmtpError, sizeof SmtpError,
+ "%s mailer (%s) exited with EX_TEMPFAIL",
+ m->m_name, m->m_mailer);
+ }
+ }
+ else
+ {
+ /*
+ ** Send the MAIL FROM: protocol
+ */
+
+ /* XXX this isn't pipelined... */
+ rcode = smtpmailfrom(m, mci, e);
+ if (rcode == EX_OK)
+ {
+ register int i;
+# if PIPELINING
+ ADDRESS *volatile pchain;
+# endif /* PIPELINING */
+
+ /* send the recipient list */
+ tobuf[0] = '\0';
+ mci->mci_retryrcpt = false;
+ mci->mci_tolist = tobuf;
+# if PIPELINING
+ pchain = NULL;
+ mci->mci_nextaddr = NULL;
+# endif /* PIPELINING */
+
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ {
+ if (!QS_IS_UNMARKED(to->q_state))
+ continue;
+
+ /* mark recipient state as "ok so far" */
+ to->q_state = QS_OK;
+ e->e_to = to->q_paddr;
+# if STARTTLS
+ i = rscheck("tls_rcpt", to->q_user, NULL, e,
+ RSF_RMCOMM|RSF_COUNT, 3,
+ mci->mci_host, e->e_id);
+ if (i != EX_OK)
+ {
+ markfailure(e, to, mci, i, false);
+ giveresponse(i, to->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ if (i == EX_TEMPFAIL)
+ {
+ mci->mci_retryrcpt = true;
+ to->q_state = QS_RETRY;
+ }
+ continue;
+ }
+# endif /* STARTTLS */
+
+ i = smtprcpt(to, m, mci, e, ctladdr, xstart);
+# if PIPELINING
+ if (i == EX_OK &&
+ bitset(MCIF_PIPELINED, mci->mci_flags))
+ {
+ /*
+ ** Add new element to list of
+ ** recipients for pipelining.
+ */
+
+ to->q_pchain = NULL;
+ if (mci->mci_nextaddr == NULL)
+ mci->mci_nextaddr = to;
+ if (pchain == NULL)
+ pchain = to;
+ else
+ {
+ pchain->q_pchain = to;
+ pchain = pchain->q_pchain;
+ }
+ }
+# endif /* PIPELINING */
+ if (i != EX_OK)
+ {
+ markfailure(e, to, mci, i, false);
+ giveresponse(i, to->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ if (i == EX_TEMPFAIL)
+ to->q_state = QS_RETRY;
+ }
+ }
+
+ /* No recipients in list and no missing responses? */
+ if (tobuf[0] == '\0'
+# if PIPELINING
+ && mci->mci_nextaddr == NULL
+# endif /* PIPELINING */
+ )
+ {
+ rcode = EX_OK;
+ e->e_to = NULL;
+ if (bitset(MCIF_CACHED, mci->mci_flags))
+ smtprset(m, mci, e);
+ }
+ else
+ {
+ e->e_to = tobuf + 1;
+ rcode = smtpdata(m, mci, e, ctladdr, xstart);
+ }
+ }
+ if (rcode == EX_TEMPFAIL && nummxhosts > hostnum)
+ {
+ /* try next MX site */
+ goto tryhost;
+ }
+ }
+#if NAMED_BIND
+ if (ConfigLevel < 2)
+ _res.options |= RES_DEFNAMES | RES_DNSRCH; /* XXX */
+#endif /* NAMED_BIND */
+
+ if (tTd(62, 1))
+ checkfds("after delivery");
+
+ /*
+ ** Do final status disposal.
+ ** We check for something in tobuf for the SMTP case.
+ ** If we got a temporary failure, arrange to queue the
+ ** addressees.
+ */
+
+ give_up:
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ lmtp_rcode = rcode;
+ tobuf[0] = '\0';
+ anyok = false;
+ strsize = 0;
+ }
+ else
+ anyok = rcode == EX_OK;
+
+ for (to = tochain; to != NULL; to = to->q_tchain)
+ {
+ /* see if address already marked */
+ if (!QS_IS_OK(to->q_state))
+ continue;
+
+ /* if running LMTP, get the status for each address */
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ if (lmtp_rcode == EX_OK)
+ rcode = smtpgetstat(m, mci, e);
+ if (rcode == EX_OK)
+ {
+ strsize += sm_strlcat2(tobuf + strsize, ",",
+ to->q_paddr,
+ tobufsize - strsize);
+ SM_ASSERT(strsize < tobufsize);
+ anyok = true;
+ }
+ else
+ {
+ e->e_to = to->q_paddr;
+ markfailure(e, to, mci, rcode, true);
+ giveresponse(rcode, to->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ e->e_to = tobuf + 1;
+ continue;
+ }
+ }
+ else
+ {
+ /* mark bad addresses */
+ if (rcode != EX_OK)
+ {
+ if (goodmxfound && rcode == EX_NOHOST)
+ rcode = EX_TEMPFAIL;
+ markfailure(e, to, mci, rcode, true);
+ continue;
+ }
+ }
+
+ /* successful delivery */
+ to->q_state = QS_SENT;
+ to->q_statdate = curtime();
+ e->e_nsent++;
+
+ /*
+ ** Checkpoint the send list every few addresses
+ */
+
+ if (CheckpointInterval > 0 && e->e_nsent >= CheckpointInterval)
+ {
+ queueup(e, false, false);
+ e->e_nsent = 0;
+ }
+
+ if (bitnset(M_LOCALMAILER, m->m_flags) &&
+ bitset(QPINGONSUCCESS, to->q_flags))
+ {
+ to->q_flags |= QDELIVERED;
+ to->q_status = "2.1.5";
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... Successfully delivered\n",
+ to->q_paddr);
+ }
+ else if (bitset(QPINGONSUCCESS, to->q_flags) &&
+ bitset(QPRIMARY, to->q_flags) &&
+ !bitset(MCIF_DSN, mci->mci_flags))
+ {
+ to->q_flags |= QRELAYED;
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... relayed; expect no further notifications\n",
+ to->q_paddr);
+ }
+ else if (IS_DLVR_NOTIFY(e) &&
+ !bitset(MCIF_DLVR_BY, mci->mci_flags) &&
+ bitset(QPRIMARY, to->q_flags) &&
+ (!bitset(QHASNOTIFY, to->q_flags) ||
+ bitset(QPINGONSUCCESS, to->q_flags) ||
+ bitset(QPINGONFAILURE, to->q_flags) ||
+ bitset(QPINGONDELAY, to->q_flags)))
+ {
+ /* RFC 2852, 4.1.4.2: no NOTIFY, or not NEVER */
+ to->q_flags |= QBYNRELAY;
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... Deliver-by notify: relayed\n",
+ to->q_paddr);
+ }
+ else if (IS_DLVR_TRACE(e) &&
+ (!bitset(QHASNOTIFY, to->q_flags) ||
+ bitset(QPINGONSUCCESS, to->q_flags) ||
+ bitset(QPINGONFAILURE, to->q_flags) ||
+ bitset(QPINGONDELAY, to->q_flags)) &&
+ bitset(QPRIMARY, to->q_flags))
+ {
+ /* RFC 2852, 4.1.4: no NOTIFY, or not NEVER */
+ to->q_flags |= QBYTRACE;
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... Deliver-By trace: relayed\n",
+ to->q_paddr);
+ }
+ }
+
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ /*
+ ** Global information applies to the last recipient only;
+ ** clear it out to avoid bogus errors.
+ */
+
+ rcode = EX_OK;
+ e->e_statmsg = NULL;
+
+ /* reset the mci state for the next transaction */
+ if (mci != NULL &&
+ (mci->mci_state == MCIS_MAIL ||
+ mci->mci_state == MCIS_RCPT ||
+ mci->mci_state == MCIS_DATA))
+ mci->mci_state = MCIS_OPEN;
+ }
+
+ if (tobuf[0] != '\0')
+ {
+ giveresponse(rcode, NULL, m, mci, ctladdr, xstart, e, tochain);
+#if 0
+ /*
+ ** This code is disabled for now because I am not
+ ** sure that copying status from the first recipient
+ ** to all non-status'ed recipients is a good idea.
+ */
+
+ if (tochain->q_message != NULL &&
+ !bitnset(M_LMTP, m->m_flags) && rcode != EX_OK)
+ {
+ for (to = tochain->q_tchain; to != NULL;
+ to = to->q_tchain)
+ {
+ /* see if address already marked */
+ if (QS_IS_QUEUEUP(to->q_state) &&
+ to->q_message == NULL)
+ to->q_message = sm_rpool_strdup_x(e->e_rpool,
+ tochain->q_message);
+ }
+ }
+#endif /* 0 */
+ }
+ if (anyok)
+ markstats(e, tochain, STATS_NORMAL);
+ mci_store_persistent(mci);
+
+ /* Some recipients were tempfailed, try them on the next host */
+ if (mci != NULL && mci->mci_retryrcpt && nummxhosts > hostnum)
+ {
+ /* try next MX site */
+ goto tryhost;
+ }
+
+ /* now close the connection */
+ if (clever && mci != NULL && mci->mci_state != MCIS_CLOSED &&
+ !bitset(MCIF_CACHED, mci->mci_flags))
+ smtpquit(m, mci, e);
+
+cleanup: ;
+ }
+ SM_FINALLY
+ {
+ /*
+ ** Restore state and return.
+ */
+#if XDEBUG
+ char wbuf[MAXLINE];
+
+ /* make absolutely certain 0, 1, and 2 are in use */
+ (void) sm_snprintf(wbuf, sizeof wbuf,
+ "%s... end of deliver(%s)",
+ e->e_to == NULL ? "NO-TO-LIST"
+ : shortenstring(e->e_to,
+ MAXSHORTSTR),
+ m->m_name);
+ checkfd012(wbuf);
+#endif /* XDEBUG */
+
+ errno = 0;
+
+ /*
+ ** It was originally necessary to set macro 'g' to NULL
+ ** because it previously pointed to an auto buffer.
+ ** We don't do this any more, so this may be unnecessary.
+ */
+
+ macdefine(&e->e_macro, A_PERM, 'g', (char *) NULL);
+ e->e_to = NULL;
+ }
+ SM_END_TRY
+ return rcode;
+}
+
+/*
+** MARKFAILURE -- mark a failure on a specific address.
+**
+** Parameters:
+** e -- the envelope we are sending.
+** q -- the address to mark.
+** mci -- mailer connection information.
+** rcode -- the code signifying the particular failure.
+** ovr -- override an existing code?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** marks the address (and possibly the envelope) with the
+** failure so that an error will be returned or
+** the message will be queued, as appropriate.
+*/
+
+void
+markfailure(e, q, mci, rcode, ovr)
+ register ENVELOPE *e;
+ register ADDRESS *q;
+ register MCI *mci;
+ int rcode;
+ bool ovr;
+{
+ int save_errno = errno;
+ char *status = NULL;
+ char *rstatus = NULL;
+
+ switch (rcode)
+ {
+ case EX_OK:
+ break;
+
+ case EX_TEMPFAIL:
+ case EX_IOERR:
+ case EX_OSERR:
+ q->q_state = QS_QUEUEUP;
+ break;
+
+ default:
+ q->q_state = QS_BADADDR;
+ break;
+ }
+
+ /* find most specific error code possible */
+ if (mci != NULL && mci->mci_status != NULL)
+ {
+ status = sm_rpool_strdup_x(e->e_rpool, mci->mci_status);
+ if (mci->mci_rstatus != NULL)
+ rstatus = sm_rpool_strdup_x(e->e_rpool,
+ mci->mci_rstatus);
+ else
+ rstatus = NULL;
+ }
+ else if (e->e_status != NULL)
+ {
+ status = e->e_status;
+ rstatus = NULL;
+ }
+ else
+ {
+ switch (rcode)
+ {
+ case EX_USAGE:
+ status = "5.5.4";
+ break;
+
+ case EX_DATAERR:
+ status = "5.5.2";
+ break;
+
+ case EX_NOUSER:
+ status = "5.1.1";
+ break;
+
+ case EX_NOHOST:
+ status = "5.1.2";
+ break;
+
+ case EX_NOINPUT:
+ case EX_CANTCREAT:
+ case EX_NOPERM:
+ status = "5.3.0";
+ break;
+
+ case EX_UNAVAILABLE:
+ case EX_SOFTWARE:
+ case EX_OSFILE:
+ case EX_PROTOCOL:
+ case EX_CONFIG:
+ status = "5.5.0";
+ break;
+
+ case EX_OSERR:
+ case EX_IOERR:
+ status = "4.5.0";
+ break;
+
+ case EX_TEMPFAIL:
+ status = "4.2.0";
+ break;
+ }
+ }
+
+ /* new status? */
+ if (status != NULL && *status != '\0' && (ovr || q->q_status == NULL ||
+ *q->q_status == '\0' || *q->q_status < *status))
+ {
+ q->q_status = status;
+ q->q_rstatus = rstatus;
+ }
+ if (rcode != EX_OK && q->q_rstatus == NULL &&
+ q->q_mailer != NULL && q->q_mailer->m_diagtype != NULL &&
+ sm_strcasecmp(q->q_mailer->m_diagtype, "X-UNIX") == 0)
+ {
+ char buf[16];
+
+ (void) sm_snprintf(buf, sizeof buf, "%d", rcode);
+ q->q_rstatus = sm_rpool_strdup_x(e->e_rpool, buf);
+ }
+
+ q->q_statdate = curtime();
+ if (CurHostName != NULL && CurHostName[0] != '\0' &&
+ mci != NULL && !bitset(M_LOCALMAILER, mci->mci_flags))
+ q->q_statmta = sm_rpool_strdup_x(e->e_rpool, CurHostName);
+
+ /* restore errno */
+ errno = save_errno;
+}
+/*
+** ENDMAILER -- Wait for mailer to terminate.
+**
+** We should never get fatal errors (e.g., segmentation
+** violation), so we report those specially. For other
+** errors, we choose a status message (into statmsg),
+** and if it represents an error, we print it.
+**
+** Parameters:
+** mci -- the mailer connection info.
+** e -- the current envelope.
+** pv -- the parameter vector that invoked the mailer
+** (for error messages).
+**
+** Returns:
+** exit code of mailer.
+**
+** Side Effects:
+** none.
+*/
+
+static jmp_buf EndWaitTimeout;
+
+static void
+endwaittimeout()
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(EndWaitTimeout, 1);
+}
+
+int
+endmailer(mci, e, pv)
+ register MCI *mci;
+ register ENVELOPE *e;
+ char **pv;
+{
+ int st;
+ int save_errno = errno;
+ char buf[MAXLINE];
+ SM_EVENT *ev = NULL;
+
+
+ mci_unlock_host(mci);
+
+ /* close output to mailer */
+ if (mci->mci_out != NULL)
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+
+ /* copy any remaining input to transcript */
+ if (mci->mci_in != NULL && mci->mci_state != MCIS_ERROR &&
+ e->e_xfp != NULL)
+ {
+ while (sfgets(buf, sizeof buf, mci->mci_in,
+ TimeOuts.to_quit, "Draining Input") != NULL)
+ (void) sm_io_fputs(e->e_xfp, SM_TIME_DEFAULT, buf);
+ }
+
+#if SASL
+ /* close SASL connection */
+ if (bitset(MCIF_AUTHACT, mci->mci_flags))
+ {
+ sasl_dispose(&mci->mci_conn);
+ mci->mci_flags &= ~MCIF_AUTHACT;
+ }
+#endif /* SASL */
+
+#if STARTTLS
+ /* shutdown TLS */
+ (void) endtlsclt(mci);
+#endif /* STARTTLS */
+
+ /* now close the input */
+ if (mci->mci_in != NULL)
+ (void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
+ mci->mci_in = mci->mci_out = NULL;
+ mci->mci_state = MCIS_CLOSED;
+
+ errno = save_errno;
+
+ /* in the IPC case there is nothing to wait for */
+ if (mci->mci_pid == 0)
+ return EX_OK;
+
+ /* put a timeout around the wait */
+ if (mci->mci_mailer->m_wait > 0)
+ {
+ if (setjmp(EndWaitTimeout) == 0)
+ ev = sm_setevent(mci->mci_mailer->m_wait,
+ endwaittimeout, 0);
+ else
+ {
+ syserr("endmailer %s: wait timeout (%ld)",
+ mci->mci_mailer->m_name,
+ (long) mci->mci_mailer->m_wait);
+ return EX_TEMPFAIL;
+ }
+ }
+
+ /* wait for the mailer process, collect status */
+ st = waitfor(mci->mci_pid);
+ save_errno = errno;
+ if (ev != NULL)
+ sm_clrevent(ev);
+ errno = save_errno;
+
+ if (st == -1)
+ {
+ syserr("endmailer %s: wait", mci->mci_mailer->m_name);
+ return EX_SOFTWARE;
+ }
+
+ if (WIFEXITED(st))
+ {
+ /* normal death -- return status */
+ return (WEXITSTATUS(st));
+ }
+
+ /* it died a horrid death */
+ syserr("451 4.3.0 mailer %s died with signal %d%s",
+ mci->mci_mailer->m_name, WTERMSIG(st),
+ WCOREDUMP(st) ? " (core dumped)" :
+ (WIFSTOPPED(st) ? " (stopped)" : ""));
+
+ /* log the arguments */
+ if (pv != NULL && e->e_xfp != NULL)
+ {
+ register char **av;
+
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, "Arguments:");
+ for (av = pv; *av != NULL; av++)
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, " %s",
+ *av);
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, "\n");
+ }
+
+ ExitStat = EX_TEMPFAIL;
+ return EX_TEMPFAIL;
+}
+/*
+** GIVERESPONSE -- Interpret an error response from a mailer
+**
+** Parameters:
+** status -- the status code from the mailer (high byte
+** only; core dumps must have been taken care of
+** already).
+** dsn -- the DSN associated with the address, if any.
+** m -- the mailer info for this mailer.
+** mci -- the mailer connection info -- can be NULL if the
+** response is given before the connection is made.
+** ctladdr -- the controlling address for the recipient
+** address(es).
+** xstart -- the transaction start time, for computing
+** transaction delays.
+** e -- the current envelope.
+** to -- the current recipient (NULL if none).
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Errors may be incremented.
+** ExitStat may be set.
+*/
+
+void
+giveresponse(status, dsn, m, mci, ctladdr, xstart, e, to)
+ int status;
+ char *dsn;
+ register MAILER *m;
+ register MCI *mci;
+ ADDRESS *ctladdr;
+ time_t xstart;
+ ENVELOPE *e;
+ ADDRESS *to;
+{
+ register const char *statmsg;
+ int errnum = errno;
+ int off = 4;
+ bool usestat = false;
+ char dsnbuf[ENHSCLEN];
+ char buf[MAXLINE];
+ char *exmsg;
+
+ if (e == NULL)
+ syserr("giveresponse: null envelope");
+
+ /*
+ ** Compute status message from code.
+ */
+
+ exmsg = sm_sysexmsg(status);
+ if (status == 0)
+ {
+ statmsg = "250 2.0.0 Sent";
+ if (e->e_statmsg != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf, "%s (%s)",
+ statmsg,
+ shortenstring(e->e_statmsg, 403));
+ statmsg = buf;
+ }
+ }
+ else if (exmsg == NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "554 5.3.0 unknown mailer error %d",
+ status);
+ status = EX_UNAVAILABLE;
+ statmsg = buf;
+ usestat = true;
+ }
+ else if (status == EX_TEMPFAIL)
+ {
+ char *bp = buf;
+
+ (void) sm_strlcpy(bp, exmsg + 1, SPACELEFT(buf, bp));
+ bp += strlen(bp);
+#if NAMED_BIND
+ if (h_errno == TRY_AGAIN)
+ statmsg = sm_errstring(h_errno + E_DNSBASE);
+ else
+#endif /* NAMED_BIND */
+ {
+ if (errnum != 0)
+ statmsg = sm_errstring(errnum);
+ else
+ statmsg = SmtpError;
+ }
+ if (statmsg != NULL && statmsg[0] != '\0')
+ {
+ switch (errnum)
+ {
+#ifdef ENETDOWN
+ case ENETDOWN: /* Network is down */
+#endif /* ENETDOWN */
+#ifdef ENETUNREACH
+ case ENETUNREACH: /* Network is unreachable */
+#endif /* ENETUNREACH */
+#ifdef ENETRESET
+ case ENETRESET: /* Network dropped connection on reset */
+#endif /* ENETRESET */
+#ifdef ECONNABORTED
+ case ECONNABORTED: /* Software caused connection abort */
+#endif /* ECONNABORTED */
+#ifdef EHOSTDOWN
+ case EHOSTDOWN: /* Host is down */
+#endif /* EHOSTDOWN */
+#ifdef EHOSTUNREACH
+ case EHOSTUNREACH: /* No route to host */
+#endif /* EHOSTUNREACH */
+ if (mci->mci_host != NULL)
+ {
+ (void) sm_strlcpyn(bp,
+ SPACELEFT(buf, bp),
+ 2, ": ",
+ mci->mci_host);
+ bp += strlen(bp);
+ }
+ break;
+ }
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ": ",
+ statmsg);
+ usestat = true;
+ }
+ statmsg = buf;
+ }
+#if NAMED_BIND
+ else if (status == EX_NOHOST && h_errno != 0)
+ {
+ statmsg = sm_errstring(h_errno + E_DNSBASE);
+ (void) sm_snprintf(buf, sizeof buf, "%s (%s)", exmsg + 1,
+ statmsg);
+ statmsg = buf;
+ usestat = true;
+ }
+#endif /* NAMED_BIND */
+ else
+ {
+ statmsg = exmsg;
+ if (*statmsg++ == ':' && errnum != 0)
+ {
+ (void) sm_snprintf(buf, sizeof buf, "%s: %s", statmsg,
+ sm_errstring(errnum));
+ statmsg = buf;
+ usestat = true;
+ }
+ else if (bitnset(M_LMTP, m->m_flags) && e->e_statmsg != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf, "%s (%s)", statmsg,
+ shortenstring(e->e_statmsg, 403));
+ statmsg = buf;
+ usestat = true;
+ }
+ }
+
+ /*
+ ** Print the message as appropriate
+ */
+
+ if (status == EX_OK || status == EX_TEMPFAIL)
+ {
+ extern char MsgBuf[];
+
+ if ((off = isenhsc(statmsg + 4, ' ')) > 0)
+ {
+ if (dsn == NULL)
+ {
+ (void) sm_snprintf(dsnbuf, sizeof dsnbuf,
+ "%.*s", off, statmsg + 4);
+ dsn = dsnbuf;
+ }
+ off += 5;
+ }
+ else
+ {
+ off = 4;
+ }
+ message("%s", statmsg + off);
+ if (status == EX_TEMPFAIL && e->e_xfp != NULL)
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, "%s\n",
+ &MsgBuf[4]);
+ }
+ else
+ {
+ char mbuf[ENHSCLEN + 4];
+
+ Errors++;
+ if ((off = isenhsc(statmsg + 4, ' ')) > 0 &&
+ off < sizeof mbuf - 4)
+ {
+ if (dsn == NULL)
+ {
+ (void) sm_snprintf(dsnbuf, sizeof dsnbuf,
+ "%.*s", off, statmsg + 4);
+ dsn = dsnbuf;
+ }
+ off += 5;
+
+ /* copy only part of statmsg to mbuf */
+ (void) sm_strlcpy(mbuf, statmsg, off);
+ (void) sm_strlcat(mbuf, " %s", sizeof mbuf);
+ }
+ else
+ {
+ dsnbuf[0] = '\0';
+ (void) sm_snprintf(mbuf, sizeof mbuf, "%.3s %%s",
+ statmsg);
+ off = 4;
+ }
+ usrerr(mbuf, &statmsg[off]);
+ }
+
+ /*
+ ** Final cleanup.
+ ** Log a record of the transaction. Compute the new
+ ** ExitStat -- if we already had an error, stick with
+ ** that.
+ */
+
+ if (OpMode != MD_VERIFY && !bitset(EF_VRFYONLY, e->e_flags) &&
+ LogLevel > ((status == EX_TEMPFAIL) ? 8 : (status == EX_OK) ? 7 : 6))
+ logdelivery(m, mci, dsn, statmsg + off, ctladdr, xstart, e);
+
+ if (tTd(11, 2))
+ sm_dprintf("giveresponse: status=%d, dsn=%s, e->e_message=%s, errnum=%d\n",
+ status,
+ dsn == NULL ? "<NULL>" : dsn,
+ e->e_message == NULL ? "<NULL>" : e->e_message,
+ errnum);
+
+ if (status != EX_TEMPFAIL)
+ setstat(status);
+ if (status != EX_OK && (status != EX_TEMPFAIL || e->e_message == NULL))
+ e->e_message = sm_rpool_strdup_x(e->e_rpool, statmsg + off);
+ if (status != EX_OK && to != NULL && to->q_message == NULL)
+ {
+ if (!usestat && e->e_message != NULL)
+ to->q_message = sm_rpool_strdup_x(e->e_rpool,
+ e->e_message);
+ else
+ to->q_message = sm_rpool_strdup_x(e->e_rpool,
+ statmsg + off);
+ }
+ errno = 0;
+ SM_SET_H_ERRNO(0);
+}
+/*
+** LOGDELIVERY -- log the delivery in the system log
+**
+** Care is taken to avoid logging lines that are too long, because
+** some versions of syslog have an unfortunate proclivity for core
+** dumping. This is a hack, to be sure, that is at best empirical.
+**
+** Parameters:
+** m -- the mailer info. Can be NULL for initial queue.
+** mci -- the mailer connection info -- can be NULL if the
+** log is occurring when no connection is active.
+** dsn -- the DSN attached to the status.
+** status -- the message to print for the status.
+** ctladdr -- the controlling address for the to list.
+** xstart -- the transaction start time, used for
+** computing transaction delay.
+** e -- the current envelope.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** none
+*/
+
+void
+logdelivery(m, mci, dsn, status, ctladdr, xstart, e)
+ MAILER *m;
+ register MCI *mci;
+ char *dsn;
+ const char *status;
+ ADDRESS *ctladdr;
+ time_t xstart;
+ register ENVELOPE *e;
+{
+ register char *bp;
+ register char *p;
+ int l;
+ time_t now = curtime();
+ char buf[1024];
+
+#if (SYSLOG_BUFSIZE) >= 256
+ /* ctladdr: max 106 bytes */
+ bp = buf;
+ if (ctladdr != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", ctladdr=",
+ shortenstring(ctladdr->q_paddr, 83));
+ bp += strlen(bp);
+ if (bitset(QGOODUID, ctladdr->q_flags))
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), " (%d/%d)",
+ (int) ctladdr->q_uid,
+ (int) ctladdr->q_gid);
+ bp += strlen(bp);
+ }
+ }
+
+ /* delay & xdelay: max 41 bytes */
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", delay=",
+ pintvl(now - e->e_ctime, true));
+ bp += strlen(bp);
+
+ if (xstart != (time_t) 0)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", xdelay=",
+ pintvl(now - xstart, true));
+ bp += strlen(bp);
+ }
+
+ /* mailer: assume about 19 bytes (max 10 byte mailer name) */
+ if (m != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", mailer=",
+ m->m_name);
+ bp += strlen(bp);
+ }
+
+ /* pri: changes with each delivery attempt */
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), ", pri=%ld",
+ e->e_msgpriority);
+ bp += strlen(bp);
+
+ /* relay: max 66 bytes for IPv4 addresses */
+ if (mci != NULL && mci->mci_host != NULL)
+ {
+ extern SOCKADDR CurHostAddr;
+
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", relay=",
+ shortenstring(mci->mci_host, 40));
+ bp += strlen(bp);
+
+ if (CurHostAddr.sa.sa_family != 0)
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), " [%s]",
+ anynet_ntoa(&CurHostAddr));
+ }
+ }
+#if _FFR_QUARANTINE
+ else if (strcmp(status, "quarantined") == 0)
+ {
+ if (e->e_quarmsg != NULL)
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", quarantine=%s",
+ shortenstring(e->e_quarmsg, 40));
+ }
+#endif /* _FFR_QUARANTINE */
+ else if (strcmp(status, "queued") != 0)
+ {
+ p = macvalue('h', e);
+ if (p != NULL && p[0] != '\0')
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", relay=%s", shortenstring(p, 40));
+ }
+ }
+ bp += strlen(bp);
+
+ /* dsn */
+ if (dsn != NULL && *dsn != '\0')
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", dsn=",
+ shortenstring(dsn, ENHSCLEN));
+ bp += strlen(bp);
+ }
+
+# define STATLEN (((SYSLOG_BUFSIZE) - 100) / 4)
+# if (STATLEN) < 63
+# undef STATLEN
+# define STATLEN 63
+# endif /* (STATLEN) < 63 */
+# if (STATLEN) > 203
+# undef STATLEN
+# define STATLEN 203
+# endif /* (STATLEN) > 203 */
+
+ /* stat: max 210 bytes */
+ if ((bp - buf) > (sizeof buf - ((STATLEN) + 20)))
+ {
+ /* desperation move -- truncate data */
+ bp = buf + sizeof buf - ((STATLEN) + 17);
+ (void) sm_strlcpy(bp, "...", SPACELEFT(buf, bp));
+ bp += 3;
+ }
+
+ (void) sm_strlcpy(bp, ", stat=", SPACELEFT(buf, bp));
+ bp += strlen(bp);
+
+ (void) sm_strlcpy(bp, shortenstring(status, STATLEN),
+ SPACELEFT(buf, bp));
+
+ /* id, to: max 13 + TOBUFSIZE bytes */
+ l = SYSLOG_BUFSIZE - 100 - strlen(buf);
+ if (l < 0)
+ l = 0;
+ p = e->e_to == NULL ? "NO-TO-LIST" : e->e_to;
+ while (strlen(p) >= l)
+ {
+ register char *q;
+
+ for (q = p + l; q > p; q--)
+ {
+ if (*q == ',')
+ break;
+ }
+ if (p == q)
+ break;
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s [more]%s",
+ (int) (++q - p), p, buf);
+ p = q;
+ }
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s%s", l, p, buf);
+
+#else /* (SYSLOG_BUFSIZE) >= 256 */
+
+ l = SYSLOG_BUFSIZE - 85;
+ if (l < 0)
+ l = 0;
+ p = e->e_to == NULL ? "NO-TO-LIST" : e->e_to;
+ while (strlen(p) >= l)
+ {
+ register char *q;
+
+ for (q = p + l; q > p; q--)
+ {
+ if (*q == ',')
+ break;
+ }
+ if (p == q)
+ break;
+
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s [more]",
+ (int) (++q - p), p);
+ p = q;
+ }
+ sm_syslog(LOG_INFO, e->e_id, "to=%.*s", l, p);
+
+ if (ctladdr != NULL)
+ {
+ bp = buf;
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, "ctladdr=",
+ shortenstring(ctladdr->q_paddr, 83));
+ bp += strlen(bp);
+ if (bitset(QGOODUID, ctladdr->q_flags))
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), " (%d/%d)",
+ ctladdr->q_uid, ctladdr->q_gid);
+ bp += strlen(bp);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "%s", buf);
+ }
+ bp = buf;
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, "delay=",
+ pintvl(now - e->e_ctime, true));
+ bp += strlen(bp);
+ if (xstart != (time_t) 0)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", xdelay=",
+ pintvl(now - xstart, true));
+ bp += strlen(bp);
+ }
+
+ if (m != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2, ", mailer=",
+ m->m_name);
+ bp += strlen(bp);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "%.1000s", buf);
+
+ buf[0] = '\0';
+ bp = buf;
+ if (mci != NULL && mci->mci_host != NULL)
+ {
+ extern SOCKADDR CurHostAddr;
+
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp), "relay=%.100s",
+ mci->mci_host);
+ bp += strlen(bp);
+
+ if (CurHostAddr.sa.sa_family != 0)
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ " [%.100s]",
+ anynet_ntoa(&CurHostAddr));
+ }
+#if _FFR_QUARANTINE
+ else if (strcmp(status, "quarantined") == 0)
+ {
+ if (e->e_quarmsg != NULL)
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ ", quarantine=%.100s",
+ e->e_quarmsg);
+ }
+#endif /* _FFR_QUARANTINE */
+ else if (strcmp(status, "queued") != 0)
+ {
+ p = macvalue('h', e);
+ if (p != NULL && p[0] != '\0')
+ (void) sm_snprintf(buf, sizeof buf, "relay=%.100s", p);
+ }
+ if (buf[0] != '\0')
+ sm_syslog(LOG_INFO, e->e_id, "%.1000s", buf);
+
+ sm_syslog(LOG_INFO, e->e_id, "stat=%s", shortenstring(status, 63));
+#endif /* (SYSLOG_BUFSIZE) >= 256 */
+}
+/*
+** PUTFROMLINE -- output a UNIX-style from line (or whatever)
+**
+** This can be made an arbitrary message separator by changing $l
+**
+** One of the ugliest hacks seen by human eyes is contained herein:
+** UUCP wants those stupid "remote from <host>" lines. Why oh why
+** does a well-meaning programmer such as myself have to deal with
+** this kind of antique garbage????
+**
+** Parameters:
+** mci -- the connection information.
+** e -- the envelope.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** outputs some text to fp.
+*/
+
+void
+putfromline(mci, e)
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ char *template = UnixFromLine;
+ char buf[MAXLINE];
+ char xbuf[MAXLINE];
+
+ if (bitnset(M_NHDR, mci->mci_mailer->m_flags))
+ return;
+
+ mci->mci_flags |= MCIF_INHEADER;
+
+ if (bitnset(M_UGLYUUCP, mci->mci_mailer->m_flags))
+ {
+ char *bang;
+
+ expand("\201g", buf, sizeof buf, e);
+ bang = strchr(buf, '!');
+ if (bang == NULL)
+ {
+ char *at;
+ char hname[MAXNAME];
+
+ /*
+ ** If we can construct a UUCP path, do so
+ */
+
+ at = strrchr(buf, '@');
+ if (at == NULL)
+ {
+ expand("\201k", hname, sizeof hname, e);
+ at = hname;
+ }
+ else
+ *at++ = '\0';
+ (void) sm_snprintf(xbuf, sizeof xbuf,
+ "From %.800s \201d remote from %.100s\n",
+ buf, at);
+ }
+ else
+ {
+ *bang++ = '\0';
+ (void) sm_snprintf(xbuf, sizeof xbuf,
+ "From %.800s \201d remote from %.100s\n",
+ bang, buf);
+ template = xbuf;
+ }
+ }
+ expand(template, buf, sizeof buf, e);
+ putxline(buf, strlen(buf), mci, PXLF_HEADER);
+}
+/*
+** PUTBODY -- put the body of a message.
+**
+** Parameters:
+** mci -- the connection information.
+** e -- the envelope to put out.
+** separator -- if non-NULL, a message separator that must
+** not be permitted in the resulting message.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The message is written onto fp.
+*/
+
+/* values for output state variable */
+#define OS_HEAD 0 /* at beginning of line */
+#define OS_CR 1 /* read a carriage return */
+#define OS_INLINE 2 /* putting rest of line */
+
+void
+putbody(mci, e, separator)
+ register MCI *mci;
+ register ENVELOPE *e;
+ char *separator;
+{
+ bool dead = false;
+ char buf[MAXLINE];
+#if MIME8TO7
+ char *boundaries[MAXMIMENESTING + 1];
+#endif /* MIME8TO7 */
+
+ /*
+ ** Output the body of the message
+ */
+
+ if (e->e_dfp == NULL && bitset(EF_HAS_DF, e->e_flags))
+ {
+ char *df = queuename(e, DATAFL_LETTER);
+
+ e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, df,
+ SM_IO_RDONLY, NULL);
+ if (e->e_dfp == NULL)
+ {
+ char *msg = "!putbody: Cannot open %s for %s from %s";
+
+ if (errno == ENOENT)
+ msg++;
+ syserr(msg, df, e->e_to, e->e_from.q_paddr);
+ }
+
+ }
+ if (e->e_dfp == NULL)
+ {
+ if (bitset(MCIF_INHEADER, mci->mci_flags))
+ {
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ }
+ putline("<<< No Message Collected >>>", mci);
+ goto endofmessage;
+ }
+
+ if (e->e_dfino == (ino_t) 0)
+ {
+ struct stat stbuf;
+
+ if (fstat(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD, NULL), &stbuf)
+ < 0)
+ e->e_dfino = -1;
+ else
+ {
+ e->e_dfdev = stbuf.st_dev;
+ e->e_dfino = stbuf.st_ino;
+ }
+ }
+
+ /* paranoia: the data file should always be in a rewound state */
+ (void) bfrewind(e->e_dfp);
+
+#if MIME8TO7
+ if (bitset(MCIF_CVT8TO7, mci->mci_flags))
+ {
+ /*
+ ** Do 8 to 7 bit MIME conversion.
+ */
+
+ /* make sure it looks like a MIME message */
+ if (hvalue("MIME-Version", e->e_header) == NULL)
+ putline("MIME-Version: 1.0", mci);
+
+ if (hvalue("Content-Type", e->e_header) == NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Content-Type: text/plain; charset=%s",
+ defcharset(e));
+ putline(buf, mci);
+ }
+
+ /* now do the hard work */
+ boundaries[0] = NULL;
+ mci->mci_flags |= MCIF_INHEADER;
+ (void) mime8to7(mci, e->e_header, e, boundaries, M87F_OUTER);
+ }
+# if MIME7TO8
+ else if (bitset(MCIF_CVT7TO8, mci->mci_flags))
+ {
+ (void) mime7to8(mci, e->e_header, e);
+ }
+# endif /* MIME7TO8 */
+ else if (MaxMimeHeaderLength > 0 || MaxMimeFieldLength > 0)
+ {
+ bool oldsuprerrs = SuprErrs;
+
+ /* Use mime8to7 to check multipart for MIME header overflows */
+ boundaries[0] = NULL;
+ mci->mci_flags |= MCIF_INHEADER;
+
+ /*
+ ** If EF_DONT_MIME is set, we have a broken MIME message
+ ** and don't want to generate a new bounce message whose
+ ** body propagates the broken MIME. We can't just not call
+ ** mime8to7() as is done above since we need the security
+ ** checks. The best we can do is suppress the errors.
+ */
+
+ if (bitset(EF_DONT_MIME, e->e_flags))
+ SuprErrs = true;
+
+ (void) mime8to7(mci, e->e_header, e, boundaries,
+ M87F_OUTER|M87F_NO8TO7);
+
+ /* restore SuprErrs */
+ SuprErrs = oldsuprerrs;
+ }
+ else
+#endif /* MIME8TO7 */
+ {
+ int ostate;
+ register char *bp;
+ register char *pbp;
+ register int c;
+ register char *xp;
+ int padc;
+ char *buflim;
+ int pos = 0;
+ char peekbuf[12];
+
+ if (bitset(MCIF_INHEADER, mci->mci_flags))
+ {
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ }
+
+ /* determine end of buffer; allow for short mailer lines */
+ buflim = &buf[sizeof buf - 1];
+ if (mci->mci_mailer->m_linelimit > 0 &&
+ mci->mci_mailer->m_linelimit < sizeof buf - 1)
+ buflim = &buf[mci->mci_mailer->m_linelimit - 1];
+
+ /* copy temp file to output with mapping */
+ ostate = OS_HEAD;
+ bp = buf;
+ pbp = peekbuf;
+ while (!sm_io_error(mci->mci_out) && !dead)
+ {
+ if (pbp > peekbuf)
+ c = *--pbp;
+ else if ((c = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT))
+ == SM_IO_EOF)
+ break;
+ if (bitset(MCIF_7BIT, mci->mci_flags))
+ c &= 0x7f;
+ switch (ostate)
+ {
+ case OS_HEAD:
+ if (c == '\0' &&
+ bitnset(M_NONULLS,
+ mci->mci_mailer->m_flags))
+ break;
+ if (c != '\r' && c != '\n' && bp < buflim)
+ {
+ *bp++ = c;
+ break;
+ }
+
+ /* check beginning of line for special cases */
+ *bp = '\0';
+ pos = 0;
+ padc = SM_IO_EOF;
+ if (buf[0] == 'F' &&
+ bitnset(M_ESCFROM, mci->mci_mailer->m_flags)
+ && strncmp(buf, "From ", 5) == 0)
+ {
+ padc = '>';
+ }
+ if (buf[0] == '-' && buf[1] == '-' &&
+ separator != NULL)
+ {
+ /* possible separator */
+ int sl = strlen(separator);
+
+ if (strncmp(&buf[2], separator, sl)
+ == 0)
+ padc = ' ';
+ }
+ if (buf[0] == '.' &&
+ bitnset(M_XDOT, mci->mci_mailer->m_flags))
+ {
+ padc = '.';
+ }
+
+ /* now copy out saved line */
+ if (TrafficLogFile != NULL)
+ {
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "%05d >>> ",
+ (int) CurrentPid);
+ if (padc != SM_IO_EOF)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ padc);
+ for (xp = buf; xp < bp; xp++)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) *xp);
+ if (c == '\n')
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ }
+ if (padc != SM_IO_EOF)
+ {
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT, padc)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ pos++;
+ }
+ for (xp = buf; xp < bp; xp++)
+ {
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT,
+ (unsigned char) *xp)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ }
+ if (dead)
+ continue;
+ if (c == '\n')
+ {
+ if (sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ break;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ pos = 0;
+ }
+ else
+ {
+ pos += bp - buf;
+ if (c != '\r')
+ *pbp++ = c;
+ }
+
+ bp = buf;
+
+ /* determine next state */
+ if (c == '\n')
+ ostate = OS_HEAD;
+ else if (c == '\r')
+ ostate = OS_CR;
+ else
+ ostate = OS_INLINE;
+ continue;
+
+ case OS_CR:
+ if (c == '\n')
+ {
+ /* got CRLF */
+ if (sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ continue;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+
+ if (TrafficLogFile != NULL)
+ {
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ }
+ ostate = OS_HEAD;
+ continue;
+ }
+
+ /* had a naked carriage return */
+ *pbp++ = c;
+ c = '\r';
+ ostate = OS_INLINE;
+ goto putch;
+
+ case OS_INLINE:
+ if (c == '\r')
+ {
+ ostate = OS_CR;
+ continue;
+ }
+ if (c == '\0' &&
+ bitnset(M_NONULLS,
+ mci->mci_mailer->m_flags))
+ break;
+putch:
+ if (mci->mci_mailer->m_linelimit > 0 &&
+ pos >= mci->mci_mailer->m_linelimit - 1 &&
+ c != '\n')
+ {
+ int d;
+
+ /* check next character for EOL */
+ if (pbp > peekbuf)
+ d = *(pbp - 1);
+ else if ((d = sm_io_getc(e->e_dfp,
+ SM_TIME_DEFAULT))
+ != SM_IO_EOF)
+ *pbp++ = d;
+
+ if (d == '\n' || d == SM_IO_EOF)
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) c);
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT,
+ (unsigned char) c)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ pos++;
+ continue;
+ }
+
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT, '!')
+ == SM_IO_EOF ||
+ sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+
+ if (TrafficLogFile != NULL)
+ {
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "!%s",
+ mci->mci_mailer->m_eol);
+ }
+ ostate = OS_HEAD;
+ *pbp++ = c;
+ continue;
+ }
+ if (c == '\n')
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ if (sm_io_fputs(mci->mci_out,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol)
+ == SM_IO_EOF)
+ continue;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ pos = 0;
+ ostate = OS_HEAD;
+ }
+ else
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) c);
+ if (sm_io_putc(mci->mci_out,
+ SM_TIME_DEFAULT,
+ (unsigned char) c)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ continue;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ pos++;
+ ostate = OS_INLINE;
+ }
+ break;
+ }
+ }
+
+ /* make sure we are at the beginning of a line */
+ if (bp > buf)
+ {
+ if (TrafficLogFile != NULL)
+ {
+ for (xp = buf; xp < bp; xp++)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char) *xp);
+ }
+ for (xp = buf; xp < bp; xp++)
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ (unsigned char) *xp)
+ == SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ }
+ pos += bp - buf;
+ }
+ if (!dead && pos > 0)
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fputs(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+ (void) sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol);
+
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ }
+
+ if (sm_io_error(e->e_dfp))
+ {
+ syserr("putbody: %s/%cf%s: read error",
+ qid_printqueue(e->e_dfqgrp, e->e_dfqdir),
+ DATAFL_LETTER, e->e_id);
+ ExitStat = EX_IOERR;
+ }
+
+endofmessage:
+ /*
+ ** Since mailfile() uses e_dfp in a child process,
+ ** the file offset in the stdio library for the
+ ** parent process will not agree with the in-kernel
+ ** file offset since the file descriptor is shared
+ ** between the processes. Therefore, it is vital
+ ** that the file always be rewound. This forces the
+ ** kernel offset (lseek) and stdio library (ftell)
+ ** offset to match.
+ */
+
+ if (e->e_dfp != NULL)
+ (void) bfrewind(e->e_dfp);
+
+ /* some mailers want extra blank line at end of message */
+ if (!dead && bitnset(M_BLANKEND, mci->mci_mailer->m_flags) &&
+ buf[0] != '\0' && buf[0] != '\n')
+ putline("", mci);
+
+ (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
+ if (sm_io_error(mci->mci_out) && errno != EPIPE)
+ {
+ syserr("putbody: write error");
+ ExitStat = EX_IOERR;
+ }
+
+ errno = 0;
+}
+/*
+** MAILFILE -- Send a message to a file.
+**
+** If the file has the set-user-ID/set-group-ID bits set, but NO
+** execute bits, sendmail will try to become the owner of that file
+** rather than the real user. Obviously, this only works if
+** sendmail runs as root.
+**
+** This could be done as a subordinate mailer, except that it
+** is used implicitly to save messages in ~/dead.letter. We
+** view this as being sufficiently important as to include it
+** here. For example, if the system is dying, we shouldn't have
+** to create another process plus some pipes to save the message.
+**
+** Parameters:
+** filename -- the name of the file to send to.
+** mailer -- mailer definition for recipient -- if NULL,
+** use FileMailer.
+** ctladdr -- the controlling address header -- includes
+** the userid/groupid to be when sending.
+** sfflags -- flags for opening.
+** e -- the current envelope.
+**
+** Returns:
+** The exit code associated with the operation.
+**
+** Side Effects:
+** none.
+*/
+
+# define RETURN(st) exit(st);
+
+static jmp_buf CtxMailfileTimeout;
+
+int
+mailfile(filename, mailer, ctladdr, sfflags, e)
+ char *volatile filename;
+ MAILER *volatile mailer;
+ ADDRESS *ctladdr;
+ volatile long sfflags;
+ register ENVELOPE *e;
+{
+ register SM_FILE_T *f;
+ register pid_t pid = -1;
+ volatile int mode;
+ int len;
+ off_t curoff;
+ bool suidwarn = geteuid() == 0;
+ char *p;
+ char *volatile realfile;
+ SM_EVENT *ev;
+ char buf[MAXPATHLEN];
+ char targetfile[MAXPATHLEN];
+
+ if (tTd(11, 1))
+ {
+ sm_dprintf("mailfile %s\n ctladdr=", filename);
+ printaddr(ctladdr, false);
+ }
+
+ if (mailer == NULL)
+ mailer = FileMailer;
+
+ if (e->e_xfp != NULL)
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+
+ /*
+ ** Special case /dev/null. This allows us to restrict file
+ ** delivery to regular files only.
+ */
+
+ if (sm_path_isdevnull(filename))
+ return EX_OK;
+
+ /* check for 8-bit available */
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ bitnset(M_7BITS, mailer->m_flags) &&
+ (bitset(EF_DONT_MIME, e->e_flags) ||
+ !(bitset(MM_MIME8BIT, MimeMode) ||
+ (bitset(EF_IS_MIME, e->e_flags) &&
+ bitset(MM_CVTMIME, MimeMode)))))
+ {
+ e->e_status = "5.6.3";
+ usrerrenh(e->e_status,
+ "554 Cannot send 8-bit data to 7-bit destination");
+ errno = 0;
+ return EX_DATAERR;
+ }
+
+ /* Find the actual file */
+ if (SafeFileEnv != NULL && SafeFileEnv[0] != '\0')
+ {
+ len = strlen(SafeFileEnv);
+
+ if (strncmp(SafeFileEnv, filename, len) == 0)
+ filename += len;
+
+ if (len + strlen(filename) + 1 >= sizeof targetfile)
+ {
+ syserr("mailfile: filename too long (%s/%s)",
+ SafeFileEnv, filename);
+ return EX_CANTCREAT;
+ }
+ (void) sm_strlcpy(targetfile, SafeFileEnv, sizeof targetfile);
+ realfile = targetfile + len;
+ if (*filename == '/')
+ filename++;
+ if (*filename != '\0')
+ {
+ /* paranoia: trailing / should be removed in readcf */
+ if (targetfile[len - 1] != '/')
+ (void) sm_strlcat(targetfile,
+ "/", sizeof targetfile);
+ (void) sm_strlcat(targetfile, filename,
+ sizeof targetfile);
+ }
+ }
+ else if (mailer->m_rootdir != NULL)
+ {
+ expand(mailer->m_rootdir, targetfile, sizeof targetfile, e);
+ len = strlen(targetfile);
+
+ if (strncmp(targetfile, filename, len) == 0)
+ filename += len;
+
+ if (len + strlen(filename) + 1 >= sizeof targetfile)
+ {
+ syserr("mailfile: filename too long (%s/%s)",
+ targetfile, filename);
+ return EX_CANTCREAT;
+ }
+ realfile = targetfile + len;
+ if (targetfile[len - 1] != '/')
+ (void) sm_strlcat(targetfile, "/", sizeof targetfile);
+ if (*filename == '/')
+ (void) sm_strlcat(targetfile, filename + 1,
+ sizeof targetfile);
+ else
+ (void) sm_strlcat(targetfile, filename,
+ sizeof targetfile);
+ }
+ else
+ {
+ if (sm_strlcpy(targetfile, filename, sizeof targetfile) >=
+ sizeof targetfile)
+ {
+ syserr("mailfile: filename too long (%s)", filename);
+ return EX_CANTCREAT;
+ }
+ realfile = targetfile;
+ }
+
+ /*
+ ** Fork so we can change permissions here.
+ ** Note that we MUST use fork, not vfork, because of
+ ** the complications of calling subroutines, etc.
+ */
+
+
+ /*
+ ** Dispose of SIGCHLD signal catchers that may be laying
+ ** around so that the waitfor() below will get it.
+ */
+
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+
+ DOFORK(fork);
+
+ if (pid < 0)
+ return EX_OSERR;
+ else if (pid == 0)
+ {
+ /* child -- actually write to file */
+ struct stat stb;
+ MCI mcibuf;
+ int err;
+ volatile int oflags = O_WRONLY|O_APPEND;
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+
+ if (e->e_lockfp != NULL)
+ (void) close(sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD,
+ NULL));
+
+ (void) sm_signal(SIGINT, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_DFL);
+ (void) sm_signal(SIGTERM, SIG_DFL);
+ (void) umask(OldUmask);
+ e->e_to = filename;
+ ExitStat = EX_OK;
+
+ if (setjmp(CtxMailfileTimeout) != 0)
+ {
+ RETURN(EX_TEMPFAIL);
+ }
+
+ if (TimeOuts.to_fileopen > 0)
+ ev = sm_setevent(TimeOuts.to_fileopen, mailfiletimeout,
+ 0);
+ else
+ ev = NULL;
+
+ /* check file mode to see if set-user-ID */
+ if (stat(targetfile, &stb) < 0)
+ mode = FileMode;
+ else
+ mode = stb.st_mode;
+
+ /* limit the errors to those actually caused in the child */
+ errno = 0;
+ ExitStat = EX_OK;
+
+ /* Allow alias expansions to use the S_IS{U,G}ID bits */
+ if ((ctladdr != NULL && !bitset(QALIAS, ctladdr->q_flags)) ||
+ bitset(SFF_RUNASREALUID, sfflags))
+ {
+ /* ignore set-user-ID and set-group-ID bits */
+ mode &= ~(S_ISGID|S_ISUID);
+ if (tTd(11, 20))
+ sm_dprintf("mailfile: ignoring set-user-ID/set-group-ID bits\n");
+ }
+
+ /* we have to open the data file BEFORE setuid() */
+ if (e->e_dfp == NULL && bitset(EF_HAS_DF, e->e_flags))
+ {
+ char *df = queuename(e, DATAFL_LETTER);
+
+ e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, df,
+ SM_IO_RDONLY, NULL);
+ if (e->e_dfp == NULL)
+ {
+ syserr("mailfile: Cannot open %s for %s from %s",
+ df, e->e_to, e->e_from.q_paddr);
+ }
+ }
+
+ /* select a new user to run as */
+ if (!bitset(SFF_RUNASREALUID, sfflags))
+ {
+ if (bitnset(M_SPECIFIC_UID, mailer->m_flags))
+ {
+ RealUserName = NULL;
+ RealUid = mailer->m_uid;
+ if (RunAsUid != 0 && RealUid != RunAsUid)
+ {
+ /* Only root can change the uid */
+ syserr("mailfile: insufficient privileges to change uid, RunAsUid=%d, RealUid=%d",
+ (int) RunAsUid, (int) RealUid);
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+ else if (bitset(S_ISUID, mode))
+ {
+ RealUserName = NULL;
+ RealUid = stb.st_uid;
+ }
+ else if (ctladdr != NULL && ctladdr->q_uid != 0)
+ {
+ if (ctladdr->q_ruser != NULL)
+ RealUserName = ctladdr->q_ruser;
+ else
+ RealUserName = ctladdr->q_user;
+ RealUid = ctladdr->q_uid;
+ }
+ else if (mailer != NULL && mailer->m_uid != 0)
+ {
+ RealUserName = DefUser;
+ RealUid = mailer->m_uid;
+ }
+ else
+ {
+ RealUserName = DefUser;
+ RealUid = DefUid;
+ }
+
+ /* select a new group to run as */
+ if (bitnset(M_SPECIFIC_UID, mailer->m_flags))
+ {
+ RealGid = mailer->m_gid;
+ if (RunAsUid != 0 &&
+ (RealGid != getgid() ||
+ RealGid != getegid()))
+ {
+ /* Only root can change the gid */
+ syserr("mailfile: insufficient privileges to change gid, RealGid=%d, RunAsUid=%d, gid=%d, egid=%d",
+ (int) RealGid, (int) RunAsUid,
+ (int) getgid(), (int) getegid());
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+ else if (bitset(S_ISGID, mode))
+ RealGid = stb.st_gid;
+ else if (ctladdr != NULL &&
+ ctladdr->q_uid == DefUid &&
+ ctladdr->q_gid == 0)
+ {
+ /*
+ ** Special case: This means it is an
+ ** alias and we should act as DefaultUser.
+ ** See alias()'s comments.
+ */
+
+ RealGid = DefGid;
+ RealUserName = DefUser;
+ }
+ else if (ctladdr != NULL && ctladdr->q_uid != 0)
+ RealGid = ctladdr->q_gid;
+ else if (mailer != NULL && mailer->m_gid != 0)
+ RealGid = mailer->m_gid;
+ else
+ RealGid = DefGid;
+ }
+
+ /* last ditch */
+ if (!bitset(SFF_ROOTOK, sfflags))
+ {
+ if (RealUid == 0)
+ RealUid = DefUid;
+ if (RealGid == 0)
+ RealGid = DefGid;
+ }
+
+ /* set group id list (needs /etc/group access) */
+ if (RealUserName != NULL && !DontInitGroups)
+ {
+ if (initgroups(RealUserName, RealGid) == -1 && suidwarn)
+ {
+ syserr("mailfile: initgroups(%s, %d) failed",
+ RealUserName, RealGid);
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = RealGid;
+ if (setgroups(1, gidset) == -1 && suidwarn)
+ {
+ syserr("mailfile: setgroups() failed");
+ RETURN(EX_TEMPFAIL);
+ }
+ }
+
+ /*
+ ** If you have a safe environment, go into it.
+ */
+
+ if (realfile != targetfile)
+ {
+ char save;
+
+ save = *realfile;
+ *realfile = '\0';
+ if (tTd(11, 20))
+ sm_dprintf("mailfile: chroot %s\n", targetfile);
+ if (chroot(targetfile) < 0)
+ {
+ syserr("mailfile: Cannot chroot(%s)",
+ targetfile);
+ RETURN(EX_CANTCREAT);
+ }
+ *realfile = save;
+ }
+
+ if (tTd(11, 40))
+ sm_dprintf("mailfile: deliver to %s\n", realfile);
+
+ if (chdir("/") < 0)
+ {
+ syserr("mailfile: cannot chdir(/)");
+ RETURN(EX_CANTCREAT);
+ }
+
+ /* now reset the group and user ids */
+ endpwent();
+ sm_mbdb_terminate();
+ if (setgid(RealGid) < 0 && suidwarn)
+ {
+ syserr("mailfile: setgid(%ld) failed", (long) RealGid);
+ RETURN(EX_TEMPFAIL);
+ }
+ vendor_set_uid(RealUid);
+ if (setuid(RealUid) < 0 && suidwarn)
+ {
+ syserr("mailfile: setuid(%ld) failed", (long) RealUid);
+ RETURN(EX_TEMPFAIL);
+ }
+
+ if (tTd(11, 2))
+ sm_dprintf("mailfile: running as r/euid=%d/%d, r/egid=%d/%d\n",
+ (int) getuid(), (int) geteuid(),
+ (int) getgid(), (int) getegid());
+
+
+ /* move into some "safe" directory */
+ if (mailer->m_execdir != NULL)
+ {
+ char *q;
+
+ for (p = mailer->m_execdir; p != NULL; p = q)
+ {
+ q = strchr(p, ':');
+ if (q != NULL)
+ *q = '\0';
+ expand(p, buf, sizeof buf, e);
+ if (q != NULL)
+ *q++ = ':';
+ if (tTd(11, 20))
+ sm_dprintf("mailfile: trydir %s\n",
+ buf);
+ if (buf[0] != '\0' && chdir(buf) >= 0)
+ break;
+ }
+ }
+
+ /*
+ ** Recheck the file after we have assumed the ID of the
+ ** delivery user to make sure we can deliver to it as
+ ** that user. This is necessary if sendmail is running
+ ** as root and the file is on an NFS mount which treats
+ ** root as nobody.
+ */
+
+#if HASLSTAT
+ if (bitnset(DBS_FILEDELIVERYTOSYMLINK, DontBlameSendmail))
+ err = stat(realfile, &stb);
+ else
+ err = lstat(realfile, &stb);
+#else /* HASLSTAT */
+ err = stat(realfile, &stb);
+#endif /* HASLSTAT */
+
+ if (err < 0)
+ {
+ stb.st_mode = ST_MODE_NOFILE;
+ mode = FileMode;
+ oflags |= O_CREAT|O_EXCL;
+ }
+ else if (bitset(S_IXUSR|S_IXGRP|S_IXOTH, mode) ||
+ (!bitnset(DBS_FILEDELIVERYTOHARDLINK,
+ DontBlameSendmail) &&
+ stb.st_nlink != 1) ||
+ (realfile != targetfile && !S_ISREG(mode)))
+ exit(EX_CANTCREAT);
+ else
+ mode = stb.st_mode;
+
+ if (!bitnset(DBS_FILEDELIVERYTOSYMLINK, DontBlameSendmail))
+ sfflags |= SFF_NOSLINK;
+ if (!bitnset(DBS_FILEDELIVERYTOHARDLINK, DontBlameSendmail))
+ sfflags |= SFF_NOHLINK;
+ sfflags &= ~SFF_OPENASROOT;
+ f = safefopen(realfile, oflags, mode, sfflags);
+ if (f == NULL)
+ {
+ if (transienterror(errno))
+ {
+ usrerr("454 4.3.0 cannot open %s: %s",
+ shortenstring(realfile, MAXSHORTSTR),
+ sm_errstring(errno));
+ RETURN(EX_TEMPFAIL);
+ }
+ else
+ {
+ usrerr("554 5.3.0 cannot open %s: %s",
+ shortenstring(realfile, MAXSHORTSTR),
+ sm_errstring(errno));
+ RETURN(EX_CANTCREAT);
+ }
+ }
+ if (filechanged(realfile, sm_io_getinfo(f, SM_IO_WHAT_FD, NULL),
+ &stb))
+ {
+ syserr("554 5.3.0 file changed after open");
+ RETURN(EX_CANTCREAT);
+ }
+ if (fstat(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL), &stb) < 0)
+ {
+ syserr("554 5.3.0 cannot fstat %s",
+ sm_errstring(errno));
+ RETURN(EX_CANTCREAT);
+ }
+
+ curoff = stb.st_size;
+
+ if (ev != NULL)
+ sm_clrevent(ev);
+
+ memset(&mcibuf, '\0', sizeof mcibuf);
+ mcibuf.mci_mailer = mailer;
+ mcibuf.mci_out = f;
+ if (bitnset(M_7BITS, mailer->m_flags))
+ mcibuf.mci_flags |= MCIF_7BIT;
+
+ /* clear out per-message flags from connection structure */
+ mcibuf.mci_flags &= ~(MCIF_CVT7TO8|MCIF_CVT8TO7);
+
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ bitnset(M_7BITS, mailer->m_flags))
+ mcibuf.mci_flags |= MCIF_CVT8TO7;
+
+#if MIME7TO8
+ if (bitnset(M_MAKE8BIT, mailer->m_flags) &&
+ !bitset(MCIF_7BIT, mcibuf.mci_flags) &&
+ (p = hvalue("Content-Transfer-Encoding", e->e_header)) != NULL &&
+ (sm_strcasecmp(p, "quoted-printable") == 0 ||
+ sm_strcasecmp(p, "base64") == 0) &&
+ (p = hvalue("Content-Type", e->e_header)) != NULL)
+ {
+ /* may want to convert 7 -> 8 */
+ /* XXX should really parse it here -- and use a class XXX */
+ if (sm_strncasecmp(p, "text/plain", 10) == 0 &&
+ (p[10] == '\0' || p[10] == ' ' || p[10] == ';'))
+ mcibuf.mci_flags |= MCIF_CVT7TO8;
+ }
+#endif /* MIME7TO8 */
+
+ putfromline(&mcibuf, e);
+ (*e->e_puthdr)(&mcibuf, e->e_header, e, M87F_OUTER);
+ (*e->e_putbody)(&mcibuf, e, NULL);
+ putline("\n", &mcibuf);
+ if (sm_io_flush(f, SM_TIME_DEFAULT) != 0 ||
+ (SuperSafe != SAFE_NO &&
+ fsync(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL)) < 0) ||
+ sm_io_error(f))
+ {
+ setstat(EX_IOERR);
+#if !NOFTRUNCATE
+ (void) ftruncate(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL),
+ curoff);
+#endif /* !NOFTRUNCATE */
+ }
+
+ /* reset ISUID & ISGID bits for paranoid systems */
+#if HASFCHMOD
+ (void) fchmod(sm_io_getinfo(f, SM_IO_WHAT_FD, NULL),
+ (MODE_T) mode);
+#else /* HASFCHMOD */
+ (void) chmod(filename, (MODE_T) mode);
+#endif /* HASFCHMOD */
+ if (sm_io_close(f, SM_TIME_DEFAULT) < 0)
+ setstat(EX_IOERR);
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ (void) setuid(RealUid);
+ exit(ExitStat);
+ /* NOTREACHED */
+ }
+ else
+ {
+ /* parent -- wait for exit status */
+ int st;
+
+ st = waitfor(pid);
+ if (st == -1)
+ {
+ syserr("mailfile: %s: wait", mailer->m_name);
+ return EX_SOFTWARE;
+ }
+ if (WIFEXITED(st))
+ {
+ errno = 0;
+ return (WEXITSTATUS(st));
+ }
+ else
+ {
+ syserr("mailfile: %s: child died on signal %d",
+ mailer->m_name, st);
+ return EX_UNAVAILABLE;
+ }
+ /* NOTREACHED */
+ }
+ return EX_UNAVAILABLE; /* avoid compiler warning on IRIX */
+}
+
+static void
+mailfiletimeout()
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxMailfileTimeout, 1);
+}
+/*
+** HOSTSIGNATURE -- return the "signature" for a host.
+**
+** The signature describes how we are going to send this -- it
+** can be just the hostname (for non-Internet hosts) or can be
+** an ordered list of MX hosts.
+**
+** Parameters:
+** m -- the mailer describing this host.
+** host -- the host name.
+**
+** Returns:
+** The signature for this host.
+**
+** Side Effects:
+** Can tweak the symbol table.
+*/
+
+#define MAXHOSTSIGNATURE 8192 /* max len of hostsignature */
+
+char *
+hostsignature(m, host)
+ register MAILER *m;
+ char *host;
+{
+ register char *p;
+ register STAB *s;
+ time_t now;
+#if NAMED_BIND
+ char sep = ':';
+ char prevsep = ':';
+ int i;
+ int len;
+ int nmx;
+ int hl;
+ char *hp;
+ char *endp;
+ int oldoptions = _res.options;
+ char *mxhosts[MAXMXHOSTS + 1];
+ unsigned short mxprefs[MAXMXHOSTS + 1];
+#endif /* NAMED_BIND */
+
+ if (tTd(17, 3))
+ sm_dprintf("hostsignature(%s)\n", host);
+
+ /*
+ ** If local delivery (and not remote), just return a constant.
+ */
+
+ if (bitnset(M_LOCALMAILER, m->m_flags) &&
+ strcmp(m->m_mailer, "[IPC]") != 0 &&
+ !(m->m_argv[0] != NULL && strcmp(m->m_argv[0], "TCP") == 0))
+ return "localhost";
+
+ /*
+ ** Check to see if this uses IPC -- if not, it can't have MX records.
+ */
+
+ if (strcmp(m->m_mailer, "[IPC]") != 0 ||
+ CurEnv->e_sendmode == SM_DEFER)
+ {
+ /* just an ordinary mailer or deferred mode */
+ return host;
+ }
+#if NETUNIX
+ else if (m->m_argv[0] != NULL &&
+ strcmp(m->m_argv[0], "FILE") == 0)
+ {
+ /* rendezvous in the file system, no MX records */
+ return host;
+ }
+#endif /* NETUNIX */
+
+ /*
+ ** Look it up in the symbol table.
+ */
+
+ now = curtime();
+ s = stab(host, ST_HOSTSIG, ST_ENTER);
+ if (s->s_hostsig.hs_sig != NULL)
+ {
+ if (s->s_hostsig.hs_exp >= now)
+ {
+ if (tTd(17, 3))
+ sm_dprintf("hostsignature(): stab(%s) found %s\n", host,
+ s->s_hostsig.hs_sig);
+ return s->s_hostsig.hs_sig;
+ }
+
+ /* signature is expired: clear it */
+ sm_free(s->s_hostsig.hs_sig);
+ s->s_hostsig.hs_sig = NULL;
+ }
+
+ /* set default TTL */
+ s->s_hostsig.hs_exp = now + SM_DEFAULT_TTL;
+
+ /*
+ ** Not already there or expired -- create a signature.
+ */
+
+#if NAMED_BIND
+ if (ConfigLevel < 2)
+ _res.options &= ~(RES_DEFNAMES | RES_DNSRCH); /* XXX */
+
+ for (hp = host; hp != NULL; hp = endp)
+ {
+#if NETINET6
+ if (*hp == '[')
+ {
+ endp = strchr(hp + 1, ']');
+ if (endp != NULL)
+ endp = strpbrk(endp + 1, ":,");
+ }
+ else
+ endp = strpbrk(hp, ":,");
+#else /* NETINET6 */
+ endp = strpbrk(hp, ":,");
+#endif /* NETINET6 */
+ if (endp != NULL)
+ {
+ sep = *endp;
+ *endp = '\0';
+ }
+
+ if (bitnset(M_NOMX, m->m_flags))
+ {
+ /* skip MX lookups */
+ nmx = 1;
+ mxhosts[0] = hp;
+ }
+ else
+ {
+ auto int rcode;
+ int ttl;
+
+ nmx = getmxrr(hp, mxhosts, mxprefs, true, &rcode, true,
+ &ttl);
+ if (nmx <= 0)
+ {
+ int save_errno;
+ register MCI *mci;
+
+ /* update the connection info for this host */
+ save_errno = errno;
+ mci = mci_get(hp, m);
+ mci->mci_errno = save_errno;
+ mci->mci_herrno = h_errno;
+ mci->mci_lastuse = now;
+ if (rcode == EX_NOHOST)
+ mci_setstat(mci, rcode, "5.1.2",
+ "550 Host unknown");
+ else
+ mci_setstat(mci, rcode, NULL, NULL);
+
+ /* use the original host name as signature */
+ nmx = 1;
+ mxhosts[0] = hp;
+ }
+ if (tTd(17, 3))
+ sm_dprintf("hostsignature(): getmxrr() returned %d, mxhosts[0]=%s\n",
+ nmx, mxhosts[0]);
+
+ /*
+ ** Set new TTL: we use only one!
+ ** We could try to use the minimum instead.
+ */
+
+ s->s_hostsig.hs_exp = now + SM_MIN(ttl, SM_DEFAULT_TTL);
+ }
+
+ len = 0;
+ for (i = 0; i < nmx; i++)
+ len += strlen(mxhosts[i]) + 1;
+ if (s->s_hostsig.hs_sig != NULL)
+ len += strlen(s->s_hostsig.hs_sig) + 1;
+ if (len < 0 || len >= MAXHOSTSIGNATURE)
+ {
+ sm_syslog(LOG_WARNING, NOQID, "hostsignature for host '%s' exceeds maxlen (%d): %d",
+ host, MAXHOSTSIGNATURE, len);
+ len = MAXHOSTSIGNATURE;
+ }
+ p = sm_pmalloc_x(len);
+ if (s->s_hostsig.hs_sig != NULL)
+ {
+ (void) sm_strlcpy(p, s->s_hostsig.hs_sig, len);
+ sm_free(s->s_hostsig.hs_sig); /* XXX */
+ s->s_hostsig.hs_sig = p;
+ hl = strlen(p);
+ p += hl;
+ *p++ = prevsep;
+ len -= hl + 1;
+ }
+ else
+ s->s_hostsig.hs_sig = p;
+ for (i = 0; i < nmx; i++)
+ {
+ hl = strlen(mxhosts[i]);
+ if (len - 1 < hl || len <= 1)
+ {
+ /* force to drop out of outer loop */
+ len = -1;
+ break;
+ }
+ if (i != 0)
+ {
+ if (mxprefs[i] == mxprefs[i - 1])
+ *p++ = ',';
+ else
+ *p++ = ':';
+ len--;
+ }
+ (void) sm_strlcpy(p, mxhosts[i], len);
+ p += hl;
+ len -= hl;
+ }
+
+ /*
+ ** break out of loop if len exceeded MAXHOSTSIGNATURE
+ ** because we won't have more space for further hosts
+ ** anyway (separated by : in the .cf file).
+ */
+
+ if (len < 0)
+ break;
+ if (endp != NULL)
+ *endp++ = sep;
+ prevsep = sep;
+ }
+ makelower(s->s_hostsig.hs_sig);
+ if (ConfigLevel < 2)
+ _res.options = oldoptions;
+#else /* NAMED_BIND */
+ /* not using BIND -- the signature is just the host name */
+ /*
+ ** 'host' points to storage that will be freed after we are
+ ** done processing the current envelope, so we copy it.
+ */
+ s->s_hostsig.hs_sig = sm_pstrdup_x(host);
+#endif /* NAMED_BIND */
+ if (tTd(17, 1))
+ sm_dprintf("hostsignature(%s) = %s\n", host, s->s_hostsig.hs_sig);
+ return s->s_hostsig.hs_sig;
+}
+/*
+** PARSE_HOSTSIGNATURE -- parse the "signature" and return MX host array.
+**
+** The signature describes how we are going to send this -- it
+** can be just the hostname (for non-Internet hosts) or can be
+** an ordered list of MX hosts which must be randomized for equal
+** MX preference values.
+**
+** Parameters:
+** sig -- the host signature.
+** mxhosts -- array to populate.
+** mailer -- mailer.
+**
+** Returns:
+** The number of hosts inserted into mxhosts array.
+**
+** Side Effects:
+** Randomizes equal MX preference hosts in mxhosts.
+*/
+
+static int
+parse_hostsignature(sig, mxhosts, mailer)
+ char *sig;
+ char **mxhosts;
+ MAILER *mailer;
+{
+ unsigned short curpref = 0;
+ int nmx = 0, i, j; /* NOTE: i, j, and nmx must have same type */
+ char *hp, *endp;
+ unsigned short prefer[MAXMXHOSTS];
+ long rndm[MAXMXHOSTS];
+
+ for (hp = sig; hp != NULL; hp = endp)
+ {
+ char sep = ':';
+
+#if NETINET6
+ if (*hp == '[')
+ {
+ endp = strchr(hp + 1, ']');
+ if (endp != NULL)
+ endp = strpbrk(endp + 1, ":,");
+ }
+ else
+ endp = strpbrk(hp, ":,");
+#else /* NETINET6 */
+ endp = strpbrk(hp, ":,");
+#endif /* NETINET6 */
+ if (endp != NULL)
+ {
+ sep = *endp;
+ *endp = '\0';
+ }
+
+ mxhosts[nmx] = hp;
+ prefer[nmx] = curpref;
+ if (mci_match(hp, mailer))
+ rndm[nmx] = 0;
+ else
+ rndm[nmx] = get_random();
+
+ if (endp != NULL)
+ {
+ /*
+ ** Since we don't have the original MX prefs,
+ ** make our own. If the separator is a ':', that
+ ** means the preference for the next host will be
+ ** higher than this one, so simply increment curpref.
+ */
+
+ if (sep == ':')
+ curpref++;
+
+ *endp++ = sep;
+ }
+ if (++nmx >= MAXMXHOSTS)
+ break;
+ }
+
+ /* sort the records using the random factor for equal preferences */
+ for (i = 0; i < nmx; i++)
+ {
+ for (j = i + 1; j < nmx; j++)
+ {
+ /*
+ ** List is already sorted by MX preference, only
+ ** need to look for equal preference MX records
+ */
+
+ if (prefer[i] < prefer[j])
+ break;
+
+ if (prefer[i] > prefer[j] ||
+ (prefer[i] == prefer[j] && rndm[i] > rndm[j]))
+ {
+ register unsigned short tempp;
+ register long tempr;
+ register char *temp1;
+
+ tempp = prefer[i];
+ prefer[i] = prefer[j];
+ prefer[j] = tempp;
+ temp1 = mxhosts[i];
+ mxhosts[i] = mxhosts[j];
+ mxhosts[j] = temp1;
+ tempr = rndm[i];
+ rndm[i] = rndm[j];
+ rndm[j] = tempr;
+ }
+ }
+ }
+ return nmx;
+}
+
+# if STARTTLS
+static SSL_CTX *clt_ctx = NULL;
+static bool tls_ok_clt = true;
+
+/*
+** SETCLTTLS -- client side TLS: allow/disallow.
+**
+** Parameters:
+** tls_ok -- should tls be done?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets tls_ok_clt (static variable in this module)
+*/
+
+void
+setclttls(tls_ok)
+ bool tls_ok;
+{
+ tls_ok_clt = tls_ok;
+ return;
+}
+/*
+** INITCLTTLS -- initialize client side TLS
+**
+** Parameters:
+** tls_ok -- should tls initialization be done?
+**
+** Returns:
+** succeeded?
+**
+** Side Effects:
+** sets tls_ok_clt (static variable in this module)
+*/
+
+bool
+initclttls(tls_ok)
+ bool tls_ok;
+{
+ if (!tls_ok_clt)
+ return false;
+ tls_ok_clt = tls_ok;
+ if (!tls_ok_clt)
+ return false;
+ if (clt_ctx != NULL)
+ return true; /* already done */
+ tls_ok_clt = inittls(&clt_ctx, TLS_I_CLT, false, CltCERTfile,
+ Cltkeyfile, CACERTpath, CACERTfile, DHParams);
+ return tls_ok_clt;
+}
+
+/*
+** STARTTLS -- try to start secure connection (client side)
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope.
+**
+** Returns:
+** success?
+** (maybe this should be some other code than EX_
+** that denotes which stage failed.)
+*/
+
+static int
+starttls(m, mci, e)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+{
+ int smtpresult;
+ int result = 0;
+ int rfd, wfd;
+ SSL *clt_ssl = NULL;
+ time_t tlsstart;
+
+ if (clt_ctx == NULL && !initclttls(true))
+ return EX_TEMPFAIL;
+ smtpmessage("STARTTLS", m, mci);
+
+ /* get the reply */
+ smtpresult = reply(m, mci, e, TimeOuts.to_starttls, NULL, NULL);
+
+ /* check return code from server */
+ if (smtpresult == 454)
+ return EX_TEMPFAIL;
+ if (smtpresult == 501)
+ return EX_USAGE;
+ if (smtpresult == -1)
+ return smtpresult;
+ if (smtpresult != 220)
+ return EX_PROTOCOL;
+
+ if (LogLevel > 13)
+ sm_syslog(LOG_INFO, NOQID, "STARTTLS=client, start=ok");
+
+ /* start connection */
+ if ((clt_ssl = SSL_new(clt_ctx)) == NULL)
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=client, error: SSL_new failed");
+ if (LogLevel > 9)
+ tlslogerr("client");
+ }
+ return EX_SOFTWARE;
+ }
+
+ rfd = sm_io_getinfo(mci->mci_in, SM_IO_WHAT_FD, NULL);
+ wfd = sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD, NULL);
+
+ /* SSL_clear(clt_ssl); ? */
+ if (rfd < 0 || wfd < 0 ||
+ (result = SSL_set_rfd(clt_ssl, rfd)) != 1 ||
+ (result = SSL_set_wfd(clt_ssl, wfd)) != 1)
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=client, error: SSL_set_xfd failed=%d",
+ result);
+ if (LogLevel > 9)
+ tlslogerr("client");
+ }
+ return EX_SOFTWARE;
+ }
+ SSL_set_connect_state(clt_ssl);
+ tlsstart = curtime();
+
+ssl_retry:
+ if ((result = SSL_connect(clt_ssl)) <= 0)
+ {
+ int i;
+ bool timedout;
+ time_t left;
+ time_t now = curtime();
+ struct timeval tv;
+
+ /* what to do in this case? */
+ i = SSL_get_error(clt_ssl, result);
+
+ /*
+ ** For SSL_ERROR_WANT_{READ,WRITE}:
+ ** There is not a complete SSL record available yet
+ ** or there is only a partial SSL record removed from
+ ** the network (socket) buffer into the SSL buffer.
+ ** The SSL_connect will only succeed when a full
+ ** SSL record is available (assuming a "real" error
+ ** doesn't happen). To handle when a "real" error
+ ** does happen the select is set for exceptions too.
+ ** The connection may be re-negotiated during this time
+ ** so both read and write "want errors" need to be handled.
+ ** A select() exception loops back so that a proper SSL
+ ** error message can be gotten.
+ */
+
+ left = TimeOuts.to_starttls - (now - tlsstart);
+ timedout = left <= 0;
+ if (!timedout)
+ {
+ tv.tv_sec = left;
+ tv.tv_usec = 0;
+ }
+
+ if (!timedout && i == SSL_ERROR_WANT_READ)
+ {
+ fd_set ssl_maskr, ssl_maskx;
+
+ FD_ZERO(&ssl_maskr);
+ FD_SET(rfd, &ssl_maskr);
+ FD_ZERO(&ssl_maskx);
+ FD_SET(rfd, &ssl_maskx);
+ if (select(rfd + 1, &ssl_maskr, NULL, &ssl_maskx, &tv)
+ > 0)
+ goto ssl_retry;
+ }
+ if (!timedout && i == SSL_ERROR_WANT_WRITE)
+ {
+ fd_set ssl_maskw, ssl_maskx;
+
+ FD_ZERO(&ssl_maskw);
+ FD_SET(wfd, &ssl_maskw);
+ FD_ZERO(&ssl_maskx);
+ FD_SET(rfd, &ssl_maskx);
+ if (select(wfd + 1, NULL, &ssl_maskw, &ssl_maskx, &tv)
+ > 0)
+ goto ssl_retry;
+ }
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_ERR, e->e_id,
+ "STARTTLS=client, error: connect failed=%d, SSL_error=%d, timedout=%d",
+ result, i, (int) timedout);
+ if (LogLevel > 8)
+ tlslogerr("client");
+ }
+ SSL_free(clt_ssl);
+ clt_ssl = NULL;
+ return EX_SOFTWARE;
+ }
+ mci->mci_ssl = clt_ssl;
+ result = tls_get_info(mci->mci_ssl, false, mci->mci_host,
+ &mci->mci_macro, true);
+
+ /* switch to use TLS... */
+ if (sfdctls(&mci->mci_in, &mci->mci_out, mci->mci_ssl) == 0)
+ return EX_OK;
+
+ /* failure */
+ SSL_free(clt_ssl);
+ clt_ssl = NULL;
+ return EX_SOFTWARE;
+}
+/*
+** ENDTLSCLT -- shutdown secure connection (client side)
+**
+** Parameters:
+** mci -- the mailer connection info.
+**
+** Returns:
+** success?
+*/
+
+static int
+endtlsclt(mci)
+ MCI *mci;
+{
+ int r;
+
+ if (!bitset(MCIF_TLSACT, mci->mci_flags))
+ return EX_OK;
+ r = endtls(mci->mci_ssl, "client");
+ mci->mci_flags &= ~MCIF_TLSACT;
+ return r;
+}
+# endif /* STARTTLS */
+# if STARTTLS || SASL
+/*
+** ISCLTFLGSET -- check whether client flag is set.
+**
+** Parameters:
+** e -- envelope.
+** flag -- flag to check in {client_flags}
+**
+** Returns:
+** true iff flag is set.
+*/
+
+static bool
+iscltflgset(e, flag)
+ ENVELOPE *e;
+ int flag;
+{
+ char *p;
+
+ p = macvalue(macid("{client_flags}"), e);
+ if (p == NULL)
+ return false;
+ for (; *p != '\0'; p++)
+ {
+ /* look for just this one flag */
+ if (*p == (char) flag)
+ return true;
+ }
+ return false;
+}
+# endif /* STARTTLS || SASL */
diff --git a/contrib/sendmail/src/domain.c b/contrib/sendmail/src/domain.c
new file mode 100644
index 0000000..f086d80
--- /dev/null
+++ b/contrib/sendmail/src/domain.c
@@ -0,0 +1,1248 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1986, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+#if NAMED_BIND
+SM_RCSID("@(#)$Id: domain.c,v 8.181.2.1 2002/06/27 16:55:04 ca Exp $ (with name server)")
+#else /* NAMED_BIND */
+SM_RCSID("@(#)$Id: domain.c,v 8.181.2.1 2002/06/27 16:55:04 ca Exp $ (without name server)")
+#endif /* NAMED_BIND */
+
+#if NAMED_BIND
+
+# include <arpa/inet.h>
+
+
+/*
+** The standard udp packet size PACKETSZ (512) is not sufficient for some
+** nameserver answers containing very many resource records. The resolver
+** may switch to tcp and retry if it detects udp packet overflow.
+** Also note that the resolver routines res_query and res_search return
+** the size of the *un*truncated answer in case the supplied answer buffer
+** it not big enough to accommodate the entire answer.
+*/
+
+# ifndef MAXPACKET
+# define MAXPACKET 8192 /* max packet size used internally by BIND */
+# endif /* ! MAXPACKET */
+
+typedef union
+{
+ HEADER qb1;
+ unsigned char qb2[MAXPACKET];
+} querybuf;
+
+# ifndef MXHOSTBUFSIZE
+# define MXHOSTBUFSIZE (128 * MAXMXHOSTS)
+# endif /* ! MXHOSTBUFSIZE */
+
+static char MXHostBuf[MXHOSTBUFSIZE];
+#if (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2)
+ ERROR: _MXHOSTBUFSIZE is out of range
+#endif /* (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2) */
+
+# ifndef MAXDNSRCH
+# define MAXDNSRCH 6 /* number of possible domains to search */
+# endif /* ! MAXDNSRCH */
+
+# ifndef RES_DNSRCH_VARIABLE
+# define RES_DNSRCH_VARIABLE _res.dnsrch
+# endif /* ! RES_DNSRCH_VARIABLE */
+
+# ifndef NO_DATA
+# define NO_DATA NO_ADDRESS
+# endif /* ! NO_DATA */
+
+# ifndef HFIXEDSZ
+# define HFIXEDSZ 12 /* sizeof(HEADER) */
+# endif /* ! HFIXEDSZ */
+
+# define MAXCNAMEDEPTH 10 /* maximum depth of CNAME recursion */
+
+# if defined(__RES) && (__RES >= 19940415)
+# define RES_UNC_T char *
+# else /* defined(__RES) && (__RES >= 19940415) */
+# define RES_UNC_T unsigned char *
+# endif /* defined(__RES) && (__RES >= 19940415) */
+
+static char *gethostalias __P((char *));
+static int mxrand __P((char *));
+static int fallbackmxrr __P((int, unsigned short *, char **));
+
+/*
+** GETFALLBACKMXRR -- get MX resource records for fallback MX host.
+**
+** We have to initialize this once before doing anything else.
+** Moreover, we have to repeat this from time to time to avoid
+** stale data, e.g., in persistent queue runners.
+** This should be done in a parent process so the child
+** processes have the right data.
+**
+** Parameters:
+** host -- the name of the fallback MX host.
+**
+** Returns:
+** number of MX records.
+**
+** Side Effects:
+** Populates NumFallBackMXHosts and fbhosts.
+** Sets renewal time (based on TTL).
+*/
+
+int NumFallBackMXHosts = 0; /* Number of fallback MX hosts (after MX expansion) */
+static char *fbhosts[MAXMXHOSTS + 1];
+
+int
+getfallbackmxrr(host)
+ char *host;
+{
+ int i, rcode;
+ int ttl;
+ static time_t renew = 0;
+
+#if 0
+ /* This is currently done before this function is called. */
+ if (host == NULL || *host == '\0')
+ return 0;
+#endif /* 0 */
+ if (NumFallBackMXHosts > 0 && renew > curtime())
+ return NumFallBackMXHosts;
+ if (host[0] == '[')
+ {
+ fbhosts[0] = host;
+ NumFallBackMXHosts = 1;
+ }
+ else
+ {
+ /* free old data */
+ for (i = 0; i < NumFallBackMXHosts; i++)
+ sm_free(fbhosts[i]);
+
+ /* get new data */
+ NumFallBackMXHosts = getmxrr(host, fbhosts, NULL, false,
+ &rcode, false, &ttl);
+ renew = curtime() + ttl;
+ for (i = 0; i < NumFallBackMXHosts; i++)
+ fbhosts[i] = newstr(fbhosts[i]);
+ }
+ return NumFallBackMXHosts;
+}
+
+/*
+** FALLBACKMXRR -- add MX resource records for fallback MX host to list.
+**
+** Parameters:
+** nmx -- current number of MX records.
+** prefs -- array of preferences.
+** mxhosts -- array of MX hosts (maximum size: MAXMXHOSTS)
+**
+** Returns:
+** new number of MX records.
+**
+** Side Effects:
+** If FallBackMX was set, it appends the MX records for
+** that host to mxhosts (and modifies prefs accordingly).
+*/
+
+static int
+fallbackmxrr(nmx, prefs, mxhosts)
+ int nmx;
+ unsigned short *prefs;
+ char **mxhosts;
+{
+ int i;
+
+ for (i = 0; i < NumFallBackMXHosts && nmx < MAXMXHOSTS; i++)
+ {
+ if (nmx > 0)
+ prefs[nmx] = prefs[nmx - 1] + 1;
+ else
+ prefs[nmx] = 0;
+ mxhosts[nmx++] = fbhosts[i];
+ }
+ return nmx;
+}
+
+/*
+** GETMXRR -- get MX resource records for a domain
+**
+** Parameters:
+** host -- the name of the host to MX.
+** mxhosts -- a pointer to a return buffer of MX records.
+** mxprefs -- a pointer to a return buffer of MX preferences.
+** If NULL, don't try to populate.
+** droplocalhost -- If true, all MX records less preferred
+** than the local host (as determined by $=w) will
+** be discarded.
+** rcode -- a pointer to an EX_ status code.
+** tryfallback -- add also fallback MX host?
+** pttl -- pointer to return TTL (can be NULL).
+**
+** Returns:
+** The number of MX records found.
+** -1 if there is an internal failure.
+** If no MX records are found, mxhosts[0] is set to host
+** and 1 is returned.
+**
+** Side Effects:
+** The entries made for mxhosts point to a static array
+** MXHostBuf[MXHOSTBUFSIZE], so the data needs to be copied,
+** if it must be preserved across calls to this function.
+*/
+
+int
+getmxrr(host, mxhosts, mxprefs, droplocalhost, rcode, tryfallback, pttl)
+ char *host;
+ char **mxhosts;
+ unsigned short *mxprefs;
+ bool droplocalhost;
+ int *rcode;
+ bool tryfallback;
+ int *pttl;
+{
+ register unsigned char *eom, *cp;
+ register int i, j, n;
+ int nmx = 0;
+ register char *bp;
+ HEADER *hp;
+ querybuf answer;
+ int ancount, qdcount, buflen;
+ bool seenlocal = false;
+ unsigned short pref, type;
+ unsigned short localpref = 256;
+ char *fallbackMX = FallBackMX;
+ bool trycanon = false;
+ unsigned short *prefs;
+ int (*resfunc)();
+ unsigned short prefer[MAXMXHOSTS];
+ int weight[MAXMXHOSTS];
+ int ttl = 0;
+ extern int res_query(), res_search();
+
+ if (tTd(8, 2))
+ sm_dprintf("getmxrr(%s, droplocalhost=%d)\n",
+ host, droplocalhost);
+
+ if ((fallbackMX != NULL && droplocalhost &&
+ wordinclass(fallbackMX, 'w')) || !tryfallback)
+ {
+ /* don't use fallback for this pass */
+ fallbackMX = NULL;
+ }
+
+ *rcode = EX_OK;
+
+ if (mxprefs != NULL)
+ prefs = mxprefs;
+ else
+ prefs = prefer;
+
+ /* efficiency hack -- numeric or non-MX lookups */
+ if (host[0] == '[')
+ goto punt;
+
+ /*
+ ** If we don't have MX records in our host switch, don't
+ ** try for MX records. Note that this really isn't "right",
+ ** since we might be set up to try NIS first and then DNS;
+ ** if the host is found in NIS we really shouldn't be doing
+ ** MX lookups. However, that should be a degenerate case.
+ */
+
+ if (!UseNameServer)
+ goto punt;
+ if (HasWildcardMX && ConfigLevel >= 6)
+ resfunc = res_query;
+ else
+ resfunc = res_search;
+
+ errno = 0;
+ n = (*resfunc)(host, C_IN, T_MX, (unsigned char *) &answer,
+ sizeof(answer));
+ if (n < 0)
+ {
+ if (tTd(8, 1))
+ sm_dprintf("getmxrr: res_search(%s) failed (errno=%d, h_errno=%d)\n",
+ host == NULL ? "<NULL>" : host, errno, h_errno);
+ switch (h_errno)
+ {
+ case NO_DATA:
+ trycanon = true;
+ /* FALLTHROUGH */
+
+ case NO_RECOVERY:
+ /* no MX data on this host */
+ goto punt;
+
+ case HOST_NOT_FOUND:
+# if BROKEN_RES_SEARCH
+ case 0: /* Ultrix resolver retns failure w/ h_errno=0 */
+# endif /* BROKEN_RES_SEARCH */
+ /* host doesn't exist in DNS; might be in /etc/hosts */
+ trycanon = true;
+ *rcode = EX_NOHOST;
+ goto punt;
+
+ case TRY_AGAIN:
+ case -1:
+ /* couldn't connect to the name server */
+ if (fallbackMX != NULL)
+ {
+ /* name server is hosed -- push to fallback */
+ return fallbackmxrr(nmx, prefs, mxhosts);
+ }
+ /* it might come up later; better queue it up */
+ *rcode = EX_TEMPFAIL;
+ break;
+
+ default:
+ syserr("getmxrr: res_search (%s) failed with impossible h_errno (%d)",
+ host, h_errno);
+ *rcode = EX_OSERR;
+ break;
+ }
+
+ /* irreconcilable differences */
+ return -1;
+ }
+
+ /* avoid problems after truncation in tcp packets */
+ if (n > sizeof(answer))
+ n = sizeof(answer);
+
+ /* find first satisfactory answer */
+ hp = (HEADER *)&answer;
+ cp = (unsigned char *)&answer + HFIXEDSZ;
+ eom = (unsigned char *)&answer + n;
+ for (qdcount = ntohs((unsigned short) hp->qdcount);
+ qdcount--;
+ cp += n + QFIXEDSZ)
+ {
+ if ((n = dn_skipname(cp, eom)) < 0)
+ goto punt;
+ }
+
+ /* NOTE: see definition of MXHostBuf! */
+ buflen = sizeof(MXHostBuf) - 1;
+ SM_ASSERT(buflen > 0);
+ bp = MXHostBuf;
+ ancount = ntohs((unsigned short) hp->ancount);
+
+ /* See RFC 1035 for layout of RRs. */
+ /* XXX leave room for FallBackMX ? */
+ while (--ancount >= 0 && cp < eom && nmx < MAXMXHOSTS - 1)
+ {
+ if ((n = dn_expand((unsigned char *)&answer, eom, cp,
+ (RES_UNC_T) bp, buflen)) < 0)
+ break;
+ cp += n;
+ GETSHORT(type, cp);
+ cp += INT16SZ; /* skip over class */
+ GETLONG(ttl, cp);
+ GETSHORT(n, cp); /* rdlength */
+ if (type != T_MX)
+ {
+ if (tTd(8, 8) || _res.options & RES_DEBUG)
+ sm_dprintf("unexpected answer type %d, size %d\n",
+ type, n);
+ cp += n;
+ continue;
+ }
+ GETSHORT(pref, cp);
+ if ((n = dn_expand((unsigned char *)&answer, eom, cp,
+ (RES_UNC_T) bp, buflen)) < 0)
+ break;
+ cp += n;
+ n = strlen(bp);
+# if 0
+ /* Can this happen? */
+ if (n == 0)
+ {
+ if (LogLevel > 4)
+ sm_syslog(LOG_ERR, NOQID,
+ "MX records for %s contain empty string",
+ host);
+ continue;
+ }
+# endif /* 0 */
+ if (wordinclass(bp, 'w'))
+ {
+ if (tTd(8, 3))
+ sm_dprintf("found localhost (%s) in MX list, pref=%d\n",
+ bp, pref);
+ if (droplocalhost)
+ {
+ if (!seenlocal || pref < localpref)
+ localpref = pref;
+ seenlocal = true;
+ continue;
+ }
+ weight[nmx] = 0;
+ }
+ else
+ weight[nmx] = mxrand(bp);
+ prefs[nmx] = pref;
+ mxhosts[nmx++] = bp;
+ bp += n;
+ if (bp[-1] != '.')
+ {
+ *bp++ = '.';
+ n++;
+ }
+ *bp++ = '\0';
+ if (buflen < n + 1)
+ {
+ /* don't want to wrap buflen */
+ break;
+ }
+ buflen -= n + 1;
+ }
+
+ /* return only one TTL entry, that should be sufficient */
+ if (ttl > 0 && pttl != NULL)
+ *pttl = ttl;
+
+ /* sort the records */
+ for (i = 0; i < nmx; i++)
+ {
+ for (j = i + 1; j < nmx; j++)
+ {
+ if (prefs[i] > prefs[j] ||
+ (prefs[i] == prefs[j] && weight[i] > weight[j]))
+ {
+ register int temp;
+ register char *temp1;
+
+ temp = prefs[i];
+ prefs[i] = prefs[j];
+ prefs[j] = temp;
+ temp1 = mxhosts[i];
+ mxhosts[i] = mxhosts[j];
+ mxhosts[j] = temp1;
+ temp = weight[i];
+ weight[i] = weight[j];
+ weight[j] = temp;
+ }
+ }
+ if (seenlocal && prefs[i] >= localpref)
+ {
+ /* truncate higher preference part of list */
+ nmx = i;
+ }
+ }
+
+ /* delete duplicates from list (yes, some bozos have duplicates) */
+ for (i = 0; i < nmx - 1; )
+ {
+ if (sm_strcasecmp(mxhosts[i], mxhosts[i + 1]) != 0)
+ i++;
+ else
+ {
+ /* compress out duplicate */
+ for (j = i + 1; j < nmx; j++)
+ {
+ mxhosts[j] = mxhosts[j + 1];
+ prefs[j] = prefs[j + 1];
+ }
+ nmx--;
+ }
+ }
+
+ if (nmx == 0)
+ {
+punt:
+ if (seenlocal)
+ {
+ struct hostent *h = NULL;
+
+ /*
+ ** If we have deleted all MX entries, this is
+ ** an error -- we should NEVER send to a host that
+ ** has an MX, and this should have been caught
+ ** earlier in the config file.
+ **
+ ** Some sites prefer to go ahead and try the
+ ** A record anyway; that case is handled by
+ ** setting TryNullMXList. I believe this is a
+ ** bad idea, but it's up to you....
+ */
+
+ if (TryNullMXList)
+ {
+ SM_SET_H_ERRNO(0);
+ errno = 0;
+ h = sm_gethostbyname(host, AF_INET);
+ if (h == NULL)
+ {
+ if (errno == ETIMEDOUT ||
+ h_errno == TRY_AGAIN ||
+ (errno == ECONNREFUSED &&
+ UseNameServer))
+ {
+ *rcode = EX_TEMPFAIL;
+ return -1;
+ }
+# if NETINET6
+ SM_SET_H_ERRNO(0);
+ errno = 0;
+ h = sm_gethostbyname(host, AF_INET6);
+ if (h == NULL &&
+ (errno == ETIMEDOUT ||
+ h_errno == TRY_AGAIN ||
+ (errno == ECONNREFUSED &&
+ UseNameServer)))
+ {
+ *rcode = EX_TEMPFAIL;
+ return -1;
+ }
+# endif /* NETINET6 */
+ }
+ }
+
+ if (h == NULL)
+ {
+ *rcode = EX_CONFIG;
+ syserr("MX list for %s points back to %s",
+ host, MyHostName);
+ return -1;
+ }
+# if NETINET6
+ freehostent(h);
+ hp = NULL;
+# endif /* NETINET6 */
+ }
+ if (strlen(host) >= sizeof MXHostBuf)
+ {
+ *rcode = EX_CONFIG;
+ syserr("Host name %s too long",
+ shortenstring(host, MAXSHORTSTR));
+ return -1;
+ }
+ (void) sm_strlcpy(MXHostBuf, host, sizeof MXHostBuf);
+ mxhosts[0] = MXHostBuf;
+ prefs[0] = 0;
+ if (host[0] == '[')
+ {
+ register char *p;
+# if NETINET6
+ struct sockaddr_in6 tmp6;
+# endif /* NETINET6 */
+
+ /* this may be an MX suppression-style address */
+ p = strchr(MXHostBuf, ']');
+ if (p != NULL)
+ {
+ *p = '\0';
+
+ if (inet_addr(&MXHostBuf[1]) != INADDR_NONE)
+ {
+ nmx++;
+ *p = ']';
+ }
+# if NETINET6
+ else if (anynet_pton(AF_INET6, &MXHostBuf[1],
+ &tmp6.sin6_addr) == 1)
+ {
+ nmx++;
+ *p = ']';
+ }
+# endif /* NETINET6 */
+ else
+ {
+ trycanon = true;
+ mxhosts[0]++;
+ }
+ }
+ }
+ if (trycanon &&
+ getcanonname(mxhosts[0], sizeof MXHostBuf - 2, false, pttl))
+ {
+ /* XXX MXHostBuf == "" ? is that possible? */
+ bp = &MXHostBuf[strlen(MXHostBuf)];
+ if (bp[-1] != '.')
+ {
+ *bp++ = '.';
+ *bp = '\0';
+ }
+ nmx = 1;
+ }
+ }
+
+ /* if we have a default lowest preference, include that */
+ if (fallbackMX != NULL && !seenlocal)
+ {
+ nmx = fallbackmxrr(nmx, prefs, mxhosts);
+ }
+ return nmx;
+}
+/*
+** MXRAND -- create a randomizer for equal MX preferences
+**
+** If two MX hosts have equal preferences we want to randomize
+** the selection. But in order for signatures to be the same,
+** we need to randomize the same way each time. This function
+** computes a pseudo-random hash function from the host name.
+**
+** Parameters:
+** host -- the name of the host.
+**
+** Returns:
+** A random but repeatable value based on the host name.
+*/
+
+static int
+mxrand(host)
+ register char *host;
+{
+ int hfunc;
+ static unsigned int seed;
+
+ if (seed == 0)
+ {
+ seed = (int) curtime() & 0xffff;
+ if (seed == 0)
+ seed++;
+ }
+
+ if (tTd(17, 9))
+ sm_dprintf("mxrand(%s)", host);
+
+ hfunc = seed;
+ while (*host != '\0')
+ {
+ int c = *host++;
+
+ if (isascii(c) && isupper(c))
+ c = tolower(c);
+ hfunc = ((hfunc << 1) ^ c) % 2003;
+ }
+
+ hfunc &= 0xff;
+ hfunc++;
+
+ if (tTd(17, 9))
+ sm_dprintf(" = %d\n", hfunc);
+ return hfunc;
+}
+/*
+** BESTMX -- find the best MX for a name
+**
+** This is really a hack, but I don't see any obvious way
+** to generalize it at the moment.
+*/
+
+/* ARGSUSED3 */
+char *
+bestmx_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int nmx;
+ int saveopts = _res.options;
+ int i;
+ ssize_t len = 0;
+ char *result;
+ char *mxhosts[MAXMXHOSTS + 1];
+#if _FFR_BESTMX_BETTER_TRUNCATION
+ char *buf;
+#else /* _FFR_BESTMX_BETTER_TRUNCATION */
+ char *p;
+ char buf[PSBUFSIZE / 2];
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
+
+ _res.options &= ~(RES_DNSRCH|RES_DEFNAMES);
+ nmx = getmxrr(name, mxhosts, NULL, false, statp, false, NULL);
+ _res.options = saveopts;
+ if (nmx <= 0)
+ return NULL;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ if ((map->map_coldelim == '\0') || (nmx == 1))
+ return map_rewrite(map, mxhosts[0], strlen(mxhosts[0]), av);
+
+ /*
+ ** We were given a -z flag (return all MXs) and there are multiple
+ ** ones. We need to build them all into a list.
+ */
+
+#if _FFR_BESTMX_BETTER_TRUNCATION
+ for (i = 0; i < nmx; i++)
+ {
+ if (strchr(mxhosts[i], map->map_coldelim) != NULL)
+ {
+ syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X",
+ mxhosts[i], map->map_coldelim);
+ return NULL;
+ }
+ len += strlen(mxhosts[i]) + 1;
+ if (len < 0)
+ {
+ len -= strlen(mxhosts[i]) + 1;
+ break;
+ }
+ }
+ buf = (char *) sm_malloc(len);
+ if (buf == NULL)
+ {
+ *statp = EX_UNAVAILABLE;
+ return NULL;
+ }
+ *buf = '\0';
+ for (i = 0; i < nmx; i++)
+ {
+ int end;
+
+ end = sm_strlcat(buf, mxhosts[i], len);
+ if (i != nmx && end + 1 < len)
+ {
+ buf[end] = map->map_coldelim;
+ buf[end + 1] = '\0';
+ }
+ }
+
+ /* Cleanly truncate for rulesets */
+ truncate_at_delim(buf, PSBUFSIZE / 2, map->map_coldelim);
+#else /* _FFR_BESTMX_BETTER_TRUNCATION */
+ p = buf;
+ for (i = 0; i < nmx; i++)
+ {
+ size_t slen;
+
+ if (strchr(mxhosts[i], map->map_coldelim) != NULL)
+ {
+ syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X",
+ mxhosts[i], map->map_coldelim);
+ return NULL;
+ }
+ slen = strlen(mxhosts[i]);
+ if (len + slen + 2 > sizeof buf)
+ break;
+ if (i > 0)
+ {
+ *p++ = map->map_coldelim;
+ len++;
+ }
+ (void) sm_strlcpy(p, mxhosts[i], sizeof buf - len);
+ p += slen;
+ len += slen;
+ }
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
+
+ result = map_rewrite(map, buf, len, av);
+#if _FFR_BESTMX_BETTER_TRUNCATION
+ sm_free(buf);
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
+ return result;
+}
+/*
+** DNS_GETCANONNAME -- get the canonical name for named host using DNS
+**
+** This algorithm tries to be smart about wildcard MX records.
+** This is hard to do because DNS doesn't tell is if we matched
+** against a wildcard or a specific MX.
+**
+** We always prefer A & CNAME records, since these are presumed
+** to be specific.
+**
+** If we match an MX in one pass and lose it in the next, we use
+** the old one. For example, consider an MX matching *.FOO.BAR.COM.
+** A hostname bletch.foo.bar.com will match against this MX, but
+** will stop matching when we try bletch.bar.com -- so we know
+** that bletch.foo.bar.com must have been right. This fails if
+** there was also an MX record matching *.BAR.COM, but there are
+** some things that just can't be fixed.
+**
+** Parameters:
+** host -- a buffer containing the name of the host.
+** This is a value-result parameter.
+** hbsize -- the size of the host buffer.
+** trymx -- if set, try MX records as well as A and CNAME.
+** statp -- pointer to place to store status.
+** pttl -- pointer to return TTL (can be NULL).
+**
+** Returns:
+** true -- if the host matched.
+** false -- otherwise.
+*/
+
+# if NETINET6
+# define SM_T_INITIAL T_AAAA
+# else /* NETINET6 */
+# define SM_T_INITIAL T_A
+# endif /* NETINET6 */
+
+bool
+dns_getcanonname(host, hbsize, trymx, statp, pttl)
+ char *host;
+ int hbsize;
+ bool trymx;
+ int *statp;
+ int *pttl;
+{
+ register unsigned char *eom, *ap;
+ register char *cp;
+ register int n;
+ HEADER *hp;
+ querybuf answer;
+ int ancount, qdcount;
+ int ret;
+ char **domain;
+ int type;
+ int ttl = 0;
+ char **dp;
+ char *mxmatch;
+ bool amatch;
+ bool gotmx = false;
+ int qtype;
+ int loopcnt;
+ char *xp;
+ char nbuf[SM_MAX(MAXPACKET, MAXDNAME*2+2)];
+ char *searchlist[MAXDNSRCH + 2];
+
+ if (tTd(8, 2))
+ sm_dprintf("dns_getcanonname(%s, trymx=%d)\n", host, trymx);
+
+ if ((_res.options & RES_INIT) == 0 && res_init() == -1)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+
+ *statp = EX_OK;
+
+ /*
+ ** Initialize domain search list. If there is at least one
+ ** dot in the name, search the unmodified name first so we
+ ** find "vse.CS" in Czechoslovakia instead of in the local
+ ** domain (e.g., vse.CS.Berkeley.EDU). Note that there is no
+ ** longer a country named Czechoslovakia but this type of problem
+ ** is still present.
+ **
+ ** Older versions of the resolver could create this
+ ** list by tearing apart the host name.
+ */
+
+ loopcnt = 0;
+cnameloop:
+ /* Check for dots in the name */
+ for (cp = host, n = 0; *cp != '\0'; cp++)
+ if (*cp == '.')
+ n++;
+
+ /*
+ ** If this is a simple name, determine whether it matches an
+ ** alias in the file defined by the environment variable HOSTALIASES.
+ */
+
+ if (n == 0 && (xp = gethostalias(host)) != NULL)
+ {
+ if (loopcnt++ > MAXCNAMEDEPTH)
+ {
+ syserr("loop in ${HOSTALIASES} file");
+ }
+ else
+ {
+ (void) sm_strlcpy(host, xp, hbsize);
+ goto cnameloop;
+ }
+ }
+
+ /*
+ ** Build the search list.
+ ** If there is at least one dot in name, start with a null
+ ** domain to search the unmodified name first.
+ ** If name does not end with a dot and search up local domain
+ ** tree desired, append each local domain component to the
+ ** search list; if name contains no dots and default domain
+ ** name is desired, append default domain name to search list;
+ ** else if name ends in a dot, remove that dot.
+ */
+
+ dp = searchlist;
+ if (n > 0)
+ *dp++ = "";
+ if (n >= 0 && *--cp != '.' && bitset(RES_DNSRCH, _res.options))
+ {
+ /* make sure there are less than MAXDNSRCH domains */
+ for (domain = RES_DNSRCH_VARIABLE, ret = 0;
+ *domain != NULL && ret < MAXDNSRCH;
+ ret++)
+ *dp++ = *domain++;
+ }
+ else if (n == 0 && bitset(RES_DEFNAMES, _res.options))
+ {
+ *dp++ = _res.defdname;
+ }
+ else if (*cp == '.')
+ {
+ *cp = '\0';
+ }
+ *dp = NULL;
+
+ /*
+ ** Now loop through the search list, appending each domain in turn
+ ** name and searching for a match.
+ */
+
+ mxmatch = NULL;
+ qtype = SM_T_INITIAL;
+
+ for (dp = searchlist; *dp != NULL; )
+ {
+ if (qtype == SM_T_INITIAL)
+ gotmx = false;
+ if (tTd(8, 5))
+ sm_dprintf("dns_getcanonname: trying %s.%s (%s)\n",
+ host, *dp,
+# if NETINET6
+ qtype == T_AAAA ? "AAAA" :
+# endif /* NETINET6 */
+ qtype == T_A ? "A" :
+ qtype == T_MX ? "MX" :
+ "???");
+ errno = 0;
+ ret = res_querydomain(host, *dp, C_IN, qtype,
+ answer.qb2, sizeof(answer.qb2));
+ if (ret <= 0)
+ {
+ int save_errno = errno;
+
+ if (tTd(8, 7))
+ sm_dprintf("\tNO: errno=%d, h_errno=%d\n",
+ save_errno, h_errno);
+
+ if (save_errno == ECONNREFUSED || h_errno == TRY_AGAIN)
+ {
+ /*
+ ** the name server seems to be down or broken.
+ */
+
+ SM_SET_H_ERRNO(TRY_AGAIN);
+# if _FFR_DONT_STOP_LOOKING
+ if (**dp == '\0')
+ {
+ if (*statp == EX_OK)
+ *statp = EX_TEMPFAIL;
+ goto nexttype;
+ }
+# endif /* _FFR_DONT_STOP_LOOKING */
+ *statp = EX_TEMPFAIL;
+
+ if (WorkAroundBrokenAAAA)
+ {
+ /*
+ ** Only return if not TRY_AGAIN as an
+ ** attempt with a different qtype may
+ ** succeed (res_querydomain() calls
+ ** res_query() calls res_send() which
+ ** sets errno to ETIMEDOUT if the
+ ** nameservers could be contacted but
+ ** didn't give an answer).
+ */
+
+ if (save_errno != ETIMEDOUT)
+ return false;
+ }
+ else
+ return false;
+ }
+
+# if _FFR_DONT_STOP_LOOKING
+nexttype:
+# endif /* _FFR_DONT_STOP_LOOKING */
+ if (h_errno != HOST_NOT_FOUND)
+ {
+ /* might have another type of interest */
+# if NETINET6
+ if (qtype == T_AAAA)
+ {
+ qtype = T_A;
+ continue;
+ }
+ else
+# endif /* NETINET6 */
+ if (qtype == T_A && !gotmx &&
+ (trymx || **dp == '\0'))
+ {
+ qtype = T_MX;
+ continue;
+ }
+ }
+
+ /* definite no -- try the next domain */
+ dp++;
+ qtype = SM_T_INITIAL;
+ continue;
+ }
+ else if (tTd(8, 7))
+ sm_dprintf("\tYES\n");
+
+ /* avoid problems after truncation in tcp packets */
+ if (ret > sizeof(answer))
+ ret = sizeof(answer);
+ if (ret < 0)
+ {
+ *statp = EX_SOFTWARE;
+ return false;
+ }
+
+ /*
+ ** Appear to have a match. Confirm it by searching for A or
+ ** CNAME records. If we don't have a local domain
+ ** wild card MX record, we will accept MX as well.
+ */
+
+ hp = (HEADER *) &answer;
+ ap = (unsigned char *) &answer + HFIXEDSZ;
+ eom = (unsigned char *) &answer + ret;
+
+ /* skip question part of response -- we know what we asked */
+ for (qdcount = ntohs((unsigned short) hp->qdcount);
+ qdcount--;
+ ap += ret + QFIXEDSZ)
+ {
+ if ((ret = dn_skipname(ap, eom)) < 0)
+ {
+ if (tTd(8, 20))
+ sm_dprintf("qdcount failure (%d)\n",
+ ntohs((unsigned short) hp->qdcount));
+ *statp = EX_SOFTWARE;
+ return false; /* ???XXX??? */
+ }
+ }
+
+ amatch = false;
+ for (ancount = ntohs((unsigned short) hp->ancount);
+ --ancount >= 0 && ap < eom;
+ ap += n)
+ {
+ n = dn_expand((unsigned char *) &answer, eom, ap,
+ (RES_UNC_T) nbuf, sizeof nbuf);
+ if (n < 0)
+ break;
+ ap += n;
+ GETSHORT(type, ap);
+ ap += INT16SZ; /* skip over class */
+ GETLONG(ttl, ap);
+ GETSHORT(n, ap); /* rdlength */
+ switch (type)
+ {
+ case T_MX:
+ gotmx = true;
+ if (**dp != '\0' && HasWildcardMX)
+ {
+ /*
+ ** If we are using MX matches and have
+ ** not yet gotten one, save this one
+ ** but keep searching for an A or
+ ** CNAME match.
+ */
+
+ if (trymx && mxmatch == NULL)
+ mxmatch = *dp;
+ continue;
+ }
+
+ /*
+ ** If we did not append a domain name, this
+ ** must have been a canonical name to start
+ ** with. Even if we did append a domain name,
+ ** in the absence of a wildcard MX this must
+ ** still be a real MX match.
+ ** Such MX matches are as good as an A match,
+ ** fall through.
+ */
+ /* FALLTHROUGH */
+
+# if NETINET6
+ case T_AAAA:
+ /* Flag that a good match was found */
+ amatch = true;
+
+ /* continue in case a CNAME also exists */
+ continue;
+# endif /* NETINET6 */
+
+ case T_A:
+ /* Flag that a good match was found */
+ amatch = true;
+
+ /* continue in case a CNAME also exists */
+ continue;
+
+ case T_CNAME:
+ if (DontExpandCnames)
+ {
+ /* got CNAME -- guaranteed canonical */
+ amatch = true;
+ break;
+ }
+
+ if (loopcnt++ > MAXCNAMEDEPTH)
+ {
+ /*XXX should notify postmaster XXX*/
+ message("DNS failure: CNAME loop for %s",
+ host);
+ if (CurEnv->e_message == NULL)
+ {
+ char ebuf[MAXLINE];
+
+ (void) sm_snprintf(ebuf,
+ sizeof ebuf,
+ "Deferred: DNS failure: CNAME loop for %.100s",
+ host);
+ CurEnv->e_message =
+ sm_rpool_strdup_x(
+ CurEnv->e_rpool, ebuf);
+ }
+ SM_SET_H_ERRNO(NO_RECOVERY);
+ *statp = EX_CONFIG;
+ return false;
+ }
+
+ /* value points at name */
+ if ((ret = dn_expand((unsigned char *)&answer,
+ eom, ap, (RES_UNC_T) nbuf,
+ sizeof(nbuf))) < 0)
+ break;
+ (void) sm_strlcpy(host, nbuf, hbsize);
+
+ /*
+ ** RFC 1034 section 3.6 specifies that CNAME
+ ** should point at the canonical name -- but
+ ** urges software to try again anyway.
+ */
+
+ goto cnameloop;
+
+ default:
+ /* not a record of interest */
+ continue;
+ }
+ }
+
+ if (amatch)
+ {
+ /*
+ ** Got a good match -- either an A, CNAME, or an
+ ** exact MX record. Save it and get out of here.
+ */
+
+ mxmatch = *dp;
+ break;
+ }
+
+ /*
+ ** Nothing definitive yet.
+ ** If this was a T_A query and we haven't yet found a MX
+ ** match, try T_MX if allowed to do so.
+ ** Otherwise, try the next domain.
+ */
+
+# if NETINET6
+ if (qtype == T_AAAA)
+ qtype = T_A;
+ else
+# endif /* NETINET6 */
+ if (qtype == T_A && !gotmx && (trymx || **dp == '\0'))
+ qtype = T_MX;
+ else
+ {
+ qtype = SM_T_INITIAL;
+ dp++;
+ }
+ }
+
+ /* if nothing was found, we are done */
+ if (mxmatch == NULL)
+ {
+ if (*statp == EX_OK)
+ *statp = EX_NOHOST;
+ return false;
+ }
+
+ /*
+ ** Create canonical name and return.
+ ** If saved domain name is null, name was already canonical.
+ ** Otherwise append the saved domain name.
+ */
+
+ (void) sm_snprintf(nbuf, sizeof nbuf, "%.*s%s%.*s", MAXDNAME, host,
+ *mxmatch == '\0' ? "" : ".",
+ MAXDNAME, mxmatch);
+ (void) sm_strlcpy(host, nbuf, hbsize);
+ if (tTd(8, 5))
+ sm_dprintf("dns_getcanonname: %s\n", host);
+ *statp = EX_OK;
+
+ /* return only one TTL entry, that should be sufficient */
+ if (ttl > 0 && pttl != NULL)
+ *pttl = ttl;
+ return true;
+}
+
+static char *
+gethostalias(host)
+ char *host;
+{
+ char *fname;
+ SM_FILE_T *fp;
+ register char *p = NULL;
+ long sff = SFF_REGONLY;
+ char buf[MAXLINE];
+ static char hbuf[MAXDNAME];
+
+ if (ResNoAliases)
+ return NULL;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ fname = getenv("HOSTALIASES");
+ if (fname == NULL ||
+ (fp = safefopen(fname, O_RDONLY, 0, sff)) == NULL)
+ return NULL;
+ while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ for (p = buf; p != '\0' && !(isascii(*p) && isspace(*p)); p++)
+ continue;
+ if (*p == 0)
+ {
+ /* syntax error */
+ continue;
+ }
+ *p++ = '\0';
+ if (sm_strcasecmp(buf, host) == 0)
+ break;
+ }
+
+ if (sm_io_eof(fp))
+ {
+ /* no match */
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ return NULL;
+ }
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+
+ /* got a match; extract the equivalent name */
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ host = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ *p = '\0';
+ (void) sm_strlcpy(hbuf, host, sizeof hbuf);
+ return hbuf;
+}
+#endif /* NAMED_BIND */
diff --git a/contrib/sendmail/src/envelope.c b/contrib/sendmail/src/envelope.c
new file mode 100644
index 0000000..27ad7cb
--- /dev/null
+++ b/contrib/sendmail/src/envelope.c
@@ -0,0 +1,1229 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: envelope.c,v 8.282 2002/05/10 15:41:11 ca Exp $")
+
+/*
+** NEWENVELOPE -- fill in a new envelope
+**
+** Supports inheritance.
+**
+** Parameters:
+** e -- the new envelope to fill in.
+** parent -- the envelope to be the parent of e.
+** rpool -- either NULL, or a pointer to a resource pool
+** from which envelope memory is allocated, and
+** to which envelope resources are attached.
+**
+** Returns:
+** e.
+**
+** Side Effects:
+** none.
+*/
+
+ENVELOPE *
+newenvelope(e, parent, rpool)
+ register ENVELOPE *e;
+ register ENVELOPE *parent;
+ SM_RPOOL_T *rpool;
+{
+ /*
+ ** This code used to read:
+ ** if (e == parent && e->e_parent != NULL)
+ ** parent = e->e_parent;
+ ** So if e == parent && e->e_parent == NULL then we would
+ ** set e->e_parent = e, which creates a loop in the e_parent chain.
+ ** This meant macvalue() could go into an infinite loop.
+ */
+
+ if (e == parent)
+ parent = e->e_parent;
+ clearenvelope(e, true, rpool);
+ if (e == CurEnv)
+ memmove((char *) &e->e_from,
+ (char *) &NullAddress,
+ sizeof e->e_from);
+ else
+ memmove((char *) &e->e_from,
+ (char *) &CurEnv->e_from,
+ sizeof e->e_from);
+ e->e_parent = parent;
+ assign_queueid(e);
+ e->e_ctime = curtime();
+ if (parent != NULL)
+ {
+ e->e_msgpriority = parent->e_msgsize;
+#if _FFR_QUARANTINE
+ if (parent->e_quarmsg == NULL)
+ {
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), "");
+ }
+ else
+ {
+ e->e_quarmsg = sm_rpool_strdup_x(rpool,
+ parent->e_quarmsg);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ }
+#endif /* _FFR_QUARANTINE */
+ }
+ e->e_puthdr = putheader;
+ e->e_putbody = putbody;
+ if (CurEnv->e_xfp != NULL)
+ (void) sm_io_flush(CurEnv->e_xfp, SM_TIME_DEFAULT);
+
+ return e;
+}
+
+/* values for msg_timeout, see also IS_* below for usage (bit layout) */
+#define MSG_T_O 0x01 /* normal timeout */
+#define MSG_T_O_NOW 0x02 /* NOW timeout */
+#define MSG_NOT_BY 0x04 /* Deliver-By time exceeded, mode R */
+#define MSG_WARN 0x10 /* normal queue warning */
+#define MSG_WARN_BY 0x20 /* Deliver-By time exceeded, mode N */
+
+#define IS_MSG_ERR(x) (((x) & 0x0f) != 0) /* return an error */
+
+/* immediate return */
+#define IS_IMM_RET(x) (((x) & (MSG_T_O_NOW|MSG_NOT_BY)) != 0)
+#define IS_MSG_WARN(x) (((x) & 0xf0) != 0) /* return a warning */
+
+/*
+** DROPENVELOPE -- deallocate an envelope.
+**
+** Parameters:
+** e -- the envelope to deallocate.
+** fulldrop -- if set, do return receipts.
+** split -- if true, split by recipient if message is queued up
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** housekeeping necessary to dispose of an envelope.
+** Unlocks this queue file.
+*/
+
+void
+dropenvelope(e, fulldrop, split)
+ register ENVELOPE *e;
+ bool fulldrop;
+ bool split;
+{
+ bool panic = false;
+ bool queueit = false;
+ int msg_timeout = 0;
+ bool failure_return = false;
+ bool delay_return = false;
+ bool success_return = false;
+ bool pmnotify = bitset(EF_PM_NOTIFY, e->e_flags);
+ bool done = false;
+ register ADDRESS *q;
+ char *id = e->e_id;
+ time_t now;
+ char buf[MAXLINE];
+
+ if (tTd(50, 1))
+ {
+ sm_dprintf("dropenvelope %p: id=", e);
+ xputs(e->e_id);
+ sm_dprintf(", flags=");
+ printenvflags(e);
+ if (tTd(50, 10))
+ {
+ sm_dprintf("sendq=");
+ printaddr(e->e_sendqueue, true);
+ }
+ }
+
+ if (LogLevel > 84)
+ sm_syslog(LOG_DEBUG, id,
+ "dropenvelope, e_flags=0x%lx, OpMode=%c, pid=%d",
+ e->e_flags, OpMode, (int) CurrentPid);
+
+ /* we must have an id to remove disk files */
+ if (id == NULL)
+ return;
+
+ /* if verify-only mode, we can skip most of this */
+ if (OpMode == MD_VERIFY)
+ goto simpledrop;
+
+ if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags))
+ logsender(e, NULL);
+ e->e_flags &= ~EF_LOGSENDER;
+
+ /* post statistics */
+ poststats(StatFile);
+
+ /*
+ ** Extract state information from dregs of send list.
+ */
+
+ now = curtime();
+ if (now >= e->e_ctime + TimeOuts.to_q_return[e->e_timeoutclass])
+ msg_timeout = MSG_T_O;
+ if (IS_DLVR_RETURN(e) && e->e_deliver_by > 0 &&
+ now >= e->e_ctime + e->e_deliver_by &&
+ !bitset(EF_RESPONSE, e->e_flags))
+ {
+ msg_timeout = MSG_NOT_BY;
+ e->e_flags |= EF_FATALERRS|EF_CLRQUEUE;
+ }
+ else if (TimeOuts.to_q_return[e->e_timeoutclass] == NOW &&
+ !bitset(EF_RESPONSE, e->e_flags))
+ {
+ msg_timeout = MSG_T_O_NOW;
+ e->e_flags |= EF_FATALERRS|EF_CLRQUEUE;
+ }
+
+ e->e_flags &= ~EF_QUEUERUN;
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_UNDELIVERED(q->q_state))
+ queueit = true;
+
+ /* see if a notification is needed */
+ if (bitset(QPINGONFAILURE, q->q_flags) &&
+ ((IS_MSG_ERR(msg_timeout) &&
+ QS_IS_UNDELIVERED(q->q_state)) ||
+ QS_IS_BADADDR(q->q_state) ||
+ IS_IMM_RET(msg_timeout)))
+ {
+ failure_return = true;
+ if (!done && q->q_owner == NULL &&
+ !emptyaddr(&e->e_from))
+ {
+ (void) sendtolist(e->e_from.q_paddr, NULLADDR,
+ &e->e_errorqueue, 0, e);
+ done = true;
+ }
+ }
+ else if ((bitset(QPINGONSUCCESS, q->q_flags) &&
+ ((QS_IS_SENT(q->q_state) &&
+ bitnset(M_LOCALMAILER, q->q_mailer->m_flags)) ||
+ bitset(QRELAYED|QEXPANDED|QDELIVERED, q->q_flags))) ||
+ bitset(QBYTRACE, q->q_flags) ||
+ bitset(QBYNRELAY, q->q_flags))
+ {
+ success_return = true;
+ }
+ }
+
+ if (e->e_class < 0)
+ e->e_flags |= EF_NO_BODY_RETN;
+
+ /*
+ ** See if the message timed out.
+ */
+
+ if (!queueit)
+ /* EMPTY */
+ /* nothing to do */ ;
+ else if (IS_MSG_ERR(msg_timeout))
+ {
+ if (failure_return)
+ {
+ if (msg_timeout == MSG_NOT_BY)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "delivery time expired %lds",
+ e->e_deliver_by);
+ }
+ else
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Cannot send message for %s",
+ pintvl(TimeOuts.to_q_return[e->e_timeoutclass],
+ false));
+ }
+
+ /* don't free, allocated from e_rpool */
+ e->e_message = sm_rpool_strdup_x(e->e_rpool, buf);
+ message(buf);
+ e->e_flags |= EF_CLRQUEUE;
+ }
+ if (msg_timeout == MSG_NOT_BY)
+ {
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Delivery time (%lds) expired\n",
+ e->e_deliver_by);
+ }
+ else
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Message could not be delivered for %s\n",
+ pintvl(TimeOuts.to_q_return[e->e_timeoutclass],
+ false));
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Message will be deleted from queue\n");
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_UNDELIVERED(q->q_state))
+ {
+ q->q_state = QS_BADADDR;
+ if (msg_timeout == MSG_NOT_BY)
+ q->q_status = "5.4.7";
+ else
+ q->q_status = "4.4.7";
+ }
+ }
+ }
+ else
+ {
+ if (TimeOuts.to_q_warning[e->e_timeoutclass] > 0 &&
+ now >= e->e_ctime +
+ TimeOuts.to_q_warning[e->e_timeoutclass])
+ msg_timeout = MSG_WARN;
+ else if (IS_DLVR_NOTIFY(e) &&
+ e->e_deliver_by > 0 &&
+ now >= e->e_ctime + e->e_deliver_by)
+ msg_timeout = MSG_WARN_BY;
+
+ if (IS_MSG_WARN(msg_timeout))
+ {
+ if (!bitset(EF_WARNING|EF_RESPONSE, e->e_flags) &&
+ e->e_class >= 0 &&
+ e->e_from.q_paddr != NULL &&
+ strcmp(e->e_from.q_paddr, "<>") != 0 &&
+ sm_strncasecmp(e->e_from.q_paddr, "owner-", 6) != 0 &&
+ (strlen(e->e_from.q_paddr) <= 8 ||
+ sm_strcasecmp(&e->e_from.q_paddr[strlen(e->e_from.q_paddr) - 8],
+ "-request") != 0))
+ {
+ for (q = e->e_sendqueue; q != NULL;
+ q = q->q_next)
+ {
+ if (QS_IS_UNDELIVERED(q->q_state)
+#if _FFR_NODELAYDSN_ON_HOLD
+ && !bitnset(M_HOLD,
+ q->q_mailer->m_flags)
+#endif /* _FFR_NODELAYDSN_ON_HOLD */
+ )
+ {
+ if (msg_timeout ==
+ MSG_WARN_BY &&
+ (bitset(QPINGONDELAY,
+ q->q_flags) ||
+ !bitset(QHASNOTIFY,
+ q->q_flags))
+ )
+ {
+ q->q_flags |= QBYNDELAY;
+ delay_return = true;
+ }
+ if (bitset(QPINGONDELAY,
+ q->q_flags))
+ {
+ q->q_flags |= QDELAYED;
+ delay_return = true;
+ }
+ }
+ }
+ }
+ if (delay_return)
+ {
+ if (msg_timeout == MSG_WARN_BY)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Warning: Delivery time (%lds) exceeded",
+ e->e_deliver_by);
+ }
+ else
+ (void) sm_snprintf(buf, sizeof buf,
+ "Warning: could not send message for past %s",
+ pintvl(TimeOuts.to_q_warning[e->e_timeoutclass],
+ false));
+
+ /* don't free, allocated from e_rpool */
+ e->e_message = sm_rpool_strdup_x(e->e_rpool,
+ buf);
+ message(buf);
+ e->e_flags |= EF_WARNING;
+ }
+ if (msg_timeout == MSG_WARN_BY)
+ {
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Warning: Delivery time (%lds) exceeded\n",
+ e->e_deliver_by);
+ }
+ else
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Warning: message still undelivered after %s\n",
+ pintvl(TimeOuts.to_q_warning[e->e_timeoutclass],
+ false));
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Will keep trying until message is %s old\n",
+ pintvl(TimeOuts.to_q_return[e->e_timeoutclass],
+ false));
+ }
+ }
+
+ if (tTd(50, 2))
+ sm_dprintf("failure_return=%d delay_return=%d success_return=%d queueit=%d\n",
+ failure_return, delay_return, success_return, queueit);
+
+ /*
+ ** If we had some fatal error, but no addresses are marked as
+ ** bad, mark them _all_ as bad.
+ */
+
+ if (bitset(EF_FATALERRS, e->e_flags) && !failure_return)
+ {
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if ((QS_IS_OK(q->q_state) ||
+ QS_IS_VERIFIED(q->q_state)) &&
+ bitset(QPINGONFAILURE, q->q_flags))
+ {
+ failure_return = true;
+ q->q_state = QS_BADADDR;
+ }
+ }
+ }
+
+ /*
+ ** Send back return receipts as requested.
+ */
+
+ if (success_return && !failure_return && !delay_return && fulldrop &&
+ !bitset(PRIV_NORECEIPTS, PrivacyFlags) &&
+ strcmp(e->e_from.q_paddr, "<>") != 0)
+ {
+ auto ADDRESS *rlist = NULL;
+
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): sending return receipt\n",
+ id);
+ e->e_flags |= EF_SENDRECEIPT;
+ (void) sendtolist(e->e_from.q_paddr, NULLADDR, &rlist, 0, e);
+ (void) returntosender("Return receipt", rlist, RTSF_NO_BODY, e);
+ }
+ e->e_flags &= ~EF_SENDRECEIPT;
+
+ /*
+ ** Arrange to send error messages if there are fatal errors.
+ */
+
+ if ((failure_return || delay_return) && e->e_errormode != EM_QUIET)
+ {
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): saving mail\n", id);
+ panic = savemail(e, !bitset(EF_NO_BODY_RETN, e->e_flags));
+ }
+
+ /*
+ ** Arrange to send warning messages to postmaster as requested.
+ */
+
+ if ((failure_return || pmnotify) &&
+ PostMasterCopy != NULL &&
+ !bitset(EF_RESPONSE, e->e_flags) &&
+ e->e_class >= 0)
+ {
+ auto ADDRESS *rlist = NULL;
+ char pcopy[MAXNAME];
+
+ if (failure_return)
+ {
+ expand(PostMasterCopy, pcopy, sizeof pcopy, e);
+
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): sending postmaster copy to %s\n",
+ id, pcopy);
+ (void) sendtolist(pcopy, NULLADDR, &rlist, 0, e);
+ }
+ if (pmnotify)
+ (void) sendtolist("postmaster", NULLADDR,
+ &rlist, 0, e);
+ (void) returntosender(e->e_message, rlist,
+ RTSF_PM_BOUNCE|RTSF_NO_BODY, e);
+ }
+
+ /*
+ ** Instantiate or deinstantiate the queue.
+ */
+
+simpledrop:
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): at simpledrop, queueit=%d\n",
+ id, queueit);
+ if (!queueit || bitset(EF_CLRQUEUE, e->e_flags))
+ {
+ if (tTd(50, 1))
+ {
+ sm_dprintf("\n===== Dropping queue files for %s... queueit=%d, e_flags=",
+ e->e_id, queueit);
+ printenvflags(e);
+ }
+ if (!panic)
+ (void) xunlink(queuename(e, DATAFL_LETTER));
+#if _FFR_QUARANTINE
+ if (panic && QueueMode == QM_LOST)
+ {
+ /*
+ ** leave the Qf file behind as
+ ** the delivery attempt failed.
+ */
+
+ /* EMPTY */
+ }
+ else
+#endif /* _FFR_QUARANTINE */
+ if (xunlink(queuename(e, ANYQFL_LETTER)) == 0)
+ {
+ /* add to available space in filesystem */
+ updfs(e, true, !panic);
+ }
+
+ if (e->e_ntries > 0 && LogLevel > 9)
+ sm_syslog(LOG_INFO, id, "done; delay=%s, ntries=%d",
+ pintvl(curtime() - e->e_ctime, true),
+ e->e_ntries);
+ }
+ else if (queueit || !bitset(EF_INQUEUE, e->e_flags))
+ {
+ if (!split)
+ queueup(e, false, true);
+ else
+ {
+ ENVELOPE *oldsib;
+ ENVELOPE *ee;
+
+ /*
+ ** Save old sibling and set it to NULL to avoid
+ ** queueing up the same envelopes again.
+ ** This requires that envelopes in that list have
+ ** been take care of before (or at some other place).
+ */
+
+ oldsib = e->e_sibling;
+ e->e_sibling = NULL;
+ if (!split_by_recipient(e) &&
+ bitset(EF_FATALERRS, e->e_flags))
+ {
+ syserr("!dropenvelope(%s): cannot commit data file %s, uid=%d",
+ e->e_id, queuename(e, DATAFL_LETTER),
+ (int) geteuid());
+ }
+ for (ee = e->e_sibling; ee != NULL; ee = ee->e_sibling)
+ queueup(ee, false, true);
+ queueup(e, false, true);
+
+ /* clean up */
+ for (ee = e->e_sibling; ee != NULL; ee = ee->e_sibling)
+ {
+ /* now unlock the job */
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): unlocking job\n",
+ ee->e_id);
+ closexscript(ee);
+ unlockqueue(ee);
+
+ /* this envelope is marked unused */
+ if (ee->e_dfp != NULL)
+ {
+ (void) sm_io_close(ee->e_dfp,
+ SM_TIME_DEFAULT);
+ ee->e_dfp = NULL;
+ }
+ ee->e_id = NULL;
+ ee->e_flags &= ~EF_HAS_DF;
+ }
+ e->e_sibling = oldsib;
+ }
+ }
+
+ /* now unlock the job */
+ if (tTd(50, 8))
+ sm_dprintf("dropenvelope(%s): unlocking job\n", id);
+ closexscript(e);
+ unlockqueue(e);
+
+ /* make sure that this envelope is marked unused */
+ if (e->e_dfp != NULL)
+ {
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_dfp = NULL;
+ }
+ e->e_id = NULL;
+ e->e_flags &= ~EF_HAS_DF;
+}
+/*
+** CLEARENVELOPE -- clear an envelope without unlocking
+**
+** This is normally used by a child process to get a clean
+** envelope without disturbing the parent.
+**
+** Parameters:
+** e -- the envelope to clear.
+** fullclear - if set, the current envelope is total
+** garbage and should be ignored; otherwise,
+** release any resources it may indicate.
+** rpool -- either NULL, or a pointer to a resource pool
+** from which envelope memory is allocated, and
+** to which envelope resources are attached.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Closes files associated with the envelope.
+** Marks the envelope as unallocated.
+*/
+
+void
+clearenvelope(e, fullclear, rpool)
+ register ENVELOPE *e;
+ bool fullclear;
+ SM_RPOOL_T *rpool;
+{
+ register HDR *bh;
+ register HDR **nhp;
+ extern ENVELOPE BlankEnvelope;
+ char **p;
+
+ if (!fullclear)
+ {
+ /* clear out any file information */
+ if (e->e_xfp != NULL)
+ (void) sm_io_close(e->e_xfp, SM_TIME_DEFAULT);
+ if (e->e_dfp != NULL)
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_xfp = e->e_dfp = NULL;
+ }
+
+ /*
+ ** Copy BlankEnvelope into *e.
+ ** It is not safe to simply copy pointers to strings;
+ ** the strings themselves must be copied (or set to NULL).
+ ** The problem is that when we assign a new string value to
+ ** a member of BlankEnvelope, we free the old string.
+ ** We did not need to do this copying in sendmail 8.11 :-(
+ ** and it is a potential performance hit. Reference counted
+ ** strings are one way out.
+ */
+
+ *e = BlankEnvelope;
+ e->e_message = NULL;
+#if _FFR_QUARANTINE
+ e->e_qfletter = '\0';
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), "");
+#endif /* _FFR_QUARANTINE */
+
+ /*
+ ** Copy the macro table.
+ ** We might be able to avoid this by zeroing the macro table
+ ** and always searching BlankEnvelope.e_macro after e->e_macro
+ ** in macvalue().
+ */
+
+ for (p = &e->e_macro.mac_table[0];
+ p <= &e->e_macro.mac_table[MAXMACROID];
+ ++p)
+ {
+ if (*p != NULL)
+ *p = sm_rpool_strdup_x(rpool, *p);
+ }
+
+ /*
+ ** XXX There are many strings in the envelope structure
+ ** XXX that we are not attempting to copy here.
+ ** XXX Investigate this further.
+ */
+
+ e->e_rpool = rpool;
+ e->e_macro.mac_rpool = rpool;
+ if (Verbose)
+ set_delivery_mode(SM_DELIVER, e);
+ bh = BlankEnvelope.e_header;
+ nhp = &e->e_header;
+ while (bh != NULL)
+ {
+ *nhp = (HDR *) sm_rpool_malloc_x(rpool, sizeof *bh);
+ memmove((char *) *nhp, (char *) bh, sizeof *bh);
+ bh = bh->h_link;
+ nhp = &(*nhp)->h_link;
+ }
+}
+/*
+** INITSYS -- initialize instantiation of system
+**
+** In Daemon mode, this is done in the child.
+**
+** Parameters:
+** e -- the envelope to use.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Initializes the system macros, some global variables,
+** etc. In particular, the current time in various
+** forms is set.
+*/
+
+void
+initsys(e)
+ register ENVELOPE *e;
+{
+ char buf[10];
+#ifdef TTYNAME
+ static char ybuf[60]; /* holds tty id */
+ register char *p;
+ extern char *ttyname();
+#endif /* TTYNAME */
+
+ /*
+ ** Give this envelope a reality.
+ ** I.e., an id, a transcript, and a creation time.
+ ** We don't select the queue until all of the recipients are known.
+ */
+
+ openxscript(e);
+ e->e_ctime = curtime();
+#if _FFR_QUARANTINE
+ e->e_qfletter = '\0';
+#endif /* _FFR_QUARANTINE */
+#if _FFR_QUEUEDELAY
+ e->e_queuealg = QueueAlg;
+ e->e_queuedelay = QueueInitDelay;
+#endif /* _FFR_QUEUEDELAY */
+
+ /*
+ ** Set OutChannel to something useful if stdout isn't it.
+ ** This arranges that any extra stuff the mailer produces
+ ** gets sent back to the user on error (because it is
+ ** tucked away in the transcript).
+ */
+
+ if (OpMode == MD_DAEMON && bitset(EF_QUEUERUN, e->e_flags) &&
+ e->e_xfp != NULL)
+ OutChannel = e->e_xfp;
+
+ /*
+ ** Set up some basic system macros.
+ */
+
+ /* process id */
+ (void) sm_snprintf(buf, sizeof buf, "%d", (int) CurrentPid);
+ macdefine(&e->e_macro, A_TEMP, 'p', buf);
+
+ /* hop count */
+ (void) sm_snprintf(buf, sizeof buf, "%d", e->e_hopcount);
+ macdefine(&e->e_macro, A_TEMP, 'c', buf);
+
+ /* time as integer, unix time, arpa time */
+ settime(e);
+
+ /* Load average */
+ sm_getla();
+
+#ifdef TTYNAME
+ /* tty name */
+ if (macvalue('y', e) == NULL)
+ {
+ p = ttyname(2);
+ if (p != NULL)
+ {
+ if (strrchr(p, '/') != NULL)
+ p = strrchr(p, '/') + 1;
+ (void) sm_strlcpy(ybuf, sizeof ybuf, p);
+ macdefine(&e->e_macro, A_PERM, 'y', ybuf);
+ }
+ }
+#endif /* TTYNAME */
+}
+/*
+** SETTIME -- set the current time.
+**
+** Parameters:
+** e -- the envelope in which the macros should be set.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets the various time macros -- $a, $b, $d, $t.
+*/
+
+void
+settime(e)
+ register ENVELOPE *e;
+{
+ register char *p;
+ auto time_t now;
+ char buf[30];
+ register struct tm *tm;
+
+ now = curtime();
+ tm = gmtime(&now);
+ (void) sm_snprintf(buf, sizeof buf, "%04d%02d%02d%02d%02d",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min);
+ macdefine(&e->e_macro, A_TEMP, 't', buf);
+ (void) sm_strlcpy(buf, ctime(&now), sizeof buf);
+ p = strchr(buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ macdefine(&e->e_macro, A_TEMP, 'd', buf);
+ macdefine(&e->e_macro, A_TEMP, 'b', arpadate(buf));
+ if (macvalue('a', e) == NULL)
+ macdefine(&e->e_macro, A_PERM, 'a', macvalue('b', e));
+}
+/*
+** OPENXSCRIPT -- Open transcript file
+**
+** Creates a transcript file for possible eventual mailing or
+** sending back.
+**
+** Parameters:
+** e -- the envelope to create the transcript in/for.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Creates the transcript file.
+*/
+
+#ifndef O_APPEND
+# define O_APPEND 0
+#endif /* ! O_APPEND */
+
+void
+openxscript(e)
+ register ENVELOPE *e;
+{
+ register char *p;
+
+ if (e->e_xfp != NULL)
+ return;
+
+#if 0
+ if (e->e_lockfp == NULL && bitset(EF_INQUEUE, e->e_flags))
+ syserr("openxscript: job not locked");
+#endif /* 0 */
+
+ p = queuename(e, XSCRPT_LETTER);
+ e->e_xfp = bfopen(p, FileMode, XscriptFileBufferSize,
+ SFF_NOTEXCL|SFF_OPENASROOT);
+
+ if (e->e_xfp == NULL)
+ {
+ syserr("Can't create transcript file %s", p);
+ e->e_xfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT,
+ SM_PATH_DEVNULL, SM_IO_RDWR, NULL);
+ if (e->e_xfp == NULL)
+ syserr("!Can't open %s", SM_PATH_DEVNULL);
+ }
+ (void) sm_io_setvbuf(e->e_xfp, SM_TIME_DEFAULT, NULL, SM_IO_LBF, 0);
+ if (tTd(46, 9))
+ {
+ sm_dprintf("openxscript(%s):\n ", p);
+ dumpfd(sm_io_getinfo(e->e_xfp, SM_IO_WHAT_FD, NULL), true,
+ false);
+ }
+}
+/*
+** CLOSEXSCRIPT -- close the transcript file.
+**
+** Parameters:
+** e -- the envelope containing the transcript to close.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+void
+closexscript(e)
+ register ENVELOPE *e;
+{
+ if (e->e_xfp == NULL)
+ return;
+#if 0
+ if (e->e_lockfp == NULL)
+ syserr("closexscript: job not locked");
+#endif /* 0 */
+ (void) sm_io_close(e->e_xfp, SM_TIME_DEFAULT);
+ e->e_xfp = NULL;
+}
+/*
+** SETSENDER -- set the person who this message is from
+**
+** Under certain circumstances allow the user to say who
+** s/he is (using -f or -r). These are:
+** 1. The user's uid is zero (root).
+** 2. The user's login name is in an approved list (typically
+** from a network server).
+** 3. The address the user is trying to claim has a
+** "!" character in it (since #2 doesn't do it for
+** us if we are dialing out for UUCP).
+** A better check to replace #3 would be if the
+** effective uid is "UUCP" -- this would require me
+** to rewrite getpwent to "grab" uucp as it went by,
+** make getname more nasty, do another passwd file
+** scan, or compile the UID of "UUCP" into the code,
+** all of which are reprehensible.
+**
+** Assuming all of these fail, we figure out something
+** ourselves.
+**
+** Parameters:
+** from -- the person we would like to believe this message
+** is from, as specified on the command line.
+** e -- the envelope in which we would like the sender set.
+** delimptr -- if non-NULL, set to the location of the
+** trailing delimiter.
+** delimchar -- the character that will delimit the sender
+** address.
+** internal -- set if this address is coming from an internal
+** source such as an owner alias.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets sendmail's notion of who the from person is.
+*/
+
+void
+setsender(from, e, delimptr, delimchar, internal)
+ char *from;
+ register ENVELOPE *e;
+ char **delimptr;
+ int delimchar;
+ bool internal;
+{
+ register char **pvp;
+ char *realname = NULL;
+ char *bp;
+ char buf[MAXNAME + 2];
+ char pvpbuf[PSBUFSIZE];
+ extern char *FullName;
+
+ if (tTd(45, 1))
+ sm_dprintf("setsender(%s)\n", from == NULL ? "" : from);
+
+ /*
+ ** Figure out the real user executing us.
+ ** Username can return errno != 0 on non-errors.
+ */
+
+ if (bitset(EF_QUEUERUN, e->e_flags) || OpMode == MD_SMTP ||
+ OpMode == MD_ARPAFTP || OpMode == MD_DAEMON)
+ realname = from;
+ if (realname == NULL || realname[0] == '\0')
+ realname = username();
+
+ if (ConfigLevel < 2)
+ SuprErrs = true;
+
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e s");
+
+ /* preset state for then clause in case from == NULL */
+ e->e_from.q_state = QS_BADADDR;
+ e->e_from.q_flags = 0;
+ if (from == NULL ||
+ parseaddr(from, &e->e_from, RF_COPYALL|RF_SENDERADDR,
+ delimchar, delimptr, e, false) == NULL ||
+ QS_IS_BADADDR(e->e_from.q_state) ||
+ e->e_from.q_mailer == ProgMailer ||
+ e->e_from.q_mailer == FileMailer ||
+ e->e_from.q_mailer == InclMailer)
+ {
+ /* log garbage addresses for traceback */
+ if (from != NULL && LogLevel > 2)
+ {
+ char *p;
+ char ebuf[MAXNAME * 2 + 2];
+
+ p = macvalue('_', e);
+ if (p == NULL)
+ {
+ char *host = RealHostName;
+
+ if (host == NULL)
+ host = MyHostName;
+ (void) sm_snprintf(ebuf, sizeof ebuf,
+ "%.*s@%.*s", MAXNAME,
+ realname, MAXNAME, host);
+ p = ebuf;
+ }
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "setsender: %s: invalid or unparsable, received from %s",
+ shortenstring(from, 83), p);
+ }
+ if (from != NULL)
+ {
+ if (!QS_IS_BADADDR(e->e_from.q_state))
+ {
+ /* it was a bogus mailer in the from addr */
+ e->e_status = "5.1.7";
+ usrerrenh(e->e_status,
+ "553 Invalid sender address");
+ }
+ SuprErrs = true;
+ }
+ if (from == realname ||
+ parseaddr(from = realname,
+ &e->e_from, RF_COPYALL|RF_SENDERADDR, ' ',
+ NULL, e, false) == NULL)
+ {
+ char nbuf[100];
+
+ SuprErrs = true;
+ expand("\201n", nbuf, sizeof nbuf, e);
+ from = sm_rpool_strdup_x(e->e_rpool, nbuf);
+ if (parseaddr(from, &e->e_from, RF_COPYALL, ' ',
+ NULL, e, false) == NULL &&
+ parseaddr(from = "postmaster", &e->e_from,
+ RF_COPYALL, ' ', NULL, e, false) == NULL)
+ syserr("553 5.3.0 setsender: can't even parse postmaster!");
+ }
+ }
+ else
+ FromFlag = true;
+ e->e_from.q_state = QS_SENDER;
+ if (tTd(45, 5))
+ {
+ sm_dprintf("setsender: QS_SENDER ");
+ printaddr(&e->e_from, false);
+ }
+ SuprErrs = false;
+
+#if USERDB
+ if (bitnset(M_CHECKUDB, e->e_from.q_mailer->m_flags))
+ {
+ register char *p;
+
+ p = udbsender(e->e_from.q_user, e->e_rpool);
+ if (p != NULL)
+ from = p;
+ }
+#endif /* USERDB */
+
+ if (bitnset(M_HASPWENT, e->e_from.q_mailer->m_flags))
+ {
+ SM_MBDB_T user;
+
+ if (!internal)
+ {
+ /* if the user already given fullname don't redefine */
+ if (FullName == NULL)
+ FullName = macvalue('x', e);
+ if (FullName != NULL)
+ {
+ if (FullName[0] == '\0')
+ FullName = NULL;
+ else
+ FullName = newstr(FullName);
+ }
+ }
+
+ if (e->e_from.q_user[0] != '\0' &&
+ sm_mbdb_lookup(e->e_from.q_user, &user) == EX_OK)
+ {
+ /*
+ ** Process passwd file entry.
+ */
+
+ /* extract home directory */
+ if (*user.mbdb_homedir == '\0')
+ e->e_from.q_home = NULL;
+ else if (strcmp(user.mbdb_homedir, "/") == 0)
+ e->e_from.q_home = "";
+ else
+ e->e_from.q_home = sm_rpool_strdup_x(e->e_rpool,
+ user.mbdb_homedir);
+ macdefine(&e->e_macro, A_PERM, 'z', e->e_from.q_home);
+
+ /* extract user and group id */
+ if (user.mbdb_uid != SM_NO_UID)
+ {
+ e->e_from.q_uid = user.mbdb_uid;
+ e->e_from.q_gid = user.mbdb_gid;
+ e->e_from.q_flags |= QGOODUID;
+ }
+
+ /* extract full name from passwd file */
+ if (FullName == NULL && !internal &&
+ user.mbdb_fullname[0] != '\0' &&
+ strcmp(user.mbdb_name, e->e_from.q_user) == 0)
+ {
+ FullName = newstr(user.mbdb_fullname);
+ }
+ }
+ else
+ {
+ e->e_from.q_home = NULL;
+ }
+ if (FullName != NULL && !internal)
+ macdefine(&e->e_macro, A_PERM, 'x', FullName);
+ }
+ else if (!internal && OpMode != MD_DAEMON && OpMode != MD_SMTP)
+ {
+ if (e->e_from.q_home == NULL)
+ {
+ e->e_from.q_home = getenv("HOME");
+ if (e->e_from.q_home != NULL)
+ {
+ if (*e->e_from.q_home == '\0')
+ e->e_from.q_home = NULL;
+ else if (strcmp(e->e_from.q_home, "/") == 0)
+ e->e_from.q_home++;
+ }
+ }
+ e->e_from.q_uid = RealUid;
+ e->e_from.q_gid = RealGid;
+ e->e_from.q_flags |= QGOODUID;
+ }
+
+ /*
+ ** Rewrite the from person to dispose of possible implicit
+ ** links in the net.
+ */
+
+ pvp = prescan(from, delimchar, pvpbuf, sizeof pvpbuf, NULL, NULL);
+ if (pvp == NULL)
+ {
+ /* don't need to give error -- prescan did that already */
+ if (LogLevel > 2)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "cannot prescan from (%s)",
+ shortenstring(from, MAXSHORTSTR));
+ finis(true, true, ExitStat);
+ }
+ (void) REWRITE(pvp, 3, e);
+ (void) REWRITE(pvp, 1, e);
+ (void) REWRITE(pvp, 4, e);
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), NULL);
+ bp = buf + 1;
+ cataddr(pvp, NULL, bp, sizeof buf - 2, '\0');
+ if (*bp == '@' && !bitnset(M_NOBRACKET, e->e_from.q_mailer->m_flags))
+ {
+ /* heuristic: route-addr: add angle brackets */
+ (void) sm_strlcat(bp, ">", sizeof buf - 1);
+ *--bp = '<';
+ }
+ e->e_sender = sm_rpool_strdup_x(e->e_rpool, bp);
+ macdefine(&e->e_macro, A_PERM, 'f', e->e_sender);
+
+ /* save the domain spec if this mailer wants it */
+ if (e->e_from.q_mailer != NULL &&
+ bitnset(M_CANONICAL, e->e_from.q_mailer->m_flags))
+ {
+ char **lastat;
+
+ /* get rid of any pesky angle brackets */
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e s");
+ (void) REWRITE(pvp, 3, e);
+ (void) REWRITE(pvp, 1, e);
+ (void) REWRITE(pvp, 4, e);
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), NULL);
+
+ /* strip off to the last "@" sign */
+ for (lastat = NULL; *pvp != NULL; pvp++)
+ if (strcmp(*pvp, "@") == 0)
+ lastat = pvp;
+ if (lastat != NULL)
+ {
+ e->e_fromdomain = copyplist(lastat, true, e->e_rpool);
+ if (tTd(45, 3))
+ {
+ sm_dprintf("Saving from domain: ");
+ printav(e->e_fromdomain);
+ }
+ }
+ }
+}
+/*
+** PRINTENVFLAGS -- print envelope flags for debugging
+**
+** Parameters:
+** e -- the envelope with the flags to be printed.
+**
+** Returns:
+** none.
+*/
+
+struct eflags
+{
+ char *ef_name;
+ unsigned long ef_bit;
+};
+
+static struct eflags EnvelopeFlags[] =
+{
+ { "OLDSTYLE", EF_OLDSTYLE },
+ { "INQUEUE", EF_INQUEUE },
+ { "NO_BODY_RETN", EF_NO_BODY_RETN },
+ { "CLRQUEUE", EF_CLRQUEUE },
+ { "SENDRECEIPT", EF_SENDRECEIPT },
+ { "FATALERRS", EF_FATALERRS },
+ { "DELETE_BCC", EF_DELETE_BCC },
+ { "RESPONSE", EF_RESPONSE },
+ { "RESENT", EF_RESENT },
+ { "VRFYONLY", EF_VRFYONLY },
+ { "WARNING", EF_WARNING },
+ { "QUEUERUN", EF_QUEUERUN },
+ { "GLOBALERRS", EF_GLOBALERRS },
+ { "PM_NOTIFY", EF_PM_NOTIFY },
+ { "METOO", EF_METOO },
+ { "LOGSENDER", EF_LOGSENDER },
+ { "NORECEIPT", EF_NORECEIPT },
+ { "HAS8BIT", EF_HAS8BIT },
+ { "NL_NOT_EOL", EF_NL_NOT_EOL },
+ { "CRLF_NOT_EOL", EF_CRLF_NOT_EOL },
+ { "RET_PARAM", EF_RET_PARAM },
+ { "HAS_DF", EF_HAS_DF },
+ { "IS_MIME", EF_IS_MIME },
+ { "DONT_MIME", EF_DONT_MIME },
+ { "DISCARD", EF_DISCARD },
+ { "TOOBIG", EF_TOOBIG },
+ { "SPLIT", EF_SPLIT },
+ { "UNSAFE", EF_UNSAFE },
+ { NULL, 0 }
+};
+
+void
+printenvflags(e)
+ register ENVELOPE *e;
+{
+ register struct eflags *ef;
+ bool first = true;
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%lx", e->e_flags);
+ for (ef = EnvelopeFlags; ef->ef_name != NULL; ef++)
+ {
+ if (!bitset(ef->ef_bit, e->e_flags))
+ continue;
+ if (first)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "<%s",
+ ef->ef_name);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, ",%s",
+ ef->ef_name);
+ first = false;
+ }
+ if (!first)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, ">\n");
+}
diff --git a/contrib/sendmail/src/err.c b/contrib/sendmail/src/err.c
new file mode 100644
index 0000000..f4ec686
--- /dev/null
+++ b/contrib/sendmail/src/err.c
@@ -0,0 +1,1163 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: err.c,v 8.189 2002/01/09 18:52:30 ca Exp $")
+
+#if LDAPMAP
+# include <lber.h>
+# include <ldap.h> /* for LDAP error codes */
+#endif /* LDAPMAP */
+
+static void putoutmsg __P((char *, bool, bool));
+static void puterrmsg __P((char *));
+static char *fmtmsg __P((char *, const char *, const char *, const char *,
+ int, const char *, va_list));
+
+/*
+** FATAL_ERROR -- handle a fatal exception
+**
+** This function is installed as the default exception handler
+** in the main sendmail process, and in all child processes
+** that we create. Its job is to handle exceptions that are not
+** handled at a lower level.
+**
+** The theory is that unhandled exceptions will be 'fatal' class
+** exceptions (with an "F:" prefix), such as the out-of-memory
+** exception "F:sm.heap". As such, they are handled by exiting
+** the process in exactly the same way that xalloc() in Sendmail 8.10
+** exits the process when it fails due to lack of memory:
+** we call syserr with a message beginning with "!".
+**
+** Parameters:
+** exc -- exception which is terminating this process
+**
+** Returns:
+** none
+*/
+
+void
+fatal_error(exc)
+ SM_EXC_T *exc;
+{
+ static char buf[256];
+ SM_FILE_T f;
+
+ /*
+ ** This function may be called when the heap is exhausted.
+ ** The following code writes the message for 'exc' into our
+ ** static buffer without allocating memory or raising exceptions.
+ */
+
+ sm_strio_init(&f, buf, sizeof(buf));
+ sm_exc_write(exc, &f);
+ (void) sm_io_flush(&f, SM_TIME_DEFAULT);
+
+ /*
+ ** Terminate the process after logging an error and cleaning up.
+ ** Problems:
+ ** - syserr decides what class of error this is by looking at errno.
+ ** That's no good; we should look at the exc structure.
+ ** - The cleanup code should be moved out of syserr
+ ** and into individual exception handlers
+ ** that are part of the module they clean up after.
+ */
+
+ errno = ENOMEM;
+ syserr("!%s", buf);
+}
+
+/*
+** SYSERR -- Print error message.
+**
+** Prints an error message via sm_io_printf to the diagnostic output.
+**
+** If the first character of the syserr message is `!' it will
+** log this as an ALERT message and exit immediately. This can
+** leave queue files in an indeterminate state, so it should not
+** be used lightly.
+**
+** If the first character of the syserr message is '!' or '@'
+** then syserr knows that the process is about to be terminated,
+** so the SMTP reply code defaults to 421. Otherwise, the
+** reply code defaults to 451 or 554, depending on errno.
+**
+** Parameters:
+** fmt -- the format string. An optional '!' or '@',
+** followed by an optional three-digit SMTP
+** reply code, followed by message text.
+** (others) -- parameters
+**
+** Returns:
+** none
+** Raises E:mta.quickabort if QuickAbort is set.
+**
+** Side Effects:
+** increments Errors.
+** sets ExitStat.
+*/
+
+char MsgBuf[BUFSIZ*2]; /* text of most recent message */
+static char HeldMessageBuf[sizeof MsgBuf]; /* for held messages */
+
+#if NAMED_BIND && !defined(NO_DATA)
+# define NO_DATA NO_ADDRESS
+#endif /* NAMED_BIND && !defined(NO_DATA) */
+
+void
+/*VARARGS1*/
+#ifdef __STDC__
+syserr(const char *fmt, ...)
+#else /* __STDC__ */
+syserr(fmt, va_alist)
+ const char *fmt;
+ va_dcl
+#endif /* __STDC__ */
+{
+ register char *p;
+ int save_errno = errno;
+ bool panic;
+ bool exiting;
+ char *user;
+ char *enhsc;
+ char *errtxt;
+ struct passwd *pw;
+ char ubuf[80];
+ SM_VA_LOCAL_DECL
+
+ switch (*fmt)
+ {
+ case '!':
+ ++fmt;
+ panic = true;
+ exiting = true;
+ break;
+ case '@':
+ ++fmt;
+ panic = false;
+ exiting = true;
+ break;
+ default:
+ panic = false;
+ exiting = false;
+ break;
+ }
+
+ /* format and output the error message */
+ if (exiting)
+ {
+ /*
+ ** Since we are terminating the process,
+ ** we are aborting the entire SMTP session,
+ ** rather than just the current transaction.
+ */
+
+ p = "421";
+ enhsc = "4.0.0";
+ }
+ else if (save_errno == 0)
+ {
+ p = "554";
+ enhsc = "5.0.0";
+ }
+ else
+ {
+ p = "451";
+ enhsc = "4.0.0";
+ }
+ SM_VA_START(ap, fmt);
+ errtxt = fmtmsg(MsgBuf, (char *) NULL, p, enhsc, save_errno, fmt, ap);
+ SM_VA_END(ap);
+ puterrmsg(MsgBuf);
+
+ /* save this message for mailq printing */
+ if (!panic && CurEnv != NULL)
+ {
+ char *nmsg = sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
+
+ if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
+ sm_free(CurEnv->e_message);
+ CurEnv->e_message = nmsg;
+ }
+
+ /* determine exit status if not already set */
+ if (ExitStat == EX_OK)
+ {
+ if (save_errno == 0)
+ ExitStat = EX_SOFTWARE;
+ else
+ ExitStat = EX_OSERR;
+ if (tTd(54, 1))
+ sm_dprintf("syserr: ExitStat = %d\n", ExitStat);
+ }
+
+ pw = sm_getpwuid(RealUid);
+ if (pw != NULL)
+ user = pw->pw_name;
+ else
+ {
+ user = ubuf;
+ (void) sm_snprintf(ubuf, sizeof ubuf, "UID%d", (int) RealUid);
+ }
+
+ if (LogLevel > 0)
+ sm_syslog(panic ? LOG_ALERT : LOG_CRIT,
+ CurEnv == NULL ? NOQID : CurEnv->e_id,
+ "SYSERR(%s): %.900s",
+ user, errtxt);
+ switch (save_errno)
+ {
+ case EBADF:
+ case ENFILE:
+ case EMFILE:
+ case ENOTTY:
+#ifdef EFBIG
+ case EFBIG:
+#endif /* EFBIG */
+#ifdef ESPIPE
+ case ESPIPE:
+#endif /* ESPIPE */
+#ifdef EPIPE
+ case EPIPE:
+#endif /* EPIPE */
+#ifdef ENOBUFS
+ case ENOBUFS:
+#endif /* ENOBUFS */
+#ifdef ESTALE
+ case ESTALE:
+#endif /* ESTALE */
+ printopenfds(true);
+ mci_dump_all(true);
+ break;
+ }
+ if (panic)
+ {
+#if XLA
+ xla_all_end();
+#endif /* XLA */
+ sync_queue_time();
+ if (tTd(0, 1))
+ abort();
+ exit(EX_OSERR);
+ }
+ errno = 0;
+ if (QuickAbort)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 2);
+}
+/*
+** USRERR -- Signal user error.
+**
+** This is much like syserr except it is for user errors.
+**
+** Parameters:
+** fmt -- the format string. If it does not begin with
+** a three-digit SMTP reply code, 550 is assumed.
+** (others) -- sm_io_printf strings
+**
+** Returns:
+** none
+** Raises E:mta.quickabort if QuickAbort is set.
+**
+** Side Effects:
+** increments Errors.
+*/
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+usrerr(const char *fmt, ...)
+#else /* __STDC__ */
+usrerr(fmt, va_alist)
+ const char *fmt;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char *enhsc;
+ char *errtxt;
+ SM_VA_LOCAL_DECL
+
+ if (fmt[0] == '5' || fmt[0] == '6')
+ enhsc = "5.0.0";
+ else if (fmt[0] == '4' || fmt[0] == '8')
+ enhsc = "4.0.0";
+ else if (fmt[0] == '2')
+ enhsc = "2.0.0";
+ else
+ enhsc = NULL;
+ SM_VA_START(ap, fmt);
+ errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "550", enhsc, 0, fmt, ap);
+ SM_VA_END(ap);
+
+ if (SuprErrs)
+ return;
+
+ /* save this message for mailq printing */
+ switch (MsgBuf[0])
+ {
+ case '4':
+ case '8':
+ if (CurEnv->e_message != NULL)
+ break;
+
+ /* FALLTHROUGH */
+
+ case '5':
+ case '6':
+ if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
+ sm_free(CurEnv->e_message);
+ if (MsgBuf[0] == '6')
+ {
+ char buf[MAXLINE];
+
+ (void) sm_snprintf(buf, sizeof buf,
+ "Postmaster warning: %.*s",
+ (int) sizeof buf - 22, errtxt);
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, buf);
+ }
+ else
+ {
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
+ }
+ break;
+ }
+
+ puterrmsg(MsgBuf);
+ if (LogLevel > 3 && LogUsrErrs)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", errtxt);
+ if (QuickAbort)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+}
+/*
+** USRERRENH -- Signal user error.
+**
+** Same as usrerr but with enhanced status code.
+**
+** Parameters:
+** enhsc -- the enhanced status code.
+** fmt -- the format string. If it does not begin with
+** a three-digit SMTP reply code, 550 is assumed.
+** (others) -- sm_io_printf strings
+**
+** Returns:
+** none
+** Raises E:mta.quickabort if QuickAbort is set.
+**
+** Side Effects:
+** increments Errors.
+*/
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+usrerrenh(char *enhsc, const char *fmt, ...)
+#else /* __STDC__ */
+usrerrenh(enhsc, fmt, va_alist)
+ char *enhsc;
+ const char *fmt;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char *errtxt;
+ SM_VA_LOCAL_DECL
+
+ if (enhsc == NULL || *enhsc == '\0')
+ {
+ if (fmt[0] == '5' || fmt[0] == '6')
+ enhsc = "5.0.0";
+ else if (fmt[0] == '4' || fmt[0] == '8')
+ enhsc = "4.0.0";
+ else if (fmt[0] == '2')
+ enhsc = "2.0.0";
+ }
+ SM_VA_START(ap, fmt);
+ errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "550", enhsc, 0, fmt, ap);
+ SM_VA_END(ap);
+
+ if (SuprErrs)
+ return;
+
+ /* save this message for mailq printing */
+ switch (MsgBuf[0])
+ {
+ case '4':
+ case '8':
+ if (CurEnv->e_message != NULL)
+ break;
+
+ /* FALLTHROUGH */
+
+ case '5':
+ case '6':
+ if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
+ sm_free(CurEnv->e_message);
+ if (MsgBuf[0] == '6')
+ {
+ char buf[MAXLINE];
+
+ (void) sm_snprintf(buf, sizeof buf,
+ "Postmaster warning: %.*s",
+ (int) sizeof buf - 22, errtxt);
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, buf);
+ }
+ else
+ {
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
+ }
+ break;
+ }
+
+ puterrmsg(MsgBuf);
+ if (LogLevel > 3 && LogUsrErrs)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id, "%.900s", errtxt);
+ if (QuickAbort)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+}
+/*
+** MESSAGE -- print message (not necessarily an error)
+**
+** Parameters:
+** msg -- the message (sm_io_printf fmt) -- it can begin with
+** an SMTP reply code. If not, 050 is assumed.
+** (others) -- sm_io_printf arguments
+**
+** Returns:
+** none
+**
+** Side Effects:
+** none.
+*/
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+message(const char *msg, ...)
+#else /* __STDC__ */
+message(msg, va_alist)
+ const char *msg;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char *errtxt;
+ SM_VA_LOCAL_DECL
+
+ errno = 0;
+ SM_VA_START(ap, msg);
+ errtxt = fmtmsg(MsgBuf, CurEnv->e_to, "050", (char *) NULL, 0, msg, ap);
+ SM_VA_END(ap);
+ putoutmsg(MsgBuf, false, false);
+
+ /* save this message for mailq printing */
+ switch (MsgBuf[0])
+ {
+ case '4':
+ case '8':
+ if (CurEnv->e_message != NULL)
+ break;
+ /* FALLTHROUGH */
+
+ case '5':
+ if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
+ sm_free(CurEnv->e_message);
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
+ break;
+ }
+}
+/*
+** NMESSAGE -- print message (not necessarily an error)
+**
+** Just like "message" except it never puts the to... tag on.
+**
+** Parameters:
+** msg -- the message (sm_io_printf fmt) -- if it begins
+** with a three digit SMTP reply code, that is used,
+** otherwise 050 is assumed.
+** (others) -- sm_io_printf arguments
+**
+** Returns:
+** none
+**
+** Side Effects:
+** none.
+*/
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+nmessage(const char *msg, ...)
+#else /* __STDC__ */
+nmessage(msg, va_alist)
+ const char *msg;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char *errtxt;
+ SM_VA_LOCAL_DECL
+
+ errno = 0;
+ SM_VA_START(ap, msg);
+ errtxt = fmtmsg(MsgBuf, (char *) NULL, "050",
+ (char *) NULL, 0, msg, ap);
+ SM_VA_END(ap);
+ putoutmsg(MsgBuf, false, false);
+
+ /* save this message for mailq printing */
+ switch (MsgBuf[0])
+ {
+ case '4':
+ case '8':
+ if (CurEnv->e_message != NULL)
+ break;
+ /* FALLTHROUGH */
+
+ case '5':
+ if (CurEnv->e_rpool == NULL && CurEnv->e_message != NULL)
+ sm_free(CurEnv->e_message);
+ CurEnv->e_message =
+ sm_rpool_strdup_x(CurEnv->e_rpool, errtxt);
+ break;
+ }
+}
+/*
+** PUTOUTMSG -- output error message to transcript and channel
+**
+** Parameters:
+** msg -- message to output (in SMTP format).
+** holdmsg -- if true, don't output a copy of the message to
+** our output channel.
+** heldmsg -- if true, this is a previously held message;
+** don't log it to the transcript file.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Outputs msg to the transcript.
+** If appropriate, outputs it to the channel.
+** Deletes SMTP reply code number as appropriate.
+*/
+
+static void
+putoutmsg(msg, holdmsg, heldmsg)
+ char *msg;
+ bool holdmsg;
+ bool heldmsg;
+{
+ char *errtxt = msg;
+ char msgcode = msg[0];
+
+ /* display for debugging */
+ if (tTd(54, 8))
+ sm_dprintf("--- %s%s%s\n", msg, holdmsg ? " (hold)" : "",
+ heldmsg ? " (held)" : "");
+
+ /* map warnings to something SMTP can handle */
+ if (msgcode == '6')
+ msg[0] = '5';
+ else if (msgcode == '8')
+ msg[0] = '4';
+
+ /* output to transcript if serious */
+ if (!heldmsg && CurEnv != NULL && CurEnv->e_xfp != NULL &&
+ strchr("45", msg[0]) != NULL)
+ (void) sm_io_fprintf(CurEnv->e_xfp, SM_TIME_DEFAULT, "%s\n",
+ msg);
+
+ if (LogLevel > 14 && (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ sm_syslog(LOG_INFO, CurEnv->e_id,
+ "--- %s%s%s", msg, holdmsg ? " (hold)" : "",
+ heldmsg ? " (held)" : "");
+
+ if (msgcode == '8')
+ msg[0] = '0';
+
+ /* output to channel if appropriate */
+ if (!Verbose && msg[0] == '0')
+ return;
+ if (holdmsg)
+ {
+ /* save for possible future display */
+ msg[0] = msgcode;
+ if (HeldMessageBuf[0] == '5' && msgcode == '4')
+ return;
+ (void) sm_strlcpy(HeldMessageBuf, msg, sizeof HeldMessageBuf);
+ return;
+ }
+
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+
+ if (OutChannel == NULL)
+ return;
+
+ /* find actual text of error (after SMTP status codes) */
+ if (ISSMTPREPLY(errtxt))
+ {
+ int l;
+
+ errtxt += 4;
+ l = isenhsc(errtxt, ' ');
+ if (l <= 0)
+ l = isenhsc(errtxt, '\0');
+ if (l > 0)
+ errtxt += l + 1;
+ }
+
+ /* if DisConnected, OutChannel now points to the transcript */
+ if (!DisConnected &&
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON || OpMode == MD_ARPAFTP))
+ (void) sm_io_fprintf(OutChannel, SM_TIME_DEFAULT, "%s\r\n",
+ msg);
+ else
+ (void) sm_io_fprintf(OutChannel, SM_TIME_DEFAULT, "%s\n",
+ errtxt);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d >>> %s\n", (int) CurrentPid,
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON)
+ ? msg : errtxt);
+#if !PIPELINING
+ /* XXX can't flush here for SMTP pipelining */
+ if (msg[3] == ' ')
+ (void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
+ if (!sm_io_error(OutChannel) || DisConnected)
+ return;
+
+ /*
+ ** Error on output -- if reporting lost channel, just ignore it.
+ ** Also, ignore errors from QUIT response (221 message) -- some
+ ** rude servers don't read result.
+ */
+
+ if (InChannel == NULL || sm_io_eof(InChannel) ||
+ sm_io_error(InChannel) || strncmp(msg, "221", 3) == 0)
+ return;
+
+ /* can't call syserr, 'cause we are using MsgBuf */
+ HoldErrs = true;
+ if (LogLevel > 0)
+ sm_syslog(LOG_CRIT, CurEnv->e_id,
+ "SYSERR: putoutmsg (%s): error on output channel sending \"%s\": %s",
+ CURHOSTNAME,
+ shortenstring(msg, MAXSHORTSTR), sm_errstring(errno));
+#endif /* !PIPELINING */
+}
+/*
+** PUTERRMSG -- like putoutmsg, but does special processing for error messages
+**
+** Parameters:
+** msg -- the message to output.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets the fatal error bit in the envelope as appropriate.
+*/
+
+static void
+puterrmsg(msg)
+ char *msg;
+{
+ char msgcode = msg[0];
+
+ /* output the message as usual */
+ putoutmsg(msg, HoldErrs, false);
+
+ /* be careful about multiple error messages */
+ if (OnlyOneError)
+ HoldErrs = true;
+
+ /* signal the error */
+ Errors++;
+
+ if (CurEnv == NULL)
+ return;
+
+ if (msgcode == '6')
+ {
+ /* notify the postmaster */
+ CurEnv->e_flags |= EF_PM_NOTIFY;
+ }
+ else if (msgcode == '5' && bitset(EF_GLOBALERRS, CurEnv->e_flags))
+ {
+ /* mark long-term fatal errors */
+ CurEnv->e_flags |= EF_FATALERRS;
+ }
+}
+/*
+** ISENHSC -- check whether a string contains an enhanced status code
+**
+** Parameters:
+** s -- string with possible enhanced status code.
+** delim -- delim for enhanced status code.
+**
+** Returns:
+** 0 -- no enhanced status code.
+** >4 -- length of enhanced status code.
+**
+** Side Effects:
+** none.
+*/
+int
+isenhsc(s, delim)
+ const char *s;
+ int delim;
+{
+ int l, h;
+
+ if (s == NULL)
+ return 0;
+ if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
+ return 0;
+ h = 0;
+ l = 2;
+ while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
+ ++h;
+ if (h == 0 || s[l + h] != '.')
+ return 0;
+ l += h + 1;
+ h = 0;
+ while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
+ ++h;
+ if (h == 0 || s[l + h] != delim)
+ return 0;
+ return l + h;
+}
+/*
+** EXTENHSC -- check and extract an enhanced status code
+**
+** Parameters:
+** s -- string with possible enhanced status code.
+** delim -- delim for enhanced status code.
+** e -- pointer to storage for enhanced status code.
+** must be != NULL and have space for at least
+** 10 characters ([245].[0-9]{1,3}.[0-9]{1,3})
+**
+** Returns:
+** 0 -- no enhanced status code.
+** >4 -- length of enhanced status code.
+**
+** Side Effects:
+** fills e with enhanced status code.
+*/
+
+int
+extenhsc(s, delim, e)
+ const char *s;
+ int delim;
+ char *e;
+{
+ int l, h;
+
+ if (s == NULL)
+ return 0;
+ if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.'))
+ return 0;
+ h = 0;
+ l = 2;
+ e[0] = s[0];
+ e[1] = '.';
+ while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
+ {
+ e[l + h] = s[l + h];
+ ++h;
+ }
+ if (h == 0 || s[l + h] != '.')
+ return 0;
+ e[l + h] = '.';
+ l += h + 1;
+ h = 0;
+ while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h]))
+ {
+ e[l + h] = s[l + h];
+ ++h;
+ }
+ if (h == 0 || s[l + h] != delim)
+ return 0;
+ e[l + h] = '\0';
+ return l + h;
+}
+/*
+** FMTMSG -- format a message into buffer.
+**
+** Parameters:
+** eb -- error buffer to get result -- MUST BE MsgBuf.
+** to -- the recipient tag for this message.
+** num -- default three digit SMTP reply code.
+** enhsc -- enhanced status code.
+** en -- the error number to display.
+** fmt -- format of string.
+** ap -- arguments for fmt.
+**
+** Returns:
+** pointer to error text beyond status codes.
+**
+** Side Effects:
+** none.
+*/
+
+static char *
+fmtmsg(eb, to, num, enhsc, eno, fmt, ap)
+ register char *eb;
+ const char *to;
+ const char *num;
+ const char *enhsc;
+ int eno;
+ const char *fmt;
+ SM_VA_LOCAL_DECL
+{
+ char del;
+ int l;
+ int spaceleft = sizeof MsgBuf;
+ char *errtxt;
+
+ /* output the reply code */
+ if (ISSMTPCODE(fmt))
+ {
+ num = fmt;
+ fmt += 4;
+ }
+ if (num[3] == '-')
+ del = '-';
+ else
+ del = ' ';
+#if _FFR_SOFT_BOUNCE
+ if (SoftBounce && num[0] == '5')
+ {
+ /* replace 5 by 4 */
+ (void) sm_snprintf(eb, spaceleft, "4%2.2s%c", num + 1, del);
+ }
+ else
+#endif /* _FFR_SOFT_BOUNCE */
+ (void) sm_snprintf(eb, spaceleft, "%3.3s%c", num, del);
+ eb += 4;
+ spaceleft -= 4;
+
+ if ((l = isenhsc(fmt, ' ' )) > 0 && l < spaceleft - 4)
+ {
+ /* copy enh.status code including trailing blank */
+ l++;
+ (void) sm_strlcpy(eb, fmt, l + 1);
+ eb += l;
+ spaceleft -= l;
+ fmt += l;
+ }
+ else if ((l = isenhsc(enhsc, '\0')) > 0 && l < spaceleft - 4)
+ {
+ /* copy enh.status code */
+ (void) sm_strlcpy(eb, enhsc, l + 1);
+ eb[l] = ' ';
+ eb[++l] = '\0';
+ eb += l;
+ spaceleft -= l;
+ }
+#if _FFR_SOFT_BOUNCE
+ if (SoftBounce && eb[-l] == '5')
+ {
+ /* replace 5 by 4 */
+ eb[-l] = '4';
+ }
+#endif /* _FFR_SOFT_BOUNCE */
+ errtxt = eb;
+
+ /* output the file name and line number */
+ if (FileName != NULL)
+ {
+ (void) sm_snprintf(eb, spaceleft, "%s: line %d: ",
+ shortenstring(FileName, 83), LineNumber);
+ eb += (l = strlen(eb));
+ spaceleft -= l;
+ }
+
+ /*
+ ** output the "to" address only if it is defined and one of the
+ ** following codes is used:
+ ** 050 internal notices, e.g., alias expansion
+ ** 250 Ok
+ ** 252 Cannot VRFY user, but will accept message and attempt delivery
+ ** 450 Requested mail action not taken: mailbox unavailable
+ ** 550 Requested action not taken: mailbox unavailable
+ ** 553 Requested action not taken: mailbox name not allowed
+ **
+ ** Notice: this still isn't "the right thing", this code shouldn't
+ ** (indirectly) depend on CurEnv->e_to.
+ */
+
+ if (to != NULL && to[0] != '\0' &&
+ (strncmp(num, "050", 3) == 0 ||
+ strncmp(num, "250", 3) == 0 ||
+ strncmp(num, "252", 3) == 0 ||
+ strncmp(num, "450", 3) == 0 ||
+ strncmp(num, "550", 3) == 0 ||
+ strncmp(num, "553", 3) == 0))
+ {
+ (void) sm_strlcpyn(eb, spaceleft, 2,
+ shortenstring(to, MAXSHORTSTR), "... ");
+ spaceleft -= strlen(eb);
+ while (*eb != '\0')
+ *eb++ &= 0177;
+ }
+
+ /* output the message */
+ (void) sm_vsnprintf(eb, spaceleft, fmt, ap);
+ spaceleft -= strlen(eb);
+ while (*eb != '\0')
+ *eb++ &= 0177;
+
+ /* output the error code, if any */
+ if (eno != 0)
+ (void) sm_strlcpyn(eb, spaceleft, 2, ": ", sm_errstring(eno));
+
+ return errtxt;
+}
+/*
+** BUFFER_ERRORS -- arrange to buffer future error messages
+**
+** Parameters:
+** none
+**
+** Returns:
+** none.
+*/
+
+void
+buffer_errors()
+{
+ HeldMessageBuf[0] = '\0';
+ HoldErrs = true;
+}
+/*
+** FLUSH_ERRORS -- flush the held error message buffer
+**
+** Parameters:
+** print -- if set, print the message, otherwise just
+** delete it.
+**
+** Returns:
+** none.
+*/
+
+void
+flush_errors(print)
+ bool print;
+{
+ if (print && HeldMessageBuf[0] != '\0')
+ putoutmsg(HeldMessageBuf, false, true);
+ HeldMessageBuf[0] = '\0';
+ HoldErrs = false;
+}
+/*
+** SM_ERRSTRING -- return string description of error code
+**
+** Parameters:
+** errnum -- the error number to translate
+**
+** Returns:
+** A string description of errnum.
+**
+** Side Effects:
+** none.
+*/
+
+const char *
+sm_errstring(errnum)
+ int errnum;
+{
+ char *dnsmsg;
+ char *bp;
+ static char buf[MAXLINE];
+#if HASSTRERROR
+ char *err;
+ char errbuf[30];
+#endif /* HASSTRERROR */
+#if !HASSTRERROR && !defined(ERRLIST_PREDEFINED)
+ extern char *sys_errlist[];
+ extern int sys_nerr;
+#endif /* !HASSTRERROR && !defined(ERRLIST_PREDEFINED) */
+
+ /*
+ ** Handle special network error codes.
+ **
+ ** These are 4.2/4.3bsd specific; they should be in daemon.c.
+ */
+
+ dnsmsg = NULL;
+ switch (errnum)
+ {
+ case ETIMEDOUT:
+ case ECONNRESET:
+ bp = buf;
+#if HASSTRERROR
+ err = strerror(errnum);
+ if (err == NULL)
+ {
+ (void) sm_snprintf(errbuf, sizeof errbuf,
+ "Error %d", errnum);
+ err = errbuf;
+ }
+ (void) sm_strlcpy(bp, err, SPACELEFT(buf, bp));
+#else /* HASSTRERROR */
+ if (errnum >= 0 && errnum < sys_nerr)
+ (void) sm_strlcpy(bp, sys_errlist[errnum],
+ SPACELEFT(buf, bp));
+ else
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ "Error %d", errnum);
+#endif /* HASSTRERROR */
+ bp += strlen(bp);
+ if (CurHostName != NULL)
+ {
+ if (errnum == ETIMEDOUT)
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ " with ");
+ bp += strlen(bp);
+ }
+ else
+ {
+ bp = buf;
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ "Connection reset by ");
+ bp += strlen(bp);
+ }
+ (void) sm_strlcpy(bp,
+ shortenstring(CurHostName, MAXSHORTSTR),
+ SPACELEFT(buf, bp));
+ bp += strlen(buf);
+ }
+ if (SmtpPhase != NULL)
+ {
+ (void) sm_snprintf(bp, SPACELEFT(buf, bp),
+ " during %s", SmtpPhase);
+ }
+ return buf;
+
+ case EHOSTDOWN:
+ if (CurHostName == NULL)
+ break;
+ (void) sm_snprintf(buf, sizeof buf, "Host %s is down",
+ shortenstring(CurHostName, MAXSHORTSTR));
+ return buf;
+
+ case ECONNREFUSED:
+ if (CurHostName == NULL)
+ break;
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "Connection refused by ",
+ shortenstring(CurHostName, MAXSHORTSTR));
+ return buf;
+
+#if NAMED_BIND
+ case HOST_NOT_FOUND + E_DNSBASE:
+ dnsmsg = "host not found";
+ break;
+
+ case TRY_AGAIN + E_DNSBASE:
+ dnsmsg = "host name lookup failure";
+ break;
+
+ case NO_RECOVERY + E_DNSBASE:
+ dnsmsg = "non-recoverable error";
+ break;
+
+ case NO_DATA + E_DNSBASE:
+ dnsmsg = "no data known";
+ break;
+#endif /* NAMED_BIND */
+
+ case EPERM:
+ /* SunOS gives "Not owner" -- this is the POSIX message */
+ return "Operation not permitted";
+
+ /*
+ ** Error messages used internally in sendmail.
+ */
+
+ case E_SM_OPENTIMEOUT:
+ return "Timeout on file open";
+
+ case E_SM_NOSLINK:
+ return "Symbolic links not allowed";
+
+ case E_SM_NOHLINK:
+ return "Hard links not allowed";
+
+ case E_SM_REGONLY:
+ return "Regular files only";
+
+ case E_SM_ISEXEC:
+ return "Executable files not allowed";
+
+ case E_SM_WWDIR:
+ return "World writable directory";
+
+ case E_SM_GWDIR:
+ return "Group writable directory";
+
+ case E_SM_FILECHANGE:
+ return "File changed after open";
+
+ case E_SM_WWFILE:
+ return "World writable file";
+
+ case E_SM_GWFILE:
+ return "Group writable file";
+
+ case E_SM_GRFILE:
+ return "Group readable file";
+
+ case E_SM_WRFILE:
+ return "World readable file";
+ }
+
+ if (dnsmsg != NULL)
+ {
+ bp = buf;
+ bp += sm_strlcpy(bp, "Name server: ", sizeof buf);
+ if (CurHostName != NULL)
+ {
+ (void) sm_strlcpyn(bp, SPACELEFT(buf, bp), 2,
+ shortenstring(CurHostName, MAXSHORTSTR), ": ");
+ bp += strlen(bp);
+ }
+ (void) sm_strlcpy(bp, dnsmsg, SPACELEFT(buf, bp));
+ return buf;
+ }
+
+#if LDAPMAP
+ if (errnum >= E_LDAPBASE)
+ return ldap_err2string(errnum - E_LDAPBASE);
+#endif /* LDAPMAP */
+
+#if HASSTRERROR
+ err = strerror(errnum);
+ if (err == NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf, "Error %d", errnum);
+ return buf;
+ }
+ return err;
+#else /* HASSTRERROR */
+ if (errnum > 0 && errnum < sys_nerr)
+ return sys_errlist[errnum];
+
+ (void) sm_snprintf(buf, sizeof buf, "Error %d", errnum);
+ return buf;
+#endif /* HASSTRERROR */
+}
diff --git a/contrib/sendmail/src/headers.c b/contrib/sendmail/src/headers.c
new file mode 100644
index 0000000..385d7a0
--- /dev/null
+++ b/contrib/sendmail/src/headers.c
@@ -0,0 +1,1919 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: headers.c,v 8.266.4.1 2002/08/16 14:56:01 ca Exp $")
+
+static size_t fix_mime_header __P((char *));
+static int priencode __P((char *));
+static void put_vanilla_header __P((HDR *, char *, MCI *));
+
+/*
+** SETUPHEADERS -- initialize headers in symbol table
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+void
+setupheaders()
+{
+ struct hdrinfo *hi;
+ STAB *s;
+
+ for (hi = HdrInfo; hi->hi_field != NULL; hi++)
+ {
+ s = stab(hi->hi_field, ST_HEADER, ST_ENTER);
+ s->s_header.hi_flags = hi->hi_flags;
+ s->s_header.hi_ruleset = NULL;
+ }
+}
+/*
+** CHOMPHEADER -- process and save a header line.
+**
+** Called by collect, readcf, and readqf to deal with header lines.
+**
+** Parameters:
+** line -- header as a text line.
+** pflag -- flags for chompheader() (from sendmail.h)
+** hdrp -- a pointer to the place to save the header.
+** e -- the envelope including this header.
+**
+** Returns:
+** flags for this header.
+**
+** Side Effects:
+** The header is saved on the header list.
+** Contents of 'line' are destroyed.
+*/
+
+static struct hdrinfo NormalHeader = { NULL, 0, NULL };
+
+unsigned long
+chompheader(line, pflag, hdrp, e)
+ char *line;
+ int pflag;
+ HDR **hdrp;
+ register ENVELOPE *e;
+{
+ unsigned char mid = '\0';
+ register char *p;
+ register HDR *h;
+ HDR **hp;
+ char *fname;
+ char *fvalue;
+ bool cond = false;
+ bool dropfrom;
+ bool headeronly;
+ STAB *s;
+ struct hdrinfo *hi;
+ bool nullheader = false;
+ BITMAP256 mopts;
+
+ if (tTd(31, 6))
+ {
+ sm_dprintf("chompheader: ");
+ xputs(line);
+ sm_dprintf("\n");
+ }
+
+ headeronly = hdrp != NULL;
+ if (!headeronly)
+ hdrp = &e->e_header;
+
+ /* strip off options */
+ clrbitmap(mopts);
+ p = line;
+ if (!bitset(pflag, CHHDR_USER) && *p == '?')
+ {
+ int c;
+ register char *q;
+
+ q = strchr(++p, '?');
+ if (q == NULL)
+ goto hse;
+
+ *q = '\0';
+ c = *p & 0377;
+
+ /* possibly macro conditional */
+ if (c == MACROEXPAND)
+ {
+ /* catch ?$? */
+ if (*++p == '\0')
+ {
+ *q = '?';
+ goto hse;
+ }
+
+ mid = (unsigned char) *p++;
+
+ /* catch ?$abc? */
+ if (*p != '\0')
+ {
+ *q = '?';
+ goto hse;
+ }
+ }
+ else if (*p == '$')
+ {
+ /* catch ?$? */
+ if (*++p == '\0')
+ {
+ *q = '?';
+ goto hse;
+ }
+
+ mid = (unsigned char) macid(p);
+ if (bitset(0200, mid))
+ p += strlen(macname(mid)) + 2;
+ else
+ p++;
+
+ /* catch ?$abc? */
+ if (*p != '\0')
+ {
+ *q = '?';
+ goto hse;
+ }
+ }
+ else
+ {
+ while (*p != '\0')
+ {
+ if (!isascii(*p))
+ {
+ *q = '?';
+ goto hse;
+ }
+
+ setbitn(bitidx(*p), mopts);
+ cond = true;
+ p++;
+ }
+ }
+ p = q + 1;
+ }
+
+ /* find canonical name */
+ fname = p;
+ while (isascii(*p) && isgraph(*p) && *p != ':')
+ p++;
+ fvalue = p;
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p++ != ':' || fname == fvalue)
+ {
+hse:
+ syserr("553 5.3.0 header syntax error, line \"%s\"", line);
+ return 0;
+ }
+ *fvalue = '\0';
+
+ /* strip field value on front */
+ if (*p == ' ')
+ p++;
+ fvalue = p;
+
+ /* if the field is null, go ahead and use the default */
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ nullheader = true;
+
+ /* security scan: long field names are end-of-header */
+ if (strlen(fname) > 100)
+ return H_EOH;
+
+ /* check to see if it represents a ruleset call */
+ if (bitset(pflag, CHHDR_DEF))
+ {
+ char hbuf[50];
+
+ (void) expand(fvalue, hbuf, sizeof hbuf, e);
+ for (p = hbuf; isascii(*p) && isspace(*p); )
+ p++;
+ if ((*p++ & 0377) == CALLSUBR)
+ {
+ auto char *endp;
+ bool strc;
+
+ strc = *p == '+'; /* strip comments? */
+ if (strc)
+ ++p;
+ if (strtorwset(p, &endp, ST_ENTER) > 0)
+ {
+ *endp = '\0';
+ s = stab(fname, ST_HEADER, ST_ENTER);
+ if (LogLevel > 9 &&
+ s->s_header.hi_ruleset != NULL)
+ sm_syslog(LOG_WARNING, NOQID,
+ "Warning: redefined ruleset for header=%s, old=%s, new=%s",
+ fname,
+ s->s_header.hi_ruleset, p);
+ s->s_header.hi_ruleset = newstr(p);
+ if (!strc)
+ s->s_header.hi_flags |= H_STRIPCOMM;
+ }
+ return 0;
+ }
+ }
+
+ /* see if it is a known type */
+ s = stab(fname, ST_HEADER, ST_FIND);
+ if (s != NULL)
+ hi = &s->s_header;
+ else
+ hi = &NormalHeader;
+
+ if (tTd(31, 9))
+ {
+ if (s == NULL)
+ sm_dprintf("no header flags match\n");
+ else
+ sm_dprintf("header match, flags=%lx, ruleset=%s\n",
+ hi->hi_flags,
+ hi->hi_ruleset == NULL ? "<NULL>"
+ : hi->hi_ruleset);
+ }
+
+ /* see if this is a resent message */
+ if (!bitset(pflag, CHHDR_DEF) && !headeronly &&
+ bitset(H_RESENT, hi->hi_flags))
+ e->e_flags |= EF_RESENT;
+
+ /* if this is an Errors-To: header keep track of it now */
+ if (UseErrorsTo && !bitset(pflag, CHHDR_DEF) && !headeronly &&
+ bitset(H_ERRORSTO, hi->hi_flags))
+ (void) sendtolist(fvalue, NULLADDR, &e->e_errorqueue, 0, e);
+
+ /* if this means "end of header" quit now */
+ if (!headeronly && bitset(H_EOH, hi->hi_flags))
+ return hi->hi_flags;
+
+ /*
+ ** Horrible hack to work around problem with Lotus Notes SMTP
+ ** mail gateway, which generates From: headers with newlines in
+ ** them and the <address> on the second line. Although this is
+ ** legal RFC 822, many MUAs don't handle this properly and thus
+ ** never find the actual address.
+ */
+
+ if (bitset(H_FROM, hi->hi_flags) && SingleLineFromHeader)
+ {
+ while ((p = strchr(fvalue, '\n')) != NULL)
+ *p = ' ';
+ }
+
+ /*
+ ** If there is a check ruleset, verify it against the header.
+ */
+
+ if (bitset(pflag, CHHDR_CHECK))
+ {
+ int rscheckflags;
+ char *rs;
+
+ /* no ruleset? look for default */
+ rs = hi->hi_ruleset;
+ rscheckflags = RSF_COUNT;
+ if (!bitset(hi->hi_flags, H_FROM|H_RCPT))
+ rscheckflags |= RSF_UNSTRUCTURED;
+ if (rs == NULL)
+ {
+ s = stab("*", ST_HEADER, ST_FIND);
+ if (s != NULL)
+ {
+ rs = (&s->s_header)->hi_ruleset;
+ if (bitset((&s->s_header)->hi_flags,
+ H_STRIPCOMM))
+ rscheckflags |= RSF_RMCOMM;
+ }
+ }
+ else if (bitset(hi->hi_flags, H_STRIPCOMM))
+ rscheckflags |= RSF_RMCOMM;
+ if (rs != NULL)
+ {
+ int l, k;
+ char qval[MAXNAME];
+
+ l = 0;
+ qval[l++] = '"';
+
+ /* - 3 to avoid problems with " at the end */
+ for (k = 0; fvalue[k] != '\0' && l < MAXNAME - 3; k++)
+ {
+ switch (fvalue[k])
+ {
+ /* XXX other control chars? */
+ case '\011': /* ht */
+ case '\012': /* nl */
+ case '\013': /* vt */
+ case '\014': /* np */
+ case '\015': /* cr */
+ qval[l++] = ' ';
+ break;
+ case '"':
+ qval[l++] = '\\';
+ /* FALLTHROUGH */
+ default:
+ qval[l++] = fvalue[k];
+ break;
+ }
+ }
+ qval[l++] = '"';
+ qval[l] = '\0';
+ k += strlen(fvalue + k);
+ if (k >= MAXNAME)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "Warning: truncated header '%s' before check with '%s' len=%d max=%d",
+ fname, rs, k, MAXNAME - 1);
+ }
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{currHeader}"), qval);
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{hdr_name}"), fname);
+
+ (void) sm_snprintf(qval, sizeof qval, "%d", k);
+ macdefine(&e->e_macro, A_TEMP, macid("{hdrlen}"), qval);
+#if _FFR_HDR_TYPE
+ /*
+ ** XXX: h isn't set yet
+ ** If we really want to be precise then we have
+ ** to lookup the header (see below).
+ ** It's probably not worth the effort.
+ */
+
+ if (bitset(H_FROM, h->h_flags))
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "h s");
+ else if (bitset(H_RCPT, h->h_flags))
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "h r");
+ else
+#endif /* _FFR_HDR_TYPE */
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "h");
+ (void) rscheck(rs, fvalue, NULL, e, rscheckflags, 3,
+ NULL, e->e_id);
+ }
+ }
+
+ /*
+ ** Drop explicit From: if same as what we would generate.
+ ** This is to make MH (which doesn't always give a full name)
+ ** insert the full name information in all circumstances.
+ */
+
+ dropfrom = false;
+ p = "resent-from";
+ if (!bitset(EF_RESENT, e->e_flags))
+ p += 7;
+ if (!bitset(pflag, CHHDR_DEF) && !headeronly &&
+ !bitset(EF_QUEUERUN, e->e_flags) && sm_strcasecmp(fname, p) == 0)
+ {
+ if (tTd(31, 2))
+ {
+ sm_dprintf("comparing header from (%s) against default (%s or %s)\n",
+ fvalue, e->e_from.q_paddr, e->e_from.q_user);
+ }
+ if (e->e_from.q_paddr != NULL &&
+ e->e_from.q_mailer != NULL &&
+ bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) &&
+ (strcmp(fvalue, e->e_from.q_paddr) == 0 ||
+ strcmp(fvalue, e->e_from.q_user) == 0))
+ dropfrom = true;
+ }
+
+ /* delete default value for this header */
+ for (hp = hdrp; (h = *hp) != NULL; hp = &h->h_link)
+ {
+ if (sm_strcasecmp(fname, h->h_field) == 0 &&
+ !bitset(H_USER, h->h_flags) &&
+ !bitset(H_FORCE, h->h_flags))
+ {
+ if (nullheader)
+ {
+ /* user-supplied value was null */
+ return 0;
+ }
+ if (dropfrom)
+ {
+ /* make this look like the user entered it */
+ h->h_flags |= H_USER;
+ return hi->hi_flags;
+ }
+ h->h_value = NULL;
+ if (!cond)
+ {
+ /* copy conditions from default case */
+ memmove((char *) mopts, (char *) h->h_mflags,
+ sizeof mopts);
+ }
+ h->h_macro = mid;
+ }
+ }
+
+ /* create a new node */
+ h = (HDR *) sm_rpool_malloc_x(e->e_rpool, sizeof *h);
+ h->h_field = sm_rpool_strdup_x(e->e_rpool, fname);
+ h->h_value = sm_rpool_strdup_x(e->e_rpool, fvalue);
+ h->h_link = NULL;
+ memmove((char *) h->h_mflags, (char *) mopts, sizeof mopts);
+ h->h_macro = mid;
+ *hp = h;
+ h->h_flags = hi->hi_flags;
+ if (bitset(pflag, CHHDR_USER) || bitset(pflag, CHHDR_QUEUE))
+ h->h_flags |= H_USER;
+
+ /* strip EOH flag if parsing MIME headers */
+ if (headeronly)
+ h->h_flags &= ~H_EOH;
+ if (bitset(pflag, CHHDR_DEF))
+ h->h_flags |= H_DEFAULT;
+ if (cond || mid != '\0')
+ h->h_flags |= H_CHECK;
+
+ /* hack to see if this is a new format message */
+ if (!bitset(pflag, CHHDR_DEF) && !headeronly &&
+ bitset(H_RCPT|H_FROM, h->h_flags) &&
+ (strchr(fvalue, ',') != NULL || strchr(fvalue, '(') != NULL ||
+ strchr(fvalue, '<') != NULL || strchr(fvalue, ';') != NULL))
+ {
+ e->e_flags &= ~EF_OLDSTYLE;
+ }
+
+ return h->h_flags;
+}
+/*
+** ADDHEADER -- add a header entry to the end of the queue.
+**
+** This bypasses the special checking of chompheader.
+**
+** Parameters:
+** field -- the name of the header field.
+** value -- the value of the field.
+** flags -- flags to add to h_flags.
+** e -- envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** adds the field on the list of headers for this envelope.
+*/
+
+void
+addheader(field, value, flags, e)
+ char *field;
+ char *value;
+ int flags;
+ ENVELOPE *e;
+{
+ register HDR *h;
+ STAB *s;
+ HDR **hp;
+ HDR **hdrlist = &e->e_header;
+
+ /* find info struct */
+ s = stab(field, ST_HEADER, ST_FIND);
+
+ /* find current place in list -- keep back pointer? */
+ for (hp = hdrlist; (h = *hp) != NULL; hp = &h->h_link)
+ {
+ if (sm_strcasecmp(field, h->h_field) == 0)
+ break;
+ }
+
+ /* allocate space for new header */
+ h = (HDR *) sm_rpool_malloc_x(e->e_rpool, sizeof *h);
+ h->h_field = field;
+ h->h_value = sm_rpool_strdup_x(e->e_rpool, value);
+ h->h_link = *hp;
+ h->h_flags = flags;
+ if (s != NULL)
+ h->h_flags |= s->s_header.hi_flags;
+ clrbitmap(h->h_mflags);
+ h->h_macro = '\0';
+ *hp = h;
+}
+/*
+** HVALUE -- return value of a header.
+**
+** Only "real" fields (i.e., ones that have not been supplied
+** as a default) are used.
+**
+** Parameters:
+** field -- the field name.
+** header -- the header list.
+**
+** Returns:
+** pointer to the value part.
+** NULL if not found.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+hvalue(field, header)
+ char *field;
+ HDR *header;
+{
+ register HDR *h;
+
+ for (h = header; h != NULL; h = h->h_link)
+ {
+ if (!bitset(H_DEFAULT, h->h_flags) &&
+ sm_strcasecmp(h->h_field, field) == 0)
+ return h->h_value;
+ }
+ return NULL;
+}
+/*
+** ISHEADER -- predicate telling if argument is a header.
+**
+** A line is a header if it has a single word followed by
+** optional white space followed by a colon.
+**
+** Header fields beginning with two dashes, although technically
+** permitted by RFC822, are automatically rejected in order
+** to make MIME work out. Without this we could have a technically
+** legal header such as ``--"foo:bar"'' that would also be a legal
+** MIME separator.
+**
+** Parameters:
+** h -- string to check for possible headerness.
+**
+** Returns:
+** true if h is a header.
+** false otherwise.
+**
+** Side Effects:
+** none.
+*/
+
+bool
+isheader(h)
+ char *h;
+{
+ register char *s = h;
+
+ if (s[0] == '-' && s[1] == '-')
+ return false;
+
+ while (*s > ' ' && *s != ':' && *s != '\0')
+ s++;
+
+ if (h == s)
+ return false;
+
+ /* following technically violates RFC822 */
+ while (isascii(*s) && isspace(*s))
+ s++;
+
+ return (*s == ':');
+}
+/*
+** EATHEADER -- run through the stored header and extract info.
+**
+** Parameters:
+** e -- the envelope to process.
+** full -- if set, do full processing (e.g., compute
+** message priority). This should not be set
+** when reading a queue file because some info
+** needed to compute the priority is wrong.
+** log -- call logsender()?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets a bunch of global variables from information
+** in the collected header.
+*/
+
+void
+eatheader(e, full, log)
+ register ENVELOPE *e;
+ bool full;
+ bool log;
+{
+ register HDR *h;
+ register char *p;
+ int hopcnt = 0;
+ char buf[MAXLINE];
+
+ /*
+ ** Set up macros for possible expansion in headers.
+ */
+
+ macdefine(&e->e_macro, A_PERM, 'f', e->e_sender);
+ macdefine(&e->e_macro, A_PERM, 'g', e->e_sender);
+ if (e->e_origrcpt != NULL && *e->e_origrcpt != '\0')
+ macdefine(&e->e_macro, A_PERM, 'u', e->e_origrcpt);
+ else
+ macdefine(&e->e_macro, A_PERM, 'u', NULL);
+
+ /* full name of from person */
+ p = hvalue("full-name", e->e_header);
+ if (p != NULL)
+ {
+ if (!rfc822_string(p))
+ {
+ /*
+ ** Quote a full name with special characters
+ ** as a comment so crackaddr() doesn't destroy
+ ** the name portion of the address.
+ */
+
+ p = addquotes(p, e->e_rpool);
+ }
+ macdefine(&e->e_macro, A_PERM, 'x', p);
+ }
+
+ if (tTd(32, 1))
+ sm_dprintf("----- collected header -----\n");
+ e->e_msgid = NULL;
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ if (tTd(32, 1))
+ sm_dprintf("%s: ", h->h_field);
+ if (h->h_value == NULL)
+ {
+ if (tTd(32, 1))
+ sm_dprintf("<NULL>\n");
+ continue;
+ }
+
+ /* do early binding */
+ if (bitset(H_DEFAULT, h->h_flags) &&
+ !bitset(H_BINDLATE, h->h_flags))
+ {
+ if (tTd(32, 1))
+ {
+ sm_dprintf("(");
+ xputs(h->h_value);
+ sm_dprintf(") ");
+ }
+ expand(h->h_value, buf, sizeof buf, e);
+ if (buf[0] != '\0')
+ {
+ if (bitset(H_FROM, h->h_flags))
+ expand(crackaddr(buf), buf, sizeof buf,
+ e);
+ h->h_value = sm_rpool_strdup_x(e->e_rpool, buf);
+ h->h_flags &= ~H_DEFAULT;
+ }
+ }
+ if (tTd(32, 1))
+ {
+ xputs(h->h_value);
+ sm_dprintf("\n");
+ }
+
+ /* count the number of times it has been processed */
+ if (bitset(H_TRACE, h->h_flags))
+ hopcnt++;
+
+ /* send to this person if we so desire */
+ if (GrabTo && bitset(H_RCPT, h->h_flags) &&
+ !bitset(H_DEFAULT, h->h_flags) &&
+ (!bitset(EF_RESENT, e->e_flags) ||
+ bitset(H_RESENT, h->h_flags)))
+ {
+#if 0
+ int saveflags = e->e_flags;
+#endif /* 0 */
+
+ (void) sendtolist(denlstring(h->h_value, true, false),
+ NULLADDR, &e->e_sendqueue, 0, e);
+
+#if 0
+ /*
+ ** Change functionality so a fatal error on an
+ ** address doesn't affect the entire envelope.
+ */
+
+ /* delete fatal errors generated by this address */
+ if (!bitset(EF_FATALERRS, saveflags))
+ e->e_flags &= ~EF_FATALERRS;
+#endif /* 0 */
+ }
+
+ /* save the message-id for logging */
+ p = "resent-message-id";
+ if (!bitset(EF_RESENT, e->e_flags))
+ p += 7;
+ if (sm_strcasecmp(h->h_field, p) == 0)
+ {
+ e->e_msgid = h->h_value;
+ while (isascii(*e->e_msgid) && isspace(*e->e_msgid))
+ e->e_msgid++;
+ }
+ }
+ if (tTd(32, 1))
+ sm_dprintf("----------------------------\n");
+
+ /* if we are just verifying (that is, sendmail -t -bv), drop out now */
+ if (OpMode == MD_VERIFY)
+ return;
+
+ /* store hop count */
+ if (hopcnt > e->e_hopcount)
+ {
+ e->e_hopcount = hopcnt;
+ (void) sm_snprintf(buf, sizeof buf, "%d", e->e_hopcount);
+ macdefine(&e->e_macro, A_TEMP, 'c', buf);
+ }
+
+ /* message priority */
+ p = hvalue("precedence", e->e_header);
+ if (p != NULL)
+ e->e_class = priencode(p);
+ if (e->e_class < 0)
+ e->e_timeoutclass = TOC_NONURGENT;
+ else if (e->e_class > 0)
+ e->e_timeoutclass = TOC_URGENT;
+ if (full)
+ {
+ e->e_msgpriority = e->e_msgsize
+ - e->e_class * WkClassFact
+ + e->e_nrcpts * WkRecipFact;
+ }
+
+ /* message timeout priority */
+ p = hvalue("priority", e->e_header);
+ if (p != NULL)
+ {
+ /* (this should be in the configuration file) */
+ if (sm_strcasecmp(p, "urgent") == 0)
+ e->e_timeoutclass = TOC_URGENT;
+ else if (sm_strcasecmp(p, "normal") == 0)
+ e->e_timeoutclass = TOC_NORMAL;
+ else if (sm_strcasecmp(p, "non-urgent") == 0)
+ e->e_timeoutclass = TOC_NONURGENT;
+ }
+
+ /* date message originated */
+ p = hvalue("posted-date", e->e_header);
+ if (p == NULL)
+ p = hvalue("date", e->e_header);
+ if (p != NULL)
+ macdefine(&e->e_macro, A_PERM, 'a', p);
+
+ /* check to see if this is a MIME message */
+ if ((e->e_bodytype != NULL &&
+ sm_strcasecmp(e->e_bodytype, "8BITMIME") == 0) ||
+ hvalue("MIME-Version", e->e_header) != NULL)
+ {
+ e->e_flags |= EF_IS_MIME;
+ if (HasEightBits)
+ e->e_bodytype = "8BITMIME";
+ }
+ else if ((p = hvalue("Content-Type", e->e_header)) != NULL)
+ {
+ /* this may be an RFC 1049 message */
+ p = strpbrk(p, ";/");
+ if (p == NULL || *p == ';')
+ {
+ /* yep, it is */
+ e->e_flags |= EF_DONT_MIME;
+ }
+ }
+
+ /*
+ ** From person in antiquated ARPANET mode
+ ** required by UK Grey Book e-mail gateways (sigh)
+ */
+
+ if (OpMode == MD_ARPAFTP)
+ {
+ register struct hdrinfo *hi;
+
+ for (hi = HdrInfo; hi->hi_field != NULL; hi++)
+ {
+ if (bitset(H_FROM, hi->hi_flags) &&
+ (!bitset(H_RESENT, hi->hi_flags) ||
+ bitset(EF_RESENT, e->e_flags)) &&
+ (p = hvalue(hi->hi_field, e->e_header)) != NULL)
+ break;
+ }
+ if (hi->hi_field != NULL)
+ {
+ if (tTd(32, 2))
+ sm_dprintf("eatheader: setsender(*%s == %s)\n",
+ hi->hi_field, p);
+ setsender(p, e, NULL, '\0', true);
+ }
+ }
+
+ /*
+ ** Log collection information.
+ */
+
+ if (log && bitset(EF_LOGSENDER, e->e_flags) && LogLevel > 4)
+ {
+ logsender(e, e->e_msgid);
+ e->e_flags &= ~EF_LOGSENDER;
+ }
+}
+/*
+** LOGSENDER -- log sender information
+**
+** Parameters:
+** e -- the envelope to log
+** msgid -- the message id
+**
+** Returns:
+** none
+*/
+
+void
+logsender(e, msgid)
+ register ENVELOPE *e;
+ char *msgid;
+{
+ char *name;
+ register char *sbp;
+ register char *p;
+ int l;
+ char hbuf[MAXNAME + 1];
+ char sbuf[MAXLINE + 1];
+ char mbuf[MAXNAME + 1];
+
+ /* don't allow newlines in the message-id */
+ /* XXX do we still need this? sm_syslog() replaces control chars */
+ if (msgid != NULL)
+ {
+ l = strlen(msgid);
+ if (l > sizeof mbuf - 1)
+ l = sizeof mbuf - 1;
+ memmove(mbuf, msgid, l);
+ mbuf[l] = '\0';
+ p = mbuf;
+ while ((p = strchr(p, '\n')) != NULL)
+ *p++ = ' ';
+ }
+
+ if (bitset(EF_RESPONSE, e->e_flags))
+ name = "[RESPONSE]";
+ else if ((name = macvalue('_', e)) != NULL)
+ /* EMPTY */
+ ;
+ else if (RealHostName == NULL)
+ name = "localhost";
+ else if (RealHostName[0] == '[')
+ name = RealHostName;
+ else
+ {
+ name = hbuf;
+ (void) sm_snprintf(hbuf, sizeof hbuf, "%.80s", RealHostName);
+ if (RealHostAddr.sa.sa_family != 0)
+ {
+ p = &hbuf[strlen(hbuf)];
+ (void) sm_snprintf(p, SPACELEFT(hbuf, p),
+ " (%.100s)",
+ anynet_ntoa(&RealHostAddr));
+ }
+ }
+
+ /* some versions of syslog only take 5 printf args */
+#if (SYSLOG_BUFSIZE) >= 256
+ sbp = sbuf;
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ "from=%.200s, size=%ld, class=%d, nrcpts=%d",
+ e->e_from.q_paddr == NULL ? "<NONE>" : e->e_from.q_paddr,
+ e->e_msgsize, e->e_class, e->e_nrcpts);
+ sbp += strlen(sbp);
+ if (msgid != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ ", msgid=%.100s", mbuf);
+ sbp += strlen(sbp);
+ }
+ if (e->e_bodytype != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ ", bodytype=%.20s", e->e_bodytype);
+ sbp += strlen(sbp);
+ }
+ p = macvalue('r', e);
+ if (p != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ ", proto=%.20s", p);
+ sbp += strlen(sbp);
+ }
+ p = macvalue(macid("{daemon_name}"), e);
+ if (p != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ ", daemon=%.20s", p);
+ sbp += strlen(sbp);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "%.850s, relay=%.100s", sbuf, name);
+
+#else /* (SYSLOG_BUFSIZE) >= 256 */
+
+ sm_syslog(LOG_INFO, e->e_id,
+ "from=%s",
+ e->e_from.q_paddr == NULL ? "<NONE>"
+ : shortenstring(e->e_from.q_paddr,
+ 83));
+ sm_syslog(LOG_INFO, e->e_id,
+ "size=%ld, class=%ld, nrcpts=%d",
+ e->e_msgsize, e->e_class, e->e_nrcpts);
+ if (msgid != NULL)
+ sm_syslog(LOG_INFO, e->e_id,
+ "msgid=%s",
+ shortenstring(mbuf, 83));
+ sbp = sbuf;
+ *sbp = '\0';
+ if (e->e_bodytype != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ "bodytype=%.20s, ", e->e_bodytype);
+ sbp += strlen(sbp);
+ }
+ p = macvalue('r', e);
+ if (p != NULL)
+ {
+ (void) sm_snprintf(sbp, SPACELEFT(sbuf, sbp),
+ "proto=%.20s, ", p);
+ sbp += strlen(sbp);
+ }
+ sm_syslog(LOG_INFO, e->e_id,
+ "%.400srelay=%.100s", sbuf, name);
+#endif /* (SYSLOG_BUFSIZE) >= 256 */
+}
+/*
+** PRIENCODE -- encode external priority names into internal values.
+**
+** Parameters:
+** p -- priority in ascii.
+**
+** Returns:
+** priority as a numeric level.
+**
+** Side Effects:
+** none.
+*/
+
+static int
+priencode(p)
+ char *p;
+{
+ register int i;
+
+ for (i = 0; i < NumPriorities; i++)
+ {
+ if (sm_strcasecmp(p, Priorities[i].pri_name) == 0)
+ return Priorities[i].pri_val;
+ }
+
+ /* unknown priority */
+ return 0;
+}
+/*
+** CRACKADDR -- parse an address and turn it into a macro
+**
+** This doesn't actually parse the address -- it just extracts
+** it and replaces it with "$g". The parse is totally ad hoc
+** and isn't even guaranteed to leave something syntactically
+** identical to what it started with. However, it does leave
+** something semantically identical.
+**
+** This algorithm has been cleaned up to handle a wider range
+** of cases -- notably quoted and backslash escaped strings.
+** This modification makes it substantially better at preserving
+** the original syntax.
+**
+** Parameters:
+** addr -- the address to be cracked.
+**
+** Returns:
+** a pointer to the new version.
+**
+** Side Effects:
+** none.
+**
+** Warning:
+** The return value is saved in local storage and should
+** be copied if it is to be reused.
+*/
+
+char *
+crackaddr(addr)
+ register char *addr;
+{
+ register char *p;
+ register char c;
+ int cmtlev;
+ int realcmtlev;
+ int anglelev, realanglelev;
+ int copylev;
+ int bracklev;
+ bool qmode;
+ bool realqmode;
+ bool skipping;
+ bool putgmac = false;
+ bool quoteit = false;
+ bool gotangle = false;
+ bool gotcolon = false;
+ register char *bp;
+ char *buflim;
+ char *bufhead;
+ char *addrhead;
+ static char buf[MAXNAME + 1];
+
+ if (tTd(33, 1))
+ sm_dprintf("crackaddr(%s)\n", addr);
+
+ /* strip leading spaces */
+ while (*addr != '\0' && isascii(*addr) && isspace(*addr))
+ addr++;
+
+ /*
+ ** Start by assuming we have no angle brackets. This will be
+ ** adjusted later if we find them.
+ */
+
+ bp = bufhead = buf;
+ buflim = &buf[sizeof buf - 7];
+ p = addrhead = addr;
+ copylev = anglelev = realanglelev = cmtlev = realcmtlev = 0;
+ bracklev = 0;
+ qmode = realqmode = false;
+
+ while ((c = *p++) != '\0')
+ {
+ /*
+ ** If the buffer is overful, go into a special "skipping"
+ ** mode that tries to keep legal syntax but doesn't actually
+ ** output things.
+ */
+
+ skipping = bp >= buflim;
+
+ if (copylev > 0 && !skipping)
+ *bp++ = c;
+
+ /* check for backslash escapes */
+ if (c == '\\')
+ {
+ /* arrange to quote the address */
+ if (cmtlev <= 0 && !qmode)
+ quoteit = true;
+
+ if ((c = *p++) == '\0')
+ {
+ /* too far */
+ p--;
+ goto putg;
+ }
+ if (copylev > 0 && !skipping)
+ *bp++ = c;
+ goto putg;
+ }
+
+ /* check for quoted strings */
+ if (c == '"' && cmtlev <= 0)
+ {
+ qmode = !qmode;
+ if (copylev > 0 && !skipping)
+ realqmode = !realqmode;
+ continue;
+ }
+ if (qmode)
+ goto putg;
+
+ /* check for comments */
+ if (c == '(')
+ {
+ cmtlev++;
+
+ /* allow space for closing paren */
+ if (!skipping)
+ {
+ buflim--;
+ realcmtlev++;
+ if (copylev++ <= 0)
+ {
+ if (bp != bufhead)
+ *bp++ = ' ';
+ *bp++ = c;
+ }
+ }
+ }
+ if (cmtlev > 0)
+ {
+ if (c == ')')
+ {
+ cmtlev--;
+ copylev--;
+ if (!skipping)
+ {
+ realcmtlev--;
+ buflim++;
+ }
+ }
+ continue;
+ }
+ else if (c == ')')
+ {
+ /* syntax error: unmatched ) */
+ if (copylev > 0 && !skipping)
+ bp--;
+ }
+
+ /* count nesting on [ ... ] (for IPv6 domain literals) */
+ if (c == '[')
+ bracklev++;
+ else if (c == ']')
+ bracklev--;
+
+ /* check for group: list; syntax */
+ if (c == ':' && anglelev <= 0 && bracklev <= 0 &&
+ !gotcolon && !ColonOkInAddr)
+ {
+ register char *q;
+
+ /*
+ ** Check for DECnet phase IV ``::'' (host::user)
+ ** or ** DECnet phase V ``:.'' syntaxes. The latter
+ ** covers ``user@DEC:.tay.myhost'' and
+ ** ``DEC:.tay.myhost::user'' syntaxes (bletch).
+ */
+
+ if (*p == ':' || *p == '.')
+ {
+ if (cmtlev <= 0 && !qmode)
+ quoteit = true;
+ if (copylev > 0 && !skipping)
+ {
+ *bp++ = c;
+ *bp++ = *p;
+ }
+ p++;
+ goto putg;
+ }
+
+ gotcolon = true;
+
+ bp = bufhead;
+ if (quoteit)
+ {
+ *bp++ = '"';
+
+ /* back up over the ':' and any spaces */
+ --p;
+ while (isascii(*--p) && isspace(*p))
+ continue;
+ p++;
+ }
+ for (q = addrhead; q < p; )
+ {
+ c = *q++;
+ if (bp < buflim)
+ {
+ if (quoteit && c == '"')
+ *bp++ = '\\';
+ *bp++ = c;
+ }
+ }
+ if (quoteit)
+ {
+ if (bp == &bufhead[1])
+ bp--;
+ else
+ *bp++ = '"';
+ while ((c = *p++) != ':')
+ {
+ if (bp < buflim)
+ *bp++ = c;
+ }
+ *bp++ = c;
+ }
+
+ /* any trailing white space is part of group: */
+ while (isascii(*p) && isspace(*p) && bp < buflim)
+ *bp++ = *p++;
+ copylev = 0;
+ putgmac = quoteit = false;
+ bufhead = bp;
+ addrhead = p;
+ continue;
+ }
+
+ if (c == ';' && copylev <= 0 && !ColonOkInAddr)
+ {
+ if (bp < buflim)
+ *bp++ = c;
+ }
+
+ /* check for characters that may have to be quoted */
+ if (strchr(MustQuoteChars, c) != NULL)
+ {
+ /*
+ ** If these occur as the phrase part of a <>
+ ** construct, but are not inside of () or already
+ ** quoted, they will have to be quoted. Note that
+ ** now (but don't actually do the quoting).
+ */
+
+ if (cmtlev <= 0 && !qmode)
+ quoteit = true;
+ }
+
+ /* check for angle brackets */
+ if (c == '<')
+ {
+ register char *q;
+
+ /* assume first of two angles is bogus */
+ if (gotangle)
+ quoteit = true;
+ gotangle = true;
+
+ /* oops -- have to change our mind */
+ anglelev = 1;
+ if (!skipping)
+ realanglelev = 1;
+
+ bp = bufhead;
+ if (quoteit)
+ {
+ *bp++ = '"';
+
+ /* back up over the '<' and any spaces */
+ --p;
+ while (isascii(*--p) && isspace(*p))
+ continue;
+ p++;
+ }
+ for (q = addrhead; q < p; )
+ {
+ c = *q++;
+ if (bp < buflim)
+ {
+ if (quoteit && c == '"')
+ *bp++ = '\\';
+ *bp++ = c;
+ }
+ }
+ if (quoteit)
+ {
+ if (bp == &buf[1])
+ bp--;
+ else
+ *bp++ = '"';
+ while ((c = *p++) != '<')
+ {
+ if (bp < buflim)
+ *bp++ = c;
+ }
+ *bp++ = c;
+ }
+ copylev = 0;
+ putgmac = quoteit = false;
+ continue;
+ }
+
+ if (c == '>')
+ {
+ if (anglelev > 0)
+ {
+ anglelev--;
+ if (!skipping)
+ {
+ realanglelev--;
+ buflim++;
+ }
+ }
+ else if (!skipping)
+ {
+ /* syntax error: unmatched > */
+ if (copylev > 0)
+ bp--;
+ quoteit = true;
+ continue;
+ }
+ if (copylev++ <= 0)
+ *bp++ = c;
+ continue;
+ }
+
+ /* must be a real address character */
+ putg:
+ if (copylev <= 0 && !putgmac)
+ {
+ if (bp > bufhead && bp[-1] == ')')
+ *bp++ = ' ';
+ *bp++ = MACROEXPAND;
+ *bp++ = 'g';
+ putgmac = true;
+ }
+ }
+
+ /* repair any syntactic damage */
+ if (realqmode)
+ *bp++ = '"';
+ while (realcmtlev-- > 0)
+ *bp++ = ')';
+ while (realanglelev-- > 0)
+ *bp++ = '>';
+ *bp++ = '\0';
+
+ if (tTd(33, 1))
+ {
+ sm_dprintf("crackaddr=>`");
+ xputs(buf);
+ sm_dprintf("'\n");
+ }
+
+ return buf;
+}
+/*
+** PUTHEADER -- put the header part of a message from the in-core copy
+**
+** Parameters:
+** mci -- the connection information.
+** hdr -- the header to put.
+** e -- envelope to use.
+** flags -- MIME conversion flags.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+void
+putheader(mci, hdr, e, flags)
+ register MCI *mci;
+ HDR *hdr;
+ register ENVELOPE *e;
+ int flags;
+{
+ register HDR *h;
+ char buf[SM_MAX(MAXLINE,BUFSIZ)];
+ char obuf[MAXLINE];
+
+ if (tTd(34, 1))
+ sm_dprintf("--- putheader, mailer = %s ---\n",
+ mci->mci_mailer->m_name);
+
+ /*
+ ** If we're in MIME mode, we're not really in the header of the
+ ** message, just the header of one of the parts of the body of
+ ** the message. Therefore MCIF_INHEADER should not be turned on.
+ */
+
+ if (!bitset(MCIF_INMIME, mci->mci_flags))
+ mci->mci_flags |= MCIF_INHEADER;
+
+ for (h = hdr; h != NULL; h = h->h_link)
+ {
+ register char *p = h->h_value;
+ char *q;
+
+ if (tTd(34, 11))
+ {
+ sm_dprintf(" %s: ", h->h_field);
+ xputs(p);
+ }
+
+ /* Skip empty headers */
+ if (h->h_value == NULL)
+ continue;
+
+ /* heuristic shortening of MIME fields to avoid MUA overflows */
+ if (MaxMimeFieldLength > 0 &&
+ wordinclass(h->h_field,
+ macid("{checkMIMEFieldHeaders}")))
+ {
+ size_t len;
+
+ len = fix_mime_header(h->h_value);
+ if (len > 0)
+ {
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Truncated MIME %s header due to field size (length = %ld) (possible attack)",
+ h->h_field, (unsigned long) len);
+ if (tTd(34, 11))
+ sm_dprintf(" truncated MIME %s header due to field size (length = %ld) (possible attack)\n",
+ h->h_field,
+ (unsigned long) len);
+ }
+ }
+
+ if (MaxMimeHeaderLength > 0 &&
+ wordinclass(h->h_field,
+ macid("{checkMIMETextHeaders}")))
+ {
+ size_t len;
+
+ len = strlen(h->h_value);
+ if (len > (size_t) MaxMimeHeaderLength)
+ {
+ h->h_value[MaxMimeHeaderLength - 1] = '\0';
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Truncated long MIME %s header (length = %ld) (possible attack)",
+ h->h_field, (unsigned long) len);
+ if (tTd(34, 11))
+ sm_dprintf(" truncated long MIME %s header (length = %ld) (possible attack)\n",
+ h->h_field,
+ (unsigned long) len);
+ }
+ }
+
+ if (MaxMimeHeaderLength > 0 &&
+ wordinclass(h->h_field,
+ macid("{checkMIMEHeaders}")))
+ {
+ size_t len;
+
+ len = strlen(h->h_value);
+ if (shorten_rfc822_string(h->h_value,
+ MaxMimeHeaderLength))
+ {
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Truncated long MIME %s header (length = %ld) (possible attack)",
+ h->h_field, (unsigned long) len);
+ if (tTd(34, 11))
+ sm_dprintf(" truncated long MIME %s header (length = %ld) (possible attack)\n",
+ h->h_field,
+ (unsigned long) len);
+ }
+ }
+
+ /*
+ ** Suppress Content-Transfer-Encoding: if we are MIMEing
+ ** and we are potentially converting from 8 bit to 7 bit
+ ** MIME. If converting, add a new CTE header in
+ ** mime8to7().
+ */
+
+ if (bitset(H_CTE, h->h_flags) &&
+ bitset(MCIF_CVT8TO7|MCIF_CVT7TO8|MCIF_INMIME,
+ mci->mci_flags) &&
+ !bitset(M87F_NO8TO7, flags))
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped (content-transfer-encoding))\n");
+ continue;
+ }
+
+ if (bitset(MCIF_INMIME, mci->mci_flags))
+ {
+ if (tTd(34, 11))
+ sm_dprintf("\n");
+ put_vanilla_header(h, p, mci);
+ continue;
+ }
+
+ if (bitset(H_CHECK|H_ACHECK, h->h_flags) &&
+ !bitintersect(h->h_mflags, mci->mci_mailer->m_flags) &&
+ (h->h_macro == '\0' ||
+ (q = macvalue(bitidx(h->h_macro), e)) == NULL ||
+ *q == '\0'))
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped)\n");
+ continue;
+ }
+
+ /* handle Resent-... headers specially */
+ if (bitset(H_RESENT, h->h_flags) && !bitset(EF_RESENT, e->e_flags))
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped (resent))\n");
+ continue;
+ }
+
+ /* suppress return receipts if requested */
+ if (bitset(H_RECEIPTTO, h->h_flags) &&
+ (RrtImpliesDsn || bitset(EF_NORECEIPT, e->e_flags)))
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped (receipt))\n");
+ continue;
+ }
+
+ /* macro expand value if generated internally */
+ if (bitset(H_DEFAULT, h->h_flags) ||
+ bitset(H_BINDLATE, h->h_flags))
+ {
+ expand(p, buf, sizeof buf, e);
+ p = buf;
+ if (*p == '\0')
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped -- null value)\n");
+ continue;
+ }
+ }
+
+ if (bitset(H_BCC, h->h_flags))
+ {
+ /* Bcc: field -- either truncate or delete */
+ if (bitset(EF_DELETE_BCC, e->e_flags))
+ {
+ if (tTd(34, 11))
+ sm_dprintf(" (skipped -- bcc)\n");
+ }
+ else
+ {
+ /* no other recipient headers: truncate value */
+ (void) sm_strlcpyn(obuf, sizeof obuf, 2,
+ h->h_field, ":");
+ putline(obuf, mci);
+ }
+ continue;
+ }
+
+ if (tTd(34, 11))
+ sm_dprintf("\n");
+
+ if (bitset(H_FROM|H_RCPT, h->h_flags))
+ {
+ /* address field */
+ bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);
+
+ if (bitset(H_FROM, h->h_flags))
+ oldstyle = false;
+ commaize(h, p, oldstyle, mci, e);
+ }
+ else
+ {
+ put_vanilla_header(h, p, mci);
+ }
+ }
+
+ /*
+ ** If we are converting this to a MIME message, add the
+ ** MIME headers (but not in MIME mode!).
+ */
+
+#if MIME8TO7
+ if (bitset(MM_MIME8BIT, MimeMode) &&
+ bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ !bitnset(M_8BITS, mci->mci_mailer->m_flags) &&
+ !bitset(MCIF_CVT8TO7|MCIF_CVT7TO8|MCIF_INMIME, mci->mci_flags) &&
+ hvalue("MIME-Version", e->e_header) == NULL)
+ {
+ putline("MIME-Version: 1.0", mci);
+ if (hvalue("Content-Type", e->e_header) == NULL)
+ {
+ (void) sm_snprintf(obuf, sizeof obuf,
+ "Content-Type: text/plain; charset=%s",
+ defcharset(e));
+ putline(obuf, mci);
+ }
+ if (hvalue("Content-Transfer-Encoding", e->e_header) == NULL)
+ putline("Content-Transfer-Encoding: 8bit", mci);
+ }
+#endif /* MIME8TO7 */
+}
+/*
+** PUT_VANILLA_HEADER -- output a fairly ordinary header
+**
+** Parameters:
+** h -- the structure describing this header
+** v -- the value of this header
+** mci -- the connection info for output
+**
+** Returns:
+** none.
+*/
+
+static void
+put_vanilla_header(h, v, mci)
+ HDR *h;
+ char *v;
+ MCI *mci;
+{
+ register char *nlp;
+ register char *obp;
+ int putflags;
+ char obuf[MAXLINE];
+
+ putflags = PXLF_HEADER;
+ if (bitnset(M_7BITHDRS, mci->mci_mailer->m_flags))
+ putflags |= PXLF_STRIP8BIT;
+ (void) sm_snprintf(obuf, sizeof obuf, "%.200s: ", h->h_field);
+ obp = obuf + strlen(obuf);
+ while ((nlp = strchr(v, '\n')) != NULL)
+ {
+ int l;
+
+ l = nlp - v;
+ if (SPACELEFT(obuf, obp) - 1 < (size_t) l)
+ l = SPACELEFT(obuf, obp) - 1;
+
+ (void) sm_snprintf(obp, SPACELEFT(obuf, obp), "%.*s", l, v);
+ putxline(obuf, strlen(obuf), mci, putflags);
+ v += l + 1;
+ obp = obuf;
+ if (*v != ' ' && *v != '\t')
+ *obp++ = ' ';
+ }
+ (void) sm_snprintf(obp, SPACELEFT(obuf, obp), "%.*s",
+ (int) (SPACELEFT(obuf, obp) - 1), v);
+ putxline(obuf, strlen(obuf), mci, putflags);
+}
+/*
+** COMMAIZE -- output a header field, making a comma-translated list.
+**
+** Parameters:
+** h -- the header field to output.
+** p -- the value to put in it.
+** oldstyle -- true if this is an old style header.
+** mci -- the connection information.
+** e -- the envelope containing the message.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** outputs "p" to file "fp".
+*/
+
+void
+commaize(h, p, oldstyle, mci, e)
+ register HDR *h;
+ register char *p;
+ bool oldstyle;
+ register MCI *mci;
+ register ENVELOPE *e;
+{
+ register char *obp;
+ int opos;
+ int omax;
+ bool firstone = true;
+ int putflags = PXLF_HEADER;
+ char obuf[MAXLINE + 3];
+
+ /*
+ ** Output the address list translated by the
+ ** mailer and with commas.
+ */
+
+ if (tTd(14, 2))
+ sm_dprintf("commaize(%s: %s)\n", h->h_field, p);
+
+ if (bitnset(M_7BITHDRS, mci->mci_mailer->m_flags))
+ putflags |= PXLF_STRIP8BIT;
+
+ obp = obuf;
+ (void) sm_snprintf(obp, SPACELEFT(obuf, obp), "%.200s: ",
+ h->h_field);
+ opos = strlen(h->h_field) + 2;
+ if (opos > 202)
+ opos = 202;
+ obp += opos;
+ omax = mci->mci_mailer->m_linelimit - 2;
+ if (omax < 0 || omax > 78)
+ omax = 78;
+
+ /*
+ ** Run through the list of values.
+ */
+
+ while (*p != '\0')
+ {
+ register char *name;
+ register int c;
+ char savechar;
+ int flags;
+ auto int status;
+
+ /*
+ ** Find the end of the name. New style names
+ ** end with a comma, old style names end with
+ ** a space character. However, spaces do not
+ ** necessarily delimit an old-style name -- at
+ ** signs mean keep going.
+ */
+
+ /* find end of name */
+ while ((isascii(*p) && isspace(*p)) || *p == ',')
+ p++;
+ name = p;
+ for (;;)
+ {
+ auto char *oldp;
+ char pvpbuf[PSBUFSIZE];
+
+ (void) prescan(p, oldstyle ? ' ' : ',', pvpbuf,
+ sizeof pvpbuf, &oldp, NULL);
+ p = oldp;
+
+ /* look to see if we have an at sign */
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+
+ if (*p != '@')
+ {
+ p = oldp;
+ break;
+ }
+ ++p;
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ }
+ /* at the end of one complete name */
+
+ /* strip off trailing white space */
+ while (p >= name &&
+ ((isascii(*p) && isspace(*p)) || *p == ',' || *p == '\0'))
+ p--;
+ if (++p == name)
+ continue;
+ savechar = *p;
+ *p = '\0';
+
+ /* translate the name to be relative */
+ flags = RF_HEADERADDR|RF_ADDDOMAIN;
+ if (bitset(H_FROM, h->h_flags))
+ flags |= RF_SENDERADDR;
+#if USERDB
+ else if (e->e_from.q_mailer != NULL &&
+ bitnset(M_UDBRECIPIENT, e->e_from.q_mailer->m_flags))
+ {
+ char *q;
+
+ q = udbsender(name, e->e_rpool);
+ if (q != NULL)
+ name = q;
+ }
+#endif /* USERDB */
+ status = EX_OK;
+ name = remotename(name, mci->mci_mailer, flags, &status, e);
+ if (*name == '\0')
+ {
+ *p = savechar;
+ continue;
+ }
+ name = denlstring(name, false, true);
+
+ /*
+ ** record data progress so DNS timeouts
+ ** don't cause DATA timeouts
+ */
+
+ DataProgress = true;
+
+ /* output the name with nice formatting */
+ opos += strlen(name);
+ if (!firstone)
+ opos += 2;
+ if (opos > omax && !firstone)
+ {
+ (void) sm_strlcpy(obp, ",\n", SPACELEFT(obuf, obp));
+ putxline(obuf, strlen(obuf), mci, putflags);
+ obp = obuf;
+ (void) sm_strlcpy(obp, " ", sizeof obp);
+ opos = strlen(obp);
+ obp += opos;
+ opos += strlen(name);
+ }
+ else if (!firstone)
+ {
+ (void) sm_strlcpy(obp, ", ", SPACELEFT(obuf, obp));
+ obp += 2;
+ }
+
+ while ((c = *name++) != '\0' && obp < &obuf[MAXLINE])
+ *obp++ = c;
+ firstone = false;
+ *p = savechar;
+ }
+ *obp = '\0';
+ putxline(obuf, strlen(obuf), mci, putflags);
+}
+/*
+** COPYHEADER -- copy header list
+**
+** This routine is the equivalent of newstr for header lists
+**
+** Parameters:
+** header -- list of header structures to copy.
+** rpool -- resource pool, or NULL
+**
+** Returns:
+** a copy of 'header'.
+**
+** Side Effects:
+** none.
+*/
+
+HDR *
+copyheader(header, rpool)
+ register HDR *header;
+ SM_RPOOL_T *rpool;
+{
+ register HDR *newhdr;
+ HDR *ret;
+ register HDR **tail = &ret;
+
+ while (header != NULL)
+ {
+ newhdr = (HDR *) sm_rpool_malloc_x(rpool, sizeof *newhdr);
+ STRUCTCOPY(*header, *newhdr);
+ *tail = newhdr;
+ tail = &newhdr->h_link;
+ header = header->h_link;
+ }
+ *tail = NULL;
+
+ return ret;
+}
+/*
+** FIX_MIME_HEADER -- possibly truncate/rebalance parameters in a MIME header
+**
+** Run through all of the parameters of a MIME header and
+** possibly truncate and rebalance the parameter according
+** to MaxMimeFieldLength.
+**
+** Parameters:
+** string -- the full header
+**
+** Returns:
+** length of last offending field, 0 if all ok.
+**
+** Side Effects:
+** string modified in place
+*/
+
+static size_t
+fix_mime_header(string)
+ char *string;
+{
+ char *begin = string;
+ char *end;
+ size_t len = 0;
+ size_t retlen = 0;
+
+ if (string == NULL || *string == '\0')
+ return 0;
+
+ /* Split on each ';' */
+ while ((end = find_character(begin, ';')) != NULL)
+ {
+ char save = *end;
+ char *bp;
+
+ *end = '\0';
+
+ len = strlen(begin);
+
+ /* Shorten individual parameter */
+ if (shorten_rfc822_string(begin, MaxMimeFieldLength))
+ retlen = len;
+
+ /* Collapse the possibly shortened string with rest */
+ bp = begin + strlen(begin);
+ if (bp != end)
+ {
+ char *ep = end;
+
+ *end = save;
+ end = bp;
+
+ /* copy character by character due to overlap */
+ while (*ep != '\0')
+ *bp++ = *ep++;
+ *bp = '\0';
+ }
+ else
+ *end = save;
+ if (*end == '\0')
+ break;
+
+ /* Move past ';' */
+ begin = end + 1;
+ }
+ return retlen;
+}
diff --git a/contrib/sendmail/src/helpfile b/contrib/sendmail/src/helpfile
new file mode 100644
index 0000000..931a06e
--- /dev/null
+++ b/contrib/sendmail/src/helpfile
@@ -0,0 +1,136 @@
+#vers 2
+cpyr
+cpyr Copyright (c) 1998-2000, 2002 Sendmail, Inc. and its suppliers.
+cpyr All rights reserved.
+cpyr Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+cpyr Copyright (c) 1988, 1993
+cpyr The Regents of the University of California. All rights reserved.
+cpyr
+cpyr
+cpyr By using this file, you agree to the terms and conditions set
+cpyr forth in the LICENSE file which can be found at the top level of
+cpyr the sendmail distribution.
+cpyr
+cpyr $$Id: helpfile,v 8.40 2002/03/19 00:23:28 gshapiro Exp $$
+cpyr
+smtp This is sendmail version $v
+smtp Topics:
+smtp HELO EHLO MAIL RCPT DATA
+smtp RSET NOOP QUIT HELP VRFY
+smtp EXPN VERB ETRN DSN AUTH
+smtp STARTTLS
+smtp For more info use "HELP <topic>".
+smtp To report bugs in the implementation send email to
+smtp sendmail-bugs@sendmail.org.
+smtp For local information send email to Postmaster at your site.
+help HELP [ <topic> ]
+help The HELP command gives help info.
+helo HELO <hostname>
+helo Introduce yourself.
+ehlo EHLO <hostname>
+ehlo Introduce yourself, and request extended SMTP mode.
+ehlo Possible replies include:
+ehlo SEND Send as mail [RFC821]
+ehlo SOML Send as mail or terminal [RFC821]
+ehlo SAML Send as mail and terminal [RFC821]
+ehlo EXPN Expand the mailing list [RFC821]
+ehlo HELP Supply helpful information [RFC821]
+ehlo TURN Turn the operation around [RFC821]
+ehlo 8BITMIME Use 8-bit data [RFC1652]
+ehlo SIZE Message size declaration [RFC1870]
+ehlo VERB Verbose [Allman]
+ehlo CHUNKING Chunking [RFC1830]
+ehlo BINARYMIME Binary MIME [RFC1830]
+ehlo PIPELINING Command Pipelining [RFC1854]
+ehlo DSN Delivery Status Notification [RFC1891]
+ehlo ETRN Remote Message Queue Starting [RFC1985]
+ehlo STARTTLS Secure SMTP [RFC2487]
+ehlo AUTH Authentication [RFC2554]
+ehlo ENHANCEDSTATUSCODES Enhanced status codes [RFC2034]
+ehlo DELIVERBY Deliver By [RFC2852]
+mail MAIL FROM: <sender> [ <parameters> ]
+mail Specifies the sender. Parameters are ESMTP extensions.
+mail See "HELP DSN" for details.
+rcpt RCPT TO: <recipient> [ <parameters> ]
+rcpt Specifies the recipient. Can be used any number of times.
+rcpt Parameters are ESMTP extensions. See "HELP DSN" for details.
+data DATA
+data Following text is collected as the message.
+data End with a single dot.
+rset RSET
+rset Resets the system.
+quit QUIT
+quit Exit sendmail (SMTP).
+auth AUTH mechanism [initial-response]
+auth Start authentication.
+starttls STARTTLS
+starttls Start TLS negotiation.
+verb VERB
+verb Go into verbose mode. This sends 0xy responses that are
+verb not RFC821 standard (but should be) They are recognized
+verb by humans and other sendmail implementations.
+vrfy VRFY <recipient>
+vrfy Verify an address. If you want to see what it aliases
+vrfy to, use EXPN instead.
+expn EXPN <recipient>
+expn Expand an address. If the address indicates a mailing
+expn list, return the contents of that list.
+noop NOOP
+noop Do nothing.
+send SEND FROM: <sender>
+send replaces the MAIL command, and can be used to send
+send directly to a users terminal. Not supported in this
+send implementation.
+soml SOML FROM: <sender>
+soml Send or mail. If the user is logged in, send directly,
+soml otherwise mail. Not supported in this implementation.
+saml SAML FROM: <sender>
+saml Send and mail. Send directly to the user's terminal,
+saml and also mail a letter. Not supported in this
+saml implementation.
+turn TURN
+turn Reverses the direction of the connection. Not currently
+turn implemented.
+etrn ETRN [ <hostname> | @<domain> | #<queuename> ]
+etrn Run the queue for the specified <hostname>, or
+etrn all hosts within a given <domain>, or a specially-named
+etrn <queuename> (implementation-specific).
+dsn MAIL FROM: <sender> [ RET={ FULL | HDRS} ] [ ENVID=<envid> ]
+dsn RCPT TO: <recipient> [ NOTIFY={NEVER,SUCCESS,FAILURE,DELAY} ]
+dsn [ ORCPT=<recipient> ]
+dsn SMTP Delivery Status Notifications.
+dsn Descriptions:
+dsn RET Return either the full message or only headers.
+dsn ENVID Sender's "envelope identifier" for tracking.
+dsn NOTIFY When to send a DSN. Multiple options are OK, comma-
+dsn delimited. NEVER must appear by itself.
+dsn ORCPT Original recipient.
+-bt Help for test mode:
+-bt ? :this help message.
+-bt .Dmvalue :define macro `m' to `value'.
+-bt .Ccvalue :add `value' to class `c'.
+-bt =Sruleset :dump the contents of the indicated ruleset.
+-bt =M :display the known mailers.
+-bt -ddebug-spec :equivalent to the command-line -d debug flag.
+-bt $$m :print the value of macro $$m.
+-bt $$=c :print the contents of class $$=c.
+-bt /mx host :returns the MX records for `host'.
+-bt /parse address :parse address, returning the value of crackaddr, and
+-bt the parsed address.
+-bt /try mailer addr :rewrite address into the form it will have when
+-bt presented to the indicated mailer.
+-bt /tryflags flags :set flags used by parsing. The flags can be `H' for
+-bt Header or `E' for Envelope, and `S' for Sender or `R'
+-bt for Recipient. These can be combined, `HR' sets
+-bt flags for header recipients.
+-bt /canon hostname :try to canonify hostname.
+-bt /map mapname key :look up `key' in the indicated `mapname'.
+-bt /quit :quit address test mode.
+-bt rules addr :run the indicated address through the named rules.
+-bt Rules can be a comma separated list of rules.
+control Help for smcontrol:
+control help This message.
+control restart Restart sendmail.
+control shutdown Shutdown sendmail.
+control status Show sendmail status.
+control memdump Dump allocated memory list (for debugging only).
diff --git a/contrib/sendmail/src/macro.c b/contrib/sendmail/src/macro.c
new file mode 100644
index 0000000..fc7a2c2
--- /dev/null
+++ b/contrib/sendmail/src/macro.c
@@ -0,0 +1,594 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: macro.c,v 8.86 2001/09/11 04:05:14 gshapiro Exp $")
+
+#if MAXMACROID != (BITMAPBITS - 1)
+ ERROR Read the comment in conf.h
+#endif /* MAXMACROID != (BITMAPBITS - 1) */
+
+static char *MacroName[MAXMACROID + 1]; /* macro id to name table */
+int NextMacroId = 0240; /* codes for long named macros */
+
+/*
+** INITMACROS -- initialize the macro system
+**
+** This just involves defining some macros that are actually
+** used internally as metasymbols to be themselves.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** initializes several macros to be themselves.
+*/
+
+struct metamac MetaMacros[] =
+{
+ /* LHS pattern matching characters */
+ { '*', MATCHZANY }, { '+', MATCHANY }, { '-', MATCHONE },
+ { '=', MATCHCLASS }, { '~', MATCHNCLASS },
+
+ /* these are RHS metasymbols */
+ { '#', CANONNET }, { '@', CANONHOST }, { ':', CANONUSER },
+ { '>', CALLSUBR },
+
+ /* the conditional operations */
+ { '?', CONDIF }, { '|', CONDELSE }, { '.', CONDFI },
+
+ /* the hostname lookup characters */
+ { '[', HOSTBEGIN }, { ']', HOSTEND },
+ { '(', LOOKUPBEGIN }, { ')', LOOKUPEND },
+
+ /* miscellaneous control characters */
+ { '&', MACRODEXPAND },
+
+ { '\0', '\0' }
+};
+
+#define MACBINDING(name, mid) \
+ stab(name, ST_MACRO, ST_ENTER)->s_macro = mid; \
+ MacroName[mid] = name;
+
+void
+initmacros(e)
+ register ENVELOPE *e;
+{
+ register struct metamac *m;
+ register int c;
+ char buf[5];
+
+ for (m = MetaMacros; m->metaname != '\0'; m++)
+ {
+ buf[0] = m->metaval;
+ buf[1] = '\0';
+ macdefine(&e->e_macro, A_TEMP, m->metaname, buf);
+ }
+ buf[0] = MATCHREPL;
+ buf[2] = '\0';
+ for (c = '0'; c <= '9'; c++)
+ {
+ buf[1] = c;
+ macdefine(&e->e_macro, A_TEMP, c, buf);
+ }
+
+ /* set defaults for some macros sendmail will use later */
+ macdefine(&e->e_macro, A_PERM, 'n', "MAILER-DAEMON");
+
+ /* set up external names for some internal macros */
+ MACBINDING("opMode", MID_OPMODE);
+ /*XXX should probably add equivalents for all short macros here XXX*/
+}
+/*
+** EXPAND -- macro expand a string using $x escapes.
+**
+** Parameters:
+** s -- the string to expand.
+** buf -- the place to put the expansion.
+** bufsize -- the size of the buffer.
+** e -- envelope in which to work.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+void
+expand(s, buf, bufsize, e)
+ register char *s;
+ register char *buf;
+ size_t bufsize;
+ register ENVELOPE *e;
+{
+ register char *xp;
+ register char *q;
+ bool skipping; /* set if conditionally skipping output */
+ bool recurse; /* set if recursion required */
+ size_t i;
+ int skiplev; /* skipping nesting level */
+ int iflev; /* if nesting level */
+ char xbuf[MACBUFSIZE];
+ static int explevel = 0;
+
+ if (tTd(35, 24))
+ {
+ sm_dprintf("expand(");
+ xputs(s);
+ sm_dprintf(")\n");
+ }
+
+ recurse = false;
+ skipping = false;
+ skiplev = 0;
+ iflev = 0;
+ if (s == NULL)
+ s = "";
+ for (xp = xbuf; *s != '\0'; s++)
+ {
+ int c;
+
+ /*
+ ** Check for non-ordinary (special?) character.
+ ** 'q' will be the interpolated quantity.
+ */
+
+ q = NULL;
+ c = *s;
+ switch (c & 0377)
+ {
+ case CONDIF: /* see if var set */
+ iflev++;
+ c = *++s;
+ if (skipping)
+ skiplev++;
+ else
+ {
+ char *mv;
+
+ mv = macvalue(c, e);
+ skipping = (mv == NULL || *mv == '\0');
+ }
+ continue;
+
+ case CONDELSE: /* change state of skipping */
+ if (iflev == 0)
+ break; /* XXX: error */
+ if (skiplev == 0)
+ skipping = !skipping;
+ continue;
+
+ case CONDFI: /* stop skipping */
+ if (iflev == 0)
+ break; /* XXX: error */
+ iflev--;
+ if (skiplev == 0)
+ skipping = false;
+ if (skipping)
+ skiplev--;
+ continue;
+
+ case MACROEXPAND: /* macro interpolation */
+ c = bitidx(*++s);
+ if (c != '\0')
+ q = macvalue(c, e);
+ else
+ {
+ s--;
+ q = NULL;
+ }
+ if (q == NULL)
+ continue;
+ break;
+ }
+
+ /*
+ ** Interpolate q or output one character
+ */
+
+ if (skipping || xp >= &xbuf[sizeof xbuf - 1])
+ continue;
+ if (q == NULL)
+ *xp++ = c;
+ else
+ {
+ /* copy to end of q or max space remaining in buf */
+ while ((c = *q++) != '\0' && xp < &xbuf[sizeof xbuf - 1])
+ {
+ /* check for any sendmail metacharacters */
+ if ((c & 0340) == 0200)
+ recurse = true;
+ *xp++ = c;
+ }
+ }
+ }
+ *xp = '\0';
+
+ if (tTd(35, 24))
+ {
+ sm_dprintf("expand ==> ");
+ xputs(xbuf);
+ sm_dprintf("\n");
+ }
+
+ /* recurse as appropriate */
+ if (recurse)
+ {
+ if (explevel < MaxMacroRecursion)
+ {
+ explevel++;
+ expand(xbuf, buf, bufsize, e);
+ explevel--;
+ return;
+ }
+ syserr("expand: recursion too deep (%d max)",
+ MaxMacroRecursion);
+ }
+
+ /* copy results out */
+ i = xp - xbuf;
+ if (i >= bufsize)
+ i = bufsize - 1;
+ memmove(buf, xbuf, i);
+ buf[i] = '\0';
+}
+
+/*
+** MACDEFINE -- bind a macro name to a value
+**
+** Set a macro to a value, with fancy storage management.
+** macdefine will make a copy of the value, if required,
+** and will ensure that the storage for the previous value
+** is not leaked.
+**
+** Parameters:
+** mac -- Macro table.
+** vclass -- storage class of 'value', ignored if value==NULL.
+** A_HEAP means that the value was allocated by
+** malloc, and that macdefine owns the storage.
+** A_TEMP means that value points to temporary storage,
+** and thus macdefine needs to make a copy.
+** A_PERM means that value points to storage that
+** will remain allocated and unchanged for
+** at least the lifetime of mac. Use A_PERM if:
+** -- value == NULL,
+** -- value points to a string literal,
+** -- value was allocated from mac->mac_rpool
+** or (in the case of an envelope macro)
+** from e->e_rpool,
+** -- in the case of an envelope macro,
+** value is a string member of the envelope
+** such as e->e_sender.
+** id -- Macro id. This is a single character macro name
+** such as 'g', or a value returned by macid().
+** value -- Macro value: either NULL, or a string.
+*/
+
+void
+#if SM_HEAP_CHECK
+macdefine_tagged(mac, vclass, id, value, file, line, grp)
+#else /* SM_HEAP_CHECK */
+macdefine(mac, vclass, id, value)
+#endif /* SM_HEAP_CHECK */
+ MACROS_T *mac;
+ ARGCLASS_T vclass;
+ int id;
+ char *value;
+#if SM_HEAP_CHECK
+ char *file;
+ int line;
+ int grp;
+#endif /* SM_HEAP_CHECK */
+{
+ char *newvalue;
+
+ if (id < 0 || id > MAXMACROID)
+ return;
+
+ if (tTd(35, 9))
+ {
+ sm_dprintf("%sdefine(%s as ",
+ mac->mac_table[id] == NULL ? "" : "re", macname(id));
+ xputs(value);
+ sm_dprintf(")\n");
+ }
+
+ if (mac->mac_rpool == NULL)
+ {
+ char *freeit = NULL;
+
+ if (mac->mac_table[id] != NULL &&
+ bitnset(id, mac->mac_allocated))
+ freeit = mac->mac_table[id];
+
+ if (value == NULL || vclass == A_HEAP)
+ {
+ sm_heap_checkptr_tagged(value, file, line);
+ newvalue = value;
+ clrbitn(id, mac->mac_allocated);
+ }
+ else
+ {
+ newvalue = sm_strdup_tagged_x(value, file, line, 0);
+ setbitn(id, mac->mac_allocated);
+ }
+ mac->mac_table[id] = newvalue;
+ if (freeit != NULL)
+ sm_free(freeit);
+ }
+ else
+ {
+ if (value == NULL || vclass == A_PERM)
+ newvalue = value;
+ else
+ newvalue = sm_rpool_strdup_x(mac->mac_rpool, value);
+ mac->mac_table[id] = newvalue;
+ if (vclass == A_HEAP)
+ sm_free(value);
+ }
+
+#if _FFR_RESET_MACRO_GLOBALS
+ switch (id)
+ {
+ case 'j':
+ PSTRSET(MyHostName, value);
+ break;
+ }
+#endif /* _FFR_RESET_MACRO_GLOBALS */
+}
+
+/*
+** MACSET -- set a named macro to a value (low level)
+**
+** No fancy storage management; the caller takes full responsibility.
+** Often used with macget; see also macdefine.
+**
+** Parameters:
+** mac -- Macro table.
+** i -- Macro name, specified as an integer offset.
+** value -- Macro value: either NULL, or a string.
+*/
+
+void
+macset(mac, i, value)
+ MACROS_T *mac;
+ int i;
+ char *value;
+{
+ if (i < 0 || i > MAXMACROID)
+ return;
+
+ if (tTd(35, 9))
+ {
+ sm_dprintf("macset(%s as ", macname(i));
+ xputs(value);
+ sm_dprintf(")\n");
+ }
+ mac->mac_table[i] = value;
+}
+
+/*
+** MACVALUE -- return uninterpreted value of a macro.
+**
+** Does fancy path searching.
+** The low level counterpart is macget.
+**
+** Parameters:
+** n -- the name of the macro.
+** e -- envelope in which to start looking for the macro.
+**
+** Returns:
+** The value of n.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+macvalue(n, e)
+ int n;
+ register ENVELOPE *e;
+{
+ n = bitidx(n);
+ if (e != NULL && e->e_mci != NULL)
+ {
+ register char *p = e->e_mci->mci_macro.mac_table[n];
+
+ if (p != NULL)
+ return p;
+ }
+ while (e != NULL)
+ {
+ register char *p = e->e_macro.mac_table[n];
+
+ if (p != NULL)
+ return p;
+ if (e == e->e_parent)
+ break;
+ e = e->e_parent;
+ }
+ return GlobalMacros.mac_table[n];
+}
+/*
+** MACNAME -- return the name of a macro given its internal id
+**
+** Parameter:
+** n -- the id of the macro
+**
+** Returns:
+** The name of n.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+macname(n)
+ int n;
+{
+ static char mbuf[2];
+
+ n = bitidx(n);
+ if (bitset(0200, n))
+ {
+ char *p = MacroName[n];
+
+ if (p != NULL)
+ return p;
+ return "***UNDEFINED MACRO***";
+ }
+ mbuf[0] = n;
+ mbuf[1] = '\0';
+ return mbuf;
+}
+/*
+** MACID_PARSE -- return id of macro identified by its name
+**
+** Parameters:
+** p -- pointer to name string -- either a single
+** character or {name}.
+** ep -- filled in with the pointer to the byte
+** after the name.
+**
+** Returns:
+** 0 -- An error was detected.
+** 1..255 -- The internal id code for this macro.
+**
+** Side Effects:
+** If this is a new macro name, a new id is allocated.
+** On error, syserr is called.
+*/
+
+int
+macid_parse(p, ep)
+ register char *p;
+ char **ep;
+{
+ int mid;
+ register char *bp;
+ char mbuf[MAXMACNAMELEN + 1];
+
+ if (tTd(35, 14))
+ {
+ sm_dprintf("macid(");
+ xputs(p);
+ sm_dprintf(") => ");
+ }
+
+ if (*p == '\0' || (p[0] == '{' && p[1] == '}'))
+ {
+ syserr("Name required for macro/class");
+ if (ep != NULL)
+ *ep = p;
+ if (tTd(35, 14))
+ sm_dprintf("NULL\n");
+ return 0;
+ }
+ if (*p != '{')
+ {
+ /* the macro is its own code */
+ if (ep != NULL)
+ *ep = p + 1;
+ if (tTd(35, 14))
+ sm_dprintf("%c\n", bitidx(*p));
+ return bitidx(*p);
+ }
+ bp = mbuf;
+ while (*++p != '\0' && *p != '}' && bp < &mbuf[sizeof mbuf - 1])
+ {
+ if (isascii(*p) && (isalnum(*p) || *p == '_'))
+ *bp++ = *p;
+ else
+ syserr("Invalid macro/class character %c", *p);
+ }
+ *bp = '\0';
+ mid = -1;
+ if (*p == '\0')
+ {
+ syserr("Unbalanced { on %s", mbuf); /* missing } */
+ }
+ else if (*p != '}')
+ {
+ syserr("Macro/class name ({%s}) too long (%d chars max)",
+ mbuf, (int) (sizeof mbuf - 1));
+ }
+ else if (mbuf[1] == '\0')
+ {
+ /* ${x} == $x */
+ mid = bitidx(mbuf[0]);
+ p++;
+ }
+ else
+ {
+ register STAB *s;
+
+ s = stab(mbuf, ST_MACRO, ST_ENTER);
+ if (s->s_macro != 0)
+ mid = s->s_macro;
+ else
+ {
+ if (NextMacroId > MAXMACROID)
+ {
+ syserr("Macro/class {%s}: too many long names",
+ mbuf);
+ s->s_macro = -1;
+ }
+ else
+ {
+ MacroName[NextMacroId] = s->s_name;
+ s->s_macro = mid = NextMacroId++;
+ }
+ }
+ p++;
+ }
+ if (ep != NULL)
+ *ep = p;
+ if (mid < 0 || mid > MAXMACROID)
+ {
+ syserr("Unable to assign macro/class ID (mid = 0x%x)", mid);
+ if (tTd(35, 14))
+ sm_dprintf("NULL\n");
+ return 0;
+ }
+ if (tTd(35, 14))
+ sm_dprintf("0x%x\n", mid);
+ return mid;
+}
+/*
+** WORDINCLASS -- tell if a word is in a specific class
+**
+** Parameters:
+** str -- the name of the word to look up.
+** cl -- the class name.
+**
+** Returns:
+** true if str can be found in cl.
+** false otherwise.
+*/
+
+bool
+wordinclass(str, cl)
+ char *str;
+ int cl;
+{
+ register STAB *s;
+
+ s = stab(str, ST_CLASS, ST_FIND);
+ return s != NULL && bitnset(bitidx(cl), s->s_class);
+}
diff --git a/contrib/sendmail/src/mailq.1 b/contrib/sendmail/src/mailq.1
new file mode 100644
index 0000000..26d7096
--- /dev/null
+++ b/contrib/sendmail/src/mailq.1
@@ -0,0 +1,81 @@
+.\" Copyright (c) 1998-2000, 2002 Sendmail, Inc. and its suppliers.
+.\" All rights reserved.
+.\" Copyright (c) 1983, 1997 Eric P. Allman. All rights reserved.
+.\" Copyright (c) 1985, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" By using this file, you agree to the terms and conditions set
+.\" forth in the LICENSE file which can be found at the top level of
+.\" the sendmail distribution.
+.\"
+.\"
+.\" $Id: mailq.1,v 8.19 2002/04/12 05:07:58 gshapiro Exp $
+.\"
+.TH MAILQ 1 "$Date: 2000/12/23 19:37:48 $"
+.SH NAME
+mailq
+\- print the mail queue
+.SH SYNOPSIS
+.B mailq
+.RB [ \-Ac ]
+.RB [ \-v ]
+.SH DESCRIPTION
+.B Mailq
+prints a summary of the mail messages queued for future delivery.
+.PP
+The first line printed for each message
+shows the internal identifier used on this host
+for the message with a possible status character,
+the size of the message in bytes,
+the date and time the message was accepted into the queue,
+and the envelope sender of the message.
+The second line shows the error message that caused this message
+to be retained in the queue;
+it will not be present if the message is being processed
+for the first time.
+The status characters are either
+.B *
+to indicate the job is being processed;
+.B X
+to indicate that the load is too high to process the job; and
+.B -
+to indicate that the job is too young to process.
+The following lines show message recipients,
+one per line.
+.PP
+.B Mailq
+is identical to ``sendmail -bp''.
+.PP
+The relevant options are as follows:
+.TP
+.B \-Ac
+Show the mail submission queue specified in
+.I /etc/mail/submit.cf
+instead of the MTA queue specified in
+.IR /etc/mail/sendmail.cf .
+.TP
+.B \-v
+Print verbose information.
+This adds the priority of the message and
+a single character indicator (``+'' or blank)
+indicating whether a warning message has been sent
+on the first line of the message.
+Additionally, extra lines may be intermixed with the recipients
+indicating the ``controlling user'' information;
+this shows who will own any programs that are executed
+on behalf of this message
+and the name of the alias this command expanded from, if any.
+Moreover, status messages for each recipient are printed
+if available.
+.PP
+The
+.B mailq
+utility exits 0 on success, and >0 if an error occurs.
+.SH SEE ALSO
+sendmail(8)
+.SH HISTORY
+The
+.B mailq
+command appeared in
+4.0BSD.
+.\" $FreeBSD$
diff --git a/contrib/sendmail/src/main.c b/contrib/sendmail/src/main.c
new file mode 100644
index 0000000..2aff085
--- /dev/null
+++ b/contrib/sendmail/src/main.c
@@ -0,0 +1,4340 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#define _DEFINE
+#include <sendmail.h>
+#include <sm/xtrap.h>
+#include <sm/signal.h>
+
+#ifndef lint
+SM_UNUSED(static char copyright[]) =
+"@(#) Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.\n\
+ All rights reserved.\n\
+ Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.\n\
+ Copyright (c) 1988, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* ! lint */
+
+SM_RCSID("@(#)$Id: main.c,v 8.887.2.1 2002/08/04 17:36:06 gshapiro Exp $")
+
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+/* for getcfname() */
+#include <sendmail/pathnames.h>
+
+static SM_DEBUG_T
+DebugNoPRestart = SM_DEBUG_INITIALIZER("no_persistent_restart",
+ "@(#)$Debug: no_persistent_restart - don't restart, log only $");
+
+static void dump_class __P((STAB *, int));
+static void obsolete __P((char **));
+static void testmodeline __P((char *, ENVELOPE *));
+static char *getextenv __P((const char *));
+static void sm_printoptions __P((char **));
+static SIGFUNC_DECL intindebug __P((int));
+static SIGFUNC_DECL sighup __P((int));
+static SIGFUNC_DECL sigpipe __P((int));
+static SIGFUNC_DECL sigterm __P((int));
+#ifdef SIGUSR1
+static SIGFUNC_DECL sigusr1 __P((int));
+#endif /* SIGUSR1 */
+
+/*
+** SENDMAIL -- Post mail to a set of destinations.
+**
+** This is the basic mail router. All user mail programs should
+** call this routine to actually deliver mail. Sendmail in
+** turn calls a bunch of mail servers that do the real work of
+** delivering the mail.
+**
+** Sendmail is driven by settings read in from /etc/mail/sendmail.cf
+** (read by readcf.c).
+**
+** Usage:
+** /usr/lib/sendmail [flags] addr ...
+**
+** See the associated documentation for details.
+**
+** Authors:
+** Eric Allman, UCB/INGRES (until 10/81).
+** Britton-Lee, Inc., purveyors of fine
+** database computers (11/81 - 10/88).
+** International Computer Science Institute
+** (11/88 - 9/89).
+** UCB/Mammoth Project (10/89 - 7/95).
+** InReference, Inc. (8/95 - 1/97).
+** Sendmail, Inc. (1/98 - present).
+** The support of the my employers is gratefully acknowledged.
+** Few of them (Britton-Lee in particular) have had
+** anything to gain from my involvement in this project.
+**
+** Gregory Neil Shapiro,
+** Worcester Polytechnic Institute (until 3/98).
+** Sendmail, Inc. (3/98 - present).
+**
+** Claus Assmann,
+** Sendmail, Inc. (12/98 - present).
+*/
+
+char *FullName; /* sender's full name */
+ENVELOPE BlankEnvelope; /* a "blank" envelope */
+static ENVELOPE MainEnvelope; /* the envelope around the basic letter */
+ADDRESS NullAddress = /* a null address */
+ { "", "", NULL, "" };
+char *CommandLineArgs; /* command line args for pid file */
+bool Warn_Q_option = false; /* warn about Q option use */
+static int MissingFds = 0; /* bit map of fds missing on startup */
+char *Mbdb = "pw"; /* mailbox database defaults to /etc/passwd */
+
+#ifdef NGROUPS_MAX
+GIDSET_T InitialGidSet[NGROUPS_MAX];
+#endif /* NGROUPS_MAX */
+
+#define MAXCONFIGLEVEL 10 /* highest config version level known */
+
+#if SASL
+static sasl_callback_t srvcallbacks[] =
+{
+ { SASL_CB_VERIFYFILE, &safesaslfile, NULL },
+ { SASL_CB_PROXY_POLICY, &proxy_policy, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+#endif /* SASL */
+
+unsigned int SubmitMode;
+int SyslogPrefixLen; /* estimated length of syslog prefix */
+#define PIDLEN 6 /* pid length for computing SyslogPrefixLen */
+#ifndef SL_FUDGE
+# define SL_FUDGE 10 /* fudge offset for SyslogPrefixLen */
+#endif /* ! SL_FUDGE */
+#define SLDLL 8 /* est. length of default syslog label */
+
+
+/* Some options are dangerous to allow users to use in non-submit mode */
+#define CHECK_AGAINST_OPMODE(cmd) \
+{ \
+ if (extraprivs && \
+ OpMode != MD_DELIVER && OpMode != MD_SMTP && \
+ OpMode != MD_VERIFY && OpMode != MD_TEST) \
+ { \
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, \
+ "WARNING: Ignoring submission mode -%c option (not in submission mode)\n", \
+ (cmd)); \
+ break; \
+ } \
+ if (extraprivs && queuerun) \
+ { \
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, \
+ "WARNING: Ignoring submission mode -%c option with -q\n", \
+ (cmd)); \
+ break; \
+ } \
+}
+
+int
+main(argc, argv, envp)
+ int argc;
+ char **argv;
+ char **envp;
+{
+ register char *p;
+ char **av;
+ extern char Version[];
+ char *ep, *from;
+ STAB *st;
+ register int i;
+ int j;
+ int dp;
+ int fill_errno;
+ int qgrp = NOQGRP; /* queue group to process */
+ bool safecf = true;
+ BITMAP256 *p_flags = NULL; /* daemon flags */
+ bool warn_C_flag = false;
+ bool auth = true; /* whether to set e_auth_param */
+ char warn_f_flag = '\0';
+ bool run_in_foreground = false; /* -bD mode */
+ bool queuerun = false, debug = false;
+ struct passwd *pw;
+ struct hostent *hp;
+ char *nullserver = NULL;
+ char *authinfo = NULL;
+ char *sysloglabel = NULL; /* label for syslog */
+ char *conffile = NULL; /* name of .cf file */
+ char *queuegroup = NULL; /* queue group to process */
+#if _FFR_QUARANTINE
+ char *quarantining = NULL; /* quarantine queue items? */
+#endif /* _FFR_QUARANTINE */
+ bool extraprivs;
+ bool forged, negate;
+ bool queuepersistent = false; /* queue runner process runs forever */
+ bool foregroundqueue = false; /* queue run in foreground */
+ bool save_val; /* to save some bool var. */
+ int cftype; /* which cf file to use? */
+ static time_t starttime = 0; /* when was process started */
+ struct stat traf_st; /* for TrafficLog FIFO check */
+ char buf[MAXLINE];
+ char jbuf[MAXHOSTNAMELEN]; /* holds MyHostName */
+ static char rnamebuf[MAXNAME]; /* holds RealUserName */
+ char *emptyenviron[1];
+#if STARTTLS
+ bool tls_ok;
+#endif /* STARTTLS */
+ QUEUE_CHAR *new;
+ ENVELOPE *e;
+ extern int DtableSize;
+ extern int optind;
+ extern int opterr;
+ extern char *optarg;
+ extern char **environ;
+#if SASL
+ extern void sm_sasl_init __P((void));
+#endif /* SASL */
+
+#if USE_ENVIRON
+ envp = environ;
+#endif /* USE_ENVIRON */
+
+ /* turn off profiling */
+ SM_PROF(0);
+
+ /* install default exception handler */
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** Check to see if we reentered.
+ ** This would normally happen if e_putheader or e_putbody
+ ** were NULL when invoked.
+ */
+
+ if (starttime != 0)
+ {
+ syserr("main: reentered!");
+ abort();
+ }
+ starttime = curtime();
+
+ /* avoid null pointer dereferences */
+ TermEscape.te_rv_on = TermEscape.te_rv_off = "";
+
+ RealUid = getuid();
+ RealGid = getgid();
+
+ /* Check if sendmail is running with extra privs */
+ extraprivs = (RealUid != 0 &&
+ (geteuid() != getuid() || getegid() != getgid()));
+
+ CurrentPid = getpid();
+
+ /* get whatever .cf file is right for the opmode */
+ cftype = SM_GET_RIGHT_CF;
+
+ /* in 4.4BSD, the table can be huge; impose a reasonable limit */
+ DtableSize = getdtsize();
+ if (DtableSize > 256)
+ DtableSize = 256;
+
+ /*
+ ** Be sure we have enough file descriptors.
+ ** But also be sure that 0, 1, & 2 are open.
+ */
+
+ /* reset errno and fill_errno; the latter is used way down below */
+ errno = fill_errno = 0;
+ fill_fd(STDIN_FILENO, NULL);
+ if (errno != 0)
+ fill_errno = errno;
+ fill_fd(STDOUT_FILENO, NULL);
+ if (errno != 0)
+ fill_errno = errno;
+ fill_fd(STDERR_FILENO, NULL);
+ if (errno != 0)
+ fill_errno = errno;
+
+ i = DtableSize;
+ while (--i > 0)
+ {
+ if (i != STDIN_FILENO && i != STDOUT_FILENO &&
+ i != STDERR_FILENO)
+ (void) close(i);
+ }
+ errno = 0;
+
+#if LOG
+# ifndef SM_LOG_STR
+# define SM_LOG_STR "sendmail"
+# endif /* ! SM_LOG_STR */
+# ifdef LOG_MAIL
+ openlog(SM_LOG_STR, LOG_PID, LOG_MAIL);
+# else /* LOG_MAIL */
+ openlog(SM_LOG_STR, LOG_PID);
+# endif /* LOG_MAIL */
+#endif /* LOG */
+
+ /*
+ ** Seed the random number generator.
+ ** Used for queue file names, picking a queue directory, and
+ ** MX randomization.
+ */
+
+ seed_random();
+
+ /* do machine-dependent initializations */
+ init_md(argc, argv);
+
+
+ SyslogPrefixLen = PIDLEN + (MAXQFNAME - 3) + SL_FUDGE + SLDLL;
+
+ /* reset status from syserr() calls for missing file descriptors */
+ Errors = 0;
+ ExitStat = EX_OK;
+
+ SubmitMode = SUBMIT_UNKNOWN;
+#if XDEBUG
+ checkfd012("after openlog");
+#endif /* XDEBUG */
+
+ tTsetup(tTdvect, sizeof tTdvect, "0-99.1,*_trace_*.1");
+
+#ifdef NGROUPS_MAX
+ /* save initial group set for future checks */
+ i = getgroups(NGROUPS_MAX, InitialGidSet);
+ if (i <= 0)
+ {
+ InitialGidSet[0] = (GID_T) -1;
+ i = 0;
+ }
+ while (i < NGROUPS_MAX)
+ InitialGidSet[i++] = InitialGidSet[0];
+#endif /* NGROUPS_MAX */
+
+ /* drop group id privileges (RunAsUser not yet set) */
+ dp = drop_privileges(false);
+ setstat(dp);
+
+#ifdef SIGUSR1
+ /* Only allow root (or non-set-*-ID binaries) to use SIGUSR1 */
+ if (!extraprivs)
+ {
+ /* arrange to dump state on user-1 signal */
+ (void) sm_signal(SIGUSR1, sigusr1);
+ }
+ else
+ {
+ /* ignore user-1 signal */
+ (void) sm_signal(SIGUSR1, SIG_IGN);
+ }
+#endif /* SIGUSR1 */
+
+ /* initialize for setproctitle */
+ initsetproctitle(argc, argv, envp);
+
+ /* Handle any non-getoptable constructions. */
+ obsolete(argv);
+
+ /*
+ ** Do a quick prescan of the argument list.
+ */
+
+
+ /* find initial opMode */
+ OpMode = MD_DELIVER;
+ av = argv;
+ p = strrchr(*av, '/');
+ if (p++ == NULL)
+ p = *av;
+ if (strcmp(p, "newaliases") == 0)
+ OpMode = MD_INITALIAS;
+ else if (strcmp(p, "mailq") == 0)
+ OpMode = MD_PRINT;
+ else if (strcmp(p, "smtpd") == 0)
+ OpMode = MD_DAEMON;
+ else if (strcmp(p, "hoststat") == 0)
+ OpMode = MD_HOSTSTAT;
+ else if (strcmp(p, "purgestat") == 0)
+ OpMode = MD_PURGESTAT;
+
+#if _FFR_QUARANTINE
+# if defined(__osf__) || defined(_AIX3)
+# define OPTIONS "A:B:b:C:cd:e:F:f:Gh:IiL:M:mN:nO:o:p:q:R:r:sTtV:vX:xQ:"
+# endif /* defined(__osf__) || defined(_AIX3) */
+# if defined(sony_news)
+# define OPTIONS "A:B:b:C:cd:E:e:F:f:Gh:IiJ:L:M:mN:nO:o:p:q:R:r:sTtV:vX:Q:"
+# endif /* defined(sony_news) */
+# ifndef OPTIONS
+# define OPTIONS "A:B:b:C:cd:e:F:f:Gh:IiL:M:mN:nO:o:p:q:R:r:sTtV:vX:Q:"
+# endif /* ! OPTIONS */
+#else /* _FFR_QUARANTINE */
+# if defined(__osf__) || defined(_AIX3)
+# define OPTIONS "A:B:b:C:cd:e:F:f:Gh:IiL:M:mN:nO:o:p:q:R:r:sTtV:vX:x"
+# endif /* defined(__osf__) || defined(_AIX3) */
+# if defined(sony_news)
+# define OPTIONS "A:B:b:C:cd:E:e:F:f:Gh:IiJ:L:M:mN:nO:o:p:q:R:r:sTtV:vX:"
+# endif /* defined(sony_news) */
+# ifndef OPTIONS
+# define OPTIONS "A:B:b:C:cd:e:F:f:Gh:IiL:M:mN:nO:o:p:q:R:r:sTtV:vX:"
+# endif /* ! OPTIONS */
+#endif /* _FFR_QUARANTINE */
+
+ opterr = 0;
+ while ((j = getopt(argc, argv, OPTIONS)) != -1)
+ {
+ switch (j)
+ {
+ case 'b': /* operations mode */
+ j = (optarg == NULL) ? ' ' : *optarg;
+ switch (j)
+ {
+ case MD_DAEMON:
+ case MD_FGDAEMON:
+ case MD_SMTP:
+ case MD_INITALIAS:
+ case MD_DELIVER:
+ case MD_VERIFY:
+ case MD_TEST:
+ case MD_PRINT:
+ case MD_PRINTNQE:
+ case MD_HOSTSTAT:
+ case MD_PURGESTAT:
+ case MD_ARPAFTP:
+ OpMode = j;
+ break;
+
+ case MD_FREEZE:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Frozen configurations unsupported\n");
+ return EX_USAGE;
+
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Invalid operation mode %c\n",
+ j);
+ return EX_USAGE;
+ }
+ break;
+
+ case 'd':
+ debug = true;
+ tTflag(optarg);
+ (void) sm_io_setvbuf(smioout, SM_TIME_DEFAULT,
+ (char *) NULL, SM_IO_NBF,
+ SM_IO_BUFSIZ);
+ break;
+
+ case 'G': /* relay (gateway) submission */
+ SubmitMode = SUBMIT_MTA;
+ break;
+
+ case 'L':
+ j = SM_MIN(strlen(optarg), 24) + 1;
+ sysloglabel = xalloc(j);
+ (void) sm_strlcpy(sysloglabel, optarg, j);
+ SyslogPrefixLen = PIDLEN + (MAXQFNAME - 3) +
+ SL_FUDGE + j;
+ break;
+
+#if _FFR_QUARANTINE
+ case 'Q':
+#endif /* _FFR_QUARANTINE */
+ case 'q':
+ /* just check if it is there */
+ queuerun = true;
+ break;
+ }
+ }
+ opterr = 1;
+
+ /* Don't leak queue information via debug flags */
+ if (extraprivs && queuerun && debug)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "WARNING: Can not use -d with -q. Disabling debugging.\n");
+ sm_debug_setfile(NULL);
+ (void) memset(tTdvect, '\0', sizeof tTdvect);
+ }
+
+#if LOG
+ if (sysloglabel != NULL)
+ {
+ /* Sanitize the string */
+ for (p = sysloglabel; *p != '\0'; p++)
+ {
+ if (!isascii(*p) || !isprint(*p) || *p == '%')
+ *p = '*';
+ }
+ closelog();
+# ifdef LOG_MAIL
+ openlog(sysloglabel, LOG_PID, LOG_MAIL);
+# else /* LOG_MAIL */
+ openlog(sysloglabel, LOG_PID);
+# endif /* LOG_MAIL */
+ }
+#endif /* LOG */
+
+ /* set up the blank envelope */
+ BlankEnvelope.e_puthdr = putheader;
+ BlankEnvelope.e_putbody = putbody;
+ BlankEnvelope.e_xfp = NULL;
+ STRUCTCOPY(NullAddress, BlankEnvelope.e_from);
+ CurEnv = &BlankEnvelope;
+ STRUCTCOPY(NullAddress, MainEnvelope.e_from);
+
+ /*
+ ** Set default values for variables.
+ ** These cannot be in initialized data space.
+ */
+
+ setdefaults(&BlankEnvelope);
+ initmacros(&BlankEnvelope);
+
+ /* reset macro */
+ set_op_mode(OpMode);
+
+ pw = sm_getpwuid(RealUid);
+ if (pw != NULL)
+ (void) sm_strlcpy(rnamebuf, pw->pw_name, sizeof rnamebuf);
+ else
+ (void) sm_snprintf(rnamebuf, sizeof rnamebuf, "Unknown UID %d",
+ (int) RealUid);
+
+ RealUserName = rnamebuf;
+
+ if (tTd(0, 101))
+ {
+ sm_dprintf("Version %s\n", Version);
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ }
+
+ /*
+ ** if running non-set-user-ID binary as non-root, pretend
+ ** we are the RunAsUid
+ */
+
+ if (RealUid != 0 && geteuid() == RealUid)
+ {
+ if (tTd(47, 1))
+ sm_dprintf("Non-set-user-ID binary: RunAsUid = RealUid = %d\n",
+ (int) RealUid);
+ RunAsUid = RealUid;
+ }
+ else if (geteuid() != 0)
+ RunAsUid = geteuid();
+
+ EffGid = getegid();
+ if (RealUid != 0 && EffGid == RealGid)
+ RunAsGid = RealGid;
+
+ if (tTd(47, 5))
+ {
+ sm_dprintf("main: e/ruid = %d/%d e/rgid = %d/%d\n",
+ (int) geteuid(), (int) getuid(),
+ (int) getegid(), (int) getgid());
+ sm_dprintf("main: RunAsUser = %d:%d\n",
+ (int) RunAsUid, (int) RunAsGid);
+ }
+
+ /* save command line arguments */
+ j = 0;
+ for (av = argv; *av != NULL; )
+ j += strlen(*av++) + 1;
+ SaveArgv = (char **) xalloc(sizeof (char *) * (argc + 1));
+ CommandLineArgs = xalloc(j);
+ p = CommandLineArgs;
+ for (av = argv, i = 0; *av != NULL; )
+ {
+ int h;
+
+ SaveArgv[i++] = newstr(*av);
+ if (av != argv)
+ *p++ = ' ';
+ (void) sm_strlcpy(p, *av++, j);
+ h = strlen(p);
+ p += h;
+ j -= h + 1;
+ }
+ SaveArgv[i] = NULL;
+
+ if (tTd(0, 1))
+ {
+ extern char *CompileOptions[];
+
+ sm_dprintf("Version %s\n Compiled with:", Version);
+ sm_printoptions(CompileOptions);
+ }
+ if (tTd(0, 10))
+ {
+ extern char *OsCompileOptions[];
+
+ sm_dprintf(" OS Defines:");
+ sm_printoptions(OsCompileOptions);
+#ifdef _PATH_UNIX
+ sm_dprintf("Kernel symbols:\t%s\n", _PATH_UNIX);
+#endif /* _PATH_UNIX */
+
+ sm_dprintf(" Conf file:\t%s (default for MSP)\n",
+ getcfname(OpMode, SubmitMode, SM_GET_SUBMIT_CF,
+ conffile));
+ sm_dprintf(" Conf file:\t%s (default for MTA)\n",
+ getcfname(OpMode, SubmitMode, SM_GET_SENDMAIL_CF,
+ conffile));
+ sm_dprintf(" Pid file:\t%s (default)\n", PidFile);
+ }
+
+ if (tTd(0, 12))
+ {
+ extern char *SmCompileOptions[];
+
+ sm_dprintf(" libsm Defines:");
+ sm_printoptions(SmCompileOptions);
+ }
+
+ if (tTd(0, 13))
+ {
+ extern char *FFRCompileOptions[];
+
+ sm_dprintf(" FFR Defines:");
+ sm_printoptions(FFRCompileOptions);
+ }
+
+ InChannel = smioin;
+ OutChannel = smioout;
+
+ /* clear sendmail's environment */
+ ExternalEnviron = environ;
+ emptyenviron[0] = NULL;
+ environ = emptyenviron;
+
+ /*
+ ** restore any original TZ setting until TimeZoneSpec has been
+ ** determined - or early log messages may get bogus time stamps
+ */
+
+ if ((p = getextenv("TZ")) != NULL)
+ {
+ char *tz;
+ int tzlen;
+
+ /* XXX check for reasonable length? */
+ tzlen = strlen(p) + 4;
+ tz = xalloc(tzlen);
+ (void) sm_strlcpyn(tz, tzlen, 2, "TZ=", p);
+
+ /* XXX check return code? */
+ (void) putenv(tz);
+ }
+
+ /* prime the child environment */
+ setuserenv("AGENT", "sendmail");
+
+ (void) sm_signal(SIGPIPE, SIG_IGN);
+ OldUmask = umask(022);
+ FullName = getextenv("NAME");
+ if (FullName != NULL)
+ FullName = newstr(FullName);
+
+ /*
+ ** Initialize name server if it is going to be used.
+ */
+
+#if NAMED_BIND
+ if (!bitset(RES_INIT, _res.options))
+ (void) res_init();
+ if (tTd(8, 8))
+ _res.options |= RES_DEBUG;
+ else
+ _res.options &= ~RES_DEBUG;
+# ifdef RES_NOALIASES
+ if (bitset(RES_NOALIASES, _res.options))
+ ResNoAliases = true;
+ _res.options |= RES_NOALIASES;
+# endif /* RES_NOALIASES */
+ TimeOuts.res_retry[RES_TO_DEFAULT] = _res.retry;
+ TimeOuts.res_retry[RES_TO_FIRST] = _res.retry;
+ TimeOuts.res_retry[RES_TO_NORMAL] = _res.retry;
+ TimeOuts.res_retrans[RES_TO_DEFAULT] = _res.retrans;
+ TimeOuts.res_retrans[RES_TO_FIRST] = _res.retrans;
+ TimeOuts.res_retrans[RES_TO_NORMAL] = _res.retrans;
+#endif /* NAMED_BIND */
+
+ errno = 0;
+ from = NULL;
+
+ /* initialize some macros, etc. */
+ init_vendor_macros(&BlankEnvelope);
+
+ /* version */
+ macdefine(&BlankEnvelope.e_macro, A_PERM, 'v', Version);
+
+ /* hostname */
+ hp = myhostname(jbuf, sizeof jbuf);
+ if (jbuf[0] != '\0')
+ {
+ struct utsname utsname;
+
+ if (tTd(0, 4))
+ sm_dprintf("Canonical name: %s\n", jbuf);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'w', jbuf);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'j', jbuf);
+ setclass('w', jbuf);
+
+ p = strchr(jbuf, '.');
+ if (p != NULL)
+ {
+ if (p[1] != '\0')
+ {
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'm',
+ &p[1]);
+ }
+ while (p != NULL && strchr(&p[1], '.') != NULL)
+ {
+ *p = '\0';
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", jbuf);
+ setclass('w', jbuf);
+ *p++ = '.';
+ p = strchr(p, '.');
+ }
+ }
+
+ if (uname(&utsname) >= 0)
+ p = utsname.nodename;
+ else
+ {
+ if (tTd(0, 22))
+ sm_dprintf("uname failed (%s)\n",
+ sm_errstring(errno));
+ makelower(jbuf);
+ p = jbuf;
+ }
+ if (tTd(0, 4))
+ sm_dprintf(" UUCP nodename: %s\n", p);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'k', p);
+ setclass('k', p);
+ setclass('w', p);
+ }
+ if (hp != NULL)
+ {
+ for (av = hp->h_aliases; av != NULL && *av != NULL; av++)
+ {
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", *av);
+ setclass('w', *av);
+ }
+#if NETINET || NETINET6
+ for (i = 0; i >= 0 && hp->h_addr_list[i] != NULL; i++)
+ {
+# if NETINET6
+ char *addr;
+ char buf6[INET6_ADDRSTRLEN];
+ struct in6_addr ia6;
+# endif /* NETINET6 */
+# if NETINET
+ struct in_addr ia;
+# endif /* NETINET */
+ char ipbuf[103];
+
+ ipbuf[0] = '\0';
+ switch (hp->h_addrtype)
+ {
+# if NETINET
+ case AF_INET:
+ if (hp->h_length != INADDRSZ)
+ break;
+
+ memmove(&ia, hp->h_addr_list[i], INADDRSZ);
+ (void) sm_snprintf(ipbuf, sizeof ipbuf,
+ "[%.100s]", inet_ntoa(ia));
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ if (hp->h_length != IN6ADDRSZ)
+ break;
+
+ memmove(&ia6, hp->h_addr_list[i], IN6ADDRSZ);
+ addr = anynet_ntop(&ia6, buf6, sizeof buf6);
+ if (addr != NULL)
+ (void) sm_snprintf(ipbuf, sizeof ipbuf,
+ "[%.100s]", addr);
+ break;
+# endif /* NETINET6 */
+ }
+ if (ipbuf[0] == '\0')
+ break;
+
+ if (tTd(0, 4))
+ sm_dprintf("\ta.k.a.: %s\n", ipbuf);
+ setclass('w', ipbuf);
+ }
+#endif /* NETINET || NETINET6 */
+#if NETINET6
+ freehostent(hp);
+ hp = NULL;
+#endif /* NETINET6 */
+ }
+
+ /* current time */
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'b', arpadate((char *) NULL));
+
+ /* current load average */
+ sm_getla();
+
+ QueueLimitRecipient = (QUEUE_CHAR *) NULL;
+ QueueLimitSender = (QUEUE_CHAR *) NULL;
+ QueueLimitId = (QUEUE_CHAR *) NULL;
+#if _FFR_QUARANTINE
+ QueueLimitQuarantine = (QUEUE_CHAR *) NULL;
+#endif /* _FFR_QUARANTINE */
+
+ /*
+ ** Crack argv.
+ */
+
+ optind = 1;
+ while ((j = getopt(argc, argv, OPTIONS)) != -1)
+ {
+ switch (j)
+ {
+ case 'b': /* operations mode */
+ /* already done */
+ break;
+
+ case 'A': /* use Alternate sendmail/submit.cf */
+ cftype = optarg[0] == 'c' ? SM_GET_SUBMIT_CF
+ : SM_GET_SENDMAIL_CF;
+ break;
+
+ case 'B': /* body type */
+ CHECK_AGAINST_OPMODE(j);
+ BlankEnvelope.e_bodytype = newstr(optarg);
+ break;
+
+ case 'C': /* select configuration file (already done) */
+ if (RealUid != 0)
+ warn_C_flag = true;
+ conffile = newstr(optarg);
+ dp = drop_privileges(true);
+ setstat(dp);
+ safecf = false;
+ break;
+
+ case 'd': /* debugging */
+ /* already done */
+ break;
+
+ case 'f': /* from address */
+ case 'r': /* obsolete -f flag */
+ CHECK_AGAINST_OPMODE(j);
+ if (from != NULL)
+ {
+ usrerr("More than one \"from\" person");
+ ExitStat = EX_USAGE;
+ break;
+ }
+ from = newstr(denlstring(optarg, true, true));
+ if (strcmp(RealUserName, from) != 0)
+ warn_f_flag = j;
+ break;
+
+ case 'F': /* set full name */
+ CHECK_AGAINST_OPMODE(j);
+ FullName = newstr(optarg);
+ break;
+
+ case 'G': /* relay (gateway) submission */
+ /* already set */
+ CHECK_AGAINST_OPMODE(j);
+ break;
+
+ case 'h': /* hop count */
+ CHECK_AGAINST_OPMODE(j);
+ BlankEnvelope.e_hopcount = (short) strtol(optarg, &ep,
+ 10);
+ (void) sm_snprintf(buf, sizeof buf, "%d",
+ BlankEnvelope.e_hopcount);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, 'c', buf);
+
+ if (*ep)
+ {
+ usrerr("Bad hop count (%s)", optarg);
+ ExitStat = EX_USAGE;
+ }
+ break;
+
+ case 'L': /* program label */
+ /* already set */
+ break;
+
+ case 'n': /* don't alias */
+ CHECK_AGAINST_OPMODE(j);
+ NoAlias = true;
+ break;
+
+ case 'N': /* delivery status notifications */
+ CHECK_AGAINST_OPMODE(j);
+ DefaultNotify |= QHASNOTIFY;
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{dsn_notify}"), optarg);
+ if (sm_strcasecmp(optarg, "never") == 0)
+ break;
+ for (p = optarg; p != NULL; optarg = p)
+ {
+ p = strchr(p, ',');
+ if (p != NULL)
+ *p++ = '\0';
+ if (sm_strcasecmp(optarg, "success") == 0)
+ DefaultNotify |= QPINGONSUCCESS;
+ else if (sm_strcasecmp(optarg, "failure") == 0)
+ DefaultNotify |= QPINGONFAILURE;
+ else if (sm_strcasecmp(optarg, "delay") == 0)
+ DefaultNotify |= QPINGONDELAY;
+ else
+ {
+ usrerr("Invalid -N argument");
+ ExitStat = EX_USAGE;
+ }
+ }
+ break;
+
+ case 'o': /* set option */
+ setoption(*optarg, optarg + 1, false, true,
+ &BlankEnvelope);
+ break;
+
+ case 'O': /* set option (long form) */
+ setoption(' ', optarg, false, true, &BlankEnvelope);
+ break;
+
+ case 'p': /* set protocol */
+ CHECK_AGAINST_OPMODE(j);
+ p = strchr(optarg, ':');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ if (*p != '\0')
+ {
+ ep = sm_malloc_x(strlen(p) + 1);
+ cleanstrcpy(ep, p, MAXNAME);
+ macdefine(&BlankEnvelope.e_macro,
+ A_HEAP, 's', ep);
+ }
+ }
+ if (*optarg != '\0')
+ {
+ ep = sm_malloc_x(strlen(optarg) + 1);
+ cleanstrcpy(ep, optarg, MAXNAME);
+ macdefine(&BlankEnvelope.e_macro, A_HEAP,
+ 'r', ep);
+ }
+ break;
+
+#if _FFR_QUARANTINE
+ case 'Q': /* change quarantining on queued items */
+ /* sanity check */
+ if (OpMode != MD_DELIVER &&
+ OpMode != MD_QUEUERUN)
+ {
+ usrerr("Can not use -Q with -b%c", OpMode);
+ ExitStat = EX_USAGE;
+ break;
+ }
+
+ if (OpMode == MD_DELIVER)
+ set_op_mode(MD_QUEUERUN);
+
+ FullName = NULL;
+
+ quarantining = newstr(optarg);
+ break;
+#endif /* _FFR_QUARANTINE */
+
+ case 'q': /* run queue files at intervals */
+ /* sanity check */
+ if (OpMode != MD_DELIVER &&
+ OpMode != MD_DAEMON &&
+ OpMode != MD_FGDAEMON &&
+ OpMode != MD_PRINT &&
+ OpMode != MD_PRINTNQE &&
+ OpMode != MD_QUEUERUN)
+ {
+ usrerr("Can not use -q with -b%c", OpMode);
+ ExitStat = EX_USAGE;
+ break;
+ }
+
+ /* don't override -bd, -bD or -bp */
+ if (OpMode == MD_DELIVER)
+ set_op_mode(MD_QUEUERUN);
+
+ FullName = NULL;
+ negate = optarg[0] == '!';
+ if (negate)
+ {
+ /* negate meaning of pattern match */
+ optarg++; /* skip '!' for next switch */
+ }
+
+ switch (optarg[0])
+ {
+ case 'G': /* Limit by queue group name */
+ if (negate)
+ {
+ usrerr("Can not use -q!G");
+ ExitStat = EX_USAGE;
+ break;
+ }
+ if (queuegroup != NULL)
+ {
+ usrerr("Can not use multiple -qG options");
+ ExitStat = EX_USAGE;
+ break;
+ }
+ queuegroup = newstr(&optarg[1]);
+ break;
+
+ case 'I': /* Limit by ID */
+ new = (QUEUE_CHAR *) xalloc(sizeof *new);
+ new->queue_match = newstr(&optarg[1]);
+ new->queue_negate = negate;
+ new->queue_next = QueueLimitId;
+ QueueLimitId = new;
+ break;
+
+ case 'R': /* Limit by recipient */
+ new = (QUEUE_CHAR *) xalloc(sizeof *new);
+ new->queue_match = newstr(&optarg[1]);
+ new->queue_negate = negate;
+ new->queue_next = QueueLimitRecipient;
+ QueueLimitRecipient = new;
+ break;
+
+ case 'S': /* Limit by sender */
+ new = (QUEUE_CHAR *) xalloc(sizeof *new);
+ new->queue_match = newstr(&optarg[1]);
+ new->queue_negate = negate;
+ new->queue_next = QueueLimitSender;
+ QueueLimitSender = new;
+ break;
+
+ case 'f': /* foreground queue run */
+ foregroundqueue = true;
+ break;
+
+#if _FFR_QUARANTINE
+ case 'Q': /* Limit by quarantine message */
+ if (optarg[1] != '\0')
+ {
+ new = (QUEUE_CHAR *) xalloc(sizeof *new);
+ new->queue_match = newstr(&optarg[1]);
+ new->queue_negate = negate;
+ new->queue_next = QueueLimitQuarantine;
+ QueueLimitQuarantine = new;
+ }
+ QueueMode = QM_QUARANTINE;
+ break;
+
+ case 'L': /* act on lost items */
+ QueueMode = QM_LOST;
+ break;
+#endif /* _FFR_QUARANTINE */
+
+ case 'p': /* Persistent queue */
+ queuepersistent = true;
+ if (QueueIntvl == 0)
+ QueueIntvl = 1;
+ if (optarg[1] == '\0')
+ break;
+ ++optarg;
+ /* FALLTHROUGH */
+
+ default:
+ i = Errors;
+ QueueIntvl = convtime(optarg, 'm');
+ if (QueueIntvl < 0)
+ {
+ usrerr("Invalid -q value");
+ ExitStat = EX_USAGE;
+ }
+
+ /* check for bad conversion */
+ if (i < Errors)
+ ExitStat = EX_USAGE;
+ break;
+ }
+ break;
+
+ case 'R': /* DSN RET: what to return */
+ CHECK_AGAINST_OPMODE(j);
+ if (bitset(EF_RET_PARAM, BlankEnvelope.e_flags))
+ {
+ usrerr("Duplicate -R flag");
+ ExitStat = EX_USAGE;
+ break;
+ }
+ BlankEnvelope.e_flags |= EF_RET_PARAM;
+ if (sm_strcasecmp(optarg, "hdrs") == 0)
+ BlankEnvelope.e_flags |= EF_NO_BODY_RETN;
+ else if (sm_strcasecmp(optarg, "full") != 0)
+ {
+ usrerr("Invalid -R value");
+ ExitStat = EX_USAGE;
+ }
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{dsn_ret}"), optarg);
+ break;
+
+ case 't': /* read recipients from message */
+ CHECK_AGAINST_OPMODE(j);
+ GrabTo = true;
+ break;
+
+ case 'V': /* DSN ENVID: set "original" envelope id */
+ CHECK_AGAINST_OPMODE(j);
+ if (!xtextok(optarg))
+ {
+ usrerr("Invalid syntax in -V flag");
+ ExitStat = EX_USAGE;
+ }
+ else
+ {
+ BlankEnvelope.e_envid = newstr(optarg);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{dsn_envid}"), optarg);
+ }
+ break;
+
+ case 'X': /* traffic log file */
+ dp = drop_privileges(true);
+ setstat(dp);
+ if (stat(optarg, &traf_st) == 0 &&
+ S_ISFIFO(traf_st.st_mode))
+ TrafficLogFile = sm_io_open(SmFtStdio,
+ SM_TIME_DEFAULT,
+ optarg,
+ SM_IO_WRONLY, NULL);
+ else
+ TrafficLogFile = sm_io_open(SmFtStdio,
+ SM_TIME_DEFAULT,
+ optarg,
+ SM_IO_APPEND, NULL);
+ if (TrafficLogFile == NULL)
+ {
+ syserr("cannot open %s", optarg);
+ ExitStat = EX_CANTCREAT;
+ break;
+ }
+ (void) sm_io_setvbuf(TrafficLogFile, SM_TIME_DEFAULT,
+ NULL, SM_IO_LBF, 0);
+ break;
+
+ /* compatibility flags */
+ case 'c': /* connect to non-local mailers */
+ case 'i': /* don't let dot stop me */
+ case 'm': /* send to me too */
+ case 'T': /* set timeout interval */
+ case 'v': /* give blow-by-blow description */
+ setoption(j, "T", false, true, &BlankEnvelope);
+ break;
+
+ case 'e': /* error message disposition */
+ case 'M': /* define macro */
+ setoption(j, optarg, false, true, &BlankEnvelope);
+ break;
+
+ case 's': /* save From lines in headers */
+ setoption('f', "T", false, true, &BlankEnvelope);
+ break;
+
+#ifdef DBM
+ case 'I': /* initialize alias DBM file */
+ set_op_mode(MD_INITALIAS);
+ break;
+#endif /* DBM */
+
+#if defined(__osf__) || defined(_AIX3)
+ case 'x': /* random flag that OSF/1 & AIX mailx passes */
+ break;
+#endif /* defined(__osf__) || defined(_AIX3) */
+#if defined(sony_news)
+ case 'E':
+ case 'J': /* ignore flags for Japanese code conversion
+ implemented on Sony NEWS */
+ break;
+#endif /* defined(sony_news) */
+
+ default:
+ finis(true, true, EX_USAGE);
+ /* NOTREACHED */
+ break;
+ }
+ }
+
+ /* if we've had errors so far, exit now */
+ if ((ExitStat != EX_OK && OpMode != MD_TEST) ||
+ ExitStat == EX_OSERR)
+ {
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+ if (bitset(SUBMIT_MTA, SubmitMode))
+ {
+ /* If set daemon_flags on command line, don't reset it */
+ if (macvalue(macid("{daemon_flags}"), &BlankEnvelope) == NULL)
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_flags}"), "CC f");
+ }
+ else if (OpMode == MD_DELIVER || OpMode == MD_SMTP)
+ {
+ SubmitMode = SUBMIT_MSA;
+
+ /* If set daemon_flags on command line, don't reset it */
+ if (macvalue(macid("{daemon_flags}"), &BlankEnvelope) == NULL)
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_flags}"), "c u");
+ }
+
+ /*
+ ** Do basic initialization.
+ ** Read system control file.
+ ** Extract special fields for local use.
+ */
+
+#if XDEBUG
+ checkfd012("before readcf");
+#endif /* XDEBUG */
+ vendor_pre_defaults(&BlankEnvelope);
+
+ readcf(getcfname(OpMode, SubmitMode, cftype, conffile),
+ safecf, &BlankEnvelope);
+#if !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_)
+ ConfigFileRead = true;
+#endif /* !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_) */
+ vendor_post_defaults(&BlankEnvelope);
+
+ /* now we can complain about missing fds */
+ if (MissingFds != 0 && LogLevel > 8)
+ {
+ char mbuf[MAXLINE];
+
+ mbuf[0] = '\0';
+ if (bitset(1 << STDIN_FILENO, MissingFds))
+ (void) sm_strlcat(mbuf, ", stdin", sizeof mbuf);
+ if (bitset(1 << STDOUT_FILENO, MissingFds))
+ (void) sm_strlcat(mbuf, ", stdout", sizeof mbuf);
+ if (bitset(1 << STDERR_FILENO, MissingFds))
+ (void) sm_strlcat(mbuf, ", stderr", sizeof mbuf);
+
+ /* Notice: fill_errno is from high above: fill_fd() */
+ sm_syslog(LOG_WARNING, NOQID,
+ "File descriptors missing on startup: %s; %s",
+ &mbuf[2], sm_errstring(fill_errno));
+ }
+
+ /* Remove the ability for a normal user to send signals */
+ if (RealUid != 0 && RealUid != geteuid())
+ {
+ uid_t new_uid = geteuid();
+
+#if HASSETREUID
+ /*
+ ** Since we can differentiate between uid and euid,
+ ** make the uid a different user so the real user
+ ** can't send signals. However, it doesn't need to be
+ ** root (euid has root).
+ */
+
+ if (new_uid == 0)
+ new_uid = DefUid;
+ if (tTd(47, 5))
+ sm_dprintf("Changing real uid to %d\n", (int) new_uid);
+ if (setreuid(new_uid, geteuid()) < 0)
+ {
+ syserr("main: setreuid(%d, %d) failed",
+ (int) new_uid, (int) geteuid());
+ finis(false, true, EX_OSERR);
+ /* NOTREACHED */
+ }
+ if (tTd(47, 10))
+ sm_dprintf("Now running as e/ruid %d:%d\n",
+ (int) geteuid(), (int) getuid());
+#else /* HASSETREUID */
+ /*
+ ** Have to change both effective and real so need to
+ ** change them both to effective to keep privs.
+ */
+
+ if (tTd(47, 5))
+ sm_dprintf("Changing uid to %d\n", (int) new_uid);
+ if (setuid(new_uid) < 0)
+ {
+ syserr("main: setuid(%d) failed", (int) new_uid);
+ finis(false, true, EX_OSERR);
+ /* NOTREACHED */
+ }
+ if (tTd(47, 10))
+ sm_dprintf("Now running as e/ruid %d:%d\n",
+ (int) geteuid(), (int) getuid());
+#endif /* HASSETREUID */
+ }
+
+#if NAMED_BIND
+ if (FallBackMX != NULL)
+ (void) getfallbackmxrr(FallBackMX);
+#endif /* NAMED_BIND */
+
+ if (SuperSafe == SAFE_INTERACTIVE && CurEnv->e_sendmode != SM_DELIVER)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "WARNING: SuperSafe=interactive should only be used with\n DeliveryMode=interactive\n");
+ }
+
+ if (UseMSP && (OpMode == MD_DAEMON || OpMode == MD_FGDAEMON))
+ {
+ usrerr("Mail submission program cannot be used as daemon");
+ finis(false, true, EX_USAGE);
+ }
+
+ if (OpMode == MD_DELIVER || OpMode == MD_SMTP ||
+ OpMode == MD_QUEUERUN || OpMode == MD_ARPAFTP ||
+ OpMode == MD_DAEMON || OpMode == MD_FGDAEMON)
+ makeworkgroups();
+
+ /* set up the basic signal handlers */
+ if (sm_signal(SIGINT, SIG_IGN) != SIG_IGN)
+ (void) sm_signal(SIGINT, intsig);
+ (void) sm_signal(SIGTERM, intsig);
+
+ /* Enforce use of local time (null string overrides this) */
+ if (TimeZoneSpec == NULL)
+ unsetenv("TZ");
+ else if (TimeZoneSpec[0] != '\0')
+ setuserenv("TZ", TimeZoneSpec);
+ else
+ setuserenv("TZ", NULL);
+ tzset();
+
+ /* initialize mailbox database */
+ i = sm_mbdb_initialize(Mbdb);
+ if (i != EX_OK)
+ {
+ usrerr("Can't initialize mailbox database \"%s\": %s",
+ Mbdb, sm_strexit(i));
+ ExitStat = i;
+ }
+
+ /* avoid denial-of-service attacks */
+ resetlimits();
+
+ if (OpMode == MD_TEST)
+ {
+ /* can't be done after readcf if RunAs* is used */
+ dp = drop_privileges(true);
+ if (dp != EX_OK)
+ {
+ finis(false, true, dp);
+ /* NOTREACHED */
+ }
+ }
+ else if (OpMode != MD_DAEMON && OpMode != MD_FGDAEMON)
+ {
+ /* drop privileges -- daemon mode done after socket/bind */
+ dp = drop_privileges(false);
+ setstat(dp);
+ if (dp == EX_OK && UseMSP && (geteuid() == 0 || getuid() == 0))
+ {
+ usrerr("Mail submission program must have RunAsUser set to non root user");
+ finis(false, true, EX_CONFIG);
+ /* NOTREACHED */
+ }
+ }
+
+#if NAMED_BIND
+ _res.retry = TimeOuts.res_retry[RES_TO_DEFAULT];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_DEFAULT];
+#endif /* NAMED_BIND */
+
+ /*
+ ** Find our real host name for future logging.
+ */
+
+ authinfo = getauthinfo(STDIN_FILENO, &forged);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, '_', authinfo);
+
+ /* suppress error printing if errors mailed back or whatever */
+ if (BlankEnvelope.e_errormode != EM_PRINT)
+ HoldErrs = true;
+
+ /* set up the $=m class now, after .cf has a chance to redefine $m */
+ expand("\201m", jbuf, sizeof jbuf, &BlankEnvelope);
+ if (jbuf[0] != '\0')
+ setclass('m', jbuf);
+
+ /* probe interfaces and locate any additional names */
+ if (DontProbeInterfaces != DPI_PROBENONE)
+ load_if_names();
+
+ if (tTd(0, 10))
+ {
+ /* Now we know which .cf file we use */
+ sm_dprintf(" Conf file:\t%s (selected)\n",
+ getcfname(OpMode, SubmitMode, cftype, conffile));
+ sm_dprintf(" Pid file:\t%s (selected)\n", PidFile);
+ }
+
+ if (tTd(0, 1))
+ {
+ sm_dprintf("\n============ SYSTEM IDENTITY (after readcf) ============");
+ sm_dprintf("\n (short domain name) $w = ");
+ xputs(macvalue('w', &BlankEnvelope));
+ sm_dprintf("\n (canonical domain name) $j = ");
+ xputs(macvalue('j', &BlankEnvelope));
+ sm_dprintf("\n (subdomain name) $m = ");
+ xputs(macvalue('m', &BlankEnvelope));
+ sm_dprintf("\n (node name) $k = ");
+ xputs(macvalue('k', &BlankEnvelope));
+ sm_dprintf("\n========================================================\n\n");
+ }
+
+ /*
+ ** Do more command line checking -- these are things that
+ ** have to modify the results of reading the config file.
+ */
+
+ /* process authorization warnings from command line */
+ if (warn_C_flag)
+ auth_warning(&BlankEnvelope, "Processed by %s with -C %s",
+ RealUserName, conffile);
+ if (Warn_Q_option && !wordinclass(RealUserName, 't'))
+ auth_warning(&BlankEnvelope, "Processed from queue %s",
+ QueueDir);
+ if (sysloglabel != NULL && !wordinclass(RealUserName, 't') &&
+ RealUid != 0 && RealUid != TrustedUid && LogLevel > 1)
+ sm_syslog(LOG_WARNING, NOQID, "user %d changed syslog label",
+ (int) RealUid);
+
+ /* check body type for legality */
+ i = check_bodytype(BlankEnvelope.e_bodytype);
+ if (i == BODYTYPE_ILLEGAL)
+ {
+ usrerr("Illegal body type %s", BlankEnvelope.e_bodytype);
+ BlankEnvelope.e_bodytype = NULL;
+ }
+ else if (i != BODYTYPE_NONE)
+ SevenBitInput = (i == BODYTYPE_7BIT);
+
+ /* tweak default DSN notifications */
+ if (DefaultNotify == 0)
+ DefaultNotify = QPINGONFAILURE|QPINGONDELAY;
+
+ /* be sure we don't pick up bogus HOSTALIASES environment variable */
+ if (OpMode == MD_QUEUERUN && RealUid != 0)
+ (void) unsetenv("HOSTALIASES");
+
+ /* check for sane configuration level */
+ if (ConfigLevel > MAXCONFIGLEVEL)
+ {
+ syserr("Warning: .cf version level (%d) exceeds sendmail version %s functionality (%d)",
+ ConfigLevel, Version, MAXCONFIGLEVEL);
+ }
+
+ /* need MCI cache to have persistence */
+ if (HostStatDir != NULL && MaxMciCache == 0)
+ {
+ HostStatDir = NULL;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: HostStatusDirectory disabled with ConnectionCacheSize = 0\n");
+ }
+
+ /* need HostStatusDir in order to have SingleThreadDelivery */
+ if (SingleThreadDelivery && HostStatDir == NULL)
+ {
+ SingleThreadDelivery = false;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: HostStatusDirectory required for SingleThreadDelivery\n");
+ }
+
+ /* check for permissions */
+ if (RealUid != 0 &&
+ RealUid != TrustedUid)
+ {
+ char *action = NULL;
+
+ switch (OpMode)
+ {
+ case MD_QUEUERUN:
+#if _FFR_QUARANTINE
+ if (quarantining != NULL)
+ action = "quarantine jobs";
+ else
+#endif /* _FFR_QUARANTINE */
+ /* Normal users can do a single queue run */
+ if (QueueIntvl == 0)
+ break;
+
+ /* but not persistent queue runners */
+ if (action == NULL)
+ action = "start a queue runner daemon";
+ /* FALLTHROUGH */
+
+ case MD_PURGESTAT:
+ if (action == NULL)
+ action = "purge host status";
+ /* FALLTHROUGH */
+
+ case MD_DAEMON:
+ case MD_FGDAEMON:
+ if (action == NULL)
+ action = "run daemon";
+
+ if (tTd(65, 1))
+ sm_dprintf("Deny user %d attempt to %s\n",
+ (int) RealUid, action);
+
+ if (LogLevel > 1)
+ sm_syslog(LOG_ALERT, NOQID,
+ "user %d attempted to %s",
+ (int) RealUid, action);
+ HoldErrs = false;
+ usrerr("Permission denied (real uid not trusted)");
+ finis(false, true, EX_USAGE);
+ /* NOTREACHED */
+ break;
+
+ case MD_VERIFY:
+ if (bitset(PRIV_RESTRICTEXPAND, PrivacyFlags))
+ {
+ /*
+ ** If -bv and RestrictExpand,
+ ** drop privs to prevent normal
+ ** users from reading private
+ ** aliases/forwards/:include:s
+ */
+
+ if (tTd(65, 1))
+ sm_dprintf("Drop privs for user %d attempt to expand (RestrictExpand)\n",
+ (int) RealUid);
+
+ dp = drop_privileges(true);
+
+ /* Fake address safety */
+ if (tTd(65, 1))
+ sm_dprintf("Faking DontBlameSendmail=NonRootSafeAddr\n");
+ setbitn(DBS_NONROOTSAFEADDR, DontBlameSendmail);
+
+ if (dp != EX_OK)
+ {
+ if (tTd(65, 1))
+ sm_dprintf("Failed to drop privs for user %d attempt to expand, exiting\n",
+ (int) RealUid);
+ CurEnv->e_id = NULL;
+ finis(true, true, dp);
+ /* NOTREACHED */
+ }
+ }
+ break;
+
+ case MD_TEST:
+ case MD_PRINT:
+ case MD_PRINTNQE:
+ case MD_FREEZE:
+ case MD_HOSTSTAT:
+ /* Nothing special to check */
+ break;
+
+ case MD_INITALIAS:
+ if (!wordinclass(RealUserName, 't'))
+ {
+ if (tTd(65, 1))
+ sm_dprintf("Deny user %d attempt to rebuild the alias map\n",
+ (int) RealUid);
+ if (LogLevel > 1)
+ sm_syslog(LOG_ALERT, NOQID,
+ "user %d attempted to rebuild the alias map",
+ (int) RealUid);
+ HoldErrs = false;
+ usrerr("Permission denied (real uid not trusted)");
+ finis(false, true, EX_USAGE);
+ /* NOTREACHED */
+ }
+ if (UseMSP)
+ {
+ HoldErrs = false;
+ usrerr("User %d cannot rebuild aliases in mail submission program",
+ (int) RealUid);
+ finis(false, true, EX_USAGE);
+ /* NOTREACHED */
+ }
+ /* FALLTHROUGH */
+
+ default:
+ if (bitset(PRIV_RESTRICTEXPAND, PrivacyFlags) &&
+ Verbose != 0)
+ {
+ /*
+ ** If -v and RestrictExpand, reset
+ ** Verbose to prevent normal users
+ ** from seeing the expansion of
+ ** aliases/forwards/:include:s
+ */
+
+ if (tTd(65, 1))
+ sm_dprintf("Dropping verbosity for user %d (RestrictExpand)\n",
+ (int) RealUid);
+ Verbose = 0;
+ }
+ break;
+ }
+ }
+
+ if (MeToo)
+ BlankEnvelope.e_flags |= EF_METOO;
+
+ switch (OpMode)
+ {
+ case MD_TEST:
+ /* don't have persistent host status in test mode */
+ HostStatDir = NULL;
+ if (Verbose == 0)
+ Verbose = 2;
+ BlankEnvelope.e_errormode = EM_PRINT;
+ HoldErrs = false;
+ break;
+
+ case MD_VERIFY:
+ BlankEnvelope.e_errormode = EM_PRINT;
+ HoldErrs = false;
+ /* arrange to exit cleanly on hangup signal */
+ if (sm_signal(SIGHUP, SIG_IGN) == (sigfunc_t) SIG_DFL)
+ (void) sm_signal(SIGHUP, intsig);
+ if (geteuid() != 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Notice: -bv may give misleading output for non-privileged user\n");
+ break;
+
+ case MD_FGDAEMON:
+ run_in_foreground = true;
+ set_op_mode(MD_DAEMON);
+ /* FALLTHROUGH */
+
+ case MD_DAEMON:
+ vendor_daemon_setup(&BlankEnvelope);
+
+ /* remove things that don't make sense in daemon mode */
+ FullName = NULL;
+ GrabTo = false;
+
+ /* arrange to restart on hangup signal */
+ if (SaveArgv[0] == NULL || SaveArgv[0][0] != '/')
+ sm_syslog(LOG_WARNING, NOQID,
+ "daemon invoked without full pathname; kill -1 won't work");
+ break;
+
+ case MD_INITALIAS:
+ Verbose = 2;
+ BlankEnvelope.e_errormode = EM_PRINT;
+ HoldErrs = false;
+ /* FALLTHROUGH */
+
+ default:
+ /* arrange to exit cleanly on hangup signal */
+ if (sm_signal(SIGHUP, SIG_IGN) == (sigfunc_t) SIG_DFL)
+ (void) sm_signal(SIGHUP, intsig);
+ break;
+ }
+
+ /* special considerations for FullName */
+ if (FullName != NULL)
+ {
+ char *full = NULL;
+
+ /* full names can't have newlines */
+ if (strchr(FullName, '\n') != NULL)
+ {
+ full = newstr(denlstring(FullName, true, true));
+ FullName = full;
+ }
+
+ /* check for characters that may have to be quoted */
+ if (!rfc822_string(FullName))
+ {
+ /*
+ ** Quote a full name with special characters
+ ** as a comment so crackaddr() doesn't destroy
+ ** the name portion of the address.
+ */
+
+ FullName = addquotes(FullName, NULL);
+ if (full != NULL)
+ sm_free(full); /* XXX */
+ }
+ }
+
+ /* do heuristic mode adjustment */
+ if (Verbose)
+ {
+ /* turn off noconnect option */
+ setoption('c', "F", true, false, &BlankEnvelope);
+
+ /* turn on interactive delivery */
+ setoption('d', "", true, false, &BlankEnvelope);
+ }
+
+#ifdef VENDOR_CODE
+ /* check for vendor mismatch */
+ if (VendorCode != VENDOR_CODE)
+ {
+ message("Warning: .cf file vendor code mismatch: sendmail expects vendor %s, .cf file vendor is %s",
+ getvendor(VENDOR_CODE), getvendor(VendorCode));
+ }
+#endif /* VENDOR_CODE */
+
+ /* check for out of date configuration level */
+ if (ConfigLevel < MAXCONFIGLEVEL)
+ {
+ message("Warning: .cf file is out of date: sendmail %s supports version %d, .cf file is version %d",
+ Version, MAXCONFIGLEVEL, ConfigLevel);
+ }
+
+ if (ConfigLevel < 3)
+ UseErrorsTo = true;
+
+ /* set options that were previous macros */
+ if (SmtpGreeting == NULL)
+ {
+ if (ConfigLevel < 7 &&
+ (p = macvalue('e', &BlankEnvelope)) != NULL)
+ SmtpGreeting = newstr(p);
+ else
+ SmtpGreeting = "\201j Sendmail \201v ready at \201b";
+ }
+ if (UnixFromLine == NULL)
+ {
+ if (ConfigLevel < 7 &&
+ (p = macvalue('l', &BlankEnvelope)) != NULL)
+ UnixFromLine = newstr(p);
+ else
+ UnixFromLine = "From \201g \201d";
+ }
+ SmtpError[0] = '\0';
+
+ /* our name for SMTP codes */
+ expand("\201j", jbuf, sizeof jbuf, &BlankEnvelope);
+ if (jbuf[0] == '\0')
+ PSTRSET(MyHostName, "localhost");
+ else
+ PSTRSET(MyHostName, jbuf);
+ if (strchr(MyHostName, '.') == NULL)
+ message("WARNING: local host name (%s) is not qualified; fix $j in config file",
+ MyHostName);
+
+ /* make certain that this name is part of the $=w class */
+ setclass('w', MyHostName);
+
+ /* fill in the structure of the *default* queue */
+ st = stab("mqueue", ST_QUEUE, ST_FIND);
+ if (st == NULL)
+ syserr("No default queue (mqueue) defined");
+ else
+ set_def_queueval(st->s_quegrp, true);
+
+ /* the indices of built-in mailers */
+ st = stab("local", ST_MAILER, ST_FIND);
+ if (st != NULL)
+ LocalMailer = st->s_mailer;
+ else if (OpMode != MD_TEST || !warn_C_flag)
+ syserr("No local mailer defined");
+
+ st = stab("prog", ST_MAILER, ST_FIND);
+ if (st == NULL)
+ syserr("No prog mailer defined");
+ else
+ {
+ ProgMailer = st->s_mailer;
+ clrbitn(M_MUSER, ProgMailer->m_flags);
+ }
+
+ st = stab("*file*", ST_MAILER, ST_FIND);
+ if (st == NULL)
+ syserr("No *file* mailer defined");
+ else
+ {
+ FileMailer = st->s_mailer;
+ clrbitn(M_MUSER, FileMailer->m_flags);
+ }
+
+ st = stab("*include*", ST_MAILER, ST_FIND);
+ if (st == NULL)
+ syserr("No *include* mailer defined");
+ else
+ InclMailer = st->s_mailer;
+
+ if (ConfigLevel < 6)
+ {
+ /* heuristic tweaking of local mailer for back compat */
+ if (LocalMailer != NULL)
+ {
+ setbitn(M_ALIASABLE, LocalMailer->m_flags);
+ setbitn(M_HASPWENT, LocalMailer->m_flags);
+ setbitn(M_TRYRULESET5, LocalMailer->m_flags);
+ setbitn(M_CHECKINCLUDE, LocalMailer->m_flags);
+ setbitn(M_CHECKPROG, LocalMailer->m_flags);
+ setbitn(M_CHECKFILE, LocalMailer->m_flags);
+ setbitn(M_CHECKUDB, LocalMailer->m_flags);
+ }
+ if (ProgMailer != NULL)
+ setbitn(M_RUNASRCPT, ProgMailer->m_flags);
+ if (FileMailer != NULL)
+ setbitn(M_RUNASRCPT, FileMailer->m_flags);
+ }
+ if (ConfigLevel < 7)
+ {
+ if (LocalMailer != NULL)
+ setbitn(M_VRFY250, LocalMailer->m_flags);
+ if (ProgMailer != NULL)
+ setbitn(M_VRFY250, ProgMailer->m_flags);
+ if (FileMailer != NULL)
+ setbitn(M_VRFY250, FileMailer->m_flags);
+ }
+
+ /* MIME Content-Types that cannot be transfer encoded */
+ setclass('n', "multipart/signed");
+
+ /* MIME message/xxx subtypes that can be treated as messages */
+ setclass('s', "rfc822");
+
+ /* MIME Content-Transfer-Encodings that can be encoded */
+ setclass('e', "7bit");
+ setclass('e', "8bit");
+ setclass('e', "binary");
+
+#ifdef USE_B_CLASS
+ /* MIME Content-Types that should be treated as binary */
+ setclass('b', "image");
+ setclass('b', "audio");
+ setclass('b', "video");
+ setclass('b', "application/octet-stream");
+#endif /* USE_B_CLASS */
+
+ /* MIME headers which have fields to check for overflow */
+ setclass(macid("{checkMIMEFieldHeaders}"), "content-disposition");
+ setclass(macid("{checkMIMEFieldHeaders}"), "content-type");
+
+ /* MIME headers to check for length overflow */
+ setclass(macid("{checkMIMETextHeaders}"), "content-description");
+
+ /* MIME headers to check for overflow and rebalance */
+ setclass(macid("{checkMIMEHeaders}"), "content-disposition");
+ setclass(macid("{checkMIMEHeaders}"), "content-id");
+ setclass(macid("{checkMIMEHeaders}"), "content-transfer-encoding");
+ setclass(macid("{checkMIMEHeaders}"), "content-type");
+ setclass(macid("{checkMIMEHeaders}"), "mime-version");
+
+ /* Macros to save in the queue file -- don't remove any */
+ setclass(macid("{persistentMacros}"), "r");
+ setclass(macid("{persistentMacros}"), "s");
+ setclass(macid("{persistentMacros}"), "_");
+ setclass(macid("{persistentMacros}"), "{if_addr}");
+ setclass(macid("{persistentMacros}"), "{daemon_flags}");
+
+ /* operate in queue directory */
+ if (QueueDir == NULL || *QueueDir == '\0')
+ {
+ if (OpMode != MD_TEST)
+ {
+ syserr("QueueDirectory (Q) option must be set");
+ ExitStat = EX_CONFIG;
+ }
+ }
+ else
+ {
+ if (OpMode != MD_TEST)
+ setup_queues(OpMode == MD_DAEMON);
+ }
+
+ /* check host status directory for validity */
+ if (HostStatDir != NULL && !path_is_dir(HostStatDir, false))
+ {
+ /* cannot use this value */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Cannot use HostStatusDirectory = %s: %s\n",
+ HostStatDir, sm_errstring(errno));
+ HostStatDir = NULL;
+ }
+
+ if (OpMode == MD_QUEUERUN &&
+ RealUid != 0 && bitset(PRIV_RESTRICTQRUN, PrivacyFlags))
+ {
+ struct stat stbuf;
+
+ /* check to see if we own the queue directory */
+ if (stat(".", &stbuf) < 0)
+ syserr("main: cannot stat %s", QueueDir);
+ if (stbuf.st_uid != RealUid)
+ {
+ /* nope, really a botch */
+ HoldErrs = false;
+ usrerr("You do not have permission to process the queue");
+ finis(false, true, EX_NOPERM);
+ /* NOTREACHED */
+ }
+ }
+
+#if MILTER
+ /* sanity checks on milter filters */
+ if (OpMode == MD_DAEMON || OpMode == MD_SMTP)
+ {
+ milter_config(InputFilterList, InputFilters, MAXFILTERS);
+# if _FFR_MILTER_PERDAEMON
+ setup_daemon_milters();
+# endif /* _FFR_MILTER_PERDAEMON */
+ }
+#endif /* MILTER */
+
+ /* Convert queuegroup string to qgrp number */
+ if (queuegroup != NULL)
+ {
+ qgrp = name2qid(queuegroup);
+ if (qgrp == NOQGRP)
+ {
+ HoldErrs = false;
+ usrerr("Queue group %s unknown", queuegroup);
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ }
+ }
+
+ /* if we've had errors so far, exit now */
+ if (ExitStat != EX_OK && OpMode != MD_TEST)
+ {
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+#if SASL
+ /* sendmail specific SASL initialization */
+ sm_sasl_init();
+#endif /* SASL */
+
+#if XDEBUG
+ checkfd012("before main() initmaps");
+#endif /* XDEBUG */
+
+ /*
+ ** Do operation-mode-dependent initialization.
+ */
+
+ switch (OpMode)
+ {
+ case MD_PRINT:
+ /* print the queue */
+ HoldErrs = false;
+ dropenvelope(&BlankEnvelope, true, false);
+ (void) sm_signal(SIGPIPE, sigpipe);
+ if (qgrp != NOQGRP)
+ {
+ int j;
+
+ /* Selecting a particular queue group to run */
+ for (j = 0; j < Queue[qgrp]->qg_numqueues; j++)
+ {
+ if (StopRequest)
+ stop_sendmail();
+ (void) print_single_queue(qgrp, j);
+ }
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ }
+ printqueue();
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ break;
+
+ case MD_PRINTNQE:
+ /* print number of entries in queue */
+ dropenvelope(&BlankEnvelope, true, false);
+ (void) sm_signal(SIGPIPE, sigpipe);
+ printnqe(smioout, NULL);
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ break;
+
+#if _FFR_QUARANTINE
+ case MD_QUEUERUN:
+ /* only handle quarantining here */
+ if (quarantining == NULL)
+ break;
+
+ if (QueueMode != QM_QUARANTINE &&
+ QueueMode != QM_NORMAL)
+ {
+ HoldErrs = false;
+ usrerr("Can not use -Q with -q%c", QueueMode);
+ ExitStat = EX_USAGE;
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ }
+ quarantine_queue(quarantining, qgrp);
+ finis(false, true, EX_OK);
+ break;
+#endif /* _FFR_QUARANTINE */
+
+ case MD_HOSTSTAT:
+ (void) sm_signal(SIGPIPE, sigpipe);
+ (void) mci_traverse_persistent(mci_print_persistent, NULL);
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ break;
+
+ case MD_PURGESTAT:
+ (void) mci_traverse_persistent(mci_purge_persistent, NULL);
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ break;
+
+ case MD_INITALIAS:
+ /* initialize maps */
+ initmaps();
+ finis(false, true, ExitStat);
+ /* NOTREACHED */
+ break;
+
+ case MD_SMTP:
+ case MD_DAEMON:
+ /* reset DSN parameters */
+ DefaultNotify = QPINGONFAILURE|QPINGONDELAY;
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{dsn_notify}"), NULL);
+ BlankEnvelope.e_envid = NULL;
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{dsn_envid}"), NULL);
+ BlankEnvelope.e_flags &= ~(EF_RET_PARAM|EF_NO_BODY_RETN);
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{dsn_ret}"), NULL);
+
+ /* don't open maps for daemon -- done below in child */
+ break;
+ }
+
+ if (tTd(0, 15))
+ {
+ /* print configuration table (or at least part of it) */
+ if (tTd(0, 90))
+ printrules();
+ for (i = 0; i < MAXMAILERS; i++)
+ {
+ if (Mailer[i] != NULL)
+ printmailer(Mailer[i]);
+ }
+ }
+
+ /*
+ ** Switch to the main envelope.
+ */
+
+ CurEnv = newenvelope(&MainEnvelope, &BlankEnvelope,
+ sm_rpool_new_x(NULL));
+ MainEnvelope.e_flags = BlankEnvelope.e_flags;
+
+ /*
+ ** If test mode, read addresses from stdin and process.
+ */
+
+ if (OpMode == MD_TEST)
+ {
+ if (isatty(sm_io_getinfo(smioin, SM_IO_WHAT_FD, NULL)))
+ Verbose = 2;
+
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)\n");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Enter <ruleset> <address>\n");
+ }
+ macdefine(&(MainEnvelope.e_macro), A_PERM,
+ macid("{addr_type}"), "e r");
+ for (;;)
+ {
+ SM_TRY
+ {
+ (void) sm_signal(SIGINT, intindebug);
+ (void) sm_releasesignal(SIGINT);
+ if (Verbose == 2)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "> ");
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ if (sm_io_fgets(smioin, SM_TIME_DEFAULT, buf,
+ sizeof buf) == NULL)
+ testmodeline("/quit", &MainEnvelope);
+ p = strchr(buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ if (Verbose < 2)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "> %s\n", buf);
+ testmodeline(buf, &MainEnvelope);
+ }
+ SM_EXCEPT(exc, "[!F]*")
+ {
+ /*
+ ** 8.10 just prints \n on interrupt.
+ ** I'm printing the exception here in case
+ ** sendmail is extended to raise additional
+ ** exceptions in this context.
+ */
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\n");
+ sm_exc_print(exc, smioout);
+ }
+ SM_END_TRY
+ }
+ }
+
+#if STARTTLS
+ tls_ok = true;
+ if (OpMode == MD_QUEUERUN || OpMode == MD_DELIVER)
+ {
+ /* check whether STARTTLS is turned off for the client */
+ if (chkclientmodifiers(D_NOTLS))
+ tls_ok = false;
+ }
+ else if (OpMode == MD_DAEMON || OpMode == MD_FGDAEMON ||
+ OpMode == MD_SMTP)
+ {
+ /* check whether STARTTLS is turned off for the server */
+ if (chkdaemonmodifiers(D_NOTLS))
+ tls_ok = false;
+ }
+ else /* other modes don't need STARTTLS */
+ tls_ok = false;
+
+ if (tls_ok)
+ {
+ /* basic TLS initialization */
+ tls_ok = init_tls_library();
+ }
+
+ if (!tls_ok && (OpMode == MD_QUEUERUN || OpMode == MD_DELIVER))
+ {
+ /* disable TLS for client */
+ setclttls(false);
+ }
+#endif /* STARTTLS */
+
+ /*
+ ** If collecting stuff from the queue, go start doing that.
+ */
+
+ if (OpMode == MD_QUEUERUN && QueueIntvl == 0)
+ {
+ pid_t pid = -1;
+
+#if STARTTLS
+ /* init TLS for client, ignore result for now */
+ (void) initclttls(tls_ok);
+#endif /* STARTTLS */
+
+ /*
+ ** The parent process of the caller of runqueue() needs
+ ** to stay around for a possible SIGTERM. The SIGTERM will
+ ** tell this process that all of the queue runners children
+ ** need to be sent SIGTERM as well. At the same time, we
+ ** want to return control to the command line. So we do an
+ ** extra fork().
+ */
+
+ if (Verbose || foregroundqueue || (pid = fork()) <= 0)
+ {
+ /*
+ ** If the fork() failed we should still try to do
+ ** the queue run. If it succeeded then the child
+ ** is going to start the run and wait for all
+ ** of the children to finish.
+ */
+
+ if (pid == 0)
+ {
+ /* Reset global flags */
+ RestartRequest = NULL;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+
+ /* disconnect from terminal */
+ disconnect(2, CurEnv);
+ }
+
+ CurrentPid = getpid();
+ if (qgrp != NOQGRP)
+ {
+ /*
+ ** To run a specific queue group mark it to
+ ** be run, select the work group it's in and
+ ** increment the work counter.
+ */
+
+ for (i = 0; i < NumQueue && Queue[i] != NULL;
+ i++)
+ Queue[i]->qg_nextrun = (time_t) -1;
+ Queue[qgrp]->qg_nextrun = 0;
+ (void) run_work_group(Queue[qgrp]->qg_wgrp,
+ false, Verbose,
+ queuepersistent, false);
+ }
+ else
+ (void) runqueue(false, Verbose,
+ queuepersistent, true);
+
+ /* set the title to make it easier to find */
+ sm_setproctitle(true, CurEnv, "Queue control");
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ while (CurChildren > 0)
+ {
+ int status;
+ pid_t ret;
+
+ while ((ret = sm_wait(&status)) <= 0)
+ continue;
+
+ /* Only drop when a child gives status */
+ if (WIFSTOPPED(status))
+ continue;
+
+ proc_list_drop(ret, status, NULL);
+ }
+ }
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+# if SASL
+ if (OpMode == MD_SMTP || OpMode == MD_DAEMON)
+ {
+ /* check whether AUTH is turned off for the server */
+ if (!chkdaemonmodifiers(D_NOAUTH) &&
+ (i = sasl_server_init(srvcallbacks, "Sendmail")) != SASL_OK)
+ syserr("!sasl_server_init failed! [%s]",
+ sasl_errstring(i, NULL, NULL));
+ }
+# endif /* SASL */
+
+ if (OpMode == MD_SMTP)
+ {
+ proc_list_add(CurrentPid, "Sendmail SMTP Agent",
+ PROC_DAEMON, 0, -1);
+
+ /* clean up background delivery children */
+ (void) sm_signal(SIGCHLD, reapchild);
+ }
+
+ /*
+ ** If a daemon, wait for a request.
+ ** getrequests will always return in a child.
+ ** If we should also be processing the queue, start
+ ** doing it in background.
+ ** We check for any errors that might have happened
+ ** during startup.
+ */
+
+ if (OpMode == MD_DAEMON || QueueIntvl > 0)
+ {
+ char dtype[200];
+
+ if (!run_in_foreground && !tTd(99, 100))
+ {
+ /* put us in background */
+ i = fork();
+ if (i < 0)
+ syserr("daemon: cannot fork");
+ if (i != 0)
+ {
+ finis(false, true, EX_OK);
+ /* NOTREACHED */
+ }
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+
+ sm_exc_newthread(fatal_error);
+
+ /* disconnect from our controlling tty */
+ disconnect(2, &MainEnvelope);
+ }
+
+ dtype[0] = '\0';
+ if (OpMode == MD_DAEMON)
+ {
+ (void) sm_strlcat(dtype, "+SMTP", sizeof dtype);
+ DaemonPid = CurrentPid;
+ }
+ if (QueueIntvl > 0)
+ {
+ (void) sm_strlcat2(dtype,
+ queuepersistent
+ ? "+persistent-queueing@"
+ : "+queueing@",
+ pintvl(QueueIntvl, true),
+ sizeof dtype);
+ }
+ if (tTd(0, 1))
+ (void) sm_strlcat(dtype, "+debugging", sizeof dtype);
+
+ sm_syslog(LOG_INFO, NOQID,
+ "starting daemon (%s): %s", Version, dtype + 1);
+#if XLA
+ xla_create_file();
+#endif /* XLA */
+
+ /* save daemon type in a macro for possible PidFile use */
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{daemon_info}"), dtype + 1);
+
+ /* save queue interval in a macro for possible PidFile use */
+ macdefine(&MainEnvelope.e_macro, A_TEMP,
+ macid("{queue_interval}"), pintvl(QueueIntvl, true));
+
+ /* workaround: can't seem to release the signal in the parent */
+ (void) sm_signal(SIGHUP, sighup);
+ (void) sm_releasesignal(SIGHUP);
+ (void) sm_signal(SIGTERM, sigterm);
+
+ if (QueueIntvl > 0)
+ {
+ (void) runqueue(true, false, queuepersistent, true);
+
+ /*
+ ** If queuepersistent but not in daemon mode then
+ ** we're going to do the queue runner monitoring here.
+ ** If in daemon mode then the monitoring will happen
+ ** elsewhere.
+ */
+
+ if (OpMode != MD_DAEMON && queuepersistent)
+ {
+ /* set the title to make it easier to find */
+ sm_setproctitle(true, CurEnv, "Queue control");
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ while (CurChildren > 0)
+ {
+ int status;
+ pid_t ret;
+ int group;
+
+ if (ShutdownRequest != NULL)
+ shutdown_daemon();
+ else if (RestartRequest != NULL)
+ restart_daemon();
+ else if (RestartWorkGroup)
+ restart_marked_work_groups();
+
+ while ((ret = sm_wait(&status)) <= 0)
+ continue;
+
+ if (WIFSTOPPED(status))
+ continue;
+
+ /* Probe only on a child status */
+ proc_list_drop(ret, status, &group);
+
+ if (WIFSIGNALED(status))
+ {
+ if (WCOREDUMP(status))
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "persistent queue runner=%d core dumped, signal=%d",
+ group, WTERMSIG(status));
+
+ /* don't restart this one */
+ mark_work_group_restart(group, -1);
+ continue;
+ }
+
+ sm_syslog(LOG_ERR, NOQID,
+ "persistent queue runner=%d died, signal=%d",
+ group, WTERMSIG(status));
+ }
+
+ /*
+ ** When debugging active, don't
+ ** restart the persistent queues.
+ ** But do log this as info.
+ */
+
+ if (sm_debug_active(&DebugNoPRestart,
+ 1))
+ {
+ sm_syslog(LOG_DEBUG, NOQID,
+ "persistent queue runner=%d, exited",
+ group);
+ mark_work_group_restart(group, -1);
+ }
+ }
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+
+ if (OpMode != MD_DAEMON)
+ {
+ char qtype[200];
+
+ /*
+ ** Write the pid to file
+ ** XXX Overwrites sendmail.pid
+ */
+
+ log_sendmail_pid(&MainEnvelope);
+
+ /* set the title to make it easier to find */
+ qtype[0] = '\0';
+ (void) sm_strlcpyn(qtype, sizeof qtype, 4,
+ "Queue runner@",
+ pintvl(QueueIntvl, true),
+ " for ",
+ QueueDir);
+ sm_setproctitle(true, CurEnv, qtype);
+ for (;;)
+ {
+ (void) pause();
+ if (ShutdownRequest != NULL)
+ shutdown_daemon();
+ else if (RestartRequest != NULL)
+ restart_daemon();
+ else if (RestartWorkGroup)
+ restart_marked_work_groups();
+
+ if (doqueuerun())
+ (void) runqueue(true, false,
+ false, false);
+ }
+ }
+ }
+ dropenvelope(&MainEnvelope, true, false);
+
+#if STARTTLS
+ /* init TLS for server, ignore result for now */
+ (void) initsrvtls(tls_ok);
+#endif /* STARTTLS */
+#if PROFILING
+ nextreq:
+#endif /* PROFILING */
+ p_flags = getrequests(&MainEnvelope);
+
+ /* drop privileges */
+ (void) drop_privileges(false);
+
+ /*
+ ** Get authentication data
+ ** Set _ macro in BlankEnvelope before calling newenvelope().
+ */
+
+ authinfo = getauthinfo(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
+ NULL), &forged);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, '_', authinfo);
+
+ /* at this point we are in a child: reset state */
+ sm_rpool_free(MainEnvelope.e_rpool);
+ (void) newenvelope(&MainEnvelope, &MainEnvelope,
+ sm_rpool_new_x(NULL));
+ }
+
+ if (LogLevel > 9)
+ {
+ /* log connection information */
+ sm_syslog(LOG_INFO, NULL, "connect from %.100s", authinfo);
+ }
+
+ /*
+ ** If running SMTP protocol, start collecting and executing
+ ** commands. This will never return.
+ */
+
+ if (OpMode == MD_SMTP || OpMode == MD_DAEMON)
+ {
+ char pbuf[20];
+
+ /*
+ ** Save some macros for check_* rulesets.
+ */
+
+ if (forged)
+ {
+ char ipbuf[103];
+
+ (void) sm_snprintf(ipbuf, sizeof ipbuf, "[%.100s]",
+ anynet_ntoa(&RealHostAddr));
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{client_name}"), ipbuf);
+ }
+ else
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{client_name}"), RealHostName);
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{client_addr}"), anynet_ntoa(&RealHostAddr));
+ sm_getla();
+
+ switch (RealHostAddr.sa.sa_family)
+ {
+#if NETINET
+ case AF_INET:
+ (void) sm_snprintf(pbuf, sizeof pbuf, "%d",
+ RealHostAddr.sin.sin_port);
+ break;
+#endif /* NETINET */
+#if NETINET6
+ case AF_INET6:
+ (void) sm_snprintf(pbuf, sizeof pbuf, "%d",
+ RealHostAddr.sin6.sin6_port);
+ break;
+#endif /* NETINET6 */
+ default:
+ (void) sm_snprintf(pbuf, sizeof pbuf, "0");
+ break;
+ }
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{client_port}"), pbuf);
+
+ if (OpMode == MD_DAEMON)
+ {
+ /* validate the connection */
+ HoldErrs = true;
+ nullserver = validate_connection(&RealHostAddr,
+ RealHostName,
+ &MainEnvelope);
+ HoldErrs = false;
+ }
+ else if (p_flags == NULL)
+ {
+ p_flags = (BITMAP256 *) xalloc(sizeof *p_flags);
+ clrbitmap(p_flags);
+ }
+#if STARTTLS
+ if (OpMode == MD_SMTP)
+ (void) initsrvtls(tls_ok);
+#endif /* STARTTLS */
+
+ /* turn off profiling */
+ SM_PROF(1);
+ smtp(nullserver, *p_flags, &MainEnvelope);
+#if PROFILING
+ /* turn off profiling */
+ SM_PROF(0);
+ if (OpMode == MD_DAEMON)
+ goto nextreq;
+#endif /* PROFILING */
+ }
+
+ sm_rpool_free(MainEnvelope.e_rpool);
+ clearenvelope(&MainEnvelope, false, sm_rpool_new_x(NULL));
+ if (OpMode == MD_VERIFY)
+ {
+ set_delivery_mode(SM_VERIFY, &MainEnvelope);
+ PostMasterCopy = NULL;
+ }
+ else
+ {
+ /* interactive -- all errors are global */
+ MainEnvelope.e_flags |= EF_GLOBALERRS|EF_LOGSENDER;
+ }
+
+ /*
+ ** Do basic system initialization and set the sender
+ */
+
+ initsys(&MainEnvelope);
+ macdefine(&MainEnvelope.e_macro, A_PERM, macid("{ntries}"), "0");
+ macdefine(&MainEnvelope.e_macro, A_PERM, macid("{nrcpts}"), "0");
+ setsender(from, &MainEnvelope, NULL, '\0', false);
+ if (warn_f_flag != '\0' && !wordinclass(RealUserName, 't') &&
+ (!bitnset(M_LOCALMAILER, MainEnvelope.e_from.q_mailer->m_flags) ||
+ strcmp(MainEnvelope.e_from.q_user, RealUserName) != 0))
+ {
+ auth_warning(&MainEnvelope, "%s set sender to %s using -%c",
+ RealUserName, from, warn_f_flag);
+#if SASL
+ auth = false;
+#endif /* SASL */
+ }
+ if (auth)
+ {
+ char *fv;
+
+ /* set the initial sender for AUTH= to $f@$j */
+ fv = macvalue('f', &MainEnvelope);
+ if (fv == NULL || *fv == '\0')
+ MainEnvelope.e_auth_param = NULL;
+ else
+ {
+ if (strchr(fv, '@') == NULL)
+ {
+ i = strlen(fv) + strlen(macvalue('j',
+ &MainEnvelope)) + 2;
+ p = sm_malloc_x(i);
+ (void) sm_strlcpyn(p, i, 3, fv, "@",
+ macvalue('j',
+ &MainEnvelope));
+ }
+ else
+ p = sm_strdup_x(fv);
+ MainEnvelope.e_auth_param = sm_rpool_strdup_x(MainEnvelope.e_rpool,
+ xtextify(p, "="));
+ sm_free(p); /* XXX */
+ }
+ }
+ if (macvalue('s', &MainEnvelope) == NULL)
+ macdefine(&MainEnvelope.e_macro, A_PERM, 's', RealHostName);
+
+ av = argv + optind;
+ if (*av == NULL && !GrabTo)
+ {
+ MainEnvelope.e_to = NULL;
+ MainEnvelope.e_flags |= EF_GLOBALERRS;
+ HoldErrs = false;
+ SuperSafe = SAFE_NO;
+ usrerr("Recipient names must be specified");
+
+ /* collect body for UUCP return */
+ if (OpMode != MD_VERIFY)
+ collect(InChannel, false, NULL, &MainEnvelope);
+ finis(true, true, EX_USAGE);
+ /* NOTREACHED */
+ }
+
+ /*
+ ** Scan argv and deliver the message to everyone.
+ */
+
+ save_val = LogUsrErrs;
+ LogUsrErrs = true;
+ sendtoargv(av, &MainEnvelope);
+ LogUsrErrs = save_val;
+
+ /* if we have had errors sofar, arrange a meaningful exit stat */
+ if (Errors > 0 && ExitStat == EX_OK)
+ ExitStat = EX_USAGE;
+
+#if _FFR_FIX_DASHT
+ /*
+ ** If using -t, force not sending to argv recipients, even
+ ** if they are mentioned in the headers.
+ */
+
+ if (GrabTo)
+ {
+ ADDRESS *q;
+
+ for (q = MainEnvelope.e_sendqueue; q != NULL; q = q->q_next)
+ q->q_state = QS_REMOVED;
+ }
+#endif /* _FFR_FIX_DASHT */
+
+ /*
+ ** Read the input mail.
+ */
+
+ MainEnvelope.e_to = NULL;
+ if (OpMode != MD_VERIFY || GrabTo)
+ {
+ int savederrors;
+ unsigned long savedflags;
+
+ /*
+ ** workaround for compiler warning on Irix:
+ ** do not initialize variable in the definition, but
+ ** later on:
+ ** warning(1548): transfer of control bypasses
+ ** initialization of:
+ ** variable "savederrors" (declared at line 2570)
+ ** variable "savedflags" (declared at line 2571)
+ ** goto giveup;
+ */
+
+ savederrors = Errors;
+ savedflags = MainEnvelope.e_flags & EF_FATALERRS;
+ MainEnvelope.e_flags |= EF_GLOBALERRS;
+ MainEnvelope.e_flags &= ~EF_FATALERRS;
+ Errors = 0;
+ buffer_errors();
+ collect(InChannel, false, NULL, &MainEnvelope);
+
+ /* header checks failed */
+ if (Errors > 0)
+ {
+ giveup:
+ if (!GrabTo)
+ {
+ /* Log who the mail would have gone to */
+ logundelrcpts(&MainEnvelope,
+ MainEnvelope.e_message,
+ 8, false);
+ }
+ flush_errors(true);
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ return -1;
+ }
+
+ /* bail out if message too large */
+ if (bitset(EF_CLRQUEUE, MainEnvelope.e_flags))
+ {
+ finis(true, true, ExitStat != EX_OK ? ExitStat
+ : EX_DATAERR);
+ /* NOTREACHED */
+ return -1;
+ }
+
+ /* set message size */
+ (void) sm_snprintf(buf, sizeof buf, "%ld",
+ MainEnvelope.e_msgsize);
+ macdefine(&MainEnvelope.e_macro, A_TEMP,
+ macid("{msg_size}"), buf);
+
+ Errors = savederrors;
+ MainEnvelope.e_flags |= savedflags;
+ }
+ errno = 0;
+
+ if (tTd(1, 1))
+ sm_dprintf("From person = \"%s\"\n",
+ MainEnvelope.e_from.q_paddr);
+
+#if _FFR_QUARANTINE
+ /* Check if quarantining stats should be updated */
+ if (MainEnvelope.e_quarmsg != NULL)
+ markstats(&MainEnvelope, NULL, STATS_QUARANTINE);
+#endif /* _FFR_QUARANTINE */
+
+ /*
+ ** Actually send everything.
+ ** If verifying, just ack.
+ */
+
+ if (Errors == 0)
+ {
+ if (!split_by_recipient(&MainEnvelope) &&
+ bitset(EF_FATALERRS, MainEnvelope.e_flags))
+ goto giveup;
+ }
+
+ /* make sure we deliver at least the first envelope */
+ i = FastSplit > 0 ? 0 : -1;
+ for (e = &MainEnvelope; e != NULL; e = e->e_sibling, i++)
+ {
+ ENVELOPE *next;
+
+ e->e_from.q_state = QS_SENDER;
+ if (tTd(1, 5))
+ {
+ sm_dprintf("main[%d]: QS_SENDER ", i);
+ printaddr(&e->e_from, false);
+ }
+ e->e_to = NULL;
+ sm_getla();
+ GrabTo = false;
+#if NAMED_BIND
+ _res.retry = TimeOuts.res_retry[RES_TO_FIRST];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
+#endif /* NAMED_BIND */
+ next = e->e_sibling;
+ e->e_sibling = NULL;
+
+ /* after FastSplit envelopes: queue up */
+ sendall(e, i >= FastSplit ? SM_QUEUE : SM_DEFAULT);
+ e->e_sibling = next;
+ }
+
+ /*
+ ** All done.
+ ** Don't send return error message if in VERIFY mode.
+ */
+
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ return ExitStat;
+}
+/*
+** STOP_SENDMAIL -- Stop the running program
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** exits.
+*/
+
+void
+stop_sendmail()
+{
+ /* reset uid for process accounting */
+ endpwent();
+ (void) setuid(RealUid);
+ exit(EX_OK);
+}
+/*
+** FINIS -- Clean up and exit.
+**
+** Parameters:
+** drop -- whether or not to drop CurEnv envelope
+** cleanup -- call exit() or _exit()?
+** exitstat -- exit status to use for exit() call
+**
+** Returns:
+** never
+**
+** Side Effects:
+** exits sendmail
+*/
+
+void
+finis(drop, cleanup, exitstat)
+ bool drop;
+ bool cleanup;
+ volatile int exitstat;
+{
+
+ /* Still want to process new timeouts added below */
+ sm_clear_events();
+ (void) sm_releasesignal(SIGALRM);
+
+ if (tTd(2, 1))
+ {
+ sm_dprintf("\n====finis: stat %d e_id=%s e_flags=",
+ exitstat,
+ CurEnv->e_id == NULL ? "NOQUEUE" : CurEnv->e_id);
+ printenvflags(CurEnv);
+ }
+ if (tTd(2, 9))
+ printopenfds(false);
+
+ SM_TRY
+ /*
+ ** Clean up. This might raise E:mta.quickabort
+ */
+
+ /* clean up temp files */
+ CurEnv->e_to = NULL;
+ if (drop)
+ {
+ if (CurEnv->e_id != NULL)
+ {
+ dropenvelope(CurEnv, true, false);
+ sm_rpool_free(CurEnv->e_rpool);
+ CurEnv->e_rpool = NULL;
+ }
+ else
+ poststats(StatFile);
+ }
+
+ /* flush any cached connections */
+ mci_flush(true, NULL);
+
+ /* close maps belonging to this pid */
+ closemaps(false);
+
+#if USERDB
+ /* close UserDatabase */
+ _udbx_close();
+#endif /* USERDB */
+
+#if SASL
+ stop_sasl_client();
+#endif /* SASL */
+
+#if XLA
+ /* clean up extended load average stuff */
+ xla_all_end();
+#endif /* XLA */
+
+ SM_FINALLY
+ /*
+ ** And exit.
+ */
+
+ if (LogLevel > 78)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "finis, pid=%d",
+ (int) CurrentPid);
+ if (exitstat == EX_TEMPFAIL ||
+ CurEnv->e_errormode == EM_BERKNET)
+ exitstat = EX_OK;
+
+ /* XXX clean up queues and related data structures */
+ cleanup_queues();
+#if SM_CONF_SHM
+ cleanup_shm(DaemonPid == getpid());
+#endif /* SM_CONF_SHM */
+
+ /* reset uid for process accounting */
+ endpwent();
+ sm_mbdb_terminate();
+ (void) setuid(RealUid);
+#if SM_HEAP_CHECK
+ /* dump the heap, if we are checking for memory leaks */
+ if (sm_debug_active(&SmHeapCheck, 2))
+ sm_heap_report(smioout,
+ sm_debug_level(&SmHeapCheck) - 1);
+#endif /* SM_HEAP_CHECK */
+ if (sm_debug_active(&SmXtrapReport, 1))
+ sm_dprintf("xtrap count = %d\n", SmXtrapCount);
+ if (cleanup)
+ exit(exitstat);
+ else
+ _exit(exitstat);
+ SM_END_TRY
+}
+/*
+** INTINDEBUG -- signal handler for SIGINT in -bt mode
+**
+** Parameters:
+** sig -- incoming signal.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** longjmps back to test mode loop.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* Type of an exception generated on SIGINT during address test mode. */
+static const SM_EXC_TYPE_T EtypeInterrupt =
+{
+ SmExcTypeMagic,
+ "S:mta.interrupt",
+ "",
+ sm_etype_printf,
+ "interrupt",
+};
+
+/* ARGSUSED */
+static SIGFUNC_DECL
+intindebug(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, intindebug);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ errno = save_errno;
+ sm_exc_raisenew_x(&EtypeInterrupt);
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** SIGTERM -- SIGTERM handler for the daemon
+**
+** Parameters:
+** sig -- signal number.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets ShutdownRequest which will hopefully trigger
+** the daemon to exit.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED */
+static SIGFUNC_DECL
+sigterm(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, sigterm);
+ ShutdownRequest = "signal";
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** SIGHUP -- handle a SIGHUP signal
+**
+** Parameters:
+** sig -- incoming signal.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets RestartRequest which should cause the daemon
+** to restart.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED */
+static SIGFUNC_DECL
+sighup(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, sighup);
+ RestartRequest = "signal";
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** SIGPIPE -- signal handler for SIGPIPE
+**
+** Parameters:
+** sig -- incoming signal.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets StopRequest which should cause the mailq/hoststatus
+** display to stop.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+/* ARGSUSED */
+static SIGFUNC_DECL
+sigpipe(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, sigpipe);
+ StopRequest = true;
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** INTSIG -- clean up on interrupt
+**
+** This just arranges to exit. It pessimizes in that it
+** may resend a message.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Unlocks the current job.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+**
+** XXX: More work is needed for this signal handler.
+*/
+
+/* ARGSUSED */
+SIGFUNC_DECL
+intsig(sig)
+ int sig;
+{
+ bool drop = false;
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, intsig);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ sm_allsignals(true);
+
+ if (sig != 0 && LogLevel > 79)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "interrupt");
+ FileName = NULL;
+
+ /* Clean-up on aborted stdin message submission */
+ if (CurEnv->e_id != NULL &&
+ (OpMode == MD_SMTP ||
+ OpMode == MD_DELIVER ||
+ OpMode == MD_ARPAFTP))
+ {
+ register ADDRESS *q;
+
+ /* don't return an error indication */
+ CurEnv->e_to = NULL;
+ CurEnv->e_flags &= ~EF_FATALERRS;
+ CurEnv->e_flags |= EF_CLRQUEUE;
+
+ /*
+ ** Spin through the addresses and
+ ** mark them dead to prevent bounces
+ */
+
+ for (q = CurEnv->e_sendqueue; q != NULL; q = q->q_next)
+ q->q_state = QS_DONTSEND;
+
+ drop = true;
+ }
+ else if (OpMode != MD_TEST)
+ {
+ unlockqueue(CurEnv);
+ }
+
+ finis(drop, false, EX_OK);
+ /* NOTREACHED */
+}
+/*
+** DISCONNECT -- remove our connection with any foreground process
+**
+** Parameters:
+** droplev -- how "deeply" we should drop the line.
+** 0 -- ignore signals, mail back errors, make sure
+** output goes to stdout.
+** 1 -- also, make stdout go to /dev/null.
+** 2 -- also, disconnect from controlling terminal
+** (only for daemon mode).
+** e -- the current envelope.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Trys to insure that we are immune to vagaries of
+** the controlling tty.
+*/
+
+void
+disconnect(droplev, e)
+ int droplev;
+ register ENVELOPE *e;
+{
+ int fd;
+
+ if (tTd(52, 1))
+ sm_dprintf("disconnect: In %d Out %d, e=%p\n",
+ sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL),
+ sm_io_getinfo(OutChannel, SM_IO_WHAT_FD, NULL), e);
+ if (tTd(52, 100))
+ {
+ sm_dprintf("don't\n");
+ return;
+ }
+ if (LogLevel > 93)
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "disconnect level %d",
+ droplev);
+
+ /* be sure we don't get nasty signals */
+ (void) sm_signal(SIGINT, SIG_IGN);
+ (void) sm_signal(SIGQUIT, SIG_IGN);
+
+ /* we can't communicate with our caller, so.... */
+ HoldErrs = true;
+ CurEnv->e_errormode = EM_MAIL;
+ Verbose = 0;
+ DisConnected = true;
+
+ /* all input from /dev/null */
+ if (InChannel != smioin)
+ {
+ (void) sm_io_close(InChannel, SM_TIME_DEFAULT);
+ InChannel = smioin;
+ }
+ if (sm_io_reopen(SmFtStdio, SM_TIME_DEFAULT, SM_PATH_DEVNULL,
+ SM_IO_RDONLY, NULL, smioin) == NULL)
+ sm_syslog(LOG_ERR, e->e_id,
+ "disconnect: sm_io_reopen(\"%s\") failed: %s",
+ SM_PATH_DEVNULL, sm_errstring(errno));
+
+ /*
+ ** output to the transcript
+ ** We also compare the fd numbers here since OutChannel
+ ** might be a layer on top of smioout due to encryption
+ ** (see sfsasl.c).
+ */
+
+ if (OutChannel != smioout &&
+ sm_io_getinfo(OutChannel, SM_IO_WHAT_FD, NULL) !=
+ sm_io_getinfo(smioout, SM_IO_WHAT_FD, NULL))
+ {
+ (void) sm_io_close(OutChannel, SM_TIME_DEFAULT);
+ OutChannel = smioout;
+
+#if 0
+ /*
+ ** Has smioout been closed? Reopen it.
+ ** This shouldn't happen anymore, the code is here
+ ** just as a reminder.
+ */
+
+ if (smioout->sm_magic == NULL &&
+ sm_io_reopen(SmFtStdio, SM_TIME_DEFAULT, SM_PATH_DEVNULL,
+ SM_IO_WRONLY, NULL, smioout) == NULL)
+ sm_syslog(LOG_ERR, e->e_id,
+ "disconnect: sm_io_reopen(\"%s\") failed: %s",
+ SM_PATH_DEVNULL, sm_errstring(errno));
+#endif /* 0 */
+ }
+ if (droplev > 0)
+ {
+ fd = open(SM_PATH_DEVNULL, O_WRONLY, 0666);
+ if (fd == -1)
+ sm_syslog(LOG_ERR, e->e_id,
+ "disconnect: open(\"%s\") failed: %s",
+ SM_PATH_DEVNULL, sm_errstring(errno));
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ (void) dup2(fd, STDOUT_FILENO);
+ (void) dup2(fd, STDERR_FILENO);
+ (void) close(fd);
+ }
+
+ /* drop our controlling TTY completely if possible */
+ if (droplev > 1)
+ {
+ (void) setsid();
+ errno = 0;
+ }
+
+#if XDEBUG
+ checkfd012("disconnect");
+#endif /* XDEBUG */
+
+ if (LogLevel > 71)
+ sm_syslog(LOG_DEBUG, e->e_id, "in background, pid=%d",
+ (int) CurrentPid);
+
+ errno = 0;
+}
+
+static void
+obsolete(argv)
+ char *argv[];
+{
+ register char *ap;
+ register char *op;
+
+ while ((ap = *++argv) != NULL)
+ {
+ /* Return if "--" or not an option of any form. */
+ if (ap[0] != '-' || ap[1] == '-')
+ return;
+
+#if _FFR_QUARANTINE
+ /* Don't allow users to use "-Q." or "-Q ." */
+ if ((ap[1] == 'Q' && ap[2] == '.') ||
+ (ap[1] == 'Q' && argv[1] != NULL &&
+ argv[1][0] == '.' && argv[1][1] == '\0'))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Can not use -Q.\n");
+ exit(EX_USAGE);
+ }
+#endif /* _FFR_QUARANTINE */
+
+ /* skip over options that do have a value */
+ op = strchr(OPTIONS, ap[1]);
+ if (op != NULL && *++op == ':' && ap[2] == '\0' &&
+ ap[1] != 'd' &&
+#if defined(sony_news)
+ ap[1] != 'E' && ap[1] != 'J' &&
+#endif /* defined(sony_news) */
+ argv[1] != NULL && argv[1][0] != '-')
+ {
+ argv++;
+ continue;
+ }
+
+ /* If -C doesn't have an argument, use sendmail.cf. */
+#define __DEFPATH "sendmail.cf"
+ if (ap[1] == 'C' && ap[2] == '\0')
+ {
+ *argv = xalloc(sizeof(__DEFPATH) + 2);
+ (void) sm_strlcpyn(argv[0], sizeof(__DEFPATH) + 2, 2,
+ "-C", __DEFPATH);
+ }
+
+ /* If -q doesn't have an argument, run it once. */
+ if (ap[1] == 'q' && ap[2] == '\0')
+ *argv = "-q0";
+
+#if _FFR_QUARANTINE
+ /* If -Q doesn't have an argument, disable quarantining */
+ if (ap[1] == 'Q' && ap[2] == '\0')
+ *argv = "-Q.";
+#endif /* _FFR_QUARANTINE */
+
+ /* if -d doesn't have an argument, use 0-99.1 */
+ if (ap[1] == 'd' && ap[2] == '\0')
+ *argv = "-d0-99.1";
+
+#if defined(sony_news)
+ /* if -E doesn't have an argument, use -EC */
+ if (ap[1] == 'E' && ap[2] == '\0')
+ *argv = "-EC";
+
+ /* if -J doesn't have an argument, use -JJ */
+ if (ap[1] == 'J' && ap[2] == '\0')
+ *argv = "-JJ";
+#endif /* defined(sony_news) */
+ }
+}
+/*
+** AUTH_WARNING -- specify authorization warning
+**
+** Parameters:
+** e -- the current envelope.
+** msg -- the text of the message.
+** args -- arguments to the message.
+**
+** Returns:
+** none.
+*/
+
+void
+#ifdef __STDC__
+auth_warning(register ENVELOPE *e, const char *msg, ...)
+#else /* __STDC__ */
+auth_warning(e, msg, va_alist)
+ register ENVELOPE *e;
+ const char *msg;
+ va_dcl
+#endif /* __STDC__ */
+{
+ char buf[MAXLINE];
+ SM_VA_LOCAL_DECL
+
+ if (bitset(PRIV_AUTHWARNINGS, PrivacyFlags))
+ {
+ register char *p;
+ static char hostbuf[48];
+
+ if (hostbuf[0] == '\0')
+ {
+ struct hostent *hp;
+
+ hp = myhostname(hostbuf, sizeof hostbuf);
+#if NETINET6
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+#endif /* NETINET6 */
+ }
+
+ (void) sm_strlcpyn(buf, sizeof buf, 2, hostbuf, ": ");
+ p = &buf[strlen(buf)];
+ SM_VA_START(ap, msg);
+ (void) sm_vsnprintf(p, SPACELEFT(buf, p), msg, ap);
+ SM_VA_END(ap);
+ addheader("X-Authentication-Warning", buf, 0, e);
+ if (LogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Authentication-Warning: %.400s",
+ buf);
+ }
+}
+/*
+** GETEXTENV -- get from external environment
+**
+** Parameters:
+** envar -- the name of the variable to retrieve
+**
+** Returns:
+** The value, if any.
+*/
+
+static char *
+getextenv(envar)
+ const char *envar;
+{
+ char **envp;
+ int l;
+
+ l = strlen(envar);
+ for (envp = ExternalEnviron; envp != NULL && *envp != NULL; envp++)
+ {
+ if (strncmp(*envp, envar, l) == 0 && (*envp)[l] == '=')
+ return &(*envp)[l + 1];
+ }
+ return NULL;
+}
+/*
+** SETUSERENV -- set an environment in the propagated environment
+**
+** Parameters:
+** envar -- the name of the environment variable.
+** value -- the value to which it should be set. If
+** null, this is extracted from the incoming
+** environment. If that is not set, the call
+** to setuserenv is ignored.
+**
+** Returns:
+** none.
+*/
+
+void
+setuserenv(envar, value)
+ const char *envar;
+ const char *value;
+{
+ int i, l;
+ char **evp = UserEnviron;
+ char *p;
+
+ if (value == NULL)
+ {
+ value = getextenv(envar);
+ if (value == NULL)
+ return;
+ }
+
+ /* XXX enforce reasonable size? */
+ i = strlen(envar) + 1;
+ l = strlen(value) + i + 1;
+ p = (char *) xalloc(l);
+ (void) sm_strlcpyn(p, l, 3, envar, "=", value);
+
+ while (*evp != NULL && strncmp(*evp, p, i) != 0)
+ evp++;
+ if (*evp != NULL)
+ {
+ *evp++ = p;
+ }
+ else if (evp < &UserEnviron[MAXUSERENVIRON])
+ {
+ *evp++ = p;
+ *evp = NULL;
+ }
+
+ /* make sure it is in our environment as well */
+ if (putenv(p) < 0)
+ syserr("setuserenv: putenv(%s) failed", p);
+}
+/*
+** DUMPSTATE -- dump state
+**
+** For debugging.
+*/
+
+void
+dumpstate(when)
+ char *when;
+{
+ register char *j = macvalue('j', CurEnv);
+ int rs;
+ extern int NextMacroId;
+
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "--- dumping state on %s: $j = %s ---",
+ when,
+ j == NULL ? "<NULL>" : j);
+ if (j != NULL)
+ {
+ if (!wordinclass(j, 'w'))
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "*** $j not in $=w ***");
+ }
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "CurChildren = %d", CurChildren);
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "NextMacroId = %d (Max %d)",
+ NextMacroId, MAXMACROID);
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "--- open file descriptors: ---");
+ printopenfds(true);
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "--- connection cache: ---");
+ mci_dump_all(true);
+ rs = strtorwset("debug_dumpstate", NULL, ST_FIND);
+ if (rs > 0)
+ {
+ int status;
+ register char **pvp;
+ char *pv[MAXATOM + 1];
+
+ pv[0] = NULL;
+ status = REWRITE(pv, rs, CurEnv);
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "--- ruleset debug_dumpstate returns stat %d, pv: ---",
+ status);
+ for (pvp = pv; *pvp != NULL; pvp++)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s", *pvp);
+ }
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "--- end of state dump ---");
+}
+
+#ifdef SIGUSR1
+/*
+** SIGUSR1 -- Signal a request to dump state.
+**
+** Parameters:
+** sig -- calling signal.
+**
+** Returns:
+** none.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+**
+** XXX: More work is needed for this signal handler.
+*/
+
+/* ARGSUSED */
+static SIGFUNC_DECL
+sigusr1(sig)
+ int sig;
+{
+ int save_errno = errno;
+# if SM_HEAP_CHECK
+ extern void dumpstab __P((void));
+# endif /* SM_HEAP_CHECK */
+
+ FIX_SYSV_SIGNAL(sig, sigusr1);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ dumpstate("user signal");
+# if SM_HEAP_CHECK
+ dumpstab();
+# endif /* SM_HEAP_CHECK */
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+#endif /* SIGUSR1 */
+
+/*
+** DROP_PRIVILEGES -- reduce privileges to those of the RunAsUser option
+**
+** Parameters:
+** to_real_uid -- if set, drop to the real uid instead
+** of the RunAsUser.
+**
+** Returns:
+** EX_OSERR if the setuid failed.
+** EX_OK otherwise.
+*/
+
+int
+drop_privileges(to_real_uid)
+ bool to_real_uid;
+{
+ int rval = EX_OK;
+ GIDSET_T emptygidset[1];
+
+ if (tTd(47, 1))
+ sm_dprintf("drop_privileges(%d): Real[UG]id=%d:%d, get[ug]id=%d:%d, gete[ug]id=%d:%d, RunAs[UG]id=%d:%d\n",
+ (int) to_real_uid,
+ (int) RealUid, (int) RealGid,
+ (int) getuid(), (int) getgid(),
+ (int) geteuid(), (int) getegid(),
+ (int) RunAsUid, (int) RunAsGid);
+
+ if (to_real_uid)
+ {
+ RunAsUserName = RealUserName;
+ RunAsUid = RealUid;
+ RunAsGid = RealGid;
+ EffGid = RunAsGid;
+ }
+
+ /* make sure no one can grab open descriptors for secret files */
+ endpwent();
+ sm_mbdb_terminate();
+
+ /* reset group permissions; these can be set later */
+ emptygidset[0] = (to_real_uid || RunAsGid != 0) ? RunAsGid : getegid();
+
+ /*
+ ** Notice: on some OS (Linux...) the setgroups() call causes
+ ** a logfile entry if sendmail is not run by root.
+ ** However, it is unclear (no POSIX standard) whether
+ ** setgroups() can only succeed if executed by root.
+ ** So for now we keep it as it is; if you want to change it, use
+ ** if (geteuid() == 0 && setgroups(1, emptygidset) == -1)
+ */
+
+ if (setgroups(1, emptygidset) == -1 && geteuid() == 0)
+ {
+ syserr("drop_privileges: setgroups(1, %d) failed",
+ (int) emptygidset[0]);
+ rval = EX_OSERR;
+ }
+
+ /* reset primary group id */
+ if (to_real_uid)
+ {
+ /*
+ ** Drop gid to real gid.
+ ** On some OS we must reset the effective[/real[/saved]] gid,
+ ** and then use setgid() to finally drop all group privileges.
+ ** Later on we check whether we can get back the
+ ** effective gid.
+ */
+
+#if HASSETEGID
+ if (setegid(RunAsGid) < 0)
+ {
+ syserr("drop_privileges: setegid(%d) failed",
+ (int) RunAsGid);
+ rval = EX_OSERR;
+ }
+#else /* HASSETEGID */
+# if HASSETREGID
+ if (setregid(RunAsGid, RunAsGid) < 0)
+ {
+ syserr("drop_privileges: setregid(%d, %d) failed",
+ (int) RunAsGid, (int) RunAsGid);
+ rval = EX_OSERR;
+ }
+# else /* HASSETREGID */
+# if HASSETRESGID
+ if (setresgid(RunAsGid, RunAsGid, RunAsGid) < 0)
+ {
+ syserr("drop_privileges: setresgid(%d, %d, %d) failed",
+ (int) RunAsGid, (int) RunAsGid, (int) RunAsGid);
+ rval = EX_OSERR;
+ }
+# endif /* HASSETRESGID */
+# endif /* HASSETREGID */
+#endif /* HASSETEGID */
+ }
+ if (rval == EX_OK && (to_real_uid || RunAsGid != 0))
+ {
+ if (setgid(RunAsGid) < 0 && (!UseMSP || getegid() != RunAsGid))
+ {
+ syserr("drop_privileges: setgid(%d) failed",
+ (int) RunAsGid);
+ rval = EX_OSERR;
+ }
+ errno = 0;
+ if (rval == EX_OK && getegid() != RunAsGid)
+ {
+ syserr("drop_privileges: Unable to set effective gid=%d to RunAsGid=%d",
+ (int) getegid(), (int) RunAsGid);
+ rval = EX_OSERR;
+ }
+ }
+
+ /* fiddle with uid */
+ if (to_real_uid || RunAsUid != 0)
+ {
+ uid_t euid;
+
+ /*
+ ** Try to setuid(RunAsUid).
+ ** euid must be RunAsUid,
+ ** ruid must be RunAsUid unless (e|r)uid wasn't 0
+ ** and we didn't have to drop privileges to the real uid.
+ */
+
+ if (setuid(RunAsUid) < 0 ||
+ geteuid() != RunAsUid ||
+ (getuid() != RunAsUid &&
+ (to_real_uid || geteuid() == 0 || getuid() == 0)))
+ {
+#if HASSETREUID
+ /*
+ ** if ruid != RunAsUid, euid == RunAsUid, then
+ ** try resetting just the real uid, then using
+ ** setuid() to drop the saved-uid as well.
+ */
+
+ if (geteuid() == RunAsUid)
+ {
+ if (setreuid(RunAsUid, -1) < 0)
+ {
+ syserr("drop_privileges: setreuid(%d, -1) failed",
+ (int) RunAsUid);
+ rval = EX_OSERR;
+ }
+ if (setuid(RunAsUid) < 0)
+ {
+ syserr("drop_privileges: second setuid(%d) attempt failed",
+ (int) RunAsUid);
+ rval = EX_OSERR;
+ }
+ }
+ else
+#endif /* HASSETREUID */
+ {
+ syserr("drop_privileges: setuid(%d) failed",
+ (int) RunAsUid);
+ rval = EX_OSERR;
+ }
+ }
+ euid = geteuid();
+ if (RunAsUid != 0 && setuid(0) == 0)
+ {
+ /*
+ ** Believe it or not, the Linux capability model
+ ** allows a non-root process to override setuid()
+ ** on a process running as root and prevent that
+ ** process from dropping privileges.
+ */
+
+ syserr("drop_privileges: setuid(0) succeeded (when it should not)");
+ rval = EX_OSERR;
+ }
+ else if (RunAsUid != euid && setuid(euid) == 0)
+ {
+ /*
+ ** Some operating systems will keep the saved-uid
+ ** if a non-root effective-uid calls setuid(real-uid)
+ ** making it possible to set it back again later.
+ */
+
+ syserr("drop_privileges: Unable to drop non-root set-user-ID privileges");
+ rval = EX_OSERR;
+ }
+ }
+
+ if ((to_real_uid || RunAsGid != 0) &&
+ rval == EX_OK && RunAsGid != EffGid &&
+ getuid() != 0 && geteuid() != 0)
+ {
+ errno = 0;
+ if (setgid(EffGid) == 0)
+ {
+ syserr("drop_privileges: setgid(%d) succeeded (when it should not)",
+ (int) EffGid);
+ rval = EX_OSERR;
+ }
+ }
+
+ if (tTd(47, 5))
+ {
+ sm_dprintf("drop_privileges: e/ruid = %d/%d e/rgid = %d/%d\n",
+ (int) geteuid(), (int) getuid(),
+ (int) getegid(), (int) getgid());
+ sm_dprintf("drop_privileges: RunAsUser = %d:%d\n",
+ (int) RunAsUid, (int) RunAsGid);
+ if (tTd(47, 10))
+ sm_dprintf("drop_privileges: rval = %d\n", rval);
+ }
+ return rval;
+}
+/*
+** FILL_FD -- make sure a file descriptor has been properly allocated
+**
+** Used to make sure that stdin/out/err are allocated on startup
+**
+** Parameters:
+** fd -- the file descriptor to be filled.
+** where -- a string used for logging. If NULL, this is
+** being called on startup, and logging should
+** not be done.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** possibly changes MissingFds
+*/
+
+void
+fill_fd(fd, where)
+ int fd;
+ char *where;
+{
+ int i;
+ struct stat stbuf;
+
+ if (fstat(fd, &stbuf) >= 0 || errno != EBADF)
+ return;
+
+ if (where != NULL)
+ syserr("fill_fd: %s: fd %d not open", where, fd);
+ else
+ MissingFds |= 1 << fd;
+ i = open(SM_PATH_DEVNULL, fd == 0 ? O_RDONLY : O_WRONLY, 0666);
+ if (i < 0)
+ {
+ syserr("!fill_fd: %s: cannot open %s",
+ where == NULL ? "startup" : where, SM_PATH_DEVNULL);
+ }
+ if (fd != i)
+ {
+ (void) dup2(i, fd);
+ (void) close(i);
+ }
+}
+/*
+** SM_PRINTOPTIONS -- print options
+**
+** Parameters:
+** options -- array of options.
+**
+** Returns:
+** none.
+*/
+
+static void
+sm_printoptions(options)
+ char **options;
+{
+ int ll;
+ char **av;
+
+ av = options;
+ ll = 7;
+ while (*av != NULL)
+ {
+ if (ll + strlen(*av) > 63)
+ {
+ sm_dprintf("\n");
+ ll = 0;
+ }
+ if (ll == 0)
+ sm_dprintf("\t\t");
+ else
+ sm_dprintf(" ");
+ sm_dprintf("%s", *av);
+ ll += strlen(*av++) + 1;
+ }
+ sm_dprintf("\n");
+}
+/*
+** TESTMODELINE -- process a test mode input line
+**
+** Parameters:
+** line -- the input line.
+** e -- the current environment.
+** Syntax:
+** # a comment
+** .X process X as a configuration line
+** =X dump a configuration item (such as mailers)
+** $X dump a macro or class
+** /X try an activity
+** X normal process through rule set X
+*/
+
+static void
+testmodeline(line, e)
+ char *line;
+ ENVELOPE *e;
+{
+ register char *p;
+ char *q;
+ auto char *delimptr;
+ int mid;
+ int i, rs;
+ STAB *map;
+ char **s;
+ struct rewrite *rw;
+ ADDRESS a;
+ static int tryflags = RF_COPYNONE;
+ char exbuf[MAXLINE];
+ extern unsigned char TokTypeNoC[];
+
+ /* skip leading spaces */
+ while (*line == ' ')
+ line++;
+
+ switch (line[0])
+ {
+ case '#':
+ case '\0':
+ return;
+
+ case '?':
+ help("-bt", e);
+ return;
+
+ case '.': /* config-style settings */
+ switch (line[1])
+ {
+ case 'D':
+ mid = macid_parse(&line[2], &delimptr);
+ if (mid == 0)
+ return;
+ translate_dollars(delimptr);
+ macdefine(&e->e_macro, A_TEMP, mid, delimptr);
+ break;
+
+ case 'C':
+ if (line[2] == '\0') /* not to call syserr() */
+ return;
+
+ mid = macid_parse(&line[2], &delimptr);
+ if (mid == 0)
+ return;
+ translate_dollars(delimptr);
+ expand(delimptr, exbuf, sizeof exbuf, e);
+ p = exbuf;
+ while (*p != '\0')
+ {
+ register char *wd;
+ char delim;
+
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ wd = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ delim = *p;
+ *p = '\0';
+ if (wd[0] != '\0')
+ setclass(mid, wd);
+ *p = delim;
+ }
+ break;
+
+ case '\0':
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: .[DC]macro value(s)\n");
+ break;
+
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Unknown \".\" command %s\n", line);
+ break;
+ }
+ return;
+
+ case '=': /* config-style settings */
+ switch (line[1])
+ {
+ case 'S': /* dump rule set */
+ rs = strtorwset(&line[2], NULL, ST_FIND);
+ if (rs < 0)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Undefined ruleset %s\n", &line[2]);
+ return;
+ }
+ rw = RewriteRules[rs];
+ if (rw == NULL)
+ return;
+ do
+ {
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT,
+ 'R');
+ s = rw->r_lhs;
+ while (*s != NULL)
+ {
+ xputs(*s++);
+ (void) sm_io_putc(smioout,
+ SM_TIME_DEFAULT, ' ');
+ }
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT,
+ '\t');
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT,
+ '\t');
+ s = rw->r_rhs;
+ while (*s != NULL)
+ {
+ xputs(*s++);
+ (void) sm_io_putc(smioout,
+ SM_TIME_DEFAULT, ' ');
+ }
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT,
+ '\n');
+ } while ((rw = rw->r_next) != NULL);
+ break;
+
+ case 'M':
+ for (i = 0; i < MAXMAILERS; i++)
+ {
+ if (Mailer[i] != NULL)
+ printmailer(Mailer[i]);
+ }
+ break;
+
+ case '\0':
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: =Sruleset or =M\n");
+ break;
+
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Unknown \"=\" command %s\n", line);
+ break;
+ }
+ return;
+
+ case '-': /* set command-line-like opts */
+ switch (line[1])
+ {
+ case 'd':
+ tTflag(&line[2]);
+ break;
+
+ case '\0':
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: -d{debug arguments}\n");
+ break;
+
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Unknown \"-\" command %s\n", line);
+ break;
+ }
+ return;
+
+ case '$':
+ if (line[1] == '=')
+ {
+ mid = macid(&line[2]);
+ if (mid != 0)
+ stabapply(dump_class, mid);
+ return;
+ }
+ mid = macid(&line[1]);
+ if (mid == 0)
+ return;
+ p = macvalue(mid, e);
+ if (p == NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Undefined\n");
+ else
+ {
+ xputs(p);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\n");
+ }
+ return;
+
+ case '/': /* miscellaneous commands */
+ p = &line[strlen(line)];
+ while (--p >= line && isascii(*p) && isspace(*p))
+ *p = '\0';
+ p = strpbrk(line, " \t");
+ if (p != NULL)
+ {
+ while (isascii(*p) && isspace(*p))
+ *p++ = '\0';
+ }
+ else
+ p = "";
+ if (line[1] == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /[canon|map|mx|parse|try|tryflags]\n");
+ return;
+ }
+ if (sm_strcasecmp(&line[1], "quit") == 0)
+ {
+ CurEnv->e_id = NULL;
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ if (sm_strcasecmp(&line[1], "mx") == 0)
+ {
+#if NAMED_BIND
+ /* look up MX records */
+ int nmx;
+ auto int rcode;
+ char *mxhosts[MAXMXHOSTS + 1];
+
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /mx address\n");
+ return;
+ }
+ nmx = getmxrr(p, mxhosts, NULL, false, &rcode, true,
+ NULL);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "getmxrr(%s) returns %d value(s):\n",
+ p, nmx);
+ for (i = 0; i < nmx; i++)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\t%s\n", mxhosts[i]);
+#else /* NAMED_BIND */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "No MX code compiled in\n");
+#endif /* NAMED_BIND */
+ }
+ else if (sm_strcasecmp(&line[1], "canon") == 0)
+ {
+ char host[MAXHOSTNAMELEN];
+
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /canon address\n");
+ return;
+ }
+ else if (sm_strlcpy(host, p, sizeof host) >= sizeof host)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Name too long\n");
+ return;
+ }
+ (void) getcanonname(host, sizeof host, HasWildcardMX,
+ NULL);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "getcanonname(%s) returns %s\n",
+ p, host);
+ }
+ else if (sm_strcasecmp(&line[1], "map") == 0)
+ {
+ auto int rcode = EX_OK;
+ char *av[2];
+
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /map mapname key\n");
+ return;
+ }
+ for (q = p; *q != '\0' && !(isascii(*q) && isspace(*q)); q++)
+ continue;
+ if (*q == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "No key specified\n");
+ return;
+ }
+ *q++ = '\0';
+ map = stab(p, ST_MAP, ST_FIND);
+ if (map == NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Map named \"%s\" not found\n", p);
+ return;
+ }
+ if (!bitset(MF_OPEN, map->s_map.map_mflags) &&
+ !openmap(&(map->s_map)))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Map named \"%s\" not open\n", p);
+ return;
+ }
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "map_lookup: %s (%s) ", p, q);
+ av[0] = q;
+ av[1] = NULL;
+ p = (*map->s_map.map_class->map_lookup)
+ (&map->s_map, q, av, &rcode);
+ if (p == NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "no match (%d)\n",
+ rcode);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "returns %s (%d)\n", p,
+ rcode);
+ }
+ else if (sm_strcasecmp(&line[1], "try") == 0)
+ {
+ MAILER *m;
+ STAB *st;
+ auto int rcode = EX_OK;
+
+ q = strpbrk(p, " \t");
+ if (q != NULL)
+ {
+ while (isascii(*q) && isspace(*q))
+ *q++ = '\0';
+ }
+ if (q == NULL || *q == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /try mailer address\n");
+ return;
+ }
+ st = stab(p, ST_MAILER, ST_FIND);
+ if (st == NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Unknown mailer %s\n", p);
+ return;
+ }
+ m = st->s_mailer;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Trying %s %s address %s for mailer %s\n",
+ bitset(RF_HEADERADDR, tryflags) ? "header"
+ : "envelope",
+ bitset(RF_SENDERADDR, tryflags) ? "sender"
+ : "recipient", q, p);
+ p = remotename(q, m, tryflags, &rcode, CurEnv);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Rcode = %d, addr = %s\n",
+ rcode, p == NULL ? "<NULL>" : p);
+ e->e_to = NULL;
+ }
+ else if (sm_strcasecmp(&line[1], "tryflags") == 0)
+ {
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /tryflags [Hh|Ee][Ss|Rr]\n");
+ return;
+ }
+ for (; *p != '\0'; p++)
+ {
+ switch (*p)
+ {
+ case 'H':
+ case 'h':
+ tryflags |= RF_HEADERADDR;
+ break;
+
+ case 'E':
+ case 'e':
+ tryflags &= ~RF_HEADERADDR;
+ break;
+
+ case 'S':
+ case 's':
+ tryflags |= RF_SENDERADDR;
+ break;
+
+ case 'R':
+ case 'r':
+ tryflags &= ~RF_SENDERADDR;
+ break;
+ }
+ }
+ exbuf[0] = bitset(RF_HEADERADDR, tryflags) ? 'h' : 'e';
+ exbuf[1] = ' ';
+ exbuf[2] = bitset(RF_SENDERADDR, tryflags) ? 's' : 'r';
+ exbuf[3] = '\0';
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{addr_type}"), exbuf);
+ }
+ else if (sm_strcasecmp(&line[1], "parse") == 0)
+ {
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Usage: /parse address\n");
+ return;
+ }
+ q = crackaddr(p);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Cracked address = ");
+ xputs(q);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\nParsing %s %s address\n",
+ bitset(RF_HEADERADDR, tryflags) ?
+ "header" : "envelope",
+ bitset(RF_SENDERADDR, tryflags) ?
+ "sender" : "recipient");
+ if (parseaddr(p, &a, tryflags, '\0', NULL, e, true)
+ == NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Cannot parse\n");
+ else if (a.q_host != NULL && a.q_host[0] != '\0')
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "mailer %s, host %s, user %s\n",
+ a.q_mailer->m_name,
+ a.q_host,
+ a.q_user);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "mailer %s, user %s\n",
+ a.q_mailer->m_name,
+ a.q_user);
+ e->e_to = NULL;
+ }
+ else
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Unknown \"/\" command %s\n",
+ line);
+ }
+ return;
+ }
+
+ for (p = line; isascii(*p) && isspace(*p); p++)
+ continue;
+ q = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p == '\0')
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "No address!\n");
+ return;
+ }
+ *p = '\0';
+ if (invalidaddr(p + 1, NULL, true))
+ return;
+ do
+ {
+ register char **pvp;
+ char pvpbuf[PSBUFSIZE];
+
+ pvp = prescan(++p, ',', pvpbuf, sizeof pvpbuf,
+ &delimptr, ConfigLevel >= 9 ? TokTypeNoC : NULL);
+ if (pvp == NULL)
+ continue;
+ p = q;
+ while (*p != '\0')
+ {
+ int status;
+
+ rs = strtorwset(p, NULL, ST_FIND);
+ if (rs < 0)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Undefined ruleset %s\n",
+ p);
+ break;
+ }
+ status = REWRITE(pvp, rs, e);
+ if (status != EX_OK)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "== Ruleset %s (%d) status %d\n",
+ p, rs, status);
+ while (*p != '\0' && *p++ != ',')
+ continue;
+ }
+ } while (*(p = delimptr) != '\0');
+}
+
+static void
+dump_class(s, id)
+ register STAB *s;
+ int id;
+{
+ if (s->s_symtype != ST_CLASS)
+ return;
+ if (bitnset(bitidx(id), s->s_class))
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s\n", s->s_name);
+}
+
+/*
+** An exception type used to create QuickAbort exceptions.
+** This is my first cut at converting QuickAbort from longjmp to exceptions.
+** These exceptions have a single integer argument, which is the argument
+** to longjmp in the original code (either 1 or 2). I don't know the
+** significance of 1 vs 2: the calls to setjmp don't care.
+*/
+
+const SM_EXC_TYPE_T EtypeQuickAbort =
+{
+ SmExcTypeMagic,
+ "E:mta.quickabort",
+ "i",
+ sm_etype_printf,
+ "quick abort %0",
+};
diff --git a/contrib/sendmail/src/map.c b/contrib/sendmail/src/map.c
new file mode 100644
index 0000000..feba80b
--- /dev/null
+++ b/contrib/sendmail/src/map.c
@@ -0,0 +1,7372 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1992, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: map.c,v 8.645.2.3 2002/08/09 22:23:13 gshapiro Exp $")
+
+#if LDAPMAP
+# include <sm/ldap.h>
+#endif /* LDAPMAP */
+
+#if NDBM
+# include <ndbm.h>
+# ifdef R_FIRST
+ ERROR README: You are running the Berkeley DB version of ndbm.h. See
+ ERROR README: the README file about tweaking Berkeley DB so it can
+ ERROR README: coexist with NDBM, or delete -DNDBM from the Makefile
+ ERROR README: and use -DNEWDB instead.
+# endif /* R_FIRST */
+#endif /* NDBM */
+#if NEWDB
+# include <db.h>
+# ifndef DB_VERSION_MAJOR
+# define DB_VERSION_MAJOR 1
+# endif /* ! DB_VERSION_MAJOR */
+#endif /* NEWDB */
+#if NIS
+ struct dom_binding; /* forward reference needed on IRIX */
+# include <rpcsvc/ypclnt.h>
+# if NDBM
+# define NDBM_YP_COMPAT /* create YP-compatible NDBM files */
+# endif /* NDBM */
+#endif /* NIS */
+
+#if NEWDB
+# if DB_VERSION_MAJOR < 2
+static bool db_map_open __P((MAP *, int, char *, DBTYPE, const void *));
+# endif /* DB_VERSION_MAJOR < 2 */
+# if DB_VERSION_MAJOR == 2
+static bool db_map_open __P((MAP *, int, char *, DBTYPE, DB_INFO *));
+# endif /* DB_VERSION_MAJOR == 2 */
+# if DB_VERSION_MAJOR > 2
+static bool db_map_open __P((MAP *, int, char *, DBTYPE, void **));
+# endif /* DB_VERSION_MAJOR > 2 */
+#endif /* NEWDB */
+static bool extract_canonname __P((char *, char *, char *, char[], int));
+static void map_close __P((STAB *, int));
+static void map_init __P((STAB *, int));
+#ifdef LDAPMAP
+static STAB * ldapmap_findconn __P((SM_LDAP_STRUCT *));
+#endif /* LDAPMAP */
+#if NISPLUS
+static bool nisplus_getcanonname __P((char *, int, int *));
+#endif /* NISPLUS */
+#if NIS
+static bool nis_getcanonname __P((char *, int, int *));
+#endif /* NIS */
+#if NETINFO
+static bool ni_getcanonname __P((char *, int, int *));
+#endif /* NETINFO */
+static bool text_getcanonname __P((char *, int, int *));
+
+/* default error message for trying to open a map in write mode */
+#ifdef ENOSYS
+# define SM_EMAPCANTWRITE ENOSYS
+#else /* ENOSYS */
+# ifdef EFTYPE
+# define SM_EMAPCANTWRITE EFTYPE
+# else /* EFTYPE */
+# define SM_EMAPCANTWRITE ENXIO
+# endif /* EFTYPE */
+#endif /* ENOSYS */
+
+/*
+** MAP.C -- implementations for various map classes.
+**
+** Each map class implements a series of functions:
+**
+** bool map_parse(MAP *map, char *args)
+** Parse the arguments from the config file. Return true
+** if they were ok, false otherwise. Fill in map with the
+** values.
+**
+** char *map_lookup(MAP *map, char *key, char **args, int *pstat)
+** Look up the key in the given map. If found, do any
+** rewriting the map wants (including "args" if desired)
+** and return the value. Set *pstat to the appropriate status
+** on error and return NULL. Args will be NULL if called
+** from the alias routines, although this should probably
+** not be relied upon. It is suggested you call map_rewrite
+** to return the results -- it takes care of null termination
+** and uses a dynamically expanded buffer as needed.
+**
+** void map_store(MAP *map, char *key, char *value)
+** Store the key:value pair in the map.
+**
+** bool map_open(MAP *map, int mode)
+** Open the map for the indicated mode. Mode should
+** be either O_RDONLY or O_RDWR. Return true if it
+** was opened successfully, false otherwise. If the open
+** failed and the MF_OPTIONAL flag is not set, it should
+** also print an error. If the MF_ALIAS bit is set
+** and this map class understands the @:@ convention, it
+** should call aliaswait() before returning.
+**
+** void map_close(MAP *map)
+** Close the map.
+**
+** This file also includes the implementation for getcanonname.
+** It is currently implemented in a pretty ad-hoc manner; it ought
+** to be more properly integrated into the map structure.
+*/
+
+#if O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL
+# define LOCK_ON_OPEN 1 /* we can open/create a locked file */
+#else /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
+# define LOCK_ON_OPEN 0 /* no such luck -- bend over backwards */
+#endif /* O_EXLOCK && HASFLOCK && !BOGUS_O_EXCL */
+
+/*
+** MAP_PARSEARGS -- parse config line arguments for database lookup
+**
+** This is a generic version of the map_parse method.
+**
+** Parameters:
+** map -- the map being initialized.
+** ap -- a pointer to the args on the config line.
+**
+** Returns:
+** true -- if everything parsed OK.
+** false -- otherwise.
+**
+** Side Effects:
+** null terminates the filename; stores it in map
+*/
+
+bool
+map_parseargs(map, ap)
+ MAP *map;
+ char *ap;
+{
+ register char *p = ap;
+
+ /*
+ ** There is no check whether there is really an argument,
+ ** but that's not important enough to warrant extra code.
+ */
+
+ map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
+ map->map_spacesub = SpaceSub; /* default value */
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'N':
+ map->map_mflags |= MF_INCLNULL;
+ map->map_mflags &= ~MF_TRY0NULL;
+ break;
+
+ case 'O':
+ map->map_mflags &= ~MF_TRY1NULL;
+ break;
+
+ case 'o':
+ map->map_mflags |= MF_OPTIONAL;
+ break;
+
+ case 'f':
+ map->map_mflags |= MF_NOFOLDCASE;
+ break;
+
+ case 'm':
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 'A':
+ map->map_mflags |= MF_APPEND;
+ break;
+
+ case 'q':
+ map->map_mflags |= MF_KEEPQUOTES;
+ break;
+
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'T':
+ map->map_tapp = ++p;
+ break;
+
+ case 'k':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ map->map_keycolnm = p;
+ break;
+
+ case 'v':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ map->map_valcolnm = p;
+ break;
+
+ case 'z':
+ if (*++p != '\\')
+ map->map_coldelim = *p;
+ else
+ {
+ switch (*++p)
+ {
+ case 'n':
+ map->map_coldelim = '\n';
+ break;
+
+ case 't':
+ map->map_coldelim = '\t';
+ break;
+
+ default:
+ map->map_coldelim = '\\';
+ }
+ }
+ break;
+
+ case 't':
+ map->map_mflags |= MF_NODEFER;
+ break;
+
+
+ case 'S':
+ map->map_spacesub = *++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ default:
+ syserr("Illegal option %c map %s", *p, map->map_mname);
+ break;
+ }
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+ if (map->map_app != NULL)
+ map->map_app = newstr(map->map_app);
+ if (map->map_tapp != NULL)
+ map->map_tapp = newstr(map->map_tapp);
+ if (map->map_keycolnm != NULL)
+ map->map_keycolnm = newstr(map->map_keycolnm);
+ if (map->map_valcolnm != NULL)
+ map->map_valcolnm = newstr(map->map_valcolnm);
+
+ if (*p != '\0')
+ {
+ map->map_file = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ map->map_file = newstr(map->map_file);
+ }
+
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '\0')
+ map->map_rebuild = newstr(p);
+
+ if (map->map_file == NULL &&
+ !bitset(MCF_OPTFILE, map->map_class->map_cflags))
+ {
+ syserr("No file name for %s map %s",
+ map->map_class->map_cname, map->map_mname);
+ return false;
+ }
+ return true;
+}
+/*
+** MAP_REWRITE -- rewrite a database key, interpolating %n indications.
+**
+** It also adds the map_app string. It can be used as a utility
+** in the map_lookup method.
+**
+** Parameters:
+** map -- the map that causes this.
+** s -- the string to rewrite, NOT necessarily null terminated.
+** slen -- the length of s.
+** av -- arguments to interpolate into buf.
+**
+** Returns:
+** Pointer to rewritten result. This is static data that
+** should be copied if it is to be saved!
+*/
+
+char *
+map_rewrite(map, s, slen, av)
+ register MAP *map;
+ register const char *s;
+ size_t slen;
+ char **av;
+{
+ register char *bp;
+ register char c;
+ char **avp;
+ register char *ap;
+ size_t l;
+ size_t len;
+ static size_t buflen = 0;
+ static char *buf = NULL;
+
+ if (tTd(39, 1))
+ {
+ sm_dprintf("map_rewrite(%.*s), av =", (int) slen, s);
+ if (av == NULL)
+ sm_dprintf(" (nullv)");
+ else
+ {
+ for (avp = av; *avp != NULL; avp++)
+ sm_dprintf("\n\t%s", *avp);
+ }
+ sm_dprintf("\n");
+ }
+
+ /* count expected size of output (can safely overestimate) */
+ l = len = slen;
+ if (av != NULL)
+ {
+ const char *sp = s;
+
+ while (l-- > 0 && (c = *sp++) != '\0')
+ {
+ if (c != '%')
+ continue;
+ if (l-- <= 0)
+ break;
+ c = *sp++;
+ if (!(isascii(c) && isdigit(c)))
+ continue;
+ for (avp = av; --c >= '0' && *avp != NULL; avp++)
+ continue;
+ if (*avp == NULL)
+ continue;
+ len += strlen(*avp);
+ }
+ }
+ if (map->map_app != NULL)
+ len += strlen(map->map_app);
+ if (buflen < ++len)
+ {
+ /* need to malloc additional space */
+ buflen = len;
+ if (buf != NULL)
+ sm_free(buf);
+ buf = sm_pmalloc_x(buflen);
+ }
+
+ bp = buf;
+ if (av == NULL)
+ {
+ memmove(bp, s, slen);
+ bp += slen;
+
+ /* assert(len > slen); */
+ len -= slen;
+ }
+ else
+ {
+ while (slen-- > 0 && (c = *s++) != '\0')
+ {
+ if (c != '%')
+ {
+ pushc:
+ if (--len <= 0)
+ break;
+ *bp++ = c;
+ continue;
+ }
+ if (slen-- <= 0 || (c = *s++) == '\0')
+ c = '%';
+ if (c == '%')
+ goto pushc;
+ if (!(isascii(c) && isdigit(c)))
+ {
+ *bp++ = '%';
+ --len;
+ goto pushc;
+ }
+ for (avp = av; --c >= '0' && *avp != NULL; avp++)
+ continue;
+ if (*avp == NULL)
+ continue;
+
+ /* transliterate argument into output string */
+ for (ap = *avp; (c = *ap++) != '\0' && len > 0; --len)
+ *bp++ = c;
+ }
+ }
+ if (map->map_app != NULL && len > 0)
+ (void) sm_strlcpy(bp, map->map_app, len);
+ else
+ *bp = '\0';
+ if (tTd(39, 1))
+ sm_dprintf("map_rewrite => %s\n", buf);
+ return buf;
+}
+/*
+** INITMAPS -- rebuild alias maps
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+*/
+
+void
+initmaps()
+{
+#if XDEBUG
+ checkfd012("entering initmaps");
+#endif /* XDEBUG */
+ stabapply(map_init, 0);
+#if XDEBUG
+ checkfd012("exiting initmaps");
+#endif /* XDEBUG */
+}
+/*
+** MAP_INIT -- rebuild a map
+**
+** Parameters:
+** s -- STAB entry: if map: try to rebuild
+** unused -- unused variable
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** will close already open rebuildable map.
+*/
+
+/* ARGSUSED1 */
+static void
+map_init(s, unused)
+ register STAB *s;
+ int unused;
+{
+ register MAP *map;
+
+ /* has to be a map */
+ if (s->s_symtype != ST_MAP)
+ return;
+
+ map = &s->s_map;
+ if (!bitset(MF_VALID, map->map_mflags))
+ return;
+
+ if (tTd(38, 2))
+ sm_dprintf("map_init(%s:%s, %s)\n",
+ map->map_class->map_cname == NULL ? "NULL" :
+ map->map_class->map_cname,
+ map->map_mname == NULL ? "NULL" : map->map_mname,
+ map->map_file == NULL ? "NULL" : map->map_file);
+
+ if (!bitset(MF_ALIAS, map->map_mflags) ||
+ !bitset(MCF_REBUILDABLE, map->map_class->map_cflags))
+ {
+ if (tTd(38, 3))
+ sm_dprintf("\tnot rebuildable\n");
+ return;
+ }
+
+ /* if already open, close it (for nested open) */
+ if (bitset(MF_OPEN, map->map_mflags))
+ {
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ }
+
+ (void) rebuildaliases(map, false);
+ return;
+}
+/*
+** OPENMAP -- open a map
+**
+** Parameters:
+** map -- map to open (it must not be open).
+**
+** Returns:
+** whether open succeeded.
+*/
+
+bool
+openmap(map)
+ MAP *map;
+{
+ bool restore = false;
+ bool savehold = HoldErrs;
+ bool savequick = QuickAbort;
+ int saveerrors = Errors;
+
+ if (!bitset(MF_VALID, map->map_mflags))
+ return false;
+
+ /* better safe than sorry... */
+ if (bitset(MF_OPEN, map->map_mflags))
+ return true;
+
+ /* Don't send a map open error out via SMTP */
+ if ((OnlyOneError || QuickAbort) &&
+ (OpMode == MD_SMTP || OpMode == MD_DAEMON))
+ {
+ restore = true;
+ HoldErrs = true;
+ QuickAbort = false;
+ }
+
+ errno = 0;
+ if (map->map_class->map_open(map, O_RDONLY))
+ {
+ if (tTd(38, 4))
+ sm_dprintf("openmap()\t%s:%s %s: valid\n",
+ map->map_class->map_cname == NULL ? "NULL" :
+ map->map_class->map_cname,
+ map->map_mname == NULL ? "NULL" :
+ map->map_mname,
+ map->map_file == NULL ? "NULL" :
+ map->map_file);
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ }
+ else
+ {
+ if (tTd(38, 4))
+ sm_dprintf("openmap()\t%s:%s %s: invalid%s%s\n",
+ map->map_class->map_cname == NULL ? "NULL" :
+ map->map_class->map_cname,
+ map->map_mname == NULL ? "NULL" :
+ map->map_mname,
+ map->map_file == NULL ? "NULL" :
+ map->map_file,
+ errno == 0 ? "" : ": ",
+ errno == 0 ? "" : sm_errstring(errno));
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ extern MAPCLASS BogusMapClass;
+
+ map->map_orgclass = map->map_class;
+ map->map_class = &BogusMapClass;
+ map->map_mflags |= MF_OPEN|MF_OPENBOGUS;
+ map->map_pid = CurrentPid;
+ }
+ else
+ {
+ /* don't try again */
+ map->map_mflags &= ~MF_VALID;
+ }
+ }
+
+ if (restore)
+ {
+ Errors = saveerrors;
+ HoldErrs = savehold;
+ QuickAbort = savequick;
+ }
+
+ return bitset(MF_OPEN, map->map_mflags);
+}
+/*
+** CLOSEMAPS -- close all open maps opened by the current pid.
+**
+** Parameters:
+** bogus -- only close bogus maps.
+**
+** Returns:
+** none.
+*/
+
+void
+closemaps(bogus)
+ bool bogus;
+{
+ stabapply(map_close, bogus);
+}
+/*
+** MAP_CLOSE -- close a map opened by the current pid.
+**
+** Parameters:
+** s -- STAB entry: if map: try to close
+** bogus -- only close bogus maps or MCF_NOTPERSIST maps.
+**
+** Returns:
+** none.
+*/
+
+/* ARGSUSED1 */
+static void
+map_close(s, bogus)
+ register STAB *s;
+ int bogus; /* int because of stabapply(), used as bool */
+{
+ MAP *map;
+ extern MAPCLASS BogusMapClass;
+
+ if (s->s_symtype != ST_MAP)
+ return;
+
+ map = &s->s_map;
+
+ /*
+ ** close the map iff:
+ ** it is valid and open and opened by this process
+ ** and (!bogus or it's a bogus map or it is not persistent)
+ ** negate this: return iff
+ ** it is not valid or it is not open or not opened by this process
+ ** or (bogus and it's not a bogus map and it's not not-persistent)
+ */
+
+ if (!bitset(MF_VALID, map->map_mflags) ||
+ !bitset(MF_OPEN, map->map_mflags) ||
+ bitset(MF_CLOSING, map->map_mflags) ||
+ map->map_pid != CurrentPid ||
+ (bogus && map->map_class != &BogusMapClass &&
+ !bitset(MCF_NOTPERSIST, map->map_class->map_cflags)))
+ return;
+
+ if (map->map_class == &BogusMapClass && map->map_orgclass != NULL &&
+ map->map_orgclass != &BogusMapClass)
+ map->map_class = map->map_orgclass;
+ if (tTd(38, 5))
+ sm_dprintf("closemaps: closing %s (%s)\n",
+ map->map_mname == NULL ? "NULL" : map->map_mname,
+ map->map_file == NULL ? "NULL" : map->map_file);
+
+ if (!bitset(MF_OPENBOGUS, map->map_mflags))
+ {
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ }
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_OPENBOGUS|MF_CLOSING);
+}
+/*
+** GETCANONNAME -- look up name using service switch
+**
+** Parameters:
+** host -- the host name to look up.
+** hbsize -- the size of the host buffer.
+** trymx -- if set, try MX records.
+** pttl -- pointer to return TTL (can be NULL).
+**
+** Returns:
+** true -- if the host was found.
+** false -- otherwise.
+*/
+
+bool
+getcanonname(host, hbsize, trymx, pttl)
+ char *host;
+ int hbsize;
+ bool trymx;
+ int *pttl;
+{
+ int nmaps;
+ int mapno;
+ bool found = false;
+ bool got_tempfail = false;
+ auto int status;
+ char *maptype[MAXMAPSTACK];
+ short mapreturn[MAXMAPACTIONS];
+
+ nmaps = switch_map_find("hosts", maptype, mapreturn);
+ if (pttl != 0)
+ *pttl = SM_DEFAULT_TTL;
+ for (mapno = 0; mapno < nmaps; mapno++)
+ {
+ int i;
+
+ if (tTd(38, 20))
+ sm_dprintf("getcanonname(%s), trying %s\n",
+ host, maptype[mapno]);
+ if (strcmp("files", maptype[mapno]) == 0)
+ {
+ found = text_getcanonname(host, hbsize, &status);
+ }
+#if NIS
+ else if (strcmp("nis", maptype[mapno]) == 0)
+ {
+ found = nis_getcanonname(host, hbsize, &status);
+ }
+#endif /* NIS */
+#if NISPLUS
+ else if (strcmp("nisplus", maptype[mapno]) == 0)
+ {
+ found = nisplus_getcanonname(host, hbsize, &status);
+ }
+#endif /* NISPLUS */
+#if NAMED_BIND
+ else if (strcmp("dns", maptype[mapno]) == 0)
+ {
+ found = dns_getcanonname(host, hbsize, trymx, &status, pttl);
+ }
+#endif /* NAMED_BIND */
+#if NETINFO
+ else if (strcmp("netinfo", maptype[mapno]) == 0)
+ {
+ found = ni_getcanonname(host, hbsize, &status);
+ }
+#endif /* NETINFO */
+ else
+ {
+ found = false;
+ status = EX_UNAVAILABLE;
+ }
+
+ /*
+ ** Heuristic: if $m is not set, we are running during system
+ ** startup. In this case, when a name is apparently found
+ ** but has no dot, treat is as not found. This avoids
+ ** problems if /etc/hosts has no FQDN but is listed first
+ ** in the service switch.
+ */
+
+ if (found &&
+ (macvalue('m', CurEnv) != NULL || strchr(host, '.') != NULL))
+ break;
+
+ /* see if we should continue */
+ if (status == EX_TEMPFAIL)
+ {
+ i = MA_TRYAGAIN;
+ got_tempfail = true;
+ }
+ else if (status == EX_NOTFOUND)
+ i = MA_NOTFOUND;
+ else
+ i = MA_UNAVAIL;
+ if (bitset(1 << mapno, mapreturn[i]))
+ break;
+ }
+
+ if (found)
+ {
+ char *d;
+
+ if (tTd(38, 20))
+ sm_dprintf("getcanonname(%s), found\n", host);
+
+ /*
+ ** If returned name is still single token, compensate
+ ** by tagging on $m. This is because some sites set
+ ** up their DNS or NIS databases wrong.
+ */
+
+ if ((d = strchr(host, '.')) == NULL || d[1] == '\0')
+ {
+ d = macvalue('m', CurEnv);
+ if (d != NULL &&
+ hbsize > (int) (strlen(host) + strlen(d) + 1))
+ {
+ if (host[strlen(host) - 1] != '.')
+ (void) sm_strlcat2(host, ".", d,
+ hbsize);
+ else
+ (void) sm_strlcat(host, d, hbsize);
+ }
+ else
+ return false;
+ }
+ return true;
+ }
+
+ if (tTd(38, 20))
+ sm_dprintf("getcanonname(%s), failed, status=%d\n", host,
+ status);
+
+ if (got_tempfail)
+ SM_SET_H_ERRNO(TRY_AGAIN);
+ else
+ SM_SET_H_ERRNO(HOST_NOT_FOUND);
+
+ return false;
+}
+/*
+** EXTRACT_CANONNAME -- extract canonical name from /etc/hosts entry
+**
+** Parameters:
+** name -- the name against which to match.
+** dot -- where to reinsert '.' to get FQDN
+** line -- the /etc/hosts line.
+** cbuf -- the location to store the result.
+** cbuflen -- the size of cbuf.
+**
+** Returns:
+** true -- if the line matched the desired name.
+** false -- otherwise.
+*/
+
+static bool
+extract_canonname(name, dot, line, cbuf, cbuflen)
+ char *name;
+ char *dot;
+ char *line;
+ char cbuf[];
+ int cbuflen;
+{
+ int i;
+ char *p;
+ bool found = false;
+
+ cbuf[0] = '\0';
+ if (line[0] == '#')
+ return false;
+
+ for (i = 1; ; i++)
+ {
+ char nbuf[MAXNAME + 1];
+
+ p = get_column(line, i, '\0', nbuf, sizeof nbuf);
+ if (p == NULL)
+ break;
+ if (*p == '\0')
+ continue;
+ if (cbuf[0] == '\0' ||
+ (strchr(cbuf, '.') == NULL && strchr(p, '.') != NULL))
+ {
+ (void) sm_strlcpy(cbuf, p, cbuflen);
+ }
+ if (sm_strcasecmp(name, p) == 0)
+ found = true;
+ else if (dot != NULL)
+ {
+ /* try looking for the FQDN as well */
+ *dot = '.';
+ if (sm_strcasecmp(name, p) == 0)
+ found = true;
+ *dot = '\0';
+ }
+ }
+ if (found && strchr(cbuf, '.') == NULL)
+ {
+ /* try to add a domain on the end of the name */
+ char *domain = macvalue('m', CurEnv);
+
+ if (domain != NULL &&
+ strlen(domain) + (i = strlen(cbuf)) + 1 < (size_t) cbuflen)
+ {
+ p = &cbuf[i];
+ *p++ = '.';
+ (void) sm_strlcpy(p, domain, cbuflen - i - 1);
+ }
+ }
+ return found;
+}
+
+/*
+** DNS modules
+*/
+
+#if NAMED_BIND
+# if DNSMAP
+
+# include "sm_resolve.h"
+# if NETINET || NETINET6
+# include <arpa/inet.h>
+# endif /* NETINET || NETINET6 */
+
+/*
+** DNS_MAP_OPEN -- stub to check proper value for dns map type
+*/
+
+bool
+dns_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ if (tTd(38,2))
+ sm_dprintf("dns_map_open(%s, %d)\n", map->map_mname, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+ return true;
+}
+
+/*
+** DNS_MAP_PARSEARGS -- parse dns map definition args.
+**
+** Parameters:
+** map -- pointer to MAP
+** args -- pointer to the args on the config line.
+**
+** Returns:
+** true -- if everything parsed OK.
+** false -- otherwise.
+*/
+
+# if _FFR_DNSMAP_MULTILIMIT
+# if !_FFR_DNSMAP_MULTI
+ ERROR README: You must define _FFR_DNSMAP_MULTI to use _FFR_DNSMAP_MULTILIMIT
+# endif /* ! _FFR_DNSMAP_MULTI */
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+
+# if _FFR_DNSMAP_MULTI
+# if _FFR_DNSMAP_MULTILIMIT
+# define map_sizelimit map_lockfd /* overload field */
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+# endif /* _FFR_DNSMAP_MULTI */
+
+struct dns_map
+{
+ int dns_m_type;
+};
+
+bool
+dns_map_parseargs(map,args)
+ MAP *map;
+ char *args;
+{
+ register char *p = args;
+ struct dns_map *map_p;
+
+ map_p = (struct dns_map *) xalloc(sizeof *map_p);
+ map_p->dns_m_type = -1;
+ map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
+
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'N':
+ map->map_mflags |= MF_INCLNULL;
+ map->map_mflags &= ~MF_TRY0NULL;
+ break;
+
+ case 'O':
+ map->map_mflags &= ~MF_TRY1NULL;
+ break;
+
+ case 'o':
+ map->map_mflags |= MF_OPTIONAL;
+ break;
+
+ case 'f':
+ map->map_mflags |= MF_NOFOLDCASE;
+ break;
+
+ case 'm':
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 'A':
+ map->map_mflags |= MF_APPEND;
+ break;
+
+ case 'q':
+ map->map_mflags |= MF_KEEPQUOTES;
+ break;
+
+ case 't':
+ map->map_mflags |= MF_NODEFER;
+ break;
+
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'T':
+ map->map_tapp = ++p;
+ break;
+
+ case 'd':
+ {
+ char *h;
+
+ ++p;
+ h = strchr(p, ' ');
+ if (h != NULL)
+ *h = '\0';
+ map->map_timeout = convtime(p, 's');
+ if (h != NULL)
+ *h = ' ';
+ }
+ break;
+
+ case 'r':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ map->map_retry = atoi(p);
+ break;
+
+# if _FFR_DNSMAP_MULTI
+ case 'z':
+ if (*++p != '\\')
+ map->map_coldelim = *p;
+ else
+ {
+ switch (*++p)
+ {
+ case 'n':
+ map->map_coldelim = '\n';
+ break;
+
+ case 't':
+ map->map_coldelim = '\t';
+ break;
+
+ default:
+ map->map_coldelim = '\\';
+ }
+ }
+ break;
+
+# if _FFR_DNSMAP_MULTILIMIT
+ case 'Z':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ map->map_sizelimit = atoi(p);
+ break;
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+# endif /* _FFR_DNSMAP_MULTI */
+
+ /* Start of dns_map specific args */
+ case 'R': /* search field */
+ {
+ char *h;
+
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ h = strchr(p, ' ');
+ if (h != NULL)
+ *h = '\0';
+ map_p->dns_m_type = dns_string_to_type(p);
+ if (h != NULL)
+ *h = ' ';
+ if (map_p->dns_m_type < 0)
+ syserr("dns map %s: wrong type %s",
+ map->map_mname, p);
+ }
+ break;
+
+# if _FFR_DNSMAP_BASE
+ case 'B': /* base domain */
+ {
+ char *h;
+
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ h = strchr(p, ' ');
+ if (h != NULL)
+ *h = '\0';
+
+ /*
+ ** slight abuse of map->map_file; it isn't
+ ** used otherwise in this map type.
+ */
+
+ map->map_file = newstr(p);
+ if (h != NULL)
+ *h = ' ';
+ }
+ break;
+# endif /* _FFR_DNSMAP_BASE */
+
+ }
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+ if (map_p->dns_m_type < 0)
+ syserr("dns map %s: missing -R type", map->map_mname);
+ if (map->map_app != NULL)
+ map->map_app = newstr(map->map_app);
+ if (map->map_tapp != NULL)
+ map->map_tapp = newstr(map->map_tapp);
+
+ /*
+ ** Assumption: assert(sizeof int <= sizeof(ARBPTR_T));
+ ** Even if this assumption is wrong, we use only one byte,
+ ** so it doesn't really matter.
+ */
+
+ map->map_db1 = (ARBPTR_T) map_p;
+ return true;
+}
+
+/*
+** DNS_MAP_LOOKUP -- perform dns map lookup.
+**
+** Parameters:
+** map -- pointer to MAP
+** name -- name to lookup
+** av -- arguments to interpolate into buf.
+** statp -- pointer to status (EX_)
+**
+** Returns:
+** result of lookup if succeeded.
+** NULL -- otherwise.
+*/
+
+char *
+dns_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+# if _FFR_DNSMAP_MULTI
+# if _FFR_DNSMAP_MULTILIMIT
+ int resnum = 0;
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+# endif /* _FFR_DNSMAP_MULTI */
+ char *vp = NULL, *result = NULL;
+ size_t vsize;
+ struct dns_map *map_p;
+ RESOURCE_RECORD_T *rr = NULL;
+ DNS_REPLY_T *r = NULL;
+# if NETINET6
+ static char buf6[INET6_ADDRSTRLEN];
+# endif /* NETINET6 */
+
+ if (tTd(38, 20))
+ sm_dprintf("dns_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ map_p = (struct dns_map *)(map->map_db1);
+# if _FFR_DNSMAP_BASE
+ if (map->map_file != NULL && *map->map_file != '\0')
+ {
+ size_t len;
+ char *appdomain;
+
+ len = strlen(map->map_file) + strlen(name) + 2;
+ appdomain = (char *) sm_malloc(len);
+ if (appdomain == NULL)
+ {
+ *statp = EX_UNAVAILABLE;
+ return NULL;
+ }
+ (void) sm_strlcpyn(appdomain, len, 3, name, ".", map->map_file);
+ r = dns_lookup_int(appdomain, C_IN, map_p->dns_m_type,
+ map->map_timeout, map->map_retry);
+ sm_free(appdomain);
+ }
+ else
+# endif /* _FFR_DNSMAP_BASE */
+ {
+ r = dns_lookup_int(name, C_IN, map_p->dns_m_type,
+ map->map_timeout, map->map_retry);
+ }
+
+ if (r == NULL)
+ {
+ result = NULL;
+ if (errno == ETIMEDOUT || h_errno == TRY_AGAIN ||
+ errno == ECONNREFUSED)
+ *statp = EX_TEMPFAIL;
+ else
+ *statp = EX_NOTFOUND;
+ goto cleanup;
+ }
+ *statp = EX_OK;
+ for (rr = r->dns_r_head; rr != NULL; rr = rr->rr_next)
+ {
+ char *type = NULL;
+ char *value = NULL;
+
+ switch (rr->rr_type)
+ {
+ case T_NS:
+ type = "T_NS";
+ value = rr->rr_u.rr_txt;
+ break;
+ case T_CNAME:
+ type = "T_CNAME";
+ value = rr->rr_u.rr_txt;
+ break;
+ case T_AFSDB:
+ type = "T_AFSDB";
+ value = rr->rr_u.rr_mx->mx_r_domain;
+ break;
+ case T_SRV:
+ type = "T_SRV";
+ value = rr->rr_u.rr_srv->srv_r_target;
+ break;
+ case T_PTR:
+ type = "T_PTR";
+ value = rr->rr_u.rr_txt;
+ break;
+ case T_TXT:
+ type = "T_TXT";
+ value = rr->rr_u.rr_txt;
+ break;
+ case T_MX:
+ type = "T_MX";
+ value = rr->rr_u.rr_mx->mx_r_domain;
+ break;
+# if NETINET
+ case T_A:
+ type = "T_A";
+ value = inet_ntoa(*(rr->rr_u.rr_a));
+ break;
+# endif /* NETINET */
+# if NETINET6
+ case T_AAAA:
+ type = "T_AAAA";
+ value = anynet_ntop(rr->rr_u.rr_aaaa, buf6,
+ sizeof buf6);
+ break;
+# endif /* NETINET6 */
+ }
+
+ (void) strreplnonprt(value, 'X');
+ if (map_p->dns_m_type != rr->rr_type)
+ {
+ if (tTd(38, 40))
+ sm_dprintf("\tskipping type %s (%d) value %s\n",
+ type != NULL ? type : "<UNKNOWN>",
+ rr->rr_type,
+ value != NULL ? value : "<NO VALUE>");
+ continue;
+ }
+
+# if NETINET6
+ if (rr->rr_type == T_AAAA && value == NULL)
+ {
+ result = NULL;
+ *statp = EX_DATAERR;
+ if (tTd(38, 40))
+ sm_dprintf("\tbad T_AAAA conversion\n");
+ goto cleanup;
+ }
+# endif /* NETINET6 */
+ if (tTd(38, 40))
+ sm_dprintf("\tfound type %s (%d) value %s\n",
+ type != NULL ? type : "<UNKNOWN>",
+ rr->rr_type,
+ value != NULL ? value : "<NO VALUE>");
+# if _FFR_DNSMAP_MULTI
+ if (value != NULL &&
+ (map->map_coldelim == '\0' ||
+# if _FFR_DNSMAP_MULTILIMIT
+ map->map_sizelimit == 1 ||
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+ bitset(MF_MATCHONLY, map->map_mflags)))
+ {
+ /* Only care about the first match */
+ vp = newstr(value);
+ break;
+ }
+ else if (vp == NULL)
+ {
+ /* First result */
+ vp = newstr(value);
+ }
+ else
+ {
+ /* concatenate the results */
+ int sz;
+ char *new;
+
+ sz = strlen(vp) + strlen(value) + 2;
+ new = xalloc(sz);
+ (void) sm_snprintf(new, sz, "%s%c%s",
+ vp, map->map_coldelim, value);
+ sm_free(vp);
+ vp = new;
+# if _FFR_DNSMAP_MULTILIMIT
+ if (map->map_sizelimit > 0 &&
+ ++resnum >= map->map_sizelimit)
+ break;
+# endif /* _FFR_DNSMAP_MULTILIMIT */
+ }
+# else /* _FFR_DNSMAP_MULTI */
+ vp = value;
+ break;
+# endif /* _FFR_DNSMAP_MULTI */
+ }
+ if (vp == NULL)
+ {
+ result = NULL;
+ *statp = EX_NOTFOUND;
+ if (tTd(38, 40))
+ sm_dprintf("\tno match found\n");
+ goto cleanup;
+ }
+
+# if _FFR_DNSMAP_MULTI
+ /* Cleanly truncate for rulesets */
+ truncate_at_delim(vp, PSBUFSIZE / 2, map->map_coldelim);
+# endif /* _FFR_DNSMAP_MULTI */
+
+ vsize = strlen(vp);
+
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, CurEnv->e_id, "dns %.100s => %s",
+ name, vp);
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ result = map_rewrite(map, name, strlen(name), NULL);
+ else
+ result = map_rewrite(map, vp, vsize, av);
+
+ cleanup:
+# if _FFR_DNSMAP_MULTI
+ if (vp != NULL)
+ sm_free(vp);
+# endif /* _FFR_DNSMAP_MULTI */
+ if (r != NULL)
+ dns_free_data(r);
+ return result;
+}
+# endif /* DNSMAP */
+#endif /* NAMED_BIND */
+
+/*
+** NDBM modules
+*/
+
+#if NDBM
+
+/*
+** NDBM_MAP_OPEN -- DBM-style map open
+*/
+
+bool
+ndbm_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ register DBM *dbm;
+ int save_errno;
+ int dfd;
+ int pfd;
+ long sff;
+ int ret;
+ int smode = S_IREAD;
+ char dirfile[MAXPATHLEN];
+ char pagfile[MAXPATHLEN];
+ struct stat st;
+ struct stat std, stp;
+
+ if (tTd(38, 2))
+ sm_dprintf("ndbm_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+ map->map_lockfd = -1;
+ mode &= O_ACCMODE;
+
+ /* do initial file and directory checks */
+ if (sm_strlcpyn(dirfile, sizeof dirfile, 2,
+ map->map_file, ".dir") >= sizeof dirfile ||
+ sm_strlcpyn(pagfile, sizeof pagfile, 2,
+ map->map_file, ".pag") >= sizeof pagfile)
+ {
+ errno = 0;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("dbm map \"%s\": map file %s name too long",
+ map->map_mname, map->map_file);
+ return false;
+ }
+ sff = SFF_ROOTOK|SFF_REGONLY;
+ if (mode == O_RDWR)
+ {
+ sff |= SFF_CREAT;
+ if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
+ sff |= SFF_NOSLINK;
+ if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
+ sff |= SFF_NOHLINK;
+ smode = S_IWRITE;
+ }
+ else
+ {
+ if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ }
+ if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ ret = safefile(dirfile, RunAsUid, RunAsGid, RunAsUserName,
+ sff, smode, &std);
+ if (ret == 0)
+ ret = safefile(pagfile, RunAsUid, RunAsGid, RunAsUserName,
+ sff, smode, &stp);
+
+ if (ret != 0)
+ {
+ char *prob = "unsafe";
+
+ /* cannot open this map */
+ if (ret == ENOENT)
+ prob = "missing";
+ if (tTd(38, 2))
+ sm_dprintf("\t%s map file: %d\n", prob, ret);
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("dbm map \"%s\": %s map file %s",
+ map->map_mname, prob, map->map_file);
+ return false;
+ }
+ if (std.st_mode == ST_MODE_NOFILE)
+ mode |= O_CREAT|O_EXCL;
+
+# if LOCK_ON_OPEN
+ if (mode == O_RDONLY)
+ mode |= O_SHLOCK;
+ else
+ mode |= O_TRUNC|O_EXLOCK;
+# else /* LOCK_ON_OPEN */
+ if ((mode & O_ACCMODE) == O_RDWR)
+ {
+# if NOFTRUNCATE
+ /*
+ ** Warning: race condition. Try to lock the file as
+ ** quickly as possible after opening it.
+ ** This may also have security problems on some systems,
+ ** but there isn't anything we can do about it.
+ */
+
+ mode |= O_TRUNC;
+# else /* NOFTRUNCATE */
+ /*
+ ** This ugly code opens the map without truncating it,
+ ** locks the file, then truncates it. Necessary to
+ ** avoid race conditions.
+ */
+
+ int dirfd;
+ int pagfd;
+ long sff = SFF_CREAT|SFF_OPENASROOT;
+
+ if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
+ sff |= SFF_NOSLINK;
+ if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
+ sff |= SFF_NOHLINK;
+
+ dirfd = safeopen(dirfile, mode, DBMMODE, sff);
+ pagfd = safeopen(pagfile, mode, DBMMODE, sff);
+
+ if (dirfd < 0 || pagfd < 0)
+ {
+ save_errno = errno;
+ if (dirfd >= 0)
+ (void) close(dirfd);
+ if (pagfd >= 0)
+ (void) close(pagfd);
+ errno = save_errno;
+ syserr("ndbm_map_open: cannot create database %s",
+ map->map_file);
+ return false;
+ }
+ if (ftruncate(dirfd, (off_t) 0) < 0 ||
+ ftruncate(pagfd, (off_t) 0) < 0)
+ {
+ save_errno = errno;
+ (void) close(dirfd);
+ (void) close(pagfd);
+ errno = save_errno;
+ syserr("ndbm_map_open: cannot truncate %s.{dir,pag}",
+ map->map_file);
+ return false;
+ }
+
+ /* if new file, get "before" bits for later filechanged check */
+ if (std.st_mode == ST_MODE_NOFILE &&
+ (fstat(dirfd, &std) < 0 || fstat(pagfd, &stp) < 0))
+ {
+ save_errno = errno;
+ (void) close(dirfd);
+ (void) close(pagfd);
+ errno = save_errno;
+ syserr("ndbm_map_open(%s.{dir,pag}): cannot fstat pre-opened file",
+ map->map_file);
+ return false;
+ }
+
+ /* have to save the lock for the duration (bletch) */
+ map->map_lockfd = dirfd;
+ (void) close(pagfd);
+
+ /* twiddle bits for dbm_open */
+ mode &= ~(O_CREAT|O_EXCL);
+# endif /* NOFTRUNCATE */
+ }
+# endif /* LOCK_ON_OPEN */
+
+ /* open the database */
+ dbm = dbm_open(map->map_file, mode, DBMMODE);
+ if (dbm == NULL)
+ {
+ save_errno = errno;
+ if (bitset(MF_ALIAS, map->map_mflags) &&
+ aliaswait(map, ".pag", false))
+ return true;
+# if !LOCK_ON_OPEN && !NOFTRUNCATE
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
+ errno = save_errno;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("Cannot open DBM database %s", map->map_file);
+ return false;
+ }
+ dfd = dbm_dirfno(dbm);
+ pfd = dbm_pagfno(dbm);
+ if (dfd == pfd)
+ {
+ /* heuristic: if files are linked, this is actually gdbm */
+ dbm_close(dbm);
+# if !LOCK_ON_OPEN && !NOFTRUNCATE
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
+ errno = 0;
+ syserr("dbm map \"%s\": cannot support GDBM",
+ map->map_mname);
+ return false;
+ }
+
+ if (filechanged(dirfile, dfd, &std) ||
+ filechanged(pagfile, pfd, &stp))
+ {
+ save_errno = errno;
+ dbm_close(dbm);
+# if !LOCK_ON_OPEN && !NOFTRUNCATE
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN && !NOFTRUNCATE */
+ errno = save_errno;
+ syserr("ndbm_map_open(%s): file changed after open",
+ map->map_file);
+ return false;
+ }
+
+ map->map_db1 = (ARBPTR_T) dbm;
+
+ /*
+ ** Need to set map_mtime before the call to aliaswait()
+ ** as aliaswait() will call map_lookup() which requires
+ ** map_mtime to be set
+ */
+
+ if (fstat(pfd, &st) >= 0)
+ map->map_mtime = st.st_mtime;
+
+ if (mode == O_RDONLY)
+ {
+# if LOCK_ON_OPEN
+ if (dfd >= 0)
+ (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
+ if (pfd >= 0)
+ (void) lockfile(pfd, map->map_file, ".pag", LOCK_UN);
+# endif /* LOCK_ON_OPEN */
+ if (bitset(MF_ALIAS, map->map_mflags) &&
+ !aliaswait(map, ".pag", true))
+ return false;
+ }
+ else
+ {
+ map->map_mflags |= MF_LOCKED;
+ if (geteuid() == 0 && TrustedUid != 0)
+ {
+# if HASFCHOWN
+ if (fchown(dfd, TrustedUid, -1) < 0 ||
+ fchown(pfd, TrustedUid, -1) < 0)
+ {
+ int err = errno;
+
+ sm_syslog(LOG_ALERT, NOQID,
+ "ownership change on %s failed: %s",
+ map->map_file, sm_errstring(err));
+ message("050 ownership change on %s failed: %s",
+ map->map_file, sm_errstring(err));
+ }
+# else /* HASFCHOWN */
+ sm_syslog(LOG_ALERT, NOQID,
+ "no fchown(): cannot change ownership on %s",
+ map->map_file);
+ message("050 no fchown(): cannot change ownership on %s",
+ map->map_file);
+# endif /* HASFCHOWN */
+ }
+ }
+ return true;
+}
+
+
+/*
+** NDBM_MAP_LOOKUP -- look up a datum in a DBM-type map
+*/
+
+char *
+ndbm_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ datum key, val;
+ int dfd, pfd;
+ char keybuf[MAXNAME + 1];
+ struct stat stbuf;
+
+ if (tTd(38, 20))
+ sm_dprintf("ndbm_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ key.dptr = name;
+ key.dsize = strlen(name);
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+ if (key.dsize > sizeof keybuf - 1)
+ key.dsize = sizeof keybuf - 1;
+ memmove(keybuf, key.dptr, key.dsize);
+ keybuf[key.dsize] = '\0';
+ makelower(keybuf);
+ key.dptr = keybuf;
+ }
+lockdbm:
+ dfd = dbm_dirfno((DBM *) map->map_db1);
+ if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(dfd, map->map_file, ".dir", LOCK_SH);
+ pfd = dbm_pagfno((DBM *) map->map_db1);
+ if (pfd < 0 || fstat(pfd, &stbuf) < 0 ||
+ stbuf.st_mtime > map->map_mtime)
+ {
+ /* Reopen the database to sync the cache */
+ int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR
+ : O_RDONLY;
+
+ if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ if (map->map_class->map_open(map, omode))
+ {
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ if ((omode && O_ACCMODE) == O_RDWR)
+ map->map_mflags |= MF_WRITABLE;
+ goto lockdbm;
+ }
+ else
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ extern MAPCLASS BogusMapClass;
+
+ *statp = EX_TEMPFAIL;
+ map->map_orgclass = map->map_class;
+ map->map_class = &BogusMapClass;
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ syserr("Cannot reopen NDBM database %s",
+ map->map_file);
+ }
+ return NULL;
+ }
+ }
+ val.dptr = NULL;
+ if (bitset(MF_TRY0NULL, map->map_mflags))
+ {
+ val = dbm_fetch((DBM *) map->map_db1, key);
+ if (val.dptr != NULL)
+ map->map_mflags &= ~MF_TRY1NULL;
+ }
+ if (val.dptr == NULL && bitset(MF_TRY1NULL, map->map_mflags))
+ {
+ key.dsize++;
+ val = dbm_fetch((DBM *) map->map_db1, key);
+ if (val.dptr != NULL)
+ map->map_mflags &= ~MF_TRY0NULL;
+ }
+ if (dfd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(dfd, map->map_file, ".dir", LOCK_UN);
+ if (val.dptr == NULL)
+ return NULL;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ else
+ return map_rewrite(map, val.dptr, val.dsize, av);
+}
+
+
+/*
+** NDBM_MAP_STORE -- store a datum in the database
+*/
+
+void
+ndbm_map_store(map, lhs, rhs)
+ register MAP *map;
+ char *lhs;
+ char *rhs;
+{
+ datum key;
+ datum data;
+ int status;
+ char keybuf[MAXNAME + 1];
+
+ if (tTd(38, 12))
+ sm_dprintf("ndbm_map_store(%s, %s, %s)\n",
+ map->map_mname, lhs, rhs);
+
+ key.dsize = strlen(lhs);
+ key.dptr = lhs;
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+ if (key.dsize > sizeof keybuf - 1)
+ key.dsize = sizeof keybuf - 1;
+ memmove(keybuf, key.dptr, key.dsize);
+ keybuf[key.dsize] = '\0';
+ makelower(keybuf);
+ key.dptr = keybuf;
+ }
+
+ data.dsize = strlen(rhs);
+ data.dptr = rhs;
+
+ if (bitset(MF_INCLNULL, map->map_mflags))
+ {
+ key.dsize++;
+ data.dsize++;
+ }
+
+ status = dbm_store((DBM *) map->map_db1, key, data, DBM_INSERT);
+ if (status > 0)
+ {
+ if (!bitset(MF_APPEND, map->map_mflags))
+ message("050 Warning: duplicate alias name %s", lhs);
+ else
+ {
+ static char *buf = NULL;
+ static int bufsiz = 0;
+ auto int xstat;
+ datum old;
+
+ old.dptr = ndbm_map_lookup(map, key.dptr,
+ (char **) NULL, &xstat);
+ if (old.dptr != NULL && *(char *) old.dptr != '\0')
+ {
+ old.dsize = strlen(old.dptr);
+ if (data.dsize + old.dsize + 2 > bufsiz)
+ {
+ if (buf != NULL)
+ (void) sm_free(buf);
+ bufsiz = data.dsize + old.dsize + 2;
+ buf = sm_pmalloc_x(bufsiz);
+ }
+ (void) sm_strlcpyn(buf, bufsiz, 3,
+ data.dptr, ",", old.dptr);
+ data.dsize = data.dsize + old.dsize + 1;
+ data.dptr = buf;
+ if (tTd(38, 9))
+ sm_dprintf("ndbm_map_store append=%s\n",
+ data.dptr);
+ }
+ }
+ status = dbm_store((DBM *) map->map_db1,
+ key, data, DBM_REPLACE);
+ }
+ if (status != 0)
+ syserr("readaliases: dbm put (%s): %d", lhs, status);
+}
+
+
+/*
+** NDBM_MAP_CLOSE -- close the database
+*/
+
+void
+ndbm_map_close(map)
+ register MAP *map;
+{
+ if (tTd(38, 9))
+ sm_dprintf("ndbm_map_close(%s, %s, %lx)\n",
+ map->map_mname, map->map_file, map->map_mflags);
+
+ if (bitset(MF_WRITABLE, map->map_mflags))
+ {
+# ifdef NDBM_YP_COMPAT
+ bool inclnull;
+ char buf[MAXHOSTNAMELEN];
+
+ inclnull = bitset(MF_INCLNULL, map->map_mflags);
+ map->map_mflags &= ~MF_INCLNULL;
+
+ if (strstr(map->map_file, "/yp/") != NULL)
+ {
+ long save_mflags = map->map_mflags;
+
+ map->map_mflags |= MF_NOFOLDCASE;
+
+ (void) sm_snprintf(buf, sizeof buf, "%010ld", curtime());
+ ndbm_map_store(map, "YP_LAST_MODIFIED", buf);
+
+ (void) gethostname(buf, sizeof buf);
+ ndbm_map_store(map, "YP_MASTER_NAME", buf);
+
+ map->map_mflags = save_mflags;
+ }
+
+ if (inclnull)
+ map->map_mflags |= MF_INCLNULL;
+# endif /* NDBM_YP_COMPAT */
+
+ /* write out the distinguished alias */
+ ndbm_map_store(map, "@", "@");
+ }
+ dbm_close((DBM *) map->map_db1);
+
+ /* release lock (if needed) */
+# if !LOCK_ON_OPEN
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN */
+}
+
+#endif /* NDBM */
+/*
+** NEWDB (Hash and BTree) Modules
+*/
+
+#if NEWDB
+
+/*
+** BT_MAP_OPEN, HASH_MAP_OPEN -- database open primitives.
+**
+** These do rather bizarre locking. If you can lock on open,
+** do that to avoid the condition of opening a database that
+** is being rebuilt. If you don't, we'll try to fake it, but
+** there will be a race condition. If opening for read-only,
+** we immediately release the lock to avoid freezing things up.
+** We really ought to hold the lock, but guarantee that we won't
+** be pokey about it. That's hard to do.
+*/
+
+/* these should be K line arguments */
+# if DB_VERSION_MAJOR < 2
+# define db_cachesize cachesize
+# define h_nelem nelem
+# ifndef DB_CACHE_SIZE
+# define DB_CACHE_SIZE (1024 * 1024) /* database memory cache size */
+# endif /* ! DB_CACHE_SIZE */
+# ifndef DB_HASH_NELEM
+# define DB_HASH_NELEM 4096 /* (starting) size of hash table */
+# endif /* ! DB_HASH_NELEM */
+# endif /* DB_VERSION_MAJOR < 2 */
+
+bool
+bt_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+# if DB_VERSION_MAJOR < 2
+ BTREEINFO btinfo;
+# endif /* DB_VERSION_MAJOR < 2 */
+# if DB_VERSION_MAJOR == 2
+ DB_INFO btinfo;
+# endif /* DB_VERSION_MAJOR == 2 */
+# if DB_VERSION_MAJOR > 2
+ void *btinfo = NULL;
+# endif /* DB_VERSION_MAJOR > 2 */
+
+ if (tTd(38, 2))
+ sm_dprintf("bt_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+# if DB_VERSION_MAJOR < 3
+ memset(&btinfo, '\0', sizeof btinfo);
+# ifdef DB_CACHE_SIZE
+ btinfo.db_cachesize = DB_CACHE_SIZE;
+# endif /* DB_CACHE_SIZE */
+# endif /* DB_VERSION_MAJOR < 3 */
+
+ return db_map_open(map, mode, "btree", DB_BTREE, &btinfo);
+}
+
+bool
+hash_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+# if DB_VERSION_MAJOR < 2
+ HASHINFO hinfo;
+# endif /* DB_VERSION_MAJOR < 2 */
+# if DB_VERSION_MAJOR == 2
+ DB_INFO hinfo;
+# endif /* DB_VERSION_MAJOR == 2 */
+# if DB_VERSION_MAJOR > 2
+ void *hinfo = NULL;
+# endif /* DB_VERSION_MAJOR > 2 */
+
+ if (tTd(38, 2))
+ sm_dprintf("hash_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+# if DB_VERSION_MAJOR < 3
+ memset(&hinfo, '\0', sizeof hinfo);
+# ifdef DB_HASH_NELEM
+ hinfo.h_nelem = DB_HASH_NELEM;
+# endif /* DB_HASH_NELEM */
+# ifdef DB_CACHE_SIZE
+ hinfo.db_cachesize = DB_CACHE_SIZE;
+# endif /* DB_CACHE_SIZE */
+# endif /* DB_VERSION_MAJOR < 3 */
+
+ return db_map_open(map, mode, "hash", DB_HASH, &hinfo);
+}
+
+static bool
+db_map_open(map, mode, mapclassname, dbtype, openinfo)
+ MAP *map;
+ int mode;
+ char *mapclassname;
+ DBTYPE dbtype;
+# if DB_VERSION_MAJOR < 2
+ const void *openinfo;
+# endif /* DB_VERSION_MAJOR < 2 */
+# if DB_VERSION_MAJOR == 2
+ DB_INFO *openinfo;
+# endif /* DB_VERSION_MAJOR == 2 */
+# if DB_VERSION_MAJOR > 2
+ void **openinfo;
+# endif /* DB_VERSION_MAJOR > 2 */
+{
+ DB *db = NULL;
+ int i;
+ int omode;
+ int smode = S_IREAD;
+ int fd;
+ long sff;
+ int save_errno;
+ struct stat st;
+ char buf[MAXPATHLEN];
+
+ /* do initial file and directory checks */
+ if (sm_strlcpy(buf, map->map_file, sizeof buf) >= sizeof buf)
+ {
+ errno = 0;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("map \"%s\": map file %s name too long",
+ map->map_mname, map->map_file);
+ return false;
+ }
+ i = strlen(buf);
+ if (i < 3 || strcmp(&buf[i - 3], ".db") != 0)
+ {
+ if (sm_strlcat(buf, ".db", sizeof buf) >= sizeof buf)
+ {
+ errno = 0;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("map \"%s\": map file %s name too long",
+ map->map_mname, map->map_file);
+ return false;
+ }
+ }
+
+ mode &= O_ACCMODE;
+ omode = mode;
+
+ sff = SFF_ROOTOK|SFF_REGONLY;
+ if (mode == O_RDWR)
+ {
+ sff |= SFF_CREAT;
+ if (!bitnset(DBS_WRITEMAPTOSYMLINK, DontBlameSendmail))
+ sff |= SFF_NOSLINK;
+ if (!bitnset(DBS_WRITEMAPTOHARDLINK, DontBlameSendmail))
+ sff |= SFF_NOHLINK;
+ smode = S_IWRITE;
+ }
+ else
+ {
+ if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ }
+ if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ i = safefile(buf, RunAsUid, RunAsGid, RunAsUserName, sff, smode, &st);
+
+ if (i != 0)
+ {
+ char *prob = "unsafe";
+
+ /* cannot open this map */
+ if (i == ENOENT)
+ prob = "missing";
+ if (tTd(38, 2))
+ sm_dprintf("\t%s map file: %s\n", prob, sm_errstring(i));
+ errno = i;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("%s map \"%s\": %s map file %s",
+ mapclassname, map->map_mname, prob, buf);
+ return false;
+ }
+ if (st.st_mode == ST_MODE_NOFILE)
+ omode |= O_CREAT|O_EXCL;
+
+ map->map_lockfd = -1;
+
+# if LOCK_ON_OPEN
+ if (mode == O_RDWR)
+ omode |= O_TRUNC|O_EXLOCK;
+ else
+ omode |= O_SHLOCK;
+# else /* LOCK_ON_OPEN */
+ /*
+ ** Pre-lock the file to avoid race conditions. In particular,
+ ** since dbopen returns NULL if the file is zero length, we
+ ** must have a locked instance around the dbopen.
+ */
+
+ fd = open(buf, omode, DBMMODE);
+ if (fd < 0)
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("db_map_open: cannot pre-open database %s", buf);
+ return false;
+ }
+
+ /* make sure no baddies slipped in just before the open... */
+ if (filechanged(buf, fd, &st))
+ {
+ save_errno = errno;
+ (void) close(fd);
+ errno = save_errno;
+ syserr("db_map_open(%s): file changed after pre-open", buf);
+ return false;
+ }
+
+ /* if new file, get the "before" bits for later filechanged check */
+ if (st.st_mode == ST_MODE_NOFILE && fstat(fd, &st) < 0)
+ {
+ save_errno = errno;
+ (void) close(fd);
+ errno = save_errno;
+ syserr("db_map_open(%s): cannot fstat pre-opened file",
+ buf);
+ return false;
+ }
+
+ /* actually lock the pre-opened file */
+ if (!lockfile(fd, buf, NULL, mode == O_RDONLY ? LOCK_SH : LOCK_EX))
+ syserr("db_map_open: cannot lock %s", buf);
+
+ /* set up mode bits for dbopen */
+ if (mode == O_RDWR)
+ omode |= O_TRUNC;
+ omode &= ~(O_EXCL|O_CREAT);
+# endif /* LOCK_ON_OPEN */
+
+# if DB_VERSION_MAJOR < 2
+ db = dbopen(buf, omode, DBMMODE, dbtype, openinfo);
+# else /* DB_VERSION_MAJOR < 2 */
+ {
+ int flags = 0;
+# if DB_VERSION_MAJOR > 2
+ int ret;
+# endif /* DB_VERSION_MAJOR > 2 */
+
+ if (mode == O_RDONLY)
+ flags |= DB_RDONLY;
+ if (bitset(O_CREAT, omode))
+ flags |= DB_CREATE;
+ if (bitset(O_TRUNC, omode))
+ flags |= DB_TRUNCATE;
+
+# if !HASFLOCK && defined(DB_FCNTL_LOCKING)
+ flags |= DB_FCNTL_LOCKING;
+# endif /* !HASFLOCK && defined(DB_FCNTL_LOCKING) */
+
+# if DB_VERSION_MAJOR > 2
+ ret = db_create(&db, NULL, 0);
+# ifdef DB_CACHE_SIZE
+ if (ret == 0 && db != NULL)
+ {
+ ret = db->set_cachesize(db, 0, DB_CACHE_SIZE, 0);
+ if (ret != 0)
+ {
+ (void) db->close(db, 0);
+ db = NULL;
+ }
+ }
+# endif /* DB_CACHE_SIZE */
+# ifdef DB_HASH_NELEM
+ if (dbtype == DB_HASH && ret == 0 && db != NULL)
+ {
+ ret = db->set_h_nelem(db, DB_HASH_NELEM);
+ if (ret != 0)
+ {
+ (void) db->close(db, 0);
+ db = NULL;
+ }
+ }
+# endif /* DB_HASH_NELEM */
+ if (ret == 0 && db != NULL)
+ {
+ ret = db->open(db, buf, NULL, dbtype, flags, DBMMODE);
+ if (ret != 0)
+ {
+#ifdef DB_OLD_VERSION
+ if (ret == DB_OLD_VERSION)
+ ret = EINVAL;
+#endif /* DB_OLD_VERSION */
+ (void) db->close(db, 0);
+ db = NULL;
+ }
+ }
+ errno = ret;
+# else /* DB_VERSION_MAJOR > 2 */
+ errno = db_open(buf, dbtype, flags, DBMMODE,
+ NULL, openinfo, &db);
+# endif /* DB_VERSION_MAJOR > 2 */
+ }
+# endif /* DB_VERSION_MAJOR < 2 */
+ save_errno = errno;
+
+# if !LOCK_ON_OPEN
+ if (mode == O_RDWR)
+ map->map_lockfd = fd;
+ else
+ (void) close(fd);
+# endif /* !LOCK_ON_OPEN */
+
+ if (db == NULL)
+ {
+ if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
+ aliaswait(map, ".db", false))
+ return true;
+# if !LOCK_ON_OPEN
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN */
+ errno = save_errno;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("Cannot open %s database %s",
+ mapclassname, buf);
+ return false;
+ }
+
+# if DB_VERSION_MAJOR < 2
+ fd = db->fd(db);
+# else /* DB_VERSION_MAJOR < 2 */
+ fd = -1;
+ errno = db->fd(db, &fd);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (filechanged(buf, fd, &st))
+ {
+ save_errno = errno;
+# if DB_VERSION_MAJOR < 2
+ (void) db->close(db);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = db->close(db, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+# if !LOCK_ON_OPEN
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN */
+ errno = save_errno;
+ syserr("db_map_open(%s): file changed after open", buf);
+ return false;
+ }
+
+ if (mode == O_RDWR)
+ map->map_mflags |= MF_LOCKED;
+# if LOCK_ON_OPEN
+ if (fd >= 0 && mode == O_RDONLY)
+ {
+ (void) lockfile(fd, buf, NULL, LOCK_UN);
+ }
+# endif /* LOCK_ON_OPEN */
+
+ /* try to make sure that at least the database header is on disk */
+ if (mode == O_RDWR)
+ {
+ (void) db->sync(db, 0);
+ if (geteuid() == 0 && TrustedUid != 0)
+ {
+# if HASFCHOWN
+ if (fchown(fd, TrustedUid, -1) < 0)
+ {
+ int err = errno;
+
+ sm_syslog(LOG_ALERT, NOQID,
+ "ownership change on %s failed: %s",
+ buf, sm_errstring(err));
+ message("050 ownership change on %s failed: %s",
+ buf, sm_errstring(err));
+ }
+# else /* HASFCHOWN */
+ sm_syslog(LOG_ALERT, NOQID,
+ "no fchown(): cannot change ownership on %s",
+ map->map_file);
+ message("050 no fchown(): cannot change ownership on %s",
+ map->map_file);
+# endif /* HASFCHOWN */
+ }
+ }
+
+ map->map_db2 = (ARBPTR_T) db;
+
+ /*
+ ** Need to set map_mtime before the call to aliaswait()
+ ** as aliaswait() will call map_lookup() which requires
+ ** map_mtime to be set
+ */
+
+ if (fd >= 0 && fstat(fd, &st) >= 0)
+ map->map_mtime = st.st_mtime;
+
+ if (mode == O_RDONLY && bitset(MF_ALIAS, map->map_mflags) &&
+ !aliaswait(map, ".db", true))
+ return false;
+ return true;
+}
+
+
+/*
+** DB_MAP_LOOKUP -- look up a datum in a BTREE- or HASH-type map
+*/
+
+char *
+db_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ DBT key, val;
+ register DB *db = (DB *) map->map_db2;
+ int i;
+ int st;
+ int save_errno;
+ int fd;
+ struct stat stbuf;
+ char keybuf[MAXNAME + 1];
+ char buf[MAXPATHLEN];
+
+ memset(&key, '\0', sizeof key);
+ memset(&val, '\0', sizeof val);
+
+ if (tTd(38, 20))
+ sm_dprintf("db_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ if (sm_strlcpy(buf, map->map_file, sizeof buf) >= sizeof buf)
+ {
+ errno = 0;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("map \"%s\": map file %s name too long",
+ map->map_mname, map->map_file);
+ return NULL;
+ }
+ i = strlen(buf);
+ if (i > 3 && strcmp(&buf[i - 3], ".db") == 0)
+ buf[i - 3] = '\0';
+
+ key.size = strlen(name);
+ if (key.size > sizeof keybuf - 1)
+ key.size = sizeof keybuf - 1;
+ key.data = keybuf;
+ memmove(keybuf, name, key.size);
+ keybuf[key.size] = '\0';
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ makelower(keybuf);
+ lockdb:
+# if DB_VERSION_MAJOR < 2
+ fd = db->fd(db);
+# else /* DB_VERSION_MAJOR < 2 */
+ fd = -1;
+ errno = db->fd(db, &fd);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(fd, buf, ".db", LOCK_SH);
+ if (fd < 0 || fstat(fd, &stbuf) < 0 || stbuf.st_mtime > map->map_mtime)
+ {
+ /* Reopen the database to sync the cache */
+ int omode = bitset(map->map_mflags, MF_WRITABLE) ? O_RDWR
+ : O_RDONLY;
+
+ if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(fd, buf, ".db", LOCK_UN);
+ map->map_mflags |= MF_CLOSING;
+ map->map_class->map_close(map);
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ if (map->map_class->map_open(map, omode))
+ {
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ if ((omode && O_ACCMODE) == O_RDWR)
+ map->map_mflags |= MF_WRITABLE;
+ db = (DB *) map->map_db2;
+ goto lockdb;
+ }
+ else
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ extern MAPCLASS BogusMapClass;
+
+ *statp = EX_TEMPFAIL;
+ map->map_orgclass = map->map_class;
+ map->map_class = &BogusMapClass;
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ syserr("Cannot reopen DB database %s",
+ map->map_file);
+ }
+ return NULL;
+ }
+ }
+
+ st = 1;
+ if (bitset(MF_TRY0NULL, map->map_mflags))
+ {
+# if DB_VERSION_MAJOR < 2
+ st = db->get(db, &key, &val, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = db->get(db, NULL, &key, &val, 0);
+ switch (errno)
+ {
+ case DB_NOTFOUND:
+ case DB_KEYEMPTY:
+ st = 1;
+ break;
+
+ case 0:
+ st = 0;
+ break;
+
+ default:
+ st = -1;
+ break;
+ }
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (st == 0)
+ map->map_mflags &= ~MF_TRY1NULL;
+ }
+ if (st != 0 && bitset(MF_TRY1NULL, map->map_mflags))
+ {
+ key.size++;
+# if DB_VERSION_MAJOR < 2
+ st = db->get(db, &key, &val, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = db->get(db, NULL, &key, &val, 0);
+ switch (errno)
+ {
+ case DB_NOTFOUND:
+ case DB_KEYEMPTY:
+ st = 1;
+ break;
+
+ case 0:
+ st = 0;
+ break;
+
+ default:
+ st = -1;
+ break;
+ }
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (st == 0)
+ map->map_mflags &= ~MF_TRY0NULL;
+ }
+ save_errno = errno;
+ if (fd >= 0 && !bitset(MF_LOCKED, map->map_mflags))
+ (void) lockfile(fd, buf, ".db", LOCK_UN);
+ if (st != 0)
+ {
+ errno = save_errno;
+ if (st < 0)
+ syserr("db_map_lookup: get (%s)", name);
+ return NULL;
+ }
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ else
+ return map_rewrite(map, val.data, val.size, av);
+}
+
+
+/*
+** DB_MAP_STORE -- store a datum in the NEWDB database
+*/
+
+void
+db_map_store(map, lhs, rhs)
+ register MAP *map;
+ char *lhs;
+ char *rhs;
+{
+ int status;
+ DBT key;
+ DBT data;
+ register DB *db = map->map_db2;
+ char keybuf[MAXNAME + 1];
+
+ memset(&key, '\0', sizeof key);
+ memset(&data, '\0', sizeof data);
+
+ if (tTd(38, 12))
+ sm_dprintf("db_map_store(%s, %s, %s)\n",
+ map->map_mname, lhs, rhs);
+
+ key.size = strlen(lhs);
+ key.data = lhs;
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+ if (key.size > sizeof keybuf - 1)
+ key.size = sizeof keybuf - 1;
+ memmove(keybuf, key.data, key.size);
+ keybuf[key.size] = '\0';
+ makelower(keybuf);
+ key.data = keybuf;
+ }
+
+ data.size = strlen(rhs);
+ data.data = rhs;
+
+ if (bitset(MF_INCLNULL, map->map_mflags))
+ {
+ key.size++;
+ data.size++;
+ }
+
+# if DB_VERSION_MAJOR < 2
+ status = db->put(db, &key, &data, R_NOOVERWRITE);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = db->put(db, NULL, &key, &data, DB_NOOVERWRITE);
+ switch (errno)
+ {
+ case DB_KEYEXIST:
+ status = 1;
+ break;
+
+ case 0:
+ status = 0;
+ break;
+
+ default:
+ status = -1;
+ break;
+ }
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (status > 0)
+ {
+ if (!bitset(MF_APPEND, map->map_mflags))
+ message("050 Warning: duplicate alias name %s", lhs);
+ else
+ {
+ static char *buf = NULL;
+ static int bufsiz = 0;
+ DBT old;
+
+ memset(&old, '\0', sizeof old);
+
+ old.data = db_map_lookup(map, key.data,
+ (char **) NULL, &status);
+ if (old.data != NULL)
+ {
+ old.size = strlen(old.data);
+ if (data.size + old.size + 2 > (size_t) bufsiz)
+ {
+ if (buf != NULL)
+ sm_free(buf);
+ bufsiz = data.size + old.size + 2;
+ buf = sm_pmalloc_x(bufsiz);
+ }
+ (void) sm_strlcpyn(buf, bufsiz, 3,
+ (char *) data.data, ",",
+ (char *) old.data);
+ data.size = data.size + old.size + 1;
+ data.data = buf;
+ if (tTd(38, 9))
+ sm_dprintf("db_map_store append=%s\n",
+ (char *) data.data);
+ }
+ }
+# if DB_VERSION_MAJOR < 2
+ status = db->put(db, &key, &data, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ status = errno = db->put(db, NULL, &key, &data, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ }
+ if (status != 0)
+ syserr("readaliases: db put (%s)", lhs);
+}
+
+
+/*
+** DB_MAP_CLOSE -- add distinguished entries and close the database
+*/
+
+void
+db_map_close(map)
+ MAP *map;
+{
+ register DB *db = map->map_db2;
+
+ if (tTd(38, 9))
+ sm_dprintf("db_map_close(%s, %s, %lx)\n",
+ map->map_mname, map->map_file, map->map_mflags);
+
+ if (bitset(MF_WRITABLE, map->map_mflags))
+ {
+ /* write out the distinguished alias */
+ db_map_store(map, "@", "@");
+ }
+
+ (void) db->sync(db, 0);
+
+# if !LOCK_ON_OPEN
+ if (map->map_lockfd >= 0)
+ (void) close(map->map_lockfd);
+# endif /* !LOCK_ON_OPEN */
+
+# if DB_VERSION_MAJOR < 2
+ if (db->close(db) != 0)
+# else /* DB_VERSION_MAJOR < 2 */
+ /*
+ ** Berkeley DB can use internal shared memory
+ ** locking for its memory pool. Closing a map
+ ** opened by another process will interfere
+ ** with the shared memory and locks of the parent
+ ** process leaving things in a bad state.
+ */
+
+ /*
+ ** If this map was not opened by the current
+ ** process, do not close the map but recover
+ ** the file descriptor.
+ */
+
+ if (map->map_pid != CurrentPid)
+ {
+ int fd = -1;
+
+ errno = db->fd(db, &fd);
+ if (fd >= 0)
+ (void) close(fd);
+ return;
+ }
+
+ if ((errno = db->close(db, 0)) != 0)
+# endif /* DB_VERSION_MAJOR < 2 */
+ syserr("db_map_close(%s, %s, %lx): db close failure",
+ map->map_mname, map->map_file, map->map_mflags);
+}
+#endif /* NEWDB */
+/*
+** NIS Modules
+*/
+
+#if NIS
+
+# ifndef YPERR_BUSY
+# define YPERR_BUSY 16
+# endif /* ! YPERR_BUSY */
+
+/*
+** NIS_MAP_OPEN -- open DBM map
+*/
+
+bool
+nis_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ int yperr;
+ register char *p;
+ auto char *vp;
+ auto int vsize;
+
+ if (tTd(38, 2))
+ sm_dprintf("nis_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+
+ p = strchr(map->map_file, '@');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ if (*p != '\0')
+ map->map_domain = p;
+ }
+
+ if (*map->map_file == '\0')
+ map->map_file = "mail.aliases";
+
+ if (map->map_domain == NULL)
+ {
+ yperr = yp_get_default_domain(&map->map_domain);
+ if (yperr != 0)
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("451 4.3.5 NIS map %s specified, but NIS not running",
+ map->map_file);
+ return false;
+ }
+ }
+
+ /* check to see if this map actually exists */
+ vp = NULL;
+ yperr = yp_match(map->map_domain, map->map_file, "@", 1,
+ &vp, &vsize);
+ if (tTd(38, 10))
+ sm_dprintf("nis_map_open: yp_match(@, %s, %s) => %s\n",
+ map->map_domain, map->map_file, yperr_string(yperr));
+ if (vp != NULL)
+ sm_free(vp);
+
+ if (yperr == 0 || yperr == YPERR_KEY || yperr == YPERR_BUSY)
+ {
+ /*
+ ** We ought to be calling aliaswait() here if this is an
+ ** alias file, but powerful HP-UX NIS servers apparently
+ ** don't insert the @:@ token into the alias map when it
+ ** is rebuilt, so aliaswait() just hangs. I hate HP-UX.
+ */
+
+# if 0
+ if (!bitset(MF_ALIAS, map->map_mflags) ||
+ aliaswait(map, NULL, true))
+# endif /* 0 */
+ return true;
+ }
+
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ syserr("451 4.3.5 Cannot bind to map %s in domain %s: %s",
+ map->map_file, map->map_domain, yperr_string(yperr));
+ }
+
+ return false;
+}
+
+
+/*
+** NIS_MAP_LOOKUP -- look up a datum in a NIS map
+*/
+
+/* ARGSUSED3 */
+char *
+nis_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char *vp;
+ auto int vsize;
+ int buflen;
+ int yperr;
+ char keybuf[MAXNAME + 1];
+ char *SM_NONVOLATILE result = NULL;
+
+ if (tTd(38, 20))
+ sm_dprintf("nis_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ buflen = strlen(name);
+ if (buflen > sizeof keybuf - 1)
+ buflen = sizeof keybuf - 1;
+ memmove(keybuf, name, buflen);
+ keybuf[buflen] = '\0';
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ makelower(keybuf);
+ yperr = YPERR_KEY;
+ vp = NULL;
+ if (bitset(MF_TRY0NULL, map->map_mflags))
+ {
+ yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
+ &vp, &vsize);
+ if (yperr == 0)
+ map->map_mflags &= ~MF_TRY1NULL;
+ }
+ if (yperr == YPERR_KEY && bitset(MF_TRY1NULL, map->map_mflags))
+ {
+ SM_FREE_CLR(vp);
+ buflen++;
+ yperr = yp_match(map->map_domain, map->map_file, keybuf, buflen,
+ &vp, &vsize);
+ if (yperr == 0)
+ map->map_mflags &= ~MF_TRY0NULL;
+ }
+ if (yperr != 0)
+ {
+ if (yperr != YPERR_KEY && yperr != YPERR_BUSY)
+ map->map_mflags &= ~(MF_VALID|MF_OPEN);
+ if (vp != NULL)
+ sm_free(vp);
+ return NULL;
+ }
+ SM_TRY
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ result = map_rewrite(map, name, strlen(name), NULL);
+ else
+ result = map_rewrite(map, vp, vsize, av);
+ SM_FINALLY
+ if (vp != NULL)
+ sm_free(vp);
+ SM_END_TRY
+ return result;
+}
+
+
+/*
+** NIS_GETCANONNAME -- look up canonical name in NIS
+*/
+
+static bool
+nis_getcanonname(name, hbsize, statp)
+ char *name;
+ int hbsize;
+ int *statp;
+{
+ char *vp;
+ auto int vsize;
+ int keylen;
+ int yperr;
+ static bool try0null = true;
+ static bool try1null = true;
+ static char *yp_domain = NULL;
+ char host_record[MAXLINE];
+ char cbuf[MAXNAME];
+ char nbuf[MAXNAME + 1];
+
+ if (tTd(38, 20))
+ sm_dprintf("nis_getcanonname(%s)\n", name);
+
+ if (sm_strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ (void) shorten_hostname(nbuf);
+ keylen = strlen(nbuf);
+
+ if (yp_domain == NULL)
+ (void) yp_get_default_domain(&yp_domain);
+ makelower(nbuf);
+ yperr = YPERR_KEY;
+ vp = NULL;
+ if (try0null)
+ {
+ yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
+ &vp, &vsize);
+ if (yperr == 0)
+ try1null = false;
+ }
+ if (yperr == YPERR_KEY && try1null)
+ {
+ SM_FREE_CLR(vp);
+ keylen++;
+ yperr = yp_match(yp_domain, "hosts.byname", nbuf, keylen,
+ &vp, &vsize);
+ if (yperr == 0)
+ try0null = false;
+ }
+ if (yperr != 0)
+ {
+ if (yperr == YPERR_KEY)
+ *statp = EX_NOHOST;
+ else if (yperr == YPERR_BUSY)
+ *statp = EX_TEMPFAIL;
+ else
+ *statp = EX_UNAVAILABLE;
+ if (vp != NULL)
+ sm_free(vp);
+ return false;
+ }
+ (void) sm_strlcpy(host_record, vp, sizeof host_record);
+ sm_free(vp);
+ if (tTd(38, 44))
+ sm_dprintf("got record `%s'\n", host_record);
+ vp = strpbrk(host_record, "#\n");
+ if (vp != NULL)
+ *vp = '\0';
+ if (!extract_canonname(nbuf, NULL, host_record, cbuf, sizeof cbuf))
+ {
+ /* this should not happen, but.... */
+ *statp = EX_NOHOST;
+ return false;
+ }
+ if (sm_strlcpy(name, cbuf, hbsize) >= hbsize)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ *statp = EX_OK;
+ return true;
+}
+
+#endif /* NIS */
+/*
+** NISPLUS Modules
+**
+** This code donated by Sun Microsystems.
+*/
+
+#if NISPLUS
+
+# undef NIS /* symbol conflict in nis.h */
+# undef T_UNSPEC /* symbol conflict in nis.h -> ... -> sys/tiuser.h */
+# include <rpcsvc/nis.h>
+# include <rpcsvc/nislib.h>
+
+# define EN_col(col) zo_data.objdata_u.en_data.en_cols.en_cols_val[(col)].ec_value.ec_value_val
+# define COL_NAME(res,i) ((res->objects.objects_val)->TA_data.ta_cols.ta_cols_val)[i].tc_name
+# define COL_MAX(res) ((res->objects.objects_val)->TA_data.ta_cols.ta_cols_len)
+# define PARTIAL_NAME(x) ((x)[strlen(x) - 1] != '.')
+
+/*
+** NISPLUS_MAP_OPEN -- open nisplus table
+*/
+
+bool
+nisplus_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ nis_result *res = NULL;
+ int retry_cnt, max_col, i;
+ char qbuf[MAXLINE + NIS_MAXNAMELEN];
+
+ if (tTd(38, 2))
+ sm_dprintf("nisplus_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ errno = EPERM;
+ return false;
+ }
+
+ if (*map->map_file == '\0')
+ map->map_file = "mail_aliases.org_dir";
+
+ if (PARTIAL_NAME(map->map_file) && map->map_domain == NULL)
+ {
+ /* set default NISPLUS Domain to $m */
+ map->map_domain = newstr(nisplus_default_domain());
+ if (tTd(38, 2))
+ sm_dprintf("nisplus_map_open(%s): using domain %s\n",
+ map->map_file, map->map_domain);
+ }
+ if (!PARTIAL_NAME(map->map_file))
+ {
+ map->map_domain = newstr("");
+ (void) sm_strlcpy(qbuf, map->map_file, sizeof qbuf);
+ }
+ else
+ {
+ /* check to see if this map actually exists */
+ (void) sm_strlcpyn(qbuf, sizeof qbuf, 3,
+ map->map_file, ".", map->map_domain);
+ }
+
+ retry_cnt = 0;
+ while (res == NULL || res->status != NIS_SUCCESS)
+ {
+ res = nis_lookup(qbuf, FOLLOW_LINKS);
+ switch (res->status)
+ {
+ case NIS_SUCCESS:
+ break;
+
+ case NIS_TRYAGAIN:
+ case NIS_RPCERROR:
+ case NIS_NAMEUNREACHABLE:
+ if (retry_cnt++ > 4)
+ {
+ errno = EAGAIN;
+ return false;
+ }
+ /* try not to overwhelm hosed server */
+ sleep(2);
+ break;
+
+ default: /* all other nisplus errors */
+# if 0
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("451 4.3.5 Cannot find table %s.%s: %s",
+ map->map_file, map->map_domain,
+ nis_sperrno(res->status));
+# endif /* 0 */
+ errno = EAGAIN;
+ return false;
+ }
+ }
+
+ if (NIS_RES_NUMOBJ(res) != 1 ||
+ (NIS_RES_OBJECT(res)->zo_data.zo_type != TABLE_OBJ))
+ {
+ if (tTd(38, 10))
+ sm_dprintf("nisplus_map_open: %s is not a table\n", qbuf);
+# if 0
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("451 4.3.5 %s.%s: %s is not a table",
+ map->map_file, map->map_domain,
+ nis_sperrno(res->status));
+# endif /* 0 */
+ errno = EBADF;
+ return false;
+ }
+ /* default key column is column 0 */
+ if (map->map_keycolnm == NULL)
+ map->map_keycolnm = newstr(COL_NAME(res,0));
+
+ max_col = COL_MAX(res);
+
+ /* verify the key column exist */
+ for (i = 0; i < max_col; i++)
+ {
+ if (strcmp(map->map_keycolnm, COL_NAME(res,i)) == 0)
+ break;
+ }
+ if (i == max_col)
+ {
+ if (tTd(38, 2))
+ sm_dprintf("nisplus_map_open(%s): can not find key column %s\n",
+ map->map_file, map->map_keycolnm);
+ errno = ENOENT;
+ return false;
+ }
+
+ /* default value column is the last column */
+ if (map->map_valcolnm == NULL)
+ {
+ map->map_valcolno = max_col - 1;
+ return true;
+ }
+
+ for (i = 0; i< max_col; i++)
+ {
+ if (strcmp(map->map_valcolnm, COL_NAME(res,i)) == 0)
+ {
+ map->map_valcolno = i;
+ return true;
+ }
+ }
+
+ if (tTd(38, 2))
+ sm_dprintf("nisplus_map_open(%s): can not find column %s\n",
+ map->map_file, map->map_keycolnm);
+ errno = ENOENT;
+ return false;
+}
+
+
+/*
+** NISPLUS_MAP_LOOKUP -- look up a datum in a NISPLUS table
+*/
+
+char *
+nisplus_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char *p;
+ auto int vsize;
+ char *skp;
+ int skleft;
+ char search_key[MAXNAME + 4];
+ char qbuf[MAXLINE + NIS_MAXNAMELEN];
+ nis_result *result;
+
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ if (!bitset(MF_OPEN, map->map_mflags))
+ {
+ if (nisplus_map_open(map, O_RDONLY))
+ {
+ map->map_mflags |= MF_OPEN;
+ map->map_pid = CurrentPid;
+ }
+ else
+ {
+ *statp = EX_UNAVAILABLE;
+ return NULL;
+ }
+ }
+
+ /*
+ ** Copy the name to the key buffer, escaping double quote characters
+ ** by doubling them and quoting "]" and "," to avoid having the
+ ** NIS+ parser choke on them.
+ */
+
+ skleft = sizeof search_key - 4;
+ skp = search_key;
+ for (p = name; *p != '\0' && skleft > 0; p++)
+ {
+ switch (*p)
+ {
+ case ']':
+ case ',':
+ /* quote the character */
+ *skp++ = '"';
+ *skp++ = *p;
+ *skp++ = '"';
+ skleft -= 3;
+ break;
+
+ case '"':
+ /* double the quote */
+ *skp++ = '"';
+ skleft--;
+ /* FALLTHROUGH */
+
+ default:
+ *skp++ = *p;
+ skleft--;
+ break;
+ }
+ }
+ *skp = '\0';
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ makelower(search_key);
+
+ /* construct the query */
+ if (PARTIAL_NAME(map->map_file))
+ (void) sm_snprintf(qbuf, sizeof qbuf, "[%s=%s],%s.%s",
+ map->map_keycolnm, search_key, map->map_file,
+ map->map_domain);
+ else
+ (void) sm_snprintf(qbuf, sizeof qbuf, "[%s=%s],%s",
+ map->map_keycolnm, search_key, map->map_file);
+
+ if (tTd(38, 20))
+ sm_dprintf("qbuf=%s\n", qbuf);
+ result = nis_list(qbuf, FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
+ if (result->status == NIS_SUCCESS)
+ {
+ int count;
+ char *str;
+
+ if ((count = NIS_RES_NUMOBJ(result)) != 1)
+ {
+ if (LogLevel > 10)
+ sm_syslog(LOG_WARNING, CurEnv->e_id,
+ "%s: lookup error, expected 1 entry, got %d",
+ map->map_file, count);
+
+ /* ignore second entry */
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_map_lookup(%s), got %d entries, additional entries ignored\n",
+ name, count);
+ }
+
+ p = ((NIS_RES_OBJECT(result))->EN_col(map->map_valcolno));
+ /* set the length of the result */
+ if (p == NULL)
+ p = "";
+ vsize = strlen(p);
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_map_lookup(%s), found %s\n",
+ name, p);
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ str = map_rewrite(map, name, strlen(name), NULL);
+ else
+ str = map_rewrite(map, p, vsize, av);
+ nis_freeresult(result);
+ *statp = EX_OK;
+ return str;
+ }
+ else
+ {
+ if (result->status == NIS_NOTFOUND)
+ *statp = EX_NOTFOUND;
+ else if (result->status == NIS_TRYAGAIN)
+ *statp = EX_TEMPFAIL;
+ else
+ {
+ *statp = EX_UNAVAILABLE;
+ map->map_mflags &= ~(MF_VALID|MF_OPEN);
+ }
+ }
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_map_lookup(%s), failed\n", name);
+ nis_freeresult(result);
+ return NULL;
+}
+
+
+
+/*
+** NISPLUS_GETCANONNAME -- look up canonical name in NIS+
+*/
+
+static bool
+nisplus_getcanonname(name, hbsize, statp)
+ char *name;
+ int hbsize;
+ int *statp;
+{
+ char *vp;
+ auto int vsize;
+ nis_result *result;
+ char *p;
+ char nbuf[MAXNAME + 1];
+ char qbuf[MAXLINE + NIS_MAXNAMELEN];
+
+ if (sm_strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ (void) shorten_hostname(nbuf);
+
+ p = strchr(nbuf, '.');
+ if (p == NULL)
+ {
+ /* single token */
+ (void) sm_snprintf(qbuf, sizeof qbuf,
+ "[name=%s],hosts.org_dir", nbuf);
+ }
+ else if (p[1] != '\0')
+ {
+ /* multi token -- take only first token in nbuf */
+ *p = '\0';
+ (void) sm_snprintf(qbuf, sizeof qbuf,
+ "[name=%s],hosts.org_dir.%s", nbuf, &p[1]);
+ }
+ else
+ {
+ *statp = EX_NOHOST;
+ return false;
+ }
+
+ if (tTd(38, 20))
+ sm_dprintf("\nnisplus_getcanonname(%s), qbuf=%s\n",
+ name, qbuf);
+
+ result = nis_list(qbuf, EXPAND_NAME|FOLLOW_LINKS|FOLLOW_PATH,
+ NULL, NULL);
+
+ if (result->status == NIS_SUCCESS)
+ {
+ int count;
+ char *domain;
+
+ if ((count = NIS_RES_NUMOBJ(result)) != 1)
+ {
+ if (LogLevel > 10)
+ sm_syslog(LOG_WARNING, CurEnv->e_id,
+ "nisplus_getcanonname: lookup error, expected 1 entry, got %d",
+ count);
+
+ /* ignore second entry */
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_getcanonname(%s), got %d entries, all but first ignored\n",
+ name, count);
+ }
+
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_getcanonname(%s), found in directory \"%s\"\n",
+ name, (NIS_RES_OBJECT(result))->zo_domain);
+
+
+ vp = ((NIS_RES_OBJECT(result))->EN_col(0));
+ vsize = strlen(vp);
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_getcanonname(%s), found %s\n",
+ name, vp);
+ if (strchr(vp, '.') != NULL)
+ {
+ domain = "";
+ }
+ else
+ {
+ domain = macvalue('m', CurEnv);
+ if (domain == NULL)
+ domain = "";
+ }
+ if (hbsize > vsize + (int) strlen(domain) + 1)
+ {
+ if (domain[0] == '\0')
+ (void) sm_strlcpy(name, vp, hbsize);
+ else
+ (void) sm_snprintf(name, hbsize,
+ "%s.%s", vp, domain);
+ *statp = EX_OK;
+ }
+ else
+ *statp = EX_NOHOST;
+ nis_freeresult(result);
+ return true;
+ }
+ else
+ {
+ if (result->status == NIS_NOTFOUND)
+ *statp = EX_NOHOST;
+ else if (result->status == NIS_TRYAGAIN)
+ *statp = EX_TEMPFAIL;
+ else
+ *statp = EX_UNAVAILABLE;
+ }
+ if (tTd(38, 20))
+ sm_dprintf("nisplus_getcanonname(%s), failed, status=%d, nsw_stat=%d\n",
+ name, result->status, *statp);
+ nis_freeresult(result);
+ return false;
+}
+
+char *
+nisplus_default_domain()
+{
+ static char default_domain[MAXNAME + 1] = "";
+ char *p;
+
+ if (default_domain[0] != '\0')
+ return default_domain;
+
+ p = nis_local_directory();
+ (void) sm_strlcpy(default_domain, p, sizeof default_domain);
+ return default_domain;
+}
+
+#endif /* NISPLUS */
+/*
+** LDAP Modules
+*/
+
+/*
+** LDAPMAP_DEQUOTE - helper routine for ldapmap_parseargs
+*/
+
+#if defined(LDAPMAP) || defined(PH_MAP)
+
+# if PH_MAP
+# define ph_map_dequote ldapmap_dequote
+# endif /* PH_MAP */
+
+static char *ldapmap_dequote __P((char *));
+
+static char *
+ldapmap_dequote(str)
+ char *str;
+{
+ char *p;
+ char *start;
+
+ if (str == NULL)
+ return NULL;
+
+ p = str;
+ if (*p == '"')
+ {
+ /* Should probably swallow initial whitespace here */
+ start = ++p;
+ }
+ else
+ return str;
+ while (*p != '"' && *p != '\0')
+ p++;
+ if (*p != '\0')
+ *p = '\0';
+ return start;
+}
+#endif /* defined(LDAPMAP) || defined(PH_MAP) */
+
+#if LDAPMAP
+
+static SM_LDAP_STRUCT *LDAPDefaults = NULL;
+
+/*
+** LDAPMAP_OPEN -- open LDAP map
+**
+** Connect to the LDAP server. Re-use existing connections since a
+** single server connection to a host (with the same host, port,
+** bind DN, and secret) can answer queries for multiple maps.
+*/
+
+bool
+ldapmap_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ SM_LDAP_STRUCT *lmap;
+ STAB *s;
+
+ if (tTd(38, 2))
+ sm_dprintf("ldapmap_open(%s, %d): ", map->map_mname, mode);
+
+ mode &= O_ACCMODE;
+
+ /* sendmail doesn't have the ability to write to LDAP (yet) */
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+
+ lmap = (SM_LDAP_STRUCT *) map->map_db1;
+
+ s = ldapmap_findconn(lmap);
+ if (s->s_lmap != NULL)
+ {
+ /* Already have a connection open to this LDAP server */
+ lmap->ldap_ld = ((SM_LDAP_STRUCT *)s->s_lmap->map_db1)->ldap_ld;
+ lmap->ldap_pid = ((SM_LDAP_STRUCT *)s->s_lmap->map_db1)->ldap_pid;
+
+ /* Add this map as head of linked list */
+ lmap->ldap_next = s->s_lmap;
+ s->s_lmap = map;
+
+ if (tTd(38, 2))
+ sm_dprintf("using cached connection\n");
+ return true;
+ }
+
+ if (tTd(38, 2))
+ sm_dprintf("opening new connection\n");
+
+ /* No connection yet, connect */
+ if (!sm_ldap_start(map->map_mname, lmap))
+ {
+ if (errno == ETIMEDOUT)
+ {
+ if (LogLevel > 1)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "timeout conning to LDAP server %.100s",
+ lmap->ldap_target == NULL ? "localhost" : lmap->ldap_target);
+ }
+
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ if (bitset(MF_NODEFER, map->map_mflags))
+ syserr("%s failed to %s in map %s",
+# if USE_LDAP_INIT
+ "ldap_init/ldap_bind",
+# else /* USE_LDAP_INIT */
+ "ldap_open",
+# endif /* USE_LDAP_INIT */
+ lmap->ldap_target == NULL ? "localhost"
+ : lmap->ldap_target,
+ map->map_mname);
+ else
+ syserr("451 4.3.5 %s failed to %s in map %s",
+# if USE_LDAP_INIT
+ "ldap_init/ldap_bind",
+# else /* USE_LDAP_INIT */
+ "ldap_open",
+# endif /* USE_LDAP_INIT */
+ lmap->ldap_target == NULL ? "localhost"
+ : lmap->ldap_target,
+ map->map_mname);
+ }
+ return false;
+ }
+
+ /* Save connection for reuse */
+ s->s_lmap = map;
+ return true;
+}
+
+/*
+** LDAPMAP_CLOSE -- close ldap map
+*/
+
+void
+ldapmap_close(map)
+ MAP *map;
+{
+ SM_LDAP_STRUCT *lmap;
+ STAB *s;
+
+ if (tTd(38, 2))
+ sm_dprintf("ldapmap_close(%s)\n", map->map_mname);
+
+ lmap = (SM_LDAP_STRUCT *) map->map_db1;
+
+ /* Check if already closed */
+ if (lmap->ldap_ld == NULL)
+ return;
+
+ /* Close the LDAP connection */
+ sm_ldap_close(lmap);
+
+ /* Mark all the maps that share the connection as closed */
+ s = ldapmap_findconn(lmap);
+
+ while (s->s_lmap != NULL)
+ {
+ MAP *smap = s->s_lmap;
+
+ if (tTd(38, 2) && smap != map)
+ sm_dprintf("ldapmap_close(%s): closed %s (shared LDAP connection)\n",
+ map->map_mname, smap->map_mname);
+ smap->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
+ lmap = (SM_LDAP_STRUCT *) smap->map_db1;
+ lmap->ldap_ld = NULL;
+ s->s_lmap = lmap->ldap_next;
+ lmap->ldap_next = NULL;
+ }
+}
+
+# ifdef SUNET_ID
+/*
+** SUNET_ID_HASH -- Convert a string to its Sunet_id canonical form
+** This only makes sense at Stanford University.
+*/
+
+static char *
+sunet_id_hash(str)
+ char *str;
+{
+ char *p, *p_last;
+
+ p = str;
+ p_last = p;
+ while (*p != '\0')
+ {
+ if (islower(*p) || isdigit(*p))
+ {
+ *p_last = *p;
+ p_last++;
+ }
+ else if (isupper(*p))
+ {
+ *p_last = tolower(*p);
+ p_last++;
+ }
+ ++p;
+ }
+ if (*p_last != '\0')
+ *p_last = '\0';
+ return str;
+}
+# endif /* SUNET_ID */
+
+/*
+** LDAPMAP_LOOKUP -- look up a datum in a LDAP map
+*/
+
+char *
+ldapmap_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+# if _FFR_LDAP_RECURSION
+ int plen = 0;
+ int psize = 0;
+# else /* _FFR_LDAP_RECURSION */
+ int entries = 0;
+ int i;
+ int ret;
+ int vsize;
+# endif /* _FFR_LDAP_RECURSION */
+ int msgid;
+ int save_errno;
+ char *vp, *p;
+ char *result = NULL;
+ SM_LDAP_STRUCT *lmap = NULL;
+ char keybuf[MAXNAME + 1];
+
+ if (tTd(38, 20))
+ sm_dprintf("ldapmap_lookup(%s, %s)\n", map->map_mname, name);
+
+ /* Get ldap struct pointer from map */
+ lmap = (SM_LDAP_STRUCT *) map->map_db1;
+ sm_ldap_setopts(lmap->ldap_ld, lmap);
+
+ (void) sm_strlcpy(keybuf, name, sizeof keybuf);
+
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+# ifdef SUNET_ID
+ sunet_id_hash(keybuf);
+# else /* SUNET_ID */
+ makelower(keybuf);
+# endif /* SUNET_ID */
+ }
+
+ msgid = sm_ldap_search(lmap, keybuf);
+ if (msgid == -1)
+ {
+ errno = sm_ldap_geterrno(lmap->ldap_ld) + E_LDAPBASE;
+ save_errno = errno;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ if (bitset(MF_NODEFER, map->map_mflags))
+ syserr("Error in ldap_search using %s in map %s",
+ keybuf, map->map_mname);
+ else
+ syserr("451 4.3.5 Error in ldap_search using %s in map %s",
+ keybuf, map->map_mname);
+ }
+ *statp = EX_TEMPFAIL;
+ switch (save_errno - E_LDAPBASE)
+ {
+# ifdef LDAP_SERVER_DOWN
+ case LDAP_SERVER_DOWN:
+# endif /* LDAP_SERVER_DOWN */
+ case LDAP_TIMEOUT:
+ case LDAP_UNAVAILABLE:
+ /* server disappeared, try reopen on next search */
+ ldapmap_close(map);
+ break;
+ }
+ errno = save_errno;
+ return NULL;
+ }
+
+ *statp = EX_NOTFOUND;
+ vp = NULL;
+
+# if _FFR_LDAP_RECURSION
+ {
+ int flags;
+ SM_RPOOL_T *rpool;
+
+ flags = 0;
+ if (bitset(MF_SINGLEMATCH, map->map_mflags))
+ flags |= SM_LDAP_SINGLEMATCH;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ flags |= SM_LDAP_MATCHONLY;
+
+ /* Create an rpool for search related memory usage */
+ rpool = sm_rpool_new_x(NULL);
+
+ p = NULL;
+ *statp = sm_ldap_results(lmap, msgid, flags, map->map_coldelim,
+ rpool, &p, &plen, &psize, NULL);
+ save_errno = errno;
+
+ /* Copy result so rpool can be freed */
+ if (*statp == EX_OK && p != NULL)
+ vp = newstr(p);
+ sm_rpool_free(rpool);
+
+ /* need to restart LDAP connection? */
+ if (*statp == EX_RESTART)
+ {
+ *statp = EX_TEMPFAIL;
+ ldapmap_close(map);
+ }
+
+ errno = save_errno;
+ if (*statp != EX_OK && *statp != EX_NOTFOUND)
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ if (bitset(MF_NODEFER, map->map_mflags))
+ syserr("Error getting LDAP results in map %s",
+ map->map_mname);
+ else
+ syserr("451 4.3.5 Error getting LDAP results in map %s",
+ map->map_mname);
+ }
+ errno = save_errno;
+ return NULL;
+ }
+ }
+# else /* _FFR_LDAP_RECURSION */
+
+ /* Get results */
+ while ((ret = ldap_result(lmap->ldap_ld, msgid, 0,
+ (lmap->ldap_timeout.tv_sec == 0 ? NULL :
+ &(lmap->ldap_timeout)),
+ &(lmap->ldap_res))) == LDAP_RES_SEARCH_ENTRY)
+ {
+ LDAPMessage *entry;
+
+ if (bitset(MF_SINGLEMATCH, map->map_mflags))
+ {
+ entries += ldap_count_entries(lmap->ldap_ld,
+ lmap->ldap_res);
+ if (entries > 1)
+ {
+ *statp = EX_NOTFOUND;
+ if (lmap->ldap_res != NULL)
+ {
+ ldap_msgfree(lmap->ldap_res);
+ lmap->ldap_res = NULL;
+ }
+ (void) ldap_abandon(lmap->ldap_ld, msgid);
+ if (vp != NULL)
+ sm_free(vp); /* XXX */
+ if (tTd(38, 25))
+ sm_dprintf("ldap search found multiple on a single match query\n");
+ return NULL;
+ }
+ }
+
+ /* If we don't want multiple values and we have one, break */
+ if (map->map_coldelim == '\0' && vp != NULL)
+ break;
+
+ /* Cycle through all entries */
+ for (entry = ldap_first_entry(lmap->ldap_ld, lmap->ldap_res);
+ entry != NULL;
+ entry = ldap_next_entry(lmap->ldap_ld, lmap->ldap_res))
+ {
+ BerElement *ber;
+ char *attr;
+ char **vals = NULL;
+
+ /*
+ ** If matching only and found an entry,
+ ** no need to spin through attributes
+ */
+
+ if (*statp == EX_OK &&
+ bitset(MF_MATCHONLY, map->map_mflags))
+ continue;
+
+# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
+ /*
+ ** Reset value to prevent lingering
+ ** LDAP_DECODING_ERROR due to
+ ** OpenLDAP 1.X's hack (see below)
+ */
+
+ lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
+# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
+
+ for (attr = ldap_first_attribute(lmap->ldap_ld, entry,
+ &ber);
+ attr != NULL;
+ attr = ldap_next_attribute(lmap->ldap_ld, entry,
+ ber))
+ {
+ char *tmp, *vp_tmp;
+
+ if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
+ {
+ vals = ldap_get_values(lmap->ldap_ld,
+ entry,
+ attr);
+ if (vals == NULL)
+ {
+ save_errno = sm_ldap_geterrno(lmap->ldap_ld);
+ if (save_errno == LDAP_SUCCESS)
+ {
+ ldap_memfree(attr);
+ continue;
+ }
+
+ /* Must be an error */
+ save_errno += E_LDAPBASE;
+ if (!bitset(MF_OPTIONAL,
+ map->map_mflags))
+ {
+ errno = save_errno;
+ if (bitset(MF_NODEFER,
+ map->map_mflags))
+ syserr("Error getting LDAP values in map %s",
+ map->map_mname);
+ else
+ syserr("451 4.3.5 Error getting LDAP values in map %s",
+ map->map_mname);
+ }
+ *statp = EX_TEMPFAIL;
+ ldap_memfree(attr);
+ if (lmap->ldap_res != NULL)
+ {
+ ldap_msgfree(lmap->ldap_res);
+ lmap->ldap_res = NULL;
+ }
+ (void) ldap_abandon(lmap->ldap_ld,
+ msgid);
+ if (vp != NULL)
+ sm_free(vp); /* XXX */
+ errno = save_errno;
+ return NULL;
+ }
+ }
+
+ *statp = EX_OK;
+
+# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
+ /*
+ ** Reset value to prevent lingering
+ ** LDAP_DECODING_ERROR due to
+ ** OpenLDAP 1.X's hack (see below)
+ */
+
+ lmap->ldap_ld->ld_errno = LDAP_SUCCESS;
+# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
+
+ /*
+ ** If matching only,
+ ** no need to spin through entries
+ */
+
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ {
+ if (lmap->ldap_attrsonly == LDAPMAP_FALSE)
+ ldap_value_free(vals);
+
+ ldap_memfree(attr);
+ continue;
+ }
+
+ /*
+ ** If we don't want multiple values,
+ ** return first found.
+ */
+
+ if (map->map_coldelim == '\0')
+ {
+ if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
+ {
+ vp = newstr(attr);
+ ldap_memfree(attr);
+ break;
+ }
+
+ if (vals[0] == NULL)
+ {
+ ldap_value_free(vals);
+ ldap_memfree(attr);
+ continue;
+ }
+
+ vsize = strlen(vals[0]) + 1;
+ if (lmap->ldap_attrsep != '\0')
+ vsize += strlen(attr) + 1;
+ vp = xalloc(vsize);
+ if (lmap->ldap_attrsep != '\0')
+ sm_snprintf(vp, vsize,
+ "%s%c%s",
+ attr,
+ lmap->ldap_attrsep,
+ vals[0]);
+ else
+ sm_strlcpy(vp, vals[0], vsize);
+ ldap_value_free(vals);
+ ldap_memfree(attr);
+ break;
+ }
+
+ /* attributes only */
+ if (lmap->ldap_attrsonly == LDAPMAP_TRUE)
+ {
+ if (vp == NULL)
+ vp = newstr(attr);
+ else
+ {
+ vsize = strlen(vp) +
+ strlen(attr) + 2;
+ tmp = xalloc(vsize);
+ (void) sm_snprintf(tmp,
+ vsize, "%s%c%s",
+ vp, map->map_coldelim,
+ attr);
+ sm_free(vp); /* XXX */
+ vp = tmp;
+ }
+ ldap_memfree(attr);
+ continue;
+ }
+
+ /*
+ ** If there is more than one,
+ ** munge then into a map_coldelim
+ ** separated string
+ */
+
+ vsize = 0;
+ for (i = 0; vals[i] != NULL; i++)
+ {
+ vsize += strlen(vals[i]) + 1;
+ if (lmap->ldap_attrsep != '\0')
+ vsize += strlen(attr) + 1;
+ }
+ vp_tmp = xalloc(vsize);
+ *vp_tmp = '\0';
+
+ p = vp_tmp;
+ for (i = 0; vals[i] != NULL; i++)
+ {
+ if (lmap->ldap_attrsep != '\0')
+ {
+ p += sm_strlcpy(p, attr,
+ vsize - (p - vp_tmp));
+ if (p >= vp_tmp + vsize)
+ syserr("ldapmap_lookup: Internal error: buffer too small for LDAP values");
+ *p++ = lmap->ldap_attrsep;
+ }
+ p += sm_strlcpy(p, vals[i],
+ vsize - (p - vp_tmp));
+ if (p >= vp_tmp + vsize)
+ syserr("ldapmap_lookup: Internal error: buffer too small for LDAP values");
+ if (vals[i + 1] != NULL)
+ *p++ = map->map_coldelim;
+ }
+
+ ldap_value_free(vals);
+ ldap_memfree(attr);
+ if (vp == NULL)
+ {
+ vp = vp_tmp;
+ continue;
+ }
+ vsize = strlen(vp) + strlen(vp_tmp) + 2;
+ tmp = xalloc(vsize);
+ (void) sm_snprintf(tmp, vsize, "%s%c%s",
+ vp, map->map_coldelim, vp_tmp);
+
+ sm_free(vp); /* XXX */
+ sm_free(vp_tmp); /* XXX */
+ vp = tmp;
+ }
+ save_errno = sm_ldap_geterrno(lmap->ldap_ld);
+
+ /*
+ ** We check errno != LDAP_DECODING_ERROR since
+ ** OpenLDAP 1.X has a very ugly *undocumented*
+ ** hack of returning this error code from
+ ** ldap_next_attribute() if the library freed the
+ ** ber attribute. See:
+ ** http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
+ */
+
+ if (save_errno != LDAP_SUCCESS &&
+ save_errno != LDAP_DECODING_ERROR)
+ {
+ /* Must be an error */
+ save_errno += E_LDAPBASE;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ errno = save_errno;
+ if (bitset(MF_NODEFER, map->map_mflags))
+ syserr("Error getting LDAP attributes in map %s",
+ map->map_mname);
+ else
+ syserr("451 4.3.5 Error getting LDAP attributes in map %s",
+ map->map_mname);
+ }
+ *statp = EX_TEMPFAIL;
+ if (lmap->ldap_res != NULL)
+ {
+ ldap_msgfree(lmap->ldap_res);
+ lmap->ldap_res = NULL;
+ }
+ (void) ldap_abandon(lmap->ldap_ld, msgid);
+ if (vp != NULL)
+ sm_free(vp); /* XXX */
+ errno = save_errno;
+ return NULL;
+ }
+
+ /* We don't want multiple values and we have one */
+ if (map->map_coldelim == '\0' && vp != NULL)
+ break;
+ }
+ save_errno = sm_ldap_geterrno(lmap->ldap_ld);
+ if (save_errno != LDAP_SUCCESS &&
+ save_errno != LDAP_DECODING_ERROR)
+ {
+ /* Must be an error */
+ save_errno += E_LDAPBASE;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ errno = save_errno;
+ if (bitset(MF_NODEFER, map->map_mflags))
+ syserr("Error getting LDAP entries in map %s",
+ map->map_mname);
+ else
+ syserr("451 4.3.5 Error getting LDAP entries in map %s",
+ map->map_mname);
+ }
+ *statp = EX_TEMPFAIL;
+ if (lmap->ldap_res != NULL)
+ {
+ ldap_msgfree(lmap->ldap_res);
+ lmap->ldap_res = NULL;
+ }
+ (void) ldap_abandon(lmap->ldap_ld, msgid);
+ if (vp != NULL)
+ sm_free(vp); /* XXX */
+ errno = save_errno;
+ return NULL;
+ }
+ ldap_msgfree(lmap->ldap_res);
+ lmap->ldap_res = NULL;
+ }
+
+ if (ret == 0)
+ save_errno = ETIMEDOUT;
+ else
+ save_errno = sm_ldap_geterrno(lmap->ldap_ld);
+ if (save_errno != LDAP_SUCCESS)
+ {
+ if (ret != 0)
+ save_errno += E_LDAPBASE;
+
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ {
+ errno = save_errno;
+ if (bitset(MF_NODEFER, map->map_mflags))
+ syserr("Error getting LDAP results in map %s",
+ map->map_mname);
+ else
+ syserr("451 4.3.5 Error getting LDAP results in map %s",
+ map->map_mname);
+ }
+ *statp = EX_TEMPFAIL;
+ if (vp != NULL)
+ sm_free(vp); /* XXX */
+
+ switch (save_errno - E_LDAPBASE)
+ {
+# ifdef LDAP_SERVER_DOWN
+ case LDAP_SERVER_DOWN:
+# endif /* LDAP_SERVER_DOWN */
+ case LDAP_TIMEOUT:
+ case LDAP_UNAVAILABLE:
+ /* server disappeared, try reopen on next search */
+ ldapmap_close(map);
+ break;
+ }
+ errno = save_errno;
+ return NULL;
+ }
+# endif /* _FFR_LDAP_RECURSION */
+
+ /* Did we match anything? */
+ if (vp == NULL && !bitset(MF_MATCHONLY, map->map_mflags))
+ return NULL;
+
+ if (*statp == EX_OK)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, CurEnv->e_id,
+ "ldap %.100s => %s", name,
+ vp == NULL ? "<NULL>" : vp);
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ result = map_rewrite(map, name, strlen(name), NULL);
+ else
+ {
+ /* vp != NULL according to test above */
+ result = map_rewrite(map, vp, strlen(vp), av);
+ }
+ if (vp != NULL)
+ sm_free(vp); /* XXX */
+ }
+ return result;
+}
+
+/*
+** LDAPMAP_FINDCONN -- find an LDAP connection to the server
+**
+** Cache LDAP connections based on the host, port, bind DN,
+** secret, and PID so we don't have multiple connections open to
+** the same server for different maps. Need a separate connection
+** per PID since a parent process may close the map before the
+** child is done with it.
+**
+** Parameters:
+** lmap -- LDAP map information
+**
+** Returns:
+** Symbol table entry for the LDAP connection.
+*/
+
+static STAB *
+ldapmap_findconn(lmap)
+ SM_LDAP_STRUCT *lmap;
+{
+ char *format;
+ char *nbuf;
+ STAB *SM_NONVOLATILE s = NULL;
+
+# if _FFR_LDAP_SETVERSION
+ format = "%s%c%d%c%d%c%s%c%s%d";
+# else /* _FFR_LDAP_SETVERSION */
+ format = "%s%c%d%c%s%c%s%d";
+# endif /* _FFR_LDAP_SETVERSION */
+ nbuf = sm_stringf_x(format,
+ (lmap->ldap_target == NULL ? "localhost"
+ : lmap->ldap_target),
+ CONDELSE,
+ lmap->ldap_port,
+ CONDELSE,
+# if _FFR_LDAP_SETVERSION
+ lmap->ldap_version,
+ CONDELSE,
+# endif /* _FFR_LDAP_SETVERSION */
+ (lmap->ldap_binddn == NULL ? ""
+ : lmap->ldap_binddn),
+ CONDELSE,
+ (lmap->ldap_secret == NULL ? ""
+ : lmap->ldap_secret),
+ (int) CurrentPid);
+ SM_TRY
+ s = stab(nbuf, ST_LMAP, ST_ENTER);
+ SM_FINALLY
+ sm_free(nbuf);
+ SM_END_TRY
+ return s;
+}
+/*
+** LDAPMAP_PARSEARGS -- parse ldap map definition args.
+*/
+
+static struct lamvalues LDAPAuthMethods[] =
+{
+ { "none", LDAP_AUTH_NONE },
+ { "simple", LDAP_AUTH_SIMPLE },
+# ifdef LDAP_AUTH_KRBV4
+ { "krbv4", LDAP_AUTH_KRBV4 },
+# endif /* LDAP_AUTH_KRBV4 */
+ { NULL, 0 }
+};
+
+static struct ladvalues LDAPAliasDereference[] =
+{
+ { "never", LDAP_DEREF_NEVER },
+ { "always", LDAP_DEREF_ALWAYS },
+ { "search", LDAP_DEREF_SEARCHING },
+ { "find", LDAP_DEREF_FINDING },
+ { NULL, 0 }
+};
+
+static struct lssvalues LDAPSearchScope[] =
+{
+ { "base", LDAP_SCOPE_BASE },
+ { "one", LDAP_SCOPE_ONELEVEL },
+ { "sub", LDAP_SCOPE_SUBTREE },
+ { NULL, 0 }
+};
+
+bool
+ldapmap_parseargs(map, args)
+ MAP *map;
+ char *args;
+{
+ bool secretread = true;
+# if _FFR_LDAP_URI
+ bool ldaphost = false;
+# endif /* _FFR_LDAP_URI */
+ int i;
+ register char *p = args;
+ SM_LDAP_STRUCT *lmap;
+ struct lamvalues *lam;
+ struct ladvalues *lad;
+ struct lssvalues *lss;
+ char ldapfilt[MAXLINE];
+ char m_tmp[MAXPATHLEN + LDAPMAP_MAX_PASSWD];
+
+ /* Get ldap struct pointer from map */
+ lmap = (SM_LDAP_STRUCT *) map->map_db1;
+
+ /* Check if setting the initial LDAP defaults */
+ if (lmap == NULL || lmap != LDAPDefaults)
+ {
+ /* We need to alloc an SM_LDAP_STRUCT struct */
+ lmap = (SM_LDAP_STRUCT *) xalloc(sizeof *lmap);
+ if (LDAPDefaults == NULL)
+ sm_ldap_clear(lmap);
+ else
+ STRUCTCOPY(*LDAPDefaults, *lmap);
+ }
+
+ /* there is no check whether there is really an argument */
+ map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
+ map->map_spacesub = SpaceSub; /* default value */
+
+ /* Check if setting up an alias or file class LDAP map */
+ if (bitset(MF_ALIAS, map->map_mflags))
+ {
+ /* Comma separate if used as an alias file */
+ map->map_coldelim = ',';
+ if (*args == '\0')
+ {
+ int n;
+ char *lc;
+ char jbuf[MAXHOSTNAMELEN];
+ char lcbuf[MAXLINE];
+
+ /* Get $j */
+ expand("\201j", jbuf, sizeof jbuf, &BlankEnvelope);
+ if (jbuf[0] == '\0')
+ {
+ (void) sm_strlcpy(jbuf, "localhost",
+ sizeof jbuf);
+ }
+
+ lc = macvalue(macid("{sendmailMTACluster}"), CurEnv);
+ if (lc == NULL)
+ lc = "";
+ else
+ {
+ expand(lc, lcbuf, sizeof lcbuf, CurEnv);
+ lc = lcbuf;
+ }
+
+ n = sm_snprintf(ldapfilt, sizeof ldapfilt,
+ "(&(objectClass=sendmailMTAAliasObject)(sendmailMTAAliasGrouping=aliases)(|(sendmailMTACluster=%s)(sendmailMTAHost=%s))(sendmailMTAKey=%%0))",
+ lc, jbuf);
+ if (n >= sizeof ldapfilt)
+ {
+ syserr("%s: Default LDAP string too long",
+ map->map_mname);
+ return false;
+ }
+
+ /* default args for an alias LDAP entry */
+ lmap->ldap_filter = ldapfilt;
+ lmap->ldap_attr[0] = "sendmailMTAAliasValue";
+ lmap->ldap_attr[1] = NULL;
+ }
+ }
+ else if (bitset(MF_FILECLASS, map->map_mflags))
+ {
+ /* Space separate if used as a file class file */
+ map->map_coldelim = ' ';
+ }
+
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'N':
+ map->map_mflags |= MF_INCLNULL;
+ map->map_mflags &= ~MF_TRY0NULL;
+ break;
+
+ case 'O':
+ map->map_mflags &= ~MF_TRY1NULL;
+ break;
+
+ case 'o':
+ map->map_mflags |= MF_OPTIONAL;
+ break;
+
+ case 'f':
+ map->map_mflags |= MF_NOFOLDCASE;
+ break;
+
+ case 'm':
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 'A':
+ map->map_mflags |= MF_APPEND;
+ break;
+
+ case 'q':
+ map->map_mflags |= MF_KEEPQUOTES;
+ break;
+
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'T':
+ map->map_tapp = ++p;
+ break;
+
+ case 't':
+ map->map_mflags |= MF_NODEFER;
+ break;
+
+ case 'S':
+ map->map_spacesub = *++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ case 'z':
+ if (*++p != '\\')
+ map->map_coldelim = *p;
+ else
+ {
+ switch (*++p)
+ {
+ case 'n':
+ map->map_coldelim = '\n';
+ break;
+
+ case 't':
+ map->map_coldelim = '\t';
+ break;
+
+ default:
+ map->map_coldelim = '\\';
+ }
+ }
+ break;
+
+ /* Start of ldapmap specific args */
+ case 'V':
+ if (*++p != '\\')
+ lmap->ldap_attrsep = *p;
+ else
+ {
+ switch (*++p)
+ {
+ case 'n':
+ lmap->ldap_attrsep = '\n';
+ break;
+
+ case 't':
+ lmap->ldap_attrsep = '\t';
+ break;
+
+ default:
+ lmap->ldap_attrsep = '\\';
+ }
+ }
+ break;
+
+ case 'k': /* search field */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_filter = p;
+ break;
+
+ case 'v': /* attr to return */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_attr[0] = p;
+ lmap->ldap_attr[1] = NULL;
+ break;
+
+ case '1':
+ map->map_mflags |= MF_SINGLEMATCH;
+ break;
+
+ /* args stolen from ldapsearch.c */
+ case 'R': /* don't auto chase referrals */
+# ifdef LDAP_REFERRALS
+ lmap->ldap_options &= ~LDAP_OPT_REFERRALS;
+# else /* LDAP_REFERRALS */
+ syserr("compile with -DLDAP_REFERRALS for referral support");
+# endif /* LDAP_REFERRALS */
+ break;
+
+ case 'n': /* retrieve attribute names only */
+ lmap->ldap_attrsonly = LDAPMAP_TRUE;
+ break;
+
+ case 'r': /* alias dereferencing */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+
+ if (sm_strncasecmp(p, "LDAP_DEREF_", 11) == 0)
+ p += 11;
+
+ for (lad = LDAPAliasDereference;
+ lad != NULL && lad->lad_name != NULL; lad++)
+ {
+ if (sm_strncasecmp(p, lad->lad_name,
+ strlen(lad->lad_name)) == 0)
+ break;
+ }
+ if (lad->lad_name != NULL)
+ lmap->ldap_deref = lad->lad_code;
+ else
+ {
+ /* bad config line */
+ if (!bitset(MCF_OPTFILE,
+ map->map_class->map_cflags))
+ {
+ char *ptr;
+
+ if ((ptr = strchr(p, ' ')) != NULL)
+ *ptr = '\0';
+ syserr("Deref must be [never|always|search|find] (not %s) in map %s",
+ p, map->map_mname);
+ if (ptr != NULL)
+ *ptr = ' ';
+ return false;
+ }
+ }
+ break;
+
+ case 's': /* search scope */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+
+ if (sm_strncasecmp(p, "LDAP_SCOPE_", 11) == 0)
+ p += 11;
+
+ for (lss = LDAPSearchScope;
+ lss != NULL && lss->lss_name != NULL; lss++)
+ {
+ if (sm_strncasecmp(p, lss->lss_name,
+ strlen(lss->lss_name)) == 0)
+ break;
+ }
+ if (lss->lss_name != NULL)
+ lmap->ldap_scope = lss->lss_code;
+ else
+ {
+ /* bad config line */
+ if (!bitset(MCF_OPTFILE,
+ map->map_class->map_cflags))
+ {
+ char *ptr;
+
+ if ((ptr = strchr(p, ' ')) != NULL)
+ *ptr = '\0';
+ syserr("Scope must be [base|one|sub] (not %s) in map %s",
+ p, map->map_mname);
+ if (ptr != NULL)
+ *ptr = ' ';
+ return false;
+ }
+ }
+ break;
+
+ case 'h': /* ldap host */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+# if _FFR_LDAP_URI
+ if (lmap->ldap_uri)
+ {
+ syserr("Can not specify both an LDAP host and an LDAP URI in map %s",
+ map->map_mname);
+ return false;
+ }
+ ldaphost = true;
+# endif /* _FFR_LDAP_URI */
+ lmap->ldap_target = p;
+ break;
+
+ case 'b': /* search base */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_base = p;
+ break;
+
+ case 'p': /* ldap port */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_port = atoi(p);
+ break;
+
+ case 'l': /* time limit */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_timelimit = atoi(p);
+ lmap->ldap_timeout.tv_sec = lmap->ldap_timelimit;
+ break;
+
+ case 'Z':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_sizelimit = atoi(p);
+ break;
+
+ case 'd': /* Dn to bind to server as */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_binddn = p;
+ break;
+
+ case 'M': /* Method for binding */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+
+ if (sm_strncasecmp(p, "LDAP_AUTH_", 10) == 0)
+ p += 10;
+
+ for (lam = LDAPAuthMethods;
+ lam != NULL && lam->lam_name != NULL; lam++)
+ {
+ if (sm_strncasecmp(p, lam->lam_name,
+ strlen(lam->lam_name)) == 0)
+ break;
+ }
+ if (lam->lam_name != NULL)
+ lmap->ldap_method = lam->lam_code;
+ else
+ {
+ /* bad config line */
+ if (!bitset(MCF_OPTFILE,
+ map->map_class->map_cflags))
+ {
+ char *ptr;
+
+ if ((ptr = strchr(p, ' ')) != NULL)
+ *ptr = '\0';
+ syserr("Method for binding must be [none|simple|krbv4] (not %s) in map %s",
+ p, map->map_mname);
+ if (ptr != NULL)
+ *ptr = ' ';
+ return false;
+ }
+ }
+
+ break;
+
+ /*
+ ** This is a string that is dependent on the
+ ** method used defined above.
+ */
+
+ case 'P': /* Secret password for binding */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_secret = p;
+ secretread = false;
+ break;
+
+# if _FFR_LDAP_URI
+ case 'H': /* Use LDAP URI */
+# if !USE_LDAP_INIT
+ syserr("Must compile with -DUSE_LDAP_INIT to use LDAP URIs (-H) in map %s",
+ map->map_mname);
+ return false;
+# else /* !USE_LDAP_INIT */
+ if (ldaphost)
+ {
+ syserr("Can not specify both an LDAP host and an LDAP URI in map %s",
+ map->map_mname);
+ return false;
+ }
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_target = p;
+ lmap->ldap_uri = true;
+ break;
+# endif /* !USE_LDAP_INIT */
+# endif /* _FFR_LDAP_URI */
+
+# if _FFR_LDAP_SETVERSION
+ case 'w':
+ /* -w should be for passwd, -P should be for version */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ lmap->ldap_version = atoi(p);
+# ifdef LDAP_VERSION_MAX
+ if (lmap->ldap_version > LDAP_VERSION_MAX)
+ {
+ syserr("LDAP version %d exceeds max of %d in map %s",
+ lmap->ldap_version, LDAP_VERSION_MAX,
+ map->map_mname);
+ return false;
+ }
+# endif /* LDAP_VERSION_MAX */
+# ifdef LDAP_VERSION_MIN
+ if (lmap->ldap_version < LDAP_VERSION_MIN)
+ {
+ syserr("LDAP version %d is lower than min of %d in map %s",
+ lmap->ldap_version, LDAP_VERSION_MIN,
+ map->map_mname);
+ return false;
+ }
+# endif /* LDAP_VERSION_MIN */
+ break;
+# endif /* _FFR_LDAP_SETVERSION */
+
+ default:
+ syserr("Illegal option %c map %s", *p, map->map_mname);
+ break;
+ }
+
+ /* need to account for quoted strings here */
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ {
+ if (*p == '"')
+ {
+ while (*++p != '"' && *p != '\0')
+ continue;
+ if (*p != '\0')
+ p++;
+ }
+ else
+ p++;
+ }
+
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+
+ if (map->map_app != NULL)
+ map->map_app = newstr(ldapmap_dequote(map->map_app));
+ if (map->map_tapp != NULL)
+ map->map_tapp = newstr(ldapmap_dequote(map->map_tapp));
+
+ /*
+ ** We need to swallow up all the stuff into a struct
+ ** and dump it into map->map_dbptr1
+ */
+
+ if (lmap->ldap_target != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_target != lmap->ldap_target))
+ lmap->ldap_target = newstr(ldapmap_dequote(lmap->ldap_target));
+ map->map_domain = lmap->ldap_target;
+
+ if (lmap->ldap_binddn != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_binddn != lmap->ldap_binddn))
+ lmap->ldap_binddn = newstr(ldapmap_dequote(lmap->ldap_binddn));
+
+ if (lmap->ldap_secret != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_secret != lmap->ldap_secret))
+ {
+ SM_FILE_T *sfd;
+ long sff = SFF_OPENASROOT|SFF_ROOTOK|SFF_NOWLINK|SFF_NOWWFILES|SFF_NOGWFILES;
+
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+
+ /* need to use method to map secret to passwd string */
+ switch (lmap->ldap_method)
+ {
+ case LDAP_AUTH_NONE:
+ /* Do nothing */
+ break;
+
+ case LDAP_AUTH_SIMPLE:
+
+ /*
+ ** Secret is the name of a file with
+ ** the first line as the password.
+ */
+
+ /* Already read in the secret? */
+ if (secretread)
+ break;
+
+ sfd = safefopen(ldapmap_dequote(lmap->ldap_secret),
+ O_RDONLY, 0, sff);
+ if (sfd == NULL)
+ {
+ syserr("LDAP map: cannot open secret %s",
+ ldapmap_dequote(lmap->ldap_secret));
+ return false;
+ }
+ lmap->ldap_secret = sfgets(m_tmp, sizeof m_tmp,
+ sfd, TimeOuts.to_fileopen,
+ "ldapmap_parseargs");
+ (void) sm_io_close(sfd, SM_TIME_DEFAULT);
+ if (strlen(m_tmp) > LDAPMAP_MAX_PASSWD)
+ {
+ syserr("LDAP map: secret in %s too long",
+ ldapmap_dequote(lmap->ldap_secret));
+ return false;
+ }
+ if (lmap->ldap_secret != NULL &&
+ strlen(m_tmp) > 0)
+ {
+ /* chomp newline */
+ if (m_tmp[strlen(m_tmp) - 1] == '\n')
+ m_tmp[strlen(m_tmp) - 1] = '\0';
+
+ lmap->ldap_secret = m_tmp;
+ }
+ break;
+
+# ifdef LDAP_AUTH_KRBV4
+ case LDAP_AUTH_KRBV4:
+
+ /*
+ ** Secret is where the ticket file is
+ ** stashed
+ */
+
+ (void) sm_snprintf(m_tmp, sizeof m_tmp,
+ "KRBTKFILE=%s",
+ ldapmap_dequote(lmap->ldap_secret));
+ lmap->ldap_secret = m_tmp;
+ break;
+# endif /* LDAP_AUTH_KRBV4 */
+
+ default: /* Should NEVER get here */
+ syserr("LDAP map: Illegal value in lmap method");
+ return false;
+ /* NOTREACHED */
+ break;
+ }
+ }
+
+ if (lmap->ldap_secret != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_secret != lmap->ldap_secret))
+ lmap->ldap_secret = newstr(ldapmap_dequote(lmap->ldap_secret));
+
+ if (lmap->ldap_base != NULL &&
+ (LDAPDefaults == NULL ||
+ LDAPDefaults == lmap ||
+ LDAPDefaults->ldap_base != lmap->ldap_base))
+ lmap->ldap_base = newstr(ldapmap_dequote(lmap->ldap_base));
+
+ /*
+ ** Save the server from extra work. If request is for a single
+ ** match, tell the server to only return enough records to
+ ** determine if there is a single match or not. This can not
+ ** be one since the server would only return one and we wouldn't
+ ** know if there were others available.
+ */
+
+ if (bitset(MF_SINGLEMATCH, map->map_mflags))
+ lmap->ldap_sizelimit = 2;
+
+ /* If setting defaults, don't process ldap_filter and ldap_attr */
+ if (lmap == LDAPDefaults)
+ return true;
+
+ if (lmap->ldap_filter != NULL)
+ lmap->ldap_filter = newstr(ldapmap_dequote(lmap->ldap_filter));
+ else
+ {
+ if (!bitset(MCF_OPTFILE, map->map_class->map_cflags))
+ {
+ syserr("No filter given in map %s", map->map_mname);
+ return false;
+ }
+ }
+
+ if (lmap->ldap_attr[0] != NULL)
+ {
+# if _FFR_LDAP_RECURSION
+ bool recurse = false;
+ bool normalseen = false;
+# endif /* _FFR_LDAP_RECURSION */
+
+ i = 0;
+ p = ldapmap_dequote(lmap->ldap_attr[0]);
+ lmap->ldap_attr[0] = NULL;
+
+# if _FFR_LDAP_RECURSION
+ /* Prime the attr list with the objectClass attribute */
+ lmap->ldap_attr[i] = "objectClass";
+ lmap->ldap_attr_type[i] = SM_LDAP_ATTR_OBJCLASS;
+ lmap->ldap_attr_needobjclass[i] = NULL;
+ i++;
+# endif /* _FFR_LDAP_RECURSION */
+
+ while (p != NULL)
+ {
+ char *v;
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ v = p;
+ p = strchr(v, ',');
+ if (p != NULL)
+ *p++ = '\0';
+
+ if (i >= LDAPMAP_MAX_ATTR)
+ {
+ syserr("Too many return attributes in %s (max %d)",
+ map->map_mname, LDAPMAP_MAX_ATTR);
+ return false;
+ }
+ if (*v != '\0')
+ {
+# if _FFR_LDAP_RECURSION
+ int j;
+ int use;
+ char *type;
+ char *needobjclass;
+
+ type = strchr(v, ':');
+ if (type != NULL)
+ {
+ *type++ = '\0';
+ needobjclass = strchr(type, ':');
+ if (needobjclass != NULL)
+ *needobjclass++ = '\0';
+ }
+ else
+ {
+ needobjclass = NULL;
+ }
+
+ use = i;
+
+ /* allow override on "objectClass" type */
+ if (sm_strcasecmp(v, "objectClass") == 0 &&
+ lmap->ldap_attr_type[0] == SM_LDAP_ATTR_OBJCLASS)
+ {
+ use = 0;
+ }
+ else
+ {
+ /*
+ ** Don't add something to attribute
+ ** list twice.
+ */
+
+ for (j = 1; j < i; j++)
+ {
+ if (sm_strcasecmp(v, lmap->ldap_attr[j]) == 0)
+ {
+ syserr("Duplicate attribute (%s) in %s",
+ v, map->map_mname);
+ return false;
+ }
+ }
+
+ lmap->ldap_attr[use] = newstr(v);
+ if (needobjclass != NULL &&
+ *needobjclass != '\0' &&
+ *needobjclass != '*')
+ {
+ lmap->ldap_attr_needobjclass[use] = newstr(needobjclass);
+ }
+ else
+ {
+ lmap->ldap_attr_needobjclass[use] = NULL;
+ }
+
+ }
+
+ if (type != NULL && *type != '\0')
+ {
+ if (sm_strcasecmp(type, "dn") == 0)
+ {
+ recurse = true;
+ lmap->ldap_attr_type[use] = SM_LDAP_ATTR_DN;
+ }
+ else if (sm_strcasecmp(type, "filter") == 0)
+ {
+ recurse = true;
+ lmap->ldap_attr_type[use] = SM_LDAP_ATTR_FILTER;
+ }
+ else if (sm_strcasecmp(type, "url") == 0)
+ {
+ recurse = true;
+ lmap->ldap_attr_type[use] = SM_LDAP_ATTR_URL;
+ }
+ else if (sm_strcasecmp(type, "normal") == 0)
+ {
+ lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL;
+ normalseen = true;
+ }
+ else
+ {
+ syserr("Unknown attribute type (%s) in %s",
+ type, map->map_mname);
+ return false;
+ }
+ }
+ else
+ {
+ lmap->ldap_attr_type[use] = SM_LDAP_ATTR_NORMAL;
+ normalseen = true;
+ }
+# else /* _FFR_LDAP_RECURSION */
+ lmap->ldap_attr[i] = newstr(v);
+# endif /* _FFR_LDAP_RECURSION */
+ i++;
+ }
+ }
+ lmap->ldap_attr[i] = NULL;
+# if _FFR_LDAP_RECURSION
+ if (recurse && !normalseen)
+ {
+ syserr("LDAP recursion requested in %s but no returnable attribute given",
+ map->map_mname);
+ return false;
+ }
+ if (recurse && lmap->ldap_attrsonly == LDAPMAP_TRUE)
+ {
+ syserr("LDAP recursion requested in %s can not be used with -n",
+ map->map_mname);
+ return false;
+ }
+# endif /* _FFR_LDAP_RECURSION */
+ }
+ map->map_db1 = (ARBPTR_T) lmap;
+ return true;
+}
+
+/*
+** LDAPMAP_SET_DEFAULTS -- Read default map spec from LDAPDefaults in .cf
+**
+** Parameters:
+** spec -- map argument string from LDAPDefaults option
+**
+** Returns:
+** None.
+*/
+
+void
+ldapmap_set_defaults(spec)
+ char *spec;
+{
+ STAB *class;
+ MAP map;
+
+ /* Allocate and set the default values */
+ if (LDAPDefaults == NULL)
+ LDAPDefaults = (SM_LDAP_STRUCT *) xalloc(sizeof *LDAPDefaults);
+ sm_ldap_clear(LDAPDefaults);
+
+ memset(&map, '\0', sizeof map);
+
+ /* look up the class */
+ class = stab("ldap", ST_MAPCLASS, ST_FIND);
+ if (class == NULL)
+ {
+ syserr("readcf: LDAPDefaultSpec: class ldap not available");
+ return;
+ }
+ map.map_class = &class->s_mapclass;
+ map.map_db1 = (ARBPTR_T) LDAPDefaults;
+ map.map_mname = "O LDAPDefaultSpec";
+
+ (void) ldapmap_parseargs(&map, spec);
+
+ /* These should never be set in LDAPDefaults */
+ if (map.map_mflags != (MF_TRY0NULL|MF_TRY1NULL) ||
+ map.map_spacesub != SpaceSub ||
+ map.map_app != NULL ||
+ map.map_tapp != NULL)
+ {
+ syserr("readcf: option LDAPDefaultSpec: Do not set non-LDAP specific flags");
+ SM_FREE_CLR(map.map_app);
+ SM_FREE_CLR(map.map_tapp);
+ }
+
+ if (LDAPDefaults->ldap_filter != NULL)
+ {
+ syserr("readcf: option LDAPDefaultSpec: Do not set the LDAP search filter");
+
+ /* don't free, it isn't malloc'ed in parseargs */
+ LDAPDefaults->ldap_filter = NULL;
+ }
+
+ if (LDAPDefaults->ldap_attr[0] != NULL)
+ {
+ syserr("readcf: option LDAPDefaultSpec: Do not set the requested LDAP attributes");
+ /* don't free, they aren't malloc'ed in parseargs */
+ LDAPDefaults->ldap_attr[0] = NULL;
+ }
+}
+#endif /* LDAPMAP */
+/*
+** PH map
+*/
+
+#if PH_MAP
+
+/*
+** Support for the CCSO Nameserver (ph/qi).
+** This code is intended to replace the so-called "ph mailer".
+** Contributed by Mark D. Roth <roth@uiuc.edu>. Contact him for support.
+*/
+
+/* what version of the ph map code we're running */
+static char phmap_id[PH_BUF_SIZE];
+
+/* sendmail version for phmap id string */
+extern const char Version[];
+
+/*
+** PH_MAP_PARSEARGS -- parse ph map definition args.
+*/
+
+bool
+ph_map_parseargs(map, args)
+ MAP *map;
+ char *args;
+{
+ register bool done;
+ register char *p = args;
+ PH_MAP_STRUCT *pmap = NULL;
+
+ /* initialize version string */
+ (void) sm_snprintf(phmap_id, sizeof phmap_id,
+ "sendmail-%s phmap-20010529 libphclient-%s",
+ Version, libphclient_version);
+
+ pmap = (PH_MAP_STRUCT *) xalloc(sizeof *pmap);
+
+ /* defaults */
+ pmap->ph_servers = NULL;
+ pmap->ph_field_list = NULL;
+ pmap->ph = NULL;
+ pmap->ph_timeout = 0;
+ pmap->ph_fastclose = 0;
+
+ map->map_mflags |= MF_TRY0NULL|MF_TRY1NULL;
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'N':
+ map->map_mflags |= MF_INCLNULL;
+ map->map_mflags &= ~MF_TRY0NULL;
+ break;
+
+ case 'O':
+ map->map_mflags &= ~MF_TRY1NULL;
+ break;
+
+ case 'o':
+ map->map_mflags |= MF_OPTIONAL;
+ break;
+
+ case 'f':
+ map->map_mflags |= MF_NOFOLDCASE;
+ break;
+
+ case 'm':
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 'A':
+ map->map_mflags |= MF_APPEND;
+ break;
+
+ case 'q':
+ map->map_mflags |= MF_KEEPQUOTES;
+ break;
+
+ case 't':
+ map->map_mflags |= MF_NODEFER;
+ break;
+
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'T':
+ map->map_tapp = ++p;
+ break;
+
+ case 'l':
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ pmap->ph_timeout = atoi(p);
+ break;
+
+ case 'S':
+ map->map_spacesub = *++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ case 'h': /* PH server list */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ pmap->ph_servers = p;
+ break;
+
+ case 'v':
+ sm_syslog(LOG_WARNING, NULL,
+ "ph_map_parseargs: WARNING: -v option will be removed in a future release - please use -k instead");
+ /* intentional fallthrough for backward compatibility */
+ /* FALLTHROUGH */
+
+ case 'k': /* fields to search for */
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ pmap->ph_field_list = p;
+ break;
+
+ default:
+ syserr("ph_map_parseargs: unknown option -%c", *p);
+ }
+
+ /* try to account for quoted strings */
+ done = isascii(*p) && isspace(*p);
+ while (*p != '\0' && !done)
+ {
+ if (*p == '"')
+ {
+ while (*++p != '"' && *p != '\0')
+ continue;
+ if (*p != '\0')
+ p++;
+ }
+ else
+ p++;
+ done = isascii(*p) && isspace(*p);
+ }
+
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+
+ if (map->map_app != NULL)
+ map->map_app = newstr(ph_map_dequote(map->map_app));
+ if (map->map_tapp != NULL)
+ map->map_tapp = newstr(ph_map_dequote(map->map_tapp));
+
+ if (pmap->ph_field_list != NULL)
+ pmap->ph_field_list = newstr(ph_map_dequote(pmap->ph_field_list));
+
+ if (pmap->ph_servers != NULL)
+ pmap->ph_servers = newstr(ph_map_dequote(pmap->ph_servers));
+ else
+ {
+ syserr("ph_map_parseargs: -h flag is required");
+ return false;
+ }
+
+ map->map_db1 = (ARBPTR_T) pmap;
+ return true;
+}
+
+/*
+** PH_MAP_CLOSE -- close the connection to the ph server
+*/
+
+void
+ph_map_close(map)
+ MAP *map;
+{
+ PH_MAP_STRUCT *pmap;
+
+ pmap = (PH_MAP_STRUCT *)map->map_db1;
+ if (tTd(38, 9))
+ sm_dprintf("ph_map_close(%s): pmap->ph_fastclose=%d\n",
+ map->map_mname, pmap->ph_fastclose);
+
+
+ if (pmap->ph != NULL)
+ {
+ ph_set_sendhook(pmap->ph, NULL);
+ ph_set_recvhook(pmap->ph, NULL);
+ ph_close(pmap->ph, pmap->ph_fastclose);
+ }
+
+ map->map_mflags &= ~(MF_OPEN|MF_WRITABLE);
+}
+
+static jmp_buf PHTimeout;
+
+/* ARGSUSED */
+static void
+ph_timeout(unused)
+ int unused;
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(PHTimeout, 1);
+}
+
+static void
+ph_map_send_debug(text)
+ char *text;
+{
+ if (LogLevel > 9)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "ph_map_send_debug: ==> %s", text);
+ if (tTd(38, 20))
+ sm_dprintf("ph_map_send_debug: ==> %s\n", text);
+}
+
+static void
+ph_map_recv_debug(text)
+ char *text;
+{
+ if (LogLevel > 10)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "ph_map_recv_debug: <== %s", text);
+ if (tTd(38, 21))
+ sm_dprintf("ph_map_recv_debug: <== %s\n", text);
+}
+
+/*
+** PH_MAP_OPEN -- sub for opening PH map
+*/
+bool
+ph_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ PH_MAP_STRUCT *pmap;
+ register SM_EVENT *ev = NULL;
+ int save_errno = 0;
+ char *hostlist, *host;
+
+ if (tTd(38, 2))
+ sm_dprintf("ph_map_open(%s)\n", map->map_mname);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+
+ if (CurEnv != NULL && CurEnv->e_sendmode == SM_DEFER &&
+ bitset(MF_DEFER, map->map_mflags))
+ {
+ if (tTd(9, 1))
+ sm_dprintf("ph_map_open(%s) => DEFERRED\n",
+ map->map_mname);
+
+ /*
+ ** Unset MF_DEFER here so that map_lookup() returns
+ ** a temporary failure using the bogus map and
+ ** map->map_tapp instead of the default permanent error.
+ */
+
+ map->map_mflags &= ~MF_DEFER;
+ return false;
+ }
+
+ pmap = (PH_MAP_STRUCT *)map->map_db1;
+ pmap->ph_fastclose = 0; /* refresh field for reopen */
+
+ /* try each host in the list */
+ hostlist = newstr(pmap->ph_servers);
+ for (host = strtok(hostlist, " ");
+ host != NULL;
+ host = strtok(NULL, " "))
+ {
+ /* set timeout */
+ if (pmap->ph_timeout != 0)
+ {
+ if (setjmp(PHTimeout) != 0)
+ {
+ ev = NULL;
+ if (LogLevel > 1)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "timeout connecting to PH server %.100s",
+ host);
+ errno = ETIMEDOUT;
+ goto ph_map_open_abort;
+ }
+ ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0);
+ }
+
+ /* open connection to server */
+ if (!ph_open(&(pmap->ph), host, PH_ROUNDROBIN|PH_DONTID,
+ ph_map_send_debug, ph_map_recv_debug) &&
+ !ph_id(pmap->ph, phmap_id))
+ {
+ if (ev != NULL)
+ sm_clrevent(ev);
+ sm_free(hostlist); /* XXX */
+ return true;
+ }
+
+ ph_map_open_abort:
+ save_errno = errno;
+ if (ev != NULL)
+ sm_clrevent(ev);
+ pmap->ph_fastclose = PH_FASTCLOSE;
+ ph_map_close(map);
+ errno = save_errno;
+ }
+
+ if (bitset(MF_NODEFER, map->map_mflags))
+ {
+ if (errno == 0)
+ errno = EAGAIN;
+ syserr("ph_map_open: %s: cannot connect to PH server",
+ map->map_mname);
+ }
+ else if (!bitset(MF_OPTIONAL, map->map_mflags) && LogLevel > 1)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "ph_map_open: %s: cannot connect to PH server",
+ map->map_mname);
+ sm_free(hostlist); /* XXX */
+ return false;
+}
+
+/*
+** PH_MAP_LOOKUP -- look up key from ph server
+*/
+
+char *
+ph_map_lookup(map, key, args, pstat)
+ MAP *map;
+ char *key;
+ char **args;
+ int *pstat;
+{
+ int i, save_errno = 0;
+ register SM_EVENT *ev = NULL;
+ PH_MAP_STRUCT *pmap;
+ char *value = NULL;
+
+ pmap = (PH_MAP_STRUCT *)map->map_db1;
+
+ *pstat = EX_OK;
+
+ /* set timeout */
+ if (pmap->ph_timeout != 0)
+ {
+ if (setjmp(PHTimeout) != 0)
+ {
+ ev = NULL;
+ if (LogLevel > 1)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "timeout during PH lookup of %.100s",
+ key);
+ errno = ETIMEDOUT;
+ *pstat = EX_TEMPFAIL;
+ goto ph_map_lookup_abort;
+ }
+ ev = sm_setevent(pmap->ph_timeout, ph_timeout, 0);
+ }
+
+ /* perform lookup */
+ i = ph_email_resolve(pmap->ph, key, pmap->ph_field_list, &value);
+ if (i == -1)
+ *pstat = EX_TEMPFAIL;
+ else if (i == PH_NOMATCH || i == PH_DATAERR)
+ *pstat = EX_UNAVAILABLE;
+
+ ph_map_lookup_abort:
+ if (ev != NULL)
+ sm_clrevent(ev);
+
+ /*
+ ** Close the connection if the timer popped
+ ** or we got a temporary PH error
+ */
+
+ if (*pstat == EX_TEMPFAIL)
+ {
+ save_errno = errno;
+ pmap->ph_fastclose = PH_FASTCLOSE;
+ ph_map_close(map);
+ errno = save_errno;
+ }
+
+ if (*pstat == EX_OK)
+ {
+ if (tTd(38,20))
+ sm_dprintf("ph_map_lookup: %s => %s\n", key, value);
+
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, key, strlen(key), NULL);
+ else
+ return map_rewrite(map, value, strlen(value), args);
+ }
+
+ return NULL;
+}
+#endif /* PH_MAP */
+/*
+** syslog map
+*/
+
+#define map_prio map_lockfd /* overload field */
+
+/*
+** SYSLOG_MAP_PARSEARGS -- check for priority level to syslog messages.
+*/
+
+bool
+syslog_map_parseargs(map, args)
+ MAP *map;
+ char *args;
+{
+ char *p = args;
+ char *priority = NULL;
+
+ /* there is no check whether there is really an argument */
+ while (*p != '\0')
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ ++p;
+ if (*p == 'D')
+ {
+ map->map_mflags |= MF_DEFER;
+ ++p;
+ }
+ else if (*p == 'S')
+ {
+ map->map_spacesub = *++p;
+ if (*p != '\0')
+ p++;
+ }
+ else if (*p == 'L')
+ {
+ while (*++p != '\0' && isascii(*p) && isspace(*p))
+ continue;
+ if (*p == '\0')
+ break;
+ priority = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+ else
+ {
+ syserr("Illegal option %c map syslog", *p);
+ ++p;
+ }
+ }
+
+ if (priority == NULL)
+ map->map_prio = LOG_INFO;
+ else
+ {
+ if (sm_strncasecmp("LOG_", priority, 4) == 0)
+ priority += 4;
+
+#ifdef LOG_EMERG
+ if (sm_strcasecmp("EMERG", priority) == 0)
+ map->map_prio = LOG_EMERG;
+ else
+#endif /* LOG_EMERG */
+#ifdef LOG_ALERT
+ if (sm_strcasecmp("ALERT", priority) == 0)
+ map->map_prio = LOG_ALERT;
+ else
+#endif /* LOG_ALERT */
+#ifdef LOG_CRIT
+ if (sm_strcasecmp("CRIT", priority) == 0)
+ map->map_prio = LOG_CRIT;
+ else
+#endif /* LOG_CRIT */
+#ifdef LOG_ERR
+ if (sm_strcasecmp("ERR", priority) == 0)
+ map->map_prio = LOG_ERR;
+ else
+#endif /* LOG_ERR */
+#ifdef LOG_WARNING
+ if (sm_strcasecmp("WARNING", priority) == 0)
+ map->map_prio = LOG_WARNING;
+ else
+#endif /* LOG_WARNING */
+#ifdef LOG_NOTICE
+ if (sm_strcasecmp("NOTICE", priority) == 0)
+ map->map_prio = LOG_NOTICE;
+ else
+#endif /* LOG_NOTICE */
+#ifdef LOG_INFO
+ if (sm_strcasecmp("INFO", priority) == 0)
+ map->map_prio = LOG_INFO;
+ else
+#endif /* LOG_INFO */
+#ifdef LOG_DEBUG
+ if (sm_strcasecmp("DEBUG", priority) == 0)
+ map->map_prio = LOG_DEBUG;
+ else
+#endif /* LOG_DEBUG */
+ {
+ syserr("syslog_map_parseargs: Unknown priority %s",
+ priority);
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+** SYSLOG_MAP_LOOKUP -- rewrite and syslog message. Always return empty string
+*/
+
+char *
+syslog_map_lookup(map, string, args, statp)
+ MAP *map;
+ char *string;
+ char **args;
+ int *statp;
+{
+ char *ptr = map_rewrite(map, string, strlen(string), args);
+
+ if (ptr != NULL)
+ {
+ if (tTd(38, 20))
+ sm_dprintf("syslog_map_lookup(%s (priority %d): %s\n",
+ map->map_mname, map->map_prio, ptr);
+
+ sm_syslog(map->map_prio, CurEnv->e_id, "%s", ptr);
+ }
+
+ *statp = EX_OK;
+ return "";
+}
+
+/*
+** HESIOD Modules
+*/
+
+#if HESIOD
+
+bool
+hes_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ if (tTd(38, 2))
+ sm_dprintf("hes_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+
+# ifdef HESIOD_INIT
+ if (HesiodContext != NULL || hesiod_init(&HesiodContext) == 0)
+ return true;
+
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("451 4.3.5 cannot initialize Hesiod map (%s)",
+ sm_errstring(errno));
+ return false;
+# else /* HESIOD_INIT */
+ if (hes_error() == HES_ER_UNINIT)
+ hes_init();
+ switch (hes_error())
+ {
+ case HES_ER_OK:
+ case HES_ER_NOTFOUND:
+ return true;
+ }
+
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("451 4.3.5 cannot initialize Hesiod map (%d)", hes_error());
+
+ return false;
+# endif /* HESIOD_INIT */
+}
+
+char *
+hes_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char **hp;
+
+ if (tTd(38, 20))
+ sm_dprintf("hes_map_lookup(%s, %s)\n", map->map_file, name);
+
+ if (name[0] == '\\')
+ {
+ char *np;
+ int nl;
+ int save_errno;
+ char nbuf[MAXNAME];
+
+ nl = strlen(name);
+ if (nl < sizeof nbuf - 1)
+ np = nbuf;
+ else
+ np = xalloc(strlen(name) + 2);
+ np[0] = '\\';
+ (void) sm_strlcpy(&np[1], name, (sizeof nbuf) - 1);
+# ifdef HESIOD_INIT
+ hp = hesiod_resolve(HesiodContext, np, map->map_file);
+# else /* HESIOD_INIT */
+ hp = hes_resolve(np, map->map_file);
+# endif /* HESIOD_INIT */
+ save_errno = errno;
+ if (np != nbuf)
+ sm_free(np); /* XXX */
+ errno = save_errno;
+ }
+ else
+ {
+# ifdef HESIOD_INIT
+ hp = hesiod_resolve(HesiodContext, name, map->map_file);
+# else /* HESIOD_INIT */
+ hp = hes_resolve(name, map->map_file);
+# endif /* HESIOD_INIT */
+ }
+# ifdef HESIOD_INIT
+ if (hp == NULL || *hp == NULL)
+ {
+ switch (errno)
+ {
+ case ENOENT:
+ *statp = EX_NOTFOUND;
+ break;
+ case ECONNREFUSED:
+ *statp = EX_TEMPFAIL;
+ break;
+ case EMSGSIZE:
+ case ENOMEM:
+ default:
+ *statp = EX_UNAVAILABLE;
+ break;
+ }
+ if (hp != NULL)
+ hesiod_free_list(HesiodContext, hp);
+ return NULL;
+ }
+# else /* HESIOD_INIT */
+ if (hp == NULL || hp[0] == NULL)
+ {
+ switch (hes_error())
+ {
+ case HES_ER_OK:
+ *statp = EX_OK;
+ break;
+
+ case HES_ER_NOTFOUND:
+ *statp = EX_NOTFOUND;
+ break;
+
+ case HES_ER_CONFIG:
+ *statp = EX_UNAVAILABLE;
+ break;
+
+ case HES_ER_NET:
+ *statp = EX_TEMPFAIL;
+ break;
+ }
+ return NULL;
+ }
+# endif /* HESIOD_INIT */
+
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ else
+ return map_rewrite(map, hp[0], strlen(hp[0]), av);
+}
+
+/*
+** HES_MAP_CLOSE -- free the Hesiod context
+*/
+
+void
+hes_map_close(map)
+ MAP *map;
+{
+ if (tTd(38, 20))
+ sm_dprintf("hes_map_close(%s)\n", map->map_file);
+
+# ifdef HESIOD_INIT
+ /* Free the hesiod context */
+ if (HesiodContext != NULL)
+ {
+ hesiod_end(HesiodContext);
+ HesiodContext = NULL;
+ }
+# endif /* HESIOD_INIT */
+}
+
+#endif /* HESIOD */
+/*
+** NeXT NETINFO Modules
+*/
+
+#if NETINFO
+
+# define NETINFO_DEFAULT_DIR "/aliases"
+# define NETINFO_DEFAULT_PROPERTY "members"
+
+/*
+** NI_MAP_OPEN -- open NetInfo Aliases
+*/
+
+bool
+ni_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ if (tTd(38, 2))
+ sm_dprintf("ni_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+ mode &= O_ACCMODE;
+
+ if (*map->map_file == '\0')
+ map->map_file = NETINFO_DEFAULT_DIR;
+
+ if (map->map_valcolnm == NULL)
+ map->map_valcolnm = NETINFO_DEFAULT_PROPERTY;
+
+ if (map->map_coldelim == '\0')
+ {
+ if (bitset(MF_ALIAS, map->map_mflags))
+ map->map_coldelim = ',';
+ else if (bitset(MF_FILECLASS, map->map_mflags))
+ map->map_coldelim = ' ';
+ }
+ return true;
+}
+
+
+/*
+** NI_MAP_LOOKUP -- look up a datum in NetInfo
+*/
+
+char *
+ni_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char *res;
+ char *propval;
+
+ if (tTd(38, 20))
+ sm_dprintf("ni_map_lookup(%s, %s)\n", map->map_mname, name);
+
+ propval = ni_propval(map->map_file, map->map_keycolnm, name,
+ map->map_valcolnm, map->map_coldelim);
+
+ if (propval == NULL)
+ return NULL;
+
+ SM_TRY
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ res = map_rewrite(map, name, strlen(name), NULL);
+ else
+ res = map_rewrite(map, propval, strlen(propval), av);
+ SM_FINALLY
+ sm_free(propval);
+ SM_END_TRY
+ return res;
+}
+
+
+static bool
+ni_getcanonname(name, hbsize, statp)
+ char *name;
+ int hbsize;
+ int *statp;
+{
+ char *vptr;
+ char *ptr;
+ char nbuf[MAXNAME + 1];
+
+ if (tTd(38, 20))
+ sm_dprintf("ni_getcanonname(%s)\n", name);
+
+ if (sm_strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ (void) shorten_hostname(nbuf);
+
+ /* we only accept single token search key */
+ if (strchr(nbuf, '.'))
+ {
+ *statp = EX_NOHOST;
+ return false;
+ }
+
+ /* Do the search */
+ vptr = ni_propval("/machines", NULL, nbuf, "name", '\n');
+
+ if (vptr == NULL)
+ {
+ *statp = EX_NOHOST;
+ return false;
+ }
+
+ /* Only want the first machine name */
+ if ((ptr = strchr(vptr, '\n')) != NULL)
+ *ptr = '\0';
+
+ if (sm_strlcpy(name, vptr, hbsize) >= hbsize)
+ {
+ sm_free(vptr);
+ *statp = EX_UNAVAILABLE;
+ return true;
+ }
+ sm_free(vptr);
+ *statp = EX_OK;
+ return false;
+}
+#endif /* NETINFO */
+/*
+** TEXT (unindexed text file) Modules
+**
+** This code donated by Sun Microsystems.
+*/
+
+#define map_sff map_lockfd /* overload field */
+
+
+/*
+** TEXT_MAP_OPEN -- open text table
+*/
+
+bool
+text_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ long sff;
+ int i;
+
+ if (tTd(38, 2))
+ sm_dprintf("text_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ errno = EPERM;
+ return false;
+ }
+
+ if (*map->map_file == '\0')
+ {
+ syserr("text map \"%s\": file name required",
+ map->map_mname);
+ return false;
+ }
+
+ if (map->map_file[0] != '/')
+ {
+ syserr("text map \"%s\": file name must be fully qualified",
+ map->map_mname);
+ return false;
+ }
+
+ sff = SFF_ROOTOK|SFF_REGONLY;
+ if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ if ((i = safefile(map->map_file, RunAsUid, RunAsGid, RunAsUserName,
+ sff, S_IRUSR, NULL)) != 0)
+ {
+ int save_errno = errno;
+
+ /* cannot open this map */
+ if (tTd(38, 2))
+ sm_dprintf("\tunsafe map file: %d\n", i);
+ errno = save_errno;
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("text map \"%s\": unsafe map file %s",
+ map->map_mname, map->map_file);
+ return false;
+ }
+
+ if (map->map_keycolnm == NULL)
+ map->map_keycolno = 0;
+ else
+ {
+ if (!(isascii(*map->map_keycolnm) && isdigit(*map->map_keycolnm)))
+ {
+ syserr("text map \"%s\", file %s: -k should specify a number, not %s",
+ map->map_mname, map->map_file,
+ map->map_keycolnm);
+ return false;
+ }
+ map->map_keycolno = atoi(map->map_keycolnm);
+ }
+
+ if (map->map_valcolnm == NULL)
+ map->map_valcolno = 0;
+ else
+ {
+ if (!(isascii(*map->map_valcolnm) && isdigit(*map->map_valcolnm)))
+ {
+ syserr("text map \"%s\", file %s: -v should specify a number, not %s",
+ map->map_mname, map->map_file,
+ map->map_valcolnm);
+ return false;
+ }
+ map->map_valcolno = atoi(map->map_valcolnm);
+ }
+
+ if (tTd(38, 2))
+ {
+ sm_dprintf("text_map_open(%s, %s): delimiter = ",
+ map->map_mname, map->map_file);
+ if (map->map_coldelim == '\0')
+ sm_dprintf("(white space)\n");
+ else
+ sm_dprintf("%c\n", map->map_coldelim);
+ }
+
+ map->map_sff = sff;
+ return true;
+}
+
+
+/*
+** TEXT_MAP_LOOKUP -- look up a datum in a TEXT table
+*/
+
+char *
+text_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char *vp;
+ auto int vsize;
+ int buflen;
+ SM_FILE_T *f;
+ char delim;
+ int key_idx;
+ bool found_it;
+ long sff = map->map_sff;
+ char search_key[MAXNAME + 1];
+ char linebuf[MAXLINE];
+ char buf[MAXNAME + 1];
+
+ found_it = false;
+ if (tTd(38, 20))
+ sm_dprintf("text_map_lookup(%s, %s)\n", map->map_mname, name);
+
+ buflen = strlen(name);
+ if (buflen > sizeof search_key - 1)
+ buflen = sizeof search_key - 1; /* XXX just cut if off? */
+ memmove(search_key, name, buflen);
+ search_key[buflen] = '\0';
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ makelower(search_key);
+
+ f = safefopen(map->map_file, O_RDONLY, FileMode, sff);
+ if (f == NULL)
+ {
+ map->map_mflags &= ~(MF_VALID|MF_OPEN);
+ *statp = EX_UNAVAILABLE;
+ return NULL;
+ }
+ key_idx = map->map_keycolno;
+ delim = map->map_coldelim;
+ while (sm_io_fgets(f, SM_TIME_DEFAULT,
+ linebuf, sizeof linebuf) != NULL)
+ {
+ char *p;
+
+ /* skip comment line */
+ if (linebuf[0] == '#')
+ continue;
+ p = strchr(linebuf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ p = get_column(linebuf, key_idx, delim, buf, sizeof buf);
+ if (p != NULL && sm_strcasecmp(search_key, p) == 0)
+ {
+ found_it = true;
+ break;
+ }
+ }
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ if (!found_it)
+ {
+ *statp = EX_NOTFOUND;
+ return NULL;
+ }
+ vp = get_column(linebuf, map->map_valcolno, delim, buf, sizeof buf);
+ if (vp == NULL)
+ {
+ *statp = EX_NOTFOUND;
+ return NULL;
+ }
+ vsize = strlen(vp);
+ *statp = EX_OK;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, name, strlen(name), NULL);
+ else
+ return map_rewrite(map, vp, vsize, av);
+}
+
+/*
+** TEXT_GETCANONNAME -- look up canonical name in hosts file
+*/
+
+static bool
+text_getcanonname(name, hbsize, statp)
+ char *name;
+ int hbsize;
+ int *statp;
+{
+ bool found;
+ char *dot;
+ SM_FILE_T *f;
+ char linebuf[MAXLINE];
+ char cbuf[MAXNAME + 1];
+ char nbuf[MAXNAME + 1];
+
+ if (tTd(38, 20))
+ sm_dprintf("text_getcanonname(%s)\n", name);
+
+ if (sm_strlcpy(nbuf, name, sizeof nbuf) >= sizeof nbuf)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ dot = shorten_hostname(nbuf);
+
+ f = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, HostsFile, SM_IO_RDONLY,
+ NULL);
+ if (f == NULL)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ found = false;
+ while (!found &&
+ sm_io_fgets(f, SM_TIME_DEFAULT,
+ linebuf, sizeof linebuf) != NULL)
+ {
+ char *p = strpbrk(linebuf, "#\n");
+
+ if (p != NULL)
+ *p = '\0';
+ if (linebuf[0] != '\0')
+ found = extract_canonname(nbuf, dot, linebuf,
+ cbuf, sizeof cbuf);
+ }
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ if (!found)
+ {
+ *statp = EX_NOHOST;
+ return false;
+ }
+
+ if (sm_strlcpy(name, cbuf, hbsize) >= hbsize)
+ {
+ *statp = EX_UNAVAILABLE;
+ return false;
+ }
+ *statp = EX_OK;
+ return true;
+}
+/*
+** STAB (Symbol Table) Modules
+*/
+
+
+/*
+** STAB_MAP_LOOKUP -- look up alias in symbol table
+*/
+
+/* ARGSUSED2 */
+char *
+stab_map_lookup(map, name, av, pstat)
+ register MAP *map;
+ char *name;
+ char **av;
+ int *pstat;
+{
+ register STAB *s;
+
+ if (tTd(38, 20))
+ sm_dprintf("stab_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+ s = stab(name, ST_ALIAS, ST_FIND);
+ if (s != NULL)
+ return s->s_alias;
+ return NULL;
+}
+
+
+/*
+** STAB_MAP_STORE -- store in symtab (actually using during init, not rebuild)
+*/
+
+void
+stab_map_store(map, lhs, rhs)
+ register MAP *map;
+ char *lhs;
+ char *rhs;
+{
+ register STAB *s;
+
+ s = stab(lhs, ST_ALIAS, ST_ENTER);
+ s->s_alias = newstr(rhs);
+}
+
+
+/*
+** STAB_MAP_OPEN -- initialize (reads data file)
+**
+** This is a wierd case -- it is only intended as a fallback for
+** aliases. For this reason, opens for write (only during a
+** "newaliases") always fails, and opens for read open the
+** actual underlying text file instead of the database.
+*/
+
+bool
+stab_map_open(map, mode)
+ register MAP *map;
+ int mode;
+{
+ SM_FILE_T *af;
+ long sff;
+ struct stat st;
+
+ if (tTd(38, 2))
+ sm_dprintf("stab_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ errno = EPERM;
+ return false;
+ }
+
+ sff = SFF_ROOTOK|SFF_REGONLY;
+ if (!bitnset(DBS_LINKEDMAPINWRITABLEDIR, DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ if (!bitnset(DBS_MAPINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ af = safefopen(map->map_file, O_RDONLY, 0444, sff);
+ if (af == NULL)
+ return false;
+ readaliases(map, af, false, false);
+
+ if (fstat(sm_io_getinfo(af, SM_IO_WHAT_FD, NULL), &st) >= 0)
+ map->map_mtime = st.st_mtime;
+ (void) sm_io_close(af, SM_TIME_DEFAULT);
+
+ return true;
+}
+/*
+** Implicit Modules
+**
+** Tries several types. For back compatibility of aliases.
+*/
+
+
+/*
+** IMPL_MAP_LOOKUP -- lookup in best open database
+*/
+
+char *
+impl_map_lookup(map, name, av, pstat)
+ MAP *map;
+ char *name;
+ char **av;
+ int *pstat;
+{
+ if (tTd(38, 20))
+ sm_dprintf("impl_map_lookup(%s, %s)\n",
+ map->map_mname, name);
+
+#if NEWDB
+ if (bitset(MF_IMPL_HASH, map->map_mflags))
+ return db_map_lookup(map, name, av, pstat);
+#endif /* NEWDB */
+#if NDBM
+ if (bitset(MF_IMPL_NDBM, map->map_mflags))
+ return ndbm_map_lookup(map, name, av, pstat);
+#endif /* NDBM */
+ return stab_map_lookup(map, name, av, pstat);
+}
+
+/*
+** IMPL_MAP_STORE -- store in open databases
+*/
+
+void
+impl_map_store(map, lhs, rhs)
+ MAP *map;
+ char *lhs;
+ char *rhs;
+{
+ if (tTd(38, 12))
+ sm_dprintf("impl_map_store(%s, %s, %s)\n",
+ map->map_mname, lhs, rhs);
+#if NEWDB
+ if (bitset(MF_IMPL_HASH, map->map_mflags))
+ db_map_store(map, lhs, rhs);
+#endif /* NEWDB */
+#if NDBM
+ if (bitset(MF_IMPL_NDBM, map->map_mflags))
+ ndbm_map_store(map, lhs, rhs);
+#endif /* NDBM */
+ stab_map_store(map, lhs, rhs);
+}
+
+/*
+** IMPL_MAP_OPEN -- implicit database open
+*/
+
+bool
+impl_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ if (tTd(38, 2))
+ sm_dprintf("impl_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+#if NEWDB
+ map->map_mflags |= MF_IMPL_HASH;
+ if (hash_map_open(map, mode))
+ {
+# ifdef NDBM_YP_COMPAT
+ if (mode == O_RDONLY || strstr(map->map_file, "/yp/") == NULL)
+# endif /* NDBM_YP_COMPAT */
+ return true;
+ }
+ else
+ map->map_mflags &= ~MF_IMPL_HASH;
+#endif /* NEWDB */
+#if NDBM
+ map->map_mflags |= MF_IMPL_NDBM;
+ if (ndbm_map_open(map, mode))
+ {
+ return true;
+ }
+ else
+ map->map_mflags &= ~MF_IMPL_NDBM;
+#endif /* NDBM */
+
+#if defined(NEWDB) || defined(NDBM)
+ if (Verbose)
+ message("WARNING: cannot open alias database %s%s",
+ map->map_file,
+ mode == O_RDONLY ? "; reading text version" : "");
+#else /* defined(NEWDB) || defined(NDBM) */
+ if (mode != O_RDONLY)
+ usrerr("Cannot rebuild aliases: no database format defined");
+#endif /* defined(NEWDB) || defined(NDBM) */
+
+ if (mode == O_RDONLY)
+ return stab_map_open(map, mode);
+ else
+ return false;
+}
+
+
+/*
+** IMPL_MAP_CLOSE -- close any open database(s)
+*/
+
+void
+impl_map_close(map)
+ MAP *map;
+{
+ if (tTd(38, 9))
+ sm_dprintf("impl_map_close(%s, %s, %lx)\n",
+ map->map_mname, map->map_file, map->map_mflags);
+#if NEWDB
+ if (bitset(MF_IMPL_HASH, map->map_mflags))
+ {
+ db_map_close(map);
+ map->map_mflags &= ~MF_IMPL_HASH;
+ }
+#endif /* NEWDB */
+
+#if NDBM
+ if (bitset(MF_IMPL_NDBM, map->map_mflags))
+ {
+ ndbm_map_close(map);
+ map->map_mflags &= ~MF_IMPL_NDBM;
+ }
+#endif /* NDBM */
+}
+/*
+** User map class.
+**
+** Provides access to the system password file.
+*/
+
+/*
+** USER_MAP_OPEN -- open user map
+**
+** Really just binds field names to field numbers.
+*/
+
+bool
+user_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ if (tTd(38, 2))
+ sm_dprintf("user_map_open(%s, %d)\n",
+ map->map_mname, mode);
+
+ mode &= O_ACCMODE;
+ if (mode != O_RDONLY)
+ {
+ /* issue a pseudo-error message */
+ errno = SM_EMAPCANTWRITE;
+ return false;
+ }
+ if (map->map_valcolnm == NULL)
+ /* EMPTY */
+ /* nothing */ ;
+ else if (sm_strcasecmp(map->map_valcolnm, "name") == 0)
+ map->map_valcolno = 1;
+ else if (sm_strcasecmp(map->map_valcolnm, "passwd") == 0)
+ map->map_valcolno = 2;
+ else if (sm_strcasecmp(map->map_valcolnm, "uid") == 0)
+ map->map_valcolno = 3;
+ else if (sm_strcasecmp(map->map_valcolnm, "gid") == 0)
+ map->map_valcolno = 4;
+ else if (sm_strcasecmp(map->map_valcolnm, "gecos") == 0)
+ map->map_valcolno = 5;
+ else if (sm_strcasecmp(map->map_valcolnm, "dir") == 0)
+ map->map_valcolno = 6;
+ else if (sm_strcasecmp(map->map_valcolnm, "shell") == 0)
+ map->map_valcolno = 7;
+ else
+ {
+ syserr("User map %s: unknown column name %s",
+ map->map_mname, map->map_valcolnm);
+ return false;
+ }
+ return true;
+}
+
+
+/*
+** USER_MAP_LOOKUP -- look up a user in the passwd file.
+*/
+
+/* ARGSUSED3 */
+char *
+user_map_lookup(map, key, av, statp)
+ MAP *map;
+ char *key;
+ char **av;
+ int *statp;
+{
+ auto bool fuzzy;
+ SM_MBDB_T user;
+
+ if (tTd(38, 20))
+ sm_dprintf("user_map_lookup(%s, %s)\n",
+ map->map_mname, key);
+
+ *statp = finduser(key, &fuzzy, &user);
+ if (*statp != EX_OK)
+ return NULL;
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, key, strlen(key), NULL);
+ else
+ {
+ char *rwval = NULL;
+ char buf[30];
+
+ switch (map->map_valcolno)
+ {
+ case 0:
+ case 1:
+ rwval = user.mbdb_name;
+ break;
+
+ case 2:
+ rwval = "x"; /* passwd no longer supported */
+ break;
+
+ case 3:
+ (void) sm_snprintf(buf, sizeof buf, "%d",
+ (int) user.mbdb_uid);
+ rwval = buf;
+ break;
+
+ case 4:
+ (void) sm_snprintf(buf, sizeof buf, "%d",
+ (int) user.mbdb_gid);
+ rwval = buf;
+ break;
+
+ case 5:
+ rwval = user.mbdb_fullname;
+ break;
+
+ case 6:
+ rwval = user.mbdb_homedir;
+ break;
+
+ case 7:
+ rwval = user.mbdb_shell;
+ break;
+ }
+ return map_rewrite(map, rwval, strlen(rwval), av);
+ }
+}
+/*
+** Program map type.
+**
+** This provides access to arbitrary programs. It should be used
+** only very sparingly, since there is no way to bound the cost
+** of invoking an arbitrary program.
+*/
+
+char *
+prog_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int i;
+ int save_errno;
+ int fd;
+ int status;
+ auto pid_t pid;
+ register char *p;
+ char *rval;
+ char *argv[MAXPV + 1];
+ char buf[MAXLINE];
+
+ if (tTd(38, 20))
+ sm_dprintf("prog_map_lookup(%s, %s) %s\n",
+ map->map_mname, name, map->map_file);
+
+ i = 0;
+ argv[i++] = map->map_file;
+ if (map->map_rebuild != NULL)
+ {
+ (void) sm_strlcpy(buf, map->map_rebuild, sizeof buf);
+ for (p = strtok(buf, " \t"); p != NULL; p = strtok(NULL, " \t"))
+ {
+ if (i >= MAXPV - 1)
+ break;
+ argv[i++] = p;
+ }
+ }
+ argv[i++] = name;
+ argv[i] = NULL;
+ if (tTd(38, 21))
+ {
+ sm_dprintf("prog_open:");
+ for (i = 0; argv[i] != NULL; i++)
+ sm_dprintf(" %s", argv[i]);
+ sm_dprintf("\n");
+ }
+ (void) sm_blocksignal(SIGCHLD);
+ pid = prog_open(argv, &fd, CurEnv);
+ if (pid < 0)
+ {
+ if (!bitset(MF_OPTIONAL, map->map_mflags))
+ syserr("prog_map_lookup(%s) failed (%s) -- closing",
+ map->map_mname, sm_errstring(errno));
+ else if (tTd(38, 9))
+ sm_dprintf("prog_map_lookup(%s) failed (%s) -- closing",
+ map->map_mname, sm_errstring(errno));
+ map->map_mflags &= ~(MF_VALID|MF_OPEN);
+ *statp = EX_OSFILE;
+ return NULL;
+ }
+ i = read(fd, buf, sizeof buf - 1);
+ if (i < 0)
+ {
+ syserr("prog_map_lookup(%s): read error %s",
+ map->map_mname, sm_errstring(errno));
+ rval = NULL;
+ }
+ else if (i == 0)
+ {
+ if (tTd(38, 20))
+ sm_dprintf("prog_map_lookup(%s): empty answer\n",
+ map->map_mname);
+ rval = NULL;
+ }
+ else
+ {
+ buf[i] = '\0';
+ p = strchr(buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+
+ /* collect the return value */
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ rval = map_rewrite(map, name, strlen(name), NULL);
+ else
+ rval = map_rewrite(map, buf, strlen(buf), av);
+
+ /* now flush any additional output */
+ while ((i = read(fd, buf, sizeof buf)) > 0)
+ continue;
+ }
+
+ /* wait for the process to terminate */
+ (void) close(fd);
+ status = waitfor(pid);
+ save_errno = errno;
+ (void) sm_releasesignal(SIGCHLD);
+ errno = save_errno;
+
+ if (status == -1)
+ {
+ syserr("prog_map_lookup(%s): wait error %s",
+ map->map_mname, sm_errstring(errno));
+ *statp = EX_SOFTWARE;
+ rval = NULL;
+ }
+ else if (WIFEXITED(status))
+ {
+ if ((*statp = WEXITSTATUS(status)) != EX_OK)
+ rval = NULL;
+ }
+ else
+ {
+ syserr("prog_map_lookup(%s): child died on signal %d",
+ map->map_mname, status);
+ *statp = EX_UNAVAILABLE;
+ rval = NULL;
+ }
+ return rval;
+}
+/*
+** Sequenced map type.
+**
+** Tries each map in order until something matches, much like
+** implicit. Stores go to the first map in the list that can
+** support storing.
+**
+** This is slightly unusual in that there are two interfaces.
+** The "sequence" interface lets you stack maps arbitrarily.
+** The "switch" interface builds a sequence map by looking
+** at a system-dependent configuration file such as
+** /etc/nsswitch.conf on Solaris or /etc/svc.conf on Ultrix.
+**
+** We don't need an explicit open, since all maps are
+** opened on demand.
+*/
+
+/*
+** SEQ_MAP_PARSE -- Sequenced map parsing
+*/
+
+bool
+seq_map_parse(map, ap)
+ MAP *map;
+ char *ap;
+{
+ int maxmap;
+
+ if (tTd(38, 2))
+ sm_dprintf("seq_map_parse(%s, %s)\n", map->map_mname, ap);
+ maxmap = 0;
+ while (*ap != '\0')
+ {
+ register char *p;
+ STAB *s;
+
+ /* find beginning of map name */
+ while (isascii(*ap) && isspace(*ap))
+ ap++;
+ for (p = ap;
+ (isascii(*p) && isalnum(*p)) || *p == '_' || *p == '.';
+ p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ while (*p != '\0' && (!isascii(*p) || !isalnum(*p)))
+ p++;
+ if (*ap == '\0')
+ {
+ ap = p;
+ continue;
+ }
+ s = stab(ap, ST_MAP, ST_FIND);
+ if (s == NULL)
+ {
+ syserr("Sequence map %s: unknown member map %s",
+ map->map_mname, ap);
+ }
+ else if (maxmap >= MAXMAPSTACK)
+ {
+ syserr("Sequence map %s: too many member maps (%d max)",
+ map->map_mname, MAXMAPSTACK);
+ maxmap++;
+ }
+ else if (maxmap < MAXMAPSTACK)
+ {
+ map->map_stack[maxmap++] = &s->s_map;
+ }
+ ap = p;
+ }
+ return true;
+}
+
+/*
+** SWITCH_MAP_OPEN -- open a switched map
+**
+** This looks at the system-dependent configuration and builds
+** a sequence map that does the same thing.
+**
+** Every system must define a switch_map_find routine in conf.c
+** that will return the list of service types associated with a
+** given service class.
+*/
+
+bool
+switch_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ int mapno;
+ int nmaps;
+ char *maptype[MAXMAPSTACK];
+
+ if (tTd(38, 2))
+ sm_dprintf("switch_map_open(%s, %s, %d)\n",
+ map->map_mname, map->map_file, mode);
+
+ mode &= O_ACCMODE;
+ nmaps = switch_map_find(map->map_file, maptype, map->map_return);
+ if (tTd(38, 19))
+ {
+ sm_dprintf("\tswitch_map_find => %d\n", nmaps);
+ for (mapno = 0; mapno < nmaps; mapno++)
+ sm_dprintf("\t\t%s\n", maptype[mapno]);
+ }
+ if (nmaps <= 0 || nmaps > MAXMAPSTACK)
+ return false;
+
+ for (mapno = 0; mapno < nmaps; mapno++)
+ {
+ register STAB *s;
+ char nbuf[MAXNAME + 1];
+
+ if (maptype[mapno] == NULL)
+ continue;
+ (void) sm_strlcpyn(nbuf, sizeof nbuf, 3,
+ map->map_mname, ".", maptype[mapno]);
+ s = stab(nbuf, ST_MAP, ST_FIND);
+ if (s == NULL)
+ {
+ syserr("Switch map %s: unknown member map %s",
+ map->map_mname, nbuf);
+ }
+ else
+ {
+ map->map_stack[mapno] = &s->s_map;
+ if (tTd(38, 4))
+ sm_dprintf("\tmap_stack[%d] = %s:%s\n",
+ mapno,
+ s->s_map.map_class->map_cname,
+ nbuf);
+ }
+ }
+ return true;
+}
+
+#if 0
+/*
+** SEQ_MAP_CLOSE -- close all underlying maps
+*/
+
+void
+seq_map_close(map)
+ MAP *map;
+{
+ int mapno;
+
+ if (tTd(38, 9))
+ sm_dprintf("seq_map_close(%s)\n", map->map_mname);
+
+ for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
+ {
+ MAP *mm = map->map_stack[mapno];
+
+ if (mm == NULL || !bitset(MF_OPEN, mm->map_mflags))
+ continue;
+ mm->map_mflags |= MF_CLOSING;
+ mm->map_class->map_close(mm);
+ mm->map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ }
+}
+#endif /* 0 */
+
+/*
+** SEQ_MAP_LOOKUP -- sequenced map lookup
+*/
+
+char *
+seq_map_lookup(map, key, args, pstat)
+ MAP *map;
+ char *key;
+ char **args;
+ int *pstat;
+{
+ int mapno;
+ int mapbit = 0x01;
+ bool tempfail = false;
+
+ if (tTd(38, 20))
+ sm_dprintf("seq_map_lookup(%s, %s)\n", map->map_mname, key);
+
+ for (mapno = 0; mapno < MAXMAPSTACK; mapbit <<= 1, mapno++)
+ {
+ MAP *mm = map->map_stack[mapno];
+ char *rv;
+
+ if (mm == NULL)
+ continue;
+ if (!bitset(MF_OPEN, mm->map_mflags) &&
+ !openmap(mm))
+ {
+ if (bitset(mapbit, map->map_return[MA_UNAVAIL]))
+ {
+ *pstat = EX_UNAVAILABLE;
+ return NULL;
+ }
+ continue;
+ }
+ *pstat = EX_OK;
+ rv = mm->map_class->map_lookup(mm, key, args, pstat);
+ if (rv != NULL)
+ return rv;
+ if (*pstat == EX_TEMPFAIL)
+ {
+ if (bitset(mapbit, map->map_return[MA_TRYAGAIN]))
+ return NULL;
+ tempfail = true;
+ }
+ else if (bitset(mapbit, map->map_return[MA_NOTFOUND]))
+ break;
+ }
+ if (tempfail)
+ *pstat = EX_TEMPFAIL;
+ else if (*pstat == EX_OK)
+ *pstat = EX_NOTFOUND;
+ return NULL;
+}
+
+/*
+** SEQ_MAP_STORE -- sequenced map store
+*/
+
+void
+seq_map_store(map, key, val)
+ MAP *map;
+ char *key;
+ char *val;
+{
+ int mapno;
+
+ if (tTd(38, 12))
+ sm_dprintf("seq_map_store(%s, %s, %s)\n",
+ map->map_mname, key, val);
+
+ for (mapno = 0; mapno < MAXMAPSTACK; mapno++)
+ {
+ MAP *mm = map->map_stack[mapno];
+
+ if (mm == NULL || !bitset(MF_WRITABLE, mm->map_mflags))
+ continue;
+
+ mm->map_class->map_store(mm, key, val);
+ return;
+ }
+ syserr("seq_map_store(%s, %s, %s): no writable map",
+ map->map_mname, key, val);
+}
+/*
+** NULL stubs
+*/
+
+/* ARGSUSED */
+bool
+null_map_open(map, mode)
+ MAP *map;
+ int mode;
+{
+ return true;
+}
+
+/* ARGSUSED */
+void
+null_map_close(map)
+ MAP *map;
+{
+ return;
+}
+
+char *
+null_map_lookup(map, key, args, pstat)
+ MAP *map;
+ char *key;
+ char **args;
+ int *pstat;
+{
+ *pstat = EX_NOTFOUND;
+ return NULL;
+}
+
+/* ARGSUSED */
+void
+null_map_store(map, key, val)
+ MAP *map;
+ char *key;
+ char *val;
+{
+ return;
+}
+
+/*
+** BOGUS stubs
+*/
+
+char *
+bogus_map_lookup(map, key, args, pstat)
+ MAP *map;
+ char *key;
+ char **args;
+ int *pstat;
+{
+ *pstat = EX_TEMPFAIL;
+ return NULL;
+}
+
+MAPCLASS BogusMapClass =
+{
+ "bogus-map", NULL, 0,
+ NULL, bogus_map_lookup, null_map_store,
+ null_map_open, null_map_close,
+};
+/*
+** MACRO modules
+*/
+
+char *
+macro_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int mid;
+
+ if (tTd(38, 20))
+ sm_dprintf("macro_map_lookup(%s, %s)\n", map->map_mname,
+ name == NULL ? "NULL" : name);
+
+ if (name == NULL ||
+ *name == '\0' ||
+ (mid = macid(name)) == 0)
+ {
+ *statp = EX_CONFIG;
+ return NULL;
+ }
+
+ if (av[1] == NULL)
+ macdefine(&CurEnv->e_macro, A_PERM, mid, NULL);
+ else
+ macdefine(&CurEnv->e_macro, A_TEMP, mid, av[1]);
+
+ *statp = EX_OK;
+ return "";
+}
+/*
+** REGEX modules
+*/
+
+#if MAP_REGEX
+
+# include <regex.h>
+
+# define DEFAULT_DELIM CONDELSE
+# define END_OF_FIELDS -1
+# define ERRBUF_SIZE 80
+# define MAX_MATCH 32
+
+# define xnalloc(s) memset(xalloc(s), '\0', s);
+
+struct regex_map
+{
+ regex_t *regex_pattern_buf; /* xalloc it */
+ int *regex_subfields; /* move to type MAP */
+ char *regex_delim; /* move to type MAP */
+};
+
+static int
+parse_fields(s, ibuf, blen, nr_substrings)
+ char *s;
+ int *ibuf; /* array */
+ int blen; /* number of elements in ibuf */
+ int nr_substrings; /* number of substrings in the pattern */
+{
+ register char *cp;
+ int i = 0;
+ bool lastone = false;
+
+ blen--; /* for terminating END_OF_FIELDS */
+ cp = s;
+ do
+ {
+ for (;; cp++)
+ {
+ if (*cp == ',')
+ {
+ *cp = '\0';
+ break;
+ }
+ if (*cp == '\0')
+ {
+ lastone = true;
+ break;
+ }
+ }
+ if (i < blen)
+ {
+ int val = atoi(s);
+
+ if (val < 0 || val >= nr_substrings)
+ {
+ syserr("field (%d) out of range, only %d substrings in pattern",
+ val, nr_substrings);
+ return -1;
+ }
+ ibuf[i++] = val;
+ }
+ else
+ {
+ syserr("too many fields, %d max", blen);
+ return -1;
+ }
+ s = ++cp;
+ } while (!lastone);
+ ibuf[i] = END_OF_FIELDS;
+ return i;
+}
+
+bool
+regex_map_init(map, ap)
+ MAP *map;
+ char *ap;
+{
+ int regerr;
+ struct regex_map *map_p;
+ register char *p;
+ char *sub_param = NULL;
+ int pflags;
+ static char defdstr[] = { (char) DEFAULT_DELIM, '\0' };
+
+ if (tTd(38, 2))
+ sm_dprintf("regex_map_init: mapname '%s', args '%s'\n",
+ map->map_mname, ap);
+
+ pflags = REG_ICASE | REG_EXTENDED | REG_NOSUB;
+ p = ap;
+ map_p = (struct regex_map *) xnalloc(sizeof *map_p);
+ map_p->regex_pattern_buf = (regex_t *)xnalloc(sizeof(regex_t));
+
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'n': /* not */
+ map->map_mflags |= MF_REGEX_NOT;
+ break;
+
+ case 'f': /* case sensitive */
+ map->map_mflags |= MF_NOFOLDCASE;
+ pflags &= ~REG_ICASE;
+ break;
+
+ case 'b': /* basic regular expressions */
+ pflags &= ~REG_EXTENDED;
+ break;
+
+ case 's': /* substring match () syntax */
+ sub_param = ++p;
+ pflags &= ~REG_NOSUB;
+ break;
+
+ case 'd': /* delimiter */
+ map_p->regex_delim = ++p;
+ break;
+
+ case 'a': /* map append */
+ map->map_app = ++p;
+ break;
+
+ case 'm': /* matchonly */
+ map->map_mflags |= MF_MATCHONLY;
+ break;
+
+ case 'S':
+ map->map_spacesub = *++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ }
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ }
+ if (tTd(38, 3))
+ sm_dprintf("regex_map_init: compile '%s' 0x%x\n", p, pflags);
+
+ if ((regerr = regcomp(map_p->regex_pattern_buf, p, pflags)) != 0)
+ {
+ /* Errorhandling */
+ char errbuf[ERRBUF_SIZE];
+
+ (void) regerror(regerr, map_p->regex_pattern_buf,
+ errbuf, sizeof errbuf);
+ syserr("pattern-compile-error: %s", errbuf);
+ sm_free(map_p->regex_pattern_buf); /* XXX */
+ sm_free(map_p); /* XXX */
+ return false;
+ }
+
+ if (map->map_app != NULL)
+ map->map_app = newstr(map->map_app);
+ if (map_p->regex_delim != NULL)
+ map_p->regex_delim = newstr(map_p->regex_delim);
+ else
+ map_p->regex_delim = defdstr;
+
+ if (!bitset(REG_NOSUB, pflags))
+ {
+ /* substring matching */
+ int substrings;
+ int *fields = (int *) xalloc(sizeof(int) * (MAX_MATCH + 1));
+
+ substrings = map_p->regex_pattern_buf->re_nsub + 1;
+
+ if (tTd(38, 3))
+ sm_dprintf("regex_map_init: nr of substrings %d\n",
+ substrings);
+
+ if (substrings >= MAX_MATCH)
+ {
+ syserr("too many substrings, %d max", MAX_MATCH);
+ sm_free(map_p->regex_pattern_buf); /* XXX */
+ sm_free(map_p); /* XXX */
+ return false;
+ }
+ if (sub_param != NULL && sub_param[0] != '\0')
+ {
+ /* optional parameter -sfields */
+ if (parse_fields(sub_param, fields,
+ MAX_MATCH + 1, substrings) == -1)
+ return false;
+ }
+ else
+ {
+ int i;
+
+ /* set default fields */
+ for (i = 0; i < substrings; i++)
+ fields[i] = i;
+ fields[i] = END_OF_FIELDS;
+ }
+ map_p->regex_subfields = fields;
+ if (tTd(38, 3))
+ {
+ int *ip;
+
+ sm_dprintf("regex_map_init: subfields");
+ for (ip = fields; *ip != END_OF_FIELDS; ip++)
+ sm_dprintf(" %d", *ip);
+ sm_dprintf("\n");
+ }
+ }
+ map->map_db1 = (ARBPTR_T) map_p; /* dirty hack */
+ return true;
+}
+
+static char *
+regex_map_rewrite(map, s, slen, av)
+ MAP *map;
+ const char *s;
+ size_t slen;
+ char **av;
+{
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ return map_rewrite(map, av[0], strlen(av[0]), NULL);
+ else
+ return map_rewrite(map, s, slen, av);
+}
+
+char *
+regex_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int reg_res;
+ struct regex_map *map_p;
+ regmatch_t pmatch[MAX_MATCH];
+
+ if (tTd(38, 20))
+ {
+ char **cpp;
+
+ sm_dprintf("regex_map_lookup: key '%s'\n", name);
+ for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
+ sm_dprintf("regex_map_lookup: arg '%s'\n", *cpp);
+ }
+
+ map_p = (struct regex_map *)(map->map_db1);
+ reg_res = regexec(map_p->regex_pattern_buf,
+ name, MAX_MATCH, pmatch, 0);
+
+ if (bitset(MF_REGEX_NOT, map->map_mflags))
+ {
+ /* option -n */
+ if (reg_res == REG_NOMATCH)
+ return regex_map_rewrite(map, "", (size_t) 0, av);
+ else
+ return NULL;
+ }
+ if (reg_res == REG_NOMATCH)
+ return NULL;
+
+ if (map_p->regex_subfields != NULL)
+ {
+ /* option -s */
+ static char retbuf[MAXNAME];
+ int fields[MAX_MATCH + 1];
+ bool first = true;
+ int anglecnt = 0, cmntcnt = 0, spacecnt = 0;
+ bool quotemode = false, bslashmode = false;
+ register char *dp, *sp;
+ char *endp, *ldp;
+ int *ip;
+
+ dp = retbuf;
+ ldp = retbuf + sizeof(retbuf) - 1;
+
+ if (av[1] != NULL)
+ {
+ if (parse_fields(av[1], fields, MAX_MATCH + 1,
+ (int) map_p->regex_pattern_buf->re_nsub + 1) == -1)
+ {
+ *statp = EX_CONFIG;
+ return NULL;
+ }
+ ip = fields;
+ }
+ else
+ ip = map_p->regex_subfields;
+
+ for ( ; *ip != END_OF_FIELDS; ip++)
+ {
+ if (!first)
+ {
+ for (sp = map_p->regex_delim; *sp; sp++)
+ {
+ if (dp < ldp)
+ *dp++ = *sp;
+ }
+ }
+ else
+ first = false;
+
+ if (*ip >= MAX_MATCH ||
+ pmatch[*ip].rm_so < 0 || pmatch[*ip].rm_eo < 0)
+ continue;
+
+ sp = name + pmatch[*ip].rm_so;
+ endp = name + pmatch[*ip].rm_eo;
+ for (; endp > sp; sp++)
+ {
+ if (dp < ldp)
+ {
+ if (bslashmode)
+ {
+ *dp++ = *sp;
+ bslashmode = false;
+ }
+ else if (quotemode && *sp != '"' &&
+ *sp != '\\')
+ {
+ *dp++ = *sp;
+ }
+ else switch (*dp++ = *sp)
+ {
+ case '\\':
+ bslashmode = true;
+ break;
+
+ case '(':
+ cmntcnt++;
+ break;
+
+ case ')':
+ cmntcnt--;
+ break;
+
+ case '<':
+ anglecnt++;
+ break;
+
+ case '>':
+ anglecnt--;
+ break;
+
+ case ' ':
+ spacecnt++;
+ break;
+
+ case '"':
+ quotemode = !quotemode;
+ break;
+ }
+ }
+ }
+ }
+ if (anglecnt != 0 || cmntcnt != 0 || quotemode ||
+ bslashmode || spacecnt != 0)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "Warning: regex may cause prescan() failure map=%s lookup=%s",
+ map->map_mname, name);
+ return NULL;
+ }
+
+ *dp = '\0';
+
+ return regex_map_rewrite(map, retbuf, strlen(retbuf), av);
+ }
+ return regex_map_rewrite(map, "", (size_t)0, av);
+}
+#endif /* MAP_REGEX */
+/*
+** NSD modules
+*/
+#if MAP_NSD
+
+# include <ndbm.h>
+# define _DATUM_DEFINED
+# include <ns_api.h>
+
+typedef struct ns_map_list
+{
+ ns_map_t *map; /* XXX ns_ ? */
+ char *mapname;
+ struct ns_map_list *next;
+} ns_map_list_t;
+
+static ns_map_t *
+ns_map_t_find(mapname)
+ char *mapname;
+{
+ static ns_map_list_t *ns_maps = NULL;
+ ns_map_list_t *ns_map;
+
+ /* walk the list of maps looking for the correctly named map */
+ for (ns_map = ns_maps; ns_map != NULL; ns_map = ns_map->next)
+ {
+ if (strcmp(ns_map->mapname, mapname) == 0)
+ break;
+ }
+
+ /* if we are looking at a NULL ns_map_list_t, then create a new one */
+ if (ns_map == NULL)
+ {
+ ns_map = (ns_map_list_t *) xalloc(sizeof *ns_map);
+ ns_map->mapname = newstr(mapname);
+ ns_map->map = (ns_map_t *) xalloc(sizeof *ns_map->map);
+ memset(ns_map->map, '\0', sizeof *ns_map->map);
+ ns_map->next = ns_maps;
+ ns_maps = ns_map;
+ }
+ return ns_map->map;
+}
+
+char *
+nsd_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ int buflen, r;
+ char *p;
+ ns_map_t *ns_map;
+ char keybuf[MAXNAME + 1];
+ char buf[MAXLINE];
+
+ if (tTd(38, 20))
+ sm_dprintf("nsd_map_lookup(%s, %s)\n", map->map_mname, name);
+
+ buflen = strlen(name);
+ if (buflen > sizeof keybuf - 1)
+ buflen = sizeof keybuf - 1; /* XXX simply cut off? */
+ memmove(keybuf, name, buflen);
+ keybuf[buflen] = '\0';
+ if (!bitset(MF_NOFOLDCASE, map->map_mflags))
+ makelower(keybuf);
+
+ ns_map = ns_map_t_find(map->map_file);
+ if (ns_map == NULL)
+ {
+ if (tTd(38, 20))
+ sm_dprintf("nsd_map_t_find failed\n");
+ *statp = EX_UNAVAILABLE;
+ return NULL;
+ }
+ r = ns_lookup(ns_map, NULL, map->map_file, keybuf, NULL,
+ buf, sizeof buf);
+ if (r == NS_UNAVAIL || r == NS_TRYAGAIN)
+ {
+ *statp = EX_TEMPFAIL;
+ return NULL;
+ }
+ if (r == NS_BADREQ
+# ifdef NS_NOPERM
+ || r == NS_NOPERM
+# endif /* NS_NOPERM */
+ )
+ {
+ *statp = EX_CONFIG;
+ return NULL;
+ }
+ if (r != NS_SUCCESS)
+ {
+ *statp = EX_NOTFOUND;
+ return NULL;
+ }
+
+ *statp = EX_OK;
+
+ /* Null out trailing \n */
+ if ((p = strchr(buf, '\n')) != NULL)
+ *p = '\0';
+
+ return map_rewrite(map, buf, strlen(buf), av);
+}
+#endif /* MAP_NSD */
+
+char *
+arith_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ long r;
+ long v[2];
+ bool res = false;
+ bool boolres;
+ static char result[16];
+ char **cpp;
+
+ if (tTd(38, 2))
+ {
+ sm_dprintf("arith_map_lookup: key '%s'\n", name);
+ for (cpp = av; cpp != NULL && *cpp != NULL; cpp++)
+ sm_dprintf("arith_map_lookup: arg '%s'\n", *cpp);
+ }
+ r = 0;
+ boolres = false;
+ cpp = av;
+ *statp = EX_OK;
+
+ /*
+ ** read arguments for arith map
+ ** - no check is made whether they are really numbers
+ ** - just ignores args after the second
+ */
+
+ for (++cpp; cpp != NULL && *cpp != NULL && r < 2; cpp++)
+ v[r++] = strtol(*cpp, NULL, 0);
+
+ /* operator and (at least) two operands given? */
+ if (name != NULL && r == 2)
+ {
+ switch (*name)
+ {
+ case '|':
+ r = v[0] | v[1];
+ break;
+
+ case '&':
+ r = v[0] & v[1];
+ break;
+
+ case '%':
+ if (v[1] == 0)
+ return NULL;
+ r = v[0] % v[1];
+ break;
+ case '+':
+ r = v[0] + v[1];
+ break;
+
+ case '-':
+ r = v[0] - v[1];
+ break;
+
+ case '*':
+ r = v[0] * v[1];
+ break;
+
+ case '/':
+ if (v[1] == 0)
+ return NULL;
+ r = v[0] / v[1];
+ break;
+
+ case 'l':
+ res = v[0] < v[1];
+ boolres = true;
+ break;
+
+ case '=':
+ res = v[0] == v[1];
+ boolres = true;
+ break;
+
+ default:
+ /* XXX */
+ *statp = EX_CONFIG;
+ if (LogLevel > 10)
+ sm_syslog(LOG_WARNING, NOQID,
+ "arith_map: unknown operator %c",
+ isprint(*name) ? *name : '?');
+ return NULL;
+ }
+ if (boolres)
+ (void) sm_snprintf(result, sizeof result,
+ res ? "TRUE" : "FALSE");
+ else
+ (void) sm_snprintf(result, sizeof result, "%ld", r);
+ return result;
+ }
+ *statp = EX_CONFIG;
+ return NULL;
+}
diff --git a/contrib/sendmail/src/mci.c b/contrib/sendmail/src/mci.c
new file mode 100644
index 0000000..7d1a4de
--- /dev/null
+++ b/contrib/sendmail/src/mci.c
@@ -0,0 +1,1489 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: mci.c,v 8.205 2002/05/24 18:53:48 gshapiro Exp $")
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+#include <dirent.h>
+
+static int mci_generate_persistent_path __P((const char *, char *,
+ int, bool));
+static bool mci_load_persistent __P((MCI *));
+static void mci_uncache __P((MCI **, bool));
+static int mci_lock_host_statfile __P((MCI *));
+static int mci_read_persistent __P((SM_FILE_T *, MCI *));
+
+/*
+** Mail Connection Information (MCI) Caching Module.
+**
+** There are actually two separate things cached. The first is
+** the set of all open connections -- these are stored in a
+** (small) list. The second is stored in the symbol table; it
+** has the overall status for all hosts, whether or not there
+** is a connection open currently.
+**
+** There should never be too many connections open (since this
+** could flood the socket table), nor should a connection be
+** allowed to sit idly for too long.
+**
+** MaxMciCache is the maximum number of open connections that
+** will be supported.
+**
+** MciCacheTimeout is the time (in seconds) that a connection
+** is permitted to survive without activity.
+**
+** We actually try any cached connections by sending a NOOP
+** before we use them; if the NOOP fails we close down the
+** connection and reopen it. Note that this means that a
+** server SMTP that doesn't support NOOP will hose the
+** algorithm -- but that doesn't seem too likely.
+**
+** The persistent MCI code is donated by Mark Lovell and Paul
+** Vixie. It is based on the long term host status code in KJS
+** written by Paul but has been adapted by Mark to fit into the
+** MCI structure.
+*/
+
+static MCI **MciCache; /* the open connection cache */
+
+/*
+** MCI_CACHE -- enter a connection structure into the open connection cache
+**
+** This may cause something else to be flushed.
+**
+** Parameters:
+** mci -- the connection to cache.
+**
+** Returns:
+** none.
+*/
+
+void
+mci_cache(mci)
+ register MCI *mci;
+{
+ register MCI **mcislot;
+
+ /*
+ ** Find the best slot. This may cause expired connections
+ ** to be closed.
+ */
+
+ mcislot = mci_scan(mci);
+ if (mcislot == NULL)
+ {
+ /* we don't support caching */
+ return;
+ }
+
+ if (mci->mci_host == NULL)
+ return;
+
+ /* if this is already cached, we are done */
+ if (bitset(MCIF_CACHED, mci->mci_flags))
+ return;
+
+ /* otherwise we may have to clear the slot */
+ if (*mcislot != NULL)
+ mci_uncache(mcislot, true);
+
+ if (tTd(42, 5))
+ sm_dprintf("mci_cache: caching %p (%s) in slot %d\n",
+ mci, mci->mci_host, (int) (mcislot - MciCache));
+ if (tTd(91, 100))
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "mci_cache: caching %lx (%.100s) in slot %d",
+ (unsigned long) mci, mci->mci_host,
+ (int) (mcislot - MciCache));
+
+ *mcislot = mci;
+ mci->mci_flags |= MCIF_CACHED;
+}
+/*
+** MCI_SCAN -- scan the cache, flush junk, and return best slot
+**
+** Parameters:
+** savemci -- never flush this one. Can be null.
+**
+** Returns:
+** The LRU (or empty) slot.
+*/
+
+MCI **
+mci_scan(savemci)
+ MCI *savemci;
+{
+ time_t now;
+ register MCI **bestmci;
+ register MCI *mci;
+ register int i;
+
+ if (MaxMciCache <= 0)
+ {
+ /* we don't support caching */
+ return NULL;
+ }
+
+ if (MciCache == NULL)
+ {
+ /* first call */
+ MciCache = (MCI **) sm_pmalloc_x(MaxMciCache * sizeof *MciCache);
+ memset((char *) MciCache, '\0', MaxMciCache * sizeof *MciCache);
+ return &MciCache[0];
+ }
+
+ now = curtime();
+ bestmci = &MciCache[0];
+ for (i = 0; i < MaxMciCache; i++)
+ {
+ mci = MciCache[i];
+ if (mci == NULL || mci->mci_state == MCIS_CLOSED)
+ {
+ bestmci = &MciCache[i];
+ continue;
+ }
+ if ((mci->mci_lastuse + MciCacheTimeout <= now ||
+ (mci->mci_mailer != NULL &&
+ mci->mci_mailer->m_maxdeliveries > 0 &&
+ mci->mci_deliveries + 1 >= mci->mci_mailer->m_maxdeliveries))&&
+ mci != savemci)
+ {
+ /* connection idle too long or too many deliveries */
+ bestmci = &MciCache[i];
+
+ /* close it */
+ mci_uncache(bestmci, true);
+ continue;
+ }
+ if (*bestmci == NULL)
+ continue;
+ if (mci->mci_lastuse < (*bestmci)->mci_lastuse)
+ bestmci = &MciCache[i];
+ }
+ return bestmci;
+}
+/*
+** MCI_UNCACHE -- remove a connection from a slot.
+**
+** May close a connection.
+**
+** Parameters:
+** mcislot -- the slot to empty.
+** doquit -- if true, send QUIT protocol on this connection.
+** if false, we are assumed to be in a forked child;
+** all we want to do is close the file(s).
+**
+** Returns:
+** none.
+*/
+
+static void
+mci_uncache(mcislot, doquit)
+ register MCI **mcislot;
+ bool doquit;
+{
+ register MCI *mci;
+ extern ENVELOPE BlankEnvelope;
+
+ mci = *mcislot;
+ if (mci == NULL)
+ return;
+ *mcislot = NULL;
+ if (mci->mci_host == NULL)
+ return;
+
+ mci_unlock_host(mci);
+
+ if (tTd(42, 5))
+ sm_dprintf("mci_uncache: uncaching %p (%s) from slot %d (%d)\n",
+ mci, mci->mci_host, (int) (mcislot - MciCache),
+ doquit);
+ if (tTd(91, 100))
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "mci_uncache: uncaching %lx (%.100s) from slot %d (%d)",
+ (unsigned long) mci, mci->mci_host,
+ (int) (mcislot - MciCache), doquit);
+
+ mci->mci_deliveries = 0;
+ if (doquit)
+ {
+ message("Closing connection to %s", mci->mci_host);
+
+ mci->mci_flags &= ~MCIF_CACHED;
+
+ /* only uses the envelope to flush the transcript file */
+ if (mci->mci_state != MCIS_CLOSED)
+ smtpquit(mci->mci_mailer, mci, &BlankEnvelope);
+#if XLA
+ xla_host_end(mci->mci_host);
+#endif /* XLA */
+ }
+ else
+ {
+ if (mci->mci_in != NULL)
+ (void) sm_io_close(mci->mci_in, SM_TIME_DEFAULT);
+ if (mci->mci_out != NULL)
+ (void) sm_io_close(mci->mci_out, SM_TIME_DEFAULT);
+ mci->mci_in = mci->mci_out = NULL;
+ mci->mci_state = MCIS_CLOSED;
+ mci->mci_exitstat = EX_OK;
+ mci->mci_errno = 0;
+ mci->mci_flags = 0;
+
+ mci->mci_retryrcpt = false;
+ mci->mci_tolist = NULL;
+#if PIPELINING
+ mci->mci_okrcpts = 0;
+#endif /* PIPELINING */
+ }
+
+ SM_FREE_CLR(mci->mci_status);
+ SM_FREE_CLR(mci->mci_rstatus);
+ SM_FREE_CLR(mci->mci_heloname);
+ if (mci->mci_rpool != NULL)
+ {
+ sm_rpool_free(mci->mci_rpool);
+ mci->mci_macro.mac_rpool = NULL;
+ mci->mci_rpool = NULL;
+ }
+}
+/*
+** MCI_FLUSH -- flush the entire cache
+**
+** Parameters:
+** doquit -- if true, send QUIT protocol.
+** if false, just close the connection.
+** allbut -- but leave this one open.
+**
+** Returns:
+** none.
+*/
+
+void
+mci_flush(doquit, allbut)
+ bool doquit;
+ MCI *allbut;
+{
+ register int i;
+
+ if (MciCache == NULL)
+ return;
+
+ for (i = 0; i < MaxMciCache; i++)
+ {
+ if (allbut != MciCache[i])
+ mci_uncache(&MciCache[i], doquit);
+ }
+}
+/*
+** MCI_GET -- get information about a particular host
+**
+** Parameters:
+** host -- host to look for.
+** m -- mailer.
+**
+** Returns:
+** mci for this host (might be new).
+*/
+
+MCI *
+mci_get(host, m)
+ char *host;
+ MAILER *m;
+{
+ register MCI *mci;
+ register STAB *s;
+ extern SOCKADDR CurHostAddr;
+
+ /* clear CurHostAddr so we don't get a bogus address with this name */
+ memset(&CurHostAddr, '\0', sizeof CurHostAddr);
+
+ /* clear out any expired connections */
+ (void) mci_scan(NULL);
+
+ if (m->m_mno < 0)
+ syserr("!negative mno %d (%s)", m->m_mno, m->m_name);
+
+ s = stab(host, ST_MCI + m->m_mno, ST_ENTER);
+ mci = &s->s_mci;
+
+ /* initialize per-message data */
+ mci->mci_retryrcpt = false;
+ mci->mci_tolist = NULL;
+#if PIPELINING
+ mci->mci_okrcpts = 0;
+#endif /* PIPELINING */
+
+ if (mci->mci_rpool == NULL)
+ mci->mci_rpool = sm_rpool_new_x(NULL);
+
+ if (mci->mci_macro.mac_rpool == NULL)
+ mci->mci_macro.mac_rpool = mci->mci_rpool;
+
+ /*
+ ** We don't need to load the persistent data if we have data
+ ** already loaded in the cache.
+ */
+
+ if (mci->mci_host == NULL &&
+ (mci->mci_host = s->s_name) != NULL &&
+ !mci_load_persistent(mci))
+ {
+ if (tTd(42, 2))
+ sm_dprintf("mci_get(%s %s): lock failed\n",
+ host, m->m_name);
+ mci->mci_exitstat = EX_TEMPFAIL;
+ mci->mci_state = MCIS_CLOSED;
+ mci->mci_statfile = NULL;
+ return mci;
+ }
+
+ if (tTd(42, 2))
+ {
+ sm_dprintf("mci_get(%s %s): mci_state=%d, _flags=%lx, _exitstat=%d, _errno=%d\n",
+ host, m->m_name, mci->mci_state, mci->mci_flags,
+ mci->mci_exitstat, mci->mci_errno);
+ }
+
+ if (mci->mci_state == MCIS_OPEN)
+ {
+ /* poke the connection to see if it's still alive */
+ (void) smtpprobe(mci);
+
+ /* reset the stored state in the event of a timeout */
+ if (mci->mci_state != MCIS_OPEN)
+ {
+ mci->mci_errno = 0;
+ mci->mci_exitstat = EX_OK;
+ mci->mci_state = MCIS_CLOSED;
+ }
+ else
+ {
+ /* get peer host address */
+ /* (this should really be in the mci struct) */
+ SOCKADDR_LEN_T socklen = sizeof CurHostAddr;
+
+ (void) getpeername(sm_io_getinfo(mci->mci_in,
+ SM_IO_WHAT_FD, NULL),
+ (struct sockaddr *) &CurHostAddr, &socklen);
+ }
+ }
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ time_t now = curtime();
+
+ /* if this info is stale, ignore it */
+ if (mci->mci_lastuse + MciInfoTimeout <= now)
+ {
+ mci->mci_lastuse = now;
+ mci->mci_errno = 0;
+ mci->mci_exitstat = EX_OK;
+ }
+ }
+
+ return mci;
+}
+/*
+** MCI_NEW -- allocate new MCI structure
+**
+** Parameters:
+** rpool -- if non-NULL: allocate from that rpool.
+**
+** Returns:
+** mci (new).
+*/
+
+MCI *
+mci_new(rpool)
+ SM_RPOOL_T *rpool;
+{
+ register MCI *mci;
+
+ if (rpool == NULL)
+ mci = (MCI *) sm_malloc_x(sizeof *mci);
+ else
+ mci = (MCI *) sm_rpool_malloc_x(rpool, sizeof *mci);
+ memset((char *) mci, '\0', sizeof *mci);
+ mci->mci_rpool = sm_rpool_new_x(NULL);
+ mci->mci_macro.mac_rpool = mci->mci_rpool;
+ return mci;
+}
+/*
+** MCI_MATCH -- check connection cache for a particular host
+**
+** Parameters:
+** host -- host to look for.
+** m -- mailer.
+**
+** Returns:
+** true iff open connection exists.
+*/
+
+bool
+mci_match(host, m)
+ char *host;
+ MAILER *m;
+{
+ register MCI *mci;
+ register STAB *s;
+
+ if (m->m_mno < 0 || m->m_mno > MAXMAILERS)
+ return false;
+ s = stab(host, ST_MCI + m->m_mno, ST_FIND);
+ if (s == NULL)
+ return false;
+
+ mci = &s->s_mci;
+ return mci->mci_state == MCIS_OPEN;
+}
+/*
+** MCI_SETSTAT -- set status codes in MCI structure.
+**
+** Parameters:
+** mci -- the MCI structure to set.
+** xstat -- the exit status code.
+** dstat -- the DSN status code.
+** rstat -- the SMTP status code.
+**
+** Returns:
+** none.
+*/
+
+void
+mci_setstat(mci, xstat, dstat, rstat)
+ MCI *mci;
+ int xstat;
+ char *dstat;
+ char *rstat;
+{
+ /* protocol errors should never be interpreted as sticky */
+ if (xstat != EX_NOTSTICKY && xstat != EX_PROTOCOL)
+ mci->mci_exitstat = xstat;
+
+ SM_FREE_CLR(mci->mci_status);
+ if (dstat != NULL)
+ mci->mci_status = sm_strdup_x(dstat);
+
+ SM_FREE_CLR(mci->mci_rstatus);
+ if (rstat != NULL)
+ mci->mci_rstatus = sm_strdup_x(rstat);
+}
+/*
+** MCI_DUMP -- dump the contents of an MCI structure.
+**
+** Parameters:
+** mci -- the MCI structure to dump.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+struct mcifbits
+{
+ int mcif_bit; /* flag bit */
+ char *mcif_name; /* flag name */
+};
+static struct mcifbits MciFlags[] =
+{
+ { MCIF_VALID, "VALID" },
+ { MCIF_CACHED, "CACHED" },
+ { MCIF_ESMTP, "ESMTP" },
+ { MCIF_EXPN, "EXPN" },
+ { MCIF_SIZE, "SIZE" },
+ { MCIF_8BITMIME, "8BITMIME" },
+ { MCIF_7BIT, "7BIT" },
+ { MCIF_INHEADER, "INHEADER" },
+ { MCIF_CVT8TO7, "CVT8TO7" },
+ { MCIF_DSN, "DSN" },
+ { MCIF_8BITOK, "8BITOK" },
+ { MCIF_CVT7TO8, "CVT7TO8" },
+ { MCIF_INMIME, "INMIME" },
+ { MCIF_AUTH, "AUTH" },
+ { MCIF_AUTHACT, "AUTHACT" },
+ { MCIF_ENHSTAT, "ENHSTAT" },
+ { MCIF_PIPELINED, "PIPELINED" },
+#if STARTTLS
+ { MCIF_TLS, "TLS" },
+ { MCIF_TLSACT, "TLSACT" },
+#endif /* STARTTLS */
+ { MCIF_DLVR_BY, "DLVR_BY" },
+ { 0, NULL }
+};
+
+void
+mci_dump(mci, logit)
+ register MCI *mci;
+ bool logit;
+{
+ register char *p;
+ char *sep;
+ char buf[4000];
+
+ sep = logit ? " " : "\n\t";
+ p = buf;
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "MCI@%p: ", mci);
+ p += strlen(p);
+ if (mci == NULL)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "NULL");
+ goto printit;
+ }
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "flags=%lx", mci->mci_flags);
+ p += strlen(p);
+ if (mci->mci_flags != 0)
+ {
+ struct mcifbits *f;
+
+ *p++ = '<';
+ for (f = MciFlags; f->mcif_bit != 0; f++)
+ {
+ if (!bitset(f->mcif_bit, mci->mci_flags))
+ continue;
+ (void) sm_strlcpyn(p, SPACELEFT(buf, p), 2,
+ f->mcif_name, ",");
+ p += strlen(p);
+ }
+ p[-1] = '>';
+ }
+
+ /* Note: sm_snprintf() takes care of NULL arguments for %s */
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ ",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s",
+ sep, mci->mci_errno, mci->mci_herrno,
+ mci->mci_exitstat, mci->mci_state, (int) mci->mci_pid, sep);
+ p += strlen(p);
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "maxsize=%ld, phase=%s, mailer=%s,%s",
+ mci->mci_maxsize, mci->mci_phase,
+ mci->mci_mailer == NULL ? "NULL" : mci->mci_mailer->m_name,
+ sep);
+ p += strlen(p);
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "status=%s, rstatus=%s,%s",
+ mci->mci_status, mci->mci_rstatus, sep);
+ p += strlen(p);
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "host=%s, lastuse=%s",
+ mci->mci_host, ctime(&mci->mci_lastuse));
+printit:
+ if (logit)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "%.1000s", buf);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", buf);
+}
+/*
+** MCI_DUMP_ALL -- print the entire MCI cache
+**
+** Parameters:
+** logit -- if set, log the result instead of printing
+** to stdout.
+**
+** Returns:
+** none.
+*/
+
+void
+mci_dump_all(logit)
+ bool logit;
+{
+ register int i;
+
+ if (MciCache == NULL)
+ return;
+
+ for (i = 0; i < MaxMciCache; i++)
+ mci_dump(MciCache[i], logit);
+}
+/*
+** MCI_LOCK_HOST -- Lock host while sending.
+**
+** If we are contacting a host, we'll need to
+** update the status information in the host status
+** file, and if we want to do that, we ought to have
+** locked it. This has the (according to some)
+** desirable effect of serializing connectivity with
+** remote hosts -- i.e.: one connection to a given
+** host at a time.
+**
+** Parameters:
+** mci -- containing the host we want to lock.
+**
+** Returns:
+** EX_OK -- got the lock.
+** EX_TEMPFAIL -- didn't get the lock.
+*/
+
+int
+mci_lock_host(mci)
+ MCI *mci;
+{
+ if (mci == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_lock_host: NULL mci\n");
+ return EX_OK;
+ }
+
+ if (!SingleThreadDelivery)
+ return EX_OK;
+
+ return mci_lock_host_statfile(mci);
+}
+
+static int
+mci_lock_host_statfile(mci)
+ MCI *mci;
+{
+ int save_errno = errno;
+ int retVal = EX_OK;
+ char fname[MAXPATHLEN];
+
+ if (HostStatDir == NULL || mci->mci_host == NULL)
+ return EX_OK;
+
+ if (tTd(56, 2))
+ sm_dprintf("mci_lock_host: attempting to lock %s\n",
+ mci->mci_host);
+
+ if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname,
+ true) < 0)
+ {
+ /* of course this should never happen */
+ if (tTd(56, 2))
+ sm_dprintf("mci_lock_host: Failed to generate host path for %s\n",
+ mci->mci_host);
+
+ retVal = EX_TEMPFAIL;
+ goto cleanup;
+ }
+
+ mci->mci_statfile = safefopen(fname, O_RDWR, FileMode,
+ SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH|SFF_CREAT);
+
+ if (mci->mci_statfile == NULL)
+ {
+ syserr("mci_lock_host: cannot create host lock file %s", fname);
+ goto cleanup;
+ }
+
+ if (!lockfile(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
+ fname, "", LOCK_EX|LOCK_NB))
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_lock_host: couldn't get lock on %s\n",
+ fname);
+ (void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
+ mci->mci_statfile = NULL;
+ retVal = EX_TEMPFAIL;
+ goto cleanup;
+ }
+
+ if (tTd(56, 12) && mci->mci_statfile != NULL)
+ sm_dprintf("mci_lock_host: Sanity check -- lock is good\n");
+
+cleanup:
+ errno = save_errno;
+ return retVal;
+}
+/*
+** MCI_UNLOCK_HOST -- unlock host
+**
+** Clean up the lock on a host, close the file, let
+** someone else use it.
+**
+** Parameters:
+** mci -- us.
+**
+** Returns:
+** nothing.
+*/
+
+void
+mci_unlock_host(mci)
+ MCI *mci;
+{
+ int save_errno = errno;
+
+ if (mci == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_unlock_host: NULL mci\n");
+ return;
+ }
+
+ if (HostStatDir == NULL || mci->mci_host == NULL)
+ return;
+
+ if (!SingleThreadDelivery && mci_lock_host_statfile(mci) == EX_TEMPFAIL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_unlock_host: stat file already locked\n");
+ }
+ else
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_unlock_host: store prior to unlock\n");
+ mci_store_persistent(mci);
+ }
+
+ if (mci->mci_statfile != NULL)
+ {
+ (void) sm_io_close(mci->mci_statfile, SM_TIME_DEFAULT);
+ mci->mci_statfile = NULL;
+ }
+
+ errno = save_errno;
+}
+/*
+** MCI_LOAD_PERSISTENT -- load persistent host info
+**
+** Load information about host that is kept
+** in common for all running sendmails.
+**
+** Parameters:
+** mci -- the host/connection to load persistent info for.
+**
+** Returns:
+** true -- lock was successful
+** false -- lock failed
+*/
+
+static bool
+mci_load_persistent(mci)
+ MCI *mci;
+{
+ int save_errno = errno;
+ bool locked = true;
+ SM_FILE_T *fp;
+ char fname[MAXPATHLEN];
+
+ if (mci == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_load_persistent: NULL mci\n");
+ return true;
+ }
+
+ if (IgnoreHostStatus || HostStatDir == NULL || mci->mci_host == NULL)
+ return true;
+
+ /* Already have the persistent information in memory */
+ if (SingleThreadDelivery && mci->mci_statfile != NULL)
+ return true;
+
+ if (tTd(56, 1))
+ sm_dprintf("mci_load_persistent: Attempting to load persistent information for %s\n",
+ mci->mci_host);
+
+ if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname,
+ false) < 0)
+ {
+ /* Not much we can do if the file isn't there... */
+ if (tTd(56, 1))
+ sm_dprintf("mci_load_persistent: Couldn't generate host path\n");
+ goto cleanup;
+ }
+
+ fp = safefopen(fname, O_RDONLY, FileMode,
+ SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
+ if (fp == NULL)
+ {
+ /* I can't think of any reason this should ever happen */
+ if (tTd(56, 1))
+ sm_dprintf("mci_load_persistent: open(%s): %s\n",
+ fname, sm_errstring(errno));
+ goto cleanup;
+ }
+
+ FileName = fname;
+ locked = lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname, "",
+ LOCK_SH|LOCK_NB);
+ if (locked)
+ {
+ (void) mci_read_persistent(fp, mci);
+ (void) lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), fname,
+ "", LOCK_UN);
+ }
+ FileName = NULL;
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+
+cleanup:
+ errno = save_errno;
+ return locked;
+}
+/*
+** MCI_READ_PERSISTENT -- read persistent host status file
+**
+** Parameters:
+** fp -- the file pointer to read.
+** mci -- the pointer to fill in.
+**
+** Returns:
+** -1 -- if the file was corrupt.
+** 0 -- otherwise.
+**
+** Warning:
+** This code makes the assumption that this data
+** will be read in an atomic fashion, and that the data
+** was written in an atomic fashion. Any other functioning
+** may lead to some form of insanity. This should be
+** perfectly safe due to underlying stdio buffering.
+*/
+
+static int
+mci_read_persistent(fp, mci)
+ SM_FILE_T *fp;
+ register MCI *mci;
+{
+ int ver;
+ register char *p;
+ int saveLineNumber = LineNumber;
+ char buf[MAXLINE];
+
+ if (fp == NULL)
+ syserr("mci_read_persistent: NULL fp");
+ if (mci == NULL)
+ syserr("mci_read_persistent: NULL mci");
+ if (tTd(56, 93))
+ {
+ sm_dprintf("mci_read_persistent: fp=%lx, mci=",
+ (unsigned long) fp);
+ }
+
+ SM_FREE_CLR(mci->mci_status);
+ SM_FREE_CLR(mci->mci_rstatus);
+
+ sm_io_rewind(fp, SM_TIME_DEFAULT);
+ ver = -1;
+ LineNumber = 0;
+ while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ LineNumber++;
+ p = strchr(buf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ switch (buf[0])
+ {
+ case 'V': /* version stamp */
+ ver = atoi(&buf[1]);
+ if (ver < 0 || ver > 0)
+ syserr("Unknown host status version %d: %d max",
+ ver, 0);
+ break;
+
+ case 'E': /* UNIX error number */
+ mci->mci_errno = atoi(&buf[1]);
+ break;
+
+ case 'H': /* DNS error number */
+ mci->mci_herrno = atoi(&buf[1]);
+ break;
+
+ case 'S': /* UNIX exit status */
+ mci->mci_exitstat = atoi(&buf[1]);
+ break;
+
+ case 'D': /* DSN status */
+ mci->mci_status = newstr(&buf[1]);
+ break;
+
+ case 'R': /* SMTP status */
+ mci->mci_rstatus = newstr(&buf[1]);
+ break;
+
+ case 'U': /* last usage time */
+ mci->mci_lastuse = atol(&buf[1]);
+ break;
+
+ case '.': /* end of file */
+ if (tTd(56, 93))
+ mci_dump(mci, false);
+ return 0;
+
+ default:
+ sm_syslog(LOG_CRIT, NOQID,
+ "%s: line %d: Unknown host status line \"%s\"",
+ FileName == NULL ? mci->mci_host : FileName,
+ LineNumber, buf);
+ LineNumber = saveLineNumber;
+ return -1;
+ }
+ }
+ LineNumber = saveLineNumber;
+ if (tTd(56, 93))
+ sm_dprintf("incomplete (missing dot for EOF)\n");
+ if (ver < 0)
+ return -1;
+ return 0;
+}
+/*
+** MCI_STORE_PERSISTENT -- Store persistent MCI information
+**
+** Store information about host that is kept
+** in common for all running sendmails.
+**
+** Parameters:
+** mci -- the host/connection to store persistent info for.
+**
+** Returns:
+** none.
+*/
+
+void
+mci_store_persistent(mci)
+ MCI *mci;
+{
+ int save_errno = errno;
+
+ if (mci == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_store_persistent: NULL mci\n");
+ return;
+ }
+
+ if (HostStatDir == NULL || mci->mci_host == NULL)
+ return;
+
+ if (tTd(56, 1))
+ sm_dprintf("mci_store_persistent: Storing information for %s\n",
+ mci->mci_host);
+
+ if (mci->mci_statfile == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_store_persistent: no statfile\n");
+ return;
+ }
+
+ sm_io_rewind(mci->mci_statfile, SM_TIME_DEFAULT);
+#if !NOFTRUNCATE
+ (void) ftruncate(sm_io_getinfo(mci->mci_statfile, SM_IO_WHAT_FD, NULL),
+ (off_t) 0);
+#endif /* !NOFTRUNCATE */
+
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "V0\n");
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "E%d\n",
+ mci->mci_errno);
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "H%d\n",
+ mci->mci_herrno);
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "S%d\n",
+ mci->mci_exitstat);
+ if (mci->mci_status != NULL)
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
+ "D%.80s\n",
+ denlstring(mci->mci_status, true, false));
+ if (mci->mci_rstatus != NULL)
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT,
+ "R%.80s\n",
+ denlstring(mci->mci_rstatus, true, false));
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, "U%ld\n",
+ (long)(mci->mci_lastuse));
+ (void) sm_io_fprintf(mci->mci_statfile, SM_TIME_DEFAULT, ".\n");
+
+ (void) sm_io_flush(mci->mci_statfile, SM_TIME_DEFAULT);
+
+ errno = save_errno;
+ return;
+}
+/*
+** MCI_TRAVERSE_PERSISTENT -- walk persistent status tree
+**
+** Recursively find all the mci host files in `pathname'. Default to
+** main host status directory if no path is provided.
+** Call (*action)(pathname, host) for each file found.
+**
+** Note: all information is collected in a list before it is processed.
+** This may not be the best way to do it, but it seems safest, since
+** the file system would be touched while we are attempting to traverse
+** the directory tree otherwise (during purges).
+**
+** Parameters:
+** action -- function to call on each node. If returns < 0,
+** return immediately.
+** pathname -- root of tree. If null, use main host status
+** directory.
+**
+** Returns:
+** < 0 -- if any action routine returns a negative value, that
+** value is returned.
+** 0 -- if we successfully went to completion.
+** > 0 -- return status from action()
+*/
+
+int
+mci_traverse_persistent(action, pathname)
+ int (*action)();
+ char *pathname;
+{
+ struct stat statbuf;
+ DIR *d;
+ int ret;
+
+ if (pathname == NULL)
+ pathname = HostStatDir;
+ if (pathname == NULL)
+ return -1;
+
+ if (tTd(56, 1))
+ sm_dprintf("mci_traverse: pathname is %s\n", pathname);
+
+ ret = stat(pathname, &statbuf);
+ if (ret < 0)
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_traverse: Failed to stat %s: %s\n",
+ pathname, sm_errstring(errno));
+ return ret;
+ }
+ if (S_ISDIR(statbuf.st_mode))
+ {
+ bool leftone, removedone;
+ size_t len;
+ char *newptr;
+ struct dirent *e;
+ char newpath[MAXPATHLEN];
+
+ if ((d = opendir(pathname)) == NULL)
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_traverse: opendir %s: %s\n",
+ pathname, sm_errstring(errno));
+ return -1;
+ }
+ len = sizeof(newpath) - MAXNAMLEN - 3;
+ if (sm_strlcpy(newpath, pathname, len) >= len)
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_traverse: path \"%s\" too long",
+ pathname);
+ return -1;
+ }
+ newptr = newpath + strlen(newpath);
+ *newptr++ = '/';
+
+ /*
+ ** repeat until no file has been removed
+ ** this may become ugly when several files "expire"
+ ** during these loops, but it's better than doing
+ ** a rewinddir() inside the inner loop
+ */
+
+ do
+ {
+ leftone = removedone = false;
+ while ((e = readdir(d)) != NULL)
+ {
+ if (e->d_name[0] == '.')
+ continue;
+
+ (void) sm_strlcpy(newptr, e->d_name,
+ sizeof newpath -
+ (newptr - newpath));
+
+ if (StopRequest)
+ stop_sendmail();
+ ret = mci_traverse_persistent(action, newpath);
+ if (ret < 0)
+ break;
+ if (ret == 1)
+ leftone = true;
+ if (!removedone && ret == 0 &&
+ action == mci_purge_persistent)
+ removedone = true;
+ }
+ if (ret < 0)
+ break;
+
+ /*
+ ** The following appears to be
+ ** necessary during purges, since
+ ** we modify the directory structure
+ */
+
+ if (removedone)
+ rewinddir(d);
+ if (tTd(56, 40))
+ sm_dprintf("mci_traverse: path %s: ret %d removed %d left %d\n",
+ pathname, ret, removedone, leftone);
+ } while (removedone);
+
+ /* purge (or whatever) the directory proper */
+ if (!leftone)
+ {
+ *--newptr = '\0';
+ ret = (*action)(newpath, NULL);
+ }
+ (void) closedir(d);
+ }
+ else if (S_ISREG(statbuf.st_mode))
+ {
+ char *end = pathname + strlen(pathname) - 1;
+ char *start;
+ char *scan;
+ char host[MAXHOSTNAMELEN];
+ char *hostptr = host;
+
+ /*
+ ** Reconstruct the host name from the path to the
+ ** persistent information.
+ */
+
+ do
+ {
+ if (hostptr != host)
+ *(hostptr++) = '.';
+ start = end;
+ while (*(start - 1) != '/')
+ start--;
+
+ if (*end == '.')
+ end--;
+
+ for (scan = start; scan <= end; scan++)
+ *(hostptr++) = *scan;
+
+ end = start - 2;
+ } while (*end == '.');
+
+ *hostptr = '\0';
+
+ /*
+ ** Do something with the file containing the persistent
+ ** information.
+ */
+
+ ret = (*action)(pathname, host);
+ }
+
+ return ret;
+}
+/*
+** MCI_PRINT_PERSISTENT -- print persistent info
+**
+** Dump the persistent information in the file 'pathname'
+**
+** Parameters:
+** pathname -- the pathname to the status file.
+** hostname -- the corresponding host name.
+**
+** Returns:
+** 0
+*/
+
+int
+mci_print_persistent(pathname, hostname)
+ char *pathname;
+ char *hostname;
+{
+ static bool initflag = false;
+ SM_FILE_T *fp;
+ int width = Verbose ? 78 : 25;
+ bool locked;
+ MCI mcib;
+
+ /* skip directories */
+ if (hostname == NULL)
+ return 0;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ if (!initflag)
+ {
+ initflag = true;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " -------------- Hostname --------------- How long ago ---------Results---------\n");
+ }
+
+ fp = safefopen(pathname, O_RDONLY, FileMode,
+ SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH);
+
+ if (fp == NULL)
+ {
+ if (tTd(56, 1))
+ sm_dprintf("mci_print_persistent: cannot open %s: %s\n",
+ pathname, sm_errstring(errno));
+ return 0;
+ }
+
+ FileName = pathname;
+ memset(&mcib, '\0', sizeof mcib);
+ if (mci_read_persistent(fp, &mcib) < 0)
+ {
+ syserr("%s: could not read status file", pathname);
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ FileName = NULL;
+ return 0;
+ }
+
+ locked = !lockfile(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), pathname,
+ "", LOCK_SH|LOCK_NB);
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ FileName = NULL;
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%c%-39s %12s ",
+ locked ? '*' : ' ', hostname,
+ pintvl(curtime() - mcib.mci_lastuse, true));
+ if (mcib.mci_rstatus != NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n", width,
+ mcib.mci_rstatus);
+ else if (mcib.mci_exitstat == EX_TEMPFAIL && mcib.mci_errno != 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Deferred: %.*s\n", width - 10,
+ sm_errstring(mcib.mci_errno));
+ else if (mcib.mci_exitstat != 0)
+ {
+ char *exmsg = sm_sysexmsg(mcib.mci_exitstat);
+
+ if (exmsg == NULL)
+ {
+ char buf[80];
+
+ (void) sm_snprintf(buf, sizeof buf,
+ "Unknown mailer error %d",
+ mcib.mci_exitstat);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
+ width, buf);
+ }
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%.*s\n",
+ width, &exmsg[5]);
+ }
+ else if (mcib.mci_errno == 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK: %.*s\n",
+ width - 4, sm_errstring(mcib.mci_errno));
+
+ return 0;
+}
+/*
+** MCI_PURGE_PERSISTENT -- Remove a persistence status file.
+**
+** Parameters:
+** pathname -- path to the status file.
+** hostname -- name of host corresponding to that file.
+** NULL if this is a directory (domain).
+**
+** Returns:
+** 0 -- ok
+** 1 -- file not deleted (too young, incorrect format)
+** < 0 -- some error occurred
+*/
+
+int
+mci_purge_persistent(pathname, hostname)
+ char *pathname;
+ char *hostname;
+{
+ struct stat statbuf;
+ char *end = pathname + strlen(pathname) - 1;
+ int ret;
+
+ if (tTd(56, 1))
+ sm_dprintf("mci_purge_persistent: purging %s\n", pathname);
+
+ ret = stat(pathname, &statbuf);
+ if (ret < 0)
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_purge_persistent: Failed to stat %s: %s\n",
+ pathname, sm_errstring(errno));
+ return ret;
+ }
+ if (curtime() - statbuf.st_mtime <= MciInfoTimeout)
+ return 1;
+ if (hostname != NULL)
+ {
+ /* remove the file */
+ ret = unlink(pathname);
+ if (ret < 0)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "mci_purge_persistent: failed to unlink %s: %s",
+ pathname, sm_errstring(errno));
+ if (tTd(56, 2))
+ sm_dprintf("mci_purge_persistent: failed to unlink %s: %s\n",
+ pathname, sm_errstring(errno));
+ return ret;
+ }
+ }
+ else
+ {
+ /* remove the directory */
+ if (*end != '.')
+ return 1;
+
+ if (tTd(56, 1))
+ sm_dprintf("mci_purge_persistent: dpurge %s\n", pathname);
+
+ ret = rmdir(pathname);
+ if (ret < 0)
+ {
+ if (tTd(56, 2))
+ sm_dprintf("mci_purge_persistent: rmdir %s: %s\n",
+ pathname, sm_errstring(errno));
+ return ret;
+ }
+ }
+
+ return 0;
+}
+/*
+** MCI_GENERATE_PERSISTENT_PATH -- generate path from hostname
+**
+** Given `host', convert from a.b.c to $QueueDir/.hoststat/c./b./a,
+** putting the result into `path'. if `createflag' is set, intervening
+** directories will be created as needed.
+**
+** Parameters:
+** host -- host name to convert from.
+** path -- place to store result.
+** pathlen -- length of path buffer.
+** createflag -- if set, create intervening directories as
+** needed.
+**
+** Returns:
+** 0 -- success
+** -1 -- failure
+*/
+
+static int
+mci_generate_persistent_path(host, path, pathlen, createflag)
+ const char *host;
+ char *path;
+ int pathlen;
+ bool createflag;
+{
+ char *elem, *p, *x, ch;
+ int ret = 0;
+ int len;
+ char t_host[MAXHOSTNAMELEN];
+#if NETINET6
+ struct in6_addr in6_addr;
+#endif /* NETINET6 */
+
+ /*
+ ** Rationality check the arguments.
+ */
+
+ if (host == NULL)
+ {
+ syserr("mci_generate_persistent_path: null host");
+ return -1;
+ }
+ if (path == NULL)
+ {
+ syserr("mci_generate_persistent_path: null path");
+ return -1;
+ }
+
+ if (tTd(56, 80))
+ sm_dprintf("mci_generate_persistent_path(%s): ", host);
+
+ if (*host == '\0' || *host == '.')
+ return -1;
+
+ /* make certain this is not a bracketed host number */
+ if (strlen(host) > sizeof t_host - 1)
+ return -1;
+ if (host[0] == '[')
+ (void) sm_strlcpy(t_host, host + 1, sizeof t_host);
+ else
+ (void) sm_strlcpy(t_host, host, sizeof t_host);
+
+ /*
+ ** Delete any trailing dots from the hostname.
+ ** Leave 'elem' pointing at the \0.
+ */
+
+ elem = t_host + strlen(t_host);
+ while (elem > t_host &&
+ (elem[-1] == '.' || (host[0] == '[' && elem[-1] == ']')))
+ *--elem = '\0';
+
+ /* check for bogus bracketed address */
+ if (host[0] == '[')
+ {
+ bool good = false;
+# if NETINET6
+ if (anynet_pton(AF_INET6, t_host, &in6_addr) == 1)
+ good = true;
+# endif /* NETINET6 */
+# if NETINET
+ if (inet_addr(t_host) != INADDR_NONE)
+ good = true;
+# endif /* NETINET */
+ if (!good)
+ return -1;
+ }
+
+ /* check for what will be the final length of the path */
+ len = strlen(HostStatDir) + 2;
+ for (p = (char *) t_host; *p != '\0'; p++)
+ {
+ if (*p == '.')
+ len++;
+ len++;
+ if (p[0] == '.' && p[1] == '.')
+ return -1;
+ }
+ if (len > pathlen || len < 1)
+ return -1;
+ (void) sm_strlcpy(path, HostStatDir, pathlen);
+ p = path + strlen(path);
+ while (elem > t_host)
+ {
+ if (!path_is_dir(path, createflag))
+ {
+ ret = -1;
+ break;
+ }
+ elem--;
+ while (elem >= t_host && *elem != '.')
+ elem--;
+ *p++ = '/';
+ x = elem + 1;
+ while ((ch = *x++) != '\0' && ch != '.')
+ {
+ if (isascii(ch) && isupper(ch))
+ ch = tolower(ch);
+ if (ch == '/')
+ ch = ':'; /* / -> : */
+ *p++ = ch;
+ }
+ if (elem >= t_host)
+ *p++ = '.';
+ *p = '\0';
+ }
+ if (tTd(56, 80))
+ {
+ if (ret < 0)
+ sm_dprintf("FAILURE %d\n", ret);
+ else
+ sm_dprintf("SUCCESS %s\n", path);
+ }
+ return ret;
+}
diff --git a/contrib/sendmail/src/milter.c b/contrib/sendmail/src/milter.c
new file mode 100644
index 0000000..271f7dc
--- /dev/null
+++ b/contrib/sendmail/src/milter.c
@@ -0,0 +1,3892 @@
+/*
+ * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: milter.c,v 8.197.2.2 2002/08/06 22:58:38 gshapiro Exp $")
+
+#if MILTER
+# include <libmilter/mfapi.h>
+# include <libmilter/mfdef.h>
+
+# include <errno.h>
+# include <sys/time.h>
+
+# if NETINET || NETINET6
+# include <arpa/inet.h>
+# endif /* NETINET || NETINET6 */
+
+# include <sm/fdset.h>
+
+static void milter_connect_timeout __P((void));
+static void milter_error __P((struct milter *, ENVELOPE *));
+static int milter_open __P((struct milter *, bool, ENVELOPE *));
+static void milter_parse_timeouts __P((char *, struct milter *));
+
+static char *MilterConnectMacros[MAXFILTERMACROS + 1];
+static char *MilterHeloMacros[MAXFILTERMACROS + 1];
+static char *MilterEnvFromMacros[MAXFILTERMACROS + 1];
+static char *MilterEnvRcptMacros[MAXFILTERMACROS + 1];
+
+# define MILTER_CHECK_DONE_MSG() \
+ if (*state == SMFIR_REPLYCODE || \
+ *state == SMFIR_REJECT || \
+ *state == SMFIR_DISCARD || \
+ *state == SMFIR_TEMPFAIL) \
+ { \
+ /* Abort the filters to let them know we are done with msg */ \
+ milter_abort(e); \
+ }
+
+# if _FFR_QUARANTINE
+# define MILTER_CHECK_ERROR(action) \
+ if (tTd(71, 101)) \
+ { \
+ if (e->e_quarmsg == NULL) \
+ { \
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, \
+ "filter failure"); \
+ macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), \
+ e->e_quarmsg); \
+ } \
+ } \
+ else if (bitnset(SMF_TEMPFAIL, m->mf_flags)) \
+ *state = SMFIR_TEMPFAIL; \
+ else if (bitnset(SMF_REJECT, m->mf_flags)) \
+ *state = SMFIR_REJECT; \
+ else \
+ action;
+# else /* _FFR_QUARANTINE */
+# define MILTER_CHECK_ERROR(action) \
+ if (bitnset(SMF_TEMPFAIL, m->mf_flags)) \
+ *state = SMFIR_TEMPFAIL; \
+ else if (bitnset(SMF_REJECT, m->mf_flags)) \
+ *state = SMFIR_REJECT; \
+ else \
+ action;
+# endif /* _FFR_QUARANTINE */
+
+# define MILTER_CHECK_REPLYCODE(default) \
+ if (response == NULL || \
+ strlen(response) + 1 != (size_t) rlen || \
+ rlen < 3 || \
+ (response[0] != '4' && response[0] != '5') || \
+ !isascii(response[1]) || !isdigit(response[1]) || \
+ !isascii(response[2]) || !isdigit(response[2])) \
+ { \
+ if (response != NULL) \
+ sm_free(response); /* XXX */ \
+ response = newstr(default); \
+ } \
+ else \
+ { \
+ char *ptr = response; \
+ \
+ /* Check for unprotected %'s in the string */ \
+ while (*ptr != '\0') \
+ { \
+ if (*ptr == '%' && *++ptr != '%') \
+ { \
+ sm_free(response); /* XXX */ \
+ response = newstr(default); \
+ break; \
+ } \
+ ptr++; \
+ } \
+ }
+
+# define MILTER_DF_ERROR(msg) \
+{ \
+ int save_errno = errno; \
+ \
+ if (tTd(64, 5)) \
+ { \
+ sm_dprintf(msg, dfname, sm_errstring(save_errno)); \
+ sm_dprintf("\n"); \
+ } \
+ if (MilterLogLevel > 0) \
+ sm_syslog(LOG_ERR, e->e_id, msg, dfname, sm_errstring(save_errno)); \
+ if (SuperSafe == SAFE_REALLY) \
+ { \
+ if (e->e_dfp != NULL) \
+ { \
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT); \
+ e->e_dfp = NULL; \
+ } \
+ e->e_flags &= ~EF_HAS_DF; \
+ } \
+ errno = save_errno; \
+}
+
+/*
+** MILTER_TIMEOUT -- make sure socket is ready in time
+**
+** Parameters:
+** routine -- routine name for debug/logging
+** secs -- number of seconds in timeout
+** write -- waiting to read or write?
+** started -- whether this is part of a previous sequence
+**
+** Assumes 'm' is a milter structure for the current socket.
+*/
+
+# define MILTER_TIMEOUT(routine, secs, write, started) \
+{ \
+ int ret; \
+ int save_errno; \
+ fd_set fds; \
+ struct timeval tv; \
+ \
+ if (SM_FD_SETSIZE > 0 && m->mf_sock >= SM_FD_SETSIZE) \
+ { \
+ if (tTd(64, 5)) \
+ sm_dprintf("milter_%s(%s): socket %d is larger than FD_SETSIZE %d\n", \
+ (routine), m->mf_name, m->mf_sock, \
+ SM_FD_SETSIZE); \
+ if (MilterLogLevel > 0) \
+ sm_syslog(LOG_ERR, e->e_id, \
+ "Milter (%s): socket(%s) %d is larger than FD_SETSIZE %d", \
+ m->mf_name, (routine), m->mf_sock, \
+ SM_FD_SETSIZE); \
+ milter_error(m, e); \
+ return NULL; \
+ } \
+ \
+ do \
+ { \
+ FD_ZERO(&fds); \
+ SM_FD_SET(m->mf_sock, &fds); \
+ tv.tv_sec = (secs); \
+ tv.tv_usec = 0; \
+ ret = select(m->mf_sock + 1, \
+ (write) ? NULL : &fds, \
+ (write) ? &fds : NULL, \
+ NULL, &tv); \
+ } while (ret < 0 && errno == EINTR); \
+ \
+ switch (ret) \
+ { \
+ case 0: \
+ if (tTd(64, 5)) \
+ sm_dprintf("milter_%s(%s): timeout\n", (routine), \
+ m->mf_name); \
+ if (MilterLogLevel > 0) \
+ sm_syslog(LOG_ERR, e->e_id, \
+ "Milter (%s): %s %s %s %s", \
+ m->mf_name, "timeout", \
+ started ? "during" : "before", \
+ "data", (routine)); \
+ milter_error(m, e); \
+ return NULL; \
+ \
+ case -1: \
+ save_errno = errno; \
+ if (tTd(64, 5)) \
+ sm_dprintf("milter_%s(%s): select: %s\n", (routine), \
+ m->mf_name, sm_errstring(save_errno)); \
+ if (MilterLogLevel > 0) \
+ { \
+ sm_syslog(LOG_ERR, e->e_id, \
+ "Milter (%s): select(%s): %s", \
+ m->mf_name, (routine), \
+ sm_errstring(save_errno)); \
+ } \
+ milter_error(m, e); \
+ return NULL; \
+ \
+ default: \
+ if (SM_FD_ISSET(m->mf_sock, &fds)) \
+ break; \
+ if (tTd(64, 5)) \
+ sm_dprintf("milter_%s(%s): socket not ready\n", \
+ (routine), m->mf_name); \
+ if (MilterLogLevel > 0) \
+ { \
+ sm_syslog(LOG_ERR, e->e_id, \
+ "Milter (%s): socket(%s) not ready", \
+ m->mf_name, (routine)); \
+ } \
+ milter_error(m, e); \
+ return NULL; \
+ } \
+}
+
+/*
+** Low level functions
+*/
+
+/*
+** MILTER_READ -- read from a remote milter filter
+**
+** Parameters:
+** m -- milter to read from.
+** cmd -- return param for command read.
+** rlen -- return length of response string.
+** to -- timeout in seconds.
+** e -- current envelope.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+static char *
+milter_sysread(m, buf, sz, to, e)
+ struct milter *m;
+ char *buf;
+ ssize_t sz;
+ time_t to;
+ ENVELOPE *e;
+{
+ time_t readstart = 0;
+ ssize_t len, curl;
+ bool started = false;
+
+ curl = 0;
+
+ if (to > 0)
+ readstart = curtime();
+
+ for (;;)
+ {
+ if (to > 0)
+ {
+ time_t now;
+
+ now = curtime();
+ if (now - readstart >= to)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_read (%s): %s %s %s",
+ m->mf_name, "timeout",
+ started ? "during" : "before",
+ "data read");
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): %s %s %s",
+ m->mf_name, "timeout",
+ started ? "during" : "before",
+ "data read");
+ milter_error(m, e);
+ return NULL;
+ }
+ to -= now - readstart;
+ readstart = now;
+ MILTER_TIMEOUT("read", to, false, started);
+ }
+
+ len = read(m->mf_sock, buf + curl, sz - curl);
+
+ if (len < 0)
+ {
+ int save_errno = errno;
+
+ if (tTd(64, 5))
+ sm_dprintf("milter_read(%s): read returned %ld: %s\n",
+ m->mf_name, (long) len,
+ sm_errstring(save_errno));
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): read returned %ld: %s",
+ m->mf_name, (long) len,
+ sm_errstring(save_errno));
+ milter_error(m, e);
+ return NULL;
+ }
+
+ started = true;
+ curl += len;
+ if (len == 0 || curl >= sz)
+ break;
+
+ }
+
+ if (curl != sz)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_read(%s): cmd read returned %ld, expecting %ld\n",
+ m->mf_name, (long) curl, (long) sz);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_read(%s): cmd read returned %ld, expecting %ld",
+ m->mf_name, (long) curl, (long) sz);
+ milter_error(m, e);
+ return NULL;
+ }
+ return buf;
+}
+
+static char *
+milter_read(m, cmd, rlen, to, e)
+ struct milter *m;
+ char *cmd;
+ ssize_t *rlen;
+ time_t to;
+ ENVELOPE *e;
+{
+ time_t readstart = 0;
+ ssize_t expl;
+ mi_int32 i;
+ char *buf;
+ char data[MILTER_LEN_BYTES + 1];
+
+ *rlen = 0;
+ *cmd = '\0';
+
+ if (to > 0)
+ readstart = curtime();
+
+ if (milter_sysread(m, data, sizeof data, to, e) == NULL)
+ return NULL;
+
+ /* reset timeout */
+ if (to > 0)
+ {
+ time_t now;
+
+ now = curtime();
+ if (now - readstart >= to)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_read(%s): timeout before data read\n",
+ m->mf_name);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter read(%s): timeout before data read",
+ m->mf_name);
+ milter_error(m, e);
+ return NULL;
+ }
+ to -= now - readstart;
+ }
+
+ *cmd = data[MILTER_LEN_BYTES];
+ data[MILTER_LEN_BYTES] = '\0';
+ (void) memcpy(&i, data, MILTER_LEN_BYTES);
+ expl = ntohl(i) - 1;
+
+ if (tTd(64, 25))
+ sm_dprintf("milter_read(%s): expecting %ld bytes\n",
+ m->mf_name, (long) expl);
+
+ if (expl < 0)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_read(%s): read size %ld out of range\n",
+ m->mf_name, (long) expl);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_read(%s): read size %ld out of range",
+ m->mf_name, (long) expl);
+ milter_error(m, e);
+ return NULL;
+ }
+
+ if (expl == 0)
+ return NULL;
+
+ buf = (char *) xalloc(expl);
+
+ if (milter_sysread(m, buf, expl, to, e) == NULL)
+ {
+ sm_free(buf); /* XXX */
+ return NULL;
+ }
+
+ if (tTd(64, 50))
+ sm_dprintf("milter_read(%s): Returning %*s\n",
+ m->mf_name, (int) expl, buf);
+ *rlen = expl;
+ return buf;
+}
+/*
+** MILTER_WRITE -- write to a remote milter filter
+**
+** Parameters:
+** m -- milter to read from.
+** cmd -- command to send.
+** buf -- optional command data.
+** len -- length of buf.
+** to -- timeout in seconds.
+** e -- current envelope.
+**
+** Returns:
+** buf if successful, NULL otherwise
+** Not actually used anywhere but function prototype
+** must match milter_read()
+*/
+
+static char *
+milter_write(m, cmd, buf, len, to, e)
+ struct milter *m;
+ char cmd;
+ char *buf;
+ ssize_t len;
+ time_t to;
+ ENVELOPE *e;
+{
+ time_t writestart = (time_t) 0;
+ ssize_t sl, i;
+ mi_int32 nl;
+ char data[MILTER_LEN_BYTES + 1];
+ bool started = false;
+
+ if (len < 0 || len > MILTER_CHUNK_SIZE)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_write(%s): length %ld out of range\n",
+ m->mf_name, (long) len);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_write(%s): length %ld out of range",
+ m->mf_name, (long) len);
+ milter_error(m, e);
+ return NULL;
+ }
+
+ if (tTd(64, 20))
+ sm_dprintf("milter_write(%s): cmd %c, len %ld\n",
+ m->mf_name, cmd, (long) len);
+
+ nl = htonl(len + 1); /* add 1 for the cmd char */
+ (void) memcpy(data, (char *) &nl, MILTER_LEN_BYTES);
+ data[MILTER_LEN_BYTES] = cmd;
+ sl = MILTER_LEN_BYTES + 1;
+
+ if (to > 0)
+ {
+ writestart = curtime();
+ MILTER_TIMEOUT("write", to, true, started);
+ }
+
+ /* use writev() instead to send the whole stuff at once? */
+ i = write(m->mf_sock, (void *) data, sl);
+ if (i != sl)
+ {
+ int save_errno = errno;
+
+ if (tTd(64, 5))
+ sm_dprintf("milter_write (%s): write(%c) returned %ld, expected %ld: %s\n",
+ m->mf_name, cmd, (long) i, (long) sl,
+ sm_errstring(save_errno));
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): write(%c) returned %ld, expected %ld: %s",
+ m->mf_name, cmd, (long) i, (long) sl,
+ sm_errstring(save_errno));
+ milter_error(m, e);
+ return buf;
+ }
+
+ if (len <= 0 || buf == NULL)
+ return buf;
+
+ if (tTd(64, 50))
+ sm_dprintf("milter_write(%s): Sending %*s\n",
+ m->mf_name, (int) len, buf);
+ started = true;
+
+ if (to > 0)
+ {
+ time_t now;
+
+ now = curtime();
+ if (now - writestart >= to)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_write(%s): timeout before data write\n",
+ m->mf_name);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): timeout before data write",
+ m->mf_name);
+ milter_error(m, e);
+ return NULL;
+ }
+ else
+ {
+ to -= now - writestart;
+ MILTER_TIMEOUT("write", to, true, started);
+ }
+ }
+
+ i = write(m->mf_sock, (void *) buf, len);
+ if (i != len)
+ {
+ int save_errno = errno;
+
+ if (tTd(64, 5))
+ sm_dprintf("milter_write(%s): write(%c) returned %ld, expected %ld: %s\n",
+ m->mf_name, cmd, (long) i, (long) sl,
+ sm_errstring(save_errno));
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): write(%c) returned %ld, expected %ld: %s",
+ m->mf_name, cmd, (long) i, (long) len,
+ sm_errstring(save_errno));
+ milter_error(m, e);
+ return NULL;
+ }
+ return buf;
+}
+
+/*
+** Utility functions
+*/
+
+/*
+** MILTER_OPEN -- connect to remote milter filter
+**
+** Parameters:
+** m -- milter to connect to.
+** parseonly -- parse but don't connect.
+** e -- current envelope.
+**
+** Returns:
+** connected socket if sucessful && !parseonly,
+** 0 upon parse success if parseonly,
+** -1 otherwise.
+*/
+
+static jmp_buf MilterConnectTimeout;
+
+static int
+milter_open(m, parseonly, e)
+ struct milter *m;
+ bool parseonly;
+ ENVELOPE *e;
+{
+ int sock = 0;
+ SOCKADDR_LEN_T addrlen = 0;
+ int addrno = 0;
+ int save_errno;
+ char *p;
+ char *colon;
+ char *at;
+ struct hostent *hp = NULL;
+ SOCKADDR addr;
+
+ if (m->mf_conn == NULL || m->mf_conn[0] == '\0')
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: empty or missing socket information\n",
+ m->mf_name);
+ if (parseonly)
+ syserr("X%s: empty or missing socket information",
+ m->mf_name);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): empty or missing socket information",
+ m->mf_name);
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* protocol:filename or protocol:port@host */
+ memset(&addr, '\0', sizeof addr);
+ p = m->mf_conn;
+ colon = strchr(p, ':');
+ if (colon != NULL)
+ {
+ *colon = '\0';
+
+ if (*p == '\0')
+ {
+# if NETUNIX
+ /* default to AF_UNIX */
+ addr.sa.sa_family = AF_UNIX;
+# else /* NETUNIX */
+# if NETINET
+ /* default to AF_INET */
+ addr.sa.sa_family = AF_INET;
+# else /* NETINET */
+# if NETINET6
+ /* default to AF_INET6 */
+ addr.sa.sa_family = AF_INET6;
+# else /* NETINET6 */
+ /* no protocols available */
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): no valid socket protocols available",
+ m->mf_name);
+ milter_error(m, e);
+ return -1;
+# endif /* NETINET6 */
+# endif /* NETINET */
+# endif /* NETUNIX */
+ }
+# if NETUNIX
+ else if (sm_strcasecmp(p, "unix") == 0 ||
+ sm_strcasecmp(p, "local") == 0)
+ addr.sa.sa_family = AF_UNIX;
+# endif /* NETUNIX */
+# if NETINET
+ else if (sm_strcasecmp(p, "inet") == 0)
+ addr.sa.sa_family = AF_INET;
+# endif /* NETINET */
+# if NETINET6
+ else if (sm_strcasecmp(p, "inet6") == 0)
+ addr.sa.sa_family = AF_INET6;
+# endif /* NETINET6 */
+ else
+ {
+# ifdef EPROTONOSUPPORT
+ errno = EPROTONOSUPPORT;
+# else /* EPROTONOSUPPORT */
+ errno = EINVAL;
+# endif /* EPROTONOSUPPORT */
+ if (tTd(64, 5))
+ sm_dprintf("X%s: unknown socket type %s\n",
+ m->mf_name, p);
+ if (parseonly)
+ syserr("X%s: unknown socket type %s",
+ m->mf_name, p);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): unknown socket type %s",
+ m->mf_name, p);
+ milter_error(m, e);
+ return -1;
+ }
+ *colon++ = ':';
+ }
+ else
+ {
+ /* default to AF_UNIX */
+ addr.sa.sa_family = AF_UNIX;
+ colon = p;
+ }
+
+# if NETUNIX
+ if (addr.sa.sa_family == AF_UNIX)
+ {
+ long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_EXECOK;
+
+ at = colon;
+ if (strlen(colon) >= sizeof addr.sunix.sun_path)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: local socket name %s too long\n",
+ m->mf_name, colon);
+ errno = EINVAL;
+ if (parseonly)
+ syserr("X%s: local socket name %s too long",
+ m->mf_name, colon);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): local socket name %s too long",
+ m->mf_name, colon);
+ milter_error(m, e);
+ return -1;
+ }
+ errno = safefile(colon, RunAsUid, RunAsGid, RunAsUserName, sff,
+ S_IRUSR|S_IWUSR, NULL);
+
+ /* if just parsing .cf file, socket doesn't need to exist */
+ if (parseonly && errno == ENOENT)
+ {
+ if (OpMode == MD_DAEMON ||
+ OpMode == MD_FGDAEMON)
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "WARNING: X%s: local socket name %s missing\n",
+ m->mf_name, colon);
+ }
+ else if (errno != 0)
+ {
+ /* if not safe, don't create */
+ save_errno = errno;
+ if (tTd(64, 5))
+ sm_dprintf("X%s: local socket name %s unsafe\n",
+ m->mf_name, colon);
+ errno = save_errno;
+ if (parseonly)
+ {
+ if (OpMode == MD_DAEMON ||
+ OpMode == MD_FGDAEMON ||
+ OpMode == MD_SMTP)
+ syserr("X%s: local socket name %s unsafe",
+ m->mf_name, colon);
+ }
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): local socket name %s unsafe",
+ m->mf_name, colon);
+ milter_error(m, e);
+ return -1;
+ }
+
+ (void) sm_strlcpy(addr.sunix.sun_path, colon,
+ sizeof addr.sunix.sun_path);
+ addrlen = sizeof (struct sockaddr_un);
+ }
+ else
+# endif /* NETUNIX */
+# if NETINET || NETINET6
+ if (false
+# if NETINET
+ || addr.sa.sa_family == AF_INET
+# endif /* NETINET */
+# if NETINET6
+ || addr.sa.sa_family == AF_INET6
+# endif /* NETINET6 */
+ )
+ {
+ unsigned short port;
+
+ /* Parse port@host */
+ at = strchr(colon, '@');
+ if (at == NULL)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: bad address %s (expected port@host)\n",
+ m->mf_name, colon);
+ if (parseonly)
+ syserr("X%s: bad address %s (expected port@host)",
+ m->mf_name, colon);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): bad address %s (expected port@host)",
+ m->mf_name, colon);
+ milter_error(m, e);
+ return -1;
+ }
+ *at = '\0';
+ if (isascii(*colon) && isdigit(*colon))
+ port = htons((unsigned short) atoi(colon));
+ else
+ {
+# ifdef NO_GETSERVBYNAME
+ if (tTd(64, 5))
+ sm_dprintf("X%s: invalid port number %s\n",
+ m->mf_name, colon);
+ if (parseonly)
+ syserr("X%s: invalid port number %s",
+ m->mf_name, colon);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): invalid port number %s",
+ m->mf_name, colon);
+ milter_error(m, e);
+ return -1;
+# else /* NO_GETSERVBYNAME */
+ register struct servent *sp;
+
+ sp = getservbyname(colon, "tcp");
+ if (sp == NULL)
+ {
+ save_errno = errno;
+ if (tTd(64, 5))
+ sm_dprintf("X%s: unknown port name %s\n",
+ m->mf_name, colon);
+ errno = save_errno;
+ if (parseonly)
+ syserr("X%s: unknown port name %s",
+ m->mf_name, colon);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): unknown port name %s",
+ m->mf_name, colon);
+ milter_error(m, e);
+ return -1;
+ }
+ port = sp->s_port;
+# endif /* NO_GETSERVBYNAME */
+ }
+ *at++ = '@';
+ if (*at == '[')
+ {
+ char *end;
+
+ end = strchr(at, ']');
+ if (end != NULL)
+ {
+ bool found = false;
+# if NETINET
+ unsigned long hid = INADDR_NONE;
+# endif /* NETINET */
+# if NETINET6
+ struct sockaddr_in6 hid6;
+# endif /* NETINET6 */
+
+ *end = '\0';
+# if NETINET
+ if (addr.sa.sa_family == AF_INET &&
+ (hid = inet_addr(&at[1])) != INADDR_NONE)
+ {
+ addr.sin.sin_addr.s_addr = hid;
+ addr.sin.sin_port = port;
+ found = true;
+ }
+# endif /* NETINET */
+# if NETINET6
+ (void) memset(&hid6, '\0', sizeof hid6);
+ if (addr.sa.sa_family == AF_INET6 &&
+ anynet_pton(AF_INET6, &at[1],
+ &hid6.sin6_addr) == 1)
+ {
+ addr.sin6.sin6_addr = hid6.sin6_addr;
+ addr.sin6.sin6_port = port;
+ found = true;
+ }
+# endif /* NETINET6 */
+ *end = ']';
+ if (!found)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: Invalid numeric domain spec \"%s\"\n",
+ m->mf_name, at);
+ if (parseonly)
+ syserr("X%s: Invalid numeric domain spec \"%s\"",
+ m->mf_name, at);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): Invalid numeric domain spec \"%s\"",
+ m->mf_name, at);
+ milter_error(m, e);
+ return -1;
+ }
+ }
+ else
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: Invalid numeric domain spec \"%s\"\n",
+ m->mf_name, at);
+ if (parseonly)
+ syserr("X%s: Invalid numeric domain spec \"%s\"",
+ m->mf_name, at);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): Invalid numeric domain spec \"%s\"",
+ m->mf_name, at);
+ milter_error(m, e);
+ return -1;
+ }
+ }
+ else
+ {
+ hp = sm_gethostbyname(at, addr.sa.sa_family);
+ if (hp == NULL)
+ {
+ save_errno = errno;
+ if (tTd(64, 5))
+ sm_dprintf("X%s: Unknown host name %s\n",
+ m->mf_name, at);
+ errno = save_errno;
+ if (parseonly)
+ syserr("X%s: Unknown host name %s",
+ m->mf_name, at);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): Unknown host name %s",
+ m->mf_name, at);
+ milter_error(m, e);
+ return -1;
+ }
+ addr.sa.sa_family = hp->h_addrtype;
+ switch (hp->h_addrtype)
+ {
+# if NETINET
+ case AF_INET:
+ memmove(&addr.sin.sin_addr,
+ hp->h_addr, INADDRSZ);
+ addr.sin.sin_port = port;
+ addrlen = sizeof (struct sockaddr_in);
+ addrno = 1;
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ hp->h_addr, IN6ADDRSZ);
+ addr.sin6.sin6_port = port;
+ addrlen = sizeof (struct sockaddr_in6);
+ addrno = 1;
+ break;
+# endif /* NETINET6 */
+
+ default:
+ if (tTd(64, 5))
+ sm_dprintf("X%s: Unknown protocol for %s (%d)\n",
+ m->mf_name, at,
+ hp->h_addrtype);
+ if (parseonly)
+ syserr("X%s: Unknown protocol for %s (%d)",
+ m->mf_name, at, hp->h_addrtype);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): Unknown protocol for %s (%d)",
+ m->mf_name, at,
+ hp->h_addrtype);
+ milter_error(m, e);
+# if NETINET6
+ freehostent(hp);
+# endif /* NETINET6 */
+ return -1;
+ }
+ }
+ }
+ else
+# endif /* NETINET || NETINET6 */
+ {
+ if (tTd(64, 5))
+ sm_dprintf("X%s: unknown socket protocol\n",
+ m->mf_name);
+ if (parseonly)
+ syserr("X%s: unknown socket protocol", m->mf_name);
+ else if (MilterLogLevel > 10)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): unknown socket protocol",
+ m->mf_name);
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* just parsing through? */
+ if (parseonly)
+ {
+ m->mf_state = SMFS_READY;
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return 0;
+ }
+
+ /* sanity check */
+ if (m->mf_state != SMFS_READY &&
+ m->mf_state != SMFS_CLOSED)
+ {
+ /* shouldn't happen */
+ if (tTd(64, 1))
+ sm_dprintf("Milter (%s): Trying to open filter in state %c\n",
+ m->mf_name, (char) m->mf_state);
+ milter_error(m, e);
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return -1;
+ }
+
+ /* nope, actually connecting */
+ for (;;)
+ {
+ sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+ if (sock < 0)
+ {
+ save_errno = errno;
+ if (tTd(64, 5))
+ sm_dprintf("Milter (%s): error creating socket: %s\n",
+ m->mf_name,
+ sm_errstring(save_errno));
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): error creating socket: %s",
+ m->mf_name, sm_errstring(save_errno));
+ milter_error(m, e);
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return -1;
+ }
+
+ if (setjmp(MilterConnectTimeout) == 0)
+ {
+ SM_EVENT *ev = NULL;
+ int i;
+
+ if (m->mf_timeout[SMFTO_CONNECT] > 0)
+ ev = sm_setevent(m->mf_timeout[SMFTO_CONNECT],
+ milter_connect_timeout, 0);
+
+ i = connect(sock, (struct sockaddr *) &addr, addrlen);
+ save_errno = errno;
+ if (ev != NULL)
+ sm_clrevent(ev);
+ errno = save_errno;
+ if (i >= 0)
+ break;
+ }
+
+ /* couldn't connect.... try next address */
+ save_errno = errno;
+ p = CurHostName;
+ CurHostName = at;
+ if (tTd(64, 5))
+ sm_dprintf("milter_open (%s): open %s failed: %s\n",
+ m->mf_name, at, sm_errstring(save_errno));
+ if (MilterLogLevel > 13)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter (%s): open %s failed: %s",
+ m->mf_name, at, sm_errstring(save_errno));
+ CurHostName = p;
+ (void) close(sock);
+
+ /* try next address */
+ if (hp != NULL && hp->h_addr_list[addrno] != NULL)
+ {
+ switch (addr.sa.sa_family)
+ {
+# if NETINET
+ case AF_INET:
+ memmove(&addr.sin.sin_addr,
+ hp->h_addr_list[addrno++],
+ INADDRSZ);
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ memmove(&addr.sin6.sin6_addr,
+ hp->h_addr_list[addrno++],
+ IN6ADDRSZ);
+ break;
+# endif /* NETINET6 */
+
+ default:
+ if (tTd(64, 5))
+ sm_dprintf("X%s: Unknown protocol for %s (%d)\n",
+ m->mf_name, at,
+ hp->h_addrtype);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): Unknown protocol for %s (%d)",
+ m->mf_name, at,
+ hp->h_addrtype);
+ milter_error(m, e);
+# if NETINET6
+ freehostent(hp);
+# endif /* NETINET6 */
+ return -1;
+ }
+ continue;
+ }
+ p = CurHostName;
+ CurHostName = at;
+ if (tTd(64, 5))
+ sm_dprintf("X%s: error connecting to filter: %s\n",
+ m->mf_name, sm_errstring(save_errno));
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): error connecting to filter: %s",
+ m->mf_name, sm_errstring(save_errno));
+ CurHostName = p;
+ milter_error(m, e);
+# if NETINET6
+ if (hp != NULL)
+ freehostent(hp);
+# endif /* NETINET6 */
+ return -1;
+ }
+ m->mf_state = SMFS_OPEN;
+# if NETINET6
+ if (hp != NULL)
+ {
+ freehostent(hp);
+ hp = NULL;
+ }
+# endif /* NETINET6 */
+ return sock;
+}
+
+static void
+milter_connect_timeout()
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(MilterConnectTimeout, 1);
+}
+/*
+** MILTER_SETUP -- setup structure for a mail filter
+**
+** Parameters:
+** line -- the options line.
+**
+** Returns:
+** none
+*/
+
+void
+milter_setup(line)
+ char *line;
+{
+ char fcode;
+ register char *p;
+ register struct milter *m;
+ STAB *s;
+
+ /* collect the filter name */
+ for (p = line;
+ *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p));
+ p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ if (line[0] == '\0')
+ {
+ syserr("name required for mail filter");
+ return;
+ }
+ m = (struct milter *) xalloc(sizeof *m);
+ memset((char *) m, '\0', sizeof *m);
+ m->mf_name = newstr(line);
+ m->mf_state = SMFS_READY;
+ m->mf_sock = -1;
+ m->mf_timeout[SMFTO_CONNECT] = (time_t) 300;
+ m->mf_timeout[SMFTO_WRITE] = (time_t) 10;
+ m->mf_timeout[SMFTO_READ] = (time_t) 10;
+ m->mf_timeout[SMFTO_EOM] = (time_t) 300;
+
+ /* now scan through and assign info from the fields */
+ while (*p != '\0')
+ {
+ char *delimptr;
+
+ while (*p != '\0' &&
+ (*p == ',' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ /* p now points to field code */
+ fcode = *p;
+ while (*p != '\0' && *p != '=' && *p != ',')
+ p++;
+ if (*p++ != '=')
+ {
+ syserr("X%s: `=' expected", m->mf_name);
+ return;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* p now points to the field body */
+ p = munchstring(p, &delimptr, ',');
+
+ /* install the field into the filter struct */
+ switch (fcode)
+ {
+ case 'S': /* socket */
+ if (p == NULL)
+ m->mf_conn = NULL;
+ else
+ m->mf_conn = newstr(p);
+ break;
+
+ case 'F': /* Milter flags configured on MTA */
+ for (; *p != '\0'; p++)
+ {
+ if (!(isascii(*p) && isspace(*p)))
+ setbitn(bitidx(*p), m->mf_flags);
+ }
+ break;
+
+ case 'T': /* timeouts */
+ milter_parse_timeouts(p, m);
+ break;
+
+ default:
+ syserr("X%s: unknown filter equate %c=",
+ m->mf_name, fcode);
+ break;
+ }
+ p = delimptr;
+ }
+
+ /* early check for errors */
+ (void) milter_open(m, true, CurEnv);
+
+ /* enter the filter into the symbol table */
+ s = stab(m->mf_name, ST_MILTER, ST_ENTER);
+ if (s->s_milter != NULL)
+ syserr("X%s: duplicate filter definition", m->mf_name);
+ else
+ s->s_milter = m;
+}
+/*
+** MILTER_CONFIG -- parse option list into an array and check config
+**
+** Called when reading configuration file.
+**
+** Parameters:
+** spec -- the filter list.
+** list -- the array to fill in.
+** max -- the maximum number of entries in list.
+**
+** Returns:
+** none
+*/
+
+void
+milter_config(spec, list, max)
+ char *spec;
+ struct milter **list;
+ int max;
+{
+ int numitems = 0;
+ register char *p;
+
+ /* leave one for the NULL signifying the end of the list */
+ max--;
+
+ for (p = spec; p != NULL; )
+ {
+ STAB *s;
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ spec = p;
+
+ if (numitems >= max)
+ {
+ syserr("Too many filters defined, %d max", max);
+ if (max > 0)
+ list[0] = NULL;
+ return;
+ }
+#if _FFR_MILTER_PERDAEMON
+ p = strpbrk(p, ";,");
+#else /* _FFR_MILTER_PERDAEMON */
+ p = strpbrk(p, ",");
+#endif /* _FFR_MILTER_PERDAEMON */
+ if (p != NULL)
+ *p++ = '\0';
+
+ s = stab(spec, ST_MILTER, ST_FIND);
+ if (s == NULL)
+ {
+ syserr("InputFilter %s not defined", spec);
+ ExitStat = EX_CONFIG;
+ return;
+ }
+ list[numitems++] = s->s_milter;
+ }
+ list[numitems] = NULL;
+
+ /* if not set, set to LogLevel */
+ if (MilterLogLevel == -1)
+ MilterLogLevel = LogLevel;
+}
+/*
+** MILTER_PARSE_TIMEOUTS -- parse timeout list
+**
+** Called when reading configuration file.
+**
+** Parameters:
+** spec -- the timeout list.
+** m -- milter to set.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_parse_timeouts(spec, m)
+ char *spec;
+ struct milter *m;
+{
+ char fcode;
+ register char *p;
+
+ p = spec;
+
+ /* now scan through and assign info from the fields */
+ while (*p != '\0')
+ {
+ char *delimptr;
+
+ while (*p != '\0' &&
+ (*p == ';' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ /* p now points to field code */
+ fcode = *p;
+ while (*p != '\0' && *p != ':')
+ p++;
+ if (*p++ != ':')
+ {
+ syserr("X%s, T=: `:' expected", m->mf_name);
+ return;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* p now points to the field body */
+ p = munchstring(p, &delimptr, ';');
+
+ /* install the field into the filter struct */
+ switch (fcode)
+ {
+ case 'C':
+ m->mf_timeout[SMFTO_CONNECT] = convtime(p, 's');
+ if (tTd(64, 5))
+ sm_dprintf("X%s: %c=%lu\n",
+ m->mf_name, fcode,
+ (unsigned long) m->mf_timeout[SMFTO_CONNECT]);
+ break;
+
+ case 'S':
+ m->mf_timeout[SMFTO_WRITE] = convtime(p, 's');
+ if (tTd(64, 5))
+ sm_dprintf("X%s: %c=%lu\n",
+ m->mf_name, fcode,
+ (unsigned long) m->mf_timeout[SMFTO_WRITE]);
+ break;
+
+ case 'R':
+ m->mf_timeout[SMFTO_READ] = convtime(p, 's');
+ if (tTd(64, 5))
+ sm_dprintf("X%s: %c=%lu\n",
+ m->mf_name, fcode,
+ (unsigned long) m->mf_timeout[SMFTO_READ]);
+ break;
+
+ case 'E':
+ m->mf_timeout[SMFTO_EOM] = convtime(p, 's');
+ if (tTd(64, 5))
+ sm_dprintf("X%s: %c=%lu\n",
+ m->mf_name, fcode,
+ (unsigned long) m->mf_timeout[SMFTO_EOM]);
+ break;
+
+ default:
+ if (tTd(64, 5))
+ sm_dprintf("X%s: %c unknown\n",
+ m->mf_name, fcode);
+ syserr("X%s: unknown filter timeout %c",
+ m->mf_name, fcode);
+ break;
+ }
+ p = delimptr;
+ }
+}
+/*
+** MILTER_SET_OPTION -- set an individual milter option
+**
+** Parameters:
+** name -- the name of the option.
+** val -- the value of the option.
+** sticky -- if set, don't let other setoptions override
+** this value.
+**
+** Returns:
+** none.
+*/
+
+/* set if Milter sub-option is stuck */
+static BITMAP256 StickyMilterOpt;
+
+static struct milteropt
+{
+ char *mo_name; /* long name of milter option */
+ unsigned char mo_code; /* code for option */
+} MilterOptTab[] =
+{
+# define MO_MACROS_CONNECT 0x01
+ { "macros.connect", MO_MACROS_CONNECT },
+# define MO_MACROS_HELO 0x02
+ { "macros.helo", MO_MACROS_HELO },
+# define MO_MACROS_ENVFROM 0x03
+ { "macros.envfrom", MO_MACROS_ENVFROM },
+# define MO_MACROS_ENVRCPT 0x04
+ { "macros.envrcpt", MO_MACROS_ENVRCPT },
+# define MO_LOGLEVEL 0x05
+ { "loglevel", MO_LOGLEVEL },
+ { NULL, 0 },
+};
+
+void
+milter_set_option(name, val, sticky)
+ char *name;
+ char *val;
+ bool sticky;
+{
+ int nummac = 0;
+ register struct milteropt *mo;
+ char *p;
+ char **macros = NULL;
+
+ if (tTd(37, 2) || tTd(64, 5))
+ sm_dprintf("milter_set_option(%s = %s)", name, val);
+
+ if (name == NULL)
+ {
+ syserr("milter_set_option: invalid Milter option, must specify suboption");
+ return;
+ }
+
+ for (mo = MilterOptTab; mo->mo_name != NULL; mo++)
+ {
+ if (sm_strcasecmp(mo->mo_name, name) == 0)
+ break;
+ }
+
+ if (mo->mo_name == NULL)
+ {
+ syserr("milter_set_option: invalid Milter option %s", name);
+ return;
+ }
+
+ /*
+ ** See if this option is preset for us.
+ */
+
+ if (!sticky && bitnset(mo->mo_code, StickyMilterOpt))
+ {
+ if (tTd(37, 2) || tTd(64,5))
+ sm_dprintf(" (ignored)\n");
+ return;
+ }
+
+ if (tTd(37, 2) || tTd(64,5))
+ sm_dprintf("\n");
+
+ switch (mo->mo_code)
+ {
+ case MO_LOGLEVEL:
+ MilterLogLevel = atoi(val);
+ break;
+
+ case MO_MACROS_CONNECT:
+ if (macros == NULL)
+ macros = MilterConnectMacros;
+ /* FALLTHROUGH */
+
+ case MO_MACROS_HELO:
+ if (macros == NULL)
+ macros = MilterHeloMacros;
+ /* FALLTHROUGH */
+
+ case MO_MACROS_ENVFROM:
+ if (macros == NULL)
+ macros = MilterEnvFromMacros;
+ /* FALLTHROUGH */
+
+ case MO_MACROS_ENVRCPT:
+ if (macros == NULL)
+ macros = MilterEnvRcptMacros;
+
+ p = newstr(val);
+ while (*p != '\0')
+ {
+ char *macro;
+
+ /* Skip leading commas, spaces */
+ while (*p != '\0' &&
+ (*p == ',' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ if (*p == '\0')
+ break;
+
+ /* Find end of macro */
+ macro = p;
+ while (*p != '\0' && *p != ',' &&
+ isascii(*p) && !isspace(*p))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+
+ if (nummac >= MAXFILTERMACROS)
+ {
+ syserr("milter_set_option: too many macros in Milter.%s (max %d)",
+ name, MAXFILTERMACROS);
+ macros[nummac] = NULL;
+ break;
+ }
+ macros[nummac++] = macro;
+ }
+ macros[nummac] = NULL;
+ break;
+
+ default:
+ syserr("milter_set_option: invalid Milter option %s", name);
+ break;
+ }
+ if (sticky)
+ setbitn(mo->mo_code, StickyMilterOpt);
+}
+/*
+** MILTER_REOPEN_DF -- open & truncate the data file (for replbody)
+**
+** Parameters:
+** e -- current envelope.
+**
+** Returns:
+** 0 if succesful, -1 otherwise
+*/
+
+static int
+milter_reopen_df(e)
+ ENVELOPE *e;
+{
+ char dfname[MAXPATHLEN];
+
+ (void) sm_strlcpy(dfname, queuename(e, DATAFL_LETTER), sizeof dfname);
+
+ /*
+ ** In SuperSafe == SAFE_REALLY mode, e->e_dfp is a read-only FP so
+ ** close and reopen writable (later close and reopen
+ ** read only again).
+ **
+ ** In SuperSafe != SAFE_REALLY mode, e->e_dfp still points at the
+ ** buffered file I/O descriptor, still open for writing
+ ** so there isn't as much work to do, just truncate it
+ ** and go.
+ */
+
+ if (SuperSafe == SAFE_REALLY)
+ {
+ /* close read-only data file */
+ if (bitset(EF_HAS_DF, e->e_flags) && e->e_dfp != NULL)
+ {
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_flags &= ~EF_HAS_DF;
+ }
+
+ /* open writable */
+ if ((e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname,
+ SM_IO_RDWR, NULL)) == NULL)
+ {
+ MILTER_DF_ERROR("milter_reopen_df: sm_io_open %s: %s");
+ return -1;
+ }
+ }
+ else if (e->e_dfp == NULL)
+ {
+ /* shouldn't happen */
+ errno = ENOENT;
+ MILTER_DF_ERROR("milter_reopen_df: NULL e_dfp (%s: %s)");
+ return -1;
+ }
+ return 0;
+}
+/*
+** MILTER_RESET_DF -- re-open read-only the data file (for replbody)
+**
+** Parameters:
+** e -- current envelope.
+**
+** Returns:
+** 0 if succesful, -1 otherwise
+*/
+
+static int
+milter_reset_df(e)
+ ENVELOPE *e;
+{
+ int afd;
+ char dfname[MAXPATHLEN];
+
+ (void) sm_strlcpy(dfname, queuename(e, DATAFL_LETTER), sizeof dfname);
+
+ if (sm_io_flush(e->e_dfp, SM_TIME_DEFAULT) != 0 ||
+ sm_io_error(e->e_dfp))
+ {
+ MILTER_DF_ERROR("milter_reset_df: error writing/flushing %s: %s");
+ return -1;
+ }
+ else if (SuperSafe != SAFE_REALLY)
+ {
+ /* skip next few clauses */
+ /* EMPTY */
+ }
+ else if ((afd = sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD, NULL)) >= 0
+ && fsync(afd) < 0)
+ {
+ MILTER_DF_ERROR("milter_reset_df: error sync'ing %s: %s");
+ return -1;
+ }
+ else if (sm_io_close(e->e_dfp, SM_TIME_DEFAULT) < 0)
+ {
+ MILTER_DF_ERROR("milter_reset_df: error closing %s: %s");
+ return -1;
+ }
+ else if ((e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname,
+ SM_IO_RDONLY, NULL)) == NULL)
+ {
+ MILTER_DF_ERROR("milter_reset_df: error reopening %s: %s");
+ return -1;
+ }
+ else
+ e->e_flags |= EF_HAS_DF;
+ return 0;
+}
+/*
+** MILTER_CAN_DELRCPTS -- can any milter filters delete recipients?
+**
+** Parameters:
+** none
+**
+** Returns:
+** true if any filter deletes recipients, false otherwise
+*/
+
+bool
+milter_can_delrcpts()
+{
+ bool can = false;
+ int i;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_can_delrcpts:");
+
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ if (bitset(SMFIF_DELRCPT, m->mf_fflags))
+ {
+ can = true;
+ break;
+ }
+ }
+ if (tTd(64, 10))
+ sm_dprintf("%s\n", can ? "true" : "false");
+
+ return can;
+}
+/*
+** MILTER_QUIT_FILTER -- close down a single filter
+**
+** Parameters:
+** m -- milter structure of filter to close down.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_quit_filter(m, e)
+ struct milter *m;
+ ENVELOPE *e;
+{
+ if (tTd(64, 10))
+ sm_dprintf("milter_quit_filter(%s)\n", m->mf_name);
+ if (MilterLogLevel > 18)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): quit filter",
+ m->mf_name);
+
+ /* Never replace error state */
+ if (m->mf_state == SMFS_ERROR)
+ return;
+
+ if (m->mf_sock < 0 ||
+ m->mf_state == SMFS_CLOSED ||
+ m->mf_state == SMFS_READY)
+ {
+ m->mf_sock = -1;
+ m->mf_state = SMFS_CLOSED;
+ return;
+ }
+
+ (void) milter_write(m, SMFIC_QUIT, (char *) NULL, 0,
+ m->mf_timeout[SMFTO_WRITE], e);
+ if (m->mf_sock >= 0)
+ {
+ (void) close(m->mf_sock);
+ m->mf_sock = -1;
+ }
+ if (m->mf_state != SMFS_ERROR)
+ m->mf_state = SMFS_CLOSED;
+}
+/*
+** MILTER_ABORT_FILTER -- tell filter to abort current message
+**
+** Parameters:
+** m -- milter structure of filter to abort.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_abort_filter(m, e)
+ struct milter *m;
+ ENVELOPE *e;
+{
+ if (tTd(64, 10))
+ sm_dprintf("milter_abort_filter(%s)\n", m->mf_name);
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): abort filter",
+ m->mf_name);
+
+ if (m->mf_sock < 0 ||
+ m->mf_state != SMFS_INMSG)
+ return;
+
+ (void) milter_write(m, SMFIC_ABORT, (char *) NULL, 0,
+ m->mf_timeout[SMFTO_WRITE], e);
+ if (m->mf_state != SMFS_ERROR)
+ m->mf_state = SMFS_DONE;
+}
+/*
+** MILTER_SEND_MACROS -- provide macros to the filters
+**
+** Parameters:
+** m -- milter to send macros to.
+** macros -- macros to send for filter smfi_getsymval().
+** cmd -- which command the macros are associated with.
+** e -- current envelope (for macro access).
+**
+** Returns:
+** none
+*/
+
+static void
+milter_send_macros(m, macros, cmd, e)
+ struct milter *m;
+ char **macros;
+ char cmd;
+ ENVELOPE *e;
+{
+ int i;
+ int mid;
+ char *v;
+ char *buf, *bp;
+ char exp[MAXLINE];
+ ssize_t s;
+
+ /* sanity check */
+ if (macros == NULL || macros[0] == NULL)
+ return;
+
+ /* put together data */
+ s = 1; /* for the command character */
+ for (i = 0; macros[i] != NULL; i++)
+ {
+ mid = macid(macros[i]);
+ if (mid == 0)
+ continue;
+ v = macvalue(mid, e);
+ if (v == NULL)
+ continue;
+ expand(v, exp, sizeof(exp), e);
+ s += strlen(macros[i]) + 1 + strlen(exp) + 1;
+ }
+
+ if (s < 0)
+ return;
+
+ buf = (char *) xalloc(s);
+ bp = buf;
+ *bp++ = cmd;
+ for (i = 0; macros[i] != NULL; i++)
+ {
+ mid = macid(macros[i]);
+ if (mid == 0)
+ continue;
+ v = macvalue(mid, e);
+ if (v == NULL)
+ continue;
+ expand(v, exp, sizeof(exp), e);
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_send_macros(%s, %c): %s=%s\n",
+ m->mf_name, cmd, macros[i], exp);
+
+ (void) sm_strlcpy(bp, macros[i], s - (bp - buf));
+ bp += strlen(bp) + 1;
+ (void) sm_strlcpy(bp, exp, s - (bp - buf));
+ bp += strlen(bp) + 1;
+ }
+ (void) milter_write(m, SMFIC_MACRO, buf, s,
+ m->mf_timeout[SMFTO_WRITE], e);
+ sm_free(buf); /* XXX */
+}
+
+/*
+** MILTER_SEND_COMMAND -- send a command and return the response for a filter
+**
+** Parameters:
+** m -- current milter filter
+** command -- command to send.
+** data -- optional command data.
+** sz -- length of buf.
+** e -- current envelope (for e->e_id).
+** state -- return state word.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+static char *
+milter_send_command(m, command, data, sz, e, state)
+ struct milter *m;
+ char command;
+ void *data;
+ ssize_t sz;
+ ENVELOPE *e;
+ char *state;
+{
+ char rcmd;
+ ssize_t rlen;
+ unsigned long skipflag;
+ char *action;
+ char *defresponse;
+ char *response;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_send_command(%s): cmd %c len %ld\n",
+ m->mf_name, (char) command, (long) sz);
+
+ /* find skip flag and default failure */
+ switch (command)
+ {
+ case SMFIC_CONNECT:
+ skipflag = SMFIP_NOCONNECT;
+ action = "connect";
+ defresponse = "554 Command rejected";
+ break;
+
+ case SMFIC_HELO:
+ skipflag = SMFIP_NOHELO;
+ action = "helo";
+ defresponse = "550 Command rejected";
+ break;
+
+ case SMFIC_MAIL:
+ skipflag = SMFIP_NOMAIL;
+ action = "mail";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+
+ case SMFIC_RCPT:
+ skipflag = SMFIP_NORCPT;
+ action = "rcpt";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+
+ case SMFIC_HEADER:
+ skipflag = SMFIP_NOHDRS;
+ action = "header";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+
+ case SMFIC_BODY:
+ skipflag = SMFIP_NOBODY;
+ action = "body";
+ defresponse = "554 5.7.1 Command rejected";
+ break;
+
+ case SMFIC_EOH:
+ skipflag = SMFIP_NOEOH;
+ action = "eoh";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+
+ case SMFIC_BODYEOB:
+ case SMFIC_OPTNEG:
+ case SMFIC_MACRO:
+ case SMFIC_ABORT:
+ case SMFIC_QUIT:
+ /* NOTE: not handled by milter_send_command() */
+ /* FALLTHROUGH */
+
+ default:
+ skipflag = 0;
+ action = "default";
+ defresponse = "550 5.7.1 Command rejected";
+ break;
+ }
+
+ /* check if filter wants this command */
+ if (skipflag != 0 &&
+ bitset(skipflag, m->mf_pflags))
+ return NULL;
+
+ /* send the command to the filter */
+ (void) milter_write(m, command, data, sz,
+ m->mf_timeout[SMFTO_WRITE], e);
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(return NULL);
+ return NULL;
+ }
+
+ /* get the response from the filter */
+ response = milter_read(m, &rcmd, &rlen,
+ m->mf_timeout[SMFTO_READ], e);
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(return NULL);
+ return NULL;
+ }
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_send_command(%s): returned %c\n",
+ m->mf_name, (char) rcmd);
+
+ switch (rcmd)
+ {
+ case SMFIR_REPLYCODE:
+ MILTER_CHECK_REPLYCODE(defresponse);
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, reject=%s",
+ m->mf_name, action, response);
+ *state = rcmd;
+ break;
+
+ case SMFIR_REJECT:
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, reject",
+ m->mf_name, action);
+ *state = rcmd;
+ break;
+
+ case SMFIR_DISCARD:
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, discard",
+ m->mf_name, action);
+ *state = rcmd;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, tempfail",
+ m->mf_name, action);
+ *state = rcmd;
+ break;
+
+ case SMFIR_ACCEPT:
+ /* this filter is done with message/connection */
+ if (command == SMFIC_HELO ||
+ command == SMFIC_CONNECT)
+ m->mf_state = SMFS_CLOSABLE;
+ else
+ m->mf_state = SMFS_DONE;
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, accepted",
+ m->mf_name, action);
+ break;
+
+ case SMFIR_CONTINUE:
+ /* if MAIL command is ok, filter is in message state */
+ if (command == SMFIC_MAIL)
+ m->mf_state = SMFS_INMSG;
+ if (MilterLogLevel > 12)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, action=%s, continue",
+ m->mf_name, action);
+ break;
+
+ default:
+ /* Invalid response to command */
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_send_command(%s): action=%s returned bogus response %c",
+ m->mf_name, action, rcmd);
+ milter_error(m, e);
+ break;
+ }
+
+ if (*state != SMFIR_REPLYCODE &&
+ response != NULL)
+ {
+ sm_free(response); /* XXX */
+ response = NULL;
+ }
+ return response;
+}
+
+/*
+** MILTER_COMMAND -- send a command and return the response for each filter
+**
+** Parameters:
+** command -- command to send.
+** data -- optional command data.
+** sz -- length of buf.
+** macros -- macros to send for filter smfi_getsymval().
+** e -- current envelope (for macro access).
+** state -- return state word.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+static char *
+milter_command(command, data, sz, macros, e, state)
+ char command;
+ void *data;
+ ssize_t sz;
+ char **macros;
+ ENVELOPE *e;
+ char *state;
+{
+ int i;
+ char *response = NULL;
+ time_t tn = 0;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_command: cmd %c len %ld\n",
+ (char) command, (long) sz);
+
+ *state = SMFIR_CONTINUE;
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ /* previous problem? */
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(continue);
+ break;
+ }
+
+ /* sanity check */
+ if (m->mf_sock < 0 ||
+ (m->mf_state != SMFS_OPEN && m->mf_state != SMFS_INMSG))
+ continue;
+
+ /* send macros (regardless of whether we send command) */
+ if (macros != NULL && macros[0] != NULL)
+ {
+ milter_send_macros(m, macros, command, e);
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(continue);
+ break;
+ }
+ }
+
+ if (MilterLogLevel > 21)
+ tn = curtime();
+
+ response = milter_send_command(m, command, data, sz, e, state);
+
+ if (MilterLogLevel > 21)
+ {
+ /* log the time it took for the command per filter */
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter (%s): time command (%c), %d",
+ m->mf_name, command, (int) (tn - curtime()));
+ }
+
+ if (*state != SMFIR_CONTINUE)
+ break;
+ }
+ return response;
+}
+/*
+** MILTER_NEGOTIATE -- get version and flags from filter
+**
+** Parameters:
+** m -- milter filter structure.
+** e -- current envelope.
+**
+** Returns:
+** 0 on success, -1 otherwise
+*/
+
+static int
+milter_negotiate(m, e)
+ struct milter *m;
+ ENVELOPE *e;
+{
+ char rcmd;
+ mi_int32 fvers;
+ mi_int32 fflags;
+ mi_int32 pflags;
+ char *response;
+ ssize_t rlen;
+ char data[MILTER_OPTLEN];
+
+ /* sanity check */
+ if (m->mf_sock < 0 || m->mf_state != SMFS_OPEN)
+ {
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate, impossible state",
+ m->mf_name);
+ milter_error(m, e);
+ return -1;
+ }
+
+ fvers = htonl(SMFI_VERSION);
+ fflags = htonl(SMFI_CURR_ACTS);
+ pflags = htonl(SMFI_CURR_PROT);
+ (void) memcpy(data, (char *) &fvers, MILTER_LEN_BYTES);
+ (void) memcpy(data + MILTER_LEN_BYTES,
+ (char *) &fflags, MILTER_LEN_BYTES);
+ (void) memcpy(data + (MILTER_LEN_BYTES * 2),
+ (char *) &pflags, MILTER_LEN_BYTES);
+ (void) milter_write(m, SMFIC_OPTNEG, data, sizeof data,
+ m->mf_timeout[SMFTO_WRITE], e);
+
+ if (m->mf_state == SMFS_ERROR)
+ return -1;
+
+ response = milter_read(m, &rcmd, &rlen, m->mf_timeout[SMFTO_READ], e);
+ if (m->mf_state == SMFS_ERROR)
+ return -1;
+
+ if (rcmd != SMFIC_OPTNEG)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): returned %c instead of %c\n",
+ m->mf_name, rcmd, SMFIC_OPTNEG);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: returned %c instead of %c",
+ m->mf_name, rcmd, SMFIC_OPTNEG);
+ if (response != NULL)
+ sm_free(response); /* XXX */
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* Make sure we have enough bytes for the version */
+ if (response == NULL || rlen < MILTER_LEN_BYTES)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): did not return valid info\n",
+ m->mf_name);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: did not return valid info",
+ m->mf_name);
+ if (response != NULL)
+ sm_free(response); /* XXX */
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* extract information */
+ (void) memcpy((char *) &fvers, response, MILTER_LEN_BYTES);
+
+ /* Now make sure we have enough for the feature bitmap */
+ if (rlen != MILTER_OPTLEN)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): did not return enough info\n",
+ m->mf_name);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: did not return enough info",
+ m->mf_name);
+ if (response != NULL)
+ sm_free(response); /* XXX */
+ milter_error(m, e);
+ return -1;
+ }
+
+ (void) memcpy((char *) &fflags, response + MILTER_LEN_BYTES,
+ MILTER_LEN_BYTES);
+ (void) memcpy((char *) &pflags, response + (MILTER_LEN_BYTES * 2),
+ MILTER_LEN_BYTES);
+ sm_free(response); /* XXX */
+ response = NULL;
+
+ m->mf_fvers = ntohl(fvers);
+ m->mf_fflags = ntohl(fflags);
+ m->mf_pflags = ntohl(pflags);
+
+ /* check for version compatibility */
+ if (m->mf_fvers == 1 ||
+ m->mf_fvers > SMFI_VERSION)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): version %d != MTA milter version %d\n",
+ m->mf_name, m->mf_fvers, SMFI_VERSION);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: version %d != MTA milter version %d",
+ m->mf_name, m->mf_fvers, SMFI_VERSION);
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* check for filter feature mismatch */
+ if ((m->mf_fflags & SMFI_CURR_ACTS) != m->mf_fflags)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): filter abilities 0x%x != MTA milter abilities 0x%lx\n",
+ m->mf_name, m->mf_fflags,
+ SMFI_CURR_ACTS);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: filter abilities 0x%x != MTA milter abilities 0x%lx",
+ m->mf_name, m->mf_fflags,
+ (unsigned long) SMFI_CURR_ACTS);
+ milter_error(m, e);
+ return -1;
+ }
+
+ /* check for protocol feature mismatch */
+ if ((m->mf_pflags & SMFI_CURR_PROT) != m->mf_pflags)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): protocol abilities 0x%x != MTA milter abilities 0x%lx\n",
+ m->mf_name, m->mf_pflags,
+ (unsigned long) SMFI_CURR_PROT);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): negotiate: protocol abilities 0x%x != MTA milter abilities 0x%lx",
+ m->mf_name, m->mf_pflags,
+ (unsigned long) SMFI_CURR_PROT);
+ milter_error(m, e);
+ return -1;
+ }
+
+ if (tTd(64, 5))
+ sm_dprintf("milter_negotiate(%s): version %u, fflags 0x%x, pflags 0x%x\n",
+ m->mf_name, m->mf_fvers, m->mf_fflags, m->mf_pflags);
+ return 0;
+}
+/*
+** MILTER_PER_CONNECTION_CHECK -- checks on per-connection commands
+**
+** Reduce code duplication by putting these checks in one place
+**
+** Parameters:
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_per_connection_check(e)
+ ENVELOPE *e;
+{
+ int i;
+
+ /* see if we are done with any of the filters */
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ if (m->mf_state == SMFS_CLOSABLE)
+ milter_quit_filter(m, e);
+ }
+}
+/*
+** MILTER_ERROR -- Put a milter filter into error state
+**
+** Parameters:
+** m -- the broken filter.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_error(m, e)
+ struct milter *m;
+ ENVELOPE *e;
+{
+ /*
+ ** We could send a quit here but
+ ** we may have gotten here due to
+ ** an I/O error so we don't want
+ ** to try to make things worse.
+ */
+
+ if (m->mf_sock >= 0)
+ {
+ (void) close(m->mf_sock);
+ m->mf_sock = -1;
+ }
+ m->mf_state = SMFS_ERROR;
+
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): to error state",
+ m->mf_name);
+}
+/*
+** MILTER_HEADERS -- send headers to a single milter filter
+**
+** Parameters:
+** m -- current filter.
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+static char *
+milter_headers(m, e, state)
+ struct milter *m;
+ ENVELOPE *e;
+ char *state;
+{
+ char *response = NULL;
+ HDR *h;
+
+ if (MilterLogLevel > 17)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): headers, send",
+ m->mf_name);
+
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ char *buf;
+ ssize_t s;
+
+ /* don't send over deleted headers */
+ if (h->h_value == NULL)
+ {
+ /* strip H_USER so not counted in milter_chgheader() */
+ h->h_flags &= ~H_USER;
+ continue;
+ }
+
+ /* skip auto-generated */
+ if (!bitset(H_USER, h->h_flags))
+ continue;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_headers: %s: %s\n",
+ h->h_field, h->h_value);
+ if (MilterLogLevel > 21)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): header, %s",
+ m->mf_name, h->h_field);
+
+ s = strlen(h->h_field) + 1 + strlen(h->h_value) + 1;
+ if (s < 0)
+ continue;
+ buf = (char *) xalloc(s);
+ (void) sm_snprintf(buf, s, "%s%c%s",
+ h->h_field, '\0', h->h_value);
+
+ /* send it over */
+ response = milter_send_command(m, SMFIC_HEADER, buf,
+ s, e, state);
+ sm_free(buf); /* XXX */
+ if (m->mf_state == SMFS_ERROR ||
+ m->mf_state == SMFS_DONE ||
+ *state != SMFIR_CONTINUE)
+ break;
+ }
+ if (MilterLogLevel > 17)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): headers, sent",
+ m->mf_name);
+ return response;
+}
+/*
+** MILTER_BODY -- send the body to a filter
+**
+** Parameters:
+** m -- current filter.
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+static char *
+milter_body(m, e, state)
+ struct milter *m;
+ ENVELOPE *e;
+ char *state;
+{
+ char bufchar = '\0';
+ char prevchar = '\0';
+ int c;
+ char *response = NULL;
+ char *bp;
+ char buf[MILTER_CHUNK_SIZE];
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_body\n");
+
+ if (bfrewind(e->e_dfp) < 0)
+ {
+ ExitStat = EX_IOERR;
+ *state = SMFIR_TEMPFAIL;
+ syserr("milter_body: %s/%cf%s: rewind error",
+ qid_printqueue(e->e_qgrp, e->e_qdir),
+ DATAFL_LETTER, e->e_id);
+ return NULL;
+ }
+
+ if (MilterLogLevel > 17)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): body, send",
+ m->mf_name);
+ bp = buf;
+ while ((c = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT)) != SM_IO_EOF)
+ {
+ /* Change LF to CRLF */
+ if (c == '\n')
+ {
+ /* Not a CRLF already? */
+ if (prevchar != '\r')
+ {
+ /* Room for CR now? */
+ if (bp + 2 > &buf[sizeof buf])
+ {
+ /* No room, buffer LF */
+ bufchar = c;
+
+ /* and send CR now */
+ c = '\r';
+ }
+ else
+ {
+ /* Room to do it now */
+ *bp++ = '\r';
+ prevchar = '\r';
+ }
+ }
+ }
+ *bp++ = (char) c;
+ prevchar = c;
+ if (bp >= &buf[sizeof buf])
+ {
+ /* send chunk */
+ response = milter_send_command(m, SMFIC_BODY, buf,
+ bp - buf, e, state);
+ bp = buf;
+ if (bufchar != '\0')
+ {
+ *bp++ = bufchar;
+ bufchar = '\0';
+ prevchar = bufchar;
+ }
+ }
+ if (m->mf_state == SMFS_ERROR ||
+ m->mf_state == SMFS_DONE ||
+ *state != SMFIR_CONTINUE)
+ break;
+ }
+
+ /* check for read errors */
+ if (sm_io_error(e->e_dfp))
+ {
+ ExitStat = EX_IOERR;
+ if (*state == SMFIR_CONTINUE ||
+ *state == SMFIR_ACCEPT)
+ {
+ *state = SMFIR_TEMPFAIL;
+ if (response != NULL)
+ {
+ sm_free(response); /* XXX */
+ response = NULL;
+ }
+ }
+ syserr("milter_body: %s/%cf%s: read error",
+ qid_printqueue(e->e_qgrp, e->e_qdir),
+ DATAFL_LETTER, e->e_id);
+ return response;
+ }
+
+ /* send last body chunk */
+ if (bp > buf &&
+ m->mf_state != SMFS_ERROR &&
+ m->mf_state != SMFS_DONE &&
+ *state == SMFIR_CONTINUE)
+ {
+ /* send chunk */
+ response = milter_send_command(m, SMFIC_BODY, buf, bp - buf,
+ e, state);
+ bp = buf;
+ }
+ if (MilterLogLevel > 17)
+ sm_syslog(LOG_INFO, e->e_id, "Milter (%s): body, sent",
+ m->mf_name);
+ return response;
+}
+
+/*
+** Actions
+*/
+
+/*
+** MILTER_ADDHEADER -- Add the supplied header to the message
+**
+** Parameters:
+** response -- encoded form of header/value.
+** rlen -- length of response.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_addheader(response, rlen, e)
+ char *response;
+ ssize_t rlen;
+ ENVELOPE *e;
+{
+ char *val;
+ HDR *h;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_addheader: ");
+
+ /* sanity checks */
+ if (response == NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("NULL response\n");
+ return;
+ }
+
+ if (rlen < 2 || strlen(response) + 1 >= (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (total len)\n");
+ return;
+ }
+
+ /* Find separating NUL */
+ val = response + strlen(response) + 1;
+
+ /* another sanity check */
+ if (strlen(response) + strlen(val) + 2 != (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (part len)\n");
+ return;
+ }
+
+ if (*response == '\0')
+ {
+ if (tTd(64, 10))
+ sm_dprintf("empty field name\n");
+ return;
+ }
+
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ if (sm_strcasecmp(h->h_field, response) == 0 &&
+ !bitset(H_USER, h->h_flags) &&
+ !bitset(H_TRACE, h->h_flags))
+ break;
+ }
+
+ /* add to e_msgsize */
+ e->e_msgsize += strlen(response) + 2 + strlen(val);
+
+ if (h != NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("Replace default header %s value with %s\n",
+ h->h_field, val);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter change: default header %s value with %s",
+ h->h_field, val);
+ h->h_value = newstr(val);
+ h->h_flags |= H_USER;
+ }
+ else
+ {
+ if (tTd(64, 10))
+ sm_dprintf("Add %s: %s\n", response, val);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id, "Milter add: header: %s: %s",
+ response, val);
+ addheader(newstr(response), val, H_USER, e);
+ }
+}
+/*
+** MILTER_CHANGEHEADER -- Change the supplied header in the message
+**
+** Parameters:
+** response -- encoded form of header/index/value.
+** rlen -- length of response.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_changeheader(response, rlen, e)
+ char *response;
+ ssize_t rlen;
+ ENVELOPE *e;
+{
+ mi_int32 i, index;
+ char *field, *val;
+ HDR *h, *sysheader;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_changeheader: ");
+
+ /* sanity checks */
+ if (response == NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("NULL response\n");
+ return;
+ }
+
+ if (rlen < 2 || strlen(response) + 1 >= (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (total len)\n");
+ return;
+ }
+
+ /* Find separating NUL */
+ (void) memcpy((char *) &i, response, MILTER_LEN_BYTES);
+ index = ntohl(i);
+ field = response + MILTER_LEN_BYTES;
+ val = field + strlen(field) + 1;
+
+ /* another sanity check */
+ if (MILTER_LEN_BYTES + strlen(field) + 1 +
+ strlen(val) + 1 != (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (part len)\n");
+ return;
+ }
+
+ if (*field == '\0')
+ {
+ if (tTd(64, 10))
+ sm_dprintf("empty field name\n");
+ return;
+ }
+
+ sysheader = NULL;
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ if (sm_strcasecmp(h->h_field, field) == 0)
+ {
+ if (bitset(H_USER, h->h_flags) &&
+ --index <= 0)
+ {
+ sysheader = NULL;
+ break;
+ }
+ else if (!bitset(H_USER, h->h_flags) &&
+ !bitset(H_TRACE, h->h_flags))
+ {
+ /*
+ ** DRUMS msg-fmt draft says can only have
+ ** multiple occurences of trace fields,
+ ** so make sure we replace any non-trace,
+ ** non-user field.
+ */
+
+ sysheader = h;
+ }
+ }
+ }
+
+ /* if not found as user-provided header at index, use sysheader */
+ if (h == NULL)
+ h = sysheader;
+
+ if (h == NULL)
+ {
+ if (*val == '\0')
+ {
+ if (tTd(64, 10))
+ sm_dprintf("Delete (noop) %s:\n", field);
+ }
+ else
+ {
+ /* treat modify value with no existing header as add */
+ if (tTd(64, 10))
+ sm_dprintf("Add %s: %s\n", field, val);
+ addheader(newstr(field), val, H_USER, e);
+ }
+ return;
+ }
+
+ if (tTd(64, 10))
+ {
+ if (*val == '\0')
+ {
+ sm_dprintf("Delete%s %s: %s\n",
+ h == sysheader ? " (default header)" : "",
+ field,
+ h->h_value == NULL ? "<NULL>" : h->h_value);
+ }
+ else
+ {
+ sm_dprintf("Change%s %s: from %s to %s\n",
+ h == sysheader ? " (default header)" : "",
+ field,
+ h->h_value == NULL ? "<NULL>" : h->h_value,
+ val);
+ }
+ }
+
+ if (MilterLogLevel > 8)
+ {
+ if (*val == '\0')
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter delete: header %s %s: %s",
+ h == sysheader ? " (default header)" : "",
+ field,
+ h->h_value == NULL ? "<NULL>" : h->h_value);
+ }
+ else
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter change: header %s %s: from %s to %s",
+ h == sysheader ? " (default header)" : "",
+ field,
+ h->h_value == NULL ? "<NULL>" : h->h_value,
+ val);
+ }
+ }
+
+ if (h != sysheader && h->h_value != NULL)
+ {
+ size_t l;
+
+ l = strlen(h->h_value);
+ if (l > e->e_msgsize)
+ e->e_msgsize = 0;
+ else
+ e->e_msgsize -= l;
+ /* rpool, don't free: sm_free(h->h_value); XXX */
+ }
+
+ if (*val == '\0')
+ {
+ /* Remove "Field: " from message size */
+ if (h != sysheader)
+ {
+ size_t l;
+
+ l = strlen(h->h_field) + 2;
+ if (l > e->e_msgsize)
+ e->e_msgsize = 0;
+ else
+ e->e_msgsize -= l;
+ }
+ h->h_value = NULL;
+ }
+ else
+ {
+ h->h_value = newstr(val);
+ h->h_flags |= H_USER;
+ e->e_msgsize += strlen(h->h_value);
+ }
+}
+/*
+** MILTER_ADDRCPT -- Add the supplied recipient to the message
+**
+** Parameters:
+** response -- encoded form of recipient address.
+** rlen -- length of response.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_addrcpt(response, rlen, e)
+ char *response;
+ ssize_t rlen;
+ ENVELOPE *e;
+{
+ if (tTd(64, 10))
+ sm_dprintf("milter_addrcpt: ");
+
+ /* sanity checks */
+ if (response == NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("NULL response\n");
+ return;
+ }
+
+ if (*response == '\0' ||
+ strlen(response) + 1 != (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (total len %d != rlen %d)\n",
+ (int) strlen(response), (int) (rlen - 1));
+ return;
+ }
+
+ if (tTd(64, 10))
+ sm_dprintf("%s\n", response);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id, "Milter add: rcpt: %s", response);
+ (void) sendtolist(response, NULLADDR, &e->e_sendqueue, 0, e);
+ return;
+}
+/*
+** MILTER_DELRCPT -- Delete the supplied recipient from the message
+**
+** Parameters:
+** response -- encoded form of recipient address.
+** rlen -- length of response.
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+static void
+milter_delrcpt(response, rlen, e)
+ char *response;
+ ssize_t rlen;
+ ENVELOPE *e;
+{
+ if (tTd(64, 10))
+ sm_dprintf("milter_delrcpt: ");
+
+ /* sanity checks */
+ if (response == NULL)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("NULL response\n");
+ return;
+ }
+
+ if (*response == '\0' ||
+ strlen(response) + 1 != (size_t) rlen)
+ {
+ if (tTd(64, 10))
+ sm_dprintf("didn't follow protocol (total len)\n");
+ return;
+ }
+
+ if (tTd(64, 10))
+ sm_dprintf("%s\n", response);
+ if (MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id, "Milter delete: rcpt %s",
+ response);
+ (void) removefromlist(response, &e->e_sendqueue, e);
+ return;
+}
+/*
+** MILTER_REPLBODY -- Replace the current data file with new body
+**
+** Parameters:
+** response -- encoded form of new body.
+** rlen -- length of response.
+** newfilter -- if first time called by a new filter
+** e -- current envelope.
+**
+** Returns:
+** 0 upon success, -1 upon failure
+*/
+
+static int
+milter_replbody(response, rlen, newfilter, e)
+ char *response;
+ ssize_t rlen;
+ bool newfilter;
+ ENVELOPE *e;
+{
+ static char prevchar;
+ int i;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_replbody\n");
+
+ /* If a new filter, reset previous character and truncate data file */
+ if (newfilter)
+ {
+ off_t prevsize;
+ char dfname[MAXPATHLEN];
+
+ (void) sm_strlcpy(dfname, queuename(e, DATAFL_LETTER),
+ sizeof dfname);
+
+ /* Reset prevchar */
+ prevchar = '\0';
+
+ /* Get the current data file information */
+ prevsize = sm_io_getinfo(e->e_dfp, SM_IO_WHAT_SIZE, NULL);
+ if (prevsize < 0)
+ prevsize = 0;
+
+ /* truncate current data file */
+ if (sm_io_getinfo(e->e_dfp, SM_IO_WHAT_ISTYPE, BF_FILE_TYPE))
+ {
+ if (sm_io_setinfo(e->e_dfp, SM_BF_TRUNCATE, NULL) < 0)
+ {
+ MILTER_DF_ERROR("milter_replbody: sm_io truncate %s: %s");
+ return -1;
+ }
+ }
+ else
+ {
+ int err;
+
+ err = sm_io_error(e->e_dfp);
+ (void) sm_io_flush(e->e_dfp, SM_TIME_DEFAULT);
+
+ /*
+ ** Clear error if tried to fflush()
+ ** a read-only file pointer and
+ ** there wasn't a previous error.
+ */
+
+ if (err == 0)
+ sm_io_clearerr(e->e_dfp);
+
+ /* errno is set implicitly by fseek() before return */
+ err = sm_io_seek(e->e_dfp, SM_TIME_DEFAULT,
+ 0, SEEK_SET);
+ if (err < 0)
+ {
+ MILTER_DF_ERROR("milter_replbody: sm_io_seek %s: %s");
+ return -1;
+ }
+# if NOFTRUNCATE
+ /* XXX: Not much we can do except rewind it */
+ errno = EINVAL;
+ MILTER_DF_ERROR("milter_replbody: ftruncate not available on this platform (%s:%s)");
+ return -1;
+# else /* NOFTRUNCATE */
+ err = ftruncate(sm_io_getinfo(e->e_dfp,
+ SM_IO_WHAT_FD, NULL),
+ 0);
+ if (err < 0)
+ {
+ MILTER_DF_ERROR("milter_replbody: sm_io ftruncate %s: %s");
+ return -1;
+ }
+# endif /* NOFTRUNCATE */
+ }
+
+ if (prevsize > e->e_msgsize)
+ e->e_msgsize = 0;
+ else
+ e->e_msgsize -= prevsize;
+ }
+
+ if (newfilter && MilterLogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id, "Milter message: body replaced");
+
+ if (response == NULL)
+ {
+ /* Flush the buffered '\r' */
+ if (prevchar == '\r')
+ {
+ (void) sm_io_putc(e->e_dfp, SM_TIME_DEFAULT, prevchar);
+ e->e_msgsize++;
+ }
+ return 0;
+ }
+
+ for (i = 0; i < rlen; i++)
+ {
+ /* Buffered char from last chunk */
+ if (i == 0 && prevchar == '\r')
+ {
+ /* Not CRLF, output prevchar */
+ if (response[i] != '\n')
+ {
+ (void) sm_io_putc(e->e_dfp, SM_TIME_DEFAULT,
+ prevchar);
+ e->e_msgsize++;
+ }
+ prevchar = '\0';
+ }
+
+ /* Turn CRLF into LF */
+ if (response[i] == '\r')
+ {
+ /* check if at end of chunk */
+ if (i + 1 < rlen)
+ {
+ /* If LF, strip CR */
+ if (response[i + 1] == '\n')
+ i++;
+ }
+ else
+ {
+ /* check next chunk */
+ prevchar = '\r';
+ continue;
+ }
+ }
+ (void) sm_io_putc(e->e_dfp, SM_TIME_DEFAULT, response[i]);
+ e->e_msgsize++;
+ }
+ return 0;
+}
+
+/*
+** MTA callouts
+*/
+
+/*
+** MILTER_INIT -- open and negotiate with all of the filters
+**
+** Parameters:
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** true iff at least one filter is active
+*/
+
+/* ARGSUSED */
+bool
+milter_init(e, state)
+ ENVELOPE *e;
+ char *state;
+{
+ int i;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_init\n");
+
+ *state = SMFIR_CONTINUE;
+ if (InputFilters[0] == NULL)
+ {
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: no active filter");
+ return false;
+ }
+
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ m->mf_sock = milter_open(m, false, e);
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(continue);
+ break;
+ }
+
+ if (m->mf_sock < 0 ||
+ milter_negotiate(m, e) < 0 ||
+ m->mf_state == SMFS_ERROR)
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_init(%s): failed to %s\n",
+ m->mf_name,
+ m->mf_sock < 0 ? "open" :
+ "negotiate");
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "Milter (%s): init failed to %s",
+ m->mf_name,
+ m->mf_sock < 0 ? "open" :
+ "negotiate");
+
+ /* if negotation failure, close socket */
+ milter_error(m, e);
+ MILTER_CHECK_ERROR(continue);
+ }
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter (%s): init success to %s",
+ m->mf_name,
+ m->mf_sock < 0 ? "open" : "negotiate");
+ }
+
+ /*
+ ** If something temp/perm failed with one of the filters,
+ ** we won't be using any of them, so clear any existing
+ ** connections.
+ */
+
+ if (*state != SMFIR_CONTINUE)
+ milter_quit(e);
+
+ return true;
+}
+/*
+** MILTER_CONNECT -- send connection info to milter filters
+**
+** Parameters:
+** hostname -- hostname of remote machine.
+** addr -- address of remote machine.
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+char *
+milter_connect(hostname, addr, e, state)
+ char *hostname;
+ SOCKADDR addr;
+ ENVELOPE *e;
+ char *state;
+{
+ char family;
+ unsigned short port;
+ char *buf, *bp;
+ char *response;
+ char *sockinfo = NULL;
+ ssize_t s;
+# if NETINET6
+ char buf6[INET6_ADDRSTRLEN];
+# endif /* NETINET6 */
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_connect(%s)\n", hostname);
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: connect to filters");
+
+ /* gather data */
+ switch (addr.sa.sa_family)
+ {
+# if NETUNIX
+ case AF_UNIX:
+ family = SMFIA_UNIX;
+ port = htons(0);
+ sockinfo = addr.sunix.sun_path;
+ break;
+# endif /* NETUNIX */
+
+# if NETINET
+ case AF_INET:
+ family = SMFIA_INET;
+ port = addr.sin.sin_port;
+ sockinfo = (char *) inet_ntoa(addr.sin.sin_addr);
+ break;
+# endif /* NETINET */
+
+# if NETINET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_V4MAPPED(&addr.sin6.sin6_addr))
+ family = SMFIA_INET;
+ else
+ family = SMFIA_INET6;
+ port = addr.sin6.sin6_port;
+ sockinfo = anynet_ntop(&addr.sin6.sin6_addr, buf6,
+ sizeof buf6);
+ if (sockinfo == NULL)
+ sockinfo = "";
+ break;
+# endif /* NETINET6 */
+
+ default:
+ family = SMFIA_UNKNOWN;
+ break;
+ }
+
+ s = strlen(hostname) + 1 + sizeof(family);
+ if (family != SMFIA_UNKNOWN)
+ s += sizeof(port) + strlen(sockinfo) + 1;
+
+ buf = (char *) xalloc(s);
+ bp = buf;
+
+ /* put together data */
+ (void) memcpy(bp, hostname, strlen(hostname));
+ bp += strlen(hostname);
+ *bp++ = '\0';
+ (void) memcpy(bp, &family, sizeof family);
+ bp += sizeof family;
+ if (family != SMFIA_UNKNOWN)
+ {
+ (void) memcpy(bp, &port, sizeof port);
+ bp += sizeof port;
+
+ /* include trailing '\0' */
+ (void) memcpy(bp, sockinfo, strlen(sockinfo) + 1);
+ }
+
+ response = milter_command(SMFIC_CONNECT, buf, s,
+ MilterConnectMacros, e, state);
+ sm_free(buf); /* XXX */
+
+ /*
+ ** If this message connection is done for,
+ ** close the filters.
+ */
+
+ if (*state != SMFIR_CONTINUE)
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: connect, ending");
+ milter_quit(e);
+ }
+ else
+ milter_per_connection_check(e);
+
+ /*
+ ** SMFIR_REPLYCODE can't work with connect due to
+ ** the requirements of SMTP. Therefore, ignore the
+ ** reply code text but keep the state it would reflect.
+ */
+
+ if (*state == SMFIR_REPLYCODE)
+ {
+ if (response != NULL &&
+ *response == '4')
+ *state = SMFIR_TEMPFAIL;
+ else
+ *state = SMFIR_REJECT;
+ if (response != NULL)
+ {
+ sm_free(response); /* XXX */
+ response = NULL;
+ }
+ }
+ return response;
+}
+/*
+** MILTER_HELO -- send SMTP HELO/EHLO command info to milter filters
+**
+** Parameters:
+** helo -- argument to SMTP HELO/EHLO command.
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+char *
+milter_helo(helo, e, state)
+ char *helo;
+ ENVELOPE *e;
+ char *state;
+{
+ int i;
+ char *response;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_helo(%s)\n", helo);
+
+ /* HELO/EHLO can come at any point */
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ switch (m->mf_state)
+ {
+ case SMFS_INMSG:
+ /* abort in message filters */
+ milter_abort_filter(m, e);
+ /* FALLTHROUGH */
+
+ case SMFS_DONE:
+ /* reset done filters */
+ m->mf_state = SMFS_OPEN;
+ break;
+ }
+ }
+
+ response = milter_command(SMFIC_HELO, helo, strlen(helo) + 1,
+ MilterHeloMacros, e, state);
+ milter_per_connection_check(e);
+ return response;
+}
+/*
+** MILTER_ENVFROM -- send SMTP MAIL command info to milter filters
+**
+** Parameters:
+** args -- SMTP MAIL command args (args[0] == sender).
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+char *
+milter_envfrom(args, e, state)
+ char **args;
+ ENVELOPE *e;
+ char *state;
+{
+ int i;
+ char *buf, *bp;
+ char *response;
+ ssize_t s;
+
+ if (tTd(64, 10))
+ {
+ sm_dprintf("milter_envfrom:");
+ for (i = 0; args[i] != NULL; i++)
+ sm_dprintf(" %s", args[i]);
+ sm_dprintf("\n");
+ }
+
+ /* sanity check */
+ if (args[0] == NULL)
+ {
+ *state = SMFIR_REJECT;
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: reject, no sender");
+ return NULL;
+ }
+
+ /* new message, so ... */
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ switch (m->mf_state)
+ {
+ case SMFS_INMSG:
+ /* abort in message filters */
+ milter_abort_filter(m, e);
+ /* FALLTHROUGH */
+
+ case SMFS_DONE:
+ /* reset done filters */
+ m->mf_state = SMFS_OPEN;
+ break;
+ }
+ }
+
+ /* put together data */
+ s = 0;
+ for (i = 0; args[i] != NULL; i++)
+ s += strlen(args[i]) + 1;
+
+ if (s < 0)
+ {
+ *state = SMFIR_TEMPFAIL;
+ return NULL;
+ }
+
+ buf = (char *) xalloc(s);
+ bp = buf;
+ for (i = 0; args[i] != NULL; i++)
+ {
+ (void) sm_strlcpy(bp, args[i], s - (bp - buf));
+ bp += strlen(bp) + 1;
+ }
+
+ if (MilterLogLevel > 14)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: senders: %s", buf);
+
+ /* send it over */
+ response = milter_command(SMFIC_MAIL, buf, s,
+ MilterEnvFromMacros, e, state);
+ sm_free(buf); /* XXX */
+
+ /*
+ ** If filter rejects/discards a per message command,
+ ** abort the other filters since we are done with the
+ ** current message.
+ */
+
+ MILTER_CHECK_DONE_MSG();
+ if (MilterLogLevel > 10 && *state == SMFIR_REJECT)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: reject, senders");
+ return response;
+}
+/*
+** MILTER_ENVRCPT -- send SMTP RCPT command info to milter filters
+**
+** Parameters:
+** args -- SMTP MAIL command args (args[0] == recipient).
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+*/
+
+char *
+milter_envrcpt(args, e, state)
+ char **args;
+ ENVELOPE *e;
+ char *state;
+{
+ int i;
+ char *buf, *bp;
+ char *response;
+ ssize_t s;
+
+ if (tTd(64, 10))
+ {
+ sm_dprintf("milter_envrcpt:");
+ for (i = 0; args[i] != NULL; i++)
+ sm_dprintf(" %s", args[i]);
+ sm_dprintf("\n");
+ }
+
+ /* sanity check */
+ if (args[0] == NULL)
+ {
+ *state = SMFIR_REJECT;
+ if (MilterLogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: reject, no rcpt");
+ return NULL;
+ }
+
+ /* put together data */
+ s = 0;
+ for (i = 0; args[i] != NULL; i++)
+ s += strlen(args[i]) + 1;
+
+ if (s < 0)
+ {
+ *state = SMFIR_TEMPFAIL;
+ return NULL;
+ }
+
+ buf = (char *) xalloc(s);
+ bp = buf;
+ for (i = 0; args[i] != NULL; i++)
+ {
+ (void) sm_strlcpy(bp, args[i], s - (bp - buf));
+ bp += strlen(bp) + 1;
+ }
+
+ if (MilterLogLevel > 14)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: rcpts: %s", buf);
+
+ /* send it over */
+ response = milter_command(SMFIC_RCPT, buf, s,
+ MilterEnvRcptMacros, e, state);
+ sm_free(buf); /* XXX */
+ return response;
+}
+/*
+** MILTER_DATA -- send message headers/body and gather final message results
+**
+** Parameters:
+** e -- current envelope.
+** state -- return state from response.
+**
+** Returns:
+** response string (may be NULL)
+**
+** Side effects:
+** - Uses e->e_dfp for access to the body
+** - Can call the various milter action routines to
+** modify the envelope or message.
+*/
+
+# define MILTER_CHECK_RESULTS() \
+ if (*state == SMFIR_ACCEPT || \
+ m->mf_state == SMFS_DONE || \
+ m->mf_state == SMFS_ERROR) \
+ { \
+ if (m->mf_state != SMFS_ERROR) \
+ m->mf_state = SMFS_DONE; \
+ continue; /* to next filter */ \
+ } \
+ if (*state != SMFIR_CONTINUE) \
+ { \
+ m->mf_state = SMFS_DONE; \
+ goto finishup; \
+ }
+
+char *
+milter_data(e, state)
+ ENVELOPE *e;
+ char *state;
+{
+ bool replbody = false; /* milter_replbody() called? */
+ bool replfailed = false; /* milter_replbody() failed? */
+ bool rewind = false; /* rewind data file? */
+ bool dfopen = false; /* data file open for writing? */
+ bool newfilter; /* reset on each new filter */
+ char rcmd;
+ int i;
+ int save_errno;
+ char *response = NULL;
+ time_t eomsent;
+ ssize_t rlen;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_data\n");
+
+ *state = SMFIR_CONTINUE;
+
+ /*
+ ** XXX: Should actually send body chunks to each filter
+ ** a chunk at a time instead of sending the whole body to
+ ** each filter in turn. However, only if the filters don't
+ ** change the body.
+ */
+
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ if (*state != SMFIR_CONTINUE &&
+ *state != SMFIR_ACCEPT)
+ {
+ /*
+ ** A previous filter has dealt with the message,
+ ** safe to stop processing the filters.
+ */
+
+ break;
+ }
+
+ /* Now reset state for later evaluation */
+ *state = SMFIR_CONTINUE;
+ newfilter = true;
+
+ /* previous problem? */
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(continue);
+ break;
+ }
+
+ /* sanity checks */
+ if (m->mf_sock < 0 ||
+ (m->mf_state != SMFS_OPEN && m->mf_state != SMFS_INMSG))
+ continue;
+
+ m->mf_state = SMFS_INMSG;
+
+ /* check if filter wants the headers */
+ if (!bitset(SMFIP_NOHDRS, m->mf_pflags))
+ {
+ response = milter_headers(m, e, state);
+ MILTER_CHECK_RESULTS();
+ }
+
+ /* check if filter wants EOH */
+ if (!bitset(SMFIP_NOEOH, m->mf_pflags))
+ {
+ if (tTd(64, 10))
+ sm_dprintf("milter_data: eoh\n");
+
+ /* send it over */
+ response = milter_send_command(m, SMFIC_EOH, NULL, 0,
+ e, state);
+ MILTER_CHECK_RESULTS();
+ }
+
+ /* check if filter wants the body */
+ if (!bitset(SMFIP_NOBODY, m->mf_pflags) &&
+ e->e_dfp != NULL)
+ {
+ rewind = true;
+ response = milter_body(m, e, state);
+ MILTER_CHECK_RESULTS();
+ }
+
+ /* send the final body chunk */
+ (void) milter_write(m, SMFIC_BODYEOB, NULL, 0,
+ m->mf_timeout[SMFTO_WRITE], e);
+
+ /* Get time EOM sent for timeout */
+ eomsent = curtime();
+
+ /* deal with the possibility of multiple responses */
+ while (*state == SMFIR_CONTINUE)
+ {
+ /* Check total timeout from EOM to final ACK/NAK */
+ if (m->mf_timeout[SMFTO_EOM] > 0 &&
+ curtime() - eomsent >= m->mf_timeout[SMFTO_EOM])
+ {
+ if (tTd(64, 5))
+ sm_dprintf("milter_data(%s): EOM ACK/NAK timeout\n",
+ m->mf_name);
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_data(%s): EOM ACK/NAK timeout",
+ m->mf_name);
+ milter_error(m, e);
+ MILTER_CHECK_ERROR(break);
+ break;
+ }
+
+ response = milter_read(m, &rcmd, &rlen,
+ m->mf_timeout[SMFTO_READ], e);
+ if (m->mf_state == SMFS_ERROR)
+ break;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_data(%s): state %c\n",
+ m->mf_name, (char) rcmd);
+
+ switch (rcmd)
+ {
+ case SMFIR_REPLYCODE:
+ MILTER_CHECK_REPLYCODE("554 5.7.1 Command rejected");
+ if (MilterLogLevel > 12)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, reject=%s",
+ m->mf_name, response);
+ *state = rcmd;
+ m->mf_state = SMFS_DONE;
+ break;
+
+ case SMFIR_REJECT: /* log msg at end of function */
+ if (MilterLogLevel > 12)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, reject",
+ m->mf_name);
+ *state = rcmd;
+ m->mf_state = SMFS_DONE;
+ break;
+
+ case SMFIR_DISCARD:
+ if (MilterLogLevel > 12)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, discard",
+ m->mf_name);
+ *state = rcmd;
+ m->mf_state = SMFS_DONE;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 12)
+ sm_syslog(LOG_INFO, e->e_id, "milter=%s, tempfail",
+ m->mf_name);
+ *state = rcmd;
+ m->mf_state = SMFS_DONE;
+ break;
+
+ case SMFIR_CONTINUE:
+ case SMFIR_ACCEPT:
+ /* this filter is done with message */
+ if (replfailed)
+ *state = SMFIR_TEMPFAIL;
+ else
+ *state = SMFIR_ACCEPT;
+ m->mf_state = SMFS_DONE;
+ break;
+
+ case SMFIR_PROGRESS:
+ break;
+
+# if _FFR_QUARANTINE
+ case SMFIR_QUARANTINE:
+ if (!bitset(SMFIF_QUARANTINE, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s): lied about quarantining, honoring request anyway",
+ m->mf_name);
+ }
+ if (response == NULL)
+ response = newstr("");
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "milter=%s, quarantine=%s",
+ m->mf_name, response);
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool,
+ response);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ break;
+# endif /* _FFR_QUARANTINE */
+
+ case SMFIR_ADDHEADER:
+ if (!bitset(SMFIF_ADDHDRS, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s): lied about adding headers, honoring request anyway",
+ m->mf_name);
+ }
+ milter_addheader(response, rlen, e);
+ break;
+
+ case SMFIR_CHGHEADER:
+ if (!bitset(SMFIF_CHGHDRS, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s): lied about changing headers, honoring request anyway",
+ m->mf_name);
+ }
+ milter_changeheader(response, rlen, e);
+ break;
+
+ case SMFIR_ADDRCPT:
+ if (!bitset(SMFIF_ADDRCPT, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s) lied about adding recipients, honoring request anyway",
+ m->mf_name);
+ }
+ milter_addrcpt(response, rlen, e);
+ break;
+
+ case SMFIR_DELRCPT:
+ if (!bitset(SMFIF_DELRCPT, m->mf_fflags))
+ {
+ if (MilterLogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "milter_data(%s): lied about removing recipients, honoring request anyway",
+ m->mf_name);
+ }
+ milter_delrcpt(response, rlen, e);
+ break;
+
+ case SMFIR_REPLBODY:
+ if (!bitset(SMFIF_MODBODY, m->mf_fflags))
+ {
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_data(%s): lied about replacing body, rejecting request and tempfailing message",
+ m->mf_name);
+ replfailed = true;
+ break;
+ }
+
+ /* already failed in attempt */
+ if (replfailed)
+ break;
+
+ if (!dfopen)
+ {
+ if (milter_reopen_df(e) < 0)
+ {
+ replfailed = true;
+ break;
+ }
+ dfopen = true;
+ rewind = true;
+ }
+
+ if (milter_replbody(response, rlen,
+ newfilter, e) < 0)
+ replfailed = true;
+ newfilter = false;
+ replbody = true;
+ break;
+
+ default:
+ /* Invalid response to command */
+ if (MilterLogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "milter_data(%s): returned bogus response %c",
+ m->mf_name, rcmd);
+ milter_error(m, e);
+ break;
+ }
+ if (rcmd != SMFIR_REPLYCODE && response != NULL)
+ {
+ sm_free(response); /* XXX */
+ response = NULL;
+ }
+
+ if (m->mf_state == SMFS_ERROR)
+ break;
+ }
+
+ if (replbody && !replfailed)
+ {
+ /* flush possible buffered character */
+ milter_replbody(NULL, 0, !replbody, e);
+ replbody = false;
+ }
+
+ if (m->mf_state == SMFS_ERROR)
+ {
+ MILTER_CHECK_ERROR(continue);
+ goto finishup;
+ }
+ }
+
+finishup:
+ /* leave things in the expected state if we touched it */
+ if (replfailed)
+ {
+ if (*state == SMFIR_CONTINUE ||
+ *state == SMFIR_ACCEPT)
+ {
+ *state = SMFIR_TEMPFAIL;
+ SM_FREE_CLR(response);
+ }
+
+ if (dfopen)
+ {
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_dfp = NULL;
+ e->e_flags &= ~EF_HAS_DF;
+ dfopen = false;
+ }
+ rewind = false;
+ }
+
+ if ((dfopen && milter_reset_df(e) < 0) ||
+ (rewind && bfrewind(e->e_dfp) < 0))
+ {
+ save_errno = errno;
+ ExitStat = EX_IOERR;
+
+ /*
+ ** If filter told us to keep message but we had
+ ** an error, we can't really keep it, tempfail it.
+ */
+
+ if (*state == SMFIR_CONTINUE ||
+ *state == SMFIR_ACCEPT)
+ {
+ *state = SMFIR_TEMPFAIL;
+ SM_FREE_CLR(response);
+ }
+
+ errno = save_errno;
+ syserr("milter_data: %s/%cf%s: read error",
+ qid_printqueue(e->e_qgrp, e->e_qdir),
+ DATAFL_LETTER, e->e_id);
+ }
+
+ MILTER_CHECK_DONE_MSG();
+ if (MilterLogLevel > 10 && *state == SMFIR_REJECT)
+ sm_syslog(LOG_INFO, e->e_id, "Milter: reject, data");
+ return response;
+}
+/*
+** MILTER_QUIT -- informs the filter(s) we are done and closes connection(s)
+**
+** Parameters:
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+void
+milter_quit(e)
+ ENVELOPE *e;
+{
+ int i;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_quit(%s)\n", e->e_id);
+
+ for (i = 0; InputFilters[i] != NULL; i++)
+ milter_quit_filter(InputFilters[i], e);
+}
+/*
+** MILTER_ABORT -- informs the filter(s) that we are aborting current message
+**
+** Parameters:
+** e -- current envelope.
+**
+** Returns:
+** none
+*/
+
+void
+milter_abort(e)
+ ENVELOPE *e;
+{
+ int i;
+
+ if (tTd(64, 10))
+ sm_dprintf("milter_abort\n");
+
+ for (i = 0; InputFilters[i] != NULL; i++)
+ {
+ struct milter *m = InputFilters[i];
+
+ /* sanity checks */
+ if (m->mf_sock < 0 || m->mf_state != SMFS_INMSG)
+ continue;
+
+ milter_abort_filter(m, e);
+ }
+}
+#endif /* MILTER */
diff --git a/contrib/sendmail/src/mime.c b/contrib/sendmail/src/mime.c
new file mode 100644
index 0000000..2fd36d2
--- /dev/null
+++ b/contrib/sendmail/src/mime.c
@@ -0,0 +1,1261 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1994, 1996-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+#include <string.h>
+
+SM_RCSID("@(#)$Id: mime.c,v 8.130 2002/05/21 03:39:34 ca Exp $")
+
+/*
+** MIME support.
+**
+** I am indebted to John Beck of Hewlett-Packard, who contributed
+** his code to me for inclusion. As it turns out, I did not use
+** his code since he used a "minimum change" approach that used
+** several temp files, and I wanted a "minimum impact" approach
+** that would avoid copying. However, looking over his code
+** helped me cement my understanding of the problem.
+**
+** I also looked at, but did not directly use, Nathaniel
+** Borenstein's "code.c" module. Again, it functioned as
+** a file-to-file translator, which did not fit within my
+** design bounds, but it was a useful base for understanding
+** the problem.
+*/
+
+#if MIME8TO7
+static int isboundary __P((char *, char **));
+static int mimeboundary __P((char *, char **));
+static int mime_getchar __P((SM_FILE_T *, char **, int *));
+static int mime_getchar_crlf __P((SM_FILE_T *, char **, int *));
+
+/* character set for hex and base64 encoding */
+static char Base16Code[] = "0123456789ABCDEF";
+static char Base64Code[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/* types of MIME boundaries */
+# define MBT_SYNTAX 0 /* syntax error */
+# define MBT_NOTSEP 1 /* not a boundary */
+# define MBT_INTERMED 2 /* intermediate boundary (no trailing --) */
+# define MBT_FINAL 3 /* final boundary (trailing -- included) */
+
+static char *MimeBoundaryNames[] =
+{
+ "SYNTAX", "NOTSEP", "INTERMED", "FINAL"
+};
+
+static bool MapNLtoCRLF;
+
+/*
+** MIME8TO7 -- output 8 bit body in 7 bit format
+**
+** The header has already been output -- this has to do the
+** 8 to 7 bit conversion. It would be easy if we didn't have
+** to deal with nested formats (multipart/xxx and message/rfc822).
+**
+** We won't be called if we don't have to do a conversion, and
+** appropriate MIME-Version: and Content-Type: fields have been
+** output. Any Content-Transfer-Encoding: field has not been
+** output, and we can add it here.
+**
+** Parameters:
+** mci -- mailer connection information.
+** header -- the header for this body part.
+** e -- envelope.
+** boundaries -- the currently pending message boundaries.
+** NULL if we are processing the outer portion.
+** flags -- to tweak processing.
+**
+** Returns:
+** An indicator of what terminated the message part:
+** MBT_FINAL -- the final boundary
+** MBT_INTERMED -- an intermediate boundary
+** MBT_NOTSEP -- an end of file
+*/
+
+struct args
+{
+ char *a_field; /* name of field */
+ char *a_value; /* value of that field */
+};
+
+int
+mime8to7(mci, header, e, boundaries, flags)
+ register MCI *mci;
+ HDR *header;
+ register ENVELOPE *e;
+ char **boundaries;
+ int flags;
+{
+ register char *p;
+ int linelen;
+ int bt;
+ off_t offset;
+ size_t sectionsize, sectionhighbits;
+ int i;
+ char *type;
+ char *subtype;
+ char *cte;
+ char **pvp;
+ int argc = 0;
+ char *bp;
+ bool use_qp = false;
+ struct args argv[MAXMIMEARGS];
+ char bbuf[128];
+ char buf[MAXLINE];
+ char pvpbuf[MAXLINE];
+ extern unsigned char MimeTokenTab[256];
+
+ if (tTd(43, 1))
+ {
+ sm_dprintf("mime8to7: flags = %x, boundaries =", flags);
+ if (boundaries[0] == NULL)
+ sm_dprintf(" <none>");
+ else
+ {
+ for (i = 0; boundaries[i] != NULL; i++)
+ sm_dprintf(" %s", boundaries[i]);
+ }
+ sm_dprintf("\n");
+ }
+ MapNLtoCRLF = true;
+ p = hvalue("Content-Transfer-Encoding", header);
+ if (p == NULL ||
+ (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
+ MimeTokenTab)) == NULL ||
+ pvp[0] == NULL)
+ {
+ cte = NULL;
+ }
+ else
+ {
+ cataddr(pvp, NULL, buf, sizeof buf, '\0');
+ cte = sm_rpool_strdup_x(e->e_rpool, buf);
+ }
+
+ type = subtype = NULL;
+ p = hvalue("Content-Type", header);
+ if (p == NULL)
+ {
+ if (bitset(M87F_DIGEST, flags))
+ p = "message/rfc822";
+ else
+ p = "text/plain";
+ }
+ if (p != NULL &&
+ (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
+ MimeTokenTab)) != NULL &&
+ pvp[0] != NULL)
+ {
+ if (tTd(43, 40))
+ {
+ for (i = 0; pvp[i] != NULL; i++)
+ sm_dprintf("pvp[%d] = \"%s\"\n", i, pvp[i]);
+ }
+ type = *pvp++;
+ if (*pvp != NULL && strcmp(*pvp, "/") == 0 &&
+ *++pvp != NULL)
+ {
+ subtype = *pvp++;
+ }
+
+ /* break out parameters */
+ while (*pvp != NULL && argc < MAXMIMEARGS)
+ {
+ /* skip to semicolon separator */
+ while (*pvp != NULL && strcmp(*pvp, ";") != 0)
+ pvp++;
+ if (*pvp++ == NULL || *pvp == NULL)
+ break;
+
+ /* complain about empty values */
+ if (strcmp(*pvp, ";") == 0)
+ {
+ usrerr("mime8to7: Empty parameter in Content-Type header");
+
+ /* avoid bounce loops */
+ e->e_flags |= EF_DONT_MIME;
+ continue;
+ }
+
+ /* extract field name */
+ argv[argc].a_field = *pvp++;
+
+ /* see if there is a value */
+ if (*pvp != NULL && strcmp(*pvp, "=") == 0 &&
+ (*++pvp == NULL || strcmp(*pvp, ";") != 0))
+ {
+ argv[argc].a_value = *pvp;
+ argc++;
+ }
+ }
+ }
+
+ /* check for disaster cases */
+ if (type == NULL)
+ type = "-none-";
+ if (subtype == NULL)
+ subtype = "-none-";
+
+ /* don't propogate some flags more than one level into the message */
+ flags &= ~M87F_DIGEST;
+
+ /*
+ ** Check for cases that can not be encoded.
+ **
+ ** For example, you can't encode certain kinds of types
+ ** or already-encoded messages. If we find this case,
+ ** just copy it through.
+ */
+
+ (void) sm_snprintf(buf, sizeof buf, "%.100s/%.100s", type, subtype);
+ if (wordinclass(buf, 'n') || (cte != NULL && !wordinclass(cte, 'e')))
+ flags |= M87F_NO8BIT;
+
+# ifdef USE_B_CLASS
+ if (wordinclass(buf, 'b') || wordinclass(type, 'b'))
+ MapNLtoCRLF = false;
+# endif /* USE_B_CLASS */
+ if (wordinclass(buf, 'q') || wordinclass(type, 'q'))
+ use_qp = true;
+
+ /*
+ ** Multipart requires special processing.
+ **
+ ** Do a recursive descent into the message.
+ */
+
+ if (sm_strcasecmp(type, "multipart") == 0 &&
+ (!bitset(M87F_NO8BIT, flags) || bitset(M87F_NO8TO7, flags)))
+ {
+
+ if (sm_strcasecmp(subtype, "digest") == 0)
+ flags |= M87F_DIGEST;
+
+ for (i = 0; i < argc; i++)
+ {
+ if (sm_strcasecmp(argv[i].a_field, "boundary") == 0)
+ break;
+ }
+ if (i >= argc || argv[i].a_value == NULL)
+ {
+ usrerr("mime8to7: Content-Type: \"%s\": %s boundary",
+ i >= argc ? "missing" : "bogus", p);
+ p = "---";
+
+ /* avoid bounce loops */
+ e->e_flags |= EF_DONT_MIME;
+ }
+ else
+ {
+ p = argv[i].a_value;
+ stripquotes(p);
+ }
+ if (sm_strlcpy(bbuf, p, sizeof bbuf) >= sizeof bbuf)
+ {
+ usrerr("mime8to7: multipart boundary \"%s\" too long",
+ p);
+
+ /* avoid bounce loops */
+ e->e_flags |= EF_DONT_MIME;
+ }
+
+ if (tTd(43, 1))
+ sm_dprintf("mime8to7: multipart boundary \"%s\"\n",
+ bbuf);
+ for (i = 0; i < MAXMIMENESTING; i++)
+ {
+ if (boundaries[i] == NULL)
+ break;
+ }
+ if (i >= MAXMIMENESTING)
+ {
+ usrerr("mime8to7: multipart nesting boundary too deep");
+
+ /* avoid bounce loops */
+ e->e_flags |= EF_DONT_MIME;
+ }
+ else
+ {
+ boundaries[i] = bbuf;
+ boundaries[i + 1] = NULL;
+ }
+ mci->mci_flags |= MCIF_INMIME;
+
+ /* skip the early "comment" prologue */
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ bt = MBT_FINAL;
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
+ != NULL)
+ {
+ bt = mimeboundary(buf, boundaries);
+ if (bt != MBT_NOTSEP)
+ break;
+ putxline(buf, strlen(buf), mci,
+ PXLF_MAPFROM|PXLF_STRIP8BIT);
+ if (tTd(43, 99))
+ sm_dprintf(" ...%s", buf);
+ }
+ if (sm_io_eof(e->e_dfp))
+ bt = MBT_FINAL;
+ while (bt != MBT_FINAL)
+ {
+ auto HDR *hdr = NULL;
+
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "--", bbuf);
+ putline(buf, mci);
+ if (tTd(43, 35))
+ sm_dprintf(" ...%s\n", buf);
+ collect(e->e_dfp, false, &hdr, e);
+ if (tTd(43, 101))
+ putline("+++after collect", mci);
+ putheader(mci, hdr, e, flags);
+ if (tTd(43, 101))
+ putline("+++after putheader", mci);
+ bt = mime8to7(mci, hdr, e, boundaries, flags);
+ }
+ (void) sm_strlcpyn(buf, sizeof buf, 3, "--", bbuf, "--");
+ putline(buf, mci);
+ if (tTd(43, 35))
+ sm_dprintf(" ...%s\n", buf);
+ boundaries[i] = NULL;
+ mci->mci_flags &= ~MCIF_INMIME;
+
+ /* skip the late "comment" epilogue */
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
+ != NULL)
+ {
+ bt = mimeboundary(buf, boundaries);
+ if (bt != MBT_NOTSEP)
+ break;
+ putxline(buf, strlen(buf), mci,
+ PXLF_MAPFROM|PXLF_STRIP8BIT);
+ if (tTd(43, 99))
+ sm_dprintf(" ...%s", buf);
+ }
+ if (sm_io_eof(e->e_dfp))
+ bt = MBT_FINAL;
+ if (tTd(43, 3))
+ sm_dprintf("\t\t\tmime8to7=>%s (multipart)\n",
+ MimeBoundaryNames[bt]);
+ return bt;
+ }
+
+ /*
+ ** Message/xxx types -- recurse exactly once.
+ **
+ ** Class 's' is predefined to have "rfc822" only.
+ */
+
+ if (sm_strcasecmp(type, "message") == 0)
+ {
+ if (!wordinclass(subtype, 's'))
+ {
+ flags |= M87F_NO8BIT;
+ }
+ else
+ {
+ auto HDR *hdr = NULL;
+
+ putline("", mci);
+
+ mci->mci_flags |= MCIF_INMIME;
+ collect(e->e_dfp, false, &hdr, e);
+ if (tTd(43, 101))
+ putline("+++after collect", mci);
+ putheader(mci, hdr, e, flags);
+ if (tTd(43, 101))
+ putline("+++after putheader", mci);
+ if (hvalue("MIME-Version", hdr) == NULL &&
+ !bitset(M87F_NO8TO7, flags))
+ putline("MIME-Version: 1.0", mci);
+ bt = mime8to7(mci, hdr, e, boundaries, flags);
+ mci->mci_flags &= ~MCIF_INMIME;
+ return bt;
+ }
+ }
+
+ /*
+ ** Non-compound body type
+ **
+ ** Compute the ratio of seven to eight bit characters;
+ ** use that as a heuristic to decide how to do the
+ ** encoding.
+ */
+
+ sectionsize = sectionhighbits = 0;
+ if (!bitset(M87F_NO8BIT|M87F_NO8TO7, flags))
+ {
+ /* remember where we were */
+ offset = sm_io_tell(e->e_dfp, SM_TIME_DEFAULT);
+ if (offset == -1)
+ syserr("mime8to7: cannot sm_io_tell on %cf%s",
+ DATAFL_LETTER, e->e_id);
+
+ /* do a scan of this body type to count character types */
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
+ != NULL)
+ {
+ if (mimeboundary(buf, boundaries) != MBT_NOTSEP)
+ break;
+ for (p = buf; *p != '\0'; p++)
+ {
+ /* count bytes with the high bit set */
+ sectionsize++;
+ if (bitset(0200, *p))
+ sectionhighbits++;
+ }
+
+ /*
+ ** Heuristic: if 1/4 of the first 4K bytes are 8-bit,
+ ** assume base64. This heuristic avoids double-reading
+ ** large graphics or video files.
+ */
+
+ if (sectionsize >= 4096 &&
+ sectionhighbits > sectionsize / 4)
+ break;
+ }
+
+ /* return to the original offset for processing */
+ /* XXX use relative seeks to handle >31 bit file sizes? */
+ if (sm_io_seek(e->e_dfp, SM_TIME_DEFAULT, offset, SEEK_SET) < 0)
+ syserr("mime8to7: cannot sm_io_fseek on %cf%s",
+ DATAFL_LETTER, e->e_id);
+ else
+ sm_io_clearerr(e->e_dfp);
+ }
+
+ /*
+ ** Heuristically determine encoding method.
+ ** If more than 1/8 of the total characters have the
+ ** eighth bit set, use base64; else use quoted-printable.
+ ** However, only encode binary encoded data as base64,
+ ** since otherwise the NL=>CRLF mapping will be a problem.
+ */
+
+ if (tTd(43, 8))
+ {
+ sm_dprintf("mime8to7: %ld high bit(s) in %ld byte(s), cte=%s, type=%s/%s\n",
+ (long) sectionhighbits, (long) sectionsize,
+ cte == NULL ? "[none]" : cte,
+ type == NULL ? "[none]" : type,
+ subtype == NULL ? "[none]" : subtype);
+ }
+ if (cte != NULL && sm_strcasecmp(cte, "binary") == 0)
+ sectionsize = sectionhighbits;
+ linelen = 0;
+ bp = buf;
+ if (sectionhighbits == 0)
+ {
+ /* no encoding necessary */
+ if (cte != NULL &&
+ bitset(MCIF_CVT8TO7|MCIF_CVT7TO8|MCIF_INMIME,
+ mci->mci_flags) &&
+ !bitset(M87F_NO8TO7, flags))
+ {
+ /*
+ ** Skip _unless_ in MIME mode and potentially
+ ** converting from 8 bit to 7 bit MIME. See
+ ** putheader() for the counterpart where the
+ ** CTE header is skipped in the opposite
+ ** situation.
+ */
+
+ (void) sm_snprintf(buf, sizeof buf,
+ "Content-Transfer-Encoding: %.200s", cte);
+ putline(buf, mci);
+ if (tTd(43, 36))
+ sm_dprintf(" ...%s\n", buf);
+ }
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
+ != NULL)
+ {
+ bt = mimeboundary(buf, boundaries);
+ if (bt != MBT_NOTSEP)
+ break;
+ putline(buf, mci);
+ }
+ if (sm_io_eof(e->e_dfp))
+ bt = MBT_FINAL;
+ }
+ else if (!MapNLtoCRLF ||
+ (sectionsize / 8 < sectionhighbits && !use_qp))
+ {
+ /* use base64 encoding */
+ int c1, c2;
+
+ if (tTd(43, 36))
+ sm_dprintf(" ...Content-Transfer-Encoding: base64\n");
+ putline("Content-Transfer-Encoding: base64", mci);
+ (void) sm_snprintf(buf, sizeof buf,
+ "X-MIME-Autoconverted: from 8bit to base64 by %s id %s",
+ MyHostName, e->e_id);
+ putline(buf, mci);
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ while ((c1 = mime_getchar_crlf(e->e_dfp, boundaries, &bt)) !=
+ SM_IO_EOF)
+ {
+ if (linelen > 71)
+ {
+ *bp = '\0';
+ putline(buf, mci);
+ linelen = 0;
+ bp = buf;
+ }
+ linelen += 4;
+ *bp++ = Base64Code[(c1 >> 2)];
+ c1 = (c1 & 0x03) << 4;
+ c2 = mime_getchar_crlf(e->e_dfp, boundaries, &bt);
+ if (c2 == SM_IO_EOF)
+ {
+ *bp++ = Base64Code[c1];
+ *bp++ = '=';
+ *bp++ = '=';
+ break;
+ }
+ c1 |= (c2 >> 4) & 0x0f;
+ *bp++ = Base64Code[c1];
+ c1 = (c2 & 0x0f) << 2;
+ c2 = mime_getchar_crlf(e->e_dfp, boundaries, &bt);
+ if (c2 == SM_IO_EOF)
+ {
+ *bp++ = Base64Code[c1];
+ *bp++ = '=';
+ break;
+ }
+ c1 |= (c2 >> 6) & 0x03;
+ *bp++ = Base64Code[c1];
+ *bp++ = Base64Code[c2 & 0x3f];
+ }
+ *bp = '\0';
+ putline(buf, mci);
+ }
+ else
+ {
+ /* use quoted-printable encoding */
+ int c1, c2;
+ int fromstate;
+ BITMAP256 badchars;
+
+ /* set up map of characters that must be mapped */
+ clrbitmap(badchars);
+ for (c1 = 0x00; c1 < 0x20; c1++)
+ setbitn(c1, badchars);
+ clrbitn('\t', badchars);
+ for (c1 = 0x7f; c1 < 0x100; c1++)
+ setbitn(c1, badchars);
+ setbitn('=', badchars);
+ if (bitnset(M_EBCDIC, mci->mci_mailer->m_flags))
+ for (p = "!\"#$@[\\]^`{|}~"; *p != '\0'; p++)
+ setbitn(*p, badchars);
+
+ if (tTd(43, 36))
+ sm_dprintf(" ...Content-Transfer-Encoding: quoted-printable\n");
+ putline("Content-Transfer-Encoding: quoted-printable", mci);
+ (void) sm_snprintf(buf, sizeof buf,
+ "X-MIME-Autoconverted: from 8bit to quoted-printable by %s id %s",
+ MyHostName, e->e_id);
+ putline(buf, mci);
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ fromstate = 0;
+ c2 = '\n';
+ while ((c1 = mime_getchar(e->e_dfp, boundaries, &bt)) !=
+ SM_IO_EOF)
+ {
+ if (c1 == '\n')
+ {
+ if (c2 == ' ' || c2 == '\t')
+ {
+ *bp++ = '=';
+ *bp++ = Base16Code[(c2 >> 4) & 0x0f];
+ *bp++ = Base16Code[c2 & 0x0f];
+ }
+ if (buf[0] == '.' && bp == &buf[1])
+ {
+ buf[0] = '=';
+ *bp++ = Base16Code[('.' >> 4) & 0x0f];
+ *bp++ = Base16Code['.' & 0x0f];
+ }
+ *bp = '\0';
+ putline(buf, mci);
+ linelen = fromstate = 0;
+ bp = buf;
+ c2 = c1;
+ continue;
+ }
+ if (c2 == ' ' && linelen == 4 && fromstate == 4 &&
+ bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
+ {
+ *bp++ = '=';
+ *bp++ = '2';
+ *bp++ = '0';
+ linelen += 3;
+ }
+ else if (c2 == ' ' || c2 == '\t')
+ {
+ *bp++ = c2;
+ linelen++;
+ }
+ if (linelen > 72 &&
+ (linelen > 75 || c1 != '.' ||
+ (linelen > 73 && c2 == '.')))
+ {
+ if (linelen > 73 && c2 == '.')
+ bp--;
+ else
+ c2 = '\n';
+ *bp++ = '=';
+ *bp = '\0';
+ putline(buf, mci);
+ linelen = fromstate = 0;
+ bp = buf;
+ if (c2 == '.')
+ {
+ *bp++ = '.';
+ linelen++;
+ }
+ }
+ if (bitnset(bitidx(c1), badchars))
+ {
+ *bp++ = '=';
+ *bp++ = Base16Code[(c1 >> 4) & 0x0f];
+ *bp++ = Base16Code[c1 & 0x0f];
+ linelen += 3;
+ }
+ else if (c1 != ' ' && c1 != '\t')
+ {
+ if (linelen < 4 && c1 == "From"[linelen])
+ fromstate++;
+ *bp++ = c1;
+ linelen++;
+ }
+ c2 = c1;
+ }
+
+ /* output any saved character */
+ if (c2 == ' ' || c2 == '\t')
+ {
+ *bp++ = '=';
+ *bp++ = Base16Code[(c2 >> 4) & 0x0f];
+ *bp++ = Base16Code[c2 & 0x0f];
+ linelen += 3;
+ }
+
+ if (linelen > 0 || boundaries[0] != NULL)
+ {
+ *bp = '\0';
+ putline(buf, mci);
+ }
+
+ }
+ if (tTd(43, 3))
+ sm_dprintf("\t\t\tmime8to7=>%s (basic)\n", MimeBoundaryNames[bt]);
+ return bt;
+}
+/*
+** MIME_GETCHAR -- get a character for MIME processing
+**
+** Treats boundaries as SM_IO_EOF.
+**
+** Parameters:
+** fp -- the input file.
+** boundaries -- the current MIME boundaries.
+** btp -- if the return value is SM_IO_EOF, *btp is set to
+** the type of the boundary.
+**
+** Returns:
+** The next character in the input stream.
+*/
+
+static int
+mime_getchar(fp, boundaries, btp)
+ register SM_FILE_T *fp;
+ char **boundaries;
+ int *btp;
+{
+ int c;
+ static unsigned char *bp = NULL;
+ static int buflen = 0;
+ static bool atbol = true; /* at beginning of line */
+ static int bt = MBT_SYNTAX; /* boundary type of next SM_IO_EOF */
+ static unsigned char buf[128]; /* need not be a full line */
+ int start = 0; /* indicates position of - in buffer */
+
+ if (buflen == 1 && *bp == '\n')
+ {
+ /* last \n in buffer may be part of next MIME boundary */
+ c = *bp;
+ }
+ else if (buflen > 0)
+ {
+ buflen--;
+ return *bp++;
+ }
+ else
+ c = sm_io_getc(fp, SM_TIME_DEFAULT);
+ bp = buf;
+ buflen = 0;
+ if (c == '\n')
+ {
+ /* might be part of a MIME boundary */
+ *bp++ = c;
+ atbol = true;
+ c = sm_io_getc(fp, SM_TIME_DEFAULT);
+ if (c == '\n')
+ {
+ (void) sm_io_ungetc(fp, SM_TIME_DEFAULT, c);
+ return c;
+ }
+ start = 1;
+ }
+ if (c != SM_IO_EOF)
+ *bp++ = c;
+ else
+ bt = MBT_FINAL;
+ if (atbol && c == '-')
+ {
+ /* check for a message boundary */
+ c = sm_io_getc(fp, SM_TIME_DEFAULT);
+ if (c != '-')
+ {
+ if (c != SM_IO_EOF)
+ *bp++ = c;
+ else
+ bt = MBT_FINAL;
+ buflen = bp - buf - 1;
+ bp = buf;
+ return *bp++;
+ }
+
+ /* got "--", now check for rest of separator */
+ *bp++ = '-';
+ while (bp < &buf[sizeof buf - 2] &&
+ (c = sm_io_getc(fp, SM_TIME_DEFAULT)) != SM_IO_EOF &&
+ c != '\n')
+ {
+ *bp++ = c;
+ }
+ *bp = '\0'; /* XXX simply cut off? */
+ bt = mimeboundary((char *) &buf[start], boundaries);
+ switch (bt)
+ {
+ case MBT_FINAL:
+ case MBT_INTERMED:
+ /* we have a message boundary */
+ buflen = 0;
+ *btp = bt;
+ return SM_IO_EOF;
+ }
+
+ atbol = c == '\n';
+ if (c != SM_IO_EOF)
+ *bp++ = c;
+ }
+
+ buflen = bp - buf - 1;
+ if (buflen < 0)
+ {
+ *btp = bt;
+ return SM_IO_EOF;
+ }
+ bp = buf;
+ return *bp++;
+}
+/*
+** MIME_GETCHAR_CRLF -- do mime_getchar, but translate NL => CRLF
+**
+** Parameters:
+** fp -- the input file.
+** boundaries -- the current MIME boundaries.
+** btp -- if the return value is SM_IO_EOF, *btp is set to
+** the type of the boundary.
+**
+** Returns:
+** The next character in the input stream.
+*/
+
+static int
+mime_getchar_crlf(fp, boundaries, btp)
+ register SM_FILE_T *fp;
+ char **boundaries;
+ int *btp;
+{
+ static bool sendlf = false;
+ int c;
+
+ if (sendlf)
+ {
+ sendlf = false;
+ return '\n';
+ }
+ c = mime_getchar(fp, boundaries, btp);
+ if (c == '\n' && MapNLtoCRLF)
+ {
+ sendlf = true;
+ return '\r';
+ }
+ return c;
+}
+/*
+** MIMEBOUNDARY -- determine if this line is a MIME boundary & its type
+**
+** Parameters:
+** line -- the input line.
+** boundaries -- the set of currently pending boundaries.
+**
+** Returns:
+** MBT_NOTSEP -- if this is not a separator line
+** MBT_INTERMED -- if this is an intermediate separator
+** MBT_FINAL -- if this is a final boundary
+** MBT_SYNTAX -- if this is a boundary for the wrong
+** enclosure -- i.e., a syntax error.
+*/
+
+static int
+mimeboundary(line, boundaries)
+ register char *line;
+ char **boundaries;
+{
+ int type = MBT_NOTSEP;
+ int i;
+ int savec;
+
+ if (line[0] != '-' || line[1] != '-' || boundaries == NULL)
+ return MBT_NOTSEP;
+ i = strlen(line);
+ if (i > 0 && line[i - 1] == '\n')
+ i--;
+
+ /* strip off trailing whitespace */
+ while (i > 0 && (line[i - 1] == ' ' || line[i - 1] == '\t'))
+ i--;
+ savec = line[i];
+ line[i] = '\0';
+
+ if (tTd(43, 5))
+ sm_dprintf("mimeboundary: line=\"%s\"... ", line);
+
+ /* check for this as an intermediate boundary */
+ if (isboundary(&line[2], boundaries) >= 0)
+ type = MBT_INTERMED;
+ else if (i > 2 && strncmp(&line[i - 2], "--", 2) == 0)
+ {
+ /* check for a final boundary */
+ line[i - 2] = '\0';
+ if (isboundary(&line[2], boundaries) >= 0)
+ type = MBT_FINAL;
+ line[i - 2] = '-';
+ }
+
+ line[i] = savec;
+ if (tTd(43, 5))
+ sm_dprintf("%s\n", MimeBoundaryNames[type]);
+ return type;
+}
+/*
+** DEFCHARSET -- return default character set for message
+**
+** The first choice for character set is for the mailer
+** corresponding to the envelope sender. If neither that
+** nor the global configuration file has a default character
+** set defined, return "unknown-8bit" as recommended by
+** RFC 1428 section 3.
+**
+** Parameters:
+** e -- the envelope for this message.
+**
+** Returns:
+** The default character set for that mailer.
+*/
+
+char *
+defcharset(e)
+ register ENVELOPE *e;
+{
+ if (e != NULL && e->e_from.q_mailer != NULL &&
+ e->e_from.q_mailer->m_defcharset != NULL)
+ return e->e_from.q_mailer->m_defcharset;
+ if (DefaultCharSet != NULL)
+ return DefaultCharSet;
+ return "unknown-8bit";
+}
+/*
+** ISBOUNDARY -- is a given string a currently valid boundary?
+**
+** Parameters:
+** line -- the current input line.
+** boundaries -- the list of valid boundaries.
+**
+** Returns:
+** The index number in boundaries if the line is found.
+** -1 -- otherwise.
+**
+*/
+
+static int
+isboundary(line, boundaries)
+ char *line;
+ char **boundaries;
+{
+ register int i;
+
+ for (i = 0; i <= MAXMIMENESTING && boundaries[i] != NULL; i++)
+ {
+ if (strcmp(line, boundaries[i]) == 0)
+ return i;
+ }
+ return -1;
+}
+#endif /* MIME8TO7 */
+
+#if MIME7TO8
+static int mime_fromqp __P((unsigned char *, unsigned char **, int));
+
+/*
+** MIME7TO8 -- output 7 bit encoded MIME body in 8 bit format
+**
+** This is a hack. Supports translating the two 7-bit body-encodings
+** (quoted-printable and base64) to 8-bit coded bodies.
+**
+** There is not much point in supporting multipart here, as the UA
+** will be able to deal with encoded MIME bodies if it can parse MIME
+** multipart messages.
+**
+** Note also that we won't be called unless it is a text/plain MIME
+** message, encoded base64 or QP and mailer flag '9' has been defined
+** on mailer.
+**
+** Contributed by Marius Olaffson <marius@rhi.hi.is>.
+**
+** Parameters:
+** mci -- mailer connection information.
+** header -- the header for this body part.
+** e -- envelope.
+**
+** Returns:
+** none.
+*/
+
+static char index_64[128] =
+{
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+ 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
+ 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+ -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+ 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+};
+
+# define CHAR64(c) (((c) < 0 || (c) > 127) ? -1 : index_64[(c)])
+
+void
+mime7to8(mci, header, e)
+ register MCI *mci;
+ HDR *header;
+ register ENVELOPE *e;
+{
+ int pxflags;
+ register char *p;
+ char *cte;
+ char **pvp;
+ unsigned char *fbufp;
+ char buf[MAXLINE];
+ unsigned char fbuf[MAXLINE + 1];
+ char pvpbuf[MAXLINE];
+ extern unsigned char MimeTokenTab[256];
+
+ p = hvalue("Content-Transfer-Encoding", header);
+ if (p == NULL ||
+ (pvp = prescan(p, '\0', pvpbuf, sizeof pvpbuf, NULL,
+ MimeTokenTab)) == NULL ||
+ pvp[0] == NULL)
+ {
+ /* "can't happen" -- upper level should have caught this */
+ syserr("mime7to8: unparsable CTE %s", p == NULL ? "<NULL>" : p);
+
+ /* avoid bounce loops */
+ e->e_flags |= EF_DONT_MIME;
+
+ /* cheap failsafe algorithm -- should work on text/plain */
+ if (p != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Content-Transfer-Encoding: %s", p);
+ putline(buf, mci);
+ }
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf, sizeof buf)
+ != NULL)
+ putline(buf, mci);
+ return;
+ }
+ cataddr(pvp, NULL, buf, sizeof buf, '\0');
+ cte = sm_rpool_strdup_x(e->e_rpool, buf);
+
+ mci->mci_flags |= MCIF_INHEADER;
+ putline("Content-Transfer-Encoding: 8bit", mci);
+ (void) sm_snprintf(buf, sizeof buf,
+ "X-MIME-Autoconverted: from %.200s to 8bit by %s id %s",
+ cte, MyHostName, e->e_id);
+ putline(buf, mci);
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+
+ /*
+ ** Translate body encoding to 8-bit. Supports two types of
+ ** encodings; "base64" and "quoted-printable". Assume qp if
+ ** it is not base64.
+ */
+
+ pxflags = PXLF_MAPFROM;
+ if (sm_strcasecmp(cte, "base64") == 0)
+ {
+ int c1, c2, c3, c4;
+
+ fbufp = fbuf;
+ while ((c1 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT)) !=
+ SM_IO_EOF)
+ {
+ if (isascii(c1) && isspace(c1))
+ continue;
+
+ do
+ {
+ c2 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT);
+ } while (isascii(c2) && isspace(c2));
+ if (c2 == SM_IO_EOF)
+ break;
+
+ do
+ {
+ c3 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT);
+ } while (isascii(c3) && isspace(c3));
+ if (c3 == SM_IO_EOF)
+ break;
+
+ do
+ {
+ c4 = sm_io_getc(e->e_dfp, SM_TIME_DEFAULT);
+ } while (isascii(c4) && isspace(c4));
+ if (c4 == SM_IO_EOF)
+ break;
+
+ if (c1 == '=' || c2 == '=')
+ continue;
+ c1 = CHAR64(c1);
+ c2 = CHAR64(c2);
+
+ *fbufp = (c1 << 2) | ((c2 & 0x30) >> 4);
+ if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE])
+ {
+ if (*--fbufp != '\n' ||
+ (fbufp > fbuf && *--fbufp != '\r'))
+ {
+ pxflags |= PXLF_NOADDEOL;
+ fbufp++;
+ }
+ putxline((char *) fbuf, fbufp - fbuf,
+ mci, pxflags);
+ pxflags &= ~PXLF_NOADDEOL;
+ fbufp = fbuf;
+ }
+ if (c3 == '=')
+ continue;
+ c3 = CHAR64(c3);
+ *fbufp = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2);
+ if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE])
+ {
+ if (*--fbufp != '\n' ||
+ (fbufp > fbuf && *--fbufp != '\r'))
+ {
+ pxflags |= PXLF_NOADDEOL;
+ fbufp++;
+ }
+ putxline((char *) fbuf, fbufp - fbuf,
+ mci, pxflags);
+ pxflags &= ~PXLF_NOADDEOL;
+ fbufp = fbuf;
+ }
+ if (c4 == '=')
+ continue;
+ c4 = CHAR64(c4);
+ *fbufp = ((c3 & 0x03) << 6) | c4;
+ if (*fbufp++ == '\n' || fbufp >= &fbuf[MAXLINE])
+ {
+ if (*--fbufp != '\n' ||
+ (fbufp > fbuf && *--fbufp != '\r'))
+ {
+ pxflags |= PXLF_NOADDEOL;
+ fbufp++;
+ }
+ putxline((char *) fbuf, fbufp - fbuf,
+ mci, pxflags);
+ pxflags &= ~PXLF_NOADDEOL;
+ fbufp = fbuf;
+ }
+ }
+ }
+ else
+ {
+ int off;
+
+ /* quoted-printable */
+ pxflags |= PXLF_NOADDEOL;
+ fbufp = fbuf;
+ while (sm_io_fgets(e->e_dfp, SM_TIME_DEFAULT, buf,
+ sizeof buf) != NULL)
+ {
+ off = mime_fromqp((unsigned char *) buf, &fbufp,
+ &fbuf[MAXLINE] - fbufp);
+again:
+ if (off < -1)
+ continue;
+
+ if (fbufp - fbuf > 0)
+ putxline((char *) fbuf, fbufp - fbuf - 1, mci,
+ pxflags);
+ fbufp = fbuf;
+ if (off >= 0 && buf[off] != '\0')
+ {
+ off = mime_fromqp((unsigned char *) (buf + off),
+ &fbufp,
+ &fbuf[MAXLINE] - fbufp);
+ goto again;
+ }
+ }
+ }
+
+ /* force out partial last line */
+ if (fbufp > fbuf)
+ {
+ *fbufp = '\0';
+ putxline((char *) fbuf, fbufp - fbuf, mci, pxflags);
+ }
+
+ /*
+ ** The decoded text may end without an EOL. Since this function
+ ** is only called for text/plain MIME messages, it is safe to
+ ** add an extra one at the end just in case. This is a hack,
+ ** but so is auto-converting MIME in the first place.
+ */
+
+ putline("", mci);
+
+ if (tTd(43, 3))
+ sm_dprintf("\t\t\tmime7to8 => %s to 8bit done\n", cte);
+}
+/*
+** The following is based on Borenstein's "codes.c" module, with simplifying
+** changes as we do not deal with multipart, and to do the translation in-core,
+** with an attempt to prevent overrun of output buffers.
+**
+** What is needed here are changes to defend this code better against
+** bad encodings. Questionable to always return 0xFF for bad mappings.
+*/
+
+static char index_hex[128] =
+{
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1, -1,-1,-1,-1,
+ -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
+};
+
+# define HEXCHAR(c) (((c) < 0 || (c) > 127) ? -1 : index_hex[(c)])
+
+/*
+** MIME_FROMQP -- decode quoted printable string
+**
+** Parameters:
+** infile -- input (encoded) string
+** outfile -- output string
+** maxlen -- size of output buffer
+**
+** Returns:
+** -2 if decoding failure
+** -1 if infile completely decoded into outfile
+** >= 0 is the position in infile decoding
+** reached before maxlen was reached
+*/
+
+static int
+mime_fromqp(infile, outfile, maxlen)
+ unsigned char *infile;
+ unsigned char **outfile;
+ int maxlen; /* Max # of chars allowed in outfile */
+{
+ int c1, c2;
+ int nchar = 0;
+ unsigned char *b;
+
+ /* decrement by one for trailing '\0', at least one other char */
+ if (--maxlen < 1)
+ return 0;
+
+ b = infile;
+ while ((c1 = *infile++) != '\0' && nchar < maxlen)
+ {
+ if (c1 == '=')
+ {
+ if ((c1 = *infile++) == '\0')
+ break;
+
+ if (c1 == '\n' || (c1 = HEXCHAR(c1)) == -1)
+ {
+ /* ignore it and the rest of the buffer */
+ return -2;
+ }
+ else
+ {
+ do
+ {
+ if ((c2 = *infile++) == '\0')
+ {
+ c2 = -1;
+ break;
+ }
+ } while ((c2 = HEXCHAR(c2)) == -1);
+
+ if (c2 == -1)
+ break;
+ nchar++;
+ *(*outfile)++ = c1 << 4 | c2;
+ }
+ }
+ else
+ {
+ nchar++;
+ *(*outfile)++ = c1;
+ if (c1 == '\n')
+ break;
+ }
+ }
+ *(*outfile)++ = '\0';
+ if (nchar >= maxlen)
+ return (infile - b - 1);
+ return -1;
+}
+#endif /* MIME7TO8 */
diff --git a/contrib/sendmail/src/newaliases.1 b/contrib/sendmail/src/newaliases.1
new file mode 100644
index 0000000..20fd0e7
--- /dev/null
+++ b/contrib/sendmail/src/newaliases.1
@@ -0,0 +1,50 @@
+.\" Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+.\" All rights reserved.
+.\" Copyright (c) 1983, 1997 Eric P. Allman. All rights reserved.
+.\" Copyright (c) 1985, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" By using this file, you agree to the terms and conditions set
+.\" forth in the LICENSE file which can be found at the top level of
+.\" the sendmail distribution.
+.\"
+.\"
+.\" $Id: newaliases.1,v 8.19 2001/10/10 03:23:17 ca Exp $
+.\"
+.TH NEWALIASES 1 "$Date: 2001/10/10 03:23:17 $"
+.SH NAME
+newaliases
+\- rebuild the data base for the mail aliases file
+.SH SYNOPSIS
+.B newaliases
+.SH DESCRIPTION
+.B Newaliases
+rebuilds the random access data base for the mail aliases file
+/etc/mail/aliases. It must be run each time this file is changed
+in order for the change to take effect.
+.PP
+.B Newaliases
+is identical to ``sendmail -bi''.
+.PP
+The
+.B newaliases
+utility exits 0 on success, and >0 if an error occurs.
+.PP
+Notice: do
+.B not
+use
+.B makemap
+to create the aliases data base, because
+.B newaliases
+puts a special token into the data base that is required by
+.B sendmail.
+.SH FILES
+.TP 2i
+/etc/mail/aliases
+The mail aliases file
+.SH SEE ALSO
+aliases(5), sendmail(8)
+.SH HISTORY
+The
+.B newaliases
+command appeared in 4.0BSD.
diff --git a/contrib/sendmail/src/parseaddr.c b/contrib/sendmail/src/parseaddr.c
new file mode 100644
index 0000000..c7de84b
--- /dev/null
+++ b/contrib/sendmail/src/parseaddr.c
@@ -0,0 +1,3183 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: parseaddr.c,v 8.359.2.2 2002/08/16 14:56:01 ca Exp $")
+
+static void allocaddr __P((ADDRESS *, int, char *, ENVELOPE *));
+static int callsubr __P((char**, int, ENVELOPE *));
+static char *map_lookup __P((STAB *, char *, char **, int *, ENVELOPE *));
+static ADDRESS *buildaddr __P((char **, ADDRESS *, int, ENVELOPE *));
+static bool hasctrlchar __P((register char *, bool, bool));
+
+/* replacement for illegal characters in addresses */
+#define BAD_CHAR_REPLACEMENT '?'
+
+/*
+** PARSEADDR -- Parse an address
+**
+** Parses an address and breaks it up into three parts: a
+** net to transmit the message on, the host to transmit it
+** to, and a user on that host. These are loaded into an
+** ADDRESS header with the values squirreled away if necessary.
+** The "user" part may not be a real user; the process may
+** just reoccur on that machine. For example, on a machine
+** with an arpanet connection, the address
+** csvax.bill@berkeley
+** will break up to a "user" of 'csvax.bill' and a host
+** of 'berkeley' -- to be transmitted over the arpanet.
+**
+** Parameters:
+** addr -- the address to parse.
+** a -- a pointer to the address descriptor buffer.
+** If NULL, an address will be created.
+** flags -- describe detail for parsing. See RF_ definitions
+** in sendmail.h.
+** delim -- the character to terminate the address, passed
+** to prescan.
+** delimptr -- if non-NULL, set to the location of the
+** delim character that was found.
+** e -- the envelope that will contain this address.
+** isrcpt -- true if the address denotes a recipient; false
+** indicates a sender.
+**
+** Returns:
+** A pointer to the address descriptor header (`a' if
+** `a' is non-NULL).
+** NULL on error.
+**
+** Side Effects:
+** e->e_to = addr
+*/
+
+/* following delimiters are inherent to the internal algorithms */
+#define DELIMCHARS "()<>,;\r\n" /* default word delimiters */
+
+ADDRESS *
+parseaddr(addr, a, flags, delim, delimptr, e, isrcpt)
+ char *addr;
+ register ADDRESS *a;
+ int flags;
+ int delim;
+ char **delimptr;
+ register ENVELOPE *e;
+ bool isrcpt;
+{
+ char **pvp;
+ auto char *delimptrbuf;
+ bool qup;
+ char pvpbuf[PSBUFSIZE];
+
+ /*
+ ** Initialize and prescan address.
+ */
+
+ e->e_to = addr;
+ if (tTd(20, 1))
+ sm_dprintf("\n--parseaddr(%s)\n", addr);
+
+ if (delimptr == NULL)
+ delimptr = &delimptrbuf;
+
+ pvp = prescan(addr, delim, pvpbuf, sizeof pvpbuf, delimptr, NULL);
+ if (pvp == NULL)
+ {
+ if (tTd(20, 1))
+ sm_dprintf("parseaddr-->NULL\n");
+ return NULL;
+ }
+
+ if (invalidaddr(addr, delim == '\0' ? NULL : *delimptr, isrcpt))
+ {
+ if (tTd(20, 1))
+ sm_dprintf("parseaddr-->bad address\n");
+ return NULL;
+ }
+
+ /*
+ ** Save addr if we are going to have to.
+ **
+ ** We have to do this early because there is a chance that
+ ** the map lookups in the rewriting rules could clobber
+ ** static memory somewhere.
+ */
+
+ if (bitset(RF_COPYPADDR, flags) && addr != NULL)
+ {
+ char savec = **delimptr;
+
+ if (savec != '\0')
+ **delimptr = '\0';
+ e->e_to = addr = sm_rpool_strdup_x(e->e_rpool, addr);
+ if (savec != '\0')
+ **delimptr = savec;
+ }
+
+ /*
+ ** Apply rewriting rules.
+ ** Ruleset 0 does basic parsing. It must resolve.
+ */
+
+ qup = false;
+ if (REWRITE(pvp, 3, e) == EX_TEMPFAIL)
+ qup = true;
+ if (REWRITE(pvp, 0, e) == EX_TEMPFAIL)
+ qup = true;
+
+ /*
+ ** Build canonical address from pvp.
+ */
+
+ a = buildaddr(pvp, a, flags, e);
+
+ if (hasctrlchar(a->q_user, isrcpt, true))
+ {
+ if (tTd(20, 1))
+ sm_dprintf("parseaddr-->bad q_user\n");
+
+ /*
+ ** Just mark the address as bad so DSNs work.
+ ** hasctrlchar() has to make sure that the address
+ ** has been sanitized, e.g., shortened.
+ */
+
+ a->q_state = QS_BADADDR;
+ }
+
+ /*
+ ** Make local copies of the host & user and then
+ ** transport them out.
+ */
+
+ allocaddr(a, flags, addr, e);
+ if (QS_IS_BADADDR(a->q_state))
+ {
+ /* weed out bad characters in the printable address too */
+ (void) hasctrlchar(a->q_paddr, isrcpt, false);
+ return a;
+ }
+
+ /*
+ ** Select a queue directory for recipient addresses.
+ ** This is done here and in split_across_queue_groups(),
+ ** but the latter applies to addresses after aliasing,
+ ** and only if splitting is done.
+ */
+
+ if ((a->q_qgrp == NOAQGRP || a->q_qgrp == ENVQGRP) &&
+ !bitset(RF_SENDERADDR|RF_HEADERADDR, flags) &&
+ OpMode != MD_INITALIAS)
+ {
+ int r;
+
+ /* call ruleset which should return a queue group name */
+ r = rscap(RS_QUEUEGROUP, a->q_user, NULL, e, &pvp, pvpbuf,
+ sizeof(pvpbuf));
+ if (r == EX_OK &&
+ pvp != NULL && pvp[0] != NULL &&
+ (pvp[0][0] & 0377) == CANONNET &&
+ pvp[1] != NULL && pvp[1][0] != '\0')
+ {
+ r = name2qid(pvp[1]);
+ if (r == NOQGRP && LogLevel > 10)
+ sm_syslog(LOG_INFO, NOQID,
+ "can't find queue group name %s, selection ignored",
+ pvp[1]);
+ if (tTd(20, 4) && r != NOQGRP)
+ sm_syslog(LOG_INFO, NOQID,
+ "queue group name %s -> %d",
+ pvp[1], r);
+ a->q_qgrp = r == NOQGRP ? ENVQGRP : r;
+ }
+ }
+
+ /*
+ ** If there was a parsing failure, mark it for queueing.
+ */
+
+ if (qup && OpMode != MD_INITALIAS)
+ {
+ char *msg = "Transient parse error -- message queued for future delivery";
+
+ if (e->e_sendmode == SM_DEFER)
+ msg = "Deferring message until queue run";
+ if (tTd(20, 1))
+ sm_dprintf("parseaddr: queuing message\n");
+ message(msg);
+ if (e->e_message == NULL && e->e_sendmode != SM_DEFER)
+ e->e_message = sm_rpool_strdup_x(e->e_rpool, msg);
+ a->q_state = QS_QUEUEUP;
+ a->q_status = "4.4.3";
+ }
+
+ /*
+ ** Compute return value.
+ */
+
+ if (tTd(20, 1))
+ {
+ sm_dprintf("parseaddr-->");
+ printaddr(a, false);
+ }
+
+ return a;
+}
+/*
+** INVALIDADDR -- check for address containing characters used for macros
+**
+** Parameters:
+** addr -- the address to check.
+** delimptr -- if non-NULL: end of address to check, i.e.,
+** a pointer in the address string.
+** isrcpt -- true iff the address is for a recipient.
+**
+** Returns:
+** true -- if the address has characters that are reservered
+** for macros or is too long.
+** false -- otherwise.
+*/
+
+bool
+invalidaddr(addr, delimptr, isrcpt)
+ register char *addr;
+ char *delimptr;
+ bool isrcpt;
+{
+ bool result = false;
+ char savedelim = '\0';
+ char *b = addr;
+ int len = 0;
+
+ if (delimptr != NULL)
+ {
+ /* delimptr points to the end of the address to test */
+ savedelim = *delimptr;
+ if (savedelim != '\0') /* if that isn't '\0' already: */
+ *delimptr = '\0'; /* set it */
+ }
+ for (; *addr != '\0'; addr++)
+ {
+ if ((*addr & 0340) == 0200)
+ {
+ setstat(EX_USAGE);
+ result = true;
+ *addr = BAD_CHAR_REPLACEMENT;
+ }
+ if (++len > MAXNAME - 1)
+ {
+ char saved = *addr;
+
+ *addr = '\0';
+ usrerr("553 5.1.0 Address \"%s\" too long (%d bytes max)",
+ b, MAXNAME - 1);
+ *addr = saved;
+ result = true;
+ goto delim;
+ }
+ }
+ if (result)
+ {
+ if (isrcpt)
+ usrerr("501 5.1.3 8-bit character in mailbox address \"%s\"",
+ b);
+ else
+ usrerr("501 5.1.7 8-bit character in mailbox address \"%s\"",
+ b);
+ }
+delim:
+ if (delimptr != NULL && savedelim != '\0')
+ *delimptr = savedelim; /* restore old character at delimptr */
+ return result;
+}
+/*
+** HASCTRLCHAR -- check for address containing meta-characters
+**
+** Checks that the address contains no meta-characters, and contains
+** no "non-printable" characters unless they are quoted or escaped.
+** Quoted or escaped characters are literals.
+**
+** Parameters:
+** addr -- the address to check.
+** isrcpt -- true if the address is for a recipient; false
+** indicates a from.
+** complain -- true if an error should issued if the address
+** is invalid and should be "repaired".
+**
+** Returns:
+** true -- if the address has any "wierd" characters or
+** non-printable characters or if a quote is unbalanced.
+** false -- otherwise.
+*/
+
+static bool
+hasctrlchar(addr, isrcpt, complain)
+ register char *addr;
+ bool isrcpt, complain;
+{
+ bool quoted = false;
+ int len = 0;
+ char *result = NULL;
+ char *b = addr;
+
+ if (addr == NULL)
+ return false;
+ for (; *addr != '\0'; addr++)
+ {
+ if (++len > MAXNAME - 1)
+ {
+ if (complain)
+ {
+ (void) shorten_rfc822_string(b, MAXNAME - 1);
+ usrerr("553 5.1.0 Address \"%s\" too long (%d bytes max)",
+ b, MAXNAME - 1);
+ return true;
+ }
+ result = "too long";
+ }
+ if (!quoted && (*addr < 32 || *addr == 127))
+ {
+ result = "non-printable character";
+ *addr = BAD_CHAR_REPLACEMENT;
+ continue;
+ }
+ if (*addr == '"')
+ quoted = !quoted;
+ else if (*addr == '\\')
+ {
+ /* XXX Generic problem: no '\0' in strings. */
+ if (*++addr == '\0')
+ {
+ result = "trailing \\ character";
+ *--addr = BAD_CHAR_REPLACEMENT;
+ break;
+ }
+ }
+ if ((*addr & 0340) == 0200)
+ {
+ setstat(EX_USAGE);
+ result = "8-bit character";
+ *addr = BAD_CHAR_REPLACEMENT;
+ continue;
+ }
+ }
+ if (quoted)
+ result = "unbalanced quote"; /* unbalanced quote */
+ if (result != NULL && complain)
+ {
+ if (isrcpt)
+ usrerr("501 5.1.3 Syntax error in mailbox address \"%s\" (%s)",
+ b, result);
+ else
+ usrerr("501 5.1.7 Syntax error in mailbox address \"%s\" (%s)",
+ b, result);
+ }
+ return result != NULL;
+}
+/*
+** ALLOCADDR -- do local allocations of address on demand.
+**
+** Also lowercases the host name if requested.
+**
+** Parameters:
+** a -- the address to reallocate.
+** flags -- the copy flag (see RF_ definitions in sendmail.h
+** for a description).
+** paddr -- the printname of the address.
+** e -- envelope
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Copies portions of a into local buffers as requested.
+*/
+
+static void
+allocaddr(a, flags, paddr, e)
+ register ADDRESS *a;
+ int flags;
+ char *paddr;
+ ENVELOPE *e;
+{
+ if (tTd(24, 4))
+ sm_dprintf("allocaddr(flags=%x, paddr=%s)\n", flags, paddr);
+
+ a->q_paddr = paddr;
+
+ if (a->q_user == NULL)
+ a->q_user = "";
+ if (a->q_host == NULL)
+ a->q_host = "";
+
+ if (bitset(RF_COPYPARSE, flags))
+ {
+ a->q_host = sm_rpool_strdup_x(e->e_rpool, a->q_host);
+ if (a->q_user != a->q_paddr)
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, a->q_user);
+ }
+
+ if (a->q_paddr == NULL)
+ a->q_paddr = sm_rpool_strdup_x(e->e_rpool, a->q_user);
+ a->q_qgrp = NOAQGRP;
+}
+/*
+** PRESCAN -- Prescan name and make it canonical
+**
+** Scans a name and turns it into a set of tokens. This process
+** deletes blanks and comments (in parentheses) (if the token type
+** for left paren is SPC).
+**
+** This routine knows about quoted strings and angle brackets.
+**
+** There are certain subtleties to this routine. The one that
+** comes to mind now is that backslashes on the ends of names
+** are silently stripped off; this is intentional. The problem
+** is that some versions of sndmsg (like at LBL) set the kill
+** character to something other than @ when reading addresses;
+** so people type "csvax.eric\@berkeley" -- which screws up the
+** berknet mailer.
+**
+** Parameters:
+** addr -- the name to chomp.
+** delim -- the delimiter for the address, normally
+** '\0' or ','; \0 is accepted in any case.
+** If '\t' then we are reading the .cf file.
+** pvpbuf -- place to put the saved text -- note that
+** the pointers are static.
+** pvpbsize -- size of pvpbuf.
+** delimptr -- if non-NULL, set to the location of the
+** terminating delimiter.
+** toktab -- if set, a token table to use for parsing.
+** If NULL, use the default table.
+**
+** Returns:
+** A pointer to a vector of tokens.
+** NULL on error.
+*/
+
+/* states and character types */
+#define OPR 0 /* operator */
+#define ATM 1 /* atom */
+#define QST 2 /* in quoted string */
+#define SPC 3 /* chewing up spaces */
+#define ONE 4 /* pick up one character */
+#define ILL 5 /* illegal character */
+
+#define NSTATES 6 /* number of states */
+#define TYPE 017 /* mask to select state type */
+
+/* meta bits for table */
+#define M 020 /* meta character; don't pass through */
+#define B 040 /* cause a break */
+#define MB M|B /* meta-break */
+
+static short StateTab[NSTATES][NSTATES] =
+{
+ /* oldst chtype> OPR ATM QST SPC ONE ILL */
+ /*OPR*/ { OPR|B, ATM|B, QST|B, SPC|MB, ONE|B, ILL|MB },
+ /*ATM*/ { OPR|B, ATM, QST|B, SPC|MB, ONE|B, ILL|MB },
+ /*QST*/ { QST, QST, OPR, QST, QST, QST },
+ /*SPC*/ { OPR, ATM, QST, SPC|M, ONE, ILL|MB },
+ /*ONE*/ { OPR, OPR, OPR, OPR, OPR, ILL|MB },
+ /*ILL*/ { OPR|B, ATM|B, QST|B, SPC|MB, ONE|B, ILL|M },
+};
+
+/* token type table -- it gets modified with $o characters */
+static unsigned char TokTypeTab[256] =
+{
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,SPC,SPC,SPC,SPC,SPC,ATM,ATM,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ SPC,ATM,QST,ATM,ATM,ATM,ATM,ATM, SPC,SPC,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* @ A B C D E F G H I J K L M N O */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* ` a b c d e f g h i j k l m n o */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* p q r s t u v w x y z { | } ~ del */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ OPR,OPR,ONE,OPR,OPR,OPR,OPR,OPR, OPR,OPR,OPR,OPR,OPR,OPR,OPR,OPR,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ OPR,OPR,OPR,ONE,ONE,ONE,OPR,OPR, OPR,OPR,OPR,OPR,OPR,OPR,OPR,OPR,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* @ A B C D E F G H I J K L M N O */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* ` a b c d e f g h i j k l m n o */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* p q r s t u v w x y z { | } ~ del */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+};
+
+/* token type table for MIME parsing */
+unsigned char MimeTokenTab[256] =
+{
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,SPC,SPC,SPC,SPC,SPC,ILL,ILL,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ SPC,ATM,QST,ATM,ATM,ATM,ATM,ATM, SPC,SPC,ATM,ATM,OPR,ATM,ATM,OPR,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,OPR,OPR,OPR,OPR,OPR,OPR,
+ /* @ A B C D E F G H I J K L M N O */
+ OPR,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,OPR,OPR,OPR,ATM,ATM,
+ /* ` a b c d e f g h i j k l m n o */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* p q r s t u v w x y z { | } ~ del */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* @ A B C D E F G H I J K L M N O */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* ` a b c d e f g h i j k l m n o */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+ /* p q r s t u v w x y z { | } ~ del */
+ ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL, ILL,ILL,ILL,ILL,ILL,ILL,ILL,ILL,
+};
+
+/* token type table: don't strip comments */
+unsigned char TokTypeNoC[256] =
+{
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,SPC,SPC,SPC,SPC,SPC,ATM,ATM,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ SPC,ATM,QST,ATM,ATM,ATM,ATM,ATM, OPR,OPR,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* @ A B C D E F G H I J K L M N O */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* ` a b c d e f g h i j k l m n o */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* p q r s t u v w x y z { | } ~ del */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+
+ /* nul soh stx etx eot enq ack bel bs ht nl vt np cr so si */
+ OPR,OPR,ONE,OPR,OPR,OPR,OPR,OPR, OPR,OPR,OPR,OPR,OPR,OPR,OPR,OPR,
+ /* dle dc1 dc2 dc3 dc4 nak syn etb can em sub esc fs gs rs us */
+ OPR,OPR,OPR,ONE,ONE,ONE,OPR,OPR, OPR,OPR,OPR,OPR,OPR,OPR,OPR,OPR,
+ /* sp ! " # $ % & ' ( ) * + , - . / */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* @ A B C D E F G H I J K L M N O */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* P Q R S T U V W X Y Z [ \ ] ^ _ */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* ` a b c d e f g h i j k l m n o */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+ /* p q r s t u v w x y z { | } ~ del */
+ ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM, ATM,ATM,ATM,ATM,ATM,ATM,ATM,ATM,
+};
+
+
+#define NOCHAR -1 /* signal nothing in lookahead token */
+
+char **
+prescan(addr, delim, pvpbuf, pvpbsize, delimptr, toktab)
+ char *addr;
+ int delim;
+ char pvpbuf[];
+ int pvpbsize;
+ char **delimptr;
+ unsigned char *toktab;
+{
+ register char *p;
+ register char *q;
+ register int c;
+ char **avp;
+ bool bslashmode;
+ bool route_syntax;
+ int cmntcnt;
+ int anglecnt;
+ char *tok;
+ int state;
+ int newstate;
+ char *saveto = CurEnv->e_to;
+ static char *av[MAXATOM + 1];
+ static bool firsttime = true;
+ extern int errno;
+
+ if (firsttime)
+ {
+ /* initialize the token type table */
+ char obuf[50];
+
+ firsttime = false;
+ if (OperatorChars == NULL)
+ {
+ if (ConfigLevel < 7)
+ OperatorChars = macvalue('o', CurEnv);
+ if (OperatorChars == NULL)
+ OperatorChars = ".:@[]";
+ }
+ expand(OperatorChars, obuf, sizeof obuf - sizeof DELIMCHARS,
+ CurEnv);
+ (void) sm_strlcat(obuf, DELIMCHARS, sizeof obuf);
+ for (p = obuf; *p != '\0'; p++)
+ {
+ if (TokTypeTab[*p & 0xff] == ATM)
+ TokTypeTab[*p & 0xff] = OPR;
+ if (TokTypeNoC[*p & 0xff] == ATM)
+ TokTypeNoC[*p & 0xff] = OPR;
+ }
+ }
+ if (toktab == NULL)
+ toktab = TokTypeTab;
+
+ /* make sure error messages don't have garbage on them */
+ errno = 0;
+
+ q = pvpbuf;
+ bslashmode = false;
+ route_syntax = false;
+ cmntcnt = 0;
+ anglecnt = 0;
+ avp = av;
+ state = ATM;
+ c = NOCHAR;
+ p = addr;
+ CurEnv->e_to = p;
+ if (tTd(22, 11))
+ {
+ sm_dprintf("prescan: ");
+ xputs(p);
+ sm_dprintf("\n");
+ }
+
+ do
+ {
+ /* read a token */
+ tok = q;
+ for (;;)
+ {
+ /* store away any old lookahead character */
+ if (c != NOCHAR && !bslashmode)
+ {
+ /* see if there is room */
+ if (q >= &pvpbuf[pvpbsize - 5])
+ {
+ usrerr("553 5.1.1 Address too long");
+ if (strlen(addr) > MAXNAME)
+ addr[MAXNAME] = '\0';
+ returnnull:
+ if (delimptr != NULL)
+ *delimptr = p;
+ CurEnv->e_to = saveto;
+ return NULL;
+ }
+
+ /* squirrel it away */
+ *q++ = c;
+ }
+
+ /* read a new input character */
+ c = *p++;
+ if (c == '\0')
+ {
+ /* diagnose and patch up bad syntax */
+ if (state == QST)
+ {
+ usrerr("553 Unbalanced '\"'");
+ c = '"';
+ }
+ else if (cmntcnt > 0)
+ {
+ usrerr("553 Unbalanced '('");
+ c = ')';
+ }
+ else if (anglecnt > 0)
+ {
+ c = '>';
+ usrerr("553 Unbalanced '<'");
+ }
+ else
+ break;
+
+ p--;
+ }
+ else if (c == delim && cmntcnt <= 0 && state != QST)
+ {
+ if (anglecnt <= 0)
+ break;
+
+ /* special case for better error management */
+ if (delim == ',' && !route_syntax)
+ {
+ usrerr("553 Unbalanced '<'");
+ c = '>';
+ p--;
+ }
+ }
+
+ if (tTd(22, 101))
+ sm_dprintf("c=%c, s=%d; ", c, state);
+
+ /* chew up special characters */
+ *q = '\0';
+ if (bslashmode)
+ {
+ bslashmode = false;
+
+ /* kludge \! for naive users */
+ if (cmntcnt > 0)
+ {
+ c = NOCHAR;
+ continue;
+ }
+ else if (c != '!' || state == QST)
+ {
+ *q++ = '\\';
+ continue;
+ }
+ }
+
+ if (c == '\\')
+ {
+ bslashmode = true;
+ }
+ else if (state == QST)
+ {
+ /* EMPTY */
+ /* do nothing, just avoid next clauses */
+ }
+ else if (c == '(' && toktab['('] == SPC)
+ {
+ cmntcnt++;
+ c = NOCHAR;
+ }
+ else if (c == ')' && toktab['('] == SPC)
+ {
+ if (cmntcnt <= 0)
+ {
+ usrerr("553 Unbalanced ')'");
+ c = NOCHAR;
+ }
+ else
+ cmntcnt--;
+ }
+ else if (cmntcnt > 0)
+ {
+ c = NOCHAR;
+ }
+ else if (c == '<')
+ {
+ char *ptr = p;
+
+ anglecnt++;
+ while (isascii(*ptr) && isspace(*ptr))
+ ptr++;
+ if (*ptr == '@')
+ route_syntax = true;
+ }
+ else if (c == '>')
+ {
+ if (anglecnt <= 0)
+ {
+ usrerr("553 Unbalanced '>'");
+ c = NOCHAR;
+ }
+ else
+ anglecnt--;
+ route_syntax = false;
+ }
+ else if (delim == ' ' && isascii(c) && isspace(c))
+ c = ' ';
+
+ if (c == NOCHAR)
+ continue;
+
+ /* see if this is end of input */
+ if (c == delim && anglecnt <= 0 && state != QST)
+ break;
+
+ newstate = StateTab[state][toktab[c & 0xff]];
+ if (tTd(22, 101))
+ sm_dprintf("ns=%02o\n", newstate);
+ state = newstate & TYPE;
+ if (state == ILL)
+ {
+ if (isascii(c) && isprint(c))
+ usrerr("553 Illegal character %c", c);
+ else
+ usrerr("553 Illegal character 0x%02x",
+ c & 0x0ff);
+ }
+ if (bitset(M, newstate))
+ c = NOCHAR;
+ if (bitset(B, newstate))
+ break;
+ }
+
+ /* new token */
+ if (tok != q)
+ {
+ *q++ = '\0';
+ if (tTd(22, 36))
+ {
+ sm_dprintf("tok=");
+ xputs(tok);
+ sm_dprintf("\n");
+ }
+ if (avp >= &av[MAXATOM])
+ {
+ usrerr("553 5.1.0 prescan: too many tokens");
+ goto returnnull;
+ }
+ if (q - tok > MAXNAME)
+ {
+ usrerr("553 5.1.0 prescan: token too long");
+ goto returnnull;
+ }
+ *avp++ = tok;
+ }
+ } while (c != '\0' && (c != delim || anglecnt > 0));
+ *avp = NULL;
+ p--;
+ if (delimptr != NULL)
+ *delimptr = p;
+ if (tTd(22, 12))
+ {
+ sm_dprintf("prescan==>");
+ printav(av);
+ }
+ CurEnv->e_to = saveto;
+ if (av[0] == NULL)
+ {
+ if (tTd(22, 1))
+ sm_dprintf("prescan: null leading token\n");
+ return NULL;
+ }
+ return av;
+}
+/*
+** REWRITE -- apply rewrite rules to token vector.
+**
+** This routine is an ordered production system. Each rewrite
+** rule has a LHS (called the pattern) and a RHS (called the
+** rewrite); 'rwr' points the the current rewrite rule.
+**
+** For each rewrite rule, 'avp' points the address vector we
+** are trying to match against, and 'pvp' points to the pattern.
+** If pvp points to a special match value (MATCHZANY, MATCHANY,
+** MATCHONE, MATCHCLASS, MATCHNCLASS) then the address in avp
+** matched is saved away in the match vector (pointed to by 'mvp').
+**
+** When a match between avp & pvp does not match, we try to
+** back out. If we back up over MATCHONE, MATCHCLASS, or MATCHNCLASS
+** we must also back out the match in mvp. If we reach a
+** MATCHANY or MATCHZANY we just extend the match and start
+** over again.
+**
+** When we finally match, we rewrite the address vector
+** and try over again.
+**
+** Parameters:
+** pvp -- pointer to token vector.
+** ruleset -- the ruleset to use for rewriting.
+** reclevel -- recursion level (to catch loops).
+** e -- the current envelope.
+** maxatom -- maximum length of buffer (usually MAXATOM)
+**
+** Returns:
+** A status code. If EX_TEMPFAIL, higher level code should
+** attempt recovery.
+**
+** Side Effects:
+** pvp is modified.
+*/
+
+struct match
+{
+ char **match_first; /* first token matched */
+ char **match_last; /* last token matched */
+ char **match_pattern; /* pointer to pattern */
+};
+
+int
+rewrite(pvp, ruleset, reclevel, e, maxatom)
+ char **pvp;
+ int ruleset;
+ int reclevel;
+ register ENVELOPE *e;
+ int maxatom;
+{
+ register char *ap; /* address pointer */
+ register char *rp; /* rewrite pointer */
+ register char *rulename; /* ruleset name */
+ register char *prefix;
+ register char **avp; /* address vector pointer */
+ register char **rvp; /* rewrite vector pointer */
+ register struct match *mlp; /* cur ptr into mlist */
+ register struct rewrite *rwr; /* pointer to current rewrite rule */
+ int ruleno; /* current rule number */
+ int rstat = EX_OK; /* return status */
+ int loopcount;
+ struct match mlist[MAXMATCH]; /* stores match on LHS */
+ char *npvp[MAXATOM + 1]; /* temporary space for rebuild */
+ char buf[MAXLINE];
+ char name[6];
+
+ if (ruleset < 0 || ruleset >= MAXRWSETS)
+ {
+ syserr("554 5.3.5 rewrite: illegal ruleset number %d", ruleset);
+ return EX_CONFIG;
+ }
+ rulename = RuleSetNames[ruleset];
+ if (rulename == NULL)
+ {
+ (void) sm_snprintf(name, sizeof name, "%d", ruleset);
+ rulename = name;
+ }
+ if (OpMode == MD_TEST)
+ prefix = "";
+ else
+ prefix = "rewrite: ruleset ";
+ if (OpMode == MD_TEST)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s%-16.16s input:", prefix, rulename);
+ printav(pvp);
+ }
+ else if (tTd(21, 1))
+ {
+ sm_dprintf("%s%-16.16s input:", prefix, rulename);
+ printav(pvp);
+ }
+ if (reclevel++ > MaxRuleRecursion)
+ {
+ syserr("rewrite: excessive recursion (max %d), ruleset %s",
+ MaxRuleRecursion, rulename);
+ return EX_CONFIG;
+ }
+ if (pvp == NULL)
+ return EX_USAGE;
+
+ /*
+ ** Run through the list of rewrite rules, applying
+ ** any that match.
+ */
+
+ ruleno = 1;
+ loopcount = 0;
+ for (rwr = RewriteRules[ruleset]; rwr != NULL; )
+ {
+ int status;
+
+ /* if already canonical, quit now */
+ if (pvp[0] != NULL && (pvp[0][0] & 0377) == CANONNET)
+ break;
+
+ if (tTd(21, 12))
+ {
+ if (tTd(21, 15))
+ sm_dprintf("-----trying rule (line %d):",
+ rwr->r_line);
+ else
+ sm_dprintf("-----trying rule:");
+ printav(rwr->r_lhs);
+ }
+
+ /* try to match on this rule */
+ mlp = mlist;
+ rvp = rwr->r_lhs;
+ avp = pvp;
+ if (++loopcount > 100)
+ {
+ syserr("554 5.3.5 Infinite loop in ruleset %s, rule %d",
+ rulename, ruleno);
+ if (tTd(21, 1))
+ {
+ sm_dprintf("workspace: ");
+ printav(pvp);
+ }
+ break;
+ }
+
+ while ((ap = *avp) != NULL || *rvp != NULL)
+ {
+ rp = *rvp;
+ if (tTd(21, 35))
+ {
+ sm_dprintf("ADVANCE rp=");
+ xputs(rp);
+ sm_dprintf(", ap=");
+ xputs(ap);
+ sm_dprintf("\n");
+ }
+ if (rp == NULL)
+ {
+ /* end-of-pattern before end-of-address */
+ goto backup;
+ }
+ if (ap == NULL && (*rp & 0377) != MATCHZANY &&
+ (*rp & 0377) != MATCHZERO)
+ {
+ /* end-of-input with patterns left */
+ goto backup;
+ }
+
+ switch (*rp & 0377)
+ {
+ case MATCHCLASS:
+ /* match any phrase in a class */
+ mlp->match_pattern = rvp;
+ mlp->match_first = avp;
+ extendclass:
+ ap = *avp;
+ if (ap == NULL)
+ goto backup;
+ mlp->match_last = avp++;
+ cataddr(mlp->match_first, mlp->match_last,
+ buf, sizeof buf, '\0');
+ if (!wordinclass(buf, rp[1]))
+ {
+ if (tTd(21, 36))
+ {
+ sm_dprintf("EXTEND rp=");
+ xputs(rp);
+ sm_dprintf(", ap=");
+ xputs(ap);
+ sm_dprintf("\n");
+ }
+ goto extendclass;
+ }
+ if (tTd(21, 36))
+ sm_dprintf("CLMATCH\n");
+ mlp++;
+ break;
+
+ case MATCHNCLASS:
+ /* match any token not in a class */
+ if (wordinclass(ap, rp[1]))
+ goto backup;
+
+ /* FALLTHROUGH */
+
+ case MATCHONE:
+ case MATCHANY:
+ /* match exactly one token */
+ mlp->match_pattern = rvp;
+ mlp->match_first = avp;
+ mlp->match_last = avp++;
+ mlp++;
+ break;
+
+ case MATCHZANY:
+ /* match zero or more tokens */
+ mlp->match_pattern = rvp;
+ mlp->match_first = avp;
+ mlp->match_last = avp - 1;
+ mlp++;
+ break;
+
+ case MATCHZERO:
+ /* match zero tokens */
+ break;
+
+ case MACRODEXPAND:
+ /*
+ ** Match against run-time macro.
+ ** This algorithm is broken for the
+ ** general case (no recursive macros,
+ ** improper tokenization) but should
+ ** work for the usual cases.
+ */
+
+ ap = macvalue(rp[1], e);
+ mlp->match_first = avp;
+ if (tTd(21, 2))
+ sm_dprintf("rewrite: LHS $&{%s} => \"%s\"\n",
+ macname(rp[1]),
+ ap == NULL ? "(NULL)" : ap);
+
+ if (ap == NULL)
+ break;
+ while (*ap != '\0')
+ {
+ if (*avp == NULL ||
+ sm_strncasecmp(ap, *avp,
+ strlen(*avp)) != 0)
+ {
+ /* no match */
+ avp = mlp->match_first;
+ goto backup;
+ }
+ ap += strlen(*avp++);
+ }
+
+ /* match */
+ break;
+
+ default:
+ /* must have exact match */
+ if (sm_strcasecmp(rp, ap))
+ goto backup;
+ avp++;
+ break;
+ }
+
+ /* successful match on this token */
+ rvp++;
+ continue;
+
+ backup:
+ /* match failed -- back up */
+ while (--mlp >= mlist)
+ {
+ rvp = mlp->match_pattern;
+ rp = *rvp;
+ avp = mlp->match_last + 1;
+ ap = *avp;
+
+ if (tTd(21, 36))
+ {
+ sm_dprintf("BACKUP rp=");
+ xputs(rp);
+ sm_dprintf(", ap=");
+ xputs(ap);
+ sm_dprintf("\n");
+ }
+
+ if (ap == NULL)
+ {
+ /* run off the end -- back up again */
+ continue;
+ }
+ if ((*rp & 0377) == MATCHANY ||
+ (*rp & 0377) == MATCHZANY)
+ {
+ /* extend binding and continue */
+ mlp->match_last = avp++;
+ rvp++;
+ mlp++;
+ break;
+ }
+ if ((*rp & 0377) == MATCHCLASS)
+ {
+ /* extend binding and try again */
+ mlp->match_last = avp;
+ goto extendclass;
+ }
+ }
+
+ if (mlp < mlist)
+ {
+ /* total failure to match */
+ break;
+ }
+ }
+
+ /*
+ ** See if we successfully matched
+ */
+
+ if (mlp < mlist || *rvp != NULL)
+ {
+ if (tTd(21, 10))
+ sm_dprintf("----- rule fails\n");
+ rwr = rwr->r_next;
+ ruleno++;
+ loopcount = 0;
+ continue;
+ }
+
+ rvp = rwr->r_rhs;
+ if (tTd(21, 12))
+ {
+ sm_dprintf("-----rule matches:");
+ printav(rvp);
+ }
+
+ rp = *rvp;
+ if (rp != NULL)
+ {
+ if ((*rp & 0377) == CANONUSER)
+ {
+ rvp++;
+ rwr = rwr->r_next;
+ ruleno++;
+ loopcount = 0;
+ }
+ else if ((*rp & 0377) == CANONHOST)
+ {
+ rvp++;
+ rwr = NULL;
+ }
+ }
+
+ /* substitute */
+ for (avp = npvp; *rvp != NULL; rvp++)
+ {
+ register struct match *m;
+ register char **pp;
+
+ rp = *rvp;
+ if ((*rp & 0377) == MATCHREPL)
+ {
+ /* substitute from LHS */
+ m = &mlist[rp[1] - '1'];
+ if (m < mlist || m >= mlp)
+ {
+ syserr("554 5.3.5 rewrite: ruleset %s: replacement $%c out of bounds",
+ rulename, rp[1]);
+ return EX_CONFIG;
+ }
+ if (tTd(21, 15))
+ {
+ sm_dprintf("$%c:", rp[1]);
+ pp = m->match_first;
+ while (pp <= m->match_last)
+ {
+ sm_dprintf(" %p=\"", *pp);
+ sm_dflush();
+ sm_dprintf("%s\"", *pp++);
+ }
+ sm_dprintf("\n");
+ }
+ pp = m->match_first;
+ while (pp <= m->match_last)
+ {
+ if (avp >= &npvp[maxatom])
+ {
+ syserr("554 5.3.0 rewrite: expansion too long");
+ if (LogLevel > 9)
+ sm_syslog(LOG_ERR,
+ e->e_id,
+ "rewrite: expansion too long, ruleset=%s, ruleno=%d",
+ rulename,
+ ruleno);
+ return EX_DATAERR;
+ }
+ *avp++ = *pp++;
+ }
+ }
+ else
+ {
+ /* some sort of replacement */
+ if (avp >= &npvp[maxatom])
+ {
+ toolong:
+ syserr("554 5.3.0 rewrite: expansion too long");
+ if (LogLevel > 9)
+ sm_syslog(LOG_ERR, e->e_id,
+ "rewrite: expansion too long, ruleset=%s, ruleno=%d",
+ rulename, ruleno);
+ return EX_DATAERR;
+ }
+ if ((*rp & 0377) != MACRODEXPAND)
+ {
+ /* vanilla replacement */
+ *avp++ = rp;
+ }
+ else
+ {
+ /* $&{x} replacement */
+ char *mval = macvalue(rp[1], e);
+ char **xpvp;
+ int trsize = 0;
+ static size_t pvpb1_size = 0;
+ static char **pvpb1 = NULL;
+ char pvpbuf[PSBUFSIZE];
+
+ if (tTd(21, 2))
+ sm_dprintf("rewrite: RHS $&{%s} => \"%s\"\n",
+ macname(rp[1]),
+ mval == NULL ? "(NULL)" : mval);
+ if (mval == NULL || *mval == '\0')
+ continue;
+
+ /* save the remainder of the input */
+ for (xpvp = pvp; *xpvp != NULL; xpvp++)
+ trsize += sizeof *xpvp;
+ if ((size_t) trsize > pvpb1_size)
+ {
+ if (pvpb1 != NULL)
+ sm_free(pvpb1);
+ pvpb1 = (char **)
+ sm_pmalloc_x(trsize);
+ pvpb1_size = trsize;
+ }
+
+ memmove((char *) pvpb1,
+ (char *) pvp,
+ trsize);
+
+ /* scan the new replacement */
+ xpvp = prescan(mval, '\0', pvpbuf,
+ sizeof pvpbuf, NULL,
+ NULL);
+ if (xpvp == NULL)
+ {
+ /* prescan pre-printed error */
+ return EX_DATAERR;
+ }
+
+ /* insert it into the output stream */
+ while (*xpvp != NULL)
+ {
+ if (tTd(21, 19))
+ sm_dprintf(" ... %s\n",
+ *xpvp);
+ *avp++ = sm_rpool_strdup_x(
+ e->e_rpool, *xpvp);
+ if (avp >= &npvp[maxatom])
+ goto toolong;
+ xpvp++;
+ }
+ if (tTd(21, 19))
+ sm_dprintf(" ... DONE\n");
+
+ /* restore the old trailing input */
+ memmove((char *) pvp,
+ (char *) pvpb1,
+ trsize);
+ }
+ }
+ }
+ *avp++ = NULL;
+
+ /*
+ ** Check for any hostname/keyword lookups.
+ */
+
+ for (rvp = npvp; *rvp != NULL; rvp++)
+ {
+ char **hbrvp;
+ char **xpvp;
+ int trsize;
+ char *replac;
+ int endtoken;
+ STAB *map;
+ char *mapname;
+ char **key_rvp;
+ char **arg_rvp;
+ char **default_rvp;
+ char cbuf[MAXNAME + 1];
+ char *pvpb1[MAXATOM + 1];
+ char *argvect[10];
+ char pvpbuf[PSBUFSIZE];
+ char *nullpvp[1];
+
+ if ((**rvp & 0377) != HOSTBEGIN &&
+ (**rvp & 0377) != LOOKUPBEGIN)
+ continue;
+
+ /*
+ ** Got a hostname/keyword lookup.
+ **
+ ** This could be optimized fairly easily.
+ */
+
+ hbrvp = rvp;
+ if ((**rvp & 0377) == HOSTBEGIN)
+ {
+ endtoken = HOSTEND;
+ mapname = "host";
+ }
+ else
+ {
+ endtoken = LOOKUPEND;
+ mapname = *++rvp;
+ }
+ map = stab(mapname, ST_MAP, ST_FIND);
+ if (map == NULL)
+ syserr("554 5.3.0 rewrite: map %s not found", mapname);
+
+ /* extract the match part */
+ key_rvp = ++rvp;
+ default_rvp = NULL;
+ arg_rvp = argvect;
+ xpvp = NULL;
+ replac = pvpbuf;
+ while (*rvp != NULL && (**rvp & 0377) != endtoken)
+ {
+ int nodetype = **rvp & 0377;
+
+ if (nodetype != CANONHOST && nodetype != CANONUSER)
+ {
+ rvp++;
+ continue;
+ }
+
+ *rvp++ = NULL;
+
+ if (xpvp != NULL)
+ {
+ cataddr(xpvp, NULL, replac,
+ &pvpbuf[sizeof pvpbuf] - replac,
+ '\0');
+ *++arg_rvp = replac;
+ replac += strlen(replac) + 1;
+ xpvp = NULL;
+ }
+ switch (nodetype)
+ {
+ case CANONHOST:
+ xpvp = rvp;
+ break;
+
+ case CANONUSER:
+ default_rvp = rvp;
+ break;
+ }
+ }
+ if (*rvp != NULL)
+ *rvp++ = NULL;
+ if (xpvp != NULL)
+ {
+ cataddr(xpvp, NULL, replac,
+ &pvpbuf[sizeof pvpbuf] - replac,
+ '\0');
+ *++arg_rvp = replac;
+ }
+ *++arg_rvp = NULL;
+
+ /* save the remainder of the input string */
+ trsize = (int) (avp - rvp + 1) * sizeof *rvp;
+ memmove((char *) pvpb1, (char *) rvp, trsize);
+
+ /* look it up */
+ cataddr(key_rvp, NULL, cbuf, sizeof cbuf,
+ map == NULL ? '\0' : map->s_map.map_spacesub);
+ argvect[0] = cbuf;
+ replac = map_lookup(map, cbuf, argvect, &rstat, e);
+
+ /* if no replacement, use default */
+ if (replac == NULL && default_rvp != NULL)
+ {
+ /* create the default */
+ cataddr(default_rvp, NULL, cbuf, sizeof cbuf, '\0');
+ replac = cbuf;
+ }
+
+ if (replac == NULL)
+ {
+ xpvp = key_rvp;
+ }
+ else if (*replac == '\0')
+ {
+ /* null replacement */
+ nullpvp[0] = NULL;
+ xpvp = nullpvp;
+ }
+ else
+ {
+ /* scan the new replacement */
+ xpvp = prescan(replac, '\0', pvpbuf,
+ sizeof pvpbuf, NULL, NULL);
+ if (xpvp == NULL)
+ {
+ /* prescan already printed error */
+ return EX_DATAERR;
+ }
+ }
+
+ /* append it to the token list */
+ for (avp = hbrvp; *xpvp != NULL; xpvp++)
+ {
+ *avp++ = sm_rpool_strdup_x(e->e_rpool, *xpvp);
+ if (avp >= &npvp[maxatom])
+ goto toolong;
+ }
+
+ /* restore the old trailing information */
+ rvp = avp - 1;
+ for (xpvp = pvpb1; (*avp++ = *xpvp++) != NULL; )
+ if (avp >= &npvp[maxatom])
+ goto toolong;
+ }
+
+ /*
+ ** Check for subroutine calls.
+ */
+
+ status = callsubr(npvp, reclevel, e);
+ if (rstat == EX_OK || status == EX_TEMPFAIL)
+ rstat = status;
+
+ /* copy vector back into original space. */
+ for (avp = npvp; *avp++ != NULL;)
+ continue;
+ memmove((char *) pvp, (char *) npvp,
+ (int) (avp - npvp) * sizeof *avp);
+
+ if (tTd(21, 4))
+ {
+ sm_dprintf("rewritten as:");
+ printav(pvp);
+ }
+ }
+
+ if (OpMode == MD_TEST)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s%-16.16s returns:", prefix, rulename);
+ printav(pvp);
+ }
+ else if (tTd(21, 1))
+ {
+ sm_dprintf("%s%-16.16s returns:", prefix, rulename);
+ printav(pvp);
+ }
+ return rstat;
+}
+/*
+** CALLSUBR -- call subroutines in rewrite vector
+**
+** Parameters:
+** pvp -- pointer to token vector.
+** reclevel -- the current recursion level.
+** e -- the current envelope.
+**
+** Returns:
+** The status from the subroutine call.
+**
+** Side Effects:
+** pvp is modified.
+*/
+
+static int
+callsubr(pvp, reclevel, e)
+ char **pvp;
+ int reclevel;
+ ENVELOPE *e;
+{
+ char **avp;
+ register int i;
+ int subr, j;
+ int nsubr;
+ int status;
+ int rstat = EX_OK;
+#define MAX_SUBR 16
+ int subrnumber[MAX_SUBR];
+ int subrindex[MAX_SUBR];
+
+ nsubr = 0;
+
+ /*
+ ** Look for subroutine calls in pvp, collect them into subr*[]
+ ** We will perform the calls in the next loop, because we will
+ ** call the "last" subroutine first to avoid recursive calls
+ ** and too much copying.
+ */
+
+ for (avp = pvp, j = 0; *avp != NULL; avp++, j++)
+ {
+ if ((**avp & 0377) == CALLSUBR && avp[1] != NULL)
+ {
+ stripquotes(avp[1]);
+ subr = strtorwset(avp[1], NULL, ST_FIND);
+ if (subr < 0)
+ {
+ syserr("554 5.3.5 Unknown ruleset %s", avp[1]);
+ return EX_CONFIG;
+ }
+
+ /*
+ ** XXX instead of doing this we could optimize
+ ** the rules after reading them: just remove
+ ** calls to empty rulesets
+ */
+
+ /* subroutine is an empty ruleset? don't call it */
+ if (RewriteRules[subr] == NULL)
+ {
+ if (tTd(21, 3))
+ sm_dprintf("-----skip subr %s (%d)\n",
+ avp[1], subr);
+ for (i = 2; avp[i] != NULL; i++)
+ avp[i - 2] = avp[i];
+ avp[i - 2] = NULL;
+ continue;
+ }
+ if (++nsubr >= MAX_SUBR)
+ {
+ syserr("554 5.3.0 Too many subroutine calls (%d max)",
+ MAX_SUBR);
+ return EX_CONFIG;
+ }
+ subrnumber[nsubr] = subr;
+ subrindex[nsubr] = j;
+ }
+ }
+
+ /*
+ ** Perform the actual subroutines calls, "last" one first, i.e.,
+ ** go from the right to the left through all calls,
+ ** do the rewriting in place.
+ */
+
+ for (; nsubr > 0; nsubr--)
+ {
+ subr = subrnumber[nsubr];
+ avp = pvp + subrindex[nsubr];
+
+ /* remove the subroutine call and name */
+ for (i = 2; avp[i] != NULL; i++)
+ avp[i - 2] = avp[i];
+ avp[i - 2] = NULL;
+
+ /*
+ ** Now we need to call the ruleset specified for
+ ** the subroutine. we can do this inplace since
+ ** we call the "last" subroutine first.
+ */
+
+ status = rewrite(avp, subr, reclevel, e,
+ MAXATOM - subrindex[nsubr]);
+ if (status != EX_OK && status != EX_TEMPFAIL)
+ return status;
+ if (rstat == EX_OK || status == EX_TEMPFAIL)
+ rstat = status;
+ }
+ return rstat;
+}
+/*
+** MAP_LOOKUP -- do lookup in map
+**
+** Parameters:
+** smap -- the map to use for the lookup.
+** key -- the key to look up.
+** argvect -- arguments to pass to the map lookup.
+** pstat -- a pointer to an integer in which to store the
+** status from the lookup.
+** e -- the current envelope.
+**
+** Returns:
+** The result of the lookup.
+** NULL -- if there was no data for the given key.
+*/
+
+static char *
+map_lookup(smap, key, argvect, pstat, e)
+ STAB *smap;
+ char key[];
+ char **argvect;
+ int *pstat;
+ ENVELOPE *e;
+{
+ auto int status = EX_OK;
+ MAP *map;
+ char *replac;
+
+ if (smap == NULL)
+ return NULL;
+
+ map = &smap->s_map;
+ DYNOPENMAP(map);
+
+ if (e->e_sendmode == SM_DEFER &&
+ bitset(MF_DEFER, map->map_mflags))
+ {
+ /* don't do any map lookups */
+ if (tTd(60, 1))
+ sm_dprintf("map_lookup(%s, %s) => DEFERRED\n",
+ smap->s_name, key);
+ *pstat = EX_TEMPFAIL;
+ return NULL;
+ }
+
+ if (!bitset(MF_KEEPQUOTES, map->map_mflags))
+ stripquotes(key);
+
+ if (tTd(60, 1))
+ {
+ sm_dprintf("map_lookup(%s, %s", smap->s_name, key);
+ if (tTd(60, 5))
+ {
+ int i;
+
+ for (i = 0; argvect[i] != NULL; i++)
+ sm_dprintf(", %%%d=%s", i, argvect[i]);
+ }
+ sm_dprintf(") => ");
+ }
+ replac = (*map->map_class->map_lookup)(map, key, argvect, &status);
+ if (tTd(60, 1))
+ sm_dprintf("%s (%d)\n",
+ replac != NULL ? replac : "NOT FOUND",
+ status);
+
+ /* should recover if status == EX_TEMPFAIL */
+ if (status == EX_TEMPFAIL && !bitset(MF_NODEFER, map->map_mflags))
+ {
+ *pstat = EX_TEMPFAIL;
+ if (tTd(60, 1))
+ sm_dprintf("map_lookup(%s, %s) tempfail: errno=%d\n",
+ smap->s_name, key, errno);
+ if (e->e_message == NULL)
+ {
+ char mbuf[320];
+
+ (void) sm_snprintf(mbuf, sizeof mbuf,
+ "%.80s map: lookup (%s): deferred",
+ smap->s_name,
+ shortenstring(key, MAXSHORTSTR));
+ e->e_message = sm_rpool_strdup_x(e->e_rpool, mbuf);
+ }
+ }
+ if (status == EX_TEMPFAIL && map->map_tapp != NULL)
+ {
+ size_t i = strlen(key) + strlen(map->map_tapp) + 1;
+ static char *rwbuf = NULL;
+ static size_t rwbuflen = 0;
+
+ if (i > rwbuflen)
+ {
+ if (rwbuf != NULL)
+ sm_free(rwbuf);
+ rwbuflen = i;
+ rwbuf = (char *) sm_pmalloc_x(rwbuflen);
+ }
+ (void) sm_strlcpyn(rwbuf, rwbuflen, 2, key, map->map_tapp);
+ if (tTd(60, 4))
+ sm_dprintf("map_lookup tempfail: returning \"%s\"\n",
+ rwbuf);
+ return rwbuf;
+ }
+ return replac;
+}
+/*
+** INITERRMAILERS -- initialize error and discard mailers
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** initializes error and discard mailers.
+*/
+
+static MAILER discardmailer;
+static MAILER errormailer;
+static char *discardargv[] = { "DISCARD", NULL };
+static char *errorargv[] = { "ERROR", NULL };
+
+void
+initerrmailers()
+{
+ if (discardmailer.m_name == NULL)
+ {
+ /* initialize the discard mailer */
+ discardmailer.m_name = "*discard*";
+ discardmailer.m_mailer = "DISCARD";
+ discardmailer.m_argv = discardargv;
+ }
+ if (errormailer.m_name == NULL)
+ {
+ /* initialize the bogus mailer */
+ errormailer.m_name = "*error*";
+ errormailer.m_mailer = "ERROR";
+ errormailer.m_argv = errorargv;
+ }
+}
+/*
+** BUILDADDR -- build address from token vector.
+**
+** Parameters:
+** tv -- token vector.
+** a -- pointer to address descriptor to fill.
+** If NULL, one will be allocated.
+** flags -- info regarding whether this is a sender or
+** a recipient.
+** e -- the current envelope.
+**
+** Returns:
+** NULL if there was an error.
+** 'a' otherwise.
+**
+** Side Effects:
+** fills in 'a'
+*/
+
+static struct errcodes
+{
+ char *ec_name; /* name of error code */
+ int ec_code; /* numeric code */
+} ErrorCodes[] =
+{
+ { "usage", EX_USAGE },
+ { "nouser", EX_NOUSER },
+ { "nohost", EX_NOHOST },
+ { "unavailable", EX_UNAVAILABLE },
+ { "software", EX_SOFTWARE },
+ { "tempfail", EX_TEMPFAIL },
+ { "protocol", EX_PROTOCOL },
+ { "config", EX_CONFIG },
+ { NULL, EX_UNAVAILABLE }
+};
+
+static ADDRESS *
+buildaddr(tv, a, flags, e)
+ register char **tv;
+ register ADDRESS *a;
+ int flags;
+ register ENVELOPE *e;
+{
+ bool tempfail = false;
+ struct mailer **mp;
+ register struct mailer *m;
+ register char *p;
+ char *mname;
+ char **hostp;
+ char hbuf[MAXNAME + 1];
+ static char ubuf[MAXNAME + 2];
+
+ if (tTd(24, 5))
+ {
+ sm_dprintf("buildaddr, flags=%x, tv=", flags);
+ printav(tv);
+ }
+
+ if (a == NULL)
+ a = (ADDRESS *) sm_rpool_malloc_x(e->e_rpool, sizeof *a);
+ memset((char *) a, '\0', sizeof *a);
+ hbuf[0] = '\0';
+
+ /* set up default error return flags */
+ a->q_flags |= DefaultNotify;
+
+ /* figure out what net/mailer to use */
+ if (*tv == NULL || (**tv & 0377) != CANONNET)
+ {
+ syserr("554 5.3.5 buildaddr: no mailer in parsed address");
+badaddr:
+#if _FFR_ALLOW_S0_ERROR_4XX
+ /*
+ ** ExitStat may have been set by an earlier map open
+ ** failure (to a permanent error (EX_OSERR) in syserr())
+ ** so we also need to check if this particular $#error
+ ** return wanted a 4XX failure.
+ **
+ ** XXX the real fix is probably to set ExitStat correctly,
+ ** i.e., to EX_TEMPFAIL if the map open is just a temporary
+ ** error.
+ **
+ ** tempfail is tested here even if _FFR_ALLOW_S0_ERROR_4XX
+ ** is not set; that's ok because it is initialized to false.
+ */
+#endif /* _FFR_ALLOW_S0_ERROR_4XX */
+
+ if (ExitStat == EX_TEMPFAIL || tempfail)
+ a->q_state = QS_QUEUEUP;
+ else
+ {
+ a->q_state = QS_BADADDR;
+ a->q_mailer = &errormailer;
+ }
+ return a;
+ }
+ mname = *++tv;
+
+ /* extract host and user portions */
+ if (*++tv != NULL && (**tv & 0377) == CANONHOST)
+ hostp = ++tv;
+ else
+ hostp = NULL;
+ while (*tv != NULL && (**tv & 0377) != CANONUSER)
+ tv++;
+ if (*tv == NULL)
+ {
+ syserr("554 5.3.5 buildaddr: no user");
+ goto badaddr;
+ }
+ if (tv == hostp)
+ hostp = NULL;
+ else if (hostp != NULL)
+ cataddr(hostp, tv - 1, hbuf, sizeof hbuf, '\0');
+ cataddr(++tv, NULL, ubuf, sizeof ubuf, ' ');
+
+ /* save away the host name */
+ if (sm_strcasecmp(mname, "error") == 0)
+ {
+ /* Set up triplet for use by -bv */
+ a->q_mailer = &errormailer;
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, ubuf);
+ /* XXX wrong place? */
+
+ if (hostp != NULL)
+ {
+ register struct errcodes *ep;
+
+ a->q_host = sm_rpool_strdup_x(e->e_rpool, hbuf);
+ if (strchr(hbuf, '.') != NULL)
+ {
+ a->q_status = sm_rpool_strdup_x(e->e_rpool,
+ hbuf);
+ setstat(dsntoexitstat(hbuf));
+ }
+ else if (isascii(hbuf[0]) && isdigit(hbuf[0]))
+ {
+ setstat(atoi(hbuf));
+ }
+ else
+ {
+ for (ep = ErrorCodes; ep->ec_name != NULL; ep++)
+ if (sm_strcasecmp(ep->ec_name, hbuf) == 0)
+ break;
+ setstat(ep->ec_code);
+ }
+ }
+ else
+ {
+ a->q_host = NULL;
+ setstat(EX_UNAVAILABLE);
+ }
+ stripquotes(ubuf);
+ if (ISSMTPCODE(ubuf) && ubuf[3] == ' ')
+ {
+ char fmt[16];
+ int off;
+
+ if ((off = isenhsc(ubuf + 4, ' ')) > 0)
+ {
+ ubuf[off + 4] = '\0';
+ off += 5;
+ }
+ else
+ {
+ off = 4;
+ ubuf[3] = '\0';
+ }
+ (void) sm_strlcpyn(fmt, sizeof fmt, 2, ubuf, " %s");
+ if (off > 4)
+ usrerr(fmt, ubuf + off);
+ else if (isenhsc(hbuf, '\0') > 0)
+ usrerrenh(hbuf, fmt, ubuf + off);
+ else
+ usrerr(fmt, ubuf + off);
+ /* XXX ubuf[off - 1] = ' '; */
+#if _FFR_ALLOW_S0_ERROR_4XX
+ if (ubuf[0] == '4')
+ tempfail = true;
+#endif /* _FFR_ALLOW_S0_ERROR_4XX */
+ }
+ else
+ {
+ usrerr("553 5.3.0 %s", ubuf);
+ }
+ goto badaddr;
+ }
+
+ for (mp = Mailer; (m = *mp++) != NULL; )
+ {
+ if (sm_strcasecmp(m->m_name, mname) == 0)
+ break;
+ }
+ if (m == NULL)
+ {
+ syserr("554 5.3.5 buildaddr: unknown mailer %s", mname);
+ goto badaddr;
+ }
+ a->q_mailer = m;
+
+ /* figure out what host (if any) */
+ if (hostp == NULL)
+ {
+ if (!bitnset(M_LOCALMAILER, m->m_flags))
+ {
+ syserr("554 5.3.5 buildaddr: no host");
+ goto badaddr;
+ }
+ a->q_host = NULL;
+ }
+ else
+ a->q_host = sm_rpool_strdup_x(e->e_rpool, hbuf);
+
+ /* figure out the user */
+ p = ubuf;
+ if (bitnset(M_CHECKUDB, m->m_flags) && *p == '@')
+ {
+ p++;
+ tv++;
+ a->q_flags |= QNOTREMOTE;
+ }
+
+ /* do special mapping for local mailer */
+ if (*p == '"')
+ p++;
+ if (*p == '|' && bitnset(M_CHECKPROG, m->m_flags))
+ a->q_mailer = m = ProgMailer;
+ else if (*p == '/' && bitnset(M_CHECKFILE, m->m_flags))
+ a->q_mailer = m = FileMailer;
+ else if (*p == ':' && bitnset(M_CHECKINCLUDE, m->m_flags))
+ {
+ /* may be :include: */
+ stripquotes(ubuf);
+ if (sm_strncasecmp(ubuf, ":include:", 9) == 0)
+ {
+ /* if :include:, don't need further rewriting */
+ a->q_mailer = m = InclMailer;
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, &ubuf[9]);
+ return a;
+ }
+ }
+
+ /* rewrite according recipient mailer rewriting rules */
+ macdefine(&e->e_macro, A_PERM, 'h', a->q_host);
+
+ if (ConfigLevel >= 10 ||
+ !bitset(RF_SENDERADDR|RF_HEADERADDR, flags))
+ {
+ /* sender addresses done later */
+ (void) REWRITE(tv, 2, e);
+ if (m->m_re_rwset > 0)
+ (void) REWRITE(tv, m->m_re_rwset, e);
+ }
+ (void) REWRITE(tv, 4, e);
+
+ /* save the result for the command line/RCPT argument */
+ cataddr(tv, NULL, ubuf, sizeof ubuf, '\0');
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, ubuf);
+
+ /*
+ ** Do mapping to lower case as requested by mailer
+ */
+
+ if (a->q_host != NULL && !bitnset(M_HST_UPPER, m->m_flags))
+ makelower(a->q_host);
+ if (!bitnset(M_USR_UPPER, m->m_flags))
+ makelower(a->q_user);
+
+ if (tTd(24, 6))
+ {
+ sm_dprintf("buildaddr => ");
+ printaddr(a, false);
+ }
+ return a;
+}
+/*
+** CATADDR -- concatenate pieces of addresses (putting in <LWSP> subs)
+**
+** Parameters:
+** pvp -- parameter vector to rebuild.
+** evp -- last parameter to include. Can be NULL to
+** use entire pvp.
+** buf -- buffer to build the string into.
+** sz -- size of buf.
+** spacesub -- the space separator character; if '\0',
+** use SpaceSub.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Destroys buf.
+*/
+
+void
+cataddr(pvp, evp, buf, sz, spacesub)
+ char **pvp;
+ char **evp;
+ char *buf;
+ register int sz;
+ int spacesub;
+{
+ bool oatomtok = false;
+ bool natomtok = false;
+ register int i;
+ register char *p;
+
+ if (sz <= 0)
+ return;
+
+ if (spacesub == '\0')
+ spacesub = SpaceSub;
+
+ if (pvp == NULL)
+ {
+ *buf = '\0';
+ return;
+ }
+ p = buf;
+ sz -= 2;
+ while (*pvp != NULL && sz > 0)
+ {
+ natomtok = (TokTypeTab[**pvp & 0xff] == ATM);
+ if (oatomtok && natomtok)
+ {
+ *p++ = spacesub;
+ if (--sz <= 0)
+ break;
+ }
+ if ((i = sm_strlcpy(p, *pvp, sz)) >= sz)
+ break;
+ oatomtok = natomtok;
+ p += i;
+ sz -= i;
+ if (pvp++ == evp)
+ break;
+ }
+#if _FFR_CATCH_LONG_STRINGS
+ /* Don't silently truncate long strings */
+ if (*pvp != NULL)
+ syserr("cataddr: string too long");
+#endif /* _FFR_CATCH_LONG_STRINGS */
+ *p = '\0';
+}
+/*
+** SAMEADDR -- Determine if two addresses are the same
+**
+** This is not just a straight comparison -- if the mailer doesn't
+** care about the host we just ignore it, etc.
+**
+** Parameters:
+** a, b -- pointers to the internal forms to compare.
+**
+** Returns:
+** true -- they represent the same mailbox.
+** false -- they don't.
+**
+** Side Effects:
+** none.
+*/
+
+bool
+sameaddr(a, b)
+ register ADDRESS *a;
+ register ADDRESS *b;
+{
+ register ADDRESS *ca, *cb;
+
+ /* if they don't have the same mailer, forget it */
+ if (a->q_mailer != b->q_mailer)
+ return false;
+
+ /* if the user isn't the same, we can drop out */
+ if (strcmp(a->q_user, b->q_user) != 0)
+ return false;
+
+ /* if we have good uids for both but they differ, these are different */
+ if (a->q_mailer == ProgMailer)
+ {
+ ca = getctladdr(a);
+ cb = getctladdr(b);
+ if (ca != NULL && cb != NULL &&
+ bitset(QGOODUID, ca->q_flags & cb->q_flags) &&
+ ca->q_uid != cb->q_uid)
+ return false;
+ }
+
+ /* otherwise compare hosts (but be careful for NULL ptrs) */
+ if (a->q_host == b->q_host)
+ {
+ /* probably both null pointers */
+ return true;
+ }
+ if (a->q_host == NULL || b->q_host == NULL)
+ {
+ /* only one is a null pointer */
+ return false;
+ }
+ if (strcmp(a->q_host, b->q_host) != 0)
+ return false;
+
+ return true;
+}
+/*
+** PRINTADDR -- print address (for debugging)
+**
+** Parameters:
+** a -- the address to print
+** follow -- follow the q_next chain.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** none.
+*/
+
+struct qflags
+{
+ char *qf_name;
+ unsigned long qf_bit;
+};
+
+static struct qflags AddressFlags[] =
+{
+ { "QGOODUID", QGOODUID },
+ { "QPRIMARY", QPRIMARY },
+ { "QNOTREMOTE", QNOTREMOTE },
+ { "QSELFREF", QSELFREF },
+ { "QBOGUSSHELL", QBOGUSSHELL },
+ { "QUNSAFEADDR", QUNSAFEADDR },
+ { "QPINGONSUCCESS", QPINGONSUCCESS },
+ { "QPINGONFAILURE", QPINGONFAILURE },
+ { "QPINGONDELAY", QPINGONDELAY },
+ { "QHASNOTIFY", QHASNOTIFY },
+ { "QRELAYED", QRELAYED },
+ { "QEXPANDED", QEXPANDED },
+ { "QDELIVERED", QDELIVERED },
+ { "QDELAYED", QDELAYED },
+ { "QTHISPASS", QTHISPASS },
+ { "QRCPTOK", QRCPTOK },
+ { NULL, 0 }
+};
+
+void
+printaddr(a, follow)
+ register ADDRESS *a;
+ bool follow;
+{
+ register MAILER *m;
+ MAILER pseudomailer;
+ register struct qflags *qfp;
+ bool firstone;
+
+ if (a == NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "[NULL]\n");
+ return;
+ }
+
+ while (a != NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%p=", a);
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+
+ /* find the mailer -- carefully */
+ m = a->q_mailer;
+ if (m == NULL)
+ {
+ m = &pseudomailer;
+ m->m_mno = -1;
+ m->m_name = "NULL";
+ }
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s:\n\tmailer %d (%s), host `%s'\n",
+ a->q_paddr == NULL ? "<null>" : a->q_paddr,
+ m->m_mno, m->m_name,
+ a->q_host == NULL ? "<null>" : a->q_host);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\tuser `%s', ruser `%s'\n",
+ a->q_user,
+ a->q_ruser == NULL ? "<null>" : a->q_ruser);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\tstate=");
+ switch (a->q_state)
+ {
+ case QS_OK:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "OK");
+ break;
+
+ case QS_DONTSEND:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "DONTSEND");
+ break;
+
+ case QS_BADADDR:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "BADADDR");
+ break;
+
+ case QS_QUEUEUP:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "QUEUEUP");
+ break;
+
+ case QS_RETRY:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "RETRY");
+ break;
+
+ case QS_SENT:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "SENT");
+ break;
+
+ case QS_VERIFIED:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "VERIFIED");
+ break;
+
+ case QS_EXPANDED:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "EXPANDED");
+ break;
+
+ case QS_SENDER:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "SENDER");
+ break;
+
+ case QS_CLONED:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "CLONED");
+ break;
+
+ case QS_DISCARDED:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "DISCARDED");
+ break;
+
+ case QS_REPLACED:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "REPLACED");
+ break;
+
+ case QS_REMOVED:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "REMOVED");
+ break;
+
+ case QS_DUPLICATE:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "DUPLICATE");
+ break;
+
+ case QS_INCLUDED:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "INCLUDED");
+ break;
+
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%d", a->q_state);
+ break;
+ }
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ", next=%p, alias %p, uid %d, gid %d\n",
+ a->q_next, a->q_alias,
+ (int) a->q_uid, (int) a->q_gid);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\tflags=%lx<",
+ a->q_flags);
+ firstone = true;
+ for (qfp = AddressFlags; qfp->qf_name != NULL; qfp++)
+ {
+ if (!bitset(qfp->qf_bit, a->q_flags))
+ continue;
+ if (!firstone)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ",");
+ firstone = false;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s",
+ qfp->qf_name);
+ }
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, ">\n");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\towner=%s, home=\"%s\", fullname=\"%s\"\n",
+ a->q_owner == NULL ? "(none)" : a->q_owner,
+ a->q_home == NULL ? "(none)" : a->q_home,
+ a->q_fullname == NULL ? "(none)" : a->q_fullname);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\torcpt=\"%s\", statmta=%s, status=%s\n",
+ a->q_orcpt == NULL ? "(none)" : a->q_orcpt,
+ a->q_statmta == NULL ? "(none)" : a->q_statmta,
+ a->q_status == NULL ? "(none)" : a->q_status);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\tfinalrcpt=\"%s\"\n",
+ a->q_finalrcpt == NULL ? "(none)" : a->q_finalrcpt);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\trstatus=\"%s\"\n",
+ a->q_rstatus == NULL ? "(none)" : a->q_rstatus);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\tstatdate=%s\n",
+ a->q_statdate == 0 ? "(none)" : ctime(&a->q_statdate));
+
+ if (!follow)
+ return;
+ a = a->q_next;
+ }
+}
+/*
+** EMPTYADDR -- return true if this address is empty (``<>'')
+**
+** Parameters:
+** a -- pointer to the address
+**
+** Returns:
+** true -- if this address is "empty" (i.e., no one should
+** ever generate replies to it.
+** false -- if it is a "regular" (read: replyable) address.
+*/
+
+bool
+emptyaddr(a)
+ register ADDRESS *a;
+{
+ return a->q_paddr == NULL || strcmp(a->q_paddr, "<>") == 0 ||
+ a->q_user == NULL || strcmp(a->q_user, "<>") == 0;
+}
+/*
+** REMOTENAME -- return the name relative to the current mailer
+**
+** Parameters:
+** name -- the name to translate.
+** m -- the mailer that we want to do rewriting relative
+** to.
+** flags -- fine tune operations.
+** pstat -- pointer to status word.
+** e -- the current envelope.
+**
+** Returns:
+** the text string representing this address relative to
+** the receiving mailer.
+**
+** Side Effects:
+** none.
+**
+** Warnings:
+** The text string returned is tucked away locally;
+** copy it if you intend to save it.
+*/
+
+char *
+remotename(name, m, flags, pstat, e)
+ char *name;
+ struct mailer *m;
+ int flags;
+ int *pstat;
+ register ENVELOPE *e;
+{
+ register char **pvp;
+ char *SM_NONVOLATILE fancy;
+ char *oldg;
+ int rwset;
+ static char buf[MAXNAME + 1];
+ char lbuf[MAXNAME + 1];
+ char pvpbuf[PSBUFSIZE];
+ char addrtype[4];
+
+ if (tTd(12, 1))
+ sm_dprintf("remotename(%s)\n", name);
+
+ /* don't do anything if we are tagging it as special */
+ if (bitset(RF_SENDERADDR, flags))
+ {
+ rwset = bitset(RF_HEADERADDR, flags) ? m->m_sh_rwset
+ : m->m_se_rwset;
+ addrtype[2] = 's';
+ }
+ else
+ {
+ rwset = bitset(RF_HEADERADDR, flags) ? m->m_rh_rwset
+ : m->m_re_rwset;
+ addrtype[2] = 'r';
+ }
+ if (rwset < 0)
+ return name;
+ addrtype[1] = ' ';
+ addrtype[3] = '\0';
+ addrtype[0] = bitset(RF_HEADERADDR, flags) ? 'h' : 'e';
+ macdefine(&e->e_macro, A_TEMP, macid("{addr_type}"), addrtype);
+
+ /*
+ ** Do a heuristic crack of this name to extract any comment info.
+ ** This will leave the name as a comment and a $g macro.
+ */
+
+ if (bitset(RF_CANONICAL, flags) || bitnset(M_NOCOMMENT, m->m_flags))
+ fancy = "\201g";
+ else
+ fancy = crackaddr(name);
+
+ /*
+ ** Turn the name into canonical form.
+ ** Normally this will be RFC 822 style, i.e., "user@domain".
+ ** If this only resolves to "user", and the "C" flag is
+ ** specified in the sending mailer, then the sender's
+ ** domain will be appended.
+ */
+
+ pvp = prescan(name, '\0', pvpbuf, sizeof pvpbuf, NULL, NULL);
+ if (pvp == NULL)
+ return name;
+ if (REWRITE(pvp, 3, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+ if (bitset(RF_ADDDOMAIN, flags) && e->e_fromdomain != NULL)
+ {
+ /* append from domain to this address */
+ register char **pxp = pvp;
+ int l = MAXATOM; /* size of buffer for pvp */
+
+ /* see if there is an "@domain" in the current name */
+ while (*pxp != NULL && strcmp(*pxp, "@") != 0)
+ {
+ pxp++;
+ --l;
+ }
+ if (*pxp == NULL)
+ {
+ /* no.... append the "@domain" from the sender */
+ register char **qxq = e->e_fromdomain;
+
+ while ((*pxp++ = *qxq++) != NULL)
+ {
+ if (--l <= 0)
+ {
+ *--pxp = NULL;
+ usrerr("553 5.1.0 remotename: too many tokens");
+ *pstat = EX_UNAVAILABLE;
+ break;
+ }
+ }
+ if (REWRITE(pvp, 3, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+ }
+ }
+
+ /*
+ ** Do more specific rewriting.
+ ** Rewrite using ruleset 1 or 2 depending on whether this is
+ ** a sender address or not.
+ ** Then run it through any receiving-mailer-specific rulesets.
+ */
+
+ if (bitset(RF_SENDERADDR, flags))
+ {
+ if (REWRITE(pvp, 1, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+ }
+ else
+ {
+ if (REWRITE(pvp, 2, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+ }
+ if (rwset > 0)
+ {
+ if (REWRITE(pvp, rwset, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+ }
+
+ /*
+ ** Do any final sanitation the address may require.
+ ** This will normally be used to turn internal forms
+ ** (e.g., user@host.LOCAL) into external form. This
+ ** may be used as a default to the above rules.
+ */
+
+ if (REWRITE(pvp, 4, e) == EX_TEMPFAIL)
+ *pstat = EX_TEMPFAIL;
+
+ /*
+ ** Now restore the comment information we had at the beginning.
+ */
+
+ cataddr(pvp, NULL, lbuf, sizeof lbuf, '\0');
+ oldg = macget(&e->e_macro, 'g');
+ macset(&e->e_macro, 'g', lbuf);
+
+ SM_TRY
+ /* need to make sure route-addrs have <angle brackets> */
+ if (bitset(RF_CANONICAL, flags) && lbuf[0] == '@')
+ expand("<\201g>", buf, sizeof buf, e);
+ else
+ expand(fancy, buf, sizeof buf, e);
+ SM_FINALLY
+ macset(&e->e_macro, 'g', oldg);
+ SM_END_TRY
+
+ if (tTd(12, 1))
+ sm_dprintf("remotename => `%s'\n", buf);
+ return buf;
+}
+/*
+** MAPLOCALUSER -- run local username through ruleset 5 for final redirection
+**
+** Parameters:
+** a -- the address to map (but just the user name part).
+** sendq -- the sendq in which to install any replacement
+** addresses.
+** aliaslevel -- the alias nesting depth.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+#define Q_COPYFLAGS (QPRIMARY|QBOGUSSHELL|QUNSAFEADDR|\
+ Q_PINGFLAGS|QHASNOTIFY|\
+ QRELAYED|QEXPANDED|QDELIVERED|QDELAYED|\
+ QBYTRACE|QBYNDELAY|QBYNRELAY)
+
+void
+maplocaluser(a, sendq, aliaslevel, e)
+ register ADDRESS *a;
+ ADDRESS **sendq;
+ int aliaslevel;
+ ENVELOPE *e;
+{
+ register char **pvp;
+ register ADDRESS *SM_NONVOLATILE a1 = NULL;
+ auto char *delimptr;
+ char pvpbuf[PSBUFSIZE];
+
+ if (tTd(29, 1))
+ {
+ sm_dprintf("maplocaluser: ");
+ printaddr(a, false);
+ }
+ pvp = prescan(a->q_user, '\0', pvpbuf, sizeof pvpbuf, &delimptr, NULL);
+ if (pvp == NULL)
+ {
+ if (tTd(29, 9))
+ sm_dprintf("maplocaluser: cannot prescan %s\n",
+ a->q_user);
+ return;
+ }
+
+ macdefine(&e->e_macro, A_PERM, 'h', a->q_host);
+ macdefine(&e->e_macro, A_PERM, 'u', a->q_user);
+ macdefine(&e->e_macro, A_PERM, 'z', a->q_home);
+
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e r");
+ if (REWRITE(pvp, 5, e) == EX_TEMPFAIL)
+ {
+ if (tTd(29, 9))
+ sm_dprintf("maplocaluser: rewrite tempfail\n");
+ a->q_state = QS_QUEUEUP;
+ a->q_status = "4.4.3";
+ return;
+ }
+ if (pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET)
+ {
+ if (tTd(29, 9))
+ sm_dprintf("maplocaluser: doesn't resolve\n");
+ return;
+ }
+
+ SM_TRY
+ a1 = buildaddr(pvp, NULL, 0, e);
+ SM_EXCEPT(exc, "E:mta.quickabort")
+
+ /*
+ ** mark address as bad, S5 returned an error
+ ** and we gave that back to the SMTP client.
+ */
+
+ a->q_state = QS_DONTSEND;
+ sm_exc_raisenew_x(&EtypeQuickAbort, 2);
+ SM_END_TRY
+
+ /* if non-null, mailer destination specified -- has it changed? */
+ if (a1 == NULL || sameaddr(a, a1))
+ {
+ if (tTd(29, 9))
+ sm_dprintf("maplocaluser: address unchanged\n");
+ return;
+ }
+
+ /* make new address take on flags and print attributes of old */
+ a1->q_flags &= ~Q_COPYFLAGS;
+ a1->q_flags |= a->q_flags & Q_COPYFLAGS;
+ a1->q_paddr = sm_rpool_strdup_x(e->e_rpool, a->q_paddr);
+ a1->q_finalrcpt = a->q_finalrcpt;
+ a1->q_orcpt = a->q_orcpt;
+
+ /* mark old address as dead; insert new address */
+ a->q_state = QS_REPLACED;
+ if (tTd(29, 5))
+ {
+ sm_dprintf("maplocaluser: QS_REPLACED ");
+ printaddr(a, false);
+ }
+ a1->q_alias = a;
+ allocaddr(a1, RF_COPYALL, sm_rpool_strdup_x(e->e_rpool, a->q_paddr), e);
+ (void) recipient(a1, sendq, aliaslevel, e);
+}
+/*
+** DEQUOTE_INIT -- initialize dequote map
+**
+** Parameters:
+** map -- the internal map structure.
+** args -- arguments.
+**
+** Returns:
+** true.
+*/
+
+bool
+dequote_init(map, args)
+ MAP *map;
+ char *args;
+{
+ register char *p = args;
+
+ /* there is no check whether there is really an argument */
+ map->map_mflags |= MF_KEEPQUOTES;
+ for (;;)
+ {
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '-')
+ break;
+ switch (*++p)
+ {
+ case 'a':
+ map->map_app = ++p;
+ break;
+
+ case 'D':
+ map->map_mflags |= MF_DEFER;
+ break;
+
+ case 'S':
+ case 's':
+ map->map_spacesub = *++p;
+ break;
+ }
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p = '\0';
+ }
+ if (map->map_app != NULL)
+ map->map_app = newstr(map->map_app);
+
+ return true;
+}
+/*
+** DEQUOTE_MAP -- unquote an address
+**
+** Parameters:
+** map -- the internal map structure (ignored).
+** name -- the name to dequote.
+** av -- arguments (ignored).
+** statp -- pointer to status out-parameter.
+**
+** Returns:
+** NULL -- if there were no quotes, or if the resulting
+** unquoted buffer would not be acceptable to prescan.
+** else -- The dequoted buffer.
+*/
+
+/* ARGSUSED2 */
+char *
+dequote_map(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ register char *p;
+ register char *q;
+ register char c;
+ int anglecnt = 0;
+ int cmntcnt = 0;
+ int quotecnt = 0;
+ int spacecnt = 0;
+ bool quotemode = false;
+ bool bslashmode = false;
+ char spacesub = map->map_spacesub;
+
+ for (p = q = name; (c = *p++) != '\0'; )
+ {
+ if (bslashmode)
+ {
+ bslashmode = false;
+ *q++ = c;
+ continue;
+ }
+
+ if (c == ' ' && spacesub != '\0')
+ c = spacesub;
+
+ switch (c)
+ {
+ case '\\':
+ bslashmode = true;
+ break;
+
+ case '(':
+ cmntcnt++;
+ break;
+
+ case ')':
+ if (cmntcnt-- <= 0)
+ return NULL;
+ break;
+
+ case ' ':
+ case '\t':
+ spacecnt++;
+ break;
+ }
+
+ if (cmntcnt > 0)
+ {
+ *q++ = c;
+ continue;
+ }
+
+ switch (c)
+ {
+ case '"':
+ quotemode = !quotemode;
+ quotecnt++;
+ continue;
+
+ case '<':
+ anglecnt++;
+ break;
+
+ case '>':
+ if (anglecnt-- <= 0)
+ return NULL;
+ break;
+ }
+ *q++ = c;
+ }
+
+ if (anglecnt != 0 || cmntcnt != 0 || bslashmode ||
+ quotemode || quotecnt <= 0 || spacecnt != 0)
+ return NULL;
+ *q++ = '\0';
+ return map_rewrite(map, name, strlen(name), NULL);
+}
+/*
+** RSCHECK -- check string(s) for validity using rewriting sets
+**
+** Parameters:
+** rwset -- the rewriting set to use.
+** p1 -- the first string to check.
+** p2 -- the second string to check -- may be null.
+** e -- the current envelope.
+** flags -- control some behavior, see RSF_ in sendmail.h
+** logl -- logging level.
+** host -- NULL or relay host.
+** logid -- id for sm_syslog.
+**
+** Returns:
+** EX_OK -- if the rwset doesn't resolve to $#error
+** else -- the failure status (message printed)
+*/
+
+int
+rscheck(rwset, p1, p2, e, flags, logl, host, logid)
+ char *rwset;
+ char *p1;
+ char *p2;
+ ENVELOPE *e;
+ int flags;
+ int logl;
+ char *host;
+ char *logid;
+{
+ char *volatile buf;
+ int bufsize;
+ int saveexitstat;
+ int volatile rstat = EX_OK;
+ char **pvp;
+ int rsno;
+ bool volatile discard = false;
+ auto ADDRESS a1;
+ bool saveQuickAbort = QuickAbort;
+ bool saveSuprErrs = SuprErrs;
+#if _FFR_QUARANTINE
+ bool quarantine = false;
+ char ubuf[BUFSIZ * 2];
+#endif /* _FFR_QUARANTINE */
+ char buf0[MAXLINE];
+ char pvpbuf[PSBUFSIZE];
+ extern char MsgBuf[];
+
+ if (tTd(48, 2))
+ sm_dprintf("rscheck(%s, %s, %s)\n", rwset, p1,
+ p2 == NULL ? "(NULL)" : p2);
+
+ rsno = strtorwset(rwset, NULL, ST_FIND);
+ if (rsno < 0)
+ return EX_OK;
+
+ if (p2 != NULL)
+ {
+ bufsize = strlen(p1) + strlen(p2) + 2;
+ if (bufsize > sizeof buf0)
+ buf = sm_malloc_x(bufsize);
+ else
+ {
+ buf = buf0;
+ bufsize = sizeof buf0;
+ }
+ (void) sm_snprintf(buf, bufsize, "%s%c%s", p1, CONDELSE, p2);
+ }
+ else
+ {
+ bufsize = strlen(p1) + 1;
+ if (bufsize > sizeof buf0)
+ buf = sm_malloc_x(bufsize);
+ else
+ {
+ buf = buf0;
+ bufsize = sizeof buf0;
+ }
+ (void) sm_strlcpy(buf, p1, bufsize);
+ }
+ SM_TRY
+ {
+ SuprErrs = true;
+ QuickAbort = false;
+ pvp = prescan(buf, '\0', pvpbuf, sizeof pvpbuf, NULL,
+ bitset(RSF_RMCOMM, flags) ? NULL : TokTypeNoC);
+ SuprErrs = saveSuprErrs;
+ if (pvp == NULL)
+ {
+ if (tTd(48, 2))
+ sm_dprintf("rscheck: cannot prescan input\n");
+ /*
+ syserr("rscheck: cannot prescan input: \"%s\"",
+ shortenstring(buf, MAXSHORTSTR));
+ rstat = EX_DATAERR;
+ */
+ goto finis;
+ }
+ if (bitset(RSF_UNSTRUCTURED, flags))
+ SuprErrs = true;
+ (void) REWRITE(pvp, rsno, e);
+ if (bitset(RSF_UNSTRUCTURED, flags))
+ SuprErrs = saveSuprErrs;
+ if (pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET ||
+ pvp[1] == NULL || (strcmp(pvp[1], "error") != 0 &&
+ strcmp(pvp[1], "discard") != 0))
+ {
+ goto finis;
+ }
+
+ if (strcmp(pvp[1], "discard") == 0)
+ {
+ if (tTd(48, 2))
+ sm_dprintf("rscheck: discard mailer selected\n");
+ e->e_flags |= EF_DISCARD;
+ discard = true;
+ }
+#if _FFR_QUARANTINE
+ else if (strcmp(pvp[1], "error") == 0 &&
+ pvp[2] != NULL && (pvp[2][0] & 0377) == CANONHOST &&
+ pvp[3] != NULL && strcmp(pvp[3], "quarantine") == 0)
+ {
+ if (pvp[4] == NULL ||
+ (pvp[4][0] & 0377) != CANONUSER ||
+ pvp[5] == NULL)
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool,
+ rwset);
+ else
+ {
+ cataddr(&(pvp[5]), NULL, ubuf,
+ sizeof ubuf, ' ');
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool,
+ ubuf);
+ }
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ quarantine = true;
+ }
+#endif /* _FFR_QUARANTINE */
+ else
+ {
+ int savelogusrerrs = LogUsrErrs;
+ static bool logged = false;
+
+ /* got an error -- process it */
+ saveexitstat = ExitStat;
+ LogUsrErrs = false;
+ (void) buildaddr(pvp, &a1, 0, e);
+ LogUsrErrs = savelogusrerrs;
+ rstat = ExitStat;
+ ExitStat = saveexitstat;
+ if (!logged)
+ {
+ if (bitset(RSF_COUNT, flags))
+ markstats(e, &a1, STATS_REJECT);
+ logged = true;
+ }
+ }
+
+ if (LogLevel > logl)
+ {
+ char *relay;
+ char *p;
+ char lbuf[MAXLINE];
+
+ p = lbuf;
+ if (p2 != NULL)
+ {
+ (void) sm_snprintf(p, SPACELEFT(lbuf, p),
+ ", arg2=%s",
+ p2);
+ p += strlen(p);
+ }
+
+ if (host != NULL)
+ relay = host;
+ else
+ relay = macvalue('_', e);
+ if (relay != NULL)
+ {
+ (void) sm_snprintf(p, SPACELEFT(lbuf, p),
+ ", relay=%s", relay);
+ p += strlen(p);
+ }
+ *p = '\0';
+ if (discard)
+ sm_syslog(LOG_NOTICE, logid,
+ "ruleset=%s, arg1=%s%s, discard",
+ rwset, p1, lbuf);
+#if _FFR_QUARANTINE
+ else if (quarantine)
+ sm_syslog(LOG_NOTICE, logid,
+ "ruleset=%s, arg1=%s%s, quarantine=%s",
+ rwset, p1, lbuf, ubuf);
+#endif /* _FFR_QUARANTINE */
+ else
+ sm_syslog(LOG_NOTICE, logid,
+ "ruleset=%s, arg1=%s%s, reject=%s",
+ rwset, p1, lbuf, MsgBuf);
+ }
+
+ finis: ;
+ }
+ SM_FINALLY
+ {
+ /* clean up */
+ if (buf != buf0)
+ sm_free(buf);
+ QuickAbort = saveQuickAbort;
+ }
+ SM_END_TRY
+
+ setstat(rstat);
+
+ /* rulesets don't set errno */
+ errno = 0;
+ if (rstat != EX_OK && QuickAbort)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 2);
+ return rstat;
+}
+/*
+** RSCAP -- call rewriting set to return capabilities
+**
+** Parameters:
+** rwset -- the rewriting set to use.
+** p1 -- the first string to check.
+** p2 -- the second string to check -- may be null.
+** e -- the current envelope.
+** pvp -- pointer to token vector.
+** pvpbuf -- buffer space.
+**
+** Returns:
+** EX_UNAVAILABLE -- ruleset doesn't exist.
+** EX_DATAERR -- prescan() failed.
+** EX_OK -- rewrite() was successful.
+** else -- return status from rewrite().
+*/
+
+int
+rscap(rwset, p1, p2, e, pvp, pvpbuf, size)
+ char *rwset;
+ char *p1;
+ char *p2;
+ ENVELOPE *e;
+ char ***pvp;
+ char *pvpbuf;
+ int size;
+{
+ char *volatile buf;
+ int bufsize;
+ int volatile rstat = EX_OK;
+ int rsno;
+ bool saveQuickAbort = QuickAbort;
+ bool saveSuprErrs = SuprErrs;
+ char buf0[MAXLINE];
+ extern char MsgBuf[];
+
+ if (tTd(48, 2))
+ sm_dprintf("rscap(%s, %s, %s)\n", rwset, p1,
+ p2 == NULL ? "(NULL)" : p2);
+
+ if (pvp != NULL)
+ *pvp = NULL;
+ rsno = strtorwset(rwset, NULL, ST_FIND);
+ if (rsno < 0)
+ return EX_UNAVAILABLE;
+
+ if (p2 != NULL)
+ {
+ bufsize = strlen(p1) + strlen(p2) + 2;
+ if (bufsize > sizeof buf0)
+ buf = sm_malloc_x(bufsize);
+ else
+ {
+ buf = buf0;
+ bufsize = sizeof buf0;
+ }
+ (void) sm_snprintf(buf, bufsize, "%s%c%s", p1, CONDELSE, p2);
+ }
+ else
+ {
+ bufsize = strlen(p1) + 1;
+ if (bufsize > sizeof buf0)
+ buf = sm_malloc_x(bufsize);
+ else
+ {
+ buf = buf0;
+ bufsize = sizeof buf0;
+ }
+ (void) sm_strlcpy(buf, p1, bufsize);
+ }
+ SM_TRY
+ {
+ SuprErrs = true;
+ QuickAbort = false;
+ *pvp = prescan(buf, '\0', pvpbuf, size, NULL, NULL);
+ if (*pvp != NULL)
+ rstat = REWRITE(*pvp, rsno, e);
+ else
+ {
+ if (tTd(48, 2))
+ sm_dprintf("rscap: cannot prescan input\n");
+ rstat = EX_DATAERR;
+ }
+ }
+ SM_FINALLY
+ {
+ /* clean up */
+ if (buf != buf0)
+ sm_free(buf);
+ SuprErrs = saveSuprErrs;
+ QuickAbort = saveQuickAbort;
+
+ /* prevent information leak, this may contain rewrite error */
+ MsgBuf[0] = '\0';
+ }
+ SM_END_TRY
+ return rstat;
+}
diff --git a/contrib/sendmail/src/queue.c b/contrib/sendmail/src/queue.c
new file mode 100644
index 0000000..26e73f0
--- /dev/null
+++ b/contrib/sendmail/src/queue.c
@@ -0,0 +1,8636 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: queue.c,v 8.863.2.6 2002/08/16 16:27:37 gshapiro Exp $")
+
+#include <dirent.h>
+
+#if SM_CONF_SHM
+# include <sm/shm.h>
+#endif /* SM_CONF_SHM */
+
+# define RELEASE_QUEUE (void) 0
+# define ST_INODE(st) (st).st_ino
+
+
+/*
+** Historical notes:
+** QF_VERSION==4 was sendmail 8.10/8.11 without _FFR_QUEUEDELAY
+** QF_VERSION==5 was sendmail 8.10/8.11 with _FFR_QUEUEDELAY
+*/
+
+#if _FFR_QUEUEDELAY
+# define QF_VERSION 7 /* version number of this queue format */
+static time_t queuedelay __P((ENVELOPE *));
+#define queuedelay_qfver_unsupported(qfver) false
+#else /* _FFR_QUEUEDELAY */
+# define QF_VERSION 6 /* version number of this queue format */
+# define queuedelay(e) MinQueueAge
+#define queuedelay_qfver_unsupported(qfver) ((qfver) == 5 || (qfver) == 7)
+#endif /* _FFR_QUEUEDELAY */
+#if _FFR_QUARANTINE
+static char queue_letter __P((ENVELOPE *, int));
+static bool quarantine_queue_item __P((int, int, ENVELOPE *, char *));
+#endif /* _FFR_QUARANTINE */
+
+/* Naming convention: qgrp: index of queue group, qg: QUEUEGROUP */
+
+/*
+** Work queue.
+*/
+
+struct work
+{
+ char *w_name; /* name of control file */
+ char *w_host; /* name of recipient host */
+ bool w_lock; /* is message locked? */
+ bool w_tooyoung; /* is it too young to run? */
+ long w_pri; /* priority of message, see below */
+ time_t w_ctime; /* creation time */
+ time_t w_mtime; /* modification time */
+ int w_qgrp; /* queue group located in */
+ int w_qdir; /* queue directory located in */
+ struct work *w_next; /* next in queue */
+};
+
+typedef struct work WORK;
+
+static WORK *WorkQ; /* queue of things to be done */
+static int NumWorkGroups; /* number of work groups */
+
+/*
+** DoQueueRun indicates that a queue run is needed.
+** Notice: DoQueueRun is modified in a signal handler!
+*/
+
+static bool volatile DoQueueRun; /* non-interrupt time queue run needed */
+
+/*
+** Work group definition structure.
+** Each work group contains one or more queue groups. This is done
+** to manage the number of queue group runners active at the same time
+** to be within the constraints of MaxQueueChildren (if it is set).
+** The number of queue groups that can be run on the next work run
+** is kept track of. The queue groups are run in a round robin.
+*/
+
+struct workgrp
+{
+ int wg_numqgrp; /* number of queue groups in work grp */
+ int wg_runners; /* total runners */
+ int wg_curqgrp; /* current queue group */
+ QUEUEGRP **wg_qgs; /* array of queue groups */
+ int wg_maxact; /* max # of active runners */
+ time_t wg_lowqintvl; /* lowest queue interval */
+ int wg_restart; /* needs restarting? */
+ int wg_restartcnt; /* count of times restarted */
+};
+
+typedef struct workgrp WORKGRP;
+
+static WORKGRP volatile WorkGrp[MAXWORKGROUPS + 1]; /* work groups */
+
+#if SM_HEAP_CHECK
+static SM_DEBUG_T DebugLeakQ = SM_DEBUG_INITIALIZER("leak_q",
+ "@(#)$Debug: leak_q - trace memory leaks during queue processing $");
+#endif /* SM_HEAP_CHECK */
+
+/*
+** We use EmptyString instead of "" to avoid
+** 'zero-length format string' warnings from gcc
+*/
+
+static const char EmptyString[] = "";
+
+static void grow_wlist __P((int, int));
+static int multiqueue_cache __P((char *, int, QUEUEGRP *, int, unsigned int *));
+static int gatherq __P((int, int, bool, bool *, bool *));
+static int sortq __P((int));
+static void printctladdr __P((ADDRESS *, SM_FILE_T *));
+static bool readqf __P((ENVELOPE *, bool));
+static void restart_work_group __P((int));
+static void runner_work __P((ENVELOPE *, int, bool, int, int));
+static void schedule_queue_runs __P((bool, int, bool));
+static char *strrev __P((char *));
+static ADDRESS *setctluser __P((char *, int, ENVELOPE *));
+#if _FFR_RHS
+static int sm_strshufflecmp __P((char *, char *));
+static void init_shuffle_alphabet __P(());
+#endif /* _FFR_RHS */
+static int workcmpf0();
+static int workcmpf1();
+static int workcmpf2();
+static int workcmpf3();
+static int workcmpf4();
+static int workcmpf5();
+static int workcmpf6();
+#if _FFR_RHS
+static int workcmpf7();
+#endif /* _FFR_RHS */
+
+#if RANDOMSHIFT
+# define get_rand_mod(m) ((get_random() >> RANDOMSHIFT) % (m))
+#else /* RANDOMSHIFT */
+# define get_rand_mod(m) (get_random() % (m))
+#endif /* RANDOMSHIFT */
+
+/*
+** File system definition.
+** Used to keep track of how much free space is available
+** on a file system in which one or more queue directories reside.
+*/
+
+typedef struct filesys_shared FILESYS;
+
+struct filesys_shared
+{
+ dev_t fs_dev; /* unique device id */
+ long fs_avail; /* number of free blocks available */
+ long fs_blksize; /* block size, in bytes */
+};
+
+/* probably kept in shared memory */
+static FILESYS FileSys[MAXFILESYS]; /* queue file systems */
+static char *FSPath[MAXFILESYS]; /* pathnames for file systems */
+
+#if SM_CONF_SHM
+
+/*
+** Shared memory data
+**
+** Current layout:
+** size -- size of shared memory segment
+** pid -- pid of owner, should be a unique id to avoid misinterpretations
+** by other processes.
+** tag -- should be a unique id to avoid misinterpretations by others.
+** idea: hash over configuration data that will be stored here.
+** NumFileSys -- number of file systems.
+** FileSys -- (arrary of) structure for used file systems.
+** RSATmpCnt -- counter for number of uses of ephemeral RSA key.
+** QShm -- (array of) structure for information about queue directories.
+*/
+
+/*
+** Queue data in shared memory
+*/
+
+typedef struct queue_shared QUEUE_SHM_T;
+
+struct queue_shared
+{
+ int qs_entries; /* number of entries */
+ /* XXX more to follow? */
+};
+
+static void *Pshm; /* pointer to shared memory */
+static FILESYS *PtrFileSys; /* pointer to queue file system array */
+int ShmId = SM_SHM_NO_ID; /* shared memory id */
+static QUEUE_SHM_T *QShm; /* pointer to shared queue data */
+
+# define SHM_OFF_PID(p) (((char *) (p)) + sizeof(int))
+# define SHM_OFF_TAG(p) (((char *) (p)) + sizeof(pid_t) + sizeof(int))
+# define SHM_OFF_HEAD (sizeof(pid_t) + sizeof(int) * 2)
+
+/* how to access FileSys */
+# define FILE_SYS(i) (PtrFileSys[i])
+
+/* first entry is a tag, for now just the size */
+# define OFF_FILE_SYS(p) (((char *) (p)) + SHM_OFF_HEAD)
+
+/* offset for PNumFileSys */
+# define OFF_NUM_FILE_SYS(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys))
+
+/* offset for PRSATmpCnt */
+# define OFF_RSA_TMP_CNT(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int))
+int *PRSATmpCnt;
+
+/* offset for queue_shm */
+# define OFF_QUEUE_SHM(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int) * 2)
+
+#define QSHM_ENTRIES(i) QShm[i].qs_entries
+
+/* basic size of shared memory segment */
+# define SM_T_SIZE (SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int) * 2)
+
+static unsigned int hash_q __P((char *, unsigned int));
+
+/*
+** HASH_Q -- simple hash function
+**
+** Parameters:
+** p -- string to hash.
+** h -- hash start value (from previous run).
+**
+** Returns:
+** hash value.
+*/
+
+static unsigned int
+hash_q(p, h)
+ char *p;
+ unsigned int h;
+{
+ int c, d;
+
+ while (*p != '\0')
+ {
+ d = *p++;
+ c = d;
+ c ^= c<<6;
+ h += (c<<11) ^ (c>>1);
+ h ^= (d<<14) + (d<<7) + (d<<4) + d;
+ }
+ return h;
+}
+
+#else /* SM_CONF_SHM */
+# define FILE_SYS(i) FileSys[i]
+#endif /* SM_CONF_SHM */
+
+/* access to the various components of file system data */
+#define FILE_SYS_NAME(i) FSPath[i]
+#define FILE_SYS_AVAIL(i) FILE_SYS(i).fs_avail
+#define FILE_SYS_BLKSIZE(i) FILE_SYS(i).fs_blksize
+#define FILE_SYS_DEV(i) FILE_SYS(i).fs_dev
+
+/*
+** Current qf file field assignments:
+**
+** A AUTH= parameter
+** B body type
+** C controlling user
+** D data file name
+** d data file directory name (added in 8.12)
+** E error recipient
+** F flag bits
+** G queue delay algorithm (_FFR_QUEUEDELAY)
+** H header
+** I data file's inode number
+** K time of last delivery attempt
+** L Solaris Content-Length: header (obsolete)
+** M message
+** N number of delivery attempts
+** P message priority
+** q quarantine reason (_FFR_QUARANTINE)
+** Q original recipient (ORCPT=)
+** r final recipient (Final-Recipient: DSN field)
+** R recipient
+** S sender
+** T init time
+** V queue file version
+** X free (was: character set if _FFR_SAVE_CHARSET)
+** Y current delay (_FFR_QUEUEDELAY)
+** Z original envelope id from ESMTP
+** ! deliver by (added in 8.12)
+** $ define macro
+** . terminate file
+*/
+
+/*
+** QUEUEUP -- queue a message up for future transmission.
+**
+** Parameters:
+** e -- the envelope to queue up.
+** announce -- if true, tell when you are queueing up.
+** msync -- if true, then fsync() if SuperSafe interactive mode.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The current request is saved in a control file.
+** The queue file is left locked.
+*/
+
+void
+queueup(e, announce, msync)
+ register ENVELOPE *e;
+ bool announce;
+ bool msync;
+{
+ register SM_FILE_T *tfp;
+ register HDR *h;
+ register ADDRESS *q;
+ int tfd = -1;
+ int i;
+ bool newid;
+ register char *p;
+ MAILER nullmailer;
+ MCI mcibuf;
+ char qf[MAXPATHLEN];
+ char tf[MAXPATHLEN];
+ char df[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ /*
+ ** Create control file.
+ */
+
+ newid = (e->e_id == NULL) || !bitset(EF_INQUEUE, e->e_flags);
+ (void) sm_strlcpy(tf, queuename(e, NEWQFL_LETTER), sizeof tf);
+ tfp = e->e_lockfp;
+ if (tfp == NULL)
+ newid = false;
+
+ /* if newid, write the queue file directly (instead of temp file) */
+ if (!newid)
+ {
+ const int flags = O_CREAT|O_WRONLY|O_EXCL;
+
+ /* get a locked tf file */
+ for (i = 0; i < 128; i++)
+ {
+ if (tfd < 0)
+ {
+ MODE_T oldumask = 0;
+
+ if (bitset(S_IWGRP, QueueFileMode))
+ oldumask = umask(002);
+ tfd = open(tf, flags, QueueFileMode);
+ if (bitset(S_IWGRP, QueueFileMode))
+ (void) umask(oldumask);
+
+ if (tfd < 0)
+ {
+ if (errno != EEXIST)
+ break;
+ if (LogLevel > 0 && (i % 32) == 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "queueup: cannot create %s, uid=%d: %s",
+ tf, (int) geteuid(),
+ sm_errstring(errno));
+ }
+ }
+ if (tfd >= 0)
+ {
+ if (lockfile(tfd, tf, NULL, LOCK_EX|LOCK_NB))
+ break;
+ else if (LogLevel > 0 && (i % 32) == 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "queueup: cannot lock %s: %s",
+ tf, sm_errstring(errno));
+ if ((i % 32) == 31)
+ {
+ (void) close(tfd);
+ tfd = -1;
+ }
+ }
+
+ if ((i % 32) == 31)
+ {
+ /* save the old temp file away */
+ (void) rename(tf, queuename(e, TEMPQF_LETTER));
+ }
+ else
+ (void) sleep(i % 32);
+ }
+ if (tfd < 0 || (tfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &tfd, SM_IO_WRONLY,
+ NULL)) == NULL)
+ {
+ int save_errno = errno;
+
+ printopenfds(true);
+ errno = save_errno;
+ syserr("!queueup: cannot create queue temp file %s, uid=%d",
+ tf, (int) geteuid());
+ }
+ }
+
+ if (tTd(40, 1))
+ sm_dprintf("\n>>>>> queueing %s/%s%s >>>>>\n",
+ qid_printqueue(e->e_qgrp, e->e_qdir),
+ queuename(e, ANYQFL_LETTER),
+ newid ? " (new id)" : "");
+ if (tTd(40, 3))
+ {
+ sm_dprintf(" e_flags=");
+ printenvflags(e);
+ }
+ if (tTd(40, 32))
+ {
+ sm_dprintf(" sendq=");
+ printaddr(e->e_sendqueue, true);
+ }
+ if (tTd(40, 9))
+ {
+ sm_dprintf(" tfp=");
+ dumpfd(sm_io_getinfo(tfp, SM_IO_WHAT_FD, NULL), true, false);
+ sm_dprintf(" lockfp=");
+ if (e->e_lockfp == NULL)
+ sm_dprintf("NULL\n");
+ else
+ dumpfd(sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL),
+ true, false);
+ }
+
+ /*
+ ** If there is no data file yet, create one.
+ */
+
+ (void) sm_strlcpy(df, queuename(e, DATAFL_LETTER), sizeof df);
+ if (bitset(EF_HAS_DF, e->e_flags))
+ {
+ if (e->e_dfp != NULL &&
+ SuperSafe != SAFE_REALLY &&
+ sm_io_setinfo(e->e_dfp, SM_BF_COMMIT, NULL) < 0 &&
+ errno != EINVAL)
+ {
+ syserr("!queueup: cannot commit data file %s, uid=%d",
+ queuename(e, DATAFL_LETTER), (int) geteuid());
+ }
+ if (e->e_dfp != NULL &&
+ SuperSafe == SAFE_INTERACTIVE && msync)
+ {
+ if (tTd(40,32))
+ sm_syslog(LOG_INFO, e->e_id,
+ "queueup: fsync(e->e_dfp)");
+
+ if (fsync(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD,
+ NULL)) < 0)
+ {
+ if (newid)
+ syserr("!552 Error writing data file %s",
+ df);
+ else
+ syserr("!452 Error writing data file %s",
+ df);
+ }
+ }
+ }
+ else
+ {
+ int dfd;
+ MODE_T oldumask = 0;
+ register SM_FILE_T *dfp = NULL;
+ struct stat stbuf;
+
+ if (e->e_dfp != NULL &&
+ sm_io_getinfo(e->e_dfp, SM_IO_WHAT_ISTYPE, BF_FILE_TYPE))
+ syserr("committing over bf file");
+
+ if (bitset(S_IWGRP, QueueFileMode))
+ oldumask = umask(002);
+ dfd = open(df, O_WRONLY|O_CREAT|O_TRUNC, QueueFileMode);
+ if (bitset(S_IWGRP, QueueFileMode))
+ (void) umask(oldumask);
+ if (dfd < 0 || (dfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &dfd, SM_IO_WRONLY,
+ NULL)) == NULL)
+ syserr("!queueup: cannot create data temp file %s, uid=%d",
+ df, (int) geteuid());
+ if (fstat(dfd, &stbuf) < 0)
+ e->e_dfino = -1;
+ else
+ {
+ e->e_dfdev = stbuf.st_dev;
+ e->e_dfino = ST_INODE(stbuf);
+ }
+ e->e_flags |= EF_HAS_DF;
+ memset(&mcibuf, '\0', sizeof mcibuf);
+ mcibuf.mci_out = dfp;
+ mcibuf.mci_mailer = FileMailer;
+ (*e->e_putbody)(&mcibuf, e, NULL);
+
+ if (SuperSafe == SAFE_REALLY ||
+ (SuperSafe == SAFE_INTERACTIVE && msync))
+ {
+ if (tTd(40,32))
+ sm_syslog(LOG_INFO, e->e_id,
+ "queueup: fsync(dfp)");
+
+ if (fsync(sm_io_getinfo(dfp, SM_IO_WHAT_FD, NULL)) < 0)
+ {
+ if (newid)
+ syserr("!552 Error writing data file %s",
+ df);
+ else
+ syserr("!452 Error writing data file %s",
+ df);
+ }
+ }
+
+ if (sm_io_close(dfp, SM_TIME_DEFAULT) < 0)
+ syserr("!queueup: cannot save data temp file %s, uid=%d",
+ df, (int) geteuid());
+ e->e_putbody = putbody;
+ }
+
+ /*
+ ** Output future work requests.
+ ** Priority and creation time should be first, since
+ ** they are required by gatherq.
+ */
+
+ /* output queue version number (must be first!) */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "V%d\n", QF_VERSION);
+
+ /* output creation time */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "T%ld\n", (long) e->e_ctime);
+
+ /* output last delivery time */
+#if _FFR_QUEUEDELAY
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "K%ld\n", (long) e->e_dtime);
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "G%d\n", e->e_queuealg);
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "Y%ld\n", (long) e->e_queuedelay);
+ if (tTd(40, 64))
+ sm_syslog(LOG_INFO, e->e_id,
+ "queue alg: %d delay %ld next: %ld (now: %ld)\n",
+ e->e_queuealg, e->e_queuedelay, e->e_dtime, curtime());
+#else /* _FFR_QUEUEDELAY */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "K%ld\n", (long) e->e_dtime);
+#endif /* _FFR_QUEUEDELAY */
+
+ /* output number of delivery attempts */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "N%d\n", e->e_ntries);
+
+ /* output message priority */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "P%ld\n", e->e_msgpriority);
+
+ /*
+ ** If data file is in a different directory than the queue file,
+ ** output a "d" record naming the directory of the data file.
+ */
+
+ if (e->e_dfqgrp != e->e_qgrp)
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "d%s\n",
+ Queue[e->e_dfqgrp]->qg_qpaths[e->e_dfqdir].qp_name);
+ }
+
+ /* output inode number of data file */
+ /* XXX should probably include device major/minor too */
+ if (e->e_dfino != -1)
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "I%ld/%ld/%llu\n",
+ (long) major(e->e_dfdev),
+ (long) minor(e->e_dfdev),
+ (ULONGLONG_T) e->e_dfino);
+ }
+
+ /* output body type */
+ if (e->e_bodytype != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "B%s\n",
+ denlstring(e->e_bodytype, true, false));
+
+#if _FFR_QUARANTINE
+ /* quarantine reason */
+ if (e->e_quarmsg != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "q%s\n",
+ denlstring(e->e_quarmsg, true, false));
+#endif /* _FFR_QUARANTINE */
+
+ /* message from envelope, if it exists */
+ if (e->e_message != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "M%s\n",
+ denlstring(e->e_message, true, false));
+
+ /* send various flag bits through */
+ p = buf;
+ if (bitset(EF_WARNING, e->e_flags))
+ *p++ = 'w';
+ if (bitset(EF_RESPONSE, e->e_flags))
+ *p++ = 'r';
+ if (bitset(EF_HAS8BIT, e->e_flags))
+ *p++ = '8';
+ if (bitset(EF_DELETE_BCC, e->e_flags))
+ *p++ = 'b';
+ if (bitset(EF_RET_PARAM, e->e_flags))
+ *p++ = 'd';
+ if (bitset(EF_NO_BODY_RETN, e->e_flags))
+ *p++ = 'n';
+ if (bitset(EF_SPLIT, e->e_flags))
+ *p++ = 's';
+ *p++ = '\0';
+ if (buf[0] != '\0')
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "F%s\n", buf);
+
+ /* save $={persistentMacros} macro values */
+ queueup_macros(macid("{persistentMacros}"), tfp, e);
+
+ /* output name of sender */
+ if (bitnset(M_UDBENVELOPE, e->e_from.q_mailer->m_flags))
+ p = e->e_sender;
+ else
+ p = e->e_from.q_paddr;
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "S%s\n",
+ denlstring(p, true, false));
+
+ /* output ESMTP-supplied "original" information */
+ if (e->e_envid != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "Z%s\n",
+ denlstring(e->e_envid, true, false));
+
+ /* output AUTH= parameter */
+ if (e->e_auth_param != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "A%s\n",
+ denlstring(e->e_auth_param, true, false));
+ if (e->e_dlvr_flag != 0)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "!%c %ld\n",
+ (char) e->e_dlvr_flag, e->e_deliver_by);
+
+ /* output list of recipient addresses */
+ printctladdr(NULL, NULL);
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (!QS_IS_UNDELIVERED(q->q_state))
+ continue;
+
+ /* message for this recipient, if it exists */
+ if (q->q_message != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "M%s\n",
+ denlstring(q->q_message, true,
+ false));
+
+ printctladdr(q, tfp);
+ if (q->q_orcpt != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "Q%s\n",
+ denlstring(q->q_orcpt, true,
+ false));
+ if (q->q_finalrcpt != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "r%s\n",
+ denlstring(q->q_finalrcpt, true,
+ false));
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'R');
+ if (bitset(QPRIMARY, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'P');
+ if (bitset(QHASNOTIFY, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'N');
+ if (bitset(QPINGONSUCCESS, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'S');
+ if (bitset(QPINGONFAILURE, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'F');
+ if (bitset(QPINGONDELAY, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'D');
+ if (q->q_alias != NULL &&
+ bitset(QALIAS, q->q_alias->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'A');
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, ':');
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s\n",
+ denlstring(q->q_paddr, true, false));
+ if (announce)
+ {
+ char *tag = "queued";
+
+#if _FFR_QUARANTINE
+ if (e->e_quarmsg != NULL)
+ tag = "quarantined";
+#endif /* _FFR_QUARANTINE */
+
+ e->e_to = q->q_paddr;
+ message(tag);
+ if (LogLevel > 8)
+ logdelivery(q->q_mailer, NULL, q->q_status,
+ tag, NULL, (time_t) 0, e);
+ e->e_to = NULL;
+ }
+ if (tTd(40, 1))
+ {
+ sm_dprintf("queueing ");
+ printaddr(q, false);
+ }
+ }
+
+ /*
+ ** Output headers for this message.
+ ** Expand macros completely here. Queue run will deal with
+ ** everything as absolute headers.
+ ** All headers that must be relative to the recipient
+ ** can be cracked later.
+ ** We set up a "null mailer" -- i.e., a mailer that will have
+ ** no effect on the addresses as they are output.
+ */
+
+ memset((char *) &nullmailer, '\0', sizeof nullmailer);
+ nullmailer.m_re_rwset = nullmailer.m_rh_rwset =
+ nullmailer.m_se_rwset = nullmailer.m_sh_rwset = -1;
+ nullmailer.m_eol = "\n";
+ memset(&mcibuf, '\0', sizeof mcibuf);
+ mcibuf.mci_mailer = &nullmailer;
+ mcibuf.mci_out = tfp;
+
+ macdefine(&e->e_macro, A_PERM, 'g', "\201f");
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ if (h->h_value == NULL)
+ continue;
+
+ /* don't output resent headers on non-resent messages */
+ if (bitset(H_RESENT, h->h_flags) &&
+ !bitset(EF_RESENT, e->e_flags))
+ continue;
+
+ /* expand macros; if null, don't output header at all */
+ if (bitset(H_DEFAULT, h->h_flags))
+ {
+ (void) expand(h->h_value, buf, sizeof buf, e);
+ if (buf[0] == '\0')
+ continue;
+ }
+
+ /* output this header */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "H?");
+
+ /* output conditional macro if present */
+ if (h->h_macro != '\0')
+ {
+ if (bitset(0200, h->h_macro))
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT,
+ "${%s}",
+ macname(bitidx(h->h_macro)));
+ else
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT,
+ "$%c", h->h_macro);
+ }
+ else if (!bitzerop(h->h_mflags) &&
+ bitset(H_CHECK|H_ACHECK, h->h_flags))
+ {
+ int j;
+
+ /* if conditional, output the set of conditions */
+ for (j = '\0'; j <= '\177'; j++)
+ if (bitnset(j, h->h_mflags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT,
+ j);
+ }
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, '?');
+
+ /* output the header: expand macros, convert addresses */
+ if (bitset(H_DEFAULT, h->h_flags) &&
+ !bitset(H_BINDLATE, h->h_flags))
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s: %s\n",
+ h->h_field,
+ denlstring(buf, false, true));
+ }
+ else if (bitset(H_FROM|H_RCPT, h->h_flags) &&
+ !bitset(H_BINDLATE, h->h_flags))
+ {
+ bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);
+ SM_FILE_T *savetrace = TrafficLogFile;
+
+ TrafficLogFile = NULL;
+
+ if (bitset(H_FROM, h->h_flags))
+ oldstyle = false;
+
+ commaize(h, h->h_value, oldstyle, &mcibuf, e);
+
+ TrafficLogFile = savetrace;
+ }
+ else
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s: %s\n",
+ h->h_field,
+ denlstring(h->h_value, false,
+ true));
+ }
+ }
+
+ /*
+ ** Clean up.
+ **
+ ** Write a terminator record -- this is to prevent
+ ** scurrilous crackers from appending any data.
+ */
+
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, ".\n");
+
+ if (sm_io_flush(tfp, SM_TIME_DEFAULT) != 0 ||
+ ((SuperSafe == SAFE_REALLY ||
+ (SuperSafe == SAFE_INTERACTIVE && msync)) &&
+ fsync(sm_io_getinfo(tfp, SM_IO_WHAT_FD, NULL)) < 0) ||
+ sm_io_error(tfp))
+ {
+ if (newid)
+ syserr("!552 Error writing control file %s", tf);
+ else
+ syserr("!452 Error writing control file %s", tf);
+ }
+
+ if (!newid)
+ {
+#if _FFR_QUARANTINE
+ char new = queue_letter(e, ANYQFL_LETTER);
+#endif /* _FFR_QUARANTINE */
+
+ /* rename (locked) tf to be (locked) [qh]f */
+ (void) sm_strlcpy(qf, queuename(e, ANYQFL_LETTER),
+ sizeof qf);
+ if (rename(tf, qf) < 0)
+ syserr("cannot rename(%s, %s), uid=%d",
+ tf, qf, (int) geteuid());
+# if _FFR_QUARANTINE
+ else
+ {
+ /*
+ ** Check if type has changed and only
+ ** remove the old item if the rename above
+ ** succeeded.
+ */
+
+ if (e->e_qfletter != '\0' &&
+ e->e_qfletter != new)
+ {
+ if (tTd(40, 5))
+ {
+ sm_dprintf("type changed from %c to %c\n",
+ e->e_qfletter, new);
+ }
+
+ if (unlink(queuename(e, e->e_qfletter)) < 0)
+ {
+ /* XXX: something more drastic? */
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "queueup: unlink(%s) failed: %s",
+ queuename(e, e->e_qfletter),
+ sm_errstring(errno));
+ }
+ }
+ }
+ e->e_qfletter = new;
+# endif /* _FFR_QUARANTINE */
+
+ /*
+ ** fsync() after renaming to make sure metadata is
+ ** written to disk on filesystems in which renames are
+ ** not guaranteed.
+ */
+
+ if (SuperSafe != SAFE_NO)
+ {
+ /* for softupdates */
+ if (tfd >= 0 && fsync(tfd) < 0)
+ {
+ syserr("!queueup: cannot fsync queue temp file %s",
+ tf);
+ }
+ SYNC_DIR(qf, true);
+ }
+
+ /* close and unlock old (locked) queue file */
+ if (e->e_lockfp != NULL)
+ (void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
+ e->e_lockfp = tfp;
+
+ /* save log info */
+ if (LogLevel > 79)
+ sm_syslog(LOG_DEBUG, e->e_id, "queueup %s", qf);
+ }
+ else
+ {
+ /* save log info */
+ if (LogLevel > 79)
+ sm_syslog(LOG_DEBUG, e->e_id, "queueup %s", tf);
+
+#if _FFR_QUARANTINE
+ e->e_qfletter = queue_letter(e, ANYQFL_LETTER);
+#endif /* _FFR_QUARANTINE */
+ }
+
+ errno = 0;
+ e->e_flags |= EF_INQUEUE;
+
+ if (tTd(40, 1))
+ sm_dprintf("<<<<< done queueing %s <<<<<\n\n", e->e_id);
+ return;
+}
+
+/*
+** PRINTCTLADDR -- print control address to file.
+**
+** Parameters:
+** a -- address.
+** tfp -- file pointer.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The control address (if changed) is printed to the file.
+** The last control address and uid are saved.
+*/
+
+static void
+printctladdr(a, tfp)
+ register ADDRESS *a;
+ SM_FILE_T *tfp;
+{
+ char *user;
+ register ADDRESS *q;
+ uid_t uid;
+ gid_t gid;
+ static ADDRESS *lastctladdr = NULL;
+ static uid_t lastuid;
+
+ /* initialization */
+ if (a == NULL || a->q_alias == NULL || tfp == NULL)
+ {
+ if (lastctladdr != NULL && tfp != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C\n");
+ lastctladdr = NULL;
+ lastuid = 0;
+ return;
+ }
+
+ /* find the active uid */
+ q = getctladdr(a);
+ if (q == NULL)
+ {
+ user = NULL;
+ uid = 0;
+ gid = 0;
+ }
+ else
+ {
+ user = q->q_ruser != NULL ? q->q_ruser : q->q_user;
+ uid = q->q_uid;
+ gid = q->q_gid;
+ }
+ a = a->q_alias;
+
+ /* check to see if this is the same as last time */
+ if (lastctladdr != NULL && uid == lastuid &&
+ strcmp(lastctladdr->q_paddr, a->q_paddr) == 0)
+ return;
+ lastuid = uid;
+ lastctladdr = a;
+
+ if (uid == 0 || user == NULL || user[0] == '\0')
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C");
+ else
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C%s:%ld:%ld",
+ denlstring(user, true, false), (long) uid,
+ (long) gid);
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, ":%s\n",
+ denlstring(a->q_paddr, true, false));
+}
+
+/*
+** RUNNERS_SIGTERM -- propagate a SIGTERM to queue runner process
+**
+** This propagates the signal to the child processes that are queue
+** runners. This is for a queue runner "cleanup". After all of the
+** child queue runner processes are signaled (it should be SIGTERM
+** being the sig) then the old signal handler (Oldsh) is called
+** to handle any cleanup set for this process (provided it is not
+** SIG_DFL or SIG_IGN). The signal may not be handled immediately
+** if the BlockOldsh flag is set. If the current process doesn't
+** have a parent then handle the signal immediately, regardless of
+** BlockOldsh.
+**
+** Parameters:
+** sig -- the signal number being sent
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets the NoMoreRunners boolean to true to stop more runners
+** from being started in runqueue().
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+static bool volatile NoMoreRunners = false;
+static sigfunc_t Oldsh_term = SIG_DFL;
+static sigfunc_t Oldsh_hup = SIG_DFL;
+static sigfunc_t volatile Oldsh = SIG_DFL;
+static bool BlockOldsh = false;
+static int volatile Oldsig = 0;
+static SIGFUNC_DECL runners_sigterm __P((int));
+static SIGFUNC_DECL runners_sighup __P((int));
+
+static SIGFUNC_DECL
+runners_sigterm(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, runners_sigterm);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ NoMoreRunners = true;
+ Oldsh = Oldsh_term;
+ Oldsig = sig;
+ proc_list_signal(PROC_QUEUE, sig);
+
+ if (!BlockOldsh || getppid() <= 1)
+ {
+ /* Check that a valid 'old signal handler' is callable */
+ if (Oldsh_term != SIG_DFL && Oldsh_term != SIG_IGN &&
+ Oldsh_term != runners_sigterm)
+ (*Oldsh_term)(sig);
+ }
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** RUNNERS_SIGHUP -- propagate a SIGHUP to queue runner process
+**
+** This propagates the signal to the child processes that are queue
+** runners. This is for a queue runner "cleanup". After all of the
+** child queue runner processes are signaled (it should be SIGHUP
+** being the sig) then the old signal handler (Oldsh) is called to
+** handle any cleanup set for this process (provided it is not SIG_DFL
+** or SIG_IGN). The signal may not be handled immediately if the
+** BlockOldsh flag is set. If the current process doesn't have
+** a parent then handle the signal immediately, regardless of
+** BlockOldsh.
+**
+** Parameters:
+** sig -- the signal number being sent
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets the NoMoreRunners boolean to true to stop more runners
+** from being started in runqueue().
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+static SIGFUNC_DECL
+runners_sighup(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, runners_sighup);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ NoMoreRunners = true;
+ Oldsh = Oldsh_hup;
+ Oldsig = sig;
+ proc_list_signal(PROC_QUEUE, sig);
+
+ if (!BlockOldsh || getppid() <= 1)
+ {
+ /* Check that a valid 'old signal handler' is callable */
+ if (Oldsh_hup != SIG_DFL && Oldsh_hup != SIG_IGN &&
+ Oldsh_hup != runners_sighup)
+ (*Oldsh_hup)(sig);
+ }
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** MARK_WORK_GROUP_RESTART -- mark a work group as needing a restart
+**
+** Sets a workgroup for restarting.
+**
+** Parameters:
+** wgrp -- the work group id to restart.
+** reason -- why (signal?), -1 to turn off restart
+**
+** Returns:
+** none.
+**
+** Side effects:
+** May set global RestartWorkGroup to true.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+mark_work_group_restart(wgrp, reason)
+ int wgrp;
+ int reason;
+{
+ if (wgrp < 0 || wgrp > NumWorkGroups)
+ return;
+
+ WorkGrp[wgrp].wg_restart = reason;
+ if (reason >= 0)
+ RestartWorkGroup = true;
+}
+/*
+** RESTART_MARKED_WORK_GROUPS -- restart work groups marked as needing restart
+**
+** Restart any workgroup marked as needing a restart provided more
+** runners are allowed.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side effects:
+** Sets global RestartWorkGroup to false.
+*/
+
+void
+restart_marked_work_groups()
+{
+ int i;
+ int wasblocked;
+
+ if (NoMoreRunners)
+ return;
+
+ /* Block SIGCHLD so reapchild() doesn't mess with us */
+ wasblocked = sm_blocksignal(SIGCHLD);
+
+ for (i = 0; i < NumWorkGroups; i++)
+ {
+ if (WorkGrp[i].wg_restart >= 0)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "restart queue runner=%d due to signal 0x%x",
+ i, WorkGrp[i].wg_restart);
+ restart_work_group(i);
+ }
+ }
+ RestartWorkGroup = false;
+
+ if (wasblocked == 0)
+ (void) sm_releasesignal(SIGCHLD);
+}
+/*
+** RESTART_WORK_GROUP -- restart a specific work group
+**
+** Restart a specific workgroup provided more runners are allowed.
+** If the requested work group has been restarted too many times log
+** this and refuse to restart.
+**
+** Parameters:
+** wgrp -- the work group id to restart
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** starts another process doing the work of wgrp
+*/
+
+#define MAX_PERSIST_RESTART 10 /* max allowed number of restarts */
+
+static void
+restart_work_group(wgrp)
+ int wgrp;
+{
+ if (NoMoreRunners ||
+ wgrp < 0 || wgrp > NumWorkGroups)
+ return;
+
+ WorkGrp[wgrp].wg_restart = -1;
+ if (WorkGrp[wgrp].wg_restartcnt < MAX_PERSIST_RESTART)
+ {
+ /* avoid overflow; increment here */
+ WorkGrp[wgrp].wg_restartcnt++;
+ (void) run_work_group(wgrp, true, false, true, true);
+ }
+ else
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "ERROR: persistent queue runner=%d restarted too many times, queue runner lost",
+ wgrp);
+ }
+}
+/*
+** SCHEDULE_QUEUE_RUNS -- schedule the next queue run for a work group.
+**
+** Parameters:
+** runall -- schedule even if individual bit is not set.
+** wgrp -- the work group id to schedule.
+** didit -- the queue run was performed for this work group.
+**
+** Returns:
+** nothing
+*/
+
+#define INCR_MOD(v, m) if (++v >= m) \
+ v = 0; \
+ else
+
+static void
+schedule_queue_runs(runall, wgrp, didit)
+ bool runall;
+ int wgrp;
+ bool didit;
+{
+ int qgrp, cgrp, endgrp;
+#if _FFR_QUEUE_SCHED_DBG
+ time_t lastsched;
+ bool sched;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ time_t now;
+ time_t minqintvl;
+
+ /*
+ ** This is a bit ugly since we have to duplicate the
+ ** code that "walks" through a work queue group.
+ */
+
+ now = curtime();
+ minqintvl = 0;
+ cgrp = endgrp = WorkGrp[wgrp].wg_curqgrp;
+ do
+ {
+ time_t qintvl;
+
+#if _FFR_QUEUE_SCHED_DBG
+ lastsched = 0;
+ sched = false;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ qgrp = WorkGrp[wgrp].wg_qgs[cgrp]->qg_index;
+ if (Queue[qgrp]->qg_queueintvl > 0)
+ qintvl = Queue[qgrp]->qg_queueintvl;
+ else if (QueueIntvl > 0)
+ qintvl = QueueIntvl;
+ else
+ qintvl = (time_t) 0;
+#if _FFR_QUEUE_SCHED_DBG
+ lastsched = Queue[qgrp]->qg_nextrun;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ if ((runall || Queue[qgrp]->qg_nextrun <= now) && qintvl > 0)
+ {
+#if _FFR_QUEUE_SCHED_DBG
+ sched = true;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ if (minqintvl == 0 || qintvl < minqintvl)
+ minqintvl = qintvl;
+
+ /*
+ ** Only set a new time if a queue run was performed
+ ** for this queue group. If the queue was not run,
+ ** we could starve it by setting a new time on each
+ ** call.
+ */
+
+ if (didit)
+ Queue[qgrp]->qg_nextrun += qintvl;
+ }
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 10))
+ sm_syslog(LOG_INFO, NOQID,
+ "sqr: wgrp=%d, cgrp=%d, qgrp=%d, intvl=%ld, QI=%ld, runall=%d, lastrun=%ld, nextrun=%ld, sched=%d",
+ wgrp, cgrp, qgrp, Queue[qgrp]->qg_queueintvl,
+ QueueIntvl, runall, lastsched,
+ Queue[qgrp]->qg_nextrun, sched);
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ INCR_MOD(cgrp, WorkGrp[wgrp].wg_numqgrp);
+ } while (endgrp != cgrp);
+ if (minqintvl > 0)
+ (void) sm_setevent(minqintvl, runqueueevent, 0);
+}
+
+#if _FFR_QUEUE_RUN_PARANOIA
+/*
+** CHECKQUEUERUNNER -- check whether a queue group hasn't been run.
+**
+** Use this if events may get lost and hence queue runners may not
+** be started and mail will pile up in a queue.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** true if a queue run is necessary.
+**
+** Side Effects:
+** may schedule a queue run.
+*/
+
+bool
+checkqueuerunner()
+{
+ int qgrp;
+ time_t now, minqintvl;
+
+ now = curtime();
+ minqintvl = 0;
+ for (qgrp = 0; qgrp < NumQueue && Queue[qgrp] != NULL; qgrp++)
+ {
+ time_t qintvl;
+
+ if (Queue[qgrp]->qg_queueintvl > 0)
+ qintvl = Queue[qgrp]->qg_queueintvl;
+ else if (QueueIntvl > 0)
+ qintvl = QueueIntvl;
+ else
+ qintvl = (time_t) 0;
+ if (Queue[qgrp]->qg_nextrun <= now - qintvl)
+ {
+ if (minqintvl == 0 || qintvl < minqintvl)
+ minqintvl = qintvl;
+ if (LogLevel > 1)
+ sm_syslog(LOG_WARNING, NOQID,
+ "checkqueuerunner: queue %d should have been run at %s, queue interval %ld",
+ qgrp,
+ arpadate(ctime(&Queue[qgrp]->qg_nextrun)),
+ qintvl);
+ }
+ }
+ if (minqintvl > 0)
+ {
+ (void) sm_setevent(minqintvl, runqueueevent, 0);
+ return true;
+ }
+ return false;
+}
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+
+/*
+** RUNQUEUE -- run the jobs in the queue.
+**
+** Gets the stuff out of the queue in some presumably logical
+** order and processes them.
+**
+** Parameters:
+** forkflag -- true if the queue scanning should be done in
+** a child process. We double-fork so it is not our
+** child and we don't have to clean up after it.
+** false can be ignored if we have multiple queues.
+** verbose -- if true, print out status information.
+** persistent -- persistent queue runner?
+** runall -- run all groups or only a subset (DoQueueRun)?
+**
+** Returns:
+** true if the queue run successfully began.
+**
+** Side Effects:
+** runs things in the mail queue using run_work_group().
+** maybe schedules next queue run.
+*/
+
+static ENVELOPE QueueEnvelope; /* the queue run envelope */
+static time_t LastQueueTime = 0; /* last time a queue ID assigned */
+static pid_t LastQueuePid = -1; /* last PID which had a queue ID */
+
+/* values for qp_supdirs */
+#define QP_NOSUB 0x0000 /* No subdirectories */
+#define QP_SUBDF 0x0001 /* "df" subdirectory */
+#define QP_SUBQF 0x0002 /* "qf" subdirectory */
+#define QP_SUBXF 0x0004 /* "xf" subdirectory */
+
+bool
+runqueue(forkflag, verbose, persistent, runall)
+ bool forkflag;
+ bool verbose;
+ bool persistent;
+ bool runall;
+{
+ int i;
+ bool ret = true;
+ static int curnum = 0;
+ sigfunc_t cursh;
+#if SM_HEAP_CHECK
+ SM_NONVOLATILE int oldgroup = 0;
+
+ if (sm_debug_active(&DebugLeakQ, 1))
+ {
+ oldgroup = sm_heap_group();
+ sm_heap_newgroup();
+ sm_dprintf("runqueue() heap group #%d\n", sm_heap_group());
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* queue run has been started, don't do any more this time */
+ DoQueueRun = false;
+
+ /* more than one queue or more than one directory per queue */
+ if (!forkflag && !verbose &&
+ (WorkGrp[0].wg_qgs[0]->qg_numqueues > 1 || NumWorkGroups > 1 ||
+ WorkGrp[0].wg_numqgrp > 1))
+ forkflag = true;
+
+ /*
+ ** For controlling queue runners via signals sent to this process.
+ ** Oldsh* will get called too by runners_sig* (if it is not SIG_IGN
+ ** or SIG_DFL) to preserve cleanup behavior. Now that this process
+ ** will have children (and perhaps grandchildren) this handler will
+ ** be left in place. This is because this process, once it has
+ ** finished spinning off queue runners, may go back to doing something
+ ** else (like being a daemon). And we still want on a SIG{TERM,HUP} to
+ ** clean up the child queue runners. Only install 'runners_sig*' once
+ ** else we'll get stuck looping forever.
+ */
+
+ cursh = sm_signal(SIGTERM, runners_sigterm);
+ if (cursh != runners_sigterm)
+ Oldsh_term = cursh;
+ cursh = sm_signal(SIGHUP, runners_sighup);
+ if (cursh != runners_sighup)
+ Oldsh_hup = cursh;
+
+ for (i = 0; i < NumWorkGroups && !NoMoreRunners; i++)
+ {
+ /*
+ ** If MaxQueueChildren active then test whether the start
+ ** of the next queue group's additional queue runners (maximum)
+ ** will result in MaxQueueChildren being exceeded.
+ **
+ ** Note: do not use continue; even though another workgroup
+ ** may have fewer queue runners, this would be "unfair",
+ ** i.e., this work group might "starve" then.
+ */
+
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 10))
+ sm_syslog(LOG_INFO, NOQID,
+ "rq: curnum=%d, MaxQueueChildren=%d, CurRunners=%d, WorkGrp[curnum].wg_maxact=%d",
+ curnum, MaxQueueChildren, CurRunners,
+ WorkGrp[curnum].wg_maxact);
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ if (MaxQueueChildren > 0 &&
+ CurRunners + WorkGrp[curnum].wg_maxact > MaxQueueChildren)
+ break;
+
+ /*
+ ** Pick up where we left off (curnum), in case we
+ ** used up all the children last time without finishing.
+ ** This give a round-robin fairness to queue runs.
+ **
+ ** Increment CurRunners before calling run_work_group()
+ ** to avoid a "race condition" with proc_list_drop() which
+ ** decrements CurRunners if the queue runners terminate.
+ ** This actually doesn't cause any harm, but CurRunners
+ ** might become negative which is at least confusing.
+ **
+ ** Notice: CurRunners is an upper limit, in some cases
+ ** (too few jobs in the queue) this value is larger than
+ ** the actual number of queue runners. The discrepancy can
+ ** increase if some queue runners "hang" for a long time.
+ */
+
+ CurRunners += WorkGrp[curnum].wg_maxact;
+ ret = run_work_group(curnum, forkflag, verbose, persistent,
+ runall);
+
+ /*
+ ** Failure means a message was printed for ETRN
+ ** and subsequent queues are likely to fail as well.
+ ** Decrement CurRunners in that case because
+ ** none have been started.
+ */
+
+ if (!ret)
+ {
+ CurRunners -= WorkGrp[curnum].wg_maxact;
+ break;
+ }
+
+ if (!persistent)
+ schedule_queue_runs(runall, curnum, true);
+ INCR_MOD(curnum, NumWorkGroups);
+ }
+
+ /* schedule left over queue runs */
+ if (i < NumWorkGroups && !NoMoreRunners && !persistent)
+ {
+ int h;
+
+ for (h = curnum; i < NumWorkGroups; i++)
+ {
+ schedule_queue_runs(runall, h, false);
+ INCR_MOD(h, NumWorkGroups);
+ }
+ }
+
+
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&DebugLeakQ, 1))
+ sm_heap_setgroup(oldgroup);
+#endif /* SM_HEAP_CHECK */
+ return ret;
+}
+/*
+** RUNNER_WORK -- have a queue runner do its work
+**
+** Have a queue runner do its work a list of entries.
+** When work isn't directly being done then this process can take a signal
+** and terminate immediately (in a clean fashion of course).
+** When work is directly being done, it's not to be interrupted
+** immediately: the work should be allowed to finish at a clean point
+** before termination (in a clean fashion of course).
+**
+** Parameters:
+** e -- envelope.
+** sequenceno -- 'th process to run WorkQ.
+** didfork -- did the calling process fork()?
+** skip -- process only each skip'th item.
+** njobs -- number of jobs in WorkQ.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** runs things in the mail queue.
+*/
+
+/* Get new load average every 30 seconds. */
+#define GET_NEW_LA_TIME 30
+
+static void
+runner_work(e, sequenceno, didfork, skip, njobs)
+ register ENVELOPE *e;
+ int sequenceno;
+ bool didfork;
+ int skip;
+ int njobs;
+{
+ int n;
+ WORK *w;
+ time_t current_la_time, now;
+
+ current_la_time = curtime();
+
+ /*
+ ** Here we temporarily block the second calling of the handlers.
+ ** This allows us to handle the signal without terminating in the
+ ** middle of direct work. If a signal does come, the test for
+ ** NoMoreRunners will find it.
+ */
+
+ BlockOldsh = true;
+
+ /* process them once at a time */
+ while (WorkQ != NULL)
+ {
+#if SM_HEAP_CHECK
+ SM_NONVOLATILE int oldgroup = 0;
+
+ if (sm_debug_active(&DebugLeakQ, 1))
+ {
+ oldgroup = sm_heap_group();
+ sm_heap_newgroup();
+ sm_dprintf("run_queue_group() heap group #%d\n",
+ sm_heap_group());
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* do no more work */
+ if (NoMoreRunners)
+ {
+ /* Check that a valid signal handler is callable */
+ if (Oldsh != SIG_DFL && Oldsh != SIG_IGN &&
+ Oldsh != runners_sighup &&
+ Oldsh != runners_sigterm)
+ (*Oldsh)(Oldsig);
+ break;
+ }
+
+ w = WorkQ; /* assign current work item */
+
+ /*
+ ** Set the head of the WorkQ to the next work item.
+ ** It is set 'skip' ahead (the number of parallel queue
+ ** runners working on WorkQ together) since each runner
+ ** works on every 'skip'th (N-th) item.
+ */
+
+ for (n = 0; n < skip && WorkQ != NULL; n++)
+ WorkQ = WorkQ->w_next;
+ e->e_to = NULL;
+
+ /*
+ ** Ignore jobs that are too expensive for the moment.
+ **
+ ** Get new load average every GET_NEW_LA_TIME seconds.
+ */
+
+ now = curtime();
+ if (current_la_time < now - GET_NEW_LA_TIME)
+ {
+ sm_getla();
+ current_la_time = now;
+ }
+ if (shouldqueue(WkRecipFact, current_la_time))
+ {
+ char *msg = "Aborting queue run: load average too high";
+
+ if (Verbose)
+ message("%s", msg);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s", msg);
+ break;
+ }
+ if (shouldqueue(w->w_pri, w->w_ctime))
+ {
+ if (Verbose)
+ message(EmptyString);
+ if (QueueSortOrder == QSO_BYPRIORITY)
+ {
+ if (Verbose)
+ message("Skipping %s/%s (sequence %d of %d) and flushing rest of queue",
+ qid_printqueue(w->w_qgrp,
+ w->w_qdir),
+ w->w_name + 2, sequenceno,
+ njobs);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "runqueue: Flushing queue from %s/%s (pri %ld, LA %d, %d of %d)",
+ qid_printqueue(w->w_qgrp,
+ w->w_qdir),
+ w->w_name + 2, w->w_pri,
+ CurrentLA, sequenceno,
+ njobs);
+ break;
+ }
+ else if (Verbose)
+ message("Skipping %s/%s (sequence %d of %d)",
+ qid_printqueue(w->w_qgrp, w->w_qdir),
+ w->w_name + 2, sequenceno, njobs);
+ }
+ else
+ {
+ if (Verbose)
+ {
+ message(EmptyString);
+ message("Running %s/%s (sequence %d of %d)",
+ qid_printqueue(w->w_qgrp, w->w_qdir),
+ w->w_name + 2, sequenceno, njobs);
+ }
+ if (didfork && MaxQueueChildren > 0)
+ {
+ sm_blocksignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, reapchild);
+ }
+ if (tTd(63, 100))
+ sm_syslog(LOG_DEBUG, NOQID,
+ "runqueue %s dowork(%s)",
+ qid_printqueue(w->w_qgrp, w->w_qdir),
+ w->w_name + 2);
+
+ (void) dowork(w->w_qgrp, w->w_qdir, w->w_name + 2,
+ false, false, e);
+ errno = 0;
+ }
+ sm_free(w->w_name); /* XXX */
+ if (w->w_host != NULL)
+ sm_free(w->w_host); /* XXX */
+ sm_free((char *) w); /* XXX */
+ sequenceno += skip; /* next sequence number */
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&DebugLeakQ, 1))
+ sm_heap_setgroup(oldgroup);
+#endif /* SM_HEAP_CHECK */
+ }
+
+ BlockOldsh = false;
+
+ /* check the signals didn't happen during the revert */
+ if (NoMoreRunners)
+ {
+ /* Check that a valid signal handler is callable */
+ if (Oldsh != SIG_DFL && Oldsh != SIG_IGN &&
+ Oldsh != runners_sighup && Oldsh != runners_sigterm)
+ (*Oldsh)(Oldsig);
+ }
+
+ Oldsh = SIG_DFL; /* after the NoMoreRunners check */
+}
+/*
+** RUN_WORK_GROUP -- run the jobs in a queue group from a work group.
+**
+** Gets the stuff out of the queue in some presumably logical
+** order and processes them.
+**
+** Parameters:
+** wgrp -- work group to process.
+** forkflag -- true if the queue scanning should be done in
+** a child process. We double-fork so it is not our
+** child and we don't have to clean up after it.
+** verbose -- if true, print out status information.
+** persistent -- persistent queue runner?
+** runall -- true: run all of the queue groups in this work group
+**
+** Returns:
+** true if the queue run successfully began.
+**
+** Side Effects:
+** runs things in the mail queue.
+*/
+
+/* Minimum sleep time for persistent queue runners */
+#define MIN_SLEEP_TIME 5
+
+bool
+run_work_group(wgrp, forkflag, verbose, persistent, runall)
+ int wgrp;
+ bool forkflag;
+ bool verbose;
+ bool persistent;
+ bool runall;
+{
+ register ENVELOPE *e;
+ int njobs, qdir;
+ int sequenceno = 1;
+ int qgrp, endgrp, h, i;
+ time_t current_la_time, now;
+ bool full, more;
+ SM_RPOOL_T *rpool;
+ extern void rmexpstab __P((void));
+ extern ENVELOPE BlankEnvelope;
+ extern SIGFUNC_DECL reapchild __P((int));
+
+ if (wgrp < 0)
+ return false;
+
+ /*
+ ** If no work will ever be selected, don't even bother reading
+ ** the queue.
+ */
+
+ sm_getla(); /* get load average */
+ current_la_time = curtime();
+
+ if (!persistent && shouldqueue(WkRecipFact, current_la_time))
+ {
+ char *msg = "Skipping queue run -- load average too high";
+
+ if (verbose)
+ message("458 %s\n", msg);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s", msg);
+ return false;
+ }
+
+ /*
+ ** See if we already have too many children.
+ */
+
+ if (forkflag && WorkGrp[wgrp].wg_lowqintvl > 0 && !persistent &&
+ MaxChildren > 0 && CurChildren >= MaxChildren)
+ {
+ char *msg = "Skipping queue run -- too many children";
+
+ if (verbose)
+ message("458 %s (%d)\n", msg, CurChildren);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s (%d)",
+ msg, CurChildren);
+ return false;
+ }
+
+ /*
+ ** See if we want to go off and do other useful work.
+ */
+
+ if (forkflag)
+ {
+ pid_t pid;
+
+ (void) sm_blocksignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, reapchild);
+
+ pid = dofork();
+ if (pid == -1)
+ {
+ const char *msg = "Skipping queue run -- fork() failed";
+ const char *err = sm_errstring(errno);
+
+ if (verbose)
+ message("458 %s: %s\n", msg, err);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s: %s",
+ msg, err);
+ (void) sm_releasesignal(SIGCHLD);
+ return false;
+ }
+ if (pid != 0)
+ {
+ /* parent -- pick up intermediate zombie */
+ (void) sm_blocksignal(SIGALRM);
+
+ /* wgrp only used when queue runners are persistent */
+ proc_list_add(pid, "Queue runner", PROC_QUEUE,
+ WorkGrp[wgrp].wg_maxact,
+ persistent ? wgrp : -1);
+ (void) sm_releasesignal(SIGALRM);
+ (void) sm_releasesignal(SIGCHLD);
+ return true;
+ }
+
+ /* child -- clean up signals */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ sm_exc_newthread(fatal_error);
+ clrcontrol();
+ proc_list_clear();
+
+ /* Add parent process as first child item */
+ proc_list_add(CurrentPid, "Queue runner child process",
+ PROC_QUEUE_CHILD, 0, -1);
+ (void) sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_DFL);
+ (void) sm_signal(SIGTERM, intsig);
+ }
+
+ /*
+ ** Release any resources used by the daemon code.
+ */
+
+ clrdaemon();
+
+ /* force it to run expensive jobs */
+ NoConnect = false;
+
+ /* drop privileges */
+ if (geteuid() == (uid_t) 0)
+ (void) drop_privileges(false);
+
+ /*
+ ** Create ourselves an envelope
+ */
+
+ CurEnv = &QueueEnvelope;
+ rpool = sm_rpool_new_x(NULL);
+ e = newenvelope(&QueueEnvelope, CurEnv, rpool);
+ e->e_flags = BlankEnvelope.e_flags;
+ e->e_parent = NULL;
+
+ /* make sure we have disconnected from parent */
+ if (forkflag)
+ {
+ disconnect(1, e);
+ QuickAbort = false;
+ }
+
+ /*
+ ** If we are running part of the queue, always ignore stored
+ ** host status.
+ */
+
+ if (QueueLimitId != NULL || QueueLimitSender != NULL ||
+#if _FFR_QUARANTINE
+ QueueLimitQuarantine != NULL ||
+#endif /* _FFR_QUARANTINE */
+ QueueLimitRecipient != NULL)
+ {
+ IgnoreHostStatus = true;
+ MinQueueAge = 0;
+ }
+
+ /*
+ ** Here is where we choose the queue group from the work group.
+ ** The caller of the "domorework" label must setup a new envelope.
+ */
+
+ endgrp = WorkGrp[wgrp].wg_curqgrp; /* to not spin endlessly */
+
+ domorework:
+
+ /*
+ ** Run a queue group if:
+ ** runall is set or the bit for this group is set.
+ */
+
+ now = curtime();
+ for (;;)
+ {
+ /*
+ ** Find the next queue group within the work group that
+ ** has been marked as needing a run.
+ */
+
+ qgrp = WorkGrp[wgrp].wg_qgs[WorkGrp[wgrp].wg_curqgrp]->qg_index;
+ WorkGrp[wgrp].wg_curqgrp++; /* advance */
+ WorkGrp[wgrp].wg_curqgrp %= WorkGrp[wgrp].wg_numqgrp; /* wrap */
+ if (runall ||
+ (Queue[qgrp]->qg_nextrun <= now &&
+ Queue[qgrp]->qg_nextrun != (time_t) -1))
+ break;
+ if (endgrp == WorkGrp[wgrp].wg_curqgrp)
+ {
+ e->e_id = NULL;
+ if (forkflag)
+ finis(true, true, ExitStat);
+ return true; /* we're done */
+ }
+ }
+
+ qdir = Queue[qgrp]->qg_curnum; /* round-robin init of queue position */
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 12))
+ sm_syslog(LOG_INFO, NOQID,
+ "rwg: wgrp=%d, qgrp=%d, qdir=%d, name=%s, curqgrp=%d, numgrps=%d",
+ wgrp, qgrp, qdir, qid_printqueue(qgrp, qdir),
+ WorkGrp[wgrp].wg_curqgrp, WorkGrp[wgrp].wg_numqgrp);
+#endif /* _FFR_QUEUE_SCHED_DBG */
+
+#if HASNICE
+ /* tweak niceness of queue runs */
+ if (Queue[qgrp]->qg_nice > 0)
+ (void) nice(Queue[qgrp]->qg_nice);
+#endif /* HASNICE */
+
+ /* XXX running queue group... */
+ sm_setproctitle(true, CurEnv, "running queue: %s",
+ qid_printqueue(qgrp, qdir));
+
+ if (LogLevel > 69 || tTd(63, 99))
+ sm_syslog(LOG_DEBUG, NOQID,
+ "runqueue %s, pid=%d, forkflag=%d",
+ qid_printqueue(qgrp, qdir), (int) CurrentPid,
+ forkflag);
+
+ /*
+ ** Start making passes through the queue.
+ ** First, read and sort the entire queue.
+ ** Then, process the work in that order.
+ ** But if you take too long, start over.
+ */
+
+ for (i = 0; i < Queue[qgrp]->qg_numqueues; i++)
+ {
+ h = gatherq(qgrp, qdir, false, &full, &more);
+#if SM_CONF_SHM
+ if (ShmId != SM_SHM_NO_ID)
+ QSHM_ENTRIES(Queue[qgrp]->qg_qpaths[qdir].qp_idx) = h;
+#endif /* SM_CONF_SHM */
+ /* If there are no more items in this queue advance */
+ if (!more)
+ {
+ /* A round-robin advance */
+ qdir++;
+ qdir %= Queue[qgrp]->qg_numqueues;
+ }
+
+ /* Has the WorkList reached the limit? */
+ if (full)
+ break; /* don't try to gather more */
+ }
+
+ /* order the existing work requests */
+ njobs = sortq(Queue[qgrp]->qg_maxlist);
+ Queue[qgrp]->qg_curnum = qdir; /* update */
+
+
+ if (!Verbose && bitnset(QD_FORK, Queue[qgrp]->qg_flags))
+ {
+ int loop, maxrunners;
+ pid_t pid;
+
+ /*
+ ** For this WorkQ we want to fork off N children (maxrunners)
+ ** at this point. Each child has a copy of WorkQ. Each child
+ ** will process every N-th item. The parent will wait for all
+ ** of the children to finish before moving on to the next
+ ** queue group within the work group. This saves us forking
+ ** a new runner-child for each work item.
+ ** It's valid for qg_maxqrun == 0 since this may be an
+ ** explicit "don't run this queue" setting.
+ */
+
+ maxrunners = Queue[qgrp]->qg_maxqrun;
+
+ /* No need to have more runners then there are jobs */
+ if (maxrunners > njobs)
+ maxrunners = njobs;
+ for (loop = 0; loop < maxrunners; loop++)
+ {
+#if _FFR_NONSTOP_PERSISTENCE
+ /*
+ ** Require a free "slot" before processing
+ ** this queue runner.
+ */
+
+ while (MaxQueueChildren > 0 &&
+ CurChildren > MaxQueueChildren)
+ {
+ int status;
+ pid_t ret;
+
+ while ((ret = sm_wait(&status)) <= 0)
+ continue;
+ proc_list_drop(ret, status, NULL);
+ }
+#endif /* _FFR_NONSTOP_PERSISTENCE */
+
+ /*
+ ** Since the delivery may happen in a child and the
+ ** parent does not wait, the parent may close the
+ ** maps thereby removing any shared memory used by
+ ** the map. Therefore, close the maps now so the
+ ** child will dynamically open them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("run_work_group: cannot fork");
+ return 0;
+ }
+ else if (pid > 0)
+ {
+ /* parent -- clean out connection cache */
+ mci_flush(false, NULL);
+ WorkQ = WorkQ->w_next; /* for the skip */
+ sequenceno++;
+ proc_list_add(pid, "Queue child runner process",
+ PROC_QUEUE_CHILD, 0, -1);
+
+ /* No additional work, no additional runners */
+ if (WorkQ == NULL)
+ break;
+ }
+ else
+ {
+ /* child -- Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+
+ /*
+ ** Initialize exception stack and default
+ ** exception handler for child process.
+ ** When fork()'d the child now has a private
+ ** copy of WorkQ at its current position.
+ */
+
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** SMTP processes (whether -bd or -bs) set
+ ** SIGCHLD to reapchild to collect
+ ** children status. However, at delivery
+ ** time, that status must be collected
+ ** by sm_wait() to be dealt with properly
+ ** (check success of delivery based
+ ** on status code, etc). Therefore, if we
+ ** are an SMTP process, reset SIGCHLD
+ ** back to the default so reapchild
+ ** doesn't collect status before
+ ** sm_wait().
+ */
+
+ if (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ MaxQueueChildren > 0)
+ {
+ proc_list_clear();
+ sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ }
+
+ /* child -- error messages to the transcript */
+ QuickAbort = OnlyOneError = false;
+ runner_work(e, sequenceno, true,
+ maxrunners, njobs);
+
+ /* This child is done */
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ }
+
+ sm_releasesignal(SIGCHLD);
+
+#if !_FFR_NONSTOP_PERSISTENCE
+ /*
+ ** Wait until all of the runners have completed before
+ ** seeing if there is another queue group in the
+ ** work group to process.
+ ** XXX Future enhancement: don't wait() for all children
+ ** here, just go ahead and make sure that overall the number
+ ** of children is not exceeded.
+ */
+
+ while (CurChildren > 0)
+ {
+ int status;
+ pid_t ret;
+
+ while ((ret = sm_wait(&status)) <= 0)
+ continue;
+ proc_list_drop(ret, status, NULL);
+ }
+#endif /* !_FFR_NONSTOP_PERSISTENCE */
+ }
+ else
+ {
+ /*
+ ** When current process will not fork children to do the work,
+ ** it will do the work itself. The 'skip' will be 1 since
+ ** there are no child runners to divide the work across.
+ */
+
+ runner_work(e, sequenceno, false, 1, njobs);
+ }
+
+ /* free memory allocated by newenvelope() above */
+ sm_rpool_free(rpool);
+ QueueEnvelope.e_rpool = NULL;
+
+ /* Are there still more queues in the work group to process? */
+ if (endgrp != WorkGrp[wgrp].wg_curqgrp)
+ {
+ rpool = sm_rpool_new_x(NULL);
+ e = newenvelope(&QueueEnvelope, CurEnv, rpool);
+ e->e_flags = BlankEnvelope.e_flags;
+ goto domorework;
+ }
+
+ /* No more queues in work group to process. Now check persistent. */
+ if (persistent)
+ {
+ sequenceno = 1;
+ sm_setproctitle(true, CurEnv, "running queue: %s",
+ qid_printqueue(qgrp, qdir));
+
+ /*
+ ** close bogus maps, i.e., maps which caused a tempfail,
+ ** so we get fresh map connections on the next lookup.
+ ** closemaps() is also called when children are started.
+ */
+
+ closemaps(true);
+
+ /* Close any cached connections. */
+ mci_flush(true, NULL);
+
+ /* Clean out expired related entries. */
+ rmexpstab();
+
+#if NAMED_BIND
+ /* Update MX records for FallBackMX. */
+ if (FallBackMX != NULL)
+ (void) getfallbackmxrr(FallBackMX);
+#endif /* NAMED_BIND */
+
+#if USERDB
+ /* close UserDatabase */
+ _udbx_close();
+#endif /* USERDB */
+
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&SmHeapCheck, 2)
+ && access("memdump", F_OK) == 0
+ )
+ {
+ SM_FILE_T *out;
+
+ remove("memdump");
+ out = sm_io_open(SmFtStdio, SM_TIME_DEFAULT,
+ "memdump.out", SM_IO_APPEND, NULL);
+ if (out != NULL)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT, "----------------------\n");
+ sm_heap_report(out,
+ sm_debug_level(&SmHeapCheck) - 1);
+ (void) sm_io_close(out, SM_TIME_DEFAULT);
+ }
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* let me rest for a second to catch my breath */
+ if (njobs == 0 && WorkGrp[wgrp].wg_lowqintvl < MIN_SLEEP_TIME)
+ sleep(MIN_SLEEP_TIME);
+ else if (WorkGrp[wgrp].wg_lowqintvl <= 0)
+ sleep(QueueIntvl > 0 ? QueueIntvl : MIN_SLEEP_TIME);
+ else
+ sleep(WorkGrp[wgrp].wg_lowqintvl);
+
+ /*
+ ** Get the LA outside the WorkQ loop if necessary.
+ ** In a persistent queue runner the code is repeated over
+ ** and over but gatherq() may ignore entries due to
+ ** shouldqueue() (do we really have to do this twice?).
+ ** Hence the queue runners would just idle around when once
+ ** CurrentLA caused all entries in a queue to be ignored.
+ */
+
+ now = curtime();
+ if (njobs == 0 && current_la_time < now - GET_NEW_LA_TIME)
+ {
+ sm_getla();
+ current_la_time = now;
+ }
+ rpool = sm_rpool_new_x(NULL);
+ e = newenvelope(&QueueEnvelope, CurEnv, rpool);
+ e->e_flags = BlankEnvelope.e_flags;
+ goto domorework;
+ }
+
+ /* exit without the usual cleanup */
+ e->e_id = NULL;
+ if (forkflag)
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ return true;
+}
+
+/*
+** DOQUEUERUN -- do a queue run?
+*/
+
+bool
+doqueuerun()
+{
+ return DoQueueRun;
+}
+
+/*
+** RUNQUEUEEVENT -- Sets a flag to indicate that a queue run should be done.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The invocation of this function via an alarm may interrupt
+** a set of actions. Thus errno may be set in that context.
+** We need to restore errno at the end of this function to ensure
+** that any work done here that sets errno doesn't return a
+** misleading/false errno value. Errno may be EINTR upon entry to
+** this function because of non-restartable/continuable system
+** API was active. Iff this is true we will override errno as
+** a timeout (as a more accurate error message).
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+runqueueevent()
+{
+ int save_errno = errno;
+
+ /*
+ ** Set the general bit that we want a queue run,
+ ** tested in doqueuerun()
+ */
+
+ DoQueueRun = true;
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 10))
+ sm_syslog(LOG_INFO, NOQID, "rqe: done");
+#endif /* _FFR_QUEUE_SCHED_DBG */
+
+ errno = save_errno;
+ if (errno == EINTR)
+ errno = ETIMEDOUT;
+}
+/*
+** GATHERQ -- gather messages from the message queue(s) the work queue.
+**
+** Parameters:
+** qgrp -- the index of the queue group.
+** qdir -- the index of the queue directory.
+** doall -- if set, include everything in the queue (even
+** the jobs that cannot be run because the load
+** average is too high, or MaxQueueRun is reached).
+** Otherwise, exclude those jobs.
+** full -- (optional) to be set 'true' if WorkList is full
+** more -- (optional) to be set 'true' if there are still more
+** messages in this queue not added to WorkList
+**
+** Returns:
+** The number of request in the queue (not necessarily
+** the number of requests in WorkList however).
+**
+** Side Effects:
+** prepares available work into WorkList
+*/
+
+#define NEED_P 0001 /* 'P': priority */
+#define NEED_T 0002 /* 'T': time */
+#define NEED_R 0004 /* 'R': recipient */
+#define NEED_S 0010 /* 'S': sender */
+#define NEED_H 0020 /* host */
+#if _FFR_QUARANTINE
+# define HAS_QUARANTINE 0040 /* has an unexpected 'q' line */
+# define NEED_QUARANTINE 0100 /* 'q': reason */
+#endif /* _FFR_QUARANTINE */
+
+static WORK *WorkList = NULL; /* list of unsort work */
+static int WorkListSize = 0; /* current max size of WorkList */
+static int WorkListCount = 0; /* # of work items in WorkList */
+
+static int
+gatherq(qgrp, qdir, doall, full, more)
+ int qgrp;
+ int qdir;
+ bool doall;
+ bool *full;
+ bool *more;
+{
+ register struct dirent *d;
+ register WORK *w;
+ register char *p;
+ DIR *f;
+ int i, num_ent;
+ int wn;
+ QUEUE_CHAR *check;
+ char qd[MAXPATHLEN];
+ char qf[MAXPATHLEN];
+
+ wn = WorkListCount - 1;
+ num_ent = 0;
+ if (qdir == NOQDIR)
+ (void) sm_strlcpy(qd, ".", sizeof qd);
+ else
+ (void) sm_strlcpyn(qd, sizeof qd, 2,
+ Queue[qgrp]->qg_qpaths[qdir].qp_name,
+ (bitset(QP_SUBQF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/qf" : ""));
+
+ if (tTd(41, 1))
+ {
+ sm_dprintf("gatherq:\n");
+
+ check = QueueLimitId;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitId = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+
+ check = QueueLimitSender;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitSender = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+
+ check = QueueLimitRecipient;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitRecipient = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+
+#if _FFR_QUARANTINE
+ if (QueueMode == QM_QUARANTINE)
+ {
+ check = QueueLimitQuarantine;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitQuarantine = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+ }
+#endif /* _FFR_QUARANTINE */
+ }
+
+ /* open the queue directory */
+ f = opendir(qd);
+ if (f == NULL)
+ {
+ syserr("gatherq: cannot open \"%s\"",
+ qid_printqueue(qgrp, qdir));
+ if (full != NULL)
+ *full = WorkListCount >= MaxQueueRun && MaxQueueRun > 0;
+ if (more != NULL)
+ *more = false;
+ return 0;
+ }
+
+ /*
+ ** Read the work directory.
+ */
+
+ while ((d = readdir(f)) != NULL)
+ {
+ SM_FILE_T *cf;
+ int qfver = 0;
+ char lbuf[MAXNAME + 1];
+ struct stat sbuf;
+
+ if (tTd(41, 50))
+ sm_dprintf("gatherq: checking %s..", d->d_name);
+
+ /* is this an interesting entry? */
+#if _FFR_QUARANTINE
+ if (!(((QueueMode == QM_NORMAL &&
+ d->d_name[0] == NORMQF_LETTER) ||
+ (QueueMode == QM_QUARANTINE &&
+ d->d_name[0] == QUARQF_LETTER) ||
+ (QueueMode == QM_LOST &&
+ d->d_name[0] == LOSEQF_LETTER)) &&
+ d->d_name[1] == 'f'))
+#else /* _FFR_QUARANTINE */
+ if (d->d_name[0] != NORMQF_LETTER || d->d_name[1] != 'f')
+#endif /* _FFR_QUARANTINE */
+ {
+ if (tTd(41, 50))
+ sm_dprintf(" skipping\n");
+ continue;
+ }
+ if (tTd(41, 50))
+ sm_dprintf("\n");
+
+ if (strlen(d->d_name) >= MAXQFNAME)
+ {
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "gatherq: %s too long, %d max characters\n",
+ d->d_name, MAXQFNAME);
+ if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, NOQID,
+ "gatherq: %s too long, %d max characters",
+ d->d_name, MAXQFNAME);
+ continue;
+ }
+
+ check = QueueLimitId;
+ while (check != NULL)
+ {
+ if (strcontainedin(false, check->queue_match,
+ d->d_name) != check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (QueueLimitId != NULL && check == NULL)
+ continue;
+
+ /* grow work list if necessary */
+ if (++wn >= MaxQueueRun && MaxQueueRun > 0)
+ {
+ if (wn == MaxQueueRun && LogLevel > 0)
+ sm_syslog(LOG_WARNING, NOQID,
+ "WorkList for %s maxed out at %d",
+ qid_printqueue(qgrp, qdir),
+ MaxQueueRun);
+ if (doall)
+ continue; /* just count entries */
+ break;
+ }
+ if (wn >= WorkListSize)
+ {
+ grow_wlist(qgrp, qdir);
+ if (wn >= WorkListSize)
+ continue;
+ }
+ SM_ASSERT(wn >= 0);
+ w = &WorkList[wn];
+
+ (void) sm_strlcpyn(qf, sizeof qf, 3, qd, "/", d->d_name);
+ if (stat(qf, &sbuf) < 0)
+ {
+ if (errno != ENOENT)
+ sm_syslog(LOG_INFO, NOQID,
+ "gatherq: can't stat %s/%s",
+ qid_printqueue(qgrp, qdir),
+ d->d_name);
+ wn--;
+ continue;
+ }
+ if (!bitset(S_IFREG, sbuf.st_mode))
+ {
+ /* Yikes! Skip it or we will hang on open! */
+ if (!((d->d_name[0] == DATAFL_LETTER ||
+ d->d_name[0] == NORMQF_LETTER ||
+#if _FFR_QUARANTINE
+ d->d_name[0] == QUARQF_LETTER ||
+ d->d_name[0] == LOSEQF_LETTER ||
+#endif /* _FFR_QUARANTINE */
+ d->d_name[0] == XSCRPT_LETTER) &&
+ d->d_name[1] == 'f' && d->d_name[2] == '\0'))
+ syserr("gatherq: %s/%s is not a regular file",
+ qid_printqueue(qgrp, qdir), d->d_name);
+ wn--;
+ continue;
+ }
+
+ /* avoid work if possible */
+ if ((QueueSortOrder == QSO_BYFILENAME ||
+ QueueSortOrder == QSO_BYMODTIME ||
+ QueueSortOrder == QSO_RANDOM) &&
+#if _FFR_QUARANTINE
+ QueueLimitQuarantine == NULL &&
+#endif /* _FFR_QUARANTINE */
+ QueueLimitSender == NULL &&
+ QueueLimitRecipient == NULL)
+ {
+ w->w_qgrp = qgrp;
+ w->w_qdir = qdir;
+ w->w_name = newstr(d->d_name);
+ w->w_host = NULL;
+ w->w_lock = w->w_tooyoung = false;
+ w->w_pri = 0;
+ w->w_ctime = 0;
+ w->w_mtime = sbuf.st_mtime;
+ ++num_ent;
+ continue;
+ }
+
+ /* open control file */
+ cf = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDONLY,
+ NULL);
+ if (cf == NULL && OpMode != MD_PRINT)
+ {
+ /* this may be some random person sending hir msgs */
+ if (tTd(41, 2))
+ sm_dprintf("gatherq: cannot open %s: %s\n",
+ d->d_name, sm_errstring(errno));
+ errno = 0;
+ wn--;
+ continue;
+ }
+ w->w_qgrp = qgrp;
+ w->w_qdir = qdir;
+ w->w_name = newstr(d->d_name);
+ w->w_host = NULL;
+ if (cf != NULL)
+ {
+ w->w_lock = !lockfile(sm_io_getinfo(cf, SM_IO_WHAT_FD,
+ NULL),
+ w->w_name, NULL,
+ LOCK_SH|LOCK_NB);
+ }
+ w->w_tooyoung = false;
+
+ /* make sure jobs in creation don't clog queue */
+ w->w_pri = 0x7fffffff;
+ w->w_ctime = 0;
+ w->w_mtime = sbuf.st_mtime;
+
+ /* extract useful information */
+ i = NEED_P|NEED_T;
+ if (QueueSortOrder == QSO_BYHOST
+#if _FFR_RHS
+ || QueueSortOrder == QSO_BYSHUFFLE
+#endif /* _FFR_RHS */
+ )
+ {
+ /* need w_host set for host sort order */
+ i |= NEED_H;
+ }
+ if (QueueLimitSender != NULL)
+ i |= NEED_S;
+ if (QueueLimitRecipient != NULL)
+ i |= NEED_R;
+#if _FFR_QUARANTINE
+ if (QueueLimitQuarantine != NULL)
+ i |= NEED_QUARANTINE;
+#endif /* _FFR_QUARANTINE */
+ while (cf != NULL && i != 0 &&
+ sm_io_fgets(cf, SM_TIME_DEFAULT, lbuf,
+ sizeof lbuf) != NULL)
+ {
+ int c;
+ time_t age;
+
+ p = strchr(lbuf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ else
+ {
+ /* flush rest of overly long line */
+ while ((c = sm_io_getc(cf, SM_TIME_DEFAULT))
+ != SM_IO_EOF && c != '\n')
+ continue;
+ }
+
+ switch (lbuf[0])
+ {
+ case 'V':
+ qfver = atoi(&lbuf[1]);
+ break;
+
+ case 'P':
+ w->w_pri = atol(&lbuf[1]);
+ i &= ~NEED_P;
+ break;
+
+ case 'T':
+ w->w_ctime = atol(&lbuf[1]);
+ i &= ~NEED_T;
+ break;
+
+#if _FFR_QUARANTINE
+ case 'q':
+ if (QueueMode != QM_QUARANTINE &&
+ QueueMode != QM_LOST)
+ {
+ if (tTd(41, 49))
+ sm_dprintf("%s not marked as quarantined but has a 'q' line\n",
+ w->w_name);
+ i |= HAS_QUARANTINE;
+ }
+ else if (QueueMode == QM_QUARANTINE)
+ {
+ if (QueueLimitQuarantine == NULL)
+ {
+ i &= ~NEED_QUARANTINE;
+ break;
+ }
+ p = &lbuf[1];
+ check = QueueLimitQuarantine;
+ while (check != NULL)
+ {
+ if (strcontainedin(false,
+ check->queue_match,
+ p) !=
+ check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (check != NULL)
+ i &= ~NEED_QUARANTINE;
+ }
+ break;
+#endif /* _FFR_QUARANTINE */
+
+ case 'R':
+ if (w->w_host == NULL &&
+ (p = strrchr(&lbuf[1], '@')) != NULL)
+ {
+#if _FFR_RHS
+ if (QueueSortOrder == QSO_BYSHUFFLE)
+ w->w_host = newstr(&p[1]);
+ else
+#endif /* _FFR_RHS */
+ w->w_host = strrev(&p[1]);
+ makelower(w->w_host);
+ i &= ~NEED_H;
+ }
+ if (QueueLimitRecipient == NULL)
+ {
+ i &= ~NEED_R;
+ break;
+ }
+ if (qfver > 0)
+ {
+ p = strchr(&lbuf[1], ':');
+ if (p == NULL)
+ p = &lbuf[1];
+ }
+ else
+ p = &lbuf[1];
+ check = QueueLimitRecipient;
+ while (check != NULL)
+ {
+ if (strcontainedin(true,
+ check->queue_match,
+ p) !=
+ check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (check != NULL)
+ i &= ~NEED_R;
+ break;
+
+ case 'S':
+ check = QueueLimitSender;
+ while (check != NULL)
+ {
+ if (strcontainedin(true,
+ check->queue_match,
+ &lbuf[1]) !=
+ check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (check != NULL)
+ i &= ~NEED_S;
+ break;
+
+ case 'K':
+ age = curtime() - (time_t) atol(&lbuf[1]);
+ if (age >= 0 && MinQueueAge > 0 &&
+ age < MinQueueAge)
+ w->w_tooyoung = true;
+ break;
+
+ case 'N':
+ if (atol(&lbuf[1]) == 0)
+ w->w_tooyoung = false;
+ break;
+
+#if _FFR_QUEUEDELAY
+/*
+ case 'G':
+ queuealg = atoi(lbuf[1]);
+ break;
+ case 'Y':
+ queuedelay = (time_t) atol(&lbuf[1]);
+ break;
+*/
+#endif /* _FFR_QUEUEDELAY */
+ }
+ }
+ if (cf != NULL)
+ (void) sm_io_close(cf, SM_TIME_DEFAULT);
+
+ if ((!doall && shouldqueue(w->w_pri, w->w_ctime)) ||
+#if _FFR_QUARANTINE
+ bitset(HAS_QUARANTINE, i) ||
+ bitset(NEED_QUARANTINE, i) ||
+#endif /* _FFR_QUARANTINE */
+ bitset(NEED_R|NEED_S, i))
+ {
+ /* don't even bother sorting this job in */
+ if (tTd(41, 49))
+ sm_dprintf("skipping %s (%x)\n", w->w_name, i);
+ sm_free(w->w_name); /* XXX */
+ if (w->w_host != NULL)
+ sm_free(w->w_host); /* XXX */
+ wn--;
+ }
+ else
+ ++num_ent;
+ }
+ (void) closedir(f);
+ wn++;
+
+ i = wn - WorkListCount;
+ WorkListCount += SM_MIN(num_ent, WorkListSize);
+
+ if (more != NULL)
+ *more = WorkListCount < wn;
+
+ if (full != NULL)
+ *full = (wn >= MaxQueueRun && MaxQueueRun > 0) ||
+ (WorkList == NULL && wn > 0);
+
+ return i;
+}
+/*
+** SORTQ -- sort the work list
+**
+** First the old WorkQ is cleared away. Then the WorkList is sorted
+** for all items so that important (higher sorting value) items are not
+** trunctated off. Then the most important items are moved from
+** WorkList to WorkQ. The lower count of 'max' or MaxListCount items
+** are moved.
+**
+** Parameters:
+** max -- maximum number of items to be placed in WorkQ
+**
+** Returns:
+** the number of items in WorkQ
+**
+** Side Effects:
+** WorkQ gets released and filled with new work. WorkList
+** gets released. Work items get sorted in order.
+*/
+
+static int
+sortq(max)
+ int max;
+{
+ register int i; /* local counter */
+ register WORK *w; /* tmp item pointer */
+ int wc = WorkListCount; /* trim size for WorkQ */
+
+ if (WorkQ != NULL)
+ {
+ /* Clear out old WorkQ. */
+ for (w = WorkQ; w != NULL; )
+ {
+ register WORK *nw = w->w_next;
+
+ WorkQ = nw;
+ sm_free(w->w_name); /* XXX */
+ if (w->w_host != NULL)
+ sm_free(w->w_host); /* XXX */
+ sm_free((char *) w); /* XXX */
+ w = nw;
+ }
+ sm_free((char *) WorkQ);
+ WorkQ = NULL;
+ }
+
+ if (WorkList == NULL || wc <= 0)
+ return 0;
+
+ /* Check if the per queue group item limit will be exceeded */
+ if (wc > max && max > 0)
+ wc = max;
+
+ /*
+ ** The sort now takes place using all of the items in WorkList.
+ ** The list gets trimmed to the most important items after the sort.
+ ** If the trim were to happen before the sort then one or more
+ ** important items might get truncated off -- not what we want.
+ */
+
+ if (QueueSortOrder == QSO_BYHOST)
+ {
+ /*
+ ** Sort the work directory for the first time,
+ ** based on host name, lock status, and priority.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf1);
+
+ /*
+ ** If one message to host is locked, "lock" all messages
+ ** to that host.
+ */
+
+ i = 0;
+ while (i < wc)
+ {
+ if (!WorkList[i].w_lock)
+ {
+ i++;
+ continue;
+ }
+ w = &WorkList[i];
+ while (++i < wc)
+ {
+ if (WorkList[i].w_host == NULL &&
+ w->w_host == NULL)
+ WorkList[i].w_lock = true;
+ else if (WorkList[i].w_host != NULL &&
+ w->w_host != NULL &&
+ sm_strcasecmp(WorkList[i].w_host,
+ w->w_host) == 0)
+ WorkList[i].w_lock = true;
+ else
+ break;
+ }
+ }
+
+ /*
+ ** Sort the work directory for the second time,
+ ** based on lock status, host name, and priority.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf2);
+ }
+ else if (QueueSortOrder == QSO_BYTIME)
+ {
+ /*
+ ** Simple sort based on submission time only.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf3);
+ }
+ else if (QueueSortOrder == QSO_BYFILENAME)
+ {
+ /*
+ ** Sort based on queue filename.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf4);
+ }
+ else if (QueueSortOrder == QSO_RANDOM)
+ {
+ /*
+ ** Sort randomly.
+ ** workcmpf5() returns a random 1 or -1.
+ ** As long as nobody does a verification pass over the
+ ** sorted list, we should be golden.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf5);
+ }
+ else if (QueueSortOrder == QSO_BYMODTIME)
+ {
+ /*
+ ** Simple sort based on modification time of queue file.
+ ** This puts the oldest items first.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf6);
+ }
+#if _FFR_RHS
+ else if (QueueSortOrder == QSO_BYSHUFFLE)
+ {
+ /*
+ ** Simple sort based on shuffled host name.
+ */
+
+ init_shuffle_alphabet();
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf7);
+ }
+#endif /* _FFR_RHS */
+ else
+ {
+ /*
+ ** Simple sort based on queue priority only.
+ */
+
+ qsort((char *) WorkList, wc, sizeof *WorkList, workcmpf0);
+ }
+
+ /*
+ ** Convert the work list into canonical form.
+ ** Should be turning it into a list of envelopes here perhaps.
+ ** Only take the most important items up to the per queue group
+ ** maximum.
+ */
+
+ for (i = wc; --i >= 0; )
+ {
+ w = (WORK *) xalloc(sizeof *w);
+ w->w_qgrp = WorkList[i].w_qgrp;
+ w->w_qdir = WorkList[i].w_qdir;
+ w->w_name = WorkList[i].w_name;
+ w->w_host = WorkList[i].w_host;
+ w->w_lock = WorkList[i].w_lock;
+ w->w_tooyoung = WorkList[i].w_tooyoung;
+ w->w_pri = WorkList[i].w_pri;
+ w->w_ctime = WorkList[i].w_ctime;
+ w->w_mtime = WorkList[i].w_mtime;
+ w->w_next = WorkQ;
+ WorkQ = w;
+ }
+ if (WorkList != NULL)
+ sm_free(WorkList); /* XXX */
+ WorkList = NULL;
+ WorkListSize = 0;
+ WorkListCount = 0;
+
+ if (tTd(40, 1))
+ {
+ for (w = WorkQ; w != NULL; w = w->w_next)
+ {
+ if (w->w_host != NULL)
+ sm_dprintf("%22s: pri=%ld %s\n",
+ w->w_name, w->w_pri, w->w_host);
+ else
+ sm_dprintf("%32s: pri=%ld\n",
+ w->w_name, w->w_pri);
+ }
+ }
+
+ return wc; /* return number of WorkQ items */
+}
+/*
+** GROW_WLIST -- make the work list larger
+**
+** Parameters:
+** qgrp -- the index for the queue group.
+** qdir -- the index for the queue directory.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Adds another QUEUESEGSIZE entries to WorkList if possible.
+** It can fail if there isn't enough memory, so WorkListSize
+** should be checked again upon return.
+*/
+
+static void
+grow_wlist(qgrp, qdir)
+ int qgrp;
+ int qdir;
+{
+ if (tTd(41, 1))
+ sm_dprintf("grow_wlist: WorkListSize=%d\n", WorkListSize);
+ if (WorkList == NULL)
+ {
+ WorkList = (WORK *) xalloc((sizeof *WorkList) *
+ (QUEUESEGSIZE + 1));
+ WorkListSize = QUEUESEGSIZE;
+ }
+ else
+ {
+ int newsize = WorkListSize + QUEUESEGSIZE;
+ WORK *newlist = (WORK *) sm_realloc((char *) WorkList,
+ (unsigned) sizeof(WORK) * (newsize + 1));
+
+ if (newlist != NULL)
+ {
+ WorkListSize = newsize;
+ WorkList = newlist;
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_INFO, NOQID,
+ "grew WorkList for %s to %d",
+ qid_printqueue(qgrp, qdir),
+ WorkListSize);
+ }
+ }
+ else if (LogLevel > 0)
+ {
+ sm_syslog(LOG_ALERT, NOQID,
+ "FAILED to grow WorkList for %s to %d",
+ qid_printqueue(qgrp, qdir), newsize);
+ }
+ }
+ if (tTd(41, 1))
+ sm_dprintf("grow_wlist: WorkListSize now %d\n", WorkListSize);
+}
+/*
+** WORKCMPF0 -- simple priority-only compare function.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf0(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ long pa = a->w_pri;
+ long pb = b->w_pri;
+
+ if (pa == pb)
+ return 0;
+ else if (pa > pb)
+ return 1;
+ else
+ return -1;
+}
+/*
+** WORKCMPF1 -- first compare function for ordering work based on host name.
+**
+** Sorts on host name, lock status, and priority in that order.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** <0 if a < b
+** 0 if a == b
+** >0 if a > b
+**
+*/
+
+static int
+workcmpf1(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ int i;
+
+ /* host name */
+ if (a->w_host != NULL && b->w_host == NULL)
+ return 1;
+ else if (a->w_host == NULL && b->w_host != NULL)
+ return -1;
+ if (a->w_host != NULL && b->w_host != NULL &&
+ (i = sm_strcasecmp(a->w_host, b->w_host)) != 0)
+ return i;
+
+ /* lock status */
+ if (a->w_lock != b->w_lock)
+ return b->w_lock - a->w_lock;
+
+ /* job priority */
+ return workcmpf0(a, b);
+}
+/*
+** WORKCMPF2 -- second compare function for ordering work based on host name.
+**
+** Sorts on lock status, host name, and priority in that order.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** <0 if a < b
+** 0 if a == b
+** >0 if a > b
+**
+*/
+
+static int
+workcmpf2(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ int i;
+
+ /* lock status */
+ if (a->w_lock != b->w_lock)
+ return a->w_lock - b->w_lock;
+
+ /* host name */
+ if (a->w_host != NULL && b->w_host == NULL)
+ return 1;
+ else if (a->w_host == NULL && b->w_host != NULL)
+ return -1;
+ if (a->w_host != NULL && b->w_host != NULL &&
+ (i = sm_strcasecmp(a->w_host, b->w_host)) != 0)
+ return i;
+
+ /* job priority */
+ return workcmpf0(a, b);
+}
+/*
+** WORKCMPF3 -- simple submission-time-only compare function.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf3(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ if (a->w_ctime > b->w_ctime)
+ return 1;
+ else if (a->w_ctime < b->w_ctime)
+ return -1;
+ else
+ return 0;
+}
+/*
+** WORKCMPF4 -- compare based on file name
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf4(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ return strcmp(a->w_name, b->w_name);
+}
+/*
+** WORKCMPF5 -- compare based on assigned random number
+**
+** Parameters:
+** a -- the first argument (ignored).
+** b -- the second argument (ignored).
+**
+** Returns:
+** randomly 1/-1
+*/
+
+/* ARGSUSED0 */
+static int
+workcmpf5(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ return (get_rand_mod(2)) ? 1 : -1;
+}
+/*
+** WORKCMPF6 -- simple modification-time-only compare function.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf6(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ if (a->w_mtime > b->w_mtime)
+ return 1;
+ else if (a->w_mtime < b->w_mtime)
+ return -1;
+ else
+ return 0;
+}
+#if _FFR_RHS
+/*
+** WORKCMPF7 -- compare function for ordering work based on shuffled host name.
+**
+** Sorts on lock status, host name, and priority in that order.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** <0 if a < b
+** 0 if a == b
+** >0 if a > b
+**
+*/
+
+static int
+workcmpf7(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ int i;
+
+ /* lock status */
+ if (a->w_lock != b->w_lock)
+ return a->w_lock - b->w_lock;
+
+ /* host name */
+ if (a->w_host != NULL && b->w_host == NULL)
+ return 1;
+ else if (a->w_host == NULL && b->w_host != NULL)
+ return -1;
+ if (a->w_host != NULL && b->w_host != NULL &&
+ (i = sm_strshufflecmp(a->w_host, b->w_host)) != 0)
+ return i;
+
+ /* job priority */
+ return workcmpf0(a, b);
+}
+#endif /* _FFR_RHS */
+/*
+** STRREV -- reverse string
+**
+** Returns a pointer to a new string that is the reverse of
+** the string pointed to by fwd. The space for the new
+** string is obtained using xalloc().
+**
+** Parameters:
+** fwd -- the string to reverse.
+**
+** Returns:
+** the reversed string.
+*/
+
+static char *
+strrev(fwd)
+ char *fwd;
+{
+ char *rev = NULL;
+ int len, cnt;
+
+ len = strlen(fwd);
+ rev = xalloc(len + 1);
+ for (cnt = 0; cnt < len; ++cnt)
+ rev[cnt] = fwd[len - cnt - 1];
+ rev[len] = '\0';
+ return rev;
+}
+
+#if _FFR_RHS
+
+#define NASCII 128
+#define NCHAR 256
+
+static unsigned char ShuffledAlphabet[NCHAR];
+
+void
+init_shuffle_alphabet()
+{
+ static bool init = false;
+ int i;
+
+ if (init)
+ return;
+
+ /* fill the ShuffledAlphabet */
+ for (i = 0; i < NCHAR; i++)
+ ShuffledAlphabet[i] = i;
+
+ /* mix it */
+ for (i = 1; i < NCHAR; i++)
+ {
+ register int j = get_random() % NCHAR;
+ register int tmp;
+
+ tmp = ShuffledAlphabet[j];
+ ShuffledAlphabet[j] = ShuffledAlphabet[i];
+ ShuffledAlphabet[i] = tmp;
+ }
+
+ /* make it case insensitive */
+ for (i = 'A'; i <= 'Z'; i++)
+ ShuffledAlphabet[i] = ShuffledAlphabet[i + 'a' - 'A'];
+
+ /* fill the upper part */
+ for (i = 0; i < NCHAR; i++)
+ ShuffledAlphabet[i + NCHAR] = ShuffledAlphabet[i];
+ init = true;
+}
+
+static int
+sm_strshufflecmp(a, b)
+ char *a;
+ char *b;
+{
+ const unsigned char *us1 = (const unsigned char *) a;
+ const unsigned char *us2 = (const unsigned char *) b;
+
+ while (ShuffledAlphabet[*us1] == ShuffledAlphabet[*us2++])
+ {
+ if (*us1++ == '\0')
+ return 0;
+ }
+ return (ShuffledAlphabet[*us1] - ShuffledAlphabet[*--us2]);
+}
+#endif /* _FFR_RHS */
+
+/*
+** DOWORK -- do a work request.
+**
+** Parameters:
+** qgrp -- the index of the queue group for the job.
+** qdir -- the index of the queue directory for the job.
+** id -- the ID of the job to run.
+** forkflag -- if set, run this in background.
+** requeueflag -- if set, reinstantiate the queue quickly.
+** This is used when expanding aliases in the queue.
+** If forkflag is also set, it doesn't wait for the
+** child.
+** e - the envelope in which to run it.
+**
+** Returns:
+** process id of process that is running the queue job.
+**
+** Side Effects:
+** The work request is satisfied if possible.
+*/
+
+pid_t
+dowork(qgrp, qdir, id, forkflag, requeueflag, e)
+ int qgrp;
+ int qdir;
+ char *id;
+ bool forkflag;
+ bool requeueflag;
+ register ENVELOPE *e;
+{
+ register pid_t pid;
+ SM_RPOOL_T *rpool;
+
+ if (tTd(40, 1))
+ sm_dprintf("dowork(%s/%s)\n", qid_printqueue(qgrp, qdir), id);
+
+ /*
+ ** Fork for work.
+ */
+
+ if (forkflag)
+ {
+ /*
+ ** Since the delivery may happen in a child and the
+ ** parent does not wait, the parent may close the
+ ** maps thereby removing any shared memory used by
+ ** the map. Therefore, close the maps now so the
+ ** child will dynamically open them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("dowork: cannot fork");
+ return 0;
+ }
+ else if (pid > 0)
+ {
+ /* parent -- clean out connection cache */
+ mci_flush(false, NULL);
+ }
+ else
+ {
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** See note above about SMTP processes and SIGCHLD.
+ */
+
+ if (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ MaxQueueChildren > 0)
+ {
+ proc_list_clear();
+ sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ }
+
+ /* child -- error messages to the transcript */
+ QuickAbort = OnlyOneError = false;
+ }
+ }
+ else
+ {
+ pid = 0;
+ }
+
+ if (pid == 0)
+ {
+ /*
+ ** CHILD
+ ** Lock the control file to avoid duplicate deliveries.
+ ** Then run the file as though we had just read it.
+ ** We save an idea of the temporary name so we
+ ** can recover on interrupt.
+ */
+
+ if (forkflag)
+ {
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ }
+
+ /* set basic modes, etc. */
+ sm_clear_events();
+ clearstats();
+ rpool = sm_rpool_new_x(NULL);
+ clearenvelope(e, false, rpool);
+ e->e_flags |= EF_QUEUERUN|EF_GLOBALERRS;
+ set_delivery_mode(SM_DELIVER, e);
+ e->e_errormode = EM_MAIL;
+ e->e_id = id;
+ e->e_qgrp = qgrp;
+ e->e_qdir = qdir;
+ GrabTo = UseErrorsTo = false;
+ ExitStat = EX_OK;
+ if (forkflag)
+ {
+ disconnect(1, e);
+ set_op_mode(MD_QUEUERUN);
+ }
+ sm_setproctitle(true, e, "%s from queue", qid_printname(e));
+ if (LogLevel > 76)
+ sm_syslog(LOG_DEBUG, e->e_id, "dowork, pid=%d",
+ (int) CurrentPid);
+
+ /* don't use the headers from sendmail.cf... */
+ e->e_header = NULL;
+
+ /* read the queue control file -- return if locked */
+ if (!readqf(e, false))
+ {
+ if (tTd(40, 4) && e->e_id != NULL)
+ sm_dprintf("readqf(%s) failed\n",
+ qid_printname(e));
+ e->e_id = NULL;
+ if (forkflag)
+ finis(false, true, EX_OK);
+ else
+ {
+ /* adding this frees 8 bytes */
+ clearenvelope(e, false, rpool);
+
+ /* adding this frees 12 bytes */
+ sm_rpool_free(rpool);
+ e->e_rpool = NULL;
+ return 0;
+ }
+ }
+
+ e->e_flags |= EF_INQUEUE;
+ eatheader(e, requeueflag, true);
+
+ if (requeueflag)
+ queueup(e, false, false);
+
+ /* do the delivery */
+ sendall(e, SM_DELIVER);
+
+ /* finish up and exit */
+ if (forkflag)
+ finis(true, true, ExitStat);
+ else
+ {
+ dropenvelope(e, true, false);
+ sm_rpool_free(rpool);
+ e->e_rpool = NULL;
+ }
+ }
+ e->e_id = NULL;
+ return pid;
+}
+
+/*
+** DOWORKLIST -- process a list of envelopes as work requests
+**
+** Similar to dowork(), except that after forking, it processes an
+** envelope and its siblings, treating each envelope as a work request.
+**
+** Parameters:
+** el -- envelope to be processed including its siblings.
+** forkflag -- if set, run this in background.
+** requeueflag -- if set, reinstantiate the queue quickly.
+** This is used when expanding aliases in the queue.
+** If forkflag is also set, it doesn't wait for the
+** child.
+**
+** Returns:
+** process id of process that is running the queue job.
+**
+** Side Effects:
+** The work request is satisfied if possible.
+*/
+
+pid_t
+doworklist(el, forkflag, requeueflag)
+ ENVELOPE *el;
+ bool forkflag;
+ bool requeueflag;
+{
+ register pid_t pid;
+ ENVELOPE *ei;
+
+ if (tTd(40, 1))
+ sm_dprintf("doworklist()\n");
+
+ /*
+ ** Fork for work.
+ */
+
+ if (forkflag)
+ {
+ /*
+ ** Since the delivery may happen in a child and the
+ ** parent does not wait, the parent may close the
+ ** maps thereby removing any shared memory used by
+ ** the map. Therefore, close the maps now so the
+ ** child will dynamically open them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("doworklist: cannot fork");
+ return 0;
+ }
+ else if (pid > 0)
+ {
+ /* parent -- clean out connection cache */
+ mci_flush(false, NULL);
+ }
+ else
+ {
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** See note above about SMTP processes and SIGCHLD.
+ */
+
+ if (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ MaxQueueChildren > 0)
+ {
+ proc_list_clear();
+ sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ }
+
+ /* child -- error messages to the transcript */
+ QuickAbort = OnlyOneError = false;
+ }
+ }
+ else
+ {
+ pid = 0;
+ }
+
+ if (pid != 0)
+ return pid;
+
+ /*
+ ** IN CHILD
+ ** Lock the control file to avoid duplicate deliveries.
+ ** Then run the file as though we had just read it.
+ ** We save an idea of the temporary name so we
+ ** can recover on interrupt.
+ */
+
+ if (forkflag)
+ {
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ }
+
+ /* set basic modes, etc. */
+ sm_clear_events();
+ clearstats();
+ GrabTo = UseErrorsTo = false;
+ ExitStat = EX_OK;
+ if (forkflag)
+ {
+ disconnect(1, el);
+ set_op_mode(MD_QUEUERUN);
+ }
+ if (LogLevel > 76)
+ sm_syslog(LOG_DEBUG, el->e_id, "doworklist, pid=%d",
+ (int) CurrentPid);
+
+ for (ei = el; ei != NULL; ei = ei->e_sibling)
+ {
+ ENVELOPE e;
+ SM_RPOOL_T *rpool;
+
+ if (WILL_BE_QUEUED(ei->e_sendmode))
+ continue;
+#if _FFR_QUARANTINE
+ else if (QueueMode != QM_QUARANTINE &&
+ ei->e_quarmsg != NULL)
+ continue;
+#endif /* _FFR_QUARANTINE */
+
+ rpool = sm_rpool_new_x(NULL);
+ clearenvelope(&e, true, rpool);
+ e.e_flags |= EF_QUEUERUN|EF_GLOBALERRS;
+ set_delivery_mode(SM_DELIVER, &e);
+ e.e_errormode = EM_MAIL;
+ e.e_id = ei->e_id;
+ e.e_qgrp = ei->e_qgrp;
+ e.e_qdir = ei->e_qdir;
+ openxscript(&e);
+ sm_setproctitle(true, &e, "%s from queue", qid_printname(&e));
+
+ /* don't use the headers from sendmail.cf... */
+ e.e_header = NULL;
+ CurEnv = &e;
+
+ /* read the queue control file -- return if locked */
+ if (readqf(&e, false))
+ {
+ e.e_flags |= EF_INQUEUE;
+ eatheader(&e, requeueflag, true);
+
+ if (requeueflag)
+ queueup(&e, false, false);
+
+ /* do the delivery */
+ sendall(&e, SM_DELIVER);
+ dropenvelope(&e, true, false);
+ }
+ else
+ {
+ if (tTd(40, 4) && e.e_id != NULL)
+ sm_dprintf("readqf(%s) failed\n",
+ qid_printname(&e));
+ }
+ sm_rpool_free(rpool);
+ ei->e_id = NULL;
+ }
+
+ /* restore CurEnv */
+ CurEnv = el;
+
+ /* finish up and exit */
+ if (forkflag)
+ finis(true, true, ExitStat);
+ return 0;
+}
+/*
+** READQF -- read queue file and set up environment.
+**
+** Parameters:
+** e -- the envelope of the job to run.
+** openonly -- only open the qf (returned as e_lockfp)
+**
+** Returns:
+** true if it successfully read the queue file.
+** false otherwise.
+**
+** Side Effects:
+** The queue file is returned locked.
+*/
+
+static bool
+readqf(e, openonly)
+ register ENVELOPE *e;
+ bool openonly;
+{
+ register SM_FILE_T *qfp;
+ ADDRESS *ctladdr;
+ struct stat st, stf;
+ char *bp;
+ int qfver = 0;
+ long hdrsize = 0;
+ register char *p;
+ char *frcpt = NULL;
+ char *orcpt = NULL;
+ bool nomore = false;
+ bool bogus = false;
+ MODE_T qsafe;
+ char *err;
+ char qf[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ /*
+ ** Read and process the file.
+ */
+
+ (void) sm_strlcpy(qf, queuename(e, ANYQFL_LETTER), sizeof qf);
+ qfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDWR, NULL);
+ if (qfp == NULL)
+ {
+ int save_errno = errno;
+
+ if (tTd(40, 8))
+ sm_dprintf("readqf(%s): sm_io_open failure (%s)\n",
+ qf, sm_errstring(errno));
+ errno = save_errno;
+ if (errno != ENOENT
+ )
+ syserr("readqf: no control file %s", qf);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ if (!lockfile(sm_io_getinfo(qfp, SM_IO_WHAT_FD, NULL), qf, NULL,
+ LOCK_EX|LOCK_NB))
+ {
+ /* being processed by another queuer */
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s: locked\n", e->e_id);
+ if (tTd(40, 8))
+ sm_dprintf("%s: locked\n", e->e_id);
+ if (LogLevel > 19)
+ sm_syslog(LOG_DEBUG, e->e_id, "locked");
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ /*
+ ** Prevent locking race condition.
+ **
+ ** Process A: readqf(): qfp = fopen(qffile)
+ ** Process B: queueup(): rename(tf, qf)
+ ** Process B: unlocks(tf)
+ ** Process A: lockfile(qf);
+ **
+ ** Process A (us) has the old qf file (before the rename deleted
+ ** the directory entry) and will be delivering based on old data.
+ ** This can lead to multiple deliveries of the same recipients.
+ **
+ ** Catch this by checking if the underlying qf file has changed
+ ** *after* acquiring our lock and if so, act as though the file
+ ** was still locked (i.e., just return like the lockfile() case
+ ** above.
+ */
+
+ if (stat(qf, &stf) < 0 ||
+ fstat(sm_io_getinfo(qfp, SM_IO_WHAT_FD, NULL), &st) < 0)
+ {
+ /* must have been being processed by someone else */
+ if (tTd(40, 8))
+ sm_dprintf("readqf(%s): [f]stat failure (%s)\n",
+ qf, sm_errstring(errno));
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ if (st.st_nlink != stf.st_nlink ||
+ st.st_dev != stf.st_dev ||
+ ST_INODE(st) != ST_INODE(stf) ||
+#if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */
+ st.st_gen != stf.st_gen ||
+#endif /* HAS_ST_GEN && 0 */
+ st.st_uid != stf.st_uid ||
+ st.st_gid != stf.st_gid ||
+ st.st_size != stf.st_size)
+ {
+ /* changed after opened */
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s: changed\n", e->e_id);
+ if (tTd(40, 8))
+ sm_dprintf("%s: changed\n", e->e_id);
+ if (LogLevel > 19)
+ sm_syslog(LOG_DEBUG, e->e_id, "changed");
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ /*
+ ** Check the queue file for plausibility to avoid attacks.
+ */
+
+ qsafe = S_IWOTH|S_IWGRP;
+ if (bitset(S_IWGRP, QueueFileMode))
+ qsafe &= ~S_IWGRP;
+
+ bogus = st.st_uid != geteuid() &&
+ st.st_uid != TrustedUid &&
+ geteuid() != RealUid;
+
+ /*
+ ** If this qf file results from a set-group-ID binary, then
+ ** we check whether the directory is group-writable,
+ ** the queue file mode contains the group-writable bit, and
+ ** the groups are the same.
+ ** Notice: this requires that the set-group-ID binary is used to
+ ** run the queue!
+ */
+
+ if (bogus && st.st_gid == getegid() && UseMSP)
+ {
+ char delim;
+ struct stat dst;
+
+ bp = SM_LAST_DIR_DELIM(qf);
+ if (bp == NULL)
+ delim = '\0';
+ else
+ {
+ delim = *bp;
+ *bp = '\0';
+ }
+ if (stat(delim == '\0' ? "." : qf, &dst) < 0)
+ syserr("readqf: cannot stat directory %s",
+ delim == '\0' ? "." : qf);
+ else
+ {
+ bogus = !(bitset(S_IWGRP, QueueFileMode) &&
+ bitset(S_IWGRP, dst.st_mode) &&
+ dst.st_gid == st.st_gid);
+ }
+ if (delim != '\0')
+ *bp = delim;
+ }
+ if (!bogus)
+ bogus = bitset(qsafe, st.st_mode);
+ if (bogus)
+ {
+ if (LogLevel > 0)
+ {
+ sm_syslog(LOG_ALERT, e->e_id,
+ "bogus queue file, uid=%d, gid=%d, mode=%o",
+ st.st_uid, st.st_gid, st.st_mode);
+ }
+ if (tTd(40, 8))
+ sm_dprintf("readqf(%s): bogus file\n", qf);
+ e->e_flags |= EF_INQUEUE;
+ if (!openonly)
+ loseqfile(e, "bogus file uid/gid in mqueue");
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ if (st.st_size == 0)
+ {
+ /* must be a bogus file -- if also old, just remove it */
+ if (!openonly && st.st_ctime + 10 * 60 < curtime())
+ {
+ (void) xunlink(queuename(e, DATAFL_LETTER));
+ (void) xunlink(queuename(e, ANYQFL_LETTER));
+ }
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ if (st.st_nlink == 0)
+ {
+ /*
+ ** Race condition -- we got a file just as it was being
+ ** unlinked. Just assume it is zero length.
+ */
+
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+#if _FFR_TRUSTED_QF
+ /*
+ ** If we don't own the file mark it as unsafe.
+ ** However, allow TrustedUser to own it as well
+ ** in case TrustedUser manipulates the queue.
+ */
+
+ if (st.st_uid != geteuid() && st.st_uid != TrustedUid)
+ e->e_flags |= EF_UNSAFE;
+#else /* _FFR_TRUSTED_QF */
+ /* If we don't own the file mark it as unsafe */
+ if (st.st_uid != geteuid())
+ e->e_flags |= EF_UNSAFE;
+#endif /* _FFR_TRUSTED_QF */
+
+ /* good file -- save this lock */
+ e->e_lockfp = qfp;
+
+ /* Just wanted the open file */
+ if (openonly)
+ return true;
+
+ /* do basic system initialization */
+ initsys(e);
+ macdefine(&e->e_macro, A_PERM, 'i', e->e_id);
+
+ LineNumber = 0;
+ e->e_flags |= EF_GLOBALERRS;
+ set_op_mode(MD_QUEUERUN);
+ ctladdr = NULL;
+#if _FFR_QUARANTINE
+ e->e_qfletter = queue_letter(e, ANYQFL_LETTER);
+#endif /* _FFR_QUARANTINE */
+ e->e_dfqgrp = e->e_qgrp;
+ e->e_dfqdir = e->e_qdir;
+#if _FFR_QUEUE_MACRO
+ macdefine(&e->e_macro, A_TEMP, macid("{queue}"),
+ qid_printqueue(e->e_qgrp, e->e_qdir));
+#endif /* _FFR_QUEUE_MACRO */
+ e->e_dfino = -1;
+ e->e_msgsize = -1;
+#if _FFR_QUEUEDELAY
+ e->e_queuealg = QD_LINEAR;
+ e->e_queuedelay = (time_t) 0;
+#endif /* _FFR_QUEUEDELAY */
+ while ((bp = fgetfolded(buf, sizeof buf, qfp)) != NULL)
+ {
+ unsigned long qflags;
+ ADDRESS *q;
+ int r;
+ time_t now;
+ auto char *ep;
+
+ if (tTd(40, 4))
+ sm_dprintf("+++++ %s\n", bp);
+ if (nomore)
+ {
+ /* hack attack */
+ hackattack:
+ syserr("SECURITY ALERT: extra or bogus data in queue file: %s",
+ bp);
+ err = "bogus queue line";
+ goto fail;
+ }
+ switch (bp[0])
+ {
+ case 'A': /* AUTH= parameter */
+ if (!xtextok(&bp[1]))
+ goto hackattack;
+ e->e_auth_param = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'B': /* body type */
+ r = check_bodytype(&bp[1]);
+ if (!BODYTYPE_VALID(r))
+ goto hackattack;
+ e->e_bodytype = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'C': /* specify controlling user */
+ ctladdr = setctluser(&bp[1], qfver, e);
+ break;
+
+ case 'D': /* data file name */
+ /* obsolete -- ignore */
+ break;
+
+ case 'd': /* data file directory name */
+ {
+ int qgrp, qdir;
+
+#if _FFR_MSP_PARANOIA
+ /* forbid queue groups in MSP? */
+ if (UseMSP)
+ goto hackattack;
+#endif /* _FFR_MSP_PARANOIA */
+ for (qgrp = 0;
+ qgrp < NumQueue && Queue[qgrp] != NULL;
+ ++qgrp)
+ {
+ for (qdir = 0;
+ qdir < Queue[qgrp]->qg_numqueues;
+ ++qdir)
+ {
+ if (strcmp(&bp[1],
+ Queue[qgrp]->qg_qpaths[qdir].qp_name)
+ == 0)
+ {
+ e->e_dfqgrp = qgrp;
+ e->e_dfqdir = qdir;
+ goto done;
+ }
+ }
+ }
+ err = "bogus queue file directory";
+ goto fail;
+ done:
+ break;
+ }
+
+ case 'E': /* specify error recipient */
+ /* no longer used */
+ break;
+
+ case 'F': /* flag bits */
+ if (strncmp(bp, "From ", 5) == 0)
+ {
+ /* we are being spoofed! */
+ syserr("SECURITY ALERT: bogus qf line %s", bp);
+ err = "bogus queue line";
+ goto fail;
+ }
+ for (p = &bp[1]; *p != '\0'; p++)
+ {
+ switch (*p)
+ {
+ case '8': /* has 8 bit data */
+ e->e_flags |= EF_HAS8BIT;
+ break;
+
+ case 'b': /* delete Bcc: header */
+ e->e_flags |= EF_DELETE_BCC;
+ break;
+
+ case 'd': /* envelope has DSN RET= */
+ e->e_flags |= EF_RET_PARAM;
+ break;
+
+ case 'n': /* don't return body */
+ e->e_flags |= EF_NO_BODY_RETN;
+ break;
+
+ case 'r': /* response */
+ e->e_flags |= EF_RESPONSE;
+ break;
+
+ case 's': /* split */
+ e->e_flags |= EF_SPLIT;
+ break;
+
+ case 'w': /* warning sent */
+ e->e_flags |= EF_WARNING;
+ break;
+ }
+ }
+ break;
+
+#if _FFR_QUEUEDELAY
+ case 'G': /* queue delay algorithm */
+ e->e_queuealg = atoi(&buf[1]);
+ break;
+#endif /* _FFR_QUEUEDELAY */
+
+#if _FFR_QUARANTINE
+ case 'q': /* quarantine reason */
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ break;
+#endif /* _FFR_QUARANTINE */
+
+ case 'H': /* header */
+
+ /*
+ ** count size before chompheader() destroys the line.
+ ** this isn't accurate due to macro expansion, but
+ ** better than before. "+3" to skip H?? at least.
+ */
+
+ hdrsize += strlen(bp + 3);
+ (void) chompheader(&bp[1], CHHDR_QUEUE, NULL, e);
+ break;
+
+ case 'I': /* data file's inode number */
+ /* regenerated below */
+ break;
+
+ case 'K': /* time of last delivery attempt */
+ e->e_dtime = atol(&buf[1]);
+ break;
+
+ case 'L': /* Solaris Content-Length: */
+ case 'M': /* message */
+ /* ignore this; we want a new message next time */
+ break;
+
+ case 'N': /* number of delivery attempts */
+ e->e_ntries = atoi(&buf[1]);
+
+ /* if this has been tried recently, let it be */
+ now = curtime();
+ if (e->e_ntries > 0 && e->e_dtime <= now &&
+ now < e->e_dtime + queuedelay(e))
+ {
+ char *howlong;
+
+ howlong = pintvl(now - e->e_dtime, true);
+ if (Verbose)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: too young (%s)\n",
+ e->e_id, howlong);
+ if (tTd(40, 8))
+ sm_dprintf("%s: too young (%s)\n",
+ e->e_id, howlong);
+ if (LogLevel > 19)
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "too young (%s)",
+ howlong);
+ e->e_id = NULL;
+ unlockqueue(e);
+ RELEASE_QUEUE;
+ return false;
+ }
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{ntries}"), &buf[1]);
+
+#if NAMED_BIND
+ /* adjust BIND parameters immediately */
+ if (e->e_ntries == 0)
+ {
+ _res.retry = TimeOuts.res_retry[RES_TO_FIRST];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
+ }
+ else
+ {
+ _res.retry = TimeOuts.res_retry[RES_TO_NORMAL];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_NORMAL];
+ }
+#endif /* NAMED_BIND */
+ break;
+
+ case 'P': /* message priority */
+ e->e_msgpriority = atol(&bp[1]) + WkTimeFact;
+ break;
+
+ case 'Q': /* original recipient */
+ orcpt = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'r': /* final recipient */
+ frcpt = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'R': /* specify recipient */
+ p = bp;
+ qflags = 0;
+ if (qfver >= 1)
+ {
+ /* get flag bits */
+ while (*++p != '\0' && *p != ':')
+ {
+ switch (*p)
+ {
+ case 'N':
+ qflags |= QHASNOTIFY;
+ break;
+
+ case 'S':
+ qflags |= QPINGONSUCCESS;
+ break;
+
+ case 'F':
+ qflags |= QPINGONFAILURE;
+ break;
+
+ case 'D':
+ qflags |= QPINGONDELAY;
+ break;
+
+ case 'P':
+ qflags |= QPRIMARY;
+ break;
+
+ case 'A':
+ if (ctladdr != NULL)
+ ctladdr->q_flags |= QALIAS;
+ break;
+
+ default: /* ignore or complain? */
+ break;
+ }
+ }
+ }
+ else
+ qflags |= QPRIMARY;
+ q = parseaddr(++p, NULLADDR, RF_COPYALL, '\0', NULL, e,
+ true);
+ if (q != NULL)
+ {
+ /* make sure we keep the current qgrp */
+ if (ISVALIDQGRP(e->e_qgrp))
+ q->q_qgrp = e->e_qgrp;
+ q->q_alias = ctladdr;
+ if (qfver >= 1)
+ q->q_flags &= ~Q_PINGFLAGS;
+ q->q_flags |= qflags;
+ q->q_finalrcpt = frcpt;
+ q->q_orcpt = orcpt;
+ (void) recipient(q, &e->e_sendqueue, 0, e);
+ }
+ frcpt = NULL;
+ orcpt = NULL;
+ break;
+
+ case 'S': /* sender */
+ setsender(sm_rpool_strdup_x(e->e_rpool, &bp[1]),
+ e, NULL, '\0', true);
+ break;
+
+ case 'T': /* init time */
+ e->e_ctime = atol(&bp[1]);
+ break;
+
+ case 'V': /* queue file version number */
+ qfver = atoi(&bp[1]);
+ if (queuedelay_qfver_unsupported(qfver))
+ syserr("queue file version %d not supported: %s",
+ qfver,
+ "sendmail not compiled with _FFR_QUEUEDELAY");
+ if (qfver <= QF_VERSION)
+ break;
+ syserr("Version number in queue file (%d) greater than max (%d)",
+ qfver, QF_VERSION);
+ err = "unsupported queue file version";
+ goto fail;
+ /* NOTREACHED */
+ break;
+
+#if _FFR_QUEUEDELAY
+ case 'Y': /* current delay */
+ e->e_queuedelay = (time_t) atol(&buf[1]);
+ break;
+#endif /* _FFR_QUEUEDELAY */
+
+ case 'Z': /* original envelope id from ESMTP */
+ e->e_envid = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_envid}"), e->e_envid);
+ break;
+
+ case '!': /* deliver by */
+
+ /* format: flag (1 char) space long-integer */
+ e->e_dlvr_flag = buf[1];
+ e->e_deliver_by = strtol(&buf[3], NULL, 10);
+
+ case '$': /* define macro */
+ {
+ char *p;
+
+ /* XXX elimate p? */
+ r = macid_parse(&bp[1], &ep);
+ if (r == 0)
+ break;
+ p = sm_rpool_strdup_x(e->e_rpool, ep);
+ macdefine(&e->e_macro, A_PERM, r, p);
+ }
+ break;
+
+ case '.': /* terminate file */
+ nomore = true;
+ break;
+
+ default:
+ syserr("readqf: %s: line %d: bad line \"%s\"",
+ qf, LineNumber, shortenstring(bp, MAXSHORTSTR));
+ err = "unrecognized line";
+ goto fail;
+ }
+
+ if (bp != buf)
+ sm_free(bp); /* XXX */
+ }
+
+ /*
+ ** If we haven't read any lines, this queue file is empty.
+ ** Arrange to remove it without referencing any null pointers.
+ */
+
+ if (LineNumber == 0)
+ {
+ errno = 0;
+ e->e_flags |= EF_CLRQUEUE|EF_FATALERRS|EF_RESPONSE;
+ RELEASE_QUEUE;
+ return true;
+ }
+
+ /* Check to make sure we have a complete queue file read */
+ if (!nomore)
+ {
+ syserr("readqf: %s: incomplete queue file read", qf);
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ /* possibly set ${dsn_ret} macro */
+ if (bitset(EF_RET_PARAM, e->e_flags))
+ {
+ if (bitset(EF_NO_BODY_RETN, e->e_flags))
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_ret}"), "hdrs");
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_ret}"), "full");
+ }
+
+ /*
+ ** Arrange to read the data file.
+ */
+
+ p = queuename(e, DATAFL_LETTER);
+ e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, p, SM_IO_RDONLY,
+ NULL);
+ if (e->e_dfp == NULL)
+ {
+ syserr("readqf: cannot open %s", p);
+ }
+ else
+ {
+ e->e_flags |= EF_HAS_DF;
+ if (fstat(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD, NULL), &st)
+ >= 0)
+ {
+ e->e_msgsize = st.st_size + hdrsize;
+ e->e_dfdev = st.st_dev;
+ e->e_dfino = ST_INODE(st);
+ (void) sm_snprintf(buf, sizeof buf, "%ld",
+ e->e_msgsize);
+ macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"),
+ buf);
+ }
+ }
+
+ RELEASE_QUEUE;
+ return true;
+
+ fail:
+ /*
+ ** There was some error reading the qf file (reason is in err var.)
+ ** Cleanup:
+ ** close file; clear e_lockfp since it is the same as qfp,
+ ** hence it is invalid (as file) after qfp is closed;
+ ** the qf file is on disk, so set the flag to avoid calling
+ ** queueup() with bogus data.
+ */
+
+ if (qfp != NULL)
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ e->e_lockfp = NULL;
+ e->e_flags |= EF_INQUEUE;
+ loseqfile(e, err);
+ RELEASE_QUEUE;
+ return false;
+}
+/*
+** PRTSTR -- print a string, "unprintable" characters are shown as \oct
+**
+** Parameters:
+** s -- string to print
+** ml -- maximum length of output
+**
+** Returns:
+** number of entries
+**
+** Side Effects:
+** Prints a string on stdout.
+*/
+
+static void
+prtstr(s, ml)
+ char *s;
+ int ml;
+{
+ int c;
+
+ if (s == NULL)
+ return;
+ while (ml-- > 0 && ((c = *s++) != '\0'))
+ {
+ if (c == '\\')
+ {
+ if (ml-- > 0)
+ {
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ }
+ }
+ else if (isascii(c) && isprint(c))
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ else
+ {
+ if ((ml -= 3) > 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\\%03o", c & 0xFF);
+ }
+ }
+}
+/*
+** PRINTNQE -- print out number of entries in the mail queue
+**
+** Parameters:
+** out -- output file pointer.
+** prefix -- string to output in front of each line.
+**
+** Returns:
+** none.
+*/
+
+void
+printnqe(out, prefix)
+ SM_FILE_T *out;
+ char *prefix;
+{
+#if SM_CONF_SHM
+ int i, k = 0, nrequests = 0;
+ bool unknown = false;
+
+ if (ShmId == SM_SHM_NO_ID)
+ {
+ if (prefix == NULL)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "Data unavailable: shared memory not updated\n");
+ else
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%sNOTCONFIGURED:-1\r\n", prefix);
+ return;
+ }
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ {
+ int j;
+
+ k++;
+ for (j = 0; j < Queue[i]->qg_numqueues; j++)
+ {
+ int n;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ n = QSHM_ENTRIES(Queue[i]->qg_qpaths[j].qp_idx);
+ if (prefix != NULL)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s%s:%d\r\n",
+ prefix, qid_printqueue(i, j), n);
+ else if (n < 0)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s: unknown number of entries\n",
+ qid_printqueue(i, j));
+ unknown = true;
+ }
+ else if (n == 0)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s is empty\n",
+ qid_printqueue(i, j));
+ }
+ else if (n > 0)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s: entries=%d\n",
+ qid_printqueue(i, j), n);
+ nrequests += n;
+ k++;
+ }
+ }
+ }
+ if (prefix == NULL && k > 1)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "\t\tTotal requests: %d%s\n",
+ nrequests, unknown ? " (about)" : "");
+#else /* SM_CONF_SHM */
+ if (prefix == NULL)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "Data unavailable without shared memory support\n");
+ else
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%sNOTAVAILABLE:-1\r\n", prefix);
+#endif /* SM_CONF_SHM */
+}
+/*
+** PRINTQUEUE -- print out a representation of the mail queue
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Prints a listing of the mail queue on the standard output.
+*/
+
+void
+printqueue()
+{
+ int i, k = 0, nrequests = 0;
+
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ {
+ int j;
+
+ k++;
+ for (j = 0; j < Queue[i]->qg_numqueues; j++)
+ {
+ if (StopRequest)
+ stop_sendmail();
+ nrequests += print_single_queue(i, j);
+ k++;
+ }
+ }
+ if (k > 1)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\t\tTotal requests: %d\n",
+ nrequests);
+}
+/*
+** PRINT_SINGLE_QUEUE -- print out a representation of a single mail queue
+**
+** Parameters:
+** qgrp -- the index of the queue group.
+** qdir -- the queue directory.
+**
+** Returns:
+** number of requests in mail queue.
+**
+** Side Effects:
+** Prints a listing of the mail queue on the standard output.
+*/
+
+int
+print_single_queue(qgrp, qdir)
+ int qgrp;
+ int qdir;
+{
+ register WORK *w;
+ SM_FILE_T *f;
+ int nrequests;
+ char qd[MAXPATHLEN];
+ char qddf[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ if (qdir == NOQDIR)
+ {
+ (void) sm_strlcpy(qd, ".", sizeof qd);
+ (void) sm_strlcpy(qddf, ".", sizeof qddf);
+ }
+ else
+ {
+ (void) sm_strlcpyn(qd, sizeof qd, 2,
+ Queue[qgrp]->qg_qpaths[qdir].qp_name,
+ (bitset(QP_SUBQF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/qf" : ""));
+ (void) sm_strlcpyn(qddf, sizeof qddf, 2,
+ Queue[qgrp]->qg_qpaths[qdir].qp_name,
+ (bitset(QP_SUBDF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/df" : ""));
+ }
+
+ /*
+ ** Check for permission to print the queue
+ */
+
+ if (bitset(PRIV_RESTRICTMAILQ, PrivacyFlags) && RealUid != 0)
+ {
+ struct stat st;
+#ifdef NGROUPS_MAX
+ int n;
+ extern GIDSET_T InitialGidSet[NGROUPS_MAX];
+#endif /* NGROUPS_MAX */
+
+ if (stat(qd, &st) < 0)
+ {
+ syserr("Cannot stat %s",
+ qid_printqueue(qgrp, qdir));
+ return 0;
+ }
+#ifdef NGROUPS_MAX
+ n = NGROUPS_MAX;
+ while (--n >= 0)
+ {
+ if (InitialGidSet[n] == st.st_gid)
+ break;
+ }
+ if (n < 0 && RealGid != st.st_gid)
+#else /* NGROUPS_MAX */
+ if (RealGid != st.st_gid)
+#endif /* NGROUPS_MAX */
+ {
+ usrerr("510 You are not permitted to see the queue");
+ setstat(EX_NOPERM);
+ return 0;
+ }
+ }
+
+ /*
+ ** Read and order the queue.
+ */
+
+ nrequests = gatherq(qgrp, qdir, true, NULL, NULL);
+ (void) sortq(Queue[qgrp]->qg_maxlist);
+
+ /*
+ ** Print the work list that we have read.
+ */
+
+ /* first see if there is anything */
+ if (nrequests <= 0)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s is empty\n",
+ qid_printqueue(qgrp, qdir));
+ return 0;
+ }
+
+ sm_getla(); /* get load average */
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\t\t%s (%d request%s",
+ qid_printqueue(qgrp, qdir),
+ nrequests, nrequests == 1 ? "" : "s");
+ if (MaxQueueRun > 0 && nrequests > MaxQueueRun)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ", only %d printed", MaxQueueRun);
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ")\n-----Q-ID----- --Size-- -Priority- ---Q-Time--- --------Sender/Recipient--------\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ")\n-----Q-ID----- --Size-- -----Q-Time----- ------------Sender/Recipient-----------\n");
+ for (w = WorkQ; w != NULL; w = w->w_next)
+ {
+ struct stat st;
+ auto time_t submittime = 0;
+ long dfsize;
+ int flags = 0;
+ int qfver;
+#if _FFR_QUARANTINE
+ char quarmsg[MAXLINE];
+#endif /* _FFR_QUARANTINE */
+ char statmsg[MAXLINE];
+ char bodytype[MAXNAME + 1];
+ char qf[MAXPATHLEN];
+
+ if (StopRequest)
+ stop_sendmail();
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%13s",
+ w->w_name + 2);
+ (void) sm_strlcpyn(qf, sizeof qf, 3, qd, "/", w->w_name);
+ f = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDONLY,
+ NULL);
+ if (f == NULL)
+ {
+ if (errno == EPERM)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (permission denied)\n");
+ else if (errno == ENOENT)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (job completed)\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (%s)\n",
+ sm_errstring(errno));
+ errno = 0;
+ continue;
+ }
+ w->w_name[0] = DATAFL_LETTER;
+ (void) sm_strlcpyn(qf, sizeof qf, 3, qddf, "/", w->w_name);
+ if (stat(qf, &st) >= 0)
+ dfsize = st.st_size;
+ else
+ {
+ ENVELOPE e;
+
+ /*
+ ** Maybe the df file can't be statted because
+ ** it is in a different directory than the qf file.
+ ** In order to find out, we must read the qf file.
+ */
+
+ newenvelope(&e, &BlankEnvelope, sm_rpool_new_x(NULL));
+ e.e_id = w->w_name + 2;
+ e.e_qgrp = qgrp;
+ e.e_qdir = qdir;
+ dfsize = -1;
+ if (readqf(&e, false))
+ {
+ char *df = queuename(&e, DATAFL_LETTER);
+ if (stat(df, &st) >= 0)
+ dfsize = st.st_size;
+ }
+ if (e.e_lockfp != NULL)
+ {
+ (void) sm_io_close(e.e_lockfp, SM_TIME_DEFAULT);
+ e.e_lockfp = NULL;
+ }
+ clearenvelope(&e, false, e.e_rpool);
+ sm_rpool_free(e.e_rpool);
+ }
+ if (w->w_lock)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "*");
+#if _FFR_QUARANTINE
+ else if (QueueMode == QM_LOST)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "?");
+#endif /* _FFR_QUARANTINE */
+ else if (w->w_tooyoung)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "-");
+ else if (shouldqueue(w->w_pri, w->w_ctime))
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "X");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " ");
+
+ errno = 0;
+
+#if _FFR_QUARANTINE
+ quarmsg[0] = '\0';
+#endif /* _FFR_QUARANTINE */
+ statmsg[0] = bodytype[0] = '\0';
+ qfver = 0;
+ while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ register int i;
+ register char *p;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ fixcrlf(buf, true);
+ switch (buf[0])
+ {
+ case 'V': /* queue file version */
+ qfver = atoi(&buf[1]);
+ break;
+
+ case 'M': /* error message */
+ if ((i = strlen(&buf[1])) >= sizeof statmsg)
+ i = sizeof statmsg - 1;
+ memmove(statmsg, &buf[1], i);
+ statmsg[i] = '\0';
+ break;
+
+#if _FFR_QUARANTINE
+ case 'q': /* quarantine reason */
+ if ((i = strlen(&buf[1])) >= sizeof quarmsg)
+ i = sizeof quarmsg - 1;
+ memmove(quarmsg, &buf[1], i);
+ quarmsg[i] = '\0';
+ break;
+#endif /* _FFR_QUARANTINE */
+
+ case 'B': /* body type */
+ if ((i = strlen(&buf[1])) >= sizeof bodytype)
+ i = sizeof bodytype - 1;
+ memmove(bodytype, &buf[1], i);
+ bodytype[i] = '\0';
+ break;
+
+ case 'S': /* sender name */
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%8ld %10ld%c%.12s ",
+ dfsize,
+ w->w_pri,
+ bitset(EF_WARNING, flags)
+ ? '+' : ' ',
+ ctime(&submittime) + 4);
+ prtstr(&buf[1], 78);
+ }
+ else
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%8ld %.16s ",
+ dfsize,
+ ctime(&submittime));
+ prtstr(&buf[1], 39);
+ }
+#if _FFR_QUARANTINE
+ if (quarmsg[0] != '\0')
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n QUARANTINE: %.*s",
+ Verbose ? 100 : 60,
+ quarmsg);
+ quarmsg[0] = '\0';
+ }
+#endif /* _FFR_QUARANTINE */
+ if (statmsg[0] != '\0' || bodytype[0] != '\0')
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n %10.10s",
+ bodytype);
+ if (statmsg[0] != '\0')
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ " (%.*s)",
+ Verbose ? 100 : 60,
+ statmsg);
+ statmsg[0] = '\0';
+ }
+ break;
+
+ case 'C': /* controlling user */
+ if (Verbose)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t\t\t\t\t(---%.64s---)",
+ &buf[1]);
+ break;
+
+ case 'R': /* recipient name */
+ p = &buf[1];
+ if (qfver >= 1)
+ {
+ p = strchr(p, ':');
+ if (p == NULL)
+ break;
+ p++;
+ }
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t\t\t\t\t");
+ prtstr(p, 71);
+ }
+ else
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t\t\t\t ");
+ prtstr(p, 38);
+ }
+ if (Verbose && statmsg[0] != '\0')
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t (%.100s)",
+ statmsg);
+ statmsg[0] = '\0';
+ }
+ break;
+
+ case 'T': /* creation time */
+ submittime = atol(&buf[1]);
+ break;
+
+ case 'F': /* flag bits */
+ for (p = &buf[1]; *p != '\0'; p++)
+ {
+ switch (*p)
+ {
+ case 'w':
+ flags |= EF_WARNING;
+ break;
+ }
+ }
+ }
+ }
+ if (submittime == (time_t) 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (no control file)");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\n");
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ }
+ return nrequests;
+}
+
+#if _FFR_QUARANTINE
+/*
+** QUEUE_LETTER -- get the proper queue letter for the current QueueMode.
+**
+** Parameters:
+** e -- envelope to build it in/from.
+** type -- the file type, used as the first character
+** of the file name.
+**
+** Returns:
+** the letter to use
+*/
+
+static char
+queue_letter(e, type)
+ ENVELOPE *e;
+ int type;
+{
+ /* Change type according to QueueMode */
+ if (type == ANYQFL_LETTER)
+ {
+ if (e->e_quarmsg != NULL)
+ type = QUARQF_LETTER;
+ else
+ {
+ switch (QueueMode)
+ {
+ case QM_NORMAL:
+ type = NORMQF_LETTER;
+ break;
+
+ case QM_QUARANTINE:
+ type = QUARQF_LETTER;
+ break;
+
+ case QM_LOST:
+ type = LOSEQF_LETTER;
+ break;
+
+ default:
+ /* should never happen */
+ abort();
+ /* NOTREACHED */
+ }
+ }
+ }
+ return type;
+}
+#endif /* _FFR_QUARANTINE */
+
+/*
+** QUEUENAME -- build a file name in the queue directory for this envelope.
+**
+** Parameters:
+** e -- envelope to build it in/from.
+** type -- the file type, used as the first character
+** of the file name.
+**
+** Returns:
+** a pointer to the queue name (in a static buffer).
+**
+** Side Effects:
+** If no id code is already assigned, queuename() will
+** assign an id code with assign_queueid(). If no queue
+** directory is assigned, one will be set with setnewqueue().
+*/
+
+char *
+queuename(e, type)
+ register ENVELOPE *e;
+ int type;
+{
+ int qd, qg;
+ char *sub = "/";
+ char pref[3];
+ static char buf[MAXPATHLEN];
+
+ /* Assign an ID if needed */
+ if (e->e_id == NULL)
+ assign_queueid(e);
+
+#if _FFR_QUARANTINE
+ type = queue_letter(e, type);
+#endif /* _FFR_QUARANTINE */
+
+ /* begin of filename */
+ pref[0] = (char) type;
+ pref[1] = 'f';
+ pref[2] = '\0';
+
+ /* Assign a queue group/directory if needed */
+ if (type == XSCRPT_LETTER)
+ {
+ /*
+ ** We don't want to call setnewqueue() if we are fetching
+ ** the pathname of the transcript file, because setnewqueue
+ ** chooses a queue, and sometimes we need to write to the
+ ** transcript file before we have gathered enough information
+ ** to choose a queue.
+ */
+
+ if (e->e_xfqgrp == NOQGRP || e->e_xfqdir == NOQDIR)
+ {
+ if (e->e_qgrp != NOQGRP && e->e_qdir != NOQDIR)
+ {
+ e->e_xfqgrp = e->e_qgrp;
+ e->e_xfqdir = e->e_qdir;
+ }
+ else
+ {
+ e->e_xfqgrp = 0;
+ if (Queue[e->e_xfqgrp]->qg_numqueues <= 1)
+ e->e_xfqdir = 0;
+ else
+ {
+ e->e_xfqdir = get_rand_mod(
+ Queue[e->e_xfqgrp]->qg_numqueues);
+ }
+ }
+ }
+ qd = e->e_xfqdir;
+ qg = e->e_xfqgrp;
+ }
+ else
+ {
+ if (e->e_qgrp == NOQGRP || e->e_qdir == NOQDIR)
+ setnewqueue(e);
+ if (type == DATAFL_LETTER)
+ {
+ qd = e->e_dfqdir;
+ qg = e->e_dfqgrp;
+ }
+ else
+ {
+ qd = e->e_qdir;
+ qg = e->e_qgrp;
+ }
+ }
+
+ /* xf files always have a valid qd and qg picked above */
+ if (e->e_qdir == NOQDIR && type != XSCRPT_LETTER)
+ (void) sm_strlcpyn(buf, sizeof buf, 2, pref, e->e_id);
+ else
+ {
+ switch (type)
+ {
+ case DATAFL_LETTER:
+ if (bitset(QP_SUBDF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
+ sub = "/df/";
+ break;
+
+#if _FFR_QUARANTINE
+ case QUARQF_LETTER:
+#endif /* _FFR_QUARANTINE */
+ case TEMPQF_LETTER:
+ case NEWQFL_LETTER:
+ case LOSEQF_LETTER:
+ case NORMQF_LETTER:
+ if (bitset(QP_SUBQF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
+ sub = "/qf/";
+ break;
+
+ case XSCRPT_LETTER:
+ if (bitset(QP_SUBXF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
+ sub = "/xf/";
+ break;
+
+ default:
+ sm_abort("queuename: bad queue file type %d", type);
+ }
+
+ (void) sm_strlcpyn(buf, sizeof buf, 4,
+ Queue[qg]->qg_qpaths[qd].qp_name,
+ sub, pref, e->e_id);
+ }
+
+ if (tTd(7, 2))
+ sm_dprintf("queuename: %s\n", buf);
+ return buf;
+}
+/*
+** ASSIGN_QUEUEID -- assign a queue ID for this envelope.
+**
+** Assigns an id code if one does not already exist.
+** This code assumes that nothing will remain in the queue for
+** longer than 60 years. It is critical that files with the given
+** name do not already exist in the queue.
+** [No longer initializes e_qdir to NOQDIR.]
+**
+** Parameters:
+** e -- envelope to set it in.
+**
+** Returns:
+** none.
+*/
+
+static const char QueueIdChars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx";
+# define QIC_LEN 60
+# define queuenextid() CurrentPid
+
+
+void
+assign_queueid(e)
+ register ENVELOPE *e;
+{
+ pid_t pid = queuenextid();
+ static int cX = 0;
+ static long random_offset;
+ struct tm *tm;
+ char idbuf[MAXQFNAME - 2];
+ int seq;
+
+ if (e->e_id != NULL)
+ return;
+
+ /* see if we need to get a new base time/pid */
+ if (cX >= QIC_LEN * QIC_LEN || LastQueueTime == 0 ||
+ LastQueuePid != pid)
+ {
+ time_t then = LastQueueTime;
+
+ /* if the first time through, pick a random offset */
+ if (LastQueueTime == 0)
+ random_offset = get_random();
+
+ while ((LastQueueTime = curtime()) == then &&
+ LastQueuePid == pid)
+ {
+ (void) sleep(1);
+ }
+ LastQueuePid = queuenextid();
+ cX = 0;
+ }
+
+ /*
+ ** Generate a new sequence number between 0 and QIC_LEN*QIC_LEN-1.
+ ** This lets us generate up to QIC_LEN*QIC_LEN unique queue ids
+ ** per second, per process. With envelope splitting,
+ ** a single message can consume many queue ids.
+ */
+
+ seq = (int)((cX + random_offset) % (QIC_LEN * QIC_LEN));
+ ++cX;
+ if (tTd(7, 50))
+ sm_dprintf("assign_queueid: random_offset = %ld (%d)\n",
+ random_offset, seq);
+
+ tm = gmtime(&LastQueueTime);
+ idbuf[0] = QueueIdChars[tm->tm_year % QIC_LEN];
+ idbuf[1] = QueueIdChars[tm->tm_mon];
+ idbuf[2] = QueueIdChars[tm->tm_mday];
+ idbuf[3] = QueueIdChars[tm->tm_hour];
+ idbuf[4] = QueueIdChars[tm->tm_min];
+ idbuf[5] = QueueIdChars[tm->tm_sec];
+ idbuf[6] = QueueIdChars[seq / QIC_LEN];
+ idbuf[7] = QueueIdChars[seq % QIC_LEN];
+ (void) sm_snprintf(&idbuf[8], sizeof idbuf - 8, "%06d",
+ (int) LastQueuePid);
+ e->e_id = sm_rpool_strdup_x(e->e_rpool, idbuf);
+ macdefine(&e->e_macro, A_PERM, 'i', e->e_id);
+#if 0
+ /* XXX: inherited from MainEnvelope */
+ e->e_qgrp = NOQGRP; /* too early to do anything else */
+ e->e_qdir = NOQDIR;
+ e->e_xfqgrp = NOQGRP;
+#endif /* 0 */
+#if _FFR_QUARANTINE
+ /* New ID means it's not on disk yet */
+ e->e_qfletter = '\0';
+#endif /* _FFR_QUARANTINE */
+ if (tTd(7, 1))
+ sm_dprintf("assign_queueid: assigned id %s, e=%p\n",
+ e->e_id, e);
+ if (LogLevel > 93)
+ sm_syslog(LOG_DEBUG, e->e_id, "assigned id");
+}
+/*
+** SYNC_QUEUE_TIME -- Assure exclusive PID in any given second
+**
+** Make sure one PID can't be used by two processes in any one second.
+**
+** If the system rotates PIDs fast enough, may get the
+** same pid in the same second for two distinct processes.
+** This will interfere with the queue file naming system.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+void
+sync_queue_time()
+{
+#if FAST_PID_RECYCLE
+ if (OpMode != MD_TEST &&
+ OpMode != MD_VERIFY &&
+ LastQueueTime > 0 &&
+ LastQueuePid == CurrentPid &&
+ curtime() == LastQueueTime)
+ (void) sleep(1);
+#endif /* FAST_PID_RECYCLE */
+}
+/*
+** UNLOCKQUEUE -- unlock the queue entry for a specified envelope
+**
+** Parameters:
+** e -- the envelope to unlock.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** unlocks the queue for `e'.
+*/
+
+void
+unlockqueue(e)
+ ENVELOPE *e;
+{
+ if (tTd(51, 4))
+ sm_dprintf("unlockqueue(%s)\n",
+ e->e_id == NULL ? "NOQUEUE" : e->e_id);
+
+
+ /* if there is a lock file in the envelope, close it */
+ if (e->e_lockfp != NULL)
+ (void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
+ e->e_lockfp = NULL;
+
+ /* don't create a queue id if we don't already have one */
+ if (e->e_id == NULL)
+ return;
+
+ /* remove the transcript */
+ if (LogLevel > 87)
+ sm_syslog(LOG_DEBUG, e->e_id, "unlock");
+ if (!tTd(51, 104))
+ (void) xunlink(queuename(e, XSCRPT_LETTER));
+}
+/*
+** SETCTLUSER -- create a controlling address
+**
+** Create a fake "address" given only a local login name; this is
+** used as a "controlling user" for future recipient addresses.
+**
+** Parameters:
+** user -- the user name of the controlling user.
+** qfver -- the version stamp of this queue file.
+** e -- envelope
+**
+** Returns:
+** An address descriptor for the controlling user,
+** using storage allocated from e->e_rpool.
+**
+*/
+
+static ADDRESS *
+setctluser(user, qfver, e)
+ char *user;
+ int qfver;
+ ENVELOPE *e;
+{
+ register ADDRESS *a;
+ struct passwd *pw;
+ char *p;
+
+ /*
+ ** See if this clears our concept of controlling user.
+ */
+
+ if (user == NULL || *user == '\0')
+ return NULL;
+
+ /*
+ ** Set up addr fields for controlling user.
+ */
+
+ a = (ADDRESS *) sm_rpool_malloc_x(e->e_rpool, sizeof *a);
+ memset((char *) a, '\0', sizeof *a);
+
+ if (*user == ':')
+ {
+ p = &user[1];
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, p);
+ }
+ else
+ {
+ p = strtok(user, ":");
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, user);
+ if (qfver >= 2)
+ {
+ if ((p = strtok(NULL, ":")) != NULL)
+ a->q_uid = atoi(p);
+ if ((p = strtok(NULL, ":")) != NULL)
+ a->q_gid = atoi(p);
+ if ((p = strtok(NULL, ":")) != NULL)
+ {
+ char *o;
+
+ a->q_flags |= QGOODUID;
+
+ /* if there is another ':': restore it */
+ if ((o = strtok(NULL, ":")) != NULL && o > p)
+ o[-1] = ':';
+ }
+ }
+ else if ((pw = sm_getpwnam(user)) != NULL)
+ {
+ if (*pw->pw_dir == '\0')
+ a->q_home = NULL;
+ else if (strcmp(pw->pw_dir, "/") == 0)
+ a->q_home = "";
+ else
+ a->q_home = sm_rpool_strdup_x(e->e_rpool, pw->pw_dir);
+ a->q_uid = pw->pw_uid;
+ a->q_gid = pw->pw_gid;
+ a->q_flags |= QGOODUID;
+ }
+ }
+
+ a->q_flags |= QPRIMARY; /* flag as a "ctladdr" */
+ a->q_mailer = LocalMailer;
+ if (p == NULL)
+ a->q_paddr = sm_rpool_strdup_x(e->e_rpool, a->q_user);
+ else
+ a->q_paddr = sm_rpool_strdup_x(e->e_rpool, p);
+ return a;
+}
+/*
+** LOSEQFILE -- rename queue file with LOSEQF_LETTER & try to let someone know
+**
+** Parameters:
+** e -- the envelope (e->e_id will be used).
+** why -- reported to whomever can hear.
+**
+** Returns:
+** none.
+*/
+
+void
+loseqfile(e, why)
+ register ENVELOPE *e;
+ char *why;
+{
+ bool loseit = true;
+ char *p;
+ char buf[MAXPATHLEN];
+
+ if (e == NULL || e->e_id == NULL)
+ return;
+ p = queuename(e, ANYQFL_LETTER);
+ if (sm_strlcpy(buf, p, sizeof buf) >= sizeof buf)
+ return;
+ if (!bitset(EF_INQUEUE, e->e_flags))
+ queueup(e, false, true);
+#if _FFR_QUARANTINE
+ else if (QueueMode == QM_LOST)
+ loseit = false;
+#endif /* _FFR_QUARANTINE */
+
+ /* if already lost, no need to re-lose */
+ if (loseit)
+ {
+ p = queuename(e, LOSEQF_LETTER);
+ if (rename(buf, p) < 0)
+ syserr("cannot rename(%s, %s), uid=%d",
+ buf, p, (int) geteuid());
+ else if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Losing %s: %s", buf, why);
+ }
+ if (e->e_dfp != NULL)
+ {
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_dfp = NULL;
+ }
+ e->e_flags &= ~EF_HAS_DF;
+}
+/*
+** NAME2QID -- translate a queue group name to a queue group id
+**
+** Parameters:
+** queuename -- name of queue group.
+**
+** Returns:
+** queue group id if found.
+** NOQGRP otherwise.
+*/
+
+int
+name2qid(queuename)
+ char *queuename;
+{
+ register STAB *s;
+
+ s = stab(queuename, ST_QUEUE, ST_FIND);
+ if (s == NULL)
+ return NOQGRP;
+ return s->s_quegrp->qg_index;
+}
+/*
+** QID_PRINTNAME -- create externally printable version of queue id
+**
+** Parameters:
+** e -- the envelope.
+**
+** Returns:
+** a printable version
+*/
+
+char *
+qid_printname(e)
+ ENVELOPE *e;
+{
+ char *id;
+ static char idbuf[MAXQFNAME + 34];
+
+ if (e == NULL)
+ return "";
+
+ if (e->e_id == NULL)
+ id = "";
+ else
+ id = e->e_id;
+
+ if (e->e_qdir == NOQDIR)
+ return id;
+
+ (void) sm_snprintf(idbuf, sizeof idbuf, "%.32s/%s",
+ Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_name,
+ id);
+ return idbuf;
+}
+/*
+** QID_PRINTQUEUE -- create full version of queue directory for data files
+**
+** Parameters:
+** qgrp -- index in queue group.
+** qdir -- the short version of the queue directory
+**
+** Returns:
+** the full pathname to the queue (might point to a static var)
+*/
+
+char *
+qid_printqueue(qgrp, qdir)
+ int qgrp;
+ int qdir;
+{
+ char *subdir;
+ static char dir[MAXPATHLEN];
+
+ if (qdir == NOQDIR)
+ return Queue[qgrp]->qg_qdir;
+
+ if (strcmp(Queue[qgrp]->qg_qpaths[qdir].qp_name, ".") == 0)
+ subdir = NULL;
+ else
+ subdir = Queue[qgrp]->qg_qpaths[qdir].qp_name;
+
+ (void) sm_strlcpyn(dir, sizeof dir, 4,
+ Queue[qgrp]->qg_qdir,
+ subdir == NULL ? "" : "/",
+ subdir == NULL ? "" : subdir,
+ (bitset(QP_SUBDF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/df" : ""));
+ return dir;
+}
+
+/*
+** PICKQDIR -- Pick a queue directory from a queue group
+**
+** Parameters:
+** qg -- queue group
+** fsize -- file size in bytes
+** e -- envelope, or NULL
+**
+** Result:
+** NOQDIR if no queue directory in qg has enough free space to
+** hold a file of size 'fsize', otherwise the index of
+** a randomly selected queue directory which resides on a
+** file system with enough disk space.
+** XXX This could be extended to select a queuedir with
+** a few (the fewest?) number of entries. That data
+** is available if shared memory is used.
+**
+** Side Effects:
+** If the request fails and e != NULL then sm_syslog is called.
+*/
+
+int
+pickqdir(qg, fsize, e)
+ QUEUEGRP *qg;
+ long fsize;
+ ENVELOPE *e;
+{
+ int qdir;
+ int i;
+ long avail = 0;
+
+ /* Pick a random directory, as a starting point. */
+ if (qg->qg_numqueues <= 1)
+ qdir = 0;
+ else
+ qdir = get_rand_mod(qg->qg_numqueues);
+
+ if (MinBlocksFree <= 0 && fsize <= 0)
+ return qdir;
+
+ /*
+ ** Now iterate over the queue directories,
+ ** looking for a directory with enough space for this message.
+ */
+
+ i = qdir;
+ do
+ {
+ QPATHS *qp = &qg->qg_qpaths[i];
+ long needed = 0;
+ long fsavail = 0;
+
+ if (fsize > 0)
+ needed += fsize / FILE_SYS_BLKSIZE(qp->qp_fsysidx)
+ + ((fsize % FILE_SYS_BLKSIZE(qp->qp_fsysidx)
+ > 0) ? 1 : 0);
+ if (MinBlocksFree > 0)
+ needed += MinBlocksFree;
+ fsavail = FILE_SYS_AVAIL(qp->qp_fsysidx);
+#if SM_CONF_SHM
+ if (fsavail <= 0)
+ {
+ long blksize;
+
+ /*
+ ** might be not correctly updated,
+ ** let's try to get the info directly.
+ */
+
+ fsavail = freediskspace(FILE_SYS_NAME(qp->qp_fsysidx),
+ &blksize);
+ if (fsavail < 0)
+ fsavail = 0;
+ }
+#endif /* SM_CONF_SHM */
+ if (needed <= fsavail)
+ return i;
+ if (avail < fsavail)
+ avail = fsavail;
+
+ if (qg->qg_numqueues > 0)
+ i = (i + 1) % qg->qg_numqueues;
+ } while (i != qdir);
+
+ if (e != NULL && LogLevel > 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "low on space (%s needs %ld bytes + %ld blocks in %s), max avail: %ld",
+ CurHostName == NULL ? "SMTP-DAEMON" : CurHostName,
+ fsize, MinBlocksFree,
+ qg->qg_qdir, avail);
+ return NOQDIR;
+}
+/*
+** SETNEWQUEUE -- Sets a new queue group and directory
+**
+** Assign a queue group and directory to an envelope and store the
+** directory in e->e_qdir.
+**
+** Parameters:
+** e -- envelope to assign a queue for.
+**
+** Returns:
+** true if successful
+** false otherwise
+**
+** Side Effects:
+** On success, e->e_qgrp and e->e_qdir are non-negative.
+** On failure (not enough disk space),
+** e->qgrp = NOQGRP, e->e_qdir = NOQDIR
+** and usrerr() is invoked (which could raise an exception).
+*/
+
+bool
+setnewqueue(e)
+ ENVELOPE *e;
+{
+ if (tTd(41, 20))
+ sm_dprintf("setnewqueue: called\n");
+
+ /* not set somewhere else */
+ if (e->e_qgrp == NOQGRP)
+ {
+ ADDRESS *q;
+
+ /*
+ ** Use the queue group of the "first" recipient, as set by
+ ** the "queuegroup" rule set. If that is not defined, then
+ ** use the queue group of the mailer of the first recipient.
+ ** If that is not defined either, then use the default
+ ** queue group.
+ ** Notice: "first" depends on the sorting of sendqueue
+ ** in recipient().
+ ** To avoid problems with "bad" recipients look
+ ** for a valid address first.
+ */
+
+ q = e->e_sendqueue;
+ while (q != NULL &&
+ (QS_IS_BADADDR(q->q_state) || QS_IS_DEAD(q->q_state)))
+ {
+ q = q->q_next;
+ }
+ if (q == NULL)
+ e->e_qgrp = 0;
+ else if (q->q_qgrp >= 0)
+ e->e_qgrp = q->q_qgrp;
+ else if (q->q_mailer != NULL &&
+ ISVALIDQGRP(q->q_mailer->m_qgrp))
+ e->e_qgrp = q->q_mailer->m_qgrp;
+ else
+ e->e_qgrp = 0;
+ e->e_dfqgrp = e->e_qgrp;
+ }
+
+ if (ISVALIDQDIR(e->e_qdir) && ISVALIDQDIR(e->e_dfqdir))
+ {
+ if (tTd(41, 20))
+ sm_dprintf("setnewqueue: e_qdir already assigned (%s)\n",
+ qid_printqueue(e->e_qgrp, e->e_qdir));
+ return true;
+ }
+
+ filesys_update();
+ e->e_qdir = pickqdir(Queue[e->e_qgrp], e->e_msgsize, e);
+ if (e->e_qdir == NOQDIR)
+ {
+ e->e_qgrp = NOQGRP;
+ if (!bitset(EF_FATALERRS, e->e_flags))
+ usrerr("452 4.4.5 Insufficient disk space; try again later");
+ e->e_flags |= EF_FATALERRS;
+ return false;
+ }
+
+ if (tTd(41, 3))
+ sm_dprintf("setnewqueue: Assigned queue directory %s\n",
+ qid_printqueue(e->e_qgrp, e->e_qdir));
+
+ if (e->e_xfqgrp == NOQGRP || e->e_xfqdir == NOQDIR)
+ {
+ e->e_xfqgrp = e->e_qgrp;
+ e->e_xfqdir = e->e_qdir;
+ }
+ e->e_dfqdir = e->e_qdir;
+ return true;
+}
+/*
+** CHKQDIR -- check a queue directory
+**
+** Parameters:
+** name -- name of queue directory
+** sff -- flags for safefile()
+**
+** Returns:
+** is it a queue directory?
+*/
+
+static bool
+chkqdir(name, sff)
+ char *name;
+ long sff;
+{
+ struct stat statb;
+ int i;
+
+ /* skip over . and .. directories */
+ if (name[0] == '.' &&
+ (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
+ return false;
+#if HASLSTAT
+ if (lstat(name, &statb) < 0)
+#else /* HASLSTAT */
+ if (stat(name, &statb) < 0)
+#endif /* HASLSTAT */
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: stat(\"%s\"): %s\n",
+ name, sm_errstring(errno));
+ return false;
+ }
+#if HASLSTAT
+ if (S_ISLNK(statb.st_mode))
+ {
+ /*
+ ** For a symlink we need to make sure the
+ ** target is a directory
+ */
+
+ if (stat(name, &statb) < 0)
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: stat(\"%s\"): %s\n",
+ name, sm_errstring(errno));
+ return false;
+ }
+ }
+#endif /* HASLSTAT */
+
+ if (!S_ISDIR(statb.st_mode))
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: \"%s\": Not a directory\n",
+ name);
+ return false;
+ }
+
+ /* Print a warning if unsafe (but still use it) */
+ /* XXX do this only if we want the warning? */
+ i = safedirpath(name, RunAsUid, RunAsGid, NULL, sff, 0, 0);
+ if (i != 0)
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: \"%s\": Not safe: %s\n",
+ name, sm_errstring(i));
+#if _FFR_CHK_QUEUE
+ if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, NOQID,
+ "queue directory \"%s\": Not safe: %s",
+ name, sm_errstring(i));
+#endif /* _FFR_CHK_QUEUE */
+ }
+ return true;
+}
+/*
+** MULTIQUEUE_CACHE -- cache a list of paths to queues.
+**
+** Each potential queue is checked as the cache is built.
+** Thereafter, each is blindly trusted.
+** Note that we can be called again after a timeout to rebuild
+** (although code for that is not ready yet).
+**
+** Parameters:
+** basedir -- base of all queue directories.
+** blen -- strlen(basedir).
+** qg -- queue group.
+** qn -- number of queue directories already cached.
+** phash -- pointer to hash value over queue dirs.
+#if SM_CONF_SHM
+** only used if shared memory is active.
+#endif * SM_CONF_SHM *
+**
+** Returns:
+** new number of queue directories.
+*/
+
+#define INITIAL_SLOTS 20
+#define ADD_SLOTS 10
+
+static int
+multiqueue_cache(basedir, blen, qg, qn, phash)
+ char *basedir;
+ int blen;
+ QUEUEGRP *qg;
+ int qn;
+ unsigned int *phash;
+{
+ char *cp;
+ int i, len;
+ int slotsleft = 0;
+ long sff = SFF_ANYFILE;
+ char qpath[MAXPATHLEN];
+ char subdir[MAXPATHLEN];
+ char prefix[MAXPATHLEN]; /* dir relative to basedir */
+
+ if (tTd(41, 20))
+ sm_dprintf("multiqueue_cache: called\n");
+
+ /* Initialize to current directory */
+ prefix[0] = '.';
+ prefix[1] = '\0';
+ if (qg->qg_numqueues != 0 && qg->qg_qpaths != NULL)
+ {
+ for (i = 0; i < qg->qg_numqueues; i++)
+ {
+ if (qg->qg_qpaths[i].qp_name != NULL)
+ (void) sm_free(qg->qg_qpaths[i].qp_name); /* XXX */
+ }
+ (void) sm_free((char *) qg->qg_qpaths); /* XXX */
+ qg->qg_qpaths = NULL;
+ qg->qg_numqueues = 0;
+ }
+
+ /* If running as root, allow safedirpath() checks to use privs */
+ if (RunAsUid == 0)
+ sff |= SFF_ROOTOK;
+#if _FFR_CHK_QUEUE
+ sff |= SFF_SAFEDIRPATH|SFF_NOWWFILES;
+ if (!UseMSP)
+ sff |= SFF_NOGWFILES;
+#endif /* _FFR_CHK_QUEUE */
+
+ if (!SM_IS_DIR_START(qg->qg_qdir))
+ {
+ /*
+ ** XXX we could add basedir, but then we have to realloc()
+ ** the string... Maybe another time.
+ */
+
+ syserr("QueuePath %s not absolute", qg->qg_qdir);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ /* qpath: directory of current workgroup */
+ len = sm_strlcpy(qpath, qg->qg_qdir, sizeof qpath);
+ if (len >= sizeof qpath)
+ {
+ syserr("QueuePath %.256s too long (%d max)",
+ qg->qg_qdir, (int) sizeof qpath);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ /* begin of qpath must be same as basedir */
+ if (strncmp(basedir, qpath, blen) != 0 &&
+ (strncmp(basedir, qpath, blen - 1) != 0 || len != blen - 1))
+ {
+ syserr("QueuePath %s not subpath of QueueDirectory %s",
+ qpath, basedir);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ /* Do we have a nested subdirectory? */
+ if (blen < len && SM_FIRST_DIR_DELIM(qg->qg_qdir + blen) != NULL)
+ {
+
+ /* Copy subdirectory into prefix for later use */
+ if (sm_strlcpy(prefix, qg->qg_qdir + blen, sizeof prefix) >=
+ sizeof prefix)
+ {
+ syserr("QueuePath %.256s too long (%d max)",
+ qg->qg_qdir, (int) sizeof qpath);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+ cp = SM_LAST_DIR_DELIM(prefix);
+ SM_ASSERT(cp != NULL);
+ *cp = '\0'; /* cut off trailing / */
+ }
+
+ /* This is guaranteed by the basedir check above */
+ SM_ASSERT(len >= blen - 1);
+ cp = &qpath[len - 1];
+ if (*cp == '*')
+ {
+ register DIR *dp;
+ register struct dirent *d;
+ int off;
+ char *delim;
+ char relpath[MAXPATHLEN];
+
+ *cp = '\0'; /* Overwrite wildcard */
+ if ((cp = SM_LAST_DIR_DELIM(qpath)) == NULL)
+ {
+ syserr("QueueDirectory: can not wildcard relative path");
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: \"%s*\": Can not wildcard relative path.\n",
+ qpath);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+ if (cp == qpath)
+ {
+ /*
+ ** Special case of top level wildcard, like /foo*
+ ** Change to //foo*
+ */
+
+ (void) sm_strlcpy(qpath + 1, qpath, sizeof qpath - 1);
+ ++cp;
+ }
+ delim = cp;
+ *(cp++) = '\0'; /* Replace / with \0 */
+ len = strlen(cp); /* Last component of queue directory */
+
+ /*
+ ** Path relative to basedir, with trailing /
+ ** It will be modified below to specify the subdirectories
+ ** so they can be opened without chdir().
+ */
+
+ off = sm_strlcpyn(relpath, sizeof relpath, 2, prefix, "/");
+ SM_ASSERT(off < sizeof relpath);
+
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: prefix=\"%s%s\"\n",
+ relpath, cp);
+
+ /* It is always basedir: we don't need to store it per group */
+ /* XXX: optimize this! -> one more global? */
+ qg->qg_qdir = newstr(basedir);
+ qg->qg_qdir[blen - 1] = '\0'; /* cut off trailing / */
+
+ /*
+ ** XXX Should probably wrap this whole loop in a timeout
+ ** in case some wag decides to NFS mount the queues.
+ */
+
+ /* Test path to get warning messages. */
+ if (qn == 0)
+ {
+ /* XXX qg_runasuid and qg_runasgid for specials? */
+ i = safedirpath(basedir, RunAsUid, RunAsGid, NULL,
+ sff, 0, 0);
+ if (i != 0 && tTd(41, 2))
+ sm_dprintf("multiqueue_cache: \"%s\": Not safe: %s\n",
+ basedir, sm_errstring(i));
+ }
+
+ if ((dp = opendir(prefix)) == NULL)
+ {
+ syserr("can not opendir(%s/%s)", qg->qg_qdir, prefix);
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: opendir(\"%s/%s\"): %s\n",
+ qg->qg_qdir, prefix,
+ sm_errstring(errno));
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+ while ((d = readdir(dp)) != NULL)
+ {
+ i = strlen(d->d_name);
+ if (i < len || strncmp(d->d_name, cp, len) != 0)
+ {
+ if (tTd(41, 5))
+ sm_dprintf("multiqueue_cache: \"%s\", skipped\n",
+ d->d_name);
+ continue;
+ }
+
+ /* Create relative pathname: prefix + local directory */
+ i = sizeof(relpath) - off;
+ if (sm_strlcpy(relpath + off, d->d_name, i) >= i)
+ continue; /* way too long */
+
+ if (!chkqdir(relpath, sff))
+ continue;
+
+ if (qg->qg_qpaths == NULL)
+ {
+ slotsleft = INITIAL_SLOTS;
+ qg->qg_qpaths = (QPATHS *)xalloc((sizeof *qg->qg_qpaths) *
+ slotsleft);
+ qg->qg_numqueues = 0;
+ }
+ else if (slotsleft < 1)
+ {
+ qg->qg_qpaths = (QPATHS *)sm_realloc((char *)qg->qg_qpaths,
+ (sizeof *qg->qg_qpaths) *
+ (qg->qg_numqueues +
+ ADD_SLOTS));
+ if (qg->qg_qpaths == NULL)
+ {
+ (void) closedir(dp);
+ return qn;
+ }
+ slotsleft += ADD_SLOTS;
+ }
+
+ /* check subdirs */
+ qg->qg_qpaths[qg->qg_numqueues].qp_subdirs = QP_NOSUB;
+
+#define CHKRSUBDIR(name, flag) \
+ (void) sm_strlcpyn(subdir, sizeof subdir, 3, relpath, "/", name); \
+ if (chkqdir(subdir, sff)) \
+ qg->qg_qpaths[qg->qg_numqueues].qp_subdirs |= flag; \
+ else
+
+
+ CHKRSUBDIR("qf", QP_SUBQF);
+ CHKRSUBDIR("df", QP_SUBDF);
+ CHKRSUBDIR("xf", QP_SUBXF);
+
+ /* assert(strlen(d->d_name) < MAXPATHLEN - 14) */
+ /* maybe even - 17 (subdirs) */
+
+ if (prefix[0] != '.')
+ qg->qg_qpaths[qg->qg_numqueues].qp_name =
+ newstr(relpath);
+ else
+ qg->qg_qpaths[qg->qg_numqueues].qp_name =
+ newstr(d->d_name);
+
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: %d: \"%s\" cached (%x).\n",
+ qg->qg_numqueues, relpath,
+ qg->qg_qpaths[qg->qg_numqueues].qp_subdirs);
+#if SM_CONF_SHM
+ qg->qg_qpaths[qg->qg_numqueues].qp_idx = qn;
+ *phash = hash_q(relpath, *phash);
+#endif /* SM_CONF_SHM */
+ qg->qg_numqueues++;
+ ++qn;
+ slotsleft--;
+ }
+ (void) closedir(dp);
+
+ /* undo damage */
+ *delim = '/';
+ }
+ if (qg->qg_numqueues == 0)
+ {
+ qg->qg_qpaths = (QPATHS *) xalloc(sizeof *qg->qg_qpaths);
+
+ /* test path to get warning messages */
+ i = safedirpath(qpath, RunAsUid, RunAsGid, NULL, sff, 0, 0);
+ if (i == ENOENT)
+ {
+ syserr("can not opendir(%s)", qpath);
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: opendir(\"%s\"): %s\n",
+ qpath, sm_errstring(i));
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ qg->qg_qpaths[0].qp_subdirs = QP_NOSUB;
+ qg->qg_numqueues = 1;
+
+ /* check subdirs */
+#define CHKSUBDIR(name, flag) \
+ (void) sm_strlcpyn(subdir, sizeof subdir, 3, qg->qg_qdir, "/", name); \
+ if (chkqdir(subdir, sff)) \
+ qg->qg_qpaths[0].qp_subdirs |= flag; \
+ else
+
+ CHKSUBDIR("qf", QP_SUBQF);
+ CHKSUBDIR("df", QP_SUBDF);
+ CHKSUBDIR("xf", QP_SUBXF);
+
+ if (qg->qg_qdir[blen - 1] != '\0' &&
+ qg->qg_qdir[blen] != '\0')
+ {
+ /*
+ ** Copy the last component into qpaths and
+ ** cut off qdir
+ */
+
+ qg->qg_qpaths[0].qp_name = newstr(qg->qg_qdir + blen);
+ qg->qg_qdir[blen - 1] = '\0';
+ }
+ else
+ qg->qg_qpaths[0].qp_name = newstr(".");
+
+#if SM_CONF_SHM
+ qg->qg_qpaths[0].qp_idx = qn;
+ *phash = hash_q(qg->qg_qpaths[0].qp_name, *phash);
+#endif /* SM_CONF_SHM */
+ ++qn;
+ }
+ return qn;
+}
+
+/*
+** FILESYS_FIND -- find entry in FileSys table, or add new one
+**
+** Given the pathname of a directory, determine the file system
+** in which that directory resides, and return a pointer to the
+** entry in the FileSys table that describes the file system.
+** A new entry is added if necessary (and requested).
+** If the directory does not exist, -1 is returned.
+**
+** Parameters:
+** path -- pathname of directory
+** add -- add to structure if not found.
+**
+** Returns:
+** >=0: found: index in file system table
+** <0: some error, i.e.,
+** FSF_TOO_MANY: too many filesystems (-> syserr())
+** FSF_STAT_FAIL: can't stat() filesystem (-> syserr())
+** FSF_NOT_FOUND: not in list
+*/
+
+static short filesys_find __P((char *, bool));
+
+#define FSF_NOT_FOUND (-1)
+#define FSF_STAT_FAIL (-2)
+#define FSF_TOO_MANY (-3)
+
+static short
+filesys_find(path, add)
+ char *path;
+ bool add;
+{
+ struct stat st;
+ short i;
+
+ if (stat(path, &st) < 0)
+ {
+ syserr("cannot stat queue directory %s", path);
+ return FSF_STAT_FAIL;
+ }
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ if (FILE_SYS_DEV(i) == st.st_dev)
+ return i;
+ }
+ if (i >= MAXFILESYS)
+ {
+ syserr("too many queue file systems (%d max)", MAXFILESYS);
+ return FSF_TOO_MANY;
+ }
+ if (!add)
+ return FSF_NOT_FOUND;
+
+ ++NumFileSys;
+ FILE_SYS_NAME(i) = path;
+ FILE_SYS_DEV(i) = st.st_dev;
+ FILE_SYS_AVAIL(i) = 0;
+ FILE_SYS_BLKSIZE(i) = 1024; /* avoid divide by zero */
+ return i;
+}
+
+/*
+** FILESYS_SETUP -- set up mapping from queue directories to file systems
+**
+** This data structure is used to efficiently check the amount of
+** free space available in a set of queue directories.
+**
+** Parameters:
+** add -- initialize structure if necessary.
+**
+** Returns:
+** 0: success
+** <0: some error, i.e.,
+** FSF_NOT_FOUND: not in list
+** FSF_STAT_FAIL: can't stat() filesystem (-> syserr())
+** FSF_TOO_MANY: too many filesystems (-> syserr())
+*/
+
+static int filesys_setup __P((bool));
+
+static int
+filesys_setup(add)
+ bool add;
+{
+ int i, j;
+ short fs;
+ int ret;
+
+ ret = 0;
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ {
+ for (j = 0; j < Queue[i]->qg_numqueues; ++j)
+ {
+ QPATHS *qp = &Queue[i]->qg_qpaths[j];
+
+ fs = filesys_find(qp->qp_name, add);
+ if (fs >= 0)
+ qp->qp_fsysidx = fs;
+ else
+ qp->qp_fsysidx = 0;
+ if (fs < ret)
+ ret = fs;
+ }
+ }
+ return ret;
+}
+
+/*
+** FILESYS_UPDATE -- update amount of free space on all file systems
+**
+** The FileSys table is used to cache the amount of free space
+** available on all queue directory file systems.
+** This function updates the cached information if it has expired.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Updates FileSys table.
+*/
+
+void
+filesys_update()
+{
+ int i;
+ long avail, blksize;
+ time_t now;
+ static time_t nextupdate = 0;
+
+#if SM_CONF_SHM
+ /* only the daemon updates this structure */
+ if (ShmId != SM_SHM_NO_ID && DaemonPid != CurrentPid)
+ return;
+#endif /* SM_CONF_SHM */
+ now = curtime();
+ if (now < nextupdate)
+ return;
+ nextupdate = now + FILESYS_UPDATE_INTERVAL;
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ FILESYS *fs = &FILE_SYS(i);
+
+ avail = freediskspace(FILE_SYS_NAME(i), &blksize);
+ if (avail < 0 || blksize <= 0)
+ {
+ if (LogLevel > 5)
+ sm_syslog(LOG_ERR, NOQID,
+ "filesys_update failed: %s, fs=%s, avail=%ld, blocksize=%ld",
+ sm_errstring(errno),
+ FILE_SYS_NAME(i), avail, blksize);
+ fs->fs_avail = 0;
+ fs->fs_blksize = 1024; /* avoid divide by zero */
+ nextupdate = now + 2; /* let's do this soon again */
+ }
+ else
+ {
+ fs->fs_avail = avail;
+ fs->fs_blksize = blksize;
+ }
+ }
+}
+
+#if _FFR_ANY_FREE_FS
+/*
+** FILESYS_FREE -- check whether there is at least one fs with enough space.
+**
+** Parameters:
+** fsize -- file size in bytes
+**
+** Returns:
+** true iff there is one fs with more than fsize bytes free.
+*/
+
+bool
+filesys_free(fsize)
+ long fsize;
+{
+ int i;
+
+ if (fsize <= 0)
+ return true;
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ long needed = 0;
+
+ if (FILE_SYS_AVAIL(i) < 0 || FILE_SYS_BLKSIZE(i) <= 0)
+ continue;
+ needed += fsize / FILE_SYS_BLKSIZE(i)
+ + ((fsize % FILE_SYS_BLKSIZE(i)
+ > 0) ? 1 : 0)
+ + MinBlocksFree;
+ if (needed <= FILE_SYS_AVAIL(i))
+ return true;
+ }
+ return false;
+}
+#endif /* _FFR_ANY_FREE_FS */
+
+#if _FFR_CONTROL_MSTAT
+/*
+** DISK_STATUS -- show amount of free space in queue directories
+**
+** Parameters:
+** out -- output file pointer.
+** prefix -- string to output in front of each line.
+**
+** Returns:
+** none.
+*/
+
+void
+disk_status(out, prefix)
+ SM_FILE_T *out;
+ char *prefix;
+{
+ int i;
+ long avail, blksize;
+ long free;
+
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ avail = freediskspace(FILE_SYS_NAME(i), &blksize);
+ if (avail >= 0 && blksize > 0)
+ {
+ free = (long)((double) avail *
+ ((double) blksize / 1024));
+ }
+ else
+ free = -1;
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s%d/%s/%ld\r\n",
+ prefix, i,
+ FILE_SYS_NAME(i),
+ free);
+ }
+}
+#endif /* _FFR_CONTROL_MSTAT */
+
+#if SM_CONF_SHM
+/*
+** UPD_QS -- update information about queue when adding/deleting an entry
+**
+** Parameters:
+** e -- envelope.
+** delete -- delete/add entry.
+** avail -- update the space available as well.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Modifies available space in filesystem.
+** Changes number of entries in queue directory.
+*/
+
+void
+upd_qs(e, delete, avail)
+ ENVELOPE *e;
+ bool delete;
+ bool avail;
+{
+ short fidx;
+ int idx;
+ long s;
+
+ if (ShmId == SM_SHM_NO_ID || e == NULL)
+ return;
+ if (e->e_qgrp == NOQGRP || e->e_qdir == NOQDIR)
+ return;
+ idx = Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_idx;
+
+ /* XXX in theory this needs to be protected with a mutex */
+ if (QSHM_ENTRIES(idx) >= 0)
+ {
+ if (delete)
+ --QSHM_ENTRIES(idx);
+ else
+ ++QSHM_ENTRIES(idx);
+ }
+
+ fidx = Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_fsysidx;
+ if (fidx < 0)
+ return;
+
+ /* update available space also? (might be loseqfile) */
+ if (!avail)
+ return;
+
+ /* convert size to blocks; this causes rounding errors */
+ s = e->e_msgsize / FILE_SYS_BLKSIZE(fidx);
+ if (s == 0)
+ return;
+
+ /* XXX in theory this needs to be protected with a mutex */
+ if (delete)
+ FILE_SYS_AVAIL(fidx) += s;
+ else
+ FILE_SYS_AVAIL(fidx) -= s;
+
+}
+
+#if _FFR_SELECT_SHM
+
+static bool write_key_file __P((char *, long));
+static long read_key_file __P((char *, long));
+
+/*
+** WRITE_KEY_FILE -- record some key into a file.
+**
+** Parameters:
+** keypath -- file name.
+** key -- key to write.
+**
+** Returns:
+** true iff file could be written.
+**
+** Side Effects:
+** writes file.
+*/
+
+static bool
+write_key_file(keypath, key)
+ char *keypath;
+ long key;
+{
+ bool ok;
+ long sff;
+ SM_FILE_T *keyf;
+
+ ok = false;
+ if (keypath == NULL || *keypath == '\0')
+ return ok;
+ sff = SFF_NOLINK|SFF_ROOTOK|SFF_REGONLY|SFF_CREAT;
+ if (TrustedUid != 0 && RealUid == TrustedUid)
+ sff |= SFF_OPENASROOT;
+ keyf = safefopen(keypath, O_WRONLY|O_TRUNC, 0644, sff);
+ if (keyf == NULL)
+ {
+ sm_syslog(LOG_ERR, NOQID, "unable to write %s: %s",
+ keypath, sm_errstring(errno));
+ }
+ else
+ {
+ ok = sm_io_fprintf(keyf, SM_TIME_DEFAULT, "%ld\n", key) !=
+ SM_IO_EOF;
+ ok = ok && (sm_io_close(keyf, SM_TIME_DEFAULT) != SM_IO_EOF);
+ }
+ return ok;
+}
+
+/*
+** READ_KEY_FILE -- read a key from a file.
+**
+** Parameters:
+** keypath -- file name.
+** key -- default key.
+**
+** Returns:
+** key.
+*/
+
+static long
+read_key_file(keypath, key)
+ char *keypath;
+ long key;
+{
+ int r;
+ long sff, n;
+ SM_FILE_T *keyf;
+
+ if (keypath == NULL || *keypath == '\0')
+ return key;
+ sff = SFF_NOLINK|SFF_ROOTOK|SFF_REGONLY;
+ if (TrustedUid != 0 && RealUid == TrustedUid)
+ sff |= SFF_OPENASROOT;
+ keyf = safefopen(keypath, O_RDONLY, 0644, sff);
+ if (keyf == NULL)
+ {
+ sm_syslog(LOG_ERR, NOQID, "unable to read %s: %s",
+ keypath, sm_errstring(errno));
+ }
+ else
+ {
+ r = sm_io_fscanf(keyf, SM_TIME_DEFAULT, "%ld", &n);
+ if (r == 1)
+ key = n;
+ (void) sm_io_close(keyf, SM_TIME_DEFAULT);
+ }
+ return key;
+}
+#endif /* _FFR_SELECT_SHM */
+
+/*
+** INIT_SHM -- initialize shared memory structure
+**
+** Initialize or attach to shared memory segment.
+** Currently it is not a fatal error if this doesn't work.
+** However, it causes us to have a "fallback" storage location
+** for everything that is supposed to be in the shared memory,
+** which makes the code slightly ugly.
+**
+** Parameters:
+** qn -- number of queue directories.
+** owner -- owner of shared memory.
+** hash -- identifies data that is stored in shared memory.
+**
+** Returns:
+** none.
+*/
+
+static void init_shm __P((int, bool, unsigned int));
+
+static void
+init_shm(qn, owner, hash)
+ int qn;
+ bool owner;
+ unsigned int hash;
+{
+ int i;
+#if _FFR_SELECT_SHM
+ bool keyselect;
+#endif /* _FFR_SELECT_SHM */
+
+ PtrFileSys = &FileSys[0];
+ PNumFileSys = &Numfilesys;
+#if _FFR_SELECT_SHM
+/* if this "key" is specified: select one yourself */
+# define SEL_SHM_KEY ((key_t) -1)
+# define FIRST_SHM_KEY 25
+#endif /* _FFR_SELECT_SHM */
+
+ /* This allows us to disable shared memory at runtime. */
+ if (ShmKey != 0)
+ {
+ int count;
+ int save_errno;
+ size_t shms;
+
+ count = 0;
+ shms = SM_T_SIZE + qn * sizeof(QUEUE_SHM_T);
+#if _FFR_SELECT_SHM
+ keyselect = ShmKey == SEL_SHM_KEY;
+ if (keyselect)
+ {
+ if (owner)
+ ShmKey = FIRST_SHM_KEY;
+ else
+ {
+ ShmKey = read_key_file(ShmKeyFile, ShmKey);
+ keyselect = false;
+ if (ShmKey == SEL_SHM_KEY)
+ goto error;
+ }
+ }
+#endif /* _FFR_SELECT_SHM */
+ for (;;)
+ {
+ /* XXX: maybe allow read access for group? */
+ Pshm = sm_shmstart(ShmKey, shms, SHM_R|SHM_W, &ShmId,
+ owner);
+ save_errno = errno;
+ if (Pshm != NULL || save_errno != EEXIST)
+ break;
+ if (++count >= 3)
+ {
+#if _FFR_SELECT_SHM
+ if (keyselect)
+ {
+ ++ShmKey;
+
+ /* back where we started? */
+ if (ShmKey == SEL_SHM_KEY)
+ break;
+ continue;
+ }
+#endif /* _FFR_SELECT_SHM */
+ break;
+ }
+#if _FFR_SELECT_SHM
+ /* only sleep if we are at the first key */
+ if (!keyselect || ShmKey == SEL_SHM_KEY)
+#endif /* _FFR_SELECT_SHM */
+ sleep(count);
+ }
+ if (Pshm != NULL)
+ {
+ int *p;
+
+#if _FFR_SELECT_SHM
+ if (keyselect)
+ (void) write_key_file(ShmKeyFile, (long) ShmKey);
+#endif /* _FFR_SELECT_SHM */
+ p = (int *) Pshm;
+ if (owner)
+ {
+ *p = (int) shms;
+ *((pid_t *) SHM_OFF_PID(Pshm)) = CurrentPid;
+ p = (int *) SHM_OFF_TAG(Pshm);
+ *p = hash;
+ }
+ else
+ {
+ if (*p != (int) shms)
+ {
+ save_errno = EINVAL;
+ cleanup_shm(false);
+ goto error;
+ }
+ p = (int *) SHM_OFF_TAG(Pshm);
+ if (*p != (int) hash)
+ {
+ save_errno = EINVAL;
+ cleanup_shm(false);
+ goto error;
+ }
+
+ /*
+ ** XXX how to check the pid?
+ ** Read it from the pid-file? That does
+ ** not need to exist.
+ ** We could disable shm if we can't confirm
+ ** that it is the right one.
+ */
+ }
+
+ PtrFileSys = (FILESYS *) OFF_FILE_SYS(Pshm);
+ PNumFileSys = (int *) OFF_NUM_FILE_SYS(Pshm);
+ QShm = (QUEUE_SHM_T *) OFF_QUEUE_SHM(Pshm);
+ PRSATmpCnt = (int *) OFF_RSA_TMP_CNT(Pshm);
+ *PRSATmpCnt = 0;
+ if (owner)
+ {
+ /* initialize values in shared memory */
+ NumFileSys = 0;
+ for (i = 0; i < qn; i++)
+ QShm[i].qs_entries = -1;
+ }
+ return;
+ }
+ error:
+ if (LogLevel > (owner ? 8 : 11))
+ {
+ sm_syslog(owner ? LOG_ERR : LOG_NOTICE, NOQID,
+ "can't %s shared memory, key=%ld: %s",
+ owner ? "initialize" : "attach to",
+ (long) ShmKey, sm_errstring(save_errno));
+ }
+ }
+}
+#endif /* SM_CONF_SHM */
+
+/*
+** SETUP_QUEUES -- setup all queue groups
+**
+** Parameters:
+** owner -- owner of shared memory.
+**
+** Returns:
+** none.
+**
+#if SM_CONF_SHM
+** Side Effects:
+** attaches shared memory.
+#endif * SM_CONF_SHM *
+*/
+
+void
+setup_queues(owner)
+ bool owner;
+{
+ int i, qn, len;
+ unsigned int hashval;
+ time_t now;
+ char basedir[MAXPATHLEN];
+ struct stat st;
+
+ /*
+ ** Determine basedir for all queue directories.
+ ** All queue directories must be (first level) subdirectories
+ ** of the basedir. The basedir is the QueueDir
+ ** without wildcards, but with trailing /
+ */
+
+ hashval = 0;
+ errno = 0;
+ len = sm_strlcpy(basedir, QueueDir, sizeof basedir);
+ if (len >= sizeof basedir)
+ {
+ syserr("QueueDirectory: path too long: %d, max %d",
+ len, (int) sizeof basedir);
+ ExitStat = EX_CONFIG;
+ return;
+ }
+ SM_ASSERT(len > 0);
+ if (basedir[len - 1] == '*')
+ {
+ char *cp;
+
+ cp = SM_LAST_DIR_DELIM(basedir);
+ if (cp == NULL)
+ {
+ syserr("QueueDirectory: can not wildcard relative path \"%s\"",
+ QueueDir);
+ if (tTd(41, 2))
+ sm_dprintf("setup_queues: \"%s\": Can not wildcard relative path.\n",
+ QueueDir);
+ ExitStat = EX_CONFIG;
+ return;
+ }
+
+ /* cut off wildcard pattern */
+ *++cp = '\0';
+ len = cp - basedir;
+ }
+ else if (!SM_IS_DIR_DELIM(basedir[len - 1]))
+ {
+ /* append trailing slash since it is a directory */
+ basedir[len] = '/';
+ basedir[++len] = '\0';
+ }
+
+ /* len counts up to the last directory delimiter */
+ SM_ASSERT(basedir[len - 1] == '/');
+
+ if (chdir(basedir) < 0)
+ {
+ int save_errno = errno;
+
+ syserr("can not chdir(%s)", basedir);
+ if (save_errno == EACCES)
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "Program mode requires special privileges, e.g., root or TrustedUser.\n");
+ if (tTd(41, 2))
+ sm_dprintf("setup_queues: \"%s\": %s\n",
+ basedir, sm_errstring(errno));
+ ExitStat = EX_CONFIG;
+ return;
+ }
+#if SM_CONF_SHM
+ hashval = hash_q(basedir, hashval);
+#endif /* SM_CONF_SHM */
+
+ /* initialize for queue runs */
+ DoQueueRun = false;
+ now = curtime();
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ Queue[i]->qg_nextrun = now;
+
+
+ if (UseMSP && OpMode != MD_TEST)
+ {
+ long sff = SFF_CREAT;
+
+ if (stat(".", &st) < 0)
+ {
+ syserr("can not stat(%s)", basedir);
+ if (tTd(41, 2))
+ sm_dprintf("setup_queues: \"%s\": %s\n",
+ basedir, sm_errstring(errno));
+ ExitStat = EX_CONFIG;
+ return;
+ }
+ if (RunAsUid == 0)
+ sff |= SFF_ROOTOK;
+
+ /*
+ ** Check queue directory permissions.
+ ** Can we write to a group writable queue directory?
+ */
+
+ if (bitset(S_IWGRP, QueueFileMode) &&
+ bitset(S_IWGRP, st.st_mode) &&
+ safefile(" ", RunAsUid, RunAsGid, RunAsUserName, sff,
+ QueueFileMode, NULL) != 0)
+ {
+ syserr("can not write to queue directory %s (RunAsGid=%d, required=%d)",
+ basedir, (int) RunAsGid, (int) st.st_gid);
+ }
+ if (bitset(S_IWOTH|S_IXOTH, st.st_mode))
+ {
+#if _FFR_MSP_PARANOIA
+ syserr("dangerous permissions=%o on queue directory %s",
+ (int) st.st_mode, basedir);
+#else /* _FFR_MSP_PARANOIA */
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, NOQID,
+ "dangerous permissions=%o on queue directory %s",
+ (int) st.st_mode, basedir);
+#endif /* _FFR_MSP_PARANOIA */
+ }
+#if _FFR_MSP_PARANOIA
+ if (NumQueue > 1)
+ syserr("can not use multiple queues for MSP");
+#endif /* _FFR_MSP_PARANOIA */
+ }
+
+ /* initial number of queue directories */
+ qn = 0;
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ qn = multiqueue_cache(basedir, len, Queue[i], qn, &hashval);
+
+#if SM_CONF_SHM
+ init_shm(qn, owner, hashval);
+ i = filesys_setup(owner || ShmId == SM_SHM_NO_ID);
+ if (i == FSF_NOT_FOUND)
+ {
+ /*
+ ** We didn't get the right filesystem data
+ ** This may happen if we don't have the right shared memory.
+ ** So let's do this without shared memory.
+ */
+
+ SM_ASSERT(!owner);
+ cleanup_shm(false); /* release shared memory */
+ i = filesys_setup(false);
+ if (i < 0)
+ syserr("filesys_setup failed twice, result=%d", i);
+ else if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, NOQID,
+ "shared memory does not contain expected data, ignored");
+ }
+#else /* SM_CONF_SHM */
+ i = filesys_setup(true);
+#endif /* SM_CONF_SHM */
+ if (i < 0)
+ ExitStat = EX_CONFIG;
+}
+
+#if SM_CONF_SHM
+/*
+** CLEANUP_SHM -- do some cleanup work for shared memory etc
+**
+** Parameters:
+** owner -- owner of shared memory?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** detaches shared memory.
+*/
+
+void
+cleanup_shm(owner)
+ bool owner;
+{
+ if (ShmId != SM_SHM_NO_ID)
+ {
+ if (sm_shmstop(Pshm, ShmId, owner) < 0 && LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "sm_shmstop failed=%s",
+ sm_errstring(errno));
+ Pshm = NULL;
+ ShmId = SM_SHM_NO_ID;
+ }
+}
+#endif /* SM_CONF_SHM */
+
+/*
+** CLEANUP_QUEUES -- do some cleanup work for queues
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+*/
+
+void
+cleanup_queues()
+{
+ sync_queue_time();
+}
+/*
+** SET_DEF_QUEUEVAL -- set default values for a queue group.
+**
+** Parameters:
+** qg -- queue group
+** all -- set all values (true for default group)?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets default values for the queue group.
+*/
+
+void
+set_def_queueval(qg, all)
+ QUEUEGRP *qg;
+ bool all;
+{
+ if (bitnset(QD_DEFINED, qg->qg_flags))
+ return;
+ if (all)
+ qg->qg_qdir = QueueDir;
+#if _FFR_QUEUE_GROUP_SORTORDER
+ qg->qg_sortorder = QueueSortOrder;
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+ qg->qg_maxqrun = all ? MaxRunnersPerQueue : -1;
+ qg->qg_nice = NiceQueueRun;
+}
+/*
+** MAKEQUEUE -- define a new queue.
+**
+** Parameters:
+** line -- description of queue. This is in labeled fields.
+** The fields are:
+** F -- the flags associated with the queue
+** I -- the interval between running the queue
+** J -- the maximum # of jobs in work list
+** [M -- the maximum # of jobs in a queue run]
+** N -- the niceness at which to run
+** P -- the path to the queue
+** S -- the queue sorting order
+** R -- number of parallel queue runners
+** r -- max recipients per envelope
+** The first word is the canonical name of the queue.
+** qdef -- this is a 'Q' definition from .cf
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** enters the queue into the queue table.
+*/
+
+void
+makequeue(line, qdef)
+ char *line;
+ bool qdef;
+{
+ register char *p;
+ register QUEUEGRP *qg;
+ register STAB *s;
+ int i;
+ char fcode;
+
+ /* allocate a queue and set up defaults */
+ qg = (QUEUEGRP *) xalloc(sizeof *qg);
+ memset((char *) qg, '\0', sizeof *qg);
+
+ if (line[0] == '\0')
+ {
+ syserr("name required for queue");
+ return;
+ }
+
+ /* collect the queue name */
+ for (p = line;
+ *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p));
+ p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ qg->qg_name = newstr(line);
+
+ /* set default values, can be overridden below */
+ set_def_queueval(qg, false);
+
+ /* now scan through and assign info from the fields */
+ while (*p != '\0')
+ {
+ auto char *delimptr;
+
+ while (*p != '\0' &&
+ (*p == ',' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ /* p now points to field code */
+ fcode = *p;
+ while (*p != '\0' && *p != '=' && *p != ',')
+ p++;
+ if (*p++ != '=')
+ {
+ syserr("queue %s: `=' expected", qg->qg_name);
+ return;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* p now points to the field body */
+ p = munchstring(p, &delimptr, ',');
+
+ /* install the field into the queue struct */
+ switch (fcode)
+ {
+ case 'P': /* pathname */
+ if (*p == '\0')
+ syserr("queue %s: empty path name",
+ qg->qg_name);
+ else
+ qg->qg_qdir = newstr(p);
+ break;
+
+ case 'F': /* flags */
+ for (; *p != '\0'; p++)
+ if (!(isascii(*p) && isspace(*p)))
+ setbitn(*p, qg->qg_flags);
+ break;
+
+ /*
+ ** Do we need two intervals here:
+ ** One for persistent queue runners,
+ ** one for "normal" queue runs?
+ */
+
+ case 'I': /* interval between running the queue */
+ qg->qg_queueintvl = convtime(p, 'm');
+ break;
+
+ case 'N': /* run niceness */
+ qg->qg_nice = atoi(p);
+ break;
+
+ case 'R': /* maximum # of runners for the group */
+ i = atoi(p);
+
+ /* can't have more runners than allowed total */
+ if (MaxQueueChildren > 0 && i > MaxQueueChildren)
+ {
+ qg->qg_maxqrun = MaxQueueChildren;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Q=%s: R=%d exceeds MaxQueueChildren=%d, set to MaxQueueChildren\n",
+ qg->qg_name, i,
+ MaxQueueChildren);
+ }
+ else
+ qg->qg_maxqrun = i;
+ break;
+
+ case 'J': /* maximum # of jobs in work list */
+ qg->qg_maxlist = atoi(p);
+ break;
+
+ case 'r': /* max recipients per envelope */
+ qg->qg_maxrcpt = atoi(p);
+ break;
+
+#if _FFR_QUEUE_GROUP_SORTORDER
+ case 'S': /* queue sorting order */
+ switch (*p)
+ {
+ case 'h': /* Host first */
+ case 'H':
+ qg->qg_sortorder = QSO_BYHOST;
+ break;
+
+ case 'p': /* Priority order */
+ case 'P':
+ qg->qg_sortorder = QSO_BYPRIORITY;
+ break;
+
+ case 't': /* Submission time */
+ case 'T':
+ qg->qg_sortorder = QSO_BYTIME;
+ break;
+
+ case 'f': /* File name */
+ case 'F':
+ qg->qg_sortorder = QSO_BYFILENAME;
+ break;
+
+ case 'm': /* Modification time */
+ case 'M':
+ qg->qg_sortorder = QSO_BYMODTIME;
+ break;
+
+ case 'r': /* Random */
+ case 'R':
+ qg->qg_sortorder = QSO_RANDOM;
+ break;
+
+# if _FFR_RHS
+ case 's': /* Shuffled host name */
+ case 'S':
+ qg->qg_sortorder = QSO_BYSHUFFLE;
+ break;
+# endif /* _FFR_RHS */
+
+ default:
+ syserr("Invalid queue sort order \"%s\"", p);
+ }
+ break;
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+
+ default:
+ syserr("Q%s: unknown queue equate %c=",
+ qg->qg_name, fcode);
+ break;
+ }
+
+ p = delimptr;
+ }
+
+#if !HASNICE
+ if (qg->qg_nice != NiceQueueRun)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Q%s: Warning: N= set on system that doesn't support nice()\n",
+ qg->qg_name);
+ }
+#endif /* !HASNICE */
+
+ /* do some rationality checking */
+ if (NumQueue >= MAXQUEUEGROUPS)
+ {
+ syserr("too many queue groups defined (%d max)",
+ MAXQUEUEGROUPS);
+ return;
+ }
+
+ if (qg->qg_qdir == NULL)
+ {
+ if (QueueDir == NULL || *QueueDir == '\0')
+ {
+ syserr("QueueDir must be defined before queue groups");
+ return;
+ }
+ qg->qg_qdir = newstr(QueueDir);
+ }
+
+ if (qg->qg_maxqrun > 1 && !bitnset(QD_FORK, qg->qg_flags))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Q=%s: R=%d: multiple queue runners specified\n\tbut flag '%c' is not set\n",
+ qg->qg_name, qg->qg_maxqrun, QD_FORK);
+ }
+
+ /* enter the queue into the symbol table */
+ if (tTd(37, 8))
+ sm_syslog(LOG_INFO, NOQID,
+ "Adding %s to stab, path: %s", qg->qg_name,
+ qg->qg_qdir);
+ s = stab(qg->qg_name, ST_QUEUE, ST_ENTER);
+ if (s->s_quegrp != NULL)
+ {
+ i = s->s_quegrp->qg_index;
+
+ /* XXX what about the pointers inside this struct? */
+ sm_free(s->s_quegrp); /* XXX */
+ }
+ else
+ i = NumQueue++;
+ Queue[i] = s->s_quegrp = qg;
+ qg->qg_index = i;
+
+ /* set default value for max queue runners */
+ if (qg->qg_maxqrun < 0)
+ {
+ if (MaxRunnersPerQueue > 0)
+ qg->qg_maxqrun = MaxRunnersPerQueue;
+ else
+ qg->qg_maxqrun = 1;
+ }
+ if (qdef)
+ setbitn(QD_DEFINED, qg->qg_flags);
+}
+#if 0
+/*
+** HASHFQN -- calculate a hash value for a fully qualified host name
+**
+** Arguments:
+** fqn -- an all lower-case host.domain string
+** buckets -- the number of buckets (queue directories)
+**
+** Returns:
+** a bucket number (signed integer)
+** -1 on error
+**
+** Contributed by Exactis.com, Inc.
+*/
+
+int
+hashfqn(fqn, buckets)
+ register char *fqn;
+ int buckets;
+{
+ register char *p;
+ register int h = 0, hash, cnt;
+
+ if (fqn == NULL)
+ return -1;
+
+ /*
+ ** A variation on the gdb hash
+ ** This is the best as of Feb 19, 1996 --bcx
+ */
+
+ p = fqn;
+ h = 0x238F13AF * strlen(p);
+ for (cnt = 0; *p != 0; ++p, cnt++)
+ {
+ h = (h + (*p << (cnt * 5 % 24))) & 0x7FFFFFFF;
+ }
+ h = (1103515243 * h + 12345) & 0x7FFFFFFF;
+ if (buckets < 2)
+ hash = 0;
+ else
+ hash = (h % buckets);
+
+ return hash;
+}
+#endif /* 0 */
+
+#if _FFR_QUEUEDELAY
+/*
+** QUEUEDELAY -- compute queue delay time
+**
+** Parameters:
+** e -- the envelope to queue up.
+**
+** Returns:
+** queue delay time
+**
+** Side Effects:
+** may change e_queuedelay
+*/
+
+static time_t
+queuedelay(e)
+ ENVELOPE *e;
+{
+ time_t qd;
+
+ if (e->e_queuealg == QD_EXP)
+ {
+ if (e->e_queuedelay == 0)
+ e->e_queuedelay = QueueInitDelay;
+ else
+ {
+ e->e_queuedelay *= 2;
+ if (e->e_queuedelay > QueueMaxDelay)
+ e->e_queuedelay = QueueMaxDelay;
+ }
+ qd = e->e_queuedelay;
+ }
+ else
+ qd = MinQueueAge;
+ return qd;
+}
+#endif /* _FFR_QUEUEDELAY */
+
+/*
+** A structure for sorting Queue according to maxqrun without
+** screwing up Queue itself.
+*/
+
+struct sortqgrp
+{
+ int sg_idx; /* original index */
+ int sg_maxqrun; /* max queue runners */
+};
+typedef struct sortqgrp SORTQGRP_T;
+static int cmpidx __P((const void *, const void *));
+
+static int
+cmpidx(a, b)
+ const void *a;
+ const void *b;
+{
+ /* The sort is highest to lowest, so the comparison is reversed */
+ if (((SORTQGRP_T *)a)->sg_maxqrun < ((SORTQGRP_T *)b)->sg_maxqrun)
+ return 1;
+ else if (((SORTQGRP_T *)a)->sg_maxqrun > ((SORTQGRP_T *)b)->sg_maxqrun)
+ return -1;
+ else
+ return 0;
+}
+
+/*
+** MAKEWORKGROUP -- balance queue groups into work groups per MaxQueueChildren
+**
+** Take the now defined queue groups and assign them to work groups.
+** This is done to balance out the number of concurrently active
+** queue runners such that MaxQueueChildren is not exceeded. This may
+** result in more than one queue group per work group. In such a case
+** the number of running queue groups in that work group will have no
+** more than the work group maximum number of runners (a "fair" portion
+** of MaxQueueRunners). All queue groups within a work group will get a
+** chance at running.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** nothing.
+**
+** Side Effects:
+** Sets up WorkGrp structure.
+*/
+
+void
+makeworkgroups()
+{
+ int i, j, total_runners = 0;
+ int dir;
+ SORTQGRP_T si[MAXQUEUEGROUPS + 1];
+
+ if (NumQueue == 1 && strcmp(Queue[0]->qg_name, "mqueue") == 0)
+ {
+ /*
+ ** There is only the "mqueue" queue group (a default)
+ ** containing all of the queues. We want to provide to
+ ** this queue group the maximum allowable queue runners.
+ ** To match older behavior (8.10/8.11) we'll try for
+ ** 1 runner per queue capping it at MaxQueueChildren.
+ ** So if there are N queues, then there will be N runners
+ ** for the "mqueue" queue group (where N is kept less than
+ ** MaxQueueChildren).
+ */
+
+ NumWorkGroups = 1;
+ WorkGrp[0].wg_numqgrp = 1;
+ WorkGrp[0].wg_qgs = (QUEUEGRP **) xalloc(sizeof(QUEUEGRP *));
+ WorkGrp[0].wg_qgs[0] = Queue[0];
+ if (MaxQueueChildren > 0 &&
+ Queue[0]->qg_numqueues > MaxQueueChildren)
+ WorkGrp[0].wg_runners = MaxQueueChildren;
+ else
+ WorkGrp[0].wg_runners = Queue[0]->qg_numqueues;
+
+ Queue[0]->qg_wgrp = 0;
+
+ /* can't have more runners than allowed total */
+ if (MaxQueueChildren > 0 &&
+ Queue[0]->qg_maxqrun > MaxQueueChildren)
+ Queue[0]->qg_maxqrun = MaxQueueChildren;
+ WorkGrp[0].wg_maxact = Queue[0]->qg_maxqrun;
+ WorkGrp[0].wg_lowqintvl = Queue[0]->qg_queueintvl;
+ return;
+ }
+
+ for (i = 0; i < NumQueue; i++)
+ {
+ si[i].sg_maxqrun = Queue[i]->qg_maxqrun;
+ si[i].sg_idx = i;
+ }
+ qsort(si, NumQueue, sizeof(si[0]), cmpidx);
+
+ NumWorkGroups = 0;
+ for (i = 0; i < NumQueue; i++)
+ {
+ total_runners += si[i].sg_maxqrun;
+ if (MaxQueueChildren <= 0 || total_runners <= MaxQueueChildren)
+ NumWorkGroups++;
+ else
+ break;
+ }
+
+ if (NumWorkGroups < 1)
+ NumWorkGroups = 1; /* gotta have one at least */
+ else if (NumWorkGroups > MAXWORKGROUPS)
+ NumWorkGroups = MAXWORKGROUPS; /* the limit */
+
+ /*
+ ** We now know the number of work groups to pack the queue groups
+ ** into. The queue groups in 'Queue' are sorted from highest
+ ** to lowest for the number of runners per queue group.
+ ** We put the queue groups with the largest number of runners
+ ** into work groups first. Then the smaller ones are fitted in
+ ** where it looks best.
+ */
+
+ j = 0;
+ dir = 1;
+ for (i = 0; i < NumQueue; i++)
+ {
+ /* a to-and-fro packing scheme, continue from last position */
+ if (j >= NumWorkGroups)
+ {
+ dir = -1;
+ j = NumWorkGroups - 1;
+ }
+ else if (j < 0)
+ {
+ j = 0;
+ dir = 1;
+ }
+
+ if (WorkGrp[j].wg_qgs == NULL)
+ WorkGrp[j].wg_qgs = (QUEUEGRP **)sm_malloc(sizeof(QUEUEGRP *) *
+ (WorkGrp[j].wg_numqgrp + 1));
+ else
+ WorkGrp[j].wg_qgs = (QUEUEGRP **)sm_realloc(WorkGrp[j].wg_qgs,
+ sizeof(QUEUEGRP *) *
+ (WorkGrp[j].wg_numqgrp + 1));
+ if (WorkGrp[j].wg_qgs == NULL)
+ {
+ syserr("!cannot allocate memory for work queues, need %d bytes",
+ (int) (sizeof(QUEUEGRP *) *
+ (WorkGrp[j].wg_numqgrp + 1)));
+ }
+
+ WorkGrp[j].wg_qgs[WorkGrp[j].wg_numqgrp] = Queue[si[i].sg_idx];
+ WorkGrp[j].wg_numqgrp++;
+ WorkGrp[j].wg_runners += Queue[i]->qg_maxqrun;
+ Queue[si[i].sg_idx]->qg_wgrp = j;
+
+ if (WorkGrp[j].wg_maxact == 0)
+ {
+ /* can't have more runners than allowed total */
+ if (MaxQueueChildren > 0 &&
+ Queue[i]->qg_maxqrun > MaxQueueChildren)
+ Queue[i]->qg_maxqrun = MaxQueueChildren;
+ WorkGrp[j].wg_maxact = Queue[i]->qg_maxqrun;
+ }
+
+ /*
+ ** XXX: must wg_lowqintvl be the GCD?
+ ** qg1: 2m, qg2: 3m, minimum: 2m, when do queue runs for
+ ** qg2 occur?
+ */
+
+ /* keep track of the lowest interval for a persistent runner */
+ if (Queue[si[i].sg_idx]->qg_queueintvl > 0 &&
+ WorkGrp[j].wg_lowqintvl < Queue[si[i].sg_idx]->qg_queueintvl)
+ WorkGrp[j].wg_lowqintvl = Queue[si[i].sg_idx]->qg_queueintvl;
+ j += dir;
+ }
+ if (tTd(41, 9))
+ {
+ for (i = 0; i < NumWorkGroups; i++)
+ {
+ sm_dprintf("Workgroup[%d]=", i);
+ for (j = 0; j < WorkGrp[i].wg_numqgrp; j++)
+ {
+ sm_dprintf("%s, ",
+ WorkGrp[i].wg_qgs[j]->qg_name);
+ }
+ sm_dprintf("\n");
+ }
+ }
+}
+
+/*
+** DUP_DF -- duplicate envelope data file
+**
+** Copy the data file from the 'old' envelope to the 'new' envelope
+** in the most efficient way possible.
+**
+** Create a hard link from the 'old' data file to the 'new' data file.
+** If the old and new queue directories are on different file systems,
+** then the new data file link is created in the old queue directory,
+** and the new queue file will contain a 'd' record pointing to the
+** directory containing the new data file.
+**
+** Parameters:
+** old -- old envelope.
+** new -- new envelope.
+**
+** Results:
+** Returns true on success, false on failure.
+**
+** Side Effects:
+** On success, the new data file is created.
+** On fatal failure, EF_FATALERRS is set in old->e_flags.
+*/
+
+static bool dup_df __P((ENVELOPE *, ENVELOPE *));
+
+static bool
+dup_df(old, new)
+ ENVELOPE *old;
+ ENVELOPE *new;
+{
+ int ofs, nfs, r;
+ char opath[MAXPATHLEN];
+ char npath[MAXPATHLEN];
+
+ if (!bitset(EF_HAS_DF, old->e_flags))
+ {
+ /*
+ ** this can happen if: SuperSafe != True
+ ** and a bounce mail is sent that is split.
+ */
+
+ queueup(old, false, true);
+ }
+ SM_REQUIRE(ISVALIDQGRP(old->e_qgrp) && ISVALIDQDIR(old->e_qdir));
+ SM_REQUIRE(ISVALIDQGRP(new->e_qgrp) && ISVALIDQDIR(new->e_qdir));
+
+ (void) sm_strlcpy(opath, queuename(old, DATAFL_LETTER), sizeof opath);
+ (void) sm_strlcpy(npath, queuename(new, DATAFL_LETTER), sizeof npath);
+
+ if (old->e_dfp != NULL)
+ {
+ r = sm_io_setinfo(old->e_dfp, SM_BF_COMMIT, NULL);
+ if (r < 0 && errno != EINVAL)
+ {
+ syserr("@can't commit %s", opath);
+ old->e_flags |= EF_FATALERRS;
+ return false;
+ }
+ }
+
+ /*
+ ** Attempt to create a hard link, if we think both old and new
+ ** are on the same file system, otherwise copy the file.
+ **
+ ** Don't waste time attempting a hard link unless old and new
+ ** are on the same file system.
+ */
+
+ ofs = Queue[old->e_qgrp]->qg_qpaths[old->e_qdir].qp_fsysidx;
+ nfs = Queue[new->e_qgrp]->qg_qpaths[new->e_qdir].qp_fsysidx;
+ if (FILE_SYS_DEV(ofs) == FILE_SYS_DEV(nfs))
+ {
+ if (link(opath, npath) == 0)
+ {
+ new->e_flags |= EF_HAS_DF;
+ SYNC_DIR(npath, true);
+ return true;
+ }
+ goto error;
+ }
+
+ /*
+ ** Can't link across queue directories, so try to create a hard
+ ** link in the same queue directory as the old df file.
+ ** The qf file will refer to the new df file using a 'd' record.
+ */
+
+ new->e_dfqgrp = old->e_dfqgrp;
+ new->e_dfqdir = old->e_dfqdir;
+ (void) sm_strlcpy(npath, queuename(new, DATAFL_LETTER), sizeof npath);
+ if (link(opath, npath) == 0)
+ {
+ new->e_flags |= EF_HAS_DF;
+ SYNC_DIR(npath, true);
+ return true;
+ }
+
+ error:
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, old->e_id,
+ "dup_df: can't link %s to %s, error=%s, envelope splitting failed",
+ opath, npath, sm_errstring(errno));
+ return false;
+}
+
+/*
+** SPLIT_ENV -- Allocate a new envelope based on a given envelope.
+**
+** Parameters:
+** e -- envelope.
+** sendqueue -- sendqueue for new envelope.
+** qgrp -- index of queue group.
+** qdir -- queue directory.
+**
+** Results:
+** new envelope.
+**
+*/
+
+static ENVELOPE *split_env __P((ENVELOPE *, ADDRESS *, int, int));
+
+static ENVELOPE *
+split_env(e, sendqueue, qgrp, qdir)
+ ENVELOPE *e;
+ ADDRESS *sendqueue;
+ int qgrp;
+ int qdir;
+{
+ ENVELOPE *ee;
+
+ ee = (ENVELOPE *) sm_rpool_malloc_x(e->e_rpool, sizeof *ee);
+ STRUCTCOPY(*e, *ee);
+ ee->e_message = NULL; /* XXX use original message? */
+ ee->e_id = NULL;
+ assign_queueid(ee);
+ ee->e_sendqueue = sendqueue;
+ ee->e_flags &= ~(EF_INQUEUE|EF_CLRQUEUE|EF_FATALERRS
+ |EF_SENDRECEIPT|EF_RET_PARAM|EF_HAS_DF);
+ ee->e_flags |= EF_NORECEIPT; /* XXX really? */
+ ee->e_from.q_state = QS_SENDER;
+ ee->e_dfp = NULL;
+ ee->e_lockfp = NULL;
+ if (e->e_xfp != NULL)
+ ee->e_xfp = sm_io_dup(e->e_xfp);
+
+ /* failed to dup e->e_xfp, start a new transcript */
+ if (ee->e_xfp == NULL)
+ openxscript(ee);
+
+ ee->e_qgrp = ee->e_dfqgrp = qgrp;
+ ee->e_qdir = ee->e_dfqdir = qdir;
+ ee->e_errormode = EM_MAIL;
+ ee->e_statmsg = NULL;
+#if _FFR_QUARANTINE
+ if (e->e_quarmsg != NULL)
+ ee->e_quarmsg = sm_rpool_strdup_x(ee->e_rpool,
+ e->e_quarmsg);
+#endif /* _FFR_QUARANTINE */
+
+ /*
+ ** XXX Not sure if this copying is necessary.
+ ** sendall() does this copying, but I (dm) don't know if that is
+ ** because of the storage management discipline we were using
+ ** before rpools were introduced, or if it is because these lists
+ ** can be modified later.
+ */
+
+ ee->e_header = copyheader(e->e_header, ee->e_rpool);
+ ee->e_errorqueue = copyqueue(e->e_errorqueue, ee->e_rpool);
+
+ return ee;
+}
+
+/* return values from split functions, check also below! */
+#define SM_SPLIT_FAIL (0)
+#define SM_SPLIT_NONE (1)
+#define SM_SPLIT_NEW(n) (1 + (n))
+
+/*
+** SPLIT_ACROSS_QUEUE_GROUPS
+**
+** This function splits an envelope across multiple queue groups
+** based on the queue group of each recipient.
+**
+** Parameters:
+** e -- envelope.
+**
+** Results:
+** SM_SPLIT_FAIL on failure
+** SM_SPLIT_NONE if no splitting occurred,
+** or 1 + the number of additional envelopes created.
+**
+** Side Effects:
+** On success, e->e_sibling points to a list of zero or more
+** additional envelopes, and the associated data files exist
+** on disk. But the queue files are not created.
+**
+** On failure, e->e_sibling is not changed.
+** The order of recipients in e->e_sendqueue is permuted.
+** Abandoned data files for additional envelopes that failed
+** to be created may exist on disk.
+*/
+
+static int q_qgrp_compare __P((const void *, const void *));
+static int e_filesys_compare __P((const void *, const void *));
+
+static int
+q_qgrp_compare(p1, p2)
+ const void *p1;
+ const void *p2;
+{
+ ADDRESS **pq1 = (ADDRESS **) p1;
+ ADDRESS **pq2 = (ADDRESS **) p2;
+
+ return (*pq1)->q_qgrp - (*pq2)->q_qgrp;
+}
+
+static int
+e_filesys_compare(p1, p2)
+ const void *p1;
+ const void *p2;
+{
+ ENVELOPE **pe1 = (ENVELOPE **) p1;
+ ENVELOPE **pe2 = (ENVELOPE **) p2;
+ int fs1, fs2;
+
+ fs1 = Queue[(*pe1)->e_qgrp]->qg_qpaths[(*pe1)->e_qdir].qp_fsysidx;
+ fs2 = Queue[(*pe2)->e_qgrp]->qg_qpaths[(*pe2)->e_qdir].qp_fsysidx;
+ if (FILE_SYS_DEV(fs1) < FILE_SYS_DEV(fs2))
+ return -1;
+ if (FILE_SYS_DEV(fs1) > FILE_SYS_DEV(fs2))
+ return 1;
+ return 0;
+}
+
+static int
+split_across_queue_groups(e)
+ ENVELOPE *e;
+{
+ int naddrs, nsplits, i;
+ bool changed;
+ char **pvp;
+ ADDRESS *q, **addrs;
+ ENVELOPE *ee, *es;
+ ENVELOPE *splits[MAXQUEUEGROUPS];
+ char pvpbuf[PSBUFSIZE];
+
+ SM_REQUIRE(ISVALIDQGRP(e->e_qgrp));
+
+ /* Count addresses and assign queue groups. */
+ naddrs = 0;
+ changed = false;
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ ++naddrs;
+
+ /* bad addresses and those already sent stay put */
+ if (QS_IS_BADADDR(q->q_state) ||
+ QS_IS_SENT(q->q_state))
+ q->q_qgrp = e->e_qgrp;
+ else if (!ISVALIDQGRP(q->q_qgrp))
+ {
+ /* call ruleset which should return a queue group */
+ i = rscap(RS_QUEUEGROUP, q->q_user, NULL, e, &pvp,
+ pvpbuf, sizeof(pvpbuf));
+ if (i == EX_OK &&
+ pvp != NULL && pvp[0] != NULL &&
+ (pvp[0][0] & 0377) == CANONNET &&
+ pvp[1] != NULL && pvp[1][0] != '\0')
+ {
+ i = name2qid(pvp[1]);
+ if (ISVALIDQGRP(i))
+ {
+ q->q_qgrp = i;
+ changed = true;
+ if (tTd(20, 4))
+ sm_syslog(LOG_INFO, NOQID,
+ "queue group name %s -> %d",
+ pvp[1], i);
+ continue;
+ }
+ else if (LogLevel > 10)
+ sm_syslog(LOG_INFO, NOQID,
+ "can't find queue group name %s, selection ignored",
+ pvp[1]);
+ }
+ if (q->q_mailer != NULL &&
+ ISVALIDQGRP(q->q_mailer->m_qgrp))
+ {
+ changed = true;
+ q->q_qgrp = q->q_mailer->m_qgrp;
+ }
+ else if (ISVALIDQGRP(e->e_qgrp))
+ q->q_qgrp = e->e_qgrp;
+ else
+ q->q_qgrp = 0;
+ }
+ }
+
+ /* only one address? nothing to split. */
+ if (naddrs <= 1 && !changed)
+ return SM_SPLIT_NONE;
+
+ /* sort the addresses by queue group */
+ addrs = sm_rpool_malloc_x(e->e_rpool, naddrs * sizeof(ADDRESS *));
+ for (i = 0, q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ addrs[i++] = q;
+ }
+ qsort(addrs, naddrs, sizeof(ADDRESS *), q_qgrp_compare);
+
+ /* split into multiple envelopes, by queue group */
+ nsplits = 0;
+ es = NULL;
+ e->e_sendqueue = NULL;
+ for (i = 0; i < naddrs; ++i)
+ {
+ if (i == naddrs - 1 || addrs[i]->q_qgrp != addrs[i + 1]->q_qgrp)
+ addrs[i]->q_next = NULL;
+ else
+ addrs[i]->q_next = addrs[i + 1];
+
+ /* same queue group as original envelope? */
+ if (addrs[i]->q_qgrp == e->e_qgrp)
+ {
+ if (e->e_sendqueue == NULL)
+ e->e_sendqueue = addrs[i];
+ continue;
+ }
+
+ /* different queue group than original envelope */
+ if (es == NULL || addrs[i]->q_qgrp != es->e_qgrp)
+ {
+ ee = split_env(e, addrs[i], addrs[i]->q_qgrp, NOQDIR);
+ es = ee;
+ splits[nsplits++] = ee;
+ }
+ }
+
+ /* no splits? return right now. */
+ if (nsplits <= 0)
+ return SM_SPLIT_NONE;
+
+ /* assign a queue directory to each additional envelope */
+ for (i = 0; i < nsplits; ++i)
+ {
+ es = splits[i];
+#if 0
+ es->e_qdir = pickqdir(Queue[es->e_qgrp], es->e_msgsize, es);
+#endif /* 0 */
+ if (!setnewqueue(es))
+ goto failure;
+ }
+
+ /* sort the additional envelopes by queue file system */
+ qsort(splits, nsplits, sizeof(ENVELOPE *), e_filesys_compare);
+
+ /* create data files for each additional envelope */
+ if (!dup_df(e, splits[0]))
+ {
+ i = 0;
+ goto failure;
+ }
+ for (i = 1; i < nsplits; ++i)
+ {
+ /* copy or link to the previous data file */
+ if (!dup_df(splits[i - 1], splits[i]))
+ goto failure;
+ }
+
+ /* success: prepend the new envelopes to the e->e_sibling list */
+ for (i = 0; i < nsplits; ++i)
+ {
+ es = splits[i];
+ es->e_sibling = e->e_sibling;
+ e->e_sibling = es;
+ }
+ return SM_SPLIT_NEW(nsplits);
+
+ /* failure: clean up */
+ failure:
+ if (i > 0)
+ {
+ int j;
+
+ for (j = 0; j < i; j++)
+ (void) unlink(queuename(splits[j], DATAFL_LETTER));
+ }
+ e->e_sendqueue = addrs[0];
+ for (i = 0; i < naddrs - 1; ++i)
+ addrs[i]->q_next = addrs[i + 1];
+ addrs[naddrs - 1]->q_next = NULL;
+ return SM_SPLIT_FAIL;
+}
+
+/*
+** SPLIT_WITHIN_QUEUE
+**
+** Split an envelope with multiple recipients into several
+** envelopes within the same queue directory, if the number of
+** recipients exceeds the limit for the queue group.
+**
+** Parameters:
+** e -- envelope.
+**
+** Results:
+** SM_SPLIT_FAIL on failure
+** SM_SPLIT_NONE if no splitting occurred,
+** or 1 + the number of additional envelopes created.
+*/
+
+#define SPLIT_LOG_LEVEL 8
+
+static int split_within_queue __P((ENVELOPE *));
+
+static int
+split_within_queue(e)
+ ENVELOPE *e;
+{
+ int maxrcpt, nrcpt, ndead, nsplit, i;
+ int j, l;
+ char *lsplits;
+ ADDRESS *q, **addrs;
+ ENVELOPE *ee, *firstsibling;
+
+ if (!ISVALIDQGRP(e->e_qgrp) || bitset(EF_SPLIT, e->e_flags))
+ return SM_SPLIT_NONE;
+
+ /* don't bother if there is no recipient limit */
+ maxrcpt = Queue[e->e_qgrp]->qg_maxrcpt;
+ if (maxrcpt <= 0)
+ return SM_SPLIT_NONE;
+
+ /* count recipients */
+ nrcpt = 0;
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ ++nrcpt;
+ }
+ if (nrcpt <= maxrcpt)
+ return SM_SPLIT_NONE;
+
+ /*
+ ** Preserve the recipient list
+ ** so that we can restore it in case of error.
+ ** (But we discard dead addresses.)
+ */
+
+ addrs = sm_rpool_malloc_x(e->e_rpool, nrcpt * sizeof(ADDRESS *));
+ for (i = 0, q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ addrs[i++] = q;
+ }
+
+ /*
+ ** Partition the recipient list so that bad and sent addresses
+ ** come first. These will go with the original envelope, and
+ ** do not count towards the maxrcpt limit.
+ ** addrs[] does not contain QS_IS_DEAD() addresses.
+ */
+
+ ndead = 0;
+ for (i = 0; i < nrcpt; ++i)
+ {
+ if (QS_IS_BADADDR(addrs[i]->q_state) ||
+ QS_IS_SENT(addrs[i]->q_state) ||
+ QS_IS_DEAD(addrs[i]->q_state)) /* for paranoia's sake */
+ {
+ if (i > ndead)
+ {
+ ADDRESS *tmp = addrs[i];
+
+ addrs[i] = addrs[ndead];
+ addrs[ndead] = tmp;
+ }
+ ++ndead;
+ }
+ }
+
+ /* Check if no splitting required. */
+ if (nrcpt - ndead <= maxrcpt)
+ return SM_SPLIT_NONE;
+
+ /* fix links */
+ for (i = 0; i < nrcpt - 1; ++i)
+ addrs[i]->q_next = addrs[i + 1];
+ addrs[nrcpt - 1]->q_next = NULL;
+ e->e_sendqueue = addrs[0];
+
+ /* prepare buffer for logging */
+ if (LogLevel > SPLIT_LOG_LEVEL)
+ {
+ l = MAXLINE;
+ lsplits = sm_malloc(l);
+ if (lsplits != NULL)
+ *lsplits = '\0';
+ j = 0;
+ }
+ else
+ {
+ /* get rid of stupid compiler warnings */
+ lsplits = NULL;
+ j = l = 0;
+ }
+
+ /* split the envelope */
+ firstsibling = e->e_sibling;
+ i = maxrcpt + ndead;
+ nsplit = 0;
+ for (;;)
+ {
+ addrs[i - 1]->q_next = NULL;
+ ee = split_env(e, addrs[i], e->e_qgrp, e->e_qdir);
+ if (!dup_df(e, ee))
+ {
+
+ ee = firstsibling;
+ while (ee != NULL)
+ {
+ (void) unlink(queuename(ee, DATAFL_LETTER));
+ ee = ee->e_sibling;
+ }
+
+ /* Error. Restore e's sibling & recipient lists. */
+ e->e_sibling = firstsibling;
+ for (i = 0; i < nrcpt - 1; ++i)
+ addrs[i]->q_next = addrs[i + 1];
+ return SM_SPLIT_FAIL;
+ }
+
+ /* prepend the new envelope to e->e_sibling */
+ ee->e_sibling = e->e_sibling;
+ e->e_sibling = ee;
+ ++nsplit;
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL)
+ {
+ if (j >= l - strlen(ee->e_id) - 3)
+ {
+ char *p;
+
+ l += MAXLINE;
+ p = sm_realloc(lsplits, l);
+ if (p == NULL)
+ {
+ /* let's try to get this done */
+ sm_free(lsplits);
+ lsplits = NULL;
+ }
+ else
+ lsplits = p;
+ }
+ if (lsplits != NULL)
+ {
+ if (j == 0)
+ j += sm_strlcat(lsplits + j,
+ ee->e_id,
+ l - j);
+ else
+ j += sm_strlcat2(lsplits + j,
+ "; ",
+ ee->e_id,
+ l - j);
+ SM_ASSERT(j < l);
+ }
+ }
+ if (nrcpt - i <= maxrcpt)
+ break;
+ i += maxrcpt;
+ }
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL && nsplit > 0)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "split: maxrcpts=%d, rcpts=%d, count=%d, id%s=%s",
+ maxrcpt, nrcpt - ndead, nsplit,
+ nsplit > 1 ? "s" : "", lsplits);
+ sm_free(lsplits);
+ }
+ return SM_SPLIT_NEW(nsplit);
+}
+/*
+** SPLIT_BY_RECIPIENT
+**
+** Split an envelope with multiple recipients into multiple
+** envelopes as required by the sendmail configuration.
+**
+** Parameters:
+** e -- envelope.
+**
+** Results:
+** Returns true on success, false on failure.
+**
+** Side Effects:
+** see split_across_queue_groups(), split_within_queue(e)
+*/
+
+bool
+split_by_recipient(e)
+ ENVELOPE *e;
+{
+ int split, n, i, j, l;
+ char *lsplits;
+ ENVELOPE *ee, *next, *firstsibling;
+
+ if (OpMode == SM_VERIFY || !ISVALIDQGRP(e->e_qgrp) ||
+ bitset(EF_SPLIT, e->e_flags))
+ return true;
+ n = split_across_queue_groups(e);
+ if (n == SM_SPLIT_FAIL)
+ return false;
+ firstsibling = ee = e->e_sibling;
+ if (n > 1 && LogLevel > SPLIT_LOG_LEVEL)
+ {
+ l = MAXLINE;
+ lsplits = sm_malloc(l);
+ if (lsplits != NULL)
+ *lsplits = '\0';
+ j = 0;
+ }
+ else
+ {
+ /* get rid of stupid compiler warnings */
+ lsplits = NULL;
+ j = l = 0;
+ }
+ for (i = 1; i < n; ++i)
+ {
+ next = ee->e_sibling;
+ if (split_within_queue(ee) == SM_SPLIT_FAIL)
+ {
+ e->e_sibling = firstsibling;
+ return false;
+ }
+ ee->e_flags |= EF_SPLIT;
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL)
+ {
+ if (j >= l - strlen(ee->e_id) - 3)
+ {
+ char *p;
+
+ l += MAXLINE;
+ p = sm_realloc(lsplits, l);
+ if (p == NULL)
+ {
+ /* let's try to get this done */
+ sm_free(lsplits);
+ lsplits = NULL;
+ }
+ else
+ lsplits = p;
+ }
+ if (lsplits != NULL)
+ {
+ if (j == 0)
+ j += sm_strlcat(lsplits + j,
+ ee->e_id, l - j);
+ else
+ j += sm_strlcat2(lsplits + j, "; ",
+ ee->e_id, l - j);
+ SM_ASSERT(j < l);
+ }
+ }
+ ee = next;
+ }
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL && n > 1)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id, "split: count=%d, id%s=%s",
+ n - 1, n > 2 ? "s" : "", lsplits);
+ sm_free(lsplits);
+ }
+ split = split_within_queue(e) != SM_SPLIT_FAIL;
+ if (split)
+ e->e_flags |= EF_SPLIT;
+ return split;
+}
+
+#if _FFR_QUARANTINE
+/*
+** QUARANTINE_QUEUE_ITEM -- {un,}quarantine a single envelope
+**
+** Add/remove quarantine reason and requeue appropriately.
+**
+** Parameters:
+** qgrp -- queue group for the item
+** qdir -- queue directory in the given queue group
+** e -- envelope information for the item
+** reason -- quarantine reason, NULL means unquarantine.
+**
+** Results:
+** true if item changed, false otherwise
+**
+** Side Effects:
+** Changes quarantine tag in queue file and renames it.
+*/
+
+static bool
+quarantine_queue_item(qgrp, qdir, e, reason)
+ int qgrp;
+ int qdir;
+ ENVELOPE *e;
+ char *reason;
+{
+ bool dirty = false;
+ bool failing = false;
+ bool foundq = false;
+ bool finished = false;
+ int fd;
+ int flags;
+ int oldtype;
+ int newtype;
+ int save_errno;
+ MODE_T oldumask = 0;
+ SM_FILE_T *oldqfp, *tempqfp;
+ char *bp;
+ char oldqf[MAXPATHLEN];
+ char tempqf[MAXPATHLEN];
+ char newqf[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ oldtype = queue_letter(e, ANYQFL_LETTER);
+ (void) sm_strlcpy(oldqf, queuename(e, ANYQFL_LETTER), sizeof oldqf);
+ (void) sm_strlcpy(tempqf, queuename(e, NEWQFL_LETTER), sizeof tempqf);
+
+ /*
+ ** Instead of duplicating all the open
+ ** and lock code here, tell readqf() to
+ ** do that work and return the open
+ ** file pointer in e_lockfp. Note that
+ ** we must release the locks properly when
+ ** we are done.
+ */
+
+ if (!readqf(e, true))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s\n", qid_printname(e));
+ return false;
+ }
+ oldqfp = e->e_lockfp;
+
+ /* open the new queue file */
+ flags = O_CREAT|O_WRONLY|O_EXCL;
+ if (bitset(S_IWGRP, QueueFileMode))
+ oldumask = umask(002);
+ fd = open(tempqf, flags, QueueFileMode);
+ if (bitset(S_IWGRP, QueueFileMode))
+ (void) umask(oldumask);
+ RELEASE_QUEUE;
+
+ if (fd < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Could not open %s: %s\n",
+ qid_printname(e), tempqf,
+ sm_errstring(save_errno));
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+ return false;
+ }
+ if (!lockfile(fd, tempqf, NULL, LOCK_EX|LOCK_NB))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Could not lock %s\n",
+ qid_printname(e), tempqf);
+ (void) close(fd);
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ tempqfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &fd,
+ SM_IO_WRONLY, NULL);
+ if (tempqfp == NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Could not lock %s\n",
+ qid_printname(e), tempqf);
+ (void) close(fd);
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ /* Copy the data over, changing the quarantine reason */
+ while ((bp = fgetfolded(buf, sizeof buf, oldqfp)) != NULL)
+ {
+ if (tTd(40, 4))
+ sm_dprintf("+++++ %s\n", bp);
+ switch (bp[0])
+ {
+ case 'q': /* quarantine reason */
+ foundq = true;
+ if (reason == NULL)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Removed quarantine of \"%s\"\n",
+ e->e_id, &bp[1]);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "unquarantine");
+ dirty = true;
+ continue;
+ }
+ else if (strcmp(reason, &bp[1]) == 0)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Already quarantined with \"%s\"\n",
+ e->e_id, reason);
+ }
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "q%s\n", reason);
+ }
+ else
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Quarantine changed from \"%s\" to \"%s\"\n",
+ e->e_id, &bp[1],
+ reason);
+ }
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "q%s\n", reason);
+ sm_syslog(LOG_INFO, e->e_id, "quarantine=%s",
+ reason);
+ dirty = true;
+ }
+ break;
+
+ case 'S':
+ /*
+ ** If we are quarantining an unquarantined item,
+ ** need to put in a new 'q' line before it's
+ ** too late.
+ */
+
+ if (!foundq && reason != NULL)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Quarantined with \"%s\"\n",
+ e->e_id, reason);
+ }
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "q%s\n", reason);
+ sm_syslog(LOG_INFO, e->e_id, "quarantine=%s",
+ reason);
+ foundq = true;
+ dirty = true;
+ }
+
+ /* Copy the line to the new file */
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "%s\n", bp);
+ break;
+
+ case '.':
+ finished = true;
+ /* FALLTHROUGH */
+
+ default:
+ /* Copy the line to the new file */
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "%s\n", bp);
+ break;
+ }
+ }
+
+ /* Make sure we read the whole old file */
+ errno = sm_io_error(tempqfp);
+ if (errno != 0 && errno != SM_IO_EOF)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Error reading %s: %s\n",
+ qid_printname(e), oldqf,
+ sm_errstring(save_errno));
+ failing = true;
+ }
+
+ if (!failing && !finished)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Incomplete file: %s\n",
+ qid_printname(e), oldqf);
+ failing = true;
+ }
+
+ /* Check if we actually changed anything or we can just bail now */
+ if (!dirty)
+ {
+ /* pretend we failed, even though we technically didn't */
+ failing = true;
+ }
+
+ /* Make sure we wrote things out safely */
+ if (!failing &&
+ (sm_io_flush(tempqfp, SM_TIME_DEFAULT) != 0 ||
+ ((SuperSafe == SAFE_REALLY || SuperSafe == SAFE_INTERACTIVE) &&
+ fsync(sm_io_getinfo(tempqfp, SM_IO_WHAT_FD, NULL)) < 0) ||
+ ((errno = sm_io_error(tempqfp)) != 0)))
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Error writing %s: %s\n",
+ qid_printname(e), tempqf,
+ sm_errstring(save_errno));
+ failing = true;
+ }
+
+
+ /* Figure out the new filename */
+ newtype = (reason == NULL ? NORMQF_LETTER : QUARQF_LETTER);
+ if (oldtype == newtype)
+ {
+ /* going to rename tempqf to oldqf */
+ (void) sm_strlcpy(newqf, oldqf, sizeof newqf);
+ }
+ else
+ {
+ /* going to rename tempqf to new name based on newtype */
+ (void) sm_strlcpy(newqf, queuename(e, newtype), sizeof newqf);
+ }
+
+ save_errno = 0;
+
+ /* rename tempqf to newqf */
+ if (!failing &&
+ rename(tempqf, newqf) < 0)
+ save_errno = (errno == 0) ? EINVAL : errno;
+
+ /* Check rename() success */
+ if (!failing && save_errno != 0)
+ {
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "quarantine_queue_item: rename(%s, %s): %s",
+ tempqf, newqf, sm_errstring(save_errno));
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Error renaming %s to %s: %s\n",
+ tempqf, newqf,
+ sm_errstring(save_errno));
+ if (oldtype == newtype)
+ {
+ /*
+ ** Bail here since we don't know the state of
+ ** the filesystem and may need to keep tempqf
+ ** for the user to rescue us.
+ */
+
+ RELEASE_QUEUE;
+ errno = save_errno;
+ syserr("!452 Error renaming control file %s", tempqf);
+ /* NOTREACHED */
+ }
+ else
+ {
+ /* remove new file (if rename() half completed) */
+ if (xunlink(newqf) < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Error removing %s: %s\n",
+ newqf,
+ sm_errstring(save_errno));
+ }
+
+ /* tempqf removed below */
+ failing = true;
+ }
+
+ }
+
+ /* If changing file types, need to remove old type */
+ if (!failing && oldtype != newtype)
+ {
+ if (xunlink(oldqf) < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Error removing %s: %s\n",
+ oldqf, sm_errstring(save_errno));
+ }
+ }
+
+ /* see if anything above failed */
+ if (failing)
+ {
+ /* Something failed: remove new file, old file still there */
+ (void) xunlink(tempqf);
+ }
+
+ /*
+ ** fsync() after file operations to make sure metadata is
+ ** written to disk on filesystems in which renames are
+ ** not guaranteed. It's ok if they fail, mail won't be lost.
+ */
+
+ if (SuperSafe != SAFE_NO)
+ {
+ /* for soft-updates */
+ (void) fsync(sm_io_getinfo(tempqfp,
+ SM_IO_WHAT_FD, NULL));
+
+ if (!failing)
+ {
+ /* for soft-updates */
+ (void) fsync(sm_io_getinfo(oldqfp,
+ SM_IO_WHAT_FD, NULL));
+ }
+
+ /* for other odd filesystems */
+ SYNC_DIR(tempqf, false);
+ }
+
+ /* Close up shop */
+ RELEASE_QUEUE;
+ if (tempqfp != NULL)
+ (void) sm_io_close(tempqfp, SM_TIME_DEFAULT);
+ if (oldqfp != NULL)
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+
+ /* All went well */
+ return !failing;
+}
+
+/*
+** QUARANTINE_QUEUE -- {un,}quarantine matching items in the queue
+**
+** Read all matching queue items, add/remove quarantine
+** reason, and requeue appropriately.
+**
+** Parameters:
+** reason -- quarantine reason, "." means unquarantine.
+** qgrplimit -- limit to single queue group unless NOQGRP
+**
+** Results:
+** none.
+**
+** Side Effects:
+** Lots of changes to the queue.
+*/
+
+void
+quarantine_queue(reason, qgrplimit)
+ char *reason;
+ int qgrplimit;
+{
+ int changed = 0;
+ int qgrp;
+
+ /* Convert internal representation of unquarantine */
+ if (reason != NULL && reason[0] == '.' && reason[1] == '\0')
+ reason = NULL;
+
+ if (reason != NULL)
+ {
+ /* clean it */
+ reason = newstr(denlstring(reason, true, true));
+ }
+
+ for (qgrp = 0; qgrp < NumQueue && Queue[qgrp] != NULL; qgrp++)
+ {
+ int qdir;
+
+ if (qgrplimit != NOQGRP && qgrplimit != qgrp)
+ continue;
+
+ for (qdir = 0; qdir < Queue[qgrp]->qg_numqueues; qdir++)
+ {
+ int i;
+ int nrequests;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ nrequests = gatherq(qgrp, qdir, true, NULL, NULL);
+
+ /* first see if there is anything */
+ if (nrequests <= 0)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT, "%s: no matches\n",
+ qid_printqueue(qgrp, qdir));
+ }
+ continue;
+ }
+
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT, "Processing %s:\n",
+ qid_printqueue(qgrp, qdir));
+ }
+
+ for (i = 0; i < WorkListCount; i++)
+ {
+ ENVELOPE e;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ /* setup envelope */
+ clearenvelope(&e, true, sm_rpool_new_x(NULL));
+ e.e_id = WorkList[i].w_name + 2;
+ e.e_qgrp = qgrp;
+ e.e_qdir = qdir;
+
+ if (tTd(70, 101))
+ {
+ sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Would do %s\n", e.e_id);
+ changed++;
+ }
+ else if (quarantine_queue_item(qgrp, qdir,
+ &e, reason))
+ changed++;
+
+ /* clean up */
+ sm_rpool_free(e.e_rpool);
+ e.e_rpool = NULL;
+ }
+ if (WorkList != NULL)
+ sm_free(WorkList); /* XXX */
+ WorkList = NULL;
+ WorkListSize = 0;
+ WorkListCount = 0;
+ }
+ }
+ if (Verbose)
+ {
+ if (changed == 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "No changes\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%d change%s\n",
+ changed,
+ changed == 1 ? "" : "s");
+ }
+}
+#endif /* _FFR_QUARANTINE */
diff --git a/contrib/sendmail/src/readcf.c b/contrib/sendmail/src/readcf.c
new file mode 100644
index 0000000..fbfdef1
--- /dev/null
+++ b/contrib/sendmail/src/readcf.c
@@ -0,0 +1,4305 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: readcf.c,v 8.607.2.2 2002/08/19 21:50:49 gshapiro Exp $")
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+#define SECONDS
+#define MINUTES * 60
+#define HOUR * 3600
+#define HOURS HOUR
+
+static void fileclass __P((int, char *, char *, bool, bool, bool));
+static char **makeargv __P((char *));
+static void settimeout __P((char *, char *, bool));
+static void toomany __P((int, int));
+static char *extrquotstr __P((char *, char **, char *, bool *));
+
+/*
+** READCF -- read configuration file.
+**
+** This routine reads the configuration file and builds the internal
+** form.
+**
+** The file is formatted as a sequence of lines, each taken
+** atomically. The first character of each line describes how
+** the line is to be interpreted. The lines are:
+** Dxval Define macro x to have value val.
+** Cxword Put word into class x.
+** Fxfile [fmt] Read file for lines to put into
+** class x. Use scanf string 'fmt'
+** or "%s" if not present. Fmt should
+** only produce one string-valued result.
+** Hname: value Define header with field-name 'name'
+** and value as specified; this will be
+** macro expanded immediately before
+** use.
+** Sn Use rewriting set n.
+** Rlhs rhs Rewrite addresses that match lhs to
+** be rhs.
+** Mn arg=val... Define mailer. n is the internal name.
+** Args specify mailer parameters.
+** Oxvalue Set option x to value.
+** O option value Set option (long name) to value.
+** Pname=value Set precedence name to value.
+** Qn arg=val... Define queue groups. n is the internal name.
+** Args specify queue parameters.
+** Vversioncode[/vendorcode]
+** Version level/vendor name of
+** configuration syntax.
+** Kmapname mapclass arguments....
+** Define keyed lookup of a given class.
+** Arguments are class dependent.
+** Eenvar=value Set the environment value to the given value.
+**
+** Parameters:
+** cfname -- configuration file name.
+** safe -- true if this is the system config file;
+** false otherwise.
+** e -- the main envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Builds several internal tables.
+*/
+
+void
+readcf(cfname, safe, e)
+ char *cfname;
+ bool safe;
+ register ENVELOPE *e;
+{
+ SM_FILE_T *cf;
+ int ruleset = -1;
+ char *q;
+ struct rewrite *rwp = NULL;
+ char *bp;
+ auto char *ep;
+ int nfuzzy;
+ char *file;
+ bool optional;
+ bool ok;
+ bool ismap;
+ int mid;
+ register char *p;
+ long sff = SFF_OPENASROOT;
+ struct stat statb;
+ char buf[MAXLINE];
+ char exbuf[MAXLINE];
+ char pvpbuf[MAXLINE + MAXATOM];
+ static char *null_list[1] = { NULL };
+ extern unsigned char TokTypeNoC[];
+
+ FileName = cfname;
+ LineNumber = 0;
+
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ cf = safefopen(cfname, O_RDONLY, 0444, sff);
+ if (cf == NULL)
+ {
+ syserr("cannot open");
+ finis(false, true, EX_OSFILE);
+ }
+
+ if (fstat(sm_io_getinfo(cf, SM_IO_WHAT_FD, NULL), &statb) < 0)
+ {
+ syserr("cannot fstat");
+ finis(false, true, EX_OSFILE);
+ }
+
+ if (!S_ISREG(statb.st_mode))
+ {
+ syserr("not a plain file");
+ finis(false, true, EX_OSFILE);
+ }
+
+ if (OpMode != MD_TEST && bitset(S_IWGRP|S_IWOTH, statb.st_mode))
+ {
+ if (OpMode == MD_DAEMON || OpMode == MD_INITALIAS)
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "%s: WARNING: dangerous write permissions\n",
+ FileName);
+ if (LogLevel > 0)
+ sm_syslog(LOG_CRIT, NOQID,
+ "%s: WARNING: dangerous write permissions",
+ FileName);
+ }
+
+#if XLA
+ xla_zero();
+#endif /* XLA */
+
+ while ((bp = fgetfolded(buf, sizeof buf, cf)) != NULL)
+ {
+ if (bp[0] == '#')
+ {
+ if (bp != buf)
+ sm_free(bp); /* XXX */
+ continue;
+ }
+
+ /* do macro expansion mappings */
+ translate_dollars(bp);
+
+ /* interpret this line */
+ errno = 0;
+ switch (bp[0])
+ {
+ case '\0':
+ case '#': /* comment */
+ break;
+
+ case 'R': /* rewriting rule */
+ if (ruleset < 0)
+ {
+ syserr("missing valid ruleset for \"%s\"", bp);
+ break;
+ }
+ for (p = &bp[1]; *p != '\0' && *p != '\t'; p++)
+ continue;
+
+ if (*p == '\0')
+ {
+ syserr("invalid rewrite line \"%s\" (tab expected)", bp);
+ break;
+ }
+
+ /* allocate space for the rule header */
+ if (rwp == NULL)
+ {
+ RewriteRules[ruleset] = rwp =
+ (struct rewrite *) xalloc(sizeof *rwp);
+ }
+ else
+ {
+ rwp->r_next = (struct rewrite *) xalloc(sizeof *rwp);
+ rwp = rwp->r_next;
+ }
+ rwp->r_next = NULL;
+
+ /* expand and save the LHS */
+ *p = '\0';
+ expand(&bp[1], exbuf, sizeof exbuf, e);
+ rwp->r_lhs = prescan(exbuf, '\t', pvpbuf,
+ sizeof pvpbuf, NULL,
+ ConfigLevel >= 9 ? TokTypeNoC : NULL);
+ nfuzzy = 0;
+ if (rwp->r_lhs != NULL)
+ {
+ register char **ap;
+
+ rwp->r_lhs = copyplist(rwp->r_lhs, true, NULL);
+
+ /* count the number of fuzzy matches in LHS */
+ for (ap = rwp->r_lhs; *ap != NULL; ap++)
+ {
+ char *botch;
+
+ botch = NULL;
+ switch (**ap & 0377)
+ {
+ case MATCHZANY:
+ case MATCHANY:
+ case MATCHONE:
+ case MATCHCLASS:
+ case MATCHNCLASS:
+ nfuzzy++;
+ break;
+
+ case MATCHREPL:
+ botch = "$0-$9";
+ break;
+
+ case CANONUSER:
+ botch = "$:";
+ break;
+
+ case CALLSUBR:
+ botch = "$>";
+ break;
+
+ case CONDIF:
+ botch = "$?";
+ break;
+
+ case CONDFI:
+ botch = "$.";
+ break;
+
+ case HOSTBEGIN:
+ botch = "$[";
+ break;
+
+ case HOSTEND:
+ botch = "$]";
+ break;
+
+ case LOOKUPBEGIN:
+ botch = "$(";
+ break;
+
+ case LOOKUPEND:
+ botch = "$)";
+ break;
+ }
+ if (botch != NULL)
+ syserr("Inappropriate use of %s on LHS",
+ botch);
+ }
+ rwp->r_line = LineNumber;
+ }
+ else
+ {
+ syserr("R line: null LHS");
+ rwp->r_lhs = null_list;
+ }
+ if (nfuzzy > MAXMATCH)
+ {
+ syserr("R line: too many wildcards");
+ rwp->r_lhs = null_list;
+ }
+
+ /* expand and save the RHS */
+ while (*++p == '\t')
+ continue;
+ q = p;
+ while (*p != '\0' && *p != '\t')
+ p++;
+ *p = '\0';
+ expand(q, exbuf, sizeof exbuf, e);
+ rwp->r_rhs = prescan(exbuf, '\t', pvpbuf,
+ sizeof pvpbuf, NULL,
+ ConfigLevel >= 9 ? TokTypeNoC : NULL);
+ if (rwp->r_rhs != NULL)
+ {
+ register char **ap;
+
+ rwp->r_rhs = copyplist(rwp->r_rhs, true, NULL);
+
+ /* check no out-of-bounds replacements */
+ nfuzzy += '0';
+ for (ap = rwp->r_rhs; *ap != NULL; ap++)
+ {
+ char *botch;
+
+ botch = NULL;
+ switch (**ap & 0377)
+ {
+ case MATCHREPL:
+ if ((*ap)[1] <= '0' || (*ap)[1] > nfuzzy)
+ {
+ syserr("replacement $%c out of bounds",
+ (*ap)[1]);
+ }
+ break;
+
+ case MATCHZANY:
+ botch = "$*";
+ break;
+
+ case MATCHANY:
+ botch = "$+";
+ break;
+
+ case MATCHONE:
+ botch = "$-";
+ break;
+
+ case MATCHCLASS:
+ botch = "$=";
+ break;
+
+ case MATCHNCLASS:
+ botch = "$~";
+ break;
+
+#if 0
+/*
+** This doesn't work yet as there are maps defined *after* the cf
+** is read such as host, user, and alias. So for now, it's removed.
+** When it comes back, the RELEASE_NOTES entry will be:
+** Emit warnings for unknown maps when reading the .cf file. Based on
+** patch from Robert Harker of Harker Systems.
+*/
+
+ case LOOKUPBEGIN:
+ /*
+ ** Got a database lookup,
+ ** check if map is defined.
+ */
+
+ ep = *(ap + 1);
+ if ((*ep & 0377) != MACRODEXPAND &&
+ stab(ep, ST_MAP,
+ ST_FIND) == NULL)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "Warning: %s: line %d: map %s not found\n",
+ FileName,
+ LineNumber,
+ ep);
+ }
+ break;
+#endif /* 0 */
+ }
+ if (botch != NULL)
+ syserr("Inappropriate use of %s on RHS",
+ botch);
+ }
+ }
+ else
+ {
+ syserr("R line: null RHS");
+ rwp->r_rhs = null_list;
+ }
+ break;
+
+ case 'S': /* select rewriting set */
+ expand(&bp[1], exbuf, sizeof exbuf, e);
+ ruleset = strtorwset(exbuf, NULL, ST_ENTER);
+ if (ruleset < 0)
+ break;
+
+ rwp = RewriteRules[ruleset];
+ if (rwp != NULL)
+ {
+ if (OpMode == MD_TEST)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "WARNING: Ruleset %s has multiple definitions\n",
+ &bp[1]);
+ if (tTd(37, 1))
+ sm_dprintf("WARNING: Ruleset %s has multiple definitions\n",
+ &bp[1]);
+ while (rwp->r_next != NULL)
+ rwp = rwp->r_next;
+ }
+ break;
+
+ case 'D': /* macro definition */
+ mid = macid_parse(&bp[1], &ep);
+ if (mid == 0)
+ break;
+ p = munchstring(ep, NULL, '\0');
+ macdefine(&e->e_macro, A_TEMP, mid, p);
+ break;
+
+ case 'H': /* required header line */
+ (void) chompheader(&bp[1], CHHDR_DEF, NULL, e);
+ break;
+
+ case 'C': /* word class */
+ case 'T': /* trusted user (set class `t') */
+ if (bp[0] == 'C')
+ {
+ mid = macid_parse(&bp[1], &ep);
+ if (mid == 0)
+ break;
+ expand(ep, exbuf, sizeof exbuf, e);
+ p = exbuf;
+ }
+ else
+ {
+ mid = 't';
+ p = &bp[1];
+ }
+ while (*p != '\0')
+ {
+ register char *wd;
+ char delim;
+
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ wd = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ delim = *p;
+ *p = '\0';
+ if (wd[0] != '\0')
+ setclass(mid, wd);
+ *p = delim;
+ }
+ break;
+
+ case 'F': /* word class from file */
+ mid = macid_parse(&bp[1], &ep);
+ if (mid == 0)
+ break;
+ for (p = ep; isascii(*p) && isspace(*p); )
+ p++;
+ if (p[0] == '-' && p[1] == 'o')
+ {
+ optional = true;
+ while (*p != '\0' &&
+ !(isascii(*p) && isspace(*p)))
+ p++;
+ while (isascii(*p) && isspace(*p))
+ p++;
+ file = p;
+ }
+ else
+ optional = false;
+
+ /* check if [key]@map:spec */
+ ismap = false;
+ if (!SM_IS_DIR_DELIM(*p) &&
+ *p != '|' &&
+ (q = strchr(p, '@')) != NULL)
+ {
+ q++;
+
+ /* look for @LDAP or @map: in string */
+ if (strcmp(q, "LDAP") == 0 ||
+ (*q != ':' &&
+ strchr(q, ':') != NULL))
+ ismap = true;
+ }
+
+ if (ismap)
+ {
+ /* use entire spec */
+ file = p;
+ }
+ else
+ {
+ file = extrquotstr(p, &q, " ", &ok);
+ if (!ok)
+ {
+ syserr("illegal filename '%s'", p);
+ break;
+ }
+ }
+
+ if (*file == '|' || ismap)
+ p = "%s";
+ else
+ {
+ p = q;
+ if (*p == '\0')
+ p = "%s";
+ else
+ {
+ *p = '\0';
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ }
+ }
+ fileclass(mid, file, p, ismap, safe, optional);
+ break;
+
+#if XLA
+ case 'L': /* extended load average description */
+ xla_init(&bp[1]);
+ break;
+#endif /* XLA */
+
+#if defined(SUN_EXTENSIONS) && defined(SUN_LOOKUP_MACRO)
+ case 'L': /* lookup macro */
+ case 'G': /* lookup class */
+ /* reserved for Sun -- NIS+ database lookup */
+ if (VendorCode != VENDOR_SUN)
+ goto badline;
+ sun_lg_config_line(bp, e);
+ break;
+#endif /* defined(SUN_EXTENSIONS) && defined(SUN_LOOKUP_MACRO) */
+
+ case 'M': /* define mailer */
+ makemailer(&bp[1]);
+ break;
+
+ case 'O': /* set option */
+ setoption(bp[1], &bp[2], safe, false, e);
+ break;
+
+ case 'P': /* set precedence */
+ if (NumPriorities >= MAXPRIORITIES)
+ {
+ toomany('P', MAXPRIORITIES);
+ break;
+ }
+ for (p = &bp[1]; *p != '\0' && *p != '='; p++)
+ continue;
+ if (*p == '\0')
+ goto badline;
+ *p = '\0';
+ Priorities[NumPriorities].pri_name = newstr(&bp[1]);
+ Priorities[NumPriorities].pri_val = atoi(++p);
+ NumPriorities++;
+ break;
+
+ case 'Q': /* define queue */
+ makequeue(&bp[1], true);
+ break;
+
+ case 'V': /* configuration syntax version */
+ for (p = &bp[1]; isascii(*p) && isspace(*p); p++)
+ continue;
+ if (!isascii(*p) || !isdigit(*p))
+ {
+ syserr("invalid argument to V line: \"%.20s\"",
+ &bp[1]);
+ break;
+ }
+ ConfigLevel = strtol(p, &ep, 10);
+
+ /*
+ ** Do heuristic tweaking for back compatibility.
+ */
+
+ if (ConfigLevel >= 5)
+ {
+ /* level 5 configs have short name in $w */
+ p = macvalue('w', e);
+ if (p != NULL && (p = strchr(p, '.')) != NULL)
+ {
+ *p = '\0';
+ macdefine(&e->e_macro, A_TEMP, 'w',
+ macvalue('w', e));
+ }
+ }
+ if (ConfigLevel >= 6)
+ {
+ ColonOkInAddr = false;
+ }
+
+ /*
+ ** Look for vendor code.
+ */
+
+ if (*ep++ == '/')
+ {
+ /* extract vendor code */
+ for (p = ep; isascii(*p) && isalpha(*p); )
+ p++;
+ *p = '\0';
+
+ if (!setvendor(ep))
+ syserr("invalid V line vendor code: \"%s\"",
+ ep);
+ }
+ break;
+
+ case 'K':
+ expand(&bp[1], exbuf, sizeof exbuf, e);
+ (void) makemapentry(exbuf);
+ break;
+
+ case 'E':
+ p = strchr(bp, '=');
+ if (p != NULL)
+ *p++ = '\0';
+ setuserenv(&bp[1], p);
+ break;
+
+ case 'X': /* mail filter */
+#if MILTER
+ milter_setup(&bp[1]);
+#else /* MILTER */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Filter usage ('X') requires Milter support (-DMILTER)\n");
+#endif /* MILTER */
+ break;
+
+ default:
+ badline:
+ syserr("unknown configuration line \"%s\"", bp);
+ }
+ if (bp != buf)
+ sm_free(bp); /* XXX */
+ }
+ if (sm_io_error(cf))
+ {
+ syserr("I/O read error");
+ finis(false, true, EX_OSFILE);
+ }
+ (void) sm_io_close(cf, SM_TIME_DEFAULT);
+ FileName = NULL;
+
+ /* initialize host maps from local service tables */
+ inithostmaps();
+
+ /* initialize daemon (if not defined yet) */
+ initdaemon();
+
+ /* determine if we need to do special name-server frotz */
+ {
+ int nmaps;
+ char *maptype[MAXMAPSTACK];
+ short mapreturn[MAXMAPACTIONS];
+
+ nmaps = switch_map_find("hosts", maptype, mapreturn);
+ UseNameServer = false;
+ if (nmaps > 0 && nmaps <= MAXMAPSTACK)
+ {
+ register int mapno;
+
+ for (mapno = 0; mapno < nmaps && !UseNameServer;
+ mapno++)
+ {
+ if (strcmp(maptype[mapno], "dns") == 0)
+ UseNameServer = true;
+ }
+ }
+ }
+}
+/*
+** TRANSLATE_DOLLARS -- convert $x into internal form
+**
+** Actually does all appropriate pre-processing of a config line
+** to turn it into internal form.
+**
+** Parameters:
+** bp -- the buffer to translate.
+**
+** Returns:
+** None. The buffer is translated in place. Since the
+** translations always make the buffer shorter, this is
+** safe without a size parameter.
+*/
+
+void
+translate_dollars(bp)
+ char *bp;
+{
+ register char *p;
+ auto char *ep;
+
+ for (p = bp; *p != '\0'; p++)
+ {
+ if (*p == '#' && p > bp && ConfigLevel >= 3)
+ {
+ register char *e;
+
+ switch (*--p & 0377)
+ {
+ case MACROEXPAND:
+ /* it's from $# -- let it go through */
+ p++;
+ break;
+
+ case '\\':
+ /* it's backslash escaped */
+ (void) sm_strlcpy(p, p + 1, strlen(p));
+ break;
+
+ default:
+ /* delete leading white space */
+ while (isascii(*p) && isspace(*p) &&
+ *p != '\n' && p > bp)
+ p--;
+ if ((e = strchr(++p, '\n')) != NULL)
+ (void) sm_strlcpy(p, e, strlen(p));
+ else
+ *p-- = '\0';
+ break;
+ }
+ continue;
+ }
+
+ if (*p != '$' || p[1] == '\0')
+ continue;
+
+ if (p[1] == '$')
+ {
+ /* actual dollar sign.... */
+ (void) sm_strlcpy(p, p + 1, strlen(p));
+ continue;
+ }
+
+ /* convert to macro expansion character */
+ *p++ = MACROEXPAND;
+
+ /* special handling for $=, $~, $&, and $? */
+ if (*p == '=' || *p == '~' || *p == '&' || *p == '?')
+ p++;
+
+ /* convert macro name to code */
+ *p = macid_parse(p, &ep);
+ if (ep != p + 1)
+ (void) sm_strlcpy(p + 1, ep, strlen(p + 1));
+ }
+
+ /* strip trailing white space from the line */
+ while (--p > bp && isascii(*p) && isspace(*p))
+ *p = '\0';
+}
+/*
+** TOOMANY -- signal too many of some option
+**
+** Parameters:
+** id -- the id of the error line
+** maxcnt -- the maximum possible values
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** gives a syserr.
+*/
+
+static void
+toomany(id, maxcnt)
+ int id;
+ int maxcnt;
+{
+ syserr("too many %c lines, %d max", id, maxcnt);
+}
+/*
+** FILECLASS -- read members of a class from a file
+**
+** Parameters:
+** class -- class to define.
+** filename -- name of file to read.
+** fmt -- scanf string to use for match.
+** ismap -- if set, this is a map lookup.
+** safe -- if set, this is a safe read.
+** optional -- if set, it is not an error for the file to
+** not exist.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** puts all lines in filename that match a scanf into
+** the named class.
+*/
+
+/*
+** Break up the match into words and add to class.
+*/
+
+static void
+parse_class_words(class, line)
+ int class;
+ char *line;
+{
+ while (line != NULL && *line != '\0')
+ {
+ register char *q;
+
+ /* strip leading spaces */
+ while (isascii(*line) && isspace(*line))
+ line++;
+ if (*line == '\0')
+ break;
+
+ /* find the end of the word */
+ q = line;
+ while (*line != '\0' && !(isascii(*line) && isspace(*line)))
+ line++;
+ if (*line != '\0')
+ *line++ = '\0';
+
+ /* enter the word in the symbol table */
+ setclass(class, q);
+ }
+}
+
+static void
+fileclass(class, filename, fmt, ismap, safe, optional)
+ int class;
+ char *filename;
+ char *fmt;
+ bool ismap;
+ bool safe;
+ bool optional;
+{
+ SM_FILE_T *f;
+ long sff;
+ pid_t pid;
+ register char *p;
+ char buf[MAXLINE];
+
+ if (tTd(37, 2))
+ sm_dprintf("fileclass(%s, fmt=%s)\n", filename, fmt);
+
+ if (*filename == '\0')
+ {
+ syserr("fileclass: missing file name");
+ return;
+ }
+ else if (ismap)
+ {
+ int status = 0;
+ char *key;
+ char *mn;
+ char *cl, *spec;
+ STAB *mapclass;
+ MAP map;
+
+ mn = newstr(macname(class));
+
+ key = filename;
+
+ /* skip past key */
+ if ((p = strchr(filename, '@')) == NULL)
+ {
+ /* should not happen */
+ syserr("fileclass: bogus map specification");
+ sm_free(mn);
+ return;
+ }
+
+ /* skip past '@' */
+ *p++ = '\0';
+ cl = p;
+
+ if (strcmp(cl, "LDAP") == 0)
+ {
+ int n;
+ char *lc;
+ char jbuf[MAXHOSTNAMELEN];
+ char lcbuf[MAXLINE];
+
+ /* Get $j */
+ expand("\201j", jbuf, sizeof jbuf, &BlankEnvelope);
+ if (jbuf[0] == '\0')
+ {
+ (void) sm_strlcpy(jbuf, "localhost",
+ sizeof jbuf);
+ }
+
+ /* impose the default schema */
+ lc = macvalue(macid("{sendmailMTACluster}"), CurEnv);
+ if (lc == NULL)
+ lc = "";
+ else
+ {
+ expand(lc, lcbuf, sizeof lcbuf, CurEnv);
+ lc = lcbuf;
+ }
+
+ cl = "ldap";
+ n = sm_snprintf(buf, sizeof buf,
+ "-k (&(objectClass=sendmailMTAClass)(sendmailMTAClassName=%s)(|(sendmailMTACluster=%s)(sendmailMTAHost=%s))) -v sendmailMTAClassValue",
+ mn, lc, jbuf);
+ if (n >= sizeof buf)
+ {
+ syserr("fileclass: F{%s}: Default LDAP string too long",
+ mn);
+ sm_free(mn);
+ return;
+ }
+ spec = buf;
+ }
+ else
+ {
+ if ((spec = strchr(cl, ':')) == NULL)
+ {
+ syserr("fileclass: F{%s}: missing map class",
+ mn);
+ sm_free(mn);
+ return;
+ }
+ *spec++ ='\0';
+ }
+
+ /* set up map structure */
+ mapclass = stab(cl, ST_MAPCLASS, ST_FIND);
+ if (mapclass == NULL)
+ {
+ syserr("fileclass: F{%s}: class %s not available",
+ mn, cl);
+ sm_free(mn);
+ return;
+ }
+ memset(&map, '\0', sizeof map);
+ map.map_class = &mapclass->s_mapclass;
+ map.map_mname = mn;
+ map.map_mflags |= MF_FILECLASS;
+
+ if (tTd(37, 5))
+ sm_dprintf("fileclass: F{%s}: map class %s, key %s, spec %s\n",
+ mn, cl, key, spec);
+
+
+ /* parse map spec */
+ if (!map.map_class->map_parse(&map, spec))
+ {
+ /* map_parse() showed the error already */
+ sm_free(mn);
+ return;
+ }
+ map.map_mflags |= MF_VALID;
+
+ /* open map */
+ if (map.map_class->map_open(&map, O_RDONLY))
+ {
+ map.map_mflags |= MF_OPEN;
+ map.map_pid = getpid();
+ }
+ else
+ {
+ if (!optional &&
+ !bitset(MF_OPTIONAL, map.map_mflags))
+ syserr("fileclass: F{%s}: map open failed",
+ mn);
+ sm_free(mn);
+ return;
+ }
+
+ /* lookup */
+ p = (*map.map_class->map_lookup)(&map, key, NULL, &status);
+ if (status != EX_OK && status != EX_NOTFOUND)
+ {
+ if (!optional)
+ syserr("fileclass: F{%s}: map lookup failed",
+ mn);
+ p = NULL;
+ }
+
+ /* use the results */
+ if (p != NULL)
+ parse_class_words(class, p);
+
+ /* close map */
+ map.map_mflags |= MF_CLOSING;
+ map.map_class->map_close(&map);
+ map.map_mflags &= ~(MF_OPEN|MF_WRITABLE|MF_CLOSING);
+ sm_free(mn);
+ return;
+ }
+ else if (filename[0] == '|')
+ {
+ auto int fd;
+ int i;
+ char *argv[MAXPV + 1];
+
+ i = 0;
+ for (p = strtok(&filename[1], " \t");
+ p != NULL && i < MAXPV;
+ p = strtok(NULL, " \t"))
+ argv[i++] = p;
+ argv[i] = NULL;
+ pid = prog_open(argv, &fd, CurEnv);
+ if (pid < 0)
+ f = NULL;
+ else
+ f = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &fd, SM_IO_RDONLY, NULL);
+ }
+ else
+ {
+ pid = -1;
+ sff = SFF_REGONLY;
+ if (!bitnset(DBS_CLASSFILEINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ if (!bitnset(DBS_LINKEDCLASSFILEINWRITABLEDIR,
+ DontBlameSendmail))
+ sff |= SFF_NOWLINK;
+ if (safe)
+ sff |= SFF_OPENASROOT;
+ else if (RealUid == 0)
+ sff |= SFF_ROOTOK;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ f = safefopen(filename, O_RDONLY, 0, sff);
+ }
+ if (f == NULL)
+ {
+ if (!optional)
+ syserr("fileclass: cannot open '%s'", filename);
+ return;
+ }
+
+ while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+#if SCANF
+ char wordbuf[MAXLINE + 1];
+#endif /* SCANF */
+
+ if (buf[0] == '#')
+ continue;
+#if SCANF
+ if (sm_io_sscanf(buf, fmt, wordbuf) != 1)
+ continue;
+ p = wordbuf;
+#else /* SCANF */
+ p = buf;
+#endif /* SCANF */
+
+ parse_class_words(class, p);
+
+ /*
+ ** If anything else is added here,
+ ** check if the '@' map case above
+ ** needs the code as well.
+ */
+ }
+
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ if (pid > 0)
+ (void) waitfor(pid);
+}
+/*
+** MAKEMAILER -- define a new mailer.
+**
+** Parameters:
+** line -- description of mailer. This is in labeled
+** fields. The fields are:
+** A -- the argv for this mailer
+** C -- the character set for MIME conversions
+** D -- the directory to run in
+** E -- the eol string
+** F -- the flags associated with the mailer
+** L -- the maximum line length
+** M -- the maximum message size
+** N -- the niceness at which to run
+** P -- the path to the mailer
+** Q -- the queue group for the mailer
+** R -- the recipient rewriting set
+** S -- the sender rewriting set
+** T -- the mailer type (for DSNs)
+** U -- the uid to run as
+** W -- the time to wait at the end
+** m -- maximum messages per connection
+** r -- maximum number of recipients per message
+** / -- new root directory
+** The first word is the canonical name of the mailer.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** enters the mailer into the mailer table.
+*/
+
+void
+makemailer(line)
+ char *line;
+{
+ register char *p;
+ register struct mailer *m;
+ register STAB *s;
+ int i;
+ char fcode;
+ auto char *endp;
+ static int nextmailer = 0; /* "free" index into Mailer struct */
+
+ /* allocate a mailer and set up defaults */
+ m = (struct mailer *) xalloc(sizeof *m);
+ memset((char *) m, '\0', sizeof *m);
+ errno = 0; /* avoid bogus error text */
+
+ /* collect the mailer name */
+ for (p = line;
+ *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p));
+ p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ if (line[0] == '\0')
+ {
+ syserr("name required for mailer");
+ return;
+ }
+ m->m_name = newstr(line);
+ m->m_qgrp = NOQGRP;
+
+ /* now scan through and assign info from the fields */
+ while (*p != '\0')
+ {
+ auto char *delimptr;
+
+ while (*p != '\0' &&
+ (*p == ',' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ /* p now points to field code */
+ fcode = *p;
+ while (*p != '\0' && *p != '=' && *p != ',')
+ p++;
+ if (*p++ != '=')
+ {
+ syserr("mailer %s: `=' expected", m->m_name);
+ return;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* p now points to the field body */
+ p = munchstring(p, &delimptr, ',');
+
+ /* install the field into the mailer struct */
+ switch (fcode)
+ {
+ case 'P': /* pathname */
+ if (*p != '\0') /* error is issued below */
+ m->m_mailer = newstr(p);
+ break;
+
+ case 'F': /* flags */
+ for (; *p != '\0'; p++)
+ {
+ if (!(isascii(*p) && isspace(*p)))
+ {
+#if _FFR_DEPRECATE_MAILER_FLAG_I
+ if (*p == M_INTERNAL)
+ sm_syslog(LOG_WARNING, NOQID,
+ "WARNING: mailer=%s, flag=%c deprecated",
+ m->m_name, *p);
+#endif /* _FFR_DEPRECATE_MAILER_FLAG_I */
+ setbitn(bitidx(*p), m->m_flags);
+ }
+ }
+ break;
+
+ case 'S': /* sender rewriting ruleset */
+ case 'R': /* recipient rewriting ruleset */
+ i = strtorwset(p, &endp, ST_ENTER);
+ if (i < 0)
+ return;
+ if (fcode == 'S')
+ m->m_sh_rwset = m->m_se_rwset = i;
+ else
+ m->m_rh_rwset = m->m_re_rwset = i;
+
+ p = endp;
+ if (*p++ == '/')
+ {
+ i = strtorwset(p, NULL, ST_ENTER);
+ if (i < 0)
+ return;
+ if (fcode == 'S')
+ m->m_sh_rwset = i;
+ else
+ m->m_rh_rwset = i;
+ }
+ break;
+
+ case 'E': /* end of line string */
+ if (*p == '\0')
+ syserr("mailer %s: null end-of-line string",
+ m->m_name);
+ else
+ m->m_eol = newstr(p);
+ break;
+
+ case 'A': /* argument vector */
+ if (*p != '\0') /* error is issued below */
+ m->m_argv = makeargv(p);
+ break;
+
+ case 'M': /* maximum message size */
+ m->m_maxsize = atol(p);
+ break;
+
+ case 'm': /* maximum messages per connection */
+ m->m_maxdeliveries = atoi(p);
+ break;
+
+ case 'r': /* max recipient per envelope */
+ m->m_maxrcpt = atoi(p);
+ break;
+
+ case 'L': /* maximum line length */
+ m->m_linelimit = atoi(p);
+ if (m->m_linelimit < 0)
+ m->m_linelimit = 0;
+ break;
+
+ case 'N': /* run niceness */
+ m->m_nice = atoi(p);
+ break;
+
+ case 'D': /* working directory */
+ if (*p == '\0')
+ syserr("mailer %s: null working directory",
+ m->m_name);
+ else
+ m->m_execdir = newstr(p);
+ break;
+
+ case 'C': /* default charset */
+ if (*p == '\0')
+ syserr("mailer %s: null charset", m->m_name);
+ else
+ m->m_defcharset = newstr(p);
+ break;
+
+ case 'Q': /* queue for this mailer */
+ if (*p == '\0')
+ {
+ syserr("mailer %s: null queue", m->m_name);
+ break;
+ }
+ s = stab(p, ST_QUEUE, ST_FIND);
+ if (s == NULL)
+ syserr("mailer %s: unknown queue %s",
+ m->m_name, p);
+ else
+ m->m_qgrp = s->s_quegrp->qg_index;
+ break;
+
+ case 'T': /* MTA-Name/Address/Diagnostic types */
+ /* extract MTA name type; default to "dns" */
+ m->m_mtatype = newstr(p);
+ p = strchr(m->m_mtatype, '/');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ if (*p == '\0')
+ p = NULL;
+ }
+ if (*m->m_mtatype == '\0')
+ m->m_mtatype = "dns";
+
+ /* extract address type; default to "rfc822" */
+ m->m_addrtype = p;
+ if (p != NULL)
+ p = strchr(p, '/');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ if (*p == '\0')
+ p = NULL;
+ }
+ if (m->m_addrtype == NULL || *m->m_addrtype == '\0')
+ m->m_addrtype = "rfc822";
+
+ /* extract diagnostic type; default to "smtp" */
+ m->m_diagtype = p;
+ if (m->m_diagtype == NULL || *m->m_diagtype == '\0')
+ m->m_diagtype = "smtp";
+ break;
+
+ case 'U': /* user id */
+ if (isascii(*p) && !isdigit(*p))
+ {
+ char *q = p;
+ struct passwd *pw;
+
+ while (*p != '\0' && isascii(*p) &&
+ (isalnum(*p) || strchr("-_", *p) != NULL))
+ p++;
+ while (isascii(*p) && isspace(*p))
+ *p++ = '\0';
+ if (*p != '\0')
+ *p++ = '\0';
+ if (*q == '\0')
+ {
+ syserr("mailer %s: null user name",
+ m->m_name);
+ break;
+ }
+ pw = sm_getpwnam(q);
+ if (pw == NULL)
+ {
+ syserr("readcf: mailer U= flag: unknown user %s", q);
+ break;
+ }
+ else
+ {
+ m->m_uid = pw->pw_uid;
+ m->m_gid = pw->pw_gid;
+ }
+ }
+ else
+ {
+ auto char *q;
+
+ m->m_uid = strtol(p, &q, 0);
+ p = q;
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p != '\0')
+ p++;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ break;
+ if (isascii(*p) && !isdigit(*p))
+ {
+ char *q = p;
+ struct group *gr;
+
+ while (isascii(*p) && isalnum(*p))
+ p++;
+ *p++ = '\0';
+ if (*q == '\0')
+ {
+ syserr("mailer %s: null group name",
+ m->m_name);
+ break;
+ }
+ gr = getgrnam(q);
+ if (gr == NULL)
+ {
+ syserr("readcf: mailer U= flag: unknown group %s", q);
+ break;
+ }
+ else
+ m->m_gid = gr->gr_gid;
+ }
+ else
+ {
+ m->m_gid = strtol(p, NULL, 0);
+ }
+ break;
+
+ case 'W': /* wait timeout */
+ m->m_wait = convtime(p, 's');
+ break;
+
+ case '/': /* new root directory */
+ if (*p == '\0')
+ syserr("mailer %s: null root directory",
+ m->m_name);
+ else
+ m->m_rootdir = newstr(p);
+ break;
+
+ default:
+ syserr("M%s: unknown mailer equate %c=",
+ m->m_name, fcode);
+ break;
+ }
+
+ p = delimptr;
+ }
+
+#if !HASRRESVPORT
+ if (bitnset(M_SECURE_PORT, m->m_flags))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "M%s: Warning: F=%c set on system that doesn't support rresvport()\n",
+ m->m_name, M_SECURE_PORT);
+ }
+#endif /* !HASRRESVPORT */
+
+#if !HASNICE
+ if (m->m_nice != 0)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "M%s: Warning: N= set on system that doesn't support nice()\n",
+ m->m_name);
+ }
+#endif /* !HASNICE */
+
+ /* do some rationality checking */
+ if (m->m_argv == NULL)
+ {
+ syserr("M%s: A= argument required", m->m_name);
+ return;
+ }
+ if (m->m_mailer == NULL)
+ {
+ syserr("M%s: P= argument required", m->m_name);
+ return;
+ }
+
+ if (nextmailer >= MAXMAILERS)
+ {
+ syserr("too many mailers defined (%d max)", MAXMAILERS);
+ return;
+ }
+
+ if (m->m_maxrcpt <= 0)
+ m->m_maxrcpt = DEFAULT_MAX_RCPT;
+
+ /* do some heuristic cleanup for back compatibility */
+ if (bitnset(M_LIMITS, m->m_flags))
+ {
+ if (m->m_linelimit == 0)
+ m->m_linelimit = SMTPLINELIM;
+ if (ConfigLevel < 2)
+ setbitn(M_7BITS, m->m_flags);
+ }
+
+ if (strcmp(m->m_mailer, "[TCP]") == 0)
+ {
+ syserr("M%s: P=[TCP] must be replaced by P=[IPC]", m->m_name);
+ return;
+ }
+
+ if (strcmp(m->m_mailer, "[IPC]") == 0)
+ {
+ /* Use the second argument for host or path to socket */
+ if (m->m_argv[0] == NULL || m->m_argv[1] == NULL ||
+ m->m_argv[1][0] == '\0')
+ {
+ syserr("M%s: too few parameters for %s mailer",
+ m->m_name, m->m_mailer);
+ return;
+ }
+ if (strcmp(m->m_argv[0], "TCP") != 0
+#if NETUNIX
+ && strcmp(m->m_argv[0], "FILE") != 0
+#endif /* NETUNIX */
+ )
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "M%s: Warning: first argument in %s mailer must be %s\n",
+ m->m_name, m->m_mailer,
+#if NETUNIX
+ "TCP or FILE"
+#else /* NETUNIX */
+ "TCP"
+#endif /* NETUNIX */
+ );
+ }
+ if (m->m_mtatype == NULL)
+ m->m_mtatype = "dns";
+ if (m->m_addrtype == NULL)
+ m->m_addrtype = "rfc822";
+ if (m->m_diagtype == NULL)
+ {
+ if (m->m_argv[0] != NULL &&
+ strcmp(m->m_argv[0], "FILE") == 0)
+ m->m_diagtype = "x-unix";
+ else
+ m->m_diagtype = "smtp";
+ }
+ }
+ else if (strcmp(m->m_mailer, "[FILE]") == 0)
+ {
+ /* Use the second argument for filename */
+ if (m->m_argv[0] == NULL || m->m_argv[1] == NULL ||
+ m->m_argv[2] != NULL)
+ {
+ syserr("M%s: too %s parameters for [FILE] mailer",
+ m->m_name,
+ (m->m_argv[0] == NULL ||
+ m->m_argv[1] == NULL) ? "few" : "many");
+ return;
+ }
+ else if (strcmp(m->m_argv[0], "FILE") != 0)
+ {
+ syserr("M%s: first argument in [FILE] mailer must be FILE",
+ m->m_name);
+ return;
+ }
+ }
+
+ if (m->m_eol == NULL)
+ {
+ char **pp;
+
+ /* default for SMTP is \r\n; use \n for local delivery */
+ for (pp = m->m_argv; *pp != NULL; pp++)
+ {
+ for (p = *pp; *p != '\0'; )
+ {
+ if ((*p++ & 0377) == MACROEXPAND && *p == 'u')
+ break;
+ }
+ if (*p != '\0')
+ break;
+ }
+ if (*pp == NULL)
+ m->m_eol = "\r\n";
+ else
+ m->m_eol = "\n";
+ }
+
+ /* enter the mailer into the symbol table */
+ s = stab(m->m_name, ST_MAILER, ST_ENTER);
+ if (s->s_mailer != NULL)
+ {
+ i = s->s_mailer->m_mno;
+ sm_free(s->s_mailer); /* XXX */
+ }
+ else
+ {
+ i = nextmailer++;
+ }
+ Mailer[i] = s->s_mailer = m;
+ m->m_mno = i;
+}
+/*
+** MUNCHSTRING -- translate a string into internal form.
+**
+** Parameters:
+** p -- the string to munch.
+** delimptr -- if non-NULL, set to the pointer of the
+** field delimiter character.
+** delim -- the delimiter for the field.
+**
+** Returns:
+** the munched string.
+**
+** Side Effects:
+** the munched string is a local static buffer.
+** it must be copied before the function is called again.
+*/
+
+char *
+munchstring(p, delimptr, delim)
+ register char *p;
+ char **delimptr;
+ int delim;
+{
+ register char *q;
+ bool backslash = false;
+ bool quotemode = false;
+ static char buf[MAXLINE];
+
+ for (q = buf; *p != '\0' && q < &buf[sizeof buf - 1]; p++)
+ {
+ if (backslash)
+ {
+ /* everything is roughly literal */
+ backslash = false;
+ switch (*p)
+ {
+ case 'r': /* carriage return */
+ *q++ = '\r';
+ continue;
+
+ case 'n': /* newline */
+ *q++ = '\n';
+ continue;
+
+ case 'f': /* form feed */
+ *q++ = '\f';
+ continue;
+
+ case 'b': /* backspace */
+ *q++ = '\b';
+ continue;
+ }
+ *q++ = *p;
+ }
+ else
+ {
+ if (*p == '\\')
+ backslash = true;
+ else if (*p == '"')
+ quotemode = !quotemode;
+ else if (quotemode || *p != delim)
+ *q++ = *p;
+ else
+ break;
+ }
+ }
+
+ if (delimptr != NULL)
+ *delimptr = p;
+ *q++ = '\0';
+ return buf;
+}
+/*
+** EXTRQUOTSTR -- extract a (quoted) string.
+**
+** This routine deals with quoted (") strings and escaped
+** spaces (\\ ).
+**
+** Parameters:
+** p -- source string.
+** delimptr -- if non-NULL, set to the pointer of the
+** field delimiter character.
+** delimbuf -- delimiters for the field.
+** st -- if non-NULL, store the return value (whether the
+** string was correctly quoted) here.
+**
+** Returns:
+** the extracted string.
+**
+** Side Effects:
+** the returned string is a local static buffer.
+** it must be copied before the function is called again.
+*/
+
+static char *
+extrquotstr(p, delimptr, delimbuf, st)
+ register char *p;
+ char **delimptr;
+ char *delimbuf;
+ bool *st;
+{
+ register char *q;
+ bool backslash = false;
+ bool quotemode = false;
+ static char buf[MAXLINE];
+
+ for (q = buf; *p != '\0' && q < &buf[sizeof buf - 1]; p++)
+ {
+ if (backslash)
+ {
+ backslash = false;
+ if (*p != ' ')
+ *q++ = '\\';
+ }
+ if (*p == '\\')
+ backslash = true;
+ else if (*p == '"')
+ quotemode = !quotemode;
+ else if (quotemode ||
+ strchr(delimbuf, (int) *p) == NULL)
+ *q++ = *p;
+ else
+ break;
+ }
+
+ if (delimptr != NULL)
+ *delimptr = p;
+ *q++ = '\0';
+ if (st != NULL)
+ *st = !(quotemode || backslash);
+ return buf;
+}
+/*
+** MAKEARGV -- break up a string into words
+**
+** Parameters:
+** p -- the string to break up.
+**
+** Returns:
+** a char **argv (dynamically allocated)
+**
+** Side Effects:
+** munges p.
+*/
+
+static char **
+makeargv(p)
+ register char *p;
+{
+ char *q;
+ int i;
+ char **avp;
+ char *argv[MAXPV + 1];
+
+ /* take apart the words */
+ i = 0;
+ while (*p != '\0' && i < MAXPV)
+ {
+ q = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ while (isascii(*p) && isspace(*p))
+ *p++ = '\0';
+ argv[i++] = newstr(q);
+ }
+ argv[i++] = NULL;
+
+ /* now make a copy of the argv */
+ avp = (char **) xalloc(sizeof *avp * i);
+ memmove((char *) avp, (char *) argv, sizeof *avp * i);
+
+ return avp;
+}
+/*
+** PRINTRULES -- print rewrite rules (for debugging)
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** prints rewrite rules.
+*/
+
+void
+printrules()
+{
+ register struct rewrite *rwp;
+ register int ruleset;
+
+ for (ruleset = 0; ruleset < 10; ruleset++)
+ {
+ if (RewriteRules[ruleset] == NULL)
+ continue;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\n----Rule Set %d:", ruleset);
+
+ for (rwp = RewriteRules[ruleset]; rwp != NULL; rwp = rwp->r_next)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\nLHS:");
+ printav(rwp->r_lhs);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "RHS:");
+ printav(rwp->r_rhs);
+ }
+ }
+}
+/*
+** PRINTMAILER -- print mailer structure (for debugging)
+**
+** Parameters:
+** m -- the mailer to print
+**
+** Returns:
+** none.
+*/
+
+void
+printmailer(m)
+ register MAILER *m;
+{
+ int j;
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "mailer %d (%s): P=%s S=", m->m_mno, m->m_name,
+ m->m_mailer);
+ if (RuleSetNames[m->m_se_rwset] == NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%d/",
+ m->m_se_rwset);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s/",
+ RuleSetNames[m->m_se_rwset]);
+ if (RuleSetNames[m->m_sh_rwset] == NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%d R=",
+ m->m_sh_rwset);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s R=",
+ RuleSetNames[m->m_sh_rwset]);
+ if (RuleSetNames[m->m_re_rwset] == NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%d/",
+ m->m_re_rwset);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s/",
+ RuleSetNames[m->m_re_rwset]);
+ if (RuleSetNames[m->m_rh_rwset] == NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%d ",
+ m->m_rh_rwset);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s ",
+ RuleSetNames[m->m_rh_rwset]);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "M=%ld U=%d:%d F=",
+ m->m_maxsize, (int) m->m_uid, (int) m->m_gid);
+ for (j = '\0'; j <= '\177'; j++)
+ if (bitnset(j, m->m_flags))
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, j);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " L=%d E=",
+ m->m_linelimit);
+ xputs(m->m_eol);
+ if (m->m_defcharset != NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " C=%s",
+ m->m_defcharset);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " T=%s/%s/%s",
+ m->m_mtatype == NULL
+ ? "<undefined>" : m->m_mtatype,
+ m->m_addrtype == NULL
+ ? "<undefined>" : m->m_addrtype,
+ m->m_diagtype == NULL
+ ? "<undefined>" : m->m_diagtype);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " r=%d", m->m_maxrcpt);
+ if (m->m_argv != NULL)
+ {
+ char **a = m->m_argv;
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " A=");
+ while (*a != NULL)
+ {
+ if (a != m->m_argv)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " ");
+ xputs(*a++);
+ }
+ }
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\n");
+}
+/*
+** SETOPTION -- set global processing option
+**
+** Parameters:
+** opt -- option name.
+** val -- option value (as a text string).
+** safe -- set if this came from a configuration file.
+** Some options (if set from the command line) will
+** reset the user id to avoid security problems.
+** sticky -- if set, don't let other setoptions override
+** this value.
+** e -- the main envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets options as implied by the arguments.
+*/
+
+static BITMAP256 StickyOpt; /* set if option is stuck */
+
+#if NAMED_BIND
+
+static struct resolverflags
+{
+ char *rf_name; /* name of the flag */
+ long rf_bits; /* bits to set/clear */
+} ResolverFlags[] =
+{
+ { "debug", RES_DEBUG },
+ { "aaonly", RES_AAONLY },
+ { "usevc", RES_USEVC },
+ { "primary", RES_PRIMARY },
+ { "igntc", RES_IGNTC },
+ { "recurse", RES_RECURSE },
+ { "defnames", RES_DEFNAMES },
+ { "stayopen", RES_STAYOPEN },
+ { "dnsrch", RES_DNSRCH },
+# ifdef RES_USE_INET6
+ { "use_inet6", RES_USE_INET6 },
+# endif /* RES_USE_INET6 */
+ { "true", 0 }, /* avoid error on old syntax */
+ { NULL, 0 }
+};
+
+#endif /* NAMED_BIND */
+
+#define OI_NONE 0 /* no special treatment */
+#define OI_SAFE 0x0001 /* safe for random people to use */
+#define OI_SUBOPT 0x0002 /* option has suboptions */
+
+static struct optioninfo
+{
+ char *o_name; /* long name of option */
+ unsigned char o_code; /* short name of option */
+ unsigned short o_flags; /* option flags */
+} OptionTab[] =
+{
+#if defined(SUN_EXTENSIONS) && defined(REMOTE_MODE)
+ { "RemoteMode", '>', OI_NONE },
+#endif /* defined(SUN_EXTENSIONS) && defined(REMOTE_MODE) */
+ { "SevenBitInput", '7', OI_SAFE },
+ { "EightBitMode", '8', OI_SAFE },
+ { "AliasFile", 'A', OI_NONE },
+ { "AliasWait", 'a', OI_NONE },
+ { "BlankSub", 'B', OI_NONE },
+ { "MinFreeBlocks", 'b', OI_SAFE },
+ { "CheckpointInterval", 'C', OI_SAFE },
+ { "HoldExpensive", 'c', OI_NONE },
+ { "DeliveryMode", 'd', OI_SAFE },
+ { "ErrorHeader", 'E', OI_NONE },
+ { "ErrorMode", 'e', OI_SAFE },
+ { "TempFileMode", 'F', OI_NONE },
+ { "SaveFromLine", 'f', OI_NONE },
+ { "MatchGECOS", 'G', OI_NONE },
+
+ /* no long name, just here to avoid problems in setoption */
+ { "", 'g', OI_NONE },
+ { "HelpFile", 'H', OI_NONE },
+ { "MaxHopCount", 'h', OI_NONE },
+ { "ResolverOptions", 'I', OI_NONE },
+ { "IgnoreDots", 'i', OI_SAFE },
+ { "ForwardPath", 'J', OI_NONE },
+ { "SendMimeErrors", 'j', OI_SAFE },
+ { "ConnectionCacheSize", 'k', OI_NONE },
+ { "ConnectionCacheTimeout", 'K', OI_NONE },
+ { "UseErrorsTo", 'l', OI_NONE },
+ { "LogLevel", 'L', OI_SAFE },
+ { "MeToo", 'm', OI_SAFE },
+
+ /* no long name, just here to avoid problems in setoption */
+ { "", 'M', OI_NONE },
+ { "CheckAliases", 'n', OI_NONE },
+ { "OldStyleHeaders", 'o', OI_SAFE },
+ { "DaemonPortOptions", 'O', OI_NONE },
+ { "PrivacyOptions", 'p', OI_SAFE },
+ { "PostmasterCopy", 'P', OI_NONE },
+ { "QueueFactor", 'q', OI_NONE },
+ { "QueueDirectory", 'Q', OI_NONE },
+ { "DontPruneRoutes", 'R', OI_NONE },
+ { "Timeout", 'r', OI_SUBOPT },
+ { "StatusFile", 'S', OI_NONE },
+ { "SuperSafe", 's', OI_SAFE },
+ { "QueueTimeout", 'T', OI_NONE },
+ { "TimeZoneSpec", 't', OI_NONE },
+ { "UserDatabaseSpec", 'U', OI_NONE },
+ { "DefaultUser", 'u', OI_NONE },
+ { "FallbackMXhost", 'V', OI_NONE },
+ { "Verbose", 'v', OI_SAFE },
+ { "TryNullMXList", 'w', OI_NONE },
+ { "QueueLA", 'x', OI_NONE },
+ { "RefuseLA", 'X', OI_NONE },
+ { "RecipientFactor", 'y', OI_NONE },
+ { "ForkEachJob", 'Y', OI_NONE },
+ { "ClassFactor", 'z', OI_NONE },
+ { "RetryFactor", 'Z', OI_NONE },
+#define O_QUEUESORTORD 0x81
+ { "QueueSortOrder", O_QUEUESORTORD, OI_SAFE },
+#define O_HOSTSFILE 0x82
+ { "HostsFile", O_HOSTSFILE, OI_NONE },
+#define O_MQA 0x83
+ { "MinQueueAge", O_MQA, OI_SAFE },
+#define O_DEFCHARSET 0x85
+ { "DefaultCharSet", O_DEFCHARSET, OI_SAFE },
+#define O_SSFILE 0x86
+ { "ServiceSwitchFile", O_SSFILE, OI_NONE },
+#define O_DIALDELAY 0x87
+ { "DialDelay", O_DIALDELAY, OI_SAFE },
+#define O_NORCPTACTION 0x88
+ { "NoRecipientAction", O_NORCPTACTION, OI_SAFE },
+#define O_SAFEFILEENV 0x89
+ { "SafeFileEnvironment", O_SAFEFILEENV, OI_NONE },
+#define O_MAXMSGSIZE 0x8a
+ { "MaxMessageSize", O_MAXMSGSIZE, OI_NONE },
+#define O_COLONOKINADDR 0x8b
+ { "ColonOkInAddr", O_COLONOKINADDR, OI_SAFE },
+#define O_MAXQUEUERUN 0x8c
+ { "MaxQueueRunSize", O_MAXQUEUERUN, OI_SAFE },
+#define O_MAXCHILDREN 0x8d
+ { "MaxDaemonChildren", O_MAXCHILDREN, OI_NONE },
+#define O_KEEPCNAMES 0x8e
+ { "DontExpandCnames", O_KEEPCNAMES, OI_NONE },
+#define O_MUSTQUOTE 0x8f
+ { "MustQuoteChars", O_MUSTQUOTE, OI_NONE },
+#define O_SMTPGREETING 0x90
+ { "SmtpGreetingMessage", O_SMTPGREETING, OI_NONE },
+#define O_UNIXFROM 0x91
+ { "UnixFromLine", O_UNIXFROM, OI_NONE },
+#define O_OPCHARS 0x92
+ { "OperatorChars", O_OPCHARS, OI_NONE },
+#define O_DONTINITGRPS 0x93
+ { "DontInitGroups", O_DONTINITGRPS, OI_NONE },
+#define O_SLFH 0x94
+ { "SingleLineFromHeader", O_SLFH, OI_SAFE },
+#define O_ABH 0x95
+ { "AllowBogusHELO", O_ABH, OI_SAFE },
+#define O_CONNTHROT 0x97
+ { "ConnectionRateThrottle", O_CONNTHROT, OI_NONE },
+#define O_UGW 0x99
+ { "UnsafeGroupWrites", O_UGW, OI_NONE },
+#define O_DBLBOUNCE 0x9a
+ { "DoubleBounceAddress", O_DBLBOUNCE, OI_NONE },
+#define O_HSDIR 0x9b
+ { "HostStatusDirectory", O_HSDIR, OI_NONE },
+#define O_SINGTHREAD 0x9c
+ { "SingleThreadDelivery", O_SINGTHREAD, OI_NONE },
+#define O_RUNASUSER 0x9d
+ { "RunAsUser", O_RUNASUSER, OI_NONE },
+#define O_DSN_RRT 0x9e
+ { "RrtImpliesDsn", O_DSN_RRT, OI_NONE },
+#define O_PIDFILE 0x9f
+ { "PidFile", O_PIDFILE, OI_NONE },
+#define O_DONTBLAMESENDMAIL 0xa0
+ { "DontBlameSendmail", O_DONTBLAMESENDMAIL, OI_NONE },
+#define O_DPI 0xa1
+ { "DontProbeInterfaces", O_DPI, OI_NONE },
+#define O_MAXRCPT 0xa2
+ { "MaxRecipientsPerMessage", O_MAXRCPT, OI_SAFE },
+#define O_DEADLETTER 0xa3
+ { "DeadLetterDrop", O_DEADLETTER, OI_NONE },
+#if _FFR_DONTLOCKFILESFORREAD_OPTION
+# define O_DONTLOCK 0xa4
+ { "DontLockFilesForRead", O_DONTLOCK, OI_NONE },
+#endif /* _FFR_DONTLOCKFILESFORREAD_OPTION */
+#define O_MAXALIASRCSN 0xa5
+ { "MaxAliasRecursion", O_MAXALIASRCSN, OI_NONE },
+#define O_CNCTONLYTO 0xa6
+ { "ConnectOnlyTo", O_CNCTONLYTO, OI_NONE },
+#define O_TRUSTUSER 0xa7
+ { "TrustedUser", O_TRUSTUSER, OI_NONE },
+#define O_MAXMIMEHDRLEN 0xa8
+ { "MaxMimeHeaderLength", O_MAXMIMEHDRLEN, OI_NONE },
+#define O_CONTROLSOCKET 0xa9
+ { "ControlSocketName", O_CONTROLSOCKET, OI_NONE },
+#define O_MAXHDRSLEN 0xaa
+ { "MaxHeadersLength", O_MAXHDRSLEN, OI_NONE },
+#if _FFR_MAX_FORWARD_ENTRIES
+# define O_MAXFORWARD 0xab
+ { "MaxForwardEntries", O_MAXFORWARD, OI_NONE },
+#endif /* _FFR_MAX_FORWARD_ENTRIES */
+#define O_PROCTITLEPREFIX 0xac
+ { "ProcessTitlePrefix", O_PROCTITLEPREFIX, OI_NONE },
+#define O_SASLINFO 0xad
+#if _FFR_ALLOW_SASLINFO
+ { "DefaultAuthInfo", O_SASLINFO, OI_SAFE },
+#else /* _FFR_ALLOW_SASLINFO */
+ { "DefaultAuthInfo", O_SASLINFO, OI_NONE },
+#endif /* _FFR_ALLOW_SASLINFO */
+#define O_SASLMECH 0xae
+ { "AuthMechanisms", O_SASLMECH, OI_NONE },
+#define O_CLIENTPORT 0xaf
+ { "ClientPortOptions", O_CLIENTPORT, OI_NONE },
+#define O_DF_BUFSIZE 0xb0
+ { "DataFileBufferSize", O_DF_BUFSIZE, OI_NONE },
+#define O_XF_BUFSIZE 0xb1
+ { "XscriptFileBufferSize", O_XF_BUFSIZE, OI_NONE },
+#define O_LDAPDEFAULTSPEC 0xb2
+ { "LDAPDefaultSpec", O_LDAPDEFAULTSPEC, OI_NONE },
+#if _FFR_QUEUEDELAY
+# define O_QUEUEDELAY 0xb3
+ { "QueueDelay", O_QUEUEDELAY, OI_NONE },
+#endif /* _FFR_QUEUEDELAY */
+#define O_SRVCERTFILE 0xb4
+ { "ServerCertFile", O_SRVCERTFILE, OI_NONE },
+#define O_SRVKEYFILE 0xb5
+ { "ServerKeyFile", O_SRVKEYFILE, OI_NONE },
+#define O_CLTCERTFILE 0xb6
+ { "ClientCertFile", O_CLTCERTFILE, OI_NONE },
+#define O_CLTKEYFILE 0xb7
+ { "ClientKeyFile", O_CLTKEYFILE, OI_NONE },
+#define O_CACERTFILE 0xb8
+ { "CACERTFile", O_CACERTFILE, OI_NONE },
+#define O_CACERTPATH 0xb9
+ { "CACERTPath", O_CACERTPATH, OI_NONE },
+#define O_DHPARAMS 0xba
+ { "DHParameters", O_DHPARAMS, OI_NONE },
+#define O_INPUTMILTER 0xbb
+ { "InputMailFilters", O_INPUTMILTER, OI_NONE },
+#define O_MILTER 0xbc
+ { "Milter", O_MILTER, OI_SUBOPT },
+#define O_SASLOPTS 0xbd
+ { "AuthOptions", O_SASLOPTS, OI_NONE },
+#define O_QUEUE_FILE_MODE 0xbe
+ { "QueueFileMode", O_QUEUE_FILE_MODE, OI_NONE },
+#if _FFR_TLS_1
+# define O_DHPARAMS5 0xbf
+ { "DHParameters512", O_DHPARAMS5, OI_NONE },
+# define O_CIPHERLIST 0xc0
+ { "CipherList", O_CIPHERLIST, OI_NONE },
+#endif /* _FFR_TLS_1 */
+#define O_RANDFILE 0xc1
+ { "RandFile", O_RANDFILE, OI_NONE },
+#define O_TLS_SRV_OPTS 0xc2
+ { "TLSSrvOptions", O_TLS_SRV_OPTS, OI_NONE },
+#define O_RCPTTHROT 0xc3
+ { "BadRcptThrottle", O_RCPTTHROT, OI_SAFE },
+#define O_DLVR_MIN 0xc4
+ { "DeliverByMin", O_DLVR_MIN, OI_NONE },
+#define O_MAXQUEUECHILDREN 0xc5
+ { "MaxQueueChildren", O_MAXQUEUECHILDREN, OI_NONE },
+#define O_MAXRUNNERSPERQUEUE 0xc6
+ { "MaxRunnersPerQueue", O_MAXRUNNERSPERQUEUE, OI_NONE },
+#define O_DIRECTSUBMODIFIERS 0xc7
+ { "DirectSubmissionModifiers", O_DIRECTSUBMODIFIERS, OI_NONE },
+#define O_NICEQUEUERUN 0xc8
+ { "NiceQueueRun", O_NICEQUEUERUN, OI_NONE },
+#define O_SHMKEY 0xc9
+ { "SharedMemoryKey", O_SHMKEY, OI_NONE },
+#define O_SASLBITS 0xca
+ { "AuthMaxBits", O_SASLBITS, OI_NONE },
+#define O_MBDB 0xcb
+ { "MailboxDatabase", O_MBDB, OI_NONE },
+#define O_MSQ 0xcc
+ { "UseMSP", O_MSQ, OI_NONE },
+#define O_DELAY_LA 0xcd
+ { "DelayLA", O_DELAY_LA, OI_NONE },
+#define O_FASTSPLIT 0xce
+ { "FastSplit", O_FASTSPLIT, OI_NONE },
+#if _FFR_SOFT_BOUNCE
+# define O_SOFTBOUNCE 0xcf
+ { "SoftBounce", O_SOFTBOUNCE, OI_NONE },
+#endif /* _FFR_SOFT_BOUNCE */
+#if _FFR_SELECT_SHM
+# define O_SHMKEYFILE 0xd0
+ { "SharedMemoryKeyFile", O_SHMKEYFILE, OI_NONE },
+#endif /* _FFR_SELECT_SHM */
+ { NULL, '\0', OI_NONE }
+};
+
+# define CANONIFY(val)
+
+# define SET_OPT_DEFAULT(opt, val) opt = val
+
+/* set a string option by expanding the value and assigning it */
+/* WARNING this belongs ONLY into a case statement! */
+#define SET_STRING_EXP(str) \
+ expand(val, exbuf, sizeof exbuf, e); \
+ newval = sm_pstrdup_x(exbuf); \
+ if (str != NULL) \
+ sm_free(str); \
+ CANONIFY(newval); \
+ str = newval; \
+ break
+
+#define OPTNAME o->o_name == NULL ? "<unknown>" : o->o_name
+
+void
+setoption(opt, val, safe, sticky, e)
+ int opt;
+ char *val;
+ bool safe;
+ bool sticky;
+ register ENVELOPE *e;
+{
+ register char *p;
+ register struct optioninfo *o;
+ char *subopt;
+ int mid;
+ bool can_setuid = RunAsUid == 0;
+ auto char *ep;
+ char buf[50];
+ extern bool Warn_Q_option;
+#if _FFR_ALLOW_SASLINFO
+ extern unsigned int SubmitMode;
+#endif /* _FFR_ALLOW_SASLINFO */
+#if STARTTLS
+ char *newval;
+ char exbuf[MAXLINE];
+#endif /* STARTTLS */
+
+ errno = 0;
+ if (opt == ' ')
+ {
+ /* full word options */
+ struct optioninfo *sel;
+
+ p = strchr(val, '=');
+ if (p == NULL)
+ p = &val[strlen(val)];
+ while (*--p == ' ')
+ continue;
+ while (*++p == ' ')
+ *p = '\0';
+ if (p == val)
+ {
+ syserr("readcf: null option name");
+ return;
+ }
+ if (*p == '=')
+ *p++ = '\0';
+ while (*p == ' ')
+ p++;
+ subopt = strchr(val, '.');
+ if (subopt != NULL)
+ *subopt++ = '\0';
+ sel = NULL;
+ for (o = OptionTab; o->o_name != NULL; o++)
+ {
+ if (sm_strncasecmp(o->o_name, val, strlen(val)) != 0)
+ continue;
+ if (strlen(o->o_name) == strlen(val))
+ {
+ /* completely specified -- this must be it */
+ sel = NULL;
+ break;
+ }
+ if (sel != NULL)
+ break;
+ sel = o;
+ }
+ if (sel != NULL && o->o_name == NULL)
+ o = sel;
+ else if (o->o_name == NULL)
+ {
+ syserr("readcf: unknown option name %s", val);
+ return;
+ }
+ else if (sel != NULL)
+ {
+ syserr("readcf: ambiguous option name %s (matches %s and %s)",
+ val, sel->o_name, o->o_name);
+ return;
+ }
+ if (strlen(val) != strlen(o->o_name))
+ {
+ int oldVerbose = Verbose;
+
+ Verbose = 1;
+ message("Option %s used as abbreviation for %s",
+ val, o->o_name);
+ Verbose = oldVerbose;
+ }
+ opt = o->o_code;
+ val = p;
+ }
+ else
+ {
+ for (o = OptionTab; o->o_name != NULL; o++)
+ {
+ if (o->o_code == opt)
+ break;
+ }
+ if (o->o_name == NULL)
+ {
+ syserr("readcf: unknown option name 0x%x", opt & 0xff);
+ return;
+ }
+ subopt = NULL;
+ }
+
+ if (subopt != NULL && !bitset(OI_SUBOPT, o->o_flags))
+ {
+ if (tTd(37, 1))
+ sm_dprintf("setoption: %s does not support suboptions, ignoring .%s\n",
+ OPTNAME, subopt);
+ subopt = NULL;
+ }
+
+ if (tTd(37, 1))
+ {
+ sm_dprintf(isascii(opt) && isprint(opt) ?
+ "setoption %s (%c)%s%s=" :
+ "setoption %s (0x%x)%s%s=",
+ OPTNAME, opt, subopt == NULL ? "" : ".",
+ subopt == NULL ? "" : subopt);
+ xputs(val);
+ }
+
+ /*
+ ** See if this option is preset for us.
+ */
+
+ if (!sticky && bitnset(opt, StickyOpt))
+ {
+ if (tTd(37, 1))
+ sm_dprintf(" (ignored)\n");
+ return;
+ }
+
+ /*
+ ** Check to see if this option can be specified by this user.
+ */
+
+ if (!safe && RealUid == 0)
+ safe = true;
+ if (!safe && !bitset(OI_SAFE, o->o_flags))
+ {
+ if (opt != 'M' || (val[0] != 'r' && val[0] != 's'))
+ {
+ int dp;
+
+ if (tTd(37, 1))
+ sm_dprintf(" (unsafe)");
+ dp = drop_privileges(true);
+ setstat(dp);
+ }
+ }
+ if (tTd(37, 1))
+ sm_dprintf("\n");
+
+ switch (opt & 0xff)
+ {
+ case '7': /* force seven-bit input */
+ SevenBitInput = atobool(val);
+ break;
+
+ case '8': /* handling of 8-bit input */
+#if MIME8TO7
+ switch (*val)
+ {
+ case 'p': /* pass 8 bit, convert MIME */
+ MimeMode = MM_CVTMIME|MM_PASS8BIT;
+ break;
+
+ case 'm': /* convert 8-bit, convert MIME */
+ MimeMode = MM_CVTMIME|MM_MIME8BIT;
+ break;
+
+ case 's': /* strict adherence */
+ MimeMode = MM_CVTMIME;
+ break;
+
+# if 0
+ case 'r': /* reject 8-bit, don't convert MIME */
+ MimeMode = 0;
+ break;
+
+ case 'j': /* "just send 8" */
+ MimeMode = MM_PASS8BIT;
+ break;
+
+ case 'a': /* encode 8 bit if available */
+ MimeMode = MM_MIME8BIT|MM_PASS8BIT|MM_CVTMIME;
+ break;
+
+ case 'c': /* convert 8 bit to MIME, never 7 bit */
+ MimeMode = MM_MIME8BIT;
+ break;
+# endif /* 0 */
+
+ default:
+ syserr("Unknown 8-bit mode %c", *val);
+ finis(false, true, EX_USAGE);
+ }
+#else /* MIME8TO7 */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires MIME8TO7 support\n",
+ OPTNAME);
+#endif /* MIME8TO7 */
+ break;
+
+ case 'A': /* set default alias file */
+ if (val[0] == '\0')
+ {
+ char *al;
+
+ SET_OPT_DEFAULT(al, "aliases");
+ setalias(al);
+ }
+ else
+ setalias(val);
+ break;
+
+ case 'a': /* look N minutes for "@:@" in alias file */
+ if (val[0] == '\0')
+ SafeAlias = 5 MINUTES;
+ else
+ SafeAlias = convtime(val, 'm');
+ break;
+
+ case 'B': /* substitution for blank character */
+ SpaceSub = val[0];
+ if (SpaceSub == '\0')
+ SpaceSub = ' ';
+ break;
+
+ case 'b': /* min blocks free on queue fs/max msg size */
+ p = strchr(val, '/');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ MaxMessageSize = atol(p);
+ }
+ MinBlocksFree = atol(val);
+ break;
+
+ case 'c': /* don't connect to "expensive" mailers */
+ NoConnect = atobool(val);
+ break;
+
+ case 'C': /* checkpoint every N addresses */
+ CheckpointInterval = atoi(val);
+ break;
+
+ case 'd': /* delivery mode */
+ switch (*val)
+ {
+ case '\0':
+ set_delivery_mode(SM_DELIVER, e);
+ break;
+
+ case SM_QUEUE: /* queue only */
+ case SM_DEFER: /* queue only and defer map lookups */
+ case SM_DELIVER: /* do everything */
+ case SM_FORK: /* fork after verification */
+ set_delivery_mode(*val, e);
+ break;
+
+ default:
+ syserr("Unknown delivery mode %c", *val);
+ finis(false, true, EX_USAGE);
+ }
+ break;
+
+ case 'E': /* error message header/header file */
+ if (*val != '\0')
+ ErrMsgFile = newstr(val);
+ break;
+
+ case 'e': /* set error processing mode */
+ switch (*val)
+ {
+ case EM_QUIET: /* be silent about it */
+ case EM_MAIL: /* mail back */
+ case EM_BERKNET: /* do berknet error processing */
+ case EM_WRITE: /* write back (or mail) */
+ case EM_PRINT: /* print errors normally (default) */
+ e->e_errormode = *val;
+ break;
+ }
+ break;
+
+ case 'F': /* file mode */
+ FileMode = atooct(val) & 0777;
+ break;
+
+ case 'f': /* save Unix-style From lines on front */
+ SaveFrom = atobool(val);
+ break;
+
+ case 'G': /* match recipients against GECOS field */
+ MatchGecos = atobool(val);
+ break;
+
+ case 'g': /* default gid */
+ g_opt:
+ if (isascii(*val) && isdigit(*val))
+ DefGid = atoi(val);
+ else
+ {
+ register struct group *gr;
+
+ DefGid = -1;
+ gr = getgrnam(val);
+ if (gr == NULL)
+ syserr("readcf: option %c: unknown group %s",
+ opt, val);
+ else
+ DefGid = gr->gr_gid;
+ }
+ break;
+
+ case 'H': /* help file */
+ if (val[0] == '\0')
+ {
+ SET_OPT_DEFAULT(HelpFile, "helpfile");
+ }
+ else
+ {
+ CANONIFY(val);
+ HelpFile = newstr(val);
+ }
+ break;
+
+ case 'h': /* maximum hop count */
+ MaxHopCount = atoi(val);
+ break;
+
+ case 'I': /* use internet domain name server */
+#if NAMED_BIND
+ for (p = val; *p != 0; )
+ {
+ bool clearmode;
+ char *q;
+ struct resolverflags *rfp;
+
+ while (*p == ' ')
+ p++;
+ if (*p == '\0')
+ break;
+ clearmode = false;
+ if (*p == '-')
+ clearmode = true;
+ else if (*p != '+')
+ p--;
+ p++;
+ q = p;
+ while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+ if (sm_strcasecmp(q, "HasWildcardMX") == 0)
+ {
+ HasWildcardMX = !clearmode;
+ continue;
+ }
+ if (sm_strcasecmp(q, "WorkAroundBrokenAAAA") == 0)
+ {
+ WorkAroundBrokenAAAA = !clearmode;
+ continue;
+ }
+ for (rfp = ResolverFlags; rfp->rf_name != NULL; rfp++)
+ {
+ if (sm_strcasecmp(q, rfp->rf_name) == 0)
+ break;
+ }
+ if (rfp->rf_name == NULL)
+ syserr("readcf: I option value %s unrecognized", q);
+ else if (clearmode)
+ _res.options &= ~rfp->rf_bits;
+ else
+ _res.options |= rfp->rf_bits;
+ }
+ if (tTd(8, 2))
+ sm_dprintf("_res.options = %x, HasWildcardMX = %d\n",
+ (unsigned int) _res.options, HasWildcardMX);
+#else /* NAMED_BIND */
+ usrerr("name server (I option) specified but BIND not compiled in");
+#endif /* NAMED_BIND */
+ break;
+
+ case 'i': /* ignore dot lines in message */
+ IgnrDot = atobool(val);
+ break;
+
+ case 'j': /* send errors in MIME (RFC 1341) format */
+ SendMIMEErrors = atobool(val);
+ break;
+
+ case 'J': /* .forward search path */
+ CANONIFY(val);
+ ForwardPath = newstr(val);
+ break;
+
+ case 'k': /* connection cache size */
+ MaxMciCache = atoi(val);
+ if (MaxMciCache < 0)
+ MaxMciCache = 0;
+ break;
+
+ case 'K': /* connection cache timeout */
+ MciCacheTimeout = convtime(val, 'm');
+ break;
+
+ case 'l': /* use Errors-To: header */
+ UseErrorsTo = atobool(val);
+ break;
+
+ case 'L': /* log level */
+ if (safe || LogLevel < atoi(val))
+ LogLevel = atoi(val);
+ break;
+
+ case 'M': /* define macro */
+ sticky = false;
+ mid = macid_parse(val, &ep);
+ if (mid == 0)
+ break;
+ p = newstr(ep);
+ if (!safe)
+ cleanstrcpy(p, p, MAXNAME);
+ macdefine(&CurEnv->e_macro, A_TEMP, mid, p);
+ break;
+
+ case 'm': /* send to me too */
+ MeToo = atobool(val);
+ break;
+
+ case 'n': /* validate RHS in newaliases */
+ CheckAliases = atobool(val);
+ break;
+
+ /* 'N' available -- was "net name" */
+
+ case 'O': /* daemon options */
+ if (!setdaemonoptions(val))
+ syserr("too many daemons defined (%d max)", MAXDAEMONS);
+ break;
+
+ case 'o': /* assume old style headers */
+ if (atobool(val))
+ CurEnv->e_flags |= EF_OLDSTYLE;
+ else
+ CurEnv->e_flags &= ~EF_OLDSTYLE;
+ break;
+
+ case 'p': /* select privacy level */
+ p = val;
+ for (;;)
+ {
+ register struct prival *pv;
+ extern struct prival PrivacyValues[];
+
+ while (isascii(*p) && (isspace(*p) || ispunct(*p)))
+ p++;
+ if (*p == '\0')
+ break;
+ val = p;
+ while (isascii(*p) && isalnum(*p))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+
+ for (pv = PrivacyValues; pv->pv_name != NULL; pv++)
+ {
+ if (sm_strcasecmp(val, pv->pv_name) == 0)
+ break;
+ }
+ if (pv->pv_name == NULL)
+ syserr("readcf: Op line: %s unrecognized", val);
+ else
+ PrivacyFlags |= pv->pv_flag;
+ }
+ sticky = false;
+ break;
+
+ case 'P': /* postmaster copy address for returned mail */
+ PostMasterCopy = newstr(val);
+ break;
+
+ case 'q': /* slope of queue only function */
+ QueueFactor = atoi(val);
+ break;
+
+ case 'Q': /* queue directory */
+ if (val[0] == '\0')
+ {
+ QueueDir = "mqueue";
+ }
+ else
+ {
+ QueueDir = newstr(val);
+ }
+ if (RealUid != 0 && !safe)
+ Warn_Q_option = true;
+ break;
+
+ case 'R': /* don't prune routes */
+ DontPruneRoutes = atobool(val);
+ break;
+
+ case 'r': /* read timeout */
+ if (subopt == NULL)
+ inittimeouts(val, sticky);
+ else
+ settimeout(subopt, val, sticky);
+ break;
+
+ case 'S': /* status file */
+ if (val[0] == '\0')
+ {
+ SET_OPT_DEFAULT(StatFile, "statistics");
+ }
+ else
+ {
+ CANONIFY(val);
+ StatFile = newstr(val);
+ }
+ break;
+
+ case 's': /* be super safe, even if expensive */
+ if (tolower(*val) == 'i')
+ SuperSafe = SAFE_INTERACTIVE;
+ else
+ SuperSafe = atobool(val) ? SAFE_REALLY : SAFE_NO;
+ break;
+
+ case 'T': /* queue timeout */
+ p = strchr(val, '/');
+ if (p != NULL)
+ {
+ *p++ = '\0';
+ settimeout("queuewarn", p, sticky);
+ }
+ settimeout("queuereturn", val, sticky);
+ break;
+
+ case 't': /* time zone name */
+ TimeZoneSpec = newstr(val);
+ break;
+
+ case 'U': /* location of user database */
+ UdbSpec = newstr(val);
+ break;
+
+ case 'u': /* set default uid */
+ for (p = val; *p != '\0'; p++)
+ {
+# if _FFR_DOTTED_USERNAMES
+ if (*p == '/' || *p == ':')
+# else /* _FFR_DOTTED_USERNAMES */
+ if (*p == '.' || *p == '/' || *p == ':')
+# endif /* _FFR_DOTTED_USERNAMES */
+ {
+ *p++ = '\0';
+ break;
+ }
+ }
+ if (isascii(*val) && isdigit(*val))
+ {
+ DefUid = atoi(val);
+ setdefuser();
+ }
+ else
+ {
+ register struct passwd *pw;
+
+ DefUid = -1;
+ pw = sm_getpwnam(val);
+ if (pw == NULL)
+ {
+ syserr("readcf: option u: unknown user %s", val);
+ break;
+ }
+ else
+ {
+ DefUid = pw->pw_uid;
+ DefGid = pw->pw_gid;
+ DefUser = newstr(pw->pw_name);
+ }
+ }
+
+# ifdef UID_MAX
+ if (DefUid > UID_MAX)
+ {
+ syserr("readcf: option u: uid value (%ld) > UID_MAX (%ld); ignored",
+ (long)DefUid, (long)UID_MAX);
+ break;
+ }
+# endif /* UID_MAX */
+
+ /* handle the group if it is there */
+ if (*p == '\0')
+ break;
+ val = p;
+ goto g_opt;
+
+ case 'V': /* fallback MX host */
+ if (val[0] != '\0')
+ FallBackMX = newstr(val);
+ break;
+
+ case 'v': /* run in verbose mode */
+ Verbose = atobool(val) ? 1 : 0;
+ break;
+
+ case 'w': /* if we are best MX, try host directly */
+ TryNullMXList = atobool(val);
+ break;
+
+ /* 'W' available -- was wizard password */
+
+ case 'x': /* load avg at which to auto-queue msgs */
+ QueueLA = atoi(val);
+ break;
+
+ case 'X': /* load avg at which to auto-reject connections */
+ RefuseLA = atoi(val);
+ break;
+
+ case O_DELAY_LA: /* load avg at which to delay connections */
+ DelayLA = atoi(val);
+ break;
+
+ case 'y': /* work recipient factor */
+ WkRecipFact = atoi(val);
+ break;
+
+ case 'Y': /* fork jobs during queue runs */
+ ForkQueueRuns = atobool(val);
+ break;
+
+ case 'z': /* work message class factor */
+ WkClassFact = atoi(val);
+ break;
+
+ case 'Z': /* work time factor */
+ WkTimeFact = atoi(val);
+ break;
+
+
+#if _FFR_QUEUE_GROUP_SORTORDER
+ /* coordinate this with makequeue() */
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+ case O_QUEUESORTORD: /* queue sorting order */
+ switch (*val)
+ {
+ case 'f': /* File Name */
+ case 'F':
+ QueueSortOrder = QSO_BYFILENAME;
+ break;
+
+ case 'h': /* Host first */
+ case 'H':
+ QueueSortOrder = QSO_BYHOST;
+ break;
+
+ case 'm': /* Modification time */
+ case 'M':
+ QueueSortOrder = QSO_BYMODTIME;
+ break;
+
+ case 'p': /* Priority order */
+ case 'P':
+ QueueSortOrder = QSO_BYPRIORITY;
+ break;
+
+ case 't': /* Submission time */
+ case 'T':
+ QueueSortOrder = QSO_BYTIME;
+ break;
+
+ case 'r': /* Random */
+ case 'R':
+ QueueSortOrder = QSO_RANDOM;
+ break;
+
+#if _FFR_RHS
+ case 's': /* Shuffled host name */
+ case 'S':
+ QueueSortOrder = QSO_BYSHUFFLE;
+ break;
+#endif /* _FFR_RHS */
+
+ default:
+ syserr("Invalid queue sort order \"%s\"", val);
+ }
+ break;
+
+#if _FFR_QUEUEDELAY
+ case O_QUEUEDELAY: /* queue delay algorithm */
+ switch (*val)
+ {
+ case 'e': /* exponential */
+ case 'E':
+ QueueAlg = QD_EXP;
+ QueueInitDelay = 10 MINUTES;
+ QueueMaxDelay = 2 HOURS;
+ p = strchr(val, '/');
+ if (p != NULL)
+ {
+ char *q;
+
+ *p++ = '\0';
+ q = strchr(p, '/');
+ if (q != NULL)
+ *q++ = '\0';
+ QueueInitDelay = convtime(p, 's');
+ if (q != NULL)
+ {
+ QueueMaxDelay = convtime(q, 's');
+ }
+ }
+ break;
+
+ case 'l': /* linear */
+ case 'L':
+ QueueAlg = QD_LINEAR;
+ break;
+
+ default:
+ syserr("Invalid queue delay algorithm \"%s\"", val);
+ }
+ break;
+#endif /* _FFR_QUEUEDELAY */
+
+ case O_HOSTSFILE: /* pathname of /etc/hosts file */
+ CANONIFY(val);
+ HostsFile = newstr(val);
+ break;
+
+ case O_MQA: /* minimum queue age between deliveries */
+ MinQueueAge = convtime(val, 'm');
+ break;
+
+ case O_DEFCHARSET: /* default character set for mimefying */
+ DefaultCharSet = newstr(denlstring(val, true, true));
+ break;
+
+ case O_SSFILE: /* service switch file */
+ CANONIFY(val);
+ ServiceSwitchFile = newstr(val);
+ break;
+
+ case O_DIALDELAY: /* delay for dial-on-demand operation */
+ DialDelay = convtime(val, 's');
+ break;
+
+ case O_NORCPTACTION: /* what to do if no recipient */
+ if (sm_strcasecmp(val, "none") == 0)
+ NoRecipientAction = NRA_NO_ACTION;
+ else if (sm_strcasecmp(val, "add-to") == 0)
+ NoRecipientAction = NRA_ADD_TO;
+ else if (sm_strcasecmp(val, "add-apparently-to") == 0)
+ NoRecipientAction = NRA_ADD_APPARENTLY_TO;
+ else if (sm_strcasecmp(val, "add-bcc") == 0)
+ NoRecipientAction = NRA_ADD_BCC;
+ else if (sm_strcasecmp(val, "add-to-undisclosed") == 0)
+ NoRecipientAction = NRA_ADD_TO_UNDISCLOSED;
+ else
+ syserr("Invalid NoRecipientAction: %s", val);
+ break;
+
+ case O_SAFEFILEENV: /* chroot() environ for writing to files */
+ if (*val == '\0')
+ break;
+
+ /* strip trailing slashes */
+ p = val + strlen(val) - 1;
+ while (p >= val && *p == '/')
+ *p-- = '\0';
+
+ if (*val == '\0')
+ break;
+
+ SafeFileEnv = newstr(val);
+ break;
+
+ case O_MAXMSGSIZE: /* maximum message size */
+ MaxMessageSize = atol(val);
+ break;
+
+ case O_COLONOKINADDR: /* old style handling of colon addresses */
+ ColonOkInAddr = atobool(val);
+ break;
+
+ case O_MAXQUEUERUN: /* max # of jobs in a single queue run */
+ MaxQueueRun = atoi(val);
+ break;
+
+ case O_MAXCHILDREN: /* max # of children of daemon */
+ MaxChildren = atoi(val);
+ break;
+
+ case O_MAXQUEUECHILDREN: /* max # of children of daemon */
+ MaxQueueChildren = atoi(val);
+ break;
+
+ case O_MAXRUNNERSPERQUEUE: /* max # runners in a queue group */
+ MaxRunnersPerQueue = atoi(val);
+ break;
+
+ case O_NICEQUEUERUN: /* nice queue runs */
+#if !HASNICE
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: NiceQueueRun set on system that doesn't support nice()\n");
+#endif /* !HASNICE */
+
+ /* XXX do we want to check the range? > 0 ? */
+ NiceQueueRun = atoi(val);
+ break;
+
+ case O_SHMKEY: /* shared memory key */
+#if SM_CONF_SHM
+ ShmKey = atol(val);
+#else /* SM_CONF_SHM */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires shared memory support (-DSM_CONF_SHM)\n",
+ OPTNAME);
+#endif /* SM_CONF_SHM */
+ break;
+
+#if _FFR_SELECT_SHM
+ case O_SHMKEYFILE: /* shared memory key file */
+# if SM_CONF_SHM
+ SET_STRING_EXP(ShmKeyFile);
+# else /* SM_CONF_SHM */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires shared memory support (-DSM_CONF_SHM)\n",
+ OPTNAME);
+ break;
+# endif /* SM_CONF_SHM */
+#endif /* _FFR_SELECT_SHM */
+
+#if _FFR_MAX_FORWARD_ENTRIES
+ case O_MAXFORWARD: /* max # of forward entries */
+ MaxForwardEntries = atoi(val);
+ break;
+#endif /* _FFR_MAX_FORWARD_ENTRIES */
+
+ case O_KEEPCNAMES: /* don't expand CNAME records */
+ DontExpandCnames = atobool(val);
+ break;
+
+ case O_MUSTQUOTE: /* must quote these characters in phrases */
+ (void) sm_strlcpy(buf, "@,;:\\()[]", sizeof buf);
+ if (strlen(val) < sizeof buf - 10)
+ (void) sm_strlcat(buf, val, sizeof buf);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: MustQuoteChars too long, ignored.\n");
+ MustQuoteChars = newstr(buf);
+ break;
+
+ case O_SMTPGREETING: /* SMTP greeting message (old $e macro) */
+ SmtpGreeting = newstr(munchstring(val, NULL, '\0'));
+ break;
+
+ case O_UNIXFROM: /* UNIX From_ line (old $l macro) */
+ UnixFromLine = newstr(munchstring(val, NULL, '\0'));
+ break;
+
+ case O_OPCHARS: /* operator characters (old $o macro) */
+ if (OperatorChars != NULL)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: OperatorChars is being redefined.\n It should only be set before ruleset definitions.\n");
+ OperatorChars = newstr(munchstring(val, NULL, '\0'));
+ break;
+
+ case O_DONTINITGRPS: /* don't call initgroups(3) */
+ DontInitGroups = atobool(val);
+ break;
+
+ case O_SLFH: /* make sure from fits on one line */
+ SingleLineFromHeader = atobool(val);
+ break;
+
+ case O_ABH: /* allow HELO commands with syntax errors */
+ AllowBogusHELO = atobool(val);
+ break;
+
+ case O_CONNTHROT: /* connection rate throttle */
+ ConnRateThrottle = atoi(val);
+ break;
+
+ case O_UGW: /* group writable files are unsafe */
+ if (!atobool(val))
+ {
+ setbitn(DBS_GROUPWRITABLEFORWARDFILESAFE,
+ DontBlameSendmail);
+ setbitn(DBS_GROUPWRITABLEINCLUDEFILESAFE,
+ DontBlameSendmail);
+ }
+ break;
+
+ case O_DBLBOUNCE: /* address to which to send double bounces */
+ DoubleBounceAddr = newstr(val);
+ break;
+
+ case O_HSDIR: /* persistent host status directory */
+ if (val[0] != '\0')
+ {
+ CANONIFY(val);
+ HostStatDir = newstr(val);
+ }
+ break;
+
+ case O_SINGTHREAD: /* single thread deliveries (requires hsdir) */
+ SingleThreadDelivery = atobool(val);
+ break;
+
+ case O_RUNASUSER: /* run bulk of code as this user */
+ for (p = val; *p != '\0'; p++)
+ {
+# if _FFR_DOTTED_USERNAMES
+ if (*p == '/' || *p == ':')
+# else /* _FFR_DOTTED_USERNAMES */
+ if (*p == '.' || *p == '/' || *p == ':')
+# endif /* _FFR_DOTTED_USERNAMES */
+ {
+ *p++ = '\0';
+ break;
+ }
+ }
+ if (isascii(*val) && isdigit(*val))
+ {
+ if (can_setuid)
+ RunAsUid = atoi(val);
+ }
+ else
+ {
+ register struct passwd *pw;
+
+ pw = sm_getpwnam(val);
+ if (pw == NULL)
+ {
+ syserr("readcf: option RunAsUser: unknown user %s", val);
+ break;
+ }
+ else if (can_setuid)
+ {
+ if (*p == '\0')
+ RunAsUserName = newstr(val);
+ RunAsUid = pw->pw_uid;
+ RunAsGid = pw->pw_gid;
+ }
+ else if (EffGid == pw->pw_gid)
+ RunAsGid = pw->pw_gid;
+ else if (UseMSP && *p == '\0')
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "WARNING: RunAsGid for MSP ignored, check group ids (egid=%d, want=%d)\n",
+ (int) EffGid,
+ (int) pw->pw_gid);
+ }
+# ifdef UID_MAX
+ if (RunAsUid > UID_MAX)
+ {
+ syserr("readcf: option RunAsUser: uid value (%ld) > UID_MAX (%ld); ignored",
+ (long) RunAsUid, (long) UID_MAX);
+ break;
+ }
+# endif /* UID_MAX */
+ if (*p != '\0')
+ {
+ if (isascii(*p) && isdigit(*p))
+ {
+ gid_t runasgid;
+
+ runasgid = (gid_t) atoi(p);
+ if (can_setuid || EffGid == runasgid)
+ RunAsGid = runasgid;
+ else if (UseMSP)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "WARNING: RunAsGid for MSP ignored, check group ids (egid=%d, want=%d)\n",
+ (int) EffGid,
+ (int) runasgid);
+ }
+ else
+ {
+ register struct group *gr;
+
+ gr = getgrnam(p);
+ if (gr == NULL)
+ syserr("readcf: option RunAsUser: unknown group %s",
+ p);
+ else if (can_setuid || EffGid == gr->gr_gid)
+ RunAsGid = gr->gr_gid;
+ else if (UseMSP)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "WARNING: RunAsGid for MSP ignored, check group ids (egid=%d, want=%d)\n",
+ (int) EffGid,
+ (int) gr->gr_gid);
+ }
+ }
+ if (tTd(47, 5))
+ sm_dprintf("readcf: RunAsUser = %d:%d\n",
+ (int) RunAsUid, (int) RunAsGid);
+ break;
+
+ case O_DSN_RRT:
+ RrtImpliesDsn = atobool(val);
+ break;
+
+ case O_PIDFILE:
+ PSTRSET(PidFile, val);
+ break;
+
+ case O_DONTBLAMESENDMAIL:
+ p = val;
+ for (;;)
+ {
+ register struct dbsval *dbs;
+ extern struct dbsval DontBlameSendmailValues[];
+
+ while (isascii(*p) && (isspace(*p) || ispunct(*p)))
+ p++;
+ if (*p == '\0')
+ break;
+ val = p;
+ while (isascii(*p) && isalnum(*p))
+ p++;
+ if (*p != '\0')
+ *p++ = '\0';
+
+ for (dbs = DontBlameSendmailValues;
+ dbs->dbs_name != NULL; dbs++)
+ {
+ if (sm_strcasecmp(val, dbs->dbs_name) == 0)
+ break;
+ }
+ if (dbs->dbs_name == NULL)
+ syserr("readcf: DontBlameSendmail option: %s unrecognized", val);
+ else if (dbs->dbs_flag == DBS_SAFE)
+ clrbitmap(DontBlameSendmail);
+ else
+ setbitn(dbs->dbs_flag, DontBlameSendmail);
+ }
+ sticky = false;
+ break;
+
+ case O_DPI:
+ if (sm_strcasecmp(val, "loopback") == 0)
+ DontProbeInterfaces = DPI_SKIPLOOPBACK;
+ else if (atobool(val))
+ DontProbeInterfaces = DPI_PROBENONE;
+ else
+ DontProbeInterfaces = DPI_PROBEALL;
+ break;
+
+ case O_MAXRCPT:
+ MaxRcptPerMsg = atoi(val);
+ break;
+
+ case O_RCPTTHROT:
+ BadRcptThrottle = atoi(val);
+ break;
+
+ case O_DEADLETTER:
+ CANONIFY(val);
+ PSTRSET(DeadLetterDrop, val);
+ break;
+
+#if _FFR_DONTLOCKFILESFORREAD_OPTION
+ case O_DONTLOCK:
+ DontLockReadFiles = atobool(val);
+ break;
+#endif /* _FFR_DONTLOCKFILESFORREAD_OPTION */
+
+ case O_MAXALIASRCSN:
+ MaxAliasRecursion = atoi(val);
+ break;
+
+ case O_CNCTONLYTO:
+ /* XXX should probably use gethostbyname */
+#if NETINET || NETINET6
+ ConnectOnlyTo.sa.sa_family = AF_UNSPEC;
+# if NETINET6
+ if (anynet_pton(AF_INET6, val,
+ &ConnectOnlyTo.sin6.sin6_addr) != 1)
+ ConnectOnlyTo.sa.sa_family = AF_INET6;
+ else
+# endif /* NETINET6 */
+# if NETINET
+ {
+ ConnectOnlyTo.sin.sin_addr.s_addr = inet_addr(val);
+ if (ConnectOnlyTo.sin.sin_addr.s_addr != INADDR_NONE)
+ ConnectOnlyTo.sa.sa_family = AF_INET;
+ }
+
+# endif /* NETINET */
+ if (ConnectOnlyTo.sa.sa_family == AF_UNSPEC)
+ {
+ syserr("readcf: option ConnectOnlyTo: invalid IP address %s",
+ val);
+ break;
+ }
+#endif /* NETINET || NETINET6 */
+ break;
+
+ case O_TRUSTUSER:
+# if !HASFCHOWN && !defined(_FFR_DROP_TRUSTUSER_WARNING)
+ if (!UseMSP)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "readcf: option TrustedUser may cause problems on systems\n which do not support fchown() if UseMSP is not set.\n");
+# endif /* !HASFCHOWN && !defined(_FFR_DROP_TRUSTUSER_WARNING) */
+ if (isascii(*val) && isdigit(*val))
+ TrustedUid = atoi(val);
+ else
+ {
+ register struct passwd *pw;
+
+ TrustedUid = 0;
+ pw = sm_getpwnam(val);
+ if (pw == NULL)
+ {
+ syserr("readcf: option TrustedUser: unknown user %s", val);
+ break;
+ }
+ else
+ TrustedUid = pw->pw_uid;
+ }
+
+# ifdef UID_MAX
+ if (TrustedUid > UID_MAX)
+ {
+ syserr("readcf: option TrustedUser: uid value (%ld) > UID_MAX (%ld)",
+ (long) TrustedUid, (long) UID_MAX);
+ TrustedUid = 0;
+ }
+# endif /* UID_MAX */
+ break;
+
+ case O_MAXMIMEHDRLEN:
+ p = strchr(val, '/');
+ if (p != NULL)
+ *p++ = '\0';
+ MaxMimeHeaderLength = atoi(val);
+ if (p != NULL && *p != '\0')
+ MaxMimeFieldLength = atoi(p);
+ else
+ MaxMimeFieldLength = MaxMimeHeaderLength / 2;
+
+ if (MaxMimeHeaderLength < 0)
+ MaxMimeHeaderLength = 0;
+ else if (MaxMimeHeaderLength < 128)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: MaxMimeHeaderLength: header length limit set lower than 128\n");
+
+ if (MaxMimeFieldLength < 0)
+ MaxMimeFieldLength = 0;
+ else if (MaxMimeFieldLength < 40)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: MaxMimeHeaderLength: field length limit set lower than 40\n");
+ break;
+
+ case O_CONTROLSOCKET:
+ PSTRSET(ControlSocketName, val);
+ break;
+
+ case O_MAXHDRSLEN:
+ MaxHeadersLength = atoi(val);
+
+ if (MaxHeadersLength > 0 &&
+ MaxHeadersLength < (MAXHDRSLEN / 2))
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: MaxHeadersLength: headers length limit set lower than %d\n",
+ (MAXHDRSLEN / 2));
+ break;
+
+ case O_PROCTITLEPREFIX:
+ PSTRSET(ProcTitlePrefix, val);
+ break;
+
+#if SASL
+ case O_SASLINFO:
+# if _FFR_ALLOW_SASLINFO
+ /*
+ ** Allow users to select their own authinfo file
+ ** under certain circumstances, otherwise just ignore
+ ** the option. If the option isn't ignored, several
+ ** commands don't work very well, e.g., mailq.
+ ** However, this is not a "perfect" solution.
+ ** If mail is queued, the authentication info
+ ** will not be used in subsequent delivery attempts.
+ ** If we really want to support this, then it has
+ ** to be stored in the queue file.
+ */
+ if (!bitset(SUBMIT_MSA, SubmitMode) && RealUid != 0 &&
+ RunAsUid != RealUid)
+ break;
+# endif /* _FFR_ALLOW_SASLINFO */
+ PSTRSET(SASLInfo, val);
+ break;
+
+ case O_SASLMECH:
+ if (AuthMechanisms != NULL)
+ sm_free(AuthMechanisms); /* XXX */
+ if (*val != '\0')
+ AuthMechanisms = newstr(val);
+ else
+ AuthMechanisms = NULL;
+ break;
+
+ case O_SASLOPTS:
+ while (val != NULL && *val != '\0')
+ {
+ switch (*val)
+ {
+ case 'A':
+ SASLOpts |= SASL_AUTH_AUTH;
+ break;
+ case 'a':
+ SASLOpts |= SASL_SEC_NOACTIVE;
+ break;
+ case 'c':
+ SASLOpts |= SASL_SEC_PASS_CREDENTIALS;
+ break;
+ case 'd':
+ SASLOpts |= SASL_SEC_NODICTIONARY;
+ break;
+ case 'f':
+ SASLOpts |= SASL_SEC_FORWARD_SECRECY;
+ break;
+# if _FFR_SASL_OPT_M
+/* to be activated in 8.13 */
+# if SASL >= 20101
+ case 'm':
+ SASLOpts |= SASL_SEC_MUTUAL_AUTH;
+ break;
+# endif /* SASL >= 20101 */
+# endif /* _FFR_SASL_OPT_M */
+ case 'p':
+ SASLOpts |= SASL_SEC_NOPLAINTEXT;
+ break;
+ case 'y':
+ SASLOpts |= SASL_SEC_NOANONYMOUS;
+ break;
+ case ' ': /* ignore */
+ case '\t': /* ignore */
+ case ',': /* ignore */
+ break;
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s unknown parameter '%c'\n",
+ OPTNAME,
+ (isascii(*val) &&
+ isprint(*val))
+ ? *val : '?');
+ break;
+ }
+ ++val;
+ val = strpbrk(val, ", \t");
+ if (val != NULL)
+ ++val;
+ }
+ break;
+ case O_SASLBITS:
+ MaxSLBits = atoi(val);
+ break;
+
+#else /* SASL */
+ case O_SASLINFO:
+ case O_SASLMECH:
+ case O_SASLOPTS:
+ case O_SASLBITS:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires SASL support (-DSASL)\n",
+ OPTNAME);
+ break;
+#endif /* SASL */
+
+#if STARTTLS
+ case O_SRVCERTFILE:
+ SET_STRING_EXP(SrvCERTfile);
+ case O_SRVKEYFILE:
+ SET_STRING_EXP(Srvkeyfile);
+ case O_CLTCERTFILE:
+ SET_STRING_EXP(CltCERTfile);
+ case O_CLTKEYFILE:
+ SET_STRING_EXP(Cltkeyfile);
+ case O_CACERTFILE:
+ SET_STRING_EXP(CACERTfile);
+ case O_CACERTPATH:
+ SET_STRING_EXP(CACERTpath);
+ case O_DHPARAMS:
+ SET_STRING_EXP(DHParams);
+# if _FFR_TLS_1
+ case O_DHPARAMS5:
+ SET_STRING_EXP(DHParams5);
+ case O_CIPHERLIST:
+ SET_STRING_EXP(CipherList);
+# endif /* _FFR_TLS_1 */
+
+ /*
+ ** XXX How about options per daemon/client instead of globally?
+ ** This doesn't work well for some options, e.g., no server cert,
+ ** but fine for others.
+ **
+ ** XXX Some people may want different certs per server.
+ **
+ ** See also srvfeatures()
+ */
+
+ case O_TLS_SRV_OPTS:
+ while (val != NULL && *val != '\0')
+ {
+ switch (*val)
+ {
+ case 'V':
+ TLS_Srv_Opts |= TLS_I_NO_VRFY;
+ break;
+# if _FFR_TLS_1
+ /*
+ ** Server without a cert? That works only if
+ ** AnonDH is enabled as cipher, which is not in the
+ ** default list. Hence the CipherList option must
+ ** be available. Moreover: which clients support this
+ ** besides sendmail with this setting?
+ */
+
+ case 'C':
+ TLS_Srv_Opts &= ~TLS_I_SRV_CERT;
+ break;
+# endif /* _FFR_TLS_1 */
+ case ' ': /* ignore */
+ case '\t': /* ignore */
+ case ',': /* ignore */
+ break;
+ default:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s unknown parameter '%c'\n",
+ OPTNAME,
+ (isascii(*val) &&
+ isprint(*val))
+ ? *val : '?');
+ break;
+ }
+ ++val;
+ val = strpbrk(val, ", \t");
+ if (val != NULL)
+ ++val;
+ }
+ break;
+
+ case O_RANDFILE:
+ PSTRSET(RandFile, val);
+ break;
+
+#else /* STARTTLS */
+ case O_SRVCERTFILE:
+ case O_SRVKEYFILE:
+ case O_CLTCERTFILE:
+ case O_CLTKEYFILE:
+ case O_CACERTFILE:
+ case O_CACERTPATH:
+ case O_DHPARAMS:
+# if _FFR_TLS_1
+ case O_DHPARAMS5:
+ case O_CIPHERLIST:
+# endif /* _FFR_TLS_1 */
+ case O_RANDFILE:
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires TLS support\n",
+ OPTNAME);
+ break;
+
+#endif /* STARTTLS */
+
+ case O_CLIENTPORT:
+ setclientoptions(val);
+ break;
+
+ case O_DF_BUFSIZE:
+ DataFileBufferSize = atoi(val);
+ break;
+
+ case O_XF_BUFSIZE:
+ XscriptFileBufferSize = atoi(val);
+ break;
+
+ case O_LDAPDEFAULTSPEC:
+#if LDAPMAP
+ ldapmap_set_defaults(val);
+#else /* LDAPMAP */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires LDAP support (-DLDAPMAP)\n",
+ OPTNAME);
+#endif /* LDAPMAP */
+ break;
+
+ case O_INPUTMILTER:
+#if MILTER
+ InputFilterList = newstr(val);
+#else /* MILTER */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires Milter support (-DMILTER)\n",
+ OPTNAME);
+#endif /* MILTER */
+ break;
+
+ case O_MILTER:
+#if MILTER
+ milter_set_option(subopt, val, sticky);
+#else /* MILTER */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Option: %s requires Milter support (-DMILTER)\n",
+ OPTNAME);
+#endif /* MILTER */
+ break;
+
+ case O_QUEUE_FILE_MODE: /* queue file mode */
+ QueueFileMode = atooct(val) & 0777;
+ break;
+
+ case O_DLVR_MIN: /* deliver by minimum time */
+ DeliverByMin = convtime(val, 's');
+ break;
+
+ /* modifiers {daemon_flags} for direct submissions */
+ case O_DIRECTSUBMODIFIERS:
+ {
+ BITMAP256 m; /* ignored */
+ extern ENVELOPE BlankEnvelope;
+
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{daemon_flags}"),
+ getmodifiers(val, m));
+ }
+ break;
+
+ case O_FASTSPLIT:
+ FastSplit = atoi(val);
+ break;
+
+ case O_MBDB:
+ Mbdb = newstr(val);
+ break;
+
+ case O_MSQ:
+ UseMSP = atobool(val);
+ break;
+
+#if _FFR_SOFT_BOUNCE
+ case O_SOFTBOUNCE:
+ SoftBounce = atobool(val);
+ break;
+#endif /* _FFR_SOFT_BOUNCE */
+
+ default:
+ if (tTd(37, 1))
+ {
+ if (isascii(opt) && isprint(opt))
+ sm_dprintf("Warning: option %c unknown\n", opt);
+ else
+ sm_dprintf("Warning: option 0x%x unknown\n", opt);
+ }
+ break;
+ }
+
+ /*
+ ** Options with suboptions are responsible for taking care
+ ** of sticky-ness (e.g., that a command line setting is kept
+ ** when reading in the sendmail.cf file). This has to be done
+ ** when the suboptions are parsed since each suboption must be
+ ** sticky, not the root option.
+ */
+
+ if (sticky && !bitset(OI_SUBOPT, o->o_flags))
+ setbitn(opt, StickyOpt);
+}
+/*
+** SETCLASS -- set a string into a class
+**
+** Parameters:
+** class -- the class to put the string in.
+** str -- the string to enter
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** puts the word into the symbol table.
+*/
+
+void
+setclass(class, str)
+ int class;
+ char *str;
+{
+ register STAB *s;
+
+ if ((*str & 0377) == MATCHCLASS)
+ {
+ int mid;
+
+ str++;
+ mid = macid(str);
+ if (mid == 0)
+ return;
+
+ if (tTd(37, 8))
+ sm_dprintf("setclass(%s, $=%s)\n",
+ macname(class), macname(mid));
+ copy_class(mid, class);
+ }
+ else
+ {
+ if (tTd(37, 8))
+ sm_dprintf("setclass(%s, %s)\n", macname(class), str);
+
+ s = stab(str, ST_CLASS, ST_ENTER);
+ setbitn(bitidx(class), s->s_class);
+ }
+}
+/*
+** MAKEMAPENTRY -- create a map entry
+**
+** Parameters:
+** line -- the config file line
+**
+** Returns:
+** A pointer to the map that has been created.
+** NULL if there was a syntax error.
+**
+** Side Effects:
+** Enters the map into the dictionary.
+*/
+
+MAP *
+makemapentry(line)
+ char *line;
+{
+ register char *p;
+ char *mapname;
+ char *classname;
+ register STAB *s;
+ STAB *class;
+
+ for (p = line; isascii(*p) && isspace(*p); p++)
+ continue;
+ if (!(isascii(*p) && isalnum(*p)))
+ {
+ syserr("readcf: config K line: no map name");
+ return NULL;
+ }
+
+ mapname = p;
+ while ((isascii(*++p) && isalnum(*p)) || *p == '_' || *p == '.')
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (!(isascii(*p) && isalnum(*p)))
+ {
+ syserr("readcf: config K line, map %s: no map class", mapname);
+ return NULL;
+ }
+ classname = p;
+ while (isascii(*++p) && isalnum(*p))
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* look up the class */
+ class = stab(classname, ST_MAPCLASS, ST_FIND);
+ if (class == NULL)
+ {
+ syserr("readcf: map %s: class %s not available", mapname,
+ classname);
+ return NULL;
+ }
+
+ /* enter the map */
+ s = stab(mapname, ST_MAP, ST_ENTER);
+ s->s_map.map_class = &class->s_mapclass;
+ s->s_map.map_mname = newstr(mapname);
+
+ if (class->s_mapclass.map_parse(&s->s_map, p))
+ s->s_map.map_mflags |= MF_VALID;
+
+ if (tTd(37, 5))
+ {
+ sm_dprintf("map %s, class %s, flags %lx, file %s,\n",
+ s->s_map.map_mname, s->s_map.map_class->map_cname,
+ s->s_map.map_mflags, s->s_map.map_file);
+ sm_dprintf("\tapp %s, domain %s, rebuild %s\n",
+ s->s_map.map_app, s->s_map.map_domain,
+ s->s_map.map_rebuild);
+ }
+ return &s->s_map;
+}
+/*
+** STRTORWSET -- convert string to rewriting set number
+**
+** Parameters:
+** p -- the pointer to the string to decode.
+** endp -- if set, store the trailing delimiter here.
+** stabmode -- ST_ENTER to create this entry, ST_FIND if
+** it must already exist.
+**
+** Returns:
+** The appropriate ruleset number.
+** -1 if it is not valid (error already printed)
+*/
+
+int
+strtorwset(p, endp, stabmode)
+ char *p;
+ char **endp;
+ int stabmode;
+{
+ int ruleset;
+ static int nextruleset = MAXRWSETS;
+
+ while (isascii(*p) && isspace(*p))
+ p++;
+ if (!isascii(*p))
+ {
+ syserr("invalid ruleset name: \"%.20s\"", p);
+ return -1;
+ }
+ if (isdigit(*p))
+ {
+ ruleset = strtol(p, endp, 10);
+ if (ruleset >= MAXRWSETS / 2 || ruleset < 0)
+ {
+ syserr("bad ruleset %d (%d max)",
+ ruleset, MAXRWSETS / 2);
+ ruleset = -1;
+ }
+ }
+ else
+ {
+ STAB *s;
+ char delim;
+ char *q = NULL;
+
+ q = p;
+ while (*p != '\0' && isascii(*p) &&
+ (isalnum(*p) || *p == '_'))
+ p++;
+ if (q == p || !(isascii(*q) && isalpha(*q)))
+ {
+ /* no valid characters */
+ syserr("invalid ruleset name: \"%.20s\"", q);
+ return -1;
+ }
+ while (isascii(*p) && isspace(*p))
+ *p++ = '\0';
+ delim = *p;
+ if (delim != '\0')
+ *p = '\0';
+ s = stab(q, ST_RULESET, stabmode);
+ if (delim != '\0')
+ *p = delim;
+
+ if (s == NULL)
+ return -1;
+
+ if (stabmode == ST_ENTER && delim == '=')
+ {
+ while (isascii(*++p) && isspace(*p))
+ continue;
+ if (!(isascii(*p) && isdigit(*p)))
+ {
+ syserr("bad ruleset definition \"%s\" (number required after `=')", q);
+ ruleset = -1;
+ }
+ else
+ {
+ ruleset = strtol(p, endp, 10);
+ if (ruleset >= MAXRWSETS / 2 || ruleset < 0)
+ {
+ syserr("bad ruleset number %d in \"%s\" (%d max)",
+ ruleset, q, MAXRWSETS / 2);
+ ruleset = -1;
+ }
+ }
+ }
+ else
+ {
+ if (endp != NULL)
+ *endp = p;
+ if (s->s_ruleset >= 0)
+ ruleset = s->s_ruleset;
+ else if ((ruleset = --nextruleset) < MAXRWSETS / 2)
+ {
+ syserr("%s: too many named rulesets (%d max)",
+ q, MAXRWSETS / 2);
+ ruleset = -1;
+ }
+ }
+ if (s->s_ruleset >= 0 &&
+ ruleset >= 0 &&
+ ruleset != s->s_ruleset)
+ {
+ syserr("%s: ruleset changed value (old %d, new %d)",
+ q, s->s_ruleset, ruleset);
+ ruleset = s->s_ruleset;
+ }
+ else if (ruleset >= 0)
+ {
+ s->s_ruleset = ruleset;
+ }
+ if (stabmode == ST_ENTER && ruleset >= 0)
+ {
+ char *h = NULL;
+
+ if (RuleSetNames[ruleset] != NULL)
+ sm_free(RuleSetNames[ruleset]); /* XXX */
+ if (delim != '\0' && (h = strchr(q, delim)) != NULL)
+ *h = '\0';
+ RuleSetNames[ruleset] = newstr(q);
+ if (delim == '/' && h != NULL)
+ *h = delim; /* put back delim */
+ }
+ }
+ return ruleset;
+}
+/*
+** SETTIMEOUT -- set an individual timeout
+**
+** Parameters:
+** name -- the name of the timeout.
+** val -- the value of the timeout.
+** sticky -- if set, don't let other setoptions override
+** this value.
+**
+** Returns:
+** none.
+*/
+
+/* set if Timeout sub-option is stuck */
+static BITMAP256 StickyTimeoutOpt;
+
+static struct timeoutinfo
+{
+ char *to_name; /* long name of timeout */
+ unsigned char to_code; /* code for option */
+} TimeOutTab[] =
+{
+#define TO_INITIAL 0x01
+ { "initial", TO_INITIAL },
+#define TO_MAIL 0x02
+ { "mail", TO_MAIL },
+#define TO_RCPT 0x03
+ { "rcpt", TO_RCPT },
+#define TO_DATAINIT 0x04
+ { "datainit", TO_DATAINIT },
+#define TO_DATABLOCK 0x05
+ { "datablock", TO_DATABLOCK },
+#define TO_DATAFINAL 0x06
+ { "datafinal", TO_DATAFINAL },
+#define TO_COMMAND 0x07
+ { "command", TO_COMMAND },
+#define TO_RSET 0x08
+ { "rset", TO_RSET },
+#define TO_HELO 0x09
+ { "helo", TO_HELO },
+#define TO_QUIT 0x0A
+ { "quit", TO_QUIT },
+#define TO_MISC 0x0B
+ { "misc", TO_MISC },
+#define TO_IDENT 0x0C
+ { "ident", TO_IDENT },
+#define TO_FILEOPEN 0x0D
+ { "fileopen", TO_FILEOPEN },
+#define TO_CONNECT 0x0E
+ { "connect", TO_CONNECT },
+#define TO_ICONNECT 0x0F
+ { "iconnect", TO_ICONNECT },
+#define TO_QUEUEWARN 0x10
+ { "queuewarn", TO_QUEUEWARN },
+ { "queuewarn.*", TO_QUEUEWARN },
+#define TO_QUEUEWARN_NORMAL 0x11
+ { "queuewarn.normal", TO_QUEUEWARN_NORMAL },
+#define TO_QUEUEWARN_URGENT 0x12
+ { "queuewarn.urgent", TO_QUEUEWARN_URGENT },
+#define TO_QUEUEWARN_NON_URGENT 0x13
+ { "queuewarn.non-urgent", TO_QUEUEWARN_NON_URGENT },
+#define TO_QUEUERETURN 0x14
+ { "queuereturn", TO_QUEUERETURN },
+ { "queuereturn.*", TO_QUEUERETURN },
+#define TO_QUEUERETURN_NORMAL 0x15
+ { "queuereturn.normal", TO_QUEUERETURN_NORMAL },
+#define TO_QUEUERETURN_URGENT 0x16
+ { "queuereturn.urgent", TO_QUEUERETURN_URGENT },
+#define TO_QUEUERETURN_NON_URGENT 0x17
+ { "queuereturn.non-urgent", TO_QUEUERETURN_NON_URGENT },
+#define TO_HOSTSTATUS 0x18
+ { "hoststatus", TO_HOSTSTATUS },
+#define TO_RESOLVER_RETRANS 0x19
+ { "resolver.retrans", TO_RESOLVER_RETRANS },
+#define TO_RESOLVER_RETRANS_NORMAL 0x1A
+ { "resolver.retrans.normal", TO_RESOLVER_RETRANS_NORMAL },
+#define TO_RESOLVER_RETRANS_FIRST 0x1B
+ { "resolver.retrans.first", TO_RESOLVER_RETRANS_FIRST },
+#define TO_RESOLVER_RETRY 0x1C
+ { "resolver.retry", TO_RESOLVER_RETRY },
+#define TO_RESOLVER_RETRY_NORMAL 0x1D
+ { "resolver.retry.normal", TO_RESOLVER_RETRY_NORMAL },
+#define TO_RESOLVER_RETRY_FIRST 0x1E
+ { "resolver.retry.first", TO_RESOLVER_RETRY_FIRST },
+#define TO_CONTROL 0x1F
+ { "control", TO_CONTROL },
+#define TO_LHLO 0x20
+ { "lhlo", TO_LHLO },
+#define TO_AUTH 0x21
+ { "auth", TO_AUTH },
+#define TO_STARTTLS 0x22
+ { "starttls", TO_STARTTLS },
+#define TO_ACONNECT 0x23
+ { "aconnect", TO_ACONNECT },
+ { NULL, 0 },
+};
+
+
+static void
+settimeout(name, val, sticky)
+ char *name;
+ char *val;
+ bool sticky;
+{
+ register struct timeoutinfo *to;
+ int i, addopts;
+ time_t toval;
+
+ if (tTd(37, 2))
+ sm_dprintf("settimeout(%s = %s)", name, val);
+
+ for (to = TimeOutTab; to->to_name != NULL; to++)
+ {
+ if (sm_strcasecmp(to->to_name, name) == 0)
+ break;
+ }
+
+ if (to->to_name == NULL)
+ {
+ errno = 0; /* avoid bogus error text */
+ syserr("settimeout: invalid timeout %s", name);
+ return;
+ }
+
+ /*
+ ** See if this option is preset for us.
+ */
+
+ if (!sticky && bitnset(to->to_code, StickyTimeoutOpt))
+ {
+ if (tTd(37, 2))
+ sm_dprintf(" (ignored)\n");
+ return;
+ }
+
+ if (tTd(37, 2))
+ sm_dprintf("\n");
+
+ toval = convtime(val, 'm');
+ addopts = 0;
+
+ switch (to->to_code)
+ {
+ case TO_INITIAL:
+ TimeOuts.to_initial = toval;
+ break;
+
+ case TO_MAIL:
+ TimeOuts.to_mail = toval;
+ break;
+
+ case TO_RCPT:
+ TimeOuts.to_rcpt = toval;
+ break;
+
+ case TO_DATAINIT:
+ TimeOuts.to_datainit = toval;
+ break;
+
+ case TO_DATABLOCK:
+ TimeOuts.to_datablock = toval;
+ break;
+
+ case TO_DATAFINAL:
+ TimeOuts.to_datafinal = toval;
+ break;
+
+ case TO_COMMAND:
+ TimeOuts.to_nextcommand = toval;
+ break;
+
+ case TO_RSET:
+ TimeOuts.to_rset = toval;
+ break;
+
+ case TO_HELO:
+ TimeOuts.to_helo = toval;
+ break;
+
+ case TO_QUIT:
+ TimeOuts.to_quit = toval;
+ break;
+
+ case TO_MISC:
+ TimeOuts.to_miscshort = toval;
+ break;
+
+ case TO_IDENT:
+ TimeOuts.to_ident = toval;
+ break;
+
+ case TO_FILEOPEN:
+ TimeOuts.to_fileopen = toval;
+ break;
+
+ case TO_CONNECT:
+ TimeOuts.to_connect = toval;
+ break;
+
+ case TO_ICONNECT:
+ TimeOuts.to_iconnect = toval;
+ break;
+
+ case TO_ACONNECT:
+ TimeOuts.to_aconnect = toval;
+ break;
+
+ case TO_QUEUEWARN:
+ toval = convtime(val, 'h');
+ TimeOuts.to_q_warning[TOC_NORMAL] = toval;
+ TimeOuts.to_q_warning[TOC_URGENT] = toval;
+ TimeOuts.to_q_warning[TOC_NONURGENT] = toval;
+ addopts = 2;
+ break;
+
+ case TO_QUEUEWARN_NORMAL:
+ toval = convtime(val, 'h');
+ TimeOuts.to_q_warning[TOC_NORMAL] = toval;
+ break;
+
+ case TO_QUEUEWARN_URGENT:
+ toval = convtime(val, 'h');
+ TimeOuts.to_q_warning[TOC_URGENT] = toval;
+ break;
+
+ case TO_QUEUEWARN_NON_URGENT:
+ toval = convtime(val, 'h');
+ TimeOuts.to_q_warning[TOC_NONURGENT] = toval;
+ break;
+
+ case TO_QUEUERETURN:
+ toval = convtime(val, 'd');
+ TimeOuts.to_q_return[TOC_NORMAL] = toval;
+ TimeOuts.to_q_return[TOC_URGENT] = toval;
+ TimeOuts.to_q_return[TOC_NONURGENT] = toval;
+ addopts = 2;
+ break;
+
+ case TO_QUEUERETURN_NORMAL:
+ toval = convtime(val, 'd');
+ TimeOuts.to_q_return[TOC_NORMAL] = toval;
+ break;
+
+ case TO_QUEUERETURN_URGENT:
+ toval = convtime(val, 'd');
+ TimeOuts.to_q_return[TOC_URGENT] = toval;
+ break;
+
+ case TO_QUEUERETURN_NON_URGENT:
+ toval = convtime(val, 'd');
+ TimeOuts.to_q_return[TOC_NONURGENT] = toval;
+ break;
+
+ case TO_HOSTSTATUS:
+ MciInfoTimeout = toval;
+ break;
+
+ case TO_RESOLVER_RETRANS:
+ toval = convtime(val, 's');
+ TimeOuts.res_retrans[RES_TO_DEFAULT] = toval;
+ TimeOuts.res_retrans[RES_TO_FIRST] = toval;
+ TimeOuts.res_retrans[RES_TO_NORMAL] = toval;
+ addopts = 2;
+ break;
+
+ case TO_RESOLVER_RETRY:
+ i = atoi(val);
+ TimeOuts.res_retry[RES_TO_DEFAULT] = i;
+ TimeOuts.res_retry[RES_TO_FIRST] = i;
+ TimeOuts.res_retry[RES_TO_NORMAL] = i;
+ addopts = 2;
+ break;
+
+ case TO_RESOLVER_RETRANS_NORMAL:
+ TimeOuts.res_retrans[RES_TO_NORMAL] = convtime(val, 's');
+ break;
+
+ case TO_RESOLVER_RETRY_NORMAL:
+ TimeOuts.res_retry[RES_TO_NORMAL] = atoi(val);
+ break;
+
+ case TO_RESOLVER_RETRANS_FIRST:
+ TimeOuts.res_retrans[RES_TO_FIRST] = convtime(val, 's');
+ break;
+
+ case TO_RESOLVER_RETRY_FIRST:
+ TimeOuts.res_retry[RES_TO_FIRST] = atoi(val);
+ break;
+
+ case TO_CONTROL:
+ TimeOuts.to_control = toval;
+ break;
+
+ case TO_LHLO:
+ TimeOuts.to_lhlo = toval;
+ break;
+
+#if SASL
+ case TO_AUTH:
+ TimeOuts.to_auth = toval;
+ break;
+#endif /* SASL */
+
+#if STARTTLS
+ case TO_STARTTLS:
+ TimeOuts.to_starttls = toval;
+ break;
+#endif /* STARTTLS */
+
+ default:
+ syserr("settimeout: invalid timeout %s", name);
+ break;
+ }
+
+ if (sticky)
+ {
+ for (i = 0; i <= addopts; i++)
+ setbitn(to->to_code + i, StickyTimeoutOpt);
+ }
+}
+/*
+** INITTIMEOUTS -- parse and set timeout values
+**
+** Parameters:
+** val -- a pointer to the values. If NULL, do initial
+** settings.
+** sticky -- if set, don't let other setoptions override
+** this suboption value.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Initializes the TimeOuts structure
+*/
+
+void
+inittimeouts(val, sticky)
+ register char *val;
+ bool sticky;
+{
+ register char *p;
+
+ if (tTd(37, 2))
+ sm_dprintf("inittimeouts(%s)\n", val == NULL ? "<NULL>" : val);
+ if (val == NULL)
+ {
+ TimeOuts.to_connect = (time_t) 0 SECONDS;
+ TimeOuts.to_aconnect = (time_t) 0 SECONDS;
+ TimeOuts.to_iconnect = (time_t) 0 SECONDS;
+ TimeOuts.to_initial = (time_t) 5 MINUTES;
+ TimeOuts.to_helo = (time_t) 5 MINUTES;
+ TimeOuts.to_mail = (time_t) 10 MINUTES;
+ TimeOuts.to_rcpt = (time_t) 1 HOUR;
+ TimeOuts.to_datainit = (time_t) 5 MINUTES;
+ TimeOuts.to_datablock = (time_t) 1 HOUR;
+ TimeOuts.to_datafinal = (time_t) 1 HOUR;
+ TimeOuts.to_rset = (time_t) 5 MINUTES;
+ TimeOuts.to_quit = (time_t) 2 MINUTES;
+ TimeOuts.to_nextcommand = (time_t) 1 HOUR;
+ TimeOuts.to_miscshort = (time_t) 2 MINUTES;
+#if IDENTPROTO
+ TimeOuts.to_ident = (time_t) 5 SECONDS;
+#else /* IDENTPROTO */
+ TimeOuts.to_ident = (time_t) 0 SECONDS;
+#endif /* IDENTPROTO */
+ TimeOuts.to_fileopen = (time_t) 60 SECONDS;
+ TimeOuts.to_control = (time_t) 2 MINUTES;
+ TimeOuts.to_lhlo = (time_t) 2 MINUTES;
+#if SASL
+ TimeOuts.to_auth = (time_t) 10 MINUTES;
+#endif /* SASL */
+#if STARTTLS
+ TimeOuts.to_starttls = (time_t) 1 HOUR;
+#endif /* STARTTLS */
+ if (tTd(37, 5))
+ {
+ sm_dprintf("Timeouts:\n");
+ sm_dprintf(" connect = %ld\n",
+ (long) TimeOuts.to_connect);
+ sm_dprintf(" aconnect = %ld\n",
+ (long) TimeOuts.to_aconnect);
+ sm_dprintf(" initial = %ld\n",
+ (long) TimeOuts.to_initial);
+ sm_dprintf(" helo = %ld\n", (long) TimeOuts.to_helo);
+ sm_dprintf(" mail = %ld\n", (long) TimeOuts.to_mail);
+ sm_dprintf(" rcpt = %ld\n", (long) TimeOuts.to_rcpt);
+ sm_dprintf(" datainit = %ld\n",
+ (long) TimeOuts.to_datainit);
+ sm_dprintf(" datablock = %ld\n",
+ (long) TimeOuts.to_datablock);
+ sm_dprintf(" datafinal = %ld\n",
+ (long) TimeOuts.to_datafinal);
+ sm_dprintf(" rset = %ld\n", (long) TimeOuts.to_rset);
+ sm_dprintf(" quit = %ld\n", (long) TimeOuts.to_quit);
+ sm_dprintf(" nextcommand = %ld\n",
+ (long) TimeOuts.to_nextcommand);
+ sm_dprintf(" miscshort = %ld\n",
+ (long) TimeOuts.to_miscshort);
+ sm_dprintf(" ident = %ld\n", (long) TimeOuts.to_ident);
+ sm_dprintf(" fileopen = %ld\n",
+ (long) TimeOuts.to_fileopen);
+ sm_dprintf(" lhlo = %ld\n",
+ (long) TimeOuts.to_lhlo);
+ sm_dprintf(" control = %ld\n",
+ (long) TimeOuts.to_control);
+ }
+ return;
+ }
+
+ for (;; val = p)
+ {
+ while (isascii(*val) && isspace(*val))
+ val++;
+ if (*val == '\0')
+ break;
+ for (p = val; *p != '\0' && *p != ','; p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+
+ if (isascii(*val) && isdigit(*val))
+ {
+ /* old syntax -- set everything */
+ TimeOuts.to_mail = convtime(val, 'm');
+ TimeOuts.to_rcpt = TimeOuts.to_mail;
+ TimeOuts.to_datainit = TimeOuts.to_mail;
+ TimeOuts.to_datablock = TimeOuts.to_mail;
+ TimeOuts.to_datafinal = TimeOuts.to_mail;
+ TimeOuts.to_nextcommand = TimeOuts.to_mail;
+ if (sticky)
+ {
+ setbitn(TO_MAIL, StickyTimeoutOpt);
+ setbitn(TO_RCPT, StickyTimeoutOpt);
+ setbitn(TO_DATAINIT, StickyTimeoutOpt);
+ setbitn(TO_DATABLOCK, StickyTimeoutOpt);
+ setbitn(TO_DATAFINAL, StickyTimeoutOpt);
+ setbitn(TO_COMMAND, StickyTimeoutOpt);
+ }
+ continue;
+ }
+ else
+ {
+ register char *q = strchr(val, ':');
+
+ if (q == NULL && (q = strchr(val, '=')) == NULL)
+ {
+ /* syntax error */
+ continue;
+ }
+ *q++ = '\0';
+ settimeout(val, q, sticky);
+ }
+ }
+}
diff --git a/contrib/sendmail/src/recipient.c b/contrib/sendmail/src/recipient.c
new file mode 100644
index 0000000..22b8377
--- /dev/null
+++ b/contrib/sendmail/src/recipient.c
@@ -0,0 +1,2023 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: recipient.c,v 8.330 2002/05/29 18:20:03 gshapiro Exp $")
+
+static void includetimeout __P((void));
+static ADDRESS *self_reference __P((ADDRESS *));
+static int sortexpensive __P((ADDRESS *, ADDRESS *));
+static int sortbysignature __P((ADDRESS *, ADDRESS *));
+static int sorthost __P((ADDRESS *, ADDRESS *));
+
+typedef int sortfn_t __P((ADDRESS *, ADDRESS *));
+
+/*
+** SORTHOST -- strcmp()-like func for host portion of an ADDRESS
+**
+** Parameters:
+** xx -- first ADDRESS
+** yy -- second ADDRESS
+**
+** Returns:
+** <0 when xx->q_host is less than yy->q_host
+** >0 when xx->q_host is greater than yy->q_host
+** 0 when equal
+*/
+
+static int
+sorthost(xx, yy)
+ register ADDRESS *xx;
+ register ADDRESS *yy;
+{
+#if _FFR_HOST_SORT_REVERSE
+ /* XXX maybe compare hostnames from the end? */
+ return sm_strrevcasecmp(xx->q_host, yy->q_host);
+#else /* _FFR_HOST_SORT_REVERSE */
+ return sm_strcasecmp(xx->q_host, yy->q_host);
+#endif /* _FFR_HOST_SORT_REVERSE */
+}
+
+/*
+** SORTEXPENSIVE -- strcmp()-like func for expensive mailers
+**
+** The mailer has been noted already as "expensive" for 'xx'. This
+** will give a result relative to 'yy'. Expensive mailers get rated
+** "greater than" non-expensive mailers because during the delivery phase
+** it will get queued -- no use it getting in the way of less expensive
+** recipients. We avoid an MX RR lookup when both 'xx' and 'yy' are
+** expensive since an MX RR lookup happens when extracted from the queue
+** later.
+**
+** Parameters:
+** xx -- first ADDRESS
+** yy -- second ADDRESS
+**
+** Returns:
+** <0 when xx->q_host is less than yy->q_host and both are
+** expensive
+** >0 when xx->q_host is greater than yy->q_host, or when
+** 'yy' is non-expensive
+** 0 when equal (by expense and q_host)
+*/
+
+static int
+sortexpensive(xx, yy)
+ ADDRESS *xx;
+ ADDRESS *yy;
+{
+ if (!bitnset(M_EXPENSIVE, yy->q_mailer->m_flags))
+ return 1; /* xx should go later */
+#if _FFR_HOST_SORT_REVERSE
+ /* XXX maybe compare hostnames from the end? */
+ return sm_strrevcasecmp(xx->q_host, yy->q_host);
+#else /* _FFR_HOST_SORT_REVERSE */
+ return sm_strcasecmp(xx->q_host, yy->q_host);
+#endif /* _FFR_HOST_SORT_REVERSE */
+}
+
+/*
+** SORTBYSIGNATURE -- a strcmp()-like func for q_mailer and q_host in ADDRESS
+**
+** Parameters:
+** xx -- first ADDRESS
+** yy -- second ADDRESS
+**
+** Returns:
+** 0 when the "signature"'s are same
+** <0 when xx->q_signature is less than yy->q_signature
+** >0 when xx->q_signature is greater than yy->q_signature
+**
+** Side Effect:
+** May set ADDRESS pointer for q_signature if not already set.
+*/
+
+static int
+sortbysignature(xx, yy)
+ ADDRESS *xx;
+ ADDRESS *yy;
+{
+ register int ret;
+
+ /* Let's avoid redoing the signature over and over again */
+ if (xx->q_signature == NULL)
+ xx->q_signature = hostsignature(xx->q_mailer, xx->q_host);
+ if (yy->q_signature == NULL)
+ yy->q_signature = hostsignature(yy->q_mailer, yy->q_host);
+ ret = strcmp(xx->q_signature, yy->q_signature);
+
+ /*
+ ** If the two signatures are the same then we will return a sort
+ ** value based on 'q_user'. But note that we have reversed xx and yy
+ ** on purpose. This additional compare helps reduce the number of
+ ** sameaddr() calls and loops in recipient() for the case when
+ ** the rcpt list has been provided already in-order.
+ */
+
+ if (ret == 0)
+ return strcmp(yy->q_user, xx->q_user);
+ else
+ return ret;
+}
+
+/*
+** SENDTOLIST -- Designate a send list.
+**
+** The parameter is a comma-separated list of people to send to.
+** This routine arranges to send to all of them.
+**
+** Parameters:
+** list -- the send list.
+** ctladdr -- the address template for the person to
+** send to -- effective uid/gid are important.
+** This is typically the alias that caused this
+** expansion.
+** sendq -- a pointer to the head of a queue to put
+** these people into.
+** aliaslevel -- the current alias nesting depth -- to
+** diagnose loops.
+** e -- the envelope in which to add these recipients.
+**
+** Returns:
+** The number of addresses actually on the list.
+*/
+
+/* q_flags bits inherited from ctladdr */
+#define QINHERITEDBITS (QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY|QHASNOTIFY)
+
+int
+sendtolist(list, ctladdr, sendq, aliaslevel, e)
+ char *list;
+ ADDRESS *ctladdr;
+ ADDRESS **sendq;
+ int aliaslevel;
+ register ENVELOPE *e;
+{
+ register char *p;
+ register ADDRESS *SM_NONVOLATILE al; /* list of addresses to send to */
+ SM_NONVOLATILE char delimiter; /* the address delimiter */
+ SM_NONVOLATILE int naddrs;
+ SM_NONVOLATILE int i;
+ char *oldto = e->e_to;
+ char *SM_NONVOLATILE bufp;
+ char buf[MAXNAME + 1];
+
+ if (list == NULL)
+ {
+ syserr("sendtolist: null list");
+ return 0;
+ }
+
+ if (tTd(25, 1))
+ {
+ sm_dprintf("sendto: %s\n ctladdr=", list);
+ printaddr(ctladdr, false);
+ }
+
+ /* heuristic to determine old versus new style addresses */
+ if (ctladdr == NULL &&
+ (strchr(list, ',') != NULL || strchr(list, ';') != NULL ||
+ strchr(list, '<') != NULL || strchr(list, '(') != NULL))
+ e->e_flags &= ~EF_OLDSTYLE;
+ delimiter = ' ';
+ if (!bitset(EF_OLDSTYLE, e->e_flags) || ctladdr != NULL)
+ delimiter = ',';
+
+ al = NULL;
+ naddrs = 0;
+
+ /* make sure we have enough space to copy the string */
+ i = strlen(list) + 1;
+ if (i <= sizeof buf)
+ {
+ bufp = buf;
+ i = sizeof buf;
+ }
+ else
+ bufp = sm_malloc_x(i);
+
+ SM_TRY
+ {
+ (void) sm_strlcpy(bufp, denlstring(list, false, true), i);
+
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e r");
+ for (p = bufp; *p != '\0'; )
+ {
+ auto char *delimptr;
+ register ADDRESS *a;
+
+ /* parse the address */
+ while ((isascii(*p) && isspace(*p)) || *p == ',')
+ p++;
+ a = parseaddr(p, NULLADDR, RF_COPYALL, delimiter,
+ &delimptr, e, true);
+ p = delimptr;
+ if (a == NULL)
+ continue;
+ a->q_next = al;
+ a->q_alias = ctladdr;
+
+ /* arrange to inherit attributes from parent */
+ if (ctladdr != NULL)
+ {
+ ADDRESS *b;
+
+ /* self reference test */
+ if (sameaddr(ctladdr, a))
+ {
+ if (tTd(27, 5))
+ {
+ sm_dprintf("sendtolist: QSELFREF ");
+ printaddr(ctladdr, false);
+ }
+ ctladdr->q_flags |= QSELFREF;
+ }
+
+ /* check for address loops */
+ b = self_reference(a);
+ if (b != NULL)
+ {
+ b->q_flags |= QSELFREF;
+ if (tTd(27, 5))
+ {
+ sm_dprintf("sendtolist: QSELFREF ");
+ printaddr(b, false);
+ }
+ if (a != b)
+ {
+ if (tTd(27, 5))
+ {
+ sm_dprintf("sendtolist: QS_DONTSEND ");
+ printaddr(a, false);
+ }
+ a->q_state = QS_DONTSEND;
+ b->q_flags |= a->q_flags & QNOTREMOTE;
+ continue;
+ }
+ }
+
+ /* full name */
+ if (a->q_fullname == NULL)
+ a->q_fullname = ctladdr->q_fullname;
+
+ /* various flag bits */
+ a->q_flags &= ~QINHERITEDBITS;
+ a->q_flags |= ctladdr->q_flags & QINHERITEDBITS;
+
+ /* DSN recipient information */
+ a->q_finalrcpt = ctladdr->q_finalrcpt;
+ a->q_orcpt = ctladdr->q_orcpt;
+ }
+
+ al = a;
+ }
+
+ /* arrange to send to everyone on the local send list */
+ while (al != NULL)
+ {
+ register ADDRESS *a = al;
+
+ al = a->q_next;
+ a = recipient(a, sendq, aliaslevel, e);
+ naddrs++;
+ }
+ }
+ SM_FINALLY
+ {
+ e->e_to = oldto;
+ if (bufp != buf)
+ sm_free(bufp);
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), NULL);
+ }
+ SM_END_TRY
+ return naddrs;
+}
+#if MILTER
+/*
+** REMOVEFROMLIST -- Remove addresses from a send list.
+**
+** The parameter is a comma-separated list of recipients to remove.
+** Note that it only deletes matching addresses. If those addresses
+** have been expanded already in the sendq, it won't mark the
+** expanded recipients as QS_REMOVED.
+**
+** Parameters:
+** list -- the list to remove.
+** sendq -- a pointer to the head of a queue to remove
+** these addresses from.
+** e -- the envelope in which to remove these recipients.
+**
+** Returns:
+** The number of addresses removed from the list.
+**
+*/
+
+int
+removefromlist(list, sendq, e)
+ char *list;
+ ADDRESS **sendq;
+ ENVELOPE *e;
+{
+ SM_NONVOLATILE char delimiter; /* the address delimiter */
+ SM_NONVOLATILE int naddrs;
+ SM_NONVOLATILE int i;
+ char *p;
+ char *oldto = e->e_to;
+ char *SM_NONVOLATILE bufp;
+ char buf[MAXNAME + 1];
+
+ if (list == NULL)
+ {
+ syserr("removefromlist: null list");
+ return 0;
+ }
+
+ if (tTd(25, 1))
+ sm_dprintf("removefromlist: %s\n", list);
+
+ /* heuristic to determine old versus new style addresses */
+ if (strchr(list, ',') != NULL || strchr(list, ';') != NULL ||
+ strchr(list, '<') != NULL || strchr(list, '(') != NULL)
+ e->e_flags &= ~EF_OLDSTYLE;
+ delimiter = ' ';
+ if (!bitset(EF_OLDSTYLE, e->e_flags))
+ delimiter = ',';
+
+ naddrs = 0;
+
+ /* make sure we have enough space to copy the string */
+ i = strlen(list) + 1;
+ if (i <= sizeof buf)
+ {
+ bufp = buf;
+ i = sizeof buf;
+ }
+ else
+ bufp = sm_malloc_x(i);
+
+ SM_TRY
+ {
+ (void) sm_strlcpy(bufp, denlstring(list, false, true), i);
+
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), "e r");
+ for (p = bufp; *p != '\0'; )
+ {
+ ADDRESS a; /* parsed address to be removed */
+ ADDRESS *q;
+ ADDRESS **pq;
+ char *delimptr;
+
+ /* parse the address */
+ while ((isascii(*p) && isspace(*p)) || *p == ',')
+ p++;
+ if (parseaddr(p, &a, RF_COPYALL,
+ delimiter, &delimptr, e, true) == NULL)
+ {
+ p = delimptr;
+ continue;
+ }
+ p = delimptr;
+ for (pq = sendq; (q = *pq) != NULL; pq = &q->q_next)
+ {
+ if (!QS_IS_DEAD(q->q_state) &&
+ sameaddr(q, &a))
+ {
+ if (tTd(25, 5))
+ {
+ sm_dprintf("removefromlist: QS_REMOVED ");
+ printaddr(&a, false);
+ }
+ q->q_state = QS_REMOVED;
+ naddrs++;
+ break;
+ }
+ }
+ }
+ }
+ SM_FINALLY
+ {
+ e->e_to = oldto;
+ if (bufp != buf)
+ sm_free(bufp);
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"), NULL);
+ }
+ SM_END_TRY
+ return naddrs;
+}
+#endif /* MILTER */
+/*
+** RECIPIENT -- Designate a message recipient
+**
+** Saves the named person for future mailing.
+**
+** Parameters:
+** new -- the (preparsed) address header for the recipient.
+** sendq -- a pointer to the head of a queue to put the
+** recipient in. Duplicate suppression is done
+** in this queue.
+** aliaslevel -- the current alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** The actual address in the queue. This will be "a" if
+** the address is not a duplicate, else the original address.
+**
+*/
+
+ADDRESS *
+recipient(new, sendq, aliaslevel, e)
+ register ADDRESS *new;
+ register ADDRESS **sendq;
+ int aliaslevel;
+ register ENVELOPE *e;
+{
+ register ADDRESS *q;
+ ADDRESS **pq;
+ ADDRESS **prev;
+ register struct mailer *m;
+ register char *p;
+ int i, buflen;
+ bool quoted; /* set if the addr has a quote bit */
+ bool insert;
+ int findusercount;
+ bool initialdontsend;
+ char *buf;
+ char buf0[MAXNAME + 1]; /* unquoted image of the user name */
+ sortfn_t *sortfn;
+
+ p = NULL;
+ quoted = false;
+ insert = false;
+ findusercount = 0;
+ initialdontsend = QS_IS_DEAD(new->q_state);
+ e->e_to = new->q_paddr;
+ m = new->q_mailer;
+ errno = 0;
+ if (aliaslevel == 0)
+ new->q_flags |= QPRIMARY;
+ if (tTd(26, 1))
+ {
+ sm_dprintf("\nrecipient (%d): ", aliaslevel);
+ printaddr(new, false);
+ }
+
+ /* if this is primary, use it as original recipient */
+ if (new->q_alias == NULL)
+ {
+ if (e->e_origrcpt == NULL)
+ e->e_origrcpt = new->q_paddr;
+ else if (e->e_origrcpt != new->q_paddr)
+ e->e_origrcpt = "";
+ }
+
+ /* find parent recipient for finalrcpt and orcpt */
+ for (q = new; q->q_alias != NULL; q = q->q_alias)
+ continue;
+
+ /* find final recipient DSN address */
+ if (new->q_finalrcpt == NULL &&
+ e->e_from.q_mailer != NULL)
+ {
+ char frbuf[MAXLINE];
+
+ p = e->e_from.q_mailer->m_addrtype;
+ if (p == NULL)
+ p = "rfc822";
+ if (sm_strcasecmp(p, "rfc822") != 0)
+ {
+ (void) sm_snprintf(frbuf, sizeof frbuf, "%s; %.800s",
+ q->q_mailer->m_addrtype,
+ q->q_user);
+ }
+ else if (strchr(q->q_user, '@') != NULL)
+ {
+ (void) sm_snprintf(frbuf, sizeof frbuf, "%s; %.800s",
+ p, q->q_user);
+ }
+ else if (strchr(q->q_paddr, '@') != NULL)
+ {
+ char *qp;
+ bool b;
+
+ qp = q->q_paddr;
+
+ /* strip brackets from address */
+ b = false;
+ if (*qp == '<')
+ {
+ b = qp[strlen(qp) - 1] == '>';
+ if (b)
+ qp[strlen(qp) - 1] = '\0';
+ qp++;
+ }
+ (void) sm_snprintf(frbuf, sizeof frbuf, "%s; %.800s",
+ p, qp);
+
+ /* undo damage */
+ if (b)
+ qp[strlen(qp)] = '>';
+ }
+ else
+ {
+ (void) sm_snprintf(frbuf, sizeof frbuf,
+ "%s; %.700s@%.100s",
+ p, q->q_user, MyHostName);
+ }
+ new->q_finalrcpt = sm_rpool_strdup_x(e->e_rpool, frbuf);
+ }
+
+#if _FFR_GEN_ORCPT
+ /* set ORCPT DSN arg if not already set */
+ if (new->q_orcpt == NULL)
+ {
+ /* check for an existing ORCPT */
+ if (q->q_orcpt != NULL)
+ new->q_orcpt = q->q_orcpt;
+ else
+ {
+ /* make our own */
+ bool b = false;
+ char *qp;
+ char obuf[MAXLINE];
+
+ if (e->e_from.q_mailer != NULL)
+ p = e->e_from.q_mailer->m_addrtype;
+ if (p == NULL)
+ p = "rfc822";
+ (void) sm_strlcpyn(obuf, sizeof obuf, 2, p, ";");
+
+ qp = q->q_paddr;
+
+ /* FFR: Needs to strip comments from stdin addrs */
+
+ /* strip brackets from address */
+ if (*qp == '<')
+ {
+ b = qp[strlen(qp) - 1] == '>';
+ if (b)
+ qp[strlen(qp) - 1] = '\0';
+ qp++;
+ }
+
+ p = xtextify(denlstring(qp, true, false), NULL);
+
+ if (sm_strlcat(obuf, p, sizeof obuf) >= sizeof obuf)
+ {
+ /* if too big, don't use it */
+ obuf[0] = '\0';
+ }
+
+ /* undo damage */
+ if (b)
+ qp[strlen(qp)] = '>';
+
+ if (obuf[0] != '\0')
+ new->q_orcpt =
+ sm_rpool_strdup_x(e->e_rpool, obuf);
+ }
+ }
+#endif /* _FFR_GEN_ORCPT */
+
+ /* break aliasing loops */
+ if (aliaslevel > MaxAliasRecursion)
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.4.6";
+ usrerrenh(new->q_status,
+ "554 aliasing/forwarding loop broken (%d aliases deep; %d max)",
+ aliaslevel, MaxAliasRecursion);
+ return new;
+ }
+
+ /*
+ ** Finish setting up address structure.
+ */
+
+ /* get unquoted user for file, program or user.name check */
+ i = strlen(new->q_user);
+ if (i >= sizeof buf0)
+ {
+ buflen = i + 1;
+ buf = xalloc(buflen);
+ }
+ else
+ {
+ buf = buf0;
+ buflen = sizeof buf0;
+ }
+ (void) sm_strlcpy(buf, new->q_user, buflen);
+ for (p = buf; *p != '\0' && !quoted; p++)
+ {
+ if (*p == '\\')
+ quoted = true;
+ }
+ stripquotes(buf);
+
+ /* check for direct mailing to restricted mailers */
+ if (m == ProgMailer)
+ {
+ if (new->q_alias == NULL || UseMSP ||
+ bitset(EF_UNSAFE, e->e_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ usrerrenh(new->q_status,
+ "550 Cannot mail directly to programs");
+ }
+ else if (bitset(QBOGUSSHELL, new->q_alias->q_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ if (new->q_alias->q_ruser == NULL)
+ usrerrenh(new->q_status,
+ "550 UID %d is an unknown user: cannot mail to programs",
+ new->q_alias->q_uid);
+ else
+ usrerrenh(new->q_status,
+ "550 User %s@%s doesn't have a valid shell for mailing to programs",
+ new->q_alias->q_ruser, MyHostName);
+ }
+ else if (bitset(QUNSAFEADDR, new->q_alias->q_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ new->q_rstatus = "550 Unsafe for mailing to programs";
+ usrerrenh(new->q_status,
+ "550 Address %s is unsafe for mailing to programs",
+ new->q_alias->q_paddr);
+ }
+ }
+
+ /*
+ ** Look up this person in the recipient list.
+ ** If they are there already, return, otherwise continue.
+ ** If the list is empty, just add it. Notice the cute
+ ** hack to make from addresses suppress things correctly:
+ ** the QS_DUPLICATE state will be set in the send list.
+ ** [Please note: the emphasis is on "hack."]
+ */
+
+ prev = NULL;
+
+ /*
+ ** If this message is going to the queue or FastSplit is set
+ ** and it is the first try and the envelope hasn't split, then we
+ ** avoid doing an MX RR lookup now because one will be done when the
+ ** message is extracted from the queue later. It can go to the queue
+ ** because all messages are going to the queue or this mailer of
+ ** the current recipient is marked expensive.
+ */
+
+ if (WILL_BE_QUEUED(e->e_sendmode) ||
+ (!bitset(EF_SPLIT, e->e_flags) && e->e_ntries == 0 &&
+ FastSplit > 0))
+ sortfn = sorthost;
+ else if (NoConnect && bitnset(M_EXPENSIVE, new->q_mailer->m_flags))
+ sortfn = sortexpensive;
+ else
+ sortfn = sortbysignature;
+
+ for (pq = sendq; (q = *pq) != NULL; pq = &q->q_next)
+ {
+ /*
+ ** If address is "less than" it should be inserted now.
+ ** If address is "greater than" current comparison it'll
+ ** insert later in the list; so loop again (if possible).
+ ** If address is "equal" (different equal than sameaddr()
+ ** call) then check if sameaddr() will be true.
+ ** Because this list is now sorted, it'll mean fewer
+ ** comparisons and fewer loops which is important for more
+ ** recipients.
+ */
+
+ i = (*sortfn)(new, q);
+ if (i == 0) /* equal */
+ {
+ /*
+ ** Sortbysignature() has said that the two have
+ ** equal MX RR's and the same user. Calling sameaddr()
+ ** now checks if the two hosts are as identical as the
+ ** MX RR's are (which might not be the case)
+ ** before saying these are the identical addresses.
+ */
+
+ if (sameaddr(q, new) &&
+ (bitset(QRCPTOK, q->q_flags) ||
+ !bitset(QPRIMARY, q->q_flags)))
+ {
+ if (tTd(26, 1))
+ {
+ sm_dprintf("%s in sendq: ",
+ new->q_paddr);
+ printaddr(q, false);
+ }
+ if (!bitset(QPRIMARY, q->q_flags))
+ {
+ if (!QS_IS_DEAD(new->q_state))
+ message("duplicate suppressed");
+ else
+ q->q_state = QS_DUPLICATE;
+ q->q_flags |= new->q_flags;
+ }
+ else if (bitset(QSELFREF, q->q_flags)
+ || q->q_state == QS_REMOVED)
+ {
+ /*
+ ** If an earlier milter removed the
+ ** address, a later one can still add
+ ** it back.
+ */
+
+ q->q_state = new->q_state;
+ q->q_flags |= new->q_flags;
+ }
+ new = q;
+ goto done;
+ }
+ }
+ else if (i < 0) /* less than */
+ {
+ insert = true;
+ break;
+ }
+ prev = pq;
+ }
+
+ /* pq should point to an address, never NULL */
+ SM_ASSERT(pq != NULL);
+
+ /* add address on list */
+ if (insert)
+ {
+ /*
+ ** insert before 'pq'. Only possible when at least 1
+ ** ADDRESS is in the list already.
+ */
+
+ new->q_next = *pq;
+ if (prev == NULL)
+ *sendq = new; /* To be the first ADDRESS */
+ else
+ (*prev)->q_next = new;
+ }
+ else
+ {
+ /*
+ ** Place in list at current 'pq' position. Possible
+ ** when there are 0 or more ADDRESS's in the list.
+ */
+
+ new->q_next = NULL;
+ *pq = new;
+ }
+
+ /* added a new address: clear split flag */
+ e->e_flags &= ~EF_SPLIT;
+
+ /*
+ ** Alias the name and handle special mailer types.
+ */
+
+ trylocaluser:
+ if (tTd(29, 7))
+ {
+ sm_dprintf("at trylocaluser: ");
+ printaddr(new, false);
+ }
+
+ if (!QS_IS_OK(new->q_state))
+ {
+ if (QS_IS_UNDELIVERED(new->q_state))
+ e->e_nrcpts++;
+ goto testselfdestruct;
+ }
+
+ if (m == InclMailer)
+ {
+ new->q_state = QS_INCLUDED;
+ if (new->q_alias == NULL || UseMSP ||
+ bitset(EF_UNSAFE, e->e_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ usrerrenh(new->q_status,
+ "550 Cannot mail directly to :include:s");
+ }
+ else
+ {
+ int ret;
+
+ message("including file %s", new->q_user);
+ ret = include(new->q_user, false, new,
+ sendq, aliaslevel, e);
+ if (transienterror(ret))
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_ERR, e->e_id,
+ "include %s: transient error: %s",
+ shortenstring(new->q_user,
+ MAXSHORTSTR),
+ sm_errstring(ret));
+ new->q_state = QS_QUEUEUP;
+ usrerr("451 4.2.4 Cannot open %s: %s",
+ shortenstring(new->q_user,
+ MAXSHORTSTR),
+ sm_errstring(ret));
+ }
+ else if (ret != 0)
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.2.4";
+ usrerrenh(new->q_status,
+ "550 Cannot open %s: %s",
+ shortenstring(new->q_user,
+ MAXSHORTSTR),
+ sm_errstring(ret));
+ }
+ }
+ }
+ else if (m == FileMailer)
+ {
+ /* check if allowed */
+ if (new->q_alias == NULL || UseMSP ||
+ bitset(EF_UNSAFE, e->e_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ usrerrenh(new->q_status,
+ "550 Cannot mail directly to files");
+ }
+ else if (bitset(QBOGUSSHELL, new->q_alias->q_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ if (new->q_alias->q_ruser == NULL)
+ usrerrenh(new->q_status,
+ "550 UID %d is an unknown user: cannot mail to files",
+ new->q_alias->q_uid);
+ else
+ usrerrenh(new->q_status,
+ "550 User %s@%s doesn't have a valid shell for mailing to files",
+ new->q_alias->q_ruser, MyHostName);
+ }
+ else if (bitset(QUNSAFEADDR, new->q_alias->q_flags))
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.7.1";
+ new->q_rstatus = "550 Unsafe for mailing to files";
+ usrerrenh(new->q_status,
+ "550 Address %s is unsafe for mailing to files",
+ new->q_alias->q_paddr);
+ }
+ }
+
+ /* try aliasing */
+ if (!quoted && QS_IS_OK(new->q_state) &&
+ bitnset(M_ALIASABLE, m->m_flags))
+ alias(new, sendq, aliaslevel, e);
+
+#if USERDB
+ /* if not aliased, look it up in the user database */
+ if (!bitset(QNOTREMOTE, new->q_flags) &&
+ QS_IS_SENDABLE(new->q_state) &&
+ bitnset(M_CHECKUDB, m->m_flags))
+ {
+ if (udbexpand(new, sendq, aliaslevel, e) == EX_TEMPFAIL)
+ {
+ new->q_state = QS_QUEUEUP;
+ if (e->e_message == NULL)
+ e->e_message = "Deferred: user database error";
+ if (new->q_message == NULL)
+ new->q_message = "Deferred: user database error";
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, e->e_id,
+ "deferred: udbexpand: %s",
+ sm_errstring(errno));
+ message("queued (user database error): %s",
+ sm_errstring(errno));
+ e->e_nrcpts++;
+ goto testselfdestruct;
+ }
+ }
+#endif /* USERDB */
+
+ /*
+ ** If we have a level two config file, then pass the name through
+ ** Ruleset 5 before sending it off. Ruleset 5 has the right
+ ** to rewrite it to another mailer. This gives us a hook
+ ** after local aliasing has been done.
+ */
+
+ if (tTd(29, 5))
+ {
+ sm_dprintf("recipient: testing local? cl=%d, rr5=%p\n\t",
+ ConfigLevel, RewriteRules[5]);
+ printaddr(new, false);
+ }
+ if (ConfigLevel >= 2 && RewriteRules[5] != NULL &&
+ bitnset(M_TRYRULESET5, m->m_flags) &&
+ !bitset(QNOTREMOTE, new->q_flags) &&
+ QS_IS_OK(new->q_state))
+ {
+ maplocaluser(new, sendq, aliaslevel + 1, e);
+ }
+
+ /*
+ ** If it didn't get rewritten to another mailer, go ahead
+ ** and deliver it.
+ */
+
+ if (QS_IS_OK(new->q_state) &&
+ bitnset(M_HASPWENT, m->m_flags))
+ {
+ auto bool fuzzy;
+ SM_MBDB_T user;
+ int status;
+
+ /* warning -- finduser may trash buf */
+ status = finduser(buf, &fuzzy, &user);
+ switch (status)
+ {
+ case EX_TEMPFAIL:
+ new->q_state = QS_QUEUEUP;
+ new->q_status = "4.5.2";
+ giveresponse(EX_TEMPFAIL, new->q_status, m, NULL,
+ new->q_alias, (time_t) 0, e, new);
+ break;
+ default:
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.1.1";
+ new->q_rstatus = "550 5.1.1 User unknown";
+ giveresponse(EX_NOUSER, new->q_status, m, NULL,
+ new->q_alias, (time_t) 0, e, new);
+ break;
+ case EX_OK:
+ if (fuzzy)
+ {
+ /* name was a fuzzy match */
+ new->q_user = sm_rpool_strdup_x(e->e_rpool,
+ user.mbdb_name);
+ if (findusercount++ > 3)
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.4.6";
+ usrerrenh(new->q_status,
+ "554 aliasing/forwarding loop for %s broken",
+ user.mbdb_name);
+ goto done;
+ }
+
+ /* see if it aliases */
+ (void) sm_strlcpy(buf, user.mbdb_name, buflen);
+ goto trylocaluser;
+ }
+ if (*user.mbdb_homedir == '\0')
+ new->q_home = NULL;
+ else if (strcmp(user.mbdb_homedir, "/") == 0)
+ new->q_home = "";
+ else
+ new->q_home = sm_rpool_strdup_x(e->e_rpool,
+ user.mbdb_homedir);
+ if (user.mbdb_uid != SM_NO_UID)
+ {
+ new->q_uid = user.mbdb_uid;
+ new->q_gid = user.mbdb_gid;
+ new->q_flags |= QGOODUID;
+ }
+ new->q_ruser = sm_rpool_strdup_x(e->e_rpool,
+ user.mbdb_name);
+ if (user.mbdb_fullname[0] != '\0')
+ new->q_fullname = sm_rpool_strdup_x(e->e_rpool,
+ user.mbdb_fullname);
+ if (!usershellok(user.mbdb_name, user.mbdb_shell))
+ {
+ new->q_flags |= QBOGUSSHELL;
+ }
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ /* don't do any more now */
+ new->q_state = QS_VERIFIED;
+ }
+ else if (!quoted)
+ forward(new, sendq, aliaslevel, e);
+ }
+ }
+ if (!QS_IS_DEAD(new->q_state))
+ e->e_nrcpts++;
+
+ testselfdestruct:
+ new->q_flags |= QTHISPASS;
+ if (tTd(26, 8))
+ {
+ sm_dprintf("testselfdestruct: ");
+ printaddr(new, false);
+ if (tTd(26, 10))
+ {
+ sm_dprintf("SENDQ:\n");
+ printaddr(*sendq, true);
+ sm_dprintf("----\n");
+ }
+ }
+ if (new->q_alias == NULL && new != &e->e_from &&
+ QS_IS_DEAD(new->q_state))
+ {
+ for (q = *sendq; q != NULL; q = q->q_next)
+ {
+ if (!QS_IS_DEAD(q->q_state))
+ break;
+ }
+ if (q == NULL)
+ {
+ new->q_state = QS_BADADDR;
+ new->q_status = "5.4.6";
+ usrerrenh(new->q_status,
+ "554 aliasing/forwarding loop broken");
+ }
+ }
+
+ done:
+ new->q_flags |= QTHISPASS;
+ if (buf != buf0)
+ sm_free(buf); /* XXX leak if above code raises exception */
+
+ /*
+ ** If we are at the top level, check to see if this has
+ ** expanded to exactly one address. If so, it can inherit
+ ** the primaryness of the address.
+ **
+ ** While we're at it, clear the QTHISPASS bits.
+ */
+
+ if (aliaslevel == 0)
+ {
+ int nrcpts = 0;
+ ADDRESS *only = NULL;
+
+ for (q = *sendq; q != NULL; q = q->q_next)
+ {
+ if (bitset(QTHISPASS, q->q_flags) &&
+ QS_IS_SENDABLE(q->q_state))
+ {
+ nrcpts++;
+ only = q;
+ }
+ q->q_flags &= ~QTHISPASS;
+ }
+ if (nrcpts == 1)
+ {
+ /* check to see if this actually got a new owner */
+ q = only;
+ while ((q = q->q_alias) != NULL)
+ {
+ if (q->q_owner != NULL)
+ break;
+ }
+ if (q == NULL)
+ only->q_flags |= QPRIMARY;
+ }
+ else if (!initialdontsend && nrcpts > 0)
+ {
+ /* arrange for return receipt */
+ e->e_flags |= EF_SENDRECEIPT;
+ new->q_flags |= QEXPANDED;
+ if (e->e_xfp != NULL &&
+ bitset(QPINGONSUCCESS, new->q_flags))
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "%s... expanded to multiple addresses\n",
+ new->q_paddr);
+ }
+ }
+ new->q_flags |= QRCPTOK;
+ (void) sm_snprintf(buf0, sizeof buf0, "%d", e->e_nrcpts);
+ macdefine(&e->e_macro, A_TEMP, macid("{nrcpts}"), buf0);
+ return new;
+}
+/*
+** FINDUSER -- find the password entry for a user.
+**
+** This looks a lot like getpwnam, except that it may want to
+** do some fancier pattern matching in /etc/passwd.
+**
+** This routine contains most of the time of many sendmail runs.
+** It deserves to be optimized.
+**
+** Parameters:
+** name -- the name to match against.
+** fuzzyp -- an outarg that is set to true if this entry
+** was found using the fuzzy matching algorithm;
+** set to false otherwise.
+** user -- structure to fill in if user is found
+**
+** Returns:
+** On success, fill in *user, set *fuzzyp and return EX_OK.
+** If the user was not found, return EX_NOUSER.
+** On error, return EX_TEMPFAIL or EX_OSERR.
+**
+** Side Effects:
+** may modify name.
+*/
+
+int
+finduser(name, fuzzyp, user)
+ char *name;
+ bool *fuzzyp;
+ SM_MBDB_T *user;
+{
+#if MATCHGECOS
+ register struct passwd *pw;
+#endif /* MATCHGECOS */
+ register char *p;
+ bool tryagain;
+ int status;
+
+ if (tTd(29, 4))
+ sm_dprintf("finduser(%s): ", name);
+
+ *fuzzyp = false;
+
+#if HESIOD
+ /* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
+ for (p = name; *p != '\0'; p++)
+ if (!isascii(*p) || !isdigit(*p))
+ break;
+ if (*p == '\0')
+ {
+ if (tTd(29, 4))
+ sm_dprintf("failed (numeric input)\n");
+ return EX_NOUSER;
+ }
+#endif /* HESIOD */
+
+ /* look up this login name using fast path */
+ status = sm_mbdb_lookup(name, user);
+ if (status != EX_NOUSER)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("%s (non-fuzzy)\n", sm_strexit(status));
+ return status;
+ }
+
+ /* try mapping it to lower case */
+ tryagain = false;
+ for (p = name; *p != '\0'; p++)
+ {
+ if (isascii(*p) && isupper(*p))
+ {
+ *p = tolower(*p);
+ tryagain = true;
+ }
+ }
+ if (tryagain && (status = sm_mbdb_lookup(name, user)) != EX_NOUSER)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("%s (lower case)\n", sm_strexit(status));
+ *fuzzyp = true;
+ return status;
+ }
+
+#if MATCHGECOS
+ /* see if fuzzy matching allowed */
+ if (!MatchGecos)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("not found (fuzzy disabled)\n");
+ return EX_NOUSER;
+ }
+
+ /* search for a matching full name instead */
+ for (p = name; *p != '\0'; p++)
+ {
+ if (*p == (SpaceSub & 0177) || *p == '_')
+ *p = ' ';
+ }
+ (void) setpwent();
+ while ((pw = getpwent()) != NULL)
+ {
+ char buf[MAXNAME + 1];
+
+# if 0
+ if (sm_strcasecmp(pw->pw_name, name) == 0)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("found (case wrapped)\n");
+ break;
+ }
+# endif /* 0 */
+
+ sm_pwfullname(pw->pw_gecos, pw->pw_name, buf, sizeof buf);
+ if (strchr(buf, ' ') != NULL && sm_strcasecmp(buf, name) == 0)
+ {
+ if (tTd(29, 4))
+ sm_dprintf("fuzzy matches %s\n", pw->pw_name);
+ message("sending to login name %s", pw->pw_name);
+ break;
+ }
+ }
+ if (pw != NULL)
+ *fuzzyp = true;
+ else if (tTd(29, 4))
+ sm_dprintf("no fuzzy match found\n");
+# if DEC_OSF_BROKEN_GETPWENT /* DEC OSF/1 3.2 or earlier */
+ endpwent();
+# endif /* DEC_OSF_BROKEN_GETPWENT */
+ if (pw == NULL)
+ return EX_NOUSER;
+ sm_mbdb_frompw(user, pw);
+ return EX_OK;
+#else /* MATCHGECOS */
+ if (tTd(29, 4))
+ sm_dprintf("not found (fuzzy disabled)\n");
+ return EX_NOUSER;
+#endif /* MATCHGECOS */
+}
+/*
+** WRITABLE -- predicate returning if the file is writable.
+**
+** This routine must duplicate the algorithm in sys/fio.c.
+** Unfortunately, we cannot use the access call since we
+** won't necessarily be the real uid when we try to
+** actually open the file.
+**
+** Notice that ANY file with ANY execute bit is automatically
+** not writable. This is also enforced by mailfile.
+**
+** Parameters:
+** filename -- the file name to check.
+** ctladdr -- the controlling address for this file.
+** flags -- SFF_* flags to control the function.
+**
+** Returns:
+** true -- if we will be able to write this file.
+** false -- if we cannot write this file.
+**
+** Side Effects:
+** none.
+*/
+
+bool
+writable(filename, ctladdr, flags)
+ char *filename;
+ ADDRESS *ctladdr;
+ long flags;
+{
+ uid_t euid = 0;
+ gid_t egid = 0;
+ char *user = NULL;
+
+ if (tTd(44, 5))
+ sm_dprintf("writable(%s, 0x%lx)\n", filename, flags);
+
+ /*
+ ** File does exist -- check that it is writable.
+ */
+
+ if (geteuid() != 0)
+ {
+ euid = geteuid();
+ egid = getegid();
+ user = NULL;
+ }
+ else if (ctladdr != NULL)
+ {
+ euid = ctladdr->q_uid;
+ egid = ctladdr->q_gid;
+ user = ctladdr->q_user;
+ }
+ else if (bitset(SFF_RUNASREALUID, flags))
+ {
+ euid = RealUid;
+ egid = RealGid;
+ user = RealUserName;
+ }
+ else if (FileMailer != NULL && !bitset(SFF_ROOTOK, flags))
+ {
+ euid = FileMailer->m_uid;
+ egid = FileMailer->m_gid;
+ user = NULL;
+ }
+ else
+ {
+ euid = egid = 0;
+ user = NULL;
+ }
+ if (!bitset(SFF_ROOTOK, flags))
+ {
+ if (euid == 0)
+ {
+ euid = DefUid;
+ user = DefUser;
+ }
+ if (egid == 0)
+ egid = DefGid;
+ }
+ if (geteuid() == 0 &&
+ (ctladdr == NULL || !bitset(QGOODUID, ctladdr->q_flags)))
+ flags |= SFF_SETUIDOK;
+
+ if (!bitnset(DBS_FILEDELIVERYTOSYMLINK, DontBlameSendmail))
+ flags |= SFF_NOSLINK;
+ if (!bitnset(DBS_FILEDELIVERYTOHARDLINK, DontBlameSendmail))
+ flags |= SFF_NOHLINK;
+
+ errno = safefile(filename, euid, egid, user, flags, S_IWRITE, NULL);
+ return errno == 0;
+}
+/*
+** INCLUDE -- handle :include: specification.
+**
+** Parameters:
+** fname -- filename to include.
+** forwarding -- if true, we are reading a .forward file.
+** if false, it's a :include: file.
+** ctladdr -- address template to use to fill in these
+** addresses -- effective user/group id are
+** the important things.
+** sendq -- a pointer to the head of the send queue
+** to put these addresses in.
+** aliaslevel -- the alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** open error status
+**
+** Side Effects:
+** reads the :include: file and sends to everyone
+** listed in that file.
+**
+** Security Note:
+** If you have restricted chown (that is, you can't
+** give a file away), it is reasonable to allow programs
+** and files called from this :include: file to be to be
+** run as the owner of the :include: file. This is bogus
+** if there is any chance of someone giving away a file.
+** We assume that pre-POSIX systems can give away files.
+**
+** There is an additional restriction that if you
+** forward to a :include: file, it will not take on
+** the ownership of the :include: file. This may not
+** be necessary, but shouldn't hurt.
+*/
+
+static jmp_buf CtxIncludeTimeout;
+
+int
+include(fname, forwarding, ctladdr, sendq, aliaslevel, e)
+ char *fname;
+ bool forwarding;
+ ADDRESS *ctladdr;
+ ADDRESS **sendq;
+ int aliaslevel;
+ ENVELOPE *e;
+{
+ SM_FILE_T *volatile fp = NULL;
+ char *oldto = e->e_to;
+ char *oldfilename = FileName;
+ int oldlinenumber = LineNumber;
+ register SM_EVENT *ev = NULL;
+ int nincludes;
+ int mode;
+ volatile bool maxreached = false;
+ register ADDRESS *ca;
+ volatile uid_t saveduid;
+ volatile gid_t savedgid;
+ volatile uid_t uid;
+ volatile gid_t gid;
+ char *volatile user;
+ int rval = 0;
+ volatile long sfflags = SFF_REGONLY;
+ register char *p;
+ bool safechown = false;
+ volatile bool safedir = false;
+ struct stat st;
+ char buf[MAXLINE];
+
+ if (tTd(27, 2))
+ sm_dprintf("include(%s)\n", fname);
+ if (tTd(27, 4))
+ sm_dprintf(" ruid=%d euid=%d\n",
+ (int) getuid(), (int) geteuid());
+ if (tTd(27, 14))
+ {
+ sm_dprintf("ctladdr ");
+ printaddr(ctladdr, false);
+ }
+
+ if (tTd(27, 9))
+ sm_dprintf("include: old uid = %d/%d\n",
+ (int) getuid(), (int) geteuid());
+
+ if (forwarding)
+ {
+ sfflags |= SFF_MUSTOWN|SFF_ROOTOK;
+ if (!bitnset(DBS_GROUPWRITABLEFORWARDFILE, DontBlameSendmail))
+ sfflags |= SFF_NOGWFILES;
+ if (!bitnset(DBS_WORLDWRITABLEFORWARDFILE, DontBlameSendmail))
+ sfflags |= SFF_NOWWFILES;
+ }
+ else
+ {
+ if (!bitnset(DBS_GROUPWRITABLEINCLUDEFILE, DontBlameSendmail))
+ sfflags |= SFF_NOGWFILES;
+ if (!bitnset(DBS_WORLDWRITABLEINCLUDEFILE, DontBlameSendmail))
+ sfflags |= SFF_NOWWFILES;
+ }
+
+ /*
+ ** If RunAsUser set, won't be able to run programs as user
+ ** so mark them as unsafe unless the administrator knows better.
+ */
+
+ if ((geteuid() != 0 || RunAsUid != 0) &&
+ !bitnset(DBS_NONROOTSAFEADDR, DontBlameSendmail))
+ {
+ if (tTd(27, 4))
+ sm_dprintf("include: not safe (euid=%d, RunAsUid=%d)\n",
+ (int) geteuid(), (int) RunAsUid);
+ ctladdr->q_flags |= QUNSAFEADDR;
+ }
+
+ ca = getctladdr(ctladdr);
+ if (ca == NULL ||
+ (ca->q_uid == DefUid && ca->q_gid == 0))
+ {
+ uid = DefUid;
+ gid = DefGid;
+ user = DefUser;
+ }
+ else
+ {
+ uid = ca->q_uid;
+ gid = ca->q_gid;
+ user = ca->q_user;
+ }
+#if MAILER_SETUID_METHOD != USE_SETUID
+ saveduid = geteuid();
+ savedgid = getegid();
+ if (saveduid == 0)
+ {
+ if (!DontInitGroups)
+ {
+ if (initgroups(user, gid) == -1)
+ {
+ rval = EAGAIN;
+ syserr("include: initgroups(%s, %d) failed",
+ user, gid);
+ goto resetuid;
+ }
+ }
+ else
+ {
+ GIDSET_T gidset[1];
+
+ gidset[0] = gid;
+ if (setgroups(1, gidset) == -1)
+ {
+ rval = EAGAIN;
+ syserr("include: setgroups() failed");
+ goto resetuid;
+ }
+ }
+
+ if (gid != 0 && setgid(gid) < -1)
+ {
+ rval = EAGAIN;
+ syserr("setgid(%d) failure", gid);
+ goto resetuid;
+ }
+ if (uid != 0)
+ {
+# if MAILER_SETUID_METHOD == USE_SETEUID
+ if (seteuid(uid) < 0)
+ {
+ rval = EAGAIN;
+ syserr("seteuid(%d) failure (real=%d, eff=%d)",
+ uid, (int) getuid(), (int) geteuid());
+ goto resetuid;
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETEUID */
+# if MAILER_SETUID_METHOD == USE_SETREUID
+ if (setreuid(0, uid) < 0)
+ {
+ rval = EAGAIN;
+ syserr("setreuid(0, %d) failure (real=%d, eff=%d)",
+ uid, (int) getuid(), (int) geteuid());
+ goto resetuid;
+ }
+# endif /* MAILER_SETUID_METHOD == USE_SETREUID */
+ }
+ }
+#endif /* MAILER_SETUID_METHOD != USE_SETUID */
+
+ if (tTd(27, 9))
+ sm_dprintf("include: new uid = %d/%d\n",
+ (int) getuid(), (int) geteuid());
+
+ /*
+ ** If home directory is remote mounted but server is down,
+ ** this can hang or give errors; use a timeout to avoid this
+ */
+
+ if (setjmp(CtxIncludeTimeout) != 0)
+ {
+ ctladdr->q_state = QS_QUEUEUP;
+ errno = 0;
+
+ /* return pseudo-error code */
+ rval = E_SM_OPENTIMEOUT;
+ goto resetuid;
+ }
+ if (TimeOuts.to_fileopen > 0)
+ ev = sm_setevent(TimeOuts.to_fileopen, includetimeout, 0);
+ else
+ ev = NULL;
+
+
+ /* check for writable parent directory */
+ p = strrchr(fname, '/');
+ if (p != NULL)
+ {
+ int ret;
+
+ *p = '\0';
+ ret = safedirpath(fname, uid, gid, user,
+ sfflags|SFF_SAFEDIRPATH, 0, 0);
+ if (ret == 0)
+ {
+ /* in safe directory: relax chown & link rules */
+ safedir = true;
+ sfflags |= SFF_NOPATHCHECK;
+ }
+ else
+ {
+ if (bitnset((forwarding ?
+ DBS_FORWARDFILEINUNSAFEDIRPATH :
+ DBS_INCLUDEFILEINUNSAFEDIRPATH),
+ DontBlameSendmail))
+ sfflags |= SFF_NOPATHCHECK;
+ else if (bitnset((forwarding ?
+ DBS_FORWARDFILEINGROUPWRITABLEDIRPATH :
+ DBS_INCLUDEFILEINGROUPWRITABLEDIRPATH),
+ DontBlameSendmail) &&
+ ret == E_SM_GWDIR)
+ {
+ setbitn(DBS_GROUPWRITABLEDIRPATHSAFE,
+ DontBlameSendmail);
+ ret = safedirpath(fname, uid, gid, user,
+ sfflags|SFF_SAFEDIRPATH,
+ 0, 0);
+ clrbitn(DBS_GROUPWRITABLEDIRPATHSAFE,
+ DontBlameSendmail);
+ if (ret == 0)
+ sfflags |= SFF_NOPATHCHECK;
+ else
+ sfflags |= SFF_SAFEDIRPATH;
+ }
+ else
+ sfflags |= SFF_SAFEDIRPATH;
+ if (ret > E_PSEUDOBASE &&
+ !bitnset((forwarding ?
+ DBS_FORWARDFILEINUNSAFEDIRPATHSAFE :
+ DBS_INCLUDEFILEINUNSAFEDIRPATHSAFE),
+ DontBlameSendmail))
+ {
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: unsafe directory path, marked unsafe",
+ shortenstring(fname, MAXSHORTSTR));
+ ctladdr->q_flags |= QUNSAFEADDR;
+ }
+ }
+ *p = '/';
+ }
+
+ /* allow links only in unwritable directories */
+ if (!safedir &&
+ !bitnset((forwarding ?
+ DBS_LINKEDFORWARDFILEINWRITABLEDIR :
+ DBS_LINKEDINCLUDEFILEINWRITABLEDIR),
+ DontBlameSendmail))
+ sfflags |= SFF_NOLINK;
+
+ rval = safefile(fname, uid, gid, user, sfflags, S_IREAD, &st);
+ if (rval != 0)
+ {
+ /* don't use this :include: file */
+ if (tTd(27, 4))
+ sm_dprintf("include: not safe (uid=%d): %s\n",
+ (int) uid, sm_errstring(rval));
+ }
+ else if ((fp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, fname,
+ SM_IO_RDONLY, NULL)) == NULL)
+ {
+ rval = errno;
+ if (tTd(27, 4))
+ sm_dprintf("include: open: %s\n", sm_errstring(rval));
+ }
+ else if (filechanged(fname, sm_io_getinfo(fp,SM_IO_WHAT_FD, NULL), &st))
+ {
+ rval = E_SM_FILECHANGE;
+ if (tTd(27, 4))
+ sm_dprintf("include: file changed after open\n");
+ }
+ if (ev != NULL)
+ sm_clrevent(ev);
+
+resetuid:
+
+#if HASSETREUID || USESETEUID
+ if (saveduid == 0)
+ {
+ if (uid != 0)
+ {
+# if USESETEUID
+ if (seteuid(0) < 0)
+ syserr("!seteuid(0) failure (real=%d, eff=%d)",
+ (int) getuid(), (int) geteuid());
+# else /* USESETEUID */
+ if (setreuid(-1, 0) < 0)
+ syserr("!setreuid(-1, 0) failure (real=%d, eff=%d)",
+ (int) getuid(), (int) geteuid());
+ if (setreuid(RealUid, 0) < 0)
+ syserr("!setreuid(%d, 0) failure (real=%d, eff=%d)",
+ (int) RealUid, (int) getuid(),
+ (int) geteuid());
+# endif /* USESETEUID */
+ }
+ if (setgid(savedgid) < 0)
+ syserr("!setgid(%d) failure (real=%d eff=%d)",
+ (int) savedgid, (int) getgid(),
+ (int) getegid());
+ }
+#endif /* HASSETREUID || USESETEUID */
+
+ if (tTd(27, 9))
+ sm_dprintf("include: reset uid = %d/%d\n",
+ (int) getuid(), (int) geteuid());
+
+ if (rval == E_SM_OPENTIMEOUT)
+ usrerr("451 4.4.1 open timeout on %s", fname);
+
+ if (fp == NULL)
+ return rval;
+
+ if (fstat(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), &st) < 0)
+ {
+ rval = errno;
+ syserr("Cannot fstat %s!", fname);
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ return rval;
+ }
+
+ /* if path was writable, check to avoid file giveaway tricks */
+ safechown = chownsafe(sm_io_getinfo(fp, SM_IO_WHAT_FD, NULL), safedir);
+ if (tTd(27, 6))
+ sm_dprintf("include: parent of %s is %s, chown is %ssafe\n",
+ fname, safedir ? "safe" : "dangerous",
+ safechown ? "" : "un");
+
+ /* if no controlling user or coming from an alias delivery */
+ if (safechown &&
+ (ca == NULL ||
+ (ca->q_uid == DefUid && ca->q_gid == 0)))
+ {
+ ctladdr->q_uid = st.st_uid;
+ ctladdr->q_gid = st.st_gid;
+ ctladdr->q_flags |= QGOODUID;
+ }
+ if (ca != NULL && ca->q_uid == st.st_uid)
+ {
+ /* optimization -- avoid getpwuid if we already have info */
+ ctladdr->q_flags |= ca->q_flags & QBOGUSSHELL;
+ ctladdr->q_ruser = ca->q_ruser;
+ }
+ else if (!forwarding)
+ {
+ register struct passwd *pw;
+
+ pw = sm_getpwuid(st.st_uid);
+ if (pw == NULL)
+ {
+ ctladdr->q_uid = st.st_uid;
+ ctladdr->q_flags |= QBOGUSSHELL;
+ }
+ else
+ {
+ char *sh;
+
+ ctladdr->q_ruser = sm_rpool_strdup_x(e->e_rpool,
+ pw->pw_name);
+ if (safechown)
+ sh = pw->pw_shell;
+ else
+ sh = "/SENDMAIL/ANY/SHELL/";
+ if (!usershellok(pw->pw_name, sh))
+ {
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: user %s has bad shell %s, marked %s",
+ shortenstring(fname,
+ MAXSHORTSTR),
+ pw->pw_name, sh,
+ safechown ? "bogus" : "unsafe");
+ if (safechown)
+ ctladdr->q_flags |= QBOGUSSHELL;
+ else
+ ctladdr->q_flags |= QUNSAFEADDR;
+ }
+ }
+ }
+
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ /* don't do any more now */
+ ctladdr->q_state = QS_VERIFIED;
+ e->e_nrcpts++;
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ return rval;
+ }
+
+ /*
+ ** Check to see if some bad guy can write this file
+ **
+ ** Group write checking could be more clever, e.g.,
+ ** guessing as to which groups are actually safe ("sys"
+ ** may be; "user" probably is not).
+ */
+
+ mode = S_IWOTH;
+ if (!bitnset((forwarding ?
+ DBS_GROUPWRITABLEFORWARDFILESAFE :
+ DBS_GROUPWRITABLEINCLUDEFILESAFE),
+ DontBlameSendmail))
+ mode |= S_IWGRP;
+
+ if (bitset(mode, st.st_mode))
+ {
+ if (tTd(27, 6))
+ sm_dprintf("include: %s is %s writable, marked unsafe\n",
+ shortenstring(fname, MAXSHORTSTR),
+ bitset(S_IWOTH, st.st_mode) ? "world"
+ : "group");
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%s: %s writable %s file, marked unsafe",
+ shortenstring(fname, MAXSHORTSTR),
+ bitset(S_IWOTH, st.st_mode) ? "world" : "group",
+ forwarding ? "forward" : ":include:");
+ ctladdr->q_flags |= QUNSAFEADDR;
+ }
+
+ /* read the file -- each line is a comma-separated list. */
+ FileName = fname;
+ LineNumber = 0;
+ ctladdr->q_flags &= ~QSELFREF;
+ nincludes = 0;
+ while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof buf) != NULL &&
+ !maxreached)
+ {
+ fixcrlf(buf, true);
+ LineNumber++;
+ if (buf[0] == '#' || buf[0] == '\0')
+ continue;
+
+ /* <sp>#@# introduces a comment anywhere */
+ /* for Japanese character sets */
+ for (p = buf; (p = strchr(++p, '#')) != NULL; )
+ {
+ if (p[1] == '@' && p[2] == '#' &&
+ isascii(p[-1]) && isspace(p[-1]) &&
+ (p[3] == '\0' || (isascii(p[3]) && isspace(p[3]))))
+ {
+ --p;
+ while (p > buf && isascii(p[-1]) &&
+ isspace(p[-1]))
+ --p;
+ p[0] = '\0';
+ break;
+ }
+ }
+ if (buf[0] == '\0')
+ continue;
+
+ e->e_to = NULL;
+ message("%s to %s",
+ forwarding ? "forwarding" : "sending", buf);
+ if (forwarding && LogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "forward %.200s => %s",
+ oldto, shortenstring(buf, MAXSHORTSTR));
+
+ nincludes += sendtolist(buf, ctladdr, sendq, aliaslevel + 1, e);
+
+ if (forwarding &&
+ MaxForwardEntries > 0 &&
+ nincludes >= MaxForwardEntries)
+ {
+ /* just stop reading and processing further entries */
+#if 0
+ /* additional: (?) */
+ ctladdr->q_state = QS_DONTSEND;
+#endif /* 0 */
+
+ syserr("Attempt to forward to more than %d addresses (in %s)!",
+ MaxForwardEntries, fname);
+ maxreached = true;
+ }
+ }
+
+ if (sm_io_error(fp) && tTd(27, 3))
+ sm_dprintf("include: read error: %s\n", sm_errstring(errno));
+ if (nincludes > 0 && !bitset(QSELFREF, ctladdr->q_flags))
+ {
+ if (tTd(27, 5))
+ {
+ sm_dprintf("include: QS_DONTSEND ");
+ printaddr(ctladdr, false);
+ }
+ ctladdr->q_state = QS_DONTSEND;
+ }
+
+ (void) sm_io_close(fp, SM_TIME_DEFAULT);
+ FileName = oldfilename;
+ LineNumber = oldlinenumber;
+ e->e_to = oldto;
+ return rval;
+}
+
+static void
+includetimeout()
+{
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ errno = ETIMEDOUT;
+ longjmp(CtxIncludeTimeout, 1);
+}
+/*
+** SENDTOARGV -- send to an argument vector.
+**
+** Parameters:
+** argv -- argument vector to send to.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** puts all addresses on the argument vector onto the
+** send queue.
+*/
+
+void
+sendtoargv(argv, e)
+ register char **argv;
+ register ENVELOPE *e;
+{
+ register char *p;
+
+ while ((p = *argv++) != NULL)
+ (void) sendtolist(p, NULLADDR, &e->e_sendqueue, 0, e);
+}
+/*
+** GETCTLADDR -- get controlling address from an address header.
+**
+** If none, get one corresponding to the effective userid.
+**
+** Parameters:
+** a -- the address to find the controller of.
+**
+** Returns:
+** the controlling address.
+*/
+
+ADDRESS *
+getctladdr(a)
+ register ADDRESS *a;
+{
+ while (a != NULL && !bitset(QGOODUID, a->q_flags))
+ a = a->q_alias;
+ return a;
+}
+/*
+** SELF_REFERENCE -- check to see if an address references itself
+**
+** The check is done through a chain of aliases. If it is part of
+** a loop, break the loop at the "best" address, that is, the one
+** that exists as a real user.
+**
+** This is to handle the case of:
+** awc: Andrew.Chang
+** Andrew.Chang: awc@mail.server
+** which is a problem only on mail.server.
+**
+** Parameters:
+** a -- the address to check.
+**
+** Returns:
+** The address that should be retained.
+*/
+
+static ADDRESS *
+self_reference(a)
+ ADDRESS *a;
+{
+ ADDRESS *b; /* top entry in self ref loop */
+ ADDRESS *c; /* entry that point to a real mail box */
+
+ if (tTd(27, 1))
+ sm_dprintf("self_reference(%s)\n", a->q_paddr);
+
+ for (b = a->q_alias; b != NULL; b = b->q_alias)
+ {
+ if (sameaddr(a, b))
+ break;
+ }
+
+ if (b == NULL)
+ {
+ if (tTd(27, 1))
+ sm_dprintf("\t... no self ref\n");
+ return NULL;
+ }
+
+ /*
+ ** Pick the first address that resolved to a real mail box
+ ** i.e has a mbdb entry. The returned value will be marked
+ ** QSELFREF in recipient(), which in turn will disable alias()
+ ** from marking it as QS_IS_DEAD(), which mean it will be used
+ ** as a deliverable address.
+ **
+ ** The 2 key thing to note here are:
+ ** 1) we are in a recursive call sequence:
+ ** alias->sendtolist->recipient->alias
+ ** 2) normally, when we return back to alias(), the address
+ ** will be marked QS_EXPANDED, since alias() assumes the
+ ** expanded form will be used instead of the current address.
+ ** This behaviour is turned off if the address is marked
+ ** QSELFREF. We set QSELFREF when we return to recipient().
+ */
+
+ c = a;
+ while (c != NULL)
+ {
+ if (tTd(27, 10))
+ sm_dprintf(" %s", c->q_user);
+ if (bitnset(M_HASPWENT, c->q_mailer->m_flags))
+ {
+ SM_MBDB_T user;
+
+ if (tTd(27, 2))
+ sm_dprintf("\t... getpwnam(%s)... ", c->q_user);
+ if (sm_mbdb_lookup(c->q_user, &user) == EX_OK)
+ {
+ if (tTd(27, 2))
+ sm_dprintf("found\n");
+
+ /* ought to cache results here */
+ if (sameaddr(b, c))
+ return b;
+ else
+ return c;
+ }
+ if (tTd(27, 2))
+ sm_dprintf("failed\n");
+ }
+ else
+ {
+ /* if local delivery, compare usernames */
+ if (bitnset(M_LOCALMAILER, c->q_mailer->m_flags) &&
+ b->q_mailer == c->q_mailer)
+ {
+ if (tTd(27, 2))
+ sm_dprintf("\t... local match (%s)\n",
+ c->q_user);
+ if (sameaddr(b, c))
+ return b;
+ else
+ return c;
+ }
+ }
+ if (tTd(27, 10))
+ sm_dprintf("\n");
+ c = c->q_alias;
+ }
+
+ if (tTd(27, 1))
+ sm_dprintf("\t... cannot break loop for \"%s\"\n", a->q_paddr);
+
+ return NULL;
+}
diff --git a/contrib/sendmail/src/sasl.c b/contrib/sendmail/src/sasl.c
new file mode 100644
index 0000000..b172677
--- /dev/null
+++ b/contrib/sendmail/src/sasl.c
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2001-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: sasl.c,v 8.19.2.1 2002/07/13 18:04:56 ca Exp $")
+
+#if SASL
+# include <stdlib.h>
+# include <sendmail.h>
+# include <errno.h>
+
+/*
+** In order to ensure that storage leaks are tracked, and to prevent
+** conflicts between the sm_heap package and sasl, we tell sasl to
+** use the following heap allocation functions. Unfortunately,
+** the sasl package incorrectly specifies the size of a block
+** using unsigned long: for portability, it should be size_t.
+*/
+
+void *sm_sasl_malloc __P((unsigned long));
+static void *sm_sasl_calloc __P((unsigned long, unsigned long));
+static void *sm_sasl_realloc __P((void *, unsigned long));
+void sm_sasl_free __P((void *));
+
+/*
+** SASLv1:
+** We can't use an rpool for Cyrus-SASL memory management routines,
+** since the encryption/decryption routines in Cyrus-SASL
+** allocate/deallocate a buffer each time. Since rpool
+** don't release memory until the very end, memory consumption is
+** proportional to the size of an e-mail, which is unacceptable.
+*/
+
+/*
+** SM_SASL_MALLOC -- malloc() for SASL
+**
+** Parameters:
+** size -- size of requested memory.
+**
+** Returns:
+** pointer to memory.
+*/
+
+void *
+sm_sasl_malloc(size)
+ unsigned long size;
+{
+ return sm_malloc((size_t) size);
+}
+
+/*
+** SM_SASL_CALLOC -- calloc() for SASL
+**
+** Parameters:
+** nelem -- number of elements.
+** elemsize -- size of each element.
+**
+** Returns:
+** pointer to memory.
+**
+** Notice:
+** this isn't currently used by SASL.
+*/
+
+static void *
+sm_sasl_calloc(nelem, elemsize)
+ unsigned long nelem;
+ unsigned long elemsize;
+{
+ size_t size;
+ void *p;
+
+ size = (size_t) nelem * (size_t) elemsize;
+ p = sm_malloc(size);
+ if (p == NULL)
+ return NULL;
+ memset(p, '\0', size);
+ return p;
+}
+
+/*
+** SM_SASL_REALLOC -- realloc() for SASL
+**
+** Parameters:
+** p -- pointer to old memory.
+** size -- size of requested memory.
+**
+** Returns:
+** pointer to new memory.
+*/
+
+static void *
+sm_sasl_realloc(o, size)
+ void *o;
+ unsigned long size;
+{
+ return sm_realloc(o, (size_t) size);
+}
+
+/*
+** SM_SASL_FREE -- free() for SASL
+**
+** Parameters:
+** p -- pointer to free.
+**
+** Returns:
+** none
+*/
+
+void
+sm_sasl_free(p)
+ void *p;
+{
+ sm_free(p);
+}
+
+/*
+** SM_SASL_INIT -- sendmail specific SASL initialization
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** installs memory management routines for SASL.
+*/
+
+void
+sm_sasl_init()
+{
+ sasl_set_alloc(sm_sasl_malloc, sm_sasl_calloc,
+ sm_sasl_realloc, sm_sasl_free);
+}
+/*
+** INTERSECT -- create the intersection between two lists
+**
+** Parameters:
+** s1, s2 -- lists of items (separated by single blanks).
+** rpool -- resource pool from which result is allocated.
+**
+** Returns:
+** the intersection of both lists.
+*/
+
+char *
+intersect(s1, s2, rpool)
+ char *s1, *s2;
+ SM_RPOOL_T *rpool;
+{
+ char *hr, *h1, *h, *res;
+ int l1, l2, rl;
+
+ if (s1 == NULL || s2 == NULL) /* NULL string(s) -> NULL result */
+ return NULL;
+ l1 = strlen(s1);
+ l2 = strlen(s2);
+ rl = SM_MIN(l1, l2);
+ res = (char *) sm_rpool_malloc(rpool, rl + 1);
+ if (res == NULL)
+ return NULL;
+ *res = '\0';
+ if (rl == 0) /* at least one string empty? */
+ return res;
+ hr = res;
+ h1 = s1;
+ h = s1;
+
+ /* walk through s1 */
+ while (h != NULL && *h1 != '\0')
+ {
+ /* is there something after the current word? */
+ if ((h = strchr(h1, ' ')) != NULL)
+ *h = '\0';
+ l1 = strlen(h1);
+
+ /* does the current word appear in s2 ? */
+ if (iteminlist(h1, s2, " ") != NULL)
+ {
+ /* add a blank if not first item */
+ if (hr != res)
+ *hr++ = ' ';
+
+ /* copy the item */
+ memcpy(hr, h1, l1);
+
+ /* advance pointer in result list */
+ hr += l1;
+ *hr = '\0';
+ }
+ if (h != NULL)
+ {
+ /* there are more items */
+ *h = ' ';
+ h1 = h + 1;
+ }
+ }
+ return res;
+}
+# if SASL >= 20000
+/*
+** IPTOSTRING -- create string for SASL_IP*PORT property
+** (borrowed from lib/iptostring.c in Cyrus-IMAP)
+**
+** Parameters:
+** addr -- (pointer to) socket address
+** addrlen -- length of socket address
+** out -- output string (result)
+** outlen -- maximum length of output string
+**
+** Returns:
+** true iff successful.
+**
+** Side Effects:
+** creates output string if successful.
+** sets errno if unsuccessful.
+*/
+
+# include <arpa/inet.h>
+
+# ifndef NI_WITHSCOPEID
+# define NI_WITHSCOPEID 0
+# endif
+# ifndef NI_MAXHOST
+# define NI_MAXHOST 1025
+# endif
+# ifndef NI_MAXSERV
+# define NI_MAXSERV 32
+# endif
+
+bool
+iptostring(addr, addrlen, out, outlen)
+ SOCKADDR *addr;
+ SOCKADDR_LEN_T addrlen;
+ char *out;
+ unsigned outlen;
+{
+ char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
+
+ if (addr == NULL || out == NULL)
+ {
+ errno = EINVAL;
+ return false;
+ }
+
+# if NETINET6
+ if (getnameinfo((struct sockaddr *) addr, addrlen,
+ hbuf, sizeof hbuf, pbuf, sizeof pbuf,
+ NI_NUMERICHOST | NI_WITHSCOPEID | NI_NUMERICSERV) != 0)
+ return false;
+# else /* NETINET6 */
+ if (addr->sa.sa_family != AF_INET)
+ {
+ errno = EINVAL;
+ return false;
+ }
+ if (sm_strlcpy(hbuf, inet_ntoa(addr->sin.sin_addr), sizeof(hbuf))
+ >= sizeof(hbuf))
+ {
+ errno = ENOMEM;
+ return false;
+ }
+ sm_snprintf(pbuf, sizeof pbuf, "%d", ntohs(addr->sin.sin_port));
+# endif /* NETINET6 */
+
+ if (outlen < strlen(hbuf) + strlen(pbuf) + 2)
+ {
+ errno = ENOMEM;
+ return false;
+ }
+ sm_snprintf(out, outlen, "%s;%s", hbuf, pbuf);
+ return true;
+}
+# endif /* SASL >= 20000 */
+#endif /* SASL */
diff --git a/contrib/sendmail/src/savemail.c b/contrib/sendmail/src/savemail.c
new file mode 100644
index 0000000..61e6bb8
--- /dev/null
+++ b/contrib/sendmail/src/savemail.c
@@ -0,0 +1,1702 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: savemail.c,v 8.299 2002/05/24 20:50:17 gshapiro Exp $")
+
+static void errbody __P((MCI *, ENVELOPE *, char *));
+static bool pruneroute __P((char *));
+
+/*
+** SAVEMAIL -- Save mail on error
+**
+** If mailing back errors, mail it back to the originator
+** together with an error message; otherwise, just put it in
+** dead.letter in the user's home directory (if he exists on
+** this machine).
+**
+** Parameters:
+** e -- the envelope containing the message in error.
+** sendbody -- if true, also send back the body of the
+** message; otherwise just send the header.
+**
+** Returns:
+** true if savemail panic'ed, (i.e., the data file should
+** be preserved by dropenvelope())
+**
+** Side Effects:
+** Saves the letter, by writing or mailing it back to the
+** sender, or by putting it in dead.letter in her home
+** directory.
+*/
+
+/* defines for state machine */
+#define ESM_REPORT 0 /* report to sender's terminal */
+#define ESM_MAIL 1 /* mail back to sender */
+#define ESM_QUIET 2 /* mail has already been returned */
+#define ESM_DEADLETTER 3 /* save in ~/dead.letter */
+#define ESM_POSTMASTER 4 /* return to postmaster */
+#define ESM_DEADLETTERDROP 5 /* save in DeadLetterDrop */
+#define ESM_PANIC 6 /* call loseqfile() */
+#define ESM_DONE 7 /* message is successfully delivered */
+
+bool
+savemail(e, sendbody)
+ register ENVELOPE *e;
+ bool sendbody;
+{
+ register SM_FILE_T *fp;
+ bool panic = false;
+ int state;
+ auto ADDRESS *q = NULL;
+ register char *p;
+ MCI mcibuf;
+ int flags;
+ long sff;
+ char buf[MAXLINE + 1];
+ char dlbuf[MAXPATHLEN];
+ SM_MBDB_T user;
+
+
+ if (tTd(6, 1))
+ {
+ sm_dprintf("\nsavemail, errormode = %c, id = %s, ExitStat = %d\n e_from=",
+ e->e_errormode, e->e_id == NULL ? "NONE" : e->e_id,
+ ExitStat);
+ printaddr(&e->e_from, false);
+ }
+
+ if (e->e_id == NULL)
+ {
+ /* can't return a message with no id */
+ return panic;
+ }
+
+ /*
+ ** In the unhappy event we don't know who to return the mail
+ ** to, make someone up.
+ */
+
+ if (e->e_from.q_paddr == NULL)
+ {
+ e->e_sender = "Postmaster";
+ if (parseaddr(e->e_sender, &e->e_from,
+ RF_COPYPARSE|RF_SENDERADDR,
+ '\0', NULL, e, false) == NULL)
+ {
+ syserr("553 5.3.5 Cannot parse Postmaster!");
+ finis(true, true, EX_SOFTWARE);
+ }
+ }
+ e->e_to = NULL;
+
+ /*
+ ** Basic state machine.
+ **
+ ** This machine runs through the following states:
+ **
+ ** ESM_QUIET Errors have already been printed iff the
+ ** sender is local.
+ ** ESM_REPORT Report directly to the sender's terminal.
+ ** ESM_MAIL Mail response to the sender.
+ ** ESM_DEADLETTER Save response in ~/dead.letter.
+ ** ESM_POSTMASTER Mail response to the postmaster.
+ ** ESM_DEADLETTERDROP
+ ** If DeadLetterDrop set, save it there.
+ ** ESM_PANIC Save response anywhere possible.
+ */
+
+ /* determine starting state */
+ switch (e->e_errormode)
+ {
+ case EM_WRITE:
+ state = ESM_REPORT;
+ break;
+
+ case EM_BERKNET:
+ case EM_MAIL:
+ state = ESM_MAIL;
+ break;
+
+ case EM_PRINT:
+ case '\0':
+ state = ESM_QUIET;
+ break;
+
+ case EM_QUIET:
+ /* no need to return anything at all */
+ return panic;
+
+ default:
+ syserr("554 5.3.0 savemail: bogus errormode x%x",
+ e->e_errormode);
+ state = ESM_MAIL;
+ break;
+ }
+
+ /* if this is already an error response, send to postmaster */
+ if (bitset(EF_RESPONSE, e->e_flags))
+ {
+ if (e->e_parent != NULL &&
+ bitset(EF_RESPONSE, e->e_parent->e_flags))
+ {
+ /* got an error sending a response -- can it */
+ return panic;
+ }
+ state = ESM_POSTMASTER;
+ }
+
+ while (state != ESM_DONE)
+ {
+ if (tTd(6, 5))
+ sm_dprintf(" state %d\n", state);
+
+ switch (state)
+ {
+ case ESM_QUIET:
+ if (bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags))
+ state = ESM_DEADLETTER;
+ else
+ state = ESM_MAIL;
+ break;
+
+ case ESM_REPORT:
+
+ /*
+ ** If the user is still logged in on the same terminal,
+ ** then write the error messages back to hir (sic).
+ */
+
+ p = ttypath();
+ if (p == NULL || sm_io_reopen(SmFtStdio,
+ SM_TIME_DEFAULT,
+ p, SM_IO_WRONLY, NULL,
+ smioout) == NULL)
+ {
+ state = ESM_MAIL;
+ break;
+ }
+
+ expand("\201n", buf, sizeof buf, e);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\r\nMessage from %s...\r\n", buf);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Errors occurred while sending mail.\r\n");
+ if (e->e_xfp != NULL)
+ {
+ (void) bfrewind(e->e_xfp);
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Transcript follows:\r\n");
+ while (sm_io_fgets(e->e_xfp, SM_TIME_DEFAULT,
+ buf, sizeof buf) != NULL &&
+ !sm_io_error(smioout))
+ (void) sm_io_fputs(smioout,
+ SM_TIME_DEFAULT,
+ buf);
+ }
+ else
+ {
+ syserr("Cannot open %s",
+ queuename(e, XSCRPT_LETTER));
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Transcript of session is unavailable.\r\n");
+ }
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Original message will be saved in dead.letter.\r\n");
+ state = ESM_DEADLETTER;
+ break;
+
+ case ESM_MAIL:
+ /*
+ ** If mailing back, do it.
+ ** Throw away all further output. Don't alias,
+ ** since this could cause loops, e.g., if joe
+ ** mails to joe@x, and for some reason the network
+ ** for @x is down, then the response gets sent to
+ ** joe@x, which gives a response, etc. Also force
+ ** the mail to be delivered even if a version of
+ ** it has already been sent to the sender.
+ **
+ ** If this is a configuration or local software
+ ** error, send to the local postmaster as well,
+ ** since the originator can't do anything
+ ** about it anyway. Note that this is a full
+ ** copy of the message (intentionally) so that
+ ** the Postmaster can forward things along.
+ */
+
+ if (ExitStat == EX_CONFIG || ExitStat == EX_SOFTWARE)
+ {
+ (void) sendtolist("postmaster", NULLADDR,
+ &e->e_errorqueue, 0, e);
+ }
+ if (!emptyaddr(&e->e_from))
+ {
+ char from[TOBUFSIZE];
+
+ if (sm_strlcpy(from, e->e_from.q_paddr,
+ sizeof from) >= sizeof from)
+ {
+ state = ESM_POSTMASTER;
+ break;
+ }
+
+ if (!DontPruneRoutes)
+ (void) pruneroute(from);
+
+ (void) sendtolist(from, NULLADDR,
+ &e->e_errorqueue, 0, e);
+ }
+
+ /*
+ ** Deliver a non-delivery report to the
+ ** Postmaster-designate (not necessarily
+ ** Postmaster). This does not include the
+ ** body of the message, for privacy reasons.
+ ** You really shouldn't need this.
+ */
+
+ e->e_flags |= EF_PM_NOTIFY;
+
+ /* check to see if there are any good addresses */
+ for (q = e->e_errorqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_SENDABLE(q->q_state))
+ break;
+ }
+ if (q == NULL)
+ {
+ /* this is an error-error */
+ state = ESM_POSTMASTER;
+ break;
+ }
+ if (returntosender(e->e_message, e->e_errorqueue,
+ sendbody ? RTSF_SEND_BODY
+ : RTSF_NO_BODY,
+ e) == 0)
+ {
+ state = ESM_DONE;
+ break;
+ }
+
+ /* didn't work -- return to postmaster */
+ state = ESM_POSTMASTER;
+ break;
+
+ case ESM_POSTMASTER:
+ /*
+ ** Similar to previous case, but to system postmaster.
+ */
+
+ q = NULL;
+ expand(DoubleBounceAddr, buf, sizeof buf, e);
+
+ /*
+ ** Just drop it on the floor if DoubleBounceAddr
+ ** expands to an empty string.
+ */
+
+ if (*buf == '\0')
+ {
+ state = ESM_DONE;
+ break;
+ }
+ if (sendtolist(buf, NULLADDR, &q, 0, e) <= 0)
+ {
+ syserr("553 5.3.0 cannot parse %s!", buf);
+ ExitStat = EX_SOFTWARE;
+ state = ESM_DEADLETTERDROP;
+ break;
+ }
+ flags = RTSF_PM_BOUNCE;
+ if (sendbody)
+ flags |= RTSF_SEND_BODY;
+ if (returntosender(e->e_message, q, flags, e) == 0)
+ {
+ state = ESM_DONE;
+ break;
+ }
+
+ /* didn't work -- last resort */
+ state = ESM_DEADLETTERDROP;
+ break;
+
+ case ESM_DEADLETTER:
+ /*
+ ** Save the message in dead.letter.
+ ** If we weren't mailing back, and the user is
+ ** local, we should save the message in
+ ** ~/dead.letter so that the poor person doesn't
+ ** have to type it over again -- and we all know
+ ** what poor typists UNIX users are.
+ */
+
+ p = NULL;
+ if (bitnset(M_HASPWENT, e->e_from.q_mailer->m_flags))
+ {
+ if (e->e_from.q_home != NULL)
+ p = e->e_from.q_home;
+ else if (sm_mbdb_lookup(e->e_from.q_user, &user)
+ == EX_OK &&
+ *user.mbdb_homedir != '\0')
+ p = user.mbdb_homedir;
+ }
+ if (p == NULL || e->e_dfp == NULL)
+ {
+ /* no local directory or no data file */
+ state = ESM_MAIL;
+ break;
+ }
+
+ /* we have a home directory; write dead.letter */
+ macdefine(&e->e_macro, A_TEMP, 'z', p);
+
+ /* get the sender for the UnixFromLine */
+ p = macvalue('g', e);
+ macdefine(&e->e_macro, A_PERM, 'g', e->e_sender);
+
+ expand("\201z/dead.letter", dlbuf, sizeof dlbuf, e);
+ sff = SFF_CREAT|SFF_REGONLY|SFF_RUNASREALUID;
+ if (RealUid == 0)
+ sff |= SFF_ROOTOK;
+ e->e_to = dlbuf;
+ if (writable(dlbuf, NULL, sff) &&
+ mailfile(dlbuf, FileMailer, NULL, sff, e) == EX_OK)
+ {
+ int oldverb = Verbose;
+
+ if (OpMode != MD_DAEMON && OpMode != MD_SMTP)
+ Verbose = 1;
+ if (Verbose > 0)
+ message("Saved message in %s", dlbuf);
+ Verbose = oldverb;
+ macdefine(&e->e_macro, A_PERM, 'g', p);
+ state = ESM_DONE;
+ break;
+ }
+ macdefine(&e->e_macro, A_PERM, 'g', p);
+ state = ESM_MAIL;
+ break;
+
+ case ESM_DEADLETTERDROP:
+ /*
+ ** Log the mail in DeadLetterDrop file.
+ */
+
+ if (e->e_class < 0)
+ {
+ state = ESM_DONE;
+ break;
+ }
+
+ if ((SafeFileEnv != NULL && SafeFileEnv[0] != '\0') ||
+ DeadLetterDrop == NULL ||
+ DeadLetterDrop[0] == '\0')
+ {
+ state = ESM_PANIC;
+ break;
+ }
+
+ sff = SFF_CREAT|SFF_REGONLY|SFF_ROOTOK|SFF_OPENASROOT|SFF_MUSTOWN;
+ if (!writable(DeadLetterDrop, NULL, sff) ||
+ (fp = safefopen(DeadLetterDrop, O_WRONLY|O_APPEND,
+ FileMode, sff)) == NULL)
+ {
+ state = ESM_PANIC;
+ break;
+ }
+
+ memset(&mcibuf, '\0', sizeof mcibuf);
+ mcibuf.mci_out = fp;
+ mcibuf.mci_mailer = FileMailer;
+ if (bitnset(M_7BITS, FileMailer->m_flags))
+ mcibuf.mci_flags |= MCIF_7BIT;
+
+ /* get the sender for the UnixFromLine */
+ p = macvalue('g', e);
+ macdefine(&e->e_macro, A_PERM, 'g', e->e_sender);
+
+ putfromline(&mcibuf, e);
+ (*e->e_puthdr)(&mcibuf, e->e_header, e, M87F_OUTER);
+ (*e->e_putbody)(&mcibuf, e, NULL);
+ putline("\n", &mcibuf); /* XXX EOL from FileMailer? */
+ (void) sm_io_flush(fp, SM_TIME_DEFAULT);
+ if (sm_io_error(fp) ||
+ sm_io_close(fp, SM_TIME_DEFAULT) < 0)
+ state = ESM_PANIC;
+ else
+ {
+ int oldverb = Verbose;
+
+ if (OpMode != MD_DAEMON && OpMode != MD_SMTP)
+ Verbose = 1;
+ if (Verbose > 0)
+ message("Saved message in %s",
+ DeadLetterDrop);
+ Verbose = oldverb;
+ if (LogLevel > 3)
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "Saved message in %s",
+ DeadLetterDrop);
+ state = ESM_DONE;
+ }
+ macdefine(&e->e_macro, A_PERM, 'g', p);
+ break;
+
+ default:
+ syserr("554 5.3.5 savemail: unknown state %d", state);
+ /* FALLTHROUGH */
+
+ case ESM_PANIC:
+ /* leave the locked queue & transcript files around */
+ loseqfile(e, "savemail panic");
+ panic = true;
+ errno = 0;
+ syserr("554 savemail: cannot save rejected email anywhere");
+ state = ESM_DONE;
+ break;
+ }
+ }
+ return panic;
+}
+/*
+** RETURNTOSENDER -- return a message to the sender with an error.
+**
+** Parameters:
+** msg -- the explanatory message.
+** returnq -- the queue of people to send the message to.
+** flags -- flags tweaking the operation:
+** RTSF_SENDBODY -- include body of message (otherwise
+** just send the header).
+** RTSF_PMBOUNCE -- this is a postmaster bounce.
+** e -- the current envelope.
+**
+** Returns:
+** zero -- if everything went ok.
+** else -- some error.
+**
+** Side Effects:
+** Returns the current message to the sender via mail.
+*/
+
+#define MAXRETURNS 6 /* max depth of returning messages */
+#define ERRORFUDGE 1024 /* nominal size of error message text */
+
+int
+returntosender(msg, returnq, flags, e)
+ char *msg;
+ ADDRESS *returnq;
+ int flags;
+ register ENVELOPE *e;
+{
+ register ENVELOPE *ee;
+ ENVELOPE *oldcur = CurEnv;
+ ENVELOPE errenvelope;
+ static int returndepth = 0;
+ register ADDRESS *q;
+ char *p;
+ char buf[MAXNAME + 1];
+
+ if (returnq == NULL)
+ return -1;
+
+ if (msg == NULL)
+ msg = "Unable to deliver mail";
+
+ if (tTd(6, 1))
+ {
+ sm_dprintf("\n*** Return To Sender: msg=\"%s\", depth=%d, e=%p, returnq=",
+ msg, returndepth, e);
+ printaddr(returnq, true);
+ if (tTd(6, 20))
+ {
+ sm_dprintf("Sendq=");
+ printaddr(e->e_sendqueue, true);
+ }
+ }
+
+ if (++returndepth >= MAXRETURNS)
+ {
+ if (returndepth != MAXRETURNS)
+ syserr("554 5.3.0 returntosender: infinite recursion on %s",
+ returnq->q_paddr);
+ /* don't "unrecurse" and fake a clean exit */
+ /* returndepth--; */
+ return 0;
+ }
+
+ macdefine(&e->e_macro, A_PERM, 'g', e->e_sender);
+ macdefine(&e->e_macro, A_PERM, 'u', NULL);
+
+ /* initialize error envelope */
+ ee = newenvelope(&errenvelope, e, sm_rpool_new_x(NULL));
+ macdefine(&ee->e_macro, A_PERM, 'a', "\201b");
+ macdefine(&ee->e_macro, A_PERM, 'r', "");
+ macdefine(&ee->e_macro, A_PERM, 's', "localhost");
+ macdefine(&ee->e_macro, A_PERM, '_', "localhost");
+#if SASL
+ macdefine(&ee->e_macro, A_PERM, macid("{auth_type}"), "");
+ macdefine(&ee->e_macro, A_PERM, macid("{auth_authen}"), "");
+ macdefine(&ee->e_macro, A_PERM, macid("{auth_author}"), "");
+ macdefine(&ee->e_macro, A_PERM, macid("{auth_ssf}"), "");
+#endif /* SASL */
+#if STARTTLS
+ macdefine(&ee->e_macro, A_PERM, macid("{cert_issuer}"), "");
+ macdefine(&ee->e_macro, A_PERM, macid("{cert_subject}"), "");
+ macdefine(&ee->e_macro, A_PERM, macid("{cipher_bits}"), "");
+ macdefine(&ee->e_macro, A_PERM, macid("{cipher}"), "");
+ macdefine(&ee->e_macro, A_PERM, macid("{tls_version}"), "");
+ macdefine(&ee->e_macro, A_PERM, macid("{verify}"), "");
+# if _FFR_TLS_1
+ macdefine(&ee->e_macro, A_PERM, macid("{alg_bits}"), "");
+ macdefine(&ee->e_macro, A_PERM, macid("{cn_issuer}"), "");
+ macdefine(&ee->e_macro, A_PERM, macid("{cn_subject}"), "");
+# endif /* _FFR_TLS_1 */
+#endif /* STARTTLS */
+
+ ee->e_puthdr = putheader;
+ ee->e_putbody = errbody;
+ ee->e_flags |= EF_RESPONSE|EF_METOO;
+ if (!bitset(EF_OLDSTYLE, e->e_flags))
+ ee->e_flags &= ~EF_OLDSTYLE;
+ if (bitset(EF_DONT_MIME, e->e_flags))
+ {
+ ee->e_flags |= EF_DONT_MIME;
+
+ /*
+ ** If we can't convert to MIME and we don't pass
+ ** 8-bit, we can't send the body.
+ */
+
+ if (bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(MM_PASS8BIT, MimeMode))
+ flags &= ~RTSF_SEND_BODY;
+ }
+
+ ee->e_sendqueue = returnq;
+ ee->e_msgsize = 0;
+ if (bitset(RTSF_SEND_BODY, flags) &&
+ !bitset(PRIV_NOBODYRETN, PrivacyFlags))
+ ee->e_msgsize = ERRORFUDGE + e->e_msgsize;
+ else
+ ee->e_flags |= EF_NO_BODY_RETN;
+
+ if (!setnewqueue(ee))
+ {
+ syserr("554 5.3.0 returntosender: cannot select queue for %s",
+ returnq->q_paddr);
+ ExitStat = EX_UNAVAILABLE;
+ returndepth--;
+ return -1;
+ }
+ initsys(ee);
+
+#if NAMED_BIND
+ _res.retry = TimeOuts.res_retry[RES_TO_FIRST];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
+#endif /* NAMED_BIND */
+ for (q = returnq; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_BADADDR(q->q_state))
+ continue;
+
+ q->q_flags &= ~(QHASNOTIFY|Q_PINGFLAGS);
+ q->q_flags |= QPINGONFAILURE;
+
+ if (!QS_IS_DEAD(q->q_state))
+ ee->e_nrcpts++;
+
+ if (q->q_alias == NULL)
+ addheader("To", q->q_paddr, 0, ee);
+ }
+
+ if (LogLevel > 5)
+ {
+ if (bitset(EF_RESPONSE, e->e_flags))
+ p = "return to sender";
+ else if (bitset(EF_WARNING, e->e_flags))
+ p = "sender notify";
+ else if (bitset(RTSF_PM_BOUNCE, flags))
+ p = "postmaster notify";
+ else
+ p = "DSN";
+ sm_syslog(LOG_INFO, e->e_id, "%s: %s: %s",
+ ee->e_id, p, shortenstring(msg, MAXSHORTSTR));
+ }
+
+ if (SendMIMEErrors)
+ {
+ addheader("MIME-Version", "1.0", 0, ee);
+ (void) sm_snprintf(buf, sizeof buf, "%s.%ld/%.100s",
+ ee->e_id, (long)curtime(), MyHostName);
+ ee->e_msgboundary = sm_rpool_strdup_x(ee->e_rpool, buf);
+ (void) sm_snprintf(buf, sizeof buf,
+#if DSN
+ "multipart/report; report-type=delivery-status;\n\tboundary=\"%s\"",
+#else /* DSN */
+ "multipart/mixed; boundary=\"%s\"",
+#endif /* DSN */
+ ee->e_msgboundary);
+ addheader("Content-Type", buf, 0, ee);
+
+ p = hvalue("Content-Transfer-Encoding", e->e_header);
+ if (p != NULL && sm_strcasecmp(p, "binary") != 0)
+ p = NULL;
+ if (p == NULL && bitset(EF_HAS8BIT, e->e_flags))
+ p = "8bit";
+ if (p != NULL)
+ addheader("Content-Transfer-Encoding", p, 0, ee);
+ }
+ if (strncmp(msg, "Warning:", 8) == 0)
+ {
+ addheader("Subject", msg, 0, ee);
+ p = "warning-timeout";
+ }
+ else if (strncmp(msg, "Postmaster warning:", 19) == 0)
+ {
+ addheader("Subject", msg, 0, ee);
+ p = "postmaster-warning";
+ }
+ else if (strcmp(msg, "Return receipt") == 0)
+ {
+ addheader("Subject", msg, 0, ee);
+ p = "return-receipt";
+ }
+ else if (bitset(RTSF_PM_BOUNCE, flags))
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Postmaster notify: see transcript for details");
+ addheader("Subject", buf, 0, ee);
+ p = "postmaster-notification";
+ }
+ else
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Returned mail: see transcript for details");
+ addheader("Subject", buf, 0, ee);
+ p = "failure";
+ }
+ (void) sm_snprintf(buf, sizeof buf, "auto-generated (%s)", p);
+ addheader("Auto-Submitted", buf, 0, ee);
+
+ /* fake up an address header for the from person */
+ expand("\201n", buf, sizeof buf, e);
+ if (parseaddr(buf, &ee->e_from,
+ RF_COPYALL|RF_SENDERADDR, '\0', NULL, e, false) == NULL)
+ {
+ syserr("553 5.3.5 Can't parse myself!");
+ ExitStat = EX_SOFTWARE;
+ returndepth--;
+ return -1;
+ }
+ ee->e_from.q_flags &= ~(QHASNOTIFY|Q_PINGFLAGS);
+ ee->e_from.q_flags |= QPINGONFAILURE;
+ ee->e_sender = ee->e_from.q_paddr;
+
+ /* push state into submessage */
+ CurEnv = ee;
+ macdefine(&ee->e_macro, A_PERM, 'f', "\201n");
+ macdefine(&ee->e_macro, A_PERM, 'x', "Mail Delivery Subsystem");
+ eatheader(ee, true, true);
+
+ /* mark statistics */
+ markstats(ee, NULLADDR, STATS_NORMAL);
+
+ /* actually deliver the error message */
+ sendall(ee, SM_DELIVER);
+
+ /* restore state */
+ dropenvelope(ee, true, false);
+ sm_rpool_free(ee->e_rpool);
+ CurEnv = oldcur;
+ returndepth--;
+
+ /* check for delivery errors */
+ if (ee->e_parent == NULL ||
+ !bitset(EF_RESPONSE, ee->e_parent->e_flags))
+ return 0;
+ for (q = ee->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_ATTEMPTED(q->q_state))
+ return 0;
+ }
+ return -1;
+}
+/*
+** ERRBODY -- output the body of an error message.
+**
+** Typically this is a copy of the transcript plus a copy of the
+** original offending message.
+**
+** Parameters:
+** mci -- the mailer connection information.
+** e -- the envelope we are working in.
+** separator -- any possible MIME separator (unused).
+**
+** Returns:
+** none
+**
+** Side Effects:
+** Outputs the body of an error message.
+*/
+
+/* ARGSUSED2 */
+static void
+errbody(mci, e, separator)
+ register MCI *mci;
+ register ENVELOPE *e;
+ char *separator;
+{
+ bool printheader;
+ bool sendbody;
+ bool pm_notify;
+ int save_errno;
+ register SM_FILE_T *xfile;
+ char *p;
+ register ADDRESS *q = NULL;
+ char actual[MAXLINE];
+ char buf[MAXLINE];
+
+ if (bitset(MCIF_INHEADER, mci->mci_flags))
+ {
+ putline("", mci);
+ mci->mci_flags &= ~MCIF_INHEADER;
+ }
+ if (e->e_parent == NULL)
+ {
+ syserr("errbody: null parent");
+ putline(" ----- Original message lost -----\n", mci);
+ return;
+ }
+
+ /*
+ ** Output MIME header.
+ */
+
+ if (e->e_msgboundary != NULL)
+ {
+ putline("This is a MIME-encapsulated message", mci);
+ putline("", mci);
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "--", e->e_msgboundary);
+ putline(buf, mci);
+ putline("", mci);
+ }
+
+ /*
+ ** Output introductory information.
+ */
+
+ pm_notify = false;
+ p = hvalue("subject", e->e_header);
+ if (p != NULL && strncmp(p, "Postmaster ", 11) == 0)
+ pm_notify = true;
+ else
+ {
+ for (q = e->e_parent->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_BADADDR(q->q_state))
+ break;
+ }
+ }
+ if (!pm_notify && q == NULL &&
+ !bitset(EF_FATALERRS|EF_SENDRECEIPT, e->e_parent->e_flags))
+ {
+ putline(" **********************************************",
+ mci);
+ putline(" ** THIS IS A WARNING MESSAGE ONLY **",
+ mci);
+ putline(" ** YOU DO NOT NEED TO RESEND YOUR MESSAGE **",
+ mci);
+ putline(" **********************************************",
+ mci);
+ putline("", mci);
+ }
+ (void) sm_snprintf(buf, sizeof buf,
+ "The original message was received at %s",
+ arpadate(ctime(&e->e_parent->e_ctime)));
+ putline(buf, mci);
+ expand("from \201_", buf, sizeof buf, e->e_parent);
+ putline(buf, mci);
+
+ /* include id in postmaster copies */
+ if (pm_notify && e->e_parent->e_id != NULL)
+ {
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "with id ",
+ e->e_parent->e_id);
+ putline(buf, mci);
+ }
+ putline("", mci);
+
+ /*
+ ** Output error message header (if specified and available).
+ */
+
+ if (ErrMsgFile != NULL &&
+ !bitset(EF_SENDRECEIPT, e->e_parent->e_flags))
+ {
+ if (*ErrMsgFile == '/')
+ {
+ long sff = SFF_ROOTOK|SFF_REGONLY;
+
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ if (!bitnset(DBS_ERRORHEADERINUNSAFEDIRPATH,
+ DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+ xfile = safefopen(ErrMsgFile, O_RDONLY, 0444, sff);
+ if (xfile != NULL)
+ {
+ while (sm_io_fgets(xfile, SM_TIME_DEFAULT, buf,
+ sizeof buf) != NULL)
+ {
+ translate_dollars(buf);
+ expand(buf, buf, sizeof buf, e);
+ putline(buf, mci);
+ }
+ (void) sm_io_close(xfile, SM_TIME_DEFAULT);
+ putline("\n", mci);
+ }
+ }
+ else
+ {
+ expand(ErrMsgFile, buf, sizeof buf, e);
+ putline(buf, mci);
+ putline("", mci);
+ }
+ }
+
+ /*
+ ** Output message introduction
+ */
+
+ /* permanent fatal errors */
+ printheader = true;
+ for (q = e->e_parent->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (!QS_IS_BADADDR(q->q_state) ||
+ !bitset(QPINGONFAILURE, q->q_flags))
+ continue;
+
+ if (printheader)
+ {
+ putline(" ----- The following addresses had permanent fatal errors -----",
+ mci);
+ printheader = false;
+ }
+
+ (void) sm_strlcpy(buf, shortenstring(q->q_paddr, MAXSHORTSTR),
+ sizeof buf);
+ putline(buf, mci);
+ if (q->q_rstatus != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ " (reason: %s)",
+ shortenstring(exitstat(q->q_rstatus),
+ MAXSHORTSTR));
+ putline(buf, mci);
+ }
+ if (q->q_alias != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ " (expanded from: %s)",
+ shortenstring(q->q_alias->q_paddr,
+ MAXSHORTSTR));
+ putline(buf, mci);
+ }
+ }
+ if (!printheader)
+ putline("", mci);
+
+ /* transient non-fatal errors */
+ printheader = true;
+ for (q = e->e_parent->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_BADADDR(q->q_state) ||
+ !bitset(QPRIMARY, q->q_flags) ||
+ !bitset(QBYNDELAY, q->q_flags) ||
+ !bitset(QDELAYED, q->q_flags))
+ continue;
+
+ if (printheader)
+ {
+ putline(" ----- The following addresses had transient non-fatal errors -----",
+ mci);
+ printheader = false;
+ }
+
+ (void) sm_strlcpy(buf, shortenstring(q->q_paddr, MAXSHORTSTR),
+ sizeof buf);
+ putline(buf, mci);
+ if (q->q_alias != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ " (expanded from: %s)",
+ shortenstring(q->q_alias->q_paddr,
+ MAXSHORTSTR));
+ putline(buf, mci);
+ }
+ }
+ if (!printheader)
+ putline("", mci);
+
+ /* successful delivery notifications */
+ printheader = true;
+ for (q = e->e_parent->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_BADADDR(q->q_state) ||
+ !bitset(QPRIMARY, q->q_flags) ||
+ bitset(QBYNDELAY, q->q_flags) ||
+ bitset(QDELAYED, q->q_flags))
+ continue;
+ else if (bitset(QBYNRELAY, q->q_flags))
+ p = "Deliver-By notify: relayed";
+ else if (bitset(QBYTRACE, q->q_flags))
+ p = "Deliver-By trace: relayed";
+ else if (!bitset(QPINGONSUCCESS, q->q_flags))
+ continue;
+ else if (bitset(QRELAYED, q->q_flags))
+ p = "relayed to non-DSN-aware mailer";
+ else if (bitset(QDELIVERED, q->q_flags))
+ {
+ if (bitset(QEXPANDED, q->q_flags))
+ p = "successfully delivered to mailing list";
+ else
+ p = "successfully delivered to mailbox";
+ }
+ else if (bitset(QEXPANDED, q->q_flags))
+ p = "expanded by alias";
+ else
+ continue;
+
+ if (printheader)
+ {
+ putline(" ----- The following addresses had successful delivery notifications -----",
+ mci);
+ printheader = false;
+ }
+
+ (void) sm_snprintf(buf, sizeof buf, "%s (%s)",
+ shortenstring(q->q_paddr, MAXSHORTSTR), p);
+ putline(buf, mci);
+ if (q->q_alias != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ " (expanded from: %s)",
+ shortenstring(q->q_alias->q_paddr,
+ MAXSHORTSTR));
+ putline(buf, mci);
+ }
+ }
+ if (!printheader)
+ putline("", mci);
+
+ /*
+ ** Output transcript of errors
+ */
+
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+ if (e->e_parent->e_xfp == NULL)
+ {
+ putline(" ----- Transcript of session is unavailable -----\n",
+ mci);
+ }
+ else
+ {
+ printheader = true;
+ (void) bfrewind(e->e_parent->e_xfp);
+ if (e->e_xfp != NULL)
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+ while (sm_io_fgets(e->e_parent->e_xfp, SM_TIME_DEFAULT, buf,
+ sizeof buf) != NULL)
+ {
+ if (printheader)
+ putline(" ----- Transcript of session follows -----\n",
+ mci);
+ printheader = false;
+ putline(buf, mci);
+ }
+ }
+ errno = 0;
+
+#if DSN
+ /*
+ ** Output machine-readable version.
+ */
+
+ if (e->e_msgboundary != NULL)
+ {
+ putline("", mci);
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "--", e->e_msgboundary);
+ putline(buf, mci);
+ putline("Content-Type: message/delivery-status", mci);
+ putline("", mci);
+
+ /*
+ ** Output per-message information.
+ */
+
+ /* original envelope id from MAIL FROM: line */
+ if (e->e_parent->e_envid != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Original-Envelope-Id: %.800s",
+ xuntextify(e->e_parent->e_envid));
+ putline(buf, mci);
+ }
+
+ /* Reporting-MTA: is us (required) */
+ (void) sm_snprintf(buf, sizeof buf,
+ "Reporting-MTA: dns; %.800s", MyHostName);
+ putline(buf, mci);
+
+ /* DSN-Gateway: not relevant since we are not translating */
+
+ /* Received-From-MTA: shows where we got this message from */
+ if (RealHostName != NULL)
+ {
+ /* XXX use $s for type? */
+ if (e->e_parent->e_from.q_mailer == NULL ||
+ (p = e->e_parent->e_from.q_mailer->m_mtatype) == NULL)
+ p = "dns";
+ (void) sm_snprintf(buf, sizeof buf,
+ "Received-From-MTA: %s; %.800s",
+ p, RealHostName);
+ putline(buf, mci);
+ }
+
+ /* Arrival-Date: -- when it arrived here */
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "Arrival-Date: ",
+ arpadate(ctime(&e->e_parent->e_ctime)));
+ putline(buf, mci);
+
+ /* Deliver-By-Date: -- when it should have been delivered */
+ if (IS_DLVR_BY(e->e_parent))
+ {
+ time_t dbyd;
+
+ dbyd = e->e_parent->e_ctime + e->e_parent->e_deliver_by;
+ (void) sm_strlcpyn(buf, sizeof buf, 2,
+ "Deliver-By-Date: ",
+ arpadate(ctime(&dbyd)));
+ putline(buf, mci);
+ }
+
+ /*
+ ** Output per-address information.
+ */
+
+ for (q = e->e_parent->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ char *action;
+
+ if (QS_IS_BADADDR(q->q_state))
+ {
+ /* RFC 1891, 6.2.6 (b) */
+ if (bitset(QHASNOTIFY, q->q_flags) &&
+ !bitset(QPINGONFAILURE, q->q_flags))
+ continue;
+ action = "failed";
+ }
+ else if (!bitset(QPRIMARY, q->q_flags))
+ continue;
+ else if (bitset(QDELIVERED, q->q_flags))
+ {
+ if (bitset(QEXPANDED, q->q_flags))
+ action = "delivered (to mailing list)";
+ else
+ action = "delivered (to mailbox)";
+ }
+ else if (bitset(QRELAYED, q->q_flags))
+ action = "relayed (to non-DSN-aware mailer)";
+ else if (bitset(QEXPANDED, q->q_flags))
+ action = "expanded (to multi-recipient alias)";
+ else if (bitset(QDELAYED, q->q_flags))
+ action = "delayed";
+ else if (bitset(QBYTRACE, q->q_flags))
+ action = "relayed (Deliver-By trace mode)";
+ else if (bitset(QBYNDELAY, q->q_flags))
+ action = "delayed (Deliver-By notify mode)";
+ else if (bitset(QBYNRELAY, q->q_flags))
+ action = "relayed (Deliver-By notify mode)";
+ else
+ continue;
+
+ putline("", mci);
+
+ /* Original-Recipient: -- passed from on high */
+ if (q->q_orcpt != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Original-Recipient: %.800s",
+ q->q_orcpt);
+ putline(buf, mci);
+ }
+
+ /* Figure out actual recipient */
+ actual[0] = '\0';
+ if (q->q_user[0] != '\0')
+ {
+ if (q->q_mailer != NULL &&
+ q->q_mailer->m_addrtype != NULL)
+ p = q->q_mailer->m_addrtype;
+ else
+ p = "rfc822";
+
+ if (sm_strcasecmp(p, "rfc822") == 0 &&
+ strchr(q->q_user, '@') == NULL)
+ {
+ (void) sm_snprintf(actual,
+ sizeof actual,
+ "%s; %.700s@%.100s",
+ p, q->q_user,
+ MyHostName);
+ }
+ else
+ {
+ (void) sm_snprintf(actual,
+ sizeof actual,
+ "%s; %.800s",
+ p, q->q_user);
+ }
+ }
+
+ /* Final-Recipient: -- the name from the RCPT command */
+ if (q->q_finalrcpt == NULL)
+ {
+ /* should never happen */
+ sm_syslog(LOG_ERR, e->e_id,
+ "returntosender: q_finalrcpt is NULL");
+
+ /* try to fall back to the actual recipient */
+ if (actual[0] != '\0')
+ q->q_finalrcpt = sm_rpool_strdup_x(e->e_rpool,
+ actual);
+ }
+
+ if (q->q_finalrcpt != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Final-Recipient: %s",
+ q->q_finalrcpt);
+ putline(buf, mci);
+ }
+
+ /* X-Actual-Recipient: -- the real problem address */
+ if (actual[0] != '\0' &&
+ q->q_finalrcpt != NULL &&
+ strcmp(actual, q->q_finalrcpt) != 0)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "X-Actual-Recipient: %s",
+ actual);
+ putline(buf, mci);
+ }
+
+ /* Action: -- what happened? */
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "Action: ",
+ action);
+ putline(buf, mci);
+
+ /* Status: -- what _really_ happened? */
+ if (q->q_status != NULL)
+ p = q->q_status;
+ else if (QS_IS_BADADDR(q->q_state))
+ p = "5.0.0";
+ else if (QS_IS_QUEUEUP(q->q_state))
+ p = "4.0.0";
+ else
+ p = "2.0.0";
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "Status: ", p);
+ putline(buf, mci);
+
+ /* Remote-MTA: -- who was I talking to? */
+ if (q->q_statmta != NULL)
+ {
+ if (q->q_mailer == NULL ||
+ (p = q->q_mailer->m_mtatype) == NULL)
+ p = "dns";
+ (void) sm_snprintf(buf, sizeof buf,
+ "Remote-MTA: %s; %.800s",
+ p, q->q_statmta);
+ p = &buf[strlen(buf) - 1];
+ if (*p == '.')
+ *p = '\0';
+ putline(buf, mci);
+ }
+
+ /* Diagnostic-Code: -- actual result from other end */
+ if (q->q_rstatus != NULL)
+ {
+ p = q->q_mailer->m_diagtype;
+ if (p == NULL)
+ p = "smtp";
+ (void) sm_snprintf(buf, sizeof buf,
+ "Diagnostic-Code: %s; %.800s",
+ p, q->q_rstatus);
+ putline(buf, mci);
+ }
+
+ /* Last-Attempt-Date: -- fine granularity */
+ if (q->q_statdate == (time_t) 0L)
+ q->q_statdate = curtime();
+ (void) sm_strlcpyn(buf, sizeof buf, 2,
+ "Last-Attempt-Date: ",
+ arpadate(ctime(&q->q_statdate)));
+ putline(buf, mci);
+
+ /* Will-Retry-Until: -- for delayed messages only */
+ if (QS_IS_QUEUEUP(q->q_state))
+ {
+ time_t xdate;
+
+ xdate = e->e_parent->e_ctime +
+ TimeOuts.to_q_return[e->e_parent->e_timeoutclass];
+ (void) sm_strlcpyn(buf, sizeof buf, 2,
+ "Will-Retry-Until: ",
+ arpadate(ctime(&xdate)));
+ putline(buf, mci);
+ }
+ }
+ }
+#endif /* DSN */
+
+ /*
+ ** Output text of original message
+ */
+
+ putline("", mci);
+ if (bitset(EF_HAS_DF, e->e_parent->e_flags))
+ {
+ sendbody = !bitset(EF_NO_BODY_RETN, e->e_parent->e_flags) &&
+ !bitset(EF_NO_BODY_RETN, e->e_flags);
+
+ if (e->e_msgboundary == NULL)
+ {
+ if (sendbody)
+ putline(" ----- Original message follows -----\n", mci);
+ else
+ putline(" ----- Message header follows -----\n", mci);
+ }
+ else
+ {
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "--",
+ e->e_msgboundary);
+
+ putline(buf, mci);
+ (void) sm_strlcpyn(buf, sizeof buf, 2, "Content-Type: ",
+ sendbody ? "message/rfc822"
+ : "text/rfc822-headers");
+ putline(buf, mci);
+
+ p = hvalue("Content-Transfer-Encoding",
+ e->e_parent->e_header);
+ if (p != NULL && sm_strcasecmp(p, "binary") != 0)
+ p = NULL;
+ if (p == NULL &&
+ bitset(EF_HAS8BIT, e->e_parent->e_flags))
+ p = "8bit";
+ if (p != NULL)
+ {
+ (void) sm_snprintf(buf, sizeof buf,
+ "Content-Transfer-Encoding: %s",
+ p);
+ putline(buf, mci);
+ }
+ }
+ putline("", mci);
+ save_errno = errno;
+ putheader(mci, e->e_parent->e_header, e->e_parent, M87F_OUTER);
+ errno = save_errno;
+ if (sendbody)
+ putbody(mci, e->e_parent, e->e_msgboundary);
+ else if (e->e_msgboundary == NULL)
+ {
+ putline("", mci);
+ putline(" ----- Message body suppressed -----", mci);
+ }
+ }
+ else if (e->e_msgboundary == NULL)
+ {
+ putline(" ----- No message was collected -----\n", mci);
+ }
+
+ if (e->e_msgboundary != NULL)
+ {
+ putline("", mci);
+ (void) sm_strlcpyn(buf, sizeof buf, 3, "--", e->e_msgboundary,
+ "--");
+ putline(buf, mci);
+ }
+ putline("", mci);
+ (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
+
+ /*
+ ** Cleanup and exit
+ */
+
+ if (errno != 0)
+ syserr("errbody: I/O error");
+}
+/*
+** SMTPTODSN -- convert SMTP to DSN status code
+**
+** Parameters:
+** smtpstat -- the smtp status code (e.g., 550).
+**
+** Returns:
+** The DSN version of the status code.
+**
+** Storage Management:
+** smtptodsn() returns a pointer to a character string literal,
+** which will remain valid forever, and thus does not need to
+** be copied. Current code relies on this property.
+*/
+
+char *
+smtptodsn(smtpstat)
+ int smtpstat;
+{
+ if (smtpstat < 0)
+ return "4.4.2";
+
+ switch (smtpstat)
+ {
+ case 450: /* Req mail action not taken: mailbox unavailable */
+ return "4.2.0";
+
+ case 451: /* Req action aborted: local error in processing */
+ return "4.3.0";
+
+ case 452: /* Req action not taken: insufficient sys storage */
+ return "4.3.1";
+
+ case 500: /* Syntax error, command unrecognized */
+ return "5.5.2";
+
+ case 501: /* Syntax error in parameters or arguments */
+ return "5.5.4";
+
+ case 502: /* Command not implemented */
+ return "5.5.1";
+
+ case 503: /* Bad sequence of commands */
+ return "5.5.1";
+
+ case 504: /* Command parameter not implemented */
+ return "5.5.4";
+
+ case 550: /* Req mail action not taken: mailbox unavailable */
+ return "5.2.0";
+
+ case 551: /* User not local; please try <...> */
+ return "5.1.6";
+
+ case 552: /* Req mail action aborted: exceeded storage alloc */
+ return "5.2.2";
+
+ case 553: /* Req action not taken: mailbox name not allowed */
+ return "5.1.0";
+
+ case 554: /* Transaction failed */
+ return "5.0.0";
+ }
+
+ if ((smtpstat / 100) == 2)
+ return "2.0.0";
+ if ((smtpstat / 100) == 4)
+ return "4.0.0";
+ return "5.0.0";
+}
+/*
+** XTEXTIFY -- take regular text and turn it into DSN-style xtext
+**
+** Parameters:
+** t -- the text to convert.
+** taboo -- additional characters that must be encoded.
+**
+** Returns:
+** The xtext-ified version of the same string.
+*/
+
+char *
+xtextify(t, taboo)
+ register char *t;
+ char *taboo;
+{
+ register char *p;
+ int l;
+ int nbogus;
+ static char *bp = NULL;
+ static int bplen = 0;
+
+ if (taboo == NULL)
+ taboo = "";
+
+ /* figure out how long this xtext will have to be */
+ nbogus = l = 0;
+ for (p = t; *p != '\0'; p++)
+ {
+ register int c = (*p & 0xff);
+
+ /* ASCII dependence here -- this is the way the spec words it */
+ if (c < '!' || c > '~' || c == '+' || c == '\\' || c == '(' ||
+ strchr(taboo, c) != NULL)
+ nbogus++;
+ l++;
+ }
+ if (nbogus < 0)
+ {
+ /* since nbogus is ssize_t and wrapped, 2 * size_t would wrap */
+ syserr("!xtextify string too long");
+ }
+ if (nbogus == 0)
+ return t;
+ l += nbogus * 2 + 1;
+
+ /* now allocate space if necessary for the new string */
+ if (l > bplen)
+ {
+ if (bp != NULL)
+ sm_free(bp); /* XXX */
+ bp = sm_pmalloc_x(l);
+ bplen = l;
+ }
+
+ /* ok, copy the text with byte expansion */
+ for (p = bp; *t != '\0'; )
+ {
+ register int c = (*t++ & 0xff);
+
+ /* ASCII dependence here -- this is the way the spec words it */
+ if (c < '!' || c > '~' || c == '+' || c == '\\' || c == '(' ||
+ strchr(taboo, c) != NULL)
+ {
+ *p++ = '+';
+ *p++ = "0123456789ABCDEF"[c >> 4];
+ *p++ = "0123456789ABCDEF"[c & 0xf];
+ }
+ else
+ *p++ = c;
+ }
+ *p = '\0';
+ return bp;
+}
+/*
+** XUNTEXTIFY -- take xtext and turn it into plain text
+**
+** Parameters:
+** t -- the xtextified text.
+**
+** Returns:
+** The decoded text. No attempt is made to deal with
+** null strings in the resulting text.
+*/
+
+char *
+xuntextify(t)
+ register char *t;
+{
+ register char *p;
+ int l;
+ static char *bp = NULL;
+ static int bplen = 0;
+
+ /* heuristic -- if no plus sign, just return the input */
+ if (strchr(t, '+') == NULL)
+ return t;
+
+ /* xtext is always longer than decoded text */
+ l = strlen(t);
+ if (l > bplen)
+ {
+ if (bp != NULL)
+ sm_free(bp); /* XXX */
+ bp = xalloc(l);
+ bplen = l;
+ }
+
+ /* ok, copy the text with byte compression */
+ for (p = bp; *t != '\0'; t++)
+ {
+ register int c = *t & 0xff;
+
+ if (c != '+')
+ {
+ *p++ = c;
+ continue;
+ }
+
+ c = *++t & 0xff;
+ if (!isascii(c) || !isxdigit(c))
+ {
+ /* error -- first digit is not hex */
+ usrerr("bogus xtext: +%c", c);
+ t--;
+ continue;
+ }
+ if (isdigit(c))
+ c -= '0';
+ else if (isupper(c))
+ c -= 'A' - 10;
+ else
+ c -= 'a' - 10;
+ *p = c << 4;
+
+ c = *++t & 0xff;
+ if (!isascii(c) || !isxdigit(c))
+ {
+ /* error -- second digit is not hex */
+ usrerr("bogus xtext: +%x%c", *p >> 4, c);
+ t--;
+ continue;
+ }
+ if (isdigit(c))
+ c -= '0';
+ else if (isupper(c))
+ c -= 'A' - 10;
+ else
+ c -= 'a' - 10;
+ *p++ |= c;
+ }
+ *p = '\0';
+ return bp;
+}
+/*
+** XTEXTOK -- check if a string is legal xtext
+**
+** Xtext is used in Delivery Status Notifications. The spec was
+** taken from RFC 1891, ``SMTP Service Extension for Delivery
+** Status Notifications''.
+**
+** Parameters:
+** s -- the string to check.
+**
+** Returns:
+** true -- if 's' is legal xtext.
+** false -- if it has any illegal characters in it.
+*/
+
+bool
+xtextok(s)
+ char *s;
+{
+ int c;
+
+ while ((c = *s++) != '\0')
+ {
+ if (c == '+')
+ {
+ c = *s++;
+ if (!isascii(c) || !isxdigit(c))
+ return false;
+ c = *s++;
+ if (!isascii(c) || !isxdigit(c))
+ return false;
+ }
+ else if (c < '!' || c > '~' || c == '=')
+ return false;
+ }
+ return true;
+}
+/*
+** PRUNEROUTE -- prune an RFC-822 source route
+**
+** Trims down a source route to the last internet-registered hop.
+** This is encouraged by RFC 1123 section 5.3.3.
+**
+** Parameters:
+** addr -- the address
+**
+** Returns:
+** true -- address was modified
+** false -- address could not be pruned
+**
+** Side Effects:
+** modifies addr in-place
+*/
+
+static bool
+pruneroute(addr)
+ char *addr;
+{
+#if NAMED_BIND
+ char *start, *at, *comma;
+ char c;
+ int braclev;
+ int rcode;
+ int i;
+ char hostbuf[BUFSIZ];
+ char *mxhosts[MAXMXHOSTS + 1];
+
+ /* check to see if this is really a route-addr */
+ if (*addr != '<' || addr[1] != '@' || addr[strlen(addr) - 1] != '>')
+ return false;
+
+ /*
+ ** Can't simply find the first ':' is the address might be in the
+ ** form: "<@[IPv6:::1]:user@host>" and the first ':' in inside
+ ** the IPv6 address.
+ */
+
+ start = addr;
+ braclev = 0;
+ while (*start != '\0')
+ {
+ if (*start == ':' && braclev <= 0)
+ break;
+ else if (*start == '[')
+ braclev++;
+ else if (*start == ']' && braclev > 0)
+ braclev--;
+ start++;
+ }
+ if (braclev > 0 || *start != ':')
+ return false;
+
+ at = strrchr(addr, '@');
+ if (at == NULL || at < start)
+ return false;
+
+ /* slice off the angle brackets */
+ i = strlen(at + 1);
+ if (i >= sizeof hostbuf)
+ return false;
+ (void) sm_strlcpy(hostbuf, at + 1, sizeof hostbuf);
+ hostbuf[i - 1] = '\0';
+
+ while (start != NULL)
+ {
+ if (getmxrr(hostbuf, mxhosts, NULL, false,
+ &rcode, true, NULL) > 0)
+ {
+ (void) sm_strlcpy(addr + 1, start + 1,
+ strlen(addr) - 1);
+ return true;
+ }
+ c = *start;
+ *start = '\0';
+ comma = strrchr(addr, ',');
+ if (comma != NULL && comma[1] == '@' &&
+ strlen(comma + 2) < sizeof hostbuf)
+ (void) sm_strlcpy(hostbuf, comma + 2, sizeof hostbuf);
+ else
+ comma = NULL;
+ *start = c;
+ start = comma;
+ }
+#endif /* NAMED_BIND */
+ return false;
+}
diff --git a/contrib/sendmail/src/sendmail.8 b/contrib/sendmail/src/sendmail.8
new file mode 100644
index 0000000..1c014fd
--- /dev/null
+++ b/contrib/sendmail/src/sendmail.8
@@ -0,0 +1,712 @@
+.\" Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+.\" All rights reserved.
+.\" Copyright (c) 1983, 1997 Eric P. Allman. All rights reserved.
+.\" Copyright (c) 1988, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" By using this file, you agree to the terms and conditions set
+.\" forth in the LICENSE file which can be found at the top level of
+.\" the sendmail distribution.
+.\"
+.\"
+.\" $Id: sendmail.8,v 8.51 2002/05/24 15:42:13 ca Exp $
+.\"
+.TH SENDMAIL 8 "$Date: 2001/03/23 22:10:00 $"
+.SH NAME
+sendmail
+\- an electronic mail transport agent
+.SH SYNOPSIS
+.B sendmail
+.RI [ flags "] [" "address ..." ]
+.br
+.B newaliases
+.br
+.B mailq
+.RB [ \-v ]
+.br
+.B hoststat
+.br
+.B purgestat
+.br
+.B smtpd
+.SH DESCRIPTION
+.B Sendmail
+sends a message to one or more
+.I recipients,
+routing the message over whatever networks
+are necessary.
+.B Sendmail
+does internetwork forwarding as necessary
+to deliver the message to the correct place.
+.PP
+.B Sendmail
+is not intended as a user interface routine;
+other programs provide user-friendly
+front ends;
+.B sendmail
+is used only to deliver pre-formatted messages.
+.PP
+With no flags,
+.B sendmail
+reads its standard input
+up to an end-of-file
+or a line consisting only of a single dot
+and sends a copy of the message found there
+to all of the addresses listed.
+It determines the network(s) to use
+based on the syntax and contents of the addresses.
+.PP
+Local addresses are looked up in a file
+and aliased appropriately.
+Aliasing can be prevented by preceding the address
+with a backslash.
+Beginning with 8.10, the sender is included in any alias
+expansions, e.g.,
+if `john' sends to `group',
+and `group' includes `john' in the expansion,
+then the letter will also be delivered to `john'.
+.SS Parameters
+.TP
+.B \-Ac
+Use submit.cf even if the operation mode does not indicate
+an initial mail submission.
+.TP
+.B \-Am
+Use sendmail.cf even if the operation mode indicates
+an initial mail submission.
+.TP
+.BI \-B type
+Set the body type to
+.IR type .
+Current legal values are
+7BIT
+or
+8BITMIME.
+.TP
+.B \-ba
+Go into
+ARPANET
+mode. All input lines must end with a CR-LF,
+and all messages will be generated with a CR-LF at the end.
+Also,
+the ``From:'' and ``Sender:''
+fields are examined for the name of the sender.
+.TP
+.B \-bd
+Run as a daemon.
+.B Sendmail
+will fork and run in background
+listening on socket 25 for incoming
+SMTP
+connections.
+This is normally run from
+/etc/rc.
+.TP
+.B \-bD
+Same as
+.B \-bd
+except runs in foreground.
+.TP
+.B \-bh
+Print the persistent host status database.
+.TP
+.B \-bH
+Purge expired entries from the persistent host status database.
+.TP
+.B \-bi
+Initialize the alias database.
+.TP
+.B \-bm
+Deliver mail in the usual way (default).
+.TP
+.B \-bp
+Print a listing of the queue(s).
+.TP
+.B \-bP
+Print number of entries in the queue(s);
+only available with shared memory support.
+.TP
+.B \-bs
+Use the
+SMTP
+protocol as described in
+RFC821
+on standard input and output.
+This flag implies all the operations of the
+.B \-ba
+flag that are compatible with
+SMTP.
+.TP
+.B \-bt
+Run in address test mode.
+This mode reads addresses and shows the steps in parsing;
+it is used for debugging configuration tables.
+.TP
+.B \-bv
+Verify names only \- do not try to collect or deliver a message.
+Verify mode is normally used for validating
+users or mailing lists.
+.TP
+.BI \-C file
+Use alternate configuration file.
+.B Sendmail
+refuses to run as root if an alternate configuration file is specified.
+.TP
+.BI \-d X
+Set debugging value to
+.IR X .
+.ne 1i
+.TP
+.BI \-F fullname
+Set the full name of the sender.
+.TP
+.BI \-f name
+Sets the name of the ``from'' person
+(i.e., the envelope sender of the mail).
+This address may also be used in the From: header
+if that header is missing during initial submission.
+The envelope sender address is used as the recipient
+for delivery status notifications
+and may also appear in a Return-Path: header.
+.B \-f
+should only be used
+by ``trusted'' users
+(normally
+.IR root ", " daemon ,
+and
+.IR network )
+or if the person you are trying to become
+is the same as the person you are.
+Otherwise,
+an X-Authentication-Warning header
+will be added to the message.
+.TP
+.BI \-G
+Relay (gateway) submission of a message,
+e.g., when
+.BR rmail
+calls
+.B sendmail .
+.TP
+.BI \-h N
+Set the hop count to
+.IR N .
+The hop count is incremented every time the mail is
+processed.
+When it reaches a limit,
+the mail is returned with an error message,
+the victim of an aliasing loop.
+If not specified,
+``Received:'' lines in the message are counted.
+.TP
+.B \-i
+Ignore dots alone on lines by themselves in incoming messages.
+This should be set if you are reading data from a file.
+.TP
+.BI "\-L " tag
+Set the identifier used in syslog messages to the supplied
+.IR tag .
+.TP
+.BI "\-N " dsn
+Set delivery status notification conditions to
+.IR dsn ,
+which can be
+`never'
+for no notifications
+or a comma separated list of the values
+`failure'
+to be notified if delivery failed,
+`delay'
+to be notified if delivery is delayed, and
+`success'
+to be notified when the message is successfully delivered.
+.TP
+.B \-n
+Don't do aliasing.
+.TP
+\fB\-O\fP \fIoption\fR=\fIvalue\fR
+Set option
+.I option
+to the specified
+.IR value .
+This form uses long names. See below for more details.
+.TP
+.BI \-o "x value"
+Set option
+.I x
+to the specified
+.IR value .
+This form uses single character names only.
+The short names are not described in this manual page;
+see the
+.I "Sendmail Installation and Operation Guide"
+for details.
+.TP
+.BI \-p protocol
+Set the name of the protocol used to receive the message.
+This can be a simple protocol name such as ``UUCP''
+or a protocol and hostname, such as ``UUCP:ucbvax''.
+.TP
+\fB\-q\fR[\fItime\fR]
+Process saved messages in the queue at given intervals.
+If
+.I time
+is omitted, process the queue once.
+.I Time
+is given as a tagged number,
+with
+`s'
+being seconds,
+`m'
+being minutes (default),
+`h'
+being hours,
+`d'
+being days,
+and
+`w'
+being weeks.
+For example,
+`\-q1h30m'
+or
+`\-q90m'
+would both set the timeout to one hour thirty minutes.
+By default,
+.B sendmail
+will run in the background.
+This option can be used safely with
+.BR \-bd .
+.TP
+\fB\-qp\fR[\fItime\fR]
+Similar to \fB\-q\fItime\fR,
+except that instead of periodically forking a child to process the queue,
+sendmail forks a single persistent child for each queue
+that alternates between processing the queue and sleeping.
+The sleep time is given as the argument; it defaults to 1 second.
+The process will always sleep at least 5 seconds if the queue was
+empty in the previous queue run.
+.TP
+\fB\-q\fRf
+Process saved messages in the queue once and do not fork(),
+but run in the foreground.
+.TP
+\fB\-q\fRG name
+Process jobs in queue group called
+.I name
+only.
+.TP
+\fB\-q\fR[\fI!\fR]I substr
+Limit processed jobs to those containing
+.I substr
+as a substring of the queue id or not when
+.I !
+is specified.
+.TP
+\fB\-q\fR[\fI!\fR]R substr
+Limit processed jobs to those containing
+.I substr
+as a substring of one of the recipients or not when
+.I !
+is specified.
+.TP
+\fB\-q\fR[\fI!\fR]S substr
+Limit processed jobs to those containing
+.I substr
+as a substring of the sender or not when
+.I !
+is specified.
+.TP
+.BI "\-R " return
+Set the amount of the message to be returned
+if the message bounces.
+The
+.I return
+parameter can be
+`full'
+to return the entire message or
+`hdrs'
+to return only the headers.
+In the latter case also local bounces return only the headers.
+.TP
+.BI \-r name
+An alternate and obsolete form of the
+.B \-f
+flag.
+.TP
+.B \-t
+Read message for recipients.
+To:, Cc:, and Bcc: lines will be scanned for recipient addresses.
+The Bcc: line will be deleted before transmission.
+.TP
+.BI "\-V " envid
+Set the original envelope id.
+This is propagated across SMTP to servers that support DSNs
+and is returned in DSN-compliant error messages.
+.TP
+.B \-v
+Go into verbose mode.
+Alias expansions will be announced, etc.
+.TP
+.BI "\-X " logfile
+Log all traffic in and out of mailers in the indicated log file.
+This should only be used as a last resort
+for debugging mailer bugs.
+It will log a lot of data very quickly.
+.TP
+.B \-\-
+Stop processing command flags and use the rest of the arguments as
+addresses.
+.SS Options
+There are also a number of processing options that may be set.
+Normally these will only be used by a system administrator.
+Options may be set either on the command line
+using the
+.B \-o
+flag (for short names), the
+.B \-O
+flag (for long names),
+or in the configuration file.
+This is a partial list limited to those options that are likely to be useful
+on the command line
+and only shows the long names;
+for a complete list (and details), consult the
+.IR "Sendmail Installation and Operation Guide" .
+The options are:
+.TP
+.RI AliasFile= file
+Use alternate alias file.
+.TP
+HoldExpensive
+On mailers that are considered ``expensive'' to connect to,
+don't initiate immediate connection.
+This requires queueing.
+.TP
+.RI CheckpointInterval= N
+Checkpoint the queue file after every
+.I N
+successful deliveries (default 10).
+This avoids excessive duplicate deliveries
+when sending to long mailing lists
+interrupted by system crashes.
+.ne 1i
+.TP
+.RI DeliveryMode= x
+Set the delivery mode to
+.IR x .
+Delivery modes are
+`i'
+for interactive (synchronous) delivery,
+`b'
+for background (asynchronous) delivery,
+`q'
+for queue only \- i.e.,
+actual delivery is done the next time the queue is run, and
+`d'
+for deferred \- the same as
+`q'
+except that database lookups for maps which have set the \-D option
+(default for the host map) are avoided.
+.TP
+.RI ErrorMode= x
+Set error processing to mode
+.IR x .
+Valid modes are
+`m'
+to mail back the error message,
+`w'
+to ``write''
+back the error message
+(or mail it back if the sender is not logged in),
+`p'
+to print the errors on the terminal
+(default),
+`q'
+to throw away error messages
+(only exit status is returned),
+and
+`e'
+to do special processing for the BerkNet.
+If the text of the message is not mailed back
+by
+modes
+`m'
+or
+`w'
+and if the sender is local to this machine,
+a copy of the message is appended to the file
+.I dead.letter
+in the sender's home directory.
+.TP
+SaveFromLine
+Save
+UNIX-style
+From lines at the front of messages.
+.TP
+.RI MaxHopCount= N
+The maximum number of times a message is allowed to ``hop''
+before we decide it is in a loop.
+.TP
+IgnoreDots
+Do not take dots on a line by themselves
+as a message terminator.
+.TP
+SendMimeErrors
+Send error messages in MIME format.
+If not set, the DSN (Delivery Status Notification) SMTP extension
+is disabled.
+.TP
+.RI ConnectionCacheTimeout= timeout
+Set connection cache timeout.
+.TP
+.RI ConnectionCacheSize= N
+Set connection cache size.
+.TP
+.RI LogLevel= n
+The log level.
+.TP
+.RI MeToo= False
+Don't send to ``me'' (the sender) if I am in an alias expansion.
+.TP
+CheckAliases
+Validate the right hand side of aliases during a
+newaliases(1)
+command.
+.TP
+OldStyleHeaders
+If set, this message may have
+old style headers.
+If not set,
+this message is guaranteed to have new style headers
+(i.e., commas instead of spaces between addresses).
+If set, an adaptive algorithm is used that will correctly
+determine the header format in most cases.
+.TP
+.RI QueueDirectory= queuedir
+Select the directory in which to queue messages.
+.TP
+.RI StatusFile= file
+Save statistics in the named file.
+.TP
+.RI Timeout.queuereturn= time
+Set the timeout on undelivered messages in the queue to the specified time.
+After delivery has failed
+(e.g., because of a host being down)
+for this amount of time,
+failed messages will be returned to the sender.
+The default is five days.
+.TP
+.RI UserDatabaseSpec= userdatabase
+If set, a user database is consulted to get forwarding information.
+You can consider this an adjunct to the aliasing mechanism,
+except that the database is intended to be distributed;
+aliases are local to a particular host.
+This may not be available if your sendmail does not have the
+USERDB
+option compiled in.
+.TP
+ForkEachJob
+Fork each job during queue runs.
+May be convenient on memory-poor machines.
+.TP
+SevenBitInput
+Strip incoming messages to seven bits.
+.TP
+.RI EightBitMode= mode
+Set the handling of eight bit input to seven bit destinations to
+.IR mode :
+m
+(mimefy) will convert to seven-bit MIME format,
+p
+(pass) will pass it as eight bits (but violates protocols),
+and
+s
+(strict) will bounce the message.
+.TP
+.RI MinQueueAge= timeout
+Sets how long a job must ferment in the queue between attempts to send it.
+.TP
+.RI DefaultCharSet= charset
+Sets the default character set used to label 8-bit data
+that is not otherwise labelled.
+.TP
+.RI DialDelay= sleeptime
+If opening a connection fails,
+sleep for
+.I sleeptime
+seconds and try again.
+Useful on dial-on-demand sites.
+.TP
+.RI NoRecipientAction= action
+Set the behaviour when there are no recipient headers (To:, Cc: or
+Bcc:) in the message to
+.IR action :
+none
+leaves the message unchanged,
+add-to
+adds a To: header with the envelope recipients,
+add-apparently-to
+adds an Apparently-To: header with the envelope recipients,
+add-bcc
+adds an empty Bcc: header, and
+add-to-undisclosed
+adds a header reading
+`To: undisclosed-recipients:;'.
+.TP
+.RI MaxDaemonChildren= N
+Sets the maximum number of children that an incoming SMTP daemon
+will allow to spawn at any time to
+.IR N .
+.TP
+.RI ConnectionRateThrottle= N
+Sets the maximum number of connections per second to the SMTP port to
+.IR N .
+.PP
+In aliases,
+the first character of a name may be
+a vertical bar to cause interpretation of
+the rest of the name as a command
+to pipe the mail to.
+It may be necessary to quote the name
+to keep
+.B sendmail
+from suppressing the blanks from between arguments.
+For example, a common alias is:
+.IP
+msgs: "|/usr/bin/msgs -s"
+.PP
+Aliases may also have the syntax
+.RI ``:include: filename ''
+to ask
+.B sendmail
+to read the named file for a list of recipients.
+For example, an alias such as:
+.IP
+poets: ":include:/usr/local/lib/poets.list"
+.PP
+would read
+.I /usr/local/lib/poets.list
+for the list of addresses making up the group.
+.PP
+.B Sendmail
+returns an exit status
+describing what it did.
+The codes are defined in
+.RI < sysexits.h >:
+.TP
+EX_OK
+Successful completion on all addresses.
+.TP
+EX_NOUSER
+User name not recognized.
+.TP
+EX_UNAVAILABLE
+Catchall meaning necessary resources
+were not available.
+.TP
+EX_SYNTAX
+Syntax error in address.
+.TP
+EX_SOFTWARE
+Internal software error,
+including bad arguments.
+.TP
+EX_OSERR
+Temporary operating system error,
+such as
+``cannot fork''.
+.TP
+EX_NOHOST
+Host name not recognized.
+.TP
+EX_TEMPFAIL
+Message could not be sent immediately,
+but was queued.
+.PP
+If invoked as
+.BR newaliases ,
+.B sendmail
+will rebuild the alias database. If invoked as
+.BR mailq ,
+.B sendmail
+will print the contents of the mail queue.
+If invoked as
+.BR hoststat ,
+.B sendmail
+will print the persistent host status database.
+If invoked as
+.BR purgestat ,
+.B sendmail
+will purge expired entries from the persistent host status database.
+If invoked as
+.BR smtpd ,
+.B sendmail
+will act as a daemon, as if the
+.B \-bd
+option were specified.
+.SH NOTES
+.B sendmail
+often gets blamed for many problems
+that are actually the result of other problems,
+such as overly permissive modes on directories.
+For this reason,
+.B sendmail
+checks the modes on system directories and files
+to determine if they can be trusted.
+Although these checks can be turned off
+and your system security reduced by setting the
+.BR DontBlameSendmail
+option,
+the permission problems should be fixed.
+For more information, see:
+
+.I http://www.sendmail.org/tips/DontBlameSendmail.html
+.SH FILES
+Except for the file
+.I /etc/mail/sendmail.cf
+itself the following pathnames are all specified in
+.IR /etc/mail/sendmail.cf .
+Thus,
+these values are only approximations.
+.PP
+.TP
+ /etc/mail/aliases
+raw data for alias names
+.TP
+ /etc/mail/aliases.db
+data base of alias names
+.TP
+ /etc/mail/sendmail.cf
+configuration file
+.TP
+ /etc/mail/helpfile
+help file
+.TP
+ /etc/mail/statistics
+collected statistics
+.TP
+ /var/spool/mqueue/*
+temp files
+.SH SEE ALSO
+mail(1),
+syslog(3),
+aliases(5),
+mailaddr(7),
+mail.local(8),
+rc(8),
+rmail(8)
+.PP
+DARPA
+Internet Request For Comments
+.IR RFC819 ,
+.IR RFC821 ,
+.IR RFC822 .
+.IR "Sendmail Installation and Operation Guide" ,
+No. 8, SMM.
+.PP
+http://www.sendmail.org/
+.SH HISTORY
+The
+.B sendmail
+command appeared in
+4.2BSD.
+.\" $FreeBSD$
diff --git a/contrib/sendmail/src/sendmail.h b/contrib/sendmail/src/sendmail.h
new file mode 100644
index 0000000..7dc34fa
--- /dev/null
+++ b/contrib/sendmail/src/sendmail.h
@@ -0,0 +1,2554 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ */
+
+/*
+** SENDMAIL.H -- MTA-specific definitions for sendmail.
+*/
+
+#ifndef _SENDMAIL_H
+# define _SENDMAIL_H 1
+
+#ifdef _DEFINE
+# define EXTERN
+#else /* _DEFINE */
+# define EXTERN extern
+#endif /* _DEFINE */
+
+
+#include <unistd.h>
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <setjmp.h>
+#include <string.h>
+#include <time.h>
+# ifdef EX_OK
+# undef EX_OK /* for SVr4.2 SMP */
+# endif /* EX_OK */
+
+#include "sendmail/sendmail.h"
+
+/* profiling? */
+#if MONCONTROL
+# define SM_PROF(x) moncontrol(x)
+#else /* MONCONTROL */
+# define SM_PROF(x)
+#endif /* MONCONTROL */
+
+#ifdef _DEFINE
+# ifndef lint
+SM_UNUSED(static char SmailId[]) = "@(#)$Id: sendmail.h,v 8.919.2.4 2002/08/16 14:56:01 ca Exp $";
+# endif /* ! lint */
+#endif /* _DEFINE */
+
+#include "bf.h"
+#include "timers.h"
+#include <sm/exc.h>
+#include <sm/heap.h>
+#include <sm/debug.h>
+#include <sm/rpool.h>
+#include <sm/io.h>
+#include <sm/path.h>
+#include <sm/signal.h>
+#include <sm/clock.h>
+#include <sm/mbdb.h>
+#include <sm/errstring.h>
+#include <sm/sysexits.h>
+#include <sm/shm.h>
+
+#ifdef LOG
+# include <syslog.h>
+#endif /* LOG */
+
+
+
+# if NETINET || NETINET6 || NETUNIX || NETISO || NETNS || NETX25
+# include <sys/socket.h>
+# endif /* NETINET || NETINET6 || NETUNIX || NETISO || NETNS || NETX25 */
+# if NETUNIX
+# include <sys/un.h>
+# endif /* NETUNIX */
+# if NETINET || NETINET6
+# include <netinet/in.h>
+# endif /* NETINET || NETINET6 */
+# if NETINET6
+/*
+** There is no standard yet for IPv6 includes.
+** Specify OS specific implementation in conf.h
+*/
+# endif /* NETINET6 */
+# if NETISO
+# include <netiso/iso.h>
+# endif /* NETISO */
+# if NETNS
+# include <netns/ns.h>
+# endif /* NETNS */
+# if NETX25
+# include <netccitt/x25.h>
+# endif /* NETX25 */
+
+# if NAMED_BIND
+# include <arpa/nameser.h>
+# ifdef NOERROR
+# undef NOERROR /* avoid <sys/streams.h> conflict */
+# endif /* NOERROR */
+# include <resolv.h>
+# else /* NAMED_BIND */
+# undef SM_SET_H_ERRNO
+# define SM_SET_H_ERRNO(err)
+# endif /* NAMED_BIND */
+
+# if HESIOD
+# include <hesiod.h>
+# if !defined(HES_ER_OK) || defined(HESIOD_INTERFACES)
+# define HESIOD_INIT /* support for the new interface */
+# endif /* !defined(HES_ER_OK) || defined(HESIOD_INTERFACES) */
+# endif /* HESIOD */
+
+#if STARTTLS
+# include <openssl/ssl.h>
+# if !TLS_NO_RSA
+# define RSA_KEYLENGTH 512
+# endif /* !TLS_NO_RSA */
+#endif /* STARTTLS */
+
+#if SASL /* include the sasl include files if we have them */
+
+
+# if SASL == 2 || SASL >= 20000
+# include <sasl/sasl.h>
+# include <sasl/saslutil.h>
+# else /* SASL == 2 || SASL >= 20000 */
+# include <sasl.h>
+# include <saslutil.h>
+# endif /* SASL == 2 || SASL >= 20000 */
+# if defined(SASL_VERSION_MAJOR) && defined(SASL_VERSION_MINOR) && defined(SASL_VERSION_STEP)
+# define SASL_VERSION (SASL_VERSION_MAJOR * 10000) + (SASL_VERSION_MINOR * 100) + SASL_VERSION_STEP
+# if SASL == 1 || SASL == 2
+# undef SASL
+# define SASL SASL_VERSION
+# else /* SASL == 1 || SASL == 2 */
+# if SASL != SASL_VERSION
+ ERROR README: -DSASL (SASL) does not agree with the version of the CYRUS_SASL library (SASL_VERSION)
+ ERROR README: see README!
+# endif /* SASL != SASL_VERSION */
+# endif /* SASL == 1 || SASL == 2 */
+# else /* defined(SASL_VERSION_MAJOR) && defined(SASL_VERSION_MINOR) && defined(SASL_VERSION_STEP) */
+# if SASL == 1
+ ERROR README: please set -DSASL to the version of the CYRUS_SASL library
+ ERROR README: see README!
+# endif /* SASL == 1 */
+# endif /* defined(SASL_VERSION_MAJOR) && defined(SASL_VERSION_MINOR) && defined(SASL_VERSION_STEP) */
+#endif /* SASL */
+
+/*
+** Following are "sort of" configuration constants, but they should
+** be pretty solid on most architectures today. They have to be
+** defined after <arpa/nameser.h> because some versions of that
+** file also define them. In all cases, we can't use sizeof because
+** some systems (e.g., Crays) always treat everything as being at
+** least 64 bits.
+*/
+
+#ifndef INADDRSZ
+# define INADDRSZ 4 /* size of an IPv4 address in bytes */
+#endif /* ! INADDRSZ */
+#ifndef IN6ADDRSZ
+# define IN6ADDRSZ 16 /* size of an IPv6 address in bytes */
+#endif /* ! IN6ADDRSZ */
+#ifndef INT16SZ
+# define INT16SZ 2 /* size of a 16 bit integer in bytes */
+#endif /* ! INT16SZ */
+#ifndef INT32SZ
+# define INT32SZ 4 /* size of a 32 bit integer in bytes */
+#endif /* ! INT32SZ */
+#ifndef INADDR_LOOPBACK
+# define INADDR_LOOPBACK 0x7f000001 /* loopback address */
+#endif /* ! INADDR_LOOPBACK */
+
+/*
+** Error return from inet_addr(3), in case not defined in /usr/include.
+*/
+
+#ifndef INADDR_NONE
+# define INADDR_NONE 0xffffffff
+#endif /* ! INADDR_NONE */
+
+
+/*
+** An 'argument class' describes the storage allocation status
+** of an object pointed to by an argument to a function.
+*/
+
+typedef enum
+{
+ A_HEAP, /* the storage was allocated by malloc, and the
+ * ownership of the storage is ceded by the caller
+ * to the called function. */
+ A_TEMP, /* The storage is temporary, and is only guaranteed
+ * to be valid for the duration of the function call. */
+ A_PERM /* The storage is 'permanent': this might mean static
+ * storage, or rpool storage. */
+} ARGCLASS_T;
+
+/* forward references for prototypes */
+typedef struct envelope ENVELOPE;
+typedef struct mailer MAILER;
+typedef struct queuegrp QUEUEGRP;
+
+/*
+** Address structure.
+** Addresses are stored internally in this structure.
+*/
+
+struct address
+{
+ char *q_paddr; /* the printname for the address */
+ char *q_user; /* user name */
+ char *q_ruser; /* real user name, or NULL if q_user */
+ char *q_host; /* host name */
+ struct mailer *q_mailer; /* mailer to use */
+ unsigned long q_flags; /* status flags, see below */
+ uid_t q_uid; /* user-id of receiver (if known) */
+ gid_t q_gid; /* group-id of receiver (if known) */
+ char *q_home; /* home dir (local mailer only) */
+ char *q_fullname; /* full name if known */
+ struct address *q_next; /* chain */
+ struct address *q_alias; /* address this results from */
+ char *q_owner; /* owner of q_alias */
+ struct address *q_tchain; /* temporary use chain */
+#if PIPELINING
+ struct address *q_pchain; /* chain for pipelining */
+#endif /* PIPELINING */
+ char *q_finalrcpt; /* Final-Recipient: DSN header */
+ char *q_orcpt; /* ORCPT parameter from RCPT TO: line */
+ char *q_status; /* status code for DSNs */
+ char *q_rstatus; /* remote status message for DSNs */
+ time_t q_statdate; /* date of status messages */
+ char *q_statmta; /* MTA generating q_rstatus */
+ short q_state; /* address state, see below */
+ char *q_signature; /* MX-based sorting value */
+ int q_qgrp; /* index into queue groups */
+ int q_qdir; /* queue directory inside group */
+ char *q_message; /* error message */
+};
+
+typedef struct address ADDRESS;
+
+/* bit values for q_flags */
+#define QGOODUID 0x00000001 /* the q_uid q_gid fields are good */
+#define QPRIMARY 0x00000002 /* set from RCPT or argv */
+#define QNOTREMOTE 0x00000004 /* address not for remote forwarding */
+#define QSELFREF 0x00000008 /* this address references itself */
+#define QBOGUSSHELL 0x00000010 /* user has no valid shell listed */
+#define QUNSAFEADDR 0x00000020 /* address acquired via unsafe path */
+#define QPINGONSUCCESS 0x00000040 /* give return on successful delivery */
+#define QPINGONFAILURE 0x00000080 /* give return on failure */
+#define QPINGONDELAY 0x00000100 /* give return on message delay */
+#define QHASNOTIFY 0x00000200 /* propagate notify parameter */
+#define QRELAYED 0x00000400 /* DSN: relayed to non-DSN aware sys */
+#define QEXPANDED 0x00000800 /* DSN: undergone list expansion */
+#define QDELIVERED 0x00001000 /* DSN: successful final delivery */
+#define QDELAYED 0x00002000 /* DSN: message delayed */
+#define QALIAS 0x00004000 /* expanded alias */
+#define QBYTRACE 0x00008000 /* DeliverBy: trace */
+#define QBYNDELAY 0x00010000 /* DeliverBy: notify, delay */
+#define QBYNRELAY 0x00020000 /* DeliverBy: notify, relayed */
+#define QTHISPASS 0x40000000 /* temp: address set this pass */
+#define QRCPTOK 0x80000000 /* recipient() processed address */
+
+#define Q_PINGFLAGS (QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY)
+
+/* values for q_state */
+#define QS_OK 0 /* address ok (for now)/not yet tried */
+#define QS_SENT 1 /* good address, delivery complete */
+#define QS_BADADDR 2 /* illegal address */
+#define QS_QUEUEUP 3 /* save address in queue */
+#define QS_RETRY 4 /* retry delivery for next MX */
+#define QS_VERIFIED 5 /* verified, but not expanded */
+
+/*
+** Notice: all of the following values are variations of QS_DONTSEND.
+** If new states are added, they must be inserted in the proper place!
+** See the macro definition of QS_IS_DEAD() down below.
+*/
+
+#define QS_DONTSEND 6 /* don't send to this address */
+#define QS_EXPANDED 7 /* expanded */
+#define QS_SENDER 8 /* message sender (MeToo) */
+#define QS_CLONED 9 /* addr cloned to split envelope */
+#define QS_DISCARDED 10 /* rcpt discarded (EF_DISCARD) */
+#define QS_REPLACED 11 /* maplocaluser()/UserDB replaced */
+#define QS_REMOVED 12 /* removed (removefromlist()) */
+#define QS_DUPLICATE 13 /* duplicate suppressed */
+#define QS_INCLUDED 14 /* :include: delivery */
+#define QS_FATALERR 15 /* fatal error, don't deliver */
+
+/* address state testing primitives */
+#define QS_IS_OK(s) ((s) == QS_OK)
+#define QS_IS_SENT(s) ((s) == QS_SENT)
+#define QS_IS_BADADDR(s) ((s) == QS_BADADDR)
+#define QS_IS_QUEUEUP(s) ((s) == QS_QUEUEUP)
+#define QS_IS_RETRY(s) ((s) == QS_RETRY)
+#define QS_IS_VERIFIED(s) ((s) == QS_VERIFIED)
+#define QS_IS_EXPANDED(s) ((s) == QS_EXPANDED)
+#define QS_IS_REMOVED(s) ((s) == QS_REMOVED)
+#define QS_IS_UNDELIVERED(s) ((s) == QS_OK || \
+ (s) == QS_QUEUEUP || \
+ (s) == QS_RETRY || \
+ (s) == QS_VERIFIED)
+#define QS_IS_UNMARKED(s) ((s) == QS_OK || \
+ (s) == QS_RETRY)
+#define QS_IS_SENDABLE(s) ((s) == QS_OK || \
+ (s) == QS_QUEUEUP || \
+ (s) == QS_RETRY)
+#define QS_IS_ATTEMPTED(s) ((s) == QS_QUEUEUP || \
+ (s) == QS_RETRY || \
+ (s) == QS_SENT)
+#define QS_IS_DEAD(s) ((s) >= QS_DONTSEND)
+
+
+#define NULLADDR ((ADDRESS *) NULL)
+
+extern ADDRESS NullAddress; /* a null (template) address [main.c] */
+
+/* functions */
+extern void cataddr __P((char **, char **, char *, int, int));
+extern char *crackaddr __P((char *));
+extern bool emptyaddr __P((ADDRESS *));
+extern ADDRESS *getctladdr __P((ADDRESS *));
+extern int include __P((char *, bool, ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern bool invalidaddr __P((char *, char *, bool));
+extern ADDRESS *parseaddr __P((char *, ADDRESS *, int, int, char **,
+ ENVELOPE *, bool));
+extern char **prescan __P((char *, int, char[], int, char **, unsigned char *));
+extern void printaddr __P((ADDRESS *, bool));
+extern ADDRESS *recipient __P((ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern char *remotename __P((char *, MAILER *, int, int *, ENVELOPE *));
+extern int rewrite __P((char **, int, int, ENVELOPE *, int));
+extern bool sameaddr __P((ADDRESS *, ADDRESS *));
+extern int sendtolist __P((char *, ADDRESS *, ADDRESS **, int, ENVELOPE *));
+#if MILTER
+extern int removefromlist __P((char *, ADDRESS **, ENVELOPE *));
+#endif /* MILTER */
+extern void setsender __P((char *, ENVELOPE *, char **, int, bool));
+
+/* macro to simplify the common call to rewrite() */
+#define REWRITE(pvp, rs, env) rewrite(pvp, rs, 0, env, MAXATOM)
+
+/*
+** Mailer definition structure.
+** Every mailer known to the system is declared in this
+** structure. It defines the pathname of the mailer, some
+** flags associated with it, and the argument vector to
+** pass to it. The flags are defined in conf.c
+**
+** The argument vector is expanded before actual use. All
+** words except the first are passed through the macro
+** processor.
+*/
+
+struct mailer
+{
+ char *m_name; /* symbolic name of this mailer */
+ char *m_mailer; /* pathname of the mailer to use */
+ char *m_mtatype; /* type of this MTA */
+ char *m_addrtype; /* type for addresses */
+ char *m_diagtype; /* type for diagnostics */
+ BITMAP256 m_flags; /* status flags, see below */
+ short m_mno; /* mailer number internally */
+ short m_nice; /* niceness to run at (mostly for prog) */
+ char **m_argv; /* template argument vector */
+ short m_sh_rwset; /* rewrite set: sender header addresses */
+ short m_se_rwset; /* rewrite set: sender envelope addresses */
+ short m_rh_rwset; /* rewrite set: recipient header addresses */
+ short m_re_rwset; /* rewrite set: recipient envelope addresses */
+ char *m_eol; /* end of line string */
+ long m_maxsize; /* size limit on message to this mailer */
+ int m_linelimit; /* max # characters per line */
+ int m_maxdeliveries; /* max deliveries per mailer connection */
+ char *m_execdir; /* directory to chdir to before execv */
+ char *m_rootdir; /* directory to chroot to before execv */
+ uid_t m_uid; /* UID to run as */
+ gid_t m_gid; /* GID to run as */
+ char *m_defcharset; /* default character set */
+ time_t m_wait; /* timeout to wait for end */
+ int m_maxrcpt; /* max recipients per envelope client-side */
+ short m_qgrp; /* queue group for this mailer */
+};
+
+/* bits for m_flags */
+#define M_ESMTP 'a' /* run Extended SMTP */
+#define M_ALIASABLE 'A' /* user can be LHS of an alias */
+#define M_BLANKEND 'b' /* ensure blank line at end of message */
+#define M_NOCOMMENT 'c' /* don't include comment part of address */
+#define M_CANONICAL 'C' /* make addresses canonical "u@dom" */
+#define M_NOBRACKET 'd' /* never angle bracket envelope route-addrs */
+ /* 'D' CF: include Date: */
+#define M_EXPENSIVE 'e' /* it costs to use this mailer.... */
+#define M_ESCFROM 'E' /* escape From lines to >From */
+#define M_FOPT 'f' /* mailer takes picky -f flag */
+ /* 'F' CF: include From: or Resent-From: */
+#define M_NO_NULL_FROM 'g' /* sender of errors should be $g */
+#define M_HST_UPPER 'h' /* preserve host case distinction */
+#define M_PREHEAD 'H' /* MAIL11V3: preview headers */
+#define M_UDBENVELOPE 'i' /* do udbsender rewriting on envelope */
+#define M_INTERNAL 'I' /* SMTP to another sendmail site */
+#define M_UDBRECIPIENT 'j' /* do udbsender rewriting on recipient lines */
+#define M_NOLOOPCHECK 'k' /* don't check for loops in HELO command */
+#define M_CHUNKING 'K' /* CHUNKING: reserved for future use */
+#define M_LOCALMAILER 'l' /* delivery is to this host */
+#define M_LIMITS 'L' /* must enforce SMTP line limits */
+#define M_MUSER 'm' /* can handle multiple users at once */
+ /* 'M' CF: include Message-Id: */
+#define M_NHDR 'n' /* don't insert From line */
+#define M_MANYSTATUS 'N' /* MAIL11V3: DATA returns multi-status */
+#define M_RUNASRCPT 'o' /* always run mailer as recipient */
+#define M_FROMPATH 'p' /* use reverse-path in MAIL FROM: */
+ /* 'P' CF: include Return-Path: */
+#define M_VRFY250 'q' /* VRFY command returns 250 instead of 252 */
+#define M_ROPT 'r' /* mailer takes picky -r flag */
+#define M_SECURE_PORT 'R' /* try to send on a reserved TCP port */
+#define M_STRIPQ 's' /* strip quote chars from user/host */
+#define M_SPECIFIC_UID 'S' /* run as specific uid/gid */
+#define M_USR_UPPER 'u' /* preserve user case distinction */
+#define M_UGLYUUCP 'U' /* this wants an ugly UUCP from line */
+#define M_CONTENT_LEN 'v' /* add Content-Length: header (SVr4) */
+ /* 'V' UIUC: !-relativize all addresses */
+#define M_HASPWENT 'w' /* check for /etc/passwd entry */
+ /* 'x' CF: include Full-Name: */
+#define M_XDOT 'X' /* use hidden-dot algorithm */
+#define M_LMTP 'z' /* run Local Mail Transport Protocol */
+#define M_DIALDELAY 'Z' /* apply dial delay sleeptime */
+#define M_NOMX '0' /* turn off MX lookups */
+#define M_NONULLS '1' /* don't send null bytes */
+#define M_FSMTP '2' /* force SMTP (no ESMTP even if offered) */
+#define M_EBCDIC '3' /* extend Q-P encoding for EBCDIC */
+#define M_TRYRULESET5 '5' /* use ruleset 5 after local aliasing */
+#define M_7BITHDRS '6' /* strip headers to 7 bits even in 8 bit path */
+#define M_7BITS '7' /* use 7-bit path */
+#define M_8BITS '8' /* force "just send 8" behaviour */
+#define M_MAKE8BIT '9' /* convert 7 -> 8 bit if appropriate */
+#define M_CHECKINCLUDE ':' /* check for :include: files */
+#define M_CHECKPROG '|' /* check for |program addresses */
+#define M_CHECKFILE '/' /* check for /file addresses */
+#define M_CHECKUDB '@' /* user can be user database key */
+#define M_CHECKHDIR '~' /* SGI: check for valid home directory */
+#define M_HOLD '%' /* Hold delivery until ETRN/-qI/-qR/-qS */
+#define M_PLUS '+' /* Reserved: Used in mc for adding new flags */
+#define M_MINUS '-' /* Reserved: Used in mc for removing flags */
+
+/* functions */
+extern void initerrmailers __P((void));
+extern void makemailer __P((char *));
+extern void makequeue __P((char *, bool));
+extern void runqueueevent __P((void));
+#if _FFR_QUEUE_RUN_PARANOIA
+extern bool checkqueuerunner __P((void));
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+
+EXTERN MAILER *FileMailer; /* ptr to *file* mailer */
+EXTERN MAILER *InclMailer; /* ptr to *include* mailer */
+EXTERN MAILER *LocalMailer; /* ptr to local mailer */
+EXTERN MAILER *ProgMailer; /* ptr to program mailer */
+EXTERN MAILER *Mailer[MAXMAILERS + 1];
+
+/*
+** Queue group definition structure.
+** Every queue group known to the system is declared in this structure.
+** It defines the basic pathname of the queue group, some flags
+** associated with it, and the argument vector to pass to it.
+*/
+
+struct qpaths_s
+{
+ char *qp_name; /* name of queue dir, relative path */
+ short qp_subdirs; /* use subdirs? */
+ short qp_fsysidx; /* file system index of this directory */
+# if SM_CONF_SHM
+ int qp_idx; /* index into array for queue information */
+# endif /* SM_CONF_SHM */
+};
+
+typedef struct qpaths_s QPATHS;
+
+struct queuegrp
+{
+ char *qg_name; /* symbolic name of this queue group */
+
+ /*
+ ** For now this is the same across all queue groups.
+ ** Otherwise we have to play around with chdir().
+ */
+
+ char *qg_qdir; /* common component of queue directory */
+ short qg_index; /* queue number internally, index in Queue[] */
+ int qg_maxqrun; /* max # of jobs in 1 queuerun */
+ int qg_numqueues; /* number of queues in this queue */
+
+ /*
+ ** qg_queueintvl == 0 denotes that no individual value is used.
+ ** Whatever accesses this must deal with "<= 0" as
+ ** "not set, use appropriate default".
+ */
+
+ time_t qg_queueintvl; /* interval for queue runs */
+ QPATHS *qg_qpaths; /* list of queue directories */
+ BITMAP256 qg_flags; /* status flags, see below */
+ short qg_nice; /* niceness for queue run */
+ int qg_wgrp; /* Assigned to this work group */
+ int qg_maxlist; /* max items in work queue for this group */
+ int qg_curnum; /* current number of queue for queue runs */
+ int qg_maxrcpt; /* max recipients per envelope, 0==no limit */
+
+ time_t qg_nextrun; /* time for next queue runs */
+#if _FFR_QUEUE_GROUP_SORTORDER
+ short qg_sortorder; /* how do we sort this queuerun */
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+#if 0
+ long qg_wkrcptfact; /* multiplier for # recipients -> priority */
+ long qg_qfactor; /* slope of queue function */
+ bool qg_doqueuerun; /* XXX flag is it time to do a queuerun */
+#endif /* 0 */
+};
+
+/* bits for qg_flags (XXX: unused as of now) */
+#define QD_DEFINED ((char) 1) /* queue group has been defined */
+#define QD_FORK 'f' /* fork queue runs */
+
+extern void filesys_update __P((void));
+#if _FFR_ANY_FREE_FS
+extern bool filesys_free __P((long));
+#endif /* _FFR_ANY_FREE_FS */
+
+#if SASL
+/*
+** SASL
+*/
+
+/* lines in authinfo file or index into SASL_AI_T */
+# define SASL_WRONG (-1)
+# define SASL_USER 0 /* authorization id (user) */
+# define SASL_AUTHID 1 /* authentication id */
+# define SASL_PASSWORD 2 /* password fuer authid */
+# define SASL_DEFREALM 3 /* realm to use */
+# define SASL_MECHLIST 4 /* list of mechanisms to try */
+# define SASL_ID_REALM 5 /* authid@defrealm */
+
+/*
+** Current mechanism; this is just used to convey information between
+** invocation of SASL callback functions.
+** It must be last in the list, because it's not allocated by us
+** and hence we don't free() it.
+*/
+# define SASL_MECH 6
+# define SASL_ENTRIES 7 /* number of entries in array */
+
+# define SASL_USER_BIT (1 << SASL_USER)
+# define SASL_AUTHID_BIT (1 << SASL_AUTHID)
+# define SASL_PASSWORD_BIT (1 << SASL_PASSWORD)
+# define SASL_DEFREALM_BIT (1 << SASL_DEFREALM)
+# define SASL_MECHLIST_BIT (1 << SASL_MECHLIST)
+
+/* authenticated? */
+# define SASL_NOT_AUTH 0 /* not authenticated */
+# define SASL_PROC_AUTH 1 /* in process of authenticating */
+# define SASL_IS_AUTH 2 /* authenticated */
+
+/* SASL options */
+# define SASL_AUTH_AUTH 0x1000 /* use auth= only if authenticated */
+# if SASL >= 20101
+# define SASL_SEC_MASK SASL_SEC_MAXIMUM /* mask for SASL_SEC_* values: sasl.h */
+# else /* SASL >= 20101 */
+# define SASL_SEC_MASK 0x0fff /* mask for SASL_SEC_* values: sasl.h */
+# if (SASL_SEC_NOPLAINTEXT & SASL_SEC_MASK) == 0 || \
+ (SASL_SEC_NOACTIVE & SASL_SEC_MASK) == 0 || \
+ (SASL_SEC_NODICTIONARY & SASL_SEC_MASK) == 0 || \
+ (SASL_SEC_FORWARD_SECRECY & SASL_SEC_MASK) == 0 || \
+ (SASL_SEC_NOANONYMOUS & SASL_SEC_MASK) == 0 || \
+ (SASL_SEC_PASS_CREDENTIALS & SASL_SEC_MASK) == 0
+ERROR: change SASL_SEC_MASK_ notify sendmail.org!
+# endif /* SASL_SEC_NOPLAINTEXT & SASL_SEC_MASK) == 0 ... */
+# endif /* SASL >= 20101 */
+# define MAXOUTLEN 1024 /* length of output buffer */
+
+/* functions */
+extern char *intersect __P((char *, char *, SM_RPOOL_T *));
+extern char *iteminlist __P((char *, char *, char *));
+# if SASL >= 20000
+extern int proxy_policy __P((sasl_conn_t *, void *, const char *, unsigned, const char *, unsigned, const char *, unsigned, struct propctx *));
+extern int safesaslfile __P((void *, const char *, sasl_verify_type_t));
+# else /* SASL >= 20000 */
+extern int proxy_policy __P((void *, const char *, const char *, const char **, const char **));
+# if SASL > 10515
+extern int safesaslfile __P((void *, char *, int));
+# else /* SASL > 10515 */
+extern int safesaslfile __P((void *, char *));
+# endif /* SASL > 10515 */
+# endif /* SASL >= 20000 */
+extern void stop_sasl_client __P((void));
+
+/* structure to store authinfo */
+typedef char *SASL_AI_T[SASL_ENTRIES];
+
+EXTERN char *AuthMechanisms; /* AUTH mechanisms */
+EXTERN char *SASLInfo; /* file with AUTH info */
+EXTERN int SASLOpts; /* options for SASL */
+EXTERN int MaxSLBits; /* max. encryption bits for SASL */
+#endif /* SASL */
+
+/*
+** Structure to store macros.
+*/
+typedef struct
+{
+ SM_RPOOL_T *mac_rpool; /* resource pool */
+ BITMAP256 mac_allocated; /* storage has been alloc()? */
+ char *mac_table[MAXMACROID + 1]; /* macros */
+} MACROS_T;
+
+EXTERN MACROS_T GlobalMacros;
+
+/*
+** Information about currently open connections to mailers, or to
+** hosts that we have looked up recently.
+*/
+
+#define MCI struct mailer_con_info
+
+MCI
+{
+ unsigned long mci_flags; /* flag bits, see below */
+ short mci_errno; /* error number on last connection */
+ short mci_herrno; /* h_errno from last DNS lookup */
+ short mci_exitstat; /* exit status from last connection */
+ short mci_state; /* SMTP state */
+ int mci_deliveries; /* delivery attempts for connection */
+ long mci_maxsize; /* max size this server will accept */
+ SM_FILE_T *mci_in; /* input side of connection */
+ SM_FILE_T *mci_out; /* output side of connection */
+ pid_t mci_pid; /* process id of subordinate proc */
+ char *mci_phase; /* SMTP phase string */
+ struct mailer *mci_mailer; /* ptr to the mailer for this conn */
+ char *mci_host; /* host name */
+ char *mci_status; /* DSN status to be copied to addrs */
+ char *mci_rstatus; /* SMTP status to be copied to addrs */
+ time_t mci_lastuse; /* last usage time */
+ SM_FILE_T *mci_statfile; /* long term status file */
+ char *mci_heloname; /* name to use as HELO arg */
+ long mci_min_by; /* minimum DELIVERBY */
+ bool mci_retryrcpt; /* tempfail for at least one rcpt */
+ char *mci_tolist; /* list of valid recipients */
+ SM_RPOOL_T *mci_rpool; /* resource pool */
+#if PIPELINING
+ int mci_okrcpts; /* number of valid recipients */
+ ADDRESS *mci_nextaddr; /* next address for pipelined status */
+#endif /* PIPELINING */
+#if SASL
+ SASL_AI_T mci_sai; /* authentication info */
+ bool mci_sasl_auth; /* authenticated? */
+ int mci_sasl_string_len;
+ char *mci_sasl_string; /* sasl reply string */
+ char *mci_saslcap; /* SASL list of mechanisms */
+ sasl_conn_t *mci_conn; /* SASL connection */
+#endif /* SASL */
+#if STARTTLS
+ SSL *mci_ssl; /* SSL connection */
+#endif /* STARTTLS */
+ MACROS_T mci_macro; /* macro definitions */
+};
+
+
+/* flag bits */
+#define MCIF_VALID 0x00000001 /* this entry is valid */
+/* 0x00000002 unused, was MCIF_TEMP */
+#define MCIF_CACHED 0x00000004 /* currently in open cache */
+#define MCIF_ESMTP 0x00000008 /* this host speaks ESMTP */
+#define MCIF_EXPN 0x00000010 /* EXPN command supported */
+#define MCIF_SIZE 0x00000020 /* SIZE option supported */
+#define MCIF_8BITMIME 0x00000040 /* BODY=8BITMIME supported */
+#define MCIF_7BIT 0x00000080 /* strip this message to 7 bits */
+/* 0x00000100 unused, was MCIF_MULTSTAT: MAIL11V3: handles MULT status */
+#define MCIF_INHEADER 0x00000200 /* currently outputing header */
+#define MCIF_CVT8TO7 0x00000400 /* convert from 8 to 7 bits */
+#define MCIF_DSN 0x00000800 /* DSN extension supported */
+#define MCIF_8BITOK 0x00001000 /* OK to send 8 bit characters */
+#define MCIF_CVT7TO8 0x00002000 /* convert from 7 to 8 bits */
+#define MCIF_INMIME 0x00004000 /* currently reading MIME header */
+#define MCIF_AUTH 0x00008000 /* AUTH= supported */
+#define MCIF_AUTHACT 0x00010000 /* SASL (AUTH) active */
+#define MCIF_ENHSTAT 0x00020000 /* ENHANCEDSTATUSCODES supported */
+#define MCIF_PIPELINED 0x00040000 /* PIPELINING supported */
+#if STARTTLS
+#define MCIF_TLS 0x00100000 /* STARTTLS supported */
+#define MCIF_TLSACT 0x00200000 /* STARTTLS active */
+#define MCIF_EXTENS (MCIF_EXPN | MCIF_SIZE | MCIF_8BITMIME | MCIF_DSN | MCIF_8BITOK | MCIF_AUTH | MCIF_ENHSTAT | MCIF_TLS)
+#else /* STARTTLS */
+#define MCIF_EXTENS (MCIF_EXPN | MCIF_SIZE | MCIF_8BITMIME | MCIF_DSN | MCIF_8BITOK | MCIF_AUTH | MCIF_ENHSTAT)
+#endif /* STARTTLS */
+#define MCIF_DLVR_BY 0x00400000 /* DELIVERBY */
+#if _FFR_IGNORE_EXT_ON_HELO
+# define MCIF_HELO 0x00800000 /* we used HELO: ignore extensions */
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+#define MCIF_ONLY_EHLO 0x10000000 /* use only EHLO in smtpinit */
+
+/* states */
+#define MCIS_CLOSED 0 /* no traffic on this connection */
+#define MCIS_OPENING 1 /* sending initial protocol */
+#define MCIS_OPEN 2 /* open, initial protocol sent */
+#define MCIS_MAIL 3 /* MAIL command sent */
+#define MCIS_RCPT 4 /* RCPT commands being sent */
+#define MCIS_DATA 5 /* DATA command sent */
+#define MCIS_QUITING 6 /* running quit protocol */
+#define MCIS_SSD 7 /* service shutting down */
+#define MCIS_ERROR 8 /* I/O error on connection */
+
+/* functions */
+extern void mci_cache __P((MCI *));
+extern void mci_dump __P((MCI *, bool));
+extern void mci_dump_all __P((bool));
+extern void mci_flush __P((bool, MCI *));
+extern MCI *mci_get __P((char *, MAILER *));
+extern int mci_lock_host __P((MCI *));
+extern bool mci_match __P((char *, MAILER *));
+extern int mci_print_persistent __P((char *, char *));
+extern int mci_purge_persistent __P((char *, char *));
+extern MCI **mci_scan __P((MCI *));
+extern void mci_setstat __P((MCI *, int, char *, char *));
+extern void mci_store_persistent __P((MCI *));
+extern int mci_traverse_persistent __P((int (*)(), char *));
+extern void mci_unlock_host __P((MCI *));
+
+EXTERN int MaxMciCache; /* maximum entries in MCI cache */
+EXTERN time_t MciCacheTimeout; /* maximum idle time on connections */
+EXTERN time_t MciInfoTimeout; /* how long 'til we retry down hosts */
+
+/*
+** Header structure.
+** This structure is used internally to store header items.
+*/
+
+struct header
+{
+ char *h_field; /* the name of the field */
+ char *h_value; /* the value of that field */
+ struct header *h_link; /* the next header */
+ unsigned char h_macro; /* include header if macro defined */
+ unsigned long h_flags; /* status bits, see below */
+ BITMAP256 h_mflags; /* m_flags bits needed */
+};
+
+typedef struct header HDR;
+
+/*
+** Header information structure.
+** Defined in conf.c, this struct declares the header fields
+** that have some magic meaning.
+*/
+
+struct hdrinfo
+{
+ char *hi_field; /* the name of the field */
+ unsigned long hi_flags; /* status bits, see below */
+ char *hi_ruleset; /* validity check ruleset */
+};
+
+extern struct hdrinfo HdrInfo[];
+
+/* bits for h_flags and hi_flags */
+#define H_EOH 0x00000001 /* field terminates header */
+#define H_RCPT 0x00000002 /* contains recipient addresses */
+#define H_DEFAULT 0x00000004 /* if another value is found, drop this */
+#define H_RESENT 0x00000008 /* this address is a "Resent-..." address */
+#define H_CHECK 0x00000010 /* check h_mflags against m_flags */
+#define H_ACHECK 0x00000020 /* ditto, but always (not just default) */
+#define H_FORCE 0x00000040 /* force this field, even if default */
+#define H_TRACE 0x00000080 /* this field contains trace information */
+#define H_FROM 0x00000100 /* this is a from-type field */
+#define H_VALID 0x00000200 /* this field has a validated value */
+#define H_RECEIPTTO 0x00000400 /* field has return receipt info */
+#define H_ERRORSTO 0x00000800 /* field has error address info */
+#define H_CTE 0x00001000 /* field is a content-transfer-encoding */
+#define H_CTYPE 0x00002000 /* this is a content-type field */
+#define H_BCC 0x00004000 /* Bcc: header: strip value or delete */
+#define H_ENCODABLE 0x00008000 /* field can be RFC 1522 encoded */
+#define H_STRIPCOMM 0x00010000 /* header check: strip comments */
+#define H_BINDLATE 0x00020000 /* only expand macros at deliver */
+#define H_USER 0x00040000 /* header came from the user/SMTP */
+
+/* bits for chompheader() */
+#define CHHDR_DEF 0x0001 /* default header */
+#define CHHDR_CHECK 0x0002 /* call ruleset for header */
+#define CHHDR_USER 0x0004 /* header from user */
+#define CHHDR_QUEUE 0x0008 /* header from queue file */
+
+/* functions */
+extern void addheader __P((char *, char *, int, ENVELOPE *));
+extern unsigned long chompheader __P((char *, int, HDR **, ENVELOPE *));
+extern void commaize __P((HDR *, char *, bool, MCI *, ENVELOPE *));
+extern HDR *copyheader __P((HDR *, SM_RPOOL_T *));
+extern void eatheader __P((ENVELOPE *, bool, bool));
+extern char *hvalue __P((char *, HDR *));
+extern bool isheader __P((char *));
+extern void putfromline __P((MCI *, ENVELOPE *));
+extern void setupheaders __P((void));
+
+/*
+** Performance monitoring
+*/
+
+#define TIMERS struct sm_timers
+
+TIMERS
+{
+ TIMER ti_overall; /* the whole process */
+};
+
+
+#define PUSHTIMER(l, t) { if (tTd(98, l)) pushtimer(&t); }
+#define POPTIMER(l, t) { if (tTd(98, l)) poptimer(&t); }
+
+/*
+** Envelope structure.
+** This structure defines the message itself. There is usually
+** only one of these -- for the message that we originally read
+** and which is our primary interest -- but other envelopes can
+** be generated during processing. For example, error messages
+** will have their own envelope.
+*/
+
+struct envelope
+{
+ HDR *e_header; /* head of header list */
+ long e_msgpriority; /* adjusted priority of this message */
+ time_t e_ctime; /* time message appeared in the queue */
+ char *e_to; /* (list of) target person(s) */
+ ADDRESS e_from; /* the person it is from */
+ char *e_sender; /* e_from.q_paddr w comments stripped */
+ char **e_fromdomain; /* the domain part of the sender */
+ ADDRESS *e_sendqueue; /* list of message recipients */
+ ADDRESS *e_errorqueue; /* the queue for error responses */
+
+ /*
+ ** Overflow detection is based on < 0, so don't change this
+ ** to unsigned. We don't use unsigned and == ULONG_MAX because
+ ** some libc's don't have strtoul(), see mail_esmtp_args().
+ */
+
+ long e_msgsize; /* size of the message in bytes */
+ char *e_msgid; /* message id (for logging) */
+ unsigned long e_flags; /* flags, see below */
+ int e_nrcpts; /* number of recipients */
+ short e_class; /* msg class (priority, junk, etc.) */
+ short e_hopcount; /* number of times processed */
+ short e_nsent; /* number of sends since checkpoint */
+ short e_sendmode; /* message send mode */
+ short e_errormode; /* error return mode */
+ short e_timeoutclass; /* message timeout class */
+ void (*e_puthdr)__P((MCI *, HDR *, ENVELOPE *, int));
+ /* function to put header of message */
+ void (*e_putbody)__P((MCI *, ENVELOPE *, char *));
+ /* function to put body of message */
+ ENVELOPE *e_parent; /* the message this one encloses */
+ ENVELOPE *e_sibling; /* the next envelope of interest */
+ char *e_bodytype; /* type of message body */
+ SM_FILE_T *e_dfp; /* data file */
+ char *e_id; /* code for this entry in queue */
+ int e_qgrp; /* queue group (index into queues) */
+ int e_qdir; /* index into queue directories */
+ int e_dfqgrp; /* data file queue group index */
+ int e_dfqdir; /* data file queue directory index */
+ int e_xfqgrp; /* queue group (index into queues) */
+ int e_xfqdir; /* index into queue directories (xf) */
+ SM_FILE_T *e_xfp; /* transcript file */
+ SM_FILE_T *e_lockfp; /* the lock file for this message */
+ char *e_message; /* error message; readonly; NULL, or
+ * static storage, or allocated from
+ * e_rpool */
+ char *e_statmsg; /* stat msg (changes per delivery).
+ * readonly. NULL or allocated from
+ * e_rpool. */
+#if _FFR_QUARANTINE
+ char *e_quarmsg; /* why envelope is quarantined */
+ char e_qfletter; /* queue file letter on disk */
+#endif /* _FFR_QUARANTINE */
+ char *e_msgboundary; /* MIME-style message part boundary */
+ char *e_origrcpt; /* original recipient (one only) */
+ char *e_envid; /* envelope id from MAIL FROM: line */
+ char *e_status; /* DSN status for this message */
+ time_t e_dtime; /* time of last delivery attempt */
+ int e_ntries; /* number of delivery attempts */
+ dev_t e_dfdev; /* data file device (crash recovery) */
+ ino_t e_dfino; /* data file inode (crash recovery) */
+ MACROS_T e_macro; /* macro definitions */
+ MCI *e_mci; /* connection info */
+ char *e_auth_param; /* readonly; NULL or static storage or
+ * allocated from e_rpool */
+ TIMERS e_timers; /* per job timers */
+#if _FFR_QUEUEDELAY
+ int e_queuealg; /* algorithm for queue delay */
+ time_t e_queuedelay; /* current delay */
+#endif /* _FFR_QUEUEDELAY */
+ long e_deliver_by; /* deliver by */
+ int e_dlvr_flag; /* deliver by flag */
+ SM_RPOOL_T *e_rpool; /* resource pool for this envelope */
+};
+
+/* values for e_flags */
+#define EF_OLDSTYLE 0x00000001L /* use spaces (not commas) in hdrs */
+#define EF_INQUEUE 0x00000002L /* this message is fully queued */
+#define EF_NO_BODY_RETN 0x00000004L /* omit message body on error */
+#define EF_CLRQUEUE 0x00000008L /* disk copy is no longer needed */
+#define EF_SENDRECEIPT 0x00000010L /* send a return receipt */
+#define EF_FATALERRS 0x00000020L /* fatal errors occurred */
+#define EF_DELETE_BCC 0x00000040L /* delete Bcc: headers entirely */
+#define EF_RESPONSE 0x00000080L /* this is an error or return receipt */
+#define EF_RESENT 0x00000100L /* this message is being forwarded */
+#define EF_VRFYONLY 0x00000200L /* verify only (don't expand aliases) */
+#define EF_WARNING 0x00000400L /* warning message has been sent */
+#define EF_QUEUERUN 0x00000800L /* this envelope is from queue */
+#define EF_GLOBALERRS 0x00001000L /* treat errors as global */
+#define EF_PM_NOTIFY 0x00002000L /* send return mail to postmaster */
+#define EF_METOO 0x00004000L /* send to me too */
+#define EF_LOGSENDER 0x00008000L /* need to log the sender */
+#define EF_NORECEIPT 0x00010000L /* suppress all return-receipts */
+#define EF_HAS8BIT 0x00020000L /* at least one 8-bit char in body */
+#define EF_NL_NOT_EOL 0x00040000L /* don't accept raw NL as EOLine */
+#define EF_CRLF_NOT_EOL 0x00080000L /* don't accept CR-LF as EOLine */
+#define EF_RET_PARAM 0x00100000L /* RCPT command had RET argument */
+#define EF_HAS_DF 0x00200000L /* set when data file is instantiated */
+#define EF_IS_MIME 0x00400000L /* really is a MIME message */
+#define EF_DONT_MIME 0x00800000L /* never MIME this message */
+#define EF_DISCARD 0x01000000L /* discard the message */
+#define EF_TOOBIG 0x02000000L /* message is too big */
+#define EF_SPLIT 0x04000000L /* envelope has been split */
+#define EF_UNSAFE 0x08000000L /* unsafe: read from untrusted source */
+
+#define DLVR_NOTIFY 0x01
+#define DLVR_RETURN 0x02
+#define DLVR_TRACE 0x10
+#define IS_DLVR_NOTIFY(e) (((e)->e_dlvr_flag & DLVR_NOTIFY) != 0)
+#define IS_DLVR_RETURN(e) (((e)->e_dlvr_flag & DLVR_RETURN) != 0)
+#define IS_DLVR_TRACE(e) (((e)->e_dlvr_flag & DLVR_TRACE) != 0)
+#define IS_DLVR_BY(e) ((e)->e_dlvr_flag != 0)
+
+#define BODYTYPE_NONE (0)
+#define BODYTYPE_7BIT (1)
+#define BODYTYPE_8BITMIME (2)
+#define BODYTYPE_ILLEGAL (-1)
+#define BODYTYPE_VALID(b) ((b) == BODYTYPE_7BIT || (b) == BODYTYPE_8BITMIME)
+
+extern ENVELOPE BlankEnvelope;
+
+/* functions */
+extern void clearenvelope __P((ENVELOPE *, bool, SM_RPOOL_T *));
+extern void dropenvelope __P((ENVELOPE *, bool, bool));
+extern ENVELOPE *newenvelope __P((ENVELOPE *, ENVELOPE *, SM_RPOOL_T *));
+extern void printenvflags __P((ENVELOPE *));
+extern void putbody __P((MCI *, ENVELOPE *, char *));
+extern void putheader __P((MCI *, HDR *, ENVELOPE *, int));
+
+/*
+** Message priority classes.
+**
+** The message class is read directly from the Priority: header
+** field in the message.
+**
+** CurEnv->e_msgpriority is the number of bytes in the message plus
+** the creation time (so that jobs ``tend'' to be ordered correctly),
+** adjusted by the message class, the number of recipients, and the
+** amount of time the message has been sitting around. This number
+** is used to order the queue. Higher values mean LOWER priority.
+**
+** Each priority class point is worth WkClassFact priority points;
+** each recipient is worth WkRecipFact priority points. Each time
+** we reprocess a message the priority is adjusted by WkTimeFact.
+** WkTimeFact should normally decrease the priority so that jobs
+** that have historically failed will be run later; thanks go to
+** Jay Lepreau at Utah for pointing out the error in my thinking.
+**
+** The "class" is this number, unadjusted by the age or size of
+** this message. Classes with negative representations will have
+** error messages thrown away if they are not local.
+*/
+
+struct priority
+{
+ char *pri_name; /* external name of priority */
+ int pri_val; /* internal value for same */
+};
+
+EXTERN int NumPriorities; /* pointer into Priorities */
+EXTERN struct priority Priorities[MAXPRIORITIES];
+
+/*
+** Rewrite rules.
+*/
+
+struct rewrite
+{
+ char **r_lhs; /* pattern match */
+ char **r_rhs; /* substitution value */
+ struct rewrite *r_next;/* next in chain */
+ int r_line; /* rule line in sendmail.cf */
+};
+
+/*
+** Special characters in rewriting rules.
+** These are used internally only.
+** The COND* rules are actually used in macros rather than in
+** rewriting rules, but are given here because they
+** cannot conflict.
+*/
+
+/* left hand side items */
+#define MATCHZANY ((unsigned char)0220) /* match zero or more tokens */
+#define MATCHANY ((unsigned char)0221) /* match one or more tokens */
+#define MATCHONE ((unsigned char)0222) /* match exactly one token */
+#define MATCHCLASS ((unsigned char)0223) /* match one token in a class */
+#define MATCHNCLASS ((unsigned char)0224) /* match anything not in class */
+#define MATCHREPL ((unsigned char)0225) /* replacement on RHS for above */
+
+/* right hand side items */
+#define CANONNET ((unsigned char)0226) /* canonical net, next token */
+#define CANONHOST ((unsigned char)0227) /* canonical host, next token */
+#define CANONUSER ((unsigned char)0230) /* canonical user, next N tokens */
+#define CALLSUBR ((unsigned char)0231) /* call another rewriting set */
+
+/* conditionals in macros */
+#define CONDIF ((unsigned char)0232) /* conditional if-then */
+#define CONDELSE ((unsigned char)0233) /* conditional else */
+#define CONDFI ((unsigned char)0234) /* conditional fi */
+
+/* bracket characters for host name lookup */
+#define HOSTBEGIN ((unsigned char)0235) /* hostname lookup begin */
+#define HOSTEND ((unsigned char)0236) /* hostname lookup end */
+
+/* bracket characters for generalized lookup */
+#define LOOKUPBEGIN ((unsigned char)0205) /* generalized lookup begin */
+#define LOOKUPEND ((unsigned char)0206) /* generalized lookup end */
+
+/* macro substitution character */
+#define MACROEXPAND ((unsigned char)0201) /* macro expansion */
+#define MACRODEXPAND ((unsigned char)0202) /* deferred macro expansion */
+
+/* to make the code clearer */
+#define MATCHZERO CANONHOST
+
+#define MAXMATCH 9 /* max params per rewrite */
+
+/* external <==> internal mapping table */
+struct metamac
+{
+ char metaname; /* external code (after $) */
+ unsigned char metaval; /* internal code (as above) */
+};
+
+/* values for macros with external names only */
+#define MID_OPMODE 0202 /* operation mode */
+
+/* functions */
+#if SM_HEAP_CHECK
+extern void
+macdefine_tagged __P((
+ MACROS_T *_mac,
+ ARGCLASS_T _vclass,
+ int _id,
+ char *_value,
+ char *_file,
+ int _line,
+ int _group));
+# define macdefine(mac,c,id,v) \
+ macdefine_tagged(mac,c,id,v,__FILE__,__LINE__,sm_heap_group())
+#else /* SM_HEAP_CHECK */
+extern void
+macdefine __P((
+ MACROS_T *_mac,
+ ARGCLASS_T _vclass,
+ int _id,
+ char *_value));
+# define macdefine_tagged(mac,c,id,v,file,line,grp) macdefine(mac,c,id,v)
+#endif /* SM_HEAP_CHECK */
+extern void macset __P((MACROS_T *, int, char *));
+#define macget(mac, i) (mac)->mac_table[i]
+extern void expand __P((char *, char *, size_t, ENVELOPE *));
+extern int macid_parse __P((char *, char **));
+#define macid(name) macid_parse(name, NULL)
+extern char *macname __P((int));
+extern char *macvalue __P((int, ENVELOPE *));
+extern int rscheck __P((char *, char *, char *, ENVELOPE *, int, int, char *, char *));
+extern int rscap __P((char *, char *, char *, ENVELOPE *, char ***, char *, int));
+extern void setclass __P((int, char *));
+extern int strtorwset __P((char *, char **, int));
+extern void translate_dollars __P((char *));
+extern bool wordinclass __P((char *, int));
+
+/*
+** Name canonification short circuit.
+**
+** If the name server for a host is down, the process of trying to
+** canonify the name can hang. This is similar to (but alas, not
+** identical to) looking up the name for delivery. This stab type
+** caches the result of the name server lookup so we don't hang
+** multiple times.
+*/
+
+#define NAMECANON struct _namecanon
+
+NAMECANON
+{
+ short nc_errno; /* cached errno */
+ short nc_herrno; /* cached h_errno */
+ short nc_stat; /* cached exit status code */
+ short nc_flags; /* flag bits */
+ char *nc_cname; /* the canonical name */
+ time_t nc_exp; /* entry expires at */
+};
+
+/* values for nc_flags */
+#define NCF_VALID 0x0001 /* entry valid */
+
+/* hostsignature structure */
+
+struct hostsig_t
+{
+ char *hs_sig; /* hostsignature */
+ time_t hs_exp; /* entry expires at */
+};
+
+typedef struct hostsig_t HOSTSIG_T;
+
+/* functions */
+extern bool getcanonname __P((char *, int, bool, int *));
+extern int getmxrr __P((char *, char **, unsigned short *, bool, int *, bool, int *));
+extern char *hostsignature __P((MAILER *, char *));
+extern int getfallbackmxrr __P((char *));
+
+/*
+** Mapping functions
+**
+** These allow arbitrary mappings in the config file. The idea
+** (albeit not the implementation) comes from IDA sendmail.
+*/
+
+#define MAPCLASS struct _mapclass
+#define MAP struct _map
+#define MAXMAPACTIONS 5 /* size of map_actions array */
+
+
+/*
+** An actual map.
+*/
+
+MAP
+{
+ MAPCLASS *map_class; /* the class of this map */
+ MAPCLASS *map_orgclass; /* the original class of this map */
+ char *map_mname; /* name of this map */
+ long map_mflags; /* flags, see below */
+ char *map_file; /* the (nominal) filename */
+ ARBPTR_T map_db1; /* the open database ptr */
+ ARBPTR_T map_db2; /* an "extra" database pointer */
+ char *map_keycolnm; /* key column name */
+ char *map_valcolnm; /* value column name */
+ unsigned char map_keycolno; /* key column number */
+ unsigned char map_valcolno; /* value column number */
+ char map_coldelim; /* column delimiter */
+ char map_spacesub; /* spacesub */
+ char *map_app; /* to append to successful matches */
+ char *map_tapp; /* to append to "tempfail" matches */
+ char *map_domain; /* the (nominal) NIS domain */
+ char *map_rebuild; /* program to run to do auto-rebuild */
+ time_t map_mtime; /* last database modification time */
+ time_t map_timeout; /* timeout for map accesses */
+ int map_retry; /* # of retries for map accesses */
+ pid_t map_pid; /* PID of process which opened map */
+ int map_lockfd; /* auxiliary lock file descriptor */
+ short map_specificity; /* specificity of aliases */
+ MAP *map_stack[MAXMAPSTACK]; /* list for stacked maps */
+ short map_return[MAXMAPACTIONS]; /* return bitmaps for stacked maps */
+};
+
+
+/* bit values for map_mflags */
+#define MF_VALID 0x00000001 /* this entry is valid */
+#define MF_INCLNULL 0x00000002 /* include null byte in key */
+#define MF_OPTIONAL 0x00000004 /* don't complain if map not found */
+#define MF_NOFOLDCASE 0x00000008 /* don't fold case in keys */
+#define MF_MATCHONLY 0x00000010 /* don't use the map value */
+#define MF_OPEN 0x00000020 /* this entry is open */
+#define MF_WRITABLE 0x00000040 /* open for writing */
+#define MF_ALIAS 0x00000080 /* this is an alias file */
+#define MF_TRY0NULL 0x00000100 /* try with no null byte */
+#define MF_TRY1NULL 0x00000200 /* try with the null byte */
+#define MF_LOCKED 0x00000400 /* this map is currently locked */
+#define MF_ALIASWAIT 0x00000800 /* alias map in aliaswait state */
+#define MF_IMPL_HASH 0x00001000 /* implicit: underlying hash database */
+#define MF_IMPL_NDBM 0x00002000 /* implicit: underlying NDBM database */
+/* 0x00004000 */
+#define MF_APPEND 0x00008000 /* append new entry on rebuild */
+#define MF_KEEPQUOTES 0x00010000 /* don't dequote key before lookup */
+#define MF_NODEFER 0x00020000 /* don't defer if map lookup fails */
+#define MF_REGEX_NOT 0x00040000 /* regular expression negation */
+#define MF_DEFER 0x00080000 /* don't lookup map in defer mode */
+#define MF_SINGLEMATCH 0x00100000 /* successful only if match one key */
+/* 0x00200000 available for use */
+#define MF_FILECLASS 0x00400000 /* this is a file class map */
+#define MF_OPENBOGUS 0x00800000 /* open failed, don't call map_close */
+#define MF_CLOSING 0x01000000 /* map is being closed */
+
+#define DYNOPENMAP(map) if (!bitset(MF_OPEN, (map)->map_mflags)) \
+ { \
+ if (!openmap(map)) \
+ return NULL; \
+ }
+
+
+/* indices for map_actions */
+#define MA_NOTFOUND 0 /* member map returned "not found" */
+#define MA_UNAVAIL 1 /* member map is not available */
+#define MA_TRYAGAIN 2 /* member map returns temp failure */
+
+/* macros to handle MapTempFail */
+#define BIT_IS_MTP 0x01 /* temp.failure occurred */
+#define BIT_ASK_MTP 0x02 /* do we care about MapTempFail? */
+#define RESET_MAPTEMPFAIL MapTempFail = 0
+#define INIT_MAPTEMPFAIL MapTempFail = BIT_ASK_MTP
+#define SET_MAPTEMPFAIL MapTempFail |= BIT_IS_MTP
+#define IS_MAPTEMPFAIL bitset(BIT_IS_MTP, MapTempFail)
+#define ASK_MAPTEMPFAIL bitset(BIT_ASK_MTP, MapTempFail)
+
+/*
+** The class of a map -- essentially the functions to call
+*/
+
+MAPCLASS
+{
+ char *map_cname; /* name of this map class */
+ char *map_ext; /* extension for database file */
+ short map_cflags; /* flag bits, see below */
+ bool (*map_parse)__P((MAP *, char *));
+ /* argument parsing function */
+ char *(*map_lookup)__P((MAP *, char *, char **, int *));
+ /* lookup function */
+ void (*map_store)__P((MAP *, char *, char *));
+ /* store function */
+ bool (*map_open)__P((MAP *, int));
+ /* open function */
+ void (*map_close)__P((MAP *));
+ /* close function */
+};
+
+/* bit values for map_cflags */
+#define MCF_ALIASOK 0x0001 /* can be used for aliases */
+#define MCF_ALIASONLY 0x0002 /* usable only for aliases */
+#define MCF_REBUILDABLE 0x0004 /* can rebuild alias files */
+#define MCF_OPTFILE 0x0008 /* file name is optional */
+#define MCF_NOTPERSIST 0x0010 /* don't keep map open all the time */
+
+/* functions */
+extern void closemaps __P((bool));
+extern bool impl_map_open __P((MAP *, int));
+extern void initmaps __P((void));
+extern MAP *makemapentry __P((char *));
+extern void maplocaluser __P((ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern char *map_rewrite __P((MAP *, const char *, size_t, char **));
+#if NETINFO
+extern char *ni_propval __P((char *, char *, char *, char *, int));
+#endif /* NETINFO */
+extern bool openmap __P((MAP *));
+#if USERDB
+extern void _udbx_close __P((void));
+extern int udbexpand __P((ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern char *udbsender __P((char *, SM_RPOOL_T *));
+#endif /* USERDB */
+
+/*
+** LDAP related items
+*/
+#if LDAPMAP
+/* struct defining LDAP Auth Methods */
+struct lamvalues
+{
+ char *lam_name; /* name of LDAP auth method */
+ int lam_code; /* numeric code */
+};
+
+/* struct defining LDAP Alias Dereferencing */
+struct ladvalues
+{
+ char *lad_name; /* name of LDAP alias dereferencing method */
+ int lad_code; /* numeric code */
+};
+
+/* struct defining LDAP Search Scope */
+struct lssvalues
+{
+ char *lss_name; /* name of LDAP search scope */
+ int lss_code; /* numeric code */
+};
+
+/* functions */
+extern bool ldapmap_parseargs __P((MAP *, char *));
+extern void ldapmap_set_defaults __P((char *));
+#endif /* LDAPMAP */
+
+/*
+** PH related items
+*/
+
+#if PH_MAP
+
+# include <phclient.h>
+
+struct ph_map_struct
+{
+ char *ph_servers; /* list of ph servers */
+ char *ph_field_list; /* list of fields to search for match */
+ PH *ph; /* PH server handle */
+ int ph_fastclose; /* send "quit" command on close */
+ time_t ph_timeout; /* timeout interval */
+};
+typedef struct ph_map_struct PH_MAP_STRUCT;
+
+#endif /* PH_MAP */
+/*
+** Process List (proclist)
+*/
+
+#define NO_PID ((pid_t) 0)
+#ifndef PROC_LIST_SEG
+# define PROC_LIST_SEG 32 /* number of pids to alloc at a time */
+#endif /* ! PROC_LIST_SEG */
+
+/* process types */
+#define PROC_NONE 0
+#define PROC_DAEMON 1
+#define PROC_DAEMON_CHILD 2
+#define PROC_QUEUE 3
+#define PROC_QUEUE_CHILD 3
+#define PROC_CONTROL 4
+#define PROC_CONTROL_CHILD 5
+
+/* functions */
+extern void proc_list_add __P((pid_t, char *, int, int, int));
+extern void proc_list_clear __P((void));
+extern void proc_list_display __P((SM_FILE_T *, char *));
+extern void proc_list_drop __P((pid_t, int, int *));
+extern void proc_list_probe __P((void));
+extern void proc_list_set __P((pid_t, char *));
+extern void proc_list_signal __P((int, int));
+
+/*
+** Symbol table definitions
+*/
+
+struct symtab
+{
+ char *s_name; /* name to be entered */
+ short s_symtype; /* general type (see below) */
+ struct symtab *s_next; /* pointer to next in chain */
+ union
+ {
+ BITMAP256 sv_class; /* bit-map of word classes */
+ ADDRESS *sv_addr; /* pointer to address header */
+ MAILER *sv_mailer; /* pointer to mailer */
+ char *sv_alias; /* alias */
+ MAPCLASS sv_mapclass; /* mapping function class */
+ MAP sv_map; /* mapping function */
+ HOSTSIG_T sv_hostsig; /* host signature */
+ MCI sv_mci; /* mailer connection info */
+ NAMECANON sv_namecanon; /* canonical name cache */
+ int sv_macro; /* macro name => id mapping */
+ int sv_ruleset; /* ruleset index */
+ struct hdrinfo sv_header; /* header metainfo */
+ char *sv_service[MAXMAPSTACK]; /* service switch */
+#if LDAPMAP
+ MAP *sv_lmap; /* Maps for LDAP connection */
+#endif /* LDAPMAP */
+#if MILTER
+ struct milter *sv_milter; /* milter filter name */
+#endif /* MILTER */
+ QUEUEGRP *sv_queue; /* pointer to queue */
+ } s_value;
+};
+
+typedef struct symtab STAB;
+
+/* symbol types */
+#define ST_UNDEF 0 /* undefined type */
+#define ST_CLASS 1 /* class map */
+#define ST_ADDRESS 2 /* an address in parsed format */
+#define ST_MAILER 3 /* a mailer header */
+#define ST_ALIAS 4 /* an alias */
+#define ST_MAPCLASS 5 /* mapping function class */
+#define ST_MAP 6 /* mapping function */
+#define ST_HOSTSIG 7 /* host signature */
+#define ST_NAMECANON 8 /* cached canonical name */
+#define ST_MACRO 9 /* macro name to id mapping */
+#define ST_RULESET 10 /* ruleset index */
+#define ST_SERVICE 11 /* service switch entry */
+#define ST_HEADER 12 /* special header flags */
+#if LDAPMAP
+# define ST_LMAP 13 /* List head of maps for LDAP connection */
+#endif /* LDAPMAP */
+#if MILTER
+# define ST_MILTER 14 /* milter filter */
+#endif /* MILTER */
+#define ST_QUEUE 15 /* a queue entry */
+
+/* This entry must be last */
+#define ST_MCI 16 /* mailer connection info (offset) */
+
+#define s_class s_value.sv_class
+#define s_address s_value.sv_addr
+#define s_mailer s_value.sv_mailer
+#define s_alias s_value.sv_alias
+#define s_mci s_value.sv_mci
+#define s_mapclass s_value.sv_mapclass
+#define s_hostsig s_value.sv_hostsig
+#define s_map s_value.sv_map
+#define s_namecanon s_value.sv_namecanon
+#define s_macro s_value.sv_macro
+#define s_ruleset s_value.sv_ruleset
+#define s_service s_value.sv_service
+#define s_header s_value.sv_header
+#if LDAPMAP
+# define s_lmap s_value.sv_lmap
+#endif /* LDAPMAP */
+#if MILTER
+# define s_milter s_value.sv_milter
+#endif /* MILTER */
+#define s_quegrp s_value.sv_queue
+
+/* opcodes to stab */
+#define ST_FIND 0 /* find entry */
+#define ST_ENTER 1 /* enter if not there */
+
+/* functions */
+extern STAB *stab __P((char *, int, int));
+extern void stabapply __P((void (*)(STAB *, int), int));
+
+/*
+** Operation, send, error, and MIME modes
+**
+** The operation mode describes the basic operation of sendmail.
+** This can be set from the command line, and is "send mail" by
+** default.
+**
+** The send mode tells how to send mail. It can be set in the
+** configuration file. Its setting determines how quickly the
+** mail will be delivered versus the load on your system. If the
+** -v (verbose) flag is given, it will be forced to SM_DELIVER
+** mode.
+**
+** The error mode tells how to return errors.
+*/
+
+#define MD_DELIVER 'm' /* be a mail sender */
+#define MD_SMTP 's' /* run SMTP on standard input */
+#define MD_ARPAFTP 'a' /* obsolete ARPANET mode (Grey Book) */
+#define MD_DAEMON 'd' /* run as a daemon */
+#define MD_FGDAEMON 'D' /* run daemon in foreground */
+#define MD_VERIFY 'v' /* verify: don't collect or deliver */
+#define MD_TEST 't' /* test mode: resolve addrs only */
+#define MD_INITALIAS 'i' /* initialize alias database */
+#define MD_PRINT 'p' /* print the queue */
+#define MD_PRINTNQE 'P' /* print number of entries in queue */
+#define MD_FREEZE 'z' /* freeze the configuration file */
+#define MD_HOSTSTAT 'h' /* print persistent host stat info */
+#define MD_PURGESTAT 'H' /* purge persistent host stat info */
+#define MD_QUEUERUN 'q' /* queue run */
+
+/* Note: see also include/sendmail/pathnames.h: GET_CLIENT_CF */
+
+/* values for e_sendmode -- send modes */
+#define SM_DELIVER 'i' /* interactive delivery */
+#define SM_FORK 'b' /* deliver in background */
+#define SM_QUEUE 'q' /* queue, don't deliver */
+#define SM_DEFER 'd' /* defer map lookups as well as queue */
+#define SM_VERIFY 'v' /* verify only (used internally) */
+
+#define WILL_BE_QUEUED(m) ((m) == SM_QUEUE || (m) == SM_DEFER)
+
+/* used only as a parameter to sendall */
+#define SM_DEFAULT '\0' /* unspecified, use SendMode */
+
+/* functions */
+extern void set_delivery_mode __P((int, ENVELOPE *));
+
+/* values for e_errormode -- error handling modes */
+#define EM_PRINT 'p' /* print errors */
+#define EM_MAIL 'm' /* mail back errors */
+#define EM_WRITE 'w' /* write back errors */
+#define EM_BERKNET 'e' /* special berknet processing */
+#define EM_QUIET 'q' /* don't print messages (stat only) */
+
+
+/* bit values for MimeMode */
+#define MM_CVTMIME 0x0001 /* convert 8 to 7 bit MIME */
+#define MM_PASS8BIT 0x0002 /* just send 8 bit data blind */
+#define MM_MIME8BIT 0x0004 /* convert 8-bit data to MIME */
+
+
+/* how to handle messages without any recipient addresses */
+#define NRA_NO_ACTION 0 /* just leave it as is */
+#define NRA_ADD_TO 1 /* add To: header */
+#define NRA_ADD_APPARENTLY_TO 2 /* add Apparently-To: header */
+#define NRA_ADD_BCC 3 /* add empty Bcc: header */
+#define NRA_ADD_TO_UNDISCLOSED 4 /* add To: undisclosed:; header */
+
+
+/* flags to putxline */
+#define PXLF_NOTHINGSPECIAL 0 /* no special mapping */
+#define PXLF_MAPFROM 0x0001 /* map From_ to >From_ */
+#define PXLF_STRIP8BIT 0x0002 /* strip 8th bit */
+#define PXLF_HEADER 0x0004 /* map newlines in headers */
+#define PXLF_NOADDEOL 0x0008 /* if EOL not present, don't add one */
+
+/*
+** Privacy flags
+** These are bit values for the PrivacyFlags word.
+*/
+
+#define PRIV_PUBLIC 0 /* what have I got to hide? */
+#define PRIV_NEEDMAILHELO 0x00000001 /* insist on HELO for MAIL */
+#define PRIV_NEEDEXPNHELO 0x00000002 /* insist on HELO for EXPN */
+#define PRIV_NEEDVRFYHELO 0x00000004 /* insist on HELO for VRFY */
+#define PRIV_NOEXPN 0x00000008 /* disallow EXPN command */
+#define PRIV_NOVRFY 0x00000010 /* disallow VRFY command */
+#define PRIV_AUTHWARNINGS 0x00000020 /* flag possible auth probs */
+#define PRIV_NOVERB 0x00000040 /* disallow VERB command */
+#define PRIV_RESTRICTMAILQ 0x00010000 /* restrict mailq command */
+#define PRIV_RESTRICTQRUN 0x00020000 /* restrict queue run */
+#define PRIV_RESTRICTEXPAND 0x00040000 /* restrict alias/forward expansion */
+#define PRIV_NOETRN 0x00080000 /* disallow ETRN command */
+#define PRIV_NOBODYRETN 0x00100000 /* do not return bodies on bounces */
+#define PRIV_NORECEIPTS 0x00200000 /* disallow return receipts */
+
+/* don't give no info, anyway, anyhow */
+#define PRIV_GOAWAY 0x0000ffff
+
+/* struct defining such things */
+struct prival
+{
+ char *pv_name; /* name of privacy flag */
+ unsigned long pv_flag; /* numeric level */
+};
+
+EXTERN unsigned long PrivacyFlags; /* privacy flags */
+
+
+/*
+** Flags passed to remotename, parseaddr, allocaddr, and buildaddr.
+*/
+
+#define RF_SENDERADDR 0x001 /* this is a sender address */
+#define RF_HEADERADDR 0x002 /* this is a header address */
+#define RF_CANONICAL 0x004 /* strip comment information */
+#define RF_ADDDOMAIN 0x008 /* OK to do domain extension */
+#define RF_COPYPARSE 0x010 /* copy parsed user & host */
+#define RF_COPYPADDR 0x020 /* copy print address */
+#define RF_COPYALL (RF_COPYPARSE|RF_COPYPADDR)
+#define RF_COPYNONE 0
+
+/*
+** Flags passed to rscheck
+*/
+
+#define RSF_RMCOMM 0x0001 /* strip comments */
+#define RSF_UNSTRUCTURED 0x0002 /* unstructured, ignore syntax errors */
+#define RSF_COUNT 0x0004 /* count rejections (statistics)? */
+
+/*
+** Flags passed to mime8to7 and putheader.
+*/
+
+#define M87F_OUTER 0 /* outer context */
+#define M87F_NO8BIT 0x0001 /* can't have 8-bit in this section */
+#define M87F_DIGEST 0x0002 /* processing multipart/digest */
+#define M87F_NO8TO7 0x0004 /* don't do 8->7 bit conversions */
+
+/* functions */
+extern void mime7to8 __P((MCI *, HDR *, ENVELOPE *));
+extern int mime8to7 __P((MCI *, HDR *, ENVELOPE *, char **, int));
+
+/*
+** Flags passed to returntosender.
+*/
+
+#define RTSF_NO_BODY 0 /* send headers only */
+#define RTSF_SEND_BODY 0x0001 /* include body of message in return */
+#define RTSF_PM_BOUNCE 0x0002 /* this is a postmaster bounce */
+
+/* functions */
+extern int returntosender __P((char *, ADDRESS *, int, ENVELOPE *));
+
+/*
+** Regular UNIX sockaddrs are too small to handle ISO addresses, so
+** we are forced to declare a supertype here.
+*/
+
+#if NETINET || NETINET6 || NETUNIX || NETISO || NETNS || NETX25
+union bigsockaddr
+{
+ struct sockaddr sa; /* general version */
+# if NETUNIX
+ struct sockaddr_un sunix; /* UNIX family */
+# endif /* NETUNIX */
+# if NETINET
+ struct sockaddr_in sin; /* INET family */
+# endif /* NETINET */
+# if NETINET6
+ struct sockaddr_in6 sin6; /* INET/IPv6 */
+# endif /* NETINET6 */
+# if NETISO
+ struct sockaddr_iso siso; /* ISO family */
+# endif /* NETISO */
+# if NETNS
+ struct sockaddr_ns sns; /* XNS family */
+# endif /* NETNS */
+# if NETX25
+ struct sockaddr_x25 sx25; /* X.25 family */
+# endif /* NETX25 */
+};
+
+# define SOCKADDR union bigsockaddr
+
+/* functions */
+extern char *anynet_ntoa __P((SOCKADDR *));
+# if NETINET6
+extern char *anynet_ntop __P((struct in6_addr *, char *, size_t));
+extern int anynet_pton __P((int, const char *, void *));
+# endif /* NETINET6 */
+extern char *hostnamebyanyaddr __P((SOCKADDR *));
+extern char *validate_connection __P((SOCKADDR *, char *, ENVELOPE *));
+# if SASL >= 20000
+extern bool iptostring __P((SOCKADDR *, SOCKADDR_LEN_T, char *, unsigned));
+# endif /* SASL >= 20000 */
+
+#endif /* NETINET || NETINET6 || NETUNIX || NETISO || NETNS || NETX25 */
+
+/*
+** Mail Filters (milter)
+*/
+
+/*
+** 32-bit type used by milter
+** (needed by libmilter even if MILTER isn't defined)
+*/
+
+typedef SM_INT32 mi_int32;
+
+#if MILTER
+# define SMFTO_WRITE 0 /* Timeout for sending information */
+# define SMFTO_READ 1 /* Timeout waiting for a response */
+# define SMFTO_EOM 2 /* Timeout for ACK/NAK to EOM */
+# define SMFTO_CONNECT 3 /* Timeout for connect() */
+
+# define SMFTO_NUM_TO 4 /* Total number of timeouts */
+
+struct milter
+{
+ char *mf_name; /* filter name */
+ BITMAP256 mf_flags; /* MTA flags */
+ mi_int32 mf_fvers; /* filter version */
+ mi_int32 mf_fflags; /* filter flags */
+ mi_int32 mf_pflags; /* protocol flags */
+ char *mf_conn; /* connection info */
+ int mf_sock; /* connected socket */
+ char mf_state; /* state of filter */
+ time_t mf_timeout[SMFTO_NUM_TO]; /* timeouts */
+};
+
+/* MTA flags */
+# define SMF_REJECT 'R' /* Reject connection on filter fail */
+# define SMF_TEMPFAIL 'T' /* tempfail connection on failure */
+
+/* states */
+# define SMFS_CLOSED 'C' /* closed for all further actions */
+# define SMFS_OPEN 'O' /* connected to remote milter filter */
+# define SMFS_INMSG 'M' /* currently servicing a message */
+# define SMFS_DONE 'D' /* done with current message */
+# define SMFS_CLOSABLE 'Q' /* done with current connection */
+# define SMFS_ERROR 'E' /* error state */
+# define SMFS_READY 'R' /* ready for action */
+
+EXTERN struct milter *InputFilters[MAXFILTERS];
+EXTERN char *InputFilterList;
+EXTERN int MilterLogLevel;
+
+# if _FFR_MILTER_PERDAEMON
+/* functions */
+extern void setup_daemon_milters __P(());
+# endif /* _FFR_MILTER_PERDAEMON */
+#endif /* MILTER */
+
+/*
+** Vendor codes
+**
+** Vendors can customize sendmail to add special behaviour,
+** generally for back compatibility. Ideally, this should
+** be set up in the .cf file using the "V" command. However,
+** it's quite reasonable for some vendors to want the default
+** be their old version; this can be set using
+** -DVENDOR_DEFAULT=VENDOR_xxx
+** in the Makefile.
+**
+** Vendors should apply to sendmail@sendmail.org for
+** unique vendor codes.
+*/
+
+#define VENDOR_BERKELEY 1 /* Berkeley-native configuration file */
+#define VENDOR_SUN 2 /* Sun-native configuration file */
+#define VENDOR_HP 3 /* Hewlett-Packard specific config syntax */
+#define VENDOR_IBM 4 /* IBM specific config syntax */
+#define VENDOR_SENDMAIL 5 /* Sendmail, Inc. specific config syntax */
+#define VENDOR_DEC 6 /* Compaq, DEC, Digital */
+
+/* prototypes for vendor-specific hook routines */
+extern void vendor_daemon_setup __P((ENVELOPE *));
+extern void vendor_set_uid __P((UID_T));
+
+
+/*
+** Terminal escape codes.
+**
+** To make debugging output clearer.
+*/
+
+struct termescape
+{
+ char *te_rv_on; /* turn reverse-video on */
+ char *te_rv_off; /* turn reverse-video off */
+};
+
+/*
+** Additional definitions
+*/
+
+/*
+** d_flags, see daemon.c
+** general rule: lower case: required, upper case: No
+*/
+
+#define D_AUTHREQ 'a' /* authentication required */
+#define D_BINDIF 'b' /* use if_addr for outgoing connection */
+#define D_CANONREQ 'c' /* canonification required (cf) */
+#define D_IFNHELO 'h' /* use if name for HELO */
+#define D_FQMAIL 'f' /* fq sender address required (cf) */
+#define D_FQRCPT 'r' /* fq recipient address required (cf) */
+#if _FFR_SMTP_SSL
+# define D_SMTPS 's' /* SMTP over SSL (smtps) */
+#endif /* _FFR_SMTP_SSL */
+#define D_UNQUALOK 'u' /* unqualified address is ok (cf) */
+#define D_NOAUTH 'A' /* no AUTH */
+#define D_NOCANON 'C' /* no canonification (cf) */
+#define D_NOETRN 'E' /* no ETRN (MSA) */
+#define D_NOTLS 'S' /* don't use STARTTLS */
+#define D_ETRNONLY ((char)0x01) /* allow only ETRN (disk low) */
+#define D_OPTIONAL 'O' /* optional socket */
+#define D_DISABLE ((char)0x02) /* optional socket disabled */
+#define D_ISSET ((char)0x03) /* this client struct is set */
+
+#if STARTTLS
+/*
+** TLS
+*/
+
+/* what to do in the TLS initialization */
+#define TLS_I_NONE 0x00000000 /* no requirements... */
+#define TLS_I_CERT_EX 0x00000001 /* CERT must exist */
+#define TLS_I_CERT_UNR 0x00000002 /* CERT must be g/o unreadable */
+#define TLS_I_KEY_EX 0x00000004 /* KEY must exist */
+#define TLS_I_KEY_UNR 0x00000008 /* KEY must be g/o unreadable */
+#define TLS_I_CERTP_EX 0x00000010 /* CA CERT PATH must exist */
+#define TLS_I_CERTP_UNR 0x00000020 /* CA CERT PATH must be g/o unreadable */
+#define TLS_I_CERTF_EX 0x00000040 /* CA CERT FILE must exist */
+#define TLS_I_CERTF_UNR 0x00000080 /* CA CERT FILE must be g/o unreadable */
+#define TLS_I_RSA_TMP 0x00000100 /* RSA TMP must be generated */
+#define TLS_I_USE_KEY 0x00000200 /* private key must usable */
+#define TLS_I_USE_CERT 0x00000400 /* certificate must be usable */
+#define TLS_I_VRFY_PATH 0x00000800 /* load verify path must succeed */
+#define TLS_I_VRFY_LOC 0x00001000 /* load verify default must succeed */
+#define TLS_I_CACHE 0x00002000 /* require cache */
+#define TLS_I_TRY_DH 0x00004000 /* try DH certificate */
+#define TLS_I_REQ_DH 0x00008000 /* require DH certificate */
+#define TLS_I_DHPAR_EX 0x00010000 /* require DH parameters */
+#define TLS_I_DHPAR_UNR 0x00020000 /* DH param. must be g/o unreadable */
+#define TLS_I_DH512 0x00040000 /* generate 512bit DH param */
+#define TLS_I_DH1024 0x00080000 /* generate 1024bit DH param */
+#define TLS_I_DH2048 0x00100000 /* generate 2048bit DH param */
+#define TLS_I_NO_VRFY 0x00200000 /* do not require authentication */
+#define TLS_I_KEY_OUNR 0x00400000 /* KEY must be o unreadable */
+
+/* require server cert */
+#define TLS_I_SRV_CERT (TLS_I_CERT_EX | TLS_I_KEY_EX | \
+ TLS_I_KEY_UNR | TLS_I_KEY_OUNR | \
+ TLS_I_CERTP_EX | TLS_I_CERTF_EX | \
+ TLS_I_USE_KEY | TLS_I_USE_CERT)
+
+/* server requirements */
+#define TLS_I_SRV (TLS_I_SRV_CERT | TLS_I_RSA_TMP | TLS_I_VRFY_PATH | \
+ TLS_I_VRFY_LOC | TLS_I_TRY_DH | TLS_I_DH512)
+
+/* client requirements */
+#define TLS_I_CLT (TLS_I_KEY_UNR | TLS_I_KEY_OUNR)
+
+#define TLS_AUTH_OK 0
+#define TLS_AUTH_NO 1
+#define TLS_AUTH_FAIL (-1)
+
+/* functions */
+extern bool init_tls_library __P((void));
+extern bool inittls __P((SSL_CTX **, unsigned long, bool, char *, char *, char *, char *, char *));
+extern bool initclttls __P((bool));
+extern void setclttls __P((bool));
+extern bool initsrvtls __P((bool));
+extern int tls_get_info __P((SSL *, bool, char *, MACROS_T *, bool));
+extern int endtls __P((SSL *, char *));
+extern void tlslogerr __P((char *));
+
+
+EXTERN char *CACERTpath; /* path to CA certificates (dir. with hashes) */
+EXTERN char *CACERTfile; /* file with CA certificate */
+EXTERN char *CltCERTfile; /* file with client certificate */
+EXTERN char *Cltkeyfile; /* file with client private key */
+# if _FFR_TLS_1
+EXTERN char *CipherList; /* list of ciphers */
+EXTERN char *DHParams5; /* file with DH parameters (512) */
+# endif /* _FFR_TLS_1 */
+EXTERN char *DHParams; /* file with DH parameters */
+EXTERN char *RandFile; /* source of random data */
+EXTERN char *SrvCERTfile; /* file with server certificate */
+EXTERN char *Srvkeyfile; /* file with server private key */
+EXTERN unsigned long TLS_Srv_Opts; /* TLS server options */
+#endif /* STARTTLS */
+
+/*
+** Queue related items
+*/
+
+/* queue file names */
+#if _FFR_QUARANTINE
+# define ANYQFL_LETTER '?'
+# define QUARQF_LETTER 'h'
+#else /* _FFR_QUARANTINE */
+/* Before quarantining, ANYQF == NORMQF */
+# define ANYQFL_LETTER 'q'
+#endif /* _FFR_QUARANTINE */
+#define DATAFL_LETTER 'd'
+#define XSCRPT_LETTER 'x'
+#define NORMQF_LETTER 'q'
+#define NEWQFL_LETTER 't'
+
+# define TEMPQF_LETTER 'T'
+# define LOSEQF_LETTER 'Q'
+
+/* queue sort order */
+#define QSO_BYPRIORITY 0 /* sort by message priority */
+#define QSO_BYHOST 1 /* sort by first host name */
+#define QSO_BYTIME 2 /* sort by submission time */
+#define QSO_BYFILENAME 3 /* sort by file name only */
+#define QSO_RANDOM 4 /* sort in random order */
+#define QSO_BYMODTIME 5 /* sort by modification time */
+#if _FFR_RHS
+# define QSO_BYSHUFFLE 6 /* sort by shuffled host name */
+#endif /* _FFR_RHS */
+
+#if _FFR_QUEUEDELAY
+# define QD_LINEAR 0 /* linear (old) delay alg */
+# define QD_EXP 1 /* exponential delay alg */
+#endif /* _FFR_QUEUEDELAY */
+
+#define NOQGRP (-1) /* no queue group (yet) */
+#define ENVQGRP (-2) /* use queue group of envelope */
+#define NOAQGRP (-3) /* no queue group in addr (yet) */
+#define ISVALIDQGRP(x) ((x) >= 0) /* valid queue group? */
+#define NOQDIR (-1) /* no queue directory (yet) */
+#define ENVQDIR (-2) /* use queue directory of envelope */
+#define NOAQDIR (-3) /* no queue directory in addr (yet) */
+#define ISVALIDQDIR(x) ((x) >= 0) /* valid queue directory? */
+#define RS_QUEUEGROUP "queuegroup" /* ruleset for queue group selection */
+
+#define NOW ((time_t) (-1)) /* queue return: now */
+
+/* SuperSafe values */
+#define SAFE_NO 0 /* no fsync(): don't use... */
+#define SAFE_INTERACTIVE 1 /* limit fsync() in -odi */
+#define SAFE_REALLY 2 /* always fsync() */
+
+#if _FFR_QUARANTINE
+/* QueueMode bits */
+# define QM_NORMAL ' '
+# define QM_QUARANTINE 'Q'
+# define QM_LOST 'L'
+#endif /* _FFR_QUARANTINE */
+
+/* Queue Run Limitations */
+struct queue_char
+{
+ char *queue_match; /* string to match */
+ bool queue_negate; /* or not match, if set */
+ struct queue_char *queue_next;
+};
+
+typedef struct queue_char QUEUE_CHAR;
+
+EXTERN int volatile CurRunners; /* current number of runner children */
+EXTERN int MaxQueueRun; /* maximum number of jobs in one queue run */
+EXTERN int MaxQueueChildren; /* max # of forked queue children */
+EXTERN int MaxRunnersPerQueue; /* max # proc's active in queue group */
+EXTERN int NiceQueueRun; /* nice queue runs to this value */
+EXTERN int NumQueue; /* number of queue groups */
+EXTERN int QueueFileMode; /* mode on files in mail queue */
+#if _FFR_QUARANTINE
+EXTERN int QueueMode; /* which queue items to act upon */
+#endif /* _FFR_QUARANTINE */
+EXTERN int QueueSortOrder; /* queue sorting order algorithm */
+EXTERN time_t MinQueueAge; /* min delivery interval */
+EXTERN time_t QueueIntvl; /* intervals between running the queue */
+EXTERN char *QueueDir; /* location of queue directory */
+#if _FFR_QUEUEDELAY
+EXTERN int QueueAlg; /* algorithm for queue delays */
+EXTERN time_t QueueInitDelay; /* initial queue delay */
+EXTERN time_t QueueMaxDelay; /* maximum queue delay */
+#endif /* _FFR_QUEUEDELAY */
+EXTERN QUEUE_CHAR *QueueLimitId; /* limit queue run to id */
+#if _FFR_QUARANTINE
+EXTERN QUEUE_CHAR *QueueLimitQuarantine; /* limit queue run to quarantine reason */
+#endif /* _FFR_QUARANTINE */
+EXTERN QUEUE_CHAR *QueueLimitRecipient; /* limit queue run to rcpt */
+EXTERN QUEUE_CHAR *QueueLimitSender; /* limit queue run to sender */
+EXTERN QUEUEGRP *Queue[MAXQUEUEGROUPS + 1]; /* queue groups */
+
+/* functions */
+extern void assign_queueid __P((ENVELOPE *));
+extern ADDRESS *copyqueue __P((ADDRESS *, SM_RPOOL_T *));
+extern void cleanup_queues __P((void));
+extern bool doqueuerun __P((void));
+extern void initsys __P((ENVELOPE *));
+extern void loseqfile __P((ENVELOPE *, char *));
+extern int name2qid __P((char *));
+extern char *qid_printname __P((ENVELOPE *));
+extern char *qid_printqueue __P((int, int));
+#if _FFR_QUARANTINE
+extern void quarantine_queue __P((char *, int));
+#endif /* _FFR_QUARANTINE */
+extern char *queuename __P((ENVELOPE *, int));
+extern void queueup __P((ENVELOPE *, bool, bool));
+extern bool runqueue __P((bool, bool, bool, bool));
+extern int run_work_group __P((int, bool, bool, bool, bool));
+extern void set_def_queueval __P((QUEUEGRP *, bool));
+extern void setup_queues __P((bool));
+extern bool setnewqueue __P((ENVELOPE *));
+extern bool shouldqueue __P((long, time_t));
+extern void sync_queue_time __P((void));
+extern int print_single_queue __P((int, int));
+#if REQUIRES_DIR_FSYNC
+# define SYNC_DIR(path, panic) sync_dir(path, panic)
+extern void sync_dir __P((char *, bool));
+#else /* REQUIRES_DIR_FSYNC */
+# define SYNC_DIR(path, panic) ((void) 0)
+#endif /* REQUIRES_DIR_FSYNC */
+
+/*
+** Timeouts
+**
+** Indicated values are the MINIMUM per RFC 1123 section 5.3.2.
+*/
+
+EXTERN struct
+{
+ /* RFC 1123-specified timeouts [minimum value] */
+ time_t to_initial; /* initial greeting timeout [5m] */
+ time_t to_mail; /* MAIL command [5m] */
+ time_t to_rcpt; /* RCPT command [5m] */
+ time_t to_datainit; /* DATA initiation [2m] */
+ time_t to_datablock; /* DATA block [3m] */
+ time_t to_datafinal; /* DATA completion [10m] */
+ time_t to_nextcommand; /* next command [5m] */
+ /* following timeouts are not mentioned in RFC 1123 */
+ time_t to_iconnect; /* initial connection timeout (first try) */
+ time_t to_connect; /* initial connection timeout (later tries) */
+ time_t to_aconnect; /* all connections timeout (MX and A records) */
+ time_t to_rset; /* RSET command */
+ time_t to_helo; /* HELO command */
+ time_t to_quit; /* QUIT command */
+ time_t to_miscshort; /* misc short commands (NOOP, VERB, etc) */
+ time_t to_ident; /* IDENT protocol requests */
+ time_t to_fileopen; /* opening :include: and .forward files */
+ time_t to_control; /* process a control socket command */
+ time_t to_lhlo; /* LMTP: LHLO command */
+#if SASL
+ time_t to_auth; /* AUTH dialogue [10m] */
+#endif /* SASL */
+#if STARTTLS
+ time_t to_starttls; /* STARTTLS dialogue [10m] */
+#endif /* STARTTLS */
+ /* following are per message */
+ time_t to_q_return[MAXTOCLASS]; /* queue return timeouts */
+ time_t to_q_warning[MAXTOCLASS]; /* queue warning timeouts */
+ time_t res_retrans[MAXRESTOTYPES]; /* resolver retransmit */
+ int res_retry[MAXRESTOTYPES]; /* resolver retry */
+} TimeOuts;
+
+/* timeout classes for return and warning timeouts */
+#define TOC_NORMAL 0 /* normal delivery */
+#define TOC_URGENT 1 /* urgent delivery */
+#define TOC_NONURGENT 2 /* non-urgent delivery */
+
+/* resolver timeout specifiers */
+#define RES_TO_FIRST 0 /* first attempt */
+#define RES_TO_NORMAL 1 /* subsequent attempts */
+#define RES_TO_DEFAULT 2 /* default value */
+
+/* functions */
+extern void inittimeouts __P((char *, bool));
+
+/*
+** Interface probing
+*/
+
+#define DPI_PROBENONE 0 /* Don't probe any interfaces */
+#define DPI_PROBEALL 1 /* Probe all interfaces */
+#define DPI_SKIPLOOPBACK 2 /* Don't probe loopback interfaces */
+
+/*
+** Trace information
+*/
+
+/* macros for debugging flags */
+#define tTd(flag, level) (tTdvect[flag] >= (unsigned char)level)
+#define tTdlevel(flag) (tTdvect[flag])
+
+/* variables */
+extern unsigned char tTdvect[100]; /* trace vector */
+
+/*
+** Miscellaneous information.
+*/
+
+/*
+** The "no queue id" queue id for sm_syslog
+*/
+
+#define NOQID "*~*"
+
+/* use id or NOQID (to avoid NOQUEUE in logfile) */
+#define E_ID(id) ((id) == NULL ? NOQID : (id))
+
+#define CURHOSTNAME (CurHostName == NULL ? "local" : CurHostName)
+
+/*
+** Some in-line functions
+*/
+
+/* set exit status */
+#define setstat(s) { \
+ if (ExitStat == EX_OK || ExitStat == EX_TEMPFAIL) \
+ ExitStat = s; \
+ }
+
+/* make a copy of a string */
+#define newstr(s) strcpy(xalloc(strlen(s) + 1), s)
+
+#define STRUCTCOPY(s, d) d = s
+
+/* free a pointer if it isn't NULL and set it to NULL */
+#define SM_FREE_CLR(p) \
+ if ((p) != NULL) \
+ { \
+ sm_free(p); \
+ (p) = NULL; \
+ } \
+ else
+
+/*
+** Update a permanent string variable with a new value.
+** The old value is freed, the new value is strdup'ed.
+**
+** We use sm_pstrdup_x to duplicate the string because it raises
+** an exception on error, and because it allocates "permanent storage"
+** which is not expected to be freed before process exit.
+** The latter is important for memory leak analysis.
+**
+** If an exception occurs while strdup'ing the new value,
+** then the variable remains set to the old value.
+** That's why the strdup must occur before we free the old value.
+**
+** The macro uses a do loop so that this idiom will work:
+** if (...)
+** PSTRSET(var, val1);
+** else
+** PSTRSET(var, val2);
+*/
+#define PSTRSET(var, val) \
+ do \
+ { \
+ char *_newval = sm_pstrdup_x(val); \
+ if (var != NULL) \
+ sm_free(var); \
+ var = _newval; \
+ } while (0)
+
+/*
+** Global variables.
+*/
+
+EXTERN bool AllowBogusHELO; /* allow syntax errors on HELO command */
+EXTERN bool CheckAliases; /* parse addresses during newaliases */
+EXTERN bool ColonOkInAddr; /* single colon legal in address */
+#if !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_)
+EXTERN bool ConfigFileRead; /* configuration file has been read */
+#endif /* !defined(_USE_SUN_NSSWITCH_) && !defined(_USE_DEC_SVC_CONF_) */
+EXTERN bool volatile DataProgress; /* have we sent anything since last check */
+EXTERN bool DisConnected; /* running with OutChannel redirect to transcript file */
+EXTERN bool DontExpandCnames; /* do not $[...$] expand CNAMEs */
+EXTERN bool DontInitGroups; /* avoid initgroups() because of NIS cost */
+EXTERN bool DontLockReadFiles; /* don't read lock support files */
+EXTERN bool DontPruneRoutes; /* don't prune source routes */
+EXTERN bool ForkQueueRuns; /* fork for each job when running the queue */
+EXTERN bool FromFlag; /* if set, "From" person is explicit */
+EXTERN bool GrabTo; /* if set, get recipients from msg */
+EXTERN bool HasEightBits; /* has at least one eight bit input byte */
+EXTERN bool HasWildcardMX; /* don't use MX records when canonifying */
+EXTERN bool HoldErrs; /* only output errors to transcript */
+EXTERN bool IgnoreHostStatus; /* ignore long term host status files */
+EXTERN bool IgnrDot; /* don't let dot end messages */
+EXTERN bool LogUsrErrs; /* syslog user errors (e.g., SMTP RCPT cmd) */
+EXTERN bool MatchGecos; /* look for user names in gecos field */
+EXTERN bool MeToo; /* send to the sender also */
+EXTERN bool NoAlias; /* suppress aliasing */
+EXTERN bool NoConnect; /* don't connect to non-local mailers */
+EXTERN bool OnlyOneError; /* .... or only want to give one SMTP reply */
+EXTERN bool QuickAbort; /* .... but only if we want a quick abort */
+EXTERN bool ResNoAliases; /* don't use $HOSTALIASES */
+EXTERN bool volatile RestartWorkGroup; /* daemon needs to restart some work groups */
+EXTERN bool RrtImpliesDsn; /* turn Return-Receipt-To: into DSN */
+EXTERN bool SaveFrom; /* save leading "From" lines */
+EXTERN bool SendMIMEErrors; /* send error messages in MIME format */
+EXTERN bool SevenBitInput; /* force 7-bit data on input */
+EXTERN bool SingleLineFromHeader; /* force From: header to be one line */
+EXTERN bool SingleThreadDelivery; /* single thread hosts on delivery */
+#if _FFR_SOFT_BOUNCE
+EXTERN bool SoftBounce; /* replace 5xy by 4xy (for testing) */
+#endif /* _FFR_SOFT_BOUNCE */
+EXTERN bool volatile StopRequest; /* stop sending output */
+EXTERN bool SuprErrs; /* set if we are suppressing errors */
+EXTERN bool TryNullMXList; /* if we are the best MX, try host directly */
+EXTERN bool UseMSP; /* mail submission: group writable queue ok? */
+EXTERN bool WorkAroundBrokenAAAA; /* some nameservers return SERVFAIL on AAAA queries */
+EXTERN bool UseErrorsTo; /* use Errors-To: header (back compat) */
+EXTERN bool UseNameServer; /* using DNS -- interpret h_errno & MX RRs */
+EXTERN char InetMode; /* default network for daemon mode */
+EXTERN char OpMode; /* operation mode, see below */
+EXTERN char SpaceSub; /* substitution for <lwsp> */
+EXTERN int BadRcptThrottle; /* Throttle rejected RCPTs per SMTP message */
+EXTERN int CheckpointInterval; /* queue file checkpoint interval */
+EXTERN int ConfigLevel; /* config file level */
+EXTERN int ConnRateThrottle; /* throttle for SMTP connection rate */
+EXTERN int volatile CurChildren; /* current number of daemonic children */
+EXTERN int CurrentLA; /* current load average */
+EXTERN int DefaultNotify; /* default DSN notification flags */
+EXTERN int DelayLA; /* load average to delay connections */
+EXTERN int DontProbeInterfaces; /* don't probe interfaces for names */
+EXTERN int Errors; /* set if errors (local to single pass) */
+EXTERN int ExitStat; /* exit status code */
+EXTERN int FastSplit; /* fast initial splitting of envelopes */
+EXTERN int FileMode; /* mode on files */
+EXTERN int LineNumber; /* line number in current input */
+EXTERN int LogLevel; /* level of logging to perform */
+EXTERN int MaxAliasRecursion; /* maximum depth of alias recursion */
+EXTERN int MaxChildren; /* maximum number of daemonic children */
+EXTERN int MaxForwardEntries; /* maximum number of forward entries */
+EXTERN int MaxHeadersLength; /* max length of headers */
+EXTERN int MaxHopCount; /* max # of hops until bounce */
+EXTERN int MaxMacroRecursion; /* maximum depth of macro recursion */
+EXTERN int MaxMimeFieldLength; /* maximum MIME field length */
+EXTERN int MaxMimeHeaderLength; /* maximum MIME header length */
+
+EXTERN int MaxRcptPerMsg; /* max recipients per SMTP message */
+EXTERN int MaxRuleRecursion; /* maximum depth of ruleset recursion */
+EXTERN int MimeMode; /* MIME processing mode */
+EXTERN int NoRecipientAction;
+
+#if SM_CONF_SHM
+EXTERN int Numfilesys; /* number of queue file systems */
+EXTERN int *PNumFileSys;
+# define NumFileSys (*PNumFileSys)
+# else /* SM_CONF_SHM */
+EXTERN int NumFileSys; /* number of queue file systems */
+# endif /* SM_CONF_SHM */
+
+EXTERN int QueueLA; /* load average starting forced queueing */
+EXTERN int RefuseLA; /* load average refusing connections */
+EXTERN int SuperSafe; /* be extra careful, even if expensive */
+EXTERN int VendorCode; /* vendor-specific operation enhancements */
+EXTERN int Verbose; /* set if blow-by-blow desired */
+EXTERN gid_t DefGid; /* default gid to run as */
+EXTERN gid_t RealGid; /* real gid of caller */
+EXTERN gid_t RunAsGid; /* GID to become for bulk of run */
+EXTERN gid_t EffGid; /* effective gid */
+#if SM_CONF_SHM
+EXTERN key_t ShmKey; /* shared memory key */
+# if _FFR_SELECT_SHM
+EXTERN char *ShmKeyFile; /* shared memory key file */
+# endif /* _FFR_SELECT_SHM */
+#endif /* SM_CONF_SHM */
+EXTERN pid_t CurrentPid; /* current process id */
+EXTERN pid_t DaemonPid; /* process id of daemon */
+EXTERN uid_t DefUid; /* default uid to run as */
+EXTERN uid_t RealUid; /* real uid of caller */
+EXTERN uid_t RunAsUid; /* UID to become for bulk of run */
+EXTERN uid_t TrustedUid; /* uid of trusted user for files and startup */
+EXTERN size_t DataFileBufferSize; /* size of buf for in-core data file */
+EXTERN time_t DeliverByMin; /* deliver by minimum time */
+EXTERN time_t DialDelay; /* delay between dial-on-demand tries */
+EXTERN time_t SafeAlias; /* interval to wait until @:@ in alias file */
+EXTERN time_t ServiceCacheMaxAge; /* refresh interval for cache */
+EXTERN size_t XscriptFileBufferSize; /* size of buf for in-core transcript file */
+EXTERN MODE_T OldUmask; /* umask when sendmail starts up */
+EXTERN long MaxMessageSize; /* advertised max size we will accept */
+EXTERN long MinBlocksFree; /* min # of blocks free on queue fs */
+EXTERN long QueueFactor; /* slope of queue function */
+EXTERN long WkClassFact; /* multiplier for message class -> priority */
+EXTERN long WkRecipFact; /* multiplier for # of recipients -> priority */
+EXTERN long WkTimeFact; /* priority offset each time this job is run */
+EXTERN char *ControlSocketName; /* control socket filename [control.c] */
+EXTERN char *CurHostName; /* current host we are dealing with */
+EXTERN char *DeadLetterDrop; /* path to dead letter office */
+EXTERN char *DefUser; /* default user to run as (from DefUid) */
+EXTERN char *DefaultCharSet; /* default character set for MIME */
+EXTERN char *DoubleBounceAddr; /* where to send double bounces */
+EXTERN char *ErrMsgFile; /* file to prepend to all error messages */
+EXTERN char *FallBackMX; /* fall back MX host */
+EXTERN char *FileName; /* name to print on error messages */
+EXTERN char *ForwardPath; /* path to search for .forward files */
+EXTERN char *HelpFile; /* location of SMTP help file */
+EXTERN char *HostStatDir; /* location of host status information */
+EXTERN char *HostsFile; /* path to /etc/hosts file */
+extern char *Mbdb; /* mailbox database type */
+EXTERN char *MustQuoteChars; /* quote these characters in phrases */
+EXTERN char *MyHostName; /* name of this host for SMTP messages */
+EXTERN char *OperatorChars; /* operators (old $o macro) */
+EXTERN char *PidFile; /* location of proc id file [conf.c] */
+EXTERN char *PostMasterCopy; /* address to get errs cc's */
+EXTERN char *ProcTitlePrefix; /* process title prefix */
+EXTERN char *RealHostName; /* name of host we are talking to */
+EXTERN char *RealUserName; /* real user name of caller */
+EXTERN char *volatile RestartRequest;/* a sendmail restart has been requested */
+EXTERN char *RunAsUserName; /* user to become for bulk of run */
+EXTERN char *SafeFileEnv; /* chroot location for file delivery */
+EXTERN char *ServiceSwitchFile; /* backup service switch */
+EXTERN char *volatile ShutdownRequest;/* a sendmail shutdown has been requested */
+EXTERN char *SmtpGreeting; /* SMTP greeting message (old $e macro) */
+EXTERN char *SmtpPhase; /* current phase in SMTP processing */
+EXTERN char SmtpError[MAXLINE]; /* save failure error messages */
+EXTERN char *StatFile; /* location of statistics summary */
+EXTERN char *TimeZoneSpec; /* override time zone specification */
+EXTERN char *UdbSpec; /* user database source spec */
+EXTERN char *UnixFromLine; /* UNIX From_ line (old $l macro) */
+EXTERN char **ExternalEnviron; /* saved user (input) environment */
+EXTERN char **SaveArgv; /* argument vector for re-execing */
+EXTERN BITMAP256 DontBlameSendmail; /* DontBlameSendmail bits */
+EXTERN SM_FILE_T *InChannel; /* input connection */
+EXTERN SM_FILE_T *OutChannel; /* output connection */
+EXTERN SM_FILE_T *TrafficLogFile; /* file in which to log all traffic */
+#if HESIOD
+EXTERN void *HesiodContext;
+#endif /* HESIOD */
+EXTERN ENVELOPE *CurEnv; /* envelope currently being processed */
+EXTERN char *RuleSetNames[MAXRWSETS]; /* ruleset number to name */
+EXTERN char *UserEnviron[MAXUSERENVIRON + 1];
+EXTERN struct rewrite *RewriteRules[MAXRWSETS];
+EXTERN struct termescape TermEscape; /* terminal escape codes */
+EXTERN SOCKADDR ConnectOnlyTo; /* override connection address (for testing) */
+EXTERN SOCKADDR RealHostAddr; /* address of host we are talking to */
+extern const SM_EXC_TYPE_T EtypeQuickAbort; /* type of a QuickAbort exception */
+
+
+
+/*
+** Declarations of useful functions
+*/
+
+/* Transcript file */
+extern void closexscript __P((ENVELOPE *));
+extern void openxscript __P((ENVELOPE *));
+
+/* error related */
+extern void buffer_errors __P((void));
+extern void flush_errors __P((bool));
+extern void PRINTFLIKE(1, 2) message __P((const char *, ...));
+extern void PRINTFLIKE(1, 2) nmessage __P((const char *, ...));
+extern void PRINTFLIKE(1, 2) syserr __P((const char *, ...));
+extern void PRINTFLIKE(2, 3) usrerrenh __P((char *, const char *, ...));
+extern void PRINTFLIKE(1, 2) usrerr __P((const char *, ...));
+extern int isenhsc __P((const char *, int));
+extern int extenhsc __P((const char *, int, char *));
+
+/* alias file */
+extern void alias __P((ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern bool aliaswait __P((MAP *, char *, bool));
+extern void forward __P((ADDRESS *, ADDRESS **, int, ENVELOPE *));
+extern void readaliases __P((MAP *, SM_FILE_T *, bool, bool));
+extern bool rebuildaliases __P((MAP *, bool));
+extern void setalias __P((char *));
+
+/* logging */
+extern void logdelivery __P((MAILER *, MCI *, char *, const char *, ADDRESS *, time_t, ENVELOPE *));
+extern void logsender __P((ENVELOPE *, char *));
+extern void PRINTFLIKE(3, 4) sm_syslog __P((int, const char *, const char *, ...));
+
+/* SMTP */
+extern void giveresponse __P((int, char *, MAILER *, MCI *, ADDRESS *, time_t, ENVELOPE *, ADDRESS *));
+extern int reply __P((MAILER *, MCI *, ENVELOPE *, time_t, void (*)(), char **));
+extern void smtp __P((char *volatile, BITMAP256, ENVELOPE *volatile));
+#if SASL
+extern int smtpauth __P((MAILER *, MCI *, ENVELOPE *));
+#endif /* SASL */
+extern int smtpdata __P((MAILER *, MCI *, ENVELOPE *, ADDRESS *, time_t));
+extern int smtpgetstat __P((MAILER *, MCI *, ENVELOPE *));
+extern int smtpmailfrom __P((MAILER *, MCI *, ENVELOPE *));
+extern void smtpmessage __P((char *, MAILER *, MCI *, ...));
+extern void smtpinit __P((MAILER *, MCI *, ENVELOPE *, bool));
+extern char *smtptodsn __P((int));
+extern int smtpprobe __P((MCI *));
+extern void smtpquit __P((MAILER *, MCI *, ENVELOPE *));
+extern int smtprcpt __P((ADDRESS *, MAILER *, MCI *, ENVELOPE *, ADDRESS *, time_t));
+extern void smtprset __P((MAILER *, MCI *, ENVELOPE *));
+
+#define ISSMTPCODE(c) (isascii(c[0]) && isdigit(c[0]) && \
+ isascii(c[1]) && isdigit(c[1]) && \
+ isascii(c[2]) && isdigit(c[2]))
+#define ISSMTPREPLY(c) (ISSMTPCODE(c) && \
+ (c[3] == ' ' || c[3] == '-' || c[3] == '\0'))
+
+/* delivery */
+extern pid_t dowork __P((int, int, char *, bool, bool, ENVELOPE *));
+extern pid_t doworklist __P((ENVELOPE *, bool, bool));
+extern int endmailer __P((MCI *, ENVELOPE *, char **));
+extern int mailfile __P((char *volatile, MAILER *volatile, ADDRESS *, volatile long, ENVELOPE *));
+extern void sendall __P((ENVELOPE *, int));
+
+/* stats */
+#define STATS_NORMAL 'n'
+#if _FFR_QUARANTINE
+# define STATS_QUARANTINE 'q'
+#endif /* _FFR_QUARANTINE */
+#define STATS_REJECT 'r'
+#define STATS_CONNECT 'c'
+
+extern void markstats __P((ENVELOPE *, ADDRESS *, int));
+extern void clearstats __P((void));
+extern void poststats __P((char *));
+
+/* control socket */
+extern void closecontrolsocket __P((bool));
+extern void clrcontrol __P((void));
+extern void control_command __P((int, ENVELOPE *));
+extern int opencontrolsocket __P((void));
+
+#if MILTER
+/* milter functions */
+extern void milter_config __P((char *, struct milter **, int));
+extern void milter_setup __P((char *));
+extern void milter_set_option __P((char *, char *, bool));
+extern bool milter_can_delrcpts __P((void));
+extern bool milter_init __P((ENVELOPE *, char *));
+extern void milter_quit __P((ENVELOPE *));
+extern void milter_abort __P((ENVELOPE *));
+extern char *milter_connect __P((char *, SOCKADDR, ENVELOPE *, char *));
+extern char *milter_helo __P((char *, ENVELOPE *, char *));
+extern char *milter_envfrom __P((char **, ENVELOPE *, char *));
+extern char *milter_envrcpt __P((char **, ENVELOPE *, char *));
+extern char *milter_data __P((ENVELOPE *, char *));
+#endif /* MILTER */
+
+extern char *addquotes __P((char *, SM_RPOOL_T *));
+extern char *arpadate __P((char *));
+extern bool atobool __P((char *));
+extern int atooct __P((char *));
+extern void auth_warning __P((ENVELOPE *, const char *, ...));
+extern int blocksignal __P((int));
+extern bool bitintersect __P((BITMAP256, BITMAP256));
+extern bool bitzerop __P((BITMAP256));
+extern int check_bodytype __P((char *));
+extern void buildfname __P((char *, char *, char *, int));
+extern bool chkclientmodifiers __P((int));
+extern bool chkdaemonmodifiers __P((int));
+extern int checkcompat __P((ADDRESS *, ENVELOPE *));
+#ifdef XDEBUG
+extern void checkfd012 __P((char *));
+extern void checkfdopen __P((int, char *));
+#endif /* XDEBUG */
+extern void checkfds __P((char *));
+extern bool chownsafe __P((int, bool));
+extern void cleanstrcpy __P((char *, char *, int));
+#if SM_CONF_SHM
+extern void cleanup_shm __P((bool));
+#endif /* SM_CONF_SHM */
+extern void clrdaemon __P((void));
+extern void collect __P((SM_FILE_T *, bool, HDR **, ENVELOPE *));
+extern time_t convtime __P((char *, int));
+extern char **copyplist __P((char **, bool, SM_RPOOL_T *));
+extern void copy_class __P((int, int));
+extern time_t curtime __P((void));
+extern char *defcharset __P((ENVELOPE *));
+extern char *denlstring __P((char *, bool, bool));
+extern void disconnect __P((int, ENVELOPE *));
+#if _FFR_CONTROL_MSTAT
+extern void disk_status __P((SM_FILE_T *, char *));
+#endif /* _FFR_CONTROL_MSTAT */
+extern bool dns_getcanonname __P((char *, int, bool, int *, int *));
+extern pid_t dofork __P((void));
+extern int drop_privileges __P((bool));
+extern int dsntoexitstat __P((char *));
+extern void dumpfd __P((int, bool, bool));
+extern void dumpstate __P((char *));
+extern bool enoughdiskspace __P((long, ENVELOPE *));
+extern char *exitstat __P((char *));
+extern void fatal_error __P((SM_EXC_T *));
+extern char *fgetfolded __P((char *, int, SM_FILE_T *));
+extern void fill_fd __P((int, char *));
+extern char *find_character __P((char *, int));
+extern int finduser __P((char *, bool *, SM_MBDB_T *));
+extern void finis __P((bool, bool, volatile int));
+extern void fixcrlf __P((char *, bool));
+extern long freediskspace __P((char *, long *));
+#if NETINET6 && NEEDSGETIPNODE
+extern void freehostent __P((struct hostent *));
+#endif /* NETINET6 && NEEDSGETIPNODE */
+extern char *get_column __P((char *, int, int, char *, int));
+extern char *getauthinfo __P((int, bool *));
+extern int getdtsize __P((void));
+extern int getla __P((void));
+extern char *getmodifiers __P((char *, BITMAP256));
+extern BITMAP256 *getrequests __P((ENVELOPE *));
+extern char *getvendor __P((int));
+extern void help __P((char *, ENVELOPE *));
+extern void init_md __P((int, char **));
+extern void initdaemon __P((void));
+extern void inithostmaps __P((void));
+extern void initmacros __P((ENVELOPE *));
+extern void initsetproctitle __P((int, char **, char **));
+extern void init_vendor_macros __P((ENVELOPE *));
+extern SIGFUNC_DECL intsig __P((int));
+extern bool isloopback __P((SOCKADDR sa));
+extern void load_if_names __P((void));
+extern bool lockfile __P((int, char *, char *, int));
+extern void log_sendmail_pid __P((ENVELOPE *));
+extern void logundelrcpts __P((ENVELOPE *, char *, int, bool));
+extern char lower __P((int));
+extern void makelower __P((char *));
+extern int makeconnection_ds __P((char *, MCI *));
+extern int makeconnection __P((char *, volatile unsigned int, MCI *, ENVELOPE *, time_t));
+extern void makeworkgroups __P((void));
+extern void mark_work_group_restart __P((int, int));
+extern char * munchstring __P((char *, char **, int));
+extern struct hostent *myhostname __P((char *, int));
+extern char *nisplus_default_domain __P((void)); /* extern for Sun */
+extern bool path_is_dir __P((char *, bool));
+extern int pickqdir __P((QUEUEGRP *qg, long fsize, ENVELOPE *e));
+extern char *pintvl __P((time_t, bool));
+extern void printav __P((char **));
+extern void printmailer __P((MAILER *));
+extern void printnqe __P((SM_FILE_T *, char *));
+extern void printopenfds __P((bool));
+extern void printqueue __P((void));
+extern void printrules __P((void));
+extern pid_t prog_open __P((char **, int *, ENVELOPE *));
+extern void putline __P((char *, MCI *));
+extern void putxline __P((char *, size_t, MCI *, int));
+extern void queueup_macros __P((int, SM_FILE_T *, ENVELOPE *));
+extern void readcf __P((char *, bool, ENVELOPE *));
+extern SIGFUNC_DECL reapchild __P((int));
+extern int releasesignal __P((int));
+extern void resetlimits __P((void));
+extern void restart_daemon __P((void));
+extern void restart_marked_work_groups __P(());
+extern bool rfc822_string __P((char *));
+extern bool savemail __P((ENVELOPE *, bool));
+extern void seed_random __P((void));
+extern void sendtoargv __P((char **, ENVELOPE *));
+extern void setclientoptions __P((char *));
+extern bool setdaemonoptions __P((char *));
+extern void setdefaults __P((ENVELOPE *));
+extern void setdefuser __P((void));
+extern bool setvendor __P((char *));
+extern void set_op_mode __P((int));
+extern void setoption __P((int, char *, bool, bool, ENVELOPE *));
+extern sigfunc_t setsignal __P((int, sigfunc_t));
+extern void setuserenv __P((const char *, const char *));
+extern void settime __P((ENVELOPE *));
+extern char *sfgets __P((char *, int, SM_FILE_T *, time_t, char *));
+extern char *shortenstring __P((const char *, size_t));
+extern char *shorten_hostname __P((char []));
+extern bool shorten_rfc822_string __P((char *, size_t));
+extern void shutdown_daemon __P((void));
+extern struct hostent *sm_gethostbyname __P((char *, int));
+extern struct hostent *sm_gethostbyaddr __P((char *, int, int));
+extern void sm_getla __P((void));
+extern struct passwd *sm_getpwnam __P((char *));
+extern struct passwd *sm_getpwuid __P((UID_T));
+extern void sm_setproctitle __P((bool, ENVELOPE *, const char *, ...));
+extern pid_t sm_wait __P((int *));
+extern bool split_by_recipient __P((ENVELOPE *e));
+extern void stop_sendmail __P((void));
+extern char *str2prt __P((char *));
+extern bool strreplnonprt __P((char *, int));
+extern bool strcontainedin __P((bool, char *, char *));
+extern int switch_map_find __P((char *, char *[], short []));
+extern bool transienterror __P((int));
+#if _FFR_BESTMX_BETTER_TRUNCATION || _FFR_DNSMAP_MULTI
+extern void truncate_at_delim __P((char *, size_t, int));
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION || _FFR_DNSMAP_MULTI */
+extern void tTflag __P((char *));
+extern void tTsetup __P((unsigned char *, unsigned int, char *));
+extern SIGFUNC_DECL tick __P((int));
+extern char *ttypath __P((void));
+extern void unlockqueue __P((ENVELOPE *));
+#if !HASUNSETENV
+extern void unsetenv __P((char *));
+#endif /* !HASUNSETENV */
+
+/* update file system information: +/- some blocks */
+#if SM_CONF_SHM
+extern void upd_qs __P((ENVELOPE *, bool, bool));
+# define updfs(e, delete, avail) upd_qs(e, delete, avail)
+#else /* SM_CONF_SHM */
+# define updfs(e, delete, avail)
+#endif /* SM_CONF_SHM */
+
+extern char *username __P((void));
+extern bool usershellok __P((char *, char *));
+extern void vendor_post_defaults __P((ENVELOPE *));
+extern void vendor_pre_defaults __P((ENVELOPE *));
+extern int waitfor __P((pid_t));
+extern bool writable __P((char *, ADDRESS *, long));
+#if SM_HEAP_CHECK
+# define xalloc(size) xalloc_tagged(size, __FILE__, __LINE__)
+extern char *xalloc_tagged __P((int, char*, int));
+#else /* SM_HEAP_CHECK */
+extern char *xalloc __P((int));
+#endif /* SM_HEAP_CHECK */
+extern void xputs __P((const char *));
+extern char *xtextify __P((char *, char *));
+extern bool xtextok __P((char *));
+extern int xunlink __P((char *));
+extern char *xuntextify __P((char *));
+
+
+#endif /* ! _SENDMAIL_H */
diff --git a/contrib/sendmail/src/sfsasl.c b/contrib/sendmail/src/sfsasl.c
new file mode 100644
index 0000000..dc87429
--- /dev/null
+++ b/contrib/sendmail/src/sfsasl.c
@@ -0,0 +1,739 @@
+/*
+ * Copyright (c) 1999-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: sfsasl.c,v 8.91.2.1 2002/08/27 01:35:17 ca Exp $")
+#include <stdlib.h>
+#include <sendmail.h>
+#include <errno.h>
+#if SASL
+# include "sfsasl.h"
+
+/* Structure used by the "sasl" file type */
+struct sasl_obj
+{
+ SM_FILE_T *fp;
+ sasl_conn_t *conn;
+};
+
+struct sasl_info
+{
+ SM_FILE_T *fp;
+ sasl_conn_t *conn;
+};
+
+/*
+** SASL_GETINFO - returns requested information about a "sasl" file
+** descriptor.
+**
+** Parameters:
+** fp -- the file descriptor
+** what -- the type of information requested
+** valp -- the thang to return the information in
+**
+** Returns:
+** -1 for unknown requests
+** >=0 on success with valp filled in (if possible).
+*/
+
+static int sasl_getinfo __P((SM_FILE_T *, int, void *));
+
+static int
+sasl_getinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie;
+
+ switch (what)
+ {
+ case SM_IO_WHAT_FD:
+ if (so->fp == NULL)
+ return -1;
+ return so->fp->f_file; /* for stdio fileno() compatability */
+
+ case SM_IO_IS_READABLE:
+ if (so->fp == NULL)
+ return 0;
+
+ /* get info from underlying file */
+ return sm_io_getinfo(so->fp, what, valp);
+
+ default:
+ return -1;
+ }
+}
+
+/*
+** SASL_OPEN -- creates the sasl specific information for opening a
+** file of the sasl type.
+**
+** Parameters:
+** fp -- the file pointer associated with the new open
+** info -- contains the sasl connection information pointer and
+** the original SM_FILE_T that holds the open
+** flags -- ignored
+** rpool -- ignored
+**
+** Returns:
+** 0 on success
+*/
+
+static int sasl_open __P((SM_FILE_T *, const void *, int, const void *));
+
+/* ARGSUSED2 */
+static int
+sasl_open(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ struct sasl_obj *so;
+ struct sasl_info *si = (struct sasl_info *) info;
+
+ so = (struct sasl_obj *) sm_malloc(sizeof(struct sasl_obj));
+ so->fp = si->fp;
+ so->conn = si->conn;
+
+ /*
+ ** The underlying 'fp' is set to SM_IO_NOW so that the entire
+ ** encoded string is written in one chunk. Otherwise there is
+ ** the possibility that it may appear illegal, bogus or
+ ** mangled to the other side of the connection.
+ ** We will read or write through 'fp' since it is the opaque
+ ** connection for the communications. We need to treat it this
+ ** way in case the encoded string is to be sent down a TLS
+ ** connection rather than, say, sm_io's stdio.
+ */
+
+ (void) sm_io_setvbuf(so->fp, SM_TIME_DEFAULT, NULL, SM_IO_NOW, 0);
+ fp->f_cookie = so;
+ return 0;
+}
+
+/*
+** SASL_CLOSE -- close the sasl specific parts of the sasl file pointer
+**
+** Parameters:
+** fp -- the file pointer to close
+**
+** Returns:
+** 0 on success
+*/
+
+static int sasl_close __P((SM_FILE_T *));
+
+static int
+sasl_close(fp)
+ SM_FILE_T *fp;
+{
+ struct sasl_obj *so;
+
+ so = (struct sasl_obj *) fp->f_cookie;
+ if (so->fp != NULL)
+ {
+ sm_io_close(so->fp, SM_TIME_DEFAULT);
+ so->fp = NULL;
+ }
+ sm_free(so);
+ so = NULL;
+ return 0;
+}
+
+/* how to deallocate a buffer allocated by SASL */
+extern void sm_sasl_free __P((void *));
+# define SASL_DEALLOC(b) sm_sasl_free(b)
+
+/*
+** SASL_READ -- read encrypted information and decrypt it for the caller
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- the location to place the decrypted information
+** size -- the number of bytes to read after decryption
+**
+** Results:
+** -1 on error
+** otherwise the number of bytes read
+*/
+
+static ssize_t sasl_read __P((SM_FILE_T *, char *, size_t));
+
+static ssize_t
+sasl_read(fp, buf, size)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t size;
+{
+ int result;
+ ssize_t len;
+# if SASL >= 20000
+ const char *outbuf = NULL;
+# else /* SASL >= 20000 */
+ static char *outbuf = NULL;
+# endif /* SASL >= 20000 */
+ static unsigned int outlen = 0;
+ static unsigned int offset = 0;
+ struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie;
+
+ /*
+ ** sasl_decode() may require more data than a single read() returns.
+ ** Hence we have to put a loop around the decoding.
+ ** This also requires that we may have to split up the returned
+ ** data since it might be larger than the allowed size.
+ ** Therefore we use a static pointer and return portions of it
+ ** if necessary.
+ */
+
+ while (outbuf == NULL && outlen == 0)
+ {
+ len = sm_io_read(so->fp, SM_TIME_DEFAULT, buf, size);
+ if (len <= 0)
+ return len;
+ result = sasl_decode(so->conn, buf,
+ (unsigned int) len, &outbuf, &outlen);
+ if (result != SASL_OK)
+ {
+ outbuf = NULL;
+ offset = 0;
+ outlen = 0;
+ return -1;
+ }
+ }
+
+ if (outbuf == NULL)
+ {
+ /* be paranoid: outbuf == NULL but outlen != 0 */
+ syserr("@sasl_read failure: outbuf == NULL but outlen != 0");
+ /* NOTREACHED */
+ }
+ if (outlen - offset > size)
+ {
+ /* return another part of the buffer */
+ (void) memcpy(buf, outbuf + offset, size);
+ offset += size;
+ len = size;
+ }
+ else
+ {
+ /* return the rest of the buffer */
+ len = outlen - offset;
+ (void) memcpy(buf, outbuf + offset, (size_t) len);
+# if SASL < 20000
+ SASL_DEALLOC(outbuf);
+# endif /* SASL < 20000 */
+ outbuf = NULL;
+ offset = 0;
+ outlen = 0;
+ }
+ return len;
+}
+
+/*
+** SASL_WRITE -- write information out after encrypting it
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- holds the data to be encrypted and written
+** size -- the number of bytes to have encrypted and written
+**
+** Returns:
+** -1 on error
+** otherwise number of bytes written
+*/
+
+static ssize_t sasl_write __P((SM_FILE_T *, const char *, size_t));
+
+static ssize_t
+sasl_write(fp, buf, size)
+ SM_FILE_T *fp;
+ const char *buf;
+ size_t size;
+{
+ int result;
+# if SASL >= 20000
+ const char *outbuf;
+# else /* SASL >= 20000 */
+ char *outbuf;
+# endif /* SASL >= 20000 */
+ unsigned int outlen;
+ size_t ret = 0, total = 0;
+ struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie;
+
+ result = sasl_encode(so->conn, buf,
+ (unsigned int) size, &outbuf, &outlen);
+
+ if (result != SASL_OK)
+ return -1;
+
+ if (outbuf != NULL)
+ {
+ while (outlen > 0)
+ {
+ /* XXX result == 0? */
+ ret = sm_io_write(so->fp, SM_TIME_DEFAULT,
+ &outbuf[total], outlen);
+ outlen -= ret;
+ total += ret;
+ }
+# if SASL < 20000
+ SASL_DEALLOC(outbuf);
+# endif /* SASL < 20000 */
+ }
+ return size;
+}
+
+/*
+** SFDCSASL -- create sasl file type and open in and out file pointers
+** for sendmail to read from and write to.
+**
+** Parameters:
+** fin -- the sm_io file encrypted data to be read from
+** fout -- the sm_io file encrypted data to be writen to
+** conn -- the sasl connection pointer
+**
+** Returns:
+** -1 on error
+** 0 on success
+**
+** Side effects:
+** The arguments "fin" and "fout" are replaced with the new
+** SM_FILE_T pointers.
+*/
+
+int
+sfdcsasl(fin, fout, conn)
+ SM_FILE_T **fin;
+ SM_FILE_T **fout;
+ sasl_conn_t *conn;
+{
+ SM_FILE_T *newin, *newout;
+ SM_FILE_T SM_IO_SET_TYPE(sasl_vector, "sasl", sasl_open, sasl_close,
+ sasl_read, sasl_write, NULL, sasl_getinfo, NULL,
+ SM_TIME_FOREVER);
+ struct sasl_info info;
+
+ if (conn == NULL)
+ {
+ /* no need to do anything */
+ return 0;
+ }
+
+ SM_IO_INIT_TYPE(sasl_vector, "sasl", sasl_open, sasl_close,
+ sasl_read, sasl_write, NULL, sasl_getinfo, NULL,
+ SM_TIME_FOREVER);
+ info.fp = *fin;
+ info.conn = conn;
+ newin = sm_io_open(&sasl_vector, SM_TIME_DEFAULT, &info, SM_IO_RDONLY,
+ NULL);
+
+ if (newin == NULL)
+ return -1;
+
+ info.fp = *fout;
+ info.conn = conn;
+ newout = sm_io_open(&sasl_vector, SM_TIME_DEFAULT, &info, SM_IO_WRONLY,
+ NULL);
+
+ if (newout == NULL)
+ {
+ (void) sm_io_close(newin, SM_TIME_DEFAULT);
+ return -1;
+ }
+ sm_io_automode(newin, newout);
+
+ *fin = newin;
+ *fout = newout;
+ return 0;
+}
+#endif /* SASL */
+
+#if STARTTLS
+# include "sfsasl.h"
+# include <openssl/err.h>
+
+/* Structure used by the "tls" file type */
+struct tls_obj
+{
+ SM_FILE_T *fp;
+ SSL *con;
+};
+
+struct tls_info
+{
+ SM_FILE_T *fp;
+ SSL *con;
+};
+
+/*
+** TLS_GETINFO - returns requested information about a "tls" file
+** descriptor.
+**
+** Parameters:
+** fp -- the file descriptor
+** what -- the type of information requested
+** valp -- the thang to return the information in (unused)
+**
+** Returns:
+** -1 for unknown requests
+** >=0 on success with valp filled in (if possible).
+*/
+
+static int tls_getinfo __P((SM_FILE_T *, int, void *));
+
+/* ARGSUSED2 */
+static int
+tls_getinfo(fp, what, valp)
+ SM_FILE_T *fp;
+ int what;
+ void *valp;
+{
+ struct tls_obj *so = (struct tls_obj *) fp->f_cookie;
+
+ switch (what)
+ {
+ case SM_IO_WHAT_FD:
+ if (so->fp == NULL)
+ return -1;
+ return so->fp->f_file; /* for stdio fileno() compatability */
+
+ case SM_IO_IS_READABLE:
+ return SSL_pending(so->con) > 0;
+
+ default:
+ return -1;
+ }
+}
+
+/*
+** TLS_OPEN -- creates the tls specific information for opening a
+** file of the tls type.
+**
+** Parameters:
+** fp -- the file pointer associated with the new open
+** info -- the sm_io file pointer holding the open and the
+** TLS encryption connection to be read from or written to
+** flags -- ignored
+** rpool -- ignored
+**
+** Returns:
+** 0 on success
+*/
+
+static int tls_open __P((SM_FILE_T *, const void *, int, const void *));
+
+/* ARGSUSED2 */
+static int
+tls_open(fp, info, flags, rpool)
+ SM_FILE_T *fp;
+ const void *info;
+ int flags;
+ const void *rpool;
+{
+ struct tls_obj *so;
+ struct tls_info *ti = (struct tls_info *) info;
+
+ so = (struct tls_obj *) sm_malloc(sizeof(struct tls_obj));
+ so->fp = ti->fp;
+ so->con = ti->con;
+
+ /*
+ ** We try to get the "raw" file descriptor that TLS uses to
+ ** do the actual read/write with. This is to allow us control
+ ** over the file descriptor being a blocking or non-blocking type.
+ ** Under the covers TLS handles the change and this allows us
+ ** to do timeouts with sm_io.
+ */
+
+ fp->f_file = sm_io_getinfo(so->fp, SM_IO_WHAT_FD, NULL);
+ (void) sm_io_setvbuf(so->fp, SM_TIME_DEFAULT, NULL, SM_IO_NOW, 0);
+ fp->f_cookie = so;
+ return 0;
+}
+
+/*
+** TLS_CLOSE -- close the tls specific parts of the tls file pointer
+**
+** Parameters:
+** fp -- the file pointer to close
+**
+** Returns:
+** 0 on success
+*/
+
+static int tls_close __P((SM_FILE_T *));
+
+static int
+tls_close(fp)
+ SM_FILE_T *fp;
+{
+ struct tls_obj *so;
+
+ so = (struct tls_obj *) fp->f_cookie;
+ if (so->fp != NULL)
+ {
+ sm_io_close(so->fp, SM_TIME_DEFAULT);
+ so->fp = NULL;
+ }
+ sm_free(so);
+ so = NULL;
+ return 0;
+}
+
+/* maximum number of retries for TLS related I/O due to handshakes */
+# define MAX_TLS_IOS 4
+
+/*
+** TLS_READ -- read secured information for the caller
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- the location to place the data
+** size -- the number of bytes to read from connection
+**
+** Results:
+** -1 on error
+** otherwise the number of bytes read
+*/
+
+static ssize_t tls_read __P((SM_FILE_T *, char *, size_t));
+
+static ssize_t
+tls_read(fp, buf, size)
+ SM_FILE_T *fp;
+ char *buf;
+ size_t size;
+{
+ int r;
+ static int again = MAX_TLS_IOS;
+ struct tls_obj *so = (struct tls_obj *) fp->f_cookie;
+ char *err;
+
+ r = SSL_read(so->con, (char *) buf, size);
+
+ if (r > 0)
+ {
+ again = MAX_TLS_IOS;
+ return r;
+ }
+
+ err = NULL;
+ switch (SSL_get_error(so->con, r))
+ {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ again = MAX_TLS_IOS;
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ if (--again <= 0)
+ err = "read W BLOCK";
+ else
+ errno = EAGAIN;
+ break;
+ case SSL_ERROR_WANT_READ:
+ if (--again <= 0)
+ err = "read R BLOCK";
+ else
+ errno = EAGAIN;
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ err = "write X BLOCK";
+ break;
+ case SSL_ERROR_SYSCALL:
+ if (r == 0 && errno == 0) /* out of protocol EOF found */
+ break;
+ err = "syscall error";
+/*
+ get_last_socket_error());
+*/
+ break;
+ case SSL_ERROR_SSL:
+#if _FFR_DEAL_WITH_ERROR_SSL
+ if (r == 0 && errno == 0) /* out of protocol EOF found */
+ break;
+#endif /* _FFR_DEAL_WITH_ERROR_SSL */
+ err = "generic SSL error";
+ if (LogLevel > 9)
+ tlslogerr("read");
+
+#if _FFR_DEAL_WITH_ERROR_SSL
+ /* avoid repeated calls? */
+ if (r == 0)
+ r = -1;
+#endif /* _FFR_DEAL_WITH_ERROR_SSL */
+ break;
+ }
+ if (err != NULL)
+ {
+ int save_errno;
+
+ save_errno = (errno == 0) ? EIO : errno;
+ again = MAX_TLS_IOS;
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: read error=%s (%d)", err, r);
+ errno = save_errno;
+ }
+ return r;
+}
+
+/*
+** TLS_WRITE -- write information out through secure connection
+**
+** Parameters:
+** fp -- the file pointer
+** buf -- holds the data to be securely written
+** size -- the number of bytes to write
+**
+** Returns:
+** -1 on error
+** otherwise number of bytes written
+*/
+
+static ssize_t tls_write __P((SM_FILE_T *, const char *, size_t));
+
+static ssize_t
+tls_write(fp, buf, size)
+ SM_FILE_T *fp;
+ const char *buf;
+ size_t size;
+{
+ int r;
+ static int again = MAX_TLS_IOS;
+ struct tls_obj *so = (struct tls_obj *) fp->f_cookie;
+ char *err;
+
+ r = SSL_write(so->con, (char *) buf, size);
+
+ if (r > 0)
+ {
+ again = MAX_TLS_IOS;
+ return r;
+ }
+ err = NULL;
+ switch (SSL_get_error(so->con, r))
+ {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ again = MAX_TLS_IOS;
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ if (--again <= 0)
+ err = "write W BLOCK";
+ else
+ errno = EAGAIN;
+ break;
+ case SSL_ERROR_WANT_READ:
+ if (--again <= 0)
+ err = "write R BLOCK";
+ else
+ errno = EAGAIN;
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ err = "write X BLOCK";
+ break;
+ case SSL_ERROR_SYSCALL:
+ if (r == 0 && errno == 0) /* out of protocol EOF found */
+ break;
+ err = "syscall error";
+/*
+ get_last_socket_error());
+*/
+ break;
+ case SSL_ERROR_SSL:
+ err = "generic SSL error";
+/*
+ ERR_GET_REASON(ERR_peek_error()));
+*/
+ if (LogLevel > 9)
+ tlslogerr("write");
+
+#if _FFR_DEAL_WITH_ERROR_SSL
+ /* avoid repeated calls? */
+ if (r == 0)
+ r = -1;
+#endif /* _FFR_DEAL_WITH_ERROR_SSL */
+ break;
+ }
+ if (err != NULL)
+ {
+ int save_errno;
+
+ save_errno = (errno == 0) ? EIO : errno;
+ again = MAX_TLS_IOS;
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: write error=%s (%d)", err, r);
+ errno = save_errno;
+ }
+ return r;
+}
+
+/*
+** SFDCTLS -- create tls file type and open in and out file pointers
+** for sendmail to read from and write to.
+**
+** Parameters:
+** fin -- data input source being replaced
+** fout -- data output source being replaced
+** conn -- the tls connection pointer
+**
+** Returns:
+** -1 on error
+** 0 on success
+**
+** Side effects:
+** The arguments "fin" and "fout" are replaced with the new
+** SM_FILE_T pointers.
+** The original "fin" and "fout" are preserved in the tls file
+** type but are not actually used because of the design of TLS.
+*/
+
+int
+sfdctls(fin, fout, con)
+ SM_FILE_T **fin;
+ SM_FILE_T **fout;
+ SSL *con;
+{
+ SM_FILE_T *tlsin, *tlsout;
+ SM_FILE_T SM_IO_SET_TYPE(tls_vector, "tls", tls_open, tls_close,
+ tls_read, tls_write, NULL, tls_getinfo, NULL,
+ SM_TIME_FOREVER);
+ struct tls_info info;
+
+ SM_ASSERT(con != NULL);
+
+ SM_IO_INIT_TYPE(tls_vector, "tls", tls_open, tls_close,
+ tls_read, tls_write, NULL, tls_getinfo, NULL,
+ SM_TIME_FOREVER);
+ info.fp = *fin;
+ info.con = con;
+ tlsin = sm_io_open(&tls_vector, SM_TIME_DEFAULT, &info, SM_IO_RDONLY,
+ NULL);
+ if (tlsin == NULL)
+ return -1;
+
+ info.fp = *fout;
+ tlsout = sm_io_open(&tls_vector, SM_TIME_DEFAULT, &info, SM_IO_WRONLY,
+ NULL);
+ if (tlsout == NULL)
+ {
+ (void) sm_io_close(tlsin, SM_TIME_DEFAULT);
+ return -1;
+ }
+ sm_io_automode(tlsin, tlsout);
+
+ *fin = tlsin;
+ *fout = tlsout;
+ return 0;
+}
+#endif /* STARTTLS */
diff --git a/contrib/sendmail/src/sfsasl.h b/contrib/sendmail/src/sfsasl.h
new file mode 100644
index 0000000..c75418a
--- /dev/null
+++ b/contrib/sendmail/src/sfsasl.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 1999, 2000 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: sfsasl.h,v 8.17 2000/09/19 21:30:49 ca Exp $"
+ */
+
+#ifndef SFSASL_H
+# define SFSASL_H
+
+#if SASL
+extern int sfdcsasl __P((SM_FILE_T **, SM_FILE_T **, sasl_conn_t *));
+#endif /* SASL */
+
+# if STARTTLS
+extern int sfdctls __P((SM_FILE_T **, SM_FILE_T **, SSL *));
+# endif /* STARTTLS */
+
+#endif /* ! SFSASL_H */
diff --git a/contrib/sendmail/src/shmticklib.c b/contrib/sendmail/src/shmticklib.c
new file mode 100644
index 0000000..6f5e301
--- /dev/null
+++ b/contrib/sendmail/src/shmticklib.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 1999-2000 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * Contributed by Exactis.com, Inc.
+ *
+ */
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: shmticklib.c,v 8.14 2001/09/11 04:05:16 gshapiro Exp $")
+
+#if _FFR_SHM_STATUS
+# include <sys/types.h>
+# include <sys/ipc.h>
+# include <sys/shm.h>
+
+# include "statusd_shm.h"
+
+/*
+** SHMTICK -- increment a shared memory variable
+**
+** Parameters:
+** inc_me -- identity of shared memory segment
+** what -- which variable to increment
+**
+** Returns:
+** none
+*/
+
+void
+shmtick(inc_me, what)
+ int inc_me;
+ int what;
+{
+ static int shmid = -1;
+ static STATUSD_SHM *sp = (STATUSD_SHM *)-1;
+ static unsigned int cookie = 0;
+
+ if (shmid < 0)
+ {
+ int size = sizeof(STATUSD_SHM);
+
+ shmid = shmget(STATUSD_SHM_KEY, size, 0);
+ if (shmid < 0)
+ return;
+ }
+ if ((unsigned long *) sp == (unsigned long *)-1)
+ {
+ sp = (STATUSD_SHM *) shmat(shmid, NULL, 0);
+ if ((unsigned long *) sp == (unsigned long *) -1)
+ return;
+ }
+ if (sp->magic != STATUSD_MAGIC)
+ {
+ /*
+ ** possible race condition, wait for
+ ** statusd to initialize.
+ */
+
+ return;
+ }
+ if (what >= STATUSD_LONGS)
+ what = STATUSD_LONGS - 1;
+ if (inc_me >= STATUSD_LONGS)
+ inc_me = STATUSD_LONGS - 1;
+
+ if (sp->ul[STATUSD_COOKIE] != cookie)
+ {
+ cookie = sp->ul[STATUSD_COOKIE];
+ ++(sp->ul[inc_me]);
+ }
+ ++(sp->ul[what]);
+}
+#endif /* _FFR_SHM_STATUS */
diff --git a/contrib/sendmail/src/sm_resolve.c b/contrib/sendmail/src/sm_resolve.c
new file mode 100644
index 0000000..a6f5862
--- /dev/null
+++ b/contrib/sendmail/src/sm_resolve.c
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+/*
+ * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sendmail.h>
+#if DNSMAP
+# if NAMED_BIND
+# include "sm_resolve.h"
+
+SM_RCSID("$Id: sm_resolve.c,v 8.24.4.6 2002/06/25 04:22:41 ca Exp $")
+
+static struct stot
+{
+ const char *st_name;
+ int st_type;
+} stot[] =
+{
+# if NETINET
+ { "A", T_A },
+# endif /* NETINET */
+# if NETINET6
+ { "AAAA", T_AAAA },
+# endif /* NETINET6 */
+ { "NS", T_NS },
+ { "CNAME", T_CNAME },
+ { "PTR", T_PTR },
+ { "MX", T_MX },
+ { "TXT", T_TXT },
+ { "AFSDB", T_AFSDB },
+ { "SRV", T_SRV },
+ { NULL, 0 }
+};
+
+/*
+** DNS_STRING_TO_TYPE -- convert resource record name into type
+**
+** Parameters:
+** name -- name of resource record type
+**
+** Returns:
+** type if succeeded.
+** -1 otherwise.
+*/
+
+int
+dns_string_to_type(name)
+ const char *name;
+{
+ struct stot *p = stot;
+
+ for (p = stot; p->st_name != NULL; p++)
+ if (sm_strcasecmp(name, p->st_name) == 0)
+ return p->st_type;
+ return -1;
+}
+
+/*
+** DNS_TYPE_TO_STRING -- convert resource record type into name
+**
+** Parameters:
+** type -- resource record type
+**
+** Returns:
+** name if succeeded.
+** NULL otherwise.
+*/
+
+const char *
+dns_type_to_string(type)
+ int type;
+{
+ struct stot *p = stot;
+
+ for (p = stot; p->st_name != NULL; p++)
+ if (type == p->st_type)
+ return p->st_name;
+ return NULL;
+}
+
+/*
+** DNS_FREE_DATA -- free all components of a DNS_REPLY_T
+**
+** Parameters:
+** r -- pointer to DNS_REPLY_T
+**
+** Returns:
+** none.
+*/
+
+void
+dns_free_data(r)
+ DNS_REPLY_T *r;
+{
+ RESOURCE_RECORD_T *rr;
+
+ if (r->dns_r_q.dns_q_domain != NULL)
+ sm_free(r->dns_r_q.dns_q_domain);
+ for (rr = r->dns_r_head; rr != NULL; )
+ {
+ RESOURCE_RECORD_T *tmp = rr;
+
+ if (rr->rr_domain != NULL)
+ sm_free(rr->rr_domain);
+ if (rr->rr_u.rr_data != NULL)
+ sm_free(rr->rr_u.rr_data);
+ rr = rr->rr_next;
+ sm_free(tmp);
+ }
+ sm_free(r);
+}
+
+/*
+** PARSE_DNS_REPLY -- parse DNS reply data.
+**
+** Parameters:
+** data -- pointer to dns data
+** len -- len of data
+**
+** Returns:
+** pointer to DNS_REPLY_T if succeeded.
+** NULL otherwise.
+*/
+
+static DNS_REPLY_T *
+parse_dns_reply(data, len)
+ unsigned char *data;
+ int len;
+{
+ unsigned char *p;
+ int status;
+ size_t l;
+ char host[MAXHOSTNAMELEN];
+ DNS_REPLY_T *r;
+ RESOURCE_RECORD_T **rr;
+
+ r = (DNS_REPLY_T *) xalloc(sizeof(*r));
+ memset(r, 0, sizeof(*r));
+ if (r == NULL)
+ return NULL;
+
+ p = data;
+
+ /* doesn't work on Crays? */
+ memcpy(&r->dns_r_h, p, sizeof(r->dns_r_h));
+ p += sizeof(r->dns_r_h);
+ status = dn_expand(data, data + len, p, host, sizeof host);
+ if (status < 0)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ r->dns_r_q.dns_q_domain = sm_strdup(host);
+ if (r->dns_r_q.dns_q_domain == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ p += status;
+ GETSHORT(r->dns_r_q.dns_q_type, p);
+ GETSHORT(r->dns_r_q.dns_q_class, p);
+ rr = &r->dns_r_head;
+ while (p < data + len)
+ {
+ int type, class, ttl, size, txtlen;
+
+ status = dn_expand(data, data + len, p, host, sizeof host);
+ if (status < 0)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ p += status;
+ GETSHORT(type, p);
+ GETSHORT(class, p);
+ GETLONG(ttl, p);
+ GETSHORT(size, p);
+ if (p + size > data + len)
+ {
+ /*
+ ** announced size of data exceeds length of
+ ** data paket: someone is cheating.
+ */
+
+ if (LogLevel > 5)
+ sm_syslog(LOG_WARNING, NOQID,
+ "ERROR: DNS RDLENGTH=%d > data len=%d",
+ size, len - (p - data));
+ dns_free_data(r);
+ return NULL;
+ }
+ *rr = (RESOURCE_RECORD_T *) xalloc(sizeof(**rr));
+ if (*rr == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_domain = sm_strdup(host);
+ if ((*rr)->rr_domain == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_type = type;
+ (*rr)->rr_class = class;
+ (*rr)->rr_ttl = ttl;
+ (*rr)->rr_size = size;
+ switch (type)
+ {
+ case T_NS:
+ case T_CNAME:
+ case T_PTR:
+ status = dn_expand(data, data + len, p, host,
+ sizeof host);
+ if (status < 0)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_u.rr_txt = sm_strdup(host);
+ if ((*rr)->rr_u.rr_txt == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ break;
+
+ case T_MX:
+ case T_AFSDB:
+ status = dn_expand(data, data + len, p + 2, host,
+ sizeof host);
+ if (status < 0)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ l = strlen(host) + 1;
+ (*rr)->rr_u.rr_mx = (MX_RECORD_T *)
+ xalloc(sizeof(*((*rr)->rr_u.rr_mx)) + l);
+ if ((*rr)->rr_u.rr_mx == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_u.rr_mx->mx_r_preference = (p[0] << 8) | p[1];
+ (void) sm_strlcpy((*rr)->rr_u.rr_mx->mx_r_domain,
+ host, l);
+ break;
+
+ case T_SRV:
+ status = dn_expand(data, data + len, p + 6, host,
+ sizeof host);
+ if (status < 0)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ l = strlen(host) + 1;
+ (*rr)->rr_u.rr_srv = (SRV_RECORDT_T*)
+ xalloc(sizeof(*((*rr)->rr_u.rr_srv)) + l);
+ if ((*rr)->rr_u.rr_srv == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_u.rr_srv->srv_r_priority = (p[0] << 8) | p[1];
+ (*rr)->rr_u.rr_srv->srv_r_weight = (p[2] << 8) | p[3];
+ (*rr)->rr_u.rr_srv->srv_r_port = (p[4] << 8) | p[5];
+ (void) sm_strlcpy((*rr)->rr_u.rr_srv->srv_r_target,
+ host, l);
+ break;
+
+ case T_TXT:
+
+ /*
+ ** The TXT record contains the length as
+ ** leading byte, hence the value is restricted
+ ** to 255, which is less than the maximum value
+ ** of RDLENGTH (size). Nevertheless, txtlen
+ ** must be less than size because the latter
+ ** specifies the length of the entire TXT
+ ** record.
+ */
+
+ txtlen = *p;
+ if (txtlen >= size)
+ {
+ if (LogLevel > 5)
+ sm_syslog(LOG_WARNING, NOQID,
+ "ERROR: DNS TXT record size=%d <= text len=%d",
+ size, txtlen);
+ dns_free_data(r);
+ return NULL;
+ }
+ (*rr)->rr_u.rr_txt = (char *) xalloc(txtlen + 1);
+ if ((*rr)->rr_u.rr_txt == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (void) sm_strlcpy((*rr)->rr_u.rr_txt, (char*) p + 1,
+ txtlen + 1);
+ break;
+
+ default:
+ (*rr)->rr_u.rr_data = (unsigned char*) xalloc(size);
+ if (size != 0 && (*rr)->rr_u.rr_data == NULL)
+ {
+ dns_free_data(r);
+ return NULL;
+ }
+ (void) memcpy((*rr)->rr_u.rr_data, p, size);
+ break;
+ }
+ p += size;
+ rr = &(*rr)->rr_next;
+ }
+ *rr = NULL;
+ return r;
+}
+
+/*
+** DNS_LOOKUP_INT -- perform dns map lookup (internal helper routine)
+**
+** Parameters:
+** domain -- name to lookup
+** rr_class -- resource record class
+** rr_type -- resource record type
+** retrans -- retransmission timeout
+** retry -- number of retries
+**
+** Returns:
+** result of lookup if succeeded.
+** NULL otherwise.
+*/
+
+DNS_REPLY_T *
+dns_lookup_int(domain, rr_class, rr_type, retrans, retry)
+ const char *domain;
+ int rr_class;
+ int rr_type;
+ time_t retrans;
+ int retry;
+{
+ int len;
+ unsigned long old_options = 0;
+ time_t save_retrans = 0;
+ int save_retry = 0;
+ DNS_REPLY_T *r = NULL;
+ unsigned char reply[1024];
+
+ if (tTd(8, 16))
+ {
+ old_options = _res.options;
+ _res.options |= RES_DEBUG;
+ sm_dprintf("dns_lookup(%s, %d, %s)\n", domain,
+ rr_class, dns_type_to_string(rr_type));
+ }
+ if (retrans > 0)
+ {
+ save_retrans = _res.retrans;
+ _res.retrans = retrans;
+ }
+ if (retry > 0)
+ {
+ save_retry = _res.retry;
+ _res.retry = retry;
+ }
+ errno = 0;
+ SM_SET_H_ERRNO(0);
+ len = res_search(domain, rr_class, rr_type, reply, sizeof reply);
+ if (tTd(8, 16))
+ {
+ _res.options = old_options;
+ sm_dprintf("dns_lookup(%s, %d, %s) --> %d\n",
+ domain, rr_class, dns_type_to_string(rr_type), len);
+ }
+ if (len >= 0)
+ r = parse_dns_reply(reply, len);
+ if (retrans > 0)
+ _res.retrans = save_retrans;
+ if (retry > 0)
+ _res.retry = save_retry;
+ return r;
+}
+
+# if 0
+DNS_REPLY_T *
+dns_lookup(domain, type_name, retrans, retry)
+ const char *domain;
+ const char *type_name;
+ time_t retrans;
+ int retry;
+{
+ int type;
+
+ type = dns_string_to_type(type_name);
+ if (type == -1)
+ {
+ if (tTd(8, 16))
+ sm_dprintf("dns_lookup: unknown resource type: `%s'\n",
+ type_name);
+ return NULL;
+ }
+ return dns_lookup_int(domain, C_IN, type, retrans, retry);
+}
+# endif /* 0 */
+# endif /* NAMED_BIND */
+#endif /* DNSMAP */
diff --git a/contrib/sendmail/src/sm_resolve.h b/contrib/sendmail/src/sm_resolve.h
new file mode 100644
index 0000000..7f169ba
--- /dev/null
+++ b/contrib/sendmail/src/sm_resolve.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2000-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+/*
+ * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan
+ * (Royal Institute of Technology, Stockholm, Sweden).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the Institute nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* $Id: sm_resolve.h,v 8.8 2001/09/01 00:06:02 gshapiro Exp $ */
+
+#if DNSMAP
+# ifndef __ROKEN_RESOLVE_H__
+# define __ROKEN_RESOLVE_H__
+
+/* We use these, but they are not always present in <arpa/nameser.h> */
+
+# ifndef T_TXT
+# define T_TXT 16
+# endif /* ! T_TXT */
+# ifndef T_AFSDB
+# define T_AFSDB 18
+# endif /* ! T_AFSDB */
+# ifndef T_SRV
+# define T_SRV 33
+# endif /* ! T_SRV */
+# ifndef T_NAPTR
+# define T_NAPTR 35
+# endif /* ! T_NAPTR */
+
+typedef struct
+{
+ char *dns_q_domain;
+ unsigned int dns_q_type;
+ unsigned int dns_q_class;
+} DNS_QUERY_T;
+
+typedef struct
+{
+ unsigned int mx_r_preference;
+ char mx_r_domain[1];
+} MX_RECORD_T;
+
+typedef struct
+{
+ unsigned int srv_r_priority;
+ unsigned int srv_r_weight;
+ unsigned int srv_r_port;
+ char srv_r_target[1];
+} SRV_RECORDT_T;
+
+
+typedef struct resource_record RESOURCE_RECORD_T;
+
+struct resource_record
+{
+ char *rr_domain;
+ unsigned int rr_type;
+ unsigned int rr_class;
+ unsigned int rr_ttl;
+ unsigned int rr_size;
+ union
+ {
+ void *rr_data;
+ MX_RECORD_T *rr_mx;
+ MX_RECORD_T *rr_afsdb; /* mx and afsdb are identical */
+ SRV_RECORDT_T *rr_srv;
+# if NETINET
+ struct in_addr *rr_a;
+# endif /* NETINET */
+# if NETINET6
+ struct in6_addr *rr_aaaa;
+# endif /* NETINET6 */
+ char *rr_txt;
+ } rr_u;
+ RESOURCE_RECORD_T *rr_next;
+};
+
+# if !defined(T_A) && !defined(T_AAAA)
+/* XXX if <arpa/nameser.h> isn't included */
+typedef int HEADER; /* will never be used */
+# endif /* !defined(T_A) && !defined(T_AAAA) */
+
+typedef struct
+{
+ HEADER dns_r_h;
+ DNS_QUERY_T dns_r_q;
+ RESOURCE_RECORD_T *dns_r_head;
+} DNS_REPLY_T;
+
+
+extern void dns_free_data __P((DNS_REPLY_T *));
+extern int dns_string_to_type __P((const char *));
+extern const char *dns_type_to_string __P((int));
+extern DNS_REPLY_T *dns_lookup_int __P((const char *,
+ int,
+ int,
+ time_t,
+ int));
+# if 0
+extern DNS_REPLY_T *dns_lookup __P((const char *domain,
+ const char *type_name,
+ time_t retrans,
+ int retry));
+# endif /* 0 */
+
+# endif /* ! __ROKEN_RESOLVE_H__ */
+#endif /* DNSMAP */
diff --git a/contrib/sendmail/src/srvrsmtp.c b/contrib/sendmail/src/srvrsmtp.c
new file mode 100644
index 0000000..8bbc029
--- /dev/null
+++ b/contrib/sendmail/src/srvrsmtp.c
@@ -0,0 +1,4238 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+#if MILTER
+# include <libmilter/mfdef.h>
+#endif /* MILTER */
+
+SM_RCSID("@(#)$Id: srvrsmtp.c,v 8.829.2.4 2002/08/16 14:56:01 ca Exp $")
+
+#if SASL || STARTTLS
+# include <sys/time.h>
+# include "sfsasl.h"
+#endif /* SASL || STARTTLS */
+#if SASL
+# define ENC64LEN(l) (((l) + 2) * 4 / 3 + 1)
+static int saslmechs __P((sasl_conn_t *, char **));
+#endif /* SASL */
+#if STARTTLS
+# include <sysexits.h>
+
+static SSL_CTX *srv_ctx = NULL; /* TLS server context */
+static SSL *srv_ssl = NULL; /* per connection context */
+
+static bool tls_ok_srv = false;
+
+extern void tls_set_verify __P((SSL_CTX *, SSL *, bool));
+# define TLS_VERIFY_CLIENT() tls_set_verify(srv_ctx, srv_ssl, \
+ bitset(SRV_VRFY_CLT, features))
+#endif /* STARTTLS */
+
+/* server features */
+#define SRV_NONE 0x0000 /* none... */
+#define SRV_OFFER_TLS 0x0001 /* offer STARTTLS */
+#define SRV_VRFY_CLT 0x0002 /* request a cert */
+#define SRV_OFFER_AUTH 0x0004 /* offer AUTH */
+#define SRV_OFFER_ETRN 0x0008 /* offer ETRN */
+#define SRV_OFFER_VRFY 0x0010 /* offer VRFY (not yet used) */
+#define SRV_OFFER_EXPN 0x0020 /* offer EXPN */
+#define SRV_OFFER_VERB 0x0040 /* offer VERB */
+#define SRV_OFFER_DSN 0x0080 /* offer DSN */
+#if PIPELINING
+# define SRV_OFFER_PIPE 0x0100 /* offer PIPELINING */
+# if _FFR_NO_PIPE
+# define SRV_NO_PIPE 0x0200 /* disable PIPELINING, sleep if used */
+# endif /* _FFR_NO_PIPE */
+#endif /* PIPELINING */
+#define SRV_REQ_AUTH 0x0400 /* require AUTH */
+#define SRV_TMP_FAIL 0x1000 /* ruleset caused a temporary failure */
+
+static unsigned int srvfeatures __P((ENVELOPE *, char *, unsigned int));
+
+static time_t checksmtpattack __P((volatile unsigned int *, int, bool,
+ char *, ENVELOPE *));
+static void mail_esmtp_args __P((char *, char *, ENVELOPE *));
+static void printvrfyaddr __P((ADDRESS *, bool, bool));
+static void rcpt_esmtp_args __P((ADDRESS *, char *, char *, ENVELOPE *));
+static char *skipword __P((char *volatile, char *));
+static void setup_smtpd_io __P((void));
+extern ENVELOPE BlankEnvelope;
+
+#define SKIP_SPACE(s) while (isascii(*s) && isspace(*s)) \
+ (s)++
+
+/*
+** SMTP -- run the SMTP protocol.
+**
+** Parameters:
+** nullserver -- if non-NULL, rejection message for
+** (almost) all SMTP commands.
+** d_flags -- daemon flags
+** e -- the envelope.
+**
+** Returns:
+** never.
+**
+** Side Effects:
+** Reads commands from the input channel and processes them.
+*/
+
+/*
+** Notice: The smtp server doesn't have a session context like the client
+** side has (mci). Therefore some data (session oriented) is allocated
+** or assigned to the "wrong" structure (esp. STARTTLS, AUTH).
+** This should be fixed in a successor version.
+*/
+
+struct cmd
+{
+ char *cmd_name; /* command name */
+ int cmd_code; /* internal code, see below */
+};
+
+/* values for cmd_code */
+#define CMDERROR 0 /* bad command */
+#define CMDMAIL 1 /* mail -- designate sender */
+#define CMDRCPT 2 /* rcpt -- designate recipient */
+#define CMDDATA 3 /* data -- send message text */
+#define CMDRSET 4 /* rset -- reset state */
+#define CMDVRFY 5 /* vrfy -- verify address */
+#define CMDEXPN 6 /* expn -- expand address */
+#define CMDNOOP 7 /* noop -- do nothing */
+#define CMDQUIT 8 /* quit -- close connection and die */
+#define CMDHELO 9 /* helo -- be polite */
+#define CMDHELP 10 /* help -- give usage info */
+#define CMDEHLO 11 /* ehlo -- extended helo (RFC 1425) */
+#define CMDETRN 12 /* etrn -- flush queue */
+#if SASL
+# define CMDAUTH 13 /* auth -- SASL authenticate */
+#endif /* SASL */
+#if STARTTLS
+# define CMDSTLS 14 /* STARTTLS -- start TLS session */
+#endif /* STARTTLS */
+/* non-standard commands */
+#define CMDVERB 17 /* verb -- go into verbose mode */
+/* unimplemented commands from RFC 821 */
+#define CMDUNIMPL 19 /* unimplemented rfc821 commands */
+/* use this to catch and log "door handle" attempts on your system */
+#define CMDLOGBOGUS 23 /* bogus command that should be logged */
+/* debugging-only commands, only enabled if SMTPDEBUG is defined */
+#define CMDDBGQSHOW 24 /* showq -- show send queue */
+#define CMDDBGDEBUG 25 /* debug -- set debug mode */
+
+/*
+** Note: If you change this list, remember to update 'helpfile'
+*/
+
+static struct cmd CmdTab[] =
+{
+ { "mail", CMDMAIL },
+ { "rcpt", CMDRCPT },
+ { "data", CMDDATA },
+ { "rset", CMDRSET },
+ { "vrfy", CMDVRFY },
+ { "expn", CMDEXPN },
+ { "help", CMDHELP },
+ { "noop", CMDNOOP },
+ { "quit", CMDQUIT },
+ { "helo", CMDHELO },
+ { "ehlo", CMDEHLO },
+ { "etrn", CMDETRN },
+ { "verb", CMDVERB },
+ { "send", CMDUNIMPL },
+ { "saml", CMDUNIMPL },
+ { "soml", CMDUNIMPL },
+ { "turn", CMDUNIMPL },
+#if SASL
+ { "auth", CMDAUTH, },
+#endif /* SASL */
+#if STARTTLS
+ { "starttls", CMDSTLS, },
+#endif /* STARTTLS */
+ /* remaining commands are here only to trap and log attempts to use them */
+ { "showq", CMDDBGQSHOW },
+ { "debug", CMDDBGDEBUG },
+ { "wiz", CMDLOGBOGUS },
+
+ { NULL, CMDERROR }
+};
+
+static char *CurSmtpClient; /* who's at the other end of channel */
+
+#ifndef MAXBADCOMMANDS
+# define MAXBADCOMMANDS 25 /* maximum number of bad commands */
+#endif /* ! MAXBADCOMMANDS */
+#ifndef MAXNOOPCOMMANDS
+# define MAXNOOPCOMMANDS 20 /* max "noise" commands before slowdown */
+#endif /* ! MAXNOOPCOMMANDS */
+#ifndef MAXHELOCOMMANDS
+# define MAXHELOCOMMANDS 3 /* max HELO/EHLO commands before slowdown */
+#endif /* ! MAXHELOCOMMANDS */
+#ifndef MAXVRFYCOMMANDS
+# define MAXVRFYCOMMANDS 6 /* max VRFY/EXPN commands before slowdown */
+#endif /* ! MAXVRFYCOMMANDS */
+#ifndef MAXETRNCOMMANDS
+# define MAXETRNCOMMANDS 8 /* max ETRN commands before slowdown */
+#endif /* ! MAXETRNCOMMANDS */
+#ifndef MAXTIMEOUT
+# define MAXTIMEOUT (4 * 60) /* max timeout for bad commands */
+#endif /* ! MAXTIMEOUT */
+
+#if SM_HEAP_CHECK
+static SM_DEBUG_T DebugLeakSmtp = SM_DEBUG_INITIALIZER("leak_smtp",
+ "@(#)$Debug: leak_smtp - trace memory leaks during SMTP processing $");
+#endif /* SM_HEAP_CHECK */
+
+typedef struct
+{
+ bool sm_gotmail; /* mail command received */
+ unsigned int sm_nrcpts; /* number of successful RCPT commands */
+#if _FFR_ADAPTIVE_EOL
+WARNING: do NOT use this FFR, it is most likely broken
+ bool sm_crlf; /* input in CRLF form? */
+#endif /* _FFR_ADAPTIVE_EOL */
+ bool sm_discard;
+#if MILTER
+ bool sm_milterize;
+ bool sm_milterlist; /* any filters in the list? */
+#endif /* MILTER */
+#if _FFR_QUARANTINE
+ char *sm_quarmsg; /* carry quarantining across messages */
+#endif /* _FFR_QUARANTINE */
+} SMTP_T;
+
+static void smtp_data __P((SMTP_T *, ENVELOPE *));
+
+#define MSG_TEMPFAIL "451 4.7.1 Please try again later"
+
+#if MILTER
+# define MILTER_ABORT(e) milter_abort((e))
+# define MILTER_REPLY(str) \
+ { \
+ int savelogusrerrs = LogUsrErrs; \
+ \
+ switch (state) \
+ { \
+ case SMFIR_REPLYCODE: \
+ if (MilterLogLevel > 3) \
+ { \
+ sm_syslog(LOG_INFO, e->e_id, \
+ "Milter: %s=%s, reject=%s", \
+ str, addr, response); \
+ LogUsrErrs = false; \
+ } \
+ usrerr(response); \
+ break; \
+ \
+ case SMFIR_REJECT: \
+ if (MilterLogLevel > 3) \
+ { \
+ sm_syslog(LOG_INFO, e->e_id, \
+ "Milter: %s=%s, reject=550 5.7.1 Command rejected", \
+ str, addr); \
+ LogUsrErrs = false; \
+ } \
+ usrerr("550 5.7.1 Command rejected"); \
+ break; \
+ \
+ case SMFIR_DISCARD: \
+ if (MilterLogLevel > 3) \
+ sm_syslog(LOG_INFO, e->e_id, \
+ "Milter: %s=%s, discard", \
+ str, addr); \
+ e->e_flags |= EF_DISCARD; \
+ break; \
+ \
+ case SMFIR_TEMPFAIL: \
+ if (MilterLogLevel > 3) \
+ { \
+ sm_syslog(LOG_INFO, e->e_id, \
+ "Milter: %s=%s, reject=%s", \
+ str, addr, MSG_TEMPFAIL); \
+ LogUsrErrs = false; \
+ } \
+ usrerr(MSG_TEMPFAIL); \
+ break; \
+ } \
+ LogUsrErrs = savelogusrerrs; \
+ if (response != NULL) \
+ sm_free(response); /* XXX */ \
+ }
+
+#else /* MILTER */
+# define MILTER_ABORT(e)
+#endif /* MILTER */
+
+/* clear all SMTP state (for HELO/EHLO/RSET) */
+#define CLEAR_STATE(cmd) \
+{ \
+ /* abort milter filters */ \
+ MILTER_ABORT(e); \
+ \
+ if (smtp.sm_nrcpts > 0) \
+ { \
+ logundelrcpts(e, cmd, 10, false); \
+ smtp.sm_nrcpts = 0; \
+ macdefine(&e->e_macro, A_PERM, \
+ macid("{nrcpts}"), "0"); \
+ } \
+ \
+ e->e_sendqueue = NULL; \
+ e->e_flags |= EF_CLRQUEUE; \
+ \
+ if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags)) \
+ logsender(e, NULL); \
+ e->e_flags &= ~EF_LOGSENDER; \
+ \
+ /* clean up a bit */ \
+ smtp.sm_gotmail = false; \
+ SuprErrs = true; \
+ dropenvelope(e, true, false); \
+ sm_rpool_free(e->e_rpool); \
+ e = newenvelope(e, CurEnv, sm_rpool_new_x(NULL)); \
+ CurEnv = e; \
+}
+
+/* sleep to flatten out connection load */
+#define MIN_DELAY_LOG 15 /* wait before logging this again */
+
+/* is it worth setting the process title for 1s? */
+#define DELAY_CONN(cmd) \
+ if (DelayLA > 0 && (CurrentLA = getla()) >= DelayLA) \
+ { \
+ time_t dnow; \
+ \
+ sm_setproctitle(true, e, \
+ "%s: %s: delaying %s: load average: %d", \
+ qid_printname(e), CurSmtpClient, \
+ cmd, DelayLA); \
+ if (LogLevel > 8 && (dnow = curtime()) > log_delay) \
+ { \
+ sm_syslog(LOG_INFO, e->e_id, \
+ "delaying=%s, load average=%d >= %d", \
+ cmd, CurrentLA, DelayLA); \
+ log_delay = dnow + MIN_DELAY_LOG; \
+ } \
+ (void) sleep(1); \
+ sm_setproctitle(true, e, "%s %s: %.80s", \
+ qid_printname(e), CurSmtpClient, inp); \
+ }
+
+
+void
+smtp(nullserver, d_flags, e)
+ char *volatile nullserver;
+ BITMAP256 d_flags;
+ register ENVELOPE *volatile e;
+{
+ register char *volatile p;
+ register struct cmd *volatile c = NULL;
+ char *cmd;
+ auto ADDRESS *vrfyqueue;
+ ADDRESS *a;
+ volatile bool gothello; /* helo command received */
+ bool vrfy; /* set if this is a vrfy command */
+ char *volatile protocol; /* sending protocol */
+ char *volatile sendinghost; /* sending hostname */
+ char *volatile peerhostname; /* name of SMTP peer or "localhost" */
+ auto char *delimptr;
+ char *id;
+ volatile unsigned int n_badcmds = 0; /* count of bad commands */
+ volatile unsigned int n_badrcpts = 0; /* number of rejected RCPT */
+ volatile unsigned int n_verifies = 0; /* count of VRFY/EXPN */
+ volatile unsigned int n_etrn = 0; /* count of ETRN */
+ volatile unsigned int n_noop = 0; /* count of NOOP/VERB/etc */
+ volatile unsigned int n_helo = 0; /* count of HELO/EHLO */
+ bool ok;
+#if _FFR_ADAPTIVE_EOL
+ volatile bool first;
+#endif /* _FFR_ADAPTIVE_EOL */
+ volatile bool tempfail = false;
+ volatile time_t wt; /* timeout after too many commands */
+ volatile time_t previous; /* time after checksmtpattack() */
+ volatile bool lognullconnection = true;
+ register char *q;
+ SMTP_T smtp;
+ char *addr;
+ char *greetcode = "220";
+ char *hostname; /* my hostname ($j) */
+ QUEUE_CHAR *new;
+ int argno;
+ char *args[MAXSMTPARGS];
+ char inp[MAXLINE];
+ char cmdbuf[MAXLINE];
+#if SASL
+ sasl_conn_t *conn;
+ volatile bool sasl_ok;
+ volatile unsigned int n_auth = 0; /* count of AUTH commands */
+ bool ismore;
+ int result;
+ volatile int authenticating;
+ char *user;
+ char *in, *out2;
+# if SASL >= 20000
+ char *auth_id;
+ const char *out;
+ sasl_ssf_t ext_ssf;
+# else /* SASL >= 20000 */
+ char *out;
+ const char *errstr;
+ sasl_external_properties_t ext_ssf;
+# endif /* SASL >= 20000 */
+ sasl_security_properties_t ssp;
+ sasl_ssf_t *ssf;
+ unsigned int inlen, out2len;
+ unsigned int outlen;
+ char *volatile auth_type;
+ char *mechlist;
+ volatile unsigned int n_mechs;
+ unsigned int len;
+#endif /* SASL */
+#if STARTTLS
+ int r;
+ int rfd, wfd;
+ volatile bool tls_active = false;
+# if _FFR_SMTP_SSL
+ volatile bool smtps = false;
+# endif /* _FFR_SMTP_SSL */
+ bool saveQuickAbort;
+ bool saveSuprErrs;
+ time_t tlsstart;
+#endif /* STARTTLS */
+ volatile unsigned int features;
+#if PIPELINING
+# if _FFR_NO_PIPE
+ int np_log = 0;
+# endif /* _FFR_NO_PIPE */
+#endif /* PIPELINING */
+ volatile time_t log_delay = (time_t) 0;
+
+ smtp.sm_nrcpts = 0;
+#if MILTER
+ smtp.sm_milterize = (nullserver == NULL);
+ smtp.sm_milterlist = false;
+#endif /* MILTER */
+
+ /* setup I/O fd correctly for the SMTP server */
+ setup_smtpd_io();
+
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&DebugLeakSmtp, 1))
+ {
+ sm_heap_newgroup();
+ sm_dprintf("smtp() heap group #%d\n", sm_heap_group());
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* XXX the rpool should be set when e is initialized in main() */
+ e->e_rpool = sm_rpool_new_x(NULL);
+ e->e_macro.mac_rpool = e->e_rpool;
+
+ settime(e);
+ sm_getla();
+ peerhostname = RealHostName;
+ if (peerhostname == NULL)
+ peerhostname = "localhost";
+ CurHostName = peerhostname;
+ CurSmtpClient = macvalue('_', e);
+ if (CurSmtpClient == NULL)
+ CurSmtpClient = CurHostName;
+
+ /* check_relay may have set discard bit, save for later */
+ smtp.sm_discard = bitset(EF_DISCARD, e->e_flags);
+
+#if PIPELINING
+ /* auto-flush output when reading input */
+ (void) sm_io_autoflush(InChannel, OutChannel);
+#endif /* PIPELINING */
+
+ sm_setproctitle(true, e, "server %s startup", CurSmtpClient);
+
+ /* Set default features for server. */
+ features = ((bitset(PRIV_NOETRN, PrivacyFlags) ||
+ bitnset(D_NOETRN, d_flags)) ? SRV_NONE : SRV_OFFER_ETRN)
+ | (bitnset(D_AUTHREQ, d_flags) ? SRV_REQ_AUTH : SRV_NONE)
+ | (bitset(PRIV_NOEXPN, PrivacyFlags) ? SRV_NONE
+ : (SRV_OFFER_EXPN
+ | (bitset(PRIV_NOVERB, PrivacyFlags)
+ ? SRV_NONE : SRV_OFFER_VERB)))
+ | (bitset(PRIV_NORECEIPTS, PrivacyFlags) ? SRV_NONE
+ : SRV_OFFER_DSN)
+#if SASL
+ | (bitnset(D_NOAUTH, d_flags) ? SRV_NONE : SRV_OFFER_AUTH)
+#endif /* SASL */
+#if PIPELINING
+ | SRV_OFFER_PIPE
+#endif /* PIPELINING */
+#if STARTTLS
+ | (bitnset(D_NOTLS, d_flags) ? SRV_NONE : SRV_OFFER_TLS)
+ | (bitset(TLS_I_NO_VRFY, TLS_Srv_Opts) ? SRV_NONE
+ : SRV_VRFY_CLT)
+#endif /* STARTTLS */
+ ;
+ if (nullserver == NULL)
+ {
+ features = srvfeatures(e, CurSmtpClient, features);
+ if (bitset(SRV_TMP_FAIL, features))
+ {
+ if (LogLevel > 4)
+ sm_syslog(LOG_ERR, NOQID,
+ "ERROR: srv_features=tempfail, relay=%.100s, access temporarily disabled",
+ CurSmtpClient);
+ nullserver = "450 4.3.0 Please try again later.";
+ }
+#if PIPELINING
+# if _FFR_NO_PIPE
+ else if (bitset(SRV_NO_PIPE, features))
+ {
+ /* for consistency */
+ features &= ~SRV_OFFER_PIPE;
+ }
+# endif /* _FFR_NO_PIPE */
+#endif /* PIPELINING */
+ }
+
+ hostname = macvalue('j', e);
+
+
+#if SASL
+ sasl_ok = bitset(SRV_OFFER_AUTH, features);
+ n_mechs = 0;
+ authenticating = SASL_NOT_AUTH;
+
+ /* SASL server new connection */
+ if (sasl_ok)
+ {
+# if SASL >= 20000
+ result = sasl_server_new("smtp", hostname, NULL, NULL, NULL,
+ NULL, 0, &conn);
+# elif SASL > 10505
+ /* use empty realm: only works in SASL > 1.5.5 */
+ result = sasl_server_new("smtp", hostname, "", NULL, 0, &conn);
+# else /* SASL >= 20000 */
+ /* use no realm -> realm is set to hostname by SASL lib */
+ result = sasl_server_new("smtp", hostname, NULL, NULL, 0,
+ &conn);
+# endif /* SASL >= 20000 */
+ sasl_ok = result == SASL_OK;
+ if (!sasl_ok)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, NOQID,
+ "AUTH error: sasl_server_new failed=%d",
+ result);
+ }
+ }
+ if (sasl_ok)
+ {
+ /*
+ ** SASL set properties for sasl
+ ** set local/remote IP
+ ** XXX Cyrus SASL v1 only supports IPv4
+ **
+ ** XXX where exactly are these used/required?
+ ** Kerberos_v4
+ */
+
+# if SASL >= 20000
+# if NETINET || NETINET6
+ in = macvalue(macid("{daemon_family}"), e);
+ if (in != NULL && (
+# if NETINET6
+ strcmp(in, "inet6") == 0 ||
+# endif /* NETINET6 */
+ strcmp(in, "inet") == 0))
+ {
+ SOCKADDR_LEN_T addrsize;
+ SOCKADDR saddr_l;
+ SOCKADDR saddr_r;
+ char localip[60], remoteip[60];
+
+ addrsize = sizeof(saddr_r);
+ if (getpeername(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *) &saddr_r,
+ &addrsize) == 0)
+ {
+ if (iptostring(&saddr_r, addrsize,
+ remoteip, sizeof remoteip))
+ {
+ sasl_setprop(conn, SASL_IPREMOTEPORT,
+ remoteip);
+ }
+ addrsize = sizeof(saddr_l);
+ if (getsockname(sm_io_getinfo(InChannel,
+ SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *) &saddr_l,
+ &addrsize) == 0)
+ {
+ if (iptostring(&saddr_l, addrsize,
+ localip,
+ sizeof localip))
+ {
+ sasl_setprop(conn,
+ SASL_IPLOCALPORT,
+ localip);
+ }
+ }
+ }
+ }
+# endif /* NETINET || NETINET6 */
+# else /* SASL >= 20000 */
+# if NETINET
+ in = macvalue(macid("{daemon_family}"), e);
+ if (in != NULL && strcmp(in, "inet") == 0)
+ {
+ SOCKADDR_LEN_T addrsize;
+ struct sockaddr_in saddr_l;
+ struct sockaddr_in saddr_r;
+
+ addrsize = sizeof(struct sockaddr_in);
+ if (getpeername(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *)&saddr_r,
+ &addrsize) == 0)
+ {
+ sasl_setprop(conn, SASL_IP_REMOTE, &saddr_r);
+ addrsize = sizeof(struct sockaddr_in);
+ if (getsockname(sm_io_getinfo(InChannel,
+ SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *)&saddr_l,
+ &addrsize) == 0)
+ sasl_setprop(conn, SASL_IP_LOCAL,
+ &saddr_l);
+ }
+ }
+# endif /* NETINET */
+# endif /* SASL >= 20000 */
+
+ auth_type = NULL;
+ mechlist = NULL;
+ user = NULL;
+# if 0
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{auth_author}"), NULL);
+# endif /* 0 */
+
+ /* set properties */
+ (void) memset(&ssp, '\0', sizeof ssp);
+
+ /* XXX should these be options settable via .cf ? */
+ /* ssp.min_ssf = 0; is default due to memset() */
+# if STARTTLS
+# endif /* STARTTLS */
+ {
+ ssp.max_ssf = MaxSLBits;
+ ssp.maxbufsize = MAXOUTLEN;
+ }
+ ssp.security_flags = SASLOpts & SASL_SEC_MASK;
+ sasl_ok = sasl_setprop(conn, SASL_SEC_PROPS, &ssp) == SASL_OK;
+
+ if (sasl_ok)
+ {
+ /*
+ ** external security strength factor;
+ ** currently we have none so zero
+ */
+
+# if SASL >= 20000
+ ext_ssf = 0;
+ auth_id = NULL;
+ sasl_ok = ((sasl_setprop(conn, SASL_SSF_EXTERNAL,
+ &ext_ssf) == SASL_OK) &&
+ (sasl_setprop(conn, SASL_AUTH_EXTERNAL,
+ auth_id) == SASL_OK));
+# else /* SASL >= 20000 */
+ ext_ssf.ssf = 0;
+ ext_ssf.auth_id = NULL;
+ sasl_ok = sasl_setprop(conn, SASL_SSF_EXTERNAL,
+ &ext_ssf) == SASL_OK;
+# endif /* SASL >= 20000 */
+ }
+ if (sasl_ok)
+ n_mechs = saslmechs(conn, &mechlist);
+ }
+#endif /* SASL */
+
+#if MILTER
+ if (smtp.sm_milterize)
+ {
+ char state;
+
+ /* initialize mail filter connection */
+ smtp.sm_milterlist = milter_init(e, &state);
+ switch (state)
+ {
+ case SMFIR_REJECT:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: initialization failed, rejecting commands");
+ greetcode = "554";
+ nullserver = "Command rejected";
+ smtp.sm_milterize = false;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: initialization failed, temp failing commands");
+ tempfail = true;
+ smtp.sm_milterize = false;
+ break;
+ }
+ }
+
+ if (smtp.sm_milterlist && smtp.sm_milterize &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ response = milter_connect(peerhostname, RealHostAddr,
+ e, &state);
+ switch (state)
+ {
+ case SMFIR_REPLYCODE: /* REPLYCODE shouldn't happen */
+ case SMFIR_REJECT:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: connect: host=%s, addr=%s, rejecting commands",
+ peerhostname,
+ anynet_ntoa(&RealHostAddr));
+ greetcode = "554";
+ nullserver = "Command rejected";
+ smtp.sm_milterize = false;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: connect: host=%s, addr=%s, temp failing commands",
+ peerhostname,
+ anynet_ntoa(&RealHostAddr));
+ tempfail = true;
+ smtp.sm_milterize = false;
+ break;
+ }
+ if (response != NULL)
+
+ sm_free(response); /* XXX */
+ }
+#endif /* MILTER */
+
+#if STARTTLS
+# if _FFR_SMTP_SSL
+ /* If this an smtps connection, start TLS now */
+ smtps = bitnset(D_SMTPS, d_flags);
+ if (smtps)
+ goto starttls;
+
+ greeting:
+
+# endif /* _FFR_SMTP_SSL */
+#endif /* STARTTLS */
+
+ /* output the first line, inserting "ESMTP" as second word */
+ if (*greetcode == '5')
+ (void) sm_snprintf(inp, sizeof inp, "%s not accepting messages",
+ hostname);
+ else
+ expand(SmtpGreeting, inp, sizeof inp, e);
+
+ p = strchr(inp, '\n');
+ if (p != NULL)
+ *p++ = '\0';
+ id = strchr(inp, ' ');
+ if (id == NULL)
+ id = &inp[strlen(inp)];
+ if (p == NULL)
+ (void) sm_snprintf(cmdbuf, sizeof cmdbuf,
+ "%s %%.*s ESMTP%%s", greetcode);
+ else
+ (void) sm_snprintf(cmdbuf, sizeof cmdbuf,
+ "%s-%%.*s ESMTP%%s", greetcode);
+ message(cmdbuf, (int) (id - inp), inp, id);
+
+ /* output remaining lines */
+ while ((id = p) != NULL && (p = strchr(id, '\n')) != NULL)
+ {
+ *p++ = '\0';
+ if (isascii(*id) && isspace(*id))
+ id++;
+ (void) sm_strlcpyn(cmdbuf, sizeof cmdbuf, 2, greetcode, "-%s");
+ message(cmdbuf, id);
+ }
+ if (id != NULL)
+ {
+ if (isascii(*id) && isspace(*id))
+ id++;
+ (void) sm_strlcpyn(cmdbuf, sizeof cmdbuf, 2, greetcode, " %s");
+ message(cmdbuf, id);
+ }
+
+ protocol = NULL;
+ sendinghost = macvalue('s', e);
+
+#if _FFR_QUARANTINE
+ /* If quarantining by a connect/ehlo action, save between messages */
+ if (e->e_quarmsg == NULL)
+ smtp.sm_quarmsg = NULL;
+ else
+ smtp.sm_quarmsg = newstr(e->e_quarmsg);
+#endif /* _FFR_QUARANTINE */
+
+ /* sendinghost's storage must outlive the current envelope */
+ if (sendinghost != NULL)
+ sendinghost = sm_strdup_x(sendinghost);
+#if _FFR_ADAPTIVE_EOL
+ first = true;
+#endif /* _FFR_ADAPTIVE_EOL */
+ gothello = false;
+ smtp.sm_gotmail = false;
+ for (;;)
+ {
+ SM_TRY
+ {
+ QuickAbort = false;
+ HoldErrs = false;
+ SuprErrs = false;
+ LogUsrErrs = false;
+ OnlyOneError = true;
+ e->e_flags &= ~(EF_VRFYONLY|EF_GLOBALERRS);
+
+ /* setup for the read */
+ e->e_to = NULL;
+ Errors = 0;
+ FileName = NULL;
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+
+ /* read the input line */
+ SmtpPhase = "server cmd read";
+ sm_setproctitle(true, e, "server %s cmd read", CurSmtpClient);
+#if SASL
+ /*
+ ** XXX SMTP AUTH requires accepting any length,
+ ** at least for challenge/response
+ */
+#endif /* SASL */
+
+ /* handle errors */
+ if (sm_io_error(OutChannel) ||
+ (p = sfgets(inp, sizeof inp, InChannel,
+ TimeOuts.to_nextcommand, SmtpPhase)) == NULL)
+ {
+ char *d;
+
+ d = macvalue(macid("{daemon_name}"), e);
+ if (d == NULL)
+ d = "stdin";
+ /* end of file, just die */
+ disconnect(1, e);
+
+#if MILTER
+ /* close out milter filters */
+ milter_quit(e);
+#endif /* MILTER */
+
+ message("421 4.4.1 %s Lost input channel from %s",
+ MyHostName, CurSmtpClient);
+ if (LogLevel > (smtp.sm_gotmail ? 1 : 19))
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "lost input channel from %.100s to %s after %s",
+ CurSmtpClient, d,
+ (c == NULL || c->cmd_name == NULL) ? "startup" : c->cmd_name);
+ /*
+ ** If have not accepted mail (DATA), do not bounce
+ ** bad addresses back to sender.
+ */
+
+ if (bitset(EF_CLRQUEUE, e->e_flags))
+ e->e_sendqueue = NULL;
+ goto doquit;
+ }
+
+#if _FFR_ADAPTIVE_EOL
+ if (first)
+ {
+ char *p;
+
+ smtp.sm_crlf = true;
+ p = strchr(inp, '\n');
+ if (p == NULL || p <= inp || p[-1] != '\r')
+ {
+ smtp.sm_crlf = false;
+ if (tTd(66, 1) && LogLevel > 8)
+ {
+ /* how many bad guys are there? */
+ sm_syslog(LOG_INFO, NOQID,
+ "%.100s did not use CRLF",
+ CurSmtpClient);
+ }
+ }
+ first = false;
+ }
+#endif /* _FFR_ADAPTIVE_EOL */
+
+ /* clean up end of line */
+ fixcrlf(inp, true);
+
+#if PIPELINING
+# if _FFR_NO_PIPE
+ /*
+ ** if there is more input and pipelining is disabled:
+ ** delay ... (and maybe discard the input?)
+ ** XXX this doesn't really work, at least in tests using
+ ** telnet SM_IO_IS_READABLE only returns 1 if there were
+ ** more than 2 input lines available.
+ */
+
+ if (bitset(SRV_NO_PIPE, features) &&
+ sm_io_getinfo(InChannel, SM_IO_IS_READABLE, NULL))
+ {
+ if (++np_log < 3)
+ sm_syslog(LOG_INFO, NOQID,
+ "unauthorized PIPELINING, sleeping");
+ sleep(1);
+ }
+
+# endif /* _FFR_NO_PIPE */
+#endif /* PIPELINING */
+
+#if SASL
+ if (authenticating == SASL_PROC_AUTH)
+ {
+# if 0
+ if (*inp == '\0')
+ {
+ authenticating = SASL_NOT_AUTH;
+ message("501 5.5.2 missing input");
+ continue;
+ }
+# endif /* 0 */
+ if (*inp == '*' && *(inp + 1) == '\0')
+ {
+ authenticating = SASL_NOT_AUTH;
+
+ /* rfc 2254 4. */
+ message("501 5.0.0 AUTH aborted");
+ continue;
+ }
+
+ /* could this be shorter? XXX */
+# if SASL >= 20000
+ in = xalloc(strlen(inp) + 1);
+ result = sasl_decode64(inp, strlen(inp), in,
+ strlen(inp), &inlen);
+# else /* SASL >= 20000 */
+ out = xalloc(strlen(inp));
+ result = sasl_decode64(inp, strlen(inp), out, &outlen);
+# endif /* SASL >= 20000 */
+ if (result != SASL_OK)
+ {
+ authenticating = SASL_NOT_AUTH;
+
+ /* rfc 2254 4. */
+ message("501 5.5.4 cannot decode AUTH parameter %s",
+ inp);
+# if SASL >= 20000
+ sm_free(in);
+# endif /* SASL >= 20000 */
+ continue;
+ }
+
+# if SASL >= 20000
+ result = sasl_server_step(conn, in, inlen,
+ &out, &outlen);
+ sm_free(in);
+# else /* SASL >= 20000 */
+ result = sasl_server_step(conn, out, outlen,
+ &out, &outlen, &errstr);
+# endif /* SASL >= 20000 */
+
+ /* get an OK if we're done */
+ if (result == SASL_OK)
+ {
+ authenticated:
+ message("235 2.0.0 OK Authenticated");
+ authenticating = SASL_IS_AUTH;
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{auth_type}"), auth_type);
+
+# if SASL >= 20000
+ user = macvalue(macid("{auth_authen}"), e);
+
+ /* get security strength (features) */
+ result = sasl_getprop(conn, SASL_SSF,
+ (const void **) &ssf);
+# else /* SASL >= 20000 */
+ result = sasl_getprop(conn, SASL_USERNAME,
+ (void **)&user);
+ if (result != SASL_OK)
+ {
+ user = "";
+ macdefine(&BlankEnvelope.e_macro,
+ A_PERM,
+ macid("{auth_authen}"), NULL);
+ }
+ else
+ {
+ macdefine(&BlankEnvelope.e_macro,
+ A_TEMP,
+ macid("{auth_authen}"), user);
+ }
+
+# if 0
+ /* get realm? */
+ sasl_getprop(conn, SASL_REALM, (void **) &data);
+# endif /* 0 */
+
+ /* get security strength (features) */
+ result = sasl_getprop(conn, SASL_SSF,
+ (void **) &ssf);
+# endif /* SASL >= 20000 */
+ if (result != SASL_OK)
+ {
+ macdefine(&BlankEnvelope.e_macro,
+ A_PERM,
+ macid("{auth_ssf}"), "0");
+ ssf = NULL;
+ }
+ else
+ {
+ char pbuf[8];
+
+ (void) sm_snprintf(pbuf, sizeof pbuf,
+ "%u", *ssf);
+ macdefine(&BlankEnvelope.e_macro,
+ A_TEMP,
+ macid("{auth_ssf}"), pbuf);
+ if (tTd(95, 8))
+ sm_dprintf("AUTH auth_ssf: %u\n",
+ *ssf);
+ }
+
+ /*
+ ** Only switch to encrypted connection
+ ** if a security layer has been negotiated
+ */
+
+ if (ssf != NULL && *ssf > 0)
+ {
+ /*
+ ** Convert I/O layer to use SASL.
+ ** If the call fails, the connection
+ ** is aborted.
+ */
+
+ if (sfdcsasl(&InChannel, &OutChannel,
+ conn) == 0)
+ {
+ /* restart dialogue */
+ n_helo = 0;
+# if PIPELINING
+ (void) sm_io_autoflush(InChannel,
+ OutChannel);
+# endif /* PIPELINING */
+ }
+ else
+ syserr("503 5.3.3 SASL TLS failed");
+ }
+
+ /* NULL pointer ok since it's our function */
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "AUTH=server, relay=%.100s, authid=%.128s, mech=%.16s, bits=%d",
+ CurSmtpClient,
+ shortenstring(user, 128),
+ auth_type, *ssf);
+ }
+ else if (result == SASL_CONTINUE)
+ {
+ len = ENC64LEN(outlen);
+ out2 = xalloc(len);
+ result = sasl_encode64(out, outlen, out2, len,
+ &out2len);
+ if (result != SASL_OK)
+ {
+ /* correct code? XXX */
+ /* 454 Temp. authentication failure */
+ message("454 4.5.4 Internal error: unable to encode64");
+ if (LogLevel > 5)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "AUTH encode64 error [%d for \"%s\"]",
+ result, out);
+ /* start over? */
+ authenticating = SASL_NOT_AUTH;
+ }
+ else
+ {
+ message("334 %s", out2);
+ if (tTd(95, 2))
+ sm_dprintf("AUTH continue: msg='%s' len=%u\n",
+ out2, out2len);
+ }
+# if SASL >= 20000
+ sm_free(out2);
+# endif /* SASL >= 20000 */
+ }
+ else
+ {
+ /* not SASL_OK or SASL_CONT */
+ message("535 5.7.0 authentication failed");
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "AUTH failure (%s): %s (%d) %s",
+ auth_type,
+ sasl_errstring(result, NULL,
+ NULL),
+ result,
+# if SASL >= 20000
+ sasl_errdetail(conn));
+# else /* SASL >= 20000 */
+ errstr == NULL ? "" : errstr);
+# endif /* SASL >= 20000 */
+ authenticating = SASL_NOT_AUTH;
+ }
+ }
+ else
+ {
+ /* don't want to do any of this if authenticating */
+#endif /* SASL */
+
+ /* echo command to transcript */
+ if (e->e_xfp != NULL)
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "<<< %s\n", inp);
+
+ if (LogLevel > 14)
+ sm_syslog(LOG_INFO, e->e_id, "<-- %s", inp);
+
+ /* break off command */
+ for (p = inp; isascii(*p) && isspace(*p); p++)
+ continue;
+ cmd = cmdbuf;
+ while (*p != '\0' &&
+ !(isascii(*p) && isspace(*p)) &&
+ cmd < &cmdbuf[sizeof cmdbuf - 2])
+ *cmd++ = *p++;
+ *cmd = '\0';
+
+ /* throw away leading whitespace */
+ SKIP_SPACE(p);
+
+ /* decode command */
+ for (c = CmdTab; c->cmd_name != NULL; c++)
+ {
+ if (sm_strcasecmp(c->cmd_name, cmdbuf) == 0)
+ break;
+ }
+
+ /* reset errors */
+ errno = 0;
+
+ /* check whether a "non-null" command has been used */
+ switch (c->cmd_code)
+ {
+#if SASL
+ case CMDAUTH:
+ /* avoid information leak; take first two words? */
+ q = "AUTH";
+ break;
+#endif /* SASL */
+
+ case CMDMAIL:
+ case CMDEXPN:
+ case CMDVRFY:
+ case CMDETRN:
+ lognullconnection = false;
+ /* FALLTHROUGH */
+ default:
+ q = inp;
+ break;
+ }
+
+ if (e->e_id == NULL)
+ sm_setproctitle(true, e, "%s: %.80s",
+ CurSmtpClient, q);
+ else
+ sm_setproctitle(true, e, "%s %s: %.80s",
+ qid_printname(e),
+ CurSmtpClient, q);
+
+ /*
+ ** Process command.
+ **
+ ** If we are running as a null server, return 550
+ ** to almost everything.
+ */
+
+ if (nullserver != NULL || bitnset(D_ETRNONLY, d_flags))
+ {
+ switch (c->cmd_code)
+ {
+ case CMDQUIT:
+ case CMDHELO:
+ case CMDEHLO:
+ case CMDNOOP:
+ case CMDRSET:
+ /* process normally */
+ break;
+
+ case CMDETRN:
+ if (bitnset(D_ETRNONLY, d_flags) &&
+ nullserver == NULL)
+ break;
+ DELAY_CONN("ETRN");
+ /* FALLTHROUGH */
+
+ default:
+#if MAXBADCOMMANDS > 0
+ /* theoretically this could overflow */
+ if (nullserver != NULL &&
+ ++n_badcmds > MAXBADCOMMANDS)
+ {
+ message("421 4.7.0 %s Too many bad commands; closing connection",
+ MyHostName);
+
+ /* arrange to ignore send list */
+ e->e_sendqueue = NULL;
+ goto doquit;
+ }
+#endif /* MAXBADCOMMANDS > 0 */
+ if (nullserver != NULL)
+ {
+ if (ISSMTPREPLY(nullserver))
+ usrerr(nullserver);
+ else
+ usrerr("550 5.0.0 %s",
+ nullserver);
+ }
+ else
+ usrerr("452 4.4.5 Insufficient disk space; try again later");
+ continue;
+ }
+ }
+
+ switch (c->cmd_code)
+ {
+#if SASL
+ case CMDAUTH: /* sasl */
+ DELAY_CONN("AUTH");
+ if (!sasl_ok || n_mechs <= 0)
+ {
+ message("503 5.3.3 AUTH not available");
+ break;
+ }
+ if (authenticating == SASL_IS_AUTH)
+ {
+ message("503 5.5.0 Already Authenticated");
+ break;
+ }
+ if (smtp.sm_gotmail)
+ {
+ message("503 5.5.0 AUTH not permitted during a mail transaction");
+ break;
+ }
+ if (tempfail)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP AUTH command (%.100s) from %.100s tempfailed (due to previous checks)",
+ p, CurSmtpClient);
+ usrerr("454 4.7.1 Please try again later");
+ break;
+ }
+
+ ismore = false;
+
+ /* crude way to avoid crack attempts */
+ (void) checksmtpattack(&n_auth, n_mechs + 1, true,
+ "AUTH", e);
+
+ /* make sure mechanism (p) is a valid string */
+ for (q = p; *q != '\0' && isascii(*q); q++)
+ {
+ if (isspace(*q))
+ {
+ *q = '\0';
+ while (*++q != '\0' &&
+ isascii(*q) && isspace(*q))
+ continue;
+ *(q - 1) = '\0';
+ ismore = (*q != '\0');
+ break;
+ }
+ }
+
+ if (*p == '\0')
+ {
+ message("501 5.5.2 AUTH mechanism must be specified");
+ break;
+ }
+
+ /* check whether mechanism is available */
+ if (iteminlist(p, mechlist, " ") == NULL)
+ {
+ message("504 5.3.3 AUTH mechanism %.32s not available",
+ p);
+ break;
+ }
+
+ if (ismore)
+ {
+ /* could this be shorter? XXX */
+# if SASL >= 20000
+ in = xalloc(strlen(q) + 1);
+ result = sasl_decode64(q, strlen(q), in,
+ strlen(q), &inlen);
+# else /* SASL >= 20000 */
+ in = sm_rpool_malloc(e->e_rpool, strlen(q));
+ result = sasl_decode64(q, strlen(q), in,
+ &inlen);
+# endif /* SASL >= 20000 */
+ if (result != SASL_OK)
+ {
+ message("501 5.5.4 cannot BASE64 decode '%s'",
+ q);
+ if (LogLevel > 5)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "AUTH decode64 error [%d for \"%s\"]",
+ result, q);
+ /* start over? */
+ authenticating = SASL_NOT_AUTH;
+# if SASL >= 20000
+ sm_free(in);
+# endif /* SASL >= 20000 */
+ in = NULL;
+ inlen = 0;
+ break;
+ }
+ }
+ else
+ {
+ in = NULL;
+ inlen = 0;
+ }
+
+ /* see if that auth type exists */
+# if SASL >= 20000
+ result = sasl_server_start(conn, p, in, inlen,
+ &out, &outlen);
+ if (in != NULL)
+ sm_free(in);
+# else /* SASL >= 20000 */
+ result = sasl_server_start(conn, p, in, inlen,
+ &out, &outlen, &errstr);
+# endif /* SASL >= 20000 */
+
+ if (result != SASL_OK && result != SASL_CONTINUE)
+ {
+ message("535 5.7.0 authentication failed");
+ if (LogLevel > 9)
+ sm_syslog(LOG_ERR, e->e_id,
+ "AUTH failure (%s): %s (%d) %s",
+ p,
+ sasl_errstring(result, NULL,
+ NULL),
+ result,
+# if SASL >= 20000
+ sasl_errdetail(conn));
+# else /* SASL >= 20000 */
+ errstr);
+# endif /* SASL >= 20000 */
+ break;
+ }
+ auth_type = newstr(p);
+
+ if (result == SASL_OK)
+ {
+ /* ugly, but same code */
+ goto authenticated;
+ /* authenticated by the initial response */
+ }
+
+ /* len is at least 2 */
+ len = ENC64LEN(outlen);
+ out2 = xalloc(len);
+ result = sasl_encode64(out, outlen, out2, len,
+ &out2len);
+
+ if (result != SASL_OK)
+ {
+ message("454 4.5.4 Temporary authentication failure");
+ if (LogLevel > 5)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "AUTH encode64 error [%d for \"%s\"]",
+ result, out);
+
+ /* start over? */
+ authenticating = SASL_NOT_AUTH;
+ }
+ else
+ {
+ message("334 %s", out2);
+ authenticating = SASL_PROC_AUTH;
+ }
+# if SASL >= 20000
+ sm_free(out2);
+# endif /* SASL >= 20000 */
+ break;
+#endif /* SASL */
+
+#if STARTTLS
+ case CMDSTLS: /* starttls */
+ DELAY_CONN("STARTTLS");
+ if (*p != '\0')
+ {
+ message("501 5.5.2 Syntax error (no parameters allowed)");
+ break;
+ }
+ if (!bitset(SRV_OFFER_TLS, features))
+ {
+ message("503 5.5.0 TLS not available");
+ break;
+ }
+ if (!tls_ok_srv)
+ {
+ message("454 4.3.3 TLS not available after start");
+ break;
+ }
+ if (smtp.sm_gotmail)
+ {
+ message("503 5.5.0 TLS not permitted during a mail transaction");
+ break;
+ }
+ if (tempfail)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP STARTTLS command (%.100s) from %.100s tempfailed (due to previous checks)",
+ p, CurSmtpClient);
+ usrerr("454 4.7.1 Please try again later");
+ break;
+ }
+# if _FFR_SMTP_SSL
+ starttls:
+# endif /* _FFR_SMTP_SSL */
+# if TLS_NO_RSA
+ /*
+ ** XXX do we need a temp key ?
+ */
+# else /* TLS_NO_RSA */
+# endif /* TLS_NO_RSA */
+
+# if TLS_VRFY_PER_CTX
+ /*
+ ** Note: this sets the verification globally
+ ** (per SSL_CTX)
+ ** it's ok since it applies only to one transaction
+ */
+
+ TLS_VERIFY_CLIENT();
+# endif /* TLS_VRFY_PER_CTX */
+
+ if (srv_ssl != NULL)
+ SSL_clear(srv_ssl);
+ else if ((srv_ssl = SSL_new(srv_ctx)) == NULL)
+ {
+ message("454 4.3.3 TLS not available: error generating SSL handle");
+# if _FFR_SMTP_SSL
+ goto tls_done;
+# else /* _FFR_SMTP_SSL */
+ break;
+# endif /* _FFR_SMTP_SSL */
+ }
+
+# if !TLS_VRFY_PER_CTX
+ /*
+ ** this could be used if it were possible to set
+ ** verification per SSL (connection)
+ ** not just per SSL_CTX (global)
+ */
+
+ TLS_VERIFY_CLIENT();
+# endif /* !TLS_VRFY_PER_CTX */
+
+ rfd = sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL);
+ wfd = sm_io_getinfo(OutChannel, SM_IO_WHAT_FD, NULL);
+
+ if (rfd < 0 || wfd < 0 ||
+ SSL_set_rfd(srv_ssl, rfd) <= 0 ||
+ SSL_set_wfd(srv_ssl, wfd) <= 0)
+ {
+ message("454 4.3.3 TLS not available: error set fd");
+ SSL_free(srv_ssl);
+ srv_ssl = NULL;
+# if _FFR_SMTP_SSL
+ goto tls_done;
+# else /* _FFR_SMTP_SSL */
+ break;
+# endif /* _FFR_SMTP_SSL */
+ }
+# if _FFR_SMTP_SSL
+ if (!smtps)
+# endif /* _FFR_SMTP_SSL */
+ message("220 2.0.0 Ready to start TLS");
+# if PIPELINING
+ (void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
+# endif /* PIPELINING */
+
+ SSL_set_accept_state(srv_ssl);
+
+# define SSL_ACC(s) SSL_accept(s)
+
+ tlsstart = curtime();
+ ssl_retry:
+ if ((r = SSL_ACC(srv_ssl)) <= 0)
+ {
+ int i;
+ bool timedout;
+ time_t left;
+ time_t now = curtime();
+ struct timeval tv;
+
+ /* what to do in this case? */
+ i = SSL_get_error(srv_ssl, r);
+
+ /*
+ ** For SSL_ERROR_WANT_{READ,WRITE}:
+ ** There is no SSL record available yet
+ ** or there is only a partial SSL record
+ ** removed from the network (socket) buffer
+ ** into the SSL buffer. The SSL_accept will
+ ** only succeed when a full SSL record is
+ ** available (assuming a "real" error
+ ** doesn't happen). To handle when a "real"
+ ** error does happen the select is set for
+ ** exceptions too.
+ ** The connection may be re-negotiated
+ ** during this time so both read and write
+ ** "want errors" need to be handled.
+ ** A select() exception loops back so that
+ ** a proper SSL error message can be gotten.
+ */
+
+ left = TimeOuts.to_starttls - (now - tlsstart);
+ timedout = left <= 0;
+ if (!timedout)
+ {
+ tv.tv_sec = left;
+ tv.tv_usec = 0;
+ }
+
+ /* XXX what about SSL_pending() ? */
+ if (!timedout && i == SSL_ERROR_WANT_READ)
+ {
+ fd_set ssl_maskr, ssl_maskx;
+
+ FD_ZERO(&ssl_maskr);
+ FD_SET(rfd, &ssl_maskr);
+ FD_ZERO(&ssl_maskx);
+ FD_SET(rfd, &ssl_maskx);
+ if (select(rfd + 1, &ssl_maskr, NULL,
+ &ssl_maskx, &tv) > 0)
+ goto ssl_retry;
+ }
+ if (!timedout && i == SSL_ERROR_WANT_WRITE)
+ {
+ fd_set ssl_maskw, ssl_maskx;
+
+ FD_ZERO(&ssl_maskw);
+ FD_SET(wfd, &ssl_maskw);
+ FD_ZERO(&ssl_maskx);
+ FD_SET(rfd, &ssl_maskx);
+ if (select(wfd + 1, NULL, &ssl_maskw,
+ &ssl_maskx, &tv) > 0)
+ goto ssl_retry;
+ }
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=server, error: accept failed=%d, SSL_error=%d, timedout=%d",
+ r, i, (int) timedout);
+ if (LogLevel > 8)
+ tlslogerr("server");
+ }
+ tls_ok_srv = false;
+ SSL_free(srv_ssl);
+ srv_ssl = NULL;
+
+ /*
+ ** according to the next draft of
+ ** RFC 2487 the connection should be dropped
+ */
+
+ /* arrange to ignore any current send list */
+ e->e_sendqueue = NULL;
+ goto doquit;
+ }
+
+ /* ignore return code for now, it's in {verify} */
+ (void) tls_get_info(srv_ssl, true,
+ CurSmtpClient,
+ &BlankEnvelope.e_macro,
+ bitset(SRV_VRFY_CLT, features));
+
+ /*
+ ** call Stls_client to find out whether
+ ** to accept the connection from the client
+ */
+
+ saveQuickAbort = QuickAbort;
+ saveSuprErrs = SuprErrs;
+ SuprErrs = true;
+ QuickAbort = false;
+ if (rscheck("tls_client",
+ macvalue(macid("{verify}"), e),
+ "STARTTLS", e,
+ RSF_RMCOMM|RSF_COUNT,
+ 5, NULL, NOQID) != EX_OK ||
+ Errors > 0)
+ {
+ extern char MsgBuf[];
+
+ if (MsgBuf[0] != '\0' && ISSMTPREPLY(MsgBuf))
+ nullserver = newstr(MsgBuf);
+ else
+ nullserver = "503 5.7.0 Authentication required.";
+ }
+ QuickAbort = saveQuickAbort;
+ SuprErrs = saveSuprErrs;
+
+ tls_ok_srv = false; /* don't offer STARTTLS again */
+ n_helo = 0;
+# if SASL
+ if (sasl_ok)
+ {
+ char *s;
+
+ s = macvalue(macid("{cipher_bits}"), e);
+# if SASL >= 20000
+ if (s != NULL && (ext_ssf = atoi(s)) > 0)
+ {
+ auth_id = macvalue(macid("{cert_subject}"),
+ e);
+ sasl_ok = ((sasl_setprop(conn, SASL_SSF_EXTERNAL,
+ &ext_ssf) == SASL_OK) &&
+ (sasl_setprop(conn, SASL_AUTH_EXTERNAL,
+ auth_id) == SASL_OK));
+# else /* SASL >= 20000 */
+ if (s != NULL && (ext_ssf.ssf = atoi(s)) > 0)
+ {
+ ext_ssf.auth_id = macvalue(macid("{cert_subject}"),
+ e);
+ sasl_ok = sasl_setprop(conn, SASL_SSF_EXTERNAL,
+ &ext_ssf) == SASL_OK;
+# endif /* SASL >= 20000 */
+ mechlist = NULL;
+ if (sasl_ok)
+ n_mechs = saslmechs(conn,
+ &mechlist);
+ }
+ }
+# endif /* SASL */
+
+ /* switch to secure connection */
+ if (sfdctls(&InChannel, &OutChannel, srv_ssl) == 0)
+ {
+ tls_active = true;
+# if PIPELINING
+ (void) sm_io_autoflush(InChannel, OutChannel);
+# endif /* PIPELINING */
+ }
+ else
+ {
+ /*
+ ** XXX this is an internal error
+ ** how to deal with it?
+ ** we can't generate an error message
+ ** since the other side switched to an
+ ** encrypted layer, but we could not...
+ ** just "hang up"?
+ */
+
+ nullserver = "454 4.3.3 TLS not available: can't switch to encrypted layer";
+ syserr("STARTTLS: can't switch to encrypted layer");
+ }
+# if _FFR_SMTP_SSL
+ tls_done:
+ if (smtps)
+ {
+ if (tls_active)
+ goto greeting;
+ else
+ goto doquit;
+ }
+# endif /* _FFR_SMTP_SSL */
+ break;
+#endif /* STARTTLS */
+
+ case CMDHELO: /* hello -- introduce yourself */
+ case CMDEHLO: /* extended hello */
+ DELAY_CONN("EHLO");
+ if (c->cmd_code == CMDEHLO)
+ {
+ protocol = "ESMTP";
+ SmtpPhase = "server EHLO";
+ }
+ else
+ {
+ protocol = "SMTP";
+ SmtpPhase = "server HELO";
+ }
+
+ /* avoid denial-of-service */
+ (void) checksmtpattack(&n_helo, MAXHELOCOMMANDS, true,
+ "HELO/EHLO", e);
+
+#if 0
+ /* RFC2821 4.1.4 allows duplicate HELO/EHLO */
+ /* check for duplicate HELO/EHLO per RFC 1651 4.2 */
+ if (gothello)
+ {
+ usrerr("503 %s Duplicate HELO/EHLO",
+ MyHostName);
+ break;
+ }
+#endif /* 0 */
+
+ /* check for valid domain name (re 1123 5.2.5) */
+ if (*p == '\0' && !AllowBogusHELO)
+ {
+ usrerr("501 %s requires domain address",
+ cmdbuf);
+ break;
+ }
+
+ /* check for long domain name (hides Received: info) */
+ if (strlen(p) > MAXNAME)
+ {
+ usrerr("501 Invalid domain name");
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, CurEnv->e_id,
+ "invalid domain name (too long) from %.100s",
+ CurSmtpClient);
+ break;
+ }
+
+ ok = true;
+ for (q = p; *q != '\0'; q++)
+ {
+ if (!isascii(*q))
+ break;
+ if (isalnum(*q))
+ continue;
+ if (isspace(*q))
+ {
+ *q = '\0';
+
+ /* only complain if strict check */
+ ok = AllowBogusHELO;
+ break;
+ }
+ if (strchr("[].-_#", *q) == NULL)
+ break;
+ }
+
+ if (*q == '\0' && ok)
+ {
+ q = "pleased to meet you";
+ sendinghost = sm_strdup_x(p);
+ }
+ else if (!AllowBogusHELO)
+ {
+ usrerr("501 Invalid domain name");
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, CurEnv->e_id,
+ "invalid domain name (%.100s) from %.100s",
+ p, CurSmtpClient);
+ break;
+ }
+ else
+ {
+ q = "accepting invalid domain name";
+ }
+
+ if (gothello)
+ {
+ CLEAR_STATE(cmdbuf);
+
+#if _FFR_QUARANTINE
+ /* restore connection quarantining */
+ if (smtp.sm_quarmsg == NULL)
+ {
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), "");
+ }
+ else
+ {
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool,
+ smtp.sm_quarmsg);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"),
+ e->e_quarmsg);
+ }
+#endif /* _FFR_QUARANTINE */
+ }
+
+#if MILTER
+ if (smtp.sm_milterlist && smtp.sm_milterize &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ response = milter_helo(p, e, &state);
+ switch (state)
+ {
+ case SMFIR_REPLYCODE:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: helo=%s, reject=%s",
+ p, response);
+ nullserver = newstr(response);
+ smtp.sm_milterize = false;
+ break;
+
+ case SMFIR_REJECT:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: helo=%s, reject=Command rejected",
+ p);
+ nullserver = "Command rejected";
+ smtp.sm_milterize = false;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: helo=%s, reject=%s",
+ p, MSG_TEMPFAIL);
+ tempfail = true;
+ smtp.sm_milterize = false;
+ break;
+ }
+ if (response != NULL)
+ sm_free(response);
+
+# if _FFR_QUARANTINE
+ /*
+ ** If quarantining by a connect/ehlo action,
+ ** save between messages
+ */
+
+ if (smtp.sm_quarmsg == NULL &&
+ e->e_quarmsg != NULL)
+ smtp.sm_quarmsg = newstr(e->e_quarmsg);
+# endif /* _FFR_QUARANTINE */
+ }
+#endif /* MILTER */
+ gothello = true;
+
+ /* print HELO response message */
+ if (c->cmd_code != CMDEHLO)
+ {
+ message("250 %s Hello %s, %s",
+ MyHostName, CurSmtpClient, q);
+ break;
+ }
+
+ message("250-%s Hello %s, %s",
+ MyHostName, CurSmtpClient, q);
+
+ /* offer ENHSC even for nullserver */
+ if (nullserver != NULL)
+ {
+ message("250 ENHANCEDSTATUSCODES");
+ break;
+ }
+
+ /*
+ ** print EHLO features list
+ **
+ ** Note: If you change this list,
+ ** remember to update 'helpfile'
+ */
+
+ message("250-ENHANCEDSTATUSCODES");
+#if PIPELINING
+ if (bitset(SRV_OFFER_PIPE, features))
+ message("250-PIPELINING");
+#endif /* PIPELINING */
+ if (bitset(SRV_OFFER_EXPN, features))
+ {
+ message("250-EXPN");
+ if (bitset(SRV_OFFER_VERB, features))
+ message("250-VERB");
+ }
+#if MIME8TO7
+ message("250-8BITMIME");
+#endif /* MIME8TO7 */
+ if (MaxMessageSize > 0)
+ message("250-SIZE %ld", MaxMessageSize);
+ else
+ message("250-SIZE");
+#if DSN
+ if (SendMIMEErrors && bitset(SRV_OFFER_DSN, features))
+ message("250-DSN");
+#endif /* DSN */
+ if (bitset(SRV_OFFER_ETRN, features))
+ message("250-ETRN");
+#if SASL
+ if (sasl_ok && mechlist != NULL && *mechlist != '\0')
+ message("250-AUTH %s", mechlist);
+#endif /* SASL */
+#if STARTTLS
+ if (tls_ok_srv &&
+ bitset(SRV_OFFER_TLS, features))
+ message("250-STARTTLS");
+#endif /* STARTTLS */
+ if (DeliverByMin > 0)
+ message("250-DELIVERBY %ld",
+ (long) DeliverByMin);
+ else if (DeliverByMin == 0)
+ message("250-DELIVERBY");
+
+ /* < 0: no deliver-by */
+
+ message("250 HELP");
+ break;
+
+ case CMDMAIL: /* mail -- designate sender */
+ SmtpPhase = "server MAIL";
+ DELAY_CONN("MAIL");
+
+ /* check for validity of this command */
+ if (!gothello && bitset(PRIV_NEEDMAILHELO, PrivacyFlags))
+ {
+ usrerr("503 5.0.0 Polite people say HELO first");
+ break;
+ }
+ if (smtp.sm_gotmail)
+ {
+ usrerr("503 5.5.0 Sender already specified");
+ break;
+ }
+#if SASL
+ if (bitset(SRV_REQ_AUTH, features) &&
+ authenticating != SASL_IS_AUTH)
+ {
+ usrerr("530 5.7.0 Authentication required");
+ break;
+ }
+#endif /* SASL */
+
+ p = skipword(p, "from");
+ if (p == NULL)
+ break;
+ if (tempfail)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP MAIL command (%.100s) from %.100s tempfailed (due to previous checks)",
+ p, CurSmtpClient);
+ usrerr(MSG_TEMPFAIL);
+ break;
+ }
+
+ /* make sure we know who the sending host is */
+ if (sendinghost == NULL)
+ sendinghost = peerhostname;
+
+
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&DebugLeakSmtp, 1))
+ {
+ sm_heap_newgroup();
+ sm_dprintf("smtp() heap group #%d\n",
+ sm_heap_group());
+ }
+#endif /* SM_HEAP_CHECK */
+
+ if (Errors > 0)
+ goto undo_no_pm;
+ if (!gothello)
+ {
+ auth_warning(e, "%s didn't use HELO protocol",
+ CurSmtpClient);
+ }
+#ifdef PICKY_HELO_CHECK
+ if (sm_strcasecmp(sendinghost, peerhostname) != 0 &&
+ (sm_strcasecmp(peerhostname, "localhost") != 0 ||
+ sm_strcasecmp(sendinghost, MyHostName) != 0))
+ {
+ auth_warning(e, "Host %s claimed to be %s",
+ CurSmtpClient, sendinghost);
+ }
+#endif /* PICKY_HELO_CHECK */
+
+ if (protocol == NULL)
+ protocol = "SMTP";
+ macdefine(&e->e_macro, A_PERM, 'r', protocol);
+ macdefine(&e->e_macro, A_PERM, 's', sendinghost);
+
+ if (Errors > 0)
+ goto undo_no_pm;
+ smtp.sm_nrcpts = 0;
+ n_badrcpts = 0;
+ macdefine(&e->e_macro, A_PERM, macid("{ntries}"), "0");
+ macdefine(&e->e_macro, A_PERM, macid("{nrcpts}"), "0");
+ e->e_flags |= EF_CLRQUEUE;
+ sm_setproctitle(true, e, "%s %s: %.80s",
+ qid_printname(e),
+ CurSmtpClient, inp);
+
+ /* do the processing */
+ SM_TRY
+ {
+ extern char *FullName;
+
+ QuickAbort = true;
+ SM_FREE_CLR(FullName);
+
+ /* must parse sender first */
+ delimptr = NULL;
+ setsender(p, e, &delimptr, ' ', false);
+ if (delimptr != NULL && *delimptr != '\0')
+ *delimptr++ = '\0';
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+
+ /* Successfully set e_from, allow logging */
+ e->e_flags |= EF_LOGSENDER;
+
+ /* put resulting triple from parseaddr() into macros */
+ if (e->e_from.q_mailer != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_mailer}"),
+ e->e_from.q_mailer->m_name);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_mailer}"), NULL);
+ if (e->e_from.q_host != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_host}"),
+ e->e_from.q_host);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_host}"), "localhost");
+ if (e->e_from.q_user != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_addr}"),
+ e->e_from.q_user);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{mail_addr}"), NULL);
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+
+ /* check for possible spoofing */
+ if (RealUid != 0 && OpMode == MD_SMTP &&
+ !wordinclass(RealUserName, 't') &&
+ (!bitnset(M_LOCALMAILER,
+ e->e_from.q_mailer->m_flags) ||
+ strcmp(e->e_from.q_user, RealUserName) != 0))
+ {
+ auth_warning(e, "%s owned process doing -bs",
+ RealUserName);
+ }
+
+ /* now parse ESMTP arguments */
+ e->e_msgsize = 0;
+ addr = p;
+ argno = 0;
+ args[argno++] = p;
+ p = delimptr;
+ while (p != NULL && *p != '\0')
+ {
+ char *kp;
+ char *vp = NULL;
+ char *equal = NULL;
+
+ /* locate the beginning of the keyword */
+ SKIP_SPACE(p);
+ if (*p == '\0')
+ break;
+ kp = p;
+
+ /* skip to the value portion */
+ while ((isascii(*p) && isalnum(*p)) || *p == '-')
+ p++;
+ if (*p == '=')
+ {
+ equal = p;
+ *p++ = '\0';
+ vp = p;
+
+ /* skip to the end of the value */
+ while (*p != '\0' && *p != ' ' &&
+ !(isascii(*p) && iscntrl(*p)) &&
+ *p != '=')
+ p++;
+ }
+
+ if (*p != '\0')
+ *p++ = '\0';
+
+ if (tTd(19, 1))
+ sm_dprintf("MAIL: got arg %s=\"%s\"\n", kp,
+ vp == NULL ? "<null>" : vp);
+
+ mail_esmtp_args(kp, vp, e);
+ if (equal != NULL)
+ *equal = '=';
+ args[argno++] = kp;
+ if (argno >= MAXSMTPARGS - 1)
+ usrerr("501 5.5.4 Too many parameters");
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ }
+ args[argno] = NULL;
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+
+#if SASL
+# if _FFR_AUTH_PASSING
+ /* set the default AUTH= if the sender didn't */
+ if (e->e_auth_param == NULL)
+ {
+ /* XXX only do this for an MSA? */
+ e->e_auth_param = macvalue(macid("{auth_authen}"),
+ e);
+ if (e->e_auth_param == NULL)
+ e->e_auth_param = "<>";
+
+ /*
+ ** XXX should we invoke Strust_auth now?
+ ** authorizing as the client that just
+ ** authenticated, so we'll trust implicitly
+ */
+ }
+# endif /* _FFR_AUTH_PASSING */
+#endif /* SASL */
+
+ /* do config file checking of the sender */
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "e s");
+#if _FFR_MAIL_MACRO
+ /* make the "real" sender address available */
+ macdefine(&e->e_macro, A_TEMP, macid("{mail_from}"),
+ e->e_from.q_paddr);
+#endif /* _FFR_MAIL_MACRO */
+ if (rscheck("check_mail", addr,
+ NULL, e, RSF_RMCOMM|RSF_COUNT, 3,
+ NULL, e->e_id) != EX_OK ||
+ Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), NULL);
+
+ if (MaxMessageSize > 0 &&
+ (e->e_msgsize > MaxMessageSize ||
+ e->e_msgsize < 0))
+ {
+ usrerr("552 5.2.3 Message size exceeds fixed maximum message size (%ld)",
+ MaxMessageSize);
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ }
+
+ /*
+ ** XXX always check whether there is at least one fs
+ ** with enough space?
+ ** However, this may not help much: the queue group
+ ** selection may later on select a FS that hasn't
+ ** enough space.
+ */
+
+ if ((NumFileSys == 1 || NumQueue == 1) &&
+ !enoughdiskspace(e->e_msgsize, e)
+#if _FFR_ANY_FREE_FS
+ && !filesys_free(e->e_msgsize)
+#endif /* _FFR_ANY_FREE_FS */
+ )
+ {
+ /*
+ ** We perform this test again when the
+ ** queue directory is selected, in collect.
+ */
+
+ usrerr("452 4.4.5 Insufficient disk space; try again later");
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ }
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+
+ LogUsrErrs = true;
+#if MILTER
+ if (smtp.sm_milterlist && smtp.sm_milterize &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ response = milter_envfrom(args, e, &state);
+ MILTER_REPLY("from");
+ }
+#endif /* MILTER */
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+
+ message("250 2.1.0 Sender ok");
+ smtp.sm_gotmail = true;
+ }
+ SM_EXCEPT(exc, "[!F]*")
+ {
+ /*
+ ** An error occurred while processing a MAIL command.
+ ** Jump to the common error handling code.
+ */
+
+ sm_exc_free(exc);
+ goto undo_no_pm;
+ }
+ SM_END_TRY
+ break;
+
+ undo_no_pm:
+ e->e_flags &= ~EF_PM_NOTIFY;
+ undo:
+ break;
+
+ case CMDRCPT: /* rcpt -- designate recipient */
+ DELAY_CONN("RCPT");
+ if (!smtp.sm_gotmail)
+ {
+ usrerr("503 5.0.0 Need MAIL before RCPT");
+ break;
+ }
+ SmtpPhase = "server RCPT";
+ SM_TRY
+ {
+ QuickAbort = true;
+ LogUsrErrs = true;
+
+ /* limit flooding of our machine */
+ if (MaxRcptPerMsg > 0 &&
+ smtp.sm_nrcpts >= MaxRcptPerMsg)
+ {
+ /* sleep(1); / * slow down? */
+ usrerr("452 4.5.3 Too many recipients");
+ goto rcpt_done;
+ }
+
+ if (e->e_sendmode != SM_DELIVER)
+ e->e_flags |= EF_VRFYONLY;
+
+#if MILTER
+ /*
+ ** If the filter will be deleting recipients,
+ ** don't expand them at RCPT time (in the call
+ ** to recipient()). If they are expanded, it
+ ** is impossible for removefromlist() to figure
+ ** out the expanded members of the original
+ ** recipient and mark them as QS_DONTSEND.
+ */
+
+ if (milter_can_delrcpts())
+ e->e_flags |= EF_VRFYONLY;
+#endif /* MILTER */
+
+ p = skipword(p, "to");
+ if (p == NULL)
+ goto rcpt_done;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "e r");
+ a = parseaddr(p, NULLADDR, RF_COPYALL, ' ', &delimptr,
+ e, true);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), NULL);
+ if (BadRcptThrottle > 0 &&
+ n_badrcpts >= BadRcptThrottle)
+ {
+ if (LogLevel > 5 &&
+ n_badrcpts == BadRcptThrottle)
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "%.100s: Possible SMTP RCPT flood, throttling.",
+ CurSmtpClient);
+
+ /* To avoid duplicated message */
+ n_badrcpts++;
+ }
+
+ /*
+ ** Don't use exponential backoff for now.
+ ** Some servers will open more connections
+ ** and actually overload the receiver even
+ ** more.
+ */
+
+ (void) sleep(1);
+ }
+ if (Errors > 0)
+ goto rcpt_done;
+ if (a == NULL)
+ {
+ usrerr("501 5.0.0 Missing recipient");
+ goto rcpt_done;
+ }
+
+ if (delimptr != NULL && *delimptr != '\0')
+ *delimptr++ = '\0';
+
+ /* put resulting triple from parseaddr() into macros */
+ if (a->q_mailer != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_mailer}"),
+ a->q_mailer->m_name);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_mailer}"), NULL);
+ if (a->q_host != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_host}"), a->q_host);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_host}"), "localhost");
+ if (a->q_user != NULL)
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_addr}"), a->q_user);
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_addr}"), NULL);
+ if (Errors > 0)
+ goto rcpt_done;
+
+ /* now parse ESMTP arguments */
+ addr = p;
+ argno = 0;
+ args[argno++] = p;
+ p = delimptr;
+ while (p != NULL && *p != '\0')
+ {
+ char *kp;
+ char *vp = NULL;
+ char *equal = NULL;
+
+ /* locate the beginning of the keyword */
+ SKIP_SPACE(p);
+ if (*p == '\0')
+ break;
+ kp = p;
+
+ /* skip to the value portion */
+ while ((isascii(*p) && isalnum(*p)) || *p == '-')
+ p++;
+ if (*p == '=')
+ {
+ equal = p;
+ *p++ = '\0';
+ vp = p;
+
+ /* skip to the end of the value */
+ while (*p != '\0' && *p != ' ' &&
+ !(isascii(*p) && iscntrl(*p)) &&
+ *p != '=')
+ p++;
+ }
+
+ if (*p != '\0')
+ *p++ = '\0';
+
+ if (tTd(19, 1))
+ sm_dprintf("RCPT: got arg %s=\"%s\"\n", kp,
+ vp == NULL ? "<null>" : vp);
+
+ rcpt_esmtp_args(a, kp, vp, e);
+ if (equal != NULL)
+ *equal = '=';
+ args[argno++] = kp;
+ if (argno >= MAXSMTPARGS - 1)
+ usrerr("501 5.5.4 Too many parameters");
+ if (Errors > 0)
+ break;
+ }
+ args[argno] = NULL;
+ if (Errors > 0)
+ goto rcpt_done;
+
+ /* do config file checking of the recipient */
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), "e r");
+ if (rscheck("check_rcpt", addr,
+ NULL, e, RSF_RMCOMM|RSF_COUNT, 3,
+ NULL, e->e_id) != EX_OK ||
+ Errors > 0)
+ goto rcpt_done;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{addr_type}"), NULL);
+
+ /* If discarding, don't bother to verify user */
+ if (bitset(EF_DISCARD, e->e_flags))
+ a->q_state = QS_VERIFIED;
+
+#if MILTER
+ if (smtp.sm_milterlist && smtp.sm_milterize &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ response = milter_envrcpt(args, e, &state);
+ MILTER_REPLY("to");
+ }
+#endif /* MILTER */
+
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_mailer}"), NULL);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_host}"), NULL);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{rcpt_addr}"), NULL);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_notify}"), NULL);
+ if (Errors > 0)
+ goto rcpt_done;
+
+ /* save in recipient list after ESMTP mods */
+ a = recipient(a, &e->e_sendqueue, 0, e);
+ if (Errors > 0)
+ goto rcpt_done;
+
+ /* no errors during parsing, but might be a duplicate */
+ e->e_to = a->q_paddr;
+ if (!QS_IS_BADADDR(a->q_state))
+ {
+ if (smtp.sm_nrcpts == 0)
+ initsys(e);
+ message("250 2.1.5 Recipient ok%s",
+ QS_IS_QUEUEUP(a->q_state) ?
+ " (will queue)" : "");
+ smtp.sm_nrcpts++;
+ }
+ else
+ {
+ /* punt -- should keep message in ADDRESS.... */
+ usrerr("550 5.1.1 Addressee unknown");
+ }
+ rcpt_done:
+ if (Errors > 0)
+ ++n_badrcpts;
+ }
+ SM_EXCEPT(exc, "[!F]*")
+ {
+ /* An exception occurred while processing RCPT */
+ e->e_flags &= ~(EF_FATALERRS|EF_PM_NOTIFY);
+ ++n_badrcpts;
+ }
+ SM_END_TRY
+ break;
+
+ case CMDDATA: /* data -- text of mail */
+ DELAY_CONN("DATA");
+ smtp_data(&smtp, e);
+ break;
+
+ case CMDRSET: /* rset -- reset state */
+ if (tTd(94, 100))
+ message("451 4.0.0 Test failure");
+ else
+ message("250 2.0.0 Reset state");
+ CLEAR_STATE(cmdbuf);
+#if _FFR_QUARANTINE
+ /* restore connection quarantining */
+ if (smtp.sm_quarmsg == NULL)
+ {
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), "");
+ }
+ else
+ {
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool,
+ smtp.sm_quarmsg);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ }
+#endif /* _FFR_QUARANTINE */
+ break;
+
+ case CMDVRFY: /* vrfy -- verify address */
+ case CMDEXPN: /* expn -- expand address */
+ vrfy = c->cmd_code == CMDVRFY;
+ DELAY_CONN(vrfy ? "VRFY" : "EXPN");
+ if (tempfail)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP %s command (%.100s) from %.100s tempfailed (due to previous checks)",
+ vrfy ? "VRFY" : "EXPN",
+ p, CurSmtpClient);
+
+ /* RFC 821 doesn't allow 4xy reply code */
+ usrerr("550 5.7.1 Please try again later");
+ break;
+ }
+ wt = checksmtpattack(&n_verifies, MAXVRFYCOMMANDS,
+ false, vrfy ? "VRFY" : "EXPN", e);
+ previous = curtime();
+ if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN,
+ PrivacyFlags))
+ {
+ if (vrfy)
+ message("252 2.5.2 Cannot VRFY user; try RCPT to attempt delivery (or try finger)");
+ else
+ message("502 5.7.0 Sorry, we do not allow this operation");
+ if (LogLevel > 5)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%.100s: %s [rejected]",
+ CurSmtpClient,
+ shortenstring(inp, MAXSHORTSTR));
+ break;
+ }
+ else if (!gothello &&
+ bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO,
+ PrivacyFlags))
+ {
+ usrerr("503 5.0.0 I demand that you introduce yourself first");
+ break;
+ }
+ if (Errors > 0)
+ break;
+ if (LogLevel > 5)
+ sm_syslog(LOG_INFO, e->e_id, "%.100s: %s",
+ CurSmtpClient,
+ shortenstring(inp, MAXSHORTSTR));
+ SM_TRY
+ {
+ QuickAbort = true;
+ vrfyqueue = NULL;
+ if (vrfy)
+ e->e_flags |= EF_VRFYONLY;
+ while (*p != '\0' && isascii(*p) && isspace(*p))
+ p++;
+ if (*p == '\0')
+ {
+ usrerr("501 5.5.2 Argument required");
+ }
+ else
+ {
+ /* do config file checking of the address */
+ if (rscheck(vrfy ? "check_vrfy" : "check_expn",
+ p, NULL, e, RSF_RMCOMM,
+ 3, NULL, NOQID) != EX_OK ||
+ Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ (void) sendtolist(p, NULLADDR, &vrfyqueue, 0, e);
+ }
+ if (wt > 0)
+ {
+ time_t t;
+
+ t = wt - (curtime() - previous);
+ if (t > 0)
+ (void) sleep(t);
+ }
+ if (Errors > 0)
+ sm_exc_raisenew_x(&EtypeQuickAbort, 1);
+ if (vrfyqueue == NULL)
+ {
+ usrerr("554 5.5.2 Nothing to %s", vrfy ? "VRFY" : "EXPN");
+ }
+ while (vrfyqueue != NULL)
+ {
+ if (!QS_IS_UNDELIVERED(vrfyqueue->q_state))
+ {
+ vrfyqueue = vrfyqueue->q_next;
+ continue;
+ }
+
+ /* see if there is more in the vrfy list */
+ a = vrfyqueue;
+ while ((a = a->q_next) != NULL &&
+ (!QS_IS_UNDELIVERED(a->q_state)))
+ continue;
+ printvrfyaddr(vrfyqueue, a == NULL, vrfy);
+ vrfyqueue = a;
+ }
+ }
+ SM_EXCEPT(exc, "[!F]*")
+ {
+ /*
+ ** An exception occurred while processing VRFY/EXPN
+ */
+
+ sm_exc_free(exc);
+ goto undo;
+ }
+ SM_END_TRY
+ break;
+
+ case CMDETRN: /* etrn -- force queue flush */
+ DELAY_CONN("ETRN");
+
+ /* Don't leak queue information via debug flags */
+ if (!bitset(SRV_OFFER_ETRN, features) || UseMSP ||
+ (RealUid != 0 && RealUid != TrustedUid &&
+ OpMode == MD_SMTP))
+ {
+ /* different message for MSA ? */
+ message("502 5.7.0 Sorry, we do not allow this operation");
+ if (LogLevel > 5)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%.100s: %s [rejected]",
+ CurSmtpClient,
+ shortenstring(inp, MAXSHORTSTR));
+ break;
+ }
+ if (tempfail)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id,
+ "SMTP ETRN command (%.100s) from %.100s tempfailed (due to previous checks)",
+ p, CurSmtpClient);
+ usrerr(MSG_TEMPFAIL);
+ break;
+ }
+
+ if (strlen(p) <= 0)
+ {
+ usrerr("500 5.5.2 Parameter required");
+ break;
+ }
+
+ /* crude way to avoid denial-of-service attacks */
+ (void) checksmtpattack(&n_etrn, MAXETRNCOMMANDS, true,
+ "ETRN", e);
+
+ /*
+ ** Do config file checking of the parameter.
+ ** Even though we have srv_features now, we still
+ ** need this ruleset because the former is called
+ ** when the connection has been established, while
+ ** this ruleset is called when the command is
+ ** actually issued and therefore has all information
+ ** available to make a decision.
+ */
+
+ if (rscheck("check_etrn", p, NULL, e,
+ RSF_RMCOMM, 3, NULL, NOQID) != EX_OK ||
+ Errors > 0)
+ break;
+
+ if (LogLevel > 5)
+ sm_syslog(LOG_INFO, e->e_id,
+ "%.100s: ETRN %s", CurSmtpClient,
+ shortenstring(p, MAXSHORTSTR));
+
+ id = p;
+ if (*id == '#')
+ {
+ int wgrp;
+
+ id++;
+ wgrp = name2qid(id);
+ if (!ISVALIDQGRP(wgrp))
+ {
+ usrerr("459 4.5.4 Queue %s unknown",
+ id);
+ break;
+ }
+ ok = run_work_group(wgrp, true, false,
+ false, true);
+ if (ok && Errors == 0)
+ message("250 2.0.0 Queuing for queue group %s started", id);
+ break;
+ }
+
+ if (*id == '@')
+ id++;
+ else
+ *--id = '@';
+
+ new = (QUEUE_CHAR *) sm_malloc(sizeof(QUEUE_CHAR));
+ if (new == NULL)
+ {
+ syserr("500 5.5.0 ETRN out of memory");
+ break;
+ }
+ new->queue_match = id;
+ new->queue_negate = false;
+ new->queue_next = NULL;
+ QueueLimitRecipient = new;
+ ok = runqueue(true, false, false, true);
+ sm_free(QueueLimitRecipient); /* XXX */
+ QueueLimitRecipient = NULL;
+ if (ok && Errors == 0)
+ message("250 2.0.0 Queuing for node %s started", p);
+ break;
+
+ case CMDHELP: /* help -- give user info */
+ DELAY_CONN("HELP");
+ help(p, e);
+ break;
+
+ case CMDNOOP: /* noop -- do nothing */
+ DELAY_CONN("NOOP");
+ (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, true,
+ "NOOP", e);
+ message("250 2.0.0 OK");
+ break;
+
+ case CMDQUIT: /* quit -- leave mail */
+ message("221 2.0.0 %s closing connection", MyHostName);
+#if PIPELINING
+ (void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
+#endif /* PIPELINING */
+
+ if (smtp.sm_nrcpts > 0)
+ logundelrcpts(e, "aborted by sender", 9, false);
+
+ /* arrange to ignore any current send list */
+ e->e_sendqueue = NULL;
+
+#if STARTTLS
+ /* shutdown TLS connection */
+ if (tls_active)
+ {
+ (void) endtls(srv_ssl, "server");
+ tls_active = false;
+ }
+#endif /* STARTTLS */
+#if SASL
+ if (authenticating == SASL_IS_AUTH)
+ {
+ sasl_dispose(&conn);
+ authenticating = SASL_NOT_AUTH;
+ /* XXX sasl_done(); this is a child */
+ }
+#endif /* SASL */
+
+doquit:
+ /* avoid future 050 messages */
+ disconnect(1, e);
+
+#if MILTER
+ /* close out milter filters */
+ milter_quit(e);
+#endif /* MILTER */
+
+ if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags))
+ logsender(e, NULL);
+ e->e_flags &= ~EF_LOGSENDER;
+
+ if (lognullconnection && LogLevel > 5 &&
+ nullserver == NULL)
+ {
+ char *d;
+
+ d = macvalue(macid("{daemon_name}"), e);
+ if (d == NULL)
+ d = "stdin";
+
+ /*
+ ** even though this id is "bogus", it makes
+ ** it simpler to "grep" related events, e.g.,
+ ** timeouts for the same connection.
+ */
+
+ sm_syslog(LOG_INFO, e->e_id,
+ "%.100s did not issue MAIL/EXPN/VRFY/ETRN during connection to %s",
+ CurSmtpClient, d);
+ }
+#if PROFILING
+ return;
+#endif /* PROFILING */
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+
+ case CMDVERB: /* set verbose mode */
+ DELAY_CONN("VERB");
+ if (bitset(PRIV_NOEXPN, PrivacyFlags) ||
+ !bitset(SRV_OFFER_VERB, features) ||
+ bitset(PRIV_NOVERB, PrivacyFlags))
+ {
+ /* this would give out the same info */
+ message("502 5.7.0 Verbose unavailable");
+ break;
+ }
+ (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, true,
+ "VERB", e);
+ Verbose = 1;
+ set_delivery_mode(SM_DELIVER, e);
+ message("250 2.0.0 Verbose mode");
+ break;
+
+#if SMTPDEBUG
+ case CMDDBGQSHOW: /* show queues */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Send Queue=");
+ printaddr(e->e_sendqueue, true);
+ break;
+
+ case CMDDBGDEBUG: /* set debug mode */
+ tTsetup(tTdvect, sizeof tTdvect, "0-99.1");
+ tTflag(p);
+ message("200 2.0.0 Debug set");
+ break;
+
+#else /* SMTPDEBUG */
+ case CMDDBGQSHOW: /* show queues */
+ case CMDDBGDEBUG: /* set debug mode */
+#endif /* SMTPDEBUG */
+ case CMDLOGBOGUS: /* bogus command */
+ DELAY_CONN("Bogus");
+ if (LogLevel > 0)
+ sm_syslog(LOG_CRIT, e->e_id,
+ "\"%s\" command from %.100s (%.100s)",
+ c->cmd_name, CurSmtpClient,
+ anynet_ntoa(&RealHostAddr));
+ /* FALLTHROUGH */
+
+ case CMDERROR: /* unknown command */
+#if MAXBADCOMMANDS > 0
+ if (++n_badcmds > MAXBADCOMMANDS)
+ {
+ message("421 4.7.0 %s Too many bad commands; closing connection",
+ MyHostName);
+
+ /* arrange to ignore any current send list */
+ e->e_sendqueue = NULL;
+ goto doquit;
+ }
+#endif /* MAXBADCOMMANDS > 0 */
+
+ usrerr("500 5.5.1 Command unrecognized: \"%s\"",
+ shortenstring(inp, MAXSHORTSTR));
+ break;
+
+ case CMDUNIMPL:
+ DELAY_CONN("Unimpl");
+ usrerr("502 5.5.1 Command not implemented: \"%s\"",
+ shortenstring(inp, MAXSHORTSTR));
+ break;
+
+ default:
+ DELAY_CONN("default");
+ errno = 0;
+ syserr("500 5.5.0 smtp: unknown code %d", c->cmd_code);
+ break;
+ }
+#if SASL
+ }
+#endif /* SASL */
+ }
+ SM_EXCEPT(exc, "[!F]*")
+ {
+ /*
+ ** The only possible exception is "E:mta.quickabort".
+ ** There is nothing to do except fall through and loop.
+ */
+ }
+ SM_END_TRY
+ }
+}
+/*
+** SMTP_DATA -- implement the SMTP DATA command.
+**
+** Parameters:
+** smtp -- status of SMTP connection.
+** e -- envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** possibly sends message.
+*/
+
+static void
+smtp_data(smtp, e)
+ SMTP_T *smtp;
+ ENVELOPE *e;
+{
+#if MILTER
+ bool milteraccept;
+#endif /* MILTER */
+ bool aborting;
+ bool doublequeue;
+ ADDRESS *a;
+ ENVELOPE *ee;
+ char *id;
+ char *oldid;
+ char buf[32];
+
+ SmtpPhase = "server DATA";
+ if (!smtp->sm_gotmail)
+ {
+ usrerr("503 5.0.0 Need MAIL command");
+ return;
+ }
+ else if (smtp->sm_nrcpts <= 0)
+ {
+ usrerr("503 5.0.0 Need RCPT (recipient)");
+ return;
+ }
+ (void) sm_snprintf(buf, sizeof buf, "%u", smtp->sm_nrcpts);
+ if (rscheck("check_data", buf, NULL, e,
+ RSF_RMCOMM|RSF_UNSTRUCTURED|RSF_COUNT, 3, NULL,
+ e->e_id) != EX_OK)
+ return;
+
+ /* put back discard bit */
+ if (smtp->sm_discard)
+ e->e_flags |= EF_DISCARD;
+
+ /* check to see if we need to re-expand aliases */
+ /* also reset QS_BADADDR on already-diagnosted addrs */
+ doublequeue = false;
+ for (a = e->e_sendqueue; a != NULL; a = a->q_next)
+ {
+ if (QS_IS_VERIFIED(a->q_state) &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ /* need to re-expand aliases */
+ doublequeue = true;
+ }
+ if (QS_IS_BADADDR(a->q_state))
+ {
+ /* make this "go away" */
+ a->q_state = QS_DONTSEND;
+ }
+ }
+
+ /* collect the text of the message */
+ SmtpPhase = "collect";
+ buffer_errors();
+
+#if _FFR_ADAPTIVE_EOL
+ /* triggers error in collect, disabled for now */
+ if (smtp->sm_crlf)
+ e->e_flags |= EF_NL_NOT_EOL;
+#endif /* _FFR_ADAPTIVE_EOL */
+
+ collect(InChannel, true, NULL, e);
+
+ /* redefine message size */
+ (void) sm_snprintf(buf, sizeof buf, "%ld", e->e_msgsize);
+ macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), buf);
+
+#if _FFR_CHECK_EOM
+ /* rscheck() will set Errors or EF_DISCARD if it trips */
+ (void) rscheck("check_eom", buf, NULL, e, RSF_UNSTRUCTURED|RSF_COUNT,
+ 3, NULL, e->e_id);
+#endif /* _FFR_CHECK_EOM */
+
+#if MILTER
+ milteraccept = true;
+ if (smtp->sm_milterlist && smtp->sm_milterize &&
+ Errors <= 0 &&
+ !bitset(EF_DISCARD, e->e_flags))
+ {
+ char state;
+ char *response;
+
+ response = milter_data(e, &state);
+ switch (state)
+ {
+ case SMFIR_REPLYCODE:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: data, reject=%s",
+ response);
+ milteraccept = false;
+ usrerr(response);
+ break;
+
+ case SMFIR_REJECT:
+ milteraccept = false;
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: data, reject=554 5.7.1 Command rejected");
+ usrerr("554 5.7.1 Command rejected");
+ break;
+
+ case SMFIR_DISCARD:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: data, discard");
+ milteraccept = false;
+ e->e_flags |= EF_DISCARD;
+ break;
+
+ case SMFIR_TEMPFAIL:
+ if (MilterLogLevel > 3)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Milter: data, reject=%s",
+ MSG_TEMPFAIL);
+ milteraccept = false;
+ usrerr(MSG_TEMPFAIL);
+ break;
+ }
+ if (response != NULL)
+ sm_free(response);
+ }
+
+ /* Milter may have changed message size */
+ (void) sm_snprintf(buf, sizeof buf, "%ld", e->e_msgsize);
+ macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), buf);
+
+ /* abort message filters that didn't get the body & log msg is OK */
+ if (smtp->sm_milterlist && smtp->sm_milterize)
+ {
+ milter_abort(e);
+ if (milteraccept && MilterLogLevel > 9)
+ sm_syslog(LOG_INFO, e->e_id, "Milter accept: message");
+ }
+#endif /* MILTER */
+
+#if _FFR_QUARANTINE
+ /* Check if quarantining stats should be updated */
+ if (e->e_quarmsg != NULL)
+ markstats(e, NULL, STATS_QUARANTINE);
+#endif /* _FFR_QUARANTINE */
+
+ /*
+ ** If a header/body check (header checks or milter)
+ ** set EF_DISCARD, don't queueup the message --
+ ** that would lose the EF_DISCARD bit and deliver
+ ** the message.
+ */
+
+ if (bitset(EF_DISCARD, e->e_flags))
+ doublequeue = false;
+
+ aborting = Errors > 0;
+ if (!aborting &&
+#if _FFR_QUARANTINE
+ (QueueMode == QM_QUARANTINE || e->e_quarmsg == NULL) &&
+#endif /* _FFR_QUARANTINE */
+ !split_by_recipient(e))
+ aborting = bitset(EF_FATALERRS, e->e_flags);
+
+ if (aborting)
+ {
+ /* Log who the mail would have gone to */
+ logundelrcpts(e, e->e_message, 8, false);
+ flush_errors(true);
+ buffer_errors();
+ goto abortmessage;
+ }
+
+ /* from now on, we have to operate silently */
+ buffer_errors();
+
+#if 0
+ /*
+ ** Clear message, it may contain an error from the SMTP dialogue.
+ ** This error must not show up in the queue.
+ ** Some error message should show up, e.g., alias database
+ ** not available, but others shouldn't, e.g., from check_rcpt.
+ */
+
+ e->e_message = NULL;
+#endif /* 0 */
+
+ /*
+ ** Arrange to send to everyone.
+ ** If sending to multiple people, mail back
+ ** errors rather than reporting directly.
+ ** In any case, don't mail back errors for
+ ** anything that has happened up to
+ ** now (the other end will do this).
+ ** Truncate our transcript -- the mail has gotten
+ ** to us successfully, and if we have
+ ** to mail this back, it will be easier
+ ** on the reader.
+ ** Then send to everyone.
+ ** Finally give a reply code. If an error has
+ ** already been given, don't mail a
+ ** message back.
+ ** We goose error returns by clearing error bit.
+ */
+
+ SmtpPhase = "delivery";
+ (void) sm_io_setinfo(e->e_xfp, SM_BF_TRUNCATE, NULL);
+ id = e->e_id;
+
+#if NAMED_BIND
+ _res.retry = TimeOuts.res_retry[RES_TO_FIRST];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
+#endif /* NAMED_BIND */
+
+ for (ee = e; ee != NULL; ee = ee->e_sibling)
+ {
+ /* make sure we actually do delivery */
+ ee->e_flags &= ~EF_CLRQUEUE;
+
+ /* from now on, operate silently */
+ ee->e_errormode = EM_MAIL;
+
+ if (doublequeue)
+ {
+ /* make sure it is in the queue */
+ queueup(ee, false, true);
+ }
+ else
+ {
+ /* send to all recipients */
+ sendall(ee, SM_DEFAULT);
+ }
+ ee->e_to = NULL;
+ }
+
+ /* put back id for SMTP logging in putoutmsg() */
+ oldid = CurEnv->e_id;
+ CurEnv->e_id = id;
+
+ /* issue success message */
+ message("250 2.0.0 %s Message accepted for delivery", id);
+ CurEnv->e_id = oldid;
+
+ /* if we just queued, poke it */
+ if (doublequeue)
+ {
+ bool anything_to_send = false;
+
+ sm_getla();
+ for (ee = e; ee != NULL; ee = ee->e_sibling)
+ {
+ if (WILL_BE_QUEUED(ee->e_sendmode))
+ continue;
+ if (shouldqueue(ee->e_msgpriority, ee->e_ctime))
+ {
+ ee->e_sendmode = SM_QUEUE;
+ continue;
+ }
+#if _FFR_QUARANTINE
+ else if (QueueMode != QM_QUARANTINE &&
+ ee->e_quarmsg != NULL)
+ {
+ ee->e_sendmode = SM_QUEUE;
+ continue;
+ }
+#endif /* _FFR_QUARANTINE */
+ anything_to_send = true;
+
+ /* close all the queue files */
+ closexscript(ee);
+ if (ee->e_dfp != NULL)
+ {
+ (void) sm_io_close(ee->e_dfp, SM_TIME_DEFAULT);
+ ee->e_dfp = NULL;
+ }
+ unlockqueue(ee);
+ }
+ if (anything_to_send)
+ {
+#if PIPELINING
+ /*
+ ** XXX if we don't do this, we get 250 twice
+ ** because it is also flushed in the child.
+ */
+
+ (void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
+#endif /* PIPELINING */
+ (void) doworklist(e, true, true);
+ }
+ }
+
+ abortmessage:
+ if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags))
+ logsender(e, NULL);
+ e->e_flags &= ~EF_LOGSENDER;
+
+ /* clean up a bit */
+ smtp->sm_gotmail = false;
+
+ /*
+ ** Call dropenvelope if and only if the envelope is *not*
+ ** being processed by the child process forked by doworklist().
+ */
+
+ if (aborting || bitset(EF_DISCARD, e->e_flags))
+ dropenvelope(e, true, false);
+ else
+ {
+ for (ee = e; ee != NULL; ee = ee->e_sibling)
+ {
+#if _FFR_QUARANTINE
+ if (!doublequeue &&
+ QueueMode != QM_QUARANTINE &&
+ ee->e_quarmsg != NULL)
+ {
+ dropenvelope(ee, true, false);
+ continue;
+ }
+#endif /* _FFR_QUARANTINE */
+ if (WILL_BE_QUEUED(ee->e_sendmode))
+ dropenvelope(ee, true, false);
+ }
+ }
+ sm_rpool_free(e->e_rpool);
+
+ /*
+ ** At this point, e == &MainEnvelope, but if we did splitting,
+ ** then CurEnv may point to an envelope structure that was just
+ ** freed with the rpool. So reset CurEnv *before* calling
+ ** newenvelope.
+ */
+
+ CurEnv = e;
+ newenvelope(e, e, sm_rpool_new_x(NULL));
+ e->e_flags = BlankEnvelope.e_flags;
+
+#if _FFR_QUARANTINE
+ /* restore connection quarantining */
+ if (smtp->sm_quarmsg == NULL)
+ {
+ e->e_quarmsg = NULL;
+ macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), "");
+ }
+ else
+ {
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, smtp->sm_quarmsg);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ }
+#endif /* _FFR_QUARANTINE */
+}
+/*
+** LOGUNDELRCPTS -- log undelivered (or all) recipients.
+**
+** Parameters:
+** e -- envelope.
+** msg -- message for Stat=
+** level -- log level.
+** all -- log all recipients.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** logs undelivered (or all) recipients
+*/
+
+void
+logundelrcpts(e, msg, level, all)
+ ENVELOPE *e;
+ char *msg;
+ int level;
+ bool all;
+{
+ ADDRESS *a;
+
+ if (LogLevel <= level || msg == NULL || *msg == '\0')
+ return;
+
+ /* Clear $h so relay= doesn't get mislogged by logdelivery() */
+ macdefine(&e->e_macro, A_PERM, 'h', NULL);
+
+ /* Log who the mail would have gone to */
+ for (a = e->e_sendqueue; a != NULL; a = a->q_next)
+ {
+ if (!QS_IS_UNDELIVERED(a->q_state) && !all)
+ continue;
+ e->e_to = a->q_paddr;
+ logdelivery(NULL, NULL, a->q_status, msg, NULL,
+ (time_t) 0, e);
+ }
+ e->e_to = NULL;
+}
+/*
+** CHECKSMTPATTACK -- check for denial-of-service attack by repetition
+**
+** Parameters:
+** pcounter -- pointer to a counter for this command.
+** maxcount -- maximum value for this counter before we
+** slow down.
+** waitnow -- sleep now (in this routine)?
+** cname -- command name for logging.
+** e -- the current envelope.
+**
+** Returns:
+** time to wait.
+**
+** Side Effects:
+** Slows down if we seem to be under attack.
+*/
+
+static time_t
+checksmtpattack(pcounter, maxcount, waitnow, cname, e)
+ volatile unsigned int *pcounter;
+ int maxcount;
+ bool waitnow;
+ char *cname;
+ ENVELOPE *e;
+{
+ if (maxcount <= 0) /* no limit */
+ return (time_t) 0;
+
+ if (++(*pcounter) >= maxcount)
+ {
+ time_t s;
+
+ if (*pcounter == maxcount && LogLevel > 5)
+ {
+ sm_syslog(LOG_INFO, e->e_id,
+ "%.100s: possible SMTP attack: command=%.40s, count=%u",
+ CurSmtpClient, cname, *pcounter);
+ }
+ s = 1 << (*pcounter - maxcount);
+ if (s >= MAXTIMEOUT || s <= 0)
+ s = MAXTIMEOUT;
+
+ /* sleep at least 1 second before returning */
+ (void) sleep(*pcounter / maxcount);
+ s -= *pcounter / maxcount;
+ if (waitnow)
+ {
+ (void) sleep(s);
+ return 0;
+ }
+ return s;
+ }
+ return (time_t) 0;
+}
+/*
+** SETUP_SMTPD_IO -- setup I/O fd correctly for the SMTP server
+**
+** Parameters:
+** none.
+**
+** Returns:
+** nothing.
+**
+** Side Effects:
+** may change I/O fd.
+*/
+
+static void
+setup_smtpd_io()
+{
+ int inchfd, outchfd, outfd;
+
+ inchfd = sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL);
+ outchfd = sm_io_getinfo(OutChannel, SM_IO_WHAT_FD, NULL);
+ outfd = sm_io_getinfo(smioout, SM_IO_WHAT_FD, NULL);
+ if (outchfd != outfd)
+ {
+ /* arrange for debugging output to go to remote host */
+ (void) dup2(outchfd, outfd);
+ }
+
+ /*
+ ** if InChannel and OutChannel are stdin/stdout
+ ** and connected to ttys
+ ** and fcntl(STDIN, F_SETFL, O_NONBLOCKING) also changes STDOUT,
+ ** then "chain" them together.
+ */
+
+ if (inchfd == STDIN_FILENO && outchfd == STDOUT_FILENO &&
+ isatty(inchfd) && isatty(outchfd))
+ {
+ int inmode, outmode;
+
+ inmode = fcntl(inchfd, F_GETFL, 0);
+ if (inmode == -1)
+ {
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, NOQID,
+ "fcntl(inchfd, F_GETFL) failed: %s",
+ sm_errstring(errno));
+ return;
+ }
+ outmode = fcntl(outchfd, F_GETFL, 0);
+ if (outmode == -1)
+ {
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, NOQID,
+ "fcntl(outchfd, F_GETFL) failed: %s",
+ sm_errstring(errno));
+ return;
+ }
+ if (bitset(O_NONBLOCK, inmode) ||
+ bitset(O_NONBLOCK, outmode) ||
+ fcntl(inchfd, F_SETFL, inmode | O_NONBLOCK) == -1)
+ return;
+ outmode = fcntl(outchfd, F_GETFL, 0);
+ if (outmode != -1 && bitset(O_NONBLOCK, outmode))
+ {
+ /* changing InChannel also changes OutChannel */
+ sm_io_automode(OutChannel, InChannel);
+ if (tTd(97, 4) && LogLevel > 9)
+ sm_syslog(LOG_INFO, NOQID,
+ "set automode for I (%d)/O (%d) in SMTP server",
+ inchfd, outchfd);
+ }
+
+ /* undo change of inchfd */
+ (void) fcntl(inchfd, F_SETFL, inmode);
+ }
+}
+/*
+** SKIPWORD -- skip a fixed word.
+**
+** Parameters:
+** p -- place to start looking.
+** w -- word to skip.
+**
+** Returns:
+** p following w.
+** NULL on error.
+**
+** Side Effects:
+** clobbers the p data area.
+*/
+
+static char *
+skipword(p, w)
+ register char *volatile p;
+ char *w;
+{
+ register char *q;
+ char *firstp = p;
+
+ /* find beginning of word */
+ SKIP_SPACE(p);
+ q = p;
+
+ /* find end of word */
+ while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p)))
+ p++;
+ while (isascii(*p) && isspace(*p))
+ *p++ = '\0';
+ if (*p != ':')
+ {
+ syntax:
+ usrerr("501 5.5.2 Syntax error in parameters scanning \"%s\"",
+ shortenstring(firstp, MAXSHORTSTR));
+ return NULL;
+ }
+ *p++ = '\0';
+ SKIP_SPACE(p);
+
+ if (*p == '\0')
+ goto syntax;
+
+ /* see if the input word matches desired word */
+ if (sm_strcasecmp(q, w))
+ goto syntax;
+
+ return p;
+}
+/*
+** MAIL_ESMTP_ARGS -- process ESMTP arguments from MAIL line
+**
+** Parameters:
+** kp -- the parameter key.
+** vp -- the value of that parameter.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+static void
+mail_esmtp_args(kp, vp, e)
+ char *kp;
+ char *vp;
+ ENVELOPE *e;
+{
+ if (sm_strcasecmp(kp, "size") == 0)
+ {
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 SIZE requires a value");
+ /* NOTREACHED */
+ }
+ macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), vp);
+ errno = 0;
+ e->e_msgsize = strtol(vp, (char **) NULL, 10);
+ if (e->e_msgsize == LONG_MAX && errno == ERANGE)
+ {
+ usrerr("552 5.2.3 Message size exceeds maximum value");
+ /* NOTREACHED */
+ }
+ if (e->e_msgsize < 0)
+ {
+ usrerr("552 5.2.3 Message size invalid");
+ /* NOTREACHED */
+ }
+ }
+ else if (sm_strcasecmp(kp, "body") == 0)
+ {
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 BODY requires a value");
+ /* NOTREACHED */
+ }
+ else if (sm_strcasecmp(vp, "8bitmime") == 0)
+ {
+ SevenBitInput = false;
+ }
+ else if (sm_strcasecmp(vp, "7bit") == 0)
+ {
+ SevenBitInput = true;
+ }
+ else
+ {
+ usrerr("501 5.5.4 Unknown BODY type %s", vp);
+ /* NOTREACHED */
+ }
+ e->e_bodytype = sm_rpool_strdup_x(e->e_rpool, vp);
+ }
+ else if (sm_strcasecmp(kp, "envid") == 0)
+ {
+ if (bitset(PRIV_NORECEIPTS, PrivacyFlags))
+ {
+ usrerr("504 5.7.0 Sorry, ENVID not supported, we do not allow DSN");
+ /* NOTREACHED */
+ }
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 ENVID requires a value");
+ /* NOTREACHED */
+ }
+ if (!xtextok(vp))
+ {
+ usrerr("501 5.5.4 Syntax error in ENVID parameter value");
+ /* NOTREACHED */
+ }
+ if (e->e_envid != NULL)
+ {
+ usrerr("501 5.5.0 Duplicate ENVID parameter");
+ /* NOTREACHED */
+ }
+ e->e_envid = sm_rpool_strdup_x(e->e_rpool, vp);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_envid}"), e->e_envid);
+ }
+ else if (sm_strcasecmp(kp, "ret") == 0)
+ {
+ if (bitset(PRIV_NORECEIPTS, PrivacyFlags))
+ {
+ usrerr("504 5.7.0 Sorry, RET not supported, we do not allow DSN");
+ /* NOTREACHED */
+ }
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 RET requires a value");
+ /* NOTREACHED */
+ }
+ if (bitset(EF_RET_PARAM, e->e_flags))
+ {
+ usrerr("501 5.5.0 Duplicate RET parameter");
+ /* NOTREACHED */
+ }
+ e->e_flags |= EF_RET_PARAM;
+ if (sm_strcasecmp(vp, "hdrs") == 0)
+ e->e_flags |= EF_NO_BODY_RETN;
+ else if (sm_strcasecmp(vp, "full") != 0)
+ {
+ usrerr("501 5.5.2 Bad argument \"%s\" to RET", vp);
+ /* NOTREACHED */
+ }
+ macdefine(&e->e_macro, A_TEMP, macid("{dsn_ret}"), vp);
+ }
+#if SASL
+ else if (sm_strcasecmp(kp, "auth") == 0)
+ {
+ int len;
+ char *q;
+ char *auth_param; /* the value of the AUTH=x */
+ bool saveQuickAbort = QuickAbort;
+ bool saveSuprErrs = SuprErrs;
+ bool saveExitStat = ExitStat;
+ char pbuf[256];
+
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 AUTH= requires a value");
+ /* NOTREACHED */
+ }
+ if (e->e_auth_param != NULL)
+ {
+ usrerr("501 5.5.0 Duplicate AUTH parameter");
+ /* NOTREACHED */
+ }
+ if ((q = strchr(vp, ' ')) != NULL)
+ len = q - vp + 1;
+ else
+ len = strlen(vp) + 1;
+ auth_param = xalloc(len);
+ (void) sm_strlcpy(auth_param, vp, len);
+ if (!xtextok(auth_param))
+ {
+ usrerr("501 5.5.4 Syntax error in AUTH parameter value");
+ /* just a warning? */
+ /* NOTREACHED */
+ }
+
+ /* XXX this might be cut off */
+ (void) sm_strlcpy(pbuf, xuntextify(auth_param), sizeof pbuf);
+ /* xalloc() the buffer instead? */
+
+ /* XXX define this always or only if trusted? */
+ macdefine(&e->e_macro, A_TEMP, macid("{auth_author}"), pbuf);
+
+ /*
+ ** call Strust_auth to find out whether
+ ** auth_param is acceptable (trusted)
+ ** we shouldn't trust it if not authenticated
+ ** (required by RFC, leave it to ruleset?)
+ */
+
+ SuprErrs = true;
+ QuickAbort = false;
+ if (strcmp(auth_param, "<>") != 0 &&
+ (rscheck("trust_auth", pbuf, NULL, e, RSF_RMCOMM,
+ 9, NULL, NOQID) != EX_OK || Errors > 0))
+ {
+ if (tTd(95, 8))
+ {
+ q = e->e_auth_param;
+ sm_dprintf("auth=\"%.100s\" not trusted user=\"%.100s\"\n",
+ pbuf, (q == NULL) ? "" : q);
+ }
+
+ /* not trusted */
+ e->e_auth_param = "<>";
+# if _FFR_AUTH_PASSING
+ macdefine(&BlankEnvelope.e_macro, A_PERM,
+ macid("{auth_author}"), NULL);
+# endif /* _FFR_AUTH_PASSING */
+ }
+ else
+ {
+ if (tTd(95, 8))
+ sm_dprintf("auth=\"%.100s\" trusted\n", pbuf);
+ e->e_auth_param = sm_rpool_strdup_x(e->e_rpool,
+ auth_param);
+ }
+ sm_free(auth_param); /* XXX */
+
+ /* reset values */
+ Errors = 0;
+ QuickAbort = saveQuickAbort;
+ SuprErrs = saveSuprErrs;
+ ExitStat = saveExitStat;
+ }
+#endif /* SASL */
+#define PRTCHAR(c) ((isascii(c) && isprint(c)) ? (c) : '?')
+
+ /*
+ ** "by" is only accepted if DeliverByMin >= 0.
+ ** We maybe could add this to the list of server_features.
+ */
+
+ else if (sm_strcasecmp(kp, "by") == 0 && DeliverByMin >= 0)
+ {
+ char *s;
+
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 BY= requires a value");
+ /* NOTREACHED */
+ }
+ errno = 0;
+ e->e_deliver_by = strtol(vp, &s, 10);
+ if (e->e_deliver_by == LONG_MIN ||
+ e->e_deliver_by == LONG_MAX ||
+ e->e_deliver_by > 999999999l ||
+ e->e_deliver_by < -999999999l)
+ {
+ usrerr("501 5.5.2 BY=%s out of range", vp);
+ /* NOTREACHED */
+ }
+ if (s == NULL || *s != ';')
+ {
+ usrerr("501 5.5.2 BY= missing ';'");
+ /* NOTREACHED */
+ }
+ e->e_dlvr_flag = 0;
+ ++s; /* XXX: spaces allowed? */
+ SKIP_SPACE(s);
+ switch (tolower(*s))
+ {
+ case 'n':
+ e->e_dlvr_flag = DLVR_NOTIFY;
+ break;
+ case 'r':
+ e->e_dlvr_flag = DLVR_RETURN;
+ if (e->e_deliver_by <= 0)
+ {
+ usrerr("501 5.5.4 mode R requires BY time > 0");
+ /* NOTREACHED */
+ }
+ if (DeliverByMin > 0 && e->e_deliver_by > 0 &&
+ e->e_deliver_by < DeliverByMin)
+ {
+ usrerr("555 5.5.2 time %ld less than %ld",
+ e->e_deliver_by, (long) DeliverByMin);
+ /* NOTREACHED */
+ }
+ break;
+ default:
+ usrerr("501 5.5.2 illegal by-mode '%c'", PRTCHAR(*s));
+ /* NOTREACHED */
+ }
+ ++s; /* XXX: spaces allowed? */
+ SKIP_SPACE(s);
+ switch (tolower(*s))
+ {
+ case 't':
+ e->e_dlvr_flag |= DLVR_TRACE;
+ break;
+ case '\0':
+ break;
+ default:
+ usrerr("501 5.5.2 illegal by-trace '%c'", PRTCHAR(*s));
+ /* NOTREACHED */
+ }
+
+ /* XXX: check whether more characters follow? */
+ }
+ else
+ {
+ usrerr("555 5.5.4 %s parameter unrecognized", kp);
+ /* NOTREACHED */
+ }
+}
+/*
+** RCPT_ESMTP_ARGS -- process ESMTP arguments from RCPT line
+**
+** Parameters:
+** a -- the address corresponding to the To: parameter.
+** kp -- the parameter key.
+** vp -- the value of that parameter.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+static void
+rcpt_esmtp_args(a, kp, vp, e)
+ ADDRESS *a;
+ char *kp;
+ char *vp;
+ ENVELOPE *e;
+{
+ if (sm_strcasecmp(kp, "notify") == 0)
+ {
+ char *p;
+
+ if (bitset(PRIV_NORECEIPTS, PrivacyFlags))
+ {
+ usrerr("504 5.7.0 Sorry, NOTIFY not supported, we do not allow DSN");
+ /* NOTREACHED */
+ }
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 NOTIFY requires a value");
+ /* NOTREACHED */
+ }
+ a->q_flags &= ~(QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY);
+ a->q_flags |= QHASNOTIFY;
+ macdefine(&e->e_macro, A_TEMP, macid("{dsn_notify}"), vp);
+
+ if (sm_strcasecmp(vp, "never") == 0)
+ return;
+ for (p = vp; p != NULL; vp = p)
+ {
+ p = strchr(p, ',');
+ if (p != NULL)
+ *p++ = '\0';
+ if (sm_strcasecmp(vp, "success") == 0)
+ a->q_flags |= QPINGONSUCCESS;
+ else if (sm_strcasecmp(vp, "failure") == 0)
+ a->q_flags |= QPINGONFAILURE;
+ else if (sm_strcasecmp(vp, "delay") == 0)
+ a->q_flags |= QPINGONDELAY;
+ else
+ {
+ usrerr("501 5.5.4 Bad argument \"%s\" to NOTIFY",
+ vp);
+ /* NOTREACHED */
+ }
+ }
+ }
+ else if (sm_strcasecmp(kp, "orcpt") == 0)
+ {
+ if (bitset(PRIV_NORECEIPTS, PrivacyFlags))
+ {
+ usrerr("504 5.7.0 Sorry, ORCPT not supported, we do not allow DSN");
+ /* NOTREACHED */
+ }
+ if (vp == NULL)
+ {
+ usrerr("501 5.5.2 ORCPT requires a value");
+ /* NOTREACHED */
+ }
+ if (strchr(vp, ';') == NULL || !xtextok(vp))
+ {
+ usrerr("501 5.5.4 Syntax error in ORCPT parameter value");
+ /* NOTREACHED */
+ }
+ if (a->q_orcpt != NULL)
+ {
+ usrerr("501 5.5.0 Duplicate ORCPT parameter");
+ /* NOTREACHED */
+ }
+ a->q_orcpt = sm_rpool_strdup_x(e->e_rpool, vp);
+ }
+ else
+ {
+ usrerr("555 5.5.4 %s parameter unrecognized", kp);
+ /* NOTREACHED */
+ }
+}
+/*
+** PRINTVRFYADDR -- print an entry in the verify queue
+**
+** Parameters:
+** a -- the address to print.
+** last -- set if this is the last one.
+** vrfy -- set if this is a VRFY command.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Prints the appropriate 250 codes.
+*/
+#define OFFF (3 + 1 + 5 + 1) /* offset in fmt: SMTP reply + enh. code */
+
+static void
+printvrfyaddr(a, last, vrfy)
+ register ADDRESS *a;
+ bool last;
+ bool vrfy;
+{
+ char fmtbuf[30];
+
+ if (vrfy && a->q_mailer != NULL &&
+ !bitnset(M_VRFY250, a->q_mailer->m_flags))
+ (void) sm_strlcpy(fmtbuf, "252", sizeof fmtbuf);
+ else
+ (void) sm_strlcpy(fmtbuf, "250", sizeof fmtbuf);
+ fmtbuf[3] = last ? ' ' : '-';
+ (void) sm_strlcpy(&fmtbuf[4], "2.1.5 ", sizeof fmtbuf - 4);
+ if (a->q_fullname == NULL)
+ {
+ if ((a->q_mailer == NULL ||
+ a->q_mailer->m_addrtype == NULL ||
+ sm_strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) &&
+ strchr(a->q_user, '@') == NULL)
+ (void) sm_strlcpy(&fmtbuf[OFFF], "<%s@%s>",
+ sizeof fmtbuf - OFFF);
+ else
+ (void) sm_strlcpy(&fmtbuf[OFFF], "<%s>",
+ sizeof fmtbuf - OFFF);
+ message(fmtbuf, a->q_user, MyHostName);
+ }
+ else
+ {
+ if ((a->q_mailer == NULL ||
+ a->q_mailer->m_addrtype == NULL ||
+ sm_strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) &&
+ strchr(a->q_user, '@') == NULL)
+ (void) sm_strlcpy(&fmtbuf[OFFF], "%s <%s@%s>",
+ sizeof fmtbuf - OFFF);
+ else
+ (void) sm_strlcpy(&fmtbuf[OFFF], "%s <%s>",
+ sizeof fmtbuf - OFFF);
+ message(fmtbuf, a->q_fullname, a->q_user, MyHostName);
+ }
+}
+
+#if SASL
+/*
+** SASLMECHS -- get list of possible AUTH mechanisms
+**
+** Parameters:
+** conn -- SASL connection info.
+** mechlist -- output parameter for list of mechanisms.
+**
+** Returns:
+** number of mechs.
+*/
+
+static int
+saslmechs(conn, mechlist)
+ sasl_conn_t *conn;
+ char **mechlist;
+{
+ int len, num, result;
+
+ /* "user" is currently unused */
+# if SASL >= 20000
+ result = sasl_listmech(conn, NULL,
+ "", " ", "", (const char **) mechlist,
+ (unsigned int *)&len, (unsigned int *)&num);
+# else /* SASL >= 20000 */
+ result = sasl_listmech(conn, "user", /* XXX */
+ "", " ", "", mechlist,
+ (unsigned int *)&len, (unsigned int *)&num);
+# endif /* SASL >= 20000 */
+ if (result != SASL_OK)
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, NOQID,
+ "AUTH error: listmech=%d, num=%d",
+ result, num);
+ num = 0;
+ }
+ if (num > 0)
+ {
+ if (LogLevel > 11)
+ sm_syslog(LOG_INFO, NOQID,
+ "AUTH: available mech=%s, allowed mech=%s",
+ *mechlist, AuthMechanisms);
+ *mechlist = intersect(AuthMechanisms, *mechlist, NULL);
+ }
+ else
+ {
+ *mechlist = NULL; /* be paranoid... */
+ if (result == SASL_OK && LogLevel > 9)
+ sm_syslog(LOG_WARNING, NOQID,
+ "AUTH warning: no mechanisms");
+ }
+ return num;
+}
+
+# if SASL >= 20000
+/*
+** PROXY_POLICY -- define proxy policy for AUTH
+**
+** Parameters:
+** conn -- unused.
+** context -- unused.
+** requested_user -- authorization identity.
+** rlen -- authorization identity length.
+** auth_identity -- authentication identity.
+** alen -- authentication identity length.
+** def_realm -- default user realm.
+** urlen -- user realm length.
+** propctx -- unused.
+**
+** Returns:
+** ok?
+**
+** Side Effects:
+** sets {auth_authen} macro.
+*/
+
+int
+proxy_policy(conn, context, requested_user, rlen, auth_identity, alen,
+ def_realm, urlen, propctx)
+ sasl_conn_t *conn;
+ void *context;
+ const char *requested_user;
+ unsigned rlen;
+ const char *auth_identity;
+ unsigned alen;
+ const char *def_realm;
+ unsigned urlen;
+ struct propctx *propctx;
+{
+ if (auth_identity == NULL)
+ return SASL_FAIL;
+
+ macdefine(&BlankEnvelope.e_macro, A_TEMP,
+ macid("{auth_authen}"), (char *) auth_identity);
+
+ return SASL_OK;
+}
+# else /* SASL >= 20000 */
+
+/*
+** PROXY_POLICY -- define proxy policy for AUTH
+**
+** Parameters:
+** context -- unused.
+** auth_identity -- authentication identity.
+** requested_user -- authorization identity.
+** user -- allowed user (output).
+** errstr -- possible error string (output).
+**
+** Returns:
+** ok?
+*/
+
+int
+proxy_policy(context, auth_identity, requested_user, user, errstr)
+ void *context;
+ const char *auth_identity;
+ const char *requested_user;
+ const char **user;
+ const char **errstr;
+{
+ if (user == NULL || auth_identity == NULL)
+ return SASL_FAIL;
+ *user = newstr(auth_identity);
+ return SASL_OK;
+}
+# endif /* SASL >= 20000 */
+#endif /* SASL */
+
+#if STARTTLS
+/*
+** INITSRVTLS -- initialize server side TLS
+**
+** Parameters:
+** tls_ok -- should tls initialization be done?
+**
+** Returns:
+** succeeded?
+**
+** Side Effects:
+** sets tls_ok_srv which is a static variable in this module.
+** Do NOT remove assignments to it!
+*/
+
+bool
+initsrvtls(tls_ok)
+ bool tls_ok;
+{
+ if (!tls_ok)
+ return false;
+
+ /* do NOT remove assignment */
+ tls_ok_srv = inittls(&srv_ctx, TLS_Srv_Opts, true, SrvCERTfile,
+ Srvkeyfile, CACERTpath, CACERTfile, DHParams);
+ return tls_ok_srv;
+}
+#endif /* STARTTLS */
+/*
+** SRVFEATURES -- get features for SMTP server
+**
+** Parameters:
+** e -- envelope (should be session context).
+** clientname -- name of client.
+** features -- default features for this invocation.
+**
+** Returns:
+** server features.
+*/
+
+/* table with options: it uses just one character, how about strings? */
+static struct
+{
+ char srvf_opt;
+ unsigned int srvf_flag;
+} srv_feat_table[] =
+{
+ { 'A', SRV_OFFER_AUTH },
+ { 'B', SRV_OFFER_VERB },
+ { 'D', SRV_OFFER_DSN },
+ { 'E', SRV_OFFER_ETRN },
+ { 'L', SRV_REQ_AUTH }, /* not documented in 8.12 */
+#if PIPELINING
+# if _FFR_NO_PIPE
+ { 'N', SRV_NO_PIPE },
+# endif /* _FFR_NO_PIPE */
+ { 'P', SRV_OFFER_PIPE },
+#endif /* PIPELINING */
+ { 'R', SRV_VRFY_CLT },
+ { 'S', SRV_OFFER_TLS },
+/* { 'T', SRV_TMP_FAIL }, */
+ { 'V', SRV_VRFY_CLT },
+ { 'X', SRV_OFFER_EXPN },
+/* { 'Y', SRV_OFFER_VRFY }, */
+ { '\0', SRV_NONE }
+};
+
+static unsigned int
+srvfeatures(e, clientname, features)
+ ENVELOPE *e;
+ char *clientname;
+ unsigned int features;
+{
+ int r, i, j;
+ char **pvp, c, opt;
+ char pvpbuf[PSBUFSIZE];
+
+ pvp = NULL;
+ r = rscap("srv_features", clientname, "", e, &pvp, pvpbuf,
+ sizeof(pvpbuf));
+ if (r != EX_OK)
+ return features;
+ if (pvp == NULL || pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET)
+ return features;
+ if (pvp[1] != NULL && sm_strncasecmp(pvp[1], "temp", 4) == 0)
+ return SRV_TMP_FAIL;
+
+ /*
+ ** General rule (see sendmail.h, d_flags):
+ ** lower case: required/offered, upper case: Not required/available
+ **
+ ** Since we can change some features per daemon, we have both
+ ** cases here: turn on/off a feature.
+ */
+
+ for (i = 1; pvp[i] != NULL; i++)
+ {
+ c = pvp[i][0];
+ j = 0;
+ for (;;)
+ {
+ if ((opt = srv_feat_table[j].srvf_opt) == '\0')
+ {
+ if (LogLevel > 9)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "srvfeatures: unknown feature %s",
+ pvp[i]);
+ break;
+ }
+ if (c == opt)
+ {
+ features &= ~(srv_feat_table[j].srvf_flag);
+ break;
+ }
+ if (c == tolower(opt))
+ {
+ features |= srv_feat_table[j].srvf_flag;
+ break;
+ }
+ ++j;
+ }
+ }
+ return features;
+}
+
+/*
+** HELP -- implement the HELP command.
+**
+** Parameters:
+** topic -- the topic we want help for.
+** e -- envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** outputs the help file to message output.
+*/
+#define HELPVSTR "#vers "
+#define HELPVERSION 2
+
+void
+help(topic, e)
+ char *topic;
+ ENVELOPE *e;
+{
+ register SM_FILE_T *hf;
+ register char *p;
+ int len;
+ bool noinfo;
+ bool first = true;
+ long sff = SFF_OPENASROOT|SFF_REGONLY;
+ char buf[MAXLINE];
+ char inp[MAXLINE];
+ static int foundvers = -1;
+ extern char Version[];
+
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ if (!bitnset(DBS_HELPFILEINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_SAFEDIRPATH;
+
+ if (HelpFile == NULL ||
+ (hf = safefopen(HelpFile, O_RDONLY, 0444, sff)) == NULL)
+ {
+ /* no help */
+ errno = 0;
+ message("502 5.3.0 Sendmail %s -- HELP not implemented",
+ Version);
+ return;
+ }
+
+ if (topic == NULL || *topic == '\0')
+ {
+ topic = "smtp";
+ noinfo = false;
+ }
+ else
+ {
+ makelower(topic);
+ noinfo = true;
+ }
+
+ len = strlen(topic);
+
+ while (sm_io_fgets(hf, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ if (buf[0] == '#')
+ {
+ if (foundvers < 0 &&
+ strncmp(buf, HELPVSTR, strlen(HELPVSTR)) == 0)
+ {
+ int h;
+
+ if (sm_io_sscanf(buf + strlen(HELPVSTR), "%d",
+ &h) == 1)
+ foundvers = h;
+ }
+ continue;
+ }
+ if (strncmp(buf, topic, len) == 0)
+ {
+ if (first)
+ {
+ first = false;
+
+ /* print version if no/old vers# in file */
+ if (foundvers < 2 && !noinfo)
+ message("214-2.0.0 This is Sendmail version %s", Version);
+ }
+ p = strpbrk(buf, " \t");
+ if (p == NULL)
+ p = buf + strlen(buf) - 1;
+ else
+ p++;
+ fixcrlf(p, true);
+ if (foundvers >= 2)
+ {
+ translate_dollars(p);
+ expand(p, inp, sizeof inp, e);
+ p = inp;
+ }
+ message("214-2.0.0 %s", p);
+ noinfo = false;
+ }
+ }
+
+ if (noinfo)
+ message("504 5.3.0 HELP topic \"%.10s\" unknown", topic);
+ else
+ message("214 2.0.0 End of HELP info");
+
+ if (foundvers != 0 && foundvers < HELPVERSION)
+ {
+ if (LogLevel > 1)
+ sm_syslog(LOG_WARNING, e->e_id,
+ "%s too old (require version %d)",
+ HelpFile, HELPVERSION);
+
+ /* avoid log next time */
+ foundvers = 0;
+ }
+
+ (void) sm_io_close(hf, SM_TIME_DEFAULT);
+}
diff --git a/contrib/sendmail/src/stab.c b/contrib/sendmail/src/stab.c
new file mode 100644
index 0000000..b2ad12d
--- /dev/null
+++ b/contrib/sendmail/src/stab.c
@@ -0,0 +1,469 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: stab.c,v 8.86 2001/12/29 04:27:56 ca Exp $")
+
+/*
+** STAB -- manage the symbol table
+**
+** Parameters:
+** name -- the name to be looked up or inserted.
+** type -- the type of symbol.
+** op -- what to do:
+** ST_ENTER -- enter the name if not already present.
+** ST_FIND -- find it only.
+**
+** Returns:
+** pointer to a STAB entry for this name.
+** NULL if not found and not entered.
+**
+** Side Effects:
+** can update the symbol table.
+*/
+
+#define STABSIZE 2003
+#define SM_LOWER(c) ((isascii(c) && isupper(c)) ? tolower(c) : (c))
+
+static STAB *SymTab[STABSIZE];
+
+STAB *
+stab(name, type, op)
+ char *name;
+ int type;
+ int op;
+{
+ register STAB *s;
+ register STAB **ps;
+ register int hfunc;
+ register char *p;
+ int len;
+
+ if (tTd(36, 5))
+ sm_dprintf("STAB: %s %d ", name, type);
+
+ /*
+ ** Compute the hashing function
+ */
+
+ hfunc = type;
+ for (p = name; *p != '\0'; p++)
+ hfunc = ((hfunc << 1) ^ (SM_LOWER(*p) & 0377)) % STABSIZE;
+
+ if (tTd(36, 9))
+ sm_dprintf("(hfunc=%d) ", hfunc);
+
+ ps = &SymTab[hfunc];
+ if (type == ST_MACRO || type == ST_RULESET)
+ {
+ while ((s = *ps) != NULL &&
+ (s->s_symtype != type || strcmp(name, s->s_name)))
+ ps = &s->s_next;
+ }
+ else
+ {
+ while ((s = *ps) != NULL &&
+ (s->s_symtype != type || sm_strcasecmp(name, s->s_name)))
+ ps = &s->s_next;
+ }
+
+ /*
+ ** Dispose of the entry.
+ */
+
+ if (s != NULL || op == ST_FIND)
+ {
+ if (tTd(36, 5))
+ {
+ if (s == NULL)
+ sm_dprintf("not found\n");
+ else
+ {
+ long *lp = (long *) s->s_class;
+
+ sm_dprintf("type %d val %lx %lx %lx %lx\n",
+ s->s_symtype, lp[0], lp[1], lp[2], lp[3]);
+ }
+ }
+ return s;
+ }
+
+ /*
+ ** Make a new entry and link it in.
+ */
+
+ if (tTd(36, 5))
+ sm_dprintf("entered\n");
+
+ /* determine size of new entry */
+ switch (type)
+ {
+ case ST_CLASS:
+ len = sizeof s->s_class;
+ break;
+
+ case ST_ADDRESS:
+ len = sizeof s->s_address;
+ break;
+
+ case ST_MAILER:
+ len = sizeof s->s_mailer;
+ break;
+
+ case ST_ALIAS:
+ len = sizeof s->s_alias;
+ break;
+
+ case ST_MAPCLASS:
+ len = sizeof s->s_mapclass;
+ break;
+
+ case ST_MAP:
+ len = sizeof s->s_map;
+ break;
+
+ case ST_HOSTSIG:
+ len = sizeof s->s_hostsig;
+ break;
+
+ case ST_NAMECANON:
+ len = sizeof s->s_namecanon;
+ break;
+
+ case ST_MACRO:
+ len = sizeof s->s_macro;
+ break;
+
+ case ST_RULESET:
+ len = sizeof s->s_ruleset;
+ break;
+
+ case ST_HEADER:
+ len = sizeof s->s_header;
+ break;
+
+ case ST_SERVICE:
+ len = sizeof s->s_service;
+ break;
+
+#if LDAPMAP
+ case ST_LMAP:
+ len = sizeof s->s_lmap;
+ break;
+#endif /* LDAPMAP */
+
+#if MILTER
+ case ST_MILTER:
+ len = sizeof s->s_milter;
+ break;
+#endif /* MILTER */
+
+ case ST_QUEUE:
+ len = sizeof s->s_quegrp;
+ break;
+
+ default:
+ /*
+ ** Each mailer has its own MCI stab entry:
+ **
+ ** s = stab(host, ST_MCI + m->m_mno, ST_ENTER);
+ **
+ ** Therefore, anything ST_MCI or larger is an s_mci.
+ */
+
+ if (type >= ST_MCI)
+ len = sizeof s->s_mci;
+ else
+ {
+ syserr("stab: unknown symbol type %d", type);
+ len = sizeof s->s_value;
+ }
+ break;
+ }
+ len += sizeof *s - sizeof s->s_value;
+
+ if (tTd(36, 15))
+ sm_dprintf("size of stab entry: %d\n", len);
+
+ /* make new entry */
+ s = (STAB *) sm_pmalloc_x(len);
+ memset((char *) s, '\0', len);
+ s->s_name = sm_pstrdup_x(name);
+ s->s_symtype = type;
+
+ /* link it in */
+ *ps = s;
+
+ /* set a default value for rulesets */
+ if (type == ST_RULESET)
+ s->s_ruleset = -1;
+
+ return s;
+}
+/*
+** STABAPPLY -- apply function to all stab entries
+**
+** Parameters:
+** func -- the function to apply. It will be given two
+** parameters (the stab entry and the arg).
+** arg -- an arbitrary argument, passed to func.
+**
+** Returns:
+** none.
+*/
+
+void
+stabapply(func, arg)
+ void (*func)__P((STAB *, int));
+ int arg;
+{
+ register STAB **shead;
+ register STAB *s;
+
+ for (shead = SymTab; shead < &SymTab[STABSIZE]; shead++)
+ {
+ for (s = *shead; s != NULL; s = s->s_next)
+ {
+ if (tTd(36, 90))
+ sm_dprintf("stabapply: trying %d/%s\n",
+ s->s_symtype, s->s_name);
+ func(s, arg);
+ }
+ }
+}
+/*
+** QUEUEUP_MACROS -- queueup the macros in a class
+**
+** Write the macros listed in the specified class into the
+** file referenced by qfp.
+**
+** Parameters:
+** class -- class ID.
+** qfp -- file pointer to the queue file.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+void
+queueup_macros(class, qfp, e)
+ int class;
+ SM_FILE_T *qfp;
+ ENVELOPE *e;
+{
+ register STAB **shead;
+ register STAB *s;
+
+ if (e == NULL)
+ return;
+
+ class = bitidx(class);
+ for (shead = SymTab; shead < &SymTab[STABSIZE]; shead++)
+ {
+ for (s = *shead; s != NULL; s = s->s_next)
+ {
+ int m;
+ char *p;
+
+ if (s->s_symtype == ST_CLASS &&
+ bitnset(bitidx(class), s->s_class) &&
+ (m = macid(s->s_name)) != '\0' &&
+ (p = macvalue(m, e)) != NULL)
+ {
+ (void) sm_io_fprintf(qfp, SM_TIME_DEFAULT,
+ "$%s%s\n",
+ s->s_name,
+ denlstring(p, true,
+ false));
+ }
+ }
+ }
+}
+/*
+** COPY_CLASS -- copy class members from one class to another
+**
+** Parameters:
+** src -- source class.
+** dst -- destination class.
+**
+** Returns:
+** none.
+*/
+
+void
+copy_class(src, dst)
+ int src;
+ int dst;
+{
+ register STAB **shead;
+ register STAB *s;
+
+ src = bitidx(src);
+ dst = bitidx(dst);
+ for (shead = SymTab; shead < &SymTab[STABSIZE]; shead++)
+ {
+ for (s = *shead; s != NULL; s = s->s_next)
+ {
+ if (s->s_symtype == ST_CLASS &&
+ bitnset(src, s->s_class))
+ setbitn(dst, s->s_class);
+ }
+ }
+}
+
+/*
+** RMEXPSTAB -- remove expired entries from SymTab.
+**
+** These entries need to be removed in long-running processes,
+** e.g., persistent queue runners, to avoid consuming memory.
+**
+** XXX It might be useful to restrict the maximum TTL to avoid
+** caching data very long.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** can remove entries from the symbol table.
+*/
+
+#define SM_STAB_FREE(x) \
+ do \
+ { \
+ char *o = (x); \
+ (x) = NULL; \
+ if (o != NULL) \
+ sm_free(o); \
+ } while (0)
+
+void
+rmexpstab()
+{
+ int i;
+ STAB *s, *p, *f;
+ time_t now;
+
+ now = curtime();
+ for (i = 0; i < STABSIZE; i++)
+ {
+ p = NULL;
+ s = SymTab[i];
+ while (s != NULL)
+ {
+ switch (s->s_symtype)
+ {
+ case ST_HOSTSIG:
+ if (s->s_hostsig.hs_exp >= now)
+ goto next; /* not expired */
+ SM_STAB_FREE(s->s_hostsig.hs_sig); /* XXX */
+ break;
+
+ case ST_NAMECANON:
+ if (s->s_namecanon.nc_exp >= now)
+ goto next; /* not expired */
+ SM_STAB_FREE(s->s_namecanon.nc_cname); /* XXX */
+ break;
+
+ default:
+ if (s->s_symtype >= ST_MCI)
+ {
+ /* call mci_uncache? */
+ SM_STAB_FREE(s->s_mci.mci_status);
+ SM_STAB_FREE(s->s_mci.mci_rstatus);
+ SM_STAB_FREE(s->s_mci.mci_heloname);
+#if 0
+ /* not dynamically allocated */
+ SM_STAB_FREE(s->s_mci.mci_host);
+ SM_STAB_FREE(s->s_mci.mci_tolist);
+#endif /* 0 */
+#if SASL
+ /* should always by NULL */
+ SM_STAB_FREE(s->s_mci.mci_sasl_string);
+#endif /* SASL */
+ if (s->s_mci.mci_rpool != NULL)
+ {
+ sm_rpool_free(s->s_mci.mci_rpool);
+ s->s_mci.mci_macro.mac_rpool = NULL;
+ s->s_mci.mci_rpool = NULL;
+ }
+ break;
+ }
+ next:
+ p = s;
+ s = s->s_next;
+ continue;
+ }
+
+ /* remove entry */
+ SM_STAB_FREE(s->s_name); /* XXX */
+ f = s;
+ s = s->s_next;
+ sm_free(f); /* XXX */
+ if (p == NULL)
+ SymTab[i] = s;
+ else
+ p->s_next = s;
+ }
+ }
+}
+
+#if SM_HEAP_CHECK
+/*
+** DUMPSTAB -- dump symbol table.
+**
+** For debugging.
+*/
+
+#define MAXSTTYPES (ST_MCI + 1)
+
+void
+dumpstab()
+{
+ int i, t, total, types[MAXSTTYPES];
+ STAB *s;
+ static int prevt[MAXSTTYPES], prev = 0;
+
+ total = 0;
+ for (i = 0; i < MAXSTTYPES; i++)
+ types[i] = 0;
+ for (i = 0; i < STABSIZE; i++)
+ {
+ s = SymTab[i];
+ while (s != NULL)
+ {
+ ++total;
+ t = s->s_symtype;
+ if (t > MAXSTTYPES - 1)
+ t = MAXSTTYPES - 1;
+ types[t]++;
+ s = s->s_next;
+ }
+ }
+ sm_syslog(LOG_INFO, NOQID, "stab: total=%d (%d)", total, total - prev);
+ prev = total;
+ for (i = 0; i < MAXSTTYPES; i++)
+ {
+ if (types[i] != 0)
+ {
+ sm_syslog(LOG_INFO, NOQID, "stab: type[%2d]=%2d (%d)",
+ i, types[i], types[i] - prevt[i]);
+ }
+ prevt[i] = types[i];
+ }
+}
+#endif /* SM_HEAP_CHECK */
diff --git a/contrib/sendmail/src/stats.c b/contrib/sendmail/src/stats.c
new file mode 100644
index 0000000..bf9d33a
--- /dev/null
+++ b/contrib/sendmail/src/stats.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: stats.c,v 8.55 2002/05/21 22:28:52 gshapiro Exp $")
+
+#include <sendmail/mailstats.h>
+
+static struct statistics Stat;
+
+static bool GotStats = false; /* set when we have stats to merge */
+
+/* See http://physics.nist.gov/cuu/Units/binary.html */
+#define ONE_K 1000 /* one thousand (twenty-four?) */
+#define KBYTES(x) (((x) + (ONE_K - 1)) / ONE_K)
+/*
+** MARKSTATS -- mark statistics
+**
+** Parameters:
+** e -- the envelope.
+** to -- to address.
+** type -- type of stats this represents.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** changes static Stat structure
+*/
+
+void
+markstats(e, to, type)
+ register ENVELOPE *e;
+ register ADDRESS *to;
+ int type;
+{
+ switch (type)
+ {
+#if _FFR_QUARANTINE
+ case STATS_QUARANTINE:
+ if (e->e_from.q_mailer != NULL)
+ Stat.stat_nq[e->e_from.q_mailer->m_mno]++;
+ break;
+#endif /* _FFR_QUARANTINE */
+
+ case STATS_REJECT:
+ if (e->e_from.q_mailer != NULL)
+ {
+ if (bitset(EF_DISCARD, e->e_flags))
+ Stat.stat_nd[e->e_from.q_mailer->m_mno]++;
+ else
+ Stat.stat_nr[e->e_from.q_mailer->m_mno]++;
+ }
+ Stat.stat_cr++;
+ break;
+
+ case STATS_CONNECT:
+ if (to == NULL)
+ Stat.stat_cf++;
+ else
+ Stat.stat_ct++;
+ break;
+
+ case STATS_NORMAL:
+ if (to == NULL)
+ {
+ if (e->e_from.q_mailer != NULL)
+ {
+ Stat.stat_nf[e->e_from.q_mailer->m_mno]++;
+ Stat.stat_bf[e->e_from.q_mailer->m_mno] +=
+ KBYTES(e->e_msgsize);
+ }
+ }
+ else
+ {
+ Stat.stat_nt[to->q_mailer->m_mno]++;
+ Stat.stat_bt[to->q_mailer->m_mno] += KBYTES(e->e_msgsize);
+ }
+ break;
+
+ default:
+ /* Silently ignore bogus call */
+ return;
+ }
+
+
+ GotStats = true;
+}
+/*
+** CLEARSTATS -- clear statistics structure
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** clears the Stat structure.
+*/
+
+void
+clearstats()
+{
+ /* clear the structure to avoid future disappointment */
+ memset(&Stat, '\0', sizeof Stat);
+ GotStats = false;
+}
+/*
+** POSTSTATS -- post statistics in the statistics file
+**
+** Parameters:
+** sfile -- the name of the statistics file.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** merges the Stat structure with the sfile file.
+*/
+
+void
+poststats(sfile)
+ char *sfile;
+{
+ int fd;
+ static bool entered = false;
+ long sff = SFF_REGONLY|SFF_OPENASROOT;
+ struct statistics stats;
+ extern off_t lseek();
+
+ if (sfile == NULL || *sfile == '\0' || !GotStats || entered)
+ return;
+ entered = true;
+
+ (void) time(&Stat.stat_itime);
+ Stat.stat_size = sizeof Stat;
+ Stat.stat_magic = STAT_MAGIC;
+ Stat.stat_version = STAT_VERSION;
+
+ if (!bitnset(DBS_WRITESTATSTOSYMLINK, DontBlameSendmail))
+ sff |= SFF_NOSLINK;
+ if (!bitnset(DBS_WRITESTATSTOHARDLINK, DontBlameSendmail))
+ sff |= SFF_NOHLINK;
+
+ fd = safeopen(sfile, O_RDWR, 0600, sff);
+ if (fd < 0)
+ {
+ if (LogLevel > 12)
+ sm_syslog(LOG_INFO, NOQID, "poststats: %s: %s",
+ sfile, sm_errstring(errno));
+ errno = 0;
+ entered = false;
+ return;
+ }
+ if (read(fd, (char *) &stats, sizeof stats) == sizeof stats &&
+ stats.stat_size == sizeof stats &&
+ stats.stat_magic == Stat.stat_magic &&
+ stats.stat_version == Stat.stat_version)
+ {
+ /* merge current statistics into statfile */
+ register int i;
+
+ for (i = 0; i < MAXMAILERS; i++)
+ {
+ stats.stat_nf[i] += Stat.stat_nf[i];
+ stats.stat_bf[i] += Stat.stat_bf[i];
+ stats.stat_nt[i] += Stat.stat_nt[i];
+ stats.stat_bt[i] += Stat.stat_bt[i];
+ stats.stat_nr[i] += Stat.stat_nr[i];
+ stats.stat_nd[i] += Stat.stat_nd[i];
+#if _FFR_QUARANTINE
+ stats.stat_nq[i] += Stat.stat_nq[i];
+#endif /* _FFR_QUARANTINE */
+ }
+ stats.stat_cr += Stat.stat_cr;
+ stats.stat_ct += Stat.stat_ct;
+ stats.stat_cf += Stat.stat_cf;
+ }
+ else
+ memmove((char *) &stats, (char *) &Stat, sizeof stats);
+
+ /* write out results */
+ (void) lseek(fd, (off_t) 0, 0);
+ (void) write(fd, (char *) &stats, sizeof stats);
+ (void) close(fd);
+
+ /* clear the structure to avoid future disappointment */
+ clearstats();
+ entered = false;
+}
diff --git a/contrib/sendmail/src/statusd_shm.h b/contrib/sendmail/src/statusd_shm.h
new file mode 100644
index 0000000..7d88964
--- /dev/null
+++ b/contrib/sendmail/src/statusd_shm.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 1999-2000 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: statusd_shm.h,v 8.7 2000/09/17 17:30:06 gshapiro Exp $
+ *
+ * Contributed by Exactis.com, Inc.
+ *
+ */
+
+/*
+** The shared memory part of statusd.
+**
+** Attach to STATUSD_SHM_KEY and update the counter appropriate
+** for your type of service.
+**
+*/
+
+#define STATUSD_MAGIC 110946
+#define STATUSD_SHM_KEY (key_t)(13)
+#define STATUSD_LONGS (2)
+
+typedef struct
+{
+ unsigned long magic;
+ unsigned long ul[STATUSD_LONGS];
+} STATUSD_SHM;
+
+/*
+** Offsets into ul[]. The appropriate program
+** increments these as appropriate.
+*/
+
+#define STATUSD_COOKIE (0) /* reregister cookie */
+
+/* sendmail */
+#define STATUSD_SM_NSENDMAIL (1) /* how many running */
+
+extern void shmtick __P((int, int));
+
diff --git a/contrib/sendmail/src/sysexits.c b/contrib/sendmail/src/sysexits.c
new file mode 100644
index 0000000..5cce2b7
--- /dev/null
+++ b/contrib/sendmail/src/sysexits.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: sysexits.c,v 8.33 2001/09/11 04:05:17 gshapiro Exp $")
+
+/*
+** DSNTOEXITSTAT -- convert DSN-style error code to EX_ style.
+**
+** Parameters:
+** dsncode -- the text of the DSN-style code.
+**
+** Returns:
+** The corresponding exit status.
+*/
+
+int
+dsntoexitstat(dsncode)
+ char *dsncode;
+{
+ int code2, code3;
+
+ /* first the easy cases.... */
+ if (*dsncode == '2')
+ return EX_OK;
+ if (*dsncode == '4')
+ return EX_TEMPFAIL;
+
+ /* now decode the other two field parts */
+ if (*++dsncode == '.')
+ dsncode++;
+ code2 = atoi(dsncode);
+ while (*dsncode != '\0' && *dsncode != '.')
+ dsncode++;
+ if (*dsncode != '\0')
+ dsncode++;
+ code3 = atoi(dsncode);
+
+ /* and do a nested switch to work them out */
+ switch (code2)
+ {
+ case 0: /* Other or Undefined status */
+ return EX_UNAVAILABLE;
+
+ case 1: /* Address Status */
+ switch (code3)
+ {
+ case 0: /* Other Address Status */
+ return EX_DATAERR;
+
+ case 1: /* Bad destination mailbox address */
+ case 6: /* Mailbox has moved, No forwarding address */
+ return EX_NOUSER;
+
+ case 2: /* Bad destination system address */
+ case 8: /* Bad senders system address */
+ return EX_NOHOST;
+
+ case 3: /* Bad destination mailbox address syntax */
+ case 7: /* Bad senders mailbox address syntax */
+ return EX_USAGE;
+
+ case 4: /* Destination mailbox address ambiguous */
+ return EX_UNAVAILABLE;
+
+ case 5: /* Destination address valid */
+ return EX_OK;
+ }
+ break;
+
+ case 2: /* Mailbox Status */
+ switch (code3)
+ {
+ case 0: /* Other or Undefined mailbox status */
+ case 1: /* Mailbox disabled, not accepting messages */
+ case 2: /* Mailbox full */
+ case 4: /* Mailing list expansion problem */
+ return EX_UNAVAILABLE;
+
+ case 3: /* Message length exceeds administrative lim */
+ return EX_DATAERR;
+ }
+ break;
+
+ case 3: /* System Status */
+ return EX_OSERR;
+
+ case 4: /* Network and Routing Status */
+ switch (code3)
+ {
+ case 0: /* Other or undefined network or routing stat */
+ return EX_IOERR;
+
+ case 1: /* No answer from host */
+ case 3: /* Routing server failure */
+ case 5: /* Network congestion */
+ return EX_TEMPFAIL;
+
+ case 2: /* Bad connection */
+ return EX_IOERR;
+
+ case 4: /* Unable to route */
+ return EX_PROTOCOL;
+
+ case 6: /* Routing loop detected */
+ return EX_CONFIG;
+
+ case 7: /* Delivery time expired */
+ return EX_UNAVAILABLE;
+ }
+ break;
+
+ case 5: /* Protocol Status */
+ return EX_PROTOCOL;
+
+ case 6: /* Message Content or Media Status */
+ return EX_UNAVAILABLE;
+
+ case 7: /* Security Status */
+ return EX_DATAERR;
+ }
+ return EX_CONFIG;
+}
+/*
+** EXITSTAT -- convert EX_ value to error text.
+**
+** Parameters:
+** excode -- rstatus which might consists of an EX_* value.
+**
+** Returns:
+** The corresponding error text or the original string.
+*/
+
+char *
+exitstat(excode)
+ char *excode;
+{
+ char *c;
+ int i;
+ char *exitmsg;
+
+ if (excode == NULL || *excode == '\0')
+ return excode;
+ i = (int) strtol(excode, &c, 10);
+ if (*c != '\0')
+ return excode;
+ exitmsg = sm_sysexitmsg(i);
+ if (exitmsg != NULL)
+ return exitmsg;
+ return excode;
+}
diff --git a/contrib/sendmail/src/timers.c b/contrib/sendmail/src/timers.c
new file mode 100644
index 0000000..43dd73a
--- /dev/null
+++ b/contrib/sendmail/src/timers.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 1999-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * Contributed by Exactis.com, Inc.
+ *
+ */
+
+#include <sm/gen.h>
+SM_RCSID("@(#)$Id: timers.c,v 8.24 2001/09/11 04:05:17 gshapiro Exp $")
+
+#if _FFR_TIMERS
+# include <sys/types.h>
+# include <sys/time.h>
+# include "sendmail.h"
+# include <sys/resource.h> /* Must be after sendmail.h for NCR MP-RAS */
+
+static TIMER BaseTimer; /* current baseline */
+static int NTimers; /* current pointer into stack */
+static TIMER *TimerStack[MAXTIMERSTACK];
+
+static void
+# ifdef __STDC__
+warntimer(const char *msg, ...)
+# else /* __STDC__ */
+warntimer(msg, va_alist)
+ const char *msg;
+ va_dcl
+# endif /* __STDC__ */
+{
+ char buf[MAXLINE];
+ SM_VA_LOCAL_DECL
+
+# if 0
+ if (!tTd(98, 30))
+ return;
+# endif /* 0 */
+ SM_VA_START(ap, msg);
+ (void) sm_vsnprintf(buf, sizeof buf, msg, ap);
+ SM_VA_END(ap);
+ sm_syslog(LOG_NOTICE, CurEnv->e_id, "%s; e_timers=0x%lx",
+ buf, (unsigned long) &CurEnv->e_timers);
+}
+
+static void
+zerotimer(ptimer)
+ TIMER *ptimer;
+{
+ memset(ptimer, '\0', sizeof *ptimer);
+}
+
+static void
+addtimer(ta, tb)
+ TIMER *ta;
+ TIMER *tb;
+{
+ tb->ti_wall_sec += ta->ti_wall_sec;
+ tb->ti_wall_usec += ta->ti_wall_usec;
+ if (tb->ti_wall_usec > 1000000)
+ {
+ tb->ti_wall_sec++;
+ tb->ti_wall_usec -= 1000000;
+ }
+ tb->ti_cpu_sec += ta->ti_cpu_sec;
+ tb->ti_cpu_usec += ta->ti_cpu_usec;
+ if (tb->ti_cpu_usec > 1000000)
+ {
+ tb->ti_cpu_sec++;
+ tb->ti_cpu_usec -= 1000000;
+ }
+}
+
+static void
+subtimer(ta, tb)
+ TIMER *ta;
+ TIMER *tb;
+{
+ tb->ti_wall_sec -= ta->ti_wall_sec;
+ tb->ti_wall_usec -= ta->ti_wall_usec;
+ if (tb->ti_wall_usec < 0)
+ {
+ tb->ti_wall_sec--;
+ tb->ti_wall_usec += 1000000;
+ }
+ tb->ti_cpu_sec -= ta->ti_cpu_sec;
+ tb->ti_cpu_usec -= ta->ti_cpu_usec;
+ if (tb->ti_cpu_usec < 0)
+ {
+ tb->ti_cpu_sec--;
+ tb->ti_cpu_usec += 1000000;
+ }
+}
+
+static int
+getcurtimer(ptimer)
+ TIMER *ptimer;
+{
+ struct rusage ru;
+ struct timeval now;
+
+ if (getrusage(RUSAGE_SELF, &ru) < 0 || gettimeofday(&now, NULL) < 0)
+ return -1;
+ ptimer->ti_wall_sec = now.tv_sec;
+ ptimer->ti_wall_usec = now.tv_usec;
+ ptimer->ti_cpu_sec = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec;
+ ptimer->ti_cpu_usec = ru.ru_utime.tv_usec + ru.ru_stime.tv_usec;
+ if (ptimer->ti_cpu_usec > 1000000)
+ {
+ ptimer->ti_cpu_sec++;
+ ptimer->ti_cpu_usec -= 1000000;
+ }
+ return 0;
+}
+
+static void
+getinctimer(ptimer)
+ TIMER *ptimer;
+{
+ TIMER cur;
+
+ if (getcurtimer(&cur) < 0)
+ {
+ zerotimer(ptimer);
+ return;
+ }
+ if (BaseTimer.ti_wall_sec == 0)
+ {
+ /* first call */
+ memset(ptimer, '\0', sizeof *ptimer);
+ }
+ else
+ {
+ *ptimer = cur;
+ subtimer(&BaseTimer, ptimer);
+ }
+ BaseTimer = cur;
+}
+
+void
+flushtimers()
+{
+ NTimers = 0;
+ (void) getcurtimer(&BaseTimer);
+}
+
+void
+pushtimer(ptimer)
+ TIMER *ptimer;
+{
+ int i;
+ int save_errno = errno;
+ TIMER incr;
+
+ /* find how much time has changed since last call */
+ getinctimer(&incr);
+
+ /* add that into the old timers */
+ i = NTimers;
+ if (i > MAXTIMERSTACK)
+ i = MAXTIMERSTACK;
+ while (--i >= 0)
+ {
+ addtimer(&incr, TimerStack[i]);
+ if (TimerStack[i] == ptimer)
+ {
+ warntimer("Timer@0x%lx already on stack, index=%d, NTimers=%d",
+ (unsigned long) ptimer, i, NTimers);
+ errno = save_errno;
+ return;
+ }
+ }
+ errno = save_errno;
+
+ /* handle stack overflow */
+ if (NTimers >= MAXTIMERSTACK)
+ return;
+
+ /* now add the timer to the stack */
+ TimerStack[NTimers++] = ptimer;
+}
+
+void
+poptimer(ptimer)
+ TIMER *ptimer;
+{
+ int i;
+ int save_errno = errno;
+ TIMER incr;
+
+ /* find how much time has changed since last call */
+ getinctimer(&incr);
+
+ /* add that into the old timers */
+ i = NTimers;
+ if (i > MAXTIMERSTACK)
+ i = MAXTIMERSTACK;
+ while (--i >= 0)
+ addtimer(&incr, TimerStack[i]);
+
+ /* pop back to this timer */
+ for (i = 0; i < NTimers; i++)
+ {
+ if (TimerStack[i] == ptimer)
+ break;
+ }
+
+ if (i != NTimers - 1)
+ warntimer("poptimer: odd pop (timer=0x%lx, index=%d, NTimers=%d)",
+ (unsigned long) ptimer, i, NTimers);
+ NTimers = i;
+
+ /* clean up and return */
+ errno = save_errno;
+}
+
+char *
+strtimer(ptimer)
+ TIMER *ptimer;
+{
+ static char buf[40];
+
+ (void) sm_snprintf(buf, sizeof buf, "%ld.%06ldr/%ld.%06ldc",
+ ptimer->ti_wall_sec, ptimer->ti_wall_usec,
+ ptimer->ti_cpu_sec, ptimer->ti_cpu_usec);
+ return buf;
+}
+#endif /* _FFR_TIMERS */
diff --git a/contrib/sendmail/src/timers.h b/contrib/sendmail/src/timers.h
new file mode 100644
index 0000000..d7faee1
--- /dev/null
+++ b/contrib/sendmail/src/timers.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 1999-2000 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ * $Id: timers.h,v 8.6 2001/04/03 01:53:18 gshapiro Exp $
+ *
+ * Contributed by Exactis.com, Inc.
+ *
+ */
+
+#ifndef TIMERS_H
+#define TIMERS_H 1
+
+#define MAXTIMERSTACK 20 /* maximum timer depth */
+
+#define TIMER struct _timer
+
+TIMER
+{
+ long ti_wall_sec; /* wall clock seconds */
+ long ti_wall_usec; /* ... microseconds */
+ long ti_cpu_sec; /* cpu time seconds */
+ long ti_cpu_usec; /* ... microseconds */
+};
+
+extern void pushtimer __P((TIMER *));
+extern void poptimer __P((TIMER *));
+extern char *strtimer __P((TIMER *));
+#endif /* ! TIMERS_H */
diff --git a/contrib/sendmail/src/tls.c b/contrib/sendmail/src/tls.c
new file mode 100644
index 0000000..e2b1b14
--- /dev/null
+++ b/contrib/sendmail/src/tls.c
@@ -0,0 +1,1486 @@
+/*
+ * Copyright (c) 2000-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: tls.c,v 8.79 2002/03/21 22:24:13 gshapiro Exp $")
+
+#if STARTTLS
+# include <openssl/err.h>
+# include <openssl/bio.h>
+# include <openssl/pem.h>
+# ifndef HASURANDOMDEV
+# include <openssl/rand.h>
+# endif /* ! HASURANDOMDEV */
+# if SM_CONF_SHM
+# include <sm/shm.h>
+# endif /* SM_CONF_SHM */
+# if !TLS_NO_RSA
+static RSA *rsa_tmp = NULL; /* temporary RSA key */
+static RSA *tmp_rsa_key __P((SSL *, int, int));
+# endif /* !TLS_NO_RSA */
+# if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L
+static int tls_verify_cb __P((X509_STORE_CTX *));
+# else /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+static int tls_verify_cb __P((X509_STORE_CTX *, void *));
+# endif /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+
+# if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L
+# define CONST097
+# else /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+# define CONST097 const
+# endif /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+static void apps_ssl_info_cb __P((CONST097 SSL *, int , int));
+
+# if !NO_DH
+static DH *get_dh512 __P((void));
+
+static unsigned char dh512_p[] =
+{
+ 0xDA,0x58,0x3C,0x16,0xD9,0x85,0x22,0x89,0xD0,0xE4,0xAF,0x75,
+ 0x6F,0x4C,0xCA,0x92,0xDD,0x4B,0xE5,0x33,0xB8,0x04,0xFB,0x0F,
+ 0xED,0x94,0xEF,0x9C,0x8A,0x44,0x03,0xED,0x57,0x46,0x50,0xD3,
+ 0x69,0x99,0xDB,0x29,0xD7,0x76,0x27,0x6B,0xA2,0xD3,0xD4,0x12,
+ 0xE2,0x18,0xF4,0xDD,0x1E,0x08,0x4C,0xF6,0xD8,0x00,0x3E,0x7C,
+ 0x47,0x74,0xE8,0x33
+};
+static unsigned char dh512_g[] =
+{
+ 0x02
+};
+
+static DH *
+get_dh512()
+{
+ DH *dh = NULL;
+
+ if ((dh = DH_new()) == NULL)
+ return NULL;
+ dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL);
+ dh->g = BN_bin2bn(dh512_g, sizeof(dh512_g), NULL);
+ if ((dh->p == NULL) || (dh->g == NULL))
+ return NULL;
+ return dh;
+}
+# endif /* !NO_DH */
+
+
+/*
+** TLS_RAND_INIT -- initialize STARTTLS random generator
+**
+** Parameters:
+** randfile -- name of file with random data
+** logl -- loglevel
+**
+** Returns:
+** success/failure
+**
+** Side Effects:
+** initializes PRNG for tls library.
+*/
+
+# define MIN_RAND_BYTES 128 /* 1024 bits */
+
+# define RF_OK 0 /* randfile OK */
+# define RF_MISS 1 /* randfile == NULL || *randfile == '\0' */
+# define RF_UNKNOWN 2 /* unknown prefix for randfile */
+
+# define RI_NONE 0 /* no init yet */
+# define RI_SUCCESS 1 /* init was successful */
+# define RI_FAIL 2 /* init failed */
+
+static bool tls_rand_init __P((char *, int));
+
+static bool
+tls_rand_init(randfile, logl)
+ char *randfile;
+ int logl;
+{
+# ifndef HASURANDOMDEV
+ /* not required if /dev/urandom exists, OpenSSL does it internally */
+
+ bool ok;
+ int randdef;
+ static int done = RI_NONE;
+
+ /*
+ ** initialize PRNG
+ */
+
+ /* did we try this before? if yes: return old value */
+ if (done != RI_NONE)
+ return done == RI_SUCCESS;
+
+ /* set default values */
+ ok = false;
+ done = RI_FAIL;
+ randdef = (randfile == NULL || *randfile == '\0') ? RF_MISS : RF_OK;
+# if EGD
+ if (randdef == RF_OK && sm_strncasecmp(randfile, "egd:", 4) == 0)
+ {
+ randfile += 4;
+ if (RAND_egd(randfile) < 0)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: RAND_egd(%s) failed: random number generator not seeded",
+ randfile);
+ }
+ else
+ ok = true;
+ }
+ else
+# endif /* EGD */
+ if (randdef == RF_OK && sm_strncasecmp(randfile, "file:", 5) == 0)
+ {
+ int fd;
+ long sff;
+ struct stat st;
+
+ randfile += 5;
+ sff = SFF_SAFEDIRPATH | SFF_NOWLINK
+ | SFF_NOGWFILES | SFF_NOWWFILES
+ | SFF_NOGRFILES | SFF_NOWRFILES
+ | SFF_MUSTOWN | SFF_ROOTOK | SFF_OPENASROOT;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+ if ((fd = safeopen(randfile, O_RDONLY, 0, sff)) >= 0)
+ {
+ if (fstat(fd, &st) < 0)
+ {
+ if (LogLevel > logl)
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS: can't fstat(%s)",
+ randfile);
+ }
+ else
+ {
+ bool use, problem;
+
+ use = true;
+ problem = false;
+
+ /* max. age of file: 10 minutes */
+ if (st.st_mtime + 600 < curtime())
+ {
+ use = bitnset(DBS_INSUFFICIENTENTROPY,
+ DontBlameSendmail);
+ problem = true;
+ if (LogLevel > logl)
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS: RandFile %s too old: %s",
+ randfile,
+ use ? "unsafe" :
+ "unusable");
+ }
+ if (use && st.st_size < MIN_RAND_BYTES)
+ {
+ use = bitnset(DBS_INSUFFICIENTENTROPY,
+ DontBlameSendmail);
+ problem = true;
+ if (LogLevel > logl)
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS: size(%s) < %d: %s",
+ randfile,
+ MIN_RAND_BYTES,
+ use ? "unsafe" :
+ "unusable");
+ }
+ if (use)
+ ok = RAND_load_file(randfile, -1) >=
+ MIN_RAND_BYTES;
+ if (use && !ok)
+ {
+ if (LogLevel > logl)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: RAND_load_file(%s) failed: random number generator not seeded",
+ randfile);
+ }
+ if (problem)
+ ok = false;
+ }
+ if (ok || bitnset(DBS_INSUFFICIENTENTROPY,
+ DontBlameSendmail))
+ {
+ /* add this even if fstat() failed */
+ RAND_seed((void *) &st, sizeof st);
+ }
+ (void) close(fd);
+ }
+ else
+ {
+ if (LogLevel > logl)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: Warning: safeopen(%s) failed",
+ randfile);
+ }
+ }
+ else if (randdef == RF_OK)
+ {
+ if (LogLevel > logl)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: Error: no proper random file definition %s",
+ randfile);
+ randdef = RF_UNKNOWN;
+ }
+ if (randdef == RF_MISS)
+ {
+ if (LogLevel > logl)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: Error: missing random file definition");
+ }
+ if (!ok && bitnset(DBS_INSUFFICIENTENTROPY, DontBlameSendmail))
+ {
+ int i;
+ long r;
+ unsigned char buf[MIN_RAND_BYTES];
+
+ /* assert((MIN_RAND_BYTES % sizeof(long)) == 0); */
+ for (i = 0; i <= sizeof(buf) - sizeof(long); i += sizeof(long))
+ {
+ r = get_random();
+ (void) memcpy(buf + i, (void *) &r, sizeof(long));
+ }
+ RAND_seed(buf, sizeof buf);
+ if (LogLevel > logl)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: Warning: random number generator not properly seeded");
+ ok = true;
+ }
+ done = ok ? RI_SUCCESS : RI_FAIL;
+ return ok;
+# else /* ! HASURANDOMDEV */
+ return true;
+# endif /* ! HASURANDOMDEV */
+}
+/*
+** INIT_TLS_LIBRARY -- Calls functions which setup TLS library for global use.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** succeeded?
+*/
+
+bool
+init_tls_library()
+{
+ /* basic TLS initialization, ignore result for now */
+ SSL_library_init();
+ SSL_load_error_strings();
+# if 0
+ /* this is currently a macro for SSL_library_init */
+ SSLeay_add_ssl_algorithms();
+# endif /* 0 */
+
+ return tls_rand_init(RandFile, 7);
+}
+/*
+** TLS_SET_VERIFY -- request client certificate?
+**
+** Parameters:
+** ctx -- TLS context
+** ssl -- TLS structure
+** vrfy -- require certificate?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets verification state for TLS
+**
+# if TLS_VRFY_PER_CTX
+** Notice:
+** This is per TLS context, not per TLS structure;
+** the former is global, the latter per connection.
+** It would be nice to do this per connection, but this
+** doesn't work in the current TLS libraries :-(
+# endif * TLS_VRFY_PER_CTX *
+*/
+
+void
+tls_set_verify(ctx, ssl, vrfy)
+ SSL_CTX *ctx;
+ SSL *ssl;
+ bool vrfy;
+{
+# if !TLS_VRFY_PER_CTX
+ SSL_set_verify(ssl, vrfy ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
+# else /* !TLS_VRFY_PER_CTX */
+ SSL_CTX_set_verify(ctx, vrfy ? SSL_VERIFY_PEER : SSL_VERIFY_NONE,
+ NULL);
+# endif /* !TLS_VRFY_PER_CTX */
+}
+
+/*
+** status in initialization
+** these flags keep track of the status of the initialization
+** i.e., whether a file exists (_EX) and whether it can be used (_OK)
+** [due to permissions]
+*/
+
+# define TLS_S_NONE 0x00000000 /* none yet */
+# define TLS_S_CERT_EX 0x00000001 /* CERT file exists */
+# define TLS_S_CERT_OK 0x00000002 /* CERT file is ok */
+# define TLS_S_KEY_EX 0x00000004 /* KEY file exists */
+# define TLS_S_KEY_OK 0x00000008 /* KEY file is ok */
+# define TLS_S_CERTP_EX 0x00000010 /* CA CERT PATH exists */
+# define TLS_S_CERTP_OK 0x00000020 /* CA CERT PATH is ok */
+# define TLS_S_CERTF_EX 0x00000040 /* CA CERT FILE exists */
+# define TLS_S_CERTF_OK 0x00000080 /* CA CERT FILE is ok */
+
+# if _FFR_TLS_1
+# define TLS_S_CERT2_EX 0x00001000 /* 2nd CERT file exists */
+# define TLS_S_CERT2_OK 0x00002000 /* 2nd CERT file is ok */
+# define TLS_S_KEY2_EX 0x00004000 /* 2nd KEY file exists */
+# define TLS_S_KEY2_OK 0x00008000 /* 2nd KEY file is ok */
+# endif /* _FFR_TLS_1 */
+
+# define TLS_S_DH_OK 0x00200000 /* DH cert is ok */
+# define TLS_S_DHPAR_EX 0x00400000 /* DH param file exists */
+# define TLS_S_DHPAR_OK 0x00800000 /* DH param file is ok to use */
+
+/*
+** TLS_OK_F -- can var be an absolute filename?
+**
+** Parameters:
+** var -- filename
+** fn -- what is the filename used for?
+** srv -- server side?
+**
+** Returns:
+** ok?
+*/
+
+static bool
+tls_ok_f(var, fn, srv)
+ char *var;
+ char *fn;
+ bool srv;
+{
+ /* must be absolute pathname */
+ if (var != NULL && *var == '/')
+ return true;
+ if (LogLevel > 12)
+ sm_syslog(LOG_WARNING, NOQID, "STARTTLS: %s%s missing",
+ srv ? "Server" : "Client", fn);
+ return false;
+}
+/*
+** TLS_SAFE_F -- is a file safe to use?
+**
+** Parameters:
+** var -- filename
+** sff -- flags for safefile()
+** srv -- server side?
+**
+** Returns:
+** ok?
+*/
+
+static bool
+tls_safe_f(var, sff, srv)
+ char *var;
+ long sff;
+ bool srv;
+{
+ int ret;
+
+ if ((ret = safefile(var, RunAsUid, RunAsGid, RunAsUserName, sff,
+ S_IRUSR, NULL)) == 0)
+ return true;
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID, "STARTTLS=%s: file %s unsafe: %s",
+ srv ? "server" : "client", var, sm_errstring(ret));
+ return false;
+}
+
+/*
+** TLS_OK_F -- macro to simplify calls to tls_ok_f
+**
+** Parameters:
+** var -- filename
+** fn -- what is the filename used for?
+** req -- is the file required?
+** st -- status bit to set if ok
+** srv -- server side?
+**
+** Side Effects:
+** uses r, ok; may change ok and status.
+**
+*/
+
+# define TLS_OK_F(var, fn, req, st, srv) if (ok) \
+ { \
+ r = tls_ok_f(var, fn, srv); \
+ if (r) \
+ status |= st; \
+ else if (req) \
+ ok = false; \
+ }
+
+/*
+** TLS_UNR -- macro to return whether a file should be unreadable
+**
+** Parameters:
+** bit -- flag to test
+** req -- flags
+**
+** Returns:
+** 0/SFF_NORFILES
+*/
+# define TLS_UNR(bit, req) (bitset(bit, req) ? SFF_NORFILES : 0)
+# define TLS_OUNR(bit, req) (bitset(bit, req) ? SFF_NOWRFILES : 0)
+# define TLS_KEYSFF(req) \
+ (bitnset(DBS_GROUPREADABLEKEYFILE, DontBlameSendmail) ? \
+ TLS_OUNR(TLS_I_KEY_OUNR, req) : \
+ TLS_UNR(TLS_I_KEY_UNR, req))
+
+/*
+** TLS_SAFE_F -- macro to simplify calls to tls_safe_f
+**
+** Parameters:
+** var -- filename
+** sff -- flags for safefile()
+** req -- is the file required?
+** ex -- does the file exist?
+** st -- status bit to set if ok
+** srv -- server side?
+**
+** Side Effects:
+** uses r, ok, ex; may change ok and status.
+**
+*/
+
+# define TLS_SAFE_F(var, sff, req, ex, st, srv) if (ex && ok) \
+ { \
+ r = tls_safe_f(var, sff, srv); \
+ if (r) \
+ status |= st; \
+ else if (req) \
+ ok = false; \
+ }
+
+/*
+** INITTLS -- initialize TLS
+**
+** Parameters:
+** ctx -- pointer to context
+** req -- requirements for initialization (see sendmail.h)
+** srv -- server side?
+** certfile -- filename of certificate
+** keyfile -- filename of private key
+** cacertpath -- path to CAs
+** cacertfile -- file with CA(s)
+** dhparam -- parameters for DH
+**
+** Returns:
+** succeeded?
+*/
+
+bool
+inittls(ctx, req, srv, certfile, keyfile, cacertpath, cacertfile, dhparam)
+ SSL_CTX **ctx;
+ unsigned long req;
+ bool srv;
+ char *certfile, *keyfile, *cacertpath, *cacertfile, *dhparam;
+{
+# if !NO_DH
+ static DH *dh = NULL;
+# endif /* !NO_DH */
+ int r;
+ bool ok;
+ long sff, status;
+ char *who;
+# if _FFR_TLS_1
+ char *cf2, *kf2;
+# endif /* _FFR_TLS_1 */
+# if SM_CONF_SHM
+ extern int ShmId;
+# endif /* SM_CONF_SHM */
+
+ status = TLS_S_NONE;
+ who = srv ? "server" : "client";
+ if (ctx == NULL)
+ syserr("STARTTLS=%s, inittls: ctx == NULL", who);
+
+ /* already initialized? (we could re-init...) */
+ if (*ctx != NULL)
+ return true;
+ ok = true;
+
+# if _FFR_TLS_1
+ /*
+ ** look for a second filename: it must be separated by a ','
+ ** no blanks allowed (they won't be skipped).
+ ** we change a global variable here! this change will be undone
+ ** before return from the function but only if it returns true.
+ ** this isn't a problem since in a failure case this function
+ ** won't be called again with the same (overwritten) values.
+ ** otherwise each return must be replaced with a goto endinittls.
+ */
+
+ cf2 = NULL;
+ kf2 = NULL;
+ if (certfile != NULL && (cf2 = strchr(certfile, ',')) != NULL)
+ {
+ *cf2++ = '\0';
+ if (keyfile != NULL && (kf2 = strchr(keyfile, ',')) != NULL)
+ *kf2++ = '\0';
+ }
+# endif /* _FFR_TLS_1 */
+
+ /*
+ ** Check whether files/paths are defined
+ */
+
+ TLS_OK_F(certfile, "CertFile", bitset(TLS_I_CERT_EX, req),
+ TLS_S_CERT_EX, srv);
+ TLS_OK_F(keyfile, "KeyFile", bitset(TLS_I_KEY_EX, req),
+ TLS_S_KEY_EX, srv);
+ TLS_OK_F(cacertpath, "CACERTPath", bitset(TLS_I_CERTP_EX, req),
+ TLS_S_CERTP_EX, srv);
+ TLS_OK_F(cacertfile, "CACERTFile", bitset(TLS_I_CERTF_EX, req),
+ TLS_S_CERTF_EX, srv);
+
+# if _FFR_TLS_1
+ /*
+ ** if the second file is specified it must exist
+ ** XXX: it is possible here to define only one of those files
+ */
+
+ if (cf2 != NULL)
+ {
+ TLS_OK_F(cf2, "CertFile", bitset(TLS_I_CERT_EX, req),
+ TLS_S_CERT2_EX, srv);
+ }
+ if (kf2 != NULL)
+ {
+ TLS_OK_F(kf2, "KeyFile", bitset(TLS_I_KEY_EX, req),
+ TLS_S_KEY2_EX, srv);
+ }
+# endif /* _FFR_TLS_1 */
+
+ /*
+ ** valid values for dhparam are (only the first char is checked)
+ ** none no parameters: don't use DH
+ ** 512 generate 512 bit parameters (fixed)
+ ** 1024 generate 1024 bit parameters
+ ** /file/name read parameters from /file/name
+ ** default is: 1024 for server, 512 for client (OK? XXX)
+ */
+
+ if (bitset(TLS_I_TRY_DH, req))
+ {
+ if (dhparam != NULL)
+ {
+ char c = *dhparam;
+
+ if (c == '1')
+ req |= TLS_I_DH1024;
+ else if (c == '5')
+ req |= TLS_I_DH512;
+ else if (c != 'n' && c != 'N' && c != '/')
+ {
+ if (LogLevel > 12)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: illegal value '%s' for DHParam",
+ who, dhparam);
+ dhparam = NULL;
+ }
+ }
+ if (dhparam == NULL)
+ dhparam = srv ? "1" : "5";
+ else if (*dhparam == '/')
+ {
+ TLS_OK_F(dhparam, "DHParameters",
+ bitset(TLS_I_DHPAR_EX, req),
+ TLS_S_DHPAR_EX, srv);
+ }
+ }
+ if (!ok)
+ return ok;
+
+ /* certfile etc. must be "safe". */
+ sff = SFF_REGONLY | SFF_SAFEDIRPATH | SFF_NOWLINK
+ | SFF_NOGWFILES | SFF_NOWWFILES
+ | SFF_MUSTOWN | SFF_ROOTOK | SFF_OPENASROOT;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+
+ TLS_SAFE_F(certfile, sff | TLS_UNR(TLS_I_CERT_UNR, req),
+ bitset(TLS_I_CERT_EX, req),
+ bitset(TLS_S_CERT_EX, status), TLS_S_CERT_OK, srv);
+ TLS_SAFE_F(keyfile, sff | TLS_KEYSFF(req),
+ bitset(TLS_I_KEY_EX, req),
+ bitset(TLS_S_KEY_EX, status), TLS_S_KEY_OK, srv);
+ TLS_SAFE_F(cacertfile, sff | TLS_UNR(TLS_I_CERTF_UNR, req),
+ bitset(TLS_I_CERTF_EX, req),
+ bitset(TLS_S_CERTF_EX, status), TLS_S_CERTF_OK, srv);
+ TLS_SAFE_F(dhparam, sff | TLS_UNR(TLS_I_DHPAR_UNR, req),
+ bitset(TLS_I_DHPAR_EX, req),
+ bitset(TLS_S_DHPAR_EX, status), TLS_S_DHPAR_OK, srv);
+ if (!ok)
+ return ok;
+# if _FFR_TLS_1
+ if (cf2 != NULL)
+ {
+ TLS_SAFE_F(cf2, sff | TLS_UNR(TLS_I_CERT_UNR, req),
+ bitset(TLS_I_CERT_EX, req),
+ bitset(TLS_S_CERT2_EX, status), TLS_S_CERT2_OK, srv);
+ }
+ if (kf2 != NULL)
+ {
+ TLS_SAFE_F(kf2, sff | TLS_KEYSFF(req),
+ bitset(TLS_I_KEY_EX, req),
+ bitset(TLS_S_KEY2_EX, status), TLS_S_KEY2_OK, srv);
+ }
+# endif /* _FFR_TLS_1 */
+
+ /* create a method and a new context */
+ if ((*ctx = SSL_CTX_new(srv ? SSLv23_server_method() :
+ SSLv23_client_method())) == NULL)
+ {
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_new(SSLv23_%s_method()) failed",
+ who, who);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ return false;
+ }
+
+# if TLS_NO_RSA
+ /* turn off backward compatibility, required for no-rsa */
+ SSL_CTX_set_options(*ctx, SSL_OP_NO_SSLv2);
+# endif /* TLS_NO_RSA */
+
+
+# if !TLS_NO_RSA
+ /*
+ ** Create a temporary RSA key
+ ** XXX Maybe we shouldn't create this always (even though it
+ ** is only at startup).
+ ** It is a time-consuming operation and it is not always necessary.
+ ** maybe we should do it only on demand...
+ */
+
+ if (bitset(TLS_I_RSA_TMP, req)
+# if SM_CONF_SHM
+ && ShmId != SM_SHM_NO_ID &&
+ (rsa_tmp = RSA_generate_key(RSA_KEYLENGTH, RSA_F4, NULL,
+ NULL)) == NULL
+# else /* SM_CONF_SHM */
+ && 0 /* no shared memory: no need to generate key now */
+# endif /* SM_CONF_SHM */
+ )
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: RSA_generate_key failed",
+ who);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ return false;
+ }
+# endif /* !TLS_NO_RSA */
+
+ /*
+ ** load private key
+ ** XXX change this for DSA-only version
+ */
+
+ if (bitset(TLS_S_KEY_OK, status) &&
+ SSL_CTX_use_PrivateKey_file(*ctx, keyfile,
+ SSL_FILETYPE_PEM) <= 0)
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_use_PrivateKey_file(%s) failed",
+ who, keyfile);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ if (bitset(TLS_I_USE_KEY, req))
+ return false;
+ }
+
+ /* get the certificate file */
+ if (bitset(TLS_S_CERT_OK, status) &&
+ SSL_CTX_use_certificate_file(*ctx, certfile,
+ SSL_FILETYPE_PEM) <= 0)
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_use_certificate_file(%s) failed",
+ who, certfile);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ if (bitset(TLS_I_USE_CERT, req))
+ return false;
+ }
+
+ /* check the private key */
+ if (bitset(TLS_S_KEY_OK, status) &&
+ (r = SSL_CTX_check_private_key(*ctx)) <= 0)
+ {
+ /* Private key does not match the certificate public key */
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_check_private_key failed(%s): %d",
+ who, keyfile, r);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ if (bitset(TLS_I_USE_KEY, req))
+ return false;
+ }
+
+# if _FFR_TLS_1
+ /* XXX this code is pretty much duplicated from above! */
+
+ /* load private key */
+ if (bitset(TLS_S_KEY2_OK, status) &&
+ SSL_CTX_use_PrivateKey_file(*ctx, kf2, SSL_FILETYPE_PEM) <= 0)
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_use_PrivateKey_file(%s) failed",
+ who, kf2);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ }
+
+ /* get the certificate file */
+ if (bitset(TLS_S_CERT2_OK, status) &&
+ SSL_CTX_use_certificate_file(*ctx, cf2, SSL_FILETYPE_PEM) <= 0)
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_use_certificate_file(%s) failed",
+ who, cf2);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ }
+
+ /* also check the private key */
+ if (bitset(TLS_S_KEY2_OK, status) &&
+ (r = SSL_CTX_check_private_key(*ctx)) <= 0)
+ {
+ /* Private key does not match the certificate public key */
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_check_private_key 2 failed: %d",
+ who, r);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ }
+# endif /* _FFR_TLS_1 */
+
+ /* SSL_CTX_set_quiet_shutdown(*ctx, 1); violation of standard? */
+ SSL_CTX_set_options(*ctx, SSL_OP_ALL); /* XXX bug compatibility? */
+
+# if !NO_DH
+ /* Diffie-Hellman initialization */
+ if (bitset(TLS_I_TRY_DH, req))
+ {
+ if (bitset(TLS_S_DHPAR_OK, status))
+ {
+ BIO *bio;
+
+ if ((bio = BIO_new_file(dhparam, "r")) != NULL)
+ {
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ if (dh == NULL && LogLevel > 7)
+ {
+ unsigned long err;
+
+ err = ERR_get_error();
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: cannot read DH parameters(%s): %s",
+ who, dhparam,
+ ERR_error_string(err, NULL));
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ }
+ else
+ {
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: BIO_new_file(%s) failed",
+ who, dhparam);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ }
+ }
+ if (dh == NULL && bitset(TLS_I_DH1024, req))
+ {
+ DSA *dsa;
+
+ /* this takes a while! (7-130s on a 450MHz AMD K6-2) */
+ dsa = DSA_generate_parameters(1024, NULL, 0, NULL,
+ NULL, 0, NULL);
+ dh = DSA_dup_DH(dsa);
+ DSA_free(dsa);
+ }
+ else
+ if (dh == NULL && bitset(TLS_I_DH512, req))
+ dh = get_dh512();
+
+ if (dh == NULL)
+ {
+ if (LogLevel > 9)
+ {
+ unsigned long err;
+
+ err = ERR_get_error();
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: cannot read or set DH parameters(%s): %s",
+ who, dhparam,
+ ERR_error_string(err, NULL));
+ }
+ if (bitset(TLS_I_REQ_DH, req))
+ return false;
+ }
+ else
+ {
+ SSL_CTX_set_tmp_dh(*ctx, dh);
+
+ /* important to avoid small subgroup attacks */
+ SSL_CTX_set_options(*ctx, SSL_OP_SINGLE_DH_USE);
+ if (LogLevel > 13)
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS=%s, Diffie-Hellman init, key=%d bit (%c)",
+ who, 8 * DH_size(dh), *dhparam);
+ DH_free(dh);
+ }
+ }
+# endif /* !NO_DH */
+
+
+ /* XXX do we need this cache here? */
+ if (bitset(TLS_I_CACHE, req))
+ SSL_CTX_sess_set_cache_size(*ctx, 128);
+ /* timeout? SSL_CTX_set_timeout(*ctx, TimeOut...); */
+
+ /* load certificate locations and default CA paths */
+ if (bitset(TLS_S_CERTP_EX, status) && bitset(TLS_S_CERTF_EX, status))
+ {
+ if ((r = SSL_CTX_load_verify_locations(*ctx, cacertfile,
+ cacertpath)) == 1)
+ {
+# if !TLS_NO_RSA
+ if (bitset(TLS_I_RSA_TMP, req))
+ SSL_CTX_set_tmp_rsa_callback(*ctx, tmp_rsa_key);
+# endif /* !TLS_NO_RSA */
+
+ /*
+ ** We have to install our own verify callback:
+ ** SSL_VERIFY_PEER requests a client cert but even
+ ** though *FAIL_IF* isn't set, the connection
+ ** will be aborted if the client presents a cert
+ ** that is not "liked" (can't be verified?) by
+ ** the TLS library :-(
+ */
+
+ /*
+ ** XXX currently we could call tls_set_verify()
+ ** but we hope that that function will later on
+ ** only set the mode per connection.
+ */
+ SSL_CTX_set_verify(*ctx,
+ bitset(TLS_I_NO_VRFY, req) ? SSL_VERIFY_NONE
+ : SSL_VERIFY_PEER,
+ NULL);
+
+ /* install verify callback */
+ SSL_CTX_set_cert_verify_callback(*ctx, tls_verify_cb,
+ NULL);
+ SSL_CTX_set_client_CA_list(*ctx,
+ SSL_load_client_CA_file(cacertfile));
+ }
+ else
+ {
+ /*
+ ** can't load CA data; do we care?
+ ** the data is necessary to authenticate the client,
+ ** which in turn would be necessary
+ ** if we want to allow relaying based on it.
+ */
+ if (LogLevel > 5)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: load verify locs %s, %s failed: %d",
+ who, cacertpath, cacertfile, r);
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ if (bitset(TLS_I_VRFY_LOC, req))
+ return false;
+ }
+ }
+
+ /* XXX: make this dependent on an option? */
+ if (tTd(96, 9))
+ SSL_CTX_set_info_callback(*ctx, apps_ssl_info_cb);
+
+# if _FFR_TLS_1
+ /* install our own cipher list */
+ if (CipherList != NULL && *CipherList != '\0')
+ {
+ if (SSL_CTX_set_cipher_list(*ctx, CipherList) <= 0)
+ {
+ if (LogLevel > 7)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, error: SSL_CTX_set_cipher_list(%s) failed, list ignored",
+ who, CipherList);
+
+ if (LogLevel > 9)
+ tlslogerr(who);
+ }
+ /* failure if setting to this list is required? */
+ }
+ }
+# endif /* _FFR_TLS_1 */
+ if (LogLevel > 12)
+ sm_syslog(LOG_INFO, NOQID, "STARTTLS=%s, init=%d", who, ok);
+
+# if _FFR_TLS_1
+# if 0
+ /*
+ ** this label is required if we want to have a "clean" exit
+ ** see the comments above at the initialization of cf2
+ */
+
+ endinittls:
+# endif /* 0 */
+
+ /* undo damage to global variables */
+ if (cf2 != NULL)
+ *--cf2 = ',';
+ if (kf2 != NULL)
+ *--kf2 = ',';
+# endif /* _FFR_TLS_1 */
+
+ return ok;
+}
+/*
+** TLS_GET_INFO -- get information about TLS connection
+**
+** Parameters:
+** ssl -- TLS connection structure
+** srv -- server or client
+** host -- hostname of other side
+** mac -- macro storage
+** certreq -- did we ask for a cert?
+**
+** Returns:
+** result of authentication.
+**
+** Side Effects:
+** sets macros: {cipher}, {tls_version}, {verify},
+** {cipher_bits}, {alg_bits}, {cert}, {cert_subject},
+** {cert_issuer}, {cn_subject}, {cn_issuer}
+*/
+
+int
+tls_get_info(ssl, srv, host, mac, certreq)
+ SSL *ssl;
+ bool srv;
+ char *host;
+ MACROS_T *mac;
+ bool certreq;
+{
+ SSL_CIPHER *c;
+ int b, r;
+ char *s, *who;
+ char bitstr[16];
+ X509 *cert;
+
+ c = SSL_get_current_cipher(ssl);
+
+ /* cast is just workaround for compiler warning */
+ macdefine(mac, A_TEMP, macid("{cipher}"),
+ (char *) SSL_CIPHER_get_name(c));
+ b = SSL_CIPHER_get_bits(c, &r);
+ (void) sm_snprintf(bitstr, sizeof bitstr, "%d", b);
+ macdefine(mac, A_TEMP, macid("{cipher_bits}"), bitstr);
+ (void) sm_snprintf(bitstr, sizeof bitstr, "%d", r);
+ macdefine(mac, A_TEMP, macid("{alg_bits}"), bitstr);
+ s = SSL_CIPHER_get_version(c);
+ if (s == NULL)
+ s = "UNKNOWN";
+ macdefine(mac, A_TEMP, macid("{tls_version}"), s);
+
+ who = srv ? "server" : "client";
+ cert = SSL_get_peer_certificate(ssl);
+ if (LogLevel > 14)
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS=%s, get_verify: %ld get_peer: 0x%lx",
+ who, SSL_get_verify_result(ssl),
+ (unsigned long) cert);
+ if (cert != NULL)
+ {
+ unsigned int n;
+ unsigned char md[EVP_MAX_MD_SIZE];
+ char buf[MAXNAME];
+
+ X509_NAME_oneline(X509_get_subject_name(cert),
+ buf, sizeof buf);
+ macdefine(mac, A_TEMP, macid("{cert_subject}"),
+ xtextify(buf, "<>\")"));
+ X509_NAME_oneline(X509_get_issuer_name(cert),
+ buf, sizeof buf);
+ macdefine(mac, A_TEMP, macid("{cert_issuer}"),
+ xtextify(buf, "<>\")"));
+ X509_NAME_get_text_by_NID(X509_get_subject_name(cert),
+ NID_commonName, buf, sizeof buf);
+ macdefine(mac, A_TEMP, macid("{cn_subject}"),
+ xtextify(buf, "<>\")"));
+ X509_NAME_get_text_by_NID(X509_get_issuer_name(cert),
+ NID_commonName, buf, sizeof buf);
+ macdefine(mac, A_TEMP, macid("{cn_issuer}"),
+ xtextify(buf, "<>\")"));
+ if (X509_digest(cert, EVP_md5(), md, &n))
+ {
+ char md5h[EVP_MAX_MD_SIZE * 3];
+ static const char hexcodes[] = "0123456789ABCDEF";
+
+ SM_ASSERT((n * 3) + 2 < sizeof(md5h));
+ for (r = 0; r < (int) n; r++)
+ {
+ md5h[r * 3] = hexcodes[(md[r] & 0xf0) >> 4];
+ md5h[(r * 3) + 1] = hexcodes[(md[r] & 0x0f)];
+ md5h[(r * 3) + 2] = ':';
+ }
+ md5h[(n * 3) - 1] = '\0';
+ macdefine(mac, A_TEMP, macid("{cert_md5}"), md5h);
+ }
+ else
+ macdefine(mac, A_TEMP, macid("{cert_md5}"), "");
+ }
+ else
+ {
+ macdefine(mac, A_PERM, macid("{cert_subject}"), "");
+ macdefine(mac, A_PERM, macid("{cert_issuer}"), "");
+ macdefine(mac, A_PERM, macid("{cn_subject}"), "");
+ macdefine(mac, A_PERM, macid("{cn_issuer}"), "");
+ macdefine(mac, A_TEMP, macid("{cert_md5}"), "");
+ }
+ switch (SSL_get_verify_result(ssl))
+ {
+ case X509_V_OK:
+ if (cert != NULL)
+ {
+ s = "OK";
+ r = TLS_AUTH_OK;
+ }
+ else
+ {
+ s = certreq ? "NO" : "NOT",
+ r = TLS_AUTH_NO;
+ }
+ break;
+ default:
+ s = "FAIL";
+ r = TLS_AUTH_FAIL;
+ break;
+ }
+ macdefine(mac, A_PERM, macid("{verify}"), s);
+ if (cert != NULL)
+ X509_free(cert);
+
+ /* do some logging */
+ if (LogLevel > 8)
+ {
+ char *vers, *s1, *s2, *cbits, *algbits;
+
+ vers = macget(mac, macid("{tls_version}"));
+ cbits = macget(mac, macid("{cipher_bits}"));
+ algbits = macget(mac, macid("{alg_bits}"));
+ s1 = macget(mac, macid("{verify}"));
+ s2 = macget(mac, macid("{cipher}"));
+
+ /* XXX: maybe cut off ident info? */
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS=%s, relay=%.100s, version=%.16s, verify=%.16s, cipher=%.64s, bits=%.6s/%.6s",
+ who,
+ host == NULL ? "local" : host,
+ vers, s1, s2, /* sm_snprintf() can deal with NULL */
+ algbits == NULL ? "0" : algbits,
+ cbits == NULL ? "0" : cbits);
+ if (LogLevel > 11)
+ {
+ /*
+ ** Maybe run xuntextify on the strings?
+ ** That is easier to read but makes it maybe a bit
+ ** more complicated to figure out the right values
+ ** for the access map...
+ */
+
+ s1 = macget(mac, macid("{cert_subject}"));
+ s2 = macget(mac, macid("{cert_issuer}"));
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS=%s, cert-subject=%.128s, cert-issuer=%.128s",
+ who, s1, s2);
+ }
+ }
+ return r;
+}
+/*
+** ENDTLS -- shutdown secure connection
+**
+** Parameters:
+** ssl -- SSL connection information.
+** side -- server/client (for logging).
+**
+** Returns:
+** success? (EX_* code)
+*/
+
+int
+endtls(ssl, side)
+ SSL *ssl;
+ char *side;
+{
+ int ret = EX_OK;
+
+ if (ssl != NULL)
+ {
+ int r;
+
+ if ((r = SSL_shutdown(ssl)) < 0)
+ {
+ if (LogLevel > 11)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, SSL_shutdown failed: %d",
+ side, r);
+ tlslogerr(side);
+ }
+ ret = EX_SOFTWARE;
+ }
+# if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER > 0x0090602fL
+
+ /*
+ ** Bug in OpenSSL (at least up to 0.9.6b):
+ ** From: Lutz.Jaenicke@aet.TU-Cottbus.DE
+ ** Message-ID: <20010723152244.A13122@serv01.aet.tu-cottbus.de>
+ ** To: openssl-users@openssl.org
+ ** Subject: Re: SSL_shutdown() woes (fwd)
+ **
+ ** The side sending the shutdown alert first will
+ ** not care about the answer of the peer but will
+ ** immediately return with a return value of "0"
+ ** (ssl/s3_lib.c:ssl3_shutdown()). SSL_get_error will evaluate
+ ** the value of "0" and as the shutdown alert of the peer was
+ ** not received (actually, the program did not even wait for
+ ** the answer), an SSL_ERROR_SYSCALL is flagged, because this
+ ** is the default rule in case everything else does not apply.
+ **
+ ** For your server the problem is different, because it
+ ** receives the shutdown first (setting SSL_RECEIVED_SHUTDOWN),
+ ** then sends its response (SSL_SENT_SHUTDOWN), so for the
+ ** server the shutdown was successfull.
+ **
+ ** As is by know, you would have to call SSL_shutdown() once
+ ** and ignore an SSL_ERROR_SYSCALL returned. Then call
+ ** SSL_shutdown() again to actually get the server's response.
+ **
+ ** In the last discussion, Bodo Moeller concluded that a
+ ** rewrite of the shutdown code would be necessary, but
+ ** probably with another API, as the change would not be
+ ** compatible to the way it is now. Things do not become
+ ** easier as other programs do not follow the shutdown
+ ** guidelines anyway, so that a lot error conditions and
+ ** compitibility issues would have to be caught.
+ **
+ ** For now the recommondation is to ignore the error message.
+ */
+
+ else if (r == 0)
+ {
+ if (LogLevel > 15)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s, SSL_shutdown not done",
+ side);
+ tlslogerr(side);
+ }
+ ret = EX_SOFTWARE;
+ }
+# endif /* !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER > 0x0090602fL */
+ SSL_free(ssl);
+ ssl = NULL;
+ }
+ return ret;
+}
+
+# if !TLS_NO_RSA
+/*
+** TMP_RSA_KEY -- return temporary RSA key
+**
+** Parameters:
+** s -- TLS connection structure
+** export --
+** keylength --
+**
+** Returns:
+** temporary RSA key.
+*/
+
+# ifndef MAX_RSA_TMP_CNT
+# define MAX_RSA_TMP_CNT 1000 /* XXX better value? */
+# endif /* ! MAX_RSA_TMP_CNT */
+
+/* ARGUSED0 */
+static RSA *
+tmp_rsa_key(s, export, keylength)
+ SSL *s;
+ int export;
+ int keylength;
+{
+# if SM_CONF_SHM
+ extern int ShmId;
+ extern int *PRSATmpCnt;
+
+ if (ShmId != SM_SHM_NO_ID && rsa_tmp != NULL &&
+ ++(*PRSATmpCnt) < MAX_RSA_TMP_CNT)
+ return rsa_tmp;
+# endif /* SM_CONF_SHM */
+
+ if (rsa_tmp != NULL)
+ RSA_free(rsa_tmp);
+ rsa_tmp = RSA_generate_key(RSA_KEYLENGTH, RSA_F4, NULL, NULL);
+ if (rsa_tmp == NULL)
+ {
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=server, tmp_rsa_key: RSA_generate_key failed!");
+ }
+ else
+ {
+# if SM_CONF_SHM
+# if 0
+ /*
+ ** XXX we can't (yet) share the new key...
+ ** The RSA structure contains pointers hence it can't be
+ ** easily kept in shared memory. It must be transformed
+ ** into a continous memory region first, then stored,
+ ** and later read out again (each time re-transformed).
+ */
+
+ if (ShmId != SM_SHM_NO_ID)
+ *PRSATmpCnt = 0;
+# endif /* 0 */
+# endif /* SM_CONF_SHM */
+ if (LogLevel > 9)
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS=server, tmp_rsa_key: new temp RSA key");
+ }
+ return rsa_tmp;
+}
+# endif /* !TLS_NO_RSA */
+/*
+** APPS_SSL_INFO_CB -- info callback for TLS connections
+**
+** Parameters:
+** s -- TLS connection structure
+** where -- state in handshake
+** ret -- return code of last operation
+**
+** Returns:
+** none.
+*/
+
+static void
+apps_ssl_info_cb(s, where, ret)
+ CONST097 SSL *s;
+ int where;
+ int ret;
+{
+ int w;
+ char *str;
+ BIO *bio_err = NULL;
+
+ if (LogLevel > 14)
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS: info_callback where=0x%x, ret=%d",
+ where, ret);
+
+ w = where & ~SSL_ST_MASK;
+ if (bio_err == NULL)
+ bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
+
+ if (bitset(SSL_ST_CONNECT, w))
+ str = "SSL_connect";
+ else if (bitset(SSL_ST_ACCEPT, w))
+ str = "SSL_accept";
+ else
+ str = "undefined";
+
+ if (bitset(SSL_CB_LOOP, where))
+ {
+ if (LogLevel > 12)
+ sm_syslog(LOG_NOTICE, NOQID,
+ "STARTTLS: %s:%s",
+ str, SSL_state_string_long(s));
+ }
+ else if (bitset(SSL_CB_ALERT, where))
+ {
+ str = bitset(SSL_CB_READ, where) ? "read" : "write";
+ if (LogLevel > 12)
+ sm_syslog(LOG_NOTICE, NOQID,
+ "STARTTLS: SSL3 alert %s:%s:%s",
+ str, SSL_alert_type_string_long(ret),
+ SSL_alert_desc_string_long(ret));
+ }
+ else if (bitset(SSL_CB_EXIT, where))
+ {
+ if (ret == 0)
+ {
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: %s:failed in %s",
+ str, SSL_state_string_long(s));
+ }
+ else if (ret < 0)
+ {
+ if (LogLevel > 7)
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS: %s:error in %s",
+ str, SSL_state_string_long(s));
+ }
+ }
+}
+/*
+** TLS_VERIFY_LOG -- log verify error for TLS certificates
+**
+** Parameters:
+** ok -- verify ok?
+** ctx -- x509 context
+**
+** Returns:
+** 0 -- fatal error
+** 1 -- ok
+*/
+
+static int
+tls_verify_log(ok, ctx)
+ int ok;
+ X509_STORE_CTX *ctx;
+{
+ SSL *ssl;
+ X509 *cert;
+ int reason, depth;
+ char buf[512];
+
+ cert = X509_STORE_CTX_get_current_cert(ctx);
+ reason = X509_STORE_CTX_get_error(ctx);
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+ ssl = (SSL *) X509_STORE_CTX_get_ex_data(ctx,
+ SSL_get_ex_data_X509_STORE_CTX_idx());
+
+ if (ssl == NULL)
+ {
+ /* internal error */
+ sm_syslog(LOG_ERR, NOQID,
+ "STARTTLS: internal error: tls_verify_cb: ssl == NULL");
+ return 0;
+ }
+
+ X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof buf);
+ sm_syslog(LOG_INFO, NOQID,
+ "STARTTLS: cert verify: depth=%d %s, state=%d, reason=%s",
+ depth, buf, ok, X509_verify_cert_error_string(reason));
+ return 1;
+}
+/*
+** TLS_VERIFY_CB -- verify callback for TLS certificates
+**
+** Parameters:
+** ctx -- x509 context
+**
+** Returns:
+** accept connection?
+** currently: always yes.
+*/
+
+static int
+# if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L
+tls_verify_cb(ctx)
+ X509_STORE_CTX *ctx;
+# else /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+tls_verify_cb(ctx, unused)
+ X509_STORE_CTX *ctx;
+ void *unused;
+# endif /* !defined() || OPENSSL_VERSION_NUMBER < 0x00907000L */
+{
+ int ok;
+
+ ok = X509_verify_cert(ctx);
+ if (ok == 0)
+ {
+ if (LogLevel > 13)
+ return tls_verify_log(ok, ctx);
+ return 1; /* override it */
+ }
+ return ok;
+}
+/*
+** TLSLOGERR -- log the errors from the TLS error stack
+**
+** Parameters:
+** who -- server/client (for logging).
+**
+** Returns:
+** none.
+*/
+
+void
+tlslogerr(who)
+ char *who;
+{
+ unsigned long l;
+ int line, flags;
+ unsigned long es;
+ char *file, *data;
+ char buf[256];
+# define CP (const char **)
+
+ es = CRYPTO_thread_id();
+ while ((l = ERR_get_error_line_data(CP &file, &line, CP &data, &flags))
+ != 0)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "STARTTLS=%s: %lu:%s:%s:%d:%s", who, es,
+ ERR_error_string(l, buf),
+ file, line,
+ bitset(ERR_TXT_STRING, flags) ? data : "");
+ }
+}
+#endif /* STARTTLS */
diff --git a/contrib/sendmail/src/trace.c b/contrib/sendmail/src/trace.c
new file mode 100644
index 0000000..701a949
--- /dev/null
+++ b/contrib/sendmail/src/trace.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+#include <sm/debug.h>
+#include <sm/string.h>
+
+SM_RCSID("@(#)$Id: trace.c,v 8.37 2001/09/11 04:05:17 gshapiro Exp $")
+
+static char *tTnewflag __P((char *));
+static char *tToldflag __P((char *));
+
+/*
+** TtSETUP -- set up for trace package.
+**
+** Parameters:
+** vect -- pointer to trace vector.
+** size -- number of flags in trace vector.
+** defflags -- flags to set if no value given.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** environment is set up.
+*/
+
+static unsigned char *tTvect;
+static unsigned int tTsize;
+static char *DefFlags;
+
+void
+tTsetup(vect, size, defflags)
+ unsigned char *vect;
+ unsigned int size;
+ char *defflags;
+{
+ tTvect = vect;
+ tTsize = size;
+ DefFlags = defflags;
+}
+
+/*
+** tToldflag -- process an old style trace flag
+**
+** Parameters:
+** s -- points to a [\0, \t] terminated string,
+** and the initial character is a digit.
+**
+** Returns:
+** pointer to terminating [\0, \t] character
+**
+** Side Effects:
+** modifies tTvect
+*/
+
+static char *
+tToldflag(s)
+ register char *s;
+{
+ unsigned int first, last;
+ register unsigned int i;
+
+ /* find first flag to set */
+ i = 0;
+ while (isascii(*s) && isdigit(*s) && i < tTsize)
+ i = i * 10 + (*s++ - '0');
+
+ /*
+ ** skip over rest of a too large number
+ ** Maybe we should complain if out-of-bounds values are used.
+ */
+
+ while (isascii(*s) && isdigit(*s) && i >= tTsize)
+ s++;
+ first = i;
+
+ /* find last flag to set */
+ if (*s == '-')
+ {
+ i = 0;
+ while (isascii(*++s) && isdigit(*s) && i < tTsize)
+ i = i * 10 + (*s - '0');
+
+ /* skip over rest of a too large number */
+ while (isascii(*s) && isdigit(*s) && i >= tTsize)
+ s++;
+ }
+ last = i;
+
+ /* find the level to set it to */
+ i = 1;
+ if (*s == '.')
+ {
+ i = 0;
+ while (isascii(*++s) && isdigit(*s))
+ i = i * 10 + (*s - '0');
+ }
+
+ /* clean up args */
+ if (first >= tTsize)
+ first = tTsize - 1;
+ if (last >= tTsize)
+ last = tTsize - 1;
+
+ /* set the flags */
+ while (first <= last)
+ tTvect[first++] = (unsigned char) i;
+
+ /* skip trailing junk */
+ while (*s != '\0' && *s != ',' && *s != ' ' && *s != '\t')
+ ++s;
+
+ return s;
+}
+
+/*
+** tTnewflag -- process a new style trace flag
+**
+** Parameters:
+** s -- Points to a non-empty [\0, \t] terminated string,
+** of which the initial character is not a digit.
+**
+** Returns:
+** pointer to terminating [\0, \t] character
+**
+** Side Effects:
+** adds trace flag to libsm debug database
+*/
+
+static char *
+tTnewflag(s)
+ register char *s;
+{
+ char *pat, *endpat;
+ int level;
+
+ pat = s;
+ while (*s != '\0' && *s != ',' && *s != ' ' && *s != '\t' && *s != '.')
+ ++s;
+ endpat = s;
+ if (*s == '.')
+ {
+ ++s;
+ level = 0;
+ while (isascii(*s) && isdigit(*s))
+ {
+ level = level * 10 + (*s - '0');
+ ++s;
+ }
+ if (level < 0)
+ level = 0;
+ }
+ else
+ {
+ level = 1;
+ }
+
+ sm_debug_addsetting_x(sm_strndup_x(pat, endpat - pat), level);
+
+ /* skip trailing junk */
+ while (*s != '\0' && *s != ',' && *s != ' ' && *s != '\t')
+ ++s;
+
+ return s;
+}
+
+/*
+** TtFLAG -- process an external trace flag list.
+**
+** Parameters:
+** s -- the trace flag.
+**
+** The syntax of a trace flag list is as follows:
+**
+** <flags> ::= <flag> | <flags> "," <flag>
+** <flag> ::= <categories> | <categories> "." <level>
+** <categories> ::= <int> | <int> "-" <int> | <pattern>
+** <pattern> ::= <an sh glob pattern matching a C identifier>
+**
+** White space is ignored before and after a flag.
+** However, note that we skip over anything we don't
+** understand, rather than report an error.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets/clears old-style trace flags.
+** registers new-style trace flags with the libsm debug package.
+*/
+
+void
+tTflag(s)
+ register char *s;
+{
+ if (*s == '\0')
+ s = DefFlags;
+
+ for (;;)
+ {
+ if (*s == '\0')
+ return;
+ if (*s == ',' || *s == ' ' || *s == '\t')
+ {
+ ++s;
+ continue;
+ }
+ if (isascii(*s) && isdigit(*s))
+ s = tToldflag(s);
+ else
+ s = tTnewflag(s);
+ }
+}
diff --git a/contrib/sendmail/src/udb.c b/contrib/sendmail/src/udb.c
new file mode 100644
index 0000000..1091cf2
--- /dev/null
+++ b/contrib/sendmail/src/udb.c
@@ -0,0 +1,1318 @@
+/*
+ * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+#if USERDB
+SM_RCSID("@(#)$Id: udb.c,v 8.153 2001/09/11 04:05:17 gshapiro Exp $ (with USERDB)")
+#else /* USERDB */
+SM_RCSID("@(#)$Id: udb.c,v 8.153 2001/09/11 04:05:17 gshapiro Exp $ (without USERDB)")
+#endif /* USERDB */
+
+#if USERDB
+
+# if NEWDB
+# include <db.h>
+# ifndef DB_VERSION_MAJOR
+# define DB_VERSION_MAJOR 1
+# endif /* ! DB_VERSION_MAJOR */
+# else /* NEWDB */
+# define DBT struct _data_base_thang_
+DBT
+{
+ void *data; /* pointer to data */
+ size_t size; /* length of data */
+};
+# endif /* NEWDB */
+
+/*
+** UDB.C -- interface between sendmail and Berkeley User Data Base.
+**
+** This depends on the 4.4BSD db package.
+*/
+
+
+struct udbent
+{
+ char *udb_spec; /* string version of spec */
+ int udb_type; /* type of entry */
+ pid_t udb_pid; /* PID of process which opened db */
+ char *udb_default; /* default host for outgoing mail */
+ union
+ {
+# if NETINET || NETINET6
+ /* type UE_REMOTE -- do remote call for lookup */
+ struct
+ {
+ SOCKADDR _udb_addr; /* address */
+ int _udb_timeout; /* timeout */
+ } udb_remote;
+# define udb_addr udb_u.udb_remote._udb_addr
+# define udb_timeout udb_u.udb_remote._udb_timeout
+# endif /* NETINET || NETINET6 */
+
+ /* type UE_FORWARD -- forward message to remote */
+ struct
+ {
+ char *_udb_fwdhost; /* name of forward host */
+ } udb_forward;
+# define udb_fwdhost udb_u.udb_forward._udb_fwdhost
+
+# if NEWDB
+ /* type UE_FETCH -- lookup in local database */
+ struct
+ {
+ char *_udb_dbname; /* pathname of database */
+ DB *_udb_dbp; /* open database ptr */
+ } udb_lookup;
+# define udb_dbname udb_u.udb_lookup._udb_dbname
+# define udb_dbp udb_u.udb_lookup._udb_dbp
+# endif /* NEWDB */
+ } udb_u;
+};
+
+# define UDB_EOLIST 0 /* end of list */
+# define UDB_SKIP 1 /* skip this entry */
+# define UDB_REMOTE 2 /* look up in remote database */
+# define UDB_DBFETCH 3 /* look up in local database */
+# define UDB_FORWARD 4 /* forward to remote host */
+# define UDB_HESIOD 5 /* look up via hesiod */
+
+# define MAXUDBENT 10 /* maximum number of UDB entries */
+
+
+struct udb_option
+{
+ char *udbo_name;
+ char *udbo_val;
+};
+
+# if HESIOD
+static int hes_udb_get __P((DBT *, DBT *));
+# endif /* HESIOD */
+static char *udbmatch __P((char *, char *, SM_RPOOL_T *));
+static int _udbx_init __P((ENVELOPE *));
+static int _udb_parsespec __P((char *, struct udb_option [], int));
+
+/*
+** UDBEXPAND -- look up user in database and expand
+**
+** Parameters:
+** a -- address to expand.
+** sendq -- pointer to head of sendq to put the expansions in.
+** aliaslevel -- the current alias nesting depth.
+** e -- the current envelope.
+**
+** Returns:
+** EX_TEMPFAIL -- if something "odd" happened -- probably due
+** to accessing a file on an NFS server that is down.
+** EX_OK -- otherwise.
+**
+** Side Effects:
+** Modifies sendq.
+*/
+
+static struct udbent UdbEnts[MAXUDBENT + 1];
+static bool UdbInitialized = false;
+
+int
+udbexpand(a, sendq, aliaslevel, e)
+ register ADDRESS *a;
+ ADDRESS **sendq;
+ int aliaslevel;
+ register ENVELOPE *e;
+{
+ int i;
+ DBT key;
+ DBT info;
+ bool breakout;
+ register struct udbent *up;
+ int keylen;
+ int naddrs;
+ char *user;
+ char keybuf[MAXKEY];
+
+ memset(&key, '\0', sizeof key);
+ memset(&info, '\0', sizeof info);
+
+ if (tTd(28, 1))
+ sm_dprintf("udbexpand(%s)\n", a->q_paddr);
+
+ /* make certain we are supposed to send to this address */
+ if (!QS_IS_SENDABLE(a->q_state))
+ return EX_OK;
+ e->e_to = a->q_paddr;
+
+ /* on first call, locate the database */
+ if (!UdbInitialized)
+ {
+ if (_udbx_init(e) == EX_TEMPFAIL)
+ return EX_TEMPFAIL;
+ }
+
+ /* short circuit the process if no chance of a match */
+ if (UdbSpec == NULL || UdbSpec[0] == '\0')
+ return EX_OK;
+
+ /* extract user to do userdb matching on */
+ user = a->q_user;
+
+ /* short circuit name begins with '\\' since it can't possibly match */
+ /* (might want to treat this as unquoted instead) */
+ if (user[0] == '\\')
+ return EX_OK;
+
+ /* if name begins with a colon, it indicates our metadata */
+ if (user[0] == ':')
+ return EX_OK;
+
+ keylen = sm_strlcpyn(keybuf, sizeof keybuf, 2, user, ":maildrop");
+
+ /* if name is too long, assume it won't match */
+ if (keylen > sizeof keybuf)
+ return EX_OK;
+
+ /* build actual database key */
+
+ breakout = false;
+ for (up = UdbEnts; !breakout; up++)
+ {
+ int usersize;
+ int userleft;
+ char userbuf[MEMCHUNKSIZE];
+# if defined(HESIOD) && defined(HES_GETMAILHOST)
+ char pobuf[MAXNAME];
+# endif /* defined(HESIOD) && defined(HES_GETMAILHOST) */
+# if defined(NEWDB) && DB_VERSION_MAJOR > 1
+ DBC *dbc = NULL;
+# endif /* defined(NEWDB) && DB_VERSION_MAJOR > 1 */
+
+ user = userbuf;
+ userbuf[0] = '\0';
+ usersize = sizeof userbuf;
+ userleft = sizeof userbuf - 1;
+
+ /*
+ ** Select action based on entry type.
+ **
+ ** On dropping out of this switch, "class" should
+ ** explain the type of the data, and "user" should
+ ** contain the user information.
+ */
+
+ switch (up->udb_type)
+ {
+# if NEWDB
+ case UDB_DBFETCH:
+ key.data = keybuf;
+ key.size = keylen;
+ if (tTd(28, 80))
+ sm_dprintf("udbexpand: trying %s (%d) via db\n",
+ keybuf, keylen);
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->seq)(up->udb_dbp, &key, &info, R_CURSOR);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = 0;
+ if (dbc == NULL &&
+# if DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6
+ (errno = (*up->udb_dbp->cursor)(up->udb_dbp,
+ NULL, &dbc, 0)) != 0)
+# else /* DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 */
+ (errno = (*up->udb_dbp->cursor)(up->udb_dbp,
+ NULL, &dbc)) != 0)
+# endif /* DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 */
+ i = -1;
+ if (i != 0 || dbc == NULL ||
+ (errno = dbc->c_get(dbc, &key,
+ &info, DB_SET)) != 0)
+ i = 1;
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (i > 0 || info.size <= 0)
+ {
+ if (tTd(28, 2))
+ sm_dprintf("udbexpand: no match on %s (%d)\n",
+ keybuf, keylen);
+# if DB_VERSION_MAJOR > 1
+ if (dbc != NULL)
+ {
+ (void) dbc->c_close(dbc);
+ dbc = NULL;
+ }
+# endif /* DB_VERSION_MAJOR > 1 */
+ break;
+ }
+ if (tTd(28, 80))
+ sm_dprintf("udbexpand: match %.*s: %.*s\n",
+ (int) key.size, (char *) key.data,
+ (int) info.size, (char *) info.data);
+
+ a->q_flags &= ~QSELFREF;
+ while (i == 0 && key.size == keylen &&
+ memcmp(key.data, keybuf, keylen) == 0)
+ {
+ char *p;
+
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ a->q_state = QS_VERIFIED;
+# if DB_VERSION_MAJOR > 1
+ if (dbc != NULL)
+ {
+ (void) dbc->c_close(dbc);
+ dbc = NULL;
+ }
+# endif /* DB_VERSION_MAJOR > 1 */
+ return EX_OK;
+ }
+
+ breakout = true;
+ if (info.size >= userleft - 1)
+ {
+ char *nuser;
+ int size = MEMCHUNKSIZE;
+
+ if (info.size > MEMCHUNKSIZE)
+ size = info.size;
+ nuser = sm_malloc_x(usersize + size);
+
+ memmove(nuser, user, usersize);
+ if (user != userbuf)
+ sm_free(user); /* XXX */
+ user = nuser;
+ usersize += size;
+ userleft += size;
+ }
+ p = &user[strlen(user)];
+ if (p != user)
+ {
+ *p++ = ',';
+ userleft--;
+ }
+ memmove(p, info.data, info.size);
+ p[info.size] = '\0';
+ userleft -= info.size;
+
+ /* get the next record */
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->seq)(up->udb_dbp, &key, &info, R_NEXT);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = 0;
+ if ((errno = dbc->c_get(dbc, &key,
+ &info, DB_NEXT)) != 0)
+ i = 1;
+# endif /* DB_VERSION_MAJOR < 2 */
+ }
+
+# if DB_VERSION_MAJOR > 1
+ if (dbc != NULL)
+ {
+ (void) dbc->c_close(dbc);
+ dbc = NULL;
+ }
+# endif /* DB_VERSION_MAJOR > 1 */
+
+ /* if nothing ever matched, try next database */
+ if (!breakout)
+ break;
+
+ message("expanded to %s", user);
+ if (LogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "expand %.100s => %s",
+ e->e_to,
+ shortenstring(user, MAXSHORTSTR));
+ naddrs = sendtolist(user, a, sendq, aliaslevel + 1, e);
+ if (naddrs > 0 && !bitset(QSELFREF, a->q_flags))
+ {
+ if (tTd(28, 5))
+ {
+ sm_dprintf("udbexpand: QS_EXPANDED ");
+ printaddr(a, false);
+ }
+ a->q_state = QS_EXPANDED;
+ }
+ if (i < 0)
+ {
+ syserr("udbexpand: db-get %.*s stat %d",
+ (int) key.size, (char *) key.data, i);
+ return EX_TEMPFAIL;
+ }
+
+ /*
+ ** If this address has a -request address, reflect
+ ** it into the envelope.
+ */
+
+ memset(&key, '\0', sizeof key);
+ memset(&info, '\0', sizeof info);
+ (void) sm_strlcpyn(keybuf, sizeof keybuf, 2, a->q_user,
+ ":mailsender");
+ keylen = strlen(keybuf);
+ key.data = keybuf;
+ key.size = keylen;
+
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = errno = (*up->udb_dbp->get)(up->udb_dbp, NULL,
+ &key, &info, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (i != 0 || info.size <= 0)
+ break;
+ a->q_owner = sm_rpool_malloc_x(e->e_rpool,
+ info.size + 1);
+ memmove(a->q_owner, info.data, info.size);
+ a->q_owner[info.size] = '\0';
+
+ /* announce delivery; NORECEIPT bit set later */
+ if (e->e_xfp != NULL)
+ {
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "Message delivered to mailing list %s\n",
+ a->q_paddr);
+ }
+ e->e_flags |= EF_SENDRECEIPT;
+ a->q_flags |= QDELIVERED|QEXPANDED;
+ break;
+# endif /* NEWDB */
+
+# if HESIOD
+ case UDB_HESIOD:
+ key.data = keybuf;
+ key.size = keylen;
+ if (tTd(28, 80))
+ sm_dprintf("udbexpand: trying %s (%d) via hesiod\n",
+ keybuf, keylen);
+ /* look up the key via hesiod */
+ i = hes_udb_get(&key, &info);
+ if (i < 0)
+ {
+ syserr("udbexpand: hesiod-get %.*s stat %d",
+ (int) key.size, (char *) key.data, i);
+ return EX_TEMPFAIL;
+ }
+ else if (i > 0 || info.size <= 0)
+ {
+# if HES_GETMAILHOST
+ struct hes_postoffice *hp;
+# endif /* HES_GETMAILHOST */
+
+ if (tTd(28, 2))
+ sm_dprintf("udbexpand: no match on %s (%d)\n",
+ (char *) keybuf, (int) keylen);
+# if HES_GETMAILHOST
+ if (tTd(28, 8))
+ sm_dprintf(" ... trying hes_getmailhost(%s)\n",
+ a->q_user);
+ hp = hes_getmailhost(a->q_user);
+ if (hp == NULL)
+ {
+ if (hes_error() == HES_ER_NET)
+ {
+ syserr("udbexpand: hesiod-getmail %s stat %d",
+ a->q_user, hes_error());
+ return EX_TEMPFAIL;
+ }
+ if (tTd(28, 2))
+ sm_dprintf("hes_getmailhost(%s): %d\n",
+ a->q_user, hes_error());
+ break;
+ }
+ if (strlen(hp->po_name) + strlen(hp->po_host) >
+ sizeof pobuf - 2)
+ {
+ if (tTd(28, 2))
+ sm_dprintf("hes_getmailhost(%s): expansion too long: %.30s@%.30s\n",
+ a->q_user,
+ hp->po_name,
+ hp->po_host);
+ break;
+ }
+ info.data = pobuf;
+ (void) sm_snprintf(pobuf, sizeof pobuf,
+ "%s@%s", hp->po_name, hp->po_host);
+ info.size = strlen(info.data);
+# else /* HES_GETMAILHOST */
+ break;
+# endif /* HES_GETMAILHOST */
+ }
+ if (tTd(28, 80))
+ sm_dprintf("udbexpand: match %.*s: %.*s\n",
+ (int) key.size, (char *) key.data,
+ (int) info.size, (char *) info.data);
+ a->q_flags &= ~QSELFREF;
+
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ a->q_state = QS_VERIFIED;
+ return EX_OK;
+ }
+
+ breakout = true;
+ if (info.size >= usersize)
+ user = sm_malloc_x(info.size + 1);
+ memmove(user, info.data, info.size);
+ user[info.size] = '\0';
+
+ message("hesioded to %s", user);
+ if (LogLevel > 10)
+ sm_syslog(LOG_INFO, e->e_id,
+ "hesiod %.100s => %s",
+ e->e_to,
+ shortenstring(user, MAXSHORTSTR));
+ naddrs = sendtolist(user, a, sendq, aliaslevel + 1, e);
+
+ if (naddrs > 0 && !bitset(QSELFREF, a->q_flags))
+ {
+ if (tTd(28, 5))
+ {
+ sm_dprintf("udbexpand: QS_EXPANDED ");
+ printaddr(a, false);
+ }
+ a->q_state = QS_EXPANDED;
+ }
+
+ /*
+ ** If this address has a -request address, reflect
+ ** it into the envelope.
+ */
+
+ (void) sm_strlcpyn(keybuf, sizeof keybuf, 2, a->q_user,
+ ":mailsender");
+ keylen = strlen(keybuf);
+ key.data = keybuf;
+ key.size = keylen;
+ i = hes_udb_get(&key, &info);
+ if (i != 0 || info.size <= 0)
+ break;
+ a->q_owner = sm_rpool_malloc_x(e->e_rpool,
+ info.size + 1);
+ memmove(a->q_owner, info.data, info.size);
+ a->q_owner[info.size] = '\0';
+ break;
+# endif /* HESIOD */
+
+ case UDB_REMOTE:
+ /* not yet implemented */
+ break;
+
+ case UDB_FORWARD:
+ if (bitset(EF_VRFYONLY, e->e_flags))
+ {
+ a->q_state = QS_VERIFIED;
+ return EX_OK;
+ }
+ i = strlen(up->udb_fwdhost) + strlen(a->q_user) + 1;
+ if (i >= usersize)
+ {
+ usersize = i + 1;
+ user = sm_malloc_x(usersize);
+ }
+ (void) sm_strlcpyn(user, usersize, 3,
+ a->q_user, "@", up->udb_fwdhost);
+ message("expanded to %s", user);
+ a->q_flags &= ~QSELFREF;
+ naddrs = sendtolist(user, a, sendq, aliaslevel + 1, e);
+ if (naddrs > 0 && !bitset(QSELFREF, a->q_flags))
+ {
+ if (tTd(28, 5))
+ {
+ sm_dprintf("udbexpand: QS_EXPANDED ");
+ printaddr(a, false);
+ }
+ a->q_state = QS_EXPANDED;
+ }
+ breakout = true;
+ break;
+
+ case UDB_EOLIST:
+ breakout = true;
+ break;
+
+ default:
+ /* unknown entry type */
+ break;
+ }
+ /* XXX if an exception occurs, there is a storage leak */
+ if (user != userbuf)
+ sm_free(user); /* XXX */
+ }
+ return EX_OK;
+}
+/*
+** UDBSENDER -- return canonical external name of sender, given local name
+**
+** Parameters:
+** sender -- the name of the sender on the local machine.
+** rpool -- resource pool from which to allocate result
+**
+** Returns:
+** The external name for this sender, if derivable from the
+** database. Storage allocated from rpool.
+** NULL -- if nothing is changed from the database.
+**
+** Side Effects:
+** none.
+*/
+
+char *
+udbsender(sender, rpool)
+ char *sender;
+ SM_RPOOL_T *rpool;
+{
+ return udbmatch(sender, "mailname", rpool);
+}
+/*
+** UDBMATCH -- match user in field, return result of lookup.
+**
+** Parameters:
+** user -- the name of the user.
+** field -- the field to lookup.
+** rpool -- resource pool from which to allocate result
+**
+** Returns:
+** The external name for this sender, if derivable from the
+** database. Storage allocated from rpool.
+** NULL -- if nothing is changed from the database.
+**
+** Side Effects:
+** none.
+*/
+
+static char *
+udbmatch(user, field, rpool)
+ char *user;
+ char *field;
+ SM_RPOOL_T *rpool;
+{
+ register char *p;
+ register struct udbent *up;
+ int i;
+ int keylen;
+ DBT key, info;
+ char keybuf[MAXKEY];
+
+ if (tTd(28, 1))
+ sm_dprintf("udbmatch(%s, %s)\n", user, field);
+
+ if (!UdbInitialized)
+ {
+ if (_udbx_init(CurEnv) == EX_TEMPFAIL)
+ return NULL;
+ }
+
+ /* short circuit if no spec */
+ if (UdbSpec == NULL || UdbSpec[0] == '\0')
+ return NULL;
+
+ /* short circuit name begins with '\\' since it can't possibly match */
+ if (user[0] == '\\')
+ return NULL;
+
+ /* long names can never match and are a pain to deal with */
+ i = strlen(field);
+ if (i < sizeof "maildrop")
+ i = sizeof "maildrop";
+ if ((strlen(user) + i) > sizeof keybuf - 4)
+ return NULL;
+
+ /* names beginning with colons indicate metadata */
+ if (user[0] == ':')
+ return NULL;
+
+ /* build database key */
+ (void) sm_strlcpyn(keybuf, sizeof keybuf, 3, user, ":", field);
+ keylen = strlen(keybuf);
+
+ for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++)
+ {
+ /*
+ ** Select action based on entry type.
+ */
+
+ switch (up->udb_type)
+ {
+# if NEWDB
+ case UDB_DBFETCH:
+ memset(&key, '\0', sizeof key);
+ memset(&info, '\0', sizeof info);
+ key.data = keybuf;
+ key.size = keylen;
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = errno = (*up->udb_dbp->get)(up->udb_dbp, NULL,
+ &key, &info, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (i != 0 || info.size <= 0)
+ {
+ if (tTd(28, 2))
+ sm_dprintf("udbmatch: no match on %s (%d) via db\n",
+ keybuf, keylen);
+ continue;
+ }
+
+ p = sm_rpool_malloc_x(rpool, info.size + 1);
+ memmove(p, info.data, info.size);
+ p[info.size] = '\0';
+ if (tTd(28, 1))
+ sm_dprintf("udbmatch ==> %s\n", p);
+ return p;
+# endif /* NEWDB */
+
+# if HESIOD
+ case UDB_HESIOD:
+ key.data = keybuf;
+ key.size = keylen;
+ i = hes_udb_get(&key, &info);
+ if (i != 0 || info.size <= 0)
+ {
+ if (tTd(28, 2))
+ sm_dprintf("udbmatch: no match on %s (%d) via hesiod\n",
+ keybuf, keylen);
+ continue;
+ }
+
+ p = sm_rpool_malloc_x(rpool, info.size + 1);
+ memmove(p, info.data, info.size);
+ p[info.size] = '\0';
+ if (tTd(28, 1))
+ sm_dprintf("udbmatch ==> %s\n", p);
+ return p;
+# endif /* HESIOD */
+ }
+ }
+
+ if (strcmp(field, "mailname") != 0)
+ return NULL;
+
+ /*
+ ** Nothing yet. Search again for a default case. But only
+ ** use it if we also have a forward (:maildrop) pointer already
+ ** in the database.
+ */
+
+ /* build database key */
+ (void) sm_strlcpyn(keybuf, sizeof keybuf, 2, user, ":maildrop");
+ keylen = strlen(keybuf);
+
+ for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++)
+ {
+ switch (up->udb_type)
+ {
+# if NEWDB
+ case UDB_DBFETCH:
+ /* get the default case for this database */
+ if (up->udb_default == NULL)
+ {
+ memset(&key, '\0', sizeof key);
+ memset(&info, '\0', sizeof info);
+ key.data = ":default:mailname";
+ key.size = strlen(key.data);
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->get)(up->udb_dbp,
+ &key, &info, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = errno = (*up->udb_dbp->get)(up->udb_dbp,
+ NULL, &key,
+ &info, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (i != 0 || info.size <= 0)
+ {
+ /* no default case */
+ up->udb_default = "";
+ continue;
+ }
+
+ /* save the default case */
+ up->udb_default = sm_pmalloc_x(info.size + 1);
+ memmove(up->udb_default, info.data, info.size);
+ up->udb_default[info.size] = '\0';
+ }
+ else if (up->udb_default[0] == '\0')
+ continue;
+
+ /* we have a default case -- verify user:maildrop */
+ memset(&key, '\0', sizeof key);
+ memset(&info, '\0', sizeof info);
+ key.data = keybuf;
+ key.size = keylen;
+# if DB_VERSION_MAJOR < 2
+ i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0);
+# else /* DB_VERSION_MAJOR < 2 */
+ i = errno = (*up->udb_dbp->get)(up->udb_dbp, NULL,
+ &key, &info, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (i != 0 || info.size <= 0)
+ {
+ /* nope -- no aliasing for this user */
+ continue;
+ }
+
+ /* they exist -- build the actual address */
+ i = strlen(user) + strlen(up->udb_default) + 2;
+ p = sm_rpool_malloc_x(rpool, i);
+ (void) sm_strlcpyn(p, i, 3, user, "@", up->udb_default);
+ if (tTd(28, 1))
+ sm_dprintf("udbmatch ==> %s\n", p);
+ return p;
+# endif /* NEWDB */
+
+# if HESIOD
+ case UDB_HESIOD:
+ /* get the default case for this database */
+ if (up->udb_default == NULL)
+ {
+ key.data = ":default:mailname";
+ key.size = strlen(key.data);
+ i = hes_udb_get(&key, &info);
+
+ if (i != 0 || info.size <= 0)
+ {
+ /* no default case */
+ up->udb_default = "";
+ continue;
+ }
+
+ /* save the default case */
+ up->udb_default = sm_pmalloc_x(info.size + 1);
+ memmove(up->udb_default, info.data, info.size);
+ up->udb_default[info.size] = '\0';
+ }
+ else if (up->udb_default[0] == '\0')
+ continue;
+
+ /* we have a default case -- verify user:maildrop */
+ key.data = keybuf;
+ key.size = keylen;
+ i = hes_udb_get(&key, &info);
+ if (i != 0 || info.size <= 0)
+ {
+ /* nope -- no aliasing for this user */
+ continue;
+ }
+
+ /* they exist -- build the actual address */
+ i = strlen(user) + strlen(up->udb_default) + 2;
+ p = sm_rpool_malloc_x(rpool, i);
+ (void) sm_strlcpyn(p, i, 3, user, "@", up->udb_default);
+ if (tTd(28, 1))
+ sm_dprintf("udbmatch ==> %s\n", p);
+ return p;
+ break;
+# endif /* HESIOD */
+ }
+ }
+
+ /* still nothing.... too bad */
+ return NULL;
+}
+/*
+** UDB_MAP_LOOKUP -- look up arbitrary entry in user database map
+**
+** Parameters:
+** map -- the map being queried.
+** name -- the name to look up.
+** av -- arguments to the map lookup.
+** statp -- to get any error status.
+**
+** Returns:
+** NULL if name not found in map.
+** The rewritten name otherwise.
+*/
+
+/* ARGSUSED3 */
+char *
+udb_map_lookup(map, name, av, statp)
+ MAP *map;
+ char *name;
+ char **av;
+ int *statp;
+{
+ char *val;
+ char *key;
+ char *SM_NONVOLATILE result = NULL;
+ char keybuf[MAXNAME + 1];
+
+ if (tTd(28, 20) || tTd(38, 20))
+ sm_dprintf("udb_map_lookup(%s, %s)\n", map->map_mname, name);
+
+ if (bitset(MF_NOFOLDCASE, map->map_mflags))
+ {
+ key = name;
+ }
+ else
+ {
+ int keysize = strlen(name);
+
+ if (keysize > sizeof keybuf - 1)
+ keysize = sizeof keybuf - 1;
+ memmove(keybuf, name, keysize);
+ keybuf[keysize] = '\0';
+ makelower(keybuf);
+ key = keybuf;
+ }
+ val = udbmatch(key, map->map_file, NULL);
+ if (val == NULL)
+ return NULL;
+ SM_TRY
+ if (bitset(MF_MATCHONLY, map->map_mflags))
+ result = map_rewrite(map, name, strlen(name), NULL);
+ else
+ result = map_rewrite(map, val, strlen(val), av);
+ SM_FINALLY
+ sm_free(val);
+ SM_END_TRY
+ return result;
+}
+/*
+** _UDBX_INIT -- parse the UDB specification, opening any valid entries.
+**
+** Parameters:
+** e -- the current envelope.
+**
+** Returns:
+** EX_TEMPFAIL -- if it appeared it couldn't get hold of a
+** database due to a host being down or some similar
+** (recoverable) situation.
+** EX_OK -- otherwise.
+**
+** Side Effects:
+** Fills in the UdbEnts structure from UdbSpec.
+*/
+
+# define MAXUDBOPTS 27
+
+static int
+_udbx_init(e)
+ ENVELOPE *e;
+{
+ int ents = 0;
+ register char *p;
+ register struct udbent *up;
+
+ if (UdbInitialized)
+ return EX_OK;
+
+# ifdef UDB_DEFAULT_SPEC
+ if (UdbSpec == NULL)
+ UdbSpec = UDB_DEFAULT_SPEC;
+# endif /* UDB_DEFAULT_SPEC */
+
+ p = UdbSpec;
+ up = UdbEnts;
+ while (p != NULL)
+ {
+ char *spec;
+ int l;
+ struct udb_option opts[MAXUDBOPTS + 1];
+
+ while (*p == ' ' || *p == '\t' || *p == ',')
+ p++;
+ if (*p == '\0')
+ break;
+ spec = p;
+ p = strchr(p, ',');
+ if (p != NULL)
+ *p++ = '\0';
+
+ if (ents >= MAXUDBENT)
+ {
+ syserr("Maximum number of UDB entries exceeded");
+ break;
+ }
+
+ /* extract options */
+ (void) _udb_parsespec(spec, opts, MAXUDBOPTS);
+
+ /*
+ ** Decode database specification.
+ **
+ ** In the sendmail tradition, the leading character
+ ** defines the semantics of the rest of the entry.
+ **
+ ** @hostname -- forward email to the indicated host.
+ ** This should be the last in the list,
+ ** since it always matches the input.
+ ** /dbname -- search the named database on the local
+ ** host using the Berkeley db package.
+ ** Hesiod -- search the named database with BIND
+ ** using the MIT Hesiod package.
+ */
+
+ switch (*spec)
+ {
+ case '@': /* forward to remote host */
+ up->udb_type = UDB_FORWARD;
+ up->udb_pid = CurrentPid;
+ up->udb_fwdhost = spec + 1;
+ ents++;
+ up++;
+ break;
+
+# if HESIOD
+ case 'h': /* use hesiod */
+ case 'H':
+ if (sm_strcasecmp(spec, "hesiod") != 0)
+ goto badspec;
+ up->udb_type = UDB_HESIOD;
+ up->udb_pid = CurrentPid;
+ ents++;
+ up++;
+ break;
+# endif /* HESIOD */
+
+# if NEWDB
+ case '/': /* look up remote name */
+ l = strlen(spec);
+ if (l > 3 && strcmp(&spec[l - 3], ".db") == 0)
+ {
+ up->udb_dbname = spec;
+ }
+ else
+ {
+ up->udb_dbname = sm_pmalloc_x(l + 4);
+ (void) sm_strlcpyn(up->udb_dbname, l + 4, 2,
+ spec, ".db");
+ }
+ errno = 0;
+# if DB_VERSION_MAJOR < 2
+ up->udb_dbp = dbopen(up->udb_dbname, O_RDONLY,
+ 0644, DB_BTREE, NULL);
+# else /* DB_VERSION_MAJOR < 2 */
+ {
+ int flags = DB_RDONLY;
+# if DB_VERSION_MAJOR > 2
+ int ret;
+# endif /* DB_VERSION_MAJOR > 2 */
+
+# if !HASFLOCK && defined(DB_FCNTL_LOCKING)
+ flags |= DB_FCNTL_LOCKING;
+# endif /* !HASFLOCK && defined(DB_FCNTL_LOCKING) */
+
+ up->udb_dbp = NULL;
+
+# if DB_VERSION_MAJOR > 2
+ ret = db_create(&up->udb_dbp, NULL, 0);
+ if (ret != 0)
+ {
+ (void) up->udb_dbp->close(up->udb_dbp,
+ 0);
+ up->udb_dbp = NULL;
+ }
+ else
+ {
+ ret = up->udb_dbp->open(up->udb_dbp,
+ up->udb_dbname,
+ NULL,
+ DB_BTREE,
+ flags,
+ 0644);
+ if (ret != 0)
+ {
+#ifdef DB_OLD_VERSION
+ if (ret == DB_OLD_VERSION)
+ ret = EINVAL;
+#endif /* DB_OLD_VERSION */
+ (void) up->udb_dbp->close(up->udb_dbp, 0);
+ up->udb_dbp = NULL;
+ }
+ }
+ errno = ret;
+# else /* DB_VERSION_MAJOR > 2 */
+ errno = db_open(up->udb_dbname, DB_BTREE,
+ flags, 0644, NULL,
+ NULL, &up->udb_dbp);
+# endif /* DB_VERSION_MAJOR > 2 */
+ }
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (up->udb_dbp == NULL)
+ {
+ if (tTd(28, 1))
+ {
+ int save_errno = errno;
+
+# if DB_VERSION_MAJOR < 2
+ sm_dprintf("dbopen(%s): %s\n",
+# else /* DB_VERSION_MAJOR < 2 */
+ sm_dprintf("db_open(%s): %s\n",
+# endif /* DB_VERSION_MAJOR < 2 */
+ up->udb_dbname,
+ sm_errstring(errno));
+ errno = save_errno;
+ }
+ if (errno != ENOENT && errno != EACCES)
+ {
+ if (LogLevel > 2)
+ sm_syslog(LOG_ERR, e->e_id,
+# if DB_VERSION_MAJOR < 2
+ "dbopen(%s): %s",
+# else /* DB_VERSION_MAJOR < 2 */
+ "db_open(%s): %s",
+# endif /* DB_VERSION_MAJOR < 2 */
+ up->udb_dbname,
+ sm_errstring(errno));
+ up->udb_type = UDB_EOLIST;
+ if (up->udb_dbname != spec)
+ sm_free(up->udb_dbname); /* XXX */
+ goto tempfail;
+ }
+ if (up->udb_dbname != spec)
+ sm_free(up->udb_dbname); /* XXX */
+ break;
+ }
+ if (tTd(28, 1))
+ {
+# if DB_VERSION_MAJOR < 2
+ sm_dprintf("_udbx_init: dbopen(%s)\n",
+# else /* DB_VERSION_MAJOR < 2 */
+ sm_dprintf("_udbx_init: db_open(%s)\n",
+# endif /* DB_VERSION_MAJOR < 2 */
+ up->udb_dbname);
+ }
+ up->udb_type = UDB_DBFETCH;
+ up->udb_pid = CurrentPid;
+ ents++;
+ up++;
+ break;
+# endif /* NEWDB */
+
+ default:
+# if HESIOD
+badspec:
+# endif /* HESIOD */
+ syserr("Unknown UDB spec %s", spec);
+ break;
+ }
+ }
+ up->udb_type = UDB_EOLIST;
+
+ if (tTd(28, 4))
+ {
+ for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++)
+ {
+ switch (up->udb_type)
+ {
+ case UDB_REMOTE:
+ sm_dprintf("REMOTE: addr %s, timeo %d\n",
+ anynet_ntoa((SOCKADDR *) &up->udb_addr),
+ up->udb_timeout);
+ break;
+
+ case UDB_DBFETCH:
+# if NEWDB
+ sm_dprintf("FETCH: file %s\n",
+ up->udb_dbname);
+# else /* NEWDB */
+ sm_dprintf("FETCH\n");
+# endif /* NEWDB */
+ break;
+
+ case UDB_FORWARD:
+ sm_dprintf("FORWARD: host %s\n",
+ up->udb_fwdhost);
+ break;
+
+ case UDB_HESIOD:
+ sm_dprintf("HESIOD\n");
+ break;
+
+ default:
+ sm_dprintf("UNKNOWN\n");
+ break;
+ }
+ }
+ }
+
+ UdbInitialized = true;
+ errno = 0;
+ return EX_OK;
+
+ /*
+ ** On temporary failure, back out anything we've already done
+ */
+
+ tempfail:
+# if NEWDB
+ for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++)
+ {
+ if (up->udb_type == UDB_DBFETCH)
+ {
+# if DB_VERSION_MAJOR < 2
+ (*up->udb_dbp->close)(up->udb_dbp);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = (*up->udb_dbp->close)(up->udb_dbp, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ if (tTd(28, 1))
+ sm_dprintf("_udbx_init: db->close(%s)\n",
+ up->udb_dbname);
+ }
+ }
+# endif /* NEWDB */
+ return EX_TEMPFAIL;
+}
+
+static int
+_udb_parsespec(udbspec, opt, maxopts)
+ char *udbspec;
+ struct udb_option opt[];
+ int maxopts;
+{
+ register char *spec;
+ register char *spec_end;
+ register int optnum;
+
+ spec_end = strchr(udbspec, ':');
+ for (optnum = 0; optnum < maxopts && (spec = spec_end) != NULL; optnum++)
+ {
+ register char *p;
+
+ while (isascii(*spec) && isspace(*spec))
+ spec++;
+ spec_end = strchr(spec, ':');
+ if (spec_end != NULL)
+ *spec_end++ = '\0';
+
+ opt[optnum].udbo_name = spec;
+ opt[optnum].udbo_val = NULL;
+ p = strchr(spec, '=');
+ if (p != NULL)
+ opt[optnum].udbo_val = ++p;
+ }
+ return optnum;
+}
+/*
+** _UDBX_CLOSE -- close all file based UDB entries.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+void
+_udbx_close()
+{
+ struct udbent *up;
+
+ if (!UdbInitialized)
+ return;
+
+ for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++)
+ {
+ if (up->udb_pid != CurrentPid)
+ continue;
+
+# if NEWDB
+ if (up->udb_type == UDB_DBFETCH)
+ {
+# if DB_VERSION_MAJOR < 2
+ (*up->udb_dbp->close)(up->udb_dbp);
+# else /* DB_VERSION_MAJOR < 2 */
+ errno = (*up->udb_dbp->close)(up->udb_dbp, 0);
+# endif /* DB_VERSION_MAJOR < 2 */
+ }
+ if (tTd(28, 1))
+ sm_dprintf("_udbx_init: db->close(%s)\n",
+ up->udb_dbname);
+# endif /* NEWDB */
+ }
+}
+
+# if HESIOD
+
+static int
+hes_udb_get(key, info)
+ DBT *key;
+ DBT *info;
+{
+ char *name, *type;
+ char **hp;
+ char kbuf[MAXKEY + 1];
+
+ if (sm_strlcpy(kbuf, key->data, sizeof kbuf) >= sizeof kbuf)
+ return 0;
+ name = kbuf;
+ type = strrchr(name, ':');
+ if (type == NULL)
+ return 1;
+ *type++ = '\0';
+ if (strchr(name, '@') != NULL)
+ return 1;
+
+ if (tTd(28, 1))
+ sm_dprintf("hes_udb_get(%s, %s)\n", name, type);
+
+ /* make the hesiod query */
+# ifdef HESIOD_INIT
+ if (HesiodContext == NULL && hesiod_init(&HesiodContext) != 0)
+ return -1;
+ hp = hesiod_resolve(HesiodContext, name, type);
+# else /* HESIOD_INIT */
+ hp = hes_resolve(name, type);
+# endif /* HESIOD_INIT */
+ *--type = ':';
+# ifdef HESIOD_INIT
+ if (hp == NULL)
+ return 1;
+ if (*hp == NULL)
+ {
+ hesiod_free_list(HesiodContext, hp);
+ if (errno == ECONNREFUSED || errno == EMSGSIZE)
+ return -1;
+ return 1;
+ }
+# else /* HESIOD_INIT */
+ if (hp == NULL || hp[0] == NULL)
+ {
+ /* network problem or timeout */
+ if (hes_error() == HES_ER_NET)
+ return -1;
+
+ return 1;
+ }
+# endif /* HESIOD_INIT */
+ else
+ {
+ /*
+ ** If there are multiple matches, just return the
+ ** first one.
+ **
+ ** XXX These should really be returned; for example,
+ ** XXX it is legal for :maildrop to be multi-valued.
+ */
+
+ info->data = hp[0];
+ info->size = (size_t) strlen(info->data);
+ }
+
+ if (tTd(28, 80))
+ sm_dprintf("hes_udb_get => %s\n", *hp);
+
+ return 0;
+}
+# endif /* HESIOD */
+
+#else /* USERDB */
+
+int
+udbexpand(a, sendq, aliaslevel, e)
+ ADDRESS *a;
+ ADDRESS **sendq;
+ int aliaslevel;
+ ENVELOPE *e;
+{
+ return EX_OK;
+}
+
+#endif /* USERDB */
diff --git a/contrib/sendmail/src/usersmtp.c b/contrib/sendmail/src/usersmtp.c
new file mode 100644
index 0000000..931e6b3
--- /dev/null
+++ b/contrib/sendmail/src/usersmtp.c
@@ -0,0 +1,3313 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: usersmtp.c,v 8.437.2.5 2002/08/16 16:48:11 ca Exp $")
+
+#include <sysexits.h>
+
+
+extern void markfailure __P((ENVELOPE *, ADDRESS *, MCI *, int, bool));
+static void datatimeout __P((void));
+static void esmtp_check __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
+static void helo_options __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
+static int smtprcptstat __P((ADDRESS *, MAILER *, MCI *, ENVELOPE *));
+
+#if SASL
+extern void *sm_sasl_malloc __P((unsigned long));
+extern void sm_sasl_free __P((void *));
+#endif /* SASL */
+
+/*
+** USERSMTP -- run SMTP protocol from the user end.
+**
+** This protocol is described in RFC821.
+*/
+
+#define REPLYTYPE(r) ((r) / 100) /* first digit of reply code */
+#define REPLYCLASS(r) (((r) / 10) % 10) /* second digit of reply code */
+#define SMTPCLOSING 421 /* "Service Shutting Down" */
+
+#define ENHSCN(e, d) ((e) == NULL ? (d) : (e))
+
+#define ENHSCN_RPOOL(e, d, rpool) \
+ ((e) == NULL ? (d) : sm_rpool_strdup_x(rpool, e))
+
+static char SmtpMsgBuffer[MAXLINE]; /* buffer for commands */
+static char SmtpReplyBuffer[MAXLINE]; /* buffer for replies */
+static bool SmtpNeedIntro; /* need "while talking" in transcript */
+/*
+** SMTPINIT -- initialize SMTP.
+**
+** Opens the connection and sends the initial protocol.
+**
+** Parameters:
+** m -- mailer to create connection to.
+** mci -- the mailer connection info.
+** e -- the envelope.
+** onlyhelo -- send only helo command?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** creates connection and sends initial protocol.
+*/
+
+void
+smtpinit(m, mci, e, onlyhelo)
+ MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+ bool onlyhelo;
+{
+ register int r;
+ int state;
+ register char *p;
+ register char *hn;
+ char *enhsc;
+
+ enhsc = NULL;
+ if (tTd(18, 1))
+ {
+ sm_dprintf("smtpinit ");
+ mci_dump(mci, false);
+ }
+
+ /*
+ ** Open the connection to the mailer.
+ */
+
+ SmtpError[0] = '\0';
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
+ SmtpNeedIntro = true;
+ state = mci->mci_state;
+ switch (state)
+ {
+ case MCIS_MAIL:
+ case MCIS_RCPT:
+ case MCIS_DATA:
+ /* need to clear old information */
+ smtprset(m, mci, e);
+ /* FALLTHROUGH */
+
+ case MCIS_OPEN:
+ if (!onlyhelo)
+ return;
+ break;
+
+ case MCIS_ERROR:
+ case MCIS_QUITING:
+ case MCIS_SSD:
+ /* shouldn't happen */
+ smtpquit(m, mci, e);
+ /* FALLTHROUGH */
+
+ case MCIS_CLOSED:
+ syserr("451 4.4.0 smtpinit: state CLOSED (was %d)", state);
+ return;
+
+ case MCIS_OPENING:
+ break;
+ }
+ if (onlyhelo)
+ goto helo;
+
+ mci->mci_state = MCIS_OPENING;
+
+ /*
+ ** Get the greeting message.
+ ** This should appear spontaneously. Give it five minutes to
+ ** happen.
+ */
+
+ SmtpPhase = mci->mci_phase = "client greeting";
+ sm_setproctitle(true, e, "%s %s: %s",
+ qid_printname(e), CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_initial, esmtp_check, NULL);
+ if (r < 0)
+ goto tempfail1;
+ if (REPLYTYPE(r) == 4)
+ goto tempfail2;
+ if (REPLYTYPE(r) != 2)
+ goto unavailable;
+
+ /*
+ ** Send the HELO command.
+ ** My mother taught me to always introduce myself.
+ */
+
+helo:
+ if (bitnset(M_ESMTP, m->m_flags) || bitnset(M_LMTP, m->m_flags))
+ mci->mci_flags |= MCIF_ESMTP;
+ hn = mci->mci_heloname ? mci->mci_heloname : MyHostName;
+
+tryhelo:
+#if _FFR_IGNORE_EXT_ON_HELO
+ mci->mci_flags &= ~MCIF_HELO;
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+ if (bitnset(M_LMTP, m->m_flags))
+ {
+ smtpmessage("LHLO %s", m, mci, hn);
+ SmtpPhase = mci->mci_phase = "client LHLO";
+ }
+ else if (bitset(MCIF_ESMTP, mci->mci_flags) &&
+ !bitnset(M_FSMTP, m->m_flags))
+ {
+ smtpmessage("EHLO %s", m, mci, hn);
+ SmtpPhase = mci->mci_phase = "client EHLO";
+ }
+ else
+ {
+ smtpmessage("HELO %s", m, mci, hn);
+ SmtpPhase = mci->mci_phase = "client HELO";
+#if _FFR_IGNORE_EXT_ON_HELO
+ mci->mci_flags |= MCIF_HELO;
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+ }
+ sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+ r = reply(m, mci, e,
+ bitnset(M_LMTP, m->m_flags) ? TimeOuts.to_lhlo
+ : TimeOuts.to_helo,
+ helo_options, NULL);
+ if (r < 0)
+ goto tempfail1;
+ else if (REPLYTYPE(r) == 5)
+ {
+ if (bitset(MCIF_ESMTP, mci->mci_flags) &&
+ !bitnset(M_LMTP, m->m_flags))
+ {
+ /* try old SMTP instead */
+ mci->mci_flags &= ~MCIF_ESMTP;
+ goto tryhelo;
+ }
+ goto unavailable;
+ }
+ else if (REPLYTYPE(r) != 2)
+ goto tempfail2;
+
+ /*
+ ** Check to see if we actually ended up talking to ourself.
+ ** This means we didn't know about an alias or MX, or we managed
+ ** to connect to an echo server.
+ */
+
+ p = strchr(&SmtpReplyBuffer[4], ' ');
+ if (p != NULL)
+ *p = '\0';
+ if (!bitnset(M_NOLOOPCHECK, m->m_flags) &&
+ !bitnset(M_LMTP, m->m_flags) &&
+ sm_strcasecmp(&SmtpReplyBuffer[4], MyHostName) == 0)
+ {
+ syserr("553 5.3.5 %s config error: mail loops back to me (MX problem?)",
+ CurHostName);
+ mci_setstat(mci, EX_CONFIG, "5.3.5",
+ "553 5.3.5 system config error");
+ mci->mci_errno = 0;
+ smtpquit(m, mci, e);
+ return;
+ }
+
+#if !_FFR_DEPRECATE_MAILER_FLAG_I
+ /*
+ ** If this is expected to be another sendmail, send some internal
+ ** commands.
+ */
+
+ if (bitnset(M_INTERNAL, m->m_flags))
+ {
+ /* tell it to be verbose */
+ smtpmessage("VERB", m, mci);
+ r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, &enhsc);
+ if (r < 0)
+ goto tempfail1;
+ }
+#endif /* !_FFR_DEPRECATE_MAILER_FLAG_I */
+
+ if (mci->mci_state != MCIS_CLOSED)
+ {
+ mci->mci_state = MCIS_OPEN;
+ return;
+ }
+
+ /* got a 421 error code during startup */
+
+ tempfail1:
+ mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.4.2"), NULL);
+ if (mci->mci_state != MCIS_CLOSED)
+ smtpquit(m, mci, e);
+ return;
+
+ tempfail2:
+ /* XXX should use code from other end iff ENHANCEDSTATUSCODES */
+ mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.5.0"),
+ SmtpReplyBuffer);
+ if (mci->mci_state != MCIS_CLOSED)
+ smtpquit(m, mci, e);
+ return;
+
+ unavailable:
+ mci_setstat(mci, EX_UNAVAILABLE, "5.5.0", SmtpReplyBuffer);
+ smtpquit(m, mci, e);
+ return;
+}
+/*
+** ESMTP_CHECK -- check to see if this implementation likes ESMTP protocol
+**
+** Parameters:
+** line -- the response line.
+** firstline -- set if this is the first line of the reply.
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope.
+**
+** Returns:
+** none.
+*/
+
+static void
+esmtp_check(line, firstline, m, mci, e)
+ char *line;
+ bool firstline;
+ MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ if (strstr(line, "ESMTP") != NULL)
+ mci->mci_flags |= MCIF_ESMTP;
+
+ /*
+ ** Dirty hack below. Quoting the author:
+ ** This was a response to people who wanted SMTP transmission to be
+ ** just-send-8 by default. Essentially, you could put this tag into
+ ** your greeting message to behave as though the F=8 flag was set on
+ ** the mailer.
+ */
+
+ if (strstr(line, "8BIT-OK") != NULL)
+ mci->mci_flags |= MCIF_8BITOK;
+}
+
+#if SASL
+/* specify prototype so compiler can check calls */
+static char *str_union __P((char *, char *, SM_RPOOL_T *));
+
+/*
+** STR_UNION -- create the union of two lists
+**
+** Parameters:
+** s1, s2 -- lists of items (separated by single blanks).
+** rpool -- resource pool from which result is allocated.
+**
+** Returns:
+** the union of both lists.
+*/
+
+static char *
+str_union(s1, s2, rpool)
+ char *s1, *s2;
+ SM_RPOOL_T *rpool;
+{
+ char *hr, *h1, *h, *res;
+ int l1, l2, rl;
+
+ if (s1 == NULL || *s1 == '\0')
+ return s2;
+ if (s2 == NULL || *s2 == '\0')
+ return s1;
+ l1 = strlen(s1);
+ l2 = strlen(s2);
+ rl = l1 + l2;
+ res = (char *) sm_rpool_malloc(rpool, rl + 2);
+ if (res == NULL)
+ {
+ if (l1 > l2)
+ return s1;
+ return s2;
+ }
+ (void) sm_strlcpy(res, s1, rl);
+ hr = res + l1;
+ h1 = s2;
+ h = s2;
+
+ /* walk through s2 */
+ while (h != NULL && *h1 != '\0')
+ {
+ /* is there something after the current word? */
+ if ((h = strchr(h1, ' ')) != NULL)
+ *h = '\0';
+ l1 = strlen(h1);
+
+ /* does the current word appear in s1 ? */
+ if (iteminlist(h1, s1, " ") == NULL)
+ {
+ /* add space as delimiter */
+ *hr++ = ' ';
+
+ /* copy the item */
+ memcpy(hr, h1, l1);
+
+ /* advance pointer in result list */
+ hr += l1;
+ *hr = '\0';
+ }
+ if (h != NULL)
+ {
+ /* there are more items */
+ *h = ' ';
+ h1 = h + 1;
+ }
+ }
+ return res;
+}
+#endif /* SASL */
+
+/*
+** HELO_OPTIONS -- process the options on a HELO line.
+**
+** Parameters:
+** line -- the response line.
+** firstline -- set if this is the first line of the reply.
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope (unused).
+**
+** Returns:
+** none.
+*/
+
+static void
+helo_options(line, firstline, m, mci, e)
+ char *line;
+ bool firstline;
+ MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ register char *p;
+#if _FFR_IGNORE_EXT_ON_HELO
+ static bool logged = false;
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+
+ if (firstline)
+ {
+#if SASL
+ mci->mci_saslcap = NULL;
+#endif /* SASL */
+#if _FFR_IGNORE_EXT_ON_HELO
+ logged = false;
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+ return;
+ }
+#if _FFR_IGNORE_EXT_ON_HELO
+ else if (bitset(MCIF_HELO, mci->mci_flags))
+ {
+ if (LogLevel > 8 && !logged)
+ {
+ sm_syslog(LOG_WARNING, NOQID,
+ "server=%s [%s] returned extensions despite HELO command",
+ macvalue(macid("{server_name}"), e),
+ macvalue(macid("{server_addr}"), e));
+ logged = true;
+ }
+ return;
+ }
+#endif /* _FFR_IGNORE_EXT_ON_HELO */
+
+ if (strlen(line) < 5)
+ return;
+ line += 4;
+ p = strpbrk(line, " =");
+ if (p != NULL)
+ *p++ = '\0';
+ if (sm_strcasecmp(line, "size") == 0)
+ {
+ mci->mci_flags |= MCIF_SIZE;
+ if (p != NULL)
+ mci->mci_maxsize = atol(p);
+ }
+ else if (sm_strcasecmp(line, "8bitmime") == 0)
+ {
+ mci->mci_flags |= MCIF_8BITMIME;
+ mci->mci_flags &= ~MCIF_7BIT;
+ }
+ else if (sm_strcasecmp(line, "expn") == 0)
+ mci->mci_flags |= MCIF_EXPN;
+ else if (sm_strcasecmp(line, "dsn") == 0)
+ mci->mci_flags |= MCIF_DSN;
+ else if (sm_strcasecmp(line, "enhancedstatuscodes") == 0)
+ mci->mci_flags |= MCIF_ENHSTAT;
+ else if (sm_strcasecmp(line, "pipelining") == 0)
+ mci->mci_flags |= MCIF_PIPELINED;
+#if STARTTLS
+ else if (sm_strcasecmp(line, "starttls") == 0)
+ mci->mci_flags |= MCIF_TLS;
+#endif /* STARTTLS */
+ else if (sm_strcasecmp(line, "deliverby") == 0)
+ {
+ mci->mci_flags |= MCIF_DLVR_BY;
+ if (p != NULL)
+ mci->mci_min_by = atol(p);
+ }
+#if SASL
+ else if (sm_strcasecmp(line, "auth") == 0)
+ {
+ if (p != NULL && *p != '\0')
+ {
+ if (mci->mci_saslcap != NULL)
+ {
+ /*
+ ** Create the union with previous auth
+ ** offerings because we recognize "auth "
+ ** and "auth=" (old format).
+ */
+
+ mci->mci_saslcap = str_union(mci->mci_saslcap,
+ p, mci->mci_rpool);
+ mci->mci_flags |= MCIF_AUTH;
+ }
+ else
+ {
+ int l;
+
+ l = strlen(p) + 1;
+ mci->mci_saslcap = (char *)
+ sm_rpool_malloc(mci->mci_rpool, l);
+ if (mci->mci_saslcap != NULL)
+ {
+ (void) sm_strlcpy(mci->mci_saslcap, p,
+ l);
+ mci->mci_flags |= MCIF_AUTH;
+ }
+ }
+ }
+ }
+#endif /* SASL */
+}
+#if SASL
+
+static int getsimple __P((void *, int, const char **, unsigned *));
+static int getsecret __P((sasl_conn_t *, void *, int, sasl_secret_t **));
+static int saslgetrealm __P((void *, int, const char **, const char **));
+static int readauth __P((char *, bool, SASL_AI_T *m, SM_RPOOL_T *));
+static int getauth __P((MCI *, ENVELOPE *, SASL_AI_T *));
+static char *removemech __P((char *, char *, SM_RPOOL_T *));
+static int attemptauth __P((MAILER *, MCI *, ENVELOPE *, SASL_AI_T *));
+
+static sasl_callback_t callbacks[] =
+{
+ { SASL_CB_GETREALM, &saslgetrealm, NULL },
+#define CB_GETREALM_IDX 0
+ { SASL_CB_PASS, &getsecret, NULL },
+#define CB_PASS_IDX 1
+ { SASL_CB_USER, &getsimple, NULL },
+#define CB_USER_IDX 2
+ { SASL_CB_AUTHNAME, &getsimple, NULL },
+#define CB_AUTHNAME_IDX 3
+ { SASL_CB_VERIFYFILE, &safesaslfile, NULL },
+#define CB_SAFESASL_IDX 4
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+
+/*
+** INIT_SASL_CLIENT -- initialize client side of Cyrus-SASL
+**
+** Parameters:
+** none.
+**
+** Returns:
+** SASL_OK -- if successful.
+** SASL error code -- otherwise.
+**
+** Side Effects:
+** checks/sets sasl_clt_init.
+*/
+
+static bool sasl_clt_init = false;
+
+static int
+init_sasl_client()
+{
+ int result;
+
+ if (sasl_clt_init)
+ return SASL_OK;
+ result = sasl_client_init(callbacks);
+
+ /* should we retry later again or just remember that it failed? */
+ if (result == SASL_OK)
+ sasl_clt_init = true;
+ return result;
+}
+/*
+** STOP_SASL_CLIENT -- shutdown client side of Cyrus-SASL
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** checks/sets sasl_clt_init.
+*/
+
+void
+stop_sasl_client()
+{
+ if (!sasl_clt_init)
+ return;
+ sasl_clt_init = false;
+ sasl_done();
+}
+/*
+** GETSASLDATA -- process the challenges from the SASL protocol
+**
+** This gets the relevant sasl response data out of the reply
+** from the server.
+**
+** Parameters:
+** line -- the response line.
+** firstline -- set if this is the first line of the reply.
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope (unused).
+**
+** Returns:
+** none.
+*/
+
+static void getsasldata __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
+
+static void
+getsasldata(line, firstline, m, mci, e)
+ char *line;
+ bool firstline;
+ MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ int len;
+ int result;
+# if SASL < 20000
+ char *out;
+# endif /* SASL < 20000 */
+
+ /* if not a continue we don't care about it */
+ len = strlen(line);
+ if ((len <= 4) ||
+ (line[0] != '3') ||
+ !isascii(line[1]) || !isdigit(line[1]) ||
+ !isascii(line[2]) || !isdigit(line[2]))
+ {
+ SM_FREE_CLR(mci->mci_sasl_string);
+ return;
+ }
+
+ /* forget about "334 " */
+ line += 4;
+ len -= 4;
+# if SASL >= 20000
+ /* XXX put this into a macro/function? It's duplicated below */
+ if (mci->mci_sasl_string != NULL)
+ {
+ if (mci->mci_sasl_string_len <= len)
+ {
+ sm_free(mci->mci_sasl_string); /* XXX */
+ mci->mci_sasl_string = xalloc(len + 1);
+ }
+ }
+ else
+ mci->mci_sasl_string = xalloc(len + 1);
+
+ result = sasl_decode64(line, len, mci->mci_sasl_string, len + 1,
+ (unsigned int *) &mci->mci_sasl_string_len);
+ if (result != SASL_OK)
+ {
+ mci->mci_sasl_string_len = 0;
+ *mci->mci_sasl_string = '\0';
+ }
+# else /* SASL >= 20000 */
+ out = (char *) sm_rpool_malloc_x(mci->mci_rpool, len + 1);
+ result = sasl_decode64(line, len, out, (unsigned int *) &len);
+ if (result != SASL_OK)
+ {
+ len = 0;
+ *out = '\0';
+ }
+
+ /*
+ ** mci_sasl_string is "shared" with Cyrus-SASL library; hence
+ ** it can't be in an rpool unless we use the same memory
+ ** management mechanism (with same rpool!) for Cyrus SASL.
+ */
+
+ if (mci->mci_sasl_string != NULL)
+ {
+ if (mci->mci_sasl_string_len <= len)
+ {
+ sm_free(mci->mci_sasl_string); /* XXX */
+ mci->mci_sasl_string = xalloc(len + 1);
+ }
+ }
+ else
+ mci->mci_sasl_string = xalloc(len + 1);
+
+ memcpy(mci->mci_sasl_string, out, len);
+ mci->mci_sasl_string[len] = '\0';
+ mci->mci_sasl_string_len = len;
+# endif /* SASL >= 20000 */
+ return;
+}
+/*
+** READAUTH -- read auth values from a file
+**
+** Parameters:
+** filename -- name of file to read.
+** safe -- if set, this is a safe read.
+** sai -- where to store auth_info.
+** rpool -- resource pool for sai.
+**
+** Returns:
+** EX_OK -- data succesfully read.
+** EX_UNAVAILABLE -- no valid filename.
+** EX_TEMPFAIL -- temporary failure.
+*/
+
+static char *sasl_info_name[] =
+{
+ "user id",
+ "authentication id",
+ "password",
+ "realm",
+ "mechlist"
+};
+static int
+readauth(filename, safe, sai, rpool)
+ char *filename;
+ bool safe;
+ SASL_AI_T *sai;
+ SM_RPOOL_T *rpool;
+{
+ SM_FILE_T *f;
+ long sff;
+ pid_t pid;
+ int lc;
+ char *s;
+ char buf[MAXLINE];
+
+ if (filename == NULL || filename[0] == '\0')
+ return EX_UNAVAILABLE;
+
+#if !_FFR_ALLOW_SASLINFO
+ /*
+ ** make sure we don't use a program that is not
+ ** accesible to the user who specified a different authinfo file.
+ ** However, currently we don't pass this info (authinfo file
+ ** specified by user) around, so we just turn off program access.
+ */
+
+ if (filename[0] == '|')
+ {
+ auto int fd;
+ int i;
+ char *p;
+ char *argv[MAXPV + 1];
+
+ i = 0;
+ for (p = strtok(&filename[1], " \t"); p != NULL;
+ p = strtok(NULL, " \t"))
+ {
+ if (i >= MAXPV)
+ break;
+ argv[i++] = p;
+ }
+ argv[i] = NULL;
+ pid = prog_open(argv, &fd, CurEnv);
+ if (pid < 0)
+ f = NULL;
+ else
+ f = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &fd, SM_IO_RDONLY, NULL);
+ }
+ else
+#endif /* !_FFR_ALLOW_SASLINFO */
+ {
+ pid = -1;
+ sff = SFF_REGONLY|SFF_SAFEDIRPATH|SFF_NOWLINK
+ |SFF_NOGWFILES|SFF_NOWWFILES|SFF_NOWRFILES;
+# if _FFR_GROUPREADABLEAUTHINFOFILE
+ if (!bitnset(DBS_GROUPREADABLEAUTHINFOFILE, DontBlameSendmail))
+# endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
+ sff |= SFF_NOGRFILES;
+ if (DontLockReadFiles)
+ sff |= SFF_NOLOCK;
+
+#if _FFR_ALLOW_SASLINFO
+ /*
+ ** XXX: make sure we don't read or open files that are not
+ ** accesible to the user who specified a different authinfo
+ ** file.
+ */
+
+ sff |= SFF_MUSTOWN;
+#else /* _FFR_ALLOW_SASLINFO */
+ if (safe)
+ sff |= SFF_OPENASROOT;
+#endif /* _FFR_ALLOW_SASLINFO */
+
+ f = safefopen(filename, O_RDONLY, 0, sff);
+ }
+ if (f == NULL)
+ {
+ if (LogLevel > 5)
+ sm_syslog(LOG_ERR, NOQID,
+ "AUTH=client, error: can't open %s: %s",
+ filename, sm_errstring(errno));
+ return EX_TEMPFAIL;
+ }
+
+ lc = 0;
+ while (lc <= SASL_MECHLIST &&
+ sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof buf) != NULL)
+ {
+ if (buf[0] != '#')
+ {
+ (*sai)[lc] = sm_rpool_strdup_x(rpool, buf);
+ if ((s = strchr((*sai)[lc], '\n')) != NULL)
+ *s = '\0';
+ lc++;
+ }
+ }
+
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ if (pid > 0)
+ (void) waitfor(pid);
+ if (lc < SASL_PASSWORD)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "AUTH=client, error: can't read %s from %s",
+ sasl_info_name[lc + 1], filename);
+ return EX_TEMPFAIL;
+ }
+ return EX_OK;
+}
+
+/*
+** GETAUTH -- get authinfo from ruleset call
+**
+** {server_name}, {server_addr} must be set
+**
+** Parameters:
+** mci -- the mailer connection structure.
+** e -- the envelope (including the sender to specify).
+** sai -- pointer to authinfo (result).
+**
+** Returns:
+** EX_OK -- ruleset was succesfully called, data may not
+** be available, sai must be checked.
+** EX_UNAVAILABLE -- ruleset unavailable (or failed).
+** EX_TEMPFAIL -- temporary failure (from ruleset).
+**
+** Side Effects:
+** Fills in sai if successful.
+*/
+
+static int
+getauth(mci, e, sai)
+ MCI *mci;
+ ENVELOPE *e;
+ SASL_AI_T *sai;
+{
+ int i, r, l, got, ret;
+ char **pvp;
+ char pvpbuf[PSBUFSIZE];
+
+ r = rscap("authinfo", macvalue(macid("{server_name}"), e),
+ macvalue(macid("{server_addr}"), e), e,
+ &pvp, pvpbuf, sizeof(pvpbuf));
+
+ if (r != EX_OK)
+ return EX_UNAVAILABLE;
+
+ /* other than expected return value: ok (i.e., no auth) */
+ if (pvp == NULL || pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET)
+ return EX_OK;
+ if (pvp[1] != NULL && sm_strncasecmp(pvp[1], "temp", 4) == 0)
+ return EX_TEMPFAIL;
+
+ /*
+ ** parse the data, put it into sai
+ ** format: "TDstring" (including the '"' !)
+ ** where T is a tag: 'U', ...
+ ** D is a delimiter: ':' or '='
+ */
+
+ ret = EX_OK; /* default return value */
+ i = 0;
+ got = 0;
+ while (i < SASL_ENTRIES)
+ {
+ if (pvp[i + 1] == NULL)
+ break;
+ if (pvp[i + 1][0] != '"')
+ break;
+ switch (pvp[i + 1][1])
+ {
+ case 'U':
+ case 'u':
+ r = SASL_USER;
+ break;
+ case 'I':
+ case 'i':
+ r = SASL_AUTHID;
+ break;
+ case 'P':
+ case 'p':
+ r = SASL_PASSWORD;
+ break;
+ case 'R':
+ case 'r':
+ r = SASL_DEFREALM;
+ break;
+ case 'M':
+ case 'm':
+ r = SASL_MECHLIST;
+ break;
+ default:
+ goto fail;
+ }
+ l = strlen(pvp[i + 1]);
+
+ /* check syntax */
+ if (l <= 3 || pvp[i + 1][l - 1] != '"')
+ goto fail;
+
+ /* remove closing quote */
+ pvp[i + 1][l - 1] = '\0';
+
+ /* remove "TD and " */
+ l -= 4;
+ (*sai)[r] = (char *) sm_rpool_malloc(mci->mci_rpool, l + 1);
+ if ((*sai)[r] == NULL)
+ goto tempfail;
+ if (pvp[i + 1][2] == ':')
+ {
+ /* ':text' (just copy) */
+ (void) sm_strlcpy((*sai)[r], pvp[i + 1] + 3, l + 1);
+ got |= 1 << r;
+ }
+ else if (pvp[i + 1][2] == '=')
+ {
+ unsigned int len;
+
+ /* '=base64' (decode) */
+# if SASL >= 20000
+ ret = sasl_decode64(pvp[i + 1] + 3,
+ (unsigned int) l, (*sai)[r],
+ (unsigned int) l + 1, &len);
+# else /* SASL >= 20000 */
+ ret = sasl_decode64(pvp[i + 1] + 3,
+ (unsigned int) l, (*sai)[r], &len);
+# endif /* SASL >= 20000 */
+ if (ret != SASL_OK)
+ goto fail;
+ got |= 1 << r;
+ }
+ else
+ goto fail;
+ if (tTd(95, 5))
+ sm_syslog(LOG_DEBUG, NOQID, "getauth %s=%s",
+ sasl_info_name[r], (*sai)[r]);
+ ++i;
+ }
+
+ /* did we get the expected data? */
+ /* XXX: EXTERNAL mechanism only requires (and only uses) SASL_USER */
+ if (!(bitset(SASL_USER_BIT|SASL_AUTHID_BIT, got) &&
+ bitset(SASL_PASSWORD_BIT, got)))
+ goto fail;
+
+ /* no authid? copy uid */
+ if (!bitset(SASL_AUTHID_BIT, got))
+ {
+ l = strlen((*sai)[SASL_USER]) + 1;
+ (*sai)[SASL_AUTHID] = (char *) sm_rpool_malloc(mci->mci_rpool,
+ l + 1);
+ if ((*sai)[SASL_AUTHID] == NULL)
+ goto tempfail;
+ (void) sm_strlcpy((*sai)[SASL_AUTHID], (*sai)[SASL_USER], l);
+ }
+
+ /* no uid? copy authid */
+ if (!bitset(SASL_USER_BIT, got))
+ {
+ l = strlen((*sai)[SASL_AUTHID]) + 1;
+ (*sai)[SASL_USER] = (char *) sm_rpool_malloc(mci->mci_rpool,
+ l + 1);
+ if ((*sai)[SASL_USER] == NULL)
+ goto tempfail;
+ (void) sm_strlcpy((*sai)[SASL_USER], (*sai)[SASL_AUTHID], l);
+ }
+ return EX_OK;
+
+ tempfail:
+ ret = EX_TEMPFAIL;
+ fail:
+ if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, NOQID,
+ "AUTH=client, relay=%.64s [%.16s], authinfo %sfailed",
+ macvalue(macid("{server_name}"), e),
+ macvalue(macid("{server_addr}"), e),
+ ret == EX_TEMPFAIL ? "temp" : "");
+ for (i = 0; i <= SASL_MECHLIST; i++)
+ (*sai)[i] = NULL; /* just clear; rpool */
+ return ret;
+}
+
+# if SASL >= 20000
+/*
+** GETSIMPLE -- callback to get userid or authid
+**
+** Parameters:
+** context -- sai
+** id -- what to do
+** result -- (pointer to) result
+** len -- (pointer to) length of result
+**
+** Returns:
+** OK/failure values
+*/
+
+static int
+getsimple(context, id, result, len)
+ void *context;
+ int id;
+ const char **result;
+ unsigned *len;
+{
+ SASL_AI_T *sai;
+
+ if (result == NULL || context == NULL)
+ return SASL_BADPARAM;
+ sai = (SASL_AI_T *) context;
+
+ switch (id)
+ {
+ case SASL_CB_USER:
+ *result = (*sai)[SASL_USER];
+ if (tTd(95, 5))
+ sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'",
+ *result);
+ if (len != NULL)
+ *len = *result != NULL ? strlen(*result) : 0;
+ break;
+
+ case SASL_CB_AUTHNAME:
+ *result = (*sai)[SASL_AUTHID];
+ if (tTd(95, 5))
+ sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'",
+ *result);
+ if (len != NULL)
+ *len = *result != NULL ? strlen(*result) : 0;
+ break;
+
+ case SASL_CB_LANGUAGE:
+ *result = NULL;
+ if (len != NULL)
+ *len = 0;
+ break;
+
+ default:
+ return SASL_BADPARAM;
+ }
+ return SASL_OK;
+}
+/*
+** GETSECRET -- callback to get password
+**
+** Parameters:
+** conn -- connection information
+** context -- sai
+** id -- what to do
+** psecret -- (pointer to) result
+**
+** Returns:
+** OK/failure values
+*/
+
+static int
+getsecret(conn, context, id, psecret)
+ sasl_conn_t *conn;
+ SM_UNUSED(void *context);
+ int id;
+ sasl_secret_t **psecret;
+{
+ int len;
+ char *authpass;
+ MCI *mci;
+
+ if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ mci = (MCI *) context;
+ authpass = mci->mci_sai[SASL_PASSWORD];
+ len = strlen(authpass);
+
+ /*
+ ** use an rpool because we are responsible for free()ing the secret,
+ ** but we can't free() it until after the auth completes
+ */
+
+ *psecret = (sasl_secret_t *) sm_rpool_malloc(mci->mci_rpool,
+ sizeof(sasl_secret_t) +
+ len + 1);
+ if (*psecret == NULL)
+ return SASL_FAIL;
+ (void) sm_strlcpy((*psecret)->data, authpass, len + 1);
+ (*psecret)->len = (unsigned long) len;
+ return SASL_OK;
+}
+# else /* SASL >= 20000 */
+/*
+** GETSIMPLE -- callback to get userid or authid
+**
+** Parameters:
+** context -- sai
+** id -- what to do
+** result -- (pointer to) result
+** len -- (pointer to) length of result
+**
+** Returns:
+** OK/failure values
+*/
+
+static int
+getsimple(context, id, result, len)
+ void *context;
+ int id;
+ const char **result;
+ unsigned *len;
+{
+ char *h, *s;
+# if SASL > 10509
+ bool addrealm;
+# endif /* SASL > 10509 */
+ size_t l;
+ SASL_AI_T *sai;
+ char *authid = NULL;
+
+ if (result == NULL || context == NULL)
+ return SASL_BADPARAM;
+ sai = (SASL_AI_T *) context;
+
+ /*
+ ** Unfortunately it is not clear whether this routine should
+ ** return a copy of a string or just a pointer to a string.
+ ** The Cyrus-SASL plugins treat these return values differently, e.g.,
+ ** plugins/cram.c free()s authid, plugings/digestmd5.c does not.
+ ** The best solution to this problem is to fix Cyrus-SASL, but it
+ ** seems there is nobody who creates patches... Hello CMU!?
+ ** The second best solution is to have flags that tell this routine
+ ** whether to return an malloc()ed copy.
+ ** The next best solution is to always return an malloc()ed copy,
+ ** and suffer from some memory leak, which is ugly for persistent
+ ** queue runners.
+ ** For now we go with the last solution...
+ ** We can't use rpools (which would avoid this particular problem)
+ ** as explained in sasl.c.
+ */
+
+ switch (id)
+ {
+ case SASL_CB_USER:
+ l = strlen((*sai)[SASL_USER]) + 1;
+ s = sm_sasl_malloc(l);
+ if (s == NULL)
+ {
+ if (len != NULL)
+ *len = 0;
+ *result = NULL;
+ return SASL_NOMEM;
+ }
+ (void) sm_strlcpy(s, (*sai)[SASL_USER], l);
+ *result = s;
+ if (tTd(95, 5))
+ sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'",
+ *result);
+ if (len != NULL)
+ *len = *result != NULL ? strlen(*result) : 0;
+ break;
+
+ case SASL_CB_AUTHNAME:
+ h = (*sai)[SASL_AUTHID];
+# if SASL > 10509
+ /* XXX maybe other mechanisms too?! */
+ addrealm = (*sai)[SASL_MECH] != NULL &&
+ sm_strcasecmp((*sai)[SASL_MECH], "CRAM-MD5") == 0;
+
+ /*
+ ** Add realm to authentication id unless authid contains
+ ** '@' (i.e., a realm) or the default realm is empty.
+ */
+
+ if (addrealm && h != NULL && strchr(h, '@') == NULL)
+ {
+ /* has this been done before? */
+ if ((*sai)[SASL_ID_REALM] == NULL)
+ {
+ char *realm;
+
+ realm = (*sai)[SASL_DEFREALM];
+
+ /* do not add an empty realm */
+ if (*realm == '\0')
+ {
+ authid = h;
+ (*sai)[SASL_ID_REALM] = NULL;
+ }
+ else
+ {
+ l = strlen(h) + strlen(realm) + 2;
+
+ /* should use rpool, but from where? */
+ authid = sm_sasl_malloc(l);
+ if (authid != NULL)
+ {
+ (void) sm_snprintf(authid, l,
+ "%s@%s",
+ h, realm);
+ (*sai)[SASL_ID_REALM] = authid;
+ }
+ else
+ {
+ authid = h;
+ (*sai)[SASL_ID_REALM] = NULL;
+ }
+ }
+ }
+ else
+ authid = (*sai)[SASL_ID_REALM];
+ }
+ else
+# endif /* SASL > 10509 */
+ authid = h;
+ l = strlen(authid) + 1;
+ s = sm_sasl_malloc(l);
+ if (s == NULL)
+ {
+ if (len != NULL)
+ *len = 0;
+ *result = NULL;
+ return SASL_NOMEM;
+ }
+ (void) sm_strlcpy(s, authid, l);
+ *result = s;
+ if (tTd(95, 5))
+ sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'",
+ *result);
+ if (len != NULL)
+ *len = authid ? strlen(authid) : 0;
+ break;
+
+ case SASL_CB_LANGUAGE:
+ *result = NULL;
+ if (len != NULL)
+ *len = 0;
+ break;
+
+ default:
+ return SASL_BADPARAM;
+ }
+ return SASL_OK;
+}
+/*
+** GETSECRET -- callback to get password
+**
+** Parameters:
+** conn -- connection information
+** context -- sai
+** id -- what to do
+** psecret -- (pointer to) result
+**
+** Returns:
+** OK/failure values
+*/
+
+static int
+getsecret(conn, context, id, psecret)
+ sasl_conn_t *conn;
+ SM_UNUSED(void *context);
+ int id;
+ sasl_secret_t **psecret;
+{
+ int len;
+ char *authpass;
+ SASL_AI_T *sai;
+
+ if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ sai = (SASL_AI_T *) context;
+ authpass = (*sai)[SASL_PASSWORD];
+ len = strlen(authpass);
+ *psecret = (sasl_secret_t *) sm_sasl_malloc(sizeof(sasl_secret_t) +
+ len + 1);
+ if (*psecret == NULL)
+ return SASL_FAIL;
+ (void) sm_strlcpy((*psecret)->data, authpass, len + 1);
+ (*psecret)->len = (unsigned long) len;
+ return SASL_OK;
+}
+# endif /* SASL >= 20000 */
+
+/*
+** SAFESASLFILE -- callback for sasl: is file safe?
+**
+** Parameters:
+** context -- pointer to context between invocations (unused)
+** file -- name of file to check
+** type -- type of file to check
+**
+** Returns:
+** SASL_OK -- file can be used
+** SASL_CONTINUE -- don't use file
+** SASL_FAIL -- failure (not used here)
+**
+*/
+
+int
+#if SASL > 10515
+safesaslfile(context, file, type)
+#else /* SASL > 10515 */
+safesaslfile(context, file)
+#endif /* SASL > 10515 */
+ void *context;
+# if SASL >= 20000
+ const char *file;
+# else /* SASL >= 20000 */
+ char *file;
+# endif /* SASL >= 20000 */
+#if SASL > 10515
+# if SASL >= 20000
+ sasl_verify_type_t type;
+# else /* SASL >= 20000 */
+ int type;
+# endif /* SASL >= 20000 */
+#endif /* SASL > 10515 */
+{
+ long sff;
+ int r;
+#if SASL <= 10515
+ size_t len;
+#endif /* SASL <= 10515 */
+ char *p;
+
+ if (file == NULL || *file == '\0')
+ return SASL_OK;
+ sff = SFF_SAFEDIRPATH|SFF_NOWLINK|SFF_NOWWFILES|SFF_ROOTOK;
+#if SASL <= 10515
+ if ((p = strrchr(file, '/')) == NULL)
+ p = file;
+ else
+ ++p;
+
+ /* everything beside libs and .conf files must not be readable */
+ len = strlen(p);
+ if ((len <= 3 || strncmp(p, "lib", 3) != 0) &&
+ (len <= 5 || strncmp(p + len - 5, ".conf", 5) != 0))
+ {
+ if (!bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail))
+ sff |= SFF_NORFILES;
+ if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail))
+ sff |= SFF_NOGWFILES;
+ }
+#else /* SASL <= 10515 */
+ /* files containing passwords should be not readable */
+ if (type == SASL_VRFY_PASSWD)
+ {
+ if (bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail))
+ sff |= SFF_NOWRFILES;
+ else
+ sff |= SFF_NORFILES;
+ if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail))
+ sff |= SFF_NOGWFILES;
+ }
+#endif /* SASL <= 10515 */
+
+ p = (char *) file;
+ if ((r = safefile(p, RunAsUid, RunAsGid, RunAsUserName, sff,
+ S_IRUSR, NULL)) == 0)
+ return SASL_OK;
+ if (LogLevel > (r != ENOENT ? 8 : 10))
+ sm_syslog(LOG_WARNING, NOQID, "error: safesasl(%s) failed: %s",
+ p, sm_errstring(r));
+ return SASL_CONTINUE;
+}
+
+/*
+** SASLGETREALM -- return the realm for SASL
+**
+** return the realm for the client
+**
+** Parameters:
+** context -- context shared between invocations
+** availrealms -- list of available realms
+** {realm, realm, ...}
+** result -- pointer to result
+**
+** Returns:
+** failure/success
+*/
+
+static int
+saslgetrealm(context, id, availrealms, result)
+ void *context;
+ int id;
+ const char **availrealms;
+ const char **result;
+{
+ char *r;
+ SASL_AI_T *sai;
+
+ sai = (SASL_AI_T *) context;
+ if (sai == NULL)
+ return SASL_FAIL;
+ r = (*sai)[SASL_DEFREALM];
+
+ if (LogLevel > 12)
+ sm_syslog(LOG_INFO, NOQID,
+ "AUTH=client, realm=%s, available realms=%s",
+ r == NULL ? "<No Realm>" : r,
+ (availrealms == NULL || *availrealms == NULL)
+ ? "<No Realms>" : *availrealms);
+
+ /* check whether context is in list */
+ if (availrealms != NULL && *availrealms != NULL)
+ {
+ if (iteminlist(context, (char *)(*availrealms + 1), " ,}") ==
+ NULL)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "AUTH=client, realm=%s not in list=%s",
+ r, *availrealms);
+ return SASL_FAIL;
+ }
+ }
+ *result = r;
+ return SASL_OK;
+}
+/*
+** ITEMINLIST -- does item appear in list?
+**
+** Check whether item appears in list (which must be separated by a
+** character in delim) as a "word", i.e. it must appear at the begin
+** of the list or after a space, and it must end with a space or the
+** end of the list.
+**
+** Parameters:
+** item -- item to search.
+** list -- list of items.
+** delim -- list of delimiters.
+**
+** Returns:
+** pointer to occurrence (NULL if not found).
+*/
+
+char *
+iteminlist(item, list, delim)
+ char *item;
+ char *list;
+ char *delim;
+{
+ char *s;
+ int len;
+
+ if (list == NULL || *list == '\0')
+ return NULL;
+ if (item == NULL || *item == '\0')
+ return NULL;
+ s = list;
+ len = strlen(item);
+ while (s != NULL && *s != '\0')
+ {
+ if (sm_strncasecmp(s, item, len) == 0 &&
+ (s[len] == '\0' || strchr(delim, s[len]) != NULL))
+ return s;
+ s = strpbrk(s, delim);
+ if (s != NULL)
+ while (*++s == ' ')
+ continue;
+ }
+ return NULL;
+}
+/*
+** REMOVEMECH -- remove item [rem] from list [list]
+**
+** Parameters:
+** rem -- item to remove
+** list -- list of items
+** rpool -- resource pool from which result is allocated.
+**
+** Returns:
+** pointer to new list (NULL in case of error).
+*/
+
+static char *
+removemech(rem, list, rpool)
+ char *rem;
+ char *list;
+ SM_RPOOL_T *rpool;
+{
+ char *ret;
+ char *needle;
+ int len;
+
+ if (list == NULL)
+ return NULL;
+ if (rem == NULL || *rem == '\0')
+ {
+ /* take out what? */
+ return NULL;
+ }
+
+ /* find the item in the list */
+ if ((needle = iteminlist(rem, list, " ")) == NULL)
+ {
+ /* not in there: return original */
+ return list;
+ }
+
+ /* length of string without rem */
+ len = strlen(list) - strlen(rem);
+ if (len <= 0)
+ {
+ ret = (char *) sm_rpool_malloc_x(rpool, 1);
+ *ret = '\0';
+ return ret;
+ }
+ ret = (char *) sm_rpool_malloc_x(rpool, len);
+ memset(ret, '\0', len);
+
+ /* copy from start to removed item */
+ memcpy(ret, list, needle - list);
+
+ /* length of rest of string past removed item */
+ len = strlen(needle) - strlen(rem) - 1;
+ if (len > 0)
+ {
+ /* not last item -- copy into string */
+ memcpy(ret + (needle - list),
+ list + (needle - list) + strlen(rem) + 1,
+ len);
+ }
+ else
+ ret[(needle - list) - 1] = '\0';
+ return ret;
+}
+/*
+** ATTEMPTAUTH -- try to AUTHenticate using one mechanism
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection structure.
+** e -- the envelope (including the sender to specify).
+** sai - sasl authinfo
+**
+** Returns:
+** EX_OK -- authentication was successful.
+** EX_NOPERM -- authentication failed.
+** EX_IOERR -- authentication dialogue failed (I/O problem?).
+** EX_TEMPFAIL -- temporary failure.
+**
+*/
+
+static int
+attemptauth(m, mci, e, sai)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+ SASL_AI_T *sai;
+{
+ int saslresult, smtpresult;
+# if SASL >= 20000
+ sasl_ssf_t ssf;
+ const char *auth_id;
+ const char *out;
+# else /* SASL >= 20000 */
+ sasl_external_properties_t ssf;
+ char *out;
+# endif /* SASL >= 20000 */
+ unsigned int outlen;
+ sasl_interact_t *client_interact = NULL;
+ char *mechusing;
+ sasl_security_properties_t ssp;
+ char in64[MAXOUTLEN];
+#if NETINET || (NETINET6 && SASL >= 20000)
+ extern SOCKADDR CurHostAddr;
+#endif /* NETINET || (NETINET6 && SASL >= 20000) */
+
+ /* no mechanism selected (yet) */
+ (*sai)[SASL_MECH] = NULL;
+
+ /* dispose old connection */
+ if (mci->mci_conn != NULL)
+ sasl_dispose(&(mci->mci_conn));
+
+ /* make a new client sasl connection */
+# if SASL >= 20000
+ saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
+ : "smtp",
+ CurHostName, NULL, NULL, NULL, 0,
+ &mci->mci_conn);
+# else /* SASL >= 20000 */
+ saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
+ : "smtp",
+ CurHostName, NULL, 0, &mci->mci_conn);
+# endif /* SASL >= 20000 */
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+
+ /* set properties */
+ (void) memset(&ssp, '\0', sizeof ssp);
+
+ /* XXX should these be options settable via .cf ? */
+# if STARTTLS
+#endif /* STARTTLS */
+ {
+ ssp.max_ssf = MaxSLBits;
+ ssp.maxbufsize = MAXOUTLEN;
+# if 0
+ ssp.security_flags = SASL_SEC_NOPLAINTEXT;
+# endif /* 0 */
+ }
+ saslresult = sasl_setprop(mci->mci_conn, SASL_SEC_PROPS, &ssp);
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+
+# if SASL >= 20000
+ /* external security strength factor, authentication id */
+ ssf = 0;
+ auth_id = NULL;
+# if STARTTLS
+ out = macvalue(macid("{cert_subject}"), e);
+ if (out != NULL && *out != '\0')
+ auth_id = out;
+ out = macvalue(macid("{cipher_bits}"), e);
+ if (out != NULL && *out != '\0')
+ ssf = atoi(out);
+# endif /* STARTTLS */
+ saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+ saslresult = sasl_setprop(mci->mci_conn, SASL_AUTH_EXTERNAL, auth_id);
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+
+# if NETINET || NETINET6
+ /* set local/remote ipv4 addresses */
+ if (mci->mci_out != NULL && (
+# if NETINET6
+ CurHostAddr.sa.sa_family == AF_INET6 ||
+# endif /* NETINET6 */
+ CurHostAddr.sa.sa_family == AF_INET))
+ {
+ SOCKADDR_LEN_T addrsize;
+ SOCKADDR saddr_l;
+ char localip[60], remoteip[60];
+
+ switch (CurHostAddr.sa.sa_family)
+ {
+ case AF_INET:
+ addrsize = sizeof(struct sockaddr_in);
+ break;
+# if NETINET6
+ case AF_INET6:
+ addrsize = sizeof(struct sockaddr_in6);
+ break;
+# endif /* NETINET6 */
+ default:
+ break;
+ }
+ if (iptostring(&CurHostAddr, addrsize,
+ remoteip, sizeof remoteip))
+ {
+ if (sasl_setprop(mci->mci_conn, SASL_IPREMOTEPORT,
+ remoteip) != SASL_OK)
+ return EX_TEMPFAIL;
+ }
+ addrsize = sizeof(saddr_l);
+ if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *) &saddr_l, &addrsize) == 0)
+ {
+ if (iptostring(&saddr_l, addrsize,
+ localip, sizeof localip))
+ {
+ if (sasl_setprop(mci->mci_conn,
+ SASL_IPLOCALPORT,
+ localip) != SASL_OK)
+ return EX_TEMPFAIL;
+ }
+ }
+ }
+# endif /* NETINET || NETINET6 */
+
+ /* start client side of sasl */
+ saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
+ &client_interact,
+ &out, &outlen,
+ (const char **) &mechusing);
+# else /* SASL >= 20000 */
+ /* external security strength factor, authentication id */
+ ssf.ssf = 0;
+ ssf.auth_id = NULL;
+# if STARTTLS
+ out = macvalue(macid("{cert_subject}"), e);
+ if (out != NULL && *out != '\0')
+ ssf.auth_id = out;
+ out = macvalue(macid("{cipher_bits}"), e);
+ if (out != NULL && *out != '\0')
+ ssf.ssf = atoi(out);
+# endif /* STARTTLS */
+ saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
+ if (saslresult != SASL_OK)
+ return EX_TEMPFAIL;
+
+# if NETINET
+ /* set local/remote ipv4 addresses */
+ if (mci->mci_out != NULL && CurHostAddr.sa.sa_family == AF_INET)
+ {
+ SOCKADDR_LEN_T addrsize;
+ struct sockaddr_in saddr_l;
+
+ if (sasl_setprop(mci->mci_conn, SASL_IP_REMOTE,
+ (struct sockaddr_in *) &CurHostAddr)
+ != SASL_OK)
+ return EX_TEMPFAIL;
+ addrsize = sizeof(struct sockaddr_in);
+ if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD,
+ NULL),
+ (struct sockaddr *) &saddr_l, &addrsize) == 0)
+ {
+ if (sasl_setprop(mci->mci_conn, SASL_IP_LOCAL,
+ &saddr_l) != SASL_OK)
+ return EX_TEMPFAIL;
+ }
+ }
+# endif /* NETINET */
+
+ /* start client side of sasl */
+ saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
+ NULL, &client_interact,
+ &out, &outlen,
+ (const char **) &mechusing);
+# endif /* SASL >= 20000 */
+
+ if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
+ {
+ if (saslresult == SASL_NOMECH && LogLevel > 8)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "AUTH=client, available mechanisms do not fulfill requirements");
+ }
+ return EX_TEMPFAIL;
+ }
+
+ /* just point current mechanism to the data in the sasl library */
+ (*sai)[SASL_MECH] = mechusing;
+
+ /* send the info across the wire */
+ if (out == NULL
+#if _FFR_SASL_INITIAL_WORKAROUND
+ /* login and digest-md5 up to 1.5.28 set out="" */
+ || (outlen == 0 &&
+ (sm_strcasecmp(mechusing, "LOGIN") == 0 ||
+ sm_strcasecmp(mechusing, "DIGEST-MD5") == 0))
+#endif /* _FFR_SASL_INITIAL_WORKAROUND */
+ )
+ {
+ /* no initial response */
+ smtpmessage("AUTH %s", m, mci, mechusing);
+ }
+ else if (outlen == 0)
+ {
+ /*
+ ** zero-length initial response, per RFC 2554 4.:
+ ** "Unlike a zero-length client answer to a 334 reply, a zero-
+ ** length initial response is sent as a single equals sign"
+ */
+
+ smtpmessage("AUTH %s =", m, mci, mechusing);
+ }
+ else
+ {
+ saslresult = sasl_encode64(out, outlen, in64, MAXOUTLEN, NULL);
+ if (saslresult != SASL_OK) /* internal error */
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, e->e_id,
+ "encode64 for AUTH failed");
+ return EX_TEMPFAIL;
+ }
+ smtpmessage("AUTH %s %s", m, mci, mechusing, in64);
+ }
+# if SASL < 20000
+ sm_sasl_free(out); /* XXX only if no rpool is used */
+# endif /* SASL < 20000 */
+
+ /* get the reply */
+ smtpresult = reply(m, mci, e, TimeOuts.to_auth, getsasldata, NULL);
+
+ for (;;)
+ {
+ /* check return code from server */
+ if (smtpresult == 235)
+ {
+ macdefine(&mci->mci_macro, A_TEMP, macid("{auth_type}"),
+ mechusing);
+ return EX_OK;
+ }
+ if (smtpresult == -1)
+ return EX_IOERR;
+ if (REPLYTYPE(smtpresult) == 5)
+ return EX_NOPERM; /* ugly, but ... */
+ if (REPLYTYPE(smtpresult) != 3)
+ {
+ /* should we fail deliberately, see RFC 2554 4. ? */
+ /* smtpmessage("*", m, mci); */
+ return EX_TEMPFAIL;
+ }
+
+ saslresult = sasl_client_step(mci->mci_conn,
+ mci->mci_sasl_string,
+ mci->mci_sasl_string_len,
+ &client_interact,
+ &out, &outlen);
+
+ if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
+ {
+ if (tTd(95, 5))
+ sm_dprintf("AUTH FAIL=%s (%d)\n",
+ sasl_errstring(saslresult, NULL, NULL),
+ saslresult);
+
+ /* fail deliberately, see RFC 2554 4. */
+ smtpmessage("*", m, mci);
+
+ /*
+ ** but we should only fail for this authentication
+ ** mechanism; how to do that?
+ */
+
+ smtpresult = reply(m, mci, e, TimeOuts.to_auth,
+ getsasldata, NULL);
+ return EX_NOPERM;
+ }
+
+ if (outlen > 0)
+ {
+ saslresult = sasl_encode64(out, outlen, in64,
+ MAXOUTLEN, NULL);
+ if (saslresult != SASL_OK)
+ {
+ /* give an error reply to the other side! */
+ smtpmessage("*", m, mci);
+ return EX_TEMPFAIL;
+ }
+ }
+ else
+ in64[0] = '\0';
+# if SASL < 20000
+ sm_sasl_free(out); /* XXX only if no rpool is used */
+# endif /* SASL < 20000 */
+ smtpmessage("%s", m, mci, in64);
+ smtpresult = reply(m, mci, e, TimeOuts.to_auth,
+ getsasldata, NULL);
+ }
+ /* NOTREACHED */
+}
+/*
+** SMTPAUTH -- try to AUTHenticate
+**
+** This will try mechanisms in the order the sasl library decided until:
+** - there are no more mechanisms
+** - a mechanism succeeds
+** - the sasl library fails initializing
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection info.
+** e -- the envelope.
+**
+** Returns:
+** EX_OK -- authentication was successful
+** EX_UNAVAILABLE -- authentication not possible, e.g.,
+** no data available.
+** EX_NOPERM -- authentication failed.
+** EX_TEMPFAIL -- temporary failure.
+**
+** Notice: AuthInfo is used for all connections, hence we must
+** return EX_TEMPFAIL only if we really want to retry, i.e.,
+** iff getauth() tempfailed or getauth() was used and
+** authentication tempfailed.
+*/
+
+int
+smtpauth(m, mci, e)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+{
+ int result;
+ int i;
+ bool usedgetauth;
+
+ mci->mci_sasl_auth = false;
+ for (i = 0; i < SASL_MECH ; i++)
+ mci->mci_sai[i] = NULL;
+
+ result = getauth(mci, e, &(mci->mci_sai));
+ if (result == EX_TEMPFAIL)
+ return result;
+ usedgetauth = true;
+
+ /* no data available: don't try to authenticate */
+ if (result == EX_OK && mci->mci_sai[SASL_AUTHID] == NULL)
+ return result;
+ if (result != EX_OK)
+ {
+ if (SASLInfo == NULL)
+ return EX_UNAVAILABLE;
+
+ /* read authinfo from file */
+ result = readauth(SASLInfo, true, &(mci->mci_sai),
+ mci->mci_rpool);
+ if (result != EX_OK)
+ return result;
+ usedgetauth = false;
+ }
+
+ /* check whether sufficient data is available */
+ if (mci->mci_sai[SASL_PASSWORD] == NULL ||
+ *(mci->mci_sai)[SASL_PASSWORD] == '\0')
+ return EX_UNAVAILABLE;
+ if ((mci->mci_sai[SASL_AUTHID] == NULL ||
+ *(mci->mci_sai)[SASL_AUTHID] == '\0') &&
+ (mci->mci_sai[SASL_USER] == NULL ||
+ *(mci->mci_sai)[SASL_USER] == '\0'))
+ return EX_UNAVAILABLE;
+
+ /* set the context for the callback function to sai */
+# if SASL >= 20000
+ callbacks[CB_PASS_IDX].context = (void *) mci;
+# else /* SASL >= 20000 */
+ callbacks[CB_PASS_IDX].context = (void *) &mci->mci_sai;
+# endif /* SASL >= 20000 */
+ callbacks[CB_USER_IDX].context = (void *) &mci->mci_sai;
+ callbacks[CB_AUTHNAME_IDX].context = (void *) &mci->mci_sai;
+ callbacks[CB_GETREALM_IDX].context = (void *) &mci->mci_sai;
+#if 0
+ callbacks[CB_SAFESASL_IDX].context = (void *) &mci->mci_sai;
+#endif /* 0 */
+
+ /* set default value for realm */
+ if ((mci->mci_sai)[SASL_DEFREALM] == NULL)
+ (mci->mci_sai)[SASL_DEFREALM] = sm_rpool_strdup_x(e->e_rpool,
+ macvalue('j', CurEnv));
+
+ /* set default value for list of mechanism to use */
+ if ((mci->mci_sai)[SASL_MECHLIST] == NULL ||
+ *(mci->mci_sai)[SASL_MECHLIST] == '\0')
+ (mci->mci_sai)[SASL_MECHLIST] = AuthMechanisms;
+
+ /* create list of mechanisms to try */
+ mci->mci_saslcap = intersect((mci->mci_sai)[SASL_MECHLIST],
+ mci->mci_saslcap, mci->mci_rpool);
+
+ /* initialize sasl client library */
+ result = init_sasl_client();
+ if (result != SASL_OK)
+ return usedgetauth ? EX_TEMPFAIL : EX_UNAVAILABLE;
+ do
+ {
+ result = attemptauth(m, mci, e, &(mci->mci_sai));
+ if (result == EX_OK)
+ mci->mci_sasl_auth = true;
+ else if (result == EX_TEMPFAIL || result == EX_NOPERM)
+ {
+ mci->mci_saslcap = removemech((mci->mci_sai)[SASL_MECH],
+ mci->mci_saslcap,
+ mci->mci_rpool);
+ if (mci->mci_saslcap == NULL ||
+ *(mci->mci_saslcap) == '\0')
+ return usedgetauth ? result
+ : EX_UNAVAILABLE;
+ }
+ else
+ return result;
+ } while (result != EX_OK);
+ return result;
+}
+#endif /* SASL */
+
+/*
+** SMTPMAILFROM -- send MAIL command
+**
+** Parameters:
+** m -- the mailer.
+** mci -- the mailer connection structure.
+** e -- the envelope (including the sender to specify).
+*/
+
+int
+smtpmailfrom(m, mci, e)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+{
+ int r;
+ char *bufp;
+ char *bodytype;
+ char *enhsc;
+ char buf[MAXNAME + 1];
+ char optbuf[MAXLINE];
+
+ if (tTd(18, 2))
+ sm_dprintf("smtpmailfrom: CurHost=%s\n", CurHostName);
+ enhsc = NULL;
+
+ /*
+ ** Check if connection is gone, if so
+ ** it's a tempfail and we use mci_errno
+ ** for the reason.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+
+ /* set up appropriate options to include */
+ if (bitset(MCIF_SIZE, mci->mci_flags) && e->e_msgsize > 0)
+ {
+ (void) sm_snprintf(optbuf, sizeof optbuf, " SIZE=%ld",
+ e->e_msgsize);
+ bufp = &optbuf[strlen(optbuf)];
+ }
+ else
+ {
+ optbuf[0] = '\0';
+ bufp = optbuf;
+ }
+
+ bodytype = e->e_bodytype;
+ if (bitset(MCIF_8BITMIME, mci->mci_flags))
+ {
+ if (bodytype == NULL &&
+ bitset(MM_MIME8BIT, MimeMode) &&
+ bitset(EF_HAS8BIT, e->e_flags) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ !bitnset(M_8BITS, m->m_flags))
+ bodytype = "8BITMIME";
+ if (bodytype != NULL &&
+ SPACELEFT(optbuf, bufp) > strlen(bodytype) + 7)
+ {
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " BODY=%s", bodytype);
+ bufp += strlen(bufp);
+ }
+ }
+ else if (bitnset(M_8BITS, m->m_flags) ||
+ !bitset(EF_HAS8BIT, e->e_flags) ||
+ bitset(MCIF_8BITOK, mci->mci_flags))
+ {
+ /* EMPTY */
+ /* just pass it through */
+ }
+#if MIME8TO7
+ else if (bitset(MM_CVTMIME, MimeMode) &&
+ !bitset(EF_DONT_MIME, e->e_flags) &&
+ (!bitset(MM_PASS8BIT, MimeMode) ||
+ bitset(EF_IS_MIME, e->e_flags)))
+ {
+ /* must convert from 8bit MIME format to 7bit encoded */
+ mci->mci_flags |= MCIF_CVT8TO7;
+ }
+#endif /* MIME8TO7 */
+ else if (!bitset(MM_PASS8BIT, MimeMode))
+ {
+ /* cannot just send a 8-bit version */
+ extern char MsgBuf[];
+
+ usrerrenh("5.6.3", "%s does not support 8BITMIME", CurHostName);
+ mci_setstat(mci, EX_NOTSTICKY, "5.6.3", MsgBuf);
+ return EX_DATAERR;
+ }
+
+ if (bitset(MCIF_DSN, mci->mci_flags))
+ {
+ if (e->e_envid != NULL &&
+ SPACELEFT(optbuf, bufp) > strlen(e->e_envid) + 7)
+ {
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " ENVID=%s", e->e_envid);
+ bufp += strlen(bufp);
+ }
+
+ /* RET= parameter */
+ if (bitset(EF_RET_PARAM, e->e_flags) &&
+ SPACELEFT(optbuf, bufp) > 9)
+ {
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " RET=%s",
+ bitset(EF_NO_BODY_RETN, e->e_flags) ?
+ "HDRS" : "FULL");
+ bufp += strlen(bufp);
+ }
+ }
+
+ if (bitset(MCIF_AUTH, mci->mci_flags) && e->e_auth_param != NULL &&
+ SPACELEFT(optbuf, bufp) > strlen(e->e_auth_param) + 7
+#if SASL
+ && (!bitset(SASL_AUTH_AUTH, SASLOpts) || mci->mci_sasl_auth)
+#endif /* SASL */
+ )
+ {
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " AUTH=%s", e->e_auth_param);
+ bufp += strlen(bufp);
+ }
+
+ /*
+ ** 17 is the max length required, we could use log() to compute
+ ** the exact length (and check IS_DLVR_TRACE())
+ */
+
+ if (bitset(MCIF_DLVR_BY, mci->mci_flags) &&
+ IS_DLVR_BY(e) && SPACELEFT(optbuf, bufp) > 17)
+ {
+ long dby;
+
+ /*
+ ** Avoid problems with delays (for R) since the check
+ ** in deliver() whether min-deliver-time is sufficient.
+ ** Alternatively we could pass the computed time to this
+ ** function.
+ */
+
+ dby = e->e_deliver_by - (curtime() - e->e_ctime);
+ if (dby <= 0 && IS_DLVR_RETURN(e))
+ dby = mci->mci_min_by <= 0 ? 1 : mci->mci_min_by;
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " BY=%ld;%c%s",
+ dby,
+ IS_DLVR_RETURN(e) ? 'R' : 'N',
+ IS_DLVR_TRACE(e) ? "T" : "");
+ bufp += strlen(bufp);
+ }
+
+ /*
+ ** Send the MAIL command.
+ ** Designates the sender.
+ */
+
+ mci->mci_state = MCIS_MAIL;
+
+ if (bitset(EF_RESPONSE, e->e_flags) &&
+ !bitnset(M_NO_NULL_FROM, m->m_flags))
+ buf[0] = '\0';
+ else
+ expand("\201g", buf, sizeof buf, e);
+ if (buf[0] == '<')
+ {
+ /* strip off <angle brackets> (put back on below) */
+ bufp = &buf[strlen(buf) - 1];
+ if (*bufp == '>')
+ *bufp = '\0';
+ bufp = &buf[1];
+ }
+ else
+ bufp = buf;
+ if (bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) ||
+ !bitnset(M_FROMPATH, m->m_flags))
+ {
+ smtpmessage("MAIL From:<%s>%s", m, mci, bufp, optbuf);
+ }
+ else
+ {
+ smtpmessage("MAIL From:<@%s%c%s>%s", m, mci, MyHostName,
+ *bufp == '@' ? ',' : ':', bufp, optbuf);
+ }
+ SmtpPhase = mci->mci_phase = "client MAIL";
+ sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_mail, NULL, &enhsc);
+ if (r < 0)
+ {
+ /* communications failure */
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
+ return EX_TEMPFAIL;
+ }
+ else if (r == SMTPCLOSING)
+ {
+ /* service shutting down: handled by reply() */
+ return EX_TEMPFAIL;
+ }
+ else if (REPLYTYPE(r) == 4)
+ {
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, smtptodsn(r)),
+ SmtpReplyBuffer);
+ return EX_TEMPFAIL;
+ }
+ else if (REPLYTYPE(r) == 2)
+ {
+ return EX_OK;
+ }
+ else if (r == 501)
+ {
+ /* syntax error in arguments */
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.5.2"),
+ SmtpReplyBuffer);
+ return EX_DATAERR;
+ }
+ else if (r == 553)
+ {
+ /* mailbox name not allowed */
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.1.3"),
+ SmtpReplyBuffer);
+ return EX_DATAERR;
+ }
+ else if (r == 552)
+ {
+ /* exceeded storage allocation */
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.3.4"),
+ SmtpReplyBuffer);
+ if (bitset(MCIF_SIZE, mci->mci_flags))
+ e->e_flags |= EF_NO_BODY_RETN;
+ return EX_UNAVAILABLE;
+ }
+ else if (REPLYTYPE(r) == 5)
+ {
+ /* unknown error */
+ mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.0.0"),
+ SmtpReplyBuffer);
+ return EX_UNAVAILABLE;
+ }
+
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP MAIL protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
+ }
+
+ /* protocol error -- close up */
+ mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
+ SmtpReplyBuffer);
+ smtpquit(m, mci, e);
+ return EX_PROTOCOL;
+}
+/*
+** SMTPRCPT -- designate recipient.
+**
+** Parameters:
+** to -- address of recipient.
+** m -- the mailer we are sending to.
+** mci -- the connection info for this transaction.
+** e -- the envelope for this transaction.
+**
+** Returns:
+** exit status corresponding to recipient status.
+**
+** Side Effects:
+** Sends the mail via SMTP.
+*/
+
+int
+smtprcpt(to, m, mci, e, ctladdr, xstart)
+ ADDRESS *to;
+ register MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+ ADDRESS *ctladdr;
+ time_t xstart;
+{
+ char *bufp;
+ char optbuf[MAXLINE];
+
+#if PIPELINING
+ /*
+ ** If there is status waiting from the other end, read it.
+ ** This should normally happen because of SMTP pipelining.
+ */
+
+ while (mci->mci_nextaddr != NULL &&
+ sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL))
+ {
+ int r;
+
+ r = smtprcptstat(mci->mci_nextaddr, m, mci, e);
+ if (r != EX_OK)
+ {
+ markfailure(e, mci->mci_nextaddr, mci, r, false);
+ giveresponse(r, mci->mci_nextaddr->q_status, m, mci,
+ ctladdr, xstart, e, to);
+ }
+ mci->mci_nextaddr = mci->mci_nextaddr->q_pchain;
+ }
+#endif /* PIPELINING */
+
+ /*
+ ** Check if connection is gone, if so
+ ** it's a tempfail and we use mci_errno
+ ** for the reason.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+
+ optbuf[0] = '\0';
+ bufp = optbuf;
+
+ /*
+ ** Warning: in the following it is assumed that the free space
+ ** in bufp is sizeof optbuf
+ */
+
+ if (bitset(MCIF_DSN, mci->mci_flags))
+ {
+ if (IS_DLVR_NOTIFY(e) &&
+ !bitset(MCIF_DLVR_BY, mci->mci_flags))
+ {
+ /* RFC 2852: 4.1.4.2 */
+ if (!bitset(QHASNOTIFY, to->q_flags))
+ to->q_flags |= QPINGONFAILURE|QPINGONDELAY|QHASNOTIFY;
+ else if (bitset(QPINGONSUCCESS, to->q_flags) ||
+ bitset(QPINGONFAILURE, to->q_flags) ||
+ bitset(QPINGONDELAY, to->q_flags))
+ to->q_flags |= QPINGONDELAY;
+ }
+
+ /* NOTIFY= parameter */
+ if (bitset(QHASNOTIFY, to->q_flags) &&
+ bitset(QPRIMARY, to->q_flags) &&
+ !bitnset(M_LOCALMAILER, m->m_flags))
+ {
+ bool firstone = true;
+
+ (void) sm_strlcat(bufp, " NOTIFY=", sizeof optbuf);
+ if (bitset(QPINGONSUCCESS, to->q_flags))
+ {
+ (void) sm_strlcat(bufp, "SUCCESS", sizeof optbuf);
+ firstone = false;
+ }
+ if (bitset(QPINGONFAILURE, to->q_flags))
+ {
+ if (!firstone)
+ (void) sm_strlcat(bufp, ",",
+ sizeof optbuf);
+ (void) sm_strlcat(bufp, "FAILURE", sizeof optbuf);
+ firstone = false;
+ }
+ if (bitset(QPINGONDELAY, to->q_flags))
+ {
+ if (!firstone)
+ (void) sm_strlcat(bufp, ",",
+ sizeof optbuf);
+ (void) sm_strlcat(bufp, "DELAY", sizeof optbuf);
+ firstone = false;
+ }
+ if (firstone)
+ (void) sm_strlcat(bufp, "NEVER", sizeof optbuf);
+ bufp += strlen(bufp);
+ }
+
+ /* ORCPT= parameter */
+ if (to->q_orcpt != NULL &&
+ SPACELEFT(optbuf, bufp) > strlen(to->q_orcpt) + 7)
+ {
+ (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
+ " ORCPT=%s", to->q_orcpt);
+ bufp += strlen(bufp);
+ }
+ }
+
+ smtpmessage("RCPT To:<%s>%s", m, mci, to->q_user, optbuf);
+ mci->mci_state = MCIS_RCPT;
+
+ SmtpPhase = mci->mci_phase = "client RCPT";
+ sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+
+#if PIPELINING
+ /*
+ ** If running SMTP pipelining, we will pick up status later
+ */
+
+ if (bitset(MCIF_PIPELINED, mci->mci_flags))
+ return EX_OK;
+#endif /* PIPELINING */
+
+ return smtprcptstat(to, m, mci, e);
+}
+/*
+** SMTPRCPTSTAT -- get recipient status
+**
+** This is only called during SMTP pipelining
+**
+** Parameters:
+** to -- address of recipient.
+** m -- mailer being sent to.
+** mci -- the mailer connection information.
+** e -- the envelope for this message.
+**
+** Returns:
+** EX_* -- protocol status
+*/
+
+static int
+smtprcptstat(to, m, mci, e)
+ ADDRESS *to;
+ MAILER *m;
+ register MCI *mci;
+ register ENVELOPE *e;
+{
+ int r;
+ int save_errno;
+ char *enhsc;
+
+ /*
+ ** Check if connection is gone, if so
+ ** it's a tempfail and we use mci_errno
+ ** for the reason.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+
+ enhsc = NULL;
+ r = reply(m, mci, e, TimeOuts.to_rcpt, NULL, &enhsc);
+ save_errno = errno;
+ to->q_rstatus = sm_rpool_strdup_x(e->e_rpool, SmtpReplyBuffer);
+ to->q_status = ENHSCN_RPOOL(enhsc, smtptodsn(r), e->e_rpool);
+ if (!bitnset(M_LMTP, m->m_flags))
+ to->q_statmta = mci->mci_host;
+ if (r < 0 || REPLYTYPE(r) == 4)
+ {
+ mci->mci_retryrcpt = true;
+ errno = save_errno;
+ return EX_TEMPFAIL;
+ }
+ else if (REPLYTYPE(r) == 2)
+ {
+ char *t;
+
+ if ((t = mci->mci_tolist) != NULL)
+ {
+ char *p;
+
+ *t++ = ',';
+ for (p = to->q_paddr; *p != '\0'; *t++ = *p++)
+ continue;
+ *t = '\0';
+ mci->mci_tolist = t;
+ }
+#if PIPELINING
+ mci->mci_okrcpts++;
+#endif /* PIPELINING */
+ return EX_OK;
+ }
+ else if (r == 550)
+ {
+ to->q_status = ENHSCN_RPOOL(enhsc, "5.1.1", e->e_rpool);
+ return EX_NOUSER;
+ }
+ else if (r == 551)
+ {
+ to->q_status = ENHSCN_RPOOL(enhsc, "5.1.6", e->e_rpool);
+ return EX_NOUSER;
+ }
+ else if (r == 553)
+ {
+ to->q_status = ENHSCN_RPOOL(enhsc, "5.1.3", e->e_rpool);
+ return EX_NOUSER;
+ }
+ else if (REPLYTYPE(r) == 5)
+ {
+ return EX_UNAVAILABLE;
+ }
+
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP RCPT protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
+ }
+
+ mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
+ SmtpReplyBuffer);
+ return EX_PROTOCOL;
+}
+/*
+** SMTPDATA -- send the data and clean up the transaction.
+**
+** Parameters:
+** m -- mailer being sent to.
+** mci -- the mailer connection information.
+** e -- the envelope for this message.
+**
+** Returns:
+** exit status corresponding to DATA command.
+*/
+
+static jmp_buf CtxDataTimeout;
+static SM_EVENT *volatile DataTimeout = NULL;
+
+int
+smtpdata(m, mci, e, ctladdr, xstart)
+ MAILER *m;
+ register MCI *mci;
+ register ENVELOPE *e;
+ ADDRESS *ctladdr;
+ time_t xstart;
+{
+ register int r;
+ int rstat;
+ int xstat;
+ time_t timeout;
+ char *enhsc;
+
+ /*
+ ** Check if connection is gone, if so
+ ** it's a tempfail and we use mci_errno
+ ** for the reason.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+
+ enhsc = NULL;
+
+ /*
+ ** Send the data.
+ ** First send the command and check that it is ok.
+ ** Then send the data (if there are valid recipients).
+ ** Follow it up with a dot to terminate.
+ ** Finally get the results of the transaction.
+ */
+
+ /* send the command and check ok to proceed */
+ smtpmessage("DATA", m, mci);
+
+#if PIPELINING
+ if (mci->mci_nextaddr != NULL)
+ {
+ char *oldto = e->e_to;
+
+ /* pick up any pending RCPT responses for SMTP pipelining */
+ while (mci->mci_nextaddr != NULL)
+ {
+ int r;
+
+ e->e_to = mci->mci_nextaddr->q_paddr;
+ r = smtprcptstat(mci->mci_nextaddr, m, mci, e);
+ if (r != EX_OK)
+ {
+ markfailure(e, mci->mci_nextaddr, mci, r,
+ false);
+ giveresponse(r, mci->mci_nextaddr->q_status, m,
+ mci, ctladdr, xstart, e,
+ mci->mci_nextaddr);
+ if (r == EX_TEMPFAIL)
+ mci->mci_nextaddr->q_state = QS_RETRY;
+ }
+ mci->mci_nextaddr = mci->mci_nextaddr->q_pchain;
+ }
+ e->e_to = oldto;
+ }
+#endif /* PIPELINING */
+
+ /* now proceed with DATA phase */
+ SmtpPhase = mci->mci_phase = "client DATA 354";
+ mci->mci_state = MCIS_DATA;
+ sm_setproctitle(true, e, "%s %s: %s",
+ qid_printname(e), CurHostName, mci->mci_phase);
+ r = reply(m, mci, e, TimeOuts.to_datainit, NULL, &enhsc);
+ if (r < 0 || REPLYTYPE(r) == 4)
+ {
+ if (r >= 0)
+ smtpquit(m, mci, e);
+ errno = mci->mci_errno;
+ return EX_TEMPFAIL;
+ }
+ else if (REPLYTYPE(r) == 5)
+ {
+ smtprset(m, mci, e);
+#if PIPELINING
+ if (mci->mci_okrcpts <= 0)
+ return mci->mci_retryrcpt ? EX_TEMPFAIL
+ : EX_UNAVAILABLE;
+#endif /* PIPELINING */
+ return EX_UNAVAILABLE;
+ }
+ else if (REPLYTYPE(r) != 3)
+ {
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP DATA-1 protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
+ }
+ smtprset(m, mci, e);
+ mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
+ SmtpReplyBuffer);
+#if PIPELINING
+ if (mci->mci_okrcpts <= 0)
+ return mci->mci_retryrcpt ? EX_TEMPFAIL
+ : EX_PROTOCOL;
+#endif /* PIPELINING */
+ return EX_PROTOCOL;
+ }
+
+#if PIPELINING
+ if (mci->mci_okrcpts > 0)
+ {
+#endif /* PIPELINING */
+
+ /*
+ ** Set timeout around data writes. Make it at least large
+ ** enough for DNS timeouts on all recipients plus some fudge
+ ** factor. The main thing is that it should not be infinite.
+ */
+
+ if (setjmp(CtxDataTimeout) != 0)
+ {
+ mci->mci_errno = errno;
+ mci->mci_state = MCIS_ERROR;
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
+
+ /*
+ ** If putbody() couldn't finish due to a timeout,
+ ** rewind it here in the timeout handler. See
+ ** comments at the end of putbody() for reasoning.
+ */
+
+ if (e->e_dfp != NULL)
+ (void) bfrewind(e->e_dfp);
+
+ errno = mci->mci_errno;
+ syserr("451 4.4.1 timeout writing message to %s", CurHostName);
+ smtpquit(m, mci, e);
+ return EX_TEMPFAIL;
+ }
+
+ if (tTd(18, 101))
+ {
+ /* simulate a DATA timeout */
+ timeout = 1;
+ }
+ else
+ timeout = DATA_PROGRESS_TIMEOUT;
+
+ DataTimeout = sm_setevent(timeout, datatimeout, 0);
+
+
+ /*
+ ** Output the actual message.
+ */
+
+ (*e->e_puthdr)(mci, e->e_header, e, M87F_OUTER);
+
+ if (tTd(18, 101))
+ {
+ /* simulate a DATA timeout */
+ (void) sleep(2);
+ }
+
+ (*e->e_putbody)(mci, e, NULL);
+
+ /*
+ ** Cleanup after sending message.
+ */
+
+ if (DataTimeout != NULL)
+ sm_clrevent(DataTimeout);
+
+#if PIPELINING
+ }
+#endif /* PIPELINING */
+
+#if _FFR_CATCH_BROKEN_MTAS
+ if (sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL))
+ {
+ /* terminate the message */
+ (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, ".%s",
+ m->m_eol);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d >>> .\n", (int) CurrentPid);
+ if (Verbose)
+ nmessage(">>> .");
+
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP DATA-1 protocol error: remote server returned response before final dot",
+ CurHostName);
+ mci->mci_errno = EIO;
+ mci->mci_state = MCIS_ERROR;
+ mci_setstat(mci, EX_PROTOCOL, "5.5.0", NULL);
+ smtpquit(m, mci, e);
+ return EX_PROTOCOL;
+ }
+#endif /* _FFR_CATCH_BROKEN_MTAS */
+
+ if (sm_io_error(mci->mci_out))
+ {
+ /* error during processing -- don't send the dot */
+ mci->mci_errno = EIO;
+ mci->mci_state = MCIS_ERROR;
+ mci_setstat(mci, EX_IOERR, "4.4.2", NULL);
+ smtpquit(m, mci, e);
+ return EX_IOERR;
+ }
+
+ /* terminate the message */
+ (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, ".%s", m->m_eol);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d >>> .\n", (int) CurrentPid);
+ if (Verbose)
+ nmessage(">>> .");
+
+ /* check for the results of the transaction */
+ SmtpPhase = mci->mci_phase = "client DATA status";
+ sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
+ CurHostName, mci->mci_phase);
+ if (bitnset(M_LMTP, m->m_flags))
+ return EX_OK;
+ r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc);
+ if (r < 0)
+ return EX_TEMPFAIL;
+ mci->mci_state = MCIS_OPEN;
+ xstat = EX_NOTSTICKY;
+ if (r == 452)
+ rstat = EX_TEMPFAIL;
+ else if (REPLYTYPE(r) == 4)
+ rstat = xstat = EX_TEMPFAIL;
+ else if (REPLYTYPE(r) == 2)
+ rstat = xstat = EX_OK;
+ else if (REPLYCLASS(r) != 5)
+ rstat = xstat = EX_PROTOCOL;
+ else if (REPLYTYPE(r) == 5)
+ rstat = EX_UNAVAILABLE;
+ else
+ rstat = EX_PROTOCOL;
+ mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)),
+ SmtpReplyBuffer);
+ if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
+ (r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
+ r += 5;
+ else
+ r = 4;
+ e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[r]);
+ SmtpPhase = mci->mci_phase = "idle";
+ sm_setproctitle(true, e, "%s: %s", CurHostName, mci->mci_phase);
+ if (rstat != EX_PROTOCOL)
+ return rstat;
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP DATA-2 protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
+ }
+ return rstat;
+}
+
+static void
+datatimeout()
+{
+ int save_errno = errno;
+
+ /*
+ ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+ ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+ ** DOING.
+ */
+
+ if (DataProgress)
+ {
+ time_t timeout;
+
+ /* check back again later */
+ if (tTd(18, 101))
+ {
+ /* simulate a DATA timeout */
+ timeout = 1;
+ }
+ else
+ timeout = DATA_PROGRESS_TIMEOUT;
+
+ /* reset the timeout */
+ DataTimeout = sm_sigsafe_setevent(timeout, datatimeout, 0);
+ DataProgress = false;
+ }
+ else
+ {
+ /* event is done */
+ DataTimeout = NULL;
+ }
+
+ /* if no progress was made or problem resetting event, die now */
+ if (DataTimeout == NULL)
+ {
+ errno = ETIMEDOUT;
+ longjmp(CtxDataTimeout, 1);
+ }
+ errno = save_errno;
+}
+/*
+** SMTPGETSTAT -- get status code from DATA in LMTP
+**
+** Parameters:
+** m -- the mailer to which we are sending the message.
+** mci -- the mailer connection structure.
+** e -- the current envelope.
+**
+** Returns:
+** The exit status corresponding to the reply code.
+*/
+
+int
+smtpgetstat(m, mci, e)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+{
+ int r;
+ int status, xstat;
+ char *enhsc;
+
+ enhsc = NULL;
+
+ /* check for the results of the transaction */
+ r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc);
+ if (r < 0)
+ return EX_TEMPFAIL;
+ xstat = EX_NOTSTICKY;
+ if (REPLYTYPE(r) == 4)
+ status = EX_TEMPFAIL;
+ else if (REPLYTYPE(r) == 2)
+ status = xstat = EX_OK;
+ else if (REPLYCLASS(r) != 5)
+ status = xstat = EX_PROTOCOL;
+ else if (REPLYTYPE(r) == 5)
+ status = EX_UNAVAILABLE;
+ else
+ status = EX_PROTOCOL;
+ if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
+ (r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
+ r += 5;
+ else
+ r = 4;
+ e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[r]);
+ mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)),
+ SmtpReplyBuffer);
+ if (LogLevel > 1 && status == EX_PROTOCOL)
+ {
+ sm_syslog(LOG_CRIT, e->e_id,
+ "%.100s: SMTP DATA-3 protocol error: %s",
+ CurHostName,
+ shortenstring(SmtpReplyBuffer, 403));
+ }
+ return status;
+}
+/*
+** SMTPQUIT -- close the SMTP connection.
+**
+** Parameters:
+** m -- a pointer to the mailer.
+** mci -- the mailer connection information.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sends the final protocol and closes the connection.
+*/
+
+void
+smtpquit(m, mci, e)
+ register MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ bool oldSuprErrs = SuprErrs;
+ int rcode;
+ char *oldcurhost;
+
+ if (mci->mci_state == MCIS_CLOSED)
+ return;
+
+ oldcurhost = CurHostName;
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
+
+#if PIPELINING
+ mci->mci_okrcpts = 0;
+#endif /* PIPELINING */
+
+ /*
+ ** Suppress errors here -- we may be processing a different
+ ** job when we do the quit connection, and we don't want the
+ ** new job to be penalized for something that isn't it's
+ ** problem.
+ */
+
+ SuprErrs = true;
+
+ /* send the quit message if we haven't gotten I/O error */
+ if (mci->mci_state != MCIS_ERROR &&
+ mci->mci_state != MCIS_QUITING)
+ {
+ SmtpPhase = "client QUIT";
+ mci->mci_state = MCIS_QUITING;
+ smtpmessage("QUIT", m, mci);
+ (void) reply(m, mci, e, TimeOuts.to_quit, NULL, NULL);
+ SuprErrs = oldSuprErrs;
+ if (mci->mci_state == MCIS_CLOSED)
+ goto end;
+ }
+
+ /* now actually close the connection and pick up the zombie */
+ rcode = endmailer(mci, e, NULL);
+ if (rcode != EX_OK)
+ {
+ char *mailer = NULL;
+
+ if (mci->mci_mailer != NULL &&
+ mci->mci_mailer->m_name != NULL)
+ mailer = mci->mci_mailer->m_name;
+
+ /* look for naughty mailers */
+ sm_syslog(LOG_ERR, e->e_id,
+ "smtpquit: mailer%s%s exited with exit value %d",
+ mailer == NULL ? "" : " ",
+ mailer == NULL ? "" : mailer,
+ rcode);
+ }
+
+ SuprErrs = oldSuprErrs;
+
+ end:
+ CurHostName = oldcurhost;
+ return;
+}
+/*
+** SMTPRSET -- send a RSET (reset) command
+**
+** Parameters:
+** m -- a pointer to the mailer.
+** mci -- the mailer connection information.
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** closes the connection if there is no reply to RSET.
+*/
+
+void
+smtprset(m, mci, e)
+ register MAILER *m;
+ register MCI *mci;
+ ENVELOPE *e;
+{
+ int r;
+
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
+
+#if PIPELINING
+ mci->mci_okrcpts = 0;
+#endif /* PIPELINING */
+
+ /*
+ ** Check if connection is gone, if so
+ ** it's a tempfail and we use mci_errno
+ ** for the reason.
+ */
+
+ if (mci->mci_state == MCIS_CLOSED)
+ {
+ errno = mci->mci_errno;
+ return;
+ }
+
+ SmtpPhase = "client RSET";
+ smtpmessage("RSET", m, mci);
+ r = reply(m, mci, e, TimeOuts.to_rset, NULL, NULL);
+ if (r < 0)
+ return;
+
+ /*
+ ** Any response is deemed to be acceptable.
+ ** The standard does not state the proper action
+ ** to take when a value other than 250 is received.
+ **
+ ** However, if 421 is returned for the RSET, leave
+ ** mci_state as MCIS_SSD (set in reply()).
+ */
+
+ if (mci->mci_state != MCIS_SSD)
+ mci->mci_state = MCIS_OPEN;
+}
+/*
+** SMTPPROBE -- check the connection state
+**
+** Parameters:
+** mci -- the mailer connection information.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** closes the connection if there is no reply to RSET.
+*/
+
+int
+smtpprobe(mci)
+ register MCI *mci;
+{
+ int r;
+ MAILER *m = mci->mci_mailer;
+ ENVELOPE *e;
+ extern ENVELOPE BlankEnvelope;
+
+ CurHostName = mci->mci_host; /* XXX UGLY XXX */
+ if (CurHostName == NULL)
+ CurHostName = MyHostName;
+
+ e = &BlankEnvelope;
+ SmtpPhase = "client probe";
+ smtpmessage("RSET", m, mci);
+ r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, NULL);
+ if (REPLYTYPE(r) != 2)
+ smtpquit(m, mci, e);
+ return r;
+}
+/*
+** REPLY -- read arpanet reply
+**
+** Parameters:
+** m -- the mailer we are reading the reply from.
+** mci -- the mailer connection info structure.
+** e -- the current envelope.
+** timeout -- the timeout for reads.
+** pfunc -- processing function called on each line of response.
+** If null, no special processing is done.
+** enhstat -- optional, returns enhanced error code string (if set)
+**
+** Returns:
+** reply code it reads.
+**
+** Side Effects:
+** flushes the mail file.
+*/
+
+int
+reply(m, mci, e, timeout, pfunc, enhstat)
+ MAILER *m;
+ MCI *mci;
+ ENVELOPE *e;
+ time_t timeout;
+ void (*pfunc)();
+ char **enhstat;
+{
+ register char *bufp;
+ register int r;
+ bool firstline = true;
+ char junkbuf[MAXLINE];
+ static char enhstatcode[ENHSCLEN];
+ int save_errno;
+
+ /*
+ ** Flush the output before reading response.
+ **
+ ** For SMTP pipelining, it would be better if we didn't do
+ ** this if there was already data waiting to be read. But
+ ** to do it properly means pushing it to the I/O library,
+ ** since it really needs to be done below the buffer layer.
+ */
+
+ if (mci->mci_out != NULL)
+ (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
+
+ if (tTd(18, 1))
+ sm_dprintf("reply\n");
+
+ /*
+ ** Read the input line, being careful not to hang.
+ */
+
+ bufp = SmtpReplyBuffer;
+ for (;;)
+ {
+ register char *p;
+
+ /* actually do the read */
+ if (e->e_xfp != NULL) /* for debugging */
+ (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
+
+ /* if we are in the process of closing just give the code */
+ if (mci->mci_state == MCIS_CLOSED)
+ return SMTPCLOSING;
+
+ /* don't try to read from a non-existant fd */
+ if (mci->mci_in == NULL)
+ {
+ if (mci->mci_errno == 0)
+ mci->mci_errno = EBADF;
+
+ /* errors on QUIT should be ignored */
+ if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0)
+ {
+ errno = mci->mci_errno;
+ return -1;
+ }
+ mci->mci_state = MCIS_ERROR;
+ smtpquit(m, mci, e);
+ errno = mci->mci_errno;
+ return -1;
+ }
+
+ if (mci->mci_out != NULL)
+ (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
+
+ /* get the line from the other side */
+ p = sfgets(bufp, MAXLINE, mci->mci_in, timeout, SmtpPhase);
+ save_errno = errno;
+ mci->mci_lastuse = curtime();
+
+ if (p == NULL)
+ {
+ bool oldholderrs;
+ extern char MsgBuf[];
+
+ /* errors on QUIT should be ignored */
+ if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0)
+ return -1;
+
+ /* if the remote end closed early, fake an error */
+ errno = save_errno;
+ if (errno == 0)
+ {
+ (void) sm_snprintf(SmtpReplyBuffer,
+ sizeof SmtpReplyBuffer,
+ "421 4.4.1 Connection reset by %s",
+ CURHOSTNAME);
+#ifdef ECONNRESET
+ errno = ECONNRESET;
+#else /* ECONNRESET */
+ errno = EPIPE;
+#endif /* ECONNRESET */
+ }
+
+ mci->mci_errno = errno;
+ oldholderrs = HoldErrs;
+ HoldErrs = true;
+ usrerr("451 4.4.1 reply: read error from %s",
+ CURHOSTNAME);
+ mci_setstat(mci, EX_TEMPFAIL, "4.4.2", MsgBuf);
+
+ /* if debugging, pause so we can see state */
+ if (tTd(18, 100))
+ (void) pause();
+ mci->mci_state = MCIS_ERROR;
+ smtpquit(m, mci, e);
+#if XDEBUG
+ {
+ char wbuf[MAXLINE];
+
+ p = wbuf;
+ if (e->e_to != NULL)
+ {
+ (void) sm_snprintf(p,
+ SPACELEFT(wbuf, p),
+ "%s... ",
+ shortenstring(e->e_to, MAXSHORTSTR));
+ p += strlen(p);
+ }
+ (void) sm_snprintf(p, SPACELEFT(wbuf, p),
+ "reply(%.100s) during %s",
+ CURHOSTNAME, SmtpPhase);
+ checkfd012(wbuf);
+ }
+#endif /* XDEBUG */
+ HoldErrs = oldholderrs;
+ errno = save_errno;
+ return -1;
+ }
+ fixcrlf(bufp, true);
+
+ /* EHLO failure is not a real error */
+ if (e->e_xfp != NULL && (bufp[0] == '4' ||
+ (bufp[0] == '5' && strncmp(SmtpMsgBuffer, "EHLO", 4) != 0)))
+ {
+ /* serious error -- log the previous command */
+ if (SmtpNeedIntro)
+ {
+ /* inform user who we are chatting with */
+ (void) sm_io_fprintf(CurEnv->e_xfp,
+ SM_TIME_DEFAULT,
+ "... while talking to %s:\n",
+ CURHOSTNAME);
+ SmtpNeedIntro = false;
+ }
+ if (SmtpMsgBuffer[0] != '\0')
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ ">>> %s\n", SmtpMsgBuffer);
+ SmtpMsgBuffer[0] = '\0';
+
+ /* now log the message as from the other side */
+ (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
+ "<<< %s\n", bufp);
+ }
+
+ /* display the input for verbose mode */
+ if (Verbose)
+ nmessage("050 %s", bufp);
+
+ /* ignore improperly formatted input */
+ if (!ISSMTPREPLY(bufp))
+ continue;
+
+ if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
+ enhstat != NULL &&
+ extenhsc(bufp + 4, ' ', enhstatcode) > 0)
+ *enhstat = enhstatcode;
+
+ /* process the line */
+ if (pfunc != NULL)
+ (*pfunc)(bufp, firstline, m, mci, e);
+
+ firstline = false;
+
+ /* decode the reply code */
+ r = atoi(bufp);
+
+ /* extra semantics: 0xx codes are "informational" */
+ if (r < 100)
+ continue;
+
+ /* if no continuation lines, return this line */
+ if (bufp[3] != '-')
+ break;
+
+ /* first line of real reply -- ignore rest */
+ bufp = junkbuf;
+ }
+
+ /*
+ ** Now look at SmtpReplyBuffer -- only care about the first
+ ** line of the response from here on out.
+ */
+
+ /* save temporary failure messages for posterity */
+ if (SmtpReplyBuffer[0] == '4')
+ (void) sm_strlcpy(SmtpError, SmtpReplyBuffer, sizeof SmtpError);
+
+ /* reply code 421 is "Service Shutting Down" */
+ if (r == SMTPCLOSING && mci->mci_state != MCIS_SSD &&
+ mci->mci_state != MCIS_QUITING)
+ {
+ /* send the quit protocol */
+ mci->mci_state = MCIS_SSD;
+ smtpquit(m, mci, e);
+ }
+
+ return r;
+}
+/*
+** SMTPMESSAGE -- send message to server
+**
+** Parameters:
+** f -- format
+** m -- the mailer to control formatting.
+** a, b, c -- parameters
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** writes message to mci->mci_out.
+*/
+
+/*VARARGS1*/
+void
+#ifdef __STDC__
+smtpmessage(char *f, MAILER *m, MCI *mci, ...)
+#else /* __STDC__ */
+smtpmessage(f, m, mci, va_alist)
+ char *f;
+ MAILER *m;
+ MCI *mci;
+ va_dcl
+#endif /* __STDC__ */
+{
+ SM_VA_LOCAL_DECL
+
+ SM_VA_START(ap, mci);
+ (void) sm_vsnprintf(SmtpMsgBuffer, sizeof SmtpMsgBuffer, f, ap);
+ SM_VA_END(ap);
+
+ if (tTd(18, 1) || Verbose)
+ nmessage(">>> %s", SmtpMsgBuffer);
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d >>> %s\n", (int) CurrentPid,
+ SmtpMsgBuffer);
+ if (mci->mci_out != NULL)
+ {
+ (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, "%s%s",
+ SmtpMsgBuffer, m == NULL ? "\r\n"
+ : m->m_eol);
+ }
+ else if (tTd(18, 1))
+ {
+ sm_dprintf("smtpmessage: NULL mci_out\n");
+ }
+}
diff --git a/contrib/sendmail/src/util.c b/contrib/sendmail/src/util.c
new file mode 100644
index 0000000..52b37ec
--- /dev/null
+++ b/contrib/sendmail/src/util.c
@@ -0,0 +1,2743 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+
+SM_RCSID("@(#)$Id: util.c,v 8.363.2.1 2002/06/21 20:25:25 ca Exp $")
+
+#include <sysexits.h>
+#include <sm/xtrap.h>
+
+/*
+** ADDQUOTES -- Adds quotes & quote bits to a string.
+**
+** Runs through a string and adds backslashes and quote bits.
+**
+** Parameters:
+** s -- the string to modify.
+** rpool -- resource pool from which to allocate result
+**
+** Returns:
+** pointer to quoted string.
+*/
+
+char *
+addquotes(s, rpool)
+ char *s;
+ SM_RPOOL_T *rpool;
+{
+ int len = 0;
+ char c;
+ char *p = s, *q, *r;
+
+ if (s == NULL)
+ return NULL;
+
+ /* Find length of quoted string */
+ while ((c = *p++) != '\0')
+ {
+ len++;
+ if (c == '\\' || c == '"')
+ len++;
+ }
+
+ q = r = sm_rpool_malloc_x(rpool, len + 3);
+ p = s;
+
+ /* add leading quote */
+ *q++ = '"';
+ while ((c = *p++) != '\0')
+ {
+ /* quote \ or " */
+ if (c == '\\' || c == '"')
+ *q++ = '\\';
+ *q++ = c;
+ }
+ *q++ = '"';
+ *q = '\0';
+ return r;
+}
+/*
+** RFC822_STRING -- Checks string for proper RFC822 string quoting.
+**
+** Runs through a string and verifies RFC822 special characters
+** are only found inside comments, quoted strings, or backslash
+** escaped. Also verified balanced quotes and parenthesis.
+**
+** Parameters:
+** s -- the string to modify.
+**
+** Returns:
+** true iff the string is RFC822 compliant, false otherwise.
+*/
+
+bool
+rfc822_string(s)
+ char *s;
+{
+ bool quoted = false;
+ int commentlev = 0;
+ char *c = s;
+
+ if (s == NULL)
+ return false;
+
+ while (*c != '\0')
+ {
+ /* escaped character */
+ if (*c == '\\')
+ {
+ c++;
+ if (*c == '\0')
+ return false;
+ }
+ else if (commentlev == 0 && *c == '"')
+ quoted = !quoted;
+ else if (!quoted)
+ {
+ if (*c == ')')
+ {
+ /* unbalanced ')' */
+ if (commentlev == 0)
+ return false;
+ else
+ commentlev--;
+ }
+ else if (*c == '(')
+ commentlev++;
+ else if (commentlev == 0 &&
+ strchr(MustQuoteChars, *c) != NULL)
+ return false;
+ }
+ c++;
+ }
+
+ /* unbalanced '"' or '(' */
+ return !quoted && commentlev == 0;
+}
+/*
+** SHORTEN_RFC822_STRING -- Truncate and rebalance an RFC822 string
+**
+** Arbitrarily shorten (in place) an RFC822 string and rebalance
+** comments and quotes.
+**
+** Parameters:
+** string -- the string to shorten
+** length -- the maximum size, 0 if no maximum
+**
+** Returns:
+** true if string is changed, false otherwise
+**
+** Side Effects:
+** Changes string in place, possibly resulting
+** in a shorter string.
+*/
+
+bool
+shorten_rfc822_string(string, length)
+ char *string;
+ size_t length;
+{
+ bool backslash = false;
+ bool modified = false;
+ bool quoted = false;
+ size_t slen;
+ int parencount = 0;
+ char *ptr = string;
+
+ /*
+ ** If have to rebalance an already short enough string,
+ ** need to do it within allocated space.
+ */
+
+ slen = strlen(string);
+ if (length == 0 || slen < length)
+ length = slen;
+
+ while (*ptr != '\0')
+ {
+ if (backslash)
+ {
+ backslash = false;
+ goto increment;
+ }
+
+ if (*ptr == '\\')
+ backslash = true;
+ else if (*ptr == '(')
+ {
+ if (!quoted)
+ parencount++;
+ }
+ else if (*ptr == ')')
+ {
+ if (--parencount < 0)
+ parencount = 0;
+ }
+
+ /* Inside a comment, quotes don't matter */
+ if (parencount <= 0 && *ptr == '"')
+ quoted = !quoted;
+
+increment:
+ /* Check for sufficient space for next character */
+ if (length - (ptr - string) <= (size_t) ((backslash ? 1 : 0) +
+ parencount +
+ (quoted ? 1 : 0)))
+ {
+ /* Not enough, backtrack */
+ if (*ptr == '\\')
+ backslash = false;
+ else if (*ptr == '(' && !quoted)
+ parencount--;
+ else if (*ptr == '"' && parencount == 0)
+ quoted = false;
+ break;
+ }
+ ptr++;
+ }
+
+ /* Rebalance */
+ while (parencount-- > 0)
+ {
+ if (*ptr != ')')
+ {
+ modified = true;
+ *ptr = ')';
+ }
+ ptr++;
+ }
+ if (quoted)
+ {
+ if (*ptr != '"')
+ {
+ modified = true;
+ *ptr = '"';
+ }
+ ptr++;
+ }
+ if (*ptr != '\0')
+ {
+ modified = true;
+ *ptr = '\0';
+ }
+ return modified;
+}
+/*
+** FIND_CHARACTER -- find an unquoted character in an RFC822 string
+**
+** Find an unquoted, non-commented character in an RFC822
+** string and return a pointer to its location in the
+** string.
+**
+** Parameters:
+** string -- the string to search
+** character -- the character to find
+**
+** Returns:
+** pointer to the character, or
+** a pointer to the end of the line if character is not found
+*/
+
+char *
+find_character(string, character)
+ char *string;
+ int character;
+{
+ bool backslash = false;
+ bool quoted = false;
+ int parencount = 0;
+
+ while (string != NULL && *string != '\0')
+ {
+ if (backslash)
+ {
+ backslash = false;
+ if (!quoted && character == '\\' && *string == '\\')
+ break;
+ string++;
+ continue;
+ }
+ switch (*string)
+ {
+ case '\\':
+ backslash = true;
+ break;
+
+ case '(':
+ if (!quoted)
+ parencount++;
+ break;
+
+ case ')':
+ if (--parencount < 0)
+ parencount = 0;
+ break;
+ }
+
+ /* Inside a comment, nothing matters */
+ if (parencount > 0)
+ {
+ string++;
+ continue;
+ }
+
+ if (*string == '"')
+ quoted = !quoted;
+ else if (*string == character && !quoted)
+ break;
+ string++;
+ }
+
+ /* Return pointer to the character */
+ return string;
+}
+
+/*
+** CHECK_BODYTYPE -- check bodytype parameter
+**
+** Parameters:
+** bodytype -- bodytype parameter
+**
+** Returns:
+** BODYTYPE_* according to parameter
+**
+*/
+
+int
+check_bodytype(bodytype)
+ char *bodytype;
+{
+ /* check body type for legality */
+ if (bodytype == NULL)
+ return BODYTYPE_NONE;
+ if (sm_strcasecmp(bodytype, "7BIT") == 0)
+ return BODYTYPE_7BIT;
+ if (sm_strcasecmp(bodytype, "8BITMIME") == 0)
+ return BODYTYPE_8BITMIME;
+ return BODYTYPE_ILLEGAL;
+}
+
+#if _FFR_BESTMX_BETTER_TRUNCATION || _FFR_DNSMAP_MULTI
+/*
+** TRUNCATE_AT_DELIM -- truncate string at a delimiter and append "..."
+**
+** Parameters:
+** str -- string to truncate
+** len -- maximum length (including '\0') (0 for unlimited)
+** delim -- delimiter character
+**
+** Returns:
+** None.
+*/
+
+void
+truncate_at_delim(str, len, delim)
+ char *str;
+ size_t len;
+ int delim;
+{
+ char *p;
+
+ if (str == NULL || len == 0 || strlen(str) < len)
+ return;
+
+ *(str + len - 1) = '\0';
+ while ((p = strrchr(str, delim)) != NULL)
+ {
+ *p = '\0';
+ if (p - str + 4 < len)
+ {
+ *p++ = ':';
+ *p = '\0';
+ (void) sm_strlcat(str, "...", len);
+ return;
+ }
+ }
+
+ /* Couldn't find a place to append "..." */
+ if (len > 3)
+ (void) sm_strlcpy(str, "...", len);
+ else
+ str[0] = '\0';
+}
+#endif /* _FFR_BESTMX_BETTER_TRUNCATION || _FFR_DNSMAP_MULTI */
+/*
+** XALLOC -- Allocate memory, raise an exception on error
+**
+** Parameters:
+** sz -- size of area to allocate.
+**
+** Returns:
+** pointer to data region.
+**
+** Exceptions:
+** SmHeapOutOfMemory (F:sm.heap) -- cannot allocate memory
+**
+** Side Effects:
+** Memory is allocated.
+*/
+
+char *
+#if SM_HEAP_CHECK
+xalloc_tagged(sz, file, line)
+ register int sz;
+ char *file;
+ int line;
+#else /* SM_HEAP_CHECK */
+xalloc(sz)
+ register int sz;
+#endif /* SM_HEAP_CHECK */
+{
+ register char *p;
+
+ /* some systems can't handle size zero mallocs */
+ if (sz <= 0)
+ sz = 1;
+
+ /* scaffolding for testing error handling code */
+ sm_xtrap_raise_x(&SmHeapOutOfMemory);
+
+ p = sm_malloc_tagged((unsigned) sz, file, line, sm_heap_group());
+ if (p == NULL)
+ {
+ sm_exc_raise_x(&SmHeapOutOfMemory);
+ }
+ return p;
+}
+/*
+** COPYPLIST -- copy list of pointers.
+**
+** This routine is the equivalent of strdup for lists of
+** pointers.
+**
+** Parameters:
+** list -- list of pointers to copy.
+** Must be NULL terminated.
+** copycont -- if true, copy the contents of the vector
+** (which must be a string) also.
+** rpool -- resource pool from which to allocate storage,
+** or NULL
+**
+** Returns:
+** a copy of 'list'.
+*/
+
+char **
+copyplist(list, copycont, rpool)
+ char **list;
+ bool copycont;
+ SM_RPOOL_T *rpool;
+{
+ register char **vp;
+ register char **newvp;
+
+ for (vp = list; *vp != NULL; vp++)
+ continue;
+
+ vp++;
+
+ newvp = (char **) sm_rpool_malloc_x(rpool, (vp - list) * sizeof *vp);
+ memmove((char *) newvp, (char *) list, (int) (vp - list) * sizeof *vp);
+
+ if (copycont)
+ {
+ for (vp = newvp; *vp != NULL; vp++)
+ *vp = sm_rpool_strdup_x(rpool, *vp);
+ }
+
+ return newvp;
+}
+/*
+** COPYQUEUE -- copy address queue.
+**
+** This routine is the equivalent of strdup for address queues;
+** addresses marked as QS_IS_DEAD() aren't copied
+**
+** Parameters:
+** addr -- list of address structures to copy.
+** rpool -- resource pool from which to allocate storage
+**
+** Returns:
+** a copy of 'addr'.
+*/
+
+ADDRESS *
+copyqueue(addr, rpool)
+ ADDRESS *addr;
+ SM_RPOOL_T *rpool;
+{
+ register ADDRESS *newaddr;
+ ADDRESS *ret;
+ register ADDRESS **tail = &ret;
+
+ while (addr != NULL)
+ {
+ if (!QS_IS_DEAD(addr->q_state))
+ {
+ newaddr = (ADDRESS *) sm_rpool_malloc_x(rpool,
+ sizeof *newaddr);
+ STRUCTCOPY(*addr, *newaddr);
+ *tail = newaddr;
+ tail = &newaddr->q_next;
+ }
+ addr = addr->q_next;
+ }
+ *tail = NULL;
+
+ return ret;
+}
+/*
+** LOG_SENDMAIL_PID -- record sendmail pid and command line.
+**
+** Parameters:
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** writes pidfile, logs command line.
+*/
+
+void
+log_sendmail_pid(e)
+ ENVELOPE *e;
+{
+ long sff;
+ SM_FILE_T *pidf;
+ char pidpath[MAXPATHLEN];
+ extern char *CommandLineArgs;
+
+ /* write the pid to the log file for posterity */
+ sff = SFF_NOLINK|SFF_ROOTOK|SFF_REGONLY|SFF_CREAT;
+ if (TrustedUid != 0 && RealUid == TrustedUid)
+ sff |= SFF_OPENASROOT;
+ expand(PidFile, pidpath, sizeof pidpath, e);
+ pidf = safefopen(pidpath, O_WRONLY|O_TRUNC, FileMode, sff);
+ if (pidf == NULL)
+ {
+ sm_syslog(LOG_ERR, NOQID, "unable to write %s: %s",
+ pidpath, sm_errstring(errno));
+ }
+ else
+ {
+ pid_t pid;
+
+ pid = getpid();
+
+ /* write the process id on line 1 */
+ (void) sm_io_fprintf(pidf, SM_TIME_DEFAULT, "%ld\n",
+ (long) pid);
+
+ /* line 2 contains all command line flags */
+ (void) sm_io_fprintf(pidf, SM_TIME_DEFAULT, "%s\n",
+ CommandLineArgs);
+
+ /* flush and close */
+ (void) sm_io_close(pidf, SM_TIME_DEFAULT);
+ }
+ if (LogLevel > 9)
+ sm_syslog(LOG_INFO, NOQID, "started as: %s", CommandLineArgs);
+}
+/*
+** SET_DELIVERY_MODE -- set and record the delivery mode
+**
+** Parameters:
+** mode -- delivery mode
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets {deliveryMode} macro
+*/
+
+void
+set_delivery_mode(mode, e)
+ int mode;
+ ENVELOPE *e;
+{
+ char buf[2];
+
+ e->e_sendmode = (char) mode;
+ buf[0] = (char) mode;
+ buf[1] = '\0';
+ macdefine(&e->e_macro, A_TEMP, macid("{deliveryMode}"), buf);
+}
+/*
+** SET_OP_MODE -- set and record the op mode
+**
+** Parameters:
+** mode -- op mode
+** e -- the current envelope.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets {opMode} macro
+*/
+
+void
+set_op_mode(mode)
+ int mode;
+{
+ char buf[2];
+ extern ENVELOPE BlankEnvelope;
+
+ OpMode = (char) mode;
+ buf[0] = (char) mode;
+ buf[1] = '\0';
+ macdefine(&BlankEnvelope.e_macro, A_TEMP, MID_OPMODE, buf);
+}
+/*
+** PRINTAV -- print argument vector.
+**
+** Parameters:
+** av -- argument vector.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** prints av.
+*/
+
+void
+printav(av)
+ register char **av;
+{
+ while (*av != NULL)
+ {
+ if (tTd(0, 44))
+ sm_dprintf("\n\t%08lx=", (unsigned long) *av);
+ else
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, ' ');
+ xputs(*av++);
+ }
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, '\n');
+}
+/*
+** XPUTS -- put string doing control escapes.
+**
+** Parameters:
+** s -- string to put.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** output to stdout
+*/
+
+void
+xputs(s)
+ register const char *s;
+{
+ register int c;
+ register struct metamac *mp;
+ bool shiftout = false;
+ extern struct metamac MetaMacros[];
+ static SM_DEBUG_T DebugANSI = SM_DEBUG_INITIALIZER("ANSI",
+ "@(#)$Debug: ANSI - enable reverse video in debug output $");
+
+ /*
+ ** TermEscape is set here, rather than in main(),
+ ** because ANSI mode can be turned on or off at any time
+ ** if we are in -bt rule testing mode.
+ */
+
+ if (sm_debug_unknown(&DebugANSI))
+ {
+ if (sm_debug_active(&DebugANSI, 1))
+ {
+ TermEscape.te_rv_on = "\033[7m";
+ TermEscape.te_rv_off = "\033[0m";
+ }
+ else
+ {
+ TermEscape.te_rv_on = "";
+ TermEscape.te_rv_off = "";
+ }
+ }
+
+ if (s == NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s<null>%s",
+ TermEscape.te_rv_on, TermEscape.te_rv_off);
+ return;
+ }
+ while ((c = (*s++ & 0377)) != '\0')
+ {
+ if (shiftout)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s",
+ TermEscape.te_rv_off);
+ shiftout = false;
+ }
+ if (!isascii(c))
+ {
+ if (c == MATCHREPL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s$",
+ TermEscape.te_rv_on);
+ shiftout = true;
+ if (*s == '\0')
+ continue;
+ c = *s++ & 0377;
+ goto printchar;
+ }
+ if (c == MACROEXPAND || c == MACRODEXPAND)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s$",
+ TermEscape.te_rv_on);
+ if (c == MACRODEXPAND)
+ (void) sm_io_putc(smioout,
+ SM_TIME_DEFAULT, '&');
+ shiftout = true;
+ if (*s == '\0')
+ continue;
+ if (strchr("=~&?", *s) != NULL)
+ (void) sm_io_putc(smioout,
+ SM_TIME_DEFAULT,
+ *s++);
+ if (bitset(0200, *s))
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "{%s}",
+ macname(bitidx(*s++)));
+ else
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%c",
+ *s++);
+ continue;
+ }
+ for (mp = MetaMacros; mp->metaname != '\0'; mp++)
+ {
+ if (bitidx(mp->metaval) == c)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s$%c",
+ TermEscape.te_rv_on,
+ mp->metaname);
+ shiftout = true;
+ break;
+ }
+ }
+ if (c == MATCHCLASS || c == MATCHNCLASS)
+ {
+ if (bitset(0200, *s))
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "{%s}",
+ macname(bitidx(*s++)));
+ else if (*s != '\0')
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%c",
+ *s++);
+ }
+ if (mp->metaname != '\0')
+ continue;
+
+ /* unrecognized meta character */
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%sM-",
+ TermEscape.te_rv_on);
+ shiftout = true;
+ c &= 0177;
+ }
+ printchar:
+ if (isprint(c))
+ {
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ continue;
+ }
+
+ /* wasn't a meta-macro -- find another way to print it */
+ switch (c)
+ {
+ case '\n':
+ c = 'n';
+ break;
+
+ case '\r':
+ c = 'r';
+ break;
+
+ case '\t':
+ c = 't';
+ break;
+ }
+ if (!shiftout)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s",
+ TermEscape.te_rv_on);
+ shiftout = true;
+ }
+ if (isprint(c))
+ {
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, '\\');
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ }
+ else
+ {
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, '^');
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c ^ 0100);
+ }
+ }
+ if (shiftout)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s",
+ TermEscape.te_rv_off);
+ (void) sm_io_flush(smioout, SM_TIME_DEFAULT);
+}
+/*
+** MAKELOWER -- Translate a line into lower case
+**
+** Parameters:
+** p -- the string to translate. If NULL, return is
+** immediate.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** String pointed to by p is translated to lower case.
+*/
+
+void
+makelower(p)
+ register char *p;
+{
+ register char c;
+
+ if (p == NULL)
+ return;
+ for (; (c = *p) != '\0'; p++)
+ if (isascii(c) && isupper(c))
+ *p = tolower(c);
+}
+/*
+** FIXCRLF -- fix <CR><LF> in line.
+**
+** Looks for the <CR><LF> combination and turns it into the
+** UNIX canonical <NL> character. It only takes one line,
+** i.e., it is assumed that the first <NL> found is the end
+** of the line.
+**
+** Parameters:
+** line -- the line to fix.
+** stripnl -- if true, strip the newline also.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** line is changed in place.
+*/
+
+void
+fixcrlf(line, stripnl)
+ char *line;
+ bool stripnl;
+{
+ register char *p;
+
+ p = strchr(line, '\n');
+ if (p == NULL)
+ return;
+ if (p > line && p[-1] == '\r')
+ p--;
+ if (!stripnl)
+ *p++ = '\n';
+ *p = '\0';
+}
+/*
+** PUTLINE -- put a line like fputs obeying SMTP conventions
+**
+** This routine always guarantees outputing a newline (or CRLF,
+** as appropriate) at the end of the string.
+**
+** Parameters:
+** l -- line to put.
+** mci -- the mailer connection information.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** output of l to mci->mci_out.
+*/
+
+void
+putline(l, mci)
+ register char *l;
+ register MCI *mci;
+{
+ putxline(l, strlen(l), mci, PXLF_MAPFROM);
+}
+/*
+** PUTXLINE -- putline with flags bits.
+**
+** This routine always guarantees outputing a newline (or CRLF,
+** as appropriate) at the end of the string.
+**
+** Parameters:
+** l -- line to put.
+** len -- the length of the line.
+** mci -- the mailer connection information.
+** pxflags -- flag bits:
+** PXLF_MAPFROM -- map From_ to >From_.
+** PXLF_STRIP8BIT -- strip 8th bit.
+** PXLF_HEADER -- map bare newline in header to newline space.
+** PXLF_NOADDEOL -- don't add an EOL if one wasn't present.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** output of l to mci->mci_out.
+*/
+
+void
+putxline(l, len, mci, pxflags)
+ register char *l;
+ size_t len;
+ register MCI *mci;
+ int pxflags;
+{
+ bool dead = false;
+ register char *p, *end;
+ int slop = 0;
+
+ /* strip out 0200 bits -- these can look like TELNET protocol */
+ if (bitset(MCIF_7BIT, mci->mci_flags) ||
+ bitset(PXLF_STRIP8BIT, pxflags))
+ {
+ register char svchar;
+
+ for (p = l; (svchar = *p) != '\0'; ++p)
+ if (bitset(0200, svchar))
+ *p = svchar &~ 0200;
+ }
+
+ end = l + len;
+ do
+ {
+ bool noeol = false;
+
+ /* find the end of the line */
+ p = memchr(l, '\n', end - l);
+ if (p == NULL)
+ {
+ p = end;
+ noeol = true;
+ }
+
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d >>> ", (int) CurrentPid);
+
+ /* check for line overflow */
+ while (mci->mci_mailer->m_linelimit > 0 &&
+ (p - l + slop) > mci->mci_mailer->m_linelimit)
+ {
+ char *l_base = l;
+ register char *q = &l[mci->mci_mailer->m_linelimit - slop - 1];
+
+ if (l[0] == '.' && slop == 0 &&
+ bitnset(M_XDOT, mci->mci_mailer->m_flags))
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ '.') == SM_IO_EOF)
+ dead = true;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT, '.');
+ }
+ else if (l[0] == 'F' && slop == 0 &&
+ bitset(PXLF_MAPFROM, pxflags) &&
+ strncmp(l, "From ", 5) == 0 &&
+ bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ '>') == SM_IO_EOF)
+ dead = true;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ '>');
+ }
+ if (dead)
+ break;
+
+ while (l < q)
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ (unsigned char) *l++) == SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ }
+ if (dead)
+ break;
+
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '!') ==
+ SM_IO_EOF ||
+ sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol) ==
+ SM_IO_EOF ||
+ sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, ' ') ==
+ SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (TrafficLogFile != NULL)
+ {
+ for (l = l_base; l < q; l++)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char)*l);
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "!\n%05d >>> ",
+ (int) CurrentPid);
+ }
+ slop = 1;
+ }
+
+ if (dead)
+ break;
+
+ /* output last part */
+ if (l[0] == '.' && slop == 0 &&
+ bitnset(M_XDOT, mci->mci_mailer->m_flags))
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '.') ==
+ SM_IO_EOF)
+ break;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT, '.');
+ }
+ else if (l[0] == 'F' && slop == 0 &&
+ bitset(PXLF_MAPFROM, pxflags) &&
+ strncmp(l, "From ", 5) == 0 &&
+ bitnset(M_ESCFROM, mci->mci_mailer->m_flags))
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT, '>') ==
+ SM_IO_EOF)
+ break;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT, '>');
+ }
+ for ( ; l < p; ++l)
+ {
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ (unsigned char)*l);
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ (unsigned char) *l) == SM_IO_EOF)
+ {
+ dead = true;
+ break;
+ }
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ }
+ if (dead)
+ break;
+
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile, SM_TIME_DEFAULT,
+ '\n');
+ if ((!bitset(PXLF_NOADDEOL, pxflags) || !noeol) &&
+ sm_io_fputs(mci->mci_out, SM_TIME_DEFAULT,
+ mci->mci_mailer->m_eol) == SM_IO_EOF)
+ break;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+ if (l < end && *l == '\n')
+ {
+ if (*++l != ' ' && *l != '\t' && *l != '\0' &&
+ bitset(PXLF_HEADER, pxflags))
+ {
+ if (sm_io_putc(mci->mci_out, SM_TIME_DEFAULT,
+ ' ') == SM_IO_EOF)
+ break;
+ else
+ {
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ }
+
+ if (TrafficLogFile != NULL)
+ (void) sm_io_putc(TrafficLogFile,
+ SM_TIME_DEFAULT, ' ');
+ }
+ }
+
+ /* record progress for DATA timeout */
+ DataProgress = true;
+ } while (l < end);
+}
+/*
+** XUNLINK -- unlink a file, doing logging as appropriate.
+**
+** Parameters:
+** f -- name of file to unlink.
+**
+** Returns:
+** return value of unlink()
+**
+** Side Effects:
+** f is unlinked.
+*/
+
+int
+xunlink(f)
+ char *f;
+{
+ register int i;
+ int save_errno;
+
+ if (LogLevel > 98)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "unlink %s", f);
+
+ i = unlink(f);
+ save_errno = errno;
+ if (i < 0 && LogLevel > 97)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id, "%s: unlink-fail %d",
+ f, errno);
+ if (i >= 0)
+ SYNC_DIR(f, false);
+ errno = save_errno;
+ return i;
+}
+/*
+** SFGETS -- "safe" fgets -- times out and ignores random interrupts.
+**
+** Parameters:
+** buf -- place to put the input line.
+** siz -- size of buf.
+** fp -- file to read from.
+** timeout -- the timeout before error occurs.
+** during -- what we are trying to read (for error messages).
+**
+** Returns:
+** NULL on error (including timeout). This may also leave
+** buf containing a null string.
+** buf otherwise.
+*/
+
+
+char *
+sfgets(buf, siz, fp, timeout, during)
+ char *buf;
+ int siz;
+ SM_FILE_T *fp;
+ time_t timeout;
+ char *during;
+{
+ register char *p;
+ int save_errno;
+ int io_timeout;
+
+ SM_REQUIRE(siz > 0);
+ SM_REQUIRE(buf != NULL);
+
+ if (fp == NULL)
+ {
+ buf[0] = '\0';
+ errno = EBADF;
+ return NULL;
+ }
+
+ /* try to read */
+ p = NULL;
+ errno = 0;
+
+ /* convert the timeout to sm_io notation */
+ io_timeout = (timeout <= 0) ? SM_TIME_DEFAULT : timeout * 1000;
+ while (!sm_io_eof(fp) && !sm_io_error(fp))
+ {
+ errno = 0;
+ p = sm_io_fgets(fp, io_timeout, buf, siz);
+ if (p == NULL && errno == EAGAIN)
+ {
+ /* The sm_io_fgets() call timedout */
+ if (LogLevel > 1)
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "timeout waiting for input from %.100s during %s",
+ CURHOSTNAME,
+ during);
+ buf[0] = '\0';
+#if XDEBUG
+ checkfd012(during);
+#endif /* XDEBUG */
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile,
+ SM_TIME_DEFAULT,
+ "%05d <<< [TIMEOUT]\n",
+ (int) CurrentPid);
+ errno = ETIMEDOUT;
+ return NULL;
+ }
+ if (p != NULL || errno != EINTR)
+ break;
+ (void) sm_io_clearerr(fp);
+ }
+ save_errno = errno;
+
+ /* clean up the books and exit */
+ LineNumber++;
+ if (p == NULL)
+ {
+ buf[0] = '\0';
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d <<< [EOF]\n",
+ (int) CurrentPid);
+ errno = save_errno;
+ return NULL;
+ }
+ if (TrafficLogFile != NULL)
+ (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
+ "%05d <<< %s", (int) CurrentPid, buf);
+ if (SevenBitInput)
+ {
+ for (p = buf; *p != '\0'; p++)
+ *p &= ~0200;
+ }
+ else if (!HasEightBits)
+ {
+ for (p = buf; *p != '\0'; p++)
+ {
+ if (bitset(0200, *p))
+ {
+ HasEightBits = true;
+ break;
+ }
+ }
+ }
+ return buf;
+}
+/*
+** FGETFOLDED -- like fgets, but knows about folded lines.
+**
+** Parameters:
+** buf -- place to put result.
+** n -- bytes available.
+** f -- file to read from.
+**
+** Returns:
+** input line(s) on success, NULL on error or SM_IO_EOF.
+** This will normally be buf -- unless the line is too
+** long, when it will be sm_malloc_x()ed.
+**
+** Side Effects:
+** buf gets lines from f, with continuation lines (lines
+** with leading white space) appended. CRLF's are mapped
+** into single newlines. Any trailing NL is stripped.
+*/
+
+char *
+fgetfolded(buf, n, f)
+ char *buf;
+ register int n;
+ SM_FILE_T *f;
+{
+ register char *p = buf;
+ char *bp = buf;
+ register int i;
+
+ SM_REQUIRE(n > 0);
+ SM_REQUIRE(buf != NULL);
+ if (f == NULL)
+ {
+ buf[0] = '\0';
+ errno = EBADF;
+ return NULL;
+ }
+
+ n--;
+ while ((i = sm_io_getc(f, SM_TIME_DEFAULT)) != SM_IO_EOF)
+ {
+ if (i == '\r')
+ {
+ i = sm_io_getc(f, SM_TIME_DEFAULT);
+ if (i != '\n')
+ {
+ if (i != SM_IO_EOF)
+ (void) sm_io_ungetc(f, SM_TIME_DEFAULT,
+ i);
+ i = '\r';
+ }
+ }
+ if (--n <= 0)
+ {
+ /* allocate new space */
+ char *nbp;
+ int nn;
+
+ nn = (p - bp);
+ if (nn < MEMCHUNKSIZE)
+ nn *= 2;
+ else
+ nn += MEMCHUNKSIZE;
+ nbp = sm_malloc_x(nn);
+ memmove(nbp, bp, p - bp);
+ p = &nbp[p - bp];
+ if (bp != buf)
+ sm_free(bp);
+ bp = nbp;
+ n = nn - (p - bp);
+ }
+ *p++ = i;
+ if (i == '\n')
+ {
+ LineNumber++;
+ i = sm_io_getc(f, SM_TIME_DEFAULT);
+ if (i != SM_IO_EOF)
+ (void) sm_io_ungetc(f, SM_TIME_DEFAULT, i);
+ if (i != ' ' && i != '\t')
+ break;
+ }
+ }
+ if (p == bp)
+ return NULL;
+ if (p[-1] == '\n')
+ p--;
+ *p = '\0';
+ return bp;
+}
+/*
+** CURTIME -- return current time.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** the current time.
+*/
+
+time_t
+curtime()
+{
+ auto time_t t;
+
+ (void) time(&t);
+ return t;
+}
+/*
+** ATOBOOL -- convert a string representation to boolean.
+**
+** Defaults to false
+**
+** Parameters:
+** s -- string to convert. Takes "tTyY", empty, and NULL as true,
+** others as false.
+**
+** Returns:
+** A boolean representation of the string.
+*/
+
+bool
+atobool(s)
+ register char *s;
+{
+ if (s == NULL || *s == '\0' || strchr("tTyY", *s) != NULL)
+ return true;
+ return false;
+}
+/*
+** ATOOCT -- convert a string representation to octal.
+**
+** Parameters:
+** s -- string to convert.
+**
+** Returns:
+** An integer representing the string interpreted as an
+** octal number.
+*/
+
+int
+atooct(s)
+ register char *s;
+{
+ register int i = 0;
+
+ while (*s >= '0' && *s <= '7')
+ i = (i << 3) | (*s++ - '0');
+ return i;
+}
+/*
+** BITINTERSECT -- tell if two bitmaps intersect
+**
+** Parameters:
+** a, b -- the bitmaps in question
+**
+** Returns:
+** true if they have a non-null intersection
+** false otherwise
+*/
+
+bool
+bitintersect(a, b)
+ BITMAP256 a;
+ BITMAP256 b;
+{
+ int i;
+
+ for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
+ {
+ if ((a[i] & b[i]) != 0)
+ return true;
+ }
+ return false;
+}
+/*
+** BITZEROP -- tell if a bitmap is all zero
+**
+** Parameters:
+** map -- the bit map to check
+**
+** Returns:
+** true if map is all zero.
+** false if there are any bits set in map.
+*/
+
+bool
+bitzerop(map)
+ BITMAP256 map;
+{
+ int i;
+
+ for (i = BITMAPBYTES / sizeof (int); --i >= 0; )
+ {
+ if (map[i] != 0)
+ return false;
+ }
+ return true;
+}
+/*
+** STRCONTAINEDIN -- tell if one string is contained in another
+**
+** Parameters:
+** icase -- ignore case?
+** a -- possible substring.
+** b -- possible superstring.
+**
+** Returns:
+** true if a is contained in b (case insensitive).
+** false otherwise.
+*/
+
+bool
+strcontainedin(icase, a, b)
+ bool icase;
+ register char *a;
+ register char *b;
+{
+ int la;
+ int lb;
+ int c;
+
+ la = strlen(a);
+ lb = strlen(b);
+ c = *a;
+ if (icase && isascii(c) && isupper(c))
+ c = tolower(c);
+ for (; lb-- >= la; b++)
+ {
+ if (icase)
+ {
+ if (*b != c &&
+ isascii(*b) && isupper(*b) && tolower(*b) != c)
+ continue;
+ if (sm_strncasecmp(a, b, la) == 0)
+ return true;
+ }
+ else
+ {
+ if (*b != c)
+ continue;
+ if (strncmp(a, b, la) == 0)
+ return true;
+ }
+ }
+ return false;
+}
+/*
+** CHECKFD012 -- check low numbered file descriptors
+**
+** File descriptors 0, 1, and 2 should be open at all times.
+** This routine verifies that, and fixes it if not true.
+**
+** Parameters:
+** where -- a tag printed if the assertion failed
+**
+** Returns:
+** none
+*/
+
+void
+checkfd012(where)
+ char *where;
+{
+#if XDEBUG
+ register int i;
+
+ for (i = 0; i < 3; i++)
+ fill_fd(i, where);
+#endif /* XDEBUG */
+}
+/*
+** CHECKFDOPEN -- make sure file descriptor is open -- for extended debugging
+**
+** Parameters:
+** fd -- file descriptor to check.
+** where -- tag to print on failure.
+**
+** Returns:
+** none.
+*/
+
+void
+checkfdopen(fd, where)
+ int fd;
+ char *where;
+{
+#if XDEBUG
+ struct stat st;
+
+ if (fstat(fd, &st) < 0 && errno == EBADF)
+ {
+ syserr("checkfdopen(%d): %s not open as expected!", fd, where);
+ printopenfds(true);
+ }
+#endif /* XDEBUG */
+}
+/*
+** CHECKFDS -- check for new or missing file descriptors
+**
+** Parameters:
+** where -- tag for printing. If null, take a base line.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** If where is set, shows changes since the last call.
+*/
+
+void
+checkfds(where)
+ char *where;
+{
+ int maxfd;
+ register int fd;
+ bool printhdr = true;
+ int save_errno = errno;
+ static BITMAP256 baseline;
+ extern int DtableSize;
+
+ if (DtableSize > BITMAPBITS)
+ maxfd = BITMAPBITS;
+ else
+ maxfd = DtableSize;
+ if (where == NULL)
+ clrbitmap(baseline);
+
+ for (fd = 0; fd < maxfd; fd++)
+ {
+ struct stat stbuf;
+
+ if (fstat(fd, &stbuf) < 0 && errno != EOPNOTSUPP)
+ {
+ if (!bitnset(fd, baseline))
+ continue;
+ clrbitn(fd, baseline);
+ }
+ else if (!bitnset(fd, baseline))
+ setbitn(fd, baseline);
+ else
+ continue;
+
+ /* file state has changed */
+ if (where == NULL)
+ continue;
+ if (printhdr)
+ {
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "%s: changed fds:",
+ where);
+ printhdr = false;
+ }
+ dumpfd(fd, true, true);
+ }
+ errno = save_errno;
+}
+/*
+** PRINTOPENFDS -- print the open file descriptors (for debugging)
+**
+** Parameters:
+** logit -- if set, send output to syslog; otherwise
+** print for debugging.
+**
+** Returns:
+** none.
+*/
+
+#if NETINET || NETINET6
+# include <arpa/inet.h>
+#endif /* NETINET || NETINET6 */
+
+void
+printopenfds(logit)
+ bool logit;
+{
+ register int fd;
+ extern int DtableSize;
+
+ for (fd = 0; fd < DtableSize; fd++)
+ dumpfd(fd, false, logit);
+}
+/*
+** DUMPFD -- dump a file descriptor
+**
+** Parameters:
+** fd -- the file descriptor to dump.
+** printclosed -- if set, print a notification even if
+** it is closed; otherwise print nothing.
+** logit -- if set, send output to syslog instead of stdout.
+**
+** Returns:
+** none.
+*/
+
+void
+dumpfd(fd, printclosed, logit)
+ int fd;
+ bool printclosed;
+ bool logit;
+{
+ register char *p;
+ char *hp;
+#ifdef S_IFSOCK
+ SOCKADDR sa;
+#endif /* S_IFSOCK */
+ auto SOCKADDR_LEN_T slen;
+ int i;
+#if STAT64 > 0
+ struct stat64 st;
+#else /* STAT64 > 0 */
+ struct stat st;
+#endif /* STAT64 > 0 */
+ char buf[200];
+
+ p = buf;
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "%3d: ", fd);
+ p += strlen(p);
+
+ if (
+#if STAT64 > 0
+ fstat64(fd, &st)
+#else /* STAT64 > 0 */
+ fstat(fd, &st)
+#endif /* STAT64 > 0 */
+ < 0)
+ {
+ if (errno != EBADF)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "CANNOT STAT (%s)",
+ sm_errstring(errno));
+ goto printit;
+ }
+ else if (printclosed)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "CLOSED");
+ goto printit;
+ }
+ return;
+ }
+
+ i = fcntl(fd, F_GETFL, 0);
+ if (i != -1)
+ {
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "fl=0x%x, ", i);
+ p += strlen(p);
+ }
+
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "mode=%o: ",
+ (int) st.st_mode);
+ p += strlen(p);
+ switch (st.st_mode & S_IFMT)
+ {
+#ifdef S_IFSOCK
+ case S_IFSOCK:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "SOCK ");
+ p += strlen(p);
+ memset(&sa, '\0', sizeof sa);
+ slen = sizeof sa;
+ if (getsockname(fd, &sa.sa, &slen) < 0)
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
+ sm_errstring(errno));
+ else
+ {
+ hp = hostnamebyanyaddr(&sa);
+ if (hp == NULL)
+ {
+ /* EMPTY */
+ /* do nothing */
+ }
+# if NETINET
+ else if (sa.sa.sa_family == AF_INET)
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s/%d", hp, ntohs(sa.sin.sin_port));
+# endif /* NETINET */
+# if NETINET6
+ else if (sa.sa.sa_family == AF_INET6)
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s/%d", hp, ntohs(sa.sin6.sin6_port));
+# endif /* NETINET6 */
+ else
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s", hp);
+ }
+ p += strlen(p);
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "->");
+ p += strlen(p);
+ slen = sizeof sa;
+ if (getpeername(fd, &sa.sa, &slen) < 0)
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "(%s)",
+ sm_errstring(errno));
+ else
+ {
+ hp = hostnamebyanyaddr(&sa);
+ if (hp == NULL)
+ {
+ /* EMPTY */
+ /* do nothing */
+ }
+# if NETINET
+ else if (sa.sa.sa_family == AF_INET)
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s/%d", hp, ntohs(sa.sin.sin_port));
+# endif /* NETINET */
+# if NETINET6
+ else if (sa.sa.sa_family == AF_INET6)
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s/%d", hp, ntohs(sa.sin6.sin6_port));
+# endif /* NETINET6 */
+ else
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "%s", hp);
+ }
+ break;
+#endif /* S_IFSOCK */
+
+ case S_IFCHR:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "CHR: ");
+ p += strlen(p);
+ goto defprint;
+
+#ifdef S_IFBLK
+ case S_IFBLK:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "BLK: ");
+ p += strlen(p);
+ goto defprint;
+#endif /* S_IFBLK */
+
+#if defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK)
+ case S_IFIFO:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "FIFO: ");
+ p += strlen(p);
+ goto defprint;
+#endif /* defined(S_IFIFO) && (!defined(S_IFSOCK) || S_IFIFO != S_IFSOCK) */
+
+#ifdef S_IFDIR
+ case S_IFDIR:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "DIR: ");
+ p += strlen(p);
+ goto defprint;
+#endif /* S_IFDIR */
+
+#ifdef S_IFLNK
+ case S_IFLNK:
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "LNK: ");
+ p += strlen(p);
+ goto defprint;
+#endif /* S_IFLNK */
+
+ default:
+defprint:
+ (void) sm_snprintf(p, SPACELEFT(buf, p),
+ "dev=%d/%d, ino=%llu, nlink=%d, u/gid=%d/%d, ",
+ major(st.st_dev), minor(st.st_dev),
+ (ULONGLONG_T) st.st_ino,
+ (int) st.st_nlink, (int) st.st_uid,
+ (int) st.st_gid);
+ p += strlen(p);
+ (void) sm_snprintf(p, SPACELEFT(buf, p), "size=%llu",
+ (ULONGLONG_T) st.st_size);
+ break;
+ }
+
+printit:
+ if (logit)
+ sm_syslog(LOG_DEBUG, CurEnv ? CurEnv->e_id : NULL,
+ "%.800s", buf);
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s\n", buf);
+}
+/*
+** SHORTEN_HOSTNAME -- strip local domain information off of hostname.
+**
+** Parameters:
+** host -- the host to shorten (stripped in place).
+**
+** Returns:
+** place where string was truncated, NULL if not truncated.
+*/
+
+char *
+shorten_hostname(host)
+ char host[];
+{
+ register char *p;
+ char *mydom;
+ int i;
+ bool canon = false;
+
+ /* strip off final dot */
+ i = strlen(host);
+ p = &host[(i == 0) ? 0 : i - 1];
+ if (*p == '.')
+ {
+ *p = '\0';
+ canon = true;
+ }
+
+ /* see if there is any domain at all -- if not, we are done */
+ p = strchr(host, '.');
+ if (p == NULL)
+ return NULL;
+
+ /* yes, we have a domain -- see if it looks like us */
+ mydom = macvalue('m', CurEnv);
+ if (mydom == NULL)
+ mydom = "";
+ i = strlen(++p);
+ if ((canon ? sm_strcasecmp(p, mydom)
+ : sm_strncasecmp(p, mydom, i)) == 0 &&
+ (mydom[i] == '.' || mydom[i] == '\0'))
+ {
+ *--p = '\0';
+ return p;
+ }
+ return NULL;
+}
+/*
+** PROG_OPEN -- open a program for reading
+**
+** Parameters:
+** argv -- the argument list.
+** pfd -- pointer to a place to store the file descriptor.
+** e -- the current envelope.
+**
+** Returns:
+** pid of the process -- -1 if it failed.
+*/
+
+pid_t
+prog_open(argv, pfd, e)
+ char **argv;
+ int *pfd;
+ ENVELOPE *e;
+{
+ pid_t pid;
+ int i;
+ int save_errno;
+ int sff;
+ int ret;
+ int fdv[2];
+ char *p, *q;
+ char buf[MAXPATHLEN];
+ extern int DtableSize;
+
+ if (pipe(fdv) < 0)
+ {
+ syserr("%s: cannot create pipe for stdout", argv[0]);
+ return -1;
+ }
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("%s: cannot fork", argv[0]);
+ (void) close(fdv[0]);
+ (void) close(fdv[1]);
+ return -1;
+ }
+ if (pid > 0)
+ {
+ /* parent */
+ (void) close(fdv[1]);
+ *pfd = fdv[0];
+ return pid;
+ }
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ sm_exc_newthread(fatal_error);
+
+ /* child -- close stdin */
+ (void) close(0);
+
+ /* stdout goes back to parent */
+ (void) close(fdv[0]);
+ if (dup2(fdv[1], 1) < 0)
+ {
+ syserr("%s: cannot dup2 for stdout", argv[0]);
+ _exit(EX_OSERR);
+ }
+ (void) close(fdv[1]);
+
+ /* stderr goes to transcript if available */
+ if (e->e_xfp != NULL)
+ {
+ int xfd;
+
+ xfd = sm_io_getinfo(e->e_xfp, SM_IO_WHAT_FD, NULL);
+ if (xfd >= 0 && dup2(xfd, 2) < 0)
+ {
+ syserr("%s: cannot dup2 for stderr", argv[0]);
+ _exit(EX_OSERR);
+ }
+ }
+
+ /* this process has no right to the queue file */
+ if (e->e_lockfp != NULL)
+ (void) close(sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL));
+
+ /* chroot to the program mailer directory, if defined */
+ if (ProgMailer != NULL && ProgMailer->m_rootdir != NULL)
+ {
+ expand(ProgMailer->m_rootdir, buf, sizeof buf, e);
+ if (chroot(buf) < 0)
+ {
+ syserr("prog_open: cannot chroot(%s)", buf);
+ exit(EX_TEMPFAIL);
+ }
+ if (chdir("/") < 0)
+ {
+ syserr("prog_open: cannot chdir(/)");
+ exit(EX_TEMPFAIL);
+ }
+ }
+
+ /* run as default user */
+ endpwent();
+ sm_mbdb_terminate();
+ if (setgid(DefGid) < 0 && geteuid() == 0)
+ {
+ syserr("prog_open: setgid(%ld) failed", (long) DefGid);
+ exit(EX_TEMPFAIL);
+ }
+ if (setuid(DefUid) < 0 && geteuid() == 0)
+ {
+ syserr("prog_open: setuid(%ld) failed", (long) DefUid);
+ exit(EX_TEMPFAIL);
+ }
+
+ /* run in some directory */
+ if (ProgMailer != NULL)
+ p = ProgMailer->m_execdir;
+ else
+ p = NULL;
+ for (; p != NULL; p = q)
+ {
+ q = strchr(p, ':');
+ if (q != NULL)
+ *q = '\0';
+ expand(p, buf, sizeof buf, e);
+ if (q != NULL)
+ *q++ = ':';
+ if (buf[0] != '\0' && chdir(buf) >= 0)
+ break;
+ }
+ if (p == NULL)
+ {
+ /* backup directories */
+ if (chdir("/tmp") < 0)
+ (void) chdir("/");
+ }
+
+ /* Check safety of program to be run */
+ sff = SFF_ROOTOK|SFF_EXECOK;
+ if (!bitnset(DBS_RUNWRITABLEPROGRAM, DontBlameSendmail))
+ sff |= SFF_NOGWFILES|SFF_NOWWFILES;
+ if (bitnset(DBS_RUNPROGRAMINUNSAFEDIRPATH, DontBlameSendmail))
+ sff |= SFF_NOPATHCHECK;
+ else
+ sff |= SFF_SAFEDIRPATH;
+ ret = safefile(argv[0], DefUid, DefGid, DefUser, sff, 0, NULL);
+ if (ret != 0)
+ sm_syslog(LOG_INFO, e->e_id,
+ "Warning: prog_open: program %s unsafe: %s",
+ argv[0], sm_errstring(ret));
+
+ /* arrange for all the files to be closed */
+ for (i = 3; i < DtableSize; i++)
+ {
+ register int j;
+
+ if ((j = fcntl(i, F_GETFD, 0)) != -1)
+ (void) fcntl(i, F_SETFD, j | FD_CLOEXEC);
+ }
+
+ /* now exec the process */
+ (void) execve(argv[0], (ARGV_T) argv, (ARGV_T) UserEnviron);
+
+ /* woops! failed */
+ save_errno = errno;
+ syserr("%s: cannot exec", argv[0]);
+ if (transienterror(save_errno))
+ _exit(EX_OSERR);
+ _exit(EX_CONFIG);
+ return -1; /* avoid compiler warning on IRIX */
+}
+/*
+** GET_COLUMN -- look up a Column in a line buffer
+**
+** Parameters:
+** line -- the raw text line to search.
+** col -- the column number to fetch.
+** delim -- the delimiter between columns. If null,
+** use white space.
+** buf -- the output buffer.
+** buflen -- the length of buf.
+**
+** Returns:
+** buf if successful.
+** NULL otherwise.
+*/
+
+char *
+get_column(line, col, delim, buf, buflen)
+ char line[];
+ int col;
+ int delim;
+ char buf[];
+ int buflen;
+{
+ char *p;
+ char *begin, *end;
+ int i;
+ char delimbuf[4];
+
+ if ((char) delim == '\0')
+ (void) sm_strlcpy(delimbuf, "\n\t ", sizeof delimbuf);
+ else
+ {
+ delimbuf[0] = (char) delim;
+ delimbuf[1] = '\0';
+ }
+
+ p = line;
+ if (*p == '\0')
+ return NULL; /* line empty */
+ if (*p == (char) delim && col == 0)
+ return NULL; /* first column empty */
+
+ begin = line;
+
+ if (col == 0 && (char) delim == '\0')
+ {
+ while (*begin != '\0' && isascii(*begin) && isspace(*begin))
+ begin++;
+ }
+
+ for (i = 0; i < col; i++)
+ {
+ if ((begin = strpbrk(begin, delimbuf)) == NULL)
+ return NULL; /* no such column */
+ begin++;
+ if ((char) delim == '\0')
+ {
+ while (*begin != '\0' && isascii(*begin) && isspace(*begin))
+ begin++;
+ }
+ }
+
+ end = strpbrk(begin, delimbuf);
+ if (end == NULL)
+ i = strlen(begin);
+ else
+ i = end - begin;
+ if (i >= buflen)
+ i = buflen - 1;
+ (void) sm_strlcpy(buf, begin, i + 1);
+ return buf;
+}
+/*
+** CLEANSTRCPY -- copy string keeping out bogus characters
+**
+** Parameters:
+** t -- "to" string.
+** f -- "from" string.
+** l -- length of space available in "to" string.
+**
+** Returns:
+** none.
+*/
+
+void
+cleanstrcpy(t, f, l)
+ register char *t;
+ register char *f;
+ int l;
+{
+ /* check for newlines and log if necessary */
+ (void) denlstring(f, true, true);
+
+ if (l <= 0)
+ syserr("!cleanstrcpy: length == 0");
+
+ l--;
+ while (l > 0 && *f != '\0')
+ {
+ if (isascii(*f) &&
+ (isalnum(*f) || strchr("!#$%&'*+-./^_`{|}~", *f) != NULL))
+ {
+ l--;
+ *t++ = *f;
+ }
+ f++;
+ }
+ *t = '\0';
+}
+/*
+** DENLSTRING -- convert newlines in a string to spaces
+**
+** Parameters:
+** s -- the input string
+** strict -- if set, don't permit continuation lines.
+** logattacks -- if set, log attempted attacks.
+**
+** Returns:
+** A pointer to a version of the string with newlines
+** mapped to spaces. This should be copied.
+*/
+
+char *
+denlstring(s, strict, logattacks)
+ char *s;
+ bool strict;
+ bool logattacks;
+{
+ register char *p;
+ int l;
+ static char *bp = NULL;
+ static int bl = 0;
+
+ p = s;
+ while ((p = strchr(p, '\n')) != NULL)
+ if (strict || (*++p != ' ' && *p != '\t'))
+ break;
+ if (p == NULL)
+ return s;
+
+ l = strlen(s) + 1;
+ if (bl < l)
+ {
+ /* allocate more space */
+ char *nbp = sm_pmalloc_x(l);
+
+ if (bp != NULL)
+ sm_free(bp);
+ bp = nbp;
+ bl = l;
+ }
+ (void) sm_strlcpy(bp, s, l);
+ for (p = bp; (p = strchr(p, '\n')) != NULL; )
+ *p++ = ' ';
+
+ if (logattacks)
+ {
+ sm_syslog(LOG_NOTICE, CurEnv->e_id,
+ "POSSIBLE ATTACK from %.100s: newline in string \"%s\"",
+ RealHostName == NULL ? "[UNKNOWN]" : RealHostName,
+ shortenstring(bp, MAXSHORTSTR));
+ }
+
+ return bp;
+}
+
+/*
+** STRREPLNONPRT -- replace "unprintable" characters in a string with subst
+**
+** Parameters:
+** s -- string to manipulate (in place)
+** subst -- character to use as replacement
+**
+** Returns:
+** true iff string did not contain "unprintable" characters
+*/
+
+bool
+strreplnonprt(s, c)
+ char *s;
+ int c;
+{
+ bool ok;
+
+ ok = true;
+ if (s == NULL)
+ return ok;
+ while (*s != '\0')
+ {
+ if (!(isascii(*s) && isprint(*s)))
+ {
+ *s = c;
+ ok = false;
+ }
+ ++s;
+ }
+ return ok;
+}
+
+/*
+** STR2PRT -- convert "unprintable" characters in a string to \oct
+**
+** Parameters:
+** s -- string to convert
+**
+** Returns:
+** converted string.
+** This is a static local buffer, string must be copied
+** before this function is called again!
+*/
+
+char *
+str2prt(s)
+ char *s;
+{
+ int l;
+ char c, *h;
+ bool ok;
+ static int len = 0;
+ static char *buf = NULL;
+
+ if (s == NULL)
+ return NULL;
+ ok = true;
+ for (h = s, l = 1; *h != '\0'; h++, l++)
+ {
+ if (*h == '\\')
+ {
+ ++l;
+ ok = false;
+ }
+ else if (!(isascii(*h) && isprint(*h)))
+ {
+ l += 3;
+ ok = false;
+ }
+ }
+ if (ok)
+ return s;
+ if (l > len)
+ {
+ char *nbuf = sm_pmalloc_x(l);
+
+ if (buf != NULL)
+ sm_free(buf);
+ len = l;
+ buf = nbuf;
+ }
+ for (h = buf; *s != '\0' && l > 0; s++, l--)
+ {
+ c = *s;
+ if (isascii(c) && isprint(c) && c != '\\')
+ {
+ *h++ = c;
+ }
+ else
+ {
+ *h++ = '\\';
+ --l;
+ switch (c)
+ {
+ case '\\':
+ *h++ = '\\';
+ break;
+ case '\t':
+ *h++ = 't';
+ break;
+ case '\n':
+ *h++ = 'n';
+ break;
+ case '\r':
+ *h++ = 'r';
+ break;
+ default:
+ (void) sm_snprintf(h, l, "%03o", (int) c);
+
+ /*
+ ** XXX since l is unsigned this may
+ ** wrap around if the calculation is screwed
+ ** up...
+ */
+
+ l -= 2;
+ h += 3;
+ break;
+ }
+ }
+ }
+ *h = '\0';
+ buf[len - 1] = '\0';
+ return buf;
+}
+/*
+** PATH_IS_DIR -- check to see if file exists and is a directory.
+**
+** There are some additional checks for security violations in
+** here. This routine is intended to be used for the host status
+** support.
+**
+** Parameters:
+** pathname -- pathname to check for directory-ness.
+** createflag -- if set, create directory if needed.
+**
+** Returns:
+** true -- if the indicated pathname is a directory
+** false -- otherwise
+*/
+
+int
+path_is_dir(pathname, createflag)
+ char *pathname;
+ bool createflag;
+{
+ struct stat statbuf;
+
+#if HASLSTAT
+ if (lstat(pathname, &statbuf) < 0)
+#else /* HASLSTAT */
+ if (stat(pathname, &statbuf) < 0)
+#endif /* HASLSTAT */
+ {
+ if (errno != ENOENT || !createflag)
+ return false;
+ if (mkdir(pathname, 0755) < 0)
+ return false;
+ return true;
+ }
+ if (!S_ISDIR(statbuf.st_mode))
+ {
+ errno = ENOTDIR;
+ return false;
+ }
+
+ /* security: don't allow writable directories */
+ if (bitset(S_IWGRP|S_IWOTH, statbuf.st_mode))
+ {
+ errno = EACCES;
+ return false;
+ }
+ return true;
+}
+/*
+** PROC_LIST_ADD -- add process id to list of our children
+**
+** Parameters:
+** pid -- pid to add to list.
+** task -- task of pid.
+** type -- type of process.
+** count -- number of processes.
+** other -- other information for this type.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** May increase CurChildren. May grow ProcList.
+*/
+
+typedef struct procs PROCS_T;
+
+struct procs
+{
+ pid_t proc_pid;
+ char *proc_task;
+ int proc_type;
+ int proc_count;
+ int proc_other;
+};
+
+static PROCS_T *volatile ProcListVec = NULL;
+static int ProcListSize = 0;
+
+void
+proc_list_add(pid, task, type, count, other)
+ pid_t pid;
+ char *task;
+ int type;
+ int count;
+ int other;
+{
+ int i;
+
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID)
+ break;
+ }
+ if (i >= ProcListSize)
+ {
+ /* probe the existing vector to avoid growing infinitely */
+ proc_list_probe();
+
+ /* now scan again */
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID)
+ break;
+ }
+ }
+ if (i >= ProcListSize)
+ {
+ /* grow process list */
+ PROCS_T *npv;
+
+ SM_ASSERT(ProcListSize < INT_MAX - PROC_LIST_SEG);
+ npv = (PROCS_T *) sm_pmalloc_x((sizeof *npv) *
+ (ProcListSize + PROC_LIST_SEG));
+ if (ProcListSize > 0)
+ {
+ memmove(npv, ProcListVec,
+ ProcListSize * sizeof (PROCS_T));
+ sm_free(ProcListVec);
+ }
+
+ /* XXX just use memset() to initialize this part? */
+ for (i = ProcListSize; i < ProcListSize + PROC_LIST_SEG; i++)
+ {
+ npv[i].proc_pid = NO_PID;
+ npv[i].proc_task = NULL;
+ npv[i].proc_type = PROC_NONE;
+ }
+ i = ProcListSize;
+ ProcListSize += PROC_LIST_SEG;
+ ProcListVec = npv;
+ }
+ ProcListVec[i].proc_pid = pid;
+ PSTRSET(ProcListVec[i].proc_task, task);
+ ProcListVec[i].proc_type = type;
+ ProcListVec[i].proc_count = count;
+ ProcListVec[i].proc_other = other;
+
+ /* if process adding itself, it's not a child */
+ if (pid != CurrentPid)
+ {
+ SM_ASSERT(CurChildren < INT_MAX);
+ CurChildren++;
+ }
+}
+/*
+** PROC_LIST_SET -- set pid task in process list
+**
+** Parameters:
+** pid -- pid to set
+** task -- task of pid
+**
+** Returns:
+** none.
+*/
+
+void
+proc_list_set(pid, task)
+ pid_t pid;
+ char *task;
+{
+ int i;
+
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == pid)
+ {
+ PSTRSET(ProcListVec[i].proc_task, task);
+ break;
+ }
+ }
+}
+/*
+** PROC_LIST_DROP -- drop pid from process list
+**
+** Parameters:
+** pid -- pid to drop
+** st -- process status
+** other -- storage for proc_other (return).
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** May decrease CurChildren, CurRunners, or
+** set RestartRequest or ShutdownRequest.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+proc_list_drop(pid, st, other)
+ pid_t pid;
+ int st;
+ int *other;
+{
+ int i;
+ int type = PROC_NONE;
+
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == pid)
+ {
+ ProcListVec[i].proc_pid = NO_PID;
+ type = ProcListVec[i].proc_type;
+ if (other != NULL)
+ *other = ProcListVec[i].proc_other;
+ break;
+ }
+ }
+ if (CurChildren > 0)
+ CurChildren--;
+
+
+ if (type == PROC_CONTROL && WIFEXITED(st))
+ {
+ /* if so, see if we need to restart or shutdown */
+ if (WEXITSTATUS(st) == EX_RESTART)
+ RestartRequest = "control socket";
+ else if (WEXITSTATUS(st) == EX_SHUTDOWN)
+ ShutdownRequest = "control socket";
+ }
+ else if (type == PROC_QUEUE_CHILD && !WIFSTOPPED(st) &&
+ ProcListVec[i].proc_other > -1)
+ {
+ /* restart this persistent runner */
+ mark_work_group_restart(ProcListVec[i].proc_other, st);
+ }
+ else if (type == PROC_QUEUE)
+ CurRunners -= ProcListVec[i].proc_count;
+}
+/*
+** PROC_LIST_CLEAR -- clear the process list
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets CurChildren to zero.
+*/
+
+void
+proc_list_clear()
+{
+ int i;
+
+ /* start from 1 since 0 is the daemon itself */
+ for (i = 1; i < ProcListSize; i++)
+ ProcListVec[i].proc_pid = NO_PID;
+ CurChildren = 0;
+}
+/*
+** PROC_LIST_PROBE -- probe processes in the list to see if they still exist
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+**
+** Side Effects:
+** May decrease CurChildren.
+*/
+
+void
+proc_list_probe()
+{
+ int i;
+
+ /* start from 1 since 0 is the daemon itself */
+ for (i = 1; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID)
+ continue;
+ if (kill(ProcListVec[i].proc_pid, 0) < 0)
+ {
+ if (LogLevel > 3)
+ sm_syslog(LOG_DEBUG, CurEnv->e_id,
+ "proc_list_probe: lost pid %d",
+ (int) ProcListVec[i].proc_pid);
+ ProcListVec[i].proc_pid = NO_PID;
+ SM_FREE_CLR(ProcListVec[i].proc_task);
+ CurChildren--;
+ }
+ }
+ if (CurChildren < 0)
+ CurChildren = 0;
+}
+
+/*
+** PROC_LIST_DISPLAY -- display the process list
+**
+** Parameters:
+** out -- output file pointer
+** prefix -- string to output in front of each line.
+**
+** Returns:
+** none.
+*/
+
+void
+proc_list_display(out, prefix)
+ SM_FILE_T *out;
+ char *prefix;
+{
+ int i;
+
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID)
+ continue;
+
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT, "%s%d %s%s\n",
+ prefix,
+ (int) ProcListVec[i].proc_pid,
+ ProcListVec[i].proc_task != NULL ?
+ ProcListVec[i].proc_task : "(unknown)",
+ (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ OpMode == MD_ARPAFTP) ? "\r" : "");
+ }
+}
+
+/*
+** PROC_LIST_SIGNAL -- send a signal to a type of process in the list
+**
+** Parameters:
+** type -- type of process to signal
+** signal -- the type of signal to send
+**
+** Results:
+** none.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+proc_list_signal(type, signal)
+ int type;
+ int signal;
+{
+ int chldwasblocked;
+ int alrmwasblocked;
+ int i;
+ pid_t mypid = getpid();
+
+ /* block these signals so that we may signal cleanly */
+ chldwasblocked = sm_blocksignal(SIGCHLD);
+ alrmwasblocked = sm_blocksignal(SIGALRM);
+
+ /* Find all processes of type and send signal */
+ for (i = 0; i < ProcListSize; i++)
+ {
+ if (ProcListVec[i].proc_pid == NO_PID ||
+ ProcListVec[i].proc_pid == mypid)
+ continue;
+ if (ProcListVec[i].proc_type != type)
+ continue;
+ (void) kill(ProcListVec[i].proc_pid, signal);
+ }
+
+ /* restore the signals */
+ if (alrmwasblocked == 0)
+ (void) sm_releasesignal(SIGALRM);
+ if (chldwasblocked == 0)
+ (void) sm_releasesignal(SIGCHLD);
+}
diff --git a/contrib/sendmail/src/version.c b/contrib/sendmail/src/version.c
new file mode 100644
index 0000000..8822333
--- /dev/null
+++ b/contrib/sendmail/src/version.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sm/gen.h>
+
+SM_RCSID("@(#)$Id: version.c,v 8.104.2.5 2002/08/24 16:27:21 ca Exp $")
+
+char Version[] = "8.12.6";
OpenPOWER on IntegriCloud