UNPKG

@kloak-it/tq-proxy

Version:
396 lines (393 loc) 16.5 kB
"use strict"; /*! * Copyright 2017 Vpn.Email network security technology Canada Inc. All Rights Reserved. * * Vpn.Email network technolog Canada Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ssModeV3 = void 0; const net_1 = require("net"); const dns_1 = require("dns"); const stream_1 = require("stream"); const Compress = __importStar(require("./compress")); const fs_1 = require("fs"); const log_1 = require("./log"); const safe_1 = __importDefault(require("colors/safe")); const https_1 = require("https"); const MaxAllowedTimeOut = 1000 * 60 * 60; const blockHostFIleName = './blockHost.json'; const otherRespon = (body, _status) => { const Ranges = (_status === 200) ? 'Accept-Ranges: bytes\r\n' : ''; const Content = (_status === 200) ? `Content-Type: text/html; charset=utf-8\r\n` : 'Content-Type: text/html\r\n'; const headers = `Server: nginx/1.6.2\r\n` + `Date: ${new Date().toUTCString()}\r\n` + Content + `Content-Length: ${body.length}\r\n` + `Connection: keep-alive\r\n` + `Vary: Accept-Encoding\r\n` //+ `Transfer-Encoding: chunked\r\n` + '\r\n'; const status = _status === 200 ? 'HTTP/1.1 200 OK\r\n' : 'HTTP/1.1 404 Not Found\r\n'; return status + headers + body; }; const return404 = () => { const kkk = '<html>\r\n<head><title>404 Not Found</title></head>\r\n<body bgcolor="white">\r\n<center><h1>404 Not Found</h1></center>\r\n<hr><center>nginx/1.6.2</center>\r\n</body>\r\n</html>\r\n'; return otherRespon(Buffer.from(kkk), 404); }; const jsonResponse = (body) => { const headers = `Server: nginx/1.6.2\r\n` + `Date: ${new Date().toUTCString()}\r\n` + `Content-Type: application/json; charset=utf-8\r\n` + `Content-Length: ${body.length}\r\n` + `Connection: keep-alive\r\n` + `Vary: Accept-Encoding\r\n` //+ `Transfer-Encoding: chunked\r\n` + '\r\n'; const status = 'HTTP/1.1 200 OK\r\n'; return status + headers + body; }; const returnHome = () => { const kkk = `<!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html> `; return otherRespon(kkk, 200); }; const dnsLookup = (hostName, CallBack) => { console.log(`on dnsLookup: hostName = [${hostName}]`); return (0, dns_1.lookup)(hostName, { all: true }, (err, data) => { if (err) return CallBack(err); const _buf = Buffer.from(JSON.stringify(data)); return CallBack(null, _buf); }); }; const saveConnectErrorIPAddress = (blockedHost, CallBack) => { return (0, fs_1.writeFile)(blockHostFIleName, JSON.stringify(blockedHost), 'utf8', CallBack); }; class FirstConnect extends stream_1.Writable { constructor(debug, clientSocket, encrypt, decrypt, freeDomain, freeIpaddress, blockList, hostCount) { super(); this.debug = debug; this.clientSocket = clientSocket; this.encrypt = encrypt; this.decrypt = decrypt; this.freeDomain = freeDomain; this.freeIpaddress = freeIpaddress; this.blockList = blockList; this.hostCount = hostCount; this.socket = null; } _write(chunk, encode, cb) { // first time // if ( !chunk?.length ) { // return cb (new Error (`chunk EOF!`)) // } const _data = chunk.toString(); let data = null; try { data = JSON.parse(_data); } catch (e) { console.log(`FirstConnect JSON.parse [${_data}]catch error:`, e); return cb(e); } if (!this.socket) { let _isIpv4 = false; /** * Client Doname Dnslook test */ if (data.hostName?.length) { //console.log ( `data.host [${ data.host }] is free `) return dnsLookup(data.hostName, (err, data) => { if (err) { return cb(err); } this.encrypt.pipe(this.clientSocket); this.encrypt.end(data); }); } if (data.uuid) { _isIpv4 = (0, net_1.isIPv4)(data.host); this.encrypt.id = this.decrypt.id = `{ ${safe_1.default.grey(data.uuid)} } ${safe_1.default.green(this.decrypt.id)}[${_isIpv4 ? safe_1.default.green('IPv4') : safe_1.default.red('IPv6')}] ---ssl[${safe_1.default.green(data.ssl ? 'true' : 'false')}]--->[ ${safe_1.default.green(data.host + ':' + data.port)} ]`; const hostMatch = data.host + ':' + data.port; let hostCount = this.hostCount.get(hostMatch) || 0; this.hostCount.set(hostMatch, ++hostCount); const isBlacked = this.blockList.findIndex(n => n.host === data.host) > -1 ? true : false; if (isBlacked) { (0, log_1.logger)(safe_1.default.red(`*************************** [${this.decrypt.id}] in blockList STOP it!`)); return cb(new Error(`[${data.host}] in blockList`)); } this.socket = (0, net_1.connect)(data.port, data.host, () => { this.socket.pipe(this.encrypt).pipe(this.clientSocket).pipe(this.decrypt).pipe(this.socket); const buffer = Buffer.from(data.buffer, 'base64'); if (this.debug) { (0, log_1.logger)(safe_1.default.blue(`write buffer to Target ${this.decrypt.id} `)); (0, log_1.hexDebug)(buffer); } this.socket.write(buffer); return cb(); }); this.socket.once('end', () => { (0, log_1.logger)(safe_1.default.blue(`${this.decrypt.id} Target on END()`)); return this.clientSocket.destroy(); }); this.socket.once('error', err => { (0, log_1.logger)(safe_1.default.red(`FirstConnect ${this.decrypt.id} Target socket on error! [${err.message}]`)); //this.blockList.push ({ host: data.host, port: data.port, error: err.message, date: new Date().toISOString ()}) this.clientSocket.destroy(); this.end(); // return saveConnectErrorIPAddress ( this.blockList, err => { // if ( err ) { // console.log ( `saveConnectErrorIPAddress error`, err ) // } // }) }); return; } console.log(`data.uuid == null!`); return cb(new Error('unknow connect!')); } // the next stream if (this.debug) { (0, log_1.logger)(safe_1.default.blue(`FirstConnect next chunk coming:`)); (0, log_1.hexDebug)(chunk); } if (this.socket.writable) { return this.socket.write(Buffer.from(chunk.toString(), 'base64'), err => { if (err) { this.socket.once('drain', () => { return cb(); }); } return cb(); }); } return cb(new Error('FirstConnect socket.writable=false')); } } const IsBase64 = (base64String) => { // Credit: oybek https://stackoverflow.com/users/794764/oybek if (/^[a-zA-Z0-9\+/]*={0,2}$/.test(base64String)) { return true; } return false; }; class preProcessData { closeWith404() { this.debug ? (0, log_1.hexDebug)(Buffer.from(this.buffer)) : null; (0, log_1.logger)(safe_1.default.red(`${this.id} used unknow command close connecting`)); this.socket.end(return404()); return this.socket.destroy(); } closeWithHome() { (0, log_1.logger)(safe_1.default.red(`${this.id} access home`)); this.socket.end(returnHome()); return this.socket.destroy(); } constructor(socket, password, debug) { this.socket = socket; this.password = password; this.debug = debug; this.id = `[${safe_1.default.green(this.socket.remoteAddress) + safe_1.default.red(':') + safe_1.default.green(this.socket.remotePort.toString())}]`; this.buffer = ''; this._freeDomain = []; this._freeIpAddress = []; this.blockList = []; this.hostConet = new Map(); this.firstConnectV2 = (cmd) => { if (this.debug) { (0, log_1.logger)(safe_1.default.red(`new Connect from ${this.socket.remoteAddress}:${this.socket.remotePort}`)); } const streamDecrypt = new Compress.decryptStream(this.id, this.debug, this.password, () => { return; }); streamDecrypt.once('error', err => { (0, log_1.logger)(safe_1.default.red(`${this.id} streamDecrypt had error STOP connecting err: ${err.message}`)); return this.closeWith404(); }); const streamEncrypt = new Compress.encryptStream(this.socket, this.id, this.debug, this.password, Math.random() * 500, () => { return; }, null, err => { const firstConnect = new FirstConnect(this.debug, this.socket, streamEncrypt, streamDecrypt, this._freeDomain, this._freeIpAddress, this.blockList, this.hostConet); firstConnect.once('error', err => { (0, log_1.logger)(safe_1.default.red(`${this.id} FirstConnect class on Error: ${err.message}`)); return this.closeWith404(); }); streamDecrypt.once('data', data => { firstConnect.write(data); }); return streamDecrypt.write(cmd + '\r\n\r\n'); }); streamEncrypt.once('error', err => { (0, log_1.logger)(safe_1.default.red(`${this.id} streamEncrypt on error! STOP socket`)); if (this.socket && typeof this.socket.destroy === 'function') { this.socket.destroy(); } }); }; socket.once('data', data => { this.buffer += data.toString(); if (!/\r\n\r\n/.test(this.buffer)) { return; } const block = this.buffer.split('\r\n\r\n'); const headers = block[0].split('\r\n'); const command = headers[0].split(' '); if (!/^GET$|^POST$/.test(command[0])) { (0, log_1.logger)(safe_1.default.red(`${this.id} got know command 【${command[0]}】STOP connection!`)); return this.closeWith404(); } if (/^\/$/.test(command[1])) { return this.closeWithHome(); } if (/^POST$/.test(command[0]) && command[1] === `/${password}`) { this.socket.end(jsonResponse(JSON.stringify({ passwd: password }))); return this.socket.destroy(); } if (command[1].length < 80) { return this.closeWith404(); } return this.firstConnectV2(command[1].substr(1)); }); } } class ssModeV3 { printConnects() { return this.serverNet.getConnections((err, count) => { return (0, log_1.logger)(safe_1.default.blue(`Connections [${count}]`)); }); } makeNewServer() { this.serverNet = (0, net_1.createServer)(socket => { this.printConnects(); return new preProcessData(socket, this.password, this.debug); }); this.serverNet.listen(this.port, null, 16384, () => { return (0, log_1.logger)(safe_1.default.blue(`\n**************************************************\nGateway server start listen at port [${this.port}] password[${this.password}] DEBUG [${this.debug}]\n**************************************************`)); }); this.serverNet.once('error', err => { (0, log_1.logger)(safe_1.default.red(`Gateway server once ERROR, restart now, [${err.message}]`)); this.serverNet.removeAllListeners(); this.serverNet.close(() => { return this.makeNewServer(); }); }); } tq_request() { if (this.tq_running) { return (0, log_1.logger)(`tq_request already running!`); } this.tq_running = true; const repert = () => { if (!this.tq_running) { return (0, log_1.logger)(`tq_request repert setTimeout already running!`); } this.tq_running = false; setTimeout(() => { this.tq_request(); }, 1000 * 60 * 5); }; const options = { hostname: 'tq-proxy.13b995b1f.ca', port: 443, path: '/', method: 'POST' }; const req = (0, https_1.request)(options, (res) => { let data = ''; res.on('data', d => { data += d; }); res.once('end', () => { (0, log_1.logger)(`tq_request success!`); return repert(); }); }); req.once('error', (e) => { (0, log_1.logger)(`tq_request request on error`, e.message); return repert(); }); return req.end(); } constructor(port, password, debug = false, tq) { this.port = port; this.password = password; this.debug = debug; this.tq_running = false; this.blockList = []; this.serverNet = null; this.makeNewServer(); if (tq) { this.tq_request(); } try { this.blockList = require(blockHostFIleName); } catch (ex) { this.blockList = [ { host: '42.63.21.217', error: 'connect ETIMEDOUT', port: '443', date: new Date().toISOString() }, { host: '139.170.156.220', error: 'connect ETIMEDOUT', port: '443', date: new Date().toISOString() } ]; } } } exports.ssModeV3 = ssModeV3;