CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/backend/tcp/locked-socket.ts
Views: 687
1
import { createConnection } from "net";
2
import type { Socket } from "net";
3
import { callback } from "awaiting";
4
import getLogger from "@cocalc/backend/logger";
5
import { CoCalcSocket } from "./enable-messaging-protocol";
6
7
const log = getLogger("locked-socket");
8
9
/*
10
unlockSocket - Wait to receive token over the socket; when it is received, call
11
cb(false), then send back "y". If any mistake is made (or the socket times out
12
after 10 seconds), send back "n" and close the connection.
13
*/
14
15
export async function unlockSocket(
16
socket: Socket,
17
token: string
18
): Promise<void> {
19
log.debug("unlockSocket: waiting for secret token...");
20
try {
21
await callback(unlock, socket, token);
22
log.debug("unlockSocket: SUCCESS");
23
} catch (err) {
24
log.debug("unlockSocket: FAILED");
25
throw err;
26
}
27
}
28
29
function unlock(socket, token, cb: (err?) => void) {
30
const timeout = setTimeout(() => {
31
socket.destroy();
32
cb("Unlock socket -- timed out waiting for secret token");
33
}, 10000);
34
35
let userToken = "";
36
function listener(data: Buffer) {
37
userToken += data.toString();
38
if (userToken.slice(0, token.length) === token) {
39
socket.removeListener("data", listener);
40
// got it!
41
socket.write("y");
42
clearTimeout(timeout);
43
cb();
44
} else if (
45
userToken.length > token.length ||
46
token.slice(0, userToken.length) !== userToken
47
) {
48
socket.removeListener("data", listener);
49
socket.write("n");
50
socket.write("Invalid secret token.");
51
socket.destroy();
52
clearTimeout(timeout);
53
cb("Invalid secret token.");
54
}
55
}
56
socket.on("data", listener);
57
}
58
59
/*
60
Connect to a locked socket on remove server.
61
WARNING: Use only on a network where you do not have to worry about
62
an attacker listening to all traffic, since this is not an *encryption*
63
protocol, and it's just a symmetric key.
64
65
In CoCalc this is used to allow a hub to connect to a project.
66
It is not used in any other way.
67
*/
68
export async function connectToLockedSocket({
69
port,
70
host = "127.0.0.1",
71
token,
72
timeout = 5, // in seconds (not milliseconds)
73
}: {
74
port: number;
75
host?: string;
76
token: string;
77
timeout?: number;
78
}): Promise<CoCalcSocket> {
79
if (port <= 0 || port >= 65536) {
80
// little consistency check
81
throw Error(`RangeError: port should be > 0 and < 65536: ${port}`);
82
}
83
log.debug("connectToLockedSocket:", `${host}:${port}`);
84
return await callback(connect, port, host, token, timeout);
85
}
86
87
function connect(port, host, token, timeout, cb) {
88
let timer: any = null;
89
function finish(err?) {
90
// NOTE: we set cb to undefined after calling it, and only
91
// call it if defined, since the event and timer callback stuff is
92
// very hard to do right without calling cb more than once
93
// (which is VERY bad to do).
94
if (timer != null) {
95
clearTimeout(timer);
96
timer = null;
97
}
98
if (cb == null) return;
99
if (err) {
100
log.debug(`connectToLockedSocket: ERROR - ${err}`);
101
cb(err);
102
} else {
103
log.debug("connectToLockedSocket: SUCCESS");
104
cb(undefined, socket);
105
}
106
cb = null;
107
}
108
109
const socket = createConnection({ host, port }, function onceConnected() {
110
socket.once("data", (data: Buffer) => {
111
log.debug("connectToLockedSocket: got back response");
112
if (data.toString() === "y") {
113
finish();
114
} else {
115
socket.destroy();
116
finish(
117
"Permission denied (invalid secret token) when connecting to the local hub."
118
);
119
}
120
});
121
log.debug("connectToLockedSocket: connected, now sending secret token");
122
socket.write(token);
123
});
124
125
// This is called in case there is an error trying to make the connection, e.g., "connection refused".
126
socket.on("error", (err) => {
127
finish(err);
128
});
129
130
function timedOut() {
131
timer = null;
132
finish(
133
`connectToLockedSocket: timed out trying to connect to locked socket at ${host}:${port}`
134
);
135
socket.end();
136
}
137
138
timer = setTimeout(timedOut, timeout * 1000);
139
}
140
141