]>
Commit | Line | Data |
---|---|---|
e98b5a51 | 1 | <?php |
81fe28b9 | 2 | if (version_compare(PHP_VERSION, '8.0.0', '<')) |
514b635f | 3 | { |
fcd08f14 | 4 | die("This webserver is using PHP version ".PHP_VERSION." but we require at least PHP 8.0.0.<br>". |
81fe28b9 BM |
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>"); | |
514b635f BM |
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 | } | |
81fe28b9 | 20 | |
91f0829e | 21 | define('UPATH', dirname(__DIR__)); |
ea90b321 BM |
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 | ||
b98a5822 | 38 | function get_current_page_helper($name, $p, &$title) |
e38f7458 BM |
39 | { |
40 | if (isset($p["script"])) | |
41 | { | |
42 | if (($p["script"] != '') && str_ends_with($_SERVER['SCRIPT_FILENAME'],$p["script"])) | |
b98a5822 BM |
43 | { |
44 | // MATCH | |
45 | if (isset($p["title"])) | |
46 | $title = $p["title"]; | |
47 | else | |
48 | $title = $name; | |
e38f7458 | 49 | return $p; |
b98a5822 | 50 | } |
e38f7458 BM |
51 | return null; |
52 | } | |
53 | foreach ($p as $k=>$v) | |
54 | { | |
b98a5822 | 55 | $ret = get_current_page_helper($k, $v, $title); |
e38f7458 BM |
56 | if ($ret !== null) |
57 | return $ret; | |
58 | } | |
59 | return null; | |
60 | } | |
61 | ||
b98a5822 BM |
62 | /** Get current page and title */ |
63 | function get_current_page(&$title) | |
e38f7458 BM |
64 | { |
65 | GLOBAL $pages; | |
66 | foreach ($pages as $k=>$v) | |
67 | { | |
b98a5822 | 68 | $ret = get_current_page_helper($k, $v, $title); |
e38f7458 BM |
69 | if ($ret !== null) |
70 | return $ret; | |
71 | } | |
72 | } | |
73 | ||
36470548 BM |
74 | function page_requires_no_config() |
75 | { | |
76 | if (str_ends_with($_SERVER['SCRIPT_FILENAME'],"install.php") || | |
2dbe2544 | 77 | str_ends_with($_SERVER['SCRIPT_FILENAME'],"installation.php")) |
36470548 BM |
78 | { |
79 | return TRUE; | |
80 | } | |
81 | return FALSE; | |
82 | } | |
83 | ||
cd1dee97 BM |
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 | ||
65fcaafb | 94 | function read_config_file() |
b41fa16f BM |
95 | { |
96 | GLOBAL $config; | |
f1027274 | 97 | GLOBAL $config_transition_unreal_server; |
b41fa16f | 98 | |
b41fa16f | 99 | $config = Array(); |
b41fa16f BM |
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 | } | |
cd1dee97 | 105 | if ((@include(UPATH . "/config/config.php")) !== 1) |
e1461204 | 106 | return false; |
f1027274 BM |
107 | if (isset($config['unrealircd'])) |
108 | $config_transition_unreal_server = true; | |
ce25dde2 BM |
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 | ||
e1461204 | 127 | return true; |
b41fa16f | 128 | } |
ea90b321 | 129 | |
65fcaafb BM |
130 | function read_config_db() |
131 | { | |
132 | GLOBAL $config; | |
133 | ||
5107db2f BM |
134 | if (page_requires_no_config()) |
135 | return; | |
136 | ||
65fcaafb BM |
137 | $merge = DbSettings::get(); |
138 | /* DB settings overwrite config.php keys: */ | |
139 | $config = array_merge($config, $merge); | |
140 | } | |
141 | ||
b41fa16f | 142 | function config_is_file_item($name) |
ea90b321 | 143 | { |
f1027274 | 144 | if (($name == "plugins") || |
65fcaafb | 145 | ($name == "mysql") || |
6b08fcb9 BM |
146 | ($name == "base_url") || |
147 | ($name == "secrets")) | |
b41fa16f BM |
148 | { |
149 | return true; | |
150 | } | |
151 | return false; | |
152 | } | |
153 | ||
154 | function write_config_file() | |
fc51fb47 | 155 | { |
b41fa16f BM |
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>"); | |
9f303b7c BM |
187 | if (function_exists('opcache_invalidate')) |
188 | opcache_invalidate($cfg_filename); | |
b41fa16f BM |
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 | */ | |
65fcaafb | 194 | return true; |
b41fa16f BM |
195 | } |
196 | ||
197 | // XXX: handle unsetting of config items :D - explicit unset function ? | |
198 | ||
199 | function write_config($setting = null) | |
2dbe2544 | 200 | { |
b41fa16f BM |
201 | GLOBAL $config; |
202 | ||
203 | /* Specific request? Easy, write only this setting to the DB (never used for file) */ | |
204 | if ($setting !== null) | |
205 | { | |
e1461204 | 206 | return DbSettings::set($setting, $config[$setting]); |
b41fa16f BM |
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 | ||
e1461204 BM |
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 | ||
6b08fcb9 BM |
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 | ||
b056895f BM |
259 | function get_active_rpc_server() |
260 | { | |
c9e0e590 BM |
261 | $servers = get_config("unrealircd"); |
262 | if (empty($servers)) | |
263 | return; | |
b056895f BM |
264 | // TODO: make user able to override this - either in user or in session |
265 | ||
c9e0e590 | 266 | foreach ($servers as $displayname=>$e) |
b056895f BM |
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 | ||
a4850187 BM |
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 | ||
e1461204 BM |
333 | function upgrade_check() |
334 | { | |
335 | GLOBAL $config_transition_unreal_server; | |
79e2577d | 336 | GLOBAL $config; |
e1461204 BM |
337 | |
338 | /* Moving of a config.php item to DB: */ | |
339 | if ($config_transition_unreal_server) | |
340 | write_config(); | |
341 | ||
6b08fcb9 | 342 | /* Our own stuff may need upgrading.. */ |
a4850187 | 343 | /* - generating secrets */ |
6b08fcb9 BM |
344 | if (!isset($config['secrets'])) |
345 | { | |
346 | generate_secrets(); | |
347 | write_config_file(); | |
348 | } | |
a4850187 BM |
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 | } | |
41aad10c BM |
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 | } | |
6b08fcb9 | 369 | |
e1461204 BM |
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 | ]; | |
6b08fcb9 | 379 | /* And inform the hook (eg the database backends) */ |
e1461204 BM |
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 | ||
d3e3ec08 BM |
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 | ||
cd1dee97 BM |
402 | $timeout = 3600; |
403 | if (isset($user->user_meta['session_timeout'])) | |
404 | $timeout = (INT)$user->user_meta['session_timeout']; | |
405 | ||
d3e3ec08 BM |
406 | if (!isset($_SESSION['session_timeout'])) |
407 | $_SESSION['session_timeout'] = $timeout; | |
408 | ||
cd1dee97 | 409 | $_SESSION['last-activity'] = time(); |
d3e3ec08 BM |
410 | return true; |
411 | } | |
412 | ||
b41fa16f | 413 | /* Now read the config, and redirect to install screen if we don't have it */ |
f1027274 | 414 | $config_transition_unreal_server = false; |
65fcaafb | 415 | if (!read_config_file()) |
fc51fb47 | 416 | { |
b41fa16f BM |
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(); | |
b41fa16f | 425 | } |
ea90b321 | 426 | } |
e1461204 | 427 | |
c06c1713 | 428 | require_once UPATH . "/Classes/class-hook.php"; |
c4f272ad BM |
429 | if (!is_dir(UPATH . "/vendor")) |
430 | die("The vendor/ directory is missing. Most likely the admin forgot to run 'composer install'\n"); | |
e98b5a51 | 431 | require_once UPATH . '/vendor/autoload.php'; |
e92763ac | 432 | require_once UPATH . "/Classes/class-cmodes.php"; |
caf7fe32 | 433 | require_once UPATH . "/inc/defines.php"; |
d72d1923 | 434 | require_once UPATH . "/misc/strings.php"; |
e92763ac | 435 | require_once UPATH . "/misc/channel-lookup-misc.php"; |
d72d1923 VP |
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"; | |
6930484c | 442 | require_once UPATH . "/Classes/class-paneluser.php"; |
d72d1923 | 443 | require_once UPATH . "/plugins.php"; |
1e6ffd06 | 444 | |
cd1dee97 BM |
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(); | |
e1461204 | 450 | |
cd1dee97 BM |
451 | /* Check if anything needs upgrading (eg on panel version change) */ |
452 | upgrade_check(); | |
65fcaafb | 453 | |
cd1dee97 BM |
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 | } | |
65fcaafb | 458 | |
5a7f0cde | 459 | $pages = [ |
e38f7458 BM |
460 | "Overview" => ["script"=>""], |
461 | "Users" => ["script"=>"users/index.php"], | |
462 | "Channels" => ["script"=>"channels/index.php"], | |
463 | "Servers" => ["script"=>"servers/index.php"], | |
d6f10d25 | 464 | "Server Bans" => [ |
e38f7458 BM |
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"], | |
d6f10d25 | 468 | ], |
e38f7458 | 469 | "Spamfilter" => ["script" => "spamfilter.php"], |
0ba71cfd | 470 | "Logs" => ["script" => "logs/index.php"], |
d6f10d25 | 471 | "Tools" => [ |
e38f7458 | 472 | "IP WHOIS" => ["script" => "tools/ip-whois.php","no_irc_server_required"=>true], |
d6f10d25 BM |
473 | ], |
474 | "Settings" => [ | |
e38f7458 BM |
475 | "Plugins" => ["script" => "settings/plugins.php","no_irc_server_required"=>true], |
476 | "RPC Servers" => ["script" => "settings/rpc-servers.php","no_irc_server_required"=>true], | |
d6f10d25 | 477 | ], |
180b8ec1 | 478 | |
e38f7458 | 479 | "News" => ["script" => "news.php","no_irc_server_required"=>true], |
5a7f0cde | 480 | ]; |
c00c34d2 | 481 | |
cd1dee97 BM |
482 | if (!panel_start_session()) |
483 | { | |
484 | if (!page_requires_no_login()) | |
485 | { | |
c51ca81c | 486 | if (!is_auth_provided()) |
ce25dde2 | 487 | die("No authentication plugin loaded. You must load either sql_db, file_db, or a similar auth plugin!"); |
cd1dee97 BM |
488 | $current_page = $_SERVER['REQUEST_URI']; |
489 | header("Location: ".get_config("base_url")."login/?redirect=".urlencode($current_page)); | |
fb06ed6c | 490 | die; |
cd1dee97 | 491 | } |
c51ca81c | 492 | } else { |
e38f7458 BM |
493 | $pages["Settings"]["Accounts"] = [ |
494 | "script" => "settings/index.php", | |
495 | "no_irc_server_required"=>true | |
496 | ]; | |
1634b6ac | 497 | if (current_user_can(PERMISSION_MANAGE_USERS)) |
e38f7458 BM |
498 | { |
499 | $pages["Settings"]["Role Editor"] = [ | |
500 | "script"=>"settings/user-role-edit.php", | |
501 | "no_irc_server_required"=>true | |
502 | ]; | |
503 | } | |
c00c34d2 VP |
504 | $user = unreal_get_current_user(); |
505 | if ($user) | |
506 | { | |
507 | /* Add logout page, if logged in */ | |
e38f7458 BM |
508 | $pages["Logout"] = [ |
509 | "script"=>"login/?logout=true", | |
510 | "no_irc_server_required"=>true | |
511 | ]; | |
c00c34d2 VP |
512 | } |
513 | } | |
c51ca81c | 514 | |
90dc8f2b VP |
515 | Hook::run(HOOKTYPE_NAVBAR, $pages); |
516 | ||
517 | /* Example to add new menu item: | |
90dc8f2b VP |
518 | * |
519 | * class MyPlugin | |
520 | * { | |
521 | * | |
55fd88eb VP |
522 | * function __construct() |
523 | * { | |
524 | * Hook::func(HOOKTYPE_NAVBAR, [$this, 'add_menu']) | |
525 | * } | |
90dc8f2b | 526 | * |
55fd88eb VP |
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 | * } | |
90dc8f2b | 533 | * } |
e38f7458 BM |
534 | */ |
535 | ||
b98a5822 | 536 | $current_page = get_current_page($current_page_name); |