]> jfr.im git - irc/unrealircd/unrealircd-webpanel.git/blobdiff - common.php
unrealircd::rpc_password is now encrypted with secret::key (XChaCha20-Poly1305-IETF)
[irc/unrealircd/unrealircd-webpanel.git] / common.php
index 93431fdf706b73c151f65156c53f28aa877ba810..a7fb94e0e2e253c42bb485ef708358a388228ffb 100644 (file)
@@ -34,25 +34,41 @@ function page_requires_no_config()
        return FALSE;
 }
 
+function page_requires_no_login()
+{
+       if (str_ends_with($_SERVER['SCRIPT_FILENAME'],"login/index.php") ||
+           page_requires_no_config())
+       {
+               return TRUE;
+       }
+       return FALSE;
+}
+
 function read_config_file()
 {
        GLOBAL $config;
+       GLOBAL $config_transition_unreal_server;
 
-       /* Load config defaults */
        $config = Array();
-       require_once UPATH . "/config/config.defaults.php";
-
        if (!file_exists(UPATH."/config/config.php") && file_exists(UPATH."/config.php"))
        {
                require_once UPATH . "/config.php";
                require_once UPATH . "/config/compat.php";
        }
+       if ((@include(UPATH . "/config/config.php")) !== 1)
+               return false;
+       if (isset($config['unrealircd']))
+               $config_transition_unreal_server = true;
+       return true;
 }
 
 function read_config_db()
 {
        GLOBAL $config;
 
+       if (page_requires_no_config())
+               return;
+
        $merge = DbSettings::get();
        /* DB settings overwrite config.php keys: */
        $config = array_merge($config, $merge);
@@ -60,11 +76,10 @@ function read_config_db()
 
 function config_is_file_item($name)
 {
-       // TODO: move 'unrealircd' and 'plugins' probably ;)
-       if (($name == "unrealircd") ||
-           ($name == "plugins") ||
+       if (($name == "plugins") ||
            ($name == "mysql") ||
-           ($name == "base_url"))
+           ($name == "base_url") ||
+           ($name == "secrets"))
        {
                return true;
        }
@@ -104,7 +119,8 @@ function write_config_file()
        /* Now atomically rename the file */
        if (!rename($tmpfile, $cfg_filename))
                die("Could not write (rename) to file ".$cfg_filename."<br>");
-       opcache_invalidate($cfg_filename);
+       if (function_exists('opcache_invalidate'))
+               opcache_invalidate($cfg_filename);
 
        /* Do not re-read config, as it would reinitialize config
         * without having the DB settings read. (And it also
@@ -122,7 +138,7 @@ function write_config($setting = null)
        /* Specific request? Easy, write only this setting to the DB (never used for file) */
        if ($setting !== null)
        {
-               return DbSettings::set($k, $v);
+               return DbSettings::set($setting, $config[$setting]);
        }
 
        /* Otherwise write the whole config.
@@ -142,7 +158,6 @@ function write_config($setting = null)
 
        foreach($db_settings as $k=>$v)
        {
-               echo "Writing $k => $v<br>\n";
                $ret = DbSettings::set($k, $v);
                if (!$ret)
                        return $ret;
@@ -151,7 +166,132 @@ function write_config($setting = null)
        return true;
 }
 
+function get_version()
+{
+       $fd = @fopen(UPATH."/.git/FETCH_HEAD", "r");
+       if ($fd === false)
+               return "unknown";
+       $line = fgets($fd, 512);
+       fclose($fd);
+       $commit = substr($line, 0, 8);
+       return $commit; /* short git commit id */
+}
+
+function generate_secrets()
+{
+       GLOBAL $config;
+
+       if (!isset($config['secrets']))
+               $config['secrets'] = Array();
+
+       if (!isset($config['secrets']['pepper']))
+               $config['secrets']['pepper'] = rtrim(base64_encode(random_bytes(16)),'=');
+
+       if (!isset($config['secrets']['key']))
+               $config['secrets']['key'] = rtrim(base64_encode(sodium_crypto_aead_xchacha20poly1305_ietf_keygen()),'=');
+}
+
+function secret_encrypt(string $text)
+{
+       GLOBAL $config;
+
+       $key = base64_decode($config['secrets']['key']);
+       $nonce = \random_bytes(\SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
+       $encrypted_text = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($text, '', $nonce, $key);
+       return "secret:".rtrim(base64_encode($nonce),'=').':'.rtrim(base64_encode($encrypted_text),'='); // secret:base64(NONCE):base64(ENCRYPTEDTEXT)
+}
+
+function secret_decrypt(string $crypted)
+{
+       GLOBAL $config;
+
+       $key = base64_decode($config['secrets']['key']);
+       $d = explode(":", $crypted);
+       if (count($d) != 3)
+               return null;
+       $nonce = base64_decode($d[1]);
+       $ciphertext = base64_decode($d[2]);
+
+       $ret = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($ciphertext, '', $nonce, $key);
+       if ($ret === false)
+               return null;
+       return $ret;
+}
+
+function upgrade_check()
+{
+       GLOBAL $config_transition_unreal_server;
+       GLOBAL $config;
+
+       /* Moving of a config.php item to DB: */
+       if ($config_transition_unreal_server)
+               write_config();
+
+       /* Our own stuff may need upgrading.. */
+       /* - generating secrets */
+       if (!isset($config['secrets']))
+       {
+               generate_secrets();
+               write_config_file();
+       }
+       /* - encrypting rpc_password */
+       if (isset($config['unrealircd']) &&
+           isset($config['unrealircd']['rpc_password']) &&
+           !str_starts_with($config['unrealircd']['rpc_password'], "secret:"))
+       {
+               $ret = secret_encrypt($config['unrealircd']['rpc_password']);
+               if ($ret !== false)
+               {
+                       $config['unrealircd']['rpc_password'] = $ret;
+                       write_config('unrealircd');
+               }
+       }
+
+       $version = get_version();
+       if (!isset($config['webpanel_version']))
+               $config['webpanel_version'] = '';
+       if ($version != $config['webpanel_version'])
+       {
+               $versioninfo = [
+                       "old_version" => $config['webpanel_version'],
+                       "new_version" => $version
+                       ];
+               /* And inform the hook (eg the database backends) */
+               Hook::run(HOOKTYPE_UPGRADE, $versioninfo);
+               /* And set the new version now that the upgrade is "done" */
+               $config['webpanel_version'] = $version;
+               write_config("webpanel_version");
+       }
+}
+
+function panel_start_session($user = false)
+{
+       if (!isset($_SESSION))
+       {
+               session_set_cookie_params(86400); // can't set this to session_timeout due to catch-22
+               session_start();
+       }
+
+       if ($user === false)
+       {
+               $user = unreal_get_current_user();
+               if ($user === false)
+                       return false;
+       }
+
+       $timeout = 3600;
+       if (isset($user->user_meta['session_timeout']))
+               $timeout = (INT)$user->user_meta['session_timeout'];
+
+       if (!isset($_SESSION['session_timeout']))
+               $_SESSION['session_timeout'] = $timeout;
+
+       $_SESSION['last-activity'] = time();
+       return true;
+}
+
 /* Now read the config, and redirect to install screen if we don't have it */
+$config_transition_unreal_server = false;
 if (!read_config_file())
 {
        if (page_requires_no_config())
@@ -162,9 +302,6 @@ if (!read_config_file())
        {
                header("Location: settings/install.php");
                die();
-       } else
-       {
-               require_once UPATH . "/config/config.php";
        }
 }
 
@@ -185,11 +322,19 @@ require_once UPATH . "/Classes/class-rpc.php";
 require_once UPATH . "/Classes/class-paneluser.php";
 require_once UPATH . "/plugins.php";
 
-/* Now that plugins are loaded, read config from DB */
-read_config_db();
+/* Do various checks and reading, except during setup step 1. */
+if (!page_requires_no_config())
+{
+       /* Now that plugins are loaded, read config from DB */
+       read_config_db();
+
+       /* Check if anything needs upgrading (eg on panel version change) */
+       upgrade_check();
 
-/* And a check... */
-if (!get_config("base_url")) die("The base_url was not found in your config. Setup went wrong?");
+       /* And a check... */
+       if (!get_config("base_url"))
+               die("The base_url was not found in your config. Setup went wrong?");
+}
 
 $pages = [
        "Overview"     => "",
@@ -212,8 +357,17 @@ $pages = [
        "News" => "news.php",
 ];
 
-if (is_auth_provided())
+if (!panel_start_session())
 {
+       if (!page_requires_no_login())
+       {
+               if (!is_auth_provided())
+                       die("No authentication plugin loaded. You must load either sql_auth, file_auth, or a similar auth plugin!");
+               $current_page = $_SERVER['REQUEST_URI'];
+               header("Location: ".get_config("base_url")."login/?redirect=".urlencode($current_page));
+               die;
+       }
+} else {
        $pages["Settings"]["Accounts"] = "settings";
 
        $user = unreal_get_current_user();
@@ -223,6 +377,7 @@ if (is_auth_provided())
                $pages["Logout"] = "login/?logout=true";
        }
 }
+
 Hook::run(HOOKTYPE_NAVBAR, $pages);
 
 /* Example to add new menu item: