#!/bin/sh

# usage
#	print usage and exit
usage() {
	cat << EOF
Usage: $0 [-qr] command [arguments...]

Commands:
  install		- perform automatic configuration and start the IE
  checkperms*		- check and fix file permissions
  disable		- disable IE for the Exim and stop the daemons
  enable		- configure Exim and start the IE daemons
  set-qport [port]	- set quarantine server TCP port number
  set-qtoken [token]	- install quarantine security token
  set-rwc [token]	- set rspamd worker-controller configuration
  start			- start the imunify email daemons
  stop			- stop the imunify email daemons
  status		- print status of the imunify email system

Options:
  -q			- be silent about configuration progress
  -r			- submit last error message to the Imunify Email team
EOF
	exit 0
}

# getpwuid_dir id
#	securely get user home directory by the given id
getpwuid_dir()
{
	_id=$1
	awk -F: -v id="$_id" '(id == $3) {print $6}' /etc/passwd
}

# dprint level message
#	print a message to stderr if requested debug level
#	is greater than DEBUG_LEVEL global variable
dprint()
{
	_level=$1
	shift

	case "$DEBUG_LEVEL" in
	[0-9])
		if [ "0$_level" -le "0$DEBUG_LEVEL" ] ; then
			echo 1>&2 "$0: $*"
		fi
		;;
	*)
		err 1 "DEBUG_LEVEL variable contains garbage"
		;;
	esac
}

# warn message
#	print warning message to stderr
warn()
{
	echo 1>&2 "$0: WARNING: $*"
}

# err return_code message
#	print error message to stderr and terminate execution
err()
{
	_code=$1
	shift

	echo 1>&2 "$0: ERROR: $*"

	_dsn_file="/usr/share/imunifyemail/imunifyemail.dsn"

	if [	-n "$REPORT_ERROR" -a "$REPORT_ERROR" = "yes" \
		-a -x /usr/bin/sentry-cli \
		-a -r "$_dsn_file" ]
	then
		/usr/bin/env SENTRY_DSN=$(head -1 "$_dsn_file") \
		    /usr/bin/sentry-cli send-event \
			-e "source:ie-config" \
			-e "errorcode:$_code" \
			-r "0.4" \
			-m "$0: ERROR: $*" > /dev/null

		if [ $? -eq 0 ] ; then
			dprint 1 "(the error message reported)"
		fi
	fi

	backup_cleanup "$BACKUP"

	exit $_code
}


# auth_root
#	check is user authenticated to use this script
auth_root()
{
	if [ $(id -u) -ne 0 ] ; then
		err 2 "must be root to run this command"
	fi
}

# findfile path
#	check and resolve path to fully qualified file name
fqfn()
{
	if [ -e "$1" ]; then
		echo "$(cd $(dirname "$1") && pwd)/$(basename "$1")"
	else
		warn "$1 not found"
		echo ""
	fi
}

# backup_file archive files...
#	pack given files into archive for backup
#	please note, it's okay to have some files missed
backup_file()
{
	_archive=$1
	shift

	if [ -z "$_archive" ] ; then
		warn "no backup archive name given"
		return
	fi

	# create empty $_archive so the subsequent tar -u can succeed
	if [ ! -f "$_archive" ] ; then
		umask 077
		touch "$_archive" || \
			err 3 "$_archive: can't open archive for writing"
	fi

	for _arg in $*; do
		_file=$(fqfn "$_arg" | sed 's:^/::')

		# backup only once!
		if [ -s "$_archive" ] && \
		   tar -tf "$_archive" | grep -q "^$_file$" ; then
			continue
		fi

		if [ -n "$_file" -a -r "/$_file" ] ; then
			dprint 2 "  backing up /$_file"
			if ! tar -uf "$_archive" -C/ "$_file" ; then
				err 3 "$_file can't be backed up"
			fi
		fi
	done
}

# backup_cleanup archive
#	remove unused backup archive or notify user
backup_cleanup()
{
	_archive="$1"

	if [ -n "$_archive" ] ; then
		if [ -s "$_archive" ] ; then
			dprint 1 "backup file is at $_archive"
		else
			dprint 3 "remove unused backup file"
			if [ -f "$_archive" ] ; then
				rm "$_archive"
			fi
		fi
	fi
}

