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