From e6930274e25ad85c43da3e26096e6732ab08dc46 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Sat, 1 Jan 2022 19:11:01 +0000 Subject: [PATCH] Bump email and include poplib to avoid example warning. --- social/email/61-email.js | 2 +- social/email/package.json | 6 +- social/email/poplib.js | 867 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 870 insertions(+), 5 deletions(-) create mode 100644 social/email/poplib.js diff --git a/social/email/61-email.js b/social/email/61-email.js index 92845323..50ee0d92 100644 --- a/social/email/61-email.js +++ b/social/email/61-email.js @@ -14,7 +14,7 @@ module.exports = function(RED) { "use strict"; var util = require("util"); var Imap = require('imap'); - var POP3Client = require("poplib"); + var POP3Client = require("./poplib.js"); var nodemailer = require("nodemailer"); var simpleParser = require("mailparser").simpleParser; var SMTPServer = require("smtp-server").SMTPServer; diff --git a/social/email/package.json b/social/email/package.json index 1a478395..6467bd34 100644 --- a/social/email/package.json +++ b/social/email/package.json @@ -1,17 +1,15 @@ { "name": "node-red-node-email", - "version": "1.13.0", + "version": "1.14.0", "description": "Node-RED nodes to send and receive simple emails.", "dependencies": { "imap": "^0.8.19", - "poplib": "^0.1.7", "mailparser": "^3.4.0", - "nodemailer": "^6.7.0", + "nodemailer": "^6.7.2", "smtp-server": "^3.9.0" }, "bundledDependencies": [ "imap", - "poplib", "mailparser", "nodemailer", "smtp-server" diff --git a/social/email/poplib.js b/social/email/poplib.js new file mode 100644 index 00000000..d7d4e4e8 --- /dev/null +++ b/social/email/poplib.js @@ -0,0 +1,867 @@ +/* + + Node.js POP3 client library + + Copyright (C) 2011-2013 by Ditesh Shashikant Gathani + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +var net = require("net"), + tls = require("tls"), + util = require("util"), + crypto = require("crypto"), + events = require("events"); + +// Constructor +function POP3Client(port, host, options) { + + if (options === undefined) options = {}; + + // Optional constructor arguments + var enabletls = options.enabletls !== undefined ? options.enabletls: false; + var ignoretlserrs = options.ignoretlserrs !== undefined ? options.ignoretlserrs: false; + var debug = options.debug || false; + + var tlsDirectOpts = options.tlsopts !== undefined ? options.tlsopts: {}; + + // Private variables follow + var self = this; + var response = null; + var checkResp = true; + var bufferedData = ""; + var state = 0; + var locked = false; + var multiline = false; + var socket = null; + var tlssock = null; + var callback = function(resp, data) { + + if (resp === false) { + + locked = false; + callback = function() {}; + self.emit("connect", false, data); + + } else { + + // Checking for APOP support + var banner = data.trim(); + var bannerComponents = banner.split(" "); + + for(var i=0; i < bannerComponents.length; i++) { + + if (bannerComponents[i].indexOf("@") > 0) { + + self.data["apop"] = true; + self.data["apop-timestamp"] = bannerComponents[i]; + break; + + } + } + + state = 1; + self.data["banner"] = banner; + self.emit("connect", true, data); + + } + }; + + // Public variables follow + this.data = { + + host: host, + port: port, + banner: "", + stls: false, + apop: false, + username: "", + tls: enabletls, + ignoretlserrs: ignoretlserrs + + }; + + // Privileged methods follow + this.setCallback = function(cb) { callback = cb; }; + this.getCallback = function() { return callback }; + this.setState = function(val) { state = val; }; + this.getState = function() { return state; }; + this.setLocked = function(val) { locked = val; }; + this.getLocked = function() { return locked; }; + this.setMultiline = function(val) { multiline = val; }; + this.getMultiline = function() { return multiline; }; + + // Writes to remote server socket + this.write = function(command, argument) { + + var text = command; + + if (argument !== undefined) text = text + " " + argument + "\r\n"; + else text = text + "\r\n"; + + if (debug) console.log("Client: " + util.inspect(text)); + + socket.write(text); + + }; + + // Kills the socket connection + this.end = function() { + socket.end(); + }; + + // Upgrades a standard unencrypted TCP connection to use TLS + // Liberally copied and modified from https://gist.github.com/848444 + // starttls() should be a private function, but I can't figure out + // how to get a public prototypal method (stls) to talk to private method (starttls) + // which references private variables without going through a privileged method + this.starttls = function(options) { + + var s = socket; + s.removeAllListeners("end"); + s.removeAllListeners("data"); + s.removeAllListeners("error"); + socket=null; + + var sslcontext = require('crypto').createCredentials(options); + var pair = tls.createSecurePair(sslcontext, false); + var cleartext = pipe(pair); + + pair.on('secure', function() { + + var verifyError = pair.ssl.verifyError(); + cleartext.authorized = true; + + if (verifyError) { + + cleartext.authorized = false; + cleartext.authorizationError = verifyError; + + } + + cleartext.on("data", onData); + cleartext.on("error", onError); + cleartext.on("end", onEnd); + socket=cleartext; + (self.getCallback())(cleartext.authorized, cleartext.authorizationError); + + }); + + cleartext._controlReleased = true; + + function pipe(pair) { + + pair.encrypted.pipe(s); + s.pipe(pair.encrypted); + + pair.fd = s.fd; + var cleartext = pair.cleartext; + cleartext.socket = s; + cleartext.encrypted = pair.encrypted; + cleartext.authorized = false; + + function onerror(e) { + if (cleartext._controlReleased) cleartext.emit('error', e); + } + + function onclose() { + s.removeListener('error', onerror); + s.removeListener('close', onclose); + } + + s.on('error', onerror); + s.on('close', onclose); + return cleartext; + + } + }; + + // Private methods follow + // Event handlers follow + function onData(data) { + + data = data.toString("ascii"); + bufferedData += data; + + if (debug) console.log("Server: " + util.inspect(data)); + + if (checkResp === true) { + + if (bufferedData.substr(0, 3) === "+OK") { + + checkResp = false; + response = true; + + } else if (bufferedData.substr(0, 4) === "-ERR") { + + checkResp = false; + response = false; + + // The following is only used for SASL + } else if (multiline === false) { + + checkResp = false; + response = true; + + } + } + + if (checkResp === false) { + + if (multiline === true && (response === false || bufferedData.substr(bufferedData.length-5) === "\r\n.\r\n")) { + + // Make a copy to avoid race conditions + var responseCopy = response; + var bufferedDataCopy = bufferedData; + + response = null; + checkResp = true; + multiline = false; + bufferedData = ""; + + callback(responseCopy, bufferedDataCopy); + + } else if (multiline === false) { + + // Make a copy to avoid race conditions + var responseCopy = response; + var bufferedDataCopy = bufferedData; + + response = null; + checkResp = true; + multiline = false; + bufferedData = ""; + + callback(responseCopy, bufferedDataCopy); + + } + } + } + + function onError(err) { + + if (err.errno === 111) self.emit("connect", false, err); + else self.emit("error", err); + + } + + function onEnd(data) { + self.setState(0); + socket = null; + } + + function onClose() { + self.emit("close"); + } + + // Constructor code follows + // Set up EventEmitter constructor function + events.EventEmitter.call(this); + + // Remote end socket + if (enabletls === true) { + + tlssock = tls.connect({ + host: host, + port: port, + rejectUnauthorized: !self.data.ignoretlserrs + }, function() { + + if (tlssock.authorized === false && + self.data["ignoretlserrs"] === false) + self.emit("tls-error", tlssock.authorizationError); + + } + ); + + socket = tlssock; + + } else socket = new net.createConnection(port, host); + + // Set up event handlers + socket.on("data", onData); + socket.on("error", onError); + socket.on("end", onEnd); + socket.on("close", onClose); + +} + +util.inherits(POP3Client, events.EventEmitter); + +POP3Client.prototype.login = function (username, password) { + + var self = this; + + if (self.getState() !== 1) self.emit("invalid-state", "login"); + else if (self.getLocked() === true) self.emit("locked", "login"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + if (resp === false) { + + self.setLocked(false); + self.setCallback(function() {}); + self.emit("login", false, data); + + } else { + + self.setCallback(function(resp, data) { + + self.setLocked(false); + self.setCallback(function() {}); + + if (resp !== false) self.setState(2); + self.emit("login", resp, data); + + }); + + self.setMultiline(false); + self.write("PASS", password); + + } + }); + + self.setMultiline(false); + self.write("USER", username); + + } +}; + +// SASL AUTH implementation +// Currently supports SASL PLAIN and CRAM-MD5 +POP3Client.prototype.auth = function (type, username, password) { + + type = type.toUpperCase(); + var self = this; + var types = {"PLAIN": 1, "CRAM-MD5": 1}; + var initialresp = ""; + + if (self.getState() !== 1) self.emit("invalid-state", "auth"); + else if (self.getLocked() === true) self.emit("locked", "auth"); + + if ((type in types) === false) { + + self.emit("auth", false, "Invalid auth type", null); + return; + + } + + function tlsok() { + + if (type === "PLAIN") { + + initialresp = " " + new Buffer(username + "\u0000" + username + "\u0000" + password).toString("base64") + "="; + self.setCallback(function(resp, data) { + + if (resp !== false) self.setState(2); + self.emit("auth", resp, data, data); + + }); + + } else if (type === "CRAM-MD5") { + + self.setCallback(function(resp, data) { + + if (resp === false) self.emit("auth", resp, "Server responded -ERR to AUTH CRAM-MD5", data); + else { + + var challenge = new Buffer(data.trim().substr(2), "base64").toString(); + var hmac = crypto.createHmac("md5", password); + var response = new Buffer(username + " " + hmac.update(challenge).digest("hex")).toString("base64"); + + self.setCallback(function(resp, data) { + + var errmsg = null; + + if (resp !== false) self.setState(2); + else errmsg = "Server responded -ERR to response"; + + self.emit("auth", resp, null, data); + + }); + + self.write(response); + + } + }); + } + + self.write("AUTH " + type + initialresp); + + } + + if (self.data["tls"] === false && self.data["stls"] === false) { + + // Remove all existing STLS listeners + self.removeAllListeners("stls"); + + self.on("stls", function(resp, rawdata) { + + if (resp === false) { + + // We (optionally) ignore self signed cert errors, + // in blatant violation of RFC 2595, Section 2.4 + if (self.data["ignoretlserrs"] === true && rawdata === "DEPTH_ZERO_SELF_SIGNED_CERT") tlsok(); + else self.emit("auth", false, "Unable to upgrade connection to STLS", rawdata); + + } else tlsok(); + + }); + + self.stls(); + + } else tlsok(); +}; + +POP3Client.prototype.apop = function (username, password) { + + var self = this; + + if (self.getState() !== 1) self.emit("invalid-state", "apop"); + else if (self.getLocked() === true) self.emit("locked", "apop"); + else if (self.data["apop"] === false) self.emit("apop", false, "APOP support not detected on remote server"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + self.setLocked(false); + self.setCallback(function() {}); + + if (resp === true) self.setState(2); + self.emit("apop", resp, data); + + }); + + self.setMultiline(false); + self.write("APOP", username + " " + crypto.createHash("md5").update(self.data["apop-timestamp"] + password).digest("hex")); + + } +}; + +POP3Client.prototype.stls = function() { + + var self = this; + + if (self.getState() !== 1) self.emit("invalid-state", "stls"); + else if (self.getLocked() === true) self.emit("locked", "stls"); + else if (self.data["tls"] === true) self.emit("stls", false, "Unable to execute STLS as TLS connection already established"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + self.setLocked(false); + self.setCallback(function() {}); + + if (resp === true) { + + self.setCallback(function(resp, data) { + + if (resp === false && self.data["ignoretlserrs"] === true && data === "DEPTH_ZERO_SELF_SIGNED_CERT") + resp = true; + + self.data["stls"] = true; + self.emit("stls", resp, data); + + }); + + self.starttls(); + + } else self.emit("stls", false, data); + }); + + self.setMultiline(false); + self.write("STLS"); + + } +}; + + +POP3Client.prototype.top = function(msgnumber, lines) { + + var self = this; + + if (self.getState() !== 2) self.emit("invalid-state", "top"); + else if (self.getLocked() === true) self.emit("locked", "top"); + else { + + self.setCallback(function(resp, data) { + + var returnValue = null; + self.setLocked(false); + self.setCallback(function() {}); + + if (resp !== false) { + + returnValue = ""; + var startOffset = data.indexOf("\r\n", 0) + 2; + var endOffset = data.indexOf("\r\n.\r\n", 0) + 2; + + if (endOffset > startOffset) + returnValue = data.substr(startOffset, endOffset-startOffset); + + } + + self.emit("top", resp, msgnumber, returnValue, data); + + }); + + self.setMultiline(true); + self.write("TOP", msgnumber + " " + lines); + + } +}; + +POP3Client.prototype.list = function(msgnumber) { + + var self = this; + + if (self.getState() !== 2) self.emit("invalid-state", "list"); + else if (self.getLocked() === true) self.emit("locked", "list"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + var returnValue = null; + var msgcount = 0; + self.setLocked(false); + self.setCallback(function() {}); + + if (resp !== false) { + + returnValue = []; + + if (msgnumber !== undefined) { + + msgcount = 1 + listitem = data.split(" "); + returnValue[listitem[1]] = listitem[2]; + + } else { + + var offset = 0; + var listitem = ""; + var newoffset = 0; + var returnValue = []; + var startOffset = data.indexOf("\r\n", 0) + 2; + var endOffset = data.indexOf("\r\n.\r\n", 0) + 2; + + if (endOffset > startOffset) { + + data = data.substr(startOffset, endOffset-startOffset); + + while(true) { + + if (offset > endOffset) + break; + + newoffset = data.indexOf("\r\n", offset); + + if (newoffset < 0) + break; + + msgcount++; + listitem = data.substr(offset, newoffset-offset); + listitem = listitem.split(" "); + returnValue[listitem[0]] = listitem[1]; + offset = newoffset + 2; + + } + } + } + } + + self.emit("list", resp, msgcount, msgnumber, returnValue, data); + + }); + + if (msgnumber !== undefined) self.setMultiline(false); + else self.setMultiline(true); + + self.write("LIST", msgnumber); + + } +}; + +POP3Client.prototype.stat = function() { + + var self = this; + + if (self.getState() !== 2) self.emit("invalid-state", "stat"); + else if (self.getLocked() === true) self.emit("locked", "stat"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + var returnValue = null; + self.setLocked(false); + self.setCallback(function() {}); + + if (resp !== false) { + + listitem = data.split(" "); + returnValue = { + + "count": listitem[1].trim(), + "octets": listitem[2].trim(), + + }; + } + + self.emit("stat", resp, returnValue, data); + + }); + + self.setMultiline(false); + self.write("STAT", undefined); + + } +}; + +POP3Client.prototype.uidl = function(msgnumber) { + + var self = this; + + if (self.getState() !== 2) self.emit("invalid-state", "uidl"); + else if (self.getLocked() === true) self.emit("locked", "uidl"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + var returnValue = null; + self.setLocked(false); + self.setCallback(function() {}); + + if (resp !== false) { + + returnValue = []; + + if (msgnumber !== undefined) { + + listitem = data.split(" "); + returnValue[listitem[1]] = listitem[2].trim(); + + } else { + + var offset = 0; + var listitem = ""; + var newoffset = 0; + var returnValue = []; + var startOffset = data.indexOf("\r\n", 0) + 2; + var endOffset = data.indexOf("\r\n.\r\n", 0) + 2; + + if (endOffset > startOffset) { + + data = data.substr(startOffset, endOffset-startOffset); + endOffset -= startOffset; + + while (offset < endOffset) { + + newoffset = data.indexOf("\r\n", offset); + listitem = data.substr(offset, newoffset-offset); + listitem = listitem.split(" "); + returnValue[listitem[0]] = listitem[1]; + offset = newoffset + 2; + + } + } + } + } + + self.emit("uidl", resp, msgnumber, returnValue, data); + + }); + + if (msgnumber !== undefined) self.setMultiline(false); + else self.setMultiline(true); + + self.write("UIDL", msgnumber); + + } +}; + +POP3Client.prototype.retr = function(msgnumber) { + + var self = this; + + if (self.getState() !== 2) self.emit("invalid-state", "retr"); + else if (self.getLocked() === true) self.emit("locked", "retr"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + var returnValue = null; + self.setLocked(false); + self.setCallback(function() {}); + + if (resp !== false) { + + var startOffset = data.indexOf("\r\n", 0) + 2; + var endOffset = data.indexOf("\r\n.\r\n", 0); + returnValue = data.substr(startOffset, endOffset-startOffset); + + } + + self.emit("retr", resp, msgnumber, returnValue, data); + + }); + + self.setMultiline(true); + self.write("RETR", msgnumber); + + } +}; + +POP3Client.prototype.dele = function(msgnumber) { + + var self = this; + + if (self.getState() !== 2) self.emit("invalid-state", "dele"); + else if (self.getLocked() === true) self.emit("locked", "dele"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + self.setLocked(false); + self.setCallback(function() {}); + self.emit("dele", resp, msgnumber, data); + + }); + + self.setMultiline(false); + self.write("DELE", msgnumber); + + } +}; + +POP3Client.prototype.noop = function() { + + var self = this; + + if (self.getState() !== 2) self.emit("invalid-state", "noop"); + else if (self.getLocked() === true) self.emit("locked", "noop"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + self.setLocked(false); + self.setCallback(function() {}); + self.emit("noop", resp, data); + + }); + + self.setMultiline(false); + self.write("NOOP", undefined); + + } +}; + +POP3Client.prototype.rset = function() { + + var self = this; + + if (self.getState() !== 2) self.emit("invalid-state", "rset"); + else if (self.getLocked() === true) self.emit("locked", "rset"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + self.setLocked(false); + self.setCallback(function() {}); + self.emit("rset", resp, data); + + }); + + self.setMultiline(false); + self.write("RSET", undefined); + + } +}; + +POP3Client.prototype.capa = function() { + + var self = this; + + if (self.getState() === 0) self.emit("invalid-state", "quit"); + else if (self.getLocked() === true) self.emit("locked", "capa"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + var returnValue = null; + self.setLocked(false); + self.setCallback(function() {}); + + if (resp === true) { + + var startOffset = data.indexOf("\r\n", 0) + 2; + var endOffset = data.indexOf("\r\n.\r\n", 0); + returnValue = data.substr(startOffset, endOffset-startOffset); + returnValue = returnValue.split("\r\n"); + + } + + self.emit("capa", resp, returnValue, data); + + }); + + self.setMultiline(true); + self.write("CAPA", undefined); + + } +}; + +POP3Client.prototype.quit = function() { + + var self = this; + + if (self.getState() === 0) self.emit("invalid-state", "quit"); + else if (self.getLocked() === true) self.emit("locked", "quit"); + else { + + self.setLocked(true); + self.setCallback(function(resp, data) { + + self.setLocked(false); + self.setCallback(function() {}); + + self.end(); + self.emit("quit", resp, data); + + }); + + self.setMultiline(false); + self.write("QUIT", undefined); + + } +}; + +module.exports = POP3Client; \ No newline at end of file