#!/bin/ksh93 # # License: CDDL 1.0 # Copyright 2007-2018, Jens Elkner. # # Sendmail Mail Transfer Agent (MTA) service. If this basename has no 'sendmail' # suffix, it runs sendmail in submit mode (-Ac) using /etc/mail/submit.cf, # otherwise in runs sendmail in server mode (-Am) using /etc/mail/sendmail.cf. # # In server mode it listens for incoming SMTP connections on the loopback # interface only (-bl) unless the environment variable LOCAL_ONLY in # /etc/default/sendmail is set to 'false' (-bd). # # In submit mode it just inspects the client mail queue (the queue, where local # clients put the messages using sendmail in mail delivery mode) in a certain # intervall (default: 15 min) and tries to deliver all mails in this queue as # defined by the submit.cf. If it is configured to deliver directly to a central # MTA aka mail gateway, there is no need to run an additional sendmail service # in server mode on this machine. However, often submit.cf is configured to # deliver all mail to the MTA running on the localhost. In this case another # instance should be run in server mode, otherwise the clien mail queue will # never be emptied, because they cannot be delivered. typeset -r DEFAULT_FILE="/etc/default/sendmail" # Tweakings: # * LOCAL_ONLY should be set to 'false' for real servers, otherwise to 'yes'. # * [CLIENT]QUEUEOPTION should be "p" or unset. # * [CLIENT]QUEUEINTERVAL should be set to some legal value like Nm # * [CLIENT]OPTIONS are catch-alls, used as is; set with care. export PATH='/bin:/usr/bin:/sbin:/usr/sbin' typeset -r SENDMAIL='/usr/sbin/sendmail' ETRN='/usr/sbin/etrn' \ NEWALIASES='/usr/sbin/newaliases' ALIASES_FILE='/etc/mail/aliases' typeset -r DEFAULTS=( [qdir]='/var/spool/mqueue' [qown]='root' [qgrp]='bin' [qperm]='0750' [cf]='/etc/mail/sendmail.cf' [mc]='/etc/mail/sendmail.mc' [aliases]='/etc/mail/aliases' [mode]='-bl' [pidfile]='/run/sendmail.pid' # keep in sync with svc ) typeset -r CLIENTDEFAULTS=( [qdir]='/var/spool/clientmqueue' [qown]='smmsp' [qgrp]='smmsp' [qperm]='0770' [cf]='/etc/mail/submit.cf' [mc]='/etc/mail/submit.mc' [mode]='-Ac' [pidfile]='/var/spool/clientmqueue/sm-client.pid' # keep in sync with svc ) # trailing slash is important here! CF_BASEDIR=/usr/lib/mail/ # exit codes SMF_EXIT_OK=0 SMF_EXIT_ERR_FATAL=95 SMF_EXIT_ERR_CONFIG=96 SMF_EXIT_MON_DEGRADE=97 SMF_EXIT_MON_OFFLINE=98 SMF_EXIT_ERR_NOSMF=99 SMF_EXIT_ERR_PERM=100 SMF_EXIT_TEMP_DISABLE=101 SMF_EXIT_TEMP_TRANSIENT=102 # $1 .. pid file to use for killing a corresponding process # $2 .. optional SIGNAL to send (no leading dash!) function check_and_kill { if [[ -z $1 || ! -f $1 ]]; then # Linux has no contracts, so let's swing thor's hammer /usr/bin/pkill -x -u root,smmsp sendmail return fi typeset PID=$(<$1) PID=${PID%%$'\n'*} [[ -z ${PID} ]] && return kill -0 ${PID} > /dev/null 2>&1 # check, whether the process exists (( $? )) || kill -${2:-TERM} ${PID} } # $1 .. function exist_or_exit { [[ -n $1 && -f $1 ]] && return print -u2 "file '$1' does not exist" exit ${SMF_EXIT_ERR_CONFIG} } # $1 .. Interval to check # $2 .. Optional: Name of the variable, where to store the answer as well. # Sets the Variable 'answer' to $1 if it is a valid value, otherwise to a # default of '15m'. function check_queue_interval_syntax { [[ $1 =~ ^([0-9]*[1-9][0-9]*[smhdw])+$ ]] && answer=$1 || answer='15m' # this is the right way to do it (i.e. not using global vars) if [[ -n $2 ]]; then typeset -n VAL=$2 VAL=${answer} fi } # $1 .. path name of the *.cf file, where to store the generated output # $2 .. absolute path name of the m4 aka *.mc file, from which to read the input function turn_m4_crank { (( $# < 2 )) && return typeset CF="$1" MC="$2" if [[ ${MC} == '_DONT_TOUCH_THIS' ]]; then if [[ -f ${CF}.old ]]; then mv "${CF}" "${CF}.new" || exit ${SMF_EXIT_ERR_CONFIG} mv "${CF}.old" "$CF" || exit ${SMF_EXIT_ERR_CONFIG} fi # If ${CF}.old does not exist, assume it was taken care # of on a previous run. return elif [[ ${MC:0:1} != '/' ]]; then # absolute path name required return fi exist_or_exit "${MC}" # If CF exists and has the same timestamp as MC, there is nothing to do [[ ${CF} -nt ${MC} || ${CF} -ot ${MC} ]] || return # just in case MC tries to include files with a relative path cd "${MC%/*}" || print -u2 "Warning: Can't change directory to '${MC%/*}'" \ '(this may produce unexpected results)' # create the new CF typeset PARAM='' [[ ${INC_INFO} != 'true' ]] && PARAM='-D_NO_MAKEINFO_' m4 -D_CF_DIR_="${CF_BASEDIR}" ${PARAM} "${CF_BASEDIR}m4/cf.m4" "${MC}" \ > "${CF}.new" # if failed or m4 did not output anything, don't replace the current CF # we don't remove screwed output to allow an admin to find the root cause # a little bit easier. [[ $? != 0 || ! -s "${CF}.new" ]] && exit ${SMF_EXIT_ERR_CONFIG} builtin cmp cmp -s "${CF}.new" "$CF" || ( chown root:bin "${CF}.new" && chmod 444 "${CF}.new" && mv "${CF}.new" "$CF" ) (( $? )) && exit ${SMF_EXIT_ERR_CONFIG} # finally make sure both files have the same timestamp touch -r "${MC}" "${CF}" } # determine the target function getTarget { typeset -n VAL=$1 typeset BN VAL=-1 BN=${.sh.file##*/} # this script or link basename name starts with ... ? if [[ ${BN: -8:8} == 'sendmail' ]]; then VAL=1 elif [[ ${BN: -15:15} == 'sendmail-client' ]]; then VAL=0 else print -u2 'WARNING: Unable to determine, whether to apply the' \ 'command to the client or server. Falling back to client.' VAL=0 fi } # IPS does not support postinstall scripts =8-( function postInstall { typeset X FILES="trusted-users ${SMDEFAULTS[mc]##*/}" if (( SERVER )); then FILES+=' aliases local-host-names' [[ ! -e /var/log/sendmail.st ]] && touch /var/log/sendmail.st # enable stats fi for X in ${FILES} ; do [[ ! -e /etc/mail/$X ]] && cp -p "${CF_BASEDIR}/defaults/$X" /etc/mail/ done typeset -A S typeset SVC PP TAIL while read SVC PP TAIL ; do [[ -z ${SVC} || ${SVC:0:1} == '#' ]] && continue S["${SVC}"]="${PP}" done < /etc/services typeset -A SVCS SVCS[smtp]=( pp='25/tcp mail' desc='# Simple Mail Transfer Protocol' ) SVCS[ident]=( pp='113/tcp auth' desc='# Identification Protocol' ) SVCS[submission]=( pp='587/tcp' desc='# Mail Submission Agent' ) TAIL='' for SVC in ${!SVCS[@]} ; do PP="${S[${SVC}]}" [[ -z ${PP} || ${PP: -4} != '/tcp' ]] && \ TAIL+="${SVC}\t\t${SVCS[${SVC}].pp}\t\t${SVCS[${SVC}].desc}\n" done [[ -n ${TAIL} ]] && print -n "${TAIL}" >>/etc/services } # just in case, one want's to see, what's going on here ;-) #set -x ; typeset -ft ${ typeset +f ; } integer SERVER getTarget SERVER (( SERVER )) && PREFIX= || PREFIX=CLIENT typeset -n SMDEFAULTS=${PREFIX}DEFAULTS \ QOPT=${PREFIX}QUEUEOPTION QINTERVAL=${PREFIX}QUEUEINTERVAL \ OPTS=${PREFIX}OPTIONS DMODE=${PREFIX}MODE \ A_PATH_TO_MC=${PREFIX}PATH_TO_MC A_PATH_TO_CF=${PREFIX}PATH_TO_CF \ INC_INFO=${PREFIX}INCLUDE_INFO LOCAL=${PREFIX}LOCAL_ONLY case "$1" in 'refresh') check_and_kill ${SMDEFAULTS[pidfile]} HUP ;; 'start') exist_or_exit ${SENDMAIL} DMODE=${SMDEFAULTS[mode]} [[ -f ${DEFAULT_FILE} ]] && . ${DEFAULT_FILE} [[ ${QOPT} != 'p' ]] && QOPT= [[ -z ${QOPT} || -n ${QINTERVAL} ]] && \ check_queue_interval_syntax "${QINTERVAL}" QINTERVAL # ensure *.cf file is availavble typeset MC="${A_PATH_TO_MC}" CF="${A_PATH_TO_CF}" [[ -z ${CF} ]] && CF=${SMDEFAULTS[cf]} if [[ -z ${MC} || ! -e ${MC} ]] && [[ ! -e ${CF} ]]; then postInstall fi [[ -z ${MC} ]] && MC=${SMDEFAULTS[mc]} turn_m4_crank "${CF}" "${MC}" exist_or_exit "${CF}" # ensure queue dir exists if [[ ! -d ${SMDEFAULTS[qdir]} ]]; then mkdir -m ${SMDEFAULTS[perm]} ${SMDEFAULTS[qdir]} chown ${SMDEFAULTS[qown]}:${SMDEFAULTS[qgrp]} ${SMDEFAULTS[qdir]} fi if (( SERVER )) ; then # run newaliases if db file[s] are missing [[ -f ${ALIASES_FILE}.db || \ ( -f ${ALIASES_FILE}.dir && -f ${ALIASES_FILE}.pag ) ]] || \ ${NEWALIASES} # check for 'listen to loopback, only' mode [[ ${LOCAL} == 'false' ]] && DMODE='-bd' fi # sendmail blocks 'til it thinks it got all information it needs to # work properly. Therefore we sent it to background to avoid hitting # the startup timeout penalty. PID monitoring will do the rest. ${SENDMAIL} ${DMODE} -q${QOPT}${QINTERVAL} ${OPTS} \ -O "PidFile=${SMDEFAULTS[pidfile]}" & if (( SERVER )) ; then # ETRN_HOSTS should be of the form # "s1:c1.1,c1.2 s2:c2.1 s3:c3.1,c3.2,c3.3" # i.e., white-space separated groups of server:client where # client can be one or more comma-separated names; N.B. that # the :client part is optional; see etrn(1M) for details. # server is the name of the server to prod; a mail queue run # is requested for each client name. This is comparable to # running "/usr/lib/sendmail -qRclient" on the host server. # # See RFC 1985 for more information. X=${ whence perl ; } # no perl, no etrn for I in ${ETRN_HOSTS}; do SERVER={I%%:*} CLIENTS={.sh.match#:} CLIENTS={CLIENTS//,/ } ${ETRN} -b ${SERVER} ${CLIENTS} >/dev/null 2>&1 & done while read DEV MPNT FS MOPTS TAIL ; do if [[ ${MPNT} == '/var/mail' ]]; then [[ ${FS} != 'nfs' || ${MOPTS} =~ (actimeo=0|noac) ]] || \ /usr/bin/logger -p mail.crit \ 'WARNING: /var/mail is NFS-mounted without setting actimeo=0,' \ 'this can cause mailbox locking and access problems.' break fi done < /etc/mtab fi ;; 'stop') check_and_kill ${SMDEFAULTS[pidfile]} ;; *) print -u2 "Usage: $0 { start | stop | refresh }" exit 1 ;; esac exit ${SMF_EXIT_OK}