Perl SMTP AUTH
-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?bottom 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.
-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.
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 is Username: encoded in base64. We then send the server our username encoded in base64 and we receive the line:
334 UGFzc3dvcmQ6
Which 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 an extra characters that messes 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.?
SSL/TLS Support
The following is an additional script modified by Stephen Sullivan to enable TLS/SSL support.? I have not had the chance to test it, but I am told it works with gmail.
use IO::Socket::SSL;
use MIME::Base64;
my??? $socket = IO::Socket::SSL->new(PeerAddr => "smtp.gmail.com",
?????????????????????????????????????????? PeerPort => 465,
?????????????????????????????????????????? Proto??? => ‘tcp’);
$socket or die "Failed to make a connection!\n";
my $from = "<someemail\@foo.com>";
my $to = "<someemail\@foo.com>";
my $subject = "A Test Message";
my $user = encode_base64("username");
my $password = encode_base64("password");
chomp ($user); chomp ($password);
sysread ($socket, $response, 1024);
print $response;
print "TX: ehlo smtp.gmail.com\n";
print $socket "ehlo smtp.gmail.com\r\n";
sysread ($socket, $response, 1024);
print $response;
print "TX: AUTH LOGIN\n";
print $socket "AUTH LOGIN\r\n";
sysread ($socket, $response, 1024);
print $response;
print "TX: $user\n";
print $socket $user, "\r\n";
sysread ($socket, $response, 1024);
print $response;
print "TX: $password\n";
print $socket $password, "\r\n";
sysread ($socket, $response, 1024);
print $response;
print "TX: MAIL FROM: $from\n";
print $socket "MAIL FROM: $from\r\n";
sysread ($socket, $response, 1024);
print $response;
print "TX: RCPT TO: $to\n";
print $socket "RCPT TO: $to\r\n";
sysread ($socket, $response, 1024);
print $response;
print "TX: data\n";
print $socket "data\r\n";
sysread ($socket, $response, 1024);
print $response;
my $body = "";
$body .= "To: $to\r\n";
$body .= "From: $from\r\n";
$body .= "Subject: $subject\r\n";
$body .= "Hey this is a test message\r\n";
$body .= ".\r\n";
print "TX: $body";
print $socket $body;
sysread ($socket, $response, 1024);
print $response;
print "TX: quit";
print $socket quit;
$sock->close();