OpenSolaris_b135/cmd/vntsd/cmd.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Vntsd handles two types of special commands, one is telnet
 * commands and another is vntsd special commands.
 * telnet commands supported are:
 * WILL
 * WONT
 * DO
 * DONT
 *  TEL_ECHO
 *  SUPRESS
 *  LINEMODE
 * BRK
 * AYT
 * HT
 * NOP
 *
 * Vntsd special commands are:
 *  Send break			(~#)
 *  Send alternate break	(~^B)
 *  Exit			(~.)
 *  Force write access		(~w)
 *  Console next		(~n)
 *  Console previous		(~p)
 *  Help			(~?)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <thread.h>
#include <ctype.h>
#include <sys/termio.h>
#include <libintl.h>
#include <syslog.h>
#include "vntsd.h"
#include "chars.h"

char vntsd_eol[] = { CR, LF, 0};

typedef	int	    (*e_func_t)(vntsd_client_t *clientp);
/* structure for daemon special cmd */
typedef struct {
	char e_char;				/* char to match on */
	char *e_help;				/* help string */
	e_func_t e_func;			/* command */
} esctable_t;

/* genbrk() -  send a break to vcc driver */
static int
genbrk(vntsd_client_t *clientp)
{

	vntsd_cons_t *consp;

	assert(clientp);
	assert(clientp->cons);

	consp = clientp->cons;
	D1(stderr, "t@%d genbrk fd=%d sockfd %d\n", thr_self(),
	    consp->vcc_fd, clientp->sockfd);

	assert(consp->clientpq != NULL);
	if (consp->clientpq->handle != clientp) {
		/* reader */
		return (vntsd_write_line(clientp,
		    gettext(VNTSD_NO_WRITE_ACCESS_MSG)));
	}

	/* writer */
	if (ioctl(consp->vcc_fd, TCSBRK, NULL)) {
		return (VNTSD_ERR_VCC_IOCTL);
	}

	return (VNTSD_STATUS_CONTINUE);
}


/* genaltbrk() - handle the alternate break sequence */
static int
genaltbrk(vntsd_client_t *clientp)
{
	vntsd_cons_t *consp;
	char brkseq[2] = { '~', CNTRL('B')};

	assert(clientp);
	assert(clientp->cons);

	consp = clientp->cons;
	D1(stderr, "t@%d genaltbrk fd=%d sockfd %d\n", thr_self(),
	    consp->vcc_fd, clientp->sockfd);

	assert(consp->clientpq != NULL);
	if (consp->clientpq->handle != clientp) {
		/* reader */
		return (vntsd_write_line(clientp,
		    gettext(VNTSD_NO_WRITE_ACCESS_MSG)));
	}

	/*
	 * Unlike the genbrk() function we will just forward the break sequence
	 * on to vcc and subsequently the underlying console driver. This will
	 * involve sending the characters '~' and CNTRL('B').
	 */
	if ((vntsd_write_fd(clientp->cons->vcc_fd, brkseq, sizeof (brkseq))) ==
	    VNTSD_SUCCESS)
		return (VNTSD_STATUS_CONTINUE);
	else
		return (VNTSD_STATUS_VCC_IO_ERR);
}

/*
 * console_forward()  - cycle client to the next console
 * in the group queue.
 */
static int
console_forward(vntsd_client_t *clientp)
{
	/* forward when there are mutiple consoles in the group */
	if (clientp->cons->group->num_cons > 1)
		return (VNTSD_STATUS_MOV_CONS_FORWARD);

	return (VNTSD_STATUS_CONTINUE);

}

/*
 * console_backward()  - cycle client to the previous
 * console in the group queue.
 */
static int
console_backward(vntsd_client_t *clientp)
{
	/* backward when there are mutiple consoles in the group */
	if (clientp->cons->group->num_cons > 1)
		return (VNTSD_STATUS_MOV_CONS_BACKWARD);

	return (VNTSD_STATUS_CONTINUE);

}

/* acquire_write() - acquire write access to a console. */
static int
acquire_write(vntsd_client_t *clientp)
{
	int	rv;
	int	yes_no = 1;
	vntsd_cons_t *consp;

	assert(clientp);
	consp = clientp->cons;
	assert(consp);

	if (consp->clientpq->handle == clientp) {
		/* client is a  writer */
		if ((rv = vntsd_write_line(clientp,
		    gettext("You have write permission"))) !=
		    VNTSD_SUCCESS) {
			return (rv);

		}
		return (VNTSD_STATUS_CONTINUE);
	}

	/* message to client */
	if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN))
	    != VNTSD_SUCCESS) {
		return (rv);
	}

	/*
	 * TRANSLATION_NOTE
	 * The following string should be formatted to fit on multiple lines
	 * assuming a line width of at most 78 characters. There must be no
	 * trailing newline.
	 */
	if ((rv = vntsd_write_lines(clientp,
	    gettext("Warning: another user currently "
	    "has write permission\nto this console and forcibly removing "
	    "him/her will terminate\nany current write action and all work "
	    "will be lost."))) != VNTSD_SUCCESS) {
		return (rv);
	}

	/* get client yes no */
	if ((rv = vntsd_write_client(clientp, vntsd_eol,
	    VNTSD_EOL_LEN)) != VNTSD_SUCCESS) {
		return (rv);
	}

	if ((rv = vntsd_get_yes_no(clientp,
	    gettext("Would you like to continue?"),
	    &yes_no)) != VNTSD_SUCCESS) {
		return (rv);
	}

	if (yes_no == B_FALSE) {
		/* client change mind no need to acquire  write access */
		return (VNTSD_STATUS_CONTINUE);
	}

	return (VNTSD_STATUS_ACQUIRE_WRITER);
}

