<?php

/*****************************************************************************
 *
 * CoreLogonMultisite.php - Module for handling cookie based logins as
 *                          generated by multisite
 *
 * Copyright (c) 2004-2016 NagVis Project (Contact: info@nagvis.org)
 *
 * License:
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *****************************************************************************/

class CoreLogonMultisite extends CoreLogonModule
{
    private function isCookiePlausible($username, $sessionId, $cookieHash)
    {
        // Mostly meant to check that we don't process malicious cookies, e.g.
        // data with special chars.

        if (preg_match('/^[\w$][-@.+\w$]*$/i', $username) !== 1) {
            // See https://github.com/Checkmk/checkmk/blob/1cc2796314508092b18d52d34b932fe0435beba4/packages/cmk-ccc/cmk/ccc/user.py#L52
            throw new AuthenticationException(l("Malformed username"));
        }
        if (preg_match('/^[-0-9a-f]{36}$/i', $sessionId) !== 1) {
            throw new AuthenticationException(l("Malformed session id"));
        }
        if (preg_match('/^[-0-9a-f]{64}$/i', $cookieHash) !== 1) {
            throw new AuthenticationException(l("Malformed hash"));
        }
    }

    private function checkAuthCookie($cookieName)
    {
        if (!isset($_COOKIE[$cookieName]) || $_COOKIE[$cookieName] == '') {
            throw new AuthenticationException(l("No auth cookie provided."));
        }
        $cookieValue = $_COOKIE[$cookieName];
        list($username, $sessionId, $cookieHash) = explode(':', $cookieValue, 3);

        $this->isCookiePlausible($username, $sessionId, $cookieHash);

        // Check session periods validity
        $site = getenv('OMD_SITE');
        $port = getenv('CONFIG_APACHE_TCP_PORT');
        $host = getenv('CONFIG_APACHE_TCP_ADDR');
        $url = "http://$host:$port/$site/check_mk/api/1.0/version";

        $headers = [
            'Content-type: application/json',
            'Accept: application/json',
            "Cookie: $cookieName=$cookieValue",
        ];

        $contextOptions = [
            'http' => [
                'method' => 'GET',
                'header' => implode("\r\n", $headers),
            ]
        ];

        $context = stream_context_create($contextOptions);
        if (filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) {
            $result = @file_get_contents($url, false, $context);
            if ($result === false) {
                throw new AuthenticationException(l("Cookie is invalid."));
            }
        } elseif (extension_loaded('curl')) {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $result = curl_exec($ch);
            $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            if ($http_status != 200) {
                throw new AuthenticationException(l("Cookie is invalid."));
            }
        } else {
            throw new NagVisException(l('No method to check the session validity is available.
                Please either enable allow_url_fopen in php.ini or install the curl PHP extension.'));
        }

        return $username;
    }

    /**
     * @return string
     */
    private function checkAuth()
    {
        // Loop all cookies trying to fetch a valid authentication
        // cookie for this installation
        foreach (array_keys($_COOKIE) as $cookieName) {
            if ($cookieName != 'auth_' . getenv('OMD_SITE')) {
                continue;
            }
            try {
                $name = $this->checkAuthCookie($cookieName);

                session_start();
                $_SESSION['multisiteLogonCookie'] = $cookieName;
                session_write_close();

                return $name;
            } catch (AuthenticationException $e) {
                throw new NagVisException(l('LogonMultisite: Not authenticated. ') . $e->getMessage());
            }
        }
        return '';
    }

    /**
     * @return void
     * @throws NagVisException
     */
    private function redirectToLogin()
    {
        // Do not redirect on ajax calls. Print out errors instead
        if (CONST_AJAX) {
            throw new NagVisException(l('LogonMultisite: Not authenticated.'));
        }
        // FIXME: Get the real path to multisite
        header('Location:../../../check_mk/login.py?_origtarget=' . urlencode($_SERVER['REQUEST_URI']));
    }

    /**
     * @param bool $printErr
     * @return bool
     * @throws NagVisException
     */
    public function check($printErr = true)
    {
        global $AUTH, $CORE;

        // Try to auth using the environment auth
        $ENV = new CoreLogonEnv();
        if ($ENV->check(false) === true) {
            return true;
        }

        $username = $this->checkAuth();
        if ($username === '') {
            $this->redirectToLogin();
            return false;
        }

        // Check if the user exists
        if (
            $this->verifyUserExists(
                $username,
                cfg('global', 'logon_multisite_createuser'),
                cfg('global', 'logon_multisite_createrole'),
                $printErr
            ) === false
        ) {
            return false;
        }

        $AUTH->setTrustUsername(true);
        $AUTH->setLogoutPossible(false);
        $AUTH->passCredentials(['user' => $username]);

        $authenticated = $AUTH->isAuthenticated();

        if (!$AUTH->usesBcrypt()) {
            $AUTH->resetPassword($AUTH->getUserId(), (time() * rand(1, 10)));
        }

        return $authenticated;
    }
}
