Using Telnet with an SMTP Server

Despite having been around for a long time, telnet is an invaluable tool for testing a mail server. It allows one to pinpoint errors in the process – some which are not logged – and to quickly validate that things are working. Moreover, it enables one to get a better understanding of what their mail server expects, which in turn allows for better code to be written for sending emails. Granted, excellent libraries (such as PHPMailer) exist for this sole purpose, but learning something new never hurts.

Installing Telnet

Since it seems that the general populace has little use for telnet, many modern operating systems do not have it installed by default.

In the case of Amazon’s Linux AMI, telnet can be installed by running:

yum install telnet

In the case of Windows 7, the telnet client is a Windows feature that can be added by going to: Control Panel > Programs and Features > Turn Windows features on or off, and checking ‘Telnet Client’ from the list of Windows Features that appears. Note that on Windows’ telnet, if you make a typographical error, backspace will not be able to correct it – the command sent to the server will be invalid.

Once we have telnet installed, it can be invoked from the command line. With regard to connecting to a server, don’t forget that some ports available internally may be firewalled to prevent access from the outside. In the case of EC2, this includes any firewalls (e.g. ipTables) running on the instance, as well as the security group settings for the instance.

Connecting to the Server

In the rest of this article the following information will be used:

  • Client server: localserver.com
  • SMTP server: remoteserver.com
  • SMTP port: 25
  • SMTP port (SSL): 465
  • SMTP username: user@remoteserver.com
  • SMTP password: password
  • Recipient: someone@somewhere.com

The typical command to initiate a telnet connection to a server is: telnet server port

We (the client) would therefore enter the following at the command prompt:

telnet remoteserver.com 25

If you wish to connect to a server using SSL, you can use openssl with the following command (note the default port is 465, not 25):

openssl s_client -crlf -connect remoteserver.com:465

To connect to an SMTP server using TLS, use the following:

openssl s_client -starttls smtp -crlf -connect remoteserver.com:25

Conversing with the Server

Once the connection is established, the server will respond with a status code and its name similar to the following:

220 mail.remoteserver.com ESMTP Postfix

The 2xx series status codes indicate success, 4xx series status codes indicate temporary failures, and 5xx series status codes indicate permanent failures.

In the case above, 220 indicates that the service is ready, the server returns its name (example.com) – this need not match the name of the server you connected to, ESMTP means that the server will accept an extended set of SMTP commands (most servers do), and Postfix is the name of the server software (other common examples being Sendmail, Exim, and Qmail). It is at the server’s discretion what is returned, some will not provide a similar greeting or will exclude some parts seen above.

Saying HELO

Keep in mind that this is a conversation with the server – it says something, and we are expected to reply. The polite reply to someone saying hello, is to respond in kind, so we will greet the server and identify ourselves.

There are two commands that can be used in this greeting, HELO (which implies the default set of SMTP commands), and EHLO (which implies the extended set of SMTP commands). In most cases, we want the latter. Greeting with EHLO will also provide us some information about the server that HELO does not provide.

Let us take a look at typical responses received in each case:

HELO localserver.com
250 mail.remoteserver.com

That is it – the mail server just responded that the request for mail action was okay and was completed. We can now start sending mail commands to the server. Most servers however, should require some form of authentication before they will send mail that doesn’t originate from their network (while some servers simply won’t send any mail not originating from their network).

If we use the other greeting, we get the following:

EHLO localserver.com
250-mail.remoteserver.com
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-AUTH PLAIN LOGIN
250-AUTH=PLAIN LOGIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN

Each line provides some information about what the server requires or will accept. Of particular interest is the AUTH line which indicates which forms of authentication are supported by the server. Most servers will differ in their response, including some lines and excluding others depending on how they are configured. Keep in mind that if telnet-ing on localhost, some servers are setup to not require authentication from machines on the same network, and will not display the AUTH line.

Authentication

