xref: /openbmc/ipmitool/lib/ipmi_tsol.c (revision c18ec02f)
1 /*
2  * Copyright (c) 2005 Tyan Computer Corp.  All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * Redistribution of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *
11  * Redistribution in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in the
13  * documentation and/or other materials provided with the distribution.
14  *
15  * Neither the name of Sun Microsystems, Inc. or the names of
16  * contributors may be used to endorse or promote products derived
17  * from this software without specific prior written permission.
18  *
19  * This software is provided "AS IS," without a warranty of any kind.
20  * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
21  * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
22  * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
23  * SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE
24  * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
25  * OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.  IN NO EVENT WILL
26  * SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA,
27  * OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR
28  * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF
29  * LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
30  * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
31  */
32 
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/poll.h>
36 #include <fcntl.h>
37 #include <unistd.h>
38 #include <errno.h>
39 #include <stdlib.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include <sys/socket.h>
43 #include <netinet/in.h>
44 #include <arpa/inet.h>
45 #include <netdb.h>
46 #include <signal.h>
47 
48 #include <sys/select.h>
49 #include <sys/time.h>
50 #include <sys/ioctl.h>
51 
52 #if defined(HAVE_CONFIG_H)
53 # include <config.h>
54 #endif
55 
56 #if defined(HAVE_TERMIOS_H)
57 # include <termios.h>
58 #elif defined (HAVE_SYS_TERMIOS_H)
59 # include <sys/termios.h>
60 #endif
61 
62 #include <ipmitool/log.h>
63 #include <ipmitool/helper.h>
64 #include <ipmitool/ipmi.h>
65 #include <ipmitool/ipmi_intf.h>
66 #include <ipmitool/ipmi_tsol.h>
67 #include <ipmitool/ipmi_strings.h>
68 #include <ipmitool/bswap.h>
69 
70 static struct timeval _start_keepalive;
71 static struct termios _saved_tio;
72 static struct winsize _saved_winsize;
73 static int _in_raw_mode = 0;
74 static int _altterm = 0;
75 
76 extern int verbose;
77 
78 static int
79 ipmi_tsol_command(struct ipmi_intf * intf, char *recvip, int port, unsigned char cmd)
80 {
81         struct ipmi_rs *rsp;
82         struct ipmi_rq	req;
83         unsigned char	data[6];
84 	unsigned	ip1, ip2, ip3, ip4;
85 
86         if (sscanf(recvip, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4) != 4) {
87                 lprintf(LOG_ERR, "Invalid IP address: %s", recvip);
88                 return -1;
89         }
90 
91 	memset(&req, 0, sizeof(struct ipmi_rq));
92         req.msg.netfn    = IPMI_NETFN_TSOL;
93         req.msg.cmd      = cmd;
94         req.msg.data_len = 6;
95         req.msg.data     = data;
96 
97 	memset(data, 0, sizeof(data));
98         data[0] = ip1;
99         data[1] = ip2;
100         data[2] = ip3;
101         data[3] = ip4;
102         data[4] = (port & 0xff00) >> 8;
103         data[5] = (port & 0xff);
104 
105         rsp = intf->sendrecv(intf, &req);
106         if (rsp == NULL) {
107 		lprintf(LOG_ERR, "Unable to perform TSOL command");
108                 return -1;
109 	}
110 	if (rsp->ccode > 0) {
111 		lprintf(LOG_ERR, "Unable to perform TSOL command: %s",
112 			val2str(rsp->ccode, completion_code_vals));
113 		return -1;
114         }
115 
116         return 0;
117 }
118 
119 static int
120 ipmi_tsol_start(struct ipmi_intf * intf, char *recvip, int port)
121 {
122 	return ipmi_tsol_command(intf, recvip, port, IPMI_TSOL_CMD_START);
123 }
124 
125 static int
126 ipmi_tsol_stop(struct ipmi_intf * intf, char *recvip, int port)
127 {
128         return ipmi_tsol_command(intf, recvip, port, IPMI_TSOL_CMD_STOP);
129 }
130 
131 static int
132 ipmi_tsol_send_keystroke(struct ipmi_intf * intf, char *buff, int length)
133 {
134         struct ipmi_rs * rsp;
135         struct ipmi_rq   req;
136         unsigned char    data[16];
137 	static unsigned char keyseq = 0;
138 
139 	memset(&req, 0, sizeof(struct ipmi_rq));
140         req.msg.netfn    = IPMI_NETFN_TSOL;
141         req.msg.cmd      = IPMI_TSOL_CMD_SENDKEY;
142         req.msg.data_len = length + 2;
143         req.msg.data     = data;
144 
145 	memset(data, 0, sizeof(data));
146         data[0] = length + 1;
147 	memcpy(data + 1, buff, length);
148 	data[length + 1] = keyseq++;
149 
150         rsp = intf->sendrecv(intf, &req);
151 	if (verbose) {
152 		if (rsp == NULL) {
153 			lprintf(LOG_ERR, "Unable to send keystroke");
154 			return -1;
155 		}
156 		if (rsp->ccode > 0) {
157 			lprintf(LOG_ERR, "Unable to send keystroke: %s",
158 				val2str(rsp->ccode, completion_code_vals));
159 			return -1;
160 		}
161 	}
162 
163         return length;
164 }
165 
166 static int
167 tsol_keepalive(struct ipmi_intf * intf)
168 {
169         struct timeval end;
170 
171         gettimeofday(&end, 0);
172 
173         if (end.tv_sec - _start_keepalive.tv_sec <= 30)
174                 return 0;
175 
176         intf->keepalive(intf);
177 
178 	gettimeofday(&_start_keepalive, 0);
179 
180         return 0;
181 }
182 
183 static void
184 print_escape_seq(struct ipmi_intf *intf)
185 {
186 	lprintf(LOG_NOTICE,
187 		"       %c.  - terminate connection\n"
188 		"       %c^Z - suspend ipmitool\n"
189 		"       %c^X - suspend ipmitool, but don't restore tty on restart\n"
190 		"       %c?  - this message\n"
191 		"       %c%c  - send the escape character by typing it twice\n"
192 		"       (Note that escapes are only recognized immediately after newline.)",
193 		intf->session->sol_escape_char,
194 		intf->session->sol_escape_char,
195 		intf->session->sol_escape_char,
196 		intf->session->sol_escape_char,
197 		intf->session->sol_escape_char,
198 		intf->session->sol_escape_char);
199 }
200 
201 static int
202 leave_raw_mode(void)
203 {
204 	if (!_in_raw_mode)
205 		return -1;
206 	else if (tcsetattr(fileno(stdin), TCSADRAIN, &_saved_tio) == -1)
207 		lperror(LOG_ERR, "tcsetattr(stdin)");
208 	else if (tcsetattr(fileno(stdout), TCSADRAIN, &_saved_tio) == -1)
209 		lperror(LOG_ERR, "tcsetattr(stdout)");
210 	else
211 		_in_raw_mode = 0;
212 
213 	return 0;
214 }
215 
216 static int
217 enter_raw_mode(void)
218 {
219 	struct termios tio;
220 
221 	if (tcgetattr(fileno(stdout), &_saved_tio) < 0) {
222 		lperror(LOG_ERR, "tcgetattr failed");
223 		return -1;
224 	}
225 
226 	tio = _saved_tio;
227 
228 	if (_altterm) {
229 		tio.c_iflag &= (ISTRIP | IGNBRK );
230 		tio.c_cflag &= ~(CSIZE | PARENB | IXON | IXOFF | IXANY);
231 		tio.c_cflag |= (CS8 |CREAD) | (IXON|IXOFF|IXANY);
232 		tio.c_lflag &= 0;
233 		tio.c_cc[VMIN] = 1;
234 		tio.c_cc[VTIME] = 0;
235 	} else {
236 		tio.c_iflag |= IGNPAR;
237 		tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
238 		tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL | IEXTEN);
239 		tio.c_oflag &= ~OPOST;
240 		tio.c_cc[VMIN] = 1;
241 		tio.c_cc[VTIME] = 0;
242 	}
243 
244 	if (tcsetattr(fileno(stdin), TCSADRAIN, &tio) < 0)
245 		lperror(LOG_ERR, "tcsetattr(stdin)");
246 	else if (tcsetattr(fileno(stdout), TCSADRAIN, &tio) < 0)
247 		lperror(LOG_ERR, "tcsetattr(stdout)");
248 	else
249 		_in_raw_mode = 1;
250 
251 	return 0;
252 }
253 
254 static void
255 suspend_self(int restore_tty)
256 {
257 	leave_raw_mode();
258 
259 	kill(getpid(), SIGTSTP);
260 
261 	if (restore_tty)
262 		enter_raw_mode();
263 }
264 
265 static int
266 do_inbuf_actions(struct ipmi_intf *intf, char *in_buff, int len)
267 {
268 	static int in_esc = 0;
269 	static int last_was_cr = 1;
270 	int i;
271 
272 	for(i = 0; i < len ;) {
273 		if (!in_esc) {
274 			if (last_was_cr &&
275 			    (in_buff[i] == intf->session->sol_escape_char)) {
276 				in_esc = 1;
277 				memmove(in_buff, in_buff + 1, len - i - 1);
278 				len--;
279 				continue;
280 			}
281 		}
282 		if (in_esc) {
283 			if (in_buff[i] == intf->session->sol_escape_char) {
284 				in_esc = 0;
285 				i++;
286 				continue;
287 			}
288 
289 			switch (in_buff[i]) {
290 			case '.':
291 				printf("%c. [terminated ipmitool]\n",
292 				       intf->session->sol_escape_char);
293 				return -1;
294 
295 			case 'Z' - 64:
296 				printf("%c^Z [suspend ipmitool]\n",
297 				       intf->session->sol_escape_char);
298 				suspend_self(1); /* Restore tty back to raw */
299 				break;
300 
301 			case 'X' - 64:
302 				printf("%c^X [suspend ipmitool]\n",
303 				       intf->session->sol_escape_char);
304 				suspend_self(0); /* Don't restore to raw mode */
305 				break;
306 
307 			case '?':
308 				printf("%c? [ipmitool help]\n",
309 				       intf->session->sol_escape_char);
310 				print_escape_seq(intf);
311 				break;
312 			}
313 
314 			memmove(in_buff, in_buff + 1, len - i - 1);
315 			len--;
316 			in_esc = 0;
317 
318 			continue;
319 		}
320 
321 		last_was_cr = (in_buff[i] == '\r' || in_buff[i] == '\n');
322 
323 		i++;
324 	}
325 
326 	return len;
327 }
328 
329 
330 static void
331 do_terminal_cleanup(void)
332 {
333 	if (_saved_winsize.ws_row > 0 && _saved_winsize.ws_col > 0)
334 		ioctl(fileno(stdout), TIOCSWINSZ, &_saved_winsize);
335 
336 	leave_raw_mode();
337 
338 	if (errno)
339 		lprintf(LOG_ERR, "Exiting due to error %d -> %s",
340 			errno, strerror(errno));
341 }
342 
343 static void
344 set_terminal_size(int rows, int cols)
345 {
346 	struct winsize winsize;
347 
348 	if (rows <= 0 || cols <= 0)
349 		return;
350 
351 	/* save initial winsize */
352 	ioctl(fileno(stdout), TIOCGWINSZ, &_saved_winsize);
353 
354 	/* set new winsize */
355 	winsize.ws_row = rows;
356 	winsize.ws_col = cols;
357 	ioctl(fileno(stdout), TIOCSWINSZ, &winsize);
358 }
359 
360 static void
361 print_tsol_usage(void)
362 {
363 	struct winsize winsize;
364 
365 	lprintf(LOG_NOTICE, "Usage: tsol [recvip] [port=NUM] [ro|rw] [rows=NUM] [cols=NUM] [altterm]");
366 	lprintf(LOG_NOTICE, "       recvip       Receiver IP Address             [default=local]");
367 	lprintf(LOG_NOTICE, "       port=NUM     Receiver UDP Port               [default=%d]",
368 		IPMI_TSOL_DEF_PORT);
369 	lprintf(LOG_NOTICE, "       ro|rw        Set Read-Only or Read-Write     [default=rw]");
370 
371 	ioctl(fileno(stdout), TIOCGWINSZ, &winsize);
372 	lprintf(LOG_NOTICE, "       rows=NUM     Set terminal rows               [default=%d]",
373 		winsize.ws_row);
374 	lprintf(LOG_NOTICE, "       cols=NUM     Set terminal columns            [default=%d]",
375 		winsize.ws_col);
376 
377 	lprintf(LOG_NOTICE, "       altterm      Alternate terminal setup        [default=off]");
378 }
379 
380 int
381 ipmi_tsol_main(struct ipmi_intf * intf, int argc, char ** argv)
382 {
383 	struct pollfd fds_wait[3], fds_data_wait[3], *fds;
384 	struct sockaddr_in sin, myaddr, *sa_in;
385 	socklen_t mylen;
386 	char *recvip = NULL;
387 	char out_buff[IPMI_BUF_SIZE * 8], in_buff[IPMI_BUF_SIZE];
388 	char buff[IPMI_BUF_SIZE + 4];
389 	int fd_socket, result, i;
390 	int out_buff_fill, in_buff_fill;
391 	int ip1, ip2, ip3, ip4;
392 	int read_only = 0, rows = 0, cols = 0;
393 	int port = IPMI_TSOL_DEF_PORT;
394 
395 	if (strlen(intf->name) < 3 || strncmp(intf->name, "lan", 3) != 0) {
396 		lprintf(LOG_ERR, "Error: Tyan SOL is only available over lan interface");
397 		return -1;
398 	}
399 
400 	for (i = 0; i<argc; i++) {
401 		if (sscanf(argv[i], "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4) == 4) {
402 			/* not free'd ...*/
403 			/* recvip = strdup(argv[i]); */
404 			recvip = argv[i];
405 		}
406 		else if (sscanf(argv[i], "port=%d", &ip1) == 1)
407 			port = ip1;
408 		else if (sscanf(argv[i], "rows=%d", &ip1) == 1)
409 			rows = ip1;
410 		else if (sscanf(argv[i], "cols=%d", &ip1) == 1)
411 			cols = ip1;
412 		else if (strlen(argv[i]) == 2 && strncmp(argv[i], "ro", 2) == 0)
413 			read_only = 1;
414 		else if (strlen(argv[i]) == 2 && strncmp(argv[i], "rw", 2) == 0)
415 			read_only = 0;
416 		else if (strlen(argv[i]) == 7 && strncmp(argv[i], "altterm", 7) == 0)
417 			_altterm = 1;
418 		else if (strlen(argv[i]) == 4 && strncmp(argv[i], "help", 4) == 0) {
419 			print_tsol_usage();
420 			return 0;
421 		}
422 		else {
423 			print_tsol_usage();
424 			return 0;
425 		}
426 	}
427 
428 	/* create udp socket to receive the packet */
429 	memset(&sin, 0, sizeof(sin));
430 	sin.sin_family = AF_INET;
431 	sin.sin_port = htons(port);
432 
433 	sa_in = (struct sockaddr_in *)&intf->session->addr;
434 	result = inet_pton(AF_INET, (const char *)intf->session->hostname,
435 			   &sa_in->sin_addr);
436 
437 	if (result <= 0) {
438 		struct hostent *host = gethostbyname((const char *)intf->session->hostname);
439 		if (host == NULL ) {
440 			lprintf(LOG_ERR, "Address lookup for %s failed",
441 				intf->session->hostname);
442 			return -1;
443 		}
444 		if (host->h_addrtype != AF_INET) {
445 			lprintf(LOG_ERR,
446 					"Address lookup for %s failed. Got %s, expected IPv4 address.",
447 					intf->session->hostname,
448 					(host->h_addrtype == AF_INET6) ? "IPv6" : "Unknown");
449 			return (-1);
450 		}
451 		sa_in->sin_family = host->h_addrtype;
452 		memcpy(&sa_in->sin_addr, host->h_addr, host->h_length);
453 	}
454 
455 	fd_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
456 	if (fd_socket < 0) {
457 		lprintf(LOG_ERR, "Can't open port %d", port);
458 		return -1;
459 	}
460 	if (-1 == bind(fd_socket, (struct sockaddr *)&sin, sizeof(sin))) {
461 		lprintf(LOG_ERR, "Failed to bind socket.");
462 		close(fd_socket);
463 		return -1;
464 	}
465 
466 	/*
467 	 * retrieve local IP address if not supplied on command line
468 	 */
469 	if (recvip == NULL) {
470 		result = intf->open(intf);	/* must connect first */
471 		if (result < 0) {
472 			close(fd_socket);
473 			return -1;
474 		}
475 
476 		mylen = sizeof(myaddr);
477 		if (getsockname(intf->fd, (struct sockaddr *)&myaddr, &mylen) < 0) {
478 			lperror(LOG_ERR, "getsockname failed");
479 			close(fd_socket);
480 			return -1;
481 		}
482 
483 		recvip = inet_ntoa(myaddr.sin_addr);
484 		if (recvip == NULL) {
485 			lprintf(LOG_ERR, "Unable to find local IP address");
486 			close(fd_socket);
487 			return -1;
488 		}
489 	}
490 
491 	printf("[Starting %sSOL with receiving address %s:%d]\n",
492 	       read_only ? "Read-only " : "", recvip, port);
493 
494 	set_terminal_size(rows, cols);
495 	enter_raw_mode();
496 
497 	/*
498 	 * talk to smdc to start Console redirect - IP address and port as parameter
499 	 * ipmitool -I lan -H 192.168.168.227 -U Administrator raw 0x30 0x06 0xC0 0xA8 0xA8 0x78 0x1A 0x0A
500 	 */
501 	result = ipmi_tsol_start(intf, recvip, port);
502         if (result < 0) {
503 		lprintf(LOG_ERR, "Error starting SOL");
504 		close(fd_socket);
505                 return -1;
506         }
507 
508 	printf("[SOL Session operational.  Use %c? for help]\n",
509 	       intf->session->sol_escape_char);
510 
511 	gettimeofday(&_start_keepalive, 0);
512 
513 	fds_wait[0].fd = fd_socket;
514 	fds_wait[0].events = POLLIN;
515 	fds_wait[0].revents = 0;
516 	fds_wait[1].fd = fileno(stdin);
517 	fds_wait[1].events = POLLIN;
518 	fds_wait[1].revents = 0;
519 	fds_wait[2].fd = -1;
520 	fds_wait[2].events = 0;
521 	fds_wait[2].revents = 0;
522 
523 	fds_data_wait[0].fd = fd_socket;
524 	fds_data_wait[0].events = POLLIN | POLLOUT;
525 	fds_data_wait[0].revents = 0;
526 	fds_data_wait[1].fd = fileno(stdin);
527 	fds_data_wait[1].events = POLLIN;
528 	fds_data_wait[1].revents = 0;
529 	fds_data_wait[2].fd = fileno(stdout);
530 	fds_data_wait[2].events = POLLOUT;
531 	fds_data_wait[2].revents = 0;
532 
533 	out_buff_fill = 0;
534 	in_buff_fill = 0;
535 	fds = fds_wait;
536 
537 	for (;;) {
538 		result = poll(fds, 3, 15*1000);
539 		if (result < 0)
540 			break;
541 
542 		/* send keepalive packet */
543 		tsol_keepalive(intf);
544 
545 		if ((fds[0].revents & POLLIN) && (sizeof(out_buff) > out_buff_fill)){
546 			socklen_t sin_len = sizeof(sin);
547 			result = recvfrom(fd_socket, buff, sizeof(out_buff) - out_buff_fill + 4, 0,
548 					  (struct sockaddr *)&sin, &sin_len);
549 
550 			/* read the data from udp socket, skip some bytes in the head */
551 			if((result - 4) > 0 ){
552 				int length = result - 4;
553 #if 1
554 		 		length = (unsigned char)buff[2] & 0xff;
555 			       	length *= 256;
556 				length += ((unsigned char)buff[3] & 0xff);
557 				if ((length <= 0) || (length > (result - 4)))
558 			              length = result - 4;
559 #endif
560 				memcpy(out_buff + out_buff_fill, buff + 4, length);
561 				out_buff_fill += length;
562 			}
563 		}
564 		if ((fds[1].revents & POLLIN) && (sizeof(in_buff) > in_buff_fill)) {
565 			result = read(fileno(stdin), in_buff + in_buff_fill,
566 				      sizeof(in_buff) - in_buff_fill); // read from keyboard
567 			if (result > 0) {
568 				int bytes;
569 				bytes = do_inbuf_actions(intf, in_buff + in_buff_fill, result);
570 				if(bytes < 0) {
571 					result = ipmi_tsol_stop(intf, recvip, port);
572 					do_terminal_cleanup();
573 					return result;
574 				}
575 				if (read_only)
576 					bytes = 0;
577 				in_buff_fill += bytes;
578 			}
579 		}
580 		if ((fds[2].revents & POLLOUT) && out_buff_fill) {
581 			result = write(fileno(stdout), out_buff, out_buff_fill); // to screen
582 			if (result > 0) {
583 				out_buff_fill -= result;
584 				if (out_buff_fill) {
585 					memmove(out_buff, out_buff + result, out_buff_fill);
586 				}
587 			}
588 		}
589 		if ((fds[0].revents & POLLOUT) && in_buff_fill) {
590 			/*
591 			 * translate key and send that to SMDC using IPMI
592 			 * ipmitool -I lan -H 192.168.168.227 -U Administrator raw 0x30 0x03 0x04 0x1B 0x5B 0x43
593 			 */
594 			result = ipmi_tsol_send_keystroke(intf, in_buff, __min(in_buff_fill,14));
595 			if (result > 0) {
596 				gettimeofday(&_start_keepalive, 0);
597 				in_buff_fill -= result;
598 				if (in_buff_fill) {
599 					memmove(in_buff, in_buff + result, in_buff_fill);
600 				}
601 			}
602 		}
603 		fds = (in_buff_fill || out_buff_fill )?
604 			fds_data_wait : fds_wait;
605 	}
606 
607 	return 0;
608 }
609