Optional Tor support

This commit is contained in:
Cadence Fish
2020-02-03 00:43:56 +13:00
parent b944042fe0
commit 96fa4758c0
11 changed files with 597 additions and 125 deletions

View File

@@ -1,13 +1,16 @@
const fetch = require("node-fetch").default
function request(url) {
console.log("-> [OUT]", String(url)) // todo: make more like pinski?
return fetch(url, {
function request(url, options = {}, settings = {}) {
if (settings.statusLine === undefined) settings.statusLine = "OUT"
if (settings.log === undefined) settings.log = true
if (settings.log) console.log(`-> [${settings.statusLine}] ${url}`) // todo: make more like pinski?
// @ts-ignore
return fetch(url, Object.assign({
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
},
redirect: "manual"
})
}, options))
}
module.exports.request = request

79
src/lib/utils/tor.js Normal file
View File

@@ -0,0 +1,79 @@
const SocksProxyAgent = require("socks-proxy-agent")
const {connect} = require("net");
const constants = require("../constants")
const {request} = require("./request")
class TorManager {
/**
* @param {import("@deadcanaries/granax/lib/controller")} tor
* @param {number} port
*/
constructor(tor, port) {
this.tor = tor
this.port = port
this.agent = new SocksProxyAgent("socks5://localhost:"+this.port)
}
async request(url, test) {
let result = null
while (!result) {
const req = await request(url, {agent: this.agent}, {log: true, statusLine: "TOR"})
try {
result = await test(req)
} catch (e) {
await this.newCircuit()
}
}
return result
}
newCircuit() {
return new Promise(resolve => {
this.tor.cleanCircuits(() => resolve())
})
}
}
try {
var granax = require("@deadcanaries/granax")
} catch (e) {}
/** @type {Promise<TorManager>} */
module.exports = new Promise(resolve => {
if (granax) {
/** @type {import("@deadcanaries/granax/lib/controller")} */
// @ts-ignore
let tor
if (constants.tor_password == null) {
// @ts-ignore
tor = new granax()
} else {
tor = new granax.TorController(connect(9051), {authOnConnect: false})
tor.authenticate(`"${constants.tor_password}"`, err => {
if (err) console.log("Tor auth error:", err)
})
}
console.log("Starting tor...")
tor.once("ready", () => {
tor.getInfo("net/listeners/socks", (err, result) => {
if (err) throw err
// result is string containing something like "127.0.0.1:36977"
// yes, the string contains double quotes!
const port = +result.match(/:(\d+)/)[1]
const torManager = new TorManager(tor, port)
console.log("Tor is ready, using SOCKS port "+port)
resolve(torManager)
})
})
tor.on("error", function() {
console.log("Tor error!")
console.log(...arguments)
})
} else {
console.log("Note: Tor functionality not installed. You may wish to run `npm install @deadcanaries/granax`. (78+ MB download required.)")
resolve(null)
}
})

View File

@@ -0,0 +1,41 @@
const constants = require("../constants")
const {request} = require("./request")
class TorSwitcher {
constructor() {
this.torManager = null
}
setManager(torManager) {
this.torManager = torManager
}
/**
* Request from the URL.
* The test function will be called with the response object.
* If the test function succeeds, its return value will be returned here.
* If the test function fails, its error will be rejected here.
* Only include rate limit logic in the test function!
* @param {string} url
* @param {(res: import("node-fetch").Response) => Promise<T>} test
* @returns {Promise<T>}
* @template T the return value of the test function
*/
request(url, test) {
if (this.torManager) {
return this.torManager.request(url, test)
} else {
return request(url).then(res => test(res))
}
}
}
const switcher = new TorSwitcher()
if (constants.use_tor) {
require("./tor").then(torManager => {
if (torManager) switcher.setManager(torManager)
})
}
module.exports = switcher