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