PostfixAdmin - web based virtual user administration interface for Postfix mail servers https://postfixadmin.github.io/postfixadmin/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

732 lines
26 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. <html lang="">
  2. <head>
  3. <meta charset="utf-8">
  4. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  7. <title>Postfix Admin - Setup</title>
  8. <link rel="shortcut icon" href="images/favicon.ico"/>
  9. <link rel="stylesheet" href="css/bootstrap-3.4.1-dist/css/bootstrap.min.css"/>
  10. <link rel="stylesheet" href="css/bootstrap.css"/>
  11. <!-- https://www.srihash.org/ -->
  12. <script src="jquery-1.12.4.min.js"
  13. integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ"
  14. crossorigin="anonymous"></script>
  15. <script src="css/bootstrap-3.4.1-dist/js/moment-with-locales.min.js"></script>
  16. <script src="css/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script>
  17. <script src="css/bootstrap-3.4.1-dist/js/bootstrap-datetimepicker.min.js"></script>
  18. </head>
  19. <body>
  20. <nav class="navbar navbar-default fixed-top">
  21. <div class="container-fluid">
  22. <div class="navbar-header">
  23. <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
  24. aria-expanded="false" aria-controls="navbar">
  25. <span class="sr-only">Toggle navigation</span>
  26. <span class="icon-bar"></span>
  27. <span class="icon-bar"></span>
  28. <span class="icon-bar"></span>
  29. </button>
  30. <a class="navbar-brand" href='main.php'><img id="login_header_logo" src="images/postbox.png"
  31. alt="Logo"/></a>
  32. </div>
  33. </div>
  34. </nav>
  35. <?php
  36. /**
  37. * Postfix Admin
  38. *
  39. * LICENSE
  40. * This source file is subject to the GPL license that is bundled with
  41. * this package in the file LICENSE.TXT.
  42. *
  43. * Further details on the project are available at http://postfixadmin.sf.net
  44. *
  45. * @version $Id$
  46. * @license GNU GPL v2 or later.
  47. *
  48. * File: setup.php
  49. * Used to help ensure a server is setup appropriately during installation/setup.
  50. */
  51. $PALANG = [];
  52. require_once(dirname(__FILE__) . '/common.php'); # make sure correct common.php is used.
  53. $configSetupPassword = Config::read_string('setup_password');
  54. $errors = [];
  55. $configSetupDone = false;
  56. $authenticated = false;
  57. $old_setup_password = false;
  58. if (strlen($configSetupPassword) == 73 && strpos($configSetupPassword, ':') == 32) {
  59. $old_setup_password = true;
  60. } elseif ($configSetupPassword != 'changeme' && $configSetupPassword != '') {
  61. $configSetupDone = true;
  62. $pass = safepost('setup_password', 'invalid');
  63. if ($pass != 'invalid') {
  64. if (password_verify(safepost('setup_password', 'invalid'), $configSetupPassword)) {
  65. $authenticated = true;
  66. } else {
  67. $errors['setup_login_password'] = "Password verification failed.";
  68. }
  69. }
  70. }
  71. ?>
  72. <?php
  73. $todo = '<span class="font-weight-bold text-warning">TODO</span>';
  74. $tick = ' ✅ ';
  75. ?>
  76. <div class="container">
  77. <div class="row">
  78. <h1 class="h1">Configure and Setup Postfixadmin</h1>
  79. <p>This page helps you setup PostfixAdmin. For further help see <a
  80. href="https://github.com/postfixadmin/postfixadmin/tree/master/DOCUMENTS">the documentation</a>.</p>
  81. <?php
  82. if (!isset($_SERVER['HTTPS'])) {
  83. echo "<h2 class='h2 text-danger'>Warning: connection not secure, switch to https if possible</h2>";
  84. } ?>
  85. <div class="col-12">
  86. <ul>
  87. <li>
  88. <?php
  89. if ($configSetupDone) {
  90. echo $tick . " setup_password configured";
  91. } else {
  92. echo $todo . " You need to have a setup_pasword hash configured in a <code>config.local.php</code> file";
  93. }
  94. ?>
  95. </li>
  96. <li>
  97. <?php
  98. if ($authenticated) {
  99. echo $tick . " You are logged in with the setup_password, some environment and hosting checks are displayed below.";
  100. } else {
  101. echo $todo . " You need to authenticate using the setup_password before you can perform some environment and hosting checks.";
  102. }
  103. ?>
  104. </li>
  105. </ul>
  106. <?php if (!$authenticated) { ?>
  107. <p> One you have logged in with the setup_password, this page will ... </p>
  108. <ul>
  109. <li> run some simple hosting/environment checks which may help identify problems with your
  110. environment
  111. </li>
  112. <li> create/update your database of choice,</li>
  113. <li> allow you to list / add super user accounts</li>
  114. </ul>
  115. <?php } ?>
  116. </div>
  117. </div>
  118. <?php
  119. if ($configSetupDone && !$authenticated) { ?>
  120. <div class="row">
  121. <div class="col-12">
  122. <h2 class="h2">Login with setup_password</h2>
  123. <form name="authenticate" class="col-2 form-horizontal" method="post">
  124. <div class="form-group">
  125. <label for="setup_password" class="col-sm-4 control-label">Setup password</label>
  126. <div class="col-sm-4">
  127. <input class="form-control" type="password" name="setup_password" minlength=5
  128. id="setup_password"
  129. value=""/>
  130. <?= _error_field($errors, 'setup_login_password'); ?>
  131. </div>
  132. </div>
  133. <div class="form-group">
  134. <div class="col-sm-offset-4 col-sm-4">
  135. <button class="btn btn-primary" type="submit" name="submit" value="setuppw">Login with
  136. setup_password.
  137. </button>
  138. </div>
  139. </div>
  140. </form>
  141. <p>If you've forgotten your super-admin password, you can generate a new one using the
  142. <em>Generate</em>
  143. form and update your <code>config.local.php</code></p>
  144. </div>
  145. </div>
  146. <?php
  147. } ?>
  148. <div class="row">
  149. <div class="col-12">
  150. <?php
  151. if (!$configSetupDone) {
  152. echo <<<EOF
  153. <p><strong>For a new installation, you must generate a 'setup_password' to go into your config.local.php file.</strong></p>
  154. <p>You can use the form below, or run something like the following in a shell - <code>php -r 'echo password_hash("password", PASSWORD_DEFAULT);'</code><p>
  155. EOF;
  156. }
  157. if ($old_setup_password) {
  158. echo '<p class="text-danger"><strong>Your setup_password is in an obsolete format. As of PostfixAdmin 3.3 it needs regenerating.</strong>';
  159. }
  160. if (!$authenticated || !$configSetupDone) { ?>
  161. <h2>Generate setup_password</h2>
  162. <?php
  163. $form_error = '';
  164. $result = '';
  165. if (safepost('form') === "setuppw") {
  166. $errors = [];
  167. # "setup password" form submitted
  168. if (safepost('setup_password', 'abc') != safepost('setup_password2')) {
  169. $errors['setup_password'] = "The two passwords differ!";
  170. $form_error = 'has-error';
  171. } else {
  172. $msgs = validate_password(safepost('setup_password'));
  173. if (empty($msgs)) {
  174. // form has been submitted; both fields filled in, so generate a new setup password.
  175. $hash = password_hash(safepost('setup_password'), PASSWORD_DEFAULT);
  176. $result = '<p>If you want to use the password you entered as setup password, edit config.inc.php or config.local.php and set</p>';
  177. $result .= "<pre>\$CONF['setup_password'] = '$hash';</pre><p>After adding, refresh this page and log in using it.</p>";
  178. } else {
  179. $form_error = 'has-error';
  180. $errors['setup_password'] = implode(', ', $msgs);
  181. }
  182. }
  183. }
  184. ?>
  185. <form name="setuppw" method="post" class="form-horizontal" action="setup.php">
  186. <input type="hidden" name="form" value="setuppw"/>
  187. <div class="form-group <?= $form_error ?>">
  188. <label for="setup_password" class="col-sm-4 control-label">Setup password</label>
  189. <div class="col-sm-4">
  190. <input class="form-control" type="password" name="setup_password" minlength=5
  191. id="setup_password"
  192. autocomplete="new-password"
  193. value=""/>
  194. <?= _error_field($errors, 'setup_password'); ?>
  195. </div>
  196. </div>
  197. <div class="form-group <?= $form_error ?>">
  198. <label for="setup_password2" class="col-sm-4 control-label">Setup password (again)</label>
  199. <div class="col-sm-4">
  200. <input class="form-control" type="password" name="setup_password2"
  201. minlength=5 id="setup_password2"
  202. autocomplete="new-password"
  203. value=""/>
  204. <?= _error_field($errors, 'setup_password2'); ?>
  205. </div>
  206. </div>
  207. <div class="form-group">
  208. <div class="col-sm-offset-4 col-sm-4">
  209. <button class="btn btn-primary" type="submit" name="submit" value="setuppw">Generate
  210. setup_password
  211. hash
  212. </button>
  213. </div>
  214. </div>
  215. </form>
  216. <?= $result ?>
  217. <?php
  218. } // end if(!$authenticated)?>
  219. </div>
  220. </div>
  221. <div class="row">
  222. <div clas="col-12">
  223. <h2 class="h2">Hosting Environment Check</h2>
  224. <?php
  225. $check = do_software_environment_check();
  226. if ($authenticated) {
  227. if (!empty($check['info'])) {
  228. echo "<h3>Information</h3><ul>";
  229. foreach ($check['info'] as $msg) {
  230. echo "<li>{$tick} {$msg}</li>";
  231. }
  232. echo "</ul>";
  233. }
  234. if (!empty($check['warn'])) {
  235. echo "<h3>Warnings</h3><ul>";
  236. foreach ($check['warn'] as $msg) {
  237. echo "<li class='text-warning'>⚠ {$msg}</li>";
  238. }
  239. echo "</ul>";
  240. }
  241. if (!empty($check['error'])) {
  242. echo "<h3>Errors (MUST be fixed)</h3><ul>";
  243. foreach ($check['error'] as $msg) {
  244. echo "<li class='text-danger'>⛔{$msg}</li>";
  245. }
  246. echo "</ul>";
  247. }
  248. $php_error_log = ini_get('error_log');
  249. } else {
  250. if (!empty($check['error'])) {
  251. echo '<h3 class="text-danger">Hosting Environment errors found. Login to see details.</h3>';
  252. }
  253. if (!empty($check['warn'])) {
  254. echo '<h3 class="text-warning">Hosting Environment warnings found. Login to see details.</h3>';
  255. }
  256. }
  257. ?>
  258. </div>
  259. </div>
  260. <div class="row">
  261. <div class="col-12">
  262. <h2 class="h2">Database Update</h2>
  263. <?php
  264. if ($authenticated) {
  265. $db = false;
  266. try {
  267. $db = db_connect();
  268. } catch (\Exception $e) {
  269. error_log("Couldn't perform PostfixAdmin database update - " . $e->getMessage());
  270. }
  271. if ($db) {
  272. print "<p>Everything seems fine... attempting to create/update database structure</p>\n";
  273. require_once(dirname(__FILE__) . '/upgrade.php');
  274. } else {
  275. echo "<h3 class='h3 text-danger'>Could not connect to database to perform updates; check PHP error log.</h3>";
  276. }
  277. } else {
  278. echo "<h3 class='h3 text-warning'>Please login to see perform database update.</h3>";
  279. }
  280. ?>
  281. </div>
  282. </div>
  283. <?php
  284. if ($authenticated) {
  285. $setupMessage = '';
  286. if (safepost("submit") === "createadmin") {
  287. echo "<div class=row><div class='col-12'>";
  288. # "create admin" form submitted, make sure the correct setup password was specified.
  289. // XXX need to ensure domains table includes an 'ALL' entry.
  290. $table_domain = table_by_key('domain');
  291. $rows = db_query_all("SELECT * FROM $table_domain WHERE domain = 'ALL'");
  292. if (empty($rows)) {
  293. // all other fields should default through the schema.
  294. db_insert('domain', array('domain' => 'ALL', 'description' => '', 'transport' => ''));
  295. }
  296. $values = array(
  297. 'username' => safepost('username'),
  298. 'password' => safepost('password'),
  299. 'password2' => safepost('password2'),
  300. 'superadmin' => 1,
  301. 'domains' => array(),
  302. 'active' => 1,
  303. );
  304. list($error, $setupMessage, $errors) = create_admin($values);
  305. if ($error == 1) {
  306. $tUsername = htmlentities($values['username']);
  307. error_log("failed to add admin - " . json_encode([$error, $setupMessage, $errors]));
  308. echo "<p class='text-danger'>Admin addition failed; check field error messages or server logs.</p>";
  309. } else {
  310. // all good!.
  311. $setupMessage .= "<p>You are done with your basic setup. <b>You can now <a href='login.php'>login to PostfixAdmin</a> using the account you just created.</b></p>";
  312. }
  313. echo "</div>";
  314. }
  315. $table_admin = table_by_key('admin');
  316. $admins = db_query_all("SELECT * FROM $table_admin WHERE superadmin = 1 AND active = 1");
  317. if (!empty($admins)) { ?>
  318. <div class="row">
  319. <div class="col-12">
  320. <h2 class="h2">Super admins</h2>
  321. <p>The following 'super-admin' accounts have already been added to the database.</p>
  322. <ul>
  323. <?php
  324. foreach ($admins as $row) {
  325. echo "<li>{$row['username']}</li>";
  326. }
  327. ?>
  328. </ul>
  329. </div>
  330. </div>
  331. <?php } ?>
  332. <div class="row">
  333. <div class="col-12">
  334. <h2>Add Superadmin Account</h2>
  335. <form name="create_admin" class="form-horizontal" method="post">
  336. <div class="form-group">
  337. <label for="setup_password" class="col-sm-4 control-label">Setup password</label>
  338. <div class="col-sm-4">
  339. <input class="form-control" type="password" required="required"
  340. name="setup_password"
  341. minlength=5
  342. value=""/>
  343. </div>
  344. </div>
  345. <div class="form-group">
  346. <label for="username" class="col-sm-4 control-label"><?= $PALANG['admin'] ?></label>
  347. <div class="col-sm-4">
  348. <input class="form-control" type="text" required="required" name="username"
  349. minlength=5
  350. id="username"
  351. value=""/>
  352. <?= _error_field($errors, 'username'); ?>
  353. </div>
  354. </div>
  355. <div class="form-group">
  356. <label for="password" class="col-sm-4 control-label"><?= $PALANG['password'] ?></label>
  357. <div class="col-sm-4">
  358. <input class="form-control" type="password" required=required
  359. name="password" minlength=5
  360. id="password" autocomplete="new-password"
  361. value=""/>
  362. <?= _error_field($errors, 'password'); ?>
  363. </div>
  364. </div>
  365. <div class="form-group">
  366. <label for="password2"
  367. class="col-sm-4 control-label"><?= $PALANG['password_again'] ?></label>
  368. <div class="col-sm-4">
  369. <input class="form-control" type="password" required=required
  370. name="password2" minlength=5
  371. id="password2" autocomplete="new-password"
  372. value=""/>
  373. <?= _error_field($errors, 'password2'); ?>
  374. </div>
  375. </div>
  376. <div class="form-group">
  377. <div class="col-sm-offset-4 col-sm-4">
  378. <button class="btn btn-primary" type="submit" name="submit"
  379. value="createadmin"><?= $PALANG['pAdminCreate_admin_button'] ?>
  380. </button>
  381. </div>
  382. </div>
  383. </form>
  384. </div>
  385. </div>
  386. <div class="row">
  387. <div class="col-12">
  388. <p class="text-success"><?= $setupMessage ?></p>
  389. </div>
  390. </div>
  391. <?php
  392. }
  393. ?>
  394. </div>
  395. <footer class="footer mt-5 bg-dark">
  396. <div class="container text-center">
  397. <a target="_blank" rel="noopener" href="https://github.com/postfixadmin/postfixadmin/blob/master/DOCUMENTS/">Documentation</a>
  398. //
  399. <a target="_blank" rel="noopener"
  400. href="https://github.com/postfixadmin/postfixadmin/">Postfix Admin</a>
  401. </div>
  402. </footer>
  403. </body>
  404. </html>
  405. <?php
  406. function _error_field($errors, $key) {
  407. if (!isset($errors[$key])) {
  408. return '';
  409. }
  410. return "<span style='color: #ff0000'>{$errors[$key]}</span>";
  411. }
  412. function create_admin($values) {
  413. define('POSTFIXADMIN_SETUP', 1); # avoids instant redirect to login.php after creating the admin
  414. $handler = new AdminHandler(1, 'setup.php');
  415. $formconf = $handler->webformConfig();
  416. if (!$handler->init($values['username'])) {
  417. return array(1, "", $handler->errormsg);
  418. }
  419. if (!$handler->set($values)) {
  420. return array(1, "", $handler->errormsg);
  421. }
  422. if (!$handler->save()) {
  423. return array(1, "", $handler->errormsg);
  424. }
  425. return array(
  426. 0,
  427. $handler->infomsg['success'],
  428. array(),
  429. );
  430. }
  431. /**
  432. * @return array['info' => string[], 'warn' => string[], 'error' => string[] ]
  433. */
  434. function do_software_environment_check() {
  435. $CONF = Config::getInstance()->getAll();
  436. $warn = [];
  437. $error = [];
  438. $info = [];
  439. //
  440. // Check for availability functions
  441. //
  442. $f_phpversion = function_exists("phpversion");
  443. $f_apache_get_version = function_exists("apache_get_version");
  444. $m_pdo = extension_loaded("PDO");
  445. $m_pdo_mysql = extension_loaded("pdo_mysql");
  446. $m_pdo_pgsql = extension_loaded('pdo_pgsql');
  447. $m_pdo_sqlite = extension_loaded("pdo_sqlite");
  448. $f_session_start = function_exists("session_start");
  449. $f_preg_match = function_exists("preg_match");
  450. $f_mb_encode_mimeheader = function_exists("mb_encode_mimeheader");
  451. $f_imap_open = function_exists("imap_open");
  452. $file_local_config = realpath(__DIR__ . "/../config.local.php");
  453. // Fall back to looking in /etc/postfixadmin for config.local.php (Debian etc)
  454. // this check might produce a false positive if someone has a legacy PostfixAdmin installation.
  455. if (!file_exists($file_local_config) && is_dir('/etc/postfixadmin')) {
  456. if (file_exists('/etc/postfixadmin/config.local.php')) {
  457. $file_local_config = '/etc/postfixadmin/config.local.php';
  458. }
  459. }
  460. // Check for PHP version
  461. $phpversion = 'unknown-version';
  462. if ($f_phpversion == 1) {
  463. if (version_compare(PHP_VERSION, '7.0.0', '<')) {
  464. $error[] = "Error: Depends on: PHP v7.0+. You must upgrade.";
  465. } else {
  466. $info[] = "PHP version - " . phpversion();
  467. }
  468. } else {
  469. $error[] = "Unable to check for PHP version. (PHP_VERSION not found?)";
  470. }
  471. // Check for Apache version
  472. if ($f_apache_get_version == 1) {
  473. $info[] = "Webserver - " . apache_get_version();
  474. }
  475. $info[] = "Postfixadmin installed at - " . realpath(__DIR__);
  476. $error_log_file = ini_get('error_log');
  477. if (file_exists($error_log_file) && is_writable($error_log_file)) {
  478. $info[] = "PHP Error log (error_log) is - $error_log_file";
  479. }
  480. if (file_exists($error_log_file) && !is_writeable($error_log_file)) {
  481. $warn[] = "PHP Error log (error_log) is - $error_log_file, but is not writeable. Postfixadmin will be unable to log error(s)";
  482. }
  483. if (file_exists($file_local_config)) {
  484. $info[] = "config.local.php file found : " . realpath($file_local_config);
  485. } else {
  486. $warn[] = "Warning: config.local.php - NOT FOUND - It's Recommended to store your own settings in config.local.php instead of editing config.inc.php";
  487. }
  488. // Check if there is support for at least 1 database
  489. if (($m_pdo == 0) and ($m_pdo_mysql == 0) and ($m_pdo_sqlite == 0) and ($m_pdo_pgsql == 0)) {
  490. $error[] = "There is no database (PDO) support in your PHP setup, you MUST install a suitable PHP PDO extension (e.g. pdo_pgsql, pdo_mysql or pdo_sqlite).";
  491. }
  492. if ($m_pdo_mysql == 1) {
  493. $info[] = "Database - MySQL support available";
  494. } else {
  495. $info[] = "Database - MySQL (pdo_mysql) extension not found";
  496. }
  497. // PostgreSQL functions
  498. if ($m_pdo_pgsql == 1) {
  499. $info[] = "Database - PostgreSQL support available ";
  500. } else {
  501. $warn[] = "Database - PostgreSQL (pdo_pgsql) extension not found";
  502. }
  503. if ($m_pdo_sqlite == 1) {
  504. $info[] = "Database - SQLite support available";
  505. if (Config::read_string('database_type') != 'sqlite') {
  506. $warn[] = "Change the database_type to 'sqlite' in config.local.php if you want to use SQLite";
  507. }
  508. } else {
  509. $warn[] = "Database support - SQLite (pdo_sqlite) extension not found";
  510. }
  511. if (empty($CONF['encrypt'])) {
  512. $error[] = 'Password hashing - $CONF["encrypt"] is empty. Please check your config.inc.php / config.local.php file.';
  513. } else {
  514. $info[] = 'Password hashing - $CONF["encrypt"] = ' . $CONF['encrypt'];
  515. try {
  516. $output = pacrypt('foobar');
  517. if ($output == 'foobar') {
  518. $warn[] = "You appear to be using a cleartext \$CONF['encrypt'] setting. This is insecure. You have been warned. Your users deserve better";
  519. }
  520. $info[] = 'Password hashing - $CONF["encrypt"] - hash generation OK';
  521. } catch (\Exception $e) {
  522. $error[] = "Password Hashing - attempted to use configured encrypt backend ({$CONF['encrypt']}) triggered an error: " . $e->getMessage();
  523. if (is_writeable($error_log_file)) {
  524. $err = "Possibly helpful error_log messages - " . htmlspecialchars(
  525. implode("",
  526. array_slice(file($error_log_file), -4, 3) // last three lines, might fail miserably if error_log is large.
  527. )
  528. );
  529. $error[] = nl2br($err);
  530. }
  531. $error[] = "You will have problems logging into PostfixAdmin.";
  532. if (preg_match('/^dovecot:/', $CONF['encrypt'])) {
  533. $error[] = "Check out our Dovecot documentation at https://github.com/postfixadmin/postfixadmin/blob/master/DOCUMENTS/DOVECOT.txt, specifically around '3. Permissions'.";
  534. }
  535. }
  536. }
  537. $link = null;
  538. $error_text = null;
  539. $dsn = 'Could not generate';
  540. try {
  541. $dsn = db_connection_string();
  542. $info[] = "Database connection configured OK (using PDO $dsn)";
  543. $link = db_connect();
  544. $info[] = "Database connection - Connected OK";
  545. } catch (Exception $e) {
  546. $error[] = "Database connection string : " . $dsn;
  547. $error[] = "Problem connecting to database, check database configuration (\$CONF['database_*'] entries in config.local.php)";
  548. $error[] = $e->getMessage();
  549. }
  550. // Session functions
  551. if ($f_session_start == 1) {
  552. $info[] = "Depends on: PHP session support - OK";
  553. } else {
  554. $error[] = "Error: Depends on: PHP session support - NOT FOUND. (FreeBSD: portinstall php$phpversion-session ?)";
  555. }
  556. // PCRE functions
  557. if ($f_preg_match == 1) {
  558. $info[] = "Depends on: PHP pcre support - OK";
  559. } else {
  560. $error[] = "Error: Depends on: PHP pcre support - NOT FOUND. (FreeBSD: portinstall php$phpversion-pcre)";
  561. }
  562. // Multibyte functions
  563. if ($f_mb_encode_mimeheader == 1) {
  564. $info[] = "Depends on: PHP mbstring support - OK";
  565. } else {
  566. $error[] = "Error: Depends on: PHP mbstring support - NOT FOUND. (FreeBSD: portinstall php$phpversion-mbstring?)";
  567. }
  568. // Imap functions
  569. if ($f_imap_open == 1) {
  570. $info[] = "Optional - PHP IMAP functions - OK";
  571. } else {
  572. $warn[] = "Warning: Optional dependency 'imap' extension missing, without this you may not be able to automate creation of sub-folders for new mailboxes";
  573. }
  574. return ['error' => $error, 'warn' => $warn, 'info' => $info];
  575. }