From 719503e471c5301d7621f303df012c589f12cc08 Mon Sep 17 00:00:00 2001 From: Mark Riddoch Date: Fri, 13 Jun 2014 23:40:07 +0100 Subject: [PATCH] Addition of new client utility, maxadmin. Supporting protocol for the admin interface New routing module, cli, which shares source with debugcli Tidyup output of lsit commands --- Makefile | 4 + client/.maxadmin.c.swp | Bin 0 -> 20480 bytes client/Makefile | 71 ++++++ client/depend.mk | 0 client/maxadmin.c | 373 +++++++++++++++++++++++++++ server/core/dcb.c | 8 +- server/core/filter.c | 4 +- server/core/load_utils.c | 3 +- server/core/server.c | 4 +- server/core/service.c | 8 +- server/core/session.c | 4 +- server/modules/include/maxscaled.h | 46 ++++ server/modules/protocol/Makefile | 8 +- server/modules/protocol/maxscaled.c | 383 ++++++++++++++++++++++++++++ server/modules/routing/Makefile | 9 +- server/modules/routing/cli.c | 297 +++++++++++++++++++++ server/modules/routing/debugcmd.c | 2 + 17 files changed, 1212 insertions(+), 12 deletions(-) create mode 100644 client/.maxadmin.c.swp create mode 100644 client/Makefile create mode 100644 client/depend.mk create mode 100644 client/maxadmin.c create mode 100644 server/modules/include/maxscaled.h create mode 100644 server/modules/protocol/maxscaled.c create mode 100644 server/modules/routing/cli.c diff --git a/Makefile b/Makefile index 67eb0d159..07a385b61 100644 --- a/Makefile +++ b/Makefile @@ -39,21 +39,25 @@ all: (cd log_manager; make) (cd query_classifier; make) (cd server; make) + (cd client; make) clean: (cd log_manager; make clean) (cd query_classifier; make clean) (cd server; make clean) + (cd client; make clean) depend: (cd log_manager; make depend) (cd query_classifier; make depend) (cd server; make depend) + (cd client; make depend) install: (cd server; make DEST=$(DEST) install) (cd log_manager; make DEST=$(DEST) install) (cd query_classifier; make DEST=$(DEST) install) + (cd client; make DEST=$(DEST) install) cleantests: $(MAKE) -C test cleantests diff --git a/client/.maxadmin.c.swp b/client/.maxadmin.c.swp new file mode 100644 index 0000000000000000000000000000000000000000..a864922abaa67d4cffcffed3281a0d4ade6de26b GIT binary patch literal 20480 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 depend.mk + +install: maxadmin + @mkdir -p $(DEST)/bin + install -D maxadmin $(DEST)/bin + +include depend.mk diff --git a/client/depend.mk b/client/depend.mk new file mode 100644 index 000000000..e69de29bb diff --git a/client/maxadmin.c b/client/maxadmin.c new file mode 100644 index 000000000..4065a971d --- /dev/null +++ b/client/maxadmin.c @@ -0,0 +1,373 @@ +/* + * This file is distributed as part of MaxScale. 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 2014 + */ + +/** + * @file maxadmin.c - The MaxScale administration client + * + * @verbatim + * Revision History + * + * Date Who Description + * 13/06/14 Mark Riddoch Initial implementation + * + * @endverbatim + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static int connectMaxScale(char *hostname, char *port); +static int setipaddress(struct in_addr *a, char *p); +static int authMaxScale(int so, char *user, char *password); +static int sendCommand(int so, char *cmd); + +static char * +prompt(EditLine *el __attribute__((__unused__))) +{ + static char prompt[] = "MaxScale> "; + + return prompt; +} + +int +main(int argc, char **argv) +{ +EditLine *el = NULL; +int i, num, rv, fatal = 0; +char *buf; +Tokenizer *tok; +History *hist; +HistEvent ev; +const LineInfo *li; +char *hostname = "localhost"; +char *port = "6603"; +char *user = "admin"; +char *passwd = NULL; +int so, cmdlen; +char *cmd; + + cmd = malloc(1); + *cmd = 0; + cmdlen = 1; + + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + switch (argv[i][1]) + { + case 'u': /* User */ + if (argv[i][2]) + user = &(argv[i][2]); + else if (i + 1 < argc) + user = argv[++i]; + else + { + fprintf(stderr, "Missing username" + "in -u option.\n"); + fatal = 1; + } + break; + case 'p': /* Password */ + if (argv[i][2]) + passwd = &(argv[i][2]); + else if (i + 1 < argc) + passwd = argv[++i]; + else + { + fprintf(stderr, "Missing password " + "in -p option.\n"); + fatal = 1; + } + break; + case 'h': /* hostname */ + if (argv[i][2]) + hostname = &(argv[i][2]); + else if (i + 1 < argc) + hostname = argv[++i]; + else + { + fprintf(stderr, "Missing hostname value " + "in -h option.\n"); + fatal = 1; + } + break; + case 'P': /* Port */ + if (argv[i][2]) + port = &(argv[i][2]); + else if (i + 1 < argc) + port = argv[++i]; + else + { + fprintf(stderr, "Missing Port value " + "in -P option.\n"); + fatal = 1; + } + break; + } + } + else + { + cmdlen += strlen(argv[i]) + 1; + cmd = realloc(cmd, cmdlen); + strcat(cmd, argv[i]); + strcat(cmd, " "); + } + } + + if (fatal) + exit(1); + + if (passwd == NULL) + { + struct termios tty_attr; + tcflag_t c_lflag; + + if (tcgetattr(STDIN_FILENO, &tty_attr) < 0) + return -1; + + c_lflag = tty_attr.c_lflag; + tty_attr.c_lflag &= ~ICANON; + tty_attr.c_lflag &= ~ECHO; + + if (tcsetattr(STDIN_FILENO, 0, &tty_attr) < 0) + return -1; + + printf("Password: "); + passwd = malloc(80); + fgets(passwd, 80, stdin); + + tty_attr.c_lflag = c_lflag; + if (tcsetattr(STDIN_FILENO, 0, &tty_attr) < 0) + return -1; + i = strlen(passwd); + if (i > 1) + passwd[i - 1] = '\0'; + printf("\n"); + } + + if ((so = connectMaxScale(hostname, port)) == -1) + exit(1); + if (!authMaxScale(so, user, passwd)) + { + fprintf(stderr, "Failed to connect to MaxScale. " + "Incorrect username or password.\n"); + exit(1); + } + + if (cmdlen > 1) + { + cmd[cmdlen - 2] = '\0'; + sendCommand(so, cmd); + exit(0); + } + + (void) setlocale(LC_CTYPE, ""); + + hist = history_init(); /* Init the builtin history */ + /* Remember 100 events */ + history(hist, &ev, H_SETSIZE, 100); + + tok = tok_init(NULL); /* Initialize the tokenizer */ + + /* Initialize editline */ + el = el_init(*argv, stdin, stdout, stderr); + + el_set(el, EL_EDITOR, "vi"); /* Default editor is vi */ + el_set(el, EL_SIGNAL, 1); /* Handle signals gracefully */ + el_set(el, EL_PROMPT, prompt);/* Set the prompt function */ + + /* Tell editline to use this history interface */ + el_set(el, EL_HIST, history, hist); + + /* + * Bind j, k in vi command mode to previous and next line, instead + * of previous and next history. + */ + el_set(el, EL_BIND, "-a", "k", "ed-prev-line", NULL); + el_set(el, EL_BIND, "-a", "j", "ed-next-line", NULL); + + /* + * Source the user's defaults file. + */ + el_source(el, NULL); + + while ((buf = el_gets(el, &num)) != NULL && num != 0) + { + /* Strip trailing \n\r */ + for (i = num - 1; buf[i] == '\r' || buf[i] == '\n'; i--) + buf[i] = 0; + + li = el_line(el); + history(hist, &ev, H_ENTER, buf); + + if (!strcasecmp(buf, "quit")) + { + break; + } + else if (!strcasecmp(buf, "history")) + { + for (rv = history(hist, &ev, H_LAST); rv != -1; + rv = history(hist, &ev, H_PREV)) + fprintf(stdout, "%4d %s\n", + ev.num, ev.str); + } + else if (*buf) + { + sendCommand(so, buf); + } + } + + el_end(el); + tok_end(tok); + history_end(hist); + close(so); + return 0; +} + +static int +connectMaxScale(char *hostname, char *port) +{ +struct sockaddr_in addr; +int so; + + if ((so = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + fprintf(stderr, "Unable to create socket: %s\n", + strerror(errno)); + return -1; + } + memset(&addr, 0, sizeof addr); + addr.sin_family = AF_INET; + setipaddress(&addr.sin_addr, hostname); + addr.sin_port = htons(atoi(port)); + if (connect(so, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + fprintf(stderr, "Unable to connect to MaxScale at %s, %s: %s\n", + hostname, port, strerror(errno)); + return -1; + } + + return so; +} + + +/* + * Set IP address in socket structure in_addr + * + * @param a Pointer to a struct in_addr into which the address is written + * @param p The hostname to lookup + * @return 1 on success, 0 on failure + */ +static int +setipaddress(struct in_addr *a, char *p) +{ +#ifdef __USE_POSIX + struct addrinfo *ai = NULL, hint; + int rc; + struct sockaddr_in * res_addr; + memset(&hint, 0, sizeof (hint)); + + hint.ai_socktype = SOCK_STREAM; + hint.ai_flags = AI_CANONNAME; + hint.ai_family = AF_INET; + + if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0) { + return 0; + } + + /* take the first one */ + if (ai != NULL) { + res_addr = (struct sockaddr_in *)(ai->ai_addr); + memcpy(a, &res_addr->sin_addr, sizeof(struct in_addr)); + + freeaddrinfo(ai); + + return 1; + } +#else + struct hostent *h; + + spinlock_acquire(&tmplock); + h = gethostbyname(p); + spinlock_release(&tmplock); + + if (h == NULL) { + if ((a->s_addr = inet_addr(p)) == -1) { + return 0; + } + } else { + /* take the first one */ + memcpy(a, h->h_addr, h->h_length); + + return 1; + } +#endif + return 0; +} + +static int +authMaxScale(int so, char *user, char *password) +{ +char buf[20]; + + read(so, buf, 4); + write(so, user, strlen(user)); + read(so, buf, 8); + write(so, password, strlen(password)); + read(so, buf, 6); + + return strncmp(buf, "FAILED", 6); +} + +static int +sendCommand(int so, char *cmd) +{ +char buf[80]; +int i; + + write(so, cmd, strlen(cmd)); + while (1) + { + if ((i = read(so, buf, 80)) == -1) + return 0; + if (i > 1 && buf[i-1] == 'K' && buf[i-2] == 'O') + { + write(1, buf, i - 2); + return 1; + } + write(1, buf, i); + } + return 1; +} diff --git a/server/core/dcb.c b/server/core/dcb.c index 517b697cd..5417abe80 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -1172,15 +1172,17 @@ DCB *dcb; dcb = allDCBs; dcb_printf(pdcb, " %-10s | %-26s | %-20s | %s\n", "DCB", "State", "Service", "Remote"); - dcb_printf(pdcb, "---------------------------------------------------------------------------\n"); + dcb_printf(pdcb, "------------+----------------------------+----------------------+----------\n"); while (dcb) { dcb_printf(pdcb, " %10p | %-26s | %-20s | %s\n", dcb, gw_dcb_state2string(dcb->state), - (dcb->service ? dcb->service->name : ""), + (dcb->session->service ? + dcb->session->service->name : ""), (dcb->remote ? dcb->remote : "")); - dcb = dcb->next; + dcb = dcb->next; } + dcb_printf(pdcb, "------------+----------------------------+----------------------+----------\n"); spinlock_release(&dcbspin); } diff --git a/server/core/filter.c b/server/core/filter.c index a3db72e3b..bf9eb4228 100644 --- a/server/core/filter.c +++ b/server/core/filter.c @@ -209,7 +209,7 @@ int i; { dcb_printf(dcb, "%-18s | %-15s | Options\n", "Filter", "Module"); - dcb_printf(dcb, "-------------------------------------------------------------------------------\n"); + dcb_printf(dcb, "--------------------+-----------------+----------------------------------------\n"); } while (ptr) { @@ -220,6 +220,8 @@ int i; dcb_printf(dcb, "\n"); ptr = ptr->next; } + if (allFilters) + dcb_printf(dcb, "--------------------+-----------------+----------------------------------------\n"); spinlock_release(&filter_spin); } diff --git a/server/core/load_utils.c b/server/core/load_utils.c index 3ace0f91f..188535375 100644 --- a/server/core/load_utils.c +++ b/server/core/load_utils.c @@ -360,7 +360,7 @@ dprintAllModules(DCB *dcb) MODULES *ptr = registered; dcb_printf(dcb, "%-15s | %-11s | Version | API | Status\n", "Module Name", "Module Type"); - dcb_printf(dcb, "--------------------------------------------------------------------------\n"); + dcb_printf(dcb, "----------------+-------------+---------+-------+-------------------------\n"); while (ptr) { dcb_printf(dcb, "%-15s | %-11s | %-7s ", ptr->module, ptr->type, ptr->version); @@ -380,4 +380,5 @@ MODULES *ptr = registered; dcb_printf(dcb, "\n"); ptr = ptr->next; } + dcb_printf(dcb, "----------------+-------------+---------+-------+-------------------------\n"); } diff --git a/server/core/server.c b/server/core/server.c index 42a60caea..0a13b7751 100644 --- a/server/core/server.c +++ b/server/core/server.c @@ -314,7 +314,7 @@ char *stat; { dcb_printf(dcb, "%-18s | %-15s | Port | %-18s | Connections\n", "Server", "Address", "Status"); - dcb_printf(dcb, "-------------------------------------------------------------------------------\n"); + dcb_printf(dcb, "-------------------+-----------------+-------+--------------------+------------\n"); } while (ptr) { @@ -326,6 +326,8 @@ char *stat; free(stat); ptr = ptr->next; } + if (allServers) + dcb_printf(dcb, "-------------------+-----------------+-------+--------------------+------------\n"); spinlock_release(&server_spin); } diff --git a/server/core/service.c b/server/core/service.c index d963987d5..f7c626a34 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -828,7 +828,7 @@ SERVICE *ptr; { dcb_printf(dcb, "%-25s | %-20s | #Users | Total Sessions\n", "Service Name", "Router Module"); - dcb_printf(dcb, "--------------------------------------------------------------------------\n"); + dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n"); } while (ptr) { @@ -837,6 +837,8 @@ SERVICE *ptr; ptr->stats.n_current, ptr->stats.n_sessions); ptr = ptr->next; } + if (allServices) + dcb_printf(dcb, "--------------------------+----------------------+--------+---------------\n"); spinlock_release(&service_spin); } @@ -857,7 +859,7 @@ SERV_PROTOCOL *lptr; { dcb_printf(dcb, "%-20s | %-18s | %-15s | Port | State\n", "Service Name", "Protocol Module", "Address"); - dcb_printf(dcb, "---------------------------------------------------------------------------\n"); + dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+------\n"); } while (ptr) { @@ -875,6 +877,8 @@ SERV_PROTOCOL *lptr; } ptr = ptr->next; } + if (allServices) + dcb_printf(dcb, "---------------------+--------------------+-----------------+-------+------\n"); spinlock_release(&service_spin); } diff --git a/server/core/session.c b/server/core/session.c index cb392258c..bd24cde9a 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -538,7 +538,7 @@ SESSION *ptr; if (ptr) { dcb_printf(dcb, "Session | Client | State\n"); - dcb_printf(dcb, "------------------------------------------\n"); + dcb_printf(dcb, "-----------------+-----------------+----------------\n"); } while (ptr) { @@ -548,6 +548,8 @@ SESSION *ptr; session_state(ptr->state)); ptr = ptr->next; } + if (allSessions) + dcb_printf(dcb, "-----------------+-----------------+----------------\n"); spinlock_release(&session_spin); } diff --git a/server/modules/include/maxscaled.h b/server/modules/include/maxscaled.h new file mode 100644 index 000000000..3733772d8 --- /dev/null +++ b/server/modules/include/maxscaled.h @@ -0,0 +1,46 @@ +#ifndef _MAXSCALED_H +#define _MAXSCALED_H +/* + * This file is distributed as part of MaxScale. 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 2014 + */ + +/** + * @file maxscaled.h The maxscaled protocol module header file + * + * @verbatim + * Revision History + * + * Date Who Description + * 13/06/14 Mark Riddoch Initial implementation + * + * @endverbatim + */ +#include + +/** + * The telnetd specific protocol structure to put in the DCB. + */ +typedef struct maxscaled { + int state; /**< The connection state */ + char *username; /**< The login name of the user */ +} MAXSCALED; + +#define MAXSCALED_STATE_LOGIN 1 /**< Issued login prompt */ +#define MAXSCALED_STATE_PASSWD 2 /**< Issued password prompt */ +#define MAXSCALED_STATE_DATA 3 /**< User logged in */ + +#endif diff --git a/server/modules/protocol/Makefile b/server/modules/protocol/Makefile index 1d98f76db..789cd9b78 100644 --- a/server/modules/protocol/Makefile +++ b/server/modules/protocol/Makefile @@ -22,6 +22,7 @@ # headers so that liblog_manager.so can # be linked in. # 09/07/2013 Massimiliano Pinto Added the HTTPD protocol module +# 13/06/2014 Mark Riddoch Added thr MaxScale protocol module # include ../../../build_gateway.inc @@ -45,10 +46,12 @@ TELNETDSRCS=telnetd.c TELNETDOBJ=$(TELNETDSRCS:.c=.o) HTTPDSRCS=httpd.c HTTPDOBJ=$(HTTPDSRCS:.c=.o) +MAXSCALEDSRCS=maxscaled.c +MAXSCALEDOBJ=$(MAXSCALEDSRCS:.c=.o) SRCS=$(MYSQLCLIENTSRCS) $(MYSQLBACKENDSRCS) $(TELNETDSRCS) $(HTTPDSRCS) OBJ=$(SRCS:.c=.o) LIBS=$(UTILSPATH)/skygw_utils.o -MODULES=libMySQLClient.so libMySQLBackend.so libtelnetd.so libHTTPD.so +MODULES=libMySQLClient.so libMySQLBackend.so libtelnetd.so libHTTPD.so libmaxscaled.so all: $(MODULES) @@ -64,6 +67,9 @@ libtelnetd.so: $(TELNETDOBJ) libHTTPD.so: $(HTTPDOBJ) $(CC) $(LDFLAGS) $(HTTPDOBJ) $(LIBS) -o $@ +libmaxscaled.so: $(MAXSCALEDOBJ) + $(CC) $(LDFLAGS) $(MAXSCALEDOBJ) $(LIBS) -lcrypt -o $@ + .c.o: $(CC) $(CFLAGS) $< -o $@ diff --git a/server/modules/protocol/maxscaled.c b/server/modules/protocol/maxscaled.c new file mode 100644 index 000000000..f26a3d17a --- /dev/null +++ b/server/modules/protocol/maxscaled.c @@ -0,0 +1,383 @@ +/* + * This file is distributed as part of MaxScale. 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 2014 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_INFO info = { + MODULE_API_PROTOCOL, + MODULE_ALPHA_RELEASE, + GWPROTOCOL_VERSION, + "A maxscale protocol for the administration interface" +}; + +extern int lm_enabled_logfiles_bitmask; + +/** + * @file maxscaled.c - MaxScale administration protocol + * + * + * @verbatim + * Revision History + * Date Who Description + * 13/06/2014 Mark Riddoch Initial implementation + * + * @endverbatim + */ + +static char *version_str = "V1.0.0"; + +static int maxscaled_read_event(DCB* dcb); +static int maxscaled_write_event(DCB *dcb); +static int maxscaled_write(DCB *dcb, GWBUF *queue); +static int maxscaled_error(DCB *dcb); +static int maxscaled_hangup(DCB *dcb); +static int maxscaled_accept(DCB *dcb); +static int maxscaled_close(DCB *dcb); +static int maxscaled_listen(DCB *dcb, char *config); + +/** + * The "module object" for the maxscaled protocol module. + */ +static GWPROTOCOL MyObject = { + maxscaled_read_event, /**< Read - EPOLLIN handler */ + maxscaled_write, /**< Write - data from gateway */ + maxscaled_write_event, /**< WriteReady - EPOLLOUT handler */ + maxscaled_error, /**< Error - EPOLLERR handler */ + maxscaled_hangup, /**< HangUp - EPOLLHUP handler */ + maxscaled_accept, /**< Accept */ + NULL, /**< Connect */ + maxscaled_close, /**< Close */ + maxscaled_listen, /**< Create a listener */ + NULL, /**< Authentication */ + NULL /**< Session */ + }; + +static void maxscaled_command(DCB *, unsigned 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() +{ + LOGIF(LT, (skygw_log_write( + LOGFILE_TRACE, + "Initialise MaxScaled 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 maxscaled protocol module. + * + * @param dcb The descriptor control block + * @return + */ +static int +maxscaled_read_event(DCB* dcb) +{ +int n; +GWBUF *head = NULL; +SESSION *session = dcb->session; +MAXSCALED *maxscaled = (MAXSCALED *)dcb->protocol; +char *password; + + if ((n = dcb_read(dcb, &head)) != -1) + { + if (head) + { + unsigned char *ptr = GWBUF_DATA(head); + ptr = GWBUF_DATA(head); + if (GWBUF_LENGTH(head)) + { + switch (maxscaled->state) + { + case MAXSCALED_STATE_LOGIN: + maxscaled->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + maxscaled->state = MAXSCALED_STATE_PASSWD; + dcb_printf(dcb, "PASSWORD"); + gwbuf_consume(head, GWBUF_LENGTH(head)); + break; + case MAXSCALED_STATE_PASSWD: + password = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); + if (admin_verify(maxscaled->username, password)) + { + dcb_printf(dcb, "OK----"); + maxscaled->state = MAXSCALED_STATE_DATA; + } + else + { + dcb_printf(dcb, "FAILED"); + maxscaled->state = MAXSCALED_STATE_LOGIN; + free(maxscaled->username); + } + gwbuf_consume(head, GWBUF_LENGTH(head)); + free(password); + break; + case MAXSCALED_STATE_DATA: + SESSION_ROUTE_QUERY(session, head); + dcb_printf(dcb, "OK"); + break; + } + } + else + { + // Force the free of the buffer header + gwbuf_consume(head, 0); + } + } + } + return n; +} + +/** + * EPOLLOUT handler for the maxscaled protocol module. + * + * @param dcb The descriptor control block + * @return + */ +static int +maxscaled_write_event(DCB *dcb) +{ + return dcb_drain_writeq(dcb); +} + +/** + * Write routine for the maxscaled protocol module. + * + * Writes the content of the buffer queue to the socket + * observing the non-blocking principles of MaxScale. + * + * @param dcb Descriptor Control Block for the socket + * @param queue Linked list of buffes to write + */ +static int +maxscaled_write(DCB *dcb, GWBUF *queue) +{ + int rc; + rc = dcb_write(dcb, queue); + return rc; +} + +/** + * Handler for the EPOLLERR event. + * + * @param dcb The descriptor control block + */ +static int +maxscaled_error(DCB *dcb) +{ + return 0; +} + +/** + * Handler for the EPOLLHUP event. + * + * @param dcb The descriptor control block + */ +static int +maxscaled_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 + * @return The number of new connections created + */ +static int +maxscaled_accept(DCB *dcb) +{ +int n_connect = 0; + + while (1) + { + int so; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(struct sockaddr); + DCB *client_dcb; + MAXSCALED *maxscaled_pr = NULL; + + so = accept(dcb->fd, (struct sockaddr *)&addr, &addrlen); + + if (so == -1) + return n_connect; + else + { + atomic_add(&dcb->stats.n_accepts, 1); + client_dcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER); + + if (client_dcb == NULL) + + { + return n_connect; + } + client_dcb->fd = so; + client_dcb->remote = strdup(inet_ntoa(addr.sin_addr)); + memcpy(&client_dcb->func, &MyObject, sizeof(GWPROTOCOL)); + client_dcb->session = + session_alloc(dcb->session->service, client_dcb); + maxscaled_pr = (MAXSCALED *)malloc(sizeof(MAXSCALED)); + client_dcb->protocol = (void *)maxscaled_pr; + + if (maxscaled_pr == NULL) + { + dcb_add_to_zombieslist(client_dcb); + return n_connect; + } + + if (poll_add_dcb(client_dcb) == -1) + { + dcb_add_to_zombieslist(dcb); + return n_connect; + } + n_connect++; + maxscaled_pr->state = MAXSCALED_STATE_LOGIN; + maxscaled_pr->username = NULL; + dcb_printf(client_dcb, "USER"); + } + } + 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 +maxscaled_close(DCB *dcb) +{ +MAXSCALED *maxscaled = dcb->protocol; + + if (maxscaled && maxscaled->username) + free(maxscaled->username); + + dcb_close(dcb); + return 0; +} + +/** + * Maxscale daemon listener entry point + * + * @param listener The Listener DCB + * @param config Configuration (ip:port) + */ +static int +maxscaled_listen(DCB *listener, char *config) +{ +struct sockaddr_in addr; +int one = 1; +int rc; + + memcpy(&listener->func, &MyObject, sizeof(GWPROTOCOL)); + + if (!parse_bindconfig(config, 6033, &addr)) + return 0; + + + 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; + } + + rc = listen(listener->fd, SOMAXCONN); + + if (rc == 0) { + LOGIF(LD, (skygw_log_write( + LOGFILE_DEBUG, + "Listening maxscale connections at %s\n", + config))); + } else { + int eno = errno; + errno = 0; + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Failed to start listening for maxscale admin connections " + "due error %d, %s\n\n", + eno, + strerror(eno)))); + return 0; + } + + + if (poll_add_dcb(listener) == -1) + { + return 0; + } + return 1; +} diff --git a/server/modules/routing/Makefile b/server/modules/routing/Makefile index 1e50f3455..8019513c7 100644 --- a/server/modules/routing/Makefile +++ b/server/modules/routing/Makefile @@ -42,10 +42,12 @@ READCONSRCS=readconnroute.c READCONOBJ=$(READCONSRCS:.c=.o) DEBUGCLISRCS=debugcli.c debugcmd.c DEBUGCLIOBJ=$(DEBUGCLISRCS:.c=.o) -SRCS=$(TESTSRCS) $(READCONSRCS) $(DEBUGCLISRCS) +CLISRCS=cli.c debugcmd.c +CLIOBJ=$(CLISRCS:.c=.o) +SRCS=$(TESTSRCS) $(READCONSRCS) $(DEBUGCLISRCS) cli.c OBJ=$(SRCS:.c=.o) LIBS=$(UTILSPATH)/skygw_utils.o -lssl -llog_manager -MODULES= libdebugcli.so libreadconnroute.so libtestroute.so +MODULES= libdebugcli.so libreadconnroute.so libtestroute.so libcli.so all: $(MODULES) @@ -59,6 +61,9 @@ libreadconnroute.so: $(READCONOBJ) libdebugcli.so: $(DEBUGCLIOBJ) $(CC) $(LDFLAGS) $(DEBUGCLIOBJ) $(LIBS) -o $@ +libcli.so: $(CLIOBJ) + $(CC) $(LDFLAGS) $(CLIOBJ) $(LIBS) -o $@ + libreadwritesplit.so: # (cd readwritesplit; touch depend.mk ; make; cp $@ ..) diff --git a/server/modules/routing/cli.c b/server/modules/routing/cli.c new file mode 100644 index 000000000..9abfa5377 --- /dev/null +++ b/server/modules/routing/cli.c @@ -0,0 +1,297 @@ +/* + * This file is distributed as part of MaxScale. 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 2014 + */ + +/** + * @file cli.c - A "routing module" that in fact merely gives access + * to a command line interface + * + * @verbatim + * Revision History + * + * Date Who Description + * 18/06/13 Mark Riddoch Initial implementation + * 13/06/14 Mark Riddoch Creted from the debugcli + * + * @endverbatim + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MODULE_INFO info = { + MODULE_API_ROUTER, + MODULE_ALPHA_RELEASE, + ROUTER_VERSION, + "The admin user interface" +}; + +extern int lm_enabled_logfiles_bitmask; + +static char *version_str = "V1.0.0"; + +/* The router entry points */ +static ROUTER *createInstance(SERVICE *service, char **options); +static void *newSession(ROUTER *instance, SESSION *session); +static void closeSession(ROUTER *instance, void *router_session); +static void freeSession(ROUTER *instance, void *router_session); +static int execute(ROUTER *instance, void *router_session, GWBUF *queue); +static void diagnostics(ROUTER *instance, DCB *dcb); +static uint8_t getCapabilities (ROUTER* inst, void* router_session); + +/** The module object definition */ +static ROUTER_OBJECT MyObject = { + createInstance, + newSession, + closeSession, + freeSession, + execute, + diagnostics, + NULL, + NULL, + getCapabilities +}; + +extern int execute_cmd(CLI_SESSION *cli); + +static SPINLOCK instlock; +static CLI_INSTANCE *instances; + +/** + * 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() +{ + LOGIF(LM, (skygw_log_write( + LOGFILE_MESSAGE, + "Initialise CLI router module %s.\n", + version_str))); + spinlock_init(&instlock); + instances = NULL; +} + +/** + * 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 + */ +ROUTER_OBJECT * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Create an instance of the router for a particular service + * within the gateway. + * + * @param service The service this router is being create for + * @param options Any array of options for the query router + * + * @return The instance data for this new instance + */ +static ROUTER * +createInstance(SERVICE *service, char **options) +{ +CLI_INSTANCE *inst; +int i; + + if ((inst = malloc(sizeof(CLI_INSTANCE))) == NULL) + return NULL; + + inst->service = service; + spinlock_init(&inst->lock); + inst->sessions = NULL; + inst->mode = CLIM_USER; + + if (options) + { + for (i = 0; options[i]; i++) + { + { + LOGIF(LE, (skygw_log_write( + LOGFILE_ERROR, + "Unknown option for CLI '%s'\n", + options[i]))); + } + } + } + + /* + * We have completed the creation of the instance data, so now + * insert this router instance into the linked list of routers + * that have been created with this module. + */ + spinlock_acquire(&instlock); + inst->next = instances; + instances = inst; + spinlock_release(&instlock); + + return (ROUTER *)inst; +} + +/** + * Associate a new session with this instance of the router. + * + * @param instance The router instance data + * @param session The session itself + * @return Session specific data for this session + */ +static void * +newSession(ROUTER *instance, SESSION *session) +{ +CLI_INSTANCE *inst = (CLI_INSTANCE *)instance; +CLI_SESSION *client; + + if ((client = (CLI_SESSION *)malloc(sizeof(CLI_SESSION))) == NULL) + { + return NULL; + } + client->session = session; + + memset(client->cmdbuf, 0, 80); + + spinlock_acquire(&inst->lock); + client->next = inst->sessions; + inst->sessions = client; + spinlock_release(&inst->lock); + + session->state = SESSION_STATE_READY; + client->mode = inst->mode; + + return (void *)client; +} + +/** + * Close a session with the router, this is the mechanism + * by which a router may cleanup data structure etc. + * + * @param instance The router instance data + * @param router_session The session being closed + */ +static void +closeSession(ROUTER *instance, void *router_session) +{ +CLI_INSTANCE *inst = (CLI_INSTANCE *)instance; +CLI_SESSION *session = (CLI_SESSION *)router_session; + + + spinlock_acquire(&inst->lock); + if (inst->sessions == session) + inst->sessions = session->next; + else + { + CLI_SESSION *ptr = inst->sessions; + while (ptr && ptr->next != session) + ptr = ptr->next; + if (ptr) + ptr->next = session->next; + } + spinlock_release(&inst->lock); + /** + * Router session is freed in session.c:session_close, when session who + * owns it, is freed. + */ +} + +/** + * Free a debugcli session + * + * @param router_instance The router session + * @param router_client_session The router session as returned from newSession + */ +static void freeSession( + ROUTER* router_instance, + void* router_client_session) +{ + free(router_client_session); + return; +} + +/** + * We have data from the client, we must route it to the backend. + * This is simply a case of sending it to the connection that was + * chosen when we started the client session. + * + * @param instance The router instance + * @param router_session The router session returned from the newSession call + * @param queue The queue of data buffers to route + * @return The number of bytes sent + */ +static int +execute(ROUTER *instance, void *router_session, GWBUF *queue) +{ +CLI_SESSION *session = (CLI_SESSION *)router_session; + + /* Extract the characters */ + while (queue) + { + strncat(session->cmdbuf, GWBUF_DATA(queue), GWBUF_LENGTH(queue)); + queue = gwbuf_consume(queue, GWBUF_LENGTH(queue)); + } + + execute_cmd(session); + return 1; +} + +/** + * Display router diagnostics + * + * @param instance Instance of the router + * @param dcb DCB to send diagnostics to + */ +static void +diagnostics(ROUTER *instance, DCB *dcb) +{ + return; /* Nothing to do currently */ +} + +static uint8_t getCapabilities( + ROUTER* inst, + void* router_session) +{ + return 0; +} diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index 4bd19e957..fb638d541 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -601,6 +601,8 @@ char *ptr, *lptr; if (args[0] == NULL || *args[0] == 0) return 1; + for (i = 0; args[i] && *args[i]; i++) + ; argc = i - 2; /* The number of extra arguments to commands */