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