diff --git a/CHANGELOG b/CHANGELOG index 55931ef..9b1cf47 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +31 Mar 2015: anyproxy 3.2.5: + + * optimize https features in windows + * add switch to mute the console + 20 Mar 2015: anyproxy 3.2.5: * bugfix for internal https server diff --git a/README.md b/README.md index 2be67d5..646d330 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Quick Start ### step 3 - launch web interface -* visit [http://127.0.0.1:8002](http://127.0.0.1:8002) ,you will see realtime requests +* visit [http://127.0.0.1:8002](http://127.0.0.1:8002) ,you will see realtime requests in your browser * be sure to use web interface with modern browsers ![screenshot](http://gtms01.alicdn.com/tps/i1/TB1IdgqGXXXXXa9apXXLExM2pXX-854-480.gif) @@ -70,10 +70,9 @@ Https features ---------------- After configuring rootCA, AnyProxy could help to decrypt https requests, whose approach is also called Man-In-The-Middle(MITM). -A guide about configuring https features is here : +A guide about configuring https features is here : [https://github.com/alibaba/anyproxy/wiki/How-to-config-https-proxy](https://github.com/alibaba/anyproxy/wiki/How-to-config-https-proxy) -* Eng : [https://github.com/alibaba/anyproxy/wiki/How-to-config-https-proxy](https://github.com/alibaba/anyproxy/wiki/How-to-config-https-proxy) -* 中文教程 : [https://github.com/alibaba/anyproxy/wiki/HTTPS%E7%9B%B8%E5%85%B3%E6%95%99%E7%A8%8B](https://github.com/alibaba/anyproxy/wiki/HTTPS%E7%9B%B8%E5%85%B3%E6%95%99%E7%A8%8B) +HTTPS配置中文教程 : [https://github.com/alibaba/anyproxy/wiki/HTTPS%E7%9B%B8%E5%85%B3%E6%95%99%E7%A8%8B](https://github.com/alibaba/anyproxy/wiki/HTTPS%E7%9B%B8%E5%85%B3%E6%95%99%E7%A8%8B) Others diff --git a/bin.js b/bin.js index 8faad7d..21e6d60 100644 --- a/bin.js +++ b/bin.js @@ -5,7 +5,8 @@ var program = require('commander'), fs = require("fs"), path = require("path"), npm = require("npm"), - packageInfo = require("./package.json"); + packageInfo = require("./package.json"), + logUtil = require("./lib/log"); program .version(packageInfo.version) @@ -17,6 +18,7 @@ program .option('-g, --root [value]', 'generate root CA') .option('-l, --throttle [value]', 'throttle speed in kb/s (kbyte / sec)') .option('-i, --intercept', 'intercept(decrypt) https requests when root CA exists') + .option('-s, --silent', 'do not print anything into terminal') .option('-c, --clear', 'clear all the tmp certificates') .option('install', 'install node modules') .parse(process.argv); @@ -31,32 +33,36 @@ if(program.clear){ require("./lib/certMgr").generateRootCA(function(){ process.exit(0); }); + }else if(program.install){ npm.load({ "prefix": process.env.NODE_PATH + '/anyproxy/' }, function (er) { npm.commands.install(program.args || [], function (er, data) { if(er)throw er; - }) - npm.registry.log.on("log", function (message) { - }) + }); + npm.registry.log.on("log", function (message) {}); }); }else{ var proxy = require("./proxy.js"); var ruleModule; + if(program.silent){ + logUtil.setPrintStatus(false); + } + if(program.rule){ var ruleFilePath = path.resolve(process.cwd(),program.rule); try{ if(fs.existsSync(ruleFilePath)){ ruleModule = require(ruleFilePath); - console.log("rule file loaded :" + ruleFilePath); + logUtil.printLog("rule file loaded :" + ruleFilePath); }else{ - console.log(ruleFilePath); - console.log(color.red("can not find rule file")); + var logText = color.red("can not find rule file at " + ruleFilePath); + logUtil.printLog(logText, logUtil.T_ERR); } }catch(e){ - console.log("failed to load rule file :" + e.toString()); + logUtil.printLog("failed to load rule file :" + e.toString(), logUtil.T_ERR); } } @@ -68,6 +74,7 @@ if(program.clear){ throttle : program.throttle, rule : ruleModule, disableWebInterface : false, - interceptHttps : program.intercept + interceptHttps : program.intercept, + silent : program.silent }); } diff --git a/cert/gen-cer.cmd b/cert/gen-cer.cmd new file mode 100644 index 0000000..d3a6f83 --- /dev/null +++ b/cert/gen-cer.cmd @@ -0,0 +1,27 @@ +@echo off + +set domain=%1 +set outputPath=%2 +set commonname=%domain% + +set country=ZH +set state=Shanghai +set locality=Shanghai +set organization=a.com +set organizationalunit=IT +set email=a@b.com +set password=a + +echo Generating key request for %domain% + +openssl genrsa -passout pass:%password% -out %domain%.key 2048 + + +echo Removing passphrase from key +openssl rsa -in %domain%.key -passin pass:%password% -out %domain%.key + +echo Creating CSR +openssl req -new -key %domain%.key -out %domain%.csr -passin pass:%password% -subj /C=%country%/ST=%state%/L=%locality%/O=%organization%/OU=%organizationalunit%/CN=%commonname%/emailAddress=%email% + +openssl x509 -req -days 3650 -in %domain%.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out %domain%.crt +echo Finished diff --git a/cert/gen-rootCA.cmd b/cert/gen-rootCA.cmd new file mode 100644 index 0000000..170adc0 --- /dev/null +++ b/cert/gen-rootCA.cmd @@ -0,0 +1,12 @@ +@echo off + +openssl genrsa -out rootCA.key 2048 +openssl req -x509 -new -nodes -key rootCA.key -days 3650 -out rootCA.crt -subj "/C=CN/ST=SH/L=SH/O=AnyProxy/OU=Section/CN=Anyproxy SSL Proxying/emailAddress=AnyProxy@AnyProxy" +echo ============= +echo rootCA generated at : +echo %cd% +echo ============= + +start . + +rem exit 0 diff --git a/lib/certMgr.js b/lib/certMgr.js index d9519e6..267859e 100644 --- a/lib/certMgr.js +++ b/lib/certMgr.js @@ -6,23 +6,24 @@ var exec = require('child_process').exec, color = require('colorful'), readline = require('readline'), util = require('./util'), + logUtil = require("./log"), asyncTask = require("async-task-mgr"); -//TODO : unstable in windows -var certDir = path.join(util.getUserHome(),"/.anyproxy_certs/"), +var isWin = /^win/.test(process.platform); + certDir = path.join(util.getUserHome(),"/.anyproxy_certs/"), cmdDir = path.join(__dirname,"..","./cert/"), - cmd_genRoot = path.join(cmdDir,"./gen-rootCA"), - cmd_genCert = path.join(cmdDir,"./gen-cer"), + cmd_genRoot = isWin ? path.join(cmdDir,"./gen-rootCA.cmd") : path.join(cmdDir,"./gen-rootCA"), + cmd_genCert = isWin ? path.join(cmdDir,"./gen-cer.cmd") : path.join(cmdDir,"./gen-cer"), createCertTaskMgr = new asyncTask(); if(!fs.existsSync(certDir)){ try{ - fs.mkdirSync(certDir,0777); //may fail in windows + fs.mkdirSync(certDir,0777); }catch(e){ - console.log("==========="); - console.log("failed to create cert dir ,please create one by yourself - " + certDir); - console.log("this error will not block main thread unless you use https-related features in anyproxy"); - console.log("==========="); + logUtil.printLog("===========", logUtil.T_ERR); + logUtil.printLog("failed to create cert dir ,please create one by yourself - " + certDir, logUtil.T_ERR); + logUtil.printLog("this error will not block main thread unless you use https-related features in anyproxy", logUtil.T_ERR); + logUtil.printLog("===========", logUtil.T_ERR); } } @@ -62,16 +63,21 @@ function createCert(hostname,callback){ callback && callback(new Error("error when generating certificate"),null); }else{ var tipText = "certificate created for __HOST".replace(/__HOST/,hostname); - console.log(color.yellow(color.bold("[internal https]")) + color.yellow(tipText)); + logUtil.printLog(color.yellow(color.bold("[internal https]")) + color.yellow(tipText)) ; callback(null); } }); } function clearCerts(cb){ - exec("rm *.key *.csr *.crt",{cwd : certDir},cb); + if(isWin){ + exec("del * /q",{cwd : certDir},cb); + }else{ + exec("rm *.key *.csr *.crt",{cwd : certDir},cb); + } } + function isRootCAFileExists(){ var crtFile = path.join(certDir,"rootCA.crt"), keyFile = path.join(certDir,"rootCA.key"); @@ -81,17 +87,18 @@ function isRootCAFileExists(){ function checkRootCA(){ if(!isRootCAFileExists()){ - console.log(color.red("can not find rootCA.crt or rootCA.key")); - console.log(color.red("you may generate one by the following methods")); - console.log(color.red("\twhen using globally : anyproxy --root")); - console.log(color.red("\twhen using as a module : require(\"anyproxy\").generateRootCA();")); + logUtil.printLog(color.red("can not find rootCA.crt or rootCA.key"), logUtil.T_ERR); + logUtil.printLog(color.red("you may generate one by the following methods"), logUtil.T_ERR); + logUtil.printLog(color.red("\twhen using globally : anyproxy --root"), logUtil.T_ERR); + logUtil.printLog(color.red("\twhen using as a module : require(\"anyproxy\").generateRootCA();"), logUtil.T_ERR); + logUtil.printLog(color.red("\tmore info : https://github.com/alibaba/anyproxy/wiki/How-to-config-https-proxy"), logUtil.T_ERR); process.exit(0); } } function generateRootCA(){ if(isRootCAFileExists()){ - console.log(color.yellow("rootCA exists at " + certDir)); + logUtil.printLog(color.yellow("rootCA exists at " + certDir)); var rl = readline.createInterface({ input : process.stdin, output: process.stdout @@ -101,7 +108,7 @@ function generateRootCA(){ if(/yes/i.test(answer)){ startGenerating(); }else{ - console.log("will not generate a new one"); + logUtil.printLog("will not generate a new one"); process.exit(0); } @@ -114,18 +121,20 @@ function generateRootCA(){ function startGenerating(){ //clear old certs clearCerts(function(){ - console.log(color.green("temp certs cleared")); + logUtil.printLog(color.green("temp certs cleared")); var spawnSteam = spawn(cmd_genRoot,['.'],{cwd:certDir,stdio: 'inherit'}); + spawnSteam.on('close', function (code) { + if(code == 0){ - console.log(color.green("rootCA generated")); - console.log(color.green(color.bold("please trust the rootCA.crt in " + certDir))); - console.log(color.green(color.bold("or you may get it via anyproxy webinterface"))); - process.exit(0); + logUtil.printLog(color.green("rootCA generated")); + logUtil.printLog(color.green(color.bold("please trust the rootCA.crt in " + certDir))); + logUtil.printLog(color.green(color.bold("or you may get it via anyproxy webinterface"))); }else{ - console.log(color.red("fail to generate root CA")); + logUtil.printLog(color.red("fail to generate root CA"),logUtil.T_ERR); } + process.exit(0); }); }); diff --git a/lib/httpsServerMgr.js b/lib/httpsServerMgr.js index 65f054c..a29d808 100644 --- a/lib/httpsServerMgr.js +++ b/lib/httpsServerMgr.js @@ -9,6 +9,7 @@ var getPort = require('./getPort'), crypto = require('crypto'), color = require('colorful'), certMgr = require("./certMgr"), + logUtil = require("./log"), asyncTask = require("async-task-mgr"); var createSecureContext = tls.createSecureContext || crypto.createSecureContext; @@ -43,11 +44,11 @@ function SNIPrepareCert(serverName,SNICallback){ ],function(err,result){ if(!err){ var tipText = "proxy server for __NAME established".replace("__NAME",serverName); - console.log(color.yellow(color.bold("[internal https]")) + color.yellow(tipText)); + logUtil.printLog(color.yellow(color.bold("[internal https]")) + color.yellow(tipText)); SNICallback(null,ctx); }else{ - console.log("err occurred when prepare certs for SNI - " + err); - console.log("you may upgrade your Node.js to the lastest version"); + logUtil.printLog("err occurred when prepare certs for SNI - " + err, logUtil.T_ERR); + logUtil.printLog("you may upgrade your Node.js to the lastest version", logUtil.T_ERR); } }); } diff --git a/lib/log.js b/lib/log.js new file mode 100644 index 0000000..4fc271d --- /dev/null +++ b/lib/log.js @@ -0,0 +1,17 @@ +var ifPrint = true; + +function setPrintStatus(status){ + ifPrint = !!status; +} + +function printLog(content,type){ + if(!ifPrint) return; + + var tip = content; + console.log(tip); +} + +module.exports.printLog = printLog; +module.exports.setPrintStatus = setPrintStatus; +module.exports.T_TIP = 0; +module.exports.T_ERR = 1; \ No newline at end of file diff --git a/lib/recorder.js b/lib/recorder.js index 99b3257..5c5f444 100644 --- a/lib/recorder.js +++ b/lib/recorder.js @@ -3,7 +3,8 @@ var zlib = require('zlib'), Datastore = require('nedb'), util = require("util"), fs = require("fs"), - events = require('events'); + events = require('events'), + logUtil = require("./log"); //option.filename function Recorder(option){ @@ -24,11 +25,11 @@ function Recorder(option){ autoload :true }); db.persistence.setAutocompactionInterval(5001); - console.log("db file : " + option.filename); + logUtil.printLog("db file : " + option.filename); }catch(e){ - console.log(e); - console.log("Failed to load on-disk db file. Will use in-meomory db instead."); + logUtil.printLog(e, logUtil.T_ERR); + logUtil.printLog("Failed to load on-disk db file. Will use in-meomory db instead.", logUtil.T_ERR); db = new Datastore(); } diff --git a/lib/requestHandler.js b/lib/requestHandler.js index 8770cd5..304a956 100644 --- a/lib/requestHandler.js +++ b/lib/requestHandler.js @@ -11,6 +11,7 @@ var http = require("http"), util = require("./util"), getPort = require("./getPort"), Stream = require("stream"), + logUtil = require("./log"), httpsServerMgr = require("./httpsServerMgr"); var defaultRule = require("./rule_default.js"), @@ -40,7 +41,7 @@ function userRequestHandler(req,userRes){ resourceInfoId = GLOBAL.recorder.appendRecord(resourceInfo); } - console.log(color.green("\nreceived request to : " + host + path)); + logUtil.printLog(color.green("\nreceived request to : " + host + path)); //get request body and route to local or remote async.series([fetchReqData,routeReq],function(){}); @@ -63,10 +64,10 @@ function userRequestHandler(req,userRes){ //route to dealing function function routeReq(callback){ if(userRule.shouldUseLocalResponse(req,reqData)){ - console.log("==>use local rules"); + logUtil.printLog("==>use local rules"); dealWithLocalResponse(callback); }else{ - console.log("==>will forward to real server by proxy"); + logUtil.printLog("==>will forward to real server by proxy"); dealWithRemoteResonse(callback); } } @@ -163,7 +164,7 @@ function userRequestHandler(req,userRes){ //get custom response },function(callback){ if(userRule.replaceServerResData){ - console.log(color.red("replaceServerResData is deprecated, and will be unavilable soon. Use replaceServerResDataAsync instead.")); + logUtil.printLog(color.red("replaceServerResData is deprecated, and will be unavilable soon. Use replaceServerResDataAsync instead."), logUtil.T_ERR); serverResData = userRule.replaceServerResData(req,res,serverResData) || serverResData; callback(); }else if(userRule.replaceServerResDataAsync){ @@ -225,13 +226,13 @@ function userRequestHandler(req,userRes){ }); res.on('error',function(error){ - console.log('error',error); + logUtil.printLog('error' + error, logUtil.T_ERR); }); }); proxyReq.on("error",function(e){ - console.log("err with request :" + e + " " + req.url); + logUtil.printLog("err with request :" + e + " " + req.url, logUtil.T_ERR); userRes.end(); }); @@ -252,11 +253,11 @@ function connectReqHandler(req, socket, head){ shouldIntercept = false; // TODO : a more general solution? } - console.log(color.green("\nreceived https CONNECT request " + host)); + logUtil.printLog(color.green("\nreceived https CONNECT request " + host)); if(shouldIntercept){ - console.log("==>will forward to local https server"); + logUtil.printLog("==>will forward to local https server"); }else{ - console.log("==>will bypass the man-in-the-middle proxy"); + logUtil.printLog("==>will bypass the man-in-the-middle proxy"); } //record @@ -334,10 +335,10 @@ function connectReqHandler(req, socket, head){ }); conn.on("error",function(e){ - console.log("err when connect to %j, %j" , host , e); + logUtil.printLog("err when connect to + " + host + " , " + e, logUtil.T_ERR); }); }catch(e){ - console.log("err when connect to remote https server (__host)".replace(/__host/,host)); + logUtil.printLog("err when connect to remote https server (__host)".replace(/__host/,host), logUtil.T_ERR); } //update record @@ -354,7 +355,7 @@ function connectReqHandler(req, socket, head){ } ],function(err,result){ if(err){ - console.log("err " + err); + logUtil.printLog("err " + err, logUtil.T_ERR); throw err; } }); @@ -381,13 +382,13 @@ function setRules(newRule){ } if('function' == typeof(userRule.summary)){ functions.push(function(cb){ - console.log(userRule.summary()); + logUtil.printLog(userRule.summary()); cb(null); }); } async.series(functions,function(errors,result){ if(!errors){ - console.log(color.green('Anyproxy rules initialize finished, have fun!')); + logUtil.printLog(color.green('Anyproxy rules initialize finished, have fun!')); } }); diff --git a/package.json b/package.json index b37fb32..9517d35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "anyproxy", - "version": "3.2.5", + "version": "3.3.0", "description": "A fully configurable proxy in NodeJS, which can handle HTTPS requests perfectly.", "main": "proxy.js", "bin": { diff --git a/proxy.js b/proxy.js index e26cd0c..3473e0e 100644 --- a/proxy.js +++ b/proxy.js @@ -3,27 +3,28 @@ try{ }catch(e){} var http = require('http'), - https = require('https'), - fs = require('fs'), - async = require("async"), - url = require('url'), - program = require('commander'), - color = require('colorful'), - certMgr = require("./lib/certMgr"), - getPort = require("./lib/getPort"), - requestHandler = require("./lib/requestHandler"), - Recorder = require("./lib/recorder"), - inherits = require("util").inherits, - util = require("./lib/util"), - path = require("path"), - juicer = require('juicer'), - events = require("events"), - express = require("express"), - ip = require("ip"), - fork = require("child_process").fork, - ThrottleGroup = require("stream-throttle").ThrottleGroup, - iconv = require('iconv-lite'), - Buffer = require('buffer').Buffer; + https = require('https'), + fs = require('fs'), + async = require("async"), + url = require('url'), + program = require('commander'), + color = require('colorful'), + certMgr = require("./lib/certMgr"), + getPort = require("./lib/getPort"), + requestHandler = require("./lib/requestHandler"), + Recorder = require("./lib/recorder"), + logUtil = require("./lib/log"), + inherits = require("util").inherits, + util = require("./lib/util"), + path = require("path"), + juicer = require('juicer'), + events = require("events"), + express = require("express"), + ip = require("ip"), + fork = require("child_process").fork, + ThrottleGroup = require("stream-throttle").ThrottleGroup, + iconv = require('iconv-lite'), + Buffer = require('buffer').Buffer; var T_TYPE_HTTP = 0, T_TYPE_HTTPS = 1, @@ -50,7 +51,7 @@ try{ } }catch(e){ if(e){ - console.log("error" + e); + logUtil.printLog("error" + e, logUtil.T_ERR); throw e; } } @@ -66,6 +67,7 @@ try{ //option.dbFile : null(default) //option.throttle : null(default) //option.disableWebInterface +//option.silent : false(default) //option.interceptHttps ,internal param for https function proxyServer(option){ option = option || {}; @@ -78,7 +80,12 @@ function proxyServer(option){ proxyWebPort = option.webPort || DEFAULT_WEB_PORT, //port for web interface socketPort = option.socketPort || DEFAULT_WEBSOCKET_PORT, //port for websocket proxyConfigPort = option.webConfigPort || DEFAULT_CONFIG_PORT, //port to ui config server - disableWebInterface = !!option.disableWebInterface ; + disableWebInterface = !!option.disableWebInterface, + ifSilent = !!option.silent; + + if(ifSilent){ + logUtil.setPrintStatus(false); + } if(option.dbFile){ GLOBAL.recorder = new Recorder({filename: option.dbFile}); @@ -91,7 +98,7 @@ function proxyServer(option){ } if(option.throttle){ - console.log("throttle :" + option.throttle + "kb/s"); + logUtil.printLog("throttle :" + option.throttle + "kb/s"); GLOBAL._throttle = new ThrottleGroup({rate: 1024 * parseInt(option.throttle) }); // rate - byte/sec } @@ -132,7 +139,7 @@ function proxyServer(option){ //start web interface function(callback){ if(disableWebInterface){ - console.log('web interface is disabled'); + logUtil.printLog('web interface is disabled'); callback(null); }else{ @@ -199,13 +206,13 @@ function proxyServer(option){ //kill web server when father process exits process.on("exit",function(code){ child_webServer.kill(); - console.log('AnyProxy is about to exit with code:', code); + logUtil.printLog('AnyProxy is about to exit with code: ' + code, logUtil.T_ERR); process.exit(); }); process.on("uncaughtException",function(err){ child_webServer.kill(); - console.log('Caught exception: ' + err); + logUtil.printLog('Caught exception: ' + err, logUtil.T_ERR); process.exit(); }); @@ -222,10 +229,10 @@ function proxyServer(option){ var tipText,webUrl; webUrl = "http://" + ip.address() + ":" + proxyWebPort +"/"; tipText = "GUI interface started at : " + webUrl; - console.log(color.green(tipText)); + logUtil.printLog(color.green(tipText)); // tipText = "[alpha]qr code to for iOS client: " + webUrl + "qr"; - // console.log(color.green(tipText)); + // logUtil.printLog(color.green(tipText)); callback(null); } } @@ -235,18 +242,18 @@ function proxyServer(option){ function(err,result){ if(!err){ var tipText = (proxyType == T_TYPE_HTTP ? "Http" : "Https") + " proxy started at " + color.bold(ip.address() + ":" + proxyPort); - console.log(color.green(tipText)); + logUtil.printLog(color.green(tipText)); }else{ var tipText = "err when start proxy server :("; - console.log(color.red(tipText)); - console.log(err); + logUtil.printLog(color.red(tipText), logUtil.T_ERR); + logUtil.printLog(err, logUtil.T_ERR); } } ); self.close = function(){ self.httpProxyServer && self.httpProxyServer.close(); - console.log(color.green("server closed :" + proxyHost + ":" + proxyPort)); + logUtil.printLog(color.green("server closed :" + proxyHost + ":" + proxyPort)); } } @@ -257,7 +264,7 @@ function UIConfigServer(port){ var app = express(), customerRule = { summary: function(){ - console.log("replace some response with local response"); + logUtil.printLog("replace some response with local response"); return "replace some response with local response"; } }, diff --git a/webServer.js b/webServer.js index b39705e..b8f5f2a 100644 --- a/webServer.js +++ b/webServer.js @@ -8,6 +8,7 @@ var express = require("express"), inherits = require("util").inherits, ent = require("ent"), qrCode = require('qrcode-npm'), + logUtil = require("./lib/log"), WebSocketServer = require('ws').Server; function proxyWebServer(port,webSocketPort,proxyConfigPort,ruleSummary,ipAddress,menuListStr){ @@ -118,7 +119,7 @@ function proxyWebServer(port,webSocketPort,proxyConfigPort,ruleSummary,ipAddress try{ this.clients[i].send(data); }catch(e){ - console.log("websocket failed to send data, " + e); + logUtil.printLog("websocket failed to send data, " + e, logUtil.T_ERR); } } };