2020-10-19 13:24:18 +02:00
|
|
|
/* eslint-disable indent */
|
2015-06-13 19:46:07 +02:00
|
|
|
|
2022-07-19 13:59:41 +02:00
|
|
|
const { domainToUnicode } = require("url");
|
|
|
|
|
2016-04-20 20:47:23 +02:00
|
|
|
/**
|
|
|
|
* POP3 protocol - RFC1939 - https://www.ietf.org/rfc/rfc1939.txt
|
2016-04-20 20:54:44 +02:00
|
|
|
*
|
2016-04-20 20:47:23 +02:00
|
|
|
* Dependencies:
|
2023-03-27 21:27:47 +02:00
|
|
|
* * node-pop3 - https://www.npmjs.com/package/node-pop3
|
2016-04-20 20:47:23 +02:00
|
|
|
* * nodemailer - https://www.npmjs.com/package/nodemailer
|
|
|
|
* * imap - https://www.npmjs.com/package/imap
|
|
|
|
* * mailparser - https://www.npmjs.com/package/mailparser
|
|
|
|
*/
|
|
|
|
|
2015-06-13 19:46:07 +02:00
|
|
|
module.exports = function(RED) {
|
|
|
|
"use strict";
|
2020-10-19 13:24:18 +02:00
|
|
|
var util = require("util");
|
2016-04-20 20:54:44 +02:00
|
|
|
var Imap = require('imap');
|
2023-03-27 21:27:47 +02:00
|
|
|
var Pop3Command = require("node-pop3");
|
2020-10-19 13:24:18 +02:00
|
|
|
var nodemailer = require("nodemailer");
|
|
|
|
var simpleParser = require("mailparser").simpleParser;
|
|
|
|
var SMTPServer = require("smtp-server").SMTPServer;
|
|
|
|
//var microMTA = require("micromta").microMTA;
|
2022-06-10 11:14:38 +02:00
|
|
|
var fs = require('fs');
|
2015-06-13 19:46:07 +02:00
|
|
|
|
2018-09-04 19:51:20 +02:00
|
|
|
if (parseInt(process.version.split("v")[1].split(".")[0]) < 8) {
|
|
|
|
throw "Error : Requires nodejs version >= 8.";
|
|
|
|
}
|
|
|
|
|
2015-06-13 19:46:07 +02:00
|
|
|
try {
|
|
|
|
var globalkeys = RED.settings.email || require(process.env.NODE_RED_HOME+"/../emailkeys.js");
|
2017-01-29 18:45:44 +01:00
|
|
|
}
|
|
|
|
catch(err) {
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function EmailNode(n) {
|
|
|
|
RED.nodes.createNode(this,n);
|
|
|
|
this.topic = n.topic;
|
|
|
|
this.name = n.name;
|
|
|
|
this.outserver = n.server;
|
|
|
|
this.outport = n.port;
|
2017-02-13 23:43:43 +01:00
|
|
|
this.secure = n.secure;
|
2018-08-22 14:57:42 +02:00
|
|
|
this.tls = true;
|
2015-06-13 19:46:07 +02:00
|
|
|
var flag = false;
|
2023-03-27 21:27:47 +02:00
|
|
|
this.authtype = n.authtype || "BASIC";
|
|
|
|
if (this.authtype !== "BASIC") {
|
|
|
|
this.inputs = 1;
|
|
|
|
this.repeat = 0;
|
|
|
|
}
|
2015-06-13 19:46:07 +02:00
|
|
|
if (this.credentials && this.credentials.hasOwnProperty("userid")) {
|
|
|
|
this.userid = this.credentials.userid;
|
|
|
|
} else {
|
|
|
|
if (globalkeys) {
|
|
|
|
this.userid = globalkeys.user;
|
|
|
|
flag = true;
|
2023-03-27 21:27:47 +02:00
|
|
|
} else {
|
|
|
|
this.error(RED._("email.errors.nouserid"));
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
|
|
|
}
|
2023-03-27 21:34:46 +02:00
|
|
|
if (this.authtype === "BASIC" ) {
|
2023-03-27 21:27:47 +02:00
|
|
|
if (this.credentials && this.credentials.hasOwnProperty("password")) {
|
|
|
|
this.password = this.credentials.password;
|
|
|
|
} else {
|
|
|
|
if (globalkeys) {
|
|
|
|
this.password = globalkeys.pass;
|
|
|
|
flag = true;
|
|
|
|
} else {
|
|
|
|
this.error(RED._("email.errors.nopassword"));
|
|
|
|
}
|
|
|
|
}
|
2015-06-13 19:46:07 +02:00
|
|
|
} else {
|
2023-03-27 21:27:47 +02:00
|
|
|
this.saslformat = n.saslformat;
|
|
|
|
if(n.token!=="") {
|
|
|
|
this.token = n.token;
|
|
|
|
} else {
|
|
|
|
this.error(RED._("email.errors.notoken"));
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (flag) {
|
|
|
|
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
|
|
|
|
}
|
2021-05-11 10:22:09 +02:00
|
|
|
if (n.tls === false) { this.tls = false; }
|
2015-06-13 19:46:07 +02:00
|
|
|
var node = this;
|
|
|
|
|
2017-02-21 14:00:21 +01:00
|
|
|
var smtpOptions = {
|
2015-06-13 19:46:07 +02:00
|
|
|
host: node.outserver,
|
|
|
|
port: node.outport,
|
2018-08-22 14:57:42 +02:00
|
|
|
secure: node.secure,
|
|
|
|
tls: {rejectUnauthorized: node.tls}
|
2017-02-21 14:00:21 +01:00
|
|
|
}
|
2023-03-27 21:34:46 +02:00
|
|
|
|
|
|
|
if (node.authtype === "BASIC" ) {
|
2017-02-21 14:00:21 +01:00
|
|
|
smtpOptions.auth = {
|
2015-06-13 19:46:07 +02:00
|
|
|
user: node.userid,
|
|
|
|
pass: node.password
|
2017-02-15 19:33:05 +01:00
|
|
|
};
|
2023-03-27 21:27:47 +02:00
|
|
|
} else if(node.authtype == "XOAUTH2") {
|
|
|
|
var value = RED.util.getMessageProperty(msg,node.token);
|
|
|
|
if (value !== undefined) {
|
|
|
|
if(node.saslformat) {
|
|
|
|
//Make base64 string for access - compatible with outlook365 and gmail
|
|
|
|
saslxoauth2 = Buffer.from("user="+node.userid+"\x01auth=Bearer "+value+"\x01\x01").toString('base64');
|
|
|
|
} else {
|
|
|
|
saslxoauth2 = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
smtpOptions.auth = {
|
|
|
|
type: "OAuth2",
|
|
|
|
user: node.userid,
|
|
|
|
accessToken: saslxoauth2
|
|
|
|
};
|
2017-02-15 19:33:05 +01:00
|
|
|
}
|
2017-02-21 14:00:21 +01:00
|
|
|
var smtpTransport = nodemailer.createTransport(smtpOptions);
|
2015-06-13 19:46:07 +02:00
|
|
|
|
2019-09-24 22:44:56 +02:00
|
|
|
this.on("input", function(msg, send, done) {
|
2015-06-13 19:46:07 +02:00
|
|
|
if (msg.hasOwnProperty("payload")) {
|
2019-09-26 14:39:25 +02:00
|
|
|
send = send || function() { node.send.apply(node,arguments) };
|
2015-06-13 19:46:07 +02:00
|
|
|
if (smtpTransport) {
|
2015-07-07 22:31:28 +02:00
|
|
|
node.status({fill:"blue",shape:"dot",text:"email.status.sending"});
|
2015-06-13 19:46:07 +02:00
|
|
|
if (msg.to && node.name && (msg.to !== node.name)) {
|
2015-06-16 12:16:29 +02:00
|
|
|
node.warn(RED._("node-red:common.errors.nooverride"));
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
2016-08-03 09:55:39 +02:00
|
|
|
var sendopts = { from: ((msg.from) ? msg.from : node.userid) }; // sender address
|
2015-06-13 19:46:07 +02:00
|
|
|
sendopts.to = node.name || msg.to; // comma separated list of addressees
|
2016-06-03 17:14:20 +02:00
|
|
|
if (node.name === "") {
|
|
|
|
sendopts.cc = msg.cc;
|
|
|
|
sendopts.bcc = msg.bcc;
|
2019-05-21 20:30:32 +02:00
|
|
|
sendopts.inReplyTo = msg.inReplyTo;
|
|
|
|
sendopts.replyTo = msg.replyTo;
|
|
|
|
sendopts.references = msg.references;
|
2020-10-26 10:08:10 +01:00
|
|
|
sendopts.headers = msg.headers;
|
|
|
|
sendopts.priority = msg.priority;
|
2016-06-03 17:14:20 +02:00
|
|
|
}
|
2022-11-08 14:52:37 +01:00
|
|
|
if (msg.hasOwnProperty("topic") && msg.topic === '') { sendopts.subject = ""; }
|
|
|
|
else { sendopts.subject = msg.topic || msg.title || "Message from Node-RED"; } // subject line
|
2022-03-19 11:31:40 +01:00
|
|
|
if (msg.hasOwnProperty("header") && msg.header.hasOwnProperty("message-id")) {
|
|
|
|
sendopts.inReplyTo = msg.header["message-id"];
|
|
|
|
sendopts.subject = "Re: " + sendopts.subject;
|
|
|
|
}
|
2016-05-19 11:13:47 +02:00
|
|
|
if (msg.hasOwnProperty("envelope")) { sendopts.envelope = msg.envelope; }
|
2015-06-13 19:46:07 +02:00
|
|
|
if (Buffer.isBuffer(msg.payload)) { // if it's a buffer in the payload then auto create an attachment instead
|
|
|
|
if (!msg.filename) {
|
|
|
|
var fe = "bin";
|
|
|
|
if ((msg.payload[0] === 0xFF)&&(msg.payload[1] === 0xD8)) { fe = "jpg"; }
|
|
|
|
if ((msg.payload[0] === 0x47)&&(msg.payload[1] === 0x49)) { fe = "gif"; } //46
|
|
|
|
if ((msg.payload[0] === 0x42)&&(msg.payload[1] === 0x4D)) { fe = "bmp"; }
|
|
|
|
if ((msg.payload[0] === 0x89)&&(msg.payload[1] === 0x50)) { fe = "png"; } //4E
|
|
|
|
msg.filename = "attachment."+fe;
|
|
|
|
}
|
2020-01-08 10:13:05 +01:00
|
|
|
var fname = msg.filename.replace(/^.*[\\\/]/, '') || "attachment.bin";
|
2017-04-03 19:02:17 +02:00
|
|
|
sendopts.attachments = [ { content:msg.payload, filename:fname } ];
|
2015-06-13 19:46:07 +02:00
|
|
|
if (msg.hasOwnProperty("headers") && msg.headers.hasOwnProperty("content-type")) {
|
|
|
|
sendopts.attachments[0].contentType = msg.headers["content-type"];
|
|
|
|
}
|
|
|
|
// Create some body text..
|
2022-11-08 14:52:37 +01:00
|
|
|
if (msg.hasOwnProperty("description")) { sendopts.text = msg.description; }
|
|
|
|
else { sendopts.text = RED._("email.default-message",{filename:fname}); }
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
var payload = RED.util.ensureString(msg.payload);
|
|
|
|
sendopts.text = payload; // plaintext body
|
2021-04-01 10:36:36 +02:00
|
|
|
if (/<[a-z][\s\S]*>/i.test(payload)) {
|
2021-03-31 21:51:43 +02:00
|
|
|
sendopts.html = payload; // html body
|
|
|
|
if (msg.hasOwnProperty("plaintext")) {
|
|
|
|
var plaintext = RED.util.ensureString(msg.plaintext);
|
|
|
|
sendopts.text = plaintext; // plaintext body - specific plaintext version
|
|
|
|
}
|
|
|
|
}
|
2021-04-01 10:36:36 +02:00
|
|
|
if (msg.attachments) {
|
|
|
|
if (!Array.isArray(msg.attachments)) { sendopts.attachments = [ msg.attachments ]; }
|
|
|
|
else { sendopts.attachments = msg.attachments; }
|
2021-01-12 13:41:41 +01:00
|
|
|
for (var a=0; a < sendopts.attachments.length; a++) {
|
|
|
|
if (sendopts.attachments[a].hasOwnProperty("content")) {
|
|
|
|
if (typeof sendopts.attachments[a].content !== "string" && !Buffer.isBuffer(sendopts.attachments[a].content)) {
|
|
|
|
node.error(RED._("email.errors.invalidattachment"),msg);
|
|
|
|
node.status({fill:"red",shape:"ring",text:"email.status.sendfail"});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
|
|
|
smtpTransport.sendMail(sendopts, function(error, info) {
|
|
|
|
if (error) {
|
|
|
|
node.error(error,msg);
|
2019-04-10 10:01:08 +02:00
|
|
|
node.status({fill:"red",shape:"ring",text:"email.status.sendfail",response:error.response,msg:{to:msg.to,topic:msg.topic,id:msg._msgid}});
|
2015-06-13 19:46:07 +02:00
|
|
|
} else {
|
2015-06-16 11:36:19 +02:00
|
|
|
node.log(RED._("email.status.messagesent",{response:info.response}));
|
2019-04-10 10:01:08 +02:00
|
|
|
node.status({text:"",response:info.response,msg:{to:msg.to,topic:msg.topic,id:msg._msgid}});
|
2019-09-24 22:44:56 +02:00
|
|
|
if (done) { done(); }
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2017-02-15 19:33:05 +01:00
|
|
|
else { node.warn(RED._("email.errors.nosmtptransport")); }
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
2015-06-16 11:36:19 +02:00
|
|
|
else { node.warn(RED._("email.errors.nopayload")); }
|
2015-06-13 19:46:07 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
RED.nodes.registerType("e-mail",EmailNode,{
|
|
|
|
credentials: {
|
|
|
|
userid: {type:"text"},
|
|
|
|
password: {type: "password"},
|
|
|
|
global: { type:"boolean"}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-04-20 20:54:44 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// EmailInNode
|
|
|
|
//
|
|
|
|
// Setup the EmailInNode
|
2015-06-13 19:46:07 +02:00
|
|
|
function EmailInNode(n) {
|
2016-04-20 20:54:44 +02:00
|
|
|
var imap;
|
2023-03-27 21:27:47 +02:00
|
|
|
var pop3;
|
2016-04-20 20:54:44 +02:00
|
|
|
|
2015-06-13 19:46:07 +02:00
|
|
|
RED.nodes.createNode(this,n);
|
2016-04-20 20:54:44 +02:00
|
|
|
this.name = n.name;
|
2019-01-31 22:00:31 +01:00
|
|
|
this.inputs = n.inputs;
|
2016-04-20 20:54:44 +02:00
|
|
|
this.repeat = n.repeat * 1000 || 300000;
|
2018-04-16 14:35:05 +02:00
|
|
|
if (this.repeat > 2147483647) {
|
|
|
|
// setTimeout/Interval has a limit of 2**31-1 Milliseconds
|
|
|
|
this.repeat = 2147483647;
|
|
|
|
this.error(RED._("email.errors.refreshtoolarge"));
|
|
|
|
}
|
2020-01-31 22:46:42 +01:00
|
|
|
if (this.repeat < 1500) {
|
|
|
|
this.repeat = 1500;
|
|
|
|
}
|
2019-01-31 22:00:31 +01:00
|
|
|
if (this.inputs === 1) { this.repeat = 0; }
|
2016-04-20 20:54:44 +02:00
|
|
|
this.inserver = n.server || (globalkeys && globalkeys.server) || "imap.gmail.com";
|
|
|
|
this.inport = n.port || (globalkeys && globalkeys.port) || "993";
|
|
|
|
this.box = n.box || "INBOX";
|
|
|
|
this.useSSL= n.useSSL;
|
2021-04-14 18:28:03 +02:00
|
|
|
this.autotls= n.autotls;
|
2016-04-20 20:54:44 +02:00
|
|
|
this.protocol = n.protocol || "IMAP";
|
2016-04-20 20:47:23 +02:00
|
|
|
this.disposition = n.disposition || "None"; // "None", "Delete", "Read"
|
2019-02-11 20:26:45 +01:00
|
|
|
this.criteria = n.criteria || "UNSEEN"; // "ALL", "ANSWERED", "FLAGGED", "SEEN", "UNANSWERED", "UNFLAGGED", "UNSEEN"
|
2023-03-27 21:27:47 +02:00
|
|
|
this.authtype = n.authtype || "BASIC";
|
|
|
|
if (this.authtype !== "BASIC") {
|
|
|
|
this.inputs = 1;
|
|
|
|
this.repeat = 0;
|
|
|
|
}
|
2016-04-20 20:54:44 +02:00
|
|
|
|
2015-06-13 19:46:07 +02:00
|
|
|
var flag = false;
|
|
|
|
|
|
|
|
if (this.credentials && this.credentials.hasOwnProperty("userid")) {
|
|
|
|
this.userid = this.credentials.userid;
|
|
|
|
} else {
|
|
|
|
if (globalkeys) {
|
|
|
|
this.userid = globalkeys.user;
|
|
|
|
flag = true;
|
|
|
|
} else {
|
2015-06-16 11:36:19 +02:00
|
|
|
this.error(RED._("email.errors.nouserid"));
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
|
|
|
}
|
2023-03-27 21:34:46 +02:00
|
|
|
if (this.authtype === "BASIC" ) {
|
2023-03-27 21:27:47 +02:00
|
|
|
if (this.credentials && this.credentials.hasOwnProperty("password")) {
|
|
|
|
this.password = this.credentials.password;
|
|
|
|
} else {
|
|
|
|
if (globalkeys) {
|
|
|
|
this.password = globalkeys.pass;
|
|
|
|
flag = true;
|
|
|
|
} else {
|
|
|
|
this.error(RED._("email.errors.nopassword"));
|
|
|
|
}
|
|
|
|
}
|
2015-06-13 19:46:07 +02:00
|
|
|
} else {
|
2023-03-27 21:27:47 +02:00
|
|
|
this.saslformat = n.saslformat;
|
|
|
|
if(n.token!=="") {
|
|
|
|
this.token = n.token;
|
2015-06-13 19:46:07 +02:00
|
|
|
} else {
|
2023-03-27 21:27:47 +02:00
|
|
|
this.error(RED._("email.errors.notoken"));
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (flag) {
|
|
|
|
RED.nodes.addCredentials(n.id,{userid:this.userid, password:this.password, global:true});
|
|
|
|
}
|
|
|
|
|
|
|
|
var node = this;
|
2022-07-19 13:59:41 +02:00
|
|
|
node.interval_id = null;
|
2016-04-20 20:54:44 +02:00
|
|
|
|
|
|
|
// Process a new email message by building a Node-RED message to be passed onwards
|
|
|
|
// in the message flow. The parameter called `msg` is the template message we
|
|
|
|
// start with while `mailMessage` is an object returned from `mailparser` that
|
|
|
|
// will be used to populate the email.
|
2016-06-07 16:57:40 +02:00
|
|
|
// DCJ NOTE: - heirachical multipart mime parsers seem to not exist - this one is barely functional.
|
2016-04-20 20:47:23 +02:00
|
|
|
function processNewMessage(msg, mailMessage) {
|
2018-08-30 14:05:39 +02:00
|
|
|
msg = RED.util.cloneMessage(msg); // Clone the message
|
2016-04-20 20:47:23 +02:00
|
|
|
// Populate the msg fields from the content of the email message
|
|
|
|
// that we have just parsed.
|
2016-06-12 18:04:07 +02:00
|
|
|
msg.payload = mailMessage.text;
|
|
|
|
msg.topic = mailMessage.subject;
|
|
|
|
msg.date = mailMessage.date;
|
2019-05-28 17:55:07 +02:00
|
|
|
msg.header = {};
|
|
|
|
mailMessage.headers.forEach((v, k) => {msg.header[k] = v;});
|
2016-12-12 22:10:36 +01:00
|
|
|
if (mailMessage.html) { msg.html = mailMessage.html; }
|
2018-08-30 14:05:39 +02:00
|
|
|
if (mailMessage.to && mailMessage.to.length > 0) { msg.to = mailMessage.to; }
|
|
|
|
if (mailMessage.cc && mailMessage.cc.length > 0) { msg.cc = mailMessage.cc; }
|
|
|
|
if (mailMessage.bcc && mailMessage.bcc.length > 0) { msg.bcc = mailMessage.bcc; }
|
2019-09-24 22:44:56 +02:00
|
|
|
if (mailMessage.from && mailMessage.from.value && mailMessage.from.value.length > 0) { msg.from = mailMessage.from.value[0].address; }
|
2016-12-12 22:10:36 +01:00
|
|
|
if (mailMessage.attachments) { msg.attachments = mailMessage.attachments; }
|
|
|
|
else { msg.attachments = []; }
|
2016-06-12 18:04:07 +02:00
|
|
|
node.send(msg); // Propagate the message down the flow
|
2016-04-20 20:54:44 +02:00
|
|
|
} // End of processNewMessage
|
|
|
|
|
|
|
|
// Check the POP3 email mailbox for any new messages. For any that are found,
|
|
|
|
// retrieve each message, call processNewMessage to process it and then delete
|
|
|
|
// the messages from the server.
|
2023-03-27 21:27:47 +02:00
|
|
|
async function checkPOP3(msg,send,done) {
|
|
|
|
var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
|
|
|
|
var saslxoauth2 = "";
|
|
|
|
var currentMessage = 1;
|
|
|
|
var maxMessage = 0;
|
|
|
|
var nextMessage;
|
2023-03-27 21:34:46 +02:00
|
|
|
|
2023-03-27 21:27:47 +02:00
|
|
|
pop3 = new Pop3Command({
|
|
|
|
"host": node.inserver,
|
|
|
|
"tls": node.useSSL,
|
|
|
|
"timeout": tout,
|
|
|
|
"port": node.inport
|
2016-04-20 20:47:23 +02:00
|
|
|
});
|
2023-03-27 21:27:47 +02:00
|
|
|
try {
|
|
|
|
node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"});
|
|
|
|
await pop3.connect();
|
2023-03-27 21:34:46 +02:00
|
|
|
if (node.authtype == "XOAUTH2") {
|
2023-03-27 21:27:47 +02:00
|
|
|
var value = RED.util.getMessageProperty(msg,node.token);
|
|
|
|
if (value !== undefined) {
|
2023-03-27 21:34:46 +02:00
|
|
|
if (node.saslformat) {
|
2023-03-27 21:27:47 +02:00
|
|
|
//Make base64 string for access - compatible with outlook365 and gmail
|
|
|
|
saslxoauth2 = Buffer.from("user="+node.userid+"\x01auth=Bearer "+value+"\x01\x01").toString('base64');
|
|
|
|
} else {
|
|
|
|
saslxoauth2 = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await pop3.command('AUTH', "XOAUTH2");
|
|
|
|
await pop3.command(saslxoauth2);
|
2016-04-20 20:54:44 +02:00
|
|
|
|
2023-03-27 21:34:46 +02:00
|
|
|
} else if (node.authtype == "BASIC") {
|
2023-03-27 21:27:47 +02:00
|
|
|
await pop3.command('USER', node.userid);
|
|
|
|
await pop3.command('PASS', node.password);
|
|
|
|
}
|
|
|
|
} catch(err) {
|
|
|
|
node.error(err.message,err);
|
|
|
|
node.status({fill:"red",shape:"ring",text:"email.status.connecterror"});
|
2018-09-03 20:56:36 +02:00
|
|
|
setInputRepeatTimeout();
|
2022-07-19 13:59:41 +02:00
|
|
|
done();
|
2023-03-27 21:27:47 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
maxMessage = (await pop3.STAT()).split(" ")[0];
|
2023-03-27 21:34:46 +02:00
|
|
|
if (maxMessage>0) {
|
2023-03-27 21:27:47 +02:00
|
|
|
node.status({fill:"blue", shape:"dot", text:"email.status.fetching"});
|
|
|
|
while(currentMessage<=maxMessage) {
|
|
|
|
try {
|
|
|
|
nextMessage = await pop3.RETR(currentMessage);
|
|
|
|
} catch(err) {
|
|
|
|
node.error(RED._("email.errors.fetchfail", err.message),err);
|
|
|
|
node.status({fill:"red",shape:"ring",text:"email.status.fetcherror"});
|
|
|
|
setInputRepeatTimeout();
|
|
|
|
done();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
// We have now received a new email message. Create an instance of a mail parser
|
|
|
|
// and pass in the email message. The parser will signal when it has parsed the message.
|
|
|
|
simpleParser(nextMessage, {}, function(err, parsed) {
|
|
|
|
//node.log(util.format("simpleParser: on(end): %j", mailObject));
|
|
|
|
if (err) {
|
|
|
|
node.status({fill:"red", shape:"ring", text:"email.status.parseerror"});
|
|
|
|
node.error(RED._("email.errors.parsefail", {folder:node.box}), err);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
processNewMessage(msg, parsed);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
//processNewMessage(msg, nextMessage);
|
|
|
|
} catch(err) {
|
|
|
|
node.error(RED._("email.errors.parsefail", {folder:node.box}), err);
|
|
|
|
node.status({fill:"red",shape:"ring",text:"email.status.parseerror"});
|
|
|
|
setInputRepeatTimeout();
|
|
|
|
done();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
await pop3.DELE(currentMessage);
|
|
|
|
currentMessage++;
|
2016-04-20 20:47:23 +02:00
|
|
|
}
|
2023-03-27 21:27:47 +02:00
|
|
|
await pop3.QUIT();
|
|
|
|
node.status({fill:"green",shape:"dot",text:"finished"});
|
|
|
|
setTimeout(status_clear, 5000);
|
|
|
|
setInputRepeatTimeout();
|
|
|
|
done();
|
|
|
|
}
|
2016-04-20 20:54:44 +02:00
|
|
|
|
|
|
|
} // End of checkPOP3
|
2016-04-20 20:47:23 +02:00
|
|
|
|
|
|
|
|
2016-04-20 20:54:44 +02:00
|
|
|
//
|
|
|
|
// checkIMAP
|
|
|
|
//
|
|
|
|
// Check the email sever using the IMAP protocol for new messages.
|
2019-02-01 01:00:16 +01:00
|
|
|
var s = false;
|
|
|
|
var ss = false;
|
2022-07-19 13:59:41 +02:00
|
|
|
function checkIMAP(msg,send,done) {
|
2023-03-27 21:27:47 +02:00
|
|
|
var tout = (node.repeat > 0) ? node.repeat - 500 : 15000;
|
|
|
|
var saslxoauth2 = "";
|
2023-03-27 21:34:46 +02:00
|
|
|
if (node.authtype == "XOAUTH2") {
|
2023-03-27 21:27:47 +02:00
|
|
|
var value = RED.util.getMessageProperty(msg,node.token);
|
|
|
|
if (value !== undefined) {
|
2023-03-27 21:34:46 +02:00
|
|
|
if (node.saslformat) {
|
2023-03-27 21:27:47 +02:00
|
|
|
//Make base64 string for access - compatible with outlook365 and gmail
|
|
|
|
saslxoauth2 = Buffer.from("user="+node.userid+"\x01auth=Bearer "+value+"\x01\x01").toString('base64');
|
|
|
|
} else {
|
|
|
|
saslxoauth2 = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
imap = new Imap({
|
|
|
|
xoauth2: saslxoauth2,
|
|
|
|
host: node.inserver,
|
|
|
|
port: node.inport,
|
|
|
|
tls: node.useSSL,
|
|
|
|
autotls: node.autotls,
|
|
|
|
tlsOptions: { rejectUnauthorized: false },
|
|
|
|
connTimeout: tout,
|
|
|
|
authTimeout: tout
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
imap = new Imap({
|
|
|
|
user: node.userid,
|
|
|
|
password: node.password,
|
|
|
|
host: node.inserver,
|
|
|
|
port: node.inport,
|
|
|
|
tls: node.useSSL,
|
|
|
|
autotls: node.autotls,
|
|
|
|
tlsOptions: { rejectUnauthorized: false },
|
|
|
|
connTimeout: tout,
|
|
|
|
authTimeout: tout
|
|
|
|
});
|
|
|
|
}
|
|
|
|
imap.on('error', function(err) {
|
|
|
|
if (err.errno !== "ECONNRESET") {
|
|
|
|
s = false;
|
|
|
|
node.error(err.message,err);
|
|
|
|
node.status({fill:"red",shape:"ring",text:"email.status.connecterror"});
|
|
|
|
}
|
|
|
|
setInputRepeatTimeout();
|
|
|
|
});
|
2019-02-01 01:00:16 +01:00
|
|
|
//console.log("Checking IMAP for new messages");
|
2016-04-20 20:54:44 +02:00
|
|
|
// We get back a 'ready' event once we have connected to imap
|
2019-02-01 01:00:16 +01:00
|
|
|
s = true;
|
2016-04-20 20:54:44 +02:00
|
|
|
imap.once("ready", function() {
|
2019-02-01 01:00:16 +01:00
|
|
|
if (ss === true) { return; }
|
|
|
|
ss = true;
|
2016-04-20 20:54:44 +02:00
|
|
|
node.status({fill:"blue", shape:"dot", text:"email.status.fetching"});
|
2016-06-07 16:57:40 +02:00
|
|
|
//console.log("> ready");
|
2020-09-24 00:54:09 +02:00
|
|
|
// Open the folder
|
2016-06-27 13:01:19 +02:00
|
|
|
imap.openBox(node.box, // Mailbox name
|
2016-04-20 20:54:44 +02:00
|
|
|
false, // Open readonly?
|
|
|
|
function(err, box) {
|
2018-10-26 18:00:27 +02:00
|
|
|
//console.log("> Inbox err : %j", err);
|
2016-06-07 16:57:40 +02:00
|
|
|
//console.log("> Inbox open: %j", box);
|
2020-01-31 22:46:42 +01:00
|
|
|
if (err) {
|
2020-09-24 00:54:09 +02:00
|
|
|
var boxs = [];
|
|
|
|
imap.getBoxes(function(err,boxes) {
|
|
|
|
if (err) { return; }
|
|
|
|
for (var prop in boxes) {
|
|
|
|
if (boxes.hasOwnProperty(prop)) {
|
|
|
|
if (boxes[prop].children) {
|
|
|
|
boxs.push(prop+"/{"+Object.keys(boxes[prop].children)+'}');
|
|
|
|
}
|
|
|
|
else { boxs.push(prop); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
node.error(RED._("email.errors.fetchfail", {folder:node.box+". Folders - "+boxs.join(', ')}),err);
|
|
|
|
});
|
2020-01-31 22:46:42 +01:00
|
|
|
node.status({fill:"red", shape:"ring", text:"email.status.foldererror"});
|
|
|
|
imap.end();
|
2020-08-28 10:36:06 +02:00
|
|
|
s = false;
|
2020-01-31 22:46:42 +01:00
|
|
|
setInputRepeatTimeout();
|
2022-07-19 13:59:41 +02:00
|
|
|
done(err);
|
2020-01-31 22:46:42 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
var criteria = ((node.criteria === '_msg_')?
|
|
|
|
(msg.criteria || ["UNSEEN"]):
|
|
|
|
([node.criteria]));
|
2021-07-16 15:41:12 +02:00
|
|
|
if (Array.isArray(criteria)) {
|
|
|
|
try {
|
|
|
|
imap.search(criteria, function(err, results) {
|
|
|
|
if (err) {
|
|
|
|
node.status({fill:"red", shape:"ring", text:"email.status.foldererror"});
|
|
|
|
node.error(RED._("email.errors.fetchfail", {folder:node.box}),err);
|
|
|
|
imap.end();
|
|
|
|
s = false;
|
|
|
|
setInputRepeatTimeout();
|
2022-07-19 13:59:41 +02:00
|
|
|
done(err);
|
2021-07-16 15:41:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
//console.log("> search - err=%j, results=%j", err, results);
|
|
|
|
if (results.length === 0) {
|
|
|
|
//console.log(" [X] - Nothing to fetch");
|
|
|
|
node.status({results:0});
|
|
|
|
imap.end();
|
|
|
|
s = false;
|
|
|
|
setInputRepeatTimeout();
|
2022-07-19 13:59:41 +02:00
|
|
|
msg.payload = 0;
|
|
|
|
done();
|
2021-07-16 15:41:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var marks = false;
|
|
|
|
if (node.disposition === "Read") { marks = true; }
|
|
|
|
// We have the search results that contain the list of unseen messages and can now fetch those messages.
|
|
|
|
var fetch = imap.fetch(results, {
|
|
|
|
bodies: '',
|
|
|
|
struct: true,
|
|
|
|
markSeen: marks
|
|
|
|
});
|
2018-10-26 18:00:27 +02:00
|
|
|
|
2021-07-16 15:41:12 +02:00
|
|
|
// For each fetched message returned ...
|
|
|
|
fetch.on('message', function(imapMessage, seqno) {
|
|
|
|
//node.log(RED._("email.status.message",{number:seqno}));
|
|
|
|
//console.log("> Fetch message - msg=%j, seqno=%d", imapMessage, seqno);
|
|
|
|
imapMessage.on('body', function(stream, info) {
|
|
|
|
//console.log("> message - body - stream=?, info=%j", info);
|
|
|
|
simpleParser(stream, {}, function(err, parsed) {
|
|
|
|
if (err) {
|
|
|
|
node.status({fill:"red", shape:"ring", text:"email.status.parseerror"});
|
|
|
|
node.error(RED._("email.errors.parsefail", {folder:node.box}),err);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
processNewMessage(msg, parsed);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}); // End of msg->body
|
|
|
|
}); // End of fetch->message
|
|
|
|
|
|
|
|
// When we have fetched all the messages, we don't need the imap connection any more.
|
|
|
|
fetch.on('end', function() {
|
|
|
|
node.status({results:results.length});
|
|
|
|
var cleanup = function() {
|
|
|
|
imap.end();
|
|
|
|
s = false;
|
|
|
|
setInputRepeatTimeout();
|
2022-07-19 13:59:41 +02:00
|
|
|
msg.payload = results.length;
|
|
|
|
done();
|
2021-07-16 15:41:12 +02:00
|
|
|
};
|
|
|
|
if (node.disposition === "Delete") {
|
2022-12-22 12:38:00 +01:00
|
|
|
imap.addFlags(results, '\\Deleted', imap.expunge(cleanup) );
|
2021-07-16 15:41:12 +02:00
|
|
|
} else if (node.disposition === "Read") {
|
2022-12-22 12:38:00 +01:00
|
|
|
imap.addFlags(results, '\\Seen', cleanup);
|
2021-07-16 15:41:12 +02:00
|
|
|
} else {
|
|
|
|
cleanup();
|
2020-01-31 22:46:42 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-07-16 15:41:12 +02:00
|
|
|
fetch.once('error', function(err) {
|
|
|
|
console.log('Fetch error: ' + err);
|
|
|
|
imap.end();
|
|
|
|
s = false;
|
|
|
|
setInputRepeatTimeout();
|
2022-07-19 13:59:41 +02:00
|
|
|
done(err);
|
2021-07-16 15:41:12 +02:00
|
|
|
});
|
2020-01-31 22:46:42 +01:00
|
|
|
}
|
2021-07-16 15:41:12 +02:00
|
|
|
}); // End of imap->search
|
2020-01-31 22:46:42 +01:00
|
|
|
}
|
2021-07-16 15:41:12 +02:00
|
|
|
catch(e) {
|
|
|
|
node.status({fill:"red", shape:"ring", text:"email.status.bad_criteria"});
|
|
|
|
node.error(e.toString(),e);
|
|
|
|
s = ss = false;
|
|
|
|
imap.end();
|
2022-07-19 13:59:41 +02:00
|
|
|
done(e);
|
2021-07-16 15:41:12 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
node.status({fill:"red", shape:"ring", text:"email.status.bad_criteria"});
|
|
|
|
node.error(RED._("email.errors.bad_criteria"),msg);
|
|
|
|
s = ss = false;
|
|
|
|
imap.end();
|
2022-07-19 13:59:41 +02:00
|
|
|
done();
|
2021-07-16 15:41:12 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-01-31 22:46:42 +01:00
|
|
|
}
|
|
|
|
}); // End of imap->openInbox
|
2016-04-20 20:54:44 +02:00
|
|
|
}); // End of imap->ready
|
|
|
|
node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"});
|
2016-12-21 18:17:35 +01:00
|
|
|
imap.connect();
|
2016-04-20 20:54:44 +02:00
|
|
|
} // End of checkIMAP
|
|
|
|
|
|
|
|
|
|
|
|
// Perform a check of the email inboxes using either POP3 or IMAP
|
2022-07-19 13:59:41 +02:00
|
|
|
function checkEmail(msg,send,done) {
|
2016-04-20 20:47:23 +02:00
|
|
|
if (node.protocol === "POP3") {
|
2022-07-19 13:59:41 +02:00
|
|
|
checkPOP3(msg,send,done);
|
2016-04-20 20:47:23 +02:00
|
|
|
} else if (node.protocol === "IMAP") {
|
2022-07-19 13:59:41 +02:00
|
|
|
if (s === false && ss == false) { checkIMAP(msg,send,done); }
|
2016-04-20 20:47:23 +02:00
|
|
|
}
|
2016-04-20 20:54:44 +02:00
|
|
|
} // End of checkEmail
|
2016-04-20 20:47:23 +02:00
|
|
|
|
2022-07-19 13:59:41 +02:00
|
|
|
node.on("input", function(msg, send, done) {
|
|
|
|
send = send || function() { node.send.apply(node,arguments) };
|
|
|
|
checkEmail(msg,send,done);
|
2015-06-13 19:46:07 +02:00
|
|
|
});
|
|
|
|
|
2022-07-19 13:59:41 +02:00
|
|
|
node.on("close", function() {
|
2015-06-13 19:46:07 +02:00
|
|
|
if (this.interval_id != null) {
|
2018-09-03 20:56:36 +02:00
|
|
|
clearTimeout(this.interval_id);
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
2021-03-28 17:55:28 +02:00
|
|
|
if (imap) {
|
|
|
|
imap.end();
|
|
|
|
setTimeout(function() { imap.destroy(); },1000);
|
|
|
|
node.status({});
|
|
|
|
}
|
2015-06-13 19:46:07 +02:00
|
|
|
});
|
|
|
|
|
2023-03-27 21:27:47 +02:00
|
|
|
function status_clear() {
|
|
|
|
node.status({});
|
|
|
|
}
|
|
|
|
|
2018-10-26 18:00:27 +02:00
|
|
|
function setInputRepeatTimeout() {
|
2018-09-03 20:56:36 +02:00
|
|
|
// Set the repetition timer as needed
|
|
|
|
if (!isNaN(node.repeat) && node.repeat > 0) {
|
|
|
|
node.interval_id = setTimeout( function() {
|
|
|
|
node.emit("input",{});
|
|
|
|
}, node.repeat );
|
|
|
|
}
|
2019-02-01 01:00:16 +01:00
|
|
|
ss = false;
|
2016-04-20 20:54:44 +02:00
|
|
|
}
|
|
|
|
|
2019-01-31 22:00:31 +01:00
|
|
|
if (this.inputs !== 1) { node.emit("input",{}); }
|
2016-04-20 20:47:23 +02:00
|
|
|
}
|
2016-04-20 20:54:44 +02:00
|
|
|
|
2015-06-13 19:46:07 +02:00
|
|
|
RED.nodes.registerType("e-mail in",EmailInNode,{
|
|
|
|
credentials: {
|
2016-04-20 20:54:44 +02:00
|
|
|
userid: { type:"text" },
|
2016-04-20 20:47:23 +02:00
|
|
|
password: { type: "password" },
|
2016-04-20 20:54:44 +02:00
|
|
|
global: { type:"boolean" }
|
2015-06-13 19:46:07 +02:00
|
|
|
}
|
|
|
|
});
|
2020-10-19 13:24:18 +02:00
|
|
|
|
|
|
|
|
2020-10-19 15:32:09 +02:00
|
|
|
function EmailMtaNode(n) {
|
2022-06-10 11:14:38 +02:00
|
|
|
RED.nodes.createNode(this, n);
|
2020-10-19 13:24:18 +02:00
|
|
|
this.port = n.port;
|
2022-06-10 11:14:38 +02:00
|
|
|
this.secure = n.secure;
|
|
|
|
this.starttls = n.starttls;
|
|
|
|
this.certFile = n.certFile;
|
|
|
|
this.keyFile = n.keyFile;
|
|
|
|
this.users = n.users;
|
|
|
|
this.auth = n.auth;
|
|
|
|
try {
|
|
|
|
this.options = JSON.parse(n.expert);
|
|
|
|
} catch (error) {
|
|
|
|
this.options = {};
|
|
|
|
}
|
2020-10-19 13:24:18 +02:00
|
|
|
var node = this;
|
2022-06-10 11:14:38 +02:00
|
|
|
if (!Array.isArray(node.options.disabledCommands)) {
|
|
|
|
node.options.disabledCommands = [];
|
|
|
|
}
|
|
|
|
node.options.secure = node.secure;
|
|
|
|
if (node.certFile) {
|
|
|
|
node.options.cert = fs.readFileSync(node.certFile);
|
|
|
|
}
|
|
|
|
if (node.keyFile) {
|
|
|
|
node.options.key = fs.readFileSync(node.keyFile);
|
|
|
|
}
|
|
|
|
if (!node.starttls) {
|
|
|
|
node.options.disabledCommands.push("STARTTLS");
|
|
|
|
}
|
|
|
|
if (!node.auth) {
|
|
|
|
node.options.disabledCommands.push("AUTH");
|
|
|
|
}
|
2020-10-19 13:24:18 +02:00
|
|
|
|
2022-06-10 11:14:38 +02:00
|
|
|
node.options.onData = function (stream, session, callback) {
|
|
|
|
simpleParser(stream, { skipTextToHtml:true, skipTextLinks:true }, (err, parsed) => {
|
|
|
|
if (err) { node.error(RED._("email.errors.parsefail"),err); }
|
|
|
|
else {
|
|
|
|
node.status({fill:"green", shape:"dot", text:""});
|
|
|
|
var msg = {}
|
|
|
|
msg.payload = parsed.text;
|
|
|
|
msg.topic = parsed.subject;
|
|
|
|
msg.date = parsed.date;
|
|
|
|
msg.header = {};
|
|
|
|
parsed.headers.forEach((v, k) => {msg.header[k] = v;});
|
|
|
|
if (parsed.html) { msg.html = parsed.html; }
|
|
|
|
if (parsed.to) {
|
|
|
|
if (typeof(parsed.to) === "string" && parsed.to.length > 0) { msg.to = parsed.to; }
|
|
|
|
else if (parsed.to.hasOwnProperty("text") && parsed.to.text.length > 0) { msg.to = parsed.to.text; }
|
|
|
|
}
|
|
|
|
if (parsed.cc) {
|
|
|
|
if (typeof(parsed.cc) === "string" && parsed.cc.length > 0) { msg.cc = parsed.cc; }
|
|
|
|
else if (parsed.cc.hasOwnProperty("text") && parsed.cc.text.length > 0) { msg.cc = parsed.cc.text; }
|
2020-10-19 13:24:18 +02:00
|
|
|
}
|
2022-06-10 11:14:38 +02:00
|
|
|
if (parsed.cc && parsed.cc.length > 0) { msg.cc = parsed.cc; }
|
|
|
|
if (parsed.bcc && parsed.bcc.length > 0) { msg.bcc = parsed.bcc; }
|
|
|
|
if (parsed.from && parsed.from.value && parsed.from.value.length > 0) { msg.from = parsed.from.value[0].address; }
|
|
|
|
if (parsed.attachments) { msg.attachments = parsed.attachments; }
|
|
|
|
else { msg.attachments = []; }
|
|
|
|
node.send(msg); // Propagate the message down the flow
|
|
|
|
setTimeout(function() { node.status({})}, 500);
|
|
|
|
}
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
node.options.onAuth = function (auth, session, callback) {
|
|
|
|
let id = node.users.findIndex(function (item) {
|
|
|
|
return item.name === auth.username;
|
|
|
|
});
|
|
|
|
if (id >= 0 && node.users[id].password === auth.password) {
|
|
|
|
callback(null, { user: id + 1 });
|
|
|
|
} else {
|
|
|
|
callback(new Error("Invalid username or password"));
|
2020-10-19 13:24:18 +02:00
|
|
|
}
|
2022-06-10 11:14:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
node.mta = new SMTPServer(node.options);
|
2020-10-19 13:24:18 +02:00
|
|
|
|
|
|
|
node.mta.listen(node.port);
|
|
|
|
|
|
|
|
node.mta.on("error", err => {
|
|
|
|
node.error("Error: " + err.message, err);
|
|
|
|
});
|
|
|
|
|
|
|
|
node.on("close", function() {
|
|
|
|
node.mta.close();
|
|
|
|
});
|
|
|
|
}
|
2020-10-19 15:32:09 +02:00
|
|
|
RED.nodes.registerType("e-mail mta",EmailMtaNode);
|
2020-10-19 13:24:18 +02:00
|
|
|
|
2015-06-13 19:46:07 +02:00
|
|
|
};
|