diff options
Diffstat (limited to 'src/mysql')
-rw-r--r-- | src/mysql/config | 13 | ||||
-rw-r--r-- | src/mysql/ngx_http_mysql_test.c | 196 | ||||
-rw-r--r-- | src/mysql/ngx_mysql.c | 386 | ||||
-rw-r--r-- | src/mysql/ngx_mysql.h | 64 |
4 files changed, 625 insertions, 34 deletions
diff --git a/src/mysql/config b/src/mysql/config new file mode 100644 index 000000000..cdec6390a --- /dev/null +++ b/src/mysql/config @@ -0,0 +1,13 @@ + +ngx_addon_name=ngx_mysql + +HTTP_MODULES="$HTTP_MODULES ngx_http_mysql_test_module" + +HTTP_INCS="$HTTP_INCS $ngx_addon_dir" +HTTP_DEPS="$HTTP_DEPS $ngx_addon_dir/ngx_mysql.h" +#CORE_LIBS="$CORE_LIBS -lmd" + +USE_SHA1=YES + +NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_mysql.c" +NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mysql_test.c" diff --git a/src/mysql/ngx_http_mysql_test.c b/src/mysql/ngx_http_mysql_test.c new file mode 100644 index 000000000..0b83f7d51 --- /dev/null +++ b/src/mysql/ngx_http_mysql_test.c @@ -0,0 +1,196 @@ + +/* + * Copyright (C) Igor Sysoev + */ + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_mysql.h> +#include <ngx_http.h> + + +typedef struct { + ngx_peers_t *peers; +} ngx_http_mysql_test_conf_t; + + +static void ngx_http_mysql_auth(ngx_mysql_t *m); +static void ngx_http_mysql_done(ngx_mysql_t *m); +static void *ngx_http_mysql_test_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_mysql_test(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static ngx_command_t ngx_http_mysql_test_commands[] = { + + { ngx_string("mysql_test"), + NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_mysql_test, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +ngx_http_module_t ngx_http_mysql_test_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_mysql_test_create_loc_conf, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_mysql_test_module = { + NGX_MODULE_V1, + &ngx_http_mysql_test_module_ctx, /* module context */ + ngx_http_mysql_test_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_str_t ngx_mysql_login = ngx_string("root"); +static ngx_str_t ngx_mysql_passwd = ngx_string("tp"); +static ngx_str_t ngx_mysql_database = ngx_string("mysql"); +static ngx_str_t ngx_mysql_command_query = + ngx_string("select * from user"); + + +static ngx_int_t +ngx_http_mysql_test_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_mysql_t *m; + ngx_http_mysql_test_conf_t *mtcf; + + mtcf = ngx_http_get_module_loc_conf(r, ngx_http_mysql_test_module); + + m = ngx_pcalloc(r->pool, sizeof(ngx_mysql_t)); + + if (m == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + m->pool = r->pool; + m->handler = ngx_http_mysql_auth; + m->data = r; + + m->login = &ngx_mysql_login; + m->passwd = &ngx_mysql_passwd; + m->database = &ngx_mysql_database; + + m->peer.log = r->connection->log; + m->peer.log_error = NGX_ERROR_ERR; + m->peer.peers = mtcf->peers; + m->peer.tries = mtcf->peers->number; + + rc = ngx_mysql_connect(m); + + if (rc == NGX_OK || rc == NGX_AGAIN) { + return NGX_DONE; + } + + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static void +ngx_http_mysql_auth(ngx_mysql_t *m) +{ + ngx_http_request_t *r; + + r = m->data; + + if (m->state != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_NO_CONTENT); + return; + } + + m->query.len = NGX_MYSQL_CMDPKT_LEN + ngx_mysql_command_query.len; + + m->query.data = ngx_palloc(r->pool, m->query.len); + if (m->query.data == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + ngx_memcpy(m->query.data + NGX_MYSQL_CMDPKT_LEN, + ngx_mysql_command_query.data, ngx_mysql_command_query.len); + + m->handler = ngx_http_mysql_done; + + ngx_mysql_query(m); +} + + +static void +ngx_http_mysql_done(ngx_mysql_t *m) +{ + ngx_http_request_t *r; + + r = m->data; + + ngx_http_finalize_request(r, NGX_HTTP_NO_CONTENT); +} + + +static void * +ngx_http_mysql_test_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_mysql_test_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_mysql_test_conf_t)); + if (conf == NULL) { + return NGX_CONF_ERROR; + } + + return conf; +} + +static char * +ngx_http_mysql_test(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_mysql_test_conf_t *mtcf = conf; + + ngx_str_t *value; + ngx_url_t u; + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_mysql_test_handler; + + value = cf->args->elts; + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = value[1]; + u.default_portn = 3306; + + if (ngx_parse_url(cf, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in upstream \"%V\"", u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + mtcf->peers = u.peers; + + return NGX_CONF_OK; +} diff --git a/src/mysql/ngx_mysql.c b/src/mysql/ngx_mysql.c index 1e008b5e4..daabcd855 100644 --- a/src/mysql/ngx_mysql.c +++ b/src/mysql/ngx_mysql.c @@ -4,13 +4,100 @@ */ +/* 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> +#if (NGX_HAVE_OPENSSL_SHA1_H) +#include <openssl/sha.h> +#else +#include <sha.h> +#endif -/* the library supports the subset of the MySQL 4.1+ protocol (version 10) */ + +#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 @@ -32,11 +119,12 @@ ngx_mysql_connect(ngx_mysql_t *m) return rc; } + m->peer.connection->data = m; + m->peer.connection->read->handler = ngx_mysql_read_server_greeting; - m->peer.connection->write->handler = ngx_mysql_emtpy_handler; + m->peer.connection->write->handler = ngx_mysql_empty_handler; ngx_add_timer(m->peer.connection->read, /* STUB */ 5000); - ngx_add_timer(m->peer.connection->write, /* STUB */ 5000); return NGX_OK; } @@ -45,10 +133,17 @@ ngx_mysql_connect(ngx_mysql_t *m) static void ngx_mysql_read_server_greeting(ngx_event_t *rev) { - size_t len; - u_char *p, *t; - ngx_mysql_t *m; - ngx_connection_t *c; + 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; + SHA_CTX sha; + u_char hash1[20], hash2[20]; c = rev->data; m = c->data; @@ -56,16 +151,16 @@ ngx_mysql_read_server_greeting(ngx_event_t *rev) if (rev->timedout) { ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT, "mysql server %V timed out", - &ctx->peer.peers->peer[0].name); + &m->peer.peers->peer[0].name); ngx_mysql_close(m, NGX_ERROR); return; } if (m->buf == NULL) { - m->peer.log->action = "reading to mysql server greeting"; + m->peer.log->action = "reading mysql server greeting"; - m->buf = ngx_create_temp(m->pool, /* STUB */ 1024); + m->buf = ngx_create_temp_buf(m->pool, /* STUB */ 1024); if (m->buf == NULL) { ngx_mysql_close(m, NGX_ERROR); return; @@ -83,43 +178,282 @@ ngx_mysql_read_server_greeting(ngx_event_t *rev) return; } - p = m->buf->pos; + gr1 = (ngx_mysql_greeting1_pkt_t *) m->buf->pos; - if (ngx_m24toh(p) > n - 4) { + if (ngx_m24toh(gr1->pktlen) > n - 4) { ngx_log_error(NGX_LOG_ERR, rev->log, 0, "mysql server %V sent incomplete greeting packet", - &ctx->peer.peers->peer[0].name); + &m->peer.peers->peer[0].name); ngx_mysql_close(m, NGX_ERROR); return; } - if (p[4]) < 10) { + if (gr1->protocol < 10) { ngx_log_error(NGX_LOG_ERR, rev->log, 0, "mysql server %V sent unsupported protocol version %ud", - &ctx->peer.peers->peer[0].name, p[4]); + &m->peer.peers->peer[0].name, gr1->protocol); ngx_mysql_close(m, NGX_ERROR); return; } - len = ngx_strlen(&p[5]); - t = p + 5 + len + 1; + gr2 = (ngx_mysql_greeting2_pkt_t *) + (gr1->version + ngx_strlen(gr1->version) + 1); - capacity = ngx_m16toh((&t[4 + 9])); + capacity = ngx_m16toh(gr2->capacity); ngx_log_debug8(NGX_LOG_DEBUG_MYSQL, rev->log, 0, - "mysql version: %ud, \"%s\", thread: %ud, salt: \"%s\", ", + "mysql version: %ud, \"%s\", thread: %ud, salt: \"%s\", " "capacity: %Xd, charset: %ud, status: %ud, salt rest \"%s\"", - p[4], &p[5], ngx_m32toh(t), &t[4], - capacity, t[4 + 9 + 2], - ngx_m16toh((&t[4 + 9 + 2 + 1])), - t[4 + 9 + 2 + 1 + 2 + 13]); + 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_palloc(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; + + SHA1_Init(&sha); + SHA1_Update(&sha, m->passwd->data, m->passwd->len); + SHA1_Final(hash1, &sha); + + SHA1_Init(&sha); + SHA1_Update(&sha, hash1, 20); + SHA1_Final(hash2, &sha); + + SHA1_Init(&sha); + SHA1_Update(&sha, gr2->salt1, 8); + SHA1_Update(&sha, gr2->salt2, 12); + SHA1_Update(&sha, hash2, 20); + 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.peers->peer[0].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.peers->peer[0].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.peers->peer[0].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.peers->peer[0].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.peers->peer[0].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; - capacity &= NGX_MYSQL_LONG_PASSWORD - | NGX_MYSQL_CONNECT_WITH_DB - | NGX_MYSQL_PROTOCOL_41; + ngx_log_error(NGX_LOG_ERR, rev->log, 0, + "mysql server %V sent error (%ud): \"%V\"", + &m->peer.peers->peer[0].name, ngx_m16toh(epkt->code), &msg); + ngx_mysql_close(m, NGX_ERROR); } diff --git a/src/mysql/ngx_mysql.h b/src/mysql/ngx_mysql.h index 99f10396b..17cb1afba 100644 --- a/src/mysql/ngx_mysql.h +++ b/src/mysql/ngx_mysql.h @@ -11,26 +11,74 @@ #include <ngx_config.h> #include <ngx_core.h> #include <ngx_event.h> +#include <ngx_event_connect.h> -typedef struct { +typedef struct ngx_mysql_s ngx_mysql_t; + +typedef void (*ngx_mysql_handler_pt)(ngx_mysql_t *m); + + +struct ngx_mysql_s { ngx_peer_connection_t peer; -} ngx_mysql_t; + + ngx_buf_t *buf; + ngx_pool_t *pool; + + ngx_str_t *login; + ngx_str_t *passwd; + ngx_str_t *database; + + ngx_str_t query; + + ngx_uint_t pktn; + + ngx_mysql_handler_pt handler; + void *data; + ngx_int_t state; + +}; + + +#define NGX_MYSQL_CMDPKT_LEN 5 #if (NGX_HAVE_LITTLE_ENDIAN && NGX_HAVE_NONALIGNED && 0) -#define ngx_m16toh(n) (*(uint32_t *) n & 0x0000ffff) -#define ngx_m24toh(n) (*(uint32_t *) n & 0x00ffffff) -#define ngx_m32toh(n) *(uint32_t *) n +#define ngx_m16toh(n) (*(uint32_t *) n & 0x0000ffff) +#define ngx_m24toh(n) (*(uint32_t *) n & 0x00ffffff) +#define ngx_m32toh(n) *(uint32_t *) n + +#define ngx_htom16(n, m) *(uint16_t *) n = (uint16_t) ((m) & 0xffff) + +#define ngx_htom24(n, m) (n)[0] = (u_char) ((m) & 0xff); \ + (n)[1] = (u_char) (((m) >> 8) & 0xff); \ + (n)[2] = (u_char) (((m) >> 16) & 0xff) + +#define ngx_htom32(n, m) *(uint32_t *) (n) = (m) #else -#define ngx_m16toh(n) (n[0] | n[1] << 8) -#define ngx_m24toh(n) (n[0] | n[1] << 8 | n[2] << 16) -#define ngx_m32toh(n) (n[0] | n[1] << 8 | n[2] << 16 | n[3] << 24) +#define ngx_m16toh(n) (n[0] | n[1] << 8) +#define ngx_m24toh(n) (n[0] | n[1] << 8 | n[2] << 16) +#define ngx_m32toh(n) (n[0] | n[1] << 8 | n[2] << 16 | n[3] << 24) + +#define ngx_htom16(n, m) (n)[0] = (u_char) (m); (n)[1] = (u_char) ((m) >> 8) + +#define ngx_htom24(n, m) (n)[0] = (u_char) ((m) & 0xff); \ + (n)[1] = (u_char) (((m) >> 8) & 0xff); \ + (n)[2] = (u_char) (((m) >> 16) & 0xff) + +#define ngx_htom32(n, m) (n)[0] = (u_char) ((m) & 0xff); \ + (n)[1] = (u_char) (((m) >> 8) & 0xff); \ + (n)[2] = (u_char) (((m) >> 16) & 0xff); \ + (n)[3] = (u_char) (((m) >> 24) & 0xff) #endif +ngx_int_t ngx_mysql_connect(ngx_mysql_t *m); +ngx_int_t ngx_mysql_query(ngx_mysql_t *m); + + #endif /* _NGX_MYSQL_H_INCLUDED_ */ |