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