645
|
1
|
|
2 /*
|
|
3 * Copyright (C) Igor Sysoev
|
4412
|
4 * Copyright (C) Nginx, Inc.
|
645
|
5 */
|
|
6
|
|
7
|
653
|
8 /* the library supports the subset of the MySQL 4.1+ protocol (version 10) */
|
|
9
|
|
10
|
645
|
11 #include <ngx_config.h>
|
|
12 #include <ngx_core.h>
|
|
13 #include <ngx_event.h>
|
653
|
14 #include <ngx_event_connect.h>
|
645
|
15 #include <ngx_mysql.h>
|
1574
|
16 #include <ngx_sha1.h>
|
645
|
17
|
653
|
18
|
|
19 #define NGX_MYSQL_LONG_PASSWORD 0x0001
|
|
20 #define NGX_MYSQL_CONNECT_WITH_DB 0x0008
|
|
21 #define NGX_MYSQL_PROTOCOL_41 0x0200
|
|
22 #define NGX_MYSQL_SECURE_CONNECTION 0x8000
|
|
23
|
|
24
|
|
25 #define NGX_MYSQL_CMD_QUERY 3
|
|
26
|
|
27
|
|
28 typedef struct {
|
|
29 u_char pktlen[3];
|
|
30 u_char pktn;
|
|
31
|
|
32 u_char protocol;
|
|
33 u_char version[1]; /* NULL-terminated string */
|
|
34 } ngx_mysql_greeting1_pkt_t;
|
|
35
|
|
36
|
|
37 typedef struct {
|
|
38 u_char thread[4];
|
|
39 u_char salt1[9];
|
|
40 u_char capacity[2];
|
|
41 u_char charset;
|
|
42 u_char status[2];
|
|
43 u_char zero[13];
|
|
44 u_char salt2[13];
|
|
45 } ngx_mysql_greeting2_pkt_t;
|
|
46
|
|
47
|
|
48 typedef struct {
|
|
49 u_char pktlen[3];
|
|
50 u_char pktn;
|
|
51
|
|
52 u_char capacity[4];
|
|
53 u_char max_packet[4];
|
|
54 u_char charset;
|
|
55 u_char zero[23];
|
|
56 u_char login[1]; /* NULL-terminated string */
|
|
57
|
|
58 /*
|
|
59 * u_char passwd_len; 0 if no password
|
|
60 * u_char passwd[20];
|
|
61 *
|
|
62 * u_char database[1]; NULL-terminated string
|
|
63 */
|
|
64
|
|
65 } ngx_mysql_auth_pkt_t;
|
|
66
|
|
67
|
|
68 typedef struct {
|
|
69 u_char pktlen[3];
|
|
70 u_char pktn;
|
|
71 u_char fields;
|
|
72 } ngx_mysql_response_pkt_t;
|
|
73
|
|
74
|
|
75 typedef struct {
|
|
76 u_char pktlen[3];
|
|
77 u_char pktn;
|
|
78 u_char err;
|
|
79 u_char code[2];
|
|
80 u_char message[1]; /* string */
|
|
81 } ngx_mysql_error_pkt_t;
|
|
82
|
|
83
|
|
84 typedef struct {
|
|
85 u_char pktlen[3];
|
|
86 u_char pktn;
|
|
87 u_char command;
|
|
88 u_char arg[1]; /* string */
|
|
89 } ngx_mysql_command_pkt_t;
|
|
90
|
|
91
|
|
92 static void ngx_mysql_read_server_greeting(ngx_event_t *rev);
|
|
93 static void ngx_mysql_empty_handler(ngx_event_t *wev);
|
|
94 static void ngx_mysql_read_auth_result(ngx_event_t *rev);
|
|
95 static void ngx_mysql_read_query_result(ngx_event_t *rev);
|
|
96 static void ngx_mysql_close(ngx_mysql_t *m, ngx_int_t rc);
|
645
|
97
|
|
98
|
|
99 ngx_int_t
|
|
100 ngx_mysql_connect(ngx_mysql_t *m)
|
|
101 {
|
|
102 ngx_int_t rc;
|
|
103
|
|
104 #if 0
|
|
105 if (cached) {
|
|
106 return NGX_OK;
|
|
107 }
|
|
108 #endif
|
|
109
|
|
110 m->peer.log->action = "connecting to mysql server";
|
|
111
|
|
112 rc = ngx_event_connect_peer(&m->peer);
|
|
113
|
|
114 if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
|
|
115 return rc;
|
|
116 }
|
|
117
|
653
|
118 m->peer.connection->data = m;
|
|
119
|
645
|
120 m->peer.connection->read->handler = ngx_mysql_read_server_greeting;
|
653
|
121 m->peer.connection->write->handler = ngx_mysql_empty_handler;
|
645
|
122
|
|
123 ngx_add_timer(m->peer.connection->read, /* STUB */ 5000);
|
|
124
|
|
125 return NGX_OK;
|
|
126 }
|
|
127
|
|
128
|
|
129 static void
|
|
130 ngx_mysql_read_server_greeting(ngx_event_t *rev)
|
|
131 {
|
653
|
132 size_t len;
|
|
133 u_char *p;
|
|
134 ssize_t n;
|
|
135 ngx_uint_t i, capacity;
|
|
136 ngx_mysql_t *m;
|
|
137 ngx_connection_t *c;
|
|
138 ngx_mysql_greeting1_pkt_t *gr1;
|
|
139 ngx_mysql_greeting2_pkt_t *gr2;
|
|
140 ngx_mysql_auth_pkt_t *auth;
|
1574
|
141 ngx_sha1_t sha;
|
653
|
142 u_char hash1[20], hash2[20];
|
645
|
143
|
|
144 c = rev->data;
|
|
145 m = c->data;
|
|
146
|
|
147 if (rev->timedout) {
|
|
148 ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT,
|
884
|
149 "mysql server %V timed out", m->peer.name);
|
645
|
150
|
|
151 ngx_mysql_close(m, NGX_ERROR);
|
|
152 return;
|
|
153 }
|
|
154
|
|
155 if (m->buf == NULL) {
|
653
|
156 m->peer.log->action = "reading mysql server greeting";
|
645
|
157
|
653
|
158 m->buf = ngx_create_temp_buf(m->pool, /* STUB */ 1024);
|
645
|
159 if (m->buf == NULL) {
|
|
160 ngx_mysql_close(m, NGX_ERROR);
|
|
161 return;
|
|
162 }
|
|
163 }
|
|
164
|
|
165 n = ngx_recv(m->peer.connection, m->buf->pos, /* STUB */ 1024);
|
|
166
|
|
167 if (n == NGX_AGAIN) {
|
|
168 return;
|
|
169 }
|
|
170
|
|
171 if (n < 5) {
|
|
172 ngx_mysql_close(m, NGX_ERROR);
|
|
173 return;
|
|
174 }
|
|
175
|
653
|
176 gr1 = (ngx_mysql_greeting1_pkt_t *) m->buf->pos;
|
645
|
177
|
653
|
178 if (ngx_m24toh(gr1->pktlen) > n - 4) {
|
645
|
179 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
180 "mysql server %V sent incomplete greeting packet",
|
884
|
181 m->peer.name);
|
653
|
182
|
|
183 ngx_mysql_close(m, NGX_ERROR);
|
|
184 return;
|
|
185 }
|
|
186
|
|
187 if (gr1->protocol < 10) {
|
|
188 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
189 "mysql server %V sent unsupported protocol version %ud",
|
884
|
190 m->peer.name, gr1->protocol);
|
653
|
191
|
|
192 ngx_mysql_close(m, NGX_ERROR);
|
|
193 return;
|
|
194 }
|
|
195
|
|
196 gr2 = (ngx_mysql_greeting2_pkt_t *)
|
|
197 (gr1->version + ngx_strlen(gr1->version) + 1);
|
|
198
|
|
199 capacity = ngx_m16toh(gr2->capacity);
|
|
200
|
|
201 ngx_log_debug8(NGX_LOG_DEBUG_MYSQL, rev->log, 0,
|
|
202 "mysql version: %ud, \"%s\", thread: %ud, salt: \"%s\", "
|
|
203 "capacity: %Xd, charset: %ud, status: %ud, salt rest \"%s\"",
|
|
204 gr1->protocol, gr1->version, ngx_m32toh(gr2->thread),
|
|
205 gr2->salt1, capacity, gr2->charset,
|
|
206 ngx_m16toh(gr2->status), &gr2->salt2);
|
|
207
|
|
208 capacity = NGX_MYSQL_LONG_PASSWORD
|
|
209 | NGX_MYSQL_CONNECT_WITH_DB
|
|
210 | NGX_MYSQL_PROTOCOL_41
|
|
211 | NGX_MYSQL_SECURE_CONNECTION;
|
|
212
|
|
213 len = 4 + 4 + 4 + 1 + 23 + m->login->len + 1 + 1 + m->database->len + 1;
|
|
214
|
|
215 if (m->passwd->len) {
|
|
216 len += 20;
|
|
217 }
|
|
218
|
2049
|
219 auth = ngx_pnalloc(m->pool, len);
|
653
|
220 if (auth == NULL) {
|
|
221 ngx_mysql_close(m, NGX_ERROR);
|
|
222 return;
|
|
223 }
|
|
224
|
|
225 ngx_htom24(auth->pktlen, len - 4);
|
|
226 auth->pktn = (u_char) (gr1->pktn + 1);
|
|
227
|
|
228 ngx_htom32(auth->capacity, capacity);
|
|
229 ngx_htom32(auth->max_packet, 0x01000000); /* max packet size 2^24 */
|
|
230 ngx_memzero(auth->zero, 24);
|
|
231 auth->charset = gr2->charset;
|
|
232
|
|
233 p = ngx_copy(auth->login, m->login->data, m->login->len);
|
|
234 *p++ = '\0';
|
|
235
|
|
236 if (m->passwd->len) {
|
|
237
|
|
238 *p++ = (u_char) 20;
|
|
239
|
1574
|
240 ngx_sha1_init(&sha);
|
|
241 ngx_sha1_update(&sha, m->passwd->data, m->passwd->len);
|
|
242 ngx_sha1_final(hash1, &sha);
|
653
|
243
|
1574
|
244 ngx_sha1_init(&sha);
|
|
245 ngx_sha1_update(&sha, hash1, 20);
|
|
246 ngx_sha1_final(hash2, &sha);
|
653
|
247
|
1574
|
248 ngx_sha1_init(&sha);
|
|
249 ngx_sha1_update(&sha, gr2->salt1, 8);
|
|
250 ngx_sha1_update(&sha, gr2->salt2, 12);
|
|
251 ngx_sha1_update(&sha, hash2, 20);
|
|
252 ngx_sha1_final(hash2, &sha);
|
653
|
253
|
|
254 for (i = 0; i < 20; i++) {
|
|
255 *p++ = (u_char) (hash1[i] ^ hash2[i]);
|
|
256 }
|
|
257
|
|
258 } else {
|
|
259 *p++ = '\0';
|
|
260 }
|
|
261
|
|
262 p = ngx_copy(p, m->database->data, m->database->len);
|
|
263 *p = '\0';
|
|
264
|
|
265
|
|
266 n = ngx_send(m->peer.connection, (void *) auth, len);
|
|
267
|
|
268 if (n < (ssize_t) len) {
|
|
269 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
270 "the incomplete packet was sent to mysql server %V",
|
884
|
271 m->peer.name);
|
645
|
272
|
|
273 ngx_mysql_close(m, NGX_ERROR);
|
|
274 return;
|
|
275 }
|
|
276
|
653
|
277 m->peer.connection->read->handler = ngx_mysql_read_auth_result;
|
|
278
|
|
279 ngx_add_timer(m->peer.connection->read, /* STUB */ 5000);
|
|
280 }
|
|
281
|
|
282
|
|
283 static void
|
|
284 ngx_mysql_empty_handler(ngx_event_t *wev)
|
|
285 {
|
|
286 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "mysql empty handler");
|
|
287
|
|
288 return;
|
|
289 }
|
|
290
|
|
291
|
|
292 static void
|
|
293 ngx_mysql_read_auth_result(ngx_event_t *rev)
|
|
294 {
|
|
295 ssize_t n, len;
|
|
296 ngx_str_t msg;
|
|
297 ngx_mysql_t *m;
|
|
298 ngx_connection_t *c;
|
|
299 ngx_mysql_error_pkt_t *epkt;
|
|
300 ngx_mysql_response_pkt_t *pkt;
|
|
301
|
|
302 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql read auth");
|
|
303
|
|
304 c = rev->data;
|
|
305 m = c->data;
|
|
306
|
|
307 m->peer.log->action = "reading mysql auth result";
|
|
308
|
|
309 n = ngx_recv(m->peer.connection, m->buf->pos, /* STUB */ 1024);
|
|
310
|
|
311 if (n == NGX_AGAIN) {
|
|
312 return;
|
|
313 }
|
|
314
|
|
315 if (n < 5) {
|
|
316 ngx_mysql_close(m, NGX_ERROR);
|
|
317 return;
|
|
318 }
|
|
319
|
|
320 pkt = (ngx_mysql_response_pkt_t *) m->buf->pos;
|
|
321
|
|
322 len = ngx_m24toh(pkt->pktlen);
|
|
323
|
|
324 if (len > n - 4) {
|
645
|
325 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
653
|
326 "mysql server %V sent incomplete response packet",
|
884
|
327 m->peer.name);
|
645
|
328
|
|
329 ngx_mysql_close(m, NGX_ERROR);
|
|
330 return;
|
|
331 }
|
|
332
|
653
|
333 if (pkt->fields == 0) {
|
|
334 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql auth OK");
|
|
335
|
|
336 m->state = NGX_OK;
|
|
337 m->pktn = 0;
|
|
338
|
|
339 m->handler(m);
|
|
340
|
|
341 return;
|
|
342 }
|
|
343
|
|
344 epkt = (ngx_mysql_error_pkt_t *) pkt;
|
|
345
|
|
346 msg.len = (u_char *) epkt + 4 + len - epkt->message;
|
|
347 msg.data = epkt->message;
|
|
348
|
|
349 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
350 "mysql server %V sent error (%ud): \"%V\"",
|
884
|
351 m->peer.name, ngx_m16toh(epkt->code), &msg);
|
653
|
352
|
|
353 ngx_mysql_close(m, NGX_ERROR);
|
|
354 }
|
|
355
|
645
|
356
|
653
|
357 ngx_int_t
|
|
358 ngx_mysql_query(ngx_mysql_t *m)
|
|
359 {
|
|
360 ssize_t n;
|
|
361 ngx_mysql_command_pkt_t *pkt;
|
|
362
|
|
363 pkt = (ngx_mysql_command_pkt_t *) m->query.data;
|
|
364
|
|
365 ngx_htom24(pkt->pktlen, m->query.len - 4);
|
|
366 pkt->pktn = (u_char) m->pktn++;
|
|
367 pkt->command = NGX_MYSQL_CMD_QUERY;
|
|
368
|
|
369 n = ngx_send(m->peer.connection, m->query.data, m->query.len);
|
|
370
|
|
371 if (n < (ssize_t) m->query.len) {
|
|
372 ngx_log_error(NGX_LOG_ERR, m->peer.log, 0,
|
|
373 "the incomplete packet was sent to mysql server %V",
|
884
|
374 m->peer.name);
|
653
|
375
|
|
376 ngx_mysql_close(m, NGX_ERROR);
|
|
377 return NGX_OK;
|
|
378 }
|
|
379
|
|
380 m->peer.connection->read->handler = ngx_mysql_read_query_result;
|
|
381
|
|
382 ngx_add_timer(m->peer.connection->read, /* STUB */ 5000);
|
|
383
|
|
384 /* STUB handle event */
|
|
385
|
|
386 return NGX_OK;
|
|
387 }
|
|
388
|
645
|
389
|
653
|
390 static void
|
|
391 ngx_mysql_read_query_result(ngx_event_t *rev)
|
|
392 {
|
|
393 ssize_t n, len;
|
|
394 ngx_str_t msg;
|
|
395 ngx_mysql_t *m;
|
|
396 ngx_connection_t *c;
|
|
397 ngx_mysql_error_pkt_t *epkt;
|
|
398 ngx_mysql_response_pkt_t *pkt;
|
|
399
|
|
400 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql read query result");
|
|
401
|
|
402 c = rev->data;
|
|
403 m = c->data;
|
|
404
|
|
405 m->peer.log->action = "reading mysql read query result";
|
|
406
|
|
407 n = ngx_recv(m->peer.connection, m->buf->pos, /* STUB */ 1024);
|
|
408
|
|
409 if (n == NGX_AGAIN) {
|
|
410 return;
|
|
411 }
|
|
412
|
|
413 if (n < 5) {
|
|
414 ngx_mysql_close(m, NGX_ERROR);
|
|
415 return;
|
|
416 }
|
|
417
|
|
418 pkt = (ngx_mysql_response_pkt_t *) m->buf->pos;
|
645
|
419
|
653
|
420 len = ngx_m24toh(pkt->pktlen);
|
|
421
|
|
422 if (len > n - 4) {
|
|
423 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
424 "mysql server %V sent incomplete response packet",
|
884
|
425 m->peer.name);
|
653
|
426
|
|
427 ngx_mysql_close(m, NGX_ERROR);
|
|
428 return;
|
|
429 }
|
|
430
|
|
431 if (pkt->fields != 0xff) {
|
|
432 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql query OK");
|
645
|
433
|
653
|
434 m->state = NGX_OK;
|
|
435 m->pktn = pkt->pktn;
|
|
436
|
|
437 m->handler(m);
|
|
438
|
|
439 return;
|
|
440 }
|
|
441
|
|
442 epkt = (ngx_mysql_error_pkt_t *) pkt;
|
|
443
|
|
444 msg.len = (u_char *) epkt + 4 + len - epkt->message;
|
|
445 msg.data = epkt->message;
|
|
446
|
|
447 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
448 "mysql server %V sent error (%ud): \"%V\"",
|
884
|
449 m->peer.name, ngx_m16toh(epkt->code), &msg);
|
653
|
450
|
|
451 ngx_mysql_close(m, NGX_ERROR);
|
645
|
452 }
|
|
453
|
|
454
|
|
455 static void
|
|
456 ngx_mysql_close(ngx_mysql_t *m, ngx_int_t rc)
|
|
457 {
|
|
458 if (rc == NGX_ERROR) {
|
|
459 ngx_close_connection(m->peer.connection);
|
|
460 }
|
|
461
|
|
462 m->state = rc;
|
|
463
|
|
464 m->handler(m);
|
|
465 }
|