var fs = require ( "fs" ) ;
var https = require ( "https" ) ;
var http = require ( "http" ) ;
var express = require ( "express" ) ;
var console = require ( "console" ) ;
var app = express ( ) ;
try
{
var settings = JSON . parse ( fs . readFileSync ( "settings.json" ) ) ;
}
catch ( e )
{
console . log ( "Error: Configuration file settings.json couldn't be parsed.\nSee Troubleshooting section in README.md for instructions." ) ;
process . exit ( 1 ) ;
}
if ( settings . Server . enable _https )
{
try
{
var privateKey = fs . readFileSync ( settings . Server . ssl _key _file ) . toString ( ) ;
}
catch ( e )
{
console . log ( "Error: unable to load SSL key. Please edit setting.json and put the correct path to your SSL private key file into \"ssl_key_file.\"" ) ;
process . exit ( 1 ) ;
}
try
{
var certificate = fs . readFileSync ( settings . Server . ssl _certificate _file ) . toString ( ) ;
}
catch ( e )
{
console . log ( "Error: unable to load SSL certificate. Please edit setting.json and put the correct path to your SSL certificate file into \"ssl_certificate_file.\"" ) ;
process . exit ( 1 ) ;
}
var credentials = { key : privateKey , cert : certificate } ;
}
maxCallsPerMinute = { } ;
maxCallsPerMinutePerIP = { } ;
callsRemaining = { } ;
perIPCounter = { } ;
droppedCallsCounter = { } ;
var invalidRequestCounter = 0 ;
var forbiddenCallCounter = 0 ;
var connectionErrorMessageDisplayed = false ;
settings . CallLimits . forEach ( function ( x ) {
maxCallsPerMinute [ x . name ] = x . maxPerMinute ;
maxCallsPerMinutePerIP [ x . name ] = x . maxPerMinutePerIP ;
callsRemaining [ x . name ] = x . maxPerMinute ;
droppedCallsCounter [ x . name ] = 0 ;
} ) ;
CounterInstance = function ( )
{
this . overLimit = 0 ;
this . invalidRequests = 0 ;
this . forbiddenCalls = 0 ;
this . callsRemaining = { } ;
for ( x in maxCallsPerMinutePerIP )
{
if ( maxCallsPerMinutePerIP [ x ] !== null )
this . callsRemaining [ x ] = maxCallsPerMinutePerIP [ x ] ;
}
}
require ( "log-timestamp" ) ;
app . get ( "*" , function ( request , response )
{
if ( settings . Server . enable _https && request . protocol == "http" )
{
if ( settings . Server . https _port == 443 )
secureUrl = "https://" + request . host + request . path ;
else
secureUrl = "https://" + request . host + ":" + settings . Server . https _port + request . path ;
response . writeHead ( 302 , { "Location" : secureUrl } ) ;
response . end ( ) ;
return ;
}
var webProxy = http . request ( { host : settings . RPC . host , port : settings . RPC . port , method : request . method , path : request . path , headers : request . headers } , function ( proxy _res )
{
proxy _res . pipe ( response , { end : true } ) ;
} ) ;
webProxy . on ( "error" , function ( error )
{
if ( ! connectionErrorMessageDisplayed )
{
console . log ( "Error: cannot connect to twisterd.\nSee Troubleshooting section in README.md for instructions." ) ;
connectionErrorMessageDisplayed = true ;
}
response . send ( 502 ) ;
} ) ;
request . pipe ( webProxy , { end : true } ) ;
} ) ;
app . post ( "/" , function ( request , response )
{
request . rawBody = "" ;
request . setEncoding ( "utf8" ) ;
request . addListener ( "data" , function ( chunk )
{
request . rawBody += chunk ;
} ) ;
request . addListener ( "end" , function ( )
{
var remoteIP = request . connection . remoteAddress ;
if ( perIPCounter [ remoteIP ] === undefined )
{
perIPCounter [ remoteIP ] = new CounterInstance ( ) ;
}
try
{
bodyJson = JSON . parse ( request . rawBody ) ;
rpcMethod = bodyJson . method ;
}
catch ( e )
{
perIPCounter [ remoteIP ] . invalidRequests ++ ;
invalidRequestCounter ++ ;
return ;
}
if ( maxCallsPerMinute [ rpcMethod ] === undefined )
{
perIPCounter [ remoteIP ] . forbiddenCalls ++ ;
forbiddenCallCounter ++ ;
return ;
}
if ( maxCallsPerMinute [ rpcMethod ] !== null )
{
if ( callsRemaining [ rpcMethod ] < 1 )
{
droppedCallsCounter [ rpcMethod ] ++ ;
return ;
}
else
{
callsRemaining [ rpcMethod ] -- ;
}
}
if ( maxCallsPerMinutePerIP [ rpcMethod ] !== null )
{
if ( perIPCounter [ remoteIP ] . callsRemaining [ rpcMethod ] < 1 )
{
perIPCounter [ remoteIP ] . overLimit ++ ;
return ;
}
else
{
perIPCounter [ remoteIP ] . callsRemaining [ rpcMethod ] -- ;
}
}
var rpcProxy = http . request ( { host : settings . RPC . host , port : settings . RPC . port , method : request . method , headers : request . headers } , function ( proxy _res )
{
proxy _res . on ( "data" , function ( chunk )
{
response . write ( chunk , "binary" ) ;
} ) ;
proxy _res . on ( "end" , function ( chunk )
{
response . end ( ) ;
} ) ;
proxy _res . on ( "error" , function ( error )
{
if ( ! connectionErrorMessageDisplayed )
{
console . log ( "Error: cannot connect to twisterd.\nSee Troubleshooting section in README.md for instructions." ) ;
connectionErrorMessageDisplayed = true ;
}
response . send ( 502 ) ;
} ) ;
response . writeHead ( proxy _res . statusCode , proxy _res . headers ) ;
} ) ;
rpcProxy . write ( request . rawBody , "binary" ) ;
rpcProxy . end ( ) ;
} ) ;
} ) ;
if ( settings . Server . enable _https )
{
https . createServer ( credentials , app ) . listen ( settings . Server . https _port ) ;
}
http . createServer ( app ) . listen ( settings . Server . http _port ) ;
setInterval ( function ( )
{
for ( method in maxCallsPerMinute )
{
callsRemaining [ method ] = maxCallsPerMinute [ method ] ;
if ( droppedCallsCounter [ method ] !== 0 )
{
console . log ( "Dropped " + droppedCallsCounter [ method ] + " calls to " + method + " over the limit of " + maxCallsPerMinute [ method ] ) ;
droppedCallsCounter [ method ] = 0 ;
}
} ;
if ( invalidRequestCounter !== 0 )
{
console . log ( "Received " + invalidRequestCounter + " invalid POST requests." ) ;
invalidRequestCounter = 0 ;
}
if ( forbiddenCallCounter !== 0 )
{
console . log ( "Denied " + forbiddenCallCounter + " attempts to access forbidden API calls." ) ;
forbiddenCallCounter = 0 ;
}
for ( ip in perIPCounter )
{
if ( perIPCounter [ ip ] . overLimit >= settings . LogAsAttackThreshold . callsOverLimits )
{
console . log ( "IP " + ip + " tried to send " + perIPCounter [ ip ] . overLimit + " calls more than the limits allow." ) ;
} ;
if ( perIPCounter [ ip ] . invalidRequests >= settings . LogAsAttackThreshold . invalidRequests )
{
console . log ( "IP " + ip + " sent " + perIPCounter [ ip ] . invalidRequests + " invalid request that couldn't be parsed." ) ;
} ;
if ( perIPCounter [ ip ] . forbiddenCalls >= settings . LogAsAttackThreshold . forbiddenCalls )
{
console . log ( "IP " + ip + " tried to send " + perIPCounter [ ip ] . forbiddenCalls + " calls to forbidden functions." ) ;
} ;
}
perIPCounter = { } ;
connectionErrorMessageDisplayed = false ;
} , 60000 ) ;