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