Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #20 from jdbevan/master

Added support for the client to authenticate using CRAM-MD5
  • Loading branch information...
commit 1948361ebfdbd635e343e643916b897397d43c05 2 parents ba752cb + a3d29e9
@andris9 authored
Showing with 60 additions and 2 deletions.
  1. +60 −2 lib/client.js
View
62 lib/client.js
@@ -7,7 +7,8 @@ var Stream = require("stream").Stream,
tls = require("tls"),
oslib = require("os"),
starttls = require("./starttls").starttls,
- xoauth2 = require("xoauth2");
+ xoauth2 = require("xoauth2"),
+ crypto = require("crypto");
// monkey patch net and tls to support nodejs 0.4
if(!net.connect && net.createConnection){
@@ -472,6 +473,10 @@ SMTPClient.prototype._authenticateUser = function(){
this.options.auth.user+"\u0000"+
this.options.auth.pass,"utf-8").toString("base64"));
return;
+ case "CRAM-MD5":
+ this._currentAction = this._actionAUTH_CRAM_MD5;
+ this.sendCommand("AUTH CRAM-MD5");
+ return;
}
this._onError(new Error("Unknown authentication method - "+auth), "UnknowAuthError");
@@ -529,6 +534,11 @@ SMTPClient.prototype._actionEHLO = function(str){
this._supportedAuth.push("LOGIN");
}
+ // Detect if the server supports CRAM-MD5 auth
+ if(str.match(/AUTH(?:\s+[^\n]*\s+|\s+)CRAM-MD5/i)){
+ this._supportedAuth.push("CRAM-MD5");
+ }
+
// Detect if the server supports XOAUTH auth
if(str.match(/AUTH(?:\s+[^\n]*\s+|\s+)XOAUTH/i)){
this._supportedAuth.push("XOAUTH");
@@ -608,6 +618,54 @@ SMTPClient.prototype._actionAUTH_LOGIN_USER = function(str){
};
/**
+ * <p>Handle the response for AUTH CRAM-MD5 command. We are expecting
+ * '334 <challenge string>'. Data to be sent as response needs to be
+ * base64 decoded challenge string, MD5 hashed using the password as
+ * a HMAC key, prefixed by the username and a space, and finally all
+ * base64 encoded again.</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionAUTH_CRAM_MD5 = function(str) {
+ var challengeMatch = str.match(/^334\s+(.+)$/),
+ challengeString = "";
+
+ if (!challengeMatch) {
+ this._onError(new Error("Invalid login sequence while waiting for server challenge string - "+str), false, str);
+ return;
+ } else {
+ challengeString = challengeMatch[1];
+ }
+
+ // Decode from base64
+ var base64decoded = new Buffer(challengeString, 'base64').toString('ascii'),
+ hmac_md5 = crypto.createHmac('md5', this.options.auth.pass);
+ hmac_md5.update(base64decoded);
+ var hex_hmac = hmac_md5.digest('hex'),
+ prepended = this.options.auth.user + " " + hex_hmac;
+
+ this._currentAction = this._actionAUTH_CRAM_MD5_PASS;
+
+ this.sendCommand(new Buffer(prepended).toString("base64"));
+};
+
+/**
+ * <p>Handles the response to CRAM-MD5 authentication, if there's no error,
+ * the user can be considered logged in. Emit 'idle' and start
+ * waiting for a message to send</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionAUTH_CRAM_MD5_PASS = function(str) {
+ if (!str.match(/^235\s+/)) {
+ this._onError(new Error("Invalid login sequence while waiting for '235 go ahead' - "+str), false, str);
+ return;
+ }
+ this._currentAction = this._actionIdle;
+ this.emit("idle"); // ready to take orders
+};
+
+/**
* <p>Handle the response for AUTH LOGIN command. We are expecting
* '334 UGFzc3dvcmQ6' (base64 for 'Password:'). Data to be sent as
* response needs to be base64 encoded password.</p>
@@ -772,4 +830,4 @@ SMTPClient.prototype._actionStream = function(str){
// Waiting for new connections
this._currentAction = this._actionIdle;
process.nextTick(this.emit.bind(this, "idle"));
-};
+};
Please sign in to comment.
Something went wrong with that request. Please try again.