]> jfr.im git - irc/unrealircd/unrealircd-webpanel.git/blob - inc/common.php
Simplify a line.
[irc/unrealircd/unrealircd-webpanel.git] / inc / common.php
1 <?php
2 if (version_compare(PHP_VERSION, '8.0.0', '<'))
3 die("This webserver is using PHP version ".PHP_VERSION." but we require at least PHP 8.0.0.<br>".
4 "If you already installed PHP8 but are still seeing this error, then it means ".
5 "apache/nginx/.. is loading an older PHP version. Eg. on Debian/Ubuntu you need ".
6 "<code>apt-get install libapache2-mod-php8.2</code> (or a similar version) and ".
7 "<code>apt-get remove libapache2-mod-php7.4</code> (or a similar version). ".
8 "You may also need to choose again the PHP module to load in apache via <code>a2enmod php8.2</code>");
9
10 define('UPATH', dirname(__DIR__));
11
12 function get_config($setting)
13 {
14 GLOBAL $config;
15
16 $item = $config;
17 foreach(explode("::", $setting) as $x)
18 {
19 if (isset($item[$x]))
20 $item = $item[$x];
21 else
22 return NULL;
23 }
24 return $item;
25 }
26
27 function page_requires_no_config()
28 {
29 if (str_ends_with($_SERVER['SCRIPT_FILENAME'],"install.php") ||
30 str_ends_with($_SERVER['SCRIPT_FILENAME'],"installation.php"))
31 {
32 return TRUE;
33 }
34 return FALSE;
35 }
36
37 function page_requires_no_login()
38 {
39 if (str_ends_with($_SERVER['SCRIPT_FILENAME'],"login/index.php") ||
40 page_requires_no_config())
41 {
42 return TRUE;
43 }
44 return FALSE;
45 }
46
47 function read_config_file()
48 {
49 GLOBAL $config;
50 GLOBAL $config_transition_unreal_server;
51
52 $config = Array();
53 if (!file_exists(UPATH."/config/config.php") && file_exists(UPATH."/config.php"))
54 {
55 require_once UPATH . "/config.php";
56 require_once UPATH . "/config/compat.php";
57 }
58 if ((@include(UPATH . "/config/config.php")) !== 1)
59 return false;
60 if (isset($config['unrealircd']))
61 $config_transition_unreal_server = true;
62 return true;
63 }
64
65 function read_config_db()
66 {
67 GLOBAL $config;
68
69 if (page_requires_no_config())
70 return;
71
72 $merge = DbSettings::get();
73 /* DB settings overwrite config.php keys: */
74 $config = array_merge($config, $merge);
75 }
76
77 function config_is_file_item($name)
78 {
79 if (($name == "plugins") ||
80 ($name == "mysql") ||
81 ($name == "base_url") ||
82 ($name == "secrets"))
83 {
84 return true;
85 }
86 return false;
87 }
88
89 function write_config_file()
90 {
91 GLOBAL $config;
92
93 $file_settings = [];
94 foreach($config as $k=>$v)
95 {
96 if (config_is_file_item($k))
97 $file_settings[$k] = $v;
98 }
99
100 $cfg_filename = UPATH.'/config/config.php';
101 $tmpfile = UPATH.'/config/config.tmp.'.bin2hex(random_bytes(8)).'.php';
102 $fd = fopen($tmpfile, "w");
103 if (!$fd)
104 die("Could not write to temporary config file $tmpfile.<br>We need write permissions on the config/ directory!<br>");
105
106 $str = var_export($file_settings, true);
107 if ($str === null)
108 die("Error while running write_config_file() -- weird!");
109 if (!fwrite($fd, "<?php\n".
110 "/* This config file is written automatically by the UnrealIRCd webpanel.\n".
111 " * You are not really supposed to edit it manually.\n".
112 " */\n".
113 '$config = '.$str.";\n"))
114 {
115 die("Error writing to config file $tmpfile (on fwrite).<br>");
116 }
117 if (!fclose($fd))
118 die("Error writing to config file $tmpfile (on close).<br>");
119 /* Now atomically rename the file */
120 if (!rename($tmpfile, $cfg_filename))
121 die("Could not write (rename) to file ".$cfg_filename."<br>");
122 if (function_exists('opcache_invalidate'))
123 opcache_invalidate($cfg_filename);
124
125 /* Do not re-read config, as it would reinitialize config
126 * without having the DB settings read. (And it also
127 * serves no purpose)
128 */
129 return true;
130 }
131
132 // XXX: handle unsetting of config items :D - explicit unset function ?
133
134 function write_config($setting = null)
135 {
136 GLOBAL $config;
137
138 /* Specific request? Easy, write only this setting to the DB (never used for file) */
139 if ($setting !== null)
140 {
141 return DbSettings::set($setting, $config[$setting]);
142 }
143
144 /* Otherwise write the whole config.
145 * TODO: avoid writing settings file if unneeded,
146 * as it is more noisy than db settings.
147 */
148 $db_settings = [];
149
150 foreach($config as $k=>$v)
151 {
152 if (!config_is_file_item($k))
153 $db_settings[$k] = $v;
154 }
155
156 if (!write_config_file())
157 return false;
158
159 foreach($db_settings as $k=>$v)
160 {
161 $ret = DbSettings::set($k, $v);
162 if (!$ret)
163 return $ret;
164 }
165
166 return true;
167 }
168
169 function get_version()
170 {
171 $fd = @fopen(UPATH."/.git/FETCH_HEAD", "r");
172 if ($fd === false)
173 return "unknown";
174 $line = fgets($fd, 512);
175 fclose($fd);
176 $commit = substr($line, 0, 8);
177 return $commit; /* short git commit id */
178 }
179
180 function generate_secrets()
181 {
182 GLOBAL $config;
183
184 if (!isset($config['secrets']))
185 $config['secrets'] = Array();
186
187 if (!isset($config['secrets']['pepper']))
188 $config['secrets']['pepper'] = rtrim(base64_encode(random_bytes(16)),'=');
189
190 if (!isset($config['secrets']['key']))
191 $config['secrets']['key'] = rtrim(base64_encode(sodium_crypto_aead_xchacha20poly1305_ietf_keygen()),'=');
192 }
193
194 function secret_encrypt(string $text)
195 {
196 GLOBAL $config;
197
198 $key = base64_decode($config['secrets']['key']);
199 $nonce = \random_bytes(\SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
200 $encrypted_text = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($text, '', $nonce, $key);
201 return "secret:".rtrim(base64_encode($nonce),'=').':'.rtrim(base64_encode($encrypted_text),'='); // secret:base64(NONCE):base64(ENCRYPTEDTEXT)
202 }
203
204 function secret_decrypt(string $crypted)
205 {
206 GLOBAL $config;
207
208 $key = base64_decode($config['secrets']['key']);
209 $d = explode(":", $crypted);
210 if (count($d) != 3)
211 return null;
212 $nonce = base64_decode($d[1]);
213 $ciphertext = base64_decode($d[2]);
214
215 $ret = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($ciphertext, '', $nonce, $key);
216 if ($ret === false)
217 return null;
218 return $ret;
219 }
220
221 function upgrade_check()
222 {
223 GLOBAL $config_transition_unreal_server;
224 GLOBAL $config;
225
226 /* Moving of a config.php item to DB: */
227 if ($config_transition_unreal_server)
228 write_config();
229
230 /* Our own stuff may need upgrading.. */
231 /* - generating secrets */
232 if (!isset($config['secrets']))
233 {
234 generate_secrets();
235 write_config_file();
236 }
237 /* - encrypting rpc_password */
238 if (isset($config['unrealircd']) &&
239 isset($config['unrealircd']['rpc_password']) &&
240 !str_starts_with($config['unrealircd']['rpc_password'], "secret:"))
241 {
242 $ret = secret_encrypt($config['unrealircd']['rpc_password']);
243 if ($ret !== false)
244 {
245 $config['unrealircd']['rpc_password'] = $ret;
246 write_config('unrealircd');
247 }
248 }
249
250 $version = get_version();
251 if (!isset($config['webpanel_version']))
252 $config['webpanel_version'] = '';
253 if ($version != $config['webpanel_version'])
254 {
255 $versioninfo = [
256 "old_version" => $config['webpanel_version'],
257 "new_version" => $version
258 ];
259 /* And inform the hook (eg the database backends) */
260 Hook::run(HOOKTYPE_UPGRADE, $versioninfo);
261 /* And set the new version now that the upgrade is "done" */
262 $config['webpanel_version'] = $version;
263 write_config("webpanel_version");
264 }
265 }
266
267 function panel_start_session($user = false)
268 {
269 if (!isset($_SESSION))
270 {
271 session_set_cookie_params(86400); // can't set this to session_timeout due to catch-22
272 session_start();
273 }
274
275 if ($user === false)
276 {
277 $user = unreal_get_current_user();
278 if ($user === false)
279 return false;
280 }
281
282 $timeout = 3600;
283 if (isset($user->user_meta['session_timeout']))
284 $timeout = (INT)$user->user_meta['session_timeout'];
285
286 if (!isset($_SESSION['session_timeout']))
287 $_SESSION['session_timeout'] = $timeout;
288
289 $_SESSION['last-activity'] = time();
290 return true;
291 }
292
293 /* Now read the config, and redirect to install screen if we don't have it */
294 $config_transition_unreal_server = false;
295 if (!read_config_file())
296 {
297 if (page_requires_no_config())
298 {
299 /* Allow empty conf */
300 } else
301 if (!file_exists(UPATH."/config/config.php") && !file_exists(UPATH."/config.php"))
302 {
303 header("Location: settings/install.php");
304 die();
305 }
306 }
307
308 require_once UPATH . "/Classes/class-hook.php";
309 if (!is_dir(UPATH . "/vendor"))
310 die("The vendor/ directory is missing. Most likely the admin forgot to run 'composer install'\n");
311 require_once UPATH . '/vendor/autoload.php';
312 require_once UPATH . "/Classes/class-cmodes.php";
313 require_once UPATH . "/inc/defines.php";
314 require_once UPATH . "/misc/strings.php";
315 require_once UPATH . "/misc/channel-lookup-misc.php";
316 require_once UPATH . "/misc/user-lookup-misc.php";
317 require_once UPATH . "/misc/server-lookup-misc.php";
318 require_once UPATH . "/misc/ip-whois-misc.php";
319 require_once UPATH . "/Classes/class-log.php";
320 require_once UPATH . "/Classes/class-message.php";
321 require_once UPATH . "/Classes/class-rpc.php";
322 require_once UPATH . "/Classes/class-paneluser.php";
323 require_once UPATH . "/plugins.php";
324
325 /* Do various checks and reading, except during setup step 1. */
326 if (!page_requires_no_config())
327 {
328 /* Now that plugins are loaded, read config from DB */
329 read_config_db();
330
331 /* Check if anything needs upgrading (eg on panel version change) */
332 upgrade_check();
333
334 /* And a check... */
335 if (!get_config("base_url"))
336 die("The base_url was not found in your config. Setup went wrong?");
337 }
338
339 $pages = [
340 "Overview" => "",
341 "Users" => "users",
342 "Channels" => "channels",
343 "Servers" => "servers",
344 "Server Bans" => [
345 "Server Bans" => "server-bans",
346 "Name Bans" => "server-bans/name-bans.php",
347 "Ban Exceptions" => "server-bans/ban-exceptions.php"
348 ],
349 "Spamfilter" => "spamfilter.php",
350 "Tools" => [
351 "IP WHOIS" => "tools/ip-whois.php",
352 ],
353 "Settings" => [
354 "Plugins" => "settings/plugins.php",
355 ],
356
357 "News" => "news.php",
358 ];
359
360 if (!panel_start_session())
361 {
362 if (!page_requires_no_login())
363 {
364 if (!is_auth_provided())
365 die("No authentication plugin loaded. You must load either sql_auth, file_auth, or a similar auth plugin!");
366 $current_page = $_SERVER['REQUEST_URI'];
367 header("Location: ".get_config("base_url")."login/?redirect=".urlencode($current_page));
368 die;
369 }
370 } else {
371 $pages["Settings"]["Accounts"] = "settings";
372
373 $user = unreal_get_current_user();
374 if ($user)
375 {
376 /* Add logout page, if logged in */
377 $pages["Logout"] = "login/?logout=true";
378 }
379 }
380
381 Hook::run(HOOKTYPE_NAVBAR, $pages);
382
383 /* Example to add new menu item:
384 *
385 * class MyPlugin
386 * {
387 *
388 * function __construct()
389 * {
390 * Hook::func(HOOKTYPE_NAVBAR, [$this, 'add_menu'])
391 * }
392 *
393 * function add_menu(&$pages) // this should pass by reference (using the & prefix)
394 * {
395 * $page_name = "My New Page";
396 * $page_link = "link/to/page.php";
397 * $pages[$page_name] = $page_link;
398 * }
399 * }
400 */