| |
| /* |
| * 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); |
| } |