RPC proxy for running a public server for the Twister P2P microblogging platform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

260 lines
7.9 KiB

var fs = require("fs");
var https = require("https");
var http = require("http");
var express = require("express");
var console = require("console");
var app = express();
var cors = require('cors');
function handlePortAccessError()
{
console.log("Error: Node.js doesn't allow access to ports lower than 1024 in normal user mode.");
console.log(" Possible solutions:");
console.log(" 1 - Edit settings.json and change \"http_port\": 80 to a higher number. It will then run under any user.");
console.log(" 2 - If you want to use standard HTTP port, run the server as root: sudo node twister-proxy.js &");
process.exit(1);
}
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;
var auth = "Basic " + new Buffer(settings.RPC.user + ":" + settings.RPC.password).toString("base64");
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.use(cors());
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;
}
request.headers.Authorization = auth;
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.headers.Authorization = auth;
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 || maxCallsPerMinute[rpcMethod]===0)
{
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).on("error", handlePortAccessError);
http.createServer(app).listen(settings.Server.http_port).on("error", handlePortAccessError);
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);