This particular server requires authentication to send an email – that means a valid username and password, recognized by the mail server. Mail servers typically implement authentication through SASL ((Simple Authentication and Security Layer). On the client end of things, the accepted types of authentication are provided on the AUTH line (in this case, plain and login). Since usernames and passwords may contain usual characters, mail servers expect them to be base64 encoded.

Base 64 Encoding

From Linux, it is quite easy to base64 encode text using Perl using the following command:

perl -MMIME::Base64 -e 'print encode_base64("text to encode")'

You must remember, however, to ‘escape’ (precede with a backslash) special characters (e.g. @).

Alternatively, on a server running PHP (CLI) you can use PHP in interactive mode by running php -a and then typing in the command:

echo base64_encode("text to encode");

In PHP the special characters that might need to be escaped include the double quote (“), backslash (\) and dollar sign ($). To exit interactive mode, type quit.

In both languages above, a NULL byte can be represented with \000 (although \0 will usually work, if the following string starts with a number, the character code may be misinterpreted).

As an aside, a couple of quick points of mention – firstly, you will often note an equal sign (‘=’) at the end of a base64 string, it is a result of padding – you can expect to see 1 or 2 equal signs at the end of your string if the length of the input string is not evenly divisible by 3.

Also, if you would like to adapt the perl snippet above to decode a base64 string, you should get the following:

perl -MMIME::Base64 -e 'print decode_base64("text to decode")'

AUTH LOGIN

The last response from the server was a list of what the server accepts and expects from us, to proceed we need to authenticate ourselves with the server. Our options, listed above, are LOGIN and PLAIN.

In the case of LOGIN, we will be prompted for a username, followed by a prompt for a password. We must enter each one base64 encoded. The conversation with the server would proceed as follows:

AUTH LOGIN
334 VXNlcm5hbWU6

That line, is the base64 response from the server – if we translate it, it reads “Username:”. The 334 status code indicates a server challenge (i.e. it is waiting for us to enter authentication information).

Recall: to encode our username (user@remoteserver.com) we run:

perl -MMIME::Base64 -e 'print encode_base64("user\@remoteserver.com")'

We must now respond with the base64 version of our username:

dXNlckByZW1vdGVzZXJ2ZXIuY29t

If the username is invalid, we will get an error message similar to the following:

535 5.7.8 Error: authentication failed: VXNlcm5hbWU6

As the error suggests, the 535 code indicates that the authentication credentials are invalid. To resume from this we need to restart our authentication (i.e. the next command should be AUTH LOGIN). Keep in mind though, that many servers are not tolerant of invalid authentications, and even if you do successfully login, the server might not send your mail.

Hopefully though, the username is valid, and we will get a prompt for the password:

334 UGFzc3dvcmQ6

As with the username, the base64 string returned by the server, is a prompt, in this case it translates to “Password:”

We respond with the base64 encoded version of our password (password):

cGFzc3dvcmQ=

AUTH PLAIN

In the case of PLAIN, no prompt (per se) is given, we can either enter the authentication string together with the AUTH command, or the server will wait for our input on the following line. With PLAIN, we must provide a base64 encoded string in the following format:

NULL byte + username + NULL byte + password

To encode our username (user@remoteserver.com) and password (password) we would run the following (note the null characters (\000) and the escaped @ (\@):

perl -MMIME::Base64 -e 'print encode_base64("\000user\@remoteserver.com\000password")'

Which gives us:

AHVzZXJAcmVtb3Rlc2VydmVyLmNvbQBwYXNzd29yZA==

Method 1

As mentioned above, we have two options on how to present this information to the server. To present it with the AUTH command we would do the following:

AUTH PLAIN AHVzZXJAcmVtb3Rlc2VydmVyLmNvbQBwYXNzd29yZA==

Method 2
AUTH PLAIN

To which the server will respond:

334

As with AUTH LOGIN, the 334 code is server challenge, and is waiting for our response – no further prompt is provided in this case. Our response should simply be the base64 string:

AHVzZXJAcmVtb3Rlc2VydmVyLmNvbQBwYXNzd29yZA==

Sending an Email

If all goes well, regardless of whether we used AUTH LOGIN or AUTH PLAIN, the server should respond with the following:

235 2.7.0 Authentication successful

We can now start composing the email itself – for this, we need 3 parts, a from address (MAIL FROM:), a to address (RCPT TO:), and the email itself (headers + body) (DATA). The MAIL command must precede the RCPT command, which must precede the data command – the server will not accept the information in any other order.

We start therefore, with the MAIL command. This line will set the ‘Return-Path’ header of the email. While sometimes extra information (e.g. size) is permitted on this line, in its simplest form it should contain only the email address (not a common name):

MAIL FROM: user@remoteserver.com

If the server does not accept the command, it will respond with an error code and message. Otherwise, the response should resemble:

250 2.1.0 Ok

We next tell the server where to send this email, again, we should not include anything other than the email address:

RCPT TO: someone@somewhere.com

Again, we should get the all good from the server:

250 2.1.0 Ok

We will now tell the server that we would like to start sending the email itself:

DATA
354 End data with <CR><LF>.<CR><LF>

Note, that the server has provided instructions on how to end the email – a period on its own line. The 354 status code tells us to start the mail input.

We now move on to entering data. Keep in mind that the email that ends up being displayed for the recipient is usually based on the headers entered here, not any other headers generated previously. As such, it is typical to enter From, To, and Subject headers, although additional headers of your choosing (e.g. I have added ‘X-Custom-Header’ in the example below) may be included as well. In this manner it is possible to generate an multipart email or even one that has an attachment. Headers should come at the start of the email, one per line, with a colon (:) after the name. In the headers, common names can be included:

From: me <user@remoteserver.com>
To: someone@somewhere.com
Subject: Test
X-Custom-Header: another header
This is an email, sent using telnet.
.

Once we end our email, we should receive another success code from the server, as well as some additional information (sometimes used as receipt/for tracking the email):

250 2.0.0 Ok: queued as AA19C7C4CC

If sending multiple emails, it is not necessary to disconnect from the server and reconnect (and re-authenticate) between each email. We would simply continue from here with another ‘MAIL FROM:’ line and write our next email.

Checking the Mail Queue

Before the server sends the email, it will be briefly queued, for PostFix, the mail queue can be viewed by running the following command from the terminal/ssh (most likely as root):

mailq

The output will resemble the following:

-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
AA19C7C4CC*     298 Thu Jan 27 15:47:10  user@remoteserver.com
                                         someone@somewhere.com
-- 1 Kbytes in 1 Request.

The email should also be logged in your mail logs, and can be typically be found by running using grep with the ID:

grep AA19C7C4CC /var/log/maillog

The result of which should look somewhat like:

Jan 27 15:47:20 remoteserver postfix/smtpd[14730]: AA19C7C4CC: client=localserver.com[XX.XXX.XX.XX], sasl_method=plain, sasl_username=user@remoteserver.com
Jan 27 15:48:01 remoteserver postfix/cleanup[14736]: AA19C7C4CC: message-id=<>
Jan 27 15:48:01 remoteserver postfix/qmgr[9183]: AA19C7C4CC: from=, size=298, nrcpt=1 (queue active)
Jan 27 15:49:04 remoteserver postfix/smtp[14776]: AA19C7C4CC: to=, relay=somewhere.com[XX.XXX.XX.XX]:25, delay=114, delays=51/62/0.15/0.41, dsn=2.0.0, status=sent (250 2.0.0 OK 1286151434 p5si16317625sdp.59)
Jan 27 15:49:04 remoteserver postfix/qmgr[9183]: AA19C7C4CC: removed

The above log displays the login (and associated IP), the FROM and TO values entered, as well as the status of the email. Finally, when the email has been processed, it is removed from the queue.

Exiting

Finally, to disconnect from the server, enter:

QUIT

To which the server should respond:

221 2.0.0 Bye

Note that in most cases, commands are not case sensitive, and can be entered as either upper or lower case (or any combination thereof).

Conversation Transcripts

To briefly recap, the complete conversations with the server are included below (C=client, S=server):

Using AUTH LOGIN:

S: 220 mail.remoteserver.com ESMTP Postfix
C: EHLO localserver.com
S: 250-mail.remoteserver.com
S: 250-PIPELINING
S: 250-SIZE 10240000
S: 250-VRFY
S: 250-ETRN
S: 250-AUTH PLAIN LOGIN
S: 250-AUTH=PLAIN LOGIN
S: 250-ENHANCEDSTATUSCODES
S: 250-8BITMIME
S: 250 DSN
C: AUTH LOGIN
S: 334 VXNlcm5hbWU6
C: dXNlckByZW1vdGVzZXJ2ZXIuY29t
S: 334 UGFzc3dvcmQ6
C: cGFzc3dvcmQ=
S: 235 2.7.0 Authentication successful
C: MAIL FROM: user@remoteserver.com
S: 250 2.1.0 Ok
C: RCPT: someone@somewhere.com
S: 250 2.1.5 Ok
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: From: me <user@remoteserver.com>
C: To: someone@somewhere.com
C: Subject: Test
C: X-Custom-Header: another header
C: This is an email, sent using telnet.
C: .
S: 250 2.0.0 Ok: queued as AA19C7C4CC
C: QUIT
S: 221 2.0.0 Bye

Using AUTH PLAIN (identical except for the AUTH part):

S: 220 mail.remoteserver.com ESMTP Postfix
C: EHLO localserver.com
S: 250-mail.remoteserver.com
S: 250-PIPELINING
S: 250-SIZE 10240000
S: 250-VRFY
S: 250-ETRN
S: 250-AUTH PLAIN LOGIN
S: 250-AUTH=PLAIN LOGIN
S: 250-ENHANCEDSTATUSCODES
S: 250-8BITMIME
S: 250 DSN
C: AUTH PLAIN AHVzZXJAcmVtb3Rlc2VydmVyLmNvbQBwYXNzd29yZA==
S: 235 2.7.0 Authentication successful
C: MAIL FROM: user@remoteserver.com
S: 250 2.1.0 Ok
C: RCPT: someone@somewhere.com
S: 250 2.1.5 Ok
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: From: me <user@remoteserver.com>
C: To: someone@somewhere.com
C: Subject: Test
C: X-Custom-Header: another header
C: This is an email, sent using telnet.
C: .
S: 250 2.0.0 Ok: queued as AA19C7C4CC
C: QUIT
S: 221 2.0.0 Bye

References

By cyberx86

Just a random guy who dabbles with assorted technologies yet works in a completely unrelated field.

9 comments

  1. I’m using

    dhimes@dewey:~$ openssl version
    OpenSSL 0.9.8o 01 Jun 2010

    There is a problem that I’ve been chasing for a while- and I thought I would share. When typing
    RCPT TO:
    the capital ‘R’ is actually a terminal command for openssl that tells the client to ‘Renegotiate.’ The workaround is to use lower case letters (off-spec, but works)
    rcpt to:
    I’m also including the very popular search terms yahoo bizmail in this comment, because a lot of people have been struggling. Finally, a link, if it is permitted:
    http://serverfault.com/questions/336617/postfix-tls-over-smtp-rcpt-to-prompts-renegotiation-then-554-5-5-1-error-no-v
    Thank you.

    1. Thanks for the comment and the ServerFault link – looks like a bug in s_client, but will definitely be helpful to a few people.

Leave a comment

Your email address will not be published. Required fields are marked *