tabby-rdp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +150 -0
- package/dist/desk.js +86 -0
- package/dist/desk.js.map +1 -0
- package/dist/deskScript.js +44 -0
- package/dist/deskScript.js.map +1 -0
- package/dist/desktop.service.js +665 -0
- package/dist/desktop.service.js.map +1 -0
- package/dist/desktopForm.js +96 -0
- package/dist/desktopForm.js.map +1 -0
- package/dist/desktops.js +45 -0
- package/dist/desktops.js.map +1 -0
- package/dist/header.js +164 -0
- package/dist/header.js.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/rdcleanpath.js +255 -0
- package/dist/rdcleanpath.js.map +1 -0
- package/dist/remoteSetup.js +188 -0
- package/dist/remoteSetup.js.map +1 -0
- package/dist/signin.js +114 -0
- package/dist/signin.js.map +1 -0
- package/dist/ssh.js +54 -0
- package/dist/ssh.js.map +1 -0
- package/dist/sshChannelStream.js +34 -0
- package/dist/sshChannelStream.js.map +1 -0
- package/dist/targets.js +247 -0
- package/dist/targets.js.map +1 -0
- package/dist/ui.js +212 -0
- package/dist/ui.js.map +1 -0
- package/package.json +41 -0
- package/remote/trd-pty.py +561 -0
- package/vendor/IRONRDP-LICENSE +27 -0
- package/vendor/iron-remote-desktop-rdp.js +2878 -0
- package/vendor/iron-remote-desktop.js +4136 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startRDCleanPathProxy = startRDCleanPathProxy;
|
|
4
|
+
// RDCleanPath proxy for the IronRDP web client, running inside Tabby's renderer.
|
|
5
|
+
// The WASM client talks WebSocket to this proxy; the proxy opens the upstream stream
|
|
6
|
+
// (a plain socket here, an SSH direct-tcpip channel later), forwards the X.224 request,
|
|
7
|
+
// terminates TLS, hands the server cert chain back to the client (for CredSSP), then relays.
|
|
8
|
+
const tls = require("tls");
|
|
9
|
+
const crypto_1 = require("crypto");
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
11
|
+
const { WebSocketServer } = require('ws');
|
|
12
|
+
const VERSION_1 = 3390;
|
|
13
|
+
const GENERAL_ERROR = 1;
|
|
14
|
+
// ---- minimal DER -------------------------------------------------------------------------------
|
|
15
|
+
function derLength(n) {
|
|
16
|
+
if (n < 0x80) {
|
|
17
|
+
return Buffer.from([n]);
|
|
18
|
+
}
|
|
19
|
+
const bytes = [];
|
|
20
|
+
for (let v = n; v > 0; v = Math.floor(v / 256)) {
|
|
21
|
+
bytes.unshift(v & 0xff);
|
|
22
|
+
}
|
|
23
|
+
return Buffer.from([0x80 | bytes.length, ...bytes]);
|
|
24
|
+
}
|
|
25
|
+
function tlv(tag, content) {
|
|
26
|
+
return Buffer.concat([Buffer.from([tag]), derLength(content.length), content]);
|
|
27
|
+
}
|
|
28
|
+
function derInteger(n) {
|
|
29
|
+
let hex = n.toString(16);
|
|
30
|
+
if (hex.length % 2) {
|
|
31
|
+
hex = '0' + hex;
|
|
32
|
+
}
|
|
33
|
+
let b = Buffer.from(hex, 'hex');
|
|
34
|
+
if (b[0] & 0x80) {
|
|
35
|
+
b = Buffer.concat([Buffer.from([0]), b]);
|
|
36
|
+
}
|
|
37
|
+
return tlv(0x02, b);
|
|
38
|
+
}
|
|
39
|
+
const seq = (...items) => tlv(0x30, Buffer.concat(items));
|
|
40
|
+
const explicit = (n, inner) => tlv(0xa0 | n, inner);
|
|
41
|
+
const octets = (b) => tlv(0x04, b);
|
|
42
|
+
const utf8 = (s) => tlv(0x0c, Buffer.from(s, 'utf8'));
|
|
43
|
+
function readTlv(buf, off) {
|
|
44
|
+
if (buf.length < off + 2) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
let len = buf[off + 1];
|
|
48
|
+
let p = off + 2;
|
|
49
|
+
if (len & 0x80) {
|
|
50
|
+
const k = len & 0x7f;
|
|
51
|
+
if (buf.length < p + k) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
len = 0;
|
|
55
|
+
for (let i = 0; i < k; i++) {
|
|
56
|
+
len = len * 256 + buf[p++];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { tag: buf[off], start: p, end: p + len };
|
|
60
|
+
}
|
|
61
|
+
function decodeRequest(buf) {
|
|
62
|
+
const outer = readTlv(buf, 0);
|
|
63
|
+
if (!outer || outer.tag !== 0x30) {
|
|
64
|
+
throw new Error('RDCleanPath: not a SEQUENCE');
|
|
65
|
+
}
|
|
66
|
+
const req = {};
|
|
67
|
+
let p = outer.start;
|
|
68
|
+
while (p < outer.end) {
|
|
69
|
+
const field = readTlv(buf, p);
|
|
70
|
+
const inner = readTlv(buf, field.start);
|
|
71
|
+
const value = buf.subarray(inner.start, inner.end);
|
|
72
|
+
switch (field.tag & 0x1f) {
|
|
73
|
+
case 0:
|
|
74
|
+
req.version = value.readUIntBE(0, value.length);
|
|
75
|
+
break;
|
|
76
|
+
case 2:
|
|
77
|
+
req.destination = value.toString('utf8');
|
|
78
|
+
break;
|
|
79
|
+
case 3:
|
|
80
|
+
req.proxyAuth = value.toString('utf8');
|
|
81
|
+
break;
|
|
82
|
+
case 6:
|
|
83
|
+
req.x224 = Buffer.from(value);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
p = field.end;
|
|
87
|
+
}
|
|
88
|
+
return req;
|
|
89
|
+
}
|
|
90
|
+
function encodeResponse(x224, certChain, serverAddr) {
|
|
91
|
+
return seq(explicit(0, derInteger(VERSION_1)), explicit(6, octets(x224)), explicit(7, seq(...certChain.map(octets))), explicit(9, utf8(serverAddr)));
|
|
92
|
+
}
|
|
93
|
+
function encodeError() {
|
|
94
|
+
return seq(explicit(0, derInteger(VERSION_1)), explicit(1, seq(explicit(0, derInteger(GENERAL_ERROR)))));
|
|
95
|
+
}
|
|
96
|
+
// ---- upstream handshake ------------------------------------------------------------------------
|
|
97
|
+
// Reads exactly one TPKT-framed PDU (the X.224 Connection Confirm) without consuming TLS bytes.
|
|
98
|
+
function readTpkt(stream) {
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
let acc = Buffer.alloc(0);
|
|
101
|
+
const cleanup = () => {
|
|
102
|
+
stream.off('data', onData);
|
|
103
|
+
stream.off('error', onError);
|
|
104
|
+
stream.off('close', onClose);
|
|
105
|
+
};
|
|
106
|
+
const onError = (e) => { cleanup(); reject(e); };
|
|
107
|
+
const onClose = () => { cleanup(); reject(new Error('upstream closed during X.224')); };
|
|
108
|
+
const onData = (chunk) => {
|
|
109
|
+
acc = Buffer.concat([acc, chunk]);
|
|
110
|
+
if (acc.length < 4) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const len = acc.readUInt16BE(2);
|
|
114
|
+
if (acc.length < len) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
stream.pause();
|
|
118
|
+
cleanup();
|
|
119
|
+
if (acc.length > len) {
|
|
120
|
+
stream.unshift(acc.subarray(len));
|
|
121
|
+
}
|
|
122
|
+
resolve(acc.subarray(0, len));
|
|
123
|
+
};
|
|
124
|
+
stream.on('data', onData);
|
|
125
|
+
stream.on('error', onError);
|
|
126
|
+
stream.on('close', onClose);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function certChainOf(socket) {
|
|
130
|
+
const chain = [];
|
|
131
|
+
let cert = socket.getPeerCertificate(true);
|
|
132
|
+
const seen = new Set();
|
|
133
|
+
while (cert?.raw && !seen.has(cert.fingerprint256)) {
|
|
134
|
+
seen.add(cert.fingerprint256);
|
|
135
|
+
chain.push(cert.raw);
|
|
136
|
+
cert = cert.issuerCertificate;
|
|
137
|
+
}
|
|
138
|
+
return chain;
|
|
139
|
+
}
|
|
140
|
+
// ---- proxy -------------------------------------------------------------------------------------
|
|
141
|
+
/**
|
|
142
|
+
* TLS for servers whose certificate only allows key encipherment, like Windows' self-signed RDP certificate.
|
|
143
|
+
* BoringSSL (Electron) enforces the certificate's key usage, and every ECDHE or TLS 1.3 handshake needs the
|
|
144
|
+
* digital-signature bit, so only TLS 1.2 with RSA key exchange works with such a certificate (other RDP
|
|
145
|
+
* clients use OpenSSL, which doesn't enforce it).
|
|
146
|
+
*/
|
|
147
|
+
const KEY_ENCIPHERMENT_ONLY_TLS = {
|
|
148
|
+
maxVersion: 'TLSv1.2',
|
|
149
|
+
ciphers: 'AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256',
|
|
150
|
+
};
|
|
151
|
+
/** Sends the client's X.224 request upstream, reads the confirm, then does TLS there. */
|
|
152
|
+
async function handshake(openUpstream, destination, x224Request, legacyTls) {
|
|
153
|
+
const raw = await openUpstream(destination);
|
|
154
|
+
try {
|
|
155
|
+
raw.write(x224Request);
|
|
156
|
+
const x224 = await readTpkt(raw);
|
|
157
|
+
const upstream = tls.connect({ socket: raw, rejectUnauthorized: false, ...legacyTls ? KEY_ENCIPHERMENT_ONLY_TLS : {} });
|
|
158
|
+
await new Promise((resolve, reject) => {
|
|
159
|
+
upstream.once('secureConnect', resolve);
|
|
160
|
+
upstream.once('error', reject);
|
|
161
|
+
});
|
|
162
|
+
return { raw, upstream, x224 };
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
raw.destroy();
|
|
166
|
+
throw e;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function startRDCleanPathProxy(openUpstream, log = () => { }) {
|
|
170
|
+
const token = (0, crypto_1.randomBytes)(24).toString('hex');
|
|
171
|
+
// Set once the server turned out to need KEY_ENCIPHERMENT_ONLY_TLS; later connections start with it.
|
|
172
|
+
let legacyTls = false;
|
|
173
|
+
const wss = new WebSocketServer({ host: '127.0.0.1', port: 0 });
|
|
174
|
+
await new Promise((resolve, reject) => {
|
|
175
|
+
wss.once('listening', resolve);
|
|
176
|
+
wss.once('error', reject);
|
|
177
|
+
});
|
|
178
|
+
wss.on('connection', (ws) => {
|
|
179
|
+
let buf = Buffer.alloc(0);
|
|
180
|
+
let stage = 'hello';
|
|
181
|
+
const early = [];
|
|
182
|
+
let upstream = null;
|
|
183
|
+
let raw = null;
|
|
184
|
+
const fail = (why) => {
|
|
185
|
+
log(`RDCleanPath failed: ${why}`);
|
|
186
|
+
try {
|
|
187
|
+
ws.send(encodeError());
|
|
188
|
+
}
|
|
189
|
+
catch { }
|
|
190
|
+
ws.close();
|
|
191
|
+
upstream?.destroy();
|
|
192
|
+
raw?.destroy();
|
|
193
|
+
};
|
|
194
|
+
ws.on('message', async (data) => {
|
|
195
|
+
if (stage === 'relay') {
|
|
196
|
+
upstream.write(data);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (stage === 'handshake') {
|
|
200
|
+
early.push(data);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
buf = Buffer.concat([buf, data]);
|
|
204
|
+
const outer = readTlv(buf, 0);
|
|
205
|
+
if (!outer || buf.length < outer.end) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
stage = 'handshake';
|
|
209
|
+
try {
|
|
210
|
+
const req = decodeRequest(buf.subarray(0, outer.end));
|
|
211
|
+
if (req.version !== VERSION_1 || !req.x224 || !req.destination) {
|
|
212
|
+
return fail('malformed request');
|
|
213
|
+
}
|
|
214
|
+
if (req.proxyAuth !== token) {
|
|
215
|
+
return fail('bad token');
|
|
216
|
+
}
|
|
217
|
+
let up;
|
|
218
|
+
try {
|
|
219
|
+
up = await handshake(openUpstream, req.destination, req.x224, legacyTls);
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
if (legacyTls || !/KEY_USAGE_BIT_INCORRECT/.test(e?.message ?? '')) {
|
|
223
|
+
throw e;
|
|
224
|
+
}
|
|
225
|
+
// The X.224 exchange is spent on that connection: start over on a fresh one.
|
|
226
|
+
log('TLS: the server certificate only allows key encipherment; using TLS 1.2 with RSA key exchange');
|
|
227
|
+
legacyTls = true;
|
|
228
|
+
up = await handshake(openUpstream, req.destination, req.x224, legacyTls);
|
|
229
|
+
}
|
|
230
|
+
raw = up.raw;
|
|
231
|
+
upstream = up.upstream;
|
|
232
|
+
const x224 = up.x224;
|
|
233
|
+
ws.send(encodeResponse(x224, certChainOf(upstream), req.destination));
|
|
234
|
+
stage = 'relay';
|
|
235
|
+
upstream.on('data', (d) => ws.send(d));
|
|
236
|
+
upstream.on('close', () => ws.close());
|
|
237
|
+
upstream.on('error', (e) => { log(`upstream error: ${e.message}`); ws.close(); });
|
|
238
|
+
for (const d of early) {
|
|
239
|
+
upstream.write(d);
|
|
240
|
+
}
|
|
241
|
+
log(`RDCleanPath relay up to ${req.destination}`);
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
fail(e?.message ?? String(e));
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
ws.on('close', () => {
|
|
248
|
+
upstream?.destroy();
|
|
249
|
+
raw?.destroy();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
const { port } = wss.address();
|
|
253
|
+
return { url: `ws://127.0.0.1:${port}`, token, close: () => wss.close() };
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=rdcleanpath.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rdcleanpath.js","sourceRoot":"","sources":["../src/rdcleanpath.ts"],"names":[],"mappings":";;AAiMA,sDAoFC;AArRD,iFAAiF;AACjF,qFAAqF;AACrF,wFAAwF;AACxF,6FAA6F;AAC7F,2BAA0B;AAC1B,mCAAoC;AAIpC,8DAA8D;AAC9D,MAAM,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAEzC,MAAM,SAAS,GAAG,IAAI,CAAA;AACtB,MAAM,aAAa,GAAG,CAAC,CAAA;AAUvB,mGAAmG;AAEnG,SAAS,SAAS,CAAE,CAAS;IACzB,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC;QACX,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3B,CAAC;IACD,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QAC7C,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IAC3B,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAA;AACvD,CAAC;AAED,SAAS,GAAG,CAAE,GAAW,EAAE,OAAe;IACtC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,CAAA;AAClF,CAAC;AAED,SAAS,UAAU,CAAE,CAAS;IAC1B,IAAI,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IACxB,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjB,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;IACnB,CAAC;IACD,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC/B,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC;QACd,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC5C,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;AACvB,CAAC;AAED,MAAM,GAAG,GAAG,CAAC,GAAG,KAAe,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;AACnE,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,KAAa,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,EAAE,KAAK,CAAC,CAAA;AACnE,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;AAC1C,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;AAI7D,SAAS,OAAO,CAAE,GAAW,EAAE,GAAW;IACtC,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAA;IACf,CAAC;IACD,IAAI,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA;IACtB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,CAAA;IACf,IAAI,GAAG,GAAG,IAAI,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,GAAG,GAAG,IAAI,CAAA;QACpB,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,IAAI,CAAA;QACf,CAAC;QACD,GAAG,GAAG,CAAC,CAAA;QACP,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACzB,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAA;QAC9B,CAAC;IACL,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,CAAA;AACpD,CAAC;AAID,SAAS,aAAa,CAAE,GAAW;IAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IAC7B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;IAClD,CAAC;IACD,MAAM,GAAG,GAAY,EAAE,CAAA;IACvB,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAA;IACnB,OAAO,CAAC,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QACnB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAE,CAAA;QAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAE,CAAA;QACxC,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;QAClD,QAAQ,KAAK,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC;YACvB,KAAK,CAAC;gBAAE,GAAG,CAAC,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;gBAAC,MAAK;YAC9D,KAAK,CAAC;gBAAE,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAAC,MAAK;YACvD,KAAK,CAAC;gBAAE,GAAG,CAAC,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAAC,MAAK;YACrD,KAAK,CAAC;gBAAE,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAAC,MAAK;QAChD,CAAC;QACD,CAAC,GAAG,KAAK,CAAC,GAAG,CAAA;IACjB,CAAC;IACD,OAAO,GAAG,CAAA;AACd,CAAC;AAED,SAAS,cAAc,CAAE,IAAY,EAAE,SAAmB,EAAE,UAAkB;IAC1E,OAAO,GAAG,CACN,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,EAClC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EACzB,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAC1C,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAChC,CAAA;AACL,CAAC;AAED,SAAS,WAAW;IAChB,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAC5G,CAAC;AAED,mGAAmG;AAEnG,gGAAgG;AAChG,SAAS,QAAQ,CAAE,MAAc;IAC7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,IAAI,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACzB,MAAM,OAAO,GAAG,GAAG,EAAE;YACjB,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;YAC1B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC5B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAChC,CAAC,CAAA;QACD,MAAM,OAAO,GAAG,CAAC,CAAQ,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC,CAAA;QACtD,MAAM,OAAO,GAAG,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAA,CAAC,CAAC,CAAA;QACtF,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE;YAC7B,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;YACjC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjB,OAAM;YACV,CAAC;YACD,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;YAC/B,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBACnB,OAAM;YACV,CAAC;YACD,MAAM,CAAC,KAAK,EAAE,CAAA;YACd,OAAO,EAAE,CAAA;YACT,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBACnB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAA;YACrC,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QACjC,CAAC,CAAA;QACD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACzB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC3B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAC/B,CAAC,CAAC,CAAA;AACN,CAAC;AAED,SAAS,WAAW,CAAE,MAAqB;IACvC,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,IAAI,GAAQ,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAA;IAC/C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,OAAO,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QAC7B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACpB,IAAI,GAAG,IAAI,CAAC,iBAAiB,CAAA;IACjC,CAAC;IACD,OAAO,KAAK,CAAA;AAChB,CAAC;AAED,mGAAmG;AAEnG;;;;;GAKG;AACH,MAAM,yBAAyB,GAA0B;IACrD,UAAU,EAAE,SAAS;IACrB,OAAO,EAAE,iEAAiE;CAC7E,CAAA;AAED,yFAAyF;AACzF,KAAK,UAAU,SAAS,CAAE,YAA6B,EAAE,WAAmB,EAAE,WAAmB,EAAE,SAAkB;IACjH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,WAAW,CAAC,CAAA;IAC3C,IAAI,CAAC;QACD,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;QACtB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAA;QAChC,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAU,EAAE,kBAAkB,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QAC9H,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxC,QAAQ,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;YACvC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAClC,CAAC,CAAC,CAAA;QACF,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;IAClC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,GAAG,CAAC,OAAO,EAAE,CAAA;QACb,MAAM,CAAC,CAAA;IACX,CAAC;AACL,CAAC;AAEM,KAAK,UAAU,qBAAqB,CAAE,YAA6B,EAAE,MAA6B,GAAG,EAAE,GAAE,CAAC;IAC7G,MAAM,KAAK,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,qGAAqG;IACrG,IAAI,SAAS,GAAG,KAAK,CAAA;IACrB,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;IAC/D,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;QAC9B,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC7B,CAAC,CAAC,CAAA;IAEF,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAO,EAAE,EAAE;QAC7B,IAAI,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACzB,IAAI,KAAK,GAAoC,OAAO,CAAA;QACpD,MAAM,KAAK,GAAa,EAAE,CAAA;QAC1B,IAAI,QAAQ,GAAyB,IAAI,CAAA;QACzC,IAAI,GAAG,GAAkB,IAAI,CAAA;QAE7B,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,EAAE;YACzB,GAAG,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAA;YACjC,IAAI,CAAC;gBAAC,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAA;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,CAAC;YACxC,EAAE,CAAC,KAAK,EAAE,CAAA;YACV,QAAQ,EAAE,OAAO,EAAE,CAAA;YACnB,GAAG,EAAE,OAAO,EAAE,CAAA;QAClB,CAAC,CAAA;QAED,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE;YACpC,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;gBACpB,QAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACrB,OAAM;YACV,CAAC;YACD,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;gBACxB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAChB,OAAM;YACV,CAAC;YACD,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAA;YAChC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;YAC7B,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;gBACnC,OAAM;YACV,CAAC;YACD,KAAK,GAAG,WAAW,CAAA;YACnB,IAAI,CAAC;gBACD,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;gBACrD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;oBAC7D,OAAO,IAAI,CAAC,mBAAmB,CAAC,CAAA;gBACpC,CAAC;gBACD,IAAI,GAAG,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;oBAC1B,OAAO,IAAI,CAAC,WAAW,CAAC,CAAA;gBAC5B,CAAC;gBACD,IAAI,EAAyC,CAAA;gBAC7C,IAAI,CAAC;oBACD,EAAE,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;gBAC5E,CAAC;gBAAC,OAAO,CAAM,EAAE,CAAC;oBACd,IAAI,SAAS,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;wBACjE,MAAM,CAAC,CAAA;oBACX,CAAC;oBACD,6EAA6E;oBAC7E,GAAG,CAAC,+FAA+F,CAAC,CAAA;oBACpG,SAAS,GAAG,IAAI,CAAA;oBAChB,EAAE,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;gBAC5E,CAAC;gBACD,GAAG,GAAG,EAAE,CAAC,GAAG,CAAA;gBACZ,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAA;gBACtB,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,CAAA;gBACpB,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAA;gBACrE,KAAK,GAAG,OAAO,CAAA;gBACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;gBAC9C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAA;gBACtC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAQ,EAAE,EAAE,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;gBACvF,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;oBACpB,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;gBACrB,CAAC;gBACD,GAAG,CAAC,2BAA2B,GAAG,CAAC,WAAW,EAAE,CAAC,CAAA;YACrD,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBACd,IAAI,CAAC,CAAC,EAAE,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;YACjC,CAAC;QACL,CAAC,CAAC,CAAA;QACF,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAChB,QAAQ,EAAE,OAAO,EAAE,CAAA;YACnB,GAAG,EAAE,OAAO,EAAE,CAAA;QAClB,CAAC,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;IAEF,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,OAAO,EAAiB,CAAA;IAC7C,OAAO,EAAE,GAAG,EAAE,kBAAkB,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAA;AAC7E,CAAC"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prepareRemoteDesktop = prepareRemoteDesktop;
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
// Runs as the SSH user; needs no root. Prints exactly one `RD_OK port=N user=U pass=P` or `RD_ERR <reason>` line.
|
|
7
|
+
// The RDP password is generated once per remote user and kept in a 0600 file; grd reads credentials only
|
|
8
|
+
// at startup, so grd is restarted only when its configuration actually changes (that drops live sessions).
|
|
9
|
+
// (grdctl takes the credentials as arguments, so they are briefly visible in the remote's process list.)
|
|
10
|
+
const SETUP_SCRIPT = String.raw `
|
|
11
|
+
say () { printf '%s\n' "$*"; }
|
|
12
|
+
fail () { say "RD_ERR $*"; exit 0; }
|
|
13
|
+
command -v grdctl >/dev/null 2>&1 || fail "GNOME Remote Desktop (grdctl) is not installed"
|
|
14
|
+
command -v gnome-shell >/dev/null 2>&1 || fail "gnome-shell is not installed"
|
|
15
|
+
if systemctl --user is-active -q gnome-remote-desktop.service; then
|
|
16
|
+
fail "a desktop-session GNOME Remote Desktop is already running for this user"
|
|
17
|
+
fi
|
|
18
|
+
D="$HOME/.local/share/tabby-remote-desktop"
|
|
19
|
+
mkdir -p "$D" && chmod 700 "$D" || fail "could not create $D"
|
|
20
|
+
if [ ! -s "$D/tls.key" ]; then
|
|
21
|
+
openssl req -new -newkey rsa:3072 -days 3650 -nodes -x509 -subj "/CN=$(hostname)" \
|
|
22
|
+
-keyout "$D/tls.key" -out "$D/tls.crt" >/dev/null 2>&1 || fail "could not create a TLS certificate"
|
|
23
|
+
fi
|
|
24
|
+
# 'desk' support (setting, off by default): interactive SSH logins start inside a shareable session (trd-pty,
|
|
25
|
+
# or tmux), so 'desk' can attach this exact console to a terminal on the desktop. Rewritten on every connect;
|
|
26
|
+
# opt out per login with TABBY_NO_SESSION=1. When the setting is off, the hook and helpers are removed.
|
|
27
|
+
if [ "${"$"}{TRD_DESK:-0}" = 1 ]; then
|
|
28
|
+
mkdir -p "$D/bin"
|
|
29
|
+
cat > "$D/login.sh" <<'TRD_EOF'
|
|
30
|
+
# tabby-rdp ('desk' setting): interactive SSH logins run inside a shareable session, so 'desk' in Tabby can
|
|
31
|
+
# bring this console to the remote desktop. Backend ("native" trd-pty or "tmux") is in $TRD_HOME/backend.
|
|
32
|
+
# Set TABBY_NO_SESSION=1 (or TABBY_NO_TMUX=1) to skip; turning 'desk' off in Tabby removes this hook.
|
|
33
|
+
TRD_HOME="$HOME/.local/share/tabby-remote-desktop"
|
|
34
|
+
case ":$PATH:" in *":$TRD_HOME/bin:"*) ;; *) PATH="$TRD_HOME/bin:$PATH"; export PATH ;; esac
|
|
35
|
+
case $- in *i*) ;; *) return 0 ;; esac
|
|
36
|
+
if [ -n "$SSH_TTY" ] && [ -z "$TMUX" ] && [ -z "$TRD_SESSION" ] && [ -z "$TABBY_NO_TMUX$TABBY_NO_SESSION" ]; then
|
|
37
|
+
TRD_BACKEND=$(cat "$TRD_HOME/backend" 2>/dev/null)
|
|
38
|
+
if [ "$TRD_BACKEND" = native ] && command -v python3 >/dev/null 2>&1; then
|
|
39
|
+
"$TRD_HOME/bin/trd-pty" new
|
|
40
|
+
case $? in 0) exit ;; 3) TRD_BACKEND=tmux ;; esac # 3: no session could be started
|
|
41
|
+
fi
|
|
42
|
+
if [ "$TRD_BACKEND" != native ] && command -v tmux >/dev/null 2>&1; then
|
|
43
|
+
tmux -L tabby -f "$TRD_HOME/tmux.conf" new-session && exit
|
|
44
|
+
fi
|
|
45
|
+
fi
|
|
46
|
+
TRD_EOF
|
|
47
|
+
cat > "$D/tmux.conf" <<'TRD_EOF'
|
|
48
|
+
# tmux server for SSH logins (tabby-rdp 'desk'). Your ~/.tmux.conf is applied on top.
|
|
49
|
+
set -g status off
|
|
50
|
+
# Like a plain SSH login: the session ends when its last client (console or desktop terminal) goes away.
|
|
51
|
+
set -g destroy-unattached on
|
|
52
|
+
# Console and desktop terminal can differ in size: follow whichever was used last. Focus reports make
|
|
53
|
+
# switching back (Tabby focuses the console) count as use, so the window resizes right away.
|
|
54
|
+
set -g window-size latest
|
|
55
|
+
set -g focus-events on
|
|
56
|
+
# Lets 'desk' reach Tabby from inside tmux.
|
|
57
|
+
set -g allow-passthrough on
|
|
58
|
+
# Keep the terminal's own scrollback (no alternate screen).
|
|
59
|
+
set -ga terminal-overrides ',*:smcup@:rmcup@'
|
|
60
|
+
source-file -q ~/.tmux.conf
|
|
61
|
+
TRD_EOF
|
|
62
|
+
cat > "$D/bin/desk" <<'TRD_EOF'
|
|
63
|
+
#!/bin/sh
|
|
64
|
+
# desk: open this machine's desktop in Tabby, with this console (its shared session) waiting in a terminal there.
|
|
65
|
+
b64 () { printf %s "$1" | base64 | tr -d '\n'; }
|
|
66
|
+
KIND= SESSION= SOCKET=
|
|
67
|
+
if [ -n "$TRD_SESSION" ]; then
|
|
68
|
+
KIND=native SESSION=$TRD_SESSION SOCKET=$TRD_SOCKET
|
|
69
|
+
elif [ -n "$TMUX" ]; then
|
|
70
|
+
KIND=tmux
|
|
71
|
+
SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
|
|
72
|
+
SOCKET=$(tmux display-message -p '#{socket_path}' 2>/dev/null)
|
|
73
|
+
tmux set-option -p allow-passthrough on 2>/dev/null
|
|
74
|
+
fi
|
|
75
|
+
SEQ=$(printf '\033]7777;desk;%s;%s;%s;%s\007' "$(b64 "$SESSION")" "$(b64 "$SOCKET")" "$(b64 "$PWD")" "$(b64 "$KIND")")
|
|
76
|
+
if [ "$KIND" = tmux ]; then
|
|
77
|
+
printf '\033Ptmux;\033%s\033\\' "$SEQ" # tmux only lets it through as passthrough
|
|
78
|
+
else
|
|
79
|
+
printf '%s' "$SEQ" # trd-pty passes output through byte for byte
|
|
80
|
+
fi
|
|
81
|
+
TRD_EOF
|
|
82
|
+
chmod 755 "$D/bin/desk"
|
|
83
|
+
cat > "$D/bin/trd-pty" <<'TRD_PY_EOF'
|
|
84
|
+
@@TRD_PTY_PY@@
|
|
85
|
+
TRD_PY_EOF
|
|
86
|
+
chmod 755 "$D/bin/trd-pty"
|
|
87
|
+
printf '%s\n' "${"$"}{TRD_BACKEND:-native}" > "$D/backend"
|
|
88
|
+
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
|
|
89
|
+
[ -f "$rc" ] || continue
|
|
90
|
+
grep -qF '# tabby-remote-desktop' "$rc" && continue
|
|
91
|
+
# Only one line: turning desk off deletes exactly it, so on/off cycles leave the file as it was.
|
|
92
|
+
[ -z "$(tail -c1 "$rc")" ] || echo >> "$rc"
|
|
93
|
+
printf '[ -f "$HOME/.local/share/tabby-remote-desktop/login.sh" ] && . "$HOME/.local/share/tabby-remote-desktop/login.sh" # tabby-remote-desktop\n' >> "$rc"
|
|
94
|
+
done
|
|
95
|
+
else
|
|
96
|
+
for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do
|
|
97
|
+
if [ -f "$rc" ] && grep -qF '# tabby-remote-desktop' "$rc"; then
|
|
98
|
+
sed -i.trd-bak '/# tabby-remote-desktop$/d' "$rc" && rm -f "$rc.trd-bak"
|
|
99
|
+
fi
|
|
100
|
+
done
|
|
101
|
+
# Sessions already running keep going; new logins are plain again.
|
|
102
|
+
rm -f "$D/login.sh" "$D/tmux.conf" "$D/backend" "$D/bin/desk" "$D/bin/trd-pty"
|
|
103
|
+
fi
|
|
104
|
+
if [ ! -s "$D/rdp-password" ]; then
|
|
105
|
+
( umask 077; od -An -N18 -tx1 /dev/urandom | tr -d ' \n' > "$D/rdp-password" ) || fail "could not create credentials"
|
|
106
|
+
fi
|
|
107
|
+
RD_USER=tabby
|
|
108
|
+
RD_PASS=$(cat "$D/rdp-password")
|
|
109
|
+
|
|
110
|
+
STATUS=$(grdctl --headless status --show-credentials 2>/dev/null)
|
|
111
|
+
has () { printf '%s\n' "$STATUS" | grep -qxF "$(printf '\t%s' "$1")"; }
|
|
112
|
+
g () { grdctl --headless rdp "$@" >/dev/null 2>&1; }
|
|
113
|
+
RESTART=0
|
|
114
|
+
if ! has "TLS key: $D/tls.key" || ! has "TLS certificate: $D/tls.crt"; then
|
|
115
|
+
g set-tls-key "$D/tls.key" && g set-tls-cert "$D/tls.crt" || fail "grdctl could not set the TLS certificate"
|
|
116
|
+
RESTART=1
|
|
117
|
+
fi
|
|
118
|
+
if ! has "View-only: no"; then g disable-view-only || fail "grdctl could not disable view-only"; RESTART=1; fi
|
|
119
|
+
if ! has "Status: enabled"; then g enable || fail "grdctl could not enable RDP"; RESTART=1; fi
|
|
120
|
+
if ! has "Username: $RD_USER" || ! has "Password: $RD_PASS"; then
|
|
121
|
+
g set-credentials "$RD_USER" "$RD_PASS" || fail "grdctl could not set credentials"
|
|
122
|
+
RESTART=1
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# Session identity for the headless shell: apps like Settings refuse to run without XDG_CURRENT_DESKTOP,
|
|
126
|
+
# and xdg-open picks the desktop's handlers from it. Use Ubuntu's session mode when present (dock etc.).
|
|
127
|
+
if [ -f /usr/share/gnome-shell/modes/ubuntu.json ]; then SESS=ubuntu; DESK=ubuntu:GNOME; else SESS=gnome; DESK=GNOME; fi
|
|
128
|
+
SESSION_VERSION=2
|
|
129
|
+
SHELL_PID=$(pgrep -u "$(id -u)" -f 'gnome-shell --headless' | head -1)
|
|
130
|
+
if [ -n "$SHELL_PID" ] && ! tr '\0' '\n' < /proc/$SHELL_PID/environ 2>/dev/null | grep -qx "TABBY_RD_SESSION=$SESSION_VERSION"; then
|
|
131
|
+
# A shell from an older setup (no session identity): replace it (closes its windows).
|
|
132
|
+
kill "$SHELL_PID" 2>/dev/null
|
|
133
|
+
i=0; while [ $i -lt 40 ] && kill -0 "$SHELL_PID" 2>/dev/null; do sleep 0.25; i=$((i+1)); done
|
|
134
|
+
SHELL_PID=
|
|
135
|
+
fi
|
|
136
|
+
if [ -z "$SHELL_PID" ]; then
|
|
137
|
+
# Apps from a previous headless session (marked below) outlive their display; single-instance apps
|
|
138
|
+
# would then swallow new launches without showing a window. End them before starting afresh.
|
|
139
|
+
for p in $(pgrep -u "$(id -u)"); do
|
|
140
|
+
grep -qz '^TABBY_RD_SESSION=' /proc/$p/environ 2>/dev/null && kill "$p" 2>/dev/null
|
|
141
|
+
done
|
|
142
|
+
systemctl --user stop tabby-headless-shell >/dev/null 2>&1
|
|
143
|
+
systemctl --user reset-failed tabby-headless-shell >/dev/null 2>&1
|
|
144
|
+
systemd-run --user --unit=tabby-headless-shell --setenv=XDG_SESSION_TYPE=wayland \
|
|
145
|
+
--setenv=XDG_CURRENT_DESKTOP=$DESK --setenv=XDG_SESSION_DESKTOP=$SESS --setenv=DESKTOP_SESSION=$SESS \
|
|
146
|
+
--setenv=GNOME_SHELL_SESSION_MODE=$SESS --setenv=TABBY_RD_SESSION=$SESSION_VERSION \
|
|
147
|
+
gnome-shell --headless >/dev/null 2>&1 \
|
|
148
|
+
|| fail "could not start a headless GNOME Shell"
|
|
149
|
+
i=0; while [ $i -lt 40 ] && ! busctl --user status org.gnome.Mutter.RemoteDesktop >/dev/null 2>&1; do sleep 0.25; i=$((i+1)); done
|
|
150
|
+
SHELL_PID=$(pgrep -u "$(id -u)" -f 'gnome-shell --headless' | head -1)
|
|
151
|
+
RESTART=1
|
|
152
|
+
fi
|
|
153
|
+
# What gnome-session does for a real login: let D-Bus/systemd-activated apps find this display.
|
|
154
|
+
WL=$(tr '\0' '\n' < /proc/$SHELL_PID/environ 2>/dev/null | sed -n 's/^WAYLAND_DISPLAY=//p')
|
|
155
|
+
[ -n "$WL" ] || WL=$(ls -t "$XDG_RUNTIME_DIR" 2>/dev/null | grep -m1 -x 'wayland-[0-9]*')
|
|
156
|
+
dbus-update-activation-environment --systemd WAYLAND_DISPLAY=${"$"}{WL:-wayland-0} XDG_SESSION_TYPE=wayland \
|
|
157
|
+
XDG_CURRENT_DESKTOP=$DESK XDG_SESSION_DESKTOP=$SESS DESKTOP_SESSION=$SESS >/dev/null 2>&1
|
|
158
|
+
# Mark D-Bus-activated apps as part of this session too (not in systemd's environment: user services would inherit it).
|
|
159
|
+
dbus-update-activation-environment TABBY_RD_SESSION=$SESSION_VERSION >/dev/null 2>&1
|
|
160
|
+
if [ $RESTART = 1 ] || ! systemctl --user is-active -q gnome-remote-desktop-headless.service; then
|
|
161
|
+
systemctl --user restart gnome-remote-desktop-headless.service >/dev/null 2>&1 || fail "could not start gnome-remote-desktop-headless"
|
|
162
|
+
fi
|
|
163
|
+
PORT=$(grdctl --headless status 2>/dev/null | awk '/Port:/ { print $2; exit }')
|
|
164
|
+
PORT=${"$"}{PORT:-3389}
|
|
165
|
+
i=0; while [ $i -lt 40 ] && ! ss -ltnH "sport = :$PORT" 2>/dev/null | grep -q .; do sleep 0.25; i=$((i+1)); done
|
|
166
|
+
ss -ltnH "sport = :$PORT" 2>/dev/null | grep -q . || fail "nothing is listening on port $PORT"
|
|
167
|
+
say "RD_OK port=$PORT user=$RD_USER pass=$RD_PASS"
|
|
168
|
+
`;
|
|
169
|
+
let trdPtySource = null;
|
|
170
|
+
function trdPty() {
|
|
171
|
+
trdPtySource ??= fs.readFileSync(path.join(__dirname, '..', 'remote', 'trd-pty.py'), 'utf8');
|
|
172
|
+
return trdPtySource;
|
|
173
|
+
}
|
|
174
|
+
async function prepareRemoteDesktop(target, desk, backend = 'native') {
|
|
175
|
+
const script = `TRD_DESK=${desk ? 1 : 0}\nTRD_BACKEND=${backend === 'tmux' ? 'tmux' : 'native'}\n` +
|
|
176
|
+
SETUP_SCRIPT.replace('@@TRD_PTY_PY@@', trdPty().trimEnd());
|
|
177
|
+
const out = await target.exec('sh -s', script);
|
|
178
|
+
const err = /^RD_ERR (.*)$/m.exec(out);
|
|
179
|
+
if (err) {
|
|
180
|
+
throw new Error(err[1]);
|
|
181
|
+
}
|
|
182
|
+
const ok = /^RD_OK port=(\d+) user=(\S+) pass=(\S+)$/m.exec(out);
|
|
183
|
+
if (!ok) {
|
|
184
|
+
throw new Error('Remote setup gave no result');
|
|
185
|
+
}
|
|
186
|
+
return { port: Number(ok[1]), username: ok[2], password: ok[3] };
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=remoteSetup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remoteSetup.js","sourceRoot":"","sources":["../src/remoteSetup.ts"],"names":[],"mappings":";;AAwLA,oDAaC;AArMD,yBAAwB;AACxB,6BAA4B;AAS5B,kHAAkH;AAClH,yGAAyG;AACzG,2GAA2G;AAC3G,yGAAyG;AACzG,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;QAiBvB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4DM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+DAqE2C,GAAG;;;;;;;;OAQ3D,GAAG;;;;CAIT,CAAA;AAMD,IAAI,YAAY,GAAkB,IAAI,CAAA;AACtC,SAAS,MAAM;IACX,YAAY,KAAK,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAA;IAC5F,OAAO,YAAY,CAAA;AACvB,CAAC;AAEM,KAAK,UAAU,oBAAoB,CAAE,MAAoB,EAAE,IAAa,EAAE,UAA0B,QAAQ;IAC/G,MAAM,MAAM,GAAG,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,IAAI;QAC9F,YAAY,CAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;IAC9D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtC,IAAI,GAAG,EAAE,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3B,CAAC;IACD,MAAM,EAAE,GAAG,2CAA2C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAChE,IAAI,CAAC,EAAE,EAAE,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;IAClD,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;AACpE,CAAC"}
|
package/dist/signin.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Sign-in for desktops that use their own accounts (Windows): a form in the desktop layer, and the keychain. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.STYLE = void 0;
|
|
5
|
+
exports.loadCredentials = loadCredentials;
|
|
6
|
+
exports.saveCredentials = saveCredentials;
|
|
7
|
+
exports.forgetCredentials = forgetCredentials;
|
|
8
|
+
exports.forgetCredentialsFor = forgetCredentialsFor;
|
|
9
|
+
exports.askCredentials = askCredentials;
|
|
10
|
+
const KEYCHAIN_SERVICE = 'tabby-rdp';
|
|
11
|
+
// Tabby ships keytar (tabby-ssh keeps SSH passwords with it). Without it, nothing is remembered.
|
|
12
|
+
function keytar() {
|
|
13
|
+
try {
|
|
14
|
+
return require('keytar');
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Saved credentials for a desktop (keyed by its session key), or null. */
|
|
21
|
+
async function loadCredentials(key) {
|
|
22
|
+
try {
|
|
23
|
+
const saved = await keytar()?.getPassword(KEYCHAIN_SERVICE, key);
|
|
24
|
+
const parsed = saved ? JSON.parse(saved) : null;
|
|
25
|
+
return parsed?.username && typeof parsed.password === 'string' ? { username: parsed.username, password: parsed.password } : null;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function saveCredentials(key, credentials) {
|
|
32
|
+
await keytar()?.setPassword(KEYCHAIN_SERVICE, key, JSON.stringify(credentials));
|
|
33
|
+
}
|
|
34
|
+
async function forgetCredentials(key) {
|
|
35
|
+
try {
|
|
36
|
+
await keytar()?.deletePassword(KEYCHAIN_SERVICE, key);
|
|
37
|
+
}
|
|
38
|
+
catch { }
|
|
39
|
+
}
|
|
40
|
+
/** Forgets saved accounts for a desktop on any SSH host (session keys ending in `#<id>`). */
|
|
41
|
+
async function forgetCredentialsFor(desktopId) {
|
|
42
|
+
try {
|
|
43
|
+
const k = keytar();
|
|
44
|
+
for (const { account } of await k?.findCredentials(KEYCHAIN_SERVICE) ?? []) {
|
|
45
|
+
if (account.endsWith(`#${desktopId}`)) {
|
|
46
|
+
await k.deletePassword(KEYCHAIN_SERVICE, account);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
}
|
|
52
|
+
exports.STYLE = `
|
|
53
|
+
.trd-signin { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; z-index: 1; }
|
|
54
|
+
.trd-signin form { display: flex; flex-direction: column; gap: 10px; width: 300px; max-width: calc(100% - 32px); color: #ccc; font-size: 13px; }
|
|
55
|
+
.trd-signin .trd-signin-title { font-size: 15px; color: #eee; }
|
|
56
|
+
.trd-signin .trd-signin-error { color: #f08080; white-space: pre-line; }
|
|
57
|
+
.trd-signin .trd-signin-error:empty { display: none; }
|
|
58
|
+
.trd-signin label.trd-signin-remember { display: flex; gap: 6px; align-items: center; }
|
|
59
|
+
.trd-signin .trd-signin-buttons { display: flex; gap: 8px; justify-content: flex-end; }
|
|
60
|
+
`;
|
|
61
|
+
/**
|
|
62
|
+
* Asks for the account in the desktop layer. Resolves with what was entered (and whether to remember it),
|
|
63
|
+
* or null when cancelled or when `abort` fires (the desktop was disconnected meanwhile).
|
|
64
|
+
*/
|
|
65
|
+
function askCredentials(layer, options, abort) {
|
|
66
|
+
const box = document.createElement('div');
|
|
67
|
+
box.className = 'trd-signin';
|
|
68
|
+
box.innerHTML = `
|
|
69
|
+
<form autocomplete="off">
|
|
70
|
+
<div class="trd-signin-title"></div>
|
|
71
|
+
<div class="trd-signin-error"></div>
|
|
72
|
+
<input class="form-control" name="username" placeholder="User name (or DOMAIN\\user)" spellcheck="false">
|
|
73
|
+
<input class="form-control" name="password" type="password" placeholder="Password">
|
|
74
|
+
<label class="trd-signin-remember"><input type="checkbox" name="remember" checked> Remember in the keychain</label>
|
|
75
|
+
<div class="trd-signin-buttons">
|
|
76
|
+
<button type="button" class="btn btn-secondary" name="cancel">Cancel</button>
|
|
77
|
+
<button type="submit" class="btn btn-primary">Connect</button>
|
|
78
|
+
</div>
|
|
79
|
+
</form>`;
|
|
80
|
+
const form = box.querySelector('form');
|
|
81
|
+
const field = (name) => form.querySelector(`[name="${name}"]`);
|
|
82
|
+
box.querySelector('.trd-signin-title').textContent = options.title;
|
|
83
|
+
box.querySelector('.trd-signin-error').textContent = options.error ?? '';
|
|
84
|
+
field('username').value = options.username ?? '';
|
|
85
|
+
if (!options.canRemember) {
|
|
86
|
+
field('remember').checked = false;
|
|
87
|
+
form.querySelector('.trd-signin-remember').style.display = 'none';
|
|
88
|
+
}
|
|
89
|
+
layer.appendChild(box);
|
|
90
|
+
// Focus goes to the form from DesktopSession.focusDesktop(), which also takes it back from Tabby.
|
|
91
|
+
return new Promise(resolve => {
|
|
92
|
+
const done = (result) => {
|
|
93
|
+
box.remove();
|
|
94
|
+
resolve(result);
|
|
95
|
+
};
|
|
96
|
+
form.addEventListener('submit', event => {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
const username = field('username').value.trim();
|
|
99
|
+
if (!username) {
|
|
100
|
+
field('username').focus();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
done({ username, password: field('password').value, remember: field('remember').checked });
|
|
104
|
+
});
|
|
105
|
+
field('cancel').addEventListener('click', () => done(null));
|
|
106
|
+
form.addEventListener('keydown', event => {
|
|
107
|
+
if (event.key === 'Escape') {
|
|
108
|
+
done(null);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
abort.then(() => done(null));
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=signin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signin.js","sourceRoot":"","sources":["../src/signin.ts"],"names":[],"mappings":"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"}
|
package/dist/ssh.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isSSHTab = isSSHTab;
|
|
4
|
+
exports.openTcpStream = openTcpStream;
|
|
5
|
+
exports.execRemote = execRemote;
|
|
6
|
+
const sshChannelStream_1 = require("./sshChannelStream");
|
|
7
|
+
function isSSHTab(tab) {
|
|
8
|
+
// The profile is set when the tab is created; sshSession only once it connects.
|
|
9
|
+
return tab?.profile?.type === 'ssh' && !!tab.element?.nativeElement;
|
|
10
|
+
}
|
|
11
|
+
function client(tab) {
|
|
12
|
+
const ssh = tab.sshSession?.ssh;
|
|
13
|
+
if (!tab.sshSession?.open || typeof ssh?.openTCPForwardChannel !== 'function') {
|
|
14
|
+
throw new Error('The SSH connection is not open');
|
|
15
|
+
}
|
|
16
|
+
return ssh;
|
|
17
|
+
}
|
|
18
|
+
/** A direct-tcpip channel to host:port as the remote sees it (e.g. its own 127.0.0.1), as a Node stream. */
|
|
19
|
+
async function openTcpStream(tab, host, port) {
|
|
20
|
+
const ssh = client(tab);
|
|
21
|
+
const channel = await ssh.activateChannel(await ssh.openTCPForwardChannel({
|
|
22
|
+
addressToConnectTo: host,
|
|
23
|
+
portToConnectTo: port,
|
|
24
|
+
originatorAddress: '127.0.0.1',
|
|
25
|
+
originatorPort: 0,
|
|
26
|
+
}));
|
|
27
|
+
return new sshChannelStream_1.SSHChannelStream(channel);
|
|
28
|
+
}
|
|
29
|
+
/** Runs `command` on the remote with `stdin`, returns stdout. No PTY, no exit status (russh doesn't expose it). */
|
|
30
|
+
async function execRemote(tab, command, stdin = '', timeoutMs = 60000) {
|
|
31
|
+
const ssh = client(tab);
|
|
32
|
+
const channel = await ssh.activateChannel(await ssh.openSessionChannel());
|
|
33
|
+
const out = [];
|
|
34
|
+
const closed = new Promise(resolve => channel.closed$.subscribe(() => resolve()));
|
|
35
|
+
channel.data$.subscribe((d) => out.push(Buffer.from(d)));
|
|
36
|
+
await channel.requestExec(command);
|
|
37
|
+
if (stdin) {
|
|
38
|
+
await channel.write(new Uint8Array(Buffer.from(stdin)));
|
|
39
|
+
}
|
|
40
|
+
await channel.eof();
|
|
41
|
+
let timer;
|
|
42
|
+
const timeout = new Promise((_, reject) => {
|
|
43
|
+
timer = setTimeout(() => reject(new Error(`Remote command timed out after ${timeoutMs / 1000}s`)), timeoutMs);
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
await Promise.race([closed, timeout]);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
channel.close().catch(() => null);
|
|
51
|
+
}
|
|
52
|
+
return Buffer.concat(out).toString('utf8');
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=ssh.js.map
|
package/dist/ssh.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssh.js","sourceRoot":"","sources":["../src/ssh.ts"],"names":[],"mappings":"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"}
|