view src/mysql/ngx_mysql.c @ 6133:af7eba90645d

Win32: shared memory base addresses and remapping. Two mechanisms are implemented to make it possible to store pointers in shared memory on Windows, in particular on Windows Vista and later versions with ASLR: - The ngx_shm_remap() function added to allow remapping of a shared memory zone to the address originally used for it in the master process. While important, it doesn't solve the problem by itself as in many cases it's not possible to use the address because of conflicts with other allocations. - We now create mappings at the same address in all processes by starting mappings at predefined addresses normally unused by newborn processes. These two mechanisms combined allow to use shared memory on Windows almost without problems, including reloads. Based on the patch by Sergey Brester: http://mailman.nginx.org/pipermail/nginx-devel/2015-April/006836.html
author Maxim Dounin <mdounin@mdounin.ru>
date Mon, 27 Apr 2015 18:25:42 +0300
parents d620f497c50f
children
line wrap: on
line source


/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


/* 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>
#include <ngx_sha1.h>


#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
ngx_mysql_connect(ngx_mysql_t *m)
{
    ngx_int_t  rc;

#if 0
    if (cached) {
        return NGX_OK;
    }
#endif

    m->peer.log->action = "connecting to mysql server";

    rc = ngx_event_connect_peer(&m->peer);

    if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
        return rc;
    }

    m->peer.connection->data = m;

    m->peer.connection->read->handler = ngx_mysql_read_server_greeting;
    m->peer.connection->write->handler = ngx_mysql_empty_handler;

    ngx_add_timer(m->peer.connection->read, /* STUB */ 5000);

    return NGX_OK;
}


static void
ngx_mysql_read_server_greeting(ngx_event_t *rev)
{
    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;
    ngx_sha1_t                  sha;
    u_char                      hash1[20], hash2[20];

    c = rev->data;
    m = c->data;

    if (rev->timedout) {
        ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT,
                      "mysql server %V timed out", m->peer.name);

        ngx_mysql_close(m, NGX_ERROR);
        return;
    }

    if (m->buf == NULL) {
        m->peer.log->action = "reading mysql server greeting";

        m->buf = ngx_create_temp_buf(m->pool, /* STUB */ 1024);
        if (m->buf == NULL) {
            ngx_mysql_close(m, NGX_ERROR);
            return;
        }
    }

    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;
    }

    gr1 = (ngx_mysql_greeting1_pkt_t *) m->buf->pos;

    if (ngx_m24toh(gr1->pktlen) > n - 4) {
        ngx_log_error(NGX_LOG_ERR, rev->log, 0,
                      "mysql server %V sent incomplete greeting packet",
                      m->peer.name);

        ngx_mysql_close(m, NGX_ERROR);
        return;
    }

    if (gr1->protocol < 10) {
        ngx_log_error(NGX_LOG_ERR, rev->log, 0,
                      "mysql server %V sent unsupported protocol version %ud",
                      m->peer.name, gr1->protocol);

        ngx_mysql_close(m, NGX_ERROR);
        return;
    }

    gr2 = (ngx_mysql_greeting2_pkt_t *)
                                 (gr1->version + ngx_strlen(gr1->version) + 1);

    capacity = ngx_m16toh(gr2->capacity);

    ngx_log_debug8(NGX_LOG_DEBUG_MYSQL, rev->log, 0,
                   "mysql version: %ud, \"%s\", thread: %ud, salt: \"%s\", "
                   "capacity: %Xd, charset: %ud, status: %ud, salt rest \"%s\"",
                   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_pnalloc(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;

        ngx_sha1_init(&sha);
        ngx_sha1_update(&sha, m->passwd->data, m->passwd->len);
        ngx_sha1_final(hash1, &sha);

        ngx_sha1_init(&sha);
        ngx_sha1_update(&sha, hash1, 20);
        ngx_sha1_final(hash2, &sha);

        ngx_sha1_init(&sha);
        ngx_sha1_update(&sha, gr2->salt1, 8);
        ngx_sha1_update(&sha, gr2->salt2, 12);
        ngx_sha1_update(&sha, hash2, 20);
        ngx_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.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.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.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.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.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;

    ngx_log_error(NGX_LOG_ERR, rev->log, 0,
                  "mysql server %V sent error (%ud): \"%V\"",
                  m->peer.name, ngx_m16toh(epkt->code), &msg);

    ngx_mysql_close(m, NGX_ERROR);
}


static void
ngx_mysql_close(ngx_mysql_t *m, ngx_int_t rc)
{
    if (rc == NGX_ERROR) {
        ngx_close_connection(m->peer.connection);
    }

    m->state = rc;

    m->handler(m);
}