socks-proxy-agent.js 3.6 KB

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