Added xterm terminal and fixed device link.

This commit is contained in:
Ylian Saint-Hilaire 2020-01-23 15:15:56 -08:00
parent b6ff01775f
commit 2f37cd882e
10 changed files with 1444 additions and 990 deletions

View File

@ -1215,7 +1215,8 @@ function createMeshCore(agent) {
var options = { uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: { HISTCONTROL: 'ignoreboth', TERM: 'xterm' } };
var setupcommands = 'alias ls=\'ls --color=auto\'\n';
if (shell == sh) setupcommands += 'stty erase ^H\n'
if (shell == sh) setupcommands += 'stty erase ^H\n';
setupcommands += 'clear\n';
if (script && shell && process.platform == 'linux') {
this.httprequest.process = childProcess.execFile(script, ['script', '--return', '--quiet', '-c', '"' + shell + '"', '/dev/null'], options); // Start as active user

View File

@ -148,34 +148,9 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au
return;
}
/*
if (typeof e.data == 'object') {
var f = new FileReader();
if (f.readAsBinaryString) {
// Chrome & Firefox (Draft)
f.onload = function (e) { obj.xxOnSocketData(e.target.result); }
f.readAsBinaryString(new Blob([e.data]));
} else if (f.readAsArrayBuffer) {
// Chrome & Firefox (Spec)
f.onloadend = function (e) { obj.xxOnSocketData(e.target.result); }
f.readAsArrayBuffer(e.data);
} else {
// IE10, readAsBinaryString does not exist, use an alternative.
var binary = '';
var bytes = new Uint8Array(e.data);
var length = bytes.byteLength;
for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); }
obj.xxOnSocketData(binary);
}
} else {
// If we get a string object, it maybe the WebRTC confirm. Ignore it.
obj.xxOnSocketData(e.data);
}
*/
if (typeof e.data == 'object') {
if (fileReaderInuse == true) { fileReaderAcc.push(e.data); return; }
if (fileReader.readAsBinaryString) {
if (fileReader.readAsBinaryString && (obj.m.ProcessBinaryData == null)) {
// Chrome & Firefox (Draft)
fileReaderInuse = true;
fileReader.readAsBinaryString(new Blob([e.data]));
@ -209,6 +184,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au
obj.xxOnSocketData = function (data) {
if (!data || obj.connectstate == -1) return;
if (typeof data === 'object') {
if (obj.m.ProcessBinaryData) { return obj.m.ProcessBinaryData(data); }
// This is an ArrayBuffer, convert it to a string array (used in IE)
var binary = '', bytes = new Uint8Array(data), length = bytes.byteLength;
for (var i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); }

View File

@ -0,0 +1,2 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(window,function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(){}return e.prototype.activate=function(e){this._terminal=e},e.prototype.dispose=function(){},e.prototype.fit=function(){var e=this.proposeDimensions();if(e&&this._terminal){var t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}},e.prototype.proposeDimensions=function(){if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement){var e=this._terminal._core,t=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(t.getPropertyValue("height")),n=Math.max(0,parseInt(t.getPropertyValue("width"))),o=window.getComputedStyle(this._terminal.element),i=r-(parseInt(o.getPropertyValue("padding-top"))+parseInt(o.getPropertyValue("padding-bottom"))),a=n-(parseInt(o.getPropertyValue("padding-right"))+parseInt(o.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth;return{cols:Math.max(2,Math.floor(a/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(i/e._renderService.dimensions.actualCellHeight))}}},e}();t.FitAddon=n}])});
//# sourceMappingURL=xterm-addon-fit.js.map

2
public/scripts/xterm.js Normal file

File diff suppressed because one or more lines are too long

171
public/styles/xterm.css Normal file
View File

@ -0,0 +1,171 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* 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.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 5;
}
.xterm .xterm-helper-textarea {
/*
* HACK: to fix IE's blinking cursor
* Move textarea out of the screen to the far left, so that the cursor is not visible.
*/
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -5;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm {
cursor: text;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm.xterm-cursor-pointer {
cursor: pointer;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 10;
color: transparent;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.xterm-dim {
opacity: 0.5;
}
.xterm-underline {
text-decoration: underline;
}

View File

@ -27,6 +27,7 @@ var meshCentralSourceFiles = [
"../views/login-mobile.handlebars",
"../views/terms.handlebars",
"../views/terms-mobile.handlebars",
"../views/xterm.handlebars",
"../views/message.handlebars",
"../views/messenger.handlebars",
"../public/player.htm"

File diff suppressed because it is too large Load Diff

View File

@ -1770,6 +1770,7 @@
if (xxcurrentView == -1) { if ('{{viewmode}}' != '') { go(parseInt('{{viewmode}}')); } else { setDialogMode(0); go(1); } }
if ('{{currentNode}}' != '') { gotoDevice('{{currentNode}}',parseInt('{{viewmode}}'));}
else if (args.gotonode != null) { goBackStack.push(1); gotoDevice('node/' + domain + '/' + args.gotonode,parseInt('{{viewmode}}')); delete args.gotonode; }
break;
}
case 'powertimeline': {
@ -4785,6 +4786,11 @@
// Show node last 7 days timeline
masterUpdate(256);
// Check if we have terminal and file access
var terminalAccess = ((meshrights == 0xFFFFFFFF) || ((meshrights & 512) == 0));
var fileAccess = ((meshrights == 0xFFFFFFFF) || ((meshrights & 1024) == 0));
var amtAccess = ((meshrights == 0xFFFFFFFF) || ((meshrights & 2048) == 0));
// Show bottom buttons
x = '<div class="p10html3right">';
if ((meshrights & 4) != 0) {
@ -4796,7 +4802,8 @@
x += '<img title=\"' + "Place link to this device in the clipboard" + '\" src="images/link1.png" style=cursor:pointer onclick=p10deviceLinkToClipboard() />&nbsp;';
if (mesh.mtype == 2) x += '<a href=# onclick=p10showNodeNetInfoDialog("' + node._id + '") title=\"' + "Show device network interface information" + '\">' + "Interfaces" + '</a>&nbsp;';
if (xxmap != null) x += '<a href=# onclick=p10showNodeLocationDialog("' + node._id + '") title=\"' + "Show device locations information" + '\">' + "Location" + '</a>&nbsp;';
if (((meshrights & 8) != 0) && (mesh.mtype == 2)) x += '<a href=# onclick=p10showMeshCmdDialog(1,"' + node._id + '") title=\"' + "Traffic router used to connect to a device thru this server" + '.\">' + "Router" + '</a>&nbsp;';
if ((terminalAccess) && ((meshrights & 8) != 0) && (mesh.mtype == 2)) x += '<a href=# onclick=p10showMeshCmdDialog(1,"' + node._id + '") title=\"' + "Traffic router used to connect to a device thru this server" + '.\">' + "Router" + '</a>&nbsp;';
if ((mesh.mtype == 2) && ((node.agent.caps & 2) != 0) && ((meshrights & 8) != 0) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 512) == 0))) { x += '<a href=# onclick=p10openxterm(event,"' + node._id + '") title=\"' + "Open XTerm terminal" + '\">' + "XTerm" + '</a>&nbsp;'; }
// RDP link, show this link only of the remote machine is Windows.
if (((connectivity & 1) != 0) && (clickOnce == true) && (mesh.mtype == 2) && ((meshrights & 8) != 0)) {
@ -4827,11 +4834,6 @@
Q('MainComputerImage').setAttribute('src', 'images/icons256-' + node.icon + '-1.png');
Q('MainComputerImage').className = ((!node.conn) || (node.conn == 0)?'gray':'');
// Check if we have terminal and file access
var terminalAccess = ((meshrights == 0xFFFFFFFF) || ((meshrights & 512) == 0));
var fileAccess = ((meshrights == 0xFFFFFFFF) || ((meshrights & 1024) == 0));
var amtAccess = ((meshrights == 0xFFFFFFFF) || ((meshrights & 2048) == 0));
// Setup/Refresh the desktop tab
if (terminalAccess) { setupTerminal(); }
if (fileAccess) { setupFiles(); }
@ -5262,7 +5264,18 @@
function p10showMqttLoginDialog(nodeid) { meshserver.send({ action: 'getmqttlogin', nodeid: nodeid }); }
// Place a device link URL in the clipboard
function p10deviceLinkToClipboard() { copyTextToClip2(document.URL.split('?')[0].split('#')[0] + '?node=' + currentNode._id.split('/')[2] + '&viewmode=10'); }
function p10deviceLinkToClipboard() { copyTextToClip2(document.URL.split('?')[0].split('#')[0] + '?gotonode=' + currentNode._id.split('/')[2] + '&viewmode=10'); }
// Open XTerm
function p10openxterm(e, nodeid) {
haltEvent(e);
var url = '/xterm?nodeid=' + encodeURIComponent(nodeid) + '&auto=1';
var node = getNodeFromId(nodeid);
if (node == null) return;
if ([1, 2, 3, 4, 21, 22].indexOf(node.agent.id) >= 0) { url += '&fixsize=1'; }
window.open(url, 'xterm:' + nodeid);
return false;
}
// Show MeshCmd dialog
function p10showMeshCmdDialog(mode, nodeid) {

227
views/xterm.handlebars Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE html>
<html dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="format-detection" content="telephone=no" />
<link type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
<link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
<script type="text/javascript" src="scripts/meshcentral.js"></script>
<script type="text/javascript" src="scripts/agent-redir-ws-0.1.1.js"></script>
<script type="text/javascript" src="scripts/agent-redir-rtc-0.1.0.js"></script>
<script type="text/javascript" src="scripts/xterm.js"></script>
<script type="text/javascript" src="scripts/xterm-addon-fit.js"></script>
<title>{{{name}}}</title>
</head>
<body style="overflow:hidden;background-color:black">
<div id=p11 class="noselect" style="overflow:hidden">
<div id=deskarea0>
<div id=deskarea1 class="areaHead">
<div class="toright2">
</div>
<div>
<input id="ConnectButton" style="display:none" type=button value="Connect" onclick="connectButton()">
<input id="DisconnectButton" type=button value="Disconnect" onclick="connectButton()">
<span><b>{{{name}}}</b></span> - <span id="termstatus"></span>
</div>
</div>
<div id=deskarea2 style="">
<div class="areaProgress"><div id="progressbar" style=""></div></div>
</div>
<div id=deskarea3x style="max-height:calc(100vh - 54px);height:calc(100vh - 54px);">
<div id="bigok" style="display:none;left:calc((100vh / 2))"><b>&checkmark;</b></div>
<div id="bigfail" style="display:none;left:calc((100vh / 2))"><b>&#10007;</b></div>
<div id="metadatadiv" style="padding:20px;color:lightgrey;text-align:left;display:none"></div>
<div id=terminal style="max-height:calc(100vh - 54px);height:calc(100vh - 54px);"></div>
<div id=p11DeskConsoleMsg style="display:none;cursor:pointer;position:absolute;left:30px;top:17px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px" onclick=clearConsoleMsg()></div>
</div>
<div id=deskarea4 class="areaHead">
<div class="toright2">
</div>
<div style="height:21px;max-height:21px">
</div>
</div>
</div>
<div id=dialog class="noselect" style="display:none">
<div id=dialogHeader>
<div tabindex=0 id=id_dialogclose onclick=setDialogMode() onkeypress="if (event.key == 'Enter') setDialogMode()">&#x2716;</div>
<div id=id_dialogtitle></div>
</div>
<div id=dialogBody>
<div id=dialog1>
<div id=id_dialogMessage style=""></div>
</div>
<div id=dialog2 style="">
<div id=id_dialogOptions></div>
</div>
</div>
<div id="idx_dlgButtonBar">
<input id="idx_dlgCancelButton" type="button" value="Cancel" style="" onclick="dialogclose(0)">
<input id="idx_dlgOkButton" type="button" value="OK" style="" onclick="dialogclose(1)">
<div><input id="idx_dlgDeleteButton" type="button" value="Delete" style="display:none" onclick="dialogclose(2)"></div>
</div>
</div>
</div>
<script>
var term = null;
var termfit = null;
var tunnel = null;
var domain = '{{{domain}}}';
var domainUrl = '{{{domainurl}}}';
var authCookie = '{{{authCookie}}}';
var authRelayCookie = '{{{authRelayCookie}}}';
var serverPublicNamePort = '{{{serverDnsName}}}:{{{serverPublicPort}}}';
var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel&reg; AMT Connected"];
function start() {
// Parse any URL arguments
args = parseUriArgs();
// Connect to the mesh server
meshserver = MeshServerCreateControl(domainUrl, authCookie);
meshserver.onStateChanged = onStateChanged;
meshserver.onMessage = onMessage;
meshserver.trace = (args.trace == 1);
meshserver.Start();
// When the user resizes the window, re-fit
window.onresize = function () { if (termfit != null) { termfit.fit(); } }
// Update the terminal status and buttons
QH('termstatus', StatusStrs[0]);
updateButtons();
}
// Show the correct button state
function updateButtons() {
var tunnelState = ((tunnel != null) && (tunnel.state != 0) && (meshserver.State == 2));
QE('ConnectButton', meshserver.State == 2);
QV('ConnectButton', !tunnelState);
QV('DisconnectButton', tunnelState);
}
// MeshServer - Change State
function onStateChanged(server, state, prevState, errorCode) {
QE('ConnectButton', (state == 2));
if ((state == 2) && (args.auto == 1)) { delete args.auto; connectButton(); }
updateButtons();
}
// MeshServer - Handle messages
function onMessage(server, message) { }
// Handles a tunnel to a remote shell
function CreateRemoteTunnel(onTunnelUpdate) {
var obj = { protocol: 1 };
obj.onTunnelUpdate = onTunnelUpdate;
obj.xxStateChange = function (state) { }
obj.ProcessBinaryData = function (data) { obj.onTunnelUpdate(data); }
obj.ProcessData = function (data) { obj.onTunnelUpdate(data); }
return obj;
}
// Called when the connect/disconnect button is pressed
function connectButton() {
if (!tunnel) {
// Setup the terminal with auto-fit
if (term != null) { term.dispose(); }
if (args.fixsize != 1) { termfit = new FitAddon.FitAddon(); }
term = new Terminal();
if (termfit) { term.loadAddon(termfit); }
term.open(Q('terminal'));
term.onData(function (data) { if (tunnel != null) { tunnel.sendText(data); } })
term.onResize(function (size) {
//console.log('Resize', size);
//term.resize(size.cols, size.rows);
//if (tunnel != null) { tunnel.send(String.fromCharCode(27) + '[8;' + size.cols + ';' + size.rows + 't') }
});
if (termfit) { termfit.fit(); }
// Setup a terminal tunnel to the agent
tunnel = CreateAgentRedirect(meshserver, CreateRemoteTunnel(tunnelUpdate), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
tunnel.Start(args.nodeid);
tunnel.onStateChanged = onTunnelStateChange;
} else {
tunnel.Stop();
}
}
function tunnelUpdate(data) { if (typeof data == 'string') { term.writeUtf8(data); } else { term.writeUtf8(new Uint8Array(data)); } }
// Called when the terminal state changes
function onTunnelStateChange(xterminal, state) {
var xstate = state;
if ((xstate == 3) && (xterminal.contype == 2)) { xstate++; }
var str = StatusStrs[xstate];
if (tunnel.webRtcActive == true) { str += ", WebRTC"; }
QH('termstatus', str);
switch (state) {
case 0:
// Disconnected, clear the terminal
term.dispose();
term = null;
termfit = null;
tunnel = null;
break;
case 3:
// Connected
term.focus();
break;
default:
// Other
break;
}
updateButtons();
}
//
// POPUP DIALOG
//
// null = Hidden, 1 = Generic Message
var xxdialogMode;
var xxdialogFunc;
var xxdialogButtons;
var xxdialogTag;
var xxcurrentView = -1;
// Display a dialog box
// Parameters: Dialog Mode (0 = none), Dialog Title, Buttons (1 = OK, 2 = Cancel, 3 = OK & Cancel), Call back function(0 = Cancel, 1 = OK), Dialog Content (Mode 2 only)
function setDialogMode(x, y, b, f, c, tag) {
xxdialogMode = x;
xxdialogFunc = f;
xxdialogButtons = b;
xxdialogTag = tag;
QE('idx_dlgOkButton', true);
QV('idx_dlgOkButton', b & 1);
QV('idx_dlgCancelButton', b & 2);
QV('id_dialogclose', (b & 2) || (b & 8));
QV('idx_dlgDeleteButton', b & 4);
QV('idx_dlgButtonBar', b & 7);
if (y) QH('id_dialogtitle', y);
for (var i = 1; i < 3; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
QV('dialog', x);
if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
}
// Called when the dialog box must be closed
function dialogclose(x) {
var f = xxdialogFunc, b = xxdialogButtons, t = xxdialogTag;
setDialogMode();
if (((b & 8) || x) && f) f(x, t);
}
function messagebox(t, m) { setSessionActivity(); QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
function statusbox(t, m) { setSessionActivity(); QH('id_dialogMessage', m); setDialogMode(1, t); }
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
function pad2(num) { var s = '00' + num; return s.substr(s.length - 2); }
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
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 = decodeURIComponent(parsedUri[x]); break; } case 1: { r[name] = decodeURIComponent(parsedUri[x]); var x = parseInt(r[name]); if (x == r[name]) { r[name] = x; } break; } default: { break; } } } return r; }
start();
</script>
</body>
</html>

View File

@ -1695,6 +1695,39 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
return '';
}
// Serve the xterm page
function handleXTermRequest(req, res) {
const domain = checkUserIpAddress(req, res);
if (domain == null) { parent.debug('web', 'handleXTermRequest: Bad domain'); res.sendStatus(404); return; }
if ((domain.loginkey != null) && (domain.loginkey.indexOf(req.query.key) == -1)) { res.sendStatus(404); return; } // Check 3FA URL key
parent.debug('web', 'handleXTermRequest: sending xterm');
res.set({ 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' });
if (req.session && req.session.userid) {
if (req.session.domainid != domain.id) { res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the session is for the correct domain
var user = obj.users[req.session.userid];
if ((user == null) || (req.query.nodeid == null)) { res.redirect(domain.url + getQueryPortion(req)); return; } // Check if the user exists
// Check permissions
obj.GetNodeWithRights(domain, user, req.query.nodeid, function (node, rights, visible) {
if ((node == null) || ((rights & 8) == 0) || ((rights != 0xFFFFFFFF) && ((rights & 512) != 0))) { res.redirect(domain.url + getQueryPortion(req)); return; }
var logoutcontrols = { name: user.name };
var extras = (req.query.key != null) ? ('&key=' + req.query.key) : '';
if ((domain.ldap == null) && (domain.sspi == null) && (obj.args.user == null) && (obj.args.nousers != true)) { logoutcontrols.logoutUrl = (domain.url + 'logout?' + Math.random() + extras); } // If a default user is in use or no user mode, don't display the logout button
// Create a authentication cookie
const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: cleanRemoteAddr(req.ip) }, obj.parent.loginCookieEncryptionKey);
const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey);
var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
render(req, res, getRenderPage('xterm', req), getRenderArgs({ serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, authCookie: authCookie, authRelayCookie: authRelayCookie, logoutControls: JSON.stringify(logoutcontrols), name: EscapeHtml(node.name) }, domain));
});
} else {
res.redirect(domain.url + getQueryPortion(req));
return;
}
}
// Render the terms of service.
function handleTermsRequest(req, res) {
const domain = checkUserIpAddress(req, res);
@ -3447,6 +3480,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
obj.app.get(url + 'backup.zip', handleBackupRequest);
obj.app.post(url + 'restoreserver.ashx', handleRestoreRequest);
obj.app.get(url + 'terms', handleTermsRequest);
obj.app.get(url + 'xterm', handleXTermRequest);
obj.app.post(url + 'login', handleLoginRequest);
obj.app.post(url + 'tokenlogin', handleLoginRequest);
obj.app.get(url + 'logout', handleLogoutRequest);