1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2025-04-04 00:17:06 +00:00
mailinabox/tests/suites/mail-access.sh

359 lines
9.7 KiB
Bash

# -*- indent-tabs-mode: t; tab-width: 4; -*-
#
_test_greylisting_x() {
# helper function sends mail and checks that it was greylisted
local email_to="$1"
local email_from="$2"
start_log_capture
start_mail_capture "$email_to"
record "[Send mail anonymously TO $email_to FROM $email_from]"
local output
output="$($PYMAIL -no-delete -f $email_from -to $email_to '' $PRIVATE_IP '' '' 2>&1)"
local code=$?
if [ $code -eq 0 ]; then
wait_mail
local file=( $(get_captured_mail_files) )
record "[Check captured mail for X-Greylist header]"
if ! grep "X-Greylist: delayed" <"$file" >/dev/null; then
record "not found"
test_failure "message not greylisted - X-Greylist header missing"
record_captured_mail
else
record "found"
fi
else
assert_python_failure $code "$output" "SMTPRecipientsRefused" "Greylisted"
fi
check_logs
}
postgrey_whitelist_recipents() {
local wl="/etc/postgrey/whitelist_recipients.local"
rm -f "$wl"
local recipient
for recipient; do
echo "$recipient" >> "$wl" || \
die "Could not add postgrey whitelist recipient to $wl"
done
if ! systemctl reload postgrey >/dev/null 2>&1; then
systemctl restart postgrey >>$TEST_OF 2>&1
fi
}
postgrey_reset_whitelists() {
local wl="/etc/postgrey/whitelist_recipients.local"
rm -f "$wl"
if ! systemctl reload postgrey >/dev/null 2>&1; then
systemctl restart postgrey >>$TEST_OF 2>&1
fi
}
postgrey_reset_state() {
# when postgrey receives a message for processing that is suspect,
# it will:
# 1. initally reject it
# 2. after a delay, permit delivery (end entity must resend),
# but with a X-Greyist header
# 3. subsequent deliveries will succeed with no header
# modifications
#
# because of #3, reset postgrey to establish a "clean" greylisting
# testing scenario
#
record "[Reset postgrey]"
if [ ! -d "/var/lib/postgrey" ]; then
die "Postgrey database directory /var/lib/postgrey does not exist!"
fi
systemctl stop postgrey >>$TEST_OF 2>&1 || die "unble to stop postgrey"
if ! rm -f /var/lib/postgrey/* >>$TEST_OF 2>&1; then
systemctl start postgrey >>$TEST_OF 2>&1
die "unable to remove the postgrey database files"
fi
systemctl start postgrey >>$TEST_OF 2>&1 || die "unble to start postgrey"
}
test_greylisting() {
# test that mail is delayed by greylisting
test_start "greylisting"
# reset postgrey's database to start the cycle over
postgrey_reset_state
# create standard user alice
local alice="alice@somedomain.com"
create_user "$alice" "alice"
# IMPORTANT: bob's domain must be from one that has no SPF record
# in DNS. At the time of creation of this script, yahoo.com's
# is set to "?all" ("neutral" or "no policy statement")
local bob="bob@yahoo.com"
# send to alice anonymously from bob
_test_greylisting_x "$alice" "$bob"
delete_user "$alice"
test_end
}
test_relay_prohibited() {
# test that the server does not relay
test_start "relay-prohibited"
start_log_capture
record "[Attempt relaying mail anonymously]"
local output
output="$($PYMAIL -no-delete -f joe@badguy.com -to naive@gmail.com '' $PRIVATE_IP '' '' 2>&1)"
assert_python_failure $? "$output" "SMTPRecipientsRefused" "Relay access denied"
check_logs
test_end
}
test_spf_fail() {
# test mail rejection due to SPF policy of envelope FROM address
test_start "spf-fail"
# create standard user alice
local alice="alice@somedomain.com"
create_user "$alice" "alice"
# who we will impersonate
#
# note: we've configured policyd-spf to fail instead of softfail
# on google.com
local from="test@google.com"
local domain=$(awk -F@ '{print $2}' <<<"$from")
# send to alice anonymously from imposter
start_log_capture
start_mail_capture "$alice"
record "[Test SPF for $domain FROM $from TO $alice]"
local output
output="$($PYMAIL -no-delete -f $from -to $alice '' $PRIVATE_IP '' '' 2>&1)"
local code=$?
if ! assert_python_failure $code "$output" "SMTPRecipientsRefused" "SPF" && [ $code -eq 0 ]
then
wait_mail
record_captured_mail
fi
check_logs
delete_user "$alice"
test_end
}
test_spf_softfail() {
# When policyd-spf-postfix determines a message's SPF disposition
# is "Softfail", it attaches a "Received-SPF: Softfail" header
# (see RFC 7208) and allows the message to move along the postfix
# delivery chain.
#
# PR #1836 sets custom spam assassin rules that cause spam
# assassin to add a score of 5.0 for Softfail messages (see
# setup/dkim.sh and setup/spamassassin.sh)
#
# This test checks that a softfail message is properly scored.
local SPF_SOFTFAIL_SCORE="5.0"
test_start "spf-softfail"
# create user alice
local alice="alice@somedomain.com"
local alice_pw="123alice"
create_user "$alice" "$alice_pw"
# who we will impersonate
#
# important: the MAIL FROM address we choose here must currently
# have dns spf configuration set to softfail (eg: ~all)
#
local from="test@guest.com"
local domain=$(awk -F@ '{print $2}' <<<"$from")
# alice must be whitelisted to avoid greylisting
postgrey_whitelist_recipents "$alice"
# send to alice anonymously from imposter
start_log_capture
start_mail_capture "$alice"
record "[Test SPF for $domain FROM $from TO $alice]"
local output
local subject="$(generate_uuid)"
output="$($PYMAIL -no-delete -subj "$subject" -f $from -to $alice '' $PRIVATE_IP '' '' 2>&1)"
if assert_python_success $? "$output"; then
if ! wait_mail; then
test_failure "Timeout waiting for mail"
else
record_captured_mail
local files=( $(get_captured_mail_files) )
local score
score=$(grep -F "SPF_SOFTFAIL" "$files" | grep -F '*' | awk '{print $2}')
floor_score=$(awk -F. '{print $1}' <<<"$score")
floor_expected=$(awk -F. '{print $1}' <<<"$SPF_SOFTFAIL_SCORE")
if [ -z "$score" ]; then
test_failure "No spam score for SPF_SOFTFAIL"
elif [ $floor_score -ne $floor_expected ]; then
test_failure "Got score $score, but exptected $SPF_SOFTFAIL_SCORE"
fi
fi
# clean up: delete the delivered mail
record "[delete delivered message]"
output="$($PYMAIL -no-send -subj $subject -timeout 1 $PRIMARY_HOSTNAME $alice "$alice_pw" 2>&1)"
record "$output"
fi
check_logs
# clean up
postgrey_reset_whitelists
delete_user "$alice"
test_end
}
test_dmarc_reject() {
# PR #1836 sets custom spam assassin rules that cause spam
# assassin to add a score of 10.0 for dmarc p=reject messages (see
# setup/dkim.sh and setup/spamassassin.sh)
#
# This test checks that a p=reject message is properly scored.
local DMARC_FAIL_REJECT_SCORE="10.0"
test_start "dmarc-reject"
# create user alice - must be a domain that does not result in SPF Fail
local alice="alice@guest.com"
local alice_pw="alice123"
create_user "$alice" "$alice_pw"
# who we will impersonate: their domain (_dmarc.domain.com dns TXT
# record), must have a dmarc policy with p=reject
local header_from="test@google.com"
# alice must be whitelisted to avoid greylisting
postgrey_whitelist_recipents "$alice"
# send to alice from alice with header From: imposter
start_log_capture
start_mail_capture "$alice"
record "[Test dmarc reject TO $alice, From: $header_from]"
local subject="$(generate_uuid)"
local output
output="$($PYMAIL -smptd -subj $subject -no-delete -f $alice -hfrom $header_from -to $alice '' $PRIVATE_IP 2>&1)"
if assert_python_success $? "$output"; then
if ! wait_mail; then
test_failure "Timeout waiting for mail"
else
record_captured_mail
local files=( $(get_captured_mail_files) )
local score
score=$(grep -F "DMARC_FAIL_REJECT" "$files" | grep -F '*' | awk '{print $2}')
floor_score=$(awk -F. '{print $1}' <<<"$score")
floor_expected=$(awk -F. '{print $1}' <<<"$DMARC_FAIL_REJECT_SCORE")
if [ -z "$score" ]; then
test_failure "No spam score for DMARC_FAIL_REJECT"
elif [ $floor_score -ne $floor_expected ]; then
test_failure "Got score $score, but exptected $DMARC_FAIL_REJECT_SCORE"
fi
fi
# clean up: delete the delivered mail
record "[delete delivered message]"
output="$($PYMAIL -no-send -subj $subject -timeout 1 $PRIMARY_HOSTNAME $alice "$alice_pw" 2>&1)"
record "$output"
fi
check_logs
# clean up
postgrey_reset_whitelists
delete_user "$alice"
test_end
}
test_mailbox_pipe() {
# postfix allows piped commands in aliases for local processing,
# which is a serious security issue. test that pipes are not
# permitted or don't work
test_start "mailbox-pipe"
# create standard user alice
local alice="alice@somedomain.com"
create_user "$alice" "alice"
local alice_dn="$ATTR_DN"
# create the program to handle piped mail
local cmd="/tmp/pipedrop.$$.sh"
local outfile="/tmp/pipedrop.$$.out"
cat 2>>$TEST_OF >$cmd <<EOF
#!/bin/bash
cat > $outfile
EOF
chmod 755 $cmd
rm -f $outfile
# add a piped maildrop
record "[Add pipe command as alice's maildrop]"
ldapmodify -H $LDAP_URL -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" >>$TEST_OF 2>&1 <<EOF
dn: $alice_dn
replace: maildrop
maildrop: |$cmd
EOF
[ $? -ne 0 ] && die "Could not modify ${alice}'s maildrop"
# send an email message to alice
start_log_capture
record "[Send an email to $alice - test pipe]"
local output
output="$($PYMAIL -no-delete $PRIVATE_IP $alice alice 2>&1)"
local code=$?
if [ $code -ne 0 ]; then
assert_python_failure $code "$output" SMTPAuthenticationError
check_logs
else
sleep 5
if grep_postfix_log "User doesn't exist: |$cmd@"; then
# ok
check_logs
else
test_failure "Expected postfix/lmtp to report 'User doesn't exist'"
assert_check_logs
fi
if [ -e $outfile ]; then
test_failure "a maildrop containing a pipe was executed by postfix"
fi
fi
delete_user "$alice"
rm -f $cmd
rm -f $outfile
test_end
}
suite_start "mail-access" ensure_root_user
test_greylisting
test_relay_prohibited
test_spf_fail
test_spf_softfail
test_dmarc_reject
test_mailbox_pipe
suite_end