Added guest web sharing of HTTP/HTTPS (#4413)
Ylian Saint-Hilaire committed
Aug 25, 2022 at 20:11 UTC
5d7fabfc2164d025436b899776a658fc80dc7111
9 files changed
+1012
-588
agents/MeshCmd-signed.exe
Binary files a/agents/MeshCmd-signed.exe and b/agents/MeshCmd-signed.exe differ
agents/MeshCmd64-signed.exe
Binary files a/agents/MeshCmd64-signed.exe and b/agents/MeshCmd64-signed.exe differ
agents/meshcore.js
+771
-463
@@ -23,8 +23,7 @@ if (process.platform == 'win32' && require('user-sessions').getDomain == null) {
23
};
24
}
25
26
-// NOTE: This seems to cause big problems, don't enable the debugger in the server's meshcore.
27
-//attachDebugger({ webport: 9999, wait: 1 }).then(function (prt) { console.log('Point Browser for Debug to port: ' + prt); });
26
+var promise = require('promise');
27
28
// Mesh Rights
29
var MNG_ERROR = 65;
@@ -1043,6 +1042,35 @@ function server_check_consentTimer(id) {
1042
return false;
1043
}
1044
1045
+function tunnel_finalized()
1046
+{
1047
+ console.info1('Tunnel Request Finalized');
1048
+}
1049
+function tunnel_checkServerIdentity(certs)
1050
+{
1051
+ /*
1052
+ try { sendConsoleText("certs[0].digest: " + certs[0].digest); } catch (ex) { sendConsoleText(ex); }
1053
+ try { sendConsoleText("certs[0].fingerprint: " + certs[0].fingerprint); } catch (ex) { sendConsoleText(ex); }
1054
+ try { sendConsoleText("control-digest: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.digest); } catch (ex) { sendConsoleText(ex); }
1055
+ try { sendConsoleText("control-fingerprint: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint); } catch (ex) { sendConsoleText(ex); }
1056
+ */
1057
+
1058
+ // Check if this is an old agent, no certificate checks are possible in this situation. Display a warning.
1059
+ if ((require('MeshAgent').ServerInfo == null) || (require('MeshAgent').ServerInfo.ControlChannelCertificate == null) || (certs[0].digest == null)) { sendAgentMessage("This agent is using insecure tunnels, consider updating.", 3, 119, true); return; }
1060
+
1061
+ // If the tunnel certificate matches the control channel certificate, accept the connection
1062
+ if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; // Control channel certificate matches using full cert hash
1063
+ if ((certs[0].fingerprint != null) && (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint)) return; // Control channel certificate matches using public key hash
1064
+
1065
+ // Check that the certificate is the one expected by the server, fail if not.
1066
+ if ((tunnel_checkServerIdentity.servertlshash != null) && (tunnel_checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
1067
+}
1068
+
1069
+function tunnel_onError()
1070
+{
1071
+ sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e));
1072
+}
1073
+
1074
// Handle a mesh agent command
1075
function handleServerCommand(data) {
1076
if (typeof data == 'object') {
@@ -1062,7 +1090,8 @@ function handleServerCommand(data) {
1090
}
1091
break;
1092
}
1065
- case 'tunnel': {
1093
+ case 'tunnel':
1094
+ {
1095
if (data.value != null) { // Process a new tunnel connection request
1096
// Create a new tunnel object
1097
var xurl = getServerTargetUrlEx(data.value);
@@ -1074,31 +1103,15 @@ function handleServerCommand(data) {
1103
1104
// Perform manual server TLS certificate checking based on the certificate hash given by the server.
1105
woptions.rejectUnauthorized = 0;
1077
- woptions.checkServerIdentity = function checkServerIdentity(certs) {
1078
- /*
1079
- try { sendConsoleText("certs[0].digest: " + certs[0].digest); } catch (ex) { sendConsoleText(ex); }
1080
- try { sendConsoleText("certs[0].fingerprint: " + certs[0].fingerprint); } catch (ex) { sendConsoleText(ex); }
1081
- try { sendConsoleText("control-digest: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.digest); } catch (ex) { sendConsoleText(ex); }
1082
- try { sendConsoleText("control-fingerprint: " + require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint); } catch (ex) { sendConsoleText(ex); }
1083
- */
1084
-
1085
- // Check if this is an old agent, no certificate checks are possible in this situation. Display a warning.
1086
- if ((require('MeshAgent').ServerInfo == null) || (require('MeshAgent').ServerInfo.ControlChannelCertificate == null) || (certs[0].digest == null)) { sendAgentMessage("This agent is using insecure tunnels, consider updating.", 3, 119, true); return; }
1087
-
1088
- // If the tunnel certificate matches the control channel certificate, accept the connection
1089
- if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; // Control channel certificate matches using full cert hash
1090
- if ((certs[0].fingerprint != null) && (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint)) return; // Control channel certificate matches using public key hash
1091
-
1092
- // Check that the certificate is the one expected by the server, fail if not.
1093
- if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
1094
- }
1106
+ woptions.checkServerIdentity = tunnel_checkServerIdentity;
1107
woptions.checkServerIdentity.servertlshash = data.servertlshash;
1108
1109
//sendConsoleText(JSON.stringify(woptions));
1110
//sendConsoleText('TUNNEL: ' + JSON.stringify(data, null, 2));
1111
+
1112
var tunnel = http.request(woptions);
1113
tunnel.upgrade = onTunnelUpgrade;
1101
- tunnel.on('error', function (e) { sendConsoleText("ERROR: Unable to connect relay tunnel to: " + this.url + ", " + JSON.stringify(e)); });
1114
+ tunnel.on('error', tunnel_onError);
1115
tunnel.sessionid = data.sessionid;
1116
tunnel.rights = data.rights;
1117
tunnel.consent = data.consent;
@@ -1122,11 +1135,13 @@ function handleServerCommand(data) {
1135
tunnel.tcpport = data.tcpport;
1136
tunnel.udpaddr = data.udpaddr;
1137
tunnel.udpport = data.udpport;
1125
- tunnel.end();
1138
+
1139
// Put the tunnel in the tunnels list
1140
var index = nextTunnelIndex++;
1141
tunnel.index = index;
1142
tunnels[index] = tunnel;
1143
+ tunnel.once('~', tunnel_finalized);
1144
+ tunnel.end();
1145
1146
//sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
1147
}
@@ -1355,7 +1370,9 @@ function handleServerCommand(data) {
1370
this._dispatcher.on('connection', function (c) {
1371
this._c = c;
1372
this._c.root = this.parent;
1358
- this._c.on('end', function () {
1373
+ this._c.on('end', function ()
1374
+ {
1375
+ this.root._dispatcher.close();
1376
this.root._dispatcher = null;
1377
this.root = null;
1378
mesh.SendCommand({ action: 'msg', type: 'setclip', sessionid: data.sessionid, success: true });
@@ -1840,20 +1857,34 @@ function getDirectoryInfo(reqpath) {
1857
return response;
1858
}
1859
1860
+function tunnel_s_finalized()
1861
+{
1862
+ console.info1('Tunnel Socket Finalized');
1863
+}
1864
+
1865
+
1866
+function tunnel_onIdleTimeout()
1867
+{
1868
+ this.ping();
1869
+ this.setTimeout(require('MeshAgent').idleTimeout * 1000);
1870
+}
1871
+
1872
// Tunnel callback operations
1844
-function onTunnelUpgrade(response, s, head) {
1873
+function onTunnelUpgrade(response, s, head)
1874
+{
1875
+
1876
this.s = s;
1877
+ s.once('~', tunnel_s_finalized);
1878
s.httprequest = this;
1879
s.end = onTunnelClosed;
1880
s.tunnel = this;
1881
s.descriptorMetadata = "MeshAgent_relayTunnel";
1882
1851
- if (require('MeshAgent').idleTimeout != null) {
1883
+
1884
+ if (require('MeshAgent').idleTimeout != null)
1885
+ {
1886
s.setTimeout(require('MeshAgent').idleTimeout * 1000);
1853
- s.on('timeout', function () {
1854
- this.ping();
1855
- this.setTimeout(require('MeshAgent').idleTimeout * 1000);
1856
- });
1887
+ s.on('timeout', tunnel_onIdleTimeout);
1888
}
1889
1890
//sendConsoleText('onTunnelUpgrade - ' + this.tcpport + ' - ' + this.udpport);
@@ -1937,7 +1968,25 @@ function onTcpRelayServerTunnelData(data) {
1968
}
1969
}
1970
1940
-function onTunnelClosed() {
1971
+function onTunnelClosed()
1972
+{
1973
+ if (this.httprequest._dispatcher != null && this.httprequest.term == null)
1974
+ {
1975
+ // Windows Dispatcher was created to spawn a child connection, but the child didn't connect yet, so we have to shutdown the dispatcher, otherwise the child may end up hanging
1976
+ if (this.httprequest._dispatcher.close) { this.httprequest._dispatcher.close(); }
1977
+ this.httprequest._dispatcher = null;
1978
+ }
1979
+
1980
+ if (this.tunnel)
1981
+ {
1982
+ if (tunnels[this.httprequest.index] == null)
1983
+ {
1984
+ this.tunnel.s = null;
1985
+ this.tunnel = null;
1986
+ return;
1987
+ }
1988
+ }
1989
+
1990
var tunnel = tunnels[this.httprequest.index];
1991
if (tunnel == null) return; // Stop duplicate calls.
1992
@@ -1987,7 +2036,7 @@ function onTunnelClosed() {
2036
} catch (ex) { }
2037
2038
//sendConsoleText("Tunnel #" + this.httprequest.index + " closed. Sent -> " + this.bytesSent_uncompressed + ' bytes (uncompressed), ' + this.bytesSent_actual + ' bytes (actual), ' + this.bytesSent_ratio + '% compression', this.httprequest.sessionid);
1990
- if (this.httprequest.index) { delete tunnels[this.httprequest.index]; }
2039
+
2040
2041
/*
2042
// Close the watcher if required
@@ -2014,11 +2063,523 @@ function onTunnelClosed() {
2063
}
2064
2065
// Clean up WebSocket
2066
+ delete tunnels[this.httprequest.index];
2067
+ tunnel = null;
2068
+ this.tunnel.s = null;
2069
+ this.tunnel = null;
2070
this.removeAllListeners('data');
2071
}
2072
function onTunnelSendOk() { /*sendConsoleText("Tunnel #" + this.index + " SendOK.", this.sessionid);*/ }
2020
-function onTunnelData(data) {
2021
- //console.log("OnTunnelData");
2073
+
2074
+function terminal_onconnection (c)
2075
+{
2076
+ if (this.httprequest.connectionPromise.completed)
2077
+ {
2078
+ c.end();
2079
+ }
2080
+ else
2081
+ {
2082
+ this.httprequest.connectionPromise._res(c);
2083
+ }
2084
+}
2085
+function terminal_user_onconnection(c)
2086
+{
2087
+ console.info1('completed-2: ' + this.connectionPromise.completed);
2088
+
2089
+ if (this.connectionPromise.completed)
2090
+ {
2091
+ c.end();
2092
+ }
2093
+ else
2094
+ {
2095
+ this.connectionPromise._res(c);
2096
+ }
2097
+}
2098
+function terminal_stderr_ondata(c)
2099
+{
2100
+ this.stdout.write(c);
2101
+}
2102
+function terminal_onend()
2103
+{
2104
+ this.httprequest.process.kill();
2105
+}
2106
+
2107
+function terminal_onexit()
2108
+{
2109
+ this.tunnel.end();
2110
+}
2111
+function terminal_onfinalized()
2112
+{
2113
+ this.httprequest = null;
2114
+ console.info1('Dispatcher Finalized');
2115
+}
2116
+function terminal_end()
2117
+{
2118
+ if (this.httprequest == null) { return; }
2119
+ if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); }
2120
+ if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); }
2121
+
2122
+ // Remove the terminal session to the count to update the server
2123
+ if (this.httprequest.userid != null)
2124
+ {
2125
+ var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2126
+ if (tunnelUserCount.terminal[userid] != null) { tunnelUserCount.terminal[userid]--; if (tunnelUserCount.terminal[userid] <= 0) { delete tunnelUserCount.terminal[userid]; } }
2127
+ try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { }
2128
+ broadcastSessionsToRegisteredApps();
2129
+ }
2130
+
2131
+ if (process.platform == 'win32')
2132
+ {
2133
+ // Unpipe the web socket
2134
+ this.unpipe(this.httprequest._term);
2135
+ if (this.httprequest._term) { this.httprequest._term.unpipe(this); }
2136
+
2137
+ // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
2138
+ if (this.rtcchannel)
2139
+ {
2140
+ this.rtcchannel.unpipe(this.httprequest._term);
2141
+ if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); }
2142
+ }
2143
+
2144
+ // Clean up
2145
+ if (this.httprequest._term) { this.httprequest._term.end(); }
2146
+ this.httprequest._term = null;
2147
+ this.httprequest._dispatcher = null;
2148
+ }
2149
+
2150
+ this.httprequest = null;
2151
+
2152
+}
2153
+
2154
+function terminal_promise_connection_rejected(e)
2155
+{
2156
+ // FAILED to connect terminal
2157
+ this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2158
+ this.ws.end();
2159
+}
2160
+
2161
+function terminal_promise_connection_resolved(term)
2162
+{
2163
+ this._internal.completedArgs = [];
2164
+
2165
+ // SUCCESS
2166
+ var stdoutstream;
2167
+ var stdinstream;
2168
+ if (process.platform == 'win32')
2169
+ {
2170
+ this.ws.httprequest._term = term;
2171
+ this.ws.httprequest._term.tunnel = this.ws;
2172
+ stdoutstream = stdinstream = term;
2173
+ }
2174
+ else
2175
+ {
2176
+ term.descriptorMetadata = 'Remote Terminal';
2177
+ this.ws.httprequest.process = term;
2178
+ this.ws.httprequest.process.tunnel = this.ws;
2179
+ term.stderr.stdout = term.stdout;
2180
+ term.stderr.on('data', terminal_stderr_ondata);
2181
+ stdoutstream = term.stdout;
2182
+ stdinstream = term.stdin;
2183
+ this.ws.prependListener('end', terminal_onend);
2184
+ term.prependListener('exit', terminal_onexit);
2185
+ }
2186
+
2187
+ this.ws.removeAllListeners('data');
2188
+ this.ws.on('data', onTunnelControlData);
2189
+
2190
+ stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2191
+ this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2192
+
2193
+ // Add the terminal session to the count to update the server
2194
+ if (this.ws.httprequest.userid != null)
2195
+ {
2196
+ var userid = getUserIdAndGuestNameFromHttpRequest(this.ws.httprequest);
2197
+ if (tunnelUserCount.terminal[userid] == null) { tunnelUserCount.terminal[userid] = 1; } else { tunnelUserCount.terminal[userid]++; }
2198
+ try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { }
2199
+ broadcastSessionsToRegisteredApps();
2200
+ }
2201
+
2202
+ // Toast Notification, if required
2203
+ if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2))
2204
+ {
2205
+ // User Notifications is required
2206
+ var notifyMessage = currentTranslation['terminalNotify'].replace('{0}', this.ws.httprequest.username);
2207
+ var notifyTitle = "MeshCentral";
2208
+ if (this.ws.httprequest.soptions != null)
2209
+ {
2210
+ if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2211
+ if (this.ws.httprequest.soptions.notifyMsgTerminal != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgTerminal.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); }
2212
+ }
2213
+ try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
2214
+ }
2215
+ this.ws = null;
2216
+}
2217
+function terminal_promise_consent_rejected(e)
2218
+{
2219
+ // DO NOT start terminal
2220
+ this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2221
+ this.that.end();
2222
+
2223
+ this.that = null;
2224
+ this.httprequest = null;
2225
+}
2226
+function promise_init(res, rej) { this._res = res; this._rej = rej; }
2227
+function terminal_userpromise_resolved(u)
2228
+{
2229
+
2230
+ var that = this.that;
2231
+ if (u.Active.length > 0)
2232
+ {
2233
+ var tmp;
2234
+ var username = '"' + u.Active[0].Domain + '\\' + u.Active[0].Username + '"';
2235
+
2236
+
2237
+ if (require('win-virtual-terminal').supported)
2238
+ {
2239
+ // ConPTY PseudoTerminal
2240
+ tmp = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [this.cols, this.rows] } });
2241
+ }
2242
+ else
2243
+ {
2244
+ // Legacy Terminal
2245
+ tmp = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-terminal', script: getJSModule('win-terminal') }], launch: { module: 'win-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [this.cols, this.rows] } });
2246
+ }
2247
+ that.httprequest._dispatcher = tmp;
2248
+ that.httprequest._dispatcher.connectionPromise = that.httprequest.connectionPromise;
2249
+ that.httprequest._dispatcher.on('connection', terminal_user_onconnection);
2250
+ that.httprequest._dispatcher.on('~', terminal_onfinalized);
2251
+ }
2252
+ this.that = null;
2253
+ that = null;
2254
+}
2255
+
2256
+function terminal_promise_consent_resolved()
2257
+{
2258
+ this.httprequest.connectionPromise = new promise(promise_init);
2259
+ this.httprequest.connectionPromise.ws = this.that;
2260
+
2261
+ // Start Terminal
2262
+ if (process.platform == 'win32')
2263
+ {
2264
+ try
2265
+ {
2266
+ var cols = 80, rows = 25;
2267
+ if (this.httprequest.xoptions)
2268
+ {
2269
+ if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
2270
+ if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
2271
+ }
2272
+
2273
+ if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6))
2274
+ {
2275
+ // Admin Terminal
2276
+ if (require('win-virtual-terminal').supported)
2277
+ {
2278
+ // ConPTY PseudoTerminal
2279
+ // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
2280
+
2281
+ // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
2282
+ this.httprequest._dispatcher = require('win-dispatcher').dispatch({ modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } });
2283
+ this.httprequest._dispatcher.httprequest = this.httprequest;
2284
+ this.httprequest._dispatcher.on('connection', terminal_onconnection);
2285
+ this.httprequest._dispatcher.on('~', terminal_onfinalized);
2286
+ }
2287
+ else
2288
+ {
2289
+ // Legacy Terminal
2290
+ this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows));
2291
+ }
2292
+ }
2293
+ else
2294
+ {
2295
+ // Logged in user
2296
+ var userPromise = require('user-sessions').enumerateUsers();
2297
+ userPromise.that = this;
2298
+ userPromise.cols = cols;
2299
+ userPromise.rows = rows;
2300
+ userPromise.then(terminal_userpromise_resolved);
2301
+ }
2302
+ } catch (ex)
2303
+ {
2304
+ this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString());
2305
+ }
2306
+ }
2307
+ else
2308
+ {
2309
+ try
2310
+ {
2311
+ var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false;
2312
+ var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false;
2313
+ var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login';
2314
+
2315
+ var env = { HISTCONTROL: 'ignoreboth' };
2316
+ if (process.env['LANG']) { env['LANG'] = process.env['LANG']; }
2317
+ if (process.env['PATH']) { env['PATH'] = process.env['PATH']; }
2318
+ if (this.httprequest.xoptions)
2319
+ {
2320
+ if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
2321
+ if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
2322
+ }
2323
+ var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
2324
+ if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin)
2325
+ {
2326
+ if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); }
2327
+ this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell
2328
+ }
2329
+ else if (bash)
2330
+ {
2331
+ var p = childProcess.execFile(bash, ['bash'], options); // Start bash
2332
+ // Spaces at the beginning of lines are needed to hide commands from the command history
2333
+ if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string'))
2334
+ {
2335
+ if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); }
2336
+ } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
2337
+ this.httprequest.connectionPromise._res(p);
2338
+ }
2339
+ else if (sh)
2340
+ {
2341
+ var p = childProcess.execFile(sh, ['sh'], options); // Start sh
2342
+ // Spaces at the beginning of lines are needed to hide commands from the command history
2343
+ if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string'))
2344
+ {
2345
+ if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); }
2346
+ } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
2347
+ this.httprequest.connectionPromise._res(p);
2348
+ }
2349
+ else
2350
+ {
2351
+ this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found');
2352
+ }
2353
+ } catch (ex)
2354
+ {
2355
+ this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString());
2356
+ }
2357
+ }
2358
+
2359
+ this.httprequest.connectionPromise.then(terminal_promise_connection_resolved, terminal_promise_connection_rejected);
2360
+ this.that = null;
2361
+ this.httprequest = null;
2362
+}
2363
+function tunnel_kvm_end()
2364
+{
2365
+ --this.desktop.kvm.connectionCount;
2366
+
2367
+ // Remove ourself from the list of remote desktop session
2368
+ var i = this.desktop.kvm.tunnels.indexOf(this);
2369
+ if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); }
2370
+
2371
+ // Send a metadata update to all desktop sessions
2372
+ var users = {};
2373
+ if (this.httprequest.desktop.kvm.tunnels != null)
2374
+ {
2375
+ for (var i in this.httprequest.desktop.kvm.tunnels)
2376
+ {
2377
+ try
2378
+ {
2379
+ var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest);
2380
+ if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; }
2381
+ } catch (ex) { sendConsoleText(ex); }
2382
+ }
2383
+ for (var i in this.httprequest.desktop.kvm.tunnels)
2384
+ {
2385
+ try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { }
2386
+ }
2387
+ tunnelUserCount.desktop = users;
2388
+ try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { }
2389
+ broadcastSessionsToRegisteredApps();
2390
+ }
2391
+
2392
+ // Unpipe the web socket
2393
+ try
2394
+ {
2395
+ this.unpipe(this.httprequest.desktop.kvm);
2396
+ this.httprequest.desktop.kvm.unpipe(this);
2397
+ } catch (ex) { }
2398
+
2399
+ // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
2400
+ if (this.rtcchannel)
2401
+ {
2402
+ try
2403
+ {
2404
+ this.rtcchannel.unpipe(this.httprequest.desktop.kvm);
2405
+ this.httprequest.desktop.kvm.unpipe(this.rtcchannel);
2406
+ }
2407
+ catch (ex) { }
2408
+ }
2409
+
2410
+ // Place wallpaper back if needed
2411
+ // TODO
2412
+
2413
+ if (this.desktop.kvm.connectionCount == 0)
2414
+ {
2415
+ // Display a toast message. This may not be supported on all platforms.
2416
+ // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { }
2417
+
2418
+ this.httprequest.desktop.kvm.end();
2419
+ if (this.httprequest.desktop.kvm.connectionBar)
2420
+ {
2421
+ this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2422
+ this.httprequest.desktop.kvm.connectionBar.close();
2423
+ this.httprequest.desktop.kvm.connectionBar = null;
2424
+ }
2425
+ } else
2426
+ {
2427
+ for (var i in this.httprequest.desktop.kvm.users)
2428
+ {
2429
+ if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar)
2430
+ {
2431
+ for (var j in this.httprequest.desktop.kvm.rusers) { if (this.httprequest.desktop.kvm.rusers[j] == this.httprequest.realname) { this.httprequest.desktop.kvm.rusers.splice(j, 1); break; } }
2432
+ this.httprequest.desktop.kvm.users.splice(i, 1);
2433
+ this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2434
+ this.httprequest.desktop.kvm.connectionBar.close();
2435
+ this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid, color_options);
2436
+ this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
2437
+ this.httprequest.desktop.kvm.connectionBar.on('close', function ()
2438
+ {
2439
+ MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
2440
+ for (var i in this.httprequest.desktop.kvm._pipedStreams)
2441
+ {
2442
+ this.httprequest.desktop.kvm._pipedStreams[i].end();
2443
+ }
2444
+ this.httprequest.desktop.kvm.end();
2445
+ });
2446
+ break;
2447
+ }
2448
+ }
2449
+ }
2450
+
2451
+ if(this.httprequest.desktop.kvm.connectionBar)
2452
+ {
2453
+ console.info1('Setting ConnectionBar request to NULL');
2454
+ this.httprequest.desktop.kvm.connectionBar.httprequest = null;
2455
+ }
2456
+
2457
+ this.httprequest = null;
2458
+ this.desktop.tunnel = null;
2459
+}
2460
+
2461
+function kvm_tunnel_consentpromise_closehandler()
2462
+{
2463
+ if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
2464
+}
2465
+
2466
+function kvm_consentpromise_rejected(e)
2467
+{
2468
+ // User Consent Denied/Failed
2469
+ this.ws._consentpromise = null;
2470
+ MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2471
+ this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2472
+ this.ws = null;
2473
+}
2474
+function kvm_consentpromise_resolved(always)
2475
+{
2476
+ if (always && process.platform=='win32') { server_set_consentTimer(this.ws.httprequest.userid); }
2477
+
2478
+ // Success
2479
+ this.ws._consentpromise = null;
2480
+ MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2481
+ this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
2482
+ if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1))
2483
+ {
2484
+ // User Notifications is required
2485
+ var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', this.ws.httprequest.realname);
2486
+ var notifyTitle = "MeshCentral";
2487
+ if (this.ws.httprequest.soptions != null)
2488
+ {
2489
+ if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2490
+ if (this.ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); }
2491
+ }
2492
+ try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { }
2493
+ }
2494
+ if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40))
2495
+ {
2496
+ // Connection Bar is required
2497
+ if (this.ws.httprequest.desktop.kvm.connectionBar)
2498
+ {
2499
+ this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2500
+ this.ws.httprequest.desktop.kvm.connectionBar.close();
2501
+ }
2502
+ try
2503
+ {
2504
+ this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace('{0}', this.ws.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.ws.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid, color_options);
2505
+ MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2506
+ } catch (ex)
2507
+ {
2508
+ if (process.platform != 'darwin')
2509
+ {
2510
+ MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2511
+ }
2512
+ }
2513
+ if (this.ws.httprequest.desktop.kvm.connectionBar)
2514
+ {
2515
+ this.ws.httprequest.desktop.kvm.connectionBar.state =
2516
+ {
2517
+ userid: this.ws.httprequest.userid,
2518
+ xuserid: this.ws.httprequest.xuserid,
2519
+ username: this.ws.httprequest.username,
2520
+ sessionid: this.ws.httprequest.sessionid,
2521
+ remoteaddr: this.ws.httprequest.remoteaddr,
2522
+ guestname: this.ws.httprequest.guestname,
2523
+ desktop: this.ws.httprequest.desktop
2524
+ };
2525
+ this.ws.httprequest.desktop.kvm.connectionBar.on('close', function ()
2526
+ {
2527
+ MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", state);
2528
+ for (var i in this.state.desktop.kvm._pipedStreams)
2529
+ {
2530
+ this.state.desktop.kvm._pipedStreams[i].end();
2531
+ }
2532
+ this.state.desktop.kvm.end();
2533
+ });
2534
+ }
2535
+ }
2536
+ this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 });
2537
+ if (this.ws.httprequest.autolock)
2538
+ {
2539
+ destopLockHelper_pipe(this.ws.httprequest);
2540
+ }
2541
+ this.ws.resume();
2542
+ this.ws = null;
2543
+}
2544
+
2545
+function files_consentpromise_resolved(always)
2546
+{
2547
+ if (always && process.platform == 'win32') { server_set_consentTimer(this.ws.httprequest.userid); }
2548
+
2549
+ // Success
2550
+ this.ws._consentpromise = null;
2551
+ MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2552
+ this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
2553
+ if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4))
2554
+ {
2555
+ // User Notifications is required
2556
+ var notifyMessage = currentTranslation['fileNotify'].replace('{0}', this.ws.httprequest.realname);
2557
+ var notifyTitle = "MeshCentral";
2558
+ if (this.ws.httprequest.soptions != null)
2559
+ {
2560
+ if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2561
+ if (this.ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgFiles.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); }
2562
+ }
2563
+ try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
2564
+ }
2565
+ this.ws.resume();
2566
+ this.ws = null;
2567
+}
2568
+function files_consentpromise_rejected(e)
2569
+{
2570
+ // User Consent Denied/Failed
2571
+ this.ws._consentpromise = null;
2572
+ MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2573
+ this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2574
+ this.ws = null;
2575
+}
2576
+function files_tunnel_endhandler()
2577
+{
2578
+ if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); }
2579
+}
2580
+
2581
+function onTunnelData(data)
2582
+{
2583
//sendConsoleText('OnTunnelData, ' + data.length + ', ' + typeof data + ', ' + data);
2584
2585
// If this is upload data, save it to file
@@ -2080,7 +2641,8 @@ function onTunnelData(data) {
2641
//
2642
2643
// Check user access rights for terminal
2083
- if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0))) {
2644
+ if (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) == 0) || ((this.httprequest.rights != 0xFFFFFFFF) && ((this.httprequest.rights & MESHRIGHT_NOTERMINAL) != 0)))
2645
+ {
2646
// Disengage this tunnel, user does not have the rights to do this!!
2647
this.httprequest.protocol = 999999;
2648
this.httprequest.s.end();
@@ -2090,7 +2652,8 @@ function onTunnelData(data) {
2652
2653
this.descriptorMetadata = "Remote Terminal";
2654
2093
- if (process.platform == 'win32') {
2655
+ if (process.platform == 'win32')
2656
+ {
2657
if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) {
2658
this.httprequest.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: 'PowerShell is not supported on this version of windows', msgid: 1 }));
2659
this.httprequest.s.end();
@@ -2099,53 +2662,29 @@ function onTunnelData(data) {
2662
}
2663
2664
var prom = require('promise');
2102
- this.httprequest.tpromise = new prom(function (res, rej) { this._res = res; this._rej = rej; });
2665
+ this.httprequest.tpromise = new prom(promise_init);
2666
this.httprequest.tpromise.that = this;
2667
this.httprequest.tpromise.httprequest = this.httprequest;
2105
-
2106
- this.end = function () {
2107
- if (this.httprequest.tpromise._consent) { this.httprequest.tpromise._consent.close(); }
2108
- if (this.httprequest.connectionPromise) { this.httprequest.connectionPromise._rej('Closed'); }
2109
-
2110
- // Remove the terminal session to the count to update the server
2111
- if (this.httprequest.userid != null) {
2112
- var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
2113
- if (tunnelUserCount.terminal[userid] != null) { tunnelUserCount.terminal[userid]--; if (tunnelUserCount.terminal[userid] <= 0) { delete tunnelUserCount.terminal[userid]; } }
2114
- try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { }
2115
- broadcastSessionsToRegisteredApps();
2116
- }
2117
-
2118
- if (process.platform == 'win32') {
2119
- // Unpipe the web socket
2120
- this.unpipe(this.httprequest._term);
2121
- if (this.httprequest._term) { this.httprequest._term.unpipe(this); }
2122
-
2123
- // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
2124
- if (this.rtcchannel) {
2125
- this.rtcchannel.unpipe(this.httprequest._term);
2126
- if (this.httprequest._term) { this.httprequest._term.unpipe(this.rtcchannel); }
2127
- }
2128
-
2129
- // Clean up
2130
- if (this.httprequest._term) { this.httprequest._term.end(); }
2131
- this.httprequest._term = null;
2132
- }
2133
- };
2668
+ this.end = terminal_end;
2669
2670
// Perform User-Consent if needed.
2136
- if (this.httprequest.consent && (this.httprequest.consent & 16)) {
2671
+ if (this.httprequest.consent && (this.httprequest.consent & 16))
2672
+ {
2673
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
2674
var consentMessage = currentTranslation['terminalConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username);
2675
var consentTitle = 'MeshCentral';
2676
2141
- if (this.httprequest.soptions != null) {
2677
+ if (this.httprequest.soptions != null)
2678
+ {
2679
if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
2680
if (this.httprequest.soptions.consentMsgTerminal != null) { consentMessage = this.httprequest.soptions.consentMsgTerminal.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
2681
}
2145
- if (process.platform == 'win32') {
2682
+ if (process.platform == 'win32')
2683
+ {
2684
var enhanced = false;
2685
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
2148
- if (enhanced) {
2686
+ if (enhanced)
2687
+ {
2688
var ipr = server_getUserImage(this.httprequest.userid);
2689
ipr.consentTitle = consentTitle;
2690
ipr.consentMessage = consentMessage;
@@ -2153,21 +2692,25 @@ function onTunnelData(data) {
2692
ipr.consentAutoAccept = this.httprequest.consentAutoAccept;
2693
ipr.username = this.httprequest.realname;
2694
ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
2156
- this.httprequest.tpromise._consent = ipr.then(function (img) {
2695
+ this.httprequest.tpromise._consent = ipr.then(function (img)
2696
+ {
2697
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
2698
this.__childPromise.close = this.consent.close.bind(this.consent);
2699
return (this.consent);
2700
});
2161
- } else {
2162
- this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.consentTimeout);
2701
+ } else
2702
+ {
2703
+ this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout);
2704
}
2164
- } else {
2165
- this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.consentTimeout);
2705
+ } else
2706
+ {
2707
+ this.httprequest.tpromise._consent = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout);
2708
}
2709
this.httprequest.tpromise._consent.retPromise = this.httprequest.tpromise;
2710
this.httprequest.tpromise._consent.then(
2169
- function (always) {
2170
- if (always) { server_set_consentTimer(this.retPromise.httprequest.userid); }
2711
+ function (always)
2712
+ {
2713
+ if (always && process.platform == 'win32') { server_set_consentTimer(this.retPromise.httprequest.userid); }
2714
2715
// Success
2716
MeshServerLogEx(27, null, "Local user accepted remote terminal request (" + this.retPromise.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
@@ -2179,174 +2722,21 @@ function onTunnelData(data) {
2722
// Denied
2723
MeshServerLogEx(28, null, "Local user rejected remote terminal request (" + this.retPromise.that.httprequest.remoteaddr + ")", this.retPromise.that.httprequest);
2724
this.retPromise.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2725
+ this.retPromise._consent = null;
2726
this.retPromise._rej(e.toString());
2727
});
2728
}
2185
- else {
2729
+ else
2730
+ {
2731
// User-Consent is not required, so just resolve this promise
2732
this.httprequest.tpromise._res();
2733
}
2734
2735
2191
- this.httprequest.tpromise.then(
2192
- function () {
2193
- this.httprequest.connectionPromise = new prom(function (res, rej) { this._res = res; this._rej = rej; });
2194
- this.httprequest.connectionPromise.ws = this.that;
2195
-
2196
- // Start Terminal
2197
- if (process.platform == 'win32') {
2198
- try {
2199
- var cols = 80, rows = 25;
2200
- if (this.httprequest.xoptions) {
2201
- if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
2202
- if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
2203
- }
2204
-
2205
- if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6)) {
2206
- // Admin Terminal
2207
- if (require('win-virtual-terminal').supported) {
2208
- // ConPTY PseudoTerminal
2209
- // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
2210
-
2211
- // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
2212
- this.httprequest._dispatcher = require('win-dispatcher').dispatch({ modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } });
2213
- this.httprequest._dispatcher.httprequest = this.httprequest;
2214
- this.httprequest._dispatcher.on('connection', function (c) { if (this.httprequest.connectionPromise.completed) { c.end(); } else { this.httprequest.connectionPromise._res(c); } });
2215
- }
2216
- else {
2217
- // Legacy Terminal
2218
- this.httprequest.connectionPromise._res(require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows));
2219
- }
2220
- }
2221
- else {
2222
- // Logged in user
2223
- var userPromise = require('user-sessions').enumerateUsers();
2224
- userPromise.that = this;
2225
- userPromise.then(function (u) {
2226
- var that = this.that;
2227
- if (u.Active.length > 0) {
2228
- var username = '"' + u.Active[0].Domain + '\\' + u.Active[0].Username + '"';
2229
- //sendConsoleText('Terminal: ' + username);
2230
- if (require('win-virtual-terminal').supported) {
2231
- // ConPTY PseudoTerminal
2232
- that.httprequest._dispatcher = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } });
2233
- }
2234
- else {
2235
- // Legacy Terminal
2236
- that.httprequest._dispatcher = require('win-dispatcher').dispatch({ user: username, modules: [{ name: 'win-terminal', script: getJSModule('win-terminal') }], launch: { module: 'win-terminal', method: (that.httprequest.protocol == 9 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } });
2237
- }
2238
- that.httprequest._dispatcher.ws = that;
2239
- that.httprequest._dispatcher.on('connection', function (c) { if (this.ws.httprequest.connectionPromise.completed) { c.end(); } else { this.ws.httprequest.connectionPromise._res(c); } });
2240
- }
2241
- });
2242
- }
2243
- } catch (ex) {
2244
- this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString());
2245
- }
2246
- }
2247
- else {
2248
- try {
2249
- var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false;
2250
- var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false;
2251
- var login = process.platform == 'linux' ? '/bin/login' : '/usr/bin/login';
2252
-
2253
- var env = { HISTCONTROL: 'ignoreboth' };
2254
- if (process.env['LANG']) { env['LANG'] = process.env['LANG']; }
2255
- if (process.env['PATH']) { env['PATH'] = process.env['PATH']; }
2256
- if (this.httprequest.xoptions) {
2257
- if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
2258
- if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
2259
- }
2260
- var options = { type: childProcess.SpawnTypes.TERM, uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
2261
- if (this.httprequest.xoptions && this.httprequest.xoptions.requireLogin) {
2262
- if (!require('fs').existsSync(login)) { throw ('Unable to spawn login process'); }
2263
- this.httprequest.connectionPromise._res(childProcess.execFile(login, ['login'], options)); // Start login shell
2264
- }
2265
- else if (bash) {
2266
- var p = childProcess.execFile(bash, ['bash'], options); // Start bash
2267
- // Spaces at the beginning of lines are needed to hide commands from the command history
2268
- if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string')) {
2269
- if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); }
2270
- } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
2271
- this.httprequest.connectionPromise._res(p);
2272
- }
2273
- else if (sh) {
2274
- var p = childProcess.execFile(sh, ['sh'], options); // Start sh
2275
- // Spaces at the beginning of lines are needed to hide commands from the command history
2276
- if ((obj.serverInfo.termlaunchcommand != null) && (typeof obj.serverInfo.termlaunchcommand[process.platform] == 'string')) {
2277
- if (obj.serverInfo.termlaunchcommand[process.platform] != '') { p.stdin.write(obj.serverInfo.termlaunchcommand[process.platform]); }
2278
- } else if (process.platform == 'linux') { p.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
2279
- this.httprequest.connectionPromise._res(p);
2280
- }
2281
- else {
2282
- this.httprequest.connectionPromise._rej('Failed to start remote terminal session, no shell found');
2283
- }
2284
- } catch (ex) {
2285
- this.httprequest.connectionPromise._rej('Failed to start remote terminal session, ' + ex.toString());
2286
- }
2287
- }
2288
-
2289
- this.httprequest.connectionPromise.then(
2290
- function (term) {
2291
- // SUCCESS
2292
- var stdoutstream;
2293
- var stdinstream;
2294
- if (process.platform == 'win32') {
2295
- this.ws.httprequest._term = term;
2296
- this.ws.httprequest._term.tunnel = this.ws;
2297
- stdoutstream = stdinstream = term;
2298
- }
2299
- else {
2300
- term.descriptorMetadata = 'Remote Terminal';
2301
- this.ws.httprequest.process = term;
2302
- this.ws.httprequest.process.tunnel = this.ws;
2303
- term.stderr.stdout = term.stdout;
2304
- term.stderr.on('data', function (c) { this.stdout.write(c); });
2305
- stdoutstream = term.stdout;
2306
- stdinstream = term.stdin;
2307
- this.ws.prependListener('end', function () { this.httprequest.process.kill(); });
2308
- term.prependListener('exit', function () { this.tunnel.end(); });
2309
- }
2310
-
2311
- this.ws.removeAllListeners('data');
2312
- this.ws.on('data', onTunnelControlData);
2313
-
2314
- stdoutstream.pipe(this.ws, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
2315
- this.ws.pipe(stdinstream, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
2316
-
2317
- // Add the terminal session to the count to update the server
2318
- if (this.ws.httprequest.userid != null) {
2319
- var userid = getUserIdAndGuestNameFromHttpRequest(this.ws.httprequest);
2320
- if (tunnelUserCount.terminal[userid] == null) { tunnelUserCount.terminal[userid] = 1; } else { tunnelUserCount.terminal[userid]++; }
2321
- try { mesh.SendCommand({ action: 'sessions', type: 'terminal', value: tunnelUserCount.terminal }); } catch (ex) { }
2322
- broadcastSessionsToRegisteredApps();
2323
- }
2324
-
2325
- // Toast Notification, if required
2326
- if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 2)) {
2327
- // User Notifications is required
2328
- var notifyMessage = currentTranslation['terminalNotify'].replace('{0}', this.ws.httprequest.username);
2329
- var notifyTitle = "MeshCentral";
2330
- if (this.ws.httprequest.soptions != null) {
2331
- if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2332
- if (this.ws.httprequest.soptions.notifyMsgTerminal != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgTerminal.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); }
2333
- }
2334
- try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
2335
- }
2336
- },
2337
- function (e) {
2338
- // FAILED to connect terminal
2339
- this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2340
- this.ws.end();
2341
- });
2342
- },
2343
- function (e) {
2344
- // DO NOT start terminal
2345
- this.that.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2346
- this.that.end();
2347
- });
2736
+ this.httprequest.tpromise.then(terminal_promise_consent_resolved, terminal_promise_consent_rejected);
2737
}
2349
- else if (this.httprequest.protocol == 2) {
2738
+ else if (this.httprequest.protocol == 2)
2739
+ {
2740
//
2741
// Remote Desktop
2742
//
@@ -2378,14 +2768,17 @@ function onTunnelData(data) {
2768
2769
// Send a metadata update to all desktop sessions
2770
var users = {};
2381
- if (this.httprequest.desktop.kvm.tunnels != null) {
2382
- for (var i in this.httprequest.desktop.kvm.tunnels) {
2771
+ if (this.httprequest.desktop.kvm.tunnels != null)
2772
+ {
2773
+ for (var i in this.httprequest.desktop.kvm.tunnels)
2774
+ {
2775
try {
2776
var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest);
2777
if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; }
2778
} catch (ex) { sendConsoleText(ex); }
2779
}
2388
- for (var i in this.httprequest.desktop.kvm.tunnels) {
2780
+ for (var i in this.httprequest.desktop.kvm.tunnels)
2781
+ {
2782
try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { }
2783
}
2784
tunnelUserCount.desktop = users;
@@ -2393,79 +2786,8 @@ function onTunnelData(data) {
2786
broadcastSessionsToRegisteredApps();
2787
}
2788
2396
- this.end = function () {
2397
- --this.desktop.kvm.connectionCount;
2398
-
2399
- // Remove ourself from the list of remote desktop session
2400
- var i = this.desktop.kvm.tunnels.indexOf(this);
2401
- if (i >= 0) { this.desktop.kvm.tunnels.splice(i, 1); }
2402
-
2403
- // Send a metadata update to all desktop sessions
2404
- var users = {};
2405
- if (this.httprequest.desktop.kvm.tunnels != null) {
2406
- for (var i in this.httprequest.desktop.kvm.tunnels) {
2407
- try {
2408
- var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest.desktop.kvm.tunnels[i].httprequest);
2409
- if (users[userid] == null) { users[userid] = 1; } else { users[userid]++; }
2410
- } catch (ex) { sendConsoleText(ex); }
2411
- }
2412
- for (var i in this.httprequest.desktop.kvm.tunnels) {
2413
- try { this.httprequest.desktop.kvm.tunnels[i].write(JSON.stringify({ ctrlChannel: '102938', type: 'metadata', users: users })); } catch (ex) { }
2414
- }
2415
- tunnelUserCount.desktop = users;
2416
- try { mesh.SendCommand({ action: 'sessions', type: 'kvm', value: users }); } catch (ex) { }
2417
- broadcastSessionsToRegisteredApps();
2418
- }
2789
+ this.end = tunnel_kvm_end;
2790
2420
- // Unpipe the web socket
2421
- try {
2422
- this.unpipe(this.httprequest.desktop.kvm);
2423
- this.httprequest.desktop.kvm.unpipe(this);
2424
- } catch (ex) { }
2425
-
2426
- // Unpipe the WebRTC channel if needed (This will also be done when the WebRTC channel ends).
2427
- if (this.rtcchannel) {
2428
- try {
2429
- this.rtcchannel.unpipe(this.httprequest.desktop.kvm);
2430
- this.httprequest.desktop.kvm.unpipe(this.rtcchannel);
2431
- }
2432
- catch (ex) { }
2433
- }
2434
-
2435
- // Place wallpaper back if needed
2436
- // TODO
2437
-
2438
- if (this.desktop.kvm.connectionCount == 0) {
2439
- // Display a toast message. This may not be supported on all platforms.
2440
- // try { require('toaster').Toast('MeshCentral', 'Remote Desktop Control Ended.'); } catch (ex) { }
2441
-
2442
- this.httprequest.desktop.kvm.end();
2443
- if (this.httprequest.desktop.kvm.connectionBar) {
2444
- this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2445
- this.httprequest.desktop.kvm.connectionBar.close();
2446
- this.httprequest.desktop.kvm.connectionBar = null;
2447
- }
2448
- } else {
2449
- for (var i in this.httprequest.desktop.kvm.users) {
2450
- if ((this.httprequest.desktop.kvm.users[i] == this.httprequest.username) && this.httprequest.desktop.kvm.connectionBar) {
2451
- for (var j in this.httprequest.desktop.kvm.rusers) { if (this.httprequest.desktop.kvm.rusers[j] == this.httprequest.realname) { this.httprequest.desktop.kvm.rusers.splice(j, 1); break; } }
2452
- this.httprequest.desktop.kvm.users.splice(i, 1);
2453
- this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2454
- this.httprequest.desktop.kvm.connectionBar.close();
2455
- this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid, color_options);
2456
- this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
2457
- this.httprequest.desktop.kvm.connectionBar.on('close', function () {
2458
- MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
2459
- for (var i in this.httprequest.desktop.kvm._pipedStreams) {
2460
- this.httprequest.desktop.kvm._pipedStreams[i].end();
2461
- }
2462
- this.httprequest.desktop.kvm.end();
2463
- });
2464
- break;
2465
- }
2466
- }
2467
- }
2468
- };
2791
if (this.httprequest.desktop.kvm.hasOwnProperty('connectionCount')) {
2792
this.httprequest.desktop.kvm.connectionCount++;
2793
this.httprequest.desktop.kvm.rusers.push(this.httprequest.realname);
@@ -2478,31 +2800,38 @@ function onTunnelData(data) {
2800
this.httprequest.desktop.kvm.users = [this.httprequest.username];
2801
}
2802
2481
- if ((this.httprequest.desktopviewonly != true) && ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0)))) {
2803
+ if ((this.httprequest.desktopviewonly != true) && ((this.httprequest.rights == 0xFFFFFFFF) || (((this.httprequest.rights & MESHRIGHT_REMOTECONTROL) != 0) && ((this.httprequest.rights & MESHRIGHT_REMOTEVIEW) == 0))))
2804
+ {
2805
// If we have remote control rights, pipe the KVM input
2806
this.pipe(this.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text. Pipe the Browser --> KVM input.
2484
- } else {
2807
+ }
2808
+ else
2809
+ {
2810
// We need to only pipe non-mouse & non-keyboard inputs.
2811
// sendConsoleText('Warning: No Remote Desktop Input Rights.');
2812
// TODO!!!
2813
}
2814
2815
// Perform notification if needed. Toast messages may not be supported on all platforms.
2491
- if (this.httprequest.consent && (this.httprequest.consent & 8)) {
2816
+ if (this.httprequest.consent && (this.httprequest.consent & 8))
2817
+ {
2818
// User Consent Prompt is required
2819
// Send a console message back using the console channel, "\n" is supported.
2820
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
2821
var consentMessage = currentTranslation['desktopConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username);
2822
var consentTitle = 'MeshCentral';
2497
- if (this.httprequest.soptions != null) {
2823
+ if (this.httprequest.soptions != null)
2824
+ {
2825
if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
2826
if (this.httprequest.soptions.consentMsgDesktop != null) { consentMessage = this.httprequest.soptions.consentMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
2827
}
2828
var pr;
2502
- if (process.platform == 'win32') {
2829
+ if (process.platform == 'win32')
2830
+ {
2831
var enhanced = false;
2832
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
2505
- if (enhanced) {
2833
+ if (enhanced)
2834
+ {
2835
var ipr = server_getUserImage(this.httprequest.userid);
2836
ipr.consentTitle = consentTitle;
2837
ipr.consentMessage = consentMessage;
@@ -2511,85 +2840,33 @@ function onTunnelData(data) {
2840
ipr.tsid = tsid;
2841
ipr.username = this.httprequest.realname;
2842
ipr.translation = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
2514
- pr = ipr.then(function (img) {
2843
+ pr = ipr.then(function (img)
2844
+ {
2845
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), uid: this.tsid, timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translation, background: color_options.background, foreground: color_options.foreground });
2846
this.__childPromise.close = this.consent.close.bind(this.consent);
2847
return (this.consent);
2848
});
2849
}
2520
- else {
2521
- pr = require('message-box').create(consentTitle, consentMessage, this.consentTimeout, null, tsid);
2850
+ else
2851
+ {
2852
+ pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null, tsid);
2853
}
2854
}
2524
- else {
2525
- pr = require('message-box').create(consentTitle, consentMessage, this.consentTimeout, null, tsid);
2855
+ else
2856
+ {
2857
+ pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null, tsid);
2858
}
2859
pr.ws = this;
2860
this.pause();
2861
this._consentpromise = pr;
2530
- this.prependOnceListener('end', function () {
2531
- if (this._consentpromise && this._consentpromise.close) {
2532
- this._consentpromise.close();
2533
- }
2534
- });
2535
- pr.then(
2536
- function (always) {
2537
- if (always) { server_set_consentTimer(this.ws.httprequest.userid); }
2538
-
2539
- // Success
2540
- this.ws._consentpromise = null;
2541
- MeshServerLogEx(30, null, "Starting remote desktop after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2542
- this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null, msgid: 0 }));
2543
- if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 1)) {
2544
- // User Notifications is required
2545
- var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', this.ws.httprequest.realname);
2546
- var notifyTitle = "MeshCentral";
2547
- if (this.ws.httprequest.soptions != null) {
2548
- if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2549
- if (this.ws.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); }
2550
- }
2551
- try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { }
2552
- }
2553
- if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 0x40)) {
2554
- // Connection Bar is required
2555
- if (this.ws.httprequest.desktop.kvm.connectionBar) {
2556
- this.ws.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2557
- this.ws.httprequest.desktop.kvm.connectionBar.close();
2558
- }
2559
- try {
2560
- this.ws.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.ws.httprequest.privacybartext.replace('{0}', this.ws.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.ws.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid, color_options);
2561
- MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2562
- } catch (ex) {
2563
- if (process.platform != 'darwin') {
2564
- MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or Not Supported (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2565
- }
2566
- }
2567
- if (this.ws.httprequest.desktop.kvm.connectionBar) {
2568
- this.ws.httprequest.desktop.kvm.connectionBar.httprequest = this.ws.httprequest;
2569
- this.ws.httprequest.desktop.kvm.connectionBar.on('close', function () {
2570
- MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
2571
- for (var i in this.httprequest.desktop.kvm._pipedStreams) {
2572
- this.httprequest.desktop.kvm._pipedStreams[i].end();
2573
- }
2574
- this.httprequest.desktop.kvm.end();
2575
- });
2576
- }
2577
- }
2578
- this.ws.httprequest.desktop.kvm.pipe(this.ws, { dataTypeSkip: 1 });
2579
- if (this.ws.httprequest.autolock) {
2580
- destopLockHelper_pipe(this.ws.httprequest);
2581
- }
2582
- this.ws.resume();
2583
- },
2584
- function (e) {
2585
- // User Consent Denied/Failed
2586
- this.ws._consentpromise = null;
2587
- MeshServerLogEx(34, null, "Failed to start remote desktop after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2588
- this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2589
- });
2590
- } else {
2862
+ this.prependOnceListener('end', kvm_tunnel_consentpromise_closehandler);
2863
+ pr.then(kvm_consentpromise_resolved, kvm_consentpromise_rejected);
2864
+ }
2865
+ else
2866
+ {
2867
// User Consent Prompt is not required
2592
- if (this.httprequest.consent && (this.httprequest.consent & 1)) {
2868
+ if (this.httprequest.consent && (this.httprequest.consent & 1))
2869
+ {
2870
// User Notifications is required
2871
MeshServerLogEx(35, null, "Started remote desktop with toast notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
2872
var notifyMessage = currentTranslation['desktopNotify'].replace('{0}', this.httprequest.realname);
@@ -2599,34 +2876,52 @@ function onTunnelData(data) {
2876
if (this.httprequest.soptions.notifyMsgDesktop != null) { notifyMessage = this.httprequest.soptions.notifyMsgDesktop.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
2877
}
2878
try { require('toaster').Toast(notifyTitle, notifyMessage, tsid); } catch (ex) { }
2602
- } else {
2879
+ } else
2880
+ {
2881
MeshServerLogEx(36, null, "Started remote desktop without notification (" + this.httprequest.remoteaddr + ")", this.httprequest);
2882
}
2605
- if (this.httprequest.consent && (this.httprequest.consent & 0x40)) {
2883
+ if (this.httprequest.consent && (this.httprequest.consent & 0x40))
2884
+ {
2885
// Connection Bar is required
2607
- if (this.httprequest.desktop.kvm.connectionBar) {
2886
+ if (this.httprequest.desktop.kvm.connectionBar)
2887
+ {
2888
this.httprequest.desktop.kvm.connectionBar.removeAllListeners('close');
2889
this.httprequest.desktop.kvm.connectionBar.close();
2890
}
2611
- try {
2891
+ try
2892
+ {
2893
this.httprequest.desktop.kvm.connectionBar = require('notifybar-desktop')(this.httprequest.privacybartext.replace('{0}', this.httprequest.desktop.kvm.rusers.join(', ')).replace('{1}', this.httprequest.desktop.kvm.users.join(', ')), require('MeshAgent')._tsid, color_options);
2894
MeshServerLogEx(31, null, "Remote Desktop Connection Bar Activated/Updated (" + this.httprequest.remoteaddr + ")", this.httprequest);
2895
} catch (ex) {
2896
MeshServerLogEx(32, null, "Remote Desktop Connection Bar Failed or not Supported (" + this.httprequest.remoteaddr + ")", this.httprequest);
2897
}
2617
- if (this.httprequest.desktop.kvm.connectionBar) {
2618
- this.httprequest.desktop.kvm.connectionBar.httprequest = this.httprequest;
2619
- this.httprequest.desktop.kvm.connectionBar.on('close', function () {
2620
- MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.httprequest.remoteaddr + ")", this.httprequest);
2621
- for (var i in this.httprequest.desktop.kvm._pipedStreams) {
2622
- this.httprequest.desktop.kvm._pipedStreams[i].end();
2898
+ if (this.httprequest.desktop.kvm.connectionBar)
2899
+ {
2900
+ this.httprequest.desktop.kvm.connectionBar.state =
2901
+ {
2902
+ userid: this.httprequest.userid,
2903
+ xuserid: this.httprequest.xuserid,
2904
+ username: this.httprequest.username,
2905
+ sessionid: this.httprequest.sessionid,
2906
+ remoteaddr: this.httprequest.remoteaddr,
2907
+ guestname: this.httprequest.guestname,
2908
+ desktop: this.httprequest.desktop
2909
+ };
2910
+ this.httprequest.desktop.kvm.connectionBar.on('close', function ()
2911
+ {
2912
+ console.info1('Connection Bar Forcefully closed');
2913
+ MeshServerLogEx(29, null, "Remote Desktop Connection forcefully closed by local user (" + this.state.remoteaddr + ")", this.state);
2914
+ for (var i in this.state.desktop.kvm._pipedStreams)
2915
+ {
2916
+ this.state.desktop.kvm._pipedStreams[i].end();
2917
}
2624
- this.httprequest.desktop.kvm.end();
2918
+ this.state.desktop.kvm.end();
2919
});
2920
}
2921
}
2922
this.httprequest.desktop.kvm.pipe(this, { dataTypeSkip: 1 });
2629
- if (this.httprequest.autolock) {
2923
+ if (this.httprequest.autolock)
2924
+ {
2925
destopLockHelper_pipe(this.httprequest);
2926
}
2927
}
@@ -2634,7 +2929,6 @@ function onTunnelData(data) {
2929
this.removeAllListeners('data');
2930
this.on('data', onTunnelControlData);
2931
//this.write('MeshCore KVM Hello!1');
2637
-
2932
} else if (this.httprequest.protocol == 5) {
2933
//
2934
// Remote Files
@@ -2659,7 +2953,8 @@ function onTunnelData(data) {
2953
broadcastSessionsToRegisteredApps();
2954
}
2955
2662
- this.end = function () {
2956
+ this.end = function ()
2957
+ {
2958
// Remove the files session from the count to update the server
2959
if (this.httprequest.userid != null) {
2960
var userid = getUserIdAndGuestNameFromHttpRequest(this.httprequest);
@@ -2670,22 +2965,26 @@ function onTunnelData(data) {
2965
};
2966
2967
// Perform notification if needed. Toast messages may not be supported on all platforms.
2673
- if (this.httprequest.consent && (this.httprequest.consent & 32)) {
2968
+ if (this.httprequest.consent && (this.httprequest.consent & 32))
2969
+ {
2970
// User Consent Prompt is required
2971
// Send a console message back using the console channel, "\n" is supported.
2972
this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: "Waiting for user to grant access...", msgid: 1 }));
2973
var consentMessage = currentTranslation['fileConsent'].replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username);
2974
var consentTitle = 'MeshCentral';
2975
2680
- if (this.httprequest.soptions != null) {
2976
+ if (this.httprequest.soptions != null)
2977
+ {
2978
if (this.httprequest.soptions.consentTitle != null) { consentTitle = this.httprequest.soptions.consentTitle; }
2979
if (this.httprequest.soptions.consentMsgFiles != null) { consentMessage = this.httprequest.soptions.consentMsgFiles.replace('{0}', this.httprequest.realname).replace('{1}', this.httprequest.username); }
2980
}
2981
var pr;
2685
- if (process.platform == 'win32') {
2982
+ if (process.platform == 'win32')
2983
+ {
2984
var enhanced = false;
2985
try { require('win-userconsent'); enhanced = true; } catch (ex) { }
2688
- if (enhanced) {
2986
+ if (enhanced)
2987
+ {
2988
var ipr = server_getUserImage(this.httprequest.userid);
2989
ipr.consentTitle = consentTitle;
2990
ipr.consentMessage = consentMessage;
@@ -2693,49 +2992,29 @@ function onTunnelData(data) {
2992
ipr.consentAutoAccept = this.httprequest.consentAutoAccept;
2993
ipr.username = this.httprequest.realname;
2994
ipr.translations = { Allow: currentTranslation['allow'], Deny: currentTranslation['deny'], Auto: currentTranslation['autoAllowForFive'], Caption: consentMessage };
2696
- pr = ipr.then(function (img) {
2995
+ pr = ipr.then(function (img)
2996
+ {
2997
this.consent = require('win-userconsent').create(this.consentTitle, this.consentMessage, this.username, { b64Image: img.split(',').pop(), timeout: this.consentTimeout * 1000, timeoutAutoAccept: this.consentAutoAccept, translations: this.translations, background: color_options.background, foreground: color_options.foreground });
2998
this.__childPromise.close = this.consent.close.bind(this.consent);
2999
return (this.consent);
3000
});
2701
- } else {
2702
- pr = require('message-box').create(consentTitle, consentMessage, this.consentTimeout, null);
3001
+ } else
3002
+ {
3003
+ pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null);
3004
}
2704
- } else {
2705
- pr = require('message-box').create(consentTitle, consentMessage, this.consentTimeout, null);
3005
+ }
3006
+ else
3007
+ {
3008
+ pr = require('message-box').create(consentTitle, consentMessage, this.httprequest.consentTimeout, null);
3009
}
3010
pr.ws = this;
3011
this.pause();
3012
this._consentpromise = pr;
2710
- this.prependOnceListener('end', function () { if (this._consentpromise && this._consentpromise.close) { this._consentpromise.close(); } });
2711
- pr.then(
2712
- function (always) {
2713
- if (always) { server_set_consentTimer(this.ws.httprequest.userid); }
2714
-
2715
- // Success
2716
- this.ws._consentpromise = null;
2717
- MeshServerLogEx(40, null, "Starting remote files after local user accepted (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2718
- this.ws.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: null }));
2719
- if (this.ws.httprequest.consent && (this.ws.httprequest.consent & 4)) {
2720
- // User Notifications is required
2721
- var notifyMessage = currentTranslation['fileNotify'].replace('{0}', this.ws.httprequest.realname);
2722
- var notifyTitle = "MeshCentral";
2723
- if (this.ws.httprequest.soptions != null) {
2724
- if (this.ws.httprequest.soptions.notifyTitle != null) { notifyTitle = this.ws.httprequest.soptions.notifyTitle; }
2725
- if (this.ws.httprequest.soptions.notifyMsgFiles != null) { notifyMessage = this.ws.httprequest.soptions.notifyMsgFiles.replace('{0}', this.ws.httprequest.realname).replace('{1}', this.ws.httprequest.username); }
2726
- }
2727
- try { require('toaster').Toast(notifyTitle, notifyMessage); } catch (ex) { }
2728
- }
2729
- this.ws.resume();
2730
- },
2731
- function (e) {
2732
- // User Consent Denied/Failed
2733
- this.ws._consentpromise = null;
2734
- MeshServerLogEx(41, null, "Failed to start remote files after local user rejected (" + this.ws.httprequest.remoteaddr + ")", this.ws.httprequest);
2735
- this.ws.end(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString(), msgid: 2 }));
2736
- });
3013
+ this.prependOnceListener('end', files_tunnel_endhandler);
3014
+ pr.then(files_consentpromise_resolved, files_consentpromise_rejected);
3015
}
2738
- else {
3016
+ else
3017
+ {
3018
// User Consent Prompt is not required
3019
if (this.httprequest.consent && (this.httprequest.consent & 4)) {
3020
// User Notifications is required
@@ -3062,6 +3341,44 @@ function onTunnelWebRTCControlData(data) {
3341
}
3342
}
3343
3344
+function tunnel_webrtc_onEnd()
3345
+{
3346
+ // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
3347
+ //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
3348
+ if (this.websocket.desktop && this.websocket.desktop.kvm)
3349
+ {
3350
+ try
3351
+ {
3352
+ this.unpipe(this.websocket.desktop.kvm);
3353
+ this.websocket.httprequest.desktop.kvm.unpipe(this);
3354
+ } catch (ex) { }
3355
+ }
3356
+ this.httprequest = null;
3357
+ this.websocket = null;
3358
+}
3359
+function tunnel_webrtc_DataChannel_OnFinalized()
3360
+{
3361
+ console.info1('WebRTC DataChannel Finalized');
3362
+}
3363
+function tunnel_webrtc_OnDataChannel(rtcchannel)
3364
+{
3365
+ //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
3366
+ //rtcchannel.maxFragmentSize = 32768;
3367
+ rtcchannel.xrtc = this;
3368
+ rtcchannel.websocket = this.websocket;
3369
+ this.rtcchannel = rtcchannel;
3370
+ this.rtcchannel.once('~', tunnel_webrtc_DataChannel_OnFinalized);
3371
+ this.websocket.rtcchannel = rtcchannel;
3372
+ this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
3373
+ this.websocket.rtcchannel.on('end', tunnel_webrtc_onEnd);
3374
+ this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over.
3375
+}
3376
+
3377
+function tunnel_webrtc_OnFinalized()
3378
+{
3379
+ console.info1('WebRTC Connection Finalized');
3380
+}
3381
+
3382
// Called when receiving control data on websocket
3383
function onTunnelControlData(data, ws) {
3384
var obj;
@@ -3135,7 +3452,8 @@ function onTunnelControlData(data, ws) {
3452
break;
3453
}
3454
case 'webrtc0': { // Browser indicates we can start WebRTC switch-over.
3138
- if (ws.httprequest.protocol == 1) { // Terminal
3455
+ if (ws.httprequest.protocol == 1)
3456
+ { // Terminal
3457
// This is a terminal data stream, unpipe the terminal now and indicate to the other side that terminal data will no longer be received over WebSocket
3458
if (process.platform == 'win32') {
3459
ws.httprequest._term.unpipe(ws);
@@ -3146,7 +3464,8 @@ function onTunnelControlData(data, ws) {
3464
} else if (ws.httprequest.protocol == 2) { // Desktop
3465
// This is a KVM data stream, unpipe the KVM now and indicate to the other side that KVM data will no longer be received over WebSocket
3466
ws.httprequest.desktop.kvm.unpipe(ws);
3149
- } else {
3467
+ } else
3468
+ {
3469
// Switch things around so all WebRTC data goes to onTunnelData().
3470
ws.rtcchannel.httprequest = ws.httprequest;
3471
ws.rtcchannel.removeAllListeners('data');
@@ -3155,8 +3474,10 @@ function onTunnelControlData(data, ws) {
3474
ws.write("{\"ctrlChannel\":\"102938\",\"type\":\"webrtc1\"}"); // End of data marker
3475
break;
3476
}
3158
- case 'webrtc1': {
3159
- if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) { // Terminal
3477
+ case 'webrtc1':
3478
+ {
3479
+ if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6))
3480
+ { // Terminal
3481
// Switch the user input from websocket to webrtc at this point.
3482
if (process.platform == 'win32') {
3483
ws.unpipe(ws.httprequest._term);
@@ -3166,7 +3487,9 @@ function onTunnelControlData(data, ws) {
3487
ws.rtcchannel.pipe(ws.httprequest.process.stdin, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
3488
}
3489
ws.resume(); // Resume the websocket to keep receiving control data
3169
- } else if (ws.httprequest.protocol == 2) { // Desktop
3490
+ }
3491
+ else if (ws.httprequest.protocol == 2)
3492
+ { // Desktop
3493
// Switch the user input from websocket to webrtc at this point.
3494
ws.unpipe(ws.httprequest.desktop.kvm);
3495
try { ws.webrtc.rtcchannel.pipe(ws.httprequest.desktop.kvm, { dataTypeSkip: 1, end: false }); } catch (ex) { sendConsoleText('EX2'); } // 0 = Binary, 1 = Text.
@@ -3193,29 +3516,12 @@ function onTunnelControlData(data, ws) {
3516
// This is a WebRTC offer.
3517
if ((ws.httprequest.protocol == 1) || (ws.httprequest.protocol == 6)) return; // TODO: Terminal is currently broken with WebRTC. Reject WebRTC upgrade for now.
3518
ws.webrtc = rtc.createConnection();
3519
+ ws.webrtc.once('~', tunnel_webrtc_OnFinalized);
3520
ws.webrtc.websocket = ws;
3197
- ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
3198
- ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
3199
- ws.webrtc.on('dataChannel', function (rtcchannel) {
3200
- //sendConsoleText('WebRTC Datachannel open, protocol: ' + this.websocket.httprequest.protocol);
3201
- //rtcchannel.maxFragmentSize = 32768;
3202
- rtcchannel.xrtc = this;
3203
- rtcchannel.websocket = this.websocket;
3204
- this.rtcchannel = rtcchannel;
3205
- this.websocket.rtcchannel = rtcchannel;
3206
- this.websocket.rtcchannel.on('data', onTunnelWebRTCControlData);
3207
- this.websocket.rtcchannel.on('end', function () {
3208
- // The WebRTC channel closed, unpipe the KVM now. This is also done when the web socket closes.
3209
- //sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC data channel closed');
3210
- if (this.websocket.desktop && this.websocket.desktop.kvm) {
3211
- try {
3212
- this.unpipe(this.websocket.desktop.kvm);
3213
- this.websocket.httprequest.desktop.kvm.unpipe(this);
3214
- } catch (ex) { }
3215
- }
3216
- });
3217
- this.websocket.write('{\"ctrlChannel\":\"102938\",\"type\":\"webrtc0\"}'); // Indicate we are ready for WebRTC switch-over.
3218
- });
3521
+ //ws.webrtc.on('connected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC connected');*/ });
3522
+ //ws.webrtc.on('disconnected', function () { /*sendConsoleText('Tunnel #' + this.websocket.tunnel.index + ' WebRTC disconnected');*/ });
3523
+ ws.webrtc.on('dataChannel', tunnel_webrtc_OnDataChannel);
3524
+
3525
var sdp = null;
3526
try { sdp = ws.webrtc.setOffer(obj.sdp); } catch (ex) { }
3527
if (sdp != null) { ws.write({ type: 'answer', ctrlChannel: '102938', sdp: sdp }); }
@@ -4040,7 +4346,9 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
4346
this._dispatcher.on('connection', function (c) {
4347
this._c = c;
4348
this._c.root = this.parent;
4043
- this._c.on('end', function () {
4349
+ this._c.on('end', function ()
4350
+ {
4351
+ this.root._dispatcher.close();
4352
this.root._dispatcher = null;
4353
this.root = null;
4354
});
apprelays.js
+8
-1
@@ -69,7 +69,7 @@ function SerialTunnel(options) {
69
}
70
71
// Construct a Web relay object
72
-module.exports.CreateWebRelaySession = function (parent, db, req, args, domain, userid, nodeid, addr, port, appid, sessionid) {
72
+module.exports.CreateWebRelaySession = function (parent, db, req, args, domain, userid, nodeid, addr, port, appid, sessionid, expire) {
73
const obj = {};
74
obj.parent = parent;
75
obj.lastOperation = Date.now();
@@ -80,6 +80,7 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
80
obj.port = port;
81
obj.appid = appid;
82
obj.sessionid = sessionid;
83
+ obj.expireTimer = null;
84
var pendingRequests = [];
85
var nextTunnelId = 1;
86
var tunnels = {};
@@ -90,6 +91,9 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
91
// Any HTTP cookie set by the device is going to be shared between all tunnels to that device.
92
obj.webCookies = {};
93
94
+ // Setup an expire time if needed
95
+ if (expire != null) { var timeout = (expire - Date.now()); if (timeout < 10) { timeout = 10; } obj.expireTimer = setTimeout(close, timeout); }
96
+
97
// Events
98
obj.closed = false;
99
obj.onclose = null;
@@ -202,6 +206,9 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
206
parent.parent.debug('webrelay', 'tunnel-close');
207
obj.closed = true;
208
209
+ // Clear the time if present
210
+ if (obj.expireTimer != null) { clearTimeout(obj.expireTimer); delete obj.expireTimer; }
211
+
212
// Close all tunnels
213
for (var i in tunnels) { tunnels[i].close(); }
214
tunnels = null;
meshcentral.js
+1
-1
@@ -466,7 +466,7 @@ function CreateMeshCentralServer(config, args) {
466
const npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : '');
467
const env = Object.assign({}, process.env); // Shallow clone
468
if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; }
469
- const xxprocess = child_process.exec(npmpath + ' install meshcentral' + version + npmproxy, { maxBuffer: Infinity, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) {
469
+ const xxprocess = child_process.exec(npmpath + ' install --no-package-lock meshcentral' + version + npmproxy, { maxBuffer: Infinity, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) {
470
if ((error != null) && (error != '')) { console.log('Update failed: ' + error); }
471
});
472
xxprocess.data = '';
meshuser.js
+3
-2
@@ -4137,8 +4137,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
4137
else if ((command.start != null) && (typeof command.start != 'number')) { err = 'Invalid start time'; } // Check the start time in UTC seconds
4138
else if ((command.end != null) && (typeof command.end != 'number')) { err = 'Invalid end time'; } // Check the end time in UTC seconds
4139
else if (common.validateInt(command.consent, 0, 256) == false) { err = 'Invalid flags'; } // Check the flags
4140
- else if (common.validateInt(command.p, 1, 7) == false) { err = 'Invalid protocol'; } // Check the protocol, 1 = Terminal, 2 = Desktop, 4 = Files
4140
+ else if (common.validateInt(command.p, 1, 31) == false) { err = 'Invalid protocol'; } // Check the protocol, 1 = Terminal, 2 = Desktop, 4 = Files, 8 = HTTP, 16 = HTTPS
4141
else if ((command.recurring != null) && (common.validateInt(command.recurring, 1, 2) == false)) { err = 'Invalid recurring value'; } // Check the recurring value, 1 = Daily, 2 = Weekly
4142
+ else if ((command.port != null) && (common.validateInt(command.port, 1, 65535) == false)) { err = 'Invalid port value'; } // Check the port if present
4143
else if ((command.recurring != null) && ((command.end != null) || (command.start == null) || (command.expire == null))) { err = 'Invalid recurring command'; }
4144
else if ((command.expire == null) && ((command.start == null) || (command.end == null) || (command.start > command.end))) { err = 'No time specified'; } // Check that a time range is present
4145
else {
@@ -4238,7 +4239,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
4239
try { ws.send(JSON.stringify(command)); } catch (ex) { }
4240
4241
// Create a device sharing database entry
4241
- var shareEntry = { _id: 'deviceshare-' + publicid, type: 'deviceshare', xmeshid: node.meshid, nodeid: node._id, p: command.p, domain: node.domain, publicid: publicid, userid: user._id, guestName: command.guestname, consent: command.consent, url: url };
4242
+ var shareEntry = { _id: 'deviceshare-' + publicid, type: 'deviceshare', xmeshid: node.meshid, nodeid: node._id, p: command.p, domain: node.domain, publicid: publicid, userid: user._id, guestName: command.guestname, consent: command.consent, port: command.port, url: url };
4243
if ((startTime != null) && (expireTime != null)) { shareEntry.startTime = startTime; shareEntry.expireTime = expireTime; }
4244
else if ((startTime != null) && (duration != null)) { shareEntry.startTime = startTime; shareEntry.duration = duration; }
4245
if (command.recurring) { shareEntry.recurring = command.recurring; }
views/default.handlebars
+20
-5
@@ -3650,7 +3650,7 @@
3650
if (message.consent & 0x0040) { y.push("Privacy bar"); }
3651
if (y.length == 0) { y.push("None"); }
3652
x += addHtmlValue("User Consent", y.join(', '));
3653
- var type = ['', "Remote Terminal Link", "Remote Desktop Link", "Remote Desktop + Terminal Link", "Remote Files Link", "Remote Terminal + Files Link", "Remote Desktop + Files Link", "Remote Desktop + Terminal + Files Link"][message.p];
3653
+ var type = ''; if (message.p <= 7) { type = ['', "Remote Terminal Link", "Remote Desktop Link", "Remote Desktop + Terminal Link", "Remote Files Link", "Remote Terminal + Files Link", "Remote Desktop + Files Link", "Remote Desktop + Terminal + Files Link"][message.p]; } else if (message.p == 8) { type = format("HTTP/{0} link", message.port); } else if (message.p == 16) { type = format("HTTPS/{0}", message.port); }
3654
x += '<div id=agentInvitationLinkDiv style="text-align:center;font-size:large;margin:16px"><a href="' + message.url + '" id=agentInvitationLink rel="noreferrer noopener" target="_blank" style=cursor:pointer>' + type + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy link to clipboard" + '" style=cursor:pointer onclick=d2CopyInviteToClip()></div></div>';
3655
setDialogMode(2, "Share Device", 1, null, x);
3656
break;
@@ -7588,7 +7588,7 @@
7588
var dshare = deviceShares[i], trash = '';
7589
if (dshare.url != null) { trash += '<a href="' + dshare.url + '" rel="noreferrer noopener" target=_blank title="' + "Device Sharing Link" + '" style=cursor:pointer><img src=images/link2.png border=0 height=10 width=10></a> '; }
7590
trash += '<a href=# onclick=\'return p30removeDeviceSharing(event,"' + encodeURIComponentEx(currentNode._id) + '","' + encodeURIComponentEx(dshare.publicid) + '","' + encodeURIComponentEx(dshare.guestName) + '")\' title="' + "Remove device sharing" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
7591
- var type = ['', "Terminal", "Desktop", "Desktop + Terminal", "Files", "Terminal + Files", "Desktop + Files", "Desktop + Terminal + Files"][dshare.p];
7591
+ var type = ''; if (dshare.p <= 7) { type = ['', "Terminal", "Desktop", "Desktop + Terminal", "Files", "Terminal + Files", "Desktop + Files", "Desktop + Terminal + Files"][dshare.p]; } else if (dshare.p == 8) { type = "HTTP/" + dshare.port; } else if (dshare.p == 16) { type = "HTTPS/" + dshare.port; }
7592
var details = type;
7593
if ((dshare.startTime != null) && (dshare.expireTime != null)) { details = format("{0}, {1} to {2}", type, printFlexDateTime(new Date(dshare.startTime)), printFlexDateTime(new Date(dshare.expireTime))); }
7594
if ((dshare.startTime != null) && (dshare.duration != null)) {
@@ -7896,6 +7896,11 @@
7896
if ((rights != 0xFFFFFFFF) && ((rights & 0x600) != 0)) { termFiles = ''; }
7897
var allFeatures = '<option value=7>' + "Desktop + Terminal + Files" + '</option>';
7898
if ((rights != 0xFFFFFFFF) && ((rights & 0x700) != 0)) { allFeatures = ''; }
7899
+ var httpFeature = '';
7900
+ if (webRelayPort != 0) {
7901
+ httpFeature = '<option value=8>' + "HTTP" + '</option><option value=9>' + "HTTPS" + '</option>';
7902
+ if ((rights != 0xFFFFFFFF) && ((rights & 8) != 0)) { httpFeature = ''; }
7903
+ }
7904
7905
var y = '', z = '';
7906
if ((currentNode.agent.caps & 1) == 1) { y += (deskFull + '<option value=3>' + "Desktop, View only" + '</option>'); } // Agent is desktop capable
@@ -7904,6 +7909,7 @@
7909
if ((currentNode.agent.caps & 5) == 5) { y += deskFiles; } // Agent is desktop + files capable
7910
if ((currentNode.agent.caps & 6) == 6) { y += termFiles; } // Agent is terminal + files capable
7911
if ((currentNode.agent.caps & 7) == 7) { y += allFeatures; } // Agent is desktop + terminal + files capable
7912
+ y += httpFeature;
7913
7914
x += addHtmlValue("Type", '<select id=d2shareType style=float:right;width:250px onchange=showShareDeviceValidate()>' + y + '</select>');
7915
var options = { 1 : "1 minute", 5 : "5 minutes", 10 : "10 minutes", 15 : "15 minutes", 30 : "30 minutes", 45 : "45 minutes", 60 : "60 minutes", 120 : "2 hours", 240 : "4 hours", 480 : "8 hours", 720 : "12 hours", 960 : "16 hours", 1440 : "24 hours", 2880 : "2 days", 5760 : "4 days", 0 : "Unlimited" }
@@ -7925,7 +7931,9 @@
7931
x += addHtmlValue("Start Time", '<input id=d2timeStartSelector style=float:right;width:250px class=flatpickr type="text" placeholder="' + "Select Date & Time..." + '" data-id="altinput">');
7932
x += addHtmlValue("Duration", '<select id=d2inviteDuration style=float:right;width:250px>' + z + '</select>');
7933
x += '</div>';
7928
- if (currentNode.agent.caps & 1) { x += addHtmlValue("User Consent", '<select id=d2userConsent style=float:right;width:250px><option value=1>' + "Prompt for consent" + '</option><option value=0>' + "Notify Only" + '</option></select>'); }
7934
+ if (currentNode.agent.caps & 1) { x += '<div id=d2userConsentSelector>' + addHtmlValue("User Consent", '<select id=d2userConsent style=float:right;width:250px><option value=1>' + "Prompt for consent" + '</option><option value=0>' + "Notify Only" + '</option></select>') + '</div>'; }
7935
+ x += '<div id=d2httpPortSelector>' + addHtmlValue("Port", '<input id=d2httpPort style=float:right;width:250px value=80 onkeyup=showShareDeviceValidate()></input>') + '</div>';
7936
+ x += '<div id=d2httpsPortSelector>' + addHtmlValue("Port", '<input id=d2httpsPort style=float:right;width:250px value=443 onkeyup=showShareDeviceValidate()></input>') + '</div>';
7937
setDialogMode(2, "Share Device", 3, showShareDeviceEx, x);
7938
showShareDeviceValidate();
7939
var tomorrow = new Date();
@@ -7936,18 +7944,25 @@
7944
}
7945
7946
function showShareDeviceValidate() {
7947
+ if (currentNode.agent.caps & 1) { QV('d2userConsentSelector', Q('d2shareType').value < 8); }
7948
+ QV('d2httpPortSelector', Q('d2shareType').value == 8);
7949
+ QV('d2httpsPortSelector', Q('d2shareType').value == 9);
7950
QV('d2modenow', Q('d2timeRange').value == 0);
7951
QV('d2moderange', Q('d2timeRange').value == 1);
7952
QV('d2moderecurring', Q('d2timeRange').value >= 2);
7953
var ok = true;
7954
+ if (Q('d2shareType').value == 8) { var port = parseInt(Q('d2httpPort').value); if ((Q('d2httpPort').value != port) || (port < 1) || (port > 65535)) { ok = false; } }
7955
+ if (Q('d2shareType').value == 9) { var port = parseInt(Q('d2httpsPort').value); if ((Q('d2httpsPort').value != port) || (port < 1) || (port > 65535)) { ok = false; } }
7956
if (Q('d2inviteName').value.trim().length == 0) { ok = false; }
7957
QE('idx_dlgOkButton', ok);
7958
}
7959
7960
function showShareDeviceEx(b, tag) {
7961
var consent = 0, p = parseInt(Q('d2shareType').value), viewOnly = false, q = 0;
7962
+ if (p == 8) { meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), p: 8, expire: parseInt(Q('d2inviteExpire').value), port: parseInt(Q('d2httpPort').value), consent: 0 }); return; }
7963
+ if (p == 9) { meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), p: 16, expire: parseInt(Q('d2inviteExpire').value), port: parseInt(Q('d2httpsPort').value), consent: 0 }); return; }
7964
if (p == 3) { viewOnly = true; }
7950
- var q = [0, 1, 2, 2, 4, 6, 5, 7][p]; // Protocol flags: 1 = Terminal, 2 = Desktop, 4 = Files.
7965
+ var q = [0, 1, 2, 2, 4, 6, 5, 7][p]; // Protocol flags: 1 = Terminal, 2 = Desktop, 4 = Files, 8 = HTTP, 16 = HTTPS.
7966
7967
if (q & 1) {
7968
consent |= 0x0002; // Terminal notify
@@ -12641,7 +12656,7 @@
12656
var dshare = deviceShares[i], trash = '';
12657
if (dshare.url != null) { trash += '<a href="' + dshare.url + '" rel="noreferrer noopener" target=_blank title="' + "Device Sharing Link" + '" style=cursor:pointer><img src=images/link2.png border=0 height=10 width=10></a> '; }
12658
trash += '<a href=# onclick=\'return p30removeDeviceSharing(event,"' + encodeURIComponentEx(dshare.nodeid) + '","' + encodeURIComponentEx(dshare.publicid) + '","' + encodeURIComponentEx(dshare.guestName) + '")\' title="' + "Remove device sharing" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
12644
- var type = ['', "Terminal", "Desktop", "Desktop + Terminal", "Files", "Terminal + Files", "Desktop + Files", "Desktop + Terminal + Files"][dshare.p];
12659
+ var type = ''; if (dshare.p <= 7) { type = ['', "Terminal", "Desktop", "Desktop + Terminal", "Files", "Terminal + Files", "Desktop + Files", "Desktop + Terminal + Files"][dshare.p]; } else if (dshare.p == 8) { type = "HTTP/" + dshare.port; } else if (dshare.p == 16) { type = "HTTPS/" + dshare.port; }
12660
var details = type;
12661
if ((dshare.startTime != null) && (dshare.expireTime != null)) { details = format("{0}, {1} to {2}", type, printFlexDateTime(new Date(dshare.startTime)), printFlexDateTime(new Date(dshare.expireTime))); }
12662
if ((dshare.startTime != null) && (dshare.duration != null)) {
webrelayserver.js
+75
-39
@@ -124,8 +124,11 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates,
124
return next();
125
} else {
126
// If this is a normal request (GET, POST, etc) handle it here
127
- if ((req.session.userid != null) && (req.session.x != null) && (parent.webserver.destroyedSessions[req.session.userid + '/' + req.session.x] == null)) {
128
- var relaySession = relaySessions[req.session.userid + '/' + req.session.x];
127
+ var webSessionId = null;
128
+ if ((req.session.userid != null) && (req.session.x != null)) { webSessionId = req.session.userid + '/' + req.session.x; }
129
+ else if (req.session.z != null) { webSessionId = req.session.z; }
130
+ if ((webSessionId != null) && (parent.webserver.destroyedSessions[webSessionId] == null)) {
131
+ var relaySession = relaySessions[webSessionId];
132
if (relaySession != null) {
133
// The web relay session is valid, use it
134
relaySession.handleRequest(req, res);
@@ -157,8 +160,11 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates,
160
161
// Handle incoming web socket calls
162
obj.app.ws('/*', function (ws, req) {
160
- if ((req.session.userid != null) && (req.session.x != null) && (parent.webserver.destroyedSessions[req.session.userid + '/' + req.session.x] == null)) {
161
- var relaySession = relaySessions[req.session.userid + '/' + req.session.x];
163
+ var webSessionId = null;
164
+ if ((req.session.userid != null) && (req.session.x != null)) { webSessionId = req.session.userid + '/' + req.session.x; }
165
+ else if (req.session.z != null) { webSessionId = req.session.z; }
166
+ if ((webSessionId != null) && (parent.webserver.destroyedSessions[webSessionId] == null)) {
167
+ var relaySession = relaySessions[webSessionId];
168
if (relaySession != null) {
169
// The multi-tunnel session is valid, use it
170
relaySession.handleWebSocket(ws, req);
@@ -178,55 +184,85 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates,
184
parent.debug('webrelay', 'webRelaySetup');
185
186
// Decode the relay cookie
181
- if (req.query.c != null) {
182
- // Decode and check if this relay cookie is valid
183
- const urlCookie = obj.parent.decodeCookie(req.query.c, parent.loginCookieEncryptionKey);
184
- if ((urlCookie != null) && (urlCookie.ruserid != null) && (urlCookie.x != null) && (parent.webserver.destroyedSessions[urlCookie.ruserid + '/' + urlCookie.x] == null)) {
185
- if (req.session.x != urlCookie.x) { req.session.x = urlCookie.x; } // Set the sessionid if missing
186
- if (req.session.userid != urlCookie.ruserid) { req.session.userid = urlCookie.ruserid; } // Set the session userid if missing
187
- }
188
- }
187
+ if (req.query.c == null) { res.sendStatus(404); return; }
188
+
189
+ // Decode and check if this relay cookie is valid
190
+ var userid, domainid, domain, nodeid, addr, port, appid, webSessionId, expire;
191
+ const urlCookie = obj.parent.decodeCookie(req.query.c, parent.loginCookieEncryptionKey);
192
+ if (urlCookie == null) { res.sendStatus(404); return; }
193
190
- // Check that all the required arguments are present
191
- if ((req.session.userid == null) || (req.session.x == null) || (req.query.n == null) || (req.query.p == null) || (parent.webserver.destroyedSessions[req.session.userid + '/' + req.session.x] != null) || ((req.query.appid != 1) && (req.query.appid != 2))) { res.redirect('/'); return; }
194
+ // Decode the incomign cookie
195
+ if ((urlCookie.ruserid != null) && (urlCookie.x != null)) {
196
+ if (parent.webserver.destroyedSessions[urlCookie.ruserid + '/' + urlCookie.x] != null) { res.sendStatus(404); return; }
197
+
198
+ // This is a standard user, figure out what our web relay will be.
199
+ if (req.session.x != urlCookie.x) { req.session.x = urlCookie.x; } // Set the sessionid if missing
200
+ if (req.session.userid != urlCookie.ruserid) { req.session.userid = urlCookie.ruserid; } // Set the session userid if missing
201
+ if (req.session.z) { delete req.session.z; } // Clear the web relay guest session
202
+ userid = req.session.userid;
203
+ domainid = userid.split('/')[1];
204
+ domain = parent.config.domains[domainid];
205
+ nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n);
206
+ addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1';
207
+ port = parseInt(req.query.p);
208
+ appid = parseInt(req.query.appid);
209
+ webSessionId = req.session.userid + '/' + req.session.x;
210
+
211
+ // Check that all the required arguments are present
212
+ if ((req.session.userid == null) || (req.session.x == null) || (req.query.n == null) || (req.query.p == null) || (parent.webserver.destroyedSessions[webSessionId] != null) || ((req.query.appid != 1) && (req.query.appid != 2))) { res.redirect('/'); return; }
213
+ } else if (urlCookie.r == 8) {
214
+ // This is a guest user, figure out what our web relay will be.
215
+ userid = urlCookie.userid;
216
+ domainid = userid.split('/')[1];
217
+ domain = parent.config.domains[domainid];
218
+ nodeid = urlCookie.nid;
219
+ addr = (urlCookie.addr != null) ? urlCookie.addr : '127.0.0.1';
220
+ port = urlCookie.port;
221
+ appid = (urlCookie.p == 16) ? 2 : 1; // appid: 1 = HTTP, 2 = HTTPS
222
+ webSessionId = userid + '/' + urlCookie.pid;
223
+ if (req.session.x) { delete req.session.x; } // Clear the web relay sessionid
224
+ if (req.session.userid) { delete req.session.userid; } // Clear the web relay userid
225
+ if (req.session.z != webSessionId) { req.session.z = webSessionId; } // Set the web relay guest session
226
+ expire = urlCookie.expire;
227
+ }
228
193
- // Get the user and domain information
194
- const userid = req.session.userid;
195
- const domainid = userid.split('/')[1];
196
- const domain = parent.config.domains[domainid];
197
- const nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n);
198
- const addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1';
199
- const port = parseInt(req.query.p);
200
- const appid = parseInt(req.query.appid);
229
+ // No session identifier was setup, exit now
230
+ if (webSessionId == null) { res.sendStatus(404); return; }
231
232
// Check to see if we already have a multi-relay session that matches exactly this device and port for this user
203
- const xrelaySession = relaySessions[req.session.userid + '/' + req.session.x];
233
+ const xrelaySession = relaySessions[webSessionId];
234
if ((xrelaySession != null) && (xrelaySession.domain.id == domain.id) && (xrelaySession.userid == userid) && (xrelaySession.nodeid == nodeid) && (xrelaySession.addr == addr) && (xrelaySession.port == port) && (xrelaySession.appid == appid)) {
235
// We found an exact match, we are all setup already, redirect to root
236
res.redirect('/');
237
return;
238
}
239
210
- // There is a relay session, but it's not correct, close it.
211
- if (xrelaySession != null) { xrelaySession.close(); delete relaySessions[req.session.userid + '/' + req.session.x]; }
240
+ // Check that the user has rights to access this device
241
+ parent.webserver.GetNodeWithRights(domain, userid, nodeid, function (node, rights, visible) {
242
+ // If there is no remote control rights, reject this web relay
243
+ if ((rights & 8) == 0) { res.sendStatus(404); return; }
244
213
- // Create a web relay session
214
- const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySession);
215
- relaySession.onclose = function (sessionId) {
216
- // Remove the relay session
217
- delete relaySessions[sessionId];
218
- // If there are not more relay sessions, clear the cleanup timer
219
- if ((Object.keys(relaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(obj.cleanupTimer); obj.cleanupTimer = null; }
220
- }
245
+ // There is a relay session, but it's not correct, close it.
246
+ if (xrelaySession != null) { xrelaySession.close(); delete relaySessions[webSessionId]; }
247
+
248
+ // Create a web relay session
249
+ const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySession, expire);
250
+ relaySession.onclose = function (sessionId) {
251
+ // Remove the relay session
252
+ delete relaySessions[sessionId];
253
+ // If there are not more relay sessions, clear the cleanup timer
254
+ if ((Object.keys(relaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(obj.cleanupTimer); obj.cleanupTimer = null; }
255
+ }
256
222
- // Set the multi-tunnel session
223
- relaySessions[userid + '/' + req.session.x] = relaySession;
257
+ // Set the multi-tunnel session
258
+ relaySessions[webSessionId] = relaySession;
259
225
- // Setup the cleanup timer if needed
226
- if (obj.cleanupTimer == null) { obj.cleanupTimer = setInterval(checkTimeout, 10000); }
260
+ // Setup the cleanup timer if needed
261
+ if (obj.cleanupTimer == null) { obj.cleanupTimer = setInterval(checkTimeout, 10000); }
262
228
- // Redirect to root
229
- res.redirect('/');
263
+ // Redirect to root
264
+ res.redirect('/');
265
+ });
266
});
267
}
268
webserver.js
+134
-77
@@ -3848,7 +3848,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3848
}
3849
3850
// Generate an old style cookie from the information in the database
3851
- var cookie = { a: 5, p: doc.p, gn: doc.guestName, nid: doc.nodeid, cf: doc.consent, pid: doc.publicid, k: doc.extrakey };
3851
+ var cookie = { a: 5, p: doc.p, gn: doc.guestName, nid: doc.nodeid, cf: doc.consent, pid: doc.publicid, k: doc.extrakey ? doc.extrakey : null, port: doc.port };
3852
if (doc.userid) { cookie.uid = doc.userid; }
3853
if ((cookie.userid == null) && (cookie.pid.startsWith('AS:node/'))) { cookie.nouser = 1; }
3854
if (doc.startTime != null) {
@@ -3870,7 +3870,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3870
3871
// Check the public id
3872
obj.db.GetAllTypeNodeFiltered([c.nid], domain.id, 'deviceshare', null, function (err, docs) {
3873
- // Check if any desktop sharing links are present, expire message.
3873
+ // Check if any sharing links are present, expire message.
3874
if ((err != null) || (docs.length == 0)) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 12, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3875
3876
// Search for the device share public identifier, expire message.
@@ -3886,22 +3886,43 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
3886
// Check the start time, not yet valid message.
3887
if ((c.start != null) && (c.expire != null) && ((c.start > Date.now()) || (c.start > c.expire))) { render(req, res, getRenderPage((domain.sitestyle == 2) ? 'message2' : 'message', req, domain), getRenderArgs({ titleid: 2, msgid: 11, domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27') }, req, domain)); return; }
3888
3889
- // Looks good, let's create the outbound session cookies.
3890
- // Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar.
3891
- const authCookieData = { userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: c.p, gn: c.gn, cf: c.cf, r: 8, expire: c.expire, pid: c.pid, vo: c.vo };
3892
- if ((authCookieData.userid == null) && (authCookieData.pid.startsWith('AS:node/'))) { authCookieData.nouser = 1; }
3893
- if (c.k != null) { authCookieData.k = c.k; }
3894
- const authCookie = obj.parent.encodeCookie(authCookieData, obj.parent.loginCookieEncryptionKey);
3895
-
3896
- // Server features
3897
- var features2 = 0;
3898
- if (obj.args.allowhighqualitydesktop !== false) { features2 += 1; } // Enable AllowHighQualityDesktop (Default true)
3899
-
3900
- // Lets respond by sending out the desktop viewer.
3901
- var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
3902
- parent.debug('web', 'handleSharingRequest: Sending guest sharing page for \"' + c.uid + '\", guest \"' + c.gn + '\".');
3903
- res.set({ 'Cache-Control': 'no-store' });
3904
- render(req, res, getRenderPage('sharing', req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: '', domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), nodeid: c.nid, serverDnsName: obj.getWebServerName(domain, req), serverRedirPort: args.redirport, serverPublicPort: httpsPort, expire: c.expire, viewOnly: (c.vo == 1) ? 1 : 0, nodeName: encodeURIComponent(node.name).replace(/'/g, '%27'), features: c.p, features2: features2 }, req, domain));
3889
+ // If this is a web relay share, check if this feature is active
3890
+ if ((c.p == 8) || (c.p == 16)) {
3891
+ // This is a HTTP or HTTPS share
3892
+ var webRelayPort = ((args.relaydns != null) ? ((typeof args.aliasport == 'number') ? args.aliasport : args.port) : ((parent.webrelayserver != null) ? ((typeof args.relayaliasport == 'number') ? args.relayaliasport : parent.webrelayserver.port) : 0));
3893
+ if (webRelayPort == 0) { res.sendStatus(404); return; }
3894
+
3895
+ // Create the authentication cookie
3896
+ const authCookieData = { userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: c.p, gn: c.gn, r: 8, expire: c.expire, pid: c.pid, port: c.port };
3897
+ if ((authCookieData.userid == null) && (authCookieData.pid.startsWith('AS:node/'))) { authCookieData.nouser = 1; }
3898
+ const authCookie = obj.parent.encodeCookie(authCookieData, obj.parent.loginCookieEncryptionKey);
3899
+
3900
+ // Redirect to a URL
3901
+ var webRelayDns = (args.relaydns != null) ? args.relaydns[0] : obj.getWebServerName(domain, req);
3902
+ var url = 'https://' + webRelayDns + ':' + webRelayPort + '/control-redirect.ashx?n=' + c.nid + '&p=' + c.port + '&appid=' + c.p + '&c=' + authCookie;
3903
+ if (c.addr != null) { url += '&addr=' + c.addr; }
3904
+ if (c.pid != null) { url += '&relayid=' + c.pid; }
3905
+ parent.debug('web', 'handleSharingRequest: Redirecting guest to HTTP relay page for \"' + c.uid + '\", guest \"' + c.gn + '\".');
3906
+ res.redirect(url);
3907
+ } else {
3908
+ // Looks good, let's create the outbound session cookies.
3909
+ // This is a desktop, terminal or files share. We need to display the sharing page.
3910
+ // Consent flags are 1 = Notify, 8 = Prompt, 64 = Privacy Bar.
3911
+ const authCookieData = { userid: c.uid, domainid: domain.id, nid: c.nid, ip: req.clientIp, p: c.p, gn: c.gn, cf: c.cf, r: 8, expire: c.expire, pid: c.pid, vo: c.vo };
3912
+ if ((authCookieData.userid == null) && (authCookieData.pid.startsWith('AS:node/'))) { authCookieData.nouser = 1; }
3913
+ if (c.k != null) { authCookieData.k = c.k; }
3914
+ const authCookie = obj.parent.encodeCookie(authCookieData, obj.parent.loginCookieEncryptionKey);
3915
+
3916
+ // Server features
3917
+ var features2 = 0;
3918
+ if (obj.args.allowhighqualitydesktop !== false) { features2 += 1; } // Enable AllowHighQualityDesktop (Default true)
3919
+
3920
+ // Lets respond by sending out the desktop viewer.
3921
+ var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
3922
+ parent.debug('web', 'handleSharingRequest: Sending guest sharing page for \"' + c.uid + '\", guest \"' + c.gn + '\".');
3923
+ res.set({ 'Cache-Control': 'no-store' });
3924
+ render(req, res, getRenderPage('sharing', req, domain), getRenderArgs({ authCookie: authCookie, authRelayCookie: '', domainurl: encodeURIComponent(domain.url).replace(/'/g, '%27'), nodeid: c.nid, serverDnsName: obj.getWebServerName(domain, req), serverRedirPort: args.redirport, serverPublicPort: httpsPort, expire: c.expire, viewOnly: (c.vo == 1) ? 1 : 0, nodeName: encodeURIComponent(node.name).replace(/'/g, '%27'), features: c.p, features2: features2 }, req, domain));
3925
+ }
3926
});
3927
});
3928
}
@@ -6549,32 +6570,56 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
6570
parent.debug('web', 'webRelaySetup');
6571
6572
// Decode the relay cookie
6552
- if (req.query.c != null) {
6553
- // Decode and check if this relay cookie is valid
6554
- const urlCookie = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey);
6555
- if ((urlCookie != null) && (urlCookie.ruserid != null) && (urlCookie.x != null)) {
6556
- if (req.session.x != urlCookie.x) { req.session.x = urlCookie.x; } // Set the sessionid if missing
6557
- if (req.session.userid != urlCookie.ruserid) { req.session.userid = urlCookie.ruserid; } // Set the session userid if missing
6558
- }
6573
+ if (req.query.c == null) { res.sendStatus(404); return; }
6574
+
6575
+ // Decode and check if this relay cookie is valid
6576
+ var userid, domainid, domain, nodeid, addr, port, appid, webSessionId, expire;
6577
+ const urlCookie = obj.parent.decodeCookie(req.query.c, parent.loginCookieEncryptionKey);
6578
+ if (urlCookie == null) { res.sendStatus(404); return; }
6579
+
6580
+ // Decode the incomign cookie
6581
+ if ((urlCookie.ruserid != null) && (urlCookie.x != null)) {
6582
+ if (parent.webserver.destroyedSessions[urlCookie.ruserid + '/' + urlCookie.x] != null) { res.sendStatus(404); return; }
6583
+
6584
+ // This is a standard user, figure out what our web relay will be.
6585
+ if (req.session.x != urlCookie.x) { req.session.x = urlCookie.x; } // Set the sessionid if missing
6586
+ if (req.session.userid != urlCookie.ruserid) { req.session.userid = urlCookie.ruserid; } // Set the session userid if missing
6587
+ if (req.session.z) { delete req.session.z; } // Clear the web relay guest session
6588
+ userid = req.session.userid;
6589
+ domainid = userid.split('/')[1];
6590
+ domain = parent.config.domains[domainid];
6591
+ nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n);
6592
+ addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1';
6593
+ port = parseInt(req.query.p);
6594
+ appid = parseInt(req.query.appid);
6595
+ webSessionId = req.session.userid + '/' + req.session.x;
6596
+
6597
+ // Check that all the required arguments are present
6598
+ if ((req.session.userid == null) || (req.session.x == null) || (req.query.n == null) || (req.query.p == null) || (parent.webserver.destroyedSessions[webSessionId] != null) || ((req.query.appid != 1) && (req.query.appid != 2))) { res.redirect('/'); return; }
6599
+ } else if (urlCookie.r == 8) {
6600
+ // This is a guest user, figure out what our web relay will be.
6601
+ userid = urlCookie.userid;
6602
+ domainid = userid.split('/')[1];
6603
+ domain = parent.config.domains[domainid];
6604
+ nodeid = urlCookie.nid;
6605
+ addr = (urlCookie.addr != null) ? urlCookie.addr : '127.0.0.1';
6606
+ port = urlCookie.port;
6607
+ appid = (urlCookie.p == 16) ? 2 : 1; // appid: 1 = HTTP, 2 = HTTPS
6608
+ webSessionId = userid + '/' + urlCookie.pid;
6609
+ if (req.session.x) { delete req.session.x; } // Clear the web relay sessionid
6610
+ if (req.session.userid) { delete req.session.userid; } // Clear the web relay userid
6611
+ if (req.session.z != webSessionId) { req.session.z = webSessionId; } // Set the web relay guest session
6612
+ expire = urlCookie.expire;
6613
}
6614
6561
- // Check that all the required arguments are present
6562
- if ((req.session.userid == null) || (req.session.x == null) || (req.query.n == null) || (req.query.p == null) || ((obj.destroyedSessions[req.session.userid + '/' + req.session.x] != null)) || ((req.query.appid != 1) && (req.query.appid != 2))) { res.redirect('/'); return; }
6563
-
6564
- // Get the user and domain information
6565
- const userid = req.session.userid;
6566
- const domainid = userid.split('/')[1];
6567
- const domain = parent.config.domains[domainid];
6568
- const nodeid = ((req.query.relayid != null) ? req.query.relayid : req.query.n);
6569
- const addr = (req.query.addr != null) ? req.query.addr : '127.0.0.1';
6570
- const port = parseInt(req.query.p);
6571
- const appid = parseInt(req.query.appid);
6615
+ // No session identifier was setup, exit now
6616
+ if (webSessionId == null) { res.sendStatus(404); return; }
6617
6618
// Check that we have an exact session on any of the relay DNS names
6619
var xrelaySessionId, xrelaySession, freeRelayHost, oldestRelayTime, oldestRelayHost;
6620
for (var hostIndex in obj.args.relaydns) {
6621
const host = obj.args.relaydns[hostIndex];
6577
- xrelaySessionId = req.session.userid + '/' + req.session.x + '/' + host;
6622
+ xrelaySessionId = webSessionId + '/' + host;
6623
xrelaySession = webRelaySessions[xrelaySessionId];
6624
if (xrelaySession == null) {
6625
// We found an unused hostname, save this as it could be useful.
@@ -6609,49 +6654,55 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
6654
}
6655
}
6656
6612
- // Check if there is a free relay DNS name we can use
6613
- var selectedHost = null;
6614
- if (freeRelayHost != null) {
6615
- // There is a free one, use it.
6616
- selectedHost = freeRelayHost;
6617
- } else {
6618
- // No free ones, close the oldest one
6619
- selectedHost = oldestRelayHost;
6620
- }
6621
- xrelaySessionId = req.session.userid + '/' + req.session.x + '/' + selectedHost;
6657
+ // Check that the user has rights to access this device
6658
+ parent.webserver.GetNodeWithRights(domain, userid, nodeid, function (node, rights, visible) {
6659
+ // If there is no remote control rights, reject this web relay
6660
+ if ((rights & 8) == 0) { res.sendStatus(404); return; }
6661
6623
- if (selectedHost == req.hostname) {
6624
- // If this web relay session id is not free, close it now
6625
- xrelaySession = webRelaySessions[xrelaySessionId];
6626
- if (xrelaySession != null) { xrelaySession.close(); delete webRelaySessions[xrelaySessionId]; }
6627
-
6628
- // Create a web relay session
6629
- const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySessionId);
6630
- relaySession.onclose = function (sessionId) {
6631
- // Remove the relay session
6632
- delete webRelaySessions[sessionId];
6633
- // If there are not more relay sessions, clear the cleanup timer
6634
- if ((Object.keys(webRelaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(webRelayCleanupTimer); obj.cleanupTimer = null; }
6662
+ // Check if there is a free relay DNS name we can use
6663
+ var selectedHost = null;
6664
+ if (freeRelayHost != null) {
6665
+ // There is a free one, use it.
6666
+ selectedHost = freeRelayHost;
6667
+ } else {
6668
+ // No free ones, close the oldest one
6669
+ selectedHost = oldestRelayHost;
6670
}
6671
+ xrelaySessionId = webSessionId + '/' + selectedHost;
6672
+
6673
+ if (selectedHost == req.hostname) {
6674
+ // If this web relay session id is not free, close it now
6675
+ xrelaySession = webRelaySessions[xrelaySessionId];
6676
+ if (xrelaySession != null) { xrelaySession.close(); delete webRelaySessions[xrelaySessionId]; }
6677
+
6678
+ // Create a web relay session
6679
+ const relaySession = require('./apprelays.js').CreateWebRelaySession(obj, db, req, args, domain, userid, nodeid, addr, port, appid, xrelaySessionId, expire);
6680
+ relaySession.onclose = function (sessionId) {
6681
+ // Remove the relay session
6682
+ delete webRelaySessions[sessionId];
6683
+ // If there are not more relay sessions, clear the cleanup timer
6684
+ if ((Object.keys(webRelaySessions).length == 0) && (obj.cleanupTimer != null)) { clearInterval(webRelayCleanupTimer); obj.cleanupTimer = null; }
6685
+ }
6686
6637
- // Set the multi-tunnel session
6638
- webRelaySessions[xrelaySessionId] = relaySession;
6687
+ // Set the multi-tunnel session
6688
+ webRelaySessions[xrelaySessionId] = relaySession;
6689
6640
- // Setup the cleanup timer if needed
6641
- if (obj.cleanupTimer == null) { webRelayCleanupTimer = setInterval(checkWebRelaySessionsTimeout, 10000); }
6690
+ // Setup the cleanup timer if needed
6691
+ if (obj.cleanupTimer == null) { webRelayCleanupTimer = setInterval(checkWebRelaySessionsTimeout, 10000); }
6692
6643
- // Redirect to root.
6644
- res.redirect('/');
6645
- } else {
6646
- if (req.query.noredirect != null) {
6647
- // No redirects allowed, fail here. This is important to make sure there is no redirect cascades
6648
- res.sendStatus(404);
6693
+ // Redirect to root.
6694
+ res.redirect('/');
6695
} else {
6650
- // Request was made to a different host, redirect using the full URL so an HTTP cookie can be created on the other DNS name.
6651
- const httpport = ((args.aliasport != null) ? args.aliasport : args.port);
6652
- res.redirect('https://' + selectedHost + ((httpport != 443) ? (':' + httpport) : '') + req.url + '&noredirect=1');
6696
+ if (req.query.noredirect != null) {
6697
+ // No redirects allowed, fail here. This is important to make sure there is no redirect cascades
6698
+ res.sendStatus(404);
6699
+ } else {
6700
+ // Request was made to a different host, redirect using the full URL so an HTTP cookie can be created on the other DNS name.
6701
+ const httpport = ((args.aliasport != null) ? args.aliasport : args.port);
6702
+ res.redirect('https://' + selectedHost + ((httpport != 443) ? (':' + httpport) : '') + req.url + '&noredirect=1');
6703
+ }
6704
}
6654
- }
6705
+ });
6706
});
6707
6708
// Handle all incoming requests as web relays
@@ -6956,8 +7007,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
7007
7008
// Handle an incoming request as a web relay
7009
function handleWebRelayRequest(req, res) {
6959
- if ((req.session.userid != null) && (req.session.x != null) && (obj.destroyedSessions[req.session.userid + '/' + req.session.x] == null)) {
6960
- var relaySession = webRelaySessions[req.session.userid + '/' + req.session.x + '/' + req.hostname];
7010
+ var webRelaySessionId = null;
7011
+ if ((req.session.userid != null) && (req.session.x != null)) { webRelaySessionId = req.session.userid + '/' + req.session.x; }
7012
+ else if (req.session.z != null) { webRelaySessionId = req.session.z; }
7013
+ if ((webRelaySessionId != null) && (obj.destroyedSessions[webRelaySessionId] == null)) {
7014
+ var relaySession = webRelaySessions[webRelaySessionId + '/' + req.hostname];
7015
if (relaySession != null) {
7016
// The web relay session is valid, use it
7017
relaySession.handleRequest(req, res);
@@ -6973,8 +7027,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
7027
7028
// Handle an incoming websocket connection as a web relay
7029
function handleWebRelayWebSocket(ws, req) {
6976
- if ((req.session.userid != null) && (req.session.x != null) && (obj.destroyedSessions[req.session.userid + '/' + req.session.x] == null)) {
6977
- var relaySession = webRelaySessions[req.session.userid + '/' + req.session.x + '/' + req.hostname];
7030
+ var webRelaySessionId = null;
7031
+ if ((req.session.userid != null) && (req.session.x != null)) { webRelaySessionId = req.session.userid + '/' + req.session.x; }
7032
+ else if (req.session.z != null) { webRelaySessionId = req.session.z; }
7033
+ if ((webRelaySessionId != null) && (obj.destroyedSessions[webRelaySessionId] == null)) {
7034
+ var relaySession = webRelaySessions[webRelaySessionId + '/' + req.hostname];
7035
if (relaySession != null) {
7036
// The multi-tunnel session is valid, use it
7037
relaySession.handleWebSocket(ws, req);