diff --git a/Documentation/Filters/CCRFilter.md b/Documentation/Filters/CCRFilter.md index 3252c55d9..3028e1af3 100644 --- a/Documentation/Filters/CCRFilter.md +++ b/Documentation/Filters/CCRFilter.md @@ -12,6 +12,8 @@ a routing hint to all following statements. This routing hint guides the routing module to route the statement to the master server where data is guaranteed to be in an up-to-date state. +### Controlling the Filter with SQL Comments + The triggering of the filter can be limited further by adding MaxScale supported comments to queries and/or by using regular expressions. The query comments take precedence: if a comment is found it is obayed even if a regular expression diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index e65a8a3b2..dd16a9995 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -1534,8 +1534,20 @@ to `true` and provide the three files for `ssl_cert`, `ssl_key` and After this, MaxScale connections between the server and/or the client will be encrypted. Note that the database must be configured to use TLS/SSL connections -if backend connection encryption is used. When client-side encryption is -enabled, only encrypted connections to MaxScale can be created. +if backend connection encryption is used. + +**Note:** MaxScale does not allow mixed use of TLS/SSL and normal connections on + the same port. + +If TLS encryption is enabled for a listener, any unencrypted connections to it +will be rejected. MaxScale does this to improve security by preventing +accidental creation on unencrypted connections. + +The separation of secure and insecure connections differs from the MariaDB +server which allows both secure and insecure connections on the same port. As +MaxScale is the gateway through which all connections go, in order to guarantee +a more secure system MaxScale enforces a stricter security policy than what the +server does. #### `ssl` diff --git a/avro/maxavro_file.c b/avro/maxavro_file.c index 6cb6dada9..532ec74fb 100644 --- a/avro/maxavro_file.c +++ b/avro/maxavro_file.c @@ -318,7 +318,6 @@ MAXAVRO_FILE* maxavro_file_open(const char* filename) } else { - MXS_ERROR("Failed to initialize avrofile."); maxavro_schema_free(avrofile->schema); error = true; } diff --git a/connectors/cdc-connector/cdc_connector.cpp b/connectors/cdc-connector/cdc_connector.cpp index be751488a..f5913bf00 100644 --- a/connectors/cdc-connector/cdc_connector.cpp +++ b/connectors/cdc-connector/cdc_connector.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -121,7 +122,7 @@ public: Closer(T t): m_t(t), - m_close(false) + m_close(true) { } @@ -185,7 +186,6 @@ Connection::Connection(const std::string& address, m_timeout(timeout), m_connected(false) { - m_buf_ptr = m_buffer.begin(); } Connection::~Connection() @@ -276,7 +276,7 @@ bool Connection::connect(const std::string& table, const std::string& gtid) m_error = "Failed to write request: "; m_error += strerror_r(errno, err, sizeof(err)); } - else if ((m_first_row = read())) + else if (read_schema()) { rval = true; } @@ -391,18 +391,13 @@ SRow Connection::process_row(json_t* js) return rval; } -SRow Connection::read() +bool Connection::read_schema() { m_error.clear(); - SRow rval; + bool rval = false; std::string row; - if (m_first_row) - { - rval.swap(m_first_row); - assert(!m_first_row); - } - else if (read_row(row)) + if (read_row(row)) { json_error_t err; json_t* js = json_loads(row.c_str(), JSON_ALLOW_NUL, &err); @@ -413,11 +408,7 @@ SRow Connection::read() { m_schema = row; process_schema(js); - rval = Connection::read(); - } - else - { - rval = process_row(js); + rval = true; } json_decref(js); @@ -429,6 +420,40 @@ SRow Connection::read() } } + if (m_error == CDC::TIMEOUT) + { + assert(rval == false); + m_error += ". Data received so far: '"; + std::copy(m_buffer.begin(), m_buffer.end(), std::back_inserter(m_error)); + m_error += "'"; + } + + return rval; +} + +SRow Connection::read() +{ + m_error.clear(); + SRow rval; + std::string row; + + if (read_row(row)) + { + json_error_t err; + json_t* js = json_loads(row.c_str(), JSON_ALLOW_NUL, &err); + + if (js) + { + rval = process_row(js); + json_decref(js); + } + else + { + m_error = "Failed to parse JSON: "; + m_error += err.text; + } + } + return rval; } @@ -517,14 +542,14 @@ bool Connection::do_registration() return rval; } -bool Connection::is_error(const char* str) +bool Connection::is_error() { bool rval = false; - if (str[0] == 'E' && str[1] == 'R' && str[2] == 'R') + if (m_buffer.size() >= 3 && m_buffer[0] == 'E' && m_buffer[1] == 'R' && m_buffer[2] == 'R') { m_error = "MaxScale responded with an error: "; - m_error += str; + m_error.append(m_buffer.begin(), m_buffer.end()); rval = true; } @@ -539,11 +564,19 @@ bool Connection::read_row(std::string& dest) { if (!m_buffer.empty()) { - std::vector::iterator it = std::find(m_buf_ptr, m_buffer.end(), '\n'); + if (is_error()) + { + rval = false; + break; + } + + std::deque::iterator it = std::find(m_buffer.begin(), m_buffer.end(), '\n'); + if (it != m_buffer.end()) { - dest.assign(m_buf_ptr, it); - m_buf_ptr = it + 1; + dest.assign(m_buffer.begin(), it); + m_buffer.erase(m_buffer.begin(), std::next(it)); + assert(m_buffer.empty() || m_buffer[0] != '\n'); break; } } @@ -566,27 +599,14 @@ bool Connection::read_row(std::string& dest) break; } - if (!m_connected) - { - // This is here to work around a missing newline in MaxScale error messages - buf[rc] = '\0'; - - if (is_error(buf)) - { - rval = false; - break; - } - } - - m_buffer.erase(m_buffer.begin(), m_buf_ptr); assert(std::find(m_buffer.begin(), m_buffer.end(), '\n') == m_buffer.end()); - m_buffer.insert(m_buffer.end(), buf, buf + rc); - m_buf_ptr = m_buffer.begin(); - } + std::copy(buf, buf + rc, std::back_inserter(m_buffer)); - if (!m_connected && is_error(dest.c_str())) - { - rval = false; + if (is_error()) + { + rval = false; + break; + } } return rval; diff --git a/connectors/cdc-connector/cdc_connector.h b/connectors/cdc-connector/cdc_connector.h index 477ec655b..7759bb84e 100644 --- a/connectors/cdc-connector/cdc_connector.h +++ b/connectors/cdc-connector/cdc_connector.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -137,17 +138,17 @@ private: SValueVector m_keys; SValueVector m_types; int m_timeout; - std::vector m_buffer; - std::vector::iterator m_buf_ptr; + std::deque m_buffer; SRow m_first_row; bool m_connected; bool do_auth(); bool do_registration(); bool read_row(std::string& dest); + bool read_schema(); void process_schema(json_t* json); SRow process_row(json_t*); - bool is_error(const char* str); + bool is_error(); // Lower-level functions int wait_for_event(short events); diff --git a/maxscale-system-test/failover_common.cpp b/maxscale-system-test/failover_common.cpp index 9377e117a..30d4c46fe 100644 --- a/maxscale-system-test/failover_common.cpp +++ b/maxscale-system-test/failover_common.cpp @@ -24,13 +24,13 @@ void reset_replication(TestConnections& test) { int ind = master_id - 1; replicate_from(test, 0, ind); - test.maxscales->wait_for_monitor(); + test.maxscales->wait_for_monitor(2); get_output(test); int ec; stringstream switchover; switchover << "maxadmin call command mysqlmon switchover MySQL-Monitor server1 server" << master_id; test.maxscales->ssh_node_output(0, switchover.str().c_str() , true, &ec); - test.maxscales->wait_for_monitor(); + test.maxscales->wait_for_monitor(2); master_id = get_master_server_id(test); cout << "Master server id is now back to " << master_id << endl; test.assert(master_id == 1, "Switchover back to server1 failed"); @@ -106,7 +106,7 @@ void check_test_2(TestConnections& test) // Reset state replicate_from(test, 1, master_id - 1); - test.maxscales->wait_for_monitor(); + test.maxscales->wait_for_monitor(2); get_output(test); StringSet node_states = test.get_server_status("server2"); test.assert(node_states.find("Slave") != node_states.end(), "Server 2 is not replicating."); @@ -143,7 +143,7 @@ void prepare_test_3(TestConnections& test) test.repl->start_node(2, (char *) ""); test.repl->start_node(3, (char *) ""); test.maxscales->start_maxscale(0); - test.maxscales->wait_for_monitor(); + test.maxscales->wait_for_monitor(2); test.repl->connect(); test.tprintf("Settings changed."); diff --git a/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc b/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc index 08271d0c7..e40969f3c 100644 --- a/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc +++ b/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -1545,21 +1546,30 @@ static bool reauthenticate_client(MXS_SESSION* session, GWBUF* packetbuf) if (session->client_dcb->authfunc.reauthenticate) { + uint64_t payloadlen = gwbuf_length(packetbuf) - MYSQL_HEADER_LEN; MySQLProtocol* proto = (MySQLProtocol*)session->client_dcb->protocol; - uint8_t payload[gwbuf_length(packetbuf) - MYSQL_HEADER_LEN]; - gwbuf_copy_data(packetbuf, MYSQL_HEADER_LEN, sizeof(payload), payload); + std::vector payload; + payload.resize(payloadlen); + gwbuf_copy_data(packetbuf, MYSQL_HEADER_LEN, payloadlen, &payload[0]); // Will contains extra data but the username is null-terminated - char user[gwbuf_length(proto->stored_query) - MYSQL_HEADER_LEN - 1]; - gwbuf_copy_data(proto->stored_query, MYSQL_HEADER_LEN + 1, - sizeof(user), (uint8_t*)user); + char user[MYSQL_USER_MAXLEN + 1]; + gwbuf_copy_data(proto->stored_query, MYSQL_HEADER_LEN + 1, sizeof(user), (uint8_t*)user); + + char* end = user + sizeof(user); + + if (std::find(user, end, '\0') == end) + { + mysql_send_auth_error(session->client_dcb, 3, 0, "Malformed AuthSwitchRequest packet"); + return false; + } // Copy the new username to the session data MYSQL_session* data = (MYSQL_session*)session->client_dcb->data; strcpy(data->user, user); int rc = session->client_dcb->authfunc.reauthenticate(session->client_dcb, data->user, - payload, sizeof(payload), + &payload[0], payload.size(), proto->scramble, sizeof(proto->scramble), data->client_sha1, sizeof(data->client_sha1)); diff --git a/server/modules/routing/avrorouter/avro_client.cc b/server/modules/routing/avrorouter/avro_client.cc index c5f151d41..516c1a8ee 100644 --- a/server/modules/routing/avrorouter/avro_client.cc +++ b/server/modules/routing/avrorouter/avro_client.cc @@ -343,17 +343,17 @@ void AvroSession::process_command(GWBUF *queue) } else { - dcb_printf(dcb, "ERR NO-FILE File '%s' not found.", avro_binfile.c_str()); + dcb_printf(dcb, "ERR NO-FILE File '%s' not found.\n", avro_binfile.c_str()); } } else { - dcb_printf(dcb, "ERR REQUEST-DATA with no data"); + dcb_printf(dcb, "ERR REQUEST-DATA with no data\n"); } } else { - const char err[] = "ERR: Unknown command"; + const char err[] = "ERR: Unknown command\n"; GWBUF *reply = gwbuf_alloc_and_load(sizeof(err), err); dcb->func.write(dcb, reply); } @@ -571,8 +571,7 @@ bool AvroSession::stream_data() } else { - fprintf(stderr, "No file specified\n"); - dcb_printf(dcb, "ERR avro file not specified"); + dcb_printf(dcb, "ERR avro file not specified\n"); } return read_more; diff --git a/server/modules/routing/avrorouter/avro_file.cc b/server/modules/routing/avrorouter/avro_file.cc index 048a93dd5..4fb2effba 100644 --- a/server/modules/routing/avrorouter/avro_file.cc +++ b/server/modules/routing/avrorouter/avro_file.cc @@ -444,6 +444,29 @@ static bool pos_is_ok(Avro* router, const REP_HEADER& hdr, uint64_t pos) return rval; } +bool read_fde(Avro* router) +{ + bool rval = false; + avro_binlog_end_t rc; + REP_HEADER hdr; + + if (read_header(router, 4, &hdr, &rc)) + { + if (GWBUF *result = read_event_data(router, &hdr, 4)) + { + router->handler.handle_event(hdr, GWBUF_DATA(result)); + rval = true; + } + } + else if (rc == AVRO_OK) + { + // Empty file + rval = true; + } + + return rval; +} + /** * @brief Read all replication events from a binlog file. * @@ -457,12 +480,19 @@ static bool pos_is_ok(Avro* router, const REP_HEADER& hdr, uint64_t pos) */ avro_binlog_end_t avro_read_all_events(Avro *router) { + mxb_assert(router->binlog_fd != -1); + + if (!read_fde(router)) + { + MXS_ERROR("Failed to read the FDE event from the binary log: %d, %s", + errno, mxs_strerror(errno)); + return AVRO_BINLOG_ERROR; + } + uint64_t pos = router->current_pos; std::string next_binlog; bool rotate_seen = false; - mxb_assert(router->binlog_fd != -1); - while (!service_should_stop) { avro_binlog_end_t rc;