/* client_exit()  - disconnect client from the console. */
static int
client_exit(void)
{
	return (VNTSD_STATUS_RESELECT_CONS);
}

static int daemon_cmd_help(vntsd_client_t *clientp);

/* table for daemon commands */

static esctable_t  etable[] = {

	/* send a break to vcc */
	{'#', "Send break",  genbrk},

	/* alternate break sequence */
	{CNTRL('B'), "Send alternate break", genaltbrk},

	/* exit */
	{'.', "Exit from this console",  (e_func_t)client_exit},

	/* acquire write access */
	{'w', "Force write access", acquire_write},

	/* connect to next console in queue */
	{'n', "Console next", (e_func_t)console_forward},

	/* connect to previous console in queue */
	{'p', "Console previous", (e_func_t)console_backward},

	/* help must be next to last */
	{'?', "Help", daemon_cmd_help},

	/* table terminator */
	{0, 0, 0}
};

void
vntsd_init_esctable_msgs(void)
{
	esctable_t  *p;

	for (p = etable; p->e_char != '\0'; p++) {
		p->e_help = gettext(p->e_help);
	}
}

/* daemon_cmd_help() - print help. */
static int
daemon_cmd_help(vntsd_client_t *clientp)
{
	esctable_t  *p;
	int	    rv;
	char	    buf[VNTSD_LINE_LEN];

	if ((rv = vntsd_write_client(clientp, vntsd_eol,
	    VNTSD_EOL_LEN)) != VNTSD_SUCCESS) {
		return (rv);
	}

	/*
	 * TRANSLATION_NOTE
	 * VNTSD is the name of the VNTS daemon and should not be translated.
	 */
	if ((rv = vntsd_write_line(clientp, gettext("VNTSD commands"))) !=
	    VNTSD_SUCCESS) {
		return (rv);
	}

	for (p = etable; p->e_char; p++) {

		if (p->e_char == CNTRL('B')) {
			(void) snprintf(buf, sizeof (buf), "~^B --%s",
			    p->e_help);
		} else {
			(void) snprintf(buf, sizeof (buf),
			    "~%c --%s", p->e_char, p->e_help);
		}

		if ((rv = vntsd_write_line(clientp, buf)) != VNTSD_SUCCESS) {
			return (rv);
		}
	}

	return (VNTSD_STATUS_CONTINUE);
}

/* exit from daemon command */
static int
exit_daemon_cmd(vntsd_client_t *clientp, int rv)
{
	(void) mutex_lock(&clientp->lock);
	clientp->status &= ~VNTSD_CLIENT_DISABLE_DAEMON_CMD;
	(void) mutex_unlock(&clientp->lock);
	return (rv);
}

/*
 * vntsd_process_daemon_cmd() - special commands
 * "<RET>~"  vntsd daemon commands
 * "<RET>~~" enter '~' character
 */
int
vntsd_process_daemon_cmd(vntsd_client_t *clientp, char c)
{
	esctable_t *p;
	int	    rv;
	char	    prev_char;

	prev_char = clientp->prev_char;

	if (c != VNTSD_DAEMON_CMD || (prev_char != 0 && prev_char != CR)) {
		/* not a daemon command */
		return (VNTSD_SUCCESS);
	}

	if (clientp->status & VNTSD_CLIENT_DISABLE_DAEMON_CMD) {
		return (VNTSD_STATUS_CONTINUE);
	}

	/* no reentry to process_daemon_cmd */
	(void) mutex_lock(&clientp->lock);
	clientp->status |= VNTSD_CLIENT_DISABLE_DAEMON_CMD;
	(void) mutex_unlock(&clientp->lock);

	D3(stderr, "t@%d process_daemon_cmd %d %d \n", thr_self(),
	    clientp->cons->vcc_fd, clientp->sockfd);

	/* read in command */
	if ((rv = vntsd_read_char(clientp, &c)) != VNTSD_SUCCESS) {
		return (exit_daemon_cmd(clientp, rv));
	}

	if (c == VNTSD_DAEMON_CMD) {
		/*
		 * received another '~'
		 * a user types '~~' to get '~'
		 */
		(void) mutex_lock(&clientp->lock);
		clientp->status &= ~VNTSD_CLIENT_DISABLE_DAEMON_CMD;
		(void) mutex_unlock(&clientp->lock);
		return (VNTSD_SUCCESS);
	}

	for (p = etable; p->e_char; p++) {
		if (p->e_char == c) {
			/* found match */
			assert(p->e_func);
			rv = (*p->e_func)(clientp);
			return (exit_daemon_cmd(clientp, rv));
		}
	}

	/* no match, print out the help */
	p--;
	assert(p->e_char == '?');
	rv = (*p->e_func)(clientp);

	return (exit_daemon_cmd(clientp, rv));

}