# cfg_exim
#	configure Exim to use the imunifyemail postfilter
cfg_exim()
{
	_name="cfg_exim"

	_exim_conf="/etc/exim.conf.local"
	_buildeximconf_cmd="/usr/local/cpanel/scripts/buildeximconf"
	_spamfilter_cmd="/usr/libexec/ie-spamfilter"
	_spamfilter_conf="/etc/rspamd/ie-spamfilter.conf"

	dprint 2 "$_name: analyzing files"
	if [ ! -f "$_exim_conf" ] ; then
		# if no config file available, create our own
		dprint 2 "$_name: creating empty $_exim_conf template"
		cat <<- EOF > "$_exim_conf"
			@PREROUTERS@

			@ROUTERSTART@

			@ROUTERMIDDLE@

			@ROUTEREND@

			@TRANSPORTSTART@

			@TRANSPORTMIDDLE@

			@TRANSPORTEND@

			EOF
	fi

	if [ ! -w "$_exim_conf" ] ; then
		err 3 "$_name: $_exim_conf: file unwriteable"
	fi
	if ! grep -q '@PREROUTERS@' $_exim_conf &&
	     grep -q '@TRANSPORTSTART@' $_exim_conf ; then
		err 3 "$_name: $_exim_conf: unknown format"
	fi
	if [ ! -x "$_buildeximconf_cmd" ] ; then
		err 3 "$_name: $_buildeximconf_cmd: non executable"
	fi

	if [ ! -x "$_spamfilter_cmd" ] ; then
		err 3 "$_name: $_spamfilter_cmd: non executable"
	fi
	if [ ! -f "$_spamfilter_conf" ] ; then
		err 3 "$_name: $_spamfilter_conf: not found"
	fi

	if ! grep -q '^imunifyemail_spamfilter.*:' $_exim_conf ; then
		dprint 2 "$_name: editing $_exim_conf"
		backup_file "$BACKUP" "$_exim_conf"
		sed -i '
/^ *@PREROUTERS@/a\
\
imunifyemail_spamfilter_router:\
  driver = manualroute\
  local_parts = !"@dovenull"\
  domains  = !+local_domains : !+relay_domains\
  condition = ${if eq {$received_protocol}{spam-scanner} {0}{1} }\
  condition = ${if eq {$authenticated_id}{_imunifyemail} {0}{1} }\
  transport = imunifyemail_spamfilter_transport\
  route_list = *\
#imunifyemail_spamfilter_router END\

/^ *@TRANSPORTSTART@/a\
\
imunifyemail_spamfilter_transport:\
  driver = pipe\
  command = "'"$_spamfilter_cmd"' -c '"$_spamfilter_conf"' $pipe_addresses"\
  environment = "IP=$sender_host_address : AUTH_SENDER=$authenticated_id"\
  log_output\
  message_prefix =\
  batch_max = 1000\
  batch_id = $domain\
  temp_errors = 2\
#imunifyemail_spamfilter_transport END\
' $_exim_conf || err 250 "$_name: unexpected error (sed)"

		dprint 2 "$_name: calling buildeximconf"
		if ! $_buildeximconf_cmd ; then
			# XXX how to recover if it fails?
			err 5 "$_name: buildeximconf failed; please check configs"
		fi

		dprint 2 "$_name: restarting exim"
		# should we also catch errors from the folowing?
		/bin/systemctl restart exim.service
	else
		dprint 1 "$_name: $_exim_conf was edited before"
	fi

}

# uncfg_exim
#	remove the spamfilter declarations from the exim.conf
uncfg_exim()
{
	_name="uncfg_exim"

	_exim_conf="/etc/exim.conf.local"
	_buildeximconf_cmd="/usr/local/cpanel/scripts/buildeximconf"
	_spamfilter_cmd="/usr/libexec/ie-spamfilter"
	_spamfilter_conf="/etc/rspamd/ie-spamfilter.conf"

	dprint 2 "$_name: analyzing files"
	if [ ! -f "$_exim_conf" ] ; then
		err 3 "$_name: $_exim_conf: no such file"
	fi
	if [ ! -w "$_exim_conf" ] ; then
		err 3 "$_name: $_exim_conf: file unwriteable"
	fi
	if [ ! -x "$_buildeximconf_cmd" ] ; then
		err 3 "$_name: $_buildeximconf_cmd: non executable"
	fi

	if grep -q '^imunifyemail_spamfilter.*:' $_exim_conf ; then
		dprint 2 "$_name: editing $_exim_conf"
		backup_file "$BACKUP" "$_exim_conf"
		# For now, drop everything between the ^imunifyemail_spamfilter
		# and the end of the relevant block. TODO: Would be nice to
		# replace it with /^start/,/^stop/ logic though.
		sed -i '/^imunifyemail_spamfilter.*:$/h
			/^# *imunifyemail_spamfilter.*END/{h;d}
			/^[[:space:]]*$/!{
				x
				/^imunifyemail_spamfilter.*:$/{
					x;d;b
				}
				x
			}' $_exim_conf || err 250 "$_name: unexpected error (sed)"

		dprint 2 "$_name: calling buildeximconf"
		if ! $_buildeximconf_cmd ; then
			# XXX how to recover if it fails?
			err 5 "$_name: buildeximconf failed; please check configs"
		fi

		dprint 2 "$_name: restarting exim"
		# should we also catch errors from the folowing?
		/bin/systemctl restart exim.service
	else
		dprint 1 "$_name: $_exim_conf is clean, skipping"
	fi
}

