X-Git-Url: https://jfr.im/git/irc/unrealircd/unrealircd-webpanel.git/blobdiff_plain/90dc8f2b5b50ff38baec8b9b67ee2c10d014e924..a48501874a0020af258d0e787cd989869c766ae0:/common.php diff --git a/common.php b/common.php index af0cfa7..a7fb94e 100644 --- a/common.php +++ b/common.php @@ -1,41 +1,400 @@ ". + "If you already installed PHP8 but are still seeing this error, then it means ". + "apache/nginx/.. is loading an older PHP version. Eg. on Debian/Ubuntu you need ". + "apt-get install libapache2-mod-php8.2 (or a similar version) and ". + "apt-get remove libapache2-mod-php7.4 (or a similar version). ". + "You may also need to choose again the PHP module to load in apache via a2enmod php8.2"); + define('UPATH', dirname(__FILE__)); -require_once "config.php"; -require_once "hook.php"; -require_once "plugins.php"; + +function get_config($setting) +{ + GLOBAL $config; + + $item = $config; + foreach(explode("::", $setting) as $x) + { + if (isset($item[$x])) + $item = $item[$x]; + else + return NULL; + } + return $item; +} + +function page_requires_no_config() +{ + if (str_ends_with($_SERVER['SCRIPT_FILENAME'],"install.php") || + str_ends_with($_SERVER['SCRIPT_FILENAME'],"installation.php")) + { + return TRUE; + } + 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; + + $config = Array(); + 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); +} + +function config_is_file_item($name) +{ + if (($name == "plugins") || + ($name == "mysql") || + ($name == "base_url") || + ($name == "secrets")) + { + return true; + } + return false; +} + +function write_config_file() +{ + GLOBAL $config; + + $file_settings = []; + foreach($config as $k=>$v) + { + if (config_is_file_item($k)) + $file_settings[$k] = $v; + } + + $cfg_filename = UPATH.'/config/config.php'; + $tmpfile = UPATH.'/config/config.tmp.'.bin2hex(random_bytes(8)).'.php'; + $fd = fopen($tmpfile, "w"); + if (!$fd) + die("Could not write to temporary config file $tmpfile.
We need write permissions on the config/ directory!
"); + + $str = var_export($file_settings, true); + if ($str === null) + die("Error while running write_config_file() -- weird!"); + if (!fwrite($fd, ""); + } + if (!fclose($fd)) + die("Error writing to config file $tmpfile (on close).
"); + /* Now atomically rename the file */ + if (!rename($tmpfile, $cfg_filename)) + die("Could not write (rename) to file ".$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 + * serves no purpose) + */ + return true; +} + +// XXX: handle unsetting of config items :D - explicit unset function ? + +function write_config($setting = null) +{ + GLOBAL $config; + + /* Specific request? Easy, write only this setting to the DB (never used for file) */ + if ($setting !== null) + { + return DbSettings::set($setting, $config[$setting]); + } + + /* Otherwise write the whole config. + * TODO: avoid writing settings file if unneeded, + * as it is more noisy than db settings. + */ + $db_settings = []; + + foreach($config as $k=>$v) + { + if (!config_is_file_item($k)) + $db_settings[$k] = $v; + } + + if (!write_config_file()) + return false; + + foreach($db_settings as $k=>$v) + { + $ret = DbSettings::set($k, $v); + if (!$ret) + return $ret; + } + + 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()) + { + /* Allow empty conf */ + } else + if (!file_exists(UPATH."/config/config.php") && !file_exists(UPATH."/config.php")) + { + header("Location: settings/install.php"); + die(); + } +} + +require_once "Classes/class-hook.php"; +if (!is_dir(UPATH . "/vendor")) + die("The vendor/ directory is missing. Most likely the admin forgot to run 'composer install'\n"); require_once UPATH . '/vendor/autoload.php'; -require_once "connection.php"; -require_once "Classes/class-log.php"; -require_once "Classes/class-message.php"; -require_once "Classes/class-rpc.php"; +require_once UPATH . "/Classes/class-cmodes.php"; +require_once UPATH . "/cfg/defines.php"; +require_once UPATH . "/misc/strings.php"; +require_once UPATH . "/misc/channel-lookup-misc.php"; +require_once UPATH . "/misc/user-lookup-misc.php"; +require_once UPATH . "/misc/server-lookup-misc.php"; +require_once UPATH . "/misc/ip-whois-misc.php"; +require_once UPATH . "/Classes/class-log.php"; +require_once UPATH . "/Classes/class-message.php"; +require_once UPATH . "/Classes/class-rpc.php"; +require_once UPATH . "/Classes/class-paneluser.php"; +require_once UPATH . "/plugins.php"; + +/* 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(); -$pages = Array("Overview" => "overview.php", - "Users" => "users.php", - "Channels" => "channels.php", - "Server Bans" => "tkl.php", - "Spamfilter" => "spamfilter.php", - "News" => "news.php"); + /* And a check... */ + if (!get_config("base_url")) + die("The base_url was not found in your config. Setup went wrong?"); +} +$pages = [ + "Overview" => "", + "Users" => "users", + "Channels" => "channels", + "Servers" => "servers", + "Server Bans" => [ + "Server Bans" => "server-bans", + "Name Bans" => "server-bans/name-bans.php", + "Ban Exceptions" => "server-bans/ban-exceptions.php" + ], + "Spamfilter" => "spamfilter.php", + "Tools" => [ + "IP WHOIS" => "tools/ip-whois.php", + ], + "Settings" => [ + "Plugins" => "settings/plugins.php", + ], + + "News" => "news.php", +]; + +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(); + if ($user) + { + /* Add logout page, if logged in */ + $pages["Logout"] = "login/?logout=true"; + } +} Hook::run(HOOKTYPE_NAVBAR, $pages); /* Example to add new menu item: - * - * Example to add a navigation bar: * * class MyPlugin * { * - * function __construct() - * { - * Hook::func(HOOKTYPE_NAVBAR, [$this, 'add_menu']) - * } + * function __construct() + * { + * Hook::func(HOOKTYPE_NAVBAR, [$this, 'add_menu']) + * } * - * function add_menu(&$pages) // this should pass by reference (using the & prefix) - * { - * $page_name = "My New Page"; - * $page_link = "link/to/page.php"; - * $pages[$page_name] = $page_link; - * } + * function add_menu(&$pages) // this should pass by reference (using the & prefix) + * { + * $page_name = "My New Page"; + * $page_link = "link/to/page.php"; + * $pages[$page_name] = $page_link; + * } * } */ \ No newline at end of file