/* vntsd_set_telnet_options() - change  telnet client to  character mode. */
int
vntsd_set_telnet_options(int fd)
{
	/* set client telnet options */
	uint8_t buf[] = {IAC, DONT, LINEMODE, IAC, WILL, SUPRESS, IAC, WILL,
		TEL_ECHO, IAC, DONT, TERM_TYPE, IAC, DONT, TERM_SP,
		IAC, DONT, STATUS, IAC, DONT, FC, IAC, DONT, TM, IAC, DONT, ENV,
		IAC, DONT, WIN_SIZE};

	return (vntsd_write_fd(fd, (char *)buf, 30));
}

/*  vntsd_telnet_cmd() process telnet commands */
int
vntsd_telnet_cmd(vntsd_client_t *clientp, char c)
{
	uint8_t	buf[4];
	char	cmd;
	int	rv = VNTSD_STATUS_CONTINUE;

	bzero(buf, 4);

	if ((uint8_t)c != IAC) {
		/* not telnet cmd */
		return (VNTSD_SUCCESS);
	}

	if ((rv = vntsd_read_char(clientp, &cmd)) != VNTSD_SUCCESS) {
		return (rv);
	}

	if ((uint8_t)cmd == WILL || (uint8_t)cmd == WONT ||
	    (uint8_t)cmd == DO || (uint8_t)cmd == DONT) {
		if ((rv = vntsd_read_char(clientp, &c)) != VNTSD_SUCCESS) {
			return (rv);
		}
	}


	switch ((uint8_t)cmd) {

	case WILL:

		switch ((uint8_t)c) {
		case TEL_ECHO:
		case SUPRESS:
		case LINEMODE:
			break;
		default:
			syslog(LOG_ERR, "not support telnet WILL %x\n", c);
			break;
		}
		break;

	case  WONT:

		switch ((uint8_t)c) {
		case TEL_ECHO:
		case SUPRESS:
		case LINEMODE:
		default:
			syslog(LOG_ERR, "not support telnet WONT %x\n", c);
			break;
		}
		break;

	case DO:
	case DONT:

		buf[0] = IAC;
		buf[1] = WILL;
		buf[2] = c;
		rv = vntsd_write_client(clientp, (char *)buf, 3);

		break;

	case BRK:

		/* send break to vcc */
		rv = genbrk(clientp);
		break;

	case IP:

		break;

	case AYT: {
			static char aytresp[] = "vntsd here";

			rv = vntsd_write_client(clientp, aytresp,
			    sizeof (aytresp) - 1);
			break;
		}

	case HT:
	case NOP:
		return (VNTSD_STATUS_CONTINUE);

	default:
		syslog(LOG_ERR, "not support telnet ctrl %2.2x\n", 0xff & cmd);
		break;
	}

	if (rv == VNTSD_SUCCESS) {
		return (VNTSD_STATUS_CONTINUE);
	} else {
		return (rv);
	}
}


/*
 * vntsd_ctrl_cmd()   - control keys
 * read and write suspend are supported.
 */
int
vntsd_ctrl_cmd(vntsd_client_t *clientp, char c)
{
	int	cmd;

	D3(stderr, "t@%d vntsd_ctrl_cmd%d %d\n", thr_self(),
	    clientp->cons->vcc_fd, clientp->sockfd);

	if ((c != START) && (c != STOP)) {
		/* not a supported control command */
		return (VNTSD_SUCCESS);
	}

	if (c == START) {
		D3(stderr, "t@%d client restart\n", thr_self());

		/* send resume read */
		cmd = 1;

		if (ioctl(clientp->cons->vcc_fd, TCXONC, &cmd)) {
			return (VNTSD_STATUS_VCC_IO_ERR);
		}

	}

	if (c == STOP) {
		D3(stderr, "t@%d client suspend\n", thr_self());

		/* send suspend read */
		cmd = 0;

		if (ioctl(clientp->cons->vcc_fd, TCXONC, &cmd)) {
			return (VNTSD_STATUS_VCC_IO_ERR);
		}

	}

	return (VNTSD_STATUS_CONTINUE);
}