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