vpn.email.client
Version:
Vpn.Email client IMAP core
475 lines (355 loc) • 12.3 kB
text/typescript
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 => {})
}
}