#!/bin/sh

##--------------------------------------------------------------
##
##  msmtpq-ng-queue: List and manage msmtpq-ng queue
##		     Based on msmtpq by Chris Gianniotis which
##		     based on the concept from Martin Lambers
##		     msmtpqueue scripts.
##                   Rewritten by Daniel Dickinson
##  Copyright (C) 2008 - 2015 Chris Gianniotis
##  Copyright (C) 2016-2019 Daniel Dickinson
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or, at
##  your option, any later version.
##
##--------------------------------------------------------------
##
## msmtpq-ng-queue is a sendmail mailq compatible queue lister
## for msmtpq queues, with the addition of some queue management
## commands from Posfix's postsuper.

## Global variables
LKD=""

[ -z "$MSMTPQ_COMMAND" ] && MSMTPQ_COMMAND="$0"

## Useful functions

dsp() {
	unset L
	for L; do
		[ -n "$L" ] && echo "  $L" || echo
	done
}

err() {
	dsp '' "$1" '' >&2
	if [ -n "$2" ]; then
		exit "$2"
	else
		exit
	fi
}

# Configuration files
#   System-wide defaults in /etc/msmtpq-ng.rc
#   User settings in ~/.msmtpq-ng.rc

if [ "$MSMTP_SKIP_CONF" != "true" ]; then
	# shellcheck source=./tmp/msmtpq-ng.rc
	[ -r /etc/msmtpq-ng.rc ] && . /etc/msmtpq-ng.rc
	# shellcheck source=./tmp/msmtpq-ng.rc
	[ -r ~/.msmtpq-ng.rc ] && . ~/.msmtpq-ng.rc
fi

if [ -n "$MSMTP_OVERRIDE_CONF" ]; then
	# shellcheck source=./tmp/msmtpq-ng.rc
	. "$MSMTP_OVERRIDE_CONF"
	export MSMTP_OVERRIDE_CONF
fi

## Path to msmtpq-ng - only needed if not in PATH
[ -z "$MSMTPQ_NG" ] && MSMTPQ_NG=msmtpq-ng
if [ -z "$(command -v "$MSMTPQ_NG")" ] && [ ! -x "$MSMTPQ_NG" ]; then
	err "msmtpq-ng-queue: Unable to find msmtpq-ng" 72
fi

## Secure log and spool settings
[ -z "$MSMTP_LOG_UMASK" ] && MSMTP_LOG_UMASK=077
[ -z "$MSMTP_UMASK" ] && MSMTP_UMASK=077

## set the queue var to the location of the msmtp queue directory
## the queue dir
[ -z "$Q" ] && Q=~/.msmtp.queue

if [ ! -d "$Q" ]; then
	umask "$MSMTP_UMASK"
	mkdir -p "${Q}"
fi

if [ -z "$LOCK_CMD" ]; then
	LOCK_CMD=flock						# Default to flock
	[ -z "$(command -v "$LOCK_CMD")" ] && LOCK_CMD=lock	# If we don't have flock try busybox lock
	[ -z "$(command -v "$LOCK_CMD")" ] && {
		err "msmtpq-ng: Unable to find lock command" 72	# Exit with EX_OSFILE
	}
fi

##   where it is or where you'd like it to be
##     ( note that the LOG setting could be the same as the )
##     ( 'logfile' setting in .msmtprc - but there may be   )
##     ( some advantage in keeping the two logs separate    )
##   if you don't want the log at all set this var to /dev/null
##     (doing so would be inadvisable under most conditions, however)
##
## the queue log file - modify (or comment out) to taste  (but no quotes !!)
[ -z "$LOG" ] && LOG=~/log/msmtp.queue.log
[ -z "$MAXLOGLEVEL" ] && MAXLOGLEVEL=7		# Default to not displaying debug log messages
[ -d "$(dirname $LOG)" ] ||  {
	if [ "$LOG" != "syslog" ]; then
		err "msmtpq-ng-queue : can't find msmtp log directory [ $(dirname $LOG) ]"
	fi
}

## the lock directory
[ -z "$MSMTP_LOCK_DIR" ] && MSMTP_LOCK_DIR=~/.msmtpq.lock
[ -d "$MSMTP_LOCK_DIR" ] || {
	if [ ! -d "$(dirname "${MSMTP_LOCK_DIR}")" ]; then
		# shellcheck disable=SC2174
		mkdir -m1777 -p "$(dirname "${MSMTP_LOCK_DIR}")"
	fi
	umask $MSMTP_UMASK
	mkdir -p "$MSMTP_LOCK_DIR"
}

