Merge
This commit is contained in:
429
modules/protocol/httpd.c
Normal file
429
modules/protocol/httpd.c
Normal file
@ -0,0 +1,429 @@
|
||||
/*
|
||||
* This file is distributed as part of the SkySQL Gateway. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright SkySQL Ab 2013
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dcb.h>
|
||||
#include <buffer.h>
|
||||
#include <service.h>
|
||||
#include <session.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <router.h>
|
||||
#include <poll.h>
|
||||
#include <atomic.h>
|
||||
#include <gw.h>
|
||||
|
||||
/**
|
||||
* @file httpd.c - HTTP daemon protocol module
|
||||
*
|
||||
* The httpd protocol module is intended as a mechanism to allow connections
|
||||
* into the gateway for the purpose of accessing information within
|
||||
* the gateway with a REST interface
|
||||
* databases.
|
||||
*
|
||||
* In the first instance it is intended to allow a debug connection to access
|
||||
* internal data structures, however it may also be used to manage the
|
||||
* configuration of the gateway via REST interface.
|
||||
*
|
||||
* @verbatim
|
||||
* Revision History
|
||||
* Date Who Description
|
||||
* 08/07/2013 Massimiliano Pinto Initial version
|
||||
*
|
||||
* @endverbatim
|
||||
*/
|
||||
|
||||
#define ISspace(x) isspace((int)(x))
|
||||
#define HTTP_SERVER_STRING "Gateway(c) v.1.0.0\r\n"
|
||||
static char *version_str = "V1.0.0";
|
||||
|
||||
static int httpd_read_event(DCB* dcb);
|
||||
static int httpd_write_event(DCB *dcb);
|
||||
static int httpd_write(DCB *dcb, GWBUF *queue);
|
||||
static int httpd_error(DCB *dcb);
|
||||
static int httpd_hangup(DCB *dcb);
|
||||
static int httpd_accept(DCB *dcb);
|
||||
static int httpd_close(DCB *dcb);
|
||||
static int httpd_listen(DCB *dcb, char *config);
|
||||
static int httpd_get_line(int sock, char *buf, int size);
|
||||
static void httpd_send_headers(int client, const char *filename);
|
||||
|
||||
/**
|
||||
* The "module object" for the httpd protocol module.
|
||||
*/
|
||||
static GWPROTOCOL MyObject = {
|
||||
httpd_read_event, /**< Read - EPOLLIN handler */
|
||||
httpd_write, /**< Write - data from gateway */
|
||||
httpd_write_event, /**< WriteReady - EPOLLOUT handler */
|
||||
httpd_error, /**< Error - EPOLLERR handler */
|
||||
httpd_hangup, /**< HangUp - EPOLLHUP handler */
|
||||
httpd_accept, /**< Accept */
|
||||
NULL, /**< Connect */
|
||||
httpd_close, /**< Close */
|
||||
httpd_listen /**< Create a listener */
|
||||
};
|
||||
|
||||
static void
|
||||
httpd_command(DCB *, char *cmd);
|
||||
|
||||
/**
|
||||
* Implementation of the mandatory version entry point
|
||||
*
|
||||
* @return version string of the module
|
||||
*/
|
||||
char *
|
||||
version()
|
||||
{
|
||||
return version_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* The module initialisation routine, called when the module
|
||||
* is first loaded.
|
||||
*/
|
||||
void
|
||||
ModuleInit()
|
||||
{
|
||||
fprintf(stderr, "Initialise HTTPD Protocol module.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* The module entry point routine. It is this routine that
|
||||
* must populate the structure that is referred to as the
|
||||
* "module object", this is a structure with the set of
|
||||
* external entry points for this module.
|
||||
*
|
||||
* @return The module object
|
||||
*/
|
||||
GWPROTOCOL *
|
||||
GetModuleObject()
|
||||
{
|
||||
return &MyObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read event for EPOLLIN on the httpd protocol module.
|
||||
*
|
||||
* @param dcb The descriptor control block
|
||||
* @return
|
||||
*/
|
||||
static int
|
||||
httpd_read_event(DCB* dcb)
|
||||
{
|
||||
int n = -1;
|
||||
GWBUF *head = NULL;
|
||||
SESSION *session = dcb->session;
|
||||
ROUTER_OBJECT *router = session->service->router;
|
||||
ROUTER *router_instance = session->service->router_instance;
|
||||
void *rsession = session->router_session;
|
||||
|
||||
int numchars = 1;
|
||||
char buf[1024];
|
||||
char *query_string = NULL;
|
||||
char *path_info = NULL;
|
||||
char method[255];
|
||||
char url[255];
|
||||
char path[512];
|
||||
int cgi = 0;
|
||||
size_t i, j;
|
||||
GWBUF *buffer=NULL;
|
||||
|
||||
dcb->state = DCB_STATE_PROCESSING;
|
||||
|
||||
numchars = httpd_get_line(dcb->fd, buf, sizeof(buf));
|
||||
i = 0; j = 0;
|
||||
while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) {
|
||||
method[i] = buf[j];
|
||||
i++; j++;
|
||||
}
|
||||
method[i] = '\0';
|
||||
|
||||
if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {
|
||||
//httpd_unimplemented(dcb->fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((buffer = gwbuf_alloc(1024)) == NULL) {
|
||||
//httpd_error(dcb->fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcasecmp(method, "POST") == 0)
|
||||
cgi = 1;
|
||||
|
||||
i = 0;
|
||||
while (ISspace(buf[j]) && (j < sizeof(buf)))
|
||||
j++;
|
||||
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) {
|
||||
url[i] = buf[j];
|
||||
i++; j++;
|
||||
}
|
||||
url[i] = '\0';
|
||||
|
||||
if (strcasecmp(method, "GET") == 0) {
|
||||
query_string = url;
|
||||
while ((*query_string != '?') && (*query_string != '\0'))
|
||||
query_string++;
|
||||
if (*query_string == '?') {
|
||||
cgi = 1;
|
||||
*query_string = '\0';
|
||||
query_string++;
|
||||
}
|
||||
}
|
||||
|
||||
while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
|
||||
numchars = httpd_get_line(dcb->fd, buf, sizeof(buf));
|
||||
|
||||
httpd_send_headers(dcb->fd, NULL);
|
||||
|
||||
strcpy(GWBUF_DATA(buffer), "Welcome to HTTPD Gateway (c)\n");
|
||||
|
||||
dcb->func.write(dcb, buffer);
|
||||
|
||||
dcb_close(dcb);
|
||||
|
||||
dcb->state = DCB_STATE_POLLING;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* EPOLLOUT handler for the HTTPD protocol module.
|
||||
*
|
||||
* @param dcb The descriptor control block
|
||||
* @return
|
||||
*/
|
||||
static int
|
||||
httpd_write_event(DCB *dcb)
|
||||
{
|
||||
return dcb_drain_writeq(dcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write routine for the HTTPD protocol module.
|
||||
*
|
||||
* Writes the content of the buffer queue to the socket
|
||||
* observing the non-blocking principles of the gateway.
|
||||
*
|
||||
* @param dcb Descriptor Control Block for the socket
|
||||
* @param queue Linked list of buffes to write
|
||||
*/
|
||||
static int
|
||||
httpd_write(DCB *dcb, GWBUF *queue)
|
||||
{
|
||||
return dcb_write(dcb, queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the EPOLLERR event.
|
||||
*
|
||||
* @param dcb The descriptor control block
|
||||
*/
|
||||
static int
|
||||
httpd_error(DCB *dcb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the EPOLLHUP event.
|
||||
*
|
||||
* @param dcb The descriptor control block
|
||||
*/
|
||||
static int
|
||||
httpd_hangup(DCB *dcb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the EPOLLIN event when the DCB refers to the listening
|
||||
* socket for the protocol.
|
||||
*
|
||||
* @param dcb The descriptor control block
|
||||
*/
|
||||
static int
|
||||
httpd_accept(DCB *dcb)
|
||||
{
|
||||
int n_connect = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
int so;
|
||||
struct sockaddr_in addr;
|
||||
socklen_t addrlen;
|
||||
DCB *client;
|
||||
|
||||
if ((so = accept(dcb->fd, (struct sockaddr *)&addr, &addrlen)) == -1)
|
||||
return n_connect;
|
||||
else
|
||||
{
|
||||
atomic_add(&dcb->stats.n_accepts, 1);
|
||||
client = dcb_alloc();
|
||||
client->fd = so;
|
||||
client->remote = strdup(inet_ntoa(addr.sin_addr));
|
||||
memcpy(&client->func, &MyObject, sizeof(GWPROTOCOL));
|
||||
client->session = session_alloc(dcb->session->service, client);
|
||||
|
||||
client->state = DCB_STATE_IDLE;
|
||||
|
||||
if (poll_add_dcb(client) == -1)
|
||||
{
|
||||
return n_connect;
|
||||
}
|
||||
n_connect++;
|
||||
|
||||
client->state = DCB_STATE_POLLING;
|
||||
}
|
||||
}
|
||||
return n_connect;
|
||||
}
|
||||
|
||||
/**
|
||||
* The close handler for the descriptor. Called by the gateway to
|
||||
* explicitly close a connection.
|
||||
*
|
||||
* @param dcb The descriptor control block
|
||||
*/
|
||||
|
||||
static int
|
||||
httpd_close(DCB *dcb)
|
||||
{
|
||||
dcb_close(dcb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTTP daemon listener entry point
|
||||
*
|
||||
* @param listener The Listener DCB
|
||||
* @param config Configuration (ip:port)
|
||||
*/
|
||||
static int
|
||||
httpd_listen(DCB *listener, char *config)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
char *port;
|
||||
int one = 1;
|
||||
short pnum;
|
||||
|
||||
memcpy(&listener->func, &MyObject, sizeof(GWPROTOCOL));
|
||||
|
||||
port = strrchr(config, ':');
|
||||
if (port)
|
||||
port++;
|
||||
else
|
||||
port = "6442";
|
||||
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
pnum = atoi(port);
|
||||
addr.sin_port = htons(pnum);
|
||||
|
||||
if ((listener->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* socket options */
|
||||
setsockopt(listener->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one));
|
||||
|
||||
/* set NONBLOCKING mode */
|
||||
setnonblocking(listener->fd);
|
||||
|
||||
/* bind address and port */
|
||||
if (bind(listener->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
listener->state = DCB_STATE_LISTENING;
|
||||
listen(listener->fd, SOMAXCONN);
|
||||
|
||||
if (poll_add_dcb(listener) == -1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTPD command implementation
|
||||
*
|
||||
* Called for each command in the HTTP stream.
|
||||
*
|
||||
* Currently we do no command execution
|
||||
*
|
||||
* @param dcb The client DCB
|
||||
* @param cmd The command stream
|
||||
*/
|
||||
static void
|
||||
httpd_command(DCB *dcb, char *cmd)
|
||||
{
|
||||
}
|
||||
|
||||
static int httpd_get_line(int sock, char *buf, int size) {
|
||||
int i = 0;
|
||||
char c = '\0';
|
||||
int n;
|
||||
|
||||
while ((i < size - 1) && (c != '\n'))
|
||||
{
|
||||
n = recv(sock, &c, 1, 0);
|
||||
/* DEBUG printf("%02X\n", c); */
|
||||
if (n > 0)
|
||||
{
|
||||
if (c == '\r')
|
||||
{
|
||||
n = recv(sock, &c, 1, MSG_PEEK);
|
||||
/* DEBUG printf("%02X\n", c); */
|
||||
if ((n > 0) && (c == '\n'))
|
||||
recv(sock, &c, 1, 0);
|
||||
else
|
||||
c = '\n';
|
||||
}
|
||||
buf[i] = c;
|
||||
i++;
|
||||
}
|
||||
else
|
||||
c = '\n';
|
||||
}
|
||||
buf[i] = '\0';
|
||||
|
||||
return(i);
|
||||
}
|
||||
|
||||
static void httpd_send_headers(int client, const char *filename)
|
||||
{
|
||||
char buf[1024];
|
||||
(void)filename; /* could use filename to determine file type */
|
||||
|
||||
strcpy(buf, "HTTP/1.0 200 OK\r\n");
|
||||
send(client, buf, strlen(buf), 0);
|
||||
strcpy(buf, HTTP_SERVER_STRING);
|
||||
send(client, buf, strlen(buf), 0);
|
||||
sprintf(buf, "Content-Type: text/html\r\n");
|
||||
send(client, buf, strlen(buf), 0);
|
||||
strcpy(buf, "\r\n");
|
||||
send(client, buf, strlen(buf), 0);
|
||||
}
|
||||
///
|
||||
@ -34,10 +34,11 @@
|
||||
* and necessary headers.
|
||||
* 01/07/2013 Massimiliano Pinto Put Log Manager example code behind SS_DEBUG macros.
|
||||
* 03/07/2013 Massimiliano Pinto Added delayq for incoming data before mysql connection
|
||||
* 04/07/2013 Massimiliano Pinto Added asyncrhronous MySQL protocol connection to backend
|
||||
* 05/07/2013 Massimiliano Pinto Added closeSession if backend auth fails
|
||||
*/
|
||||
|
||||
static char *version_str = "V1.0.0";
|
||||
extern char *gw_strend(register const char *s);
|
||||
static char *version_str = "V2.0.0";
|
||||
int gw_mysql_connect(char *host, int port, char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn);
|
||||
static int gw_create_backend_connection(DCB *client_dcb, SERVER *server, SESSION *in_session);
|
||||
static int gw_read_backend_event(DCB* dcb);
|
||||
@ -45,15 +46,18 @@ static int gw_write_backend_event(DCB *dcb);
|
||||
static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue);
|
||||
static int gw_error_backend_event(DCB *dcb);
|
||||
static int gw_backend_close(DCB *dcb);
|
||||
static int gw_backend_hangup(DCB *dcb);
|
||||
static int backend_write_delayqueue(DCB *dcb);
|
||||
static void backend_set_delayqueue(DCB *dcb, GWBUF *queue);
|
||||
|
||||
extern char *gw_strend(register const char *s);
|
||||
|
||||
static GWPROTOCOL MyObject = {
|
||||
gw_read_backend_event, /* Read - EPOLLIN handler */
|
||||
gw_MySQLWrite_backend, /* Write - data from gateway */
|
||||
gw_write_backend_event, /* WriteReady - EPOLLOUT handler */
|
||||
gw_error_backend_event, /* Error - EPOLLERR handler */
|
||||
NULL, /* HangUp - EPOLLHUP handler */
|
||||
gw_backend_hangup, /* HangUp - EPOLLHUP handler */
|
||||
NULL, /* Accept */
|
||||
gw_create_backend_connection, /* Connect */
|
||||
gw_backend_close, /* Close */
|
||||
@ -101,58 +105,106 @@ GetModuleObject()
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////
|
||||
//backend read event triggered by EPOLLIN
|
||||
//////////////////////////////////////////
|
||||
/**
|
||||
* Backend Read Event for EPOLLIN on the MySQL backend protocol module
|
||||
* @param dcb The backend Descriptor Control Block
|
||||
* @return 1 on operation, 0 for no action
|
||||
*/
|
||||
static int gw_read_backend_event(DCB *dcb) {
|
||||
int n;
|
||||
MySQLProtocol *client_protocol = NULL;
|
||||
MySQLProtocol *backend_protocol = NULL;
|
||||
MYSQL_session *current_session = NULL;
|
||||
|
||||
if (dcb)
|
||||
if(dcb->session)
|
||||
client_protocol = SESSION_PROTOCOL(dcb->session, MySQLProtocol);
|
||||
if(dcb->session) {
|
||||
client_protocol = SESSION_PROTOCOL(dcb->session, MySQLProtocol);
|
||||
}
|
||||
|
||||
#ifdef GW_DEBUG_READ_EVENT
|
||||
fprintf(stderr, "Backend ready! Read from Backend %i, write to client %i, client state %i\n", dcb->fd, dcb->session->client->fd, client_protocol->state);
|
||||
#endif
|
||||
backend_protocol = (MySQLProtocol *) dcb->protocol;
|
||||
current_session = (MYSQL_session *)dcb->session->data;
|
||||
|
||||
//fprintf(stderr, ">>> backend EPOLLIN from %i, protocol state [%s]\n", dcb->fd, gw_mysql_protocol_state2string(backend_protocol->state));
|
||||
|
||||
/* backend is connected:
|
||||
*
|
||||
* 1. read server handshake
|
||||
* 2. and write auth request
|
||||
* 3. and return
|
||||
*/
|
||||
if (backend_protocol->state == MYSQL_CONNECTED) {
|
||||
|
||||
gw_read_backend_handshake(backend_protocol);
|
||||
|
||||
gw_send_authentication_to_backend(current_session->db, current_session->user, current_session->client_sha1, backend_protocol);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ready to check the authentication reply from backend */
|
||||
|
||||
if (backend_protocol->state == MYSQL_AUTH_RECV) {
|
||||
ROUTER_OBJECT *router = NULL;
|
||||
ROUTER *router_instance = NULL;
|
||||
void *rsession = NULL;
|
||||
int rv = -1;
|
||||
SESSION *session = dcb->session;
|
||||
|
||||
if (session) {
|
||||
router = session->service->router;
|
||||
router_instance = session->service->router_instance;
|
||||
rsession = session->router_session;
|
||||
}
|
||||
|
||||
/* read backed auth reply */
|
||||
rv = gw_receive_backend_auth(backend_protocol);
|
||||
|
||||
switch (rv) {
|
||||
case MYSQL_FAILED_AUTHENTICATION:
|
||||
fprintf(stderr, ">>>> Backend Auth failed for user [%s], fd %i\n", current_session->user, dcb->fd);
|
||||
|
||||
backend_protocol->state = MYSQL_AUTH_FAILED;
|
||||
|
||||
/* send an error to the client */
|
||||
mysql_send_custom_error(dcb->session->client, 1, 0, "Connection to backend lost right now");
|
||||
|
||||
/* close the active session */
|
||||
router->closeSession(router_instance, rsession);
|
||||
|
||||
/* force the router_session to NULL
|
||||
* Later we will implement a proper status for the session
|
||||
*/
|
||||
session->router_session = NULL;
|
||||
|
||||
return 1;
|
||||
|
||||
case MYSQL_SUCCESFUL_AUTHENTICATION:
|
||||
spinlock_acquire(&dcb->authlock);
|
||||
|
||||
backend_protocol->state = MYSQL_IDLE;
|
||||
|
||||
/* check the delay queue and flush the data */
|
||||
if(dcb->delayq) {
|
||||
backend_write_delayqueue(dcb);
|
||||
spinlock_release(&dcb->authlock);
|
||||
return 1;
|
||||
}
|
||||
spinlock_release(&dcb->authlock);
|
||||
|
||||
return 1;
|
||||
|
||||
default:
|
||||
/* no other authentication state here right now, so just return */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* reading MySQL command output from backend and writing to the client */
|
||||
|
||||
if ((client_protocol->state == MYSQL_WAITING_RESULT) || (client_protocol->state == MYSQL_IDLE)) {
|
||||
int b = -1;
|
||||
GWBUF *buffer, *head;
|
||||
GWBUF *head = NULL;
|
||||
|
||||
if (ioctl(dcb->fd, FIONREAD, &b)) {
|
||||
fprintf(stderr, "Backend Ioctl FIONREAD error %i, %s\n", errno , strerror(errno));
|
||||
} else {
|
||||
//fprintf(stderr, "Backend IOCTL FIONREAD bytes to read = %i\n", b);
|
||||
}
|
||||
/* read available backend data */
|
||||
dcb_read(dcb, &head);
|
||||
|
||||
/*
|
||||
* Read all the data that is available into a chain of buffers
|
||||
*/
|
||||
head = NULL;
|
||||
while (b > 0)
|
||||
{
|
||||
int bufsize = b < MAX_BUFFER_SIZE ? b : MAX_BUFFER_SIZE;
|
||||
if ((buffer = gwbuf_alloc(bufsize)) == NULL)
|
||||
{
|
||||
/* Bad news, we have run out of memory */
|
||||
return 0;
|
||||
}
|
||||
GW_NOINTR_CALL(n = read(dcb->fd, GWBUF_DATA(buffer), bufsize); dcb->stats.n_reads++);
|
||||
if (n < 0)
|
||||
{
|
||||
// if eerno == EAGAIN || EWOULDBLOCK is missing
|
||||
// do the right task, not just break
|
||||
break;
|
||||
}
|
||||
|
||||
head = gwbuf_append(head, buffer);
|
||||
|
||||
// how many bytes left
|
||||
b -= n;
|
||||
}
|
||||
|
||||
// write the gwbuffer to client
|
||||
/* and write the gwbuffer to client */
|
||||
dcb->session->client->func.write(dcb->session->client, head);
|
||||
|
||||
return 1;
|
||||
@ -168,6 +220,22 @@ static int gw_read_backend_event(DCB *dcb) {
|
||||
* @return The number of bytes written
|
||||
*/
|
||||
static int gw_write_backend_event(DCB *dcb) {
|
||||
MySQLProtocol *backend_protocol = dcb->protocol;
|
||||
|
||||
//fprintf(stderr, ">>> backend EPOLLOUT %i, protocol state [%s]\n", backend_protocol->fd, gw_mysql_protocol_state2string(backend_protocol->state));
|
||||
|
||||
// spinlock_acquire(&dcb->connectlock);
|
||||
|
||||
if (backend_protocol->state == MYSQL_PENDING_CONNECT) {
|
||||
backend_protocol->state = MYSQL_CONNECTED;
|
||||
|
||||
// spinlock_release(&dcb->connectlock);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// spinlock_release(&dcb->connectlock);
|
||||
|
||||
return dcb_drain_writeq(dcb);
|
||||
}
|
||||
|
||||
@ -181,87 +249,123 @@ static int gw_write_backend_event(DCB *dcb) {
|
||||
static int
|
||||
gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue)
|
||||
{
|
||||
MySQLProtocol *backend_protocol = dcb->protocol;
|
||||
|
||||
spinlock_acquire(&dcb->authlock);
|
||||
|
||||
/**
|
||||
* Now put the incoming data to the delay queue unless backend is connected with auth ok
|
||||
*/
|
||||
if (backend_protocol->state != MYSQL_IDLE) {
|
||||
//fprintf(stderr, ">>> Writing in the backend %i delay queue\n", dcb->fd);
|
||||
|
||||
backend_set_delayqueue(dcb, queue);
|
||||
spinlock_release(&dcb->authlock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
spinlock_release(&dcb->authlock);
|
||||
|
||||
return dcb_write(dcb, queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Backend Error Handling
|
||||
*
|
||||
*/
|
||||
static int gw_error_backend_event(DCB *dcb) {
|
||||
|
||||
fprintf(stderr, "#### Handle Backend error function for %i\n", dcb->fd);
|
||||
fprintf(stderr, ">>> Handle Backend error function for %i\n", dcb->fd);
|
||||
|
||||
if (dcb->state != DCB_STATE_LISTENING) {
|
||||
if (poll_remove_dcb(dcb) == -1) {
|
||||
fprintf(stderr, "Backend poll_remove_dcb: from events check failed to delete %i, [%i]:[%s]\n", dcb->fd, errno, strerror(errno));
|
||||
}
|
||||
|
||||
#ifdef GW_EVENT_DEBUG
|
||||
fprintf(stderr, "Backend closing fd [%i]=%i, from events check\n", dcb->fd, protocol->fd);
|
||||
#endif
|
||||
if (dcb->fd) {
|
||||
dcb->state = DCB_STATE_DISCONNECTED;
|
||||
fprintf(stderr, "Freeing backend MySQL conn %p, %p\n", dcb->protocol, &dcb->protocol);
|
||||
gw_mysql_close((MySQLProtocol **)&dcb->protocol);
|
||||
fprintf(stderr, "Freeing backend MySQL conn %p, %p\n", dcb->protocol, &dcb->protocol);
|
||||
}
|
||||
}
|
||||
dcb_close(dcb);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new MySQL backend connection.
|
||||
* Create a new backend connection.
|
||||
*
|
||||
* This routine performs the MySQL connection to the backend and fills the session->backends of the callier dcb
|
||||
* with the new allocatetd dcb and adds the new socket to the poll set
|
||||
* This routine will connect to a backend server and it is called by dbc_connect in router->newSession
|
||||
*
|
||||
* - backend dcb allocation
|
||||
* - MySQL session data fetch
|
||||
* - backend connection using data in MySQL session
|
||||
*
|
||||
* @param client_dcb The client DCB struct
|
||||
* @param backend The Backend DCB allocated from dcb_connect
|
||||
* @param server The selected server to connect to
|
||||
* @param session The current session from Client DCB
|
||||
* @return 0 on Success or 1 on Failure.
|
||||
*/
|
||||
|
||||
static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *session) {
|
||||
MySQLProtocol *ptr_proto = NULL;
|
||||
MySQLProtocol *protocol = NULL;
|
||||
MYSQL_session *s_data = NULL;
|
||||
int rv = -1;
|
||||
|
||||
fprintf(stderr, "HERE, the server to connect is [%s]:[%i]\n", server->name, server->port);
|
||||
//fprintf(stderr, "HERE, the server to connect is [%s]:[%i]\n", server->name, server->port);
|
||||
|
||||
backend->protocol = (MySQLProtocol *) calloc(1, sizeof(MySQLProtocol));
|
||||
protocol = (MySQLProtocol *) calloc(1, sizeof(MySQLProtocol));
|
||||
protocol->state = MYSQL_ALLOC;
|
||||
|
||||
backend->protocol = protocol;
|
||||
|
||||
/* put the backend dcb in the protocol struct */
|
||||
protocol->descriptor = backend;
|
||||
|
||||
ptr_proto = (MySQLProtocol *)backend->protocol;
|
||||
s_data = (MYSQL_session *)session->client->data;
|
||||
|
||||
// fprintf(stderr, "HERE before connect, s_data is [%p]\n", s_data);
|
||||
// fprintf(stderr, "HERE before connect, username is [%s]\n", s_data->user);
|
||||
/**
|
||||
* let's try to connect to a backend server, only connect sys call
|
||||
* The socket descriptor is in Non Blocking status, this is set in the function
|
||||
*/
|
||||
rv = gw_do_connect_to_backend(server->name, server->port, protocol);
|
||||
|
||||
// this is blocking until auth done
|
||||
if (gw_mysql_connect(server->name, server->port, s_data->db, s_data->user, s_data->client_sha1, backend->protocol) == 0) {
|
||||
memcpy(&backend->fd, &ptr_proto->fd, sizeof(backend->fd));
|
||||
// we could also move later, this in to the gw_do_connect_to_backend using protocol->descriptor
|
||||
|
||||
setnonblocking(backend->fd);
|
||||
fprintf(stderr, "Connected to backend mysql server. fd is %i\n", backend->fd);
|
||||
} else {
|
||||
fprintf(stderr, "<<<< NOT Connected to backend mysql server!!!\n");
|
||||
backend->fd = -1;
|
||||
return -1;
|
||||
memcpy(&backend->fd, &protocol->fd, sizeof(backend->fd));
|
||||
|
||||
switch (rv) {
|
||||
|
||||
case 0:
|
||||
//fprintf(stderr, "Connected to backend mysql server: fd is %i\n", backend->fd);
|
||||
protocol->state = MYSQL_CONNECTED;
|
||||
|
||||
break;
|
||||
|
||||
case 1:
|
||||
//fprintf(stderr, ">>> Connection is PENDING to backend mysql server: fd is %i\n", backend->fd);
|
||||
protocol->state = MYSQL_PENDING_CONNECT;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, ">>> ERROR: NOT Connected to the backend mysql server!!!\n");
|
||||
backend->fd = -1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// if connected, it will be addeed to the epoll from the caller of connect()
|
||||
fprintf(stderr, ">>> Backend [%s:%i] added [%i], in the client session [%i]\n", server->name, server->port, backend->fd, session->client->fd);
|
||||
|
||||
if (backend->fd <= 0) {
|
||||
perror("ERROR: epoll_ctl: backend sock");
|
||||
backend->fd = -1;
|
||||
return -1;
|
||||
} else {
|
||||
fprintf(stderr, "--> Backend conn added, bk_fd [%i], scramble [%s], is session with client_fd [%i]\n", backend->fd, ptr_proto->scramble, session->client->fd);
|
||||
backend->state = DCB_STATE_POLLING;
|
||||
backend->state = DCB_STATE_POLLING;
|
||||
|
||||
return backend->fd;
|
||||
}
|
||||
return -1;
|
||||
return backend->fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hangup routine the backend dcb: it does nothing right now
|
||||
*
|
||||
* @param dcb The current Backend DCB
|
||||
* @return 1 always
|
||||
*/
|
||||
static int
|
||||
gw_backend_hangup(DCB *dcb)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the backend dcb
|
||||
*
|
||||
* @param dcb The current Backend DCB
|
||||
* @return 1 always
|
||||
*/
|
||||
static int
|
||||
gw_backend_close(DCB *dcb)
|
||||
{
|
||||
@ -269,444 +373,6 @@ gw_backend_close(DCB *dcb)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new MySQL connection.
|
||||
*
|
||||
* This routine performs the full MySQL connection to the specified server.
|
||||
* It does
|
||||
* - socket init
|
||||
* - socket connect
|
||||
* - server handshake parsing
|
||||
* - authenticatio reply
|
||||
* - the Auth ack receive
|
||||
*
|
||||
* Please note, all socket operation are in blocking state
|
||||
* Status: work in progress.
|
||||
*
|
||||
* @param host The TCP/IP host address to connect to
|
||||
* @param port The TCP/IP host port to connect to
|
||||
* @param dbname The optional database name. Use NULL if not interested in
|
||||
* @param user The MySQL database Username: required
|
||||
* @param passwd The MySQL database Password: required
|
||||
* @param conn The MySQLProtocol structure to be filled: must be preallocated with gw_mysql_init()
|
||||
* @return 0 on Success or 1 on Failure.
|
||||
*/
|
||||
int gw_mysql_connect(char *host, int port, char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn) {
|
||||
|
||||
struct sockaddr_in serv_addr;
|
||||
int compress = 0;
|
||||
int rv;
|
||||
int so = 0;
|
||||
int ciclo = 0;
|
||||
uint8_t buffer[SMALL_CHUNK];
|
||||
uint8_t packet_buffer[SMALL_CHUNK];
|
||||
uint8_t *payload = NULL;
|
||||
int server_protocol;
|
||||
uint8_t *server_version_end = NULL;
|
||||
uint16_t mysql_server_capabilities_one;
|
||||
uint16_t mysql_server_capabilities_two;
|
||||
unsigned long tid =0;
|
||||
long bytes;
|
||||
uint8_t scramble_data_1[8 + 1] = "";
|
||||
uint8_t scramble_data_2[12 + 1] = "";
|
||||
uint8_t capab_ptr[4];
|
||||
int scramble_len;
|
||||
uint8_t scramble[GW_MYSQL_SCRAMBLE_SIZE + 1];
|
||||
uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE + 1];
|
||||
uint8_t client_capabilities[4];
|
||||
uint32_t server_capabilities;
|
||||
uint32_t final_capabilities;
|
||||
char dbpass[129]="";
|
||||
|
||||
char *curr_db = NULL;
|
||||
uint8_t *curr_passwd = NULL;
|
||||
|
||||
if (strlen(dbname))
|
||||
curr_db = dbname;
|
||||
|
||||
if (strlen((char *)passwd))
|
||||
curr_passwd = passwd;
|
||||
|
||||
conn->state = MYSQL_ALLOC;
|
||||
conn->fd = -1;
|
||||
|
||||
memset(&server_capabilities, '\0', sizeof(server_capabilities));
|
||||
memset(&final_capabilities, '\0', sizeof(final_capabilities));
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
//fprintf(stderr, ")))) Connect to MySQL: user[%s], SHA1(passwd)[%s], db [%s]\n", user, passwd, dbname);
|
||||
#endif
|
||||
|
||||
memset(&serv_addr, 0, sizeof serv_addr);
|
||||
serv_addr.sin_family = AF_INET;
|
||||
|
||||
so = socket(AF_INET,SOCK_STREAM,0);
|
||||
if (so < 0) {
|
||||
fprintf(stderr, "Errore creazione socket: [%s] %i\n", strerror(errno), errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
conn->fd = so;
|
||||
|
||||
setipaddress(&serv_addr.sin_addr, host);
|
||||
serv_addr.sin_port = htons(port);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Socket initialized\n");
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
while(1) {
|
||||
if ((rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0) {
|
||||
fprintf(stderr, "Errore connect %i, %s: RV = [%i]\n", errno, strerror(errno), rv);
|
||||
|
||||
if (errno == EINPROGRESS) {
|
||||
continue;
|
||||
} else {
|
||||
close(so);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "CONNECT is DONE\n");
|
||||
fprintf(stderr, "Socket FD is %i\n", so);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
|
||||
memset(&buffer, '\0', sizeof(buffer));
|
||||
|
||||
bytes = SMALL_CHUNK;
|
||||
|
||||
rv = read(so, buffer, bytes);
|
||||
|
||||
if ( rv >0 ) {
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "RESPONSE ciclo %i HO letto [%s] bytes %li\n",ciclo, buffer, bytes);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
ciclo++;
|
||||
} else {
|
||||
if (rv == 0 && errno == EOF) {
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "EOF reached. Bytes = %li\n", bytes);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "###### Receive error FINAL : connection not completed %i %s: RV = [%i]\n", errno, strerror(errno), rv);
|
||||
#endif
|
||||
close(so);
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fwrite(buffer, bytes, 1, stderr);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
//decode mysql handshake
|
||||
|
||||
payload = buffer + 4;
|
||||
server_protocol= payload[0];
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Server Protocol [%i]\n", server_protocol);
|
||||
|
||||
#endif
|
||||
payload++;
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Protocol Version [%s]\n", payload);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
server_version_end = (uint8_t *) gw_strend((char*) payload);
|
||||
payload = server_version_end + 1;
|
||||
|
||||
// TID
|
||||
tid = gw_mysql_get_byte4(payload);
|
||||
memcpy(&conn->tid, &tid, 4);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Thread ID is %lu\n", conn->tid);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
payload +=4;
|
||||
|
||||
// scramble_part 1
|
||||
memcpy(scramble_data_1, payload, 8);
|
||||
payload += 8;
|
||||
|
||||
// 1 filler
|
||||
payload++;
|
||||
|
||||
mysql_server_capabilities_one = gw_mysql_get_byte2(payload);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Capab_1[\n");
|
||||
fwrite(&mysql_server_capabilities_one, 2, 1, stderr);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
//2 capab_part 1 + 1 language + 2 server_status
|
||||
payload +=5;
|
||||
|
||||
mysql_server_capabilities_two = gw_mysql_get_byte2(payload);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "]Capab_2[\n");
|
||||
fwrite(&mysql_server_capabilities_two, 2, 1, stderr);
|
||||
fprintf(stderr, "]\n");
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
memcpy(&capab_ptr, &mysql_server_capabilities_one, 2);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Capab_1[\n");
|
||||
fwrite(capab_ptr, 2, 1, stderr);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
memcpy(&(capab_ptr[2]), &mysql_server_capabilities_two, 2);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Capab_2[\n");
|
||||
fwrite(capab_ptr, 2, 1, stderr);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
// 2 capab_part 2
|
||||
payload+=2;
|
||||
|
||||
scramble_len = payload[0] -1;
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Scramble_len [%i]\n", scramble_len);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
payload += 11;
|
||||
|
||||
memcpy(scramble_data_2, payload, scramble_len - 8);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Scramble_buff1[");
|
||||
fwrite(scramble_data_1, 8, 1, stderr);
|
||||
fprintf(stderr, "]\nScramble_buff2 [");
|
||||
fwrite(scramble_data_2, scramble_len - 8, 1, stderr);
|
||||
fprintf(stderr, "]\n");
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
memcpy(scramble, scramble_data_1, 8);
|
||||
memcpy(scramble + 8, scramble_data_2, scramble_len - 8);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Full Scramble 20 bytes is [\n");
|
||||
fwrite(scramble, GW_MYSQL_SCRAMBLE_SIZE, 1, stderr);
|
||||
fprintf(stderr, "\n]\n");
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
memcpy(conn->scramble, scramble, GW_MYSQL_SCRAMBLE_SIZE);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Scramble from MYSQL_Conn is [\n");
|
||||
fwrite(scramble, GW_MYSQL_SCRAMBLE_SIZE, 1, stderr);
|
||||
fprintf(stderr, "\n]\n");
|
||||
fflush(stderr);
|
||||
fprintf(stderr, "Now sending user, pass & db\n[");
|
||||
fwrite(&server_capabilities, 4, 1, stderr);
|
||||
fprintf(stderr, "]\n");
|
||||
#endif
|
||||
|
||||
final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "CAPABS [%u]\n", final_capabilities);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
memset(packet_buffer, '\0', sizeof(packet_buffer));
|
||||
//packet_header(byte3 +1 pack#)
|
||||
packet_buffer[3] = '\x01';
|
||||
|
||||
final_capabilities |= GW_MYSQL_CAPABILITIES_PROTOCOL_41;
|
||||
final_capabilities |= GW_MYSQL_CAPABILITIES_CLIENT;
|
||||
if (compress) {
|
||||
final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS;
|
||||
fprintf(stderr, "Backend Connection with compression\n");
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
if (curr_passwd != NULL) {
|
||||
uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE]="";
|
||||
uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE]="";
|
||||
uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE]="";
|
||||
|
||||
|
||||
memcpy(hash1, passwd, GW_MYSQL_SCRAMBLE_SIZE);
|
||||
gw_sha1_str(hash1, GW_MYSQL_SCRAMBLE_SIZE, hash2);
|
||||
gw_bin2hex(dbpass, hash2, GW_MYSQL_SCRAMBLE_SIZE);
|
||||
gw_sha1_2_str(scramble, GW_MYSQL_SCRAMBLE_SIZE, hash2, GW_MYSQL_SCRAMBLE_SIZE, new_sha);
|
||||
gw_str_xor(client_scramble, new_sha, hash1, GW_MYSQL_SCRAMBLE_SIZE);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Hash1 [%s]\n", hash1);
|
||||
fprintf(stderr, "Hash2 [%s]\n", hash2);
|
||||
fprintf(stderr, "SHA1(SHA1(password in hex)\n");
|
||||
fprintf(stderr, "PAss [%s]\n", dbpass);
|
||||
fflush(stderr);
|
||||
fprintf(stderr, "newsha [%s]\n", new_sha);
|
||||
fprintf(stderr, "Client send scramble 20 [\n");
|
||||
fwrite(client_scramble, GW_MYSQL_SCRAMBLE_SIZE, 1, stderr);
|
||||
fprintf(stderr, "\n]\n");
|
||||
fflush(stderr);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (curr_db == NULL) {
|
||||
// now without db!!
|
||||
final_capabilities &= ~GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
|
||||
} else {
|
||||
final_capabilities |= GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB;
|
||||
}
|
||||
|
||||
payload = packet_buffer + 4;
|
||||
|
||||
final_capabilities |= GW_MYSQL_CAPABILITIES_PLUGIN_AUTH;
|
||||
|
||||
gw_mysql_set_byte4(client_capabilities, final_capabilities);
|
||||
memcpy(payload, client_capabilities, 4);
|
||||
|
||||
//packet_buffer[4] = '\x8d';
|
||||
//packet_buffer[5] = '\xa6';
|
||||
//packet_buffer[6] = '\x0f';
|
||||
//packet_buffer[7] = '\x00';
|
||||
|
||||
// set now the max-packet size
|
||||
payload += 4;
|
||||
gw_mysql_set_byte4(payload, 16777216);
|
||||
|
||||
// set the charset
|
||||
payload += 4;
|
||||
*payload = '\x08';
|
||||
|
||||
payload++;
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "User is [%s]\n", user);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
|
||||
// 4 + 4 + 4 + 1 + 23 = 36
|
||||
payload += 23;
|
||||
memcpy(payload, user, strlen(user));
|
||||
|
||||
// 4 + 4 + 1 + 23 = 32 + 1 (scramble_len) + 20 (fixed_scramble) + 1 (user NULL term) + 1 (db NULL term) = 55
|
||||
bytes = 32;
|
||||
|
||||
bytes += strlen(user);
|
||||
// the NULL
|
||||
bytes++;
|
||||
|
||||
payload += strlen(user);
|
||||
payload++;
|
||||
|
||||
if (curr_passwd != NULL) {
|
||||
// set the auth-length
|
||||
*payload = GW_MYSQL_SCRAMBLE_SIZE;
|
||||
payload++;
|
||||
bytes++;
|
||||
|
||||
//copy the 20 bytes scramble data after packet_buffer+36+user+NULL+1 (byte of auth-length)
|
||||
memcpy(payload, client_scramble, GW_MYSQL_SCRAMBLE_SIZE);
|
||||
|
||||
payload += GW_MYSQL_SCRAMBLE_SIZE;
|
||||
bytes += GW_MYSQL_SCRAMBLE_SIZE;
|
||||
|
||||
} else {
|
||||
// skip the auth-length and write a NULL
|
||||
payload++;
|
||||
bytes++;
|
||||
}
|
||||
|
||||
// if the db is not NULL append it
|
||||
if (curr_db) {
|
||||
memcpy(payload, curr_db, strlen(curr_db));
|
||||
payload += strlen(curr_db);
|
||||
payload++;
|
||||
bytes += strlen(curr_db);
|
||||
// the NULL
|
||||
bytes++;
|
||||
}
|
||||
|
||||
memcpy(payload, "mysql_native_password", strlen("mysql_native_password"));
|
||||
|
||||
payload += strlen("mysql_native_password");
|
||||
payload++;
|
||||
|
||||
bytes +=strlen("mysql_native_password");
|
||||
bytes++;
|
||||
|
||||
gw_mysql_set_byte3(packet_buffer, bytes);
|
||||
|
||||
// the packet header
|
||||
bytes += 4;
|
||||
|
||||
rv = write(so, packet_buffer, bytes);
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "Sent [%s], [%i] bytes\n", packet_buffer, bytes);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
|
||||
if (rv == -1) {
|
||||
fprintf(stderr, "CONNECT Error in send auth\n");
|
||||
}
|
||||
|
||||
bytes = SMALL_CHUNK;
|
||||
|
||||
memset(buffer, '\0', sizeof (buffer));
|
||||
|
||||
rv = read(so, buffer, SMALL_CHUNK);
|
||||
|
||||
if (rv == -1) {
|
||||
fprintf(stderr, "CONNCET Error in recv OK for auth\n");
|
||||
}
|
||||
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "ok packet\[");
|
||||
fwrite(buffer, bytes, 1, stderr);
|
||||
fprintf(stderr, "]\n");
|
||||
fflush(stderr);
|
||||
#endif
|
||||
if (buffer[4] == '\x00') {
|
||||
#ifdef MYSQL_CONN_DEBUG
|
||||
fprintf(stderr, "OK packet received, packet # %i\n", buffer[3]);
|
||||
fflush(stderr);
|
||||
#endif
|
||||
conn->state = MYSQL_IDLE;
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
|
||||
close(so);
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine put into the delay queue the input queue
|
||||
* The input is what backend DCB is receiving
|
||||
|
||||
@ -179,87 +179,6 @@ mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mys
|
||||
return sizeof(mysql_packet_header) + mysql_payload_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* mysql_send_auth_error
|
||||
*
|
||||
* Send a MySQL protocol Generic ERR message, to the dcb
|
||||
* Note the errno and state are still fixed now
|
||||
*
|
||||
* @param dcb Descriptor Control Block for the connection to which the OK is sent
|
||||
* @param packet_number
|
||||
* @param in_affected_rows
|
||||
* @param mysql_message
|
||||
* @return packet length
|
||||
*
|
||||
*/
|
||||
|
||||
int
|
||||
mysql_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) {
|
||||
uint8_t *outbuf = NULL;
|
||||
uint8_t mysql_payload_size = 0;
|
||||
uint8_t mysql_packet_header[4];
|
||||
uint8_t *mysql_payload = NULL;
|
||||
uint8_t field_count = 0;
|
||||
uint8_t mysql_err[2];
|
||||
uint8_t mysql_statemsg[6];
|
||||
unsigned int mysql_errno = 0;
|
||||
const char *mysql_error_msg = NULL;
|
||||
const char *mysql_state = NULL;
|
||||
|
||||
GWBUF *buf;
|
||||
|
||||
mysql_errno = 2003;
|
||||
mysql_error_msg = "An errorr occurred ...";
|
||||
mysql_state = "HY000";
|
||||
|
||||
field_count = 0xff;
|
||||
gw_mysql_set_byte2(mysql_err, mysql_errno);
|
||||
mysql_statemsg[0]='#';
|
||||
memcpy(mysql_statemsg+1, mysql_state, 5);
|
||||
|
||||
if (mysql_message != NULL) {
|
||||
mysql_error_msg = mysql_message;
|
||||
}
|
||||
|
||||
mysql_payload_size = sizeof(field_count) + sizeof(mysql_err) + sizeof(mysql_statemsg) + strlen(mysql_error_msg);
|
||||
|
||||
// allocate memory for packet header + payload
|
||||
if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
outbuf = GWBUF_DATA(buf);
|
||||
|
||||
// write packet header with packet number
|
||||
gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
|
||||
mysql_packet_header[3] = packet_number;
|
||||
|
||||
// write header
|
||||
memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
|
||||
|
||||
mysql_payload = outbuf + sizeof(mysql_packet_header);
|
||||
|
||||
// write field
|
||||
memcpy(mysql_payload, &field_count, sizeof(field_count));
|
||||
mysql_payload = mysql_payload + sizeof(field_count);
|
||||
|
||||
// write errno
|
||||
memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
|
||||
mysql_payload = mysql_payload + sizeof(mysql_err);
|
||||
|
||||
// write sqlstate
|
||||
memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
|
||||
mysql_payload = mysql_payload + sizeof(mysql_statemsg);
|
||||
|
||||
// write err messg
|
||||
memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));
|
||||
|
||||
// writing data in the Client buffer queue
|
||||
dcb->func.write(dcb, buf);
|
||||
|
||||
return sizeof(mysql_packet_header) + mysql_payload_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* mysql_send_auth_error
|
||||
*
|
||||
@ -542,8 +461,6 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
||||
fprintf(stderr, "<<< Client is NOT connected with db\n");
|
||||
}
|
||||
|
||||
fprintf(stderr, "HERE auth token len is %i\n", auth_token_len);
|
||||
|
||||
// allocate memory for token only if auth_token_len > 0
|
||||
if (auth_token_len) {
|
||||
auth_token = (uint8_t *)malloc(auth_token_len);
|
||||
@ -558,10 +475,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
||||
if (auth_token)
|
||||
free(auth_token);
|
||||
|
||||
if (auth_ret == 0) {
|
||||
fprintf(stderr, "<<< CLIENT AUTH is OK\n");
|
||||
} else {
|
||||
fprintf(stderr, "<<< CLIENT AUTH FAILED\n");
|
||||
if (auth_ret != 0) {
|
||||
fprintf(stderr, "<<< CLIENT AUTH FAILEDi for user [%s]\n", username);
|
||||
}
|
||||
|
||||
return auth_ret;
|
||||
@ -586,7 +501,6 @@ static int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_pas
|
||||
fprintf(stderr, ">>> MYSQL user NOT FOUND: %s\n", username);
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, ">>> MYSQL user FOUND !!!!: [%s]:[%s]\n", username, user_password);
|
||||
|
||||
// convert hex data (40 bytes) to binary (20 bytes)
|
||||
// gateway_password represents the SHA1(SHA1(real_password))
|
||||
@ -618,16 +532,14 @@ static int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int t
|
||||
if (ret_val) {
|
||||
fprintf(stderr, "<<<< User [%s] was not found\n", username);
|
||||
return 1;
|
||||
} else {
|
||||
fprintf(stderr, "<<<< User [%s] OK\n", username);
|
||||
}
|
||||
|
||||
if (token && token_len) {
|
||||
fprintf(stderr, ">>> continue with auth\n");
|
||||
// convert in hex format: this is the content of mysql.user table, field password without the '*' prefix
|
||||
// an it is 40 bytes long
|
||||
// and it is 40 bytes long
|
||||
gw_bin2hex(hex_double_sha1, password, SHA_DIGEST_LENGTH);
|
||||
} else {
|
||||
// check if the password is not set in the user table
|
||||
if (!strlen((char *)password)) {
|
||||
fprintf(stderr, ">>> continue WITHOUT auth, no password\n");
|
||||
return 0;
|
||||
@ -673,14 +585,12 @@ static int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int t
|
||||
gw_sha1_str(step2, SHA_DIGEST_LENGTH, check_hash);
|
||||
|
||||
|
||||
fprintf(stderr, "<<<< Client_SHA1 [%20s]\n", stage1_hash);
|
||||
|
||||
#ifdef GW_DEBUG_CLIENT_AUTH
|
||||
{
|
||||
char inpass[128]="";
|
||||
gw_bin2hex(inpass, check_hash, SHA_DIGEST_LENGTH);
|
||||
|
||||
//fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass);
|
||||
fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -865,6 +775,8 @@ int gw_read_client_event(DCB* dcb) {
|
||||
int ret = -1;
|
||||
|
||||
session = dcb->session;
|
||||
|
||||
// get the backend session, if available
|
||||
if (session) {
|
||||
router = session->service->router;
|
||||
router_instance = session->service->router_instance;
|
||||
@ -877,65 +789,66 @@ int gw_read_client_event(DCB* dcb) {
|
||||
if ((ret = gw_read_gwbuff(dcb, &gw_buffer, b)) != 0)
|
||||
return ret;
|
||||
|
||||
// Now assuming in the first buffer there is the information form mysql command
|
||||
/* Now, we are assuming in the first buffer there is the information form mysql command */
|
||||
|
||||
// following code is only for debug now
|
||||
queue = gw_buffer;
|
||||
len = GWBUF_LENGTH(queue);
|
||||
|
||||
ptr_buff = GWBUF_DATA(queue);
|
||||
|
||||
// get mysql commang
|
||||
/* get mysql commang at fourth byte */
|
||||
if (ptr_buff)
|
||||
mysql_command = ptr_buff[4];
|
||||
|
||||
if (mysql_command == '\x03') {
|
||||
/// this is a query !!!!
|
||||
//fprintf(stderr, "<<< MySQL Query from Client %i bytes: [%s]\n", len, ptr_buff+5);
|
||||
//else
|
||||
//fprintf(stderr, "<<< Reading from Client %i bytes: [%s]\n", len, ptr_buff);
|
||||
/// this is a standard MySQL query !!!!
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Routing Client input to Backend
|
||||
*/
|
||||
|
||||
/* Do not route the query without session! */
|
||||
if(!rsession) {
|
||||
|
||||
if (mysql_command == '\x01') {
|
||||
fprintf(stderr, "COM_QUIT received with no connected backends\n");
|
||||
/* COM_QUIT handling */
|
||||
//fprintf(stderr, "COM_QUIT received with no connected backends from %i\n", dcb->fd);
|
||||
(dcb->func).close(dcb);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
/* Send a custom error as MySQL command reply */
|
||||
mysql_send_custom_error(dcb, 1, 0, "Connection to backend lost");
|
||||
|
||||
protocol->state = MYSQL_IDLE;
|
||||
|
||||
return 1;
|
||||
}
|
||||
mysql_send_custom_error(dcb, 1, 0, "Connection to backend lost");
|
||||
protocol->state = MYSQL_IDLE;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
// Handling the COM_QUIT
|
||||
//////////////////////////
|
||||
if (mysql_command == '\x01') {
|
||||
fprintf(stderr, "COM_QUIT received\n");
|
||||
// uncomment the following lines for closing
|
||||
// client and backend conns
|
||||
// dcb still to be freed
|
||||
|
||||
/* We can route the query */
|
||||
|
||||
// this will propagate COM_QUIT to backends
|
||||
/* COM_QUIT handling */
|
||||
if (mysql_command == '\x01') {
|
||||
//fprintf(stderr, "COM_QUIT received from %i and passed to backed\n", dcb->fd);
|
||||
|
||||
/* this will propagate COM_QUIT to backend(s) */
|
||||
//fprintf(stderr, "<<< Routing the COM_QUIT ...\n");
|
||||
router->routeQuery(router_instance, rsession, queue);
|
||||
// close client
|
||||
|
||||
/* close client connection */
|
||||
(dcb->func).close(dcb);
|
||||
// call errors, it will be removed after tests
|
||||
//(dcb->func).error(dcb);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* MySQL Command Routing */
|
||||
|
||||
protocol->state = MYSQL_ROUTING;
|
||||
|
||||
///////////////////////////////////////
|
||||
// writing in the backend buffer queue, via routeQuery
|
||||
///////////////////////////////////////
|
||||
/* writing in the backend buffer queue, via routeQuery */
|
||||
|
||||
//fprintf(stderr, "<<< Routing the Query ...\n");
|
||||
router->routeQuery(router_instance, rsession, queue);
|
||||
|
||||
protocol->state = MYSQL_WAITING_RESULT;
|
||||
@ -1085,7 +998,6 @@ int gw_MySQLAccept(DCB *listener) {
|
||||
|
||||
if (c_sock == -1) {
|
||||
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
|
||||
fprintf(stderr, ">>>> NO MORE conns for MySQL Listener: errno is %i for %i\n", errno, listener->fd);
|
||||
/* We have processed all incoming connections. */
|
||||
break;
|
||||
} else {
|
||||
@ -1145,7 +1057,8 @@ int gw_MySQLAccept(DCB *listener) {
|
||||
/*
|
||||
*/
|
||||
static int gw_error_client_event(DCB *dcb) {
|
||||
fprintf(stderr, "#### Handle error function gw_error_client_event, for [%i] is [%s]\n", dcb->fd, gw_dcb_state2string(dcb->state));
|
||||
//fprintf(stderr, "#### Handle error function gw_error_client_event, for [%i] is [%s]\n", dcb->fd, gw_dcb_state2string(dcb->state));
|
||||
//dcb_close(dcb);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -432,4 +432,162 @@ int gw_send_authentication_to_backend(char *dbname, char *user, uint8_t *passwd,
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only backend connect syscall
|
||||
*/
|
||||
int gw_do_connect_to_backend(char *host, int port, MySQLProtocol *conn) {
|
||||
struct sockaddr_in serv_addr;
|
||||
int rv;
|
||||
int so = 0;
|
||||
|
||||
memset(&serv_addr, 0, sizeof serv_addr);
|
||||
serv_addr.sin_family = AF_INET;
|
||||
|
||||
so = socket(AF_INET,SOCK_STREAM,0);
|
||||
|
||||
conn->fd = so;
|
||||
|
||||
if (so < 0) {
|
||||
fprintf(stderr, "Error creating backend socket: [%s] %i\n", strerror(errno), errno);
|
||||
/* this is an error */
|
||||
return -1;
|
||||
}
|
||||
|
||||
setipaddress(&serv_addr.sin_addr, host);
|
||||
serv_addr.sin_port = htons(port);
|
||||
|
||||
/* set NON BLOCKING here */
|
||||
setnonblocking(so);
|
||||
|
||||
if ((rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0) {
|
||||
/* If connection is not yet completed just return 1 */
|
||||
if (errno == EINPROGRESS) {
|
||||
//fprintf(stderr, ">>> Connection is not yet completed for backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
/* this is a real error */
|
||||
fprintf(stderr, ">>> ERROR connecting to backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* The connection succesfully completed now */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of a MySQL protocol state.
|
||||
*
|
||||
* @param state The protocol state
|
||||
* @return String representation of the state
|
||||
*
|
||||
*/
|
||||
const char *
|
||||
gw_mysql_protocol_state2string (int state) {
|
||||
switch(state) {
|
||||
case MYSQL_ALLOC:
|
||||
return "MySQL Protocl struct allocated";
|
||||
case MYSQL_PENDING_CONNECT:
|
||||
return "MySQL Backend socket PENDING connect";
|
||||
case MYSQL_CONNECTED:
|
||||
return "MySQL Backend socket CONNECTED";
|
||||
case MYSQL_AUTH_SENT:
|
||||
return "MySQL Authentication handshake has been sent";
|
||||
case MYSQL_AUTH_RECV:
|
||||
return "MySQL Received user, password, db and capabilities";
|
||||
case MYSQL_AUTH_FAILED:
|
||||
return "MySQL Authentication failed";
|
||||
case MYSQL_IDLE:
|
||||
return "MySQL Auth done. Protocol is idle, waiting for statements";
|
||||
case MYSQL_ROUTING:
|
||||
return "MySQL received command has been routed to backend(s)";
|
||||
case MYSQL_WAITING_RESULT:
|
||||
return "MySQL Waiting for result set";
|
||||
default:
|
||||
return "MySQL (unknown protocol state)";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mysql_send_custom_error
|
||||
*
|
||||
* Send a MySQL protocol Generic ERR message, to the dcb
|
||||
* Note the errno and state are still fixed now
|
||||
*
|
||||
* @param dcb Descriptor Control Block for the connection to which the OK is sent
|
||||
* @param packet_number
|
||||
* @param in_affected_rows
|
||||
* @param mysql_message
|
||||
* @return packet length
|
||||
*
|
||||
*/
|
||||
int
|
||||
mysql_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) {
|
||||
uint8_t *outbuf = NULL;
|
||||
uint8_t mysql_payload_size = 0;
|
||||
uint8_t mysql_packet_header[4];
|
||||
uint8_t *mysql_payload = NULL;
|
||||
uint8_t field_count = 0;
|
||||
uint8_t mysql_err[2];
|
||||
uint8_t mysql_statemsg[6];
|
||||
unsigned int mysql_errno = 0;
|
||||
const char *mysql_error_msg = NULL;
|
||||
const char *mysql_state = NULL;
|
||||
|
||||
GWBUF *buf;
|
||||
|
||||
mysql_errno = 2003;
|
||||
mysql_error_msg = "An errorr occurred ...";
|
||||
mysql_state = "HY000";
|
||||
|
||||
field_count = 0xff;
|
||||
gw_mysql_set_byte2(mysql_err, mysql_errno);
|
||||
mysql_statemsg[0]='#';
|
||||
memcpy(mysql_statemsg+1, mysql_state, 5);
|
||||
|
||||
if (mysql_message != NULL) {
|
||||
mysql_error_msg = mysql_message;
|
||||
}
|
||||
|
||||
mysql_payload_size = sizeof(field_count) + sizeof(mysql_err) + sizeof(mysql_statemsg) + strlen(mysql_error_msg);
|
||||
|
||||
// allocate memory for packet header + payload
|
||||
if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
outbuf = GWBUF_DATA(buf);
|
||||
|
||||
// write packet header with packet number
|
||||
gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
|
||||
mysql_packet_header[3] = packet_number;
|
||||
|
||||
// write header
|
||||
memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
|
||||
|
||||
mysql_payload = outbuf + sizeof(mysql_packet_header);
|
||||
|
||||
// write field
|
||||
memcpy(mysql_payload, &field_count, sizeof(field_count));
|
||||
mysql_payload = mysql_payload + sizeof(field_count);
|
||||
|
||||
// write errno
|
||||
memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
|
||||
mysql_payload = mysql_payload + sizeof(mysql_err);
|
||||
|
||||
// write sqlstate
|
||||
memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
|
||||
mysql_payload = mysql_payload + sizeof(mysql_statemsg);
|
||||
|
||||
// write err messg
|
||||
memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));
|
||||
|
||||
// writing data in the Client buffer queue
|
||||
dcb->func.write(dcb, buf);
|
||||
|
||||
return sizeof(mysql_packet_header) + mysql_payload_size;
|
||||
}
|
||||
/////
|
||||
|
||||
Reference in New Issue
Block a user