2017-01-31 23:11:36 +00:00
const socks = require ( 'socksv5' ) ;
2018-09-09 19:42:34 +00:00
const Promise = require ( 'bluebird' ) ;
2018-09-09 05:45:12 +00:00
const { Server } = socks ;
2017-01-31 23:11:36 +00:00
2018-09-14 23:54:14 +00:00
/ * *
* Configuration for the "proxy by name" feature ( connecting to specific instances or groups of instances using the username field when connecting ) .
* @ typedef ProxyByNameConfig
*
* @ property { boolean } [ deny _unidentified _users = false ] - Deny unauthenticated ( e . g . no username - socks : //my-server:9050) users access to the proxy server.
* @ property { string } mode - Either "group" for routing to a group of instances or "individual" for routing to individual instances .
* /
/ * *
* Details on the source of a connection the proxy server .
* @ typedef InstanceConnectionSource
* @ property { string } hostname - Hostname where the connection was made from .
* @ property { number } port - Port where the connection was made from .
* @ property { boolean } by _name - Indicates whether the connection was made using a username ( made to a specific instance or group of instances ) .
* @ property { string } proto - The protocol of the connection "socks" , "http" , "http-connect" or "dns"
* /
/ * *
* A SOCKS5 proxy server that will route requests to instances in the TorPool provided .
* @ extends Server
* /
2018-09-09 05:45:12 +00:00
class SOCKSServer extends Server {
2018-09-14 23:54:14 +00:00
/ * *
* Callback for ` authenticate_user ` .
* @ callback SOCKSServer ~ authenticate _user _callback
* @ param { boolean } allow - Indicates if the connection should be allowed .
* @ param { boolean } user - Indicates if the connection should have a session ( authentication was successful ) .
* /
/ * *
* 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
/ * *
* Retrieves an instance from the pool or an instance from a group by the name provided .
* @ param { string } username - Name of the group or instance to route to .
* @ returns { TorProcess }
* @ throws If { @ link SOCKSServer # proxy _by _name } is set to an invalid value .
* @ throws If the name of the instance or group provided is invalid .
* @ private
* /
2018-09-11 19:53:52 +00:00
get _instance _pbn ( username ) {
if ( this . proxy _by _name . mode === 'individual' )
return this . tor _pool . instance _by _name ( username ) ;
else if ( this . proxy _by _name . mode === 'group' ) {
return this . tor _pool . next _by _group ( username ) ;
} else
throw Error ( ` Unknown "proxy_by_name" mode ${ this . proxy _by _name . mode } ` ) ;
}
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 { string } username
* @ param { string } password
* @ param { SOCKSServer ~ authenticate _user _callback } callback - Callback for ` authenticate_user ` .
* @ throws If { @ link SOCKSServer # proxy _by _name } is invalid .
* @ private
* /
2018-09-10 03:44:24 +00:00
authenticate _user ( username , password , callback ) {
2018-09-10 17:03:02 +00:00
let deny _un = this . proxy _by _name . deny _unidentified _users ;
2018-09-11 19:53:52 +00:00
2018-09-10 03:44:24 +00:00
// No username and deny unindentifed then deny
if ( ! username && deny _un ) callback ( false ) ;
// Otherwise if there is no username allow
else if ( ! username ) callback ( true ) ;
2018-09-11 19:53:52 +00:00
if ( this . proxy _by _name . mode === 'individual' ) {
if ( ! this . tor _pool . instance _names . includes ( username ) ) return callback ( false ) ;
}
else if ( this . proxy _by _name . mode === 'group' ) {
if ( ! this . tor _pool . group _names . has ( username ) ) return callback ( false ) ;
}
else
throw Error ( ` Unknown "proxy_by_name" mode " ${ this . proxy _by _name . mode } " ` ) ;
2018-09-10 03:44:24 +00:00
// Otherwise allow
2018-09-10 17:03:02 +00:00
callback ( true , true ) ;
2018-09-10 03:44:24 +00:00
}
2018-09-14 23:54:14 +00:00
/ * *
* Creates an instance of ` SOCKSServer ` .
* @ 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 ( socks : //instance-1:@my-server:9050) when connecting.
* /
2018-09-10 02:26:55 +00:00
constructor ( tor _pool , logger , proxy _by _name ) {
2018-09-14 23:54:14 +00:00
/ * *
* Handles SOCKS5 inbound connections .
*
* @ function handle _connections
* @ param { object } info - Information about the inbound connection .
* @ param { Function } accept - Callback that allows the connection .
* @ param { Function } deny - Callback that denies the connection .
* @ private
* /
const handle _connections = ( info , accept , deny ) => {
2017-01-31 23:11:36 +00:00
let inbound _socket = accept ( true ) ;
2018-09-10 03:44:24 +00:00
let instance ;
if ( inbound _socket . user )
2018-09-11 19:53:52 +00:00
instance = this . get _instance _pbn ( inbound _socket . user ) ;
2018-09-10 03:44:24 +00:00
let outbound _socket ;
2017-01-31 23:11:36 +00:00
let buffer = [ ] ;
2017-03-26 01:14:46 +00:00
let onInboundData = ( data ) => buffer . push ( data )
let onClose = ( error ) => {
inbound _socket && inbound _socket . end ( ) ;
outbound _socket && outbound _socket . end ( ) ;
2017-03-26 01:02:56 +00:00
2017-03-26 01:14:46 +00:00
inbound _socket = outbound _socket = buffer = void ( 0 ) ;
2017-03-26 01:02:56 +00:00
2017-03-26 01:14:46 +00:00
if ( error )
this . logger . error ( ` [socks]: an error occured: ${ error . message } ` )
} ;
2017-03-26 01:02:56 +00:00
2017-03-26 01:14:46 +00:00
if ( ! inbound _socket ) return ;
2017-03-26 01:02:56 +00:00
2017-03-26 01:14:46 +00:00
inbound _socket . on ( 'close' , onClose ) ;
inbound _socket . on ( 'data' , onInboundData ) ;
inbound _socket . on ( 'error' , onClose ) ;
2017-03-26 01:02:56 +00:00
2017-03-26 01:14:46 +00:00
let connect = ( tor _instance ) => {
2018-09-10 17:03:02 +00:00
let source = { hostname : info . srcAddr , port : info . srcPort , proto : 'socks' , by _name : Boolean ( instance ) } ;
2017-03-26 01:14:46 +00:00
let socks _port = tor _instance . socks _port ;
2017-03-26 01:02:56 +00:00
2018-10-15 21:36:32 +00:00
let client = socks . connect ( {
2018-08-09 17:42:11 +00:00
host : info . dstAddr ,
port : info . dstPort ,
proxyHost : '127.0.0.1' ,
proxyPort : socks _port ,
localDNS : false ,
auths : [ socks . auth . None ( ) ]
} , ( $outbound _socket ) => {
2018-09-14 23:54:14 +00:00
/ * *
* Fires when the proxy has made a connection through an instance .
*
* @ event SOCKSServer # 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 ( ` [socks]: ${ source . hostname } : ${ source . port } → 127.0.0.1: ${ socks _port } ${ tor _instance . definition . Name ? ' (' + tor _instance . definition . Name + ')' : '' } → ${ info . dstAddr } : ${ info . dstPort } ` )
2018-08-09 17:42:11 +00:00
outbound _socket = $outbound _socket ;
outbound _socket && outbound _socket . on ( 'close' , onClose ) ;
inbound _socket && inbound _socket . removeListener ( 'data' , onInboundData ) ;
inbound _socket && inbound _socket . on ( 'data' , ( data ) => {
outbound _socket && outbound _socket . write ( data ) ;
} ) ;
outbound _socket && outbound _socket . on ( 'data' , ( data ) => {
inbound _socket && inbound _socket . write ( data ) ;
} ) ;
outbound _socket && outbound _socket . on ( 'error' , onClose ) ;
while ( buffer && buffer . length && outbound _socket ) {
outbound _socket . write ( buffer . shift ( ) ) ;
}
2017-03-26 01:02:56 +00:00
} ) ;
2018-10-15 21:36:32 +00:00
client . on ( 'error' , onClose ) ;
2017-01-31 23:11:36 +00:00
} ;
2018-09-10 03:44:24 +00:00
if ( instance ) {
if ( instance . ready ) {
connect ( instance ) ;
}
else {
this . logger . debug ( ` [socks]: a connection has been attempted to " ${ instance . instance _name } ", but it is not live... waiting for the instance to come online ` ) ;
instance . once ( 'ready' , ( ( ) => connect ( instance ) ) ) ;
}
}
else if ( this . tor _pool . instances . length ) {
connect ( this . tor _pool . next ( ) ) ;
2017-03-26 01:02:56 +00:00
} else {
2018-09-09 19:42:34 +00:00
this . logger . debug ( ` [socks]: 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-03-26 01:02:56 +00:00
}
2018-09-14 23:54:14 +00:00
}
super ( handle _connections ) ;
2017-01-31 23:11:36 +00:00
2018-09-10 03:44:24 +00:00
let auth = socks . auth . None ( ) ;
2018-09-10 17:03:02 +00:00
if ( proxy _by _name ) {
auth = socks . auth . UserPassword ( this . authenticate _user . bind ( this ) ) ;
2018-09-10 02:26:55 +00:00
}
this . useAuth ( auth ) ;
2018-09-10 17:03:02 +00:00
2018-09-14 23:54:14 +00:00
/ * *
* Winston logger to use .
*
* @ type { Logger }
* @ public
* /
this . logger = logger || require ( './winston_silent_logger' ) ;
/ * *
* Pool of instances use to service requests .
*
* @ type { TorPool }
* @ public
* /
2018-09-10 17:03:02 +00:00
this . tor _pool = tor _pool ;
2018-09-14 23:54:14 +00:00
/ * *
* Configuration for the "proxy by name" feature .
*
* @ type { ProxyByNameConfig }
* @ public
* /
2018-09-10 17:03:02 +00:00
this . proxy _by _name = proxy _by _name ;
this . logger . debug ( ` [socks]: connecting to a specific instance by name has ben turned ${ proxy _by _name ? 'on' : 'off' } ` ) ;
2017-01-31 23:11:36 +00:00
}
} ;
2018-09-14 23:54:14 +00:00
/ * *
* Module that contains the { @ link SOCKSServer } class .
* @ module tor - router / SOCKSServer
* @ see SOCKSServer
* /
2017-01-31 23:11:36 +00:00
module . exports = SOCKSServer ;