## set the queue log file var to the location of the msmtp queue log file
## Only send / display messages marked hold than number of seconds
[ -z "$MSMTP_SEND_DELAY" ] && MSMTP_SEND_DELAY=0

umask $MSMTP_UMASK

# export all configuration globals so that commands
# run inside lock_queue work correctly
export MSMTP_OVERRIDE_CONF MSMTP Q LOG MAXLOGLEVEL MSMTP_LOG_UMASK MSMTP_UMASK MSMTP_HOLD_CLI_MAIL MSMTP_HOLD_MSMTP_MAIL MSMTP_SEND_DELAY MSMTP_MAXIMUM_QUEUE_LIFETIME MSMTP_LOCK_DIR MSMTPQ_NG_QUEUE EMAIL_CONN_TEST EMAIL_CONN_TEST_PING EMAIL_CONN_TEST_IP EMAIL_CONN_TEST_SITE LOCK_CMD ID FQP

usage() {

	echo "$(basename "$MSMTPQ_COMMAND"): [options]"
	echo ""
	echo 'no options	List queue'
	# shellcheck disable=SC2039
	echo '-f		Flush queue'
	# shellcheck disable=SC2039
	echo '-h <ID>		Put message with ID <ID> on hold (ALL for all)'
	# shellcheck disable=SC2039
	echo '-H <ID>		Take message with ID <ID> off hold (ALL for all)'
	# shellcheck disable=SC2039
	echo '-i <ID>		Flush message with ID <ID>'
	# shellcheck disable=SC2039
	echo '-d <ID>		Purge message with ID <ID> (ALL for all)'
	# shellcheck disable=SC2039
	echo '-s <ID>		Show contents of message with ID <ID>'
	# shellcheck disable=SC2039
	echo '--help		This message'
	exit 1
}

## make an entry to the queue log file, possibly an error
##   (log queue changes only ; not interactive chatter)
## usage : log [ -e errcode ] msg [ msg ... ]
##  opts : -e <exit code>  an error ; log msg including error code
## display msg to user, as well
##
log() {
	unset ARG RC PFX PRI LOGLEVEL
	PRI="$1"						# Log priority
	shift

	if [ "$LOG" != "syslog" ]; then
		# time stamp prefix - "[2008 13 Mar 03:59:45] "
		TIMEPFX="[$('date' +'%Y %d %b %H:%M:%S')] msmtpq-ng: "
		PFX="msmtpq-ng: "
		case "$PRI" in
		err)
			PFX="$PFX [ERROR]"
			LOGLEVEL=4
			;;
		notice)
			PFX="$PFX [NOTICE]"
			LOGLEVEL=6
			;;
		info)
			PFX="$PFX [INFO]"
			LOGLEVEL=7
			;;
		esac
		if [ "$LOGLEVEL" -gt "$MAXLOGLEVEL" ]; then
			return
		fi
	fi

	if [ "$1" = '-e' ] ; then				# there's an error exit code
		RC="$2"						# take it
		shift 2						# shift opt & its arg off
	fi

	case "$RC" in
	"71")
		EXITCODE="EX_OSERR: system error"
		;;
	""|"0")
		unset EXITCODE
		;;
	esac

	if [ -n "$EXITCODE" ]; then
		ARG="$*"" ""$EXITCODE"
	else
		ARG="$*"
	fi


	umask $MSMTP_LOG_UMASK
	if [ -n "$ARG" ]; then
		if [ "$MSMTP_QUEUE_QUIET" != "true" ]; then
			dsp "$PFX"" ""$ARG"				# display msg to user, as well as logging it
		fi
		if [ -n "$LOG" ] && [ "$LOG" != "syslog" ]; then	# log is defined and in use
			echo "$TIMEPFX $PFX : $ARG" >> "$LOG"	# line has content ; send it to log
		elif [ "$LOG" = "syslog" ]; then
			CURPID=$$
			echo "$ARG" | logger -t "msmtpq-ng[$CURPID]" -p mail."$PRI"
		fi
	fi
	umask $MSMTP_UMASK
	return 0
}

#
# Prevent multiple process from accessing queue at once
#
# $@: Command and paramters to execute inside the lock
#
lock_err() {
	# Couldn't acquire lock
	log err -e 71 "cannot use queue $Q : Waited $MAX seconds to" \
		"acquire lock [ $LOK ], so giving up." \
		'If you are certain that no other instance of this script' \
		"is running then 'rm -f' the lock file manually: " ''
	exit 71						# exit with EX_OSERR
}

