Introduction:
For months I have been trying to create a Perl SMTP AUTH script
that would allow me to log into my mail server, that requires authentication,
from a perl script and send mail. I was under the impression that
the encryption was CRAM-MD5, however I found that it other encryptions
are acceptable and are much easier than this. I have tried this
script on a number of servers and it appears to work well. I must
thank Justin Clark for sparking my interest in this again.
Script:
The script that you can see to the right is just a simple sockets
version of SMTP communication. It does not send any mail so as not
to flood my server with mail. I have also diabled the account it
goes to for fear of some not so trustworthy people. The script takes
the server information, username and password then uses these to
log in to the server after it has encrypted the username and password.
SMTP AUTH was originally created try and prevent spam. Almost all
major mail clients support it, and quite a few servers implement
it. The issue arises when you try and create a perl script to communicate
with these servers. I was unable to find anything about this topic,
and Net::SMTP, the standard perl SMTP module, does not support SMTP_AUTH.
Notes
Please note the following font definitions:
Incoming line from server
Outgoing line to server
Special
Authentication
Types
When a client
first logs on to a server it receives a similar response to below:
250-pioneer.host-serve.net
250-AUTH=LOGIN CRAM-MD5
250-AUTH LOGIN CRAM-MD5
250-PIPELINING
250 8BITMIME
The lines
that we are interested in are the AUTH lines. Most servers do the
AUTH and AUTH= line I assume that there was some disagreement in
the RFC as to how this was supposed to be parsed so both lines are
included. Other servers also put the AUTH commands on seperate lines
so just be prepared to see anything. My server is telling me that
it will accept LOGIN and CRAM-MD5 authentication methods. There
are three main methods with the last being PLAIN which is what old
servers ask for.
Inform
the sever we wish to use AUTH
To let the server know that we want to use authentication we send
the command:
AUTH
***
Where the ***
is filled in with one of the servers available AUTH methods such
as LOGIN.
PLAIN
First let us look at the PLAIN log in type. After we send the AUTH
PLAIN command we receive the line:
334
Username:
We then send
the username in plain text and we receive the next line:
334
Password:
We then send
the password in plain text and if everything is correct we receive
the line:
235
Go Ahead
If we sent the
wrong user/pass combination we receive:
535
auth failure
As you can see
the PLAIN method offers no protection from packet sniffers seeing
your password.
LOGIN
This offers only slightly more protection from packet sniffing.
To start we send the server the following line:
AUTH
LOGIN
And we receive
the line:
334
VXNlcm5hbWU6
Which Username:
encoded in base64. We then send the server our username encoded
in base64 and we receive the line:
334
UGFzc3dvcmQ6
Whic is Password:
encoded in base64. We then send the server our password encoded
in base64 and we receive the following line:
235
Go Ahead
If we sent a
bad combination we receive the line:
535
auth failure
As you can see
LOGIN only provides a mask of your username and password. However
an intelligent person can still view the packets for this and unencode
your username and password, or even just reuse it since the encoding
never changes.
CRAM-MD5
This method provides real undecryptable authetication that changes
with every login. To start this we tell the server:
AUTH
CRAM-MD5
We then receive
a similar line to the following:
334
PDIzODkxLjEwMTU5ODM0OTNAcGlvbmVlci5ob3N0LXNlcnZlLm5ldApaAj4=
If we decode
base64 this line it should say:
<23891.1015983493@pioneer.host-serve.net>
(However my
server has a glitch in its CRAM-MD5 authentication and it sends
extra characters that mess up the authentication). We then respond
with the following line:
ZGVtbyBlMDRjMDkyMDA5NzY3MWE3NmM3YzAyMzhkMjU2ZTdjMg==
If we base64
decode this line it says:
demo
e04c0920097671a76c7c0238d256e7c2
We can see that
it says demo which is my username and some junk after it. But what
is this junk? The junk is the previous server ticket, that funny
line that looks like an email address of numbers. Hashed and CRAM-MD5
encoded with my password. The server ticket changes every millisecond
so my response never looks the same and the CRAM-MD5 cannot be undone.
So how does the server verify me? It does the same process on its
end and looks at the two responses, if they match you can enter.
Basically your password changes every millisecond and only the server
and you know how to create the new password each millisecond. If
authentication works we get the following message:
235
Go Ahead
If we sent a
bad combination we receive the line:
535
auth failure
How
to build a CRAM-MD5 Response in perl
This is actually very easy you will need the modules:
MIME::Base64;
Digest::HMAC_MD5
I will assume
that you can figure out how to use MIME::BASE64 from my script example.
To call HMAC_MD5 you need to specify the following use:
use
Digest::HMAC_MD5 qw(hmac_md5 hmac_md5_hex);
Then in your
code you need to base64 unencode the session ticket from the server.
Then pass both the unencoded session ticket and your password into
the following command:
$encPass
= hmac_md5_hex($ticket, $password);
Next you need
to add your username to the front of the $encPass string. Finally
Base64 encode this and send it to the server.
I am sorry I
have not added CRAM-MD5 to my sample script but I am having some
difficulties with my server, however this will work with other servers
I have been able to successfully try it on 5 servers.
|