blob: 4a31f62c32c9e8fc521392097d7f9c57a185a405 [file] [log] [blame]
var net = require('net');
var ip = require('ip');
var SmartBuffer = require('smart-buffer');
(function () {
var COMMAND = {
Connect: 0x01,
Bind: 0x02,
Associate: 0x03
};
var SOCKS4_RESPONSE = {
Granted: 0x5A,
Failed: 0x5B,
Rejected: 0x5C,
RejectedIdent: 0x5D
};
var SOCKS5_AUTH = {
NoAuth: 0x00,
GSSApi: 0x01,
UserPass: 0x02
};
var SOCKS5_RESPONSE = {
Granted: 0x00,
Failure: 0x01,
NotAllowed: 0x02,
NetworkUnreachable: 0x03,
HostUnreachable: 0x04,
ConnectionRefused: 0x05,
TTLExpired: 0x06,
CommandNotSupported: 0x07,
AddressNotSupported: 0x08
};
exports.createConnection = function (options, callback) {
var socket = new net.Socket(), finished = false, buff = new SmartBuffer();
// Defaults
options.timeout = options.timeout || 10000;
options.proxy.command = commandFromString(options.proxy.command);
options.proxy.userid = options.proxy.userid || "";
var auth = options.proxy.authentication || {};
auth.username = auth.username || "";
auth.password = auth.password || "";
options.proxy.authentication = auth;
// Connect & negotiation timeout
function onTimeout() {
finish(new Error("Connection Timed Out"), socket, null, callback);
}
socket.setTimeout(options.timeout, onTimeout);
// Socket events
socket.once('close', function () {
finish(new Error("Socket Closed"), socket, null, callback);
});
socket.once('error', function (err) {
});
socket.once('connect', function () {
if (options.proxy.type === 4) {
negotiateSocks4(options, socket, callback);
} else if (options.proxy.type === 5) {
negotiateSocks5(options, socket, callback);
} else {
throw new Error("Please specify a proxy type in options.proxy.type");
}
});
socket.connect(options.proxy.port, options.proxy.ipaddress);
// 4/4a (connect, bind) - Supports domains & ipaddress
function negotiateSocks4(options, socket, callback) {
buff.writeUInt8(0x04);
buff.writeUInt8(options.proxy.command);
buff.writeUInt16BE(options.target.port);
// ipv4 or domain?
if (net.isIPv4(options.target.host)) {
buff.writeBuffer(ip.toBuffer(options.target.host));
buff.writeStringNT(options.proxy.userid);
} else {
buff.writeUInt8(0x00);
buff.writeUInt8(0x00);
buff.writeUInt8(0x00);
buff.writeUInt8(0x01);
buff.writeStringNT(options.proxy.userid);
buff.writeStringNT(options.target.host);
}
socket.once('data', receivedResponse);
socket.write(buff.toBuffer());
function receivedResponse(data) {
socket.pause();
if (data.length === 8 && data[1] === SOCKS4_RESPONSE.Granted) {
if (options.proxy.command === COMMAND.Bind) {
buff.clear();
buff.writeBuffer(data);
buff.skip(2);
var info = {
port: buff.readUInt16BE(),
host: buff.readUInt32BE()
};
if (info.host === 0) {
info.host = options.proxy.ipaddress;
} else {
info.host = ip.fromLong(info.host);
}
finish(null, socket, info, callback);
} else {
finish(null, socket, null, callback);
}
} else {
finish(new Error("Rejected (" + data[1] + ")"), socket, null, callback);
}
}
}
// Socks 5 (connect, bind, associate) - Supports domains and ipv4, ipv6.
function negotiateSocks5(options, socket, callback) {
buff.writeUInt8(0x05);
buff.writeUInt8(2);
buff.writeUInt8(SOCKS5_AUTH.NoAuth);
buff.writeUInt8(SOCKS5_AUTH.UserPass);
socket.once('data', handshake);
socket.write(buff.toBuffer());
function handshake(data) {
if (data.length !== 2) {
finish(new Error("Negotiation Error"), socket, null, callback);
} else if (data[0] !== 0x05) {
finish(new Error("Negotiation Error (invalid version)"), socket, null, callback);
} else if (data[1] === 0xFF) {
finish(new Error("Negotiation Error (unacceptable authentication)"), socket, null, callback);
} else {
if (data[1] === SOCKS5_AUTH.NoAuth) {
sendRequest();
} else if (data[1] === SOCKS5_AUTH.UserPass) {
sendAuthentication(options.proxy.authentication);
} else {
finish(new Error("Negotiation Error (unknown authentication type)"), socket, null, callback);
}
}
}
function sendAuthentication(authinfo) {
buff.clear();
buff.writeUInt8(0x01);
buff.writeUInt8(Buffer.byteLength(authinfo.username));
buff.writeString(authinfo.username);
buff.writeUInt8(Buffer.byteLength(authinfo.password));
buff.writeString(authinfo.password);
socket.once('data', authenticationResponse);
socket.write(buff.toBuffer());
function authenticationResponse(data) {
if (data.length === 2 && data[1] === 0x00) {
sendRequest();
} else {
finish(new Error("Negotiation Error (authentication failed)"), socket, null, callback);
}
}
}
function sendRequest() {
buff.clear();
buff.writeUInt8(0x05);
buff.writeUInt8(options.proxy.command);
buff.writeUInt8(0x00);
// ipv4, ipv6, domain?
if (net.isIPv4(options.target.host)) {
buff.writeUInt8(0x01);
buff.writeBuffer(ip.toBuffer(options.target.host));
} else if (net.isIPv6(options.target.host)) {
buff.writeUInt8(0x04);
buff.writeBuffer(ip.toBuffer(options.target.host));
} else {
buff.writeUInt8(0x03);
buff.writeUInt8(options.target.host.length);
buff.writeString(options.target.host);
}
buff.writeUInt16BE(options.target.port);
socket.once('data', receivedResponse);
socket.write(buff.toBuffer());
}
function receivedResponse(data) {
socket.pause();
if (data.length < 4) {
finish(new Error("Negotiation Error"), socket, null, callback);
} else if (data[0] === 0x05 && data[1] === SOCKS5_RESPONSE.Granted) {
if (options.proxy.command === COMMAND.Connect) {
finish(null, socket, null, callback);
} else if (options.proxy.command === COMMAND.Bind || options.proxy.command === COMMAND.Associate) {
buff.clear();
buff.writeBuffer(data);
buff.skip(3);
var info = {};
var addrtype = buff.readUInt8();
try {
if (addrtype === 0x01) {
info.host = buff.readUInt32BE();
if (info.host === 0)
info.host = options.proxy.ipaddress;
else
info.host = ip.fromLong(info.host);
} else if (addrtype === 0x03) {
var len = buff.readUInt8();
info.host = buff.readString(len);
} else if (addrtype === 0x04) {
info.host = buff.readBuffer(16);
} else {
finish(new Error("Negotiation Error (invalid host address)"), socket, null, callback);
}
info.port = buff.readUInt16BE();
finish(null, socket, info, callback);
} catch (ex) {
finish(new Error("Negotiation Error (missing data)"), socket, null, callback);
}
}
} else {
finish(new Error("Negotiation Error (" + data[1] + ")"), socket, null, callback);
}
}
}
function finish(err, socket, info, callback) {
socket.setTimeout(0, onTimeout);
if (!finished) {
finished = true;
if (buff instanceof SmartBuffer)
buff.destroy();
if (err && socket instanceof net.Socket) {
socket.removeAllListeners('close');
socket.removeAllListeners('timeout');
socket.removeAllListeners('data');
socket.destroy();
socket = null;
}
callback(err, socket, info);
}
}
function commandFromString(str) {
var result = COMMAND.Connect;
if (str === "connect") {
result = COMMAND.Connect;
} else if (str === 'associate') {
result = COMMAND.Associate;
} else if (str === 'bind') {
result = COMMAND.Bind;
}
return result;
}
};
exports.createUDPFrame = function (target, data, frame) {
var buff = new SmartBuffer();
buff.writeUInt16BE(0);
buff.writeUInt8(frame || 0x00);
if (net.isIPv4(target.host)) {
buff.writeUInt8(0x01);
buff.writeUInt32BE(ip.toLong(target.host));
} else if (net.isIPv6(target.host)) {
buff.writeUInt8(0x04);
buff.writeBuffer(ip.toBuffer(target.host));
} else {
buff.writeUInt8(0x03);
buff.writeUInt8(Buffer.byteLength(target.host));
buff.writeString(target.host);
}
buff.writeUInt16BE(target.port);
buff.writeBuffer(data);
return buff.toBuffer();
};
})();