1/** 2 * Module dependencies. 3 */ 4 5var tls; // lazy-loaded... 6var url = require('url'); 7var dns = require('dns'); 8var Agent = require('agent-base'); 9var SocksClient = require('socks').SocksClient; 10var inherits = require('util').inherits; 11 12/** 13 * Module exports. 14 */ 15 16module.exports = SocksProxyAgent; 17 18/** 19 * The `SocksProxyAgent`. 20 * 21 * @api public 22 */ 23 24function SocksProxyAgent(opts) { 25 if (!(this instanceof SocksProxyAgent)) return new SocksProxyAgent(opts); 26 if ('string' == typeof opts) opts = url.parse(opts); 27 if (!opts) 28 throw new Error( 29 'a SOCKS proxy server `host` and `port` must be specified!' 30 ); 31 Agent.call(this, opts); 32 33 var proxy = Object.assign({}, opts); 34 35 // prefer `hostname` over `host`, because of `url.parse()` 36 proxy.host = proxy.hostname || proxy.host; 37 38 // SOCKS doesn't *technically* have a default port, but this is 39 // the same default that `curl(1)` uses 40 proxy.port = +proxy.port || 1080; 41 42 if (proxy.host && proxy.path) { 43 // if both a `host` and `path` are specified then it's most likely the 44 // result of a `url.parse()` call... we need to remove the `path` portion so 45 // that `net.connect()` doesn't attempt to open that as a unix socket file. 46 delete proxy.path; 47 delete proxy.pathname; 48 } 49 50 // figure out if we want socks v4 or v5, based on the "protocol" used. 51 // Defaults to 5. 52 proxy.lookup = false; 53 switch (proxy.protocol) { 54 case 'socks4:': 55 proxy.lookup = true; 56 // pass through 57 case 'socks4a:': 58 proxy.version = 4; 59 break; 60 case 'socks5:': 61 proxy.lookup = true; 62 // pass through 63 case 'socks:': // no version specified, default to 5h 64 case 'socks5h:': 65 proxy.version = 5; 66 break; 67 default: 68 throw new TypeError( 69 'A "socks" protocol must be specified! Got: ' + proxy.protocol 70 ); 71 } 72 73 if (proxy.auth) { 74 var auth = proxy.auth.split(':'); 75 proxy.authentication = { username: auth[0], password: auth[1] }; 76 proxy.userid = auth[0]; 77 } 78 this.proxy = proxy; 79} 80inherits(SocksProxyAgent, Agent); 81 82/** 83 * Initiates a SOCKS connection to the specified SOCKS proxy server, 84 * which in turn connects to the specified remote host and port. 85 * 86 * @api public 87 */ 88 89SocksProxyAgent.prototype.callback = function connect(req, opts, fn) { 90 var proxy = this.proxy; 91 92 // called once the SOCKS proxy has connected to the specified remote endpoint 93 function onhostconnect(err, result) { 94 if (err) return fn(err); 95 96 var socket = result.socket; 97 98 var s = socket; 99 if (opts.secureEndpoint) { 100 // since the proxy is connecting to an SSL server, we have 101 // to upgrade this socket connection to an SSL connection 102 if (!tls) tls = require('tls'); 103 opts.socket = socket; 104 opts.servername = opts.host; 105 opts.host = null; 106 opts.hostname = null; 107 opts.port = null; 108 s = tls.connect(opts); 109 } 110 111 fn(null, s); 112 } 113 114 // called for the `dns.lookup()` callback 115 function onlookup(err, ip) { 116 if (err) return fn(err); 117 options.destination.host = ip; 118 SocksClient.createConnection(options, onhostconnect); 119 } 120 121 var options = { 122 proxy: { 123 ipaddress: proxy.host, 124 port: +proxy.port, 125 type: proxy.version 126 }, 127 destination: { 128 port: +opts.port 129 }, 130 command: 'connect' 131 }; 132 133 if (proxy.authentication) { 134 options.proxy.userId = proxy.userid; 135 options.proxy.password = proxy.authentication.password; 136 } 137 138 if (proxy.lookup) { 139 // client-side DNS resolution for "4" and "5" socks proxy versions 140 dns.lookup(opts.host, onlookup); 141 } else { 142 // proxy hostname DNS resolution for "4a" and "5h" socks proxy servers 143 onlookup(null, opts.host); 144 } 145} 146