Happy New Year
[squirrelmail.git] / src / configtest.php
index 095164c27de5b9db5c6e579fa4a7d7fb0e84d66a..528a57312f2b499c9b885407eed068c21e5dc203 100644 (file)
@@ -3,7 +3,7 @@
 /**
  * SquirrelMail configtest script
  *
- * @copyright © 2003-2007 The SquirrelMail Project Team
+ * @copyright 2003-2020 The SquirrelMail Project Team
  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
@@ -21,7 +21,10 @@ define('PAGE_NAME', 'configtest');
 // This script could really use some restructuring as it has grown quite rapidly
 // but is not very 'clean'. Feel free to get some structure into this thing.
 
-/** force verbose error reporting and turn on display of errors */
+// force verbose error reporting and turn on display of errors, but not before
+// getting their original values
+$php_display_errors_original_value = ini_get('display_errors');
+$php_error_reporting_original_value = ini_get('error_reporting');
 error_reporting(E_ALL);
 ini_set('display_errors',1);
 
@@ -87,6 +90,7 @@ define('SM_PATH', '../');
 require(SM_PATH . 'include/constants.php');
 require(SM_PATH . 'functions/global.php');
 require(SM_PATH . 'functions/strings.php');
+require(SM_PATH . 'functions/files.php');
 $SQM_INTERNAL_VERSION = explode('.', SM_VERSION, 3);
 $SQM_INTERNAL_VERSION[2] = intval($SQM_INTERNAL_VERSION[2]);
 
@@ -116,6 +120,9 @@ if (file_exists(SM_PATH . 'config/config_local.php')) {
     require(SM_PATH . 'config/config_local.php');
 }
 
+sqGetGlobalVar('REMOTE_ADDR',$client_ip,SQ_SERVER);
+sqGetGlobalVar('SERVER_ADDR',$server_ip,SQ_SERVER);
+
 /**
  * Include Compatibility plugin if available.
  */
