mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-10-09 15:58:34 +00:00
1b0cb918d8
- Extract createProxySocket to shared proxyUtils.cjs module - Add 5-minute TTL cleanup for abandoned keyboard-interactive requests - Update sshBridge.cjs and sftpBridge.cjs to use shared module
136 lines
6.0 KiB
JavaScript
136 lines
6.0 KiB
JavaScript
/**
|
|
* Proxy Utilities - Shared proxy socket creation for SSH connections
|
|
* Extracted from sshBridge.cjs and sftpBridge.cjs to eliminate code duplication
|
|
*/
|
|
|
|
const net = require("node:net");
|
|
|
|
/**
|
|
* Create a socket through a proxy (HTTP CONNECT or SOCKS5)
|
|
* @param {Object} proxy - Proxy configuration
|
|
* @param {string} proxy.type - 'http' or 'socks5'
|
|
* @param {string} proxy.host - Proxy host
|
|
* @param {number} proxy.port - Proxy port
|
|
* @param {string} [proxy.username] - Optional username for auth
|
|
* @param {string} [proxy.password] - Optional password for auth
|
|
* @param {string} targetHost - Target host to connect through proxy
|
|
* @param {number} targetPort - Target port to connect through proxy
|
|
* @returns {Promise<net.Socket>} Connected socket through proxy
|
|
*/
|
|
function createProxySocket(proxy, targetHost, targetPort) {
|
|
return new Promise((resolve, reject) => {
|
|
if (proxy.type === 'http') {
|
|
// HTTP CONNECT proxy
|
|
const socket = net.connect(proxy.port, proxy.host, () => {
|
|
let authHeader = '';
|
|
if (proxy.username && proxy.password) {
|
|
const auth = Buffer.from(`${proxy.username}:${proxy.password}`).toString('base64');
|
|
authHeader = `Proxy-Authorization: Basic ${auth}\r\n`;
|
|
}
|
|
const connectRequest = `CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\nHost: ${targetHost}:${targetPort}\r\n${authHeader}\r\n`;
|
|
socket.write(connectRequest);
|
|
|
|
let response = '';
|
|
const onData = (data) => {
|
|
response += data.toString();
|
|
if (response.includes('\r\n\r\n')) {
|
|
socket.removeListener('data', onData);
|
|
if (response.startsWith('HTTP/1.1 200') || response.startsWith('HTTP/1.0 200')) {
|
|
resolve(socket);
|
|
} else {
|
|
socket.destroy();
|
|
reject(new Error(`HTTP proxy error: ${response.split('\r\n')[0]}`));
|
|
}
|
|
}
|
|
};
|
|
socket.on('data', onData);
|
|
});
|
|
socket.on('error', reject);
|
|
} else if (proxy.type === 'socks5') {
|
|
// SOCKS5 proxy
|
|
const socket = net.connect(proxy.port, proxy.host, () => {
|
|
// SOCKS5 greeting
|
|
const authMethods = proxy.username && proxy.password ? [0x00, 0x02] : [0x00];
|
|
socket.write(Buffer.from([0x05, authMethods.length, ...authMethods]));
|
|
|
|
let step = 'greeting';
|
|
const onData = (data) => {
|
|
if (step === 'greeting') {
|
|
if (data[0] !== 0x05) {
|
|
socket.destroy();
|
|
reject(new Error('Invalid SOCKS5 response'));
|
|
return;
|
|
}
|
|
const method = data[1];
|
|
if (method === 0x02 && proxy.username && proxy.password) {
|
|
// Username/password auth
|
|
step = 'auth';
|
|
const userBuf = Buffer.from(proxy.username);
|
|
const passBuf = Buffer.from(proxy.password);
|
|
socket.write(Buffer.concat([
|
|
Buffer.from([0x01, userBuf.length]),
|
|
userBuf,
|
|
Buffer.from([passBuf.length]),
|
|
passBuf
|
|
]));
|
|
} else if (method === 0x00) {
|
|
// No auth, proceed to connect
|
|
step = 'connect';
|
|
sendConnectRequest();
|
|
} else {
|
|
socket.destroy();
|
|
reject(new Error('SOCKS5 authentication method not supported'));
|
|
}
|
|
} else if (step === 'auth') {
|
|
if (data[1] !== 0x00) {
|
|
socket.destroy();
|
|
reject(new Error('SOCKS5 authentication failed'));
|
|
return;
|
|
}
|
|
step = 'connect';
|
|
sendConnectRequest();
|
|
} else if (step === 'connect') {
|
|
socket.removeListener('data', onData);
|
|
if (data[1] === 0x00) {
|
|
resolve(socket);
|
|
} else {
|
|
const errors = {
|
|
0x01: 'General failure',
|
|
0x02: 'Connection not allowed',
|
|
0x03: 'Network unreachable',
|
|
0x04: 'Host unreachable',
|
|
0x05: 'Connection refused',
|
|
0x06: 'TTL expired',
|
|
0x07: 'Command not supported',
|
|
0x08: 'Address type not supported',
|
|
};
|
|
socket.destroy();
|
|
reject(new Error(`SOCKS5 error: ${errors[data[1]] || 'Unknown'}`));
|
|
}
|
|
}
|
|
};
|
|
|
|
const sendConnectRequest = () => {
|
|
// SOCKS5 connect request
|
|
const hostBuf = Buffer.from(targetHost);
|
|
const request = Buffer.concat([
|
|
Buffer.from([0x05, 0x01, 0x00, 0x03, hostBuf.length]),
|
|
hostBuf,
|
|
Buffer.from([(targetPort >> 8) & 0xff, targetPort & 0xff])
|
|
]);
|
|
socket.write(request);
|
|
};
|
|
|
|
socket.on('data', onData);
|
|
});
|
|
socket.on('error', reject);
|
|
} else {
|
|
reject(new Error(`Unknown proxy type: ${proxy.type}`));
|
|
}
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
createProxySocket,
|
|
};
|