450 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			450 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * This file is distributed as part of the MariaDB Corporation 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 MariaDB Corporation Ab 2013-2014
 | 
						|
 */
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <unistd.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>
 | 
						|
#include <telnetd.h>
 | 
						|
#include <adminusers.h>
 | 
						|
#include <skygw_utils.h>
 | 
						|
#include <log_manager.h>
 | 
						|
#include <modinfo.h>
 | 
						|
 | 
						|
MODULE_INFO info = {
 | 
						|
	MODULE_API_PROTOCOL,
 | 
						|
	MODULE_GA,
 | 
						|
	GWPROTOCOL_VERSION,
 | 
						|
	"A telnet deamon protocol for simple administration interface"
 | 
						|
};
 | 
						|
 | 
						|
/** Defined in log_manager.cc */
 | 
						|
extern int            lm_enabled_logfiles_bitmask;
 | 
						|
extern size_t         log_ses_count[];
 | 
						|
extern __thread log_info_t tls_log_info;
 | 
						|
 | 
						|
/**
 | 
						|
 * @file telnetd.c - telnet daemon protocol module
 | 
						|
 *
 | 
						|
 * The telnetd protocol module is intended as a mechanism to allow connections
 | 
						|
 * into the gateway for the purpsoe of accessing debugging information within
 | 
						|
 * the gateway rather than a protocol to be used to send queries to backend
 | 
						|
 * 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.
 | 
						|
 *
 | 
						|
 * @verbatim
 | 
						|
 * Revision History
 | 
						|
 * Date		Who			Description
 | 
						|
 * 17/06/2013	Mark Riddoch		Initial version
 | 
						|
 * 17/07/2013	Mark Riddoch		Addition of login phase
 | 
						|
 *
 | 
						|
 * @endverbatim
 | 
						|
 */
 | 
						|
 | 
						|
static char *version_str = "V1.0.1";
 | 
						|
 | 
						|
static int telnetd_read_event(DCB* dcb);
 | 
						|
static int telnetd_write_event(DCB *dcb);
 | 
						|
static int telnetd_write(DCB *dcb, GWBUF *queue);
 | 
						|
static int telnetd_error(DCB *dcb);
 | 
						|
static int telnetd_hangup(DCB *dcb);
 | 
						|
static int telnetd_accept(DCB *dcb);
 | 
						|
static int telnetd_close(DCB *dcb);
 | 
						|
static int telnetd_listen(DCB *dcb, char *config);
 | 
						|
 | 
						|
/**
 | 
						|
 * The "module object" for the telnetd protocol module.
 | 
						|
 */
 | 
						|
static GWPROTOCOL MyObject = { 
 | 
						|
	telnetd_read_event,		/**< Read - EPOLLIN handler	 */
 | 
						|
	telnetd_write,			/**< Write - data from gateway	 */
 | 
						|
	telnetd_write_event,		/**< WriteReady - EPOLLOUT handler */
 | 
						|
	telnetd_error,			/**< Error - EPOLLERR handler	 */
 | 
						|
	telnetd_hangup,			/**< HangUp - EPOLLHUP handler	 */
 | 
						|
	telnetd_accept,			/**< Accept			 */
 | 
						|
	NULL,				/**< Connect			 */
 | 
						|
	telnetd_close,			/**< Close			 */
 | 
						|
	telnetd_listen,			/**< Create a listener		 */
 | 
						|
	NULL,				/**< Authentication		 */
 | 
						|
	NULL				/**< Session			 */
 | 
						|
	};
 | 
						|
 | 
						|
static void 	telnetd_command(DCB *, unsigned char *cmd);
 | 
						|
static void 	telnetd_echo(DCB *dcb, int enable);
 | 
						|
 | 
						|
/**
 | 
						|
 * 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 Telnetd 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 telnetd protocol module.
 | 
						|
 *
 | 
						|
 * @param dcb	The descriptor control block
 | 
						|
 * @return
 | 
						|
 */
 | 
						|
static int
 | 
						|
