UNPKG

vpn.email.client

Version:
368 lines (356 loc) 14.1 kB
"use strict"; const Rfc1928 = require("./rfc1928"); const shortID = require("shortid"); const Compress = require("./compress"); const mainWrite_1 = require("./mainWrite"); const HttpProxy = require("./httpProxy"); const fs = require("fs"); const res_NO_AUTHENTICATION_REQUIRED = new Buffer('0500', 'hex'); const res_AUTHENTICATION_REQUIRED = new Buffer('0502', 'hex'); const res_ENDSOCKET_REQUIRED = new Buffer('05ff', 'hex'); const res_NO_ACCEPTABLE_METHODS = new Buffer('05ff', 'hex'); const respon_se = new Buffer('05000001000000000000', 'hex'); const body_401 = '<!DOCTYPE html><html>Error user/password</html>'; const body_403 = '<!DOCTYPE html><html><p>這個域名被代理服務器列入黑名單</p><p>This domain in proxy blacklist.</p><p>このサイドはプロクシーの禁止リストにあります</p></html>'; const cachePath = '.cache/'; const HTTP_407 = `HTTP/1.1 407 Proxy Authentication Required Proxy-Authenticate: Basic realm="Vpn.Email login" Content-Length: 0 Connection: close Proxy-Connection: close Content-Type: text/html; charset=UTF-8 Cache-Control: private, max-age=0 `; const HTTP_401 = `HTTP/1.1 401 Unauthorized Proxy-Authenticate: Basic realm="Vpn.Email user/password error!" Content-Length: ${body_401.length} Connection: close Proxy-Connection: close ${body_401} `; const HTTP_407_LOGIN_ERR = `HTTP/1.1 407 Proxy Authentication Required Proxy-Authenticate: Basic realm="Vpn.Email login error" Content-Length: 0 Connection: close Proxy-Connection: close Content-Type: text/html; charset=UTF-8 Cache-Control: private, max-age=0 `; const HTTP_PROXY_200 = `HTTP/1.1 200 Connection Established Content-Type: text/html; charset=UTF-8 `; const HTTP_403 = `HTTP/1.1 403 Forbidden Content-Type: text/html; charset=UTF-8 Connection: close Proxy-Connection: close Content-Length: 300 ${body_403} `; const IsSslConnect = (buffer) => { const kk = buffer.toString('hex', 0, 4); return /^1603(01|02|03|00)|^80..0103|^(14|15|17)03(00|01)/.test(kk); }; /** * buffer format * bit [0] = 0 next is buffer * bit [0] = 1 COMMAND * bit [0] = 2 socket.end() * bit [32] = serial number (must) * bit [5] = 36 bit uuid 'uft8 * bit [41] = buffer * */ class localProxy { constructor(masterPassword, socket, ImapCluster, whiteIpList, cacheKeepTime, hostLocalIp, socketPool, blackIpList, hostList, endCallBack) { this.masterPassword = masterPassword; this.socket = socket; this.ImapCluster = ImapCluster; this.whiteIpList = whiteIpList; this.cacheKeepTime = cacheKeepTime; this.hostLocalIp = hostLocalIp; this.socketPool = socketPool; this.blackIpList = blackIpList; this.hostList = hostList; this.endCallBack = endCallBack; this.bufferPool = new Map(); this.save = (data) => { const _data = { type: 'proxyTcp', host: this.host, port: this.port, buffer: data.toString('base64'), ATYP: this.ATYP, cmd: this.cmd, }; const _dataJson = new Buffer(JSON.stringify(_data), 'utf8'); //const uuu = Compress.encrypt ( _dataJson, this.masterPassword ) const buffer = Compress.packetBuffer(1, 0, this.id, _dataJson); this.socketPool.set(this.id, this); return this.ImapCluster.pushMainData(buffer); }; this.ending = false; this.count1 = 0; this.serial = 0; this.id = shortID.generate(); // cache this.savePath = null; this.keep = false; socket.once('data', (data) => { return this.connectStat1(data); }); socket.once('end', () => { if (!this.ending) return this.endConnect(); }); socket.once('error', err => { console.log(this.id, 'localProxyServer socket.once error:', err); this.endConnect(); }); } connectStat2_after(retBuffer, cmd) { if (this.keep) { this.socket.once('data', (data) => { const header = new HttpProxy.httpProxy(data); if (this.hostList.putListSock5(this.host, IsSslConnect ? null : header)) return this.endConnect(HTTP_403); if (header.cachePath && this.cacheKeepTime) { return this.proxyCacheSave(header, (err, data1) => { if (!data1) { this.mainWrite = new mainWrite_1.default(this.ImapCluster, this.masterPassword, IsSslConnect(data), this.id, 0); this.socket.pipe(this.mainWrite); if (this.savePath) return this.save(header.BufferWithOutKeepAlife); return this.save(data); } this.ending = true; return this.endConnect(data1); }); } this.mainWrite = new mainWrite_1.default(this.ImapCluster, this.masterPassword, IsSslConnect(data), this.id, 0); this.socket.pipe(this.mainWrite); return this.save(data); }); return this.socket.write(retBuffer.buffer); } this.socket.write(retBuffer.buffer); console.log('====================> cmd is not Rfc1928.CMD.CONNECT close connect', cmd); return this.socket.end(); } connectStat2(data) { //this.socket.pause() const req = new Rfc1928.Requests(data); this.ATYP = req.ATYP; this.host = req.host; this.port = req.port; this.cmd = req.cmd; let Continue = true; const localIp = this.socket.localAddress.split(':')[3]; let retBuffer = new Rfc1928.Requests(respon_se); retBuffer.ATYP_IP4Address = localIp; let cmd = ''; switch (this.cmd) { case Rfc1928.CMD.CONNECT: /* if ( this.ATYP === Rfc1928.ATYP.DOMAINNAME ) return this.checkDomain ( this.host, true, ( err, ip ) => { if ( err ) { retBuffer.REP = Rfc1928.Replies.HOST_UNREACHABLE Continue = false } console.log (`return a ip ${ip}, this.ATYP[${this.ATYP}]`) this.host = ip this.connectStat2_after ( Continue, retBuffer ) }) */ break; case Rfc1928.CMD.BIND: cmd = 'Rfc1928.CMD.BIND'; Continue = false; retBuffer.REP = Rfc1928.Replies.COMMAND_NOT_SUPPORTED_or_PROTOCOL_ERROR; case Rfc1928.CMD.UDP_ASSOCIATE: cmd = 'Rfc1928.CMD.UDP_ASSOCIATE'; Continue = false; retBuffer.REP = Rfc1928.Replies.COMMAND_NOT_SUPPORTED_or_PROTOCOL_ERROR; break; default: Continue = false; retBuffer.REP = Rfc1928.Replies.COMMAND_NOT_SUPPORTED_or_PROTOCOL_ERROR; break; } this.keep = Continue; return this.connectStat2_after(retBuffer, cmd); } proxyCacheSave(data, CallBack) { this.savePath = data.cachePath; const kkkk = data.Url.host + data.Url.href; if (!data.cachePath) { return CallBack(); } const path = cachePath + '/' + this.savePath; fs.stat(path, (err, stat) => { if (err) { return CallBack(); } const now = new Date().getTime(); const birthtime = new Date(stat.birthtime).getTime(); if (now - birthtime > this.cacheKeepTime) { return fs.unlink(path, err1 => { if (err1) { console.log('file system got error, cache save stop', err.message); } return CallBack(); }); } fs.readFile(path, (err2, data) => { if (err2) { fs.unlink(path, err3 => { }); return CallBack(); } return CallBack(null, data); }); }); } findIpInWhiteList(ip) { if (!this.whiteIpList || !this.whiteIpList.length) return false; return this.whiteIpList.find(n => { return ip === n; }); } connectToLoginSever() { } httpProxy(data) { const httpHead = new HttpProxy.httpProxy(data); const ip = this.socket.remoteAddress; let count = this.blackIpList.get(ip) || 1; if (!httpHead.isHttpRequest) { this.blackIpList.set(ip, ++count); console.log('Bad request from : ', ip, count, httpHead.command); return this.endConnect(); } if (this.hostList.putList(httpHead)) return this.endConnect(HTTP_403); const connectremote = () => { this.host = httpHead.Url.hostname; this.port = parseInt(httpHead.Url.port || httpHead.isHttps ? '443' : '80'); this.ATYP = Rfc1928.ATYP.IP_V4; this.cmd = Rfc1928.CMD.CONNECT; this.keep = true; console.log(`connect:[${httpHead.Url.protocol}]${httpHead.Url.hostname}${httpHead.Url.path}:${this.port}`); if (httpHead.isConnect) { this.socket.once('data', (data1) => { this.mainWrite = new mainWrite_1.default(this.ImapCluster, this.masterPassword, true, this.id, 0); this.socket.pipe(this.mainWrite); return this.save(data1); }); return this.socket.write(HTTP_PROXY_200); } this.mainWrite = new mainWrite_1.default(this.ImapCluster, this.masterPassword, false, this.id, 0); this.socket.pipe(this.mainWrite); /* if ( this.savePath && this.cacheKeepTime ) { return this.save ( httpHead.BufferWithOutKeepAlife ) } */ return this.save(data); }; /* if ( httpHead.cachePath && this.cacheKeepTime ) { return this.proxyCacheSave ( httpHead, ( err, data: Buffer ) => { if ( !data ) { return connectremote () } this.ending = true return this.endConnect ( data ) }) } */ return connectremote(); } connectStat1(data) { switch (data.readUInt8(0)) { case 0x4: return; case 0x5: const ip = this.socket.remoteAddress; if (!this.findIpInWhiteList(ip)) { return this.socket.end(res_ENDSOCKET_REQUIRED); } this.socket.once('data', (chunk) => { return this.connectStat2(chunk); }); return this.socket.write(res_NO_AUTHENTICATION_REQUIRED); default: return this.httpProxy(data); } } saveEndToServer() { if (!this.keep || this.ending) return this.ending = true; this.ending = true; if (this.mainWrite && this.mainWrite.sendEnd) this.mainWrite.sendEnd(); } endConnect(data = null) { this.saveEndToServer(); if (this.socket && this.socket.writable) { data ? this.socket.end(data) : this.socket.end(); } return this.endCallBack(); } checkBufferPool() { if (this.bufferPool.size == 0) return; const data = this.bufferPool.get(this.serial); if (!data) return; this.bufferPool.delete(this.serial); return this.getData(data); } getData(data) { console.log(`<===== uuid[${this.id}],serial[${data.serial}],buffer[${data.buffer.length}]`); if (data.serial > this.serial) { //console.log ( `============-----------------》[${ this.serial }] < [${ data.serial }]`) return this.bufferPool.set(data.serial, data); } if (data.serial < this.serial) { console.log(`============》[${this.id}], this.count[${this.serial}] > count[${data.serial}], buffer[${data.buffer.length}]`); return; } if (data.command == 2) { return this.endConnect(); } this.serial++; if (data.buffer && data.buffer.length) { if (data.command === 5) { if (!this.socket || !this.socket.writable) { //console.log (`[${this.id}] socker is closed!`) return this.endConnect(); } this.socket.write(data.buffer); return this.checkBufferPool(); } return Compress.decrypt(data.buffer, this.masterPassword, (err, _data) => { if (err) return console.log('Compress.decrypt error', data, data.buffer.toString('hex')); const cacheData = new Buffer(_data, 'base64'); this.bufferToCache(cacheData); if (!this.socket || !this.socket.writable) { //console.log (`[${this.id}] socker is closed!`) return this.endConnect(); } this.socket.write(cacheData); this.socket.resume(); return this.checkBufferPool(); }); } return this.checkBufferPool(); } bufferToCache(data) { if (!this.savePath) { return; } const path = cachePath + '/' + this.savePath; const openFs = fs.appendFile(path, data, err => { }); } } Object.defineProperty(exports, "__esModule", { value: true }); exports.default = localProxy;