mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-04 15:54:48 +01:00
Issue #1340 - LDAP backend for accounts
This commit will: 1. Change the user account database from sqlite to OpenLDAP 2. Add policyd-spf to postfix for SPF validation 3. Add a test runner with some automated test suites Notes: User account password hashes are preserved. There is a new Roundcube contact list called "Directory" that lists the users in LDAP (MiaB users), similar to what Google Suite does. Users can still change their password in Roundcube. OpenLDAP is configured with TLS, but all remote access is blocked by firewall rules. Manual changes are required to open it for remote access (eg. "ufw allow proto tcp from <HOST> to any port ldaps"). The test runner is started by executing tests/runner.sh. Be aware that it will make changes to your system, including adding new users, domains, mailboxes, start/stop services, etc. It is highly unadvised to run it on a production system! The LDAP schema that supports mail delivery with postfix and dovecot is located in conf/postfix.schema. This file is copied verbatim from the LdapAdmin project (GPL, ldapadmin.org). Instead of including the file in git, it could be referenced by URL and downloaded by the setup script if GPL is an issue or apply for a PEN from IANA. Mangement console and other services should not appear or behave any differently than before.
This commit is contained in:
1
tests/.gitignore
vendored
Normal file
1
tests/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
out
|
||||
58
tests/prep_vm.sh
Executable file
58
tests/prep_vm.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Run this on a VM to pre-install all the packages, then
|
||||
# take a snapshot - it will greatly speed up subsequent
|
||||
# test installs
|
||||
|
||||
|
||||
remove_line_continuation() {
|
||||
local file="$1"
|
||||
awk '
|
||||
BEGIN { C=0 }
|
||||
C==1 && /[^\\]$/ { C=0; print $0; next }
|
||||
C==1 { printf("%s",substr($0,0,length($0)-1)); next }
|
||||
/\\$/ { C=1; printf("%s",substr($0,0,length($0)-1)); next }
|
||||
{ print $0 }' \
|
||||
"$file"
|
||||
}
|
||||
|
||||
install_packages() {
|
||||
while read line; do
|
||||
pkgs=""
|
||||
case "$line" in
|
||||
apt_install* )
|
||||
pkgs="$(cut -c12- <<<"$line")"
|
||||
;;
|
||||
"apt-get install"* )
|
||||
pkgs="$(cut -c16- <<<"$line")"
|
||||
;;
|
||||
"apt install"* )
|
||||
pkgs="$(cut -c12- <<<"$line")"
|
||||
;;
|
||||
esac
|
||||
|
||||
# don't install postfix - causes problems with setup scripts
|
||||
pkgs="$(sed s/postfix//g <<<"$pkgs")"
|
||||
|
||||
if [ ! -z "$pkgs" ]; then
|
||||
echo "install: $pkgs"
|
||||
apt-get install $pkgs -y
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
apt-get update -y
|
||||
apt-get upgrade -y
|
||||
apt-get autoremove -y
|
||||
|
||||
for file in $(ls setup/*.sh); do
|
||||
remove_line_continuation "$file" | install_packages
|
||||
done
|
||||
|
||||
apt-get install openssh-server -y
|
||||
apt-get install emacs-nox -y
|
||||
|
||||
echo ""
|
||||
echo ""
|
||||
echo "Done. Take a snapshot...."
|
||||
echo ""
|
||||
81
tests/runner.sh
Executable file
81
tests/runner.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
|
||||
#
|
||||
# Runner for test suites
|
||||
#
|
||||
|
||||
# operate from the runner's directory
|
||||
cd "$(dirname $0)"
|
||||
|
||||
# load global functions and variables
|
||||
. suites/_init.sh
|
||||
|
||||
runner_suites=(
|
||||
ldap-connection
|
||||
ldap-access
|
||||
mail-basic
|
||||
mail-from
|
||||
mail-aliases
|
||||
mail-access
|
||||
management-users
|
||||
)
|
||||
|
||||
usage() {
|
||||
echo ""
|
||||
echo "Usage: $(basename $0) [-failfatal] [suite-name ...]"
|
||||
echo "Valid suite names:"
|
||||
for runner_suite in ${runner_suites[@]}; do
|
||||
echo " $runner_suite"
|
||||
done
|
||||
echo "If no suite-name(s) given, all suites are run"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -failfatal The runner will stop if any test fails"
|
||||
echo ""
|
||||
echo "Output directory: $(dirname $0)/${base_outputdir}"
|
||||
echo ""
|
||||
exit 1
|
||||
}
|
||||
|
||||
# process command line
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-failfatal )
|
||||
# failure is fatal (via global option, see _init.sh)
|
||||
FAILURE_IS_FATAL=yes
|
||||
;;
|
||||
-* )
|
||||
echo "Invalid argument $1" 1>&2
|
||||
usage
|
||||
;;
|
||||
* )
|
||||
# run named suite
|
||||
if array_contains "$1" ${runner_suites[@]}; then
|
||||
. "suites/$1.sh"
|
||||
else
|
||||
echo "Unknown suite '$1'" 1>&2
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# if no suites specified on command line, run all suites
|
||||
if [ $OVERALL_COUNT_SUITES -eq 0 ]; then
|
||||
rm -rf "${base_outputdir}"
|
||||
for runner_suite in ${runner_suites[@]}; do
|
||||
. suites/$runner_suite.sh
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Done"
|
||||
echo "$OVERALL_COUNT tests ($OVERALL_SUCCESSES success/$OVERALL_FAILURES failures) in $OVERALL_COUNT_SUITES test suites"
|
||||
|
||||
if [ $OVERALL_FAILURES -gt 0 ]; then
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
176
tests/suites/_init.sh
Normal file
176
tests/suites/_init.sh
Normal file
@@ -0,0 +1,176 @@
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
|
||||
# load useful functions from setup
|
||||
. ../setup/functions.sh || exit 1
|
||||
. ../setup/functions-ldap.sh || exit 1
|
||||
set +eu
|
||||
|
||||
# load test suite helper functions
|
||||
. suites/_ldap-functions.sh || exit 1
|
||||
. suites/_mail-functions.sh || exit 1
|
||||
. suites/_mgmt-functions.sh || exit 1
|
||||
|
||||
# globals - all global variables are UPPERCASE
|
||||
BASE_OUTPUTDIR="out"
|
||||
PYMAIL="./test_mail.py"
|
||||
declare -i OVERALL_SUCCESSES=0
|
||||
declare -i OVERALL_FAILURES=0
|
||||
declare -i OVERALL_COUNT=0
|
||||
declare -i OVERALL_COUNT_SUITES=0
|
||||
|
||||
# ansi escapes for hilighting text
|
||||
F_DANGER=$(echo -e "\033[31m")
|
||||
F_WARN=$(echo -e "\033[93m")
|
||||
F_RESET=$(echo -e "\033[39m")
|
||||
|
||||
# options
|
||||
FAILURE_IS_FATAL=no
|
||||
|
||||
|
||||
suite_start() {
|
||||
let TEST_NUM=1
|
||||
let SUITE_COUNT_SUCCESS=0
|
||||
let SUITE_COUNT_FAILURE=0
|
||||
let SUITE_COUNT_TOTAL=0
|
||||
SUITE_NAME="$1"
|
||||
OUTDIR="$BASE_OUTPUTDIR/$SUITE_NAME"
|
||||
mkdir -p "$OUTDIR"
|
||||
echo ""
|
||||
echo "Starting suite: $SUITE_NAME"
|
||||
suite_setup "$2"
|
||||
}
|
||||
|
||||
suite_end() {
|
||||
suite_cleanup "$1"
|
||||
echo "Suite $SUITE_NAME finished"
|
||||
let OVERALL_SUCCESSES+=$SUITE_COUNT_SUCCESS
|
||||
let OVERALL_FAILURES+=$SUITE_COUNT_FAILURE
|
||||
let OVERALL_COUNT+=$SUITE_COUNT_TOTAL
|
||||
let OVERALL_COUNT_SUITES+=1
|
||||
}
|
||||
|
||||
suite_setup() {
|
||||
[ -z "$1" ] && return 0
|
||||
TEST_OF="$OUTDIR/setup"
|
||||
eval "$1"
|
||||
TEST_OF=""
|
||||
}
|
||||
|
||||
suite_cleanup() {
|
||||
[ -z "$1" ] && return 0
|
||||
TEST_OF="$OUTDIR/cleanup"
|
||||
eval "$1"
|
||||
TEST_OF=""
|
||||
}
|
||||
|
||||
test_start() {
|
||||
TEST_DESC="${1:-}"
|
||||
TEST_NAME="$(printf "%03d" $TEST_NUM)"
|
||||
TEST_OF="$OUTDIR/$TEST_NAME"
|
||||
TEST_STATE=""
|
||||
TEST_STATE_MSG=()
|
||||
echo "TEST-START \"${TEST_DESC:-unnamed}\"" >$TEST_OF
|
||||
echo -n " $TEST_NAME: $TEST_DESC: "
|
||||
let TEST_NUM+=1
|
||||
let SUITE_COUNT_TOTAL+=1
|
||||
}
|
||||
|
||||
test_end() {
|
||||
[ -z "$TEST_OF" ] && return
|
||||
if [ $# -gt 0 ]; then
|
||||
[ -z "$1" ] && test_success || test_failure "$1"
|
||||
fi
|
||||
case $TEST_STATE in
|
||||
SUCCESS | "" )
|
||||
record "[SUCCESS]"
|
||||
echo "SUCCESS"
|
||||
let SUITE_COUNT_SUCCESS+=1
|
||||
;;
|
||||
FAILURE )
|
||||
record "[FAILURE]"
|
||||
echo "${F_DANGER}FAILURE${F_RESET}:"
|
||||
local idx=0
|
||||
while [ $idx -lt ${#TEST_STATE_MSG[*]} ]; do
|
||||
record "${TEST_STATE_MSG[$idx]}"
|
||||
echo " why: ${TEST_STATE_MSG[$idx]}"
|
||||
let idx+=1
|
||||
done
|
||||
echo " see: $(dirname $0)/$TEST_OF"
|
||||
let SUITE_COUNT_FAILURE+=1
|
||||
if [ "$FAILURE_IS_FATAL" == "yes" ]; then
|
||||
record "FATAL: failures are fatal option enabled"
|
||||
echo "FATAL: failures are fatal option enabled"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
* )
|
||||
record "[INVALID TEST STATE '$TEST_STATE']"
|
||||
echo "Invalid TEST_STATE=$TEST_STATE"
|
||||
let SUITE_COUNT_FAILURE+=1
|
||||
;;
|
||||
esac
|
||||
TEST_OF=""
|
||||
}
|
||||
|
||||
test_success() {
|
||||
[ -z "$TEST_OF" ] && return
|
||||
[ -z "$TEST_STATE" ] && TEST_STATE="SUCCESS"
|
||||
}
|
||||
|
||||
test_failure() {
|
||||
local why="$1"
|
||||
[ -z "$TEST_OF" ] && return
|
||||
TEST_STATE="FAILURE"
|
||||
TEST_STATE_MSG+=( "$why" )
|
||||
}
|
||||
|
||||
have_test_failures() {
|
||||
[ "$TEST_STATE" == "FAILURE" ] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
record() {
|
||||
if [ ! -z "$TEST_OF" ]; then
|
||||
echo "$@" >>$TEST_OF
|
||||
else
|
||||
echo "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
die() {
|
||||
record "FATAL: $@"
|
||||
test_failure "a fatal error occurred"
|
||||
test_end
|
||||
echo "FATAL: $@"
|
||||
exit 1
|
||||
}
|
||||
|
||||
array_contains() {
|
||||
local searchfor="$1"
|
||||
shift
|
||||
local item
|
||||
for item; do
|
||||
[ "$item" == "$searchfor" ] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
python_error() {
|
||||
# finds tracebacks and outputs just the final error message of
|
||||
# each
|
||||
local output="$1"
|
||||
awk 'BEGIN { TB=0; FOUND=0 } TB==0 && /^Traceback/ { TB=1; FOUND=1; next } TB==1 && /^[^ ]/ { print $0; TB=0 } END { if (FOUND==0) exit 1 }' <<< "$output"
|
||||
[ $? -eq 1 ] && echo "$output"
|
||||
}
|
||||
|
||||
|
||||
|
||||
##
|
||||
## Initialize
|
||||
##
|
||||
|
||||
mkdir -p "$BASE_OUTPUTDIR"
|
||||
|
||||
# load global vars
|
||||
. /etc/mailinabox.conf || die "Could not load '/etc/mailinabox.conf'"
|
||||
. "${STORAGE_ROOT}/ldap/miab_ldap.conf" || die "Could not load miab_ldap.conf"
|
||||
427
tests/suites/_ldap-functions.sh
Normal file
427
tests/suites/_ldap-functions.sh
Normal file
@@ -0,0 +1,427 @@
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
|
||||
generate_uuid() {
|
||||
local uuid
|
||||
uuid=$(python3 -c "import uuid; print(uuid.uuid4())")
|
||||
[ $? -ne 0 ] && die "Unable to generate a uuid"
|
||||
echo "$uuid"
|
||||
}
|
||||
|
||||
delete_user() {
|
||||
local email="$1"
|
||||
local domainpart="$(awk -F@ '{print $2}' <<< "$email")"
|
||||
get_attribute "$LDAP_USERS_BASE" "mail=$email" "dn"
|
||||
[ -z "$ATTR_DN" ] && return 0
|
||||
record "[delete user $email]"
|
||||
ldapdelete -H $LDAP_URL -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" "$ATTR_DN" >>$TEST_OF 2>&1 || die "Unable to delete user $ATTR_DN (as admin)"
|
||||
record "deleted"
|
||||
# delete the domain if there are no more users in the domain
|
||||
get_attribute "$LDAP_USERS_BASE" "mail=*@${domainpart}" "dn"
|
||||
[ ! -z "$ATTR_DN" ] && return 0
|
||||
get_attribute "$LDAP_DOMAINS_BASE" "dc=${domainpart}" "dn"
|
||||
if [ ! -z "$ATTR_DN" ]; then
|
||||
record "[delete domain $domainpart]"
|
||||
ldapdelete -H $LDAP_URL -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" "$ATTR_DN" >>$TEST_OF 2>&1 || die "Unable to delete domain $ATTR_DN (as admin)"
|
||||
record "deleted"
|
||||
fi
|
||||
}
|
||||
|
||||
create_user() {
|
||||
local email="$1"
|
||||
local pass="${2:-$email}"
|
||||
local priv="${3:-test}"
|
||||
local localpart="$(awk -F@ '{print $1}' <<< "$email")"
|
||||
local domainpart="$(awk -F@ '{print $2}' <<< "$email")"
|
||||
local uid="$localpart"
|
||||
local dn="uid=${uid},${LDAP_USERS_BASE}"
|
||||
|
||||
delete_user "$email"
|
||||
|
||||
record "[create user $email]"
|
||||
delete_dn "$dn"
|
||||
|
||||
ldapadd -H "$LDAP_URL" -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" >>$TEST_OF 2>&1 <<EOF
|
||||
dn: $dn
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: mailUser
|
||||
objectClass: shadowAccount
|
||||
uid: $uid
|
||||
cn: $localpart
|
||||
sn: $localpart
|
||||
displayName: $localpart
|
||||
mail: $email
|
||||
maildrop: $email
|
||||
mailaccess: $priv
|
||||
userPassword: $(slappasswd_hash "$pass")
|
||||
EOF
|
||||
[ $? -ne 0 ] && die "Unable to add user $dn (as admin)"
|
||||
|
||||
# create domain entry, if needed
|
||||
get_attribute "$LDAP_DOMAINS_BASE" "dc=${domainpart}" dn
|
||||
if [ -z "$ATTR_DN" ]; then
|
||||
record "[create domain entry $domainpart]"
|
||||
ldapadd -H $LDAP_URL -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" >>$TEST_OF 2>&1 <<EOF
|
||||
dn: dc=${domainpart},$LDAP_DOMAINS_BASE
|
||||
objectClass: domain
|
||||
dc: ${domainpart}
|
||||
businessCategory: mail
|
||||
EOF
|
||||
[ $? -ne 0 ] && die "Unable to add domain ${domainpart} (as admin)"
|
||||
fi
|
||||
|
||||
ATTR_DN="$dn"
|
||||
}
|
||||
|
||||
|
||||
delete_dn() {
|
||||
local dn="$1"
|
||||
get_attribute "$dn" "objectClass=*" "dn" base
|
||||
[ -z "$ATTR_DN" ] && return 0
|
||||
record "delete dn: $dn"
|
||||
ldapdelete -H $LDAP_URL -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" "$dn" >>$TEST_OF 2>&1 || die "Unable to delete $dn (as admin)"
|
||||
}
|
||||
|
||||
create_service_account() {
|
||||
local cn="$1"
|
||||
local pass="${2:-$cn}"
|
||||
local dn="cn=${cn},${LDAP_SERVICES_BASE}"
|
||||
|
||||
record "[create service account $cn]"
|
||||
delete_dn "$dn"
|
||||
|
||||
ldapadd -H "$LDAP_URL" -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" >>$TEST_OF 2>&1 <<EOF
|
||||
dn: $dn
|
||||
objectClass: simpleSecurityObject
|
||||
objectClass: organizationalRole
|
||||
cn: $cn
|
||||
description: TEST ${cn} service account
|
||||
userPassword: $(slappasswd_hash "$pass")
|
||||
EOF
|
||||
[ $? -ne 0 ] && die "Unable to add service account $dn (as admin)"
|
||||
ATTR_DN="$dn"
|
||||
}
|
||||
|
||||
delete_service_account() {
|
||||
local cn="$1"
|
||||
local dn="cn=${cn},${LDAP_SERVICES_BASE}"
|
||||
record "[delete service account $cn]"
|
||||
delete_dn "$dn"
|
||||
}
|
||||
|
||||
create_alias_group() {
|
||||
local alias="$1"
|
||||
shift
|
||||
record "[Create new alias group $alias]"
|
||||
# add alias group with dn's as members
|
||||
get_attribute "$LDAP_ALIASES_BASE" "mail=$alias" "dn"
|
||||
if [ ! -z "$ATTR_DN" ]; then
|
||||
delete_dn "$ATTR_DN"
|
||||
fi
|
||||
|
||||
ATTR_DN="cn=$(generate_uuid),$LDAP_ALIASES_BASE"
|
||||
of="/tmp/create_alias.$$.ldif"
|
||||
cat >$of 2>>$TEST_OF <<EOF
|
||||
dn: $ATTR_DN
|
||||
objectClass: mailGroup
|
||||
mail: $alias
|
||||
EOF
|
||||
local member
|
||||
for member; do
|
||||
case $member in
|
||||
*@* )
|
||||
echo "rfc822MailMember: $member" >>$TEST_OF
|
||||
echo "rfc822MailMember: $member" >>$of 2>>$TEST_OF
|
||||
;;
|
||||
* )
|
||||
echo "member: $member" >>$TEST_OF
|
||||
echo "member: $member" >>$of 2>>$TEST_OF
|
||||
;;
|
||||
esac
|
||||
done
|
||||
ldapadd -H "$LDAP_URL" -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" -f $of >>$TEST_OF 2>&1 || die "Unable to add alias group $alias"
|
||||
rm -f $of
|
||||
}
|
||||
|
||||
delete_alias_group() {
|
||||
record "[delete alias group $1]"
|
||||
get_attribute "$LDAP_ALIASES_BASE" "(mail=$1)" dn
|
||||
[ ! -z "$ATTR_DN" ] && delete_dn "$ATTR_DN"
|
||||
}
|
||||
|
||||
|
||||
add_alias() {
|
||||
local user_dn="$1"
|
||||
local alias="$2"
|
||||
local type="${3:-group}"
|
||||
if [ $type == user ]; then
|
||||
# add alias as additional 'mail' attribute to user's dn
|
||||
record "[Add alias $alias to $user_dn]"
|
||||
ldapmodify -H "$LDAP_URL" -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" >>$TEST_OF 2>&1 <<EOF
|
||||
dn: $user_dn
|
||||
add: mail
|
||||
mail: $alias
|
||||
EOF
|
||||
local r=$?
|
||||
[ $r -ne 0 ] && die "Unable to modify $user_dn"
|
||||
|
||||
elif [ $type == group ]; then
|
||||
# add alias as additional 'member" to a mailGroup alias list
|
||||
record "[Add member $user_dn to alias $alias]"
|
||||
get_attribute "$LDAP_ALIASES_BASE" "mail=$alias" "dn"
|
||||
if [ -z "$ATTR_DN" ]; then
|
||||
# don't automatically add because it should be cleaned
|
||||
# up by the caller
|
||||
die "Alias grour $alias does not exist"
|
||||
else
|
||||
ldapmodify -H "$LDAP_URL" -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" >>$TEST_OF 2>&1 <<EOF
|
||||
dn: $ATTR_DN
|
||||
add: member
|
||||
member: $user_dn
|
||||
EOF
|
||||
local code=$?
|
||||
if [ $code -ne 20 -a $code -ne 0 ]; then
|
||||
# 20=Type or value exists
|
||||
die "Unable to add user $user_dn to alias $alias"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
die "Invalid type '$type' to add_alias"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
|
||||
create_permitted_senders_group() {
|
||||
# add a permitted senders group. specify the email address that
|
||||
# the members may MAIL FROM as the first argument, followed by all
|
||||
# member dns. If the group already exists, it is deleted first.
|
||||
#
|
||||
# on return, the global variable ATTR_DN is set to the dn of the
|
||||
# created mailGroup
|
||||
local mail_from="$1"
|
||||
shift
|
||||
record "[create permitted sender list $mail_from]"
|
||||
get_attribute "$LDAP_PERMITTED_SENDERS_BASE" "(&(objectClass=mailGroup)(mail=$mail_from))" dn
|
||||
if [ ! -z "$ATTR_DN" ]; then
|
||||
delete_dn "$ATTR_DN"
|
||||
fi
|
||||
|
||||
local tmp="/tmp/tests.$$.ldif"
|
||||
ATTR_DN="cn=$(generate_uuid),$LDAP_PERMITTED_SENDERS_BASE"
|
||||
cat >$tmp <<EOF
|
||||
dn: $ATTR_DN
|
||||
objectClass: mailGroup
|
||||
mail: $mail_from
|
||||
EOF
|
||||
local member
|
||||
for member; do
|
||||
echo "member: $member" >>$tmp
|
||||
echo "member: $member" >>$TEST_OF
|
||||
done
|
||||
|
||||
ldapadd -H "$LDAP_URL" -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" -f $tmp >>$TEST_OF 2>&1
|
||||
local r=$?
|
||||
rm -f $tmp
|
||||
[ $r -ne 0 ] && die "Unable to add permitted senders group $mail_from"
|
||||
}
|
||||
|
||||
delete_permitted_senders_group() {
|
||||
local mail_from="$1"
|
||||
record "[delete permitted sender list $mail_from]"
|
||||
get_attribute "$LDAP_PERMITTED_SENDERS_BASE" "(&(objectClass=mailGroup)(mail=$mail_from))" dn
|
||||
if [ ! -z "$ATTR_DN" ]; then
|
||||
delete_dn "$ATTR_DN"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
test_r_access() {
|
||||
# tests read or unreadable access
|
||||
# sets global variable FAILURE on return
|
||||
local user_dn="$1"
|
||||
local login_dn="$2"
|
||||
local login_pass="$3"
|
||||
local access="${4:-no-read}" # should be "no-read" or "read"
|
||||
shift; shift; shift; shift
|
||||
|
||||
if ! array_contains $access read no-read; then
|
||||
die "Invalid parameter '$access' to function test_r_access"
|
||||
fi
|
||||
|
||||
# get all attributes using login_dn's account
|
||||
local attr
|
||||
local search_output result=()
|
||||
record "[Get attributes of $user_dn by $login_dn]"
|
||||
search_output=$(ldapsearch -LLL -o ldif-wrap=no -H "$LDAP_URL" -b "$user_dn" -s base -x -D "$login_dn" -w "$login_pass" 2>>$TEST_OF)
|
||||
local code=$?
|
||||
# code 32: No such object (doesn't exist or login can't see it)
|
||||
[ $code -ne 0 -a $code -ne 32 ] && die "Unable to find entry $user_dn by $login_dn"
|
||||
while read attr; do
|
||||
record "line: $attr"
|
||||
attr=$(awk -F: '{print $1}' <<< "$attr")
|
||||
[ "$attr" != "dn" -a "$attr" != "objectClass" ] && result+=($attr)
|
||||
done <<< "$search_output"
|
||||
record "check for $access access to ${@:-ALL}"
|
||||
record "comparing to actual: ${result[@]}"
|
||||
|
||||
|
||||
local failure=""
|
||||
if [ $access == "no-read" -a $# -eq 0 ]; then
|
||||
# check that no attributes are readable
|
||||
if [ ${#result[*]} -gt 0 ]; then
|
||||
failure="Attributes '${result[*]}' of $user_dn should not be readable by $login_dn"
|
||||
fi
|
||||
else
|
||||
# check that specified attributes are/aren't readable
|
||||
for attr; do
|
||||
if [ $access == "no-read" ]; then
|
||||
if array_contains $attr ${result[@]}; then
|
||||
failure="Attribute $attr of $user_dn should not be readable by $login_dn"
|
||||
break
|
||||
fi
|
||||
else
|
||||
if ! array_contains $attr ${result[@]}; then
|
||||
failure="Attribute $attr of $user_dn should be readable by $login_dn got (${result[*]})"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
FAILURE="$failure"
|
||||
}
|
||||
|
||||
|
||||
assert_r_access() {
|
||||
# asserts read or unreadable access
|
||||
FAILURE=""
|
||||
test_r_access "$@"
|
||||
[ ! -z "$FAILURE" ] && test_failure "$FAILURE"
|
||||
}
|
||||
|
||||
|
||||
test_w_access() {
|
||||
# tests write or unwritable access
|
||||
# sets global variable FAILURE on return
|
||||
# if no attributes given, test user attributes
|
||||
# uuid, cn, sn, mail, maildrop, mailaccess
|
||||
local user_dn="$1"
|
||||
local login_dn="$2"
|
||||
local login_pass="$3"
|
||||
local access="${4:-no-write}" # should be "no-write" or "write"
|
||||
shift; shift; shift; shift
|
||||
local moddn=""
|
||||
local attrs=( $@ )
|
||||
|
||||
if ! array_contains $access write no-write; then
|
||||
die "Invalid parameter '$access' to function test_w_access"
|
||||
fi
|
||||
|
||||
if [ ${#attrs[*]} -eq 0 ]; then
|
||||
moddn=uid
|
||||
attrs=("cn=alice fiction" "sn=fiction" "mail" "maildrop" "mailaccess=admin")
|
||||
fi
|
||||
|
||||
local failure=""
|
||||
|
||||
# check that select attributes are not writable
|
||||
if [ ! -z "$moddn" ]; then
|
||||
record "[Change attribute ${moddn}]"
|
||||
delete_dn "${moddn}=some-uuid,$LDAP_USERS_BASE"
|
||||
ldapmodify -H "$LDAP_URL" -x -D "$login_dn" -w "$login_pass" >>$TEST_OF 2>&1 <<EOF
|
||||
dn: $user_dn
|
||||
changetype: moddn
|
||||
newrdn: ${moddn}=some-uuid
|
||||
deleteoldrdn: 1
|
||||
EOF
|
||||
local r=$?
|
||||
if [ $r -eq 0 ]; then
|
||||
if [ "$access" == "no-write" ]; then
|
||||
failure="Attribute $moddn of $user_dn should not be changeable by $login_dn"
|
||||
fi
|
||||
elif [ $r -eq 50 ]; then
|
||||
if [ "$access" == "write" ]; then
|
||||
failure="Attribute $moddn of $user_dn should be changeable by $login_dn"
|
||||
fi
|
||||
else
|
||||
die "Error attempting moddn change of $moddn (code $?)"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [ -z "$failure" ]; then
|
||||
local attrvalue attr value
|
||||
for attrvalue in "${attrs[@]}"; do
|
||||
attr="$(awk -F= '{print $1}' <<< "$attrvalue")"
|
||||
value="$(awk -F= '{print substr($0,length($1)+2)}' <<< "$attrvalue")"
|
||||
[ -z "$value" ] && value="alice2@abc.com"
|
||||
record "[Change attribute $attr]"
|
||||
ldapmodify -H "$LDAP_URL" -x -D "$login_dn" -w "$login_pass" >>$TEST_OF 2>&1 <<EOF
|
||||
dn: $user_dn
|
||||
replace: $attr
|
||||
$attr: $value
|
||||
EOF
|
||||
r=$?
|
||||
if [ $r -eq 0 ]; then
|
||||
if [ $access == "no-write" ]; then
|
||||
failure="Attribute $attr of $user_dn should not be changeable by $login_dn"
|
||||
break
|
||||
fi
|
||||
elif [ $r -eq 50 ]; then
|
||||
if [ $access == "write" ]; then
|
||||
failure="Attribute $attr of $user_dn should be changeable by $login_dn"
|
||||
break
|
||||
fi
|
||||
else
|
||||
die "Error attempting change of $attr to '$value'"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
FAILURE="$failure"
|
||||
}
|
||||
|
||||
assert_w_access() {
|
||||
# asserts write or unwritable access
|
||||
FAILURE=""
|
||||
test_w_access "$@"
|
||||
[ ! -z "$FAILURE" ] && test_failure "$FAILURE"
|
||||
}
|
||||
|
||||
|
||||
test_search() {
|
||||
# test if access to search something is allowed
|
||||
# sets global variable SEARCH_DN_COUNT on return
|
||||
local base_dn="$1"
|
||||
local login_dn="$2"
|
||||
local login_pass="$3"
|
||||
local scope="${4:-sub}"
|
||||
local filter="$5"
|
||||
|
||||
let SEARCH_DN_COUNT=0
|
||||
|
||||
local line search_output
|
||||
record "[Perform $scope search of $base_dn by $login_dn]"
|
||||
search_output=$(ldapsearch -H $LDAP_URL -o ldif-wrap=no -b "$base_dn" -s "$scope" -LLL -x -D "$login_dn" -w "$login_pass" $filter 2>>$TEST_OF)
|
||||
local code=$?
|
||||
# code 32: No such object (doesn't exist or login can't see it)
|
||||
[ $code -ne 0 -a $code -ne 32 ] && die "Unable to search $base_dn by $login_dn"
|
||||
|
||||
while read line; do
|
||||
record "line: $line"
|
||||
case $line in
|
||||
dn:*)
|
||||
let SEARCH_DN_COUNT+=1
|
||||
;;
|
||||
esac
|
||||
done <<< "$search_output"
|
||||
record "$SEARCH_DN_COUNT entries found"
|
||||
}
|
||||
|
||||
|
||||
record_search() {
|
||||
local dn="$1"
|
||||
record "[Contents of $dn]"
|
||||
debug_search "$dn" >>$TEST_OF 2>&1
|
||||
return 0
|
||||
}
|
||||
369
tests/suites/_mail-functions.sh
Normal file
369
tests/suites/_mail-functions.sh
Normal file
@@ -0,0 +1,369 @@
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
|
||||
clear_postfix_queue() {
|
||||
record "[Clear postfix queue]"
|
||||
postsuper -d ALL >>$TEST_OF 2>&1 || die "Unable to clear postfix undeliverable mail queue"
|
||||
}
|
||||
|
||||
|
||||
ensure_root_user() {
|
||||
# ensure there is a local email account for root.
|
||||
#
|
||||
# on exit, ROOT, ROOT_MAILDROP, and ROOT_DN are set, and if no
|
||||
# account exists, a new root@$(hostname) is created having a
|
||||
# random password
|
||||
#
|
||||
if [ ! -z "$ROOT_MAILDROP" ]; then
|
||||
# already have it
|
||||
return
|
||||
fi
|
||||
ROOT="${USER}@$(hostname)"
|
||||
record "[Find user $ROOT]"
|
||||
get_attribute "$LDAP_USERS_BASE" "mail=$ROOT" "maildrop"
|
||||
ROOT_MAILDROP="$ATTR_VALUE"
|
||||
ROOT_DN="$ATTR_DN"
|
||||
if [ -z "$ROOT_DN" ]; then
|
||||
local pw="$(generate_password 128)"
|
||||
create_user "$ROOT" "$pw"
|
||||
record "new password is: $pw"
|
||||
ROOT_DN="$ATTR_DN"
|
||||
ROOT_MAILDROP="$ROOT"
|
||||
else
|
||||
record "$ROOT => $ROOT_DN ($ROOT_MAILDROP)"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
dovecot_mailbox_home() {
|
||||
local email="$1"
|
||||
echo -n "${STORAGE_ROOT}/mail/mailboxes/"
|
||||
awk -F@ '{print $2"/"$1}' <<< "$email"
|
||||
}
|
||||
|
||||
|
||||
start_log_capture() {
|
||||
SYS_LOG_LINECOUNT=$(wc -l /var/log/syslog 2>>$TEST_OF | awk '{print $1}') || die "could not access /var/log/syslog"
|
||||
SLAPD_LOG_LINECOUNT=0
|
||||
if [ -e /var/log/ldap/slapd.log ]; then
|
||||
SLAPD_LOG_LINECOUNT=$(wc -l /var/log/ldap/slapd.log 2>>$TEST_OF | awk '{print $1}') || die "could not access /var/log/ldap/slapd.log"
|
||||
fi
|
||||
MAIL_ERRLOG_LINECOUNT=0
|
||||
if [ -e /var/log/mail.err ]; then
|
||||
MAIL_ERRLOG_LINECOUNT=$(wc -l /var/log/mail.err 2>>$TEST_OF | awk '{print $1}') || die "could not access /var/log/mail.err"
|
||||
fi
|
||||
MAIL_LOG_LINECOUNT=0
|
||||
if [ -e /var/log/mail.log ]; then
|
||||
MAIL_LOG_LINECOUNT=$(wc -l /var/log/mail.log 2>>$TEST_OF | awk '{print $1}') || die "could not access /var/log/mail.log"
|
||||
fi
|
||||
DOVECOT_LOG_LINECOUNT=$(doveadm log errors 2>>$TEST_OF | wc -l | awk '{print $1}') || die "could not access doveadm error logs"
|
||||
}
|
||||
|
||||
start_mail_capture() {
|
||||
local email="$1"
|
||||
local newdir="$(dovecot_mailbox_home "$email")/new"
|
||||
record "[Start mail capture $email]"
|
||||
DOVECOT_CAPTURE_USER="$email"
|
||||
DOVECOT_CAPTURE_FILECOUNT=0
|
||||
if [ -e "$newdir" ]; then
|
||||
DOVECOT_CAPTURE_FILECOUNT=$(ls "$newdir" 2>>$TEST_OF | wc -l)
|
||||
[ $? -ne 0 ] && die "Error accessing mailbox of $email"
|
||||
fi
|
||||
record "mailbox: $(dirname $newdir)"
|
||||
record "mailbox has $DOVECOT_CAPTURE_FILECOUNT files"
|
||||
}
|
||||
|
||||
dump_capture_logs() {
|
||||
# dump log files
|
||||
record "[capture log dump]"
|
||||
echo ""
|
||||
echo "============= SYSLOG ================"
|
||||
tail --lines=+$SYS_LOG_LINECOUNT /var/log/syslog 2>>$TEST_OF
|
||||
echo ""
|
||||
echo "============= SLAPD ================="
|
||||
tail --lines=+$SLAPD_LOG_LINECOUNT /var/log/ldap/slapd.log 2>>$TEST_OF
|
||||
echo ""
|
||||
echo "============= MAIL.ERR =============="
|
||||
tail --lines=+$MAIL_ERRLOG_LINECOUNT /var/log/mail.err 2>>$TEST_OF
|
||||
echo ""
|
||||
echo "============= MAIL.LOG =============="
|
||||
tail --lines=+$MAIL_LOG_LINECOUNT /var/log/mail.log 2>>$TEST_OF
|
||||
echo ""
|
||||
echo "============= DOVECOT ERRORS =============="
|
||||
doveadm log errors | tail --lines=+$DOVECOT_LOG_LINECOUNT 2>>$TEST_OF
|
||||
}
|
||||
|
||||
detect_syslog_error() {
|
||||
record
|
||||
record "[Detect syslog errors]"
|
||||
local count
|
||||
let count="$SYS_LOG_LINECOUNT + 1"
|
||||
tail --lines=+$count /var/log/syslog 2>>$TEST_OF | (
|
||||
let ec=0 # error count
|
||||
while read line; do
|
||||
awk '
|
||||
/status=(bounced|deferred|undeliverable)/ { exit 1 }
|
||||
!/postfix\/qmgr/ && /warning:/ { exit 1 }
|
||||
/(fatal|reject|error):/ { exit 1 }
|
||||
/Error in / { exit 1 }
|
||||
/named\[\d+\]:.* verify failed/ { exit 1 }
|
||||
' \
|
||||
>>$TEST_OF 2>&1 <<< "$line"
|
||||
if [ $? -eq 1 ]; then
|
||||
let ec+=1
|
||||
record "$F_DANGER[ERROR] $line$F_RESET"
|
||||
else
|
||||
record "[ OK] $line"
|
||||
fi
|
||||
done
|
||||
[ $ec -gt 0 ] && exit 0
|
||||
exit 1 # no errors
|
||||
)
|
||||
local x=( ${PIPESTATUS[*]} )
|
||||
[ ${x[0]} -ne 0 ] && die "Could not read /var/log/syslog"
|
||||
return ${x[1]}
|
||||
}
|
||||
|
||||
detect_slapd_log_error() {
|
||||
record
|
||||
record "[Detect slapd log errors]"
|
||||
local count
|
||||
let count="SLAPD_LOG_LINECOUNT + 1"
|
||||
tail --lines=+$count /var/log/ldap/slapd.log 2>>$TEST_OF | (
|
||||
let ec=0 # error count
|
||||
let wc=0 # warning count
|
||||
let ignored=0
|
||||
while read line; do
|
||||
# slapd error 68 = "entry already exists". Mark it as a
|
||||
# warning because code often attempts to add entries
|
||||
# silently ignoring the error, which is expected behavior
|
||||
#
|
||||
# slapd error 32 = "no such object". Mark it as a warning
|
||||
# because code often attempts to resolve a dn (eg member)
|
||||
# that is orphaned, so no entry exists. Code may or may
|
||||
# not care about this.
|
||||
#
|
||||
# slapd error 4 - "size limit exceeded". Mark it as a warning
|
||||
# because code often attempts to find just 1 entry so sets
|
||||
# the limit to 1 purposefully.
|
||||
#
|
||||
# slapd error 20 - "attribute or value exists". Mark it as a
|
||||
# warning becuase code often attempts to add a new value
|
||||
# to an existing attribute and doesn't care if the new
|
||||
# value fails to add because it already exists.
|
||||
#
|
||||
awk '
|
||||
/SEARCH RESULT.*err=(32|4) / { exit 2}
|
||||
/RESULT.*err=(68|20) / { exit 2 }
|
||||
/ not indexed/ { exit 2 }
|
||||
/RESULT.*err=[^0]/ { exit 1 }
|
||||
/(get|test)_filter/ { exit 3 }
|
||||
/mdb_(filter|list)_candidates/ { exit 3 }
|
||||
/:( | #011| )(AND|OR|EQUALITY)/ { exit 3 }
|
||||
' \
|
||||
>>$TEST_OF 2>&1 <<< "$line"
|
||||
r=$?
|
||||
if [ $r -eq 1 ]; then
|
||||
let ec+=1
|
||||
record "$F_DANGER[ERROR] $line$F_RESET"
|
||||
elif [ $r -eq 2 ]; then
|
||||
let wc+=1
|
||||
record "$F_WARN[WARN ] $line$F_RESET"
|
||||
elif [ $r -eq 3 ]; then
|
||||
let ignored+=1
|
||||
else
|
||||
record "[OK ] $line"
|
||||
fi
|
||||
done
|
||||
record "$ignored unreported/ignored log lines"
|
||||
[ $ec -gt 0 ] && exit 0
|
||||
exit 1 # no errors
|
||||
)
|
||||
local x=( ${PIPESTATUS[*]} )
|
||||
[ ${x[0]} -ne 0 ] && die "Could not read /var/log/ldap/slapd.log"
|
||||
return ${x[1]}
|
||||
}
|
||||
|
||||
|
||||
detect_dovecot_log_error() {
|
||||
record
|
||||
record "[Detect dovecot log errors]"
|
||||
local count
|
||||
let count="$MAIL_LOG_LINECOUNT + 1"
|
||||
if [ ! -e /var/log/mail.log ]; then
|
||||
return 0
|
||||
fi
|
||||
# prefer mail.log over `dovadm log errors` because the latter does
|
||||
# not have as much output - it's helpful to have success logs when
|
||||
# diagnosing logs...
|
||||
cat /var/log/mail.log 2>>$TEST_OF | tail --lines=+$count | (
|
||||
let ec=0 # error count
|
||||
let ignored=0
|
||||
while read line; do
|
||||
awk '
|
||||
/LDAP server, reconnecting/ { exit 2 }
|
||||
/postfix/ { exit 2 }
|
||||
/auth failed/ { exit 1 }
|
||||
/ Error: / { exit 1 }
|
||||
' \
|
||||
>>$TEST_OF 2>&1 <<< "$line"
|
||||
r=$?
|
||||
if [ $r -eq 1 ]; then
|
||||
let ec+=1
|
||||
record "$F_DANGER[ERROR] $line$F_RESET"
|
||||
elif [ $r -eq 2 ]; then
|
||||
let ignored+=1
|
||||
else
|
||||
record "[ OK] $line"
|
||||
fi
|
||||
done
|
||||
record "$ignored unreported/ignored log lines"
|
||||
[ $ec -gt 0 ] && exit 0
|
||||
exit 1 # no errors
|
||||
)
|
||||
local x=( ${PIPESTATUS[*]} )
|
||||
[ ${x[0]} -ne 0 -o ${x[1]} -ne 0 ] && die "Could not read mail log"
|
||||
return ${x[2]}
|
||||
}
|
||||
|
||||
|
||||
check_logs() {
|
||||
local assert="${1:-false}"
|
||||
[ "$1" == "true" -o "$1" == "false" ] && shift
|
||||
local types=($@)
|
||||
[ ${#types[@]} -eq 0 ] && types=(syslog slapd mail)
|
||||
|
||||
# flush records
|
||||
kill -HUP $(cat /var/run/rsyslogd.pid)
|
||||
sleep 2
|
||||
|
||||
if array_contains syslog ${types[@]}; then
|
||||
detect_syslog_error && $assert &&
|
||||
test_failure "detected errors in syslog"
|
||||
fi
|
||||
|
||||
if array_contains slapd ${types[@]}; then
|
||||
detect_slapd_log_error && $assert &&
|
||||
test_failure "detected errors in slapd log"
|
||||
fi
|
||||
|
||||
if array_contains mail ${types[@]}; then
|
||||
detect_dovecot_log_error && $assert &&
|
||||
test_failure "detected errors in dovecot log"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_check_logs() {
|
||||
check_logs true $@
|
||||
}
|
||||
|
||||
grep_postfix_log() {
|
||||
local msg="$1"
|
||||
local count
|
||||
let count="$SYS_LOG_LINECOUNT + 1"
|
||||
tail --lines=+$count /var/log/syslog 2>>$TEST_OF | grep -iF "$msg" >/dev/null 2>>$TEST_OF
|
||||
return $?
|
||||
}
|
||||
|
||||
wait_mail() {
|
||||
local x mail_files elapsed max_s="${1:-60}"
|
||||
let elapsed=0
|
||||
record "[Waiting for mail to $DOVECOT_CAPTURE_USER]"
|
||||
while [ $elapsed -lt $max_s ]; do
|
||||
mail_files=( $(get_captured_mail_files) )
|
||||
[ ${#mail_files[*]} -gt 0 ] && break
|
||||
sleep 1
|
||||
let elapsed+=1
|
||||
let x="$elapsed % 10"
|
||||
[ $x -eq 0 ] && record "...~${elapsed} seconds has passed"
|
||||
done
|
||||
if [ $elapsed -ge $max_s ]; then
|
||||
record "Timeout waiting for mail"
|
||||
return 1
|
||||
fi
|
||||
record "new mail files:"
|
||||
for x in ${mail_files[@]}; do
|
||||
record "$x"
|
||||
done
|
||||
}
|
||||
|
||||
get_captured_mail_files() {
|
||||
local newdir="$(dovecot_mailbox_home "$DOVECOT_CAPTURE_USER")/new"
|
||||
local count
|
||||
let count="$DOVECOT_CAPTURE_FILECOUNT + 1"
|
||||
[ ! -e "$newdir" ] && return 0
|
||||
# output absolute path names
|
||||
local file
|
||||
for file in $(ls "$newdir" 2>>$TEST_OF | tail --lines=+${count}); do
|
||||
echo "$newdir/$file"
|
||||
done
|
||||
}
|
||||
|
||||
record_captured_mail() {
|
||||
local files=( $(get_captured_mail_files) )
|
||||
local file
|
||||
for file in ${files[@]}; do
|
||||
record
|
||||
record "[Captured mail file: $file]"
|
||||
cat "$file" >>$TEST_OF 2>&1
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
sendmail_bv_send() {
|
||||
# test sending mail, but don't actually send it...
|
||||
local recpt="$1"
|
||||
local timeout="$2"
|
||||
local bvfrom from="$3"
|
||||
# delivery status is emailed back to us, or 'from' if supplied
|
||||
clear_postfix_queue
|
||||
if [ -z "$from" ]; then
|
||||
ensure_root_user
|
||||
start_mail_capture "$ROOT"
|
||||
else
|
||||
bvfrom="-f $from"
|
||||
start_mail_capture "$from"
|
||||
fi
|
||||
record "[Running sendmail -bv $bvfrom]"
|
||||
sendmail $bvfrom -bv "$recpt" >>$TEST_OF 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
test_failure "Error executing sendmail"
|
||||
else
|
||||
wait_mail $timeout || test_failure "Timeout waiting for delivery report"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
assert_python_success() {
|
||||
local code="$1"
|
||||
local output="$2"
|
||||
record "$output"
|
||||
record
|
||||
record "python exit code: $code"
|
||||
if [ $code -ne 0 ]; then
|
||||
test_failure "unable to process mail: $(python_error "$output")"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
assert_python_failure() {
|
||||
local code="$1"
|
||||
local output="$2"
|
||||
shift; shift
|
||||
record "$output"
|
||||
record
|
||||
record "python exit code: $code"
|
||||
if [ $code -eq 0 ]; then
|
||||
test_failure "python succeeded but expected failure"
|
||||
return 1
|
||||
fi
|
||||
local look_for
|
||||
for look_for; do
|
||||
if [ ! -z "$look_for" ] && ! grep "$look_for" <<< "$output" 1>/dev/null
|
||||
then
|
||||
test_failure "unexpected python failure: $(python_error "$output")"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
175
tests/suites/_mgmt-functions.sh
Normal file
175
tests/suites/_mgmt-functions.sh
Normal file
@@ -0,0 +1,175 @@
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
|
||||
# Available REST calls:
|
||||
#
|
||||
# general curl format:
|
||||
# curl -X <b>VERB</b> [-d "<b>parameters</b>"] --user {email}:{password} https://{{hostname}}/admin/mail/users[<b>action</b>]
|
||||
|
||||
# ALIASES:
|
||||
# curl -X GET https://{{hostname}}/admin/mail/aliases?format=json
|
||||
# curl -X POST -d "address=new_alias@mydomail.com" -d "forwards_to=my_email@mydomain.com" https://{{hostname}}/admin/mail/aliases/add
|
||||
# curl -X POST -d "address=new_alias@mydomail.com" https://{{hostname}}/admin/mail/aliases/remove
|
||||
|
||||
# USERS:
|
||||
# curl -X GET https://{{hostname}}/admin/mail/users?format=json
|
||||
# curl -X POST -d "email=new_user@mydomail.com" -d "password=s3curE_pa5Sw0rD" https://{{hostname}}/admin/mail/users/add
|
||||
# curl -X POST -d "email=new_user@mydomail.com" https://{{hostname}}/admin/mail/users/remove
|
||||
# curl -X POST -d "email=new_user@mydomail.com" -d "privilege=admin" https://{{hostname}}/admin/mail/users/privileges/add
|
||||
# curl -X POST -d "email=new_user@mydomail.com" https://{{hostname}}/admin/mail/users/privileges/remove
|
||||
|
||||
|
||||
mgmt_start() {
|
||||
# Must be called before performing any REST calls
|
||||
local domain="${1:-somedomain.com}"
|
||||
MGMT_ADMIN_EMAIL="test_admin@$domain"
|
||||
MGMT_ADMIN_PW="$(generate_password)"
|
||||
|
||||
delete_user "$MGMT_ADMIN_EMAIL"
|
||||
|
||||
record "[Creating a new account with admin rights for management tests]"
|
||||
create_user "$MGMT_ADMIN_EMAIL" "$MGMT_ADMIN_PW" "admin"
|
||||
MGMT_ADMIN_DN="$ATTR_DN"
|
||||
record "Created: $MGMT_ADMIN_EMAIL at $MGMT_ADMIN_DN"
|
||||
}
|
||||
|
||||
mgmt_end() {
|
||||
# Clean up after mgmt_start
|
||||
delete_user "$MGMT_ADMIN_EMAIL"
|
||||
}
|
||||
|
||||
|
||||
mgmt_rest() {
|
||||
# Issue a REST call to the management subsystem
|
||||
local verb="$1" # eg "POST"
|
||||
local uri="$2" # eg "/mail/users/add"
|
||||
shift; shift; # remaining arguments are data
|
||||
|
||||
local auth_user="${MGMT_ADMIN_EMAIL}"
|
||||
local auth_pass="${MGMT_ADMIN_PW}"
|
||||
local url="https://$PRIMARY_HOSTNAME${uri}"
|
||||
local data=()
|
||||
local item output
|
||||
|
||||
for item; do data+=("--data-urlencode" "$item"); done
|
||||
|
||||
record "spawn: curl -w \"%{http_code}\" -X $verb --user \"${auth_user}:xxx\" ${data[@]} $url"
|
||||
output=$(curl -s -S -w "%{http_code}" -X $verb --user "${auth_user}:${auth_pass}" "${data[@]}" $url 2>>$TEST_OF)
|
||||
local code=$?
|
||||
|
||||
# http status is last 3 characters of output, extract it
|
||||
REST_HTTP_CODE=$(awk '{S=substr($0,length($0)-2)} END {print S}' <<<"$output")
|
||||
REST_OUTPUT=$(awk 'BEGIN{L=""}{ if(L!="") print L; L=$0 } END { print substr(L,1,length(L)-3) }' <<<"$output")
|
||||
REST_ERROR=""
|
||||
[ -z "$REST_HTTP_CODE" ] && REST_HTTP_CODE="000"
|
||||
|
||||
if [ $code -ne 0 ]; then
|
||||
if [ $code -ne 16 -o $REST_HTTP_CODE -ne 200 ]; then
|
||||
REST_ERROR="CURL failed with code $code"
|
||||
record "${F_DANGER}$REST_ERROR${F_RESET}"
|
||||
record "$output"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if [ $REST_HTTP_CODE -lt 200 -o $REST_HTTP_CODE -ge 300 ]; then
|
||||
REST_ERROR="REST status $REST_HTTP_CODE: $REST_OUTPUT"
|
||||
record "${F_DANGER}$REST_ERROR${F_RESET}"
|
||||
return 2
|
||||
fi
|
||||
record "CURL succeded, HTTP status $REST_HTTP_CODE"
|
||||
record "$output"
|
||||
return 0
|
||||
}
|
||||
|
||||
mgmt_create_user() {
|
||||
local email="$1"
|
||||
local pass="${2:-$email}"
|
||||
local delete_first="${3:-yes}"
|
||||
|
||||
# ensure the user is deleted (clean test run)
|
||||
if [ "$delete_first" == "yes" ]; then
|
||||
delete_user "$email"
|
||||
fi
|
||||
record "[create user $email]"
|
||||
mgmt_rest POST /admin/mail/users/add "email=$email" "password=$pass"
|
||||
return $?
|
||||
}
|
||||
|
||||
mgmt_assert_create_user() {
|
||||
local email="$1"
|
||||
local pass="$2"
|
||||
local delete_first="${3}"
|
||||
if ! mgmt_create_user "$email" "$pass" "$delete_first"; then
|
||||
test_failure "Unable to create user $email"
|
||||
test_failure "${REST_ERROR}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
mgmt_delete_user() {
|
||||
local email="$1"
|
||||
record "[delete user $email]"
|
||||
mgmt_rest POST /admin/mail/users/remove "email=$email"
|
||||
return $?
|
||||
}
|
||||
|
||||
mgmt_assert_delete_user() {
|
||||
local email="$1"
|
||||
if ! mgmt_delete_user "$email"; then
|
||||
test_failure "Unable to cleanup/delete user $email"
|
||||
test_failure "$REST_ERROR"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
mgmt_create_alias_group() {
|
||||
local alias="$1"
|
||||
shift
|
||||
record "[Create new alias group $alias]"
|
||||
record "members: $@"
|
||||
# ensure the group is deleted (clean test run)
|
||||
record "Try deleting any existing entry"
|
||||
if ! mgmt_rest POST /admin/mail/aliases/remove "address=$alias"; then
|
||||
get_attribute "$LDAP_ALIASES_BASE" "mail=$alias" "dn"
|
||||
if [ ! -z "$ATTR_DN" ]; then
|
||||
delete_dn "$ATTR_DN"
|
||||
fi
|
||||
fi
|
||||
|
||||
record "Create the alias group"
|
||||
local members="$1" member
|
||||
shift
|
||||
for member; do members="${members},${member}"; done
|
||||
|
||||
mgmt_rest POST /admin/mail/aliases/add "address=$alias" "forwards_to=$members"
|
||||
return $?
|
||||
}
|
||||
|
||||
mgmt_assert_create_alias_group() {
|
||||
local alias="$1"
|
||||
shift
|
||||
if ! mgmt_create_alias_group "$alias" "$@"; then
|
||||
test_failure "Unable to create alias group $alias"
|
||||
test_failure "${REST_ERROR}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
mgmt_delete_alias_group() {
|
||||
local alias="$1"
|
||||
record "[Delete alias group $alias]"
|
||||
mgmt_rest POST /admin/mail/aliases/remove "address=$alias"
|
||||
return $?
|
||||
}
|
||||
|
||||
mgmt_assert_delete_alias_group() {
|
||||
local alias="$1"
|
||||
if ! mgmt_delete_alias_group "$alias"; then
|
||||
test_failure "Unable to cleanup/delete alias group $alias"
|
||||
test_failure "$REST_ERROR"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
233
tests/suites/ldap-access.sh
Normal file
233
tests/suites/ldap-access.sh
Normal file
@@ -0,0 +1,233 @@
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
#
|
||||
# Access assertions:
|
||||
# service accounts, except management:
|
||||
# can bind but not change passwords, including their own
|
||||
# can read all attributes of all users but not userPassword
|
||||
# can not write any user attributes, include shadowLastChange
|
||||
# can read config subtree (permitted-senders, domains)
|
||||
# no access to services subtree, except their own dn
|
||||
# users:
|
||||
# can bind and change their own password
|
||||
# can read and change their own shadowLastChange
|
||||
# can read attributess of all users except mailaccess
|
||||
# no access to config subtree
|
||||
# no access to services subtree
|
||||
# other:
|
||||
# no anonymous binds to root DSE
|
||||
# no anonymous binds to database
|
||||
#
|
||||
|
||||
|
||||
test_user_change_password() {
|
||||
# users should be able to change their own passwords
|
||||
test_start "user-change-password"
|
||||
|
||||
# create regular user with password "alice"
|
||||
local alice="alice@somedomain.com"
|
||||
create_user "$alice" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
|
||||
# bind as alice and update userPassword
|
||||
assert_w_access "$alice_dn" "$alice_dn" "alice" write "userPassword=$(slappasswd_hash "alice-new")"
|
||||
delete_user "$alice"
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_user_access() {
|
||||
# 1. can read attributess of all users except mailaccess
|
||||
# 2. can read and change their own shadowLastChange
|
||||
# 3. no access to config subtree
|
||||
# 4. no access to services subtree
|
||||
test_start "user-access"
|
||||
|
||||
# create regular user's alice and bob
|
||||
local alice="alice@somedomain.com"
|
||||
create_user "alice@somedomain.com" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
|
||||
local bob="bob@somedomain.com"
|
||||
create_user "bob@somedomain.com" "bob"
|
||||
local bob_dn="$ATTR_DN"
|
||||
|
||||
# alice should be able to set her own shadowLastChange
|
||||
assert_w_access "$alice_dn" "$alice_dn" "alice" write "shadowLastChange=0"
|
||||
|
||||
# test that alice can read her own attributes
|
||||
assert_r_access "$alice_dn" "$alice_dn" "alice" read mail maildrop cn sn shadowLastChange
|
||||
# alice should not have access to her own mailaccess, though
|
||||
assert_r_access "$alice_dn" "$alice_dn" "alice" no-read mailaccess
|
||||
# test that alice cannot change her own select attributes
|
||||
assert_w_access "$alice_dn" "$alice_dn" "alice"
|
||||
|
||||
|
||||
# test that alice can read bob's attributes
|
||||
assert_r_access "$bob_dn" "$alice_dn" "alice" read mail maildrop cn sn
|
||||
# alice does not have access to bob's mailaccess though
|
||||
assert_r_access "$bob_dn" "$alice_dn" "alice" no-read mailaccess
|
||||
# test that alice cannot change bob's attributes
|
||||
assert_w_access "$bob_dn" "$alice_dn" "alice"
|
||||
|
||||
|
||||
# test that alice cannot read a service account's attributes
|
||||
assert_r_access "$LDAP_POSTFIX_DN" "$alice_dn" "alice"
|
||||
|
||||
# test that alice cannot read config entries
|
||||
assert_r_access "dc=somedomain.com,$LDAP_DOMAINS_BASE" "$alice_dn" "alice"
|
||||
assert_r_access "$LDAP_PERMITTED_SENDERS_BASE" "$alice_dn" "alice"
|
||||
|
||||
# test that alice cannot find anything searching config
|
||||
test_search "$LDAP_CONFIG_BASE" "$alice_dn" "alice"
|
||||
[ $SEARCH_DN_COUNT -gt 0 ] && test_failure "Users should not be able to search config"
|
||||
|
||||
# test that alice cannot find anything searching config domains
|
||||
test_search "$LDAP_DOMAINS_BASE" "$alice_dn" "alice"
|
||||
[ $SEARCH_DN_COUNT -gt 0 ] && test_failure "Users should not be able to search config domains"
|
||||
|
||||
# test that alice cannot find anything searching services
|
||||
test_search "$LDAP_SERVICES_BASE" "$alice_dn" "alice"
|
||||
[ $SEARCH_DN_COUNT -gt 0 ] && test_failure "Users should not be able to search services"
|
||||
|
||||
delete_user "$alice"
|
||||
delete_user "$bob"
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
|
||||
test_service_change_password() {
|
||||
# service accounts should not be able to change other user's
|
||||
# passwords
|
||||
# service accounts should not be able to change their own password
|
||||
test_start "service-change-password"
|
||||
|
||||
# create regular user with password "alice"
|
||||
local alice="alice@somedomain.com"
|
||||
create_user "alice@somedomain.com" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
|
||||
# create a test service account
|
||||
create_service_account "test" "test"
|
||||
local service_dn="$ATTR_DN"
|
||||
|
||||
# update userPassword of user using service account
|
||||
assert_w_access "$alice_dn" "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD" no-write "userPassword=$(slappasswd_hash "alice-new")"
|
||||
|
||||
# update userPassword of service account using service account
|
||||
assert_w_access "$service_dn" "$service_dn" "test" no-write "userPassword=$(slappasswd_hash "test-new")"
|
||||
|
||||
delete_user "$alice"
|
||||
delete_service_account "test"
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_service_access() {
|
||||
# service accounts should have read-only access to all attributes
|
||||
# of all users except userPassword
|
||||
# can not write any user attributes, include shadowLastChange
|
||||
# can read config subtree (permitted-senders, domains)
|
||||
# no access to services subtree, except their own dn
|
||||
|
||||
test_start "service-access"
|
||||
|
||||
# create regular user with password "alice"
|
||||
local alice="alice@somedomain.com"
|
||||
create_user "alice@somedomain.com" "alice"
|
||||
|
||||
# create a test service account
|
||||
create_service_account "test" "test"
|
||||
local service_dn="$ATTR_DN"
|
||||
|
||||
# Use service account to find alice
|
||||
record "[Use service account to find alice]"
|
||||
get_attribute "$LDAP_USERS_BASE" "mail=${alice}" dn sub "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD"
|
||||
if [ -z "$ATTR_DN" ]; then
|
||||
test_failure "Unable to search for user account using service account"
|
||||
else
|
||||
local alice_dn="$ATTR_DN"
|
||||
|
||||
# set shadowLastChange on alice's entry (to test reading it back)
|
||||
assert_w_access "$alice_dn" "$alice_dn" "alice" write "shadowLastChange=0"
|
||||
|
||||
# check that service account can read user attributes
|
||||
assert_r_access "$alice_dn" "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD" read mail maildrop uid cn sn shadowLastChange
|
||||
|
||||
# service account should not be able to read user's userPassword
|
||||
assert_r_access "$alice_dn" "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD" no-read userPassword
|
||||
|
||||
# service accounts cannot change user attributes
|
||||
assert_w_access "$alice_dn" "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD"
|
||||
assert_w_access "$alice_dn" "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD" no-write "shadowLastChange=1"
|
||||
fi
|
||||
|
||||
# service accounts can read config subtree (permitted-senders, domains)
|
||||
assert_r_access "dc=somedomain.com,$LDAP_DOMAINS_BASE" "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD" read dc
|
||||
|
||||
# service accounts can search and find things in the config subtree
|
||||
test_search "$LDAP_CONFIG_BASE" "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD" sub
|
||||
[ $SEARCH_DN_COUNT -lt 4 ] && test_failure "Service accounts should be able to search config"
|
||||
|
||||
# service accounts can read attributes in their own dn
|
||||
assert_r_access "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD" read cn description
|
||||
# ... but not userPassword
|
||||
assert_r_access "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD" no-read userPassword
|
||||
|
||||
# services cannot read other service's attributes
|
||||
assert_r_access "$service_dn" "$LDAP_POSTFIX_DN" "$LDAP_POSTFIX_PASSWORD" no-read cn description userPassword
|
||||
|
||||
delete_user "$alice"
|
||||
delete_service_account "test"
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_root_dse() {
|
||||
# no anonymous binds to root dse
|
||||
test_start "root-dse"
|
||||
|
||||
record "[bind anonymously to root dse]"
|
||||
ldapsearch -H $LDAP_URL -x -b "" -s base >>$TEST_OF 2>&1
|
||||
local r=$?
|
||||
if [ $r -eq 0 ]; then
|
||||
test_failure "Anonymous access to root dse should not be permitted"
|
||||
elif [ $r -eq 48 ]; then
|
||||
# 48=inappropriate authentication (anon binds not allowed)
|
||||
test_success
|
||||
else
|
||||
die "Error accessing root dse"
|
||||
fi
|
||||
test_end
|
||||
}
|
||||
|
||||
test_anon_bind() {
|
||||
test_start "anon-bind"
|
||||
|
||||
record "[bind anonymously to $LDAP_BASE]"
|
||||
ldapsearch -H $LDAP_URL -x -b "$LDAP_BASE" -s base >>$TEST_OF 2>&1
|
||||
local r=$?
|
||||
if [ $r -eq 0 ]; then
|
||||
test_failure "Anonymous access should not be permitted"
|
||||
elif [ $r -eq 48 ]; then
|
||||
# 48=inappropriate authentication (anon binds not allowed)
|
||||
test_success
|
||||
else
|
||||
die "Error accessing $LDAP_BASE"
|
||||
fi
|
||||
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
|
||||
suite_start "ldap-access"
|
||||
|
||||
test_user_change_password
|
||||
test_user_access
|
||||
test_service_change_password
|
||||
test_service_access
|
||||
test_root_dse
|
||||
test_anon_bind
|
||||
|
||||
suite_end
|
||||
151
tests/suites/ldap-connection.sh
Normal file
151
tests/suites/ldap-connection.sh
Normal file
@@ -0,0 +1,151 @@
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
|
||||
exe_test() {
|
||||
# run an executable and assert success or failure
|
||||
# argument 1 must be:
|
||||
# "ZERO_RC" to assert the return code was 0
|
||||
# "NONZERO_RC" to assert the return code was not 0
|
||||
# argument 2 is a description of the test for logging
|
||||
# argument 3 and higher are the executable and arguments
|
||||
local result_type=$1
|
||||
shift
|
||||
local desc="$1"
|
||||
shift
|
||||
test_start "$desc"
|
||||
record "[CMD: $@]"
|
||||
"$@" >>"$TEST_OF" 2>&1
|
||||
local code=$?
|
||||
case $result_type in
|
||||
ZERO_RC)
|
||||
if [ $code -ne 0 ]; then
|
||||
test_failure "expected zero return code, got $code"
|
||||
else
|
||||
test_success
|
||||
fi
|
||||
;;
|
||||
|
||||
NONZERO_RC)
|
||||
if [ $code -eq 0 ]; then
|
||||
test_failure "expected non-zero return code"
|
||||
else
|
||||
test_success
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
test_failure "unknown TEST type '$result_type'"
|
||||
;;
|
||||
esac
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
tests() {
|
||||
# TLS: auth search to (local)host - expect success
|
||||
exe_test ZERO_RC "TLS-auth-host" \
|
||||
ldapsearch -d 1 -b "dc=mailinabox" -H ldaps://$PRIMARY_HOSTNAME/ -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD"
|
||||
|
||||
# TLS: auth search to localhost - expect failure ("hostname does not match CN in peer certificate")
|
||||
exe_test NONZERO_RC "TLS-auth-local" \
|
||||
ldapsearch -d 1 -b "dc=mailinabox" -H ldaps://127.0.0.1/ -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD"
|
||||
|
||||
# TLS: anon search - expect failure (anon bind disallowed)
|
||||
exe_test NONZERO_RC "TLS-anon-host" \
|
||||
ldapsearch -d 1 -b "dc=mailinabox" -H ldaps://$PRIMARY_HOSTNAME/ -x
|
||||
|
||||
# CLEAR: auth search to host - expected failure (not listening there)
|
||||
exe_test NONZERO_RC "CLEAR-auth-host" \
|
||||
ldapsearch -d 1 -b "dc=mailinabox" -H ldap://$PRIVATE_IP/ -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD"
|
||||
|
||||
# CLEAR: auth search to localhost - expect success
|
||||
exe_test ZERO_RC "CLEAR-auth-local" \
|
||||
ldapsearch -d 1 -b "dc=mailinabox" -H ldap://127.0.0.1/ -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD"
|
||||
|
||||
# CLEAR: anon search - expect failure (anon bind disallowed)
|
||||
exe_test NONZERO_RC "CLEAR-anon-local" \
|
||||
ldapsearch -d 1 -b "dc=mailinabox" -H ldap://127.0.0.1/ -x
|
||||
|
||||
# STARTTLS: auth search to localhost - expected failure ("hostname does not match CN in peer certificate")
|
||||
exe_test NONZERO_RC "STARTTLS-auth-local" \
|
||||
ldapsearch -d 1 -b "dc=mailinabox" -H ldap://127.0.0.1/ -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" -ZZ
|
||||
|
||||
# STARTTLS: auth search to host - expected failure (not listening there)
|
||||
exe_test NONZERO_RC "STARTTLS-auth-host" \
|
||||
ldapsearch -d 1 -b "dc=mailinabox" -H ldap://$PRIVATE_IP/ -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" -ZZ
|
||||
|
||||
}
|
||||
|
||||
|
||||
test_fail2ban() {
|
||||
test_start "fail2ban"
|
||||
|
||||
# reset fail2ban
|
||||
record "[reset fail2ban]"
|
||||
fail2ban-client unban --all >>$TEST_OF 2>&1 ||
|
||||
test_failure "Unable to execute unban --all"
|
||||
|
||||
# create regular user with password "alice"
|
||||
local alice="alice@somedomain.com"
|
||||
create_user "$alice" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
|
||||
# log in a bunch of times with wrong password
|
||||
local n=0
|
||||
local total=25
|
||||
local banned=no
|
||||
record '[log in 25 times with wrong password]'
|
||||
while ! have_test_failures && [ $n -lt $total ]; do
|
||||
ldapsearch -H $LDAP_URL -D "$alice_dn" -w "bad-alice" -b "$LDAP_USERS_BASE" -s base "(objectClass=*)" 1>>$TEST_OF 2>&1
|
||||
local code=$?
|
||||
record "TRY $n: result code $code"
|
||||
|
||||
if [ $code -eq 255 -a $n -gt 5 ]; then
|
||||
# banned - could not connect
|
||||
banned=yes
|
||||
break
|
||||
|
||||
elif [ $code -ne 49 ]; then
|
||||
test_failure "Expected error code 49 (invalidCredentials), but got $code"
|
||||
continue
|
||||
fi
|
||||
|
||||
let n+=1
|
||||
if [ $n -lt $total ]; then
|
||||
record "sleep 1"
|
||||
sleep 1
|
||||
fi
|
||||
done
|
||||
|
||||
if ! have_test_failures && [ "$banned" == "no" ]; then
|
||||
# wait for fail2ban to ban
|
||||
record "[waiting for fail2ban]"
|
||||
record "sleep 5"
|
||||
sleep 5
|
||||
ldapsearch -H ldap://$PRIVATE_IP -D "$alice_dn" -w "bad-alice" -b "$LDAP_USERS_BASE" -s base "(objectClass=*)" 1>>$TEST_OF 2>&1
|
||||
local code=$?
|
||||
record "$n result: $code"
|
||||
if [ $code -ne 255 ]; then
|
||||
test_failure "Expected to be banned after repeated login failures, but wasn't"
|
||||
fi
|
||||
fi
|
||||
|
||||
# delete alice
|
||||
delete_user "$alice"
|
||||
|
||||
# reset fail2ban
|
||||
record "[reset fail2ban]"
|
||||
fail2ban-client unban --all >>$TEST_OF 2>&1 ||
|
||||
test_failure "Unable to execute unban --all"
|
||||
|
||||
# done
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
suite_start "ldap-connection"
|
||||
|
||||
tests
|
||||
test_fail2ban
|
||||
|
||||
suite_end
|
||||
|
||||
199
tests/suites/mail-access.sh
Normal file
199
tests/suites/mail-access.sh
Normal file
@@ -0,0 +1,199 @@
|
||||
# -*- 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_reset() {
|
||||
# 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
|
||||
|
||||
# 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 did
|
||||
# not...
|
||||
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() {
|
||||
# test mail rejection due to SPF policy of FROM address
|
||||
test_start "spf"
|
||||
|
||||
# create standard user alice
|
||||
local alice="alice@somedomain.com"
|
||||
create_user "$alice" "alice"
|
||||
|
||||
# who we will impersonate
|
||||
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_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
|
||||
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"
|
||||
|
||||
test_greylisting
|
||||
test_relay_prohibited
|
||||
test_spf
|
||||
test_mailbox_pipe
|
||||
|
||||
suite_end
|
||||
329
tests/suites/mail-aliases.sh
Normal file
329
tests/suites/mail-aliases.sh
Normal file
@@ -0,0 +1,329 @@
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
# mail alias tests
|
||||
#
|
||||
|
||||
test_shared_user_alias_login() {
|
||||
# a login attempt should fail when using 'mail' aliases that map
|
||||
# to two or more users
|
||||
|
||||
test_start "shared-user-alias-login"
|
||||
# create standard users alice and bob
|
||||
local alice="alice@somedomain.com"
|
||||
local bob="bob@anotherdomain.com"
|
||||
create_user "$alice" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
create_user "$bob" "bob"
|
||||
local bob_dn="$ATTR_DN"
|
||||
|
||||
# add common alias to alice and bob
|
||||
local alias="us@somedomain.com"
|
||||
add_alias $alice_dn $alias user
|
||||
add_alias $bob_dn $alias user
|
||||
|
||||
start_log_capture
|
||||
record "[Log in as alias to postfix]"
|
||||
local output
|
||||
local subject="Mail-In-A-Box test $(generate_uuid)"
|
||||
|
||||
# login as the alias to postfix - should fail
|
||||
output="$($PYMAIL -subj "$subject" -no-delete $PRIVATE_IP $alias alice 2>&1)"
|
||||
assert_python_failure $? "$output" "SMTPAuthenticationError"
|
||||
|
||||
# login as the alias to dovecot - should fail
|
||||
record "[Log in as alias to dovecot]"
|
||||
local timeout=""
|
||||
if have_test_failures; then
|
||||
timeout="-timeout 0"
|
||||
fi
|
||||
output="$($PYMAIL -subj "$subject" $timeout -no-send $PRIVATE_IP $alias alice 2>&1)"
|
||||
assert_python_failure $? "$output" "authentication failure"
|
||||
|
||||
check_logs
|
||||
|
||||
delete_user "$alice"
|
||||
delete_user "$bob"
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_alias_group_member_login() {
|
||||
# a login attempt should fail when using an alias defined in a
|
||||
# mailGroup type alias
|
||||
|
||||
test_start "alias-group-member-login"
|
||||
# create standard user alice
|
||||
local alice="alice@somedomain.com"
|
||||
create_user "$alice" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
|
||||
# create alias group with alice in it
|
||||
local alias="us@somedomain.com"
|
||||
create_alias_group "$alias" "$alice_dn"
|
||||
|
||||
start_log_capture
|
||||
record "[Log in as alias to postfix]"
|
||||
local output
|
||||
local subject="Mail-In-A-Box test $(generate_uuid)"
|
||||
|
||||
# login as the alias to postfix - should fail
|
||||
output="$($PYMAIL -subj "$subject" -no-delete $PRIVATE_IP $alias alice 2>&1)"
|
||||
assert_python_failure $? "$output" "SMTPAuthenticationError"
|
||||
|
||||
# login as the alias to dovecot - should fail
|
||||
record "[Log in as alias to dovecot]"
|
||||
local timeout=""
|
||||
if have_test_failures; then
|
||||
timeout="-timeout 0"
|
||||
fi
|
||||
output="$($PYMAIL -subj "$subject" $timeout -no-send $PRIVATE_IP $alias alice 2>&1)"
|
||||
assert_python_failure $? "$output" "AUTHENTICATIONFAILED"
|
||||
|
||||
check_logs
|
||||
|
||||
delete_user "$alice"
|
||||
delete_alias_group "$alias"
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_shared_alias_delivery() {
|
||||
# mail sent to the shared alias of two users (eg. postmaster),
|
||||
# should be sent to both users
|
||||
test_start "shared-alias-delivery"
|
||||
# create standard users alice, bob, and mary
|
||||
local alice="alice@somedomain.com"
|
||||
local bob="bob@anotherdomain.com"
|
||||
local mary="mary@anotherdomain.com"
|
||||
create_user "$alice" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
create_user "$bob" "bob"
|
||||
local bob_dn="$ATTR_DN"
|
||||
create_user "$mary" "mary"
|
||||
|
||||
# add common alias to alice and bob
|
||||
local alias="us@somedomain.com"
|
||||
create_alias_group $alias $alice_dn $bob_dn
|
||||
|
||||
# login as mary and send to alias
|
||||
start_log_capture
|
||||
record "[Sending mail to alias]"
|
||||
local output
|
||||
local subject="Mail-In-A-Box test $(generate_uuid)"
|
||||
output="$($PYMAIL -subj "$subject" -no-delete -to $alias na $PRIVATE_IP $mary mary 2>&1)"
|
||||
if assert_python_success $? "$output"; then
|
||||
# check that alice and bob received it by deleting the mail in
|
||||
# both mailboxes
|
||||
record "[Delete mail alice's mailbox]"
|
||||
output="$($PYMAIL -subj "$subject" -no-send $PRIVATE_IP $alice alice 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
record "[Delete mail bob's mailbox]"
|
||||
output="$($PYMAIL -subj "$subject" -no-send $PRIVATE_IP $bob bob 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
fi
|
||||
|
||||
assert_check_logs
|
||||
|
||||
delete_user "$alice"
|
||||
delete_user "$bob"
|
||||
delete_user "$mary"
|
||||
delete_alias_group $alias
|
||||
test_end
|
||||
|
||||
}
|
||||
|
||||
|
||||
test_trial_nonlocal_alias_delivery() {
|
||||
# verify that mail sent to an alias with a non-local address
|
||||
# (rfc822MailMember) can be delivered
|
||||
test_start "trial-nonlocal-alias-delivery"
|
||||
|
||||
# add alias
|
||||
local alias="external@somedomain.com"
|
||||
create_alias_group $alias "test@google.com"
|
||||
|
||||
# trail send...doesn't actually get delivered
|
||||
start_log_capture
|
||||
sendmail_bv_send "$alias" 120
|
||||
assert_check_logs
|
||||
have_test_failures && record_captured_mail
|
||||
delete_alias_group $alias
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
test_catch_all() {
|
||||
# 1. ensure users in the catch-all alias receive messages to
|
||||
# invalid users for handled domains
|
||||
#
|
||||
# 2. ensure sending mail to valid user does not go to catch-all
|
||||
#
|
||||
test_start "catch-all"
|
||||
# create standard users alice, bob, and mary
|
||||
local alice="alice@somedomain.com"
|
||||
local bob="bob@anotherdomain.com"
|
||||
local mary="mary@anotherdomain.com"
|
||||
create_user "$alice" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
create_user "$bob" "bob"
|
||||
local bob_dn="$ATTR_DN"
|
||||
create_user "$mary" "mary"
|
||||
|
||||
# add catch-all alias to alice and bob
|
||||
local alias="@somedomain.com"
|
||||
create_alias_group $alias $alice_dn $bob_dn
|
||||
|
||||
# login as mary, then send to an invalid address. alice and bob
|
||||
# should receive that mail because they're aliases to the
|
||||
# catch-all for the domain
|
||||
record "[Sending mail to invalid user at catch-all domain]"
|
||||
start_log_capture
|
||||
local output
|
||||
local subject="Mail-In-A-Box test $(generate_uuid)"
|
||||
output="$($PYMAIL -subj "$subject" -no-delete -to INVALID${alias} na $PRIVATE_IP $mary mary 2>&1)"
|
||||
if assert_python_success $? "$output"; then
|
||||
# check that alice and bob received it by deleting the mail in
|
||||
# both mailboxes
|
||||
record "[Delete mail in alice's and bob's mailboxes]"
|
||||
output="$($PYMAIL -subj "$subject" -no-send $PRIVATE_IP $alice alice 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
output="$($PYMAIL -subj "$subject" -no-send $PRIVATE_IP $bob bob 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
fi
|
||||
assert_check_logs
|
||||
|
||||
# login as mary and send to a valid address at the catch-all
|
||||
# domain. that user should receive it and the catch-all should not
|
||||
record "[Sending mail to valid user at catch-all domain]"
|
||||
start_log_capture
|
||||
subject="Mail-In-A-Box test $(generate_uuid)"
|
||||
output="$($PYMAIL -subj "$subject" -to $alice alice $PRIVATE_IP $mary mary 2>&1)"
|
||||
if assert_python_success $? "$output"; then
|
||||
# alice got the mail and it was deleted
|
||||
# make sure bob didn't also receive the message
|
||||
record "[Delete mail in bob's mailbox]"
|
||||
output="$($PYMAIL -timeout 10 -subj "$subject" -no-send $PRIVATE_IP $bob bob 2>&1)"
|
||||
assert_python_failure $? "$output" "TimeoutError"
|
||||
fi
|
||||
assert_check_logs
|
||||
|
||||
delete_user "$alice"
|
||||
delete_user "$bob"
|
||||
delete_user "$mary"
|
||||
delete_alias_group $alias
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_nested_alias_groups() {
|
||||
# sending to an alias with embedded aliases should reach final
|
||||
# recipients
|
||||
test_start "nested-alias-groups"
|
||||
# create standard users alice and bob
|
||||
local alice="alice@zdomain.z"
|
||||
create_user "$alice" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
local bob="bob@zdomain.z"
|
||||
create_user "$bob" "bob"
|
||||
local bob_dn="$ATTR_DN"
|
||||
|
||||
# add nested alias groups [ alias1 -> alias2 -> alice ]
|
||||
local alias1="z1@xyzdomain.z"
|
||||
local alias2="z2@xyzdomain.z"
|
||||
create_alias_group $alias2 $alice_dn
|
||||
create_alias_group $alias1 $ATTR_DN
|
||||
|
||||
# send to alias1 from bob, then ensure alice received it
|
||||
record "[Sending mail to alias $alias1]"
|
||||
start_log_capture
|
||||
local output
|
||||
local subject="Mail-In-A-Box test $(generate_uuid)"
|
||||
output="$($PYMAIL -subj "$subject" -no-delete -to $alias1 na $PRIVATE_IP $bob bob 2>&1)"
|
||||
if assert_python_success $? "$output"; then
|
||||
record "[Test delivery - delete mail in alice's mailbox]"
|
||||
output="$($PYMAIL -subj "$subject" -no-send $PRIVATE_IP $alice alice 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
fi
|
||||
|
||||
assert_check_logs
|
||||
|
||||
delete_user "$alice"
|
||||
delete_user "$bob"
|
||||
delete_alias_group "$alias1"
|
||||
delete_alias_group "$alias2"
|
||||
|
||||
test_end
|
||||
}
|
||||
|
||||
test_user_rename() {
|
||||
# test the use case where someone's name changed
|
||||
# in this test we rename the user's 'mail' address, but
|
||||
# leave maildrop as-is
|
||||
test_start "user-rename"
|
||||
|
||||
# create standard user alice
|
||||
local alice1="alice.smith@somedomain.com"
|
||||
local alice2="alice.jones@somedomain.com"
|
||||
create_user "$alice1" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
local output
|
||||
|
||||
# send email to alice with subject1
|
||||
record "[Testing mail to alice1]"
|
||||
local subject1="Mail-In-A-Box test $(generate_uuid)"
|
||||
local success1=false
|
||||
start_mail_capture "$alice1"
|
||||
record "[Sending mail to $alice1]"
|
||||
output="$($PYMAIL -subj "$subject1" -no-delete $PRIVATE_IP $alice1 alice 2>&1)"
|
||||
assert_python_success $? "$output" && success1=true
|
||||
|
||||
# alice1 got married, add a new mail address alice2
|
||||
wait_mail # rename too soon, and the first message is bounced
|
||||
record "[Changing alice's mail address]"
|
||||
ldapmodify -H $LDAP_URL -x -D "$LDAP_ADMIN_DN" -w "$LDAP_ADMIN_PASSWORD" >>$TEST_OF 2>&1 <<EOF
|
||||
dn: $alice_dn
|
||||
replace: mail
|
||||
mail: $alice2
|
||||
EOF
|
||||
[ $? -ne 0 ] && die "Unable to modify ${alice1}'s mail address!"
|
||||
|
||||
# send email to alice with subject2
|
||||
start_log_capture
|
||||
local subject2="Mail-In-A-Box test $(generate_uuid)"
|
||||
local success2=false
|
||||
record "[Sending mail to $alice2]"
|
||||
output="$($PYMAIL -subj "$subject2" -no-delete $PRIVATE_IP $alice2 alice 2>&1)"
|
||||
assert_python_success $? "$output" && success2=true
|
||||
assert_check_logs
|
||||
|
||||
# delete both messages
|
||||
if $success1; then
|
||||
record "[Deleting mail 1]"
|
||||
output="$($PYMAIL -subj "$subject1" -no-send $PRIVATE_IP $alice2 alice 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
fi
|
||||
|
||||
if $success2; then
|
||||
record "[Deleting mail 2]"
|
||||
output="$($PYMAIL -subj "$subject2" -no-send $PRIVATE_IP $alice2 alice 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
fi
|
||||
|
||||
delete_user "$alice2"
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
|
||||
suite_start "mail-aliases"
|
||||
|
||||
test_shared_user_alias_login
|
||||
test_alias_group_member_login
|
||||
test_shared_alias_delivery # local alias delivery
|
||||
test_trial_nonlocal_alias_delivery
|
||||
test_catch_all
|
||||
test_nested_alias_groups
|
||||
test_user_rename
|
||||
|
||||
suite_end
|
||||
73
tests/suites/mail-basic.sh
Normal file
73
tests/suites/mail-basic.sh
Normal file
@@ -0,0 +1,73 @@
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
#
|
||||
# Test basic mail functionality
|
||||
|
||||
|
||||
|
||||
test_trial_send_local() {
|
||||
# use sendmail -bv to test mail delivery without actually mailing
|
||||
# anything
|
||||
test_start "trial_send_local"
|
||||
|
||||
# create a standard users alice and bobo
|
||||
local alice="alice@somedomain.com" bob="bob@somedomain.com"
|
||||
create_user "$alice" "alice"
|
||||
create_user "$bob" "bob"
|
||||
|
||||
# test delivery, but don't actually mail it
|
||||
start_log_capture
|
||||
sendmail_bv_send "$alice" 30 "$bob"
|
||||
assert_check_logs
|
||||
have_test_failures && record_captured_mail
|
||||
|
||||
# clean up / end
|
||||
delete_user "$alice"
|
||||
delete_user "$bob"
|
||||
test_end
|
||||
}
|
||||
|
||||
test_trial_send_remote() {
|
||||
# use sendmail -bv to test mail delivery without actually mailing
|
||||
# anything
|
||||
test_start "trial_send_remote"
|
||||
start_log_capture
|
||||
sendmail_bv_send "test@google.com" 120
|
||||
assert_check_logs
|
||||
have_test_failures && record_captured_mail
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_self_send_receive() {
|
||||
# test sending mail to yourself
|
||||
test_start "self-send-receive"
|
||||
# create standard user alice
|
||||
local alice="alice@somedomain.com"
|
||||
create_user "$alice" "alice"
|
||||
|
||||
# test actual delivery
|
||||
start_log_capture
|
||||
record "[Sending mail to alice as alice]"
|
||||
local output
|
||||
output="$($PYMAIL $PRIVATE_IP $alice alice 2>&1)"
|
||||
local code=$?
|
||||
record "$output"
|
||||
if [ $code -ne 0 ]; then
|
||||
test_failure "$PYMAIL exit code $code: $output"
|
||||
fi
|
||||
assert_check_logs
|
||||
|
||||
delete_user "$alice"
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
|
||||
suite_start "mail-basic"
|
||||
|
||||
test_trial_send_local
|
||||
test_trial_send_remote
|
||||
test_self_send_receive
|
||||
|
||||
suite_end
|
||||
|
||||
141
tests/suites/mail-from.sh
Normal file
141
tests/suites/mail-from.sh
Normal file
@@ -0,0 +1,141 @@
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
|
||||
|
||||
test_permitted_sender_fail() {
|
||||
# a user may not send MAIL FROM someone else, when not permitted
|
||||
test_start "permitted-sender-fail"
|
||||
# create standard users alice, bob, and mary
|
||||
local alice="alice@somedomain.com"
|
||||
local bob="bob@anotherdomain.com"
|
||||
local mary="mary@anotherdomain.com"
|
||||
create_user "$alice" "alice"
|
||||
create_user "$bob" "bob"
|
||||
create_user "$mary" "mary"
|
||||
|
||||
# login as mary, send from bob, to alice
|
||||
start_log_capture
|
||||
record "[Mailing to alice from bob as mary]"
|
||||
local output
|
||||
output="$($PYMAIL -f $bob -to $alice alice $PRIVATE_IP $mary mary 2>&1)"
|
||||
if ! assert_python_failure $? "$output" SMTPRecipientsRefused
|
||||
then
|
||||
# additional "color"
|
||||
test_failure "user should not be permitted to send as another user"
|
||||
fi
|
||||
|
||||
# expect errors, so don't assert
|
||||
check_logs
|
||||
|
||||
delete_user "$alice"
|
||||
delete_user "$bob"
|
||||
delete_user "$mary"
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_permitted_sender_alias() {
|
||||
# a user may send MAIL FROM one of their own aliases
|
||||
test_start "permitted-sender-alias"
|
||||
# create standard users alice and bob
|
||||
local alice="alice@somedomain.com"
|
||||
local bob="bob@anotherdomain.com"
|
||||
local mary="mary@anotherdomain.com"
|
||||
local jane="jane@google.com"
|
||||
create_user "$alice" "alice"
|
||||
create_user "$bob" "bob"
|
||||
local bob_dn="$ATTR_DN"
|
||||
|
||||
# add mary as one of bob's aliases - to bob's 'mail' attribute
|
||||
add_alias $bob_dn $mary user
|
||||
|
||||
# add jane as one of bob's aliases - to jane's alias group
|
||||
create_alias_group $jane $bob_dn
|
||||
|
||||
# login as bob, send from mary, to alice
|
||||
start_log_capture
|
||||
record "[Mailing to alice from mary as bob]"
|
||||
local output
|
||||
output="$($PYMAIL -f $mary -to $alice alice $PRIVATE_IP $bob bob 2>&1)"
|
||||
if ! assert_python_success $? "$output"; then
|
||||
# additional "color"
|
||||
test_failure "bob should be permitted to MAIL FROM $mary, his own alias: $(python_error "$output")"
|
||||
fi
|
||||
|
||||
assert_check_logs
|
||||
|
||||
# login as bob, send from jane, to alice
|
||||
start_log_capture
|
||||
record "[Mailing to alice from jane as bob]"
|
||||
local output
|
||||
output="$($PYMAIL -f $jane -to $alice alice $PRIVATE_IP $bob bob 2>&1)"
|
||||
if ! assert_python_success $? "$output"; then
|
||||
# additional "color"
|
||||
test_failure "bob should be permitted to MAIL FROM $jane, his own alias: $(python_error "$output")"
|
||||
fi
|
||||
|
||||
assert_check_logs
|
||||
|
||||
delete_user "$alice"
|
||||
delete_user "$bob"
|
||||
delete_alias_group "$jane"
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_permitted_sender_explicit() {
|
||||
# a user may send MAIL FROM an address that is explicitly allowed
|
||||
# by a permitted-senders group
|
||||
# a user may not send MAIL FROM an address that has a permitted
|
||||
# senders list which they are not a member, even if they are an
|
||||
# alias group member
|
||||
test_start "permitted-sender-explicit"
|
||||
|
||||
# create standard users alice and bob
|
||||
local alice="alice@somedomain.com"
|
||||
local bob="bob@anotherdomain.com"
|
||||
create_user "$alice" "alice"
|
||||
local alice_dn="$ATTR_DN"
|
||||
create_user "$bob" "bob"
|
||||
local bob_dn="$ATTR_DN"
|
||||
|
||||
# create an alias that forwards to bob and alice
|
||||
local alias="mary@anotherdomain.com"
|
||||
create_alias_group $alias $bob_dn $alice_dn
|
||||
|
||||
# create a permitted-senders group with only alice in it
|
||||
create_permitted_senders_group $alias $alice_dn
|
||||
|
||||
# login as alice, send from alias to bob
|
||||
start_log_capture
|
||||
record "[Mailing to bob from alice as alias/mary]"
|
||||
local output
|
||||
output="$($PYMAIL -f $alias -to $bob bob $PRIVATE_IP $alice alice 2>&1)"
|
||||
if ! assert_python_success $? "$output"; then
|
||||
test_failure "user should be allowed to MAIL FROM a user for which they are a permitted sender: $(python_error "$output")"
|
||||
fi
|
||||
assert_check_logs
|
||||
|
||||
# login as bob, send from alias to alice
|
||||
# expect failure because bob is not a permitted-sender
|
||||
start_log_capture
|
||||
record "[Mailing to alice from bob as alias/mary]"
|
||||
output="$($PYMAIL -f $alias -to $alice alice $PRIVATE_IP $bob bob 2>&1)"
|
||||
assert_python_failure $? "$output" "SMTPRecipientsRefused" "not owned by user"
|
||||
check_logs
|
||||
|
||||
delete_user $alice
|
||||
delete_user $bob
|
||||
delete_permitted_senders_group $alias
|
||||
create_alias_group $alias
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
|
||||
suite_start "mail-from"
|
||||
|
||||
test_permitted_sender_fail
|
||||
test_permitted_sender_alias
|
||||
test_permitted_sender_explicit
|
||||
|
||||
suite_end
|
||||
210
tests/suites/management-users.sh
Normal file
210
tests/suites/management-users.sh
Normal file
@@ -0,0 +1,210 @@
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
#
|
||||
# User management tests
|
||||
|
||||
_test_mixed_case() {
|
||||
# helper function sends multiple email messages to test mixed case
|
||||
# input scenarios
|
||||
local alices=($1) # list of mixed-case email addresses for alice
|
||||
local bobs=($2) # list of mixed-case email addresses for bob
|
||||
local aliases=($3) # list of mixed-case email addresses for an alias
|
||||
|
||||
start_log_capture
|
||||
|
||||
local alice_pw="$(generate_password 16)"
|
||||
local bob_pw="$(generate_password 16)"
|
||||
# create local user alice and alias group
|
||||
if mgmt_assert_create_user "${alices[0]}" "$alice_pw"; then
|
||||
# test that alice cannot also exist at the same time
|
||||
if mgmt_create_user "${alices[1]}" "$alice_pw" no; then
|
||||
test_failure "Creation of a user with the same email address, but different case, succeeded."
|
||||
test_failure "${REST_ERROR}"
|
||||
fi
|
||||
|
||||
# create an alias group with alice in it
|
||||
mgmt_assert_create_alias_group "${aliases[0]}" "${alices[1]}"
|
||||
fi
|
||||
|
||||
# create local user bob
|
||||
mgmt_assert_create_user "${bobs[0]}" "$bob_pw"
|
||||
|
||||
assert_check_logs
|
||||
|
||||
|
||||
# send mail from bob to alice
|
||||
#
|
||||
if ! have_test_failures; then
|
||||
record "[Mailing to alice from bob]"
|
||||
start_log_capture
|
||||
local output
|
||||
output="$($PYMAIL -to ${alices[2]} "$alice_pw" $PRIVATE_IP ${bobs[1]} "$bob_pw" 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
assert_check_logs
|
||||
|
||||
# send mail from bob to the alias, ensure alice got it
|
||||
#
|
||||
record "[Mailing to alias from bob]"
|
||||
start_log_capture
|
||||
local subject="Mail-In-A-Box test $(generate_uuid)"
|
||||
output="$($PYMAIL -subj "$subject" -no-delete -to ${aliases[1]} na $PRIVATE_IP ${bobs[2]} "$bob_pw" 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
output="$($PYMAIL -subj "$subject" -no-send $PRIVATE_IP ${alices[3]} "$alice_pw" 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
assert_check_logs
|
||||
|
||||
# send mail from alice as the alias to bob, ensure bob got it
|
||||
#
|
||||
record "[Mailing to bob as alias from alice]"
|
||||
start_log_capture
|
||||
local subject="Mail-In-A-Box test $(generate_uuid)"
|
||||
output="$($PYMAIL -subj "$subject" -no-delete -f ${aliases[2]} -to ${bobs[2]} "$bob_pw" $PRIVATE_IP ${alices[4]} "$alice_pw" 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
output="$($PYMAIL -subj "$subject" -no-send $PRIVATE_IP ${bobs[3]} "$bob_pw" 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
assert_check_logs
|
||||
fi
|
||||
|
||||
mgmt_assert_delete_user "${alices[1]}"
|
||||
mgmt_assert_delete_user "${bobs[1]}"
|
||||
mgmt_assert_delete_alias_group "${aliases[1]}"
|
||||
}
|
||||
|
||||
|
||||
test_mixed_case_users() {
|
||||
# create mixed-case user name
|
||||
# add user to alias using different cases
|
||||
# send mail from another user to that user - validates smtp, imap, delivery
|
||||
# send mail from another user to the alias
|
||||
# send mail from that user as the alias to the other user
|
||||
|
||||
test_start "mixed-case-users"
|
||||
|
||||
local alices=(alice@mgmt.somedomain.com
|
||||
aLICE@mgmt.somedomain.com
|
||||
aLiCe@mgmt.somedomain.com
|
||||
ALICE@mgmt.somedomain.com
|
||||
alIce@mgmt.somedomain.com)
|
||||
local bobs=(bob@mgmt.somedomain.com
|
||||
Bob@mgmt.somedomain.com
|
||||
boB@mgmt.somedomain.com
|
||||
BOB@mgmt.somedomain.com)
|
||||
local aliases=(aLICE@mgmt.anotherdomain.com
|
||||
aLiCe@mgmt.anotherdomain.com
|
||||
ALICE@mgmt.anotherdomain.com)
|
||||
|
||||
_test_mixed_case "${alices[*]}" "${bobs[*]}" "${aliases[*]}"
|
||||
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_mixed_case_domains() {
|
||||
# create mixed-case domain names
|
||||
# add user to alias using different cases
|
||||
# send mail from another user to that user - validates smtp, imap, delivery
|
||||
# send mail from another user to the alias
|
||||
# send mail from that user as the alias to the other user
|
||||
|
||||
test_start "mixed-case-domains"
|
||||
|
||||
local alices=(alice@mgmt.somedomain.com
|
||||
alice@MGMT.somedomain.com
|
||||
alice@mgmt.SOMEDOMAIN.com
|
||||
alice@mgmt.somedomain.COM
|
||||
alice@Mgmt.SomeDomain.Com)
|
||||
local bobs=(bob@mgmt.somedomain.com
|
||||
bob@MGMT.somedomain.com
|
||||
bob@mgmt.SOMEDOMAIN.com
|
||||
bob@Mgmt.SomeDomain.com)
|
||||
local aliases=(alice@MGMT.anotherdomain.com
|
||||
alice@mgmt.ANOTHERDOMAIN.com
|
||||
alice@Mgmt.AnotherDomain.Com)
|
||||
|
||||
_test_mixed_case "${alices[*]}" "${bobs[*]}" "${aliases[*]}"
|
||||
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
test_intl_domains() {
|
||||
test_start "intl-domains"
|
||||
|
||||
# local intl alias
|
||||
local alias="alice@bücher.example"
|
||||
local alias_idna="alice@xn--bcher-kva.example"
|
||||
|
||||
# remote intl user / forward-to
|
||||
local intl_person="hans@bücher.example"
|
||||
local intl_person_idna="hans@xn--bcher-kva.example"
|
||||
|
||||
# local users
|
||||
local bob="bob@somedomain.com"
|
||||
local bob_pw="$(generate_password 16)"
|
||||
local mary="mary@somedomain.com"
|
||||
local mary_pw="$(generate_password 16)"
|
||||
|
||||
start_log_capture
|
||||
|
||||
# international domains are not permitted for user accounts
|
||||
if mgmt_create_user "$intl_person" "$bob_pw"; then
|
||||
test_failure "A user account is not permitted to have an international domain"
|
||||
# ensure user is removed as is expected by the remaining tests
|
||||
mgmt_delele_user "$intl_person"
|
||||
delete_user "$intl_person"
|
||||
delete_user "$intl_person_idna"
|
||||
fi
|
||||
|
||||
# create local users bob and mary
|
||||
mgmt_assert_create_user "$bob" "$bob_pw"
|
||||
mgmt_assert_create_user "$mary" "$mary_pw"
|
||||
|
||||
# create intl alias with local user bob and intl_person in it
|
||||
if mgmt_assert_create_alias_group "$alias" "$bob" "$intl_person"; then
|
||||
# examine LDAP server to verify IDNA-encodings
|
||||
get_attribute "$LDAP_ALIASES_BASE" "(mail=$alias_idna)" "rfc822MailMember"
|
||||
if [ -z "$ATTR_DN" ]; then
|
||||
test_failure "IDNA-encoded alias group not found! created as:$alias expected:$alias_idna"
|
||||
elif [ "$ATTR_VALUE" != "$intl_person_idna" ]; then
|
||||
test_failure "Alias group with user having an international domain was not ecoded properly. added as:$intl_person expected:$intl_person_idna"
|
||||
fi
|
||||
fi
|
||||
|
||||
# re-create intl alias with local user bob only
|
||||
mgmt_assert_create_alias_group "$alias" "$bob"
|
||||
|
||||
assert_check_logs
|
||||
|
||||
if ! have_test_failures; then
|
||||
# send mail to alias from mary, ensure bob got it
|
||||
record "[Sending to intl alias from mary]"
|
||||
# note PYMAIL does not do idna conversion - it'll throw
|
||||
# "UnicodeEncodeError: 'ascii' codec can't encode character
|
||||
# '\xfc' in position 38".
|
||||
#
|
||||
# we'll have to send to the idna address directly
|
||||
start_log_capture
|
||||
local subject="Mail-In-A-Box test $(generate_uuid)"
|
||||
local output
|
||||
output="$($PYMAIL -subj "$subject" -no-delete -to "$alias_idna" na $PRIVATE_IP $mary "$mary_pw" 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
output="$($PYMAIL -subj "$subject" -no-send $PRIVATE_IP $bob "$bob_pw" 2>&1)"
|
||||
assert_python_success $? "$output"
|
||||
assert_check_logs
|
||||
fi
|
||||
|
||||
mgmt_assert_delete_alias_group "$alias"
|
||||
mgmt_assert_delete_user "$bob"
|
||||
mgmt_assert_delete_user "$mary"
|
||||
|
||||
test_end
|
||||
}
|
||||
|
||||
|
||||
suite_start "management-users" mgmt_start
|
||||
|
||||
test_mixed_case_users
|
||||
test_mixed_case_domains
|
||||
test_intl_domains
|
||||
|
||||
suite_end mgmt_end
|
||||
|
||||
@@ -1,109 +1,204 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- indent-tabs-mode: t; tab-width: 4; -*-
|
||||
#
|
||||
# Tests sending and receiving mail by sending a test message to yourself.
|
||||
|
||||
import sys, imaplib, smtplib, uuid, time
|
||||
import socket, dns.reversename, dns.resolver
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: tests/mail.py hostname emailaddress password")
|
||||
|
||||
def usage():
|
||||
print("Usage: test_mail.py [options] hostname login password")
|
||||
print("Send, then delete message")
|
||||
print(" options")
|
||||
print(" -f <email>: use <email> as the MAIL FROM address")
|
||||
print(" -to <email> <pass>: recipient of email and password")
|
||||
print(" -subj <subject>: subject of the message (required with --no-send)")
|
||||
print(" -no-send: don't send, just delete")
|
||||
print(" -no-delete: don't delete, just send")
|
||||
print(" -timeout <seconds>: how long to wait for message")
|
||||
print("");
|
||||
sys.exit(1)
|
||||
|
||||
host, emailaddress, pw = sys.argv[1:4]
|
||||
def if_unset(a,b):
|
||||
return b if a is None else a
|
||||
|
||||
# Attempt to login with IMAP. Our setup uses email addresses
|
||||
# as IMAP/SMTP usernames.
|
||||
try:
|
||||
M = imaplib.IMAP4_SSL(host)
|
||||
M.login(emailaddress, pw)
|
||||
except OSError as e:
|
||||
print("Connection error:", e)
|
||||
sys.exit(1)
|
||||
except imaplib.IMAP4.error as e:
|
||||
# any sort of login error
|
||||
e = ", ".join(a.decode("utf8") for a in e.args)
|
||||
print("IMAP error:", e)
|
||||
sys.exit(1)
|
||||
# option defaults
|
||||
host=None # smtp server address
|
||||
login=None # smtp server login
|
||||
pw=None # smtp server password
|
||||
emailfrom=None # MAIL FROM address
|
||||
emailto=None # RCPT TO address
|
||||
emailto_pw=None # recipient password for imap login
|
||||
send_msg=True # deliver message
|
||||
delete_msg=True # login to imap and delete message
|
||||
wait_timeout=30 # abandon timeout wiating for message delivery
|
||||
wait_cycle_sleep=5 # delay between delivery checks
|
||||
subject="Mail-in-a-Box Automated Test Message " + uuid.uuid4().hex # message subject
|
||||
|
||||
M.select()
|
||||
print("IMAP login is OK.")
|
||||
# process command line
|
||||
argi=1
|
||||
while argi<len(sys.argv):
|
||||
arg=sys.argv[argi]
|
||||
arg_remaining = len(sys.argv) - argi - 1
|
||||
if not arg.startswith('-'):
|
||||
break
|
||||
if (arg=="-f" or arg=="-from") and arg_remaining>0:
|
||||
emailfrom=sys.argv[argi+1]
|
||||
argi+=2
|
||||
elif arg=="-to" and arg_remaining>1:
|
||||
emailto=sys.argv[argi+1]
|
||||
emailto_pw=sys.argv[argi+2]
|
||||
argi+=3
|
||||
elif arg=="-subj" and arg_remaining>1:
|
||||
subject=sys.argv[argi+1]
|
||||
argi+=2
|
||||
elif arg=="-no-send":
|
||||
send_msg=False
|
||||
argi+=1
|
||||
elif arg=="-no-delete":
|
||||
delete_msg=False
|
||||
argi+=1
|
||||
elif arg=="-timeout" and arg_remaining>1:
|
||||
wait_timeout=int(sys.argv[argi+1])
|
||||
argi+=2
|
||||
else:
|
||||
usage()
|
||||
|
||||
|
||||
if len(sys.argv) - argi != 3: usage()
|
||||
host, login, pw = sys.argv[argi:argi+3]
|
||||
argi+=3
|
||||
|
||||
# Attempt to send a mail to ourself.
|
||||
mailsubject = "Mail-in-a-Box Automated Test Message " + uuid.uuid4().hex
|
||||
emailto = emailaddress
|
||||
msg = """From: {emailaddress}
|
||||
emailfrom = if_unset(emailfrom, login)
|
||||
emailto = if_unset(emailto, login)
|
||||
emailto_pw = if_unset(emailto_pw, pw)
|
||||
|
||||
msg = """From: {emailfrom}
|
||||
To: {emailto}
|
||||
Subject: {subject}
|
||||
|
||||
This is a test message. It should be automatically deleted by the test script.""".format(
|
||||
emailaddress=emailaddress,
|
||||
emailfrom=emailfrom,
|
||||
emailto=emailto,
|
||||
subject=mailsubject,
|
||||
subject=subject,
|
||||
)
|
||||
|
||||
# Connect to the server on the SMTP submission TLS port.
|
||||
server = smtplib.SMTP(host, 587)
|
||||
#server.set_debuglevel(1)
|
||||
server.starttls()
|
||||
|
||||
# Verify that the EHLO name matches the server's reverse DNS.
|
||||
ipaddr = socket.gethostbyname(host) # IPv4 only!
|
||||
reverse_ip = dns.reversename.from_address(ipaddr) # e.g. "1.0.0.127.in-addr.arpa."
|
||||
try:
|
||||
reverse_dns = dns.resolver.query(reverse_ip, 'PTR')[0].target.to_text(omit_final_dot=True) # => hostname
|
||||
except dns.resolver.NXDOMAIN:
|
||||
print("Reverse DNS lookup failed for %s. SMTP EHLO name check skipped." % ipaddr)
|
||||
reverse_dns = None
|
||||
if reverse_dns is not None:
|
||||
server.ehlo_or_helo_if_needed() # must send EHLO before getting the server's EHLO name
|
||||
helo_name = server.ehlo_resp.decode("utf8").split("\n")[0] # first line is the EHLO name
|
||||
if helo_name != reverse_dns:
|
||||
print("The server's EHLO name does not match its reverse hostname. Check DNS settings.")
|
||||
else:
|
||||
print("SMTP EHLO name (%s) is OK." % helo_name)
|
||||
|
||||
# Login and send a test email.
|
||||
server.login(emailaddress, pw)
|
||||
server.sendmail(emailaddress, [emailto], msg)
|
||||
server.quit()
|
||||
print("SMTP submission is OK.")
|
||||
def imap_login(host, login, pw):
|
||||
# Attempt to login with IMAP. Our setup uses email addresses
|
||||
# as IMAP/SMTP usernames.
|
||||
try:
|
||||
M = imaplib.IMAP4_SSL(host)
|
||||
M.login(login, pw)
|
||||
except OSError as e:
|
||||
print("Connection error:", e)
|
||||
sys.exit(1)
|
||||
except imaplib.IMAP4.error as e:
|
||||
# any sort of login error
|
||||
e = ", ".join(a.decode("utf8") for a in e.args)
|
||||
print("IMAP error:", e)
|
||||
sys.exit(1)
|
||||
|
||||
while True:
|
||||
# Wait so the message can propagate to the inbox.
|
||||
time.sleep(10)
|
||||
M.select()
|
||||
print("IMAP login is OK.")
|
||||
return M
|
||||
|
||||
|
||||
def imap_search_for(M, subject):
|
||||
# Read the subject lines of all of the emails in the inbox
|
||||
# to find our test message, and then delete it.
|
||||
found = False
|
||||
# to find our test message, then return the number
|
||||
typ, data = M.search(None, 'ALL')
|
||||
for num in data[0].split():
|
||||
typ, data = M.fetch(num, '(BODY[HEADER.FIELDS (SUBJECT)])')
|
||||
imapsubjectline = data[0][1].strip().decode("utf8")
|
||||
if imapsubjectline == "Subject: " + mailsubject:
|
||||
# We found our test message.
|
||||
found = True
|
||||
if imapsubjectline == "Subject: " + subject:
|
||||
return num
|
||||
return None
|
||||
|
||||
# To test DKIM, download the whole mssage body. Unfortunately,
|
||||
# pydkim doesn't actually work.
|
||||
# You must 'sudo apt-get install python3-dkim python3-dnspython' first.
|
||||
#typ, msgdata = M.fetch(num, '(RFC822)')
|
||||
#msg = msgdata[0][1]
|
||||
#if dkim.verify(msg):
|
||||
# print("DKIM signature on the test message is OK (verified).")
|
||||
#else:
|
||||
# print("DKIM signature on the test message failed verification.")
|
||||
|
||||
def imap_test_dkim(M, num):
|
||||
# To test DKIM, download the whole mssage body. Unfortunately,
|
||||
# pydkim doesn't actually work.
|
||||
# You must 'sudo apt-get install python3-dkim python3-dnspython' first.
|
||||
#typ, msgdata = M.fetch(num, '(RFC822)')
|
||||
#msg = msgdata[0][1]
|
||||
#if dkim.verify(msg):
|
||||
# print("DKIM signature on the test message is OK (verified).")
|
||||
#else:
|
||||
# print("DKIM signature on the test message failed verification.")
|
||||
pass
|
||||
|
||||
|
||||
def smtp_login(host, login, pw):
|
||||
# Connect to the server on the SMTP submission TLS port.
|
||||
server = smtplib.SMTP(host, 587)
|
||||
#server.set_debuglevel(1)
|
||||
server.starttls()
|
||||
|
||||
# Verify that the EHLO name matches the server's reverse DNS.
|
||||
ipaddr = socket.gethostbyname(host) # IPv4 only!
|
||||
reverse_ip = dns.reversename.from_address(ipaddr) # e.g. "1.0.0.127.in-addr.arpa."
|
||||
try:
|
||||
reverse_dns = dns.resolver.query(reverse_ip, 'PTR')[0].target.to_text(omit_final_dot=True) # => hostname
|
||||
except dns.resolver.NXDOMAIN:
|
||||
print("Reverse DNS lookup failed for %s. SMTP EHLO name check skipped." % ipaddr)
|
||||
reverse_dns = None
|
||||
if reverse_dns is not None:
|
||||
server.ehlo_or_helo_if_needed() # must send EHLO before getting the server's EHLO name
|
||||
helo_name = server.ehlo_resp.decode("utf8").split("\n")[0] # first line is the EHLO name
|
||||
if helo_name != reverse_dns:
|
||||
print("The server's EHLO name does not match its reverse hostname. Check DNS settings.")
|
||||
else:
|
||||
print("SMTP EHLO name (%s) is OK." % helo_name)
|
||||
|
||||
# Login and send a test email.
|
||||
if login is not None and login != "":
|
||||
server.login(login, pw)
|
||||
return server
|
||||
|
||||
|
||||
|
||||
|
||||
if send_msg:
|
||||
# Attempt to send a mail.
|
||||
server = smtp_login(host, login, pw)
|
||||
server.sendmail(emailfrom, [emailto], msg)
|
||||
server.quit()
|
||||
print("SMTP submission is OK.")
|
||||
|
||||
|
||||
if delete_msg:
|
||||
# Wait for mail and delete it.
|
||||
M = imap_login(host, emailto, emailto_pw)
|
||||
|
||||
start_time = time.time()
|
||||
found = False
|
||||
if send_msg:
|
||||
# Wait so the message can propagate to the inbox.
|
||||
time.sleep(wait_cycle_sleep / 2)
|
||||
|
||||
while time.time() - start_time < wait_timeout:
|
||||
num = imap_search_for(M, subject)
|
||||
if num is not None:
|
||||
# Delete the test message.
|
||||
found = True
|
||||
imap_test_dkim(M, num)
|
||||
M.store(num, '+FLAGS', '\\Deleted')
|
||||
M.expunge()
|
||||
|
||||
print("Message %s deleted successfully." % num)
|
||||
break
|
||||
|
||||
print("Test message not present in the inbox yet...")
|
||||
time.sleep(wait_cycle_sleep)
|
||||
|
||||
M.close()
|
||||
M.logout()
|
||||
|
||||
if not found:
|
||||
raise TimeoutError("Timeout waiting for message")
|
||||
|
||||
if found:
|
||||
break
|
||||
if send_msg and delete_msg:
|
||||
print("Test message sent & received successfully.")
|
||||
|
||||
print("Test message not present in the inbox yet...")
|
||||
|
||||
M.close()
|
||||
M.logout()
|
||||
|
||||
print("Test message sent & received successfully.")
|
||||
|
||||
Reference in New Issue
Block a user