// https://github.com/gnh1201/caterpillar // Created at: 2022-10-06 // Updated at: 2024-02-27 define("PHP_HTTPPROXY_VERSION", "0.1.5"); if (strpos($_SERVER['HTTP_USER_AGENT'], "php-httpproxy/") !== 0) { exit('
php-httpproxy/' . PHP_HTTPPROXY_VERSION . ' (Server; PHP ' . phpversion() . '; abuse@catswords.net)
'); } ini_set("default_socket_timeout", 1); // must be. because of `feof()` works ini_set("max_execution_time", 0); function jsonrpc2_encode($method, $params, $id = '') { $data = array( "jsonrpc" => "2.0", "method" => $method, "params" => $params, "id" => $id ); return json_encode($data); } function jsonrpc2_result_encode($result, $id = '') { $data = array( "jsonrpc" => "2.0", "result" => $result, "id" => $id ); return json_encode($data); } function jsonrpc2_error_encode($error, $id = '') { $data = array( "jsonrpc" => "2.0", "error" => $error, "id" => $id ); return json_encode($data); } function parse_headers($str) { // Parses HTTP headers into an array // https://stackoverflow.com/questions/16934409/curl-as-proxy-deal-with-https-connect-method // https://stackoverflow.com/questions/12433958/how-to-parse-response-headers-in-php $headers = array(); $lines = preg_split("'\r?\n'", $str); $first_line = array_shift($lines); $headers['@method'] = explode(' ', $first_line); foreach ($lines as $line) { if (!preg_match('/^([^:]+):(.*)$/', $line, $out)) continue; $headers[$out[1]] = trim($out[2]); } return $headers; } function read_from_remote_server($remote_address, $remote_port, $scheme, $data = null, $conn = null, $buffer_size = 8192, $id = '') { if (in_array($scheme, array("https", "ssl", "tls"))) { $remote_address = "tls://" . $remote_address; } $sock = fsockopen($remote_address, $remote_port, $error_code, $error_message, 1); if (!$sock) { $error = array( "status" => 502, "code" => $error_code, "message" => $error_message ); if ($conn == null) { echo jsonrpc2_error_encode($error, $id); } else { $buf = sprintf("HTTP/1.1 502 Bad Gateway\r\n\r\n"); $buf .= jsonrpc2_error_encode($error, $id); fwrite($conn, $buf); } } else { if ($conn == null) { // send data fwrite($sock, $data); // receive data $buf = null; while (!feof($sock) && $buf !== false) { $buf = fgets($sock, $buffer_size); echo $buf; } } else { // send data $buf = null; while (!feof($conn) && $buf !== false) { $buf = fgets($conn, $buffer_size); fwrite($sock, $buf); } // receive data $buf = null; while (!feof($sock) && $buf !== false) { $buf = fgets($sock, $buffer_size); fwrite($conn, $buf); } } fclose($sock); } } // stateless mode function relay_request($params, $id = '') { $buffer_size = $params['buffer_size']; $request_data = base64_decode($params['request_data']); $request_header = parse_headers($request_data); $request_length = intval($params['request_length']); $client_address = $params['client_address']; $client_port = intval($params['client_port']); $client_encoding = $params['client_encoding']; $remote_address = $params['remote_address']; $remote_port = intval($params['remote_port']); $scheme = $params['scheme']; $datetime = $params['datetime']; // format: %Y-%m-%d %H:%M:%S.%f if (in_array($scheme, array("https", "ssl", "tls"))) { $remote_address = "tls://" . $remote_address; } switch ($request_header['@method'][0]) { case "CONNECT": $error = array( "status" => 405, "code" => -1, "message" => "Method Not Allowed" ); echo jsonrpc2_error_encode($error, $id); break; default: read_from_remote_server($remote_address, $remote_port, $scheme, $request_data, null, $buffer_size, $id); } } // stateful mode function relay_connect($params, $id = '') { $buffer_size = $params['buffer_size']; $client_address = $params['client_address']; $client_port = intval($params['client_port']); $client_encoding = $params['client_encoding']; $remote_address = $params['remote_address']; $remote_port = intval($params['remote_port']); $scheme = $params['scheme']; $datetime = $params['datetime']; // format: %Y-%m-%d %H:%M:%S.%f $starttime = microtime(true); $conn = fsockopen($client_address, $client_port, $error_code, $error_message, 1); if (!$conn) { $error = array( "status" => 502, "code" => $error_code, "message" => $error_message, "_params" => $params ); echo jsonrpc2_error_encode($error, $id); } else { $stoptime = microtime(true); $connection_speed = floor(($stoptime - $starttime) * 1000); $data = jsonrpc2_encode("relay_accept", array( "success" => true, "connection_speed" => $connection_speed ), $id); fwrite($conn, $data . "\r\n\r\n"); read_from_remote_server($remote_address, $remote_port, $scheme, null, $conn, $buffer_size, $id); fclose($conn); } } function relay_mysql_connect($params) { $hostname = $params['hostname']; $username = $params['username']; $password = $params['password']; $database = $params['database']; $port = array_key_exists('port', $params) ? intval($params['port']) : null; $charset = array_key_exists('charset', $params) ? $params['charset'] : "utf8"; $mysqli = new mysqli($hostname, $username, $password, $database, $port); if ($mysqli->connect_errno) { return array( "success" => false, "error" => array( "status" => 503, "code" => $mysqli->connect_errno, "message" => $mysqli->connect_error ) ); } else { $mysqli->set_charset($charset); } return array( "success" => true, "mysqli" => $mysqli, "result" => array( "status" => 200 ) ); } function relay_mysql_query($params, $mysqli) { $query = trim($params['query']); $query_type = ""; // e.g., select, insert, update, delete $pos = strpos($query, ' '); if ($pos !== false) { $query_type = strtolower(substr($query, 0, $pos)); } $result = $mysqli->query($query); if (!$mysqli->error) { return array( "success" => false, "error" => array( "status" => 503, "code" => $msqli->errno, "message" => $mysqli->error ) ); } $data = array(); switch($query_type) { case "select": $data = mysqli_fetch_all($result, MYSQLI_ASSOC); break; case "insert": $data[] = $result; $data[] = @$mysqli->insert_id(); break; default: $data[] = $result; } return array( "success" => true, "result" => array( "status" => 200, "data" => $data ) ); } function relay_sendmail($params) { $to = $params['to']; $from = $params['from']; $subject = $params['subject']; $message = $params['message']; $headers = 'From: ' . $from . "\r\n" . 'X-Mailer: php-httpproxy/' . PHP_HTTPPROXY_VERSION . ' (Server; PHP ' . phpversion() . ')'; $sent = @mail($to, $subject, $message, $headers); if (!$sent) { $e = error_get_last(); return array( "success" => false, "error" => array( "status" => 500, "code" => $e['type'], "message" => $e['message'] ) ); } return array( "success" => true, "result" => array( "status" => 200 ) ); } function relay_get_version() { return PHP_HTTPPROXY_VERSION; } function relay_get_phpversion() { return phpversion(); } function relay_get_loaded_extensions() { return get_loaded_extensions(); } function relay_dns_get_record($params) { $hostname = $params['hostname']; $data = dns_get_record($hostname); if (!$data) { return array( "success" => false, "error" => array( "status" => 502, "code" => -1, "message" => $hostname . " is not found in DNS records" ) ); } return array( "success" => true, "result" => array( "status" => 200, "data" => $data ) ); } function relay_get_geolocation() { $url = "https://ipapi.co/json/"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $response = curl_exec($ch); $error_code = curl_errno($ch); if ($error_code) { $error_message = curl_error($ch); curl_close($ch); return array( "success" => false, "error" => array( "status" => 502, "code" => $error_code, "message" => $error_message ) ); } curl_close($ch); $data = json_decode($response, true); return array( "success" => true, "result" => array( "status" => 200, "data" => $data ) ); } function get_client_address() { $client_address = ''; if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $client_address = $_SERVER['HTTP_CLIENT_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $client_address = $_SERVER['HTTP_X_FORWARDED_FOR']; } else { $client_address = $_SERVER['REMOTE_ADDR']; } return array("client_address" => $client_address); } // parse a context $context = json_decode(file_get_contents('php://input'), true); // check is it jsonrpc (stateless) if ($context['jsonrpc'] == "2.0") { $method = $context['method']; switch ($method) { case "relay_request": relay_request($context['params'], $context['id']); // stateless mode break; case "relay_connect": relay_connect($context['params'], $context['id']); // stateful mode break; case "relay_mysql_query": $result = relay_mysql_connect($context['params']); if ($result['success']) { $mysqli = $result['mysqli']; $query_result = relay_mysql_query($context['params'], $mysqli); if ($query_result['success']) { echo jsonrpc2_result_encode($query_result['result'], $context['id']); } else { echo jsonrpc2_error_encode($query_result['error'], $context['id']); } } else { echo jsonrpc2_error_encode($result['error'], $context['id']); } break; case "relay_sendmail": $result = relay_sendmail($context['params']); if ($result['success']) { echo jsonrpc2_result_encode($result['result'], $context['id']); } else { echo jsonrpc2_error_encode($result['error'], $context['id']); } break; case "relay_get_version": echo jsonrpc2_result_encode(relay_get_version(), $context['id']); break; case "relay_get_phpversion": echo jsonrpc2_result_encode(relay_get_phpversion(), $context['id']); break; case "relay_get_loaded_extensions": echo jsonrpc2_result_encode(relay_get_loaded_extensions(), $context['id']); break; case "relay_dns_get_record": $result = relay_dns_get_record($context['params']); if ($result['success']) { echo jsonrpc2_result_encode($result['result'], $context['id']); } else { echo jsonrpc2_error_encode($result['error'], $context['id']); } break; case "relay_get_geolocation": $result = relay_get_geolocation(); if ($result['success']) { echo jsonrpc2_result_encode($result['result'], $context['id']); } else { echo jsonrpc2_error_encode($result['error'], $context['id']); } break; case "get_client_address": echo jsonrpc2_result_encode(get_client_address(), $context['id']); break; } }