xref: /illumos-gate/usr/src/cmd/vntsd/cmd.c (revision e3ae4b35c024af1196582063ecee3ab79367227d)
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 
67 char vntsd_eol[] = { CR, LF, 0};
68 
69 typedef	int	    (*e_func_t)(vntsd_client_t *clientp);
70 /* structure for daemon special cmd */
71 typedef 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 */
78 static int
79 genbrk(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 */
108 static int
109 genaltbrk(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  */
144 static int
145 console_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  */
159 static int
160 console_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. */
171 static int
172 acquire_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. */
234 static int
235 client_exit(vntsd_client_t *arg __unused)
236 {
237 	return (VNTSD_STATUS_RESELECT_CONS);
238 }
239 
240 static int daemon_cmd_help(vntsd_client_t *clientp);
241 
242 /* table for daemon commands */
243 
244 static 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",  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 
271 void
272 vntsd_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. */
282 static int
283 daemon_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 */
322 static int
323 exit_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  */
336 int
337 vntsd_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. */
397 int
398 vntsd_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 */
410 int
411 vntsd_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  */
512 int
513 vntsd_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