view src/mysql/ngx_mysql.c @ 4147:7f64de1cc2c0

Fix for double content when return is used in error_page handler. Test case: location / { error_page 405 /nope; return 405; } location /nope { return 200; } This is expected to return 405 with empty body, but in 0.8.42+ will return builtin 405 error page as well (though not counted in Content-Length, thus breaking protocol). Fix is to use status provided by rewrite script execution in case it's less than NGX_HTTP_BAD_REQUEST even if r->error_status set. This check is in line with one in ngx_http_script_return_code(). Note that this patch also changes behaviour for "return 302 ..." and "rewrite ... redirect" used as error handler. E.g. location / { error_page 405 /redirect; return 405; } location /redirect { rewrite ^ http://example.com/; } will actually return redirect to "http://example.com/" instead of builtin 405 error page with meaningless Location header. This looks like correct change and it's in line with what happens on e.g. directory redirects in error handlers.
author Maxim Dounin <mdounin@mdounin.ru>
date Tue, 27 Sep 2011 11:11:30 +0000
parents 2a92804f4109
children d620f497c50f
line wrap: on
line source


/*
 * Copyright (C) Igor Sysoev
 */


/* 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);
}