lock_queue() {
	unset LOK MAX SEC RC PARAMS OP
	LOK="${MSMTP_LOCK_DIR}/msmtpq-ng-${ID}.lock"	# lock file name
	MAX=120					# max seconds to gain a lock
	SEC=0 					# seconds waiting
	LOCK_MSMTP="$MSMTPQ_COMMAND"
	if [ "$KCOV_LOCK_QUEUE_MODE" = "true" ]; then
		LOCK_MSMTP="$KCOV_WRAPPER $MSMTPQ_COMMAND"
	fi

	if [ "$(basename "$LOCK_CMD")" = "flock" ]; then		# flock makes life easy
		while [ $SEC -lt $MAX ]; do
			# shellcheck disable=SC2086
			flock -n "$LOK" $LOCK_MSMTP "$@"	# execute commands inside flock
			LRC=$?
			if [ "$LRC" = "1" ] && ! flock -n "$LOK" true; then # if it was a locking error
				sleep 1
			else
				SEC=120
				break
			fi
		done
		if [ "$LRC" = "1" ] && [ "$SEC" = "120" ]; then
			lock_err
		fi
		rm -f "$LOK"				# make sure we don't have file with permissions
							# that blocks others users from locking
		return $LRC				# return exit code of command
	else						# busybox lock
		touch "$LOK" 2>/dev/null
		$LOCK_CMD -n "$LOK" && LKD='t'
		while [ "$LKD" != 't' ] && [ $SEC -lt $MAX ]; do	# lock file present
			sleep 1					# wait a second
			SEC=$((SEC + 1))			# accumulate seconds
			touch "$LOK" 2>/dev/null
			$LOCK_CMD -n "$LOK" && LKD='t'
		done						# try again while locked for MAX secs
		[ "$LKD" != 't' ] && lock_err

		unset LKD
		# shellcheck disable=SC2086
		$LOCK_MSMTP "$@"				# Execute command
		LRC=$?
		$LOCK_CMD -u "$LOK"
		rm -f "$LOK"					# make sure we don't have file with permissions
								# that blocks others users from locking
		return $LRC
	fi
}

list_queue() {
	unset LST CNT MQ
	CNT=0
	unset HID SIZE TIME SENDER RECIPIENT
	MQ="${Q}"
	unset Q

	LST="$(ls -A "$MQ"/*.msmtp 2>/dev/null)"

	if [ "$USER" = "root" ]; then
		MLST="$(find "$(dirname "${MQ}")" -maxdepth 2 -mindepth 2 -type f -name '*.msmtp')"
		[ -n "$MLST" ] && LST="$MLST"
	fi

	for mail in $LST; do
		if [ -r "$mail" ]; then
			CNT=$((CNT + 1))
		fi
	done
	echo "                 $Q ($CNT requests)"
	echo "----Q-ID-------- --Size-- -----Q-Time----- ----------Sender/Recipient----------"
	for mail in $LST; do
		[ ! -r "$mail" ] && continue
		Q="$(dirname "${mail}")"
		HID="$(basename "$mail" .msmtp)"
		if [  -e "${Q}/${HID}.hold" ]; then
			if [ "$(($(date +%s -r "${Q}/${HID}.mail" 2>/dev/null) + MSMTP_SEND_DELAY))" -gt "$(date +%s)" ]; then
				STATUS="-"
			else
				STATUS="!"
			fi
		else
			STATUS="*"
		fi
		# shellcheck disable=SC2012
		SIZE="$(ls -l "$mail"|awk '{ print $5 }')"
		TIME="$(date +"%a %b %d %H:%M" -r "${Q}/${HID}.mail" 2>/dev/null)"
		SENDER="$(sed -e 's/^.*-f \([^ ][^ ]*\) .*$/\1/' "${Q}/${HID}.msmtp" 2>/dev/null)"
		RECIPIENT="$(sed -e 's/^.* -- \([^ ][^ ]*\) *.*$/\1/' "${Q}/${HID}.msmtp" 2>/dev/null)"
		echo "${HID}""${STATUS}" "$(printf "%8d" "${SIZE}")" "${TIME}" "${SENDER}"
		echo "                 $(cat "${Q}"/"${HID}".msg 2>/dev/null)"
		echo "                                           ${RECIPIENT}"
	done
	echo "                 Total requests: $CNT"
	return 0
}

