Mercurial > hg > nginx-quic
view src/mysql/ngx_mysql.c @ 5396:42f874c0b970
Mail: added session close on smtp_greeting_delay violation.
A server MUST send greeting before other replies, while before this
change in case of smtp_greeting_delay violation the 220 greeting was
sent after several 503 replies to commands received before greeting,
resulting in protocol synchronization loss. Moreover, further commands
were accepted after the greeting.
While closing a connection isn't strictly RFC compliant (RFC 5321
requires servers to wait for a QUIT before closing a connection), it's
probably good enough for practial uses.
author | Maxim Dounin <mdounin@mdounin.ru> |
---|---|
date | Mon, 30 Sep 2013 22:09:50 +0400 |
parents | d620f497c50f |
children |
line wrap: on
line source
/* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ /* 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); }