#!/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

## write/remove queue lockfile on forced termination
on_exit() { # unlock the queue on exit if the lock was set here
	trap '' INT TERM EXIT				# clear trap so it doesn't happen again
	unset LOK
	LOK="${MSMTP_LOCK_DIR}/msmtpq-ng.lock"	# lock file name
	if [ "$(basename "$LOCK_CMD")" != "flock" ]; then
		[ -n "$LKD" ] && $LOCK_CMD -u "$LOK" && rm -f "$LOK"
	fi
	[ -n "$LKD" ] && rm -f "$LOK"			# Make sure to remove lock file
}

trap on_exit INT TERM EXIT				# run 'on_exit' on exit

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_MAX_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

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
	#	emerge)
	#		PFX="$PFX [EMERGENCY]"
	#		LOGLEVEL=1
	#		;;
	#	alert)
	#		PFX="$PFX [ALERT]"
	#		LOGLEVEL=2
	#		;;
	#	crit)
	#		PFX="$PFX [CRITICAL]"
	#		LOGLEVEL=3
	#		;;
		err)
			PFX="$PFX [ERROR]"
			LOGLEVEL=4
			;;
	#	warning)
	#		PFX="$PFX [WARNING]"
	#		LOGLEVEL=5
	#		;;
		notice)
			PFX="$PFX [NOTICE]"
			LOGLEVEL=6
			;;
		info)
			PFX="$PFX [INFO]"
			LOGLEVEL=7
			;;
	#	debug)
	#		PFX="$PFX [DEBUG]"
	#		LOGLEVEL=8
	#		;;
		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
	#"64")
	#	EXITCODE="EX_USAGE: error in command line"
	#	;;
	#"65")
	#	EXITCODE="EX_DATA: data format error"
	#	;;
	#"66")
	#	EXITCODE="EX_NOINPUT: cannot open input"
	#	;;
	#"67")
	#	EXITCODE="EX_NOUSER: addressee unknown"
	#	;;
	#"68")
	#	EXITCODE="EX_NOHOST: hostname not known"
	#	;;
	#"69")
	#	EXITCODE="EX_UNAVAILABLE: service unavailable"
	#	;;
	#"70")
	#	EXITCODE="EX_SOFTWARE: internal software error"
	#	;;
	"71")
		EXITCODE="EX_OSERR: system error"
		;;
	#"72")
	#	EXITCODE="EX_OSFILE: critical os file missing"
	#	;;
	#"73")
	#	EXITCODE="EX_CANTCREAT: can't create (user) output file"
	#	;;
	#"74")
	#	EXITCODE="EX_IOERR: i/o error"
	#	;;
	#"75")
	#	EXITCODE="EX_TEMPFAIL: temporary failure"
	#	;;
	#"76")
	#	EXITCODE="EX_PROTOCOL: remote error in protocol"
	#	;;
	#"77")
	#	EXITCODE="EX_NOPERM: permission denied"
	#	;;
	#"78")
	#	EXITCODE="EX_CONFIG: configuration error"
	#	;;
	""|"0")
		EXITCODE=""
		;;
	#*)
	#	EXITCODE="unknown exit code = $RC"		# exit code for log
	#	;;
	esac

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


	umask $MSMTP_LOG_UMASK
	if [ -n "$ARG" ]; then
		[ "$MSMTP_QUEUE_QUIET" != "true" ] && \
			dsp "$PFX $ARG"				# display msg to user, as well as logging it
		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

}

#
# 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 LRC PARAMS OP
	LOK="${MSMTP_LOCK_DIR}/msmtpq-ng.lock"	# lock file name
	MAX=120					# max seconds to gain a lock
	SEC=0 					# seconds waiting

	if [ "$LOCK_CMD" = "flock" ]; then		# flock makes life easy
		while [ $SEC -lt $MAX ]; do
			flock -n "$LOK" "$MSMTPQ_COMMAND" "$@"	# 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
		"$MSMTPQ_COMMAND" "$@"				# 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
}

hold_message() {
	if [ "$1" != "ALL" ]; then
		touch "$Q"/"$1".hold
		log info "message [ ""$1"" ] held"
	else
		unset LST HID
		LST="$(ls -A "$Q"/*.mail 2>/dev/null)"

		for mail in $LST; do
			HID="$(basename "$mail" .mail)"
			touch "$Q"/"$HID".hold
			log info "message [ ""$HID"" ] held"
		done

	fi
	return 0
}

unhold_message() {
	unset LST ID MQ MLST
	MQ="${Q}"
	unset Q

	if [ "$1" != "ALL" ]; then
		LST="$(ls -A "$MQ"/"${1}".hold 2>/dev/null)"
		if [ "$USER" = "root" ]; then
			MLST="$(find "$(dirname "${MQ}")" -maxdepth 2 -mindepth 2 -type f -name "${1}".hold)"
			[ -n "$MLST" ] && LST="$MLST"
		fi
	else
		LST="$(ls -A "$MQ"/*.hold 2>/dev/null)"
		if [ "$USER" = "root" ]; then
			MLST="$(find "$(dirname "${MQ}")" -maxdepth 2 -mindepth 2 -type f -name '*.hold')"
			[ -n "$MLST" ] && LST="$MLST"
		fi
	fi

	for hold in $LST; do
		Q="$(dirname "${hold}")"
		HID="$(basename "${hold}" .hold)"
		# If send delay has not yet past
		if [ "$(($(date +%s -r "${Q}/${HID}.mail") + MSMTP_SEND_DELAY))" -gt "$(date +%s)" ]; then
			# skip this message
			log notice "message [ ""$HID"" ] too new to release"
			continue
		fi
		rm -f "${hold}"
		log info "message [ ""$HID"" ] released"
	done
	return 0
}

purge_message() {
	if [ "$1" != "ALL" ]; then
		if [ "$USER" = "root" ]; then
			rm -f "$(dirname "$Q")"/*/"$1".*
		fi
		rm -f "$Q/$1".*
		log notice "message [ $1 ] purged"
	else
		if [ "$USER" = "root" ]; then
			rm -f "$(dirname "${Q}")/*/*"
		fi
		rm -f "$Q"/*
		log notice "all messages purged"
	fi
	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
}

RC=0

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
	lock_queue -lq list_queue
	MPRMRC=$?
	;;
"-f")
	exec $MSMTPQ_NG -q
	;;
"-h")
	MQPRMID="$2"
	shift 2
	lock_queue -lq hold_message "$MQPRMID"
	MPRMRC=$?
	;;
"-H")
	MQPRMID="$2"
	shift 2
	lock_queue -lq unhold_message "$MQPRMID"
	MPRMRC=$?
	;;
"-i")
	MQPRMID="$2"
	shift 2
	exec "$MSMTPQ_NG" -qI "$MQPRMID"
	;;
"-d")
	MQPRMID="$2"
	shift 2
	lock_queue -lq purge_message "$MQPRMID"
	MPRMRC=$?
	;;
"-s")
	MQPRMID="$2"
	shift 2
	lock_queue -lq show_message "$MQPRMID"
	MPRMRC=$?
	;;
"--help")
	usage
	;;
esac

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