@@ -171,8 +178,6 @@ if(!in_array('strings.php', $included)) {
 
 /* Block remote use of script */
 if (! $allow_remote_configtest) {
-    sqGetGlobalVar('REMOTE_ADDR',$client_ip,SQ_SERVER);
-    sqGetGlobalVar('SERVER_ADDR',$server_ip,SQ_SERVER);
 
     if ((! isset($client_ip) || $client_ip!='127.0.0.1') &&
             (! isset($client_ip) || ! isset($server_ip) || $client_ip!=$server_ip)) {
@@ -202,12 +207,39 @@ if(!check_php_version(4,1,0)) {
 
 echo $IND . 'PHP version ' . PHP_VERSION . ' OK. (You have: ' . phpversion() . ". Minimum: 4.1.0)<br />\n";
 
-echo $IND . 'display_errors: ' . ini_get('display_errors') . "<br />\n";
-
-echo $IND . 'error_reporting: ' . ini_get('error_reporting') . "<br />\n";
+// try to determine information about the user and group the web server is running as
+//
+$webOwnerID = 'N/A';
+$webOwnerInfo = array('name' => 'N/A');
+if (function_exists('posix_getuid'))
+    $webOwnerID = posix_getuid();
+if ($webOwnerID === FALSE)
+    $webOwnerID = 'N/A';
+if ($webOwnerID !== 'N/A' && function_exists('posix_getpwuid'))
+    $webOwnerInfo = posix_getpwuid($webOwnerID);
+if (!$webOwnerInfo)
+    $webOwnerInfo = array('name' => 'N/A');
+$webGroupID = 'N/A';
+$webGroupInfo = array('name' => 'N/A');
+if (function_exists('posix_getgid'))
+    $webGroupID = posix_getgid();
+if ($webGroupID === FALSE)
+    $webGroupID = 'N/A';
+if ($webGroupID !== 'N/A' && function_exists('posix_getgrgid'))
+    $webGroupInfo = posix_getgrgid($webGroupID);
+if (!$webGroupInfo)
+    $webGroupInfo = array('name' => 'N/A');
+
+echo $IND . 'Running as ' . $webOwnerInfo['name'] . '(' . $webOwnerID
+          . ') / ' . $webGroupInfo['name'] . '(' . $webGroupID . ")<br />\n";
+
+echo $IND . 'display_errors: ' . $php_display_errors_original_value . " (overridden with 1 for this page only)<br />\n";
+
+echo $IND . 'error_reporting: ' . $php_error_reporting_original_value . " (overridden with " . E_ALL . " for this page only)<br />\n";
 
 $safe_mode = ini_get('safe_mode');
 if ($safe_mode) {
+    //FIXME: should show that safe_mode is off when it is (this only shows the safe_mode setting when it's on) (also might be generally helpful to show things like open_basedir, too or even add phpinfo() output or a link to another script that has phpinfo()
     echo $IND . 'safe_mode: ' . $safe_mode;
     if (empty($prefs_dsn) || empty($addrbook_dsn))
         echo ' (<font color="red">double check data and attachment directory ownership, etc!</font>)';
@@ -236,7 +268,7 @@ else if (strpos($variables_order, 'G') === FALSE
  || strpos($variables_order, 'P') === FALSE
  || strpos($variables_order, 'C') === FALSE
  || strpos($variables_order, 'S') === FALSE) {
-    do_err('Your variables_order setting is insufficient for SquirrelMail to function. It needs at least "GPCS", but you have it set to "' . htmlspecialchars($variables_order) . '"', true);
+    do_err('Your variables_order setting is insufficient for SquirrelMail to function. It needs at least "GPCS", but you have it set to "' . sm_encode_html_special_chars($variables_order) . '"', true);
 } else {
     echo $IND . "variables_order OK: $variables_order.<br />\n";
 }
@@ -252,7 +284,7 @@ if (!check_php_version(5)) {
     else if (strpos($gpc_order, 'G') === FALSE
      || strpos($gpc_order, 'P') === FALSE
      || strpos($gpc_order, 'C') === FALSE) {
-        do_err('Your gpc_order setting is insufficient for SquirrelMail to function. It needs to be set to "GPC", but you have it set to "' . htmlspecialchars($gpc_order) . '"', true);
+        do_err('Your gpc_order setting is insufficient for SquirrelMail to function. It needs to be set to "GPC", but you have it set to "' . sm_encode_html_special_chars($gpc_order) . '"', true);
     } else {
         echo $IND . "gpc_order OK: $gpc_order.<br />\n";
     }
@@ -319,6 +351,17 @@ if (ini_get('short_open_tag') == 0) {
     do_err($short_open_tag_warning, false);
 }
 
+
+/* check who the web server is running as if possible */
+
+if ($process_info = get_process_owner_info()) {
+    echo $IND . 'Web server is running as user: ' . $process_info['name'] . ' (' . $process_info['uid'] . ")<br />\n";
+    //echo $IND . 'Web server is running as effective user: ' . $process_info['ename'] . ' (' . $process_info['euid'] . ")<br />\n";
+    echo $IND . 'Web server is running as group: ' . $process_info['group'] . ' (' . $process_info['gid'] . ")<br />\n";
+    //echo $IND . 'Web server is running as effective group: ' . $process_info['egroup'] . ' (' . $process_info['egid'] . ")<br />\n";
+}
+
+
 /* checking paths */
 
 echo "Checking paths...<br />\n";
@@ -437,7 +480,7 @@ if (isset($plugins[0])) {
 
         // if plugin outputs more than newlines and spacing, stop script execution.
         if (!empty($output)) {
-            $plugin_load_error = 'Some output was produced when plugin <i>' . $name . '</i> was loaded.  Usually this means there is an error in the plugin\'s setup or configuration file.  The output was: '.htmlspecialchars($output);
+            $plugin_load_error = 'Some output was produced when plugin <i>' . $name . '</i> was loaded.  Usually this means there is an error in the plugin\'s setup or configuration file.  The output was: '.sm_encode_html_special_chars($output);
             do_err($plugin_load_error);
         }
     }
@@ -546,9 +589,9 @@ if ( $squirrelmail_default_language != 'en_US' ) {
     echo $IND . "Default language OK.<br />\n";
 }
 
-echo $IND . "Base URL detected as: <tt>" . htmlspecialchars($test_location) .
+echo $IND . "Base URL detected as: <tt>" . sm_encode_html_special_chars($test_location) .
     "</tt> (location base " . (empty($config_location_base) ? 'autodetected' : 'set to <tt>' .
-    htmlspecialchars($config_location_base)."</tt>") . ")<br />\n";
+    sm_encode_html_special_chars($config_location_base)."</tt>") . ")<br />\n";
 
 /* check minimal requirements for other security options */
 
@@ -589,26 +632,38 @@ if($useSendmail) {
 
     echo $IND . "sendmail OK<br />\n";
 } else {
-    $stream = fsockopen( ($use_smtp_tls==1?'tls://':'').$smtpServerAddress, $smtpPort,
+    // NB: Using "ssl://" ensures the highest possible TLS version
+    // will be negotiated with the server (whereas "tls://" only
+    // uses TLS version 1.0)
+    $stream = fsockopen( ($use_smtp_tls==1?'ssl://':'').$smtpServerAddress, $smtpPort,
             $errorNumber, $errorString);
     if(!$stream) {
         do_err("Error connecting to SMTP server \"$smtpServerAddress:$smtpPort\".".
-                "Server error: ($errorNumber) ".htmlspecialchars($errorString));
+                "Server error: ($errorNumber) ".sm_encode_html_special_chars($errorString));
     }
 
     // check for SMTP code; should be 2xx to allow us access
     $smtpline = fgets($stream, 1024);
     if(((int) $smtpline{0}) > 3) {
         do_err("Error connecting to SMTP server. Server error: ".
-                htmlspecialchars($smtpline));
+                sm_encode_html_special_chars($smtpline));
     }
 
     /* smtp starttls checks */
     if ($use_smtp_tls===2) {
         // if something breaks, script should close smtp connection on exit.
 
+
+        // format EHLO argument correctly if needed
+        //
+        if (preg_match('/^\d+\.\d+\.\d+\.\d+$/', $client_ip))
+            $helohost = '[' . $client_ip . ']';
+        else // some day might add IPv6 here
+            $helohost = $client_ip;
+
+
         // say helo
-        fwrite($stream,"EHLO $client_ip\r\n");
+        fwrite($stream,"EHLO $helohost\r\n");
 
         $ehlo=array();
         $ehlo_error = false;
@@ -643,7 +698,7 @@ if($useSendmail) {
         $starttls_response=fgets($stream, 1024);
         if ($starttls_response[0]!=2) {
             $starttls_cmd_err = 'SMTP STARTTLS failed. Server replied: '
-                .htmlspecialchars($starttls_response);
+                .sm_encode_html_special_chars($starttls_response);
             do_err($starttls_cmd_err);
         } elseif(! stream_socket_enable_crypto($stream,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
             do_err('Failed to enable encryption on SMTP STARTTLS connection.');
@@ -656,7 +711,7 @@ if($useSendmail) {
     fputs($stream, 'QUIT');
     fclose($stream);
     echo $IND . 'SMTP server OK (<tt><small>'.
-            trim(htmlspecialchars($smtpline))."</small></tt>)<br />\n";
+            trim(sm_encode_html_special_chars($smtpline))."</small></tt>)<br />\n";
 
     /* POP before SMTP */
     if($pop_before_smtp) {
@@ -664,13 +719,13 @@ if($useSendmail) {
         $stream = fsockopen($pop_before_smtp_host, 110, $err_no, $err_str);
         if (!$stream) {
             do_err("Error connecting to POP Server ($pop_before_smtp_host:110) "
-                . $err_no . ' : ' . htmlspecialchars($err_str));
+                . $err_no . ' : ' . sm_encode_html_special_chars($err_str));
         }
 
         $tmp = fgets($stream, 1024);
         if (substr($tmp, 0, 3) != '+OK') {
             do_err("Error connecting to POP Server ($pop_before_smtp_host:110)"
-                . ' '.htmlspecialchars($tmp));
+                . ' '.sm_encode_html_special_chars($tmp));
         }
         fputs($stream, 'QUIT');
         fclose($stream);
@@ -684,23 +739,26 @@ if($useSendmail) {
 echo "Checking IMAP service....<br />\n";
 
 /** Can we open a connection? */
-$stream = fsockopen( ($use_imap_tls==1?'tls://':'').$imapServerAddress, $imapPort,
+// NB: Using "ssl://" ensures the highest possible TLS version
+// will be negotiated with the server (whereas "tls://" only
+// uses TLS version 1.0)
+$stream = fsockopen( ($use_imap_tls==1?'ssl://':'').$imapServerAddress, $imapPort,
         $errorNumber, $errorString);
 if(!$stream) {
     do_err("Error connecting to IMAP server \"$imapServerAddress:$imapPort\".".
             "Server error: ($errorNumber) ".
-            htmlspecialchars($errorString));
+            sm_encode_html_special_chars($errorString));
 }
 
 /** Is the first response 'OK'? */
 $imapline = fgets($stream, 1024);
 if(substr($imapline, 0,4) != '* OK') {
     do_err('Error connecting to IMAP server. Server error: '.
-            htmlspecialchars($imapline));
+            sm_encode_html_special_chars($imapline));
 }
 
 echo $IND . 'IMAP server ready (<tt><small>'.
-    htmlspecialchars(trim($imapline))."</small></tt>)<br />\n";
+    sm_encode_html_special_chars(trim($imapline))."</small></tt>)<br />\n";
 
 /** Check capabilities */
 fputs($stream, "A001 CAPABILITY\r\n");
@@ -722,7 +780,7 @@ if ($use_imap_tls===2 && stristr($capline, 'STARTTLS') === false) {
     $starttls_line=fgets($stream, 1024);
     if (! preg_match("/^A002 OK.*/i",$starttls_line)) {
         $imap_starttls_err = 'IMAP STARTTLS failed. Server replied: '
-            .htmlspecialchars($starttls_line);
+            .sm_encode_html_special_chars($starttls_line);
         do_err($imap_starttls_err);
     } elseif (! stream_socket_enable_crypto($stream,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
         do_err('Failed to enable encryption on IMAP connection.');
@@ -742,7 +800,7 @@ if ($use_imap_tls===2 && stristr($capline, 'STARTTLS') === false) {
     }
 }
 
-echo $IND . 'Capabilities: <tt>'.htmlspecialchars($capline)."</tt><br />\n";
+echo $IND . 'Capabilities: <tt>'.sm_encode_html_special_chars($capline)."</tt><br />\n";
 
 if($imap_auth_mech == 'login' && stristr($capline, 'LOGINDISABLED') !== FALSE) {
     do_err('Your server doesn\'t allow plaintext logins. '.
@@ -803,7 +861,7 @@ if (function_exists('gettext')) {
                     $display_locale = $setlocale;
                     $locale_count = 1;
                 }
-                $tested_locales_msg = 'Tested '.htmlspecialchars($display_locale).' '
+                $tested_locales_msg = 'Tested '.sm_encode_html_special_chars($display_locale).' '
                     .($locale_count>1 ? 'locales':'locale'). '.';
 
                 echo $IND . $IND .$IND . $lang_data['NAME'].' (' .$lang_code. ') - ';
@@ -814,7 +872,7 @@ if (function_exists('gettext')) {
                 } else {
                     echo 'supported. '
                         .$tested_locales_msg
-                        .' setlocale() returned "'.htmlspecialchars($retlocale).'"';
+                        .' setlocale() returned "'.sm_encode_html_special_chars($retlocale).'"';
                 }
                 echo "<br />\n";
             }
@@ -867,76 +925,119 @@ if ( (!ini_get('safe_mode')) || function_exists('date_default_timezone_set') ||
     echo "Webmail users can't change their time zone settings. \n";
 }
 if (isset($_ENV['TZ'])) {
-    echo 'Default time zone is '.htmlspecialchars($_ENV['TZ']);
+    echo 'Default time zone is '.sm_encode_html_special_chars($_ENV['TZ']);
 } else {
     echo 'Current time zone is '.date('T');
 }
 echo ".<br />\n";
 
-// Pear DB tests
+// Database tests
 echo "Checking database functions...<br />\n";
 if($addrbook_dsn || $prefs_dsn || $addrbook_global_dsn) {
-    @include_once('DB.php');
-    if (class_exists('DB')) {
-        echo "$IND PHP Pear DB support is present.<br />\n";
-        $db_functions=array(
-                'dbase' => 'dbase_open',
-                'fbsql' => 'fbsql_connect',
-                'interbase' => 'ibase_connect',
-                'informix' => 'ifx_connect',
-                'msql' => 'msql_connect',
-                'mssql' => 'mssql_connect',
-                'mysql' => 'mysql_connect',
-                'mysqli' => 'mysqli_connect',
-                'oci8' => 'ocilogon',
-                'odbc' => 'odbc_connect',
-                'pgsql' => 'pg_connect',
-                'sqlite' => 'sqlite_open',
-                'sybase' => 'sybase_connect'
-                );
-
-        $dsns = array();
-        if($prefs_dsn) {
-            $dsns['preferences'] = $prefs_dsn;
-        }
-        if($addrbook_dsn) {
-            $dsns['addressbook'] = $addrbook_dsn;
-        }
-        if($addrbook_global_dsn) {
-            $dsns['global addressbook'] = $addrbook_global_dsn;
-        }
-
-        foreach($dsns as $type => $dsn) {
-            $aDsn = explode(':', $dsn);
-            $dbtype = array_shift($aDsn);
+    $dsns = array();
+    if($prefs_dsn) {
+        $dsns['preferences'] = $prefs_dsn;
+    }
+    if($addrbook_dsn) {
+        $dsns['addressbook'] = $addrbook_dsn;
+    }
+    if($addrbook_global_dsn) {
+        $dsns['global addressbook'] = $addrbook_global_dsn;
+    }
 
-            if(isset($db_functions[$dbtype]) && function_exists($db_functions[$dbtype])) {
-                echo "$IND$dbtype database support present.<br />\n";
-            } elseif(!(bool)ini_get('enable_dl') || (bool)ini_get('safe_mode')) {
-                do_err($dbtype.' database support not present!');
-            } else {
-                // Non-fatal error
-                do_err($dbtype.' database support not present or not configured!
-                    Trying to dynamically load '.$dbtype.' extension.
-                    Please note that it is advisable to not rely on dynamic loading of extensions.', FALSE);
-            }
+    global $disable_pdo, $use_pdo;
+    if (empty($disable_pdo) && class_exists('PDO'))
+        $use_pdo = TRUE;
+    else
+        $use_pdo = FALSE;
 
+    if ($use_pdo) {
 
-            // now, test this interface:
+        // test connecting to each DSN
+        foreach($dsns as $type => $dsn) {
 
-            $dbh = DB::connect($dsn, true);
-            if (DB::isError($dbh)) {
-                do_err('Database error: '. htmlspecialchars(DB::errorMessage($dbh)) .
+            // parse and convert DSN to PDO style
+            // $matches will contain:
+            // 1: database type
+            // 2: username
+            // 3: password
+            // 4: hostname
+            // 5: database name
+//TODO: add support for unix_socket and charset
+            if (!preg_match('|^(.+)://(.+):(.+)@(.+)/(.+)$|i', $dsn, $matches)) {
+                return $this->set_error(_("Could not parse prefs DSN"));
+                do_err('DSN parse error in ' .$type .' DSN.');
+            }
+            if (preg_match('|^(.+):(\d+)$|', $matches[4], $host_port_matches)) {
+                $matches[4] = $host_port_matches[1];
+                $matches[6] = $host_port_matches[2];
+            } else
+                $matches[6] = NULL;
+            $pdo_prefs_dsn = $matches[1] . ':host=' . $matches[4] . (!empty($matches[6]) ? ';port=' . $matches[6] : '') . ';dbname=' . $matches[5];
+            try {
+                $dbh = new PDO($pdo_prefs_dsn, $matches[2], $matches[3]);
+            } catch (Exception $e) {
+                do_err('Database error: '. sm_encode_html_special_chars($e->getMessage()) .
                         ' in ' .$type .' DSN.');
             }
-            $dbh->disconnect();
             echo "$IND$type database connect successful.<br />\n";
         }
+
     } else {
-        $db_error='Required PHP PEAR DB support is not available.'
-            .' Is PEAR installed and is the include path set correctly to find <tt>DB.php</tt>?'
-            .' The include path is now: "<tt>' . ini_get('include_path') . '</tt>".';
-        do_err($db_error);
+        @include_once('DB.php');
+        if (class_exists('DB')) {
+            echo "$IND PHP Pear DB support is present, however it is deprecated and PHP PDO is recommended.<br />\n"
+                .(!empty($disable_pdo) ? "$IND You have set \$disable_pdo - if you experience errors below, try removing that.<br />\n" : '');
+            $db_functions=array(
+                    'dbase' => 'dbase_open',
+                    'fbsql' => 'fbsql_connect',
+                    'interbase' => 'ibase_connect',
+                    'informix' => 'ifx_connect',
+                    'msql' => 'msql_connect',
+                    'mssql' => 'mssql_connect',
+                    'mysql' => 'mysql_connect',
+                    'mysqli' => 'mysqli_connect',
+                    'oci8' => 'ocilogon',
+                    'odbc' => 'odbc_connect',
+                    'pgsql' => 'pg_connect',
+                    'sqlite' => 'sqlite_open',
+                    'sybase' => 'sybase_connect'
+                    );
+
+            foreach($dsns as $type => $dsn) {
+                $aDsn = explode(':', $dsn);
+                $dbtype = array_shift($aDsn);
+
+                if(isset($db_functions[$dbtype]) && function_exists($db_functions[$dbtype])) {
+                    echo "$IND$dbtype database support present.<br />\n";
+                } elseif(!(bool)ini_get('enable_dl') || (bool)ini_get('safe_mode')) {
+                    do_err($dbtype.' database support not present!');
+                } else {
+                    // Non-fatal error
+                    do_err($dbtype.' database support not present or not configured!
+                        Trying to dynamically load '.$dbtype.' extension.
+                        Please note that it is advisable to not rely on dynamic loading of extensions.', FALSE);
+                }
+
+
+                // now, test this interface:
+
+                $dbh = DB::connect($dsn, true);
+                if (DB::isError($dbh)) {
+                    do_err('Database error: '. sm_encode_html_special_chars(DB::errorMessage($dbh)) .
+                            ' in ' .$type .' DSN.');
+                }
+                $dbh->disconnect();
+                echo "$IND$type database connect successful.<br />\n";
+            }
+        } else {
+            $db_error='Required PHP PDO or PEAR DB support is not available.'
+                .(!empty($disable_pdo) ? ' You have set $disable_pdo - please try removing that.' : '')
+                .' PDO should come preinstalled with PHP version 5.1 or higher.'
+                .' Otherwise, is PEAR installed and is the include path set correctly to find <tt>DB.php</tt>?'
+                .' The include path is now: "<tt>' . ini_get('include_path') . '</tt>".';
+            do_err($db_error);
+        }
     }
 } else {
     echo $IND."not using database functionality.<br />\n";