Skip to content

Commit 87fe0af

Browse files
committed
MySQL: add the option to force sending the password as plain text
* This re-uses the pam authentication method * pdo_mysql: added option MYSQL_ATTR_SEND_CLEAR_PASSWORD * mysqlnd: implemented CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA option in order to send auth data longer than 255 bytes
1 parent c10afa9 commit 87fe0af

9 files changed

+65
-17
lines changed

ext/mysqlnd/mysqlnd_auth.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ mysqlnd_run_authentication(
6868
memcpy(plugin_data, auth_plugin_data.s, plugin_data_len);
6969
plugin_data[plugin_data_len] = '\0';
7070

71-
requested_protocol = mnd_pestrdup(auth_protocol? auth_protocol : MYSQLND_DEFAULT_AUTH_PROTOCOL, FALSE);
71+
requested_protocol = mnd_pestrdup(mysql_flags & CLIENT_SEND_CLEAR_PASSWORD ? MYSQLND_CLEAR_PASSWORD_AUTH_PROTOCOL : (auth_protocol? auth_protocol : MYSQLND_DEFAULT_AUTH_PROTOCOL), FALSE);
7272
if (!requested_protocol) {
7373
goto end;
7474
}

ext/mysqlnd/mysqlnd_enum_n_def.h

+9-2
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@
3434

3535
#define MYSQLND_ASSEMBLED_PACKET_MAX_SIZE 3UL*1024UL*1024UL*1024UL
3636

37-
#define MYSQLND_DEFAULT_AUTH_PROTOCOL "mysql_native_password"
37+
#define MYSQLND_DEFAULT_AUTH_PROTOCOL "mysql_native_password"
38+
#define MYSQLND_CLEAR_PASSWORD_AUTH_PROTOCOL "mysql_clear_password"
3839

3940
#define MYSQLND_ERRMSG_SIZE 512
4041
#define MYSQLND_SQLSTATE_LENGTH 5
4142
#define MYSQLND_SQLSTATE_NULL "00000"
4243

4344
#define MYSQLND_MAX_ALLOWED_USER_LEN 252 /* 63 char * 4byte . MySQL supports now only 32 char, but let it be forward compatible */
45+
#define MYSQLND_MAX_ALLOWED_AUTH_LEN 4096 /* This would be a very large token! */
4446
#define MYSQLND_MAX_ALLOWED_DB_LEN 1024 /* 256 char * 4byte. MySQL supports now only 64 char in the tables, but on the FS could be different. Forward compatible. */
4547

4648
#define MYSQLND_NET_CMD_BUFFER_MIN_SIZE 4096
@@ -101,6 +103,10 @@
101103
#define CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA (1UL << 21) /* Enable authentication response packet to be larger than 255 bytes. */
102104
#define CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS (1UL << 22) /* Don't close the connection for a connection with expired password. */
103105
#define CLIENT_SESSION_TRACK (1UL << 23) /* Extended OK */
106+
/*
107+
This is a mysqlnd extension. CLIENT_IGNORE_SIGPIPE is not used anyway. We will reuse it for our case and translate it to forcing the mysql_clear_password protocol
108+
*/
109+
#define CLIENT_SEND_CLEAR_PASSWORD CLIENT_IGNORE_SIGPIPE /* Force plaintext password */
104110
/*
105111
This is a mysqlnd extension. CLIENT_ODBC is not used anyway. We will reuse it for our case and translate it to not using SSL peer verification
106112
*/
@@ -110,7 +116,8 @@
110116

111117
#define MYSQLND_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | \
112118
CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | \
113-
CLIENT_MULTI_RESULTS | CLIENT_LOCAL_FILES | CLIENT_PLUGIN_AUTH)
119+
CLIENT_MULTI_RESULTS | CLIENT_LOCAL_FILES | CLIENT_PLUGIN_AUTH | \
120+
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA)
114121

115122
#define MYSQLND_PROTOCOL_FLAG_USE_COMPRESSION 1
116123

ext/mysqlnd/mysqlnd_wireprotocol.c

+25-13
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ void php_mysqlnd_greet_free_mem(void * _packet)
520520
/* }}} */
521521

522522

523-
#define AUTH_WRITE_BUFFER_LEN (MYSQLND_HEADER_SIZE + MYSQLND_MAX_ALLOWED_USER_LEN + SCRAMBLE_LENGTH + MYSQLND_MAX_ALLOWED_DB_LEN + 1 + 4096)
523+
#define AUTH_WRITE_BUFFER_LEN (MYSQLND_HEADER_SIZE + MYSQLND_MAX_ALLOWED_USER_LEN + MYSQLND_MAX_ALLOWED_AUTH_LEN + MYSQLND_MAX_ALLOWED_DB_LEN + 1 + 4096)
524524

525525
/* {{{ php_mysqlnd_auth_write */
526526
static
@@ -561,21 +561,33 @@ size_t php_mysqlnd_auth_write(MYSQLND_CONN_DATA * conn, void * _packet)
561561
if (packet->auth_data == NULL) {
562562
packet->auth_data_len = 0;
563563
}
564-
if (packet->auth_data_len > 0xFF) {
565-
const char * const msg = "Authentication data too long. "
566-
"Won't fit into the buffer and will be truncated. Authentication will thus fail";
567-
SET_CLIENT_ERROR(error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, msg);
568-
php_error_docref(NULL, E_WARNING, "%s", msg);
569-
DBG_RETURN(0);
570-
}
571564

