| /* | |
| * Copyright (C) Igor Sysoev | |
| */ | |
| /* the library supports the subset of the MySQL 4.1+ protocol (version 10) */ | |
| #include <ngx_config.h> | |
| #include <ngx_core.h> | |
| #include <ngx_event.h> | |
| #include <ngx_event_connect.h> | |
| #include <ngx_mysql.h> | |
| #include <ngx_sha1.h> | |
| #define NGX_MYSQL_LONG_PASSWORD 0x0001 | |
| #define NGX_MYSQL_CONNECT_WITH_DB 0x0008 | |
| #define NGX_MYSQL_PROTOCOL_41 0x0200 | |
| #define NGX_MYSQL_SECURE_CONNECTION 0x8000 | |
| #define NGX_MYSQL_CMD_QUERY 3 | |
| typedef struct { | |
| u_char pktlen[3]; | |
| u_char pktn; | |
| u_char protocol; | |
| u_char version[1]; /* NULL-terminated string */ | |
| } ngx_mysql_greeting1_pkt_t; | |
| typedef struct { | |
| u_char thread[4]; | |
| u_char salt1[9]; | |
| u_char capacity[2]; | |
| u_char charset; | |
| u_char status[2]; | |
| u_char zero[13]; | |
| u_char salt2[13]; | |
| } ngx_mysql_greeting2_pkt_t; | |
| typedef struct { | |
| u_char pktlen[3]; | |
| u_char pktn; | |
| u_char capacity[4]; | |
| u_char max_packet[4]; | |
| u_char charset; | |
| u_char zero[23]; | |
| u_char login[1]; /* NULL-terminated string */ | |
| /* | |
| * u_char passwd_len; 0 if no password | |
| * u_char passwd[20]; | |
| * | |
| * u_char database[1]; NULL-terminated string | |
| */ | |
| } ngx_mysql_auth_pkt_t; | |
| typedef struct { | |
| u_char pktlen[3]; | |
| u_char pktn; | |
| u_char fields; | |
| } ngx_mysql_response_pkt_t; | |
| typedef struct { | |
| u_char pktlen[3]; | |
| u_char pktn; | |
| u_char err; | |
| u_char code[2]; | |
| u_char message[1]; /* string */ | |
| } ngx_mysql_error_pkt_t; | |
| typedef struct { | |
| u_char pktlen[3]; | |
| u_char pktn; | |
| u_char command; | |
| u_char arg[1]; /* string */ | |
| } ngx_mysql_command_pkt_t; | |
| static void ngx_mysql_read_server_greeting(ngx_event_t *rev); | |
| static void ngx_mysql_empty_handler(ngx_event_t *wev); | |
| static void ngx_mysql_read_auth_result(ngx_event_t *rev); | |
| static void ngx_mysql_read_query_result(ngx_event_t *rev); | |
| static void ngx_mysql_close(ngx_mysql_t *m, ngx_int_t rc); | |
| ngx_int_t | |
| ngx_mysql_connect(ngx_mysql_t *m) | |
| { | |
| ngx_int_t rc; | |
| #if 0 | |
| if (cached) { | |
| return NGX_OK; | |
| } | |
| #endif | |
| m->peer.log->action = "connecting to mysql server"; | |
| rc = ngx_event_connect_peer(&m->peer); | |
| if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) { | |
| return rc; | |
| } | |
| m->peer.connection->data = m; | |
| m->peer.connection->read->handler = ngx_mysql_read_server_greeting; | |
| m->peer.connection->write->handler = ngx_mysql_empty_handler; | |
| ngx_add_timer(m->peer.connection->read, /* STUB */ 5000); | |
| return NGX_OK; | |
| } | |
| static void | |
| ngx_mysql_read_server_greeting(ngx_event_t *rev) | |
| { | |
| size_t len; | |
| u_char *p; | |
| ssize_t n; | |
| ngx_uint_t i, capacity; | |
| ngx_mysql_t *m; | |
| ngx_connection_t *c; | |
| ngx_mysql_greeting1_pkt_t *gr1; | |
| ngx_mysql_greeting2_pkt_t *gr2; | |
| ngx_mysql_auth_pkt_t *auth; | |
| ngx_sha1_t sha; | |
| u_char hash1[20], hash2[20]; | |
| c = rev->data; | |
| m = c->data; | |
| if (rev->timedout) { | |
| ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT, | |
| "mysql server %V timed out", m->peer.name); | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| if (m->buf == NULL) { | |
| m->peer.log->action = "reading mysql server greeting"; | |
| m->buf = ngx_create_temp_buf(m->pool, /* STUB */ 1024); | |
| if (m->buf == NULL) { | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| } | |
| n = ngx_recv(m->peer.connection, m->buf->pos, /* STUB */ 1024); | |
| if (n == NGX_AGAIN) { | |
| return; | |
| } | |
| if (n < 5) { | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| gr1 = (ngx_mysql_greeting1_pkt_t *) m->buf->pos; | |
| if (ngx_m24toh(gr1->pktlen) > n - 4) { | |
| ngx_log_error(NGX_LOG_ERR, rev->log, 0, | |
| "mysql server %V sent incomplete greeting packet", | |
| m->peer.name); | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| if (gr1->protocol < 10) { | |
| ngx_log_error(NGX_LOG_ERR, rev->log, 0, | |
| "mysql server %V sent unsupported protocol version %ud", | |
| m->peer.name, gr1->protocol); | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| gr2 = (ngx_mysql_greeting2_pkt_t *) | |
| (gr1->version + ngx_strlen(gr1->version) + 1); | |
| capacity = ngx_m16toh(gr2->capacity); | |
| ngx_log_debug8(NGX_LOG_DEBUG_MYSQL, rev->log, 0, | |
| "mysql version: %ud, \"%s\", thread: %ud, salt: \"%s\", " | |
| "capacity: %Xd, charset: %ud, status: %ud, salt rest \"%s\"", | |
| gr1->protocol, gr1->version, ngx_m32toh(gr2->thread), | |
| gr2->salt1, capacity, gr2->charset, | |
| ngx_m16toh(gr2->status), &gr2->salt2); | |
| capacity = NGX_MYSQL_LONG_PASSWORD | |
| | NGX_MYSQL_CONNECT_WITH_DB | |
| | NGX_MYSQL_PROTOCOL_41 | |
| | NGX_MYSQL_SECURE_CONNECTION; | |
| len = 4 + 4 + 4 + 1 + 23 + m->login->len + 1 + 1 + m->database->len + 1; | |
| if (m->passwd->len) { | |
| len += 20; | |
| } | |
| auth = ngx_pnalloc(m->pool, len); | |
| if (auth == NULL) { | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| ngx_htom24(auth->pktlen, len - 4); | |
| auth->pktn = (u_char) (gr1->pktn + 1); | |
| ngx_htom32(auth->capacity, capacity); | |
| ngx_htom32(auth->max_packet, 0x01000000); /* max packet size 2^24 */ | |
| ngx_memzero(auth->zero, 24); | |
| auth->charset = gr2->charset; | |
| p = ngx_copy(auth->login, m->login->data, m->login->len); | |
| *p++ = '\0'; | |
| if (m->passwd->len) { | |
| *p++ = (u_char) 20; | |
| ngx_sha1_init(&sha); | |
| ngx_sha1_update(&sha, m->passwd->data, m->passwd->len); | |
| ngx_sha1_final(hash1, &sha); | |
| ngx_sha1_init(&sha); | |
| ngx_sha1_update(&sha, hash1, 20); | |
| ngx_sha1_final(hash2, &sha); | |
| ngx_sha1_init(&sha); | |
| ngx_sha1_update(&sha, gr2->salt1, 8); | |
| ngx_sha1_update(&sha, gr2->salt2, 12); | |
| ngx_sha1_update(&sha, hash2, 20); | |
| ngx_sha1_final(hash2, &sha); | |
| for (i = 0; i < 20; i++) { | |
| *p++ = (u_char) (hash1[i] ^ hash2[i]); | |
| } | |
| } else { | |
| *p++ = '\0'; | |
| } | |
| p = ngx_copy(p, m->database->data, m->database->len); | |
| *p = '\0'; | |
| n = ngx_send(m->peer.connection, (void *) auth, len); | |
| if (n < (ssize_t) len) { | |
| ngx_log_error(NGX_LOG_ERR, rev->log, 0, | |
| "the incomplete packet was sent to mysql server %V", | |
| m->peer.name); | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| m->peer.connection->read->handler = ngx_mysql_read_auth_result; | |
| ngx_add_timer(m->peer.connection->read, /* STUB */ 5000); | |
| } | |
| static void | |
| ngx_mysql_empty_handler(ngx_event_t *wev) | |
| { | |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "mysql empty handler"); | |
| return; | |
| } | |
| static void | |
| ngx_mysql_read_auth_result(ngx_event_t *rev) | |
| { | |
| ssize_t n, len; | |
| ngx_str_t msg; | |
| ngx_mysql_t *m; | |
| ngx_connection_t *c; | |
| ngx_mysql_error_pkt_t *epkt; | |
| ngx_mysql_response_pkt_t *pkt; | |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql read auth"); | |
| c = rev->data; | |
| m = c->data; | |
| m->peer.log->action = "reading mysql auth result"; | |
| n = ngx_recv(m->peer.connection, m->buf->pos, /* STUB */ 1024); | |
| if (n == NGX_AGAIN) { | |
| return; | |
| } | |
| if (n < 5) { | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| pkt = (ngx_mysql_response_pkt_t *) m->buf->pos; | |
| len = ngx_m24toh(pkt->pktlen); | |
| if (len > n - 4) { | |
| ngx_log_error(NGX_LOG_ERR, rev->log, 0, | |
| "mysql server %V sent incomplete response packet", | |
| m->peer.name); | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| if (pkt->fields == 0) { | |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql auth OK"); | |
| m->state = NGX_OK; | |
| m->pktn = 0; | |
| m->handler(m); | |
| return; | |
| } | |
| epkt = (ngx_mysql_error_pkt_t *) pkt; | |
| msg.len = (u_char *) epkt + 4 + len - epkt->message; | |
| msg.data = epkt->message; | |
| ngx_log_error(NGX_LOG_ERR, rev->log, 0, | |
| "mysql server %V sent error (%ud): \"%V\"", | |
| m->peer.name, ngx_m16toh(epkt->code), &msg); | |
| ngx_mysql_close(m, NGX_ERROR); | |
| } | |
| ngx_int_t | |
| ngx_mysql_query(ngx_mysql_t *m) | |
| { | |
| ssize_t n; | |
| ngx_mysql_command_pkt_t *pkt; | |
| pkt = (ngx_mysql_command_pkt_t *) m->query.data; | |
| ngx_htom24(pkt->pktlen, m->query.len - 4); | |
| pkt->pktn = (u_char) m->pktn++; | |
| pkt->command = NGX_MYSQL_CMD_QUERY; | |
| n = ngx_send(m->peer.connection, m->query.data, m->query.len); | |
| if (n < (ssize_t) m->query.len) { | |
| ngx_log_error(NGX_LOG_ERR, m->peer.log, 0, | |
| "the incomplete packet was sent to mysql server %V", | |
| m->peer.name); | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return NGX_OK; | |
| } | |
| m->peer.connection->read->handler = ngx_mysql_read_query_result; | |
| ngx_add_timer(m->peer.connection->read, /* STUB */ 5000); | |
| /* STUB handle event */ | |
| return NGX_OK; | |
| } | |
| static void | |
| ngx_mysql_read_query_result(ngx_event_t *rev) | |
| { | |
| ssize_t n, len; | |
| ngx_str_t msg; | |
| ngx_mysql_t *m; | |
| ngx_connection_t *c; | |
| ngx_mysql_error_pkt_t *epkt; | |
| ngx_mysql_response_pkt_t *pkt; | |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql read query result"); | |
| c = rev->data; | |
| m = c->data; | |
| m->peer.log->action = "reading mysql read query result"; | |
| n = ngx_recv(m->peer.connection, m->buf->pos, /* STUB */ 1024); | |
| if (n == NGX_AGAIN) { | |
| return; | |
| } | |
| if (n < 5) { | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| pkt = (ngx_mysql_response_pkt_t *) m->buf->pos; | |
| len = ngx_m24toh(pkt->pktlen); | |
| if (len > n - 4) { | |
| ngx_log_error(NGX_LOG_ERR, rev->log, 0, | |
| "mysql server %V sent incomplete response packet", | |
| m->peer.name); | |
| ngx_mysql_close(m, NGX_ERROR); | |
| return; | |
| } | |
| if (pkt->fields != 0xff) { | |
| ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql query OK"); | |
| m->state = NGX_OK; | |
| m->pktn = pkt->pktn; | |
| m->handler(m); | |
| return; | |
| } | |
| epkt = (ngx_mysql_error_pkt_t *) pkt; | |
| msg.len = (u_char *) epkt + 4 + len - epkt->message; | |
| msg.data = epkt->message; | |
| ngx_log_error(NGX_LOG_ERR, rev->log, 0, | |
| "mysql server %V sent error (%ud): \"%V\"", | |
| m->peer.name, ngx_m16toh(epkt->code), &msg); | |
| ngx_mysql_close(m, NGX_ERROR); | |
| } | |
| static void | |
| ngx_mysql_close(ngx_mysql_t *m, ngx_int_t rc) | |
| { | |
| if (rc == NGX_ERROR) { | |
| ngx_close_connection(m->peer.connection); | |
| } | |
| m->state = rc; | |
| m->handler(m); | |
| } |