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 /* EXPORT DELETE START */ 30 #include <sys/types.h> 31 #include <sys/param.h> 32 #include <sys/salib.h> 33 #include <sys/promif.h> 34 #include <sys/wanboot_impl.h> 35 #include <netinet/in.h> 36 #include <parseURL.h> 37 #include <bootlog.h> 38 #include <sys/socket.h> 39 #include <netinet/inetutil.h> 40 #include <netinet/dhcp.h> 41 #include <dhcp_impl.h> 42 #include <lib/inet/mac.h> 43 #include <lib/inet/ipv4.h> 44 #include <lib/inet/dhcpv4.h> 45 #include <lib/sock/sock_test.h> 46 #include <sys/sunos_dhcp_class.h> 47 #include <aes.h> 48 #include <des3.h> 49 #include <hmac_sha1.h> 50 #include <netdb.h> 51 #include <wanboot_conf.h> 52 #include <bootinfo.h> 53 /* EXPORT DELETE END */ 54 55 #include "wbcli.h" 56 57 /* EXPORT DELETE START */ 58 59 #define skipspace(p) while (isspace(*(p))) ++p 60 61 #define skiptext(p) while (*(p) != '\0' && !isspace(*(p)) && \ 62 *(p) != '=' && *(p) != ',') ++p 63 64 #define PROMPT "boot> " 65 #define TEST_PROMPT "boot-test> " 66 67 #define CLI_SET 0 68 #define CLI_FAIL (-1) 69 #define CLI_EXIT (-2) 70 #define CLI_CONT (-3) 71 72 #define CLF_CMD 0x00000001 /* builtin command */ 73 #define CLF_ARG 0x00000002 /* boot argument directive */ 74 75 #define CLF_IF 0x00000100 /* interface parameter */ 76 #define CLF_BM 0x00000200 /* bootmisc parameter */ 77 78 #define CLF_VALSET 0x00010000 /* value set, may be null */ 79 #define CLF_HIDDEN 0x00020000 /* don't show its value (key) */ 80 #define CLF_VALMOD 0x00040000 /* value modified by the user */ 81 82 /* 83 * Macros for use in managing the flags in the cli_list[]. 84 * The conventions we follow are: 85 * 86 * CLF_VALSET is cleared if a value is removed from varptr 87 * CLF_VALSET is set if a value has been placed in varptr 88 * (that value need not be vetted) 89 * CLF_HIDDEN is set if a value must not be exposed to the user 90 * CLF_HIDDEN is cleared if a value can be exposed to the user 91 * CLF_VALMOD is cleared if a value in varptr has not been modified 92 * CLF_VALMOD is set if a value in varptr has been modified by 93 * the user 94 */ 95 #ifdef DEBUG 96 #define CLF_SETVAL(var) { \ 97 (((var)->flags) |= CLF_VALSET); \ 98 printf("set %s\n", var->varname);\ 99 } 100 101 #define CLF_ISSET(var) (printf("%s\n", \ 102 (((var)->flags) & CLF_VALSET) != 0 \ 103 ? "is set" : "not set"), \ 104 ((((var)->flags) & CLF_VALSET) != 0)) 105 106 #define CLF_CLRHIDDEN(var) { \ 107 (((var)->flags) &= ~CLF_HIDDEN); \ 108 printf("unhide %s\n", var->varname); \ 109 } 110 111 #define CLF_ISHIDDEN(var) (printf("%s\n", \ 112 (((var)->flags) & CLF_HIDDEN) != 0 \ 113 ? "is hidden" : "not hidden"), \ 114 ((((var)->flags) & CLF_HIDDEN) != 0)) 115 116 #define CLF_MODVAL(var) { \ 117 (((var)->flags) |= \ 118 (CLF_VALMOD | CLF_VALSET)); \ 119 printf("modified %s\n", var->varname);\ 120 } 121 122 #define CLF_ISMOD(var) (printf("%s\n", \ 123 (((var)->flags) & CLF_VALMOD) != 0 \ 124 ? "is set" : "not set"), \ 125 ((((var)->flags) & CLF_VALMOD) != 0)) 126 #else /* DEBUG */ 127 128 #define CLF_SETVAL(var) (((var)->flags) |= CLF_VALSET) 129 #define CLF_ISSET(var) ((((var)->flags) & CLF_VALSET) != 0) 130 #define CLF_CLRHIDDEN(var) (((var)->flags) &= ~CLF_HIDDEN) 131 #define CLF_ISHIDDEN(var) ((((var)->flags) & CLF_HIDDEN) != 0) 132 #define CLF_MODVAL(var) (((var)->flags) |= (CLF_VALMOD | CLF_VALSET)) 133 #define CLF_ISMOD(var) ((((var)->flags) & CLF_VALMOD) != 0) 134 135 #endif /* DEBUG */ 136 137 /* 138 * The width of the widest varname below - currently "subnet_mask". 139 */ 140 #define VAR_MAXWIDTH strlen(BI_SUBNET_MASK) 141 142 struct cli_ent; 143 typedef int claction_t(struct cli_ent *, char *, boolean_t); 144 145 typedef struct cli_ent { 146 char *varname; 147 claction_t *action; 148 int flags; 149 void *varptr; 150 uint_t varlen; 151 uint_t varmax; 152 } cli_ent_t; 153 154 static cli_ent_t *find_cli_ent(char *varstr); 155 156 static char cmdbuf[2048]; /* interpreter buffer */ 157 static char hostip[INET_ADDRSTRLEN]; 158 static char subnet[INET_ADDRSTRLEN]; 159 static char router[INET_ADDRSTRLEN]; 160 static char hostname[MAXHOSTNAMELEN]; 161 static char httpproxy[INET_ADDRSTRLEN + 5]; /* a.b.c.d:p */ 162 static char bootserverURL[URL_MAX_STRLEN + 1]; 163 static unsigned char clientid[WB_MAX_CID_LEN]; 164 static unsigned char aeskey[AES_128_KEY_SIZE]; 165 static unsigned char des3key[DES3_KEY_SIZE]; 166 static unsigned char sha1key[WANBOOT_HMAC_KEY_SIZE]; 167 static boolean_t args_specified_prompt = B_FALSE; 168 169 extern bc_handle_t bc_handle; 170 extern int getchar(void); 171 172 static claction_t clcid, clkey, clip, clstr, clurl, clhp; 173 static claction_t clhelp, cllist, clprompt, cldhcp, cltest, clgo, clexit; 174 175 static cli_ent_t cli_list[] = { 176 /* 177 * Commands/bootargs: 178 */ 179 { "test", cltest, CLF_ARG, 180 NULL, 0, 0 }, 181 { "dhcp", cldhcp, CLF_ARG, 182 NULL, 0, 0 }, 183 { "prompt", clprompt, CLF_CMD | CLF_ARG, 184 NULL, 0, 0 }, 185 { "list", cllist, CLF_CMD, 186 NULL, 0, 0 }, 187 { "help", clhelp, CLF_CMD, 188 NULL, 0, 0 }, 189 { "go", clgo, CLF_CMD, 190 NULL, 0, 0 }, 191 { "exit", clexit, CLF_CMD, 192 NULL, 0, 0 }, 193 194 /* 195 * Interface: 196 */ 197 { BI_HOST_IP, clip, CLF_IF, 198 hostip, 0, sizeof (hostip) }, 199 { BI_SUBNET_MASK, clip, CLF_IF, 200 subnet, 0, sizeof (subnet) }, 201 { BI_ROUTER_IP, clip, CLF_IF, 202 router, 0, sizeof (router) }, 203 { BI_HOSTNAME, clstr, CLF_IF, 204 hostname, 0, sizeof (hostname) }, 205 { BI_HTTP_PROXY, clhp, CLF_IF, 206 httpproxy, 0, sizeof (httpproxy) }, 207 { BI_CLIENT_ID, clcid, CLF_IF, 208 clientid, 0, sizeof (clientid) }, 209 210 /* 211 * Bootmisc: 212 */ 213 { BI_AES_KEY, clkey, CLF_BM | CLF_HIDDEN, 214 aeskey, 0, sizeof (aeskey) }, 215 { BI_3DES_KEY, clkey, CLF_BM | CLF_HIDDEN, 216 des3key, 0, sizeof (des3key) }, 217 { BI_SHA1_KEY, clkey, CLF_BM | CLF_HIDDEN, 218 sha1key, 0, sizeof (sha1key) }, 219 { BI_BOOTSERVER, clurl, CLF_BM, 220 bootserverURL, 0, sizeof (bootserverURL) }, 221 }; 222 223 static int num_cli_ent = (sizeof (cli_list) / sizeof (cli_ent_t)); 224 225 /* 226 * Fetch a line from the user, handling backspace appropriately. 227 */ 228 static int 229 editline(char *buf, int count) 230 { 231 int i = 0; 232 char c; 233 234 while (i < count - 1) { 235 c = getchar(); 236 if (c == '\n') { 237 break; 238 } else if (c == '\b') { 239 /* Clear for backspace. */ 240 if (i > 0) 241 i--; 242 continue; 243 } else { 244 buf[i++] = c; 245 } 246 } 247 buf[i] = '\0'; 248 return (i); 249 } 250 251 /* 252 * Assign a client-id to cliptr, or output cliptr's value as a client-id. 253 * On assignment the value is specified in valstr, either in hexascii or 254 * as a quoted string; on output its value is printed in hexascii. 255 */ 256 static int 257 clcid(cli_ent_t *cliptr, char *valstr, boolean_t out) 258 { 259 uint_t len, vmax; 260 boolean_t hexascii = B_TRUE; 261 char buffer[2 * WB_MAX_CID_LEN + 1]; 262 263 if (out) { 264 len = cliptr->varlen * 2 + 1; 265 (void) octet_to_hexascii(cliptr->varptr, cliptr->varlen, 266 buffer, &len); 267 printf("%s", buffer); 268 return (CLI_CONT); 269 } else { 270 len = strlen(valstr); 271 vmax = cliptr->varmax - 1; /* space for the prefix */ 272 273 /* 274 * Check whether the value is a quoted string; if so, strip 275 * the quotes and note that it's not in hexascii. 276 */ 277 if ((valstr[0] == '"' || valstr[0] == '\'') && 278 valstr[len-1] == valstr[0]) { 279 hexascii = B_FALSE; 280 ++valstr; 281 len -= 2; 282 valstr[len] = '\0'; 283 } else { 284 /* 285 * If the value contains any non-hex digits assume 286 * that it's not in hexascii. 287 */ 288 char *p; 289 290 for (p = valstr; *p != '\0'; ++p) { 291 if (!isxdigit(*p)) { 292 hexascii = B_FALSE; 293 break; 294 } 295 } 296 } 297 298 if (hexascii) { 299 if (len > vmax * 2 || 300 hexascii_to_octet(valstr, len, 301 (char *)(cliptr->varptr), &vmax) != 0) { 302 return (CLI_FAIL); 303 } 304 cliptr->varlen = vmax; 305 } else { 306 if (len > vmax) { 307 return (CLI_FAIL); 308 } 309 bcopy(valstr, cliptr->varptr, len); 310 cliptr->varlen = len; 311 } 312 313 return (CLI_SET); 314 } 315 } 316 317 /* 318 * Assign a key to cliptr, or output cliptr's value as a key. 319 * On assignment the value is specified in valstr in hexascii; 320 * on output its value is printed in hexascii, provided the key 321 * was entered at the interpreter (not obtained from OBP and 322 * thus hidden). 323 */ 324 static int 325 clkey(cli_ent_t *cliptr, char *valstr, boolean_t out) 326 { 327 uint_t len, vmax; 328 329 if (out) { 330 char buffer[2 * WANBOOT_MAXKEYLEN + 1]; 331 332 if (!CLF_ISHIDDEN(cliptr)) { 333 len = cliptr->varlen * 2 + 1; 334 (void) octet_to_hexascii(cliptr->varptr, 335 cliptr->varlen, buffer, &len); 336 printf("%s", buffer); 337 } else { 338 printf("*HIDDEN*"); 339 } 340 return (CLI_CONT); 341 } else { 342 len = strlen(valstr); 343 vmax = cliptr->varmax; 344 if (len != vmax * 2 || hexascii_to_octet(valstr, len, 345 cliptr->varptr, &vmax) != 0) { 346 return (CLI_FAIL); 347 } 348 cliptr->varlen = vmax; 349 CLF_CLRHIDDEN(cliptr); 350 return (CLI_SET); 351 } 352 } 353 354 /* 355 * Assign an IP address to cliptr, or output cliptr's value as an 356 * IP address. On assignment the value is specified in valstr in 357 * dotted-decimal format; on output its value is printed in dotted- 358 * decimal format. 359 */ 360 static int 361 clip(cli_ent_t *cliptr, char *valstr, boolean_t out) 362 { 363 uint_t len; 364 365 if (out) { 366 printf("%s", (char *)cliptr->varptr); 367 return (CLI_CONT); 368 } 369 370 if (inet_addr(valstr) == (in_addr_t)-1 || 371 (len = strlen(valstr)) >= cliptr->varmax) { 372 return (CLI_FAIL); 373 } 374 375 (void) strcpy(cliptr->varptr, valstr); 376 cliptr->varlen = len + 1; 377 return (CLI_SET); 378 } 379 380 /* 381 * Assign an arbitrary string to cliptr, or output cliptr's value as a string. 382 */ 383 static int 384 clstr(cli_ent_t *cliptr, char *valstr, boolean_t out) 385 { 386 uint_t len; 387 388 if (out) { 389 printf("%s", (char *)cliptr->varptr); 390 return (CLI_CONT); 391 } else { 392 if ((len = strlen(valstr)) >= cliptr->varmax) { 393 return (CLI_FAIL); 394 } else { 395 (void) strcpy(cliptr->varptr, valstr); 396 cliptr->varlen = len + 1; 397 return (CLI_SET); 398 } 399 } 400 } 401 402 /* 403 * Assign a URL to cliptr (having verified the format), or output cliptr's 404 * value as a URL. The host must be specified in dotted-decimal, and the 405 * scheme must not be https. 406 */ 407 static int 408 clurl(cli_ent_t *cliptr, char *valstr, boolean_t out) 409 { 410 url_t u; 411 uint_t len; 412 413 if (out) { 414 printf("%s", (char *)cliptr->varptr); 415 return (CLI_CONT); 416 } 417 418 if (url_parse(valstr, &u) != URL_PARSE_SUCCESS || 419 u.https || inet_addr(u.hport.hostname) == (in_addr_t)-1 || 420 (len = strlen(valstr)) >= cliptr->varmax) { 421 return (CLI_FAIL); 422 } 423 424 (void) strcpy(cliptr->varptr, valstr); 425 cliptr->varlen = len + 1; 426 return (CLI_SET); 427 } 428 429 /* 430 * Assign a hostport to cliptr (having verified the format), or output cliptr's 431 * value as a hostport. The host must be specified in dotted-decimal. 432 */ 433 static int 434 clhp(cli_ent_t *cliptr, char *valstr, boolean_t out) 435 { 436 url_hport_t u; 437 uint_t len; 438 439 if (out) { 440 printf("%s", (char *)cliptr->varptr); 441 return (CLI_CONT); 442 } 443 444 if (url_parse_hostport(valstr, &u, URL_DFLT_PROXY_PORT) != 445 URL_PARSE_SUCCESS || 446 inet_addr(u.hostname) == (in_addr_t)-1 || 447 (len = strlen(valstr)) >= cliptr->varmax) { 448 return (CLI_FAIL); 449 } 450 451 (void) strcpy(cliptr->varptr, valstr); 452 cliptr->varlen = len + 1; 453 return (CLI_SET); 454 } 455 456 /* 457 * Exit the interpreter and return to the booter. 458 */ 459 /*ARGSUSED*/ 460 static int 461 clgo(cli_ent_t *cliptr, char *valstr, boolean_t out) 462 { 463 return (CLI_EXIT); 464 } 465 466 /* 467 * Exit the interpreter and return to OBP. 468 */ 469 /*ARGSUSED*/ 470 static int 471 clexit(cli_ent_t *cliptr, char *valstr, boolean_t out) 472 { 473 prom_exit_to_mon(); 474 /*NOTREACHED*/ 475 return (CLI_EXIT); 476 } 477 478 /* 479 * Provide simple help information. 480 */ 481 /*ARGSUSED*/ 482 static int 483 clhelp(cli_ent_t *cliptr, char *valstr, boolean_t out) 484 { 485 printf("var=val - set variable\n"); 486 printf("var= - unset variable\n"); 487 printf("var - print variable\n"); 488 printf("list - list variables and their values\n"); 489 printf("prompt - prompt for unset variables\n"); 490 printf("go - continue booting\n"); 491 printf("exit - quit boot interpreter and return to OBP\n"); 492 493 return (CLI_CONT); 494 } 495 496 /* 497 * List variables and their current values. 498 */ 499 /*ARGSUSED*/ 500 static int 501 cllist(cli_ent_t *cliptr, char *valstr, boolean_t out) 502 { 503 int wanted = (int)(uintptr_t)valstr; /* use uintptr_t for gcc */ 504 int i; 505 506 wanted &= ~(CLF_CMD | CLF_ARG); 507 508 for (cliptr = cli_list; cliptr < &cli_list[num_cli_ent]; cliptr++) { 509 if ((cliptr->flags & (CLF_CMD | CLF_ARG)) != 0 || 510 (cliptr->flags & wanted) == 0) { 511 continue; 512 } 513 printf("%s: ", cliptr->varname); 514 /* 515 * Line the values up - space to the width of the widest 516 * varname + 1 for the ':'. 517 */ 518 for (i = VAR_MAXWIDTH + 1 - strlen(cliptr->varname); 519 i > 0; --i) { 520 printf(" "); 521 } 522 523 if (CLF_ISSET(cliptr) || CLF_ISHIDDEN(cliptr)) { 524 (void) cliptr->action(cliptr, NULL, B_TRUE); 525 printf("\n"); 526 } else { 527 printf("UNSET\n"); 528 } 529 } 530 531 return (CLI_CONT); 532 } 533 534 /* 535 * Prompt for wanted values. 536 */ 537 /*ARGSUSED*/ 538 static int 539 clprompt(cli_ent_t *cliptr, char *valstr, boolean_t out) 540 { 541 char *p; 542 int wanted = (int)(uintptr_t)valstr; /* use uintrptr_t for gcc */ 543 544 /* 545 * If processing boot arguments, simply note the fact that clprompt() 546 * should be invoked later when other parameters may be supplied. 547 */ 548 if ((wanted & CLF_ARG) != 0) { 549 args_specified_prompt = B_TRUE; 550 return (CLI_CONT); 551 } 552 wanted &= ~(CLF_CMD | CLF_ARG); 553 554 for (cliptr = cli_list; cliptr < &cli_list[num_cli_ent]; ++cliptr) { 555 if ((cliptr->flags & wanted) == 0) { 556 continue; 557 } 558 559 printf("%s", cliptr->varname); 560 if (CLF_ISSET(cliptr)) { 561 printf(" ["); 562 (void) cliptr->action(cliptr, NULL, B_TRUE); 563 printf("]"); 564 } 565 printf("? "); 566 (void) editline(cmdbuf, sizeof (cmdbuf)); 567 printf("\n"); 568 569 p = cmdbuf; 570 skipspace(p); 571 if (*p == '\0') { /* nothing there */ 572 continue; 573 } 574 575 /* Get valstr and nul terminate */ 576 valstr = p; 577 ++p; 578 skiptext(p); 579 *p = '\0'; 580 581 /* If empty value, do nothing */ 582 if (strlen(valstr) == 0) { 583 continue; 584 } 585 586 switch (cliptr->action(cliptr, valstr, B_FALSE)) { 587 case CLI_SET: 588 CLF_MODVAL(cliptr); 589 break; 590 case CLI_FAIL: 591 printf("Incorrect format, parameter unchanged!\n"); 592 break; 593 case CLI_EXIT: 594 return (CLI_EXIT); 595 case CLI_CONT: 596 break; 597 } 598 } 599 600 return (CLI_CONT); 601 } 602 603 /* 604 * If the PROM has done DHCP, bind the interface; otherwise do the full 605 * DHCP packet exchange. 606 */ 607 /*ARGSUSED*/ 608 static int 609 cldhcp(cli_ent_t *cliptr, char *valstr, boolean_t out) 610 { 611 static boolean_t first_time = B_TRUE; 612 static int ret = CLI_CONT; 613 614 if (first_time) { 615 /* 616 * Set DHCP's idea of the client_id from our cached value. 617 */ 618 cliptr = find_cli_ent(BI_CLIENT_ID); 619 if (CLF_ISMOD(cliptr)) { 620 dhcp_set_client_id(cliptr->varptr, cliptr->varlen); 621 } 622 623 bootlog("wanboot", BOOTLOG_INFO, "Starting DHCP configuration"); 624 625 (void) ipv4_setpromiscuous(B_TRUE); 626 if (dhcp() == 0) { 627 bootlog("wanboot", BOOTLOG_INFO, 628 "DHCP configuration succeeded"); 629 } else { 630 bootlog("wanboot", BOOTLOG_CRIT, 631 "DHCP configuration failed"); 632 ret = CLI_FAIL; 633 } 634 (void) ipv4_setpromiscuous(B_FALSE); 635 636 first_time = B_FALSE; 637 } 638 639 return (ret); 640 } 641 642 /* 643 * Invoke the socket test interpreter (for testing purposes only). 644 */ 645 /*ARGSUSED*/ 646 static int 647 cltest(cli_ent_t *cliptr, char *valstr, boolean_t out) 648 { 649 (void) ipv4_setpromiscuous(B_FALSE); 650 printf("\n"); 651 for (;;) { 652 printf(TEST_PROMPT); 653 if (editline(cmdbuf, sizeof (cmdbuf)) > 0) { 654 printf("\n"); 655 (void) st_interpret(cmdbuf); 656 } else { 657 prom_exit_to_mon(); 658 /* NOTREACHED */ 659 } 660 } 661 662 /* NOTREACHED */ 663 return (CLI_CONT); 664 } 665 666 /* 667 * Return the cliptr corresponding to the named variable. 668 */ 669 static cli_ent_t * 670 find_cli_ent(char *varstr) 671 { 672 cli_ent_t *cliptr; 673 674 for (cliptr = cli_list; cliptr < &cli_list[num_cli_ent]; ++cliptr) { 675 if (strcmp(varstr, cliptr->varname) == 0) { 676 return (cliptr); 677 } 678 } 679 680 return (NULL); 681 } 682 683 /* 684 * Evaluate the commands provided by the user (either as "-o" boot arguments 685 * or interactively at the boot interpreter). 686 */ 687 static int 688 cli_eval_buf(char *inbuf, int wanted) 689 { 690 char *p, *varstr, *end_varstr, *valstr, *end_valstr; 691 boolean_t assign; 692 cli_ent_t *cliptr; 693 694 for (p = inbuf; *p != '\0'; ) { 695 skipspace(p); 696 697 /* If nothing more on line, go get the next one */ 698 if (*p == '\0') { 699 break; 700 } else if (*p == ',') { /* orphan ',' ? */ 701 ++p; 702 continue; 703 } 704 705 /* Get ptrs to start & end of variable */ 706 varstr = p; 707 ++p; 708 skiptext(p); 709 end_varstr = p; 710 skipspace(p); 711 712 /* See if we're doing an assignment */ 713 valstr = NULL; 714 if (*p != '=') { /* nope, just printing */ 715 assign = B_FALSE; 716 } else { 717 assign = B_TRUE; 718 ++p; /* past '=' */ 719 skipspace(p); 720 721 /* Assigning something? (else clear variable) */ 722 if (*p != '\0' && *p != ',') { 723 /* Get ptrs to start & end of valstr */ 724 valstr = p; 725 ++p; 726 skiptext(p); 727 end_valstr = p; 728 skipspace(p); 729 } 730 } 731 732 /* Skip ',' delimiter if present */ 733 if (*p == ',') { 734 ++p; 735 } 736 737 /* Nul-terminate varstr and valstr (if appropriate) */ 738 *end_varstr = '\0'; 739 if (valstr != NULL) { 740 *end_valstr = '\0'; 741 } 742 743 if ((cliptr = find_cli_ent(varstr)) == NULL) { 744 printf("Unknown variable '%s'; ignored\n", varstr); 745 continue; 746 } 747 748 /* 749 * It's an error to specify a parameter which can only be a 750 * boot argument (and not a command) when not processing the 751 * boot arguments. 752 */ 753 if ((cliptr->flags & (CLF_CMD | CLF_ARG)) == CLF_ARG && 754 (wanted & CLF_ARG) == 0) { 755 printf("'%s' may only be specified as a " 756 "boot argument; ignored\n", varstr); 757 continue; 758 } 759 760 /* 761 * When doing an assignment, verify that it's not a command 762 * or argument name, and that it is permissible in the current 763 * context. An 'empty' assignment (var=) is treated the same 764 * as a null assignment (var=""). 765 * 766 * If processing the boot arguments, it is an error to not 767 * assign a value to a non-argument parameter. 768 */ 769 if (assign) { 770 if ((cliptr->flags & (CLF_CMD | CLF_ARG)) != 0) { 771 printf("'%s' is a command and cannot " 772 "be assigned\n", varstr); 773 return (CLI_FAIL); 774 } 775 if ((cliptr->flags & wanted) == 0) { 776 printf("'%s' cannot be assigned\n", varstr); 777 return (CLI_FAIL); 778 } 779 780 if (valstr == NULL) { 781 cliptr->varlen = 0; 782 CLF_MODVAL(cliptr); 783 continue; 784 } 785 } else if ((wanted & CLF_ARG) != 0 && 786 (cliptr->flags & (CLF_CMD | CLF_ARG)) == 0) { 787 printf("'%s' must be assigned when specified in " 788 " the boot arguments\n", varstr); 789 return (CLI_FAIL); 790 } 791 792 /* 793 * Pass 'wanted' to command-handling functions, in particular 794 * clprompt() and cllist(). 795 */ 796 if ((cliptr->flags & CLF_CMD) != 0) { 797 /* use uintptr_t to suppress the gcc warning */ 798 valstr = (char *)(uintptr_t)wanted; 799 } 800 801 /* 802 * Call the parameter's action function. 803 */ 804 switch (cliptr->action(cliptr, valstr, !assign)) { 805 case CLI_SET: 806 CLF_MODVAL(cliptr); 807 break; 808 case CLI_FAIL: 809 printf("Incorrect format: variable '%s' not set\n", 810 cliptr->varname); 811 break; 812 case CLI_EXIT: 813 return (CLI_EXIT); 814 case CLI_CONT: 815 if (!assign) { 816 printf("\n"); 817 } 818 break; 819 } 820 } 821 822 return (CLI_CONT); 823 } 824 825 static void 826 cli_interpret(int wanted) 827 { 828 printf("\n"); 829 do { 830 printf(PROMPT); 831 (void) editline(cmdbuf, sizeof (cmdbuf)); 832 printf("\n"); 833 834 } while (cli_eval_buf(cmdbuf, wanted) != CLI_EXIT); 835 } 836 837 #if defined(__sparcv9) 838 /* 839 * This routine queries the PROM to see what encryption keys exist. 840 */ 841 static void 842 get_prom_encr_keys() 843 { 844 cli_ent_t *cliptr; 845 char encr_key[WANBOOT_MAXKEYLEN]; 846 int keylen; 847 int status; 848 int ret; 849 850 /* 851 * At the top of the priority list, we have AES. 852 */ 853 ret = prom_get_security_key(WANBOOT_AES_128_KEY_NAME, encr_key, 854 WANBOOT_MAXKEYLEN, &keylen, &status); 855 if ((ret == 0) && (status == 0) && (keylen == AES_128_KEY_SIZE)) { 856 cliptr = find_cli_ent(BI_AES_KEY); 857 bcopy(encr_key, cliptr->varptr, AES_128_KEY_SIZE); 858 cliptr->varlen = AES_128_KEY_SIZE; 859 CLF_MODVAL(cliptr); 860 } 861 862 /* 863 * Next, 3DES. 864 */ 865 ret = prom_get_security_key(WANBOOT_DES3_KEY_NAME, encr_key, 866 WANBOOT_MAXKEYLEN, &keylen, &status); 867 if ((ret == 0) && (status == 0) && (keylen == DES3_KEY_SIZE)) { 868 cliptr = find_cli_ent(BI_3DES_KEY); 869 bcopy(encr_key, cliptr->varptr, DES3_KEY_SIZE); 870 cliptr->varlen = DES3_KEY_SIZE; 871 CLF_MODVAL(cliptr); 872 } 873 } 874 875 /* 876 * This routine queries the PROM to see what hashing keys exist. 877 */ 878 static void 879 get_prom_hash_keys() 880 { 881 cli_ent_t *cliptr; 882 char hash_key[WANBOOT_HMAC_KEY_SIZE]; 883 int keylen; 884 int status; 885 int ret; 886 887 /* 888 * The only supported key thus far is SHA1. 889 */ 890 ret = prom_get_security_key(WANBOOT_HMAC_SHA1_KEY_NAME, hash_key, 891 WANBOOT_HMAC_KEY_SIZE, &keylen, &status); 892 if ((ret == 0) && (status == 0) && (keylen == WANBOOT_HMAC_KEY_SIZE)) { 893 cliptr = find_cli_ent(BI_SHA1_KEY); 894 bcopy(hash_key, cliptr->varptr, WANBOOT_HMAC_KEY_SIZE); 895 cliptr->varlen = WANBOOT_HMAC_KEY_SIZE; 896 CLF_MODVAL(cliptr); 897 } 898 } 899 #endif /* defined(__sparcv9) */ 900 901 /* 902 * For the given parameter type(s), get values from bootinfo and cache in 903 * the local variables used by the "boot>" interpreter. 904 */ 905 static void 906 bootinfo_defaults(int which) 907 { 908 cli_ent_t *cliptr; 909 910 for (cliptr = cli_list; cliptr < &cli_list[num_cli_ent]; ++cliptr) { 911 if ((cliptr->flags & which) != 0 && !CLF_ISSET(cliptr)) { 912 size_t len = cliptr->varmax; 913 914 if (bootinfo_get(cliptr->varname, cliptr->varptr, 915 &len, NULL) == BI_E_SUCCESS) { 916 cliptr->varlen = len; 917 CLF_SETVAL(cliptr); 918 } 919 } 920 } 921 } 922 923 /* 924 * For the given parameter type(s), store values entered at the "boot>" 925 * interpreter back into bootinfo. 926 */ 927 static void 928 update_bootinfo(int which) 929 { 930 cli_ent_t *cliptr; 931 932 for (cliptr = cli_list; cliptr < &cli_list[num_cli_ent]; ++cliptr) { 933 if ((cliptr->flags & which) != 0 && CLF_ISMOD(cliptr)) { 934 (void) bootinfo_put(cliptr->varname, 935 cliptr->varptr, cliptr->varlen, 0); 936 } 937 } 938 } 939 940 /* 941 * Return the net-config-strategy: "dhcp", "manual" or "rarp" 942 */ 943 static char * 944 net_config_strategy(void) 945 { 946 static char ncs[8]; /* "dhcp" or "manual" */ 947 size_t len = sizeof (ncs); 948 949 if (ncs[0] == '\0' && 950 bootinfo_get(BI_NET_CONFIG_STRATEGY, ncs, &len, NULL) != 951 BI_E_SUCCESS) { 952 /* 953 * Support for old PROMs: create the net-config-strategy 954 * property under /chosen with an appropriate value. If we 955 * have a bootp-response (not interested in its value, just 956 * its presence) then we did DHCP; otherwise configuration 957 * is manual. 958 */ 959 if (bootinfo_get(BI_BOOTP_RESPONSE, NULL, NULL, 960 NULL) == BI_E_BUF2SMALL) { 961 (void) strcpy(ncs, "dhcp"); 962 } else { 963 (void) strcpy(ncs, "manual"); 964 } 965 (void) bootinfo_put(BI_NET_CONFIG_STRATEGY, ncs, strlen(ncs), 966 BI_R_CHOSEN); 967 968 bootlog("wanboot", BOOTLOG_INFO, 969 "Default net-config-strategy: %s", ncs); 970 } 971 972 return (ncs); 973 } 974 975 /* 976 * If there is no client-id property published in /chosen (by the PROM or the 977 * boot interpreter) provide a default client-id based on the MAC address of 978 * the client. 979 * As specified in RFC2132 (section 9.14), this is prefixed with a byte 980 * which specifies the ARP hardware type defined in RFC1700 (for Ethernet, 981 * this should be 1). 982 */ 983 static void 984 generate_default_clientid(void) 985 { 986 char clid[WB_MAX_CID_LEN]; 987 size_t len = sizeof (clid); 988 989 if (bootinfo_get(BI_CLIENT_ID, clid, &len, NULL) != BI_E_SUCCESS) { 990 len = mac_get_addr_len() + 1; /* include hwtype */ 991 992 if (len > sizeof (clid)) { 993 return; 994 } 995 996 clid[0] = mac_arp_type(mac_get_type()); 997 bcopy(mac_get_addr_buf(), &clid[1], len - 1); 998 999 (void) bootinfo_put(BI_CLIENT_ID, clid, len, 0); 1000 } 1001 } 1002 1003 /* 1004 * Determine the URL of the boot server from the 'file' parameter to OBP, 1005 * the SbootURI or BootFile DHCP options, or the 'bootserver' value entered 1006 * either as a "-o" argument or at the interpreter. 1007 */ 1008 static void 1009 determine_bootserver_url(void) 1010 { 1011 char bs[URL_MAX_STRLEN + 1]; 1012 size_t len; 1013 url_t url; 1014 1015 if (bootinfo_get(BI_BOOTSERVER, bs, &len, NULL) != BI_E_SUCCESS) { 1016 /* 1017 * If OBP has published a network-boot-file property in 1018 * /chosen (or there is a DHCP BootFile or SbootURI vendor 1019 * option) and it's a URL, construct the bootserver URL 1020 * from it. 1021 */ 1022 len = URL_MAX_STRLEN; 1023 if (bootinfo_get(BI_NETWORK_BOOT_FILE, bs, &len, NULL) != 1024 BI_E_SUCCESS) { 1025 len = URL_MAX_STRLEN; 1026 if (bootinfo_get(BI_BOOTFILE, bs, &len, NULL) != 1027 BI_E_SUCCESS) { 1028 return; 1029 } 1030 } 1031 if (url_parse(bs, &url) == URL_PARSE_SUCCESS) { 1032 (void) bootinfo_put(BI_BOOTSERVER, bs, len, 0); 1033 } 1034 } 1035 } 1036 1037 /* 1038 * Provide a classful subnet mask based on the client's IP address. 1039 */ 1040 static in_addr_t 1041 generate_classful_subnet(in_addr_t client_ipaddr) 1042 { 1043 struct in_addr subnetmask; 1044 char *netstr; 1045 1046 if (IN_CLASSA(client_ipaddr)) { 1047 subnetmask.s_addr = IN_CLASSA_NET; 1048 } else if (IN_CLASSB(client_ipaddr)) { 1049 subnetmask.s_addr = IN_CLASSB_NET; 1050 } else { 1051 subnetmask.s_addr = IN_CLASSC_NET; 1052 } 1053 1054 netstr = inet_ntoa(subnetmask); 1055 (void) bootinfo_put(BI_SUBNET_MASK, netstr, strlen(netstr) + 1, 0); 1056 1057 return (subnetmask.s_addr); 1058 } 1059 1060 /* 1061 * Informational output to the user (if interactive) or the bootlogger. 1062 */ 1063 static void 1064 info(const char *msg, boolean_t interactive) 1065 { 1066 if (interactive) { 1067 printf("%s\n", msg); 1068 } else { 1069 bootlog("wanboot", BOOTLOG_INFO, "%s", msg); 1070 } 1071 } 1072 1073 /* 1074 * Determine whether we have sufficient information to proceed with booting, 1075 * either for configuring the interface and downloading the bootconf file, 1076 * or for downloading the miniroot. 1077 */ 1078 static int 1079 config_incomplete(int why, boolean_t interactive) 1080 { 1081 boolean_t error = B_FALSE; 1082 char buf[URL_MAX_STRLEN + 1]; 1083 size_t len; 1084 char *urlstr; 1085 url_t u; 1086 struct hostent *hp; 1087 in_addr_t client_ipaddr, ipaddr, bsnet, pxnet; 1088 static in_addr_t subnetmask, clnet; 1089 static boolean_t have_router = B_FALSE; 1090 static boolean_t have_proxy = B_FALSE; 1091 boolean_t have_root_server = B_FALSE; 1092 boolean_t have_boot_logger = B_FALSE; 1093 in_addr_t rsnet, blnet; 1094 1095 /* 1096 * Note that 'have_router', 'have_proxy', 'subnetmask', and 'clnet' 1097 * are static, so that their values (gathered when checking the 1098 * interface configuration) may be used again when checking the boot 1099 * configuration. 1100 */ 1101 if (why == CLF_IF) { 1102 /* 1103 * A valid host IP address is an absolute requirement. 1104 */ 1105 len = sizeof (buf); 1106 if (bootinfo_get(BI_HOST_IP, buf, &len, NULL) == BI_E_SUCCESS) { 1107 if ((client_ipaddr = inet_addr(buf)) == (in_addr_t)-1) { 1108 info("host-ip invalid!", interactive); 1109 error = B_TRUE; 1110 } 1111 } else { 1112 info("host-ip not set!", interactive); 1113 error = B_TRUE; 1114 } 1115 1116 /* 1117 * If a subnet mask was provided, use it; otherwise infer it. 1118 */ 1119 len = sizeof (buf); 1120 if (bootinfo_get(BI_SUBNET_MASK, buf, &len, NULL) == 1121 BI_E_SUCCESS) { 1122 if ((subnetmask = inet_addr(buf)) == (in_addr_t)-1) { 1123 info("subnet-mask invalid!", interactive); 1124 error = B_TRUE; 1125 } 1126 } else { 1127 info("Defaulting to classful subnetting", interactive); 1128 1129 subnetmask = generate_classful_subnet(client_ipaddr); 1130 } 1131 clnet = client_ipaddr & subnetmask; 1132 1133 /* 1134 * A legal bootserver URL is also an absolute requirement. 1135 */ 1136 len = sizeof (buf); 1137 if (bootinfo_get(BI_BOOTSERVER, buf, &len, NULL) == 1138 BI_E_SUCCESS) { 1139 if (url_parse(buf, &u) != URL_PARSE_SUCCESS || 1140 u.https || 1141 (ipaddr = inet_addr(u.hport.hostname)) == 1142 (in_addr_t)-1) { 1143 info("bootserver not legal URL!", interactive); 1144 error = B_TRUE; 1145 } else { 1146 bsnet = ipaddr & subnetmask; 1147 } 1148 } else { 1149 info("bootserver not specified!", interactive); 1150 error = B_TRUE; 1151 } 1152 1153 /* 1154 * Is there a correctly-defined router? 1155 */ 1156 len = sizeof (buf); 1157 if (bootinfo_get(BI_ROUTER_IP, buf, &len, NULL) == 1158 BI_E_SUCCESS) { 1159 if ((ipaddr = inet_addr(buf)) == (in_addr_t)-1) { 1160 info("router-ip invalid!", interactive); 1161 error = B_TRUE; 1162 } else if (clnet != (ipaddr & subnetmask)) { 1163 info("router not on local subnet!", 1164 interactive); 1165 error = B_TRUE; 1166 } else { 1167 have_router = B_TRUE; 1168 } 1169 } 1170 1171 /* 1172 * Is there a correctly-defined proxy? 1173 */ 1174 len = sizeof (buf); 1175 if (bootinfo_get(BI_HTTP_PROXY, buf, &len, NULL) == 1176 BI_E_SUCCESS) { 1177 url_hport_t u; 1178 1179 if (url_parse_hostport(buf, &u, URL_DFLT_PROXY_PORT) != 1180 URL_PARSE_SUCCESS || 1181 (ipaddr = inet_addr(u.hostname)) == (in_addr_t)-1) { 1182 info("http-proxy port invalid!", interactive); 1183 error = B_TRUE; 1184 } else { 1185 /* 1186 * The proxy is only of use to us if it's on 1187 * our local subnet, or if a router has been 1188 * specified (which should hopefully allow us 1189 * to access the proxy). 1190 */ 1191 pxnet = ipaddr & subnetmask; 1192 have_proxy = (have_router || pxnet == clnet); 1193 } 1194 } 1195 1196 /* 1197 * If there is no router and no proxy (either on the local 1198 * subnet or reachable via a router), then the bootserver 1199 * URL must be on the local net. 1200 */ 1201 if (!error && !have_router && !have_proxy && bsnet != clnet) { 1202 info("bootserver URL not on local subnet", 1203 interactive); 1204 error = B_TRUE; 1205 } 1206 } else { 1207 /* 1208 * There must be a correctly-defined root_server URL. 1209 */ 1210 if ((urlstr = bootconf_get(&bc_handle, 1211 BC_ROOT_SERVER)) == NULL) { 1212 info("no root_server URL!", interactive); 1213 error = B_TRUE; 1214 } else if (url_parse(urlstr, &u) != URL_PARSE_SUCCESS) { 1215 info("root_server not legal URL!", interactive); 1216 error = B_TRUE; 1217 } else if ((hp = gethostbyname(u.hport.hostname)) == NULL) { 1218 info("cannot resolve root_server hostname!", 1219 interactive); 1220 error = B_TRUE; 1221 } else { 1222 rsnet = *(in_addr_t *)hp->h_addr & subnetmask; 1223 have_root_server = B_TRUE; 1224 } 1225 1226 /* 1227 * Is there a correctly-defined (non-empty) boot_logger URL? 1228 */ 1229 if ((urlstr = bootconf_get(&bc_handle, 1230 BC_BOOT_LOGGER)) != NULL) { 1231 if (url_parse(urlstr, &u) != URL_PARSE_SUCCESS) { 1232 info("boot_logger not legal URL!", interactive); 1233 error = B_TRUE; 1234 } else if ((hp = gethostbyname(u.hport.hostname)) == 1235 NULL) { 1236 info("cannot resolve boot_logger hostname!", 1237 interactive); 1238 error = B_TRUE; 1239 } else { 1240 blnet = *(in_addr_t *)hp->h_addr & subnetmask; 1241 have_boot_logger = B_TRUE; 1242 } 1243 } 1244 1245 /* 1246 * If there is no router and no proxy (either on the local 1247 * subnet or reachable via a router), then the root_server 1248 * URL (and the boot_logger URL if specified) must be on the 1249 * local net. 1250 */ 1251 if (!error && !have_router && !have_proxy) { 1252 if (have_root_server && rsnet != clnet) { 1253 info("root_server URL not on local subnet", 1254 interactive); 1255 error = B_TRUE; 1256 } 1257 if (have_boot_logger && blnet != clnet) { 1258 info("boot_logger URL not on local subnet", 1259 interactive); 1260 error = B_TRUE; 1261 } 1262 } 1263 } 1264 1265 return (error); 1266 } 1267 1268 /* 1269 * Actually setup our network interface with the values derived from the 1270 * PROM, DHCP or interactively from the user. 1271 */ 1272 static void 1273 setup_interface() 1274 { 1275 char str[MAXHOSTNAMELEN]; /* will accomodate an IP too */ 1276 size_t len; 1277 struct in_addr in_addr; 1278 1279 len = sizeof (str); 1280 if (bootinfo_get(BI_HOST_IP, str, &len, NULL) == BI_E_SUCCESS && 1281 (in_addr.s_addr = inet_addr(str)) != (in_addr_t)-1) { 1282 in_addr.s_addr = htonl(in_addr.s_addr); 1283 ipv4_setipaddr(&in_addr); 1284 } 1285 1286 len = sizeof (str); 1287 if (bootinfo_get(BI_SUBNET_MASK, str, &len, NULL) == BI_E_SUCCESS && 1288 (in_addr.s_addr = inet_addr(str)) != (in_addr_t)-1) { 1289 in_addr.s_addr = htonl(in_addr.s_addr); 1290 ipv4_setnetmask(&in_addr); 1291 } 1292 1293 len = sizeof (str); 1294 if (bootinfo_get(BI_ROUTER_IP, str, &len, NULL) == BI_E_SUCCESS && 1295 (in_addr.s_addr = inet_addr(str)) != (in_addr_t)-1) { 1296 in_addr.s_addr = htonl(in_addr.s_addr); 1297 ipv4_setdefaultrouter(&in_addr); 1298 (void) ipv4_route(IPV4_ADD_ROUTE, RT_DEFAULT, NULL, &in_addr); 1299 } 1300 1301 len = sizeof (str); 1302 if (bootinfo_get(BI_HOSTNAME, str, &len, NULL) == BI_E_SUCCESS) { 1303 (void) sethostname(str, len); 1304 } 1305 } 1306 1307 /* EXPORT DELETE END */ 1308 boolean_t 1309 wanboot_init_interface(char *boot_arguments) 1310 { 1311 /* EXPORT DELETE START */ 1312 boolean_t interactive; 1313 int which; 1314 1315 #if defined(__sparcv9) 1316 /* 1317 * Get the keys from PROM before we allow the user 1318 * to override them from the CLI. 1319 */ 1320 get_prom_encr_keys(); 1321 get_prom_hash_keys(); 1322 #endif /* defined(__sparcv9) */ 1323 1324 /* 1325 * If there is already a bootp-response property under 1326 * /chosen then the PROM must have done DHCP for us; 1327 * invoke dhcp() to 'bind' the interface. 1328 */ 1329 if (bootinfo_get(BI_BOOTP_RESPONSE, NULL, NULL, NULL) == 1330 BI_E_BUF2SMALL) { 1331 (void) cldhcp(NULL, NULL, 0); 1332 } 1333 1334 /* 1335 * Obtain default interface values from bootinfo. 1336 */ 1337 bootinfo_defaults(CLF_IF); 1338 1339 /* 1340 * Process the boot arguments (following the "-o" option). 1341 */ 1342 if (boot_arguments != NULL) { 1343 (void) cli_eval_buf(boot_arguments, 1344 (CLF_ARG | CLF_IF | CLF_BM)); 1345 } 1346 1347 /* 1348 * Stash away any interface/bootmisc parameter values we got 1349 * from either the PROM or the boot arguments. 1350 */ 1351 update_bootinfo(CLF_IF | CLF_BM); 1352 1353 /* 1354 * If we don't already have a value for bootserver, try to 1355 * deduce one. Refresh wbcli's idea of these values. 1356 */ 1357 determine_bootserver_url(); 1358 bootinfo_defaults(CLF_BM); 1359 1360 /* 1361 * Check that the information we have collected thus far is sufficient. 1362 */ 1363 interactive = args_specified_prompt; 1364 1365 if (interactive) { 1366 /* 1367 * Drop into the boot interpreter to allow the input 1368 * of keys, bootserver and bootmisc, and in the case 1369 * that net-config-strategy == "manual" the interface 1370 * parameters. 1371 */ 1372 which = CLF_BM | CLF_CMD; 1373 if (strcmp(net_config_strategy(), "manual") == 0) 1374 which |= CLF_IF; 1375 1376 do { 1377 cli_interpret(which); 1378 update_bootinfo(CLF_IF | CLF_BM); 1379 } while (config_incomplete(CLF_IF, interactive)); 1380 } else { 1381 /* 1382 * The user is not to be given the opportunity to 1383 * enter further values; fail. 1384 */ 1385 if (config_incomplete(CLF_IF, interactive)) { 1386 bootlog("wanboot", BOOTLOG_CRIT, 1387 "interface incorrectly configured"); 1388 return (B_FALSE); 1389 } 1390 } 1391 1392 /* 1393 * If a wanboot-enabled PROM hasn't processed client-id in 1394 * network-boot-arguments, or no value for client-id has been 1395 * specified to the boot interpreter, then provide a default 1396 * client-id based on our MAC address. 1397 */ 1398 generate_default_clientid(); 1399 1400 /* 1401 * If net-config-strategy == "manual" then we must setup 1402 * the interface now; if "dhcp" then it will already have 1403 * been setup. 1404 */ 1405 if (strcmp(net_config_strategy(), "manual") == 0) 1406 setup_interface(); 1407 /* EXPORT DELETE END */ 1408 return (B_TRUE); 1409 } 1410 1411 boolean_t 1412 wanboot_verify_config(void) 1413 { 1414 /* EXPORT DELETE START */ 1415 /* 1416 * Check that the wanboot.conf file defines a valid root_server 1417 * URL, and check that, if given, the boot_logger URL is valid. 1418 */ 1419 if (config_incomplete(0, B_FALSE)) { 1420 bootlog("wanboot", BOOTLOG_CRIT, 1421 "incomplete boot configuration"); 1422 return (B_FALSE); 1423 } 1424 /* EXPORT DELETE END */ 1425 return (B_TRUE); 1426 } 1427