1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * This code implements the Starcat Virtual Console host daemon (see cvcd(1M)). 31 * It accepts one TCP connection at a time on a well-known port. Once a 32 * connection is accepted, the console redirection driver (cvcdredir(7D)) is 33 * opened, and console I/O is routed back and forth between the two file 34 * descriptors (network and redirection driver). Per-socket IPsec is used to 35 * secure the connection if it is enabled with the "-a", "-u" and or "-e" 36 * command line options. 37 */ 38 39 #include <stdio.h> 40 #include <stdarg.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <strings.h> 44 45 #include <fcntl.h> 46 #include <sys/filio.h> /* Just to get FIONBIO... */ 47 #include <unistd.h> 48 #include <errno.h> 49 #include <stropts.h> 50 #include <signal.h> 51 #include <syslog.h> 52 #include <sys/utsname.h> 53 #include <sys/stat.h> 54 #include <locale.h> 55 #include <limits.h> 56 57 #include <sys/priocntl.h> 58 #include <sys/tspriocntl.h> 59 #include <sys/rtpriocntl.h> 60 61 #include <netdb.h> 62 #include <sys/socket.h> 63 #include <tiuser.h> 64 65 #include <sys/sc_cvcio.h> 66 67 68 /* 69 * Header files for per-socket IPsec 70 */ 71 #include <netinet/in.h> 72 #include <net/pfkeyv2.h> 73 74 /* 75 * The IPsec socket option struct, from ipsec(7P): 76 * 77 * typedef struct ipsec_req { 78 * uint_t ipsr_ah_req; AH request 79 * uint_t ipsr_esp_req; ESP request 80 * uint_t ipsr_self_encap_req; Self-Encap request 81 * uint8_t ipsr_auth_alg; Auth algs for AH 82 * uint8_t ipsr_esp_alg; Encr algs for ESP 83 * uint8_t ipsr_esp_auth_alg; Auth algs for ESP 84 * } ipsec_req_t; 85 * 86 * The -a option sets the ipsr_auth_alg field. Allowable arguments 87 * are "none", "md5", or "sha1". The -e option sets the ipsr_esp_alg 88 * field. Allowable arguments are "none", "des", or "3des". "none" 89 * is the default for both options. The -u option sets ipsr_esp_auth_alg. 90 * Allowable arguments are the same as -a. 91 * 92 * The arguments ("md5", "des", etc.) are named so that they match 93 * kmd(1m)'s accepted arguments which are listed on the SC in 94 * /etc/opt/SUNWSMS/SMS/config/kmd_policy.cf. 95 */ 96 #define AH_REQ (IPSEC_PREF_REQUIRED | IPSEC_PREF_UNIQUE) 97 #define ESP_REQ (IPSEC_PREF_REQUIRED | IPSEC_PREF_UNIQUE) 98 #define SELF_ENCAP_REQ 0x0 99 100 /* 101 * A type to hold the command line argument string used to select a 102 * particular authentication header (AH) or encapsulating security 103 * payload (ESP) algorithm and the ID used for that algorithm when 104 * filling the ipsec_req_t structure which is passed to 105 * setsockopt(3SOCKET). 106 */ 107 typedef struct cvcd_alg { 108 char *arg_name; 109 uint8_t alg_id; 110 } cvcd_alg_t; 111 112 /* 113 * Misc. defines. 114 */ 115 #define NODENAME "/etc/nodename" 116 #define NETWORK_PFD 0 117 #define REDIR_PFD 1 118 #define LISTEN_PFD 2 119 #define NUM_PFDS 3 120 121 /* 122 * Function prototypes 123 */ 124 static void cvcd_set_priority(void); 125 static int cvcd_init_host_socket(int port, uint8_t ah_auth_alg, 126 uint8_t esp_encr_alg, uint8_t esp_auth_alg); 127 static void cvcd_do_network_console(void); 128 static void cvcd_err(int code, char *format, ...); 129 static void cvcd_usage(void); 130 static uint8_t cvcd_get_alg(cvcd_alg_t *algs, char *arg); 131 132 /* 133 * Globals 134 */ 135 static struct pollfd pfds[NUM_PFDS]; 136 static char progname[MAXPATHLEN]; 137 static int debug = 0; 138 139 /* 140 * Array of acceptable -a, -u and -e arguments. 141 */ 142 static cvcd_alg_t auth_algs_array[] = { 143 { "none", SADB_AALG_NONE }, /* -a none or -u none */ 144 { "md5", SADB_AALG_MD5HMAC }, /* -a md5 or -u md5 */ 145 { "sha1", SADB_AALG_SHA1HMAC }, /* -a sha1 or -u sha1 */ 146 { NULL, 0x0 } 147 }, esp_algs_array[] = { 148 { "none", SADB_EALG_NONE }, /* -e none */ 149 { "des", SADB_EALG_DESCBC }, /* -e des */ 150 { "3des", SADB_EALG_3DESCBC }, /* -e 3des */ 151 { NULL, 0x0 } 152 }; 153 154 155 int 156 main(int argc, char **argv) 157 { 158 int err; 159 int opt; 160 int tport = 0; 161 char *hostname; 162 struct utsname utsname; 163 int fd; 164 int i; 165 struct servent *se; 166 char prefix[256]; 167 uint8_t ah_auth_alg = SADB_AALG_NONE; 168 uint8_t esp_encr_alg = SADB_EALG_NONE; 169 uint8_t esp_auth_alg = SADB_AALG_NONE; 170 171 (void) setlocale(LC_ALL, ""); 172 (void) strcpy(progname, argv[0]); 173 174 #ifdef DEBUG 175 while ((opt = getopt(argc, argv, "a:e:u:dp:")) != EOF) { 176 #else 177 while ((opt = getopt(argc, argv, "a:e:u:")) != EOF) { 178 #endif 179 switch (opt) { 180 case 'a' : 181 case 'u' : 182 if (opt == 'a') 183 ah_auth_alg = cvcd_get_alg( 184 auth_algs_array, optarg); 185 else 186 esp_auth_alg = cvcd_get_alg( 187 auth_algs_array, optarg); 188 break; 189 190 case 'e' : esp_encr_alg = cvcd_get_alg( 191 esp_algs_array, optarg); 192 break; 193 #ifdef DEBUG 194 case 'd' : debug = 1; 195 break; 196 197 case 'p' : tport = atoi(optarg); 198 break; 199 #endif /* DEBUG */ 200 201 default : cvcd_usage(); 202 exit(1); 203 } 204 } 205 206 if (uname(&utsname) == -1) { 207 perror("HOSTNAME not defined"); 208 exit(1); 209 } 210 hostname = utsname.nodename; 211 212 /* 213 * hostname may still be NULL, depends on when cvcd was started 214 * in the boot sequence. If it is NULL, try one more time 215 * to get a hostname -> look in the /etc/nodename file. 216 */ 217 if (!strlen(hostname)) { 218 /* 219 * try to get the hostname from the /etc/nodename file 220 * we reuse the utsname.nodename buffer here! hostname 221 * already points to it. 222 */ 223 if ((fd = open(NODENAME, O_RDONLY)) > 0) { 224 if ((i = read(fd, utsname.nodename, SYS_NMLN)) <= 0) { 225 cvcd_err(LOG_WARNING, 226 "failed to acquire hostname"); 227 } else { 228 utsname.nodename[i-1] = '\0'; 229 } 230 (void) close(fd); 231 } 232 } 233 /* 234 * If all attempts to get the hostname have failed, put something 235 * meaningful in the buffer. 236 */ 237 if (!strlen(hostname)) { 238 (void) strcpy(utsname.nodename, "(unknown)"); 239 } 240 241 /* 242 * Must be root. 243 */ 244 if (debug == 0 && geteuid() != 0) { 245 fprintf(stderr, "cvcd: Must be root"); 246 exit(1); 247 } 248 249 /* 250 * Daemonize... 251 */ 252 if (debug == 0) { 253 closefrom(0); 254 (void) chdir("/"); 255 (void) umask(0); 256 if (fork() != 0) { 257 exit(0); 258 } 259 (void) setpgrp(); 260 (void) sprintf(prefix, "%s-(HOSTNAME:%s)", progname, hostname); 261 openlog(prefix, LOG_CONS | LOG_NDELAY, LOG_LOCAL0); 262 } 263 264 /* 265 * Initialize the array of pollfds used to track the listening socket, 266 * the connection to the console redirection driver, and the network 267 * connection. 268 */ 269 (void) memset((void *)pfds, 0, NUM_PFDS * sizeof (struct pollfd)); 270 for (i = 0; i < NUM_PFDS; i++) { 271 pfds[i].fd = -1; 272 } 273 274 /* SPR 94004 */ 275 (void) sigignore(SIGTERM); 276 277 /* 278 * SPR 83644: cvc and kadb are not compatible under heavy loads. 279 * Fix: will give cvcd highest TS priority at execution time. 280 */ 281 cvcd_set_priority(); 282 283 /* 284 * If not already determined by a command-line flag, figure out which 285 * port we're supposed to be listening on. 286 */ 287 if (tport == 0) { 288 if ((se = getservbyname(CVCD_SERVICE, "tcp")) == NULL) { 289 cvcd_err(LOG_ERR, "getservbyname(%s) not found", 290 CVCD_SERVICE); 291 exit(1); 292 } 293 tport = se->s_port; 294 } 295 296 if (debug == 1) { 297 cvcd_err(LOG_DEBUG, "tport = %d, debug = %d", tport, debug); 298 } 299 300 /* 301 * Attempt to initialize the socket we'll use to listen for incoming 302 * connections. No need to check the return value, as the call will 303 * exit if it fails. 304 */ 305 pfds[LISTEN_PFD].fd = cvcd_init_host_socket(tport, ah_auth_alg, 306 esp_encr_alg, esp_auth_alg); 307 308 /* 309 * Now that we're all set up, we loop forever waiting for connections 310 * (one at a time) and then driving network console activity over them. 311 */ 312 for (;;) { 313 /* 314 * Start by waiting for an incoming connection. 315 */ 316 do { 317 pfds[LISTEN_PFD].events = POLLIN; 318 err = poll(&(pfds[LISTEN_PFD]), 1, -1); 319 if (err == -1) { 320 cvcd_err(LOG_ERR, "poll: %s", strerror(errno)); 321 exit(1); 322 } 323 if ((err > 0) && 324 (pfds[LISTEN_PFD].revents & POLLIN)) { 325 fd = accept(pfds[LISTEN_PFD].fd, NULL, NULL); 326 if ((fd == -1) && (errno != EWOULDBLOCK)) { 327 cvcd_err(LOG_ERR, "accept: %s", 328 strerror(errno)); 329 exit(1); 330 } 331 } 332 } while (fd == -1); 333 334 /* 335 * We have a connection. Set the new socket nonblocking, and 336 * initialize the appropriate pollfd. In theory, the new socket 337 * is _already_ non-blocking because accept() is supposed to 338 * hand us a socket with the same properties as the socket we're 339 * listening on, but it won't hurt to make sure. 340 */ 341 opt = 1; 342 err = ioctl(fd, FIONBIO, &opt); 343 if (err == -1) { 344 cvcd_err(LOG_ERR, "ioctl: %s", strerror(errno)); 345 (void) close(fd); 346 continue; 347 } 348 pfds[NETWORK_PFD].fd = fd; 349 350 /* 351 * Since we're ready to do network console stuff, go ahead and 352 * open the Network Console redirection driver, which will 353 * switch traffic from the IOSRAM path to the network path if 354 * the network path has been selected in cvc. 355 */ 356 fd = open(CVCREDIR_DEV, O_RDWR|O_NDELAY); 357 if (fd == -1) { 358 cvcd_err(LOG_ERR, "open(redir): %s", strerror(errno)); 359 exit(1); 360 } 361 pfds[REDIR_PFD].fd = fd; 362 363 /* 364 * We have a network connection and we have the redirection 365 * driver open, so drive the network console until something 366 * changes. 367 */ 368 cvcd_do_network_console(); 369 370 /* 371 * cvcd_do_network_console doesn't return until there's a 372 * problem, so we need to close the network connection and the 373 * redirection driver and start the whole loop over again. 374 */ 375 (void) close(pfds[NETWORK_PFD].fd); 376 pfds[NETWORK_PFD].fd = -1; 377 (void) close(pfds[REDIR_PFD].fd); 378 pfds[REDIR_PFD].fd = -1; 379 } 380 381 /* NOTREACHED */ 382 return (1); 383 } 384 385 /* 386 * cvcd_get_alg 387 * 388 * Returns the ID of the first algorithm found in 389 * the 'algs' array with a name matching 'arg'. If 390 * there is no matching algorithm, the function does 391 * not return. The 'algs' array must be terminated 392 * by an entry containing a NULL 'arg_name' field. 393 */ 394 static uint8_t 395 cvcd_get_alg(cvcd_alg_t *algs, char *arg) 396 { 397 cvcd_alg_t *alg; 398 399 for (alg = algs; alg->arg_name != NULL && arg != NULL; alg++) { 400 if (strncmp(alg->arg_name, arg, strlen(alg->arg_name) + 1) 401 == 0) { 402 return (alg->alg_id); 403 } 404 } 405 406 cvcd_usage(); 407 exit(1); 408 /* NOTREACHED */ 409 } 410 411 /* 412 * cvcd_set_priority 413 * 414 * DESCRIBE 415 * SPR 83644: cvc and kadb are not compatible under heavy loads. 416 * Fix: will give cvcd highest TS priority at execution time. 417 */ 418 static void 419 cvcd_set_priority(void) 420 { 421 id_t pid, tsID; 422 pcparms_t pcparms; 423 tsparms_t *tsparmsp; 424 short tsmaxpri; 425 pcinfo_t info; 426 427 pid = getpid(); 428 pcparms.pc_cid = PC_CLNULL; 429 tsparmsp = (tsparms_t *)pcparms.pc_clparms; 430 431 /* Get scheduler properties for this PID */ 432 if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&pcparms) == -1L) { 433 cvcd_err(LOG_ERR, "Warning: can't set priority."); 434 cvcd_err(LOG_ERR, "priocntl(GETPARMS): %s", strerror(errno)); 435 return; 436 } 437 438 /* Get class ID and maximum priority for TS process class */ 439 (void) strcpy(info.pc_clname, "TS"); 440 if (priocntl(0L, 0L, PC_GETCID, (caddr_t)&info) == -1L) { 441 cvcd_err(LOG_ERR, "Warning: can't set priority."); 442 cvcd_err(LOG_ERR, "priocntl(GETCID): %s", strerror(errno)); 443 return; 444 } 445 tsmaxpri = ((struct tsinfo *)info.pc_clinfo)->ts_maxupri; 446 tsID = info.pc_cid; 447 448 /* Print priority info in debug mode */ 449 if (debug) { 450 if (pcparms.pc_cid == tsID) { 451 cvcd_err(LOG_DEBUG, 452 "PID: %d, current priority: %d, Max priority: %d.", 453 pid, tsparmsp->ts_upri, tsmaxpri); 454 } 455 } 456 /* Change proc's priority to maxtspri */ 457 pcparms.pc_cid = tsID; 458 tsparmsp->ts_upri = tsmaxpri; 459 tsparmsp->ts_uprilim = tsmaxpri; 460 461 if (priocntl(P_PID, pid, PC_SETPARMS, (caddr_t)&pcparms) == -1L) { 462 cvcd_err(LOG_ERR, "Warning: can't set priority."); 463 cvcd_err(LOG_ERR, "priocntl(SETPARMS): %s", strerror(errno)); 464 } 465 466 /* Print new priority info in debug mode */ 467 if (debug) { 468 if (priocntl(P_PID, pid, PC_GETPARMS, (caddr_t)&pcparms) == 469 -1L) { 470 cvcd_err(LOG_ERR, "priocntl(GETPARMS): %s", 471 strerror(errno)); 472 } else { 473 cvcd_err(LOG_DEBUG, "PID: %d, new priority: %d.", pid, 474 tsparmsp->ts_upri); 475 } 476 } 477 } 478 479 480 /* 481 * cvcd_init_host_socket 482 * 483 * Given a TCP port number, create and initialize a socket appropriate for 484 * accepting incoming connections to that port. 485 */ 486 static int 487 cvcd_init_host_socket(int port, uint8_t ah_auth_alg, uint8_t esp_encr_alg, 488 uint8_t esp_auth_alg) 489 { 490 int err; 491 int fd; 492 int optval; 493 int optlen = sizeof (optval); 494 ipsec_req_t ipsec_req; /* For per-socket IPsec */ 495 struct sockaddr_in6 sin6; /* IPv6 listen socket */ 496 497 /* 498 * Start by creating the socket, which needs to support IPv6. 499 */ 500 fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); 501 if (fd == -1) { 502 cvcd_err(LOG_ERR, "socket: %s", strerror(errno)); 503 exit(1); 504 } 505 506 /* 507 * Set the SO_REUSEADDR option, and make the socket non-blocking. 508 */ 509 optval = 1; 510 err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, optlen); 511 if (err == -1) { 512 cvcd_err(LOG_ERR, "setsockopt: %s", strerror(errno)); 513 exit(1); 514 } 515 516 err = ioctl(fd, FIONBIO, &optval); 517 if (err == -1) { 518 cvcd_err(LOG_ERR, "ioctl: %s", strerror(errno)); 519 exit(1); 520 } 521 522 /* 523 * Enable per-socket IPsec if the user specified an AH or ESP 524 * algorithm to use. 525 */ 526 if (ah_auth_alg != SADB_AALG_NONE || esp_encr_alg != SADB_EALG_NONE || 527 esp_auth_alg != SADB_AALG_NONE) { 528 bzero(&ipsec_req, sizeof (ipsec_req)); 529 530 /* Hardcoded values */ 531 ipsec_req.ipsr_self_encap_req = SELF_ENCAP_REQ; 532 /* User defined */ 533 ipsec_req.ipsr_auth_alg = ah_auth_alg; 534 ipsec_req.ipsr_esp_alg = esp_encr_alg; 535 if (ah_auth_alg != SADB_AALG_NONE) 536 ipsec_req.ipsr_ah_req = AH_REQ; 537 if (esp_encr_alg != SADB_EALG_NONE || 538 esp_auth_alg != SADB_AALG_NONE) { 539 ipsec_req.ipsr_esp_req = ESP_REQ; 540 ipsec_req.ipsr_esp_auth_alg = esp_auth_alg; 541 } 542 543 err = setsockopt(fd, IPPROTO_IPV6, IPV6_SEC_OPT, 544 (void *)&ipsec_req, sizeof (ipsec_req)); 545 546 if (err == -1) { 547 cvcd_err(LOG_ERR, "failed to enable per-socket IPsec"); 548 cvcd_err(LOG_ERR, "setsockopt: %s", strerror(errno)); 549 exit(1); 550 } 551 } 552 553 /* 554 * Bind the socket to our local address and port. 555 */ 556 bzero(&sin6, sizeof (sin6)); 557 sin6.sin6_family = AF_INET6; 558 sin6.sin6_port = htons(port); 559 sin6.sin6_addr = in6addr_any; 560 err = bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)); 561 if (err == -1) { 562 cvcd_err(LOG_ERR, "bind: %s", strerror(errno)); 563 exit(1); 564 } 565 566 /* 567 * Indicate that we want to accept connections on this socket. Since we 568 * only allow one connection at a time anyway, specify a maximum backlog 569 * of 1. 570 */ 571 err = listen(fd, 1); 572 if (err == -1) { 573 cvcd_err(LOG_ERR, "listen: %s", strerror(errno)); 574 exit(1); 575 } 576 577 return (fd); 578 } 579 580 581 /* 582 * cvcd_do_network_console 583 * 584 * With established connections to the network and the redirection driver, 585 * shuttle data between the two until something goes wrong. 586 */ 587 static void 588 cvcd_do_network_console(void) 589 { 590 int i; 591 int err; 592 int count; 593 short revents; 594 int input_len = 0; 595 int output_len = 0; 596 int input_off = 0; 597 int output_off = 0; 598 char input_buf[MAXPKTSZ]; 599 char output_buf[MAXPKTSZ]; 600 601 for (;;) { 602 /* 603 * Wait for activity on any of the open file descriptors, which 604 * includes the ability to write data if we have any to write. 605 * If poll() fails, break out of the network console processing 606 * loop. 607 */ 608 pfds[LISTEN_PFD].events = POLLIN; 609 pfds[NETWORK_PFD].events = POLLIN; 610 if (output_len != 0) { 611 pfds[NETWORK_PFD].events |= POLLOUT; 612 } 613 pfds[REDIR_PFD].events = POLLIN; 614 if (input_len != 0) { 615 pfds[REDIR_PFD].events |= POLLOUT; 616 } 617 err = poll(pfds, NUM_PFDS, -1); 618 if (err == -1) { 619 cvcd_err(LOG_ERR, "poll: %s", strerror(errno)); 620 break; 621 } 622 623 /* 624 * If any errors or hangups were detected, or one of our file 625 * descriptors is bad, bail out of the network console 626 * processing loop. 627 */ 628 for (i = 0; i < NUM_PFDS; i++) { 629 revents = pfds[i].revents; 630 if (revents & (POLLERR | POLLHUP | POLLNVAL)) { 631 cvcd_err(LOG_NOTICE, 632 "poll: status on %s fd:%s%s%s", 633 ((i == LISTEN_PFD) ? "listen" : 634 ((i == NETWORK_PFD) ? "network" : "redir")), 635 (revents & POLLERR) ? " error" : "", 636 (revents & POLLHUP) ? " hangup" : "", 637 (revents & POLLNVAL) ? " bad fd" : ""); 638 goto fail; /* 'break' wouldn't work here */ 639 } 640 } 641 642 /* 643 * Start by rejecting any connection attempts, since we only 644 * allow one network connection at a time. 645 */ 646 if (pfds[LISTEN_PFD].revents & POLLIN) { 647 int fd; 648 649 fd = accept(pfds[LISTEN_PFD].fd, NULL, NULL); 650 if (fd > 0) { 651 (void) close(fd); 652 } 653 } 654 655 /* 656 * If we have data waiting to be written in one direction or the 657 * other, go ahead and try to send the data on its way. We're 658 * going to attempt the writes regardless of whether the poll 659 * indicated that the destinations are ready, because we want to 660 * find out if either descriptor has a problem (e.g. broken 661 * network link). 662 * If an "unexpected" error is detected, give up and break out 663 * of the network console processing loop. 664 */ 665 if (output_len != 0) { 666 count = write(pfds[NETWORK_PFD].fd, 667 &(output_buf[output_off]), output_len); 668 if ((count == -1) && (errno != EAGAIN)) { 669 cvcd_err(LOG_ERR, "write(network): %s", 670 strerror(errno)); 671 break; 672 } else if (count > 0) { 673 output_len -= count; 674 if (output_len == 0) { 675 output_off = 0; 676 } else { 677 output_off += count; 678 } 679 } 680 } 681 682 if (input_len != 0) { 683 count = write(pfds[REDIR_PFD].fd, 684 &(input_buf[input_off]), input_len); 685 if ((count == -1) && (errno != EAGAIN)) { 686 cvcd_err(LOG_ERR, "write(redir): %s", 687 strerror(errno)); 688 break; 689 } else if (count > 0) { 690 input_len -= count; 691 if (input_len == 0) { 692 input_off = 0; 693 } else { 694 input_off += count; 695 } 696 } 697 } 698 699 /* 700 * Finally, take a look at each data source and, if there isn't 701 * any residual data from that source still waiting to be 702 * processed, see if more data can be read. We don't want to 703 * read more data from a source if we haven't finished 704 * processing the last data we read from it because doing so 705 * would maximize the amount of data lost if the network console 706 * failed or was closed. 707 * If an "unexpected" error is detected, give up and break out 708 * of the network console processing loop. 709 * The call to read() appears to be in the habit of returning 0 710 * when you've read all of the data from a stream that has been 711 * hung up, and poll apparently feels that that condition 712 * justifies setting POLLIN, so we're going to treat 0 as an 713 * error return from read(). 714 */ 715 if ((output_len == 0) && (pfds[REDIR_PFD].revents & POLLIN)) { 716 count = read(pfds[REDIR_PFD].fd, output_buf, MAXPKTSZ); 717 if (count <= 0) { 718 /* 719 * Reading 0 simply means there is no data 720 * available, since this is a terminal. 721 */ 722 if ((count < 0) && (errno != EAGAIN)) { 723 cvcd_err(LOG_ERR, "read(redir): %s", 724 strerror(errno)); 725 break; 726 } 727 } else { 728 output_len = count; 729 output_off = 0; 730 } 731 } 732 733 if ((input_len == 0) && (pfds[NETWORK_PFD].revents & POLLIN)) { 734 count = read(pfds[NETWORK_PFD].fd, input_buf, MAXPKTSZ); 735 if (count <= 0) { 736 /* 737 * Reading 0 here implies a hangup, since this 738 * is a non-blocking socket that poll() reported 739 * as having data available. This will 740 * typically occur when the console user drops 741 * to OBP or intentially switches to IOSRAM 742 * mode. 743 */ 744 if (count == 0) { 745 cvcd_err(LOG_NOTICE, 746 "read(network): hangup detected"); 747 break; 748 } else if (errno != EAGAIN) { 749 cvcd_err(LOG_ERR, "read(network): %s", 750 strerror(errno)); 751 break; 752 } 753 } else { 754 input_len = count; 755 input_off = 0; 756 } 757 } 758 } /* End forever loop */ 759 760 /* 761 * If we get here, something bad happened during an attempt to access 762 * either the redirection driver or the network connection. There 763 * doesn't appear to be any way to avoid the possibility of losing 764 * console input and/or input in that case, so we should at least report 765 * the loss if it happens. 766 * XXX - We could do more, but is it worth the effort? Logging the 767 * lost data would be pretty easy... actually preserving it 768 * in the console flow would be a lot harder. We're more robust 769 * than the previous generation at this point, at least, so 770 * perhaps that's enough for now? 771 */ 772 fail: 773 if (input_len != 0) { 774 cvcd_err(LOG_ERR, "console input lost"); 775 } 776 if (output_len != 0) { 777 cvcd_err(LOG_ERR, "console output lost"); 778 } 779 } 780 781 782 static void 783 cvcd_usage() 784 { 785 #if defined(DEBUG) 786 (void) printf("%s [-d] [-p port] " 787 "[-a none|md5|sha1] [-e none|des|3des] [-u none|md5|sha1]\n", 788 progname); 789 #else 790 (void) printf("%s [-a none|md5|sha1] [-e none|des|3des] " 791 "[-u none|md5|sha1]\n", progname); 792 #endif /* DEBUG */ 793 } 794 795 /* 796 * cvcd_err () 797 * 798 * Description: 799 * Log messages via syslog daemon. 800 * 801 * Input: 802 * code - logging code 803 * format - messages to log 804 * 805 * Output: 806 * void 807 * 808 */ 809 static void 810 cvcd_err(int code, char *format, ...) 811 { 812 va_list varg_ptr; 813 char buf[MAXPKTSZ]; 814 815 va_start(varg_ptr, format); 816 (void) vsnprintf(buf, MAXPKTSZ, format, varg_ptr); 817 va_end(varg_ptr); 818 819 if (debug == 0) { 820 syslog(code, buf); 821 } else { 822 (void) fprintf(stderr, "%s: %s\n", progname, buf); 823 } 824 } 825