572-
int1store(p, (int8_t)packet->auth_data_len);
573-
++p;
565+
if (packet->client_flags & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) {
566+
if (packet->auth_data_len > MYSQLND_MAX_ALLOWED_AUTH_LEN) {
567+
const char * const msg = "Authentication data too long. "
568+
"Won't fit into the buffer and will be truncated. Authentication will thus fail";
569+
SET_CLIENT_ERROR(error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, msg);
570+
php_error_docref(NULL, E_WARNING, "%s", msg);
571+
DBG_RETURN(0);
572+
}
574573
/*!!!!! is the buffer big enough ??? */
575-
if (sizeof(buffer) < (packet->auth_data_len + (p - buffer))) {
576-
DBG_ERR("the stack buffer was not enough!!");
577-
DBG_RETURN(0);
574+
if (sizeof(buffer) < (packet->auth_data_len + (p - buffer))) {
575+
DBG_ERR("the stack buffer was not enough!!");
576+
DBG_RETURN(0);
577+
}
578+
p = php_mysqlnd_net_store_length(p, packet->auth_data_len);
579+
} else {
580+
if (packet->auth_data_len > 0xFF) {
581+
const char * const msg = "Authentication data too long. "
582+
"Won't fit into the buffer and will be truncated. Authentication will thus fail";
583+
SET_CLIENT_ERROR(error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, msg);
584+
php_error_docref(NULL, E_WARNING, "%s", msg);
585+
DBG_RETURN(0);
586+
}
587+
int1store(p, (int8_t)packet->auth_data_len);
588+
++p;
578589
}
590+
579591
if (packet->auth_data_len) {
580592
p = zend_mempcpy(p, packet->auth_data, packet->auth_data_len);
581593
}

ext/pdo_mysql/mysql_driver.c

+10
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,16 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
905905
}
906906
}
907907
#endif
908+
909+
#ifdef PDO_USE_MYSQLND
910+
{
911+
zend_long send_clear_password = pdo_attr_lval(driver_options,
912+
PDO_MYSQL_ATTR_SEND_CLEAR_PASSWORD, -1);
913+
if (send_clear_password == 1) {
914+
connect_opts |= CLIENT_SEND_CLEAR_PASSWORD;
915+
}
916+
}
917+
#endif
908918
}
909919

910920
/* Always explicitly set the LOCAL_INFILE option. */

ext/pdo_mysql/pdo_mysql.c

+3
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ static PHP_MINIT_FUNCTION(pdo_mysql)
144144
#if MYSQL_VERSION_ID >= 80021 || defined(PDO_USE_MYSQLND)
145145
REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_LOCAL_INFILE_DIRECTORY", (zend_long)PDO_MYSQL_ATTR_LOCAL_INFILE_DIRECTORY);
146146
#endif
147+
#ifdef PDO_USE_MYSQLND
148+
REGISTER_PDO_CLASS_CONST_LONG("MYSQL_ATTR_SEND_CLEAR_PASSWORD", (zend_long)PDO_MYSQL_ATTR_SEND_CLEAR_PASSWORD);
149+
#endif
147150

148151
#ifdef PDO_USE_MYSQLND
149152
mysqlnd_reverse_api_register_api(&pdo_mysql_reverse_api);

ext/pdo_mysql/pdo_mysql.stub.php

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ class Mysql extends \PDO
7272
/** @cvalue PDO_MYSQL_ATTR_LOCAL_INFILE_DIRECTORY */
7373
public const int ATTR_LOCAL_INFILE_DIRECTORY = UNKNOWN;
7474
#endif
75+
#ifdef PDO_USE_MYSQLND
76+
/** @cvalue PDO_MYSQL_ATTR_SEND_CLEAR_PASSWORD */
77+
public const int ATTR_SEND_CLEAR_PASSWORD = UNKNOWN;
78+
#endif
7579

7680
public function getWarningCount(): int {}
7781
}

ext/pdo_mysql/pdo_mysql_arginfo.h

+9-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/pdo_mysql/php_pdo_mysql_int.h

+3
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ enum {
184184
#if MYSQL_VERSION_ID >= 80021 || defined(PDO_USE_MYSQLND)
185185
PDO_MYSQL_ATTR_LOCAL_INFILE_DIRECTORY,
186186
#endif
187+
#ifdef PDO_USE_MYSQLND
188+
PDO_MYSQL_ATTR_SEND_CLEAR_PASSWORD,
189+
#endif
187190
};
188191

189192
#endif

ext/pdo_mysql/tests/pdo_mysql_class_constants.phpt

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ if (!extension_loaded('mysqli') && !extension_loaded('mysqlnd')) {
4949
if (extension_loaded('mysqlnd')) {
5050
$expected['MYSQL_ATTR_SSL_VERIFY_SERVER_CERT'] = true;
5151
$expected['MYSQL_ATTR_SERVER_PUBLIC_KEY'] = true;
52+
$expected['MYSQL_ATTR_SEND_CLEAR_PASSWORD'] = true;
5253
} else if (get_client_version() > 50605) {
5354
$expected['MYSQL_ATTR_SERVER_PUBLIC_KEY'] = true;
5455
}

0 commit comments

Comments
 (0)