/* SSL error handling */ static void my_SSL_error(MYSQL *mysql) { ulong ssl_errno= ERR_get_error(); char ssl_error[MAX_SSL_ERR_LEN]; const char *ssl_error_reason; DBUG_ENTER("my_SSL_error"); if (mysql_errno(mysql)) DBUG_VOID_RETURN; if (!ssl_errno) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); DBUG_VOID_RETURN; } if ((ssl_error_reason= ERR_reason_error_string(ssl_errno))) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), ssl_error_reason); DBUG_VOID_RETURN; } my_snprintf(ssl_error, MAX_SSL_ERR_LEN, "SSL errno=%lu", ssl_errno, mysql->charset); my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), ssl_error); DBUG_VOID_RETURN; }
unsigned int ma_ssl_get_finger_print(MARIADB_SSL *cssl, unsigned char *fp, unsigned int len) { MYSQL *mysql; size_t fp_len= len; const gnutls_datum_t *cert_list; unsigned int cert_list_size; if (!cssl || !cssl->ssl) return 0; mysql= (MYSQL *)gnutls_session_get_ptr(cssl->ssl); cert_list = gnutls_certificate_get_peers (cssl->ssl, &cert_list_size); if (cert_list == NULL) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Unable to get server certificate"); return 0; } if (gnutls_fingerprint(GNUTLS_DIG_SHA1, &cert_list[0], fp, &fp_len) == 0) return fp_len; else { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Finger print buffer too small"); return 0; } }
/* establish SSL connection between client and server SYNOPSIS my_ssl_connect ssl ssl object RETURN VALUES 0 success 1 error */ int my_ssl_connect(SSL *ssl) { my_bool blocking; MYSQL *mysql; long rc; my_bool try_connect= 1; DBUG_ENTER("my_ssl_connect"); DBUG_ASSERT(ssl != NULL); mysql= (MYSQL *)SSL_get_app_data(ssl); CLEAR_CLIENT_ERROR(mysql); /* Set socket to non blocking */ if (!(blocking= vio_is_blocking(mysql->net.vio))) vio_blocking(mysql->net.vio, FALSE, 0); SSL_clear(ssl); SSL_SESSION_set_timeout(SSL_get_session(ssl), mysql->options.connect_timeout); SSL_set_fd(ssl, mysql->net.vio->sd); while (try_connect && (rc= SSL_connect(ssl)) == -1) { switch(SSL_get_error(ssl, rc)) { case SSL_ERROR_WANT_READ: if (vio_wait_or_timeout(mysql->net.vio, TRUE, mysql->options.connect_timeout) < 1) try_connect= 0; break; case SSL_ERROR_WANT_WRITE: if (vio_wait_or_timeout(mysql->net.vio, TRUE, mysql->options.connect_timeout) < 1) try_connect= 0; break; default: try_connect= 0; } } if (rc != 1) { my_SSL_error(mysql); DBUG_RETURN(1); } rc= SSL_get_verify_result(ssl); if (rc != X509_V_OK) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(rc)); /* restore blocking mode */ if (!blocking) vio_blocking(mysql->net.vio, FALSE, 0); DBUG_RETURN(1); } vio_reset(mysql->net.vio, VIO_TYPE_SSL, mysql->net.vio->sd, 0, 0); mysql->net.vio->ssl= ssl; DBUG_RETURN(0); }
static int client_mpvio_write_packet(struct st_plugin_vio *mpv, const uchar *pkt, size_t pkt_len) { int res; MCPVIO_EXT *mpvio= (MCPVIO_EXT*)mpv; if (mpvio->packets_written == 0) { if (mpvio->mysql_change_user) res= send_change_user_packet(mpvio, pkt, (int)pkt_len); else res= send_client_reply_packet(mpvio, pkt, (int)pkt_len); } else { NET *net= &mpvio->mysql->net; if (mpvio->mysql->thd) res= 1; /* no chit-chat in embedded */ else res= my_net_write(net, (char *)pkt, pkt_len) || net_flush(net); if (res) my_set_error(mpvio->mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, ER(CR_SERVER_LOST_EXTENDED), "sending authentication information", errno); } mpvio->packets_written++; return res; }
int my_ssl_verify_server_cert(SSL *ssl) { X509 *cert; MYSQL *mysql; char *p1, *p2, buf[256]; DBUG_ENTER("my_ssl_verify_server_cert"); mysql= (MYSQL *)SSL_get_app_data(ssl); if (!mysql->host) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Invalid (empty) hostname"); DBUG_RETURN(1); } if (!(cert= SSL_get_peer_certificate(ssl))) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Unable to get server certificate"); DBUG_RETURN(1); } X509_NAME_oneline(X509_get_subject_name(cert), buf, 256); X509_free(cert); /* Extract the server name from buffer: Format: ....CN=/hostname/.... */ if ((p1= strstr(buf, "/CN="))) { p1+= 4; if ((p2= strchr(p1, '/'))) *p2= 0; if (!strcmp(mysql->host, p1)) DBUG_RETURN(0); } my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Validation of SSL server certificate failed"); DBUG_RETURN(1); }
static int send_change_user_packet(MCPVIO_EXT *mpvio, const uchar *data, int data_len) { MYSQL *mysql= mpvio->mysql; char *buff, *end; int res= 1; size_t conn_attr_len= (mysql->options.extension) ? mysql->options.extension->connect_attrs_len : 0; buff= my_alloca(USERNAME_LENGTH+1 + data_len+1 + NAME_LEN+1 + 2 + NAME_LEN+1 + 9 + conn_attr_len); end= strmake(buff, mysql->user, USERNAME_LENGTH) + 1; if (!data_len) *end++= 0; else { if (mysql->client_flag & CLIENT_SECURE_CONNECTION) { DBUG_ASSERT(data_len <= 255); if (data_len > 255) { my_set_error(mysql, CR_MALFORMED_PACKET, SQLSTATE_UNKNOWN, 0); goto error; } *end++= data_len; } else { DBUG_ASSERT(data_len == SCRAMBLE_LENGTH_323 + 1); DBUG_ASSERT(data[SCRAMBLE_LENGTH_323] == 0); } memcpy(end, data, data_len); end+= data_len; } end= strmake(end, mpvio->db ? mpvio->db : "", NAME_LEN) + 1; if (mysql->server_capabilities & CLIENT_PROTOCOL_41) { int2store(end, (ushort) mysql->charset->nr); end+= 2; } if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) end= strmake(end, mpvio->plugin->name, NAME_LEN) + 1; end= ma_send_connect_attr(mysql, end); res= simple_command(mysql, MYSQL_COM_CHANGE_USER, buff, (ulong)(end-buff), 1, NULL); error: my_afree(buff); return res; }
my_bool ma_pvio_tls_check_fp(MARIADB_TLS *ctls, const char *fp, const char *fp_list) { unsigned int cert_fp_len= 64; char *cert_fp= NULL; my_bool rc=1; MYSQL *mysql= ctls->pvio->mysql; cert_fp= (char *)malloc(cert_fp_len); if ((cert_fp_len= ma_tls_get_finger_print(ctls, cert_fp, cert_fp_len)) < 1) goto end; if (fp) rc= ma_pvio_tls_compare_fp(cert_fp, cert_fp_len, fp, (unsigned int)strlen(fp)); else if (fp_list) { MA_FILE *fp; char buff[255]; if (!(fp = ma_open(fp_list, "r", mysql))) goto end; while (ma_gets(buff, sizeof(buff)-1, fp)) { /* remove trailing new line character */ char *pos= strchr(buff, '\r'); if (!pos) pos= strchr(buff, '\n'); if (pos) *pos= '\0'; if (!ma_pvio_tls_compare_fp(cert_fp, cert_fp_len, buff, (unsigned int)strlen(buff))) { /* finger print is valid: close file and exit */ ma_close(fp); rc= 0; goto end; } } /* No finger print matched - close file and return error */ ma_close(fp); } end: if (cert_fp) free(cert_fp); if (rc) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Fingerprint verification of server certificate failed"); } return rc; }
unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, unsigned char *fp, unsigned int len) { EVP_MD *digest= (EVP_MD *)EVP_sha1(); X509 *cert; MYSQL *mysql; unsigned int fp_len; if (!ctls || !ctls->ssl) return 0; mysql= SSL_get_app_data(ctls->ssl); if (!(cert= SSL_get_peer_certificate(ctls->ssl))) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Unable to get server certificate"); return 0; } if (len < EVP_MAX_MD_SIZE) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Finger print buffer too small"); return 0; } fp_len= len; if (!X509_digest(cert, digest, fp, &fp_len)) { ma_free(fp); my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "invalid finger print of server certificate"); return 0; } return (fp_len); }
/* establish SSL connection between client and server SYNOPSIS my_ssl_connect ssl ssl object RETURN VALUES 0 success 1 error */ int my_ssl_connect(SSL *ssl) { my_bool blocking; MYSQL *mysql; long rc; DBUG_ENTER("my_ssl_connect"); DBUG_ASSERT(ssl != NULL); mysql= (MYSQL *)SSL_get_app_data(ssl); CLEAR_CLIENT_ERROR(mysql); /* Set socket to blocking if not already set */ if (!(blocking= vio_is_blocking(mysql->net.vio))) vio_blocking(mysql->net.vio, TRUE, 0); SSL_clear(ssl); SSL_SESSION_set_timeout(SSL_get_session(ssl), mysql->options.connect_timeout); SSL_set_fd(ssl, mysql->net.vio->sd); if (SSL_connect(ssl) != 1) { my_SSL_error(mysql); /* restore blocking mode */ if (!blocking) vio_blocking(mysql->net.vio, FALSE, 0); DBUG_RETURN(1); } rc= SSL_get_verify_result(ssl); if (rc != X509_V_OK) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(rc)); /* restore blocking mode */ if (!blocking) vio_blocking(mysql->net.vio, FALSE, 0); DBUG_RETURN(1); } vio_reset(mysql->net.vio, VIO_TYPE_SSL, mysql->net.vio->sd, 0, 0); mysql->net.vio->ssl= ssl; DBUG_RETURN(0); }
my_bool ma_tls_connect(MARIADB_TLS *ctls) { SSL *ssl = (SSL *)ctls->ssl; my_bool blocking; MYSQL *mysql; MARIADB_PVIO *pvio; int rc; mysql= (MYSQL *)SSL_get_app_data(ssl); pvio= mysql->net.pvio; /* Set socket to blocking if not already set */ if (!(blocking= pvio->methods->is_blocking(pvio))) pvio->methods->blocking(pvio, TRUE, 0); SSL_clear(ssl); SSL_SESSION_set_timeout(SSL_get_session(ssl), mysql->options.connect_timeout); SSL_set_fd(ssl, mysql_get_socket(mysql)); if (SSL_connect(ssl) != 1) { ma_tls_set_error(mysql); /* restore blocking mode */ if (!blocking) pvio->methods->blocking(pvio, FALSE, 0); return 1; } if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT)) { rc= SSL_get_verify_result(ssl); if (rc != X509_V_OK) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(rc)); /* restore blocking mode */ if (!blocking) pvio->methods->blocking(pvio, FALSE, 0); return 1; } } pvio->ctls->ssl= ctls->ssl= (void *)ssl; return 0; }
static int my_verify_callback(int ok, X509_STORE_CTX *ctx) { X509 *check_cert; SSL *ssl; MYSQL *mysql; DBUG_ENTER("my_verify_callback"); ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); DBUG_ASSERT(ssl != NULL); mysql= (MYSQL *)SSL_get_app_data(ssl); DBUG_ASSERT(mysql != NULL); /* skip verification if no ca_file/path was specified */ if (!mysql->options.ssl_ca && !mysql->options.ssl_capath) { ok= 1; DBUG_RETURN(1); } if (!ok) { uint depth; if (!(check_cert= X509_STORE_CTX_get_current_cert(ctx))) DBUG_RETURN(0); depth= X509_STORE_CTX_get_error_depth(ctx); if (depth == 0) { ok= 1; DBUG_RETURN(1); } } else DBUG_RETURN(1); my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(ctx->error)); DBUG_RETURN(0); }
int my_ssl_verify_server_cert(SSL *ssl) { X509 *cert; MYSQL *mysql; X509_NAME *x509sn; int cn_pos; X509_NAME_ENTRY *cn_entry; ASN1_STRING *cn_asn1; const char *cn_str; DBUG_ENTER("my_ssl_verify_server_cert"); mysql= (MYSQL *)SSL_get_app_data(ssl); if (!mysql->host) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Invalid (empty) hostname"); DBUG_RETURN(1); } if (!(cert= SSL_get_peer_certificate(ssl))) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Unable to get server certificate"); DBUG_RETURN(1); } x509sn= X509_get_subject_name(cert); if ((cn_pos= X509_NAME_get_index_by_NID(x509sn, NID_commonName, -1)) < 0) goto error; if (!(cn_entry= X509_NAME_get_entry(x509sn, cn_pos))) goto error; if (!(cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry))) goto error; cn_str = (char *)ASN1_STRING_data(cn_asn1); /* Make sure there is no embedded \0 in the CN */ if ((size_t)ASN1_STRING_length(cn_asn1) != strlen(cn_str)) goto error; if (strcmp(cn_str, mysql->host)) goto error; X509_free(cert); DBUG_RETURN(0); error: X509_free(cert); my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Validation of SSL server certificate failed"); DBUG_RETURN(1); }
static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, int data_len) { MYSQL *mysql= mpvio->mysql; NET *net= &mysql->net; char *buff, *end; size_t conn_attr_len= (mysql->options.extension) ? mysql->options.extension->connect_attrs_len : 0; /* see end= buff+32 below, fixed size of the packet is 32 bytes */ buff= malloc(33 + USERNAME_LENGTH + data_len + NAME_LEN + NAME_LEN + conn_attr_len + 9); end= buff; mysql->client_flag|= mysql->options.client_flag; mysql->client_flag|= CLIENT_CAPABILITIES; if (mysql->client_flag & CLIENT_MULTI_STATEMENTS) mysql->client_flag|= CLIENT_MULTI_RESULTS; #if defined(HAVE_TLS) && !defined(EMBEDDED_LIBRARY) if (mysql->options.ssl_key || mysql->options.ssl_cert || mysql->options.ssl_ca || mysql->options.ssl_capath || mysql->options.ssl_cipher || mysql->options.use_ssl || (mysql->options.client_flag & CLIENT_SSL_VERIFY_SERVER_CERT)) mysql->options.use_ssl= 1; if (mysql->options.use_ssl) mysql->client_flag|= CLIENT_SSL; #endif /* HAVE_TLS && !EMBEDDED_LIBRARY*/ if (mpvio->db) mysql->client_flag|= CLIENT_CONNECT_WITH_DB; /* if server doesn't support SSL and verification of server certificate was set to mandatory, we need to return an error */ if (mysql->options.use_ssl && !(mysql->server_capabilities & CLIENT_SSL)) { if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) || (mysql->options.extension && (mysql->options.extension->tls_fp || mysql->options.extension->tls_fp_list))) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "SSL is required, but the server does not support it"); goto error; } } /* Remove options that server doesn't support */ mysql->client_flag= mysql->client_flag & (~(CLIENT_COMPRESS | CLIENT_SSL | CLIENT_PROTOCOL_41) | mysql->server_capabilities); #ifndef HAVE_COMPRESS mysql->client_flag&= ~CLIENT_COMPRESS; #endif if (mysql->client_flag & CLIENT_PROTOCOL_41) { /* 4.1 server and 4.1 client has a 32 byte option flag */ if (!(mysql->server_capabilities & CLIENT_MYSQL)) mysql->client_flag&= ~CLIENT_MYSQL; int4store(buff,mysql->client_flag); int4store(buff+4, net->max_packet_size); buff[8]= (char) mysql->charset->nr; memset(buff + 9, 0, 32-9); if (!(mysql->server_capabilities & CLIENT_MYSQL)) { mysql->extension->mariadb_client_flag = MARIADB_CLIENT_SUPPORTED_FLAGS >> 32; int4store(buff + 28, mysql->extension->mariadb_client_flag); } end= buff+32; }
static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, int data_len) { MYSQL *mysql= mpvio->mysql; NET *net= &mysql->net; char *buff, *end; size_t conn_attr_len= (mysql->options.extension) ? mysql->options.extension->connect_attrs_len : 0; /* see end= buff+32 below, fixed size of the packet is 32 bytes */ buff= my_alloca(33 + USERNAME_LENGTH + data_len + NAME_LEN + NAME_LEN + conn_attr_len + 9); mysql->client_flag|= mysql->options.client_flag; mysql->client_flag|= CLIENT_CAPABILITIES; if (mysql->client_flag & CLIENT_MULTI_STATEMENTS) mysql->client_flag|= CLIENT_MULTI_RESULTS; #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) if (mysql->options.ssl_key || mysql->options.ssl_cert || mysql->options.ssl_ca || mysql->options.ssl_capath || mysql->options.ssl_cipher) mysql->options.use_ssl= 1; if (mysql->options.use_ssl) mysql->client_flag|= CLIENT_SSL; /* if server doesn't support SSL and verification of server certificate was set to mandatory, we need to return an error */ if (mysql->options.use_ssl && !(mysql->server_capabilities & CLIENT_SSL)) { if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) || (mysql->options.extension && (mysql->options.extension->ssl_fp || mysql->options.extension->ssl_fp_list))) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Server doesn't support SSL"); goto error; } } #endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY*/ if (mpvio->db) mysql->client_flag|= CLIENT_CONNECT_WITH_DB; /* Remove options that server doesn't support */ mysql->client_flag= mysql->client_flag & (~(CLIENT_COMPRESS | CLIENT_SSL | CLIENT_PROTOCOL_41) | mysql->server_capabilities); #ifndef HAVE_COMPRESS mysql->client_flag&= ~CLIENT_COMPRESS; #endif if (mysql->client_flag & CLIENT_PROTOCOL_41) { /* 4.1 server and 4.1 client has a 32 byte option flag */ int4store(buff,mysql->client_flag); int4store(buff+4, net->max_packet_size); buff[8]= (char) mysql->charset->nr; bzero(buff+9, 32-9); end= buff+32; } else { int2store(buff, mysql->client_flag); int3store(buff+2, net->max_packet_size); end= buff+5; } #ifdef HAVE_OPENSSL if (mysql->options.ssl_key || mysql->options.ssl_cert || mysql->options.ssl_ca || mysql->options.ssl_capath || mysql->options.ssl_cipher #ifdef CRL_IMPLEMENTED || (mysql->options.extension && (mysql->options.extension->ssl_crl || mysql->options.extension->ssl_crlpath)) #endif ) mysql->options.use_ssl= 1; if (mysql->options.use_ssl && (mysql->client_flag & CLIENT_SSL)) { SSL *ssl; /* Send mysql->client_flag, max_packet_size - unencrypted otherwise the server does not know we want to do SSL */ if (my_net_write(net, (char*)buff, (size_t) (end-buff)) || net_flush(net)) { my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, ER(CR_SERVER_LOST_EXTENDED), "sending connection information to server", errno); goto error; } /* Create SSL */ if (!(ssl= my_ssl_init(mysql))) goto error; /* Connect to the server */ if (my_ssl_connect(ssl)) { SSL_free(ssl); goto error; } if (mysql->options.extension && (mysql->options.extension->ssl_fp || mysql->options.extension->ssl_fp_list)) { if (ma_ssl_verify_fingerprint(ssl)) goto error; } if ((mysql->options.ssl_ca || mysql->options.ssl_capath) && (mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) && my_ssl_verify_server_cert(ssl)) goto error; } #endif /* HAVE_OPENSSL */ DBUG_PRINT("info",("Server version = '%s' capabilites: %lu status: %u client_flag: %lu", mysql->server_version, mysql->server_capabilities, mysql->server_status, mysql->client_flag)); compile_time_assert(MYSQL_USERNAME_LENGTH == USERNAME_LENGTH); /* This needs to be changed as it's not useful with big packets */ if (mysql->user[0]) strmake(end, mysql->user, USERNAME_LENGTH); else read_user_name(end); /* We have to handle different version of handshake here */ DBUG_PRINT("info",("user: %s",end)); end= strend(end) + 1; if (data_len) { if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) { *end++= data_len; memcpy(end, data, data_len); end+= data_len; } else { DBUG_ASSERT(data_len == SCRAMBLE_LENGTH_323 + 1); /* incl. \0 at the end */ memcpy(end, data, data_len); end+= data_len; } } else *end++= 0; /* Add database if needed */ if (mpvio->db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB)) { end= strmake(end, mpvio->db, NAME_LEN) + 1; mysql->db= my_strdup(mpvio->db, MYF(MY_WME)); } if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) end= strmake(end, mpvio->plugin->name, NAME_LEN) + 1; end= ma_send_connect_attr(mysql, end); /* Write authentication package */ if (my_net_write(net, buff, (size_t) (end-buff)) || net_flush(net)) { my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, ER(CR_SERVER_LOST_EXTENDED), "sending authentication information", errno); goto error; } my_afree(buff); return 0; error: my_afree(buff); return 1; }
int ma_ssl_verify_fingerprint(SSL *ssl) { X509 *cert= SSL_get_peer_certificate(ssl); MYSQL *mysql= (MYSQL *)SSL_get_app_data(ssl); unsigned char fingerprint[EVP_MAX_MD_SIZE]; EVP_MD *digest; unsigned int fp_length; DBUG_ENTER("ma_ssl_verify_fingerprint"); if (!cert) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Unable to get server certificate"); DBUG_RETURN(1); } digest= (EVP_MD *)EVP_sha1(); fp_length= sizeof(fingerprint); if (!ma_get_cert_fingerprint(cert, digest, fingerprint, &fp_length)) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Unable to get finger print of server certificate"); DBUG_RETURN(1); } /* single finger print was specified */ if (mysql->options.extension->ssl_fp) { if (ma_check_fingerprint(fingerprint, fp_length, mysql->options.extension->ssl_fp, strlen(mysql->options.extension->ssl_fp))) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "invalid finger print of server certificate"); DBUG_RETURN(1); } } /* white list of finger prints was specified */ if (mysql->options.extension->ssl_fp_list) { FILE *fp; char buff[255]; if (!(fp = my_fopen(mysql->options.extension->ssl_fp_list ,O_RDONLY, MYF(0)))) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Can't open finger print list"); DBUG_RETURN(1); } while (fgets(buff, sizeof(buff)-1, fp)) { /* remove trailing new line character */ char *pos= strchr(buff, '\r'); if (!pos) pos= strchr(buff, '\n'); if (pos) *pos= '\0'; if (!ma_check_fingerprint(fingerprint, fp_length, buff, strlen(buff))) { /* finger print is valid: close file and exit */ my_fclose(fp, MYF(0)); DBUG_RETURN(0); } } /* No finger print matched - close file and return error */ my_fclose(fp, MYF(0)); my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "invalid finger print of server certificate"); DBUG_RETURN(1); } DBUG_RETURN(0); }
/* {{{ mysql_handle_local_infile */ my_bool mysql_handle_local_infile(MYSQL *conn, const char *filename) { unsigned int buflen= 4096; int bufread; unsigned char *buf= NULL; void *info= NULL; my_bool result= 1; /* check if all callback functions exist */ if (!conn->options.local_infile_init || !conn->options.local_infile_end || !conn->options.local_infile_read || !conn->options.local_infile_error) { conn->options.local_infile_userdata= conn; mysql_set_local_infile_default(conn); } if (!(conn->options.client_flag & CLIENT_LOCAL_FILES)) { my_set_error(conn, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, "Load data local infile forbidden"); /* write empty packet to server */ ma_net_write(&conn->net, (unsigned char *)"", 0); ma_net_flush(&conn->net); goto infile_error; } /* allocate buffer for reading data */ buf = (uchar *)malloc(buflen); /* init handler: allocate read buffer and open file */ if (conn->options.local_infile_init(&info, filename, conn->options.local_infile_userdata)) { char tmp_buf[MYSQL_ERRMSG_SIZE]; int tmp_errno; tmp_errno= conn->options.local_infile_error(info, tmp_buf, sizeof(tmp_buf)); my_set_error(conn, tmp_errno, SQLSTATE_UNKNOWN, tmp_buf); ma_net_write(&conn->net, (unsigned char *)"", 0); ma_net_flush(&conn->net); goto infile_error; } /* read data */ while ((bufread= conn->options.local_infile_read(info, (char *)buf, buflen)) > 0) { if (ma_net_write(&conn->net, (unsigned char *)buf, bufread)) { my_set_error(conn, CR_SERVER_LOST, SQLSTATE_UNKNOWN, NULL); goto infile_error; } } /* send empty packet for eof */ if (ma_net_write(&conn->net, (unsigned char *)"", 0) || ma_net_flush(&conn->net)) { my_set_error(conn, CR_SERVER_LOST, SQLSTATE_UNKNOWN, NULL); goto infile_error; } /* error during read occurred */ if (bufread < 0) { char tmp_buf[MYSQL_ERRMSG_SIZE]; int tmp_errno= conn->options.local_infile_error(info, tmp_buf, sizeof(tmp_buf)); my_set_error(conn, tmp_errno, SQLSTATE_UNKNOWN, tmp_buf); goto infile_error; } result = 0; infile_error: conn->options.local_infile_end(info); free(buf); return(result); }
my_bool ma_tls_connect(MARIADB_TLS *ctls) { SSL *ssl = (SSL *)ctls->ssl; my_bool blocking, try_connect= 1; MYSQL *mysql; MARIADB_PVIO *pvio; int rc; #if OPENSSL_USE_BIOMETHOD BIO_METHOD *bio_method= NULL; BIO *bio; #endif mysql= (MYSQL *)SSL_get_app_data(ssl); pvio= mysql->net.pvio; /* Set socket to non blocking if not already set */ if (!(blocking= pvio->methods->is_blocking(pvio))) pvio->methods->blocking(pvio, FALSE, 0); SSL_clear(ssl); #if OPENSSL_USE_BIOMETHOD bio= BIO_new(&ma_BIO_method); bio->ptr= pvio; SSL_set_bio(ssl, bio, bio); BIO_set_fd(bio, mysql_get_socket(mysql), BIO_NOCLOSE); #else SSL_set_fd(ssl, mysql_get_socket(mysql)); #endif while (try_connect && (rc= SSL_connect(ssl)) == -1) { switch(SSL_get_error(ssl, rc)) { case SSL_ERROR_WANT_READ: if (pvio->methods->wait_io_or_timeout(pvio, TRUE, mysql->options.connect_timeout) < 1) try_connect= 0; break; case SSL_ERROR_WANT_WRITE: if (pvio->methods->wait_io_or_timeout(pvio, TRUE, mysql->options.connect_timeout) < 1) try_connect= 0; break; default: try_connect= 0; } } if (rc != 1) { ma_tls_set_error(mysql); /* restore blocking mode */ if (!blocking) pvio->methods->blocking(pvio, FALSE, 0); return 1; } if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT)) { rc= SSL_get_verify_result(ssl); if (rc != X509_V_OK) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(rc)); /* restore blocking mode */ if (!blocking) pvio->methods->blocking(pvio, FALSE, 0); return 1; } } pvio->ctls->ssl= ctls->ssl= (void *)ssl; return 0; }
/* {{{ ma_open */ MA_FILE *ma_open(const char *location, const char *mode, MYSQL *mysql) { int CodePage= -1; FILE *fp= NULL; MA_FILE *ma_file= NULL; if (!location || !location[0]) return NULL; #ifdef HAVE_REMOTEIO if (strstr(location, "://")) goto remote; #endif #ifdef _WIN32 if (mysql && mysql->charset) CodePage= madb_get_windows_cp(mysql->charset->csname); #endif if (CodePage == -1) { if (!(fp= fopen(location, mode))) { #ifdef _WIN32 my_errno= GetLastError(); #else my_errno= errno; #endif return NULL; } } #ifdef _WIN32 /* See CONC-44: we need to support non ascii filenames too, so we convert current character set to wchar_t and try to open the file via _wsopen */ else { wchar_t *w_filename= NULL; wchar_t *w_mode= NULL; int len; DWORD Length; len= MultiByteToWideChar(CodePage, 0, location, (int)strlen(location), NULL, 0); if (!len) return NULL; if (!(w_filename= (wchar_t *)my_malloc((len + 1) * sizeof(wchar_t), MYF(MY_ZEROFILL)))) { my_set_error(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); return NULL; } Length= len; len= MultiByteToWideChar(CodePage, 0, location, (int)strlen(location), w_filename, (int)Length); if (!len) { /* todo: error handling */ my_free(w_filename); return NULL; } len= (int)strlen(mode); if (!(w_mode= (wchar_t *)my_malloc((len + 1) * sizeof(wchar_t), MYF(MY_ZEROFILL)))) { my_set_error(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); my_free(w_filename); return NULL; } Length= len; len= MultiByteToWideChar(CodePage, 0, mode, (int)strlen(mode), w_mode, (int)Length); if (!len) { /* todo: error handling */ my_free(w_filename); my_free(w_mode); return NULL; } fp= _wfopen(w_filename, w_mode); my_errno= GetLastError(); my_free(w_filename); my_free(w_mode); } #endif if (fp) { ma_file= (MA_FILE *)my_malloc(sizeof(MA_FILE), MYF(0)); if (!ma_file) { my_set_error(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); return NULL; } ma_file->type= MA_FILE_LOCAL; ma_file->ptr= (void *)fp; } return ma_file; #ifdef HAVE_REMOTEIO remote: /* check if plugin for remote io is available and try * to open location */ { MYSQL mysql; if (rio_plugin ||(rio_plugin= (struct st_mysql_client_plugin_REMOTEIO *) mysql_client_find_plugin(&mysql, NULL, MARIADB_CLIENT_REMOTEIO_PLUGIN))) return rio_plugin->methods->open(location, mode); return NULL; } #endif }
int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, const char *data_plugin, const char *db) { const char *auth_plugin_name; auth_plugin_t *auth_plugin; MCPVIO_EXT mpvio; ulong pkt_length; int res; /* determine the default/initial plugin to use */ if (mysql->options.extension && mysql->options.extension->default_auth && mysql->server_capabilities & CLIENT_PLUGIN_AUTH) { auth_plugin_name= mysql->options.extension->default_auth; if (!(auth_plugin= (auth_plugin_t*) mysql_client_find_plugin(mysql, auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) return 1; /* oops, not found */ } else { auth_plugin= mysql->server_capabilities & CLIENT_PROTOCOL_41 ? &native_password_client_plugin : &old_password_client_plugin; auth_plugin_name= auth_plugin->name; } mysql->net.last_errno= 0; /* just in case */ if (data_plugin && strcmp(data_plugin, auth_plugin_name)) { /* data was prepared for a different plugin, don't show it to this one */ data= 0; data_len= 0; } mpvio.mysql_change_user= data_plugin == 0; mpvio.cached_server_reply.pkt= (uchar*)data; mpvio.cached_server_reply.pkt_len= data_len; mpvio.read_packet= client_mpvio_read_packet; mpvio.write_packet= client_mpvio_write_packet; mpvio.info= client_mpvio_info; mpvio.mysql= mysql; mpvio.packets_read= mpvio.packets_written= 0; mpvio.db= db; mpvio.plugin= auth_plugin; res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql); compile_time_assert(CR_OK == -1); compile_time_assert(CR_ERROR == 0); if (res > CR_OK && mysql->net.read_pos[0] != 254) { /* the plugin returned an error. write it down in mysql, unless the error code is CR_ERROR and mysql->net.last_errno is already set (the plugin has done it) */ if (res > CR_ERROR) my_set_error(mysql, res, SQLSTATE_UNKNOWN, 0); else if (!mysql->net.last_errno) my_set_error(mysql, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, 0); return 1; } /* read the OK packet (or use the cached value in mysql->net.read_pos */ if (res == CR_OK) pkt_length= net_safe_read(mysql); else /* res == CR_OK_HANDSHAKE_COMPLETE */ pkt_length= mpvio.last_read_packet_len; if (pkt_length == packet_error) { if (mysql->net.last_errno == CR_SERVER_LOST) my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, ER(CR_SERVER_LOST_EXTENDED), "reading authorization packet", errno); return 1; } if (mysql->net.read_pos[0] == 254) { /* The server asked to use a different authentication plugin */ if (pkt_length == 1) { /* old "use short scramble" packet */ auth_plugin_name= old_password_plugin_name; mpvio.cached_server_reply.pkt= (uchar*)mysql->scramble_buff; mpvio.cached_server_reply.pkt_len= SCRAMBLE_LENGTH + 1; } else { /* new "use different plugin" packet */ uint len; auth_plugin_name= (char*)mysql->net.read_pos + 1; len= (uint)strlen(auth_plugin_name); /* safe as my_net_read always appends \0 */ mpvio.cached_server_reply.pkt_len= pkt_length - len - 2; mpvio.cached_server_reply.pkt= mysql->net.read_pos + len + 2; } if (!(auth_plugin= (auth_plugin_t *) mysql_client_find_plugin(mysql, auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) return 1; mpvio.plugin= auth_plugin; res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql); if (res > CR_OK) { if (res > CR_ERROR) my_set_error(mysql, res, SQLSTATE_UNKNOWN, 0); else if (!mysql->net.last_errno) my_set_error(mysql, CR_UNKNOWN_ERROR, SQLSTATE_UNKNOWN, 0); return 1; } if (res != CR_OK_HANDSHAKE_COMPLETE) { /* Read what server thinks about out new auth message report */ if (net_safe_read(mysql) == packet_error) { if (mysql->net.last_errno == CR_SERVER_LOST) my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, ER(CR_SERVER_LOST_EXTENDED), "reading final connect information", errno); return 1; } } } /* net->read_pos[0] should always be 0 here if the server implements the protocol correctly */ return mysql->net.read_pos[0] != 0; }