#! /bin/sh -e

# This kludge of a shell script warps a sendmail.cf produced from a .mc file
#   to report some spam to the Distributed Checksum Clearinghouse (DCC)
#   in addition to rejecting it.
#
# Mail that is rejected by a sendmail access_db is reported via dccm to
#   a DCC server as extremely bulky or spam.  Error messages in the access_db
#    file must start with "DCC:" or they will be ignored by this mechanism.

# This script should be run in the sendmail cf/cf directory, and given a list
#   of .mc files, as in
#	cd cf/cf
#	.../misc/hackmc -AROT ../m4/cf.m4 local.mc > local.cf

# It seems to work on sendmail.cf generated for sendmail versions 8.11
#   through 8.14.3.  There is no guarantee that it will work with other
#   versions.  You must compare the result of this script with the unmodified
#   sendmail.cf.


# This script "denatures" RCS keywords in its output so that revisions of
#   the resulting sendmail.cf can be archived with RCS without losing
#   the original RCS lines from the Sendmail organization.

# In addition to sending mail blacklisted by the sendmail access_db to DCC,
# the following can also be turned on:

#   -x	turn on debugging

#   -A	send mail with bogus Mail_From domain names to DCC with a count of
#	"many" instead of only rejecting it.   See -M.

#   -R	silently discard unauthorized relay attempts after reporting them
#	to the DCC.  This mechanism also implies -f to ensure that relay
#	attempts do not leak if dccm is not running.

#   -r	reject unauthorized relay attempts after reporting them
#	to the DCC.  This mechanism also implies -f to ensure that relayed
#	attempts do not leak if dccm is not running.

#   -D	add a local rule that rejects mail from SMTP clients without reverse
#	DNS and reports the mail as spam to the DCC.
#	This has a fairly high false positive rate.

#   -O	modify the sendmail rules to treat access_db "OK" and "RELAY"
#	or "Spam:...FRIEND" entries as whitelisting the message.

#   -M	modify the sendmail rules generated by FEATURE(badmx), FEATURE(dnsbl),
#	FEATURE(enhdnsbl), FEATURE(block_bad_helo) so that mail rejected by
#	sendmail is reported via dccm to DCC with a count of "many".
#	See -A.

#   -T	modify the sendmail rules to trust (whitelist) mail from users
#	authenticated with an SMTP AUTH TRUST_AUTH_MECH() mechanism or from
#	SMTP clients with certificates verified with START TLS.  See -U
#	If STMP-AUTH used, TRUST_AUTH_MECH must be set in the .mc file and
#	sendmail must be built with SASL or otherwise have working SMTP auth.

#   -U	modify the sendmail rules to trust (whitelist) mail from users
#	authenticated with an SMTP AUTH TRUST_AUTH_MECH() mechanism.
#	SMTP clients with certificates verified with START TLS are not trusted
#	See -T.  TRUST_AUTH_MECH must be set in the .mc file and
#	sendmail must be built with SASL or otherwise have working SMTP auth.

#   -f	if dccm fails, reject mail with a temporary failure status code
#	instead of passing it.  This changes the default FEATURE(dcc)
#	parameters.  See dcc.m4.

#   -m m4
#	specifies the path to the m4 program as well as any m4 args
#	such as `hackmc -m "/usr/bin/m4 -D_CF_DIR_=/usr/share/sendmail/cf/"`



# Copyright (c) 2017 by Rhyolite Software, LLC
#
# This agreement is not applicable to any entity which sells anti-spam
# solutions to others or provides an anti-spam solution as part of a
# security solution sold to other entities, or to a private network
# which employs the DCC or uses data provided by operation of the DCC
# but does not provide corresponding data to other users.
#
# Permission to use, copy, modify, and distribute this software without
# changes for any purpose with or without fee is hereby granted, provided
# that the above copyright notice and this permission notice appear in all
# copies and any distributed versions or copies are either unchanged
# or not called anything similar to "DCC" or "Distributed Checksum
# Clearinghouse".
#
# Parties not eligible to receive a license under this agreement can
# obtain a commercial license to use DCC by contacting Rhyolite Software
# at sales@rhyolite.com.
#
# A commercial license would be for Distributed Checksum and Reputation
# Clearinghouse software.  That software includes additional features.  This
# free license for Distributed ChecksumClearinghouse Software does not in any
# way grant permision to use Distributed Checksum and Reputation Clearinghouse
# software
#
# THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
# BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
# OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# Rhyolite Software DCC 1.3.159-1.49 $Revision$

USAGE="`basename $0`: [-xfARrDOMTU] [-m m4] file1.mc file2.mc ..."
M4=m4
DNS1='#'
DNS2='#'
RELAY='#'
NOTSPAM='#'
TLS='#'
AUTH='#'
# fail temporarily if dccm is not running.
#	Add F=T to reject mail when dccm is dead,
#	but only if there is not already an F=x setting
TEMPFAIL='#'
TEMPFAIL0='/F=/!s/S=[^ ,]*/&, F=T/'
RDNS='#'
ISSPAM1='#'
ISSPAM2='#'
ISSPAM3='#'

