trilogy 2.10.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e84050730e47a4bd826b9c15b226d0f825a46df8849c3c4a89c01011e27fb6c
4
- data.tar.gz: eddfedb0481b09220bb95f1de423b473aa78f2ad8058de692b85c52bba1c0480
3
+ metadata.gz: 0625a7ff415d68c8f5e421188e66fe7c41ebadd2afc39a7d6b89c6318ea9ef9d
4
+ data.tar.gz: c6b10922d195af6ec3b5140963749b59d4ef4a967728aeeb826377bc66992bc0
5
5
  SHA512:
6
- metadata.gz: a1fa8ecb46aa1b1595c6d6f1a20b5dd4267452d5c59b0571fe31c4cc854907aeb8f8ad1496099e53ba83b29c0594f88dc6f4f45586ede8b1ad53d70b0ff2a774
7
- data.tar.gz: a18b393f8b035c8ee3315d42b23d8a46e1597f4ccbac0347acab0161a8c041572eeb8a13d4d323c97a63040f9f18baa04e0742c73e65df33d5763fdd27b6c581
6
+ metadata.gz: 5da895e095a69094c7f1844d341ea2bf0c4461f7744f892a5a6f3aff1552761ebb8f099a5df151450c6c32e894e88cfebf72052c8190aef749be0d8d56ad0611
7
+ data.tar.gz: 60830ae194994fa09a975d3cea8146632300bfef62e98726c7cd6edfdb18a91bd3bce55a9fbf0d5b5d2fa0932d2a264333d87ccb57ae222e59a46bd3c5740fd7
data/Rakefile CHANGED
@@ -19,10 +19,25 @@ Rake::TestTask.new do |t|
19
19
  t.test_files = FileList['test/*_test.rb']
20
20
  t.verbose = true
21
21
  end
22
- task :test => :compile
22
+
23
+ task :test => [:compile, "db:clean"]
23
24
 
24
25
  task :default => :test
25
26
 
26
27
  task :console => :compile do
27
28
  sh "ruby -I lib -r trilogy -S irb"
28
29
  end
30
+
31
+ namespace :db do
32
+ task :create do
33
+ mysql_command = "mysql -uroot -e"
34
+ %x( #{mysql_command} "create DATABASE IF NOT EXISTS test DEFAULT CHARACTER SET utf8mb4" )
35
+ end
36
+
37
+ task :drop do
38
+ mysql_command = "mysql -uroot -e"
39
+ %x( #{mysql_command} "drop DATABASE IF EXISTS test" )
40
+ end
41
+
42
+ task :clean => ["db:drop", "db:create"]
43
+ end
@@ -124,7 +124,7 @@ static void buffer_checkout(trilogy_buffer_t *buffer, size_t initial_capacity)
124
124
  buffer->buff = pool->entries[pool->len].buff;
125
125
  buffer->cap = pool->entries[pool->len].cap;
126
126
  } else {
127
- buffer->buff = RB_ALLOC_N(uint8_t, initial_capacity);
127
+ buffer->buff = malloc(initial_capacity);
128
128
  buffer->cap = initial_capacity;
129
129
  }
130
130
  }
@@ -134,7 +134,7 @@ static bool buffer_checkin(trilogy_buffer_t *buffer)
134
134
  buffer_pool * pool = get_buffer_pool();
135
135
 
136
136
  if (pool->len >= BUFFER_POOL_MAX_SIZE) {
137
- xfree(buffer->buff);
137
+ free(buffer->buff);
138
138
  buffer->buff = NULL;
139
139
  buffer->cap = 0;
140
140
  return false;
@@ -172,9 +172,9 @@ static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows,
172
172
 
173
173
  struct trilogy_ctx {
174
174
  trilogy_conn_t conn;
175
- char server_version[TRILOGY_SERVER_VERSION_SIZE + 1];
175
+ rb_encoding *encoding;
176
176
  unsigned int query_flags;
177
- VALUE encoding;
177
+ char server_version[TRILOGY_SERVER_VERSION_SIZE + 1];
178
178
  };
179
179
 
180
180
  static void rb_trilogy_acquire_buffer(struct trilogy_ctx *ctx)
@@ -191,12 +191,6 @@ static void rb_trilogy_release_buffer(struct trilogy_ctx *ctx)
191
191
  }
192
192
  }
193
193
 
194
- static void mark_trilogy(void *ptr)
195
- {
196
- struct trilogy_ctx *ctx = ptr;
197
- rb_gc_mark(ctx->encoding);
198
- }
199
-
200
194
  static void free_trilogy(void *ptr)
