blob: d934485436ac3190afb14672417cd35c930ad306 [file] [log] [blame]
/**
* Module dependencies.
*/
var tls; // lazy-loaded...
var url = require('url');
var dns = require('dns');
var extend = require('extend');
var Agent = require('agent-base');
var SocksClient = require('socks');
var inherits = require('util').inherits;
/**
* Module exports.
*/
module.exports = SocksProxyAgent;
/**
* The `SocksProxyAgent`.
*
* @api public
*/
function SocksProxyAgent(opts) {
if (!(this instanceof SocksProxyAgent)) return new SocksProxyAgent(opts);
if ('string' == typeof opts) opts = url.parse(opts);
if (!opts)
throw new Error(
'a SOCKS proxy server `host` and `port` must be specified!'
);
Agent.call(this, connect);
var proxy = extend({}, opts);
// prefer `hostname` over `host`, because of `url.parse()`
proxy.host = proxy.hostname || proxy.host;
// SOCKS doesn't *technically* have a default port, but this is
// the same default that `curl(1)` uses
proxy.port = +proxy.port || 1080;
if (proxy.host && proxy.path) {
// if both a `host` and `path` are specified then it's most likely the
// result of a `url.parse()` call... we need to remove the `path` portion so
// that `net.connect()` doesn't attempt to open that as a unix socket file.
delete proxy.path;
delete proxy.pathname;
}
// figure out if we want socks v4 or v5, based on the "protocol" used.
// Defaults to 5.
proxy.lookup = false;
switch (proxy.protocol) {
case 'socks4:':
proxy.lookup = true;
// pass through
case 'socks4a:':
proxy.version = 4;
break;
case 'socks5:':
proxy.lookup = true;
// pass through
case 'socks:': // no version specified, default to 5h
case 'socks5h:':
proxy.version = 5;
break;
default:
throw new TypeError(
'A "socks" protocol must be specified! Got: ' + proxy.protocol
);
}
if (proxy.auth) {
var auth = proxy.auth.split(':');
proxy.authentication = { username: auth[0], password: auth[1] };
proxy.userid = auth[0];
}
this.proxy = proxy;
}
inherits(SocksProxyAgent, Agent);
/**
* Initiates a SOCKS connection to the specified SOCKS proxy server,
* which in turn connects to the specified remote host and port.
*
* @api public
*/
function connect(req, opts, fn) {
var proxy = this.proxy;
// called once the SOCKS proxy has connected to the specified remote endpoint
function onhostconnect(err, socket) {
if (err) return fn(err);
var s = socket;
if (opts.secureEndpoint) {
// since the proxy is connecting to an SSL server, we have
// to upgrade this socket connection to an SSL connection
if (!tls) tls = require('tls');
opts.socket = socket;
opts.servername = opts.host;
opts.host = null;
opts.hostname = null;
opts.port = null;
s = tls.connect(opts);
}
socket.resume();
fn(null, s);
}
// called for the `dns.lookup()` callback
function onlookup(err, ip) {
if (err) return fn(err);
options.target.host = ip;
SocksClient.createConnection(options, onhostconnect);
}
var options = {
proxy: {
ipaddress: proxy.host,
port: +proxy.port,
type: proxy.version
},
target: {
port: +opts.port
},
command: 'connect'
};
if (proxy.authentication) {
options.proxy.authentication = proxy.authentication;
options.proxy.userid = proxy.userid;
}
if (proxy.lookup) {
// client-side DNS resolution for "4" and "5" socks proxy versions
dns.lookup(opts.host, onlookup);
} else {
// proxy hostname DNS resolution for "4a" and "5h" socks proxy servers
onlookup(null, opts.host);
}
}