#!/bin/sh

#++
# NAME
#	postfix-script 1
# SUMMARY
#	execute Postfix administrative commands
# SYNOPSIS
#	\fBpostfix-script\fR \fIcommand\fR
# DESCRIPTION
#	The \fBpostfix-script\fR script executes Postfix administrative
#	commands in an environment that is set up by the \fBpostfix\fR(1)
#	command.
# SEE ALSO
#	master(8) Postfix master program
#	postfix(1) Postfix administrative interface
# LICENSE
# .ad
# .fi
#	The Secure Mailer license must be distributed with this software.
# AUTHOR(S)
#	Wietse Venema
#	IBM T.J. Watson Research
#	P.O. Box 704
#	Yorktown Heights, NY 10598, USA
#
#	Wietse Venema
#	Google, Inc.
#	111 8th Avenue
#	New York, NY 10011, USA
#
#	Wietse Venema
#	porcupine.org
#	Amawalk, NY 10501, USA
#--

# Avoid POSIX death due to SIGHUP when some parent process exits.

trap '' 1

case $daemon_directory in
"") echo This script must be run by the postfix command. 1>&2
    echo Do not run directly. 1>&2
    exit 1
esac

LOGGER="$command_directory/postlog -t $MAIL_LOGTAG/postfix-script"
INFO="$LOGGER -p info"
WARN="$LOGGER -p warn"
ERROR="$LOGGER -p error"
FATAL="$LOGGER -p fatal"
PANIC="$LOGGER -p panic"

umask 022
SHELL=/bin/sh

#
# Can't do much without these in place.
#
cd $command_directory || {
	$FATAL no Postfix command directory $command_directory!
	exit 1
}
cd $daemon_directory || {
	$FATAL no Postfix daemon directory $daemon_directory!
	exit 1
}
test -f master || {
	$FATAL no Postfix master program $daemon_directory/master!
	exit 1
}
cd $config_directory || {
	$FATAL no Postfix configuration directory $config_directory!
	exit 1
}
case $shlib_directory in
no) ;;
 *) cd $shlib_directory || {
	$FATAL no Postfix shared-library directory $shlib_directory!
        exit 1
    }
esac
cd $meta_directory || {
	$FATAL no Postfix meta directory $meta_directory!
	exit 1
}
cd $queue_directory || {
	$FATAL no Postfix queue directory $queue_directory!
	exit 1
}
def_config_directory=`$command_directory/postconf -dh config_directory` || {
	$FATAL cannot execute $command_directory/postconf!
	exit 1
}

# If this is a secondary instance, don't touch shared files.

instances=`test ! -f $def_config_directory/main.cf ||
    $command_directory/postconf -qc $def_config_directory \
    -h multi_instance_directories | sed 'y/,/ /'` || {
	$FATAL cannot execute $command_directory/postconf!
	exit 1
}

check_shared_files=1
for name in $instances
do
    case "$name" in
    "$def_config_directory") ;;
    "$config_directory") check_shared_files=; break;;
    esac
done

#
# Parse JCL
#
case $1 in

start_msg)

	echo "Start postfix"
	;;

stop_msg)

	echo "Stop postfix"
	;;

debian-systemd-start) # debian-specific
	# Second part of the startup.  First part is done in ExecStartPre.
	rm -f $queue_directory/quick-start
	rm -f $queue_directory/debian-systemd-start #XXX read MAIL_DEBUG & MAIL_VERBOSE
	[ -d /run/systemd/system ] && [ -n "$INVOCATION_ID" ] || {
		$FATAL "the Postfix mail system is started through systemd but not under systemd?"
		exit 1
	}
	$daemon_directory/master -w
	;;

