]> granicus.if.org Git - php/commitdiff
Fix bug #69362 (PDO-pgsql fails to connect if password contains a leading single...
authorMatteo Beccati <mbeccati@php.net>
Thu, 11 Jun 2015 21:41:56 +0000 (23:41 +0200)
committerMatteo Beccati <mbeccati@php.net>
Thu, 11 Jun 2015 22:20:33 +0000 (00:20 +0200)
NEWS
ext/pdo_pgsql/pgsql_driver.c
ext/pdo_pgsql/tests/bug69362.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index cbebd97f785cd5dae778049c2c20dadb01e990cb..80c2f9e7d84737d1ace286888b3555f3afc76f0e 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,8 @@
 - PDO_pgsql:
   . Fixed bug #69752 (PDOStatement::execute() leaks memory with DML
     Statements when closeCuror() is u). (Philip Hofstetter)
+  . Fixed bug #69362 (PDO-pgsql fails to connect if password contains a
+    leading single quote). (Matteo)
 
 - SimpleXML:
   . Refactored the fix for bug #66084 (simplexml_load_string() mangles empty
index a5c2d18ac955ce04bf1d56d4fd6ff51a2654692f..b2c6189ee34170adf39dfe2da08265f14518f4cf 100644 (file)
@@ -27,6 +27,7 @@
 #include "php.h"
 #include "php_ini.h"
 #include "ext/standard/info.h"
+#include "ext/standard/php_string.h"
 #include "pdo/php_pdo.h"
 #include "pdo/php_pdo_driver.h"
 #include "ext/standard/file.h"
@@ -60,6 +61,17 @@ static char * _pdo_pgsql_trim_message(const char *message, int persistent)
        return tmp;
 }
 
+static char * _pdo_pgsql_escape_credentials(char *str TSRMLS_DC)
+{
+       int len;
+
+       if (str) {
+               return php_addcslashes(str, strlen(str), &len, 0, "\\'", sizeof("\\'") TSRMLS_CC);
+       }
+
+       return NULL;
+}
+
 int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *file, int line TSRMLS_DC) /* {{{ */
 {
        pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
@@ -1055,7 +1067,7 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_
        pdo_pgsql_db_handle *H;
        int ret = 0;
        char *conn_str, *p, *e;
-       char *tmp_pass;
+       char *tmp_user, *tmp_pass;
        long connect_timeout = 30;
 
        H = pecalloc(1, sizeof(pdo_pgsql_db_handle), dbh->is_persistent);
@@ -1063,7 +1075,7 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_
 
        H->einfo.errcode = 0;
        H->einfo.errmsg = NULL;
-       
+
        /* PostgreSQL wants params in the connect string to be separated by spaces,
         * if the PDO standard semicolons are used, we convert them to spaces
         */
@@ -1077,42 +1089,27 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_
                connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30 TSRMLS_CC);
        }
 
-       if (dbh->password) {
-               if (dbh->password[0] != '\'' && dbh->password[strlen(dbh->password) - 1] != '\'') {
-                       char *pwd = dbh->password;
-                       int pos = 1;
-
-                       tmp_pass = safe_emalloc(2, strlen(dbh->password), 3);
-                       tmp_pass[0] = '\'';
-
-                       while (*pwd != '\0') {
-                               if (*pwd == '\\' || *pwd == '\'') {
-                                       tmp_pass[pos++] = '\\';
-                               }
-
-                               tmp_pass[pos++] = *pwd++;
-                       }
-
-                       tmp_pass[pos++] = '\'';
-                       tmp_pass[pos] = '\0';
-               } else {
-                       tmp_pass = dbh->password;
-               }
-       }
+       /* escape username and password, if provided */
+       tmp_user = _pdo_pgsql_escape_credentials(dbh->username TSRMLS_CC);
+       tmp_pass = _pdo_pgsql_escape_credentials(dbh->password TSRMLS_CC);
 
        /* support both full connection string & connection string + login and/or password */
-       if (dbh->username && dbh->password) {
-               spprintf(&conn_str, 0, "%s user=%s password=%s connect_timeout=%ld", dbh->data_source, dbh->username, tmp_pass, connect_timeout);
-       } else if (dbh->username) {
-               spprintf(&conn_str, 0, "%s user=%s connect_timeout=%ld", dbh->data_source, dbh->username, connect_timeout);
-       } else if (dbh->password) {
-               spprintf(&conn_str, 0, "%s password=%s connect_timeout=%ld", dbh->data_source, tmp_pass, connect_timeout);
+       if (tmp_user && tmp_pass) {
+               spprintf(&conn_str, 0, "%s user='%s' password='%s' connect_timeout=%ld", dbh->data_source, tmp_user, tmp_pass, connect_timeout);
+       } else if (tmp_user) {
+               spprintf(&conn_str, 0, "%s user='%s' connect_timeout=%ld", dbh->data_source, tmp_user, connect_timeout);
+       } else if (tmp_pass) {
+               spprintf(&conn_str, 0, "%s password='%s' connect_timeout=%ld", dbh->data_source, tmp_pass, connect_timeout);
        } else {
                spprintf(&conn_str, 0, "%s connect_timeout=%ld", (char *) dbh->data_source, connect_timeout);
        }
 
        H->server = PQconnectdb(conn_str);
-       if (dbh->password && tmp_pass != dbh->password) {
+
+       if (tmp_user) {
+               efree(tmp_user);
+       }
+       if (tmp_pass) {
                efree(tmp_pass);
        }
 
diff --git a/ext/pdo_pgsql/tests/bug69362.phpt b/ext/pdo_pgsql/tests/bug69362.phpt
new file mode 100644 (file)
index 0000000..33212a8
--- /dev/null
@@ -0,0 +1,63 @@
+--TEST--
+PDO PgSQL Bug #69362 (PDO-pgsql fails to connect if password contains a leading single quote)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_pgsql')) die('skip not loaded');
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+
+$dsn = getenv('PDOTEST_DSN');
+if (empty($dsn)) die('skip no dsn found in env');
+
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+
+$user = 'pdo_test_'.rand(5, 400);
+$pass = 'testpass';
+
+// Assume that if we can't create or drop a user, this test needs to be skipped
+try {
+       $db->exec("DROP USER IF EXISTS $user");
+       $db->exec("CREATE USER $user WITH PASSWORD '$pass'");
+} catch (PDOException $e) {
+       die("skip You need CREATEUSER permissions to run the test");
+}
+
+// Peer authentication might prevent the test from properly running
+try {
+       $testConn = new PDO($dsn, $user, $pass);
+} catch (PDOException $e) {
+       echo "skip ".$e->getMessage();
+}
+
+$db->exec("DROP USER $user");
+
+?>
+--FILE--
+<?php
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+$pdo = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
+$rand = rand(5, 400);
+$user = "pdo_test_$rand";
+$template = "CREATE USER $user WITH PASSWORD '%s'";
+$dropUser = "DROP USER $user";
+$testQuery = 'SELECT 1 as verification';
+
+// Create temp user with leading single quote
+$sql = sprintf($template, "''mypassword");
+$pdo->query($sql);
+$testConn = new PDO($conf['ENV']['PDOTEST_DSN'], $user, "'mypassword");
+$result = $testConn->query($testQuery)->fetch();
+$check = $result[0];
+var_dump($check);
+
+// Remove the user
+$pdo->query($dropUser);
+
+?>
+--EXPECT--
+int(1)
+