diff --git a/meshcentral.js b/meshcentral.js index 13ae02a2..af600a4a 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -183,7 +183,8 @@ function CreateMeshCentralServer(config, args) { xprocess.stdout.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } if (data.indexOf('Updating settings folder...') >= 0) { xprocess.xrestart = 1; } else if (data.indexOf('Updating server certificates...') >= 0) { xprocess.xrestart = 1; } else if (data.indexOf('Server Ctrl-C exit...') >= 0) { xprocess.xrestart = 2; } else if (data.indexOf('Starting self upgrade...') >= 0) { xprocess.xrestart = 3; } console.log(data); }); xprocess.stderr.on('data', function (data) { if (data.startsWith('le.challenges[tls-sni-01].loopback')) { return; } // Ignore this error output from GreenLock - if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } obj.fs.appendFileSync(obj.getConfigFilePath('mesherrors.txt'), '-------- ' + new Date().toLocaleString() + ' ---- ' + obj.currentVer + ' --------\r\n\r\n' + data + '\r\n\r\n\r\n'); + if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } + try { obj.fs.appendFileSync(obj.getConfigFilePath('mesherrors.txt'), '-------- ' + new Date().toLocaleString() + ' ---- ' + obj.currentVer + ' --------\r\n\r\n' + data + '\r\n\r\n\r\n'); } catch (ex) { console.log('ERROR: Unable to write to mesherrors.txt.'); } }); xprocess.on('close', function (code) { if ((code != 0) && (code != 123)) { /* console.log("Exited with code " + code); */ } }); }; diff --git a/meshrelay.js b/meshrelay.js index f4f59d50..16f21a81 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -94,6 +94,13 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if (obj.id == null) { try { obj.close(); } catch (e) { } return null; } // Attempt to connect without id, drop this. ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive + // If this is a MeshMessenger session, the ID is the two userid's and authentication must match one of them. + if (obj.id.startsWith('meshmessenger/')) { + if (obj.user == null) { try { obj.close(); } catch (e) { } return null; } + var x = obj.id.split('/'), user1 = x[1] + '/' + x[2] + '/' + x[3], user2 = x[4] + '/' + x[5] + '/' + x[6]; + if ((obj.user._id != user1) && (obj.user._id != user2)) { try { obj.close(); } catch (e) { } return null; } + } + // Validate that the id is valid, we only need to do this on non-authenticated sessions. // TODO: Figure out when this needs to be done. /* diff --git a/meshuser.js b/meshuser.js index 6ef37359..c5a7e3f4 100644 --- a/meshuser.js +++ b/meshuser.js @@ -592,6 +592,25 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use var sessions = obj.parent.wssessions[command.userid]; if (sessions != null) { for (i in sessions) { try { sessions[i].send(JSON.stringify(notification)); } catch (ex) { } } } + if (obj.parent.parent.multiServer != null) { + // TODO: Add multi-server support + } + break; + } + case 'meshmessenger': + { + // Send a notification message to a user + if ((user.siteadmin & 2) == 0) break; + if (obj.common.validateString(command.userid, 1, 2048) == false) break; + + // Create the notification message + var notification = { + "action": "msg", "type": "notify", "value": "" + user.name + ": Chat Request, Click here to accept.", "userid": user._id, "username": user.name, "tag": 'meshmessenger/' + encodeURIComponent(command.userid) + '/' + encodeURIComponent(user._id) }; + + // Get the list of sessions for this user + var sessions = obj.parent.wssessions[command.userid]; + if (sessions != null) { for (i in sessions) { try { sessions[i].send(JSON.stringify(notification)); } catch (ex) { } } } + if (obj.parent.parent.multiServer != null) { // TODO: Add multi-server support } diff --git a/public/images/icon-chat.png b/public/images/icon-chat.png new file mode 100644 index 00000000..f156df7d Binary files /dev/null and b/public/images/icon-chat.png differ diff --git a/public/messenger.htm b/public/messenger.htm index 28401d82..3ed66a14 100644 --- a/public/messenger.htm +++ b/public/messenger.htm @@ -1,6 +1,7 @@ + MeshMessenger @@ -8,7 +9,7 @@ -
Messenger
+
MeshMessenger
@@ -25,7 +26,7 @@ var state = 0; // Set the title - if (args.title) { QH('xtitle', ' - ' + args.title); } + if (args.title) { QH('xtitle', ' - ' + args.title); document.title = document.title + ' - ' + args.title; } // Trap document key presses document.onkeypress = function ondockeypress(e) { @@ -76,31 +77,34 @@ function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; } function parseUriArgs() { var name, r = {}, parsedUri = window.document.location.href.split(/[\?&|\=]/); parsedUri.splice(0, 1); for (x in parsedUri) { switch (x % 2) { case 0: { name = parsedUri[x]; break; } case 1: { r[name] = parsedUri[x]; var x = parseInt(r[name]); if (x == r[name]) { r[name] = x; } break; } } } return r; } - // Get started - enableControls(false); - if ((typeof args.id == 'string') && (args.id.length > 0)) { - xcontrol('Connecting...'); - socket = new WebSocket(window.location.protocol.replace("http", "ws") + "//" + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + '/meshrelay.ashx?id=' + args.id); - socket.onopen = function () { xcontrol('Waiting for other user...'); } - socket.onerror = function (e) { console.error(e); } - socket.onclose = function () { enableControls(false); xcontrol('Connection closed.'); socket = null; } - socket.onmessage = function (msg) { - if ((state == 0) && (typeof msg.data == 'string')) { enableControls(true); xcontrol('Connected.'); state = 1; return; } - if (state == 1) { - if (typeof msg.data == 'string') { - var obj = JSON.parse(msg.data); - switch (obj.action) { - case 'chat': { xrecv(obj.msg); break; } + function start() { + // Get started + enableControls(false); + if ((typeof args.id == 'string') && (args.id.length > 0)) { + //xcontrol('Connecting...'); + socket = new WebSocket(window.location.protocol.replace("http", "ws") + "//" + window.location.host + window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')) + '/meshrelay.ashx?id=' + args.id); + socket.onopen = function () { state = 1; xcontrol('Waiting for other user...'); } + socket.onerror = function (e) { console.error(e); } + socket.onclose = function () { enableControls(false); if (state > 0) { xcontrol('Connection closed.'); } socket = null; if (state > 1) { setTimeout(start, 500); } state = 0; } + socket.onmessage = function (msg) { + if ((state < 2) && (typeof msg.data == 'string')) { enableControls(true); xcontrol('Connected.'); state = 2; return; } + if (state == 2) { + if (typeof msg.data == 'string') { + var obj = JSON.parse(msg.data); + switch (obj.action) { + case 'chat': { xrecv(obj.msg); break; } + } + } else { + //xrecv(JSON.stringify(msg)); } - } else { - //xrecv(JSON.stringify(msg)); } } + } else { + xcontrol('Error: No connection key specified.'); } - } else { - xcontrol('Error: No connection key specified.'); } + start(); diff --git a/views/default.handlebars b/views/default.handlebars index 31874954..d780e08d 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1195,6 +1195,7 @@ var n = { text:message.value }; if (message.nodeid != null) { n.nodeid = message.nodeid; } if (message.tag != null) { n.tag = message.tag; } + if (message.username != null) { n.username = message.username; } addNotification(n); } else if (message.type == 'ps') { showDeskToolsProcesses(message); @@ -1204,6 +1205,7 @@ if (message.type == 'notify') { // This is a notification message. var n = { text:message.value }; if (message.tag != null) { n.tag = message.tag; } + if (message.username != null) { n.username = message.username; } addNotification(n); } } @@ -5720,10 +5722,13 @@ } function addUserHtml(user, sessions) { - var x = '', gray = ' gray', icon = 'm2', msg = '', self = (user.name != userinfo.name); + var x = '', gray = ' gray', icon = 'm2', msg = '', msg2 = '', self = (user.name != userinfo.name); if (sessions != null) { gray = ''; - if (self) { msg += ""; } + if (self) { + msg2 = ""; + msg += ""; + } if (sessions == 1) { msg += '1 active session'; } else { msg += sessions + ' active sessions'; } if (self) { msg += ""; } } else { @@ -5752,7 +5757,7 @@ x += '
'; x += '
'; x += '
'; - x += '
' + username + '' + msg + '
'; // + x += '
' + username + '' + msg2 + '' + msg + '
'; // return x; } @@ -5765,6 +5770,13 @@ element.children[0].children[0].style['background-color'] = ((over == 0) ? '#c9c9c9' : '#b9b9b9'); } + function userChat(e, userid, name) { + haltEvent(e); + window.open('/messenger.htm?id=meshmessenger/' + userid + '/' + encodeURIComponent(userinfo._id) + '&title=' + name, 'meshmessenger:' + userid); + meshserver.send({ action: 'meshmessenger', userid: decodeURIComponent(userid) }); + return false; + } + function showUserAlertDialog(e, userid) { if (xxdialogMode) return; haltEvent(e); @@ -6223,6 +6235,11 @@ else if (n.tag == 'intelamt') gotoDevice(n.nodeid, 14); // Intel AMT else if (n.tag == 'console') gotoDevice(n.nodeid, 15); // Files else gotoDevice(n.nodeid, 10); // General + } else { + if (n.tag.startsWith('meshmessenger/')) { + window.open('/messenger.htm?id=' + n.tag + '&title=' + encodeURIComponent(n.username), n.tag.split('/')[2]); + notificationDelete(id); + } } } } diff --git a/views/login-mobile.handlebars b/views/login-mobile.handlebars index 6c97d230..0abdc1da 100644 --- a/views/login-mobile.handlebars +++ b/views/login-mobile.handlebars @@ -193,7 +193,7 @@ validateLogin(); validateCreate(); if ('{{loginmode}}' != '') { go(parseInt('{{loginmode}}')); } else { go(1); } - QV('newAccountDiv', '{{{newAccount}}}' != '0' ); + QV('newAccountDiv', ('{{{newAccount}}}' != '0') && ('{{{newAccount}}}' != 'false')); // If new accounts are not allowed, don't display the new account link. if ((passhint != null) && (passhint.length > 0)) { QV("showPassHintLink", true); } QV("newAccountPass", (newAccountPass == 1)); QV("resetAccountDiv", (emailCheck == true)); diff --git a/views/login.handlebars b/views/login.handlebars index 836bcb64..57be4ab4 100644 --- a/views/login.handlebars +++ b/views/login.handlebars @@ -276,7 +276,7 @@ validateLogin(); validateCreate(); if ('{{loginmode}}' != '') { go(parseInt('{{loginmode}}')); } else { go(1); } - QV('newAccountDiv', '{{{newAccount}}}' != '0' ); + QV('newAccountDiv', ('{{{newAccount}}}' != '0') && ('{{{newAccount}}}' != 'false')); // If new accounts are not allowed, don't display the new account link. if ((passhint != null) && (passhint.length > 0)) { QV("showPassHintLink", true); } QV("newAccountPass", (newAccountPass == 1)); QV("resetAccountDiv", (emailCheck == true)); diff --git a/webserver.js b/webserver.js index 5c17c512..39bd6098 100644 --- a/webserver.js +++ b/webserver.js @@ -220,7 +220,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { for (i in docs) { var u = obj.users[docs[i]._id] = docs[i]; domainUserCount[u.domain]++; } for (i in parent.config.domains) { if (domainUserCount[i] == 0) { - if (parent.config.domains[i].newaccounts == 0) { parent.config.domains[i].newaccounts = 2; } + // If newaccounts is set to no new accounts, but no accounts exists, temporarly allow account creation. + if ((parent.config.domains[i].newaccounts === 0) || (parent.config.domains[i].newaccounts === false)) { parent.config.domains[i].newaccounts = 2; } console.log('Server ' + ((i == '') ? '' : (i + ' ')) + 'has no users, next new account will be site administrator.'); } } @@ -390,7 +391,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { function handleCreateAccountRequest(req, res) { var domain = checkUserIpAddress(req, res); if (domain == null) return; - if (domain.newaccounts == 0) { res.sendStatus(401); return; } + if ((domain.newaccounts === 0) || (domain.newaccounts === false)) { res.sendStatus(401); return; } if (!obj.common.validateUsername(req.body.username, 1, 64) || !obj.common.validateEmail(req.body.email, 1, 256) || !obj.common.validateString(req.body.password1, 1, 256) || !obj.common.validateString(req.body.password2, 1, 256) || (req.body.password1 != req.body.password2) || req.body.username == '~') { req.session.loginmode = 2; req.session.error = 'Unable to create account.'; @@ -420,7 +421,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { var user = { type: 'user', _id: 'user/' + domain.id + '/' + req.body.username.toLowerCase(), name: req.body.username, email: req.body.email, creation: Date.now(), login: Date.now(), domain: domain.id, passhint: hint }; var usercount = 0; for (var i in obj.users) { if (obj.users[i].domain == domain.id) { usercount++; } } - if (usercount == 0) { user.siteadmin = 0xFFFFFFFF; if (domain.newaccounts == 2) { domain.newaccounts = 0; } } // If this is the first user, give the account site admin. + if (usercount == 0) { user.siteadmin = 0xFFFFFFFF; if (domain.newaccounts === 2) { domain.newaccounts = 0; } } // If this is the first user, give the account site admin. obj.users[user._id] = user; req.session.userid = user._id; req.session.domainid = domain.id; @@ -444,7 +445,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { function handleResetAccountRequest(req, res) { var domain = checkUserIpAddress(req, res); if (domain == null) return; - if (domain.newaccounts == 0) { res.sendStatus(401); return; } + if ((domain.newaccounts === 0) || (domain.newaccounts === false)) { res.sendStatus(401); return; } if (!req.body.email || checkEmail(req.body.email) == false) { req.session.loginmode = 3; req.session.error = 'Invalid email.';