start|start-fg)

	$daemon_directory/master -t 2>/dev/null || {
		$FATAL the Postfix mail system is already running
		exit 1
	}

	if [ -f $queue_directory/debian-systemd-start ] # debian-specific
	then # same as in debian-systemd-start patch, keep it here so old systemd unit works
	    rm -f $queue_directory/debian-systemd-start #XXX read MAIL_DEBUG & MAIL_VERBOSE
	    rm -f $queue_directory/quick-start
	    [ -d /run/systemd/system ] && [ -n "$INVOCATION_ID" ] || {
		$FATAL "the Postfix mail system is started through systemd but not under systemd?"
		exit 1
	    }
	    $daemon_directory/master -w
	    exit
	fi

	if [ -f $queue_directory/quick-start ]
	then
		rm -f $queue_directory/quick-start
	else
		$daemon_directory/postfix-script check-fatal || {
			$FATAL Postfix integrity check failed!
			exit 1
		}
		# Foreground this so it can be stopped. All inodes are cached.
		$daemon_directory/postfix-script check-warn
	fi

	if [ -d /run/systemd/system ] && [ ! "$INVOCATION_ID" ] && # debian-specific
	   [ $1 = start ]
	then
	    srv=$(systemd-escape -p $config_directory) || exit 1
	    case "$srv" in
		(etc-postfix) srv=postfix.service ;;
		(etc-postfix\\x2d*) srv=postfix@${srv#*\\x2d}.service ;;
		(*) srv= ;;
	    esac
	    if [ -n "$srv" ]; then
		$INFO "redirecting the Postfix mail system startup to systemd $srv" || exit 1
		touch $queue_directory/debian-systemd-start #XXX write MAIL_DEBUG & MAIL_VERBOSE
		systemctl start $srv || {
		    $FATAL "mail system startup through systemd failed"
		    exit 1
		}
		exit 0
	    fi
	fi

	$INFO starting the Postfix mail system || exit 1
	case $1 in
	start)
	    # NOTE: wait in foreground process to get the initialization status.
	    $daemon_directory/master -w || {
		$FATAL "mail system startup failed"
		exit 1
	    }
	    ;;
	start-fg)
	    # Foreground start-up is incompatible with multi-instance mode.
	    # Use "exec $daemon_directory/master" only if PID == 1.
	    # Otherwise, doing so would break process group management,
	    # and "postfix stop" would kill too many processes.
	    case $instances in
	    "") case $$ in
		 1) exec $daemon_directory/master -i
		    $FATAL "cannot start-fg the master daemon"
		    exit 1;;
		 *) $daemon_directory/master -s;;
		esac
		;;
	     *) $FATAL "start-fg does not support multi_instance_directories"
		exit 1
		;;
	    esac
	    ;;
	esac
	;;

drain)

	$daemon_directory/master -t 2>/dev/null && {
		$FATAL the Postfix mail system is not running
		exit 1
	}
	$INFO stopping the Postfix mail system
	kill -9 `sed 1q pid/master.pid`
	;;

quick-stop)

	$daemon_directory/postfix-script stop
	touch $queue_directory/quick-start
	;;

stop)

	$daemon_directory/master -t 2>/dev/null && {
		$FATAL the Postfix mail system is not running
		exit 1
	}
	$INFO stopping the Postfix mail system
	kill `sed 1q pid/master.pid`
	for i in 5 4 3 2 1
	do
	    $daemon_directory/master -t && exit 0
	    $INFO waiting for the Postfix mail system to terminate
	    sleep 1
	done
	$WARN stopping the Postfix mail system with force
	pid=`awk '{ print $1; exit 0 } END { exit 1 }' pid/master.pid` && 
		kill -9 -$pid
	;;

abort)

	$daemon_directory/master -t 2>/dev/null && {
		$FATAL the Postfix mail system is not running
		exit 1
	}
	$INFO aborting the Postfix mail system
	kill `sed 1q pid/master.pid`
	;;

reload)

	# Warn once for deprecated parameters.
	$command_directory/postconf >/dev/null

	$daemon_directory/master -t 2>/dev/null && {
		$FATAL the Postfix mail system is not running
		exit 1
	}
	$INFO refreshing the Postfix mail system
	$command_directory/postsuper active || exit 1
	kill -HUP `sed 1q pid/master.pid`
	$command_directory/postsuper &
	;;

flush)

	cd $queue_directory || {
		$FATAL no Postfix queue directory $queue_directory!
		exit 1
	}
	$command_directory/postqueue -f
	;;

check)

	$daemon_directory/postfix-script check-fatal || exit 1
	$daemon_directory/postfix-script check-warn
	exit 0
	;;

status)

	# Warn once for deprecated parameters.
	$command_directory/postconf >/dev/null

	$daemon_directory/master -t 2>/dev/null && {
		$INFO the Postfix mail system is not running
		exit 1
	}
	$INFO the Postfix mail system is running: PID: `sed 1q pid/master.pid`
	exit 0
	;;


