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
ipmi_tsol_command(struct ipmi_intf * intf,char * recvip,int port,unsigned char cmd)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
ipmi_tsol_start(struct ipmi_intf * intf,char * recvip,int port)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
ipmi_tsol_stop(struct ipmi_intf * intf,char * recvip,int port)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
ipmi_tsol_send_keystroke(struct ipmi_intf * intf,char * buff,int length)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
tsol_keepalive(struct ipmi_intf * intf)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
print_escape_seq(struct ipmi_intf * intf)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
leave_raw_mode(void)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
enter_raw_mode(void)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
suspend_self(int restore_tty)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
do_inbuf_actions(struct ipmi_intf * intf,char * in_buff,int len)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
do_terminal_cleanup(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
set_terminal_size(int rows,int cols)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
print_tsol_usage(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
ipmi_tsol_main(struct ipmi_intf * intf,int argc,char ** argv)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