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.
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: email@example.com
- SMTP password: password
- Recipient: firstname.lastname@example.org
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.
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.
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")'
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
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 (email@example.com) we run:
perl -MMIME::Base64 -e 'print encode_base64("user\@remoteserver.com")'
We must now respond with the base64 version of our username:
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:
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):
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 (firstname.lastname@example.org) 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:
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==
To which the server will respond:
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:
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: email@example.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: firstname.lastname@example.org
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 <email@example.com> To: firstname.lastname@example.org 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):
The output will resemble the following:
-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient------- AA19C7C4CC* 298 Thu Jan 27 15:47:10 email@example.com firstname.lastname@example.org -- 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: AA19C7C4CC: client=localserver.com[XX.XXX.XX.XX], sasl_method=plain, email@example.com Jan 27 15:48:01 remoteserver postfix/cleanup: AA19C7C4CC: message-id=<> Jan 27 15:48:01 remoteserver postfix/qmgr: AA19C7C4CC: from=, size=298, nrcpt=1 (queue active) Jan 27 15:49:04 remoteserver postfix/smtp: 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: 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.
Finally, to disconnect from the server, enter:
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).
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: firstname.lastname@example.org S: 250 2.1.0 Ok C: RCPT: email@example.com S: 250 2.1.5 Ok C: DATA S: 354 End data with <CR><LF>.<CR><LF> C: From: me <firstname.lastname@example.org> C: To: email@example.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: firstname.lastname@example.org S: 250 2.1.0 Ok C: RCPT: email@example.com S: 250 2.1.5 Ok C: DATA S: 354 End data with <CR><LF>.<CR><LF> C: From: me <firstname.lastname@example.org> C: To: email@example.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
- RFC-3463: http://tools.ietf.org/html/rfc3463 (Enhanced Mail System Status Codes)
- RFC-4954: http://tools.ietf.org/html/rfc4954 (SMTP Service Extension for Authentication)
awesome, thanks, just what i needed… help on the base64.
Saved me so much time.
Glad you found it useful. Thanks for reading and commenting.
This has been a great learning experience, Very well done
Great to know it was helpful. Thanks for reading and commenting.
There is a problem that I’ve been chasing for a while- and I thought I would share. When typing
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)
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:
Thanks for the comment and the ServerFault link – looks like a bug in s_client, but will definitely be helpful to a few people.
Thank you, pretty much helpful to test and communicate with mail server by using CLI