index.lib.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Various checks and message functions used on index page.
  5. *
  6. * @package PhpMyAdmin-Setup
  7. */
  8. if (!defined('PHPMYADMIN')) {
  9. exit;
  10. }
  11. /**
  12. * Initializes message list
  13. *
  14. * @return void
  15. */
  16. function messages_begin()
  17. {
  18. if (! isset($_SESSION['messages']) || !is_array($_SESSION['messages'])) {
  19. $_SESSION['messages'] = array('error' => array(), 'notice' => array());
  20. } else {
  21. // reset message states
  22. foreach ($_SESSION['messages'] as &$messages) {
  23. foreach ($messages as &$msg) {
  24. $msg['fresh'] = false;
  25. $msg['active'] = false;
  26. }
  27. }
  28. }
  29. }
  30. /**
  31. * Adds a new message to message list
  32. *
  33. * @param string $type one of: notice, error
  34. * @param string $id unique message identifier
  35. * @param string $title language string id (in $str array)
  36. * @param string $message message text
  37. *
  38. * @return void
  39. */
  40. function messages_set($type, $id, $title, $message)
  41. {
  42. $fresh = ! isset($_SESSION['messages'][$type][$id]);
  43. $_SESSION['messages'][$type][$id] = array(
  44. 'fresh' => $fresh,
  45. 'active' => true,
  46. 'title' => $title,
  47. 'message' => $message);
  48. }
  49. /**
  50. * Cleans up message list
  51. *
  52. * @return void
  53. */
  54. function messages_end()
  55. {
  56. foreach ($_SESSION['messages'] as &$messages) {
  57. $remove_ids = array();
  58. foreach ($messages as $id => &$msg) {
  59. if ($msg['active'] == false) {
  60. $remove_ids[] = $id;
  61. }
  62. }
  63. foreach ($remove_ids as $id) {
  64. unset($messages[$id]);
  65. }
  66. }
  67. }
  68. /**
  69. * Prints message list, must be called after messages_end()
  70. *
  71. * @return void
  72. */
  73. function messages_show_html()
  74. {
  75. $old_ids = array();
  76. foreach ($_SESSION['messages'] as $type => $messages) {
  77. foreach ($messages as $id => $msg) {
  78. echo '<div class="' . $type . '" id="' . $id . '">'
  79. . '<h4>' . $msg['title'] . '</h4>'
  80. . $msg['message'] . '</div>';
  81. if (!$msg['fresh'] && $type != 'error') {
  82. $old_ids[] = $id;
  83. }
  84. }
  85. }
  86. echo "\n" . '<script type="text/javascript">';
  87. foreach ($old_ids as $id) {
  88. echo "\nhiddenMessages.push('$id');";
  89. }
  90. echo "\n</script>\n";
  91. }
  92. /**
  93. * Checks for newest phpMyAdmin version and sets result as a new notice
  94. *
  95. * @return void
  96. */
  97. function PMA_version_check()
  98. {
  99. // version check messages should always be visible so let's make
  100. // a unique message id each time we run it
  101. $message_id = uniqid('version_check');
  102. // wait 3s at most for server response, it's enough to get information
  103. // from a working server
  104. $connection_timeout = 3;
  105. $url = 'http://phpmyadmin.net/home_page/version.json';
  106. $context = stream_context_create(
  107. array(
  108. 'http' => array('timeout' => $connection_timeout)
  109. )
  110. );
  111. $data = @file_get_contents($url, null, $context);
  112. if ($data === false) {
  113. if (function_exists('curl_init')) {
  114. $ch = curl_init($url);
  115. curl_setopt($ch, CURLOPT_HEADER, false);
  116. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  117. curl_setopt($ch, CURLOPT_TIMEOUT, $connection_timeout);
  118. $data = curl_exec($ch);
  119. curl_close($ch);
  120. } else {
  121. messages_set(
  122. 'error',
  123. $message_id,
  124. __('Version check'),
  125. __('Neither URL wrapper nor CURL is available. Version check is not possible.')
  126. );
  127. return;
  128. }
  129. }
  130. if (empty($data)) {
  131. messages_set(
  132. 'error',
  133. $message_id,
  134. __('Version check'),
  135. __('Reading of version failed. Maybe you\'re offline or the upgrade server does not respond.')
  136. );
  137. return;
  138. }
  139. $data_list = json_decode($data);
  140. $releases = $data_list->releases;
  141. $latestCompatible = PMA_Util::getLatestCompatibleVersion($releases);
  142. if ($latestCompatible != null) {
  143. $version = $latestCompatible['version'];
  144. $date = $latestCompatible['date'];
  145. } else {
  146. return;
  147. }
  148. $version_upstream = version_to_int($version);
  149. if ($version_upstream === false) {
  150. messages_set(
  151. 'error',
  152. $message_id,
  153. __('Version check'),
  154. __('Got invalid version string from server')
  155. );
  156. return;
  157. }
  158. $version_local = version_to_int($GLOBALS['PMA_Config']->get('PMA_VERSION'));
  159. if ($version_local === false) {
  160. messages_set(
  161. 'error',
  162. $message_id,
  163. __('Version check'),
  164. __('Unparsable version string')
  165. );
  166. return;
  167. }
  168. if ($version_upstream > $version_local) {
  169. $version = htmlspecialchars($version);
  170. $date = htmlspecialchars($date);
  171. messages_set(
  172. 'notice',
  173. $message_id,
  174. __('Version check'),
  175. sprintf(__('A newer version of phpMyAdmin is available and you should consider upgrading. The newest version is %s, released on %s.'), $version, $date)
  176. );
  177. } else {
  178. if ($version_local % 100 == 0) {
  179. messages_set(
  180. 'notice',
  181. $message_id,
  182. __('Version check'),
  183. PMA_sanitize(sprintf(__('You are using Git version, run [kbd]git pull[/kbd] :-)[br]The latest stable version is %s, released on %s.'), $version, $date))
  184. );
  185. } else {
  186. messages_set(
  187. 'notice',
  188. $message_id,
  189. __('Version check'),
  190. __('No newer stable version is available')
  191. );
  192. }
  193. }
  194. }
  195. /**
  196. * Calculates numerical equivalent of phpMyAdmin version string
  197. *
  198. * @param string $version version
  199. *
  200. * @return mixed false on failure, integer on success
  201. */
  202. function version_to_int($version)
  203. {
  204. $matches = array();
  205. if (!preg_match('/^(\d+)\.(\d+)\.(\d+)((\.|-(pl|rc|dev|beta|alpha))(\d+)?(-dev)?)?$/', $version, $matches)) {
  206. return false;
  207. }
  208. if (!empty($matches[6])) {
  209. switch ($matches[6]) {
  210. case 'pl':
  211. $added = 60;
  212. break;
  213. case 'rc':
  214. $added = 30;
  215. break;
  216. case 'beta':
  217. $added = 20;
  218. break;
  219. case 'alpha':
  220. $added = 10;
  221. break;
  222. case 'dev':
  223. $added = 0;
  224. break;
  225. default:
  226. messages_set(
  227. 'notice',
  228. 'version_match',
  229. __('Version check'),
  230. 'Unknown version part: ' . htmlspecialchars($matches[6])
  231. );
  232. $added = 0;
  233. break;
  234. }
  235. } else {
  236. $added = 50; // for final
  237. }
  238. if (!empty($matches[7])) {
  239. $added = $added + $matches[7];
  240. }
  241. return $matches[1] * 1000000 + $matches[2] * 10000 + $matches[3] * 100 + $added;
  242. }
  243. /**
  244. * Performs various compatibility, security and consistency checks on current config
  245. *
  246. * Outputs results to message list, must be called between messages_begin()
  247. * and messages_end()
  248. *
  249. * @return void
  250. */
  251. function perform_config_checks()
  252. {
  253. $cf = ConfigFile::getInstance();
  254. $blowfish_secret = $cf->get('blowfish_secret');
  255. $blowfish_secret_set = false;
  256. $cookie_auth_used = false;
  257. $strAllowArbitraryServerWarning = __('This %soption%s should be disabled as it allows attackers to bruteforce login to any MySQL server. If you feel this is necessary, use %strusted proxies list%s. However, IP-based protection may not be reliable if your IP belongs to an ISP where thousands of users, including you, are connected to.');
  258. $strAllowArbitraryServerWarning = sprintf($strAllowArbitraryServerWarning, '[a@?page=form&amp;formset=Features#tab_Security]', '[/a]', '[a@?page=form&amp;formset=Features#tab_Security]', '[/a]');
  259. $strBlowfishSecretMsg = __('You didn\'t have blowfish secret set and have enabled cookie authentication, so a key was automatically generated for you. It is used to encrypt cookies; you don\'t need to remember it.');
  260. $strBZipDumpWarning = __('%sBzip2 compression and decompression%s requires functions (%s) which are unavailable on this system.');
  261. $strBZipDumpWarning = sprintf($strBZipDumpWarning, '[a@?page=form&amp;formset=Features#tab_Import_export]', '[/a]', '%s');
  262. $strDirectoryNotice = __('This value should be double checked to ensure that this directory is neither world accessible nor readable or writable by other users on your server.');
  263. $strForceSSLNotice = __('This %soption%s should be enabled if your web server supports it.');
  264. $strForceSSLNotice = sprintf($strForceSSLNotice, '[a@?page=form&amp;formset=Features#tab_Security]', '[/a]');
  265. $strGZipDumpWarning = __('%sGZip compression and decompression%s requires functions (%s) which are unavailable on this system.');
  266. $strGZipDumpWarning = sprintf($strGZipDumpWarning, '[a@?page=form&amp;formset=Features#tab_Import_export]', '[/a]', '%s');
  267. $strLoginCookieValidityWarning = __('%sLogin cookie validity%s greater than 1440 seconds may cause random session invalidation if %ssession.gc_maxlifetime%s is lower than its value (currently %d).');
  268. $strLoginCookieValidityWarning = sprintf($strLoginCookieValidityWarning, '[a@?page=form&amp;formset=Features#tab_Security]', '[/a]', '[a@' . PMA_getPHPDocLink('session.configuration.php#ini.session.gc-maxlifetime') . ']', '[/a]', ini_get('session.gc_maxlifetime'));
  269. $strLoginCookieValidityWarning2 = __('%sLogin cookie validity%s should be set to 1800 seconds (30 minutes) at most. Values larger than 1800 may pose a security risk such as impersonation.');
  270. $strLoginCookieValidityWarning2 = sprintf($strLoginCookieValidityWarning2, '[a@?page=form&amp;formset=Features#tab_Security]', '[/a]');
  271. $strLoginCookieValidityWarning3 = __('If using cookie authentication and %sLogin cookie store%s is not 0, %sLogin cookie validity%s must be set to a value less or equal to it.');
  272. $strLoginCookieValidityWarning3 = sprintf($strLoginCookieValidityWarning3, '[a@?page=form&amp;formset=Features#tab_Security]', '[/a]', '[a@?page=form&amp;formset=Features#tab_Security]', '[/a]');
  273. $strSecurityInfoMsg = __('If you feel this is necessary, use additional protection settings - %shost authentication%s settings and %strusted proxies list%s. However, IP-based protection may not be reliable if your IP belongs to an ISP where thousands of users, including you, are connected to.');
  274. $strSecurityInfoMsg = sprintf($strSecurityInfoMsg, '[a@?page=servers&amp;mode=edit&amp;id=%1$d#tab_Server_config]', '[/a]', '[a@?page=form&amp;formset=Features#tab_Security]', '[/a]');
  275. $strServerAuthConfigMsg = __('You set the [kbd]config[/kbd] authentication type and included username and password for auto-login, which is not a desirable option for live hosts. Anyone who knows or guesses your phpMyAdmin URL can directly access your phpMyAdmin panel. Set %sauthentication type%s to [kbd]cookie[/kbd] or [kbd]http[/kbd].');
  276. $strServerAuthConfigMsg = sprintf($strServerAuthConfigMsg, '[a@?page=servers&amp;mode=edit&amp;id=%1$d#tab_Server]', '[/a]');
  277. $strZipDumpExportWarning = __('%sZip compression%s requires functions (%s) which are unavailable on this system.');
  278. $strZipDumpExportWarning = sprintf($strZipDumpExportWarning, '[a@?page=form&amp;formset=Features#tab_Import_export]', '[/a]', '%s');
  279. $strZipDumpImportWarning = __('%sZip decompression%s requires functions (%s) which are unavailable on this system.');
  280. $strZipDumpImportWarning = sprintf($strZipDumpImportWarning, '[a@?page=form&amp;formset=Features#tab_Import_export]', '[/a]', '%s');
  281. for ($i = 1, $server_cnt = $cf->getServerCount(); $i <= $server_cnt; $i++) {
  282. $cookie_auth_server = ($cf->getValue("Servers/$i/auth_type") == 'cookie');
  283. $cookie_auth_used |= $cookie_auth_server;
  284. $server_name = $cf->getServerName($i);
  285. if ($server_name == 'localhost') {
  286. $server_name .= " [$i]";
  287. }
  288. $server_name = htmlspecialchars($server_name);
  289. if ($cookie_auth_server && $blowfish_secret === null) {
  290. $blowfish_secret = bin2hex(crypt_random_string(32));
  291. $blowfish_secret_set = true;
  292. $cf->set('blowfish_secret', $blowfish_secret);
  293. }
  294. //
  295. // $cfg['Servers'][$i]['ssl']
  296. // should be enabled if possible
  297. //
  298. if (!$cf->getValue("Servers/$i/ssl")) {
  299. $title = PMA_lang(PMA_lang_name('Servers/1/ssl')) . " ($server_name)";
  300. messages_set(
  301. 'notice',
  302. "Servers/$i/ssl",
  303. $title,
  304. __('You should use SSL connections if your database server supports it.')
  305. );
  306. }
  307. //
  308. // $cfg['Servers'][$i]['extension']
  309. // warn about using 'mysql'
  310. //
  311. if ($cf->getValue("Servers/$i/extension") == 'mysql') {
  312. $title = PMA_lang(PMA_lang_name('Servers/1/extension'))
  313. . " ($server_name)";
  314. messages_set(
  315. 'notice',
  316. "Servers/$i/extension",
  317. $title,
  318. __('You should use mysqli for performance reasons.')
  319. );
  320. }
  321. //
  322. // $cfg['Servers'][$i]['auth_type']
  323. // warn about full user credentials if 'auth_type' is 'config'
  324. //
  325. if ($cf->getValue("Servers/$i/auth_type") == 'config'
  326. && $cf->getValue("Servers/$i/user") != ''
  327. && $cf->getValue("Servers/$i/password") != ''
  328. ) {
  329. $title = PMA_lang(PMA_lang_name('Servers/1/auth_type'))
  330. . " ($server_name)";
  331. messages_set(
  332. 'notice',
  333. "Servers/$i/auth_type",
  334. $title,
  335. PMA_lang($strServerAuthConfigMsg, $i) . ' '
  336. . PMA_lang($strSecurityInfoMsg, $i)
  337. );
  338. }
  339. //
  340. // $cfg['Servers'][$i]['AllowRoot']
  341. // $cfg['Servers'][$i]['AllowNoPassword']
  342. // serious security flaw
  343. //
  344. if ($cf->getValue("Servers/$i/AllowRoot")
  345. && $cf->getValue("Servers/$i/AllowNoPassword")
  346. ) {
  347. $title = PMA_lang(PMA_lang_name('Servers/1/AllowNoPassword'))
  348. . " ($server_name)";
  349. messages_set(
  350. 'notice',
  351. "Servers/$i/AllowNoPassword",
  352. $title,
  353. __('You allow for connecting to the server without a password.') . ' '
  354. . PMA_lang($strSecurityInfoMsg, $i)
  355. );
  356. }
  357. }
  358. //
  359. // $cfg['blowfish_secret']
  360. // it's required for 'cookie' authentication
  361. //
  362. if ($cookie_auth_used) {
  363. if ($blowfish_secret_set) {
  364. // 'cookie' auth used, blowfish_secret was generated
  365. messages_set(
  366. 'notice',
  367. 'blowfish_secret_created',
  368. PMA_lang(PMA_lang_name('blowfish_secret')),
  369. $strBlowfishSecretMsg
  370. );
  371. } else {
  372. $blowfish_warnings = array();
  373. // check length
  374. if (strlen($blowfish_secret) < 32) {
  375. // too short key
  376. $blowfish_warnings[] = __('Key is too short, it should have at least 32 characters.');
  377. }
  378. // check used characters
  379. $has_digits = (bool) preg_match('/\d/', $blowfish_secret);
  380. $has_chars = (bool) preg_match('/\S/', $blowfish_secret);
  381. $has_nonword = (bool) preg_match('/\W/', $blowfish_secret);
  382. if (!$has_digits || !$has_chars || !$has_nonword) {
  383. $blowfish_warnings[] = PMA_lang(__('Key should contain letters, numbers [em]and[/em] special characters.'));
  384. }
  385. if (!empty($blowfish_warnings)) {
  386. messages_set(
  387. 'error',
  388. 'blowfish_warnings' . count($blowfish_warnings),
  389. PMA_lang(PMA_lang_name('blowfish_secret')),
  390. implode('<br />', $blowfish_warnings)
  391. );
  392. }
  393. }
  394. }
  395. //
  396. // $cfg['ForceSSL']
  397. // should be enabled if possible
  398. //
  399. if (!$cf->getValue('ForceSSL')) {
  400. messages_set(
  401. 'notice',
  402. 'ForceSSL',
  403. PMA_lang(PMA_lang_name('ForceSSL')),
  404. PMA_lang($strForceSSLNotice)
  405. );
  406. }
  407. //
  408. // $cfg['AllowArbitraryServer']
  409. // should be disabled
  410. //
  411. if ($cf->getValue('AllowArbitraryServer')) {
  412. messages_set(
  413. 'notice',
  414. 'AllowArbitraryServer',
  415. PMA_lang(PMA_lang_name('AllowArbitraryServer')),
  416. PMA_lang($strAllowArbitraryServerWarning)
  417. );
  418. }
  419. //
  420. // $cfg['LoginCookieValidity']
  421. // value greater than session.gc_maxlifetime will cause
  422. // random session invalidation after that time
  423. if ($cf->getValue('LoginCookieValidity') > 1440
  424. || $cf->getValue('LoginCookieValidity') > ini_get('session.gc_maxlifetime')
  425. ) {
  426. $message_type = $cf->getValue('LoginCookieValidity') > ini_get('session.gc_maxlifetime')
  427. ? 'error'
  428. : 'notice';
  429. messages_set(
  430. $message_type,
  431. 'LoginCookieValidity',
  432. PMA_lang(PMA_lang_name('LoginCookieValidity')),
  433. PMA_lang($strLoginCookieValidityWarning)
  434. );
  435. }
  436. //
  437. // $cfg['LoginCookieValidity']
  438. // should be at most 1800 (30 min)
  439. //
  440. if ($cf->getValue('LoginCookieValidity') > 1800) {
  441. messages_set(
  442. 'notice',
  443. 'LoginCookieValidity',
  444. PMA_lang(PMA_lang_name('LoginCookieValidity')),
  445. PMA_lang($strLoginCookieValidityWarning2)
  446. );
  447. }
  448. //
  449. // $cfg['LoginCookieValidity']
  450. // $cfg['LoginCookieStore']
  451. // LoginCookieValidity must be less or equal to LoginCookieStore
  452. //
  453. if ($cf->getValue('LoginCookieStore') != 0
  454. && $cf->getValue('LoginCookieValidity') > $cf->getValue('LoginCookieStore')
  455. ) {
  456. messages_set(
  457. 'error',
  458. 'LoginCookieValidity',
  459. PMA_lang(PMA_lang_name('LoginCookieValidity')),
  460. PMA_lang($strLoginCookieValidityWarning3)
  461. );
  462. }
  463. //
  464. // $cfg['SaveDir']
  465. // should not be world-accessible
  466. //
  467. if ($cf->getValue('SaveDir') != '') {
  468. messages_set(
  469. 'notice',
  470. 'SaveDir',
  471. PMA_lang(PMA_lang_name('SaveDir')),
  472. PMA_lang($strDirectoryNotice)
  473. );
  474. }
  475. //
  476. // $cfg['TempDir']
  477. // should not be world-accessible
  478. //
  479. if ($cf->getValue('TempDir') != '') {
  480. messages_set(
  481. 'notice',
  482. 'TempDir',
  483. PMA_lang(PMA_lang_name('TempDir')),
  484. PMA_lang($strDirectoryNotice)
  485. );
  486. }
  487. //
  488. // $cfg['GZipDump']
  489. // requires zlib functions
  490. //
  491. if ($cf->getValue('GZipDump')
  492. && (@!function_exists('gzopen') || @!function_exists('gzencode'))
  493. ) {
  494. messages_set(
  495. 'error',
  496. 'GZipDump',
  497. PMA_lang(PMA_lang_name('GZipDump')),
  498. PMA_lang($strGZipDumpWarning, 'gzencode')
  499. );
  500. }
  501. //
  502. // $cfg['BZipDump']
  503. // requires bzip2 functions
  504. //
  505. if ($cf->getValue('BZipDump')
  506. && (!@function_exists('bzopen') || !@function_exists('bzcompress'))
  507. ) {
  508. $functions = @function_exists('bzopen')
  509. ? '' :
  510. 'bzopen';
  511. $functions .= @function_exists('bzcompress')
  512. ? ''
  513. : ($functions ? ', ' : '') . 'bzcompress';
  514. messages_set(
  515. 'error',
  516. 'BZipDump',
  517. PMA_lang(PMA_lang_name('BZipDump')),
  518. PMA_lang($strBZipDumpWarning, $functions)
  519. );
  520. }
  521. //
  522. // $cfg['ZipDump']
  523. // requires zip_open in import
  524. //
  525. if ($cf->getValue('ZipDump') && !@function_exists('zip_open')) {
  526. messages_set(
  527. 'error',
  528. 'ZipDump_import',
  529. PMA_lang(PMA_lang_name('ZipDump')),
  530. PMA_lang($strZipDumpImportWarning, 'zip_open')
  531. );
  532. }
  533. //
  534. // $cfg['ZipDump']
  535. // requires gzcompress in export
  536. //
  537. if ($cf->getValue('ZipDump') && !@function_exists('gzcompress')) {
  538. messages_set(
  539. 'error',
  540. 'ZipDump_export',
  541. PMA_lang(PMA_lang_name('ZipDump')),
  542. PMA_lang($strZipDumpExportWarning, 'gzcompress')
  543. );
  544. }
  545. }
  546. ?>