telnetd_read_event(DCB* dcb)
 | 
						|
{
 | 
						|
int		n;
 | 
						|
GWBUF		*head = NULL;
 | 
						|
SESSION		*session = dcb->session;
 | 
						|
TELNETD		*telnetd = (TELNETD *)dcb->protocol;
 | 
						|
char		*password, *t;
 | 
						|
 | 
						|
	if ((n = dcb_read(dcb, &head)) != -1)
 | 
						|
	{
 | 
						|
		if (head)
 | 
						|
		{
 | 
						|
			unsigned char *ptr = GWBUF_DATA(head);
 | 
						|
			ptr = GWBUF_DATA(head);
 | 
						|
			while (GWBUF_LENGTH(head) && *ptr == TELNET_IAC)
 | 
						|
			{
 | 
						|
				telnetd_command(dcb, ptr + 1);
 | 
						|
				GWBUF_CONSUME(head, 3);
 | 
						|
				ptr = GWBUF_DATA(head);
 | 
						|
			}
 | 
						|
			if (GWBUF_LENGTH(head))
 | 
						|
			{
 | 
						|
				switch (telnetd->state)
 | 
						|
				{
 | 
						|
				case TELNETD_STATE_LOGIN:
 | 
						|
					telnetd->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head));
 | 
						|
					/* Strip the cr/lf from the username */
 | 
						|
				        t = strstr(telnetd->username, "\r\n");
 | 
						|
				        if (t)
 | 
						|
                				*t = 0;
 | 
						|
					telnetd->state = TELNETD_STATE_PASSWD;
 | 
						|
					dcb_printf(dcb, "Password: ");
 | 
						|
					telnetd_echo(dcb, 0);
 | 
						|
					gwbuf_consume(head, GWBUF_LENGTH(head));
 | 
						|
					break;
 | 
						|
				case TELNETD_STATE_PASSWD:
 | 
						|
					password = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head));
 | 
						|
					/* Strip the cr/lf from the username */
 | 
						|
				        t = strstr(password, "\r\n");
 | 
						|
				        if (t)
 | 
						|
                				*t = 0;
 | 
						|
					if (admin_verify(telnetd->username, password))
 | 
						|
					{
 | 
						|
						telnetd_echo(dcb, 1);
 | 
						|
						telnetd->state = TELNETD_STATE_DATA;
 | 
						|
						dcb_printf(dcb, "\n\nMaxScale> ");
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						dcb_printf(dcb, "\n\rLogin incorrect\n\rLogin: ");
 | 
						|
						telnetd_echo(dcb, 1);
 | 
						|
						telnetd->state = TELNETD_STATE_LOGIN;
 | 
						|
						free(telnetd->username);
 | 
						|
					}
 | 
						|
					gwbuf_consume(head, GWBUF_LENGTH(head));
 | 
						|
					free(password);
 | 
						|
					break;
 | 
						|
				case TELNETD_STATE_DATA:
 | 
						|
					SESSION_ROUTE_QUERY(session, head);
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				// Force the free of the buffer header
 | 
						|
				gwbuf_consume(head, 0);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return n;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * EPOLLOUT handler for the telnetd protocol module.
 | 
						|
 *
 | 
						|
 * @param dcb	The descriptor control block
 | 
						|
 * @return
 | 
						|
 */
 | 
						|
static int
 | 
						|
telnetd_write_event(DCB *dcb)
 | 
						|
{
 | 
						|
	return dcb_drain_writeq(dcb);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Write routine for the telnetd 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
 | 
						|
telnetd_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
 | 
						|
telnetd_error(DCB *dcb)
 | 
						|
{
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Handler for the EPOLLHUP event.
 | 
						|
 *
 | 
						|
 * @param dcb	The descriptor control block
 | 
						|
 */
 | 
						|
static int
 | 
						|
telnetd_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
 | 
						|
telnetd_accept(DCB *dcb)
 | 
						|
{
 | 
						|
int	n_connect = 0;
 | 
						|
 | 
						|
	while (1)
 | 
						|
	{
 | 
						|
		int			so;
 | 
						|
		struct sockaddr_in	addr;
 | 
						|
		socklen_t		addrlen = sizeof(struct sockaddr);
 | 
						|
		DCB			*client_dcb;
 | 
						|
                TELNETD*                telnetd_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)
 | 
						|
 | 
						|
			{
 | 
						|
				close(so);
 | 
						|
				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);
 | 
						|
                        telnetd_pr = (TELNETD *)malloc(sizeof(TELNETD));
 | 
						|
                        client_dcb->protocol = (void *)telnetd_pr;
 | 
						|
 | 
						|
                        if (telnetd_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++;
 | 
						|
			telnetd_pr->state = TELNETD_STATE_LOGIN;
 | 
						|
			telnetd_pr->username = NULL;
 | 
						|
			dcb_printf(client_dcb, "MaxScale login: ");
 | 
						|
		}
 | 
						|
	}
 | 
						|
	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
 | 
						|
telnetd_close(DCB *dcb)
 | 
						|
{
 | 
						|
TELNETD *telnetd = dcb->protocol;
 | 
						|
 | 
						|
	if (telnetd && telnetd->username)
 | 
						|
		free(telnetd->username);
 | 
						|
 | 
						|
        return 0;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Telnet daemon listener entry point
 | 
						|
 *
 | 
						|
 * @param	listener	The Listener DCB
 | 
						|
 * @param	config		Configuration (ip:port)
 | 
						|
 */
 | 
						|
static int
 | 
						|
telnetd_listen(DCB *listener, char *config)
 | 
						|
{
 | 
						|
struct sockaddr_in	addr;
 | 
						|
int			one = 1;
 | 
						|
int         rc;
 | 
						|
int			syseno = 0;
 | 
						|
 | 
						|
	memcpy(&listener->func, &MyObject, sizeof(GWPROTOCOL));
 | 
						|
 | 
						|
	if (!parse_bindconfig(config, 4442, &addr))
 | 
						|
		return 0;
 | 
						|
 | 
						|
 | 
						|
	if ((listener->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
 | 
						|
	{
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
        // socket options
 | 
						|
	syseno = setsockopt(listener->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one));
 | 
						|
	
 | 
						|
	if(syseno != 0){
 | 
						|
		LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,"Error: Failed to set socket options. Error %d: %s",errno,strerror(errno))));
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
        // 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(LM, (skygw_log_write_flush(LOGFILE_MESSAGE,"Listening telnet connections at %s", config)));
 | 
						|
        } else {
 | 
						|
            int eno = errno;
 | 
						|
            errno = 0;
 | 
						|
            fprintf(stderr,
 | 
						|
                    "\n* Failed to start listening telnet due error %d, %s\n\n",
 | 
						|
                    eno,
 | 
						|
                    strerror(eno));
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
 | 
						|
        
 | 
						|
        if (poll_add_dcb(listener) == -1)
 | 
						|
	{
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	return 1;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Telnet command implementation
 | 
						|
 *
 | 
						|
 * Called for each command in the telnet stream.
 | 
						|
 *
 | 
						|
 * Currently we do no command execution
 | 
						|
 *
 | 
						|
 * @param	dcb	The client DCB
 | 
						|
 * @param	cmd	The command stream
 | 
						|
 */
 | 
						|
static void
 | 
						|
telnetd_command(DCB *dcb, unsigned char *cmd)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Enable or disable telnet protocol echo
 | 
						|
 *
 | 
						|
 * @param dcb		DCB of the telnet connection
 | 
						|
 * @param enable	Enable or disable echo functionality
 | 
						|
 */
 | 
						|
static void
 | 
						|
telnetd_echo(DCB *dcb, int enable)
 | 
						|
{
 | 
						|
GWBUF	*gwbuf;
 | 
						|
char	*buf;
 | 
						|
 | 
						|
	if ((gwbuf = gwbuf_alloc(3)) == NULL)
 | 
						|
		return;
 | 
						|
	buf = GWBUF_DATA(gwbuf);
 | 
						|
	buf[0] = TELNET_IAC;
 | 
						|
	buf[1] = enable ? TELNET_WONT : TELNET_WILL;
 | 
						|
	buf[2] = TELNET_ECHO;
 | 
						|
	dcb_write(dcb, gwbuf);
 | 
						|
}
 |