check-fatal)
	# This command is NOT part of the public interface.

	$SHELL $daemon_directory/post-install create-missing || {
		$FATAL unable to create missing queue directories
		exit 1
	}

	# Look for incomplete installations.

	test -f $config_directory/master.cf || {
		$FATAL no $config_directory/master.cf file found
		exit 1
	}

	maillog_file=`$command_directory/postconf -qh maillog_file` || {
		$FATAL cannot execute $command_directory/postconf!
		exit 1
	}
	test -n "$maillog_file" && {
		$command_directory/postconf -qM postlog/unix-dgram 2>/dev/null \
		    | grep . >/dev/null || {
			$FATAL "missing 'postlog' service in master.cf - run 'postfix upgrade-configuration'"
			exit 1
		}
	}

	# See if all queue files are in the right place. This is slow.
	# We must scan all queues for mis-named queue files before the
	# mail system can run.

	$command_directory/postsuper || exit 1
	exit 0
	;;

check-warn)
	# This command is NOT part of the public interface.

	# Warn once for deprecated parameters.
	$command_directory/postconf >/dev/null

	# Check Postfix root-owned directory owner/permissions.

	find $queue_directory/. $queue_directory/pid \
	    -prune ! -user root \
	    -exec $WARN not owned by root: {} \;

	find $queue_directory/. $queue_directory/pid \
	    -prune \( -perm -020 -o -perm -002 \) \
	    -exec $WARN group or other writable: {} \;

	# Check Postfix root-owned directory tree owner/permissions.

	todo="$config_directory/."
	test -n "$check_shared_files" && {
		todo="$daemon_directory/. $meta_directory/. $todo"
		test "$shlib_directory" = "no" || 
		    todo="$shlib_directory/. $todo"
	}
	todo=`echo "$todo" | tr ' ' '\12' | sort -u`

	find $todo ! -user root \
	    -exec $WARN not owned by root: {} \;

	find $todo \( -perm -020 -o -perm -002 \) \
	    -exec $WARN group or other writable: {} \;

	# Check Postfix mail_owner-owned directory tree owner/permissions.

	find $data_directory/. ! -user $mail_owner \
	    -exec $WARN not owned by $mail_owner: {} \;

	find $data_directory/. \( -perm -020 -o -perm -002 \) \
	    -exec $WARN group or other writable: {} \;

	# Check Postfix mail_owner-owned directory tree owner.

	find `ls -d $queue_directory/* | \
	    grep -E '/(saved|incoming|active|defer|deferred|bounce|hold|trace|corrupt|public|private|flush)$'` \
	    ! \( -type p -o -type s \) ! -user $mail_owner \
		-exec $WARN not owned by $mail_owner: {} \;

	# WARNING: this should not descend into the maildrop directory.
	# maildrop is the least trusted Postfix directory.

	find $queue_directory/maildrop -prune ! -user $mail_owner \
	    -exec $WARN not owned by $mail_owner: $queue_directory/maildrop \;

	# Check Postfix setgid_group-owned directory and file group/permissions.

	todo="$queue_directory/public $queue_directory/maildrop"
	test -n "$check_shared_files" && 
	   todo="$command_directory/postqueue $command_directory/postdrop $todo"

	find $todo \
	    -prune ! -group $setgid_group \
	    -exec $WARN not owned by group $setgid_group: {} \;

	test -n "$check_shared_files" &&
	find $command_directory/postqueue $command_directory/postdrop \
	    -prune ! -perm -02111 \
	    -exec $WARN not set-gid or not owner+group+world executable: {} \;

	# Check non-Postfix root-owned directory tree owner/content.

	for dir in bin etc lib sbin usr
	do
	    test -d $dir && {
		find $dir ! -user root \
		    -exec $WARN not owned by root: $queue_directory/{} \;

		find $dir -type f -print | while read path
		do
		    test -f /$path && {
			cmp -s $path /$path || 
			    $WARN $queue_directory/$path and /$path differ
		    }
		done
	    }
	done

	find corrupt -type f -exec $WARN damaged message: {} \;

	# Check for non-Postfix MTA remnants.

	test -n "$check_shared_files" -a -f /usr/sbin/sendmail -a \
		-f /usr/lib/sendmail && {
	    cmp -s /usr/sbin/sendmail /usr/lib/sendmail || {
		$WARN /usr/lib/sendmail and /usr/sbin/sendmail differ
		$WARN Replace one by a symbolic link to the other
	    }
	}
	exit 0
	;;

