diff options
Diffstat (limited to 'tools/node_modules/nodemailer/node_modules/simplesmtp/lib')
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:<address></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:<address></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:<address>" + */ +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:<address>" + */ +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 |