@applitools/eg-socks5-proxy-server
Advanced tools
Comparing version 0.1.7 to 0.2.0-beta
{ | ||
"name": "@applitools/eg-socks5-proxy-server", | ||
"version": "0.1.7", | ||
"version": "0.2.0-beta", | ||
"description": "", | ||
@@ -17,3 +17,3 @@ "main": "src/index.js", | ||
"test": "npm run eslint && npm run test:mocha-parallel", | ||
"test:mocha": "mocha --no-timeouts --trace-warnings --exit 'test/unit/*.test.js' 'test/it/*.test.js' 'test/e2e/*.test.js'", | ||
"test:mocha": "mocha --no-timeouts --trace-warnings --exit 'test/it/*.test.js'", | ||
"eslint": "eslint '**/*.js'", | ||
@@ -44,5 +44,7 @@ "build": "#" | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"mocha": "^9.1.3", | ||
"prettier": "^2.5.0" | ||
"mocha": "^10.0.0", | ||
"node-fetch": "^2.6.7", | ||
"prettier": "^2.5.0", | ||
"socks-proxy-agent": "^7.0.0" | ||
} | ||
} |
440
src/index.js
@@ -11,3 +11,7 @@ const { | ||
const {createRequestExecuter} = require('./create-request-executer') | ||
const EVENTS = require('./events') | ||
const {handshake} = require('./handshake') | ||
const {authenticate} = require('./authenticate') | ||
const {executeRequest} = require('./execute-request') | ||
const {executeRequestThroughRemoteProxy} = require('./execute-request-through-remote-proxy') | ||
@@ -18,27 +22,6 @@ const binary = require('binary') | ||
// module specific events | ||
const EVENTS = { | ||
AUTHENTICATION: 'authenticate', | ||
AUTHENTICATION_ERROR: 'authenticateError', | ||
CONNECTION_FILTER: 'connectionFilter', | ||
HANDSHAKE: 'handshake', | ||
PROXY_CONNECT: 'proxyConnect', | ||
PROXY_DATA: 'proxyData', | ||
PROXY_DISCONNECT: 'proxyDisconnect', | ||
PROXY_END: 'proxyEnd', | ||
PROXY_ERROR: 'proxyError', | ||
const _alwaysTrue = () => true | ||
ACCEPT_NEW_REQUEST: 'accept-new-request', | ||
CONNECTED_TO_REMOTE_ADDRESS: 'connected-to-remote-address', | ||
CONNECTED_TO_REMOTE_PROXY: 'connected-to-remote-proxy', | ||
REMOTE_PROXY_HANDSHAKE_COMPLETED: 'remote-proxy-handshake-completed', | ||
REMOTE_PROXY_HANDSHAKE_TIMEOUT: 'remote-proxy-handshake-timeout', | ||
const LENGTH_RFC_1928_ATYP = 4 | ||
ORIGIN_SOCKET_ERROR: 'orgin-socket-error', | ||
REMOTE_SOCKET_ERROR: 'remote-socket-error', | ||
REMOTE_CONNECTION_TIMEOUT_ERROR: 'remote-connection-timeout-error', | ||
}, | ||
LENGTH_RFC_1928_ATYP = 4 | ||
/** | ||
@@ -70,64 +53,14 @@ * The following RFCs may be useful as background: | ||
/** | ||
* +----+------+----------+------+----------+ | ||
* |VER | ULEN | UNAME | PLEN | PASSWD | | ||
* +----+------+----------+------+----------+ | ||
* | 1 | 1 | 1 to 255 | 1 | 1 to 255 | | ||
* +----+------+----------+------+----------+ | ||
* | ||
* | ||
* @param {Buffer} buffer - a buffer | ||
* @returns {undefined} | ||
**/ | ||
function authenticate(buffer) { | ||
let authDomain = domain.create() | ||
binary | ||
.stream(buffer) | ||
.word8('ver') | ||
.word8('ulen') | ||
.buffer('uname', 'ulen') | ||
.word8('plen') | ||
.buffer('passwd', 'plen') | ||
.tap((args) => { | ||
// capture the raw buffer | ||
args.requestBuffer = buffer | ||
// verify version is appropriate | ||
if (args.ver !== RFC_1929_VERSION) { | ||
return end(RFC_1929_REPLIES.GENERAL_FAILURE, args) | ||
} | ||
authDomain.on('error', (err) => { | ||
// emit failed authentication event | ||
self.server.emit(EVENTS.AUTHENTICATION_ERROR, args.uname.toString(), err) | ||
// respond with auth failure | ||
return end(RFC_1929_REPLIES.GENERAL_FAILURE, args) | ||
}) | ||
// perform authentication | ||
self.options.authenticate( | ||
args.uname.toString(), | ||
args.passwd.toString(), | ||
socket, | ||
authDomain.intercept(() => { | ||
// emit successful authentication event | ||
self.server.emit(EVENTS.AUTHENTICATION, args.uname.toString()) | ||
// respond with success... | ||
let responseBuffer = Buffer.allocUnsafe(2) | ||
responseBuffer[0] = RFC_1929_VERSION | ||
responseBuffer[1] = RFC_1929_REPLIES.SUCCEEDED | ||
// respond then listen for cmd and dst info | ||
socket.write(responseBuffer, () => { | ||
// now listen for more details | ||
socket.once('data', connect) | ||
}) | ||
}), | ||
) | ||
}) | ||
function _authenticate(buffer) { | ||
authenticate({ | ||
socket, | ||
buffer, | ||
options: self.options, | ||
connect, | ||
end, | ||
onAuthenticated: (args) => self.server.emit(EVENTS.AUTHENTICATION, args.uname.toString()), | ||
onFailed: (args, err) => | ||
self.server.emit(EVENTS.AUTHENTICATION_ERROR, args.uname.toString(), err), | ||
}) | ||
} | ||
/** | ||
@@ -212,35 +145,20 @@ * +----+-----+-------+------+----------+----------+ | ||
.tap((args) => { | ||
if (args.cmd === RFC_1928_COMMANDS.CONNECT) { | ||
let connectionFilter = self.options.connectionFilter, | ||
connectionFilterDomain = domain.create() | ||
if (args.cmd !== RFC_1928_COMMANDS.CONNECT) { | ||
// bind and udp associate commands | ||
return end(RFC_1928_REPLIES.SUCCEEDED, args) | ||
} | ||
// if no connection filter is provided, stub one | ||
if (!connectionFilter || typeof connectionFilter !== 'function') { | ||
connectionFilter = (destination, origin, callback) => setImmediate(callback) | ||
} | ||
let connectionFilter = self.options.connectionFilter, | ||
connectionFilterDomain = domain.create() | ||
// capture connection filter errors | ||
connectionFilterDomain.on('error', (err) => { | ||
// emit failed destination connection event | ||
self.server.emit( | ||
EVENTS.CONNECTION_FILTER, | ||
// destination | ||
{ | ||
address: args.dst.addr, | ||
port: args.dst.port, | ||
}, | ||
// origin | ||
{ | ||
address: socket.remoteAddress, | ||
port: socket.remotePort, | ||
}, | ||
err, | ||
) | ||
// if no connection filter is provided, stub one | ||
if (!connectionFilter || typeof connectionFilter !== 'function') { | ||
connectionFilter = (destination, origin, callback) => setImmediate(callback) | ||
} | ||
// respond with failure | ||
return end(RFC_1929_REPLIES.CONNECTION_NOT_ALLOWED, args) | ||
}) | ||
// perform connection | ||
return connectionFilter( | ||
// capture connection filter errors | ||
connectionFilterDomain.on('error', (err) => { | ||
// emit failed destination connection event | ||
self.server.emit( | ||
EVENTS.CONNECTION_FILTER, | ||
// destination | ||
@@ -256,25 +174,128 @@ { | ||
}, | ||
connectionFilterDomain.intercept(() => { | ||
const destinationInfo = { | ||
address: args.dst.addr, | ||
port: args.dst.port, | ||
err, | ||
) | ||
// respond with failure | ||
return end(RFC_1929_REPLIES.CONNECTION_NOT_ALLOWED, args) | ||
}) | ||
// perform connection | ||
return connectionFilter( | ||
// destination | ||
{ | ||
address: args.dst.addr, | ||
port: args.dst.port, | ||
}, | ||
// origin | ||
{ | ||
address: socket.remoteAddress, | ||
port: socket.remotePort, | ||
}, | ||
connectionFilterDomain.intercept(() => { | ||
const destinationInfo = { | ||
address: args.dst.addr, | ||
port: args.dst.port, | ||
} | ||
const originInfo = { | ||
address: socket.remoteAddress, | ||
port: socket.remotePort, | ||
} | ||
let createExecuterAndRegisterToEvents | ||
const proxyServer = self.options.proxyServer | ||
? { | ||
...self.options.proxyServer, | ||
shouldUseProxy: self.options.proxyServer.shouldUseProxy || _alwaysTrue | ||
} | ||
: undefined | ||
const originInfo = { | ||
address: socket.remoteAddress, | ||
port: socket.remotePort, | ||
const shouldUseProxy = proxyServer && proxyServer.shouldUseProxy({socket, destinationInfo, originInfo}) | ||
const proxyInfo = shouldUseProxy? { | ||
address: proxyServer.address, | ||
port: proxyServer.port | ||
}: undefined | ||
self.server.emit(EVENTS.ACCEPT_NEW_REQUEST, { | ||
originInfo, | ||
destinationInfo, | ||
proxyInfo, | ||
}) | ||
createExecuterAndRegisterToEvents = () => { | ||
let connectionTimeout = setTimeout( | ||
() => | ||
self.server.emit(EVENTS.REMOTE_CONNECTION_TIMEOUT_ERROR, { | ||
originInfo, | ||
destinationInfo, | ||
proxyInfo, | ||
}), | ||
120000, | ||
) | ||
const clearConnectionTimeout = () => { | ||
connectionTimeout && clearTimeout(connectionTimeout) | ||
connectionTimeout = null | ||
} | ||
const proxyServer = self.options.proxyServer ? {...self.options.proxyServer} : undefined | ||
const onConnect = (destinationSocket) => { | ||
connectionFilterDomain.exit() | ||
clearConnectionTimeout() | ||
let createExecuterAndRegisterToEvents | ||
// close destination socket when after original socket was closed | ||
socket.on('close', () => { | ||
destinationSocket.destroy() | ||
self.destinationSockets.splice(self.destinationSockets.indexOf(destinationSocket), 1) | ||
}) | ||
if (proxyServer){ | ||
const event = shouldUseProxy | ||
? EVENTS.CONNECTED_TO_REMOTE_PROXY | ||
: EVENTS.CONNECTED_TO_REMOTE_ADDRESS | ||
self.server.emit(event, {destinationInfo, originInfo, proxyInfo}) | ||
} | ||
const onError = (err) => { | ||
// exit the connection filter domain | ||
connectionFilterDomain.exit() | ||
self.server.emit(EVENTS.REMOTE_SOCKET_ERROR, { | ||
err, | ||
destination, | ||
args, | ||
originInfo, | ||
destinationInfo, | ||
proxyInfo, | ||
}) | ||
} | ||
let destination | ||
if (!shouldUseProxy) { | ||
destination = executeRequest({ | ||
originalSocket: socket, | ||
args, | ||
end, | ||
onConnect, | ||
onError, | ||
}) | ||
} else { | ||
proxyServer.onHandShakeCompleted = () => { | ||
self.server.emit(EVENTS.REMOTE_PROXY_HANDSHAKE_COMPLETED, {originInfo, destinationInfo, proxyServer}) | ||
self.server.emit(EVENTS.REMOTE_PROXY_HANDSHAKE_COMPLETED, { | ||
originInfo, | ||
destinationInfo, | ||
proxyInfo, | ||
}) | ||
} | ||
let retry = 0 | ||
proxyServer.onHandshakeTimeout = () => { | ||
self.server.emit(EVENTS.REMOTE_PROXY_HANDSHAKE_TIMEOUT, {originInfo, destinationInfo, proxyServer}) | ||
self.server.emit(EVENTS.REMOTE_PROXY_HANDSHAKE_TIMEOUT, { | ||
originInfo, | ||
destinationInfo, | ||
proxyInfo, | ||
}) | ||
retry++ | ||
@@ -284,27 +305,4 @@ if (retry <= 3) createExecuterAndRegisterToEvents() | ||
} | ||
} | ||
self.server.emit(EVENTS.ACCEPT_NEW_REQUEST, { | ||
originInfo, | ||
destinationInfo, | ||
proxyServer, | ||
}) | ||
createExecuterAndRegisterToEvents = () => { | ||
let connectionTimeout = setTimeout( | ||
() => | ||
self.server.emit(EVENTS.REMOTE_CONNECTION_TIMEOUT_ERROR, { | ||
originInfo, | ||
destinationInfo, | ||
proxyServer, | ||
}), | ||
120000, | ||
) | ||
const clearConnectionTimeout = () => { | ||
connectionTimeout && clearTimeout(connectionTimeout) | ||
connectionTimeout = null | ||
} | ||
let destination = createRequestExecuter({ | ||
destination = executeRequestThroughRemoteProxy({ | ||
originalSocket: socket, | ||
@@ -314,60 +312,14 @@ clientHandshakeBuffer, | ||
args, | ||
end, | ||
onConnect, | ||
onError, | ||
}) | ||
self.destinationSockets.push(socket) | ||
destination.on('connect', () => { | ||
connectionFilterDomain.exit() | ||
clearConnectionTimeout() | ||
// close destination socket when after original socket was closed | ||
socket.on('close', () => { | ||
destination.destroy() | ||
self.destinationSockets.splice(self.destinationSockets.indexOf(socket), 1) | ||
}) | ||
const event = proxyServer | ||
? EVENTS.CONNECTED_TO_REMOTE_PROXY | ||
: EVENTS.CONNECTED_TO_REMOTE_ADDRESS | ||
self.server.emit(event, {destinationInfo, originInfo, proxyServer}) | ||
}) | ||
destination.on('error', (err) => { | ||
// exit the connection filter domain | ||
connectionFilterDomain.exit() | ||
clearConnectionTimeout() | ||
// notify of connection error | ||
err.addr = args.dst.addr | ||
err.atyp = args.atyp | ||
err.port = args.dst.port | ||
self.server.emit(EVENTS.REMOTE_SOCKET_ERROR, { | ||
err, | ||
destination, | ||
args, | ||
originInfo, | ||
destinationInfo, | ||
proxyServer, | ||
}) | ||
if (err.code && err.code === 'EADDRNOTAVAIL') { | ||
return end(RFC_1928_REPLIES.HOST_UNREACHABLE, args) | ||
} | ||
if (err.code && err.code === 'ECONNREFUSED') { | ||
return end(RFC_1928_REPLIES.CONNECTION_REFUSED, args) | ||
} | ||
return end(RFC_1928_REPLIES.NETWORK_UNREACHABLE, args) | ||
}) | ||
} | ||
createExecuterAndRegisterToEvents() | ||
}), | ||
) | ||
} else { | ||
// bind and udp associate commands | ||
return end(RFC_1928_REPLIES.SUCCEEDED, args) | ||
} | ||
self.destinationSockets.push(destinationSocket) | ||
} | ||
createExecuterAndRegisterToEvents() | ||
}), | ||
) | ||
}) | ||
@@ -418,58 +370,22 @@ } | ||
**/ | ||
function handshake(buffer) { | ||
function _handshake(buffer) { | ||
clientHandshakeBuffer = buffer | ||
binary | ||
.stream(buffer) | ||
.word8('ver') | ||
.word8('nmethods') | ||
.buffer('methods', 'nmethods') | ||
.tap((args) => { | ||
// verify version is appropriate | ||
if (args.ver !== RFC_1928_VERSION) { | ||
return end(RFC_1928_REPLIES.GENERAL_FAILURE, args) | ||
} | ||
// convert methods buffer to an array | ||
let acceptedMethods = [].slice.call(args.methods).reduce((methods, method) => { | ||
methods[method] = true | ||
return methods | ||
}, {}), | ||
basicAuth = typeof self.options.authenticate === 'function', | ||
next = connect, | ||
noAuth = | ||
!basicAuth && typeof acceptedMethods[0] !== 'undefined' && acceptedMethods[0], | ||
responseBuffer = Buffer.allocUnsafe(2) | ||
const onHandshakeCompleted = (buffer) => { | ||
self.server.emit(EVENTS.HANDSHAKE, socket) | ||
} | ||
// form response Buffer | ||
responseBuffer[0] = RFC_1928_VERSION | ||
responseBuffer[1] = RFC_1928_METHODS.NO_AUTHENTICATION_REQUIRED | ||
// check for basic auth configuration | ||
if (basicAuth) { | ||
responseBuffer[1] = RFC_1928_METHODS.BASIC_AUTHENTICATION | ||
next = authenticate | ||
// if NO AUTHENTICATION REQUIRED and | ||
} else if (!basicAuth && noAuth) { | ||
responseBuffer[1] = RFC_1928_METHODS.NO_AUTHENTICATION_REQUIRED | ||
next = connect | ||
// basic auth callback not provided and no auth is not supported | ||
} else { | ||
return end(RFC_1928_METHODS.NO_ACCEPTABLE_METHODS, args) | ||
} | ||
// respond then listen for cmd and dst info | ||
socket.write(responseBuffer, () => { | ||
// emit handshake event | ||
self.server.emit(EVENTS.HANDSHAKE, socket) | ||
// now listen for more details | ||
socket.once('data', next) | ||
}) | ||
}) | ||
handshake({ | ||
socket, | ||
buffer, | ||
options: self.options, | ||
connect, | ||
authenticate: _authenticate, | ||
onHandshakeCompleted, | ||
end, | ||
}) | ||
} | ||
// capture the client handshake | ||
socket.once('data', handshake) | ||
socket.once('data', _handshake) | ||
@@ -484,8 +400,14 @@ // capture socket closure | ||
on = (...args) => this.server.on(...args) | ||
listen = (...args) => this.server.listen(...args) | ||
close = (...args) => this.server.close(...args) | ||
setProxyServer = (proxyServer) => { | ||
this.options.proxyServer = proxyServer | ||
} | ||
getProxyServer = () => this.options.proxyServer | ||
} | ||
const createSocks5ProxyServer = ({options} = {}) => { | ||
const createSocks5ProxyServer = (options) => { | ||
let socksServer = new SocksServer(options) | ||
@@ -492,0 +414,0 @@ return socksServer |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
24329
8
649
10
1
4