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,
|
884
|
153 "mysql server %V timed out", m->peer.name);
|
645
|
154
|
|
155 ngx_mysql_close(m, NGX_ERROR);
|
|
156 return;
|
|
157 }
|
|
158
|
|
159 if (m->buf == NULL) {
|
653
|
160 m->peer.log->action = "reading mysql server greeting";
|
645
|
161
|
653
|
162 m->buf = ngx_create_temp_buf(m->pool, /* STUB */ 1024);
|
645
|
163 if (m->buf == NULL) {
|
|
164 ngx_mysql_close(m, NGX_ERROR);
|
|
165 return;
|
|
166 }
|
|
167 }
|
|
168
|
|
169 n = ngx_recv(m->peer.connection, m->buf->pos, /* STUB */ 1024);
|
|
170
|
|
171 if (n == NGX_AGAIN) {
|
|
172 return;
|
|
173 }
|
|
174
|
|
175 if (n < 5) {
|
|
176 ngx_mysql_close(m, NGX_ERROR);
|
|
177 return;
|
|
178 }
|
|
179
|
653
|
180 gr1 = (ngx_mysql_greeting1_pkt_t *) m->buf->pos;
|
645
|
181
|
653
|
182 if (ngx_m24toh(gr1->pktlen) > n - 4) {
|
645
|
183 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
184 "mysql server %V sent incomplete greeting packet",
|
884
|
185 m->peer.name);
|
653
|
186
|
|
187 ngx_mysql_close(m, NGX_ERROR);
|
|
188 return;
|
|
189 }
|
|
190
|
|
191 if (gr1->protocol < 10) {
|
|
192 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
193 "mysql server %V sent unsupported protocol version %ud",
|
884
|
194 m->peer.name, gr1->protocol);
|
653
|
195
|
|
196 ngx_mysql_close(m, NGX_ERROR);
|
|
197 return;
|
|
198 }
|
|
199
|
|
200 gr2 = (ngx_mysql_greeting2_pkt_t *)
|
|
201 (gr1->version + ngx_strlen(gr1->version) + 1);
|
|
202
|
|
203 capacity = ngx_m16toh(gr2->capacity);
|
|
204
|
|
205 ngx_log_debug8(NGX_LOG_DEBUG_MYSQL, rev->log, 0,
|
|
206 "mysql version: %ud, \"%s\", thread: %ud, salt: \"%s\", "
|
|
207 "capacity: %Xd, charset: %ud, status: %ud, salt rest \"%s\"",
|
|
208 gr1->protocol, gr1->version, ngx_m32toh(gr2->thread),
|
|
209 gr2->salt1, capacity, gr2->charset,
|
|
210 ngx_m16toh(gr2->status), &gr2->salt2);
|
|
211
|
|
212 capacity = NGX_MYSQL_LONG_PASSWORD
|
|
213 | NGX_MYSQL_CONNECT_WITH_DB
|
|
214 | NGX_MYSQL_PROTOCOL_41
|
|
215 | NGX_MYSQL_SECURE_CONNECTION;
|
|
216
|
|
217 len = 4 + 4 + 4 + 1 + 23 + m->login->len + 1 + 1 + m->database->len + 1;
|
|
218
|
|
219 if (m->passwd->len) {
|
|
220 len += 20;
|
|
221 }
|
|
222
|
|
223 auth = ngx_palloc(m->pool, len);
|
|
224 if (auth == NULL) {
|
|
225 ngx_mysql_close(m, NGX_ERROR);
|
|
226 return;
|
|
227 }
|
|
228
|
|
229 ngx_htom24(auth->pktlen, len - 4);
|
|
230 auth->pktn = (u_char) (gr1->pktn + 1);
|
|
231
|
|
232 ngx_htom32(auth->capacity, capacity);
|
|
233 ngx_htom32(auth->max_packet, 0x01000000); /* max packet size 2^24 */
|
|
234 ngx_memzero(auth->zero, 24);
|
|
235 auth->charset = gr2->charset;
|
|
236
|
|
237 p = ngx_copy(auth->login, m->login->data, m->login->len);
|
|
238 *p++ = '\0';
|
|
239
|
|
240 if (m->passwd->len) {
|
|
241
|
|
242 *p++ = (u_char) 20;
|
|
243
|
|
244 SHA1_Init(&sha);
|
|
245 SHA1_Update(&sha, m->passwd->data, m->passwd->len);
|
|
246 SHA1_Final(hash1, &sha);
|
|
247
|
|
248 SHA1_Init(&sha);
|
|
249 SHA1_Update(&sha, hash1, 20);
|
|
250 SHA1_Final(hash2, &sha);
|
|
251
|
|
252 SHA1_Init(&sha);
|
|
253 SHA1_Update(&sha, gr2->salt1, 8);
|
|
254 SHA1_Update(&sha, gr2->salt2, 12);
|
|
255 SHA1_Update(&sha, hash2, 20);
|
|
256 SHA1_Final(hash2, &sha);
|
|
257
|
|
258 for (i = 0; i < 20; i++) {
|
|
259 *p++ = (u_char) (hash1[i] ^ hash2[i]);
|
|
260 }
|
|
261
|
|
262 } else {
|
|
263 *p++ = '\0';
|
|
264 }
|
|
265
|
|
266 p = ngx_copy(p, m->database->data, m->database->len);
|
|
267 *p = '\0';
|
|
268
|
|
269
|
|
270 n = ngx_send(m->peer.connection, (void *) auth, len);
|
|
271
|
|
272 if (n < (ssize_t) len) {
|
|
273 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
274 "the incomplete packet was sent to mysql server %V",
|
884
|
275 m->peer.name);
|
645
|
276
|
|
277 ngx_mysql_close(m, NGX_ERROR);
|
|
278 return;
|
|
279 }
|
|
280
|
653
|
281 m->peer.connection->read->handler = ngx_mysql_read_auth_result;
|
|
282
|
|
283 ngx_add_timer(m->peer.connection->read, /* STUB */ 5000);
|
|
284 }
|
|
285
|
|
286
|
|
287 static void
|
|
288 ngx_mysql_empty_handler(ngx_event_t *wev)
|
|
289 {
|
|
290 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "mysql empty handler");
|
|
291
|
|
292 return;
|
|
293 }
|
|
294
|
|
295
|
|
296 static void
|
|
297 ngx_mysql_read_auth_result(ngx_event_t *rev)
|
|
298 {
|
|
299 ssize_t n, len;
|
|
300 ngx_str_t msg;
|
|
301 ngx_mysql_t *m;
|
|
302 ngx_connection_t *c;
|
|
303 ngx_mysql_error_pkt_t *epkt;
|
|
304 ngx_mysql_response_pkt_t *pkt;
|
|
305
|
|
306 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql read auth");
|
|
307
|
|
308 c = rev->data;
|
|
309 m = c->data;
|
|
310
|
|
311 m->peer.log->action = "reading mysql auth result";
|
|
312
|
|
313 n = ngx_recv(m->peer.connection, m->buf->pos, /* STUB */ 1024);
|
|
314
|
|
315 if (n == NGX_AGAIN) {
|
|
316 return;
|
|
317 }
|
|
318
|
|
319 if (n < 5) {
|
|
320 ngx_mysql_close(m, NGX_ERROR);
|
|
321 return;
|
|
322 }
|
|
323
|
|
324 pkt = (ngx_mysql_response_pkt_t *) m->buf->pos;
|
|
325
|
|
326 len = ngx_m24toh(pkt->pktlen);
|
|
327
|
|
328 if (len > n - 4) {
|
645
|
329 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
653
|
330 "mysql server %V sent incomplete response packet",
|
884
|
331 m->peer.name);
|
645
|
332
|
|
333 ngx_mysql_close(m, NGX_ERROR);
|
|
334 return;
|
|
335 }
|
|
336
|
653
|
337 if (pkt->fields == 0) {
|
|
338 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql auth OK");
|
|
339
|
|
340 m->state = NGX_OK;
|
|
341 m->pktn = 0;
|
|
342
|
|
343 m->handler(m);
|
|
344
|
|
345 return;
|
|
346 }
|
|
347
|
|
348 epkt = (ngx_mysql_error_pkt_t *) pkt;
|
|
349
|
|
350 msg.len = (u_char *) epkt + 4 + len - epkt->message;
|
|
351 msg.data = epkt->message;
|
|
352
|
|
353 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
354 "mysql server %V sent error (%ud): \"%V\"",
|
884
|
355 m->peer.name, ngx_m16toh(epkt->code), &msg);
|
653
|
356
|
|
357 ngx_mysql_close(m, NGX_ERROR);
|
|
358 }
|
|
359
|
645
|
360
|
653
|
361 ngx_int_t
|
|
362 ngx_mysql_query(ngx_mysql_t *m)
|
|
363 {
|
|
364 ssize_t n;
|
|
365 ngx_mysql_command_pkt_t *pkt;
|
|
366
|
|
367 pkt = (ngx_mysql_command_pkt_t *) m->query.data;
|
|
368
|
|
369 ngx_htom24(pkt->pktlen, m->query.len - 4);
|
|
370 pkt->pktn = (u_char) m->pktn++;
|
|
371 pkt->command = NGX_MYSQL_CMD_QUERY;
|
|
372
|
|
373 n = ngx_send(m->peer.connection, m->query.data, m->query.len);
|
|
374
|
|
375 if (n < (ssize_t) m->query.len) {
|
|
376 ngx_log_error(NGX_LOG_ERR, m->peer.log, 0,
|
|
377 "the incomplete packet was sent to mysql server %V",
|
884
|
378 m->peer.name);
|
653
|
379
|
|
380 ngx_mysql_close(m, NGX_ERROR);
|
|
381 return NGX_OK;
|
|
382 }
|
|
383
|
|
384 m->peer.connection->read->handler = ngx_mysql_read_query_result;
|
|
385
|
|
386 ngx_add_timer(m->peer.connection->read, /* STUB */ 5000);
|
|
387
|
|
388 /* STUB handle event */
|
|
389
|
|
390 return NGX_OK;
|
|
391 }
|
|
392
|
645
|
393
|
653
|
394 static void
|
|
395 ngx_mysql_read_query_result(ngx_event_t *rev)
|
|
396 {
|
|
397 ssize_t n, len;
|
|
398 ngx_str_t msg;
|
|
399 ngx_mysql_t *m;
|
|
400 ngx_connection_t *c;
|
|
401 ngx_mysql_error_pkt_t *epkt;
|
|
402 ngx_mysql_response_pkt_t *pkt;
|
|
403
|
|
404 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql read query result");
|
|
405
|
|
406 c = rev->data;
|
|
407 m = c->data;
|
|
408
|
|
409 m->peer.log->action = "reading mysql read query result";
|
|
410
|
|
411 n = ngx_recv(m->peer.connection, m->buf->pos, /* STUB */ 1024);
|
|
412
|
|
413 if (n == NGX_AGAIN) {
|
|
414 return;
|
|
415 }
|
|
416
|
|
417 if (n < 5) {
|
|
418 ngx_mysql_close(m, NGX_ERROR);
|
|
419 return;
|
|
420 }
|
|
421
|
|
422 pkt = (ngx_mysql_response_pkt_t *) m->buf->pos;
|
645
|
423
|
653
|
424 len = ngx_m24toh(pkt->pktlen);
|
|
425
|
|
426 if (len > n - 4) {
|
|
427 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
428 "mysql server %V sent incomplete response packet",
|
884
|
429 m->peer.name);
|
653
|
430
|
|
431 ngx_mysql_close(m, NGX_ERROR);
|
|
432 return;
|
|
433 }
|
|
434
|
|
435 if (pkt->fields != 0xff) {
|
|
436 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "mysql query OK");
|
645
|
437
|
653
|
438 m->state = NGX_OK;
|
|
439 m->pktn = pkt->pktn;
|
|
440
|
|
441 m->handler(m);
|
|
442
|
|
443 return;
|
|
444 }
|
|
445
|
|
446 epkt = (ngx_mysql_error_pkt_t *) pkt;
|
|
447
|
|
448 msg.len = (u_char *) epkt + 4 + len - epkt->message;
|
|
449 msg.data = epkt->message;
|
|
450
|
|
451 ngx_log_error(NGX_LOG_ERR, rev->log, 0,
|
|
452 "mysql server %V sent error (%ud): \"%V\"",
|
884
|
453 m->peer.name, ngx_m16toh(epkt->code), &msg);
|
653
|
454
|
|
455 ngx_mysql_close(m, NGX_ERROR);
|
645
|
456 }
|
|
457
|
|
458
|
|
459 static void
|
|
460 ngx_mysql_close(ngx_mysql_t *m, ngx_int_t rc)
|
|
461 {
|
|
462 if (rc == NGX_ERROR) {
|
|
463 ngx_close_connection(m->peer.connection);
|
|
464 }
|
|
465
|
|
466 m->state = rc;
|
|
467
|
|
468 m->handler(m);
|
|
469 }
|