Testing Destination Mailserver With Telnet, Netcat And Bash Scripting SMTP/ESMTP

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
$ 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>
> From: [SMTP TEST] <smtp.test@gw3.node25.com>
> To: <valid.email@bjaerris.com>
> Subject: Test message.
> Hello!
> .

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

# 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
# Define variables
LOCAL_HOSTNAME=$(uname -n)
REMOTE_MX=$(dig +short $REMOTE_DOMAIN MX |head -n1 |cut -d ' ' -f 2)
DATE=$(date '+%a, %d %b %Y %H:%M:%S %z')

# Check if an MX record was returned for the domain
[ -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=( \
    "MAIL FROM: <$SENDER>" \
    "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." \
# Define sending function
email_smtp () {
    echo "Trying connect to MX:\"$REMOTE_MX\""
    exec 3<>/dev/tcp/$REMOTE_MX/$SMTP_PORT
    read -u 3 reply
    echo "$reply"
    # Set internal field separator for "SMTP_commands" array looping
    # Loop over "SMTP_commands" array
    for i in ${SMTP_commands[@]}
        echo "----Sending Data:"
        echo "$i"
        echo -en "$i\r\n" >&3
        read -u 3 reply
        echo "----Server Reply:"
        echo "$reply"
#Call senderfunction

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:
----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:
----Server Reply:
221 2.0.0 Bye


Hope you enjoyed it!
Lars Bjaerris