# print_status
#	check the imunify email system status
print_status()
{
	_name="print_status"
	dprint 2 "$_name: checking system state"

	_exim_conf="/etc/exim.conf"
	if [ -r "$_exim_conf" ] ; then
		echo -n "spamfilter exim configuation: "
		if grep -q '^imunifyemail_spamfilter.*:' "$_exim_conf" ; then
			echo "enabled"
		else
			echo "disabled"
		fi
	else
		warn "$_name: $_exim_conf: unreadable"
	fi

	for _svc in ie-quarantine rspamd ; do
		echo -n "$_svc state: "
		/bin/systemctl show $_svc --property=ActiveState,SubState \
			| sort \
			| sed '/^ActiveState=/{
				N
				s/^ActiveState=\([^ ]*\)/\1/
				s/\nSubState=\([^ ]*\)$/ (\1)/
			}'
	done
}

# qport port
#	set quarantine server address port
cfg_qport()
{
	_name="cfg_qport"

	_port="$1"

	if [ -z "$_port" ] ; then
		_port=8443
		dprint 2 "$_name: using default port number $_port"
	fi
	if ! echo "$_port" | egrep -q '^[0-9]+$' ; then
		err 1 "$_name: $_port: invalid port number"
	fi

	_imunifyemail_conf="/etc/rspamd/modules.d/imunifyemail.conf"
	_quarantine_conf="/etc/imunifyemail/quarantine.yaml"

	dprint 2 "$_name: analyzing files"
	if [ ! -w "$_imunifyemail_conf" ] ; then
		err 3 "$_name: $_imunifyemail_conf: file unwriteable"
	fi
	if [ ! -w "$_quarantine_conf" ] ; then
		err 3 "$_name: $_quarantine_conf: file unwriteable"
	fi

	dprint 2 "$_name: editing $_imunifyemail_conf"
	backup_file "$BACKUP" "$_imunifyemail_conf"

	# N.B. for now, we use no HTTPS
	_api_url="http://localhost:$_port/quarantine/api/v1/emails";
	sed -i '/^quarantine {/,/^} *$/{
		/^[[:space:]]*api_url *=/c\
	api_url = "'$_api_url'";
	}' "$_imunifyemail_conf" || err 250 "$_name: unexpected error (sed)"

	dprint 2 "$_name: editing $_quarantine_conf"
	backup_file "$BACKUP" "$_quarantine_conf"
	sed -i '/^server:/,/^[a-z0-9]+:/{
		s/^\([[:space:]]*address:\).*$/\1 ":'"$_port"'"/
	}' "$_quarantine_conf" || err 250 "$_name: unexpected error (sed)"
}

# qtoken token
#	install new security token for the Quarantine 
cfg_qtoken()
{
	_name="cfg_qtoken"

	_token="$1"

	# In the case if there's no token given, generate a random string.
	# To obtain at least 512 bits key from a-zA-Z0-9 sequence it's
	# required at least ceil(512/log2(26+26+10)) symbols.
	if [ -z "$_token" ] ; then
		dprint 2 "$_name: generating new security token"
		_token=$(tr -cd a-zA-Z0-9 < /dev/urandom | head -c 86)
	fi

	if ! echo "$_token" | egrep -q '^[a-zA-Z0-9]+$' ; then
		err 1 "$_name: token must be of alphanumeric characters only"
	fi

	_imunifyemail_conf="/etc/rspamd/modules.d/imunifyemail.conf"
	_quarantine_conf="/etc/imunifyemail/quarantine.yaml"

	dprint 2 "$_name: analyzing files"
	if [ ! -w "$_imunifyemail_conf" ] ; then
		err 3 "$_name: $_imunifyemail_conf: file unwriteable"
	fi
	if [ ! -w "$_quarantine_conf" ] ; then
		err 3 "$_name: $_quarantine_conf: file unwriteable"
	fi

	dprint 2 "$_name: editing $_imunifyemail_conf"
	backup_file "$BACKUP" "$_imunifyemail_conf"
	sed -i '/^quarantine {/,/^}$/{
		s/^\([[:space:]]*api_token *=\).*$/\1 "'"$_token"'";/
	}' "$_imunifyemail_conf" || err 250 "$_name: unexpected error (sed)"

	dprint 2 "$_name: editing $_quarantine_conf"
	backup_file "$BACKUP" "$_quarantine_conf"
	sed -i '/^server:/,/^[a-z0-9]+:/{
		s/^\([[:space:]]*rspamdToken:\).*$/\1 "'"$_token"'"/
	}' "$_quarantine_conf" || err 250 "$_name: unexpected error (sed)"
}

