<?php

use dokuwiki\Extension\SyntaxPlugin;

/**
 * DokuWiki Plugin dig (Syntax Component)
 *
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
 * @author  Andreas Gohr <andi@splitbrain.org>
 */
class syntax_plugin_dig extends SyntaxPlugin
{
    /** @inheritdoc */
    public function getType()
    {
        return 'substition';
    }

    /** @inheritdoc */
    public function getPType()
    {
        return 'block';
    }

    /** @inheritdoc */
    public function getSort()
    {
        return 333;
    }

    /** @inheritdoc */
    public function connectTo($mode)
    {
        $this->Lexer->addSpecialPattern('<dig>\n.*?\n</dig>', $mode, 'plugin_dig');
    }

    /** @inheritdoc */
    public function handle($match, $state, $pos, Doku_Handler $handler)
    {
        $lines = explode("\n", $match);
        array_shift($lines); // skip opening tag
        array_pop($lines);   // skip closing tag

        return linesToHash($lines);
    }

    /** @inheritdoc */
    public function render($mode, Doku_Renderer $R, $data)
    {
        if ($mode == 'metadata') return false;

        $R->table_open();
        $R->tablethead_open();
        $R->tablerow_open();
        $R->tableheader_open();
        $R->cdata($this->getLang('host'));
        $R->tableheader_close();
        $R->tableheader_open();
        $R->cdata($this->getLang('ip'));
        $R->tableheader_close();
        $R->tableheader_open();
        $R->cdata($this->getLang('ns'));
        $R->tableheader_close();
        $R->tableheader_open();
        $R->cdata($this->getLang('ssl'));
        $R->tableheader_close();
        $R->tablerow_close();
        $R->tablethead_close();

        $R->tabletbody_open();

        foreach ($data as $domain => $comment) {
            $R->tablerow_open();
            $this->renderCell($R, [$domain, $comment]);

            try {
                $dns = $this->getDNS($domain);
                $this->renderCell($R, $dns['ip']);
                $this->renderCell($R, $dns['ns']);
            } catch (Exception $e) {
                $this->renderCell($R, $e->getMessage(), 2);
            }

            try {
                $ssl = $this->getSSL($domain);

                $this->renderCell(
                    $R,
                    [
                        'valid' => $ssl['date_valid'] && $ssl['domain_valid'] ? '✔️' : '❌',
                        'until' => date('Y-m-d', $ssl['date_to']),
                        'days' => floor(($ssl['date_to'] - time()) / (24 * 60 * 60)),
                        'issuer' => $ssl['issuer'],
                        'domains' => count($ssl['domains']),
                    ]
                );
            } catch (Exception $e) {
                $this->renderCell($R, $e->getMessage());
            }

            $R->tablerow_close();
        }
        $R->tabletbody_close();

        $R->tabletfoot_open();
        $R->tablerow_open();
        $R->tablecell_open(4);
        $R->cdata($this->getLang('lastupdate') . ' ' . date('Y-m-d H:i:s'));
        $R->tablecell_close();
        $R->tablerow_close();
        $R->tabletfoot_close();

        $R->table_close();

        return true;
    }

    /**
     * Get the DNS information for a domain
     *
     * @param string $domain
     * @return array
     * @throws Exception if the DNS lookup fails
     */
    protected function getDNS($domain)
    {
        $info = [
            'ip' => [],
            'ns' => [],
        ];

        $record = dns_get_record($domain, DNS_ALL);
        if (!$record) throw new Exception($this->getLang('dnsfail'));

        foreach ($record as $r) {
            switch ($r['type']) {
                case 'A':
                    $info['ip']['IP'] = $r['ip'];
                    break;
                case 'AAAA':
                    $info['ip']['IPv6'] = $r['ipv6'];
                    break;
                case 'CNAME':
                    $info['ip']['CNAME'] = $r['target'];
                    break;
                case 'SOA':
                    $info['ns']['NS'] = $r['mname'];
                    break;
                case 'MX':
                    $info['ns']['MX'] = $r['target'];
                    break;
            }
        }
        return $info;
    }

    /**
     * Get the certificate information for a domain
     *
     * @param string $domain
     * @return array
     * @throws Exception if the SSL lookup fails
     */
    protected function getSSL($domain)
    {
        if (!function_exists('openssl_x509_parse')) {
            throw new Exception($this->getLang('sslfail') . ': ' . 'openssl not available in your PHP installation');
        }

        $context = stream_context_create(
            ["ssl" => ["capture_peer_cert" => true, 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true]]
        );
        $read = @stream_socket_client(
            "ssl://" . $domain . ":443",
            $errno,
            $errstr,
            15,
            STREAM_CLIENT_CONNECT,
            $context
        );
        if ($errstr) throw new Exception($this->getLang('sslfail') . ': ' . $errstr, $errno);


        $cert = stream_context_get_params($read);
        $certinfo = openssl_x509_parse($cert['options']['ssl']['peer_certificate']);

        $issuer = $certinfo['issuer']['O'] ?? implode(' ', $certinfo['issuer']);

        $domains = array_merge(
            [$certinfo['subject']['CN']],
            explode(',', $certinfo['extensions']['subjectAltName'])
        );
        $domains = array_map(static fn($d) => preg_replace('/^DNS:/', '', trim($d)), $domains);

        return [
            'date_from' => $certinfo['validFrom_time_t'],
            'date_to' => $certinfo['validTo_time_t'],
            'date_valid' => $certinfo['validFrom_time_t'] < time() && time() < $certinfo['validTo_time_t'],
            'issuer' => $issuer,
            'domains' => $domains,
            'domain_valid' => in_array($domain, $domains),
        ];
    }

    /**
     * @param Doku_Renderer $R
     * @param string|array $data
     * @param int $colspan
     */
    protected function renderCell($R, $data, $colspan = 1)
    {
        $R->tablecell_open($colspan);
        if (is_array($data)) {
            foreach ($data as $key => $item) {
                if (!is_numeric($key)) {
                    $R->cdata($key . ': ');
                }
                $R->cdata($item);
                $R->linebreak();
            }
        } else {
            $R->cdata($data);
        }
        $R->tablecell_close();
    }
}