|
@@ -0,0 +1,341 @@
|
|
|
|
|
+const fplay = require("@lawlers/4play");
|
|
|
|
|
+const http = require("http");
|
|
|
|
|
+
|
|
|
|
|
+// 4play settings
|
|
|
|
|
+var port = 3030;
|
|
|
|
|
+var timeout = 30000;
|
|
|
|
|
+var password = "cnc";
|
|
|
|
|
+
|
|
|
|
|
+// ==================================================
|
|
|
|
|
+
|
|
|
|
|
+// webserver
|
|
|
|
|
+
|
|
|
|
|
+var global_ws = null;
|
|
|
|
|
+const webserver_port = 3000;
|
|
|
|
|
+
|
|
|
|
|
+var callback_q = new Map();
|
|
|
|
|
+
|
|
|
|
|
+const server = http.createServer(async function(req, res){
|
|
|
|
|
+
|
|
|
|
|
+ const parsed = new URL(req.url, "http://localhost");
|
|
|
|
|
+
|
|
|
|
|
+ const url = parsed.searchParams.get("url");
|
|
|
|
|
+ const pw = parsed.searchParams.get("pw");
|
|
|
|
|
+ var proxy = parsed.searchParams.get("proxy");
|
|
|
|
|
+ const url_match = parsed.searchParams.get("url_match");
|
|
|
|
|
+ const content_match = parsed.searchParams.get("content_match");
|
|
|
|
|
+ const fail_url_match = parsed.searchParams.get("fail_url_match");
|
|
|
|
|
+ const fail_content_match = parsed.searchParams.get("fail_content_match");
|
|
|
|
|
+
|
|
|
|
|
+ var container = parsed.searchParams.get("container");
|
|
|
|
|
+
|
|
|
|
|
+ if(fplay.browser_connected === false){
|
|
|
|
|
+
|
|
|
|
|
+ res.writeHead(
|
|
|
|
|
+ 400,
|
|
|
|
|
+ {
|
|
|
|
|
+ "Content-Type": "text/plain"
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ res.end("No browser available to render the motherfucking page");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var errors = [];
|
|
|
|
|
+
|
|
|
|
|
+ if(
|
|
|
|
|
+ pw === null ||
|
|
|
|
|
+ pw != password
|
|
|
|
|
+ ){
|
|
|
|
|
+
|
|
|
|
|
+ errors.push("pw");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if(url === null){ errors.push("url"); }
|
|
|
|
|
+ if(proxy === null){ errors.push("proxy"); }
|
|
|
|
|
+
|
|
|
|
|
+ if(errors.length !== 0){
|
|
|
|
|
+
|
|
|
|
|
+ res.writeHead(
|
|
|
|
|
+ 400,
|
|
|
|
|
+ {
|
|
|
|
|
+ "Content-Type": "text/plain"
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ res.end("The following parameters are missing or invalid: " + errors.join(", "));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log("Rendering " + url);
|
|
|
|
|
+
|
|
|
|
|
+ if(
|
|
|
|
|
+ container === null ||
|
|
|
|
|
+ await fplay.container_exists(global_ws, container) === false
|
|
|
|
|
+ ){
|
|
|
|
|
+
|
|
|
|
|
+ container = await fplay.container_create(global_ws);
|
|
|
|
|
+
|
|
|
|
|
+ proxy = get_proxy(proxy);
|
|
|
|
|
+
|
|
|
|
|
+ // assign proxy
|
|
|
|
|
+ if(proxy.config !== false){
|
|
|
|
|
+
|
|
|
|
|
+ await fplay.container_attach_proxy(
|
|
|
|
|
+ global_ws,
|
|
|
|
|
+ container,
|
|
|
|
|
+ proxy.config
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }else{
|
|
|
|
|
+
|
|
|
|
|
+ proxy = {raw: proxy};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // open tab
|
|
|
|
|
+ const newtab = await fplay.tab_open(global_ws, url, false, container);
|
|
|
|
|
+
|
|
|
|
|
+ if(newtab === false){
|
|
|
|
|
+
|
|
|
|
|
+ // failed to open tab
|
|
|
|
|
+ res.writeHead(
|
|
|
|
|
+ 400,
|
|
|
|
|
+ {
|
|
|
|
|
+ "Content-Type": "text/plain",
|
|
|
|
|
+ "X-Proxy": proxy.raw,
|
|
|
|
|
+ "X-Container-ID": typeof container.id == "undefined" ? container : container.id
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ res.end("Failed to open tab");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // callback will be handled in "web_response"
|
|
|
|
|
+ callback_q.set(
|
|
|
|
|
+ newtab.id,
|
|
|
|
|
+ {
|
|
|
|
|
+ http_res: res,
|
|
|
|
|
+ proxy: proxy,
|
|
|
|
|
+ container: container,
|
|
|
|
|
+ url_match: url_match,
|
|
|
|
|
+ content_match: content_match,
|
|
|
|
|
+ fail_url_match: fail_url_match,
|
|
|
|
|
+ fail_content_match: fail_content_match
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // close tab in 30 seconds
|
|
|
|
|
+ await fplay.wait_random(15000, 35000);
|
|
|
|
|
+ await fplay.tab_close(global_ws, newtab);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+server.listen(webserver_port, function(){
|
|
|
|
|
+
|
|
|
|
|
+ console.log("http server running");
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+function get_proxy(parts){
|
|
|
|
|
+
|
|
|
|
|
+ parts = parts.split(":");
|
|
|
|
|
+ // format: type:address:port:username:password
|
|
|
|
|
+ // PHP explode(":", $ip, 5) splits into max 5 parts
|
|
|
|
|
+ var type = parts[0];
|
|
|
|
|
+ const address = parts[1];
|
|
|
|
|
+ const port = parts[2];
|
|
|
|
|
+ const username = parts[3];
|
|
|
|
|
+ const password = parts[4];
|
|
|
|
|
+
|
|
|
|
|
+ var proxy_dns = true;
|
|
|
|
|
+
|
|
|
|
|
+ switch(type){
|
|
|
|
|
+ case "raw_ip":
|
|
|
|
|
+ case "ip":
|
|
|
|
|
+ // no proxy, use direct connection
|
|
|
|
|
+ return {
|
|
|
|
|
+ config: false,
|
|
|
|
|
+ raw: "raw_ip::::"
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case "socks5":
|
|
|
|
|
+ type = "socks";
|
|
|
|
|
+ proxy_dns = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case "socks4a":
|
|
|
|
|
+ type = "socks4";
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case "socks4":
|
|
|
|
|
+ proxy_dns = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case "socks5_hostname":
|
|
|
|
|
+ case "socks5h":
|
|
|
|
|
+ case "socks5a":
|
|
|
|
|
+ type = "socks";
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case "http":
|
|
|
|
|
+ case "https":
|
|
|
|
|
+ // do nothing
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ // invalid proxy provided
|
|
|
|
|
+ return {
|
|
|
|
|
+ config: false,
|
|
|
|
|
+ raw: "raw_ip::::"
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ config: {
|
|
|
|
|
+ type: type,
|
|
|
|
|
+ host: address,
|
|
|
|
|
+ port: port,
|
|
|
|
|
+ username: username,
|
|
|
|
|
+ password: password,
|
|
|
|
|
+ proxyDNS: proxy_dns
|
|
|
|
|
+ },
|
|
|
|
|
+ raw: proxy_line
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fplay.event.on("server_ready", function(){
|
|
|
|
|
+
|
|
|
|
|
+ console.log("listening on port " + port + " (timeout=" + timeout + ")");
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+fplay.event.on("browser_connect", async function(ws){
|
|
|
|
|
+
|
|
|
|
|
+ global_ws = ws;
|
|
|
|
|
+
|
|
|
|
|
+ const ua = await fplay.get_ua(ws);
|
|
|
|
|
+ console.log("New browser instance connected: " + ua);
|
|
|
|
|
+
|
|
|
|
|
+ await fplay.close_all_tabs(ws);
|
|
|
|
|
+ await fplay.delete_all_containers(ws);
|
|
|
|
|
+
|
|
|
|
|
+ fplay.web_response_whitelist(ws, ["main_frame"]);
|
|
|
|
|
+
|
|
|
|
|
+ // clean up
|
|
|
|
|
+ const blanktab = await fplay.close_all_tabs(ws);
|
|
|
|
|
+ await fplay.delete_all_containers(ws);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+fplay.event.on("web_response", async function(page){
|
|
|
|
|
+
|
|
|
|
|
+ var callback_data = callback_q.get(page.id);
|
|
|
|
|
+
|
|
|
|
|
+ if(typeof callback_data == "undefined"){ return; }
|
|
|
|
|
+
|
|
|
|
|
+ var body = page.body.toString();
|
|
|
|
|
+
|
|
|
|
|
+ var url_match = page.url.match(new RegExp(callback_data.url_match, "i"));
|
|
|
|
|
+ var content_match = body.match(new RegExp(callback_data.content_match, "i"));
|
|
|
|
|
+ var fail_url_match = page.url.match(new RegExp(callback_data.fail_url_match, "i"));
|
|
|
|
|
+ var fail_content_match = body.match(new RegExp(callback_data.fail_content_match, "i"));
|
|
|
|
|
+
|
|
|
|
|
+ // handle positive response
|
|
|
|
|
+ if(
|
|
|
|
|
+ (
|
|
|
|
|
+ callback_data.url_match === null &&
|
|
|
|
|
+ callback_data.content_match === null
|
|
|
|
|
+ ) ||
|
|
|
|
|
+ (
|
|
|
|
|
+ url_match &&
|
|
|
|
|
+ content_match
|
|
|
|
|
+ ) ||
|
|
|
|
|
+ (
|
|
|
|
|
+ url_match &&
|
|
|
|
|
+ callback_data.content_match === null
|
|
|
|
|
+ ) ||
|
|
|
|
|
+ (
|
|
|
|
|
+ callback_data.url_match === null &&
|
|
|
|
|
+ content_match
|
|
|
|
|
+ )
|
|
|
|
|
+ ){
|
|
|
|
|
+
|
|
|
|
|
+ callback_data.http_res.writeHead(
|
|
|
|
|
+ 200,
|
|
|
|
|
+ {
|
|
|
|
|
+ "Content-Type": "text/plain;charset=UTF-8",
|
|
|
|
|
+ "X-Proxy": callback_data.proxy.raw,
|
|
|
|
|
+ "X-Container-ID": typeof callback_data.container.id == "undefined" ? callback_data.container : callback_data.container.id
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ callback_data.http_res.end(body);
|
|
|
|
|
+
|
|
|
|
|
+ callback_q.delete(page.id); // cleanup callback only
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // handle negative response
|
|
|
|
|
+ if(
|
|
|
|
|
+ (
|
|
|
|
|
+ callback_data.fail_url_match !== null &&
|
|
|
|
|
+ callback_data.fail_content_match !== null
|
|
|
|
|
+ ) &&
|
|
|
|
|
+ (
|
|
|
|
|
+ (
|
|
|
|
|
+ fail_url_match &&
|
|
|
|
|
+ fail_content_match
|
|
|
|
|
+ ) ||
|
|
|
|
|
+ (
|
|
|
|
|
+ fail_url_match &&
|
|
|
|
|
+ callback_data.fail_content_match === null
|
|
|
|
|
+ ) ||
|
|
|
|
|
+ (
|
|
|
|
|
+ callback_data.fail_url_match === null &&
|
|
|
|
|
+ fail_content_match
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ ){
|
|
|
|
|
+
|
|
|
|
|
+ callback_data.http_res.writeHead(
|
|
|
|
|
+ 400,
|
|
|
|
|
+ {
|
|
|
|
|
+ "Content-Type": "text/plain",
|
|
|
|
|
+ "X-Proxy": callback_data.proxy.raw,
|
|
|
|
|
+ "X-Container-ID": callback_data.container.id
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ callback_data.http_res.end("Reached fail state (matched fail_url_match or fail_content_match)");
|
|
|
|
|
+
|
|
|
|
|
+ // cleanup browser + callback
|
|
|
|
|
+ await fplay.tab_close(global_ws, page.id);
|
|
|
|
|
+ await fplay.container_delete(global_ws, callback_data.container);
|
|
|
|
|
+ callback_q.delete(page.id);
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+fplay.event.on("dom_load_fail", async function(page){
|
|
|
|
|
+
|
|
|
|
|
+ var callback_data = callback_q.get(page.id);
|
|
|
|
|
+
|
|
|
|
|
+ if(typeof callback_data == "undefined"){ return; }
|
|
|
|
|
+
|
|
|
|
|
+ callback_q.delete(page.id);
|
|
|
|
|
+
|
|
|
|
|
+ callback_data.http_res.writeHead(
|
|
|
|
|
+ 400,
|
|
|
|
|
+ {
|
|
|
|
|
+ "Content-Type": "text/plain",
|
|
|
|
|
+ "X-Proxy": callback_data.proxy.raw,
|
|
|
|
|
+ "X-Container-ID": typeof callback_data.container.id == "undefined" ? callback_data.container : callback_data.container.id
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ callback_data.http_res.end("Network error (" + page.error + ")");
|
|
|
|
|
+
|
|
|
|
|
+ // cleanup browser
|
|
|
|
|
+ await fplay.tab_close(global_ws, page.id);
|
|
|
|
|
+ await fplay.container_delete(global_ws, callback_data.container);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+fplay.init(port, password, timeout);
|