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