set-permissions|upgrade-configuration)
	$daemon_directory/post-install create-missing "$@"
	;;

post-install)
	# Currently not part of the public interface.
	shift
	$daemon_directory/post-install "$@"
	;;

chroot) # debian-specific
	shift
	r=`$command_directory/postconf -h compatibility_level` || exit
	if expr $r : '\([0-9]*\)' \< 3 >/dev/null
	then yes='[-y]' no='[n]' # postfix  <3.0, - means y
	else yes='[y]' no='[-n]' # postfix >=3.0, - means n
	fi
	while getopts nSXc r; do case "$r" in
	    n) show=echo;; # do not make changes, only show
	    S) nosimple=y;; # omit known simple/safe services
	    X) nocomplex=y;; # omit known complex/unsafe services
	    c) custom=y;; # also apply to custom commands if any
	    *) exit 1;;
	esac; done
	shift `expr ${OPTIND} - 1`
	case "$1" in
	    on | y) verify="$yes" set=y ;;
	    off | n) verify="$no"  set=n ;;
	    query | q | "") verify="$yes" set= ;;
	    *) echo "E: chroot: unknown arg $1 (expected [-n][-S][-X][-c] on|off|query)" >&2
	       exit 1
	    ;;
	esac
	r=`$command_directory/postconf -M |
	  { while read name type x x chroot x x cmd x; do
		case $cmd in

		    local|pipe|postlogd|proxymap|spawn|virtual)
			continue ;; # ignore non-chrootable ones

		    anvil|bounce|cleanup|discard|dnsblog|error|flush|pickup| \
		    postscreen|scache|showq|tlsmgr|tlsproxy|trivial-rewrite| \
		    verify|nqmgr|oqmgr|qmgr|qmqpd)
			[ -n "$nosimple" ] && continue;;

		    lmtp|smtp|smtpd)
			[ -n "$nocomplex" ] && continue;;

		    *) # a custom/unknown/unrecognized command
			if [ -z "$custom" ]; then
			   echo "W: custom service $name/$type/command=$cmd (ignored)" >&2
			   continue
			fi
			;;

		esac
		case "$chroot" in
		    $verify) match=y ;;
		    *) nomatch=y; [ -n "$set" ] && echo $name/$type/chroot=$set ;;
		esac
	    done
	    if [ -z "$set" ]; then # query only
		case "$match:$nomatch" in
		    y:)  echo on ;;
		    :y)  echo off ;;
		    y:y) echo mixed ;;
		    :)   echo off ;;
		esac
	    fi
	  }
	`
	if [ -z "$set" ]; then # query only
	    echo $r
	elif [ -n "$r" ]; then # something should be changed
	    $show $command_directory/postconf -c $config_directory \
		-F $r
	fi
	;;

tls)
	shift
	$daemon_directory/postfix-tls-script "$@"
	;;

/*)
	# Currently not part of the public interface.
	"$@"
	;;

logrotate)
	case $# in
	1) ;;
	*) $FATAL "usage postfix $1 (no arguments)"; exit 1;;
	esac
	for name in maillog_file maillog_file_compressor \
	   maillog_file_rotate_suffix
	do
	    value="`$command_directory/postconf -qh $name`"
	    case "$value" in
	    "") $FATAL "empty '$name' parameter value - logfile rotation failed"
		exit 1;;
	    esac
	    eval $name='"$value"';
	done

	case "$maillog_file" in
	/dev/*) $FATAL "not rotating '$maillog_file'"; exit 1;;
	esac

	errors=`(
	    suffix="\`date +$maillog_file_rotate_suffix\`" || exit 1
	    mv "$maillog_file" "$maillog_file.$suffix" || exit 1
	    $daemon_directory/master -t 2>/dev/null ||
		kill -HUP \`sed 1q pid/master.pid\` || exit 1
	    sleep 1
	    "$maillog_file_compressor" "$maillog_file.$suffix" || exit 1
	) 2>&1` || {
	    $FATAL "logfile '$maillog_file' rotation failed: $errors"
	    exit 1
	}
	;;

*)
	$FATAL "unknown command: '$1'. Usage: postfix start (or stop, reload, abort, flush, check, status, set-permissions, upgrade-configuration, logrotate)"
	exit 1
	;;

esac
