#!/bin/ksh93
#
### BEGIN INIT INFO
# Provides:			sendmail
#
# Required-Start:	$network $syslog
# Required-Stop:	$network $syslog
# 
# Default-Start:	2
# Default-Stop:		S 0 1 6
#
# Short-Description: Sendmail Mail Transfer Agent (MTA)
### END INIT INFO

# License: CDDL 1.0
# Copyright 2007-2015, Jens Elkner.

export PATH="/bin:/usr/bin:/sbin:/usr/sbin"

DEFAULT_FILE="/etc/default/sendmail"
SENDMAIL="/usr/sbin/sendmail"
NEWALIASES='/usr/sbin/newaliases'
ALIASES_FILE='/etc/mail/aliases'
ETRN='/usr/sbin/etrn'

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'
	[pidfile]="/var/run/sendmail.pid" [mode]='-bl'
)
typeset -r CLIENTDEFAULTS=(
	[qdir]='/var/spool/clientmqueue' [qown]='smmsp' [qgrp]='smmsp'
	[qperm]='0770' [cf]='/etc/mail/submit.cf' [mc]='/etc/mail/submit.mc'
	[pidfile]='/var/spool/clientmqueue/sm-client.pid' [mode]='-Ac'
)

# 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='-DSUN_HIDE_INTERNAL_DETAILS'
	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 BN
	typeset -n VAL=$1
	VAL=-1
	if (( VAL == -1 )); then
		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
	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]}
		# Source in "tweakings":
		# * MODE should be '-bd' or null (MODE= or MODE='') or
		#   left alone.  Anything else and you're on your own.
		# * CLIENTMODE should be '-Ac' or null (as above) or
		#   left alone.  Anything else and you're on your own.
		# * [CLIENT]QUEUEOPTION should be "p" or null (as above).
		# * [CLIENT]QUEUEINTERVAL should be set to some legal value;
		#   sanity checks are done below.
		# * [CLIENT]OPTIONS are catch-alls; set with care.
		[[ -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} == 'true' ]] && DMODE='-bl'
		fi

		${SENDMAIL} ${DMODE} -q${QOPT}${QINTERVAL} ${OPTS} &
	
		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}