list_messages() {
	unset MLST
	if [ "$2" != "ALL" ]; then
		LST="$(find "$1" -maxdepth 1 -mindepth 1 -name "${2}.${3}" | head -n1)"
		if [ "$USER" = "root" ]; then
			MLST="$(find "$(dirname "${1}")" -maxdepth 2 -mindepth 2 -type f -name "${2}.${3}")"
			[ -n "$MLST" ] && LST="$MLST"
		fi
	else
		LST="$(find "$1" -maxdepth 1 -mindepth 1 -name "*.${3}")"
		if [ "$USER" = "root" ]; then
			MLST="$(find "$(dirname "${1}")" -maxdepth 2 -mindepth 2 -type f -name "*.${3}")"
			[ -n "$MLST" ] && LST="$MLST"
		fi
	fi
}

hold_single_message() {
	touch "${FQP}".hold
	log info "message [ ""$1"" ] held"
}

hold_message() {
	unset LST ID MQ FQP

	list_messages "$Q" "$1" "mail"

	for mail in $LST; do
		HID="$(basename "$mail" .mail)"
		ID="$HID"
		FQP="$Q"/"$HID"
		export ID FQP
		lock_queue -lq hold_single_message "$HID"
	done
}

unhold_single_message() {
	# If send delay has not yet past
	if [ "$(($(date +%s -r "${FQP}.mail") + MSMTP_SEND_DELAY))" -gt "$(date +%s)" ]; then
		# skip this message
		log notice "message [ ""$ID"" ] too new to release"
		return 0
	fi
	rm -f "${FQP}.hold"
	log info "message [ ""$ID"" ] released"
	return 0
}

unhold_message() {
	unset LST ID MQ FQP

	list_messages "$Q" "$1" "hold"

	for hold in $LST; do
		MQ="$(dirname "${hold}")"
		HID="$(basename "${hold}" .hold)"
		ID="$HID"
		FQP="$MQ"/"$ID"
		export FQP ID MSMTP_SEND_DELAY
		lock_queue -lq unhold_single_message
	done
	return 0
}

purge_single_message() {
	rm -f "$FQP".*
	log notice "message [ $ID ] purged"
}

purge_message() {
	unset LST ID MQ FQP

	list_messages "$Q" "$1" "mail"

	for mail in $LST; do
		MQ="$(dirname "${mail}")"
		HID="$(basename "${mail}" .mail)"
		ID="$HID"
		FQP="$MQ"/"$ID"
		export FQP ID
		lock_queue -lq purge_single_message
	done

	return 0
}

show_message() {
	if [ "$USER" = "root" ]; then
		cat "$(dirname "${Q}"/*/"$1".mail 2>/dev/null)" || cat "$Q/$1".mail || return 74
	else
		cat "$Q/$1.mail" || return 74
	fi
	return 0
}

## ENTRY POINT

RC=0

unset KCOV_LOCK_QUEUE_MODE KCOV_WRAPPER
if [ "$1" = "-kcov" ]; then
	KCOV_LOCK_QUEUE_MODE=true
	shift
	KCOV_WRAPPER="$1"
	shift
fi

export KCOV_LOCK_QUEUE_MODE KCOV_WRAPPER

if [ "$(readlink -f "$(command -v "$1" 2>/dev/null)")" = "$(readlink -f "$(command -v "$MSMTPQ_COMMAND")")" ]; then
	shift
fi

if [ -n "$MSMTPQ_WRAPPER" ] && [ "$(readlink -f "$(command -v "$1" 2>/dev/null)")" = "$(readlink -f "$(command -v "$MSMTPQ_WRAPPER")")" ]; then
	shift
fi

if [ "$1" = "-lq" ]; then
	shift
	"$@"
	exit $?
fi

case "$1" in
""|-p)
	if [ "$1" = "-p" ]; then
		shift
	fi
	list_queue
	MPRMRC=$?
	;;
"-f")
	exec $MSMTPQ_NG -q
	;;
"-h")
	MQPRMID="$2"
	shift 2
	 hold_message "$MQPRMID"
	MPRMRC=$?
	;;
"-H")
	MQPRMID="$2"
	shift 2
	unhold_message "$MQPRMID"
	MPRMRC=$?
	;;
"-i")
	MQPRMID="$2"
	shift 2
	exec "$MSMTPQ_NG" -qI "$MQPRMID"
	;;
"-d")
	MQPRMID="$2"
	shift 2
	purge_message "$MQPRMID"
	MPRMRC=$?
	;;
"-s")
	MQPRMID="$2"
	shift 2
	show_message "$MQPRMID"
	MPRMRC=$?
	;;
"--help")
	usage
	;;
esac

# Simply ignore commands we don't understand
exit $MPRMRC
