1#include <ma_global.h>
2#include <ma_sys.h>
3#include <errmsg.h>
4#include <string.h>
5#include <ma_common.h>
6#include <mysql/client_plugin.h>
7
8typedef struct st_mysql_client_plugin_AUTHENTICATION auth_plugin_t;
9static int client_mpvio_write_packet(struct st_plugin_vio*, const uchar*, size_t);
10static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql);
11extern void read_user_name(char *name);
12extern char *ma_send_connect_attr(MYSQL *mysql, unsigned char *buffer);
13
14typedef struct {
15 int (*read_packet)(struct st_plugin_vio *vio, uchar **buf);
16 int (*write_packet)(struct st_plugin_vio *vio, const uchar *pkt, size_t pkt_len);
17 void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info);
18 /* -= end of MYSQL_PLUGIN_VIO =- */
19 MYSQL *mysql;
20 auth_plugin_t *plugin; /**< what plugin we're under */
21 const char *db;
22 struct {
23 uchar *pkt; /**< pointer into NET::buff */
24 uint pkt_len;
25 } cached_server_reply;
26 uint packets_read, packets_written; /**< counters for send/received packets */
27 my_bool mysql_change_user; /**< if it's mysql_change_user() */
28 int last_read_packet_len; /**< the length of the last *read* packet */
29} MCPVIO_EXT;
30/*
31#define compile_time_assert(A) \
32do {\
33 typedef char constraint[(A) ? 1 : -1];\
34} while (0);
35*/
36
37auth_plugin_t mysql_native_password_client_plugin=
38{
39 MYSQL_CLIENT_AUTHENTICATION_PLUGIN,
40 MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION,
41 native_password_plugin_name,
42 "R.J.Silk, Sergei Golubchik",
43 "Native MySQL authentication",
44 {1, 0, 0},
45 "LGPL",
46 NULL,
47 NULL,
48 NULL,
49 NULL,
50 native_password_auth_client
51};
52
53
54static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
55{
56 int pkt_len;
57 uchar *pkt;
58
59 if (((MCPVIO_EXT *)vio)->mysql_change_user)
60 {
61 /*
62 in mysql_change_user() the client sends the first packet.
63 we use the old scramble.
64 */
65 pkt= (uchar*)mysql->scramble_buff;
66 pkt_len= SCRAMBLE_LENGTH + 1;
67 }
68 else
69 {
70 /* read the scramble */
71 if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
72 return CR_ERROR;
73
74 if (pkt_len != SCRAMBLE_LENGTH + 1)
75 return CR_SERVER_HANDSHAKE_ERR;
76
77 /* save it in MYSQL */
78 memmove(mysql->scramble_buff, pkt, SCRAMBLE_LENGTH);
79 mysql->scramble_buff[SCRAMBLE_LENGTH] = 0;
80 }
81
82 if (mysql && mysql->passwd[0])
83 {
84 char scrambled[SCRAMBLE_LENGTH + 1];
85 ma_scramble_41((uchar *)scrambled, (char*)pkt, mysql->passwd);
86 if (vio->write_packet(vio, (uchar*)scrambled, SCRAMBLE_LENGTH))
87 return CR_ERROR;
88 }
89 else
90 if (vio->write_packet(vio, 0, 0)) /* no password */
91 return CR_ERROR;
92
93 return CR_OK;
94}
95
96static int send_change_user_packet(MCPVIO_EXT *mpvio,
97 const uchar *data, int data_len)
98{
99 MYSQL *mysql= mpvio->mysql;
100 char *buff, *end;
101 int res= 1;
102 size_t conn_attr_len= (mysql->options.extension) ?
103 mysql->options.extension->connect_attrs_len : 0;
104
105 buff= malloc(USERNAME_LENGTH+1 + data_len+1 + NAME_LEN+1 + 2 + NAME_LEN+1 + 9 + conn_attr_len);
106
107 end= ma_strmake(buff, mysql->user, USERNAME_LENGTH) + 1;
108
109 if (!data_len)
110 *end++= 0;
111 else
112 {
113 if (mysql->client_flag & CLIENT_SECURE_CONNECTION)
114 {
115 DBUG_ASSERT(data_len <= 255);
116 if (data_len > 255)
117 {
118 my_set_error(mysql, CR_MALFORMED_PACKET, SQLSTATE_UNKNOWN, 0);
119 goto error;
120 }
121 *end++= data_len;
122 }
123 else
124 {
125 DBUG_ASSERT(data_len == SCRAMBLE_LENGTH_323 + 1);
126 DBUG_ASSERT(data[SCRAMBLE_LENGTH_323] == 0);
127 }
128 memcpy(end, data, data_len);
129 end+= data_len;
130 }
131 end= ma_strmake(end, mpvio->db ? mpvio->db : "", NAME_LEN) + 1;
132
133 if (mysql->server_capabilities & CLIENT_PROTOCOL_41)
134 {
135 int2store(end, (ushort) mysql->charset->nr);
136 end+= 2;
137 }
138
139 if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH)
140 end= ma_strmake(end, mpvio->plugin->name, NAME_LEN) + 1;
141
142 end= ma_send_connect_attr(mysql, (unsigned char *)end);
143
144 res= ma_simple_command(mysql, COM_CHANGE_USER,
145 buff, (ulong)(end-buff), 1, NULL);
146
147error:
148 free(buff);
149 return res;
150}
151
152
153
154static int send_client_reply_packet(MCPVIO_EXT *mpvio,
155 const uchar *data, int data_len)
156{
157 MYSQL *mysql= mpvio->mysql;
158 NET *net= &mysql->net;
159 char *buff, *end;
160 size_t conn_attr_len= (mysql->options.extension) ?
161 mysql->options.extension->connect_attrs_len : 0;
162
163 /* see end= buff+32 below, fixed size of the packet is 32 bytes */
164 buff= malloc(33 + USERNAME_LENGTH + data_len + NAME_LEN + NAME_LEN + conn_attr_len + 9);
165 end= buff;
166
167 mysql->client_flag|= mysql->options.client_flag;
168 mysql->client_flag|= CLIENT_CAPABILITIES;
169
170 if (mysql->client_flag & CLIENT_MULTI_STATEMENTS)
171 mysql->client_flag|= CLIENT_MULTI_RESULTS;
172
173#if defined(HAVE_TLS) && !defined(EMBEDDED_LIBRARY)
174 if (mysql->options.ssl_key || mysql->options.ssl_cert ||
175 mysql->options.ssl_ca || mysql->options.ssl_capath ||
176 mysql->options.ssl_cipher || mysql->options.use_ssl ||
177 (mysql->options.client_flag & CLIENT_SSL_VERIFY_SERVER_CERT))
178 mysql->options.use_ssl= 1;
179 if (mysql->options.use_ssl)
180 mysql->client_flag|= CLIENT_SSL;
181#endif /* HAVE_TLS && !EMBEDDED_LIBRARY*/
182 if (mpvio->db)
183 mysql->client_flag|= CLIENT_CONNECT_WITH_DB;
184
185 /* if server doesn't support SSL and verification of server certificate
186 was set to mandatory, we need to return an error */
187 if (mysql->options.use_ssl && !(mysql->server_capabilities & CLIENT_SSL))
188 {
189 if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) ||
190 (mysql->options.extension && (mysql->options.extension->tls_fp ||
191 mysql->options.extension->tls_fp_list)))
192 {
193 my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
194 ER(CR_SSL_CONNECTION_ERROR),
195 "SSL is required, but the server does not support it");
196 goto error;
197 }
198 }
199
200
201 /* Remove options that server doesn't support */
202 mysql->client_flag= mysql->client_flag &
203 (~(CLIENT_COMPRESS | CLIENT_SSL | CLIENT_PROTOCOL_41)
204 | mysql->server_capabilities);
205
206#ifndef HAVE_COMPRESS
207 mysql->client_flag&= ~CLIENT_COMPRESS;
208#endif
209
210 if (mysql->client_flag & CLIENT_PROTOCOL_41)
211 {
212 /* 4.1 server and 4.1 client has a 32 byte option flag */
213 if (!(mysql->server_capabilities & CLIENT_MYSQL))
214 mysql->client_flag&= ~CLIENT_MYSQL;
215 int4store(buff,mysql->client_flag);
216 int4store(buff+4, net->max_packet_size);
217 buff[8]= (char) mysql->charset->nr;
218 memset(buff + 9, 0, 32-9);
219 if (!(mysql->server_capabilities & CLIENT_MYSQL))
220 {
221 mysql->extension->mariadb_client_flag = MARIADB_CLIENT_SUPPORTED_FLAGS >> 32;
222 int4store(buff + 28, mysql->extension->mariadb_client_flag);
223 }
224 end= buff+32;
225 }
226 else
227 {
228 int2store(buff, mysql->client_flag);
229 int3store(buff+2, net->max_packet_size);
230 end= buff+5;
231 }
232#ifdef HAVE_TLS
233 if (mysql->options.ssl_key ||
234 mysql->options.ssl_cert ||
235 mysql->options.ssl_ca ||
236 mysql->options.ssl_capath ||
237 mysql->options.ssl_cipher
238#ifdef CRL_IMPLEMENTED
239 || (mysql->options.extension &&
240 (mysql->options.extension->ssl_crl ||
241 mysql->options.extension->ssl_crlpath))
242#endif
243 )
244 mysql->options.use_ssl= 1;
245 if (mysql->options.use_ssl &&
246 (mysql->client_flag & CLIENT_SSL))
247 {
248 /*
249 Send mysql->client_flag, max_packet_size - unencrypted otherwise
250 the server does not know we want to do SSL
251 */
252 if (ma_net_write(net, (unsigned char *)buff, (size_t) (end-buff)) || ma_net_flush(net))
253 {
254 my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
255 ER(CR_SERVER_LOST_EXTENDED),
256 "sending connection information to server",
257 errno);
258 goto error;
259 }
260 if (ma_pvio_start_ssl(mysql->net.pvio))
261 goto error;
262 }
263#endif /* HAVE_TLS */
264
265 /* This needs to be changed as it's not useful with big packets */
266 if (mysql->user && mysql->user[0])
267 ma_strmake(end, mysql->user, USERNAME_LENGTH);
268 else
269 read_user_name(end);
270
271 /* We have to handle different version of handshake here */
272 end+= strlen(end) + 1;
273 if (data_len)
274 {
275 if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION)
276 {
277 *end++= data_len;
278 memcpy(end, data, data_len);
279 end+= data_len;
280 }
281 else
282 {
283 DBUG_ASSERT(data_len == SCRAMBLE_LENGTH_323 + 1); /* incl. \0 at the end */
284 memcpy(end, data, data_len);
285 end+= data_len;
286 }
287 }
288 else
289 *end++= 0;
290
291 /* Add database if needed */
292 if (mpvio->db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB))
293 {
294 end= ma_strmake(end, mpvio->db, NAME_LEN) + 1;
295 mysql->db= strdup(mpvio->db);
296 }
297
298 if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH)
299 end= ma_strmake(end, mpvio->plugin->name, NAME_LEN) + 1;
300
301 end= ma_send_connect_attr(mysql, (unsigned char *)end);
302
303 /* Write authentication package */
304 if (ma_net_write(net, (unsigned char *)buff, (size_t) (end-buff)) || ma_net_flush(net))
305 {
306 my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
307 ER(CR_SERVER_LOST_EXTENDED),
308 "sending authentication information",
309 errno);
310 goto error;
311 }
312 free(buff);
313 return 0;
314
315error:
316 free(buff);
317 return 1;
318}
319
320/**
321 vio->read_packet() callback method for client authentication plugins
322
323 This function is called by a client authentication plugin, when it wants
324 to read data from the server.
325*/
326
327static int client_mpvio_read_packet(struct st_plugin_vio *mpv, uchar **buf)
328{
329 MCPVIO_EXT *mpvio= (MCPVIO_EXT*)mpv;
330 MYSQL *mysql= mpvio->mysql;
331 ulong pkt_len;
332
333 /* there are cached data left, feed it to a plugin */
334 if (mpvio->cached_server_reply.pkt)
335 {
336 *buf= mpvio->cached_server_reply.pkt;
337 mpvio->cached_server_reply.pkt= 0;
338 mpvio->packets_read++;
339 return mpvio->cached_server_reply.pkt_len;
340 }
341
342 if (mpvio->packets_read == 0)
343 {
344 /*
345 the server handshake packet came from the wrong plugin,
346 or it's mysql_change_user(). Either way, there is no data
347 for a plugin to read. send a dummy packet to the server
348 to initiate a dialog.
349 */
350 if (client_mpvio_write_packet(mpv, 0, 0))
351 return (int)packet_error;
352 }
353
354 /* otherwise read the data */
355 pkt_len= ma_net_safe_read(mysql);
356 mpvio->last_read_packet_len= pkt_len;
357 *buf= mysql->net.read_pos;
358
359 /* was it a request to change plugins ? */
360 if (**buf == 254)
361 return (int)packet_error; /* if yes, this plugin shan't continue */
362
363 /*
364 the server sends \1\255 or \1\254 instead of just \255 or \254 -
365 for us to not confuse it with an error or "change plugin" packets.
366 We remove this escaping \1 here.
367
368 See also server_mpvio_write_packet() where the escaping is done.
369 */
370 if (pkt_len && **buf == 1)
371 {
372 (*buf)++;
373 pkt_len--;
374 }
375 mpvio->packets_read++;
376 return pkt_len;
377}
378
379/**
380 vio->write_packet() callback method for client authentication plugins
381
382 This function is called by a client authentication plugin, when it wants
383 to send data to the server.
384
385 It transparently wraps the data into a change user or authentication
386 handshake packet, if necessary.
387*/
388
389static int client_mpvio_write_packet(struct st_plugin_vio *mpv,
390 const uchar *pkt, size_t pkt_len)
391{
392 int res;
393 MCPVIO_EXT *mpvio= (MCPVIO_EXT*)mpv;
394
395 if (mpvio->packets_written == 0)
396 {
397 if (mpvio->mysql_change_user)
398 res= send_change_user_packet(mpvio, pkt, (int)pkt_len);
399 else
400 res= send_client_reply_packet(mpvio, pkt, (int)pkt_len);
401 }
402 else
403 {
404 NET *net= &mpvio->mysql->net;
405 if (mpvio->mysql->thd)
406 res= 1; /* no chit-chat in embedded */
407 else
408 res= ma_net_write(net, (unsigned char *)pkt, pkt_len) || ma_net_flush(net);
409 }
410
411 if (res)
412 {
413 /* don't overwrite errors */
414 if (!mysql_errno(mpvio->mysql))
415 my_set_error(mpvio->mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
416 ER(CR_SERVER_LOST_EXTENDED),
417 "sending authentication information",
418 errno);
419 }
420 mpvio->packets_written++;
421 return res;
422}
423
424/**
425 fills MYSQL_PLUGIN_VIO_INFO structure with the information about the
426 connection
427*/
428
429void mpvio_info(MARIADB_PVIO *pvio, MYSQL_PLUGIN_VIO_INFO *info)
430{
431 memset(info, 0, sizeof(*info));
432 switch (pvio->type) {
433 case PVIO_TYPE_SOCKET:
434 info->protocol= MYSQL_VIO_TCP;
435 ma_pvio_get_handle(pvio, &info->socket);
436 return;
437 case PVIO_TYPE_UNIXSOCKET:
438 info->protocol= MYSQL_VIO_SOCKET;
439 ma_pvio_get_handle(pvio, &info->socket);
440 return;
441 /*
442 case VIO_TYPE_SSL:
443 {
444 struct sockaddr addr;
445 SOCKET_SIZE_TYPE addrlen= sizeof(addr);
446 if (getsockname(vio->sd, &addr, &addrlen))
447 return;
448 info->protocol= addr.sa_family == AF_UNIX ?
449 MYSQL_VIO_SOCKET : MYSQL_VIO_TCP;
450 info->socket= vio->sd;
451 return;
452 }
453 */
454#ifdef _WIN32
455 /*
456 case VIO_TYPE_NAMEDPIPE:
457 info->protocol= MYSQL_VIO_PIPE;
458 info->handle= vio->hPipe;
459 return;
460 */
461/* not supported yet
462 case VIO_TYPE_SHARED_MEMORY:
463 info->protocol= MYSQL_VIO_MEMORY;
464 info->handle= vio->handle_file_map;
465 return;
466*/
467#endif
468 default: DBUG_ASSERT(0);
469 }
470}
471
472static void client_mpvio_info(MYSQL_PLUGIN_VIO *vio,
473 MYSQL_PLUGIN_VIO_INFO *info)
474{
475 MCPVIO_EXT *mpvio= (MCPVIO_EXT*)vio;
476 mpvio_info(mpvio->mysql->net.pvio, info);
477}
478
479/**
480 Client side of the plugin driver authentication.
481
482 @note this is used by both the mysql_real_connect and mysql_change_user
483
484 @param mysql mysql
485 @param data pointer to the plugin auth data (scramble) in the
486 handshake packet
487 @param data_len the length of the data
488 @param data_plugin a plugin that data were prepared for
489 or 0 if it's mysql_change_user()
490 @param db initial db to use, can be 0
491
492 @retval 0 ok
493 @retval 1 error
494*/
495
496int run_plugin_auth(MYSQL *mysql, char *data, uint data_len,
497 const char *data_plugin, const char *db)
498{
499 const char *auth_plugin_name;
500 auth_plugin_t *auth_plugin;
501 MCPVIO_EXT mpvio;
502 ulong pkt_length;
503 int res;
504
505 /* determine the default/initial plugin to use */
506 if (mysql->options.extension && mysql->options.extension->default_auth &&
507 mysql->server_capabilities & CLIENT_PLUGIN_AUTH)
508 {
509 auth_plugin_name= mysql->options.extension->default_auth;
510 if (!(auth_plugin= (auth_plugin_t*) mysql_client_find_plugin(mysql,
511 auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN)))
512 return 1; /* oops, not found */
513 }
514 else
515 {
516 if (mysql->server_capabilities & CLIENT_PROTOCOL_41)
517 auth_plugin= &mysql_native_password_client_plugin;
518 else
519 {
520 if (!(auth_plugin= (auth_plugin_t*)mysql_client_find_plugin(mysql,
521 "mysql_old_password", MYSQL_CLIENT_AUTHENTICATION_PLUGIN)))
522 return 1; /* not found */
523 }
524 auth_plugin_name= auth_plugin->name;
525 }
526
527 mysql->net.last_errno= 0; /* just in case */
528
529 if (data_plugin && strcmp(data_plugin, auth_plugin_name))
530 {
531 /* data was prepared for a different plugin, don't show it to this one */
532 data= 0;
533 data_len= 0;
534 }
535
536 mpvio.mysql_change_user= data_plugin == 0;
537 mpvio.cached_server_reply.pkt= (uchar*)data;
538 mpvio.cached_server_reply.pkt_len= data_len;
539 mpvio.read_packet= client_mpvio_read_packet;
540 mpvio.write_packet= client_mpvio_write_packet;
541 mpvio.info= client_mpvio_info;
542 mpvio.mysql= mysql;
543 mpvio.packets_read= mpvio.packets_written= 0;
544 mpvio.db= db;
545 mpvio.plugin= auth_plugin;
546
547 res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql);
548
549 if (res > CR_OK && mysql->net.read_pos[0] != 254)
550 {
551 /*
552 the plugin returned an error. write it down in mysql,
553 unless the error code is CR_ERROR and mysql->net.last_errno
554 is already set (the plugin has done it)
555 */
556 if (res > CR_ERROR)
557 my_set_error(mysql, res, SQLSTATE_UNKNOWN, 0);
558 else
559 if (!mysql->net.last_errno) {
560 my_set_error(mysql, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, 0);
561 }
562 return 1;
563 }
564
565 /* read the OK packet (or use the cached value in mysql->net.read_pos */
566 if (res == CR_OK)
567 pkt_length= ma_net_safe_read(mysql);
568 else /* res == CR_OK_HANDSHAKE_COMPLETE */
569 pkt_length= mpvio.last_read_packet_len;
570
571 if (pkt_length == packet_error)
572 {
573 if (mysql->net.last_errno == CR_SERVER_LOST)
574 my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
575 ER(CR_SERVER_LOST_EXTENDED),
576 "reading authorization packet",
577 errno);
578 return 1;
579 }
580
581 if (mysql->net.read_pos[0] == 254)
582 {
583 /* The server asked to use a different authentication plugin */
584 if (pkt_length == 1)
585 {
586 /* old "use short scramble" packet */
587 auth_plugin_name= old_password_plugin_name;
588 mpvio.cached_server_reply.pkt= (uchar*)mysql->scramble_buff;
589 mpvio.cached_server_reply.pkt_len= SCRAMBLE_LENGTH + 1;
590 }
591 else
592 {
593 /* new "use different plugin" packet */
594 uint len;
595 auth_plugin_name= (char*)mysql->net.read_pos + 1;
596 len= (uint)strlen(auth_plugin_name); /* safe as ma_net_read always appends \0 */
597 mpvio.cached_server_reply.pkt_len= pkt_length - len - 2;
598 mpvio.cached_server_reply.pkt= mysql->net.read_pos + len + 2;
599 }
600 if (!(auth_plugin= (auth_plugin_t *) mysql_client_find_plugin(mysql,
601 auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN)))
602 return 1;
603
604 mpvio.plugin= auth_plugin;
605 res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql);
606
607 if (res > CR_OK)
608 {
609 if (res > CR_ERROR)
610 my_set_error(mysql, res, SQLSTATE_UNKNOWN, 0);
611 else
612 if (!mysql->net.last_errno)
613 my_set_error(mysql, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, 0);
614 return 1;
615 }
616
617 if (res != CR_OK_HANDSHAKE_COMPLETE)
618 {
619 /* Read what server thinks about out new auth message report */
620 if (ma_net_safe_read(mysql) == packet_error)
621 {
622 if (mysql->net.last_errno == CR_SERVER_LOST)
623 my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
624 ER(CR_SERVER_LOST_EXTENDED),
625 "reading final connect information",
626 errno);
627 return 1;
628 }
629 }
630 }
631 /*
632 net->read_pos[0] should always be 0 here if the server implements
633 the protocol correctly
634 */
635 return mysql->net.read_pos[0] != 0;
636}
637
638