# cfg_rwc token
#	reconfigure the rspamd worker-controller.inc
cfg_rwc()
{
	_name="cfg_rwc"

	_password="$1"

	# In the case if there's no password given, generate a random string
	# XXX - must be submitted to the stats report daemon instead
	if [ -z "$_password" ] ; then
		_password=$(tr -cd a-zA-Z0-9 < /dev/urandom | head -c 24)
		dprint 1 "$_name: random password generated"
	fi

	if ! echo "$_password" | egrep -q '^[a-zA-Z0-9_.]+$' ; then
		err 1 "$_name: password must be of alphanumeric characters only"
	fi

	_token=$(/usr/bin/rspamadm pw -p "$_password") \
		|| err 250 "$_name: can't generate security token"

	_quarantine_conf="/etc/imunifyemail/quarantine.yaml"
	_worker_controller="/etc/rspamd/override.d/worker-controller.inc"
	_rspamd_group="_rspamd"
	_rspamd_ctladdr="localhost:11334"

	dprint 2 "$_name: replacing $_worker_controller"
	backup_file "$BACKUP" "$_worker_controller"

	touch "$_worker_controller" \
		|| err 250 "$_name: unexpected error (touch)"
	chown root:_rspamd "$_worker_controller" \
		|| err 250 "$_name: unexpected error (chown)"
	chmod 0640 "$_worker_controller" \
		|| err 250 "$_name: unexpected error (chmod)"

	cat <<- EOF > "$_worker_controller"
		bind_socket = "$_rspamd_ctladdr";
		password = "$_token";
	EOF

	dprint 2 "$_name: editing $_quarantine_conf"
	backup_file "$BACKUP" "$_quarantine_conf"
	sed -i '/^stat:/,/^[a-z0-9]+:/{
		s/^\([[:space:]]*rspamdCtlAddress:\).*$/\1 http:\/\/'$_rspamd_ctladdr'/
		s/^\([[:space:]]*rspamdCtlPassword:\).*$/\1 '$_password'/
	}' "$_quarantine_conf" || err 250 "$_name: unexpected error (sed)"
}

# svc_ie
#	start the imunify daemons, in order
svc_ie()
{
	_name="svc_ie"

	case "$1" in
	start|stop|status|restart|enable|disable)
		_cmd=$1 ;;
	*)
		err 1 "$_name: $1: bad command" ;;
	esac

	dprint 2 "$_name: $_cmd rspamd"
	if ! /bin/systemctl $_cmd rspamd.service ; then
		err 5 "$_name: rspamd $_cmd failed"
	fi

	dprint 2 "$_name: $_cmd ie-quarantine"
	if ! /bin/systemctl $_cmd ie-quarantine.service ; then
		err 5 "$_name: ie-quarantine $_cmd failed"
	fi
}

#
#	Entry point
#

# TODO: move this out to the command line
export DEBUG_LEVEL=2

# we don't want the environment to affect the script
export LC_ALL=C
export LC_CTYPE=C
export LANG=""
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
export HOME=$(getpwuid_dir $(id -u))

# use global backup archive to save everything
BACKUP=$(date +"$HOME/imunifyemail-backup-$$-%s.tar")
export BACKUP

export REPORT_ERROR=no

while getopts qr f ; do
	case "$f" in
		q)	export DEBUG_LEVEL=0 ;;
		r)	export REPORT_ERROR="yes" ;;
		\?)	usage ;; # NOT REACHED
	esac
done
shift $(expr $OPTIND - 1)

case "$1" in
install)
	auth_root
	cfg_qport
	cfg_qtoken
	cfg_rwc
	svc_ie enable
	svc_ie start
	cfg_exim
	;;
enable)
	auth_root
	svc_ie enable
	svc_ie start
	cfg_exim
	;;
disable)
	auth_root
	uncfg_exim
	svc_ie stop
	svc_ie disable
	;;
checkperms)
	#TODO
	err 255 'checkperms: not implemented'
	;;
set-qport)
	arg=$2
	auth_root
	cfg_qport "$arg"
	;;
set-qtoken)
	arg=$2
	auth_root
	cfg_qtoken "$arg"
	;;
set-rwc)
	arg=$2
	auth_root
	cfg_rwc "$arg"
	;;
start)
	auth_root
	svc_ie start
	;;
stop)
	auth_root
	svc_ie stop
	;;
status)
	print_status
	;;
*)
	usage
	# not reached
	;;
esac

dprint 2 "finished"
backup_cleanup "$BACKUP"
