1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26/*
27 * Vntsd handles two types of special commands, one is telnet
28 * commands and another is vntsd special commands.
29 * telnet commands supported are:
30 * WILL
31 * WONT
32 * DO
33 * DONT
34 *  TEL_ECHO
35 *  SUPRESS
36 *  LINEMODE
37 * BRK
38 * AYT
39 * HT
40 * NOP
41 *
42 * Vntsd special commands are:
43 *  Send break			(~#)
44 *  Send alternate break	(~^B)
45 *  Exit			(~.)
46 *  Force write access		(~w)
47 *  Console next		(~n)
48 *  Console previous		(~p)
49 *  Help			(~?)
50 */
51
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56#include <sys/types.h>
57#include <sys/socket.h>
58#include <netinet/in.h>
59#include <thread.h>
60#include <ctype.h>
61#include <sys/termio.h>
62#include <libintl.h>
63#include <syslog.h>
64#include "vntsd.h"
65#include "chars.h"
66
67char vntsd_eol[] = { CR, LF, 0};
68
69typedef	int	    (*e_func_t)(vntsd_client_t *clientp);
70/* structure for daemon special cmd */
71typedef struct {
72	char e_char;				/* char to match on */
73	char *e_help;				/* help string */
74	e_func_t e_func;			/* command */
75} esctable_t;
76
77/* genbrk() -  send a break to vcc driver */
78static int
79genbrk(vntsd_client_t *clientp)
80{
81
82	vntsd_cons_t *consp;
83
84	assert(clientp);
85	assert(clientp->cons);
86
87	consp = clientp->cons;
88	D1(stderr, "t@%d genbrk fd=%d sockfd %d\n", thr_self(),
89	    consp->vcc_fd, clientp->sockfd);
90
91	assert(consp->clientpq != NULL);
92	if (consp->clientpq->handle != clientp) {
93		/* reader */
94		return (vntsd_write_line(clientp,
95		    gettext(VNTSD_NO_WRITE_ACCESS_MSG)));
96	}
97
98	/* writer */
99	if (ioctl(consp->vcc_fd, TCSBRK, NULL)) {
100		return (VNTSD_ERR_VCC_IOCTL);
101	}
102
103	return (VNTSD_STATUS_CONTINUE);
104}
105
106
107/* genaltbrk() - handle the alternate break sequence */
108static int
109genaltbrk(vntsd_client_t *clientp)
110{
111	vntsd_cons_t *consp;
112	char brkseq[2] = { '~', CNTRL('B')};
113
114	assert(clientp);
115	assert(clientp->cons);
116
117	consp = clientp->cons;
118	D1(stderr, "t@%d genaltbrk fd=%d sockfd %d\n", thr_self(),
119	    consp->vcc_fd, clientp->sockfd);
120
121	assert(consp->clientpq != NULL);
122	if (consp->clientpq->handle != clientp) {
123		/* reader */
124		return (vntsd_write_line(clientp,
125		    gettext(VNTSD_NO_WRITE_ACCESS_MSG)));
126	}
127
128	/*
129	 * Unlike the genbrk() function we will just forward the break sequence
130	 * on to vcc and subsequently the underlying console driver. This will
131	 * involve sending the characters '~' and CNTRL('B').
132	 */
133	if ((vntsd_write_fd(clientp->cons->vcc_fd, brkseq, sizeof (brkseq))) ==
134	    VNTSD_SUCCESS)
135		return (VNTSD_STATUS_CONTINUE);
136	else
137		return (VNTSD_STATUS_VCC_IO_ERR);
138}
139
140/*
141 * console_forward()  - cycle client to the next console
142 * in the group queue.
143 */
144static int
145console_forward(vntsd_client_t *clientp)
146{
147	/* forward when there are mutiple consoles in the group */
148	if (clientp->cons->group->num_cons > 1)
149		return (VNTSD_STATUS_MOV_CONS_FORWARD);
150
151	return (VNTSD_STATUS_CONTINUE);
152
153}
154
155/*
156 * console_backward()  - cycle client to the previous
157 * console in the group queue.
158 */
159static int
160console_backward(vntsd_client_t *clientp)
161{
162	/* backward when there are mutiple consoles in the group */
163	if (clientp->cons->group->num_cons > 1)
164		return (VNTSD_STATUS_MOV_CONS_BACKWARD);
165
166	return (VNTSD_STATUS_CONTINUE);
167
168}
169
170/* acquire_write() - acquire write access to a console. */
171static int
172acquire_write(vntsd_client_t *clientp)
173{
174	int	rv;
175	int	yes_no = 1;
176	vntsd_cons_t *consp;
177
178	assert(clientp);
179	consp = clientp->cons;
180	assert(consp);
181
182	if (consp->clientpq->handle == clientp) {
183		/* client is a  writer */
184		if ((rv = vntsd_write_line(clientp,
185		    gettext("You have write permission"))) !=
186		    VNTSD_SUCCESS) {
187			return (rv);
188
189		}
190		return (VNTSD_STATUS_CONTINUE);
191	}
192
193	/* message to client */
194	if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN))
195	    != VNTSD_SUCCESS) {
196		return (rv);
197	}
198
199	/*
200	 * TRANSLATION_NOTE
201	 * The following string should be formatted to fit on multiple lines
202	 * assuming a line width of at most 78 characters. There must be no
203	 * trailing newline.
204	 */
205	if ((rv = vntsd_write_lines(clientp,
206	    gettext("Warning: another user currently "
207	    "has write permission\nto this console and forcibly removing "
208	    "him/her will terminate\nany current write action and all work "
209	    "will be lost."))) != VNTSD_SUCCESS) {
210		return (rv);
211	}
212
213	/* get client yes no */
214	if ((rv = vntsd_write_client(clientp, vntsd_eol,
215	    VNTSD_EOL_LEN)) != VNTSD_SUCCESS) {
216		return (rv);
217	}
218
219	if ((rv = vntsd_get_yes_no(clientp,
220	    gettext("Would you like to continue?"),
221	    &yes_no)) != VNTSD_SUCCESS) {
222		return (rv);
223	}
224
225	if (yes_no == B_FALSE) {
226		/* client change mind no need to acquire  write access */
227		return (VNTSD_STATUS_CONTINUE);
228	}
229
230	return (VNTSD_STATUS_ACQUIRE_WRITER);
231}
232
233/* client_exit()  - disconnect client from the console. */
234static int
235client_exit(void)
236{
237	return (VNTSD_STATUS_RESELECT_CONS);
238}
239
240static int daemon_cmd_help(vntsd_client_t *clientp);
241
242/* table for daemon commands */
243
244static esctable_t  etable[] = {
245
246	/* send a break to vcc */
247	{'#', "Send break",  genbrk},
248
249	/* alternate break sequence */
250	{CNTRL('B'), "Send alternate break", genaltbrk},
251
252	/* exit */
253	{'.', "Exit from this console",  (e_func_t)client_exit},
254
255	/* acquire write access */
256	{'w', "Force write access", acquire_write},
257
258	/* connect to next console in queue */
259	{'n', "Console next", (e_func_t)console_forward},
260
261	/* connect to previous console in queue */
262	{'p', "Console previous", (e_func_t)console_backward},
263
264	/* help must be next to last */
265	{'?', "Help", daemon_cmd_help},
266
267	/* table terminator */
268	{0, 0, 0}
269};
270
271void
272vntsd_init_esctable_msgs(void)
273{
274	esctable_t  *p;
275
276	for (p = etable; p->e_char != '\0'; p++) {
277		p->e_help = gettext(p->e_help);
278	}
279}
280
281/* daemon_cmd_help() - print help. */
282static int
283daemon_cmd_help(vntsd_client_t *clientp)
284{
285	esctable_t  *p;
286	int	    rv;
287	char	    buf[VNTSD_LINE_LEN];
288
289	if ((rv = vntsd_write_client(clientp, vntsd_eol,
290	    VNTSD_EOL_LEN)) != VNTSD_SUCCESS) {
291		return (rv);
292	}
293
294	/*
295	 * TRANSLATION_NOTE
296	 * VNTSD is the name of the VNTS daemon and should not be translated.
297	 */
298	if ((rv = vntsd_write_line(clientp, gettext("VNTSD commands"))) !=
299	    VNTSD_SUCCESS) {
300		return (rv);
301	}
302
303	for (p = etable; p->e_char; p++) {
304
305		if (p->e_char == CNTRL('B')) {
306			(void) snprintf(buf, sizeof (buf), "~^B --%s",
307			    p->e_help);
308		} else {
309			(void) snprintf(buf, sizeof (buf),
310			    "~%c --%s", p->e_char, p->e_help);
311		}
312
313		if ((rv = vntsd_write_line(clientp, buf)) != VNTSD_SUCCESS) {
314			return (rv);
315		}
316	}
317
318	return (VNTSD_STATUS_CONTINUE);
319}
320
321/* exit from daemon command */
322static int
323exit_daemon_cmd(vntsd_client_t *clientp, int rv)
324{
325	(void) mutex_lock(&clientp->lock);
326	clientp->status &= ~VNTSD_CLIENT_DISABLE_DAEMON_CMD;
327	(void) mutex_unlock(&clientp->lock);
328	return (rv);
329}
330
331/*
332 * vntsd_process_daemon_cmd() - special commands
333 * "<RET>~"  vntsd daemon commands
334 * "<RET>~~" enter '~' character
335 */
336int
337vntsd_process_daemon_cmd(vntsd_client_t *clientp, char c)
338{
339	esctable_t *p;
340	int	    rv;
341	char	    prev_char;
342
343	prev_char = clientp->prev_char;
344
345	if (c != VNTSD_DAEMON_CMD || (prev_char != 0 && prev_char != CR)) {
346		/* not a daemon command */
347		return (VNTSD_SUCCESS);
348	}
349
350	if (clientp->status & VNTSD_CLIENT_DISABLE_DAEMON_CMD) {
351		return (VNTSD_STATUS_CONTINUE);
352	}
353
354	/* no reentry to process_daemon_cmd */
355	(void) mutex_lock(&clientp->lock);
356	clientp->status |= VNTSD_CLIENT_DISABLE_DAEMON_CMD;
357	(void) mutex_unlock(&clientp->lock);
358
359	D3(stderr, "t@%d process_daemon_cmd %d %d \n", thr_self(),
360	    clientp->cons->vcc_fd, clientp->sockfd);
361
362	/* read in command */
363	if ((rv = vntsd_read_char(clientp, &c)) != VNTSD_SUCCESS) {
364		return (exit_daemon_cmd(clientp, rv));
365	}
366
367	if (c == VNTSD_DAEMON_CMD) {
368		/*
369		 * received another '~'
370		 * a user types '~~' to get '~'
371		 */
372		(void) mutex_lock(&clientp->lock);
373		clientp->status &= ~VNTSD_CLIENT_DISABLE_DAEMON_CMD;
374		(void) mutex_unlock(&clientp->lock);
375		return (VNTSD_SUCCESS);
376	}
377
378	for (p = etable; p->e_char; p++) {
379		if (p->e_char == c) {
380			/* found match */
381			assert(p->e_func);
382			rv = (*p->e_func)(clientp);
383			return (exit_daemon_cmd(clientp, rv));
384		}
385	}
386
387	/* no match, print out the help */
388	p--;
389	assert(p->e_char == '?');
390	rv = (*p->e_func)(clientp);
391
392	return (exit_daemon_cmd(clientp, rv));
393
394}
395
396/* vntsd_set_telnet_options() - change  telnet client to  character mode. */
397int
398vntsd_set_telnet_options(int fd)
399{
400	/* set client telnet options */
401	uint8_t buf[] = {IAC, DONT, LINEMODE, IAC, WILL, SUPRESS, IAC, WILL,
402		TEL_ECHO, IAC, DONT, TERM_TYPE, IAC, DONT, TERM_SP,
403		IAC, DONT, STATUS, IAC, DONT, FC, IAC, DONT, TM, IAC, DONT, ENV,
404		IAC, DONT, WIN_SIZE};
405
406	return (vntsd_write_fd(fd, (char *)buf, 30));
407}
408
409/*  vntsd_telnet_cmd() process telnet commands */
410int
411vntsd_telnet_cmd(vntsd_client_t *clientp, char c)
412{
413	uint8_t	buf[4];
414	char	cmd;
415	int	rv = VNTSD_STATUS_CONTINUE;
416
417	bzero(buf, 4);
418
419	if ((uint8_t)c != IAC) {
420		/* not telnet cmd */
421		return (VNTSD_SUCCESS);
422	}
423
424	if ((rv = vntsd_read_char(clientp, &cmd)) != VNTSD_SUCCESS) {
425		return (rv);
426	}
427
428	if ((uint8_t)cmd == WILL || (uint8_t)cmd == WONT ||
429	    (uint8_t)cmd == DO || (uint8_t)cmd == DONT) {
430		if ((rv = vntsd_read_char(clientp, &c)) != VNTSD_SUCCESS) {
431			return (rv);
432		}
433	}
434
435
436	switch ((uint8_t)cmd) {
437
438	case WILL:
439
440		switch ((uint8_t)c) {
441		case TEL_ECHO:
442		case SUPRESS:
443		case LINEMODE:
444			break;
445		default:
446			syslog(LOG_ERR, "not support telnet WILL %x\n", c);
447			break;
448		}
449		break;
450
451	case  WONT:
452
453		switch ((uint8_t)c) {
454		case TEL_ECHO:
455		case SUPRESS:
456		case LINEMODE:
457		default:
458			syslog(LOG_ERR, "not support telnet WONT %x\n", c);
459			break;
460		}
461		break;
462
463	case DO:
464	case DONT:
465
466		buf[0] = IAC;
467		buf[1] = WILL;
468		buf[2] = c;
469		rv = vntsd_write_client(clientp, (char *)buf, 3);
470
471		break;
472
473	case BRK:
474
475		/* send break to vcc */
476		rv = genbrk(clientp);
477		break;
478
479	case IP:
480
481		break;
482
483	case AYT: {
484			static char aytresp[] = "vntsd here";
485
486			rv = vntsd_write_client(clientp, aytresp,
487			    sizeof (aytresp) - 1);
488			break;
489		}
490
491	case HT:
492	case NOP:
493		return (VNTSD_STATUS_CONTINUE);
494
495	default:
496		syslog(LOG_ERR, "not support telnet ctrl %2.2x\n", 0xff & cmd);
497		break;
498	}
499
500	if (rv == VNTSD_SUCCESS) {
501		return (VNTSD_STATUS_CONTINUE);
502	} else {
503		return (rv);
504	}
505}
506
507
508/*
509 * vntsd_ctrl_cmd()   - control keys
510 * read and write suspend are supported.
511 */
512int
513vntsd_ctrl_cmd(vntsd_client_t *clientp, char c)
514{
515	int	cmd;
516
517	D3(stderr, "t@%d vntsd_ctrl_cmd%d %d\n", thr_self(),
518	    clientp->cons->vcc_fd, clientp->sockfd);
519
520	if ((c != START) && (c != STOP)) {
521		/* not a supported control command */
522		return (VNTSD_SUCCESS);
523	}
524
525	if (c == START) {
526		D3(stderr, "t@%d client restart\n", thr_self());
527
528		/* send resume read */
529		cmd = 1;
530
531		if (ioctl(clientp->cons->vcc_fd, TCXONC, &cmd)) {
532			return (VNTSD_STATUS_VCC_IO_ERR);
533		}
534
535	}
536
537	if (c == STOP) {
538		D3(stderr, "t@%d client suspend\n", thr_self());
539
540		/* send suspend read */
541		cmd = 0;
542
543		if (ioctl(clientp->cons->vcc_fd, TCXONC, &cmd)) {
544			return (VNTSD_STATUS_VCC_IO_ERR);
545		}
546
547	}
548
549	return (VNTSD_STATUS_CONTINUE);
550}
551