Testing email delivery against a destination/receiving mailserver, using telnet, netcat (ncat, nc) and bash scripting.
After installing a border/destination/incoming MTA, or for that matter any "public" facing server, testing connectivity and functionality/operation from the viewpoint of a remote user/client should be your number one priority.
Most TCP/IP servers can be tested with use of commonly available command-line tools such as "nc" and "telnet".
For the sake of this post I will demonstrate SMTP communication against border MTA(s), with the use of both telnet, nc (netcat) and a bash shell script.
Generally the purpose of a border/destination/incoming MTA is to perform checks against a set of rules during the SMTP conversation/connection, before handing over the email to a local delivery agent or relay to another MTA for further delivery.
Checks usually performed on a destination MTA:
- Connecting IP address reputation checks (DNSBL lookups and return codes).
- Scan for viruses or other malicious payloads.
- Decide if the message is UBE (Spam).
- Check if destination domain is one we accept email or relay for.
- Check if user in the domain or local user is valid for delivery.
One thing to keep in mind when configuring a destination MTA; once a destination MTA accepts an email with a 2.x.x SMTP code (Success) that MTA is responsible for further delivery of that message.
Email messages containing viruses, spam etc. are almost always sent with spoofed/fake return email addresses, so ending/closing the SMTP conversation with the connecting MTA and later trying to bounce an email with a NDN (Non-Delivery Notification), will in the majority of cases end up as email backscatter. Also the risk of a malicious sender using your MTA to bounce Spam/Virus emails to other systems should be considered.
Degradation of your ip/domain reputation on DNSBLs and entry in http://www.backscatterer.org would likely be the eventual outcome of such a configuration.
Make sure to always test your MTA(s) for proper rejection to invalid users and domains during the SMTP conversation, making sure you do not allow a configuration that accepts and then bounces emails.
Let's test a good configuration
Let us test against the MX server handing email for my domain "bjaerris.com"
I have set up a valid email address for this purpose: "valid.email@bjaerris.com".
Let's get an MX record for the domain:
$ dig +short MX bjaerris.com
10 gw4.node25.com.
Let's connect to "gw4.node25.com" and talk some SMTP:
$ echo $HOSTNAME <---- Command
gw3.node25.com
$ telnet gw4.node25.com 25 <---- Command
Trying 2a01:7e00::f03c:91ff:fe50:fb1a...
Connected to gw4.node25.com.
Escape character is '^]'.
220 gw4.node25.com ESMTP Postfix
HELO gw3.node25.com <---- Command
250 gw4.node25.com
MAIL FROM: <smtp.test@gw3.node25.com> <---- Command
250 2.1.0 Ok
RCPT TO: <invalid@bjaerris.com> <---- Command
550 5.1.1 <invalid@bjaerris.com>: Recipient address rejected: User unknown in virtual mailbox table
RCPT TO: <test@example.com> <---- Command
554 5.7.1 <test@example.com>: Relay access denied
RCPT TO: <valid.email@bjaerris.com> <---- Command
250 2.1.5 Ok
DATA <---- Command
354 End data with <CR><LF>.<CR><LF>
To: valid.email@bjaerris.com <---- Data
Subject: Test message <---- Data
Hello! <---- Data
. <---- "Data" (Will be interpreted as end of message by the SMTP server)
250 2.0.0 Ok: queued as F3B40498E
QUIT <---- Command
221 2.0.0 Bye
Connection closed by foreign host.
To summarize the above "good configuration", testing for invalid user, relaying, and valid user.
RCPT TO: <invalid@bjaerris.com
"550 5.1.1 invalid@bjaerris.com: Recipient address rejected: User unknown in virtual mailbox table"
RCPT TO: <test@example.com>
"554 5.7.1 test@example.com: Relay access denied"
"RCPT TO: <valid.email@bjaerris.com>"
"250 2.1.5 Ok"
.
"250 2.0.0 Ok: queued as F3B40498E"
Testing with netcat (ncat, nc)
We could use Netcat the same way as the previous telnet example, but for this demonstration we're going to prepare a file with the input and redirect to stdin of the netcat process.
First let's create a file with the commands/data we want to send to the SMTP server.
$ cat > SMTP_TALK <<EOF
> HELO gw3.node25.com
> MAIL FROM:<smtp.test@gw3.node25.com>
> RCPT TO:<invalid@bjaerris.com>
> RCPT TO:<test@example.com>
> RCPT TO:<valid.email@bjaerris.com>
> DATA
> From: [SMTP TEST] <smtp.test@gw3.node25.com>
> To: <valid.email@bjaerris.com>
> Subject: Test message.
> Hello!
> .
> QUIT
> EOF
Use netcat to connect to the MX server and redirect output from our newly created file to stdin of the netcat process.
# Note on some systems Netcat is called "ncat"
$ nc gw4.node25.com 25 < SMTP_TALK
220 gw4.node25.com ESMTP Postfix
250 gw4.node25.com
250 2.1.0 Ok
550 5.1.1 <invalid@bjaerris.com>: Recipient address rejected: User unknown in virtual mailbox table
554 5.7.1 <test@example.com>: Relay access denied
250 2.1.5 Ok
354 End data with <CR><LF>.<CR><LF>
250 2.0.0 Ok: queued as 0BEF949CC
221 2.0.0 Bye
Having the commands ready in a file makes things a bit easier if testing more than one server.
Let's make a bash script for more permanent SMTP/ESMTP testing purposes
#!/bin/sh
# smtp_test.sh
# Lars Bjaerris <lars at bjaerris.com>
# Version 0.2
# Argument checks
if [[ ( $# == "--help" || $# == "-h") || ! ($# -eq 1 && $1 =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$) ]]; then
echo -e "\nThis script takes an email address as argument."
echo -e "Usage:\n$0 user@example.com \n"
exit 1
fi
# Define variables
LOCAL_HOSTNAME=$(uname -n)
EMAIL_ADDR="$1"
REMOTE_LOCAL_ADDR="${EMAIL_ADDR%@*}"
REMOTE_DOMAIN="${EMAIL_ADDR#*@}"
REMOTE_MX=$(dig +short $REMOTE_DOMAIN MX |head -n1 |cut -d ' ' -f 2)
SMTP_PORT="25"
SENDER="smtp.test@$LOCAL_HOSTNAME"
MESSAGE="Hello!"
INVALID_RECIPIENT="invalid@$REMOTE_DOMAIN"
RELAY_TEST="test@example.com"
DATE=$(date '+%a, %d %b %Y %H:%M:%S %z')
# Check if an MX record was returned for the domain
echo
[ -z "$REMOTE_MX" ] && echo "Failed to get MX record from \"$REMOTE_DOMAIN\"" && exit 1 || echo "Got MX:\"$REMOTE_MX\" for DOMAIN:\"$REMOTE_DOMAIN\""
# Populate array with SMTP commands to run
SMTP_commands=( \
"HELO $LOCAL_HOSTNAME" \
"MAIL FROM: <$SENDER>" \
"RCPT TO: <$INVALID_RECIPIENT>" \
"RCPT TO: <$RELAY_TEST>" \
"RCPT TO: <$EMAIL_ADDR>" \
"DATA" \
"To: <$EMAIL_ADDR>\r\nFrom: [SMTP TEST] <$SENDER>\r\nSubject: Test Message.\r\nDate: $DATE\r\n$MESSAGE\r\n." \
"quit"
)
# Define sending function
email_smtp () {
echo "Trying connect to MX:\"$REMOTE_MX\""
echo
exec 3<>/dev/tcp/$REMOTE_MX/$SMTP_PORT
read -u 3 reply
echo "$reply"
# Set internal field separator for "SMTP_commands" array looping
IFS=""
# Loop over "SMTP_commands" array
for i in ${SMTP_commands[@]}
do
echo "----Sending Data:"
echo "$i"
echo -en "$i\r\n" >&3
read -u 3 reply
echo "----Server Reply:"
echo "$reply"
done
}
#Call senderfunction
email_smtp
Let's test it!
$ ./smtp_test.sh valid.email@bjaerris.com
Got MX:"gw4.node25.com." for DOMAIN:"bjaerris.com"
Trying connect to MX:"gw4.node25.com."
220 gw4.node25.com ESMTP Postfix
----Sending Data:
HELO gw3.node25.com
----Server Reply:
250 gw4.node25.com
----Sending Data:
MAIL FROM: <smtp.test@gw3.node25.com>
----Server Reply:
250 2.1.0 Ok
----Sending Data:
RCPT TO: <invalid@bjaerris.com>
----Server Reply:
550 5.1.1 <invalid@bjaerris.com>: Recipient address rejected: User unknown in virtual mailbox table
----Sending Data:
RCPT TO: <test@example.com>
----Server Reply:
554 5.7.1 <test@example.com>: Relay access denied
----Sending Data:
RCPT TO: <valid.email@bjaerris.com>
----Server Reply:
250 2.1.5 Ok
----Sending Data:
DATA
----Server Reply:
354 End data with <CR><LF>.<CR><LF>
----Sending Data:
To: <valid.email@bjaerris.com>\r\nFrom: [SMTP TEST] <smtp.test@gw3.node25.com>\r\nSubject: Test message.\r\nDate: Thu, 05 Mar 2015 14:05:10 +0000\r\nHello!\r\n.
----Server Reply:
250 2.0.0 Ok: queued as F0EBD498E
----Sending Data:
quit
----Server Reply:
221 2.0.0 Bye
Voila!
Hope you enjoyed it!
Lars Bjaerris