2017-12-08 01:21:42 +00:00
const http = require ( 'http' ) ;
const URL = require ( 'url' ) ;
2018-09-09 05:45:12 +00:00
const { Server } = http ;
2018-09-09 19:42:34 +00:00
const Promise = require ( 'bluebird' ) ;
2018-09-09 05:45:12 +00:00
const socks = require ( 'socksv5' ) ;
2017-12-08 01:21:42 +00:00
2018-09-14 23:54:14 +00:00
/ * *
* Value of the "Proxy-Agent" header that will be sent with each http - connect ( https ) request
* @ constant
* @ type { string }
* @ default
* /
2018-09-09 05:45:12 +00:00
const TOR _ROUTER _PROXY _AGENT = 'tor-router' ;
2018-09-14 23:54:14 +00:00
/ * *
* What will show up when an unauthenticated user attempts to connect when an invalid username
* @ constant
* @ type { string }
* @ default
* /
const REALM = 'Name of instance to route to' ;
/ * *
* A HTTP ( S ) proxy server that will route requests to instances in the TorPool provided .
* @ extends Server
* /
2017-12-08 01:21:42 +00:00
class HTTPServer extends Server {
2018-09-14 23:54:14 +00:00
/ * *
* Binds the server to a port and IP Address .
*
* @ async
* @ param { number } port - The port to bind to
* @ param { string } [ host = "::" ] - Address to bind to . Will default to : : or 0.0 . 0.0 if not specified .
* @ returns { Promise }
*
* /
2018-09-09 19:42:34 +00:00
async listen ( ) {
return await new Promise ( ( resolve , reject ) => {
let args = Array . from ( arguments ) ;
let inner _func = super . listen ;
args . push ( ( ) => {
let args = Array . from ( arguments ) ;
resolve . apply ( args ) ;
} ) ;
inner _func . apply ( this , args ) ;
} ) ;
}
2018-09-14 23:54:14 +00:00
/ * *
* Handles username authentication for HTTP requests
* @ param { ClientRequest } req - Incoming HTTP request
* @ param { ClientResponse } res - Outgoing HTTP response
* @ private
* /
2018-09-10 03:44:24 +00:00
authenticate _user _http ( req , res ) {
return this . authenticate _user ( req , ( ) => {
2018-09-14 23:54:14 +00:00
res . writeHead ( 407 , { 'Proxy-Authenticate' : ` Basic realm=" ${ REALM } " ` } ) ;
2018-09-10 03:44:24 +00:00
res . end ( ) ;
return false ;
} )
}
2018-09-14 23:54:14 +00:00
/ * *
* Handles username authentication for HTTP - Connect requests
* @ param { ClientRequest } req - Incoming HTTP request
* @ param { Socket } socket - Inbound HTTP - Connect socket
* @ private
* /
2018-09-10 03:44:24 +00:00
authenticate _user _connect ( req , socket ) {
return this . authenticate _user ( req , ( ) => {
2018-09-14 23:54:14 +00:00
socket . write ( ` HTTP/1.1 407 Proxy Authentication Required \r \n '+'Proxy-Authenticate: Basic realm=" ${ REALM } " \r \n ` + '\r\n' ) ;
2018-09-10 03:44:24 +00:00
socket . end ( ) ;
return false ;
} )
}
2018-09-14 23:54:14 +00:00
/ * *
* Checks the username provided against all groups ( for "group" mode ) or all instances ( for "individual" mode ) .
* @ param { ClientRequest } req - Incoming HTTP request
* @ param { Function } deny - Function that when called will deny the connection to the proxy server and prompt the user for credentials ( HTTP 407 ) .
* @ private
* @ throws If the { @ link HTTPServer # proxy _by _name } mode is invalid
* /
2018-09-10 03:44:24 +00:00
authenticate _user ( req , deny ) {
if ( ! this . proxy _by _name )
return true ;
let deny _un = this . proxy _by _name . deny _unidentified _users ;
let header = req . headers [ 'authorization' ] || req . headers [ 'proxy-authorization' ] ;
if ( ! header && deny _un ) return deny ( ) ;
else if ( ! header ) return true ;
let token = header . split ( /\s+/ ) . pop ( ) ;
if ( ! token && deny _un ) return deny ( ) ;
else if ( ! token ) return true ;
let buf = new Buffer . from ( token , 'base64' ) . toString ( ) ;
2018-09-11 22:17:35 +00:00
if ( ! buf && deny _un ) return deny ( ) ;
else if ( ! buf ) return true ;
2018-09-10 03:44:24 +00:00
let username = buf . split ( /:/ ) . shift ( ) ;
if ( ! username && deny _un ) return deny ( ) ;
else if ( ! username ) return true ;
2018-09-11 19:53:52 +00:00
let instance ;
if ( this . proxy _by _name . mode === 'individual' )
instance = this . tor _pool . instance _by _name ( username ) ;
else if ( this . proxy _by _name . mode === 'group' ) {
if ( ! this . tor _pool . group _names . has ( username ) ) return deny ( ) ;
2018-09-10 03:44:24 +00:00
2018-09-11 19:53:52 +00:00
instance = this . tor _pool . next _by _group ( username ) ;
}
else
throw Error ( ` Unknown "proxy_by_name" mode ${ this . proxy _by _name . mode } ` ) ;
2018-09-10 03:44:24 +00:00
if ( ! instance ) return deny ( ) ;
req . instance = instance ;
return true ;
}
2018-09-14 23:54:14 +00:00
/ * *
* Creates an instance of ` HTTPServer ` .
* @ param { TorPool } tor _pool - The pool of instances that will be used for requests .
* @ param { Logger } [ logger ] - Winston logger that will be used for logging . If not specified will disable logging .
* @ param { ProxyByNameConfig } [ proxy _by _name ] - Enable routing to specific instances or groups of instances using the username field ( http : //instance-1:@my-server:9050) when connecting.
* /
2018-09-10 03:44:24 +00:00
constructor ( tor _pool , logger , proxy _by _name ) {
2018-09-14 23:54:14 +00:00
/ * *
* Handles incoming HTTP Connections .
* @ function handle _http _connections
* @ param { ClientRequest } - Incoming HTTP request .
* @ param { ClientResponse } - Outgoing HTTP response .
* @ private
* /
const handle _http _connections = ( req , res ) => {
2018-09-10 03:44:24 +00:00
if ( ! this . authenticate _user _http ( req , res ) )
return ;
let { instance } = req ;
2017-12-08 01:21:42 +00:00
let url = URL . parse ( req . url ) ;
url . port = url . port || 80 ;
2018-09-09 05:45:12 +00:00
let buffer = [ ] ;
2017-12-08 01:21:42 +00:00
function onIncomingData ( chunk ) {
buffer . push ( chunk ) ;
}
function preConnectClosed ( ) {
req . finished = true ;
}
req . on ( 'data' , onIncomingData ) ;
req . on ( 'end' , preConnectClosed ) ;
2018-08-10 04:46:34 +00:00
req . on ( 'error' , function ( err ) {
2018-09-09 19:42:34 +00:00
this . logger . error ( "[http-proxy]: an error occured: " + err . message ) ;
2018-08-10 04:46:34 +00:00
} ) ;
2017-12-08 01:21:42 +00:00
let connect = ( tor _instance ) => {
2018-09-10 17:03:02 +00:00
let source = { hostname : req . connection . remoteAddress , port : req . connection . remotePort , proto : 'http' , by _name : Boolean ( instance ) } ;
2017-12-08 01:21:42 +00:00
let socks _port = tor _instance . socks _port ;
2018-09-14 23:54:14 +00:00
2018-08-09 17:42:11 +00:00
let proxy _req = http . request ( {
method : req . method ,
hostname : url . hostname ,
port : url . port ,
path : url . path ,
headers : req . headers ,
2018-09-10 17:03:02 +00:00
agent : socks . HttpAgent ( {
proxyHost : '127.0.0.1' ,
proxyPort : socks _port ,
auths : [ socks . auth . None ( ) ] ,
localDNS : false
} )
2018-08-09 17:42:11 +00:00
} , ( proxy _res ) => {
2018-09-14 23:54:14 +00:00
/ * *
* Fires when the proxy has made a connection through an instance using HTTP or HTTP - Connect .
*
* @ event HTTPServer # instance - connection
* @ param { TorProcess } instance - Instance that has been connected to .
* @ param { InstanceConnectionSource } source - Details on the source of the connection .
* /
this . emit ( 'instance_connection' , tor _instance , source ) ;
this . logger . verbose ( ` [http-proxy]: ${ source . hostname } : ${ source . port } → 127.0.0.1: ${ socks _port } ${ tor _instance . definition . Name ? ' (' + tor _instance . definition . Name + ')' : '' } → ${ url . hostname } : ${ url . port } ` ) ;
2018-08-09 17:42:11 +00:00
proxy _res . on ( 'data' , ( chunk ) => {
res . write ( chunk ) ;
2017-12-08 01:21:42 +00:00
} ) ;
2018-08-09 17:42:11 +00:00
proxy _res . on ( 'end' , ( ) => {
res . end ( ) ;
} ) ;
2017-12-08 01:21:42 +00:00
2018-08-09 17:42:11 +00:00
res . writeHead ( proxy _res . statusCode , proxy _res . headers ) ;
} ) ;
2017-12-08 01:21:42 +00:00
2018-08-09 17:42:11 +00:00
req . removeListener ( 'data' , onIncomingData ) ;
2017-12-08 01:21:42 +00:00
2018-08-09 17:42:11 +00:00
req . on ( 'data' , ( chunk ) => {
proxy _req . write ( chunk ) ;
} )
2017-12-08 01:21:42 +00:00
2018-08-09 17:42:11 +00:00
req . on ( 'end' , ( ) => {
proxy _req . end ( ) ;
} )
while ( buffer . length ) {
proxy _req . write ( buffer . shift ( ) ) ;
}
if ( req . finished )
proxy _req . end ( ) ;
2017-12-08 01:21:42 +00:00
} ;
2018-09-10 03:44:24 +00:00
if ( instance ) {
if ( instance . ready ) {
connect ( instance ) ;
}
else {
2018-09-11 17:39:49 +00:00
this . logger . debug ( ` [http-proxy]: a connection has been attempted to " ${ instance . instance _name } ", but it is not live... waiting for the instance to come online ` ) ;
2018-09-10 03:44:24 +00:00
instance . once ( 'ready' , ( ( ) => connect ( instance ) ) ) ;
}
}
else if ( this . tor _pool . instances . length ) {
2017-12-08 01:21:42 +00:00
connect ( tor _pool . next ( ) ) ;
} else {
2018-09-09 19:42:34 +00:00
this . logger . debug ( ` [http-proxy]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online ` ) ;
2017-12-08 01:21:42 +00:00
tor _pool . once ( 'instance_created' , connect ) ;
}
2018-09-14 23:54:14 +00:00
}
2017-12-08 01:21:42 +00:00
2018-09-14 23:54:14 +00:00
/ * *
* Handles incoming HTTP - Connect connections .
* @ function handle _connect _connections
* @ param { ClientRequest } req - Incoming HTTP Request .
* @ param { Socket } inbound _socket - Incoming socket .
* @ param { Buffer | string } head - HTTP Request head .
* @ private
* /
const handle _connect _connections = ( req , inbound _socket , head ) => {
2018-09-10 03:44:24 +00:00
if ( ! this . authenticate _user _connect ( req , inbound _socket ) )
return ;
let { instance } = req ;
2017-12-08 01:52:10 +00:00
let hostname = req . url . split ( ':' ) . shift ( ) ;
let port = Number ( req . url . split ( ':' ) . pop ( ) ) ;
2017-12-08 01:21:42 +00:00
let connect = ( tor _instance ) => {
2018-09-10 17:03:02 +00:00
let source = { hostname : req . connection . remoteAddress , port : req . connection . remotePort , proto : 'http-connect' , by _name : Boolean ( instance ) } ;
2018-09-14 23:54:14 +00:00
2017-12-08 01:21:42 +00:00
let socks _port = tor _instance . socks _port ;
2017-12-08 01:52:10 +00:00
var outbound _socket ;
2017-12-08 01:21:42 +00:00
let onClose = ( error ) => {
inbound _socket && inbound _socket . end ( ) ;
outbound _socket && outbound _socket . end ( ) ;
2018-09-10 17:25:39 +00:00
inbound _socket = outbound _socket = void ( 0 ) ;
2017-12-08 01:21:42 +00:00
2018-09-10 17:25:39 +00:00
if ( error instanceof Error )
2017-12-08 01:52:10 +00:00
this . logger . error ( ` [http-connect]: an error occured: ${ error . message } ` )
2017-12-08 01:21:42 +00:00
} ;
2018-09-10 17:25:39 +00:00
inbound _socket . on ( 'error' , onClose ) ;
inbound _socket . on ( 'close' , onClose ) ;
2017-12-08 01:52:10 +00:00
2018-08-09 17:42:11 +00:00
socks . connect ( {
host : hostname ,
port : port ,
proxyHost : '127.0.0.1' ,
proxyPort : socks _port ,
localDNS : false ,
auths : [ socks . auth . None ( ) ]
} , ( $outbound _socket ) => {
2018-09-14 23:54:14 +00:00
this . emit ( 'instance_connection' , tor _instance , source ) ;
this . logger && this . logger . verbose ( ` [http-connect]: ${ source . hostname } : ${ source . port } → 127.0.0.1: ${ socks _port } ${ tor _instance . definition . Name ? ' (' + tor _instance . definition . Name + ')' : '' } → ${ hostname } : ${ port } ` )
2018-08-09 17:42:11 +00:00
outbound _socket = $outbound _socket ;
2018-09-10 17:25:39 +00:00
outbound _socket . on ( 'close' , onClose ) ;
outbound _socket . on ( 'error' , onClose ) ;
2018-08-09 17:42:11 +00:00
2018-09-09 05:45:12 +00:00
inbound _socket . write ( ` HTTP/1.1 200 Connection Established \r \n '+'Proxy-agent: ${ TOR _ROUTER _PROXY _AGENT } \r \n ` + '\r\n' ) ;
2018-08-09 17:42:11 +00:00
outbound _socket . write ( head ) ;
outbound _socket . pipe ( inbound _socket ) ;
inbound _socket . pipe ( outbound _socket ) ;
2017-12-08 01:21:42 +00:00
} ) ;
} ;
2018-09-10 03:44:24 +00:00
if ( instance ) {
if ( instance . ready ) {
connect ( instance ) ;
}
else {
2018-09-11 17:39:49 +00:00
this . logger . debug ( ` [http-connect]: a connection has been attempted to " ${ instance . instance _name } ", but it is not live... waiting for the instance to come online ` ) ;
2018-09-10 03:44:24 +00:00
instance . once ( 'ready' , ( ( ) => connect ( instance ) ) ) ;
}
}
else if ( this . tor _pool . instances . length ) {
connect ( this . tor _pool . next ( ) ) ;
2017-12-08 01:21:42 +00:00
} else {
2018-09-09 19:42:34 +00:00
this . logger . debug ( ` [http-connect]: a connection has been attempted, but no tor instances are live... waiting for an instance to come online ` ) ;
2018-09-10 03:44:24 +00:00
this . tor _pool . once ( 'instance_created' , connect ) ;
2017-12-08 01:21:42 +00:00
}
2018-09-14 23:54:14 +00:00
}
2017-12-08 01:21:42 +00:00
super ( handle _http _connections ) ;
2017-12-08 01:52:10 +00:00
this . on ( 'connect' , handle _connect _connections ) ;
2018-09-09 19:42:34 +00:00
2018-09-14 23:54:14 +00:00
/ * *
* Winston logger .
* @ type { Logger }
* @ public
* /
this . logger = logger || require ( './winston_silent_logger' ) ;
/ * *
* The pool of instances that will be used for requests .
* @ type { TorPool }
* @ public
* /
2017-12-08 01:21:42 +00:00
this . tor _pool = tor _pool ;
2018-09-14 23:54:14 +00:00
/ * *
* Configuration for "proxy by name" feature .
* @ type { ProxyByNameConfig }
* @ public
* /
2018-09-10 03:44:24 +00:00
this . proxy _by _name = proxy _by _name ;
2017-12-08 01:21:42 +00:00
}
} ;
2018-09-14 23:54:14 +00:00
/ * *
* Module that contains the { @ link HTTPServer } class .
* @ module tor - router / HTTPServer
* @ see HTTPServer
* /
2017-12-08 01:21:42 +00:00
module . exports = HTTPServer ;