Merge branch 'QA_5_0'
[phpmyadmin.git] / libraries / common.inc.php
blob1117f6d1f2f36486cdbdbf57742f86505f23c2ef
1 <?php
2 /**
3 * Misc stuff and REQUIRED by ALL the scripts.
4 * MUST be included by every script
6 * Among other things, it contains the advanced authentication work.
8 * Order of sections for common.inc.php:
10 * the authentication libraries must be before the connection to db
12 * ... so the required order is:
14 * LABEL_variables_init
15 * - initialize some variables always needed
16 * LABEL_parsing_config_file
17 * - parsing of the configuration file
18 * LABEL_loading_language_file
19 * - loading language file
20 * LABEL_setup_servers
21 * - check and setup configured servers
22 * LABEL_theme_setup
23 * - setting up themes
25 * - load of MySQL extension (if necessary)
26 * - loading of an authentication library
27 * - db connection
28 * - authentication work
30 declare(strict_types=1);
32 use PhpMyAdmin\Config;
33 use PhpMyAdmin\Core;
34 use PhpMyAdmin\DatabaseInterface;
35 use PhpMyAdmin\Di\Migration;
36 use PhpMyAdmin\ErrorHandler;
37 use PhpMyAdmin\LanguageManager;
38 use PhpMyAdmin\Logging;
39 use PhpMyAdmin\Message;
40 use PhpMyAdmin\MoTranslator\Loader;
41 use PhpMyAdmin\Plugins\AuthenticationPlugin;
42 use PhpMyAdmin\Response;
43 use PhpMyAdmin\Sanitize;
44 use PhpMyAdmin\Session;
45 use PhpMyAdmin\SqlParser\Lexer;
46 use PhpMyAdmin\ThemeManager;
47 use PhpMyAdmin\Tracker;
48 use PhpMyAdmin\Util;
49 use Symfony\Component\Config\FileLocator;
50 use Symfony\Component\DependencyInjection\ContainerBuilder;
51 use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
53 global $containerBuilder, $error_handler, $PMA_Config, $server, $dbi, $lang, $cfg, $isConfigLoading, $auth_plugin;
55 /**
56 * block attempts to directly run this script
58 if (getcwd() == __DIR__) {
59 die('Attack stopped');
62 /**
63 * Minimum PHP version; can't call Core::fatalError() which uses a
64 * PHP 5 function, so cannot easily localize this message.
66 if (PHP_VERSION_ID < 70103) {
67 die(
68 '<p>PHP 7.1.3+ is required.</p>'
69 . '<p>Currently installed version is: ' . PHP_VERSION . '</p>'
73 // phpcs:disable PSR1.Files.SideEffects
74 /**
75 * for verification in all procedural scripts under libraries
77 define('PHPMYADMIN', true);
78 // phpcs:enable
80 /**
81 * Load vendor configuration.
83 require_once ROOT_PATH . 'libraries/vendor_config.php';
85 /**
86 * Activate autoloader
88 if (! @is_readable(AUTOLOAD_FILE)) {
89 die(
90 '<p>File <samp>' . AUTOLOAD_FILE . '</samp> missing or not readable.</p>'
91 . '<p>Most likely you did not run Composer to '
92 . '<a href="https://docs.phpmyadmin.net/en/latest/setup.html#installing-from-git">'
93 . 'install library files</a>.</p>'
96 require_once AUTOLOAD_FILE;
98 $containerBuilder = new ContainerBuilder();
99 $loader = new PhpFileLoader($containerBuilder, new FileLocator(__DIR__));
100 $loader->load('services_loader.php');
101 /** @var Migration $diMigration */
102 $diMigration = $containerBuilder->get('di_migration');
105 * Load gettext functions.
107 Loader::loadFunctions();
109 /** @var ErrorHandler $error_handler */
110 $error_handler = $containerBuilder->get('error_handler');
113 * Warning about missing PHP extensions.
115 Core::checkExtensions();
118 * Configure required PHP settings.
120 Core::configure();
122 /* start procedural code label_start_procedural */
124 Core::cleanupPathInfo();
126 /* parsing configuration file LABEL_parsing_config_file */
128 /** @var bool $isConfigLoading Indication for the error handler */
129 $isConfigLoading = false;
132 * Force reading of config file, because we removed sensitive values
133 * in the previous iteration.
135 * @var Config $PMA_Config
137 $PMA_Config = $containerBuilder->get('config');
139 register_shutdown_function([Config::class, 'fatalErrorHandler']);
142 * include session handling after the globals, to prevent overwriting
144 if (! defined('PMA_NO_SESSION')) {
145 Session::setUp($PMA_Config, $error_handler);
149 * init some variables LABEL_variables_init
153 * holds parameters to be passed to next page
155 * @global array $url_params
157 $diMigration->setGlobal('url_params', []);
160 * holds page that should be displayed
162 * @global string $goto
164 $diMigration->setGlobal('goto', '');
165 // Security fix: disallow accessing serious server files via "?goto="
166 if (isset($_REQUEST['goto']) && Core::checkPageValidity($_REQUEST['goto'])) {
167 $diMigration->setGlobal('goto', $_REQUEST['goto']);
168 $diMigration->setGlobal('url_params', ['goto' => $_REQUEST['goto']]);
169 } else {
170 $PMA_Config->removeCookie('goto');
171 unset($_REQUEST['goto'], $_GET['goto'], $_POST['goto']);
175 * returning page
177 * @global string $back
179 if (isset($_REQUEST['back']) && Core::checkPageValidity($_REQUEST['back'])) {
180 $diMigration->setGlobal('back', $_REQUEST['back']);
181 } else {
182 $PMA_Config->removeCookie('back');
183 unset($_REQUEST['back'], $_GET['back'], $_POST['back']);
187 * Check whether user supplied token is valid, if not remove any possibly
188 * dangerous stuff from request.
190 * remember that some objects in the session with session_start and __wakeup()
191 * could access this variables before we reach this point
192 * f.e. PhpMyAdmin\Config: fontsize
194 * Check for token mismatch only if the Request method is POST
195 * GET Requests would never have token and therefore checking
196 * mis-match does not make sense
198 * @todo variables should be handled by their respective owners (objects)
199 * f.e. lang, server in PhpMyAdmin\Config
202 $token_mismatch = true;
203 $token_provided = false;
205 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
206 if (Core::isValid($_POST['token'])) {
207 $token_provided = true;
208 $token_mismatch = ! @hash_equals($_SESSION[' PMA_token '], $_POST['token']);
211 if ($token_mismatch) {
212 /* Warn in case the mismatch is result of failed setting of session cookie */
213 if (isset($_POST['set_session']) && $_POST['set_session'] != session_id()) {
214 trigger_error(
216 'Failed to set session cookie. Maybe you are using '
217 . 'HTTP instead of HTTPS to access phpMyAdmin.'
219 E_USER_ERROR
223 * We don't allow any POST operation parameters if the token is mismatched
224 * or is not provided
226 $whitelist = ['ajax_request'];
227 Sanitize::removeRequestVars($whitelist);
233 * current selected database
235 * @global string $db
237 Core::setGlobalDbOrTable('db');
240 * current selected table
242 * @global string $table
244 Core::setGlobalDbOrTable('table');
247 * Store currently selected recent table.
248 * Affect $db and $table globals
250 if (isset($_REQUEST['selected_recent_table']) && Core::isValid($_REQUEST['selected_recent_table'])) {
251 $recent_table = json_decode($_REQUEST['selected_recent_table'], true);
253 $diMigration->setGlobal(
254 'db',
255 array_key_exists('db', $recent_table) && is_string($recent_table['db']) ? $recent_table['db'] : ''
257 $diMigration->setGlobal(
258 'url_params',
259 ['db' => $containerBuilder->getParameter('db')] + $containerBuilder->getParameter('url_params')
262 $diMigration->setGlobal(
263 'table',
264 array_key_exists('table', $recent_table) && is_string($recent_table['table']) ? $recent_table['table'] : ''
266 $diMigration->setGlobal(
267 'url_params',
268 ['table' => $containerBuilder->getParameter('table')] + $containerBuilder->getParameter('url_params')
273 * SQL query to be executed
275 * @global string $sql_query
277 $diMigration->setGlobal('sql_query', '');
278 if (Core::isValid($_POST['sql_query'])) {
279 $diMigration->setGlobal('sql_query', $_POST['sql_query']);
282 //$_REQUEST['set_theme'] // checked later in this file LABEL_theme_setup
283 //$_REQUEST['server']; // checked later in this file
284 //$_REQUEST['lang']; // checked by LABEL_loading_language_file
286 /* loading language file LABEL_loading_language_file */
289 * lang detection is done here
291 $language = LanguageManager::getInstance()->selectLanguage();
292 $language->activate();
295 * check for errors occurred while loading configuration
296 * this check is done here after loading language files to present errors in locale
298 $PMA_Config->checkPermissions();
299 $PMA_Config->checkErrors();
301 /* Check server configuration */
302 Core::checkConfiguration();
304 /* Check request for possible attacks */
305 Core::checkRequest();
307 /* setup servers LABEL_setup_servers */
309 $PMA_Config->checkServers();
312 * current server
314 * @global integer $server
316 $diMigration->setGlobal('server', $PMA_Config->selectServer());
317 $diMigration->setGlobal('url_params', ['server' => $containerBuilder->getParameter('server')] + $containerBuilder->getParameter('url_params'));
320 * BC - enable backward compatibility
321 * exports all configuration settings into globals ($cfg global)
323 $PMA_Config->enableBc();
325 /* setup themes LABEL_theme_setup */
327 ThemeManager::initializeTheme();
329 /** @var DatabaseInterface $dbi */
330 $dbi = null;
332 if (! defined('PMA_MINIMUM_COMMON')) {
334 * save some settings in cookies
336 * @todo should be done in PhpMyAdmin\Config
338 $PMA_Config->setCookie('pma_lang', $lang);
340 ThemeManager::getInstance()->setThemeCookie();
342 $containerBuilder->set(DatabaseInterface::class, DatabaseInterface::load());
343 $containerBuilder->setAlias('dbi', DatabaseInterface::class);
345 if (! empty($cfg['Server'])) {
346 // get LoginCookieValidity from preferences cache
347 // no generic solution for loading preferences from cache as some settings
348 // need to be kept for processing in
349 // PhpMyAdmin\Config::loadUserPreferences()
350 $cache_key = 'server_' . $server;
351 if (isset($_SESSION['cache'][$cache_key]['userprefs']['LoginCookieValidity'])
353 $value
354 = $_SESSION['cache'][$cache_key]['userprefs']['LoginCookieValidity'];
355 $PMA_Config->set('LoginCookieValidity', $value);
356 $cfg['LoginCookieValidity'] = $value;
357 unset($value);
359 unset($cache_key);
361 // Gets the authentication library that fits the $cfg['Server'] settings
362 // and run authentication
365 * the required auth type plugin
367 $auth_class = 'PhpMyAdmin\\Plugins\\Auth\\Authentication' . ucfirst(strtolower($cfg['Server']['auth_type']));
368 if (! @class_exists($auth_class)) {
369 Core::fatalError(
370 __('Invalid authentication method set in configuration:')
371 . ' ' . $cfg['Server']['auth_type']
374 if (isset($_POST['pma_password']) && strlen($_POST['pma_password']) > 256) {
375 $_POST['pma_password'] = substr($_POST['pma_password'], 0, 256);
378 /** @var AuthenticationPlugin $auth_plugin */
379 $auth_plugin = new $auth_class();
380 $auth_plugin->authenticate();
382 // Try to connect MySQL with the control user profile (will be used to
383 // get the privileges list for the current user but the true user link
384 // must be open after this one so it would be default one for all the
385 // scripts)
386 $controllink = false;
387 if ($cfg['Server']['controluser'] != '') {
388 $controllink = $dbi->connect(
389 DatabaseInterface::CONNECT_CONTROL
393 // Connects to the server (validates user's login)
394 /** @var DatabaseInterface $userlink */
395 $userlink = $dbi->connect(DatabaseInterface::CONNECT_USER);
397 if ($userlink === false) {
398 $auth_plugin->showFailure('mysql-denied');
401 if (! $controllink) {
403 * Open separate connection for control queries, this is needed
404 * to avoid problems with table locking used in main connection
405 * and phpMyAdmin issuing queries to configuration storage, which
406 * is not locked by that time.
408 $controllink = $dbi->connect(
409 DatabaseInterface::CONNECT_USER,
410 null,
411 DatabaseInterface::CONNECT_CONTROL
415 $auth_plugin->rememberCredentials();
417 $auth_plugin->checkTwoFactor();
419 /* Log success */
420 Logging::logUser($cfg['Server']['user']);
422 if ($dbi->getVersion() < $cfg['MysqlMinVersion']['internal']) {
423 Core::fatalError(
424 __('You should upgrade to %s %s or later.'),
426 'MySQL',
427 $cfg['MysqlMinVersion']['human'],
432 // Sets the default delimiter (if specified).
433 if (! empty($_REQUEST['sql_delimiter'])) {
434 Lexer::$DEFAULT_DELIMITER = $_REQUEST['sql_delimiter'];
437 // TODO: Set SQL modes too.
438 } else { // end server connecting
439 $response = Response::getInstance();
440 $response->getHeader()->disableMenuAndConsole();
441 $response->getFooter()->setMinimal();
445 * check if profiling was requested and remember it
446 * (note: when $cfg['ServerDefault'] = 0, constant is not defined)
448 if (isset($_REQUEST['profiling'])
449 && Util::profilingSupported()
451 $_SESSION['profiling'] = true;
452 } elseif (isset($_REQUEST['profiling_form'])) {
453 // the checkbox was unchecked
454 unset($_SESSION['profiling']);
458 * Inclusion of profiling scripts is needed on various
459 * pages like sql, tbl_sql, db_sql, tbl_select
461 $response = Response::getInstance();
462 if (isset($_SESSION['profiling'])) {
463 $scripts = $response->getHeader()->getScripts();
464 $scripts->addFile('chart.js');
465 $scripts->addFile('vendor/jqplot/jquery.jqplot.js');
466 $scripts->addFile('vendor/jqplot/plugins/jqplot.pieRenderer.js');
467 $scripts->addFile('vendor/jqplot/plugins/jqplot.highlighter.js');
468 $scripts->addFile('vendor/jquery/jquery.tablesorter.js');
472 * There is no point in even attempting to process
473 * an ajax request if there is a token mismatch
475 if ($response->isAjax() && $_SERVER['REQUEST_METHOD'] == 'POST' && $token_mismatch) {
476 $response->setRequestStatus(false);
477 $response->addJSON(
478 'message',
479 Message::error(__('Error: Token mismatch'))
481 exit;
484 $containerBuilder->set('response', Response::getInstance());
487 // load user preferences
488 $PMA_Config->loadUserPreferences();
490 $containerBuilder->set('theme_manager', ThemeManager::getInstance());
492 /* Tell tracker that it can actually work */
493 Tracker::enable();
495 if (! defined('PMA_MINIMUM_COMMON')
496 && ! empty($GLOBALS['server'])
497 && isset($GLOBALS['cfg']['ZeroConf'])
498 && $GLOBALS['cfg']['ZeroConf'] == true
500 $GLOBALS['dbi']->postConnectControl();