aboutsummaryrefslogtreecommitdiffstats
path: root/tools/node_modules/nodemailer/node_modules/simplesmtp/lib
diff options
context:
space:
mode:
Diffstat (limited to 'tools/node_modules/nodemailer/node_modules/simplesmtp/lib')
-rw-r--r--tools/node_modules/nodemailer/node_modules/simplesmtp/lib/client.js709
-rw-r--r--tools/node_modules/nodemailer/node_modules/simplesmtp/lib/pool.js316
-rw-r--r--tools/node_modules/nodemailer/node_modules/simplesmtp/lib/server.js664
-rw-r--r--tools/node_modules/nodemailer/node_modules/simplesmtp/lib/starttls.js112
4 files changed, 1801 insertions, 0 deletions
diff --git a/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/client.js b/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/client.js
new file mode 100644
index 0000000..cfe8cea
--- /dev/null
+++ b/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/client.js
@@ -0,0 +1,709 @@
+// TODO:
+// * Lisada timeout serveri ühenduse jaoks
+
+var Stream = require("stream").Stream,
+ utillib = require("util"),
+ net = require("net"),
+ tls = require("tls"),
+ oslib = require("os"),
+ starttls = require("./starttls").starttls;
+
+// monkey patch net and tls to support nodejs 0.4
+if(!net.connect && net.createConnection){
+ net.connect = net.createConnection;
+}
+
+if(!tls.connect && tls.createConnection){
+ tls.connect = tls.createConnection;
+}
+
+// expose to the world
+module.exports = function(port, host, options){
+ var connection = new SMTPClient(port, host, options);
+ process.nextTick(connection.connect.bind(connection));
+ return connection;
+};
+
+/**
+ * <p>Generates a SMTP connection object</p>
+ *
+ * <p>Optional options object takes the following possible properties:</p>
+ * <ul>
+ * <li><b>secureConnection</b> - use SSL</li>
+ * <li><b>name</b> - the name of the client server</li>
+ * <li><b>auth</b> - authentication object <code>{user:"...", pass:"..."}</code>
+ * <li><b>ignoreTLS</b> - ignore server support for STARTTLS</li>
+ * <li><b>debug</b> - output client and server messages to console</li>
+ * <li><b>instanceId</b> - unique instance id for debugging</li>
+ * </ul>
+ *
+ * @constructor
+ * @namespace SMTP Client module
+ * @param {Number} [port=25] Port number to connect to
+ * @param {String} [host="localhost"] Hostname to connect to
+ * @param {Object} [options] Option properties
+ */
+function SMTPClient(port, host, options){
+ Stream.call(this);
+ this.writable = true;
+ this.readable = true;
+
+ this.options = options || {};
+
+ this.port = port || (this.options.secureConnection ? 465 : 25);
+ this.host = host || "localhost";
+
+ this.options.secureConnection = !!this.options.secureConnection;
+ this.options.auth = this.options.auth || false;
+ this.options.maxConnections = this.options.maxConnections || 5;
+
+ if(!this.options.name){
+ // defaul hostname is machine hostname or [IP]
+ var defaultHostname = (oslib.hostname && oslib.hostname()) ||
+ (oslib.getHostname && oslib.getHostname()) ||
+ "";
+ if(defaultHostname.indexOf('.')<0){
+ defaultHostname = "[127.0.0.1]";
+ }
+ if(defaultHostname.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)){
+ defaultHostname = "["+defaultHostname+"]";
+ }
+
+ this.options.name = defaultHostname;
+ }
+
+ this._init();
+}
+utillib.inherits(SMTPClient, Stream);
+
+/**
+ * <p>Initializes instance variables</p>
+ */
+SMTPClient.prototype._init = function(){
+ /**
+ * Defines if the current connection is secure or not. If not,
+ * STARTTLS can be used if available
+ * @private
+ */
+ this._secureMode = false;
+
+ /**
+ * Ignore incoming data on TLS negotiation
+ * @private
+ */
+ this._ignoreData = false;
+
+ /**
+ * Store incomplete messages coming from the server
+ * @private
+ */
+ this._remainder = "";
+
+ /**
+ * If set to true, then this object is no longer active
+ * @private
+ */
+ this.destroyed = false;
+
+ /**
+ * The socket connecting to the server
+ * @publick
+ */
+ this.socket = false;
+
+ /**
+ * Lists supported auth mechanisms
+ * @private
+ */
+ this._supportedAuth = [];
+
+ /**
+ * Currently in data transfer state
+ * @private
+ */
+ this._dataMode = false;
+
+ /**
+ * Keep track if the client sends a leading \r\n in data mode
+ * @private
+ */
+ this._lastDataBytes = new Buffer(2);
+
+ /**
+ * Function to run if a data chunk comes from the server
+ * @private
+ */
+ this._currentAction = false;
+
+ if(this.options.ignoreTLS || this.options.secureConnection){
+ this._secureMode = true;
+ }
+};
+
+/**
+ * <p>Creates a connection to a SMTP server and sets up connection
+ * listener</p>
+ */
+SMTPClient.prototype.connect = function(){
+
+ if(this.options.secureConnection){
+ this.socket = tls.connect(this.port, this.host, {}, this._onConnect.bind(this));
+ }else{
+ this.socket = net.connect(this.port, this.host);
+ this.socket.on("connect", this._onConnect.bind(this));
+ }
+
+ this.socket.on("error", this._onError.bind(this));
+};
+
+/**
+ * <p>Upgrades the connection to TLS</p>
+ *
+ * @param {Function} callback Callbac function to run when the connection
+ * has been secured
+ */
+SMTPClient.prototype._upgradeConnection = function(callback){
+ this._ignoreData = true;
+ starttls(this.socket, (function(socket){
+ this.socket = socket;
+ this._ignoreData = false;
+ this._secureMode = true;
+ this.socket.on("data", this._onData.bind(this));
+
+ return callback(null, true);
+ }).bind(this));
+};
+
+/**
+ * <p>Connection listener that is run when the connection to
+ * the server is opened</p>
+ *
+ * @event
+ */
+SMTPClient.prototype._onConnect = function(){
+ if("setKeepAlive" in this.socket){
+ this.socket.setKeepAlive(true);
+ }else if(this.socket.encrypted && "setKeepAlive" in this.socket.encrypted){
+ this.socket.encrypted.setKeepAlive(true); // secure connection
+ }
+
+ this.socket.on("data", this._onData.bind(this));
+ this.socket.on("close", this._onClose.bind(this));
+ this.socket.on("end", this._onEnd.bind(this));
+
+ this._currentAction = this._actionGreeting;
+};
+
+/**
+ * <p>Destroys the client - removes listeners etc.</p>
+ */
+SMTPClient.prototype._destroy = function(){
+ if(this._destroyed)return;
+ this._destroyed = true;
+ this.emit("end");
+ this.removeAllListeners();
+};
+
+/**
+ * <p>'data' listener for data coming from the server</p>
+ *
+ * @event
+ * @param {Buffer} chunk Data chunk coming from the server
+ */
+SMTPClient.prototype._onData = function(chunk){
+ var str;
+
+ if(this._ignoreData || !chunk || !chunk.length){
+ return;
+ }
+
+ // Wait until end of line
+ if(chunk[chunk.length-1] != 0x0A){
+ this._remainder += chunk.toString();
+ return;
+ }else{
+ str = (this._remainder + chunk.toString()).trim();
+ this._remainder = "";
+ }
+
+ if(this.options.debug){
+ console.log("SERVER"+(this.options.instanceId?" "+
+ this.options.instanceId:"")+":\n└──"+str.replace(/\r?\n/g,"\n "));
+ }
+
+ if(typeof this._currentAction == "function"){
+ this._currentAction.call(this, str);
+ }
+};
+
+/**
+ * <p>'error' listener for the socket</p>
+ *
+ * @event
+ * @param {Error} err Error object
+ * @param {String} type Error name
+ */
+SMTPClient.prototype._onError = function(err, type, data){
+ if(type && type != "Error"){
+ err.name = type;
+ }
+ if(data){
+ err.data = data;
+ }
+ this.emit("error", err);
+ this.close();
+};
+
+/**
+ * <p>'close' listener for the socket</p>
+ *
+ * @event
+ */
+SMTPClient.prototype._onClose = function(){
+ this._destroy();
+};
+
+/**
+ * <p>'end' listener for the socket</p>
+ *
+ * @event
+ */
+SMTPClient.prototype._onEnd = function(){
+ this._destroy();
+};
+
+/**
+ * <p>Passes data stream to socket if in data mode</p>
+ *
+ * @param {Buffer} chunk Chunk of data to be sent to the server
+ */
+SMTPClient.prototype.write = function(chunk){
+ // works only in data mode
+ if(!this._dataMode){
+ // this line should never be reached but if it does, then
+ // say act like everything's normal.
+ return true;
+ }
+
+ if(typeof chunk == "string"){
+ chunk = new Buffer(chunk, "utf-8");
+ }
+
+ if(chunk.length > 2){
+ this._lastDataBytes[0] = chunk[chunk.length-2];
+ this._lastDataBytes[1] = chunk[chunk.length-1];
+ }else if(chunk.length == 1){
+ this._lastDataBytes[0] = this._lastDataBytes[1];
+ this._lastDataBytes[1] = chunk[0];
+ }
+
+ if(this.options.debug){
+ console.log("CLIENT (DATA)"+(this.options.instanceId?" "+
+ this.options.instanceId:"")+":\n└──"+chunk.toString().trim().replace(/\n/g,"\n "));
+ }
+
+ // pass the chunk to the socket
+ return this.socket.write(chunk);
+};
+
+/**
+ * <p>Indicates that a data stream for the socket is ended. Works only
+ * in data mode.</p>
+ *
+ * @param {Buffer} [chunk] Chunk of data to be sent to the server
+ */
+SMTPClient.prototype.end = function(chunk){
+ // works only in data mode
+ if(!this._dataMode){
+ // this line should never be reached but if it does, then
+ // say act like everything's normal.
+ return true;
+ }
+
+ if(chunk && chunk.length){
+ this.write(chunk);
+ }
+
+ // redirect output from the server to _actionStream
+ this._currentAction = this._actionStream;
+
+ // indicate that the stream has ended by sending a single dot on its own line
+ // if the client already closed the data with \r\n no need to do it again
+ if(this._lastDataBytes[0] == 0x0D && this._lastDataBytes[1] == 0x0A){
+ this.socket.write(new Buffer(".\r\n", "utf-8"));
+ }else if(this._lastDataBytes[1] == 0x0D){
+ this.socket.write(new Buffer("\n.\r\n"));
+ }else{
+ this.socket.write(new Buffer("\r\n.\r\n"));
+ }
+
+ // end data mode
+ this._dataMode = false;
+};
+
+/**
+ * <p>Send a command to the server, append \r\n</p>
+ *
+ * @param {String} str String to be sent to the server
+ */
+SMTPClient.prototype.sendCommand = function(str){
+ if(this.options.debug){
+ console.log("CLIENT"+(this.options.instanceId?" "+
+ this.options.instanceId:"")+":\n└──"+(str || "").toString().trim().replace(/\n/g,"\n "));
+ }
+ this.socket.write(new Buffer(str+"\r\n", "utf-8"));
+};
+
+/**
+ * <p>Sends QUIT</p>
+ */
+SMTPClient.prototype.quit = function(){
+ this.sendCommand("QUIT");
+ this._currentAction = this.close;
+};
+
+/**
+ * <p>Closes the connection to the server</p>
+ */
+SMTPClient.prototype.close = function(){
+ if(this.options.debug){
+ console.log("Closing connection to the server");
+ }
+ if(this.socket && !this.socket.destroyed){
+ this.socket.end();
+ }
+ this._destroy();
+};
+
+/**
+ * <p>Initiates a new message by submitting envelope data, starting with
+ * <code>MAIL FROM:</code> command</p>
+ *
+ * @param {Object} envelope Envelope object in the form of
+ * <code>{from:"...", to:["..."]}</code>
+ */
+SMTPClient.prototype.useEnvelope = function(envelope){
+ this._envelope = envelope || {};
+ this._envelope.from = this._envelope.from || ("anonymous@"+this.options.name);
+
+ // clone the recipients array for latter manipulation
+ this._envelope.rcptQueue = JSON.parse(JSON.stringify(this._envelope.to || []));
+ this._envelope.rcptFailed = [];
+
+ this._currentAction = this._actionMAIL;
+ this.sendCommand("MAIL FROM:<"+(this._envelope.from)+">");
+};
+
+/**
+ * <p>If needed starts the authentication, if not emits 'idle' to
+ * indicate that this client is ready to take in an outgoing mail</p>
+ */
+SMTPClient.prototype._authenticateUser = function(){
+
+ if(!this.options.auth){
+ // no need to authenticate, at least no data given
+ this._currentAction = this._actionIdle;
+ this.emit("idle"); // ready to take orders
+ return;
+ }
+
+ var auth;
+
+ if(this.options.auth.XOAuthToken && this._supportedAuth.indexOf("XOAUTH")>=0){
+ auth = "XOAUTH";
+ }else if(this.options.authMethod) {
+ auth = this.options.authMethod.toUpperCase().trim();
+ }else{
+ // use first supported
+ auth = (this._supportedAuth[0] || "PLAIN").toUpperCase().trim();
+ }
+
+ switch(auth){
+ case "XOAUTH":
+ this._currentAction = this._actionAUTHComplete;
+
+ if(typeof this.options.auth.XOAuthToken == "object" &&
+ typeof this.options.auth.XOAuthToken.generate == "function"){
+ this.options.auth.XOAuthToken.generate((function(err, XOAuthToken){
+ if(err){
+ return this._onError(err, "XOAuthTokenError");
+ }
+ this.sendCommand("AUTH XOAUTH " + XOAuthToken);
+ }).bind(this));
+ }else{
+ this.sendCommand("AUTH XOAUTH " + this.options.auth.XOAuthToken.toString());
+ }
+ return;
+ case "LOGIN":
+ this._currentAction = this._actionAUTH_LOGIN_USER;
+ this.sendCommand("AUTH LOGIN");
+ return;
+ case "PLAIN":
+ this._currentAction = this._actionAUTHComplete;
+ this.sendCommand("AUTH PLAIN "+new Buffer(
+ this.options.auth.user+"\u0000"+
+ this.options.auth.user+"\u0000"+
+ this.options.auth.pass,"utf-8").toString("base64"));
+ return;
+ }
+
+ this._onError(new Error("Unknown authentication method - "+auth), "UnknowAuthError");
+};
+
+/** ACTIONS **/
+
+/**
+ * <p>Will be run after the connection is created and the server sends
+ * a greeting. If the incoming message starts with 220 initiate
+ * SMTP session by sending EHLO command</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionGreeting = function(str){
+ if(str.substr(0,3) != "220"){
+ this._onError(new Error("Invalid greeting from server - "+str), false, str);
+ return;
+ }
+
+ this._currentAction = this._actionEHLO;
+ this.sendCommand("EHLO "+this.options.name);
+};
+
+/**
+ * <p>Handles server response for EHLO command. If it yielded in
+ * error, try HELO instead, otherwise initiate TLS negotiation
+ * if STARTTLS is supported by the server or move into the
+ * authentication phase.</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionEHLO = function(str){
+ if(str.charAt(0) != "2"){
+ // Try HELO instead
+ this._currentAction = this._actionHELO;
+ this.sendCommand("HELO "+this.options.name);
+ return;
+ }
+
+ // Detect if the server supports STARTTLS
+ if(!this._secureMode && str.match(/[ \-]STARTTLS\r?$/mi)){
+ this.sendCommand("STARTTLS");
+ this._currentAction = this._actionSTARTTLS;
+ return;
+ }
+
+ // Detect if the server supports PLAIN auth
+ if(str.match(/AUTH(?:\s+[^\n]*\s+|\s+)PLAIN/i)){
+ this._supportedAuth.push("PLAIN");
+ }
+
+ // Detect if the server supports LOGIN auth
+ if(str.match(/AUTH(?:\s+[^\n]*\s+|\s+)LOGIN/i)){
+ this._supportedAuth.push("LOGIN");
+ }
+
+ // Detect if the server supports LOGIN auth
+ if(str.match(/AUTH(?:\s+[^\n]*\s+|\s+)XOAUTH/i)){
+ this._supportedAuth.push("XOAUTH");
+ }
+
+ this._authenticateUser.call(this);
+};
+
+/**
+ * <p>Handles server response for HELO command. If it yielded in
+ * error, emit 'error', otherwise move into the authentication phase.</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionHELO = function(str){
+ if(str.charAt(0) != "2"){
+ this._onError(new Error("Invalid response for EHLO/HELO - "+str), false, str);
+ return;
+ }
+ this._authenticateUser.call(this);
+};
+
+/**
+ * <p>Handles server response for STARTTLS command. If there's an error
+ * try HELO instead, otherwise initiate TLS upgrade. If the upgrade
+ * succeedes restart the EHLO</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionSTARTTLS = function(str){
+ if(str.charAt(0) != "2"){
+ // Try HELO instead
+ this._currentAction = this._actionHELO;
+ this.sendCommand("HELO "+this.options.name);
+ return;
+ }
+
+ this._upgradeConnection((function(err, secured){
+ if(err){
+ this._onError(new Error("Error initiating TLS - "+(err.message || err)), "TLSError");
+ return;
+ }
+ if(this.options.debug){
+ console.log("Connection secured");
+ }
+
+ if(secured){
+ // restart session
+ this._currentAction = this._actionEHLO;
+ this.sendCommand("EHLO "+this.options.name);
+ }else{
+ this._authenticateUser.call(this);
+ }
+ }).bind(this));
+};
+
+/**
+ * <p>Handle the response for AUTH LOGIN command. We are expecting
+ * '334 VXNlcm5hbWU6' (base64 for 'Username:'). Data to be sent as
+ * response needs to be base64 encoded username.</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionAUTH_LOGIN_USER = function(str){
+ if(str != "334 VXNlcm5hbWU6"){
+ this._onError(new Error("Invalid login sequence while waiting for '334 VXNlcm5hbWU6' - "+str), false, str);
+ return;
+ }
+ this._currentAction = this._actionAUTH_LOGIN_PASS;
+ this.sendCommand(new Buffer(
+ this.options.auth.user, "utf-8").toString("base64"));
+};
+
+/**
+ * <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>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionAUTH_LOGIN_PASS = function(str){
+ if(str != "334 UGFzc3dvcmQ6"){
+ this._onError(new Error("Invalid login sequence while waiting for '334 UGFzc3dvcmQ6' - "+str), false, str);
+ return;
+ }
+ this._currentAction = this._actionAUTHComplete;
+ this.sendCommand(new Buffer(this.options.auth.pass, "utf-8").toString("base64"));
+};
+
+/**
+ * <p>Handles the response for 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._actionAUTHComplete = function(str){
+ if(str.charAt(0) != "2"){
+ this._onError(new Error("Invalid login - "+str), "AuthError", str);
+ return;
+ }
+
+ this._currentAction = this._actionIdle;
+ this.emit("idle"); // ready to take orders
+};
+
+/**
+ * <p>This function is not expected to run. If it does then there's probably
+ * an error (timeout etc.)</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionIdle = function(str){
+ if(Number(str.charAt(0)) > 3){
+ this._onError(new Error(str), false, str);
+ return;
+ }
+
+ // this line should never get called
+};
+
+/**
+ * <p>Handle response for a <code>MAIL FROM:</code> command</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionMAIL = function(str){
+ if(Number(str.charAt(0)) != "2"){
+ this._onError(new Error("Mail from command failed - " + str), "SenderError", str);
+ return;
+ }
+
+ if(!this._envelope.rcptQueue.length){
+ this._onError(new Error("Can't send mail - no recipients defined"), "RecipientError");
+ }else{
+ this._envelope.curRecipient = this._envelope.rcptQueue.shift();
+ this._currentAction = this._actionRCPT;
+ this.sendCommand("RCPT TO:<"+this._envelope.curRecipient+">");
+ }
+};
+
+/**
+ * <p>Handle response for a <code>RCPT TO:</code> command</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionRCPT = function(str){
+ if(Number(str.charAt(0)) != "2"){
+ // this is a soft error
+ this._envelope.rcptFailed.push(this._envelope.curRecipient);
+ }
+
+ if(!this._envelope.rcptQueue.length){
+ if(this._envelope.rcptFailed.length < this._envelope.to.length){
+ this.emit("rcptFailed", this._envelope.rcptFailed);
+ this._currentAction = this._actionDATA;
+ this.sendCommand("DATA");
+ }else{
+ this._onError(new Error("Can't send mail - all recipients were rejected"), "RecipientError");
+ return;
+ }
+ }else{
+ this._envelope.curRecipient = this._envelope.rcptQueue.shift();
+ this._currentAction = this._actionRCPT;
+ this.sendCommand("RCPT TO:<"+this._envelope.curRecipient+">");
+ }
+};
+
+/**
+ * <p>Handle response for a <code>DATA</code> command</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionDATA = function(str){
+ // response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
+ // some servers might use 250 instead, so lets check for 2 or 3 as the first digit
+ if([2,3].indexOf(Number(str.charAt(0)))<0){
+ this._onError(new Error("Data command failed - " + str), false, str);
+ return;
+ }
+
+ // Emit that connection is set up for streaming
+ this._dataMode = true;
+ this._currentAction = this._actionIdle;
+ this.emit("message");
+};
+
+/**
+ * <p>Handle response for a <code>DATA</code> stream</p>
+ *
+ * @param {String} str Message from the server
+ */
+SMTPClient.prototype._actionStream = function(str){
+ if(Number(str.charAt(0)) != "2"){
+ // Message failed
+ this.emit("ready", false, str);
+ }else{
+ // Message sent succesfully
+ this.emit("ready", true, str);
+ }
+
+ // Waiting for new connections
+ this._currentAction = this._actionIdle;
+ process.nextTick(this.emit.bind(this, "idle"));
+}; \ No newline at end of file
diff --git a/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/pool.js b/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/pool.js
new file mode 100644
index 0000000..7a96d68
--- /dev/null
+++ b/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/pool.js
@@ -0,0 +1,316 @@
+var simplesmtp = require("../index"),
+ EventEmitter = require('events').EventEmitter,
+ utillib = require("util");
+
+// expose to the world
+module.exports = function(port, host, options){
+ var pool = new SMTPConnectionPool(port, host, options);
+ return pool;
+};
+
+/**
+ * <p>Creates a SMTP connection pool</p>
+ *
+ * <p>Optional options object takes the following possible properties:</p>
+ * <ul>
+ * <li><b>secureConnection</b> - use SSL</li>
+ * <li><b>name</b> - the name of the client server</li>
+ * <li><b>auth</b> - authentication object <code>{user:"...", pass:"..."}</code>
+ * <li><b>ignoreTLS</b> - ignore server support for STARTTLS</li>
+ * <li><b>debug</b> - output client and server messages to console</li>
+ * <li><b>maxConnections</b> - how many connections to keep in the pool</li>
+ * </ul>
+ *
+ * @constructor
+ * @namespace SMTP Client Pool module
+ * @param {Number} [port=25] The port number to connecto to
+ * @param {String} [host="localhost"] THe hostname to connect to
+ * @param {Object} [options] optional options object
+ */
+function SMTPConnectionPool(port, host, options){
+ EventEmitter.call(this);
+
+ /**
+ * Port number to connect to
+ * @public
+ */
+ this.port = port || 25;
+
+ /**
+ * Hostname to connect to
+ * @public
+ */
+ this.host = host || "localhost";
+
+ /**
+ * Options object
+ * @public
+ */
+ this.options = options || {};
+ this.options.maxConnections = this.options.maxConnections || 5;
+
+ /**
+ * An array of connections that are currently idle
+ * @private
+ */
+ this._connectionsAvailable = [];
+
+ /**
+ * An array of connections that are currently in use
+ * @private
+ */
+ this._connectionsInUse = [];
+
+ /**
+ * Message queue (FIFO)
+ * @private
+ */
+ this._messageQueue = [];
+
+ /**
+ * Counter for generating ID values for debugging
+ * @private
+ */
+ this._idgen = 0;
+}
+utillib.inherits(SMTPConnectionPool, EventEmitter);
+
+/**
+ * <p>Sends a message. If there's any idling connections available
+ * use one to send the message immediatelly, otherwise add to queue.</p>
+ *
+ * @param {Object} message MailComposer object
+ * @param {Function} callback Callback function to run on finish, gets an
+ * <code>error</code> object as a parameter if the sending failed
+ * and on success an object with <code>failedRecipients</code> array as
+ * a list of addresses that were rejected (if any) and
+ * <code>message</code> which indicates the last message received from
+ * the server
+ */
+SMTPConnectionPool.prototype.sendMail = function(message, callback){
+ var connection;
+
+ message.returnCallback = callback;
+
+ if(this._connectionsAvailable.length){
+ // if available connections pick one
+ connection = this._connectionsAvailable.pop();
+ this._connectionsInUse.push(connection);
+ this._processMessage(message, connection);
+ }else{
+ this._messageQueue.push(message);
+
+ if(this._connectionsAvailable.length + this._connectionsInUse.length < this.options.maxConnections){
+ this._createConnection();
+ }
+ }
+
+};
+
+/**
+ * <p>Closes all connections</p>
+ */
+SMTPConnectionPool.prototype.close = function(callback){
+ var connection;
+
+ // for some reason destroying the connections seem to be the only way :S
+ while(this._connectionsAvailable.length){
+ connection = this._connectionsAvailable.pop();
+ if(connection.socket){
+ connection.socket.destroy();
+ }
+ }
+
+ while(this._connectionsInUse.length){
+ connection = this._connectionsInUse.pop();
+ if(connection.socket){
+ connection.socket.destroy();
+ }
+ }
+
+ if(callback){
+ process.nextTick(callback);
+ }
+};
+
+/**
+ * <p>Initiates a connection to the SMTP server and adds it to the pool</p>
+ */
+SMTPConnectionPool.prototype._createConnection = function(){
+ var connectionOptions = {
+ instanceId: ++this._idgen,
+ debug: !!this.options.debug,
+ ignoreTLS: !!this.options.ignoreTLS,
+ auth: this.options.auth || false,
+ authMethod: this.options.authMethod,
+ name: this.options.name || false,
+ secureConnection: !!this.options.secureConnection
+ },
+ connection = simplesmtp.connect(this.port, this.host, connectionOptions);
+
+ connection.on("idle", this._onConnectionIdle.bind(this, connection));
+ connection.on("message", this._onConnectionMessage.bind(this, connection));
+ connection.on("ready", this._onConnectionReady.bind(this, connection));
+ connection.on("error", this._onConnectionError.bind(this, connection));
+ connection.on("end", this._onConnectionEnd.bind(this, connection));
+ connection.on("rcptFailed", this._onConnectionRCPTFailed.bind(this, connection));
+
+ this.emit('connectionCreated', connection);
+
+ // as the connection is not ready yet, add to "in use" queue
+ this._connectionsInUse.push(connection);
+};
+
+/**
+ * <p>Processes a message by assigning it to a connection object and initiating
+ * the sending process by setting the envelope</p>
+ *
+ * @param {Object} message MailComposer message object
+ * @param {Object} connection <code>simplesmtp.connect</code> connection
+ */
+SMTPConnectionPool.prototype._processMessage = function(message, connection){
+ connection.currentMessage = message;
+ message.currentConnection = connection;
+
+ // send envelope
+ connection.useEnvelope(message.getEnvelope());
+};
+
+/**
+ * <p>Will be fired on <code>'idle'</code> events by the connection, if
+ * there's a message currently in queue</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ */
+SMTPConnectionPool.prototype._onConnectionIdle = function(connection){
+
+ var message = this._messageQueue.shift();
+
+ if(message){
+ this._processMessage(message, connection);
+ }else{
+ for(var i=0, len = this._connectionsInUse.length; i<len; i++){
+ if(this._connectionsInUse[i] == connection){
+ this._connectionsInUse.splice(i,1); // remove from list
+ break;
+ }
+ }
+ this._connectionsAvailable.push(connection);
+ }
+};
+
+/**
+ * <p>Will be called when not all recipients were accepted</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ * @param {Array} addresses Failed addresses as an array of strings
+ */
+SMTPConnectionPool.prototype._onConnectionRCPTFailed = function(connection, addresses){
+ if(connection.currentMessage){
+ connection.currentMessage.failedRecipients = addresses;
+ }
+};
+
+/**
+ * <p>Will be called when the client is waiting for a message to deliver</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ */
+SMTPConnectionPool.prototype._onConnectionMessage = function(connection){
+ if(connection.currentMessage){
+ connection.currentMessage.streamMessage();
+ connection.currentMessage.pipe(connection);
+ }
+};
+
+/**
+ * <p>Will be called when a message has been delivered</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ * @param {Boolean} success True if the message was queued by the SMTP server
+ * @param {String} message Last message received from the server
+ */
+SMTPConnectionPool.prototype._onConnectionReady = function(connection, success, message){
+ var error, responseObj = {};
+ if(connection.currentMessage && connection.currentMessage.returnCallback){
+ if(success){
+
+ if(connection.currentMessage.failedRecipients){
+ responseObj.failedRecipients = connection.currentMessage.failedRecipients;
+ }
+ if(message){
+ responseObj.message = message;
+ }
+
+ connection.currentMessage.returnCallback(null, responseObj);
+
+ }else{
+ error = new Error("Message delivery failed");
+ error.name = "DeliveryError";
+ connection.currentMessage.returnCallback(error);
+ }
+ }
+ connection.currentMessage = false;
+};
+
+/**
+ * <p>Will be called when an error occurs</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ * @param {Object} error Error object
+ */
+SMTPConnectionPool.prototype._onConnectionError = function(connection, error){
+ var message = connection.currentMessage;
+ connection.currentMessage = false;
+
+ // clear a first message from the list, otherwise an infinite loop will emerge
+ if(!message){
+ message = this._messageQueue.shift();
+ }
+
+ if(message && message.returnCallback){
+ message.returnCallback(error);
+ }
+};
+
+/**
+ * <p>Will be called when a connection to the client is closed</p>
+ *
+ * @event
+ * @param {Object} connection Connection object that fired the event
+ */
+SMTPConnectionPool.prototype._onConnectionEnd = function(connection){
+ var removed = false, i, len;
+
+ // if in "available" list, remove
+ for(i=0, len = this._connectionsAvailable.length; i<len; i++){
+ if(this._connectionsAvailable[i] == connection){
+ this._connectionsAvailable.splice(i,1); // remove from list
+ removed = true;
+ break;
+ }
+ }
+
+ if(!removed){
+ // if in "in use" list, remove
+ for(i=0, len = this._connectionsInUse.length; i<len; i++){
+ if(this._connectionsInUse[i] == connection){
+ this._connectionsInUse.splice(i,1); // remove from list
+ removed = true;
+ break;
+ }
+ }
+ }
+
+ // if there's still unprocessed mail and available connection slots, create
+ // a new connection
+ if(this._messageQueue.length &&
+ this._connectionsInUse.length + this._connectionsAvailable.length < this.options.maxConnections){
+ this._createConnection();
+ }
+}; \ No newline at end of file
diff --git a/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/server.js b/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/server.js
new file mode 100644
index 0000000..62aa650
--- /dev/null
+++ b/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/server.js
@@ -0,0 +1,664 @@
+/**
+ * @fileOverview This is the main file for the simplesmtp library to create custom SMTP servers
+ * @author <a href="mailto:andris@node.ee">Andris Reinman</a>
+ */
+
+var RAIServer = require("rai").RAIServer,
+ EventEmitter = require('events').EventEmitter,
+ oslib = require('os'),
+ utillib = require("util"),
+ dnslib = require("dns");
+
+// expose to the world
+module.exports = function(options){
+ return new SMTPServer(options);
+};
+
+/**
+ * <p>Constructs a SMTP server</p>
+ *
+ * <p>Possible options are:</p>
+ *
+ * <ul>
+ * <li><b>name</b> - the hostname of the server, will be used for
+ * informational messages</li>
+ * <li><b>debug</b> - if set to true, print out messages about the connection</li>
+ * <li><b>timeout</b> - client timeout in milliseconds, defaults to 60 000</li>
+ * <li><b>secureConnection</b> - start a server on secure connection</li>
+ * <li><b>SMTPBanner</b> - greeting banner that is sent to the client on connection</li>
+ * <li><b>requireAuthentication</b> - if set to true, require that the client
+ * must authenticate itself</li>
+ * <li><b>enableAuthentication</b> - if set to true, client may authenticate itself but don't have to</li>
+ * <li><b>validateSender</b> - if set to true, emit <code>'validateSender'</code>
+ * with <code>envelope</code>, <code>email</code> and <code>callback</code> when the client
+ * enters <code>MAIL FROM:&lt;address&gt;</code></li>
+ * <li><b>validateRecipients</b> - if set to true, emit <code>'validateRecipient'</code>
+ * with <code>envelope</code>, <code>email</code> and <code>callback</code> when the client
+ * enters <code>RCPT TO:&lt;address&gt;</code></li>
+ * <li><b>maxSize</b> - maximum size of an e-mail in bytes</li>
+ * <li><b>credentials</b> - TLS credentials</li>
+ * <li><b>authMethods</b> - allowed authentication methods, defaults to <code>["PLAIN", "LOGIN"]</code></li>
+ * <li><b>disableEHLO</b> - if set, support HELO only</li>
+ * <li><b>ignoreTLS</b> - if set, allow client do not use STARTTLS</li>
+ * </ul>
+ *
+ * @constructor
+ * @namespace SMTP Server module
+ * @param {Object} [options] Options object
+ */
+function SMTPServer(options){
+ EventEmitter.call(this);
+
+ this.options = options || {};
+ this.options.name = this.options.name || (oslib.hostname && oslib.hostname()) ||
+ (oslib.getHostname && oslib.getHostname()) ||
+ "127.0.0.1";
+
+ this.options.authMethods = (this.options.authMethods || ["PLAIN", "LOGIN"]).map(
+ function(auth){
+ return auth.toUpperCase().trim();
+ });
+
+ this.options.disableEHLO = !!this.options.disableEHLO;
+ this.options.ignoreTLS = !!this.options.ignoreTLS;
+
+ this.SMTPServer = new RAIServer({
+ secureConnection: !!this.options.secureConnection,
+ timeout: this.options.timeout || 60*1000,
+ disconnectOnTimeout: false,
+ debug: !!this.options.debug
+ });
+
+ this.SMTPServer.on("connect", this._createSMTPServerConnection.bind(this));
+}
+utillib.inherits(SMTPServer, EventEmitter);
+
+/**
+ * Server starts listening on defined port and hostname
+ *
+ * @param {Number} port The port number to listen
+ * @param {String} [host] The hostname to listen
+ * @param {Function} callback The callback function to run when the server is listening
+ */
+SMTPServer.prototype.listen = function(port, host, callback){
+ this.SMTPServer.listen(port, host, callback);
+};
+
+/**
+ * <p>Closes the server</p>
+ *
+ * @param {Function} callback The callback function to run when the server is closed
+ */
+SMTPServer.prototype.end = function(callback){
+ this.SMTPServer.end(callback);
+};
+
+/**
+ * <p>Creates a new {@link SMTPServerConnection} object and links the main server with
+ * the client socket</p>
+ *
+ * @param {Object} client RAISocket object to a client
+ */
+SMTPServer.prototype._createSMTPServerConnection = function(client){
+ new SMTPServerConnection(this, client);
+};
+
+/**
+ * <p>Sets up a handler for the connected client</p>
+ *
+ * <p>Restarts the state and sets up event listeners for client actions</p>
+ *
+ * @constructor
+ * @param {Object} server {@link SMTPServer} instance
+ * @param {Object} client RAISocket instance for the client
+ */
+function SMTPServerConnection(server, client){
+ this.server = server;
+ this.client = client;
+
+ this.init();
+
+ if(this.server.options.debug){
+ console.log("Connection from", this.client.remoteAddress);
+ }
+
+ this.client.on("timeout", this._onTimeout.bind(this));
+ this.client.on("error", this._onError.bind(this));
+ this.client.on("command", this._onCommand.bind(this));
+ this.client.on("end", this._onEnd.bind(this));
+
+ this.client.on("data", this._onData.bind(this));
+ this.client.on("ready", this._onDataReady.bind(this));
+
+ // Send the greeting banner
+ this.client.send("220 "+this.server.options.name+" "+(this.server.options.SMTPBanner || "ESMTP node.js simplesmtp"));
+}
+
+/**
+ * <p>Reset the envelope state</p>
+ *
+ * <p>If <code>keepAuthData</code> is set to true, then doesn't remove
+ * authentication data</p>
+ *
+ * @param {Boolean} [keepAuthData=false] If set to true keep authentication data
+ */
+SMTPServerConnection.prototype.init = function(keepAuthData){
+ this.envelope = {from: "", to:[], date: new Date()};
+
+ if(this.hostNameAppearsAs){
+ this.envelope.host = this.hostNameAppearsAs;
+ }
+
+ if(this.client.remoteAddress){
+ this.envelope.remoteAddress = this.client.remoteAddress;
+ }
+
+ if(!keepAuthData){
+ this.authentication = {
+ username: false,
+ authenticated: false,
+ state: "NORMAL"
+ };
+ }
+
+ this.envelope.authentication = this.authentication;
+};
+
+/**
+ * <p>Sends a message to the client and closes the connection</p>
+ *
+ * @param {String} [message] if set, send it to the client before disconnecting
+ */
+SMTPServerConnection.prototype.end = function(message){
+ if(message){
+ this.client.send(message);
+ }
+ this.client.end();
+};
+
+/**
+ * <p>Will be called when the connection to the client is closed</p>
+ *
+ * @event
+ */
+SMTPServerConnection.prototype._onEnd = function(){
+ if(this.server.options.debug){
+ console.log("Connection closed to", this.client.remoteAddress);
+ }
+ this.server.emit("close", this.envelope);
+};
+
+/**
+ * <p>Will be called when timeout occurs</p>
+ *
+ * @event
+ */
+SMTPServerConnection.prototype._onTimeout = function(){
+ this.end("421 4.4.2 "+this.server.options.name+" Error: timeout exceeded");
+};
+
+/**
+ * <p>Will be called when an error occurs</p>
+ *
+ * @event
+ */
+SMTPServerConnection.prototype._onError = function(){
+ this.end("421 4.4.2 "+this.server.options.name+" Error: client error");
+};
+
+/**
+ * <p>Will be called when a command is received from the client</p>
+ *
+ * <p>If there's curently an authentication process going on, route
+ * the data to <code>_handleAuthLogin</code>, otherwise act as
+ * defined</p>
+ *
+ * @event
+ * @param {String} command Command
+ * @param {Buffer} command Payload related to the command
+ */
+SMTPServerConnection.prototype._onCommand = function(command, payload){
+
+ if(this.authentication.state == "AUTHPLAINUSERDATA"){
+ this._handleAuthPlain(command.toString("utf-8").trim().split(" "));
+ return;
+ }
+
+ if(this.authentication.state == "AUTHENTICATING"){
+ this._handleAuthLogin(command);
+ return;
+ }
+
+ switch((command || "").toString().trim().toUpperCase()){
+
+ // Should not occur too often
+ case "HELO":
+ this._onCommandHELO(payload.toString("utf-8").trim());
+ break;
+
+ // Lists server capabilities
+ case "EHLO":
+ if(!this.server.options.disableEHLO){
+ this._onCommandEHLO(payload.toString("utf-8").trim());
+ }else{
+ this.client.send("502 5.5.2 Error: command not recognized");
+ }
+ break;
+
+ // Closes the connection
+ case "QUIT":
+ this.end("221 2.0.0 Goodbye!");
+ break;
+
+ // Resets the current state
+ case "RSET":
+ this._onCommandRSET();
+ break;
+
+ // Doesn't work for spam related purposes
+ case "VRFY":
+ this.client.send("252 2.1.5 Send some mail, I'll try my best");
+ break;
+
+ // Initiate an e-mail by defining a sender
+ case "MAIL":
+ this._onCommandMAIL(payload.toString("utf-8").trim());
+ break;
+
+ // Add recipients to the e-mail envelope
+ case "RCPT":
+ this._onCommandRCPT(payload.toString("utf-8").trim());
+ break;
+
+ // Authenticate if needed
+ case "AUTH":
+ this._onCommandAUTH(payload);
+ break;
+
+ // Start accepting binary data stream
+ case "DATA":
+ this._onCommandDATA();
+ break;
+
+ // Upgrade connection to secure TLS
+ case "STARTTLS":
+ this._onCommandSTARTTLS();
+ break;
+
+ // Display an error on anything else
+ default:
+ this.client.send("502 5.5.2 Error: command not recognized");
+ }
+};
+
+/**
+ * <p>Initiate an e-mail by defining a sender.</p>
+ *
+ * <p>This doesn't work if authorization is required but the client is
+ * not logged in yet.</p>
+ *
+ * <p>If <code>validateSender</code> option is set to true, then emits
+ * <code>'validateSender'</code> and wait for the callback before moving
+ * on</p>
+ *
+ * @param {String} mail Address payload in the form of "FROM:&lt;address&gt;"
+ */
+SMTPServerConnection.prototype._onCommandMAIL = function(mail){
+ var match, email, domain;
+
+ if(!this.hostNameAppearsAs){
+ return this.client.send("503 5.5.1 Error: send HELO/EHLO first");
+ }
+
+ if(this.server.options.requireAuthentication && !this.authentication.authenticated){
+ return this.client.send("530 5.5.1 Authentication Required");
+ }
+
+ if(this.envelope.from){
+ return this.client.send("503 5.5.1 Error: nested MAIL command");
+ }
+
+ if(!(match = mail.match(/^from\:\s*<([^@>]+\@([^@>]+))>$/i))){
+ return this.client.send("501 5.1.7 Bad sender address syntax");
+ }
+
+ email = match[1] || "";
+ domain = (match[2] || "").toLowerCase();
+
+ dnslib.resolveMx(domain, (function(err, addresses){
+ if(err || !addresses || !addresses.length){
+ return this.client.send("450 4.1.8 <"+email+">: Sender address rejected: Domain not found");
+ }
+
+ if(this.server.options.validateSender){
+ this.server.emit("validateSender", this.envelope, email, (function(err){
+ if(err){
+ return this.client.send("550 5.1.1 <"+email+">: Sender address rejected: User unknown in local sender table");
+ }
+
+ // force domain part to be lowercase
+ email = email.substr(0, email.length - domain.length) + domain;
+ this.envelope.from = email;
+ this.client.send("250 2.1.0 Ok");
+
+ }).bind(this));
+ }else{
+ // force domain part to be lowercase
+ email = email.substr(0, email.length - domain.length) + domain;
+ this.envelope.from = email;
+ this.client.send("250 2.1.0 Ok");
+ }
+ }).bind(this));
+};
+
+/**
+ * <p>Add recipients to the e-mail envelope</p>
+ *
+ * <p>This doesn't work if <code>MAIL</code> command is not yet executed</p>
+ *
+ * <p>If <code>validateRecipients</code> option is set to true, then emits
+ * <code>'validateRecipient'</code> and wait for the callback before moving
+ * on</p>
+ *
+ * @param {String} mail Address payload in the form of "TO:&lt;address&gt;"
+ */
+SMTPServerConnection.prototype._onCommandRCPT = function(mail){
+ var match, email, domain;
+
+ if(!this.envelope.from){
+ return this.client.send("503 5.5.1 Error: need MAIL command");
+ }
+
+ if(!(match = mail.match(/^to\:\s*<([^@>]+\@([^@>]+))>$/i))){
+ return this.client.send("501 5.1.7 Bad recipient address syntax");
+ }
+
+ email = match[1] || "";
+ domain = (match[2] || "").toLowerCase();
+
+ dnslib.resolveMx(domain, (function(err, addresses){
+ if(err || !addresses || !addresses.length){
+ return this.client.send("450 4.1.8 <"+email+">: Recipient address rejected: Domain not found");
+ }
+
+ if(this.server.options.validateRecipients){
+ this.server.emit("validateRecipient", this.envelope, email, (function(err){
+ if(err){
+ return this.client.send("550 5.1.1 <"+email+">: Recipient address rejected: User unknown in local recipient table");
+ }
+
+ // force domain part to be lowercase
+ email = email.substr(0, email.length - domain.length) + domain;
+
+ // add to recipients list
+ if(this.envelope.to.indexOf(email)<0){
+ this.envelope.to.push(email);
+ }
+ this.client.send("250 2.1.0 Ok");
+ }).bind(this));
+ }else{
+ // force domain part to be lowercase
+ email = email.substr(0, email.length - domain.length) + domain;
+
+ // add to recipients list
+ if(this.envelope.to.indexOf(email)<0){
+ this.envelope.to.push(email);
+ }
+ this.client.send("250 2.1.0 Ok");
+ }
+ }).bind(this));
+};
+
+/**
+ * <p>Switch to data mode and starts waiting for a binary data stream. Emits
+ * <code>'startData'</code>.</p>
+ *
+ * <p>If <code>RCPT</code> is not yet run, stop</p>
+ */
+SMTPServerConnection.prototype._onCommandDATA = function(){
+
+ if(!this.envelope.to.length){
+ return this.client.send("503 5.5.1 Error: need RCPT command");
+ }
+
+ this.client.startDataMode();
+ this.client.send("354 End data with <CR><LF>.<CR><LF>");
+ this.server.emit("startData", this.envelope);
+};
+
+/**
+ * <p>Resets the current state - e-mail data and authentication info</p>
+ */
+SMTPServerConnection.prototype._onCommandRSET = function(){
+ this.init();
+ this.client.send("250 2.0.0 Ok");
+};
+
+/**
+ * <p>If the server is in secure connection mode, start the authentication
+ * process. Param <code>payload</code> defines the authentication mechanism.</p>
+ *
+ * <p>Currently supported - PLAIN and LOGIN. There is no need for more
+ * complicated mechanisms (different CRAM versions etc.) since authentication
+ * is only done in secure connection mode</p>
+ *
+ * @param {Buffer} payload Defines the authentication mechanism
+ */
+SMTPServerConnection.prototype._onCommandAUTH = function(payload){
+ var method;
+
+ if(!this.server.options.requireAuthentication && !this.server.options.enableAuthentication){
+ return this.client.send("503 5.5.1 Error: authentication not enabled");
+ }
+
+ if(!this.server.options.ignoreTLS && !this.client.secureConnection){
+ return this.client.send("530 5.7.0 Must issue a STARTTLS command first");
+ }
+
+ if(this.authentication.authenticated){
+ return this.client.send("503 5.7.0 No identity changes permitted");
+ }
+
+ payload = payload.toString("utf-8").trim().split(" ");
+ method = payload.shift().trim().toUpperCase();
+
+ if(this.server.options.authMethods.indexOf(method)<0){
+ return this.client.send("535 5.7.8 Error: authentication failed: no mechanism available");
+ }
+
+ switch(method){
+ case "PLAIN":
+ this._handleAuthPlain(payload);
+ break;
+ case "LOGIN":
+ this._handleAuthLogin();
+ break;
+ }
+};
+
+/**
+ * <p>Upgrade the connection to a secure TLS connection</p>
+ */
+SMTPServerConnection.prototype._onCommandSTARTTLS = function(){
+ if(this.client.secureConnection){
+ return this.client.send("554 5.5.1 Error: TLS already active");
+ }
+
+ this.client.send("220 2.0.0 Ready to start TLS");
+
+ this.client.startTLS(this.server.options.credentials, (function(){
+ // Connection secured
+ // nothing to do here, since it is the client that should
+ // make the next move
+ }).bind(this));
+};
+
+/**
+ * <p>Retrieve hostname from the client. Not very important, since client
+ * IP is already known and the client can send fake data</p>
+ *
+ * @param {String} host Hostname of the client
+ */
+SMTPServerConnection.prototype._onCommandHELO = function(host){
+ if(!host){
+ return this.client.send("501 Syntax: EHLO hostname");
+ }else{
+ this.hostNameAppearsAs = host;
+ this.envelope.host = host;
+ }
+ this.client.send("250 "+this.server.options.name+" at your service, ["+
+ this.client.remoteAddress+"]");
+};
+
+/**
+ * <p>Retrieve hostname from the client. Not very important, since client
+ * IP is already known and the client can send fake data</p>
+ *
+ * <p>Additionally displays server capability list to the client</p>
+ *
+ * @param {String} host Hostname of the client
+ */
+SMTPServerConnection.prototype._onCommandEHLO = function(host){
+ var response = [this.server.options.name+" at your service, ["+
+ this.client.remoteAddress+"]", "8BITMIME", "ENHANCEDSTATUSCODES"];
+
+ if(this.server.options.maxSize){
+ response.push("SIZE "+this.server.options.maxSize);
+ }
+
+ if((this.client.secureConnection || this.server.options.ignoreTLS) && (this.server.options.requireAuthentication || this.server.options.enableAuthentication)){
+ response.push("AUTH "+this.server.options.authMethods.join(" "));
+ response.push("AUTH="+this.server.options.authMethods.join(" "));
+ }
+
+ if(!this.client.secureConnection){
+ response.push("STARTTLS");
+ }
+
+ if(!host){
+ return this.client.send("501 Syntax: EHLO hostname");
+ }else{
+ this.hostNameAppearsAs = host;
+ this.envelope.host = host;
+ }
+
+ this.client.send(response.map(function(feature, i, arr){
+ return "250"+(i<arr.length-1?"-":" ")+feature;
+ }).join("\r\n"));
+};
+
+/**
+ * <p>Detect login information from the payload and initiate authentication
+ * by emitting <code>'authorizeUser'</code> and waiting for its callback</p>
+ *
+ * @param {Buffer} payload AUTH PLAIN login information
+ */
+SMTPServerConnection.prototype._handleAuthPlain = function(payload){
+ if (payload.length) {
+ var userdata = new Buffer(payload.join(" "), "base64"), password;
+ userdata = userdata.toString("utf-8").split("\u0000");
+
+ if (userdata.length != 3) {
+ return this.client.send("500 5.5.2 Error: invalid userdata to decode");
+ }
+
+ this.authentication.username = userdata[1] || userdata[0] || "";
+ password = userdata[2] || "";
+
+ this.server.emit("authorizeUser",
+ this.envelope,
+ this.authentication.username,
+ password,
+ (function(err, success){
+ if(err || !success){
+ this.authentication.authenticated = false;
+ this.authentication.username = false;
+ this.authentication.state = "NORMAL";
+ return this.client.send("535 5.7.8 Error: authentication failed: generic failure");
+ }
+ this.client.send("235 2.7.0 Authentication successful");
+ this.authentication.authenticated = true;
+ this.authentication.state = "AUTHENTICATED";
+ }).bind(this));
+ } else {
+ if(this.authentication.state == "NORMAL"){
+ this.authentication.state = "AUTHPLAINUSERDATA";
+ this.client.send("334");
+ }
+ }
+
+};
+
+/**
+ * <p>Sets authorization state to "AUTHENTICATING" and reuqests for the
+ * username and password from the client</p>
+ *
+ * <p>If username and password are set initiate authentication
+ * by emitting <code>'authorizeUser'</code> and waiting for its callback</p>
+ *
+ * @param {Buffer} payload AUTH LOGIN login information
+ */
+SMTPServerConnection.prototype._handleAuthLogin = function(payload){
+ if(this.authentication.state == "NORMAL"){
+ this.authentication.state = "AUTHENTICATING";
+ this.client.send("334 VXNlcm5hbWU6");
+ }else if(this.authentication.state == "AUTHENTICATING"){
+ if(this.authentication.username === false){
+ this.authentication.username = new Buffer(payload, "base64").toString("utf-8");
+ this.client.send("334 UGFzc3dvcmQ6");
+ }else{
+ this.authentication.state = "VERIFYING";
+ this.server.emit("authorizeUser",
+ this.envelope,
+ this.authentication.username,
+ new Buffer(payload, "base64").toString("utf-8"),
+ (function(err, success){
+ if(err || !success){
+ this.authentication.authenticated = false;
+ this.authentication.username = false;
+ this.authentication.state = "NORMAL";
+ return this.client.send("535 5.7.8 Error: authentication failed: generic failure");
+ }
+ this.client.send("235 2.7.0 Authentication successful");
+ this.authentication.authenticated = true;
+ this.authentication.state = "AUTHENTICATED";
+ }).bind(this));
+ }
+
+ }
+};
+
+/**
+ * <p>Emits the data received from the client with <code>'data'</code>
+ *
+ * @event
+ * @param {Buffer} chunk Binary data sent by the client on data mode
+ */
+SMTPServerConnection.prototype._onData = function(chunk){
+ this.server.emit("data", this.envelope, chunk);
+};
+
+/**
+ * <p>If the data stream ends, emit <code>'dataReady'</code>and wait for
+ * the callback, only if server listened for it.</p>
+ *
+ * @event
+ */
+SMTPServerConnection.prototype._onDataReady = function(){
+ if (this.server.listeners('dataReady').length) {
+ this.server.emit("dataReady", this.envelope, (function(err, code){
+ this.init(true); //reset state, keep auth data
+
+ if(err){
+ this.client.send("550 FAILED");
+ }else{
+ this.client.send("250 2.0.0 Ok: queued as "+(code || "FOOBARBAZ"));
+ }
+
+ }).bind(this));
+ } else {
+ this.init(true); //reset state, keep auth data
+ this.client.send("250 2.0.0 Ok: queued as FOOBARBAZ");
+ }
+};
+
diff --git a/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/starttls.js b/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/starttls.js
new file mode 100644
index 0000000..836f0f6
--- /dev/null
+++ b/tools/node_modules/nodemailer/node_modules/simplesmtp/lib/starttls.js
@@ -0,0 +1,112 @@
+// SOURCE: https://gist.github.com/848444
+
+// Target API:
+//
+// var s = require('net').createStream(25, 'smtp.example.com');
+// s.on('connect', function() {
+// require('starttls')(s, options, function() {
+// if (!s.authorized) {
+// s.destroy();
+// return;
+// }
+//
+// s.end("hello world\n");
+// });
+// });
+//
+//
+
+/**
+ * @namespace Client STARTTLS module
+ * @name starttls
+ */
+module.exports.starttls = starttls;
+
+/**
+ * <p>Upgrades a socket to a secure TLS connection</p>
+ *
+ * @memberOf starttls
+ * @param {Object} socket Plaintext socket to be upgraded
+ * @param {Function} callback Callback function to be run after upgrade
+ */
+function starttls(socket, callback) {
+ var sslcontext, pair, cleartext;
+
+ socket.removeAllListeners("data");
+ sslcontext = require('crypto').createCredentials();
+ pair = require('tls').createSecurePair(sslcontext, false);
+ cleartext = pipe(pair, socket);
+
+ pair.on('secure', function() {
+ var verifyError = (pair._ssl || pair.ssl).verifyError();
+
+ if (verifyError) {
+ cleartext.authorized = false;
+ cleartext.authorizationError = verifyError;
+ } else {
+ cleartext.authorized = true;
+ }
+
+ callback(cleartext);
+ });
+
+ cleartext._controlReleased = true;
+ return pair;
+}
+
+function forwardEvents(events, emitterSource, emitterDestination) {
+ var map = [], name, handler;
+
+ for(var i = 0, len = events.length; i < len; i++) {
+ name = events[i];
+
+ handler = forwardEvent.bind(emitterDestination, name);
+
+ map.push(name);
+ emitterSource.on(name, handler);
+ }
+
+ return map;
+}
+
+function forwardEvent() {
+ this.emit.apply(this, arguments);
+}
+
+function removeEvents(map, emitterSource) {
+ for(var i = 0, len = map.length; i < len; i++){
+ emitterSource.removeAllListeners(map[i]);
+ }
+}
+
+function pipe(pair, socket) {
+ pair.encrypted.pipe(socket);
+ socket.pipe(pair.encrypted);
+
+ pair.fd = socket.fd;
+
+ var cleartext = pair.cleartext;
+
+ cleartext.socket = socket;
+ cleartext.encrypted = pair.encrypted;
+ cleartext.authorized = false;
+
+ function onerror(e) {
+ if (cleartext._controlReleased) {
+ cleartext.emit('error', e);
+ }
+ }
+
+ var map = forwardEvents(["timeout", "end", "close", "drain", "error"], socket, cleartext);
+
+ function onclose() {
+ socket.removeListener('error', onerror);
+ socket.removeListener('close', onclose);
+ removeEvents(map,socket);
+ }
+
+ socket.on('error', onerror);
+ socket.on('close', onclose);
+
+ return cleartext;
+} \ No newline at end of file