while getopts "xm:fARrDOMTU" c; do
    case $c in
	x) set -x;;
	m) M4="$OPTARG";;
	f) TEMPFAIL=$TEMPFAIL0;;
	A)
	    DNS1='s/$#error $@ \([.0-9]*\) $: "\(5.*[Dd]omain name required.*\)/$# $(macro {dcc_isspam} $@ "\1 \2"  Sent to DCC" $) TODCC/'
	    DNS2='s/$#error $@ \([.0-9]*\) $: "\(5.*Domain of sender.*\)/$# $(macro {dcc_isspam} $@ "\1 \2"  Sent to DCC" $) TODCC/'
	    ;;
	R)
	    # dccm expects to see "DISCARD: Relaying denied"
	    RELAY='s/$#error $@ [.0-9]* $: "5[.0-9 ]*\(Relaying denied.*\)/$# $(macro {dcc_isspam} $@ "DISCARD: \1"  Sent to DCC" $) TODCC/'
	    TEMPFAIL=$TEMPFAIL0
	    ;;
	r)
	    # dccm expects to see "REJECT: Relaying denied"
	    RELAY='s/$#error $@ [.0-9]* $: "5[.0-9 ]*\(Relaying denied.*\)/$# $(macro {dcc_isspam} $@ "REJECT: \1"  Sent to DCC" $) TODCC/'
	    TEMPFAIL=$TEMPFAIL0
	    ;;
	D) RDNS=
	    ;;
	M)
	    ISSPAM1='/^# DNS based IP address spam list/,/^$/s/$#error .* $: *"\(.*\)/$@ $(macro {dcc_isspam} $@ "\1"  Sent to DCC" $) TODCC/'
	    ISSPAM2='s/$#error .* $: *"\(.*MX record.*\)/$@ $(macro {dcc_isspam} $@ "\1"  Sent to DCC" $) TODCC/'
	    ISSPAM3='s/$#error .* $: *"\(.*bogus HELO.*\)/$: $(macro {dcc_isspam} $@ "\1"  Sent to DCC" $) $1/'
	    ;;
	T) TLS=
	    ;;
	U) AUTH=
	    ;;
	O) NOTSPAM='s/^R<\$={Accept}> *<*\$\*>*		*[^	]*/& $(macro {dcc_notspam} $@ $1 $)/'
	    ;;
	*) echo 1>&2 "$USAGE"; exit 1;;
    esac
done
ARGS="$0 $*"
shift `expr $OPTIND - 1 || true`


(
if test -z "$RDNS"; then
    # add -D rules
    cat <<'EOF'
LOCAL_RULESETS
# reject mail from clients without reverse DNS and report it as spam to DCC
SLocal_check_relay
R$*			$: <$&{client_resolve}> $1
R<FAIL> $*		$# $(macro {dcc_isspam} $@ "SMTP client "$&{client_addr}" has no reverse DNS name" $) TODCC
R<$*> $*		$: $2
EOF
fi

if test -z "$AUTH" -o -z "$TLS"; then
    # add -T or -U rules
    cat <<'EOF'
LOCAL_RULESETS
SLocal_check_rcpt
EOF
    if test -z "$TLS"; then
	cat <<'EOF'
# mail from an SMTP client with a verified TLS cert is not spam for dccm
R$*			$: <$&{verify}> $1
R<OK> $*		$: $(macro {dcc_notspam} $@ "STARTTLS verified" $) <OK> $1
EOF
    fi
    cat <<'EOF'
# mail authenticated with SMTP AUTH is not spam for dccm
R<$*> $*		$: <$&{auth_type}> $2
R<$={TrustAuthMech}> $*	$: $(macro {dcc_notspam} $@ "SMTP AUTH by "$1 $) <$1> $2
R<$*> $*		$: $2
EOF
fi
) | $M4 $* -								\
    | sed -e '# remove '$' from Id and Revision RCS keywords'		\
	    -e 's/\$\(Id:.*\)\$/\1/' -e 's/\$\(Revision:.*\)\$/\1/'	\
									\
	    -e '# document ourself in the output'			\
	    -e '/^#[ #]*dcc.m4/a\
#####  built by: '"$ARGS" \
									\
	    -e '# add -M rules for bogus Mail_From domain names'	\
	    -e "${DNS1}" -e "${DNS2}"					\
									\
	    -e '# add -R and -R rules for bogus Mail_From domain names'	\
	    -e "${RELAY}"						\
									\
	    -e '# add -O rules to honor access_db OK, Relay, etc. '	\
	    -e "${NOTSPAM}"						\
									\
	    -e '# add -M rules for badmx dnsbl enhdnsbl block_bad_helo'	\
	    -e "${ISSPAM1}" -e "${ISSPAM2}" -e "${ISSPAM3}"		\
									\
	    -e "# adjust DCC milter flags for -r and -R"		\
	    -e '/^Xdcc/{' -e "$TEMPFAIL" -e '}'				\
									\
	    -e '# add the access.db hook'				\
	    -e '/^R<$={Accept}>/a\
R<DCC:$={Accept}> $*	$@ $1 $(macro {dcc_notspam} $@ $1 $) \
R<DCC:$*> <$*>		$# TODCC $(macro {dcc_isspam} $@ $1": Sent to DCC" $)' \
									\
	    -e '# remove extra quotes'					\
	    -e'/TODCC/s/""//'
