Skip to content

Commit

Permalink
Added per-domain SMTP/SendGrid support.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ylianst committed Feb 10, 2021
1 parent ddf78e3 commit d96bf4b
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 73 deletions.
26 changes: 26 additions & 0 deletions meshcentral-config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,32 @@
"required": [ "protocols" ]
},
"showPasswordLogin": { "type": "boolean", "default": true, "description": "When set to false, hides the username and password prompt on login screen." },
"sendgrid": {
"title" : "SendGrid.com Email server",
"description": "Connects MeshCentral to the SendGrid email server, allows MeshCentral to send email messages for 2FA or user notification.",
"type": "object",
"properties": {
"from": { "type": "string", "format": "email", "description": "Email address used in the messages from field." },
"apikey": { "type": "string", "description": "The SendGrid API key." },
"verifyemail": { "type": "boolean", "default": true, "description": "When set to false, the email format and DNS MX record are not checked." }
},
"required": [ "from", "apikey" ]
},
"smtp": {
"title" : "SMTP email server",
"description": "Connects MeshCentral to a SMTP email server, allows MeshCentral to send email messages for 2FA or user notification.",
"type": "object",
"properties": {
"host": { "type": "string", "format": "hostname" },
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
"from": { "type": "string", "format": "email", "description": "Email address used in the messages from field." },
"tls": { "type": "boolean" },
"tlscertcheck": { "type": "boolean" },
"tlsstrict": { "type": "boolean" },
"verifyemail": { "type": "boolean", "default": true, "description": "When set to false, the email format and DNS MX record are not checked." }
},
"required": [ "host", "port", "from", "tls" ]
},
"authStrategies": {
"type": "object",
"additionalProperties": false,
Expand Down
28 changes: 25 additions & 3 deletions meshcentral.js
Original file line number Diff line number Diff line change
Expand Up @@ -1525,7 +1525,7 @@ function CreateMeshCentralServer(config, args) {
obj.swarmserver = require('./swarmserver.js').CreateSwarmServer(obj, obj.db, obj.args, obj.certificates);
}

// Setup email server
// Setup the main email server
if (obj.config.sendgrid != null) {
// Sendgrid server
obj.mailserver = require('./meshmail.js').CreateMeshMail(obj);
Expand All @@ -1538,6 +1538,24 @@ function CreateMeshCentralServer(config, args) {
if (obj.args.lanonly == true) { addServerWarning("SMTP server has limited use in LAN mode."); }
}

// Setup the email server for each domain
for (i in obj.config.domains) {
if (obj.config.domains[i].sendgrid != null) {
// Sendgrid server
obj.config.domains[i].mailserver = require('./meshmail.js').CreateMeshMail(obj, obj.config.domains[i]);
obj.config.domains[i].mailserver.verify();
if (obj.args.lanonly == true) { addServerWarning("SendGrid server has limited use in LAN mode."); }
} else if ((obj.config.domains[i].smtp != null) && (obj.config.domains[i].smtp.host != null) && (obj.config.domains[i].smtp.from != null)) {
// SMTP server
obj.config.domains[i].mailserver = require('./meshmail.js').CreateMeshMail(obj, obj.config.domains[i]);
obj.config.domains[i].mailserver.verify();
if (obj.args.lanonly == true) { addServerWarning("SMTP server has limited use in LAN mode."); }
} else {
// Setup the parent mail server for this domain
if (obj.mailserver != null) { obj.config.domains[i].mailserver = obj.mailserver; }
}
}

// Setup SMS gateway
if (config.sms != null) {
obj.smsserver = require('./meshsms.js').CreateMeshSMS(obj);
Expand Down Expand Up @@ -2994,10 +3012,14 @@ function mainStart() {
var recordingIndex = false;
var domainCount = 0;
var wildleek = false;
var nodemailer = false;
var sendgrid = false;
if (require('os').platform() == 'win32') { for (var i in config.domains) { domainCount++; if (config.domains[i].auth == 'sspi') { sspi = true; } else { allsspi = false; } } } else { allsspi = false; }
if (domainCount == 0) { allsspi = false; }
for (var i in config.domains) {
if (i.startsWith('_')) continue;
if (config.domains[i].smtp != null) { nodemailer = true; }
if (config.domains[i].sendgrid != null) { sendgrid = true; }
if (config.domains[i].yubikey != null) { yubikey = true; }
if (config.domains[i].auth == 'ldap') { ldap = true; }
if (config.domains[i].mstsc === true) { mstsc = true; }
Expand Down Expand Up @@ -3030,8 +3052,8 @@ function mainStart() {
if (config.settings.plugins != null) { modules.push('semver'); } // Required for version compat testing and update checks
if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('https-proxy-agent'); } // Required for HTTP/HTTPS proxy support
else if (config.settings.xmongodb != null) { modules.push('mongojs'); } // Add MongoJS, old driver.
if (config.smtp != null) { modules.push('nodemailer'); } // Add SMTP support
if (config.sendgrid != null) { modules.push('@sendgrid/mail'); } // Add SendGrid support
if (nodemailer || (config.smtp != null)) { modules.push('nodemailer'); } // Add SMTP support
if (sendgrid || (config.sendgrid != null)) { modules.push('@sendgrid/mail'); } // Add SendGrid support
if (args.translate) { modules.push('jsdom'); modules.push('esprima'); modules.push('minify-js'); modules.push('html-minifier'); } // Translation support

// If running NodeJS < 8, install "util.promisify"
Expand Down
64 changes: 35 additions & 29 deletions meshmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,39 @@
// TODO: Add NTML support with "nodemailer-ntlm-auth" https://github.com/nodemailer/nodemailer-ntlm-auth

// Construct a MeshAgent object, called upon connection
module.exports.CreateMeshMail = function (parent) {
module.exports.CreateMeshMail = function (parent, domain) {
var obj = {};
obj.pendingMails = [];
obj.parent = parent;
obj.retry = 0;
obj.sendingMail = false;
obj.mailCookieEncryptionKey = null;
obj.verifyemail = false;
obj.domain = domain;
//obj.mailTemplates = {};
const constants = (obj.parent.crypto.constants ? obj.parent.crypto.constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.

function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
//function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, '&nbsp;&nbsp;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }

if (parent.config.sendgrid != null) {
// Setup where we read our configuration from
if (obj.domain == null) { obj.config = parent.config; } else { obj.config = domain; }

if (obj.config.sendgrid != null) {
// Setup SendGrid mail server
obj.sendGridServer = require('@sendgrid/mail');
obj.sendGridServer.setApiKey(parent.config.sendgrid.apikey);
} else if (parent.config.smtp != null) {
obj.sendGridServer.setApiKey(obj.config.sendgrid.apikey);
if (obj.config.sendgrid.verifyemail == true) { obj.verifyemail = true; }
} else if (obj.config.smtp != null) {
// Setup SMTP mail server
const nodemailer = require('nodemailer');
var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: {} };
//var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: { secureProtocol: 'SSLv23_method', ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false } };
if (parent.config.smtp.port != null) { options.port = parent.config.smtp.port; }
if (parent.config.smtp.tlscertcheck === false) { options.tls.rejectUnauthorized = false; }
if (parent.config.smtp.tlsstrict === true) { options.tls.secureProtocol = 'SSLv23_method'; options.tls.ciphers = 'RSA+AES:!aNULL:!MD5:!DSS'; options.tls.secureOptions = constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE; }
if ((parent.config.smtp.user != null) && (parent.config.smtp.pass != null)) { options.auth = { user: parent.config.smtp.user, pass: parent.config.smtp.pass }; }
var options = { host: obj.config.smtp.host, secure: (obj.config.smtp.tls == true), tls: {} };
//var options = { host: obj.config.smtp.host, secure: (obj.config.smtp.tls == true), tls: { secureProtocol: 'SSLv23_method', ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false } };
if (obj.config.smtp.port != null) { options.port = obj.config.smtp.port; }
if (obj.config.smtp.tlscertcheck === false) { options.tls.rejectUnauthorized = false; }
if (obj.config.smtp.tlsstrict === true) { options.tls.secureProtocol = 'SSLv23_method'; options.tls.ciphers = 'RSA+AES:!aNULL:!MD5:!DSS'; options.tls.secureOptions = constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE; }
if ((obj.config.smtp.user != null) && (obj.config.smtp.pass != null)) { options.auth = { user: obj.config.smtp.user, pass: obj.config.smtp.pass }; }
if (obj.config.smtp.verifyemail == true) { obj.verifyemail = true; }
obj.smtpServer = nodemailer.createTransport(options);
}

Expand Down Expand Up @@ -149,10 +156,10 @@ module.exports.CreateMeshMail = function (parent) {

// Send a generic email
obj.sendMail = function (to, subject, text, html) {
if (parent.config.sendgrid != null) {
obj.pendingMails.push({ to: to, from: parent.config.sendgrid.from, subject: subject, text: text, html: html });
} else if (parent.config.smtp != null) {
obj.pendingMails.push({ to: to, from: parent.config.smtp.from, subject: subject, text: text, html: html });
if (obj.config.sendgrid != null) {
obj.pendingMails.push({ to: to, from: obj.config.sendgrid.from, subject: subject, text: text, html: html });
} else if (obj.config.smtp != null) {
obj.pendingMails.push({ to: to, from: obj.config.smtp.from, subject: subject, text: text, html: html });
}
sendNextMail();
};
Expand Down Expand Up @@ -180,8 +187,8 @@ module.exports.CreateMeshMail = function (parent) {

// Get from field
var from = null;
if (parent.config.sendgrid && (typeof parent.config.sendgrid.from == 'string')) { from = parent.config.sendgrid.from; }
else if (parent.config.smtp && (typeof parent.config.smtp.from == 'string')) { from = parent.config.smtp.from; }
if (obj.config.sendgrid && (typeof obj.config.sendgrid.from == 'string')) { from = obj.config.sendgrid.from; }
else if (obj.config.smtp && (typeof obj.config.smtp.from == 'string')) { from = obj.config.smtp.from; }

// Send the email
obj.pendingMails.push({ to: email, from: from, subject: mailReplacements(template.htmlSubject, domain, options), text: mailReplacements(template.txt, domain, options), html: mailReplacements(template.html, domain, options) });
Expand Down Expand Up @@ -213,8 +220,8 @@ module.exports.CreateMeshMail = function (parent) {

// Get from field
var from = null;
if (parent.config.sendgrid && (typeof parent.config.sendgrid.from == 'string')) { from = parent.config.sendgrid.from; }
else if (parent.config.smtp && (typeof parent.config.smtp.from == 'string')) { from = parent.config.smtp.from; }
if (obj.config.sendgrid && (typeof obj.config.sendgrid.from == 'string')) { from = obj.config.sendgrid.from; }
else if (obj.config.smtp && (typeof obj.config.smtp.from == 'string')) { from = obj.config.smtp.from; }

// Send the email
obj.pendingMails.push({ to: email, from: from, subject: mailReplacements(template.htmlSubject, domain, options), text: mailReplacements(template.txt, domain, options), html: mailReplacements(template.html, domain, options) });
Expand Down Expand Up @@ -247,8 +254,8 @@ module.exports.CreateMeshMail = function (parent) {

// Get from field
var from = null;
if (parent.config.sendgrid && (typeof parent.config.sendgrid.from == 'string')) { from = parent.config.sendgrid.from; }
else if (parent.config.smtp && (typeof parent.config.smtp.from == 'string')) { from = parent.config.smtp.from; }
if (obj.config.sendgrid && (typeof obj.config.sendgrid.from == 'string')) { from = obj.config.sendgrid.from; }
else if (obj.config.smtp && (typeof obj.config.smtp.from == 'string')) { from = obj.config.smtp.from; }

// Send the email
obj.pendingMails.push({ to: email, from: from, subject: mailReplacements(template.htmlSubject, domain, options), text: mailReplacements(template.txt, domain, options), html: mailReplacements(template.html, domain, options) });
Expand Down Expand Up @@ -281,8 +288,8 @@ module.exports.CreateMeshMail = function (parent) {

// Get from field
var from = null;
if (parent.config.sendgrid && (typeof parent.config.sendgrid.from == 'string')) { from = parent.config.sendgrid.from; }
else if (parent.config.smtp && (typeof parent.config.smtp.from == 'string')) { from = parent.config.smtp.from; }
if (obj.config.sendgrid && (typeof obj.config.sendgrid.from == 'string')) { from = obj.config.sendgrid.from; }
else if (obj.config.smtp && (typeof obj.config.smtp.from == 'string')) { from = obj.config.smtp.from; }

// Send the email
obj.pendingMails.push({ to: email, from: from, subject: mailReplacements(template.htmlSubject, domain, options), text: mailReplacements(template.txt, domain, options), html: mailReplacements(template.html, domain, options) });
Expand Down Expand Up @@ -319,8 +326,8 @@ module.exports.CreateMeshMail = function (parent) {

// Get from field
var from = null;
if (parent.config.sendgrid && (typeof parent.config.sendgrid.from == 'string')) { from = parent.config.sendgrid.from; }
else if (parent.config.smtp && (typeof parent.config.smtp.from == 'string')) { from = parent.config.smtp.from; }
if (obj.config.sendgrid && (typeof obj.config.sendgrid.from == 'string')) { from = obj.config.sendgrid.from; }
else if (obj.config.smtp && (typeof obj.config.smtp.from == 'string')) { from = obj.config.smtp.from; }

// Send the email
obj.pendingMails.push({ to: email, from: from, subject: mailReplacements(template.htmlSubject, domain, options), text: mailReplacements(template.txt, domain, options), html: mailReplacements(template.html, domain, options) });
Expand Down Expand Up @@ -399,13 +406,13 @@ module.exports.CreateMeshMail = function (parent) {
if (obj.smtpServer == null) return;
obj.smtpServer.verify(function (err, info) {
if (err == null) {
console.log('SMTP mail server ' + parent.config.smtp.host + ' working as expected.');
console.log('SMTP mail server ' + obj.config.smtp.host + ' working as expected.');
} else {
// Remove all non-object types from error to avoid a JSON stringify error.
var err2 = {};
for (var i in err) { if (typeof (err[i]) != 'object') { err2[i] = err[i]; } }
parent.debug('email', 'SMTP mail server ' + parent.config.smtp.host + ' failed: ' + JSON.stringify(err2));
console.log('SMTP mail server ' + parent.config.smtp.host + ' failed: ' + JSON.stringify(err2));
parent.debug('email', 'SMTP mail server ' + obj.config.smtp.host + ' failed: ' + JSON.stringify(err2));
console.log('SMTP mail server ' + obj.config.smtp.host + ' failed: ' + JSON.stringify(err2));
}
});
};
Expand All @@ -430,8 +437,7 @@ module.exports.CreateMeshMail = function (parent) {
// Check the email domain DNS MX record.
obj.approvedEmailDomains = {};
obj.checkEmail = function (email, func) {
if ((parent.config.smtp) && (parent.config.smtp.verifyemail === false)) { func(true); return; }
if ((parent.config.sendgrid) && (parent.config.sendgrid.verifyemail === false)) { func(true); return; }
if (obj.verifyemail == false) { func(true); return; }
var emailSplit = email.split('@');
if (emailSplit.length != 2) { func(false); return; }
if (obj.approvedEmailDomains[emailSplit[1]] === true) { func(true); return; }
Expand Down
Loading

0 comments on commit d96bf4b

Please sign in to comment.