diff options
Diffstat (limited to 'contrib/sendmail/src')
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"; |