diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 461a93a0..47ddc5fd 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -47,6 +47,7 @@ jobs: [[ "$MYSQL_VERSION" == "8.0" ]] && $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < docker-entrypoint-initdb.d/caching_sha2_password_user.sql $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < docker-entrypoint-initdb.d/native_password_user.sql $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < docker-entrypoint-initdb.d/x509_user.sql + $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < docker-entrypoint-initdb.d/cleartext_user.sql - name: Install dependencies run: | cd contrib/ruby diff --git a/contrib/ruby/ext/trilogy-ruby/cext.c b/contrib/ruby/ext/trilogy-ruby/cext.c index b7e88dff..69f84511 100644 --- a/contrib/ruby/ext/trilogy-ruby/cext.c +++ b/contrib/ruby/ext/trilogy-ruby/cext.c @@ -18,12 +18,12 @@ VALUE Trilogy_CastError; static VALUE Trilogy_BaseConnectionError, Trilogy_ProtocolError, Trilogy_SSLError, Trilogy_QueryError, Trilogy_ConnectionClosedError, - Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result, Trilogy_EOFError; + Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result, Trilogy_EOFError, Trilogy_AuthPluginError; static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows, id_connect_timeout, id_read_timeout, id_write_timeout, id_keepalive_enabled, id_keepalive_idle, id_keepalive_interval, id_keepalive_count, id_ivar_affected_rows, id_ivar_fields, id_ivar_last_insert_id, id_ivar_rows, id_ivar_query_time, id_password, - id_database, id_ssl_ca, id_ssl_capath, id_ssl_cert, id_ssl_cipher, id_ssl_crl, id_ssl_crlpath, id_ssl_key, + id_database, id_enable_cleartext_plugin, id_ssl_ca, id_ssl_capath, id_ssl_cert, id_ssl_cipher, id_ssl_crl, id_ssl_crlpath, id_ssl_key, id_ssl_mode, id_tls_ciphersuites, id_tls_min_version, id_tls_max_version, id_multi_statement, id_multi_result, id_from_code, id_from_errno, id_connection_options, id_max_allowed_packet; @@ -156,6 +156,10 @@ static void handle_trilogy_error(struct trilogy_ctx *ctx, int rc, const char *ms rb_raise(Trilogy_EOFError, "%" PRIsVALUE ": TRILOGY_CLOSED_CONNECTION", rbmsg); } + case TRILOGY_AUTH_PLUGIN_ERROR: { + rb_raise(Trilogy_AuthPluginError, "%" PRIsVALUE ": TRILOGY_AUTH_PLUGIN_ERROR", rbmsg); + } + case TRILOGY_UNSUPPORTED: { rb_raise(Trilogy_BaseConnectionError, "%" PRIsVALUE ": TRILOGY_UNSUPPORTED", rbmsg); } @@ -531,6 +535,10 @@ static VALUE rb_trilogy_connect(VALUE self, VALUE encoding, VALUE charset, VALUE connopt.flags |= TRILOGY_CAPABILITIES_CONNECT_WITH_DB; } + if (RTEST(rb_hash_aref(opts, ID2SYM(id_enable_cleartext_plugin)))) { + connopt.enable_cleartext_plugin = true; + } + if (RTEST(rb_hash_aref(opts, ID2SYM(id_found_rows)))) { connopt.flags |= TRILOGY_CAPABILITIES_FOUND_ROWS; } @@ -1202,6 +1210,9 @@ RUBY_FUNC_EXPORTED void Init_cext(void) Trilogy_EOFError = rb_const_get(Trilogy, rb_intern("EOFError")); rb_global_variable(&Trilogy_EOFError); + rb_global_variable(&Trilogy_AuthPluginError); + Trilogy_AuthPluginError = rb_const_get(Trilogy, rb_intern("AuthPluginError")); + id_socket = rb_intern("socket"); id_host = rb_intern("host"); id_port = rb_intern("port"); @@ -1217,6 +1228,7 @@ RUBY_FUNC_EXPORTED void Init_cext(void) id_keepalive_count = rb_intern("keepalive_count"); id_keepalive_interval = rb_intern("keepalive_interval"); id_database = rb_intern("database"); + id_enable_cleartext_plugin = rb_intern("enable_cleartext_plugin"); id_ssl_ca = rb_intern("ssl_ca"); id_ssl_capath = rb_intern("ssl_capath"); id_ssl_cert = rb_intern("ssl_cert"); diff --git a/contrib/ruby/lib/trilogy/error.rb b/contrib/ruby/lib/trilogy/error.rb index 614d2d40..880c3a97 100644 --- a/contrib/ruby/lib/trilogy/error.rb +++ b/contrib/ruby/lib/trilogy/error.rb @@ -115,4 +115,9 @@ class ConnectionClosed < IOError # attempted on a socket which previously encountered an error. class EOFError < BaseConnectionError end + + # Occurs when the server request an auth switch to an incompatible + # authentication plugin + class AuthPluginError < Trilogy::BaseConnectionError + end end diff --git a/contrib/ruby/test/auth_test.rb b/contrib/ruby/test/auth_test.rb new file mode 100644 index 00000000..24f677e1 --- /dev/null +++ b/contrib/ruby/test/auth_test.rb @@ -0,0 +1,16 @@ +require "test_helper" + +class AuthTest < TrilogyTest + def test_cleartext_auth_plugin + client = new_tcp_client username: "cleartext_user", password: "password", enable_cleartext_plugin: true + refute_nil client + ensure + ensure_closed client + end + + def test_cleartext_auth_plugin_disabled + assert_raises Trilogy::AuthPluginError do + new_tcp_client username: "cleartext_user", password: "password" + end + end +end diff --git a/docker-compose.yml b/docker-compose.yml index 96dd91a3..48502c34 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,7 @@ services: volumes: - "db-data:/var/lib/mysql" - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d + - ./test/auth_plugins/${MYSQL_VERSION}/auth_test_plugin.so:/usr/lib/mysql/plugin/auth_test_plugin.so app: image: ghcr.io/trilogy-libraries/trilogy/ci-app:distro-${DISTRIBUTION_SLUG}-ruby-${RUBY_VERSION}-mysql-${MYSQL_VERSION} privileged: true @@ -24,7 +25,7 @@ services: args: - DISTRIBUTION=${DISTRIBUTION} - RUBY_VERSION=${RUBY_VERSION} - cache_from: + cache_from: - ghcr.io/trilogy-libraries/trilogy/ci-app:distro-${DISTRIBUTION_SLUG}-ruby-${RUBY_VERSION}-mysql-${MYSQL_VERSION} environment: MYSQL_HOST: db.local diff --git a/docker-entrypoint-initdb.d/cleartext_user.sql b/docker-entrypoint-initdb.d/cleartext_user.sql new file mode 100644 index 00000000..797e0ed2 --- /dev/null +++ b/docker-entrypoint-initdb.d/cleartext_user.sql @@ -0,0 +1,4 @@ +INSTALL PLUGIN cleartext_plugin_server SONAME 'auth_test_plugin.so'; +CREATE USER 'cleartext_user'@'%'; +GRANT ALL PRIVILEGES ON test.* TO 'cleartext_user'@'%'; +ALTER USER 'cleartext_user'@'%' IDENTIFIED WITH cleartext_plugin_server BY 'password'; diff --git a/inc/trilogy/error.h b/inc/trilogy/error.h index 225c9b39..dd0be72e 100644 --- a/inc/trilogy/error.h +++ b/inc/trilogy/error.h @@ -24,7 +24,8 @@ XX(TRILOGY_AUTH_SWITCH, -19) \ XX(TRILOGY_MAX_PACKET_EXCEEDED, -20) \ XX(TRILOGY_UNKNOWN_TYPE, -21) \ - XX(TRILOGY_TIMEOUT, -22) + XX(TRILOGY_TIMEOUT, -22) \ + XX(TRILOGY_AUTH_PLUGIN_ERROR, -23) enum { #define XX(name, code) name = code, diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index 5b66af87..a0d7e900 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -443,14 +443,16 @@ int trilogy_build_auth_packet(trilogy_builder_t *builder, const char *user, cons * pass_len - The length of password in bytes. * auth_plugin - Plugin authentication mechanism that the server requested. * scramble - The scramble value received from the server. + * enable_cleartext_plugin - Send cleartext password if requested by server. * * Return values: * TRILOGY_OK - The packet was successfully built and written to the * builder's internal buffer. * TRILOGY_SYSERR - A system error occurred, check errno. + * TRILOGY_AUTH_PLUGIN_ERROR - The server requested auth plugin is not supported. */ int trilogy_build_auth_switch_response_packet(trilogy_builder_t *builder, const char *pass, size_t pass_len, - const char *auth_plugin, const char *scramble); + const char *auth_plugin, const char *scramble, const bool enable_cleartext_plugin); /* trilogy_build_change_db_packet - Build a change database command packet. This * command will change the default database for the connection. diff --git a/inc/trilogy/socket.h b/inc/trilogy/socket.h index 010deb1f..6de19476 100644 --- a/inc/trilogy/socket.h +++ b/inc/trilogy/socket.h @@ -67,6 +67,8 @@ typedef struct { uint16_t keepalive_count; uint16_t keepalive_interval; + bool enable_cleartext_plugin; + TRILOGY_CAPABILITIES_t flags; size_t max_allowed_packet; diff --git a/script/cibuild b/script/cibuild index cebe327f..b56598c6 100755 --- a/script/cibuild +++ b/script/cibuild @@ -15,7 +15,7 @@ output_fold() { # Only echo the tags when in CI_MODE if [ "$CI_MODE" ]; then - echo "::group::{$label}" + echo "::group::${label}" fi # run the remaining arguments. If the command exits non-0, the `||` will @@ -34,7 +34,7 @@ output_fold() { function cleanup() { echo - echo "::group::{Shutting down services...}" + echo "::group::Shutting down services..." docker compose down -v echo "::endgroup::" } diff --git a/src/client.c b/src/client.c index db2fe2aa..53680620 100644 --- a/src/client.c +++ b/src/client.c @@ -248,8 +248,9 @@ static int read_auth_switch_packet(trilogy_conn_t *conn, trilogy_handshake_t *ha } if (strcmp("mysql_native_password", auth_switch_packet.auth_plugin) && - strcmp("caching_sha2_password", auth_switch_packet.auth_plugin)) { - // Only support native password & caching sha2 password here. + strcmp("caching_sha2_password", auth_switch_packet.auth_plugin) && + strcmp("mysql_clear_password", auth_switch_packet.auth_plugin)) { + // Only support native password, caching sha2 and cleartext password here. return TRILOGY_PROTOCOL_VIOLATION; } @@ -391,7 +392,7 @@ int trilogy_auth_switch_send(trilogy_conn_t *conn, const trilogy_handshake_t *ha rc = trilogy_build_auth_switch_response_packet(&builder, conn->socket->opts.password, conn->socket->opts.password_len, handshake->auth_plugin, - handshake->scramble); + handshake->scramble, conn->socket->opts.enable_cleartext_plugin); if (rc < 0) { return rc; diff --git a/src/protocol.c b/src/protocol.c index 49fe5c64..69900798 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -609,21 +609,32 @@ int trilogy_build_auth_clear_password(trilogy_builder_t *builder, const char *pa } int trilogy_build_auth_switch_response_packet(trilogy_builder_t *builder, const char *pass, size_t pass_len, - const char *auth_plugin, const char *scramble) + const char *auth_plugin, const char *scramble, const bool enable_cleartext_plugin) { int rc = TRILOGY_OK; unsigned int auth_response_len = 0; uint8_t auth_response[EVP_MAX_MD_SIZE]; if (pass_len > 0) { - if (!strcmp("caching_sha2_password", auth_plugin)) { - trilogy_pack_scramble_sha2_hash(scramble, pass, pass_len, auth_response, &auth_response_len); + if (!strcmp("mysql_clear_password", auth_plugin)) { + if (enable_cleartext_plugin) { + CHECKED(trilogy_builder_write_buffer(builder, pass, pass_len)); + } else { + return TRILOGY_AUTH_PLUGIN_ERROR; + } } else { - trilogy_pack_scramble_native_hash(scramble, pass, pass_len, auth_response, &auth_response_len); + if (!strcmp("caching_sha2_password", auth_plugin)) { + trilogy_pack_scramble_sha2_hash(scramble, pass, pass_len, auth_response, &auth_response_len); + } else if (!strcmp("mysql_native_password", auth_plugin)) { + trilogy_pack_scramble_native_hash(scramble, pass, pass_len, auth_response, &auth_response_len); + } else { + return TRILOGY_AUTH_PLUGIN_ERROR; + } + + CHECKED(trilogy_builder_write_buffer(builder, auth_response, auth_response_len)); } } - CHECKED(trilogy_builder_write_buffer(builder, auth_response, auth_response_len)); trilogy_builder_finalize(builder); return TRILOGY_OK; diff --git a/test/auth_plugins/5.7/auth_test_plugin.so b/test/auth_plugins/5.7/auth_test_plugin.so new file mode 100755 index 00000000..fc4fc1f1 Binary files /dev/null and b/test/auth_plugins/5.7/auth_test_plugin.so differ diff --git a/test/auth_plugins/8/auth_test_plugin.so b/test/auth_plugins/8/auth_test_plugin.so new file mode 100644 index 00000000..5b990734 Binary files /dev/null and b/test/auth_plugins/8/auth_test_plugin.so differ