1. Added Windows Terminal Fix (ConPTY Leak/Workaround) back in 2. Added python/script XOPTION 3. Changed resize to use tcsetsize if available 4. Added tunnel idleTimeout Ping

1. Added Windows Terminal Fix (ConPTY Leak/Workaround) back in 2. Added python/script XOPTION 3. Changed resize to use tcsetsize if available 4. Added tunnel idleTimeout Ping

Bryan Roe committed Jan 28, 2020 at 15:08 UTC e7cd6d1db4044a717415c65cf72f6f39b3d65ae7
1 file changed +92 -49
agents/meshcore.js
+92 -49
@@ -1140,117 +1140,160 @@ function createMeshCore(agent) {
1140 };
1141
1142 // Remote terminal using native pipes
1143 - if (process.platform == 'win32') {
1144 - try {
1143 + if (process.platform == 'win32')
1144 + {
1145 + try
1146 + {
1147 var cols = 80, rows = 25;
1146 - if (this.httprequest.xoptions) {
1148 + if (this.httprequest.xoptions)
1149 + {
1150 if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
1151 if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
1152 }
1153
1154 if (!require('win-terminal').PowerShellCapable() && (this.httprequest.protocol == 6 || this.httprequest.protocol == 9)) { throw ('PowerShell is not supported on this version of windows'); }
1152 - if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6)) {
1155 + if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6))
1156 + {
1157 // Admin Terminal
1154 - if (require('win-virtual-terminal').supported) {
1158 + if (require('win-virtual-terminal').supported)
1159 + {
1160 // ConPTY PseudoTerminal
1156 - this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows);
1161 + // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
1162 +
1163 + // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
1164 + 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 == 9 ? 'StartPowerShell' : 'Start'), args: [cols, rows] } });
1165 + this.httprequest._dispatcher.ws = this;
1166 + this.httprequest._dispatcher.on('connection', function (c)
1167 + {
1168 + console.log('client connected');
1169 + this.ws._term = c;
1170 + c.pipe(this.ws, { dataTypeSkip: 1 });
1171 + this.ws.pipe(c, { dataTypeSkip: 1 });
1172 + });
1173 }
1158 - else {
1174 + else
1175 + {
1176 // Legacy Terminal
1177 this.httprequest._term = require('win-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](cols, rows);
1178 }
1162 - } else {
1179 + }
1180 + else
1181 + {
1182 // Logged in user
1183 var userPromise = require('user-sessions').enumerateUsers();
1184 userPromise.that = this;
1166 - userPromise.then(function (u) {
1185 + userPromise.then(function (u)
1186 + {
1187 var that = this.that;
1168 - if (u.Active.length > 0) {
1188 + if (u.Active.length > 0)
1189 + {
1190 var username = u.Active[0].Username;
1170 - if (require('win-virtual-terminal').supported) {
1191 + if (require('win-virtual-terminal').supported)
1192 + {
1193 // ConPTY PseudoTerminal
1194 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] } });
1173 - } else {
1195 + }
1196 + else
1197 + {
1198 // Legacy Terminal
1199 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] } });
1200 }
1201 that.httprequest._dispatcher.ws = that;
1178 - that.httprequest._dispatcher.on('connection', function (c) {
1202 + that.httprequest._dispatcher.on('connection', function (c)
1203 + {
1204 console.log('client connected');
1205 this.ws._term = c;
1206 c.pipe(this.ws, { dataTypeSkip: 1 });
1182 - this.ws.pipe(c, { dataTypeSkip: 1, end: false });
1183 - this.ws.prependListener('end', function () {
1184 - if (this.httprequest._term) { this.httprequest._term.end(function () { console.log("Terminal was closed"); }); }
1185 - });
1207 + this.ws.pipe(c, { dataTypeSkip: 1 });
1208 });
1209 }
1188 - });
1210 + });
1211 }
1190 - } catch (e) {
1212 + } catch (e)
1213 + {
1214 MeshServerLog('Failed to start remote terminal session, ' + e.toString() + ' (' + this.httprequest.remoteaddr + ')', this.httprequest);
1215 this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() }));
1216 this.end();
1217 return;
1218 }
1196 - if (!this.httprequest._dispatcher) {
1219 + if (!this.httprequest._dispatcher)
1220 + {
1221 this.httprequest._term.pipe(this, { dataTypeSkip: 1 });
1222 this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false });
1223 this.prependListener('end', function () { this.httprequest._term.end(function () { console.log("Terminal was closed"); }); });
1224 }
1201 - } else {
1202 - try {
1225 + }
1226 + else
1227 + {
1228 + try
1229 + {
1230 var bash = fs.existsSync('/bin/bash') ? '/bin/bash' : false;
1231 var sh = fs.existsSync('/bin/sh') ? '/bin/sh' : false;
1232 var script = false;
1206 - try {
1207 - if (require('linux-gnome-helpers').scriptVersion) {
1208 - if (require('linux-gnome-helpers').scriptVersion.major > 2 ||
1209 - (require('linux-gnome-helpers').scriptVersion.major == 2 && require('linux-gnome-helpers').scriptVersion.minor >= 25)) {
1210 - script = '/usr/bin/script';
1233 + if (this.httprequest.xoptions.script)
1234 + {
1235 + try
1236 + {
1237 + if (require('linux-gnome-helpers').scriptVersion)
1238 + {
1239 + if (require('linux-gnome-helpers').scriptVersion.major > 2 ||
1240 + (require('linux-gnome-helpers').scriptVersion.major == 2 && require('linux-gnome-helpers').scriptVersion.minor >= 25))
1241 + {
1242 + script = '/usr/bin/script';
1243 + }
1244 }
1212 - }
1213 - } catch (ex) { }
1214 - var python = fs.existsSync('/usr/bin/python') ? '/usr/bin/python' : false;
1245 + } catch (ex) { }
1246 + }
1247 + var python = (this.httprequest.xoptions.python && fs.existsSync('/usr/bin/python')) ? '/usr/bin/python' : false;
1248 var shell = bash || sh;
1216 - var pty = python || script;
1249
1250 var env = { HISTCONTROL: 'ignoreboth', TERM: 'xterm' };
1219 - if (this.httprequest.xoptions) {
1251 + if (this.httprequest.xoptions)
1252 + {
1253 if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
1254 if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
1255 }
1256 var options = { uid: (this.httprequest.protocol == 8) ? require('user-sessions').consoleUid() : null, env: env };
1257 var setupcommands = ' alias ls=\'ls --color=auto\'\n';
1258 if (shell == sh) setupcommands += ' stty erase ^H\n';
1226 - // Dynamic resizing is only supported in PTYs
1227 - if (pty) setupcommands += ' mcresize() { old=$(stty -g);stty raw -echo min 0 time 5;printf \'\\0337\\033[r\\033[999;999H\\033[6n\\0338\' > /dev/tty;IFS=\'[;R\' read -r _ rows cols _ < /dev/tty;stty "$old";stty cols "$cols" rows "$rows"; };trap mcresize SIGWINCH;\n'
1259
1229 - if (script && shell && process.platform == 'linux') {
1260 + if (script && shell && process.platform == 'linux')
1261 + {
1262 this.httprequest.process = childProcess.execFile(script, ['script', '--return', '--quiet', '-c', '"' + shell + '"', '/dev/null'], options); // Start as active user
1263 this.httprequest.process.stdin.write(setupcommands);
1232 - } else if (python && shell) {
1264 + }
1265 + else if (python && shell)
1266 + {
1267 this.httprequest.process = childProcess.execFile(python, ['python', '-c', 'import pty; pty.spawn(["' + shell + '"])'], options); // Start as active user
1268 if (process.platform == 'linux') { this.httprequest.process.stdin.write(setupcommands); }
1235 - } else if (bash) {
1269 + }
1270 + else if (bash)
1271 + {
1272 options.type = childProcess.SpawnTypes.TERM;
1273 this.httprequest.process = childProcess.execFile(bash, ['bash', '-i'], options); // Start as active user
1274 if (process.platform == 'linux') { this.httprequest.process.stdin.write(setupcommands); }
1239 - } else if (sh) {
1275 + }
1276 + else if (sh)
1277 + {
1278 options.type = childProcess.SpawnTypes.TERM;
1279 this.httprequest.process = childProcess.execFile(sh, ['sh'], options); // Start as active user
1280 if (process.platform == 'linux') { this.httprequest.process.stdin.write(setupcommands + "PS1='$ '\n"); }
1243 - } else {
1281 + }
1282 + else
1283 + {
1284 MeshServerLog("Failed to start remote terminal session, no shell found");
1285 return;
1286 }
1247 - } catch (e) {
1287 + } catch (e)
1288 + {
1289 MeshServerLog("Failed to start remote terminal session, " + e.toString() + ' (' + this.httprequest.remoteaddr + ')', this.httprequest);
1290 this.write(JSON.stringify({ ctrlChannel: '102938', type: 'console', msg: e.toString() }));
1291 this.end();
1292 return;
1293 }
1253 - this.httprequest.process.pty = pty;
1294 +
1295 + if (this.httprequest.process.tcsetsize && this.httprequest.xoptions) { this.httprequest.process.tcsetsize(this.httprequest.xoptions.rows, this.httprequest.xoptions.cols); }
1296 +
1297 this.httprequest.process.tunnel = this;
1298 this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
1299 this.httprequest.process.stdin.write(" clear\n");
@@ -1786,17 +1829,17 @@ function createMeshCore(agent) {
1829 }
1830 case 'termsize': {
1831 // Indicates a change in terminal size
1789 - if (process.platform == 'win32') {
1832 + if (process.platform == 'win32')
1833 + {
1834 if (ws.httprequest._term == null) return;
1835 //sendConsoleText('Win32-TermSize: ' + obj.cols + 'x' + obj.rows);
1836 // TODO
1793 - } else {
1794 - if (ws.httprequest.process == null || !ws.httprequest.process.pty) return;
1795 - // ILibDuktape_ChildProcess kill doesn't support sending signals
1796 - if (fs.existsSync('/bin/kill')) {
1797 - // We need to send signal to the child of the process, since the child is the shell
1798 - childProcess.execFile('/bin/bash', ['bash', '-c', 'kill -SIGWINCH $(pgrep -P ' + ws.httprequest.process.pid + ')']);
1799 - }
1837 + } else
1838 + {
1839 + if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
1840 + //sendConsoleText('Linux Resize: ' + obj.cols + 'x' + obj.rows);
1841 +
1842 + if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); }
1843 }
1844 break;
1845 }