201
195
  {
202
196
  struct trilogy_ctx *ctx = ptr;
@@ -218,7 +212,7 @@ static size_t trilogy_memsize(const void *ptr) {
218
212
  static const rb_data_type_t trilogy_data_type = {
219
213
  .wrap_struct_name = "trilogy",
220
214
  .function = {
221
- .dmark = mark_trilogy,
215
+ .dmark = NULL,
222
216
  .dfree = free_trilogy,
223
217
  .dsize = trilogy_memsize,
224
218
  },
@@ -455,42 +449,29 @@ static int _cb_ruby_wait(trilogy_sock_t *sock, trilogy_wait_t wait)
455
449
  return TRILOGY_OK;
456
450
  }
457
451
 
458
- struct nogvl_sock_args {
459
- int rc;
460
- trilogy_sock_t *sock;
461
- };
462
-
463
- static void *no_gvl_resolve(void *data)
452
+ static int try_connect(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake, const trilogy_sockopt_t *opts, int fd)
464
453
  {
465
- struct nogvl_sock_args *args = data;
466
- args->rc = trilogy_sock_resolve(args->sock);
467
- return NULL;
468
- }
454
+ if (fd < 0) {
455
+ return TRILOGY_ERR;
456
+ }
469
457
 
470
- static int try_connect(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake, const trilogy_sockopt_t *opts)
471
- {
472
458
  trilogy_sock_t *sock = trilogy_sock_new(opts);
473
459
  if (sock == NULL) {
474
460
  return TRILOGY_ERR;
475
461
  }
476
462
 
477
- struct nogvl_sock_args args = {.rc = 0, .sock = sock};
478
-
479
- // Do the DNS resolving with the GVL unlocked. At this point all
480
- // configuration data is copied and available to the trilogy socket.
481
- rb_thread_call_without_gvl(no_gvl_resolve, (void *)&args, RUBY_UBF_IO, NULL);
482
-
483
- int rc = args.rc;
484
-
485
- if (rc != TRILOGY_OK) {
486
- trilogy_sock_close(sock);
487
- return rc;
488
- }
463
+ int rc;
489
464
 
490
465
  /* replace the default wait callback with our GVL-aware callback so we can
491
466
  escape the GVL on each wait operation without going through call_without_gvl */
492
467
  sock->wait_cb = _cb_ruby_wait;
493
- rc = trilogy_connect_send_socket(&ctx->conn, sock);
468
+
469
+ int newfd = dup(fd);
470
+ if (newfd < 0) {
471
+ return TRILOGY_ERR;
472
+ }
473
+
474
+ rc = trilogy_connect_set_fd(&ctx->conn, sock, newfd);
494
475
  if (rc < 0) {
495
476
  trilogy_sock_close(sock);
496
477
  return rc;
@@ -534,7 +515,12 @@ static void auth_switch(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake)
534
515
  }
535
516
 
536
517
  if (rc != TRILOGY_AGAIN) {
537
- handle_trilogy_error(ctx, rc, "trilogy_auth_recv");
518
+ if (rc == TRILOGY_UNSUPPORTED) {
519
+ handle_trilogy_error(ctx, rc, "trilogy_auth_recv: caching_sha2_password requires either TCP with TLS or a unix socket");
520
+ }
521
+ else {
522
+ handle_trilogy_error(ctx, rc, "trilogy_auth_recv");
523
+ }
538
524
  }
539
525
 
540
526
  rc = trilogy_sock_wait_read(ctx->conn.socket);
@@ -588,12 +574,7 @@ static void authenticate(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake
588
574
  }
589
575
 
590
576
  if (rc != TRILOGY_AGAIN) {
591
- if (rc == TRILOGY_UNSUPPORTED) {
592
- handle_trilogy_error(ctx, rc, "trilogy_auth_recv: caching_sha2_password requires either TCP with TLS or a unix socket");
593
- }
594
- else {
595
- handle_trilogy_error(ctx, rc, "trilogy_auth_recv");
596
- }
577
+ handle_trilogy_error(ctx, rc, "trilogy_auth_recv");
597
578
  }
598
579
 
599
580
  rc = trilogy_sock_wait_read(ctx->conn.socket);
@@ -607,14 +588,24 @@ static void authenticate(struct trilogy_ctx *ctx, trilogy_handshake_t *handshake
607
588
  }
608
589
  }
609
590
 
610
- static VALUE rb_trilogy_connect(VALUE self, VALUE encoding, VALUE charset, VALUE opts)
591
+ #ifndef HAVE_RB_IO_DESCRIPTOR /* Ruby < 3.1 */
592
+ static int rb_io_descriptor(VALUE io)
593
+ {
594
+ rb_io_t *fptr;
595
+ GetOpenFile(io, fptr);
596
+ rb_io_check_closed(fptr);
597
+ return fptr->fd;
598
+ }
599
+ #endif
600
+
601
+ static VALUE rb_trilogy_connect(VALUE self, VALUE raw_socket, VALUE encoding, VALUE charset, VALUE opts)
611
602
  {
612
603
  struct trilogy_ctx *ctx = get_ctx(self);
613
604
  trilogy_sockopt_t connopt = {0};
614
605
  trilogy_handshake_t handshake;
615
606
  VALUE val;
616
607
 
617
- RB_OBJ_WRITE(self, &ctx->encoding, encoding);
608
+ ctx->encoding = rb_to_encoding(encoding);
618
609
  connopt.encoding = NUM2INT(charset);
619
610
 
620
611
  Check_Type(opts, T_HASH);
@@ -762,9 +753,17 @@ static VALUE rb_trilogy_connect(VALUE self, VALUE encoding, VALUE charset, VALUE
762
753
  connopt.tls_max_version = NUM2INT(val);
763
754
  }
764
755
 
765
- rb_trilogy_acquire_buffer(ctx);
756
+ VALUE io = rb_io_get_io(raw_socket);
757
+
758
+ rb_io_t *fptr;
759
+ GetOpenFile(io, fptr);
760
+ rb_io_check_readable(fptr);
761
+ rb_io_check_writable(fptr);
766
762
 
767
- int rc = try_connect(ctx, &handshake, &connopt);
763
+ int fd = rb_io_descriptor(io);
764
+
765
+ rb_trilogy_acquire_buffer(ctx);
766
+ int rc = try_connect(ctx, &handshake, &connopt, fd);
768
767
  if (rc != TRILOGY_OK) {
769
768
  if (connopt.path) {
770
769
  handle_trilogy_error(ctx, rc, "trilogy_connect - unable to connect to %s", connopt.path);
@@ -983,10 +982,10 @@ static VALUE read_query_response(VALUE vargs)
983
982
  }
984
983
  }
985
984
 
986
- #ifdef HAVE_RB_INTERNED_STR
987
- VALUE column_name = rb_interned_str(column.name, column.name_len);
985
+ #ifdef HAVE_RB_ENC_INTERNED_STR
986
+ VALUE column_name = rb_enc_interned_str(column.name, column.name_len, ctx->encoding);
988
987
  #else
989
- VALUE column_name = rb_str_new(column.name, column.name_len);
988
+ VALUE column_name = rb_enc_str_new(column.name, column.name_len, ctx->encoding);
990
989
  OBJ_FREEZE(column_name);
991
990
  #endif
992
991
 
@@ -1093,12 +1092,37 @@ static VALUE rb_trilogy_more_results_exist(VALUE self)
1093
1092
  }
1094
1093
  }
1095
1094
 
1095
+ static VALUE rb_trilogy_abandon_results(VALUE self)
1096
+ {
1097
+ struct trilogy_ctx *ctx = get_open_ctx(self);
1098
+
1099
+ long count = 0;
1100
+ while (ctx->conn.server_status & TRILOGY_SERVER_STATUS_MORE_RESULTS_EXISTS) {
1101
+ count++;
1102
+ int rc = trilogy_drain_results(&ctx->conn);
1103
+ while (rc == TRILOGY_AGAIN) {
1104
+ rc = trilogy_sock_wait_read(ctx->conn.socket);
1105
+ if (rc != TRILOGY_OK) {
1106
+ handle_trilogy_error(ctx, rc, "trilogy_sock_wait_read");
1107
+ }
1108
+
1109
+ rc = trilogy_drain_results(&ctx->conn);
1110
+ }
1111
+
1112
+ if (rc != TRILOGY_OK) {
1113
+ handle_trilogy_error(ctx, rc, "trilogy_drain_results");
1114
+ }
1115
+ }
1116
+
1117
+ return LONG2NUM(count);
1118
+ }
1119
+
1096
1120
  static VALUE rb_trilogy_query(VALUE self, VALUE query)
1097
1121
  {
1098
1122
  struct trilogy_ctx *ctx = get_open_ctx(self);
1099
1123
 
1100
1124
  StringValue(query);
1101
- query = rb_str_export_to_enc(query, rb_to_encoding(ctx->encoding));
1125
+ query = rb_str_export_to_enc(query, ctx->encoding);
1102
1126
 
1103
1127
  rb_trilogy_acquire_buffer(ctx);
1104
1128
 
@@ -1333,7 +1357,7 @@ RUBY_FUNC_EXPORTED void Init_cext(void)
1333
1357
  VALUE Trilogy = rb_const_get(rb_cObject, rb_intern("Trilogy"));
1334
1358
  rb_define_alloc_func(Trilogy, allocate_trilogy);
1335
1359
 
1336
- rb_define_private_method(Trilogy, "_connect", rb_trilogy_connect, 3);
1360
+ rb_define_private_method(Trilogy, "_connect", rb_trilogy_connect, 4);
1337
1361
  rb_define_method(Trilogy, "change_db", rb_trilogy_change_db, 1);
1338
1362
  rb_define_alias(Trilogy, "select_db", "change_db");
1339
1363
  rb_define_method(Trilogy, "query", rb_trilogy_query, 1);
@@ -1357,6 +1381,7 @@ RUBY_FUNC_EXPORTED void Init_cext(void)
1357
1381
  rb_define_method(Trilogy, "server_version", rb_trilogy_server_version, 0);
1358
1382
  rb_define_method(Trilogy, "more_results_exist?", rb_trilogy_more_results_exist, 0);
1359
1383
  rb_define_method(Trilogy, "next_result", rb_trilogy_next_result, 0);
1384
+ rb_define_method(Trilogy, "abandon_results!", rb_trilogy_abandon_results, 0);
1360
1385
  rb_define_method(Trilogy, "set_server_option", rb_trilogy_set_server_option, 1);
1361
1386
  rb_define_const(Trilogy, "TLS_VERSION_10", INT2NUM(TRILOGY_TLS_VERSION_10));
1362
1387
  rb_define_const(Trilogy, "TLS_VERSION_11", INT2NUM(TRILOGY_TLS_VERSION_11));
@@ -12,10 +12,12 @@ File.binwrite("trilogy.c",
12
12
  $objs = %w[trilogy.o cast.o cext.o]
13
13
  append_cflags(["-I #{__dir__}/inc", "-std=gnu99", "-fvisibility=hidden"])
14
14
 
15
- dir_config("openssl")
15
+ dir_config("openssl").any? || pkg_config("openssl")
16
16
 
17
17
  have_library("crypto", "CRYPTO_malloc")
18
18
  have_library("ssl", "SSL_new")
19
- have_func("rb_interned_str", "ruby.h")
20
19
  have_func("rb_ractor_local_storage_value_newkey", "ruby.h")
20
+ have_func("rb_enc_interned_str", "ruby.h")
21
+ have_func("rb_io_descriptor", "ruby.h") # Ruby 3.1+
22
+
21
23
  create_makefile "trilogy/cext"
@@ -159,6 +159,8 @@ int trilogy_connect_send(trilogy_conn_t *conn, const trilogy_sockopt_t *opts);
159
159
  */
160
160
  int trilogy_connect_send_socket(trilogy_conn_t *conn, trilogy_sock_t *sock);
161
161
 
162
+ int trilogy_connect_set_fd(trilogy_conn_t *conn, trilogy_sock_t *sock, int fd);
163
+
162
164
  /* trilogy_connect_recv - Read the initial handshake from the server.
163
165
  *
164
166
  * This should be called after trilogy_connect_send returns TRILOGY_OK. Calling
@@ -88,6 +88,8 @@ typedef struct trilogy_sock_t {
88
88
 
89
89
  static inline int trilogy_sock_connect(trilogy_sock_t *sock) { return sock->connect_cb(sock); }
90
90
 
91
+ void trilogy_sock_set_fd(trilogy_sock_t *sock, int fd);
92
+
91
93
  static inline ssize_t trilogy_sock_read(trilogy_sock_t *sock, void *buf, size_t n)
92
94
  {
93
95
  return sock->read_cb(sock, buf, n);
@@ -1,4 +1,10 @@
1
1
  #include <fcntl.h>
2
+ #include <limits.h>
3
+ #include <openssl/err.h>
4
+ #include <openssl/evp.h>
5
+ #include <openssl/pem.h>
6
+ #include <openssl/rsa.h>
7
+ #include <stdlib.h>
2
8
  #include <string.h>
3
9
 
4
10
  #include "trilogy/client.h"
@@ -118,6 +124,38 @@ static int begin_write(trilogy_conn_t *conn)
118
124
  return trilogy_flush_writes(conn);
119
125
  }
120
126
 
127
+ static int flush_current_packet(trilogy_conn_t *conn)
128
+ {
129
+ int rc = begin_write(conn);
130
+
131
+ while (rc == TRILOGY_AGAIN) {
132
+ rc = trilogy_sock_wait_write(conn->socket);
133
+
134
+ if (rc != TRILOGY_OK) {
135
+ return rc;
136
+ }
137
+
138
+ rc = trilogy_flush_writes(conn);
139
+ }
140
+
141
+ return rc;
142
+ }
143
+
144
+ static int read_packet_blocking(trilogy_conn_t *conn)
145
+ {
146
+ int rc;
147
+
148
+ while ((rc = read_packet(conn)) == TRILOGY_AGAIN) {
149
+ rc = trilogy_sock_wait_read(conn->socket);
150
+
151
+ if (rc != TRILOGY_OK) {
152
+ return rc;
153
+ }
154
+ }
155
+
156
+ return rc;
157
+ }
158
+
121
159
  int trilogy_init_no_buffer(trilogy_conn_t *conn)
122
160
  {
123
161
  conn->affected_rows = 0;
@@ -279,7 +317,8 @@ static int read_auth_switch_packet(trilogy_conn_t *conn, trilogy_handshake_t *ha
279
317
  return TRILOGY_AUTH_SWITCH;
280
318
  }
281
319
 
282
- static int handle_generic_response(trilogy_conn_t *conn) {
320
+ static int handle_generic_response(trilogy_conn_t *conn)
321
+ {
283
322
  switch (current_packet_type(conn)) {
284
323
  case TRILOGY_PACKET_OK:
285
324
  return read_ok_packet(conn);
@@ -330,6 +369,16 @@ int trilogy_connect_send_socket(trilogy_conn_t *conn, trilogy_sock_t *sock)
330
369
  return TRILOGY_OK;
331
370
  }
332
371
 
372
+ int trilogy_connect_set_fd(trilogy_conn_t *conn, trilogy_sock_t *sock, int fd)
373
+ {
374
+ trilogy_sock_set_fd(sock, fd);
375
+
376
+ conn->socket = sock;
377
+ conn->packet_parser.sequence_number = 0;
378
+
379
+ return TRILOGY_OK;
380
+ }
381
+
333
382
  int trilogy_connect_recv(trilogy_conn_t *conn, trilogy_handshake_t *handshake_out)
334
383
  {
335
384
  int rc = read_packet(conn);
@@ -427,9 +476,294 @@ void trilogy_auth_clear_password(trilogy_conn_t *conn)
427
476
  }
428
477
  }
429
478
 
479
+ #define CACHING_SHA2_REQUEST_PUBLIC_KEY 2
480
+ #define CACHING_SHA2_SCRAMBLE_LEN 20
430
481
  #define FAST_AUTH_OK 3
431
482
  #define FAST_AUTH_FAIL 4
432
483
 
484
+ static int read_auth_result(trilogy_conn_t *conn)
485
+ {
486
+ int rc = read_packet_blocking(conn);
487
+
488
+ if (rc < 0) {
489
+ return rc;
490
+ }
491
+
492
+ trilogy_auth_clear_password(conn);
493
+ return handle_generic_response(conn);
494
+ }
495
+
496
+ static int send_cleartext_password(trilogy_conn_t *conn)
497
+ {
498
+ trilogy_builder_t builder;
499
+
500
+ int rc = begin_command_phase(&builder, conn, conn->packet_parser.sequence_number);
501
+
502
+ if (rc < 0) {
503
+ return rc;
504
+ }
505
+
506
+ if (conn->socket->opts.password_len == 0) {
507
+ rc = trilogy_builder_write_uint8(&builder, 0);
508
+
509
+ if (rc < 0) {
510
+ return rc;
511
+ }
512
+
513
+ trilogy_builder_finalize(&builder);
514
+ return flush_current_packet(conn);
515
+ }
516
+
517
+ rc = trilogy_build_auth_clear_password(&builder, conn->socket->opts.password, conn->socket->opts.password_len);
518
+
519
+ if (rc < 0) {
520
+ return rc;
521
+ }
522
+
523
+ return flush_current_packet(conn);
524
+ }
525
+
526
+ static int send_auth_buffer(trilogy_conn_t *conn, const void *buff, size_t buff_len)
527
+ {
528
+ trilogy_builder_t builder;
529
+ int rc = begin_command_phase(&builder, conn, conn->packet_parser.sequence_number);
530
+
531
+ if (rc < 0) {
532
+ return rc;
533
+ }
534
+
535
+ rc = trilogy_builder_write_buffer(&builder, buff, buff_len);
536
+ if (rc < 0) {
537
+ return rc;
538
+ }
539
+
540
+ trilogy_builder_finalize(&builder);
541
+
542
+ return flush_current_packet(conn);
543
+ }
544
+
545
+ static int send_public_key_request(trilogy_conn_t *conn)
546
+ {
547
+ uint8_t request = CACHING_SHA2_REQUEST_PUBLIC_KEY;
548
+
549
+ return send_auth_buffer(conn, &request, sizeof(request));
550
+ }
551
+
552
+ static int encrypt_password_with_public_key(const uint8_t *scramble, size_t scramble_len, trilogy_conn_t *conn,
553
+ const uint8_t *key_data, size_t key_data_len, uint8_t **encrypted_out,
554
+ size_t *encrypted_len)
555
+ {
556
+ int rc = TRILOGY_OK;
557
+ uint8_t *ciphertext = NULL;
558
+ size_t ciphertext_len = 0;
559
+
560
+ if (key_data_len == 0 || key_data_len > INT_MAX) {
561
+ return TRILOGY_AUTH_PLUGIN_ERROR;
562
+ }
563
+
564
+ size_t password_len = conn->socket->opts.password_len;
565
+ if (password_len == SIZE_MAX) {
566
+ return TRILOGY_MEM_ERROR;
567
+ }
568
+ size_t plaintext_len = password_len + 1;
569
+ uint8_t *plaintext = malloc(plaintext_len);
570
+
571
+ if (plaintext == NULL) {
572
+ return TRILOGY_MEM_ERROR;
573
+ }
574
+
575
+ if (password_len > 0) {
576
+ memcpy(plaintext, conn->socket->opts.password, password_len);
577
+ }
578
+ plaintext[plaintext_len - 1] = '\0';
579
+
580
+ if (scramble_len > 0) {
581
+ for (size_t i = 0; i < plaintext_len; i++) {
582
+ plaintext[i] ^= scramble[i % scramble_len];
583
+ }
584
+ }
585
+
586
+ BIO *bio = BIO_new_mem_buf((void *)key_data, (int)key_data_len);
587
+ if (bio == NULL) {
588
+ free(plaintext);
589
+ return TRILOGY_OPENSSL_ERR;
590
+ }
591
+
592
+ #if OPENSSL_VERSION_NUMBER >= 0x30000000L
593
+ EVP_PKEY *public_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
594
+ #else
595
+ RSA *public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
596
+ #endif
597
+
598
+ BIO_free(bio);
599
+
600
+ if (public_key == NULL) {
601
+ ERR_clear_error();
602
+ memset(plaintext, 0, plaintext_len);
603
+ free(plaintext);
604
+ return TRILOGY_AUTH_PLUGIN_ERROR;
605
+ }
606
+
607
+ #if OPENSSL_VERSION_NUMBER >= 0x30000000L
608
+ int key_size = EVP_PKEY_get_size(public_key);
609
+ if (key_size <= 0) {
610
+ EVP_PKEY_free(public_key);
611
+ memset(plaintext, 0, plaintext_len);
612
+ free(plaintext);
613
+ return TRILOGY_AUTH_PLUGIN_ERROR;
614
+ }
615
+ ciphertext_len = (size_t)key_size;
616
+ #else
617
+ ciphertext_len = (size_t)RSA_size(public_key);
618
+ #endif
619
+
620
+ /*
621
+ When using RSA_PKCS1_OAEP_PADDING the password length must be less
622
+ than RSA_size(rsa) - 41.
623
+ */
624
+ if (ciphertext_len == 0 || plaintext_len + 41 >= ciphertext_len) {
625
+ #if OPENSSL_VERSION_NUMBER >= 0x30000000L
626
+ EVP_PKEY_free(public_key);
627
+ #else
628
+ RSA_free(public_key);
629
+ #endif
630
+ memset(plaintext, 0, plaintext_len);
631
+ free(plaintext);
632
+ return TRILOGY_AUTH_PLUGIN_ERROR;
633
+ }
634
+
635
+ ciphertext = malloc(ciphertext_len);
636
+
637
+ if (ciphertext == NULL) {
638
+ #if OPENSSL_VERSION_NUMBER >= 0x30000000L
639
+ EVP_PKEY_free(public_key);
640
+ #else
641
+ RSA_free(public_key);
642
+ #endif
643
+ memset(plaintext, 0, plaintext_len);
644
+ free(plaintext);
645
+ return TRILOGY_MEM_ERROR;
646
+ }
647
+
648
+ #if OPENSSL_VERSION_NUMBER >= 0x30000000L
649
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(public_key, NULL);
650
+ if (ctx == NULL || EVP_PKEY_encrypt_init(ctx) <= 0 ||
651
+ EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
652
+ rc = TRILOGY_OPENSSL_ERR;
653
+ } else {
654
+ size_t out_len = ciphertext_len;
655
+
656
+ if (EVP_PKEY_encrypt(ctx, ciphertext, &out_len, plaintext, plaintext_len) <= 0) {
657
+ rc = TRILOGY_OPENSSL_ERR;
658
+ } else {
659
+ *encrypted_len = out_len;
660
+ }
661
+ }
662
+
663
+ if (ctx) {
664
+ EVP_PKEY_CTX_free(ctx);
665
+ }
666
+ EVP_PKEY_free(public_key);
667
+ #else
668
+ int out_len = RSA_public_encrypt((int)plaintext_len, plaintext, ciphertext, public_key, RSA_PKCS1_OAEP_PADDING);
669
+ RSA_free(public_key);
670
+
671
+ if (out_len < 0) {
672
+ rc = TRILOGY_OPENSSL_ERR;
673
+ } else {
674
+ *encrypted_len = (size_t)out_len;
675
+ }
676
+ #endif
677
+
678
+ memset(plaintext, 0, plaintext_len);
679
+ free(plaintext);
680
+
681
+ if (rc == TRILOGY_OK) {
682
+ *encrypted_out = ciphertext;
683
+ } else {
684
+ memset(ciphertext, 0, ciphertext_len);
685
+ free(ciphertext);
686
+ }
687
+
688
+ return rc;
689
+ }
690
+
691
+ static int handle_fast_auth_fail(trilogy_conn_t *conn, trilogy_handshake_t *handshake, const uint8_t *auth_data,
692
+ size_t auth_data_len)
693
+ {
694
+ int rc;
695
+ bool use_ssl = (conn->socket->opts.flags & TRILOGY_CAPABILITIES_SSL) != 0;
696
+ bool has_unix_socket = (conn->socket->opts.path != NULL);
697
+
698
+ // No password to send, so we can safely respond even without TLS.
699
+ if (conn->socket->opts.password_len == 0) {
700
+ rc = send_cleartext_password(conn);
701
+ if (rc < 0) {
702
+ return rc;
703
+ }
704
+
705
+ return read_auth_result(conn);
706
+ }
707
+
708
+ if (use_ssl || has_unix_socket) {
709
+ rc = send_cleartext_password(conn);
710
+ if (rc < 0) {
711
+ return rc;
712
+ }
713
+
714
+ return read_auth_result(conn);
715
+ }
716
+
717
+ const uint8_t *public_key_data = NULL;
718
+ size_t public_key_len = 0;
719
+
720
+ if (auth_data_len > 1) {
721
+ public_key_data = auth_data + 1;
722
+ public_key_len = auth_data_len - 1;
723
+ } else {
724
+ rc = send_public_key_request(conn);
725
+ if (rc < 0) {
726
+ return rc;
727
+ }
728
+
729
+ rc = read_packet_blocking(conn);
730
+ if (rc < 0) {
731
+ return rc;
732
+ }
733
+
734
+ if (current_packet_type(conn) == TRILOGY_PACKET_ERR) {
735
+ return read_err_packet(conn);
736
+ }
737
+
738
+ if (current_packet_type(conn) != TRILOGY_PACKET_AUTH_MORE_DATA || conn->packet_buffer.len < 2) {
739
+ return TRILOGY_PROTOCOL_VIOLATION;
740
+ }
741
+
742
+ public_key_data = conn->packet_buffer.buff + 1;
743
+ public_key_len = conn->packet_buffer.len - 1;
744
+ }
745
+
746
+ uint8_t *encrypted = NULL;
747
+ size_t encrypted_len = 0;
748
+
749
+ rc = encrypt_password_with_public_key((const uint8_t *)handshake->scramble, CACHING_SHA2_SCRAMBLE_LEN, conn,
750
+ public_key_data, public_key_len, &encrypted, &encrypted_len);
751
+
752
+ if (rc < 0) {
753
+ return rc;
754
+ }
755
+
756
+ rc = send_auth_buffer(conn, encrypted, encrypted_len);
757
+ memset(encrypted, 0, encrypted_len);
758
+ free(encrypted);
759
+
760
+ if (rc < 0) {
761
+ return rc;
762
+ }
763
+
764
+ return read_auth_result(conn);
765
+ }
766
+
433
767
  int trilogy_auth_recv(trilogy_conn_t *conn, trilogy_handshake_t *handshake)
434
768
  {
435
769
  int rc = read_packet(conn);
@@ -440,67 +774,24 @@ int trilogy_auth_recv(trilogy_conn_t *conn, trilogy_handshake_t *handshake)
440
774
 
441
775
  switch (current_packet_type(conn)) {
442
776
  case TRILOGY_PACKET_AUTH_MORE_DATA: {
443
- bool use_ssl = (conn->socket->opts.flags & TRILOGY_CAPABILITIES_SSL) != 0;
444
- bool has_unix_socket = (conn->socket->opts.path != NULL);
777
+ const uint8_t *auth_data = conn->packet_buffer.buff + 1;
778
+ size_t auth_data_len = conn->packet_buffer.len - 1;
445
779
 
446
- if (!use_ssl && !has_unix_socket) {
447
- return TRILOGY_UNSUPPORTED;
780
+ if (auth_data_len < 1) {
781
+ return TRILOGY_PROTOCOL_VIOLATION;
448
782
  }
449
783
 
450
- uint8_t byte = conn->packet_buffer.buff[1];
784
+ uint8_t byte = auth_data[0];
451
785
  switch (byte) {
452
- case FAST_AUTH_OK:
453
- break;
454
- case FAST_AUTH_FAIL:
455
- {
456
- trilogy_builder_t builder;
457
- int err = begin_command_phase(&builder, conn, conn->packet_parser.sequence_number);
458
-
459
- if (err < 0) {
460
- return err;
461
- }
462
-
463
- err = trilogy_build_auth_clear_password(&builder, conn->socket->opts.password, conn->socket->opts.password_len);
464
-
465
- if (err < 0) {
466
- return err;
467
- }
468
-
469
- int rc = begin_write(conn);
470
-
471
- while (rc == TRILOGY_AGAIN) {
472
- rc = trilogy_sock_wait_write(conn->socket);
473
- if (rc != TRILOGY_OK) {
474
- return rc;
475
- }
476
-
477
- rc = trilogy_flush_writes(conn);
478
- }
479
- if (rc != TRILOGY_OK) {
480
- return rc;
481
- }
482
-
483
- break;
484
- }
485
- default:
486
- return TRILOGY_UNEXPECTED_PACKET;
487
- }
488
- while (1) {
489
- rc = read_packet(conn);
786
+ case FAST_AUTH_OK:
787
+ return read_auth_result(conn);
490
788
 
491
- if (rc == TRILOGY_OK) {
492
- break;
493
- }
494
- else if (rc == TRILOGY_AGAIN) {
495
- rc = trilogy_sock_wait_read(conn->socket);
496
- }
789
+ case FAST_AUTH_FAIL:
790
+ return handle_fast_auth_fail(conn, handshake, auth_data, auth_data_len);
497
791
 
498
- if (rc != TRILOGY_OK) {
499
- return rc;
500
- }
792
+ default:
793
+ return TRILOGY_UNEXPECTED_PACKET;
501
794
  }
502
- trilogy_auth_clear_password(conn);
503
- return handle_generic_response(conn);
504
795
  }
505
796
 
506
797
  case TRILOGY_PACKET_EOF:
@@ -555,7 +846,8 @@ int trilogy_set_option_send(trilogy_conn_t *conn, const uint16_t option)
555
846
  return begin_write(conn);
556
847
  }
557
848
 
558
- int trilogy_set_option_recv(trilogy_conn_t *conn) {
849
+ int trilogy_set_option_recv(trilogy_conn_t *conn)
850
+ {
559
851
  int rc = read_packet(conn);
560
852
 
561
853
  if (rc < 0) {
@@ -575,7 +867,6 @@ int trilogy_set_option_recv(trilogy_conn_t *conn) {
575
867
  }
576
868
  }
577
869
 
578
-
579
870
  int trilogy_ping_send(trilogy_conn_t *conn)
580
871
  {
581
872
  trilogy_builder_t builder;
@@ -727,6 +1018,7 @@ int trilogy_drain_results(trilogy_conn_t *conn)
727
1018
  }
728
1019
 
729
1020
  if (current_packet_type(conn) == TRILOGY_PACKET_EOF && conn->packet_buffer.len < 9) {
1021
+ read_eof_packet(conn);
730
1022
  return TRILOGY_OK;
731
1023
  }
732
1024
  }
@@ -998,9 +1290,7 @@ int trilogy_stmt_reset_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt)
998
1290
  return begin_write(conn);
999
1291
  }
1000
1292
 
1001
- int trilogy_stmt_reset_recv(trilogy_conn_t *conn) {
1002
- return read_generic_response(conn);
1003
- }
1293
+ int trilogy_stmt_reset_recv(trilogy_conn_t *conn) { return read_generic_response(conn); }
1004
1294
 
1005
1295
  int trilogy_stmt_close_send(trilogy_conn_t *conn, trilogy_stmt_t *stmt)
1006
1296
  {
@@ -74,7 +74,7 @@ size_t trilogy_packet_parser_execute(trilogy_packet_parser_t *parser, const uint
74
74
  break;
75
75
  }
76
76
  case S_SEQ: {
77
- if (cur_byte != parser->sequence_number) {
77
+ if (cur_byte != parser->sequence_number && cur_byte > 0) {
78
78
  *error = TRILOGY_INVALID_SEQUENCE_ID;
79
79
  return i;
80
80
  }
@@ -25,6 +25,12 @@ struct trilogy_sock {
25
25
  SSL *ssl;
26
26
  };
27
27
 
28
+ void trilogy_sock_set_fd(trilogy_sock_t *_sock, int fd)
29
+ {
30
+ struct trilogy_sock *sock = (struct trilogy_sock *)_sock;
31
+ sock->fd = fd;
32
+ }
33
+
28
34
  static int _cb_raw_fd(trilogy_sock_t *_sock)
29
35
  {
30
36
  struct trilogy_sock *sock = (struct trilogy_sock *)_sock;
data/lib/trilogy/error.rb CHANGED
@@ -54,6 +54,12 @@ class Trilogy
54
54
  include ConnectionError
55
55
  end
56
56
 
57
+ class SynchronizationError < BaseError
58
+ def initialize(message = "This connection is already in use by another thread or fiber")
59
+ super
60
+ end
61
+ end
62
+
57
63
  # Trilogy::ClientError is the base error type for invalid queries or parameters
58
64
  # that shouldn't be retried.
59
65
  class ClientError < BaseError
@@ -94,6 +100,7 @@ class Trilogy
94
100
  1160 => BaseConnectionError, # ER_NET_ERROR_ON_WRITE
95
101
  1161 => BaseConnectionError, # ER_NET_WRITE_INTERRUPTED
96
102
  1927 => BaseConnectionError, # ER_CONNECTION_KILLED
103
+ 4031 => BaseConnectionError, # Disconnected by server
97
104
  }
98
105
  class << self
99
106
  def from_code(message, code)
@@ -1,3 +1,3 @@
1
1
  class Trilogy
2
- VERSION = "2.10.0"
2
+ VERSION = "2.11.0"
3
3
  end
data/lib/trilogy.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "socket"
3
4
  require "trilogy/version"
4
5
  require "trilogy/error"
5
6
  require "trilogy/result"
@@ -7,7 +8,34 @@ require "trilogy/cext"
7
8
  require "trilogy/encoding"
8
9
 
9
10
  class Trilogy
11
+ IO_TIMEOUT_ERROR =
12
+ if defined?(IO::TimeoutError)
13
+ IO::TimeoutError
14
+ else
15
+ Class.new(StandardError)
16
+ end
17
+ private_constant :IO_TIMEOUT_ERROR
18
+
19
+ Synchronization = Module.new
20
+
21
+ source = public_instance_methods(false).flat_map do |method|
22
+ [
23
+ "def #{method}(...)",
24
+ "raise SynchronizationError unless @mutex.try_lock",
25
+ "begin",
26
+ "super",
27
+ "ensure",
28
+ "@mutex.unlock",
29
+ "end",
30
+ "end",
31
+ ]
32
+ end
33
+ Synchronization.class_eval(source.join(";"), __FILE__, __LINE__)
34
+
35
+ prepend(Synchronization)
36
+
10
37
  def initialize(options = {})
38
+ @mutex = Mutex.new
11
39
  options[:port] = options[:port].to_i if options[:port]
12
40
  mysql_encoding = options[:encoding] || "utf8mb4"
13
41
  encoding = Trilogy::Encoding.find(mysql_encoding)
@@ -15,7 +43,54 @@ class Trilogy
15
43
  @connection_options = options
16
44
  @connected_host = nil
17
45
 
18
- _connect(encoding, charset, options)
46
+ socket = nil
47
+ begin
48
+ if host = options[:host]
49
+ port = options[:port] || 3306
50
+ connect_timeout = options[:connect_timeout] || options[:write_timeout]
51
+
52
+ socket = TCPSocket.new(host, port, connect_timeout: connect_timeout)
53
+
54
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
55
+
56
+ if keepalive_enabled = options[:keepalive_enabled]
57
+ keepalive_idle = options[:keepalive_idle]
58
+ keepalive_interval = options[:keepalive_interval]
59
+ keepalive_count = options[:keepalive_count]
60
+
61
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
62
+
63
+ if keepalive_idle > 0 && defined?(Socket::TCP_KEEPIDLE)
64
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_KEEPIDLE, keepalive_idle)
65
+ end
66
+ if keepalive_interval > 0 && defined?(Socket::TCP_KEEPINTVL)
67
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_KEEPINTVL, keepalive_interval)
68
+ end
69
+ if keepalive_count > 0 && defined?(Socket::TCP_KEEPCNT)
70
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_KEEPCNT, keepalive_count)
71
+ end
72
+ end
73
+ else
74
+ path = options[:socket] ||= "/tmp/mysql.sock"
75
+ socket = UNIXSocket.new(path)
76
+ end
77
+ rescue Errno::ETIMEDOUT, IO_TIMEOUT_ERROR => e
78
+ raise Trilogy::TimeoutError, e.message
79
+ rescue SocketError => e
80
+ connection_str = host ? "#{host}:#{port}" : path
81
+ raise Trilogy::BaseConnectionError, "unable to connect to \"#{connection_str}\": #{e.message}"
82
+ rescue => e
83
+ if e.respond_to?(:errno)
84
+ raise Trilogy::SyscallError.from_errno(e.errno, e.message)
85
+ else
86
+ raise
87
+ end
88
+ end
89
+
90
+ _connect(socket, encoding, charset, options)
91
+ ensure
92
+ # Socket's fd will be dup'd in C
93
+ socket&.close
19
94
  end
20
95
 
21
96
  def connection_options
data/trilogy.gemspec CHANGED
@@ -25,7 +25,4 @@ Gem::Specification.new do |s|
25
25
  s.required_ruby_version = ">= 3.0"
26
26
 
27
27
  s.add_dependency "bigdecimal"
28
-
29
- s.add_development_dependency "rake-compiler", "~> 1.0"
30
- s.add_development_dependency "minitest", "~> 5.5"
31
28
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trilogy
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.10.0
4
+ version: 2.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Engineering
@@ -23,34 +23,6 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
- - !ruby/object:Gem::Dependency
27
- name: rake-compiler
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '1.0'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '1.0'
40
- - !ruby/object:Gem::Dependency
41
- name: minitest
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '5.5'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '5.5'
54
26
  email: opensource+trilogy@github.com
55
27
  executables: []
56
28
  extensions: