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