Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: a639f9d04e
Fetching contributors…

Cannot retrieve contributors at this time

524 lines (489 sloc) 18.195 kb
/**
// Copyright DazaGrohovaz.Net / ProxyJS.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
**/
/*
HOW TO USE:
echo 'facebook.com' >> blacklist
echo '1.2.3.4' >> iplist
echo '80' >> portlist
echo '443' >> portlist
echo 'yourdomain.com 192.168.0.2' >> reverse_servers
echo 'www.yourdomain.com 192.168.0.3' >> reverse_servers
echo 'somethingelse.yourdomain.com mynetinternaldnsname' >> reverse_servers
TIPP:
reverse_server list work only with http-protocol. And without WebSocket support ( For Now ;) )
if you want use this with https too, you need to add some lines into the /etc/hosts file
echo '192.168.0.3 www.yourdomain.com' >> /etc/hosts
*/
const crypto = require('crypto'),
fs = require("fs"),
http = require("http"),
net = require('net'),
url = require('url');
var realm = 'ProxyJS.com - Proxy Authetication Required', // don't use coma , or quotes " or equal =
proxy_agent = 'proxyjs.v.0.1.0', // don't use coma , or quotes " or equal =
nonce_max_duration = 30, // minutes
nonce_max_count = 150,
server_key = fs.readFileSync('server-key.pem').toString();
// i use as server key a file created with openssl
// plaintext can be used here
// that it used to encode and decode passwords
// it is required to http/proxy authetication
var PORT = 8000,
PORT_SSL = 443, // only require for HTTPS reverse proxy. not implemented jet.
run_as = 'forward' // 'forward' or 'reverse'
debug = true,
use_auth_control = true, // set true to activate this
use_blacklist_control = true, // set true to activate this
use_iplist_control = true, // set true to activate this
use_portlist_control = true, // set true to activate this
use_http_reverse_service = true; // set true to activate this
use_CONNECT_support = true; // set true to activate this
// use_CONNECT_support is required for https and socks (transfer binary data)
var users = [],
blacklist = [],
iplist = [],
portlist = [],
reverse_servers = [];
var blacklist_file = './blacklist',
iplist_file = './iplist',
portlist_file = './portlist',
reverse_servers_file = './reverse_servers',
users_file = './users';
var filteredHeaders = ['proxy-connection', 'set-cookie', 'accept-encoding', 'connection', 'keep-alive', 'proxy-authenticate', 'upgrade', 'proxy-authorization', 'trailers', 'transfer-encoding'];
if (use_auth_control) fs.watchFile(users_file, function(c,p) { update_user_list(); });
if (use_blacklist_control) fs.watchFile(blacklist_file, function(c,p) { update_blacklist(); });
if (use_iplist_control) fs.watchFile(iplist_file, function(c,p) { update_iplist(); });
if (use_portlist_control) fs.watchFile(portlist_file, function(c,p) { update_portlist(); });
if (use_http_reverse_service) fs.watchFile(reverse_servers_file, function(c,p) { update_reverse_service(); });
function update_user_list() {
log("Updating user list.");
users = fs.readFileSync(users_file).toString().split('\n')
.filter(function(rx) { return rx.length });
}
function update_blacklist() {
log("Updating blacklist.");
blacklist = fs.readFileSync(blacklist_file).toString().split('\n')
.filter(function(rx) { return rx.length })
.map(function(rx) { return RegExp(rx) });
}
function update_iplist() {
log("Updating iplist.");
iplist = fs.readFileSync(iplist_file).toString().split('\n')
.filter(function(rx) { return rx.length });
}
function update_portlist() {
log("Updating portlist.");
portlist = fs.readFileSync(portlist_file).toString().split('\n')
.filter(function(rx) { return rx.length });
}
function update_reverse_service() {
log("Updating reverse servers.");
reverse_servers = fs.readFileSync(reverse_servers_file).toString().split('\n')
.filter(function(rx) { return rx.length });
}
function log(msg){
if (debug) console.log(msg);
}
function host_allowed(host) {
for (i in blacklist) {
if (blacklist[i].test(host)) {
return false;
}
}
return true;
}
function ip_allowed(ip) {
for (i in iplist) {
if (iplist[i] == ip) {
return true;
}
}
return false;
}
function port_allowed(port) {
for (i in portlist) {
if (portlist[i] == port) {
return true;
}
}
return false;
}
function get_user_password(username) {
for (i in users) {
user = users[i].split(':');
if (user[0] == username) {
return user[1];
}
}
return null;
}
function reverse_server(host) {
for (i in reverse_servers) {
try {
var route = reverse_servers[i].split(' ');
route[0] = route[0].trim();
route[1] = route[1].trim();
if (route[0]==host) {
return route[1];
}
} catch (e) {
// return host;
}
}
return host;
}
function auth_allowed(req, socket){
if(!req.headers['proxy-authorization']){
auth_required(req, socket);
return false;
} else {
var header = req.headers['proxy-authorization'];
var digest = header.slice(0,6);
if (digest!="Digest"){
auth_required(req, socket);
return false;
}
var auth = {};
var params = header.slice(7).split(', ');
for(var i in params){
var item = params[i].toString().trim().replace("=","°^°").split("°^°");
item[1] = item[1].toString().replace('"','').replace('"','');
auth[item[0].toLowerCase()] = item[1];
params[i] = item;
};
if((!auth.username&&auth.username!='')||!auth.realm||!auth.nonce||!auth.uri||!auth.response||!auth.opaque||!auth.qop||!auth.nc||!auth.cnonce){
deny_request(req, socket);
return false;
}
auth.username = auth.username.toLowerCase();
auth.password = get_user_password(auth.username);
if(!auth.password){
log('username or password are invalid. Remote Address: '+req.connection.remoteAddress+' - '+req.url)
auth_required(req, socket);
return false;
}
auth.method = req.method;
var HA1 = hex_md5(''.concat(auth.username,':',auth.realm,':',decode_aes192(auth.password)));
var HA2 = hex_md5(''.concat(auth.method,':',auth.uri,''));
var response = hex_md5(HA1+':'+auth.nonce+':'+auth.nc+':'+auth.cnonce+':'+auth.qop+':'+HA2);
if(response != auth.response){
log('request-digest is invalid. Remote Address: '+req.connection.remoteAddress+' - '+req.url)
deny_request(req, socket);
return false;
}
var timestamp = (new Date).getTime();
var expires = parseInt(decode_aes192(auth.opaque));
if(!expires||(expires+(24*60*1000)) < timestamp){ // request username and password if expires is one day old for security reasons
auth_required(req, socket);
return false;
}
var nc = parseInt(auth.nc, 16);
var nonce = hex_md5(expires+":"+req.connection.remoteAddress+":"+server_key);
if((nonce_max_count && nonce_max_count < nc)||(nonce != auth.nonce)||(expires < timestamp && nonce == auth.nonce)){
auth_required(req, socket,'true');
return false;
}
return true;
}
}
function auth_required(req, socket, stale){
if(!stale)stale='false';
var timestamp = (new Date).getTime();
var expires = timestamp+(nonce_max_duration*60*1000);
var nonce = hex_md5(expires+":"+req.connection.remoteAddress+":"+server_key);
var opaque = encode_aes192(expires);
socket.write( 'HTTP/1.0 407 Proxy Authentication Required\r\n'
+ 'Proxy-agent: '+proxy_agent+'\r\n'
+ 'Proxy-Authenticate: Digest '
+ 'realm="'+realm+'",'
+ 'qop="auth",'
+ 'stale="'+stale+'",'
+ 'nonce="'+nonce+'",'
+ 'opaque="'+opaque+'"'
+ '\r\n'
+ '\r\n');
socket.end();
}
function encode_aes192(data){
var cipher = crypto.createCipher('aes192', server_key);
cipher.update(data.toString(), 'utf8', 'hex');
return cipher.final('hex');
}
function decode_aes192(hex){
var decipher = crypto.createDecipher('aes192', server_key);
decipher.update(hex.toString(), 'hex', 'utf8');
return decipher.final('utf8');
}
function hex_md5(data){
return crypto.createHash('md5').update(data).digest('hex');
}
function cleanHeaders(h) {
cleaned = {};
for(var p in h) {
if (filteredHeaders.indexOf(p) == -1) {
cleaned[p] = h[p];
}
}
return cleaned;
}
function responseContentType(headers) {
try {
if (headers['content-encoding'] == "gzip" ||
(headers['content-type'].indexOf("text/") == -1)) {
return "binary";
} else {
return "utf8"
}
} catch(e) {
return "utf8";
}
}
function request_allowed(req, socket){
var allowed_auth = !use_auth_control;
var allowed_host = !use_blacklist_control;
var allowed_ip = !use_iplist_control;
var allowed_port = !use_portlist_control;
var strUrl = ''+req.url;
strUrl = (strUrl.slice(0,1)=='/')?strUrl.slice(1):strUrl;
var Url = url.parse(strUrl);
var options = {
host: Url.hostname || Url.host || req.headers.host || '',
port: Url.port || ((req.method=='CONNECT')?443:((/GET|HEAD|POST|PUT|DELETE/).test(req.method))?80:''),
path: (Url.pathname || '/') + (Url.search || ''),
method: req.method
};
if(use_auth_control){
allowed_auth = auth_allowed(req, socket);
if(!allowed_auth)return false;
}
if(use_blacklist_control){
allowed_host = host_allowed(options.host);
if (!allowed_host){
msg = "Host " + req.url + " has been denied by proxy configuration";
deny_request(req, socket, {errno:1,message:msg});
return false;
}
}
if(use_iplist_control){
var ip = req.connection.remoteAddress;
allowed_ip = ip_allowed(ip);
if (!allowed_ip) {
msg = "IP " + ip + " is not allowed to use this proxy";
deny_request(req, socket, {errno:1,message:msg});
return false;
}
}
if(use_portlist_control){
if(options.port!=''){
allowed_port = port_allowed(options.port);
}
if(!allowed_port){
msg = "PORT " + options.port + " is not allow on this proxy";
deny_request(req, socket, {errno:1,message:msg});
return false;
}
}
return allowed_auth && allowed_host && allowed_ip && allowed_port
}
function connection_established(req, socket){
socket.write( 'HTTP/1.1 200 Connection established\r\n'
+ 'Proxy-agent: '+proxy_agent+'\r\n'
+ '\r\n'
);
}
function deny_request(req, socket, e){
if(!e)e={errno:0,message:'Bad Request'};
socket.write( 'HTTP/1.0 '+(400+e.errno)+' '+e.message+'\r\n'
+ 'Proxy-agent: '+proxy_agent+'\r\n'
+ '\r\n'
+ (400+e.errno)+' '+e.message+'\r\n');
socket.end();
log(e.message);
}
function http_handler(req, res){
if (!request_allowed(req, res.socket)) return;
var encoding = responseContentType(req.headers);
var strUrl = ''+req.url;
strUrl = (strUrl.slice(0,1)=='/')?strUrl.slice(1):strUrl;
var Url = url.parse(strUrl);
var options = {
host: Url.hostname || Url.host || req.headers.host || '',
port: Url.port || 80,
path: (Url.pathname || '/') + (Url.search || ''),
method: req.method
};
if(run_as == 'reverse'){ // 'forward' or 'reverse'
if(options.path.slice(0,1)!='/' && Url.protocol != 'http:')options.path = '/'+options.path;
if(!Url.protocol)Url.protocol = 'http:';
}
if(options.host == '' || Url.protocol != 'http:'){
deny_request(req, res.socket)
return;
};
if (use_http_reverse_service) {
var reverse_host = reverse_server(options.host);
if( options.host != reverse_host){
log('Redirecting ' + Url.protocol + '//' + options.host + options.path + ' to ' + Url.protocol + '//' + reverse_host + options.path);
options.host = reverse_host;
}
}
var proxy = http;
// create request object and response callback
var proxyReq = proxy.request(options, function(proxyRes){
log('Connection established: '+req.method+' '+req.url)
var encoding = responseContentType(proxyRes.headers);
// return status code and headers to the client
res.statusCode = proxyRes.statusCode;
for(var header in proxyRes.headers){
res.setHeader(header, proxyRes.headers[header]);
};
// return data chunk to the cliente
proxyRes.on('data', function (chunk) {
res.write(chunk, encoding);
});
// close client connection
proxyRes.on('end',function(){
res.end('');
});
});
// send headers to the server
var headers = cleanHeaders(req.headers)
for(var header in headers){
proxyReq.setHeader(header, headers[header]);
};
// send data chunk to the server
req.on('data', function(chunk){
try {
proxyReq.write(chunk, encoding);
}catch(e){}
});
// close request object
req.on('end', function(){
try {
proxyReq.end();
}catch(e){}
});
proxyReq.on('error',function(e){
try {
deny_request(req, res.socket, e);
}catch(e){
try {
proxyReq.end();
res.end('');
}catch(e){}
}
});
};
function upgrade_handler(req, socket, upgradeHead) {
if (!request_allowed(req, socket)) return;
if (!(/CONNECT|GET/).test(req.method)) { // CONNECT binary (HTTPS/WebSocket as Forward-Proxy) , GET WebSocket (as Reverse-Proxy)
msg = "Request " + req.method + " is not supported on this proxy";
deny_request(req, socket, {errno:0,message:msg});
log(msg);
return;
}
if(run_as = 'forward'){ // 'forward' or 'reverse'
if ((/GET/).test(req.method)) { // this is required for reverse-proxy implementation
msg = "Request " + req.method + " is not supported on this proxy";
deny_request(req, socket, {errno:0,message:msg});
log(msg);
return;
}
}
if(run_as = 'reverse'){ // 'forward' or 'reverse'
if ((/GET/).test(req.method)) { // this is required for reverse-proxy implementation
//websocket_handler(req, socket); // not implemented
// this "deny_request" call should deleted after the WebSocket implementation
msg = "Request " + req.method + " is not supported on this proxy";
deny_request(req, socket, {errno:0,message:msg});
log(msg);
// this "deny_request" call should deleted after the WebSocket implementation
return;
}
}
var strUrl = ''+req.url;
strUrl = (strUrl.slice(0,1)=='/')?strUrl.slice(1):strUrl;
if(strUrl.slice(0,8)!='https://') strUrl = 'https://'+strUrl;
var Url = url.parse(strUrl);
var options = {
host: Url.hostname || Url.host || '',
port: Url.port || 443
};
if(options.host == ''){
deny_request(req, socket)
return;
};
var proxySocket = new net.Socket();
proxySocket.connect(options.port, options.host, function() {
log('Connection established: '+req.method+' '+req.url)
connection_established(req, socket);
proxySocket.on('data', function (data) {
try { socket.write(data); } catch(e){
try { proxySocket.end(); } catch(e){}
try { socket.end(); } catch(e){}
}
});
proxySocket.on('end', function () {
socket.end();
});
});
proxySocket.on('error', function () {
try { proxySocket.end(); } catch(e){}
try { socket.end(); } catch(e){}
});
proxySocket.on('timeout', function () {
try { proxySocket.end(); } catch(e){}
try { socket.end(); } catch(e){}
});
socket.on('data', function(data){
try { proxySocket.write(data); } catch(e){
try { proxySocket.end(); } catch(e){}
try { socket.end(); } catch(e){}
}
});
socket.on('end', function(){
proxySocket.end();
});
socket.on('error', function () {
try { proxySocket.end(); } catch(e){}
try { socket.end(); } catch(e){}
});
socket.on('timeout', function () {
try { proxySocket.end(); } catch(e){}
try { socket.end(); } catch(e){}
});
};
function error_handler(e){
log("Got error: " + e.message);
// Attach your code here
}
var server = http.createServer()
server.addListener('error', error_handler);
server.addListener("request", http_handler);
if ( use_CONNECT_support ) server.addListener("upgrade", upgrade_handler);
server.listen(PORT /*,'localhost'*/); // localhost should be the ip_address to listen (like 10.0.0.1)
if (use_auth_control) update_user_list();
if (use_blacklist_control) update_blacklist();
if (use_iplist_control) update_iplist();
if (use_portlist_control) update_portlist();
if (use_http_reverse_service) update_reverse_service();
Jump to Line
Something went wrong with that request. Please try again.