UNPKG

vpn.email.client

Version:
475 lines (355 loc) 12.3 kB
import * as Net from 'net' import * as Rfc1928 from './rfc1928' import * as shortID from 'shortid' import * as Async from 'async' import ImapCluster from './imapCluster' import * as Compress from './compress' import mainWrite from './mainWrite' import * as HttpProxy from './httpProxy' import * as fs from 'fs' import * as hostList from './hostList' //import TransfromFromServer from './transformServerBack' import * as Stream from 'stream' 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: 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 * */ export default class localProxy { private bufferPool: Map < number, Compress.packetBuffer > = new Map () private save = ( data ) => { const _data: VE_IPptpStream = { 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 ) } private connectStat2_after ( retBuffer: Rfc1928.Requests, cmd: string ) { if ( this.keep ) { this.socket.once ( 'data', ( data: Buffer ) => { 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: Buffer ) => { if ( !data1 ) { this.mainWrite = new mainWrite ( 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 ( 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 () } private connectStat2 ( data: Buffer ) { //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 ) } private proxyCacheSave ( data: HttpProxy.httpProxy, 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 ) }) }) } private findIpInWhiteList ( ip: string ) { if ( ! this.whiteIpList || ! this.whiteIpList.length ) return false return this.whiteIpList.find ( n => { return ip === n }) } private connectToLoginSever () { } private httpProxy ( data: Buffer ) { 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: Buffer ) => { this.mainWrite = new mainWrite ( 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 ( 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 () } private connectStat1 ( data: Buffer ) { 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: Buffer ) => { return this.connectStat2 ( chunk ) }) return this.socket.write ( res_NO_AUTHENTICATION_REQUIRED ) default: return this.httpProxy ( data ) } } private saveEndToServer () { if ( !this.keep || this.ending ) return this.ending = true this.ending = true if ( this.mainWrite && this.mainWrite.sendEnd ) this.mainWrite.sendEnd () } private endConnect ( data: string|Buffer = null ) { this.saveEndToServer () if ( this.socket && this.socket.writable ) { data ? this.socket.end ( data ) : this.socket.end () } return this.endCallBack () } public host: string; public ATYP: number; public port: number; public cmd: number; private ending = false private count1 = 0 private serial = 0 private mainWrite: mainWrite public id = shortID.generate() // cache private savePath = null private keep = false private 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 ) } constructor ( private masterPassword: string, public socket: Net.Socket, public ImapCluster: ImapCluster, private whiteIpList: string[], private cacheKeepTime: number, private hostLocalIp: string, private socketPool: Map < string, localProxy >, private blackIpList: Map < string, number >, private hostList: hostList.hostList, private endCallBack: () => void ) { socket.once ( 'data', ( data: Buffer ) => { 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 () }) } public getData ( data: Compress.packetBuffer ) { 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 () } public bufferToCache ( data: Buffer ) { if ( ! this.savePath ) { return } const path = cachePath + '/' + this.savePath const openFs = fs.appendFile ( path, data, err => {}) } }