vpn.email.client
Version:
Vpn.Email client IMAP core
368 lines (356 loc) • 14.1 kB
JavaScript
"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;