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->ssn_params.sol_escape_char, 189 intf->ssn_params.sol_escape_char, 190 intf->ssn_params.sol_escape_char, 191 intf->ssn_params.sol_escape_char, 192 intf->ssn_params.sol_escape_char, 193 intf->ssn_params.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->ssn_params.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->ssn_params.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->ssn_params.sol_escape_char); 285 return -1; 286 case 'Z' - 64: 287 printf("%c^Z [suspend ipmitool]\n", 288 intf->ssn_params.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->ssn_params.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->ssn_params.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 lprintf(LOG_ERR, "Invalid tsol command: '%s'\n", 416 argv[i]); 417 print_tsol_usage(); 418 return (-1); 419 } 420 } 421 422 /* create udp socket to receive the packet */ 423 memset(&sin, 0, sizeof(sin)); 424 sin.sin_family = AF_INET; 425 sin.sin_port = htons(port); 426 427 sa_in = (struct sockaddr_in *)&intf->session->addr; 428 result = inet_pton(AF_INET, (const char *)intf->ssn_params.hostname, 429 &sa_in->sin_addr); 430 431 if (result <= 0) { 432 struct hostent *host = gethostbyname((const char *)intf->ssn_params.hostname); 433 if (host == NULL ) { 434 lprintf(LOG_ERR, "Address lookup for %s failed", 435 intf->ssn_params.hostname); 436 return -1; 437 } 438 if (host->h_addrtype != AF_INET) { 439 lprintf(LOG_ERR, 440 "Address lookup for %s failed. Got %s, expected IPv4 address.", 441 intf->ssn_params.hostname, 442 (host->h_addrtype == AF_INET6) ? "IPv6" : "Unknown"); 443 return (-1); 444 } 445 sa_in->sin_family = host->h_addrtype; 446 memcpy(&sa_in->sin_addr, host->h_addr_list[0], host->h_length); 447 } 448 449 fd_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); 450 if (fd_socket < 0) { 451 lprintf(LOG_ERR, "Can't open port %d", port); 452 return -1; 453 } 454 if (bind(fd_socket, (struct sockaddr *)&sin, sizeof(sin)) == (-1)) { 455 lprintf(LOG_ERR, "Failed to bind socket."); 456 close(fd_socket); 457 return -1; 458 } 459 460 /* 461 * retrieve local IP address if not supplied on command line 462 */ 463 if (recvip == NULL) { 464 /* must connect first */ 465 result = intf->open(intf); 466 if (result < 0) { 467 close(fd_socket); 468 return -1; 469 } 470 471 mylen = sizeof(myaddr); 472 if (getsockname(intf->fd, (struct sockaddr *)&myaddr, &mylen) < 0) { 473 lperror(LOG_ERR, "getsockname failed"); 474 close(fd_socket); 475 return -1; 476 } 477 478 recvip = inet_ntoa(myaddr.sin_addr); 479 if (recvip == NULL) { 480 lprintf(LOG_ERR, "Unable to find local IP address"); 481 close(fd_socket); 482 return -1; 483 } 484 } 485 486 printf("[Starting %sSOL with receiving address %s:%d]\n", 487 read_only ? "Read-only " : "", recvip, port); 488 489 set_terminal_size(rows, cols); 490 enter_raw_mode(); 491 492 /* 493 * talk to smdc to start Console redirect - IP address and port as parameter 494 * ipmitool -I lan -H 192.168.168.227 -U Administrator raw 0x30 0x06 0xC0 0xA8 0xA8 0x78 0x1A 0x0A 495 */ 496 result = ipmi_tsol_start(intf, recvip, port); 497 if (result < 0) { 498 lprintf(LOG_ERR, "Error starting SOL"); 499 close(fd_socket); 500 return (-1); 501 } 502 503 printf("[SOL Session operational. Use %c? for help]\n", 504 intf->ssn_params.sol_escape_char); 505 506 gettimeofday(&_start_keepalive, 0); 507 508 fds_wait[0].fd = fd_socket; 509 fds_wait[0].events = POLLIN; 510 fds_wait[0].revents = 0; 511 fds_wait[1].fd = fileno(stdin); 512 fds_wait[1].events = POLLIN; 513 fds_wait[1].revents = 0; 514 fds_wait[2].fd = -1; 515 fds_wait[2].events = 0; 516 fds_wait[2].revents = 0; 517 518 fds_data_wait[0].fd = fd_socket; 519 fds_data_wait[0].events = POLLIN | POLLOUT; 520 fds_data_wait[0].revents = 0; 521 fds_data_wait[1].fd = fileno(stdin); 522 fds_data_wait[1].events = POLLIN; 523 fds_data_wait[1].revents = 0; 524 fds_data_wait[2].fd = fileno(stdout); 525 fds_data_wait[2].events = POLLOUT; 526 fds_data_wait[2].revents = 0; 527 528 out_buff_fill = 0; 529 in_buff_fill = 0; 530 fds = fds_wait; 531 for (;;) { 532 result = poll(fds, 3, 15 * 1000); 533 if (result < 0) { 534 break; 535 } 536 537 /* send keepalive packet */ 538 tsol_keepalive(intf); 539 540 if ((fds[0].revents & POLLIN) && (sizeof(out_buff) > out_buff_fill)) { 541 socklen_t sin_len = sizeof(sin); 542 int buff_size = sizeof(buff); 543 if ((sizeof(out_buff) - out_buff_fill + 4) < buff_size) { 544 buff_size = (sizeof(out_buff) - out_buff_fill) + 4; 545 if ((buff_size - 4) <= 0) { 546 buff_size = 0; 547 } 548 } 549 result = recvfrom(fd_socket, buff, 550 buff_size, 0, 551 (struct sockaddr *)&sin, &sin_len); 552 /* read the data from udp socket, 553 * skip some bytes in the head 554 */ 555 if ((result - 4) > 0) { 556 int length = result - 4; 557 memcpy(out_buff + out_buff_fill, buff + 4, length); 558 out_buff_fill += length; 559 } 560 } 561 if ((fds[1].revents & POLLIN) && (sizeof(in_buff) > in_buff_fill)) { 562 /* Read from keyboard */ 563 result = read(fileno(stdin), in_buff + in_buff_fill, 564 sizeof(in_buff) - in_buff_fill); 565 if (result > 0) { 566 int bytes; 567 bytes = do_inbuf_actions(intf, 568 in_buff + in_buff_fill, result); 569 if (bytes < 0) { 570 result = ipmi_tsol_stop(intf, recvip, port); 571 do_terminal_cleanup(); 572 return result; 573 } 574 if (read_only) { 575 bytes = 0; 576 } 577 in_buff_fill += bytes; 578 } 579 } 580 if ((fds[2].revents & POLLOUT) && out_buff_fill) { 581 /* To screen */ 582 result = write(fileno(stdout), out_buff, out_buff_fill); 583 if (result > 0) { 584 out_buff_fill -= result; 585 if (out_buff_fill) { 586 memmove(out_buff, out_buff + result, out_buff_fill); 587 } 588 } 589 } 590 if ((fds[0].revents & POLLOUT) && in_buff_fill) { 591 /* 592 * translate key and send that to SMDC using IPMI 593 * ipmitool -I lan -H 192.168.168.227 -U Administrator raw 0x30 0x03 0x04 0x1B 0x5B 0x43 594 */ 595 result = ipmi_tsol_send_keystroke(intf, 596 in_buff, __min(in_buff_fill, 14)); 597 if (result > 0) { 598 gettimeofday(&_start_keepalive, 0); 599 in_buff_fill -= result; 600 if (in_buff_fill) { 601 memmove(in_buff, in_buff + result, in_buff_fill); 602 } 603 } 604 } 605 fds = (in_buff_fill || out_buff_fill )? 606 fds_data_wait : fds_wait; 607 } 608 return 0; 609 } 610